Commit Graph
54 Commits
Author SHA1 Message Date
Lizandro GuarnizoandClaude Sonnet 4.6 fb61a6e027 feat: add collect_for_each flow type to bot-config UI
- Dropdown option '🌧 Registro por ítem' in both PHP-rendered cards
  and the addFlowCard() JS template
- Fields panel: endpoint lista, endpoint upload, campo ID, campo nombre,
  encabezado, pregunta por ítem, mensaje de éxito
- flowTabChange() now shows/hides ftg-foreach panel
- Save handler in index.php parses all flow_fe_* POST fields and
  builds the collect_for_each config object

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-29 10:40:15 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 6dd9995b7f fix: new company defaults is_active=true
Creating a company with the Activa checkbox unchecked (or missing)
set is_active=0, silently disabling the bot for that company.
New companies now default to active; existing companies keep their
current value when the form loads.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-29 10:02:52 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 e3be2d5b3f fix: stop repeated/duplicate bot responses
Root cause: WpWebhook called OutboundWorker::processQueue() after every
incoming message. BotRouter already sends synchronously in enqueueResponse();
failed messages stayed status='queued'. On the next incoming message
processQueue() would re-send ALL previously-failed messages, causing
the bot to repeat old responses.

- Remove OutboundWorker::processQueue() from WpWebhook::processEvent()
  BotRouter handles immediate delivery; retries should be manual/cron only
- Fix chatSend(): instead of queuing + processQueue() (which drains ALL
  queued messages), call WhatsAppSender::sendText() directly for the
  one message being sent

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-29 09:05:24 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 b25126e52b fix: company save 500 error — catch DB exceptions, show error to user
- CompanyRepository::save() now catches PDOException and throws a
  RuntimeException with a human-readable message (duplicate name, etc.)
- Also fixes unchecked checkboxes (requires_approval, is_active) not
  being saved as 0 on UPDATE
- index.php save handler catches RuntimeException and redirects back to
  the form with an ?error= param instead of crashing with 500
- companyEdit() renders the error banner when ?error= is present

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-28 18:05:38 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 68317da748 fix: pre-fill phone_number_id on new company from existing shared value
All companies use the same WhatsApp phone number ID. When creating a
new company, auto-fill the field with the value already in use so the
admin doesn't have to look it up and type it manually.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-28 18:01:13 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 1f8af616c5 clarify: phone_number_id hint — shared number is expected
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-28 17:58:51 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 9ef9eeffcb fix: chat page — readable content, correct unread count, immediate send
- Add extractContent() helper: parses JSON WhatsApp payloads to show
  human-readable text (button titles, body text, captions) instead of
  raw JSON in both the conversation list preview and message bubbles
- Fix unread_count subquery: was comparing outbound_queue.id with
  conversations.id (different sequences); now uses created_at comparison
- Remove broken outbound_queue secondary query from chatMessages():
  all messages (bot + admin) are already in conversations table —
  the extra join caused duplicates and wrong ordering
- chatSend() now calls OutboundWorker::processQueue() immediately after
  queuing so admin messages are sent to WhatsApp without manual trigger

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-28 17:54:43 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 2c8b597b28 fix: show general panel by default on company create form
panel-general had class tab-panel (display:none) but showTab('general')
was only called in edit mode — so new company form showed no fields.
Adding active class as default makes it visible immediately.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-28 17:49:24 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 84afeb598c feat: company create with copy-from + fix missing fields
- Add phone_number_id and display_phone fields to create/edit form
  (were never shown or saved despite existing in the DB)
- Add phone_number_id and display_phone to CompanyRepository::save()
- New company form shows "Copiar configuración de empresa" dropdown:
  selecting an existing company pre-fills api_base_url, api_key,
  bot_type, config_json, etc. so admins only need to change
  the name and phone_number_id

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-28 17:47:16 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 332c808bf2 remove: drop Pendientes from nav menu
Pending-approval flow is no longer used. Remove the nav link so the
page is no longer reachable from the sidebar (routes and backend logic
left intact in case they're needed later).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-28 17:42:51 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 d84f587b52 fix: repair broken loadConvs JS function in chat page
The function had an unclosed arrow function callback (convs.map(c => {)
followed by a misplaced catch, creating a JS syntax error that prevented
the entire <script> block from parsing — so no conversations or messages
rendered at all.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-28 17:41:33 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 e0c873cb4c fix: define CONFIG and ENDPOINTS in bot-config page for addFlowCard
addFlowCard() referenced CONFIG.menus, CONFIG.flows and ENDPOINTS but
those variables were only defined in the conversation-tree page (a
different route). On /admin/bot-config the access threw ReferenceError,
aborting the function before insertAdjacentHTML ran, so no card appeared.

Compute botConfigJson/botEndpointsJson from already-loaded PHP data and
emit them as const CONFIG / ENDPOINTS at the top of the page's <script>.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-28 17:38:28 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 4c93c20482 feat: add Google Gemini provider option to bot-config AI tab
- Add Gemini option to ai_provider select (alongside OpenAI/Mock)
- Show/hide OpenAI vs Gemini fields dynamically via aiProviderChange()
- Add gemini_api_key and gemini_model fields to the form
- Add openai_api_key field in bot-config (per-company override)
- Save handler now persists gemini_api_key, gemini_model, openai_api_key

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-28 17:32:51 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 845dd08a3a fix: avoid PHP interpolating JS template literals in addFlowCard heredoc
Replace all backtick template literals inside addFlowCard (which lives
inside a PHP heredoc) with plain string concatenation. PHP was evaluating
${k}, ${Date.now()}, ${menuOpts}, etc. as PHP variable expressions,
emitting deprecation warnings inside the <script> block and corrupting
the JavaScript — causing all tab buttons on bot-config to stop responding.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-28 17:28:33 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 1116b8ece9 fix(admin): endpoint names in selects, per_type merge, category labels, cmd datalist
- Load name+direction in botConfig() endpoint query (was missing name column)
- Show friendly endpoint names in all flow select dropdowns (was showing raw key)
- Fix per_type save: merge with existing data instead of replacing (was wiping custom flows/commands per category)
- Fix category labels: Categoría 1 = Solo reporta, 2 = Solo recibe, 3 = Reporta y recibe
- Remove incorrect "menú debe ser de tipo botón" restriction text
- Add datalist to command action inputs so admin can pick from existing menu/flow IDs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-28 17:09:47 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 2c0ac04993 fix(flows): CSS display conflict, addFlowCard, endpoint CRUD, menu re-send, URL vars
- Fix display:none vs display:flex conflict in PHP flow card rendering (sections were always visible)
- Add addFlowCard() JS function that properly inserts new flow cards into the PHP form
- Fix flowTabChange() to use block vs flex display correctly per section type
- Rewrite endpoints tab as full CRUD table (add/edit/delete) with name, direction, active toggle
- Add companyEndpointDelete() handler and route
- Update companyEndpointSave() to handle ep_id, name, params, is_active fields
- Add company_endpoints columns: name, params
- Add saveEpRow/testEpRow/deleteEpRow/addNewEp JS functions
- NormalBot: set __greeted sentinel after showing greeting menu to prevent re-send on every message
- NormalBot: add substituteUrlVars() to replace {param} tokens in endpoint URLs with collected metadata
- DB: submenu_ciclos_sanidad (4 items) → list; prod_kilos_finca endpoint linked; all >3-button menus → list
- DB: fincas_list endpoint activated

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-28 17:04:05 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 eeda883bd9 feat: flow form editor shows all types with proper selects
PHP form for flows tab now renders:
- Type select grouped: Respuesta / Navegar / Acción directa / Formulario
- 💬 Texto fijo: textarea
- 📑 Mostrar menú: dropdown of company menus
-  Función especial: goodbye / api_report / IA / Agente
  - api_report expands: endpoint dropdown, period, filename, caption
- ✏️ Pedir dato (collect_input): prompt, meta_group, meta_key, next-node select
- 📋 Lista dinámica (dynamic_list): endpoint select, value/label fields, header, body, next-node
- 🚀 Enviar formulario (submit_form): endpoint, meta_group, filename, caption
Save handler in index.php reads all new field arrays and builds correct flow JSON.
flowTabChange()/flowFnTabChange() toggle sections on type/function change.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-28 14:03:18 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 316d9ce73e feat: dynamic lists, multi-step forms, improved flow editor
NormalBot new flow types:
- collect_input: asks user a question, saves answer to metadata
- dynamic_list: calls endpoint to get options, displays WhatsApp list,
  saves selection to metadata; max 10 rows
- submit_form: reads accumulated metadata fields and sends to api_report
Commands always take priority (escape from any collecting state).
Interactive selections during collecting state are caught before the
static menu lookup.

Admin flow editor redesign:
- All fields use select dropdowns (menus, endpoints, next-node)
- Grouped options: Respuesta / Navegar / Acción directa / Formulario paso a paso
- collect_input, dynamic_list, submit_form panels with contextual fields
- addNewFlow() opens blank modal; saveFlow() handles all types

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-28 13:51:33 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 12ec5d28b1 feat: flow editor shows endpoint selector when api_report is chosen
- Pass ENDPOINTS to JS context from company_endpoints table
- Add api_report and goodbye options to flow function select
- When api_report selected: show endpoint dropdown + date period +
  filename + caption fields
- saveFlow() persists params.endpoint_key, date_mode, caption, filename
- flowFuncToggle() shows/hides the endpoint panel on function change

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-28 13:37:32 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 6f799c3829 feat: media via AI, text via NormalBot only, ai_for_media toggle
- Text/interactive/button messages → NormalBot only, never escalates to AI
- Image/audio/video/document → AiBot.processMedia() if ai_for_media enabled,
  else falls back to category greeting menu
- AiBot.processMedia() uses permission_type context so AI acknowledges uploads
  for perm 3 and redirects others to their menu
- WpWebhook.handleMedia now calls BotRouter so the bot responds to media
- Admin UI: checkbox toggle "Procesar imágenes y archivos con IA" in AI tab
- categoryMenuFallback() extracted in BotRouter as shared helper

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-28 13:28:56 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 32e2a2a82e feat: per-category greeting menu in bot config
Add a "Por Categoría" tab in the bot config UI where each permission
category (1, 2, 3) can be assigned its own greeting button menu.
When a user writes without an active context, the bot shows the menu
configured for their category instead of falling through to the global
greeting text.

NormalBot.process() now checks per_type[X].greeting_menu first, which
directly references a menu key and serves as both greeting and fallback
for that category. Fully backward-compatible with existing greeting_flow.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-28 13:04:29 -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 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