--- name: whatsapp description: 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". allowed-tools: 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=`. 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: ```bash ~/.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=&q=&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=` — búsqueda parcial ILIKE en el contenido - `sender=` — 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=&since=&until=&chatType=&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: ```json "media": { "type": "image", "mimetype": "image/jpeg", "size": 123456, "caption": "...", "downloadUrl": "/api/messages/media//" } ``` Para obtener el archivo **desencriptado**: ```bash ~/.claude/skills/whatsapp/scripts/query.sh \ "/api/messages/media//" \ -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" ```bash # 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?" ```bash 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: ```bash ~/.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): ```bash ~/.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" ```bash ~/.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.): ```bash ~/.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) — contiene `WA_BRIDGE_TOKEN=`.