';
return employees;
}
var html = '';
employees.forEach(function(emp) {
var ini = initials(emp.name);
var statusBadge = emp.is_active
? 'Activo'
: 'Inactivo';
html += '
'
+ '
'
+ '
' + ini + '
'
+ '' + escHtml(emp.name) + ''
+ '
'
+ '
' + escHtml(emp.email || '-') + '
'
+ '
' + roleBadge(emp.role) + '
'
+ '
' + escHtml(emp.branch_name || 'Todas') + '
'
+ '
' + statusBadge + '
'
+ '
' + (emp.max_discount_pct || 0) + '%
'
+ '
'
+ '
';
});
tbody.innerHTML = html;
return employees;
}
async function saveEmployee(data) {
// Check if we're editing (modal has editId) or creating
var modal = document.getElementById('employee-modal');
var editId = modal ? modal.dataset.editId : null;
var url = API + '/employees';
var method = 'POST';
if (editId) {
url = API + '/employees/' + editId;
method = 'PUT';
// Clear the edit marker so next use is a fresh create
delete modal.dataset.editId;
}
var res = await fetch(url, {
method: method,
headers: headers(),
body: JSON.stringify(data)
});
if (!res.ok) {
var err = await res.json().catch(function() { return { error: res.statusText }; });
throw new Error(err.error || 'Save failed');
}
return res.json();
}
// -------------------------------------------------------------------------
// Business data (read-only)
// -------------------------------------------------------------------------
async function loadBusiness() {
try {
var res = await fetch(API + '/business', { headers: headers() });
if (!res.ok) return;
var d = await res.json();
setVal('biz-razon-social', d.razon_social);
setVal('biz-nombre', d.nombre);
setVal('biz-rfc', d.rfc);
setVal('biz-regimen', d.regimen_fiscal);
setVal('biz-direccion', d.direccion);
setVal('biz-telefono', d.telefono);
setVal('biz-email', d.email);
} catch (e) {
console.error('Config.loadBusiness:', e);
}
}
function setVal(id, v) {
var el = document.getElementById(id);
if (el) el.value = v || '';
}
function getVal(id) {
var el = document.getElementById(id);
return el ? el.value.trim() : '';
}
async function editEmployee(empId) {
if (!checkAuth()) return;
// Find the employee in the loaded data by re-fetching
try {
var res = await fetch(API + '/employees', { headers: headers() });
if (!res.ok) throw new Error('Failed to load employees');
var json = await res.json();
var emp = (json.data || []).find(function(e) { return e.id === empId; });
if (!emp) { toast('Empleado no encontrado', 'error'); return; }
// Pre-fill the "new employee" modal with existing data for editing
setVal('new-emp-name', emp.name);
setVal('new-emp-email', emp.email || '');
var roleSelect = document.getElementById('new-emp-role');
if (roleSelect) roleSelect.value = emp.role || 'cashier';
var branchSelect = document.getElementById('new-emp-branch');
if (branchSelect) branchSelect.value = emp.branch_id || '';
setVal('new-emp-discount', emp.max_discount_pct || '');
setVal('new-emp-pin', ''); // Don't pre-fill PIN for security
// Store the ID so saveEmployee knows it's an update
var modal = document.getElementById('employee-modal');
if (modal) {
modal.dataset.editId = empId;
var title = modal.querySelector('.modal-title, h3');
if (title) title.textContent = 'Editar Empleado';
}
openModal('employee-modal');
} catch (e) {
toast('Error: ' + e.message, 'error');
}
}
async function saveTaxParams() {
if (!checkAuth()) return;
var data = {
tax_iva: getVal('tax-iva') || '16',
tax_ieps: getVal('tax-ieps') || '0',
invoice_serie: getVal('tax-serie') || 'FA',
invoice_folio: getVal('tax-folio') || '1',
default_currency: document.getElementById('tax-moneda') ? document.getElementById('tax-moneda').value : 'MXN',
default_payment_method: document.getElementById('tax-forma-pago') ? document.getElementById('tax-forma-pago').value : '01',
};
try {
// Use the business PUT endpoint with tax_ prefixed keys
var res = await fetch(API + '/business', {
method: 'PUT',
headers: headers(),
body: JSON.stringify(data),
});
if (!res.ok) throw new Error('Error al guardar');
toast('ParĂ¡metros de impuestos guardados', 'ok');
} catch (e) {
toast(e.message, 'error');
}
}
async function saveBusiness() {
if (!checkAuth()) return;
var data = {
razon_social: getVal('biz-razon-social'),
nombre: getVal('biz-nombre'),
rfc: getVal('biz-rfc'),
regimen_fiscal: getVal('biz-regimen'),
direccion: getVal('biz-direccion'),
telefono: getVal('biz-telefono'),
email: getVal('biz-email'),
};
try {
var res = await fetch(API + '/business', {
method: 'PUT',
headers: headers(),
body: JSON.stringify(data),
});
if (!res.ok) {
var err = await res.json().catch(function() { return { error: res.statusText }; });
throw new Error(err.error || 'Error al guardar');
}
toast('Datos de empresa guardados', 'ok');
} catch (e) {
toast(e.message, 'error');
}
}
// -------------------------------------------------------------------------
// Event bindings
// -------------------------------------------------------------------------
function bindEvents() {
// New Branch modal
var btnNewBranch = document.querySelector('#branches-grid');
// The "Agregar Sucursal" card is rendered dynamically, handled via onclick
// Save Branch
var btnSaveBranch = document.getElementById('btn-save-branch');
if (btnSaveBranch) {
btnSaveBranch.addEventListener('click', async function() {
var name = document.getElementById('branch-name').value.trim();
if (!name) { toast('Nombre de sucursal requerido', 'error'); return; }
btnSaveBranch.disabled = true;
btnSaveBranch.textContent = 'Guardando...';
try {
await saveBranch({
name: name,
address: document.getElementById('branch-address').value.trim(),
phone: document.getElementById('branch-phone').value.trim()
});
toast('Sucursal creada');
closeModal('modal-branch');
document.getElementById('branch-name').value = '';
document.getElementById('branch-address').value = '';
document.getElementById('branch-phone').value = '';
await loadBranches();
} catch (e) {
toast(e.message, 'error');
} finally {
btnSaveBranch.disabled = false;
btnSaveBranch.textContent = 'Guardar Sucursal';
}
});
}
// New Employee modal
var btnNewEmp = document.getElementById('btn-new-employee');
if (btnNewEmp) {
btnNewEmp.addEventListener('click', function() {
openModal('modal-employee');
});
}
// Save Employee
var btnSaveEmp = document.getElementById('btn-save-employee');
if (btnSaveEmp) {
btnSaveEmp.addEventListener('click', async function() {
var name = document.getElementById('emp-name').value.trim();
var role = document.getElementById('emp-role').value;
var pin = document.getElementById('emp-pin').value.trim();
if (!name) { toast('Nombre requerido', 'error'); return; }
if (!role) { toast('Selecciona un rol', 'error'); return; }
if (!pin || pin.length !== 4 || !/^\d{4}$/.test(pin)) {
toast('PIN debe ser 4 digitos', 'error');
return;
}
var branchId = document.getElementById('emp-branch').value;
btnSaveEmp.disabled = true;
btnSaveEmp.textContent = 'Guardando...';
try {
await saveEmployee({
name: name,
email: document.getElementById('emp-email').value.trim() || null,
phone: document.getElementById('emp-phone').value.trim() || null,
role: role,
pin: pin,
branch_id: branchId ? parseInt(branchId, 10) : null,
max_discount_pct: parseFloat(document.getElementById('emp-discount').value) || 0
});
toast('Empleado creado');
closeModal('modal-employee');
// Reset form
document.getElementById('emp-name').value = '';
document.getElementById('emp-email').value = '';
document.getElementById('emp-phone').value = '';
document.getElementById('emp-role').value = '';
document.getElementById('emp-pin').value = '';
document.getElementById('emp-branch').value = '';
document.getElementById('emp-discount').value = '0';
await loadEmployees();
} catch (e) {
toast(e.message, 'error');
} finally {
btnSaveEmp.disabled = false;
btnSaveEmp.textContent = 'Guardar Empleado';
}
});
}
// Close modals on overlay click
document.querySelectorAll('.cfg-modal-overlay').forEach(function(overlay) {
overlay.addEventListener('click', function(ev) {
if (ev.target === overlay) {
overlay.style.display = 'none';
}
});
});
// Close modals on Escape key
document.addEventListener('keydown', function(ev) {
if (ev.key === 'Escape') {
document.querySelectorAll('.cfg-modal-overlay').forEach(function(o) {
o.style.display = 'none';
});
}
});
}
// -------------------------------------------------------------------------
// Currency
// -------------------------------------------------------------------------
async function loadCurrency() {
try {
var res = await fetch(API + '/currency', { headers: headers() });
if (!res.ok) return;
var d = await res.json();
var selCurrency = document.getElementById('cfg-currency');
var inpRate = document.getElementById('cfg-exchange-rate');
if (selCurrency) selCurrency.value = d.currency || 'MXN';
if (inpRate) inpRate.value = d.exchange_rate || 17.5;
// Store in localStorage for POS fmt() to pick up
localStorage.setItem('pos_currency', d.currency || 'MXN');
localStorage.setItem('pos_exchange_rate', d.exchange_rate || 17.5);
} catch (e) {
console.error('Config.loadCurrency:', e);
}
}
async function saveCurrency() {
var selCurrency = document.getElementById('cfg-currency');
var inpRate = document.getElementById('cfg-exchange-rate');
var statusEl = document.getElementById('currency-status');
var btn = document.getElementById('btn-save-currency');
if (!selCurrency || !inpRate) return;
var currency = selCurrency.value;
var rate = parseFloat(inpRate.value);
if (!rate || rate <= 0) {
toast('Tipo de cambio invalido', 'error');
return;
}
if (btn) { btn.disabled = true; btn.textContent = 'Guardando...'; }
try {
var res = await fetch(API + '/currency', {
method: 'PUT',
headers: headers(),
body: JSON.stringify({ currency: currency, exchange_rate: rate })
});
if (!res.ok) {
var err = await res.json().catch(function() { return { error: res.statusText }; });
throw new Error(err.error || 'Save failed');
}
localStorage.setItem('pos_currency', currency);
localStorage.setItem('pos_exchange_rate', rate);
toast('Moneda actualizada');
if (statusEl) statusEl.textContent = currency + ' — TC: ' + rate;
} catch (e) {
toast(e.message, 'error');
} finally {
if (btn) {
btn.disabled = false;
btn.textContent = 'Guardar Moneda';
}
}
}
// -------------------------------------------------------------------------
// Vehicle Compatibility Source
// -------------------------------------------------------------------------
async function loadVehicleCompatSource() {
try {
var res = await fetch(API + '/vehicle-compat-source', { headers: headers() });
if (!res.ok) return;
var d = await res.json();
var sel = document.getElementById('cfg-compat-source');
if (sel) sel.value = d.source || 'both';
} catch (e) {
console.error('Config.loadVehicleCompatSource:', e);
}
}
async function saveVehicleCompatSource() {
var sel = document.getElementById('cfg-compat-source');
var btn = document.getElementById('btn-save-compat-source');
if (!sel) return;
if (btn) { btn.disabled = true; btn.textContent = 'Guardando...'; }
try {
var res = await fetch(API + '/vehicle-compat-source', {
method: 'PUT',
headers: headers(),
body: JSON.stringify({ source: sel.value })
});
if (!res.ok) {
var err = await res.json().catch(function() { return { error: res.statusText }; });
throw new Error(err.error || 'Save failed');
}
toast('Fuente de compatibilidad actualizada');
} catch (e) {
toast(e.message, 'error');
} finally {
if (btn) {
btn.disabled = false;
btn.textContent = 'Guardar';
}
}
}
// -------------------------------------------------------------------------
// Allowed Part Brands
// -------------------------------------------------------------------------
async function loadAllowedBrands() {
var container = document.getElementById('allowed-brands-container');
if (!container) return;
try {
var res = await fetch(API + '/available-brands', { headers: headers() });
if (!res.ok) throw new Error('Failed to load brands');
var d = await res.json();
var allBrands = d.brands || [];
var res2 = await fetch(API + '/allowed-brands', { headers: headers() });
if (!res2.ok) throw new Error('Failed to load allowed brands');
var d2 = await res2.json();
var allowed = d2.brands || [];
if (!allBrands.length) {
container.innerHTML = '
No hay marcas disponibles.
';
return;
}
var html = '';
allBrands.forEach(function(b) {
var checked = allowed.indexOf(b) !== -1 ? 'checked' : '';
html += '';
});
container.innerHTML = html;
} catch (e) {
console.error('Config.loadAllowedBrands:', e);
if (container) container.innerHTML = '
Error al cargar marcas.
';
}
}
async function saveAllowedBrands() {
var btn = document.getElementById('btn-save-allowed-brands');
if (btn) { btn.disabled = true; btn.textContent = 'Guardando...'; }
try {
var checked = [];
document.querySelectorAll('[data-brand-checkbox]').forEach(function(cb) {
if (cb.checked) checked.push(cb.value);
});
var res = await fetch(API + '/allowed-brands', {
method: 'PUT',
headers: headers(),
body: JSON.stringify({ brands: checked })
});
if (!res.ok) {
var err = await res.json().catch(function() { return { error: res.statusText }; });
throw new Error(err.error || 'Save failed');
}
toast('Marcas permitidas actualizadas');
} catch (e) {
toast(e.message, 'error');
} finally {
if (btn) { btn.disabled = false; btn.textContent = 'Guardar Marcas'; }
}
}
// -------------------------------------------------------------------------
// Init
// -------------------------------------------------------------------------
function init() {
if (!checkAuth()) return;
// Restore theme
try {
var saved = localStorage.getItem('nexus-theme');
if (saved === 'industrial' || saved === 'modern') {
setTheme(saved);
}
} catch(e) {}
// Start clock
updateClock();
setInterval(updateClock, 30000);
// Bind UI events
bindEvents();
// Vehicle compat source save button
var btnCompat = document.getElementById('btn-save-compat-source');
if (btnCompat) {
btnCompat.addEventListener('click', saveVehicleCompatSource);
}
// Allowed brands save button
var btnBrands = document.getElementById('btn-save-allowed-brands');
if (btnBrands) {
btnBrands.addEventListener('click', saveAllowedBrands);
}
// Kiosk mode toggle
var kioskToggle = document.getElementById('cfg-kiosk-mode');
if (kioskToggle && window.NexusKiosk) {
kioskToggle.checked = window.NexusKiosk.isEnabled();
kioskToggle.addEventListener('change', function () {
if (this.checked) {
window.NexusKiosk.enable();
toast('Modo Kiosko activado');
} else {
window.NexusKiosk.disable();
toast('Modo Kiosko desactivado');
}
});
}
// Load real data in parallel
loadBranches();
loadEmployees();
loadBusiness();
loadCurrency();
loadVehicleCompatSource();
loadAllowedBrands();
}
document.addEventListener('DOMContentLoaded', init);
return {
init, setTheme, selectThemeOption, loadAllowedBrands, saveAllowedBrands,
loadBranches, loadEmployees, saveBranch, saveEmployee, editEmployee,
loadBusiness, saveBusiness, saveTaxParams,
loadCurrency, saveCurrency,
openModal, closeModal
};
})();