{
  "name": "Logixb2b — WhatsApp Business API → CRM (Lead)",
  "meta": {
    "templateCredsSetupCompleted": false,
    "description": "Webhook Cloud API WhatsApp → Config → Code normaliza mensaje → IF upsert → HTTP CRM (HubSpot/Pipedrive placeholder) → Slack opcional → Respond."
  },
  "settings": {
    "executionOrder": "v1",
    "timezone": "Europe/Madrid"
  },
  "active": false,
  "pinData": {},
  "tags": [
    {
      "name": "Logixb2b"
    },
    {
      "name": "recursos"
    },
    {
      "name": "whatsapp"
    },
    {
      "name": "crm"
    }
  ],
  "nodes": [
    {
      "id": "sticky-instrucciones-logix",
      "name": "INSTRUCCIONES (conectar + VPS)",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -480,
        40
      ],
      "parameters": {
        "content": "## INSTRUCCIONES — WhatsApp Business API → CRM (Lead)\n\n**Recurso:** `/recursos/whatsapp-business-crm-n8n`\n**Guía:** /integrar-whatsapp-business-crm-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- **Webhook WhatsApp** (`webhook`): sin credencial. Tras Active, copia **URL de producción** (HTTPS del VPS/Cloud). El emisor (formulario, CRM, bot) debe POST a esa URL.\n- **Config WhatsApp CRM** (`Set` Config): **no lleva credencial**. Aquí van URLs/placeholders (`REEMPLAZA_*`, `example.com`, `TU-CRM`). Sustituye todos antes de Active.\n- **Normalizar mensaje** (`Code`): sin credencial. Revisa límites/umbrales en el JS; no hardcodees secretos.\n- **IF tiene teléfono** (`IF`): sin credencial. Comprueba la condición (email, umbral, over_limit, etc.) con datos reales de prueba.\n- **Upsert CRM HTTP** (`HTTP Request` CRM): Credentials → **Header Auth** / OAuth del CRM. Sustituye `crm*Url` en el nodo **Config**. Prueba con un Lead de sandbox.\n- **Aviso Slack opcional** (`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- **Responder OK** (`respondToWebhook`): sin credencial. Tras Active, copia **URL de producción** (HTTPS del VPS/Cloud). El emisor (formulario, CRM, bot) debe POST a esa URL.\n- **Responder skip** (`respondToWebhook`): sin credencial. Tras Active, copia **URL de producción** (HTTPS del VPS/Cloud). El emisor (formulario, CRM, bot) debe POST a esa URL.\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": "wa-webhook",
      "name": "Webhook WhatsApp",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        0,
        300
      ],
      "webhookId": "logix-whatsapp-cloud-inbound",
      "parameters": {
        "httpMethod": "POST",
        "path": "whatsapp-cloud-inbound",
        "responseMode": "responseNode",
        "options": {}
      }
    },
    {
      "id": "wa-config",
      "name": "Config WhatsApp CRM",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        280,
        300
      ],
      "parameters": {
        "mode": "manual",
        "duplicateItem": false,
        "assignments": {
          "assignments": [
            {
              "id": "wa-c1",
              "name": "metaVerifyToken",
              "value": "REEMPLAZA_VERIFY_TOKEN",
              "type": "string"
            },
            {
              "id": "wa-c2",
              "name": "crmProvider",
              "value": "hubspot",
              "type": "string"
            },
            {
              "id": "wa-c3",
              "name": "crmBaseUrl",
              "value": "https://api.hubapi.com",
              "type": "string"
            },
            {
              "id": "wa-c4",
              "name": "crmToken",
              "value": "REEMPLAZA_CRM_TOKEN",
              "type": "string"
            },
            {
              "id": "wa-c5",
              "name": "defaultPipelineStage",
              "value": "nuevo_whatsapp",
              "type": "string"
            },
            {
              "id": "wa-c6",
              "name": "slackWebhookUrl",
              "value": "",
              "type": "string"
            },
            {
              "id": "wa-c7",
              "name": "timezone",
              "value": "Europe/Madrid",
              "type": "string"
            }
          ]
        },
        "options": {}
      }
    },
    {
      "id": "wa-normalize",
      "name": "Normalizar mensaje",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        560,
        300
      ],
      "parameters": {
        "jsCode": "const cfg = $('Config WhatsApp CRM').first().json;\nconst root = items[0].json;\nconst body = root.body || root;\nconst entry = (body.entry && body.entry[0]) || {};\nconst change = (entry.changes && entry.changes[0]) || {};\nconst value = change.value || {};\nconst msg = (value.messages && value.messages[0]) || null;\nconst contact = (value.contacts && value.contacts[0]) || {};\nif (!msg) {\n  return [{ json: { skip: true, reason: 'no_message', crmProvider: cfg.crmProvider } }];\n}\nconst phone = String(msg.from || '').replace(/\\D/g, '');\nconst text = (msg.text && msg.text.body) || (msg.button && msg.button.text) || msg.type || '';\nconst name = (contact.profile && contact.profile.name) || '';\nconst waId = msg.id || '';\nreturn [{\n  json: {\n    skip: false,\n    phone,\n    e164Hint: phone ? '+' + phone : '',\n    name,\n    text: String(text).slice(0, 2000),\n    waMessageId: waId,\n    waTimestamp: msg.timestamp || '',\n    crmProvider: cfg.crmProvider,\n    crmBaseUrl: cfg.crmBaseUrl,\n    crmToken: cfg.crmToken,\n    defaultPipelineStage: cfg.defaultPipelineStage,\n    slackWebhookUrl: cfg.slackWebhookUrl || '',\n    timezone: cfg.timezone || 'Europe/Madrid',\n    source: 'whatsapp_cloud_api'\n  }\n}];"
      }
    },
    {
      "id": "wa-if",
      "name": "IF tiene teléfono",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        840,
        300
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "wa-if1",
              "leftValue": "={{ !$json.skip && $json.phone && $json.phone.length >= 8 }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      }
    },
    {
      "id": "wa-http-crm",
      "name": "Upsert CRM HTTP",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1120,
        220
      ],
      "parameters": {
        "method": "POST",
        "url": "={{ $json.crmBaseUrl }}/crm/v3/objects/contacts",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "=Bearer {{ $json.crmToken }}"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"properties\": {\n    \"phone\": \"{{ $json.e164Hint }}\",\n    \"firstname\": \"{{ $json.name }}\",\n    \"hs_lead_status\": \"NEW\",\n    \"whatsapp_last_message\": \"{{ $json.text.replace(/\"/g, \"'\") }}\",\n    \"whatsapp_wa_id\": \"{{ $json.waMessageId }}\",\n    \"lead_source\": \"WhatsApp Business API\"\n  }\n}",
        "options": {
          "response": {
            "response": {
              "neverError": true
            }
          }
        }
      }
    },
    {
      "id": "wa-slack",
      "name": "Aviso Slack opcional",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1400,
        220
      ],
      "parameters": {
        "method": "POST",
        "url": "={{ $('Normalizar mensaje').item.json.slackWebhookUrl || 'https://example.com/noop-slack' }}",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"text\": \"WhatsApp → CRM: {{ $('Normalizar mensaje').item.json.name || 'sin nombre' }} ({{ $('Normalizar mensaje').item.json.e164Hint }}) · {{ $('Normalizar mensaje').item.json.text.slice(0,120) }}\"\n}",
        "options": {
          "response": {
            "response": {
              "neverError": true
            }
          }
        }
      }
    },
    {
      "id": "wa-respond-ok",
      "name": "Responder OK",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        1680,
        220
      ],
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ { ok: true, phone: $('Normalizar mensaje').item.json.phone, waMessageId: $('Normalizar mensaje').item.json.waMessageId } }}"
      }
    },
    {
      "id": "wa-respond-skip",
      "name": "Responder skip",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        1120,
        420
      ],
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ { ok: true, skipped: true, reason: $json.reason || 'no_phone' } }}"
      }
    }
  ],
  "connections": {
    "Webhook WhatsApp": {
      "main": [
        [
          {
            "node": "Config WhatsApp CRM",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Config WhatsApp CRM": {
      "main": [
        [
          {
            "node": "Normalizar mensaje",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalizar mensaje": {
      "main": [
        [
          {
            "node": "IF tiene teléfono",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF tiene teléfono": {
      "main": [
        [
          {
            "node": "Upsert CRM HTTP",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Responder skip",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Upsert CRM HTTP": {
      "main": [
        [
          {
            "node": "Aviso Slack opcional",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Aviso Slack opcional": {
      "main": [
        [
          {
            "node": "Responder OK",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
