Files
bot_palmas/setup/validate_config.php
Lizandro GuarnizoandClaude Sonnet 4.6 0e89258f67 feat: navegacion por niveles, intencion pendiente y NLU que aterriza en el informe
- "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>
2026-08-01 09:59:30 -05:00

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";