Módulo 4: El modelo de datos del sistema

6. El stack local para el estado, a costo cero

Descripción

Al terminar esta lección vas a tener el ledger y la tienda de dedup que diseñaste viviendo en una base de datos real —el Postgres que ya trae el Self-Hosted AI Starter Kit v2— corriendo en tu propia máquina, sin pagar nada. Vas a saber qué servicios trae el Starter Kit y para qué sirve cada uno, cómo n8n ya usa ese Postgres por dentro, cómo crear una credencial de Postgres en n8n y apuntarla al Postgres empaquetado, y cómo ejecutar el SQL que crea tus tablas. Vas a tener las señales visuales que confirman que cada paso funcionó, para que no te quedes con la duda de si avanzaste o algo se rompió.

Esto importa porque hasta ahora diseñaste el mecanismo en abstracto —tablas, sentencias, marcadores de parámetro— y un diseño que no está enchufado a nada no deduplica nada. Esta es la lección que convierte el plano en una instalación funcionando. Y la hace de la forma más accesible posible: no montas ni contratas una base de datos, porque ya tienes una corriendo. El Starter Kit la levantó junto con n8n desde el primer minuto; lo único que falta es apuntar tus nodos hacia ella.

Conexión con el módulo: las lecciones 3 y 5 diseñaron las tablas run_ledger y processed_orders y las sentencias que las usan; esta les da un hogar físico. La lección 7 va a usar otros dos servicios del mismo Starter Kit —Qdrant y Ollama— para la ingesta RAG idempotente, así que aquí también los conoces de pasada. Y el proyecto de la lección 8 corre entero sobre lo que montas aquí. Es la lección más procedimental del módulo: menos concepto nuevo, más "haz esto en tu máquina y confirma que funcionó".

Qué gusto, vamos a enchufar el estado

Antes de tocar nada, una tranquilidad. Vamos a hacer algo muy satisfactorio: tomar el mecanismo que diseñaste en las lecciones anteriores y verlo cobrar vida sobre una base de datos real, en tu propia máquina. Esta lección es de instalación y configuración, y ese tipo de tareas tiene fama de frustrantes porque cada computadora es un mundo: una ruta distinta, un puerto ocupado, una versión que cambió el nombre de un botón. Voy a darte el camino feliz —lo que funciona para la mayoría— y, cuando algo pueda variar, te lo voy a decir con honestidad para que sepas dónde mirar en vez de sentir que hiciste algo mal. Si te topas con un error muy raro que no está en la guía, es esperable: usa la documentación oficial, tu asistente de IA o la comunidad de n8n. El fracaso de entorno no es culpa tuya; es la naturaleza del setup.

Y una honestidad de fechas, porque este contenido envejece más rápido que el resto: al momento de escribir esta guía —julio de 2026— el Starter Kit se levanta con docker compose --profile cpu up, n8n queda en http://localhost:5678, y los servicios se llaman como los vas a ver abajo. Es probable que con el tiempo cambien nombres, comandos o rutas. Cuando algo no coincida con lo que ves en tu pantalla, la fuente de verdad es el repositorio oficial del Starter Kit y la documentación de n8n, no esta guía. Marco los datos volátiles a propósito para que sepas cuáles verificar.

El Starter Kit v2: cuatro servicios que ya están corriendo

El Self-Hosted AI Starter Kit es una plantilla oficial de n8n: un archivo docker-compose.yml que, con un comando, levanta un entorno completo de automatización con IA en tu máquina. La palabra clave es juntos: no instalas cuatro cosas por separado, las levantas de una. Según la documentación oficial, el kit incluye:

ServicioQué es, en simplePara qué lo usas en este módulo
n8nLa plataforma de workflows que ya conocesDonde corren order-triage y todos los flujos
PostgreSQLUna base de datos relacional robusta y maduraEl hogar de run_ledger y processed_orders
QdrantUn almacén de vectores para búsqueda por significadoLa ingesta RAG idempotente (lección 7)
OllamaUn motor para correr modelos de lenguaje en tu máquinaGenera embeddings sin API de pago (lección 7)

Docker, si no lo tienes fresco, es la herramienta que empaqueta cada uno de estos servicios en un "contenedor" —una cajita aislada con todo lo que el servicio necesita para correr— y los hace convivir en la misma máquina sin pisarse. Piénsalo como cuatro electrodomésticos que llegan cada uno en su caja, ya armados, y solo tienes que enchufarlos a la misma pared. docker compose es el interruptor que los enciende a todos a la vez.

De esos cuatro servicios, en este módulo el protagonista es PostgreSQL, que es donde vas a montar el ledger y la tienda de dedup. Los otros dos servicios de IA —Qdrant y Ollama— entran en escena en la lección 7, así que vale la pena presentarlos de pasada. Qdrant guarda "vectores": representaciones numéricas del significado de un texto, que permiten buscar por parecido semántico en vez de por palabras exactas —es el motor de un sistema de RAG—. Ollama corre modelos de lenguaje en tu propia máquina, sin llamar a ninguna API de pago; en la lección 7 lo usas para convertir documentos en esos vectores (los "embeddings"). Por ahora te basta saber que están ahí, encendidos, esperando su turno.

Una nota sobre el comando de arranque: el --profile cpu que aparece abajo levanta el stack usando solo el procesador. Si tu máquina tiene una tarjeta gráfica compatible, hay perfiles que la aprovechan (por ejemplo, uno para GPU de Nvidia), lo que acelera a Ollama. Para este módulo, el perfil de CPU alcanza de sobra —vamos a usar Postgres, que no necesita GPU—; el perfil de GPU es una optimización para la lección 7 si tu equipo la tiene. Elige el que corresponda a tu máquina y no te preocupes si solo puedes usar CPU: funciona igual, solo un poco más lento en la parte de embeddings.

Hay un detalle que es la clave de toda esta lección, y conviene decirlo claro: n8n, cuando lo levantas con el Starter Kit, ya guarda sus propios datos en ese Postgres. Sus workflows, sus credenciales, su historial de ejecuciones —todo eso vive en la base de datos que el kit levantó. Es decir, el Postgres no es un extra que tengas que montar: está ahí, encendido, con n8n hablándole desde el primer minuto. Lo único nuevo que vas a hacer tú es crear un par de tablas propias en esa misma base y apuntar unos nodos hacia ellas.

Ejemplo trabajado: levantar el kit y confirmar que Postgres está vivo

Si todavía no tienes el kit corriendo, el camino feliz es este. Al momento de esta guía:

# En una terminal, dentro de la carpeta del Starter Kit clonado:
docker compose --profile cpu up

Ese comando enciende los cuatro servicios. La primera vez tarda, porque Docker descarga las imágenes —las "cajas" de cada electrodoméstico—. Se van a ver muchas líneas de texto pasando. Está bien. Es normal y es esperable; no es un error, es Docker trayendo lo que necesita.

Qué vas a ver y qué significa. Cuando el arranque termina, entre todas esas líneas vas a distinguir mensajes de cada servicio iniciando. La señal que confirma que n8n está listo es que puedes abrir http://localhost:5678 en tu navegador y ves la interfaz de n8n. Si la ves, n8n está vivo y —lo importante para nosotros— Postgres también, porque n8n no arranca sin su base de datos. Es decir: si entraste a n8n, tu Postgres ya está corriendo. Excelente, ese es el checkpoint que necesitábamos.

Si algo falla aquí —un puerto ocupado, Docker que no está instalado, un perfil que cambió de nombre— es justo el tipo de tropiezo de entorno que mencioné. El repositorio oficial del Starter Kit tiene las instrucciones vigentes y los problemas comunes; esa es tu primera parada, no esta guía.

Cómo n8n le habla a Postgres: la credencial

Para que un nodo Postgres pueda leer y escribir tus tablas, n8n necesita saber cómo conectarse a la base: en qué dirección está, en qué puerto, con qué usuario y contraseña, y a qué base de datos. Todo eso junto es una credencial de Postgres en n8n. La creas una vez, n8n la guarda cifrada, y todos tus nodos Postgres la reutilizan.

Aquí hay un concepto que confunde a mucha gente al principio, y vale la pena desmontarlo con calma: desde dónde se conecta n8n. Como n8n corre dentro de un contenedor de Docker, y Postgres corre en otro contenedor del mismo kit, n8n no busca a Postgres en "tu computadora" (localhost), sino por el nombre del servicio que Docker le dio dentro de la red del kit. En el docker-compose.yml del Starter Kit, ese servicio suele llamarse postgres. Entonces, desde el punto de vista de n8n, la dirección de la base de datos no es localhost sino postgres.

Piénsalo así: dentro de un edificio de oficinas (la red de Docker), no llamas a un compañero marcando el número de la calle; marcas su extensión interna. postgres es la extensión interna de la base de datos dentro del edificio del Starter Kit. Este es el detalle que más tropiezos causa: la gente pone localhost porque es lo que usaría desde su propia máquina, y desde dentro del contenedor de n8n, localhost se refiere al propio contenedor de n8n, no a la base. Por eso postgres.

Los valores concretos —usuario, contraseña, nombre de la base— no los inventes: viven en el archivo .env del Starter Kit, en variables que al momento de esta guía se llaman algo como POSTGRES_USER, POSTGRES_PASSWORD y POSTGRES_DB. Abre ese archivo y usa exactamente lo que diga. Los nombres de esas variables pueden cambiar entre versiones del kit; el archivo .env es la fuente de verdad de tu instalación.

Ejemplo trabajado: crear la credencial paso a paso

Vamos a crearla. En la interfaz de n8n:

# Crear una credencial de Postgres en n8n

1. Abre un workflow y agrega un nodo Postgres (o ve a la sección de Credentials).
2. En el nodo, en el campo Credential, elige "Create New Credential".
3. Llena los campos con los valores de tu .env:

   Host:      postgres          ← el nombre del servicio en Docker, NO localhost
   Database:  <lo que diga POSTGRES_DB en tu .env>
   User:      <lo que diga POSTGRES_USER en tu .env>
   Password:  <lo que diga POSTGRES_PASSWORD en tu .env>
   Port:      5432              ← el puerto estándar de Postgres
   SSL:       normalmente desactivado para el Postgres local del kit

4. Guarda la credencial.

Vamos con el porqué de cada campo, porque "llena esto" a secas no enseña nada:

  • Host: postgres — la extensión interna de la base dentro de la red de Docker. Es el campo donde más gente se equivoca poniendo localhost; desde el contenedor de n8n, la base se llama postgres.
  • Database — el nombre de la base de datos concreta dentro de Postgres. Un servidor Postgres puede tener varias bases; esta es la que el kit creó, cuyo nombre está en POSTGRES_DB.
  • User y Password — las credenciales de acceso, tal como el kit las definió en .env. No las adivines; cópialas de ahí.
  • Port: 5432 — el puerto en el que Postgres escucha por defecto. Es un valor estándar y casi siempre es este.
  • SSL — para una base local dentro de tu propia máquina, normalmente va desactivado. En producción, sobre una red real, esto cambia; pero eso es tema de la guía de operaciones, no de aquí.

Qué vas a ver y qué significa. Muchas versiones de n8n tienen un botón de "Test connection" (probar conexión) al guardar la credencial. Si aparece un mensaje de éxito —normalmente en verde, algo como "Connection successful"—, significa que n8n encontró tu Postgres y pudo entrar con esos datos. Ese es el checkpoint. Si en cambio ves un error de conexión, el sospechoso número uno es el Host (¿pusiste postgres y no localhost?), y el número dos son el usuario o la contraseña (¿coinciden exactamente con el .env?). Perfecto si te dio verde; ya tienes el canal abierto entre n8n y la base.

Crear tus tablas en esa base

Con la credencial funcionando, el último paso de montaje es crear las dos tablas que diseñaste. Esto lo haces una sola vez. La forma más directa desde n8n es un nodo Postgres en operación Execute Query que corre el CREATE TABLE.

# Nodo: Postgres — "Setup — create tables" (operación: Execute Query)
# Se ejecuta UNA vez, al montar el sistema. Después lo puedes borrar o desactivar.

Query:
  CREATE TABLE IF NOT EXISTS run_ledger (
    id               BIGSERIAL     PRIMARY KEY,
    idempotency_key  TEXT          NOT NULL UNIQUE,
    order_id         TEXT          NOT NULL,
    status           TEXT          NOT NULL DEFAULT 'pending',
    result           JSONB,
    created_at       TIMESTAMPTZ   NOT NULL DEFAULT now(),
    updated_at       TIMESTAMPTZ   NOT NULL DEFAULT now()
  );

  CREATE TABLE IF NOT EXISTS processed_orders (
    idempotency_key  TEXT          PRIMARY KEY,
    order_id         TEXT          NOT NULL,
    processed_at     TIMESTAMPTZ   NOT NULL DEFAULT now()
  );

Recuerda de la lección 3 que el IF NOT EXISTS es tu red de seguridad: si corres esto dos veces, la segunda no falla ni borra nada, simplemente no hace nada. Eso te deja ejecutarlo sin miedo aunque no recuerdes si ya lo hiciste.

Qué vas a ver y qué significa. Al ejecutar el nodo, si no aparece ningún error rojo, las tablas se crearon (o ya existían). Para confirmarlo con tus propios ojos, corre en otro nodo Execute Query una consulta que las liste o que cuente sus filas:

-- Confirmar que las tablas existen y están vacías al inicio
SELECT count(*) AS ledger_rows FROM run_ledger;
SELECT count(*) AS dedup_rows  FROM processed_orders;

Si esa consulta devuelve 0 en cada una sin error, felicidades: las tablas existen, están vacías y listas para recibir su primer asiento y su primera clave. Ese es el estado de arranque correcto. Acabas de pasar de "diseñé unas tablas en abstracto" a "tengo dos tablas reales en una base de datos real corriendo en mi máquina". No está nada mal.

Una duda razonable que surge aquí: si apago Docker o reinicio la computadora, ¿pierdo las tablas y sus datos? La respuesta corta, y tranquilizadora, es no —siempre que el stack esté configurado como viene—. El Starter Kit guarda los datos de Postgres en un "volumen" de Docker: un espacio de almacenamiento que sobrevive a que los contenedores se apaguen y se vuelvan a encender. Piénsalo como el disco duro del electrodoméstico: aunque desenchufes el aparato, lo que guardó sigue ahí cuando lo vuelves a enchufar. Esto es exactamente la durabilidad que la lección 2 pedía y que Static Data no garantizaba: tu verdad del sistema no vive en la memoria volátil de una ejecución, vive en disco, y ahí se queda. (Administrar esos volúmenes con seriedad —respaldos, migraciones— es tema de la guía de operaciones; aquí basta con saber que, por defecto, tus datos persisten entre reinicios.)

Mirar el estado con tus propios ojos

Ahora que las tablas viven en una base de verdad, tienes algo que Static Data nunca te dio: puedes asomarte a ver qué hay dentro. Esta capacidad no es un lujo; es la diferencia entre operar a ciegas y operar sobre datos, y es una de las razones por las que elegiste una base de datos en la lección 2.

La forma más a la mano, sin salir de n8n, es un nodo Postgres en operación Select o Execute Query que corre una consulta de lectura. Por ejemplo, para ver los últimos asientos del ledger:

-- Los 10 asientos más recientes del ledger
SELECT order_id, status, result, created_at, updated_at
FROM run_ledger
ORDER BY created_at DESC
LIMIT 10;

O para revisar qué claves recuerda la tienda de dedup:

-- Las claves que la tienda de dedup ya procesó, más recientes primero
SELECT order_id, idempotency_key, processed_at
FROM processed_orders
ORDER BY processed_at DESC
LIMIT 10;

Qué vas a ver y qué significa. Antes de correr cualquier flujo, estas consultas devuelven cero filas: las tablas están vacías, y eso es correcto para el arranque. Después de que order-triage procese su primer pedido, la misma consulta va a mostrar una fila en cada tabla. Ese cambio —de vacío a con datos— es la prueba visible de que tu sistema está escribiendo su verdad donde debe. Cuando en la lección 8 dispares el webhook dos veces, vas a volver a estas consultas para demostrar que hay una sola fila y un solo cobro, no dos. La base de datos deja de ser una caja cerrada y se vuelve una ventana.

Si prefieres una herramienta dedicada para explorar la base —con tablas visuales, en vez de consultas sueltas— existen clientes gráficos de Postgres muy usados. No los necesitas para este módulo, pero si te sientes cómodo con ellos, apúntalos al mismo Postgres (recordando que desde tu máquina, fuera de Docker, la dirección sí suele ser localhost con el puerto que el kit exponga, al revés que desde dentro del contenedor de n8n). Ese matiz —postgres desde dentro del contenedor, localhost desde tu máquina— confunde a todos al principio; tenerlo claro te ahorra un buen rato de frustración. Si te enredas, la documentación del Starter Kit y tu asistente de IA son buenos lugares para preguntar por la configuración exacta de tu versión.

Una tabla propia, o una base aparte: la decisión honesta

Vale la pena una nota de criterio, porque toca una duda razonable. Estás creando tus tablas en la misma base de datos donde n8n guarda sus propias cosas (workflows, ejecuciones). ¿Está bien mezclar tus tablas con las de n8n?

Para aprender y para un proyecto personal, está perfectamente bien y es lo más simple: una sola base, tus tablas conviviendo con las de n8n, cada una con su nombre. Postgres las mantiene separadas sin problema; no hay riesgo de que tu run_ledger interfiera con las tablas internas de n8n mientras no las toques.

Para un sistema que va en serio, la práctica más limpia es darle a tu aplicación su propia base de datos (o al menos su propio esquema) separada de la que n8n usa para sí mismo, de modo que puedas respaldar, migrar o inspeccionar tus datos sin enredarte con los internos de n8n. Eso se hace creando una base o un esquema aparte y apuntando ahí tu credencial. No lo desarrollamos en detalle porque cruza hacia la guía de operaciones —cómo administrar Postgres en producción—; aquí basta con que sepas que la opción existe y por qué. Empieza simple, con la base que ya tienes; separa cuando el sistema lo pida.

Errores comunes

Poner localhost en el Host de la credencial (práctico, el clásico). Qué pasa: se configura la credencial con Host localhost, la prueba de conexión falla, y no se entiende por qué si "Postgres está corriendo en mi máquina". Por qué pasa: n8n corre dentro de un contenedor, y desde ahí localhost es el propio contenedor de n8n, no la máquina ni el contenedor de Postgres. Cómo detectarlo: si tu Host es localhost y la conexión falla dentro del Starter Kit, es esto casi seguro. Cómo corregirlo: usa el nombre del servicio de Docker, que en el Starter Kit suele ser postgres. Confirma el nombre exacto en el docker-compose.yml.

Inventar el usuario o la contraseña en vez de leerlos del .env (práctico). Qué pasa: se ponen valores "de ejemplo" o los que uno cree recordar, y la conexión rebota con error de autenticación. Por qué pasa: cada instalación tiene sus propias credenciales, definidas en el .env del kit. Cómo detectarlo: si el error es de autenticación (usuario/contraseña) y no de conexión, es esto. Cómo corregirlo: abre el .env del Starter Kit y copia exactamente los valores de POSTGRES_USER, POSTGRES_PASSWORD y POSTGRES_DB.

Creer que hay que instalar Postgres aparte (conceptual). Qué pasa: alguien pospone el módulo entero para "instalar una base de datos primero", sin darse cuenta de que ya tiene una corriendo. Por qué pasa: no es evidente que el Starter Kit trae Postgres incluido. Cómo detectarlo: si tu bloqueo es "no tengo base de datos" y ya levantaste el Starter Kit, ya la tienes. Cómo corregirlo: si abriste n8n en localhost:5678, tu Postgres está vivo; solo falta apuntar la credencial.

Olvidar el IF NOT EXISTS y romperse al reejecutar (práctico). Qué pasa: se crean las tablas con CREATE TABLE a secas, y al correr el setup de nuevo —por costumbre o por error— el nodo falla con "la tabla ya existe". Por qué pasa: sin IF NOT EXISTS, CREATE TABLE sobre una tabla existente es un error. Cómo detectarlo: si tu nodo de setup falla la segunda vez que lo corres, es esto. Cómo corregirlo: usa CREATE TABLE IF NOT EXISTS, que hace el setup repetible sin consecuencias.

Confundir el estado del sistema con los datos internos de n8n (conceptual). Qué pasa: alguien intenta leer o modificar las tablas internas de n8n para "ver las ejecuciones", en vez de usar sus propias tablas. Por qué pasa: como todo vive en el mismo Postgres, se difumina la frontera. Cómo detectarlo: si estás consultando tablas que no creaste tú (con nombres internos de n8n), saliste de tu terreno. Cómo corregirlo: para la verdad de tu sistema usa tus tablas (run_ledger, processed_orders); las tablas internas de n8n son suyas y no se tocan. Si quieres datos de ejecuciones de forma estable, el ledger que tú escribes es la fuente, no la base interna de n8n.

Borrar el volumen y perder los datos sin querer (práctico). Qué pasa: al hacer limpieza de Docker con comandos que eliminan volúmenes, desaparecen las tablas y su contenido, y no se entiende por qué. Por qué pasa: los datos de Postgres viven en un volumen de Docker; si borras el volumen, borras los datos. Un docker compose down normal apaga los contenedores pero conserva el volumen; agregar la opción que elimina volúmenes sí los borra. Cómo detectarlo: si tus tablas "desaparecieron" justo después de una limpieza de Docker, es esto. Cómo corregirlo: para apagar sin perder datos, apaga sin eliminar volúmenes; y si el sistema importa, ten un respaldo —cómo hacerlo es tema de la guía de operaciones, pero saber que el dato vive en el volumen ya te evita el susto—.

Ejercicios

Ejercicio 1 — Diagnostica la conexión. Para cada síntoma al probar la credencial, di cuál es la causa más probable y cómo lo confirmarías.

(a) Error de conexión (no llega a la base), con Host en localhost. (b) Conecta, pero error de autenticación. (c) Conecta y autentica, pero al crear la tabla dice que ya existe.

Ver solución

(a) Host equivocado. Dentro del Starter Kit, n8n alcanza a Postgres por el nombre del servicio (postgres), no por localhost. Confírmalo cambiando el Host a postgres y volviendo a probar; si conecta, era eso. Para estar seguro del nombre, míralo en el docker-compose.yml.

(b) Usuario o contraseña que no coinciden con el .env. La conexión llegó a la base (buena señal de que el Host está bien), pero las credenciales no cuadran. Confírmalo abriendo el .env del kit y comparando carácter por carácter con lo que pusiste.

(c) No es un error real: la tabla ya estaba creada. Si usaste CREATE TABLE sin IF NOT EXISTS y ya la habías creado, es esperable. Confírmalo consultando la tabla (SELECT count(*) FROM run_ledger): si responde, existe y está todo bien; solo agrega IF NOT EXISTS para que el setup sea repetible.

Por qué funciona: fíjate en que los tres síntomas se distinguen por dónde fallan. Falla en conectar → Host. Conecta pero no autentica → usuario/contraseña. Autentica pero la tabla existe → es un no-problema. Diagnosticar por la etapa donde algo se rompe es la mitad del trabajo de resolverlo.

Ejercicio 2 — Explica el postgres vs localhost. Un compañero insiste en que su Host debe ser localhost "porque Postgres está en mi computadora". Escribe la explicación que le darías para que entienda por qué, desde n8n, la dirección es postgres.

Ver solución

Una versión de referencia:

Tienes razón en que Postgres está en tu computadora, pero desde el punto de vista de n8n la cosa se ve distinta. n8n no corre "en tu computadora" a secas: corre dentro de un contenedor de Docker, que es como una máquina pequeña y aislada dentro de la tuya. Y Postgres corre en otro contenedor. Cuando n8n dice localhost, se refiere a su propio contenedor —a sí mismo—, no a tu máquina ni al contenedor de Postgres. Ahí no hay ninguna base de datos, por eso falla.

Docker conecta los contenedores del Starter Kit en una red interna, y dentro de esa red cada servicio tiene un nombre. El de la base es postgres (está en el docker-compose.yml). Es como una extensión telefónica interna del edificio: para llamar a la base marcas postgres, no la dirección de la calle. Por eso el Host va postgres y no localhost.

Por qué funciona: la explicación no dice "hazlo porque sí"; da el modelo mental —contenedores aislados, red interna, nombres de servicio— que hace que la regla tenga sentido y que la persona pueda razonar el próximo caso parecido sola.

Ejercicio 3 — Verifica tu montaje. Sin mirar la lección, lista los checkpoints que confirman, uno por uno, que tu stack está listo para el proyecto de la lección 8, y qué señal visual esperas en cada uno.

Ver solución

Una lista de referencia:

  1. El Starter Kit está corriendo → puedes abrir http://localhost:5678 y ves la interfaz de n8n. (Si entras a n8n, Postgres también está vivo, porque n8n no arranca sin él.)
  2. La credencial de Postgres funciona → la prueba de conexión da éxito ("Connection successful" o similar), con Host postgres y los datos del .env.
  3. Las tablas existen → un SELECT count(*) sobre run_ledger y processed_orders responde 0 sin error.

Con esos tres verdes, tienes el estado del sistema viviendo en una base real, a costo cero, y listo para que el proyecto lo use. Un cuarto checkpoint opcional, pero tranquilizador: reinicia Docker y vuelve a correr el SELECT count(*); si las tablas siguen ahí, confirmaste con tus ojos que los datos persisten entre reinicios, que es justo la durabilidad que buscabas.

Por qué funciona: cada checkpoint tiene una señal visual concreta, no un "debería funcionar". Esa es la diferencia entre creer que montaste algo y saber que lo montaste. Cuando algo se rompa más adelante, vas a poder volver a estos tres puntos y encontrar en cuál se cayó.

Resumen y siguiente paso

En esta lección enchufaste el mecanismo que diseñaste a una base de datos real. El Self-Hosted AI Starter Kit v2 levanta con un comando cuatro servicios —n8n, PostgreSQL, Qdrant y Ollama— en tu máquina, a costo cero, y n8n ya usa ese Postgres por dentro desde el primer minuto. Así que la base de datos no había que montarla: ya estaba ahí.

Los pasos fueron tres, cada uno con su señal de confirmación: levantar el kit (checkpoint: entras a localhost:5678), crear la credencial de Postgres apuntando al Host postgres —no localhost, porque n8n corre en un contenedor y alcanza la base por su nombre de servicio— con los valores del archivo .env (checkpoint: prueba de conexión en verde), y crear las tablas run_ledger y processed_orders con CREATE TABLE IF NOT EXISTS (checkpoint: un SELECT count(*) que responde 0). El error más común de todos es poner localhost en el Host; el segundo, inventar el usuario y la contraseña en vez de leerlos del .env.

Y ganaste dos cosas que Static Data nunca te dio y que la lección 2 pedía: una ventana para asomarte al estado con un SELECT, y durabilidad real, porque los datos viven en un volumen de Docker que sobrevive a los reinicios. La verdad de tu sistema ya no es un post-it; es una tabla en disco que puedes consultar cuando quieras.

Antes de avanzar deberías poder: nombrar los cuatro servicios del Starter Kit; explicar por qué el Host es postgres y no localhost; y listar los tres checkpoints de tu montaje.

La lección 7 usa los otros dos servicios que acabas de conocer —Qdrant y Ollama— para llevar la idempotencia a un terreno nuevo: la ingesta de RAG. Vas a ver que no re-embeber un documento que ya procesaste es exactamente el mismo patrón de deduplicación de este módulo, con un giro: la clave no es un order_id, es un hash del contenido del archivo. El mismo torniquete, otra puerta. Y de paso vas a comprobar que lo que aprendiste no es un truco para cobros, sino un patrón general que sirve donde haya un efecto que no quieres repetir.

Recursos

  • Deploy with the AI starter kit — n8n Docs — la guía oficial: qué servicios trae, cómo levantarlo con Docker y el comando vigente. Tu fuente de verdad cuando algo no coincida con esta lección.
  • self-hosted-ai-starter-kit — GitHub — el repositorio con el docker-compose.yml real y el .env.example. Ahí confirmas el nombre del servicio de Postgres y los nombres de las variables de entorno de tu versión.
  • Postgres credentials — n8n Docs — los campos de la credencial de Postgres en n8n (Host, Database, User, Password, Port, SSL) y qué espera cada uno.
  • Postgres node — n8n Docs — la operación Execute Query con la que corres el CREATE TABLE y las consultas de verificación.
  • Docker Compose — overview — qué es docker compose y cómo levanta varios servicios juntos, por si quieres entender la herramienta que enciende el stack.