n8n self-hosted — automatización con estructura

Migrar n8n a un VPS propio, reorganizar 25 cuentas en una arquitectura de microservicios, y darle al LLM control total sobre la automatización. La oportunidad estaba ahí. Faltaba hacerlo bien.

n8n Hetzner VPS CRM BigQuery 25 cuentas
01

El driver

La combinación fue oportunidad más necesidad más costo.

Necesidad: con 25 cuentas de clientes con webhooks activos, el plan cloud de 10.000 ejecuciones por mes se agotaba rápido. El problema de fondo no era el límite — era que todo había crecido sin estructura. El dispatcher mezclaba routing con lógica de negocio, las credenciales estaban hardcodeadas en cada workflow, y agregar una cuenta nueva significaba editar código y esperar no romper las demás.

Oportunidad: la versión cloud de n8n no permite conectar un LLM como capa de control. La versión self-hosted sí. Eso cambió lo que era posible: en vez de workflows que corren solos, un LLM puede tener alcance sobre toda la infraestructura — observar, diagnosticar, modificar, extender.

Costo: un VPS en Hetzner (3 vCPU AMD, 4GB RAM) cuesta €9/mes con ejecuciones ilimitadas. El plan cloud que cubría el volumen era un dígito más.

La pregunta que ordenó todo: ¿qué tiene que pasar para que cambiar el token del CRM no requiera tocar 7 workflows diferentes? La respuesta fue una capa de connectors reusables con credenciales centralizadas. A partir de ahí, el diseño fue obvio.

02

Antes era IT

Hay un post en Reddit que cubre todo lo que hay que saber para hacer esto. Docker, nginx, SSL, variables de entorno, backups, actualizaciones sin downtime, monitoreo. Es extensivo. El tipo que lo escribió tardó semanas en juntarlo. No es mi área core.

Antes de tener un LLM como par de trabajo, esto era territorio de un sysadmin o de muchas horas de documentación y prueba y error. Ahora la dinámica es distinta: el LLM tiene contexto de la infra, puede diagnosticar qué falló, proponer el fix, y ejecutarlo. Lo que antes era una barrera de entrada es ahora una tarde de trabajo.

La migración en sí tomó un día. Lo que tomó más tiempo fue decidir qué refactorizar, en qué orden, y cómo verificar que el comportamiento era idéntico antes de flipar cada cambio. Eso sí requiere criterio propio — el LLM ejecuta, pero la arquitectura la tenés que entender vos.

03

Arquitectura en microservicios

El objetivo era que cada capa supiera lo menos posible de las demás. Un dispatcher que no sepa nada de APIs externas, source handlers que no conozcan el destino, y connectors reusables que centralicen cada integración.

Webhook Receiver —— routing puro: switch por origen
   ↓
Source handlers   —— lógica por origen de datos
   ↓
Connectors      —— conn-bigquery, conn-crm, conn-mailchimp, conn-crm-cliente
   ↓
APIs externas    —— CRM, BigQuery, Mailchimp, CRM del cliente

Por qué así y no de otra manera:

  • Si alguien edita el dispatcher, no puede romper una integración externa — no hay ninguna ahí. El dispatcher tiene 10 nodos: webhook, normalize, switch, execute workflow, respond.
  • Agregar una cuenta nueva es insertar filas en una tabla, no editar workflows. Las credenciales por cuenta viven en una data table de n8n. El connector resuelve qué token usar a partir del nombre de cuenta — el caller nunca tiene credenciales.
  • Agregar una fuente nueva es crear un workflow nuevo, no tocar los existentes. El dispatcher hace switch por origen y llama al handler que corresponde.

Los connectors usan un envelope estándar: reciben { op, account, payload, meta } y devuelven { ok, op, result, error }. Cualquier handler puede llamar a cualquier connector sin saber nada de la API destino.

OrigenBigQueryCRMMailchimpCRM cliente
atribución
lead ads
formulario web
e-commerce
04

Enriquecimiento de contactos con atribución

El caso de uso central: cuando un visitante convierte (crea un contacto o una oportunidad en el CRM), n8n actualiza automáticamente el campo source con el primer toque atribuido — fuente, medio, campaña.

El flujo del workflow:

Llamado por Webhook Receiver
  → Lookup cuenta              # credenciales desde data table
  → Extraer contactId + canal  # del payload del evento
  → Filter: tiene canal + contactId
      → CRM: actualizar source del contacto
      → CRM: agregar nota de atribución
          → Filter: tiene oportunidad asociada
              → CRM: actualizar source de la oportunidad
  → BigQuery: insertar evento         # siempre corre

Cuando llega un evento de conversión, el payload ya trae el ID de la oportunidad y la atribución de primera visita. No hace falta buscar en BigQuery ni tocar las automatizaciones del CRM — n8n actualiza directamente con esa información.

Mapeo de fuentes

Los datos de atribución llegan crudos (source + medium). El mapeo los convierte en un valor legible:

sourcemediumResultado
googlecpc / ppcGoogle Ads
googleorganicOrganic Search
facebook / instagram / metacpc / cpm / paidMeta Ads
facebook / instagram / metaorganic o sin mediumOrganic Social
linkedincpc / cpmLinkedIn Ads
tiktokcpc / cpmTikTok Ads
cualquierareferralReferido
cualquieraemailEmail
direct o vacíovacíoDirecto
otrootrosource/medium (raw)
05

Backfill histórico

Con el workflow corregido, todos los contactos y oportunidades creados antes del fix seguían con source vacío. La solución fue un script Python que consultó BigQuery por el primer toque de cada contacto y actualizó el CRM en batch.

  1. Query BigQuery Primer evento por contacto (ROW_NUMBER OVER PARTITION BY contact_id ORDER BY received_at ASC) filtrando los que tienen source no nulo.
  2. Mapear canal Aplicar la misma lógica de mapeo del workflow de n8n, implementada en Python.
  3. Actualizar contacto en CRM PUT /contacts/{id} con el source mapeado.
  4. Actualizar oportunidades del contacto Buscar todas las oportunidades asociadas y aplicar el mismo source.

El resultado sobre más de 800 contactos fue prácticamente 100% — los únicos fallos eran contactos eliminados en el CRM que todavía aparecían en BigQuery. Las oportunidades cerraron completo.

El bug que costó 3 horas: curl sin --max-time hace que una sola request que no responde bloquee el script completo. El log estaba vacío porque Python con | tee bufferiza por defecto. Fix: python3 -u + --max-time 20 en cada llamada.

06

Lo que gané

Velocidad. Agregar una cuenta nueva es insertar filas en una tabla. Agregar una integración nueva es crear un connector. Agregar una fuente nueva es crear un source handler. Cada cambio está aislado — no hay forma de romper algo que no sea lo que estás tocando.

Control del LLM. La arquitectura en capas le da al LLM alcance real sobre la infra. Puede leer la estructura de workflows, entender qué hace cada capa, diagnosticar dónde falló algo, y proponer o ejecutar cambios. Antes, la mezcla de lógica en el dispatcher hacía imposible razonar sobre el sistema sin entender todo a la vez.

Backups. Con self-hosted, los backups son archivos. Un cron que comprime y sube a almacenamiento externo cada noche. Estoy satisfecho con cómo quedó: es simple, predecible, y no depende de que el proveedor haga lo correcto.

Costo. El VPS cuesta lo que antes era un solo plan de acceso. Con ejecuciones ilimitadas y sin restricciones de integración con LLMs, el trade-off no tiene discusión.