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

@@ -9,6 +9,8 @@ const Config = (() => {
// Cache for branches (used by employee modal selector)
let _branches = [];
let _rolePermissions = {};
let _availablePermissions = [];
function token() {
return localStorage.getItem('pos_token') || '';
@@ -108,6 +110,7 @@ const Config = (() => {
owner: 'Dueno',
admin: 'Admin',
cashier: 'Cajero',
counter: 'Mostrador',
warehouse: 'Almacenista',
accountant: 'Contador',
workshop: 'Taller',
@@ -118,6 +121,7 @@ const Config = (() => {
owner: 'badge--owner',
admin: 'badge--blue',
cashier: 'badge--green',
counter: 'badge--gray',
warehouse: 'badge--yellow',
accountant: 'badge--purple',
workshop: 'badge--orange',
@@ -961,6 +965,15 @@ const Config = (() => {
} catch (e) {
console.error('Config.loadModules:', e);
}
try {
var res2 = await fetch(API + '/counter-remission', { headers: headers() });
if (!res2.ok) return;
var d2 = await res2.json();
var cbCr = document.getElementById('cfg-module-counter-remission');
if (cbCr) cbCr.checked = d2.enabled === true;
} catch (e) {
console.error('Config.loadCounterRemission:', e);
}
}
async function saveModules() {
@@ -968,7 +981,8 @@ const Config = (() => {
var cbMp = document.getElementById('cfg-module-marketplace');
var cbMeli = document.getElementById('cfg-module-meli');
var cbCat = document.getElementById('cfg-module-catalog');
if (!cbWa && !cbMp && !cbMeli && !cbCat) return;
var cbCr = document.getElementById('cfg-module-counter-remission');
if (!cbWa && !cbMp && !cbMeli && !cbCat && !cbCr) return;
try {
var data = {
whatsapp: cbWa ? cbWa.checked : true,
@@ -986,12 +1000,112 @@ const Config = (() => {
throw new Error(err.error || 'Save failed');
}
localStorage.setItem('pos_modules', JSON.stringify(data));
if (cbCr) {
var res2 = await fetch(API + '/counter-remission', {
method: 'PUT',
headers: headers(),
body: JSON.stringify({ enabled: cbCr.checked })
});
if (!res2.ok) {
var err2 = await res2.json().catch(function() { return { error: res2.statusText }; });
throw new Error(err2.error || 'Save failed');
}
}
toast('Módulos actualizados');
} catch (e) {
toast(e.message, 'error');
}
}
// -------------------------------------------------------------------------
// Tab navigation
// -------------------------------------------------------------------------
function switchTab(tab) {
document.querySelectorAll('.cfg-tab-btn').forEach(function(btn) {
btn.classList.toggle('active', btn.dataset.tab === tab);
});
document.querySelectorAll('.settings-section[data-tab]').forEach(function(sec) {
var isActive = sec.dataset.tab === tab;
sec.classList.toggle('active', isActive);
sec.style.display = isActive ? '' : 'none';
});
try { localStorage.setItem('pos_config_tab', tab); } catch(e) {}
}
// -------------------------------------------------------------------------
// Role permissions editor
// -------------------------------------------------------------------------
async function loadRolePermissions() {
try {
var res = await fetch(API + '/role-permissions', { headers: headers() });
if (!res.ok) return;
var data = await res.json();
_rolePermissions = data.roles || {};
_availablePermissions = data.available || [];
renderRolePermissions();
} catch (e) {
console.error('Config.loadRolePermissions:', e);
}
}
function renderRolePermissions() {
var container = document.getElementById('role-permissions-container');
var roleSel = document.getElementById('cfg-perm-role');
if (!container || !roleSel) return;
var role = roleSel.value;
if (!role) {
container.innerHTML = '<p style="color:var(--color-text-muted);">Selecciona un rol para ver y editar sus permisos.</p>';
return;
}
var current = _rolePermissions[role] || [];
var html = '<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:var(--space-4);">';
_availablePermissions.forEach(function(group) {
html += '<div style="border:1px solid var(--color-border);border-radius:var(--radius-md);padding:var(--space-3);background:var(--color-surface-2);">';
html += '<h4 style="margin:0 0 var(--space-3);font-size:var(--text-body-sm);color:var(--color-text-primary);">' + escapeHtml(group.module) + '</h4>';
group.permissions.forEach(function(p) {
var checked = current.indexOf(p.key) !== -1 ? 'checked' : '';
html += '<label style="display:flex;align-items:center;gap:var(--space-2);margin-bottom:var(--space-2);cursor:pointer;font-size:var(--text-body-sm);">';
html += '<input type="checkbox" data-perm-key="' + escapeHtml(p.key) + '" ' + checked + ' style="width:auto;" />';
html += '<span>' + escapeHtml(p.label) + '</span>';
html += '</label>';
});
html += '</div>';
});
html += '</div>';
container.innerHTML = html;
}
async function saveRolePermissions() {
var roleSel = document.getElementById('cfg-perm-role');
var status = document.getElementById('role-permissions-status');
if (!roleSel || !roleSel.value) {
if (status) status.textContent = 'Selecciona un rol';
return;
}
var role = roleSel.value;
var selected = [];
document.querySelectorAll('#role-permissions-container input[data-perm-key]').forEach(function(cb) {
if (cb.checked) selected.push(cb.dataset.permKey);
});
var payload = { roles: {} };
payload.roles[role] = selected;
try {
var res = await fetch(API + '/role-permissions', {
method: 'PUT',
headers: headers(),
body: JSON.stringify(payload)
});
var data = await res.json().catch(function() { return { error: res.statusText }; });
if (!res.ok) throw new Error(data.error || 'Error al guardar');
_rolePermissions[role] = selected;
if (status) status.textContent = 'Permisos guardados y aplicados a empleados existentes';
setTimeout(function() { if (status) status.textContent = ''; }, 4000);
} catch (e) {
if (status) status.textContent = e.message;
toast(e.message, 'error');
}
}
// -------------------------------------------------------------------------
// Init
// -------------------------------------------------------------------------
@@ -1044,6 +1158,11 @@ const Config = (() => {
});
}
// Show permissions tab only for owner/admin
var isAdmin = user.role === 'owner' || user.role === 'admin';
var permTabBtn = document.querySelector('.cfg-tab-btn--permissions');
if (permTabBtn) permTabBtn.style.display = isAdmin ? '' : 'none';
// Load real data in parallel
loadBranches();
loadEmployees();
@@ -1053,6 +1172,15 @@ const Config = (() => {
loadAllowedBrands();
loadModules();
loadReceiptConfig();
if (isAdmin) loadRolePermissions();
// Activate default or stored tab
var defaultTab = 'general';
try {
var storedTab = localStorage.getItem('pos_config_tab');
if (storedTab) defaultTab = storedTab;
} catch(e) {}
switchTab(defaultTab);
}
document.addEventListener('DOMContentLoaded', init);
@@ -1074,7 +1202,8 @@ const Config = (() => {
loadVehicleCompatSource, saveVehicleCompatSource,
loadModules, saveModules,
loadReceiptConfig, saveReceiptConfig, handleReceiptLogo, removeReceiptLogo,
openModal, closeModal, openBranchModal, editBranch
openModal, closeModal, openBranchModal, editBranch,
switchTab, loadRolePermissions, renderRolePermissions, saveRolePermissions
};
})();