Mejoras de sesion: estabilizacion, IA, Nexus Yonke y marketplace B2B
- 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:
@@ -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">← Inicio</a>
|
||||
<button class="theme-toggle" onclick="toggleTheme()" id="themeToggle">
|
||||
<span id="themeIcon">☾</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">
|
||||
|
||||
@@ -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 ──
|
||||
|
||||
@@ -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) {
|
||||
|
||||
2086
dashboard/server.py
2086
dashboard/server.py
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user