Skip to main content

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

index.ts
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:
index.ts
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.

Formato de errores de validación

Cada error en el array details contiene:
CampoTipoDescripción
pathstring[]Ruta al campo con error, ej: ["email"] o ["direccion", "ciudad"]
messagestringMensaje legible del error
codestringCó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"
    }
  ]
}