Configuración
Define schedules cron directamente en el config de tu función. No necesitas configuración externa.
import { define, z } from "@jelou/functions";
export default define({
name: "recordatorio-citas",
description: "Envía recordatorios de citas por WhatsApp",
input: z.object({}),
config: {
cron: [
{ expression: "0 8 * * *", timezone: "America/Guayaquil" },
{ expression: "0 8 * * *", timezone: "America/Bogota" },
],
},
handler: async (_input, ctx) => {
if (!ctx.isCron) return { skipped: true };
ctx.log("Enviando recordatorios", { cron: ctx.trigger.cron });
const apiKey = ctx.env.get("JELOU_API_KEY");
const res = await fetch("https://api.jelou.ai/v1/messages/send", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${apiKey}`,
},
body: JSON.stringify({
botId: ctx.bot.id,
phone: "593987654321",
message: "Hola, te recordamos que tienes una cita mañana a las 10:00 AM.",
}),
});
return { enviados: 1, status: res.status };
},
});
Prueba local
Inicia el servidor con jelou dev. Puedes enviar una petición HTTP a la función, pero el guard isCron la rechazará porque no es un trigger cron real:
curl -X POST http://localhost:3000 \
-H "Content-Type: application/json" \
-d '{}'
No puedes simular un trigger cron real desde curl — la plataforma inyecta ctx.isCron y la firma criptográfica automáticamente. Para probar la lógica del cron, usa createMockContext({ isCron: true }) en tus tests.
Sintaxis
Cada schedule tiene dos campos:
| Campo | Tipo | Requerido | Descripción |
|---|
expression | string | Sí | Cron estándar de 5 campos |
timezone | string | No | Zona horaria IANA. Default: UTC. |
┌───────────── minuto (0-59)
│ ┌───────────── hora (0-23)
│ │ ┌───────────── día del mes (1-31)
│ │ │ ┌───────────── mes (1-12)
│ │ │ │ ┌───────────── día de la semana (0-7, 0 y 7 = domingo)
│ │ │ │ │
* * * * *
Ejemplos comunes
| Expresión | Descripción |
|---|
0 9 * * * | Todos los días a las 9:00 AM |
0 9 * * 1-5 | Lunes a viernes a las 9:00 AM |
*/30 * * * * | Cada 30 minutos |
0 0 1 * * | Primer día de cada mes a medianoche |
0 */2 * * * | Cada 2 horas |
30 14 * * 3 | Miércoles a las 2:30 PM |
Zonas horarias
Usa cualquier zona IANA válida:
config: {
cron: [
{ expression: "0 9 * * *", timezone: "America/Guayaquil" }, // Ecuador
{ expression: "0 9 * * *", timezone: "America/Bogota" }, // Colombia
{ expression: "0 9 * * *", timezone: "America/Mexico_City" }, // México
{ expression: "0 9 * * *", timezone: "America/Argentina/Buenos_Aires" },
{ expression: "0 3 * * *" }, // UTC por defecto
],
}
Guard isCron
Tu función puede recibir tanto peticiones HTTP como disparos cron. Usa ctx.isCron para distinguirlos:
handler: async (_input, ctx) => {
if (!ctx.isCron) {
return { skipped: true, reason: "no es un trigger cron" };
}
ctx.log("Tarea cron ejecutándose", { cron: ctx.trigger.cron });
return { ejecutado: true };
}
Sin el guard isCron, cualquier petición HTTP a tu función ejecutará la lógica del cron. Siempre incluye esta verificación.
Cómo funciona
- Defines los schedules en
config.cron
- Al ejecutar
jelou deploy, la plataforma lee tu configuración y crea los schedules
- Cuando un schedule se dispara, tu función recibe una petición con
ctx.isCron === true y ctx.trigger.cron con la expresión que lo activó
- La verificación de firma criptográfica previene invocaciones no autorizadas
Límites
- Máximo 10 schedules cron por función
- Exceder este límite lanza un error en tiempo de definición
Gestión
Los schedules son declarativos — se definen en el código y se sincronizan en cada despliegue. Para modificar un schedule, cambia config.cron en tu código y vuelve a desplegar.
Para ver los schedules activos:
jelou cron list consultar-cliente
# ▸ Expression Timezone Enabled Last Triggered
# ▸ 0 8 * * * America/Guayaquil yes 2 hours ago
# ▸ 0 8 * * * America/Bogota yes 2 hours ago
Cuando usas app(), cada tool puede tener sus propios schedules cron independientes. Las peticiones cron se envían a la ruta específica de cada tool.
import { app, define, z } from "@jelou/functions";
export default app({
tools: {
limpiezaDiaria: define({
description: "Limpia registros obsoletos",
input: z.object({}),
config: { cron: [{ expression: "0 3 * * *", timezone: "UTC" }] },
handler: async (_input, ctx) => {
if (!ctx.isCron) return { skipped: true };
return { cleaned: true };
},
}),
sincronizacionHoraria: define({
description: "Sincroniza datos externos",
input: z.object({}),
config: { cron: [{ expression: "0 * * * *" }] },
handler: async (_input, ctx) => {
if (!ctx.isCron) return { skipped: true };
return { synced: true };
},
}),
},
});
El límite de 10 schedules cron es agregado entre todos los tools de un app(). Si un tool usa 6 schedules, los demás tools solo pueden usar 4 en total.
Problemas comunes
Los schedules cron se sincronizan al hacer deploy. Si cambiaste la expresión cron, necesitas redesplegar:Verifica que el schedule esté activo:jelou cron list mi-funcion
# ▸ Expression Timezone Enabled Last Triggered
# ▸ 0 8 * * * America/Guayaquil yes 2 hours ago
Si la columna Enabled muestra no, revisa que la expresión cron sea válida. Sin el guard isCron, cualquier petición HTTP ejecutará la lógica del cron. Agrega la verificación al inicio del handler:handler: async (_input, ctx) => {
if (!ctx.isCron) return { skipped: true };
// tu lógica cron aquí
return { ejecutado: true };
}
Esto retorna { skipped: true } para peticiones HTTP normales y solo ejecuta la lógica cuando es un trigger cron real.