Módulo 4: El modelo de datos del sistema
1. Introducción: dónde vive la verdad del sistema
Descripción
Al terminar esta lección vas a poder explicar por qué la idempotencia y la deduplicación —los dos temas que trabajaste en los módulos anteriores— necesitan un lugar físico donde vivir, y por qué ese lugar no puede ser la memoria del workflow. Vas a tener el mapa completo de las ocho lecciones de este módulo, vas a saber qué vas a construir al final (un run ledger y una tienda de deduplicación en una base de datos real), y vas a conocer el stack local, a costo cero, sobre el que vas a montar todo: el Postgres que ya trae el Self-Hosted AI Starter Kit v2.
Esto importa por una razón muy concreta. En el módulo 2 hiciste idempotente un paso que crea registros: antes de actuar, el workflow se preguntaba "¿ya hice esto?". Pero esa pregunta no tiene sentido si no hay dónde guardar la respuesta. Un workflow que se pregunta "¿ya procesé este pedido?" y no tiene memoria persistente es como un cajero que decide si ya te cobró mirando únicamente lo que recuerda de esta mañana: en cuanto se va a comer, olvida todo. La idempotencia entre ejecuciones distintas —el caso real, el del webhook que se dispara dos veces con diez minutos de diferencia— exige un lugar donde la verdad del sistema quede escrita y sobreviva a que la ejecución termine. Este módulo construye ese lugar.
Conexión con el módulo: esta lección es el mapa, no el territorio. Aquí planteas el problema (la idempotencia necesita estado persistente y el workflow no lo tiene de forma confiable), conoces el caso que vas a resolver y recibes el plan de las siete lecciones que siguen. La lección 2 es el corazón del argumento: por qué Static Data y las variables no alcanzan. La lección 3 diseña el run ledger. Las lecciones 4 y 5 construyen la tienda de deduplicación y el patrón que la hace atómica. La lección 6 conecta todo al Postgres local del Starter Kit. La lección 7 aplica el mismo patrón a un pipeline de RAG. Y la lección 8 lo junta en un proyecto: un ledger de deduplicación para un webhook que dispara doble. Una nota de límite desde ya: este módulo no te enseña SQL desde cero ni a operar una base de datos en producción —eso es el ecosistema de datos y la guía de operaciones—. Aquí usas Postgres como la herramienta que sostiene la correctitud del sistema, con el mínimo de SQL que ese trabajo necesita, explicado pieza por pieza.
Por qué la memoria del workflow no es un buen lugar para la verdad
Vamos a empezar por el problema, porque es el que le da sentido a todo lo demás.
Piensa en dos maneras de llevar la cuenta de a quién ya le entregaste un paquete. La primera: lo recuerdas de memoria mientras haces el reparto de la mañana. Funciona bien durante un rato —sabes que a la señora del 3B ya le dejaste su caja—. Pero en cuanto terminas el turno, cierras la camioneta y te vas a casa, esa memoria se borra. Mañana empiezas de cero. Si por error te toca repetir una entrega del día anterior, no tienes forma de saberlo: tu única fuente de verdad era tu cabeza, y tu cabeza ya se reinició.
La segunda manera: cada entrega la anotas en una libreta, con la fecha y la firma de quien recibió. La libreta no se borra cuando termina tu turno. Al día siguiente, antes de dejar un paquete, puedes abrirla y preguntar: "¿esta dirección ya recibió este pedido?". Si la respuesta está escrita, no repites. La libreta sobrevive a tus turnos; tu memoria, no.
En n8n pasa exactamente lo mismo, y es el punto que organiza este módulo entero. Cada vez que un workflow se dispara, arranca una ejecución nueva. Esa ejecución tiene su propia memoria: los items que fluyen entre nodos, las variables que calculas, lo que un nodo Code guarda en una constante. Todo eso vive mientras la ejecución corre y desaparece cuando termina. Es la memoria de la mañana del repartidor. Sirve para lo que pasa dentro de una ejecución, y no sirve para nada que tenga que recordarse entre ejecuciones.
Esto conecta directo con el modelo de ejecución que viste en el módulo 1. Ahí quedó claro que cada disparo produce una ejecución aislada, con su propio historial en n8n, y que dos ejecuciones del mismo workflow no se conocen entre sí: no hay una variable compartida que una escriba y la otra lea. Lo que en el módulo 1 era una observación sobre cómo corre n8n, aquí se vuelve el problema central. Porque la aislación que hace robusto a n8n —que un fallo en una ejecución no contamine a otra— es la misma que impide que una ejecución le avise a la siguiente "oye, este pedido yo ya lo cobré". Esa aislación es una virtud del motor, y el precio de esa virtud es que la memoria compartida tienes que ponerla tú, afuera.
Y aquí está el problema, porque el caso que nos importa vive justo del lado equivocado de esa frontera.
Ejemplo trabajado: el pedido que se procesa dos veces
Vamos a poner sobre la mesa el caso que vas a resolver durante todo el módulo. Es el mismo de las guías anteriores: Cumbre, la distribuidora mayorista de café y té, y su workflow order-triage.
order-triage hace tres cosas, en orden:
Webhook ──► AI Agent ──► HTTP Request: "Create charge in CRM"
(entra un (clasifica (crea un cobro real por el pedido)
pedido) el pedido)
El pedido entra por un webhook. Un AI Agent lo clasifica —prioridad, ruta, lo que sea—. Y al final, un nodo HTTP Request llama al CRM de Cumbre y crea un cobro. Ese último paso es un efecto: mueve dinero en el mundo real. No es una lectura que puedas repetir sin consecuencias; es una acción que, repetida, cobra dos veces.
Ahora el escenario que rompe todo. El webhook se dispara dos veces por el mismo pedido. Puede pasar por muchas razones que ya viste en el módulo 1: el sistema que llama reintentó porque no recibió la respuesta a tiempo, la red duplicó el mensaje, alguien hizo doble clic. El punto es que llegan dos disparos con el mismo order_id, digamos ORD-2041, separados por unos minutos.
Cada disparo arranca una ejecución distinta. Y aquí está el detalle que hace tan difícil el problema: la primera ejecución ya terminó cuando llega la segunda. Su memoria —todo lo que "sabía"— ya se borró. La segunda ejecución no tiene forma de saber que la primera existió, a menos que la primera haya dejado algo escrito en un lugar que sobreviva.
Sin ese lugar, esto es lo que pasa:
09:12 Ejecución #1 → ORD-2041 → crea cobro → termina (y olvida todo)
09:19 Ejecución #2 → ORD-2041 → crea cobro → termina
▲
segundo cobro por el mismo pedido
Qué esperar. Dos ejecuciones exitosas, las dos en verde en el historial de n8n, sin un solo error. Y dos cobros en el CRM por un pedido que el cliente hizo una sola vez. Es el peor tipo de falla: silenciosa. Nada se rompió. El sistema hizo exactamente lo que le pediste dos veces, porque nunca le diste manera de recordar que ya lo había hecho una.
La pregunta que resuelve este módulo es directa: ¿dónde tiene que vivir la frase "ORD-2041 ya fue procesado" para que la segunda ejecución pueda leerla? No en la memoria de la primera ejecución, que ya no existe. Tiene que vivir en un lugar externo, persistente, compartido: una base de datos. La libreta del repartidor.
Qué vas a construir en este módulo
La respuesta de este módulo tiene dos piezas, y vas a construir las dos sobre el Postgres local.
Un run ledger (registro de ejecuciones). Una tabla que anota qué corrió, con qué clave, en qué estado y con qué resultado. Es la libreta completa: no solo dice "esto ya pasó", sino "esto pasó, a esta hora, y terminó así". La lección 3 lo diseña.
Una tienda de deduplicación (dedup store). Una tabla más pequeña y más filosa, pensada para una sola pregunta: "¿ya vi esta clave?". Su truco es una restricción de unicidad en la base de datos que convierte la pregunta "¿ya existe?" y la acción "márcalo como visto" en un solo paso indivisible. Las lecciones 4 y 5 la construyen, y ahí vas a ver por qué ese "un solo paso" resuelve una trampa que en el módulo 2 quedó abierta.
Las dos tablas se complementan, y conviene tener claro desde ya en qué se diferencian:
run_ledger (lección 3) | processed_orders (lección 5) | |
|---|---|---|
| Pregunta que responde | "¿Qué corrió, cuándo y cómo terminó?" | "¿Ya vi esta clave, sí o no?" |
| Cuánto guarda por ejecución | Bastante: clave, estado, resultado, tiempos | Lo mínimo: la clave y poco más |
| Para qué la consultas | Auditar, entender qué pasó, recuperar un resultado | Decidir en un instante si actúas o descartas |
| Analogía | El libro de contabilidad completo | La lista de "ya atendidos" en la puerta |
No compiten: muchas veces las vas a usar juntas. La tienda de dedup te da el "sí/no" rápido antes de actuar; el ledger te da la historia completa para cuando alguien pregunte qué pasó con ORD-2041. Este módulo te enseña a construir y a combinar las dos.
Con esas dos piezas, order-triage cambia de forma. Antes de crear el cobro, consulta la tienda de deduplicación:
Webhook ──► ¿ORD-2041 ya está en la tienda de dedup?
│
├─ No → márcalo → AI Agent → crea el cobro
│
└─ Sí → descártalo, no hagas nada
La segunda ejecución llega, hace la misma pregunta, y esta vez la respuesta es "sí". No crea el segundo cobro. La verdad —"ORD-2041 ya fue procesado"— vivió fuera de las dos ejecuciones, en un lugar que las dos pudieron consultar. Eso es todo el módulo, en una imagen.
Estado de ejecución y estado del sistema: la distinción que lo ordena todo
Hay una distinción que, una vez que la ves, ordena no solo este módulo sino toda tu forma de diseñar automatizaciones. Vale la pena hacerla explícita, porque es la que separa a quien arma flujos de quien es dueño de un sistema.
En cualquier automatización hay dos clases de datos conviviendo, y confundirlas es el origen de casi todos los bugs de duplicado.
El estado de ejecución es todo lo que existe mientras una ejecución corre y solo tiene sentido dentro de ella. El pedido que entró por el webhook, el total que calculaste, la respuesta del AI Agent, el cuerpo que armaste para la llamada al CRM. Nace cuando la ejecución arranca y muere cuando termina. Es correcto que muera: si el mismo pedido se procesa de nuevo, todo eso se vuelve a calcular sin problema. Este estado vive de forma natural en los items de n8n y en las variables de tus nodos.
El estado del sistema es lo que tiene que ser verdad entre ejecuciones, lo que define qué ha hecho el sistema a lo largo del tiempo. "Ya cobré ORD-2041". "Ya embebí este documento". "El último pedido que sincronicé con el CRM fue el ORD-2040". Este estado no puede morir con la ejecución, porque su única razón de existir es que la siguiente ejecución pueda consultarlo. Si vive en la memoria de una ejecución, es como escribir el inventario de la bodega en la palma de la mano de quien sale de turno: nadie del siguiente turno lo puede leer.
Piénsalo con una imagen de una tienda. El estado de ejecución es la conversación con un cliente en la caja: los productos que va pasando, el subtotal que se acumula, el cambio que le das. Cuando el cliente se va, esa conversación se acaba y está bien que se acabe. El estado del sistema es el inventario y el libro de ventas: no pertenecen a ninguna conversación en particular, sobreviven a todos los clientes del día, y son lo que le da continuidad al negocio. Si al cerrar la caja se borrara el inventario, la tienda no podría abrir mañana.
El error de diseño que este módulo previene se puede decir en una frase: tratar el estado del sistema como si fuera estado de ejecución. Guardar "ya cobré ORD-2041" en una variable del nodo Code —que es estado de ejecución— es exactamente ese error. Funciona en la demo, donde pruebas todo en una sola corrida, y falla en producción, donde cada disparo es una ejecución nueva que no hereda la memoria de la anterior.
Y aquí hay una trampa que la lección 2 desarma en detalle. n8n parece ofrecer un lugar para el estado del sistema sin salir del workflow: $getWorkflowStaticData(). Su nombre lo sugiere —"datos estáticos del workflow", algo que persiste—. La lección 2 muestra por qué esa promesa no se sostiene para un sistema idempotente serio, empezando por un hecho que sorprende a casi todos: Static Data ni siquiera se guarda cuando pruebas el workflow desde el editor. Por ahora quédate con la distinción, que es lo importante: hay estado que muere con la ejecución y estado que tiene que sobrevivirla, y el segundo necesita un hogar de verdad.
El stack local: dónde va a vivir esa verdad, a costo cero
Podrías pensar que "una base de datos real" significa contratar un servicio, pagar una mensualidad y configurar un servidor. Para aprender y para la mayoría de los equipos pequeños, no. Ya tienes una base de datos de sobra, y probablemente ni sabías que la tenías corriendo.
El Self-Hosted AI Starter Kit v2 es una plantilla oficial de n8n que levanta, con un solo comando, un entorno completo de automatización con IA en tu propia máquina. Según su documentación, incluye cuatro piezas:
| Servicio | Qué es | Para qué lo usamos en este módulo |
|---|---|---|
| n8n | La plataforma de workflows que ya conoces | Donde corren order-triage y los flujos de este módulo |
| PostgreSQL | Una base de datos relacional, robusta y madura | El hogar del run ledger y la tienda de dedup |
| Qdrant | Un almacén de vectores para búsqueda semántica | La ingesta RAG idempotente de la lección 7 |
| Ollama | Un motor para correr modelos de lenguaje locales | Genera los embeddings de la lección 7, sin API de pago |
Fíjate en algo importante: n8n, cuando lo levantas con el Starter Kit, ya guarda sus propios workflows y ejecuciones en ese Postgres. La base de datos no es un extra que tengas que montar; está ahí, corriendo, desde el primer minuto. Lo único nuevo que vas a hacer es crear un par de tablas tuyas —run_ledger y processed_orders— en esa misma base, y conectar unos nodos Postgres a ellas. La lección 6 es la que hace ese cableado paso a paso.
Y el costo de todo esto es cero. Corre en tu computadora, con Docker, sin cuentas de nube ni tarjetas de crédito. Al momento de escribir esta guía —julio de 2026— el Starter Kit se levanta con docker compose --profile cpu up y n8n queda disponible en http://localhost:5678. Como con cualquier dato de versión, es probable que los detalles cambien con el tiempo; el patrón —un stack local con Postgres incluido— es lo que se transfiere. La lección 6 marca con honestidad qué confirmar en tu propia instalación.
Por qué una base de datos relacional, y no una hoja de cálculo o un archivo
Una pregunta legítima antes de seguir: si lo único que necesitas es anotar "ya vi esta clave", ¿por qué Postgres y no algo más simple, como una hoja de Google o un archivo? La respuesta adelanta lo que hace especial a este módulo, y conviene tenerla desde ya.
Una base de datos relacional como Postgres tiene tres capacidades que una hoja de cálculo no te da, y las tres son exactamente las que la idempotencia necesita.
La restricción de unicidad. Puedes decirle a una tabla "esta columna no admite valores repetidos, jamás", y la base de datos lo hace cumplir por ti. No es una regla que tú programes y esperes recordar aplicar; es una propiedad de la tabla que la base garantiza en cada escritura. Cuando en la lección 5 marques la columna idempotency_key como única, Postgres se vuelve tu aliado: rechaza el segundo ORD-2041 sin que tú tengas que verificar nada. Una hoja de cálculo te deja meter la misma fila mil veces sin quejarse.
La atomicidad. Postgres puede hacer "verifica si existe y, si no, insértalo" como una sola operación indivisible, imposible de interrumpir a la mitad. Esto suena menor y es el corazón de todo el módulo. En una hoja de cálculo, "leer si ya está" y "escribir que ahora está" son dos pasos separados, y entre esos dos pasos puede colarse el segundo disparo del webhook —y duplicar—. Esa es, exactamente, la trampa de "verificar y luego actuar" que el módulo 2 dejó marcada y que la lección 5 cierra con ON CONFLICT.
La durabilidad seria. Cuando Postgres te confirma que guardó algo, lo guardó de verdad, con garantías pensadas para no perder datos ante un corte. Un archivo suelto o una hoja compartida no te dan ese nivel de seguridad sobre lo que quedó escrito y lo que no.
No es que una hoja de cálculo esté "mal" —para muchas cosas es perfecta, y de hecho n8n tiene nodos excelentes para hablarle a Google Sheets—. Es que la idempotencia se apoya justo en las tres cosas que una base de datos relacional hace bien y una hoja no: unicidad garantizada, operaciones atómicas y durabilidad seria. Por eso el hogar de la verdad del sistema es Postgres, y por eso el Starter Kit, que lo trae incluido, es tan cómodo para aprender esto. Si en tu trabajo ya usas otra base de datos relacional —MySQL, SQL Server— los conceptos se transfieren tal cual; cambian los detalles de sintaxis, no el patrón.
El mapa de este módulo
| Lección | Qué resuelve |
|---|---|
| 2 | Por qué Static Data, $vars y la memoria del workflow no alcanzan para la verdad del sistema, con la prueba concreta de que Static Data no se guarda al probar desde el editor |
| 3 | Diseñar el run ledger: qué columnas tiene, qué estados registra (pendiente / hecho / fallido) y por qué es la fuente única de verdad de lo que ya pasó |
| 4 | Las tres estrategias de deduplicación —ventana de tiempo, clave-ya-vista y el nodo Remove Duplicates— y dónde se queda corta cada una |
| 5 | La tienda de dedup entre ejecuciones: INSERT ... ON CONFLICT DO NOTHING como el paso atómico que resuelve la trampa de "verificar y luego actuar" |
| 6 | El stack local: conectar los nodos Postgres al Postgres del Starter Kit, con la credencial paso a paso |
| 7 | Ingesta RAG idempotente: no re-embeber un documento ya procesado, usando un hash de contenido como clave |
| 8 | Proyecto: el ledger de dedup completo para un webhook que dispara doble, probado de punta a punta |
El orden no es casual. Primero el argumento (lección 2): por qué necesitas una base de datos y no puedes evitarla con un truco dentro del workflow. Después el diseño de las dos tablas (lecciones 3 a 5). Después el cableado al stack real (lección 6). Después una aplicación del mismo patrón en un dominio distinto, el RAG (lección 7). Y al final, el proyecto que lo integra (lección 8).
Qué vas a poder hacer al terminar el módulo
La capacidad de salida es concreta y verificable. Al final de la lección 8 vas a poder:
- Explicar, con un ejemplo, por qué la memoria de una ejecución no sirve para deduplicar entre ejecuciones distintas, y por qué Static Data no cierra ese hueco de forma confiable.
- Diseñar un run ledger en Postgres: sus columnas, sus estados y su clave única.
- Construir una tienda de deduplicación que, con una restricción de unicidad y
ON CONFLICT DO NOTHING, decide en un solo paso atómico si una clave es nueva o repetida. - Conectar un nodo Postgres al Postgres local del Starter Kit y usarlo para consultar y registrar claves.
- Aplicar el mismo patrón a una ingesta de RAG para no re-embeber un documento que ya procesaste.
Lo que no vas a hacer en este módulo, y está bien: administrar Postgres en producción —backups, réplicas, tuning—, ni escribir SQL avanzado. Vas a usar el SQL justo para sostener la idempotencia, y cada consulta viene explicada. Si al terminar sientes que entiendes por qué cada tabla existe y qué hace cada consulta, aunque no te sientas un experto en bases de datos, ese es exactamente el resultado esperado.
Errores comunes
Creer que la idempotencia se resuelve "dentro del código" (conceptual). Qué pasa: alguien termina el módulo 2, entiende la idea de "verificar antes de actuar", y la implementa con una variable o una lista dentro de un nodo Code, convencido de que con eso basta. Por qué pasa: dentro de una sola ejecución, esa lógica funciona —si el mismo pedido aparece dos veces en el mismo lote, la puedes filtrar—. El engaño es que el caso real no es ese. Cómo detectarlo: pregúntate si tu deduplicación sobrevive a que la ejecución termine. Si la respuesta vive en una variable del nodo Code, no sobrevive. Cómo corregirlo: acepta desde ya que la verdad del sistema tiene que salir del workflow y vivir en una base de datos. Es el argumento completo de la lección 2, y es la razón de ser de este módulo.
Confundir "el sistema ya lo sabe" con "yo lo puedo consultar" (conceptual). Qué pasa: el CRM de Cumbre, por dentro, seguramente tiene el pedido ORD-2041 registrado; alguien concluye que entonces el workflow "ya tiene" la información y no hace falta un ledger propio. Por qué pasa: es cierto que el dato existe en algún lado. El problema es el costo y la fiabilidad de consultarlo a tiempo, antes de actuar, en cada disparo. Cómo detectarlo: si tu plan para no duplicar es "el sistema de destino lo va a rechazar", estás delegando tu correctitud a otro sistema que quizás no la garantiza. Cómo corregirlo: en las lecciones 4 y 5 vas a ver por qué conviene que tu workflow tenga su propia tienda de dedup, barata y bajo tu control, en vez de depender de una consulta remota en cada llamada.
Pensar que "base de datos" implica dinero y servidores (conceptual). Qué pasa: alguien pospone todo el módulo porque cree que necesita contratar una base de datos en la nube. Por qué pasa: la palabra "base de datos" suena a infraestructura cara. Cómo detectarlo: si tu obstáculo para empezar es "no tengo una base de datos", casi seguro sí la tienes. Cómo corregirlo: el Starter Kit v2 trae Postgres corriendo en tu máquina, a costo cero. Ya está ahí. La lección 6 solo te muestra cómo apuntar un nodo hacia él.
Ejercicios
Ejercicio 1 — Traza la vida de una verdad. Para el caso de order-triage con el doble disparo, escribe en una frase cada una: (a) qué información necesita conocer la segunda ejecución para no duplicar el cobro, (b) en qué momento exacto la primera ejecución debería escribir esa información, y (c) por qué esa información no puede vivir dentro de la primera ejecución.
Ver solución
(a) La segunda ejecución necesita saber que el order_id ORD-2041 ya fue procesado (o al menos, que ya se inició su procesamiento). Es un solo hecho: "esta clave ya se vio".
(b) La primera ejecución debería escribir ese hecho antes de crear el cobro, no después. La razón fina la vas a ver en la lección 5: si escribes después del efecto y el segundo disparo llega en medio, todavía puedes duplicar. El registro va primero, el efecto después.
(c) No puede vivir dentro de la primera ejecución porque esa ejecución ya terminó cuando llega la segunda, y con ella se borró toda su memoria. Dos ejecuciones distintas no comparten memoria; solo pueden comunicarse a través de algo externo que sobreviva a las dos. Ese "algo" es la base de datos.
Por qué funciona: fíjate en que las tres respuestas apuntan al mismo lugar. El qué (un hecho pequeño), el cuándo (antes del efecto) y el dónde (fuera de la ejecución) son las tres decisiones que definen una tienda de deduplicación. Ya las tienes planteadas; el módulo solo las hace concretas.
Ejercicio 2 — Clasifica dónde vive cada dato. Para cada uno de estos datos de order-triage, decide si tiene sentido que viva en la memoria de la ejecución (se borra al terminar) o en una base de datos (sobrevive). Justifica en una frase.
(a) El total del pedido, que calculas sumando las líneas para armar el cobro.
(b) El hecho de que ORD-2041 ya fue procesado.
(c) La clasificación de prioridad que devolvió el AI Agent para este pedido.
(d) La cuenta de cuántos pedidos procesaste en total desde que existe el workflow.
Ver solución
(a) Memoria de la ejecución. El total se calcula, se usa para armar el cobro dentro de esta misma ejecución, y no hace falta que sobreviva: si el pedido se vuelve a procesar, se recalcula. Es un dato de tránsito.
(b) Base de datos. Es exactamente la verdad que tiene que sobrevivir entre ejecuciones para que la segunda no duplique. Es el caso central del módulo.
(c) Depende de para qué. Si solo la usas dentro de esta ejecución para decidir la ruta, memoria. Si quieres poder responder después "¿qué prioridad le dimos a ORD-2041?", entonces conviene guardarla —y de hecho el run ledger de la lección 3 tiene una columna result precisamente para eso—. Este es un buen ejemplo de que la frontera no siempre es obvia.
(d) Base de datos. Una cuenta acumulada "desde siempre" es, por definición, algo que tiene que sobrevivir a cada ejecución. Si vive en memoria, cada ejecución empezaría a contar desde cero. La lección 2 muestra por qué intentar llevar esta cuenta con Static Data es frágil.
Por qué funciona: la pregunta que estás practicando es una sola —¿este dato tiene que recordarse después de que la ejecución termine?—. Si la respuesta es sí, es base de datos. Si es no, es memoria. Casi todo el diseño de estado de un sistema se reduce a hacer bien esa clasificación.
Ejercicio 3 — Anticipa el módulo. Sin volver a mirar la tabla del mapa, escribe de memoria qué crees que resuelve cada lección de la 2 a la 8, en una frase. Después compara y marca las que se te escaparon.
Ver solución
Una versión de referencia: (2) por qué la memoria del workflow y Static Data no alcanzan; (3) el diseño del run ledger; (4) las estrategias de deduplicación y sus límites; (5) la tienda de dedup con ON CONFLICT; (6) conectar todo al Postgres local del Starter Kit; (7) aplicar el patrón a la ingesta de RAG; (8) el proyecto integrador con el webhook que dispara doble.
Por qué funciona: si reconstruiste al menos cinco de las siete, ya tienes internalizada la progresión —del argumento (¿por qué una base de datos?) al diseño (las dos tablas) al cableado (el stack real) a la aplicación (RAG) al proyecto—. Las que más se escapan suelen ser la 4 y la 7, porque son las que parecen "de paso"; en realidad la 4 te da el criterio para elegir estrategia y la 7 te demuestra que el patrón es general, no un truco para un solo caso.
Resumen y siguiente paso
En esta lección planteaste el problema que organiza todo el módulo. La idempotencia y la deduplicación que trabajaste antes necesitan un lugar donde viva la verdad del sistema, y ese lugar no puede ser la memoria del workflow, porque cada ejecución arranca de cero y olvida todo al terminar. Lo viste con order-triage: dos disparos del mismo pedido ORD-2041 arrancan dos ejecuciones que no comparten memoria, y sin un registro externo, la segunda crea un segundo cobro sin que nada falle visiblemente.
La solución tiene dos piezas que vas a construir: un run ledger, la libreta completa que anota qué corrió y cómo terminó; y una tienda de deduplicación, la tabla filosa que responde "¿ya vi esta clave?" en un solo paso atómico. Las dos viven en el Postgres que ya trae el Self-Hosted AI Starter Kit v2, corriendo en tu máquina a costo cero, junto con Qdrant y Ollama que vas a usar en la lección 7.
Antes de avanzar deberías poder: explicar por qué dos ejecuciones distintas no comparten memoria; nombrar las dos tablas que vas a construir y para qué sirve cada una; y decir cuáles cuatro servicios trae el Starter Kit.
La lección 2 es el argumento que hace inevitable todo lo demás. Ahí vas a ver de frente por qué $getWorkflowStaticData() —que parece la respuesta natural de n8n a "necesito guardar algo entre ejecuciones"— no es un lugar confiable para la verdad de un sistema idempotente. Vas a ver la prueba concreta: que ni siquiera se guarda cuando pruebas el workflow desde el editor. Y con eso claro, la base de datos deja de ser una opción entre varias y pasa a ser la única respuesta seria.
Recursos
- Deploy with the AI starter kit — n8n Docs — la guía oficial del Self-Hosted AI Starter Kit: qué servicios trae (n8n, PostgreSQL, Qdrant, Ollama) y cómo levantarlo con Docker en tu máquina.
- Self-hosted AI Starter Kit — n8n Blog — la presentación del kit, útil para entender qué resuelve cada componente y por qué vienen juntos.
- self-hosted-ai-starter-kit — GitHub — el repositorio con el
docker-compose.ymlreal; ahí confirmas los servicios y los comandos vigentes para tu versión. - Postgres node — n8n Docs — el nodo con el que vas a hablarle a la base de datos: sus operaciones y cómo pasa parámetros de forma segura. Lo usas de la lección 3 en adelante.
- getWorkflowStaticData — n8n Docs — el mecanismo de Static Data que la lección 2 examina y descarta como hogar de la verdad del sistema.