- F0 blindaje: Redis/Celery, pg_hba sin trust LAN, secretos a env 600, backups, webhook auth, jobs muertos - F1 caja: bugs POS, reembolsos al corte, apartados, CFDI, topes de descuento en cotizacion/apartado - F2/F7 IA de catalogo: autocategorizacion, dedup de SKUs con embeddings, OCR de facturas, descripciones, claves SAT, limpieza de fitment - F3 IA en el flujo: vision Yonke, preguntas MeLi, venta cruzada, anomalias, sinonimos, busqueda semantica (rerank/embeddings) - F4/F8 escala y cartera: /metrics+Prometheus+Grafana, pool, score de credito, churn, metricas multi-tenant - Nexus Yonke (M1-M6): modelo de datos 1-de-1, alta por donador, vitrina publica, MeLi usado, consignacion, garantia por dias, reportes, etiquetas - Marketplace B2B entre yonkes (catalogo + pedidos cross-tenant) - Seguridad: auth en /api/admin/* (35 endpoints), alertas por tenant - Migraciones v4.27/v4.28; timers expire-demos y rollup-metrics
86 lines
3.6 KiB
JavaScript
86 lines
3.6 KiB
JavaScript
#!/usr/bin/env node
|
|
/* Auditoría JS: carga cada página del POS y reporta errores de consola y handlers rotos. */
|
|
const { chromium } = require('playwright');
|
|
const { execSync } = require('child_process');
|
|
|
|
const JWT_SECRET = execSync(
|
|
"grep -oP 'POS_JWT_SECRET=\\K.*' /etc/systemd/system/nexus-pos.service | head -1"
|
|
).toString().trim();
|
|
|
|
function makeToken() {
|
|
const header = { alg: 'HS256', typ: 'JWT' };
|
|
const now = Math.floor(Date.now() / 1000);
|
|
const payload = {
|
|
tenant_id: 32, employee_id: 1, name: 'Audit Admin', role: 'owner',
|
|
branch_id: 1, max_discount_pct: 100,
|
|
permissions: ['pos.sell', 'pos.view', 'pos.remission', 'pos.discount', 'pos.cancel',
|
|
'catalog.view', 'inventory.view', 'inventory.create', 'inventory.edit',
|
|
'inventory.adjust', 'inventory.transfer', 'customers.view', 'customers.create',
|
|
'invoicing.view', 'invoicing.create', 'reports.view', 'config.view', 'config.edit',
|
|
'config.edit_prices', 'workshop.view', 'workshop.edit', 'dashboard.view'],
|
|
device_id: 'audit', type: 'pos_access', exp: now + 900, iat: now,
|
|
};
|
|
const b64 = (o) => Buffer.from(JSON.stringify(o)).toString('base64url');
|
|
const crypto = require('crypto');
|
|
const sig = crypto.createHmac('sha256', JWT_SECRET)
|
|
.update(b64(header) + '.' + b64(payload)).digest('base64url');
|
|
return b64(header) + '.' + b64(payload) + '.' + sig;
|
|
}
|
|
|
|
const PAGES = [
|
|
'/pos/sale', '/pos/inventory', '/pos/customers', '/pos/config',
|
|
'/pos/dashboard', '/pos/invoicing', '/pos/remission-notes', '/pos/catalog',
|
|
'/pos/quotations', '/pos/reports', '/pos/accounting', '/pos/workshop',
|
|
'/pos/whatsapp', '/pos/marketplace-external',
|
|
];
|
|
|
|
(async () => {
|
|
const browser = await chromium.launch();
|
|
const ctx = await browser.newContext();
|
|
const page = await ctx.newPage();
|
|
|
|
const brokenHandlers = [];
|
|
await page.addInitScript((token) => {
|
|
localStorage.setItem('pos_token', token);
|
|
localStorage.setItem('pos_employee', JSON.stringify({ id: 1, name: 'Audit Admin', role: 'owner' }));
|
|
localStorage.setItem('pos_tenant_id', '32');
|
|
document.cookie = 'pos_role=owner; path=/pos; SameSite=Lax';
|
|
// Capturar errores de handlers inline (onclick con función inexistente)
|
|
window.addEventListener('error', function (e) {
|
|
if (e.message && /is not a function|is not defined/.test(e.message)) {
|
|
(window.__broken = window.__broken || []).push(e.message + ' @ ' + location.pathname);
|
|
}
|
|
});
|
|
}, makeToken());
|
|
|
|
for (const p of PAGES) {
|
|
const errs = [];
|
|
const onConsole = (m) => {
|
|
if (m.type() === 'error') errs.push(m.text().slice(0, 160));
|
|
};
|
|
page.on('pageerror', (e) => errs.push('PAGEERROR: ' + String(e).slice(0, 160)));
|
|
page.on('console', onConsole);
|
|
|
|
try {
|
|
await page.goto('http://localhost:5001' + p, { waitUntil: 'load', timeout: 20000 });
|
|
await page.waitForTimeout(2200);
|
|
// Los errores capturados en window.__broken (handlers inline)
|
|
const inline = await page.evaluate(() => (window.__broken || []).splice(0));
|
|
const realErrs = errs.filter((t) =>
|
|
!/favicon|net::ERR|Failed to load resource|404|sourcemap|DevTools/i.test(t));
|
|
if (realErrs.length || inline.length) {
|
|
console.log('✗ ' + p);
|
|
realErrs.slice(0, 4).forEach((t) => console.log(' consola: ' + t));
|
|
inline.slice(0, 6).forEach((t) => console.log(' handler: ' + t));
|
|
} else {
|
|
console.log('✓ ' + p);
|
|
}
|
|
} catch (e) {
|
|
console.log('✗✗ ' + p + ' → ' + e.message.slice(0, 120));
|
|
}
|
|
page.removeAllListeners('console');
|
|
page.removeAllListeners('pageerror');
|
|
}
|
|
await browser.close();
|
|
})();
|