Mejoras de sesion: estabilizacion, IA, Nexus Yonke y marketplace B2B
Some checks failed
CI / lint-and-test (3.11) (push) Has been cancelled
CI / lint-and-test (3.13) (push) Has been cancelled

- 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
This commit is contained in:
2026-10-12 00:57:31 +00:00
parent f2d4558b80
commit a386ca5b4a
165 changed files with 31313 additions and 2203 deletions

69
test_double_refresh.js Normal file
View File

@@ -0,0 +1,69 @@
#!/usr/bin/env node
/* Reproduce double-refresh on tab change: count document loads per navigation. */
const { chromium } = require('playwright');
const fs = require('fs');
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();
// Sign a token identical to auth_bp.login (HS256, 10 min)
function makeToken() {
const header = { alg: 'HS256', typ: 'JWT' };
const now = Math.floor(Date.now() / 1000);
const payload = {
tenant_id: 45, employee_id: 1, name: 'Test Owner', role: 'owner',
branch_id: 1, max_discount_pct: 100,
permissions: ['pos.sell', 'pos.view', 'pos.remission', 'catalog.view',
'inventory.view', 'inventory.create', 'customers.view', 'customers.create',
'invoicing.view', 'reports.view', 'config.view', 'config.edit',
'workshop.view', 'dashboard.view'],
device_id: 'pw-test', type: 'pos_access', exp: now + 600, 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;
}
(async () => {
const browser = await chromium.launch();
const ctx = await browser.newContext();
const page = await ctx.newPage();
let docLoads = [];
page.on('framenavigated', (f) => {
if (f === page.mainFrame()) {
docLoads.push(f.url());
}
});
page.on('console', (m) => {
const t = m.text();
if (/reload|SW_UPDATED|AppInit/i.test(t)) console.log('[console]', t);
});
await page.addInitScript((token) => {
localStorage.setItem('pos_token', token);
localStorage.setItem('pos_employee', JSON.stringify({ id: 1, name: 'Test Owner', role: 'owner' }));
localStorage.setItem('pos_tenant_id', '45');
document.cookie = 'pos_role=owner; path=/pos; SameSite=Lax';
}, makeToken());
// 1. Load dashboard
docLoads = [];
await page.goto('http://localhost:5001/pos/dashboard', { waitUntil: 'load' });
await page.waitForTimeout(2500);
console.log('LOAD /pos/dashboard → cargas de documento:', docLoads.length, docLoads);
// 2. Click sidebar link to Inventory (change tab)
docLoads = [];
const link = page.locator('a.nav-item[href="/pos/inventory"]').first();
await link.click({ timeout: 5000 }).catch((e) => console.log('click err:', e.message));
await page.waitForTimeout(3000);
console.log('CLICK a /pos/inventory → cargas de documento:', docLoads.length, docLoads);
console.log('URL final:', page.url());
await browser.close();
})();