12 KiB
name, description, allowed-tools
| name | description | allowed-tools |
|---|---|---|
| Consulta y envía mensajes de WhatsApp del beneficio Rio Frio a través del servicio whatsapp-bridge desplegado en nucleo001 (https://whatsapp.nucleoriofrio.com). Úsala cuando el usuario pida buscar, resumir, listar, extraer o enviar información a chats o grupos de WhatsApp de ese número. Cubre casos como "qué se habló en el grupo X", "alguien mencionó retenciones esta semana", "cuál fue el último precio discutido", "mandale un mensaje a Y". | Bash, Read, Grep |
WhatsApp Bridge — skill de consulta on-demand
Qué es este servicio
whatsapp-bridge es un servicio de Nucleo que mantiene una sesión de WhatsApp
vía Baileys contra el número +504 9742-9865 del beneficio Rio Frio. Guarda
todos los mensajes de los chats (individuales y grupos) a los que ese número
pertenece en Postgres, con retención infinita.
La API expone consultas read + write. El endpoint /send está habilitado.
- Base URL:
https://whatsapp.nucleoriofrio.com - Auth:
Authorization: Bearer $WA_BRIDGE_TOKEN - El token vive en:
~/.claude/skills/whatsapp/.env(local, no versionado)
Nota sobre el endpoint MCP: https://whatsapp.nucleoriofrio.com/mcp existe
para que Claude.ai web/móvil consuma el mismo servicio como Custom Integration.
No lo uses tú desde Claude Code — tu path principal es la API REST vía
scripts/query.sh. El MCP es solo para los otros clientes de Claude.
Cómo invocarla correctamente
Antes de cualquier query:
- Carga el token:
source ~/.claude/skills/whatsapp/.env(o lee$WA_BRIDGE_TOKENdel entorno). - Si no hay token, avisa al usuario: pedir que cree
~/.claude/skills/whatsapp/.envconWA_BRIDGE_TOKEN=<valor>. - Verifica estado:
GET /api/auth/status. Sistatus !== "open", no hagas queries — avísale al usuario que el bridge está caído o necesita re-pairing.
El helper scripts/query.sh ya maneja el bearer:
~/.claude/skills/whatsapp/scripts/query.sh /api/auth/status
Endpoints disponibles
Todos bajo https://whatsapp.nucleoriofrio.com con Authorization: Bearer $TOKEN.
Diagnóstico
GET /healthz— liveness, sin auth. Úsalo para verificar que el servicio responde.GET /api/auth/status→{ status, hasSession, user, pairingCode, lastError }. Valores destatus:idle | connecting | open | closed | needs-pair.
Listar chats
GET /api/chats?type=<group|individual|all>&q=<texto>&limit=100&offset=0type: filtrar por tipo. Defaultall.q: búsqueda parcial por nombre del chat o JID.- Responde:
{ count, chats: [{ jid, isGroup, name, lastMessageAt, participants }] }.
- Patrón típico: cuando el usuario menciona un grupo por nombre (ej "el grupo de productores"), primero
GET /api/chats?type=group&q=productorespara resolver el JID, después consulta mensajes con ese JID.
Un grupo específico
GET /api/groups/:jid— metadata (live + stored). El:jiddebe ser URL-encoded (termina en@g.us).GET /api/groups/:jid/messages— mensajes del grupo. Query params:since=2026-04-14T00:00:00Z(ISO timestamp)until=2026-04-21T23:59:59Zq=<texto>— búsqueda parcial ILIKE en el contenidosender=<jid>— filtrar por quién escribió (participant)order=asc|desc(defaultdesc)limit=100(máx 1000),offset=0- Responde:
{ jid, count, filters, messages: [{ msgId, fromMe, senderJid, pushName, messageType, content, contextInfo, timestamp }] }.
Chats individuales
GET /api/messages/individual/:jid?since=&until=&q=&order=&limit=&offset=:jidtermina en@s.whatsapp.net.- Responde mensajes de ese chat.
Búsqueda cross-chat
GET /api/messages/search?q=<texto>&since=&until=&chatType=<group|individual|all>&limit=100- Busca en TODO el historial. Úsalo cuando el usuario pide algo como "alguien mencionó retenciones" o "busca el precio por quintal de la semana pasada".
- Responde:
{ query, count, matches: [{ chatJid, msgId, senderJid, pushName, isGroup, content, timestamp }] }. - Para pasar de matches a contexto, obtén el
chatJidde un match y pideGET /api/groups/:jid/messagesconsinceunos minutos antes.
Endpoints de escritura
-
POST /api/messages/send— texto. Body:{ jid, text, quotedChatJid?, quotedMsgId?, mentions? }.quotedMsgIdopcional para responder a un mensaje existente (pass quotedChatJid si es de otro chat).mentionsarray de JIDs para @-menciones. -
POST /api/messages/forward— reenviar un mensaje del historial a otro chat. Body:{ sourceChatJid, sourceMsgId, destJid }. El mensaje debe existir en nuestro storage (lo vio el socket en vivo). -
POST /api/messages/react— reaccionar con emoji. Body:{ chatJid, msgId, emoji }. Emoji vacío""= quitar reacción previa. -
POST /api/messages/send-media— multimedia (image/video/audio/document). Body:{ jid, url, type, caption?, fileName?, mimetype?, ptt?, quotedChatJid?, quotedMsgId? }. La URL debe ser HTTP(S) público — IPs privadas bloqueadas por SSRF guard.typees obligatorio. -
POST /api/messages/send-location— ubicación geográfica. Body:{ jid, latitude, longitude, name?, address?, quotedChatJid?, quotedMsgId? }. -
POST /api/messages/send-poll— encuesta. Body:{ jid, name, values[2..12], selectableCount?, quotedChatJid?, quotedMsgId? }.selectableCount=1(default) = single-choice;=0= multi-choice;=N= hasta N opciones. -
POST /api/messages/send-contact— tarjeta(s) de contacto vCard. Hasta 20. Body:{ jid, contacts: [{ displayName, firstName?, lastName?, phone?, phoneDisplay?, email?, organization?, title?, vcard? }], quotedChatJid?, quotedMsgId? }. Siphoneestá en E.164 sin+, WhatsApp lo detecta como contacto vinculable (tap → chatear).
Eventos: NO soportado todavía. Baileys 6.17 no tiene API estable para crear event messages. Si el user lo pide, avisale del estado actual.
Descarga de archivos (image/video/audio/document/sticker)
Los mensajes en respuestas de /groups/:jid/messages, /individual/:jid, /search incluyen un campo media cuando contienen archivo:
"media": {
"type": "image",
"mimetype": "image/jpeg",
"size": 123456,
"caption": "...",
"downloadUrl": "/api/messages/media/<encoded-chatJid>/<msgId>"
}
Para obtener el archivo desencriptado:
~/.claude/skills/whatsapp/scripts/query.sh \
"/api/messages/media/<encoded-chatJid>/<msgId>" \
-o /tmp/wa-image.jpg
Eso guarda el binario. query.sh pasa el bearer automáticamente; la ruta que copies del campo downloadUrl ya funciona.
Para solo metadata sin descargar: GET /api/messages/media-info/:chatJid/:msgId.
Todos retornan { ok: true, msgId: "..." } al éxito, 4xx/5xx con { error: "..." } si falla.
Si retornan 403 significa que ENABLE_SEND=false — hay que cambiarlo en stack.yml y redeployar.
Pairing (setup / recovery)
POST /api/auth/pair→ devuelve{ code }(válido ~60s).- El usuario toma su teléfono con el número, abre WhatsApp > Ajustes > Dispositivos vinculados > Vincular con número, y teclea el código.
POST /api/auth/logoutcon headerX-Confirm-Wipe: yes→ borra la sesión. DESTRUCTIVO: después hay que re-pairear. Nunca llamar esto salvo que el usuario lo pida explícitamente.
Reglas de comportamiento
Antes de ejecutar queries
- Si el status no es
open, avísale al usuario y no consumas la API en vano. - Si el usuario menciona un grupo por nombre, resuelve el JID primero con
/api/chats?type=group&q=…y pídele confirmar "¿te refieres a este grupo: ?".
Cuándo usar qué endpoint
- Resumir un grupo en una ventana de tiempo →
/api/groups/:jid/messages?since=&until=conorder=ascpara leer en orden cronológico. - Buscar menciones (palabra clave) →
/api/messages/search?q=primero; si hay resultados, fetch contexto con/api/groups/:jid/messages?since=&until=alrededor del match. - Ver actividad reciente →
/api/chats?type=group&limit=20ordena porlastMessageAtdesc. - Perfilar un contacto (cuánto escribió, en qué chats) →
/api/messages/search?q=&sender=…(no implementado aún — si es necesario, usa el endpoint de grupo consender=repetidamente).
Cómo mostrar resultados al usuario
- Los JIDs (
549…@g.us) son ruido visual. MuestrapushNameonamedel chat, no el JID, salvo que el usuario lo pida explícitamente. - Agrupa por chat cuando haya resultados de múltiples chats.
- Si la lista es larga (>30 mensajes), resume en lugar de volcar todo.
- Incluye el
timestampen formato humano (ej2026-04-21 15:30) ypushNamedel autor. - Mensajes con
fromMe: trueson del número del bridge (no los confundas con respuestas de otros).
Errores típicos y cómo reaccionar
- 401 unauthorized → el token está mal. Revisa
WA_BRIDGE_TOKENen~/.claude/skills/whatsapp/.env. - 503 socket not ready → el bridge está caído o desconectado.
GET /api/auth/statuspara diagnosticar. Sistatus=needs-pair, el usuario tiene que re-pairear. - 404 en un JID → el chat no existe en nuestro storage. El número puede no estar en ese grupo, o el JID está mal escrito.
Ejemplos de sesiones
Caso: "Resume lo que se habló hoy en el grupo de operaciones"
# 1. Resolver el JID
~/.claude/skills/whatsapp/scripts/query.sh '/api/chats?type=group&q=operaciones'
# → { chats: [{ jid: "123@g.us", name: "Operaciones Rio Frio", ... }] }
# 2. Pedir mensajes de hoy
TODAY=$(date -u +%Y-%m-%dT00:00:00Z)
~/.claude/skills/whatsapp/scripts/query.sh "/api/groups/123%40g.us/messages?since=$TODAY&order=asc&limit=500"
# → resumir manualmente en la respuesta al usuario
Caso: "¿Alguien mencionó el precio por quintal esta semana?"
WEEK_AGO=$(date -u -d '7 days ago' +%Y-%m-%dT00:00:00Z)
~/.claude/skills/whatsapp/scripts/query.sh "/api/messages/search?q=quintal&since=$WEEK_AGO&chatType=group"
# → presentar los matches agrupados por chat con timestamp y autor
Caso: "Envía un mensaje al grupo X diciendo Y"
POST:
~/.claude/skills/whatsapp/scripts/query.sh -X POST \
-H 'Content-Type: application/json' \
-d '{"jid":"123@g.us","text":"..."}' /api/messages/send
Si responde 403, avisale al user que ENABLE_SEND=false en stack.yml.
Caso: "Responde a ese mensaje con X"
Pasá quotedMsgId (y opcional quotedChatJid si es de otro chat):
~/.claude/skills/whatsapp/scripts/query.sh -X POST \
-H 'Content-Type: application/json' \
-d '{"jid":"123@g.us","text":"claro, ya vamos","quotedMsgId":"ABCD..."}' \
/api/messages/send
Caso: "Reenvía ese mensaje al grupo de productores"
- Obtené el JID del grupo destino (vía
/api/chats). - POST a
/api/messages/forwardcon{ sourceChatJid, sourceMsgId, destJid }.
Caso: "Reacciona con 👍 a ese mensaje"
~/.claude/skills/whatsapp/scripts/query.sh -X POST \
-H 'Content-Type: application/json' \
-d '{"chatJid":"123@g.us","msgId":"ABCD","emoji":"👍"}' \
/api/messages/react
Caso: "Mandá esa imagen con el caption X"
El usuario te pasa una URL pública (imgur, Gitea attachment, etc.):
~/.claude/skills/whatsapp/scripts/query.sh -X POST \
-H 'Content-Type: application/json' \
-d '{"jid":"123@g.us","url":"https://i.imgur.com/X.jpg","type":"image","caption":"..."}' \
/api/messages/send-media
Si no tiene URL pública y el archivo está local, primero hay que subirlo a algún lado accesible. No tenemos (aún) endpoint de upload directo multipart.
Archivos de referencia en esta skill
endpoints.md— tabla de referencia rápida de todos los endpoints y sus parámetros.contacts.md— LEELO siempre que vayas a mostrar o enviar a un JID; contiene el mapping de identificadores a personas conocidas.scripts/query.sh— helper de curl autenticado..env(no versionado) — contieneWA_BRIDGE_TOKEN=<valor>.