{
  "name": "Logixb2b — Límites prospección LinkedIn (segura)",
  "meta": {
    "templateCredsSetupCompleted": false,
    "description": "Manual Trigger → Config límites LinkedIn → Payload de prueba (3 días) → Evaluar cupos + ventana Europe/Madrid → IF freno → Slack + CRM pause_linkedin → Append Sheets. Placeholders de credenciales."
  },
  "settings": {
    "executionOrder": "v1",
    "timezone": "Europe/Madrid"
  },
  "active": false,
  "pinData": {},
  "tags": [
    {
      "name": "Logixb2b"
    },
    {
      "name": "recursos"
    },
    {
      "name": "linkedin"
    },
    {
      "name": "prospeccion"
    }
  ],
  "nodes": [
    {
      "id": "sticky-instrucciones-logix",
      "name": "INSTRUCCIONES (conectar + VPS)",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -480,
        40
      ],
      "parameters": {
        "content": "## INSTRUCCIONES — Límites prospección LinkedIn (segura)\n\n**Recurso:** `/recursos/limites-prospeccion-linkedin-n8n`\n**Guía:** /automatizar-prospeccion-linkedin-sin-arriesgar-cuenta\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 linkedin** (`manual`): sin credencial. Úsalo para pruebas; en producción sustituye por Webhook o Schedule si quieres automatismo.\n- **Config límites LinkedIn** (`Set` Config): **no lleva credencial**. Aquí van URLs/placeholders (`REEMPLAZA_*`, `example.com`, `TU-CRM`). Sustituye todos antes de Active.\n- **Payload de prueba (3 días)** (`Code`): sin credencial. Revisa límites/umbrales en el JS; no hardcodees secretos.\n- **Evaluar cupos + ventana** (`Code`): sin credencial. Revisa límites/umbrales en el JS; no hardcodees secretos.\n- **¿Activar freno?** (`IF`): sin credencial. Comprueba la condición (email, umbral, over_limit, etc.) con datos reales de prueba.\n- **Slack alerta freno** (`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- **CRM pause_linkedin** (`HTTP Request` CRM): Credentials → **Header Auth** / OAuth del CRM. Sustituye `crm*Url` en el nodo **Config**. Prueba con un Lead de sandbox.\n- **Append Sheets (freno)** (`Google Sheets`): Credentials → **Google Sheets OAuth2**. Sustituye Spreadsheet ID / Sheet name. Comparte la hoja con la cuenta OAuth.\n- **Append Sheets (OK)** (`Google Sheets`): Credentials → **Google Sheets OAuth2**. Sustituye Spreadsheet ID / Sheet name. Comparte la hoja con la cuenta OAuth.\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": "li-trigger",
      "name": "Manual Trigger linkedin",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        0,
        320
      ],
      "parameters": {}
    },
    {
      "id": "li-config",
      "name": "Config límites LinkedIn",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        260,
        320
      ],
      "parameters": {
        "mode": "manual",
        "duplicateItem": false,
        "assignments": {
          "assignments": [
            {
              "id": "a1",
              "name": "maxConnections",
              "value": 20,
              "type": "number"
            },
            {
              "id": "a2",
              "name": "maxMessages",
              "value": 40,
              "type": "number"
            },
            {
              "id": "a3",
              "name": "maxProfileViews",
              "value": 80,
              "type": "number"
            },
            {
              "id": "a4",
              "name": "warnPct",
              "value": 0.8,
              "type": "number"
            },
            {
              "id": "a5",
              "name": "windowStart",
              "value": 9,
              "type": "number"
            },
            {
              "id": "a6",
              "name": "windowEnd",
              "value": 18,
              "type": "number"
            },
            {
              "id": "a7",
              "name": "timezone",
              "value": "Europe/Madrid",
              "type": "string"
            },
            {
              "id": "a8",
              "name": "slackWebhookUrl",
              "value": "https://hooks.example.com/services/REEMPLAZA_SLACK",
              "type": "string"
            },
            {
              "id": "a9",
              "name": "crmPauseUrl",
              "value": "https://hooks.example.com/crm/pause-linkedin",
              "type": "string"
            },
            {
              "id": "a10",
              "name": "crmToken",
              "value": "REEMPLAZA_CRM_TOKEN",
              "type": "string"
            },
            {
              "id": "a11",
              "name": "sheetsId",
              "value": "REEMPLAZA_SHEETS_ID",
              "type": "string"
            },
            {
              "id": "a12",
              "name": "batchLabel",
              "value": "linkedin-limites-demo",
              "type": "string"
            }
          ]
        },
        "options": {}
      }
    },
    {
      "id": "li-sample",
      "name": "Payload de prueba (3 días)",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        520,
        320
      ],
      "parameters": {
        "jsCode": "const cfg = $input.first().json;\nconst days = [\n  { account: 'cuenta-demo-es', date: '2026-08-04', connections: 12, messages: 18, profile_views: 40, hourLocal: 11 },\n  { account: 'cuenta-demo-es', date: '2026-08-03', connections: 17, messages: 33, profile_views: 70, hourLocal: 16 },\n  { account: 'cuenta-demo-es', date: '2026-08-02', connections: 28, messages: 55, profile_views: 95, hourLocal: 21 }\n];\nreturn days.map((d) => ({ json: { ...cfg, ...d } }));"
      }
    },
    {
      "id": "li-eval",
      "name": "Evaluar cupos + ventana",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        780,
        320
      ],
      "parameters": {
        "jsCode": "const b = $input.first().json;\nconst limits = {\n  connections: Number(b.maxConnections) || 20,\n  messages: Number(b.maxMessages) || 40,\n  profile_views: Number(b.maxProfileViews) || 80\n};\nconst usage = {\n  connections: Number(b.connections) || 0,\n  messages: Number(b.messages) || 0,\n  profile_views: Number(b.profile_views) || 0\n};\nconst warnPct = Number(b.warnPct) || 0.8;\nconst hour = Number(b.hourLocal);\nconst start = Number(b.windowStart) || 9;\nconst end = Number(b.windowEnd) || 18;\nconst outsideWindow = Number.isFinite(hour) && (hour < start || hour >= end);\nconst breaches = [];\nconst warn = [];\nfor (const k of Object.keys(limits)) {\n  if (usage[k] > limits[k]) breaches.push(k + ':' + usage[k] + '>' + limits[k]);\n  else if (usage[k] >= Math.floor(limits[k] * warnPct)) warn.push(k + ':' + usage[k]);\n}\nconst over = breaches.length > 0 || outsideWindow;\nlet action = 'ok';\nif (breaches.length > 0 || outsideWindow) action = 'pause_linkedin';\nelse if (warn.length) action = 'slow_down';\nconst reasons = [];\nif (outsideWindow) reasons.push('fuera_ventana_' + start + '-' + end + '_Europe/Madrid');\nreasons.push(...breaches);\nif (!breaches.length) reasons.push(...warn.map((w) => 'aviso_' + w));\nconst slackText = '[Logixb2b LinkedIn] ' + (b.account || 'cuenta') + ' · ' + (b.date || '') + ' · acción=' + action + (reasons.length ? ' · ' + reasons.join(', ') : '');\nreturn [{\n  json: {\n    account: b.account || 'unknown',\n    date: b.date || new Date().toISOString().slice(0, 10),\n    usage,\n    limits,\n    hourLocal: hour,\n    windowStart: start,\n    windowEnd: end,\n    timezone: b.timezone || 'Europe/Madrid',\n    outsideWindow,\n    breaches,\n    warn,\n    over_limit: over,\n    action,\n    reasons,\n    slackText,\n    slackWebhookUrl: b.slackWebhookUrl,\n    crmPauseUrl: b.crmPauseUrl,\n    crmToken: b.crmToken,\n    sheetsId: b.sheetsId,\n    batchLabel: b.batchLabel || 'linkedin-limites',\n    market: 'ES/UE',\n    complianceFlag: 'sin_pii_leads_solo_contadores'\n  }\n}];"
      }
    },
    {
      "id": "li-if",
      "name": "¿Activar freno?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1040,
        320
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "c1",
              "leftValue": "={{ $json.over_limit }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      }
    },
    {
      "id": "li-slack",
      "name": "Slack alerta freno",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1300,
        140
      ],
      "parameters": {
        "method": "POST",
        "url": "={{ $json.slackWebhookUrl }}",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ text: $json.slackText }) }}",
        "options": {}
      }
    },
    {
      "id": "li-crm",
      "name": "CRM pause_linkedin",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1560,
        140
      ],
      "parameters": {
        "method": "POST",
        "url": "={{ $json.crmPauseUrl }}",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "=Bearer {{ $json.crmToken }}"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ account: $json.account, date: $json.date, action: $json.action, pause_linkedin: true, reasons: $json.reasons, market: 'ES/UE' }) }}",
        "options": {}
      }
    },
    {
      "id": "li-sheets-pause",
      "name": "Append Sheets (freno)",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        1820,
        140
      ],
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "value": "={{ $json.sheetsId }}",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "LinkedIn",
          "mode": "name"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "fecha": "={{ $json.date }}",
            "batchLabel": "={{ $json.batchLabel }}",
            "account": "={{ $json.account }}",
            "action": "={{ $json.action }}",
            "connections": "={{ $json.usage.connections }}",
            "messages": "={{ $json.usage.messages }}",
            "profile_views": "={{ $json.usage.profile_views }}",
            "complianceFlag": "={{ $json.complianceFlag }}",
            "estado": "freno"
          }
        },
        "options": {}
      }
    },
    {
      "id": "li-sheets-ok",
      "name": "Append Sheets (OK)",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        1300,
        500
      ],
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "value": "={{ $json.sheetsId }}",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "LinkedIn",
          "mode": "name"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "fecha": "={{ $json.date }}",
            "batchLabel": "={{ $json.batchLabel }}",
            "account": "={{ $json.account }}",
            "action": "={{ $json.action }}",
            "connections": "={{ $json.usage.connections }}",
            "messages": "={{ $json.usage.messages }}",
            "profile_views": "={{ $json.usage.profile_views }}",
            "complianceFlag": "={{ $json.complianceFlag }}",
            "estado": "ok"
          }
        },
        "options": {}
      }
    }
  ],
  "connections": {
    "Manual Trigger linkedin": {
      "main": [
        [
          {
            "node": "Config límites LinkedIn",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Config límites LinkedIn": {
      "main": [
        [
          {
            "node": "Payload de prueba (3 días)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Payload de prueba (3 días)": {
      "main": [
        [
          {
            "node": "Evaluar cupos + ventana",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Evaluar cupos + ventana": {
      "main": [
        [
          {
            "node": "¿Activar freno?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "¿Activar freno?": {
      "main": [
        [
          {
            "node": "Slack alerta freno",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Append Sheets (OK)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Slack alerta freno": {
      "main": [
        [
          {
            "node": "CRM pause_linkedin",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "CRM pause_linkedin": {
      "main": [
        [
          {
            "node": "Append Sheets (freno)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
