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

View File

@@ -44,7 +44,7 @@
var roleLabels = {
'owner': _t('role_owner'), 'admin': _t('role_admin'), 'cashier': _t('role_cashier'),
'warehouse': _t('role_warehouse'), 'accountant': _t('role_accountant'),
'workshop': 'Taller', 'mechanic': 'Mecanico'
'workshop': 'Taller', 'mechanic': 'Mecanico', 'driver': 'Repartidor'
};
var roleLabel = roleLabels[role] || role;
var initials = name.split(' ').map(function(p) { return p[0]; }).join('').toUpperCase().substring(0, 2);
@@ -199,6 +199,8 @@
homeHref = '/pos/sale';
} else if (userRole === 'workshop' && userPerms.indexOf('workshop.view') !== -1 && moduleEnabled('workshop')) {
homeHref = '/pos/workshop';
} else if (userRole === 'driver' && userPerms.indexOf('pos.remission') !== -1) {
homeHref = '/pos/remission-notes';
}
document.querySelectorAll('a[href="/pos/dashboard"]').forEach(function(a) {
a.setAttribute('href', homeHref);
@@ -208,8 +210,8 @@
function isPageAllowed(pagePath, userRole, userPerms) {
if (userRole === 'owner' || userRole === 'admin') return true;
// Restricted roles (workshop/mechanic/counter/cashier) see modules based on permissions.
if (['workshop', 'mechanic', 'counter', 'cashier'].indexOf(userRole) !== -1) {
// Restricted roles (workshop/mechanic/counter/cashier/driver) see modules based on permissions.
if (['workshop', 'mechanic', 'counter', 'cashier', 'driver'].indexOf(userRole) !== -1) {
var allowed = [];
if ((userRole === 'workshop' || userRole === 'mechanic') && moduleEnabled('workshop')) {
allowed = ['/pos/workshop'];
@@ -226,7 +228,8 @@
'quotations.view': '/pos/quotations',
'accounting.view': '/pos/accounting',
'reports.view': '/pos/reports',
'dashboard.view': '/pos/dashboard'
'dashboard.view': '/pos/dashboard',
'yonke.view': '/pos/yonke'
};
for (var p in permMap) {
if (userPerms.indexOf(p) !== -1 && allowed.indexOf(permMap[p]) === -1) {
@@ -251,7 +254,7 @@
var fallback = null;
if (userRole === 'owner' || userRole === 'admin') {
fallback = '/pos/dashboard';
} else if (['workshop', 'mechanic', 'counter', 'cashier'].indexOf(userRole) !== -1) {
} else if (['workshop', 'mechanic', 'counter', 'cashier', 'driver'].indexOf(userRole) !== -1) {
var allowed = [];
if ((userRole === 'workshop' || userRole === 'mechanic') && moduleEnabled('workshop')) {
allowed = ['/pos/workshop'];
@@ -268,7 +271,8 @@
'quotations.view': '/pos/quotations',
'accounting.view': '/pos/accounting',
'reports.view': '/pos/reports',
'dashboard.view': '/pos/dashboard'
'dashboard.view': '/pos/dashboard',
'yonke.view': '/pos/yonke'
};
for (var p in permMap) {
if (userPerms.indexOf(p) !== -1 && allowed.indexOf(permMap[p]) === -1) {
@@ -350,13 +354,18 @@
backBtn.style.display = 'none';
})();
// ─── Service Worker update handler ───
// ─── Service Worker update handler (patrón controllerchange) ───
// Recarga SOLO cuando el SW que controlaba esta página fue reemplazado
// (deploy con assets nuevos). La primera activación (sin controlador
// previo) NO recarga — así se elimina el doble refresh al cambiar de
// pestaña tras cada deploy.
var _hadSwController = !!navigator.serviceWorker.controller;
var _swRefreshing = false;
if ('serviceWorker' in navigator) {
navigator.serviceWorker.addEventListener('message', function (event) {
if (event.data && event.data.type === 'SW_UPDATED') {
console.log('[AppInit] SW updated to', event.data.cacheName, '— reloading...');
window.location.reload();
}
navigator.serviceWorker.addEventListener('controllerchange', function () {
if (!_hadSwController || _swRefreshing) return;
_swRefreshing = true;
window.location.reload();
});
}