fix(tests): sin pdo_sqlite — db() falso en PHP puro

El servidor no trae el driver de SQLite y el arnes reventaba al arrancar.
NormalBot solo hace dos consultas —el endpoint por clave y el perfil por
numero— asi que dos mapas en memoria alcanzan y el arnes deja de depender de
cualquier driver de PDO.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Lizandro Guarnizo
2026-08-19 22:30:56 -05:00
co-authored by Claude Sonnet 4.6
parent 1f7d33b40d
commit 9ae4b0dee3
2 changed files with 61 additions and 19 deletions
+59 -16
View File
@@ -4,8 +4,8 @@ declare(strict_types=1);
/** /**
* Arnés para ejecutar NormalBot de verdad, sin MySQL ni WhatsApp: * Arnés para ejecutar NormalBot de verdad, sin MySQL ni WhatsApp:
* *
* db() -> SQLite en memoria, con los endpoints del seed * db() -> falso en PHP puro (sin drivers), con los endpoints
* reescritos hacia el servidor de fixtures * del seed reescritos hacia el servidor de fixtures
* ConversationContext -> estado en memoria, un contexto por teléfono * ConversationContext -> estado en memoria, un contexto por teléfono
* WhatsAppSender -> captura los envíos directos en ::$enviados * WhatsAppSender -> captura los envíos directos en ::$enviados
* AiBot -> respuestas guionadas en ::$respuestas * AiBot -> respuestas guionadas en ::$respuestas
@@ -15,28 +15,71 @@ declare(strict_types=1);
const FIXTURE_BASE = 'http://127.0.0.1:8973/fixtures_api.php'; const FIXTURE_BASE = 'http://127.0.0.1:8973/fixtures_api.php';
// ── db(): SQLite con los endpoints reales del seed ─────────────────────────── // ── db(): falso en PHP puro ──────────────────────────────────────────────────
function db(): PDO // Sin SQLite: los servidores no siempre traen pdo_sqlite. NormalBot solo hace
// dos consultas —el endpoint por clave y el perfil por número— así que un par
// de mapas en memoria alcanza y no dependemos de ningún driver.
class FakeStmt
{ {
static $pdo = null; private $row = false;
if ($pdo !== null) return $pdo; public function __construct(private string $sql) {}
$pdo = new PDO('sqlite::memory:', null, null, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]); public function execute(array $p = []): bool
$pdo->exec("CREATE TABLE company_endpoints ( {
company_id INT, endpoint_key TEXT, direction TEXT, url TEXT, if (str_contains($this->sql, 'company_endpoints')) {
method TEXT DEFAULT 'GET', params TEXT DEFAULT NULL, is_active INT DEFAULT 1)"); $key = $p[1] ?? '';
$pdo->exec("CREATE TABLE company_phones ( $url = FakeDb::$endpoints[$key] ?? null;
company_id INT, wa_number TEXT, label TEXT, permission_type INT, $this->row = $url === null ? false
tercero_id INT NULL, modulos_json TEXT NULL, es_supervisor INT DEFAULT 0, is_active INT DEFAULT 1)"); : ['url' => $url, 'method' => 'GET', 'params' => null];
} elseif (str_contains($this->sql, 'company_phones')) {
$this->row = FakeDb::$phones[$p[1] ?? ''] ?? false;
} else {
$this->row = false;
}
return true;
}
public function fetch() { return $this->row; }
public function fetchColumn() { return $this->row ? array_values($this->row)[0] : false; }
public function fetchAll(): array { return $this->row ? [$this->row] : []; }
}
class FakeDb
{
/** endpoint_key => url absoluta hacia el servidor de fixtures */
public static array $endpoints = [];
/** wa_number => fila de company_phones */
public static array $phones = [];
public function prepare(string $sql): FakeStmt { return new FakeStmt($sql); }
public function query(string $sql): FakeStmt { $s = new FakeStmt($sql); $s->execute(); return $s; }
public function exec(string $sql): int { return 0; }
}
function db(): FakeDb
{
static $db = null;
if ($db !== null) return $db;
$db = new FakeDb();
// Los endpoints salen del seed real: mismas claves y placeholders, otra base. // Los endpoints salen del seed real: mismas claves y placeholders, otra base.
$seed = file_get_contents(dirname(__DIR__) . '/seed_palmas.php'); $seed = file_get_contents(dirname(__DIR__) . '/seed_palmas.php');
preg_match_all("/\['key' => '([^']+)',\s*'dir' => '([^']+)',\s*'url' => \\\$BASE \. '([^']*)'/", $seed, $m); preg_match_all("/\['key' => '([^']+)',\s*'dir' => '([^']+)',\s*'url' => \\\$BASE \. '([^']*)'/", $seed, $m);
$ins = $pdo->prepare("INSERT INTO company_endpoints (company_id, endpoint_key, direction, url) VALUES (1, ?, ?, ?)");
foreach ($m[1] as $i => $key) { foreach ($m[1] as $i => $key) {
$ins->execute([$key, $m[2][$i], FIXTURE_BASE . $m[3][$i]]); FakeDb::$endpoints[$key] = FIXTURE_BASE . $m[3][$i];
} }
return $pdo; return $db;
}
/** Registra un número con perfil para los escenarios de trabajador/supervisor. */
function fakePhone(string $wa, ?int $tercero, int $esSupervisor): void
{
FakeDb::$phones[$wa] = [
'tercero_id' => $tercero,
'modulos_json' => null,
'es_supervisor' => $esSupervisor,
];
} }
// ── ConversationContext en memoria ─────────────────────────────────────────── // ── ConversationContext en memoria ───────────────────────────────────────────
+2 -3
View File
@@ -84,9 +84,8 @@ check('y la trazabilidad', $post['body']['telefono'] ?? null, '57300AUSEN');
// ════ 3. Ausentismo según el perfil del número ═══════════════════════════════ // ════ 3. Ausentismo según el perfil del número ═══════════════════════════════
echo "\nPerfil — trabajador vinculado y supervisor\n"; echo "\nPerfil — trabajador vinculado y supervisor\n";
db()->exec("INSERT INTO company_phones (company_id, wa_number, label, permission_type, tercero_id, es_supervisor) fakePhone('57300TRABAJADOR', 412, 0);
VALUES (1, '57300TRABAJADOR', 'Julio', 1, 412, 0), fakePhone('57300JEFE', 412, 1);
(1, '57300JEFE', 'Sup', 3, 412, 1)");
$ctx = contexto('57300TRABAJADOR'); $ctx = contexto('57300TRABAJADOR');
$r = NormalBot::processInteractive($co, $ctx, 'registrar_ausentismo'); $r = NormalBot::processInteractive($co, $ctx, 'registrar_ausentismo');