Files
skill-whatsapp/SKILL.md

12 KiB

name, description, allowed-tools
name description allowed-tools
whatsapp 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:

  1. Carga el token: source ~/.claude/skills/whatsapp/.env (o lee $WA_BRIDGE_TOKEN del entorno).
  2. Si no hay token, avisa al usuario: pedir que cree ~/.claude/skills/whatsapp/.env con WA_BRIDGE_TOKEN=<valor>.
  3. Verifica estado: GET /api/auth/status. Si status !== "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 de status: idle | connecting | open | closed | needs-pair.

Listar chats

  • GET /api/chats?type=<group|individual|all>&q=<texto>&limit=100&offset=0
    • type: filtrar por tipo. Default all.
    • 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=productores para resolver el JID, después consulta mensajes con ese JID.

Un grupo específico

  • GET /api/groups/:jid — metadata (live + stored). El :jid debe 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:59Z
    • q=<texto> — búsqueda parcial ILIKE en el contenido
    • sender=<jid> — filtrar por quién escribió (participant)
    • order=asc|desc (default desc)
    • 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=
    • :jid termina 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 chatJid de un match y pide GET /api/groups/:jid/messages con since unos minutos antes.

Endpoints de escritura

  • POST /api/messages/send — texto. Body: { jid, text, quotedChatJid?, quotedMsgId?, mentions? }. quotedMsgId opcional para responder a un mensaje existente (pass quotedChatJid si es de otro chat). mentions array 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. type es 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? }. Si phone está 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/logout con header X-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= con order=asc para 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=20 ordena por lastMessageAt desc.
  • 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 con sender= repetidamente).

Cómo mostrar resultados al usuario

  • Los JIDs (549…@g.us) son ruido visual. Muestra pushName o name del 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 timestamp en formato humano (ej 2026-04-21 15:30) y pushName del autor.
  • Mensajes con fromMe: true son 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_TOKEN en ~/.claude/skills/whatsapp/.env.
  • 503 socket not ready → el bridge está caído o desconectado. GET /api/auth/status para diagnosticar. Si status=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"

  1. Obtené el JID del grupo destino (vía /api/chats).
  2. POST a /api/messages/forward con { 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.mdLEELO 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) — contiene WA_BRIDGE_TOKEN=<valor>.