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>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
693623e10c
commit
a64421c527
+101
-36
@@ -50,7 +50,7 @@ class NormalBot
|
|||||||
}
|
}
|
||||||
// Commands that match a menu
|
// Commands that match a menu
|
||||||
if (isset($menus[$action])) {
|
if (isset($menus[$action])) {
|
||||||
return self::buildMenuResponse($menus[$action], $context['from'], $company);
|
return self::buildMenuResponse($menus[$action], $context['from'], $company, $ctxId);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -91,14 +91,14 @@ class NormalBot
|
|||||||
$welcomeMenuKey = $config['welcome_menu'] ?? null;
|
$welcomeMenuKey = $config['welcome_menu'] ?? null;
|
||||||
if ($welcomeMenuKey && $currentNode === null && isset($menus[$welcomeMenuKey])) {
|
if ($welcomeMenuKey && $currentNode === null && isset($menus[$welcomeMenuKey])) {
|
||||||
ConversationContext::updateNode($ctxId, '__greeted');
|
ConversationContext::updateNode($ctxId, '__greeted');
|
||||||
return self::buildMenuResponse($menus[$welcomeMenuKey], $context['from'], $company);
|
return self::buildMenuResponse($menus[$welcomeMenuKey], $context['from'], $company, $ctxId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4b. Per-type greeting menu — only for brand-new sessions (currentNode === null)
|
// 4b. Per-type greeting menu — only for brand-new sessions (currentNode === null)
|
||||||
$greetingMenuKey = $perType['greeting_menu'] ?? null;
|
$greetingMenuKey = $perType['greeting_menu'] ?? null;
|
||||||
if ($greetingMenuKey !== null && $currentNode === null && isset($menus[$greetingMenuKey])) {
|
if ($greetingMenuKey !== null && $currentNode === null && isset($menus[$greetingMenuKey])) {
|
||||||
ConversationContext::updateNode($ctxId, '__greeted'); // sentinel: evita re-envío del menú
|
ConversationContext::updateNode($ctxId, '__greeted'); // sentinel: evita re-envío del menú
|
||||||
return self::buildMenuResponse($menus[$greetingMenuKey], $context['from'], $company);
|
return self::buildMenuResponse($menus[$greetingMenuKey], $context['from'], $company, $ctxId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. Greeting text
|
// 5. Greeting text
|
||||||
@@ -127,19 +127,22 @@ class NormalBot
|
|||||||
ConversationContext::updateMetadata($ctxId, $m);
|
ConversationContext::updateMetadata($ctxId, $m);
|
||||||
$key = 'ask_finca';
|
$key = 'ask_finca';
|
||||||
}
|
}
|
||||||
// Inyectar entities para auto-select en dynamic_list
|
// Inyectar entities para auto-select en dynamic_list.
|
||||||
|
// Se guarda el mapa completo: los formularios las buscan por nombre
|
||||||
|
// de campo, y aplanarlas dejaba ese pre-llenado sin funcionar.
|
||||||
|
$m = ConversationContext::getMetadata($ctxId);
|
||||||
|
unset($m['__nlu_entities']); // descartar sobras de un ruteo anterior
|
||||||
if (!empty($route['entities'])) {
|
if (!empty($route['entities'])) {
|
||||||
$m = ConversationContext::getMetadata($ctxId);
|
$m['__nlu_entities'] = $route['entities'];
|
||||||
$m['__nlu_entities'] = array_values($route['entities']);
|
|
||||||
ConversationContext::updateMetadata($ctxId, $m);
|
|
||||||
}
|
}
|
||||||
|
ConversationContext::updateMetadata($ctxId, $m);
|
||||||
if (isset($flows[$key])) {
|
if (isset($flows[$key])) {
|
||||||
ConversationContext::updateNode($ctxId, $key);
|
ConversationContext::updateNode($ctxId, $key);
|
||||||
return self::handleFlow($flows[$key], $context, $company, $ctxId, $menus, $key);
|
return self::handleFlow($flows[$key], $context, $company, $ctxId, $menus, $key);
|
||||||
}
|
}
|
||||||
if (isset($menus[$key])) {
|
if (isset($menus[$key])) {
|
||||||
ConversationContext::updateNode($ctxId, $key);
|
ConversationContext::updateNode($ctxId, $key);
|
||||||
return self::buildMenuResponse($menus[$key], $context['from'], $company);
|
return self::buildMenuResponse($menus[$key], $context['from'], $company, $ctxId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($route['action'] === 'chat' && ($route['text'] ?? '') !== '') {
|
if ($route['action'] === 'chat' && ($route['text'] ?? '') !== '') {
|
||||||
@@ -166,7 +169,7 @@ class NormalBot
|
|||||||
// 8. Greeting menu as fallback (texto no reconocido después de estar en __greeted)
|
// 8. Greeting menu as fallback (texto no reconocido después de estar en __greeted)
|
||||||
if (!$suppressFallback && $greetingMenuKey !== null && isset($menus[$greetingMenuKey])) {
|
if (!$suppressFallback && $greetingMenuKey !== null && isset($menus[$greetingMenuKey])) {
|
||||||
ConversationContext::updateNode($ctxId, '__greeted');
|
ConversationContext::updateNode($ctxId, '__greeted');
|
||||||
return self::buildMenuResponse($menus[$greetingMenuKey], $context['from'], $company);
|
return self::buildMenuResponse($menus[$greetingMenuKey], $context['from'], $company, $ctxId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 9. Static fallback text
|
// 9. Static fallback text
|
||||||
@@ -227,9 +230,12 @@ class NormalBot
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
$formData = $meta[$group] ?? [];
|
// Se recupera la etiqueta del id elegido para poder mostrarla después
|
||||||
$formData[$key] = $resolved;
|
$etiqueta = '';
|
||||||
$meta[$group] = $formData;
|
foreach ($labelMap as $label => $id) {
|
||||||
|
if ((string)$id === $resolved) { $etiqueta = (string)$label; break; }
|
||||||
|
}
|
||||||
|
$meta = self::guardarSeleccion($meta, $group, $key, $resolved, $etiqueta);
|
||||||
unset($meta['collecting']);
|
unset($meta['collecting']);
|
||||||
ConversationContext::updateMetadata($ctxId, $meta);
|
ConversationContext::updateMetadata($ctxId, $meta);
|
||||||
|
|
||||||
@@ -239,7 +245,7 @@ class NormalBot
|
|||||||
return self::handleFlow($flows[$nextNode], $context, $company, $ctxId, $menus, $nextNode);
|
return self::handleFlow($flows[$nextNode], $context, $company, $ctxId, $menus, $nextNode);
|
||||||
}
|
}
|
||||||
if (isset($menus[$nextNode])) {
|
if (isset($menus[$nextNode])) {
|
||||||
return self::buildMenuResponse($menus[$nextNode], $context['from'], $company);
|
return self::buildMenuResponse($menus[$nextNode], $context['from'], $company, $ctxId);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ConversationContext::updateNode($ctxId, null);
|
ConversationContext::updateNode($ctxId, null);
|
||||||
@@ -301,39 +307,53 @@ class NormalBot
|
|||||||
|
|
||||||
// Auto-select cuando hay exactamente 1 opción — no mostrar lista
|
// Auto-select cuando hay exactamente 1 opción — no mostrar lista
|
||||||
if (count($items) === 1) {
|
if (count($items) === 1) {
|
||||||
$formData = $meta[$group] ?? [];
|
$meta = self::guardarSeleccion($meta, $group, $key, (string)($items[0][$valueField] ?? ''), (string)($items[0][$labelField] ?? ''));
|
||||||
$formData[$key] = (string)($items[0][$valueField] ?? '');
|
|
||||||
$meta[$group] = $formData;
|
|
||||||
ConversationContext::updateMetadata($ctxId, $meta);
|
ConversationContext::updateMetadata($ctxId, $meta);
|
||||||
return self::advanceAfterSelect($nextNode, $context, $company, $ctxId);
|
return self::advanceAfterSelect($nextNode, $context, $company, $ctxId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Si el NLU extrajo entities, intentar auto-seleccionar sin mostrar lista
|
// Si el NLU extrajo entities, intentar auto-seleccionar sin mostrar lista.
|
||||||
$entities = $meta['__nlu_entities'] ?? [];
|
// entity_key acota la búsqueda al campo propio: sin eso, una finca llamada
|
||||||
|
// "reposo" se compara contra grupos de mantenimiento y se consume al pedo.
|
||||||
|
$entities = $meta['__nlu_entities'] ?? [];
|
||||||
|
$entityKey = $flow['entity_key'] ?? '';
|
||||||
if (!empty($entities)) {
|
if (!empty($entities)) {
|
||||||
$matchedId = null;
|
$propias = [];
|
||||||
foreach ($entities as $ev) {
|
if ($entityKey !== '') {
|
||||||
|
foreach ($entities as $ek => $ev) {
|
||||||
|
if (self::normalize((string)$ek) === self::normalize($entityKey)) $propias[$ek] = $ev;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$propias = $entities; // flow sin entity_key: comportamiento histórico
|
||||||
|
}
|
||||||
|
|
||||||
|
$matchedId = $matchedLabel = $matchedEk = null;
|
||||||
|
foreach ($propias as $ek => $ev) {
|
||||||
$evNorm = self::normalize((string)$ev);
|
$evNorm = self::normalize((string)$ev);
|
||||||
if ($evNorm === '') continue;
|
if ($evNorm === '') continue;
|
||||||
foreach ($labelMap as $label => $id) {
|
foreach ($labelMap as $label => $id) {
|
||||||
$lNorm = self::normalize($label);
|
$lNorm = self::normalize($label);
|
||||||
if ($lNorm === '') continue;
|
if ($lNorm === '') continue;
|
||||||
if ($lNorm === $evNorm || str_contains($evNorm, $lNorm) || str_contains($lNorm, $evNorm)) {
|
if ($lNorm === $evNorm || str_contains($evNorm, $lNorm) || str_contains($lNorm, $evNorm)) {
|
||||||
$matchedId = $id;
|
$matchedId = $id; $matchedLabel = $label; $matchedEk = $ek;
|
||||||
break 2;
|
break 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($matchedId !== null) {
|
if ($matchedId !== null) {
|
||||||
$formData = $meta[$group] ?? [];
|
$meta = self::guardarSeleccion($meta, $group, $key, $matchedId, (string)$matchedLabel);
|
||||||
$formData[$key] = $matchedId;
|
unset($meta['__nlu_entities'][$matchedEk]); // solo la consumida
|
||||||
$meta[$group] = $formData;
|
if (empty($meta['__nlu_entities'])) unset($meta['__nlu_entities']);
|
||||||
ConversationContext::updateMetadata($ctxId, $meta);
|
ConversationContext::updateMetadata($ctxId, $meta);
|
||||||
return self::advanceAfterSelect($nextNode, $context, $company, $ctxId);
|
return self::advanceAfterSelect($nextNode, $context, $company, $ctxId);
|
||||||
}
|
}
|
||||||
// Sin match: se descartan las entities y se muestra la lista normal
|
|
||||||
unset($meta['__nlu_entities']);
|
// Sin match: las entities de otro campo siguen sirviendo más adelante
|
||||||
ConversationContext::updateMetadata($ctxId, $meta);
|
if ($entityKey === '') {
|
||||||
|
unset($meta['__nlu_entities']);
|
||||||
|
ConversationContext::updateMetadata($ctxId, $meta);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Opciones fijas que redirigen a otro flow en vez de guardar el valor
|
// Opciones fijas que redirigen a otro flow en vez de guardar el valor
|
||||||
@@ -363,14 +383,30 @@ class NormalBot
|
|||||||
return self::buildDynamicListResponse($items, $flow, $context['from'], $company);
|
return self::buildDynamicListResponse($items, $flow, $context['from'], $company);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Guarda la opción elegida con su etiqueta, para poder mostrarle al usuario
|
||||||
|
* en qué finca/grupo está parado sin tener que volver a consultar la API.
|
||||||
|
*/
|
||||||
|
private static function guardarSeleccion(array $meta, string $group, string $key, string $id, string $label): array
|
||||||
|
{
|
||||||
|
if ($group === '') return $meta;
|
||||||
|
$datos = $meta[$group] ?? [];
|
||||||
|
$datos[$key] = $id;
|
||||||
|
if ($label !== '') $datos[$group . '_label'] = $label;
|
||||||
|
$meta[$group] = $datos;
|
||||||
|
return $meta;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Continúa tras resolver una selección: consume la intención pendiente y
|
* Continúa tras resolver una selección: consume la intención pendiente y
|
||||||
* las entities, y ejecuta el destino.
|
* las entities, y ejecuta el destino.
|
||||||
*/
|
*/
|
||||||
private static function advanceAfterSelect(?string $nextNode, array $context, array $company, int $ctxId): ?array
|
private static function advanceAfterSelect(?string $nextNode, array $context, array $company, int $ctxId): ?array
|
||||||
{
|
{
|
||||||
|
// Las entities que no consumió esta lista pueden ser de la siguiente
|
||||||
|
// ("finca reposo, grupo plateo" resuelve las dos), así que no se borran acá.
|
||||||
$meta = ConversationContext::getMetadata($ctxId);
|
$meta = ConversationContext::getMetadata($ctxId);
|
||||||
unset($meta['__after_select'], $meta['__nlu_entities']);
|
unset($meta['__after_select']);
|
||||||
ConversationContext::updateMetadata($ctxId, $meta);
|
ConversationContext::updateMetadata($ctxId, $meta);
|
||||||
|
|
||||||
if ($nextNode !== null) {
|
if ($nextNode !== null) {
|
||||||
@@ -380,7 +416,7 @@ class NormalBot
|
|||||||
return self::handleFlow($flows[$nextNode], $context, $company, $ctxId, $menus, $nextNode);
|
return self::handleFlow($flows[$nextNode], $context, $company, $ctxId, $menus, $nextNode);
|
||||||
}
|
}
|
||||||
if (isset($menus[$nextNode])) {
|
if (isset($menus[$nextNode])) {
|
||||||
return self::buildMenuResponse($menus[$nextNode], $context['from'], $company);
|
return self::buildMenuResponse($menus[$nextNode], $context['from'], $company, $ctxId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -659,7 +695,7 @@ class NormalBot
|
|||||||
{
|
{
|
||||||
$fe = $meta['__foreach'];
|
$fe = $meta['__foreach'];
|
||||||
$endpointKey = $fe['endpoint_key'] ?? '';
|
$endpointKey = $fe['endpoint_key'] ?? '';
|
||||||
$nav = "\n\nEscribe *menu* para ver más opciones.";
|
$nav = "\n\nEscribe *menú* para volver al inicio, *atrás* para subir un nivel o *salir* para terminar.";
|
||||||
|
|
||||||
// Build POST body: array of {id, valor} objects
|
// Build POST body: array of {id, valor} objects
|
||||||
$body = [];
|
$body = [];
|
||||||
@@ -1039,7 +1075,7 @@ class NormalBot
|
|||||||
{
|
{
|
||||||
$cap = $meta['__cap'];
|
$cap = $meta['__cap'];
|
||||||
$endpointKey = $cap['endpoint_key'] ?? '';
|
$endpointKey = $cap['endpoint_key'] ?? '';
|
||||||
$nav = "\n\nEscribe *menu* para ver más opciones.";
|
$nav = "\n\nEscribe *menú* para volver al inicio, *atrás* para subir un nivel o *salir* para terminar.";
|
||||||
|
|
||||||
unset($meta['__cap']);
|
unset($meta['__cap']);
|
||||||
ConversationContext::updateMetadata($ctxId, $meta);
|
ConversationContext::updateMetadata($ctxId, $meta);
|
||||||
@@ -1187,7 +1223,7 @@ class NormalBot
|
|||||||
return match ($type) {
|
return match ($type) {
|
||||||
'text' => self::sendText($flow['message'] ?? '', $context['from'], $company),
|
'text' => self::sendText($flow['message'] ?? '', $context['from'], $company),
|
||||||
'image' => self::sendImage($flow['media_id'] ?? '', $flow['caption'] ?? null, $context['from'], $company),
|
'image' => self::sendImage($flow['media_id'] ?? '', $flow['caption'] ?? null, $context['from'], $company),
|
||||||
'menu' => self::buildMenuResponse(self::resolveMenu($flow['menu'] ?? [], $company, $menus), $context['from'], $company),
|
'menu' => self::buildMenuResponse(self::resolveMenu($flow['menu'] ?? [], $company, $menus), $context['from'], $company, $ctxId),
|
||||||
'function' => self::executeFunction($flow['function'] ?? '', $flow['params'] ?? [], $context, $company, $ctxId),
|
'function' => self::executeFunction($flow['function'] ?? '', $flow['params'] ?? [], $context, $company, $ctxId),
|
||||||
'collect_input' => self::handleCollectInput($flow, $context, $company, $ctxId),
|
'collect_input' => self::handleCollectInput($flow, $context, $company, $ctxId),
|
||||||
'dynamic_list' => self::handleDynamicList($flow, $context, $company, $ctxId),
|
'dynamic_list' => self::handleDynamicList($flow, $context, $company, $ctxId),
|
||||||
@@ -1269,7 +1305,7 @@ class NormalBot
|
|||||||
// ── Handles sequential collection of URL vars before calling endpoint ───
|
// ── Handles sequential collection of URL vars before calling endpoint ───
|
||||||
private static function handleApiVarsInput(array $meta, string $input, array $context, array $company, int $ctxId, array $flows, array $menus): ?array
|
private static function handleApiVarsInput(array $meta, string $input, array $context, array $company, int $ctxId, array $flows, array $menus): ?array
|
||||||
{
|
{
|
||||||
$nav = "\n\nEscribe *menu* para ver opciones o *salir* para terminar.";
|
$nav = "\n\nEscribe *menú* para volver al inicio, *atrás* para subir un nivel o *salir* para terminar.";
|
||||||
$state = $meta['__api_vars'];
|
$state = $meta['__api_vars'];
|
||||||
|
|
||||||
$pending = $state['pending'] ?? [];
|
$pending = $state['pending'] ?? [];
|
||||||
@@ -1306,7 +1342,7 @@ class NormalBot
|
|||||||
private static function executeApiReport(array $params, array $context, array $company, int $ctxId): ?array
|
private static function executeApiReport(array $params, array $context, array $company, int $ctxId): ?array
|
||||||
{
|
{
|
||||||
$endpointKey = $params['endpoint_key'] ?? '';
|
$endpointKey = $params['endpoint_key'] ?? '';
|
||||||
$nav = "\n\nEscribe *menu* para ver más opciones o *salir* para terminar.";
|
$nav = "\n\nEscribe *menú* para volver al inicio, *atrás* para subir un nivel o *salir* para terminar.";
|
||||||
|
|
||||||
if ($endpointKey === '') {
|
if ($endpointKey === '') {
|
||||||
self::preservingReset($ctxId, ConversationContext::getMetadata($ctxId));
|
self::preservingReset($ctxId, ConversationContext::getMetadata($ctxId));
|
||||||
@@ -1634,7 +1670,7 @@ class NormalBot
|
|||||||
}
|
}
|
||||||
if (isset($menus[$resolvedInput])) {
|
if (isset($menus[$resolvedInput])) {
|
||||||
ConversationContext::updateNode($ctxId, $resolvedInput);
|
ConversationContext::updateNode($ctxId, $resolvedInput);
|
||||||
return self::buildMenuResponse($menus[$resolvedInput], $context['from'], $company);
|
return self::buildMenuResponse($menus[$resolvedInput], $context['from'], $company, $ctxId);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@@ -1642,8 +1678,33 @@ class NormalBot
|
|||||||
|
|
||||||
// ── WhatsApp senders ─────────────────────────────────────────────────────
|
// ── WhatsApp senders ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
private static function buildMenuResponse(array $menu, string $to, array $company): ?array
|
/**
|
||||||
|
* Resuelve {finca} en los textos del menú y deja la finca activa en el footer,
|
||||||
|
* para que el usuario sepa siempre sobre qué está consultando.
|
||||||
|
*/
|
||||||
|
private static function aplicarVarsMenu(array $menu, ?int $ctxId, array $company): array
|
||||||
{
|
{
|
||||||
|
if ($ctxId === null) return $menu;
|
||||||
|
|
||||||
|
$meta = ConversationContext::getMetadata($ctxId);
|
||||||
|
$finca = (string)($meta['finca']['finca_label'] ?? '');
|
||||||
|
|
||||||
|
foreach (['header', 'body', 'footer'] as $campo) {
|
||||||
|
if (isset($menu[$campo]) && is_string($menu[$campo])) {
|
||||||
|
$menu[$campo] = str_replace('{finca}', $finca, $menu[$campo]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sin finca (cat 1 solo reporta) queda el footer de siempre
|
||||||
|
if ($finca !== '' && !str_contains((string)($menu['footer'] ?? ''), $finca)) {
|
||||||
|
$menu['footer'] = '📍 ' . $finca;
|
||||||
|
}
|
||||||
|
return $menu;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function buildMenuResponse(array $menu, string $to, array $company, ?int $ctxId = null): ?array
|
||||||
|
{
|
||||||
|
$menu = self::aplicarVarsMenu($menu, $ctxId, $company);
|
||||||
$menuType = $menu['type'] ?? 'list';
|
$menuType = $menu['type'] ?? 'list';
|
||||||
|
|
||||||
if ($menuType === 'list') {
|
if ($menuType === 'list') {
|
||||||
@@ -1696,6 +1757,10 @@ class NormalBot
|
|||||||
'body' => ['text' => mb_substr($menu['body'] ?? 'Selecciona:', 0, 1024)],
|
'body' => ['text' => mb_substr($menu['body'] ?? 'Selecciona:', 0, 1024)],
|
||||||
'action' => ['buttons' => $buttons],
|
'action' => ['buttons' => $buttons],
|
||||||
];
|
];
|
||||||
|
// Los submenús de ciclos son de botones: sin footer no se veía la finca
|
||||||
|
if (($menu['footer'] ?? '') !== '') {
|
||||||
|
$interactive['footer'] = ['text' => mb_substr($menu['footer'], 0, 60)];
|
||||||
|
}
|
||||||
|
|
||||||
return self::enqueueInteractive($to, $interactive, $company);
|
return self::enqueueInteractive($to, $interactive, $company);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -213,6 +213,7 @@ $configJson = [
|
|||||||
'section_title' => 'Fincas disponibles',
|
'section_title' => 'Fincas disponibles',
|
||||||
'meta_group' => 'finca',
|
'meta_group' => 'finca',
|
||||||
'meta_key' => 'finca_id',
|
'meta_key' => 'finca_id',
|
||||||
|
'entity_key' => 'finca',
|
||||||
'next_node' => 'show_main_menu',
|
'next_node' => 'show_main_menu',
|
||||||
'skip_if_set' => true,
|
'skip_if_set' => true,
|
||||||
'nlu_description' => 'Seleccionar o cambiar la finca de trabajo',
|
'nlu_description' => 'Seleccionar o cambiar la finca de trabajo',
|
||||||
@@ -322,6 +323,7 @@ $configJson = [
|
|||||||
'section_title' => 'Grupos frecuentes',
|
'section_title' => 'Grupos frecuentes',
|
||||||
'meta_group' => 'grupo_mant',
|
'meta_group' => 'grupo_mant',
|
||||||
'meta_key' => 'grupo_id',
|
'meta_key' => 'grupo_id',
|
||||||
|
'entity_key' => 'grupo',
|
||||||
'next_node' => 'submenu_ciclo_mantenimiento',
|
'next_node' => 'submenu_ciclo_mantenimiento',
|
||||||
'append_options' => [['id' => 'select_grupo_mant_otros', 'title' => '➕ Otro grupo']],
|
'append_options' => [['id' => 'select_grupo_mant_otros', 'title' => '➕ Otro grupo']],
|
||||||
'nlu_description' => 'Informes de mantenimiento de un grupo (plateo, corona, poda...) — usar cuando NO dice qué informe quiere',
|
'nlu_description' => 'Informes de mantenimiento de un grupo (plateo, corona, poda...) — usar cuando NO dice qué informe quiere',
|
||||||
|
|||||||
@@ -145,6 +145,69 @@ check('parado en un menú el NLU atiende',
|
|||||||
check('en medio de una lista no interfiere',
|
check('en medio de una lista no interfiere',
|
||||||
$atiende('collecting', '', null, $flows), 'nlu');
|
$atiende('collecting', '', null, $flows), 'nlu');
|
||||||
|
|
||||||
|
echo "\nEntities por campo\n";
|
||||||
|
|
||||||
|
/** Espejo del matching de handleDynamicList con entity_key */
|
||||||
|
$matchear = function (array $flow, array $entities, array $etiquetas): array {
|
||||||
|
$ek = $flow['entity_key'] ?? '';
|
||||||
|
$propias = [];
|
||||||
|
if ($ek !== '') {
|
||||||
|
foreach ($entities as $k => $v) if (mb_strtolower($k) === mb_strtolower($ek)) $propias[$k] = $v;
|
||||||
|
} else {
|
||||||
|
$propias = $entities;
|
||||||
|
}
|
||||||
|
foreach ($propias as $k => $v) {
|
||||||
|
foreach ($etiquetas as $etiqueta) {
|
||||||
|
$a = mb_strtolower($v); $b = mb_strtolower($etiqueta);
|
||||||
|
if ($a === $b || str_contains($a, $b) || str_contains($b, $a)) {
|
||||||
|
unset($entities[$k]);
|
||||||
|
return ['match' => $etiqueta, 'sobran' => $entities];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ['match' => null, 'sobran' => $entities];
|
||||||
|
};
|
||||||
|
|
||||||
|
$dichas = ['finca' => 'reposo', 'grupo' => 'plateo'];
|
||||||
|
$fincas = ['ROSA BLANCA', 'REPOSO', '🌐 Todas las fincas'];
|
||||||
|
$grupos = ['PLATEO', 'CORONA', 'PODA'];
|
||||||
|
|
||||||
|
// Cada lista toma lo suyo y deja el resto para la siguiente
|
||||||
|
$r1 = $matchear($flows['ask_finca'], $dichas, $fincas);
|
||||||
|
check('ask_finca toma la finca', $r1['match'], 'REPOSO');
|
||||||
|
check('y deja el grupo para después', $r1['sobran'], ['grupo' => 'plateo']);
|
||||||
|
|
||||||
|
$r2 = $matchear($flows['ciclo_mantenimiento'], $r1['sobran'], $grupos);
|
||||||
|
check('ciclo_mantenimiento toma el grupo', $r2['match'], 'PLATEO');
|
||||||
|
check('no queda nada sin consumir', $r2['sobran'], []);
|
||||||
|
|
||||||
|
// El bug que motivó todo: una finca no debe matchear contra grupos
|
||||||
|
$r3 = $matchear($flows['ciclo_mantenimiento'], ['finca' => 'reposo'], $grupos);
|
||||||
|
check('una finca no se consume como grupo', $r3['match'], null);
|
||||||
|
check('y sobrevive para ask_finca', $r3['sobran'], ['finca' => 'reposo']);
|
||||||
|
|
||||||
|
echo "\nFinca visible en el menú\n";
|
||||||
|
|
||||||
|
/** Espejo de aplicarVarsMenu() */
|
||||||
|
$footer = function (array $menu, string $finca): string {
|
||||||
|
$f = $menu['footer'] ?? '';
|
||||||
|
$f = str_replace('{finca}', $finca, $f);
|
||||||
|
if ($finca !== '' && !str_contains($f, $finca)) $f = '📍 ' . $finca;
|
||||||
|
return $f;
|
||||||
|
};
|
||||||
|
|
||||||
|
$menus = $config['menus'];
|
||||||
|
check('lista muestra la finca activa',
|
||||||
|
$footer($menus['show_menu_cat2'], 'ROSA BLANCA'), '📍 ROSA BLANCA');
|
||||||
|
check('"todas" se lee natural',
|
||||||
|
$footer($menus['show_menu_cat2'], '🌐 Todas las fincas'), '📍 🌐 Todas las fincas');
|
||||||
|
check('sin finca queda el footer de siempre',
|
||||||
|
$footer($menus['show_menu_cat2'], ''), 'Palmas360');
|
||||||
|
check('submenú de botones también la lleva',
|
||||||
|
$footer($menus['submenu_ciclo_mantenimiento'], 'REPOSO'), '📍 REPOSO');
|
||||||
|
check('entra en los 60 caracteres del footer',
|
||||||
|
mb_strlen($footer($menus['show_menu_cat2'], 'ROSA BLANCA')) <= 60, true);
|
||||||
|
|
||||||
echo "\nask_finca\n";
|
echo "\nask_finca\n";
|
||||||
$af = $flows['ask_finca'];
|
$af = $flows['ask_finca'];
|
||||||
check('no repregunta si ya hay finca', $af['skip_if_set'] ?? false, true);
|
check('no repregunta si ya hay finca', $af['skip_if_set'] ?? false, true);
|
||||||
|
|||||||
Reference in New Issue
Block a user