From 976f61b682aac4df0930a97d9aa9b354dbb1ca5c Mon Sep 17 00:00:00 2001 From: Lizandro Guarnizo <77708265+lizandrogd@users.noreply.github.com> Date: Sun, 5 Jul 2026 22:03:57 -0500 Subject: [PATCH] fix: match fuzzy de label en dynamic_list y collecting mid-flow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- services/NormalBot.php | 41 ++++++++++++++++++++++++++++++++--------- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/services/NormalBot.php b/services/NormalBot.php index c47ec48..bc1486f 100644 --- a/services/NormalBot.php +++ b/services/NormalBot.php @@ -139,16 +139,31 @@ class NormalBot $key = $col['meta_key'] ?? ''; $nextNode = $col['next_node'] ?? null; $validIds = $col['__valid_ids'] ?? null; + $labelMap = $col['__label_map'] ?? []; - if ($validIds !== null && !in_array(trim($input), $validIds, true)) { - return self::sendText( - '⚠️ Por favor selecciona una opción válida de la lista.', - $context['from'], $company - ); + $resolved = trim($input); + if ($validIds !== null && !in_array($resolved, $validIds, true)) { + // Intento de match por label (útil cuando viene texto transcrito de audio) + $inputNorm = self::normalize($resolved); + $matched = null; + foreach ($labelMap as $label => $id) { + $lNorm = self::normalize((string)$label); + if ($lNorm === $inputNorm || str_contains($inputNorm, $lNorm) || str_contains($lNorm, $inputNorm)) { + $matched = (string)$id; + break; + } + } + if ($matched === null) { + return self::sendText( + '⚠️ Por favor selecciona una opción válida de la lista.', + $context['from'], $company + ); + } + $resolved = $matched; } $formData = $meta[$group] ?? []; - $formData[$key] = trim($input); + $formData[$key] = $resolved; $meta[$group] = $formData; unset($meta['collecting']); ConversationContext::updateMetadata($ctxId, $meta); @@ -198,6 +213,12 @@ class NormalBot $key = $flow['meta_key'] ?? ''; $nextNode = $flow['next_node'] ?? null; + // Mapa label → id para match fuzzy (usado tanto en auto-select como en collecting) + $labelMap = []; + foreach ($items as $item) { + $labelMap[(string)($item[$labelField] ?? '')] = (string)($item[$valueField] ?? ''); + } + // Si el NLU extrajo entities, intentar auto-seleccionar sin mostrar lista $entities = $meta['__nlu_entities'] ?? []; unset($meta['__nlu_entities']); @@ -205,9 +226,10 @@ class NormalBot $matchedId = null; foreach ($entities as $ev) { $evNorm = self::normalize((string)$ev); - foreach ($items as $item) { - if (self::normalize((string)($item[$labelField] ?? '')) === $evNorm) { - $matchedId = (string)($item[$valueField] ?? ''); + foreach ($labelMap as $label => $id) { + $lNorm = self::normalize($label); + if ($lNorm === $evNorm || str_contains($evNorm, $lNorm) || str_contains($lNorm, $evNorm)) { + $matchedId = $id; break 2; } } @@ -238,6 +260,7 @@ class NormalBot 'meta_key' => $key, 'next_node' => $nextNode, '__valid_ids' => array_column($items, $valueField), + '__label_map' => $labelMap, ]; ConversationContext::updateMetadata($ctxId, $meta); ConversationContext::updateNode($ctxId, 'collecting');