/**
 * DVERGENT — Sistema de Diseño v6.2 «Volt»
 * Identidad: negro OLED escalonado + acento lima Volt + tipografía
 * Space Grotesk (display) · Inter (UI/datos) · JetBrains Mono (cifras/labels).
 * 2 temas: dark (default) + light. UN SOLO VERDE en todo el ERP.
 *
 * CANON DEL ACENTO (decisión Adri 2026-07-26). Dos ejes, no uno:
 *   · TEXTO / BORDE / ICONO / STROKE acentuado → var(--accent-primary).
 *     Token CONMUTADO: dark = lima Volt (16.16:1 sobre negro, AAA) ·
 *     light = lima oscurecido (AA sobre blanco Y sobre --bg-app).
 *   · RELLENO de marca (CTA, chip activo, barra de progreso, dot de estado,
 *     ::selection) → background: var(--accent-fill); color: var(--accent-fill-contrast);
 *     --accent-fill es lima Volt en LOS DOS temas: así la marca sigue viva en light
 *     en vez de desaparecer en un oliva apagado.
 *   · LÍMITE del relleno de marca en CONTROLES → border-color: var(--accent-fill-border).
 *     WCAG 1.4.11 exige 3:1 para el contorno de un control. En dark el borde ES el
 *     propio fill (16.2:1 sobre negro); en light el fill lima da 1.30:1 sobre card
 *     blanca → el botón deja de leerse como botón. El token conmuta a un lima
 *     oscurecido de la misma familia (4.23:1 sobre card, 3.56:1 sobre --bg-tertiary,
 *     3.26:1 contra el propio relleno). Se aplica SOLO a controles interactivos:
 *     barras de progreso, dots, ::selection y badges de solo lectura NO llevan borde
 *     (1.4.11 no los cubre y los ensucia).
 * PROHIBIDO el lima claro como texto/icono/borde sobre fondo claro (1.30:1 — V49-01).
 * PROHIBIDO hardcodear hex de marca fuera de estos dos bloques de tokens.
 * Regla de marca: hue del lima bloqueado en 70-90° (lado amarillo).
 * Fuentes: cargadas desde index.html (un solo <link>, sin @import aquí).
 */

/* =====================================================
   VARIABLES — DARK MODE (default) · Negro OLED escalonado
   Elevación por saltos de luminosidad, no por sombras.
   ===================================================== */
:root {
    /* Tipografía */
    --font-display: 'Space Grotesk', 'Inter', -apple-system, system-ui, sans-serif;
    --font-ui:      'Inter', -apple-system, BlinkMacSystemFont, system-ui, 'Segoe UI', Roboto, sans-serif;
    --font-mono:    'JetBrains Mono', ui-monospace, 'SF Mono', Menlo, monospace;

    /* Backgrounds — canvas negro OLED + superficies escalonadas */
    --bg-app:       #000000;
    --bg-primary:   #0a0a0c;
    --bg-secondary: #111113;
    --bg-tertiary:  #18181b;
    --bg-sidebar:   #050506;
    --bg-card:      #0c0c0e;
    --bg-hover:     rgba(255, 255, 255, 0.04);
    --bg-active:    rgba(255, 255, 255, 0.07);
    --bg-input:    #0e0e10;

    /* Text — blanco roto (nunca #FFF puro: halation) */
    --text-primary:   #eceef1;
    --text-secondary: #9ba1a8;
    --text-tertiary:  #7a8189;   /* AA 4.6:1 sobre #000 — labels mono 10px */
    --text-muted:     #4d525a;   /* decorativo/placeholder — no texto informativo */

    /* Borders — hairlines semitransparentes */
    --border-primary:   rgba(255, 255, 255, 0.09);
    --border-secondary: rgba(255, 255, 255, 0.05);
    --border-strong:    rgba(255, 255, 255, 0.17);
    --border-focus:     color-mix(in srgb, var(--accent-primary) 40%, transparent);

    /* Acento — Volt. EJE 1: texto/borde/icono (conmuta por tema) */
    --accent-primary:  #c6f23a;
    --accent-hover:    #d7ff5c;
    --accent-contrast: #131a03;   /* texto sobre relleno acento */
    /* v6.2 — texto sobre relleno ROJO. Es un token conmutado por el mismo motivo que
       --accent-contrast: --accent-red vale #ff6b6b en dark (rojo CLARO, pensado para
       leerse sobre negro) y #b3261e en light (rojo OSCURO). Un `color:#fff` fijo daba
       2.44:1 en dark — falla AA en el tema POR DEFECTO — y 7.0:1 en light.
       Ahora: #2a0606 sobre #ff6b6b = 6.7:1 · #ffffff sobre #b3261e = 6.5:1. */
    --accent-red-contrast: #2a0606;
    --accent-soft:     color-mix(in srgb, var(--accent-primary) 10%, transparent);
    --accent-gradient: linear-gradient(135deg, var(--accent-hover), var(--accent-primary));

    /* EJE 2: relleno de marca. En dark coincide con el acento; en light NO
       (allí el lima sigue siendo relleno vivo mientras el texto se oscurece). */
    --accent-fill:          var(--accent-primary);
    --accent-fill-contrast: var(--accent-contrast);
    --accent-fill-hover:    var(--accent-hover);
    /* Borde del relleno de marca en controles (WCAG 1.4.11). Sobre negro el lima
       da 16.16:1: el borde puede ser el propio fill. En light conmuta (ver abajo). */
    --accent-fill-border:   var(--accent-fill);

    /* Paleta semántica (conmutada por tema).
       v6.2: NO hay segundo verde — todo "verde/éxito" apunta al acento. */
    --accent-green:  var(--accent-primary);
    --accent-red:    #ff6b6b;
    --accent-orange: #f4b740;
    --accent-yellow: #facc15;
    --accent-purple: #a78bfa;
    --accent-blue:   #38bdf8;
    --accent-cyan:   #22d3ee;
    --accent-indigo: var(--accent-primary);

    /* Semantic KPI */
    --kpi-positive: var(--accent-primary);
    --kpi-negative: #ff6b6b;
    --kpi-info:     #38bdf8;
    --kpi-alert:    #f4b740;

    /* ── Tinta sobre RELLENOS semánticos (2026-08-02) ──────────────────────────
       Existía --accent-contrast (lima) y --accent-red-contrast (rojo) pero NINGÚN
       par para naranja/amarillo/azul/púrpura/cian, así que los badges y chips que
       usan esas semánticas como FONDO pintaban `color:#fff` o `color:#000` fijos.
       Es el mismo defecto que motivó --accent-red-contrast: las semánticas CONMUTAN
       por tema (claras en dark, oscuras en light), luego una tinta fija falla
       siempre en uno de los dos temas. En dark todas son claras → la tinta es un
       oscuro del mismo hue (no negro puro: ensucia menos y evita halation).
       Ratios medidos (WCAG 2.1) sobre su propio relleno. */
    --accent-orange-contrast: #2a1c00;   /* sobre #f4b740 →  9.2:1 */
    --accent-yellow-contrast: #2b2200;   /* sobre #facc15 → 10.3:1 */
    --accent-blue-contrast:   #052436;   /* sobre #38bdf8 →  7.5:1 */
    --accent-cyan-contrast:   #052b33;   /* sobre #22d3ee →  8.3:1 */
    --accent-purple-contrast: #1b0f3a;   /* sobre #a78bfa →  6.6:1 */
    /* --accent-green / --accent-indigo apuntan a --accent-primary (un solo verde):
       su tinta es la del acento, ya conmutada. Se resuelven en el punto de uso. */
    --accent-green-contrast:  var(--accent-contrast);
    --accent-indigo-contrast: var(--accent-contrast);

    /* Mismos pares, por el alias semántico con el que se escriben los componentes.
       Regla: el -contrast de un alias apunta al -contrast del color que aliasa. */
    --success-contrast:       var(--accent-contrast);
    --danger-contrast:        var(--accent-red-contrast);
    --warning-contrast:       var(--accent-orange-contrast);
    --info-contrast:          var(--accent-blue-contrast);
    --color-success-contrast: var(--accent-contrast);
    --color-danger-contrast:  var(--accent-red-contrast);
    --color-warning-contrast: var(--accent-orange-contrast);
    --danger-color-contrast:  var(--accent-red-contrast);
    --warning-color-contrast: var(--accent-orange-contrast);
    --kpi-positive-contrast:  var(--accent-contrast);
    --kpi-negative-contrast:  var(--accent-red-contrast);
    --kpi-info-contrast:      var(--accent-blue-contrast);
    --kpi-alert-contrast:     var(--accent-orange-contrast);

    /* Aliases semánticos — usados por componentes (antes indefinidos: bug).
       Inventario completo auditado 2026-07-24 (revisión adversarial v6):
       ~150 usos inline en JS de módulos referencian estas variantes. */
    --accent:       var(--accent-primary);
    --primary:      var(--accent-primary);
    --primary-color: var(--accent-primary);
    --border:       var(--border-primary);
    --border-color: var(--border-primary);
    --success:      var(--accent-primary);
    --danger:       var(--accent-red);
    --warning:      var(--accent-orange);
    --info:         var(--accent-blue);
    --color-danger:  var(--accent-red);
    --color-success: var(--accent-green);
    --color-warning: var(--accent-orange);
    --danger-color:  var(--accent-red);
    --warning-color: var(--accent-orange);
    --text-danger:  var(--accent-red);
    --bg-danger:    color-mix(in srgb, var(--accent-red) 12%, transparent);
    --accent-bg:    var(--accent-soft);
    --danger-bg:    color-mix(in srgb, var(--accent-red) 12%, transparent);
    --warning-bg:   color-mix(in srgb, var(--accent-orange) 12%, transparent);
    --success-bg:   color-mix(in srgb, var(--accent-green) 12%, transparent);
    --info-bg:      color-mix(in srgb, var(--accent-blue) 12%, transparent);
    --accent-orange-bg: color-mix(in srgb, var(--accent-orange) 12%, transparent);
    --accent-blue-subtle: color-mix(in srgb, var(--accent-blue) 12%, transparent);
    --surface:      var(--bg-secondary);
    --surface-2:    var(--bg-tertiary);
    --surface-3:    var(--bg-tertiary);
    --radius:       var(--radius-md);
    --border-radius-sm: var(--radius-sm);
    /* Tamaños tipográficos usados como var() en estilos inline */
    --text-xs:      0.75rem;
    --text-sm:      0.8125rem;

    /* Overlays */
    --bg-overlay: rgba(0, 0, 0, 0.72);

    /* Shadows — mínimas; la elevación la da la luminosidad */
    --shadow-xs:  0 1px 2px rgba(0, 0, 0, 0.4);
    --shadow-sm:  0 2px 8px rgba(0, 0, 0, 0.35);
    --shadow-md:  0 4px 16px rgba(0, 0, 0, 0.45);
    --shadow-lg:  0 8px 32px rgba(0, 0, 0, 0.55);
    --shadow-xl:  0 24px 70px -12px rgba(0, 0, 0, 0.65);
    --shadow-card: none;

    /* Radii */
    --radius-xs:   6px;
    --radius-sm:   9px;
    --radius-md:   12px;
    --radius-lg:   14px;
    --radius-xl:   16px;
    --radius-2xl:  20px;
    --radius-full: 9999px;

    /* Transitions */
    --ease-out:          cubic-bezier(0.25, 1, 0.5, 1);
    --ease-spring:       cubic-bezier(0.34, 1.56, 0.64, 1);
    --transition-fast:   0.15s var(--ease-out);
    --transition-normal: 0.2s var(--ease-out);
    --transition-slow:   0.35s var(--ease-out);

    /* Spacing scale */
    --space-1:  0.25rem;
    --space-2:  0.5rem;
    --space-3:  0.75rem;
    --space-4:  1rem;
    --space-5:  1.25rem;
    --space-6:  1.5rem;
    --space-8:  2rem;
    --space-10: 2.5rem;
    --space-12: 3rem;


    /* DVERGENT Liquid — materiales del bundle React. Los valores se activan
       únicamente en html[data-ui="liquid"]; Vanilla conserva sus tokens. */
    --orbe-ink: #eceef1;
    --orbe-edge: rgba(255,255,255,.14);
    --orbe-bg: #050506;
    --liquid-canvas: #000000;
    --liquid-panel: #111214;
    --liquid-card: rgba(16,18,21,.48);
    --liquid-secondary: #181a1c;
    --liquid-input: #09090b;
    --liquid-sidebar: #030304;
    --liquid-ink: #eff0ed;
    --liquid-secondary-ink: #a4a8b0;
    --liquid-tertiary-ink: #8b9099;
    --liquid-line: rgba(255, 255, 255, 0.063);
    --liquid-edge: rgba(255, 255, 255, 0.18);
    --liquid-accent: #c6f23a;
    --liquid-accent-hover: #d7ff5c;
    --liquid-accent-ink: #131a03;
    --liquid-material: linear-gradient(138deg, rgba(255,255,255,.095), rgba(255,255,255,.012) 34%, transparent 55%, rgba(255,255,255,.028)), linear-gradient(165deg, rgba(25,28,32,.42), rgba(6,7,9,.28) 70%);
    --liquid-control: linear-gradient(150deg, rgba(255,255,255,.07), rgba(255,255,255,.015) 65%);
    --liquid-shadow: inset 0 1px 0 rgba(255,255,255,.15), inset 0 -1px 0 rgba(255,255,255,.025), 0 2px 4px rgba(0,0,0,.12), 0 16px 38px rgba(0,0,0,.24);
    --liquid-gloss: linear-gradient(120deg, rgba(255,255,255,.08), transparent 45%);
    --liquid-header: rgba(5,5,6,.9);
    --liquid-overlay: rgba(0,0,0,.7);
    /* Tercer escalón de superficie (pistas de barra, cajas dentro de un panel) y
       material de lo que FLOTA sobre datos (avisos, diálogos, desplegables): más
       denso que la tarjeta para que el texto de debajo no se lea a través. */
    --liquid-tertiary: #1b1d20;
    --liquid-float: rgba(14,15,18,.86);

    /* Layout */
    --sidebar-width:     240px;
    --sidebar-collapsed: 68px;
    --header-height:     56px;
}

/* =====================================================
   VARIABLES — LIGHT MODE (rediseño v6.2, 2026-07-26)
   Canvas neutro-cálido limpio (sin tinte verde en los neutros) y elevación
   REAL: en light la profundidad la dan sombra + hairline, no la luminosidad.
   El acento se parte en dos: texto/borde oscurecido (AA) + relleno lima vivo.
   El sidebar permanece dark (marca) — retokenizado más abajo.
   Contrastes medidos (WCAG 2.1) sobre --bg-app #f3f4f1 / --bg-card #ffffff:
     --text-primary   16.1 / 17.8  · --text-secondary 6.2 / 6.9
     --text-tertiary   4.8 /  5.3  · --accent-primary 5.6 / 6.2
   ===================================================== */
[data-theme="light"] {
    --bg-app:       #f3f4f1;   /* canvas neutro-cálido, sin verdor */
    --bg-primary:   #ffffff;
    --bg-secondary: #f7f8f5;
    --bg-tertiary:  #ebece8;
    --bg-sidebar:   #050506;
    --bg-card:      #ffffff;
    --bg-hover:     rgba(21, 24, 29, 0.045);
    --bg-active:    rgba(21, 24, 29, 0.075);
    --bg-input:     #ffffff;

    --text-primary:   #15181d;
    --text-secondary: #545b64;   /* AAA-ish 6.2:1 sobre el canvas */
    --text-tertiary:  #666c75;   /* AA 4.8:1 — labels mono 10px */
    --text-muted:     #8d939b;   /* decorativo/placeholder — no texto informativo */

    /* Hairlines finos: la separación la remata la sombra, no el borde */
    --border-primary:   rgba(21, 24, 29, 0.11);
    --border-secondary: rgba(21, 24, 29, 0.07);
    --border-strong:    rgba(21, 24, 29, 0.19);

    /* EJE 1 — acento conmutado: lima oscurecido, AA como TEXTO/BORDE/ICONO.
       ⚠️ El acento NO vive solo sobre #ffffff / #f3f4f1: vive también sobre sus
       propios tintes (--accent-soft 10%, .badge-success color-mix 12%) y sobre
       --bg-tertiary #ebece8. El valor anterior (#557a12) daba 5.03 sobre blanco
       pero 4.23 sobre --bg-tertiary, 4.42 sobre accent-soft/card y 3.91 sobre el
       tinte 12% del canvas → texto pequeño por debajo de 4.5:1 (revisión
       adversarial 2026-07-26). #4b6b10 (mismo hue 81°) es el valor más claro que
       cumple AA en TODOS esos fondos: card 6.16 · canvas 5.58 · tertiary 5.19 ·
       soft10/canvas 4.86 · mix12/canvas 4.75 · mix14/canvas 4.61. */
    --accent-primary:  #4b6b10;
    --accent-hover:    #476512;
    --accent-contrast: #ffffff;   /* texto sobre relleno --accent-primary #4b6b10 (6.2:1, AA) */
    --accent-red-contrast: #ffffff;  /* sobre --accent-red #b3261e → 6.5:1 */

    /* EJE 2 — el lima de marca NO desaparece en light: sigue siendo el relleno.
       Todo CTA / chip activo / barra / dot usa este par, no --accent-primary. */
    --accent-fill:          #c6f23a;
    --accent-fill-contrast: #131a03;
    /* Hex PLANO a propósito, no color-mix: este token también lo leen los canvas
       (Chart.js) vía getComputedStyle, y `ctx.fillStyle` NO acepta color-mix() —
       la declaración se descarta en silencio y la cara superior de las barras 3D
       se queda sin pintar. Valor = #c6f23a oscurecido un 12% hacia #131a03. */
    --accent-fill-hover:    #b1d833;
    /* Límite del relleno de marca (WCAG 1.4.11, revisión 2026-07-26). El fill lima
       como borde daba 1.30:1 sobre --bg-card y 1.09:1 sobre --bg-tertiary: el CTA
       existía como color pero no como FORMA. Derivado del propio acento light (no
       hex nuevo → si cambia --accent-primary, el borde le sigue). Resuelve #648618,
       hue 78.5° (dentro del bloqueo de marca 70-90°). Ratios medidos:
         card #ffffff 4.23 · app #f3f4f1 3.83 · secondary #f7f8f5 3.97 ·
         tertiary #ebece8 3.56 · contra el propio relleno #c6f23a 3.26.
       SOLO controles interactivos: barras/dots/::selection/badges siguen sin borde. */
    --accent-fill-border:   color-mix(in srgb, var(--accent-primary) 80%, var(--accent-fill));

    /* Anillo de foco: el 40% de :root componía a #b7c49f sobre card = 1.84:1 (falla
       1.4.11 / 2.4.11). Al 80% compone #6f8940 sobre card = 3.94:1, #6d863d sobre
       el canvas = 3.71:1, #6b853b sobre --bg-tertiary = 3.51:1. */
    --border-focus:     color-mix(in srgb, var(--accent-primary) 80%, transparent);

    /* --accent-green / --accent-indigo / --kpi-positive / --success NO se
       redefinen aquí: apuntan a var(--accent-primary) desde :root y las custom
       properties resuelven en el punto de uso → conmutan solas. Un solo verde. */
    --accent-red:    #b3261e;
    --accent-orange: #8a4b0a;
    --accent-yellow: #7a4a0b;
    --accent-purple: #5b21b6;
    --accent-blue:   #0b5c8a;
    --accent-cyan:   #14596d;

    --kpi-negative: #b3261e;
    --kpi-info:     #0b5c8a;
    --kpi-alert:    #8a4b0a;

    /* Tinta sobre rellenos semánticos en light: aquí TODAS las semánticas son
       oscuras (se eligieron para leerse como TEXTO sobre blanco), así que encima
       va blanco. Justo lo contrario que en dark — de ahí que el token tenga que
       conmutar y no pueda ser un #fff/#000 escrito en el componente.
       Los aliases (--success-contrast, --kpi-*-contrast, --accent-green-contrast…)
       NO se redefinen: apuntan a estos con var() y resuelven en el punto de uso. */
    --accent-orange-contrast: #ffffff;   /* sobre #8a4b0a → 6.8:1 */
    --accent-yellow-contrast: #ffffff;   /* sobre #7a4a0b → 7.5:1 */
    --accent-blue-contrast:   #ffffff;   /* sobre #0b5c8a → 7.2:1 */
    --accent-cyan-contrast:   #ffffff;   /* sobre #14596d → 7.8:1 */
    --accent-purple-contrast: #ffffff;   /* sobre #5b21b6 → 9.0:1 */

    --bg-overlay: rgba(16, 20, 26, 0.42);

    /* Escalera de sombras creíble: contacto (nítida, corta) + difusión (larga,
       suave). Sin esto las cards blancas sobre canvas claro se ven planas. */
    --shadow-xs:  0 1px 2px rgba(16, 20, 26, 0.06);
    --shadow-sm:  0 1px 2px rgba(16, 20, 26, 0.06), 0 2px 6px -1px rgba(16, 20, 26, 0.08);
    --shadow-md:  0 2px 4px -1px rgba(16, 20, 26, 0.07), 0 8px 20px -4px rgba(16, 20, 26, 0.13);
    --shadow-lg:  0 4px 8px -2px rgba(16, 20, 26, 0.08), 0 18px 38px -8px rgba(16, 20, 26, 0.17);
    --shadow-xl:  0 8px 16px -4px rgba(16, 20, 26, 0.10), 0 32px 64px -16px rgba(16, 20, 26, 0.24);
    --shadow-card: 0 1px 1px rgba(16, 20, 26, 0.04), 0 2px 6px -1px rgba(16, 20, 26, 0.07), 0 14px 26px -14px rgba(16, 20, 26, 0.12);
    /* DVERGENT Light: blanco neutro y controles negros. El color semántico
       sigue distinguiendo éxito/aviso/error; el material nunca lo sustituye. */
    --liquid-success: #4b6b10;
    --liquid-canvas: #f7f7f8;
    --liquid-panel: #ffffff;
    --liquid-card: rgba(255,255,255,.72);
    --liquid-secondary: #f0f0f2;
    --liquid-input: #ffffff;
    --liquid-sidebar: #000000;   /* menú lateral negro también en Light (2026-10-09) */
    --liquid-ink: #111114;
    --liquid-secondary-ink: #55565e;
    --liquid-tertiary-ink: #686a73;
    --liquid-line: rgba(17,17,20,.14);
    --liquid-edge: rgba(17,17,20,.23);
    --liquid-accent: #18181b;
    --liquid-accent-hover: #303035;
    --liquid-accent-ink: #ffffff;
    --liquid-material: linear-gradient(135deg, rgba(255,255,255,.7), transparent 48%, rgba(17,17,20,.018));
    --liquid-control: linear-gradient(145deg, rgba(255,255,255,.95), rgba(255,255,255,.45));
    --liquid-shadow: inset 0 1px 0 rgba(255,255,255,.95), 0 2px 4px rgba(17,17,20,.025), 0 12px 32px rgba(17,17,20,.055);
    --liquid-gloss: linear-gradient(120deg, rgba(255,255,255,.9), transparent 50%);
    --liquid-header: rgba(255,255,255,.92);
    --liquid-overlay: rgba(17,17,20,.32);
    --liquid-tertiary: #e9e9ec;
    --liquid-float: rgba(255,255,255,.9);

}

/* ── Variante dorada saas_admin: RETIRADA (2026-08-09, decisión Adri, mismo
   día que se pidió) — un solo acento identitario DVERGENT (lima Volt) para todos
   los roles. Los tokens :root.rol-saas-admin vivieron aquí; histórico en git
   (`git log -S rol-saas-admin -- app/public/css/theme.css`). app.js hace
   classList.remove por si la clase quedó de una sesión en caliente. */

/* Light mode: el sidebar mantiene el dark de marca — retokenizado local */
[data-theme="light"] .sidebar {
    --text-primary:   #eceef1;
    --text-secondary: #9ba1a8;
    --text-tertiary:  #7a8189;   /* AA 4.6:1 sobre #000 — labels mono 10px */
    --text-muted:     #4d525a;   /* decorativo/placeholder — no texto informativo */
    --border-primary:   rgba(255, 255, 255, 0.09);
    --border-secondary: rgba(255, 255, 255, 0.05);
    --bg-hover:     rgba(255, 255, 255, 0.05);
    --bg-active:    rgba(255, 255, 255, 0.08);
    /* Superficie oscura → el acento vuelve a ser Volt pleno en los dos ejes */
    --accent-primary:  #c6f23a;
    --accent-hover:    #d7ff5c;
    --accent-contrast: #131a03;
    --accent-soft:     color-mix(in srgb, var(--accent-primary) 10%, transparent);
    --accent-fill:          var(--accent-primary);
    --accent-fill-contrast: var(--accent-contrast);
    --accent-fill-hover:    var(--accent-hover);
    /* Superficie casi negra → el lima vuelve a valer como borde (16.0:1 sobre
       --bg-sidebar #050506) y el anillo de foco vuelve al 40% de :root. */
    --accent-fill-border:   var(--accent-fill);
    --border-focus:         color-mix(in srgb, var(--accent-primary) 40%, transparent);
}

/* =====================================================
   RESET & BASE
   ===================================================== */
*, *::before, *::after {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
}

html {
    font-size: 16px;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    text-rendering: optimizeLegibility;
}

body {
    font-family: var(--font-ui);
    background: var(--bg-app);
    color: var(--text-primary);
    line-height: 1.55;
    min-height: 100vh;
    font-size: 14px;
}

::selection {
    /* relleno de marca → par fill (lima vivo también en light) */
    background: var(--accent-fill);
    color: var(--accent-fill-contrast);
}

/* Lucide icons inline alignment */
[data-lucide], svg.lucide {
    display: inline-block;
    vertical-align: middle;
    stroke: currentColor;
    stroke-width: 1.75;
    fill: none;
    stroke-linecap: round;
    stroke-linejoin: round;
    flex-shrink: 0;
}

/* Scrollbar */
::-webkit-scrollbar { width: 6px; height: 6px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
    background: var(--border-primary);
    border-radius: var(--radius-full);
}
::-webkit-scrollbar-thumb:hover { background: var(--text-muted); }

/* =====================================================
   TYPOGRAPHY
   ===================================================== */
h1, h2, h3, h4, h5, h6 {
    font-family: var(--font-display);
    font-weight: 600;
    line-height: 1.25;
    color: var(--text-primary);
    letter-spacing: -0.01em;
}

h1 { font-size: 1.5rem; font-weight: 700; letter-spacing: -0.02em; }
h2 { font-size: 1.25rem; letter-spacing: -0.015em; }
h3 { font-size: 1.125rem; }
h4 { font-size: 1rem; }

p { margin-bottom: 0.5rem; }

a { color: var(--accent-primary); text-decoration: none; transition: color var(--transition-fast); }
a:hover { color: var(--accent-hover); text-decoration: none; }

/* =====================================================
   UTILITIES
   ===================================================== */
.text-xs   { font-size: 0.75rem; }
.text-sm   { font-size: 0.8125rem; }
.text-base { font-size: 0.875rem; }
.text-lg   { font-size: 1.125rem; }
.text-xl   { font-size: 1.25rem; }
.text-muted   { color: var(--text-tertiary); }
.text-center  { text-align: center; }
.text-right   { text-align: right; }

.font-medium   { font-weight: 500; }
.font-semibold { font-weight: 600; }
.font-bold     { font-weight: 700; }

/* v6 Volt — utilidades tipográficas de datos */
.font-mono {
    font-family: var(--font-mono);
    font-variant-ligatures: none;
}
.tabular { font-variant-numeric: tabular-nums; }
/* Label técnico: microcopy mono uppercase (kickers, etiquetas de dato) */
.label-tech {
    font-family: var(--font-mono);
    font-size: 0.625rem;
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--text-tertiary);
}

.flex         { display: flex; }
.flex-col     { flex-direction: column; }
.flex-wrap    { flex-wrap: wrap; }
.items-center { align-items: center; }
.justify-center  { justify-content: center; }
.justify-between { justify-content: space-between; }
.gap-1 { gap: var(--space-1); }
.gap-2 { gap: var(--space-2); }
.gap-3 { gap: var(--space-3); }
.gap-4 { gap: var(--space-4); }

.w-full { width: 100%; }
.h-full { height: 100%; }

.p-2 { padding: var(--space-2); }
.p-3 { padding: var(--space-3); }
.p-4 { padding: var(--space-4); }
.p-5 { padding: var(--space-5); }
.p-6 { padding: var(--space-6); }

.mt-2 { margin-top: var(--space-2); }
.mt-3 { margin-top: var(--space-3); }
.mt-4 { margin-top: var(--space-4); }
.mb-2 { margin-bottom: var(--space-2); }
.mb-3 { margin-bottom: var(--space-3); }
.mb-4 { margin-bottom: var(--space-4); }
.ml-2 { margin-left: var(--space-2); }

.hidden { display: none !important; }

/* =====================================================
   BUTTONS
   ===================================================== */
.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-2);
    padding: 0.5rem 1rem;
    font-family: inherit;
    font-size: 0.8125rem;
    font-weight: 500;
    line-height: 1.4;
    border-radius: var(--radius-sm);
    border: 1px solid var(--border-primary);
    background: var(--bg-secondary);
    color: var(--text-primary);
    cursor: pointer;
    transition: all var(--transition-fast);
    white-space: nowrap;
    text-decoration: none;
    user-select: none;
    outline: none;
}

.btn:hover {
    background: var(--bg-tertiary);
    border-color: var(--border-primary);
    text-decoration: none;
}

.btn:active {
    transform: scale(0.97);
    transition-duration: 0.05s;
}

.btn:focus-visible {
    box-shadow: 0 0 0 3px var(--border-focus);
}

/* CTA de marca → par fill: lima Volt en dark Y en light.
   El BORDE va por --accent-fill-border (1.4.11: el contorno del control necesita
   3:1; el fill lima sobre card blanca solo da 1.30:1). */
.btn-primary {
    background: var(--accent-fill);
    border-color: var(--accent-fill-border);
    color: var(--accent-fill-contrast);
    font-weight: 600;
}
.btn-primary:hover {
    background: var(--accent-fill-hover);
    border-color: var(--accent-fill-border);
    color: var(--accent-fill-contrast);
}

.btn-success {
    background: var(--accent-fill);
    border-color: var(--accent-fill-border);
    color: var(--accent-fill-contrast);
    font-weight: 600;
}
.btn-success:hover { filter: brightness(1.08); }

.btn-danger {
    background: var(--accent-red);
    border-color: var(--accent-red);
    color: var(--accent-red-contrast);
}
.btn-danger:hover { filter: brightness(1.08); }

.btn-warning {
    background: var(--accent-orange);
    border-color: var(--accent-orange);
    color: var(--accent-contrast);
    font-weight: 600;
}
.btn-warning:hover { filter: brightness(1.08); }

.btn-active {
    background: var(--accent-fill) !important;
    color: var(--accent-fill-contrast) !important;
    border-color: var(--accent-fill-border) !important;
}
.btn-ghost {
    background: transparent;
    border-color: transparent;
    color: var(--text-primary);
    opacity: 0.75;
}
.btn-ghost:hover {
    background: var(--bg-active);
    color: var(--text-primary);
    opacity: 1;
}

/* Action icon buttons — visible in both light and dark modes */
.btn-icon-edit {
    background: transparent;
    border-color: transparent;
    color: var(--accent-orange);
    opacity: 0.85;
}
.btn-icon-edit:hover {
    background: color-mix(in srgb, var(--accent-orange) 12%, transparent);
    color: var(--accent-orange);
    opacity: 1;
}
.btn-icon-delete {
    background: transparent;
    border-color: transparent;
    color: var(--accent-red);
    opacity: 0.85;
}
.btn-icon-delete:hover {
    background: color-mix(in srgb, var(--accent-red) 12%, transparent);
    color: var(--accent-red);
    opacity: 1;
}

/* ── Restricción visual: empleado no puede eliminar ── */
body[data-role="empleado"] .btn-icon-delete,
body[data-role="empleado"] .btn-danger {
    display: none !important;
}

/* ── Badge ENTORNO TEST ── */
.badge-test {
    display: inline-block;
    background: var(--accent-orange);
    color: var(--accent-contrast);
    font-family: var(--font-mono);
    font-size: 10px;
    font-weight: 700;
    padding: 2px 8px;
    border-radius: 4px;
    letter-spacing: .05em;
    text-transform: uppercase;
    margin-left: 8px;
    vertical-align: middle;
}

.btn-outline {
    background: transparent;
    border-color: var(--border-primary);
    color: var(--text-secondary);
}
.btn-outline:hover {
    background: var(--bg-hover);
    border-color: var(--accent-primary);
    color: var(--accent-primary);
}

.btn-sm {
    padding: 0.3125rem 0.625rem;
    font-size: 0.75rem;
    border-radius: var(--radius-xs);
}

.btn-lg {
    padding: 0.625rem 1.25rem;
    font-size: 0.875rem;
    border-radius: var(--radius-sm);
}

.btn-icon {
    padding: 0.5rem;
    width: 36px;
    height: 36px;
    border-radius: var(--radius-sm);
}

/* =====================================================
   FORMS
   ===================================================== */
.form-group {
    margin-bottom: var(--space-4);
}

.form-label {
    display: block;
    font-family: var(--font-mono);
    font-size: 0.6875rem;
    font-weight: 500;
    color: var(--text-secondary);
    margin-bottom: var(--space-2);
    letter-spacing: 0.06em;
    text-transform: uppercase;
}

.form-row {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: var(--space-4);
}

/* form-control: estilo global para inputs, selects y textareas */
.form-control {
    width: 100%;
    padding: 0.5rem 0.75rem;
    font-family: inherit;
    font-size: 0.875rem;
    line-height: 1.5;
    border: 1px solid var(--border-primary);
    border-radius: var(--radius-md);
    background: var(--bg-input);
    color: var(--text-primary);
    transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
    outline: none;
}

.form-control:focus {
    border-color: var(--accent-primary);
    box-shadow: 0 0 0 3px var(--border-focus);
}

.form-control::placeholder { color: var(--text-muted); }

select.form-control {
    appearance: none;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23888' d='M6 8L1 3h10z'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 0.75rem center;
    padding-right: 2rem;
    cursor: pointer;
}

select.form-control option {
    background: var(--bg-primary);
    color: var(--text-primary);
}

.input {
    width: 100%;
    padding: 0.5rem 0.75rem;
    font-family: inherit;
    font-size: 0.8125rem;
    line-height: 1.5;
    border: 1px solid var(--border-primary);
    border-radius: var(--radius-sm);
    background: var(--bg-input);
    color: var(--text-primary);
    transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
    outline: none;
}

.input:focus {
    border-color: var(--accent-primary);
    box-shadow: 0 0 0 3px var(--border-focus);
}

.input::placeholder { color: var(--text-muted); }

.input-lg {
    padding: 0.625rem 0.875rem;
    font-size: 0.875rem;
}

/* .input-sm llevaba ~29 usos en JS (almacen, fabricacion, productos, soporte —
   8 de ellos nuevos en el modal de cierre 1→N y la fila de salidas) y no estaba
   definida en ningún CSS: era una clase muerta, todos esos campos se pintaban
   con el tamaño de .input y desbordaban las rejillas de columna estrecha.
   Escalón simétrico al de .input-lg, no un tamaño inventado. */
.input-sm {
    padding: 0.3125rem 0.5rem;
    font-size: 0.75rem;
}

textarea.input {
    min-height: 100px;
    resize: vertical;
}

select.input {
    cursor: pointer;
    appearance: none;
    /* Arrow color: #9ba1a8 = --text-secondary (SVG data-URI can't use var()) */
    background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%239ba1a8' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");
    background-position: right 0.625rem center;
    background-repeat: no-repeat;
    background-size: 1.25em 1.25em;
    padding-right: 2.5rem;
}
/* Light: la flecha #9ba1a8 da 2.4:1 sobre blanco (invisible). Data-URI no admite
   var() → se reemplaza el SVG entero con el gris de --text-tertiary (#666c75). */
[data-theme="light"] select.input {
    background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%23666c75' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");
}
/* Ajuste de la flecha SOLO donde existe flecha propia, o sea donde el select
   lleva TAMBIÉN `.input` (que es quien pone `appearance:none` + el SVG de fondo).
   Con el selector suelto `select.input-sm` esto también cogía a los tres selects
   de soporte.js, que se escriben `class="input-sm"` a pelo: ahí NO hay
   appearance:none, así que la flecha es la NATIVA del sistema —pegada al borde—
   y el padding-right de 1.9rem solo abría un hueco muerto delante de ella.
   `select.input.input-sm` (0,2,1) gana además a `select.input` (0,1,1) por
   especificidad, no por orden en el fichero. */
select.input.input-sm {
    padding-right: 1.9rem;
    background-position: right 0.5rem center;
    background-size: 1.05em 1.05em;
}

/* =====================================================
   CARDS
   ===================================================== */
.card {
    background: var(--bg-card);
    border: 1px solid var(--border-secondary);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-card);
    overflow: hidden;
    transition: all var(--transition-normal);
}

.card:hover {
    /* subtle lift on hover - can be overridden */
}

/* Utility hover modifiers — usados en lugar de onmouseenter/leave inline.
   Audit 26-05 §4.9 🟠: estos efectos vivían en `onmouseenter=...` inline (módulo
   calidad), violando CSP layer 2. Migrados a clases utility. */
.card-hover-lift {
    cursor: pointer;
}
.card-hover-lift:hover {
    transform: translateY(-1px);
    box-shadow: var(--shadow-md);
}
.card-hover-border:hover {
    border-color: var(--primary);
}

.card-header {
    padding: var(--space-4) var(--space-5);
    border-bottom: 1px solid var(--border-secondary);
    display: flex;
    align-items: center;
    justify-content: space-between;
}

.card-header h3, .card-header h4 { margin: 0; }

.card-body { padding: var(--space-5); }

.card-footer {
    padding: var(--space-3) var(--space-5);
    border-top: 1px solid var(--border-secondary);
    background: var(--bg-secondary);
}

/* =====================================================
   KPI / STAT CARDS
   ===================================================== */
.stat-card {
    background: var(--bg-card);
    border: 1px solid var(--border-secondary);
    border-radius: var(--radius-lg);
    padding: 24px;
    box-shadow: var(--shadow-card);
    transition: all var(--transition-normal);
    position: relative;
    overflow: hidden;
    /* CANON v6.2: altura mínima común + reparto vertical → todas las tarjetas
       del ERP miden igual aunque unas lleven sparkline y otras no. */
    min-height: 132px;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
}

.stat-card:hover {
    border-color: var(--border-focus);
    box-shadow: var(--shadow-md);
    transform: translateY(-1px);
}

.stat-card-header {
    display: flex;
    align-items: flex-start;
    justify-content: flex-end;
    margin-bottom: 12px;
    min-height: 18px;
}

/* v6.1 canon (2026-07-25, decisión Adri): la tarjeta KPI canónica NO lleva
   chip de icono — label mono + número blanco tabular + delta arriba-derecha.
   display:none gana a cualquier background/color inline de los módulos. */
.stat-card-icon { display: none !important; }

.stat-card-icon {
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
}

/* Botón ⚙ configurar KPI — visible en hover (desktop) o siempre (touch) */
.kpi-settings-btn {
    background: none;
    border: none;
    padding: 4px;
    cursor: pointer;
    color: var(--text-muted);
    opacity: 0.4;
    transition: opacity 0.2s, color 0.2s;
    border-radius: var(--radius-sm);
    display: flex;
    align-items: center;
    justify-content: center;
}
.kpi-settings-btn:hover {
    opacity: 1;
    color: var(--text-primary);
    background: var(--bg-secondary);
}
.stat-card:hover .kpi-settings-btn {
    opacity: 0.7;
}
/* Touch devices: siempre visible */
@media (hover: none) and (pointer: coarse) {
    .kpi-settings-btn {
        opacity: 0.6;
    }
}

/* Iconos de KPI — neutros gris/negro (directriz Adri 2026-07-24): el color
   queda para el dato (sparkline, delta), no para el icono. Las variantes
   icon-* se conservan como selectores pero todas rinden neutro. */
.stat-card-icon.icon-green,
.stat-card-icon.icon-red,
.stat-card-icon.icon-blue,
.stat-card-icon.icon-orange,
.stat-card-icon.icon-purple,
.stat-card-icon.icon-cyan {
    background: var(--bg-tertiary);
    color: var(--text-secondary);
}

.stat-card-value {
    font-family: var(--font-display);
    font-size: 1.75rem;
    font-weight: 700;
    letter-spacing: -0.02em;
    font-variant-numeric: tabular-nums;
    line-height: 1.2;
    color: var(--text-primary) !important; /* v6.1: números siempre en blanco/tinta */
}

.stat-card-label {
    font-family: var(--font-mono);
    font-size: 0.6563rem;
    color: var(--text-tertiary);
    margin-top: 4px;
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: 0.07em;
}

.stat-card-change {
    font-size: 0.75rem;
    font-weight: 600;
    margin-top: var(--space-1);
}
.stat-card-change.positive { color: var(--kpi-positive); }
.stat-card-change.negative { color: var(--kpi-negative); }

/* Variation badge */
.kpi-badge {
    display: inline-flex;
    align-items: center;
    gap: 2px;
    padding: 2px 8px;
    font-size: 12px;
    font-weight: 600;
    border-radius: var(--radius-full);
}
.kpi-badge.up {
    background: color-mix(in srgb, var(--kpi-positive) 10%, transparent);
    color: var(--kpi-positive);
}
.kpi-badge.down {
    background: color-mix(in srgb, var(--kpi-negative) 10%, transparent);
    color: var(--kpi-negative);
}

/* Sparkline container */
.kpi-sparkline {
    margin-top: 12px;
    height: 48px;
    width: 100%;
}
.kpi-sparkline canvas {
    width: 100% !important;
    height: 48px !important;
}

/* Solid-color KPI variant (CoreUI-style) */
.stat-card.kpi-solid {
    border: none;
    color: #fff;
    box-shadow: var(--shadow-sm);
}
.stat-card.kpi-solid .stat-card-value {
    color: #fff;
}
.stat-card.kpi-solid .stat-card-label {
    color: rgba(255,255,255,0.75);
}
.stat-card.kpi-solid:hover {
    border-color: transparent;
    box-shadow: var(--shadow-md);
}

/* ── Top Vendedores cards ── */
.vendedor-card {
    background: var(--bg-card);
    border: 1px solid var(--border-secondary);
    border-radius: var(--radius-lg);
    padding: 20px;
    transition: all var(--transition-normal);
}
.vendedor-card:hover {
    border-color: var(--border-focus);
    box-shadow: var(--shadow-md);
    transform: translateY(-1px);
}
.vendedor-avatar {
    width: 44px;
    height: 44px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    font-weight: 700;
    font-size: 0.9rem;
    flex-shrink: 0;
}
.vendedor-progress {
    height: 6px;
    border-radius: 3px;
    background: var(--border-primary);
    overflow: hidden;
    margin-top: 8px;
}
.vendedor-progress-bar {
    height: 100%;
    border-radius: 3px;
    background: var(--accent-fill);   /* relleno de marca (los módulos pueden recolorearlo) */
    transition: width 0.5s;
}
.vendedor-cats {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-top: 10px;
}
.vendedor-cat {
    display: flex;
    align-items: center;
    gap: 4px;
    font-size: 11px;
    color: var(--text-secondary);
    font-family: var(--font-ui);
}
.vendedor-cat-dot {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    flex-shrink: 0;
}

/* =====================================================
   BADGES / PILLS
   ===================================================== */
.badge {
    display: inline-flex;
    align-items: center;
    padding: 3px 10px;
    font-family: var(--font-mono);
    font-size: 0.625rem;
    font-weight: 600;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    border-radius: var(--radius-full);
    background: var(--bg-tertiary);
    color: var(--text-secondary);
    white-space: nowrap;
}

.badge-primary { background: color-mix(in srgb, var(--accent-primary) 12%, transparent); color: var(--accent-primary); }
.badge-success { background: color-mix(in srgb, var(--accent-green) 12%, transparent); color: var(--accent-green); }
.badge-danger  { background: color-mix(in srgb, var(--accent-red) 12%, transparent); color: var(--accent-red); }
.badge-warning { background: color-mix(in srgb, var(--accent-orange) 12%, transparent); color: var(--accent-orange); }
.badge-info    { background: color-mix(in srgb, var(--accent-blue) 12%, transparent); color: var(--accent-blue); }

/* =====================================================
   ALERTS — avisos en bloque
   =====================================================
   Existían ~6 usos de `.alert` + `.alert-warning/-info/-success` en JS
   (configuracion, facturacion, calidad) y NINGUNA regla en ningún CSS: el aviso
   se renderizaba como texto suelto, sin caja ni color. Se notaba sobre todo en
   una empresa NO española, donde el bloque "VeriFactu/SII no aplica" es lo ÚNICO
   que se pinta en esas dos pestañas — quedaba un párrafo huérfano.
   Mismo lenguaje visual que .badge-*: tinte 12% del token + hairline del mismo
   token + texto en el token (conmutado, luego AA en dark y en light). */
.alert {
    display: block;
    padding: 10px 14px;
    border: 1px solid var(--border-primary);
    border-radius: var(--radius-md);
    background: var(--bg-tertiary);
    color: var(--text-secondary);
    font-family: var(--font-ui);
    font-size: 0.8125rem;
    line-height: 1.5;
}
/* El icono lucide va como primer hijo inline, seguido del texto. Los usos que
   quieren icono centrado ya aplican display:flex+gap por estilo inline. */
.alert > svg,
.alert > [data-lucide] { flex-shrink: 0; }
.alert strong { font-weight: 700; }

.alert-info    { background: color-mix(in srgb, var(--accent-blue) 12%, transparent);    border-color: color-mix(in srgb, var(--accent-blue) 32%, transparent);    color: var(--accent-blue); }
.alert-success { background: color-mix(in srgb, var(--accent-green) 12%, transparent);   border-color: color-mix(in srgb, var(--accent-green) 32%, transparent);   color: var(--accent-green); }
.alert-warning { background: color-mix(in srgb, var(--accent-orange) 12%, transparent);  border-color: color-mix(in srgb, var(--accent-orange) 32%, transparent);  color: var(--accent-orange); }
.alert-danger  { background: color-mix(in srgb, var(--accent-red) 12%, transparent);     border-color: color-mix(in srgb, var(--accent-red) 32%, transparent);     color: var(--accent-red); }

/* Autocomplete dropdown */
.autocomplete-dropdown {
    position: absolute; top: 100%; left: 0; right: 0; z-index: 100;
    background: var(--bg-secondary); border: 1px solid var(--border-primary);
    border-radius: 8px; box-shadow: var(--shadow-lg); max-height: 300px; overflow-y: auto;
    margin-top: 4px;
}
.autocomplete-dropdown .autocomplete-item:hover {
    background: var(--bg-tertiary);
}

/* Status pills */
.status-pill {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 4px 12px;
    font-family: var(--font-mono);
    font-size: 0.625rem;
    font-weight: 600;
    border-radius: 20px;
    letter-spacing: 0.05em;
    text-transform: uppercase;
}
.status-pill::before {
    content: '';
    width: 6px;
    height: 6px;
    border-radius: 50%;
    flex-shrink: 0;
    background: currentColor;
}
.status-pill.active { background: color-mix(in srgb, var(--accent-green) 10%, transparent); color: var(--accent-green); }
.status-pill.inactive { background: color-mix(in srgb, var(--accent-red) 10%, transparent); color: var(--accent-red); }
.status-pill.pending { background: color-mix(in srgb, var(--accent-orange) 10%, transparent); color: var(--accent-orange); }

/* =====================================================
   TABLES
   ===================================================== */
.table-container {
    overflow-x: auto;
    border-radius: var(--radius-md);
}

table {
    width: 100%;
    border-collapse: collapse;
}

th, td {
    padding: 0.625rem 1rem;
    text-align: left;
}

th {
    font-family: var(--font-mono);
    font-size: 0.625rem;
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: 0.07em;
    color: var(--text-tertiary);
    background: var(--bg-secondary);
    border-bottom: 1px solid var(--border-primary);
    position: sticky;
    top: 0;
    z-index: 1;
}

td {
    font-size: 0.8125rem;
    border-bottom: 1px solid var(--border-secondary);
    color: var(--text-primary);
    font-variant-numeric: tabular-nums;
}

td strong { color: var(--text-primary); }

tr:last-child td { border-bottom: none; }

/* Alternating rows */
tbody tr:nth-child(even) td {
    background: var(--bg-hover);
}

/* Hover */
tbody tr {
    transition: background var(--transition-fast);
}

tbody tr:hover td {
    background: var(--bg-active);
}

/* =====================================================
   MODALS
   ===================================================== */
.modal-overlay {
    position: fixed;
    inset: 0;
    background: var(--bg-overlay);
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--space-4);
    z-index: 1000;
    opacity: 0;
    visibility: hidden;
    transition: all var(--transition-normal);
}

.modal-overlay.active {
    opacity: 1;
    visibility: visible;
}

/* #genericModal es el modal de PREGUNTA de la aplicación: cuando está activo, es
   porque algo espera una respuesta del usuario, así que tiene que quedar por
   encima del modal que lo abrió.

   Sin esta línea el aviso de destinatario (2026-09-06) era peor que no existir.
   Los dos modales de envío —#emailModal (configuracion.js) y #facEmailLoteModal
   (facturacion.js)— se crean con `document.body.appendChild`, o sea DESPUÉS de
   #genericModal, que vive en index.html. Con el mismo z-index gana el último del
   DOM: el aviso se pintaba DETRÁS, el usuario no veía nada nuevo, y como el
   helper devuelve `true` («espera al usuario») el envío no salía. Un botón que
   no hace nada, sin error ni pista. La regla dice «advierte, no autoriza» y el
   apilado la convertía en «bloquea, y en silencio».

   1200 y no 1100: en mobile.css la banda 1100 ya la ocupa .mobile-slidedown. Y no
   hace falta subir a la familia 99xx de los modales flotantes: los dos overlays
   que hay que superar son .modal-overlay, que es 1000. */
#genericModal {
    z-index: 1200;
}

.modal {
    background: var(--bg-primary);
    border: 1px solid var(--border-primary);
    border-radius: var(--radius-xl);
    box-shadow: var(--shadow-xl);
    width: 100%;
    max-width: 520px;
    max-height: 90vh;
    overflow: hidden;
    display: flex;
    flex-direction: column;
    transform: scale(0.95) translateY(16px);
    transition: transform var(--transition-normal);
}

.modal-overlay.active .modal {
    transform: scale(1) translateY(0);
}

.modal-header {
    padding: var(--space-5) var(--space-6);
    border-bottom: 1px solid var(--border-secondary);
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-shrink: 0;
}

.modal-title {
    font-size: 1rem;
    font-weight: 600;
}

.modal-close {
    width: 28px;
    height: 28px;
    display: flex;
    align-items: center;
    justify-content: center;
    border: none;
    background: var(--bg-tertiary);
    border-radius: var(--radius-full);
    cursor: pointer;
    font-size: 0.8125rem;
    color: var(--text-secondary);
    transition: all var(--transition-fast);
}

.modal-close:hover {
    background: var(--accent-red);
    color: var(--accent-red-contrast);
}

.modal-body {
    padding: var(--space-5) var(--space-6);
    overflow-y: auto;
    flex: 1;
}

.modal-footer {
    padding: var(--space-4) var(--space-6);
    border-top: 1px solid var(--border-secondary);
    display: flex;
    justify-content: flex-end;
    gap: var(--space-3);
    background: var(--bg-secondary);
    position: relative;
    z-index: 2;
    flex-shrink: 0;
}

/* =====================================================
   TOAST
   ===================================================== */
.toast-container {
    position: fixed;
    bottom: var(--space-8);
    left: 50%;
    transform: translateX(-50%);
    /* DP-271: por encima de los modales flotantes (z-index 9990). Si no, los
       toasts de confirmación/error quedaban tapados por los modales de API
       Integradas (VeriFactu, Shopify, Stamp) — el usuario no veía el resultado. */
    z-index: 10000;
    /* DP-257: el contenedor de toasts no debe interceptar clicks; tapaba el
       boton COBRAR del TPV movil. Los toasts no son interactivos. */
    pointer-events: none;
}

.toast {
    background: var(--bg-primary);
    color: var(--text-primary);
    border: 1px solid var(--border-primary);
    padding: 0.625rem 1.25rem;
    border-radius: var(--radius-full);
    font-size: 0.8125rem;
    font-weight: 500;
    box-shadow: var(--shadow-lg);
    opacity: 0;
    transform: translateY(16px) scale(0.96);
    transition: all var(--transition-normal);
}

.toast.show {
    opacity: 1;
    transform: translateY(0) scale(1);
}

/* =====================================================
   EMPTY STATES
   ===================================================== */
.empty-state {
    text-align: center;
    padding: var(--space-12) var(--space-8);
}

.empty-state-icon {
    font-size: 2.5rem;
    margin-bottom: var(--space-4);
    opacity: 0.6;
}

.empty-state-title {
    font-size: 1rem;
    font-weight: 600;
    margin-bottom: var(--space-2);
    color: var(--text-secondary);
}

/* =====================================================
   SPINNER
   ===================================================== */
.spinner {
    width: 24px;
    height: 24px;
    border: 2.5px solid var(--border-secondary);
    border-top-color: var(--accent-primary);
    border-radius: 50%;
    animation: spin 0.7s linear infinite;
}

@keyframes spin { to { transform: rotate(360deg); } }
@keyframes fadeIn {
    from { opacity: 0; transform: translateY(6px); }
    to   { opacity: 1; transform: translateY(0); }
}
@keyframes slideIn {
    from { opacity: 0; transform: translateX(-8px); }
    to   { opacity: 1; transform: translateX(0); }
}
@keyframes fadeInRow {
    from { opacity: 0; background: color-mix(in srgb, var(--accent-primary) 8%, transparent); }
    to   { opacity: 1; background: transparent; }
}

/* =====================================================
   BREADCRUMBS
   ===================================================== */
.breadcrumb {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    font-size: 0.875rem;
    font-weight: 500;
}

.breadcrumb-item { color: var(--text-primary); }

.breadcrumb-item:first-child {
    color: var(--text-tertiary);
    font-weight: 400;
    transition: color var(--transition-fast);
    cursor: pointer;
}

.breadcrumb-item:first-child:hover {
    color: var(--accent-primary);
}

.breadcrumb-sep {
    color: var(--text-muted);
    font-weight: 300;
    margin: 0 2px;
}

/* =====================================================
   TABS
   ===================================================== */
.tabs {
    display: flex;
    gap: var(--space-1);
    margin-bottom: var(--space-6);
    border-bottom: 1px solid var(--border-secondary);
    padding-bottom: 0;
}

.tab {
    padding: var(--space-3) var(--space-4);
    font-family: inherit;
    font-size: 0.8125rem;
    font-weight: 500;
    color: var(--text-tertiary);
    background: transparent;
    border: none;
    border-bottom: 2px solid transparent;
    cursor: pointer;
    transition: all var(--transition-fast);
    margin-bottom: -1px;
}

.tab:hover {
    color: var(--text-primary);
}

.tab.active {
    color: var(--accent-primary);
    border-bottom-color: var(--accent-primary);
    background: transparent;
}

/* =====================================================
   LAYOUT — LOGIN
   ===================================================== */
.login-wrapper {
    min-height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--bg-app);
    padding: var(--space-6);
}

.login-card {
    width: 100%;
    max-width: 380px;
    background: var(--bg-card);
    border: 1px solid var(--border-primary);
    border-radius: var(--radius-2xl);
    box-shadow: var(--shadow-xl);
    padding: 44px 36px;
}

.login-header {
    text-align: center;
    margin-bottom: var(--space-8);
}

.login-logo-img {
    width: 180px;
    height: auto;
    margin-bottom: var(--space-4);
    /* Nitidez: sin glow — render crisp */
    filter: none;
}

/* Marca de login v6: cubo volt + wordmark Space Grotesk (texto real, nítido) */
.login-mark {
    width: 56px;
    height: 56px;
    border-radius: 14px;
    margin: 0 auto var(--space-4);
    display: block;
}
.login-wordmark {
    font-family: var(--font-display);
    font-size: 2.4rem;
    font-weight: 600;
    letter-spacing: 0.08em;
    margin-right: -0.08em; /* compensa el tracking del último carácter */
    text-align: center;
    line-height: 1;
    /* Wordmark web: degradado plateado (home-src) + halo volt sutil */
    background: linear-gradient(176deg, #ffffff 0%, #d6dadf 50%, #9aa1a9 100%);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    filter: drop-shadow(0 8px 28px color-mix(in srgb, var(--accent-primary) 18%, transparent));
}
[data-theme="light"] .login-wordmark {
    background: linear-gradient(176deg, #15181d 0%, #3a3f46 55%, #6a7079 100%);
    -webkit-background-clip: text;
    background-clip: text;
    filter: none;
}
.login-kicker {
    font-family: var(--font-mono);
    font-size: 0.625rem;
    font-weight: 500;
    letter-spacing: 0.42em;
    margin-right: -0.42em;
    text-transform: uppercase;
    color: var(--accent-primary);
    text-align: center;
    margin-top: var(--space-3);
}

.login-subtitle {
    color: var(--text-tertiary);
    font-size: 0.8125rem;
    margin-top: var(--space-1);
}

.login-error {
    padding: var(--space-3) var(--space-4);
    background: color-mix(in srgb, var(--accent-red) 10%, transparent);
    border: 1px solid color-mix(in srgb, var(--accent-red) 22%, transparent);
    border-radius: var(--radius-sm);
    color: var(--accent-red);
    font-size: 0.8125rem;
    margin-bottom: var(--space-4);
    display: none;
}

.login-error.show { display: block; }

/* Shake en credenciales incorrectas (patrón OTP v5) */
@keyframes verifyShake {
    0%, 100% { transform: translateX(0); }
    20% { transform: translateX(-9px); }
    40% { transform: translateX(8px); }
    60% { transform: translateX(-6px); }
    80% { transform: translateX(4px); }
}
.login-card.shake { animation: verifyShake 0.4s ease; }

/* =====================================================
   VERIFICACIÓN — overlay animado (login / PIN / contraseña)
   Patrón OTP v5: check dibujado + anillo pulsante volt.
   ===================================================== */
.login-card, .verify-host { position: relative; }
.verify-overlay {
    position: absolute;
    inset: 0;
    z-index: 20;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 16px;
    background: var(--bg-card);
    border-radius: inherit;
    animation: fadeIn 0.2s var(--ease-out);
}
.verify-ring {
    width: 68px;
    height: 68px;
    border-radius: 50%;
    border: 2px solid var(--accent-primary);
    display: flex;
    align-items: center;
    justify-content: center;
    animation: verifyRingIn 0.5s var(--ease-spring) both;
    position: relative;
}
.verify-ring::after {
    content: '';
    position: absolute;
    inset: -2px;
    border-radius: 50%;
    border: 2px solid var(--accent-primary);
    animation: verifyPulse 1.1s ease-out 0.35s infinite;
}
.verify-ring svg {
    width: 30px;
    height: 30px;
    stroke: var(--accent-primary);
    stroke-width: 3;
    fill: none;
    stroke-linecap: round;
    stroke-linejoin: round;
    stroke-dasharray: 30;
    stroke-dashoffset: 30;
    animation: verifyDraw 0.4s ease-out 0.25s forwards;
}
.verify-title {
    font-family: var(--font-display);
    font-size: 1.05rem;
    font-weight: 600;
    color: var(--text-primary);
    animation: fadeIn 0.3s ease-out 0.3s both;
}
.verify-sub {
    font-family: var(--font-mono);
    font-size: 0.625rem;
    font-weight: 500;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--accent-primary);
    display: flex;
    align-items: center;
    gap: 6px;
    animation: fadeIn 0.3s ease-out 0.42s both;
}
@keyframes verifyRingIn {
    0% { opacity: 0; transform: scale(0.4); }
    100% { opacity: 1; transform: scale(1); }
}
@keyframes verifyPulse {
    0% { opacity: 0.7; transform: scale(1); }
    100% { opacity: 0; transform: scale(1.55); }
}
@keyframes verifyDraw { to { stroke-dashoffset: 0; } }
@media (prefers-reduced-motion: reduce) {
    .verify-overlay, .verify-ring, .verify-ring::after, .verify-ring svg,
    .verify-title, .verify-sub, .login-card.shake { animation: none !important; }
    .verify-ring svg { stroke-dashoffset: 0; }
}

.passkey-divider {
    display: flex;
    align-items: center;
    margin: 16px 0;
    gap: 12px;
}
.passkey-divider::before,
.passkey-divider::after {
    content: '';
    flex: 1;
    border-top: 1px solid var(--border);
}
.passkey-divider span {
    color: var(--text-muted);
    font-size: 13px;
    text-transform: uppercase;
    letter-spacing: 1px;
}

/* =====================================================
   LAYOUT — HOLDING VIEW
   ===================================================== */
.holding-wrapper {
    min-height: 100vh;
    background: var(--bg-app);
    padding: var(--space-6);
}

.holding-header {
    max-width: 1200px;
    margin: 0 auto var(--space-6);
    display: flex;
    align-items: center;
    justify-content: space-between;
}

.holding-title {
    display: flex;
    align-items: center;
    gap: var(--space-4);
}

.holding-title h1 {
    font-size: 1.375rem;
    letter-spacing: -0.02em;
}

/* =====================================================
   LAYOUT — EMPRESAS GRID
   ===================================================== */
.empresas-grid {
    max-width: 1200px;
    margin: 0 auto var(--space-6);
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
    gap: var(--space-4);
}

.empresa-card {
    background: var(--bg-card);
    border: 1px solid var(--border-secondary);
    border-radius: var(--radius-lg);
    padding: var(--space-5);
    cursor: pointer;
    transition: all var(--transition-normal);
    box-shadow: var(--shadow-card);
    position: relative;
}

.empresa-card:hover {
    border-color: var(--border-focus);
    box-shadow: var(--shadow-md);
    transform: translateY(-1px);
}

.empresa-card.selected {
    border-color: var(--accent-primary);
    box-shadow: 0 0 0 2px var(--accent-primary);
}

.empresa-card-header {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    margin-bottom: var(--space-3);
}

.empresa-icon {
    width: 72px;
    height: 72px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--bg-primary);
    border-radius: var(--radius-lg);
    flex-shrink: 0;
    color: var(--text-tertiary);
    border: 1px solid var(--border-secondary);
}

.empresa-icon-logo {
    width: 72px;
    height: 72px;
    object-fit: contain;
    border-radius: var(--radius-lg);
    flex-shrink: 0;
    border: 1px solid var(--border-secondary);
    background: transparent;
    padding: 4px;
}

.empresa-info h3 {
    font-size: 0.9375rem;
    margin-bottom: 2px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.empresa-info p {
    font-size: 0.75rem;
    color: var(--text-tertiary);
}

.empresa-stats {
    display: flex;
    gap: var(--space-4);
    font-size: 0.75rem;
    color: var(--text-secondary);
}

.empresa-add {
    background: transparent;
    border: 1.5px dashed var(--border-primary);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    min-height: 140px;
    color: var(--text-muted);
    box-shadow: none;
}

.empresa-add:hover {
    border-color: var(--accent-primary);
    color: var(--accent-primary);
    background: var(--bg-hover);
    box-shadow: none;
    transform: none;
}

.empresa-add-icon {
    font-size: 1.75rem;
    margin-bottom: var(--space-2);
}

/* =====================================================
   LAYOUT — APP (sidebar + main)
   ===================================================== */
.app {
    display: flex;
    height: 100vh;
    overflow: hidden;
}

/* =====================================================
   SIDEBAR
   ===================================================== */
.sidebar {
    width: var(--sidebar-width);
    background: var(--bg-sidebar);
    border-right: 1px solid var(--border-secondary);
    display: flex;
    flex-direction: column;
    flex-shrink: 0;
    transition: width var(--transition-normal);
    overflow: hidden;
    position: relative;
    z-index: 10;
}

.sidebar.collapsed { width: var(--sidebar-collapsed); }

.sidebar-header {
    height: var(--header-height);
    padding: 0 var(--space-4);
    display: flex;
    align-items: center;
    gap: var(--space-3);
    border-bottom: 1px solid var(--border-secondary);
    cursor: pointer;
    flex-shrink: 0;
    transition: all var(--transition-fast);
}

.sidebar-header:hover {
    background: var(--bg-hover);
}

.sidebar-logo-img {
    width: 30px;
    height: 30px;
    object-fit: contain;
    flex-shrink: 0;
    border-radius: 8px;
}

.sidebar-brand {
    font-family: var(--font-display);
    font-size: 0.9375rem;
    font-weight: 600;
    letter-spacing: 0.14em;
    /* Mismo wordmark plateado de la web, a escala nav */
    background: linear-gradient(176deg, #ffffff 0%, #d6dadf 55%, #9aa1a9 100%);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
}

/* Wordmark «AI» del toggle Agente IA (ficha de empresa, Panel SaaS): mismos
   stops plateados que .login-wordmark, a escala etiqueta. Peso 700 y 0.875rem
   para que el degradado no pierda contraste en light sobre la tarjeta. */
.ai-wordmark {
    font-family: var(--font-display);
    font-size: 0.875rem;
    font-weight: 700;
    line-height: 1;
    letter-spacing: 0.12em;
    margin-right: -0.12em;
    background: linear-gradient(176deg, #ffffff 0%, #d6dadf 50%, #9aa1a9 100%);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
}
[data-theme="light"] .ai-wordmark {
    background: linear-gradient(176deg, #15181d 0%, #3a3f46 55%, #6a7079 100%);
    -webkit-background-clip: text;
    background-clip: text;
}

.sidebar-empresa-logo {
    width: 28px;
    height: 28px;
    object-fit: contain;
    border-radius: 6px;
    flex-shrink: 0;
    margin-left: auto;
    border: 1px solid var(--border);
}

.sidebar.collapsed .sidebar-brand,
.sidebar.collapsed .sidebar-empresa-logo,
.sidebar.collapsed .nav-section-title,
.sidebar.collapsed .nav-item-label { display: none; }

.sidebar-nav {
    flex: 1;
    padding: var(--space-3);
    overflow-y: auto;
    position: relative; /* offsetParent del bead y de los .nav-item (v6 MOVIMIENTO) */
}

/* v6 · MOVIMIENTO — anillo de luz que se desliza hasta el módulo activo. UNA sola capa
   transformada; app.js escribe --bead-y (offsetTop relativo a .sidebar-nav) y height. */
.nav-bead {
    position: absolute;
    left: 6px;
    top: 0;
    width: 3px;
    height: 22px;
    border-radius: 2px;
    background: var(--accent-fill);
    box-shadow: 0 0 14px color-mix(in srgb, var(--accent-fill) 55%, transparent);
    transform: translateY(var(--bead-y, 0px));
    transition: transform 0.28s var(--ease-spring), height 0.28s var(--ease-out), opacity var(--transition-fast);
    opacity: 0;
    pointer-events: none;
}
.sidebar-nav.has-active .nav-bead { opacity: 1; }

.nav-section { margin-bottom: var(--space-4); }

.nav-section-title {
    font-family: var(--font-mono);
    font-size: 0.5938rem;
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: 0.12em;
    color: var(--text-muted);
    padding: 0 var(--space-3);
    margin-bottom: var(--space-2);
}

.nav-item {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: 0.5rem 0.75rem;
    border-radius: var(--radius-sm);
    color: var(--text-secondary);
    font-size: 0.8125rem;
    font-weight: 500;
    cursor: pointer;
    transition: all var(--transition-fast);
    margin-bottom: 1px;
    position: relative;
}

.nav-item:hover {
    background: var(--bg-hover);
    color: var(--text-primary);
}

/* v6 · MOVIMIENTO: tinte suave + texto del tema — el bead es el protagonista, no un
   bloque lima. Contraste light: --text-primary sobre lima al 14% sobre blanco ≥ 12:1. */
.nav-item.active {
    background: color-mix(in srgb, var(--accent-fill) 14%, transparent);
    color: var(--text-primary);
    box-shadow: none;
    font-weight: 600;
}
.nav-item.active .nav-item-icon { color: var(--accent-primary); }

.nav-item-icon {
    font-size: 1rem;
    width: 22px;
    text-align: center;
    flex-shrink: 0;
}

.sidebar.collapsed .nav-item {
    justify-content: center;
    padding: 0.625rem;
    position: relative;
}

/* Tooltip on hover when sidebar is collapsed */
.sidebar.collapsed .nav-item::after {
    content: attr(data-tooltip);
    position: absolute;
    left: calc(100% + 8px);
    top: 50%;
    transform: translateY(-50%);
    background: var(--bg-primary);
    color: var(--text-primary);
    padding: 4px 10px;
    border-radius: var(--radius-sm);
    font-size: 0.8rem;
    white-space: nowrap;
    pointer-events: none;
    opacity: 0;
    transition: opacity 0.15s;
    box-shadow: var(--shadow-sm);
    border: 1px solid var(--border);
    z-index: 100;
}

.sidebar.collapsed .nav-item:hover::after {
    opacity: 1;
}

/* iPad/táctil (2026-08-02): el tooltip por :hover convertía el PRIMER tap en
   hover (iPadOS no manda el click cuando el hover revela contenido) — "selecciono
   TPV y a la primera no reacciona". Sin puntero fino no hay tooltip: tap directo. */
@media (hover: none), (pointer: coarse) {
    .sidebar.collapsed .nav-item::after { display: none; }
}

.sidebar-footer {
    padding: var(--space-3);
    border-top: 1px solid var(--border-secondary);
    flex-shrink: 0;
}

/* v6.1 — handle sutil a media altura para contraer/expandir la sidebar */
.sidebar-collapse-handle {
    position: absolute;
    top: 50%;
    right: 4px;
    transform: translateY(-50%);
    width: 20px;
    height: 44px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: transparent;
    border: none;
    border-radius: var(--radius-full);
    color: var(--text-muted);
    cursor: pointer;
    opacity: 0;
    transition: opacity var(--transition-fast), color var(--transition-fast), background var(--transition-fast);
    z-index: 20;
    padding: 0;
}
.sidebar:hover .sidebar-collapse-handle { opacity: 1; }
.sidebar-collapse-handle:hover {
    color: var(--accent-primary);
    background: var(--bg-hover);
}
.sidebar-collapse-handle svg { transition: transform var(--transition-normal); }
.sidebar.collapsed .sidebar-collapse-handle svg { transform: rotate(180deg); }
@media (max-width: 1024px) {
    .sidebar-collapse-handle { display: none; } /* en tablet/móvil la sidebar es off-canvas */
}

/* =====================================================
   LAYOUT — MAIN AREA
   ===================================================== */
.main {
    flex: 1;
    display: flex;
    flex-direction: column;
    overflow: hidden;
    min-width: 0;
}

.header {
    height: var(--header-height);
    background: var(--bg-primary);
    border-bottom: 1px solid var(--border-secondary);
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0 var(--space-6);
    flex-shrink: 0;
}

.header-left {
    display: flex;
    align-items: center;
    gap: var(--space-4);
    min-width: 0;
}

.header-title {
    font-size: 0.9375rem;
    font-weight: 600;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.header-right {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    flex-shrink: 0;
}

.header-time {
    font-size: 0.75rem;
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
}

.theme-toggle {
    width: 32px;
    height: 32px;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 0.875rem;
    background: transparent;
    border: none;
    border-radius: var(--radius-sm);
    cursor: pointer;
    color: var(--text-secondary);
    transition: all var(--transition-fast);
}

.theme-toggle:hover {
    background: var(--bg-hover);
    color: var(--text-primary);
}

/* Notification bell */
.notification-bell {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 34px;
    height: 34px;
    border-radius: 50%;
    cursor: pointer;
    transition: all var(--transition-fast);
    color: var(--text-secondary);
}
.notification-bell:hover {
    background: var(--bg-tertiary);
    color: var(--text-primary);
}
.notif-badge {
    position: absolute;
    top: 2px;
    right: 2px;
    min-width: 16px;
    height: 16px;
    padding: 0 4px;
    border-radius: 8px;
    background: var(--danger);
    color: var(--accent-red-contrast);
    font-size: 10px;
    font-weight: 700;
    display: flex;
    align-items: center;
    justify-content: center;
    line-height: 1;
}
.notif-badge.hidden { display: none; }

/* Notification panel */
.notif-panel {
    position: fixed;
    top: 56px;
    right: 16px;
    width: 380px;
    max-height: calc(100vh - 80px);
    background: var(--bg-primary);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-lg);
    z-index: 1100;
    display: flex;
    flex-direction: column;
    overflow: hidden;
}
.notif-panel.hidden { display: none; }
.notif-panel-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 12px 16px;
    border-bottom: 1px solid var(--border);
}
.notif-panel-header h4 { margin: 0; font-size: 14px; }
.notif-panel-body {
    overflow-y: auto;
    max-height: calc(100vh - 140px);
}
.notif-item {
    display: flex;
    gap: 12px;
    padding: 12px 16px;
    border-bottom: 1px solid var(--border-secondary);
    cursor: pointer;
    transition: background var(--transition-fast);
}
.notif-item:hover { background: var(--bg-secondary); }
.notif-item.unread { background: color-mix(in srgb, var(--primary) 5%, var(--bg-primary)); }
.notif-item .notif-icon {
    flex-shrink: 0;
    width: 32px;
    height: 32px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 14px;
}
.notif-item .notif-icon.info { background: color-mix(in srgb, var(--info) 15%, transparent); color: var(--info); }
.notif-item .notif-icon.success { background: color-mix(in srgb, var(--success) 15%, transparent); color: var(--success); }
.notif-item .notif-icon.warning { background: color-mix(in srgb, var(--warning) 15%, transparent); color: var(--warning); }
.notif-item .notif-icon.error { background: color-mix(in srgb, var(--danger) 15%, transparent); color: var(--danger); }
.notif-item .notif-icon.tpv { background: color-mix(in srgb, var(--primary) 15%, transparent); color: var(--primary); }
.notif-item .notif-content { flex: 1; min-width: 0; }
.notif-item .notif-title { font-weight: 600; font-size: 13px; margin-bottom: 2px; }
.notif-item .notif-msg { font-size: 12px; color: var(--text-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.notif-item .notif-time { font-size: 11px; color: var(--text-muted); white-space: nowrap; flex-shrink: 0; }

.user-menu {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    padding: 3px var(--space-3) 3px 3px;
    background: var(--bg-secondary);
    border: 1px solid var(--border-secondary);
    border-radius: var(--radius-full);
    cursor: pointer;
    transition: all var(--transition-fast);
}

.user-menu:hover {
    background: var(--bg-tertiary);
    border-color: var(--border-primary);
}

.user-avatar {
    width: 26px;
    height: 26px;
    background: var(--accent-fill);
    color: var(--accent-fill-contrast);
    border-radius: var(--radius-full);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 0.625rem;
    font-weight: 700;
    letter-spacing: 0.02em;
}

.user-name {
    font-size: 0.8125rem;
    font-weight: 500;
}

/* User dropdown menu */
.user-menu { position: relative; cursor: pointer; }
.user-dropdown {
    display: none;
    position: absolute;
    top: calc(100% + 6px);
    right: 0;
    min-width: 220px;
    background: var(--bg-primary);
    border: 1px solid var(--border-primary);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-xl);
    z-index: 1000;
    padding: 6px 0;
}
.user-dropdown.open { display: block; }
.user-dropdown-info {
    padding: 10px 14px;
    font-size: 12px;
    color: var(--text-secondary);
    line-height: 1.5;
}
.user-dropdown-info strong { color: var(--text-primary); font-size: 13px; display: block; margin-bottom: 2px; }
.user-dropdown-sep { height: 1px; background: var(--border); margin: 4px 0; }
.user-dropdown-item {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 8px 14px;
    font-size: 13px;
    color: var(--text-primary);
    cursor: pointer;
    transition: background .15s;
}
.user-dropdown-item:hover { background: var(--bg-tertiary); }
.user-dropdown-item.text-danger { color: var(--color-danger); }

.content {
    flex: 1;
    overflow: auto;
    padding: var(--space-6);
    background: var(--bg-app);
}

.view { display: none; }
.view.active { display: block; animation: fadeIn 0.2s var(--ease-out); }

/* =====================================================
   ORBE HERMES — hero del dashboard (v6.2, 2026-07-26)
   Esfera WebGL (canvas) con las mismas capas que la landing shoyoai.com:
   haze morado detrás · canvas · veil de viñeta delante. Sin degradado CSS
   compitiendo con el WebGL — solo fallback para .no-webgl.
   ===================================================== */
/* ── Layout del dashboard: contenido + panel IA a la derecha (mockup) ──
   CANON v6.2: las dos columnas miden LO MISMO de alto (stretch). La derecha
   NO es sticky: es una columna que se estira y cuyo panel IA rellena el hueco. */
.dash-layout {
    display: grid;
    /* 372px (antes 330): el orbe Hermes es el hero del dashboard y necesita ancho
       para leerse como el de la landing (ver § .hermes-orb-stage). El nº de columnas
       de KPI de la izquierda lo decide la container query de `.dash-main` (§ DASHBOARD):
       4 desde 888px de columna (viewport ≥ ~1564px con sidebar expandida), si no 2. */
    grid-template-columns: minmax(0, 1fr) 372px;
    gap: var(--space-4);
    align-items: stretch;
}

/* Panel Hermes — columna derecha, orbe arriba, chat abajo */
.dash-right {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
    height: 100%;
    min-height: 0;
}
.hermes-ia-panel {
    background: var(--bg-card);
    border: 1px solid var(--border-secondary);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-card);
    padding: var(--space-5);
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
    /* sin min-height fijo: rellena hasta igualar la columna izquierda.
       ⚠️ NO poner `min-height: 0` aquí (revisión adversarial 2026-07-26): en un
       contenedor flex de columna, `flex:1` implica `flex-basis:0` y con
       min-height:0 la aportación del panel al alto intrínseco de .dash-right —y
       por tanto a la fila del grid— pasa a ser CERO. Si la columna izquierda es
       corta (pocos KPIs, sin gráfica) y la derecha larga (orbe 236px + estado +
       varios cierres + input), el panel se queda con el alto sobrante, no crece,
       y como no hay overflow declarado el contenido se DERRAMA fuera de la card.
       Con min-height:auto (por defecto) el panel nunca baja de su min-content y
       la fila del grid crece con él. */
    flex: 1;
}

/* Una sola columna: el stretch deja de aplicar y la derecha vuelve a auto */
@media (max-width: 1180px) {
    .dash-layout { grid-template-columns: 1fr; }
    .dash-right  { height: auto; }
    .hermes-ia-panel { flex: 0 1 auto; min-height: 360px; }
}

/* Card "Requiere tu acción" — filas de aviso de UNA línea con CTA */
.dash-aviso-row {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 7px 6px;
    border-radius: var(--radius-xs);
    cursor: pointer;
    white-space: nowrap;
}
.dash-aviso-row:hover { background: var(--bg-hover); }
.dash-aviso-row .dot { width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0; }
.dash-aviso-row .txt {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    font-size: 0.75rem;
    color: var(--text-secondary);
}
.dash-aviso-row .txt strong { color: var(--text-primary); }
.dash-aviso-row .cta {
    font-family: var(--font-mono);
    font-size: 0.5938rem;
    font-weight: 600;
    letter-spacing: 0.08em;
    padding: 3px 9px;
    border-radius: var(--radius-full);
    background: var(--accent-soft);
    color: var(--accent-primary);
    flex-shrink: 0;
}

/* Cierres de caja bajo el orbe — SIEMPRE una línea */
.dvergent-cierre-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    padding: 5px 6px;
    border-radius: var(--radius-xs);
    cursor: pointer;
    white-space: nowrap;
}
.dvergent-cierre-row:hover { background: var(--bg-hover); }
.dvergent-cierre-row .lbl {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    font-size: 0.75rem;
    color: var(--text-secondary);
}
.dvergent-cierre-row .val {
    font-family: var(--font-mono);
    font-size: 0.75rem;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    color: var(--text-primary);
    flex-shrink: 0;
}
/* ── Orbe: contrato de markup (A1 pone el CSS, B1 emite el HTML) ──
   <div class="hermes-orb-stage">
     <div class="hermes-orb-haze"></div>
     <canvas id="hermesOrbCanvas" class="hermes-orb-canvas"></canvas>
     <div class="hermes-orb-veil"></div>
   </div>
   Stage cuadrado y grande (hero, no icono). El canvas ocupa el stage entero
   (inset:0 / 100%): con inset negativo la esfera se recortaba por los bordes. */
/* v6.2 (2026-07-26) — TAMAÑO DEL STAGE: verificado renderizando el orbe real contra
   shoyoai.com. El shader es idéntico (ver hermes-orb.js), pero el tamaño de partícula
   en px CSS es  u_size*k*alturaCSS/900  → PROPORCIONAL AL LADO DEL STAGE. A 236 px las
   2600 partículas miden 0,5–2,3 px y el orbe se lee como una malla fina; en la landing
   (720 px de alto) miden 1,5–7,1 px y se leen como puntos. Proporcionalmente idéntico,
   perceptualmente no. El ÚNICO parámetro que recupera la presencia de la landing sin
   romper la identidad del shader es el TAMAÑO — subir u_size engordaría los puntos
   respecto a la esfera y las 2600 partículas se fundirían en una bola sólida.
   Por eso el stage llena la columna (330→372 px) en vez de quedarse en 236. */
.hermes-orb-stage {
    position: relative;
    width: clamp(200px, 100%, 320px);
    max-width: 100%;
    aspect-ratio: 1 / 1;
    margin: var(--space-2) auto var(--space-1);
    isolation: isolate;
    flex-shrink: 0;   /* dentro del panel flex: la esfera no se ovala */
}

/* Capa 1 — halo morado difuso DETRÁS del canvas (landing: haze) */
.hermes-orb-haze {
    position: absolute;
    inset: -6%;
    z-index: 0;
    pointer-events: none;
    opacity: 0.6;
    background: radial-gradient(62% 60% at 50% 40%, rgba(126, 84, 206, 0.2), transparent 66%);
}

/* Capa 2 — el WebGL manda: canvas a inset:0, sin fondo que compita */
.hermes-orb-canvas {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    display: block;
    z-index: 1;
    background: transparent;
}

/* Capa 3 — viñeta de la landing: núcleo apagado + fundido arriba/abajo, para
   que las partículas del borde lleven toda la luz. */
.hermes-orb-veil {
    position: absolute;
    inset: 0;
    z-index: 2;
    pointer-events: none;
    border-radius: 50%;
    background:
        radial-gradient(58% 52% at 50% 52%, rgba(0, 0, 0, 0.55) 0%, rgba(0, 0, 0, 0.25) 46%, transparent 74%),
        linear-gradient(180deg, rgba(0, 0, 0, 0.6) 0%, transparent 24%, transparent 70%, rgba(0, 0, 0, 0.85) 100%);
}

/* En light las veils ensuciarían el panel blanco: se atenúan, no se quitan
   (sin ellas la esfera pierde volumen). */
/* v6.2 (2026-07-26) — EL ORBE CONSERVA SU ESCENARIO OSCURO EN TEMA LIGHT.
   Misma regla de marca que `.sidebar`, que también permanece dark en light: las
   partículas del orbe son UV rgb(170,118,228) con blending aditivo premultiplicado
   sobre transparente. Sobre una card blanca eso se lee como una bola gris lavada —
   verificado renderizando: el orbe perdía por completo su identidad y quedaba peor
   que en dark. Con el pozo oscuro, el orbe es IDÉNTICO en los dos temas (que es lo
   que pidió Adri) y las veils vuelven a su intensidad plena porque ya tienen fondo
   negro debajo. El degradado muere en `transparent` para que el borde no recorte
   como un agujero sobre la superficie clara. */
.hermes-orb-stage {
    background: radial-gradient(circle at 50% 50%,
        #0b0714 0%,
        #0b0714 52%,
        color-mix(in srgb, #0b0714 72%, transparent) 64%,
        color-mix(in srgb, #0b0714 28%, transparent) 74%,
        transparent 84%);
}

/* Fallback SOLO sin WebGL (JS marca el stage con .no-webgl) */
.hermes-orb-stage.no-webgl .hermes-orb-canvas { display: none; }
.hermes-orb-stage.no-webgl .hermes-orb-haze {
    inset: 0;
    opacity: 1;
    border-radius: 50%;
    background: radial-gradient(circle at 36% 32%, rgba(178, 152, 240, 0.55), rgba(96, 64, 170, 0.45) 42%, rgba(28, 12, 60, 0.85) 72%, transparent 78%);
    animation: orbBreathe 4.5s ease-in-out infinite;
}
@keyframes orbBreathe {
    0%, 100% { filter: brightness(0.92); }
    50%      { filter: brightness(1.08); }
}
.hermes-orb-title {
    font-family: var(--font-display);
    font-size: 1rem;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-align: center;
}
.hermes-orb-status {
    font-family: var(--font-mono);
    font-size: 0.625rem;
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: 0.12em;
    color: var(--accent-primary);
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
}
.hermes-orb-status::before {
    content: '';
    width: 6px; height: 6px; border-radius: 50%;
    background: var(--accent-fill);   /* dot de estado = relleno de marca */
    animation: pulse 1.8s infinite;
}
@keyframes pulse { 0%, 100% { opacity: .45; } 50% { opacity: 1; } }
.hermes-orb-msg { font-size: 0.8125rem; color: var(--text-secondary); text-align: center; }
.hermes-ia-alerts-title {
    font-family: var(--font-mono);
    font-size: 0.625rem;
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: var(--text-tertiary);
    margin-top: var(--space-2);
    display: flex;
    justify-content: space-between;
}
.hermes-orb-alerts { display: flex; flex-direction: column; gap: 4px; }
.hermes-orb-alert {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 0.75rem;
    color: var(--text-secondary);
    cursor: pointer;
    border-radius: var(--radius-xs);
    padding: 4px 6px;
}
.hermes-orb-alert:hover { background: var(--bg-hover); color: var(--text-primary); }
.hermes-orb-alert .dot { width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0; }
.hermes-ia-spacer { flex: 1; }
.hermes-orb-ask { display: flex; gap: var(--space-2); }
.hermes-orb-ask input {
    flex: 1;
    min-width: 0;
    padding: 0.5rem 0.875rem;
    font-family: inherit;
    font-size: 0.8125rem;
    border: 1px solid var(--border-primary);
    border-radius: var(--radius-full);
    background: var(--bg-input);
    color: var(--text-primary);
    outline: none;
    transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}
.hermes-orb-ask input:focus { border-color: var(--accent-primary); box-shadow: 0 0 0 3px var(--border-focus); }
.hermes-orb-ask button {
    width: 34px; height: 34px;
    border-radius: var(--radius-full);
    /* era border:none → en light el círculo lima sobre card blanca no tenía
       contorno legible (1.30:1). box-sizing:border-box global → no mueve layout. */
    border: 1px solid var(--accent-fill-border);
    background: var(--accent-fill);
    color: var(--accent-fill-contrast);
    cursor: pointer;
    display: flex; align-items: center; justify-content: center;
    flex-shrink: 0;
}
.hermes-orb-ask button:hover { background: var(--accent-fill-hover); }

/* ── Listas del dashboard (mockup "Ingresos por módulo"): filas con barra volt ── */
.dash-lists {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--space-4);
    margin-top: var(--space-4);
}
@media (max-width: 900px) { .dash-lists { grid-template-columns: 1fr; } }

/* ── Gráfica anual del dashboard: el ALTO lo fija el contenedor ──
   Chart.js con maintainAspectRatio:false lee el alto del padre; si el padre es
   un .card-body de alto auto, el alto lo acaba decidiendo el atributo height
   del canvas y la gráfica queda clavada en 270px a cualquier ancho. Con este
   contenedor (aspect-ratio acotado por min/max) la gráfica se recorta sola con
   el ancho de la columna. El canvas va absoluto para que su tamaño intrínseco
   no retroalimente el alto del padre (bucle de ResizeObserver). */
.dash-chart-box {
    position: relative;
    width: 100%;
    aspect-ratio: 16 / 6;
    min-height: 190px;
    max-height: 300px;
}
.dash-chart-box > canvas { position: absolute; inset: 0; }
@media (max-width: 900px) { .dash-chart-box { aspect-ratio: 16 / 9; max-height: 260px; } }
.dash-list-row {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    gap: 4px 12px;
    align-items: center;
    padding: 7px 0;
}
.dash-list-row .nm {
    font-size: 0.8125rem;
    color: var(--text-primary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.dash-list-row .val {
    font-family: var(--font-mono);
    font-size: 0.75rem;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}
.dash-list-row .bar {
    grid-column: 1 / -1;
    height: 4px;
    border-radius: 2px;
    background: var(--bg-tertiary);
    overflow: hidden;
}
/* barra de progreso = relleno de marca → par fill (lima vivo en los dos temas) */
.dash-list-row .bar > i {
    display: block;
    height: 100%;
    border-radius: 2px;
    background: linear-gradient(90deg, var(--accent-fill), color-mix(in srgb, var(--accent-fill) 55%, transparent));
    box-shadow: 0 0 8px color-mix(in srgb, var(--accent-fill) 45%, transparent);
    transition: width 0.7s var(--ease-out);
}

@media (prefers-reduced-motion: reduce) {
    .hermes-orb-stage.no-webgl .hermes-orb-haze,
    .hermes-orb-status::before { animation: none !important; }
}

/* =====================================================
   DASHBOARD
   ===================================================== */
/* CANON v6.2: mismo tamaño de tarjeta KPI en TODOS los módulos. El número de
   columnas lo decide el ancho disponible, no un repeat(N) por módulo.
   ⚠️ PROHIBIDO override inline de grid-template-columns sobre .dashboard-grid. */
.dashboard-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(210px, 1fr));
    gap: var(--space-4);
    margin-bottom: var(--space-6);
}

/* CANON v6.3 (Adri 2026-08-22): el dashboard PRINCIPAL son SIEMPRE 8 KPIs en
   2 filas de 4. El nº de columnas tiene que DIVIDIR a 4 (4 · 2 · 1): con el
   auto-fill genérico salían 3 tracks a 1366-1512px y 5 a 1920px, y auto-fill
   CREA los tracks aunque no haya tarjeta → celdas vacías al final de cada fila
   (los "2 huecos"). Scopeado a .dash-main: `.dashboard-grid` sigue siendo
   auto-fill para los otros módulos (3, 5 o 6 tarjetas por grid — regla 10c).
   Se decide por CONTAINER QUERY sobre la columna izquierda, no por viewport:
   así absorbe la sidebar colapsada (240→68px), el panel derecho de 372px y el
   paso de .dash-layout a 1 columna sin replicar breakpoints. Umbrales: 4 cols
   desde 888px (4×210 + 3×16), 1 col hasta 436px (2×210 + 16), 2 cols en medio.
   `container-type: inline-size` solo contiene el eje inline: el alto de
   .dash-main sigue saliendo de su contenido, así que el stretch de .dash-layout
   y la paridad de altura con .dash-right (10c) no cambian.
   ⚠️ Esta regla (0,2,0) gana a las responsive de `.dashboard-grid` (0,1,0) de
   ≤1024/≤720px: por eso el fallback va aquí y no en esas media queries. La de
   ≤480px (`1fr !important`) sigue ganando, y coincide con el umbral de 1 col. */
.dash-main { container-type: inline-size; container-name: dashmain; }
.dash-main > .dashboard-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    margin-bottom: var(--space-4);   /* entre fila 1 y fila 2: mismo ritmo que el gap */
}
.dash-main > .dashboard-grid + :not(.dashboard-grid) { margin-top: var(--space-6); }
@container dashmain (min-width: 888px) {
    .dash-main > .dashboard-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}
@container dashmain (max-width: 436px) {
    .dash-main > .dashboard-grid { grid-template-columns: minmax(0, 1fr); }
}

/* =====================================================
   MODULE HEADER
   ===================================================== */
.module-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: var(--space-5);
}

.module-title {
    font-size: 1.125rem;
    font-weight: 600;
    letter-spacing: -0.01em;
}

.module-actions {
    display: flex;
    gap: var(--space-2);
    flex-wrap: wrap;
}

/* =====================================================
   FILE UPLOAD
   ===================================================== */
.file-upload {
    border: 1.5px dashed var(--border-primary);
    border-radius: var(--radius-lg);
    padding: var(--space-8);
    text-align: center;
    cursor: pointer;
    transition: all var(--transition-fast);
}

.file-upload:hover {
    border-color: var(--accent-primary);
    background: var(--bg-hover);
}

.file-upload-icon {
    font-size: 1.75rem;
    margin-bottom: var(--space-3);
    color: var(--text-tertiary);
}

.file-upload-text { color: var(--text-secondary); font-size: 0.8125rem; }
.file-upload input { display: none; }

/* =====================================================
   CHECKBOX GROUP (módulos activos)
   ===================================================== */
.checkbox-group {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-3);
}

.checkbox-item {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-2) var(--space-4);
    background: var(--bg-tertiary);
    border-radius: var(--radius-md);
    cursor: pointer;
    transition: all var(--transition-fast);
    border: 1.5px solid var(--border-primary);
    font-size: 0.875rem;
    color: var(--text-secondary);
    user-select: none;
}

.checkbox-item:hover {
    background: var(--bg-active);
    border-color: var(--text-tertiary);
    color: var(--text-primary);
}

/* chip activo → relleno de marca (par fill), no tinte */
.checkbox-item.checked {
    border-color: var(--accent-fill-border);
    background: var(--accent-fill);
    color: var(--accent-fill-contrast);
    font-weight: 600;
}

.checkbox-item input { display: none; }
.checkbox-icon { font-size: 1rem; }

/* =====================================================
   GRID UTILITY
   ===================================================== */
.grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--space-4);
}

/* =====================================================
   SIDEBAR OVERLAY (for mobile)
   ===================================================== */
.sidebar-overlay {
    display: none;
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.5);
    z-index: 499;
}

/* =====================================================
   MOBILE MODULE TABS — Horizontal scrollable navigation
   ===================================================== */
.mobile-module-tabs {
    display: none;
}

/* =====================================================
   RESPONSIVE — DESKTOP (>1024px)
   Dashboard grid 4 columns is the default
   ===================================================== */

/* =====================================================
   RESPONSIVE — TABLET (≤1024px)
   ===================================================== */
@media (max-width: 1024px) {
    .mobile-module-tabs {
        display: flex;
        overflow-x: auto;
        -webkit-overflow-scrolling: touch;
        white-space: nowrap;
        gap: 0;
        scrollbar-width: none;
        background: var(--bg-primary);
        border-bottom: 1px solid var(--border-secondary);
        padding: 0 var(--space-2);
        position: sticky;
        top: 0;
        z-index: 90;
    }
    .mobile-module-tabs::-webkit-scrollbar { display: none; }

    .mobile-tab {
        display: inline-flex;
        align-items: center;
        gap: 6px;
        padding: 10px 14px;
        font-size: 0.8125rem;
        font-weight: 500;
        color: var(--text-secondary);
        background: none;
        border: none;
        border-bottom: 2px solid transparent;
        cursor: pointer;
        flex-shrink: 0;
        transition: color var(--transition-fast), border-color var(--transition-fast);
        font-family: inherit;
    }
    .mobile-tab:hover {
        color: var(--text-primary);
    }
    /* v6 · MOVIMIENTO: el subrayado ya no salta — lo pinta .mobile-tab-bead deslizante */
    .mobile-tab.active {
        color: var(--accent);
    }
    .mobile-tab-bead {
        position: absolute;
        left: 0;
        bottom: 0;
        height: 2px;
        width: var(--tab-w, 0px);
        border-radius: 2px;
        background: var(--accent-fill);
        box-shadow: 0 0 10px color-mix(in srgb, var(--accent-fill) 55%, transparent);
        transform: translateX(var(--tab-x, 0px));
        transition: transform 0.28s var(--ease-spring), width 0.28s var(--ease-out), opacity var(--transition-fast);
        opacity: 0;
        pointer-events: none;
    }
    .mobile-module-tabs.has-active .mobile-tab-bead { opacity: 1; }
    .mobile-tab i {
        flex-shrink: 0;
    }

    .sidebar {
        position: fixed;
        left: 0;
        top: 0;
        bottom: 0;
        z-index: 500;
        transform: translateX(-100%);
        transition: transform var(--transition-normal);
        width: var(--sidebar-width);
        box-shadow: var(--shadow-xl);
    }

    .sidebar.open { transform: translateX(0); }
    .sidebar.collapsed { transform: translateX(-100%); }

    .sidebar-overlay.active { display: block; }

    .main { margin-left: 0 !important; }

    .dashboard-grid { grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); }
    .empresas-grid  { grid-template-columns: repeat(2, 1fr); }

    /* Top vendedores: 2 cols on tablet */
    #tpvVendedoresGrid { grid-template-columns: repeat(2, 1fr) !important; }

    /* Holding dashboard charts: stack vertically on tablet */
    .holding-charts-grid { grid-template-columns: 1fr !important; }

    .module-header {
        flex-direction: column;
        gap: var(--space-3);
        align-items: flex-start;
    }

    .module-actions { width: 100%; }

    .modal { max-width: 90vw; }

    th, td {
        padding: 0.5rem 0.75rem;
        font-size: 0.8125rem;
    }

    .card-body { padding: var(--space-4); }
    .card-header { padding: var(--space-3) var(--space-4); }
    .content { padding: var(--space-4); }
}

/* =====================================================
   RESPONSIVE — SMARTPHONE (≤640px)
   ===================================================== */
/* 2026-05-28 (Adri auditoría responsive L3 task #6): ampliado de 640px a 720px
   para cubrir el Sunmi L3 (viewport ~720px CSS con DPR 2 — pre-DP-297). Sin
   esto, los modales genéricos del ERP (búsqueda cliente, cierre caja, etc.)
   se renderizaban en tamaño desktop en el L3 porque caían entre 640 y 768.
   El cambio NO afecta a iPads (>720px) ni desktop. Tablets 7" raros con ancho
   <720 recibirán los estilos compactos — aceptable. */
@media (max-width: 720px) {
    html { font-size: 15px; }

    .sidebar { width: 85vw; max-width: 300px; }

    .dashboard-grid {
        grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
        gap: var(--space-3);
    }

    .empresas-grid { grid-template-columns: 1fr; }

    .stat-card { padding: 16px; min-height: 112px; }
    .stat-card-value { font-size: 22px; }
    .kpi-sparkline { height: 36px; }
    .kpi-sparkline canvas { height: 36px !important; }

    /* Top vendedores: 1 col on mobile */
    #tpvVendedoresGrid { grid-template-columns: 1fr !important; }

    .module-header { padding: 0; margin-bottom: var(--space-4); }

    .module-actions .btn {
        font-size: 0.75rem;
        padding: 0.25rem 0.5rem;
    }

    .tabs {
        overflow-x: auto;
        -webkit-overflow-scrolling: touch;
        white-space: nowrap;
        gap: 0;
        scrollbar-width: none;
    }
    .tabs::-webkit-scrollbar { display: none; }

    .tab {
        flex-shrink: 0;
        font-size: 0.75rem;
        padding: var(--space-2) var(--space-3);
    }

    th, td {
        padding: 0.4rem 0.5rem;
        font-size: 0.75rem;
    }

    .form-row { grid-template-columns: 1fr; }

    .modal {
        max-width: 100vw;
        max-height: 100vh;
        border-radius: var(--radius-lg);
        margin: var(--space-2);
    }

    .modal-body { padding: var(--space-4); }
    .modal-header { padding: var(--space-4); }
    .modal-footer { padding: var(--space-3) var(--space-4); }

    .breadcrumb { font-size: 0.8125rem; }

    .header { padding: 0 var(--space-3); }
    .header-title { font-size: 0.8125rem; }

    .content { padding: var(--space-3); }

    .card { border-radius: var(--radius-md); }
    .card-body { padding: var(--space-3); }

    .table-container { font-size: 0.75rem; }

    .btn-sm {
        padding: 0.25rem 0.5rem;
        font-size: 0.6875rem;
    }

    #holdingTabs {
        overflow-x: auto;
        flex-wrap: nowrap;
    }

    .holding-wrapper { padding: var(--space-3); }
    .holding-header { margin-bottom: var(--space-4); }

    /* 2026-05-28 (Adri drill2+drill3 fix task #16/#18): login limpio sin card.
       - Wrapper SCROLLABLE — con teclado virtual ocupando bottom, el form
         debe poder hacerse scroll para que el botón sea alcanzable.
       - Card sin fondo/border/sombra — solo los inputs visibles.
       - Logo DVERGENT grande (240px) y nítido (no halo) — usa logo SVG transparente. */
    .login-wrapper {
        align-items: flex-start !important;
        padding-top: 4vh !important;
        padding-bottom: 4vh !important;
        padding-left: 16px !important;
        padding-right: 16px !important;
        overflow-y: auto !important;
        -webkit-overflow-scrolling: touch !important;
    }
    .login-card {
        padding: 0 !important;
        background: transparent !important;
        border: 0 !important;
        box-shadow: none !important;
        border-radius: 0 !important;
        margin-bottom: 40vh; /* asegurar que cabe el botón con teclado abierto */
    }
    .login-logo-img {
        width: 240px !important;
        max-width: 70vw !important;
        margin-bottom: 4px !important;
        /* Nitidez NVidia-style: sin filtros de glow/blur, render crisp */
        filter: none !important;
        image-rendering: -webkit-optimize-contrast;
    }
    .login-header {
        margin-bottom: 12px !important;
    }
    .login-subtitle {
        font-size: 13px !important;
        opacity: 0.6 !important;
    }

    .empresa-card { padding: var(--space-4); }
}

/* =====================================================
   RESPONSIVE — SMALL PHONE / SUNMI P3 (≤380px)
   ===================================================== */
@media (max-width: 380px) {
    html { font-size: 14px; }

    .dashboard-grid { grid-template-columns: 1fr; }
    .stat-card-value { font-size: 1.125rem; }

    .login-card { padding: 24px 16px; }

    .holding-title h1 { font-size: 1.125rem; }

    .stat-card {
        padding: var(--space-3);
        min-height: 96px;
    }

    .stat-card-icon { width: 32px; height: 32px; font-size: 0.875rem; }
    .stat-card-label { font-size: 0.6875rem; }

    .empresa-card { padding: var(--space-3); }
    .empresa-icon { width: 48px; height: 48px; }
    .empresa-icon-logo { width: 48px; height: 48px; }

    .modal { margin: var(--space-1); border-radius: var(--radius-md); }
    .modal-body { padding: var(--space-3); }

    .tabs { gap: 0; }
    .tab { padding: var(--space-2); font-size: 0.6875rem; }
}

/* =====================================================
   TPV — Estética minimalista tipo Apple
   ===================================================== */
/* =====================================================
   TPV — PANTALLA DE VENTA COMPLETA
   Migrado desde JS <style> block → theme.css
   ===================================================== */
.tpv-venta-fullscreen {
    position: fixed;
    inset: 0;
    z-index: 1000;
    background: var(--bg-primary);
    display: flex;
    flex-direction: column;
}

.tpv-venta-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: var(--space-2) var(--space-4);
    border-bottom: 1px solid var(--border-primary);
    background: var(--bg-secondary);
    gap: var(--space-3);
    min-height: 56px;
}

.tpv-search-container {
    flex: 1;
    display: flex;
    justify-content: center;
}

.tpv-search-dropdown {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    background: var(--bg-secondary);
    border: 1px solid var(--border-primary);
    border-radius: var(--radius-md);
    max-height: 400px;
    overflow-y: auto;
    z-index: 10;
    box-shadow: var(--shadow-lg);
}

.tpv-search-item {
    padding: var(--space-2) var(--space-3);
    cursor: pointer;
    display: flex;
    justify-content: space-between;
    align-items: center;
    border-bottom: 1px solid var(--border-primary);
}
.tpv-search-item:hover { background: var(--bg-tertiary); }

/* Tarifa product search dropdown */
.tarifa-prod-dropdown {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    background: var(--bg-secondary);
    border: 1px solid var(--border-primary);
    border-radius: var(--radius-md);
    max-height: 250px;
    overflow-y: auto;
    z-index: 100;
    box-shadow: var(--shadow-lg);
    margin-top: 2px;
}
.tarifa-prod-item {
    padding: var(--space-2) var(--space-3);
    cursor: pointer;
    border-bottom: 1px solid var(--border-primary);
}
.tarifa-prod-item:last-child { border-bottom: none; }
.tarifa-prod-item:hover { background: var(--bg-tertiary); }

.tpv-venta-body {
    flex: 1;
    display: flex;
    overflow: hidden;
}

.tpv-venta-left {
    flex: 1 1 0%;
    overflow-y: auto;
    padding: var(--space-3);
    min-width: 0;
}

.tpv-venta-right {
    flex: 0 0 320px;
    max-width: 360px;
    border-left: 1px solid var(--border-primary);
    display: flex;
    flex-direction: column;
    background: var(--bg-secondary);
}

/* Categorías grid */
.tpv-cat-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
    gap: var(--space-2);
}

.tpv-cat-btn {
    padding: var(--space-3);
    border-radius: var(--radius-md);
    border: 2px solid var(--border-primary);
    cursor: pointer;
    text-align: center;
    transition: all 0.15s;
    min-height: 80px;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--space-1);
    background: var(--bg-secondary);
}
.tpv-cat-btn:hover { transform: scale(1.03); border-color: var(--accent-primary); }
.tpv-cat-btn.active { border-color: var(--accent-primary); background: var(--bg-active); }
.tpv-cat-btn .cat-name { font-weight: 600; font-size: 0.85rem; }
.tpv-cat-btn .cat-count { font-size: 0.75rem; opacity: 0.7; }

/* Productos grid */
.tpv-prod-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
    gap: var(--space-2);
    margin-top: var(--space-2);
}

.tpv-prod-btn {
    position: relative;
    padding: var(--space-2);
    border-radius: var(--radius-md);
    border: 1px solid var(--border-primary);
    cursor: pointer;
    text-align: center;
    transition: all 0.15s;
    min-height: 140px;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 4px;
    background: var(--bg-primary);
    user-select: none;
    -webkit-user-select: none;
}
.tpv-prod-btn:hover { border-color: var(--accent-primary); transform: scale(1.02); }
.tpv-prod-btn:active { transform: scale(0.97); }
.tpv-prod-btn .prod-name {
    font-size: 0.82rem; font-weight: 600; line-height: 1.2;
    overflow: hidden; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
    margin-top: 4px;
}
.tpv-prod-btn .prod-price { font-family: var(--font-mono); font-size: 0.9rem; font-weight: 600; color: var(--accent-primary); margin-top: 2px; font-variant-numeric: tabular-nums; }

/* Category filter bar (horizontal chips) */
.tpv-cat-bar {
    display: flex;
    gap: 6px;
    padding: 0 0 var(--space-2) 0;
    overflow-x: auto;
    flex-shrink: 0;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
}
.tpv-cat-bar::-webkit-scrollbar { display: none; }
.tpv-cat-chip {
    flex-shrink: 0;
    padding: 6px 14px;
    border-radius: 20px;
    border: 2px solid var(--border-primary);
    background: var(--bg-secondary);
    cursor: pointer;
    font-size: 0.82rem;
    font-weight: 500;
    white-space: nowrap;
    transition: all 0.15s;
    color: var(--text-primary);
}
.tpv-cat-chip:hover { border-color: var(--accent-primary); background: var(--bg-active); }
.tpv-cat-chip.active {
    background: var(--accent-fill);
    color: var(--accent-fill-contrast);
    border-color: var(--accent-fill-border);
    font-weight: 600;
}
.tpv-prod-btn img { width: 100px; height: 100px; object-fit: contain; border-radius: var(--radius-sm); }
.tpv-prod-btn.grande { grid-column: span 2; }
.tpv-prod-btn.tpv-drag-over { border: 2px dashed var(--accent-primary); background: var(--bg-active); }

.tpv-prod-btn .prod-delete-btn {
    position: absolute; top: -6px; left: -6px; width: 20px; height: 20px; border-radius: 50%;
    background: var(--accent-red); color: var(--accent-red-contrast); border: none; cursor: pointer; font-size: 12px;
    display: flex; align-items: center; justify-content: center; z-index: 1;
}

/* Wobble (layout edit mode — tipo iOS) */
.tpv-prod-btn.tpv-wobble {
    animation: tpvWobble 0.25s ease-in-out infinite;
    cursor: grab;
}
.tpv-prod-btn.tpv-wobble:nth-child(even) { animation-delay: 0.12s; }
.tpv-prod-btn.tpv-wobble:nth-child(3n) { animation-duration: 0.28s; }
@keyframes tpvWobble {
    0%, 100% { transform: rotate(-1.8deg); }
    50% { transform: rotate(1.8deg); }
}

/* Chips de categoría en modo ordenar (mismo concepto iOS que los productos) */
.tpv-cat-chip.tpv-cat-wobble {
    animation: tpvWobble 0.25s ease-in-out infinite;
    cursor: grab;
}
.tpv-cat-chip.tpv-cat-wobble:nth-child(even) { animation-delay: 0.1s; }

/* Aviso de modo ordenar (sustituye a los botones Editar/Listo) */
.tpv-orden-hint {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 0.8rem;
    color: var(--accent-primary);
    margin: 0 0 var(--space-2);
    opacity: 0.9;
}

/* Drag clone (floating element while dragging) */
.tpv-drag-clone {
    border-radius: var(--radius-md) !important;
    background: var(--bg-primary) !important;
    overflow: hidden;
}

/* Drop target highlight */
.tpv-prod-btn.tpv-drag-over {
    border: 2px dashed var(--accent-primary) !important;
    background: var(--bg-active) !important;
    transform: scale(1.05);
}

/* Context menu (long-press en producto) */
.tpv-ctx-menu {
    position: fixed;
    z-index: 10000;
    min-width: 200px;
    background: var(--bg-primary);
    border: 1px solid var(--border-primary);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-lg);
    overflow: hidden;
    animation: tpvCtxIn 0.15s ease-out;
}
@keyframes tpvCtxIn {
    from { opacity: 0; transform: scale(0.9); }
    to { opacity: 1; transform: scale(1); }
}
.tpv-ctx-header {
    padding: 10px 14px;
    font-size: 0.82rem;
    font-weight: 700;
    border-bottom: 1px solid var(--border-primary);
    background: var(--bg-secondary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 220px;
}
.tpv-ctx-item {
    padding: 10px 14px;
    font-size: 0.85rem;
    display: flex;
    align-items: center;
    gap: 10px;
    cursor: pointer;
    transition: background 0.1s;
}
.tpv-ctx-item:hover { background: var(--bg-active); }
.tpv-ctx-item:active { background: var(--accent-fill); color: var(--accent-fill-contrast); }

/* Back button */
.tpv-back-btn {
    display: inline-flex; align-items: center; gap: var(--space-1);
    padding: var(--space-2) var(--space-3); border-radius: var(--radius-md);
    border: 1px solid var(--border-primary); cursor: pointer;
    background: var(--bg-secondary); margin-bottom: var(--space-2); font-size: 0.85rem;
}
.tpv-back-btn:hover { background: var(--bg-tertiary); }

/* Ticket panel */
.tpv-ticket-header {
    padding: var(--space-2) var(--space-3);
    border-bottom: 1px solid var(--border-primary);
    display: flex; align-items: center; justify-content: space-between;
}

/* Contenedor del ticket (header + líneas + totales + acciones) — flex column para scroll */
#tpvTicketContent {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-height: 0;       /* Crítico: sin esto, flex children no respetan overflow */
    overflow: hidden;
}
/* Totales y acciones siempre visibles, nunca se esconden por overflow */
#tpvTicketContent .tpv-ticket-totals { flex-shrink: 0; }
#tpvTicketContent .tpv-ticket-actions { flex-shrink: 0; }

.tpv-ticket-lines { flex: 1; overflow-y: auto; padding: 0; min-height: 0; }

.tpv-ticket-line {
    padding: var(--space-2) var(--space-3);
    border-bottom: 1px solid var(--border-primary);
    display: flex; align-items: center; gap: var(--space-2); font-size: 0.85rem;
}
.tpv-ticket-line:hover { background: var(--bg-tertiary); }
.tpv-ticket-line .line-desc { flex: 1; min-width: 0; }
.tpv-ticket-line .line-desc .desc-name { font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.tpv-ticket-line .line-desc .desc-detail { font-size: 0.75rem; opacity: 0.6; }

.tpv-ticket-line .line-qty { display: flex; align-items: center; gap: 2px; }
.tpv-ticket-line .line-qty button {
    width: 28px; height: 28px; border-radius: var(--radius-sm);
    border: 1px solid var(--border-primary); background: var(--bg-primary);
    cursor: pointer; display: flex; align-items: center; justify-content: center;
    color: var(--text-primary); font-size: 1rem;
}
.tpv-ticket-line .line-qty button:hover { background: var(--bg-tertiary); }
.tpv-ticket-line .line-qty span { min-width: 28px; text-align: center; font-weight: 600; }
.tpv-ticket-line .line-total { font-weight: 600; min-width: 65px; text-align: right; }
.tpv-ticket-line .line-actions { display: flex; gap: 2px; }
.tpv-ticket-line .line-actions button {
    width: 24px; height: 24px; border: none; background: none; cursor: pointer;
    border-radius: var(--radius-sm); display: flex; align-items: center; justify-content: center;
    color: var(--text-secondary);
}
.tpv-ticket-line .line-actions button:hover { background: var(--bg-tertiary); color: var(--text-primary); }

/* Totals */
.tpv-ticket-totals { padding: var(--space-3); border-top: 2px solid var(--border-primary); }
.tpv-ticket-totals .total-row { display: flex; justify-content: space-between; font-size: 0.85rem; padding: 2px 0; }
.tpv-ticket-totals .total-row.total-final { font-size: 1.3rem; font-weight: 700; padding: var(--space-2) 0; color: var(--accent-primary); font-variant-numeric: tabular-nums; }

/* Actions */
.tpv-ticket-actions {
    padding: var(--space-2) var(--space-3);
    border-top: 1px solid var(--border-primary);
    display: flex; gap: var(--space-2);
}
.tpv-ticket-actions .btn { flex: 1; height: 52px; font-size: 1rem; font-weight: 600; }

/* Bug 1 (sesión fix tpv,core,docs 2026-04-29): el botón Cobrar (flex:1)
 * truncaba "€" a "E" cuando el panel lateral derecho era estrecho. Forzamos
 * min-width al contenido para que el símbolo monetario nunca se corte. */
.tpv-ticket-actions .btn[style*="flex:1"],
.tpv-ticket-actions .btn:not([style*="flex:0.3"]) {
    min-width: max-content;
    overflow: visible;
}

/* Empty ticket */
.tpv-ticket-empty {
    flex: 1; display: flex; flex-direction: column;
    align-items: center; justify-content: center; opacity: 0.4; gap: var(--space-2);
}

/* TPV Modals */
.tpv-modal-overlay {
    position: fixed; inset: 0; z-index: 2000;
    background: var(--bg-overlay);
    display: flex; align-items: center; justify-content: center;
}

.tpv-modal {
    background: var(--bg-secondary); border-radius: var(--radius-lg);
    padding: var(--space-4); min-width: 400px; max-width: 90vw; max-height: 90vh; overflow-y: auto;
}
.tpv-modal h3 { margin: 0 0 var(--space-3) 0; }

/* Payment buttons */
.tpv-pago-btn {
    display: flex; align-items: center; gap: var(--space-2); padding: var(--space-3);
    border-radius: var(--radius-md); border: 2px solid var(--border-primary); cursor: pointer;
    background: var(--bg-primary); transition: all 0.15s; margin-bottom: var(--space-2);
    touch-action: manipulation; user-select: none; /* iPad: sin double-tap-zoom ni selección al tocar métodos */
}
.tpv-pago-btn:hover { border-color: var(--accent-primary); }
.tpv-pago-btn.selected { border-color: var(--accent-primary); background: var(--bg-active); }
.tpv-pago-icon {
    width: 40px; height: 40px; border-radius: var(--radius-sm);
    display: flex; align-items: center; justify-content: center; color: white;
}
.tpv-pago-btn .pago-name { font-weight: 600; font-size: 0.95rem; }

/* Change display */
.tpv-cambio-display {
    text-align: center; padding: var(--space-3); margin: var(--space-3) 0;
    border-radius: var(--radius-md); background: var(--bg-tertiary);
}
.tpv-cambio-display .cambio-label { font-size: 0.85rem; opacity: 0.7; }
.tpv-cambio-display .cambio-value { font-size: 2rem; font-weight: 700; }
.tpv-cambio-display .cambio-value.positive { color: var(--accent-green); }
.tpv-cambio-display .cambio-value.negative { color: var(--accent-red); }

/* Numpad */
.tpv-numpad { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--space-1); margin-top: var(--space-2); }
.tpv-numpad button {
    height: 48px; border-radius: var(--radius-sm); border: 1px solid var(--border-primary);
    background: var(--bg-primary); cursor: pointer; font-size: 1.1rem; font-weight: 600;
    color: var(--text-primary);
}
.tpv-numpad button:hover { background: var(--bg-tertiary); }
.tpv-numpad button:active { transform: scale(0.95); }

/* Delivery button */
.tpv-entrega-btn {
    display: flex; align-items: center; gap: var(--space-2); padding: var(--space-3);
    border-radius: var(--radius-md); border: 2px solid var(--border-primary); cursor: pointer;
    background: var(--bg-primary); transition: all 0.15s; width: 100%; margin-bottom: var(--space-2);
}
.tpv-entrega-btn:hover { border-color: var(--accent-primary); }

/* Legacy classes (kept for backward compat) */
.tpv-productos-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
    gap: var(--space-3);
    padding: var(--space-4);
}

.tpv-producto-card {
    background: var(--bg-card);
    border-radius: var(--radius-sm);
    border: 1px solid var(--border-secondary);
    transition: all 0.15s;
    cursor: pointer;
    padding: var(--space-4);
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: var(--space-2);
}
.tpv-producto-card:hover { border-color: var(--border-focus); background: var(--bg-tertiary); }

.tpv-ticket-resumen {
    background: var(--bg-secondary);
    border-left: 1px solid var(--border-primary);
    display: flex;
    flex-direction: column;
    height: 100%;
}

.tpv-btn-cobrar {
    background: var(--accent-fill) !important;
    color: var(--accent-fill-contrast) !important;
    font-weight: 700;
    font-size: 1.1rem;
    height: 56px;
    border-radius: var(--radius-sm);
    width: 100%;
    /* era border:none → botón de cobro sin contorno legible en light (1.30:1) */
    border: 1px solid var(--accent-fill-border);
    cursor: pointer;
    transition: all 0.15s;
}
.tpv-btn-cobrar:hover { background: var(--accent-fill-hover) !important; transform: translateY(-1px); }

.tpv-metodo-pago-btn {
    background: var(--bg-tertiary);
    border: 1px solid var(--border-primary);
    border-radius: var(--radius-sm);
    padding: 12px 16px;
    color: var(--text-primary);
    cursor: pointer;
    transition: all 0.15s;
    display: flex;
    align-items: center;
    gap: var(--space-2);
}
.tpv-metodo-pago-btn:hover { border-color: var(--border-focus); background: var(--bg-active); }
.tpv-metodo-pago-btn.selected { border-color: var(--accent-primary); background: var(--bg-active); }

/* TPV Responsive — Sunmi Zepat 14" (1920x1080) */
@media (max-width: 1400px) {
    .tpv-venta-right { flex: 0 0 300px; max-width: 320px; }
    .tpv-prod-grid { grid-template-columns: repeat(auto-fill, minmax(130px, 1fr)); }
    .tpv-prod-btn { min-height: 120px; }
    .tpv-prod-btn img { width: 64px; height: 64px; }
}

/* TPV Responsive — Sunmi L3 tablet (~10") */
@media (max-width: 1100px) {
    .tpv-venta-right { flex: 0 0 270px; max-width: 280px; }
    .tpv-venta-left { padding: var(--space-2); }
    .tpv-prod-grid { grid-template-columns: repeat(auto-fill, minmax(110px, 1fr)); gap: 6px; }
    .tpv-prod-btn { min-height: 100px; padding: 6px; }
    .tpv-prod-btn .prod-name { font-size: 0.75rem; }
    .tpv-prod-btn .prod-price { font-size: 0.85rem; }
    .tpv-prod-btn img { width: 56px; height: 56px; }
    .tpv-venta-header { padding: var(--space-2) var(--space-3); }
    .tpv-cat-chip { padding: 5px 12px; font-size: 0.78rem; }
}

/* Expand button — hidden on desktop, visible on mobile */
.tpv-cart-expand-btn {
    display: none;
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
    z-index: 10;
    width: 40px;
    height: 24px;
    border-radius: 12px 12px 0 0;
    background: var(--bg-secondary);
    border: 1px solid var(--border-primary);
    border-bottom: none;
    cursor: pointer;
    align-items: center;
    justify-content: center;
    color: var(--text-secondary);
}
.tpv-cart-expand-btn:hover { background: var(--bg-tertiary); }

/* TPV Responsive — Sunmi P3R smartphone (~6") */
@media (max-width: 768px) {
    .tpv-venta-body { flex-direction: column; position: relative; }
    .tpv-venta-left { flex: none; height: 55vh; transition: height 0.3s ease; }
    .tpv-venta-right { flex: none; height: 45vh; max-width: 100%; border-left: none; border-top: 1px solid var(--border-primary); transition: height 0.3s ease; }
    .tpv-venta-left { padding: var(--space-2); }
    .tpv-prod-grid { grid-template-columns: repeat(auto-fill, minmax(90px, 1fr)); gap: 4px; }
    .tpv-prod-btn { min-height: 80px; }
    .tpv-prod-btn .prod-name { font-size: 0.7rem; -webkit-line-clamp: 1; }
    .tpv-prod-btn .prod-price { font-size: 0.78rem; }
    .tpv-prod-btn img { width: 44px; height: 44px; }
    .tpv-venta-header { padding: var(--space-1) var(--space-2); }
    .tpv-search-container { display: none; }
    .tpv-cat-chip { padding: 4px 10px; font-size: 0.75rem; }

    /* Show expand button */
    .tpv-cart-expand-btn { display: flex; top: 54.5vh; margin-top: -24px; }

    /* Expanded cart states */
    .tpv-venta-right.tpv-cart-expanded { height: 85vh; }
    .tpv-venta-left.tpv-left-collapsed { height: 15vh; }
    .tpv-left-collapsed + .tpv-cart-expand-btn { top: 14.5vh; }

    /* TPV Smartphone — Botón cobrar siempre visible */
    .tpv-venta-right {
        display: flex;
        flex-direction: column;
        overflow: hidden;
    }
}

/* Botón Cobrar SIEMPRE visible (también en Sunmi L3 landscape 1280×720, que NO
   cae en max-width:768px). Anclar el panel del ticket como flex column con scroll
   solo en las líneas/empty. Aplica en todos los viewports — no rompe escritorio
   porque .tpv-venta-right ya es flex column en ese caso por su propio CSS. */
.tpv-venta-fullscreen .tpv-venta-right {
    display: flex;
    flex-direction: column;
    overflow: hidden;
    min-height: 0;
}
.tpv-venta-fullscreen .tpv-venta-right .tpv-ticket-header,
.tpv-venta-fullscreen .tpv-venta-right .tpv-ticket-totals,
.tpv-venta-fullscreen .tpv-venta-right .tpv-ticket-actions {
    flex-shrink: 0;
}
.tpv-venta-fullscreen .tpv-venta-right .tpv-ticket-lines {
    flex: 1;
    overflow-y: auto;
    min-height: 0;
}
.tpv-venta-fullscreen .tpv-venta-right .tpv-ticket-empty {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
}

/* Sunmi L3 fix prod 2026-04-29 v2: defensa AGRESIVA — el flex layout no era
   suficiente porque el panel del ticket no recibía altura limitada del padre en
   algunos casos. Forzar `position:absolute` en el footer de acciones para
   anclarlo al fondo del .tpv-venta-right (que pasa a position:relative). Padding
   extra abajo en .tpv-ticket-lines/empty para que el último item no quede tapado
   por el botón superpuesto. */
@media (max-height: 800px) {
    .tpv-venta-fullscreen .tpv-venta-right { position: relative; }
    .tpv-venta-fullscreen .tpv-venta-right .tpv-ticket-actions {
        position: absolute; left: 0; right: 0; bottom: 0;
        background: var(--bg-secondary);
        border-top: 1px solid var(--border-primary);
        padding: 6px 8px; z-index: 5;
    }
    .tpv-venta-fullscreen .tpv-venta-right .tpv-ticket-lines,
    .tpv-venta-fullscreen .tpv-venta-right .tpv-ticket-empty {
        padding-bottom: 64px;
    }
    .tpv-ticket-actions .btn { height: 40px; }
    .tpv-ticket-actions .btn[style*="flex:0.3"] {
        flex: 0 0 36px !important; padding: 0; min-width: 36px;
    }
}

/* 2026-05-26: defensa secundaria del header desktop tpv-venta-header en
   viewport <600px. La solución primaria es _isMobileViewport() activando
   tpvMobileVentaArrancar (pantalla móvil dedicada). Este bloque es la red de
   seguridad para casos donde la pantalla desktop se renderice por error
   (WebView raro, usuario toca "Abrir TPV" desde el dashboard del módulo en un
   handheld). Sin esto: chip "Empleado TEST" enorme + IA + ☕ Consumo cortado
   en el borde derecho (verificado por Adri 2026-05-26 vía scrcpy Sunmi L3
   324x720). */
@media (max-width: 599px) {
    .tpv-venta-header {
        gap: var(--space-1) !important;
        padding: var(--space-1) var(--space-2) !important;
    }
    /* Ocultar el span del nombre de usuario en el header (es info redundante
       con el sidebar/launcher). Ahorra ~80-120px en el cluster derecho. */
    .tpv-venta-header > div:last-child > .text-muted {
        display: none !important;
    }
    /* Ocultar labels de Vendedor / Consumo personal, dejando solo iconos.
       Los nombres completos siguen viéndose al tocar el botón (modal). */
    .tpv-venta-header #tpvVendedorLabel,
    .tpv-venta-header #tpvConsumoLabel {
        display: none !important;
    }
    /* Compactar gap entre botones del cluster derecho */
    .tpv-venta-header > div:last-child {
        gap: 4px !important;
        flex-shrink: 0;
    }
    /* Compactar botón IA gradient (icono + label "IA") */
    .tpv-venta-header [data-action="agentChatToggle"] {
        padding: 4px 8px !important;
    }
    /* Compactar buscador (ya está oculto en <768px desde 2592) */
    /* Permitir wrap del header como último recurso si aún no cabe */
    .tpv-venta-header {
        flex-wrap: wrap;
    }
}

@media (max-width: 768px) {
    .tpv-ticket-actions {
        padding: var(--space-1) var(--space-2);
    }
    .tpv-ticket-actions .btn { height: 44px; }
    .tpv-ticket-actions .btn[style*="flex:0.3"] {
        flex: 0 0 36px !important;
        padding: 0;
        min-width: 36px;
    }
}

/* Icon selector for payment methods */
.icon-selector-grid {
    display: grid;
    grid-template-columns: repeat(7, 1fr);
    gap: var(--space-2);
    padding: var(--space-2);
}

.icon-selector-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    border-radius: var(--radius-sm);
    border: 2px solid var(--border-primary);
    background: var(--bg-tertiary);
    color: var(--text-secondary);
    cursor: pointer;
    transition: all 0.15s;
}

.icon-selector-btn:hover {
    border-color: var(--accent-primary);
    color: var(--text-primary);
}

.icon-selector-btn.selected {
    border-color: var(--accent-primary);
    background: var(--accent-soft);
    color: var(--accent-primary);
}

/* =====================================================
   STAT CARD ACCENT BORDERS (Fabricación)
   ===================================================== */
.stat-accent-green  { border-left: 3px solid var(--accent-green); }
.stat-accent-red    { border-left: 3px solid var(--accent-red); }
.stat-accent-orange { border-left: 3px solid var(--accent-orange); }
.stat-accent-purple { border-left: 3px solid var(--accent-purple); }

/* =====================================================
   STATUS PILL EXTRA VARIANTS
   ===================================================== */
.status-pill.warning  { background: color-mix(in srgb, var(--accent-orange) 10%, transparent); color: var(--accent-orange); }
.status-pill.danger   { background: color-mix(in srgb, var(--accent-red) 10%, transparent); color: var(--accent-red); }
.status-pill.info     { background: color-mix(in srgb, var(--accent-blue) 10%, transparent); color: var(--accent-blue); }
.status-pill.completed { background: color-mix(in srgb, var(--accent-green) 10%, transparent); color: var(--accent-green); }
.status-pill.neutral  { background: color-mix(in srgb, var(--text-secondary) 10%, transparent); color: var(--text-secondary); }

/* =====================================================
   MÓDULO INFORMES — Estilos específicos
   ===================================================== */

/* B1: Píldoras de filtros en Informes */
#infContent .btn-ghost,
#infContent .btn.btn-ghost {
    background: var(--bg-tertiary);
    border: 1px solid var(--border-primary);
    color: var(--text-primary);
    border-radius: 20px;
    padding: 8px 16px;
    font-family: var(--font-ui);
    font-size: 14px;
    cursor: pointer;
    min-height: 36px;
    transition: all 0.2s;
}
#infContent .btn-ghost:hover,
#infContent .btn.btn-ghost:hover {
    border-color: var(--accent-primary);
    color: var(--accent-primary);
}
#infContent .btn-primary,
#infContent .btn.btn-primary {
    background: var(--accent-fill);
    color: var(--accent-fill-contrast);
    border: 1px solid var(--accent-fill-border);
    border-radius: 20px;
    padding: 8px 16px;
    font-family: var(--font-ui);
    font-size: 14px;
    cursor: pointer;
    min-height: 36px;
    font-weight: 500;
}
#infContent .btn-primary:hover,
#infContent .btn.btn-primary:hover {
    background: var(--accent-fill-hover);
    border-color: var(--accent-fill-border);
}

/* B1: Selectores y date inputs en filtros de Informes */
#infContent .form-control,
#infContent select.form-control,
#infContent input[type="date"].form-control {
    background: var(--bg-tertiary);
    border: 1px solid var(--border-primary);
    color: var(--text-primary);
    border-radius: 20px;
    padding: 8px 16px;
    font-family: var(--font-ui);
    font-size: 14px;
    min-height: 36px;
    transition: border-color 0.2s;
}
#infContent .form-control:hover,
#infContent .form-control:focus {
    border-color: var(--accent-primary);
    outline: none;
}
#infContent .form-label {
    font-family: var(--font-ui);
    font-size: 13px;
    color: var(--text-secondary);
}

/* B2: Títulos de sección en Informes */
#infContent .inf-section-title {
    font-family: var(--font-ui);
    font-size: 18px;
    font-weight: 300;
    color: var(--text-primary);
    margin-bottom: 16px;
}

/* B2: KPI cards en Informes — consistentes */
#infContent .card.kpi-card,
/* v6.2 (2026-07-26): Informes NO redefine .stat-card. Estos overrides daban a las
   tarjetas KPI de Informes otro padding (20/24 vs 24), otro borde (--border-primary
   vs --border-secondary), otro radio (md vs lg), otra tipografía de label (--font-ui
   13px vs el label mono uppercase del canon) y otro tamaño de valor (22px/600 vs
   1.75rem/700) → era la última causa de "el dashboard no mide lo mismo en cada
   módulo" (queja de Adri). El canon manda: § KPI / STAT CARDS.
   .kpi-label/.kpi-value SÍ se conservan: son OTRO componente (.card.kpi-card), el
   lenguaje propio de los informes de detalle, no la tarjeta KPI del dashboard. */
#infContent .kpi-label {
    font-family: var(--font-ui);
    font-size: 13px;
    color: var(--text-secondary);
}
#infContent .kpi-value {
    font-size: 22px;
    font-weight: 600;
    color: var(--text-primary);
}

/* B2: Tablas en Informes — estilo consistente */
#infContent .table thead tr,
#infContent table thead tr {
    background: var(--bg-tertiary);
}
#infContent .table thead th,
#infContent table thead th {
    color: var(--text-secondary);
    font-size: 12px;
    text-transform: uppercase;
    font-family: var(--font-ui);
    font-weight: 500;
    letter-spacing: 0.03em;
    padding: 12px 16px;
    border-bottom: 1px solid var(--border-primary);
}
#infContent .table tbody tr,
#infContent table tbody tr {
    border-bottom: 1px solid var(--border-primary);
    transition: background 0.15s;
}
#infContent .table tbody tr:hover,
#infContent table tbody tr:hover {
    background: var(--bg-tertiary);
}
#infContent .table td,
#infContent table td {
    padding: 10px 16px;
    font-family: var(--font-ui);
    font-size: 14px;
}

/* B2: Cards generales en Informes */
#infContent .card {
    background: var(--bg-card);
    border: 1px solid var(--border-primary);
    border-radius: var(--radius-md);
}

/* B3: Pestañas de navegación principal del módulo Informes */
#infTabs {
    gap: 0;
    border-bottom: 1px solid var(--border-primary);
}
#infTabs .tab {
    color: var(--text-secondary);
    border-bottom: 2px solid transparent;
    padding: 12px 20px;
    font-family: var(--font-ui);
    font-size: 14px;
    font-weight: 400;
    transition: all 0.2s;
    background: transparent;
}
#infTabs .tab:hover {
    color: var(--text-primary);
}
#infTabs .tab.active {
    color: var(--accent-primary);
    border-bottom-color: var(--accent-primary);
    font-weight: 500;
}

/* =====================================================
   AYUDA — Botón ? y Drawer lateral
   ===================================================== */

/* Botón ? en header */
.btn-ayuda {
    width: 32px;
    height: 32px;
    border-radius: 50%;
    border: 1.5px solid var(--border-primary);
    background: var(--bg-tertiary);
    color: var(--text-secondary);
    font-family: var(--font-ui);
    font-size: 15px;
    font-weight: 600;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: all 0.2s ease;
    flex-shrink: 0;
}
.btn-ayuda:hover {
    border-color: var(--accent-primary);
    color: var(--accent-primary);
    background: var(--accent-soft);
}

/* Overlay detrás del drawer */
.ayuda-overlay {
    display: none;
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.4);
    z-index: 9998;
}
.ayuda-overlay.open {
    display: block;
}

/* Drawer lateral */
.ayuda-drawer {
    position: fixed;
    top: 0;
    right: 0;
    width: 30%;
    min-width: 340px;
    max-width: 480px;
    height: 100vh;
    background: var(--bg-secondary);
    border-left: 1px solid var(--border-primary);
    z-index: 9999;
    transform: translateX(100%);
    transition: transform 0.25s ease;
    display: flex;
    flex-direction: column;
    overflow: hidden;
}
.ayuda-drawer.open {
    transform: translateX(0);
}

/* Header del drawer */
.ayuda-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 20px 24px;
    border-bottom: 1px solid var(--border-primary);
    flex-shrink: 0;
}
.ayuda-titulo {
    font-family: var(--font-ui);
    font-size: 20px;
    font-weight: 400;
    color: var(--text-primary);
    margin: 0;
}
.ayuda-cerrar {
    width: 36px;
    height: 36px;
    border-radius: 8px;
    border: none;
    background: transparent;
    color: var(--text-secondary);
    font-size: 18px;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: background 0.15s;
}
.ayuda-cerrar:hover {
    background: var(--bg-tertiary);
    color: var(--text-primary);
}

/* Body scrollable */
.ayuda-body {
    flex: 1;
    overflow-y: auto;
    padding: 16px 24px 32px;
}

/* Secciones */
.ayuda-seccion-titulo {
    font-family: var(--font-ui);
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 1.2px;
    color: var(--text-muted);
    margin: 24px 0 12px;
    padding-bottom: 8px;
    border-bottom: 1px solid var(--border-primary);
}
.ayuda-seccion-titulo:first-child {
    margin-top: 0;
}

/* Items de la lista */
.ayuda-item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 14px 12px;
    border-radius: 8px;
    cursor: pointer;
    transition: background 0.15s;
    min-height: 48px;
}
.ayuda-item:hover {
    background: var(--bg-tertiary);
}
.ayuda-item-titulo {
    font-family: var(--font-ui);
    font-size: 14px;
    color: var(--text-primary);
    flex: 1;
}
.ayuda-item-arrow {
    color: var(--text-muted);
    font-size: 14px;
    margin-left: 8px;
    flex-shrink: 0;
}

/* Vista detalle */
.ayuda-detalle-volver {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 10px 0;
    color: var(--accent-primary);
    font-family: var(--font-ui);
    font-size: 13px;
    font-weight: 500;
    cursor: pointer;
    background: none;
    border: none;
    min-height: 48px;
    transition: opacity 0.15s;
}
.ayuda-detalle-volver:hover {
    opacity: 0.8;
}
.ayuda-detalle-titulo {
    font-family: var(--font-ui);
    font-size: 18px;
    font-weight: 400;
    color: var(--text-primary);
    margin: 8px 0 4px;
}
.ayuda-detalle-modulo {
    font-family: var(--font-ui);
    font-size: 12px;
    color: var(--text-muted);
    margin-bottom: 20px;
}
.ayuda-detalle-separador {
    height: 1px;
    background: var(--border-primary);
    margin-bottom: 20px;
}
.ayuda-paso {
    display: flex;
    gap: 12px;
    padding: 10px 0;
    min-height: 48px;
    align-items: flex-start;
}
.ayuda-paso-num {
    width: 28px;
    height: 28px;
    border-radius: 50%;
    background: var(--bg-tertiary);
    border: 1px solid var(--border-primary);
    color: var(--accent-primary);
    font-family: var(--font-ui);
    font-size: 12px;
    font-weight: 600;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    margin-top: 1px;
}
.ayuda-paso-texto {
    font-family: var(--font-ui);
    font-size: 14px;
    color: var(--text-primary);
    line-height: 1.5;
    padding-top: 3px;
}
.ayuda-btn-video {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    margin-top: 24px;
    padding: 12px 20px;
    min-height: 48px;
    border-radius: 8px;
    border: 1px solid var(--accent-primary);
    background: var(--accent-soft);
    color: var(--accent-primary);
    font-family: var(--font-ui);
    font-size: 14px;
    font-weight: 500;
    cursor: pointer;
    transition: background 0.15s;
}
.ayuda-btn-video:hover {
    background: color-mix(in srgb, var(--accent-primary) 15%, transparent);
}

/* Responsive: móvil y tablet → 100% ancho */
@media (max-width: 1024px) {
    .ayuda-drawer {
        width: 100%;
        min-width: unset;
        max-width: unset;
    }
}

/* =====================================================
   RESPONSIVE — MOBILE ERP (≤768px)
   Override inline styles for Sunmi L3 / smartphones
   ===================================================== */
@media (max-width: 768px) {

    /* ── Viewport meta ── */
    html { font-size: 15px; }

    /* ── Filter toolbars: stack vertically ── */
    .filter-toolbar,
    [style*="display: flex"][style*="flex-wrap: wrap"],
    [style*="display:flex"][style*="flex-wrap:wrap"] {
        flex-wrap: wrap !important;
    }

    /* ── Search inputs: remove min-width constraint ── */
    .input[style*="min-width: 200px"],
    .input[style*="min-width:200px"],
    input[style*="min-width: 200px"],
    input[style*="min-width:200px"] {
        min-width: 100% !important;
        width: 100% !important;
    }

    /* ── Select filters: full width on mobile ── */
    select.input[style*="width: 140px"],
    select.input[style*="width:140px"],
    select.input[style*="width: 150px"],
    select.input[style*="width:150px"],
    select.input[style*="width: 160px"],
    select.input[style*="width:160px"],
    select.input[style*="width: 180px"],
    select.input[style*="width:180px"],
    select.input[style*="width: 200px"],
    select.input[style*="width:200px"],
    select.input[style*="width: 120px"],
    select.input[style*="width:120px"],
    select.input[style*="width: 100px"],
    select.input[style*="width:100px"],
    input.input[style*="width: 160px"],
    input.input[style*="width:160px"],
    select.form-control[style*="min-width"],
    .input[style*="max-width:240px"],
    .input[style*="max-width: 240px"],
    .input[style*="max-width:160px"],
    .input[style*="max-width: 160px"],
    .input[style*="max-width:180px"],
    .input[style*="max-width: 180px"] {
        width: 100% !important;
        max-width: 100% !important;
        min-width: 0 !important;
    }

    /* ── 4-column grids → 2 columns ── */
    .dashboard-grid[style*="repeat(4"],
    [style*="grid-template-columns:repeat(4"],
    [style*="grid-template-columns: repeat(4"] {
        grid-template-columns: repeat(2, 1fr) !important;
    }

    /* ── 2-column detail grids → 1 column (fichas) ── */
    [style*="grid-template-columns: 1fr 1fr"],
    [style*="grid-template-columns:1fr 1fr"] {
        grid-template-columns: 1fr !important;
    }

    /* ── 3-column grids → 1 column ── */
    [style*="grid-template-columns:1fr 1fr 1fr"],
    [style*="grid-template-columns: 1fr 1fr 1fr"] {
        grid-template-columns: 1fr !important;
    }

    /* ── Fabricación: rejillas de escandallo → responsive ──
       .fab-salida-grid (salidas 1→N, fabricacion.js) es la gemela de
       .fab-escandallo-grid: 6 columnas de ancho fijo, ~550 px mínimos. No tenía
       ninguna regla, así que en el Sunmi de planta se salía de la pantalla y la
       columna del botón de borrar quedaba fuera del viewport. Colapsa igual. */
    .fab-escandallo-grid,
    .fab-salida-grid {
        grid-template-columns: 1fr 1fr !important;
        gap: 8px !important;
    }
    .fab-escandallo-grid .form-group,
    .fab-salida-grid .form-group { margin: 0 !important; }

    /* ── max-width containers: full width ── */
    [style*="max-width:1200px"],
    [style*="max-width: 1200px"],
    [style*="max-width:1400px"],
    [style*="max-width: 1400px"] {
        max-width: 100% !important;
        padding-left: var(--space-2) !important;
        padding-right: var(--space-2) !important;
    }

    /* ── Images in product fichas ── */
    img[style*="max-width:200px"],
    img[style*="max-width: 200px"] {
        max-width: 100% !important;
        height: auto !important;
    }

    /* ── File upload areas ── */
    .file-upload[style*="max-width:300px"],
    .file-upload[style*="max-width: 300px"] {
        max-width: 100% !important;
    }

    /* ── Modals: full width ── */
    .modal[style*="max-width:640px"],
    .modal[style*="max-width: 640px"] {
        max-width: 95vw !important;
    }

    /* ── Holding dashboard KPI grids ── */
    .holding-charts-grid,
    [style*="grid-template-columns:1fr 1fr;gap:20px"] {
        grid-template-columns: 1fr !important;
    }

    /* ── TPV venta: panel derecho stackable ── */
    .tpv-venta-body {
        flex-direction: column !important;
        position: relative;
    }
    .tpv-venta-right {
        flex: none !important;
        max-width: 100% !important;
        border-left: none !important;
        border-top: 1px solid var(--border-primary);
        max-height: none;
        height: 45vh;
        transition: height 0.3s ease;
    }
    .tpv-venta-left {
        flex: none !important;
        height: 55vh;
        min-height: auto;
        transition: height 0.3s ease;
    }
    .tpv-cart-expand-btn { display: flex; }
    .tpv-venta-right.tpv-cart-expanded { height: 85vh !important; }
    .tpv-venta-left.tpv-left-collapsed { height: 15vh !important; }

    /* ── TPV categorías: smaller cards ── */
    .tpv-cat-grid {
        grid-template-columns: repeat(auto-fill, minmax(100px, 1fr)) !important;
    }
    .tpv-cat-btn {
        min-height: 60px !important;
        padding: var(--space-2) !important;
    }

    /* ── TPV productos: smaller buttons ── */
    .tpv-prod-grid {
        grid-template-columns: repeat(auto-fill, minmax(100px, 1fr)) !important;
    }
    .tpv-prod-btn {
        min-height: 70px !important;
    }

    /* ── KDS grid ── */
    [style*="minmax(300px"] {
        grid-template-columns: 1fr !important;
    }

    /* ── Audit log detail grid ── */
    [style*="grid-template-columns:repeat(auto-fit,minmax(180px,1fr))"] {
        grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)) !important;
    }

    /* ── Pagination: compact ── */
    .card-footer[style*="display:flex"] {
        flex-wrap: wrap !important;
        gap: var(--space-2) !important;
    }

    /* ── Header: hide time, compact user menu ── */
    .header-time { display: none; }
    .user-name { display: none; }
    .user-menu { padding: 3px; }

    /* ── Holding header: stack on mobile ── */
    .holding-header {
        flex-direction: column !important;
        align-items: flex-start !important;
        gap: var(--space-3) !important;
    }

    /* ── Config logo ── */
    img[style*="max-width:180px"],
    img[style*="max-width: 180px"],
    img[style*="max-height:80px"],
    img[style*="max-height: 80px"] {
        max-width: 120px !important;
        max-height: 60px !important;
    }

    /* ── RRHH employee grid ── */
    [style*="grid-template-columns: repeat(auto-fit, minmax(200px"] {
        grid-template-columns: 1fr !important;
    }

    /* ── Informes KPI grid ── */
    [style*="grid-template-columns: repeat(4, 1fr)"] {
        grid-template-columns: repeat(2, 1fr) !important;
    }

    /* ── Flex rows with many children: wrap ── */
    [style*="display:flex"][style*="gap:var(--space-3)"],
    [style*="display: flex"][style*="gap: var(--space-3)"] {
        flex-wrap: wrap !important;
    }

    /* ── Action buttons in fichas: stack ── */
    [style*="display:flex"][style*="gap:var(--space-3)"][style*="margin-top"] {
        flex-direction: column !important;
    }
    [style*="display:flex"][style*="gap:var(--space-3)"][style*="margin-top"] .btn {
        width: 100% !important;
    }

    /* ── Tables: smaller text ── */
    .table-container {
        font-size: 0.7rem;
    }
    .table-container th,
    .table-container td {
        padding: 0.35rem 0.4rem;
        font-size: 0.7rem;
    }

    /* ── Label checkboxes compact ── */
    label[style*="display:flex"][style*="gap:6px"],
    label[style*="display: flex"][style*="gap: 6px"],
    label[style*="display:flex"][style*="gap:4px"],
    label[style*="display: flex"][style*="gap: 4px"] {
        font-size: 0.75rem !important;
    }

    /* ── TPV modals: force responsive width ── */
    .tpv-modal[style*="min-width:480px"],
    .tpv-modal[style*="min-width: 480px"],
    .tpv-modal[style*="min-width:450px"],
    .tpv-modal[style*="min-width: 450px"],
    .tpv-modal[style*="min-width:400px"],
    .tpv-modal[style*="min-width: 400px"],
    .tpv-modal[style*="min-width:380px"],
    .tpv-modal[style*="min-width: 380px"] {
        min-width: unset !important;
        max-width: 95vw !important;
        width: 95vw !important;
    }

    /* ── Grids with minmax(340px+): force single column ── */
    [style*="minmax(340px"],
    [style*="minmax(380px"],
    [style*="minmax(400px"],
    [style*="minmax(350px"] {
        grid-template-columns: 1fr !important;
    }

    /* ── Tables: remove large min-width constraints ── */
    table[style*="min-width:900px"],
    table[style*="min-width: 900px"],
    table[style*="min-width:800px"],
    table[style*="min-width: 800px"],
    table[style*="min-width:700px"],
    table[style*="min-width: 700px"],
    .table[style*="min-width:900px"],
    .table[style*="min-width: 900px"],
    .table[style*="min-width:800px"],
    .table[style*="min-width: 800px"] {
        min-width: unset !important;
    }
    .table-container {
        overflow-x: auto !important;
        -webkit-overflow-scrolling: touch;
    }

    /* ── Modals 700-800px: safe width ── */
    .modal[style*="max-width:700px"],
    .modal[style*="max-width: 700px"],
    .modal[style*="max-width:800px"],
    .modal[style*="max-width: 800px"],
    .modal[style*="max-width:900px"],
    .modal[style*="max-width: 900px"] {
        max-width: calc(100vw - 16px) !important;
    }

    /* ── Fabricación: fixed-width column grids ── */
    [style*="grid-template-columns:100px 1fr 60px"],
    [style*="grid-template-columns: 100px 1fr 60px"] {
        grid-template-columns: 1fr !important;
    }
}

/* =====================================================
   RESPONSIVE — SMALL DEVICES (≤480px) — Sunmi L3 portrait
   ===================================================== */
@media (max-width: 480px) {

    /* ── Dashboard: 1 column for very small screens ── */
    .dashboard-grid,
    .dashboard-grid[style*="repeat(4"],
    .dashboard-grid[style*="repeat(2"] {
        grid-template-columns: 1fr !important;
    }

    /* ── Empresas grid: 1 col ── */
    .empresas-grid {
        grid-template-columns: 1fr !important;
    }

    /* ── TPV: even smaller ── */
    .tpv-cat-grid {
        grid-template-columns: repeat(auto-fill, minmax(80px, 1fr)) !important;
    }
    .tpv-prod-grid {
        grid-template-columns: repeat(auto-fill, minmax(80px, 1fr)) !important;
    }

    /* ── Holding tabs: horizontal scroll ── */
    #holdingTabs {
        overflow-x: auto !important;
        flex-wrap: nowrap !important;
        -webkit-overflow-scrolling: touch;
    }
    #holdingTabs .tab {
        flex-shrink: 0 !important;
        font-size: 0.7rem !important;
    }

    /* ── Stat cards: even more compact ── */
    .stat-card {
        padding: var(--space-2) !important;
        min-height: 88px;
    }
    .stat-card-value {
        font-size: 1.25rem !important;
    }

    /* ── Content area: less padding ── */
    .content {
        padding: var(--space-2) !important;
    }

    /* ── Module header: compact ── */
    .module-header {
        gap: var(--space-2) !important;
    }
    .module-title {
        font-size: 0.95rem !important;
    }
    .module-actions .btn {
        font-size: 0.7rem !important;
        padding: 0.25rem 0.5rem !important;
    }

    /* ── Form rows → single column ── */
    .form-row {
        grid-template-columns: 1fr !important;
    }

    /* ── Fabricación grid → single column ──
       El escandallo colapsaba aquí por el selector de atributo (calcado del style
       inline de fabricacion.js). La rejilla de salidas 1→N tiene OTRO style inline
       (130px 1fr 90px…), así que ese selector no la cogía y se quedaba a 2 columnas
       justo en el ancho del Sunmi de planta (360 dp) mientras su gemela iba a 1.
       Se añade por CLASE: no depende de que el literal del inline no cambie. */
    [style*="grid-template-columns:120px 1fr 80px"],
    .fab-salida-grid {
        grid-template-columns: 1fr !important;
    }
}

/* =====================================================
   CSP capa 2: Utilidades hover (reemplazan onmouseover/onmouseout inline)
   Sesión S2 — migración final onclick → data-* / CSS
   ===================================================== */
.hover-scale { transition: transform 0.15s; }
.hover-scale:hover { transform: scale(1.03); }

.hover-bg-tertiary { transition: background 0.15s; }
.hover-bg-tertiary:hover { background: var(--bg-tertiary); }

.hover-bg-secondary { transition: background 0.15s; }
.hover-bg-secondary:hover { background: var(--bg-secondary); }

/* Fila/tarjeta con hover de borde + fondo (facturación: selector de línea) */
.hover-border-accent {
    transition: border-color 0.15s, background 0.15s;
    border: 1px solid transparent;
}
.hover-border-accent:hover {
    border-color: var(--accent-primary);
    background: var(--bg-tertiary);
}

/* Lift: translateY + sombra (calidad) */
.hover-lift { transition: transform 0.15s, box-shadow 0.15s; }
.hover-lift:hover {
    transform: translateY(-1px);
    box-shadow: var(--shadow-md);
}

/* Borde con color primario al hover (calidad) */
.hover-border-primary { transition: border-color 0.15s; }
.hover-border-primary:hover { border-color: var(--primary); }

/* =====================================================
   PRINT
   ===================================================== */
@media print {
    .sidebar, .header, .toast-container, .modal-overlay, .ayuda-drawer, .ayuda-overlay { display: none !important; }
    .main { margin-left: 0 !important; }
    .content { padding: 0; overflow: visible; }
    body { background: white; color: black; }
}

/* =====================================================
   v6 · MOVIMIENTO (OTP · DROPZONE · NAV) — 2026-08-22
   Componentes shared/otp-input.js y shared/dropzone.js + transiciones de
   navegación (título, contenido, tab bar). Solo tokens; cero hex de marca.
   Un único bloque prefers-reduced-motion al final anula todo lo de aquí.
   ===================================================== */

/* ── OTP / PIN ── */
.otp {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 18px;
    outline: none;
    user-select: none;
    -webkit-user-select: none;
}
.otp:focus-visible .otp-boxes { outline: 2px solid var(--accent-primary); outline-offset: 8px; border-radius: var(--radius-md); }
.otp-boxes {
    position: relative;
    display: flex;
    gap: 12px;
}
.otp-box {
    position: relative;
    z-index: 1;
    width: 52px;
    height: 60px;
    border: 1.5px solid var(--border-primary);
    border-radius: var(--radius-md);
    background: var(--bg-secondary);
    display: grid;
    place-items: center;
    font-family: var(--font-mono);
    font-size: 1.5rem;
    font-weight: 600;
    color: var(--text-primary);
    transition: border-color var(--transition-fast), background var(--transition-fast);
}
.otp--compact .otp-boxes { gap: 7px; }
.otp--compact .otp-box { width: 34px; height: 46px; font-size: 1.1rem; border-radius: var(--radius-sm); }
.otp-box.filled { border-color: var(--accent-primary); }
.otp-box > i { font-style: normal; display: block; line-height: 1; }
.otp-box > i.otp-settle { animation: otpSettle 0.32s var(--ease-spring) both; }
/* anillo único que persigue la caja activa (transform sobre UNA capa) */
.otp-ring {
    position: absolute;
    top: -3px;
    left: 0;
    width: var(--otp-w, 52px);
    height: calc(100% + 6px);
    margin-left: -3px;
    padding: 0 3px;
    box-sizing: content-box;
    border-radius: calc(var(--radius-md) + 3px);
    border: 1.5px solid var(--accent-primary);
    box-shadow: 0 0 16px color-mix(in srgb, var(--accent-primary) 35%, transparent);
    transform: translateX(var(--otp-ring-x, 0px));
    transition: transform 0.26s var(--ease-spring), opacity var(--transition-fast);
    pointer-events: none;
    z-index: 0;
}
.otp--compact .otp-ring { border-radius: calc(var(--radius-sm) + 3px); }
.otp.is-busy .otp-ring { animation: otpRingPulse 1s ease-in-out infinite; }
.otp--shake .otp-boxes { animation: verifyShake 0.4s ease; }
.otp-keypad {
    display: grid;
    grid-template-columns: repeat(3, 72px);
    gap: 12px;
}
.otp-key {
    font-family: var(--font-ui);
    font-size: 26px;
    font-weight: 600;
    padding: 16px 0;
    border: none;
    border-radius: 14px;
    background: var(--bg-tertiary);
    color: var(--text-primary);
    cursor: pointer;
    transition: transform var(--transition-fast), background var(--transition-fast), opacity var(--transition-fast);
    -webkit-tap-highlight-color: transparent;
}
.otp-key:active { transform: scale(0.94); background: color-mix(in srgb, var(--accent-fill) 18%, var(--bg-tertiary)); }
.otp-key--aux, .otp-key--del { background: transparent; color: var(--text-secondary); }
.otp-key--aux { font-size: 13px; }
.otp-key--del { font-size: 22px; }
.otp-key:disabled { opacity: 0.5; cursor: default; }
.otp.is-busy .otp-key { pointer-events: none; }
@keyframes otpSettle {
    0%   { transform: translateY(-14px) scale(0.7); opacity: 0; }
    60%  { transform: translateY(2px) scale(1.06); opacity: 1; }
    100% { transform: none; opacity: 1; }
}
@keyframes otpRingPulse {
    0%, 100% { box-shadow: 0 0 10px color-mix(in srgb, var(--accent-primary) 25%, transparent); }
    50%      { box-shadow: 0 0 22px color-mix(in srgb, var(--accent-primary) 55%, transparent); }
}

/* ── DROPZONE ── */
.dz { display: flex; flex-direction: column; gap: var(--space-3); }
.dz-drop {
    position: relative;
    min-height: 140px;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 6px;
    padding: var(--space-4);
    text-align: center;
    border: 2px dashed var(--border-primary);
    border-radius: var(--radius-md);
    background: var(--bg-secondary);
    color: var(--text-secondary);
    cursor: pointer;
    transition: border-color var(--transition-fast), background var(--transition-fast), transform var(--transition-fast);
}
.dz-drop:hover, .dz-drop:focus-visible { border-color: var(--accent-primary); color: var(--text-primary); outline: none; }
.dz.is-over .dz-drop {
    border-color: var(--accent-primary);
    background: color-mix(in srgb, var(--accent-primary) 6%, transparent);
    transform: scale(1.01);
}
.dz-icon { color: var(--accent-primary); display: inline-flex; }
.dz-icon svg { width: 30px; height: 30px; }
.dz.is-over .dz-icon { animation: dzIconBob 0.6s var(--ease-out) infinite alternate; }
.dz-text { font-size: 0.875rem; color: var(--text-primary); }
.dz-hint { font-size: 0.75rem; color: var(--text-tertiary); }
.dz-input { display: none; }
.dz-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    font-family: var(--font-mono);
    font-size: 0.6875rem;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-secondary);
}
.dz-clear {
    font-family: inherit;
    font-size: inherit;
    letter-spacing: inherit;
    text-transform: inherit;
    background: transparent;
    border: none;
    color: var(--accent-primary);
    cursor: pointer;
    padding: 2px 4px;
}
.dz-clear:hover { text-decoration: underline; }
.dz-list { display: flex; flex-direction: column; gap: 6px; }
.dz-card {
    display: grid;
    grid-template-columns: 40px 1fr auto;
    gap: var(--space-3);
    align-items: center;
    padding: 8px 10px;
    background: var(--bg-secondary);
    border: 1px solid var(--border-primary);
    border-radius: var(--radius-md);
    animation: dzCardIn 0.26s var(--ease-out) both;
    animation-delay: calc(var(--i, 0) * 45ms);
}
.dz-thumb {
    width: 40px;
    height: 40px;
    border-radius: var(--radius-sm);
    background: var(--bg-tertiary);
    display: grid;
    place-items: center;
    overflow: hidden;
    color: var(--text-secondary);
}
.dz-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.dz-meta { min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.dz-name { font-size: 0.8125rem; color: var(--text-primary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dz-sub { display: flex; align-items: center; gap: 10px; font-size: 0.75rem; color: var(--text-tertiary); font-variant-numeric: tabular-nums; }
.dz-state { display: inline-flex; align-items: center; gap: 4px; }
.dz-state svg { width: 12px; height: 12px; }
.dz-card[data-estado="ok"] .dz-state { color: var(--accent-primary); font-weight: 600; }
.dz-card[data-estado="error"] .dz-state {
    color: var(--accent-red);
    font-weight: 600;
    background: color-mix(in srgb, var(--accent-red) 12%, transparent);
    border-radius: 999px;
    padding: 1px 8px;
}
.dz-card[data-estado="subiendo"] .dz-state { color: var(--text-secondary); }
.dz-bar { height: 4px; border-radius: 999px; background: var(--bg-tertiary); overflow: hidden; }
.dz-bar > i {
    display: block;
    height: 100%;
    width: 0;
    border-radius: inherit;
    background: linear-gradient(90deg, color-mix(in srgb, var(--accent-fill) 70%, transparent), var(--accent-fill));
    box-shadow: 0 0 8px color-mix(in srgb, var(--accent-fill) 55%, transparent);
    transition: width 0.4s var(--ease-out);
}
.dz-card[data-estado="error"] .dz-bar > i { background: var(--accent-red); box-shadow: none; }
.dz-rm {
    width: 28px;
    height: 28px;
    display: grid;
    place-items: center;
    border: none;
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--text-tertiary);
    cursor: pointer;
    transition: background var(--transition-fast), color var(--transition-fast);
}
.dz-rm:hover { background: var(--bg-hover); color: var(--text-primary); }
.dz-rm[hidden], .dz-bar[hidden], .dz-head[hidden], .dz-clear[hidden] { display: none; }
@keyframes dzCardIn {
    from { opacity: 0; transform: translateY(8px) scale(0.98); }
    to   { opacity: 1; transform: none; }
}
@keyframes dzIconBob {
    from { transform: translateY(0); }
    to   { transform: translateY(-4px); }
}
/* overlay de captura global (facturación): fondo tintado + círculo relleno lima */
.dz-overlay {
    position: fixed;
    inset: 0;
    z-index: 9998;
    pointer-events: none;
    background: color-mix(in srgb, var(--accent-primary) 8%, transparent);
    backdrop-filter: blur(2px);
    -webkit-backdrop-filter: blur(2px);
    animation: fadeIn 0.18s var(--ease-out);
}
.dz-overlay[hidden] { display: none; }
.dz-overlay-inner {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    text-align: center;
}
.dz-overlay-circle {
    width: 80px;
    height: 80px;
    border-radius: 50%;
    margin: 0 auto 16px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--accent-fill);
    color: var(--accent-fill-contrast);
    box-shadow: 0 8px 32px color-mix(in srgb, var(--accent-fill) 45%, transparent);
    animation: dzOverlayPop 0.32s var(--ease-spring) both;
}
.dz-overlay-title { font-size: 1.25rem; font-weight: 600; color: var(--text-primary); }
.dz-overlay-sub { font-size: 0.875rem; color: var(--text-secondary); margin-top: 8px; }
@keyframes dzOverlayPop {
    from { transform: scale(0.6); opacity: 0; }
    to   { transform: none; opacity: 1; }
}

/* ── NAVEGACIÓN: cross-fade del título y entrada del contenido ── */
.header-title.title-swap { animation: titleSwap 0.28s var(--ease-out) both; }
@keyframes titleSwap {
    from { opacity: 0; transform: translateY(4px); }
    to   { opacity: 1; transform: none; }
}
/* Se aplica SOLO mientras dura la animación (app.js la quita en animationend): un
   transform permanente crearía contexto de apilamiento y clipearía los fixed hijos. */
.content.module-enter { animation: moduleEnter 0.22s var(--ease-out) both; }
@keyframes moduleEnter {
    from { opacity: 0; transform: translateY(6px); }
    to   { opacity: 1; transform: none; }
}

/* ── reduced-motion: anula TODO lo de este bloque ── */
@media (prefers-reduced-motion: reduce) {
    .otp-box, .otp-ring, .otp-key, .nav-bead, .mobile-tab-bead, .dz-drop, .dz-bar > i, .dz-rm {
        transition: none !important;
    }
    .otp-box > i.otp-settle, .otp.is-busy .otp-ring, .otp--shake .otp-boxes,
    .dz-card, .dz.is-over .dz-icon, .dz-overlay, .dz-overlay-circle,
    .header-title.title-swap, .content.module-enter {
        animation: none !important;
    }
    .dz.is-over .dz-drop { transform: none; }
}

/* ══════════════════════════════════════════════════════════════════════════
   IDIOMA DE INTERFAZ — modal de primera vez y «Mi cuenta → Idioma»
   (ADR_Internacionalizacion F0, 2026-08-22). Solo tokens; dark y light; el
   estado lo dice el borde de relleno (--accent-fill-border, WCAG 1.4.11) y el
   check en --accent-primary, nunca un semáforo lateral (v6.3).
   ══════════════════════════════════════════════════════════════════════════ */
.idioma-modal { max-width: 460px; }
.idioma-kicker { font-family: var(--font-mono); font-size: 10px; letter-spacing: .12em; text-transform: uppercase; color: var(--text-tertiary); margin-bottom: 4px; }
.idioma-texto { color: var(--text-secondary); font-size: .875rem; line-height: 1.5; margin: 0 0 var(--space-4); }
.idioma-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-3); }
.idioma-opt {
    position: relative; display: flex; flex-direction: column; align-items: flex-start; gap: 2px;
    padding: var(--space-4); background: var(--bg-secondary); border: 1px solid var(--border-primary);
    border-radius: var(--radius-lg); cursor: pointer; text-align: left; color: var(--text-primary);
    font-family: var(--font-ui); transition: border-color var(--transition-fast), background var(--transition-fast);
}
.idioma-opt:hover { border-color: var(--border-secondary); background: var(--bg-tertiary); }
.idioma-opt.active { border-color: var(--accent-fill-border); background: color-mix(in srgb, var(--accent-primary) 10%, transparent); }
.idioma-flag { font-size: 1.5rem; line-height: 1; }
.idioma-nombre { font-family: var(--font-display); font-weight: 600; font-size: .95rem; }
.idioma-code { font-family: var(--font-mono); font-size: 10px; letter-spacing: .08em; color: var(--text-tertiary); text-transform: uppercase; }
.idioma-check { position: absolute; top: var(--space-3); right: var(--space-3); width: 16px; height: 16px; color: var(--accent-primary); opacity: 0; }
.idioma-opt.active .idioma-check { opacity: 1; }
@media (max-width: 480px) { .idioma-grid { grid-template-columns: 1fr; } }


/* React · DVERGENT Liquid. El atributo lo declara su index; se hereda también
   en portales, sin cambiar la entrada clásica, permisos ni estados del ERP. */
:root[data-ui="liquid"] {
    --sidebar-width: 208px;
    --bg-app: var(--liquid-canvas);
    --bg-primary: var(--liquid-panel);
    --bg-secondary: var(--liquid-secondary);
    --bg-card: var(--liquid-card);
    --bg-sidebar: var(--liquid-sidebar);
    --bg-input: var(--liquid-input);
    /* El tercer escalón y el texto «apagado» también pasan a Liquid (SPEC § 4.1):
       el Volt claro tenía un gris cálido y un --text-muted de 3,1:1 que en React
       se usaba para fechas, códigos y pies que sí son información. */
    --bg-tertiary: var(--liquid-tertiary);
    --text-primary: var(--liquid-ink);
    --text-secondary: var(--liquid-secondary-ink);
    --text-tertiary: var(--liquid-tertiary-ink);
    --text-muted: var(--liquid-tertiary-ink);
    --border-primary: var(--liquid-line);
    --border-strong: var(--liquid-edge);
    --accent-primary: var(--liquid-accent);
    --accent-hover: var(--liquid-accent-hover);
    --accent-contrast: var(--liquid-accent-ink);
    --accent-fill: var(--liquid-accent);
    --accent-fill-hover: var(--liquid-accent-hover);
    --accent-fill-contrast: var(--liquid-accent-ink);
    --accent-fill-border: var(--liquid-accent);
    --border-focus: var(--liquid-accent);
    --bg-overlay: var(--liquid-overlay);
    --glass-surface: var(--liquid-material);
    --glass-control: var(--liquid-control);
    --glass-edge: var(--liquid-edge);
    --glass-shadow: var(--liquid-shadow);
    --glass-highlight: var(--liquid-gloss);
    --glass-header: var(--liquid-header);
    /* Lo que flota sobre datos: denso con desenfoque; opaco cuando no lo hay. */
    --glass-float: var(--liquid-float);
    /* Solidez en el dato (SPEC § 2.4): el fondo opaco de tablas, campos y lienzos.
       Es el tono del listado de Clientes aprobado el 08-10 (#09090b en Dark, casi
       el negro del lienzo; blanco en Light), no el gris de --bg-primary. */
    --glass-solid: var(--liquid-input);
    /* Papel: blanco en los DOS temas. Fondo de las firmas (tinta #1D3461, hecha para
       estamparse en papel: sobre el campo de Dark quedaba a 1,4-1,6:1) y de la muestra
       del color «blanco», que con --bg-primary salía casi negra y se leía «negro». */
    --liquid-paper: #ffffff;
    --glass-blur: 18px;
    --glass-panel-radius: 18px;
    --glass-control-radius: 10px;
    /* Radios aprobados que no son panel ni control: las tarjetas KPI y las burbujas
       (16), y el asistente flotante con su acceso (24). */
    --glass-tile-radius: 16px;
    --glass-float-radius: 24px;
    /* La tarjeta de la gráfica mensual aprobada (22; 19 en el móvil, más abajo) y los
       remates de 2 px de sus columnas «edificio» (el asa): valores de las capturas
       del 08-10, no los del panel ni los de un control. */
    --glass-chart-radius: 22px;
    --glass-detail-radius: 2px;
    --glass-panel-padding: 24px;
    --radius-lg: 18px;
    --radius-xl: 20px;
    --shadow-card: var(--liquid-shadow);
    /* Acceso: luz Vesper de la web DVERGENT, pigmentos definidos en el tema. */
    --acceso-scene-bg: #000000;
    --acceso-scene-a: #8b98fe;
    --acceso-scene-b: #6bfdff;
    --acceso-scene-strength: .32;
    --acceso-scene-opacity: 1;
    --acceso-card: linear-gradient(145deg, rgba(255,255,255,.07), rgba(255,255,255,.012) 45%), rgba(14,16,20,.56);
    --acceso-card-edge: rgba(232,236,255,.19);
    --acceso-card-glint: rgba(235,247,255,.65);
    --acceso-card-shadow: inset 0 1px 0 rgba(255,255,255,.12), 0 24px 80px rgba(0,0,0,.5);
    --acceso-card-reduced: linear-gradient(145deg, rgba(255,255,255,.07), rgba(255,255,255,.015) 45%), rgba(14,16,20,.68);
    --acceso-control: #13151a;
    /* Geometría de la tarjeta aprobada el 04-10 («Ok, dejalo asi»). */
    --acceso-card-radius: 26px;
    --acceso-card-radius-compacta: 24px;
    --acceso-control-radius: 12px;
    /* Menú lateral: negro en los DOS temas (decisión de Adri, 2026-10-09). Sobre
       esa superficie oscura el acento vuelve a ser el lima también en Light. */
    --liquid-rail: #000000;
    --liquid-rail-raised: #111214;
    --liquid-rail-ink: #eff0ed;
    --liquid-rail-ink-2: #a4a8b0;
    --liquid-rail-ink-3: #8b9099;
    --liquid-rail-line: rgba(255,255,255,.063);
    --liquid-rail-hover: rgba(255,255,255,.05);
    --liquid-rail-accent: #c6f23a;
    --liquid-rail-accent-ink: #131a03;
    /* El cristal del menú también es el del tema oscuro: con el material de Light
       (blanco al 70-95 %) el título de la primera sección y el item activo
       quedaban blanco sobre gris claro. */
    --liquid-rail-material: linear-gradient(138deg, rgba(255,255,255,.095), rgba(255,255,255,.012) 34%, transparent 55%, rgba(255,255,255,.028)), linear-gradient(165deg, rgba(25,28,32,.42), rgba(6,7,9,.28) 70%);
    --liquid-rail-control: linear-gradient(150deg, rgba(255,255,255,.07), rgba(255,255,255,.015) 65%);
    --liquid-rail-edge: rgba(255, 255, 255, 0.18);
    --liquid-rail-shadow: inset 0 1px 0 rgba(255,255,255,.15), inset 0 -1px 0 rgba(255,255,255,.025), 0 2px 4px rgba(0,0,0,.12), 0 16px 38px rgba(0,0,0,.24);
    /* Geometría por tramo de ventana (plegables, 2026-10-10). data-ventana lo escribe
       ViewportProvider: compacta < 600 · media 600-839 · expandida 840-1199 · amplia
       ≥ 1200. NO decide el marco (eso es data-viewport, D17). Aquí, los valores de
       «amplia»; los otros tramos los redefinen más abajo. Sin data-ventana (antes de
       que React monte) rige esto, que es el escritorio de siempre. */
    /* Margen lateral de la página: el 28 del escritorio y el 16 del móvil aprobados. */
    --liquid-gutter: 28px;
    /* Ancho máximo del contenido, sin el gutter (el 1840 del Dashboard). */
    --liquid-content-max: 1840px;
    /* Tope de lo que en móvil es hoja a todo el ancho (diálogos, avisos, Hermes):
       en «compacta» ocupa el ancho; desde «media», centrado con este tope. */
    --liquid-hoja-max: 640px;
    /* Menú lateral: abierto, en rail (plegado) y lo que el contenido le reserva por
       defecto en cada tramo (abierto en «amplia», rail en «media»/«expandida», nada
       en «compacta», que siempre monta el marco móvil). Abierto sobre el contenido
       en rail, la sombra de lo superpuesto (el menú es negro en los dos temas). */
    --liquid-menu-ancho: var(--sidebar-width);
    --liquid-menu-rail: var(--sidebar-collapsed);
    --liquid-menu-reserva: var(--liquid-menu-ancho);
    --liquid-menu-sombra-superpuesto: 0 0 0 1px rgba(255,255,255,.06), 0 24px 64px rgba(0,0,0,.5);
    /* Zonas seguras (notch, isla, barra de gestos, esquinas) con viewport-fit=cover y
       la barra de estado translúcida de iOS. 0 donde el navegador no las da. */
    --safe-top: env(safe-area-inset-top, 0px);
    --safe-right: env(safe-area-inset-right, 0px);
    --safe-bottom: env(safe-area-inset-bottom, 0px);
    --safe-left: env(safe-area-inset-left, 0px);
}

/* En Light el CTA es negro, pero los estados siguen teniendo semántica. */
:root[data-ui="liquid"][data-theme="light"] {
    --accent-green: var(--liquid-success);
    --accent-indigo: var(--accent-primary);
    --kpi-positive: var(--accent-green);
    --success: var(--accent-green);
    --success-contrast: var(--accent-contrast);
    --color-success: var(--accent-green);
    --color-success-contrast: var(--accent-contrast);
    --accent-green-contrast: var(--accent-contrast);
    --acceso-scene-bg: #f7f7f8;
    --acceso-scene-a: #b7becd;
    --acceso-scene-b: #e3e6ed;
    --acceso-scene-strength: .08;
    --acceso-scene-opacity: .1;
    --acceso-card: linear-gradient(145deg, rgba(255,255,255,.66), rgba(255,255,255,.52));
    --acceso-card-edge: rgba(0,0,0,.18);
    --acceso-card-glint: rgba(255,255,255,.9);
    --acceso-card-shadow: inset 0 1px 0 #ffffff, 0 24px 80px rgba(21,29,49,.09);
    --acceso-card-reduced: linear-gradient(145deg, rgba(255,255,255,.82), rgba(255,255,255,.70));
    --acceso-control: #ffffff;
}

/* Material progresivo: campos sólidos, tarjetas translúcidas y reflejos sin
   filtros por panel en terminales. Respeta las preferencias del sistema. */
:root[data-ui="liquid"][data-viewport="mobile"] {
    --glass-blur: 0px;
    /* Sin desenfoque, lo que flota tiene que ser opaco: si no, se lee el dato de debajo. */
    --glass-float: var(--liquid-panel);
    --glass-chart-radius: 19px;
}
/* Tramos de ventana (data-ventana, ViewportProvider). Solo geometría: ni colores ni
   material, que siguen colgando de data-theme y data-viewport. «amplia» es el bloque
   base de arriba. El L3 (criterio 5) es siempre «compacta» aunque informe de 720 px. */
:root[data-ui="liquid"][data-ventana="compacta"] {
    --liquid-gutter: 16px;
    --liquid-content-max: 100%;
    --liquid-hoja-max: 100%;
    --liquid-menu-reserva: 0px;
}
:root[data-ui="liquid"][data-ventana="media"] {
    --liquid-gutter: 24px;
    --liquid-content-max: 720px;
    --liquid-menu-reserva: var(--liquid-menu-rail);
}
:root[data-ui="liquid"][data-ventana="expandida"] {
    --liquid-menu-reserva: var(--liquid-menu-rail);
}
/* Bisagra (mejora progresiva: Chromium en plegables; iOS no la da). Solo existe con
   data-segmentos, que ViewportProvider escribe si (horizontal|vertical)-viewport-
   segments: 2. Inicio y fin de la bisagra en px desde el borde izquierdo (2h) o
   superior (2v) de la ventana; grosor, lo que no se ve entre los dos segmentos. El
   respaldo (la mitad) solo actuaría con la media query sin las variables env(). */
:root[data-ui="liquid"][data-segmentos="2h"] {
    --bisagra-inicio: env(viewport-segment-right 0 0, 50vw);
    --bisagra-fin: env(viewport-segment-left 1 0, 50vw);
    --bisagra-grosor: calc(env(viewport-segment-left 1 0, 50vw) - env(viewport-segment-right 0 0, 50vw));
}
:root[data-ui="liquid"][data-segmentos="2v"] {
    --bisagra-inicio: env(viewport-segment-bottom 0 0, 50dvh);
    --bisagra-fin: env(viewport-segment-top 0 1, 50dvh);
    --bisagra-grosor: calc(env(viewport-segment-top 0 1, 50dvh) - env(viewport-segment-bottom 0 0, 50dvh));
}
/* «Reducir transparencia» (Adri la tiene activada, SPEC § 4.4): tarjetas y flotantes
   opacos, sin desenfoque, y cabecera sólida. El velo de los diálogos sigue siendo velo. */
@media (prefers-reduced-transparency: reduce) {
    :root[data-ui="liquid"] {
        --bg-card: var(--liquid-panel);
        --glass-float: var(--liquid-panel);
        --glass-blur: 0px;
        --glass-header: var(--bg-primary);
    }
}
@media (forced-colors: active) {
    :root[data-ui="liquid"] {
        --bg-card: var(--liquid-panel);
        --glass-float: var(--liquid-panel);
        --glass-surface: none;
        --glass-control: none;
        --glass-highlight: none;
        --glass-blur: 0px;
        --glass-header: var(--bg-primary);
        --glass-shadow: none;
        --liquid-rail-material: none;
        --liquid-rail-control: none;
        --liquid-rail-shadow: none;
    }
}
@media print {
    :root[data-ui="liquid"] {
        --bg-card: var(--liquid-panel);
        --glass-float: var(--liquid-panel);
        --glass-surface: none;
        --glass-control: none;
        --glass-shadow: none;
        --glass-blur: 0px;
        --liquid-rail-material: none;
        --liquid-rail-control: none;
        --liquid-rail-shadow: none;
    }
}
