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

@@ -112,7 +112,8 @@ const Config = (() => {
warehouse: 'Almacenista',
accountant: 'Contador',
workshop: 'Taller',
mechanic: 'Mecanico'
mechanic: 'Mecanico',
driver: 'Repartidor'
};
var ROLE_BADGE = {
@@ -123,7 +124,8 @@ const Config = (() => {
warehouse: 'badge--yellow',
accountant: 'badge--purple',
workshop: 'badge--orange',
mechanic: 'badge--teal'
mechanic: 'badge--teal',
driver: 'badge--gray'
};
function roleBadge(role) {
@@ -513,6 +515,15 @@ const Config = (() => {
setChecked('receipt-show-iva', d.show_iva_breakdown);
setChecked('receipt-show-payment', d.show_payment_details);
setChecked('receipt-show-employee', d.show_employee);
setChecked('receipt-auto-print', d.auto_print);
fetch(API + '/invoice-settings', { headers: headers() }).then(function (r) { return r.json(); }).then(function (s) {
var a = document.getElementById('cfg-invoice-auto');
var b = document.getElementById('cfg-invoice-email');
var c = document.getElementById('cfg-invoice-global');
if (a) a.checked = !!s.invoice_auto;
if (b) b.checked = !!s.invoice_email;
if (c) c.checked = !!s.invoice_global;
}).catch(function () {});
renderReceiptLogoThumb();
} catch (e) {
console.error('Config.loadReceiptConfig:', e);
@@ -599,13 +610,26 @@ const Config = (() => {
show_iva_breakdown: getChecked('receipt-show-iva'),
show_payment_details: getChecked('receipt-show-payment'),
show_employee: getChecked('receipt-show-employee'),
invoice_auto: document.getElementById('cfg-invoice-auto') ? document.getElementById('cfg-invoice-auto').checked : undefined,
invoice_email: document.getElementById('cfg-invoice-email') ? document.getElementById('cfg-invoice-email').checked : undefined,
invoice_global: document.getElementById('cfg-invoice-global') ? document.getElementById('cfg-invoice-global').checked : undefined,
auto_print: getChecked('receipt-auto-print'),
};
try {
var inv = {};
['invoice_auto', 'invoice_email', 'invoice_global'].forEach(function (k) {
if (data[k] !== undefined) { inv[k] = data[k]; delete data[k]; }
});
var res = await fetch(API + '/receipt', {
method: 'PUT',
headers: headers(),
body: JSON.stringify(data),
});
if (Object.keys(inv).length) {
await fetch(API + '/invoice-settings', {
method: 'PUT', headers: headers(), body: JSON.stringify(inv),
});
}
if (!res.ok) {
var err = await res.json().catch(function() { return { error: res.statusText }; });
throw new Error(err.error || 'Error al guardar');
@@ -628,6 +652,7 @@ const Config = (() => {
await saveAllowedBrands();
await saveModules();
await saveSalesSettings();
await saveAlertContacts();
await saveReceiptConfig();
toast('Configuración guardada', 'ok');
} catch (e) {
@@ -842,6 +867,53 @@ const Config = (() => {
}
}
// -------------------------------------------------------------------------
// Tier Discounts (Taller / Mayoreo)
// -------------------------------------------------------------------------
async function loadTierDiscounts() {
try {
var res = await fetch('/pos/api/inventory/tier-discounts', { headers: headers() });
if (!res.ok) return;
var d = await res.json();
(d.data || []).forEach(function(t) {
var el = document.getElementById('cfg-tier-disc-' + t.tier_id);
if (el) el.value = t.discount_pct;
});
} catch (e) {
console.error('Config.loadTierDiscounts:', e);
}
}
async function saveTierDiscounts() {
var el2 = document.getElementById('cfg-tier-disc-2');
var el3 = document.getElementById('cfg-tier-disc-3');
var statusEl = document.getElementById('tier-discount-status');
var btn = document.getElementById('btn-save-tier-discounts');
if (!el2 || !el3) return;
var d2 = Math.max(0, Math.min(100, parseFloat(el2.value) || 0));
var d3 = Math.max(0, Math.min(100, parseFloat(el3.value) || 0));
if (btn) { btn.disabled = true; btn.textContent = 'Guardando...'; }
try {
var res = await fetch('/pos/api/inventory/tier-discounts', {
method: 'PUT',
headers: headers(),
body: JSON.stringify({ discount_pct_2: d2, discount_pct_3: d3 })
});
if (!res.ok) {
var err = await res.json().catch(function() { return { error: res.statusText }; });
throw new Error(err.error || 'Save failed');
}
toast('Descuentos por tipo de cliente guardados');
if (statusEl) statusEl.textContent = 'Taller -' + d2 + '% · Mayoreo -' + d3 + '%';
} catch (e) {
toast(e.message, 'error');
} finally {
if (btn) { btn.disabled = false; btn.textContent = 'Guardar Descuentos'; }
}
}
// -------------------------------------------------------------------------
// Vehicle Compatibility Source
// -------------------------------------------------------------------------
@@ -1059,6 +1131,50 @@ const Config = (() => {
}
}
// -------------------------------------------------------------------------
// Alert contacts (per-business WhatsApp alerts)
// -------------------------------------------------------------------------
async function loadAlertContacts() {
try {
var res = await fetch(API + '/alert-contacts', { headers: headers() });
if (!res.ok) return;
var data = await res.json();
var input = document.getElementById('alert-phones');
if (input) input.value = (data.alert_phones || []).join(', ');
var cb = document.getElementById('cfg-alert-whatsapp-enabled');
if (cb) cb.checked = data.alert_whatsapp_enabled === true;
} catch (e) {
console.error('Config.loadAlertContacts:', e);
}
}
async function saveAlertContacts() {
if (!checkAuth()) return;
var input = document.getElementById('alert-phones');
var cb = document.getElementById('cfg-alert-whatsapp-enabled');
if (!input && !cb) return;
var btn = document.getElementById('btn-save-alert-contacts');
if (btn) { btn.disabled = true; btn.textContent = 'Guardando...'; }
try {
var res = await fetch(API + '/alert-contacts', {
method: 'PUT',
headers: headers(),
body: JSON.stringify({
alert_phones: input ? input.value : '',
alert_whatsapp_enabled: cb ? cb.checked : false,
}),
});
var data = await res.json().catch(function() { return { error: res.statusText }; });
if (!res.ok) throw new Error(data.error || 'Error al guardar');
if (input) input.value = (data.alert_phones || []).join(', ');
toast('Contactos de alerta guardados', 'ok');
} catch (e) {
toast(e.message, 'error');
} finally {
if (btn) { btn.disabled = false; btn.textContent = 'Guardar alertas'; }
}
}
// -------------------------------------------------------------------------
// Tab navigation
// -------------------------------------------------------------------------
@@ -1293,6 +1409,12 @@ const Config = (() => {
btnBrands.addEventListener('click', saveAllowedBrands);
}
// Alert contacts save button (per-business WhatsApp alerts)
var btnAlertContacts = document.getElementById('btn-save-alert-contacts');
if (btnAlertContacts) {
btnAlertContacts.addEventListener('click', saveAlertContacts);
}
// Kiosk mode toggle
var kioskToggle = document.getElementById('cfg-kiosk-mode');
if (kioskToggle && window.NexusKiosk) {
@@ -1322,7 +1444,9 @@ const Config = (() => {
loadAllowedBrands();
loadModules();
loadSalesSettings();
loadAlertContacts();
loadReceiptConfig();
loadTierDiscounts();
if (isAdmin) {
loadRolePermissions();
loadWorkshopPermissions();
@@ -1353,8 +1477,10 @@ const Config = (() => {
deleteBranch,
loadBusiness, saveBusiness, saveTaxParams, saveAll,
loadCurrency, saveCurrency,
loadTierDiscounts, saveTierDiscounts,
loadVehicleCompatSource, saveVehicleCompatSource,
loadModules, saveModules,
loadAlertContacts, saveAlertContacts,
loadReceiptConfig, saveReceiptConfig, handleReceiptLogo, removeReceiptLogo,
openModal, closeModal, openBranchModal, editBranch,
switchTab, loadRolePermissions, renderRolePermissions, saveRolePermissions,