ctx.jelou es un cliente WhatsApp auto-configurado a partir de las credenciales del company. No requiere configuración en tu código.
Disponibilidad
if ( ! ctx . jelou . available ) {
ctx . log ( "Jelou API no configurada — faltan credenciales del company" );
return { error: "messaging_unavailable" };
}
ctx.jelou.available es false cuando el company no tiene clientId/clientSecret. Llamar a send() o sendTemplate() en un cliente no disponible lanza un Error.
send(options)
Envía un mensaje. Retorna Promise<MessageResponse>.
interface MessageResponse {
messageId : string ;
}
Tipos de mensaje
Todos los tipos requieren to (destinatario) y type. El campo botId es opcional (usa el bot por defecto del company).
Tipo Campos requeridos Campos opcionales texttextimagemediaUrlcaptionvideomediaUrlcaptionaudiomediaUrlfilemediaUrlfilename, captionstickermediaUrllocationlat, lngname, addresscontactscontacts[]quick_replytext, replies[]buttonstext, buttons[]
Ejemplos
Texto
Imagen
Ubicación
Quick replies
Botones
await ctx . jelou . send ({
type: "text" ,
to: "+593987654321" ,
text: "Tu pedido #ORD-123 fue confirmado" ,
});
await ctx . jelou . send ({
type: "image" ,
to: "+593987654321" ,
mediaUrl: "https://cdn.example.com/recibo.png" ,
caption: "Tu recibo de compra" ,
});
await ctx . jelou . send ({
type: "location" ,
to: "+593987654321" ,
lat: - 2.1894 ,
lng: - 79.8891 ,
name: "Oficina Guayaquil" ,
address: "Av. 9 de Octubre 123" ,
});
await ctx . jelou . send ({
type: "quick_reply" ,
to: "+593987654321" ,
text: "¿Cómo calificarías nuestro servicio?" ,
replies: [
{ title: "Excelente" , payload: "rating_5" },
{ title: "Bueno" , payload: "rating_3" },
{ title: "Malo" , payload: "rating_1" },
],
});
await ctx . jelou . send ({
type: "buttons" ,
to: "+593987654321" ,
text: "¿Qué deseas hacer?" ,
buttons: [
{ id: "rastrear_pedido" , title: "Rastrear pedido" },
{ id: "contactar_soporte" , title: "Contactar soporte" },
],
});
Otros tipos
Video
Audio
Archivo
Sticker
Contactos
await ctx . jelou . send ({
type: "video" ,
to: "+593987654321" ,
mediaUrl: "https://cdn.example.com/tutorial.mp4" ,
caption: "Tutorial de activación" ,
});
sendTemplate(options)
Envía una plantilla HSM de WhatsApp. Retorna Promise<TemplateResponse[]>.
interface TemplateResponse {
id : string ;
destination : string ;
}
Opciones
Campo Tipo Requerido Descripción templatestringSí Nombre del elemento de plantilla tostring | string[]Sí Destinatario(s) languagestringNo Código de idioma de la plantilla paramsstring[]No Valores de parámetros del body mediaUrlstringNo URL del media del header filenamestringNo Nombre del archivo para headers de documento headerParamsRecord<string, unknown>[]No Parámetros del header buttonPayloadsstring[]No Valores de payload de botones botIdstringNo Sobrescribe el bot por defecto
Ejemplos
Plantilla simple
Con media y múltiples destinatarios
Con botones
const results = await ctx . jelou . sendTemplate ({
template: "confirmacion_pedido" ,
to: "+593987654321" ,
params: [ "ORD-123" , "$99.99" ],
});
JelouApiError
Se lanza cuando la API de Jelou retorna una respuesta no-2xx.
import { JelouApiError } from "@jelou/functions" ;
try {
await ctx . jelou . send ({ type: "text" , to: "+593987654321" , text: "Hola" });
} catch ( err ) {
if ( err instanceof JelouApiError ) {
ctx . log ( "Jelou API falló" , { status: err . status , code: err . code });
if ( err . isRateLimit ()) {
await new Promise (( r ) => setTimeout ( r , 2000 ));
}
if ( err . isAuth ()) {
ctx . log ( "Problema de credenciales del company" );
}
if ( err . isValidation ()) {
ctx . log ( "Petición inválida" , { code: err . code });
}
}
}
Propiedad / Método Tipo Descripción statusnumberCódigo HTTP (ej: 429, 401, 400) codestringCódigo de error (ej: "HTTP_429") messagestringMensaje descriptivo del error isRateLimit()booleantrue cuando status === 429isAuth()booleantrue cuando status === 401 o 403isValidation()booleantrue cuando status === 400 o 422
Las plantillas requieren aprobación previa de Meta. Enviar una plantilla no aprobada retorna error de validación.
Las URLs de media deben ser públicamente accesibles. URLs que requieren autenticación fallarán silenciosamente.
Siempre verifica ctx.jelou.available antes de enviar mensajes.
Testing
import { assertEquals } from "jsr:@std/assert" ;
import { createMockContext , createMockJelouClient } from "@jelou/functions/testing" ;
const mockJelou = createMockJelouClient ();
const ctx = createMockContext ({ jelou: mockJelou });
Deno . test ( "envía mensaje de texto" , async () => {
await ctx . jelou . send ({
type: "text" ,
to: "+593987654321" ,
text: "Pedido confirmado" ,
});
assertEquals ( mockJelou . calls . length , 1 );
assertEquals ( mockJelou . calls [ 0 ]. method , "send" );
assertEquals ( mockJelou . calls [ 0 ]. args . type , "text" );
});
Deno . test ( "usa resultados custom" , async () => {
const custom = createMockJelouClient ({
sendResult: { messageId: "msg-custom-123" },
});
const ctx2 = createMockContext ({ jelou: custom });
const result = await ctx2 . jelou . send ({
type: "text" ,
to: "+593987654321" ,
text: "Test" ,
});
assertEquals ( result . messageId , "msg-custom-123" );
});
Deno . test ( "reset entre tests" , () => {
mockJelou . reset ();
assertEquals ( mockJelou . calls . length , 0 );
});
createMockJelouClient() Referencia completa del mock de mensajería.