Files
Autoparts-DB/pos/templates/supplier_catalog.html
consultoria-as ea29cc31c0 feat(catalog): supplier catalog cleanup, fuzzy matching, and navigation fixes
- Cleaned 137+ fake engine-displacement models from supplier imports
  (v3/v4 scripts: Chevrolet, Ford, Chrysler, Dodge, Jeep, Nissan, etc.)
- Removed 1,251+ corrupted models (INT. prefixes, year-suffix, torque specs,
  empty names, trailing-year variants)
- Migrated supplier tables to master DB (supplier_catalog,
  supplier_catalog_compat, supplier_catalog_interchange)
- Fixed _get_mye_ids_with_parts() to query supplier_catalog_compat from
  master DB so supplier-only vehicles appear for all tenants
- Added fuzzy model matcher with parenthesis stripping, noise suffix removal,
  compact matching, prefix/substring fallback, model aliases, and ±3 year
  proximity
- Matched compat rows: KEEP GREEN +14,152, KNADIAN +3,021, VAZLO +127,500,
  LUK +477, RAYBESTOS +1,743
- Added KNADIAN catalog importer with year-range expansion and future-year
  filtering
- Added VAZLO catalog importer with position parsing and SKU-in-model cleanup
- Added Keep Green, LUK, Yokomitsu, Raybestos catalog importers
- Cache clearing after cleanups (_classify_cache_*, nexus:mye_ids:*,
  nexus:brand_mye_counts:*)

Final match rates:
- KEEP GREEN: 90.3%
- VAZLO: 93.6%
- YOKOMITSU: 100.0%
- KNADIAN: 57.4%
- LUK: 51.0%
- RAYBESTOS: 55.9%
2026-06-09 07:47:42 +00:00

136 lines
9.2 KiB
HTML

<!DOCTYPE html>
<html lang="es">
<head>
<script>(function(){var t=localStorage.getItem("pos_theme")||"industrial";document.documentElement.setAttribute("data-theme",t);})()</script>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Catalogo de Proveedores — Nexus Autoparts POS</title>
<link rel="stylesheet" href="/pos/static/css/tokens.css" />
<link rel="stylesheet" href="/pos/static/css/common.css" />
<link rel="stylesheet" href="/pos/static/css/pos-ui.css?v=2" />
<link rel="stylesheet" href="/pos/static/css/sidebar.css" />
<link rel="stylesheet" href="/pos/static/css/pos-glass.css" />
<link rel="manifest" href="/pos/static/pwa/manifest.json" />
<meta name="theme-color" content="#F5A623" />
<link rel="shortcut icon" type="image/png" href="/pos/static/pwa/icon-192.png" />
<style>
.supplier-catalog { padding: var(--space-5); max-width: 1400px; margin: 0 auto; }
.sc-header { display:flex; align-items:center; justify-content:space-between; gap:var(--space-4); margin-bottom:var(--space-5); flex-wrap:wrap; }
.sc-search { display:flex; gap:var(--space-3); flex:1; min-width:280px; }
.sc-search input { flex:1; padding:var(--space-3) var(--space-4); border:1px solid var(--color-border); border-radius:var(--radius-md); background:var(--color-bg-elevated); color:var(--color-text-primary); font-size:var(--text-body); }
.sc-filters { display:flex; gap:var(--space-3); flex-wrap:wrap; margin-bottom:var(--space-5); }
.sc-filters select { padding:var(--space-2) var(--space-3); border:1px solid var(--color-border); border-radius:var(--radius-md); background:var(--color-bg-elevated); color:var(--color-text-primary); min-width:140px; }
.sc-categories { display:grid; grid-template-columns:repeat(auto-fill, minmax(160px, 1fr)); gap:var(--space-3); margin-bottom:var(--space-5); }
.sc-cat-card { background:var(--color-bg-elevated); border:1px solid var(--color-border); border-radius:var(--radius-lg); padding:var(--space-4); cursor:pointer; transition:all .15s; text-align:center; }
.sc-cat-card:hover, .sc-cat-card.active { border-color:var(--color-primary); box-shadow:var(--shadow-sm); }
.sc-cat-card .count { font-size:var(--text-caption); color:var(--color-text-muted); margin-top:2px; }
.sc-grid { display:grid; grid-template-columns:repeat(auto-fill, minmax(280px, 1fr)); gap:var(--space-4); }
.sc-card { background:var(--color-bg-elevated); border:1px solid var(--color-border); border-radius:var(--radius-lg); padding:var(--space-4); cursor:pointer; transition:all .15s; display:flex; flex-direction:column; gap:var(--space-2); }
.sc-card:hover { border-color:var(--color-primary); transform:translateY(-2px); box-shadow:var(--shadow-sm); }
.sc-card__sku { font-family:var(--font-mono); font-size:var(--text-caption); color:var(--color-primary); font-weight:var(--font-weight-bold); }
.sc-card__name { font-weight:var(--font-weight-semibold); color:var(--color-text-primary); line-height:1.3; }
.sc-card__meta { font-size:var(--text-caption); color:var(--color-text-muted); margin-top:auto; }
.sc-card__badge { display:inline-block; padding:2px 8px; border-radius:var(--radius-full); background:var(--color-primary-muted); color:var(--color-primary); font-size:10px; font-weight:var(--font-weight-bold); text-transform:uppercase; }
.sc-empty { text-align:center; padding:var(--space-8); color:var(--color-text-muted); }
.sc-pagination { display:flex; justify-content:center; align-items:center; gap:var(--space-3); margin-top:var(--space-6); }
.sc-pagination button { padding:var(--space-2) var(--space-4); border:1px solid var(--color-border); border-radius:var(--radius-md); background:var(--color-bg-elevated); color:var(--color-text-primary); cursor:pointer; }
.sc-pagination button:disabled { opacity:.4; cursor:not-allowed; }
.sc-pagination span { font-size:var(--text-caption); color:var(--color-text-muted); }
/* Modal */
.sc-modal-overlay { position:fixed; inset:0; background:var(--overlay-backdrop); z-index:var(--z-modal); display:none; align-items:center; justify-content:center; padding:var(--space-4); }
.sc-modal-overlay.open { display:flex; }
.sc-modal { background:var(--color-bg-elevated); border:1px solid var(--color-border); border-radius:var(--radius-xl); width:100%; max-width:720px; max-height:90vh; overflow-y:auto; display:flex; flex-direction:column; }
.sc-modal__header { display:flex; align-items:center; justify-content:space-between; padding:var(--space-4) var(--space-5); border-bottom:1px solid var(--color-border); }
.sc-modal__body { padding:var(--space-5); display:flex; flex-direction:column; gap:var(--space-4); }
.sc-modal__section h4 { font-size:var(--text-body-sm); font-weight:var(--font-weight-bold); color:var(--color-text-secondary); margin-bottom:var(--space-2); text-transform:uppercase; letter-spacing:var(--tracking-wider); }
.sc-compat-grid { display:grid; grid-template-columns:repeat(auto-fill, minmax(180px, 1fr)); gap:var(--space-2); }
.sc-compat-item { background:var(--color-surface-1); border:1px solid var(--color-border); border-radius:var(--radius-md); padding:var(--space-2) var(--space-3); font-size:var(--text-caption); }
.sc-interchange-list { display:flex; flex-wrap:wrap; gap:var(--space-2); }
.sc-interchange-chip { background:var(--color-surface-2); border:1px solid var(--color-border); border-radius:var(--radius-full); padding:2px 10px; font-size:var(--text-caption); }
.sc-close { background:none; border:none; font-size:20px; color:var(--color-text-muted); cursor:pointer; }
</style>
</head>
<body>
<!-- Theme bar -->
<div class="theme-bar">
<span class="theme-bar__label">Tema:</span>
<button class="theme-btn" id="btn-industrial" onclick="setTheme('industrial')">Industrial</button>
<button class="theme-btn" id="btn-modern" onclick="setTheme('modern')">Moderno</button>
</div>
<div class="sidebar-overlay" id="sidebar-overlay" onclick="closeSidebar()"></div>
<div class="app-shell">
<nav class="sidebar themed-scrollbar" id="sidebar">
<div class="sidebar__logo">
<div class="sidebar__logo-text">Nexus</div>
<div class="sidebar__logo-sub">Autoparts POS</div>
</div>
<div class="sidebar__nav">
<div class="sidebar__section-label">Principal</div>
<a href="/pos/dashboard" class="nav-link"><span class="nav-link__icon">📊</span> Dashboard</a>
<a href="/pos/sale" class="nav-link"><span class="nav-link__icon">🛒</span> POS</a>
<a href="/pos/catalog" class="nav-link"><span class="nav-link__icon">📁</span> Catalogo</a>
<a href="/pos/supplier-catalog" class="nav-link active"><span class="nav-link__icon">🏭</span> Cat. Proveedores</a>
<a href="/pos/inventory" class="nav-link"><span class="nav-link__icon">📦</span> Inventario</a>
<a href="/pos/config" class="nav-link"><span class="nav-link__icon">⚙️</span> Configuracion</a>
</div>
</nav>
<div class="main">
<header class="header">
<div class="header__left">
<button class="hamburger-btn" onclick="toggleSidebar()"></button>
<div class="header__greeting">
<div class="header__title">Catalogo de Proveedores</div>
<div class="header__subtitle">Busca por vehiculo, SKU o nombre de parte</div>
</div>
</div>
</header>
<div class="supplier-catalog themed-scrollbar">
<div class="sc-header">
<div class="sc-search">
<input type="text" id="searchInput" placeholder="Buscar SKU, nombre o intercambio..." onkeydown="if(event.key==='Enter') doSearch()" />
<button class="btn btn--primary" onclick="doSearch()">Buscar</button>
<button class="btn btn--secondary" onclick="clearFilters()">Limpiar</button>
</div>
</div>
<div class="sc-filters">
<select id="filterMake" onchange="onMakeChange()"><option value="">Marca vehiculo</option></select>
<select id="filterModel" onchange="onModelChange()" disabled><option value="">Modelo</option></select>
<select id="filterYear" onchange="onYearChange()" disabled><option value="">Año</option></select>
<select id="filterEngine" onchange="doSearch()" disabled><option value="">Motorizacion</option></select>
</div>
<div class="sc-categories" id="categoriesGrid"></div>
<div id="resultsArea">
<div class="sc-grid" id="partsGrid"></div>
<div class="sc-pagination" id="pagination"></div>
</div>
</div>
</div>
</div>
<!-- Detail Modal -->
<div class="sc-modal-overlay" id="detailModal" onclick="closeModal(event)">
<div class="sc-modal" onclick="event.stopPropagation()">
<div class="sc-modal__header">
<h3 id="modalTitle">Detalle</h3>
<button class="sc-close" onclick="closeModal()">&times;</button>
</div>
<div class="sc-modal__body" id="modalBody"></div>
</div>
</div>
<script src="/pos/static/js/app-init.js" defer></script>
<script src="/pos/static/js/sidebar.js" defer></script>
<script src="/pos/static/js/supplier_catalog.js?v=2" defer></script>
<script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script>
</body>
</html>