- Add MercadoLibre OAuth, listings, orders, webhooks and category search - New marketplace_external_bp.py, meli_service.py, marketplace_external_service.py - New marketplace_external.html/js with ML management UI - Inventory: bulk publish to ML with category autocomplete, listing type and shipping selectors - Inventory: new .btn--meli styles, select/label CSS fixes - WhatsApp bridge: rate limiting, 440/515/408 error handling, stale watchdog - DB migration v3.4_meli_integration.sql for marketplace_listings, orders, sync_queue - Add Celery tasks for ML sync and webhook processing - Sidebar: MercadoLibre navigation link
639 lines
30 KiB
HTML
639 lines
30 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="es" data-theme="industrial">
|
|
<head>
|
|
<script>/*pos_theme_early*/(function(){var t=localStorage.getItem("pos_theme")||"industrial";document.documentElement.setAttribute("data-theme",t);})()</script>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
<title>Nexus Autoparts — Punto de Venta</title>
|
|
<link rel="stylesheet" href="/pos/static/css/tokens.css" />
|
|
<link rel="stylesheet" href="/pos/static/css/common.css" />
|
|
<link rel="stylesheet" href="/pos/static/css/sidebar.css" />
|
|
<link rel="stylesheet" href="/pos/static/css/pos-glass.css" />
|
|
<link rel="stylesheet" href="/pos/static/css/chat.css" />
|
|
<link rel="manifest" href="/pos/static/pwa/manifest.json" />
|
|
<meta name="theme-color" content="#F5A623" />
|
|
<script src="/pos/static/js/native-bridge.js" defer></script>
|
|
|
|
<link rel="stylesheet" href="/pos/static/css/pos.css?v=4">
|
|
</head>
|
|
|
|
<body class="pos-shell" id="appBody">
|
|
|
|
<!-- ================================================================
|
|
STATUS BAR
|
|
================================================================ -->
|
|
<header class="status-bar" role="banner">
|
|
<div class="status-bar__store">
|
|
<span class="status-bar__store-dot"></span>
|
|
<span id="branchName">Nexus Autoparts</span>
|
|
</div>
|
|
<div class="status-bar__center">
|
|
<span id="registerInfo">Caja #--</span>
|
|
<span id="statusClock"></span>
|
|
</div>
|
|
<div class="status-bar__right">
|
|
<a href="/pos/dashboard" class="btn btn--ghost btn--sm" id="backToSystemBtn" style="margin-right:8px;display:none;">
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg>
|
|
Sistema
|
|
</a>
|
|
<div class="status-bar__user" aria-label="Usuario activo">
|
|
<div class="status-bar__user-avatar" aria-hidden="true">--</div>
|
|
<span id="employeeName">Empleado</span>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
<!-- ================================================================
|
|
MAIN POS LAYOUT
|
|
================================================================ -->
|
|
<main class="pos-main" role="main">
|
|
|
|
<!-- ============================================================
|
|
LEFT -- PRODUCT BROWSER
|
|
============================================================ -->
|
|
<section class="panel-products" aria-label="Catalogo de productos">
|
|
<div class="search-row">
|
|
<div class="search-wrap">
|
|
<svg class="search-icon" width="18" height="18" viewBox="0 0 24 24" fill="none"
|
|
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
<circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/>
|
|
</svg>
|
|
<input class="search-input" type="search" id="itemSearch"
|
|
placeholder="Buscar por nombre, No. parte, codigo..."
|
|
autocomplete="off" spellcheck="false" aria-label="Buscar productos" />
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Search Results Dropdown -->
|
|
<div id="searchResults" class="product-grid-wrap" style="display:none;"></div>
|
|
|
|
<!-- Totals Panel / Product Grid -->
|
|
<div class="product-grid-wrap" id="totalsPanel">
|
|
<div class="product-grid" id="productGrid" role="list" aria-label="Productos disponibles">
|
|
<!-- Populated by search -->
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- ============================================================
|
|
RIGHT -- CART / TICKET
|
|
============================================================ -->
|
|
<aside class="panel-cart" aria-label="Carrito de venta">
|
|
|
|
<!-- Cart Header -->
|
|
<div class="cart-header">
|
|
<div class="cart-header__top">
|
|
<div class="cart-header__sale-id">Venta Activa</div>
|
|
<button class="cost-toggle" id="costToggle" title="Mostrar costo/margen (Admin)" style="display:none;">C/M</button>
|
|
<span class="cart-header__status">Activa</span>
|
|
</div>
|
|
|
|
<!-- Customer Search -->
|
|
<div class="customer-row" id="customerSearchWrap">
|
|
<div class="customer-icon" aria-hidden="true">
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none"
|
|
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/>
|
|
</svg>
|
|
</div>
|
|
<div class="customer-info" style="flex:1;position:relative;">
|
|
<input type="text" id="customerSearch" class="search-input"
|
|
style="height:34px;font-size:var(--text-body-sm);padding-left:var(--space-3);"
|
|
placeholder="F2 — Buscar cliente..." autocomplete="off" />
|
|
<div id="customerAutocomplete" style="display:none;position:absolute;top:100%;left:0;right:0;z-index:100;background:var(--color-bg-elevated);border:1px solid var(--color-border);border-radius:var(--radius-md);max-height:200px;overflow-y:auto;box-shadow:var(--shadow-lg);">
|
|
</div>
|
|
</div>
|
|
<button class="btn-change-customer" onclick="POS.showNewCustomerModal()" aria-label="Nuevo cliente">+ Nuevo</button>
|
|
</div>
|
|
|
|
<!-- Selected Customer Display -->
|
|
<div id="customerSelected" style="display:none;" class="customer-row">
|
|
<div class="customer-icon" aria-hidden="true">
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none"
|
|
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/>
|
|
</svg>
|
|
</div>
|
|
<div class="customer-info">
|
|
<div class="customer-info__name" id="customerName">--</div>
|
|
<div class="customer-info__label">
|
|
<span id="customerTier">P1</span> |
|
|
<span id="customerCredit">Credito: $0 / $0</span>
|
|
</div>
|
|
</div>
|
|
<button class="btn-change-customer" onclick="POS.clearCustomer()" aria-label="Cambiar cliente">Cambiar</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Banner Cliente (shown when customer selected) -->
|
|
<div class="banner-cliente" id="vehicleBanner">
|
|
<div class="banner-info">
|
|
<div class="banner-info-item">
|
|
<div class="banner-info-label">Vehiculo</div>
|
|
<div class="banner-info-value" id="vehicleInfo">--</div>
|
|
</div>
|
|
<div class="banner-info-item">
|
|
<div class="banner-info-label">Ultima compra</div>
|
|
<div class="banner-info-value" id="lastPurchaseInfo">--</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Cart Items -->
|
|
<div class="cart-items" id="cartItems" role="list" aria-label="Articulos en carrito">
|
|
<!-- Empty state -->
|
|
<div id="cartEmpty" class="cart-empty" role="status">
|
|
<svg width="32" height="32" viewBox="0 0 24 24" fill="none"
|
|
stroke="currentColor" stroke-width="1.5" opacity="0.4" aria-hidden="true">
|
|
<circle cx="9" cy="21" r="1"/><circle cx="20" cy="21" r="1"/>
|
|
<path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6"/>
|
|
</svg>
|
|
<span>Carrito vacio -- agrega productos con F1</span>
|
|
</div>
|
|
|
|
<!-- Cart table -->
|
|
<table id="cartTable" style="display:none;width:100%;border-collapse:collapse;font-size:var(--text-body-sm);">
|
|
<thead>
|
|
<tr style="border-bottom:1px solid var(--color-border);">
|
|
<th style="text-align:left;padding:var(--space-2);">#</th>
|
|
<th style="text-align:left;padding:var(--space-2);">Producto</th>
|
|
<th style="text-align:center;padding:var(--space-2);">Cant</th>
|
|
<th style="text-align:right;padding:var(--space-2);">P.Unit</th>
|
|
<th style="text-align:center;padding:var(--space-2);">Desc%</th>
|
|
<th style="text-align:right;padding:var(--space-2);">Importe</th>
|
|
<th id="thCost" style="display:none;text-align:right;padding:var(--space-2);">Costo</th>
|
|
<th id="thMargin" style="display:none;text-align:center;padding:var(--space-2);">Margen</th>
|
|
<th style="width:30px;"></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="cartBody"></tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- Margin Summary -->
|
|
<div class="margin-summary" id="marginSummary">
|
|
<span>Margen promedio:</span>
|
|
<span class="margin-summary__value" id="avgMargin">--</span>
|
|
</div>
|
|
|
|
<!-- Cart Footer -->
|
|
<div class="cart-footer">
|
|
<div class="totals-block">
|
|
<div class="totals-row">
|
|
<span class="totals-row__label">Subtotal</span>
|
|
<span class="totals-row__value" id="dispSubtotal">$0.00</span>
|
|
</div>
|
|
<div class="totals-row" id="discountRow" style="display:none;">
|
|
<span class="totals-row__label">Descuento</span>
|
|
<span class="totals-row__value" id="dispDiscount">$0.00</span>
|
|
</div>
|
|
<div class="totals-row">
|
|
<span class="totals-row__label">IVA (16%)</span>
|
|
<span class="totals-row__value" id="dispTax">$0.00</span>
|
|
</div>
|
|
<div class="totals-row totals-row--total">
|
|
<span class="totals-row__label">Total</span>
|
|
<span class="totals-row__value" id="dispTotal">$0.00</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- COBRAR Button -->
|
|
<button class="btn-cobrar" id="btnCobrar" onclick="POS.checkout()" aria-label="Procesar cobro">
|
|
<span>COBRAR</span>
|
|
</button>
|
|
|
|
<!-- Secondary Actions -->
|
|
<div class="secondary-actions" role="toolbar" aria-label="Acciones secundarias">
|
|
<button class="btn-secondary-action" onclick="POS.saveQuotation()" title="Cotizacion (F4)">Cotizar</button>
|
|
<button class="btn-secondary-action" onclick="POS.showLastSale()" title="Ultima venta (F5)">Ult.Venta</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>
|
|
</div>
|
|
</aside>
|
|
</main>
|
|
|
|
<!-- ================================================================
|
|
F-KEYS FOOTER BAR
|
|
================================================================ -->
|
|
<div class="fkeys-footer" id="fkeysFooter">
|
|
<div class="fkey" onclick="document.getElementById('itemSearch').focus()" title="Buscar producto">
|
|
<span class="fkey-key">F1</span><span class="fkey-label">Buscar</span>
|
|
</div>
|
|
<div class="fkey" onclick="document.getElementById('customerSearch').focus()" title="Seleccionar cliente">
|
|
<span class="fkey-key">F2</span><span class="fkey-label">Cliente</span>
|
|
</div>
|
|
<div class="fkey highlight" onclick="POS.checkout()" title="Cobrar venta">
|
|
<span class="fkey-key">F3</span><span class="fkey-label">Cobrar</span>
|
|
</div>
|
|
<div class="fkey" onclick="POS.saveQuotation()" title="Cotizacion">
|
|
<span class="fkey-key">F4</span><span class="fkey-label">Cotizacion</span>
|
|
</div>
|
|
<div class="fkey-sep"></div>
|
|
<div class="fkey" onclick="POS.showLastSale()" title="Ultima venta">
|
|
<span class="fkey-key">F5</span><span class="fkey-label">Ult.Venta</span>
|
|
</div>
|
|
<div class="fkey" onclick="POS.openDrawer()" title="Abrir cajon">
|
|
<span class="fkey-key">F6</span><span class="fkey-label">Cajon</span>
|
|
</div>
|
|
<div class="fkey-sep"></div>
|
|
<div class="fkey" onclick="POS.changeQuantity()" title="Cantidad +/-">
|
|
<span class="fkey-key">+/-</span><span class="fkey-label">Cantidad</span>
|
|
</div>
|
|
<div class="fkey" onclick="POS.applyDiscount()" title="Descuento">
|
|
<span class="fkey-key">*</span><span class="fkey-label">Descuento</span>
|
|
</div>
|
|
<div class="fkey-sep"></div>
|
|
<div class="fkey" id="fkeyEsc" onclick="POS.openCancelModal()" title="Cancelar">
|
|
<span class="fkey-key">Esc</span><span class="fkey-label">Cancelar</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ================================================================
|
|
PAYMENT MODAL (modal-pago + calculadora-cambio)
|
|
================================================================ -->
|
|
<div class="modal-overlay" id="paymentModal">
|
|
<div class="modal-pago">
|
|
<!-- Header -->
|
|
<div class="modal-header">
|
|
<h3>Cobrar Venta</h3>
|
|
<button class="modal-close" onclick="POS.closePaymentModal()">✕</button>
|
|
</div>
|
|
|
|
<!-- Total Banner -->
|
|
<div class="total-banner">
|
|
<span class="total-label">Total a Cobrar</span>
|
|
<span class="total-amount" id="modalTotal">$0.00</span>
|
|
</div>
|
|
|
|
<!-- Items Summary -->
|
|
<div class="items-summary" id="modalSummary">
|
|
<span id="modalItemCount">0 productos</span>
|
|
<span>•</span>
|
|
<span id="modalCustomerName">Publico General</span>
|
|
</div>
|
|
|
|
<!-- Payment Tabs -->
|
|
<div class="pago-tabs">
|
|
<button class="pago-tab active" data-method="efectivo" onclick="POS.selectPaymentMethod('efectivo', this)">
|
|
Efectivo
|
|
</button>
|
|
<button class="pago-tab" data-method="transferencia" onclick="POS.selectPaymentMethod('transferencia', this)">
|
|
Transferencia
|
|
</button>
|
|
<button class="pago-tab" data-method="tarjeta" onclick="POS.selectPaymentMethod('tarjeta', this)">
|
|
Tarjeta
|
|
</button>
|
|
<button class="pago-tab" data-method="mixto" onclick="POS.selectPaymentMethod('mixto', this)">
|
|
Mixto
|
|
</button>
|
|
</div>
|
|
|
|
<!-- TAB: Efectivo -->
|
|
<div class="tab-content active" id="cashPayment">
|
|
<div class="form-group">
|
|
<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 class="quick-amounts" id="quickAmounts">
|
|
<!-- Populated dynamically -->
|
|
</div>
|
|
<div class="cambio-display">
|
|
<span class="cambio-label">Cambio</span>
|
|
<span class="cambio-amount positive" id="changeDisplay">$0.00</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- TAB: Transferencia / Tarjeta -->
|
|
<div class="tab-content" id="refPayment">
|
|
<div class="form-group">
|
|
<label class="form-label">Monto</label>
|
|
<input type="text" class="form-input form-input-lg" id="refAmount" readonly />
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Referencia / No. operacion</label>
|
|
<input type="text" class="form-input" id="paymentRef" placeholder="No. de referencia" />
|
|
</div>
|
|
<div class="form-hint">Verificar que el pago se haya recibido antes de confirmar</div>
|
|
</div>
|
|
|
|
<!-- TAB: Mixto -->
|
|
<div class="tab-content" id="mixedPayment">
|
|
<div class="mixed-row" style="margin-bottom:var(--space-3);">
|
|
<div class="form-group">
|
|
<label class="form-label">Metodo 1</label>
|
|
<select class="form-input" style="margin-bottom:var(--space-2);">
|
|
<option value="efectivo">Efectivo</option>
|
|
<option value="tarjeta">Tarjeta</option>
|
|
<option value="transferencia">Transferencia</option>
|
|
</select>
|
|
<input type="number" class="form-input mixed-amount" placeholder="0.00" step="0.01" oninput="POS.updateMixedTotal()" />
|
|
<input type="text" class="form-input" placeholder="Referencia (si aplica)" style="margin-top:var(--space-2);" />
|
|
</div>
|
|
</div>
|
|
<div class="mixed-row" style="margin-bottom:var(--space-3);">
|
|
<div class="form-group">
|
|
<label class="form-label">Metodo 2</label>
|
|
<select class="form-input" style="margin-bottom:var(--space-2);">
|
|
<option value="tarjeta">Tarjeta</option>
|
|
<option value="efectivo">Efectivo</option>
|
|
<option value="transferencia">Transferencia</option>
|
|
</select>
|
|
<input type="number" class="form-input mixed-amount" placeholder="0.00" step="0.01" oninput="POS.updateMixedTotal()" />
|
|
<input type="text" class="form-input" placeholder="Referencia (si aplica)" style="margin-top:var(--space-2);" />
|
|
</div>
|
|
</div>
|
|
<div class="split-remaining">
|
|
<span>Restante:</span>
|
|
<span class="amount" id="mixedRemaining">$0.00</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- CFDI Checkbox -->
|
|
<div class="cfdi-check">
|
|
<input type="checkbox" id="cfdiCheck" />
|
|
<label for="cfdiCheck">Facturar CFDI</label>
|
|
<span class="cfdi-hint" id="cfdiHint"></span>
|
|
</div>
|
|
|
|
<!-- Footer -->
|
|
<div class="modal-footer">
|
|
<button class="btn btn-ghost" onclick="POS.closePaymentModal()">Cancelar</button>
|
|
<button class="btn btn-primary btn-lg" id="btnConfirmPayment" onclick="POS.confirmPayment()">Confirmar Pago</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ================================================================
|
|
CANCEL SALE CONFIRMATION MODAL
|
|
================================================================ -->
|
|
<div class="confirm-overlay" id="overlay-cancelar-venta" onclick="if(event.target===this) closeCancelModal()"></div>
|
|
<div class="confirm-dialog" id="modal-cancelar-venta">
|
|
<div class="modal__icon modal__icon--warning">⚠</div>
|
|
<div class="modal__title">Cancelar venta?</div>
|
|
<div class="modal__desc">Se perderan los articulos agregados al ticket actual.</div>
|
|
<div class="modal__actions">
|
|
<button class="modal__btn modal__btn--secondary" onclick="closeCancelModal()">No, continuar</button>
|
|
<button class="modal__btn modal__btn--danger" id="btnConfirmCancel">Si, cancelar</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ================================================================
|
|
TICKET PRINT AREA (hidden div for thermal print)
|
|
================================================================ -->
|
|
<div class="ticket-print-area" id="ticketPrintArea">
|
|
<div class="ticket ticket-80" id="ticketContent">
|
|
<!-- Populated by JS after successful sale -->
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ================================================================
|
|
TICKET PREVIEW MODAL (on-screen after sale)
|
|
================================================================ -->
|
|
<div class="modal-overlay" id="ticketModal">
|
|
<div class="modal-pago" style="width:380px;">
|
|
<div class="modal-header">
|
|
<h3>Ticket de Venta</h3>
|
|
<button class="modal-close" onclick="POS.closeTicketModal()">✕</button>
|
|
</div>
|
|
<div style="padding:var(--space-4);display:flex;justify-content:center;">
|
|
<div class="ticket ticket-80" id="ticketPreviewContent">
|
|
<!-- Populated by JS -->
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button class="btn btn-ghost" onclick="POS.closeTicketModal()">Cerrar</button>
|
|
<button class="btn btn-ghost" id="btnConnectPrinter" onclick="POS.connectThermal()" title="Conectar impresora termica USB/Serial">🖨 Conectar</button>
|
|
<button class="btn btn-secondary" id="btnThermalPrint" onclick="POS.thermalPrint()" style="display:none;" title="Imprimir en impresora termica">🖨 Termica</button>
|
|
<button class="btn btn-primary" onclick="POS.printTicket()">Imprimir</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ================================================================
|
|
NEW CUSTOMER MODAL
|
|
================================================================ -->
|
|
<div class="modal-overlay" id="newCustomerModal">
|
|
<div class="modal-pago" style="width:480px;">
|
|
<div class="modal-header">
|
|
<h3>Nuevo Cliente</h3>
|
|
<button class="modal-close" onclick="POS.closeNewCustomerModal()">✕</button>
|
|
</div>
|
|
<div style="padding:var(--space-6);">
|
|
<div class="form-group">
|
|
<label class="form-label">Nombre *</label>
|
|
<input type="text" class="form-input" id="ncName" placeholder="Nombre o Razon Social" />
|
|
</div>
|
|
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3);">
|
|
<div class="form-group">
|
|
<label class="form-label">RFC</label>
|
|
<input type="text" class="form-input" id="ncRfc" placeholder="XAXX010101000" maxlength="13" />
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Telefono</label>
|
|
<input type="text" class="form-input" id="ncPhone" placeholder="55 1234 5678" />
|
|
</div>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Razon Social</label>
|
|
<input type="text" class="form-input" id="ncRazonSocial" placeholder="Razon Social para facturacion" />
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Email</label>
|
|
<input type="email" class="form-input" id="ncEmail" placeholder="correo@ejemplo.com" />
|
|
</div>
|
|
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3);">
|
|
<div class="form-group">
|
|
<label class="form-label">Lista de precios</label>
|
|
<select class="form-input" id="ncPriceTier">
|
|
<option value="1">P1 Mostrador</option>
|
|
<option value="2">P2 Taller</option>
|
|
<option value="3">P3 Mayoreo</option>
|
|
</select>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Limite de credito</label>
|
|
<input type="number" class="form-input" id="ncCreditLimit" placeholder="0.00" step="0.01" />
|
|
</div>
|
|
</div>
|
|
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3);">
|
|
<div class="form-group">
|
|
<label class="form-label">Regimen Fiscal</label>
|
|
<select class="form-input" id="ncRegimenFiscal">
|
|
<option value="">-- Seleccionar --</option>
|
|
<option value="601">601 - General de Ley PM</option>
|
|
<option value="612">612 - Personas Fisicas</option>
|
|
<option value="621">621 - Inc. Fiscal</option>
|
|
<option value="626">626 - RESICO</option>
|
|
</select>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Uso CFDI</label>
|
|
<select class="form-input" id="ncUsoCfdi">
|
|
<option value="G03">G03 - Gastos en general</option>
|
|
<option value="G01">G01 - Adq. de mercancias</option>
|
|
<option value="P01">P01 - Por definir</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div style="margin-top:var(--space-4);border-top:1px solid var(--color-border);padding-top:var(--space-4);">
|
|
<div class="form-label" style="margin-bottom:var(--space-3);">Vehiculo (opcional)</div>
|
|
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3);">
|
|
<div class="form-group"><input type="text" class="form-input" id="ncVehMake" placeholder="Marca" /></div>
|
|
<div class="form-group"><input type="text" class="form-input" id="ncVehModel" placeholder="Modelo" /></div>
|
|
<div class="form-group"><input type="text" class="form-input" id="ncVehYear" placeholder="Ano" /></div>
|
|
<div class="form-group"><input type="text" class="form-input" id="ncVehPlates" placeholder="Placas" /></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button class="btn btn-ghost" onclick="POS.closeNewCustomerModal()">Cancelar</button>
|
|
<button class="btn btn-primary" onclick="POS.saveNewCustomer()">Guardar Cliente</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ================================================================
|
|
CUT Z MODAL (Cerrar Caja)
|
|
================================================================ -->
|
|
<div class="modal-overlay" id="cutZModal">
|
|
<div class="modal-pago" style="width:520px;max-height:90vh;overflow-y:auto;">
|
|
<div class="modal-header">
|
|
<h3>Corte Z — Cerrar Caja</h3>
|
|
<button class="modal-close" onclick="POS.closeCutZModal()">✕</button>
|
|
</div>
|
|
<div style="padding:var(--space-6);">
|
|
<div id="cutZSummary" style="margin-bottom:var(--space-4);">
|
|
<p style="color:var(--color-text-muted);font-size:var(--text-body-sm);">Cargando resumen...</p>
|
|
</div>
|
|
<div style="border-top:1px solid var(--color-border);padding-top:var(--space-4);">
|
|
<div class="form-group">
|
|
<label class="form-label">Efectivo contado en caja *</label>
|
|
<input type="number" class="form-input" id="cutZClosingAmount" value="0" min="0" step="0.01" />
|
|
</div>
|
|
</div>
|
|
<div id="cutZResult" style="min-height:1.5em;font-size:var(--text-body-sm);margin-top:var(--space-3);"></div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button class="btn btn-ghost" onclick="POS.closeCutZModal()">Cancelar</button>
|
|
<button class="btn btn-secondary" onclick="POS.loadCutX()">Ver Resumen (Corte X)</button>
|
|
<button class="btn btn-primary" onclick="POS.confirmCutZ()">Cerrar Caja</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ================================================================
|
|
OPEN REGISTER MODAL
|
|
================================================================ -->
|
|
<div class="modal-overlay" id="openRegisterModal">
|
|
<div class="modal-pago" style="width:420px;">
|
|
<div class="modal-header">
|
|
<h3>Abrir Caja</h3>
|
|
<button class="modal-close" onclick="POS.closeOpenRegisterModal()">✕</button>
|
|
</div>
|
|
<div style="padding:var(--space-6);">
|
|
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3);margin-bottom:var(--space-4);">
|
|
<div class="form-group">
|
|
<label class="form-label">No. de Caja *</label>
|
|
<input type="number" class="form-input" id="regNumber" value="1" min="1" />
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Efectivo inicial *</label>
|
|
<input type="number" class="form-input" id="regOpeningAmount" value="0" min="0" step="0.01" />
|
|
</div>
|
|
</div>
|
|
<div id="registerOpenResult" style="min-height:1.5em;font-size:var(--text-body-sm);"></div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button class="btn btn-ghost" onclick="POS.closeOpenRegisterModal()">Cancelar</button>
|
|
<button class="btn btn-primary" onclick="POS.openRegister()">Abrir Caja</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ================================================================
|
|
TOAST CONTAINER
|
|
================================================================ -->
|
|
<div class="toast-container" id="toastContainer" aria-live="assertive" aria-atomic="true"></div>
|
|
|
|
<!-- ================================================================
|
|
JAVASCRIPT
|
|
================================================================ -->
|
|
<script src="/pos/static/js/i18n.js" defer></script>
|
|
<script src="/pos/static/js/kiosk.js" defer></script>
|
|
<script src="/pos/static/js/pos-utils.js" defer></script>
|
|
<script src="/pos/static/js/app-init.js" 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=5" defer></script>
|
|
|
|
<script>
|
|
// Cancel sale button wiring
|
|
document.getElementById('btnCancelSale').addEventListener('click', function() {
|
|
document.getElementById('overlay-cancelar-venta').classList.add('active');
|
|
document.getElementById('modal-cancelar-venta').classList.add('active');
|
|
document.body.style.overflow = 'hidden';
|
|
});
|
|
|
|
document.getElementById('btnConfirmCancel').addEventListener('click', function() {
|
|
closeCancelModal();
|
|
// Clear cart via POS module
|
|
if (typeof POS !== 'undefined') {
|
|
POS.clearCart();
|
|
POS.clearCustomer();
|
|
}
|
|
});
|
|
|
|
function closeCancelModal() {
|
|
const overlay = document.getElementById('overlay-cancelar-venta');
|
|
const dialog = document.getElementById('modal-cancelar-venta');
|
|
overlay.classList.add('closing');
|
|
dialog.classList.add('closing');
|
|
setTimeout(() => {
|
|
overlay.classList.remove('active', 'closing');
|
|
dialog.classList.remove('active', 'closing');
|
|
document.body.style.overflow = '';
|
|
}, 200);
|
|
}
|
|
|
|
// Cost toggle (show only if permitted)
|
|
document.getElementById('costToggle').addEventListener('click', function() {
|
|
document.body.classList.toggle('show-cost-columns');
|
|
this.classList.toggle('active');
|
|
});
|
|
|
|
// Clock
|
|
function updateClock() {
|
|
const now = new Date();
|
|
const opts = { year: 'numeric', month: 'short', day: 'numeric',
|
|
hour: '2-digit', minute: '2-digit', hour12: true };
|
|
const el = document.getElementById('statusClock');
|
|
if (el) el.textContent = now.toLocaleString('es-MX', opts);
|
|
}
|
|
updateClock();
|
|
setInterval(updateClock, 30000);
|
|
|
|
// Show "Back to System" button when NOT in kiosk mode
|
|
(function checkKiosk() {
|
|
var btn = document.getElementById('backToSystemBtn');
|
|
if (!btn) return;
|
|
function showIfNotKiosk() {
|
|
try {
|
|
var isKiosk = window.isKioskEnabled && window.isKioskEnabled();
|
|
btn.style.display = isKiosk ? 'none' : 'inline-flex';
|
|
} catch (e) { btn.style.display = 'inline-flex'; }
|
|
}
|
|
showIfNotKiosk();
|
|
setInterval(showIfNotKiosk, 2000);
|
|
})();
|
|
</script>
|
|
|
|
<script src="/pos/static/js/chat.js" defer></script>
|
|
<script src="/pos/static/js/sync-engine.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>
|
|
</body>
|
|
</html>
|