From d9741b21f69f47d1cb22ac05c5f8886b937be9d5 Mon Sep 17 00:00:00 2001 From: consultoria-as Date: Mon, 18 May 2026 06:59:18 +0000 Subject: [PATCH] feat(pos): add Cut Z (close register) UI flow - Add 'Corte Z' button in secondary actions panel - Add modal showing register summary before closing: - opening amount, total sales, cash sales, change given - cash movements in/out, cancellations, expected cash - payment method breakdown and movement detail list - loadCutX() fetches current register summary (read-only) - confirmCutZ() calls POST /pos/api/register/cut-z with counted amount - Auto-fills closing amount with expected cash - Shows toast with difference after closing - Resets register state to 'Sin caja abierta' after close - Bump pos.css and pos.js cache-bust to v=3 --- pos/static/js/pos.js | 73 ++++++++++++++++++++++++++++++++++++++++++ pos/templates/pos.html | 34 ++++++++++++++++++-- 2 files changed, 105 insertions(+), 2 deletions(-) diff --git a/pos/static/js/pos.js b/pos/static/js/pos.js index e22cac4..905df87 100644 --- a/pos/static/js/pos.js +++ b/pos/static/js/pos.js @@ -162,6 +162,78 @@ const POS = (() => { } } + // ─── Cut X / Z (Close Register) ────── + function showCutZModal() { + document.getElementById('cutZModal').classList.add('open'); + document.getElementById('cutZResult').innerHTML = ''; + loadCutX(); + } + function closeCutZModal() { + document.getElementById('cutZModal').classList.remove('open'); + document.getElementById('cutZResult').innerHTML = ''; + } + async function loadCutX() { + const el = document.getElementById('cutZSummary'); + try { + const data = await api('/pos/api/register/cut-x'); + if (data.error) { + el.innerHTML = '

' + data.error + '

'; + return; + } + let html = '
'; + html += '
Efectivo inicial
$' + fmt(data.opening_amount) + '
'; + html += '
Ventas totales
$' + fmt(data.total_sales) + ' (' + data.total_sales_count + ')
'; + html += '
Efectivo en ventas
$' + fmt(data.cash_from_sales) + '
'; + html += '
Cambio entregado
-$' + fmt(data.change_given) + '
'; + html += '
Entradas de efectivo
+$' + fmt(data.cash_movements_in) + '
'; + html += '
Salidas de efectivo
-$' + fmt(data.cash_movements_out) + '
'; + html += '
Cancelaciones
' + data.cancelled_count + ' ($' + fmt(data.cancelled_amount) + ')
'; + html += '
Efectivo esperado
$' + fmt(data.expected_cash) + '
'; + html += '
'; + if (data.sales_by_method && Object.keys(data.sales_by_method).length) { + html += '
'; + html += 'Por metodo de pago:
'; + for (const [method, info] of Object.entries(data.sales_by_method)) { + html += '' + method + ': $' + fmt(info.amount) + ' (' + info.count + ')'; + } + html += '
'; + } + if (data.movement_detail && data.movement_detail.length) { + html += '
'; + html += 'Movimientos de caja:
'; + data.movement_detail.forEach(function(m) { + html += '
' + m.type + ' $' + fmt(m.amount) + ' — ' + (m.reason || '') + '
'; + }); + html += '
'; + } + el.innerHTML = html; + document.getElementById('cutZClosingAmount').value = data.expected_cash.toFixed(2); + } catch (e) { + el.innerHTML = '

Error cargando resumen

'; + } + } + async function confirmCutZ() { + const amount = parseFloat(document.getElementById('cutZClosingAmount').value) || 0; + try { + const data = await api('/pos/api/register/cut-z', { + method: 'POST', + body: JSON.stringify({ closing_amount: amount }) + }); + if (data.error) { + document.getElementById('cutZResult').innerHTML = '' + data.error + ''; + return; + } + const diffColor = data.difference > 0 ? 'var(--color-success)' : (data.difference < 0 ? 'var(--color-error)' : 'var(--color-text-muted)'); + document.getElementById('cutZResult').innerHTML = 'Caja cerrada correctamente'; + document.getElementById('registerInfo').innerHTML = 'Sin caja abierta'; + currentRegister = null; + closeCutZModal(); + showToast('Corte Z completado. Diferencia: $' + data.difference.toFixed(2)); + } catch (e) { + document.getElementById('cutZResult').innerHTML = 'Error de red'; + } + } + // ─── Cart ──────────────────────────── function addToCart(item) { const existing = cart.find(c => c.inventory_id === item.inventory_id); @@ -1195,5 +1267,6 @@ const POS = (() => { showTicket, closeTicketModal, printTicket, connectThermal, thermalPrint, showOpenRegisterModal, closeOpenRegisterModal, openRegister, + showCutZModal, closeCutZModal, loadCutX, confirmCutZ, }; })(); diff --git a/pos/templates/pos.html b/pos/templates/pos.html index ac40483..b3f573b 100644 --- a/pos/templates/pos.html +++ b/pos/templates/pos.html @@ -14,7 +14,7 @@ - + @@ -202,6 +202,7 @@ @@ -490,6 +491,35 @@ + + + @@ -533,7 +563,7 @@ - +