test: auditoria de flujos de punta a punta contra el ERP
Cruza el seed del bot con controller_api_externa.php y verifica lo que las
pruebas de navegacion no ven: que la peticion exista del otro lado, que cada
{placeholder} de la URL salga de un campo o del contexto, que el tipo de campo
este implementado, y que los menus respeten los limites de WhatsApp.
Resuelve las claves que dependen de lo elegido —lotes_{accion}_dn,
ciclos_{ciclo}_up— expandiendolas por las opciones del campo.
Verificada rompiendo el seed a proposito en siete formas distintas: las detecta
todas. Un chequeo que nunca falla no sirve de nada.
Uso: php setup/audit_flujos.php [ruta-a-PALMAS360]
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
42adeb64d0
commit
6df1d482cc
@@ -0,0 +1,220 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Audita cada flujo de punta a punta, cruzando el seed del bot contra el
|
||||
* controlador del ERP. Verifica lo que las pruebas de navegación no ven:
|
||||
* que la peticion exista del otro lado, que los {placeholder} se puedan
|
||||
* resolver, que el tipo de campo esté implementado y que el POST mande lo
|
||||
* que el procesador exige.
|
||||
*
|
||||
* Uso: php setup/audit_flujos.php [ruta-a-PALMAS360]
|
||||
*/
|
||||
|
||||
$rutaErp = $argv[1] ?? '/Users/lizandro/Documents/GitHub/PALMAS360';
|
||||
|
||||
// ─── Config del bot ──────────────────────────────────────────────────────────
|
||||
$seed = file_get_contents(__DIR__ . '/seed_palmas.php');
|
||||
$open = strpos($seed, '[', strpos($seed, '$configJson = ['));
|
||||
$d = 0; $end = $open;
|
||||
for ($i = $open, $n = strlen($seed); $i < $n; $i++) {
|
||||
if ($seed[$i] === '[') $d++;
|
||||
elseif ($seed[$i] === ']') { $d--; if (!$d) { $end = $i; break; } }
|
||||
}
|
||||
$config = eval('return ' . substr($seed, $open, $end - $open + 1) . ';');
|
||||
|
||||
// Endpoints registrados: clave => url
|
||||
preg_match_all("/\['key' => '([^']+)',\s*'dir' => '([^']+)',\s*'url' => \\\$BASE \. '([^']*)'/", substr($seed, $end), $m);
|
||||
$endpoints = [];
|
||||
foreach ($m[1] as $i => $k) $endpoints[$k] = ['dir' => $m[2][$i], 'url' => $m[3][$i]];
|
||||
|
||||
// ─── Lo que expone el ERP ────────────────────────────────────────────────────
|
||||
$apiPath = $rutaErp . '/php/controller/controller_api_externa.php';
|
||||
$hayErp = is_file($apiPath);
|
||||
$peticiones = [];
|
||||
$procesador = [];
|
||||
if ($hayErp) {
|
||||
preg_match_all("/case '([a-z0-9_]+)':/i", file_get_contents($apiPath), $mp);
|
||||
$peticiones = array_flip($mp[1]);
|
||||
|
||||
$procPath = $rutaErp . '/php/services/BotEntradaProcesador.php';
|
||||
if (is_file($procPath)) {
|
||||
$proc = file_get_contents($procPath);
|
||||
// requeridos por tipo, para cruzar con lo que manda el bot
|
||||
if (preg_match_all("/\\\$requeridos = \[([^\]]+)\]/", $proc, $mr)) $procesador['labores_up'] = $mr[1][0];
|
||||
if (preg_match_all("/\\\$required = \[([^\]]+)\]/", $proc, $mq)) $procesador['otros'] = $mq[1];
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Tipos de campo y de flujo implementados ─────────────────────────────────
|
||||
$bot = file_get_contents(dirname(__DIR__) . '/services/NormalBot.php');
|
||||
$tiposFlujo = [];
|
||||
if (preg_match_all("/'([a-z_]+)'\s*=> self::handle|'([a-z_]+)'\s*=> self::send|'([a-z_]+)'\s*=> self::build/", $bot, $mt)) {
|
||||
$tiposFlujo = array_filter(array_merge($mt[1], $mt[2], $mt[3]));
|
||||
}
|
||||
$tiposFlujo = array_flip(array_merge($tiposFlujo, ['text', 'menu', 'function', 'image']));
|
||||
|
||||
$tiposCampo = ['text', 'lookup', 'select', 'multi_select', 'static_select', 'date_quick'];
|
||||
|
||||
// ─── Auditoría ───────────────────────────────────────────────────────────────
|
||||
$prob = [];
|
||||
$avis = [];
|
||||
$ok = 0;
|
||||
|
||||
function grupoDeMeta(array $config, string $clave): bool {
|
||||
// Claves que salen del metadata acumulado, no de un campo del flujo
|
||||
foreach ($config['flows'] as $f) {
|
||||
if (($f['meta_key'] ?? '') === $clave) return true;
|
||||
}
|
||||
return in_array($clave, ['finca_id', 'grupo_id', 'wa_number'], true);
|
||||
}
|
||||
|
||||
foreach (['global' => []] + $config['per_type'] as $cat => $pt) {
|
||||
$flows = array_merge($config['flows'], $pt['flows'] ?? []);
|
||||
$menus = array_merge($config['menus'], $pt['menus'] ?? []);
|
||||
|
||||
foreach ($flows as $key => $f) {
|
||||
$tipo = $f['type'] ?? 'text';
|
||||
$ref = "cat {$cat} · {$key}";
|
||||
|
||||
if (!isset($tiposFlujo[$tipo])) {
|
||||
$prob[] = "{$ref}: tipo de flujo '{$tipo}' no implementado";
|
||||
continue;
|
||||
}
|
||||
|
||||
// Endpoints que el flujo usa, con los placeholders que llevan
|
||||
$usados = [];
|
||||
foreach (['endpoint_key', 'source_endpoint_key', 'full_endpoint_key', 'source_endpoint_key_all'] as $k) {
|
||||
if (!empty($f[$k])) $usados[] = $f[$k];
|
||||
}
|
||||
foreach ($f['fields'] ?? [] as $campo) {
|
||||
foreach (['source_endpoint_key', 'full_endpoint_key', 'lookup_endpoint_key'] as $k) {
|
||||
if (!empty($campo[$k])) $usados[] = $campo[$k];
|
||||
}
|
||||
$tc = $campo['type'] ?? 'text';
|
||||
if (!in_array($tc, $tiposCampo, true)) {
|
||||
$prob[] = "{$ref}: campo '{$campo['key']}' usa tipo '{$tc}', que no existe";
|
||||
}
|
||||
}
|
||||
if (!empty($f['params']['endpoint_key'])) $usados[] = $f['params']['endpoint_key'];
|
||||
if (!empty($f['params']['next_endpoint_key'])) $usados[] = $f['params']['next_endpoint_key'];
|
||||
|
||||
$clavesCampo = array_column($f['fields'] ?? [], 'key');
|
||||
|
||||
foreach (array_unique($usados) as $ek) {
|
||||
// La clave puede depender de lo elegido: lotes_{accion}_dn
|
||||
$variantes = [$ek];
|
||||
if (preg_match('/\{(\w+)\}/', $ek, $mm)) {
|
||||
$campo = $mm[1];
|
||||
$opts = [];
|
||||
foreach ($f['fields'] ?? [] as $c) {
|
||||
if (($c['key'] ?? '') === $campo) $opts = array_column($c['options'] ?? [], 'id');
|
||||
}
|
||||
if (!$opts) {
|
||||
$prob[] = "{$ref}: la clave '{$ek}' depende de '{$campo}', que no es un campo con opciones";
|
||||
continue;
|
||||
}
|
||||
$variantes = array_map(fn($o) => str_replace($mm[0], $o, $ek), $opts);
|
||||
}
|
||||
|
||||
foreach ($variantes as $v) {
|
||||
if (!isset($endpoints[$v])) {
|
||||
$prob[] = "{$ref}: endpoint '{$v}' sin registrar";
|
||||
continue;
|
||||
}
|
||||
$ok++;
|
||||
$url = $endpoints[$v]['url'];
|
||||
|
||||
// La peticion tiene que existir del otro lado
|
||||
if ($hayErp && preg_match('/peticion=([a-z0-9_{}]+)/i', $url, $mu)) {
|
||||
$pet = $mu[1];
|
||||
$pets = [$pet];
|
||||
if (preg_match('/\{(\w+)\}/', $pet, $mc)) {
|
||||
$opts = [];
|
||||
foreach ($f['fields'] ?? [] as $c) {
|
||||
if (($c['key'] ?? '') === $mc[1]) $opts = array_column($c['options'] ?? [], 'id');
|
||||
}
|
||||
$pets = array_map(fn($o) => str_replace($mc[0], $o, $pet), $opts);
|
||||
}
|
||||
foreach ($pets as $p) {
|
||||
if (!isset($peticiones[$p])) $prob[] = "{$ref}: el ERP no expone '?peticion={$p}'";
|
||||
}
|
||||
}
|
||||
|
||||
// Cada {placeholder} de la URL tiene que poder resolverse
|
||||
preg_match_all('/\{(\w+)\}/', $url, $mph);
|
||||
foreach (array_unique($mph[1]) as $ph) {
|
||||
if (in_array($ph, $clavesCampo, true)) continue;
|
||||
if (grupoDeMeta($config, $ph)) continue;
|
||||
$prob[] = "{$ref}: '{$ph}' en la URL de '{$v}' no sale de ningún campo ni del contexto";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Un flujo que postea sin campos no manda nada
|
||||
if ($tipo === 'collect_and_post' && empty($f['fields'])) {
|
||||
$prob[] = "{$ref}: collect_and_post sin campos";
|
||||
}
|
||||
// requires necesita su resolver, y el resolver tiene que existir
|
||||
if (!empty($f['requires'])) {
|
||||
$r = $f['resolver'] ?? '';
|
||||
if ($r === '') $prob[] = "{$ref}: declara requires sin resolver";
|
||||
elseif (!isset($flows[$r])) $prob[] = "{$ref}: resolver '{$r}' no existe";
|
||||
}
|
||||
}
|
||||
|
||||
// Cada opción de menú debe llevar a algún lado
|
||||
foreach ($menus as $mk => $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 (!isset($flows[$id]) && !isset($menus[$id])) {
|
||||
$prob[] = "cat {$cat} · menú {$mk}: la opción '{$id}' no lleva a ningún flujo";
|
||||
}
|
||||
}
|
||||
// WhatsApp corta las listas largas
|
||||
foreach ($menu['sections'] ?? [] as $sec) {
|
||||
if (count($sec['rows'] ?? []) > 10) {
|
||||
$avis[] = "cat {$cat} · menú {$mk}: " . count($sec['rows']) . ' filas, WhatsApp muestra 10';
|
||||
}
|
||||
}
|
||||
if (count($menu['buttons'] ?? []) > 3) {
|
||||
$prob[] = "cat {$cat} · menú {$mk}: " . count($menu['buttons']) . ' botones, WhatsApp admite 3';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Endpoints registrados que nadie usa
|
||||
$usadosTodos = [];
|
||||
foreach ($config['flows'] as $f) {
|
||||
foreach (['endpoint_key', 'source_endpoint_key', 'full_endpoint_key', 'source_endpoint_key_all'] as $k) {
|
||||
if (!empty($f[$k])) $usadosTodos[] = $f[$k];
|
||||
}
|
||||
foreach ($f['fields'] ?? [] as $c) {
|
||||
foreach (['source_endpoint_key', 'full_endpoint_key', 'lookup_endpoint_key'] as $k) {
|
||||
if (!empty($c[$k])) $usadosTodos[] = $c[$k];
|
||||
}
|
||||
}
|
||||
if (!empty($f['params']['endpoint_key'])) $usadosTodos[] = $f['params']['endpoint_key'];
|
||||
if (!empty($f['params']['next_endpoint_key'])) $usadosTodos[] = $f['params']['next_endpoint_key'];
|
||||
}
|
||||
$patrones = array_map(fn($u) => '/^' . str_replace('\{accion\}', '\w+', preg_quote($u, '/')) . '$/', $usadosTodos);
|
||||
foreach ($endpoints as $k => $e) {
|
||||
$usado = false;
|
||||
foreach ($patrones as $p) { if (preg_match($p, $k)) { $usado = true; break; } }
|
||||
if (!$usado && $k !== 'numeros_dn') $avis[] = "endpoint '{$k}' registrado pero sin usar";
|
||||
}
|
||||
|
||||
// ─── Resultado ───────────────────────────────────────────────────────────────
|
||||
echo "\nAuditoría de flujos\n";
|
||||
echo str_repeat('─', 60) . "\n";
|
||||
echo "ERP: " . ($hayErp ? $apiPath : 'no encontrado — se omiten esos chequeos') . "\n";
|
||||
echo "Endpoints: " . count($endpoints) . " registrados, {$ok} referencias resueltas\n";
|
||||
echo "Peticiones: " . count($peticiones) . " expuestas por el ERP\n\n";
|
||||
|
||||
foreach (array_unique($avis) as $a) echo " aviso {$a}\n";
|
||||
foreach (array_unique($prob) as $p) echo " PROBLEMA {$p}\n";
|
||||
|
||||
$n = count(array_unique($prob));
|
||||
echo "\n" . ($n ? "{$n} problema(s)\n" : "Sin problemas: todos los flujos cierran de punta a punta\n");
|
||||
exit($n ? 1 : 0);
|
||||
Reference in New Issue
Block a user