{
  "name": "Logixb2b — Secuencias outbound automatizadas (pasos + delays)",
  "meta": {
    "templateCredsSetupCompleted": false,
    "description": "Manual Trigger → Config Secuencia Outbound → Payload de prueba (3 leads, 3 estados) → Evaluar siguiente paso (nextStep/nextChannel/waitHours) → IF toque ahora → Preparar mensaje (borrador) → IF requireHitl → Slack HITL aprobación | Auto → Upsert CRM last_outbound_step + Sheets + Slack. Europe/Madrid. Placeholders REEMPLAZA_*. No envía email/LinkedIn real."
  },
  "settings": {
    "executionOrder": "v1",
    "timezone": "Europe/Madrid"
  },
  "active": false,
  "pinData": {},
  "tags": [
    { "name": "Logixb2b" },
    { "name": "recursos" },
    { "name": "outbound" }
  ],
  "nodes": [
    {
      "id": "so-sticky-instrucciones",
      "name": "INSTRUCCIONES (conectar + VPS)",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [-560, -40],
      "parameters": {
        "content": "## INSTRUCCIONES — Secuencias outbound automatizadas (pasos + delays)\n\n**Recurso:** `/recursos/secuencias-outbound-automatizadas-n8n`\n**Guía:** /secuencias-outbound-automatizadas-guia-paso-a-paso\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 secuencia** (`manual`): sin credencial. Pruebas; en producción → Schedule Trigger (máquina de estados, no bucle de envío masivo).\n- **Config Secuencia Outbound** (`Set`): **sin credencial**. `sequenceName`, `maxSteps`, `delayHoursStep1/2/3`, `channelsCsv` (email,linkedin), `crmProvider` + tokens HubSpot/Pipedrive (`REEMPLAZA_*`), `maxTouchesPerDay`, `stopOnReply`, `requireHitlBeforeStep1`, `sheetsId`, `slackWebhookUrl`, `batchLabel`, `timezone`.\n- **Payload de prueba (3 leads, 3 estados)** (`Code`): sin credencial. 3 filas @example.com: `pending_step1` (listo), `waiting_step2` (listo), `replied_stop` (detenido). Sustituye por tu fuente real (Sheets/CRM).\n- **Evaluar siguiente paso** (`Code`): sin credencial. Calcula `nextStep`, `nextChannel`, `waitHours`, `skipReason`, `scheduledNextAtUtc` y respeta `stopOnReply` + `maxSteps`.\n- **IF debe ejecutar toque ahora**: sin credencial. `shouldTouchNow === true`.\n- **Preparar mensaje paso (borrador)** (`Code`): sin credencial. Genera **solo un borrador** (asunto/cuerpo) según paso+canal. **No** envía email ni llama API de LinkedIn — el envío real lo haces tú, fuera de este nodo, con tu proveedor ya autorizado.\n- **IF requireHitl (paso 1)**: sin credencial. Si `requireHitlBeforeStep1=true` y es el primer toque, pide aprobación humana.\n- **Slack HITL aprobación** (`HTTP`): Incoming Webhook. Notifica el borrador para revisión antes del primer toque.\n- **Auto (sin HITL requerido)** (`NoOp`): pasos 2+ o HITL desactivado; continúa automático.\n- **Upsert CRM (last_outbound_step)** (`HTTP`): Credentials → Header Auth / Bearer. Elige proveedor con `crmProvider`.\n- **Append Sheets (Secuencia_Outbound)**: Google Sheets OAuth2. Misma pestaña `Secuencia_Outbound`, 3 nodos (enviado / pendiente HITL / omitido) para trazabilidad completa.\n- **Slack resumen toque**: Incoming Webhook. URL en Config.\n\n### 3) Orden de puesta en marcha\n1. Rellena **Config** (canales, delays, maxSteps, crmProvider, `requireHitlBeforeStep1=true` recomendado).\n2. Crea Credentials (CRM, Sheets, Slack) y enlázalas donde falte Header Auth.\n3. Ejecuta **una vez** con Manual Trigger: verás los 3 estados de ejemplo (listo paso 1 con HITL, listo paso 2 automático, detenido por respuesta).\n4. Revisa Sheets + Slack antes de activar. **HITL** obligatorio antes del primer toque real.\n5. Solo entonces: **Active** — pero en producción sustituye Manual Trigger + Payload de prueba por **Schedule Trigger** que lea el estado real desde Sheets/CRM (máquina de estados), no por un envío masivo en bucle.\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** llama APIs de envío masivo de email ni scrapers de LinkedIn: solo calcula el siguiente paso. El envío real va en tu herramienta de email/LinkedIn ya autorizada, con sus propios límites diarios y opt-out. `maxTouchesPerDay` es un techo de referencia — aplícalo también en tu proveedor de envío.\n5. En vez de un nodo **Wait** de días (bloquea la ejecución y no escala): usa **Schedule Trigger** (p. ej. cada hora) + este workflow como evaluador de \"qué toca ahora\" leyendo el estado guardado en Sheets/CRM. `scheduledNextAtUtc` queda documentado en cada item para tu propia lógica de reintento.\n6. Alternativa: n8n Cloud.\n\n### 5) RGPD + EU AI Act\nTratas datos de contacto B2B: documenta interés legítimo, honra bajas/respuestas (`stopOnReply`) y registra el motivo de cada toque (`skipReason`) para trazabilidad. Si generas los mensajes con un LLM sois **deployers** (Reglamento UE 2024/1689 Art. 4): **HITL** obligatorio antes del primer toque (`requireHitlBeforeStep1`) y logs en Sheets/CRM. Informativo — no es asesoramiento legal. /uso-responsable-de-ia-y-transparencia\n",
        "height": 1780,
        "width": 640,
        "color": 4
      }
    },
    {
      "id": "so-trigger",
      "name": "Manual Trigger secuencia",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [0, 420],
      "parameters": {}
    },
    {
      "id": "so-config",
      "name": "Config Secuencia Outbound",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [260, 420],
      "parameters": {
        "mode": "manual",
        "duplicateItem": false,
        "assignments": {
          "assignments": [
            { "id": "a1", "name": "sequenceName", "value": "secuencia_outbound_b2b_es_2026", "type": "string" },
            { "id": "a2", "name": "maxSteps", "value": "3", "type": "string" },
            { "id": "a3", "name": "delayHoursStep1", "value": "2", "type": "string" },
            { "id": "a4", "name": "delayHoursStep2", "value": "48", "type": "string" },
            { "id": "a5", "name": "delayHoursStep3", "value": "72", "type": "string" },
            { "id": "a6", "name": "channelsCsv", "value": "email,linkedin", "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": "maxTouchesPerDay", "value": "40", "type": "string" },
            { "id": "a13", "name": "stopOnReply", "value": "true", "type": "string" },
            { "id": "a14", "name": "requireHitlBeforeStep1", "value": "true", "type": "string" },
            { "id": "a15", "name": "sheetsId", "value": "REEMPLAZA_SHEETS_ID", "type": "string" },
            { "id": "a16", "name": "slackWebhookUrl", "value": "https://hooks.example.com/services/REEMPLAZA_SLACK", "type": "string" },
            { "id": "a17", "name": "batchLabel", "value": "secuencia-outbound-demo-2026-08", "type": "string" },
            { "id": "a18", "name": "timezone", "value": "Europe/Madrid", "type": "string" }
          ]
        },
        "options": {}
      }
    },
    {
      "id": "so-sample",
      "name": "Payload de prueba (3 leads, 3 estados)",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [540, 420],
      "parameters": {
        "mode": "runOnceForAllItems",
        "language": "javaScript",
        "jsCode": "const cfg = $('Config Secuencia Outbound').first().json;\nconst nowIso = new Date().toISOString();\nconst hoursAgoIso = (h) => new Date(Date.now() - h * 3600000).toISOString();\n\nconst sample = [\n  {\n    firstName: 'Marta',\n    lastName: 'Roig',\n    email: 'marta.roig@vertexcloud-example.es',\n    title: 'Head of RevOps',\n    company: 'Vertex Cloud SL',\n    sequenceState: 'pending_step1',\n    currentStep: 0,\n    enrolledAt: hoursAgoIso(3),\n    lastTouchAt: null\n  },\n  {\n    firstName: 'Pau',\n    lastName: 'Illa',\n    email: 'pau.illa@nortlog-example.es',\n    title: 'Director Comercial',\n    company: 'NortLog B2B',\n    sequenceState: 'waiting_step2',\n    currentStep: 1,\n    enrolledAt: hoursAgoIso(96),\n    lastTouchAt: hoursAgoIso(60)\n  },\n  {\n    firstName: 'Clara',\n    lastName: 'Peña',\n    email: 'clara.pena@saasforge-example.com',\n    title: 'VP Sales',\n    company: 'SaaSForge Inc',\n    sequenceState: 'replied_stop',\n    currentStep: 2,\n    enrolledAt: hoursAgoIso(200),\n    lastTouchAt: hoursAgoIso(20),\n    repliedAt: hoursAgoIso(18)\n  }\n];\n\nreturn sample.map((row) => ({ json: { ...row, crmProvider: cfg.crmProvider, _generatedAt: nowIso } }));"
      }
    },
    {
      "id": "so-evaluate",
      "name": "Evaluar siguiente paso",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [820, 420],
      "parameters": {
        "mode": "runOnceForEachItem",
        "language": "javaScript",
        "jsCode": "const cfg = $('Config Secuencia Outbound').first().json;\nconst maxSteps = Math.max(1, Number(cfg.maxSteps) || 3);\nconst stopOnReply = String(cfg.stopOnReply) !== 'false';\nconst requireHitlStep1 = String(cfg.requireHitlBeforeStep1) !== 'false';\nconst channels = String(cfg.channelsCsv || 'email,linkedin')\n  .split(',').map((c) => c.trim().toLowerCase()).filter(Boolean);\nif (channels.length === 0) channels.push('email');\nconst delaysByStep = {\n  1: Number(cfg.delayHoursStep1) || 0,\n  2: Number(cfg.delayHoursStep2) || 24,\n  3: Number(cfg.delayHoursStep3) || 48\n};\n\nconst p = item.json || {};\nconst currentStep = Number(p.currentStep) || 0;\nconst nowMs = Date.now();\nconst anchorIsoRaw = p.lastTouchAt || p.enrolledAt || new Date().toISOString();\nconst anchorMs = new Date(anchorIsoRaw).getTime();\n\nlet shouldTouchNow = false;\nlet nextStep = null;\nlet nextChannel = null;\nlet waitHours = null;\nlet skipReason = null;\nlet scheduledNextAtUtc = null;\n\nif (p.sequenceState === 'replied_stop' && stopOnReply) {\n  skipReason = 'respondio_stop_secuencia';\n} else if (currentStep >= maxSteps) {\n  skipReason = 'secuencia_completada_max_steps';\n} else {\n  nextStep = currentStep + 1;\n  const delayHours = delaysByStep[nextStep] != null ? delaysByStep[nextStep] : delaysByStep[3];\n  nextChannel = channels[(nextStep - 1) % channels.length] || 'email';\n  const hoursSinceAnchor = Number.isFinite(anchorMs) ? (nowMs - anchorMs) / 3600000 : 0;\n  scheduledNextAtUtc = new Date(anchorMs + delayHours * 3600000).toISOString();\n\n  if (hoursSinceAnchor >= delayHours) {\n    shouldTouchNow = true;\n    waitHours = 0;\n  } else {\n    shouldTouchNow = false;\n    waitHours = Math.max(0, Math.round((delayHours - hoursSinceAnchor) * 10) / 10);\n    skipReason = 'ventana_delay_no_cumplida';\n  }\n}\n\nconst requireHitl = Boolean(shouldTouchNow && nextStep === 1 && requireHitlStep1);\n\nreturn {\n  json: {\n    sequenceName: cfg.sequenceName,\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    maxTouchesPerDay: cfg.maxTouchesPerDay,\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    sequenceState: p.sequenceState || 'unknown',\n    currentStep,\n    nextStep,\n    nextChannel,\n    shouldTouchNow,\n    waitHours,\n    skipReason,\n    scheduledNextAtUtc,\n    requireHitl,\n    enrolledAt: p.enrolledAt || null,\n    lastTouchAt: p.lastTouchAt || null,\n    repliedAt: p.repliedAt || null,\n    complianceFlag: 'rgpd_interes_legitimo_b2b_hitl_antes_primer_toque'\n  }\n};"
      }
    },
    {
      "id": "so-if-now",
      "name": "IF debe ejecutar toque ahora",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [1100, 420],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "n1",
              "leftValue": "={{ $json.shouldTouchNow }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        }
      }
    },
    {
      "id": "so-prepare-message",
      "name": "Preparar mensaje paso (borrador)",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [1380, 220],
      "parameters": {
        "mode": "runOnceForEachItem",
        "language": "javaScript",
        "jsCode": "const p = item.json || {};\n\nconst templates = {\n  1: {\n    email: {\n      subject: 'Rápida pregunta sobre {{company}}',\n      body: 'Hola {{firstName}}, vi que {{company}} está creciendo y quería compartir cómo ayudamos a equipos comerciales B2B a automatizar prospección con n8n self-hosted (RGPD, UE). ¿Tiene sentido hablar 15 min esta semana?'\n    },\n    linkedin: {\n      subject: null,\n      body: 'Hola {{firstName}}, encantado de conectar. Vi que trabajas en {{company}} y creo que os puede interesar cómo automatizamos secuencias outbound cumpliendo RGPD. ¿Te parece si comparto un par de ideas?'\n    }\n  },\n  2: {\n    email: {\n      subject: 'Seguimiento — {{company}}',\n      body: 'Hola {{firstName}}, quería dar seguimiento a mi mensaje anterior sobre automatizar la prospección de {{company}}. Si no es el momento, dímelo y no insisto más.'\n    },\n    linkedin: {\n      subject: null,\n      body: 'Hola {{firstName}}, ¿tuviste ocasión de ver mi mensaje anterior? Si prefieres que no continúe con la secuencia, respóndeme y lo detengo.'\n    }\n  },\n  3: {\n    email: {\n      subject: 'Última nota — {{company}}',\n      body: 'Hola {{firstName}}, este será mi último mensaje por ahora sobre {{company}}. Dejo la puerta abierta si en el futuro os interesa revisar el proceso de prospección.'\n    },\n    linkedin: {\n      subject: null,\n      body: 'Hola {{firstName}}, cierro el hilo por aquí para no saturar. Si más adelante os interesa, aquí estoy.'\n    }\n  }\n};\n\nconst stepTpl = templates[p.nextStep] || templates[3];\nconst channelTpl = stepTpl[p.nextChannel] || stepTpl.email;\n\nconst fill = (s) => String(s || '')\n  .split('{{firstName}}').join(p.firstName || '')\n  .split('{{company}}').join(p.company || '');\n\nconst draftMessage = {\n  step: p.nextStep,\n  channel: p.nextChannel,\n  subject: fill(channelTpl.subject),\n  body: fill(channelTpl.body),\n  isDraftOnly: true,\n  sendNote: 'Borrador generado; el envío real (SMTP/proveedor email o LinkedIn ya autorizado) se hace fuera de este nodo, con HITL para el primer toque.'\n};\n\nreturn { json: { ...p, draftMessage } };"
      }
    },
    {
      "id": "so-if-hitl",
      "name": "IF requireHitl (paso 1)",
      "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": "so-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 Outbound · HITL] Aprobar paso ' + $json.nextStep + ' (' + $json.nextChannel + ') → ' + $json.company + ' (' + $json.email + ')\\nAsunto: ' + ($json.draftMessage.subject || '(sin asunto, LinkedIn)') + '\\nCuerpo: ' + $json.draftMessage.body }) }}",
        "options": {
          "timeout": 15000,
          "response": {
            "response": { "neverError": true }
          }
        }
      }
    },
    {
      "id": "so-hitl-pending",
      "name": "Pendiente aprobación humana",
      "type": "n8n-nodes-base.noOp",
      "typeVersion": 1,
      "position": [2200, 80],
      "parameters": {}
    },
    {
      "id": "so-sheets-log-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 Secuencia Outbound').item.json.sheetsId }}",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "Secuencia_Outbound",
          "mode": "name"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "fecha": "={{ $now.setZone('Europe/Madrid').toFormat('yyyy-MM-dd HH:mm') }}",
            "batchLabel": "={{ $('Preparar mensaje paso (borrador)').item.json.batchLabel }}",
            "sequenceName": "={{ $('Preparar mensaje paso (borrador)').item.json.sequenceName }}",
            "company": "={{ $('Preparar mensaje paso (borrador)').item.json.company }}",
            "email": "={{ $('Preparar mensaje paso (borrador)').item.json.email }}",
            "step": "={{ $('Preparar mensaje paso (borrador)').item.json.nextStep }}",
            "channel": "={{ $('Preparar mensaje paso (borrador)').item.json.nextChannel }}",
            "crmProvider": "={{ $('Preparar mensaje paso (borrador)').item.json.crmProvider }}",
            "estado": "pendiente_hitl"
          }
        },
        "options": {}
      }
    },
    {
      "id": "so-skip-hitl",
      "name": "Auto (sin HITL requerido)",
      "type": "n8n-nodes-base.noOp",
      "typeVersion": 1,
      "position": [1940, 360],
      "parameters": {}
    },
    {
      "id": "so-crm-upsert",
      "name": "Upsert CRM (last_outbound_step)",
      "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, secuencia_nombre: $json.sequenceName, last_outbound_step: String($json.nextStep), last_outbound_channel: $json.nextChannel, last_outbound_at: $now.toISO() } }] }) }}",
        "options": { "timeout": 30000 }
      }
    },
    {
      "id": "so-sheets-log-sent",
      "name": "Append Sheets (toque enviado)",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [2460, 360],
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "value": "={{ $('Config Secuencia Outbound').item.json.sheetsId }}",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "Secuencia_Outbound",
          "mode": "name"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "fecha": "={{ $now.setZone('Europe/Madrid').toFormat('yyyy-MM-dd HH:mm') }}",
            "batchLabel": "={{ $('Preparar mensaje paso (borrador)').item.json.batchLabel }}",
            "sequenceName": "={{ $('Preparar mensaje paso (borrador)').item.json.sequenceName }}",
            "company": "={{ $('Preparar mensaje paso (borrador)').item.json.company }}",
            "email": "={{ $('Preparar mensaje paso (borrador)').item.json.email }}",
            "step": "={{ $('Preparar mensaje paso (borrador)').item.json.nextStep }}",
            "channel": "={{ $('Preparar mensaje paso (borrador)').item.json.nextChannel }}",
            "crmProvider": "={{ $('Preparar mensaje paso (borrador)').item.json.crmProvider }}",
            "estado": "toque_enviado"
          }
        },
        "options": {}
      }
    },
    {
      "id": "so-slack-resumen",
      "name": "Slack resumen toque",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [2720, 360],
      "parameters": {
        "method": "POST",
        "url": "={{ $('Preparar mensaje paso (borrador)').item.json.slackWebhookUrl }}",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ text: '[Logixb2b Outbound] ' + $('Preparar mensaje paso (borrador)').item.json.sequenceName + ' · ' + $('Preparar mensaje paso (borrador)').item.json.company + ' (' + $('Preparar mensaje paso (borrador)').item.json.email + ') paso ' + $('Preparar mensaje paso (borrador)').item.json.nextStep + '/' + $('Preparar mensaje paso (borrador)').item.json.nextChannel + ' → ' + $('Preparar mensaje paso (borrador)').item.json.crmProvider + ' actualizado' }) }}",
        "options": {
          "timeout": 15000,
          "response": {
            "response": { "neverError": true }
          }
        }
      }
    },
    {
      "id": "so-skip-now",
      "name": "No toca ahora / stop",
      "type": "n8n-nodes-base.noOp",
      "typeVersion": 1,
      "position": [1380, 620],
      "parameters": {}
    },
    {
      "id": "so-sheets-log-skip",
      "name": "Append Sheets (omitido)",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [1660, 620],
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "value": "={{ $('Config Secuencia Outbound').item.json.sheetsId }}",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "Secuencia_Outbound",
          "mode": "name"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "fecha": "={{ $now.setZone('Europe/Madrid').toFormat('yyyy-MM-dd HH:mm') }}",
            "batchLabel": "={{ $('Evaluar siguiente paso').item.json.batchLabel }}",
            "sequenceName": "={{ $('Evaluar siguiente paso').item.json.sequenceName }}",
            "company": "={{ $('Evaluar siguiente paso').item.json.company }}",
            "email": "={{ $('Evaluar siguiente paso').item.json.email }}",
            "step": "={{ $('Evaluar siguiente paso').item.json.nextStep }}",
            "channel": "={{ $('Evaluar siguiente paso').item.json.nextChannel }}",
            "crmProvider": "={{ $('Evaluar siguiente paso').item.json.crmProvider }}",
            "estado": "={{ $('Evaluar siguiente paso').item.json.skipReason }}"
          }
        },
        "options": {}
      }
    }
  ],
  "connections": {
    "Manual Trigger secuencia": {
      "main": [[{ "node": "Config Secuencia Outbound", "type": "main", "index": 0 }]]
    },
    "Config Secuencia Outbound": {
      "main": [[{ "node": "Payload de prueba (3 leads, 3 estados)", "type": "main", "index": 0 }]]
    },
    "Payload de prueba (3 leads, 3 estados)": {
      "main": [[{ "node": "Evaluar siguiente paso", "type": "main", "index": 0 }]]
    },
    "Evaluar siguiente paso": {
      "main": [[{ "node": "IF debe ejecutar toque ahora", "type": "main", "index": 0 }]]
    },
    "IF debe ejecutar toque ahora": {
      "main": [
        [{ "node": "Preparar mensaje paso (borrador)", "type": "main", "index": 0 }],
        [{ "node": "No toca ahora / stop", "type": "main", "index": 0 }]
      ]
    },
    "Preparar mensaje paso (borrador)": {
      "main": [[{ "node": "IF requireHitl (paso 1)", "type": "main", "index": 0 }]]
    },
    "IF requireHitl (paso 1)": {
      "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_outbound_step)", "type": "main", "index": 0 }]]
    },
    "Upsert CRM (last_outbound_step)": {
      "main": [[{ "node": "Append Sheets (toque enviado)", "type": "main", "index": 0 }]]
    },
    "Append Sheets (toque enviado)": {
      "main": [[{ "node": "Slack resumen toque", "type": "main", "index": 0 }]]
    },
    "No toca ahora / stop": {
      "main": [[{ "node": "Append Sheets (omitido)", "type": "main", "index": 0 }]]
    }
  }
}
