Módulo 3: Exportar, normalizar y estructurar el repositorio

4. Normalizar el JSON para diffs limpios

Descripción

Al terminar esta lección vas a poder tomar el JSON crudo que exporta la CLI de n8n y limpiarlo para que Git te muestre diffs legibles: solo lo que de verdad cambió, sin el ruido de campos que se modifican solos. Vas a saber qué es un campo volátil y cuáles son en un workflow de n8n, por qué el orden de las claves del JSON provoca diffs falsos, y vas a escribir un pequeño script de normalización —con jq o con Node— que quita el ruido y ordena las claves de forma estable. Ese script es una de las piezas que la lección 7 va a automatizar.

Esto importa porque un diff ilegible mata la razón de versionar. El punto de tener order-triage en Git es poder abrir un git diff y entender, en diez segundos, qué cambió entre ayer y hoy. Si cada guardado ensucia el diff con cuarenta líneas que cambiaron solas, revisar se vuelve imposible, nadie lee los diffs, y el control de versiones se degrada a "una carpeta con backups". Peor aún: cuando dos personas trabajan sobre el mismo workflow, un JSON desordenado multiplica los conflictos de fusión. Normalizar convierte el JSON de "técnicamente versionado" a "de verdad revisable".

Conexión con el módulo: en la lección 2 exportaste el JSON; en la 3 sacaste los secretos del camino. Ahora ese JSON limpio de secretos todavía es ruidoso, y esta lección lo pule. Es el último paso de "conseguir material limpio" antes de pasar a "darle forma" en la lección 5 (estructura) y la 6 (documentación). Y el script que escribas aquí se junta con el comando de exportación de la lección 2 para formar, en la lección 7, un solo export.sh que hace todo de una pasada. Presta atención a la forma del script: lo vas a reutilizar.

Por qué el JSON crudo da diffs sucios

Volvamos al JSON de order-triage que exportaste. Guárdalo, cambia una cosa mínima en el editor —mueve un nodo dos centímetros, o nada en absoluto: solo abre y guarda—, exporta de nuevo, y compara los dos con git diff. Lo que vas a ver es desconcertante la primera vez: Git marca varias líneas como cambiadas, aunque tú no tocaste la lógica.

¿Qué cambió, entonces? Campos que n8n modifica por su cuenta cada vez que guardas o exportas. A esos campos los llamamos volátiles: cambian solos, sin relación con lo que tú hiciste, como una temperatura que sube y baja aunque nadie toque el termostato. Estos son los principales en un workflow de n8n:

CampoQué esPor qué es ruido
pinDataDatos de prueba que "fijaste" en un nodo mientras editabasCambian según con qué pruebes; no son lógica
versionIdUn identificador de la versión guardadan8n lo regenera en cada guardado
meta.instanceIdIdentifica tu servidor de n8nEs de tu máquina, no del workflow; distinto en cada instancia
idEl identificador del workflow en la base de datosEs específico de la instancia; otra instancia le da otro
activeSi el workflow está encendido o noEs un estado, no lógica; y depende del entorno
triggerCountUn contador interno de disparadoresCambia con el uso, no con la edición
createdAt, updatedAtMarcas de tiempo de creación y última modificaciónCambian con cada guardado, por definición

Fíjate en el patrón: ninguno de estos campos describe la lógica del workflow. Son metadatos de estado, de instancia o de momento. Cuando revisas un diff, lo que quieres ver es "cambió la condición del nodo If" o "se agregó un nodo HTTP", no "el versionId pasó de e7f8... a a1b2...". Los volátiles son puro humo entre tú y la señal.

Hay un segundo culpable, más sutil, y es el que provoca los diffs más traicioneros.

El orden de las claves: el diff fantasma

El JSON, por diseño, no garantiza el orden de las claves dentro de un objeto. Estos dos fragmentos son idénticos para una máquina:

{ "name": "order-triage", "active": false }
{ "active": false, "name": "order-triage" }

Representan exactamente el mismo dato. Pero para Git, que compara texto línea por línea, son distintos: las líneas están en otro orden. Si una exportación pone name antes que active y la siguiente los invierte, Git te va a marcar ambas líneas como cambiadas, aunque el contenido sea el mismo.

Esto pasa de verdad: distintas versiones de n8n, o incluso el mismo n8n en momentos distintos, pueden serializar las claves en órdenes diferentes. El resultado es un diff que grita "¡cambió todo!" cuando no cambió nada. Es un diff fantasma: ruido que se ve como señal.

La solución a los dos problemas —volátiles y orden— es la normalización.

Qué es normalizar

Normalizar un JSON es transformarlo a una forma canónica: siempre la misma estructura, siempre el mismo orden, sin los campos que no aportan. La palabra viene de "norma": le impones una norma fija al archivo, para que dos exportaciones del mismo workflow produzcan exactamente el mismo texto, byte por byte.

Son dos operaciones, y conviene tenerlas separadas en la cabeza:

  1. Quitar los campos volátiles. Borras pinData, versionId, meta.instanceId, y compañía. Lo que queda es solo la lógica.
  2. Ordenar las claves de forma estable. Reescribes el JSON con las claves siempre en el mismo orden (alfabético, típicamente). Así el diff fantasma desaparece: si la lógica no cambió, el texto es idéntico.

Piénsalo como ordenar un cajón de herramientas antes de guardarlo. Si cada vez que cierras el cajón las herramientas quedan en un orden distinto, nunca sabes si falta alguna: todo se ve diferente. Si siempre las guardas en el mismo lugar —la llave inglesa a la izquierda, el destornillador a la derecha—, un vistazo te dice al instante si algo cambió. Normalizar es guardar el JSON siempre en el mismo orden, para que el cambio salte a la vista.

El resultado: después de normalizar, git diff solo muestra lo que de verdad tocaste. Mueves un nodo dos centímetros sin cambiar su configuración, y el diff está vacío. Cambias la condición de un nodo, y el diff muestra exactamente esa línea. Eso es lo que hace revisable un workflow.

Herramienta 1: jq

La forma más directa de normalizar JSON en la terminal es jq.

jq es un programa de línea de comandos para procesar JSON. Su nombre se lee "jota-cu". Lee JSON por un lado, le aplica una transformación que tú describes, y escribe el JSON transformado por el otro. Es a JSON lo que una calculadora es a los números: le das una expresión y te da el resultado. No viene instalado por defecto en todos los sistemas; se instala con el gestor de paquetes (brew install jq en macOS, apt install jq en Debian/Ubuntu). Si no lo tienes, en un momento verás la alternativa con Node.

Las dos operaciones de la normalización se traducen a jq así:

Ordenar las claves: la bandera -S (o su forma larga --sort-keys) reescribe el JSON con todas las claves ordenadas alfabéticamente, de forma recursiva —también las claves anidadas dentro de nodos—. Una sola bandera resuelve el diff fantasma entero.

Quitar campos: la función del(...) borra las claves que le indiques. Se escribe con un punto delante de cada campo, y varios se separan con coma:

del(.pinData, .versionId, .active, .triggerCount, .meta.instanceId)

Léelo como una instrucción: "borra pinData, versionId, active, triggerCount y, dentro de meta, la clave instanceId". El punto significa "la clave en la raíz del objeto"; .meta.instanceId baja un nivel para borrar solo esa clave anidada sin tocar el resto de meta.

Anatomía del comando de normalización con jq

Juntando las dos operaciones:

jq -S 'del(.pinData, .versionId, .active, .triggerCount, .meta.instanceId)' order-triage.json

Pieza por pieza:

  • jq — el programa.
  • -S — ordena las claves alfabéticamente (resuelve el orden).
  • 'del(...)' — la transformación, entre comillas simples para que la terminal no interprete los puntos ni los paréntesis. Borra los volátiles.
  • order-triage.json — el archivo de entrada que jq lee.

Qué esperar: jq imprime en la terminal el JSON de order-triage sin esos cinco campos y con las claves ordenadas. Ojo: lo imprime, no modifica el archivo. Para que el cambio quede guardado, hay que capturar esa salida en un archivo, y ahí aparece una trampa importante.

La trampa del "escribir en el mismo archivo"

La tentación es hacer esto:

# ⚠️ MAL: esto vacía el archivo
jq -S 'del(.pinData)' order-triage.json > order-triage.json

No lo hagas. El problema es de orden de operaciones. El símbolo > redirige la salida a order-triage.json, y la terminal abre y vacía ese archivo antes de que jq empiece a leerlo. Resultado: jq intenta leer un archivo ya vacío, y terminas con un order-triage.json en blanco. Tu workflow desaparece.

La forma correcta es escribir a un archivo temporal y después reemplazar:

jq -S 'del(.pinData, .versionId, .active, .triggerCount, .meta.instanceId)' order-triage.json > order-triage.tmp && mv order-triage.tmp order-triage.json

Desármalo:

  • ... > order-triage.tmp — jq escribe el resultado a un archivo nuevo, temporal. El original queda intacto mientras jq lee.
  • && — "y si lo anterior salió bien, entonces". Encadena los dos comandos con una condición: solo sigue si jq terminó sin error. Si jq falla, el mv no corre y tu original se salva.
  • mv order-triage.tmp order-triage.json — reemplaza el original por el temporal ya normalizado. mv es "mover/renombrar".

Este patrón —escribir a temporal, y con && reemplazar solo si salió bien— es un reflejo que vale la pena adoptar para cualquier herramienta que "procese un archivo en su lugar". No es exclusivo de jq.

Ejemplo trabajado: normalizar order-triage

Vamos a verlo de punta a punta, con el antes y el después.

Antes. Este es un recorte del order-triage.json recién exportado, con los volátiles marcados:

{
  "active": true,
  "id": "aBcD1234EfGh5678",
  "name": "order-triage",
  "nodes": [ /* ... la lógica de verdad ... */ ],
  "connections": { /* ... */ },
  "pinData": {
    "Webhook": [ { "json": { "order_id": "ORD-2041", "customer_name": "Luna Coffee" } } ]
  },
  "triggerCount": 3,
  "versionId": "e7f8a9b0-1111-2222-3333-444455556666",
  "meta": { "instanceId": "9c8b7a6d5e4f3a2b1c0d..." }
}

El comando:

jq -S 'del(.pinData, .versionId, .active, .triggerCount, .meta.instanceId)' order-triage.json > order-triage.tmp && mv order-triage.tmp order-triage.json

Después. El archivo queda así —sin volátiles, con las claves ordenadas alfabéticamente:

{
  "connections": { /* ... */ },
  "id": "aBcD1234EfGh5678",
  "meta": {},
  "name": "order-triage",
  "nodes": [ /* ... la lógica de verdad ... */ ]
}

Fíjate en tres cosas. Desaparecieron active, pinData, triggerCount y versionId. meta quedó como un objeto vacío {} porque le quitamos su única clave, instanceId —opcionalmente puedes borrar meta entero con del(.meta) si prefieres, es una decisión de gusto—. Y las claves quedaron en orden alfabético: connections, id, meta, name, nodes. La lógica —nodes y connections— está intacta. Solo se fue el humo.

La prueba real: ahora abre order-triage en el editor, guárdalo sin cambiar nada, expórtalo de nuevo y normalízalo con el mismo comando. Compara con git diff. Qué esperar: el diff está vacío. Cambió el versionId en la exportación cruda, sí, pero como la normalización lo borra, el archivo normalizado es idéntico. Eso es exactamente lo que buscábamos: guardar sin cambiar lógica no ensucia el repo.

Una decisión honesta: ¿y el id?

Notaste que en el ejemplo dejé el id del workflow. Es una decisión con un matiz que vale la pena que entiendas, porque no hay una única respuesta correcta.

El id es específico de la instancia: en el dev de Cumbre, order-triage tiene un id; en prod, podría tener otro. Eso lo hace medio volátil. Podrías borrarlo con del(.id) para que el archivo sea totalmente independiente de la instancia. El costo: al reimportar, n8n usa el id para saber si un workflow ya existe y actualizarlo, o si es nuevo y hay que crearlo. Sin id, corres el riesgo de que la importación cree un duplicado en vez de actualizar el existente.

La recomendación práctica, hasta que el Módulo 6 trate la promoción entre entornos a fondo: conserva el id si tu flujo es exportar e importar en la misma instancia (respaldo y restauración). Considera quitarlo solo cuando el archivo tenga que viajar entre instancias distintas y prefieras que cada una gestione sus propios ids. Lo que no admite duda son los otros —pinData, versionId, meta.instanceId, triggerCount, active—: esos son ruido puro y se van siempre.

Herramienta 2: Node, si no tienes jq

Si jq no está disponible en tu máquina, o prefieres no instalar otra herramienta, un script corto de Node hace lo mismo. Y aquí una aclaración importante para esta guía: este es un script que corre en tu terminal, fuera de n8n. No es un nodo Code. Todas las restricciones del nodo Code de n8n 2.0 —nada de require, nada de sistema de archivos— no aplican aquí, porque esto es Node normal en tu computadora, con acceso completo a leer y escribir archivos. La restricción es solo para el código dentro de un workflow.

Este es normalize.js:

// normalize.js — normaliza un JSON de workflow de n8n para diffs limpios.
// Uso: node normalize.js order-triage.json
// Corre en tu terminal (Node normal), NO dentro de n8n.

const fs = require('fs');                 // módulo para leer y escribir archivos

const filePath = process.argv[2];         // el nombre de archivo que pasaste como argumento
const raw = fs.readFileSync(filePath, 'utf8');
const workflow = JSON.parse(raw);         // el texto JSON convertido a objeto

// 1) Quitar los campos volátiles (el "humo" que cambia solo).
const volatile = ['pinData', 'versionId', 'active', 'triggerCount'];
for (const key of volatile) {
  delete workflow[key];                   // delete quita la clave del objeto
}
if (workflow.meta) {
  delete workflow.meta.instanceId;        // el instanceId vive anidado dentro de meta
}

// 2) Reescribir con las claves ordenadas de forma estable.
//    El tercer argumento de JSON.stringify es la sangría (2 espacios) para que sea legible.
const normalized = JSON.stringify(workflow, sortedKeys, 2);

fs.writeFileSync(filePath, normalized + '\n');  // sobrescribe el archivo, con salto de línea final
console.log(`Normalizado: ${filePath}`);

// Esta función le dice a JSON.stringify que recorra las claves en orden alfabético.
function sortedKeys(key, value) {
  if (value && typeof value === 'object' && !Array.isArray(value)) {
    return Object.keys(value)
      .sort()                             // orden alfabético estable
      .reduce((acc, k) => {
        acc[k] = value[k];
        return acc;
      }, {});
  }
  return value;                           // los arreglos y valores simples se dejan igual
}

Se corre así:

node normalize.js order-triage.json

Qué esperar: la terminal imprime Normalizado: order-triage.json, y el archivo queda sin volátiles y con las claves ordenadas, igual que con jq. La ventaja de Node es que no depende de instalar jq y que el criterio de qué borrar está escrito de forma explícita, línea por línea, fácil de ajustar. La ventaja de jq es que es una sola línea. Elige el que te resulte más cómodo; el resultado es el mismo.

Un detalle del script que conviene notar: aquí JSON.stringify reescribe el archivo directamente con writeFileSync sobre el mismo filePath, sin la danza del archivo temporal. Es seguro porque Node primero leyó todo el contenido a memoria (readFileSync) y después escribe; no hay riesgo de vaciar el archivo antes de leerlo, como sí lo había con la redirección > de la terminal.

Verifica que tu normalización es determinista

Una normalización que sirve tiene una propiedad que se puede comprobar: es determinista e idempotente. Determinista significa que la misma entrada siempre da la misma salida. Idempotente —una palabra que suena rara y describe una idea simple— significa que aplicarla dos veces da lo mismo que aplicarla una: normalizar algo ya normalizado no lo cambia. Como pasar un peine por el pelo ya peinado: no hace nada nuevo porque ya está en orden.

Vale la pena confirmarlo con un chequeo de treinta segundos, porque si tu normalización no es idempotente, tienes un problema escondido que va a ensuciar diffs sin que sepas por qué:

# Normaliza una vez.
jq -S 'del(.pinData, .versionId, .active, .triggerCount, .meta.instanceId)' order-triage.json > pass1.json
# Normaliza el resultado otra vez.
jq -S 'del(.pinData, .versionId, .active, .triggerCount, .meta.instanceId)' pass1.json > pass2.json
# Compara las dos pasadas.
diff pass1.json pass2.json

Qué esperar: diff no imprime nada. Silencio total significa que los dos archivos son idénticos, es decir, que normalizar por segunda vez no cambió nada: tu normalización es idempotente. Si diff imprime diferencias, algo en tu proceso no es estable —quizás el orden de claves no se aplicó de forma consistente— y hay que revisarlo antes de confiar en él. (diff es la herramienta de sistema que compara dos archivos línea por línea; Git la usa por dentro, pero también funciona suelta como aquí.)

Este chequeo es tu red de seguridad cuando ajustes el script en el futuro: cada vez que cambies qué campos borras, corre la prueba de las dos pasadas. Si diff calla, tu cambio es seguro.

Una nota honesta sobre las marcas de tiempo

Listé createdAt y updatedAt entre los volátiles, y son los que más varían entre versiones de n8n: dependiendo de tu versión, la exportación por CLI puede incluirlos o no, y pueden vivir en la raíz o dentro de otro objeto. No te fíes de mi lista a ciegas: abre tu propio order-triage.json recién exportado y mira qué campos tiene de verdad. Si ves createdAt o updatedAt, agrégalos a tu del(...). Si no aparecen, tu versión no los exporta y no hay nada que borrar. Este es el mismo hábito de la lección 2 —verificar contra tu instancia— aplicado a la normalización: la lista canónica de volátiles es la que descubres mirando tu propio archivo, no la que copias de una guía.

Por qué esto reduce los conflictos de fusión

Hay un beneficio de la normalización que no es evidente hasta que trabajas en equipo: reduce los conflictos de fusión (merge conflicts).

Un conflicto de fusión ocurre cuando dos personas cambian la misma línea de un archivo y Git no sabe cuál conservar. En un JSON no normalizado, esto pasa mucho más de lo que debería, por el orden inestable de las claves: la persona A guarda y las claves quedan en un orden; la persona B guarda y quedan en otro. Ahora las mismas líneas están en posiciones distintas en las dos versiones, y cuando se intenta fusionar, Git ve conflictos por todas partes —aunque cada uno haya cambiado partes distintas de la lógica—.

Con el orden estable, cada clave vive siempre en el mismo renglón. Si A cambió la configuración del nodo AI Agent y B cambió el nodo HTTP Request, sus cambios tocan renglones distintos, Git los fusiona sin drama, y no hay conflicto. La normalización no solo hace legible el diff de una persona; hace que el trabajo de dos personas encaje sin pelearse.

Es la diferencia entre dos personas escribiendo en un cuaderno con renglones fijos y numerados, y dos personas escribiendo en hojas en blanco que después hay que superponer. Con renglones fijos, cada quien sabe dónde va lo suyo.

Un tercer diff fantasma: los finales de línea

Hay una fuente de ruido más, que muerde sobre todo a equipos con máquinas mixtas (unos en Windows, otros en macOS o Linux): los finales de línea. Windows termina cada línea de un archivo de texto con dos caracteres invisibles (CR y LF); macOS y Linux usan uno solo (LF). Para el ojo, los archivos se ven idénticos. Para Git, cada línea es distinta, porque los caracteres invisibles del final cambiaron. Resultado: si Ana en Windows normaliza y commitea, y Beto en Linux abre el mismo archivo, puede ver "todo el archivo cambiado" sin que nadie tocara la lógica.

La cura es un archivo .gitattributes en la raíz del repo que le fija a Git una norma de finales de línea para los JSON:

# Normaliza los finales de línea de los workflows a LF, en cualquier sistema operativo.
*.json text eol=lf

Léelo así: para cualquier archivo que termine en .json, trátalo como texto (text) y usa finales de línea estilo LF (eol=lf), sin importar en qué sistema esté quien lo edita. Con eso, Ana y Beto ven el mismo archivo aunque trabajen en sistemas distintos, y el tercer diff fantasma desaparece. Es un archivo de una línea que ahorra tardes enteras de "pero si yo no cambié nada".

Errores comunes

Vaciar el archivo con jq ... > mismo-archivo.json (práctico). Qué pasa: corres jq 'del(.pinData)' order-triage.json > order-triage.json y el archivo queda vacío; tu workflow desaparece. Por qué pasa: la terminal abre y vacía el archivo de destino antes de que jq lo lea, así que jq lee un archivo ya en blanco. Cómo detectarlo: si después de un comando así el archivo pesa cero bytes, es esto. Cómo corregirlo: escribe siempre a un archivo temporal y reemplaza con &&: jq '...' in.json > in.tmp && mv in.tmp in.json. Y trabaja con Git: si ya tenías el workflow commiteado, git checkout -- order-triage.json lo recupera de la última versión guardada. Esta es otra razón para commitear seguido.

Borrar campos que sí son lógica (práctico). Qué pasa: alguien, entusiasmado con la limpieza, agrega nodes o connections a la lista de del(...) y termina con un archivo que ya no describe ningún workflow. Por qué pasa: la línea entre "volátil" y "lógica" no siempre es obvia si vas rápido. Cómo detectarlo: después de normalizar, el archivo debe seguir teniendo nodes y connections, que son el corazón del workflow; si no están, borraste de más. Cómo corregirlo: quédate con la lista confirmada de volátiles (pinData, versionId, meta.instanceId, triggerCount, active) y no agregues nada a del() sin estar seguro de que ese campo no es lógica. Ante la duda, no lo borres: un campo de más en el diff molesta; un campo de lógica de menos rompe el workflow.

Normalizar a mano, una vez, y creer que ya está resuelto (conceptual). Qué pasa: alguien limpia el JSON a mano en un editor de texto una vez, lo commitea, y a la siguiente exportación vuelve el ruido, porque la limpieza manual no se repite sola. Por qué pasa: la normalización solo sirve si se aplica cada vez que exportas; hecha una sola vez, es un espejismo. Cómo detectarlo: si tu proceso de normalizar depende de que te acuerdes de borrar campos a mano, no es reproducible. Cómo corregirlo: guarda el comando (jq) o el script (normalize.js) y córrelo en cada exportación. Mejor aún: espera a la lección 7, donde ese script se engancha automáticamente después de exportar, para que nunca dependas de tu memoria.

Olvidar que el orden de las claves también cuenta (conceptual). Qué pasa: alguien borra los volátiles pero no ordena las claves, y sigue viendo diffs fantasma donde "cambió todo" sin haber cambiado nada. Por qué pasa: es fácil concentrarse en los campos volátiles y olvidar el segundo problema, el del orden inestable. Cómo detectarlo: si el diff marca líneas movidas de lugar sin cambio de contenido, es el orden. Cómo corregirlo: asegúrate de que tu normalización ordena las claves —-S en jq, la función sortedKeys en Node—. Quitar volátiles sin ordenar es media normalización.

Ejercicios

Ejercicio 1 — Clasifica volátil o lógica. Para cada uno de estos siete campos de un JSON de workflow, di si es volátil (se va en la normalización) o lógica (se queda), y por qué en pocas palabras: (a) nodes; (b) versionId; (c) connections; (d) pinData; (e) meta.instanceId; (f) name; (g) triggerCount.

Ver solución

(a) nodeslógica. Es la definición de cada nodo; el corazón del workflow. Se queda. (b) versionIdvolátil. n8n lo regenera en cada guardado. Se va. (c) connectionslógica. Describe cómo se conectan los nodos entre sí. Se queda. (d) pinDatavolátil. Datos de prueba fijados al editar. Se va. (e) meta.instanceIdvolátil. Identifica tu servidor, no el workflow. Se va. (f) namelógica (o al menos, identidad estable). Es el nombre del workflow; no cambia solo. Se queda. (g) triggerCountvolátil. Contador interno que cambia con el uso. Se va.

Por qué funciona: la prueba mental para clasificar es una sola pregunta —"¿este campo cambia cuando NO cambio la lógica?"—. Si la respuesta es sí (versionId, pinData, instanceId, triggerCount), es volátil. Si describe qué hace el workflow (nodes, connections, name), es lógica. Tener afilada esa pregunta es lo que te deja decidir con seguridad ante un campo que esta lección no listó.

Ejercicio 2 — Escribe el comando jq. Escribe el comando jq completo que normaliza weekly-report.json: que ordene las claves y que borre pinData, versionId, active, triggerCount y meta.instanceId, escribiendo el resultado de forma segura sobre el mismo archivo.

Ver solución
jq -S 'del(.pinData, .versionId, .active, .triggerCount, .meta.instanceId)' weekly-report.json > weekly-report.tmp && mv weekly-report.tmp weekly-report.json

Las piezas clave: -S ordena las claves; del(...) con los cinco campos, separados por coma, borra los volátiles; .meta.instanceId con dos puntos baja al campo anidado sin tocar el resto de meta; y el archivo temporal con > ....tmp && mv evita vaciar el original. Si escribiste > weekly-report.json directo, revisa el error "vaciar el archivo": ese comando borra tu workflow.

Por qué funciona: este es, casi textual, el comando que vas a meter en el script de la lección 7. Escribirlo de memoria ahora hace que cuando lo veas dentro del script, ya lo reconozcas en vez de tener que descifrarlo.

Ejercicio 3 — Predice el diff. Tienes order-triage.json normalizado y commiteado. Abres el workflow en el editor, cambias el umbral de un nodo If de 1500 a 2000, guardas, exportas y normalizas con el mismo comando. ¿Qué esperas ver en git diff? ¿Y si en vez de cambiar el umbral solo hubieras abierto y guardado sin tocar nada?

Ver solución

Si cambiaste el umbral, el git diff muestra exactamente una línea de cambio: la que tenía 1500 ahora dice 2000, dentro de la configuración de ese nodo. Nada más. Ese es el sueño: el diff cuenta la historia real del cambio.

Si solo abriste y guardaste sin tocar nada, el git diff está vacío. En la exportación cruda, n8n habría cambiado el versionId (y quizás alguna marca de tiempo), pero como la normalización los borra, el archivo normalizado es idéntico al commiteado. Git no ve diferencia porque, en lo que importa, no la hay.

Por qué funciona: estos dos escenarios son la prueba de fuego de que la normalización sirve. Un cambio real produce un diff mínimo y legible; un no-cambio produce un diff vacío. Si en tu instancia ves algo distinto —por ejemplo, líneas de más en el segundo caso—, es señal de que algún volátil se te escapó de la lista de del(), y ya sabes cómo cazarlo: mira qué línea marcó el diff y agrégala si de verdad es ruido.

Resumen y siguiente paso

En esta lección viste por qué el JSON crudo de un workflow da diffs sucios: campos volátiles que n8n cambia solo —pinData, versionId, meta.instanceId, id, active, triggerCount, las marcas de tiempo— y el orden inestable de las claves, que produce diffs fantasma donde "cambió todo" sin cambiar nada. La solución es normalizar: quitar los volátiles y ordenar las claves de forma estable, para que dos exportaciones de la misma lógica den el mismo texto byte por byte. Lo hiciste de dos formas —con jq -S 'del(...)', cuidándote de no vaciar el archivo con la redirección >, y con un script normalize.js de Node que corre fuera de n8n sin las restricciones del nodo Code— y viste cómo la normalización, además de hacer legible el diff de una persona, reduce los conflictos de fusión cuando trabajan dos.

Antes de avanzar deberías poder: nombrar cuatro campos volátiles y explicar por qué son ruido; explicar qué es el diff fantasma del orden de claves; escribir de memoria el comando jq que normaliza un archivo sin vaciarlo; y predecir que un guardado sin cambios de lógica produce, tras normalizar, un diff vacío.

La lección 5 da el salto de "material limpio" a "material con forma". Ya tienes workflows exportados, sin secretos y normalizados, pero todos amontonados con nombres feos de ids. Vas a estructurar el repositorio cumbre-automations con un layout profesional —workflows/, credentials/ (solo esquema, nunca valores), docs/, scripts/, .env.example, README— y a mapear las carpetas y tags de n8n a ese layout, para que otro desarrollador abra el repo y lo entienda de un vistazo.

Recursos