On any api_report failure (missing endpoint, curl error, HTTP error,
upload failure), reset the bot_context so the next message goes through
the normal flow instead of retrying the failed report indefinitely.
Also reduce curl timeout from 60s to 15s to avoid WhatsApp webhook
timeouts that cause Meta to retry the same message repeatedly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
conversationFlow saveConfig():
- Was not serializing buttons[] array for button-type menus (would erase
all buttons on save). Now sends menu_btn_id/menu_btn_title like botConfig.
- Defaulted menu type to 'list' instead of 'button'.
conversationFlow addNew('menu') modal:
- Defaulted to 'list' with header/footer/button fields.
- Now defaults to 'button' with inline buttons editor; toggle via type select.
- saveNewMenu() creates correct {type,body,buttons} or {type,...,sections}.
conversationFlow orphan-flow detection:
- Only checked sections[].rows, so flows linked via button menus appeared
as orphaned. Now checks buttons[] array for button-type menus.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Instead of making a separate fetch to /admin/chat/conversations (which
Nginx intercepts), pre-load conversations in the PHP chat() method and
embed them as window.INITIAL_CONVS JSON in the page script.
renderConvs(INITIAL_CONVS) runs synchronously on page load — no HTTP
request needed. setInterval still tries to refresh via fetch every 15s
but the initial render no longer depends on it.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Nginx intercepts any request to /admin/chat/* sub-routes. Fix by handling
all chat API requests on the same /admin/chat path using query params:
GET /admin/chat?api=convs → conversations list
GET /admin/chat?api=msgs&phone=X → messages for a phone
POST /admin/chat → send message
JS fetch URLs updated accordingly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
/admin/chat/conversations → /admin/chat-convs
/admin/chat/messages → /admin/chat-msgs
/admin/chat/send → /admin/chat-send
The production server was intercepting all subpaths of /admin/chat and
redirecting them back to /admin/chat, so fetch never received JSON.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
If msgInput element is somehow null, addEventListener throws and prevents
loadConvs() from being called — wrapping in null check makes it safe.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- NormalBot: fix type check 'buttons' -> 'button' in buildMenuResponse
- DashboardController: botConfig editor now shows buttons editor for button-type menus,
list fields (header/footer/button trigger) hidden for button type; menuTypeChange/addBtnRow
JS functions toggle the UI; addMenu() defaults to button type
- DashboardController: renderMenuNode and renderFlowNode now read buttons[] array for
button-type menus instead of only sections[].rows
- DashboardController: editMenu modal shows buttons editor or sections editor based on type;
editMenuTypeToggle/addEditBtnRow; saveMenu reads buttons for button type
- public/index.php: botConfigSave handler parses menu_btn_id/menu_btn_title for button menus;
saves compact {type,body,buttons} structure; list menus still save {type,header,...,sections}
- DB: migrated all list menus to button type for both companies (show_main_menu,
submenu_informes, submenu_ciclos, show_menu_cat1, show_menu_cat2)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- resolveMenu ahora recibe los menus ya fusionados (global + per_type) para
que flujos que referencian menús de otro per_type se resuelvan correctamente
- handleFlow y processInteractive propagan el array de menus fusionados
- Corrige variable indefinida $endpoint → $endpointKey en 3 logs de executeApiReport
- buildMenuResponse cambia tipo de retorno a ?array para evitar TypeError en PHP 8
- BD: flow global show_main_menu apunta a menú show_main_menu (completo) en lugar
de show_menu_cat2 (solo existía en per_type[2]), fix para usuarios permission_type=3
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- NormalBot: nueva funcion api_report para descargar PDF/Excel del ERP y enviarlo por WhatsApp
- NormalBot: resolveMenu() para referenciar menus por nombre en los flujos
- WhatsAppSender: uploadMedia() y sendDocument() para envio de documentos
- BotRouter: guarda respuestas en conversations para visibilidad en el chat
- Script apply-menus.php con estructura completa de menus y endpoints
Saves a raw entry to webhook_logs immediately on any POST to the webhook,
before HMAC check or JSON parsing. Shows HMAC status (ok/invalida/sin-firma).
Live feed now shows all traffic including rejected and malformed payloads.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
SSE kept a PHP-FPM worker open for 28s per connection, blocking the whole
server with only 20 workers shared across all vhosts. Polling with
setInterval(3000) releases the worker on each request immediately.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add gemini_api_key and gemini_model to allowed keys in settings save handler
- Add POST /admin/settings/test-ai endpoint for testing Gemini/OpenAI connection
- Add "Probar conexion IA" card in settings page with live feedback
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Settings: agrega Gemini al selector de proveedor, campos gemini_api_key y gemini_model
- Modelos: gemini-2.0-flash, gemini-1.5-flash, gemini-1.5-pro
- AiBot::callGemini(): llama generateContent API con system_instruction y historial
- migrate.php: seed defaults gemini_api_key y gemini_model
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Consume el endpoint numeros_dn configurado para la empresa
- Acepta JSON con array raíz o {numeros:[...]}
- Mapea campos wa_number/numero/phone, nombre/name/label, permiso/permission_type/tipo
- Upsert en company_phones: nuevos se insertan, existentes se actualizan
- Reporta conteo de nuevos / actualizados / omitidos
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- El número WA Business es global (configuración general), no por empresa
- OutboundWorker usa fallback a WHATSAPP_DEFAULT_PHONE_NUMBER_ID si la empresa no tiene
- CompanyRepository::save() ya no requiere phone_number_id
- phone_number_id permite DEFAULT '' en schema
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
El routing ahora es por número remitente (company_phones), no por phone_number_id.
phone_number_id solo se usa para enviar mensajes salientes.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Convierte backtick template literals con \${it.id} a concatenación de strings
- Fix query de chatConversations: GROUP BY solo por phone_number, evita duplicados
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- resolveCompanyByNumber(): busca el número en company_phones, identifica empresa
- sendRejectionMessage(): responde "su número no se encuentra habilitado" via WA API
- handleMessages(): por cada mensaje resuelve empresa por remitente; si no está
registrado, guarda log y envía rechazo automático
- permission_type disponible en context para BotRouter
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- GET /admin/test-message: formulario con empresa, número, nombre y texto
- POST /admin/test-message/send: construye payload WhatsApp, firma HMAC y
hace POST al webhook propio — pipeline completo sin tocar Meta
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Layout: fusiona topbar + nav en una sola barra de 56px (elimina doble menú)
- Desktop: brand izquierda, topnav centrado con underline activo, usuario derecha
- Móvil: hamburger animado (3 líneas → X), drawer lateral con overlay
- app.css: reescritura completa sin gradientes ni sombras pesadas
- DashboardController: corrige heights 104px→56px (chat, bot-config tabs)
- Live Feed: cierra EventSource en pagehide para evitar bloqueo al navegar
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- admin/Layout.php: nueva clase compartida con topbar, nav agrupado y hamburger móvil
- assets/app.css: CSS centralizado (~130 líneas), elimina ~2000 líneas duplicadas
- DashboardController: 11 vistas refactorizadas con Layout; tarjetas de stats clicables;
drawer de aprobación rápida desde el dashboard; Live Feed reemplaza polling 3s por SSE;
bot-config con preview WhatsApp en tiempo real; chat con back-button móvil y textarea auto-resize
- public/index.php: rutas SSE (/admin/webhook/stream/sse) y pending-list (/admin/pending-list)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>