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>
This commit is contained in:
Lizandro Guarnizo
2026-07-05 22:03:57 -05:00
co-authored by Claude Sonnet 4.6
parent 3d04be14bc
commit 976f61b682
+32 -9
View File
@@ -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');