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

@@ -19,6 +19,9 @@
<link rel="stylesheet" href="/pos/static/css/pos.css?v=35"></head>
<body class="pos-shell" id="appBody">
<div id="offlineBanner" style="display:none;position:fixed;top:0;left:0;right:0;z-index:9999;padding:10px 16px;align-items:center;justify-content:center;gap:8px;font-size:var(--text-body-sm);">
<span id="offlineBannerText"></span>
</div>
<!-- ================================================================
STATUS BAR
@@ -63,6 +66,13 @@
placeholder="Buscar por nombre, No. parte, codigo..."
autocomplete="off" spellcheck="false" aria-label="Buscar productos" />
</div>
<div class="search-wrap" style="max-width:260px;display:flex;gap:6px;">
<input class="search-input" type="text" id="vehicleLookup"
placeholder="Placa o VIN"
autocomplete="off" spellcheck="false" aria-label="Placa o VIN"
onkeydown="if(event.key==='Enter'){event.preventDefault();POS.vehicleLookup();}" />
<button class="btn-secondary-action" onclick="POS.vehicleLookup()" title="Ver piezas compatibles">🚗</button>
</div>
</div>
<!-- Search Results Dropdown -->
@@ -177,6 +187,15 @@
<span class="margin-summary__value" id="avgMargin">--</span>
</div>
<!-- Cross-sell suggestions (A10) -->
<div id="crossSellPanel" style="display:none;margin:6px 0;padding:8px 10px;border:1px dashed var(--color-border);border-radius:var(--radius-sm);background:var(--color-bg-base);">
<div style="display:flex;justify-content:space-between;align-items:center;font-size:var(--text-caption);font-weight:var(--font-weight-semibold);color:var(--color-text-muted);text-transform:uppercase;letter-spacing:.4px;">
<span>&iquest;Ya que est&aacute;s en eso&hellip;?</span>
<button type="button" title="Ocultar" onclick="POS.hideCrossSell()" style="background:transparent;border:none;color:var(--color-text-muted);cursor:pointer;font-size:14px;">&times;</button>
</div>
<div id="crossSellList" style="display:flex;flex-direction:column;gap:4px;margin-top:6px;"></div>
</div>
<!-- Cart Footer -->
<div class="cart-footer">
<div class="totals-block">
@@ -224,6 +243,9 @@
<button class="btn-secondary-action" onclick="POS.createLayaway()" title="Apartado (requiere cliente)">Apartado</button>
<button class="btn-secondary-action" onclick="POS.createServiceOrder()" title="Orden de servicio (F7)">Orden Taller</button>
<button class="btn-secondary-action" onclick="POS.showLastSale()" title="Ultima venta (F5)">Ult.Venta</button>
<span id="offlineQueueBadge" style="display:none;background:var(--color-warning);color:#fff;border-radius:999px;padding:2px 10px;font-weight:700;font-size:var(--text-caption);">0</span>
<button class="btn-secondary-action" onclick="POS.openReturnModal()" title="Devolución de venta">Devolución</button>
<button class="btn-secondary-action" onclick="POS.openLayawaysModal()" title="Apartados activos">Apartados</button>
<button class="btn-secondary-action" onclick="POS.showCutZModal()" title="Corte Z - Cerrar caja">Corte Z</button>
<button class="btn-secondary-action danger" id="btnCancelSale" onclick="POS.openCancelModal()" title="Cancelar (Esc)">Cancelar</button>
</div>
@@ -325,7 +347,15 @@
<label class="form-label">Monto recibido</label>
<input type="number" class="form-input form-input-lg" id="cashReceived"
placeholder="0.00" step="0.01" min="0" oninput="POS.updateChange()" />
</div>
<div style="display:flex;align-items:center;gap:8px;margin-top:8px;">
<label style="font-size:var(--text-caption);color:var(--color-text-muted);">Moneda:</label>
<select id="payCurrency" class="form-input" style="width:90px;padding:4px 8px;" onchange="POS.updateChange()">
<option value="MXN">MXN $</option>
<option value="USD">USD</option>
</select>
<span id="usdRateHint" style="font-size:var(--text-caption);color:var(--color-text-muted);"></span>
</div></div>
<div class="quick-amounts" id="quickAmounts">
<!-- Populated dynamically -->
</div>
@@ -416,6 +446,14 @@
<span class="cfdi-hint" id="cfdiHint"></span>
</div>
<!-- Repartidor (opcional) -->
<div class="form-group" style="margin-top: 12px;">
<label class="form-label" for="saleCourier">Repartidor (opcional)</label>
<select class="form-input" id="saleCourier">
<option value="">— Sin repartidor —</option>
</select>
</div>
<!-- Footer -->
<div class="modal-footer">
<button class="btn btn-ghost" onclick="POS.closePaymentModal()">Cancelar</button>
@@ -729,11 +767,11 @@
<script src="/pos/static/js/i18n.js?v=39" defer></script>
<script src="/pos/static/js/kiosk.js" defer></script>
<script src="/pos/static/js/pos-utils.js?v=33" defer></script>
<script src="/pos/static/js/app-init.js?v=8" defer></script>
<script src="/pos/static/js/app-init.js?v=9" defer></script>
<script src="/pos/static/js/splash-loader.js?v=33" defer></script>
<script src="/pos/static/js/push.js" defer></script>
<script src="/pos/static/js/printer.js" defer></script>
<script src="/pos/static/js/pos.js?v=42" defer></script>
<script src="/pos/static/js/pos.js?v=46" defer></script>
<script>
// Cancel sale button wiring
@@ -798,7 +836,68 @@
<script src="/pos/static/js/chat.js" defer></script>
<script src="/pos/static/js/sync-engine.js" defer></script>
<script src="/pos/static/js/offline-banner.js" defer></script>
<script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script>
<script src="/pos/static/js/pwa-install.js" defer></script>
<!-- ══════════ RETURN MODAL ══════════ -->
<div class="modal-overlay" id="returnModal">
<div class="modal-box" style="max-width:640px;">
<div class="modal-header">
<h3>Devolución de venta</h3>
<button class="btn-close" onclick="POS.closeReturnModal()">&times;</button>
</div>
<div class="modal-body">
<div style="display:flex;gap:8px;margin-bottom:12px;">
<input type="number" id="returnSaleId" class="form-input" placeholder="Folio de venta (ej. 123)" style="flex:1;" />
<button class="btn btn-primary" onclick="POS.searchReturnSale()">Buscar</button>
</div>
<div id="returnSaleInfo" style="display:none;margin-bottom:10px;font-size:var(--text-body-sm);color:var(--color-text-muted);"></div>
<div id="returnItemsList" style="display:none;max-height:280px;overflow-y:auto;border:1px solid var(--color-border);border-radius:8px;"></div>
<div style="margin-top:10px;">
<input type="text" id="returnNotes" class="form-input" placeholder="Notas (opcional)" />
</div>
<div id="returnResult" style="margin-top:10px;font-size:var(--text-body-sm);"></div>
</div>
<div class="modal-footer" id="returnFooter" style="display:none;">
<button class="btn btn-ghost" onclick="POS.closeReturnModal()">Cerrar</button>
<button class="btn btn-primary" onclick="POS.submitReturn()">Registrar devolución</button>
</div>
</div>
</div>
<!-- ══════════ LAYAWAYS MODAL ══════════ -->
<div class="modal-overlay" id="layawaysModal">
<div class="modal-box" style="max-width:720px;">
<div class="modal-header">
<h3>Apartados activos</h3>
<button class="btn-close" onclick="POS.closeLayawaysModal()">&times;</button>
</div>
<div class="modal-body">
<div id="layawaysList" style="max-height:340px;overflow-y:auto;">
<span style="color:var(--color-text-muted);font-size:var(--text-caption);">Cargando...</span>
</div>
<div id="layawayActions" style="display:none;margin-top:12px;border-top:1px solid var(--color-border);padding-top:12px;">
<div id="layawayDetail" style="font-size:var(--text-body-sm);margin-bottom:10px;"></div>
<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;">
<input type="number" id="layawayPayAmount" class="form-input" placeholder="Monto abono" style="width:140px;" />
<select id="layawayPayMethod" class="form-input" style="width:150px;">
<option value="efectivo">Efectivo</option>
<option value="tarjeta">Tarjeta</option>
<option value="transferencia">Transferencia</option>
</select>
<button class="btn btn-primary" onclick="POS.layawayPay()">Abonar</button>
<button class="btn btn-primary" onclick="POS.layawayComplete()" style="background:var(--color-success);">Liquidar</button>
<button class="btn btn-ghost" onclick="POS.layawayCancel()" style="color:var(--color-error);">Cancelar apartado</button>
</div>
<div id="layawayResult" style="margin-top:8px;font-size:var(--text-body-sm);"></div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-ghost" onclick="POS.closeLayawaysModal()">Cerrar</button>
</div>
</div>
</div>
</body>
</html>