{
  "name": "Logixb2b — Apollo y Cognism al CRM",
  "meta": {
    "templateCredsSetupCompleted": false,
    "description": "Manual Trigger → Config proveedor → Fetch/Test payload persona → Normalizar + fitScore → IF fit válido → Upsert Lead CRM → Append Sheets → Slack resumen. Europe/Madrid. Placeholders de credenciales."
  },
  "settings": {
    "executionOrder": "v1",
    "timezone": "Europe/Madrid"
  },
  "active": false,
  "pinData": {},
  "tags": [
    {
      "name": "Logixb2b"
    },
    {
      "name": "recursos"
    },
    {
      "name": "prospeccion"
    }
  ],
  "nodes": [
    {
      "id": "sticky-instrucciones-logix",
      "name": "INSTRUCCIONES (conectar + VPS)",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -480,
        40
      ],
      "parameters": {
        "content": "## INSTRUCCIONES — Apollo y Cognism al CRM\n\n**Recurso:** `/recursos/apollo-cognism-fit-prospeccion-n8n`\n**Guía:** /apollo-io-vs-cognism-comparativa-prospeccion-b2b\n**Zona:** Europe/Madrid · `active: false` al importar (tú lo activas).\n\n### 1) Importar\n1. n8n → ⋯ → **Import from File** → este JSON.\n2. El flujo llega **inactive**. No pegues secretos en el lienzo ni en el archivo.\n\n### 2) Credenciales y configuración por nodo\n- **Manual Trigger fit** (`manual`): sin credencial. Úsalo para pruebas; en producción sustituye por Webhook o Schedule si quieres automatismo.\n- **Config fit Apollo/Cognism** (`Set` Config): **no lleva credencial**. Aquí van URLs/placeholders (`REEMPLAZA_*`, `example.com`, `TU-CRM`). Sustituye todos antes de Active.\n- **Payload de prueba (sample)** (`Code`): sin credencial. Revisa límites/umbrales en el JS; no hardcodees secretos.\n- **Fetch personas (Apollo/Cognism)** (`HTTP Request` proveedor): Credentials → **Header Auth** / API Key del proveedor (Apollo, Cognism, Clay…). Sustituye URL y query en Config. No scrapees LinkedIn.\n- **Split personas** (`Split Out`): sin credencial. Confirma el campo a expandir (array de personas/items).\n- **Normalizar + fitScore** (`Code`): sin credencial. Revisa límites/umbrales en el JS; no hardcodees secretos.\n- **IF fitScore ≥ umbral** (`IF`): sin credencial. Comprueba la condición (email, umbral, over_limit, etc.) con datos reales de prueba.\n- **Upsert Lead CRM (HTTP)** (`HTTP Request` proveedor): Credentials → **Header Auth** / API Key del proveedor (Apollo, Cognism, Clay…). Sustituye URL y query en Config. No scrapees LinkedIn.\n- **Append log fit Sheets** (`Google Sheets`): Credentials → **Google Sheets OAuth2**. Sustituye Spreadsheet ID / Sheet name. Comparte la hoja con la cuenta OAuth.\n- **Slack resumen fit** (`HTTP Request` → Slack/Teams): crea Incoming Webhook en Slack (o Adaptive Card URL en Teams). Pega la URL en **Config** o en este nodo. Auth: none (la URL ya es secreta).\n\n### 3) Orden recomendado de puesta en marcha\n1. Rellena el nodo **Config** (URLs reales, umbrales, webhooks de Slack).\n2. Crea Credentials en n8n (candado de cada nodo HTTP / Sheets / OpenAI) y **enlázalas**.\n3. Ejecuta **una vez** (Manual / Test URL del Webhook / \"Execute workflow\").\n4. Verifica CRM + Slack/Sheets (y que la IA no escriba en producción sin HITL).\n5. Solo entonces: interruptor **Active**.\n\n### 4) Producción 24/7 en VPS (UE)\nImportar en localhost no basta: webhooks y cron necesitan n8n encendido.\n\n1. VPS en UE (Hetzner/OVH/IONOS…) + Docker Compose + **Postgres** + HTTPS (Caddy/Nginx).\n2. Variables mínimas: `GENERIC_TIMEZONE=Europe/Madrid`, `WEBHOOK_URL=https://n8n.tudominio.com/`, `N8N_ENCRYPTION_KEY`, `N8N_HOST` / `N8N_PROTOCOL=https`.\n3. Firewall: solo 80/443 (+ SSH). Backups del volumen Postgres + export de workflows.\n4. Importa este JSON en la instancia de producción, vuelve a crear Credentials (no se copian solas), actualiza URLs del Config al dominio real.\n5. Pon **Active**. Prueba el Webhook de producción o espera el próximo cron.\n6. Alternativa: **n8n Cloud** (mismo JSON; Credentials en la UI Cloud).\n\n### 5) RGPD + EU AI Act (resumen)\nSi hay nodos IA sois **deployers** (Reglamento UE 2024/1689). Art. 4 alfabetización. **HITL** antes de outreach, scores masivos o pausas de cuenta. Minimiza PII en Slack/logs. Informativo — no es asesoramiento legal. /uso-responsable-de-ia-y-transparencia\n",
        "height": 1500,
        "width": 580,
        "color": 4
      }
    },
    {
      "id": "fit-trigger",
      "name": "Manual Trigger fit",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        0,
        340
      ],
      "parameters": {}
    },
    {
      "id": "fit-config",
      "name": "Config fit Apollo/Cognism",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        260,
        340
      ],
      "parameters": {
        "mode": "manual",
        "duplicateItem": false,
        "assignments": {
          "assignments": [
            {
              "id": "f1",
              "name": "provider",
              "value": "apollo",
              "type": "string"
            },
            {
              "id": "f2",
              "name": "crmProvider",
              "value": "hubspot",
              "type": "string"
            },
            {
              "id": "f3",
              "name": "apolloApiUrl",
              "value": "https://api.apollo.io/v1/mixed_people/search",
              "type": "string"
            },
            {
              "id": "f4",
              "name": "apolloApiKey",
              "value": "YOUR_APOLLO_API_KEY",
              "type": "string"
            },
            {
              "id": "f5",
              "name": "cognismApiUrl",
              "value": "https://api.cognism.com/v2/contacts/search",
              "type": "string"
            },
            {
              "id": "f6",
              "name": "cognismApiKey",
              "value": "YOUR_COGNISM_API_KEY",
              "type": "string"
            },
            {
              "id": "f7",
              "name": "crmBaseUrl",
              "value": "https://api.hubapi.com",
              "type": "string"
            },
            {
              "id": "f8",
              "name": "crmToken",
              "value": "REEMPLAZA_CRM_TOKEN",
              "type": "string"
            },
            {
              "id": "f9",
              "name": "minEmailConfidence",
              "value": "70",
              "type": "string"
            },
            {
              "id": "f10",
              "name": "requirePhone",
              "value": "true",
              "type": "string"
            },
            {
              "id": "f11",
              "name": "icpKeywords",
              "value": "ventas,revops,comercial,sales,growth",
              "type": "string"
            },
            {
              "id": "f12",
              "name": "fitThreshold",
              "value": "60",
              "type": "string"
            },
            {
              "id": "f13",
              "name": "sheetsId",
              "value": "REEMPLAZA_SHEETS_ID_OPCIONAL",
              "type": "string"
            },
            {
              "id": "f14",
              "name": "slackWebhookUrl",
              "value": "",
              "type": "string"
            },
            {
              "id": "f15",
              "name": "batchLabel",
              "value": "fit-apollo-cognism-2026",
              "type": "string"
            }
          ]
        },
        "options": {}
      }
    },
    {
      "id": "fit-sample",
      "name": "Payload de prueba (sample)",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        540,
        460
      ],
      "parameters": {
        "mode": "runOnceForAllItems",
        "language": "javaScript",
        "jsCode": "const cfg = $('Config fit Apollo/Cognism').first().json;\nconst sample = [\n  { first_name: 'Marta', last_name: 'Ruiz', email: 'marta.ruiz@example-b2b.es', email_confidence: 88, phone: '+34600111222', title: 'Head of Sales', country: 'España', organization_name: 'ExampleCorp SL' },\n  { first_name: 'Diego', last_name: 'Alonso', email: 'diego.alonso@example-b2b.es', email_confidence: 55, phone: '', title: 'Marketing Intern', country: 'España', organization_name: 'ExampleCorp SL' },\n  { first_name: 'Laura', last_name: 'Gómez', email: 'laura.gomez@example-b2b.fr', email_confidence: 92, phone: '+33600111222', title: 'RevOps Manager', country: 'Francia', organization_name: 'ExampleCorp SL' }\n];\nreturn sample.map((p) => ({ json: { ...p, provider: cfg.provider } }));"
      }
    },
    {
      "id": "fit-fetch",
      "name": "Fetch personas (Apollo/Cognism)",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        540,
        220
      ],
      "parameters": {
        "method": "POST",
        "url": "={{ $json.provider === 'cognism' ? $json.cognismApiUrl : $json.apolloApiUrl }}",
        "authentication": "none",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "X-Api-Key",
              "value": "={{ $json.provider === 'cognism' ? $json.cognismApiKey : $json.apolloApiKey }}"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"q_organization_domains\": \"\",\n  \"person_titles\": {{ JSON.stringify(($json.icpKeywords || '').split(',').map((s) => s.trim()).filter(Boolean)) }},\n  \"per_page\": 25\n}",
        "options": {
          "timeout": 30000
        }
      },
      "disabled": true
    },
    {
      "id": "fit-split",
      "name": "Split personas",
      "type": "n8n-nodes-base.splitOut",
      "typeVersion": 1,
      "position": [
        800,
        220
      ],
      "parameters": {
        "fieldToSplitOut": "people",
        "options": {}
      },
      "disabled": true
    },
    {
      "id": "fit-normalize",
      "name": "Normalizar + fitScore",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1060,
        340
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "language": "javaScript",
        "jsCode": "const cfg = $('Config fit Apollo/Cognism').first().json;\nconst p = item.json || {};\nconst icp = (cfg.icpKeywords || '').split(',').map((s) => s.trim().toLowerCase()).filter(Boolean);\nconst euCountries = ['españa', 'spain', 'francia', 'france', 'alemania', 'germany', 'italia', 'italy', 'portugal', 'países bajos', 'netherlands'];\n\nconst email = (p.email || '').toLowerCase().trim();\nconst emailOk = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(email);\nconst emailConfidence = Number(p.email_confidence || p.emailConfidence || 0);\nconst phone = (p.phone || p.mobile_phone || '').trim();\nconst hasPhone = phone.length >= 8;\nconst title = (p.title || '').toLowerCase();\nconst country = (p.country || '').toLowerCase();\nconst isEu = euCountries.includes(country);\nconst titleMatch = icp.some((k) => title.includes(k));\n\nlet score = 0;\nif (emailOk) score += 25;\nif (emailConfidence >= Number(cfg.minEmailConfidence || 70)) score += 20;\nif (hasPhone) score += 20;\nelse if (String(cfg.requirePhone) === 'true') score -= 10;\nif (isEu) score += 15;\nif (titleMatch) score += 20;\nscore = Math.max(0, Math.min(100, score));\n\nconst complianceFlag = isEu ? 'ok_ue_minimizar_datos' : 'revisar_base_juridica_extra_ue';\n\nreturn {\n  json: {\n    batchLabel: cfg.batchLabel,\n    provider: cfg.provider,\n    crmProvider: cfg.crmProvider,\n    firstName: p.first_name || p.firstName || '',\n    lastName: p.last_name || p.lastName || '',\n    email,\n    emailOk,\n    emailConfidence,\n    phone,\n    hasPhone,\n    title: p.title || '',\n    company: p.organization_name || p.company || '',\n    country: p.country || '',\n    fitScore: score,\n    complianceFlag\n  }\n};"
      }
    },
    {
      "id": "fit-if",
      "name": "IF fitScore ≥ umbral",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        1320,
        340
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "c1",
              "leftValue": "={{ $json.emailOk }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            },
            {
              "id": "c2",
              "leftValue": "={{ $json.fitScore }}",
              "rightValue": "={{ Number($('Config fit Apollo/Cognism').item.json.fitThreshold) }}",
              "operator": {
                "type": "number",
                "operation": "gte"
              }
            }
          ],
          "combinator": "and"
        }
      }
    },
    {
      "id": "fit-upsert",
      "name": "Upsert Lead CRM (HTTP)",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1600,
        220
      ],
      "parameters": {
        "method": "POST",
        "url": "={{ $('Config fit Apollo/Cognism').item.json.crmBaseUrl }}/crm/v3/objects/contacts/batch/upsert",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "=Bearer {{ $('Config fit Apollo/Cognism').item.json.crmToken }}"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"inputs\": [{\n    \"idProperty\": \"email\",\n    \"id\": \"{{ $json.email }}\",\n    \"properties\": {\n      \"email\": \"{{ $json.email }}\",\n      \"firstname\": \"{{ $json.firstName }}\",\n      \"lastname\": \"{{ $json.lastName }}\",\n      \"phone\": \"{{ $json.phone }}\",\n      \"company\": \"{{ $json.company }}\",\n      \"jobtitle\": \"{{ $json.title }}\",\n      \"lead_source_provider\": \"{{ $json.provider }}\",\n      \"fit_score\": \"{{ $json.fitScore }}\"\n    }\n  }]\n}",
        "options": {
          "timeout": 30000
        }
      }
    },
    {
      "id": "fit-log",
      "name": "Append log fit Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        1880,
        220
      ],
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "value": "={{ $('Config fit Apollo/Cognism').item.json.sheetsId }}",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "Fit",
          "mode": "name"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "fecha": "={{ $now.setZone('Europe/Madrid').toFormat('yyyy-MM-dd HH:mm') }}",
            "batchLabel": "={{ $('Normalizar + fitScore').item.json.batchLabel }}",
            "provider": "={{ $('Normalizar + fitScore').item.json.provider }}",
            "email": "={{ $('Normalizar + fitScore').item.json.email }}",
            "fitScore": "={{ $('Normalizar + fitScore').item.json.fitScore }}",
            "complianceFlag": "={{ $('Normalizar + fitScore').item.json.complianceFlag }}",
            "estado": "upsert_ok"
          }
        },
        "options": {}
      }
    },
    {
      "id": "fit-slack",
      "name": "Slack resumen fit",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2160,
        220
      ],
      "parameters": {
        "method": "POST",
        "url": "={{ $('Config fit Apollo/Cognism').item.json.slackWebhookUrl }}",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"text\": \"Fit {{ $('Normalizar + fitScore').item.json.provider }} {{ $('Config fit Apollo/Cognism').item.json.batchLabel }}: {{ $('Normalizar + fitScore').item.json.email }} score {{ $('Normalizar + fitScore').item.json.fitScore }} → upsert {{ $('Config fit Apollo/Cognism').item.json.crmProvider }}. Compliance: {{ $('Normalizar + fitScore').item.json.complianceFlag }}.\"\n}",
        "options": {
          "timeout": 15000,
          "response": {
            "response": {
              "neverError": true
            }
          }
        }
      }
    },
    {
      "id": "fit-skip",
      "name": "Skip fit bajo umbral",
      "type": "n8n-nodes-base.noOp",
      "typeVersion": 1,
      "position": [
        1600,
        460
      ],
      "parameters": {}
    }
  ],
  "connections": {
    "Manual Trigger fit": {
      "main": [
        [
          {
            "node": "Config fit Apollo/Cognism",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Config fit Apollo/Cognism": {
      "main": [
        [
          {
            "node": "Fetch personas (Apollo/Cognism)",
            "type": "main",
            "index": 0
          },
          {
            "node": "Payload de prueba (sample)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch personas (Apollo/Cognism)": {
      "main": [
        [
          {
            "node": "Split personas",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split personas": {
      "main": [
        [
          {
            "node": "Normalizar + fitScore",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Payload de prueba (sample)": {
      "main": [
        [
          {
            "node": "Normalizar + fitScore",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalizar + fitScore": {
      "main": [
        [
          {
            "node": "IF fitScore ≥ umbral",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF fitScore ≥ umbral": {
      "main": [
        [
          {
            "node": "Upsert Lead CRM (HTTP)",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Skip fit bajo umbral",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Upsert Lead CRM (HTTP)": {
      "main": [
        [
          {
            "node": "Append log fit Sheets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Append log fit Sheets": {
      "main": [
        [
          {
            "node": "Slack resumen fit",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
