Módulo 5: Dependencias entre workflows
4. Fan-out y fan-in sin perder items
Descripción
Al terminar esta lección vas a poder repartir un trabajo en varias ramas o sub-ejecuciones —lo que se llama fan-out— y volver a juntar sus resultados en un solo lugar —el fan-in— sin perder ni duplicar items por el camino. Vas a conocer las dos formas de hacer fan-out en n8n (ramificar dentro de una ejecución, o disparar una sub-ejecución por item), vas a saber cómo el nodo Merge hace el fan-in y por qué juntar por clave es más seguro que juntar por posición, y vas a entender el peligro que le da nombre a la lección: el reintento parcial, cuando de N ramas unas terminaron y otras no, y volver a intentar re-ejecuta las que ya estaban listas. Y vas a ver cómo el ledger del Módulo 4 —el pizarrón compartido— es exactamente lo que tapa ese hueco.
Esto importa porque el fan-out es de las cosas que mejor se ven en la demo y peor se portan en producción. Repartir un pedido de diez líneas en diez descuentos de inventario funciona precioso cuando las diez salen bien. El problema aparece el día que la línea siete falla: ¿qué pasó con las otras nueve?, ¿se hicieron?, ¿se van a volver a hacer si reintento?, ¿la línea siete se perdió? Sin una respuesta clara a esas preguntas, un fan-out es una fábrica de efectos duplicados y de trabajo perdido. Esta lección te da la respuesta.
Conexión con el módulo: la lección 3 te enseñó a dibujar el grafo; el fan-out es una forma concreta de flecha —una caja que se abre en muchas— y el fan-in es donde esas flechas vuelven a converger. En el grafo de Cumbre, cuando order-triage descuenta inventario línea por línea, eso es un fan-out. El reintento parcial que veremos aquí es un caso particular del efecto duplicado de la lección 1, y su cura es la misma que va a reaparecer en la lección 6 (outbox) y en la 7 (agentes): un efecto idempotente verificado contra el ledger. Aquí lo ves en el contexto de repartir y juntar; en las próximas, en otros contextos. Es el mismo martillo, distintos clavos.
Repartir el trabajo y volver a juntarlo
Piensa en un encargado de compras que tiene que surtir una lista de diez productos, y cada producto está en una tienda distinta. Tiene dos maneras de hacerlo.
La primera: ir él mismo, tienda por tienda, en fila. Compra en la primera, vuelve, compra en la segunda, vuelve. Tarda la suma de los diez viajes. Es sencillo y ordenado, pero lento.
La segunda: mandar a diez ayudantes al mismo tiempo, uno a cada tienda. Eso es un fan-out: un trabajo se abre en muchos que ocurren en paralelo. Tarda lo que tarde el ayudante más lento, no la suma. Pero ahora aparece un problema nuevo que con un solo encargado no existía: cuando los ayudantes empiezan a volver, hay que esperar a que vuelvan todos y juntar las diez bolsas antes de poder decir "la compra está completa". Ese momento de esperar-a-todos-y-juntar es el fan-in.
Y aquí está el detalle que hace interesante la lección: el fan-in es más difícil que el fan-out. Repartir es fácil —mandas a los diez y ya—. Juntar bien es lo difícil: ¿qué haces si un ayudante no vuelve?, ¿esperas para siempre?, ¿das la compra por completa con nueve bolsas?, ¿y si mandas a buscar la décima, cómo evitas que los otros nueve vuelvan a ir a comprar lo que ya trajeron? Todas esas preguntas son las de esta lección, traducidas a workflows.
Fan-out en n8n: dos formas
n8n te da dos maneras de repartir trabajo, y conviene distinguirlas porque se comportan distinto.
Forma A — ramificar dentro de una ejecución
La más simple: un nodo tiene varias conexiones de salida, y cada una arranca una rama distinta del mismo workflow. Las ramas corren dentro de una sola ejecución.
┌──▶ rama 1: check-credit
AI Agent ────────┤
└──▶ rama 2: inventory-sync
Aquí order-triage se abre en dos ramas que hacen cosas distintas con el mismo pedido. Es un fan-out "por trabajo": cada rama es una tarea diferente. Un matiz honesto sobre n8n: aunque el dibujo sugiere paralelismo, dentro de una sola ejecución n8n procesa las ramas una tras otra, no literalmente al mismo tiempo. El "paralelo" es conceptual —las dos ramas son independientes— pero la ejecución las recorre en secuencia. Para la correctitud eso no cambia nada; para la velocidad, sí: no esperes que ramificar dentro de una ejecución te dé la velocidad de diez ayudantes reales. El paralelismo de verdad viene del queue mode, tema de la lección 5.
Forma B — una sub-ejecución por item
La segunda forma reparte por item: tienes N items —por ejemplo, las diez líneas de un pedido— y quieres correr el mismo trabajo una vez por cada uno. Esto se hace con el nodo Execute Sub-workflow en modo Run once for each item: dispara una ejecución del sub-workflow por cada item que entra.
Split líneas del pedido (10 items)
│
▼
Execute Sub-workflow: inventory-sync (Mode: Run once for each item)
└─▶ se ejecuta 10 veces, una por línea
Cada línea del pedido dispara su propia sub-ejecución de inventory-sync. Por defecto, n8n las corre en secuencia —una termina, empieza la siguiente— no todas a la vez. Otra vez: el paralelismo real es asunto del queue mode. Lo que sí ganas con esta forma es aislamiento: cada item es su propia ejecución, con su propio resultado y su propio posible fallo, lo que —como vas a ver— es clave para manejar el reintento parcial.
Hay una tercera pieza que conviene nombrar: el nodo Loop Over Items (Split in Batches), que recorre los items en lotes de un tamaño que tú defines (Batch Size), entregando cada lote por su salida loop y, al terminar todos, combinando el resultado por su salida done. Se usa cuando quieres procesar de a poco —por ejemplo, para respetar el límite de peticiones de una API— en vez de soltar los N de golpe. Su salida done es, en sí misma, una forma de fan-in: junta lo procesado cuando el bucle termina.
Fan-in en n8n: el nodo Merge
El fan-in —volver a juntar lo que se repartió— tiene un nodo dedicado: Merge. Su trabajo es combinar datos de varias entradas en una sola salida, y su característica más importante para nosotros es esta: espera a que todas sus entradas conectadas tengan datos antes de producir su salida. Es, literalmente, el "esperar a que vuelvan todos los ayudantes".
El Merge tiene varios modos, y elegir el correcto es la mitad de hacer un fan-in bien:
| Modo | Qué hace | Cuándo usarlo |
|---|---|---|
Append | Pone los items de cada entrada uno tras otro, en una sola lista | Cuando solo quieres juntar todo en una lista, sin emparejar |
Combine → Matching Fields | Empareja los items de las entradas por el valor de un campo que coincide (por ejemplo, order_id) | Cuando cada resultado tiene que reunirse con su item original por identidad |
Combine → Position | Empareja el item 1 de una entrada con el item 1 de la otra, por su posición en la lista | Solo cuando estás seguro de que el orden se conservó exactamente |
Combine → All Possible Combinations | Produce todas las combinaciones posibles entre las entradas | Casos especiales, poco común en coordinación |
SQL Query | Combina con una consulta SQL que tú escribes | Uniones más complejas |
Choose Branch | Deja pasar los datos de una entrada elegida | Cuando solo te interesa una de las ramas |
De toda esa tabla, quédate con una comparación que es la que más errores evita: Matching Fields vs Position.
Position empareja por orden: el primero con el primero, el segundo con el segundo. Es tentador porque es simple, y es una trampa. Funciona solo si estás absolutamente seguro de que las dos entradas llegaron en el mismo orden y con la misma cantidad de items. En un fan-out donde las ramas corrieron a ritmos distintos, o donde una rama filtró o falló un item, el orden se desalinea, y Position empareja el resultado de la línea 3 con la línea 5 sin avisar. El daño es silencioso: no da error, simplemente junta mal.
Matching Fields empareja por identidad: reúne los items que comparten el mismo valor de una clave —en Cumbre, el order_id o el sku—. No le importa el orden ni si faltan algunos; junta cada resultado con su item por quién es, no por dónde quedó en la fila. Es más robusto, y es la opción por defecto correcta para casi todo fan-in de coordinación. La regla, grábala: junta por clave, no por posición. La posición se desordena; la identidad no.
El reto de verdad: el reintento parcial
Ahora el problema que le da nombre a la lección, y que es donde casi todos los fan-out se rompen en producción.
Imagina que order-triage reparte un pedido de cinco líneas en cinco sub-ejecuciones de inventory-sync, una por línea. Las líneas 1, 2 y 3 descuentan su inventario sin problema. La línea 4 falla —el sistema de bodega tuvo un parpadeo justo en ese momento—. La línea 5 ni siquiera llegó a correr, porque el fallo de la 4 detuvo la cadena.
Estado del mundo en ese instante:
línea 1: descontada ✓
línea 2: descontada ✓
línea 3: descontada ✓
línea 4: FALLÓ ✗
línea 5: sin correr —
Ahora reintenta la ejecución completa —porque eso es lo que hace n8n, o lo que haces tú al ver el error—. ¿Qué pasa? El reintento vuelve a empezar desde el principio: descuenta la línea 1 otra vez, la 2 otra vez, la 3 otra vez, reintenta la 4 (que ahora quizás sí funciona) y por fin corre la 5. Resultado: las líneas 1, 2 y 3 se descontaron dos veces. La bodega quedó con un stock más bajo que el real. El reintento, que era para recuperar la línea 4, terminó duplicando tres efectos que ya estaban bien.
Eso es el reintento parcial: cuando un trabajo repartido falla a la mitad, y reintentar el todo re-ejecuta las partes que ya habían terminado. Es el efecto duplicado de la lección 1, pero nacido de repartir. Y es endémico del fan-out: cuantas más ramas, más probable es que una falle, y más efectos ya-hechos se duplican al reintentar.
Fíjate en por qué el fan-out lo empeora respecto a un flujo lineal. En un flujo de un solo efecto, un reintento repite un efecto. En un fan-out de cinco efectos donde falla el cuarto, un reintento repite tres efectos buenos para recuperar uno malo. La aritmética del daño está en contra tuya.
La cura: el ledger cubre cada rama
La solución no es evitar los reintentos —los necesitas para recuperar la línea 4—. La solución es hacer que reintentar una rama que ya terminó no haga nada. Es decir: que cada efecto sea idempotente, verificado contra el pizarrón compartido.
Esto es exactamente lo que construiste en el Módulo 4. Cada descuento de inventario, antes de ejecutarse, mira el ledger: "¿ya desconté la línea 3 de este pedido?". Si el ledger dice que sí, no lo hace de nuevo. Si dice que no, lo hace y lo anota. La clave de idempotencia aquí no es solo el order_id —eso identificaría el pedido completo— sino algo más fino: el pedido más la línea, por ejemplo ORD-2041:CF-ARA-500. Cada rama del fan-out tiene su propia entrada en el ledger.
Con eso, volvamos a reintentar la ejecución que falló en la línea 4:
Reintento, con el ledger de por medio:
línea 1: el ledger dice "ORD-2041:CF-ARA-500 ya descontada" → no hace nada ✓
línea 2: el ledger dice "ya descontada" → no hace nada ✓
línea 3: el ledger dice "ya descontada" → no hace nada ✓
línea 4: el ledger dice "no registrada" → la descuenta y la anota ✓
línea 5: el ledger dice "no registrada" → la descuenta y la anota ✓
El reintento hizo exactamente lo que debía: recuperó las líneas 4 y 5, y dejó en paz las tres que ya estaban. Las tres primeras se "re-ejecutaron" en el sentido de que el flujo pasó por ellas, pero como cada una consultó el ledger y vio que su trabajo ya estaba hecho, no duplicaron nada. Eso es el pizarrón de la cocina trabajando: cada cocinero mira si el plato ya salió antes de prepararlo.
La estructura de cada rama idempotente, dentro de inventory-sync, es la del Módulo 4:
# Dentro de inventory-sync, por cada línea:
1. Construir la clave: order_id + ":" + sku (ej. "ORD-2041:CF-ARA-500")
2. Postgres: ¿existe esta clave en el ledger?
3. IF ya existe → terminar sin hacer nada (idempotente)
IF no existe → HTTP Request al sistema de bodega (el efecto)
→ Postgres: anotar la clave en el ledger (hecho)
Un recordatorio de las reglas de n8n 2.0 que aplican aquí: el efecto —descontar en la bodega— lo hace un nodo HTTP Request, no un nodo Code, porque desde un nodo Code no puedes hacer peticiones HTTP. La consulta y escritura del ledger las hace un nodo de Postgres, no un nodo Code, porque desde el nodo Code tampoco accedes a la base directamente. El nodo Code, si lo usas, es solo para construir la clave —concatenar order_id y sku, o calcular un hash con crypto, que es de los pocos módulos permitidos—. La regla de fondo: el nodo Code calcula y decide; los efectos y las llamadas los hacen los nodos dedicados.
Ejemplo trabajado: order-triage reparte el inventario por línea
Vamos a construir el fan-out completo y a probar el reintento parcial, para que veas el mecanismo entero.
Paso 1 — Separar las líneas. El pedido de Cumbre trae line_items, una lista. Para repartir por línea, primero las separas en items individuales con un nodo Split Out (el nodo que toma una lista dentro de un item y la convierte en un item por elemento). De un pedido con dos líneas salen dos items:
[
{ "order_id": "ORD-2041", "sku": "CF-ARA-500", "quantity": 12 },
{ "order_id": "ORD-2041", "sku": "TE-CHM-100", "quantity": 6 }
]
Paso 2 — Fan-out por item. Conectas un Execute Sub-workflow en modo Run once for each item apuntando a inventory-sync. Cada línea dispara su propia sub-ejecución.
# Nodo: Execute Sub-workflow — inventory-sync (una por línea)
Source: Database
Workflow: inventory-sync
Mode: Run once for each item
Wait for Sub-Workflow Completion: activado
Workflow Inputs:
order_id = {{ $json.order_id }}
sku = {{ $json.sku }}
quantity = {{ $json.quantity }}
Qué esperar. Al ejecutar con el pedido de dos líneas, vas a ver inventory-sync correr dos veces —dos ejecuciones enlazadas, una por línea—. En el panel de ejecución de order-triage, el nodo Execute Sub-workflow muestra dos items de salida, uno por cada sub-ejecución, cada uno con lo que inventory-sync devolvió para su línea. Si una línea falla, verás su ejecución en rojo mientras las otras quedan en verde: ese aislamiento por item es justo lo que te va a servir.
Paso 3 — El fan-in con Merge. Para juntar los resultados de las N líneas en un solo item de resumen —"pedido ORD-2041: 2 de 2 líneas descontadas"—, usas un Merge. Como cada resultado trae su order_id y su sku, y quieres reunirlos por identidad, usas Combine → Matching Fields sobre la clave que corresponda, no Position. Si solo quieres apilarlos en una lista para contarlos, Append alcanza.
Paso 4 — Provocar el reintento parcial. Ahora la parte que importa. Con un pedido de cinco líneas, haces que la bodega falle en la cuarta (en pruebas, un item con un sku que el sistema de bodega rechace). La ejecución falla en la línea 4. Miras el ledger: las líneas 1, 2 y 3 están anotadas; la 4 y la 5 no.
Qué esperar al reintentar. Reintentas la ejecución. Como inventory-sync consulta el ledger antes de descontar, las líneas 1, 2 y 3 ven su clave ya registrada y no vuelven a descontar —terminan de inmediato, sin tocar la bodega—. La línea 4, ahora que la bodega funciona, se descuenta y se anota. La línea 5 también. Al final, el ledger tiene las cinco líneas anotadas exactamente una vez, y la bodega descontó cada sku una sola vez. Verifícalo consultando el ledger: cinco entradas para ORD-2041, una por sku, sin repetidos. Ese es el fan-out a prueba de reintento parcial.
Cuando una rama nunca vuelve: el fan-in incompleto
Falta el caso más incómodo del fan-in: ¿qué pasa si una rama no vuelve nunca? El ayudante que se fue a la décima tienda y no regresó. El Merge espera a todas sus entradas; si una nunca llega, el Merge nunca produce su salida, y tu fan-in se cuelga.
En n8n esto se manifiesta según cómo armaste el fan-out. Con sub-ejecuciones por item, si una sub-ejecución falla, esa rama no aporta su resultado al Merge, y dependiendo de la configuración el Merge puede quedarse esperando o el fallo puede propagarse. La honestidad aquí es importante: el fan-in perfecto —"espera a todos, y si uno no vuelve en X tiempo, sigue con los que sí"— no sale de un solo nodo en n8n. Hay que diseñarlo.
La forma robusta de diseñarlo evita depender de que el Merge "espere bien", y en su lugar usa el ledger como fuente de verdad del fan-in. En vez de preguntarle al Merge "¿ya volvieron todos?", le preguntas al ledger: "¿cuántas de las cinco líneas de ORD-2041 están anotadas como hechas?". Si están las cinco, el pedido está completo. Si faltan, sabes exactamente cuáles y puedes reintentar solo esas. El ledger no solo cubre el reintento parcial; también es tu medidor de completitud del fan-in: la verdad de "cuánto del trabajo repartido ya se hizo" no vive en un nodo que puede colgarse, vive en una tabla que puedes consultar. Este patrón —el director consulta el ledger para saber si el fan-out terminó— es el que el proyecto de la lección 8 arma completo.
Errores comunes
Juntar por posición en un fan-in donde el orden no está garantizado (práctico). Qué pasa: alguien usa Merge → Combine → Position para reunir los resultados de un fan-out con los items originales, y funciona en las pruebas —donde todo llegó en orden— pero en producción, cuando una rama filtró un item o llegó a destiempo, empareja el resultado de un pedido con los datos de otro. El daño es silencioso: no hay error, los datos simplemente quedan cruzados. Por qué pasa: Position es la opción más simple de configurar y en la demo el orden siempre se conserva. Cómo detectarlo: revisa si tus dos entradas al Merge pueden tener distinta cantidad de items o distinto orden en algún escenario; si la respuesta es "quizás", Position es inseguro. Cómo corregirlo: junta por Matching Fields sobre una clave estable (order_id, sku); emparejar por identidad no se desordena aunque las ramas lleguen en cualquier orden.
Hacer fan-out de efectos sin idempotencia y confiar en que "casi nunca falla una rama" (conceptual). Qué pasa: se reparte un efecto en N ramas sin protección de ledger, razonando que los fallos son raros. En la demo, cero problemas. En producción, la primera vez que una rama falla y alguien reintenta, se duplican todas las ramas que ya habían terminado. Por qué pasa: con pocas ramas y servicios rápidos, el reintento parcial parece improbable, hasta que el volumen y el tiempo lo vuelven inevitable. Cómo detectarlo: por cada rama de tu fan-out que sea un efecto, pregúntate "si reintento la ejecución completa, ¿esta rama vuelve a ejecutar su efecto?". Si la respuesta es sí, no está protegida. Cómo corregirlo: cada rama que sea un efecto pasa por el patrón del Módulo 4 —clave de idempotencia por rama, consulta al ledger, efecto solo si no está hecho—; nunca hagas fan-out de efectos sin eso.
Usar una clave de idempotencia demasiado gruesa para el fan-out (práctico). Qué pasa: alguien protege el fan-out de inventario con la clave order_id, y descubre que solo se descuenta la primera línea: como todas las líneas comparten el mismo order_id, después de la primera el ledger dice "ORD-2041 ya hecho" y las demás se saltan. Por qué pasa: la clave correcta para un flujo de un solo efecto (order_id) es demasiado gruesa cuando el mismo pedido tiene N efectos que deben ocurrir cada uno una vez. Cómo detectarlo: si al proteger un fan-out solo se ejecuta una de las N ramas, la clave es demasiado gruesa. Cómo corregirlo: la clave tiene que identificar la rama, no solo el pedido: order_id más lo que distingue a cada rama (sku, número de línea, tipo de efecto). Una entrada de ledger por unidad de trabajo que debe ocurrir exactamente una vez.
Confiar en que el Merge "espera bien" cuando una rama puede fallar (conceptual). Qué pasa: se arma un fan-in con Merge asumiendo que si una rama falla, el Merge seguirá con las demás, y en cambio la ejecución se cuelga esperando la rama que nunca llegó, o falla entera. Por qué pasa: "espera a que todas las entradas tengan datos" suena a "espera lo que pueda y sigue", y no es eso: es "espera a todas". Cómo detectarlo: prueba tu fan-in matando a propósito una rama y observa qué hace el Merge; si se cuelga o falla, tu diseño depende de que ninguna rama falle. Cómo corregirlo: no hagas del Merge el juez de la completitud; usa el ledger —consulta cuántas ramas están anotadas como hechas— para saber si el fan-out terminó, y reintenta solo las que faltan. El Merge junta datos; el ledger mide completitud.
Ejercicios
Ejercicio 1 — Elige el modo de Merge. Para cada caso, di qué modo de Merge usarías y por qué:
(a) Repartiste un pedido en tres consultas de lectura —crédito, historial y verificación— y quieres reunir las tres respuestas del mismo cliente en un solo item, sabiendo que cada respuesta trae el customer_id.
(b) Corriste dos ramas que producen listas de items y solo quieres apilarlas todas en una sola lista para contarlas.
(c) Tienes dos ramas que devuelven exactamente la misma cantidad de items, en el mismo orden garantizado, y quieres emparejarlas 1 a 1.
Ver solución
(a) Combine → Matching Fields sobre customer_id. Cada respuesta trae la clave del cliente, así que reunirlas por identidad garantiza que juntas las tres respuestas del cliente correcto, sin importar en qué orden llegaron o si alguna se atrasó. Es el caso de libro de Matching Fields.
(b) Append. No quieres emparejar nada, solo poner todos los items de las dos ramas en una sola lista. Append los pone uno tras otro sin intentar relacionarlos, que es exactamente lo que pide "apilar para contar".
(c) Combine → Position, pero con una advertencia. Es el único caso donde Position es defendible: misma cantidad, mismo orden garantizado. Aun así, pregúntate si ese "orden garantizado" lo es de verdad en todos los escenarios —incluyendo reintentos y fallos parciales—; si tienes una clave para emparejar por identidad, Matching Fields sigue siendo más seguro y no cuesta más. Position solo cuando de verdad no hay una clave.
Por qué funciona: los tres casos cubren la decisión real: por identidad (lo normal y seguro), por apilado (cuando no emparejas), por posición (el caso raro y frágil). La lección transversal es que Matching Fields es la opción por defecto y Position la excepción justificada.
Ejercicio 2 — Diagnostica el stock desaparecido. Un fan-out descuenta inventario por línea, sin protección de ledger. Un pedido de ocho líneas falla en la línea 6; alguien reintenta la ejecución completa y el pedido termina "exitoso". Al día siguiente, la bodega reporta que a cinco productos les falta el doble de unidades de lo que debía. Explica exactamente qué pasó, cuántas veces se descontó cada línea, y cuál es el arreglo.
Ver solución
Qué pasó: en el primer intento, las líneas 1 a 5 se descontaron bien, la 6 falló, y las 7 y 8 no llegaron a correr. Al reintentar la ejecución completa —sin protección de ledger— el flujo empezó desde el principio: descontó otra vez las líneas 1, 2, 3, 4 y 5, luego reintentó la 6 (que esta vez funcionó) y corrió la 7 y la 8. Resultado por línea:
líneas 1-5: descontadas DOS veces (una por intento) ✗
línea 6: descontada una vez (falló en el primer intento) ✓
líneas 7-8: descontadas una vez ✓
Por eso a cinco productos —los de las líneas 1 a 5— les falta el doble: se descontaron dos veces. Las líneas 6, 7 y 8 quedaron bien porque solo se descontaron en el reintento. Cinco efectos duplicados para recuperar un fallo; la aritmética del reintento parcial en su forma más cruda.
El arreglo: proteger cada rama con el ledger, usando una clave por línea (order_id:sku). Con eso, en el reintento las líneas 1 a 5 verían su clave ya registrada y no volverían a descontar; solo correrían de verdad la 6, la 7 y la 8. Cada línea quedaría descontada exactamente una vez, sin importar cuántas veces se reintente la ejecución.
Por qué funciona: reconstruiste el conteo por línea —que es la única forma de ver con claridad el daño del reintento parcial— y aplicaste la cura correcta con la clave a la granularidad correcta (por línea, no por pedido). Ese conteo "cuántas veces se ejecutó cada rama" es el reflejo mental que hay que tener frente a todo fan-out.
Ejercicio 3 — Diseña el medidor de completitud. order-triage reparte un pedido de N líneas en N descuentos de inventario. Quieres que el director sepa, de forma confiable, cuándo el pedido está "completamente descontado", incluso si alguna sub-ejecución falló o nunca volvió. Diseña, con cajas y pasos, cómo usarías el ledger —no el nodo Merge— para medir la completitud del fan-out y reintentar solo lo que falta.
Ver solución
Una forma robusta:
# En el director, después del fan-out:
1. Postgres: contar cuántas líneas de este order_id están anotadas
como "hechas" en el ledger.
SELECT count(*) FROM ledger
WHERE order_id = 'ORD-2041' AND effect = 'inventory' AND status = 'done';
2. Comparar contra el total esperado (N líneas del pedido).
3. IF count = N → el pedido está completamente descontado. Terminar.
IF count < N → faltan (N - count) líneas.
→ Postgres: obtener qué sku's del pedido NO están en el ledger.
→ Fan-out SOLO sobre esas líneas faltantes (reintento dirigido).
→ volver al paso 1.
Los puntos clave del diseño: la verdad de "cuántas líneas están hechas" vive en el ledger, una tabla que puedes consultar en cualquier momento, no en el Merge, que puede colgarse esperando una rama que no vuelve. El director mide completitud contando filas, y cuando algo falta, reintenta solo lo que falta —no la ejecución completa— porque el ledger le dice exactamente qué skus no están registrados. Eso evita re-ejecutar las líneas buenas incluso en el reintento dirigido. Y como cada rama sigue siendo idempotente por su clave, aunque el reintento dirigido se solapara con una sub-ejecución rezagada, no habría duplicado.
Por qué funciona: separaste dos trabajos que el fan-in ingenuo mezcla —juntar datos (eso lo hace Merge) y medir completitud (eso lo hace el ledger)— y pusiste la completitud donde no puede colgarse. Este diseño es, en esencia, el esqueleto del proyecto de la lección 8, así que si lo entendiste aquí, llegas con ventaja.
Resumen y siguiente paso
En esta lección repartiste trabajo y lo volviste a juntar. El fan-out en n8n tiene dos formas: ramificar dentro de una ejecución (varias salidas de un nodo, que n8n recorre en secuencia) y disparar una sub-ejecución por item (Execute Sub-workflow en Run once for each item), más el Loop Over Items (Split in Batches) para procesar por lotes. El fan-in lo hace el nodo Merge, que espera a todas sus entradas; y su decisión clave es juntar por Matching Fields (identidad, robusto) en vez de por Position (orden, frágil). El peligro central es el reintento parcial: cuando un fan-out falla a la mitad, reintentar el todo re-ejecuta las ramas que ya habían terminado y duplica sus efectos —y cuantas más ramas, peor la aritmética—. La cura es la del Módulo 4: cada rama idempotente, con una clave a la granularidad de la rama (order_id:sku), verificada contra el ledger. Y viste que el ledger hace doble trabajo: cubre el reintento parcial y, además, es el medidor de completitud del fan-in, más confiable que un Merge que puede colgarse si una rama no vuelve.
Antes de avanzar a la lección 5 deberías poder: nombrar las dos formas de fan-out en n8n; explicar por qué Matching Fields es más seguro que Position; describir el reintento parcial y por qué el fan-out lo empeora; y decir a qué granularidad va la clave de idempotencia de un fan-out y por qué.
La lección 5 se mete en un problema de ritmo que el fan-out hace más agudo: qué pasa cuando los eventos llegan más rápido de lo que se procesan. Vas a ver la contrapresión —la acumulación que se forma cuando el que produce va más rápido que el que consume—, cómo se pierde el orden en el camino, y cómo el queue mode de n8n da capacidad y control de ritmo. Y vas a ver una consecuencia incómoda que conecta directo con esta lección: el queue mode, al correr cosas de verdad en paralelo, no garantiza el orden por sí solo, y eso hay que resolverlo en la capa de datos —otra vez, con el ledger—.
Recursos
- Merge node — n8n Docs — el nodo del fan-in: sus modos
Append,Combine(conMatching Fields,Position,All Possible Combinations),SQL QueryyChoose Branch, y su comportamiento de esperar a todas las entradas. Verifica los nombres exactos de los modos en tu versión. - Execute Sub-workflow node — n8n Docs — el fan-out por item con el modo
Run once for each item, que dispara una sub-ejecución por cada item de entrada. - Loop Over Items (Split in Batches) — n8n Docs — el nodo para procesar en lotes de tamaño
Batch Size, con su salidalooppara cada lote y su salidadoneque junta el resultado al terminar. - Split Out node — n8n Docs — el nodo que convierte una lista dentro de un item (como
line_items) en un item por elemento, el primer paso del fan-out por línea. - Postgres node — n8n Docs — el nodo con el que cada rama consulta y escribe el ledger; recuerda que el nodo Code no accede a la base ni hace HTTP, así que la consulta al ledger y el efecto van por nodos dedicados.