- F0 blindaje: Redis/Celery, pg_hba sin trust LAN, secretos a env 600, backups, webhook auth, jobs muertos - F1 caja: bugs POS, reembolsos al corte, apartados, CFDI, topes de descuento en cotizacion/apartado - F2/F7 IA de catalogo: autocategorizacion, dedup de SKUs con embeddings, OCR de facturas, descripciones, claves SAT, limpieza de fitment - F3 IA en el flujo: vision Yonke, preguntas MeLi, venta cruzada, anomalias, sinonimos, busqueda semantica (rerank/embeddings) - F4/F8 escala y cartera: /metrics+Prometheus+Grafana, pool, score de credito, churn, metricas multi-tenant - Nexus Yonke (M1-M6): modelo de datos 1-de-1, alta por donador, vitrina publica, MeLi usado, consignacion, garantia por dias, reportes, etiquetas - Marketplace B2B entre yonkes (catalogo + pedidos cross-tenant) - Seguridad: auth en /api/admin/* (35 endpoints), alertas por tenant - Migraciones v4.27/v4.28; timers expire-demos y rollup-metrics
1463 lines
73 KiB
HTML
1463 lines
73 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="es">
|
||
<head>
|
||
<script>/*pos_theme_early*/(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>Yonke — 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=33" />
|
||
<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?v=34" />
|
||
<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>
|
||
/* ─── Yonke: page-specific bits ─────────────────────────────────────── */
|
||
|
||
.ynk-thumb {
|
||
width: 44px; height: 44px; object-fit: cover; border-radius: var(--radius-sm);
|
||
background: var(--color-surface-2); border: 1px solid var(--color-border); cursor: pointer;
|
||
display: block;
|
||
}
|
||
.ynk-thumb-slot { display: inline-block; cursor: pointer; line-height: 0; }
|
||
.ynk-thumb--empty {
|
||
display: flex; align-items: center; justify-content: center;
|
||
color: var(--color-text-muted); font-size: 9px; text-transform: uppercase;
|
||
letter-spacing: var(--tracking-wide); line-height: 1.15; text-align: center;
|
||
}
|
||
|
||
.ynk-grade {
|
||
display: inline-flex; align-items: center; justify-content: center;
|
||
width: 22px; height: 22px; border-radius: 50%; font-weight: 700; font-size: 11px;
|
||
border: 1px solid transparent;
|
||
}
|
||
.ynk-grade--A { background: rgba(34,197,94,.15); color: var(--color-success); border-color: rgba(34,197,94,.35); }
|
||
.ynk-grade--B { background: rgba(59,130,246,.15); color: var(--color-info, #3b82f6); border-color: rgba(59,130,246,.35); }
|
||
.ynk-grade--C { background: rgba(234,179,8,.15); color: #eab308; border-color: rgba(234,179,8,.35); }
|
||
.ynk-grade--D { background: rgba(239,68,68,.15); color: var(--color-error); border-color: rgba(239,68,68,.35); }
|
||
.ynk-grade--none { color: var(--color-text-muted); }
|
||
|
||
.ynk-uniq {
|
||
display: inline-block; margin-left: 4px; padding: 1px 5px; border-radius: var(--radius-full);
|
||
background: var(--color-primary-muted); color: var(--color-primary);
|
||
font-size: 0.625rem; font-weight: 700; letter-spacing: var(--tracking-wide);
|
||
text-transform: uppercase; vertical-align: middle;
|
||
}
|
||
|
||
.ynk-seg {
|
||
display: inline-flex; border: 1px solid var(--color-border);
|
||
border-radius: var(--radius-md); overflow: hidden; background: var(--color-bg-elevated);
|
||
}
|
||
.ynk-seg button {
|
||
border: none; background: transparent; color: var(--color-text-muted); cursor: pointer;
|
||
padding: 0 var(--space-4); height: 36px; font-family: var(--font-body);
|
||
font-size: var(--text-body-sm); font-weight: var(--font-weight-semibold);
|
||
letter-spacing: var(--tracking-wide); white-space: nowrap;
|
||
}
|
||
.ynk-seg button + button { border-left: 1px solid var(--color-border); }
|
||
.ynk-seg button:hover { color: var(--color-text-primary); background: var(--color-surface-2); }
|
||
.ynk-seg button.is-active { background: var(--color-primary); color: var(--color-text-inverse); }
|
||
|
||
.ynk-sku { font-family: var(--font-mono); font-weight: 700; color: var(--color-primary); }
|
||
|
||
.ynk-hint { font-size: var(--text-caption); color: var(--color-text-muted); }
|
||
|
||
.ynk-drop {
|
||
border: 1px dashed var(--color-border); border-radius: var(--radius-md);
|
||
padding: var(--space-4); text-align: center; background: var(--color-surface-1);
|
||
}
|
||
|
||
.ynk-photo-grid {
|
||
display: grid; grid-template-columns: repeat(auto-fill, minmax(88px, 1fr));
|
||
gap: var(--space-2); margin-top: var(--space-2);
|
||
}
|
||
.ynk-photo {
|
||
position: relative; aspect-ratio: 1 / 1; border-radius: var(--radius-md);
|
||
overflow: hidden; border: 1px solid var(--color-border); background: var(--color-surface-2);
|
||
}
|
||
.ynk-photo img { width: 100%; height: 100%; object-fit: cover; display: block; }
|
||
.ynk-photo__remove {
|
||
position: absolute; top: 3px; right: 3px; width: 20px; height: 20px; padding: 0;
|
||
border: none; border-radius: 50%; cursor: pointer; line-height: 1;
|
||
background: rgba(0,0,0,.65); color: #fff; font-size: 13px;
|
||
}
|
||
|
||
.ynk-empty {
|
||
padding: var(--space-6); text-align: center; color: var(--color-text-muted);
|
||
font-size: var(--text-body-sm);
|
||
}
|
||
|
||
.ynk-summary-card {
|
||
background: var(--color-surface-1); border: 1px solid var(--color-border);
|
||
border-radius: var(--radius-lg); padding: var(--space-4);
|
||
}
|
||
.ynk-summary-card h4 {
|
||
margin: 0 0 var(--space-3); font-size: var(--text-caption);
|
||
letter-spacing: var(--tracking-widest); text-transform: uppercase;
|
||
color: var(--color-text-muted);
|
||
}
|
||
.ynk-kv {
|
||
display: flex; justify-content: space-between; gap: var(--space-3);
|
||
padding: 5px 0; border-bottom: 1px dashed var(--color-border);
|
||
font-size: var(--text-body-sm);
|
||
}
|
||
.ynk-kv:last-child { border-bottom: none; }
|
||
.ynk-kv__k { color: var(--color-text-muted); }
|
||
.ynk-kv__v { color: var(--color-text-primary); font-weight: var(--font-weight-semibold); text-align: right; }
|
||
|
||
.ynk-save-result { margin-top: var(--space-3); min-height: 1.5em; font-size: var(--text-body-sm); }
|
||
.ynk-ok { color: var(--color-success); }
|
||
.ynk-err { color: var(--color-error); }
|
||
.ynk-warn{ color: #eab308; }
|
||
|
||
.ynk-rep-grid {
|
||
display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||
gap: var(--space-3); align-items: start;
|
||
}
|
||
.ynk-rep-grid > .ynk-hint { grid-column: 1 / -1; }
|
||
.ynk-link { color: var(--color-primary); text-decoration: underline; word-break: break-all; }
|
||
|
||
/* ─── Mayoreo B2B ─────────────────────────────────────────────────── */
|
||
.ynk-b2b-help {
|
||
background: var(--color-primary-muted); border: 1px solid rgba(245,166,35,.28);
|
||
border-radius: var(--radius-md); padding: var(--space-2) var(--space-3);
|
||
font-size: var(--text-caption); color: var(--color-text-primary);
|
||
margin-bottom: var(--space-3); line-height: 1.5;
|
||
}
|
||
.ynk-b2b-grid {
|
||
display: grid; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr));
|
||
gap: var(--space-3);
|
||
}
|
||
.ynk-b2b-card {
|
||
display: flex; flex-direction: column; overflow: hidden;
|
||
background: var(--color-surface-1); border: 1px solid var(--color-border);
|
||
border-radius: var(--radius-lg);
|
||
}
|
||
.ynk-b2b-card__img {
|
||
width: 100%; aspect-ratio: 4 / 3; object-fit: cover; display: block;
|
||
background: var(--color-surface-2); border-bottom: 1px solid var(--color-border);
|
||
}
|
||
.ynk-b2b-card__img--empty {
|
||
display: flex; align-items: center; justify-content: center;
|
||
color: var(--color-text-muted); font-size: 9px; text-transform: uppercase;
|
||
letter-spacing: var(--tracking-wide);
|
||
}
|
||
.ynk-b2b-card__body {
|
||
display: flex; flex-direction: column; gap: 5px; flex: 1;
|
||
padding: var(--space-3);
|
||
}
|
||
.ynk-b2b-card__name {
|
||
font-weight: var(--font-weight-semibold); font-size: var(--text-body-sm);
|
||
color: var(--color-text-primary);
|
||
}
|
||
.ynk-b2b-card__meta {
|
||
font-size: var(--text-caption); color: var(--color-text-muted);
|
||
display: flex; align-items: center; gap: 5px;
|
||
}
|
||
.ynk-b2b-card__price {
|
||
font-family: var(--font-mono); font-weight: 700;
|
||
color: var(--color-primary); font-size: 1.05rem;
|
||
}
|
||
.ynk-b2b-card__foot { margin-top: auto; padding-top: var(--space-2); }
|
||
.ynk-b2b-price-input { width: 110px; text-align: right; }
|
||
.ynk-b2b-actions { display: flex; flex-wrap: wrap; gap: var(--space-2); }
|
||
</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">Yonke — Partes usadas</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 is replaced/injected by sidebar.js -->
|
||
<aside class="sidebar" id="sidebar" role="navigation" aria-label="Navegación principal"></aside>
|
||
|
||
<main class="main" role="main">
|
||
|
||
<!-- Page Header -->
|
||
<div class="page-header">
|
||
<div class="page-header__title-group">
|
||
<span class="page-header__eyebrow">Deshuesadero · Partes usadas</span>
|
||
<h1 class="page-header__title">Nexus Yonke</h1>
|
||
</div>
|
||
<div class="page-header__actions">
|
||
<button class="btn btn--ghost" onclick="switchTab('donador')">
|
||
<svg viewBox="0 0 24 24"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
|
||
Buscar donador
|
||
</button>
|
||
<button class="btn btn--ghost" onclick="ynkRefreshPieces()">
|
||
<svg viewBox="0 0 24 24"><polyline points="23 4 23 10 17 10"/><path d="M20.49 15a9 9 0 1 1-.38-4.93"/></svg>
|
||
Sincronizar
|
||
</button>
|
||
<button class="btn btn--primary" onclick="switchTab('nueva')">
|
||
<svg viewBox="0 0 24 24"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
|
||
Nueva pieza
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Summary Cards -->
|
||
<div class="summary-strip">
|
||
<div class="summary-card">
|
||
<div class="summary-card__icon">
|
||
<svg viewBox="0 0 24 24"><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>
|
||
</div>
|
||
<div class="summary-card__body">
|
||
<div class="summary-card__label">Piezas usadas</div>
|
||
<div class="summary-card__value" id="ynkStatPieces">--</div>
|
||
<div class="summary-card__sub">En catálogo</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="summary-card">
|
||
<div class="summary-card__icon summary-card__icon--time">
|
||
<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><path d="M12 8v4l3 2"/></svg>
|
||
</div>
|
||
<div class="summary-card__body">
|
||
<div class="summary-card__label">Donadores</div>
|
||
<div class="summary-card__value" id="ynkStatDonors">--</div>
|
||
<div class="summary-card__sub">Vehículos desarmados</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="summary-card">
|
||
<div class="summary-card__icon summary-card__icon--value">
|
||
<svg viewBox="0 0 24 24"><line x1="12" y1="1" x2="12" y2="23"/><path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"/></svg>
|
||
</div>
|
||
<div class="summary-card__body">
|
||
<div class="summary-card__label">Piezas en consigna</div>
|
||
<div class="summary-card__value" id="ynkStatConsignPieces">--</div>
|
||
<div class="summary-card__sub">Propiedad de terceros</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="summary-card">
|
||
<div class="summary-card__icon summary-card__icon--alert">
|
||
<svg viewBox="0 0 24 24"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>
|
||
</div>
|
||
<div class="summary-card__body">
|
||
<div class="summary-card__label">Consignaciones</div>
|
||
<div class="summary-card__value" id="ynkStatConsignments">--</div>
|
||
<div class="summary-card__sub"><span id="ynkStatConsignmentsSub">Abiertas</span></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Tabs Row -->
|
||
<div class="tabs-row" role="tablist" aria-label="Módulos de Yonke">
|
||
<button class="tab-btn is-active" role="tab" aria-selected="true" aria-controls="panel-piezas" onclick="switchTab('piezas')">
|
||
Piezas usadas <span class="tab-btn__badge" id="badge-piezas">0</span>
|
||
</button>
|
||
<button class="tab-btn" role="tab" aria-selected="false" aria-controls="panel-nueva" onclick="switchTab('nueva')">
|
||
Nueva pieza
|
||
</button>
|
||
<button class="tab-btn" role="tab" aria-selected="false" aria-controls="panel-donador" onclick="switchTab('donador')">
|
||
Por donador <span class="tab-btn__badge" id="badge-donadores">0</span>
|
||
</button>
|
||
<button class="tab-btn" role="tab" aria-selected="false" aria-controls="panel-consignaciones" onclick="switchTab('consignaciones')">
|
||
Consignaciones <span class="tab-btn__badge" id="badge-consignaciones">0</span>
|
||
</button>
|
||
<button class="tab-btn" role="tab" aria-selected="false" aria-controls="panel-b2b" onclick="switchTab('b2b')" title="Mayoreo entre negocios: compra y vende piezas usadas al mayoreo">
|
||
Mayoreo B2B <span class="tab-btn__badge" id="badge-b2b">0</span>
|
||
</button>
|
||
<button class="tab-btn" role="tab" aria-selected="false" aria-controls="panel-reportes" onclick="switchTab('reportes')">
|
||
Reportes
|
||
</button>
|
||
</div>
|
||
|
||
<!-- Tab Panels -->
|
||
<div class="tab-panels" id="tab-panels">
|
||
|
||
<!-- ===================================================================
|
||
TAB 1 — PIEZAS USADAS
|
||
=================================================================== -->
|
||
<div class="tab-panel is-active" id="panel-piezas" 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="ynkQ" placeholder="Buscar por nombre, No. parte, SKU o VIN…" autocomplete="off" />
|
||
</div>
|
||
<select class="select-filter" id="ynkFiltCondition">
|
||
<option value="">Usadas y reconstruidas</option>
|
||
<option value="usado">Usado</option>
|
||
<option value="reconstruido">Reconstruido</option>
|
||
</select>
|
||
<select class="select-filter" id="ynkFiltGrade">
|
||
<option value="">Todo grado</option>
|
||
<option value="A">Grado A</option>
|
||
<option value="B">Grado B</option>
|
||
<option value="C">Grado C</option>
|
||
<option value="D">Grado D</option>
|
||
</select>
|
||
<select class="select-filter" id="ynkFiltOwnership">
|
||
<option value="">Propiedad: toda</option>
|
||
<option value="propio">Propio</option>
|
||
<option value="consigna">Consigna</option>
|
||
</select>
|
||
<select class="select-filter" id="ynkFiltPerPage">
|
||
<option value="25">25 / pág.</option>
|
||
<option value="50" selected>50 / pág.</option>
|
||
<option value="100">100 / pág.</option>
|
||
</select>
|
||
<div class="toolbar__spacer"></div>
|
||
<button class="btn btn--ghost btn--sm" onclick="ynkResetPieceFilters()">Limpiar</button>
|
||
<button class="btn btn--ghost btn--sm" onclick="ynkOpenImportModal()">
|
||
<svg viewBox="0 0 24 24"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>
|
||
Importar CSV
|
||
</button>
|
||
<button class="btn btn--primary btn--sm" onclick="switchTab('nueva')">
|
||
<svg viewBox="0 0 24 24"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
|
||
Nueva pieza
|
||
</button>
|
||
</div>
|
||
|
||
<div class="table-wrapper">
|
||
<table class="data-table" id="ynkPiecesTable">
|
||
<thead>
|
||
<tr>
|
||
<th style="width:60px;">Foto</th>
|
||
<th>No. Parte / SKU</th>
|
||
<th>Nombre</th>
|
||
<th>Marca</th>
|
||
<th>Condición</th>
|
||
<th>Grado</th>
|
||
<th>Donador</th>
|
||
<th style="text-align:right">Precio</th>
|
||
<th style="text-align:right">Costo</th>
|
||
<th style="text-align:right">Stock</th>
|
||
<th>Ubicación</th>
|
||
<th>Acciones</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="ynkPiecesBody">
|
||
<tr><td colspan="12" class="ynk-empty">Cargando piezas…</td></tr>
|
||
</tbody>
|
||
</table>
|
||
<div class="table-footer">
|
||
<span id="ynkPiecesFooter">—</span>
|
||
<div class="pagination" id="ynkPiecesPagination"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ===================================================================
|
||
TAB 2 — NUEVA PIEZA (alta rápida)
|
||
=================================================================== -->
|
||
<div class="tab-panel" id="panel-nueva" role="tabpanel">
|
||
<div class="table-wrapper" style="padding:var(--space-5);">
|
||
<div style="display:flex;justify-content:space-between;align-items:center;gap:var(--space-3);margin-bottom:var(--space-4);flex-wrap:wrap;">
|
||
<div>
|
||
<h3 style="font-family:var(--font-heading);font-size:var(--text-h5,1rem);text-transform:uppercase;letter-spacing:var(--tracking-wide);">Alta rápida de pieza usada</h3>
|
||
<p class="ynk-hint" style="margin-top:4px;">Solo <strong>nombre</strong> y <strong>precio</strong> son obligatorios. El No. de parte se genera automático si lo dejas vacío.</p>
|
||
</div>
|
||
<span class="ynk-hint" id="ynkTimer">—</span>
|
||
</div>
|
||
|
||
<div class="toolbar" style="margin-bottom:var(--space-4);">
|
||
<div class="ynk-seg" role="tablist" aria-label="Modo de alta">
|
||
<button type="button" id="ynkSegSingle" class="is-active" onclick="ynkSetIntakeMode('single')">Pieza individual</button>
|
||
<button type="button" id="ynkSegFull" onclick="ynkSetIntakeMode('full')">Donador completo (varias piezas)</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="ynkNuevaSimple">
|
||
<div class="inv-form-grid">
|
||
<div class="inv-field inv-field--full">
|
||
<label for="ynkName">Nombre de la pieza *</label>
|
||
<input type="text" id="ynkName" placeholder="Ej: Faro delantero izquierdo" autocomplete="off" />
|
||
</div>
|
||
|
||
<div class="inv-field">
|
||
<label for="ynkPartNumber">No. de parte / SKU</label>
|
||
<input type="text" id="ynkPartNumber" placeholder="se genera automático" autocomplete="off" />
|
||
</div>
|
||
<div class="inv-field">
|
||
<label for="ynkBrand">Marca de la pieza</label>
|
||
<input type="text" id="ynkBrand" placeholder="Ej: Toyota / Genérico" autocomplete="off" />
|
||
</div>
|
||
|
||
<div class="inv-field">
|
||
<label for="ynkPrice">Precio de venta *</label>
|
||
<input type="number" id="ynkPrice" step="0.01" min="0" placeholder="0.00" />
|
||
</div>
|
||
<div class="inv-field">
|
||
<label for="ynkCost">Costo</label>
|
||
<input type="number" id="ynkCost" step="0.01" min="0" placeholder="0.00" />
|
||
</div>
|
||
|
||
<div class="inv-field">
|
||
<label for="ynkCondition">Condición</label>
|
||
<select id="ynkCondition">
|
||
<option value="usado" selected>Usado</option>
|
||
<option value="reconstruido">Reconstruido</option>
|
||
</select>
|
||
</div>
|
||
<div class="inv-field">
|
||
<label for="ynkGrade">Grado</label>
|
||
<select id="ynkGrade">
|
||
<option value="">Sin grado</option>
|
||
<option value="A">A — Excelente</option>
|
||
<option value="B">B — Bueno</option>
|
||
<option value="C">C — Regular</option>
|
||
<option value="D">D — Para partes</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div class="inv-field inv-field--full" style="flex-direction:row;align-items:center;gap:var(--space-2);">
|
||
<input type="checkbox" id="ynkUnique" checked style="width:auto;" />
|
||
<label for="ynkUnique" style="text-transform:none;letter-spacing:0;font-size:var(--text-body-sm);">Pieza única (1 de 1) — proviene de un vehículo específico</label>
|
||
</div>
|
||
|
||
<!-- Donor vehicle -->
|
||
<div class="inv-field inv-field--full" style="border-top:1px solid var(--color-border);padding-top:var(--space-3);">
|
||
<label>Vehículo donador</label>
|
||
</div>
|
||
|
||
<div class="inv-field inv-field--full">
|
||
<label for="ynkVin">VIN del donador</label>
|
||
<div style="display:flex;gap:var(--space-2);">
|
||
<input type="text" id="ynkVin" placeholder="17 caracteres (ej: JTDKB20U123456789)" maxlength="20" autocomplete="off" style="flex:1;text-transform:uppercase;" />
|
||
<button class="btn btn--secondary" type="button" onclick="ynkDecodeVin()" id="ynkDecodeBtn">
|
||
<svg viewBox="0 0 24 24"><path d="M4 6h16M4 12h16M4 18h10"/></svg>
|
||
Decodificar
|
||
</button>
|
||
</div>
|
||
<span class="ynk-hint" id="ynkVinStatus">Decodifica el VIN para autocompletar marca, modelo y año.</span>
|
||
</div>
|
||
|
||
<div class="inv-field">
|
||
<label for="ynkDonorMake">Marca del donador</label>
|
||
<input type="text" id="ynkDonorMake" placeholder="Ej: Nissan" autocomplete="off" />
|
||
</div>
|
||
<div class="inv-field">
|
||
<label for="ynkDonorModel">Modelo del donador</label>
|
||
<input type="text" id="ynkDonorModel" placeholder="Ej: Sentra" autocomplete="off" />
|
||
</div>
|
||
<div class="inv-field">
|
||
<label for="ynkDonorYear">Año del donador</label>
|
||
<input type="number" id="ynkDonorYear" min="1900" max="2100" placeholder="2015" />
|
||
</div>
|
||
<div class="inv-field">
|
||
<label for="ynkDonorKm">Km del donador</label>
|
||
<input type="number" id="ynkDonorKm" min="0" placeholder="120000" />
|
||
</div>
|
||
|
||
<!-- Inventory details -->
|
||
<div class="inv-field inv-field--full" style="border-top:1px solid var(--color-border);padding-top:var(--space-3);">
|
||
<label>Inventario</label>
|
||
</div>
|
||
|
||
<div class="inv-field">
|
||
<label for="ynkOwnership">Propiedad</label>
|
||
<select id="ynkOwnership">
|
||
<option value="propio" selected>Propio</option>
|
||
<option value="consigna">Consigna (de tercero)</option>
|
||
</select>
|
||
</div>
|
||
<div class="inv-field" id="ynkConsignmentWrap" style="display:none;">
|
||
<label for="ynkConsignmentId">Consignación</label>
|
||
<select id="ynkConsignmentId">
|
||
<option value="">— Selecciona consignante —</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div class="inv-field">
|
||
<label for="ynkLocation">Ubicación</label>
|
||
<input type="text" id="ynkLocation" placeholder="Ej: YON-A-3-2" autocomplete="off" />
|
||
</div>
|
||
<div class="inv-field">
|
||
<label for="ynkWarranty">Cobertura (días)</label>
|
||
<input type="number" id="ynkWarranty" min="0" placeholder="30" />
|
||
<span class="ynk-hint">Se guarda como cobertura por defecto del módulo.</span>
|
||
</div>
|
||
<div class="inv-field">
|
||
<label for="ynkInitialStock">Stock inicial</label>
|
||
<input type="number" id="ynkInitialStock" min="0" placeholder="1" value="1" />
|
||
</div>
|
||
<div class="inv-field">
|
||
<label for="ynkSerial">No. de serie (opcional)</label>
|
||
<input type="text" id="ynkSerial" placeholder="Serie de la pieza" autocomplete="off" />
|
||
</div>
|
||
|
||
<!-- Photos -->
|
||
<div class="inv-field inv-field--full">
|
||
<label>Fotos de la pieza</label>
|
||
<div class="ynk-drop">
|
||
<input type="file" id="ynkPhotos" accept="image/jpeg,image/png,image/webp" multiple style="display:none;" />
|
||
<button class="btn btn--ghost btn--sm" type="button" onclick="document.getElementById('ynkPhotos').click()">
|
||
<svg viewBox="0 0 24 24"><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/></svg>
|
||
Elegir fotos
|
||
</button>
|
||
<button class="btn btn--ghost btn--sm" type="button" id="ynkGradeSuggestBtn" onclick="ynkSuggestGradeFromPhoto()">
|
||
Sugerir grado por foto (IA)
|
||
</button>
|
||
<span class="ynk-hint" id="ynkPhotoCount" style="display:block;margin-top:6px;">Sin fotos seleccionadas</span>
|
||
<span class="ynk-hint" id="ynkGradeSuggestStatus" style="display:block;margin-top:4px;"></span>
|
||
</div>
|
||
<div class="ynk-photo-grid" id="ynkPhotoPreview"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="ynk-save-result" id="ynkSaveResult"></div>
|
||
|
||
<div style="display:flex;justify-content:flex-end;gap:var(--space-3);margin-top:var(--space-4);border-top:1px solid var(--color-border);padding-top:var(--space-4);">
|
||
<button class="btn btn--ghost" type="button" onclick="ynkResetPieceForm()">Limpiar</button>
|
||
<button class="btn btn--primary" type="button" id="ynkSaveBtn" onclick="ynkSubmitPiece()">Guardar pieza</button>
|
||
</div>
|
||
</div><!-- /#ynkNuevaSimple -->
|
||
|
||
<!-- ── Alta por donador completo (varias piezas) ────────────── -->
|
||
<div id="ynkNuevaFull" style="display:none;">
|
||
<p class="ynk-hint" style="margin-bottom:var(--space-3);">
|
||
Registra el vehículo donador y todas sus piezas de una sola vez. Cada fila crea una pieza usada con su SKU.
|
||
</p>
|
||
|
||
<div class="inv-form-grid">
|
||
<div class="inv-field inv-field--full">
|
||
<label for="ynkFullVin">VIN del donador *</label>
|
||
<div style="display:flex;gap:var(--space-2);">
|
||
<input type="text" id="ynkFullVin" placeholder="17 caracteres" maxlength="20" autocomplete="off" style="flex:1;text-transform:uppercase;" />
|
||
<button class="btn btn--secondary" type="button" onclick="ynkFullDecodeVin()" id="ynkFullDecodeBtn">Decodificar</button>
|
||
</div>
|
||
<span class="ynk-hint" id="ynkFullVinStatus">Decodifica el VIN para autocompletar marca, modelo y año.</span>
|
||
</div>
|
||
|
||
<div class="inv-field">
|
||
<label for="ynkFullMake">Marca del donador *</label>
|
||
<input type="text" id="ynkFullMake" placeholder="Ej: Nissan" autocomplete="off" />
|
||
</div>
|
||
<div class="inv-field">
|
||
<label for="ynkFullModel">Modelo del donador *</label>
|
||
<input type="text" id="ynkFullModel" placeholder="Ej: Sentra" autocomplete="off" />
|
||
</div>
|
||
<div class="inv-field">
|
||
<label for="ynkFullYear">Año *</label>
|
||
<input type="number" id="ynkFullYear" min="1900" max="2100" placeholder="2015" />
|
||
</div>
|
||
<div class="inv-field">
|
||
<label for="ynkFullMileage">Kilometraje</label>
|
||
<input type="number" id="ynkFullMileage" min="0" placeholder="120000" />
|
||
</div>
|
||
<div class="inv-field">
|
||
<label for="ynkFullColor">Color</label>
|
||
<input type="text" id="ynkFullColor" placeholder="Ej: Gris" autocomplete="off" />
|
||
</div>
|
||
<div class="inv-field">
|
||
<label for="ynkFullOwnership">Propiedad</label>
|
||
<select id="ynkFullOwnership">
|
||
<option value="propio" selected>Propio</option>
|
||
<option value="consigna">Consigna (de tercero)</option>
|
||
</select>
|
||
</div>
|
||
<div class="inv-field" id="ynkFullConsignmentWrap" style="display:none;">
|
||
<label for="ynkFullConsignmentId">Consignación</label>
|
||
<select id="ynkFullConsignmentId">
|
||
<option value="">— Selecciona consignante —</option>
|
||
</select>
|
||
</div>
|
||
<div class="inv-field inv-field--full">
|
||
<label for="ynkFullTotalCost">Costo total del vehículo (opcional)</label>
|
||
<input type="number" id="ynkFullTotalCost" step="0.01" min="0" placeholder="0.00" />
|
||
<span class="ynk-hint">Si lo capturas, se prorratea entre las piezas del donador.</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div style="display:flex;justify-content:space-between;align-items:center;margin:var(--space-4) 0 var(--space-2);gap:var(--space-3);flex-wrap:wrap;">
|
||
<h4 style="margin:0;font-size:var(--text-caption);letter-spacing:var(--tracking-widest);text-transform:uppercase;color:var(--color-text-muted);">Piezas del donador</h4>
|
||
<div style="display:flex;gap:var(--space-2);">
|
||
<button class="btn btn--ghost btn--sm" type="button" onclick="ynkFullAddRow()">+ Agregar fila</button>
|
||
<button class="btn btn--ghost btn--sm" type="button" onclick="ynkFullClearRows()">Vaciar filas</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="table-wrapper">
|
||
<table class="data-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Nombre de la pieza *</th>
|
||
<th style="width:130px;">Precio *</th>
|
||
<th style="width:130px;">Costo</th>
|
||
<th style="width:110px;">Grado</th>
|
||
<th style="width:150px;">Ubicación</th>
|
||
<th style="width:70px;"></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="ynkFullPiecesBody"></tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<div class="ynk-save-result" id="ynkFullResult"></div>
|
||
|
||
<div style="display:flex;justify-content:flex-end;gap:var(--space-3);margin-top:var(--space-4);border-top:1px solid var(--color-border);padding-top:var(--space-4);">
|
||
<button class="btn btn--ghost" type="button" onclick="ynkFullResetForm()">Limpiar piezas</button>
|
||
<button class="btn btn--primary" type="button" id="ynkFullSaveBtn" onclick="ynkSubmitDonorFull()">Registrar donador y piezas</button>
|
||
</div>
|
||
</div><!-- /#ynkNuevaFull -->
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ===================================================================
|
||
TAB 3 — POR DONADOR
|
||
=================================================================== -->
|
||
<div class="tab-panel" id="panel-donador" role="tabpanel">
|
||
|
||
<div class="toolbar">
|
||
<div class="ynk-seg" role="tablist" aria-label="Vistas por donador">
|
||
<button type="button" id="ynkSegDonors" class="is-active" onclick="ynkSetDonorView('donors')">¿Qué tengo de este auto?</button>
|
||
<button type="button" id="ynkSegPieces" onclick="ynkSetDonorView('pieces')">Piezas por donador</button>
|
||
</div>
|
||
<div class="toolbar__spacer"></div>
|
||
<button class="btn btn--ghost btn--sm" onclick="ynkSearchDonors(1)">Actualizar</button>
|
||
</div>
|
||
|
||
<!-- View A: donors summary -->
|
||
<div id="ynkDonorViewDonors">
|
||
<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="ynkDonorQ" placeholder="Buscar por VIN, marca o modelo del donador…" autocomplete="off" />
|
||
</div>
|
||
<span class="ynk-hint">Agrupa las piezas registradas por vehículo donador.</span>
|
||
</div>
|
||
|
||
<div class="table-wrapper">
|
||
<table class="data-table">
|
||
<thead>
|
||
<tr>
|
||
<th>VIN</th>
|
||
<th>Marca</th>
|
||
<th>Modelo</th>
|
||
<th>Año</th>
|
||
<th style="text-align:right">Piezas</th>
|
||
<th style="text-align:right">Valor</th>
|
||
<th>Acciones</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="ynkDonorsBody">
|
||
<tr><td colspan="7" class="ynk-empty">Cargando donadores…</td></tr>
|
||
</tbody>
|
||
</table>
|
||
<div class="table-footer">
|
||
<span id="ynkDonorsFooter">—</span>
|
||
<div class="pagination" id="ynkDonorsPagination"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- View B: pieces of a donor -->
|
||
<div id="ynkDonorViewPieces" style="display:none;">
|
||
<div class="toolbar">
|
||
<input type="text" class="select-filter" id="ynkDonorMake" placeholder="Marca (donador)" autocomplete="off" style="width:150px;" />
|
||
<input type="text" class="select-filter" id="ynkDonorModel" placeholder="Modelo (donador)" autocomplete="off" style="width:150px;" />
|
||
<input type="number" class="select-filter" id="ynkDonorYear" placeholder="Año" min="1900" max="2100" style="width:100px;" />
|
||
<input type="text" class="select-filter" id="ynkDonorVin" placeholder="VIN donador" autocomplete="off" style="width:190px;text-transform:uppercase;" />
|
||
<input type="text" class="select-filter" id="ynkDonorPieceQ" placeholder="Nombre / No. parte" autocomplete="off" style="width:170px;" />
|
||
<button class="btn btn--primary btn--sm" onclick="ynkSearchDonorPieces(1)">
|
||
<svg viewBox="0 0 24 24"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
|
||
Buscar
|
||
</button>
|
||
<button class="btn btn--ghost btn--sm" onclick="ynkClearDonorPieceFilters()">Limpiar</button>
|
||
</div>
|
||
|
||
<div class="table-wrapper">
|
||
<table class="data-table">
|
||
<thead>
|
||
<tr>
|
||
<th style="width:60px;">Foto</th>
|
||
<th>No. Parte</th>
|
||
<th>Nombre</th>
|
||
<th>Grado</th>
|
||
<th>Condición</th>
|
||
<th>Donador</th>
|
||
<th style="text-align:right">Precio</th>
|
||
<th style="text-align:right">Stock</th>
|
||
<th>Ubicación</th>
|
||
<th>Acciones</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="ynkDonorPiecesBody">
|
||
<tr><td colspan="10" class="ynk-empty">Aplica un filtro o presiona Buscar.</td></tr>
|
||
</tbody>
|
||
</table>
|
||
<div class="table-footer">
|
||
<span id="ynkDonorPiecesFooter">—</span>
|
||
<div class="pagination" id="ynkDonorPiecesPagination"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ===================================================================
|
||
TAB 4 — CONSIGNACIONES
|
||
=================================================================== -->
|
||
<div class="tab-panel" id="panel-consignaciones" 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="ynkConsignQ" placeholder="Filtrar por consignante, RFC o teléfono…" autocomplete="off" />
|
||
</div>
|
||
<div class="toolbar__spacer"></div>
|
||
<button class="btn btn--ghost btn--sm" onclick="ynkLoadConsignments()">Actualizar</button>
|
||
<button class="btn btn--primary btn--sm" onclick="ynkOpenConsignmentModal()">
|
||
<svg viewBox="0 0 24 24"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
|
||
Nueva consigna
|
||
</button>
|
||
</div>
|
||
|
||
<div class="table-wrapper">
|
||
<table class="data-table">
|
||
<thead>
|
||
<tr>
|
||
<th>#</th>
|
||
<th>Consignante</th>
|
||
<th>RFC</th>
|
||
<th>Teléfono</th>
|
||
<th style="text-align:right">Comisión</th>
|
||
<th style="text-align:right">Piezas</th>
|
||
<th style="text-align:right">Vendido</th>
|
||
<th style="text-align:right">Comisión $</th>
|
||
<th>Estado</th>
|
||
<th>Creada</th>
|
||
<th>Acciones</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="ynkConsignBody">
|
||
<tr><td colspan="11" class="ynk-empty">Cargando consignaciones…</td></tr>
|
||
</tbody>
|
||
</table>
|
||
<div class="table-footer">
|
||
<span id="ynkConsignFooter">—</span>
|
||
<div class="pagination"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ===================================================================
|
||
TAB 5 — REPORTES
|
||
=================================================================== -->
|
||
<div class="tab-panel" id="panel-reportes" role="tabpanel">
|
||
|
||
<div class="toolbar">
|
||
<div class="ynk-seg" role="tablist" aria-label="Reportes de Yonke">
|
||
<button type="button" id="ynkRepSegAging" class="is-active" onclick="ynkSetReportView('aging')">Antigüedad en patio</button>
|
||
<button type="button" id="ynkRepSegSummary" onclick="ynkSetReportView('summary')">Resumen</button>
|
||
<button type="button" id="ynkRepSegDup" onclick="ynkSetReportView('duplicates')">Duplicados</button>
|
||
</div>
|
||
<div class="toolbar__spacer"></div>
|
||
</div>
|
||
|
||
<!-- Aging -->
|
||
<div id="ynkRepAging">
|
||
<div class="toolbar">
|
||
<label class="ynk-hint" for="ynkAgingDays">Días mínimos en patio:</label>
|
||
<input type="number" class="select-filter" id="ynkAgingDays" value="90" min="0" style="width:100px;" />
|
||
<button class="btn btn--primary btn--sm" onclick="ynkLoadAging()">Consultar</button>
|
||
<div class="toolbar__spacer"></div>
|
||
<span class="ynk-hint" id="ynkAgingSummary"></span>
|
||
</div>
|
||
<div class="table-wrapper">
|
||
<table class="data-table">
|
||
<thead>
|
||
<tr>
|
||
<th>ID</th>
|
||
<th>No. Parte</th>
|
||
<th>Nombre</th>
|
||
<th>Grado</th>
|
||
<th style="text-align:right">Precio</th>
|
||
<th style="text-align:right">Costo</th>
|
||
<th style="text-align:right">Días en patio</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="ynkAgingBody">
|
||
<tr><td colspan="7" class="ynk-empty">Presiona "Consultar" para ver las piezas con más tiempo en patio.</td></tr>
|
||
</tbody>
|
||
</table>
|
||
<div class="table-footer">
|
||
<span id="ynkAgingFooter">—</span>
|
||
<div class="pagination" id="ynkAgingPagination"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Summary -->
|
||
<div id="ynkRepSummary" style="display:none;">
|
||
<div class="toolbar">
|
||
<button class="btn btn--primary btn--sm" onclick="ynkLoadReportSummary()">Cargar resumen</button>
|
||
<div class="toolbar__spacer"></div>
|
||
<span class="ynk-hint" id="ynkRepSummaryUpdated"></span>
|
||
</div>
|
||
<div id="ynkRepSummaryBody" class="ynk-rep-grid">
|
||
<p class="ynk-hint">Presiona "Cargar resumen".</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Duplicates -->
|
||
<div id="ynkRepDup" style="display:none;">
|
||
<div class="toolbar">
|
||
<label class="ynk-hint" for="ynkDupThreshold">Umbral de similitud (0–1):</label>
|
||
<input type="number" class="select-filter" id="ynkDupThreshold" value="0.9" step="0.05" min="0" max="1" style="width:100px;" />
|
||
<button class="btn btn--primary btn--sm" onclick="ynkLoadDuplicates()">Buscar duplicados</button>
|
||
<div class="toolbar__spacer"></div>
|
||
<span class="ynk-hint">Coincidencias por nombre / No. de parte.</span>
|
||
</div>
|
||
<div class="table-wrapper">
|
||
<table class="data-table">
|
||
<thead id="ynkDupHead">
|
||
<tr><th>Grupo</th><th>Piezas</th><th>Detalle</th></tr>
|
||
</thead>
|
||
<tbody id="ynkDupBody">
|
||
<tr><td colspan="3" class="ynk-empty">Presiona "Buscar duplicados".</td></tr>
|
||
</tbody>
|
||
</table>
|
||
<div class="table-footer">
|
||
<span id="ynkDupFooter">—</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<!-- ===================================================================
|
||
TAB 6 — MAYOREO B2B
|
||
=================================================================== -->
|
||
<div class="tab-panel" id="panel-b2b" role="tabpanel">
|
||
|
||
<div class="toolbar">
|
||
<div class="ynk-seg" role="tablist" aria-label="Mayoreo B2B">
|
||
<button type="button" id="ynkB2bSegCatalog" class="is-active" aria-selected="true" onclick="ynkB2bView('catalog')" title="Comprar piezas usadas al mayoreo de otros negocios">Catálogo</button>
|
||
<button type="button" id="ynkB2bSegListings" aria-selected="false" onclick="ynkB2bView('listings')" title="Vender tus piezas usadas a otros negocios">Mis publicaciones</button>
|
||
<button type="button" id="ynkB2bSegOrders" aria-selected="false" onclick="ynkB2bView('orders')" title="Pedidos de mayoreo que recibes y que haces">Pedidos</button>
|
||
</div>
|
||
<div class="toolbar__spacer"></div>
|
||
<button class="btn btn--ghost btn--sm" onclick="ynkB2bRefresh()">
|
||
<svg viewBox="0 0 24 24"><polyline points="23 4 23 10 17 10"/><path d="M20.49 15a9 9 0 1 1-.38-4.93"/></svg>
|
||
Actualizar
|
||
</button>
|
||
</div>
|
||
|
||
<div class="ynk-b2b-help" role="note">
|
||
<strong>¿Cómo funciona el mayoreo?</strong>
|
||
Publicas una pieza usada en <em>Mis publicaciones</em> → otro negocio la compra en el <em>Catálogo</em> →
|
||
tú <strong>confirmas</strong> el pedido y lo marcas como <strong>enviado</strong> → el comprador lo marca como <strong>recibido</strong>.
|
||
En <em>Pedidos › Recibidos</em> gestionas lo que te compran; en <em>Pedidos › Mis compras</em>, lo que tú compras.
|
||
</div>
|
||
|
||
<!-- ── Sub-vista: Catálogo (comprar) ─────────────────────────── -->
|
||
<div id="ynkB2bCatalog">
|
||
<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="ynkB2bQ" placeholder="Buscar pieza, No. parte o vendedor…" autocomplete="off" />
|
||
</div>
|
||
<input type="text" class="select-filter" id="ynkB2bMake" placeholder="Marca donador" autocomplete="off" style="width:140px;" />
|
||
<input type="text" class="select-filter" id="ynkB2bModel" placeholder="Modelo donador" autocomplete="off" style="width:140px;" />
|
||
<input type="number" class="select-filter" id="ynkB2bYear" placeholder="Año" min="1900" max="2100" style="width:95px;" />
|
||
<select class="select-filter" id="ynkB2bGrade">
|
||
<option value="">Todo grado</option>
|
||
<option value="A">Grado A</option>
|
||
<option value="B">Grado B</option>
|
||
<option value="C">Grado C</option>
|
||
<option value="D">Grado D</option>
|
||
</select>
|
||
<button class="btn btn--primary btn--sm" onclick="loadB2bCatalogUi()">
|
||
<svg viewBox="0 0 24 24"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
|
||
Buscar
|
||
</button>
|
||
<button class="btn btn--ghost btn--sm" onclick="ynkB2bClearCatalogFilters()">Limpiar</button>
|
||
</div>
|
||
|
||
<div id="ynkB2bCatalogGrid" class="ynk-b2b-grid">
|
||
<p class="ynk-empty">Cargando catálogo…</p>
|
||
</div>
|
||
<div class="table-footer">
|
||
<span id="ynkB2bCatalogFooter">—</span>
|
||
<div class="pagination" id="ynkB2bCatalogPagination"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ── Sub-vista: Mis publicaciones (vender) ─────────────────── -->
|
||
<div id="ynkB2bListings" style="display:none;">
|
||
<div class="toolbar">
|
||
<span class="ynk-hint" id="ynkB2bSellerStatus"></span>
|
||
<div class="toolbar__spacer"></div>
|
||
<button class="btn btn--primary btn--sm" id="ynkB2bPublishBtn" onclick="ynkB2bOpenPublish()">
|
||
<svg viewBox="0 0 24 24"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
|
||
Publicar pieza en mayoreo
|
||
</button>
|
||
</div>
|
||
|
||
<div class="table-wrapper">
|
||
<table class="data-table">
|
||
<thead>
|
||
<tr>
|
||
<th style="width:60px;">Foto</th>
|
||
<th>No. Parte</th>
|
||
<th>Nombre</th>
|
||
<th style="text-align:center;">Grado</th>
|
||
<th>Donador</th>
|
||
<th style="text-align:right;">Precio mayoreo</th>
|
||
<th>Estado</th>
|
||
<th>Creada</th>
|
||
<th>Acciones</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="ynkB2bListingsBody">
|
||
<tr><td colspan="9" class="ynk-empty">Cargando publicaciones…</td></tr>
|
||
</tbody>
|
||
</table>
|
||
<div class="table-footer">
|
||
<span id="ynkB2bListingsFooter">—</span>
|
||
<div class="pagination"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ── Sub-vista: Pedidos ────────────────────────────────────── -->
|
||
<div id="ynkB2bOrders" style="display:none;">
|
||
<div class="toolbar">
|
||
<div class="ynk-seg" role="tablist" aria-label="Pedidos de mayoreo">
|
||
<button type="button" id="ynkB2bSegIncoming" class="is-active" aria-selected="true" onclick="ynkB2bOrdersView('incoming')" title="Pedidos que otros negocios te hacen">Recibidos</button>
|
||
<button type="button" id="ynkB2bSegPurchases" aria-selected="false" onclick="ynkB2bOrdersView('purchases')" title="Pedidos que tú hiciste a otros negocios">Mis compras</button>
|
||
</div>
|
||
<div class="toolbar__spacer"></div>
|
||
<span class="ynk-hint" id="ynkB2bOrdersHint"></span>
|
||
</div>
|
||
|
||
<div class="table-wrapper">
|
||
<table class="data-table">
|
||
<thead>
|
||
<tr>
|
||
<th>#</th>
|
||
<th id="ynkB2bOrdersCounterpartyHead">Comprador</th>
|
||
<th>Piezas</th>
|
||
<th style="text-align:right;">Total</th>
|
||
<th>Estado</th>
|
||
<th>Creado</th>
|
||
<th>Acciones</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="ynkB2bOrdersBody">
|
||
<tr><td colspan="7" class="ynk-empty">Cargando pedidos…</td></tr>
|
||
</tbody>
|
||
</table>
|
||
<div class="table-footer">
|
||
<span id="ynkB2bOrdersFooter">—</span>
|
||
<div class="pagination"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</div><!-- /panel-b2b -->
|
||
|
||
</div><!-- /tab-panels -->
|
||
</main>
|
||
</div><!-- /app-shell -->
|
||
|
||
<!-- ══════════ Modal: nueva consignación ══════════ -->
|
||
<div class="inv-modal-overlay" id="ynkConsignModal">
|
||
<div class="inv-modal">
|
||
<div class="inv-modal__header">
|
||
<h3>Nueva consignación</h3>
|
||
<button class="inv-modal__close" onclick="ynkCloseModal('ynkConsignModal')">×</button>
|
||
</div>
|
||
<div class="inv-modal__body">
|
||
<div class="inv-form-grid">
|
||
<div class="inv-field inv-field--full">
|
||
<label for="ynkConsName">Nombre del consignante *</label>
|
||
<input type="text" id="ynkConsName" placeholder="Ej: Juan Pérez / Yonke El Sol" autocomplete="off" />
|
||
</div>
|
||
<div class="inv-field">
|
||
<label for="ynkConsRfc">RFC</label>
|
||
<input type="text" id="ynkConsRfc" placeholder="Opcional" maxlength="13" autocomplete="off" />
|
||
</div>
|
||
<div class="inv-field">
|
||
<label for="ynkConsPhone">Teléfono</label>
|
||
<input type="text" id="ynkConsPhone" placeholder="Opcional" autocomplete="off" />
|
||
</div>
|
||
<div class="inv-field">
|
||
<label for="ynkConsCommission">Comisión (%)</label>
|
||
<input type="number" id="ynkConsCommission" step="0.01" min="0" max="100" placeholder="0.00" />
|
||
</div>
|
||
<div class="inv-field inv-field--full">
|
||
<label for="ynkConsNotes">Notas</label>
|
||
<input type="text" id="ynkConsNotes" placeholder="Notas internas" autocomplete="off" />
|
||
</div>
|
||
</div>
|
||
<div class="ynk-save-result" id="ynkConsResult"></div>
|
||
</div>
|
||
<div class="inv-modal__footer">
|
||
<button class="btn btn--ghost" onclick="ynkCloseModal('ynkConsignModal')">Cancelar</button>
|
||
<button class="btn btn--primary" id="ynkConsCreateBtn" onclick="ynkCreateConsignment()">Crear consigna</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ══════════ Modal: detalle / liquidación de consignación ══════════ -->
|
||
<div class="inv-modal-overlay" id="ynkConsignDetailModal">
|
||
<div class="inv-modal inv-modal--wide">
|
||
<div class="inv-modal__header">
|
||
<h3 id="ynkConsignDetailTitle">Consignación</h3>
|
||
<button class="inv-modal__close" onclick="ynkCloseModal('ynkConsignDetailModal')">×</button>
|
||
</div>
|
||
<div class="inv-modal__body">
|
||
<div id="ynkConsignDetailInfo" class="ynk-hint" style="margin-bottom:var(--space-3);">Cargando…</div>
|
||
|
||
<div class="table-wrapper" style="margin-bottom:var(--space-4);">
|
||
<table class="data-table">
|
||
<thead>
|
||
<tr>
|
||
<th>ID Inv.</th>
|
||
<th>No. Parte</th>
|
||
<th>Pieza</th>
|
||
<th style="text-align:right">Precio acordado</th>
|
||
<th style="text-align:right">Comisión</th>
|
||
<th>Estado</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="ynkConsignItemsBody">
|
||
<tr><td colspan="6" class="ynk-empty">Cargando piezas…</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<div class="ynk-summary-card">
|
||
<h4>Agregar pieza a la consigna</h4>
|
||
<div class="inv-form-grid">
|
||
<div class="inv-field">
|
||
<label for="ynkItemInvId">ID de inventario *</label>
|
||
<input type="number" id="ynkItemInvId" min="1" placeholder="ID de la pieza" />
|
||
</div>
|
||
<div class="inv-field">
|
||
<label for="ynkItemAgreed">Precio acordado *</label>
|
||
<input type="number" id="ynkItemAgreed" step="0.01" min="0" placeholder="0.00" />
|
||
</div>
|
||
<div class="inv-field">
|
||
<label for="ynkItemCommission">Comisión</label>
|
||
<input type="number" id="ynkItemCommission" step="0.01" min="0" placeholder="0.00" />
|
||
</div>
|
||
<div class="inv-field" style="justify-content:flex-end;">
|
||
<button class="btn btn--secondary" type="button" onclick="ynkAddConsignmentItem()">Agregar pieza</button>
|
||
</div>
|
||
</div>
|
||
<div class="ynk-save-result" id="ynkConsItemResult"></div>
|
||
</div>
|
||
|
||
<div id="ynkSettleResult" style="margin-top:var(--space-4);"></div>
|
||
</div>
|
||
<div class="inv-modal__footer">
|
||
<button class="btn btn--ghost" onclick="ynkCloseModal('ynkConsignDetailModal')">Cerrar</button>
|
||
<button class="btn btn--primary" id="ynkSettleBtn" onclick="ynkSettleConsignment()">Liquidar consigna</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ══════════ Modal: editar pieza ══════════ -->
|
||
<div class="inv-modal-overlay" id="ynkEditModal">
|
||
<div class="inv-modal">
|
||
<div class="inv-modal__header">
|
||
<h3>Editar pieza usada</h3>
|
||
<button class="inv-modal__close" onclick="ynkCloseModal('ynkEditModal')">×</button>
|
||
</div>
|
||
<div class="inv-modal__body">
|
||
<input type="hidden" id="ynkEditId" />
|
||
<div class="inv-form-grid">
|
||
<div class="inv-field inv-field--full">
|
||
<label for="ynkEditName">Nombre *</label>
|
||
<input type="text" id="ynkEditName" />
|
||
</div>
|
||
<div class="inv-field">
|
||
<label for="ynkEditPart">No. de parte / SKU</label>
|
||
<input type="text" id="ynkEditPart" disabled />
|
||
<span class="ynk-hint">No editable desde Yonke.</span>
|
||
</div>
|
||
<div class="inv-field">
|
||
<label for="ynkEditBrand">Marca</label>
|
||
<input type="text" id="ynkEditBrand" disabled />
|
||
<span class="ynk-hint">No editable desde Yonke.</span>
|
||
</div>
|
||
<div class="inv-field">
|
||
<label for="ynkEditPrice">Precio *</label>
|
||
<input type="number" id="ynkEditPrice" step="0.01" min="0" />
|
||
</div>
|
||
<div class="inv-field">
|
||
<label for="ynkEditCost">Costo</label>
|
||
<input type="number" id="ynkEditCost" step="0.01" min="0" />
|
||
</div>
|
||
<div class="inv-field">
|
||
<label for="ynkEditCondition">Condición</label>
|
||
<select id="ynkEditCondition">
|
||
<option value="usado">Usado</option>
|
||
<option value="reconstruido">Reconstruido</option>
|
||
<option value="nuevo">Nuevo</option>
|
||
</select>
|
||
</div>
|
||
<div class="inv-field">
|
||
<label for="ynkEditGrade">Grado</label>
|
||
<select id="ynkEditGrade">
|
||
<option value="">Sin grado</option>
|
||
<option value="A">A</option>
|
||
<option value="B">B</option>
|
||
<option value="C">C</option>
|
||
<option value="D">D</option>
|
||
</select>
|
||
</div>
|
||
<div class="inv-field">
|
||
<label for="ynkEditOwnership">Propiedad</label>
|
||
<select id="ynkEditOwnership">
|
||
<option value="propio">Propio</option>
|
||
<option value="consigna">Consigna</option>
|
||
</select>
|
||
</div>
|
||
<div class="inv-field">
|
||
<label for="ynkEditLocation">Ubicación</label>
|
||
<input type="text" id="ynkEditLocation" />
|
||
</div>
|
||
<div class="inv-field inv-field--full">
|
||
<label for="ynkEditVin">VIN del donador</label>
|
||
<input type="text" id="ynkEditVin" style="text-transform:uppercase;" />
|
||
</div>
|
||
<div class="inv-field">
|
||
<label for="ynkEditMake">Marca del donador</label>
|
||
<input type="text" id="ynkEditMake" />
|
||
</div>
|
||
<div class="inv-field">
|
||
<label for="ynkEditModel">Modelo del donador</label>
|
||
<input type="text" id="ynkEditModel" />
|
||
</div>
|
||
<div class="inv-field">
|
||
<label for="ynkEditYear">Año del donador</label>
|
||
<input type="number" id="ynkEditYear" min="1900" max="2100" />
|
||
</div>
|
||
<div class="inv-field">
|
||
<label for="ynkEditKm">Km del donador</label>
|
||
<input type="number" id="ynkEditKm" min="0" />
|
||
</div>
|
||
<div class="inv-field inv-field--full" style="flex-direction:row;align-items:center;gap:var(--space-2);">
|
||
<input type="checkbox" id="ynkEditUnique" style="width:auto;" />
|
||
<label for="ynkEditUnique" style="text-transform:none;letter-spacing:0;font-size:var(--text-body-sm);">Pieza única (1 de 1)</label>
|
||
</div>
|
||
</div>
|
||
<div class="ynk-save-result" id="ynkEditResult"></div>
|
||
</div>
|
||
<div class="inv-modal__footer">
|
||
<button class="btn btn--ghost" onclick="ynkCloseModal('ynkEditModal')">Cancelar</button>
|
||
<button class="btn btn--primary" id="ynkEditSaveBtn" onclick="ynkSaveEditPiece()">Guardar cambios</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ══════════ Modal: fotos de la pieza ══════════ -->
|
||
<div class="inv-modal-overlay" id="ynkImagesModal">
|
||
<div class="inv-modal">
|
||
<div class="inv-modal__header">
|
||
<h3 id="ynkImagesTitle">Fotos de la pieza</h3>
|
||
<button class="inv-modal__close" onclick="ynkCloseModal('ynkImagesModal')">×</button>
|
||
</div>
|
||
<div class="inv-modal__body">
|
||
<div class="ynk-photo-grid" id="ynkImagesGrid"></div>
|
||
<div class="ynk-drop" style="margin-top:var(--space-4);">
|
||
<input type="file" id="ynkImagesInput" accept="image/jpeg,image/png,image/webp" multiple style="display:none;" />
|
||
<button class="btn btn--ghost btn--sm" type="button" onclick="document.getElementById('ynkImagesInput').click()">Agregar fotos</button>
|
||
<span class="ynk-hint" id="ynkImagesStatus" style="display:block;margin-top:6px;"></span>
|
||
</div>
|
||
</div>
|
||
<div class="inv-modal__footer">
|
||
<button class="btn btn--ghost" onclick="ynkCloseModal('ynkImagesModal')">Cerrar</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ══════════ Modal: ficha / acciones de pieza ══════════ -->
|
||
<div class="inv-modal-overlay" id="ynkPieceModal">
|
||
<div class="inv-modal inv-modal--wide">
|
||
<div class="inv-modal__header">
|
||
<h3 id="ynkPieceTitle">Pieza</h3>
|
||
<button class="inv-modal__close" onclick="ynkCloseModal('ynkPieceModal')">×</button>
|
||
</div>
|
||
<div class="inv-modal__body">
|
||
<div id="ynkPieceInfo" class="ynk-hint" style="margin-bottom:var(--space-3);">Cargando…</div>
|
||
|
||
<div class="ynk-summary-card">
|
||
<h4>Publicación y herramientas</h4>
|
||
<div style="display:flex;flex-wrap:wrap;gap:var(--space-2);">
|
||
<button class="btn btn--secondary btn--sm" type="button" onclick="ynkPublishMeli()">Publicar en MeLi</button>
|
||
<button class="btn btn--ghost btn--sm" type="button" onclick="ynkOpenLabel()">Etiqueta</button>
|
||
<button class="btn btn--ghost btn--sm" type="button" onclick="document.getElementById('ynkIdentifyInput').click()">Identificar por foto</button>
|
||
<input type="file" id="ynkIdentifyInput" accept="image/jpeg,image/png,image/webp" style="display:none;" />
|
||
<button class="btn btn--ghost btn--sm" type="button" onclick="ynkSuggestPrice()">Sugerir precio</button>
|
||
<button class="btn btn--ghost btn--sm" type="button" onclick="document.getElementById('ynkValidateInput').click()">Validar foto</button>
|
||
<input type="file" id="ynkValidateInput" accept="image/jpeg,image/png,image/webp" style="display:none;" />
|
||
</div>
|
||
<div class="inv-form-grid" style="margin-top:var(--space-3);">
|
||
<div class="inv-field">
|
||
<label for="ynkMeliCategory">Categoría MeLi (opcional)</label>
|
||
<input type="text" id="ynkMeliCategory" placeholder="Ej: MLM1747" autocomplete="off" />
|
||
</div>
|
||
<div class="inv-field">
|
||
<label for="ynkMeliPrice">Precio MeLi (opcional)</label>
|
||
<input type="number" id="ynkMeliPrice" step="0.01" min="0" placeholder="usa el precio de la pieza" />
|
||
</div>
|
||
<div class="inv-field">
|
||
<label for="ynkMeliStock">Stock MeLi (opcional)</label>
|
||
<input type="number" id="ynkMeliStock" min="0" placeholder="1" />
|
||
</div>
|
||
</div>
|
||
<div class="ynk-save-result" id="ynkPieceActionResult"></div>
|
||
</div>
|
||
|
||
<div class="ynk-summary-card" style="margin-top:var(--space-3);">
|
||
<h4>Compatibilidad — «sirve para»</h4>
|
||
<div id="ynkCompatList"><span class="ynk-hint">Cargando compatibilidad…</span></div>
|
||
<div class="inv-form-grid" style="margin-top:var(--space-3);">
|
||
<div class="inv-field">
|
||
<label for="ynkCompatMake">Marca</label>
|
||
<input type="text" id="ynkCompatMake" placeholder="Ej: Nissan" autocomplete="off" />
|
||
</div>
|
||
<div class="inv-field">
|
||
<label for="ynkCompatModel">Modelo</label>
|
||
<input type="text" id="ynkCompatModel" placeholder="Ej: Sentra" autocomplete="off" />
|
||
</div>
|
||
<div class="inv-field">
|
||
<label for="ynkCompatYear">Año</label>
|
||
<input type="number" id="ynkCompatYear" min="1900" max="2100" placeholder="2015" />
|
||
</div>
|
||
<div class="inv-field">
|
||
<label for="ynkCompatEngine">Motor (opcional)</label>
|
||
<input type="text" id="ynkCompatEngine" placeholder="Ej: 1.8L" autocomplete="off" />
|
||
</div>
|
||
<div class="inv-field" style="justify-content:flex-end;">
|
||
<button class="btn btn--secondary" type="button" onclick="ynkAddCompat()">Agregar compatibilidad</button>
|
||
</div>
|
||
</div>
|
||
<div class="ynk-save-result" id="ynkCompatResult"></div>
|
||
</div>
|
||
|
||
<div class="ynk-summary-card" style="margin-top:var(--space-3);">
|
||
<h4>Fotos</h4>
|
||
<div class="ynk-photo-grid" id="ynkPiecePhotos"></div>
|
||
</div>
|
||
</div>
|
||
<div class="inv-modal__footer">
|
||
<button class="btn btn--ghost" onclick="ynkCloseModal('ynkPieceModal')">Cerrar</button>
|
||
<button class="btn btn--secondary" type="button" onclick="ynkEditFromDetail()">Editar pieza</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ══════════ Modal: estado de cuenta de consignación ══════════ -->
|
||
<div class="inv-modal-overlay" id="ynkStatementModal">
|
||
<div class="inv-modal inv-modal--wide">
|
||
<div class="inv-modal__header">
|
||
<h3 id="ynkStatementTitle">Estado de cuenta</h3>
|
||
<button class="inv-modal__close" onclick="ynkCloseModal('ynkStatementModal')">×</button>
|
||
</div>
|
||
<div class="inv-modal__body">
|
||
<div class="inv-form-grid">
|
||
<div class="inv-field">
|
||
<label for="ynkStmtFrom">Desde</label>
|
||
<input type="date" id="ynkStmtFrom" />
|
||
</div>
|
||
<div class="inv-field">
|
||
<label for="ynkStmtTo">Hasta</label>
|
||
<input type="date" id="ynkStmtTo" />
|
||
</div>
|
||
<div class="inv-field" style="flex-direction:row;gap:var(--space-2);align-items:flex-end;">
|
||
<button class="btn btn--primary" type="button" onclick="ynkLoadStatement()">Consultar</button>
|
||
<button class="btn btn--ghost" type="button" onclick="ynkPrintStatement()">Imprimir</button>
|
||
</div>
|
||
</div>
|
||
<div id="ynkStatementInfo" class="ynk-hint" style="margin:var(--space-3) 0;"></div>
|
||
<div id="ynkStatementBody">
|
||
<p class="ynk-hint">Selecciona un periodo y presiona "Consultar".</p>
|
||
</div>
|
||
</div>
|
||
<div class="inv-modal__footer">
|
||
<button class="btn btn--ghost" onclick="ynkCloseModal('ynkStatementModal')">Cerrar</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ══════════ Modal: importar CSV de piezas ══════════ -->
|
||
<div class="inv-modal-overlay" id="ynkImportModal">
|
||
<div class="inv-modal">
|
||
<div class="inv-modal__header">
|
||
<h3>Importar piezas desde CSV</h3>
|
||
<button class="inv-modal__close" onclick="ynkCloseModal('ynkImportModal')">×</button>
|
||
</div>
|
||
<div class="inv-modal__body">
|
||
<p class="ynk-hint" style="margin-bottom:var(--space-3);">
|
||
Encabezados sugeridos: <code>name, price_1, cost, condition_grade, location, part_number, donor_vin, donor_make, donor_model, donor_year</code>.
|
||
La respuesta del servidor se muestra abajo (creadas y errores por fila).
|
||
</p>
|
||
<div class="ynk-drop">
|
||
<input type="file" id="ynkImportFile" accept=".csv,text/csv" style="display:none;" />
|
||
<button class="btn btn--ghost btn--sm" type="button" onclick="document.getElementById('ynkImportFile').click()">Elegir archivo CSV</button>
|
||
<span class="ynk-hint" id="ynkImportFileName" style="display:block;margin-top:6px;">Sin archivo seleccionado</span>
|
||
</div>
|
||
<div class="ynk-save-result" id="ynkImportResult"></div>
|
||
</div>
|
||
<div class="inv-modal__footer">
|
||
<button class="btn btn--ghost" onclick="ynkCloseModal('ynkImportModal')">Cancelar</button>
|
||
<button class="btn btn--primary" id="ynkImportBtn" onclick="ynkSubmitImport()">Importar</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ══════════ Modal: comprar en mayoreo ══════════ -->
|
||
<div class="inv-modal-overlay" id="ynkB2bBuyModal">
|
||
<div class="inv-modal">
|
||
<div class="inv-modal__header">
|
||
<h3 id="ynkB2bBuyTitle">Comprar en mayoreo</h3>
|
||
<button class="inv-modal__close" onclick="ynkCloseModal('ynkB2bBuyModal')">×</button>
|
||
</div>
|
||
<div class="inv-modal__body">
|
||
<div id="ynkB2bBuyInfo" class="ynk-hint" style="margin-bottom:var(--space-3);"></div>
|
||
<div class="inv-form-grid">
|
||
<div class="inv-field">
|
||
<label for="ynkB2bBuyQty">Cantidad *</label>
|
||
<input type="number" id="ynkB2bBuyQty" min="1" step="1" value="1" oninput="ynkB2bUpdateBuyTotal()" />
|
||
</div>
|
||
<div class="inv-field">
|
||
<label>Total estimado</label>
|
||
<div id="ynkB2bBuyTotal" style="font-family:var(--font-mono);font-weight:700;color:var(--color-primary);font-size:1.05rem;">$0.00</div>
|
||
</div>
|
||
<div class="inv-field inv-field--full">
|
||
<label for="ynkB2bBuyNotes">Notas para el vendedor</label>
|
||
<input type="text" id="ynkB2bBuyNotes" placeholder="Opcional" autocomplete="off" />
|
||
</div>
|
||
</div>
|
||
<div class="ynk-save-result" id="ynkB2bBuyResult"></div>
|
||
</div>
|
||
<div class="inv-modal__footer">
|
||
<button class="btn btn--ghost" onclick="ynkCloseModal('ynkB2bBuyModal')">Cerrar</button>
|
||
<button class="btn btn--primary" id="ynkB2bBuyBtn" onclick="ynkB2bSubmitOrder()">Crear orden</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ══════════ Modal: publicar pieza en mayoreo ══════════ -->
|
||
<div class="inv-modal-overlay" id="ynkB2bPublishModal">
|
||
<div class="inv-modal">
|
||
<div class="inv-modal__header">
|
||
<h3>Publicar pieza en mayoreo</h3>
|
||
<button class="inv-modal__close" onclick="ynkCloseModal('ynkB2bPublishModal')">×</button>
|
||
</div>
|
||
<div class="inv-modal__body">
|
||
<div class="inv-form-grid">
|
||
<div class="inv-field inv-field--full">
|
||
<label for="ynkB2bPublishPiece">Pieza usada del inventario *</label>
|
||
<select id="ynkB2bPublishPiece">
|
||
<option value="">Cargando piezas…</option>
|
||
</select>
|
||
<span class="ynk-hint">La pieza se ofrece a otros negocios al precio de mayoreo que definas.</span>
|
||
</div>
|
||
<div class="inv-field">
|
||
<label for="ynkB2bPublishPrice">Precio mayoreo *</label>
|
||
<input type="number" id="ynkB2bPublishPrice" step="0.01" min="0" placeholder="0.00" />
|
||
</div>
|
||
<div class="inv-field">
|
||
<label for="ynkB2bPublishNotes">Notas</label>
|
||
<input type="text" id="ynkB2bPublishNotes" placeholder="Opcional" autocomplete="off" />
|
||
</div>
|
||
</div>
|
||
<div class="ynk-save-result" id="ynkB2bPublishResult"></div>
|
||
</div>
|
||
<div class="inv-modal__footer">
|
||
<button class="btn btn--ghost" onclick="ynkCloseModal('ynkB2bPublishModal')">Cancelar</button>
|
||
<button class="btn btn--primary" id="ynkB2bPublishSubmitBtn" onclick="ynkB2bSubmitPublish()">Publicar</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ══════════ Modal: detalle de pedido mayoreo ══════════ -->
|
||
<div class="inv-modal-overlay" id="ynkB2bOrderModal">
|
||
<div class="inv-modal inv-modal--wide">
|
||
<div class="inv-modal__header">
|
||
<h3 id="ynkB2bOrderTitle">Pedido</h3>
|
||
<button class="inv-modal__close" onclick="ynkCloseModal('ynkB2bOrderModal')">×</button>
|
||
</div>
|
||
<div class="inv-modal__body">
|
||
<div id="ynkB2bOrderBody" class="ynk-hint">Cargando pedido…</div>
|
||
<div id="ynkB2bOrderActions" class="ynk-b2b-actions" style="margin-top:var(--space-4);"></div>
|
||
</div>
|
||
<div class="inv-modal__footer">
|
||
<button class="btn btn--ghost" onclick="ynkCloseModal('ynkB2bOrderModal')">Cerrar</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Offline Banner -->
|
||
<div id="offlineBanner" class="banner banner--warning" style="display:none;position:fixed;top:0;left:0;right:0;z-index:9999;border-radius:0;animation:none;">
|
||
<span class="banner__icon"></span>
|
||
<span class="banner__text" id="offlineBannerText"><strong>Modo offline</strong> — Funciones limitadas.</span>
|
||
<button class="banner__dismiss" onclick="document.getElementById('offlineBanner').style.display='none'" aria-label="Cerrar">×</button>
|
||
</div>
|
||
|
||
<!-- =========================================================================
|
||
SCRIPTS
|
||
========================================================================= -->
|
||
|
||
<script>
|
||
function setTheme(theme) {
|
||
document.documentElement.setAttribute('data-theme', theme);
|
||
document.querySelectorAll('.theme-btn').forEach(function(btn) {
|
||
btn.classList.toggle('is-active', btn.dataset.themeTarget === theme);
|
||
});
|
||
try { localStorage.setItem('pos_theme', theme); } catch (e) {}
|
||
}
|
||
(function() {
|
||
var saved;
|
||
try { saved = localStorage.getItem('pos_theme'); } catch (e) {}
|
||
if (saved === 'industrial' || saved === 'modern') setTheme(saved);
|
||
})();
|
||
</script>
|
||
|
||
<script src="/pos/static/js/i18n.js?v=39" defer></script>
|
||
<script src="/pos/static/js/app-init.js?v=9" defer></script>
|
||
<script src="/pos/static/js/splash-loader.js?v=33" defer></script>
|
||
<script src="/pos/static/js/pos-utils.js?v=33" defer></script>
|
||
<script src="/pos/static/js/sidebar.js?v=46" defer></script>
|
||
<script src="/pos/static/js/yonke.js?v=3" defer></script>
|
||
<script src="/pos/static/js/offline-banner.js" defer></script>
|
||
<script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script>
|
||
<script src="/pos/static/js/pwa-install.js" defer></script>
|
||
<script src="/pos/static/js/chat.js" defer></script>
|
||
</body>
|
||
</html>
|