Files
Autoparts-DB/dashboard/demo.html
consultoria-as f893391916 FASE 7e: CSS Inline Extraction + Minificación
- Extraído CSS inline de 15 templates POS + 13 templates Dashboard
- CSS movido a archivos .css externos en pos/static/css/ y dashboard/
- Generados .min.css vía minify-assets.sh
- Nginx auto-serve transparente para .min.css
- Tests: 73/73 pasando
- Script: scripts/extract-inline-css.py
2026-04-27 08:50:19 +00:00

514 lines
26 KiB
HTML

<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>NEXUS AUTOPARTS — Cat&aacute;logo OEM</title>
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700;800&family=Outfit:wght@700;800;900&family=JetBrains+Mono:wght@500;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="demo.css">
</head>
<body>
<header class="header">
<div class="logo">
<div class="logo-mark">&#9881;&#65039;</div>
<div>
<div class="logo-text">NEXUS AUTOPARTS</div>
<div class="logo-sub">Cat&aacute;logo de partes OEM</div>
</div>
</div>
<div class="header-search">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><path d="M21 21l-4.35-4.35"/></svg>
<input id="search-input" type="text" placeholder="Buscar por n&uacute;mero OEM o nombre..." autocomplete="off">
<div id="search-drop" class="search-drop"></div>
</div>
<span class="badge-demo">DEMO</span>
</header>
<main class="main">
<div id="breadcrumb" class="breadcrumb"></div>
<h1 id="title" class="section-title"></h1>
<p id="subtitle" class="section-subtitle"></p>
<div id="content"></div>
</main>
<!-- Part Detail Modal -->
<div id="modal-bg" class="modal-bg">
<div class="modal">
<div class="modal-head">
<div class="modal-head-info">
<div class="modal-oem" id="modal-oem"></div>
<div class="modal-part-name" id="modal-name"></div>
</div>
<button class="modal-close" id="modal-close">&times;</button>
</div>
<div id="modal-image" style="text-align:center;margin-bottom:1rem;display:none;">
<img id="modal-image-img" style="max-width:100%;max-height:300px;border-radius:8px;object-fit:contain;" />
</div>
<div class="modal-fields" id="modal-fields"></div>
<div class="modal-section" id="section-alts">
<div class="modal-section-title">Intercambios / Alternativas</div>
<div id="modal-alts"><div class="modal-loading">Cargando...</div></div>
</div>
<div class="modal-section" id="section-xrefs">
<div class="modal-section-title">Referencias cruzadas</div>
<div id="modal-xrefs"><div class="modal-loading">Cargando...</div></div>
</div>
</div>
</div>
<script>
(function () {
'use strict';
var API = '';
var state = { brand: null, model: null, mye_id: null, mye_label: null, category: null, category_name: null };
// Region filter: bitmask 1=MX, 2=US, 4=CA, 8=RW
var REGIONS = [
{ bit: 1, code: 'MX', label: 'M\u00e9xico', flag: '\uD83C\uDDF2\uD83C\uDDFD' },
{ bit: 2, code: 'US', label: 'USA', flag: '\uD83C\uDDFA\uD83C\uDDF8' },
{ bit: 4, code: 'CA', label: 'Canad\u00e1', flag: '\uD83C\uDDE8\uD83C\uDDE6' },
{ bit: 8, code: 'RW', label: 'Resto', flag: '\uD83C\uDF0E' }
];
// Load from localStorage or default MX+US (bitmask = 3)
var regionMask = parseInt(localStorage.getItem('nexus_region') || '3');
function esc(s) {
if (!s) return '';
var d = document.createElement('div');
d.textContent = s;
return d.innerHTML;
}
// ============================================================
// Navigation
// ============================================================
function go(level, data) {
if (level === 'brands') {
state = { brand: null, model: null, mye_id: null, mye_label: null, category: null, category_name: null };
loadBrands();
} else if (level === 'models') {
state.brand = data;
state.model = null; state.mye_id = null; state.mye_label = null; state.category = null;
loadModels(data);
} else if (level === 'vehicles') {
state.model = data;
state.mye_id = null; state.mye_label = null; state.category = null;
loadVehicles(state.brand, data);
} else if (level === 'categories') {
state.mye_id = data.id;
state.mye_label = data.year + ' ' + data.engine;
state.category = null;
loadCategories(data.id);
} else if (level === 'parts') {
state.category = data.id;
state.category_name = data.name;
loadParts(state.mye_id, data.id, data.name);
}
updateBreadcrumb();
}
function updateBreadcrumb() {
var bc = document.getElementById('breadcrumb');
var parts = ['<a onclick="go(\'brands\')">Marcas</a>'];
if (state.brand) {
parts.push('<span class="sep">&rsaquo;</span>');
parts.push('<a onclick="go(\'models\',\'' + esc(state.brand) + '\')">' + esc(state.brand) + '</a>');
}
if (state.model) {
parts.push('<span class="sep">&rsaquo;</span>');
parts.push('<a onclick="go(\'vehicles\',\'' + esc(state.model) + '\')">' + esc(state.model) + '</a>');
}
if (state.mye_id) {
parts.push('<span class="sep">&rsaquo;</span>');
parts.push('<a onclick="go(\'categories\',{id:' + state.mye_id + ',year:\'\',engine:\'\'})">' + esc(state.mye_label) + '</a>');
}
if (state.category_name) {
parts.push('<span class="sep">&rsaquo;</span>');
parts.push('<span>' + esc(state.category_name) + '</span>');
}
bc.innerHTML = parts.join('');
}
// expose go to inline onclick
window.go = go;
// ============================================================
// Brands
// ============================================================
function buildRegionBar() {
var html = '<div class="region-bar">'
+ '<span class="region-label">Mercado:</span>';
REGIONS.forEach(function (r) {
var active = (regionMask & r.bit) ? ' active' : '';
html += '<div class="region-chip' + active + '" data-bit="' + r.bit + '" onclick="toggleRegion(' + r.bit + ')">'
+ '<span class="rc-flag">' + r.flag + '</span>'
+ r.label
+ '</div>';
});
html += '</div>';
return html;
}
window.toggleRegion = function (bit) {
regionMask ^= bit; // Toggle the bit
if (regionMask === 0) regionMask = bit; // Prevent all-off
localStorage.setItem('nexus_region', regionMask);
// Update chip styles
document.querySelectorAll('.region-chip').forEach(function (chip) {
var b = parseInt(chip.getAttribute('data-bit'));
if (regionMask & b) {
chip.classList.add('active');
} else {
chip.classList.remove('active');
}
});
// Reload brands
fetchBrands();
};
function loadBrands() {
var el = document.getElementById('content');
document.getElementById('title').textContent = 'Selecciona una marca';
document.getElementById('subtitle').innerHTML = buildRegionBar();
el.innerHTML = '<div class="loading">Cargando marcas...</div>';
fetchBrands();
}
function fetchBrands() {
var el = document.getElementById('content');
el.innerHTML = '<div class="loading">Cargando marcas...</div>';
fetch(API + '/api/brands?detailed=true&region=' + regionMask)
.then(function (r) { return r.json(); })
.then(function (brands) {
var sub = document.getElementById('subtitle');
// Keep region bar, update count text after it
var countEl = sub.querySelector('.region-count');
if (!countEl) {
var span = document.createElement('div');
span.className = 'region-count';
span.style.cssText = 'font-size:0.85rem; color:var(--text2); margin-top:0.3rem;';
sub.appendChild(span);
countEl = span;
}
countEl.textContent = brands.length + ' marcas disponibles';
el.className = 'grid grid-brands';
el.innerHTML = brands.map(function (b) {
return '<div class="card card-brand" onclick="go(\'models\',\'' + esc(b.name) + '\')">'
+ '<span class="cb-name">' + esc(b.name) + '</span>'
+ '<span class="cb-count">' + b.model_count + ' modelos</span>'
+ '</div>';
}).join('');
});
}
// ============================================================
// Models
// ============================================================
function loadModels(brand) {
var el = document.getElementById('content');
document.getElementById('title').textContent = brand;
el.innerHTML = '<div class="loading">Cargando modelos...</div>';
fetch(API + '/api/models?brand=' + encodeURIComponent(brand) + '&detailed=true')
.then(function (r) { return r.json(); })
.then(function (models) {
document.getElementById('subtitle').textContent = models.length + ' modelos';
el.className = 'grid grid-models';
el.innerHTML = models.map(function (m) {
var years = m.year_min === m.year_max ? m.year_min : m.year_min + ' - ' + m.year_max;
return '<div class="card card-model" onclick="go(\'vehicles\',\'' + esc(m.name) + '\')">'
+ '<span class="cm-name">' + esc(m.name) + '</span>'
+ '<span class="cm-years">' + years + '</span>'
+ '<span class="cm-count">' + m.vehicle_count + ' variantes</span>'
+ '</div>';
}).join('');
});
}
// ============================================================
// Vehicles (year + engine combos)
// ============================================================
function loadVehicles(brand, model) {
var el = document.getElementById('content');
document.getElementById('title').textContent = brand + ' ' + model;
el.innerHTML = '<div class="loading">Cargando variantes...</div>';
fetch(API + '/api/model-year-engine?brand=' + encodeURIComponent(brand) + '&model=' + encodeURIComponent(model) + '&per_page=100')
.then(function (r) { return r.json(); })
.then(function (res) {
var data = res.data || [];
document.getElementById('subtitle').textContent = data.length + ' variantes';
el.className = 'grid grid-vehicles';
if (data.length === 0) {
el.innerHTML = '<div class="empty">No se encontraron variantes para este modelo</div>';
return;
}
el.innerHTML = data.map(function (v) {
return '<div class="card card-vehicle" onclick="go(\'categories\',' + JSON.stringify(v).replace(/"/g, '&quot;') + ')">'
+ '<div class="cv-main">'
+ '<span class="cv-year">' + v.year + '</span>'
+ '<div>'
+ '<div class="cv-engine">' + esc(v.engine) + '</div>'
+ '<div class="cv-details">' + esc(v.drivetrain || '') + (v.transmission ? ' &middot; ' + esc(v.transmission) : '') + '</div>'
+ '</div>'
+ '</div>'
+ (v.trim_level ? '<span class="cv-trim">' + esc(v.trim_level) + '</span>' : '')
+ '</div>';
}).join('');
});
}
// ============================================================
// Categories
// ============================================================
function loadCategories(myeId) {
var el = document.getElementById('content');
document.getElementById('title').textContent = state.brand + ' ' + state.model + ' ' + state.mye_label;
el.innerHTML = '<div class="loading">Cargando categor&iacute;as...</div>';
fetch(API + '/api/vehicles/' + myeId + '/categories')
.then(function (r) { return r.json(); })
.then(function (cats) {
if (cats.length === 0) {
document.getElementById('subtitle').textContent = '';
el.innerHTML = '<div class="empty">No hay partes registradas para este veh&iacute;culo</div>';
return;
}
document.getElementById('subtitle').textContent = cats.length + ' categor&iacute;as con partes';
el.className = 'grid grid-categories';
el.innerHTML = cats.map(function (c) {
return '<div class="card card-category" onclick="go(\'parts\',' + JSON.stringify({id: c.id, name: c.name}).replace(/"/g, '&quot;') + ')">'
+ '<span class="cc-name">' + esc(c.name) + '</span>'
+ '</div>';
}).join('');
});
}
// ============================================================
// Parts
// ============================================================
function loadParts(myeId, categoryId, categoryName) {
var el = document.getElementById('content');
document.getElementById('title').textContent = categoryName;
el.innerHTML = '<div class="loading">Cargando partes...</div>';
fetch(API + '/api/vehicles/' + myeId + '/parts?category_id=' + categoryId + '&per_page=100')
.then(function (r) { return r.json(); })
.then(function (res) {
var parts = res.data || res;
if (parts.length === 0) {
document.getElementById('subtitle').textContent = '';
el.innerHTML = '<div class="empty">No hay partes en esta categor&iacute;a</div>';
return;
}
document.getElementById('subtitle').textContent = parts.length + ' partes OEM';
el.className = 'grid grid-parts';
// Group by group_name
var groups = {};
var groupOrder = [];
parts.forEach(function (p) {
var g = p.group_name || 'Otros';
if (!groups[g]) { groups[g] = []; groupOrder.push(g); }
groups[g].push(p);
});
var html = '';
groupOrder.forEach(function (g) {
html += '<div class="group-header"><span class="gh-name">' + esc(g) + '</span>'
+ '<span class="gh-count">' + groups[g].length + '</span></div>';
groups[g].forEach(function (p) {
html += '<div class="part-row" data-part-id="' + p.id + '">'
+ '<span class="part-oem">' + esc(p.oem_part_number) + '</span>'
+ '<span class="part-name">' + esc(p.name || p.name_es || '') + '</span>'
+ (p.position ? '<span class="part-pos">' + esc(p.position) + '</span>' : '')
+ '<span class="part-qty">x' + (p.quantity_required || 1) + '</span>'
+ '</div>';
});
});
el.innerHTML = html;
// Attach click handlers to part rows
el.querySelectorAll('.part-row[data-part-id]').forEach(function (row) {
row.addEventListener('click', function () {
openPartModal(parseInt(row.getAttribute('data-part-id')));
});
});
});
}
// ============================================================
// Part Detail Modal
// ============================================================
var modalBg = document.getElementById('modal-bg');
document.getElementById('modal-close').addEventListener('click', closeModal);
modalBg.addEventListener('click', function (e) {
if (e.target === modalBg) closeModal();
});
document.addEventListener('keydown', function (e) {
if (e.key === 'Escape' && modalBg.classList.contains('open')) closeModal();
});
function closeModal() {
modalBg.classList.remove('open');
}
function openPartModal(partId) {
// Show modal immediately with loading state
document.getElementById('modal-oem').textContent = '...';
document.getElementById('modal-name').textContent = 'Cargando...';
document.getElementById('modal-fields').innerHTML = '';
document.getElementById('modal-image').style.display = 'none';
document.getElementById('modal-alts').innerHTML = '<div class="modal-loading">Cargando intercambios...</div>';
document.getElementById('modal-xrefs').innerHTML = '<div class="modal-loading">Cargando referencias...</div>';
modalBg.classList.add('open');
// Fetch part detail
fetch(API + '/api/parts/' + partId)
.then(function (r) { return r.json(); })
.then(function (p) {
document.getElementById('modal-oem').textContent = p.oem_part_number || '';
document.getElementById('modal-name').textContent = p.name || p.name_es || '';
if (p.image_url) {
document.getElementById('modal-image-img').src = p.image_url;
document.getElementById('modal-image').style.display = 'block';
} else {
document.getElementById('modal-image').style.display = 'none';
}
var fields = '';
if (p.name_es) fields += '<div class="mf"><span class="mf-label">Nombre (ES)</span><span class="mf-value">' + esc(p.name_es) + '</span></div>';
if (p.category_name) fields += '<div class="mf"><span class="mf-label">Categor\u00eda</span><span class="mf-value">' + esc(p.category_name) + '</span></div>';
if (p.group_name) fields += '<div class="mf"><span class="mf-label">Grupo</span><span class="mf-value">' + esc(p.group_name) + '</span></div>';
if (p.description) fields += '<div class="mf" style="grid-column:1/-1"><span class="mf-label">Descripci\u00f3n</span><span class="mf-value">' + esc(p.description) + '</span></div>';
if (p.description_es) fields += '<div class="mf" style="grid-column:1/-1"><span class="mf-label">Descripci\u00f3n (ES)</span><span class="mf-value">' + esc(p.description_es) + '</span></div>';
document.getElementById('modal-fields').innerHTML = fields || '<div class="mf"><span class="mf-label">Categor\u00eda</span><span class="mf-value">' + esc(p.category_name || '') + '</span></div><div class="mf"><span class="mf-label">Grupo</span><span class="mf-value">' + esc(p.group_name || '') + '</span></div>';
});
// Fetch alternatives
fetch(API + '/api/parts/' + partId + '/alternatives')
.then(function (r) { return r.json(); })
.then(function (alts) {
var el = document.getElementById('modal-alts');
if (alts.length === 0) {
el.innerHTML = '<div class="modal-empty">No hay intercambios registrados para esta parte</div>';
return;
}
var html = '<table class="alt-table"><thead><tr>'
+ '<th>N\u00famero</th><th>Fabricante</th><th>Calidad</th><th>Precio</th><th>Garant\u00eda</th>'
+ '</tr></thead><tbody>';
alts.forEach(function (a) {
var qClass = (a.quality_tier || '').toLowerCase().indexOf('premium') >= 0 ? 'premium'
: (a.quality_tier || '').toLowerCase().indexOf('economy') >= 0 ? 'economy' : 'oem';
html += '<tr>'
+ '<td class="alt-pn">' + esc(a.part_number) + '</td>'
+ '<td class="alt-mfr">' + esc(a.manufacturer_name) + '</td>'
+ '<td>' + (a.quality_tier ? '<span class="alt-quality ' + qClass + '">' + esc(a.quality_tier) + '</span>' : '-') + '</td>'
+ '<td>' + (a.price_usd ? '$' + parseFloat(a.price_usd).toFixed(2) : '-') + '</td>'
+ '<td>' + (a.warranty_months ? a.warranty_months + ' meses' : '-') + '</td>'
+ '</tr>';
});
html += '</tbody></table>';
el.innerHTML = html;
});
// Fetch cross-references
fetch(API + '/api/parts/' + partId + '/cross-references')
.then(function (r) { return r.json(); })
.then(function (xrefs) {
var el = document.getElementById('modal-xrefs');
if (xrefs.length === 0) {
el.innerHTML = '<div class="modal-empty">No hay referencias cruzadas registradas</div>';
return;
}
el.innerHTML = xrefs.map(function (x) {
return '<div class="xref-row">'
+ '<span class="xref-number">' + esc(x.cross_reference_number) + '</span>'
+ (x.reference_type ? '<span class="xref-type">' + esc(x.reference_type) + '</span>' : '')
+ (x.source ? '<span class="xref-source">' + esc(x.source) + '</span>' : '')
+ '</div>';
}).join('');
});
}
// Expose for search results
window.openPartModal = openPartModal;
// ============================================================
// Global Search
// ============================================================
var searchTimer = null;
var searchInput = document.getElementById('search-input');
var searchDrop = document.getElementById('search-drop');
searchInput.addEventListener('input', function () {
clearTimeout(searchTimer);
var q = this.value.trim();
if (q.length < 2) { searchDrop.classList.remove('open'); return; }
searchTimer = setTimeout(function () {
fetch(API + '/api/search?q=' + encodeURIComponent(q) + '&limit=12')
.then(function (r) { return r.json(); })
.then(function (results) {
var items = results.parts || [];
if (!items.length) {
searchDrop.innerHTML = '<div style="padding:0.8rem;color:var(--text2);font-size:0.85rem">Sin resultados para "' + esc(q) + '"</div>';
} else {
searchDrop.innerHTML = items.slice(0, 10).map(function (p) {
return '<div class="search-item" data-part-id="' + p.id + '">'
+ '<div>'
+ '<span class="si-oem">' + esc(p.oem_part_number) + '</span>'
+ '<span class="si-name">' + esc(p.name || '') + '</span>'
+ '</div>'
+ '<span class="si-cat">' + esc(p.category_name || '') + '</span>'
+ '</div>';
}).join('');
searchDrop.querySelectorAll('.search-item[data-part-id]').forEach(function (item) {
item.addEventListener('mousedown', function (e) {
e.preventDefault();
var pid = parseInt(item.getAttribute('data-part-id'));
searchDrop.classList.remove('open');
searchInput.value = '';
openPartModal(pid);
});
});
}
searchDrop.classList.add('open');
});
}, 200);
});
searchInput.addEventListener('blur', function () {
setTimeout(function () { searchDrop.classList.remove('open'); }, 200);
});
searchInput.addEventListener('focus', function () {
if (searchDrop.innerHTML.trim()) searchDrop.classList.add('open');
});
// ============================================================
// Init
// ============================================================
go('brands');
})();
</script>
</body>
</html>