75 Commits
Author SHA1 Message Date
Lizandro GuarnizoandClaude Sonnet 4.6 fe048f53a3 refactor: simplificar Por Categoría — sin toggle, greeting_flow directo
- Eliminar checkbox 'Pedir finca al inicio': innecesario porque el
  dynamic_list ya auto-selecciona cuando solo hay 1 opción (finca, empresa, etc.)
- greeting_flow controla si hay flujo de bienvenida; si está vacío → greeting_menu
- Si greeting_flow tiene valor → greeting='' (dispara el flow) y se oculta greeting_menu
- Hint text explica que con 1 sola opción se auto-selecciona sin preguntar
- save-per-type deriva greeting/greeting_menu del greeting_flow, no de un toggle

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-30 23:19:50 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 909c6be32f feat: expandir tab Por Categoría en bot-config
- Toggle "Pedir finca al inicio" por categoría → maneja greeting/greeting_flow
- Campo editable de greeting_flow (pre-llena ask_finca al activar el toggle)
- Selector de menú inicial (greeting_menu, visible cuando NO pide finca)
- Selector de flujo de respaldo (fallback_flow) por categoría
- Datalist con flows y menús disponibles para autocompletar
- save-per-type actualiza greeting, greeting_flow, greeting_menu y fallback_flow
  preservando menus/flows/commands del seed en cada categoría
- Cat 1: sin finca, configura greeting_menu desde admin
- Cat 2/3: pide finca activado, greeting_flow = ask_finca

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-30 23:14:49 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 816c6d27c8 feat: show whisper-own in ai-logs filter and badge
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-05 20:16:18 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 fc54ec992b feat: self-hosted Whisper server support
- MediaTranscriber: if whisper_url set, POSTs to own server with
  Basic auth (audio_file field); falls back to OpenAI cloud Whisper
- Admin AI tab: new section for whisper_url / whisper_user / whisper_pass
- save-ai endpoint: saves the three whisper fields to config_json

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-05 20:07:49 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 54d778783e fix: use Layout::open/close in aiLogs() — page() does not exist
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-05 09:06:50 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 9852a749e6 fix: remove broken duplicate query in aiLogs()
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-05 09:05:05 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 5506ebd2af feat: AI usage logging (AiLogger, ai_logs table, admin view)
- AiLogger.php: new service, inserts into ai_logs on every AI call
- ai_logs table: added to migrate.php (provider, model, call_type, tokens, duration_ms)
- AiBot: logs chat, media_response, nlu calls with timing and token counts
- MediaTranscriber: logs whisper, geminiAudio, openaiVision, geminiVision, claudeVision
- /admin/ai-logs: paginated table with provider/company filter
- Layout.php: added IA Logs nav link
- index.php: require AiLogger before AiBot; added GET /admin/ai-logs route

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-05 09:03:21 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 1d9484a1e0 Fix NLU flow: media_id, upload perm, catalog filter, nlu_description field
- WpWebhook: inject media_id into context before calling BotRouter so
  MediaTranscriber can download audio/image (was passing caption='')
- BotRouter: use context['media_id'] for transcription (non-breaking fallback)
- AiBot::processMedia: fix canUpload to include permType 1 (solo reporta)
- AiBot::buildFlowCatalog: include text flows that have nlu_description set
- DashboardController + index.php: add nlu_description field per flow card

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-05 08:51:13 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 bb4b5dab10 Fix AI tab save: dedicated AJAX endpoint /save-ai
Instead of relying on the native HTML form (which couldn't reliably
isolate AI fields), the IA tab now uses an AJAX call identical to
save-per-type. The new /admin/bot-config/save-ai endpoint only
reads and merges AI fields into config_json, preserving menus/flows.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-05 08:28:41 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 5afa846b4e Add per-tab save buttons in bot-config page
Each tab (Comandos, Menús, Flujos, IA, General) now has its own
submit button so the user doesn't have to scroll to the bottom.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-04 22:16:04 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 eb436638fb fix: AI tab settings not saving — read from DOM in bot-config save JS
The JS FormData was built from the CONFIG object (DB state), missing
all AI inputs the user types: api_key, models, checkboxes (nlu,
ai_for_media). Now reads them directly from DOM before the fetch.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-04 22:04:52 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 9e36a965c9 feat: NLU routing + audio/image transcription via AI
- NluRouter in AiBot: builds permission-filtered flow catalog, asks AI
  to route user intent to exact flow key or return chat response
- MediaTranscriber: downloads WA media, transcribes audio (Whisper/Gemini),
  extracts image intent (OpenAI/Gemini/Claude vision)
- BotRouter hybrid: text → NormalBot → NLU fallback if no match;
  audio/image → transcribe → NLU route; sends transcript preview first
- Admin IA tab: NLU checkbox with description
- Per-company nlu flag saved in config_json

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-04 21:50:01 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 2b859e3dc0 feat: multi-provider AI with per-company override and Claude support
- Global settings: custom AI panel with OpenAI/Gemini/Claude each with
  API key, model selector, and live test-connection button (no save needed)
- Per-company IA tab: same three providers + fallback to global when empty
- AiBot: reads per-company config_json first, falls back to env() global
- Added callClaude() for Anthropic Messages API
- More OpenAI models: gpt-4.1, gpt-4.1-mini
- test-ai endpoint: accepts provider/api_key/model from POST for live test

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-04 19:06:51 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 b82fcf6aaa feat: add Gemini 2.5 Flash as default model option
Added gemini-2.5-flash to the admin panel selector (first/default option)
and updated the fallback default in AiBot and migrate seed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-04 18:52:08 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 43933bfe3a feat: endpoints usan api_base_url como base, rutas relativas en UI
- Banner en tab Endpoints muestra la URL base de General
- Inputs de URL aceptan rutas relativas (?param=x o /ruta)
- Preview live de la URL completa (base+ruta) bajo cada input
- Backend prepend api_base_url en test y sync si URL no es absoluta
- Compatible hacia atrás: URLs absolutas existentes siguen funcionando

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-04 15:19:46 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 bfbc70451d feat: clonar endpoints desde otra empresa en pestaña Endpoints API
Agrega panel de clonación en la pestaña Endpoints API del editor de empresa.
Permite copiar los endpoints de otra empresa con dos modos: sobreescribir todo
o solo añadir los endpoints que aún no tienen URL configurada.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-04 13:50:39 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 601169525e fix: dedicated save button for categories + preserve all extra DB keys
- Replace onchange+AJAX with explicit button '💾 Guardar categorías'
  that shows alert on success/error for clear feedback
- Get company_id from DOM (not COMPANY_ID JS const from another script block)
- Main form save now preserves welcome_menu and per_type from DB even if
  POST is truncated by max_input_vars
- Restore welcome_menu='welcome_msg' in DB (was wiped by a previous save)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 19:16:36 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 8f51659f1e fix: save per-category menus via dedicated endpoint, not the main form
The main form exceeded max_input_vars (27 flows × 37 fields each) so PHP
silently dropped per_type_menu before reaching the save handler.

- Add POST /admin/bot-config/save-per-type — reads existing DB config,
  merges only the per_type_menu values, saves back
- Per-type selects fire savePerType() onchange → AJAX → toast on success
- Add public/.user.ini with max_input_vars=5000 as belt+suspenders
- Remove array_is_list() (PHP 8.1) — replaced with PHP 7.4 compatible check

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 19:06:13 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 cae448a9c6 fix: use $menus after it is defined (was undefined in welcomeMenuOpts loop)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 19:02:08 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 b288b07ff5 feat: welcome message, fix per_type save, add missing ciclos flows
- Show welcome_msg (button with "Ver menú") for all new sessions before category menu
- Fix saveConfig() AJAX to include per_type_menu DOM values so they survive individual saves
- Add welcome_menu field to General tab in admin (render + save)
- Add 'atras' command alias (→ show_main_menu)
- Add ciclos_plagas, ciclos_palm, ciclos_tratamientos flows to DB config
- Fix per_type in DB from [] to {} (proper JSON object)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 18:39:04 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 e6dbd5c5bf fix: endpoint test button handles binary (xlsx/pdf) responses correctly
Previously json_encode failed on binary bytes → empty body → JS crash.
Now binary responses show "Archivo recibido: xlsx — 8.7 KB" instead.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 14:49:53 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 e177919be2 fix: resolve {vars} in endpoint test button using saved params config
Fixed vars use their preset (month_start, current_date, etc.); ask-mode
vars fall back to today's date so the test always sends a valid URL.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 13:50:17 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 ff9ff67445 feat: URL var config per endpoint + smart date handling in api_report
- Admin: each endpoint with {vars} shows a vars config section; per-var
  mode is "ask user" (bot prompts in WhatsApp) or "fixed" (preset: today,
  month_start, month_end, last_7, last_30, year_start, or literal)
- Bot: resolves fixed vars before calling endpoint; queues "ask" vars and
  collects them sequentially from the user before executing the report
- date_mode now uses fecha_desde/fecha_hasta (ERP expected names); added
  current_month mode
- JSON responses from ERP (sin_datos etc.) shown as text, not uploaded as file

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 13:36:33 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 21d30a0895 feat: auto-detect collect_and_post fields from endpoint body_fields
Instead of defining fields inline per flow, the admin now defines them
once on the endpoint (direction=upload). The bot reads body_fields from
company_endpoints at runtime, so the same endpoint can be reused by
multiple collect_and_post flows without duplicating field config.

DB: ALTER TABLE company_endpoints ADD COLUMN body_fields TEXT DEFAULT NULL

Changes:
- NormalBot: falls back to endpoint.body_fields when flow.fields is empty
- DashboardController: endpoint rows show a body_fields section for
  upload endpoints (key / label / prompt per field, add/remove rows)
- companyEndpointSave: reads + validates body_fields JSON and persists it

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-29 11:10:37 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 3c7ea9fdf9 feat: collect_and_post flow type — sequential field collection + POST
New flow type that asks the user for a configured list of fields one
by one, shows a summary with confirm/cancel, then POSTs all answers
as JSON to the configured endpoint with Bearer auth.

Config example:
  {
    "type": "collect_and_post",
    "endpoint_key": "subir_cosecha",
    "header": "🌿 Ciclos Cosecha",
    "confirm": true,
    "success_text": " Ciclo registrado.",
    "fields": [
      {"key":"fecha",    "label":"Fecha",    "prompt":"📅 ¿Fecha del ciclo? (YYYY-MM-DD)"},
      {"key":"cantidad", "label":"Racimos",  "prompt":"🔢 ¿Cantidad de racimos?"},
      {"key":"finca",    "label":"Finca",    "prompt":"🏡 ¿Nombre de la finca?"}
    ]
  }

POST payload: {fecha, cantidad, finca, telefono, nombre}

Also adds the UI panel in bot-config with dynamic add/remove field rows.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-29 11:06:42 -05:00
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 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