- "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>
92 lines
3.8 KiB
PHP
92 lines
3.8 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
/**
|
|
* Valida el grafo de navegación de seed_palmas.php sin tocar la base de datos.
|
|
* Un 'back', 'resolver' o id de botón que apunte a un flow inexistente deja al
|
|
* usuario en un callejón sin salida, y eso no se nota hasta que alguien lo pisa.
|
|
*
|
|
* Uso: php setup/validate_config.php
|
|
*/
|
|
|
|
$seed = file_get_contents(__DIR__ . '/seed_palmas.php');
|
|
$ini = strpos($seed, '$configJson = [');
|
|
if ($ini === false) { fwrite(STDERR, "No se encontró \$configJson\n"); exit(1); }
|
|
|
|
// Recorte por balance de corchetes desde la apertura del array
|
|
$open = strpos($seed, '[', $ini);
|
|
$depth = 0; $end = $open;
|
|
for ($i = $open, $n = strlen($seed); $i < $n; $i++) {
|
|
if ($seed[$i] === '[') $depth++;
|
|
elseif ($seed[$i] === ']') { $depth--; if ($depth === 0) { $end = $i; break; } }
|
|
}
|
|
$config = eval('return ' . substr($seed, $open, $end - $open + 1) . ';');
|
|
|
|
$errores = [];
|
|
$avisos = [];
|
|
|
|
// Menús raíz: no suben a ningún lado, es correcto que no tengan 'back'
|
|
$esRaiz = fn(string $k): bool => $k === 'show_main_menu' || str_starts_with($k, 'show_menu_');
|
|
|
|
$categorias = ['global' => []] + ($config['per_type'] ?? []);
|
|
foreach ($categorias as $cat => $pt) {
|
|
$flows = array_merge($config['flows'] ?? [], $pt['flows'] ?? []);
|
|
$menus = array_merge($config['menus'] ?? [], $pt['menus'] ?? []);
|
|
$cmds = array_merge($config['commands'] ?? [], $pt['commands'] ?? []);
|
|
$donde = "cat {$cat}";
|
|
|
|
$existe = fn(string $k): bool => isset($flows[$k]) || isset($menus[$k]);
|
|
|
|
foreach ($cmds as $palabra => $destino) {
|
|
if ($destino === '__back') continue; // se resuelve en runtime
|
|
if (!$existe($destino)) $errores[] = "{$donde}: comando '{$palabra}' → '{$destino}' no existe";
|
|
}
|
|
|
|
foreach ($flows as $key => $flow) {
|
|
foreach (['back', 'resolver', 'next_node'] as $campo) {
|
|
$destino = $flow[$campo] ?? null;
|
|
if ($destino !== null && !$existe($destino)) {
|
|
$errores[] = "{$donde}: flow '{$key}'.{$campo} → '{$destino}' no existe";
|
|
}
|
|
}
|
|
if (!empty($flow['requires']) && empty($flow['resolver'])) {
|
|
$errores[] = "{$donde}: flow '{$key}' declara requires sin resolver";
|
|
}
|
|
if (($flow['type'] ?? '') === 'menu' && !isset($menus[$flow['menu'] ?? ''])) {
|
|
$errores[] = "{$donde}: flow '{$key}' apunta al menú '{$flow['menu']}' que no existe";
|
|
}
|
|
if (($flow['type'] ?? '') === 'menu' && !isset($flow['back']) && !$esRaiz($key)) {
|
|
$avisos[] = "{$donde}: flow '{$key}' es menú y no define 'back'";
|
|
}
|
|
}
|
|
|
|
// Cada id de botón/fila debe resolver a un flow
|
|
foreach ($menus as $mKey => $menu) {
|
|
$ids = array_column($menu['buttons'] ?? [], 'id');
|
|
foreach ($menu['sections'] ?? [] as $sec) {
|
|
$ids = array_merge($ids, array_column($sec['rows'] ?? [], 'id'));
|
|
}
|
|
foreach ($ids as $id) {
|
|
if (!$existe($id)) $errores[] = "{$donde}: menú '{$mKey}' tiene opción '{$id}' sin flow";
|
|
}
|
|
}
|
|
}
|
|
|
|
// Endpoints referenciados por los flows
|
|
preg_match_all("/'key'\s*=>\s*'([^']+)'/", substr($seed, $end), $m);
|
|
$endpoints = array_flip($m[1]);
|
|
foreach ($config['flows'] ?? [] as $key => $flow) {
|
|
foreach (['source_endpoint_key', 'source_endpoint_key_all'] as $campo) {
|
|
$ep = $flow[$campo] ?? null;
|
|
if ($ep !== null && !isset($endpoints[$ep])) $errores[] = "flow '{$key}'.{$campo} → endpoint '{$ep}' no registrado";
|
|
}
|
|
$ep = $flow['params']['endpoint_key'] ?? null;
|
|
if ($ep !== null && !isset($endpoints[$ep])) $errores[] = "flow '{$key}' → endpoint '{$ep}' no registrado";
|
|
}
|
|
|
|
foreach (array_unique($avisos) as $a) echo "aviso: {$a}\n";
|
|
foreach (array_unique($errores) as $e) echo "ERROR: {$e}\n";
|
|
|
|
if ($errores) { echo "\n" . count(array_unique($errores)) . " error(es)\n"; exit(1); }
|
|
echo "\nOK — grafo de navegación consistente\n";
|