{
  "name": "Logixb2b — Evaluar fit HubSpot vs Salesforce",
  "meta": {
    "templateCredsSetupCompleted": false,
    "description": "Webhook formulario → Config → Code score HubSpot vs Salesforce → Slack. Criterios: inbound, complejidad pipeline, admin, TCO, % nativo."
  },
  "settings": {
    "executionOrder": "v1",
    "timezone": "Europe/Madrid"
  },
  "active": false,
  "pinData": {},
  "tags": [
    {
      "name": "Logixb2b"
    },
    {
      "name": "recursos"
    },
    {
      "name": "crm"
    },
    {
      "name": "hubspot"
    },
    {
      "name": "salesforce"
    }
  ],
  "nodes": [
    {
      "id": "sticky-instrucciones-logix",
      "name": "INSTRUCCIONES (conectar + VPS)",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -480,
        40
      ],
      "parameters": {
        "content": "## INSTRUCCIONES — Evaluar fit HubSpot vs Salesforce\n\n**Recurso:** `/recursos/evaluar-fit-hubspot-vs-salesforce`\n**Guía:** /hubspot-vs-salesforce-equipos-b2b-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 formulario** (`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 fit** (`Set` Config): **no lleva credencial**. Aquí van URLs/placeholders (`REEMPLAZA_*`, `example.com`, `TU-CRM`). Sustituye todos antes de Active.\n- **Calcular score** (`Code`): sin credencial. Revisa límites/umbrales en el JS; no hardcodees secretos.\n- **Aviso Slack** (`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\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": "fit-webhook",
      "name": "Webhook formulario",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        0,
        300
      ],
      "webhookId": "logix-fit-hubspot-salesforce",
      "parameters": {
        "httpMethod": "POST",
        "path": "evaluar-fit-crm",
        "responseMode": "onReceived",
        "options": {}
      }
    },
    {
      "id": "fit-config",
      "name": "Config fit",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        300,
        300
      ],
      "parameters": {
        "mode": "manual",
        "duplicateItem": false,
        "assignments": {
          "assignments": [
            {
              "id": "fit-c1",
              "name": "slackWebhookUrl",
              "value": "https://example.com/aviso-equipo-slack",
              "type": "string"
            },
            {
              "id": "fit-c2",
              "name": "teamLabel",
              "value": "Equipo comercial B2B",
              "type": "string"
            },
            {
              "id": "fit-c3",
              "name": "rawBody",
              "value": "={{ $json.body || $json }}",
              "type": "object"
            },
            {
              "id": "fit-c4",
              "name": "receivedAt",
              "value": "={{ $now.setZone('Europe/Madrid').toFormat('yyyy-MM-dd HH:mm') }}",
              "type": "string"
            }
          ]
        }
      }
    },
    {
      "id": "fit-score",
      "name": "Calcular score",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        600,
        300
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "language": "javaScript",
        "jsCode": "const raw = item.json.rawBody || {};\nconst flag = (v) => {\n  if (v === true || v === 1 || v === '1' || String(v).toLowerCase() === 'true' || String(v).toLowerCase() === 'si' || String(v).toLowerCase() === 'sí') return 1;\n  return 0;\n};\nconst hub =\n  flag(raw.inbound_motor) +\n  flag(raw.portal_unificado) +\n  flag(raw.speed_90d) +\n  flag(raw.revops_ligero);\nconst sf =\n  flag(raw.pipeline_complejo) +\n  flag(raw.gobernanza_fina) +\n  flag(raw.admin_sf) +\n  flag(raw.horizonte_3a5) +\n  flag(raw.marketing_fase2);\nconst pct = Math.max(0, Math.min(100, Number(raw.pct_nativo != null ? raw.pct_nativo : 70)));\nlet winner = 'Empate — ampliad POC';\nif (hub >= sf + 2) winner = 'Inclina a HubSpot';\nelse if (sf >= hub + 2) winner = 'Inclina a Salesforce';\nelse if (hub > sf) winner = 'Leve inclinación a HubSpot';\nelse if (sf > hub) winner = 'Leve inclinación a Salesforce';\nlet note = 'Mantened owner de CRM y capa n8n documentada.';\nif (pct < 50) note = 'Más del 50% externo: asegurad owner de orquestación antes de comprar.';\nif (pct >= 85) note = 'Apostáis por nativo: validad que el plan/tier desbloquea vuestros 5 flujos.';\nreturn {\n  json: {\n    teamLabel: item.json.teamLabel,\n    slackWebhookUrl: item.json.slackWebhookUrl,\n    receivedAt: item.json.receivedAt,\n    scoreHubSpot: hub,\n    scoreSalesforce: sf,\n    pctNativo: pct,\n    winner,\n    note,\n    contact: String(raw.email || raw.contacto || raw.nombre || 'sin-contacto').trim()\n  }\n};"
      }
    },
    {
      "id": "fit-slack",
      "name": "Aviso Slack",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        900,
        300
      ],
      "parameters": {
        "method": "POST",
        "url": "={{ $('Calcular score').item.json.slackWebhookUrl }}",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ text: 'CRM fit — ' + $('Calcular score').item.json.teamLabel + '\\n' + $('Calcular score').item.json.winner + '\\nHubSpot ' + $('Calcular score').item.json.scoreHubSpot + '/4 · Salesforce ' + $('Calcular score').item.json.scoreSalesforce + '/5 · Nativo ' + $('Calcular score').item.json.pctNativo + '%\\n' + ($('Calcular score').item.json.note) + '\\nContacto: ' + $('Calcular score').item.json.contact + ' · ' + $('Calcular score').item.json.receivedAt }) }}",
        "options": {
          "timeout": 20000
        }
      }
    }
  ],
  "connections": {
    "Webhook formulario": {
      "main": [
        [
          {
            "node": "Config fit",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Config fit": {
      "main": [
        [
          {
            "node": "Calcular score",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Calcular score": {
      "main": [
        [
          {
            "node": "Aviso Slack",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
