Files
Autoparts-DB/pos/templates/marketplace_external.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

355 lines
18 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>MercadoLibre — Nexus Autoparts POS</title>
<link rel="stylesheet" href="/pos/static/css/chat.css" />
<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="stylesheet" href="/pos/static/css/inventory.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>
.meli-status { display:inline-flex;align-items:center;gap:6px;padding:4px 10px;border-radius:20px;font-size:12px;font-weight:600; }
.meli-status--active { background:#d4edda;color:#155724; }
.meli-status--paused { background:#fff3cd;color:#856404; }
.meli-status--closed { background:#f8d7da;color:#721c24; }
.meli-status--pending { background:#e2e3e5;color:#383d41; }
.meli-card { background:var(--color-surface-1);border:1px solid var(--color-border);border-radius:var(--radius-md);padding:var(--space-4); }
.meli-grid { display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:var(--space-4); }
.meli-connect-btn { display:inline-flex;align-items:center;gap:8px;padding:10px 20px;background:#FFE600;color:#2D3277;border:none;border-radius:var(--radius-md);font-weight:700;cursor:pointer; }
.meli-connect-btn:hover { filter:brightness(0.95); }
.meli-config-row { display:flex;gap:var(--space-4);flex-wrap:wrap;margin-bottom:var(--space-4); }
.meli-config-row label { display:block;font-size:var(--text-caption);color:var(--color-text-muted);margin-bottom:4px; }
.meli-config-row input, .meli-config-row select { padding:8px 12px;border:1px solid var(--color-border);border-radius:var(--radius-sm);background:var(--color-surface-0);color:var(--color-text-primary); }
</style>
</head>
<body>
<!-- =========================================================================
THEME SWITCHER BAR
========================================================================= -->
<header class="theme-bar" role="banner">
<div class="theme-bar__left">
<div class="theme-bar__store">
<span class="theme-bar__dot"></span>
Nexus Autoparts
</div>
<div class="theme-bar__sep"></div>
<span class="theme-bar__label">MercadoLibre — Integración</span>
</div>
<div class="theme-bar__right">
<span class="theme-bar__label">Tema:</span>
<button class="theme-btn theme-btn--industrial is-active" data-theme-target="industrial" onclick="setTheme('industrial')">
<span class="theme-btn__swatch"></span>
Industrial
</button>
<button class="theme-btn theme-btn--modern" data-theme-target="modern" onclick="setTheme('modern')">
<span class="theme-btn__swatch"></span>
Moderno
</button>
</div>
</header>
<!-- =========================================================================
APP SHELL
========================================================================= -->
<div class="app-shell">
<!-- -----------------------------------------------------------------------
SIDEBAR NAVIGATION
----------------------------------------------------------------------- -->
<aside class="sidebar" role="navigation" aria-label="Navegación principal">
<div class="sidebar__brand">
<div class="brand-logo">NA</div>
<div class="brand-name">
<span class="brand-name__primary">Nexus</span>
<span class="brand-name__sub">Autoparts POS</span>
</div>
</div>
<nav class="sidebar__nav">
<div class="nav-section-label">Principal</div>
<a class="nav-item" href="/pos/dashboard">
<svg class="nav-item__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
<rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/>
<rect x="14" y="14" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/>
</svg>
<span>Dashboard</span>
</a>
<a class="nav-item" href="/pos/sale">
<svg class="nav-item__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
<rect x="2" y="3" width="20" height="14" rx="2"/><path d="M8 21h8M12 17v4"/>
</svg>
<span>POS</span>
</a>
<a class="nav-item" href="/pos/catalog">
<svg class="nav-item__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
<path d="M4 6h16M4 10h16M4 14h16M4 18h16"/>
</svg>
<span>Catálogo</span>
</a>
<a class="nav-item" href="/pos/inventory">
<svg class="nav-item__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/>
<polyline points="3.27 6.96 12 12.01 20.73 6.96"/><line x1="12" y1="22.08" x2="12" y2="12"/>
</svg>
<span>Inventario</span>
</a>
<div class="nav-section-label">Gestión</div>
<a class="nav-item" href="/pos/customers">
<svg class="nav-item__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/>
<circle cx="9" cy="7" r="4"/>
<path d="M23 21v-2a4 4 0 0 0-3-3.87M16 3.13a4 4 0 0 1 0 7.75"/>
</svg>
<span>Clientes</span>
</a>
<a class="nav-item" href="/pos/marketplace">
<svg class="nav-item__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
<circle cx="9" cy="21" r="1"/><circle cx="20" cy="21" r="1"/>
<path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6"/>
</svg>
<span>Marketplace B2B</span>
</a>
<a class="nav-item is-active" href="/pos/marketplace-external" aria-current="page">
<svg class="nav-item__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
<rect x="2" y="3" width="20" height="14" rx="2"/><path d="M8 21h8M12 17v4"/>
</svg>
<span>MercadoLibre</span>
</a>
<a class="nav-item" href="/pos/invoicing">
<svg class="nav-item__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
<polyline points="14 2 14 8 20 8"/>
<line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/>
<polyline points="10 9 9 9 8 9"/>
</svg>
<span>Facturación</span>
</a>
<a class="nav-item" href="/pos/config">
<svg class="nav-item__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="3"/>
<path d="M19.07 4.93a10 10 0 0 1 0 14.14M4.93 4.93a10 10 0 0 0 0 14.14"/>
</svg>
<span>Configuración</span>
</a>
</nav>
<div class="sidebar__footer">
<div class="sidebar__user-avatar" id="sidebarAvatar">U</div>
<div class="sidebar__user-info">
<div class="sidebar__user-name" id="sidebarName">Usuario</div>
<div class="sidebar__user-role" id="sidebarRole"></div>
</div>
</div>
</aside>
<!-- -----------------------------------------------------------------------
MAIN CONTENT
----------------------------------------------------------------------- -->
<main class="main" role="main">
<!-- Page Header -->
<div class="page-header">
<div class="page-header__title-group">
<span class="page-header__eyebrow">Marketplace</span>
<h1 class="page-header__title">MercadoLibre</h1>
</div>
<div class="page-header__actions">
<a class="btn btn--ghost" href="/pos/dashboard">
<svg viewBox="0 0 24 24"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg>
Dashboard
</a>
</div>
</div>
<!-- Tab Bar -->
<div class="tab-bar">
<button class="tab-btn is-active" role="tab" aria-selected="true" aria-controls="panel-config" onclick="switchTab('config')">
Configuración
</button>
<button class="tab-btn" role="tab" aria-selected="false" aria-controls="panel-listings" onclick="switchTab('listings')">
Publicaciones
</button>
<button class="tab-btn" role="tab" aria-selected="false" aria-controls="panel-orders" onclick="switchTab('orders')">
Órdenes
</button>
<button class="tab-btn" role="tab" aria-selected="false" aria-controls="panel-questions" onclick="switchTab('questions')">
Preguntas
</button>
</div>
<!-- Tab Panels -->
<div class="tab-panels" id="tab-panels">
<!-- ══════════ TAB: Configuración ══════════ -->
<div class="tab-panel is-active" id="panel-config" role="tabpanel">
<div class="meli-card" style="max-width:600px;">
<h3 style="margin:0 0 var(--space-4);font-family:var(--font-heading);">Conexión con MercadoLibre</h3>
<div id="configStatus">
<div class="skeleton skeleton--text" style="width:120px;"></div>
</div>
<div id="configForm" style="display:none;margin-top:var(--space-4);">
<p style="margin-bottom:var(--space-3);font-size:var(--text-body-sm);color:var(--color-text-secondary);">
Para conectar, necesitas una <strong>aplicación de MercadoLibre</strong>.
Ve a <a href="https://developers.mercadolibre.com.mx" target="_blank">developers.mercadolibre.com.mx</a>
y crea una app. Luego pega los datos aquí:
</p>
<div class="meli-config-row">
<div>
<label>Client ID</label>
<input type="text" id="cfgClientId" placeholder="Tu App ID" style="width:200px;" />
</div>
<div>
<label>Client Secret</label>
<input type="password" id="cfgClientSecret" placeholder="Tu Secret Key" style="width:200px;" />
</div>
</div>
<div class="meli-config-row">
<div>
<label>Categoría Default</label>
<input type="text" id="cfgCategory" placeholder="MLM1747" style="width:150px;" />
</div>
<div>
<label>Modo de Envío</label>
<select id="cfgShipping">
<option value="me2">MercadoEnvíos (me2)</option>
<option value="custom">Propio (custom)</option>
</select>
</div>
</div>
<button class="meli-connect-btn" onclick="startOAuth()">🔗 Conectar con MercadoLibre</button>
</div>
<div id="configConnected" style="display:none;margin-top:var(--space-4);">
<div style="display:flex;align-items:center;gap:12px;margin-bottom:var(--space-3);">
<div style="width:48px;height:48px;border-radius:50%;background:#FFE600;display:flex;align-items:center;justify-content:center;font-weight:800;color:#2D3277;">ML</div>
<div>
<div style="font-weight:700;" id="connectedNickname">Usuario ML</div>
<div style="font-size:var(--text-caption);color:var(--color-text-muted);" id="connectedSite">MLM</div>
</div>
</div>
<button class="btn btn--danger btn--sm" onclick="disconnectMeli()">Desconectar</button>
</div>
</div>
</div>
<!-- ══════════ TAB: Publicaciones ══════════ -->
<div class="tab-panel" id="panel-listings" role="tabpanel">
<div class="toolbar">
<div class="search-box">
<svg viewBox="0 0 24 24" stroke-linecap="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
<input type="text" id="listingSearch" placeholder="Buscar publicación..." oninput="filterListings()" />
</div>
<select class="select-filter" id="listingStatusFilter" onchange="filterListings()">
<option value="">Todos los estados</option>
<option value="active">Activas</option>
<option value="paused">Pausadas</option>
<option value="closed">Cerradas</option>
</select>
<div class="toolbar__spacer"></div>
<button class="btn btn--ghost btn--sm" onclick="loadMeliStats()">📊 Resumen</button>
<button class="btn btn--primary" onclick="loadListings()">🔄 Actualizar</button>
</div>
<div id="meliStatsBar" style="margin-bottom:var(--space-4);"></div>
<div id="listingsContainer" class="meli-grid"></div>
<div id="listingsPagination" class="table-footer" style="margin-top:var(--space-4);"></div>
</div>
<!-- ══════════ TAB: Preguntas ══════════ -->
<div class="tab-panel" id="panel-questions" role="tabpanel">
<div class="toolbar">
<div class="search-box">
<svg viewBox="0 0 24 24" stroke-linecap="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
<input type="text" id="questionSearch" placeholder="Buscar pregunta..." oninput="filterQuestions()" />
</div>
<select class="select-filter" id="questionStatusFilter" onchange="filterQuestions()">
<option value="">Todas</option>
<option value="unanswered">Sin responder</option>
<option value="answered">Respondidas</option>
<option value="closed">Cerradas</option>
</select>
<div class="toolbar__spacer"></div>
<button class="btn btn--ghost btn--sm" onclick="syncQuestions()">🔄 Sincronizar con ML</button>
<button class="btn btn--primary" onclick="loadQuestions()">🔄 Actualizar</button>
</div>
<div id="questionsStatsBar" style="margin-bottom:var(--space-4);"></div>
<div id="questionsContainer" class="meli-grid"></div>
</div>
<!-- ══════════ TAB: Órdenes ══════════ -->
<div class="tab-panel" id="panel-orders" role="tabpanel">
<div class="toolbar">
<div class="search-box">
<svg viewBox="0 0 24 24" stroke-linecap="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
<input type="text" id="orderSearch" placeholder="Buscar orden..." oninput="filterOrders()" />
</div>
<select class="select-filter" id="orderStatusFilter" onchange="filterOrders()">
<option value="">Todos los estados</option>
<option value="pending">Pendientes</option>
<option value="confirmed">Confirmadas</option>
<option value="packed">Empacadas</option>
<option value="shipped">Enviadas</option>
<option value="delivered">Entregadas</option>
<option value="cancelled">Canceladas</option>
</select>
<div class="toolbar__spacer"></div>
<button class="btn btn--ghost btn--sm" id="btnKanbanView" onclick="toggleOrderView()">📋 Kanban</button>
<button class="btn btn--primary" onclick="loadOrders()">🔄 Actualizar</button>
</div>
<div id="ordersKanbanView" style="display:none;"></div>
<div class="table-wrapper" id="ordersTableView">
<table class="data-table">
<thead>
<tr>
<th>Orden ML</th>
<th>Comprador</th>
<th style="text-align:right">Total</th>
<th>Estado Nexus</th>
<th>Fecha</th>
<th>Acciones</th>
</tr>
</thead>
<tbody id="ordersTableBody"></tbody>
</table>
<div id="ordersPagination" class="table-footer"></div>
</div>
</div>
</div><!-- /tab-panels -->
</main>
</div><!-- /app-shell -->
<!-- ══════════ Order Detail Modal ══════════ -->
<div class="inv-modal-overlay" id="orderModal">
<div class="inv-modal inv-modal--wide">
<div class="inv-modal__header">
<h3>Detalle de Orden ML</h3>
<button class="inv-modal__close" onclick="closeModal('orderModal')">&times;</button>
</div>
<div class="inv-modal__body" id="orderModalBody">cargando...</div>
<div class="inv-modal__footer" id="orderModalFooter"></div>
</div>
</div>
<script src="/pos/static/js/i18n.js" defer></script>
<script src="/pos/static/js/app-init.js" defer></script>
<script src="/pos/static/js/splash-loader.js?v=1" defer></script>
<script src="/pos/static/js/pos-utils.js?v=2" defer></script>
<script src="/pos/static/js/sidebar.js" defer></script>
<script src="/pos/static/js/marketplace_external.js?v=4" defer></script>
<script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script>
</body>
</html>