C.G.

ETL con n8n y PostgreSQL

Mover una hoja de cálculo a una base de datos parece sencillo hasta que llegan encabezados distintos, filas duplicadas, fechas ambiguas o una ejecución que se interrumpe a mitad. Un flujo ETL confiable separa la recepción, la validación y la carga; registra qué pasó y puede procesar de nuevo el mismo archivo sin duplicar información. Esta guía presenta un patrón práctico con n8n y PostgreSQL, aplicable a importaciones periódicas y a cargas puntuales.

Qué debe resolver el flujo ETL

ETL significa extraer, transformar y cargar. En este caso, n8n coordina la recepción de un archivo, la validación y las llamadas a PostgreSQL. La base de datos conserva los registros y el historial de importaciones. Separar esas responsabilidades permite investigar una fila rechazada sin volver a revisar todo el workflow.

Antes de crear nodos, define el contrato de entrada: formatos permitidos, nombres de columnas, campos obligatorios, tipos, zona horaria y regla para identificar de forma única cada registro. También decide qué ocurre si un archivo se repite, si trae una fila inválida o si el proceso falla después de insertar parte de los datos. Esas decisiones determinan la arquitectura más que el número de nodos.

Una arquitectura sencilla y auditable

Un flujo mantenible suele tener etapas explícitas: recibir el archivo, registrar el lote, leer filas, normalizar valores, validar, cargar datos temporales, aplicar cambios a las tablas de negocio y cerrar el lote con un resultado. En n8n conviene que cada etapa tenga entradas y salidas previsibles, con nombres que expliquen su propósito. Evita un único bloque de código que lea, transforme y escriba todo sin dejar puntos de control.

Para cargas que necesitan validación o revisión, usa una tabla de staging. Las filas llegan allí con un identificador de lote y conservan suficiente información de origen para diagnosticar errores. Solo después de validar el conjunto se aplican las filas aceptadas a las tablas finales. Para una sincronización pequeña y simple, staging puede ser innecesario; elige el diseño según el coste de una carga parcial o incorrecta.

Define el esquema y la identidad de cada fila

Una base de datos necesita saber qué representa cada columna. Evita guardar todo como texto solo porque el origen es una hoja: fechas, cantidades e identificadores deben tener tipos adecuados en PostgreSQL. Define también la clave natural o técnica que permite reconocer la misma entidad en dos importaciones. Sin una clave estable, el flujo no puede distinguir una actualización de un duplicado.

CREATE TABLE registros (
  source_id text PRIMARY KEY,
  amount numeric NOT NULL,
  source_updated_at timestamptz,
  imported_at timestamptz NOT NULL DEFAULT now()
);

El ejemplo es deliberadamente pequeño: adapta los nombres y restricciones al dominio real. Si un identificador solo es único dentro de una entidad, crea una clave compuesta; si el archivo no trae una clave fiable, define cómo generarla y documenta sus límites. No uses el número de fila como identidad: cambia cuando alguien ordena o inserta filas en la hoja.

Normaliza los datos antes de validarlos

La normalización convierte variantes de representación a una forma consistente: quitar espacios, normalizar mayúsculas cuando el dominio lo permite, convertir números y fechas, y representar valores vacíos de una sola manera. Mantén el valor original cuando sea útil para explicar una transformación. No conviertas silenciosamente un dato inválido en cero o en la fecha actual: eso transforma un error visible en un dato incorrecto que parece legítimo.

La validación debe distinguir errores de fila y errores del lote. Una fila con un correo mal formado puede quedar en cuarentena mientras el resto sigue, si el negocio lo permite. Un archivo sin las columnas mínimas, con una codificación ilegible o con un encabezado desconocido debería detenerse antes de escribir en las tablas finales. Devuelve un motivo concreto, como amount_not_numeric, en vez de un genérico «fila inválida».

Haz que repetir una importación sea seguro

Una ejecución puede terminar después de escribir en PostgreSQL pero antes de confirmar a n8n que tuvo éxito. Si el workflow vuelve a intentarlo, una carga que solo inserta puede duplicar filas. Diseña la operación para que procesar dos veces el mismo registro produzca el mismo estado que procesarlo una sola vez. A esta propiedad se le suele llamar idempotencia.

Con una clave única, PostgreSQL permite insertar o actualizar de forma atómica:

INSERT INTO registros (source_id, amount, source_updated_at)
VALUES ($1, $2, $3)
ON CONFLICT (source_id) DO UPDATE
SET amount = EXCLUDED.amount,
    source_updated_at = EXCLUDED.source_updated_at,
    imported_at = now();

Usa parámetros del nodo PostgreSQL en lugar de concatenar valores dentro del SQL. Decide además si debe ganar siempre el archivo más reciente, si solo se actualizan columnas cambiadas o si una fila antigua debe rechazarse. La regla de conflicto es una decisión de negocio y debe estar cubierta por pruebas.

Procesa por lotes sin perder trazabilidad

Leer y escribir miles de filas en una sola operación puede elevar el consumo de memoria y hacer difícil localizar el registro que falla. Procesar en lotes controlados permite limitar el tamaño de cada operación y registrar cuántas filas se aceptaron, rechazaron y cargaron. El tamaño adecuado depende del volumen, los límites del servicio y la latencia de la base; mídelo con datos representativos en lugar de asumir que el lote más grande siempre es mejor.

Guarda un registro de importación con un identificador de lote, nombre o huella del archivo, hora de inicio, estado, cantidades y mensaje de error. Si el mismo archivo no debería aplicarse dos veces, una huella criptográfica puede ayudar a detectarlo, pero no reemplaza la clave por fila: dos archivos distintos pueden contener las mismas entidades y un mismo archivo puede necesitar reprocesarse tras corregir una regla.

Diseña fallos, reintentos y recuperación

No todos los errores se resuelven reintentando. Un timeout temporal puede justificar un número limitado de reintentos con espera; un valor que no cumple el esquema requiere corregir o poner en cuarentena la fila. Evita reintentos infinitos y registra el identificador del lote en cada mensaje para poder seguir la ejecución de punta a punta.

Piensa también en el límite de la transacción. Si una importación debe ser todo o nada, las validaciones y escrituras finales tienen que ocurrir dentro de una estrategia transaccional compatible con los nodos y conexiones que uses. Si se aceptan cargas parciales, deja ese comportamiento explícito en el estado del lote y en el resumen que recibe quien opera el proceso. No informes «éxito» si quedaron filas pendientes sin explicar.

Monitorea el resultado, no solo la ejecución

Un workflow verde solo indica que sus pasos terminaron según su lógica; no demuestra que los datos tengan sentido. Registra métricas como filas recibidas, válidas, rechazadas, insertadas y actualizadas, duración, último lote procesado y antigüedad de los datos. Una alerta por ausencia de una importación esperada puede ser más útil que recibir una notificación por cada ejecución exitosa.

Los logs deben ayudar a diagnosticar sin exponer secretos ni datos personales innecesarios. Guarda errores con el identificador de fila o lote y un código estable; limita o enmascara el contenido sensible. Define quién recibe alertas y qué acción se espera: reintentar, corregir el archivo o revisar el esquema.

Prueba el flujo antes de automatizarlo

Prepara archivos de prueba con casos normales y casos límite: encabezado cambiado, fila vacía, duplicado, fecha con zona horaria, número decimal, caracteres acentuados y una fila incompleta. Verifica que un archivo válido genere el estado esperado, que el inválido no contamine las tablas finales y que ejecutar el mismo lote dos veces no duplique datos.

Prueba también una interrupción durante la carga y la recuperación posterior. La prueba debe comprobar el estado final de PostgreSQL, no solo que n8n muestre una marca verde. Mantén una muestra anonimizada como regresión cuando cambies el mapeo o las reglas de validación.

Ejemplo de ejecución, del archivo a la tabla

Imagina un archivo periódico con identificadores de proveedor, código de producto, cantidad y fecha de actualización. Al recibirlo, el workflow registra un lote y comprueba que estén presentes las columnas requeridas. Después convierte cantidades y fechas a tipos definidos, elimina espacios en claves y valida que la cantidad no sea negativa. Las filas correctas pasan a staging; las demás quedan asociadas al lote con un código de rechazo.

Cuando termina la validación, el flujo aplica las filas aceptadas mediante la clave de proveedor y producto. Si vuelve a ejecutarse el mismo archivo, la restricción única y la regla de conflicto actualizan el registro en vez de crear otro. El cierre del lote guarda conteos, duración y estado. El resumen puede indicar, por ejemplo, «98 aceptadas, 2 rechazadas» e incluir el motivo, sin copiar datos sensibles al mensaje. Es un ejemplo genérico: cada sistema debe adaptar claves, reglas y política de carga a su proceso.

Lista de verificación

  • Contrato de archivo, columnas, tipos y zona horaria documentado.
  • Clave estable para identificar registros y regla de conflicto definida.
  • Validación diferenciada para filas y para el lote completo.
  • Staging cuando se requiere inspección o carga atómica.
  • Reintentos limitados para errores transitorios, no para datos inválidos.
  • Historial de lotes con conteos, duración, estado y errores.
  • Pruebas de repetición, interrupción y recuperación.
  • Credenciales gestionadas fuera del contenido del workflow y del repositorio.

Errores frecuentes

  • Usar el número de fila como clave y crear duplicados al reordenar la hoja.
  • Convertir errores de formato en valores por defecto sin avisar.
  • Reintentar errores de validación como si fueran fallos temporales.
  • Marcar el lote como exitoso aunque algunas filas no se hayan cargado.
  • Guardar todo el archivo en los logs y exponer datos sensibles.
  • Usar una consulta SQL construida por concatenación en lugar de parámetros.

Preguntas frecuentes

¿Necesito una tabla de staging para cada flujo ETL?

No siempre. Es útil cuando necesitas validar el conjunto antes de publicarlo, investigar rechazos o recuperarte de cargas parciales. Para una sincronización pequeña, con esquema y clave bien definidos, una carga directa puede ser suficiente.

¿Cómo evito duplicados si n8n reintenta una ejecución?

Define una clave única que represente al registro y usa una operación de inserción o actualización basada en esa clave. Registra además el lote para auditar reintentos. La huella del archivo por sí sola no reemplaza la identidad de cada fila.

¿Debo detener todo el lote por una fila inválida?

Depende del impacto de aceptar una carga parcial. Si una fila incorrecta puede cambiar un total o una decisión, detén el lote y corrígelo. Si las filas son independientes, puedes poner las inválidas en cuarentena, siempre que el resultado informe claramente cuántas quedaron fuera.

¿Cómo elijo el tamaño del lote?

Mide memoria, duración, límites de conexión y facilidad de recuperación con volúmenes representativos. Un lote menor suele facilitar el diagnóstico; uno mayor puede reducir viajes a la base. El equilibrio depende de la carga y no conviene fijarlo sin pruebas.

Para seguir

La documentación de n8n y de PostgreSQL INSERT detalla las opciones de cada herramienta. Para la parte de contenedores y operación, puedes continuar con Docker para Desarrolladores. Si estás diseñando un proceso similar, hablemos.

← Volver