Los submenus estaban marcados nlu_skip para que la IA no ruteara a navegacion,
pero eso dejo solo las hojas en el catalogo: "ausentismo" tenia cuatro
candidatos y ninguno se llamaba asi, "sanidad" ninguno, "produccion" dos. Como
no podia elegir, caia al fallback y aparecia el menu principal, que se siente
como que ignoro el mensaje.
Las cuatro categorias vuelven a ser ruteables. Para que el filtro de permisos
las alcance —por tipo un menu no es ni subida ni descarga— se agrega nlu_dir,
con lo que cat 1 solo ve registrar y cat 2 solo consultar, y la ambiguedad se
resuelve sola. Cat 3 ve las dos, asi que tiene un menu que pregunta cual.
Cuando aun asi no rutea, ahora avisa antes de mostrar el menu.
Se quita el aprendizaje por usuario: el orden por uso lo resuelve Palmas360
sobre los registros reales, y dos criterios compitiendo sobraba.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
El catalogo llega ordenado alfabeticamente y son 17 opciones, asi que el motivo
habitual quedaba en cualquier lado y a veces en la segunda pagina.
El bot cuenta que elige cada usuario y los sube. Es por usuario y no global a
proposito: quien registra incapacidades registra sobre todo incapacidades, y
eso le sirve mas que un promedio de toda la operacion.
El orden de PHP 8 es estable, asi que los empates conservan el alfabetico. El
contador se limita a 20 entradas y sobrevive al reset que corre tras cada
registro. Va como opcion por campo (frecuentes) para no reordenar listas donde
el alfabetico importa, como los lotes.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Al sacar subir_ciclo_cosecha quedo nombrando solo ausentismo, cuando cat 1
tambien abre y cierra ciclos y carga pluviometria: eso tambien es reportar.
La prueba buscaba la frase vieja "subir ciclo"; ahora verifica los tres
flujos por separado.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Ultimo flujo de Fase 1. Pregunta ciclo, accion, fecha y lotes, y postea a
ciclos_{ciclo}_up con lote_ids[], que es el contrato que quedo en el ERP.
Pieza nueva del motor, multi_select: la lista acumula, cada toque marca o
desmarca con un check en la etiqueta, y "Listo" cierra. WhatsApp no tiene
casillas, asi que el estado se muestra redibujando. De las 10 filas, dos van
para "Ver mas" y "Listo".
Tambien resolverPorCampos(), que reemplaza {campo} con lo ya elegido tanto en
la clave del endpoint como en la URL del POST. Con eso los seis tipos de ciclo
y las dos acciones comparten tres entradas de endpoint en vez de dieciocho:
apertura ofrece los lotes de la finca y cierre solo los que estan abiertos.
Mantenimiento no aparece: no tiene tabla de ciclos ni apertura/cierre, es una
labor y va con Fase 2.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
buildDynamicListResponse cortaba en 10 filas con array_slice y descartaba el
resto sin aviso, asi que el catalogo de motivos mostraba 10 de 17 y las otras
7 no habia forma de elegirlas.
Ahora pagina: 9 opciones y "Ver mas" con cuantas faltan, hasta agotarlas. El
10 dejo de estar suelto en el codigo y quedo como LISTA_MAX_FILAS, que es el
limite de WhatsApp de donde salio el problema.
Solo cubre los campos select; dynamic_list lo va a necesitar para los lotes de
Ciclos y va con ese trabajo.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
El selector de fecha que agregue a collect_for_each responde por lista, pero el
bloque __foreach de processInteractive solo contemplaba confirmar y cancelar:
la fecha caia en la resolucion de menus estaticos, no coincidia con ninguno y
el flujo moria sin mensaje.
Hasta ahora collect_for_each solo recibia numeros escritos, por eso nunca hizo
falta ese ruteo. Solo se desvia mientras se espera la fecha, para no secuestrar
la navegacion por menus durante el resto del bucle.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
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>
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>
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>
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>
- "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>