feat: marcar varios lotes escribiendolos, no de a uno
Cada toque redibuja la lista en un mensaje nuevo: seis lotes eran seis mensajes. Ahora se puede escribir "1a, 2b 5c" y quedan marcados de una. Suma a lo ya tocado en vez de reemplazarlo, asi las dos formas conviven. Compara sin acentos ni mayusculas y acepta el lote solo o con la finca delante. Si dos fincas tienen un "1A" no adivina: exige el nombre completo. Y lo que no reconoce lo dice —marcar cinco de seis en silencio registraria un ciclo de menos sin que nadie se entere. Se separa por comas y se prueba el segmento entero antes de partirlo por espacios: al reves, "RETIRO 1A" se rompia en dos y no matcheaba nada. Descartado "marcar todos": nunca se abren todos los lotes de una finca. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
52dbd1e203
commit
dd894e5aab
@@ -1244,6 +1244,31 @@ class NormalBot
|
||||
ConversationContext::updateMetadata($ctxId, $meta);
|
||||
return self::capAskNext($cap, $context['from'], $company, $ctxId);
|
||||
}
|
||||
|
||||
// Escrito a mano: "1a, 2b 5c" marca varios de una. Un toque por lote
|
||||
// redibujaba la lista en un mensaje nuevo cada vez, y para seis lotes
|
||||
// eran seis mensajes. Suma a lo ya marcado, no lo reemplaza.
|
||||
// Un toque manda siempre un id del catalogo y ya se resolvio arriba:
|
||||
// lo que llega hasta aca fue escrito a mano.
|
||||
if (trim($value) !== '') {
|
||||
[$marcados, $sinReconocer] = self::multiPorTexto($value, $catalogo, $elegidos);
|
||||
|
||||
if ($marcados || $sinReconocer) {
|
||||
$cap['__multi'][$key] = $marcados;
|
||||
$meta['__cap'] = $cap;
|
||||
ConversationContext::updateMetadata($ctxId, $meta);
|
||||
|
||||
// Lo que no reconoce se dice: en silencio se registraria de menos
|
||||
if ($sinReconocer) {
|
||||
WhatsAppSender::sendText(
|
||||
$context['from'],
|
||||
'⚠️ No encontré: ' . implode(', ', array_map(fn($t) => '"' . $t . '"', $sinReconocer)),
|
||||
(string)($context['phone_number_id'] ?? '')
|
||||
);
|
||||
}
|
||||
return self::capAskNext($cap, $context['from'], $company, $ctxId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── 1b. "Ver más": avanza la página y vuelve a mostrar la misma lista ─
|
||||
@@ -1626,6 +1651,62 @@ class NormalBot
|
||||
* final anterior a la inicial), que el ERP acepta y recién detecta el
|
||||
* revisor. Las fechas van en YYYY-MM-DD, así que comparar como texto basta.
|
||||
*/
|
||||
/**
|
||||
* Interpreta "1a, 2b 5c" contra el catalogo de un multi_select.
|
||||
*
|
||||
* Compara sin acentos ni mayusculas y acepta el nombre del lote solo o con
|
||||
* la finca delante, porque nadie escribe "REPOSO 1A" estando ya en REPOSO.
|
||||
* Devuelve [elegidos, lo que no reconocio].
|
||||
*/
|
||||
private static function multiPorTexto(string $texto, array $catalogo, array $elegidos): array
|
||||
{
|
||||
$normalizar = function (string $t): string {
|
||||
$t = mb_strtolower(trim($t), 'UTF-8');
|
||||
$t = strtr($t, ['á'=>'a','é'=>'e','í'=>'i','ó'=>'o','ú'=>'u','ñ'=>'n']);
|
||||
return preg_replace('/[^a-z0-9]/', '', $t) ?? '';
|
||||
};
|
||||
|
||||
// Indice del catalogo: nombre completo y ultima palabra ("REPOSO 1A" -> "1a")
|
||||
$indice = [];
|
||||
foreach ($catalogo as $id => $etiqueta) {
|
||||
$limpia = trim(preg_replace('/\s*\(.*?\)\s*$/', '', (string)$etiqueta));
|
||||
$indice[$normalizar($limpia)] = (string)$id;
|
||||
$partes = preg_split('/\s+/', $limpia);
|
||||
if (count($partes) > 1) {
|
||||
$corto = $normalizar(end($partes));
|
||||
// Solo si no choca con otro lote: si dos fincas tienen "1A" se exige el nombre completo
|
||||
if ($corto !== '' && !array_key_exists($corto, $indice)) $indice[$corto] = (string)$id;
|
||||
elseif ($corto !== '' && ($indice[$corto] ?? null) !== (string)$id) $indice[$corto] = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Se separa por comas y se prueba el segmento entero antes de partirlo
|
||||
// por espacios: si no, "RETIRO 1A" se rompia en dos y no matcheaba nada.
|
||||
$sinReconocer = [];
|
||||
foreach (preg_split('/[,;\n]+/', $texto, -1, PREG_SPLIT_NO_EMPTY) as $segmento) {
|
||||
$n = $normalizar($segmento);
|
||||
if ($n === '') continue;
|
||||
|
||||
if (($indice[$n] ?? null) !== null) {
|
||||
$id = $indice[$n];
|
||||
$elegidos[$id] = $catalogo[$id];
|
||||
continue;
|
||||
}
|
||||
|
||||
// Sin coincidencia completa: pueden ser varios sueltos ("1a 2b 5c")
|
||||
$partes = preg_split('/\s+/', trim($segmento), -1, PREG_SPLIT_NO_EMPTY);
|
||||
if (count($partes) < 2) { $sinReconocer[] = trim($segmento); continue; }
|
||||
foreach ($partes as $token) {
|
||||
$t = $normalizar($token);
|
||||
if ($t === '') continue;
|
||||
$id = $indice[$t] ?? null;
|
||||
if ($id === null) { $sinReconocer[] = trim($token); continue; }
|
||||
$elegidos[$id] = $catalogo[$id];
|
||||
}
|
||||
}
|
||||
return [$elegidos, $sinReconocer];
|
||||
}
|
||||
|
||||
private static function capValidar(array $field, string $value, array $collected): ?string
|
||||
{
|
||||
$min = $field['min_field'] ?? '';
|
||||
|
||||
@@ -146,6 +146,22 @@ check('y la página 2 alcanza el lote 12', isset(filasDe($r)['12']));
|
||||
$r = NormalBot::processInteractive($co, $ctx, '__mas'); // vuelve: no hay página 3
|
||||
$r = NormalBot::processInteractive($co, $ctx, '__mas');
|
||||
|
||||
// Escribir varios de una: seis toques eran seis mensajes
|
||||
$r = NormalBot::process($co, $ctx, '3a, 4a 5a');
|
||||
$filas = filasDe($r);
|
||||
check('escribir marca varios en un solo mensaje',
|
||||
str_contains($filas['__listo'] ?? '', '(5)'));
|
||||
// El contador no depende de la pagina que se este viendo
|
||||
check('suma a lo tocado en vez de reemplazarlo: 2 + 3 = 5',
|
||||
str_contains($filas['__listo'] ?? '', '(5)'));
|
||||
|
||||
WhatsAppSender::$enviados = [];
|
||||
$r = NormalBot::process($co, $ctx, '6a, 9z9z');
|
||||
check('lo que no reconoce lo avisa, no lo ignora',
|
||||
str_contains(WhatsAppSender::$enviados[0]['text'] ?? '', '"9z9z"'));
|
||||
check('y marca igual lo que sí entendió',
|
||||
str_contains(filasDe($r)['__listo'] ?? '', '(6)'));
|
||||
|
||||
$r = NormalBot::processInteractive($co, $ctx, '__listo');
|
||||
check('resumen final', isset(filasDe($r)['__cap_confirm']));
|
||||
|
||||
@@ -153,7 +169,8 @@ capturas();
|
||||
NormalBot::processInteractive($co, $ctx, '__cap_confirm');
|
||||
$post = array_values(array_filter(capturas(), fn($c) => str_contains($c['peticion'] ?? '', 'ciclos_')))[0] ?? null;
|
||||
check('la URL resolvió el tipo elegido', $post['peticion'] ?? null, 'ciclos_cosecha_up');
|
||||
check('lote_ids como arreglo', $post['body']['lote_ids'] ?? null, ['1', '2']);
|
||||
check('lote_ids lleva los tocados y los escritos',
|
||||
$post['body']['lote_ids'] ?? null, ['1', '2', '3', '4', '5', '6']);
|
||||
check('con la acción y la fecha',
|
||||
[$post['body']['accion'] ?? null, $post['body']['fecha'] ?? null], ['apertura', $hoy]);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user