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

@@ -23,6 +23,7 @@
<button data-mode="oem" onclick="setCatalogMode('oem')">OEM</button>
<button data-mode="local" onclick="setCatalogMode('local')">Local</button>
</div>
<a href="/usados" class="header-back" id="usedPartsLink" title="Partes usadas y reconstruidas">Usados</a>
<a href="/" class="header-back">&larr; Inicio</a>
<button class="theme-toggle" onclick="toggleTheme()" id="themeToggle">
<span id="themeIcon">&#9790;</span>
@@ -86,7 +87,7 @@
})();
</script>
<script src="/catalog-public.js"></script>
<script src="/catalog-public.js?v=2"></script>
<!-- AI Chat Widget -->
<link rel="stylesheet" href="/chat-public.css">

View File

@@ -27,7 +27,7 @@
nxPartType: null, // {slug, name} ← Nexpart part type
region: 'north-america',
mode: (localStorage.getItem('catalog_mode') === 'local' ? 'local' : 'oem'),
mode: (localStorage.getItem('catalog_mode') === 'oem' ? 'oem' : 'local'),
page: 1,
totalPages: 1,
};
@@ -64,6 +64,7 @@
state.brand = state.model = state.year = state.engine = null;
state.level = 'brands';
loadBrands();
waConfig(function () {}); // preload WhatsApp contact config
};
// ── Region selector (global) ──
@@ -109,6 +110,31 @@
loadBrands();
}
// ── Link a la vitrina de partes usadas del tenant (si existe) ──
(function () {
var link = document.getElementById('usedPartsLink');
if (!link) return;
var parts = window.location.hostname.split('.');
var sub = (parts.length >= 3) ? parts[0]
: (parts.length === 2 && parts[1] === 'localhost') ? parts[0] : '';
var reserved = ['www', 'api', 'admin', 'mail', 'staging', 'dev', 'nexus', 'pos', 'app', 'dashboard'];
if (reserved.indexOf(sub) !== -1) sub = '';
var urlParams2 = new URLSearchParams(window.location.search);
var tenant = urlParams2.get('tenant') || sub;
if (!tenant) return;
fetch('/api/public/used-parts?per_page=1&tenant=' + encodeURIComponent(tenant))
.then(function (r) { return r.ok ? r.json() : null; })
.then(function (d) {
if (d && d.total > 0) {
link.href = '/usado/' + encodeURIComponent(d.tenant.subdomain || tenant);
link.textContent = 'Usados (' + d.total + ')';
} else {
link.style.display = 'none';
}
})
.catch(function () { link.style.display = 'none'; });
})();
// Enter on search
// ── Smart search detector ──
function detectQueryType(raw) {
@@ -729,6 +755,7 @@
}
html += '<button class="part-detail-btn" onclick="openDetail(' + p.id_part + ')">Ver detalle y alternativas</button>';
html += waQuoteButtonHtml(p);
html += '</div>';
if (p.image_url) {
html += '<img class="part-img" src="' + esc(p.image_url) + '" alt="" loading="lazy" onerror="this.style.display=\'none\'">';
@@ -782,6 +809,26 @@
content.innerHTML = html;
}
// ── WhatsApp quote button ──
var _waConfig = null;
function waConfig(cb) {
if (_waConfig) return cb(_waConfig);
fetch('/api/public/contact-config').then(function (r) { return r.json(); })
.then(function (d) { _waConfig = d || { enabled: false }; cb(_waConfig); })
.catch(function () { _waConfig = { enabled: false }; cb(_waConfig); });
}
function waQuoteButtonHtml(p) {
var cfg = _waConfig;
if (!cfg || !cfg.enabled || !cfg.wa_number) return '';
var text = 'Hola, me interesa cotizar esta pieza:%0A%0A*OEM:* ' + encodeURIComponent(p.oem_part_number || '') +
'%0A*Descripción:* ' + encodeURIComponent(p.name || '');
if (p.vehicle_info) text += '%0A*Vehículo:* ' + encodeURIComponent(p.vehicle_info);
return '<a class="btn-wa-quote" target="_blank" rel="noopener" href="https://wa.me/' + cfg.wa_number + '?text=' + text + '"' +
' style="display:inline-flex;align-items:center;gap:8px;margin-top:var(--space-4);padding:12px 20px;border-radius:var(--radius-md);background:#25D366;color:#fff;text-decoration:none;font-weight:600;">' +
'Cotizar por WhatsApp</a>';
}
// ── Part detail ──
function renderDetail(d, body) {
if (!d || !d.part) {
@@ -831,7 +878,7 @@
html += '</tbody></table></div>';
}
body.innerHTML = html;
waConfig(function () { html += waQuoteButtonHtml(p); body.innerHTML = html; });
}
// ── Helpers ──

View File

@@ -31,14 +31,14 @@
}
var navLinks = [
{ label: 'Demo', href: '/demo' },
{ label: 'Tienda', href: '/tienda' },
{ label: 'Demo', href: '/demo.html' },
{ label: 'Tienda', href: '/tienda.html' },
{ label: 'Cat\u00e1logo', href: '/index.html' },
{ label: 'Captura', href: '/captura' },
{ label: 'POS', href: '/pos' },
{ label: 'Cuentas', href: '/cuentas' },
{ label: 'Bodega', href: '/bodega' },
{ label: 'Admin', href: '/admin' }
{ label: 'Captura', href: '/captura.html' },
{ label: 'POS', href: '/pos/login' },
{ label: 'Cuentas', href: '/cuentas.html' },
{ label: 'Bodega', href: '/bodega.html' },
{ label: 'Admin', href: '/admin.html' }
];
var linksHTML = navLinks.map(function (link) {

File diff suppressed because it is too large Load Diff