{
  "name": "CliquezDanserz_Google_Ads_Optimization",
  "nodes": [
    {
      "parameters": {
        "triggerTimes": {
          "item": [
            {
              "hour": 6,
              "minute": 0
            }
          ]
        }
      },
      "name": "Schedule Daily",
      "type": "n8n-nodes-base.cron",
      "typeVersion": 1,
      "position": [
        200,
        -200
      ],
      "id": "b87f9be8-4af2-462f-95b6-35a0b04eb9d0"
    },
    {
      "parameters": {
        "values": {
          "string": [
            {
              "name": "customer_id",
              "value": "1234567890"
            }
          ],
          "number": [
            {
              "name": "client_value",
              "value": 50
            },
            {
              "name": "roi_low_threshold",
              "value": 1
            },
            {
              "name": "roi_high_threshold",
              "value": 3
            },
            {
              "name": "cost_conv_high",
              "value": 20
            },
            {
              "name": "cost_conv_low",
              "value": 10
            },
            {
              "name": "spent_no_conv_limit",
              "value": 10
            }
          ]
        },
        "options": {}
      },
      "name": "Set Constants",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3,
      "position": [
        400,
        -200
      ],
      "id": "9bd11c66-3460-4b50-b8fa-0685588b3b0f"
    },
    {
      "parameters": {
        "authentication": "predefinedCredentialType",
        "requestMethod": "POST",
        "url": "=https://googleads.googleapis.com/v14/customers/{{$json[\"customer_id\"]}}/googleAds:search",
        "options": {},
        "bodyParametersJson": "={\n  \"query\": \"SELECT campaign.id, campaign.name, ad_group.id, ad_group.name, metrics.impressions, metrics.clicks, metrics.ctr, metrics.average_cpc, metrics.cost_micros, metrics.conversions, metrics.cost_per_conversion FROM ad_group WHERE segments.date DURING YESTERDAY\"\n}"
      },
      "name": "Get Ad Group Stats",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 1,
      "position": [
        600,
        -200
      ],
      "id": "32d32a8f-9c2b-4b8e-a0ba-87d521c5c537",
      "credentials": {
        "googleAdsOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "functionCode": "const raw = $json.results || [];\nconst clientValue = $json[\"client_value\"] || 50;\n\nconst output = [];\nfor (const row of raw) {\n    const campaignId = row.campaign?.id || row[\"campaign.id\"];\n    const campaignName = row.campaign?.name || row[\"campaign.name\"];\n    const adGroupId = row.adGroup?.id || row[\"ad_group.id\"];\n    const adGroupName = row.adGroup?.name || row[\"ad_group.name\"];\n    const impressions = Number(row.metrics?.impressions || row[\"metrics.impressions\"] || 0);\n    const clicks = Number(row.metrics?.clicks || row[\"metrics.clicks\"] || 0);\n    const ctr = Number(row.metrics?.ctr || row[\"metrics.ctr\"] || 0) * 100;\n    const averageCpcMicros = Number(row.metrics?.averageCpc || row[\"metrics.average_cpc\"] || 0);\n    const costMicros = Number(row.metrics?.costMicros || row[\"metrics.cost_micros\"] || 0);\n    const conversions = Number(row.metrics?.conversions || row[\"metrics.conversions\"] || 0);\n    const costPerConvMicros = Number(row.metrics?.costPerConversion || row[\"metrics.cost_per_conversion\"] || 0);\n\n    const costEur = costMicros / 1e6;\n    const cpcEur = averageCpcMicros / 1e6;\n    const costPerConvEur = costPerConvMicros > 0 ? costPerConvMicros / 1e6 : 0;\n\n    let roi = 0;\n    if (costEur > 0) {\n        roi = ((conversions * clientValue) - costEur) / costEur;\n    }\n\n    output.push({\n        campaign_id: campaignId,\n        campaign_name: campaignName,\n        ad_group_id: adGroupId,\n        ad_group_name: adGroupName,\n        impressions,\n        clicks,\n        ctr: parseFloat(ctr.toFixed(2)),\n        cost_eur: parseFloat(costEur.toFixed(2)),\n        cpc_eur: parseFloat(cpcEur.toFixed(2)),\n        conversions,\n        cost_per_conv_eur: parseFloat(costPerConvEur.toFixed(2)),\n        roi: parseFloat(roi.toFixed(2))\n    });\n}\nreturn output;\n"
      },
      "name": "Process Ad Group Stats",
      "type": "n8n-nodes-base.function",
      "typeVersion": 1,
      "position": [
        800,
        -200
      ],
      "id": "7d1bf40b-d691-4a05-96e9-a4fa08e3d5d9"
    },
    {
      "parameters": {
        "functionCode": "// Determine needed adjustments\nconst items = $input.all();\nconst roiLow = $json[\"roi_low_threshold\"] || 1;\nconst roiHigh = $json[\"roi_high_threshold\"] || 3;\nconst costConvHigh = $json[\"cost_conv_high\"] || 20;\nconst costConvLow = $json[\"cost_conv_low\"] || 10;\n\nlet output = [];\nfor (const item of items) {\n    const j = item.json;\n    let action = \"none\";\n\n    if (j.roi < roiLow || j.cost_per_conv_eur > costConvHigh) {\n        action = \"decrease\";\n    } else if (j.roi > roiHigh || (j.cost_per_conv_eur > 0 && j.cost_per_conv_eur < costConvLow)) {\n        action = \"increase\";\n    }\n\n    output.push({\n        json: {\n            ...j,\n            action\n        }\n    });\n}\nreturn output;\n"
      },
      "name": "Decide Bids",
      "type": "n8n-nodes-base.function",
      "typeVersion": 1,
      "position": [
        1000,
        -200
      ],
      "id": "b3052bcb-68bf-4986-a21e-fcfb4359b095"
    },
    {
      "parameters": {
        "propertyName": "action",
        "switch": [
          {
            "value": "decrease"
          },
          {
            "value": "increase"
          }
        ]
      },
      "name": "Split Increase/Decrease",
      "type": "n8n-nodes-base.switch",
      "typeVersion": 1,
      "position": [
        1200,
        -200
      ],
      "id": "f4be5c66-9709-4d1f-918b-cb82db217c99"
    },
    {
      "parameters": {
        "authentication": "predefinedCredentialType",
        "requestMethod": "POST",
        "url": "=https://googleads.googleapis.com/v14/customers/{{$json[\"customer_id\"]}}/adGroups:mutate",
        "options": {},
        "bodyParametersJson": "={\n  \"operations\": [\n    {\n      \"update\": {\n        \"resourceName\": \"customers/{{$json[\"customer_id\"]}}/adGroups/{{$json[\"ad_group_id\"]}}\",\n        \"cpcBidMicros\": {{ Math.floor($json[\"cpc_eur\"] * 1000000 * 0.9) }}\n      },\n      \"updateMask\": \"cpcBidMicros\"\n    }\n  ]\n}"
      },
      "name": "Apply Decrease Bid",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 1,
      "position": [
        1400,
        -280
      ],
      "id": "585b6466-a073-447c-b6b2-8aa2bd398d02",
      "credentials": {
        "googleAdsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "continueOnFail": true
    },
    {
      "parameters": {
        "authentication": "predefinedCredentialType",
        "requestMethod": "POST",
        "url": "=https://googleads.googleapis.com/v14/customers/{{$json[\"customer_id\"]}}/adGroups:mutate",
        "options": {},
        "bodyParametersJson": "={\n  \"operations\": [\n    {\n      \"update\": {\n        \"resourceName\": \"customers/{{$json[\"customer_id\"]}}/adGroups/{{$json[\"ad_group_id\"]}}\",\n        \"cpcBidMicros\": {{ Math.floor($json[\"cpc_eur\"] * 1000000 * 1.15) }}\n      },\n      \"updateMask\": \"cpcBidMicros\"\n    }\n  ]\n}"
      },
      "name": "Apply Increase Bid",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 1,
      "position": [
        1400,
        -120
      ],
      "id": "57cb1f3d-7c84-4c5e-b3ac-9719a4b5c20c",
      "credentials": {
        "googleAdsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "continueOnFail": true
    },
    {
      "parameters": {
        "resource": "task",
        "operation": "create",
        "workspace": "",
        "name": "={{ \"[Google Ads] \" + ($json[\"action\"] === \"increase\" ? \"Augmentation\" : \"Diminution\") + \" ench\u00e8re\" }}",
        "additionalFields": {
          "notes": "=Date: {{ new Date().toISOString().split('T')[0] }}\nCampagne: {{ $json[\"campaign_name\"] }}\nGroupe d'annonces: {{ $json[\"ad_group_name\"] }}\nAction: {{ $json[\"action\"] === \"increase\" ? \"+15%\" : \"-10%\" }}\nROI: {{ $json[\"roi\"] }}",
          "projects": [
            "Cliquezdansez"
          ]
        }
      },
      "name": "Log Bid Change to Asana",
      "type": "n8n-nodes-base.asana",
      "typeVersion": 1,
      "position": [
        1600,
        -200
      ],
      "id": "584bcf07-0577-4d70-b6f2-efda5ffbc8af",
      "credentials": {
        "asanaApi": {
          "name": "<your credential>"
        }
      },
      "continueOnFail": true
    },
    {
      "parameters": {
        "functionCode": "const items = $input.all();\nreturn items.filter(item => item.json.action !== 'none');"
      },
      "name": "Filter No-Action",
      "type": "n8n-nodes-base.function",
      "typeVersion": 1,
      "position": [
        1100,
        -200
      ],
      "id": "edda2dbb-6b31-43f5-bc42-132f49ce30c8"
    },
    {
      "parameters": {
        "authentication": "predefinedCredentialType",
        "requestMethod": "POST",
        "url": "=https://googleads.googleapis.com/v14/customers/{{$json[\"customer_id\"]}}/googleAds:search",
        "options": {},
        "bodyParametersJson": "={\n  \"query\": \"SELECT campaign.id, campaign.name, ad_group.id, ad_group.name, ad_group_criterion.keyword.text, metrics.cost_micros, metrics.conversions FROM ad_group_criterion WHERE segments.date DURING LAST_30_DAYS AND metrics.conversions = 0 AND metrics.cost_micros > {{ $json[\"spent_no_conv_limit\"] * 1000000 }} AND ad_group_criterion.type = 'KEYWORD'\"\n}"
      },
      "name": "Find Inefficient Keywords",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 1,
      "position": [
        400,
        100
      ],
      "id": "bd6caaf8-4525-4423-86ca-0b9c555036f6",
      "credentials": {
        "googleAdsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "continueOnFail": true
    },
    {
      "parameters": {
        "functionCode": "const raw = $json.results || [];\nlet output = [];\nfor (const row of raw) {\n    const campaignId = row.campaign?.id || row[\"campaign.id\"];\n    const campaignName = row.campaign?.name || row[\"campaign.name\"];\n    const adGroupId = row.adGroup?.id || row[\"ad_group.id\"];\n    const adGroupName = row.adGroup?.name || row[\"ad_group.name\"];\n    const keywordText = row.adGroupCriterion?.keyword?.text || row[\"ad_group_criterion.keyword.text\"];\n    output.push({ json: {\n      campaign_id: campaignId,\n      campaign_name: campaignName,\n      ad_group_id: adGroupId,\n      ad_group_name: adGroupName,\n      keyword: keywordText\n    }});\n}\nreturn output;\n"
      },
      "name": "Process Keywords",
      "type": "n8n-nodes-base.function",
      "typeVersion": 1,
      "position": [
        600,
        100
      ],
      "id": "65693d57-60e7-4c4d-8d43-574866ecf0f2"
    },
    {
      "parameters": {
        "authentication": "predefinedCredentialType",
        "requestMethod": "POST",
        "url": "=https://googleads.googleapis.com/v14/customers/{{$json[\"customer_id\"]}}/campaignCriteria:mutate",
        "options": {},
        "bodyParametersJson": "={\n  \"operations\": [\n    {\n      \"create\": {\n        \"campaign\": \"customers/{{$json[\"customer_id\"]}}/campaigns/{{$json[\"campaign_id\"]}}\",\n        \"negative\": true,\n        \"keyword\": {\n          \"text\": \"{{$json[\"keyword\"]}}\",\n          \"matchType\": \"BROAD\"\n        }\n      }\n    }\n  ]\n}"
      },
      "name": "Add Negative Keyword",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 1,
      "position": [
        800,
        100
      ],
      "id": "8aa49ed4-3c56-4d97-84c4-643c4e2893cd",
      "credentials": {
        "googleAdsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "continueOnFail": true
    },
    {
      "parameters": {
        "resource": "task",
        "operation": "create",
        "workspace": "",
        "name": "={{ \"[Google Ads] Mot-cl\u00e9 n\u00e9gatif ajout\u00e9: \" + $json[\"keyword\"] }}",
        "additionalFields": {
          "notes": "=Date: {{ new Date().toISOString().split('T')[0] }}\nCampagne: {{ $json[\"campaign_name\"] }}\nMot-cl\u00e9: {{ $json[\"keyword\"] }}\n\nCar trop de d\u00e9penses sans conversions.",
          "projects": [
            "Cliquezdansez"
          ]
        }
      },
      "name": "Log Negative Keyword to Asana",
      "type": "n8n-nodes-base.asana",
      "typeVersion": 1,
      "position": [
        1000,
        100
      ],
      "id": "6f7afec8-a4b7-4748-8ce3-7a1e3bc31949",
      "credentials": {
        "asanaApi": {
          "name": "<your credential>"
        }
      },
      "continueOnFail": true
    },
    {
      "parameters": {
        "functionCode": "const items = $input.all();\nlet totalConv = 0;\nlet totalCost = 0;\nfor (const it of items) {\n    const conv = it.json.conversions || 0;\n    const cost = it.json.cost_eur || 0;\n    totalConv += conv;\n    totalCost += cost;\n}\nlet roiGlobal = 0;\nif (totalCost > 0) {\n    roiGlobal = ((totalConv * 50) - totalCost) / totalCost;\n}\n// Simple alert if ROI < 0.5\nif (roiGlobal < 0.5 && totalCost > 0) {\n    return [{ json: { message: `Alerte: ROI global = ${roiGlobal.toFixed(2)} (< 0.5)` } }];\n}\nreturn [];\n"
      },
      "name": "Check Global ROI",
      "type": "n8n-nodes-base.function",
      "typeVersion": 1,
      "position": [
        1000,
        -80
      ],
      "id": "3fa46b59-c49a-4ba2-a1b7-a13cece8bd92"
    },
    {
      "parameters": {
        "chatId": "",
        "text": "={{ $json[\"message\"] }}",
        "additionalFields": {
          "disable_notification": false
        }
      },
      "name": "Send Telegram Alert",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1,
      "position": [
        1200,
        -80
      ],
      "id": "a852281e-7c44-4a84-a71c-a2fa6a8a3639",
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "continueOnFail": true
    }
  ],
  "connections": {
    "Schedule Daily": {
      "main": [
        [
          {
            "node": "Set Constants",
            "type": "main",
            "index": 0
          },
          {
            "node": "Find Inefficient Keywords",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Constants": {
      "main": [
        [
          {
            "node": "Get Ad Group Stats",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Ad Group Stats": {
      "main": [
        [
          {
            "node": "Process Ad Group Stats",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Process Ad Group Stats": {
      "main": [
        [
          {
            "node": "Check Global ROI",
            "type": "main",
            "index": 0
          },
          {
            "node": "Decide Bids",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Decide Bids": {
      "main": [
        [
          {
            "node": "Filter No-Action",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter No-Action": {
      "main": [
        [
          {
            "node": "Split Increase/Decrease",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split Increase/Decrease": {
      "main": [
        [
          {
            "node": "Apply Decrease Bid",
            "type": "main",
            "index": 0
          },
          {
            "node": "Log Bid Change to Asana",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Apply Increase Bid",
            "type": "main",
            "index": 0
          },
          {
            "node": "Log Bid Change to Asana",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Apply Decrease Bid": {
      "main": [
        []
      ]
    },
    "Apply Increase Bid": {
      "main": [
        []
      ]
    },
    "Log Bid Change to Asana": {
      "main": [
        []
      ]
    },
    "Find Inefficient Keywords": {
      "main": [
        [
          {
            "node": "Process Keywords",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Process Keywords": {
      "main": [
        [
          {
            "node": "Add Negative Keyword",
            "type": "main",
            "index": 0
          },
          {
            "node": "Log Negative Keyword to Asana",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Add Negative Keyword": {
      "main": [
        []
      ]
    },
    "Log Negative Keyword to Asana": {
      "main": [
        []
      ]
    },
    "Check Global ROI": {
      "main": [
        [
          {
            "node": "Send Telegram Alert",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "id": "workflow_google_ads_final",
  "tags": []
}