Files
fspm-web/deploy/verificar-produccion.mjs
Uriel JarethandClaude Opus 5 15d31b5d6b deploy: la demo queda en línea en fspm-demo.urieljareth.org
Desplegada y verificada de punta a punta contra el dominio público, en
Chromium y en WebKit a 375px: sitio, catálogo filtrado, acceso al panel,
persistencia de sesión y captura de leads con atribución de campaña.

No quedó como recurso de Coolify, y está documentado por qué: la API de esa
instancia no expone creación de aplicaciones (las rutas /applications/*
responden 404 con cuerpo vacío y /openapi.yaml devuelve el HTML del panel),
y al crear un servicio por /services la tarea de despliegue quedó colgada sin
escribir nada en disco. El servidor está sano; el problema es la instancia.

El contenedor corre en el Docker del LXC 102, en la red coolify, con las
etiquetas de Traefik que replican el patrón de las apps que ya sirven con TLS
ahí, y con coolify.managed=false para que nadie lo confunda con un recurso del
panel. La imagen se construye dentro del LXC desde el repo público de Gitea.

El costo de esta ruta está escrito en la documentación: un push no redespliega.
Se agregó el procedimiento de tres comandos para publicar una versión nueva sin
tocar el volumen, y la vía para migrar a app git-based si se consigue el acceso
al panel web.

Se documenta también una carrera que provocó un diagnóstico equivocado: el
acceso usa una acción de servidor, y pulsar el botón antes de que React hidrate
no hace nada ni da error. La primera verificación fallaba solo por el dominio
público y funcionaba contra Traefik directo, lo que parecía señalar a
Cloudflare. No era: los chunks son idénticos por ambos caminos y la hidratación
es igual. Era la espera de la prueba.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-08-28 10:23:12 -06:00

52 lines
2.5 KiB
JavaScript

import { chromium, webkit } from "@playwright/test";
const BASE = "https://fspm-demo.urieljareth.org";
let fallas = 0;
const ok = (c, m) => { console.log(`${c ? " ok " : "FALLA "} ${m}`); if (!c) fallas++; };
for (const [nombre, motor, viewport] of [
["chromium 1440", chromium, { width: 1440, height: 900 }],
["webkit 375", webkit, { width: 375, height: 780 }],
]) {
console.log(`\n=== ${nombre} ===`);
const nav = await motor.launch();
const pag = await nav.newPage({ viewport, locale: "es-MX" });
// sitio público
await pag.goto(BASE + "/", { waitUntil: "domcontentloaded" });
ok((await pag.locator("h1").count()) === 1, "el inicio tiene un solo h1");
ok((await pag.locator('a[href^="/admin"]').count()) === 0, "el panel no está enlazado");
// la página no se corre de lado
const corre = await pag.evaluate(() => {
window.scrollTo(800, 0); const m = window.scrollX; window.scrollTo(0, 0); return m;
});
ok(corre <= 2, `el inicio no se desplaza en horizontal (se corrió ${corre}px)`);
// catálogo filtrado por escenario
await pag.goto(BASE + "/catalogo?escenario=gasolineras", { waitUntil: "domcontentloaded" });
ok((await pag.locator("h1").count()) === 1, "el catálogo filtrado carga");
// Acceso rápido al panel. Se espera `load` y un margen antes de pulsar: el
// botón dispara una acción de servidor y hasta que React no hidrata, el clic
// no hace nada. Con la latencia del dominio público esa carrera se pierde, y
// la primera versión de esta verificación culpó a Cloudflare por eso.
await pag.goto(BASE + "/admin/login", { waitUntil: "load" });
const perfil = pag.getByRole("button").filter({ hasText: /administrador/i }).first();
ok(await perfil.isVisible().catch(() => false), "el acceso rápido lista perfiles");
await pag.waitForTimeout(2500);
await perfil.click();
await pag.waitForURL(/\/admin(?!\/login)/, { timeout: 30000 }).catch(() => {});
ok(!/\/admin\/login/.test(pag.url()), `entró al panel (${pag.url().replace(BASE, "")})`);
// y la sesión persiste en otra página: eso es la cookie Secure funcionando
await pag.goto(BASE + "/admin/oportunidades", { waitUntil: "load" });
ok(!/\/admin\/login/.test(pag.url()), "la sesión persiste entre páginas");
const cuerpo = (await pag.locator("body").textContent()) ?? "";
ok(/OPP-\d{4}-\d{4}/.test(cuerpo), "el listado muestra oportunidades reales");
await nav.close();
}
console.log(fallas === 0 ? "\nTODO EN VERDE EN PRODUCCION" : `\n${fallas} FALLAS EN PRODUCCION`);
process.exit(fallas === 0 ? 0 : 1);