// /home/Autopartes/pos/static/js/catalog.js
// Catalog UI: TecDoc vehicle navigation with local stock enrichment, cart, detail panel.
(function () {
'use strict';
var API = '/pos/api/catalog';
var token = localStorage.getItem('pos_token');
if (!token) { window.location.href = '/pos/login'; return; }
var headers = { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' };
// ─── DOM refs ───
var breadcrumb = document.getElementById('breadcrumb');
var searchInput = document.getElementById('searchInput');
var searchDropdown = document.getElementById('searchDropdown');
var levelTitle = document.getElementById('levelTitle');
var levelFilter = document.getElementById('levelFilter');
var loading = document.getElementById('loading');
var emptyState = document.getElementById('emptyState');
var emptyTitle = document.getElementById('emptyTitle');
var emptySubtitle = document.getElementById('emptySubtitle');
var navGrid = document.getElementById('navGrid');
var partsGrid = document.getElementById('partsGrid');
var paginationNav = document.getElementById('pagination');
var pageBody = document.getElementById('pageBody');
// Detail panel
var detailPanel = document.getElementById('detailPanel');
var detailOverlay = document.getElementById('detailOverlay');
var detailBody = document.getElementById('detailBody');
var detailFooter = document.getElementById('detailFooter');
var detailClose = document.getElementById('detailClose');
var qtyMinus = document.getElementById('qtyMinus');
var qtyPlus = document.getElementById('qtyPlus');
var qtyDisplay = document.getElementById('qtyDisplay');
var addToCartBtn = document.getElementById('addToCartBtn');
// Cart
var cartSidebar = document.getElementById('cartSidebar');
var cartOverlay = document.getElementById('cartOverlay');
var cartItemsEl = document.getElementById('cartItems');
var cartEmptyEl = document.getElementById('cartEmpty');
var cartSubtotalEl= document.getElementById('cartSubtotal');
var cartTaxEl = document.getElementById('cartTax');
var cartTotalEl = document.getElementById('cartTotal');
var cartBadge = document.getElementById('cartBadge');
var checkoutBtn = document.getElementById('checkoutBtn');
var cartFab = document.getElementById('cartFab');
var cartCloseBtn = document.getElementById('cartCloseBtn');
// ─── Navigation State ───
var nav = {
level: 'brands', // brands|models|years|engines|categories|groups|parts
brand: null, // {id, name}
model: null, // {id, name}
year: null, // {id, year}
engine: null, // {id_mye, name}
category: null, // {id, name}
group: null, // {id, name}
};
var currentPage = 1;
var currentDetailPart = null;
var detailQty = 1;
var isOffline = false;
// ─── Cart State ───
var cartItems = JSON.parse(localStorage.getItem('pos_cart') || '[]');
// ─── API helper ───
function apiFetch(url) {
return fetch(url, { headers: headers })
.then(function (resp) {
if (resp.status === 401) {
localStorage.removeItem('pos_token');
window.location.href = '/pos/login';
return null;
}
return resp.json();
})
.catch(function (e) {
console.error('API error:', e);
return null;
});
}
// ─── UI helpers ───
function showLoading() { loading.classList.add('is-visible'); navGrid.innerHTML = ''; partsGrid.style.display = 'none'; partsGrid.innerHTML = ''; emptyState.classList.remove('is-visible'); paginationNav.innerHTML = ''; }
function hideLoading() { loading.classList.remove('is-visible'); }
function showEmpty(title, subtitle) {
emptyTitle.textContent = title;
emptySubtitle.textContent = subtitle || '';
emptyState.classList.add('is-visible');
navGrid.innerHTML = '';
partsGrid.style.display = 'none';
}
function fmt(n) { return (parseFloat(n) || 0).toFixed(2); }
function esc(s) {
if (!s) return '';
var d = document.createElement('div');
d.textContent = s;
return d.innerHTML;
}
// ─── Breadcrumb ───
function updateBreadcrumb() {
var parts = [];
parts.push({ label: 'Catalogo', action: 'loadBrands' });
if (nav.brand) parts.push({ label: nav.brand.name, action: 'loadModels' });
if (nav.model) parts.push({ label: nav.model.name, action: 'loadYears' });
if (nav.year) parts.push({ label: String(nav.year.year), action: 'loadEngines' });
if (nav.engine) parts.push({ label: nav.engine.name, action: 'loadCategories' });
if (nav.category) parts.push({ label: nav.category.name, action: 'loadGroups' });
if (nav.group) parts.push({ label: nav.group.name, action: null });
var html = '';
for (var i = 0; i < parts.length; i++) {
if (i > 0) html += '/ ';
if (i < parts.length - 1 && parts[i].action) {
html += '' + esc(parts[i].label) + ' ';
} else {
html += '' + esc(parts[i].label) + ' ';
}
}
breadcrumb.innerHTML = html;
// Wire breadcrumb clicks
breadcrumb.querySelectorAll('[data-bc-action]').forEach(function (el) {
el.addEventListener('click', function () {
var action = this.dataset.bcAction;
if (action === 'loadBrands') { resetNav(); loadBrands(); }
else if (action === 'loadModels') { resetNavFrom('models'); loadModels(); }
else if (action === 'loadYears') { resetNavFrom('years'); loadYears(); }
else if (action === 'loadEngines') { resetNavFrom('engines'); loadEngines(); }
else if (action === 'loadCategories') { resetNavFrom('categories'); loadCategories(); }
else if (action === 'loadGroups') { resetNavFrom('groups'); loadGroups(); }
});
});
}
function resetNav() {
nav.level = 'brands';
nav.brand = nav.model = nav.year = nav.engine = nav.category = nav.group = null;
}
function resetNavFrom(level) {
var levels = ['brands', 'models', 'years', 'engines', 'categories', 'groups', 'parts'];
var idx = levels.indexOf(level);
if (idx <= 0) { resetNav(); return; }
nav.level = level;
var keys = [null, 'model', 'year', 'engine', 'category', 'group', null];
for (var i = idx; i < keys.length; i++) {
if (keys[i]) nav[keys[i]] = null;
}
}
// ─── Level filter ───
function setupLevelFilter(show) {
if (!show) { levelFilter.style.display = 'none'; levelFilter.value = ''; return; }
levelFilter.style.display = '';
levelFilter.value = '';
levelFilter.oninput = function () {
var q = this.value.toLowerCase();
var cards = navGrid.querySelectorAll('.nav-card');
cards.forEach(function (card) {
var text = card.textContent.toLowerCase();
card.style.display = text.indexOf(q) >= 0 ? '' : 'none';
});
};
}
// ─── LEVEL LOADERS ───
function loadBrands() {
nav.level = 'brands';
updateBreadcrumb();
levelTitle.textContent = 'Selecciona una marca';
setupLevelFilter(true);
showLoading();
apiFetch(API + '/brands').then(function (data) {
hideLoading();
if (!data || !data.data || !data.data.length) {
if (!data) {
enterOfflineMode();
return;
}
showEmpty('Sin marcas', 'El catalogo no tiene marcas con partes disponibles.');
return;
}
navGrid.className = 'nav-grid';
navGrid.innerHTML = data.data.map(function (b) {
return '
' +
'
' + esc(b.name_brand) + '
' +
'
';
}).join('');
navGrid.querySelectorAll('.nav-card').forEach(function (card) {
card.addEventListener('click', function () {
nav.brand = { id: parseInt(this.dataset.brandId), name: this.dataset.name };
loadModels();
});
});
});
}
function loadModels() {
nav.level = 'models';
updateBreadcrumb();
levelTitle.textContent = 'Modelos de ' + nav.brand.name;
setupLevelFilter(true);
showLoading();
apiFetch(API + '/models?brand_id=' + nav.brand.id).then(function (data) {
hideLoading();
if (!data || !data.data || !data.data.length) { showEmpty('Sin modelos', 'No hay modelos con partes para ' + nav.brand.name); return; }
navGrid.className = 'nav-grid';
navGrid.innerHTML = data.data.map(function (m) {
return '' +
'
' + esc(m.name_model) + '
' +
'
';
}).join('');
navGrid.querySelectorAll('.nav-card').forEach(function (card) {
card.addEventListener('click', function () {
nav.model = { id: parseInt(this.dataset.modelId), name: this.dataset.name };
loadYears();
});
});
});
}
function loadYears() {
nav.level = 'years';
updateBreadcrumb();
levelTitle.textContent = nav.brand.name + ' ' + nav.model.name + ' — Anios';
setupLevelFilter(false);
showLoading();
apiFetch(API + '/years?model_id=' + nav.model.id).then(function (data) {
hideLoading();
if (!data || !data.data || !data.data.length) { showEmpty('Sin anios', 'No hay anios con partes para este modelo.'); return; }
navGrid.className = 'nav-grid nav-grid--years';
navGrid.innerHTML = data.data.map(function (y) {
return '' +
'
' + y.year_car + '
' +
'
';
}).join('');
navGrid.querySelectorAll('.nav-card').forEach(function (card) {
card.addEventListener('click', function () {
nav.year = { id: parseInt(this.dataset.yearId), year: parseInt(this.dataset.year) };
loadEngines();
});
});
});
}
function loadEngines() {
nav.level = 'engines';
updateBreadcrumb();
levelTitle.textContent = nav.brand.name + ' ' + nav.model.name + ' ' + nav.year.year + ' — Motor';
setupLevelFilter(false);
showLoading();
apiFetch(API + '/engines?model_id=' + nav.model.id + '&year_id=' + nav.year.id).then(function (data) {
hideLoading();
if (!data || !data.data || !data.data.length) { showEmpty('Sin motores', 'No hay configuraciones de motor para esta combinacion.'); return; }
// If only one engine, auto-select it
if (data.data.length === 1) {
var e = data.data[0];
nav.engine = { id_mye: e.id_mye, name: e.name_engine + (e.trim_level ? ' ' + e.trim_level : '') };
loadCategories();
return;
}
navGrid.className = 'nav-grid';
navGrid.innerHTML = data.data.map(function (e) {
var label = e.name_engine + (e.trim_level ? ' — ' + e.trim_level : '');
return '' +
'
' + esc(e.name_engine) + '
' +
(e.trim_level ? '
' + esc(e.trim_level) + '
' : '') +
'
';
}).join('');
navGrid.querySelectorAll('.nav-card').forEach(function (card) {
card.addEventListener('click', function () {
nav.engine = { id_mye: parseInt(this.dataset.myeId), name: this.dataset.name };
loadCategories();
});
});
});
}
function loadCategories() {
nav.level = 'categories';
updateBreadcrumb();
levelTitle.textContent = 'Categorias de partes';
setupLevelFilter(true);
showLoading();
apiFetch(API + '/categories?mye_id=' + nav.engine.id_mye).then(function (data) {
hideLoading();
if (!data || !data.data || !data.data.length) { showEmpty('Sin categorias', 'No hay partes catalogadas para este vehiculo.'); return; }
navGrid.className = 'nav-grid';
navGrid.innerHTML = data.data.map(function (c) {
return '' +
'
' + esc(c.name) + '
' +
'
' + c.part_count + ' partes
' +
'
';
}).join('');
navGrid.querySelectorAll('.nav-card').forEach(function (card) {
card.addEventListener('click', function () {
nav.category = { id: parseInt(this.dataset.catId), name: this.dataset.name };
loadGroups();
});
});
});
}
function loadGroups() {
nav.level = 'groups';
updateBreadcrumb();
levelTitle.textContent = nav.category.name;
setupLevelFilter(true);
showLoading();
apiFetch(API + '/groups?mye_id=' + nav.engine.id_mye + '&category_id=' + nav.category.id).then(function (data) {
hideLoading();
if (!data || !data.data || !data.data.length) { showEmpty('Sin subcategorias', 'No hay subcategorias para ' + nav.category.name); return; }
navGrid.className = 'nav-grid';
navGrid.innerHTML = data.data.map(function (g) {
return '' +
'
' + esc(g.name) + '
' +
'
' + g.part_count + ' partes
' +
'
';
}).join('');
navGrid.querySelectorAll('.nav-card').forEach(function (card) {
card.addEventListener('click', function () {
nav.group = { id: parseInt(this.dataset.groupId), name: this.dataset.name };
loadParts(1);
});
});
});
}
function loadParts(page) {
nav.level = 'parts';
currentPage = page || 1;
updateBreadcrumb();
levelTitle.textContent = nav.group.name;
setupLevelFilter(false);
showLoading();
navGrid.innerHTML = '';
apiFetch(API + '/parts?mye_id=' + nav.engine.id_mye + '&group_id=' + nav.group.id + '&page=' + currentPage + '&per_page=30').then(function (data) {
hideLoading();
if (!data || !data.data || !data.data.length) { showEmpty('Sin partes', 'No hay partes en este grupo.'); return; }
partsGrid.style.display = '';
partsGrid.innerHTML = data.data.map(function (p) {
var stockBadge;
if (p.local_stock > 0) {
stockBadge = 'En stock: ' + p.local_stock + ' ';
} else if (p.bodega_count > 0) {
stockBadge = '' + p.bodega_count + ' bodega' + (p.bodega_count > 1 ? 's' : '') + ' ';
} else {
stockBadge = 'Sin stock ';
}
var imgHtml = p.image_url
? ' '
: ' ';
return '' +
'' + imgHtml + '
' +
'' +
'
' + esc(p.oem_part_number) + '
' +
'
' + esc(p.name) + '
' +
'
' +
'' +
' ';
}).join('');
// Wire part card clicks → open detail panel
partsGrid.querySelectorAll('.part-card').forEach(function (card) {
card.addEventListener('click', function () {
openPartDetail(parseInt(this.dataset.partId));
});
});
// Pagination
if (data.pagination) renderPagination(data.pagination);
});
}
// ─── PAGINATION ───
function renderPagination(pg) {
if (!pg || pg.total_pages <= 1) { paginationNav.innerHTML = ''; return; }
var html = '';
if (pg.page <= 1) {
html += 'Anterior ';
} else {
html += 'Anterior ';
}
var pages = buildPageNumbers(pg.page, pg.total_pages);
pages.forEach(function (p) {
if (p === '...') {
html += '... ';
} else if (p === pg.page) {
html += '' + p + ' ';
} else {
html += '' + p + ' ';
}
});
if (pg.page >= pg.total_pages) {
html += 'Siguiente ';
} else {
html += 'Siguiente ';
}
paginationNav.innerHTML = html;
paginationNav.querySelectorAll('[data-page]').forEach(function (btn) {
btn.addEventListener('click', function () {
pageBody.scrollTo({ top: 0, behavior: 'smooth' });
loadParts(parseInt(this.dataset.page));
});
});
}
function buildPageNumbers(current, total) {
if (total <= 7) { var a = []; for (var i = 1; i <= total; i++) a.push(i); return a; }
var p = [1];
if (current > 3) p.push('...');
for (var j = Math.max(2, current - 1); j <= Math.min(total - 1, current + 1); j++) p.push(j);
if (current < total - 2) p.push('...');
p.push(total);
return p;
}
// ─── DETAIL PANEL ───
function openPartDetail(partId) {
detailBody.innerHTML = '';
detailFooter.style.display = 'none';
detailPanel.classList.add('is-open');
detailOverlay.classList.add('is-visible');
detailQty = 1;
qtyDisplay.textContent = '1';
apiFetch(API + '/part/' + partId).then(function (data) {
if (!data || data.error) {
detailBody.innerHTML = 'Error al cargar detalle.
';
return;
}
currentDetailPart = data;
var p = data.part;
var local = data.local;
var bodegas = data.bodegas || [];
var alts = data.alternatives || [];
var html = '';
// Part info
html += '';
if (p.category_name) html += '
' + esc(p.category_name) + ' > ' + esc(p.group_name) + '
';
html += '
' + esc(p.oem_part_number) + '
';
html += '
' + esc(p.name) + '
';
if (p.description) html += '
' + esc(p.description) + '
';
if (p.image_url) html += '
';
html += '
';
// Local stock
html += '';
html += '
Mi stock
';
if (local && local.stock > 0) {
html += '
Cantidad ' + local.stock + ' ' + (local.unit || 'PZA') + '
';
html += '
Precio publico $' + fmt(local.price_1) + '
';
if (local.price_2) html += '
Precio mayoreo $' + fmt(local.price_2) + '
';
if (local.price_3) html += '
Precio taller $' + fmt(local.price_3) + '
';
if (local.location) html += '
Ubicacion ' + esc(local.location) + '
';
} else {
html += '
No tienes esta parte en inventario.
';
}
html += '
';
// Bodegas
if (bodegas.length) {
html += '';
html += '
Disponible en bodegas
';
html += '
Bodega Precio Stock ';
bodegas.forEach(function (b) {
html += '' + esc(b.business_name) + ' ' + (b.price ? '$' + fmt(b.price) : '--') + ' ' + b.stock + ' ';
});
html += '
';
}
// Alternatives
if (alts.length) {
html += '';
html += '
Alternativas / Cross-references
';
alts.forEach(function (a) {
var stockLabel = a.local_stock > 0
? '
Stock: ' + a.local_stock + ' '
: (a.bodega_count > 0 ? '
' + a.bodega_count + ' bod. ' : '');
html += '
' +
'
' + esc(a.part_number) + '
' +
'
' + esc(a.manufacturer) + (a.name ? ' — ' + esc(a.name) : '') + '
' +
'
' + stockLabel + '
' +
'
';
});
html += '
';
}
detailBody.innerHTML = html;
// Show footer only if we have local stock
if (local && local.stock > 0) {
detailFooter.style.display = '';
} else {
detailFooter.style.display = 'none';
}
});
}
function closeDetail() {
detailPanel.classList.remove('is-open');
detailOverlay.classList.remove('is-visible');
currentDetailPart = null;
}
detailClose.addEventListener('click', closeDetail);
detailOverlay.addEventListener('click', closeDetail);
qtyMinus.addEventListener('click', function () { if (detailQty > 1) { detailQty--; qtyDisplay.textContent = detailQty; } });
qtyPlus.addEventListener('click', function () { detailQty++; qtyDisplay.textContent = detailQty; });
addToCartBtn.addEventListener('click', function () {
if (!currentDetailPart) return;
var p = currentDetailPart.part;
var local = currentDetailPart.local;
if (!local) return;
addToCart({
id: p.id_part,
part_number: p.oem_part_number,
name: p.name,
brand: '',
price: local.price_1,
tax_rate: local.tax_rate || 0.16,
unit: local.unit || 'PZA',
stock: local.stock,
source: 'local',
inventory_id: local.inventory_id,
}, detailQty);
closeDetail();
});
// ─── SMART SEARCH ───
var searchTimeout = null;
searchInput.addEventListener('input', function () {
clearTimeout(searchTimeout);
var q = this.value.trim();
if (q.length < 2) { searchDropdown.classList.remove('is-visible'); return; }
searchTimeout = setTimeout(function () { runSearch(q); }, 350);
});
searchInput.addEventListener('keydown', function (e) {
if (e.key === 'Enter') {
e.preventDefault();
clearTimeout(searchTimeout);
var q = this.value.trim();
if (q.length >= 2) runSearch(q);
}
if (e.key === 'Escape') {
searchDropdown.classList.remove('is-visible');
}
});
// Close dropdown on outside click
document.addEventListener('click', function (e) {
if (!searchDropdown.contains(e.target) && e.target !== searchInput) {
searchDropdown.classList.remove('is-visible');
}
});
function runSearch(q) {
apiFetch(API + '/search?q=' + encodeURIComponent(q) + '&limit=20').then(function (data) {
if (!data || !data.data || !data.data.length) {
searchDropdown.innerHTML = 'Sin resultados para "' + esc(q) + '"
';
searchDropdown.classList.add('is-visible');
return;
}
searchDropdown.innerHTML = data.data.map(function (r) {
var stockLabel = r.local_stock > 0
? 'Stock: ' + r.local_stock + ' '
: '';
return '' +
'
' +
'
' + esc(r.oem_part_number) + '
' +
'
' + esc(r.name) + '
' +
(r.vehicle_info ? '
' + esc(r.vehicle_info) + '
' : '') +
'
' +
stockLabel +
'
';
}).join('');
searchDropdown.classList.add('is-visible');
searchDropdown.querySelectorAll('.search-result-item').forEach(function (el) {
el.addEventListener('click', function () {
searchDropdown.classList.remove('is-visible');
openPartDetail(parseInt(this.dataset.partId));
});
});
});
}
// ─── CART ───
function addToCart(item, qty) {
qty = qty || 1;
var existing = cartItems.find(function (c) { return c.id === item.id; });
if (existing) {
existing.quantity += qty;
} else {
cartItems.push({
id: item.id,
part_number: item.part_number,
name: item.name,
brand: item.brand || '',
price: item.price,
tax_rate: item.tax_rate || 0.16,
unit: item.unit || 'PZA',
stock: item.stock,
source: item.source || 'local',
inventory_id: item.inventory_id,
quantity: qty,
});
}
saveCart();
renderCart();
if (!cartSidebar.classList.contains('open')) toggleCart();
}
function removeFromCart(index) {
cartItems.splice(index, 1);
saveCart();
renderCart();
}
function updateQuantity(index, qty) {
qty = parseInt(qty);
if (qty <= 0) { removeFromCart(index); return; }
cartItems[index].quantity = qty;
saveCart();
renderCart();
}
function clearCart() {
cartItems = [];
saveCart();
renderCart();
}
function saveCart() { localStorage.setItem('pos_cart', JSON.stringify(cartItems)); }
function renderCart() {
var total = cartItems.reduce(function (s, c) { return s + c.quantity; }, 0);
if (cartBadge) {
cartBadge.textContent = total;
cartBadge.style.display = total > 0 ? 'flex' : 'none';
}
if (!cartItems.length) {
cartItemsEl.innerHTML = '';
cartEmptyEl.style.display = 'block';
if (checkoutBtn) checkoutBtn.disabled = true;
cartSubtotalEl.textContent = '$0.00';
cartTaxEl.textContent = '$0.00';
cartTotalEl.textContent = '$0.00';
return;
}
cartEmptyEl.style.display = 'none';
if (checkoutBtn) checkoutBtn.disabled = false;
var subtotal = 0;
var tax = 0;
cartItemsEl.innerHTML = cartItems.map(function (c, i) {
var lineTotal = c.price * c.quantity;
var lineTax = lineTotal * c.tax_rate;
subtotal += lineTotal;
tax += lineTax;
return '' +
'
' +
'
' + esc(c.name) + '
' +
'
' + esc(c.part_number) + '
' +
'
' +
'- ' +
'' + c.quantity + ' ' +
'+ ' +
'
' +
'
' +
'
$' + fmt(lineTotal) + '
' +
'
Quitar ' +
'
';
}).join('');
cartSubtotalEl.textContent = '$' + fmt(subtotal);
cartTaxEl.textContent = '$' + fmt(tax);
cartTotalEl.textContent = '$' + fmt(subtotal + tax);
// Wire cart buttons
cartItemsEl.querySelectorAll('[data-cart-action]').forEach(function (btn) {
btn.addEventListener('click', function () {
var idx = parseInt(this.dataset.idx);
var action = this.dataset.cartAction;
if (action === 'dec') updateQuantity(idx, cartItems[idx].quantity - 1);
else if (action === 'inc') updateQuantity(idx, cartItems[idx].quantity + 1);
else if (action === 'remove') removeFromCart(idx);
});
});
}
function toggleCart() {
var isOpen = cartSidebar.classList.toggle('open');
cartOverlay.classList.toggle('open', isOpen);
}
function goToCheckout() {
if (!cartItems.length) return;
localStorage.setItem('pos_cart', JSON.stringify(cartItems));
window.location.href = '/pos/sale';
}
cartFab.addEventListener('click', toggleCart);
cartCloseBtn.addEventListener('click', toggleCart);
cartOverlay.addEventListener('click', toggleCart);
checkoutBtn.addEventListener('click', goToCheckout);
// ─── OFFLINE FALLBACK ───
function enterOfflineMode() {
isOffline = true;
document.getElementById('offlineBanner').style.display = '';
document.getElementById('offlineBannerText').innerHTML = 'Modo offline — Mostrando solo tu inventario local.';
levelTitle.textContent = 'Inventario local';
setupLevelFilter(false);
// TODO: load local inventory via legacy /pos/api/catalog/search endpoint
showEmpty('Sin conexion al catalogo', 'Verifica tu conexion. El catalogo TecDoc requiere acceso al servidor central.');
}
// ─── BARCODE SCANNER ───
var barcodeBuffer = '';
var barcodeTimeout = null;
document.addEventListener('keydown', function (e) {
// F1 → focus search
if (e.key === 'F1') { e.preventDefault(); searchInput.focus(); return; }
// Escape → close panels
if (e.key === 'Escape') {
closeDetail();
if (cartSidebar.classList.contains('open')) toggleCart();
return;
}
// Barcode scanner detection
if (document.activeElement.tagName === 'INPUT' || document.activeElement.tagName === 'TEXTAREA') return;
if (e.key === 'Enter' && barcodeBuffer.length >= 4) {
var code = barcodeBuffer.trim();
barcodeBuffer = '';
// Search for the barcode
searchInput.value = code;
runSearch(code);
return;
}
if (e.key.length === 1) {
barcodeBuffer += e.key;
clearTimeout(barcodeTimeout);
barcodeTimeout = setTimeout(function () { barcodeBuffer = ''; }, 200);
}
});
// ─── THEME SWITCHER ───
document.querySelectorAll('[data-theme-switch]').forEach(function (btn) {
btn.addEventListener('click', function () {
var theme = this.dataset.themeSwitch;
document.documentElement.setAttribute('data-theme', theme);
localStorage.setItem('pos_theme', theme);
document.querySelectorAll('[data-theme-switch]').forEach(function (b) {
b.classList.remove('is-active');
b.setAttribute('aria-pressed', 'false');
});
this.classList.add('is-active');
this.setAttribute('aria-pressed', 'true');
});
// Set initial active state
var current = localStorage.getItem('pos_theme') || 'industrial';
if (btn.dataset.themeSwitch === current) {
btn.classList.add('is-active');
btn.setAttribute('aria-pressed', 'true');
} else {
btn.classList.remove('is-active');
btn.setAttribute('aria-pressed', 'false');
}
});
// ─── VEHICLE SELECTOR (dropdown bar) ───
var vsYear = document.getElementById('vsYear');
var vsBrand = document.getElementById('vsBrand');
var vsModel = document.getElementById('vsModel');
var vsEngine = document.getElementById('vsEngine');
var vsClear = document.getElementById('vsClear');
// Load years on init
function vsLoadYears() {
apiFetch(API + '/years-all').then(function (data) {
if (!data) return;
var years = data.data || data;
// If endpoint doesn't exist, generate from 1990-2026
if (!years || !years.length) {
years = [];
for (var y = 2026; y >= 1990; y--) years.push({ id_year: y, year_car: y });
}
vsYear.innerHTML = 'Año... ';
years.forEach(function (y) {
vsYear.innerHTML += '' + y.year_car + ' ';
});
}).catch(function () {
// Fallback: generate years statically
vsYear.innerHTML = 'Año... ';
for (var y = 2026; y >= 1990; y--) {
vsYear.innerHTML += '' + y + ' ';
}
});
}
function vsYearChanged() {
var yearId = vsYear.value;
vsBrand.innerHTML = 'Marca... ';
vsModel.innerHTML = 'Modelo... ';
vsEngine.innerHTML = 'Motor... ';
vsBrand.disabled = true;
vsModel.disabled = true;
vsEngine.disabled = true;
vsClear.style.display = yearId ? '' : 'none';
if (!yearId) return;
// Load brands (reuse existing endpoint)
vsBrand.disabled = false;
apiFetch(API + '/brands').then(function (data) {
var brands = data.data || data;
if (!brands) return;
vsBrand.innerHTML = 'Marca... ';
brands.forEach(function (b) {
vsBrand.innerHTML += '' + esc(b.name_brand) + ' ';
});
});
}
function vsBrandChanged() {
var brandId = vsBrand.value;
vsModel.innerHTML = 'Modelo... ';
vsEngine.innerHTML = 'Motor... ';
vsModel.disabled = true;
vsEngine.disabled = true;
if (!brandId) return;
vsModel.disabled = false;
apiFetch(API + '/models?brand_id=' + brandId).then(function (data) {
var models = data.data || data;
if (!models) return;
vsModel.innerHTML = 'Modelo... ';
models.forEach(function (m) {
vsModel.innerHTML += '' + esc(m.name_model) + ' ';
});
});
}
function vsModelChanged() {
var modelId = vsModel.value;
var yearVal = vsYear.value;
vsEngine.innerHTML = 'Motor... ';
vsEngine.disabled = true;
if (!modelId || !yearVal) return;
vsEngine.disabled = false;
apiFetch(API + '/engines?model_id=' + modelId + '&year_id=' + yearVal).then(function (data) {
var engines = data.data || data;
if (!engines) return;
vsEngine.innerHTML = 'Motor... ';
engines.forEach(function (e) {
var label = e.name_engine + (e.trim_level ? ' (' + e.trim_level + ')' : '');
vsEngine.innerHTML += '' + esc(label) + ' ';
});
// If only 1 engine, auto-select
if (engines.length === 1) {
vsEngine.value = engines[0].id_mye;
vsEngineChanged();
}
});
}
function vsEngineChanged() {
var myeId = vsEngine.value;
if (!myeId) return;
// Update state and load categories
var yearText = vsYear.options[vsYear.selectedIndex].text;
var brandText = vsBrand.options[vsBrand.selectedIndex].text;
var modelText = vsModel.options[vsModel.selectedIndex].text;
var engineText = vsEngine.options[vsEngine.selectedIndex].text;
state.brand = { id: parseInt(vsBrand.value), name: brandText };
state.model = { id: parseInt(vsModel.value), name: modelText };
state.year = { id: parseInt(vsYear.value), value: yearText };
state.engine = { id_mye: parseInt(myeId), name: engineText };
state.level = 'categories';
loadCategories();
}
function vsClearAll() {
vsYear.value = '';
vsBrand.innerHTML = 'Marca... ';
vsModel.innerHTML = 'Modelo... ';
vsEngine.innerHTML = 'Motor... ';
vsBrand.disabled = true;
vsModel.disabled = true;
vsEngine.disabled = true;
vsClear.style.display = 'none';
state = { level: 'brands', brand: null, model: null, year: null, engine: null, category: null, group: null, page: 1, totalPages: 1 };
loadBrands();
}
// ─── EXPOSE GLOBALS (for backward compat) ───
window.CatalogApp = {
toggleCart: toggleCart,
goToCheckout: goToCheckout,
addToCart: addToCart,
removeFromCart: removeFromCart,
updateQty: updateQuantity,
clearCart: clearCart,
loadPage: function (p) { loadParts(p); },
vsYearChanged: vsYearChanged,
vsBrandChanged: vsBrandChanged,
vsModelChanged: vsModelChanged,
vsEngineChanged: vsEngineChanged,
vsClear: vsClearAll,
};
// ─── INIT ───
renderCart();
vsLoadYears();
loadBrands();
})();