Commit Graph
48 Commits
Author SHA1 Message Date
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 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 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 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 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 196855b32d fix: quitar tipo mixed y ENT_SUBSTITUTE para PHP 7.4 2026-06-04 20:09:21 -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 202a263e25 feat: bot WhatsApp Business API — Palmas360 inicial 2026-06-04 13:35:23 -05:00