APX corre un plugin de WhatsApp sobre una sesión de WhatsApp Web: una cuenta, vinculada por QR, que
maneja el daemon. Las credenciales viven en ~/.apx/whatsapp/auth/default/ con permisos 0700 —
quien tenga esa carpeta ES la cuenta.
Los tres desenlaces
Sección titulada «Los tres desenlaces»Cada mensaje entrante termina en exactamente uno de estos, decidido por código, antes de que ningún modelo vea el mensaje. Si a un desconocido se le contesta o no nunca es un juicio que hace el modelo sobre un mensaje que está tratando de convencerlo.
| Quién escribe | Qué corre | Qué recibe |
|---|---|---|
| El dueño (el que nombra el roster) | el turno completo del super-agent — tools, memoria, proyectos | una respuesta normal |
| Un contacto del roster con un rol | un turno sellado: sin tools, sin memoria, sin ningún otro canal, con esa única conversación como historia | una respuesta corta en texto plano |
| Cualquier otro | no corre nada | silencio — se registra, y se le avisa al dueño |
El silencio para un desconocido es el resultado buscado, no una falla. Dos cosas pasan con todo
mensaje que no es del dueño, conteste o no: se registra en el canal whatsapp con el JID del
remitente como actor, y se le avisa al dueño desde el daemon, no desde el modelo — un turno
sellado no tiene tools, así que no podría escalar aunque quisiera.
Vincular una cuenta
Sección titulada «Vincular una cuenta»-
Abrí Settings → WhatsApp en el panel web y apretá Pair. Aparece un QR.
🖥️ SCREENSHOT · web Settings → WhatsApp — el QR de vinculaciónapx web, Settings → WhatsApp, apretar PairSettings → WhatsApp — el QR de vinculación -
En el teléfono: WhatsApp → Configuración → Dispositivos vinculados → Vincular un dispositivo, y escanealo.
-
Configurá
owner_jid— el número desde el que escribís vos. APX no lo adivina.
La línea que vinculaste y la persona que la posee son cosas distintas. Un despliegue común
vincula un número dedicado en el que contesta el asistente, mientras el humano le escribe desde su
propio teléfono, otro. Poner “el dueño es el que vinculó” haría que la línea sea dueña de sí misma
y dejaría al humano real como un desconocido, así que owner_jid se pregunta y nunca se deduce.
El roster es la lista blanca
Sección titulada «El roster es la lista blanca»{ "whatsapp": { "enabled": true, "auto_reply": true, "reply_to_groups": false, "owner_jid": "1234567890@s.whatsapp.net", "contacts": [{ "jid": "1234567891@s.whatsapp.net", "name": "Sam", "nickname": "Sammy", "relationship": "mi contadora", "bio": "quién es, en palabras del dueño", "rules": "qué quiere el dueño que se haga con esta persona", "role": "client", "auto_reply": true }], "roles": { "client": { "auto_reply": true } } }}- El que escribe queda registrado automáticamente como guest. Eso no es permiso — a un guest igual nunca se le contesta. El registro existe para que el dueño tenga algo a lo que darle permitir.
role: "owner"no se puede otorgar desde el roster.- Un rol indefinido falla cerrado (silencio), así que un typo nunca amplía el acceso.
bioyrulesllegan solo al turno que contesta a esa persona. Lo que está escrito sobre un contacto nunca está en el prompt que le contesta a otro.- Los grupos vienen apagados: todo lo que se dice en un grupo lo lee gente que nadie avaló.
Un mismo humano puede tener dos direcciones — un JID de teléfono y un …@lid opaco. Las dos caen
en el mismo hilo.
Menús, botones y listas
Sección titulada «Menús, botones y listas»Una cuenta de empresa rara vez escribe oraciones. Manda un menú, y APX lo desarma en texto antes de que ningún turno lo vea:
Hola! Con qué te ayudo?[Opciones: 1. Autos | 2. Hogar | 3. Vida]Y un tap que alguien hizo sobre uno se lee como [eligió: Autos]. Se decodifican las cuatro
generaciones que siguen vivas — botones de respuesta rápida, listas, templates hidratados y
nativeFlow (incluida una lista single_select escondida adentro de un solo botón, y carruseles) —
más las encuestas, y todas envueltas en viewOnceMessage o ephemeralMessage, que es como las
mandan habitualmente las cuentas de empresa.
Para contestar uno, pasale option a la herramienta send_whatsapp — el número tal como se mostró
("2"), el título exacto ("Autos") o el id de la opción. APX busca el último menú de ese chat en
el ledger de mensajes y manda la selección real, así el bot ve su botón apretado y no una oración.
- Los botones de respuesta rápida, las listas y los templates reciben un tap real.
- Los menús nativeFlow y las encuestas se contestan tipeando la etiqueta de la opción — no hay
encoder para
interactiveResponseMessage, e inventar un nodo del protocolo sería poner un mensaje malformado en el server de otro. En la práctica es lo que hace una persona cuando el botón no abre. - Si un tap queda sin respuesta, reintentá con
as_text: true. - Un fragmento ambiguo no matchea nada en vez de apretar un botón a la adivinanza:
"seguro"contra Seguro de auto y Seguro de hogar se rechaza, y el rechazo trae las dos opciones.
El hilo registra la etiqueta, no el id del botón — una transcripción llena de ids opacos no es la conversación que pasó.
Dónde vive, y cuánto tarda en contestar
Sección titulada «Dónde vive, y cuánto tarda en contestar»WhatsApp tiene su propia entrada en el riel, debajo de Code: el roster, el diccionario de figuritas, los pedidos que te están esperando y las conversaciones de esta línea, en una sola página. Ajustes → WhatsApp es el mismo panel — se gana el lugar en el riel porque es un lugar al que entrás mientras alguien te está escribiendo, no una página que configurás una vez.
Las respuestas esperan un par de segundos antes de salir (whatsapp.reply_delay_ms, 2500 ms por
defecto, 0 para contestar al toque). No es pose de cortesía: la gente escribe de a tandas — “hola”,
“che”, y recién ahí la pregunta — y una respuesta que llega en menos de un segundo contesta el primer
tercio de una idea y manda tres mensajes por uno. Si llega otro mensaje del mismo chat mientras corre
el reloj, el turno anterior se corre y el nuevo contesta todo. El “escribiendo…” está prendido
mientras tanto, así que la espera se lee como que está pensando.
Multimedia
Sección titulada «Multimedia»Archivos
Sección titulada «Archivos»Un archivo que te mandan se guarda. Si es un PDF o algo de texto, además se lee: sus palabras entran en el turno, así el agente contesta lo que dice el presupuesto y no que llegó un presupuesto. El resto se guarda con su propio nombre y se ofrece en el panel.
Dos cosas no se descargan nunca, y el hilo dice cuál de las dos fue: programas y scripts
(.exe .msi .sh .apk .dmg .ps1 .jar …, mirando la ÚLTIMA extensión, así que cotizacion.pdf.exe es
un exe) y cualquier cosa de más de 25 MB. Acá nada ejecuta un adjunto — la negativa existe porque
si no el archivo queda en tu carpeta de medios con un nombre que eligió un desconocido. Los
comprimidos (zip, rar, 7z) se guardan y no se abren.
Un archivo que llegó antes de que esto existiera se puede traer después: apx whatsapp repair le
pide el mensaje al teléfono y ahí sí lo baja.
Mandar uno también anda: el send_whatsapp del agente acepta un file (una ruta absoluta de esta
máquina), un documento conserva su nombre y una imagen llega como foto, y el texto que lo acompañe
queda como epígrafe.
Lo que entra se resuelve antes de que corra el turno, así el registro queda completo incluso para gente a la que nunca se le contesta.
| Llega como | Lo que ve el turno |
|---|---|
| Audio | [audio] <transcripción> — transcripto localmente |
| Foto | píxeles que un modelo de visión puede mirar de verdad |
| Sticker | [sticker: <significado>] |
| GIF | su primer frame |
| Video | se rechaza — hay que decirlo derecho en vez de adivinar por el caption |
| Documento | [document: <nombre> — not opened] |
| Reacción | se registra como [reaccionó ❤️], y nunca se contesta |
Los stickers se aprenden una sola vez: la primera vez que aparece uno lo describe un modelo de
visión y se guarda por hash de contenido en ~/.apx/whatsapp/stickers.json; cada aparición
posterior reusa esas mismas palabras, así el historial queda coherente. El dueño puede reescribir
el texto y el modelo nunca se lo pisa de vuelta.
Mandar mensajes
Sección titulada «Mandar mensajes»Usá la herramienta send_whatsapp: { to, text }. to acepta un número de teléfono en cualquier
formato o un JID completo. Solo texto plano — WhatsApp no renderiza markdown, así que los
asteriscos y las backticks llegan como caracteres literales.
| Campo | Qué hace |
|---|---|
text | el cuerpo, en texto plano |
sticker | describí uno en palabras; se matchea contra la biblioteca aprendida |
option | contestar un menú — el número, el título exacto o el id |
as_text | con option: tipear la etiqueta en vez de apretar el botón de verdad |
react_to | poner un emoji sobre un mensaje en vez de mandar uno |
Se entrega en el instante en que la llamada retorna. No hay borrador, no hay undo y no hay recall.
Los mensajes que el dueño escribe desde su propio teléfono también se registran (WhatsApp los
espeja al dispositivo companion como fromMe). Se registran y nunca se contestan — contestarlos
sería contestarse a uno mismo — y llevan meta.authored_by: "owner", así el hilo se lee como la
conversación entera.
Todas las rutas cuelgan de /api/whatsapp.
GET /status estado de la sesión + resumen del roster (nunca el QR)POST /pair arrancar la vinculación → { qr, qr_data_url }POST /logout soltar las credencialesPATCH /settings { enabled, auto_reply, reply_to_groups }POST /send { jid, text }GET /contacts PATCH · DELETE /contacts/:jidPOST /contacts/:jid/owner marcar ese contacto como el dueñoGET /roles PUT · DELETE /roles/:nameGET /suggestions POST /suggestions/:id/confirm · /dismissGET /stickers PATCH · DELETE /stickers/:keyGET /repair qué está mal en los chats (pregunta, no toca nada)POST /repair { dry_run, force }POST /choose { jid, option } tocar una opción del último menú de ese chatReparar chats que salieron mal
Sección titulada «Reparar chats que salieron mal»Hay fallas que no se arreglan solas con el próximo mensaje. Una empresa cae en el roster sin
nombre (una cuenta de negocio no manda push name), así que su hilo queda encabezado por una
dirección cruda. Una conversación que abrió APX no se puede continuar, porque el roster se alimentaba
sólo de quien escribía. Un menú queda escrito en el ledger como [empty message], por un decodificador
que no conocía la forma en que llegó. Y el mensaje de un contacto arranca un turno que un restart mata
a mitad de camino: WhatsApp no entrega ese mensaje dos veces, así que nada lo reintenta.
apx whatsapp chats # qué está mal — no toca nadaapx whatsapp repair # arreglarloapx whatsapp repair --dry-run # ...o verlo primeroapx whatsapp repair --force # reintentar mensajes que un teléfono apagado nunca devolvióapx whatsapp statusQué hace repair:
- liga la otra dirección de una persona (su
@lidy su número son una fila, no dos), - le pone nombre a una fila sin nombre, con lo que el ledger ya le escuchó,
- hace contestable a un invitado al que APX le escribió primero, marcado
pending_review: contestable, no revisado, - le pide al teléfono cualquier mensaje que llegó ilegible. El teléfono todavía lo tiene; el reenvío se reconoce como una reparación, así que nunca se contesta ni se te reporta de nuevo. Un menú recuperado vuelve con sus opciones, y el panel las dibuja como botones.
Lo que no hace: inventar un nombre, promover a alguien sobre quien ya decidiste, fusionar dos filas que pueden tener notas tuyas, ni contestar un mensaje por su cuenta. Un chat que quedó sin respuesta se reporta, nunca se contesta automáticamente — escribirle a alguien horas después lo decidís vos.
Cuando algo se ve mal
Sección titulada «Cuando algo se ve mal»| Síntoma | Causa |
|---|---|
| Conectado, pero no se le contesta a nadie — ni a vos | owner_jid está vacío, así que todos resuelven como desconocidos |
logged_out | las credenciales están muertas. El plugin no reintenta solo — reintentar con credenciales muertas en loop es como se te marca una cuenta. Hay que vincular de nuevo |
| Idle después de un restart, sin sesión | esperado cuando nunca se vinculó nada. El daemon no abre un socket ni produce un QR sin que se lo pidan |
| Las respuestas de un contacto abren un segundo hilo | escribieron desde una dirección …@lid que APX todavía no plegó — apx whatsapp repair la liga |
| Un contacto escribió y no recibió nada | cayó un restart con el turno corriendo. El mensaje ya fue entregado, así que nada lo reintenta: apx whatsapp chats lista todos los chats así, con el texto |
Un hilo titulado con una dirección cruda, o un mensaje que dice [empty message] | apx whatsapp repair |