Commit Graph
53 Commits
Author SHA1 Message Date
Lizandro GuarnizoandClaude Sonnet 4.6 c0fb2dc089 fix: rechazar rangos de fecha invertidos en ausentismo
El analisis de Gemini en Palmas360 marco un ausentismo con fecha final
anterior a la inicial: el bot lo dejaba pasar y el error recien aparecia
frente al revisor.

min_field valida entre campos al guardar, por los dos caminos (opcion de
lista y fecha escrita a mano). El aviso se envia aparte para poder volver a
mostrar el selector debajo.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-04 20:33:00 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 76ee42b96e feat: flujo de registro de pluviometria (Fase 1, carga de datos)
Segundo flujo de POST: fecha una sola vez y luego los milimetros de cada finca.

collect_for_each gana tres piezas que este flujo necesitaba:
- ask_date pregunta la fecha antes del bucle (Hoy/Ayer/Anteayer/Otra), en vez
  de repetirla por item o asumir hoy.
- exclude_values descarta el "Todas las fincas" (id 0) que el catalogo antepone
  para los informes y que aca no es una finca real.
- El payload pasa de un array plano a {fecha, fincas:[{id,label,valor}]}, que es
  lo que BotEntradaProcesador::pluviosidad() espera; ademas manda telefono y
  nombre, que el controller usa para la trazabilidad en bot_entrada.

Nota: el procesador ACTUALIZA la fila de pluviosidad del dia y falla si no
existe. Queda pendiente confirmar con Palmas360 quien las crea.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-04 20:22:03 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 0e72aa3d5a feat: flujo de registro de ausentismo (Fase 1, carga de datos)
Primer flujo de POST. El bot recolecta trabajador, motivo y rango de fechas, y
postea a ausentismos_up; Palmas360 decide si auto-aprueba o encola para
revision, como ya hace bot_entrada.

El campo lookup asumia que el endpoint devolvia un solo objeto, pero /empleados
devuelve una lista paginada: con eso el trabajador salia como "Desconocido" y
sin id. Ahora distingue lista de objeto, y si hay varias coincidencias las
ofrece para elegir en vez de quedarse con la primera.

Campos opcionales del contrato (eps, diagnostico_id) quedan fuera hasta
confirmar con Palmas360 que novedades son incapacidad.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-04 19:37:16 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 0bb1d3447e feat: bienvenida diaria; fix: el NLU filtraba JSON crudo al usuario
Bienvenida una vez por dia y por empresa, distinta por categoria (cat 1 no ve
ejemplos de descarga). Se manda aparte del retorno de process(), asi el usuario
recibe el saludo y acto seguido lo que pidio. __saludo sobrevive a
preservingReset para no repetirse tras cada informe.

Fix del leak: cuando la respuesta del modelo no parseaba como JSON se enviaba
cruda, y el usuario veia {"action":"chat","text":"...  Ahora se rescata solo el
texto, incluso de JSON truncado, y si no hay nada legible se devuelve vacio
para caer al menu de fallback.

Ademas el prompt le prohibe preguntar por datos que una opcion ya pide: ante
"descargar informe de mantenimiento" debe rutear al selector de grupo en vez de
preguntar por chat.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-01 11:06:35 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 a64421c527 feat: finca visible en cada menu, entities por campo y aviso de comandos
Entities:
- Se conserva el mapa {campo: valor} en vez de aplanarlo. El pre-llenado de
  formularios y el auto-registro de valores comparan la clave contra el nombre
  del campo, asi que con la lista plana nunca se disparaban.
- entity_key acota cada dynamic_list a su propio campo: antes una finca llamada
  "reposo" se comparaba contra grupos de mantenimiento y se descartaba ahi.
- Cada lista consume solo lo suyo, asi "finca reposo, grupo plateo" resuelve
  las dos cosas en una frase.

Finca visible:
- Se guarda la etiqueta ademas del id al elegir una opcion.
- El footer de cada menu muestra la finca activa; sin finca queda el nombre de
  la empresa, asi cat 1 no cambia. Los menus de botones ahora emiten footer.
- {finca} disponible en header, body y footer.

Aviso de comandos: los textos tras cada informe nombran menu, atras y salir.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-01 10:55:55 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 ce5d4ef2b2 fix: el NLU nunca atendia texto libre tras un informe; api_base_url apunta al ERP
preservingReset dejaba current_node en NULL, que process() no distingue de una
sesion nueva: se redisparaba el flujo de bienvenida (ask_finca), skip_if_set lo
saltaba al menu principal y el paso de NLU nunca corria. Por eso "elegir otra
finca" o "informe de mantenimiento de plateo" devolvian el menu. Ahora queda el
centinela __greeted, que marca sesion iniciada.

api_base_url guardaba el Graph API de WhatsApp, pero WhatsAppSender lo tiene
hardcodeado y todos los demas consumidores esperan el ERP.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-01 10:39:10 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 0e89258f67 feat: navegacion por niveles, intencion pendiente y NLU que aterriza en el informe
- "atras"/"volver"/"regresar" suben un nivel via mapa 'back' declarativo en el
  seed, en vez de saltar siempre al menu principal.
- "finca"/"cambiar finca" como comandos directos a reset_finca.
- requires/resolver: si el NLU rutea a un informe sin el dato que necesita, el
  bot lo pide y vuelve al informe original, no al submenu. Asi "informe de
  mantenimiento de plateo" entrega el PDF sin pasos intermedios.
- ciclo_mantenimiento consulta el catalogo completo cuando hay entities: los
  grupos fuera del top 3 tambien matchean.
- ask_finca con skip_if_set: deja de repreguntar la finca despues de cada
  informe; para cambiarla esta reset_finca.
- Boton "Otro grupo" en el submenu de mantenimiento.
- Fix: el auto-select por entities no mergeaba per_type y mandaba a cat 3 al
  menu equivocado al nombrar una finca.

validate_config.php verifica el grafo (back/resolver/botones/endpoints) y
test_navegacion.php recorre en seco los escenarios de ruteo.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-01 09:59:30 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 671fccd6b2 fix: mantenimiento PDF vacio — conservar grupo_mant y detectar placeholders
preservingReset borraba grupo_mant despues de cada informe, dejando
{grupo_id} sin resolver en la siguiente llamada del mismo submenu.
La API recibia grupo=0 y devolvia cero filas sin error visible.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-01 09:42:39 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 09db466fa6 fix: navegación — atras/cancelar/regresar + NLU desde menús activos
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-30 23:50:09 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 79d2399ddf fix: agregar comando 'salir' y limpiar __api_vars en reset de comandos
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-30 23:47:12 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 0ddab6adcf feat: NLU routing + fix greeting para cat2/cat3 + nlu_descriptions
- Cat 2 y cat 3: greeting '' (no null) para que greeting_flow: ask_finca
  se dispare en el primer mensaje y el usuario seleccione su finca
- nlu_enabled: true en config root activa el routing inteligente
- NormalBot: step 6 llama AiBot::routeOrChat() antes del fallback_flow;
  si retorna 'route' ejecuta el flow; si 'chat' responde en texto libre
- reset_finca: nuevo flow+función que limpia el meta de finca y relanza
  ask_finca; el NLU puede enrutar "quiero cambiar de finca" aquí
- nlu_description en todos los flows consultables por el NLU; nlu_skip
  en menus de navegación interna para no exponerlos al modelo
- AiBot::buildFlowCatalog: agrega upload_ciclo a isUpload para que cat 2
  no vea flujos de subida en el catálogo del NLU

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-30 23:03:25 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 fd907c8cc0 fix: __valid_ids usa strval para evitar mismatch int vs string
buildDynamicListResponse castea IDs a string al armar la lista de
WhatsApp. La comparación estricta in_array fallaba si el JSON devolvía
el id como int. Ahora __valid_ids siempre contiene strings.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-30 21:03:05 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 620ae3a1b3 fix: dynamic_list next_node busca en menus si no está en flows
Cuando el next_node de un collecting está en menus (no flows),
handleCollectingInput retornaba null sin respuesta. Ahora intenta
buildMenuResponse como fallback, lo que corrige el flujo de
selección de grupo en mantenimiento.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-30 20:55:40 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 f23283124a fix: preservar finca_id al resetear contexto tras generar reporte
El reset post-reporte borraba meta['finca']['finca_id'] causando que
todos los informes siguientes se generaran sin filtro de finca.

Nuevo helper preservingReset() restaura el grupo 'finca' después del
reset para que el usuario solo seleccione su finca una vez por sesión.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-30 17:58:10 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 2d98a9e7e0 fix(bot): enviar texto via WhatsAppSender antes de retornar PDF encadenado
El return de sendText() se descartaba; ahora se llama WhatsAppSender::sendText()
directamente para que el texto llegue antes del PDF/Excel.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-29 16:36:09 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 e3d9aac26d feat(bot): add last_7 date_mode y next_endpoint_key para texto+PDF en cadena
- last_7: fecha_desde = hoy-7 días, fecha_hasta = hoy (para ausentismos semana)
- next_endpoint_key en executeApiReport: si la primera llamada retorna JSON texto
  con status=1, envía el texto al usuario y luego ejecuta el endpoint siguiente
  (ej: produccion_total → texto inmediato + PDF a continuación)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-28 21:59:16 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 0df08bf953 feat: append_options en dynamic_list con redirect a otro flow
Permite añadir opciones fijas al final de una lista dinámica. Si el
usuario elige una de esas opciones, el bot redirige al flow con ese ID
en vez de guardar el valor como campo del formulario. Usado para el
botón "Ver más grupos" en el flujo de mantenimiento.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-28 20:29:51 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 91dd84cc8d feat: auto-select finca cuando la lista tiene un solo ítem
Si el endpoint de fincas devuelve exactamente 1 opción, el bot la
selecciona automáticamente sin mostrar la lista al usuario, y navega
directo al menú principal usando los flows del per_type activo.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-28 20:12:35 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 976f61b682 fix: match fuzzy de label en dynamic_list y collecting mid-flow
- handleDynamicList: usa str_contains bilateral para que "mantenimiento plateo"
  coincida con label "Plateo" (el NLU suele incluir palabras extra)
- handleCollectingInput: si el texto transcrito no es un ID válido, intenta
  match fuzzy contra __label_map guardado en el estado collecting
- handleDynamicList: guarda __label_map en el estado collecting para
  que mid-flow audio pueda auto-seleccionar sin ID exacto

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-05 22:03:57 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 3d04be14bc feat: auto-selección en dynamic_list cuando NLU extrae el grupo/filtro
- handleDynamicList: si __nlu_entities contiene un valor que coincide
  con el label de un ítem, lo auto-selecciona y navega al next_node
  sin mostrar la lista interactiva
- AiBot: NLU prompt ahora pide explícitamente grupos y filtros en entities

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-05 21:57:22 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 54a93adea9 feat: one-shot audio entity extraction y pre-fill de formularios
- AiBot: NLU ahora extrae entities (finca, valor, etc.) del audio
- BotRouter: guarda __nlu_entities en metadata antes de enrutar
- NormalBot: handleCollectAndPost pre-rellena campos desde entities
  y capAskNext salta los ya completados
- NormalBot: handleCollectForEach registra ítem directo si entities
  trae finca+valor coincidente, sin iterar todo el listado
- BotRouter: mid-flow audio va a NormalBot directamente (no NLU)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-05 21:48:48 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 1114c52df4 fix: wrap collected values in __ep_vars_combined for URL substitution
substituteUrlVars requires flat key-value pairs under __ep_vars_combined;
passing collected directly as meta skipped substitution since values are
strings not arrays, so {finca_id} was never replaced in the lotes URL.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-05 21:30:50 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 09342fa786 feat: pass collected values to fetchDynamicList for dependent selects
Enables select fields to filter their list based on previously collected
values via URL variable substitution (e.g. lotes filtered by finca_id).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-05 20:57:18 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 9509c3b4fc feat: add skip_if support to collect_and_post flow fields
Allows fields to declare a skip_if condition so the bot skips them
based on previously collected values — used for eps/diagnostico fields
that only apply when novedad is an incapacidad type.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-05 20:07:44 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 497743ceb1 feat: NLU fallback — route unrecognized text to AI in hybrid+NLU mode
NormalBot.process() gains suppressFallback param. When true, steps 7-8
(greeting menu fallback, static fallback text) return null instead of
responding, letting BotRouter hand off to NLU/AI.

BotRouter.runHybridBot() passes suppressFallback=$nluEnabled so that:
- NLU on: unrecognized text → NLU → route to flow or AI chat
- NLU off: existing fallback behavior unchanged
- Commands, active flows, first-session greeting: always unaffected

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-05 09:09:49 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 5883801989 fix: prepend api_base_url to relative endpoint URLs in NormalBot
After migration stripped base URLs from DB records, bot was calling
relative paths (e.g. /ruta) as-is without the base. buildUrl() helper
now prepends api_base_url for all four curl call sites.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-04 16:25:10 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 34322ae3dc Change "Reporte enviado" to "Reporte generado"
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 20:10:09 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 97a48d0fe4 Validate dynamic_list input and fix cosecha button IDs
- dynamic_list now stores valid IDs in collecting state; rejects
  free-text input that doesn't match a list option
- Fixed submenu_ciclo_cosecha button IDs to match real flow names
  (ciclo_cosecha_dia / ciclo_cosecha_historico)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 20:07:52 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 0ce766a9eb feat: kilos por lotes con selección de período (mes actual/anterior/personalizado)
- Add last_month date_mode to executeApiReport
- prod_kilos_lotes now shows period menu instead of immediately asking dates
- lotes_mes_actual / lotes_mes_anterior use api_report with date_mode
- lotes_personalizado collects fecha_desde + fecha_hasta (2 messages only when needed)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 19:35:12 -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 e9bbf8efce feat: per-type greeting_menu takes priority over global show_main_menu
When user says "menu/inicio/volver", bot now shows the menu configured
for their category (per_type.greeting_menu) instead of the global
show_main_menu. Same redirect applies to interactive button taps.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 16:54:47 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 6bac0ee823 fix: dynamic list body fallback uses ?: to handle empty string
WhatsApp rejects list messages with empty body.text. Using ?? missed
empty string from config; ?: correctly falls back to default.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 16:32:58 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 322aa287dc fix: fetchDynamicList accepts 'datos' key (Palmas360 API convention)
API returns {status,datos:[...]} but bot was looking for {data:[...]},
causing empty rows and dynamic lists never rendering.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 16:19:25 -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 16a03b546d fix: commands clear in-progress state + per_type config rebuilt in DB
Two fixes:
1. Any command (menu, salir, etc.) now clears collecting/__foreach/__cap
   meta before executing, so navigation commands are always a clean reset
   regardless of what flow the user was in.

2. DB per_type was empty ([]); rebuilt with correct structure:
   - Type 1: greeting=submenu_subir, menu/inicio/volver → recibir_info
   - Type 2: greeting=submenu_informes, menu/inicio/volver → descargar_informes
   - Type 3: greeting=show_main_menu, inherits global commands

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-29 11:42:56 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 b2abece097 fix: per-type commands extend globals instead of replacing them
Changed commands merge from ?? (replace) to array_merge (extend) so that
per-type overrides for menu/inicio/volver are applied without losing the
global salir, exit, informes, info commands.

Also updated DB config per_type:
- Type 1 (subir info): menu/inicio/volver → recibir_info → submenu_subir
- Type 2 (descargar): menu/inicio/volver → descargar_informes → submenu_informes
- Type 3 (admin): menu → show_main_menu (inherits global)

Each category now lands on its own menu from the first message.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-29 11:25:15 -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 b45f0fbe51 feat: add collect_for_each flow type for sequential per-item data entry
New flow type that fetches a dynamic list from an endpoint then asks
the user for a value for each item one by one. On completion shows a
summary with confirm/cancel buttons, then POSTs all collected data as
a JSON array to a configured upload endpoint with Bearer + X-API-Key auth.

Usage in flow config:
  {
    "type": "collect_for_each",
    "source_endpoint_key": "fincas_list",
    "endpoint_key": "pluvio_upload",
    "value_field": "id",
    "label_field": "name",
    "question": "¿Cuál es el valor de pluviometría? (mm)",
    "header": "🌧 Pluviometría",
    "success_text": " Pluviometría registrada."
  }

POST payload: [{id, label, valor}, ...]

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-29 10:36:44 -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 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 b6a85c33f7 fix: salir button works, cap at 3 buttons, no AI error on unmatched input
- Add 'goodbye' function: resets context + sends farewell with menu hint
- Add safety slice to 3 buttons in buildMenuResponse (WhatsApp hard limit)
- In hybrid mode: when greeting_menu is configured for the user's category,
  use it as fallback instead of escalating to AI — prevents "Lo siento,
  tengo problemas..." from appearing when nothing matches
- Expose buildGreetingMenu() as public for BotRouter access
- DB: fixed submenu_informes and submenu_ciclos from 4→3 buttons,
  added salir flow (goodbye function) and salir/exit commands (company 2)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-28 13:10:08 -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 0b851208ea fix: show navigation hint after api_report completes or fails
After delivering a report (or on any error), send a text message telling
the user to write "menu" or "salir" so they are never left without guidance.
Also return the hint as a proper response on success (was returning null).

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