{
  "name": "Logixb2b — Cold email a escala (cola + HITL)",
  "meta": {
    "templateCredsSetupCompleted": false,
    "description": "Manual Trigger → Config Cold Email Escala → Payload de prueba (4 leads) → Filtrar y rate-limit → IF cola lista → Preparar borrador → IF requireHitl → Slack HITL | Auto → Upsert CRM last_cold_email_at + Sheets Cold_Email + Slack. Europe/Madrid. Placeholders REEMPLAZA_*. No envía SMTP real."
  },
  "settings": {
    "executionOrder": "v1",
    "timezone": "Europe/Madrid"
  },
  "active": false,
  "pinData": {},
  "tags": [
    { "name": "Logixb2b" },
    { "name": "recursos" },
    { "name": "outbound" }
  ],
  "nodes": [
    {
      "id": "ce-sticky-instrucciones",
      "name": "INSTRUCCIONES (conectar + VPS)",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [-560, -40],
      "parameters": {
        "content": "## INSTRUCCIONES — Cold email a escala (cola + HITL)\n\n**Recurso:** `/recursos/cold-email-escala-n8n`\n**Guía:** /cold-email-escala-herramientas-automatizacion\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 cold email** (`manual`): sin credencial. Pruebas; en producción → Schedule Trigger leyendo cola Sheets/CRM.\n- **Config Cold Email Escala** (`Set`): **sin credencial**. `campaignName`, `maxEmailsPerDay`, `maxPerInbox`, `requireVerifiedEmail`, `requireHitlBeforeSend`, `crmProvider` + tokens (`REEMPLAZA_*`), `stopOnReply`, `sheetsId`, `slackWebhookUrl`, `batchLabel`, `timezone`.\n- **Payload de prueba (4 leads)** (`Code`): sin credencial. 4 filas @example.com: verified ready, unverified skip, replied_stop, pending_hitl.\n- **Filtrar y rate-limit** (`Code`): sin credencial. Aplica verificación, techo diario y stopOnReply.\n- **IF cola lista**: sin credencial. `queueReady === true`.\n- **Preparar borrador cold email** (`Code`): sin credencial. Genera **solo un borrador**. **No** envía SMTP — el envío real lo haces en Instantly u otro ESP autorizado.\n- **IF requireHitl**: sin credencial.\n- **Slack HITL aprobación** (`HTTP`): Incoming Webhook.\n- **Auto (sin HITL requerido)** (`NoOp`).\n- **Upsert CRM (last_cold_email_at)** (`HTTP`): Header Auth / Bearer según `crmProvider`.\n- **Append Sheets (Cold_Email)**: Google Sheets OAuth2.\n- **Slack resumen cola**: Incoming Webhook.\n\n### 3) Orden de puesta en marcha\n1. Rellena **Config** (`requireVerifiedEmail=true`, `requireHitlBeforeSend=true` recomendado).\n2. Crea Credentials (CRM, Sheets, Slack).\n3. Ejecuta **una vez** con Manual Trigger y revisa las 4 ramas demo.\n4. Revisa Sheets + Slack antes de Active.\n5. En producción: Schedule + cola real; el ESP (Instantly, etc.) aplica sus propios límites y warmup.\n\n### 4) Producción 24/7 en VPS (UE)\n1. VPS UE + Docker Compose + Postgres + HTTPS.\n2. `GENERIC_TIMEZONE=Europe/Madrid`, `WEBHOOK_URL`, `N8N_ENCRYPTION_KEY`.\n3. Firewall 80/443 (+SSH). Backups Postgres + export workflows.\n4. Este flujo **no** envía email masivo: orquesta cola y borradores. Warmup y rotación van en tu ESP.\n5. Alternativa: n8n Cloud.\n\n### 5) RGPD + EU AI Act\nDocumenta interés legítimo B2B, honra bajas/respuestas (`stopOnReply`) y registra skipReason. Si redactas con LLM sois **deployers** (Reglamento UE 2024/1689 Art. 4): HITL obligatorio en primeros lotes. Informativo — no es asesoramiento legal. /uso-responsable-de-ia-y-transparencia\n",
        "height": 1680,
        "width": 640,
        "color": 4
      }
    },
    {
      "id": "ce-trigger",
      "name": "Manual Trigger cold email",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [0, 420],
      "parameters": {}
    },
    {
      "id": "ce-config",
      "name": "Config Cold Email Escala",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [260, 420],
      "parameters": {
        "mode": "manual",
        "duplicateItem": false,
        "assignments": {
          "assignments": [
            { "id": "a1", "name": "campaignName", "value": "cold_email_escala_b2b_es_2026", "type": "string" },
            { "id": "a2", "name": "maxEmailsPerDay", "value": "80", "type": "string" },
            { "id": "a3", "name": "maxPerInbox", "value": "25", "type": "string" },
            { "id": "a4", "name": "requireVerifiedEmail", "value": "true", "type": "string" },
            { "id": "a5", "name": "requireHitlBeforeSend", "value": "true", "type": "string" },
            { "id": "a6", "name": "stopOnReply", "value": "true", "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": "batchLabel", "value": "cold-email-demo-2026-08", "type": "string" },
            { "id": "a15", "name": "timezone", "value": "Europe/Madrid", "type": "string" },
            { "id": "a16", "name": "emailsSentToday", "value": "12", "type": "string" }
          ]
        },
        "options": {}
      }
    },
    {
      "id": "ce-sample",
      "name": "Payload de prueba (4 leads)",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [540, 420],
      "parameters": {
        "mode": "runOnceForAllItems",
        "language": "javaScript",
        "jsCode": "const cfg = $('Config Cold Email Escala').first().json;\nconst nowIso = new Date().toISOString();\nconst hoursAgoIso = (h) => new Date(Date.now() - h * 3600000).toISOString();\n\nconst sample = [\n  {\n    firstName: 'Ana',\n    lastName: 'Rios',\n    email: 'ana.rios@nortech-example.es',\n    title: 'Head of Sales',\n    company: 'NorTech SL',\n    emailVerified: true,\n    queueState: 'verified_ready',\n    repliedAt: null\n  },\n  {\n    firstName: 'Bruno',\n    lastName: 'Vila',\n    email: 'bruno.vila@orbitsoft-example.es',\n    title: 'CEO',\n    company: 'OrbitSoft B2B',\n    emailVerified: false,\n    queueState: 'unverified_skip',\n    repliedAt: null\n  },\n  {\n    firstName: 'Carla',\n    lastName: 'Mendez',\n    email: 'carla.mendez@plaindata-example.com',\n    title: 'VP Revenue',\n    company: 'PlainData Inc',\n    emailVerified: true,\n    queueState: 'replied_stop',\n    repliedAt: hoursAgoIso(6)\n  },\n  {\n    firstName: 'Diego',\n    lastName: 'Soler',\n    email: 'diego.soler@atlasops-example.es',\n    title: 'Director Comercial',\n    company: 'AtlasOps SL',\n    emailVerified: true,\n    queueState: 'pending_hitl',\n    repliedAt: null\n  }\n];\n\nreturn sample.map((row) => ({ json: { ...row, crmProvider: cfg.crmProvider, _generatedAt: nowIso } }));"
      }
    },
    {
      "id": "ce-filter",
      "name": "Filtrar y rate-limit",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [820, 420],
      "parameters": {
        "mode": "runOnceForEachItem",
        "language": "javaScript",
        "jsCode": "const cfg = $('Config Cold Email Escala').first().json;\nconst requireVerified = String(cfg.requireVerifiedEmail) !== 'false';\nconst stopOnReply = String(cfg.stopOnReply) !== 'false';\nconst requireHitl = String(cfg.requireHitlBeforeSend) !== 'false';\nconst maxDay = Math.max(1, Number(cfg.maxEmailsPerDay) || 80);\nconst sentToday = Math.max(0, Number(cfg.emailsSentToday) || 0);\nconst p = item.json || {};\n\nlet queueReady = true;\nlet skipReason = null;\n\nif (stopOnReply && (p.queueState === 'replied_stop' || p.repliedAt)) {\n  queueReady = false;\n  skipReason = 'respondio_stop_cola';\n} else if (requireVerified && !p.emailVerified) {\n  queueReady = false;\n  skipReason = 'email_no_verificado';\n} else if (sentToday >= maxDay) {\n  queueReady = false;\n  skipReason = 'techo_diario_alcanzado';\n}\n\nconst needHitl = Boolean(queueReady && requireHitl);\n\nreturn {\n  json: {\n    campaignName: cfg.campaignName,\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    maxEmailsPerDay: cfg.maxEmailsPerDay,\n    maxPerInbox: cfg.maxPerInbox,\n    sheetsId: cfg.sheetsId,\n    slackWebhookUrl: cfg.slackWebhookUrl,\n    timezone: cfg.timezone || 'Europe/Madrid',\n    firstName: p.firstName || '',\n    lastName: p.lastName || '',\n    email: String(p.email || '').toLowerCase().trim(),\n    title: p.title || '',\n    company: p.company || '',\n    emailVerified: Boolean(p.emailVerified),\n    queueState: p.queueState || 'unknown',\n    queueReady,\n    skipReason,\n    requireHitl: needHitl,\n    repliedAt: p.repliedAt || null,\n    complianceFlag: 'rgpd_interes_legitimo_b2b_hitl_antes_envio'\n  }\n};"
      }
    },
    {
      "id": "ce-if-ready",
      "name": "IF cola lista",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [1100, 420],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "n1",
              "leftValue": "={{ $json.queueReady }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        }
      }
    },
    {
      "id": "ce-prepare",
      "name": "Preparar borrador cold email",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [1380, 220],
      "parameters": {
        "mode": "runOnceForEachItem",
        "language": "javaScript",
        "jsCode": "const p = item.json || {};\nconst subject = 'Rápida pregunta sobre ' + (p.company || 'tu equipo');\nconst body = 'Hola ' + (p.firstName || '') + ',\\n\\nVi que ' + (p.company || 'vuestra empresa') + ' trabaja en B2B y quería compartir cómo equipos en España orquestan cold email a escala con n8n + ESP (warmup, verificación, HITL) bajo RGPD Europe/Madrid.\\n\\n¿Tiene sentido 15 min esta semana? Si no es el momento, dímelo y no insisto.\\n\\n—';\n\nreturn {\n  json: {\n    ...p,\n    draftMessage: {\n      subject,\n      body,\n      isDraftOnly: true,\n      sendNote: 'Borrador generado; el envío real (Instantly u otro ESP autorizado) se hace fuera de este nodo, con HITL si aplica.'\n    }\n  }\n};"
      }
    },
    {
      "id": "ce-if-hitl",
      "name": "IF requireHitl",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [1660, 220],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "h1",
              "leftValue": "={{ $json.requireHitl }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        }
      }
    },
    {
      "id": "ce-slack-hitl",
      "name": "Slack HITL aprobación",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [1940, 80],
      "parameters": {
        "method": "POST",
        "url": "={{ $json.slackWebhookUrl }}",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ text: '[Logixb2b Cold Email · HITL] Aprobar envío → ' + $json.company + ' (' + $json.email + ')\\nAsunto: ' + $json.draftMessage.subject + '\\nCuerpo: ' + $json.draftMessage.body }) }}",
        "options": {
          "timeout": 15000,
          "response": {
            "response": { "neverError": true }
          }
        }
      }
    },
    {
      "id": "ce-hitl-pending",
      "name": "Pendiente aprobación humana",
      "type": "n8n-nodes-base.noOp",
      "typeVersion": 1,
      "position": [2200, 80],
      "parameters": {}
    },
    {
      "id": "ce-sheets-hitl",
      "name": "Append Sheets (pendiente HITL)",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [2460, 80],
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "value": "={{ $('Config Cold Email Escala').item.json.sheetsId }}",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "Cold_Email",
          "mode": "name"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "fecha": "={{ $now.setZone('Europe/Madrid').toFormat('yyyy-MM-dd HH:mm') }}",
            "batchLabel": "={{ $('Preparar borrador cold email').item.json.batchLabel }}",
            "campaignName": "={{ $('Preparar borrador cold email').item.json.campaignName }}",
            "company": "={{ $('Preparar borrador cold email').item.json.company }}",
            "email": "={{ $('Preparar borrador cold email').item.json.email }}",
            "verified": "={{ String($('Preparar borrador cold email').item.json.emailVerified) }}",
            "crmProvider": "={{ $('Preparar borrador cold email').item.json.crmProvider }}",
            "estado": "pendiente_hitl"
          }
        },
        "options": {}
      }
    },
    {
      "id": "ce-auto",
      "name": "Auto (sin HITL requerido)",
      "type": "n8n-nodes-base.noOp",
      "typeVersion": 1,
      "position": [1940, 360],
      "parameters": {}
    },
    {
      "id": "ce-crm",
      "name": "Upsert CRM (last_cold_email_at)",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [2200, 360],
      "parameters": {
        "method": "POST",
        "url": "={{ $json.crmProvider === 'pipedrive' ? ($json.pipedriveBaseUrl + '/persons?api_token=' + $json.pipedriveApiToken) : ($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.stringify($json.crmProvider === 'pipedrive' ? { name: ($json.firstName + ' ' + $json.lastName).trim(), email: [{ value: $json.email, primary: true }], org_name: $json.company } : { inputs: [{ idProperty: 'email', id: $json.email, properties: { email: $json.email, firstname: $json.firstName, lastname: $json.lastName, company: $json.company, cold_campaign: $json.campaignName, last_cold_email_at: $now.toISO() } }] }) }}",
        "options": { "timeout": 30000 }
      }
    },
    {
      "id": "ce-sheets-sent",
      "name": "Append Sheets (cola preparada)",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [2460, 360],
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "value": "={{ $('Config Cold Email Escala').item.json.sheetsId }}",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "Cold_Email",
          "mode": "name"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "fecha": "={{ $now.setZone('Europe/Madrid').toFormat('yyyy-MM-dd HH:mm') }}",
            "batchLabel": "={{ $('Preparar borrador cold email').item.json.batchLabel }}",
            "campaignName": "={{ $('Preparar borrador cold email').item.json.campaignName }}",
            "company": "={{ $('Preparar borrador cold email').item.json.company }}",
            "email": "={{ $('Preparar borrador cold email').item.json.email }}",
            "verified": "={{ String($('Preparar borrador cold email').item.json.emailVerified) }}",
            "crmProvider": "={{ $('Preparar borrador cold email').item.json.crmProvider }}",
            "estado": "cola_preparada"
          }
        },
        "options": {}
      }
    },
    {
      "id": "ce-slack-resumen",
      "name": "Slack resumen cola",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [2720, 360],
      "parameters": {
        "method": "POST",
        "url": "={{ $('Preparar borrador cold email').item.json.slackWebhookUrl }}",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ text: '[Logixb2b Cold Email] ' + $('Preparar borrador cold email').item.json.campaignName + ' · ' + $('Preparar borrador cold email').item.json.company + ' (' + $('Preparar borrador cold email').item.json.email + ') → ' + $('Preparar borrador cold email').item.json.crmProvider + ' last_cold_email_at' }) }}",
        "options": {
          "timeout": 15000,
          "response": {
            "response": { "neverError": true }
          }
        }
      }
    },
    {
      "id": "ce-skip",
      "name": "No entra en cola / stop",
      "type": "n8n-nodes-base.noOp",
      "typeVersion": 1,
      "position": [1380, 620],
      "parameters": {}
    },
    {
      "id": "ce-sheets-skip",
      "name": "Append Sheets (omitido)",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [1660, 620],
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "value": "={{ $('Config Cold Email Escala').item.json.sheetsId }}",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "Cold_Email",
          "mode": "name"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "fecha": "={{ $now.setZone('Europe/Madrid').toFormat('yyyy-MM-dd HH:mm') }}",
            "batchLabel": "={{ $('Filtrar y rate-limit').item.json.batchLabel }}",
            "campaignName": "={{ $('Filtrar y rate-limit').item.json.campaignName }}",
            "company": "={{ $('Filtrar y rate-limit').item.json.company }}",
            "email": "={{ $('Filtrar y rate-limit').item.json.email }}",
            "verified": "={{ String($('Filtrar y rate-limit').item.json.emailVerified) }}",
            "crmProvider": "={{ $('Filtrar y rate-limit').item.json.crmProvider }}",
            "estado": "={{ $('Filtrar y rate-limit').item.json.skipReason }}"
          }
        },
        "options": {}
      }
    }
  ],
  "connections": {
    "Manual Trigger cold email": {
      "main": [[{ "node": "Config Cold Email Escala", "type": "main", "index": 0 }]]
    },
    "Config Cold Email Escala": {
      "main": [[{ "node": "Payload de prueba (4 leads)", "type": "main", "index": 0 }]]
    },
    "Payload de prueba (4 leads)": {
      "main": [[{ "node": "Filtrar y rate-limit", "type": "main", "index": 0 }]]
    },
    "Filtrar y rate-limit": {
      "main": [[{ "node": "IF cola lista", "type": "main", "index": 0 }]]
    },
    "IF cola lista": {
      "main": [
        [{ "node": "Preparar borrador cold email", "type": "main", "index": 0 }],
        [{ "node": "No entra en cola / stop", "type": "main", "index": 0 }]
      ]
    },
    "Preparar borrador cold email": {
      "main": [[{ "node": "IF requireHitl", "type": "main", "index": 0 }]]
    },
    "IF requireHitl": {
      "main": [
        [{ "node": "Slack HITL aprobación", "type": "main", "index": 0 }],
        [{ "node": "Auto (sin HITL requerido)", "type": "main", "index": 0 }]
      ]
    },
    "Slack HITL aprobación": {
      "main": [[{ "node": "Pendiente aprobación humana", "type": "main", "index": 0 }]]
    },
    "Pendiente aprobación humana": {
      "main": [[{ "node": "Append Sheets (pendiente HITL)", "type": "main", "index": 0 }]]
    },
    "Auto (sin HITL requerido)": {
      "main": [[{ "node": "Upsert CRM (last_cold_email_at)", "type": "main", "index": 0 }]]
    },
    "Upsert CRM (last_cold_email_at)": {
      "main": [[{ "node": "Append Sheets (cola preparada)", "type": "main", "index": 0 }]]
    },
    "Append Sheets (cola preparada)": {
      "main": [[{ "node": "Slack resumen cola", "type": "main", "index": 0 }]]
    },
    "No entra en cola / stop": {
      "main": [[{ "node": "Append Sheets (omitido)", "type": "main", "index": 0 }]]
    }
  }
}
