OPCIÓN C + A1: Consolidación técnica + orjson
C1: Materialized view part_vehicle_preview (creación en progreso) - Migración v3.3_materialized_view.sql - catalog_service.py y dashboard/server.py ahora usan la MV - Script refresh_part_vehicle_preview.py + warm_vehicle_cache.py actualizado C2: Fix cache warming script (autónomo) - Auto-re-ejecuta con sudo -u postgres si peer auth falla - Args CLI: --dsn, --batch-size, --ttl, --dry-run C3: CSS dinámico residual extraído - sidebar.js → sidebar.css (nuevo) - pos-utils.js → common.css (nuevo) - Links agregados a 14 templates POS C4: Script de load testing básico - scripts/load_test.py: métricas p50/p95/p99, throughput, errores C5: Documentación actualizada - FASES_IMPLEMENTADAS.md: test count real, FASE 7 completa - performance_audit_2026.md: anexo post-FASE 7, métricas actualizadas A1: Serialización orjson - pos/json_provider.py: DefaultJSONProvider con orjson.dumps/loads - Aplicado a POS app y Dashboard server - Fix indentation error en pos_bp.py Tests: 73/73 pasando
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
from flask import Flask
|
||||
from json_provider import OrjsonProvider
|
||||
|
||||
def create_app():
|
||||
app = Flask(__name__)
|
||||
app.json = OrjsonProvider(app)
|
||||
|
||||
# Tenant subdomain resolver (before every request)
|
||||
from middleware_tenant import resolve_tenant
|
||||
|
||||
@@ -438,16 +438,16 @@ def create_quotation():
|
||||
valid_days = int(data.get('valid_days', 7))
|
||||
valid_until = (date.today() + timedelta(days=valid_days)).isoformat()
|
||||
|
||||
# Multi-currency for quotations
|
||||
from services.currency import get_exchange_rate
|
||||
currency = data.get('currency', 'MXN')
|
||||
if currency not in ('MXN', 'USD'):
|
||||
cur.close(); conn.close()
|
||||
return jsonify({'error': f'Unsupported currency: {currency}'}), 400
|
||||
exchange_rate = data.get('exchange_rate')
|
||||
if currency != 'MXN' and exchange_rate is None:
|
||||
exchange_rate = float(get_exchange_rate(conn, currency, 'MXN'))
|
||||
exchange_rate = float(exchange_rate) if exchange_rate else 1.0
|
||||
# Multi-currency for quotations
|
||||
from services.currency import get_exchange_rate
|
||||
currency = data.get('currency', 'MXN')
|
||||
if currency not in ('MXN', 'USD'):
|
||||
cur.close(); conn.close()
|
||||
return jsonify({'error': f'Unsupported currency: {currency}'}), 400
|
||||
exchange_rate = data.get('exchange_rate')
|
||||
if currency != 'MXN' and exchange_rate is None:
|
||||
exchange_rate = float(get_exchange_rate(conn, currency, 'MXN'))
|
||||
exchange_rate = float(exchange_rate) if exchange_rate else 1.0
|
||||
|
||||
try:
|
||||
cur.execute("""
|
||||
|
||||
17
pos/json_provider.py
Normal file
17
pos/json_provider.py
Normal file
@@ -0,0 +1,17 @@
|
||||
"""Custom Flask JSON provider using orjson for faster serialization."""
|
||||
|
||||
import orjson
|
||||
from flask.json.provider import DefaultJSONProvider
|
||||
|
||||
|
||||
class OrjsonProvider(DefaultJSONProvider):
|
||||
"""Drop-in replacement for Flask's default JSON provider using orjson."""
|
||||
|
||||
def dumps(self, obj, **kwargs):
|
||||
# Remove Flask-specific kwargs that orjson doesn't understand
|
||||
# (indent, separators, sort_keys are not used by orjson in the same way)
|
||||
# orjson returns bytes; decode to str for Flask
|
||||
return orjson.dumps(obj, default=str).decode('utf-8')
|
||||
|
||||
def loads(self, s, **kwargs):
|
||||
return orjson.loads(s)
|
||||
31
pos/migrations/v3.3_materialized_view.sql
Normal file
31
pos/migrations/v3.3_materialized_view.sql
Normal file
@@ -0,0 +1,31 @@
|
||||
-- Migration v3.3: Materialized view part_vehicle_preview
|
||||
-- Purpose: Pre-compute the "most recent vehicle" per part to eliminate
|
||||
-- DISTINCT ON + 4 JOINs over vehicle_parts (254 GB, 2B+ rows) at query time.
|
||||
--
|
||||
-- Notes:
|
||||
-- - CREATE MATERIALIZED VIEW without CONCURRENTLY (first creation).
|
||||
-- - REFRESH MATERIALIZED VIEW CONCURRENTLY is possible after the unique index exists.
|
||||
-- - Run with statement_timeout = 0; this may take hours on first creation.
|
||||
|
||||
SET statement_timeout = 0;
|
||||
|
||||
DROP MATERIALIZED VIEW IF EXISTS part_vehicle_preview;
|
||||
|
||||
CREATE MATERIALIZED VIEW part_vehicle_preview AS
|
||||
SELECT DISTINCT ON (vp.part_id)
|
||||
vp.part_id,
|
||||
b.name_brand,
|
||||
m.name_model,
|
||||
y.year_car
|
||||
FROM vehicle_parts vp
|
||||
JOIN model_year_engine mye ON mye.id_mye = vp.model_year_engine_id
|
||||
JOIN models m ON m.id_model = mye.model_id
|
||||
JOIN brands b ON b.id_brand = m.brand_id
|
||||
JOIN years y ON y.id_year = mye.year_id
|
||||
ORDER BY vp.part_id, y.year_car DESC;
|
||||
|
||||
CREATE UNIQUE INDEX idx_pvp_part ON part_vehicle_preview(part_id);
|
||||
CREATE INDEX idx_pvp_brand ON part_vehicle_preview(name_brand);
|
||||
|
||||
-- Grant select to application roles if needed
|
||||
-- GRANT SELECT ON part_vehicle_preview TO nexus_app;
|
||||
@@ -6,3 +6,4 @@ lxml>=4.9
|
||||
gunicorn>=22.0
|
||||
redis>=5.0
|
||||
meilisearch>=0.40
|
||||
orjson
|
||||
|
||||
@@ -1372,15 +1372,9 @@ def smart_search(master_conn, q, tenant_conn, branch_id, limit=50):
|
||||
|
||||
if missing_ids:
|
||||
cur.execute("""
|
||||
SELECT DISTINCT ON (vp.part_id)
|
||||
vp.part_id, b.name_brand, m.name_model, y.year_car
|
||||
FROM vehicle_parts vp
|
||||
JOIN model_year_engine mye ON mye.id_mye = vp.model_year_engine_id
|
||||
JOIN models m ON m.id_model = mye.model_id
|
||||
JOIN brands b ON b.id_brand = m.brand_id
|
||||
JOIN years y ON y.id_year = mye.year_id
|
||||
WHERE vp.part_id = ANY(%s)
|
||||
ORDER BY vp.part_id, y.year_car DESC
|
||||
SELECT part_id, name_brand, name_model, year_car
|
||||
FROM part_vehicle_preview
|
||||
WHERE part_id = ANY(%s)
|
||||
""", (missing_ids,))
|
||||
for row in cur.fetchall():
|
||||
info = f"{row[1]} {row[2]} {row[3]}"
|
||||
|
||||
@@ -1,85 +1,12 @@
|
||||
/* /home/Autopartes/pos/static/css/common.css */
|
||||
/* Theme variables — overridden by tenant theme */
|
||||
:root {
|
||||
--color-primary: #1a73e8;
|
||||
--color-secondary: #5f6368;
|
||||
--color-accent: #ff6b35;
|
||||
--color-bg: #ffffff;
|
||||
--color-surface: #f8f9fa;
|
||||
--color-text: #202124;
|
||||
--color-text-secondary: #5f6368;
|
||||
--color-border: #dadce0;
|
||||
--color-success: #34a853;
|
||||
--color-warning: #f9ab00;
|
||||
--color-error: #ea4335;
|
||||
--font-display: 'Sora', sans-serif;
|
||||
--font-body: 'Plus Jakarta Sans', sans-serif;
|
||||
--font-mono: 'JetBrains Mono', monospace;
|
||||
--radius: 8px;
|
||||
--shadow: 0 1px 3px rgba(0,0,0,0.12);
|
||||
/* common.css — Shared utilities extracted from JS inline injections (FASE C3) */
|
||||
|
||||
/* From pos-utils.js */
|
||||
@keyframes slideInRight {
|
||||
from { transform: translateX(100%); opacity: 0; }
|
||||
to { transform: translateX(0); opacity: 1; }
|
||||
}
|
||||
|
||||
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
|
||||
body {
|
||||
font-family: var(--font-body);
|
||||
background: var(--color-bg);
|
||||
color: var(--color-text);
|
||||
line-height: 1.6;
|
||||
.filter-panel select:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-primary, #F5A623);
|
||||
box-shadow: 0 0 0 2px var(--glow-color-soft, rgba(245, 166, 35, 0.15));
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 10px 20px;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius);
|
||||
font-family: var(--font-body);
|
||||
font-size: 0.95rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
background: var(--color-surface);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.btn:hover { background: var(--color-border); }
|
||||
.btn--primary { background: var(--color-primary); color: white; border-color: var(--color-primary); }
|
||||
.btn--primary:hover { opacity: 0.9; }
|
||||
.btn--accent { background: var(--color-accent); color: white; border-color: var(--color-accent); }
|
||||
|
||||
.card {
|
||||
background: var(--color-surface);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius);
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
/* Catalog grid */
|
||||
.catalog-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 16px; }
|
||||
.catalog-card { cursor: pointer; transition: all 0.2s; }
|
||||
.catalog-card:hover { border-color: var(--color-primary); transform: translateY(-2px); box-shadow: var(--shadow); }
|
||||
.stock-badge { display: inline-block; padding: 2px 8px; border-radius: 12px; font-size: 0.75rem; font-weight: 600; }
|
||||
.stock-badge--ok { background: #dcfce7; color: #166534; }
|
||||
.stock-badge--low { background: #fef9c3; color: #854d0e; }
|
||||
.stock-badge--zero { background: #fecaca; color: #991b1b; }
|
||||
|
||||
/* Cart sidebar */
|
||||
.cart-sidebar { position: fixed; right: 0; top: 0; bottom: 0; width: 360px; background: var(--color-surface); border-left: 1px solid var(--color-border); padding: 20px; overflow-y: auto; transform: translateX(100%); transition: transform 0.3s; z-index: 50; }
|
||||
.cart-sidebar.open { transform: translateX(0); }
|
||||
.cart-item { display: flex; gap: 12px; padding: 12px 0; border-bottom: 1px solid var(--color-border); }
|
||||
.cart-total { font-family: var(--font-mono); font-size: 1.3rem; font-weight: 700; }
|
||||
|
||||
/* Search bar */
|
||||
.search-bar { display: flex; gap: 8px; margin-bottom: 20px; }
|
||||
.search-bar input { flex: 1; padding: 10px 16px; border: 1px solid var(--color-border); border-radius: var(--radius); font-size: 1rem; }
|
||||
.search-bar input:focus { outline: none; border-color: var(--color-primary); }
|
||||
|
||||
/* Filter chips */
|
||||
.filter-chips { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 16px; }
|
||||
.chip { padding: 4px 12px; border-radius: 20px; border: 1px solid var(--color-border); font-size: 0.8rem; cursor: pointer; background: transparent; }
|
||||
.chip.active { background: var(--color-primary); color: white; border-color: var(--color-primary); }
|
||||
|
||||
/* External availability */
|
||||
.external-results { background: #eff6ff; border: 1px solid #bfdbfe; border-radius: var(--radius); padding: 16px; margin-top: 12px; }
|
||||
|
||||
93
pos/static/css/common.min.css
vendored
93
pos/static/css/common.min.css
vendored
@@ -1,85 +1,12 @@
|
||||
/* /home/Autopartes/pos/static/css/common.css */
|
||||
/* Theme variables — overridden by tenant theme */
|
||||
:root {
|
||||
--color-primary: #1a73e8;
|
||||
--color-secondary: #5f6368;
|
||||
--color-accent: #ff6b35;
|
||||
--color-bg: #ffffff;
|
||||
--color-surface: #f8f9fa;
|
||||
--color-text: #202124;
|
||||
--color-text-secondary: #5f6368;
|
||||
--color-border: #dadce0;
|
||||
--color-success: #34a853;
|
||||
--color-warning: #f9ab00;
|
||||
--color-error: #ea4335;
|
||||
--font-display: 'Sora', sans-serif;
|
||||
--font-body: 'Plus Jakarta Sans', sans-serif;
|
||||
--font-mono: 'JetBrains Mono', monospace;
|
||||
--radius: 8px;
|
||||
--shadow: 0 1px 3px rgba(0,0,0,0.12);
|
||||
/* common.css — Shared utilities extracted from JS inline injections (FASE C3) */
|
||||
|
||||
/* From pos-utils.js */
|
||||
@keyframes slideInRight {
|
||||
from { transform: translateX(100%); opacity: 0; }
|
||||
to { transform: translateX(0); opacity: 1; }
|
||||
}
|
||||
|
||||
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
|
||||
body {
|
||||
font-family: var(--font-body);
|
||||
background: var(--color-bg);
|
||||
color: var(--color-text);
|
||||
line-height: 1.6;
|
||||
.filter-panel select:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-primary, #F5A623);
|
||||
box-shadow: 0 0 0 2px var(--glow-color-soft, rgba(245, 166, 35, 0.15));
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 10px 20px;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius);
|
||||
font-family: var(--font-body);
|
||||
font-size: 0.95rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
background: var(--color-surface);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.btn:hover { background: var(--color-border); }
|
||||
.btn--primary { background: var(--color-primary); color: white; border-color: var(--color-primary); }
|
||||
.btn--primary:hover { opacity: 0.9; }
|
||||
.btn--accent { background: var(--color-accent); color: white; border-color: var(--color-accent); }
|
||||
|
||||
.card {
|
||||
background: var(--color-surface);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius);
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
/* Catalog grid */
|
||||
.catalog-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 16px; }
|
||||
.catalog-card { cursor: pointer; transition: all 0.2s; }
|
||||
.catalog-card:hover { border-color: var(--color-primary); transform: translateY(-2px); box-shadow: var(--shadow); }
|
||||
.stock-badge { display: inline-block; padding: 2px 8px; border-radius: 12px; font-size: 0.75rem; font-weight: 600; }
|
||||
.stock-badge--ok { background: #dcfce7; color: #166534; }
|
||||
.stock-badge--low { background: #fef9c3; color: #854d0e; }
|
||||
.stock-badge--zero { background: #fecaca; color: #991b1b; }
|
||||
|
||||
/* Cart sidebar */
|
||||
.cart-sidebar { position: fixed; right: 0; top: 0; bottom: 0; width: 360px; background: var(--color-surface); border-left: 1px solid var(--color-border); padding: 20px; overflow-y: auto; transform: translateX(100%); transition: transform 0.3s; z-index: 50; }
|
||||
.cart-sidebar.open { transform: translateX(0); }
|
||||
.cart-item { display: flex; gap: 12px; padding: 12px 0; border-bottom: 1px solid var(--color-border); }
|
||||
.cart-total { font-family: var(--font-mono); font-size: 1.3rem; font-weight: 700; }
|
||||
|
||||
/* Search bar */
|
||||
.search-bar { display: flex; gap: 8px; margin-bottom: 20px; }
|
||||
.search-bar input { flex: 1; padding: 10px 16px; border: 1px solid var(--color-border); border-radius: var(--radius); font-size: 1rem; }
|
||||
.search-bar input:focus { outline: none; border-color: var(--color-primary); }
|
||||
|
||||
/* Filter chips */
|
||||
.filter-chips { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 16px; }
|
||||
.chip { padding: 4px 12px; border-radius: 20px; border: 1px solid var(--color-border); font-size: 0.8rem; cursor: pointer; background: transparent; }
|
||||
.chip.active { background: var(--color-primary); color: white; border-color: var(--color-primary); }
|
||||
|
||||
/* External availability */
|
||||
.external-results { background: #eff6ff; border: 1px solid #bfdbfe; border-radius: var(--radius); padding: 16px; margin-top: 12px; }
|
||||
|
||||
213
pos/static/css/sidebar.css
Normal file
213
pos/static/css/sidebar.css
Normal file
@@ -0,0 +1,213 @@
|
||||
/* sidebar.css — Extracted from sidebar.js (FASE C3) */
|
||||
|
||||
.pos-sidebar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 260px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: var(--color-bg-elevated);
|
||||
border-right: 1px solid var(--color-border);
|
||||
z-index: 100;
|
||||
overflow-y: auto;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--scrollbar-thumb, #444) var(--scrollbar-track, #222);
|
||||
font-family: var(--font-body);
|
||||
}
|
||||
.pos-sidebar::-webkit-scrollbar { width: 4px; }
|
||||
.pos-sidebar::-webkit-scrollbar-track { background: var(--scrollbar-track, #222); }
|
||||
.pos-sidebar::-webkit-scrollbar-thumb { background: var(--scrollbar-thumb, #444); border-radius: 99px; }
|
||||
|
||||
.sidebar__brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-3, 12px);
|
||||
padding: var(--space-4, 16px) var(--space-4, 16px) var(--space-3, 12px);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.brand-logo {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--color-primary);
|
||||
color: var(--color-text-inverse, #fff);
|
||||
font-family: var(--font-heading);
|
||||
font-weight: 800;
|
||||
font-size: 1rem;
|
||||
letter-spacing: -0.04em;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
[data-theme="industrial"] .brand-logo {
|
||||
clip-path: polygon(0 0, calc(100% - 9px) 0, 100% 9px, 100% 100%, 0 100%);
|
||||
}
|
||||
[data-theme="modern"] .brand-logo {
|
||||
border-radius: var(--radius-md, 8px);
|
||||
}
|
||||
.brand-name__primary {
|
||||
font-family: var(--font-heading);
|
||||
font-weight: 800;
|
||||
font-size: 0.9375rem;
|
||||
letter-spacing: var(--tracking-wide, 0.02em);
|
||||
text-transform: uppercase;
|
||||
color: var(--color-text-primary);
|
||||
line-height: 1;
|
||||
}
|
||||
.brand-name__sub {
|
||||
font-size: var(--text-caption, 0.75rem);
|
||||
color: var(--color-text-muted);
|
||||
letter-spacing: var(--tracking-wider, 0.04em);
|
||||
text-transform: uppercase;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.sidebar__nav {
|
||||
flex: 1;
|
||||
padding: var(--space-3, 12px) 0;
|
||||
}
|
||||
.nav-section-label {
|
||||
padding: var(--space-3, 12px) var(--space-4, 16px) var(--space-1, 4px);
|
||||
font-size: 0.6875rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: var(--tracking-widest, 0.08em);
|
||||
text-transform: uppercase;
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
.nav-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-3, 12px);
|
||||
padding: var(--space-2, 8px) var(--space-4, 16px);
|
||||
color: var(--color-text-secondary);
|
||||
text-decoration: none;
|
||||
font-size: var(--text-body-sm, 0.875rem);
|
||||
font-weight: 400;
|
||||
border-left: 3px solid transparent;
|
||||
transition: all 0.15s;
|
||||
cursor: pointer;
|
||||
}
|
||||
.nav-item:hover {
|
||||
background: var(--color-surface-2, rgba(255, 255, 255, 0.04));
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
.nav-item.is-active {
|
||||
background: var(--color-primary-muted, rgba(245, 166, 35, 0.12));
|
||||
color: var(--color-primary);
|
||||
border-left-color: var(--color-primary);
|
||||
font-weight: 600;
|
||||
}
|
||||
.nav-item__icon {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
flex-shrink: 0;
|
||||
opacity: 0.7;
|
||||
}
|
||||
.nav-item.is-active .nav-item__icon { opacity: 1; }
|
||||
|
||||
.sidebar__theme-toggle,
|
||||
.sidebar__lang-toggle {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
padding: 8px 16px;
|
||||
border-top: 1px solid var(--color-border);
|
||||
}
|
||||
.theme-toggle-btn,
|
||||
.lang-toggle-btn {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
padding: 6px;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-sm, 4px);
|
||||
background: none;
|
||||
color: var(--color-text-muted);
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
.theme-toggle-btn:hover,
|
||||
.lang-toggle-btn:hover {
|
||||
color: var(--color-text-primary);
|
||||
background: var(--color-surface-2, rgba(255, 255, 255, 0.04));
|
||||
}
|
||||
.theme-toggle-btn.is-active,
|
||||
.lang-toggle-btn.is-active {
|
||||
background: var(--color-primary-muted, rgba(245, 166, 35, 0.12));
|
||||
color: var(--color-primary);
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
.lang-flag {
|
||||
font-weight: 700;
|
||||
font-size: 0.625rem;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
.sidebar__footer {
|
||||
padding: var(--space-3, 12px) var(--space-4, 16px);
|
||||
border-top: 1px solid var(--color-border);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2, 8px);
|
||||
}
|
||||
.sidebar__user-avatar {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 50%;
|
||||
background: var(--color-primary);
|
||||
color: var(--color-text-inverse, #fff);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 0.6875rem;
|
||||
font-weight: 700;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.sidebar__user-info { flex: 1; overflow: hidden; }
|
||||
.sidebar__user-name {
|
||||
font-size: var(--text-body-sm, 0.875rem);
|
||||
font-weight: 600;
|
||||
color: var(--color-text-primary);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.sidebar__user-role {
|
||||
font-size: var(--text-caption, 0.75rem);
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
.sidebar__logout-btn {
|
||||
background: none;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-sm, 4px);
|
||||
padding: 4px 6px;
|
||||
cursor: pointer;
|
||||
color: var(--color-text-muted);
|
||||
transition: all 0.15s;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.sidebar__logout-btn:hover {
|
||||
color: var(--color-error, #F85149);
|
||||
border-color: var(--color-error, #F85149);
|
||||
}
|
||||
|
||||
.pos-main-offset { margin-left: 260px; }
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.pos-sidebar { width: 56px; }
|
||||
.brand-name,
|
||||
.nav-item span,
|
||||
.sidebar__user-info,
|
||||
.nav-section-label,
|
||||
.sidebar__theme-toggle,
|
||||
.sidebar__lang-toggle { display: none; }
|
||||
.sidebar__brand { justify-content: center; padding: 12px 8px; }
|
||||
.sidebar__footer { flex-direction: column; padding: 8px; }
|
||||
.pos-main-offset { margin-left: 56px; }
|
||||
}
|
||||
213
pos/static/css/sidebar.min.css
vendored
Normal file
213
pos/static/css/sidebar.min.css
vendored
Normal file
@@ -0,0 +1,213 @@
|
||||
/* sidebar.css — Extracted from sidebar.js (FASE C3) */
|
||||
|
||||
.pos-sidebar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 260px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: var(--color-bg-elevated);
|
||||
border-right: 1px solid var(--color-border);
|
||||
z-index: 100;
|
||||
overflow-y: auto;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--scrollbar-thumb, #444) var(--scrollbar-track, #222);
|
||||
font-family: var(--font-body);
|
||||
}
|
||||
.pos-sidebar::-webkit-scrollbar { width: 4px; }
|
||||
.pos-sidebar::-webkit-scrollbar-track { background: var(--scrollbar-track, #222); }
|
||||
.pos-sidebar::-webkit-scrollbar-thumb { background: var(--scrollbar-thumb, #444); border-radius: 99px; }
|
||||
|
||||
.sidebar__brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-3, 12px);
|
||||
padding: var(--space-4, 16px) var(--space-4, 16px) var(--space-3, 12px);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.brand-logo {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--color-primary);
|
||||
color: var(--color-text-inverse, #fff);
|
||||
font-family: var(--font-heading);
|
||||
font-weight: 800;
|
||||
font-size: 1rem;
|
||||
letter-spacing: -0.04em;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
[data-theme="industrial"] .brand-logo {
|
||||
clip-path: polygon(0 0, calc(100% - 9px) 0, 100% 9px, 100% 100%, 0 100%);
|
||||
}
|
||||
[data-theme="modern"] .brand-logo {
|
||||
border-radius: var(--radius-md, 8px);
|
||||
}
|
||||
.brand-name__primary {
|
||||
font-family: var(--font-heading);
|
||||
font-weight: 800;
|
||||
font-size: 0.9375rem;
|
||||
letter-spacing: var(--tracking-wide, 0.02em);
|
||||
text-transform: uppercase;
|
||||
color: var(--color-text-primary);
|
||||
line-height: 1;
|
||||
}
|
||||
.brand-name__sub {
|
||||
font-size: var(--text-caption, 0.75rem);
|
||||
color: var(--color-text-muted);
|
||||
letter-spacing: var(--tracking-wider, 0.04em);
|
||||
text-transform: uppercase;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.sidebar__nav {
|
||||
flex: 1;
|
||||
padding: var(--space-3, 12px) 0;
|
||||
}
|
||||
.nav-section-label {
|
||||
padding: var(--space-3, 12px) var(--space-4, 16px) var(--space-1, 4px);
|
||||
font-size: 0.6875rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: var(--tracking-widest, 0.08em);
|
||||
text-transform: uppercase;
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
.nav-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-3, 12px);
|
||||
padding: var(--space-2, 8px) var(--space-4, 16px);
|
||||
color: var(--color-text-secondary);
|
||||
text-decoration: none;
|
||||
font-size: var(--text-body-sm, 0.875rem);
|
||||
font-weight: 400;
|
||||
border-left: 3px solid transparent;
|
||||
transition: all 0.15s;
|
||||
cursor: pointer;
|
||||
}
|
||||
.nav-item:hover {
|
||||
background: var(--color-surface-2, rgba(255, 255, 255, 0.04));
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
.nav-item.is-active {
|
||||
background: var(--color-primary-muted, rgba(245, 166, 35, 0.12));
|
||||
color: var(--color-primary);
|
||||
border-left-color: var(--color-primary);
|
||||
font-weight: 600;
|
||||
}
|
||||
.nav-item__icon {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
flex-shrink: 0;
|
||||
opacity: 0.7;
|
||||
}
|
||||
.nav-item.is-active .nav-item__icon { opacity: 1; }
|
||||
|
||||
.sidebar__theme-toggle,
|
||||
.sidebar__lang-toggle {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
padding: 8px 16px;
|
||||
border-top: 1px solid var(--color-border);
|
||||
}
|
||||
.theme-toggle-btn,
|
||||
.lang-toggle-btn {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
padding: 6px;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-sm, 4px);
|
||||
background: none;
|
||||
color: var(--color-text-muted);
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
.theme-toggle-btn:hover,
|
||||
.lang-toggle-btn:hover {
|
||||
color: var(--color-text-primary);
|
||||
background: var(--color-surface-2, rgba(255, 255, 255, 0.04));
|
||||
}
|
||||
.theme-toggle-btn.is-active,
|
||||
.lang-toggle-btn.is-active {
|
||||
background: var(--color-primary-muted, rgba(245, 166, 35, 0.12));
|
||||
color: var(--color-primary);
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
.lang-flag {
|
||||
font-weight: 700;
|
||||
font-size: 0.625rem;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
.sidebar__footer {
|
||||
padding: var(--space-3, 12px) var(--space-4, 16px);
|
||||
border-top: 1px solid var(--color-border);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2, 8px);
|
||||
}
|
||||
.sidebar__user-avatar {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 50%;
|
||||
background: var(--color-primary);
|
||||
color: var(--color-text-inverse, #fff);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 0.6875rem;
|
||||
font-weight: 700;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.sidebar__user-info { flex: 1; overflow: hidden; }
|
||||
.sidebar__user-name {
|
||||
font-size: var(--text-body-sm, 0.875rem);
|
||||
font-weight: 600;
|
||||
color: var(--color-text-primary);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.sidebar__user-role {
|
||||
font-size: var(--text-caption, 0.75rem);
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
.sidebar__logout-btn {
|
||||
background: none;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-sm, 4px);
|
||||
padding: 4px 6px;
|
||||
cursor: pointer;
|
||||
color: var(--color-text-muted);
|
||||
transition: all 0.15s;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.sidebar__logout-btn:hover {
|
||||
color: var(--color-error, #F85149);
|
||||
border-color: var(--color-error, #F85149);
|
||||
}
|
||||
|
||||
.pos-main-offset { margin-left: 260px; }
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.pos-sidebar { width: 56px; }
|
||||
.brand-name,
|
||||
.nav-item span,
|
||||
.sidebar__user-info,
|
||||
.nav-section-label,
|
||||
.sidebar__theme-toggle,
|
||||
.sidebar__lang-toggle { display: none; }
|
||||
.sidebar__brand { justify-content: center; padding: 12px 8px; }
|
||||
.sidebar__footer { flex-direction: column; padding: 8px; }
|
||||
.pos-main-offset { margin-left: 56px; }
|
||||
}
|
||||
@@ -392,13 +392,4 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Inject styles
|
||||
if (!document.getElementById('pos-utils-styles')) {
|
||||
var style = document.createElement('style');
|
||||
style.id = 'pos-utils-styles';
|
||||
style.textContent = '@keyframes slideInRight{from{transform:translateX(100%);opacity:0}to{transform:translateX(0);opacity:1}}' +
|
||||
'.filter-panel select:focus{outline:none;border-color:var(--color-primary,#F5A623);box-shadow:0 0 0 2px var(--glow-color-soft,rgba(245,166,35,0.15));}';
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
|
||||
})();
|
||||
|
||||
@@ -106,48 +106,6 @@
|
||||
+ ' </button>'
|
||||
+ '</div>';
|
||||
|
||||
// CSS matching the design system
|
||||
var css = document.createElement('style');
|
||||
css.textContent = [
|
||||
'.pos-sidebar{position:fixed;top:0;left:0;bottom:0;width:260px;display:flex;flex-direction:column;background:var(--color-bg-elevated);border-right:1px solid var(--color-border);z-index:100;overflow-y:auto;scrollbar-width:thin;scrollbar-color:var(--scrollbar-thumb,#444) var(--scrollbar-track,#222);font-family:var(--font-body)}',
|
||||
'.pos-sidebar::-webkit-scrollbar{width:4px}',
|
||||
'.pos-sidebar::-webkit-scrollbar-track{background:var(--scrollbar-track,#222)}',
|
||||
'.pos-sidebar::-webkit-scrollbar-thumb{background:var(--scrollbar-thumb,#444);border-radius:99px}',
|
||||
|
||||
'.sidebar__brand{display:flex;align-items:center;gap:var(--space-3,12px);padding:var(--space-4,16px) var(--space-4,16px) var(--space-3,12px);border-bottom:1px solid var(--color-border);flex-shrink:0}',
|
||||
'.brand-logo{width:36px;height:36px;display:flex;align-items:center;justify-content:center;background:var(--color-primary);color:var(--color-text-inverse,#fff);font-family:var(--font-heading);font-weight:800;font-size:1rem;letter-spacing:-0.04em;flex-shrink:0}',
|
||||
'[data-theme="industrial"] .brand-logo{clip-path:polygon(0 0,calc(100% - 9px) 0,100% 9px,100% 100%,0 100%)}',
|
||||
'[data-theme="modern"] .brand-logo{border-radius:var(--radius-md,8px)}',
|
||||
'.brand-name__primary{font-family:var(--font-heading);font-weight:800;font-size:0.9375rem;letter-spacing:var(--tracking-wide,0.02em);text-transform:uppercase;color:var(--color-text-primary);line-height:1}',
|
||||
'.brand-name__sub{font-size:var(--text-caption,0.75rem);color:var(--color-text-muted);letter-spacing:var(--tracking-wider,0.04em);text-transform:uppercase;margin-top:2px}',
|
||||
|
||||
'.sidebar__nav{flex:1;padding:var(--space-3,12px) 0}',
|
||||
'.nav-section-label{padding:var(--space-3,12px) var(--space-4,16px) var(--space-1,4px);font-size:0.6875rem;font-weight:600;letter-spacing:var(--tracking-widest,0.08em);text-transform:uppercase;color:var(--color-text-muted)}',
|
||||
'.nav-item{display:flex;align-items:center;gap:var(--space-3,12px);padding:var(--space-2,8px) var(--space-4,16px);color:var(--color-text-secondary);text-decoration:none;font-size:var(--text-body-sm,0.875rem);font-weight:400;border-left:3px solid transparent;transition:all 0.15s;cursor:pointer}',
|
||||
'.nav-item:hover{background:var(--color-surface-2,rgba(255,255,255,0.04));color:var(--color-text-primary)}',
|
||||
'.nav-item.is-active{background:var(--color-primary-muted,rgba(245,166,35,0.12));color:var(--color-primary);border-left-color:var(--color-primary);font-weight:600}',
|
||||
'.nav-item__icon{width:18px;height:18px;flex-shrink:0;opacity:0.7}',
|
||||
'.nav-item.is-active .nav-item__icon{opacity:1}',
|
||||
|
||||
'.sidebar__theme-toggle,.sidebar__lang-toggle{display:flex;gap:4px;padding:8px 16px;border-top:1px solid var(--color-border)}',
|
||||
'.theme-toggle-btn,.lang-toggle-btn{flex:1;display:flex;align-items:center;justify-content:center;gap:6px;padding:6px;border:1px solid var(--color-border);border-radius:var(--radius-sm,4px);background:none;color:var(--color-text-muted);cursor:pointer;transition:all 0.15s;font-size:0.75rem}',
|
||||
'.theme-toggle-btn:hover,.lang-toggle-btn:hover{color:var(--color-text-primary);background:var(--color-surface-2,rgba(255,255,255,0.04))}',
|
||||
'.theme-toggle-btn.is-active,.lang-toggle-btn.is-active{background:var(--color-primary-muted,rgba(245,166,35,0.12));color:var(--color-primary);border-color:var(--color-primary)}',
|
||||
'.lang-flag{font-weight:700;font-size:0.625rem;letter-spacing:0.04em}',
|
||||
|
||||
'.sidebar__footer{padding:var(--space-3,12px) var(--space-4,16px);border-top:1px solid var(--color-border);display:flex;align-items:center;gap:var(--space-2,8px)}',
|
||||
'.sidebar__user-avatar{width:28px;height:28px;border-radius:50%;background:var(--color-primary);color:var(--color-text-inverse,#fff);display:flex;align-items:center;justify-content:center;font-size:0.6875rem;font-weight:700;flex-shrink:0}',
|
||||
'.sidebar__user-info{flex:1;overflow:hidden}',
|
||||
'.sidebar__user-name{font-size:var(--text-body-sm,0.875rem);font-weight:600;color:var(--color-text-primary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}',
|
||||
'.sidebar__user-role{font-size:var(--text-caption,0.75rem);color:var(--color-text-muted)}',
|
||||
'.sidebar__logout-btn{background:none;border:1px solid var(--color-border);border-radius:var(--radius-sm,4px);padding:4px 6px;cursor:pointer;color:var(--color-text-muted);transition:all 0.15s;display:flex;align-items:center}',
|
||||
'.sidebar__logout-btn:hover{color:var(--color-error,#F85149);border-color:var(--color-error,#F85149)}',
|
||||
|
||||
'.pos-main-offset{margin-left:260px}',
|
||||
'@media(max-width:768px){.pos-sidebar{width:56px}.brand-name,.nav-item span,.sidebar__user-info,.nav-section-label,.sidebar__theme-toggle,.sidebar__lang-toggle{display:none}.sidebar__brand{justify-content:center;padding:12px 8px}.sidebar__footer{flex-direction:column;padding:8px}.pos-main-offset{margin-left:56px}}',
|
||||
].join('\n');
|
||||
document.head.appendChild(css);
|
||||
|
||||
// Replace existing sidebar
|
||||
var existing = document.querySelector('aside.sidebar, .sidebar, #sidebar');
|
||||
if (existing) {
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Contabilidad — Nexus Autoparts POS</title>
|
||||
<link rel="stylesheet" href="/pos/static/css/tokens.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/common.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/sidebar.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/pos-glass.css" />
|
||||
<link rel="manifest" href="/pos/static/pwa/manifest.json" />
|
||||
<meta name="theme-color" content="#F5A623" />
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Catalogo — Nexus Autoparts POS</title>
|
||||
<link rel="stylesheet" href="/pos/static/css/tokens.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/common.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/sidebar.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/pos-glass.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/chat.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/onboarding.css" />
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Configuración — Nexus Autoparts POS</title>
|
||||
<link rel="stylesheet" href="/pos/static/css/tokens.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/common.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/sidebar.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/pos-glass.css" />
|
||||
<link rel="manifest" href="/pos/static/pwa/manifest.json" />
|
||||
<meta name="theme-color" content="#F5A623" />
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Nexus Autoparts — Clientes</title>
|
||||
<link rel="stylesheet" href="/pos/static/css/tokens.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/common.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/sidebar.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/pos-glass.css" />
|
||||
<link rel="manifest" href="/pos/static/pwa/manifest.json" />
|
||||
<meta name="theme-color" content="#F5A623" />
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Nexus Autoparts — Dashboard</title>
|
||||
<link rel="stylesheet" href="/pos/static/css/tokens.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/common.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/sidebar.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/pos-glass.css" />
|
||||
<link rel="manifest" href="/pos/static/pwa/manifest.json" />
|
||||
<meta name="theme-color" content="#F5A623" />
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Diagramas — Nexus Autoparts POS</title>
|
||||
<link rel="stylesheet" href="/pos/static/css/tokens.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/common.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/sidebar.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/pos-glass.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/chat.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/onboarding.css" />
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Flotillas — Nexus Autoparts POS</title>
|
||||
<link rel="stylesheet" href="/pos/static/css/tokens.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/common.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/sidebar.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/pos-glass.css" />
|
||||
<link rel="manifest" href="/pos/static/pwa/manifest.json" />
|
||||
<meta name="theme-color" content="#F5A623" />
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Inventario — Nexus Autoparts POS</title>
|
||||
<link rel="stylesheet" href="/pos/static/css/tokens.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/common.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/sidebar.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/pos-glass.css" />
|
||||
<link rel="manifest" href="/pos/static/pwa/manifest.json" />
|
||||
<meta name="theme-color" content="#F5A623" />
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Facturación CFDI — Nexus Autoparts POS</title>
|
||||
<link rel="stylesheet" href="/pos/static/css/tokens.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/common.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/sidebar.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/pos-glass.css" />
|
||||
<link rel="manifest" href="/pos/static/pwa/manifest.json" />
|
||||
<meta name="theme-color" content="#F5A623" />
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Marketplace B2B — Nexus Autoparts POS</title>
|
||||
<link rel="stylesheet" href="/pos/static/css/tokens.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/common.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/sidebar.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/pos-glass.css" />
|
||||
<link rel="manifest" href="/pos/static/pwa/manifest.json" />
|
||||
<meta name="theme-color" content="#F5A623" />
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Nexus Autoparts — Punto de Venta</title>
|
||||
<link rel="stylesheet" href="/pos/static/css/tokens.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/common.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/sidebar.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/pos-glass.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/chat.css" />
|
||||
<link rel="manifest" href="/pos/static/pwa/manifest.json" />
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Cotizaciones — Nexus Autoparts POS</title>
|
||||
<link rel="stylesheet" href="/pos/static/css/tokens.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/common.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/sidebar.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/pos-glass.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/quotations.css">
|
||||
</head>
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Reportes — Nexus Autoparts POS</title>
|
||||
<link rel="stylesheet" href="/pos/static/css/tokens.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/common.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/sidebar.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/pos-glass.css" />
|
||||
<link rel="manifest" href="/pos/static/pwa/manifest.json" />
|
||||
<meta name="theme-color" content="#F5A623" />
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>WhatsApp — Nexus Autoparts POS</title>
|
||||
<link rel="stylesheet" href="/pos/static/css/tokens.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/common.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/sidebar.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/pos-glass.css" />
|
||||
<link rel="manifest" href="/pos/static/pwa/manifest.json" />
|
||||
<meta name="theme-color" content="#F5A623" />
|
||||
|
||||
Reference in New Issue
Block a user