Validación de entrada
Cuando defines un esquema input, cada petición se valida antes de ejecutar el handler. Si la validación falla, el handler no se ejecuta y se retorna un 400:
{
"error": "Validation failed",
"details": [
{
"path": ["email"],
"message": "Invalid email",
"code": "invalid_string"
}
]
}
Ejemplo con esquema completo
import { define, z } from "@jelou/functions";
export default define({
name: "crear-ticket",
description: "Crea un ticket de soporte desde WhatsApp",
input: z.object({
nombre: z.string().min(1).describe("Nombre del cliente"),
email: z.string().email().describe("Email de contacto"),
asunto: z.string().max(200).describe("Asunto del ticket"),
prioridad: z.enum(["baja", "media", "alta"]).default("media"),
}),
output: z.object({
ticketId: z.string(),
estado: z.string(),
}),
handler: async (input, ctx) => {
ctx.log("Creando ticket", { cliente: input.nombre, prioridad: input.prioridad });
return { ticketId: "TKT-2024-0042", estado: "abierto" };
},
});
Tipos soportados
Puedes usar cualquier tipo de Zod dentro de z.object():
input: z.object({
nombre: z.string().min(1),
edad: z.number().int().positive(),
email: z.string().email(),
activo: z.boolean().default(true),
rol: z.enum(["admin", "usuario", "invitado"]),
tags: z.array(z.string()).optional(),
metadata: z.record(z.string(), z.unknown()).optional(),
})
Coerción en peticiones GET
Para peticiones GET, los query parameters son strings. Usa z.coerce para convertir tipos automáticamente:
import { define, z } from "@jelou/functions";
export default define({
name: "buscar-pedidos",
description: "Busca pedidos por estado",
input: z.object({
q: z.string(),
limit: z.coerce.number().default(10),
pagina: z.coerce.number().default(1),
activo: z.coerce.boolean().default(true),
}),
handler: async (input, ctx) => {
ctx.log("Buscando", { q: input.q, limit: input.limit });
return { resultados: [], total: 0 };
},
});
curl "https://buscar-pedidos.fn.jelou.ai/?q=pendiente&limit=5&pagina=2"
Anotaciones .describe()
Usa .describe() en cada campo para documentar los parámetros. Estas descripciones aparecen automáticamente en el esquema MCP, lo que ayuda a los agentes IA a entender cómo usar tu función:
input: z.object({
telefono: z.string().min(10).describe("Número de teléfono con código de país, ej: 593987654321"),
incluirHistorial: z.boolean().default(false).describe("Si incluir el historial de conversaciones"),
})
Validación de salida
Cuando defines un esquema output, el valor retornado por el handler se valida después de la ejecución. Si no coincide:
- Se registra una advertencia en los logs
- La respuesta se envía normalmente con status
200
La validación de output nunca bloquea la respuesta. Es una herramienta de desarrollo para detectar inconsistencias.
output: z.object({
nombre: z.string(),
saldo: z.number(),
})
Si el handler retorna { nombre: "María", saldo: "150" } (saldo como string), verás una advertencia en los logs pero el cliente recibe la respuesta sin cambios.
Cada error en el array details contiene:
| Campo | Tipo | Descripción |
|---|
path | string[] | Ruta al campo con error, ej: ["email"] o ["direccion", "ciudad"] |
message | string | Mensaje legible del error |
code | string | Código de error de Zod (ej: invalid_string, too_small, invalid_enum_value) |
{
"error": "Validation failed",
"details": [
{
"path": ["nombre"],
"message": "String must contain at least 1 character(s)",
"code": "too_small"
},
{
"path": ["prioridad"],
"message": "Invalid enum value. Expected 'baja' | 'media' | 'alta', received 'urgente'",
"code": "invalid_enum_value"
}
]
}