/** * app-init.js — Shared initialization for all POS pages * * Handles: * 1. Auth check (redirect to login if no valid token) * 2. Set real employee name/role in sidebar and header * 3. Set navigation links as active based on current page * 4. Sidebar toggle for mobile */ (function() { 'use strict'; // ─── Auth Check ─── var token = localStorage.getItem('pos_token'); if (!token) { window.location.href = '/pos/login'; return; } // Validate token not expired try { var payload = JSON.parse(atob(token.split('.')[1])); if (payload.exp * 1000 < Date.now()) { localStorage.removeItem('pos_token'); localStorage.removeItem('pos_employee'); window.location.href = '/pos/login'; return; } } catch(e) { localStorage.removeItem('pos_token'); window.location.href = '/pos/login'; return; } // ─── Get employee info ─── var employee = {}; try { employee = JSON.parse(localStorage.getItem('pos_employee') || '{}'); } catch(e) {} var name = employee.name || payload.name || 'Usuario'; var role = employee.role || payload.role || ''; var _t = typeof window.t === 'function' ? window.t : function(k) { return k; }; var roleLabels = { 'owner': _t('role_owner'), 'admin': _t('role_admin'), 'cashier': _t('role_cashier'), 'warehouse': _t('role_warehouse'), 'accountant': _t('role_accountant'), 'workshop': 'Taller', 'mechanic': 'Mecanico' }; var roleLabel = roleLabels[role] || role; var initials = name.split(' ').map(function(p) { return p[0]; }).join('').toUpperCase().substring(0, 2); // ─── Replace hardcoded names in sidebar ─── // Sidebar user name document.querySelectorAll('.sidebar__user-name').forEach(function(el) { el.textContent = name; }); // Sidebar user role document.querySelectorAll('.sidebar__user-role').forEach(function(el) { el.textContent = roleLabel; }); // Sidebar user initials/avatar document.querySelectorAll('.sidebar__user-avatar, .sidebar__avatar').forEach(function(el) { el.textContent = initials; }); // Profile info in headers (catalog uses this pattern) document.querySelectorAll('.profile-info__name').forEach(function(el) { el.textContent = name; }); document.querySelectorAll('.profile-info__role').forEach(function(el) { el.textContent = roleLabel; }); // Theme bar user labels document.querySelectorAll('.theme-bar__label').forEach(function(el) { if (el.textContent.indexOf('Usuario:') !== -1 || el.textContent.indexOf('Sucursal') !== -1) { el.textContent = 'Sucursal Principal — ' + name; } }); // Status bar user names document.querySelectorAll('.status-bar .user-name, .status-info span').forEach(function(el) { var text = el.textContent; // Replace common demo names ['Hugo M.', 'Hugo García', 'J. Ramírez', 'José Ramírez', 'Carlos M.', 'Admin'].forEach(function(demo) { if (text.indexOf(demo) !== -1) { el.textContent = text.replace(demo, name); } }); }); // ─── Set active nav link ─── var path = window.location.pathname; var navMap = { '/pos/dashboard': 'dashboard', '/pos/sale': 'pos', '/pos/catalog': 'catalogo', '/pos/inventory': 'inventario', '/pos/customers': 'clientes', '/pos/invoicing': 'facturacion', '/pos/accounting': 'contabilidad', '/pos/reports': 'reportes', '/pos/config': 'configuracion' }; document.querySelectorAll('.nav-item, .nav-link').forEach(function(link) { link.classList.remove('is-active', 'active'); var href = link.getAttribute('href') || ''; if (href === path) { link.classList.add('is-active'); link.classList.add('active'); } }); // ─── Logout ─── window.posLogout = function() { localStorage.removeItem('pos_token'); localStorage.removeItem('pos_employee'); localStorage.removeItem('pos_tenant_id'); localStorage.removeItem('pos_cart'); document.cookie = 'pos_role=; path=/pos; SameSite=Lax; expires=Thu, 01 Jan 1970 00:00:00 GMT'; window.location.href = '/pos/login'; }; // Wire any logout buttons document.querySelectorAll('[data-action="logout"], .btn-logout, .logout-btn').forEach(function(btn) { btn.addEventListener('click', function(e) { e.preventDefault(); posLogout(); }); }); // ─── Theme management ─── // Determine theme: saved preference > system preference > default 'industrial' var savedTheme = localStorage.getItem('pos_theme'); if (!savedTheme) { // No saved preference — use system color scheme if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { savedTheme = 'industrial'; } else { savedTheme = 'modern'; } } document.documentElement.setAttribute('data-theme', savedTheme); // Hide all theme bars (they overlap content with position:fixed) document.querySelectorAll('.theme-bar').forEach(function(bar) { bar.style.display = 'none'; }); // Expose theme toggle function (global) window.posSetTheme = function(theme) { document.documentElement.setAttribute('data-theme', theme); localStorage.setItem('pos_theme', theme); }; // Override any page-level setTheme functions so they use our persistent version window.setTheme = window.posSetTheme; // Listen for system color scheme changes and auto-switch (only if user hasn't manually set a preference) if (window.matchMedia) { window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', function(e) { // Only auto-switch if user hasn't explicitly set a preference var userExplicit = localStorage.getItem('pos_theme'); if (!userExplicit) { var autoTheme = e.matches ? 'industrial' : 'modern'; document.documentElement.setAttribute('data-theme', autoTheme); } }); } // ─── Expose globally ─── window.POS_USER = { name: name, role: role, roleLabel: roleLabel, initials: initials, token: token, tenantId: payload.tenant_id, employeeId: payload.employee_id, branchId: payload.branch_id, permissions: payload.permissions || [] }; // ─── Page guard based on role + permissions ─── function isPageAllowed(pagePath, userRole, userPerms) { if (userRole === 'owner' || userRole === 'admin') return true; // Restricted roles (workshop/mechanic/counter/cashier) see modules based on permissions. if (['workshop', 'mechanic', 'counter', 'cashier'].indexOf(userRole) !== -1) { var allowed = []; if (userRole === 'workshop' || userRole === 'mechanic') { allowed = ['/pos/workshop']; } var permMap = { 'pos.sell': '/pos/sale', 'pos.view': '/pos/sale', 'catalog.view': '/pos/catalog', 'inventory.view': '/pos/inventory', 'customers.view': '/pos/customers', 'workshop.view': '/pos/workshop', 'pos.remission': '/pos/remission-notes', 'invoicing.view': '/pos/invoicing', 'quotations.view': '/pos/quotations', 'accounting.view': '/pos/accounting', 'reports.view': '/pos/reports', 'dashboard.view': '/pos/dashboard' }; for (var p in permMap) { if (userPerms.indexOf(p) !== -1 && allowed.indexOf(permMap[p]) === -1) { allowed.push(permMap[p]); } } return allowed.indexOf(pagePath) !== -1; } // Always allow login/logout pages so users can sign out without hitting the guard. if (pagePath === '/pos/login' || pagePath === '/pos/logout') return true; // Any other role (accountant, warehouse, sales, etc.) keeps the previous permissive behavior. return true; } function enforcePageGuard(userRole, userPerms) { if (isPageAllowed(path, userRole, userPerms)) return true; var fallback = '/pos/catalog'; if (userRole === 'counter' || userRole === 'cashier') fallback = '/pos/sale'; else if (userRole === 'workshop' || userRole === 'mechanic') fallback = '/pos/workshop'; window.location.replace(fallback); return false; } // ─── Refresh permissions/token from server before enforcing the guard ─── // This makes permission changes effective without requiring a full re-login. try { fetch('/pos/api/auth/refresh', { method: 'POST', headers: { 'Authorization': 'Bearer ' + token } }).then(function(r) { if (r.ok) return r.json(); return null; }).then(function(data) { if (data && data.token) { localStorage.setItem('pos_token', data.token); localStorage.setItem('pos_employee', JSON.stringify(data.employee)); token = data.token; window.POS_USER.token = data.token; window.POS_USER.permissions = data.permissions || []; window.POS_USER.branchId = data.employee.branch_id; } if (!enforcePageGuard(window.POS_USER.role, window.POS_USER.permissions)) return; if (typeof window.renderSidebar === 'function') { window.renderSidebar(window.POS_USER.modules || JSON.parse(localStorage.getItem('pos_modules') || '{}')); } }).catch(function() { enforcePageGuard(role, payload.permissions || []); }); } catch(e) { enforcePageGuard(role, payload.permissions || []); } // ─── Preload enabled modules for sidebar filtering ─── try { fetch('/pos/api/config/modules', { headers: { 'Authorization': 'Bearer ' + token } }).then(function(r) { if (r.ok) return r.json(); }).then(function(data) { if (data) { localStorage.setItem('pos_modules', JSON.stringify(data)); window.POS_USER.modules = data; if (typeof window.renderSidebar === 'function') { window.renderSidebar(data); } } }).catch(function() {}); } catch(e) {} // ─── Hide POS "Sistema" button for roles that cannot access the dashboard ─── (function hideBackToSystemForRestrictedRoles() { var backBtn = document.getElementById('backToSystemBtn'); if (!backBtn) return; // owner/admin always see it; others only if they have dashboard.view. if (role === 'owner' || role === 'admin') return; if ((window.POS_USER.permissions || []).indexOf('dashboard.view') !== -1) return; backBtn.style.display = 'none'; })(); // ─── Service Worker update handler ─── if ('serviceWorker' in navigator) { navigator.serviceWorker.addEventListener('message', function (event) { if (event.data && event.data.type === 'SW_UPDATED') { console.log('[AppInit] SW updated to', event.data.cacheName, '— reloading...'); window.location.reload(); } }); } // ─── Global toast utility ─── window.toast = function(msg, type) { type = type || 'success'; var bg = type === 'error' ? '#d32f2f' : '#388e3c'; var el = document.createElement('div'); el.style.cssText = 'position:fixed;bottom:20px;right:20px;z-index:99999;padding:12px 20px;border-radius:8px;background:' + bg + ';color:#fff;font-weight:500;box-shadow:0 4px 12px rgba(0,0,0,.2);opacity:0;transition:opacity .3s;'; el.textContent = msg; document.body.appendChild(el); // trigger reflow el.offsetHeight; el.style.opacity = '1'; setTimeout(function() { el.style.opacity = '0'; setTimeout(function() { if (el.parentNode) el.parentNode.removeChild(el); }, 300); }, 3000); }; })();