Saltar al contenido
AIM Desarrollo y Automatizaciones

Automatización en vivo en menos de 2 segundos: n8n diseña, Cloudflare ejecuta

La demo esperaba hasta 112 s a que n8n despertara en Render. Ahora el flujo corre en el edge con las reglas de n8n y una prueba de paridad.

Publicado el
Tiempo de lectura
6 min de lectura
  • Automatización
  • n8n
  • Cloudflare
  • Pruebas

En el Lab hay una demo que me gusta mucho: llenas un contacto de prueba y ves, paso a paso, cómo un flujo lo valida, lo califica, decide qué hacer con él y redacta la respuesta. La Automatización en vivo nació con n8n corriendo de verdad en un servidor, y funcionaba. El problema era el tiempo: entre el clic y el primer paso podían pasar casi dos minutos.

Te cuento cómo la llevé a menos de 2 segundos sin dejar de usar n8n, y cómo me aseguro de que el sitio no se aparte del flujo que diseñé.

El punto de partida: n8n real en Render Free

La primera versión (DEC-021) ponía n8n en Render, en el plan gratuito, como un servicio más. Era un n8n sin estado: el flujo vivía versionado en el repositorio y se importaba en cada arranque, y el editor y la API pública quedaban cerrados desde internet. El sitio le pasaba cada contacto y n8n le iba reportando los pasos.

Todo eso funcionaba, pero el plan gratuito de Render duerme el servicio tras 15 minutos sin tráfico. El 2026-11-07 medí qué significaba eso, con la misma imagen de Docker y el flujo completo de un contacto:

  • Con 0,1 de CPU, como Render Free: unos 72 s hasta estar listo en local y 112 s en Render; la primera ejecución, unos 14 s; las siguientes, unos 2 s.
  • Con 0,5 de CPU, como Render Starter: unos 14 s hasta estar listo; la primera ejecución, unos 2,1 s; las siguientes, entre 0,3 y 1 s.
  • Con 1 CPU: unos 17 s hasta estar listo; la primera ejecución, unos 1,4 s; las siguientes, entre 0,2 y 0,5 s.

Es decir: entre 70 y 112 segundos solo para despertar y, ya despierto, unos 2 segundos por ejecución, porque 0,1 de CPU no da para más. Una demo que te hace esperar dos minutos no demuestra nada: casi nadie se queda a verla.

La meta: menos de 2 segundos, del clic al resultado

Con esas medidas la decisión fue clara (DEC-023). Para bajar de 2 segundos con n8n en vivo haría falta un servidor siempre encendido con 0,5 de CPU o más. Miré tres caminos que mantenían n8n en vivo: Render Starter (US$7 al mes), Hugging Face Spaces gratis con un ping diario para que no se duerma y una VM gratis de Oracle. Los tres cuestan, dependen de un ping o me obligan a mantener un servidor.

La otra opción era separar dos cosas que hasta ahí iban juntas: dónde se diseña el flujo y dónde se ejecuta. n8n sigue siendo la herramienta de diseño; el sitio ejecuta el flujo en Cloudflare, en la misma función que ya atiende su API.

n8n diseña, el edge ejecuta

El flujo exportado de n8n, lead-demo.json, sigue siendo la fuente de verdad. El paquete @aim/automations lee de ese JSON el nombre del flujo y el nodo Reglas del flujo, donde están juntas todas las reglas de negocio: qué cuenta como correo corporativo, cuántos puntos suma cada presupuesto, los niveles, las rutas y las plantillas del correo.

function readRules(source: Workflow): LeadFlowRules {
  const node = source.nodes.find((candidate) => candidate.name === RULES_NODE)
  const json = node?.parameters.jsonOutput
  if (typeof json !== 'string') throw new Error(`n8n workflow: node "${RULES_NODE}" has no JSON`)
  return LeadFlowRules.parse(JSON.parse(json))
}

Así, cambiar una regla en n8n y exportarla cambia el sitio sin tocar código. Si al JSON le falta una regla que el flujo necesita, Zod lo rechaza al cargar, y la CI lo detecta antes de publicar.

La lógica de los demás nodos está en lead-flow.ts, que ejecuta el flujo nodo por nodo, como lo evalúa n8n. Cada bloque dice qué nodo replica, incluidas sus rarezas. Por ejemplo, el nodo «Calcular nivel» limita el puntaje a 100, pero decide el nivel con la suma sin limitar, y el motor hace exactamente lo mismo:

/** "Calcular nivel": the score is clamped to 0–max; the tier uses the raw sum, as the node does. */
function grade(R: LeadFlowRules, list: AutomationFactor[]): { score: number; tier: AutomationTier } {
  const sum = list.reduce((total, factor) => total + factor.points, 0)
  const { tiers } = R.scoring
  return {
    score: Math.max(0, Math.min(R.scoring.max, sum)),
    tier: sum >= tiers.hot ? 'hot' : sum >= tiers.warm ? 'warm' : 'cold',
  }
}

Para que esto sea posible, el flujo evita el nodo Code: el motor tendría que replicar código arbitrario. Usa nodos Set e IF con expresiones cortas, que se pueden replicar línea por línea.

¿Cómo sé que el motor hace lo mismo que n8n?

Esta es la parte que me deja tranquilo. En automations/test/fixtures/n8n-runs.json está lo que n8n real, la misma imagen de Docker, reportó para 11 contactos de prueba, desde uno caliente y uno rechazado por enlaces hasta uno con emojis y saltos de línea. La CI exige que el motor del edge devuelva exactamente los mismos pasos con los mismos datos. La única diferencia permitida es el primer paso, received: n8n ponía ahí su número de ejecución y el edge pone el nombre del flujo.

Falta un detalle: ¿y si cambio el flujo en n8n y olvido grabar de nuevo? La grabación guarda una huella del flujo, un hash de los nodos y sus conexiones, sin las notas ni la posición de los nodos en el lienzo:

export function fingerprint(workflow) {
  const nodes = workflow.nodes
    .filter((node) => node.type !== 'n8n-nodes-base.stickyNote')
    .map(({ name, type, typeVersion, parameters }) => ({ name, type, typeVersion, parameters }))
    .sort((a, b) => (a.name < b.name ? -1 : 1))
  const json = JSON.stringify(sorted({ nodes, connections: workflow.connections }))
  return createHash('sha256').update(json).digest('hex').slice(0, 16)
}

Y una prueba compara esa huella con la del flujo actual:

it('was recorded from the current workflow', () => {
  // Fails after a change to a node or a connection: record again (automations/README.md).
  expect(recording.fingerprint).toBe(fingerprint(leadDemoWorkflow))
})

Si muevo un nodo o edito una nota, nada cambia. Si cambio una expresión o una conexión, la prueba falla hasta que vuelvo a grabar con pnpm --filter @aim/automations record:n8n, que levanta n8n en Docker y le pasa los contactos de prueba (tarda alrededor de un minuto). Y si cambió la lógica, no solo las reglas, la paridad falla hasta que ajusto lead-flow.ts.

Neon también salió del camino

Al medir en producción apareció otra sorpresa. Cuando n8n corría en Render, la API contaba cada ejecución en Neon para cuidar sus horas gratis. Solo abrir esa conexión y contar tardaba unos 1,25 s, y 2,1 s con Neon dormido: más que todo el resto de la demo.

Con el flujo en el edge ya no hay nada que cuidar. Una ejecución cuesta una verificación de Turnstile y menos de 1 ms de CPU, y no se envía ni se guarda nada. Así que la base de datos salió del camino: la migración 0005 borró las tablas de la automatización, y la protección contra bots es Turnstile, con un token de un solo uso por ejecución. Un bot no gana nada repitiéndola.

Lo que ves hoy

El navegador envía el contacto a POST /api/automation/runs. El edge verifica Turnstile, ejecuta el flujo en memoria y devuelve los pasos con Server-Sent Events. Lo verifiqué en producción el 2026-11-07: la API responde en unos 40 ms, con la verificación de Turnstile incluida. La página mide el tiempo que te importa a ti: del clic al resultado, en tu navegador.

Hubo un costo, y lo digo tal cual: la página ya no puede decir que n8n ejecuta el flujo en vivo. Dice que el flujo se diseñó en n8n y que el sitio lo ejecuta con las mismas reglas, y enlaza al JSON en GitHub. Si algún día pago un servidor siempre encendido, el protocolo anterior sigue en el historial de git.

Para revisarlo tú

Y lo mejor es probarla en el Lab. Usa datos de prueba: nada se guarda y el correo solo se muestra.