#!/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(); })();