$k) { if (($meta[$g][$k] ?? '') === '') { $falta = [$g, $k]; break; } } if ($falta === null) break; if (($meta['__resolve_attempt'] ?? '') === $key) { $pasos[] = '*sin-resolver*'; break; } $meta['__after_select'] = $key; $meta['__resolve_attempt'] = $key; $resolver = $flow['resolver']; $pasos[] = $resolver; // El resolver resuelve si tenemos con qué (entity o tap); si no, muestra lista [$g, $k] = $falta; if (!isset($seleccionaria[$g])) { $pasos[] = '*muestra-lista*'; break; } $meta[$g][$k] = $seleccionaria[$g]; $key = $meta['__after_select']; unset($meta['__after_select']); } return [$pasos, $meta]; } $fallas = 0; function check(string $nombre, $obtenido, $esperado): void { global $fallas; $ok = $obtenido === $esperado; if (!$ok) $fallas++; printf("%s %s\n", $ok ? ' ok ' : ' FALLA', $nombre); if (!$ok) { echo " esperaba: " . json_encode($esperado, JSON_UNESCAPED_UNICODE) . "\n"; echo " obtuvo: " . json_encode($obtenido, JSON_UNESCAPED_UNICODE) . "\n"; } } $flows = flowsDe($config, '2'); echo "\nIntención pendiente\n"; // "quiero informe de mantenimiento de plateo": el NLU rutea al informe y la // entity resuelve el grupo, así que el submenú nunca se muestra. [$pasos] = ejecutar($flows, 'ciclo_mantenimiento_todos', [], ['grupo_mant' => '7']); check('con grupo por entity llega al informe', $pasos, ['ciclo_mantenimiento_todos', 'ciclo_mantenimiento', 'ciclo_mantenimiento_todos']); // Sin poder resolver el grupo, muestra la lista y espera al usuario [$pasos] = ejecutar($flows, 'ciclo_mantenimiento_todos', []); check('sin grupo pide la lista', $pasos, ['ciclo_mantenimiento_todos', 'ciclo_mantenimiento', '*muestra-lista*']); // Grupo ya elegido (viene del submenú): va directo, sin volver a preguntar [$pasos] = ejecutar($flows, 'ciclo_mantenimiento_todos', ['grupo_mant' => ['grupo_id' => '7']]); check('con grupo ya elegido no repregunta', $pasos, ['ciclo_mantenimiento_todos']); // Un resolver que no deja el dato no puede rebotar para siempre $roto = $flows; $roto['resolver_roto'] = ['type' => 'menu', 'menu' => 'submenu_ciclos']; $roto['informe_roto'] = ['type' => 'function', 'requires' => ['nada' => 'x'], 'resolver' => 'resolver_roto']; [$pasos] = ejecutar($roto, 'informe_roto', [], ['otra_cosa' => '1']); check('resolver mal configurado corta el bucle', $pasos, ['informe_roto', 'resolver_roto', '*muestra-lista*']); echo "\nComando atrás\n"; $atras = function (array $flows, ?string $nodo): string { return ($nodo !== null ? ($flows[$nodo]['back'] ?? null) : null) ?? 'show_main_menu'; }; check('desde un ciclo de sanidad sube a sanidad', $atras($flows, 'ciclo_plagas'), 'submenu_ciclos_sanidad'); check('desde sanidad sube a ciclos', $atras($flows, 'submenu_ciclos_sanidad'), 'submenu_ciclos'); check('desde ciclos sube a la raíz', $atras($flows, 'submenu_ciclos'), 'show_main_menu'); check('desde un informe de mantenimiento al submenú', $atras($flows, 'ciclo_mantenimiento_todos'), 'submenu_ciclo_mantenimiento'); check('sin nodo activo cae a la raíz', $atras($flows, null), 'show_main_menu'); echo "\nComandos\n"; foreach (['2', '3'] as $cat) { $cmds = comandosDe($config, $cat); check("cat {$cat}: 'atras' resuelve por mapa", $cmds['atras'] ?? null, '__back'); check("cat {$cat}: 'cambiar finca' existe", $cmds['cambiar finca'] ?? null, 'reset_finca'); check("cat {$cat}: 'salir' va a la raíz", $cmds['salir'] ?? null, 'show_main_menu'); } echo "\nask_finca\n"; $af = $flows['ask_finca']; check('no repregunta si ya hay finca', $af['skip_if_set'] ?? false, true); check('reset_finca la vuelve a pedir', isset($flows['reset_finca']), true); echo "\n"; if ($fallas) { echo "{$fallas} falla(s)\n"; exit(1); } echo "OK — navegación correcta\n";