- 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
1023 lines
51 KiB
HTML
1023 lines
51 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="es">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Admin Panel - Nexus Autoparts</title>
|
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Orbitron:wght@700&display=swap" rel="stylesheet">
|
|
<link rel="stylesheet" href="/shared.css">
|
|
<link rel="stylesheet" href="admin.css">
|
|
</head>
|
|
<body>
|
|
<!-- Shared Navigation -->
|
|
<div id="shared-nav"></div>
|
|
<script src="/nav.js"></script>
|
|
|
|
<div class="container">
|
|
<!-- Sidebar -->
|
|
<aside class="sidebar">
|
|
<div class="sidebar-section">
|
|
<h3>Dashboard</h3>
|
|
<div class="sidebar-item active" data-section="dashboard">
|
|
<span class="icon">📊</span>
|
|
<span>Resumen</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="sidebar-section">
|
|
<h3>Catálogo</h3>
|
|
<div class="sidebar-item" data-section="categories">
|
|
<span class="icon">📁</span>
|
|
<span>Categorías</span>
|
|
</div>
|
|
<div class="sidebar-item" data-section="groups">
|
|
<span class="icon">📂</span>
|
|
<span>Grupos</span>
|
|
</div>
|
|
<div class="sidebar-item" data-section="parts">
|
|
<span class="icon">🔧</span>
|
|
<span>Partes OEM</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="sidebar-section">
|
|
<h3>Aftermarket</h3>
|
|
<div class="sidebar-item" data-section="manufacturers">
|
|
<span class="icon">🏭</span>
|
|
<span>Fabricantes</span>
|
|
</div>
|
|
<div class="sidebar-item" data-section="aftermarket">
|
|
<span class="icon">🔩</span>
|
|
<span>Partes Aftermarket</span>
|
|
</div>
|
|
<div class="sidebar-item" data-section="crossref">
|
|
<span class="icon">🔗</span>
|
|
<span>Cross-References</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="sidebar-section">
|
|
<h3>Fitment</h3>
|
|
<div class="sidebar-item" data-section="fitment">
|
|
<span class="icon">🚗</span>
|
|
<span>Vehículo-Partes</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="sidebar-section">
|
|
<h3>Diagramas</h3>
|
|
<div class="sidebar-item" data-section="diagrams">
|
|
<span class="icon">📐</span>
|
|
<span>Hotspot Editor</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="sidebar-section">
|
|
<h3>Importar/Exportar</h3>
|
|
<div class="sidebar-item" data-section="import">
|
|
<span class="icon">📥</span>
|
|
<span>Importar CSV</span>
|
|
</div>
|
|
<div class="sidebar-item" data-section="export">
|
|
<span class="icon">📤</span>
|
|
<span>Exportar CSV</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="sidebar-section">
|
|
<h3>Sistema</h3>
|
|
<div class="sidebar-item" data-section="users">
|
|
<span class="icon">👤</span>
|
|
<span>Usuarios</span>
|
|
<span class="badge" id="pendingUsersBadge" style="display:none; background:var(--warning); color:#000; font-size:0.7rem; padding:2px 6px; border-radius:10px; margin-left:auto;"></span>
|
|
</div>
|
|
</div>
|
|
</aside>
|
|
|
|
<!-- Main Content -->
|
|
<main class="main-content">
|
|
<!-- Alert container -->
|
|
<div id="alertContainer"></div>
|
|
|
|
<!-- Dashboard Section -->
|
|
<section id="section-dashboard" class="admin-section active">
|
|
<div class="page-header">
|
|
<h1 class="page-title">Panel de Administración</h1>
|
|
</div>
|
|
|
|
<div class="stats-grid" id="dashboardStats">
|
|
<div class="stat-card">
|
|
<div class="stat-value" id="statCategories">-</div>
|
|
<div class="stat-label">Categorías</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-value" id="statGroups">-</div>
|
|
<div class="stat-label">Grupos</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-value" id="statParts">-</div>
|
|
<div class="stat-label">Partes OEM</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-value" id="statAftermarket">-</div>
|
|
<div class="stat-label">Partes Aftermarket</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-value" id="statManufacturers">-</div>
|
|
<div class="stat-label">Fabricantes</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-value" id="statFitment">-</div>
|
|
<div class="stat-label">Fitments</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h2 class="card-title">Acciones Rápidas</h2>
|
|
</div>
|
|
<div style="display: flex; gap: 1rem; flex-wrap: wrap;">
|
|
<button class="btn btn-primary" onclick="showSection('import')">📥 Importar CSV</button>
|
|
<button class="btn btn-secondary" onclick="showSection('parts')">🔧 Agregar Parte</button>
|
|
<button class="btn btn-secondary" onclick="showSection('manufacturers')">🏭 Agregar Fabricante</button>
|
|
<button class="btn btn-secondary" onclick="showSection('export')">📤 Exportar Datos</button>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Categories Section -->
|
|
<section id="section-categories" class="admin-section">
|
|
<div class="page-header">
|
|
<h1 class="page-title">Categorías</h1>
|
|
<div class="page-actions">
|
|
<button class="btn btn-primary" onclick="openCategoryModal()">+ Nueva Categoría</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="table-wrapper">
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>ID</th>
|
|
<th>Nombre</th>
|
|
<th>Nombre (ES)</th>
|
|
<th>Slug</th>
|
|
<th>Icono</th>
|
|
<th>Orden</th>
|
|
<th>Acciones</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="categoriesTable">
|
|
<tr><td colspan="7" class="loading"><div class="spinner"></div></td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Groups Section -->
|
|
<section id="section-groups" class="admin-section">
|
|
<div class="page-header">
|
|
<h1 class="page-title">Grupos</h1>
|
|
<div class="page-actions">
|
|
<button class="btn btn-primary" onclick="openGroupModal()">+ Nuevo Grupo</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="form-group" style="max-width: 300px; margin-bottom: 1rem;">
|
|
<label class="form-label">Filtrar por Categoría</label>
|
|
<select class="form-input" id="groupCategoryFilter" onchange="loadGroups()">
|
|
<option value="">Todas las categorías</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="table-wrapper">
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>ID</th>
|
|
<th>Nombre</th>
|
|
<th>Nombre (ES)</th>
|
|
<th>Categoría</th>
|
|
<th>Orden</th>
|
|
<th>Acciones</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="groupsTable">
|
|
<tr><td colspan="6" class="loading"><div class="spinner"></div></td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Parts Section -->
|
|
<section id="section-parts" class="admin-section">
|
|
<div class="page-header">
|
|
<h1 class="page-title">Partes OEM</h1>
|
|
<div class="page-actions">
|
|
<button class="btn btn-primary" onclick="openPartModal()">+ Nueva Parte</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="form-row" style="margin-bottom: 1rem;">
|
|
<div class="search-box">
|
|
<input type="text" id="partSearch" placeholder="Buscar por nombre o número..." onkeyup="debounceSearch(loadParts)">
|
|
</div>
|
|
<div class="form-group" style="margin-bottom: 0;">
|
|
<select class="form-input" id="partGroupFilter" onchange="loadParts()">
|
|
<option value="">Todos los grupos</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="table-wrapper">
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>ID</th>
|
|
<th>Img</th>
|
|
<th>Número OEM</th>
|
|
<th>Nombre</th>
|
|
<th>Grupo</th>
|
|
<th>Acciones</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="partsTable">
|
|
<tr><td colspan="5" class="loading"><div class="spinner"></div></td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<div class="pagination" id="partsPagination"></div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Manufacturers Section -->
|
|
<section id="section-manufacturers" class="admin-section">
|
|
<div class="page-header">
|
|
<h1 class="page-title">Fabricantes</h1>
|
|
<div class="page-actions">
|
|
<button class="btn btn-primary" onclick="openManufacturerModal()">+ Nuevo Fabricante</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="table-wrapper">
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>ID</th>
|
|
<th>Nombre</th>
|
|
<th>Tipo</th>
|
|
<th>Calidad</th>
|
|
<th>País</th>
|
|
<th>Acciones</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="manufacturersTable">
|
|
<tr><td colspan="6" class="loading"><div class="spinner"></div></td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Aftermarket Section -->
|
|
<section id="section-aftermarket" class="admin-section">
|
|
<div class="page-header">
|
|
<h1 class="page-title">Partes Aftermarket</h1>
|
|
<div class="page-actions">
|
|
<button class="btn btn-primary" onclick="openAftermarketModal()">+ Nueva Parte</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="form-row" style="margin-bottom: 1rem;">
|
|
<div class="search-box">
|
|
<input type="text" id="aftermarketSearch" placeholder="Buscar..." onkeyup="debounceSearch(loadAftermarket)">
|
|
</div>
|
|
<div class="form-group" style="margin-bottom: 0;">
|
|
<select class="form-input" id="aftermarketManufacturerFilter" onchange="loadAftermarket()">
|
|
<option value="">Todos los fabricantes</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="table-wrapper">
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>ID</th>
|
|
<th>Número</th>
|
|
<th>Nombre</th>
|
|
<th>OEM Ref</th>
|
|
<th>Fabricante</th>
|
|
<th>Calidad</th>
|
|
<th>Precio</th>
|
|
<th>Acciones</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="aftermarketTable">
|
|
<tr><td colspan="8" class="loading"><div class="spinner"></div></td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<div class="pagination" id="aftermarketPagination"></div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Cross-References Section -->
|
|
<section id="section-crossref" class="admin-section">
|
|
<div class="page-header">
|
|
<h1 class="page-title">Cross-References</h1>
|
|
<div class="page-actions">
|
|
<button class="btn btn-primary" onclick="openCrossRefModal()">+ Nueva Referencia</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="table-wrapper">
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>ID</th>
|
|
<th>Parte OEM</th>
|
|
<th>Número Referencia</th>
|
|
<th>Tipo</th>
|
|
<th>Fuente</th>
|
|
<th>Acciones</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="crossrefTable">
|
|
<tr><td colspan="6" class="loading"><div class="spinner"></div></td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<div class="pagination" id="crossrefPagination"></div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Fitment Section -->
|
|
<section id="section-fitment" class="admin-section">
|
|
<div class="page-header">
|
|
<h1 class="page-title">Fitment (Vehículo-Partes)</h1>
|
|
<div class="page-actions">
|
|
<button class="btn btn-primary" onclick="openFitmentModal()">+ Nuevo Fitment</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="form-row" style="margin-bottom: 1rem;">
|
|
<div class="form-group" style="margin-bottom: 0;">
|
|
<select class="form-input" id="fitmentBrandFilter" onchange="loadFitmentModels()">
|
|
<option value="">Selecciona marca...</option>
|
|
</select>
|
|
</div>
|
|
<div class="form-group" style="margin-bottom: 0;">
|
|
<select class="form-input" id="fitmentModelFilter" onchange="loadFitment()">
|
|
<option value="">Todos los modelos</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="table-wrapper">
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>ID</th>
|
|
<th>Vehículo</th>
|
|
<th>Parte</th>
|
|
<th>Cantidad</th>
|
|
<th>Posición</th>
|
|
<th>Acciones</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="fitmentTable">
|
|
<tr><td colspan="6" class="loading"><div class="spinner"></div></td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<div class="pagination" id="fitmentPagination"></div>
|
|
</div>
|
|
|
|
<!-- Bulk Fitment Editor -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h2 class="card-title">⚡ Editor Masivo de Fitment</h2>
|
|
</div>
|
|
<p style="color: var(--text-secondary); margin-bottom: 1.5rem;">
|
|
Selecciona un vehículo y agrega múltiples partes de una vez.
|
|
</p>
|
|
|
|
<div class="form-row" style="margin-bottom: 1.5rem;">
|
|
<div class="form-group">
|
|
<label class="form-label">1. Selecciona Marca</label>
|
|
<select class="form-input" id="bulkBrand" onchange="loadBulkModels()">
|
|
<option value="">Selecciona marca...</option>
|
|
</select>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">2. Selecciona Modelo</label>
|
|
<select class="form-input" id="bulkModel" onchange="loadBulkYears()">
|
|
<option value="">Selecciona modelo...</option>
|
|
</select>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">3. Selecciona Año</label>
|
|
<select class="form-input" id="bulkYear" onchange="loadBulkEngines()">
|
|
<option value="">Selecciona año...</option>
|
|
</select>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">4. Selecciona Motor</label>
|
|
<select class="form-input" id="bulkEngine" onchange="selectBulkVehicle()">
|
|
<option value="">Selecciona motor...</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="bulkVehicleSelected" style="display: none;">
|
|
<div class="alert alert-success" style="margin-bottom: 1rem;">
|
|
<strong>Vehículo seleccionado:</strong> <span id="bulkVehicleName"></span>
|
|
(MYE ID: <span id="bulkMYEId"></span>)
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label class="form-label">5. Selecciona Categoría de Partes</label>
|
|
<select class="form-input" id="bulkCategory" onchange="loadBulkParts()" style="max-width: 300px;">
|
|
<option value="">Todas las categorías</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label class="form-label">6. Selecciona Partes a Vincular</label>
|
|
<div class="bulk-parts-container" id="bulkPartsContainer">
|
|
<p style="color: var(--text-secondary);">Cargando partes disponibles...</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div style="margin-top: 1rem;">
|
|
<button class="btn btn-success" onclick="saveBulkFitments()">
|
|
✓ Guardar Fitments Seleccionados
|
|
</button>
|
|
<span id="bulkSelectedCount" style="margin-left: 1rem; color: var(--text-secondary);">
|
|
0 partes seleccionadas
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Import Section -->
|
|
<section id="section-import" class="admin-section">
|
|
<div class="page-header">
|
|
<h1 class="page-title">Importar Datos (CSV)</h1>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h2 class="card-title">Selecciona tipo de datos</h2>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label class="form-label">Tipo de importación</label>
|
|
<select class="form-input" id="importType" style="max-width: 300px;">
|
|
<option value="parts">Partes OEM</option>
|
|
<option value="aftermarket">Partes Aftermarket</option>
|
|
<option value="manufacturers">Fabricantes</option>
|
|
<option value="categories">Categorías</option>
|
|
<option value="groups">Grupos</option>
|
|
<option value="crossref">Cross-References</option>
|
|
<option value="fitment">Fitment (Vehículo-Partes)</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="file-upload" id="dropZone">
|
|
<input type="file" id="csvFile" accept=".csv">
|
|
<div class="file-upload-icon">📄</div>
|
|
<div class="file-upload-text">
|
|
Arrastra un archivo CSV aquí o <strong>haz clic para seleccionar</strong>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="importPreview" style="margin-top: 1.5rem; display: none;">
|
|
<h3 style="margin-bottom: 1rem;">Vista previa (<span id="previewCount">0</span> registros)</h3>
|
|
<div class="table-wrapper" style="max-height: 300px; overflow-y: auto;">
|
|
<table id="previewTable">
|
|
<thead id="previewHead"></thead>
|
|
<tbody id="previewBody"></tbody>
|
|
</table>
|
|
</div>
|
|
<div style="margin-top: 1rem;">
|
|
<button class="btn btn-success" onclick="executeImport()">✓ Importar Datos</button>
|
|
<button class="btn btn-secondary" onclick="cancelImport()">✕ Cancelar</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h2 class="card-title">Formato de CSV</h2>
|
|
</div>
|
|
<div id="csvFormatHelp">
|
|
<p style="color: var(--text-secondary); margin-bottom: 1rem;">Selecciona un tipo de importación para ver el formato requerido.</p>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Export Section -->
|
|
<section id="section-export" class="admin-section">
|
|
<div class="page-header">
|
|
<h1 class="page-title">Exportar Datos (CSV)</h1>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<p style="color: var(--text-secondary); margin-bottom: 1.5rem;">
|
|
Descarga los datos en formato CSV para respaldo o importación en otros sistemas.
|
|
</p>
|
|
|
|
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem;">
|
|
<button class="btn btn-secondary" onclick="exportData('categories')">📁 Categorías</button>
|
|
<button class="btn btn-secondary" onclick="exportData('groups')">📂 Grupos</button>
|
|
<button class="btn btn-secondary" onclick="exportData('parts')">🔧 Partes OEM</button>
|
|
<button class="btn btn-secondary" onclick="exportData('manufacturers')">🏭 Fabricantes</button>
|
|
<button class="btn btn-secondary" onclick="exportData('aftermarket')">🔩 Aftermarket</button>
|
|
<button class="btn btn-secondary" onclick="exportData('crossref')">🔗 Cross-References</button>
|
|
<button class="btn btn-secondary" onclick="exportData('fitment')">🚗 Fitment</button>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Diagrams / Hotspot Editor Section -->
|
|
<section id="section-diagrams" class="admin-section">
|
|
<div class="page-header">
|
|
<h1 class="page-title">Editor de Hotspots</h1>
|
|
</div>
|
|
|
|
<div class="card" style="margin-bottom: 1.5rem;">
|
|
<p style="color: var(--text-secondary); margin-bottom: 1rem;">
|
|
Busca un diagrama por código y haz clic en la imagen para agregar hotspots vinculados a partes.
|
|
</p>
|
|
<div style="display: flex; gap: 0.75rem; align-items: center; flex-wrap: wrap;">
|
|
<input type="text" class="form-input" id="diagramSearchInput"
|
|
placeholder="Buscar diagrama (ej: F200, S341...)"
|
|
style="max-width: 300px;"
|
|
onkeypress="if(event.key==='Enter') searchDiagramsAdmin()">
|
|
<button class="btn btn-primary" onclick="searchDiagramsAdmin()">
|
|
Buscar
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Diagram search results grid -->
|
|
<div id="diagramSearchResults" style="display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 1rem; margin-bottom: 1.5rem;"></div>
|
|
|
|
<!-- Hotspot Editor Area (shown when a diagram is selected) -->
|
|
<div id="hotspotEditorArea" style="display: none;">
|
|
<div class="card">
|
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem;">
|
|
<h2 id="hotspotEditorTitle" style="margin: 0; font-size: 1.1rem;">Diagrama</h2>
|
|
<button class="btn btn-secondary" onclick="closeHotspotEditor()">Cerrar Editor</button>
|
|
</div>
|
|
|
|
<div style="display: flex; gap: 1.5rem; flex-wrap: wrap;">
|
|
<!-- Image with click-to-place -->
|
|
<div style="flex: 1; min-width: 400px; position: relative; background: #f0f0f0; border-radius: 8px; overflow: hidden; cursor: crosshair;" id="hotspotImageContainer">
|
|
<img id="hotspotEditorImg" src="" alt="Diagram"
|
|
style="width: 100%; display: block;"
|
|
onclick="onHotspotImageClick(event)">
|
|
<div id="hotspotMarkersContainer" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none;"></div>
|
|
</div>
|
|
|
|
<!-- Hotspot form + list -->
|
|
<div style="width: 320px; flex-shrink: 0;">
|
|
<h3 style="font-size: 0.95rem; margin-bottom: 0.75rem;">Agregar / Editar Hotspot</h3>
|
|
<form id="hotspotForm" style="margin-bottom: 1rem;">
|
|
<input type="hidden" id="hsEditId">
|
|
<div class="form-group">
|
|
<label class="form-label">Posición (x%, y%)</label>
|
|
<input type="text" class="form-input" id="hsCoords" placeholder="Clic en imagen..." readonly>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label"># Callout</label>
|
|
<input type="number" class="form-input" id="hsCallout" min="1" value="1">
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Parte OEM (buscar)</label>
|
|
<input type="text" class="form-input" id="hsPartSearch"
|
|
placeholder="Buscar parte por nombre o #..."
|
|
oninput="searchPartsForHotspot(this.value)">
|
|
<select class="form-input" id="hsPartSelect" size="4" style="margin-top: 0.25rem; display: none;">
|
|
</select>
|
|
<input type="hidden" id="hsPartId">
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Etiqueta</label>
|
|
<input type="text" class="form-input" id="hsLabel" placeholder="Opcional">
|
|
</div>
|
|
<div style="display: flex; gap: 0.5rem;">
|
|
<button type="button" class="btn btn-primary" onclick="saveHotspot()">Guardar</button>
|
|
<button type="button" class="btn btn-secondary" onclick="clearHotspotForm()">Limpiar</button>
|
|
</div>
|
|
</form>
|
|
|
|
<h3 style="font-size: 0.95rem; margin-bottom: 0.5rem;">Hotspots Existentes</h3>
|
|
<div id="hotspotsList" style="max-height: 300px; overflow-y: auto;"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Users Section -->
|
|
<section id="section-users" class="admin-section">
|
|
<div class="page-header">
|
|
<h1 class="page-title">Usuarios</h1>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="table-wrapper">
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>Nombre</th>
|
|
<th>Email</th>
|
|
<th>Negocio</th>
|
|
<th>Rol</th>
|
|
<th>Activo</th>
|
|
<th>Último Login</th>
|
|
<th>Acciones</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="usersTable">
|
|
<tr><td colspan="7" class="loading"><div class="spinner"></div></td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
</main>
|
|
</div>
|
|
|
|
<!-- Category Modal -->
|
|
<div class="modal-overlay" id="categoryModal">
|
|
<div class="modal">
|
|
<div class="modal-header">
|
|
<h2 class="modal-title" id="categoryModalTitle">Nueva Categoría</h2>
|
|
<button class="modal-close" onclick="closeModal('categoryModal')">×</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<form id="categoryForm">
|
|
<input type="hidden" id="categoryId">
|
|
<div class="form-group">
|
|
<label class="form-label">Nombre (EN)</label>
|
|
<input type="text" class="form-input" id="categoryName" required>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Nombre (ES)</label>
|
|
<input type="text" class="form-input" id="categoryNameEs">
|
|
</div>
|
|
<div class="form-row">
|
|
<div class="form-group">
|
|
<label class="form-label">Slug</label>
|
|
<input type="text" class="form-input" id="categorySlug" placeholder="auto-generado">
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Icono</label>
|
|
<input type="text" class="form-input" id="categoryIcon" placeholder="ej: engine">
|
|
</div>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Orden de visualización</label>
|
|
<input type="number" class="form-input" id="categoryOrder" value="0">
|
|
</div>
|
|
</form>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button class="btn btn-secondary" onclick="closeModal('categoryModal')">Cancelar</button>
|
|
<button class="btn btn-primary" onclick="saveCategory()">Guardar</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Group Modal -->
|
|
<div class="modal-overlay" id="groupModal">
|
|
<div class="modal">
|
|
<div class="modal-header">
|
|
<h2 class="modal-title" id="groupModalTitle">Nuevo Grupo</h2>
|
|
<button class="modal-close" onclick="closeModal('groupModal')">×</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<form id="groupForm">
|
|
<input type="hidden" id="groupId">
|
|
<div class="form-group">
|
|
<label class="form-label">Categoría</label>
|
|
<select class="form-input" id="groupCategory" required></select>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Nombre (EN)</label>
|
|
<input type="text" class="form-input" id="groupName" required>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Nombre (ES)</label>
|
|
<input type="text" class="form-input" id="groupNameEs">
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Orden de visualización</label>
|
|
<input type="number" class="form-input" id="groupOrder" value="0">
|
|
</div>
|
|
</form>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button class="btn btn-secondary" onclick="closeModal('groupModal')">Cancelar</button>
|
|
<button class="btn btn-primary" onclick="saveGroup()">Guardar</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Part Modal -->
|
|
<div class="modal-overlay" id="partModal">
|
|
<div class="modal">
|
|
<div class="modal-header">
|
|
<h2 class="modal-title" id="partModalTitle">Nueva Parte OEM</h2>
|
|
<button class="modal-close" onclick="closeModal('partModal')">×</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<form id="partForm">
|
|
<input type="hidden" id="partId">
|
|
<div class="form-group">
|
|
<label class="form-label">Número de Parte OEM</label>
|
|
<input type="text" class="form-input" id="partOemNumber" required>
|
|
</div>
|
|
<div class="form-row">
|
|
<div class="form-group">
|
|
<label class="form-label">Nombre (EN)</label>
|
|
<input type="text" class="form-input" id="partName" required>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Nombre (ES)</label>
|
|
<input type="text" class="form-input" id="partNameEs">
|
|
</div>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Grupo</label>
|
|
<select class="form-input" id="partGroup" required></select>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Descripción (EN)</label>
|
|
<textarea class="form-input" id="partDescription"></textarea>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Descripción (ES)</label>
|
|
<textarea class="form-input" id="partDescriptionEs"></textarea>
|
|
</div>
|
|
<div class="form-row">
|
|
<div class="form-group">
|
|
<label class="form-label">Peso (kg)</label>
|
|
<input type="number" step="0.01" class="form-input" id="partWeight">
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Material</label>
|
|
<input type="text" class="form-input" id="partMaterial">
|
|
</div>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Imagen de la parte</label>
|
|
<div class="image-upload-container">
|
|
<div class="image-preview" id="partImagePreview">
|
|
<span class="image-placeholder">📷 Sin imagen</span>
|
|
</div>
|
|
<div class="image-upload-actions">
|
|
<input type="file" id="partImageFile" accept="image/*" onchange="previewPartImage(this)">
|
|
<button type="button" class="btn btn-secondary btn-sm" onclick="document.getElementById('partImageFile').click()">
|
|
📤 Subir imagen
|
|
</button>
|
|
<input type="text" class="form-input" id="partImageUrl" placeholder="O pegar URL de imagen..." style="margin-top: 0.5rem;">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button class="btn btn-secondary" onclick="closeModal('partModal')">Cancelar</button>
|
|
<button class="btn btn-primary" onclick="savePart()">Guardar</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Manufacturer Modal -->
|
|
<div class="modal-overlay" id="manufacturerModal">
|
|
<div class="modal">
|
|
<div class="modal-header">
|
|
<h2 class="modal-title" id="manufacturerModalTitle">Nuevo Fabricante</h2>
|
|
<button class="modal-close" onclick="closeModal('manufacturerModal')">×</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<form id="manufacturerForm">
|
|
<input type="hidden" id="manufacturerId">
|
|
<div class="form-group">
|
|
<label class="form-label">Nombre</label>
|
|
<input type="text" class="form-input" id="manufacturerName" required>
|
|
</div>
|
|
<div class="form-row">
|
|
<div class="form-group">
|
|
<label class="form-label">Tipo</label>
|
|
<select class="form-input" id="manufacturerType" required>
|
|
<option value="aftermarket">Aftermarket</option>
|
|
<option value="oem">OEM</option>
|
|
<option value="remanufactured">Remanufacturado</option>
|
|
</select>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Tier de Calidad</label>
|
|
<select class="form-input" id="manufacturerQuality">
|
|
<option value="economy">Economy</option>
|
|
<option value="standard">Standard</option>
|
|
<option value="premium">Premium</option>
|
|
<option value="oem">OEM</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="form-row">
|
|
<div class="form-group">
|
|
<label class="form-label">País</label>
|
|
<input type="text" class="form-input" id="manufacturerCountry">
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Website</label>
|
|
<input type="url" class="form-input" id="manufacturerWebsite">
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button class="btn btn-secondary" onclick="closeModal('manufacturerModal')">Cancelar</button>
|
|
<button class="btn btn-primary" onclick="saveManufacturer()">Guardar</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Aftermarket Part Modal -->
|
|
<div class="modal-overlay" id="aftermarketModal">
|
|
<div class="modal">
|
|
<div class="modal-header">
|
|
<h2 class="modal-title" id="aftermarketModalTitle">Nueva Parte Aftermarket</h2>
|
|
<button class="modal-close" onclick="closeModal('aftermarketModal')">×</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<form id="aftermarketForm">
|
|
<input type="hidden" id="aftermarketId">
|
|
<div class="form-group">
|
|
<label class="form-label">Parte OEM de referencia</label>
|
|
<select class="form-input" id="aftermarketOemPart" required></select>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Fabricante</label>
|
|
<select class="form-input" id="aftermarketManufacturer" required></select>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Número de Parte</label>
|
|
<input type="text" class="form-input" id="aftermarketPartNumber" required>
|
|
</div>
|
|
<div class="form-row">
|
|
<div class="form-group">
|
|
<label class="form-label">Nombre (EN)</label>
|
|
<input type="text" class="form-input" id="aftermarketName">
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Nombre (ES)</label>
|
|
<input type="text" class="form-input" id="aftermarketNameEs">
|
|
</div>
|
|
</div>
|
|
<div class="form-row">
|
|
<div class="form-group">
|
|
<label class="form-label">Calidad</label>
|
|
<select class="form-input" id="aftermarketQuality">
|
|
<option value="economy">Economy</option>
|
|
<option value="standard">Standard</option>
|
|
<option value="premium">Premium</option>
|
|
</select>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Precio (USD)</label>
|
|
<input type="number" step="0.01" class="form-input" id="aftermarketPrice">
|
|
</div>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Garantía (meses)</label>
|
|
<input type="number" class="form-input" id="aftermarketWarranty">
|
|
</div>
|
|
</form>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button class="btn btn-secondary" onclick="closeModal('aftermarketModal')">Cancelar</button>
|
|
<button class="btn btn-primary" onclick="saveAftermarket()">Guardar</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Cross-Reference Modal -->
|
|
<div class="modal-overlay" id="crossrefModal">
|
|
<div class="modal">
|
|
<div class="modal-header">
|
|
<h2 class="modal-title" id="crossrefModalTitle">Nueva Cross-Reference</h2>
|
|
<button class="modal-close" onclick="closeModal('crossrefModal')">×</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<form id="crossrefForm">
|
|
<input type="hidden" id="crossrefId">
|
|
<div class="form-group">
|
|
<label class="form-label">Parte OEM</label>
|
|
<select class="form-input" id="crossrefPart" required></select>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Número de Referencia</label>
|
|
<input type="text" class="form-input" id="crossrefNumber" required>
|
|
</div>
|
|
<div class="form-row">
|
|
<div class="form-group">
|
|
<label class="form-label">Tipo</label>
|
|
<select class="form-input" id="crossrefType" required>
|
|
<option value="oem_alternate">OEM Alternativo</option>
|
|
<option value="supersession">Supersesión</option>
|
|
<option value="interchange">Intercambio</option>
|
|
<option value="competitor">Competidor</option>
|
|
</select>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Fuente</label>
|
|
<input type="text" class="form-input" id="crossrefSource">
|
|
</div>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Notas</label>
|
|
<textarea class="form-input" id="crossrefNotes"></textarea>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button class="btn btn-secondary" onclick="closeModal('crossrefModal')">Cancelar</button>
|
|
<button class="btn btn-primary" onclick="saveCrossRef()">Guardar</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Fitment Modal -->
|
|
<div class="modal-overlay" id="fitmentModal">
|
|
<div class="modal">
|
|
<div class="modal-header">
|
|
<h2 class="modal-title" id="fitmentModalTitle">Nuevo Fitment</h2>
|
|
<button class="modal-close" onclick="closeModal('fitmentModal')">×</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<form id="fitmentForm">
|
|
<input type="hidden" id="fitmentId">
|
|
<div class="form-group">
|
|
<label class="form-label">Vehículo (Marca/Modelo/Año)</label>
|
|
<select class="form-input" id="fitmentVehicle" required></select>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Parte OEM</label>
|
|
<select class="form-input" id="fitmentPart" required></select>
|
|
</div>
|
|
<div class="form-row">
|
|
<div class="form-group">
|
|
<label class="form-label">Cantidad requerida</label>
|
|
<input type="number" class="form-input" id="fitmentQuantity" value="1" min="1">
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Posición</label>
|
|
<select class="form-input" id="fitmentPosition">
|
|
<option value="">N/A</option>
|
|
<option value="front">Frontal</option>
|
|
<option value="rear">Trasero</option>
|
|
<option value="left">Izquierdo</option>
|
|
<option value="right">Derecho</option>
|
|
<option value="front-left">Frontal Izq.</option>
|
|
<option value="front-right">Frontal Der.</option>
|
|
<option value="rear-left">Trasero Izq.</option>
|
|
<option value="rear-right">Trasero Der.</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Notas de fitment</label>
|
|
<textarea class="form-input" id="fitmentNotes"></textarea>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button class="btn btn-secondary" onclick="closeModal('fitmentModal')">Cancelar</button>
|
|
<button class="btn btn-primary" onclick="saveFitment()">Guardar</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="admin.js"></script>
|
|
</body>
|
|
</html>
|