Módulo 4: El modelo de datos del sistema
2. Static Data, variables y por qué no alcanzan
Descripción
Al terminar esta lección vas a poder explicar, con una prueba concreta, por qué $getWorkflowStaticData() y las variables de n8n no son un lugar confiable para la verdad de un sistema idempotente. Vas a saber qué es exactamente Static Data, qué promete su nombre y dónde esa promesa se rompe. Vas a conocer el hecho que sorprende a casi todos —que Static Data no se guarda cuando pruebas el workflow desde el editor— y vas a tener la lista completa de sus límites, cada uno con la razón por la que descalifica a Static Data como hogar de la deduplicación. Y vas a salir con el criterio para saber, ante cualquier dato que quieras "guardar entre ejecuciones", si un mecanismo interno de n8n alcanza o si necesitas una base de datos.
Esto importa porque es la tentación número uno de quien llega a este problema. Terminas el módulo 2, entiendes que la idempotencia necesita memoria, buscas en n8n "cómo guardar algo entre ejecuciones", y lo primero que encuentras es $getWorkflowStaticData(). El nombre es una invitación: "datos estáticos del workflow", algo que suena permanente. Miles de tutoriales lo usan para llevar la cuenta del último item procesado de un feed RSS, y funciona lo suficientemente bien en ese caso como para que parezca la respuesta general. No lo es para la idempotencia, y esta lección es la que te ahorra descubrirlo en producción, con cobros duplicados de por medio.
Conexión con el módulo: esta es la lección que hace inevitable todo lo que sigue. La lección 1 planteó que la verdad del sistema tiene que vivir fuera de la ejecución; esta demuestra por qué los mecanismos que n8n ofrece dentro del workflow no cierran ese hueco. Cuando termines, la base de datos —el Postgres del Starter Kit— deja de ser "una opción" y pasa a ser la única respuesta seria, y las lecciones 3, 4 y 5 la construyen sabiendo exactamente qué problema resuelve cada pieza. Este es el argumento; el resto del módulo es la solución.
El post-it pegado al monitor
Empecemos por una imagen, porque el concepto de Static Data se entiende mejor por lo que parece que es antes de ver lo que es.
Imagina que llevas la cuenta de algo importante en un post-it pegado en el borde de tu monitor. Cada vez que terminas una tarea, tachas y anotas el nuevo número. Mientras estás en tu escritorio, funciona de maravilla: el post-it está ahí, lo lees, lo actualizas. Es rápido, está a la mano, no tuviste que abrir ningún programa.
El problema del post-it no aparece cuando lo usas. Aparece en los bordes. Si trabajas en otro escritorio ese día, el post-it no está —se quedó en tu monitor—. Si el equipo de limpieza lo tira pensando que es basura, tu cuenta desaparece sin aviso. Si otra persona necesita ese número, no puede leerlo: es tu post-it, en tu monitor. Y si alguien te pregunta "¿estás seguro de que ese número está bien?", la respuesta honesta es "está bien mientras nadie haya tocado el post-it, y no tengo forma de garantizar que nadie lo haya tocado".
Static Data es el post-it de n8n. Es un espacio donde un workflow puede guardar un dato pequeño y volverlo a leer más tarde. Es cómodo, está integrado, no requiere montar nada. Y tiene exactamente los problemas del post-it: su persistencia depende de condiciones que no controlas del todo, no se comparte bien, y no puedes garantizar su integridad cuando algo lo toca por el borde. Para llevar la cuenta de algo poco crítico —el timestamp del último correo que revisaste— el post-it alcanza. Para la verdad de la que depende que no cobres dos veces, no.
Vamos a ver qué es con precisión, y después por qué esos bordes lo descalifican para la idempotencia.
Qué es, exactamente, Static Data
En n8n, dentro de un nodo Code, tienes acceso a una función: $getWorkflowStaticData(). La documentación oficial la describe como el mecanismo que "da acceso a los datos estáticos del workflow" y con el que "puedes guardar datos directamente en el workflow".
La anatomía es esta. La función te devuelve un objeto —una especie de cajón vacío la primera vez— donde puedes escribir propiedades. Lo que escribas ahí, n8n intentará conservarlo para la próxima ejecución.
// Nodo Code — leer y escribir Static Data
const staticData = $getWorkflowStaticData('global');
// Leer lo que quedó de una ejecución anterior (undefined la primera vez)
const lastId = staticData.lastProcessedId;
// Escribir un valor nuevo para la próxima ejecución
staticData.lastProcessedId = 'ORD-2041';
Hay dos variantes, según el argumento que le pases, y conviene entenderlas porque una de las dos agrava el problema:
$getWorkflowStaticData('global') — el cajón compartido por todo el workflow. Cualquier nodo del workflow puede leer y escribir ahí. La documentación lo dice así: "Global static data is the same in the whole workflow. Every node in the workflow can access it."
$getWorkflowStaticData('node') — un cajón privado del nodo que llama la función. Solo ese nodo puede volver a leer lo que escribió. La documentación: "The node static data is unique to the node. Only the node that set it can retrieve it again."
Fíjate en la palabra que aparece en las dos definiciones y no en otras: workflow. El alcance de Static Data es, en el mejor de los casos, un solo workflow. No hay una variante $getWorkflowStaticData('everything') que comparta el dato entre workflows distintos. Este es el primer borde, y lo retomamos abajo.
El uso para el que Static Data está pensado —y donde es razonable— es guardar un dato pequeño que marca un progreso. La propia documentación pone el ejemplo: guardar el timestamp del último item procesado de un feed RSS o de una base de datos, para que la próxima ejecución solo traiga lo nuevo. Un número, una fecha, un identificador. Nada grande, nada crítico.
Ejemplo trabajado: intentar deduplicar order-triage con Static Data
Vamos a hacer lo que haría cualquiera que acaba de conocer la función: usarla para resolver el problema del módulo. La idea es directa —guardar en Static Data la lista de order_id que ya procesamos, y consultarla antes de crear el cobro—.
// ============================================================
// Nodo: Code — "Dedup con Static Data" (INTENTO — no lo uses en serio)
// Modo: Run Once for All Items
//
// IDEA: guardar los order_id ya vistos en Static Data,
// y descartar el pedido si ya está en la lista.
// ============================================================
const staticData = $getWorkflowStaticData('global');
// La primera vez no existe, así que arranco con una lista vacía
const seen = staticData.seenOrderIds || [];
const order = $input.first().json;
const orderId = order.order_id;
if (seen.includes(orderId)) {
// Ya lo vimos: marco el pedido como duplicado
return [{ json: { ...order, is_duplicate: true } }];
}
// No lo habíamos visto: lo agrego a la lista y dejo pasar el pedido
seen.push(orderId);
staticData.seenOrderIds = seen;
return [{ json: { ...order, is_duplicate: false } }];
Si lo lees, parece correcto. Guardas los IDs vistos, consultas antes de actuar, agregas el nuevo. Es la traducción literal de "verifica si ya lo hiciste, y si no, hazlo y anótalo".
Qué esperar al probarlo desde el editor. Aquí viene la sorpresa que le arruina el día a mucha gente. Abres el workflow en el editor de n8n, ejecutas con el pedido ORD-2041, y el nodo devuelve is_duplicate: false. Correcto, es la primera vez. Ejecutas otra vez, el mismo ORD-2041, esperando que ahora diga is_duplicate: true. Y devuelve is_duplicate: false de nuevo. Y otra vez. Y otra. Static Data no recuerda nada entre una ejecución de prueba y la siguiente.
No es un bug en tu código. Es el comportamiento documentado, y es la razón número uno por la que Static Data no sirve para lo que estás intentando. La documentación oficial lo dice sin rodeos:
"Static data isn't available when testing workflows. The workflow must be published and called by a trigger or webhook to save static data."
Traducido: Static Data no está disponible al probar workflows. El workflow tiene que estar publicado (activo) y ser llamado por un trigger o un webhook para que Static Data se guarde. Las ejecuciones manuales desde el editor —justamente las que usas para desarrollar y depurar— no guardan Static Data.
Detente un segundo en lo que esto significa para tu trabajo. El lugar donde construyes y pruebas tu lógica de deduplicación es exactamente el lugar donde esa lógica no funciona, y sin ningún error que te avise. Depuras, ves is_duplicate: false repetido, y no tienes forma de distinguir "mi código está mal" de "Static Data no persiste en este modo". Es la peor combinación posible: falla en silencio, y falla justo donde mirarías para diagnosticar.
Podrías decir: "bueno, entonces lo activo y lo pruebo con el webhook real". Y sí, ahí Static Data empieza a persistir. Pero eso solo te lleva al siguiente borde, que es peor, porque este ya no lo ves venir.
Los bordes que descalifican a Static Data para la idempotencia
Supongamos que activaste el workflow y ahora Static Data sí guarda entre ejecuciones de producción. ¿Ya está resuelto? No. Static Data tiene una lista de límites, y cada uno, por su cuenta, sería suficiente para descartarlo como hogar de la deduplicación. Juntos, cierran el caso.
1. No hay atomicidad: la trampa de "verificar y luego actuar" sigue abierta. Este es el más profundo, y conecta directo con lo que el módulo 2 dejó marcado. Tu código hace tres cosas en secuencia: lee la lista de vistos, decide si el ID está, y escribe la lista con el ID nuevo. Entre esos pasos pasa tiempo. Si dos disparos del mismo ORD-2041 corren casi a la vez —el caso real del webhook que dispara doble—, los dos pueden leer la lista antes de que cualquiera la haya actualizado, los dos ven "no está", y los dos crean el cobro. Static Data no tiene forma de decir "verifica-e-inserta en un solo paso indivisible". Una base de datos con una restricción de unicidad sí, y esa es exactamente la solución de la lección 5. Static Data reproduce la carrera; no la resuelve.
2. El alcance es un solo workflow. Static Data vive dentro del workflow que lo escribió. Si order-triage marca ORD-2041 como visto, y otro workflow —digamos order-refund o una versión nueva del triage— necesita saber que ese pedido ya se procesó, no puede leerlo. La verdad de "este pedido ya fue cobrado" es una verdad del sistema, no de un workflow; debería ser consultable desde cualquier flujo que la necesite. Una tabla en Postgres la ven todos los workflows que quieras; el post-it de un workflow, solo ese workflow.
3. Se comporta de forma poco fiable bajo ejecuciones frecuentes. La propia documentación advierte que esta función "puede comportarse de forma poco fiable bajo ejecuciones de alta frecuencia". Un webhook de pedidos que a veces recibe ráfagas es justo un caso de alta frecuencia. El mecanismo pensado para "guardar el timestamp de un RSS que revisas cada hora" no está diseñado para ser el árbitro de la correctitud en un flujo que puede recibir varios eventos por segundo.
4. Está pensado para datos pequeños. La lista de "vistos" crece con cada pedido. Si guardas ahí todos los order_id de la historia de Cumbre, ese post-it se vuelve un cartel enorme que n8n tiene que leer, cargar en memoria y volver a escribir en cada ejecución. Static Data está pensado explícitamente para datos chicos —un timestamp, un cursor—, no para un registro que crece sin parar. Una tabla, en cambio, está hecha para millones de filas y las consulta por su clave en un instante.
5. Es frágil ante cambios en el workflow. Static Data viaja pegado al workflow. Cuando exportas e importas el workflow —para moverlo entre entornos, para versionarlo, para restaurar un respaldo—, ese estado interno puede no acompañarlo, o hacerlo de forma inconsistente. Piénsalo así: estás guardando la verdad del sistema dentro del archivo del programa que la procesa, y ese archivo se copia, se edita y se reimporta como parte del trabajo normal. Cada una de esas operaciones es una oportunidad de que el post-it se despegue. La verdad del sistema tiene que vivir en un lugar separado del código que la usa, precisamente para que puedas cambiar el workflow sin tocar la verdad, y respaldar la verdad sin depender del workflow.
Junta los cinco: no es atómico (reproduce la carrera), no se comparte entre workflows, es poco fiable en frecuencia alta, no escala en tamaño, y se rompe al mover el workflow. Y súmale el que abrió la lección: ni siquiera se guarda cuando lo pruebas desde el editor. No es que Static Data sea "malo" —para su caso de diseño, un cursor pequeño en un workflow activo de baja frecuencia, es perfectamente útil—. Es que la idempotencia le pide exactamente las cinco cosas que no da.
¿Y las variables? $vars tampoco es el lugar
La otra tentación es $vars, las variables de proyecto. Si ya no está $env y buscas "dónde pongo un valor que quiero leer", $vars aparece. Conviene cerrar también esa puerta, porque el error es sutil: $vars resuelve un problema parecido pero distinto.
$vars es para configuración, no para estado. La diferencia es la misma de la lección 1: la configuración es un valor que alguien del equipo pone y que cambia rara vez —un umbral, una URL, un límite—; el estado es algo que el sistema va escribiendo a medida que trabaja —qué pedidos ya procesó—. Y $vars tiene tres propiedades que lo hacen imposible como estado, incluso dejando de lado que en Community sin licencia ni siquiera está disponible:
- Es de solo lectura desde el workflow. Tu código puede leer
$vars.free_shipping_threshold, pero no puede escribir$vars.seenOrderIds = [...]. Un mecanismo donde no puedes escribir no puede ser donde registras lo que ya viste. Se acabó la discusión ahí mismo: la deduplicación necesita escribir. - Todos los valores llegan como texto. Aun si pudieras escribir, tendrías que serializar y parsear a mano.
- Está pensado para valores puntuales de configuración, con límites de tamaño, no para un registro que crece.
La regla, entonces, cabe en una línea: $vars guarda lo que tú decides una vez; una base de datos guarda lo que el sistema descubre mientras trabaja. La deduplicación es lo segundo. $vars no es su lugar, igual que Static Data no lo es.
Lo que una base de datos da y estos mecanismos no
Vale la pena cerrar poniendo, uno frente a otro, cada límite y su solución. Esta tabla es, en el fondo, el plan de las próximas tres lecciones.
| Lo que la idempotencia necesita | Static Data / $vars | Postgres (lecciones 3-5) |
|---|---|---|
| Persistir al probar desde el editor | No (Static Data no se guarda en modo test) | Sí, escribe siempre que ejecutas la consulta |
| "Verifica-e-inserta" en un solo paso atómico | No (reproduce la carrera) | Sí, con INSERT ... ON CONFLICT (lección 5) |
| Compartir la verdad entre varios workflows | No (alcance de un workflow) | Sí, cualquier workflow consulta la tabla |
| Fiabilidad bajo alta frecuencia | Documentada como poco fiable | Diseñada exactamente para esa carga |
| Crecer a muchos registros | No (pensado para datos pequeños) | Sí, millones de filas, consulta por clave |
| Sobrevivir a exportar/reimportar el workflow | Frágil (viaja pegado al workflow) | Sí, vive separada del workflow |
| Escribir estado desde el workflow | Static Data sí / $vars no | Sí, con el nodo Postgres |
Fíjate en la columna del medio: en cada fila hay un "no" o un "frágil". No es que hayamos elegido criterios que Postgres gana; son, literalmente, los requisitos de la deduplicación entre ejecuciones, y los mecanismos internos fallan todos. Por eso el módulo se construye sobre una base de datos y no sobre un truco dentro del workflow.
Una honestidad para cerrar, porque enseñar solo un lado sería enseñar mal: Static Data existe por buenas razones y tiene su lugar. Un cursor de "último item leído" en un workflow activo, de baja frecuencia, que no comparte ese dato con nadie, es su caso ideal, y ahí es más simple que montar una tabla. La lección no es "Static Data es basura"; es "Static Data no es donde vive la verdad de un sistema idempotente". Saber distinguir los dos casos —cuándo un cursor pequeño alcanza y cuándo necesitas una base de datos— es parte del criterio que separa a quien arma flujos de quien es dueño de un sistema.
Errores comunes
Depurar la lógica de dedup en el editor y concluir que "no funciona" (práctico). Qué pasa: escribes tu deduplicación con Static Data, la pruebas desde el editor con el mismo ID varias veces, siempre da "primera vez", y pasas una hora buscando el bug en tu código. Por qué pasa: Static Data no se guarda en ejecuciones manuales, así que cada prueba arranca con el cajón vacío; tu código está bien, el mecanismo no persiste en ese modo. Cómo detectarlo: si tu estado "se reinicia" cada vez que ejecutas desde el editor pero no tienes ningún error, es esto. Cómo corregirlo: recuerda que Static Data solo persiste en workflows activos disparados por trigger o webhook; pero más de fondo, para deduplicación no uses Static Data —usa una tabla, que persiste también cuando pruebas—.
Confundir "funciona para el RSS" con "funciona para la idempotencia" (conceptual). Qué pasa: ves decenas de tutoriales que usan Static Data para llevar el cursor de un feed y asumes que sirve igual para no duplicar cobros. Por qué pasa: los dos casos suenan a "guardar algo entre ejecuciones", pero son distintos: el cursor del RSS no tiene una carrera crítica ni exige atomicidad, y un error ahí a lo sumo relee un item viejo. Un error en la dedup de un cobro duplica dinero. Cómo detectarlo: pregúntate qué pasa si el mecanismo falla; si la respuesta es "reproceso un dato inofensivo", Static Data quizás alcance; si es "cobro dos veces", no. Cómo corregirlo: reserva Static Data para cursores de bajo riesgo y lleva a Postgres todo lo que, al fallar, produzca un efecto que no puedas deshacer fácil.
Poner el estado del sistema en $vars porque es "donde se ponen los valores" (conceptual). Qué pasa: alguien intenta guardar la lista de vistos en una variable de proyecto. Por qué pasa: $vars es lo más visible como "almacén de valores" en n8n. Cómo detectarlo: $vars es de solo lectura desde el workflow, así que en cuanto intentas escribir, no puedes; si tu diseño requiere escribir en $vars, ya sabes que vas por el camino equivocado. Cómo corregirlo: $vars es configuración que tú decides una vez; el estado que el sistema descubre mientras trabaja va en una base de datos.
Guardar una lista que crece sin parar en Static Data (práctico). Qué pasa: la deduplicación con Static Data "funciona" en un workflow activo por un tiempo, y con los meses el workflow se pone lento o se comporta raro. Por qué pasa: la lista de vistos crece con cada pedido, y Static Data está pensado para datos pequeños; n8n carga y reescribe ese blob en cada ejecución. Cómo detectarlo: si tu Static Data guarda una colección que solo crece, es esto. Cómo corregirlo: una tabla indexada por clave consulta "¿existe ORD-2041?" en tiempo constante sin cargar toda la historia; es exactamente para lo que sirve.
No separar la verdad del sistema del archivo del workflow (conceptual). Qué pasa: se versiona y reimporta el workflow como parte del trabajo normal, y en algún punto la deduplicación "se resetea" o se comporta distinto sin que nadie tocara la lógica. Por qué pasa: el estado vivía dentro del workflow, y al exportar/importar viajó de forma inconsistente. Cómo detectarlo: si tu estado cambia al mover el workflow entre entornos, está acoplado al archivo. Cómo corregirlo: la verdad del sistema vive en una base de datos separada; así puedes cambiar, versionar y restaurar el workflow sin tocar el estado, y respaldar el estado sin depender del workflow.
Ejercicios
Ejercicio 1 — Nombra el borde. Para cada situación, di cuál de los límites de Static Data es el responsable, en una frase.
(a) Pruebas tu dedup desde el editor y el mismo ID nunca aparece como duplicado.
(b) Dos disparos casi simultáneos del mismo pedido crean dos cobros, aunque el workflow está activo.
(c) Un segundo workflow necesita saber si order-triage ya procesó un pedido, y no tiene cómo.
(d) Con los meses, el workflow que usa Static Data para dedup se pone lento.
Ver solución
(a) No persiste al probar desde el editor. Static Data solo se guarda en ejecuciones de producción (trigger/webhook con el workflow activo). En modo test, cada corrida arranca con el cajón vacío.
(b) Falta de atomicidad. Los dos disparos leen la lista antes de que cualquiera la actualice, los dos ven "no está", y los dos actúan. Es la carrera de "verificar y luego actuar", que Static Data no puede cerrar porque no ofrece un "verifica-e-inserta" indivisible.
(c) Alcance de un solo workflow. Static Data vive dentro del workflow que lo escribió; otro workflow no puede leerlo. La verdad "este pedido ya se procesó" es del sistema y debería vivir en un lugar que todos consulten.
(d) Pensado para datos pequeños. La lista de vistos crece sin parar, y Static Data carga y reescribe todo ese blob en cada ejecución. Una tabla indexada no tiene ese costo.
Por qué funciona: fíjate en que cada síntoma mapea a un borde distinto. No es que Static Data "a veces falle": falla de maneras específicas y predecibles, y cada una tiene un nombre. Reconocer cuál es cuál es lo que te deja decidir rápido si un mecanismo interno alcanza para un caso o no.
Ejercicio 2 — ¿Static Data o base de datos? Para cada dato, decide si Static Data sería un lugar razonable o si exige una base de datos, y justifica en una frase.
(a) El timestamp del último correo que un workflow de reportes revisó, en un flujo que corre una vez por hora.
(b) La lista de order_id ya cobrados, en un flujo de pedidos que puede recibir ráfagas.
(c) El número de folio consecutivo que debe asignarse a cada factura nueva, sin repetir jamás.
(d) El cursor de "última página leída" de una API que paginas, dentro de una sola ejecución larga.
Ver solución
(a) Static Data es razonable. Es un cursor pequeño, de baja frecuencia, que no se comparte con nadie y cuyo peor caso al fallar es releer unos correos. Es el caso de diseño de Static Data.
(b) Base de datos, sin dudarlo. Alta frecuencia, exige atomicidad (dos disparos casi a la vez), la lista crece, y al fallar duplica un cobro. Cada uno de esos rasgos, por su cuenta, ya descarta Static Data.
(c) Base de datos. Un folio que "no se repite jamás" es precisamente lo que una restricción de unicidad y una secuencia de la base garantizan; con Static Data, dos ejecuciones concurrentes podrían leer el mismo último folio y asignar el mismo número.
(d) Ni Static Data ni base de datos: es estado de ejecución. Si el cursor solo vive dentro de una ejecución que pagina hasta terminar, es una variable normal del nodo; no necesita sobrevivir a la ejecución. Confundir esto y meterlo en Static Data es guardar de más.
Por qué funciona: la pregunta de siempre —¿tiene que sobrevivir a la ejecución?— decide entre estado de ejecución y estado del sistema. Y entre los que sí sobreviven, una segunda pregunta —¿su fallo es inofensivo o costoso, y hay carrera?— decide entre el post-it (Static Data) y la libreta seria (base de datos).
Ejercicio 3 — Explícalo en la revisión de código. Un compañero te muestra su solución de deduplicación: guarda los order_id vistos con $getWorkflowStaticData('global'), la probó en el editor "y a veces funciona y a veces no", y te pide ayuda. Escribe la respuesta que le darías, cubriendo: (a) por qué "a veces funciona y a veces no", (b) por qué, aunque lo active, seguiría sin ser confiable para dedup, y (c) qué le propondrías.
Ver solución
Una versión de referencia. Fíjate en que primero explica el síntoma exacto que él vio, y solo después generaliza.
(a) Por qué "a veces sí, a veces no". Casi seguro es el modo de ejecución. Static Data no se guarda en ejecuciones de prueba desde el editor: solo persiste cuando el workflow está activo y lo dispara un trigger o un webhook. Si algunas de tus pruebas fueron manuales y otras con el webhook real, verías justo eso: en las manuales nunca recuerda, en las de producción sí. No es aleatorio, es esa diferencia.
(b) Por qué activarlo no lo arregla. Aunque logres que persista, la deduplicación con Static Data tiene un agujero de fondo: no es atómico. Tu código lee la lista, decide y escribe, en tres pasos. Si dos disparos del mismo pedido llegan casi juntos —el caso que justamente queremos cubrir—, los dos leen "no está" antes de que cualquiera escriba, y los dos crean el cobro. Static Data no ofrece un "verifica-e-inserta" en un solo paso. Además vive pegado a este workflow, así que ningún otro flujo puede consultarlo, y crece sin límite con cada pedido.
(c) Qué propongo. Mover la verdad a una tabla en el Postgres que ya trae el Starter Kit. Una tabla
processed_ordersconidempotency_keycomo columna única, y antes de cobrar hacemosINSERT ... ON CONFLICT DO NOTHING: si la fila se insertó, es la primera vez y cobramos; si chocó con la restricción, es duplicado y no hacemos nada. Ese "insertar o chocar" es un solo paso indivisible, así que resuelve la carrera que Static Data no puede. Y de paso la tabla persiste también cuando pruebas desde el editor, así que dejas de depender del modo de ejecución para desarrollar.
Por qué funciona: la respuesta no se queda en "Static Data está mal". Explica el síntoma concreto (el modo test), sube al problema de fondo (atomicidad) y aterriza en la solución exacta del módulo (ON CONFLICT sobre una columna única). Es la conversación que convierte "a veces funciona" en un diagnóstico y un plan.
Resumen y siguiente paso
En esta lección cerraste el argumento que hace inevitable el resto del módulo. $getWorkflowStaticData() —el post-it de n8n— parece la respuesta natural a "guardar algo entre ejecuciones", y para un cursor pequeño de baja frecuencia lo es. Para la verdad de un sistema idempotente, no. Lo probaste tú mismo: al deduplicar order-triage con Static Data desde el editor, el mismo ORD-2041 nunca aparece como duplicado, porque Static Data no se guarda en ejecuciones de prueba —solo persiste en workflows activos disparados por trigger o webhook—.
Y aunque lo actives, quedan los bordes que lo descalifican: no es atómico (reproduce la carrera de "verificar y luego actuar"), su alcance es un solo workflow, se comporta de forma poco fiable en alta frecuencia, está pensado para datos pequeños y es frágil al exportar e importar el workflow. $vars tampoco es el lugar: es configuración de solo lectura, no estado que el sistema escribe.
La conclusión no es que Static Data esté mal, sino que la verdad de un sistema idempotente tiene que vivir en una base de datos real, separada del workflow. Y ya la tienes: el Postgres del Starter Kit v2. Cada límite que viste —atomicidad, alcance compartido, fiabilidad, tamaño, separación del código— es algo que una base de datos relacional da de fábrica.
Antes de avanzar deberías poder: citar el hecho de que Static Data no persiste al probar desde el editor; nombrar al menos tres de sus cinco bordes; y explicar por qué $vars no sirve para estado.
La lección 3 empieza a construir la solución. Vas a diseñar el run ledger: la tabla que anota qué corrió, con qué clave de idempotencia, en qué estado —pendiente, hecho o fallido— y con qué resultado. Es la libreta completa del sistema, la que no solo dice "esto ya pasó" sino "esto pasó, a esta hora, y terminó así". Con ella, la pregunta "¿ya procesé ORD-2041?" por fin tiene un lugar confiable donde vivir.
Recursos
- getWorkflowStaticData — n8n Docs — la fuente del comportamiento clave: qué es Static Data, la diferencia entre
'global'y'node', y la advertencia de que no está disponible al probar workflows y solo se guarda con el workflow activo disparado por trigger o webhook. - Define custom variables — n8n Docs —
$vars: disponibilidad por plan, que todas las variables son texto y de solo lectura. Útil para confirmar por qué no sirven como estado que el sistema escribe. - Postgres node — n8n Docs — el nodo con el que, a partir de la lección 3, vas a leer y escribir la verdad del sistema en una base de datos real.
- Deploy with the AI starter kit — n8n Docs — el stack local que ya trae el Postgres donde va a vivir esa verdad, a costo cero.
- Understand n8n's data structure — n8n Docs — la estructura de los items que viajan entre nodos, que es estado de ejecución (se borra al terminar), para tener clara la frontera con el estado del sistema (persiste).