Todos los recursos

Todo lo que debe correr a una hora fija (una sincronización nocturna, un chequeo cada hora, un reporte el lunes por la mañana) merece más que una línea de cron que vas a terminar olvidando. Esta guía recorre el patrón exacto que uso en un n8n self-hosted: un Schedule Trigger con la zona horaria correcta, trabajo idempotente que aguanta una doble corrida, un camino de error que de verdad te avisa, y un seguro para que un job lento nunca arranque encima del anterior.

Programa un flujo de n8n con cron que no te dé sorpresas

En resumen

  • Usa el Schedule Trigger de n8n antes que el cron del sistema. Te da reintentos, historial de corridas y una UI sin que hagas nada.
  • Define la zona horaria del flujo a mano. Por defecto evalúa en UTC y va a disparar varias horas desfasado de lo que tenías en mente.
  • Haz el trabajo idempotente: un tick que corre dos veces no debe enviar, cobrar ni insertar por duplicado.
  • Conecta un flujo aparte con Error Trigger. La falla silenciosa es el verdadero riesgo de cualquier cosa programada.
  • Protégete del solape con un flag de lock, y recuerda que un contenedor reiniciado se salta en silencio cada tick que estuvo caído.

Un cron que corre es trivial. Un cron que corre bien, a tiempo, exactamente una vez, y que se queja cuando no puede es lo que vale la pena construir. Programar parece la parte fácil de la automatización: eliges una hora, la apuntas a algún trabajo y te olvidas. El problema aparece semanas después, sin hacer ruido. La sincronización nocturna corrió a la hora equivocada porque nadie configuró la zona horaria. Una API inestable hizo que el job disparara dos veces y le cobraste doble a un cliente. O todo dejó de correr después de un deploy y no te enteraste hasta cuatro días después. Esta guía recorre el patrón que de verdad uso en un n8n self-hosted para que esas fallas no te toquen a ti: el Schedule Trigger bien configurado, trabajo que es seguro repetir, un camino de error que te avisa, y un seguro contra el solape. Al final vas a tener un flujo programado en el que puedes confiar: o hace su trabajo, o te avisa que no pudo.

Esto es un paso a paso y el porqué detrás, todo junto. Te voy a dar la configuración exacta de los nodos y una expresión cron lista para pegar, pero también te voy a explicar por qué existe cada salvaguarda, porque las salvaguardas son lo que importa de verdad.

01 · Requisitos y cuándo un cron es la herramienta equivocada

Antes de tocar un solo nodo, dos cosas que revisar. Primero, lo que necesitas tener listo. Segundo, y esto la gente se lo salta, si un schedule es siquiera el trigger correcto.

Necesitas:

  1. Una instancia de n8n corriendo a la que puedas entrar. Self-hosted (Docker en un VPS, como yo lo tengo) o n8n Cloud, las dos sirven, y el Schedule Trigger se comporta igual.
  2. Las credenciales ya configuradas para lo que el flujo vaya a tocar: una base de datos, una API, un servicio de correo. Déjalas listas primero para que no termines haciendo debug de auth y de scheduling al mismo tiempo.
  3. Una respuesta clara a "¿a qué hora, en qué zona horaria, y qué pasa si corre dos veces?". Si no puedes responder la tercera, detente y lee la sección 03 antes de construir nada.

Nota

Un schedule es el trigger correcto cuando el trabajo debe pasar a una hora fija sin importar lo que hagan los usuarios: un export nocturno, un digest a las 9am, una reconciliación cada hora. Si el trabajo debe pasar en respuesta a un evento (una fila nueva, un webhook entrante, un archivo que llega al storage), usa el trigger que corresponda. Hacer polling cada poco para simular un flujo por eventos quema corridas y agrega latencia. Un Webhook o un trigger de base de datos sale más barato y más rápido.

La regla honesta: tira primero del Schedule Trigger de n8n antes de irte al crontab del sistema. El cron del sistema está bien, pero estarías rehaciendo a mano los reintentos, el historial de corridas, el almacenamiento de secretos y las alertas, todo lo que n8n ya te da. Baja al cron crudo solo cuando el job de verdad no tenga nada que ver con n8n.

02 · Agrega el Schedule Trigger y acierta con la hora

Abre un flujo nuevo y agrega un Schedule Trigger como primer nodo. Te ofrece dos formas de expresar el tiempo, y la elección importa más de lo que parece.

Intervalo vs. expresión cron

El modo intervalo ("cada 15 minutos", "todos los días a las 07:00") es claro de leer y alcanza para la mayoría de los jobs, así que úsalo siempre que encaje. Pásate a una expresión cron solo cuando necesites algo que el intervalo no expresa de forma limpia: "solo días de semana", "el 1 y el 15 del mes", "cada 30 minutos pero solo entre 9 y 5".

Una expresión cron tiene cinco campos: minuto, hora, día-del-mes, mes, día-de-semana.

0 7 * * 1-5    # 07:00, de lunes a viernes
*/30 9-17 * * *  # cada 30 min, 9:00–17:00, todos los días
0 0 1,15 * *   # medianoche el 1 y el 15

Léelas de izquierda a derecha y aguanta las ganas de pasarte de listo. Una línea de cron que nadie puede leer de un vistazo es una línea de cron que tarde o temprano va a correr a la hora equivocada y nadie se va a dar cuenta.

La trampa de la zona horaria

Este es el bug de scheduling más común, y es silencioso. Por defecto una instancia de n8n evalúa los schedules en UTC. Digamos que estás en la costa este de Estados Unidos y pones un job para las "07:00" pensando en tu mañana. Dispara a las 07:00 UTC, que es plena madrugada para ti, varias horas desfasado de lo que querías. Nada da error. El job simplemente corre a la hora equivocada para siempre.

Arréglalo en uno de dos lugares, y ten claro cuál estás usando:

  1. Por flujo: abre los Settings del flujo (el menú de tres puntos) y define su zona horaria a mano, por ejemplo America/New_York. Esto viaja con el flujo y es el default más seguro.
  2. Para toda la instancia: define la variable de entorno GENERIC_TIMEZONE en el contenedor de n8n para que cada flujo nuevo la herede.

Atención

No des por sentado que la zona horaria de la instancia es la tuya. Una imagen de VPS casi siempre viene en UTC, y n8n hereda eso. Define la zona horaria a propósito y luego compruébalo: programa una corrida única para dentro de dos minutos y mira el reloj. Verificar una vez sale mucho mejor que descubrir que llevas seis meses de reportes cayendo a las 2am.

03 · Haz el trabajo idempotente

Aquí va la regla que separa un schedule de juguete de uno de producción: un job programado tarde o temprano va a correr dos veces, así que tiene que ser seguro correrlo dos veces. Un reintento dispara después de un error transitorio. Un deploy reinicia el contenedor a mitad de corrida. Alguien le da "ejecutar" para probarlo justo cuando el timer también dispara. Si correr dos veces manda un correo duplicado o inserta un registro duplicado, no tienes un bug que no puedes reproducir. Tienes un bug que vas a reproducir, en horario fijo.

Idempotencia significa que una segunda corrida idéntica no produce ningún efecto adicional. Unas cuantas formas concretas de lograrlo:

  • Haz upsert, no insert. Indexa tus escrituras por un identificador estable (un id de orden, una fecha, un id externo) y usa semántica de insertar-o-actualizar para que una repetición sobrescriba en vez de duplicar.
  • Verifica y luego actúa, usando una marca. Antes de ejecutar el efecto secundario, revisa si esta unidad de trabajo ya se hizo (un booleano "procesado", un timestamp "last_synced_at") y sáltala si ya está.
  • Haz idempotente la llamada externa. Muchas APIs aceptan una idempotency key en el request. Pásale una determinística, por ejemplo derivada de la fecha, para que el proveedor elimine los duplicados por ti.

En concreto, un job nocturno de "manda el resumen de ayer" debería registrar que ya envió para una fecha dada y negarse a enviar de nuevo para esa misma fecha. Así una re-corrida manual o un reintento es un no-op, no un segundo correo a cada usuario.

Consejo

Prueba la idempotencia a propósito: corre el flujo a mano dos veces seguidas y confirma que la segunda corrida no cambia nada más adelante en la cadena. Si la segunda corrida hace algo, encontraste el bug ahora, en desarrollo, en vez de a las 3am en producción.

04 · Atrapa las fallas con un Error Trigger

Un job programado no tiene a nadie mirándolo. Ese es justo el punto, y el peligro. Un flujo que lleva una semana fallando en silencio cada noche es peor que uno que nunca corrió, porque creías que estaba funcionando. Así que antes de confiar en un schedule, dale una forma de avisarte cuando algo sale mal.

El mecanismo de n8n es un flujo aparte con Error Trigger. Arma un flujo cuyo primer nodo sea un Error Trigger, y luego apunta tus flujos programados hacia él:

  1. Crea un flujo nuevo. Agrega un nodo Error Trigger como inicio.
  2. Después de él, agrega un nodo de notificación: correo, un mensaje de Slack, o un HTTP Request a un webhook que tengas vigilado. Incluye el nombre del flujo y el error en el mensaje para que la alerta sirva para actuar, no que solo diga "algo se rompió".
  3. En los Settings de cada flujo programado, define el Error Workflow apuntando a este. Ahora cualquier falla no manejada se enruta aquí automáticamente.

Eso cubre los crashes. Para el éxito parcial, donde el job corrió pero procesó cero filas cuando debió procesar cientos, agrega una verificación explícita dentro del flujo (un nodo IF sobre el conteo) que lance un error cuando el resultado se vea raro. Un job que "tiene éxito" sin hacer nada es una falla que por sí sola nunca va a disparar el camino de error.

05 · Protégete del solape y de la caída

Hay dos modos de falla específicos de los schedules, y los dos pegan sin hacer ruido.

Solape. Si una corrida tarda más que el intervalo, el siguiente tick puede arrancar mientras el anterior todavía va. Dos copias peleándose por los mismos datos es receta para duplicados y estado corrupto. La solución es un lock: al inicio del flujo, pon un flag (una fila en tu base de datos, una key en Redis) que diga "corriendo", y al final, límpialo. A la entrada, si el flag ya está puesto, sal de inmediato.

en cada tic:
  si el lock "nightly-sync" está puesto -> log "saltado, todavía corriendo" y para
  pon el lock "nightly-sync"
  ...haz el trabajo...
  limpia el lock "nightly-sync"   # límpialo también en el camino de error

El detalle que se te escapa: limpia el lock también en el camino de error, o un solo crash deja el lock trabado en "puesto" para siempre y toda corrida futura se salta a sí misma. Un lock con timeout (que expire solo tras, digamos, 2× el tiempo esperado de corrida) es todavía más seguro.

Caída. El Schedule Trigger solo dispara mientras el flujo está activo y el proceso de n8n está corriendo. No hay cola de recuperación. Si el contenedor está caído por un deploy o un reinicio justo cuando tocaba un tick, ese tick simplemente se perdió. n8n no lo va a correr tarde cuando vuelva. Para un job cada hora, ni te preocupas. Para un job una vez al día, un deploy en el minuto equivocado significa un día entero perdido en silencio.

Importante

Si perder una corrida es inaceptable, no te fíes solo del tick. Haz que el job se ponga al día por diseño: en vez de "procesa la última hora", que haga "procesa todo desde la última corrida exitosa", leyendo un watermark guardado. Así un tick perdido se cura solo en la siguiente corrida, porque la siguiente corrida cubre el hueco.

Juntando todo: un Schedule Trigger con zona horaria explícita, trabajo que es seguro repetir, un Error Trigger que te avisa, y un lock más un watermark para que ni el solape ni la caída corrompan tus datos en silencio. Nada de esto es del otro mundo. Es la diferencia entre un cron que tienes que estar vigilando y uno que de verdad puedes olvidar, en el buen sentido. Arma las salvaguardas una vez, y el próximo job programado es solo el trigger más el trabajo.

Puntos clave

  • Prefiere el Schedule Trigger sobre el cron del sistema cuando el trabajo ya vive en n8n, así heredas reintentos, historial y alertas.
  • Define la zona horaria a propósito y compruébala una vez; el default en UTC es el bug de scheduling silencioso más común.
  • La idempotencia no es negociable: asume que cada corrida puede pasar dos veces y haz que la segunda sea un no-op.
  • Conecta un Error Trigger para que las fallas te avisen, y agrega una verificación de conteo para que 'tuvo éxito pero no hizo nada' también dispare la alerta.
  • Protege el solape con un lock que expira solo, y usa un watermark para que un tick perdido (por una caída) se cure solo.

Preguntas frecuentes

¿Por qué usar el Schedule Trigger de n8n en vez del cron del sistema?

Porque n8n ya te da reintentos, historial completo de corridas, almacenamiento de secretos y un lugar donde conectar las alertas, todo lo que, de otro modo, tendrías que rehacer a mano alrededor de una línea de crontab. El cron del sistema está bien para jobs que no tienen nada que ver con n8n, pero si el trabajo vive en n8n de todas formas, programarlo ahí significa un solo sistema que operar y un solo lugar donde hacer debug.

Mi flujo corre a la hora equivocada. ¿Qué se me pasó?

Casi con seguridad es la zona horaria. n8n evalúa los schedules en UTC por defecto, y una imagen típica de VPS también viene en UTC, así que un job puesto para las 07:00 dispara a las 07:00 UTC en vez de a las 07:00 de tu hora local. Define la zona horaria a mano en los Settings del flujo (o con GENERIC_TIMEZONE en el contenedor), y luego compruébalo con una corrida única programada para dentro de dos minutos.

¿Qué pasa si una corrida sigue cuando llega el siguiente tick?

Por defecto puede arrancar una segunda copia y pelearse con la primera por los mismos datos, produciendo duplicados o estado corrupto. Protégete con un lock: pon un flag de 'corriendo' al inicio, límpialo al final (incluido en el camino de error) y sal de inmediato a la entrada si ya está puesto. Un lock con timeout que expira solo es todavía más seguro, para que un crash no lo deje trabado en 'puesto'.

¿n8n recupera las corridas que se perdió mientras el contenedor estuvo caído?

No. El Schedule Trigger solo dispara mientras el flujo está activo y el proceso está arriba; no hay cola de recuperación, así que cualquier tick que tocaba durante la caída simplemente se perdió. Si perder una corrida es inaceptable, diseña el job para que procese todo desde la última corrida exitosa usando un watermark guardado, así la siguiente corrida se cura sola cubriendo el hueco.

¿Cómo sé si un job programado lleva tiempo fallando en silencio?

Haces que sea imposible que falle en silencio. Arma un flujo con Error Trigger que te notifique (correo, Slack, un webhook que tengas vigilado) y ponlo como Error Workflow en cada flujo programado, para que las fallas no manejadas siempre le avisen a alguien. Para el éxito parcial, cuando corrió pero no procesó nada, agrega una verificación explícita dentro del flujo que lance un error cuando el resultado se vea raro.

¿Un polling muy seguido es buena forma de reaccionar a eventos nuevos?

Casi nunca. Hacer polling cada minuto para simular un flujo por eventos quema corridas y agrega latencia entre el evento y tu reacción. Si el trabajo debe pasar en respuesta a algo (una fila nueva, un request entrante, un archivo que llega) usa el trigger que corresponda (un Webhook o un trigger de base de datos), que sale más barato y más rápido. Reserva el schedule para trabajo que de verdad corre a una hora fija.

¿Prefieres que lo hagamos por ti?

Esto mismo lo construimos para negocios como el tuyo. La primera conversación es gratis y sin compromiso.

Escríbenos por WhatsApp

Escríbenos por WhatsApp

Escanéalo con tu teléfono para escribirnos por WhatsApp.

Escanéalo con tu teléfono para escribirnos por WhatsApp.

¿Estás desde el teléfono y no puedes escanear? Escríbenos a info@ilustrari.com

Primera conversación gratis. Te responde el fundador.

Recursos relacionados