Commit Graph
46 Commits
Author SHA1 Message Date
Lizandro GuarnizoandClaude Sonnet 4.6 dee459d53f fix: reset context on api_report failure so user is never stuck
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>
2026-06-28 11:40:23 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 0555b35766 fix: multiple consistency bugs found in full audit
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>
2026-06-27 19:57:55 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 0be3d467ff fix: embed conversations data inline in chat page HTML
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>
2026-06-27 19:51:15 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 5bae03e5a3 fix: consolidate all chat API calls onto /admin/chat with ?api= param
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>
2026-06-27 19:48:21 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 4f30ed4447 fix: rename chat API routes to avoid Nginx /admin/chat/* redirect conflict
/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>
2026-06-27 19:43:06 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 33869a6622 fix: guard against null msgInput before addEventListener in chat page
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>
2026-06-27 19:38:22 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 446cf640e8 fix: add error handling and empty state to chat conversation list
- loadConvs: try/catch shows error message instead of silent empty list
- loadConvs: shows "Sin conversaciones aún" when array is empty
- loadConvs: checks r.ok before parsing JSON to catch 4xx/5xx
- loadMessages: shows loading indicator and catches network/parse errors

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-27 19:31:31 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 639c3e7938 feat: convert all WhatsApp menus from list to button type
- 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>
2026-06-27 19:27:37 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 e4841e2741 fix: resolveMenu usa menus fusionados de per_type + bugs de $endpoint y tipo de retorno
- 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>
2026-06-27 19:09:24 -05:00
Lizandro Guarnizo 932a7a78a7 fix: fallback_flow por categoria, menu directo en saludo/fallback 2026-06-27 18:53:41 -05:00
Lizandro Guarnizo 02108b7ffa fix: reset nodo tras saludo + fallback por categoria 3 2026-06-27 18:51:30 -05:00
Lizandro Guarnizo b96dbcc394 feat: menus por permission_type (1=info, 2=informes, 3=completo) 2026-06-27 18:46:35 -05:00
Lizandro Guarnizo 7407c716d4 fix: usar phone_number_id real del webhook + actualizado en BD 2026-06-27 18:25:03 -05:00
Lizandro Guarnizo c053ba3295 fix: enviar respuestas del bot directamente por WhatsApp sin depender de cola 2026-06-27 18:12:50 -05:00
Lizandro Guarnizo 010613846c fix: api_report busca URL en company_endpoints por endpoint_key
- executeApiReport ahora consulta company_endpoints para obtener la URL
- Soporta metodos GET/POST segun configuracion
- Date_mode: today/last_30 para fechas dinámicas
2026-06-27 11:38:25 -05:00
Lizandro Guarnizo 51c187e47d feat: menu navegable con submenus y descarga de reportes via api_report
- 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
2026-06-27 11:30:08 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 454ee297e2 feat: log every incoming webhook request before validation
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>
2026-06-22 11:39:23 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 483cc4797e fix: replace SSE with polling in live feed to stop blocking FPM workers
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>
2026-06-22 11:37:21 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 c702dbadc1 fix: save gemini keys in settings + add AI connection test
- 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>
2026-06-22 11:34:25 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 5277d47c14 feat: soporte Google Gemini como proveedor de IA
- 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>
2026-06-19 22:55:13 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 ef799a4a0a feat: botón Sincronizar desde ERP en pestaña Números WhatsApp
- 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>
2026-06-19 22:45:21 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 b84dd01c40 fix: separa cosecha/sanidad/polinización en 3 endpoints independientes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 22:27:58 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 ea7560f9bf refactor: elimina phone_number_id y display_phone del formulario de empresa
- 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>
2026-06-19 22:19:05 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 2632eff3fb fix: elimina UNIQUE de phone_number_id — varias empresas comparten el mismo número WA
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>
2026-06-19 22:14:57 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 14db6fe27f fix: PHP heredoc interpretaba template literals JS como constantes PHP
- 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>
2026-06-19 22:10:38 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 b308de2b3b feat: routing por número remitente + rechazo de números no habilitados
- 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>
2026-06-19 22:07:37 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 7657e0f55c fix: CompanyRepository::all() → findAll()
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 22:04:00 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 860e93a20b feat: módulo de empresa con números WhatsApp y endpoints API por empresa
- DB: company_phones (3 tipos de permiso, límites por usuarios ERP)
- DB: company_endpoints (11 endpoints upload/download configurables)
- DB: erp_active_users en companies (base para calcular límites)
- Company edit: 3 pestañas — General / Números WhatsApp / Endpoints API
- Límites: tipo 1 = usuarios_erp×10, tipo 2+3 = usuarios_erp
- Endpoints: URL configurable + botón Probar + preview de respuesta JSON
- Layout::close() agregado

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 21:57:31 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 b083d49769 feat: página de mensaje de prueba para simular webhook entrante
- 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>
2026-06-19 21:50:25 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 7b8a4c2056 fix: SSE libera worker en 28s y detecta desconexión cada 200ms
- TTL de 28s por conexión; EventSource reconecta automáticamente (retry:3000)
- Reemplaza sleep(2) por loop usleep(200ms) para detectar pagehide rápido
- PHP-FPM workers aumentados de 5 → 20 (directo en servidor)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 21:47:24 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 2082323586 fix: cache-bust app.css para forzar recarga en browser
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 21:44:37 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 333317e268 feat: UI minimalista — una sola barra de navegación, drawer móvil, fix SSE
- 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>
2026-06-19 21:41:37 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 ef6e9c1f70 feat: UI/UX overhaul — shared layout, SSE live feed, responsive nav, approval drawer
- 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>
2026-06-19 21:21:32 -05:00
Lizandro Guarnizo daf1947f13 cambios importantes 2026-06-13 12:03:30 -05:00
Lizandro Guarnizo dd57cba0e2 feat: bot config visual editor, chat admin, debug logging, HMAC toggle 2026-06-13 11:24:32 -05:00
Lizandro Guarnizo 774cd829d9 cambios importantes 2026-06-13 09:19:36 -05:00
Lizandro Guarnizo a6595862ec cambios importantes 2026-06-12 22:17:05 -05:00
Lizandro Guarnizo 4bd353c128 cambios importantes 2026-06-12 22:02:03 -05:00
Lizandro Guarnizo bc422e0a1c cambios importantes 2026-06-12 12:18:21 -05:00
Lizandro Guarnizo efebec3c1b fix: eliminar texto de WhatsApp en politicas.html 2026-06-04 20:21:06 -05:00
Lizandro Guarnizo 196855b32d fix: quitar tipo mixed y ENT_SUBSTITUTE para PHP 7.4 2026-06-04 20:09:21 -05:00
Lizandro Guarnizo 1d74463265 fix: ruta raíz / redirige a /login 2026-06-04 20:05:36 -05:00
Lizandro Guarnizo 43f337ee34 fix: compatibilidad PHP 7.4 — str_starts_with, str_contains, match, never, mixed 2026-06-04 20:04:37 -05:00
Lizandro Guarnizo 943e629120 fix: htaccess + index raíz con soporte APP_BASE para subdirectorio /admin 2026-06-04 17:50:55 -05:00
Lizandro Guarnizo e984b2f255 fix: index.php raíz que incluye public/index.php 2026-06-04 17:46:40 -05:00
Lizandro Guarnizo 202a263e25 feat: bot WhatsApp Business API — Palmas360 inicial 2026-06-04 13:35:23 -05:00