- 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>
- 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>
- 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>
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>
- 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>
- 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>
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>
- 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>
The main save rebuilt config from scratch — per_type always started empty,
so clicking Guardar wiped whatever savePerType() had just stored.
Now: load existing DB config first, use its per_type as the base, apply
POST per_type_menu on top only when it is present. Same for welcome_menu.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
- 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>
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>
- 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>
- Cast erp_active_users (and requires_approval, is_active) to int
before INSERT/UPDATE so empty string '' doesn't fail the INT column
- Fix error redirect for new company: was /admin/companies/new (404),
now /admin/company/edit (the actual new-company route)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 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>
- 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>
- 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>
- 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>
- 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>
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>
Nginx intercepts any request to /admin/chat/* sub-routes. Fix by handling
all chat API requests on the same /admin/chat path using query params:
GET /admin/chat?api=convs → conversations list
GET /admin/chat?api=msgs&phone=X → messages for a phone
POST /admin/chat → send message
JS fetch URLs updated accordingly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
/admin/chat/conversations → /admin/chat-convs
/admin/chat/messages → /admin/chat-msgs
/admin/chat/send → /admin/chat-send
The production server was intercepting all subpaths of /admin/chat and
redirecting them back to /admin/chat, so fetch never received JSON.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 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>
- 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>
- 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>
- 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>
- 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>