{
  "name": "Logixb2b — Verificar emails B2B (cola + HITL)",
  "meta": {
    "templateCredsSetupCompleted": false,
    "description": "Manual Trigger → Config Verificar Emails → Payload demo (4) → Clasificar resultado → IF válido → Upsert CRM emailVerified + Sheets; IF catch-all/risky → Slack HITL; IF inválido → Sheets omitido + Slack resumen. Europe/Madrid. Placeholders REEMPLAZA_*. No envía SMTP."
  },
  "settings": {
    "executionOrder": "v1",
    "timezone": "Europe/Madrid"
  },
  "active": false,
  "pinData": {},
  "tags": [
    { "name": "Logixb2b" },
    { "name": "recursos" },
    { "name": "outbound" }
  ],
  "nodes": [
    {
      "id": "ve-sticky",
      "name": "INSTRUCCIONES (conectar + VPS)",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [-560, -40],
      "parameters": {
        "content": "## INSTRUCCIONES — Verificar emails B2B\n\n**Recurso:** `/recursos/verificar-emails-b2b-n8n`\n**Guía:** /verificar-emails-b2b-automaticamente-antes-enviar\n**Zona:** Europe/Madrid · `active: false` al importar.\n\n### 1) Importar\n1. n8n → ⋯ → **Import from File** → este JSON.\n2. No pegues secretos en el lienzo.\n\n### 2) Nodos\n- **Manual Trigger verificar emails**: pruebas; en producción → Schedule + lectura Sheets/CRM.\n- **Config Verificar Emails** (`Set`): `batchLabel`, `acceptCatchAll`, `requireHitlOnRisky`, `demoMode`, `verifierApiUrl`, `verifierApiKey` (`REEMPLAZA_*`), CRM HubSpot/Pipedrive, `sheetsId`, `slackWebhookUrl`, `timezone`.\n- **Payload de prueba (4 emails)**: valid / invalid / catch_all / risky (@example.com).\n- **Clasificar resultado**: en `demoMode=true` usa `statusHint` del payload. En producción pon `demoMode=false` y sustituye por HTTP a tu API de verificación (NeverBounce, ZeroBounce, MillionVerifier u otra) mapeando el mismo shape (`verificationStatus`).\n- **IF email válido** → Upsert CRM `emailVerified` + Append Sheets OK.\n- **IF risky/HITL** → Slack HITL.\n- **Append Sheets (omitido)** + Slack resumen.\n\n### 3) Puesta en marcha\n1. Rellena Config (`requireHitlOnRisky=true` recomendado).\n2. Credentials CRM / Sheets / Slack.\n3. Manual Trigger → revisa 4 ramas.\n4. Sustituye demo por cola real; no envíes SMTP desde este flujo.\n\n### 4) VPS UE 24/7\nDocker Compose + Postgres + HTTPS + `GENERIC_TIMEZONE=Europe/Madrid`.\n\n### 5) RGPD + EU AI Act\nMinimiza PII en logs; documenta interés legítimo B2B. Si un LLM interpreta resultados sois deployers (Art. 4). /uso-responsable-de-ia-y-transparencia\n",
        "height": 1500,
        "width": 620,
        "color": 4
      }
    },
    {
      "id": "ve-trigger",
      "name": "Manual Trigger verificar emails",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [0, 420],
      "parameters": {}
    },
    {
      "id": "ve-config",
      "name": "Config Verificar Emails",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [260, 420],
      "parameters": {
        "mode": "manual",
        "duplicateItem": false,
        "assignments": {
          "assignments": [
            { "id": "a1", "name": "batchLabel", "value": "verify-demo-2026-08", "type": "string" },
            { "id": "a2", "name": "demoMode", "value": "true", "type": "string" },
            { "id": "a3", "name": "acceptCatchAll", "value": "false", "type": "string" },
            { "id": "a4", "name": "requireHitlOnRisky", "value": "true", "type": "string" },
            { "id": "a5", "name": "verifierApiUrl", "value": "https://api.example.com/v1/verify", "type": "string" },
            { "id": "a6", "name": "verifierApiKey", "value": "REEMPLAZA_VERIFIER_API_KEY", "type": "string" },
            { "id": "a7", "name": "crmProvider", "value": "hubspot", "type": "string" },
            { "id": "a8", "name": "crmBaseUrl", "value": "https://api.hubapi.com", "type": "string" },
            { "id": "a9", "name": "crmToken", "value": "REEMPLAZA_HUBSPOT_TOKEN", "type": "string" },
            { "id": "a10", "name": "pipedriveBaseUrl", "value": "https://REEMPLAZA_TU_EMPRESA.pipedrive.com/api/v1", "type": "string" },
            { "id": "a11", "name": "pipedriveApiToken", "value": "REEMPLAZA_PIPEDRIVE_API_TOKEN", "type": "string" },
            { "id": "a12", "name": "sheetsId", "value": "REEMPLAZA_SHEETS_ID", "type": "string" },
            { "id": "a13", "name": "slackWebhookUrl", "value": "https://hooks.example.com/services/REEMPLAZA_SLACK", "type": "string" },
            { "id": "a14", "name": "timezone", "value": "Europe/Madrid", "type": "string" }
          ]
        },
        "options": {}
      }
    },
    {
      "id": "ve-sample",
      "name": "Payload de prueba (4 emails)",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [540, 420],
      "parameters": {
        "mode": "runOnceForAllItems",
        "language": "javaScript",
        "jsCode": "const cfg = $('Config Verificar Emails').first().json;\nconst nowIso = new Date().toISOString();\nconst sample = [\n  { firstName: 'Ana', lastName: 'Rios', email: 'ana.rios@nortech-example.es', company: 'NorTech SL', title: 'Head of Sales', statusHint: 'valid', sourceLabel: 'apollo_export' },\n  { firstName: 'Bruno', lastName: 'Vila', email: 'bruno.vila@orbitsoft-example.es', company: 'OrbitSoft B2B', title: 'CEO', statusHint: 'invalid', sourceLabel: 'apollo_export' },\n  { firstName: 'Carla', lastName: 'Mendez', email: 'info@plaindata-example.com', company: 'PlainData Inc', title: 'VP Revenue', statusHint: 'catch_all', sourceLabel: 'clay_waterfall' },\n  { firstName: 'Diego', lastName: 'Soler', email: 'diego.soler@atlasops-example.es', company: 'AtlasOps SL', title: 'Director Comercial', statusHint: 'risky', sourceLabel: 'manual_list' }\n];\nreturn sample.map((row) => ({ json: { ...row, crmProvider: cfg.crmProvider, _generatedAt: nowIso } }));"
      }
    },
    {
      "id": "ve-classify",
      "name": "Clasificar resultado",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [820, 420],
      "parameters": {
        "mode": "runOnceForEachItem",
        "language": "javaScript",
        "jsCode": "const cfg = $('Config Verificar Emails').first().json;\nconst p = item.json || {};\nconst email = String(p.email || '').toLowerCase().trim();\nconst acceptCatchAll = String(cfg.acceptCatchAll) === 'true';\nconst requireHitl = String(cfg.requireHitlOnRisky) !== 'false';\nconst demoMode = String(cfg.demoMode) !== 'false';\n\nlet verificationStatus = String(p.statusHint || p.verificationStatus || 'unknown').toLowerCase();\nif (!demoMode && p.apiStatus) verificationStatus = String(p.apiStatus).toLowerCase();\n\nconst map = {\n  valid: 'valid',\n  deliverable: 'valid',\n  ok: 'valid',\n  invalid: 'invalid',\n  undeliverable: 'invalid',\n  bounce: 'invalid',\n  catch_all: 'catch_all',\n  catch-all: 'catch_all',\n  accept_all: 'catch_all',\n  risky: 'risky',\n  unknown: 'risky',\n  unknown_status: 'risky'\n};\nverificationStatus = map[verificationStatus] || 'risky';\n\nlet queueReady = false;\nlet skipReason = null;\nlet requireHitlFlag = false;\nlet emailVerified = false;\n\nif (verificationStatus === 'valid') {\n  queueReady = true;\n  emailVerified = true;\n} else if (verificationStatus === 'catch_all') {\n  if (acceptCatchAll) {\n    queueReady = true;\n    emailVerified = false;\n    requireHitlFlag = requireHitl;\n  } else {\n    skipReason = 'catch_all_rechazado';\n    requireHitlFlag = requireHitl;\n  }\n} else if (verificationStatus === 'risky') {\n  skipReason = 'estado_riesgoso';\n  requireHitlFlag = requireHitl;\n} else {\n  skipReason = 'email_invalido';\n}\n\nreturn {\n  json: {\n    batchLabel: cfg.batchLabel,\n    crmProvider: cfg.crmProvider,\n    crmBaseUrl: cfg.crmBaseUrl,\n    crmToken: cfg.crmToken,\n    pipedriveBaseUrl: cfg.pipedriveBaseUrl,\n    pipedriveApiToken: cfg.pipedriveApiToken,\n    sheetsId: cfg.sheetsId,\n    slackWebhookUrl: cfg.slackWebhookUrl,\n    timezone: cfg.timezone || 'Europe/Madrid',\n    firstName: p.firstName || '',\n    lastName: p.lastName || '',\n    email,\n    company: p.company || '',\n    title: p.title || '',\n    sourceLabel: p.sourceLabel || '',\n    verificationStatus,\n    emailVerified,\n    queueReady,\n    skipReason,\n    requireHitl: Boolean(requireHitlFlag),\n    demoMode,\n    complianceFlag: 'rgpd_interes_legitimo_b2b_verificacion_antes_envio'\n  }\n};"
      }
    },
    {
      "id": "ve-if-valid",
      "name": "IF email válido",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [1100, 280],
      "parameters": {
        "conditions": {
          "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict" },
          "conditions": [
            {
              "id": "c1",
              "leftValue": "={{ $json.queueReady }}",
              "rightValue": true,
              "operator": { "type": "boolean", "operation": "true", "singleValue": true }
            }
          ],
          "combinator": "and"
        }
      }
    },
    {
      "id": "ve-if-hitl",
      "name": "IF requireHitl",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [1100, 560],
      "parameters": {
        "conditions": {
          "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict" },
          "conditions": [
            {
              "id": "c2",
              "leftValue": "={{ $json.requireHitl }}",
              "rightValue": true,
              "operator": { "type": "boolean", "operation": "true", "singleValue": true }
            }
          ],
          "combinator": "and"
        }
      }
    },
    {
      "id": "ve-crm",
      "name": "Upsert CRM (emailVerified)",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [1380, 200],
      "parameters": {
        "method": "POST",
        "url": "={{ $json.crmProvider === 'pipedrive' ? ($json.pipedriveBaseUrl + '/persons') : ($json.crmBaseUrl + '/crm/v3/objects/contacts/batch/upsert') }}",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "={{ $json.crmProvider === 'pipedrive' ? '' : ('Bearer ' + $json.crmToken) }}"
            },
            { "name": "Content-Type", "value": "application/json" }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ $json.crmProvider === 'pipedrive' ? { name: ($json.firstName + ' ' + $json.lastName).trim(), email: [{ value: $json.email, primary: true }], custom_fields: { emailVerified: $json.emailVerified, verificationStatus: $json.verificationStatus }, api_token: $json.pipedriveApiToken } : { inputs: [{ idProperty: 'email', id: $json.email, properties: { email: $json.email, firstname: $json.firstName, lastname: $json.lastName, company: $json.company, emailverified: String($json.emailVerified), verification_status: $json.verificationStatus, last_email_verified_at: new Date().toISOString() } }] } }}",
        "options": { "response": { "response": { "neverError": true } } }
      }
    },
    {
      "id": "ve-sheets-ok",
      "name": "Append Sheets (verificado OK)",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [1640, 200],
      "parameters": {
        "operation": "append",
        "documentId": { "__rl": true, "value": "={{ $json.sheetsId }}", "mode": "id" },
        "sheetName": { "__rl": true, "value": "Verificacion_Email", "mode": "name" },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "fecha": "={{ $now.setZone('Europe/Madrid').toISO() }}",
            "batchLabel": "={{ $json.batchLabel }}",
            "email": "={{ $json.email }}",
            "company": "={{ $json.company }}",
            "verificationStatus": "={{ $json.verificationStatus }}",
            "emailVerified": "={{ $json.emailVerified }}",
            "estado": "verificado_ok",
            "crmProvider": "={{ $json.crmProvider }}",
            "complianceFlag": "={{ $json.complianceFlag }}"
          }
        },
        "options": {}
      }
    },
    {
      "id": "ve-slack-hitl",
      "name": "Slack HITL verificación",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [1380, 480],
      "parameters": {
        "method": "POST",
        "url": "={{ $json.slackWebhookUrl }}",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ { text: 'HITL verificación: ' + $json.email + ' · ' + $json.company + ' · status=' + $json.verificationStatus + ' · skip=' + ($json.skipReason || '-') + ' · batch=' + $json.batchLabel } }}",
        "options": { "response": { "response": { "neverError": true } } }
      }
    },
    {
      "id": "ve-sheets-hitl",
      "name": "Append Sheets (pendiente HITL)",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [1640, 480],
      "parameters": {
        "operation": "append",
        "documentId": { "__rl": true, "value": "={{ $json.sheetsId }}", "mode": "id" },
        "sheetName": { "__rl": true, "value": "Verificacion_Email", "mode": "name" },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "fecha": "={{ $now.setZone('Europe/Madrid').toISO() }}",
            "batchLabel": "={{ $json.batchLabel }}",
            "email": "={{ $json.email }}",
            "company": "={{ $json.company }}",
            "verificationStatus": "={{ $json.verificationStatus }}",
            "emailVerified": "={{ $json.emailVerified }}",
            "estado": "pendiente_hitl",
            "crmProvider": "={{ $json.crmProvider }}",
            "complianceFlag": "={{ $json.complianceFlag }}"
          }
        },
        "options": {}
      }
    },
    {
      "id": "ve-sheets-skip",
      "name": "Append Sheets (omitido)",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [1380, 720],
      "parameters": {
        "operation": "append",
        "documentId": { "__rl": true, "value": "={{ $json.sheetsId }}", "mode": "id" },
        "sheetName": { "__rl": true, "value": "Verificacion_Email", "mode": "name" },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "fecha": "={{ $now.setZone('Europe/Madrid').toISO() }}",
            "batchLabel": "={{ $json.batchLabel }}",
            "email": "={{ $json.email }}",
            "company": "={{ $json.company }}",
            "verificationStatus": "={{ $json.verificationStatus }}",
            "emailVerified": "={{ $json.emailVerified }}",
            "estado": "={{ 'omitido:' + ($json.skipReason || 'n/a') }}",
            "crmProvider": "={{ $json.crmProvider }}",
            "complianceFlag": "={{ $json.complianceFlag }}"
          }
        },
        "options": {}
      }
    },
    {
      "id": "ve-slack-sum",
      "name": "Slack resumen verificación",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [1900, 360],
      "parameters": {
        "method": "POST",
        "url": "={{ $json.slackWebhookUrl }}",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ { text: 'Verificación: ' + $json.email + ' · ' + $json.verificationStatus + ' · verified=' + $json.emailVerified + ' · ' + ($json.skipReason || 'ok') + ' · ' + $json.crmProvider } }}",
        "options": { "response": { "response": { "neverError": true } } }
      }
    }
  ],
  "connections": {
    "Manual Trigger verificar emails": {
      "main": [[{ "node": "Config Verificar Emails", "type": "main", "index": 0 }]]
    },
    "Config Verificar Emails": {
      "main": [[{ "node": "Payload de prueba (4 emails)", "type": "main", "index": 0 }]]
    },
    "Payload de prueba (4 emails)": {
      "main": [[{ "node": "Clasificar resultado", "type": "main", "index": 0 }]]
    },
    "Clasificar resultado": {
      "main": [[{ "node": "IF email válido", "type": "main", "index": 0 }]]
    },
    "IF email válido": {
      "main": [
        [
          { "node": "Upsert CRM (emailVerified)", "type": "main", "index": 0 }
        ],
        [
          { "node": "IF requireHitl", "type": "main", "index": 0 }
        ]
      ]
    },
    "Upsert CRM (emailVerified)": {
      "main": [[{ "node": "Append Sheets (verificado OK)", "type": "main", "index": 0 }]]
    },
    "Append Sheets (verificado OK)": {
      "main": [[{ "node": "Slack resumen verificación", "type": "main", "index": 0 }]]
    },
    "IF requireHitl": {
      "main": [
        [
          { "node": "Slack HITL verificación", "type": "main", "index": 0 }
        ],
        [
          { "node": "Append Sheets (omitido)", "type": "main", "index": 0 }
        ]
      ]
    },
    "Slack HITL verificación": {
      "main": [[{ "node": "Append Sheets (pendiente HITL)", "type": "main", "index": 0 }]]
    },
    "Append Sheets (pendiente HITL)": {
      "main": [[{ "node": "Slack resumen verificación", "type": "main", "index": 0 }]]
    },
    "Append Sheets (omitido)": {
      "main": [[{ "node": "Slack resumen verificación", "type": "main", "index": 0 }]]
    }
  }
}
