{
  "name": "CRM Outreach Engine v2 (API-Based)",
  "nodes": [
    {
      "parameters": {
        "content": "## CRM Outreach Engine v2\n\n**Substitui o workflow MySQL legado por chamadas \u00e0 API do CRM.**\n\n### M\u00f3dulos:\n- **M\u00f3dulo 1 - Importa\u00e7\u00e3o**: Schedule 8h + Manual \u2192 Apify \u2192 POST /customers/import\n- **M\u00f3dulo 2 - Segmenta\u00e7\u00e3o**: Schedule a cada 2h \u2192 POST /outreach/segment\n- **M\u00f3dulo 3 - Envio**: Schedule 9h, 14h, 18h \u2192 POST /outreach/send\n- **Error Handler**: Captura erros globais\n\n### Autentica\u00e7\u00e3o (API Key est\u00e1tica \u2014 N\u00c3O usa JWT):\nCrie uma credencial **Header Auth** no n8n com nome exato `CRM API Key`:\n- **Header Name**: `X-Api-Key`\n- **Header Value**: (ver secret DIAX_SERVICE_API_KEY no GitHub / AWS SM)\n\n> \u26a0\ufe0f N\u00c3O use `Authorization: Bearer <jwt>`. JWTs expiram em 60min e quebram o agendamento.\n> A API aceita `X-Api-Key` com uma chave est\u00e1tica para automa\u00e7\u00f5es M2M.\n\n**API Base:** `https://api.alexandrequeiroz.com.br/api/v1`",
        "height": 320,
        "width": 460,
        "color": 5
      },
      "id": "sticky-readme",
      "name": "Leia-me",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -480,
        -120
      ]
    },
    {
      "parameters": {
        "content": "### M\u00f3dulo 1: Importa\u00e7\u00e3o\nTrigger: Di\u00e1rio \u00e0s 8h + Manual\n1. Busca config (URL do Apify dataset)\n2. Baixa dados do Apify\n3. Normaliza para BulkImportRequest\n4. POST /customers/import",
        "height": 180,
        "width": 280,
        "color": 6
      },
      "id": "sticky-mod1",
      "name": "Nota - M\u00f3dulo 1",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -480,
        240
      ]
    },
    {
      "parameters": {
        "content": "### M\u00f3dulo 2: Segmenta\u00e7\u00e3o\nTrigger: A cada 2 horas\nSegmenta leads HOT/WARM/COLD via\nPOST /outreach/segment",
        "height": 140,
        "width": 280,
        "color": 4
      },
      "id": "sticky-mod2",
      "name": "Nota - M\u00f3dulo 2",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -480,
        460
      ]
    },
    {
      "parameters": {
        "content": "### M\u00f3dulo 3: Envio de Campanha\nTrigger: 9h, 14h e 18h\nCria e enfileira campanha via\nPOST /outreach/send",
        "height": 140,
        "width": 280,
        "color": 3
      },
      "id": "sticky-mod3",
      "name": "Nota - M\u00f3dulo 3",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -480,
        640
      ]
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 8 * * *"
            }
          ]
        }
      },
      "id": "trigger-import-schedule",
      "name": "Schedule - Importa\u00e7\u00e3o (8h)",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.1,
      "position": [
        60,
        280
      ]
    },
    {
      "parameters": {},
      "id": "trigger-import-manual",
      "name": "Manual - Importa\u00e7\u00e3o",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        60,
        420
      ]
    },
    {
      "parameters": {
        "method": "GET",
        "url": "https://api.alexandrequeiroz.com.br/api/v1/outreach/config",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "options": {}
      },
      "id": "http-get-config",
      "name": "GET /outreach/config",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        320,
        350
      ],
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "method": "GET",
        "url": "={{ $json.apifyDatasetUrl }}",
        "options": {
          "response": {
            "response": {
              "responseFormat": "json"
            }
          }
        }
      },
      "id": "http-get-apify",
      "name": "GET Apify Dataset",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        580,
        350
      ]
    },
    {
      "parameters": {
        "jsCode": "// Normaliza os itens do Apify para o formato BulkImportRequest do CRM\nconst apifyItems = $input.all();\n\nfunction extractWhatsApp(phone) {\n  if (!phone) return null;\n  // Tenta extrair de URLs wa.me/5511...\n  const waMatch = phone.match(/wa\\.me\\/(\\d+)/);\n  if (waMatch) return '+' + waMatch[1];\n  // Limpa o n\u00famero e assume que \u00e9 WhatsApp se tiver DDD v\u00e1lido\n  const cleaned = phone.replace(/[^0-9+]/g, '');\n  if (cleaned.length >= 10) return cleaned.startsWith('+') ? cleaned : '+55' + cleaned.replace(/^0/, '');\n  return null;\n}\n\nfunction cleanPhone(phone) {\n  if (!phone) return null;\n  const cleaned = phone.replace(/[^0-9+]/g, '');\n  if (cleaned.length < 8) return null;\n  return cleaned.startsWith('+') ? cleaned : '+55' + cleaned.replace(/^0/, '');\n}\n\nconst customers = apifyItems.map(item => {\n  const d = item.json;\n  const email = Array.isArray(d.emails) && d.emails.length > 0 ? d.emails[0] : null;\n  const phone = cleanPhone(d.phone);\n  const whatsApp = extractWhatsApp(d.phone) || phone;\n\n  // Monta tags: cidade + categoria\n  const tagParts = [];\n  if (d.city) tagParts.push(d.city.toLowerCase().replace(/\\s+/g, '-'));\n  if (d.categoryName) tagParts.push(d.categoryName.toLowerCase().replace(/\\s+/g, '-'));\n  if (d.website) tagParts.push('tem-site');\n  else tagParts.push('sem-site');\n  tagParts.push('google-maps');\n\n  // Monta notes com contexto do lead\n  const notesParts = [];\n  if (d.address) notesParts.push(`Endere\u00e7o: ${d.address}`);\n  if (d.totalScore) notesParts.push(`Avalia\u00e7\u00e3o Google: ${d.totalScore}`);\n  if (d.reviewsCount) notesParts.push(`Reviews: ${d.reviewsCount}`);\n  if (d.website) notesParts.push(`Site: ${d.website}`);\n\n  return {\n    name: d.title || d.name || 'Sem nome',\n    email: email,\n    phone: phone,\n    whatsApp: whatsApp,\n    companyName: d.title || d.name || null,\n    website: d.website || null,\n    tags: tagParts.join(','),\n    notes: notesParts.join(' | ') || null\n  };\n}).filter(c => c.name && c.name !== 'Sem nome' && (c.phone || c.email));\n\nif (customers.length === 0) {\n  return [{ json: { skipped: true, reason: 'Nenhum lead v\u00e1lido encontrado no dataset Apify' } }];\n}\n\nreturn [{\n  json: {\n    customers: customers,\n    source: 11\n  }\n}];"
      },
      "id": "code-normalize-apify",
      "name": "Normalizar Dados Apify",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        840,
        350
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.alexandrequeiroz.com.br/api/v1/customers/import",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "contentType": "json",
        "body": "={{ JSON.stringify($json) }}",
        "options": {}
      },
      "id": "http-post-import",
      "name": "POST /customers/import",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1100,
        350
      ],
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "hours",
              "hoursInterval": 2
            }
          ]
        }
      },
      "id": "trigger-segmentation",
      "name": "Schedule - Segmenta\u00e7\u00e3o (2h)",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.1,
      "position": [
        60,
        560
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.alexandrequeiroz.com.br/api/v1/outreach/segment",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "options": {}
      },
      "id": "http-post-segment",
      "name": "POST /outreach/segment",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        320,
        560
      ],
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const result = $input.first().json;\nconsole.log(`[Segmenta\u00e7\u00e3o] Total: ${result.totalProcessed} | HOT: ${result.hotCount} | WARM: ${result.warmCount} | COLD: ${result.coldCount}`);\nreturn [{ json: { success: true, result } }];"
      },
      "id": "code-log-segmentation",
      "name": "Log Segmenta\u00e7\u00e3o",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        580,
        560
      ]
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 9,14,18 * * *"
            }
          ]
        }
      },
      "id": "trigger-send",
      "name": "Schedule - Envio (9h, 14h, 18h)",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.1,
      "position": [
        60,
        740
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.alexandrequeiroz.com.br/api/v1/outreach/send",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "options": {}
      },
      "id": "http-post-send",
      "name": "POST /outreach/send",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        320,
        740
      ],
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const result = $input.first().json;\nconst campaignId = result.campaignId || 'N/A';\nconst queued = result.queuedCount ?? 0;\nconst skipped = result.skippedCount ?? 0;\nconsole.log(`[Envio de Campanha] CampaignId: ${campaignId} | Enfileirados: ${queued} | Pulados: ${skipped}`);\nif (result.skippedReasons && result.skippedReasons.length > 0) {\n  console.log(`[Envio de Campanha] Motivos pulados: ${result.skippedReasons.join('; ')}`);\n}\nreturn [{ json: { success: true, campaignId, queued, skipped, result } }];"
      },
      "id": "code-log-send",
      "name": "Log Envio",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        580,
        740
      ]
    },
    {
      "parameters": {},
      "id": "trigger-error",
      "name": "Error Trigger",
      "type": "n8n-nodes-base.errorTrigger",
      "typeVersion": 1,
      "position": [
        60,
        940
      ]
    },
    {
      "parameters": {
        "jsCode": "const error = $input.first().json;\nconst workflow = error.workflow?.name || 'Desconhecido';\nconst node = error.execution?.lastNodeExecuted || 'Desconhecido';\nconst errorMsg = error.execution?.error?.message || JSON.stringify(error);\nconst execId = error.execution?.id || 'N/A';\n\nconsole.error(`[ERRO] Workflow: ${workflow}`);\nconsole.error(`[ERRO] N\u00f3: ${node}`);\nconsole.error(`[ERRO] Execu\u00e7\u00e3o ID: ${execId}`);\nconsole.error(`[ERRO] Mensagem: ${errorMsg}`);\n\n// Retorna resumo do erro para poss\u00edvel integra\u00e7\u00e3o futura (ex: Slack, email)\nreturn [{\n  json: {\n    alertType: 'workflow_error',\n    workflow,\n    node,\n    execId,\n    errorMsg,\n    timestamp: new Date().toISOString()\n  }\n}];"
      },
      "id": "code-error-handler",
      "name": "Tratar Erro",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        320,
        940
      ]
    }
  ],
  "connections": {
    "Schedule - Importa\u00e7\u00e3o (8h)": {
      "main": [
        [
          {
            "node": "GET /outreach/config",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Manual - Importa\u00e7\u00e3o": {
      "main": [
        [
          {
            "node": "GET /outreach/config",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GET /outreach/config": {
      "main": [
        [
          {
            "node": "GET Apify Dataset",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GET Apify Dataset": {
      "main": [
        [
          {
            "node": "Normalizar Dados Apify",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalizar Dados Apify": {
      "main": [
        [
          {
            "node": "POST /customers/import",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule - Segmenta\u00e7\u00e3o (2h)": {
      "main": [
        [
          {
            "node": "POST /outreach/segment",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "POST /outreach/segment": {
      "main": [
        [
          {
            "node": "Log Segmenta\u00e7\u00e3o",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule - Envio (9h, 14h, 18h)": {
      "main": [
        [
          {
            "node": "POST /outreach/send",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "POST /outreach/send": {
      "main": [
        [
          {
            "node": "Log Envio",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Error Trigger": {
      "main": [
        [
          {
            "node": "Tratar Erro",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1",
    "saveManualExecutions": true,
    "saveDataErrorExecution": "all",
    "saveDataSuccessExecution": "last",
    "callerPolicy": "workflowsFromSameOwner",
    "errorWorkflow": ""
  },
  "meta": {
    "templateCredsSetupCompleted": false,
    "notes": "ACAO NECESSARIA: Configurar credencial 'CRM API Key' no n8n com Header=X-Api-Key e Value=<DIAX_SERVICE_API_KEY>"
  }
}