feat: cashier/counter reports, service-order & remission flows, Rached migration utils
Some checks failed
CI / lint-and-test (3.11) (push) Has been cancelled
CI / lint-and-test (3.13) (push) Has been cancelled

- Add "Mis cortes de caja" report for cashiers/counters with sales detail.
- Cash register history scoped to own cuts for non-admin roles; new /register/<id>/sales endpoint.
- Remove dashboard from cashier menu; add Reports to cashier/counter.
- Service orders: assign mechanic, budget field, invoice flag, counter/cashier can add items/remissions, convert to remission.
- Remission notes module (UI, CSS, courier, counter remissions).
- Customer hard-delete and vehicle/customer linkage in workshop.
- POS: always show search results, compact payment grid, credit validation, tier pricing (5%/10%), ticket with customer/folio.
- Inventory: CSV template with sku_secondary, alias import.
- Rached migration scripts and DB migrations.
- Version-bump cached JS/CSS query strings.

Excludes local Rached session tokens/captures (rached_*.json / rached_*.txt).
This commit is contained in:
2026-07-02 12:51:56 +00:00
parent 483498cfcc
commit f42910f4f6
71 changed files with 5388 additions and 626 deletions

View File

@@ -15,7 +15,7 @@
<meta name="theme-color" content="#F5A623" />
<link rel="shortcut icon" type="image/png" href="/pos/static/pwa/icon-192.png" />
<link rel="stylesheet" href="/pos/static/css/workshop.css?v=42">
<link rel="stylesheet" href="/pos/static/css/workshop.css?v=44">
<style>
.so-notes-grid { display: grid; grid-template-columns: 120px 1fr; gap: var(--space-2); align-items: start; }
.so-notes-grid .form-label { margin: 0; padding-top: var(--space-2); }
@@ -55,7 +55,7 @@
<svg viewBox="0 0 24 24"><path d="M21 15a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v10z"/><polyline points="3 8 12 13 21 8"/></svg>
</div>
<div class="summary-card__body">
<div class="summary-card__label">Recibidos</div>
<div class="summary-card__label">Por revisar</div>
<div class="summary-card__value" id="statReceived">--</div>
</div>
</div>
@@ -73,7 +73,7 @@
<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><polyline points="9 12 12 15 16 10"/></svg>
</div>
<div class="summary-card__body">
<div class="summary-card__label">Listos</div>
<div class="summary-card__label">Por entregar</div>
<div class="summary-card__value" id="statReady">--</div>
</div>
</div>
@@ -96,20 +96,28 @@
</select>
<select class="form-input" id="filterStatus">
<option value="">Todos los estatus</option>
<option value="received">Recibido</option>
<option value="diagnosis">Diagn&oacute;stico</option>
<option value="waiting_parts">Espera refacciones</option>
<option value="repair">En reparaci&oacute;n</option>
<option value="quality_check">Control calidad</option>
<option value="ready">Listo</option>
<option value="delivered">Entregado</option>
<option value="cancelled">Cancelado</option>
<option value="por_revisar">Por revisar</option>
<option value="en_revision">En revisi&oacute;n</option>
<option value="revisada">Revisada</option>
<option value="cotizada">Cotizada</option>
<option value="por_autorizar">Por autorizar</option>
<option value="autorizada">Autorizada</option>
<option value="autorizacion_parcial">Autorizaci&oacute;n parcial</option>
<option value="en_reparacion">En reparaci&oacute;n</option>
<option value="reparada">Reparada</option>
<option value="por_entregar">Por entregar</option>
<option value="entregado">Entregado</option>
<option value="por_enviar">Por enviar</option>
<option value="enviado">Enviado</option>
<option value="por_facturar">Por facturar</option>
<option value="facturada">Facturada</option>
<option value="por_recolectar">Por recolectar</option>
<option value="cancelada">Cancelada</option>
</select>
<select class="form-input" id="filterDelivery">
<option value="">Todas las v&iacute;as de entrega</option>
<option value="pickup">Pasa cliente</option>
<option value="pickup">Mostrador</option>
<option value="delivery">Env&iacute;o a domicilio</option>
<option value="courier">Motociclista</option>
</select>
<label class="toolbar-toggle">
<input type="checkbox" id="filterDirect" />
@@ -137,15 +145,16 @@
<tr>
<th>Sucursal</th>
<th>Orden</th>
<th>Cliente</th>
<th>Veh&iacute;culo</th>
<th class="restricted-hide">Cliente</th>
<th class="restricted-hide">Taller</th>
<th class="restricted-hide">Veh&iacute;culo</th>
<th>Estatus</th>
<th class="price-col">Total</th>
<th class="price-col restricted-hide">Total</th>
<th>Acciones</th>
</tr>
</thead>
<tbody id="listBody">
<tr><td colspan="7" style="text-align:center;padding:var(--space-4);">Cargando...</td></tr>
<tr><td colspan="8" style="text-align:center;padding:var(--space-4);">Cargando...</td></tr>
</tbody>
</table>
</div>
@@ -184,6 +193,10 @@
</div>
<div class="modal__body">
<form id="newOrderForm" class="form-grid">
<div class="form-field">
<label class="form-label" for="noBranch">Sucursal</label>
<select class="form-input" id="noBranch" required></select>
</div>
<div class="form-field">
<label class="form-label" for="noCustomer">Cliente</label>
<div style="display:flex;gap:var(--space-2);">
@@ -192,29 +205,26 @@
</div>
</div>
<div class="form-field">
<label class="form-label" for="noVehicle">Veh&iacute;culo</label>
<div style="display:flex;gap:var(--space-2);">
<select class="form-input" id="noVehicle" style="flex:1;"></select>
<button class="btn btn--secondary" type="button" onclick="Workshop.openNewVehicleModalFromNewOrder()">+ Nuevo</button>
</div>
<label class="form-label" for="noWorkshopName">Taller</label>
<input class="form-input" id="noWorkshopName" placeholder="Nombre del taller" />
</div>
<div class="form-field">
<label class="form-label" for="noMechanic">Mec&aacute;nico asignado</label>
<select class="form-input" id="noMechanic"></select>
<label class="form-label" for="noCustomerPhone">Tel&eacute;fono</label>
<input class="form-input" id="noCustomerPhone" placeholder="3312345678" />
</div>
<div class="form-field form-field--span2">
<label class="form-label" for="noCustomerAddress">Direcci&oacute;n</label>
<input class="form-input" id="noCustomerAddress" placeholder="Calle, n&uacute;mero, colonia" />
</div>
<div class="form-field">
<label class="form-label" for="noPriority">Prioridad</label>
<select class="form-input" id="noPriority">
<option value="normal">Normal</option>
<option value="high">Alta</option>
<option value="urgent">Urgente</option>
</select>
<label class="form-label" for="noVehicleDescription">Veh&iacute;culo</label>
<input class="form-input" id="noVehicleDescription" placeholder="Ej. Versa 2020" />
</div>
<div class="form-field">
<label class="form-label" for="noDelivery">V&iacute;a de entrega</label>
<select class="form-input" id="noDelivery">
<option value=""></option>
<option value="pickup">Pasa cliente</option>
<option value="pickup">Mostrador</option>
<option value="delivery">Env&iacute;o a domicilio</option>
<option value="courier">Motociclista</option>
</select>
@@ -224,21 +234,31 @@
<select class="form-input" id="noCourier"></select>
</div>
<div class="form-field">
<label class="form-label" for="noEstimatedCompletion">Entrega estimada</label>
<input class="form-input" type="datetime-local" id="noEstimatedCompletion" />
<label class="form-label" for="noMechanic">Mec&aacute;nico asignado (usuario)</label>
<select class="form-input" id="noMechanic"></select>
</div>
<div class="form-field">
<label class="form-label" for="noMileage">Kilometraje</label>
<input class="form-input" type="number" id="noMileage" placeholder="Ej. 45200" />
<label class="form-label" for="noMechanicName">Mec&aacute;nico asignado (nombre libre)</label>
<input class="form-input" type="text" id="noMechanicName" placeholder="Ej. Juan Pérez" />
</div>
<div class="form-field form-field--span2">
<div class="form-field">
<label class="form-label" for="noEstimatedCost">Presupuesto</label>
<input class="form-input" type="number" id="noEstimatedCost" step="0.01" placeholder="0.00" />
</div>
<div class="form-field">
<label class="toolbar-toggle">
<input type="checkbox" id="noRequiresInvoice" />
<span>Requiere factura</span>
</label>
</div>
<div class="form-field">
<label class="toolbar-toggle">
<input type="checkbox" id="noDirect" />
<span>Orden directa (sin inventario)</span>
<span>Orden directa</span>
</label>
</div>
<div class="form-field form-field--span2">
<label class="form-label" for="noNotes">Notas de recepci&oacute;n</label>
<label class="form-label" for="noNotes">Observaciones</label>
<textarea class="form-input" id="noNotes" rows="3" placeholder="Falla reportada, observaciones..."></textarea>
</div>
</form>
@@ -270,63 +290,59 @@
</div>
<input type="hidden" id="eoCustomerId" />
</div>
<div class="form-field form-field--span2" style="position:relative;">
<label class="form-label" for="eoVehicleSearch">Veh&iacute;culo</label>
<div style="display:flex;gap:var(--space-2);">
<div style="position:relative;flex:1;">
<input class="form-input" id="eoVehicleSearch" autocomplete="off" placeholder="Buscar por placa..." oninput="Workshop.searchVehiclesForSO()" />
<div id="eoVehicleResults" style="display:none;position:absolute;z-index:10;top:100%;left:0;right:0;max-height:180px;overflow-y:auto;background:#fff;border:1px solid var(--color-border);border-radius:var(--radius-md);box-shadow:0 4px 12px rgba(0,0,0,.15);"></div>
</div>
<button class="btn btn--secondary" type="button" onclick="Workshop.openNewVehicleModal('edit')">+ Nuevo</button>
</div>
<input type="hidden" id="eoVehicleId" />
</div>
<div class="form-field">
<label class="form-label" for="eoBranch">Sucursal</label>
<select class="form-input" id="eoBranch"></select>
</div>
<div class="form-field">
<label class="form-label" for="eoMechanic">Mec&aacute;nico asignado</label>
<select class="form-input" id="eoMechanic"><option value="">— Ninguno —</option></select>
<label class="form-label" for="eoWorkshopName">Taller</label>
<input class="form-input" id="eoWorkshopName" placeholder="Nombre del taller" />
</div>
<div class="form-field">
<label class="form-label" for="eoPriority">Prioridad</label>
<select class="form-input" id="eoPriority">
<option value="low">Baja</option>
<option value="normal">Normal</option>
<option value="high">Alta</option>
<option value="urgent">Urgente</option>
</select>
</div>
<div class="form-field">
<label class="form-label" for="eoFuelLevel">Nivel de combustible</label>
<select class="form-input" id="eoFuelLevel">
<option value=""></option>
<option value="empty">Vac&iacute;o</option>
<option value="quarter">1/4</option>
<option value="half">1/2</option>
<option value="three_quarters">3/4</option>
<option value="full">Lleno</option>
</select>
</div>
<div class="form-field">
<label class="form-label" for="eoMileageIn">Kilometraje entrada</label>
<input class="form-input" type="number" id="eoMileageIn" />
</div>
<div class="form-field">
<label class="form-label" for="eoMileageOut">Kilometraje salida</label>
<input class="form-input" type="number" id="eoMileageOut" />
</div>
<div class="form-field">
<label class="form-label" for="eoEstimatedCompletion">Entrega estimada</label>
<input class="form-input" type="datetime-local" id="eoEstimatedCompletion" />
</div>
<div class="form-field">
<label class="form-label" for="eoEstimatedCost">Costo estimado</label>
<input class="form-input" type="number" id="eoEstimatedCost" step="0.01" />
<label class="form-label" for="eoCustomerPhone">Tel&eacute;fono</label>
<input class="form-input" id="eoCustomerPhone" placeholder="3312345678" />
</div>
<div class="form-field form-field--span2">
<label class="form-label" for="eoNotes">Notas de recepci&oacute;n</label>
<label class="form-label" for="eoCustomerAddress">Direcci&oacute;n</label>
<input class="form-input" id="eoCustomerAddress" placeholder="Calle, n&uacute;mero, colonia" />
</div>
<div class="form-field">
<label class="form-label" for="eoVehicleDescription">Veh&iacute;culo</label>
<input class="form-input" id="eoVehicleDescription" placeholder="Ej. Versa 2020" />
</div>
<div class="form-field">
<label class="form-label" for="eoDelivery">V&iacute;a de entrega</label>
<select class="form-input" id="eoDelivery">
<option value=""></option>
<option value="pickup">Mostrador</option>
<option value="delivery">Env&iacute;o a domicilio</option>
<option value="courier">Motociclista</option>
</select>
</div>
<div class="form-field" id="eoCourierField" style="display:none;">
<label class="form-label" for="eoCourier">Motociclista</label>
<select class="form-input" id="eoCourier"></select>
</div>
<div class="form-field">
<label class="form-label" for="eoEstimatedCost">Presupuesto</label>
<input class="form-input" type="number" id="eoEstimatedCost" step="0.01" />
</div>
<div class="form-field">
<label class="form-label" for="eoMechanic">Mec&aacute;nico asignado (usuario)</label>
<select class="form-input" id="eoMechanic"></select>
</div>
<div class="form-field">
<label class="form-label" for="eoMechanicName">Mec&aacute;nico asignado (nombre libre)</label>
<input class="form-input" type="text" id="eoMechanicName" placeholder="Ej. Juan Pérez" />
</div>
<div class="form-field">
<label class="toolbar-toggle">
<input type="checkbox" id="eoRequiresInvoice" />
<span>Requiere factura</span>
</label>
</div>
<div class="form-field form-field--span2">
<label class="form-label" for="eoNotes">Observaciones</label>
<textarea class="form-input" id="eoNotes" rows="3"></textarea>
</div>
</form>
@@ -477,13 +493,13 @@
</div>
</div>
<script src="/pos/static/js/i18n.js?v=36" defer></script>
<script src="/pos/static/js/app-init.js?v=2" defer></script>
<script src="/pos/static/js/i18n.js?v=39" defer></script>
<script src="/pos/static/js/app-init.js?v=5" defer></script>
<script src="/pos/static/js/splash-loader.js?v=33" defer></script>
<script src="/pos/static/js/pos-utils.js?v=33" defer></script>
<script src="/pos/static/js/sidebar.js?v=36" defer></script>
<script src="/pos/static/js/sidebar.js?v=44" defer></script>
<script src="/pos/static/js/offline-banner.js" defer></script>
<script src="/pos/static/js/workshop.js?v=42" defer></script>
<script src="/pos/static/js/workshop.js?v=55" 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>
<script src="/pos/static/js/chat.js" defer></script>