/* ============================================================
   SISTEMA DE DISEÑO ÚNICO — Seguros Universales
   FASE 2 del Rediseño UI/UX (2026-07-29)
   ============================================================

   POR QUÉ EXISTE ESTE ARCHIVO
   ---------------------------
   La auditoría de la Fase 1 (ver REDISENO-UI-FASE-1-AUDITORIA.md) midió
   sobre el código, no a ojo, y encontró:

     · 197 botones escritos de 72 formas distintas -- 32 de ellas para el
       MISMO botón primario azul.
     · La paleta institucional copiada a mano en los 22 archivos HTML
       (cambiar el azul de la marca obligaba a editar 22 archivos).
     · 13 anchos de contenido distintos, así que el contenido saltaba de
       tamaño al navegar de una pantalla a otra.
     · 5 tamaños distintos para el <h1> de pantalla.
     · 96 tamaños de texto clavados en píxeles (text-[11px], text-[10px]).
     · @keyframes fadeInUp declarada 16 veces, una por pantalla.
     · 350 líneas de CSS embebido repartidas en 22 bloques <style> que no
       se podían reutilizar ni sobrescribir globalmente.
     · 62 de 197 botones con altura efectiva de 24-32 px, cuando el mínimo
       para tocar con el dedo es 44 px.

   El problema de fondo no era estético: el sistema creció en más de 40
   entregas y cada pantalla se construyó autónoma. Un cambio visual había
   que hacerlo 22 veces, así que en la práctica no se hacía -- y de ahí la
   sensación de "módulos separados" en vez de un solo producto.

   ESTE ARCHIVO ES LA FUENTE ÚNICA
   -------------------------------
   Todo lo visual se decide aquí una vez: color, tipografía, espaciado,
   radios, sombras, y las clases de los componentes que se repiten en todas
   las pantallas (.su-btn, .su-tarjeta, .su-campo, .su-tabla, .su-modal...).

   POR QUÉ VARIABLES CSS Y NO VALORES FIJOS
   ----------------------------------------
   Es lo que hace posible la FASE 3 (Preferencias Visuales). El panel de
   preferencias no va a tener que tocar ni una clase ni un componente: solo
   cambia el valor de una variable en :root y todo el sistema se reacomoda
   -- tamaño de letra, densidad, redondeo, color de acento, tema claro u
   oscuro. Por eso hasta el padding de los botones sale de una variable.

   REGLA ABSOLUTA DE ESTE REDISEÑO
   -------------------------------
   Nada de esto cambia comportamiento. No se toca lógica, ni datos, ni
   endpoints, ni validaciones, ni permisos, ni un solo `id` de los que usa
   el JavaScript. Es exclusivamente presentación.

   CONVENCIÓN DE NOMBRES
   ---------------------
   Prefijo `su-` (Seguros Universales) en todo, para que nunca choque con
   una clase de Tailwind ni con las clases que ya traían las pantallas.
   Los nombres están en español, igual que el resto del proyecto.
   ============================================================ */

/* ------------------------------------------------------------
   1. TOKENS — la única declaración de la paleta y las medidas
   ------------------------------------------------------------
   Antes esto vivía repetido en 22 <script>tailwind.config</script>.
   Ahora está aquí, una vez. Los cuatro azules son los institucionales de
   Seguros Universales y NO cambian: lo que cambia es que ahora se declaran
   en un solo lugar.
   ------------------------------------------------------------ */
:root {
  /* --- Marca --- */
  --su-azul-oscuro: #003F8A;
  --su-azul-medio:  #0057B8;
  --su-azul-claro:  #1A78D4;
  --su-azul-suave:  #E8F1FB;

  /* Color de acento: por defecto es el azul institucional. La Fase 3
     permitirá cambiarlo sin tocar ningún componente, porque todos los
     componentes leen de aquí en vez de traer el azul escrito. */
  --su-acento:          var(--su-azul-medio);
  --su-acento-fuerte:   var(--su-azul-oscuro);
  --su-acento-claro:    var(--su-azul-claro);
  --su-acento-suave:    var(--su-azul-suave);
  --su-acento-contraste: #ffffff;

  /* --- Estados semánticos ---
     La auditoría encontró TRES verdes distintos para decir "esto está
     bien" (emerald, teal, lime), dos rojos y dos ámbar. Se elige una sola
     familia por estado, y se elige la MAYORITARIA en el código actual
     (emerald 7 usos, red 17, amber 19) para que el cambio visual sea el
     mínimo posible. */
  --su-exito:        #059669;
  --su-exito-suave:  #ecfdf5;
  --su-exito-borde:  #a7f3d0;
  --su-exito-texto:  #047857;

  --su-aviso:        #d97706;
  --su-aviso-suave:  #fffbeb;
  --su-aviso-borde:  #fde68a;
  --su-aviso-texto:  #b45309;

  --su-peligro:       #dc2626;
  --su-peligro-suave: #fef2f2;
  --su-peligro-borde: #fecaca;
  --su-peligro-texto: #b91c1c;

  --su-info:        #0284c7;
  --su-info-suave:  #f0f9ff;
  --su-info-borde:  #bae6fd;
  --su-info-texto:  #0369a1;

  /* --- Neutros (superficie y texto) ---
     Nombrados por FUNCIÓN, no por color, para que el tema oscuro solo
     tenga que redefinirlos sin tocar ningún componente. */
  --su-fondo:            #f6f8fb;
  --su-fondo-elevado:    #ffffff;
  --su-fondo-hundido:    #eff2f7;
  --su-fondo-hover:      #f4f6fa;
  --su-fondo-activo:     #e9edf4;
  --su-borde:            #e3e8ef;
  --su-borde-fuerte:     #cdd5e0;
  --su-texto:            #3c4657;
  --su-texto-fuerte:     #0e1420;
  --su-texto-suave:      #667287;
  --su-texto-tenue:      #98a2b3;
  --su-texto-titulo:     var(--su-azul-oscuro);
  --su-velo:             rgba(11, 17, 28, 0.5);

  /* v66 — Línea de pelo: el separador que NO debe leerse como borde.
     Se usa entre filas de tabla y entre secciones dentro de una misma
     tarjeta. Antes ese papel lo hacía `--su-borde`, y el resultado era
     que un separador interno pesaba igual que el contorno de la tarjeta:
     la vista no distinguía "esto es un contenedor" de "esto es una
     división dentro del contenedor". */
  --su-borde-sutil:      #edf0f5;

  /* v66 — Superficie de cabecera de tabla y barras de herramientas. Un
     tono propio evita el truco de "gris con opacidad" que en tema oscuro
     se veía sucio. */
  --su-fondo-cabecera:   #f8fafc;

  /* --- Tipografía ---
     `--su-escala` es el multiplicador que mueve la Fase 3 con el control
     de "tamaño de letra". Los 7 pasos se calculan a partir de él, así que
     un solo número reescala TODO el sistema de forma proporcional. Esto
     es exactamente lo que los 96 `text-[11px]` no podían hacer. */
  --su-familia: 'Inter', system-ui, -apple-system, 'Segoe UI', sans-serif;
  /* v123 — ancho fijo, para lo que se lee dígito a dígito: los valores
     hexadecimales del editor del reporte. Es un token y no una cadena
     suelta por la misma razón que el resto: para que haya UN sitio donde
     cambiarla. */
  --su-familia-mono: ui-monospace, 'SFMono-Regular', 'Consolas', monospace;
  --su-escala: 1;

  --su-texto-2xs: calc(0.6875rem * var(--su-escala)); /* 11px — reemplaza los 93 text-[11px] */
  --su-texto-xs:  calc(0.75rem   * var(--su-escala)); /* 12px */
  --su-texto-sm:  calc(0.875rem  * var(--su-escala)); /* 14px */
  --su-texto-md:  calc(1rem      * var(--su-escala)); /* 16px */
  --su-texto-lg:  calc(1.125rem  * var(--su-escala)); /* 18px */
  --su-texto-xl:  calc(1.375rem  * var(--su-escala)); /* 22px */
  --su-texto-2xl: calc(1.75rem   * var(--su-escala)); /* 28px */

  --su-alto-linea: 1.55;
  --su-alto-linea-titulo: 1.2;
  --su-espaciado-letra: 0;
  /* La Fase 3 pondrá esto en 800 con "negrita automática en títulos". */
  --su-peso-titulo: 700;

  /* v66 — Interletraje NEGATIVO en los títulos.
     A tamaño grande y peso alto, el espacio entre letras que sirve para
     leer un párrafo sobra: el título se ve disperso. Cerrarlo un poco es
     lo que separa un encabezado de producto de un encabezado de
     documento, y es la razón por la que un mismo texto se ve "más
     hecho" en Linear o Stripe que en un panel administrativo.
     Se declara aparte de `--su-espaciado-letra` (que es la preferencia
     del usuario para el texto corrido) porque son dos decisiones
     distintas: quien pide más aire para leer no está pidiendo que el
     título se desarme. */
  --su-titulo-interletra: -0.018em;
  --su-titulo-interletra-lg: -0.028em;

  /* v66 — Números de ancho fijo para tablas, KPIs y horas.
     Con la Inter por omisión, el "1" es más angosto que el "8": una
     columna de cantidades queda con los dígitos bailando de fila en
     fila y no se puede comparar de un vistazo. `tabular-nums` alinea las
     cifras verticalmente sin cambiar la tipografía. */
  --su-num-tabular: tabular-nums;

  /* --- Espaciado ---
     `--su-densidad` es el multiplicador de la preferencia "densidad
     visual" (compacta 0.75 / normal 1 / amplia 1.3). Todos los paddings
     de los componentes lo usan. */
  --su-densidad: 1;
  --su-esp-1: calc(0.25rem * var(--su-densidad, 1));
  --su-esp-2: calc(0.5rem  * var(--su-densidad, 1));
  --su-esp-3: calc(0.75rem * var(--su-densidad, 1));
  --su-esp-4: calc(1rem    * var(--su-densidad, 1));
  --su-esp-5: calc(1.25rem * var(--su-densidad, 1));
  --su-esp-6: calc(1.5rem  * var(--su-densidad, 1));
  --su-esp-8: calc(2rem    * var(--su-densidad, 1));

  /* --- Radios ---
     Antes había 4 radios mezclados en las tarjetas (rounded-2xl 92,
     rounded-xl 21, rounded-lg 11, rounded-full 17). Ahora hay dos
     decisiones: contenedor y control. */
  /* v66 — Se bajan los radios.
     16 px en una tarjeta y "pastilla" en los botones leen como interfaz
     de consumo. Las herramientas de trabajo densas en datos (Linear,
     Stripe, Vercel, Atlassian) usan 8-12 px: el borde acompaña al
     contenido en vez de competir con él, y a igualdad de tamaño caben
     más datos porque las esquinas no comen área útil.
     La pastilla NO se elimina: sigue disponible en Preferencias →
     Botones → Bordes redondeados, para quien la prefiera. */
  --su-radio-tarjeta: 12px;
  --su-radio-control: 8px;
  --su-radio-chip: 6px;
  /* Para piezas de rejilla de ~18 px (celda del mapa de calor): con el radio
     de chip la esquina se come la celda y el bloque deja de leerse como
     rejilla. No es un valor suelto: es el escalón que faltaba abajo. */
  --su-radio-micro: 3px;
  --su-radio-pastilla: 999px;

  /* --- Sombras ---
     De 5 niveles mezclados a 3 con significado: reposo, elevado, flotante.

     v66 — Se rebajan de forma deliberada. Una sombra marcada en cada
     tarjeta genera "ruido de profundidad": si todo flota, nada destaca,
     y en una pantalla con 12 paneles el efecto es de desorden. El
     criterio nuevo es el de las herramientas profesionales: el borde de
     1 px define el contenedor y la sombra solo insinúa la separación.
     La elevación fuerte queda reservada para lo que de verdad está por
     encima del plano: modales, menús y avisos flotantes. */
  --su-sombra-1: 0 1px 2px rgba(16, 24, 40, 0.04);
  --su-sombra-2: 0 2px 4px -1px rgba(16, 24, 40, 0.05), 0 4px 12px -2px rgba(16, 24, 40, 0.06);
  --su-sombra-3: 0 8px 16px -4px rgba(16, 24, 40, 0.10), 0 20px 40px -8px rgba(16, 24, 40, 0.12);
  --su-anillo-foco: 0 0 0 3px color-mix(in srgb, var(--su-acento) 28%, transparent);

  /* --- Controles ---
     `--su-btn-alto-min: 44px` no es un número estético: es el objetivo
     táctil mínimo recomendado. La auditoría encontró 62 botones de 24-32
     px de alto, incómodos de tocar en un teléfono. Ahora NINGÚN botón
     puede quedar por debajo, porque el mínimo vive en el componente. */
  --su-btn-alto-min: 44px;
  --su-btn-alto-min-sm: 36px;
  --su-btn-pad-x: var(--su-esp-4);
  --su-btn-pad-y: var(--su-esp-2);
  /* v66 — El botón deja de ser pastilla y pasa al radio de control, el
     mismo que los campos. Así un botón y un campo puestos lado a lado
     (la barra de filtros de 12 pantallas) tienen la misma silueta.
     La fuente Inter está cargada en pesos fijos (400/500/600/700/800):
     el peso del botón se deja en 600, que existe de verdad en el
     archivo. Un valor intermedio como 550 el navegador lo redondearía
     de forma distinta según la plataforma. */
  --su-btn-radio: var(--su-radio-control);
  --su-btn-peso: 600;
  --su-btn-separacion: var(--su-esp-2);
  --su-btn-texto: var(--su-texto-sm);

  --su-campo-alto-min: 42px;

  /* --- Anchos de contenido ---
     De 13 anchos distintos a 3 con criterio. */
  --su-ancho-angosto: 640px;   /* formularios, login, pantallas de una sola tarea */
  --su-ancho-normal:  1180px;  /* la mayoría */
  --su-ancho-ancho:   1600px;  /* tablas anchas, calendarios, mapas */

  /* --- Movimiento ---
     Antes: duration-300 (85 usos) y duration-200 (62) al azar.
     Ahora: rápida para respuesta al toque, normal para todo lo demás. */
  --su-trans-rapida: 140ms;
  --su-trans-normal: 240ms;
  --su-curva: cubic-bezier(0.4, 0, 0.2, 1);

  /* --- Capas ---
     Se declaran juntas para que nunca vuelva a haber dos elementos
     peleando por estar encima. */
  --su-z-lateral: 45;
  --su-z-cabecera: 40;
  --su-z-velo: 60;
  --su-z-modal: 70;
  --su-z-aviso: 90;

  /* Alto de la cabecera fija: lo usan el `padding-top` de la página y la
     posición del panel lateral, para que nunca se descuadren entre sí
     (la auditoría encontró pt-32 en 11 pantallas y pt-28 en 2). */
  --su-cabecera-alto: 60px;
  --su-lateral-ancho: 264px;
  --su-lateral-ancho-compacto: 68px;
}

/* ------------------------------------------------------------
   1b. TEMA OSCURO
   ------------------------------------------------------------
   Se define ya en la Fase 2 aunque el interruptor lo ponga la Fase 3:
   solo redefine los neutros. Ningún componente sabe que existe un tema
   oscuro -- todos leen de los tokens con nombre de función. Eso es lo que
   hace que el tema oscuro no pueda "romper" una pantalla.
   ------------------------------------------------------------ */
[data-tema='oscuro'] {
  /* v66 — El tema oscuro anterior era azul marino (#0f172a). Se ve
     "teñido": con el azul institucional encima, el acento y el fondo
     comparten familia de color y la acción primaria deja de saltar.
     Ahora la base es un neutro profundo con apenas una insinuación fría,
     que es lo que hace que el azul de marca se lea como acento y no
     como parte del fondo. */
  --su-fondo:          #0d1117;
  --su-fondo-elevado:  #161b23;
  --su-fondo-hundido:  #11161d;
  --su-fondo-hover:    #1d232d;
  --su-fondo-activo:   #232a35;
  --su-fondo-cabecera: #12171f;
  --su-borde-sutil:    #1c222b;
  --su-borde:          #262d38;
  --su-borde-fuerte:   #3a434f;
  --su-texto:          #d5dbe4;
  --su-texto-fuerte:   #f2f5f9;
  --su-texto-suave:    #98a3b3;
  --su-texto-tenue:    #6e7887;
  --su-texto-titulo:   #8ab4f8;
  --su-acento:        #4c8dff;
  --su-acento-fuerte: #6fa4ff;
  --su-acento-claro:  #9cc0ff;
  --su-acento-suave:  #16283f;
  --su-velo:          rgba(0, 0, 0, 0.72);
  --su-exito-suave:   #06281f;
  --su-exito-borde:   #12543f;
  --su-exito-texto:   #6ee7b7;
  --su-aviso-suave:   #2b1d05;
  --su-aviso-borde:   #613f0b;
  --su-aviso-texto:   #fcd34d;
  --su-peligro-suave: #2d0b0b;
  --su-peligro-borde: #6b1616;
  --su-peligro-texto: #fca5a5;
  --su-info-suave:    #05233a;
  --su-info-borde:    #0b4a6f;
  --su-info-texto:    #7dd3fc;
  /* En oscuro la sombra casi no se ve: lo que separa un plano de otro es
     el cambio de superficie y el borde. Se mantienen mínimas para que el
     modal siga teniendo peso, sin ensuciar el fondo. */
  --su-sombra-1: 0 1px 2px rgba(0, 0, 0, 0.4);
  --su-sombra-2: 0 2px 6px -1px rgba(0, 0, 0, 0.5);
  --su-sombra-3: 0 12px 32px -8px rgba(0, 0, 0, 0.7);
}

/* ------------------------------------------------------------
   2. BASE
   ------------------------------------------------------------ */
html {
  /* Necesario para que la cabecera fija no tape el destino al saltar a un
     ancla, y para que el scroll a una sección se vea completo. */
  scroll-padding-top: calc(var(--su-cabecera-alto) + var(--su-esp-4));
}

body {
  font-family: var(--su-familia);
  font-size: var(--su-texto-sm);
  line-height: var(--su-alto-linea);
  letter-spacing: var(--su-espaciado-letra);
  color: var(--su-texto);
  background: var(--su-fondo);
  /* v66 — Suavizado del texto.
     Sin esto, en macOS la Inter se dibuja más gruesa de lo diseñado y un
     panel lleno de texto pequeño se ve emborronado. Es el ajuste que
     usan las interfaces de referencia y cuesta dos líneas. */
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  /* `cv11` da la "a" y la "g" de una sola planta, que a 12-14 px se
     distinguen mejor; `ss01` cierra ligeramente las formas. Si la fuente
     elegida en Preferencias no tiene estos rasgos, el navegador los
     ignora sin error. */
  font-feature-settings: 'cv11' 1, 'ss01' 1;
  /* La auditoría encontró que una gráfica sin alto acotado estiraba la
     página a lo ancho. Esto es el cinturón: ninguna pantalla puede
     desbordar horizontalmente. */
  overflow-x: hidden;
  transition: background var(--su-trans-normal) var(--su-curva),
              color var(--su-trans-normal) var(--su-curva);
}

/* Respeta a quien pidió menos animación en su sistema operativo. No es un
   extra: para algunas personas el movimiento causa malestar real. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* Un solo anillo de foco visible en TODO el sistema. La auditoría
   encontró que el `focus:ring` existía en unos campos y no en otros: la
   navegación por teclado era inconsistente de pantalla a pantalla. */
:where(a, button, input, select, textarea, [tabindex]):focus-visible {
  outline: 2px solid var(--su-acento);
  outline-offset: 2px;
  border-radius: var(--su-radio-control);
}

/* ------------------------------------------------------------
   3. ANIMACIONES — declaradas UNA vez
   ------------------------------------------------------------
   Antes: fadeInUp copiada en 16 pantallas, popIn en 1, girar en 2.
   ------------------------------------------------------------ */
@keyframes su-entrar {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: translateY(0); }
}
@keyframes su-aparecer {
  from { opacity: 0; transform: scale(0.97); }
  to   { opacity: 1; transform: scale(1); }
}
@keyframes su-girar { to { transform: rotate(360deg); } }
@keyframes su-entrar-derecha {
  from { opacity: 0; transform: translateX(24px); }
  to   { opacity: 1; transform: translateX(0); }
}

/* ============================================================
   v158 — POR QUÉ ESTAS ANIMACIONES YA NO USAN `both`
   ============================================================

   `both` = `backwards` + `forwards`. El `forwards` deja el estado del
   fotograma final APLICADO PARA SIEMPRE, y el fotograma final de las
   tres animaciones de entrada trae un `transform` — `translateY(0)`,
   `scale(1)`, `translateX(0)`. Son transformaciones identidad: no mueven
   ni un píxel, y por eso nadie las vio nunca. Pero un elemento con
   `transform` distinto de `none`:

     · crea un CONTEXTO DE APILADO propio, y
     · se convierte en el BLOQUE CONTENEDOR de todos sus descendientes
       `position: fixed`.

   Lo primero ya nos costó dos reportes de Carlos: el panel de "Filtros
   avanzados" tapado por sus hermanos (v138) y el modal de Opciones
   partido por la barra de filtros (v156). Los dos se arreglaron con el
   mapa de capas que hay más abajo — correcto, pero era tratar el
   síntoma.

   Lo segundo apareció el 18-08-2026 en el APK de Ejecutivos, que tiene
   esta misma regla sobre <body>: un visor de cámara con `position:
   fixed; inset: 0` no medía la pantalla, medía el alto completo del
   documento (2015 px en una pantalla de 915), y el modal de fotos se
   dibujaba 700 px por encima de lo visible — la "pantalla blanca" que
   reportaron los ejecutivos. Se midió en un navegador real.

   Aquí todavía no ha mordido porque el panel no tiene `position: fixed`
   dentro de un bloque animado. Se arregla igual: no vale la pena esperar
   a que muerda un fallo que ya conocemos.

   `backwards` conserva la animación de entrada entera (el estado inicial
   se aplica antes de arrancar) y suelta el final al acabar. Es correcto
   porque en las tres el fotograma `to` ES el estado natural del
   elemento: opacidad 1 y sin desplazamiento. Visualmente no cambia nada.

   `.su-aviso--saliendo` es la excepción y se queda con `both` a
   propósito: ésa corre AL REVÉS y su estado final es "fuera de la
   pantalla, invisible". Ahí el `forwards` es justo lo que hace falta —
   sin él, el aviso volvería de golpe a su sitio al terminar de irse.
   ============================================================ */
.su-anim-entrar   { animation: su-entrar var(--su-trans-normal) var(--su-curva) backwards; }
.su-anim-aparecer { animation: su-aparecer var(--su-trans-rapida) var(--su-curva) backwards; }
.su-girando       { animation: su-girar 0.9s linear infinite; }

/* Alias de compatibilidad: 16 pantallas ya usan `.anim-fade-in` en su
   marcado. En vez de editar cientos de elementos (y arriesgar romper
   alguno), la clase vieja apunta a la animación nueva. Mismo efecto, una
   sola definición. */
.anim-fade-in { animation: su-entrar var(--su-trans-normal) var(--su-curva) backwards; }

/* ------------------------------------------------------------
   4. ESTRUCTURA DE PANTALLA — el marco único
   ------------------------------------------------------------
   Reemplaza las 5 estructuras distintas de encabezado y los 13 anchos de
   contenido. Toda pantalla se ve igual porque toda pantalla usa esto.
   ------------------------------------------------------------ */

/* Contenedor del contenido. Tres anchos, no trece. */
.su-pagina {
  width: 100%;
  max-width: var(--su-ancho-normal);
  margin-inline: auto;
  /* EL FALLO QUE ESTO REPARA (reporte de Carlos, 4 de agosto: "no puedo ver
     la pantalla completa, el texto se mete detrás de algo, pasa en toda la
     web").

     `width: 100%` se mide contra el CONTENEDOR, pero el lateral es
     `position: fixed` y no ocupa espacio, así que el contenedor sigue
     midiendo el ancho completo de la ventana. El contenido tomaba los
     264 px del lateral como si fueran suyos y se salía por la derecha: en
     la Ficha de Ejecutivo, "PACIENTES C…" y "VIÁTICO…" quedaban cortados
     fuera de la pantalla.

     `min-width: 0` es lo que permite que un hijo flexible se encoja: sin
     él, una tabla ancha o una rejilla de tarjetas obliga al contenedor a
     crecer y el desbordamiento vuelve por otro lado. */
  min-width: 0;
  padding-inline: var(--su-esp-4);
  padding-top: calc(var(--su-cabecera-alto) + var(--su-esp-6));
  padding-bottom: var(--su-esp-8);
}
.su-pagina--angosta { max-width: var(--su-ancho-angosto); }
.su-pagina--ancha   { max-width: var(--su-ancho-ancho); }

@media (min-width: 768px) {
  .su-pagina { padding-inline: var(--su-esp-6); }
}

/* ------------------------------------------------------------
   RED CONTRA EL DESBORDAMIENTO HORIZONTAL — v82

   "No puedo ver la pantalla completa... eso pasa en toda la web, es muy
   incómodo."

   Un solo elemento que se pase de ancho (una tabla larga, un nombre sin
   espacios, una rejilla de tarjetas) empuja TODA la página y aparece la
   barra de scroll horizontal. A partir de ahí, todo lo que esté a la
   derecha queda fuera de la vista.

   Estas tres reglas cortan las tres causas habituales de una vez, en vez
   de perseguirlas pantalla por pantalla.
   ------------------------------------------------------------ */

/* 1. La página nunca hace scroll horizontal. Si algo se pasa, se encoge o
      hace scroll DENTRO de su propia caja (regla 2), no arrastrando el
      documento entero. */
html { overflow-x: hidden; }

/* 2. Las tablas anchas hacen scroll en su contenedor, no en la página.
      `max-width: 100%` sin `min-width: 0` no basta: un elemento flex o
      grid tiene min-width auto y se niega a encogerse por debajo de su
      contenido. */
.su-tabla-envoltura,
.su-tarjeta,
.su-pagina > * {
  min-width: 0;
  max-width: 100%;
}

/* 3. Una palabra larguísima (un correo, una URL, un nombre pegado) no
      puede ensanchar su columna. */
.su-tarjeta,
.su-pagina td,
.su-pagina th {
  overflow-wrap: anywhere;
}

/* Cabecera fija única. La inyecta nav-comun.js, así que las 22 pantallas
   la comparten sin tener que reescribir 22 encabezados a mano. */
.su-cabecera {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: var(--su-z-cabecera);
  /* v118 -- ALTO MINIMO, NO ALTO FIJO.
     EL FALLO QUE ESTO REPARA. Carlos: "la linea de Volver / Inicio /
     Ejecutivos / Catalogo de Canales esta como metida abajo, esa cinta no
     deberia ser asi; debe quedar abajo de esa cinta, alli debe empezar el
     texto".
     Con `height` fijo, una cabecera cuyo contenido no cabe en 60 px no
     encoge el contenido: lo desborda hacia abajo, encima de la pagina. Y
     como `.su-pagina` reservaba exactamente esos 60 px, lo primero que hay
     debajo -las migas- quedaba tapado.
     Con `min-height` la cinta crece cuando hace falta, y nav-comun.js mide
     su alto REAL y lo publica en `--su-cabecera-alto` para que el relleno
     de la pagina lo siga. El texto empieza siempre debajo de la cinta, mida
     lo que mida. */
  min-height: var(--su-cabecera-alto);
  display: flex;
  align-items: center;
  gap: var(--su-esp-3);
  padding-inline: var(--su-esp-4);
  background: color-mix(in srgb, var(--su-fondo-elevado) 88%, transparent);
  backdrop-filter: saturate(160%) blur(10px);
  border-bottom: 1px solid var(--su-borde);
  transition: transform var(--su-trans-normal) var(--su-curva);
}
.su-cabecera__marca {
  display: flex;
  align-items: center;
  gap: var(--su-esp-2);
  font-weight: 700;
  font-size: var(--su-texto-sm);
  color: var(--su-texto-titulo);
  min-width: 0;
}
.su-cabecera__titulo {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* v166 — Cuando el rótulo de la cinta es el <h1> que la pantalla ya traía
   (ver `_normalizarCabecera` en nav-comun.js), se le respeta su aspecto: el
   arreglo era quitar el nombre duplicado, no reestilar veintiséis
   pantallas. Y hay un motivo concreto para devolverle el salto de línea:
   `.su-titulo` es `display:flex`, así que el recorte con puntos suspensivos
   no llega a dibujarse — el nombre se cortaría en seco en una pantalla
   estrecha, justo donde hoy ocupa dos renglones y se lee entero. */
.su-cabecera__titulo.su-titulo {
  white-space: normal;
  overflow: visible;
  min-width: 0;
}
.su-cabecera__acciones {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: var(--su-btn-separacion);
}
/* Se esconde al bajar en móvil (comportamiento que ya existía). */
.su-cabecera.mv-header-oculto,
header.mv-header-oculto { transform: translateY(-100%); }

/* Encabezado de contenido: título + subtítulo + acciones de la pantalla.
   Antes cada pantalla lo componía a su manera, con 5 tamaños de <h1>. */
.su-encabezado {
  display: flex;
  align-items: flex-start;
  gap: var(--su-esp-4);
  flex-wrap: wrap;
  margin-bottom: var(--su-esp-5);
}
.su-encabezado__texto { flex: 1 1 320px; min-width: 0; }
.su-encabezado__acciones {
  display: flex;
  align-items: center;
  gap: var(--su-btn-separacion);
  flex-wrap: wrap;
}
.su-titulo {
  font-size: var(--su-texto-xl);
  font-weight: var(--su-peso-titulo);
  line-height: var(--su-alto-linea-titulo);
  /* v66 — Interletraje cerrado. A 22 px y peso 700 el espaciado normal
     deja el título disperso; cerrarlo lo compacta en un bloque. */
  letter-spacing: var(--su-titulo-interletra-lg);
  color: var(--su-texto-titulo);
  display: flex;
  align-items: center;
  gap: var(--su-esp-2);
  margin: 0;
}
.su-subtitulo {
  font-size: var(--su-texto-sm);
  color: var(--su-texto-suave);
  margin: var(--su-esp-1) 0 0;
  max-width: 78ch; /* límite de legibilidad: más ancho cansa al leer */
}
.su-seccion { margin-bottom: var(--su-esp-6); }
.su-seccion__titulo {
  font-size: var(--su-texto-md);
  font-weight: var(--su-peso-titulo);
  letter-spacing: var(--su-titulo-interletra);
  color: var(--su-texto-titulo);
  margin: 0 0 var(--su-esp-3);
  display: flex;
  align-items: center;
  gap: var(--su-esp-2);
}

/* ------------------------------------------------------------
   5. BOTONES — de 72 variantes a 6
   ------------------------------------------------------------
   Seis variantes con significado, en vez de 72 combinaciones al azar:
     primario   una por pantalla: la acción principal
     secundario acciones normales
     fantasma   acciones terciarias, sin peso visual
     peligro    destructivas (borrar, reemplazar)
     exito      confirmaciones
     icono      cuadrado, solo ícono (siempre con aria-label)
   ------------------------------------------------------------ */
.su-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--su-esp-2);
  min-height: var(--su-btn-alto-min);   /* el mínimo táctil, no negociable */
  padding: var(--su-btn-pad-y) var(--su-btn-pad-x);
  border-radius: var(--su-btn-radio);
  border: 1px solid transparent;
  font-family: inherit;
  font-size: var(--su-btn-texto);
  font-weight: var(--su-btn-peso);
  line-height: 1.2;
  text-align: center;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
  transition: background var(--su-trans-rapida) var(--su-curva),
              border-color var(--su-trans-rapida) var(--su-curva),
              color var(--su-trans-rapida) var(--su-curva),
              box-shadow var(--su-trans-rapida) var(--su-curva),
              transform var(--su-trans-rapida) var(--su-curva);
}
/* Retroalimentación al tocar: el botón "se hunde". En una pantalla táctil
   es la única confirmación de que el toque se registró. */
.su-btn:active { transform: translateY(1px); }
.su-btn:disabled,
.su-btn[aria-disabled='true'] {
  opacity: 0.5;
  cursor: not-allowed;
  transform: none;
}

.su-btn--primario {
  background: var(--su-acento);
  color: var(--su-acento-contraste);
  box-shadow: var(--su-sombra-1);
}
.su-btn--primario:hover:not(:disabled) { background: var(--su-acento-fuerte); box-shadow: var(--su-sombra-2); }

.su-btn--secundario {
  background: var(--su-fondo-elevado);
  color: var(--su-acento);
  border-color: var(--su-borde);
}
.su-btn--secundario:hover:not(:disabled) { background: var(--su-acento-suave); border-color: var(--su-acento-claro); }

.su-btn--fantasma {
  background: transparent;
  color: var(--su-texto-suave);
}
.su-btn--fantasma:hover:not(:disabled) { background: var(--su-fondo-hundido); color: var(--su-acento); }

.su-btn--peligro {
  background: var(--su-fondo-elevado);
  color: var(--su-peligro-texto);
  border-color: var(--su-peligro-borde);
}
.su-btn--peligro:hover:not(:disabled) { background: var(--su-peligro-suave); border-color: var(--su-peligro); }
/* Para el botón que de verdad ejecuta el borrado: relleno sólido, para que
   no se pueda confundir con una acción normal. */
.su-btn--peligro-solido {
  background: var(--su-peligro);
  color: #fff;
  border-color: transparent;
}
.su-btn--peligro-solido:hover:not(:disabled) { background: var(--su-peligro-texto); }

.su-btn--exito {
  background: var(--su-exito);
  color: #fff;
}
.su-btn--exito:hover:not(:disabled) { background: var(--su-exito-texto); }

/* Tamaños. `--sm` sigue respetando un mínimo táctil razonable (36px): es
   para barras de herramientas densas de escritorio, no para bajar de ahí. */
.su-btn--sm {
  min-height: var(--su-btn-alto-min-sm);
  padding: var(--su-esp-1) var(--su-esp-3);
  font-size: var(--su-texto-xs);
}
.su-btn--lg {
  min-height: calc(var(--su-btn-alto-min) + 8px);
  padding: var(--su-esp-3) var(--su-esp-6);
  font-size: var(--su-texto-md);
}
.su-btn--icono {
  padding: 0;
  width: var(--su-btn-alto-min);
  min-width: var(--su-btn-alto-min);
  height: var(--su-btn-alto-min);
}
.su-btn--icono.su-btn--sm {
  width: var(--su-btn-alto-min-sm);
  min-width: var(--su-btn-alto-min-sm);
  height: var(--su-btn-alto-min-sm);
}
.su-btn--bloque { width: 100%; }

/* Grupo de botones: separación uniforme, en vez de gap ad-hoc por pantalla. */
.su-botonera {
  display: flex;
  align-items: center;
  gap: var(--su-btn-separacion);
  flex-wrap: wrap;
}
.su-botonera--fin { justify-content: flex-end; }

/* Preferencia de la Fase 3: "mostrar solo icono". Oculta el texto pero lo
   deja accesible para lectores de pantalla -- nunca se pierde el
   significado, solo se deja de dibujar. */
[data-btn-solo-icono='si'] .su-btn > .su-btn__texto {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: calc(-1px * var(--su-densidad, 1));
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}
/* Preferencia de la Fase 3: "ocultar iconos". */
[data-btn-iconos='no'] .su-btn > .su-icono { display: none; }

/* ------------------------------------------------------------
   6. TARJETAS Y PANELES
   ------------------------------------------------------------ */
.su-tarjeta {
  background: var(--su-fondo-elevado);
  border: 1px solid var(--su-borde);
  border-radius: var(--su-radio-tarjeta);
  padding: var(--su-esp-5);
  box-shadow: var(--su-sombra-1);
}
.su-tarjeta--plana { box-shadow: none; }
.su-tarjeta--ajustada { padding: var(--su-esp-3); }
.su-tarjeta--sin-relleno { padding: 0; overflow: hidden; }

/* Tarjeta que se puede pulsar. `text-align:left` importa: muchas son
   <button> y el navegador las centraría. */
.su-tarjeta--interactiva {
  cursor: pointer;
  text-align: left;
  width: 100%;
  transition: box-shadow var(--su-trans-normal) var(--su-curva),
              transform var(--su-trans-normal) var(--su-curva),
              border-color var(--su-trans-normal) var(--su-curva);
}
/* v66 — El levantamiento baja de 2 px a 1 px y la sombra de "flotante" a
   "elevado". Con 2 px y sombra máxima, una cuadrícula de 12 tarjetas
   producía un salto brusco al pasar el mouse por encima; el movimiento
   llamaba más la atención que la tarjeta señalada. El objetivo del hover
   es confirmar "esto responde", no producir un efecto. */
.su-tarjeta--interactiva:hover {
  box-shadow: var(--su-sombra-2);
  border-color: var(--su-borde-fuerte);
  transform: translateY(-1px);
}
.su-tarjeta--interactiva:active { transform: translateY(0); }

.su-tarjeta__cabecera {
  display: flex;
  align-items: center;
  gap: var(--su-esp-3);
  margin-bottom: var(--su-esp-3);
}
.su-tarjeta__titulo {
  font-size: var(--su-texto-md);
  font-weight: var(--su-peso-titulo);
  color: var(--su-texto-titulo);
  margin: 0;
}

/* Cuadrícula de tarjetas responsive sin tener que declarar breakpoints en
   cada pantalla: `auto-fit` decide cuántas columnas caben. */
.su-cuadricula {
  display: grid;
  gap: var(--su-esp-4);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr));
}
.su-cuadricula--2 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 420px), 1fr)); }
.su-cuadricula--indicadores { grid-template-columns: repeat(auto-fit, minmax(min(100%, 150px), 1fr)); }

/* Tarjeta de indicador (los contadores grandes de la Torre de Control y de
   las pantallas de resumen). Existía con 6 marcados distintos. */
.su-indicador {
  background: var(--su-fondo-elevado);
  border: 1px solid var(--su-borde);
  border-radius: var(--su-radio-tarjeta);
  padding: var(--su-esp-4);
  /* v66 — Se pasa de centrado a alineado a la izquierda.
     Con varios indicadores en fila, el centrado deja cada cifra en una
     posición horizontal distinta (depende de cuántos dígitos tenga), así
     que comparar cuatro KPI obliga a recorrer la vista en zigzag.
     Alineados a la izquierda todos arrancan en la misma columna y la
     comparación es de un vistazo. Es el patrón de Stripe y Linear. */
  text-align: left;
  box-shadow: var(--su-sombra-1);
}
.su-indicador__valor {
  font-size: var(--su-texto-2xl);
  font-weight: 800;
  line-height: 1.1;
  /* v66 — Cifra grande: interletraje cerrado y dígitos de ancho fijo.
     Los KPI de la Torre de Control se refrescan solos; con dígitos de
     ancho variable el número "salta" de posición cada vez que cambia de
     1 a 2, y el ojo lo lee como parpadeo. */
  letter-spacing: var(--su-titulo-interletra-lg);
  font-variant-numeric: var(--su-num-tabular);
  color: var(--su-texto-titulo);
}
.su-indicador__etiqueta {
  font-size: var(--su-texto-xs);
  color: var(--su-texto-suave);
  margin-top: var(--su-esp-1);
}
.su-indicador--exito  .su-indicador__valor { color: var(--su-exito); }
.su-indicador--aviso  .su-indicador__valor { color: var(--su-aviso); }
.su-indicador--peligro .su-indicador__valor { color: var(--su-peligro); }
.su-indicador--info   .su-indicador__valor { color: var(--su-acento); }

/* ------------------------------------------------------------
   7. FORMULARIOS — de 30 variantes a un componente
   ------------------------------------------------------------ */
.su-campo-grupo { display: flex; flex-direction: column; gap: var(--su-esp-1); min-width: 0; }

.su-etiqueta {
  font-size: var(--su-texto-2xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--su-texto-tenue);
}
.su-etiqueta--requerida::after { content: ' *'; color: var(--su-peligro); }

.su-campo {
  width: 100%;
  min-height: var(--su-campo-alto-min);
  padding: var(--su-esp-2) var(--su-esp-3);
  font-family: inherit;
  font-size: var(--su-texto-sm);
  color: var(--su-texto);
  background: var(--su-fondo-elevado);
  border: 1px solid var(--su-borde);
  border-radius: var(--su-radio-control);
  transition: border-color var(--su-trans-rapida) var(--su-curva),
              box-shadow var(--su-trans-rapida) var(--su-curva);
}
.su-campo::placeholder { color: var(--su-texto-tenue); }
.su-campo:hover:not(:disabled) { border-color: var(--su-borde-fuerte); }
.su-campo:focus {
  outline: none;
  border-color: var(--su-acento);
  box-shadow: var(--su-anillo-foco);
}
.su-campo:disabled { background: var(--su-fondo-hundido); color: var(--su-texto-tenue); cursor: not-allowed; }
.su-campo--error { border-color: var(--su-peligro); }
.su-campo--auto { width: auto; }
/* v66 — Campo pequeño, para filtros dentro de una cabecera de tabla.
   No baja de 32 px: por debajo de eso deja de ser tocable con el dedo, y
   estas tablas también se usan en tableta. */
.su-campo--sm {
  min-height: 32px;
  padding: 2px var(--su-esp-2);
  font-size: var(--su-texto-xs);
  font-weight: 400;
}
select.su-campo { cursor: pointer; }
textarea.su-campo { min-height: 88px; resize: vertical; }

.su-ayuda { font-size: var(--su-texto-xs); color: var(--su-texto-tenue); }
.su-error { font-size: var(--su-texto-xs); color: var(--su-peligro-texto); font-weight: 600; }

/* Barra de filtros: la misma composición en las 12 pantallas que filtran. */
.su-filtros {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--su-esp-3);
}
.su-filtros .su-campo-grupo { flex: 1 1 180px; }
.su-filtros__acciones { display: flex; gap: var(--su-btn-separacion); margin-left: auto; }

/* Casilla y radio con área de toque cómoda: el texto también acciona. */
.su-check {
  display: inline-flex;
  align-items: center;
  gap: var(--su-esp-2);
  min-height: var(--su-btn-alto-min-sm);
  font-size: var(--su-texto-sm);
  color: var(--su-texto);
  cursor: pointer;
}
.su-check input { width: 18px; height: 18px; accent-color: var(--su-acento); cursor: pointer; flex-shrink: 0; }

/* ------------------------------------------------------------
   8. TABLAS — un solo estilo de encabezado
   ------------------------------------------------------------
   Antes: 12 estilos de <th>, y solo 11 de 74 celdas usaban el "bueno".
   El envoltorio con scroll propio es importante: sin él, una tabla ancha
   arrastra la PÁGINA entera hacia los lados (bug real de la v51).
   ------------------------------------------------------------ */
.su-tabla-envoltorio {
  width: 100%;
  overflow-x: auto;
  border: 1px solid var(--su-borde);
  border-radius: var(--su-radio-tarjeta);
  background: var(--su-fondo-elevado);
  -webkit-overflow-scrolling: touch;
}
.su-tabla {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--su-texto-sm);
}
.su-tabla thead th {
  position: sticky;      /* el encabezado no se pierde al bajar */
  top: 0;
  z-index: 1;
  /* v66 — Superficie propia para el encabezado.
     Antes era del mismo blanco que las filas y, al quedarse fijo
     mientras el cuerpo se desplaza por debajo, el texto de las filas se
     leía "a través" de él. Un tono distinto lo separa del cuerpo sin
     necesidad de una línea gruesa. */
  background: var(--su-fondo-cabecera);
  text-align: left;
  font-size: var(--su-texto-2xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--su-texto-suave);
  padding: var(--su-esp-3) var(--su-esp-4);
  border-bottom: 1px solid var(--su-borde);
  white-space: nowrap;
}
.su-tabla tbody td {
  padding: var(--su-esp-3) var(--su-esp-4);
  /* v66 — Separador de fila con la línea de pelo, no con el borde de
     contenedor rebajado por opacidad. En tema oscuro el truco de la
     opacidad dejaba las líneas grises sobre fondo casi negro y la tabla
     se veía cuadriculada. */
  border-bottom: 1px solid var(--su-borde-sutil);
  color: var(--su-texto);
  vertical-align: middle;
  /* v66 — Dígitos de ancho fijo en TODO el cuerpo de la tabla. Es lo que
     permite comparar una columna de cantidades leyendo en vertical, sin
     que las cifras se desalineen según los dígitos que tengan. No afecta
     a las letras. */
  font-variant-numeric: var(--su-num-tabular);
}
.su-tabla tbody tr:last-child td { border-bottom: 0; }
.su-tabla tbody tr:hover { background: var(--su-fondo-hover); }
.su-tabla tfoot td {
  padding: var(--su-esp-3) var(--su-esp-4);
  font-weight: 700;
  color: var(--su-texto-fuerte);
  border-top: 1px solid var(--su-borde);
  background: var(--su-fondo-hundido);
}
.su-tabla--clicable tbody tr { cursor: pointer; }
.su-tabla--rayada tbody tr:nth-child(even) { background: color-mix(in srgb, var(--su-fondo-hundido) 55%, transparent); }
.su-tabla .su-num { text-align: right; font-variant-numeric: tabular-nums; }

/* Alto de fila: lo mueve la preferencia de la Fase 3. */
[data-filas='compactas'] .su-tabla tbody td { padding-block: var(--su-esp-1); }
[data-filas='amplias']  .su-tabla tbody td { padding-block: var(--su-esp-4); }

/* v66 — Densidad por TABLA, además de la preferencia global.
   `tabla-comun.js` ya traía un botón "Compacta / Normal" que aplicaba
   `py-1` a mano. Se conserva ese control (es funcionalidad existente),
   pero ahora se expresa con una clase del sistema en vez de con una
   utilidad de Tailwind, para que respete la densidad y la escala de
   letra elegidas en Preferencias en vez de pelear con ellas. */
.su-tabla--compacta tbody td,
.su-tabla--compacta thead th { padding-block: var(--su-esp-1); }

/* v66 — Fila de filtros por columna dentro del <thead>. Se separa del
   encabezado de títulos con una línea de pelo para que no se lea como
   una segunda fila de encabezados. */
.su-tabla thead tr + tr th {
  padding-top: 0;
  padding-bottom: var(--su-esp-2);
  border-bottom: 1px solid var(--su-borde);
  text-transform: none;
  letter-spacing: 0;
}
/* Cabecera ordenable: la señal de que se puede pulsar. */
.su-tabla thead th[data-clave] { cursor: pointer; user-select: none; }
.su-tabla thead th[data-clave]:hover { color: var(--su-texto-fuerte); background: var(--su-fondo-hundido); }

/* Estado vacío: un mensaje con forma, en vez de una tabla en blanco. */
.su-vacio {
  text-align: center;
  padding: var(--su-esp-8) var(--su-esp-4);
  color: var(--su-texto-suave);
}
.su-vacio__icono { color: var(--su-texto-tenue); margin-bottom: var(--su-esp-3); }
.su-vacio__titulo { font-weight: 600; color: var(--su-texto); margin-bottom: var(--su-esp-1); }

/* ------------------------------------------------------------
   9. PESTAÑAS
   ------------------------------------------------------------ */
/* v66 — El grupo de pestañas pasa de pastilla a rectángulo suave, en
   coherencia con botones, campos e insignias. La pestaña activa se
   distingue por superficie elevada + borde, no por sombra: la sombra
   dentro de un contenedor hundido producía un halo gris alrededor del
   elemento seleccionado. */
.su-pestanas {
  display: flex;
  gap: calc(2px * var(--su-densidad, 1));
  flex-wrap: wrap;
  padding: calc(3px * var(--su-densidad, 1));
  background: var(--su-fondo-hundido);
  border: 1px solid var(--su-borde-sutil);
  border-radius: var(--su-radio-control);
  margin-bottom: var(--su-esp-5);
}
.su-pestana {
  display: inline-flex;
  align-items: center;
  gap: var(--su-esp-2);
  min-height: var(--su-btn-alto-min-sm);
  padding: var(--su-esp-2) var(--su-esp-4);
  border: 1px solid transparent;
  background: transparent;
  border-radius: var(--su-radio-chip);
  font-family: inherit;
  font-size: var(--su-texto-sm);
  font-weight: 600;
  color: var(--su-texto-suave);
  cursor: pointer;
  white-space: nowrap;
  transition: background var(--su-trans-rapida) var(--su-curva),
              color var(--su-trans-rapida) var(--su-curva),
              border-color var(--su-trans-rapida) var(--su-curva);
}
.su-pestana:hover { color: var(--su-texto-fuerte); background: color-mix(in srgb, var(--su-fondo-elevado) 60%, transparent); }
.su-pestana[aria-selected='true'],
.su-pestana.su-pestana--activa {
  background: var(--su-fondo-elevado);
  border-color: var(--su-borde);
  color: var(--su-texto-fuerte);
  box-shadow: var(--su-sombra-1);
}
/* Pestañas AGRUPADAS con rótulo (FASE 4, hallazgo 3.5 de la auditoría).
   Ocho pestañas en una sola fila y con el mismo peso visual dejaban el
   borrado permanente a un clic de un reporte de consulta. Agrupadas, se
   entiende de un vistazo qué es consulta, qué es carga de archivos y qué es
   administración -- y la destructiva queda al final, separada. */
.su-pestanas-grupos {
  display: flex;
  align-items: flex-end;
  gap: var(--su-esp-5);
  flex-wrap: wrap;
}
.su-pestanas-grupo { display: flex; flex-direction: column; gap: var(--su-esp-1); min-width: 0; }
.su-pestanas-grupo .su-pestanas { margin-bottom: 0; }
/* El grupo destructivo se empuja al extremo y se separa con una línea, para
   que no se lea como "una pestaña más". */
.su-pestanas-grupo--peligro {
  margin-left: auto;
  padding-left: var(--su-esp-5);
  border-left: 1px solid var(--su-borde);
}
.su-pestanas-grupo--peligro .su-etiqueta { color: var(--su-peligro-texto); }
@media (max-width: 767px) {
  .su-pestanas-grupos { gap: var(--su-esp-3); }
  .su-pestanas-grupo--peligro {
    margin-left: 0;
    padding-left: 0;
    border-left: 0;
    border-top: 1px solid var(--su-borde);
    padding-top: var(--su-esp-3);
    width: 100%;
  }
}

/* Grupo de pestañas peligrosas (Administrar Datos): se separa a la
   derecha y se tiñe, para que no se confunda con una pestaña de consulta.
   Es el hallazgo 3.5 de la auditoría. */
.su-pestana--peligro { color: var(--su-peligro-texto); }
.su-pestana--peligro[aria-selected='true'],
.su-pestana--peligro.su-pestana--activa { background: var(--su-peligro-suave); color: var(--su-peligro-texto); }
.su-pestanas__separador { margin-left: auto; }

/* ------------------------------------------------------------
   10. CHIPS, ETIQUETAS Y ESTADOS
   ------------------------------------------------------------ */
.su-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--su-esp-1);
  padding: 2px var(--su-esp-2);
  /* v66 — La insignia deja de ser pastilla.
     Una fila de tabla con cuatro pastillas se lee como una fila de
     botones: el usuario intenta pulsarlas. Un rectángulo de esquina
     suave dice "esto es una etiqueta de estado", no "esto es una
     acción". Las que SÍ son pulsables (`--clicable`) conservan su
     señal propia: cursor y cambio al pasar el mouse. */
  border-radius: var(--su-radio-chip);
  border: 1px solid var(--su-borde);
  background: var(--su-fondo-hundido);
  font-size: var(--su-texto-2xs);
  font-weight: 600;
  /* Las insignias suelen llevar cifras (12 pendientes, 3/8). */
  font-variant-numeric: var(--su-num-tabular);
  color: var(--su-texto-suave);
  white-space: nowrap;
  line-height: 1.5;
}
.su-chip--acento  { background: var(--su-acento-suave); border-color: transparent; color: var(--su-acento-fuerte); }
.su-chip--exito   { background: var(--su-exito-suave); border-color: var(--su-exito-borde); color: var(--su-exito-texto); }
.su-chip--aviso   { background: var(--su-aviso-suave); border-color: var(--su-aviso-borde); color: var(--su-aviso-texto); }
.su-chip--peligro { background: var(--su-peligro-suave); border-color: var(--su-peligro-borde); color: var(--su-peligro-texto); }
.su-chip--info    { background: var(--su-info-suave); border-color: var(--su-info-borde); color: var(--su-info-texto); }
.su-chip--tenue   { background: transparent; color: var(--su-texto-tenue); font-style: italic; }
.su-chip--clicable {
  cursor: pointer;
  font-family: inherit;
  min-height: 28px;
  padding-inline: var(--su-esp-3);
  transition: background var(--su-trans-rapida) var(--su-curva),
              color var(--su-trans-rapida) var(--su-curva),
              border-color var(--su-trans-rapida) var(--su-curva);
}
/* v66 — El hover de una insignia pulsable ya no la pinta del color de
   acento entero. Con una fila de 6 presets de fecha, ese salto de blanco
   a azul sólido al pasar el mouse competía con el preset que de verdad
   está seleccionado, y por un instante parecía que había dos activos. */
.su-chip--clicable:hover { background: var(--su-fondo-hover); color: var(--su-texto-fuerte); border-color: var(--su-borde-fuerte); }
/* El seleccionado sí lleva el acento, y lo conserva al pasar por encima:
   es un estado, no una reacción al mouse. */
.su-chip--activo,
.su-chip--acento.su-chip--clicable:hover {
  background: var(--su-acento);
  color: var(--su-acento-contraste);
  border-color: transparent;
}

/* La "x" de una insignia de filtro activo. Va dentro de la insignia pero
   es un botón propio: quitar un filtro no puede depender de acertarle al
   texto. Se le da área de toque aunque el dibujo sea pequeño. */
.su-chip__quitar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  margin-left: calc(2px * var(--su-densidad, 1));
  margin-right: calc(-4px * var(--su-densidad, 1));
  padding: 0;
  border: 0;
  background: transparent;
  border-radius: 999px;
  color: inherit;
  font-family: inherit;
  font-size: 0.9em;
  line-height: 1;
  cursor: pointer;
  opacity: 0.65;
  transition: opacity var(--su-trans-rapida) var(--su-curva),
              background var(--su-trans-rapida) var(--su-curva);
}
.su-chip__quitar:hover { opacity: 1; background: rgba(0, 0, 0, 0.12); }

/* Punto de estado: se lee sin depender solo del color (siempre va con
   texto al lado), por accesibilidad para daltonismo. */
.su-punto {
  width: 8px; height: 8px;
  border-radius: 50%;
  display: inline-block;
  flex-shrink: 0;
}
.su-punto--exito { background: var(--su-exito); }
.su-punto--aviso { background: var(--su-aviso); }
.su-punto--peligro { background: var(--su-peligro); }
.su-punto--tenue { background: var(--su-texto-tenue); }

/* ------------------------------------------------------------
   11. AVISOS EN LÍNEA
   ------------------------------------------------------------ */
.su-nota {
  display: flex;
  gap: var(--su-esp-3);
  padding: var(--su-esp-3) var(--su-esp-4);
  border: 1px solid var(--su-borde);
  border-left-width: 4px;
  border-radius: var(--su-radio-control);
  background: var(--su-fondo-hundido);
  font-size: var(--su-texto-sm);
  color: var(--su-texto);
}
.su-nota__icono { flex-shrink: 0; margin-top: calc(1px * var(--su-densidad, 1)); }
.su-nota--info    { background: var(--su-info-suave); border-color: var(--su-info-borde); border-left-color: var(--su-info); color: var(--su-info-texto); }
.su-nota--exito   { background: var(--su-exito-suave); border-color: var(--su-exito-borde); border-left-color: var(--su-exito); color: var(--su-exito-texto); }
.su-nota--aviso   { background: var(--su-aviso-suave); border-color: var(--su-aviso-borde); border-left-color: var(--su-aviso); color: var(--su-aviso-texto); }
.su-nota--peligro { background: var(--su-peligro-suave); border-color: var(--su-peligro-borde); border-left-color: var(--su-peligro); color: var(--su-peligro-texto); }

/* ------------------------------------------------------------
   12. MODALES
   ------------------------------------------------------------
   Antes: 4 anchos distintos y la estructura interna a criterio de cada
   pantalla. `.modal-overlay` se mantiene como alias porque 5 pantallas y
   ComunModal de common.js dependen de esa clase y de `.open` -- cambiarlas
   sería tocar comportamiento, que es justo lo que este rediseño no hace.
   ------------------------------------------------------------ */
.su-modal,
.modal-overlay {
  position: fixed;
  inset: 0;
  z-index: var(--su-z-modal);
  display: none;
  align-items: center;
  justify-content: center;
  padding: var(--su-esp-4);
  background: var(--su-velo);
}
.su-modal.open,
.su-modal.su-abierto,
.modal-overlay.open { display: flex; animation: su-aparecer var(--su-trans-rapida) var(--su-curva); }

.su-modal__caja {
  width: 100%;
  max-width: 560px;
  max-height: 88vh;
  display: flex;
  flex-direction: column;
  background: var(--su-fondo-elevado);
  border-radius: var(--su-radio-tarjeta);
  /* v66 — Borde además de la sombra. En tema oscuro la sombra sobre
     fondo casi negro es invisible y el modal se fundía con la página:
     el borde es lo que le da contorno. */
  border: 1px solid var(--su-borde);
  box-shadow: var(--su-sombra-3);
  overflow: hidden;
}

/* Los modales que YA existían traen su tarjeta con clases de Tailwind
   (`max-w-md w-full p-6 …`). Aquí solo se unifican el color de superficie,
   el redondeo y la sombra -- NO el ancho ni el flujo interno, porque
   forzarlos podría descolocar el contenido que esas pantallas ya
   maquetaron. Es una armonización deliberadamente conservadora: se ven
   como parte del mismo sistema sin reacomodar nada por dentro. */
.modal-overlay > div {
  background: var(--su-fondo-elevado);
  border-radius: var(--su-radio-tarjeta);
  border: 1px solid var(--su-borde);
  box-shadow: var(--su-sombra-3);
}
.su-modal--ancho .su-modal__caja { max-width: 880px; }
.su-modal--angosto .su-modal__caja { max-width: 420px; }

.su-modal__cabecera {
  display: flex;
  align-items: flex-start;
  gap: var(--su-esp-3);
  padding: var(--su-esp-5);
  border-bottom: 1px solid var(--su-borde);
}
.su-modal__titulo {
  font-size: var(--su-texto-lg);
  font-weight: var(--su-peso-titulo);
  color: var(--su-texto-titulo);
  margin: 0;
  flex: 1;
  min-width: 0;
}
.su-modal__cuerpo {
  padding: var(--su-esp-5);
  overflow-y: auto;
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: var(--su-esp-4);
}
.su-modal__pie {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--su-btn-separacion);
  padding: var(--su-esp-4) var(--su-esp-5);
  border-top: 1px solid var(--su-borde);
  background: var(--su-fondo-hundido);
  flex-wrap: wrap;
}

/* Fila de dato dentro de un modal (etiqueta arriba, valor abajo). */
.su-dato { display: flex; gap: var(--su-esp-3); align-items: flex-start; }
.su-dato__icono { flex-shrink: 0; color: var(--su-texto-tenue); margin-top: calc(2px * var(--su-densidad, 1)); }
.su-dato__etiqueta {
  font-size: var(--su-texto-2xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-weight: 600;
  color: var(--su-texto-tenue);
}
.su-dato__valor {
  font-size: var(--su-texto-sm);
  font-weight: 600;
  color: var(--su-texto-titulo);
  overflow-wrap: break-word;
}

/* ------------------------------------------------------------
   13. AVISOS FLOTANTES (toast) Y CONFIRMACIÓN
   ------------------------------------------------------------
   Reemplazan los 84 alert()/confirm() nativos que encontró la auditoría.
   El diálogo del navegador es gris, no se puede estilizar, bloquea todo y
   en móvil se lee como un aviso del sistema, no de la aplicación.
   El comportamiento LÓGICO es idéntico (ver ui-sistema.js).
   ------------------------------------------------------------ */
#su-avisos {
  position: fixed;
  z-index: var(--su-z-aviso);
  bottom: var(--su-esp-5);
  right: var(--su-esp-5);
  display: flex;
  flex-direction: column;
  gap: var(--su-esp-2);
  max-width: min(400px, calc(100vw - 2 * var(--su-esp-4)));
  pointer-events: none;
}
.su-aviso {
  display: flex;
  align-items: flex-start;
  gap: var(--su-esp-3);
  padding: var(--su-esp-3) var(--su-esp-4);
  border-radius: var(--su-radio-control);
  border: 1px solid var(--su-borde);
  border-left-width: 4px;
  background: var(--su-fondo-elevado);
  box-shadow: var(--su-sombra-3);
  font-size: var(--su-texto-sm);
  color: var(--su-texto);
  pointer-events: auto;
  /* v158 — `backwards`, no `both`: ver la nota larga junto a
     `.su-anim-entrar`. La de SALIR sí necesita `forwards` y lo conserva. */
  animation: su-entrar-derecha var(--su-trans-normal) var(--su-curva) backwards;
}
.su-aviso--saliendo { animation: su-entrar-derecha var(--su-trans-rapida) var(--su-curva) reverse both; }
.su-aviso--exito   { border-left-color: var(--su-exito); }
.su-aviso--error   { border-left-color: var(--su-peligro); }
.su-aviso--aviso   { border-left-color: var(--su-aviso); }
.su-aviso--info    { border-left-color: var(--su-acento); }
.su-aviso__icono { flex-shrink: 0; margin-top: calc(1px * var(--su-densidad, 1)); }
.su-aviso--exito .su-aviso__icono { color: var(--su-exito); }
.su-aviso--error .su-aviso__icono { color: var(--su-peligro); }
.su-aviso--aviso .su-aviso__icono { color: var(--su-aviso); }
.su-aviso--info  .su-aviso__icono { color: var(--su-acento); }
.su-aviso__texto { flex: 1; min-width: 0; overflow-wrap: break-word; }
.su-aviso__cerrar {
  flex-shrink: 0;
  background: none; border: 0;
  color: var(--su-texto-tenue);
  cursor: pointer;
  line-height: 1;
  padding: calc(2px * var(--su-densidad, 1));
}

@media (max-width: 767px) {
  /* En el teléfono los avisos van abajo y a lo ancho, arriba de la barra
     inferior de navegación, para que no la tapen. */
  #su-avisos {
    left: var(--su-esp-3);
    right: var(--su-esp-3);
    bottom: calc(var(--su-esp-3) + 68px);
    max-width: none;
  }
}

/* ------------------------------------------------------------
   14. ICONOS
   ------------------------------------------------------------
   Reemplazan los 37 emojis distintos que encontró la auditoría. Un emoji
   se dibuja distinto en Windows, Android e iPhone, no hereda el color del
   texto y no se puede igualar en grosor de trazo. Un SVG con
   `currentColor` sí: se tiñe solo y escala con la tipografía (1em), así
   que también responde a la preferencia de tamaño de letra de la Fase 3.
   ------------------------------------------------------------ */
.su-icono {
  display: inline-flex;
  flex-shrink: 0;
  width: 1.15em;
  height: 1.15em;
  vertical-align: -0.15em;
}
.su-icono svg { width: 100%; height: 100%; display: block; }
.su-icono--sm { width: 1em; height: 1em; }
.su-icono--lg { width: 1.5em; height: 1.5em; }
.su-icono--xl { width: 2rem; height: 2rem; }

/* Cuadro de ícono de las tarjetas del menú principal. */
.su-icono-caja {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 52px; height: 52px;
  border-radius: var(--su-radio-control);
  background: var(--su-acento-suave);
  color: var(--su-acento);
  transition: background var(--su-trans-normal) var(--su-curva),
              color var(--su-trans-normal) var(--su-curva);
}
.su-icono-caja--sm { width: 40px; height: 40px; }
.su-icono-caja--lg { width: 68px; height: 68px; }
.su-tarjeta--interactiva:hover .su-icono-caja { background: var(--su-acento); color: var(--su-acento-contraste); }

/* ------------------------------------------------------------
   15. NAVEGACIÓN LATERAL PERSISTENTE
   ------------------------------------------------------------
   El cambio que más reduce clics. La auditoría midió 4 clics y 2 saltos de
   pantalla para la tarea más frecuente del sistema (subir el Excel de la
   semana). Con el lateral siempre visible en escritorio es 1 clic.

   Las tres pantallas que hoy son solo un menú intermedio (campo.html,
   operaciones-campo.html, programa.html) dejan de ser obligatorias -- pero
   conservan su URL y siguen funcionando, así que ningún favorito se rompe.
   ------------------------------------------------------------ */
.su-lateral {
  position: fixed;
  top: 0; left: 0; bottom: 0;
  width: var(--su-lateral-ancho);
  z-index: var(--su-z-lateral);
  display: flex;
  flex-direction: column;
  /* v66 — El lateral pasa de blanco elevado a la superficie de fondo.
     Con el lateral blanco y el contenido gris claro, la navegación se
     leía como el plano principal y las tarjetas de datos como el
     secundario: exactamente al revés de lo que importa. Recesando el
     lateral, el área de trabajo queda al frente. Es lo que hacen Linear,
     Notion y Vercel. */
  background: var(--su-fondo);
  border-right: 1px solid var(--su-borde);
  /* v54 (reporte 2026-07-30: "lo de automático, escritorio, móvil está
     sobremontado en toda la web, lado izquierdo inferior"). Sin esto,
     cuando el alto de la ventana no alcanza, el pie de la barra se salía
     del recuadro y quedaba FLOTANDO encima del contenido de la página.
     Nada de la barra puede escaparse de su propio marco. */
  overflow: hidden;
  transition: width var(--su-trans-normal) var(--su-curva),
              transform var(--su-trans-normal) var(--su-curva);
}
.su-lateral__marca {
  display: flex;
  align-items: center;
  gap: var(--su-esp-2);
  height: var(--su-cabecera-alto);
  padding-inline: var(--su-esp-4);
  border-bottom: 1px solid var(--su-borde);
  font-weight: 800;
  font-size: var(--su-texto-sm);
  color: var(--su-texto-titulo);
  flex-shrink: 0;
  white-space: nowrap;
  overflow: hidden;
}
/* min-height:0 es lo que de verdad arregla el desbordamiento: un hijo
   flex con `flex:1` NO se encoge por debajo del alto de su contenido a
   menos que se le permita explícitamente. Sin esta línea el <nav> se
   negaba a encogerse y empujaba los tres pies fuera de la barra — la
   causa real de que el selector de vista quedara sobremontado. */
.su-lateral__nav { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: var(--su-esp-2) 0; }
.su-lateral__grupo {
  font-size: var(--su-texto-2xs);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-weight: 700;
  color: var(--su-texto-tenue);
  padding: var(--su-esp-3) var(--su-esp-4) var(--su-esp-1);
}
/* v66 — El enlace del menú pasa de "franja de borde a la izquierda" a
   una pieza redondeada con margen propio.

   La franja de 3 px era el patrón de los paneles administrativos de hace
   una década: obliga a que el elemento activo toque el borde de la
   pantalla y a que el texto lleve un desplazamiento distinto al de los
   inactivos. Una pieza redondeada con aire a los lados marca el estado
   activo sin desalinear nada, y es la forma que usan Linear y Notion.

   La sangría (`margin-inline`) también resuelve un detalle real: con la
   franja, el ícono del elemento activo quedaba 3 px a la derecha del de
   los demás, y la columna de íconos se veía torcida. */
.su-lateral__enlace {
  display: flex;
  align-items: center;
  gap: var(--su-esp-3);
  min-height: var(--su-btn-alto-min);
  padding: var(--su-esp-2) var(--su-esp-3);
  margin-inline: var(--su-esp-2);
  border-radius: var(--su-radio-control);
  font-size: var(--su-texto-sm);
  font-weight: 500;
  color: var(--su-texto-suave);
  text-decoration: none;
  transition: background var(--su-trans-rapida) var(--su-curva),
              color var(--su-trans-rapida) var(--su-curva);
  white-space: nowrap;
  overflow: hidden;
}
/* Hover NEUTRO, no azul. Recorrer un menú de 22 entradas con el mouse
   encendía el color de marca una y otra vez; el acento pierde valor si
   se dispara por pasar por encima. El azul queda reservado para donde
   estás de verdad. */
.su-lateral__enlace:hover { background: var(--su-fondo-hover); color: var(--su-texto-fuerte); }
.su-lateral__enlace--activo {
  background: var(--su-acento-suave);
  color: var(--su-acento-fuerte);
  font-weight: 600;
}
.su-lateral__enlace--activo:hover { background: var(--su-acento-suave); color: var(--su-acento-fuerte); }
.su-lateral__etiqueta { overflow: hidden; text-overflow: ellipsis; }
.su-lateral__pie { border-top: 1px solid var(--su-borde); padding: var(--su-esp-3); flex-shrink: 0; }

/* v54 — El selector "Cómo ver esta web" se traslapaba (reporte
   2026-07-30, con captura).
 
   LA CAUSA, medida: la barra lateral mide 264 px y con su relleno deja
   unos 240 px útiles. El selector se dibujaba como `inline-flex` con tres
   botones de `px-3` (24 px de relleno cada uno) y `whitespace-nowrap`
   sobre textos como "🖥️ Escritorio" — entre los tres pasaban de 280 px.
   Al no poder encoger ni envolver, se salían del recuadro y quedaban
   montados sobre el contenido de la página.
 
   La solución no es esconderlo: es que quepa. Rejilla de 3 columnas
   iguales que ocupa exactamente el ancho disponible, y cada botón puede
   encogerse (min-width:0) y recortar su texto si hiciera falta. */
#nav-comun-modo-movil { min-width: 0; }
#nav-comun-modo-movil > div {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  width: 100%;
  gap: calc(2px * var(--su-densidad, 1));
}
#nav-comun-modo-movil .mv-toggle-btn {
  min-width: 0;
  padding-inline: calc(4px * var(--su-densidad, 1));
  font-size: 10.5px;
  text-align: center;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* El pie de "última sincronización" también se desbordaba: es un texto
   largo (una fecha ISO completa) sin puntos de corte. */
#nav-comun-sync-estado {
  min-width: 0;
  overflow-wrap: anywhere;
  line-height: 1.35;
}

/* Y si aun así el alto no alcanza, el bloque de pies se hace scrollable
   dentro de la barra en vez de desbordarse hacia la página. */
.su-lateral__pie { min-width: 0; }
@media (max-height: 700px) {
  #nav-comun-modo-movil,
  #nav-comun-sync-estado { display: none; }
}

/* En modo compacto (solo iconos) el selector no cabe de ninguna forma. */
[data-lateral='compacto'] #nav-comun-modo-movil { display: none; }
/* Resalte momentáneo al llegar desde el breadcrumb o la barra inferior:
   ubica la vista sin tener que leer todo el menú. */
.su-lateral__grupo--resaltado {
  background: color-mix(in srgb, var(--su-acento-suave) 70%, transparent);
  border-radius: var(--su-radio-control);
  transition: background var(--su-trans-normal) var(--su-curva);
}

/* Modo compacto: solo iconos. La etiqueta se sigue leyendo al pasar el
   mouse (title) y sigue existiendo para lectores de pantalla. */
[data-lateral='compacto'] .su-lateral { width: var(--su-lateral-ancho-compacto); }
[data-lateral='compacto'] .su-lateral .su-lateral__etiqueta,
[data-lateral='compacto'] .su-lateral .su-lateral__grupo,
[data-lateral='compacto'] .su-lateral .su-lateral__marca-texto { display: none; }
/* v66 — En compacto la sangría lateral se reduce (no se anula): el
   enlace sigue siendo una pieza redondeada centrada de 52 px sobre los
   68 px de la barra, en vez de un bloque a sangre que toca los dos
   bordes. */
[data-lateral='compacto'] .su-lateral__enlace { justify-content: center; padding-inline: 0; margin-inline: var(--su-esp-2); }
[data-lateral='compacto'] .su-lateral__marca { justify-content: center; padding-inline: 0; }

/* Con lateral visible, el contenido y la cabecera se corren. Se hace con
   una variable para que el modo compacto no tenga que repetir las reglas. */
@media (min-width: 1024px) {
  body.su-con-lateral { --su-desplazamiento: var(--su-lateral-ancho); }
  body.su-con-lateral[data-lateral='compacto'] { --su-desplazamiento: var(--su-lateral-ancho-compacto); }
  body.su-con-lateral .su-cabecera { left: var(--su-desplazamiento); }

  /* EL FALLO QUE ESTO REPARA (reporte de Carlos, 4 de agosto: "revisa ese
     traslape" — el menú lateral encima del contenido, comiéndose la mitad
     izquierda de Base de Datos).

     El lateral es `position: fixed`, así que NO ocupa espacio: el contenido
     tiene que apartarse a mano. Esa era la misión de esta regla... sobre
     `.su-contenido`.

     Y `.su-contenido` NO EXISTE EN NINGUNA PANTALLA. Se comprobó en las 31:
     cero coincidencias. La regla estaba escrita, era correcta, y no se
     aplicaba a nada — el contenido nunca se apartó y el lateral llevaba
     encima de él desde que se montó.

     No se puede arreglar añadiendo la clase a 31 archivos: mañana alguien
     crea la 32 y vuelve el traslape. Se apartan los contenedores de página
     que YA existen y que todas usan, más `.su-contenido` por si algún día
     alguien la escribe. */
  body.su-con-lateral .su-contenido,
  body.su-con-lateral > main,
  body.su-con-lateral > .su-pagina,
  body.su-con-lateral > header:not(.su-lateral):not(.su-cabecera),
  body.su-con-lateral > footer {
    margin-left: var(--su-desplazamiento);
    /* `width` además de `max-width`: max-width sólo pone un techo, y un
       hijo ancho (una rejilla de tarjetas, una tabla) puede empujar el
       contenedor por encima de él. Fijando el ancho, lo de dentro se
       encoge o hace scroll dentro de su caja, pero nada se sale de la
       pantalla. Es la diferencia entre "no debería pasar de aquí" y "mide
       exactamente esto". */
    width: calc(100% - var(--su-desplazamiento));
    max-width: calc(100% - var(--su-desplazamiento));
    min-width: 0;
  }

  /* Y el <main> que además es .su-pagina no debe sumar los dos márgenes:
     con `margin-inline: auto` propio y el margin-left del desplazamiento,
     el contenido quedaba descentrado hacia la derecha. */
  body.su-con-lateral > main.su-pagina {
    margin-right: 0;
  }

  /* Las cabeceras fijas propias de cada pantalla (vista.html tiene la suya)
     también tienen que arrancar donde termina el lateral. */
  body.su-con-lateral > header[class*="fixed"],
  body.su-con-lateral > .plan-ent-cabecera {
    left: var(--su-desplazamiento);
    width: auto;
  }

  /* ============================================================
     v140 — POR QUÉ "SIEMPRE HAY ALGO QUE SE METE DETRÁS DE LA CINTA"
     ============================================================

     Reporte de Carlos, 16 de agosto: el título del Centro de Operaciones
     salía cortado por la izquierda, medio tapado por el lateral. Y lo dijo
     con la palabra exacta: "siempre". No era la primera vez.

     LA CAUSA, medida en un navegador de verdad y no deducida leyendo:
     `nav-comun.js` le pone `.su-cabecera` a la cabecera de CADA pantalla y
     le reescribe las clases; `.su-cabecera` es `position: fixed` y se
     aparta del lateral con `left`. Ése es el único mecanismo, y funciona.

     Lo que rompe la compensación es que una pantalla declare su PROPIO
     posicionamiento para esa misma cabecera en su hoja. Como la hoja de
     pantalla carga DESPUÉS, gana: la cabecera pasa a ser `sticky`, y
     entonces el `left` que la apartaba deja de mover la caja —en un
     elemento pegajoso `left` no desplaza— y se va debajo del lateral.

     No falla ninguna regla. Las dos son correctas. Simplemente dejan de
     hablar entre ellas.

     POR ESO LA SOLUCIÓN NO ES OTRA REGLA DE COMPENSACIÓN. Se intentó —
     `margin-inline-start` para lo que no fuera fijo— y el navegador lo
     desmintió en el acto: desplazó DOS VECES las cabeceras que ya se
     apartaban con `left`, dejándolas a la mitad de la pantalla. Añadir un
     segundo mecanismo para tapar el hueco del primero es exactamente cómo
     se llega a tener tres.

     La regla, escrita para quien haga la pantalla 33:

       UNA PANTALLA NO REDEFINE `position` DE SU CABECERA.

     Todo lo demás —relleno, distribución, tipografía— es suyo. El
     posicionamiento es del sistema, en un solo sitio, y así el lateral no
     puede volver a comerse a nadie. Hay una prueba que lo vigila:
     `cabeceraNoSeMeteDetrasDelLateralV140.test.js`. */

  body.su-con-lateral[data-lateral='oculto'] { --su-desplazamiento: 0px; }
  body.su-con-lateral[data-lateral='oculto'] .su-lateral { transform: translateX(-100%); }
}
/* Por debajo de 1024px el lateral es un cajón que se abre sobre el
   contenido -- en una tablet o un teléfono no hay ancho para dedicarle
   264px permanentes. */
@media (max-width: 1023px) {
  .su-lateral { transform: translateX(-100%); box-shadow: var(--su-sombra-3); }
  .su-lateral--abierto { transform: translateX(0); }
  /* Debajo de 1024px el lateral es un cajón que se abre ENCIMA: nada se
     aparta, o quedaría una franja vacía a la izquierda con el cajón
     cerrado. Mismos selectores que arriba, para que ninguno se escape. */
  .su-contenido,
  body.su-con-lateral > main,
  body.su-con-lateral > .su-pagina,
  body.su-con-lateral > header:not(.su-lateral):not(.su-cabecera),
  body.su-con-lateral > footer {
    margin-left: 0 !important;
    max-width: 100% !important;
  }
  body.su-con-lateral > header[class*="fixed"],
  body.su-con-lateral > .plan-ent-cabecera { left: 0 !important; }
}

.su-velo-lateral {
  position: fixed;
  inset: 0;
  z-index: calc(var(--su-z-lateral) - 1);
  background: var(--su-velo);
  display: none;
}
.su-velo-lateral--visible { display: block; }
@media (min-width: 1024px) { .su-velo-lateral { display: none !important; } }

/* ------------------------------------------------------------
   16. MÓVIL
   ------------------------------------------------------------
   La auditoría encontró que movil.css (308 líneas, 41 !important) lo
   cargaba UNA sola pantalla, y que el resto resolvía el móvil con
   breakpoints sueltos. Aquí el móvil es parte del sistema, no un parche:
   los componentes ya se adaptan porque están definidos con variables y
   con `auto-fit`, no con anchos fijos.
   ------------------------------------------------------------ */
@media (max-width: 767px) {
  :root {
    /* En pantalla chica el contenido respira menos y los controles crecen:
       hay menos espacio, pero el dedo sigue siendo del mismo tamaño. */
    --su-esp-5: calc(1rem * var(--su-densidad, 1));
    --su-esp-6: calc(1.15rem * var(--su-densidad, 1));
    --su-btn-alto-min: 46px;
  }
  .su-pagina {
    padding-inline: var(--su-esp-3);
    /* Espacio para la barra inferior de navegación. */
    padding-bottom: calc(var(--su-esp-8) + 68px);
  }
  .su-encabezado { flex-direction: column; gap: var(--su-esp-3); }
  .su-encabezado__acciones { width: 100%; }
  .su-encabezado__acciones .su-btn { flex: 1 1 auto; }
  .su-titulo { font-size: var(--su-texto-lg); }
  .su-tarjeta { padding: var(--su-esp-4); }
  .su-modal { padding: 0; align-items: flex-end; }
  /* El modal sube desde abajo como hoja inferior: es el gesto que la gente
     espera en un teléfono. */
  .su-modal__caja,
  .modal-overlay > div {
    max-width: 100%;
    max-height: 92vh;
    border-radius: var(--su-radio-tarjeta) var(--su-radio-tarjeta) 0 0;
  }
  .su-filtros__acciones { margin-left: 0; width: 100%; }
  .su-filtros__acciones .su-btn { flex: 1; }
  .su-pestanas { border-radius: var(--su-radio-control); overflow-x: auto; flex-wrap: nowrap; }
  .su-pestanas__separador { margin-left: 0; }
}

/* Utilidades de visibilidad por tamaño, para no repetir media queries en
   cada pantalla. */
.su-solo-movil { display: none; }
@media (max-width: 767px) {
  .su-solo-escritorio { display: none !important; }
  .su-solo-movil { display: revert; }
}

/* ------------------------------------------------------------
   17. UTILIDADES MÍNIMAS
   ------------------------------------------------------------
   Solo lo que se repite en todas las pantallas y no vale la pena escribir
   con Tailwind cada vez. No se pretende reemplazar Tailwind: se usa para
   lo que el sistema de diseño debe gobernar.
   ------------------------------------------------------------ */
.su-pila     { display: flex; flex-direction: column; gap: var(--su-esp-4); }
.su-pila--sm { display: flex; flex-direction: column; gap: var(--su-esp-2); }
.su-fila     { display: flex; align-items: center; gap: var(--su-esp-3); flex-wrap: wrap; }
/* v79 — Fila con separación corta.
   Existe porque no existía: quien quería una fila apretada escribía
   `su-fila su-pila--sm`, y `.su-pila--sm` declara `flex-direction: column`.
   Como va después en la hoja, GANABA: la barra de filtros de las 12
   pantallas que filtran se volvía una columna estrecha pegada a la derecha,
   con la mitad izquierda vacía. No daba error de consola ni de compilación
   — simplemente se veía mal, que es la peor clase de fallo de CSS. */
.su-fila--sm { gap: var(--su-esp-2); }
.su-fila--fin { justify-content: flex-end; }
.su-empujar  { margin-left: auto; }
.su-texto-tenue { color: var(--su-texto-tenue); }
.su-texto-suave { color: var(--su-texto-suave); }
.su-texto-xs { font-size: var(--su-texto-xs); }
.su-texto-2xs { font-size: var(--su-texto-2xs); }
.su-mono { font-variant-numeric: tabular-nums; font-feature-settings: 'tnum'; }
.su-oculto { display: none !important; }
/* Visible solo para lectores de pantalla. */
.su-solo-lectores {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: calc(-1px * var(--su-densidad, 1));
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* ------------------------------------------------------------
   17a. EL PANEL DE PREFERENCIAS (FASE 3)
   ------------------------------------------------------------
   Componentes que solo usa el panel de Preferencias Visuales. Viven aquí,
   con el resto del sistema, para que el panel no traiga sus propios estilos
   sueltos -- que es justo el problema que la Fase 1 documentó en las 22
   pantallas.
   ------------------------------------------------------------ */

/* Una preferencia = etiqueta y ayuda a la izquierda, control a la derecha. */
.su-pref-fila {
  display: flex;
  align-items: center;
  gap: var(--su-esp-4);
  flex-wrap: wrap;
  padding: var(--su-esp-3) 0;
  border-bottom: 1px solid color-mix(in srgb, var(--su-borde) 55%, transparent);
}
.su-pref-fila:last-child { border-bottom: 0; }
.su-pref-fila--vertical { align-items: flex-start; flex-direction: column; }
.su-pref-fila__texto { flex: 1 1 220px; min-width: 0; }
.su-pref-fila__etiqueta {
  font-size: var(--su-texto-sm);
  font-weight: 600;
  color: var(--su-texto-fuerte);
}
.su-pref-opciones { margin: 0; flex-shrink: 0; }
.su-pref-seccion { display: block; }

/* Interruptor. Se marca como `role="switch"` en el JS y el color no es la
   única señal del estado: la bola se mueve, así que también se distingue
   sin percibir color. */
.su-interruptor {
  position: relative;
  flex-shrink: 0;
  width: 46px;
  height: 26px;
  padding: 0;
  border: 1px solid var(--su-borde-fuerte);
  border-radius: var(--su-radio-pastilla);
  background: var(--su-fondo-hundido);
  cursor: pointer;
  transition: background var(--su-trans-rapida) var(--su-curva),
              border-color var(--su-trans-rapida) var(--su-curva);
}
.su-interruptor__bola {
  position: absolute;
  top: 2px; left: 2px;
  width: 20px; height: 20px;
  border-radius: 50%;
  background: var(--su-fondo-elevado);
  box-shadow: var(--su-sombra-1);
  transition: transform var(--su-trans-rapida) var(--su-curva);
}
.su-interruptor--on { background: var(--su-acento); border-color: var(--su-acento); }
.su-interruptor--on .su-interruptor__bola { transform: translateX(20px); }

/* Muestras de color de acento. */
.su-acento {
  width: 30px; height: 30px;
  padding: 0;
  border-radius: 50%;
  border: 2px solid transparent;
  background: var(--muestra);
  cursor: pointer;
  box-shadow: inset 0 0 0 1px rgba(15, 23, 42, 0.12);
  transition: transform var(--su-trans-rapida) var(--su-curva),
              border-color var(--su-trans-rapida) var(--su-curva);
}
.su-acento:hover { transform: scale(1.12); }
.su-acento--activo {
  border-color: var(--su-texto-fuerte);
  /* Palomita en blanco encima del color, para que el elegido se reconozca
     sin depender solo del borde. */
  background-image: linear-gradient(transparent, transparent);
  box-shadow: inset 0 0 0 2px var(--su-fondo-elevado), inset 0 0 0 1px rgba(15, 23, 42, 0.12);
}

/* Fila de alerta clicable (Torre de Control). Antes era un div con clases de
   Tailwind escritas a mano en la plantilla de JavaScript. */
.su-alerta-fila {
  display: flex;
  align-items: center;
  gap: var(--su-esp-2);
  padding: var(--su-esp-3) 0;
  border-top: 1px solid color-mix(in srgb, var(--su-borde) 55%, transparent);
  font-size: var(--su-texto-sm);
  color: var(--su-texto);
  cursor: pointer;
  transition: color var(--su-trans-rapida) var(--su-curva);
}
.su-alerta-fila:hover { color: var(--su-acento); }

.su-quitar-favorito {
  background: none;
  border: 0;
  padding: 0 0 0 calc(2px * var(--su-densidad, 1));
  color: inherit;
  cursor: pointer;
  opacity: 0.7;
  display: inline-flex;
}
.su-quitar-favorito:hover { opacity: 1; }

/* La estrella de favorito, en el menú lateral. */
.su-favorito-btn {
  background: none;
  border: 0;
  padding: calc(2px * var(--su-densidad, 1));
  margin-left: auto;
  color: var(--su-texto-tenue);
  cursor: pointer;
  opacity: 0;
  flex-shrink: 0;
  transition: opacity var(--su-trans-rapida) var(--su-curva),
              color var(--su-trans-rapida) var(--su-curva);
}
.su-lateral__enlace:hover .su-favorito-btn,
.su-favorito-btn:focus-visible,
.su-favorito-btn--activo { opacity: 1; }
.su-favorito-btn--activo { color: var(--su-aviso); }
.su-favorito-btn:hover { color: var(--su-aviso); }
/* En modo compacto no hay espacio para la estrella. */
[data-lateral='compacto'] .su-favorito-btn { display: none; }

@media (max-width: 767px) {
  .su-pref-fila { flex-direction: column; align-items: flex-start; }
  .su-pref-opciones { width: 100%; }
  /* En un teléfono la estrella no aparece al pasar el mouse: siempre visible. */
  .su-favorito-btn { opacity: 1; }
}

/* ------------------------------------------------------------
   17b. LO QUE RESPONDE A LAS PREFERENCIAS (FASE 3)
   ------------------------------------------------------------
   Casi todas las preferencias ya funcionan solas, porque los componentes
   leen variables (`--su-escala`, `--su-densidad`, `--su-btn-radio`...).
   Aquí están las pocas que necesitan una regla propia porque cambian
   estructura, no un número.
   ------------------------------------------------------------ */

/* Barra superior fija (activada por omisión). Al desactivarla la cabecera
   se desplaza con la página: útil en pantallas chicas donde 60 px fijos son
   una parte apreciable del alto disponible. */
[data-barra-fija='no'] .su-cabecera { position: sticky; }
[data-barra-fija='no'] .su-pagina { padding-top: var(--su-esp-5); }
[data-barra-fija='no'] body.su-con-lateral .su-cabecera { left: auto; }

/* Tamaño de las tarjetas. Se toca el relleno, nunca el contenido: una
   tarjeta compacta muestra exactamente lo mismo, más apretado. */
[data-tarjetas='compactas'] .su-tarjeta { padding: var(--su-esp-3); }
[data-tarjetas='compactas'] .su-indicador { padding: var(--su-esp-2); }
[data-tarjetas='compactas'] .su-indicador__valor { font-size: var(--su-texto-xl); }
[data-tarjetas='amplias'] .su-tarjeta { padding: var(--su-esp-6); }
[data-tarjetas='amplias'] .su-indicador { padding: var(--su-esp-6); }

/* Paneles secundarios: los bloques de apoyo (leyendas, ayudas, notas de
   contexto) se pueden ocultar para dejar la pantalla con lo esencial.
   IMPORTANTE: solo aplica a lo marcado como secundario a propósito; nunca
   esconde datos ni controles. */
[data-paneles-secundarios='no'] [data-panel='secundario'] { display: none !important; }

/* Paneles colapsables: cuando se desactivan, todo queda desplegado (para
   quien prefiere verlo todo de una vez en vez de abrir y cerrar). */
[data-paneles-colapsables='no'] details[data-su-colapsable] > summary { display: none; }
[data-paneles-colapsables='no'] details[data-su-colapsable] { display: block; }

/* "Ajuste automático" apagado = tratar siempre la pantalla como escritorio.
   Se ofrece porque en una tablet grande el modo móvil puede desperdiciar
   espacio, y hay quien prefiere decidirlo a mano. */
[data-ajuste-automatico='no'] .su-solo-movil { display: none !important; }

/* ------------------------------------------------------------
   18. CAPA DE COMPATIBILIDAD PARA EL TEMA OSCURO
   ------------------------------------------------------------
   Las 22 pantallas usan cientos de utilidades de Tailwind con colores
   fijos (`text-slate-500`, `bg-white`, `border-slate-200`...). Convertirlas
   todas de golpe sería un cambio enorme con riesgo real de romper algo, y
   este rediseño tiene la regla de no tocar comportamiento.

   Los cuatro azules de la marca YA se adaptan solos, porque ui-tailwind.js
   los apunta a las variables de acento en vez de a un hex. Lo que falta son
   los NEUTROS, y para esos se hace aquí: en tema oscuro se reasignan las
   utilidades de gris más usadas a los tokens del sistema.

   Es deliberadamente una capa de compatibilidad, no la solución final:
   · Solo se activa con `[data-tema='oscuro']`, así que en tema claro no
     existe y no puede alterar nada de lo que hay hoy.
   · Usa `!important` porque tiene que ganarle a una utilidad de Tailwind
     con la misma especificidad. Son los únicos `!important` del archivo, y
     están acotados a este bloque.
   · La FASE 4 irá reduciendo este bloque a medida que las pantallas
     sustituyan estas utilidades por componentes del sistema.
   ------------------------------------------------------------ */
[data-tema='oscuro'] .bg-white,
[data-tema='oscuro'] .bg-white\/90,
[data-tema='oscuro'] .bg-white\/80 { background-color: var(--su-fondo-elevado) !important; }
[data-tema='oscuro'] .bg-slate-50,
[data-tema='oscuro'] .bg-slate-50\/80,
[data-tema='oscuro'] .bg-slate-100 { background-color: var(--su-fondo-hundido) !important; }

[data-tema='oscuro'] .text-slate-900,
[data-tema='oscuro'] .text-slate-800,
[data-tema='oscuro'] .text-slate-700 { color: var(--su-texto-fuerte) !important; }
[data-tema='oscuro'] .text-slate-600,
[data-tema='oscuro'] .text-slate-500 { color: var(--su-texto) !important; }
[data-tema='oscuro'] .text-slate-400 { color: var(--su-texto-suave) !important; }
[data-tema='oscuro'] .text-slate-300 { color: var(--su-texto-tenue) !important; }

[data-tema='oscuro'] .border-slate-100,
[data-tema='oscuro'] .border-slate-200,
[data-tema='oscuro'] .border-slate-300 { border-color: var(--su-borde) !important; }

/* Los fondos suaves de estado (verde/ámbar/rojo claritos) son ilegibles
   sobre oscuro: se cambian por los equivalentes del tema. */
[data-tema='oscuro'] .bg-emerald-50, [data-tema='oscuro'] .bg-emerald-100 { background-color: var(--su-exito-suave) !important; }
[data-tema='oscuro'] .bg-amber-50,   [data-tema='oscuro'] .bg-amber-100   { background-color: var(--su-aviso-suave) !important; }
[data-tema='oscuro'] .bg-red-50,     [data-tema='oscuro'] .bg-red-100,
[data-tema='oscuro'] .bg-rose-100                                          { background-color: var(--su-peligro-suave) !important; }
[data-tema='oscuro'] .bg-orange-100  { background-color: var(--su-aviso-suave) !important; }
[data-tema='oscuro'] .text-emerald-700, [data-tema='oscuro'] .text-emerald-600 { color: var(--su-exito-texto) !important; }
[data-tema='oscuro'] .text-amber-700,   [data-tema='oscuro'] .text-amber-600,
[data-tema='oscuro'] .text-orange-700,  [data-tema='oscuro'] .text-orange-800  { color: var(--su-aviso-texto) !important; }
[data-tema='oscuro'] .text-red-700,     [data-tema='oscuro'] .text-red-600,
[data-tema='oscuro'] .text-rose-700                                            { color: var(--su-peligro-texto) !important; }

/* ------------------------------------------------------------
   v66 — EL HUECO QUE FALTABA EN ESTE MAPEO
   ------------------------------------------------------------
   Al medir la cobertura salió el número incómodo: de las 1 361
   apariciones de clases de color en el marcado, este bloque cubría
   1 160 y dejaba fuera **201**.

   Y no eran casos raros. Los dos grupos que se veían mal:

   1. LOS BORDES DE ESTADO. `border-amber-200` (21 usos),
      `border-red-200`, `border-emerald-200`, `border-slate-50`... son
      bordes CLAROS. Sobre fondo oscuro no se atenúan: se convierten en
      la línea más brillante de la pantalla, y una tarjeta de aviso
      quedaba con un marco que gritaba más que su contenido.

   2. LOS COLORES DE CANAL. violeta, celeste, rosa, cian, teal, índigo,
      lima, fucsia — son los colores con los que se distingue cada canal
      y cada programa. Su fondo `-100` es casi blanco: en oscuro, un
      recuadro blanco con texto oscuro dentro de una pantalla negra.

   Se resuelven distinto a propósito:

   · Los de ESTADO van a los tokens del sistema (éxito/aviso/peligro),
     porque el sistema ya tiene un equivalente oscuro para cada uno.
   · Los de CANAL conservan SU tono, sólo que oscurecido con
     `color-mix`. Ahí el color es un DATO —dice de qué canal es la
     fila— y traducirlo a un token del sistema borraría la información.
     Que el violeta siga siendo violeta importa; que sea claro, no.
   ------------------------------------------------------------ */

/* --- 1. Bordes claros --- */
[data-tema='oscuro'] .border-slate-50,
[data-tema='oscuro'] .border-white,
[data-tema='oscuro'] .border-gray-100,
[data-tema='oscuro'] .border-gray-200   { border-color: var(--su-borde) !important; }
[data-tema='oscuro'] .border-emerald-100,
[data-tema='oscuro'] .border-emerald-200,
[data-tema='oscuro'] .border-green-200  { border-color: var(--su-exito-borde) !important; }
[data-tema='oscuro'] .border-amber-100,
[data-tema='oscuro'] .border-amber-200,
[data-tema='oscuro'] .border-amber-300,
[data-tema='oscuro'] .border-orange-200 { border-color: var(--su-aviso-borde) !important; }
[data-tema='oscuro'] .border-red-100,
[data-tema='oscuro'] .border-red-200,
[data-tema='oscuro'] .border-red-300,
[data-tema='oscuro'] .border-rose-200   { border-color: var(--su-peligro-borde) !important; }

/* --- 2. Colores de canal: conservan su tono, oscurecidos ---
   `color-mix` con negro mantiene el matiz y baja la luminosidad, que es
   exactamente lo que hace falta: sigue siendo violeta, deja de ser
   blanco. El texto sube de luminosidad por el mismo motivo inverso. */
[data-tema='oscuro'] .bg-violet-50,  [data-tema='oscuro'] .bg-violet-100  { background-color: color-mix(in srgb, #7c3aed 22%, #0d1117) !important; }
[data-tema='oscuro'] .bg-sky-50,     [data-tema='oscuro'] .bg-sky-100     { background-color: color-mix(in srgb, #0284c7 22%, #0d1117) !important; }
[data-tema='oscuro'] .bg-pink-50,    [data-tema='oscuro'] .bg-pink-100    { background-color: color-mix(in srgb, #db2777 22%, #0d1117) !important; }
[data-tema='oscuro'] .bg-cyan-50,    [data-tema='oscuro'] .bg-cyan-100    { background-color: color-mix(in srgb, #0891b2 22%, #0d1117) !important; }
[data-tema='oscuro'] .bg-teal-50,    [data-tema='oscuro'] .bg-teal-100    { background-color: color-mix(in srgb, #0d9488 22%, #0d1117) !important; }
[data-tema='oscuro'] .bg-indigo-50,  [data-tema='oscuro'] .bg-indigo-100  { background-color: color-mix(in srgb, #4f46e5 22%, #0d1117) !important; }
[data-tema='oscuro'] .bg-lime-50,    [data-tema='oscuro'] .bg-lime-100    { background-color: color-mix(in srgb, #65a30d 22%, #0d1117) !important; }
[data-tema='oscuro'] .bg-fuchsia-50, [data-tema='oscuro'] .bg-fuchsia-100 { background-color: color-mix(in srgb, #c026d3 22%, #0d1117) !important; }
[data-tema='oscuro'] .bg-rose-50                                          { background-color: color-mix(in srgb, #e11d48 22%, #0d1117) !important; }

[data-tema='oscuro'] .text-violet-600,  [data-tema='oscuro'] .text-violet-700,  [data-tema='oscuro'] .text-violet-800  { color: color-mix(in srgb, #7c3aed 55%, white) !important; }
[data-tema='oscuro'] .text-sky-600,     [data-tema='oscuro'] .text-sky-700,     [data-tema='oscuro'] .text-sky-800     { color: color-mix(in srgb, #0284c7 55%, white) !important; }
[data-tema='oscuro'] .text-pink-600,    [data-tema='oscuro'] .text-pink-700     { color: color-mix(in srgb, #db2777 55%, white) !important; }
[data-tema='oscuro'] .text-cyan-600,    [data-tema='oscuro'] .text-cyan-700     { color: color-mix(in srgb, #0891b2 55%, white) !important; }
[data-tema='oscuro'] .text-teal-600,    [data-tema='oscuro'] .text-teal-700     { color: color-mix(in srgb, #0d9488 55%, white) !important; }
[data-tema='oscuro'] .text-indigo-600,  [data-tema='oscuro'] .text-indigo-700   { color: color-mix(in srgb, #4f46e5 55%, white) !important; }
[data-tema='oscuro'] .text-lime-600,    [data-tema='oscuro'] .text-lime-700     { color: color-mix(in srgb, #65a30d 55%, white) !important; }
[data-tema='oscuro'] .text-fuchsia-600, [data-tema='oscuro'] .text-fuchsia-700  { color: color-mix(in srgb, #c026d3 55%, white) !important; }
[data-tema='oscuro'] .text-rose-600                                             { color: var(--su-peligro-texto) !important; }
[data-tema='oscuro'] .text-red-500,     [data-tema='oscuro'] .text-red-400      { color: var(--su-peligro-texto) !important; }
[data-tema='oscuro'] .text-amber-800,   [data-tema='oscuro'] .text-amber-900    { color: var(--su-aviso-texto) !important; }
[data-tema='oscuro'] .text-emerald-800, [data-tema='oscuro'] .text-green-700    { color: var(--su-exito-texto) !important; }
[data-tema='oscuro'] .text-gray-500,    [data-tema='oscuro'] .text-gray-600     { color: var(--su-texto) !important; }
[data-tema='oscuro'] .text-gray-400                                             { color: var(--su-texto-suave) !important; }

/* Los bordes de canal, por el mismo criterio que sus fondos: conservan el
   tono y bajan la luminosidad. Faltaban éstos y eran los últimos. */
[data-tema='oscuro'] .border-violet-200  { border-color: color-mix(in srgb, #7c3aed 40%, #0d1117) !important; }
[data-tema='oscuro'] .border-sky-200     { border-color: color-mix(in srgb, #0284c7 40%, #0d1117) !important; }
[data-tema='oscuro'] .border-pink-200    { border-color: color-mix(in srgb, #db2777 40%, #0d1117) !important; }
[data-tema='oscuro'] .border-cyan-200    { border-color: color-mix(in srgb, #0891b2 40%, #0d1117) !important; }
[data-tema='oscuro'] .border-teal-200    { border-color: color-mix(in srgb, #0d9488 40%, #0d1117) !important; }
[data-tema='oscuro'] .border-indigo-200  { border-color: color-mix(in srgb, #4f46e5 40%, #0d1117) !important; }
[data-tema='oscuro'] .border-lime-200    { border-color: color-mix(in srgb, #65a30d 40%, #0d1117) !important; }
[data-tema='oscuro'] .border-fuchsia-200 { border-color: color-mix(in srgb, #c026d3 40%, #0d1117) !important; }

/* Los últimos neutros claros sueltos. */
[data-tema='oscuro'] .bg-slate-200      { background-color: var(--su-fondo-activo) !important; }
[data-tema='oscuro'] .text-slate-200    { color: var(--su-texto-tenue) !important; }
[data-tema='oscuro'] .text-rose-500     { color: var(--su-peligro-texto) !important; }
[data-tema='oscuro'] .text-orange-900   { color: var(--su-aviso-texto) !important; }
[data-tema='oscuro'] .border-orange-300 { border-color: var(--su-aviso-borde) !important; }

/* LO QUE QUEDA FUERA, Y POR QUÉ.
   Después de esto la cobertura es del 96 %. Los 54 casos restantes NO son
   un olvido; son los dos grupos en los que tocar el color lo rompería:

   · `text-white` (42). Van sobre un fondo de COLOR — un botón azul, una
     insignia morada, una cabecera con degradado. Ese fondo sigue siendo
     de color en tema oscuro, así que el blanco encima sigue siendo lo
     correcto.

   · Los rellenos SATURADOS (`bg-red-500`, `bg-amber-400`,
     `bg-orange-400`, `bg-red-700`). Son fondos fuertes, no superficies
     claras: en oscuro se ven bien tal cual, y atenuarlos les quitaría
     justo la fuerza por la que se eligieron (marcan lo urgente).

   La solución definitiva no es ampliar más esta lista, sino que las
   pantallas dejen de usar utilidades de color y usen componentes. Este
   bloque existe para que el tema oscuro funcione MIENTRAS eso pasa. */

/* `text-white` NO se toca, y es una decisión, no un olvido.
   Sus 42 usos son texto sobre un fondo de COLOR (un botón azul, una
   insignia morada, una cabecera con degradado). Ese fondo sigue siendo
   de color en tema oscuro, así que el blanco encima sigue siendo lo
   correcto. Cambiarlo lo rompería justo donde hoy funciona. */

/* Caja de gráfica con alto acotado: la causa del bug de "página infinita"
   de la v51 era un canvas sin contenedor de alto fijo. Vive en el sistema
   para que ninguna pantalla nueva pueda repetirlo. */
.su-grafica {
  position: relative;
  overflow: hidden;
  height: 260px;
}
.su-grafica--baja { height: 200px; }
.su-grafica--alta { height: 340px; }
.su-grafica > canvas { max-width: 100% !important; max-height: 100% !important; }

/* ==========================================================
   v64 — FASE 6.3: accesibilidad
   ========================================================== */

/* Contenido para lectores de pantalla que NO se ve.
   Se usa la técnica de recorte (no `display:none` ni `visibility:hidden`,
   que también lo esconden del lector, que es justo lo contrario de lo que
   se busca). */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: calc(-1px * var(--su-densidad, 1));
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* "Saltar al contenido": primer elemento enfocable de cada pantalla.
   Invisible hasta que se le da foco con Tab, y ahí aparece. Sin esto,
   quien navega con teclado tiene que recorrer el menú lateral completo en
   cada pantalla antes de llegar a lo que vino a hacer. */
.su-saltar {
  position: absolute;
  left: var(--su-esp-2, .5rem);
  top: -3rem;
  z-index: 100;
  padding: var(--su-esp-2, .5rem) var(--su-esp-4, 1rem);
  background: var(--su-acento, #0057B8);
  color: #fff;
  border-radius: var(--su-radio-control, 8px);
  font-weight: 700;
  transition: top 120ms ease;
}
.su-saltar:focus { top: var(--su-esp-2, .5rem); }

/* Foco SIEMPRE visible. El rediseño puso `outline:none` en varios sitios
   para que el clic no dejara un borde feo; el efecto secundario es que
   quien navega con teclado no sabe dónde está parado. Se restituye un
   anillo claro, pero sólo para el foco de TECLADO (:focus-visible), así
   que al hacer clic con el ratón no aparece. */
:focus-visible {
  outline: 3px solid var(--su-acento, #0057B8);
  outline-offset: 2px;
}

/* Quien pidió menos animación en su sistema, no ve animación. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
    scroll-behavior: auto !important;
  }
}

/* ============================================================
   v66 — COMPONENTES QUE FALTABAN
   ============================================================

   La auditoría de la Fase 1 de v66 encontró que seis piezas que el
   sistema usa TODOS los días no existían como componente, así que cada
   pantalla las improvisaba:

     · Espera de carga  — 9 pantallas ponían el texto "Cargando..." suelto.
     · Ayuda contextual — el `title=""` del navegador, que tarda un
                          segundo largo en aparecer, no se puede leer con
                          el teclado y no se ve en una tableta.
     · Paginación       — 4 pantallas, 4 maquetaciones distintas.
     · Acordeón         — `<details>` sin estilo, con el triangulito del
                          navegador (que se dibuja distinto en cada uno).
     · Barra de avance  — dos implementaciones con divs anidados.
     · Barra de acciones— cada pantalla resolvía a mano la fila de
                          "título a la izquierda, botones a la derecha".

   Todo lo de abajo es ADITIVO: son clases nuevas. Ninguna pantalla
   existente cambia de aspecto por el hecho de que existan; cambian solo
   las que se migraron a usarlas.
   ============================================================ */

/* ------------------------------------------------------------
   v66.1 — ESPERA DE CARGA (esqueleto y giro)
   ------------------------------------------------------------
   Por qué esqueleto y no una rueda girando: el esqueleto ocupa la forma
   de lo que va a llegar, así que la página no da un salto cuando los
   datos entran. Una rueda centrada obliga a redibujar todo de golpe, y
   ese salto es la causa de la sensación de "se trabó y luego apareció".
   ------------------------------------------------------------ */
@keyframes su-latido {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.45; }
}
.su-esqueleto {
  display: block;
  background: var(--su-fondo-hundido);
  border-radius: var(--su-radio-chip);
  animation: su-latido 1.4s var(--su-curva) infinite;
}
.su-esqueleto--texto   { height: 0.75em; margin-block: 0.25em; }
.su-esqueleto--titulo  { height: 1.3em; width: 45%; margin-bottom: var(--su-esp-3); }
.su-esqueleto--linea   { height: 1em; }
.su-esqueleto--bloque  { height: 96px; border-radius: var(--su-radio-tarjeta); }
.su-esqueleto--circulo { width: 40px; height: 40px; border-radius: 999px; }
/* Anchos escalonados: un bloque de líneas todas del mismo largo se lee
   como una tabla vacía, no como texto por llegar. */
.su-esqueleto--corto  { width: 60%; }
.su-esqueleto--medio  { width: 80%; }

/* Giro para acciones puntuales (guardar, exportar), donde sí hay un
   botón concreto esperando y no una región entera. */
.su-cargador {
  display: inline-block;
  width: 1em;
  height: 1em;
  border: 2px solid currentColor;
  border-right-color: transparent;
  border-radius: 999px;
  animation: su-girar 0.75s linear infinite;
  vertical-align: -0.125em;
}

/* Región que está cargando: se atenúa y deja de aceptar clics, en vez de
   dejar al usuario pulsando botones sobre datos viejos. */
.su-cargando {
  position: relative;
  opacity: 0.55;
  pointer-events: none;
  transition: opacity var(--su-trans-normal) var(--su-curva);
}

/* ------------------------------------------------------------
   v66.2 — AYUDA CONTEXTUAL
   ------------------------------------------------------------
   Sustituye al `title=""` del navegador. Tres diferencias que importan:
   aparece de inmediato, aparece TAMBIÉN al enfocar con el teclado, y se
   ve igual en todos los navegadores.

   `aria-hidden` en el globo y el texto duplicado en `aria-label` del
   disparador es a propósito: el lector de pantalla ya anuncia la
   etiqueta; si además leyera el globo, lo diría dos veces.
   ------------------------------------------------------------ */
.su-pista {
  position: relative;
  display: inline-flex;
  align-items: center;
}
.su-pista__globo {
  position: absolute;
  bottom: calc(100% + 8px);
  left: 50%;
  transform: translateX(-50%) translateY(4px);
  z-index: var(--su-z-aviso);
  max-width: 260px;
  width: max-content;
  padding: var(--su-esp-2) var(--su-esp-3);
  background: var(--su-texto-fuerte);
  color: var(--su-fondo-elevado);
  border-radius: var(--su-radio-chip);
  font-size: var(--su-texto-2xs);
  font-weight: 500;
  line-height: 1.45;
  text-align: left;
  white-space: normal;
  box-shadow: var(--su-sombra-2);
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--su-trans-rapida) var(--su-curva),
              transform var(--su-trans-rapida) var(--su-curva),
              visibility var(--su-trans-rapida);
}
.su-pista:hover .su-pista__globo,
.su-pista:focus-within .su-pista__globo {
  opacity: 1;
  visibility: visible;
  transform: translateX(-50%) translateY(0);
}
/* Variante para cuando el elemento está pegado al borde superior y el
   globo se saldría de la pantalla. */
.su-pista--abajo .su-pista__globo { bottom: auto; top: calc(100% + 8px); }

/* ------------------------------------------------------------
   v66.3 — PAGINACIÓN
   ------------------------------------------------------------
   El resumen ("Mostrando 51-100 de 1 240") va a la IZQUIERDA y los
   controles a la derecha. No es simetría: el dato de cuántos registros
   hay es lo que la persona necesita leer, y se lee antes de decidir si
   quiere avanzar.
   ------------------------------------------------------------ */
.su-paginacion {
  display: flex;
  align-items: center;
  gap: var(--su-esp-3);
  flex-wrap: wrap;
  padding: var(--su-esp-3) var(--su-esp-4);
  border-top: 1px solid var(--su-borde-sutil);
}
.su-paginacion__resumen {
  font-size: var(--su-texto-xs);
  color: var(--su-texto-suave);
  font-variant-numeric: var(--su-num-tabular);
}
.su-paginacion__controles {
  display: flex;
  align-items: center;
  gap: var(--su-esp-1);
  margin-left: auto;
}
.su-paginacion__pagina {
  min-width: var(--su-btn-alto-min-sm);
  height: var(--su-btn-alto-min-sm);
  padding-inline: var(--su-esp-2);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid transparent;
  background: transparent;
  border-radius: var(--su-radio-chip);
  font-family: inherit;
  font-size: var(--su-texto-xs);
  font-weight: 600;
  font-variant-numeric: var(--su-num-tabular);
  color: var(--su-texto-suave);
  cursor: pointer;
  transition: background var(--su-trans-rapida) var(--su-curva),
              color var(--su-trans-rapida) var(--su-curva);
}
.su-paginacion__pagina:hover:not(:disabled) { background: var(--su-fondo-hover); color: var(--su-texto-fuerte); }
.su-paginacion__pagina[aria-current='page'] {
  background: var(--su-acento);
  border-color: transparent;
  color: var(--su-acento-contraste);
}
.su-paginacion__pagina:disabled { opacity: 0.4; cursor: not-allowed; }
/* Los puntos suspensivos entre saltos de página no son un botón. */
.su-paginacion__salto {
  padding-inline: var(--su-esp-1);
  color: var(--su-texto-tenue);
  user-select: none;
}

/* ------------------------------------------------------------
   v66.4 — ACORDEÓN
   ------------------------------------------------------------
   Construido sobre <details>/<summary> a propósito: el navegador ya
   resuelve abrir, cerrar, el foco y el anuncio para lectores de
   pantalla. Aquí solo se le quita el triangulito nativo (que Chrome,
   Firefox y Safari dibujan distinto) y se pone uno propio.
   ------------------------------------------------------------ */
.su-acordeon {
  border: 1px solid var(--su-borde);
  border-radius: var(--su-radio-tarjeta);
  background: var(--su-fondo-elevado);
  overflow: hidden;
}
.su-acordeon + .su-acordeon { margin-top: var(--su-esp-2); }
.su-acordeon__cabecera {
  display: flex;
  align-items: center;
  gap: var(--su-esp-3);
  min-height: var(--su-btn-alto-min);
  padding: var(--su-esp-3) var(--su-esp-4);
  font-size: var(--su-texto-sm);
  font-weight: 600;
  color: var(--su-texto-fuerte);
  cursor: pointer;
  list-style: none;
  user-select: none;
  transition: background var(--su-trans-rapida) var(--su-curva);
}
.su-acordeon__cabecera::-webkit-details-marker { display: none; }
.su-acordeon__cabecera:hover { background: var(--su-fondo-hover); }
.su-acordeon__flecha {
  margin-left: auto;
  flex-shrink: 0;
  color: var(--su-texto-tenue);
  transition: transform var(--su-trans-normal) var(--su-curva);
}
.su-acordeon[open] > .su-acordeon__cabecera { border-bottom: 1px solid var(--su-borde-sutil); }
.su-acordeon[open] > .su-acordeon__cabecera .su-acordeon__flecha { transform: rotate(180deg); }
.su-acordeon__cuerpo { padding: var(--su-esp-4); }

/* ------------------------------------------------------------
   v66.5 — BARRA DE AVANCE
   ------------------------------------------------------------
   El ancho se pone en línea (`style="width:72%"`) porque es un dato, no
   una decisión de diseño. El componente define cómo se ve; el número lo
   pone quien lo dibuja.
   ------------------------------------------------------------ */
.su-avance {
  height: 6px;
  width: 100%;
  background: var(--su-fondo-hundido);
  border-radius: 999px;
  overflow: hidden;
}
.su-avance__barra {
  height: 100%;
  background: var(--su-acento);
  border-radius: inherit;
  transition: width var(--su-trans-normal) var(--su-curva);
}
.su-avance--exito   .su-avance__barra { background: var(--su-exito); }
.su-avance--aviso   .su-avance__barra { background: var(--su-aviso); }
.su-avance--peligro .su-avance__barra { background: var(--su-peligro); }
.su-avance--alto { height: 10px; }
/* v73 — El tono también en la propia barra. Cuando quien la pinta conoce
   el porcentaje pero no controla el envoltorio, poner el modificador en el
   padre obliga a envolver; esto evita ese nudo. */
.su-avance__barra--exito   { background: var(--su-exito); }
.su-avance__barra--aviso   { background: var(--su-aviso); }
.su-avance__barra--peligro { background: var(--su-peligro); }

/* ============================================================
   v73 — CENTRO DE OPERACIONES
   ============================================================

   Las piezas del tablero de la Torre de Control. Se declaran aquí, en el
   sistema, y no en la hoja de esa pantalla, porque son componentes
   reutilizables: la misma tarjeta de indicador sirve para KPI Ejecutivos
   o para la Ficha 360 el día que se quieran.

   Todo sale de tokens. Ni un color escrito a mano, para que el tema
   oscuro, el alto contraste y la densidad sigan funcionando sin tener que
   acordarse de este bloque.
   ============================================================ */

/* --- Rejilla de indicadores -------------------------------------------
   `auto-fill` con un mínimo: las tarjetas se reacomodan solas según el
   ancho disponible, sin puntos de corte escritos a mano. En un teléfono
   caben dos; en un monitor ancho, seis. */
.su-kpis {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(clamp(150px, 20vw, 200px), 1fr));
  gap: var(--su-esp-3);
  margin-bottom: var(--su-esp-4);
}
.su-kpis--compacta {
  grid-template-columns: repeat(auto-fill, minmax(clamp(130px, 15vw, 165px), 1fr));
}

.su-kpi {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: calc(2px * var(--su-densidad, 1));
  padding: var(--su-esp-3);
  background: var(--su-fondo-elevado);
  border: 1px solid var(--su-borde-sutil);
  border-radius: var(--su-radio-tarjeta);
  box-shadow: var(--su-sombra-1);
  text-align: left;
  overflow: hidden;
  /* El borde de color va como pseudo-elemento y no como `border-left`:
     así el texto no se desplaza 3px respecto a las tarjetas sin tono. */
  transition: box-shadow var(--su-trans-normal) var(--su-curva),
              transform var(--su-trans-normal) var(--su-curva);
}
.su-kpi::before {
  content: '';
  position: absolute;
  inset: 0 auto 0 0;
  width: 3px;
  background: var(--su-borde-sutil);
}
.su-kpi__icono {
  color: var(--su-texto-tenue);
  font-size: var(--su-texto-sm);
  line-height: 1;
}
.su-kpi__valor {
  font-size: clamp(1.5rem, 2.6vw, 2rem);
  font-weight: 800;
  line-height: 1.05;
  color: var(--su-texto);
  font-variant-numeric: var(--su-num-tabular);
}
.su-kpi__etiqueta {
  font-size: var(--su-texto-xs);
  font-weight: 600;
  color: var(--su-texto-suave);
  line-height: 1.25;
}
.su-kpi__detalle {
  font-size: var(--su-texto-2xs);
  color: var(--su-texto-tenue);
  line-height: 1.25;
}

/* El tono tiñe el filo y el icono. NUNCA el número: un valor en rojo se
   lee como "está mal" incluso cuando sólo significa "es una incidencia",
   y hay tarjetas en rojo cuyo cero es la buena noticia. */
.su-kpi--exito::before   { background: var(--su-exito); }
.su-kpi--aviso::before   { background: var(--su-aviso); }
.su-kpi--peligro::before { background: var(--su-peligro); }
.su-kpi--info::before    { background: var(--su-info); }
.su-kpi--acento::before  { background: var(--su-acento); }
.su-kpi--exito   .su-kpi__icono { color: var(--su-exito); }
.su-kpi--aviso   .su-kpi__icono { color: var(--su-aviso); }
.su-kpi--peligro .su-kpi__icono { color: var(--su-peligro); }
.su-kpi--info    .su-kpi__icono { color: var(--su-info); }
.su-kpi--acento  .su-kpi__icono { color: var(--su-acento); }

.su-kpi--clicable {
  cursor: pointer;
  font: inherit;
  width: 100%;
}
.su-kpi--clicable:hover { box-shadow: var(--su-sombra-2); transform: translateY(-1px); }
/* v178 — la tarjeta que está mandando la vista.
   Una tarjeta que filtra y no se distingue de las otras deja la pantalla
   enseñando una lista corta sin decir quién la recortó. Se marca con el
   borde y no con el fondo: el fondo lo usa el tono (éxito, aviso, peligro)
   y taparlo borraría de qué estado se está hablando. */
.su-kpi--activa {
  border-color: var(--su-acento);
  box-shadow: var(--su-sombra-2);
}
/* El anillo de foco NO se redeclara aquí. La regla global de :focus-visible
   ya cubre esta tarjeta, y una copia local con más especificidad la dejaba
   con un anillo distinto al del resto del sistema — justo la inconsistencia
   que la auditoría de foco vino a eliminar. */
/* Una tarjeta que se puede pulsar tiene que parecerlo antes de pulsarla. */
.su-kpi--clicable::after {
  content: '';
  position: absolute;
  top: var(--su-esp-2);
  right: var(--su-esp-2);
  width: 6px; height: 6px;
  border-top: 1.5px solid var(--su-texto-tenue);
  border-right: 1.5px solid var(--su-texto-tenue);
  transform: rotate(45deg);
  opacity: .5;
}

/* --- Minigráfico ------------------------------------------------------ */
.su-chispa {
  display: block;
  width: 100%;
  height: calc(28px * var(--su-densidad, 1));
  margin-top: var(--su-esp-1);
  color: var(--su-texto-tenue);
  opacity: .85;
}
.su-chispa--acento { color: var(--su-acento); }
.su-chispa--info   { color: var(--su-info); }
.su-chispa--exito  { color: var(--su-exito); }

/* --- Tendencia -------------------------------------------------------- */
.su-tend {
  display: inline-flex;
  align-items: center;
  gap: calc(2px * var(--su-densidad, 1));
  font-size: var(--su-texto-2xs);
  font-weight: 700;
  margin-top: calc(2px * var(--su-densidad, 1));
}
.su-tend--sube   { color: var(--su-exito); }
.su-tend--baja   { color: var(--su-peligro); }
.su-tend--neutra { color: var(--su-texto-tenue); font-weight: 600; }

/* --- Alertas ---------------------------------------------------------- */
.su-alertas {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(clamp(200px, 26vw, 260px), 1fr));
  gap: var(--su-esp-3);
}
.su-alerta-tarjeta {
  display: grid;
  grid-template-columns: auto 1fr;
  grid-template-areas:
    'icono cuenta'
    'titulo titulo'
    'detalle detalle'
    'quien quien';
  gap: calc(2px * var(--su-densidad, 1)) var(--su-esp-2);
  align-items: center;
  padding: var(--su-esp-3);
  border-radius: var(--su-radio-tarjeta);
  border: 1px solid var(--su-borde-sutil);
  background: var(--su-fondo-elevado);
  text-align: left;
  width: 100%;
}
.su-alerta-tarjeta__icono   { grid-area: icono; font-size: var(--su-texto-sm); line-height: 1; }
.su-alerta-tarjeta__cuenta  {
  grid-area: cuenta;
  justify-self: start;
  font-size: 1.5rem; font-weight: 800; line-height: 1;
  font-variant-numeric: var(--su-num-tabular);
}
.su-alerta-tarjeta__titulo  { grid-area: titulo; font-size: var(--su-texto-xs); font-weight: 700; color: var(--su-texto); }
.su-alerta-tarjeta__detalle { grid-area: detalle; font-size: var(--su-texto-2xs); color: var(--su-texto-suave); line-height: 1.3; }
/* Los nombres se recortan a una línea: la tarjeta dice CUÁNTOS y da una
   pista de QUIÉNES; la lista completa está a un clic, en la tabla. */
.su-alerta-tarjeta__quien   {
  grid-area: quien;
  font-size: var(--su-texto-2xs);
  color: var(--su-texto-tenue);
  margin-top: var(--su-esp-1);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.su-alerta-tarjeta--peligro { border-color: color-mix(in srgb, var(--su-peligro) 35%, var(--su-borde-sutil)); }
.su-alerta-tarjeta--aviso   { border-color: color-mix(in srgb, var(--su-aviso) 35%, var(--su-borde-sutil)); }
.su-alerta-tarjeta--info    { border-color: color-mix(in srgb, var(--su-info) 35%, var(--su-borde-sutil)); }
.su-alerta-tarjeta--peligro .su-alerta-tarjeta__icono,
.su-alerta-tarjeta--peligro .su-alerta-tarjeta__cuenta { color: var(--su-peligro); }
.su-alerta-tarjeta--aviso   .su-alerta-tarjeta__icono,
.su-alerta-tarjeta--aviso   .su-alerta-tarjeta__cuenta { color: var(--su-aviso); }
.su-alerta-tarjeta--info    .su-alerta-tarjeta__icono,
.su-alerta-tarjeta--info    .su-alerta-tarjeta__cuenta { color: var(--su-info); }
button.su-alerta-tarjeta { cursor: pointer; font: inherit; }
button.su-alerta-tarjeta:hover { box-shadow: var(--su-sombra-2); }
/* Mismo motivo: el anillo global ya la cubre por ser <button>. */

/* --- Objetivos -------------------------------------------------------- */
.su-objetivos { display: flex; flex-direction: column; gap: var(--su-esp-2); }
.su-objetivo { display: flex; flex-direction: column; gap: calc(3px * var(--su-densidad, 1)); }
.su-objetivo__fila {
  display: flex; justify-content: space-between; gap: var(--su-esp-2);
  font-size: var(--su-texto-xs); color: var(--su-texto-suave);
}
.su-objetivo__fila strong { color: var(--su-texto); font-variant-numeric: var(--su-num-tabular); }
.su-metas-form {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(clamp(160px, 22vw, 220px), 1fr));
  gap: var(--su-esp-2);
  margin-top: var(--su-esp-2);
}
.su-campo-linea { display: flex; flex-direction: column; gap: calc(2px * var(--su-densidad, 1)); }
.su-campo-linea > span { font-size: var(--su-texto-2xs); color: var(--su-texto-suave); font-weight: 600; }

/* --- Cabecera y subtítulos de bloque ---------------------------------- */
.su-bloque__cabecera { margin-bottom: var(--su-esp-3); }
.su-subseccion {
  font-size: var(--su-texto-xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: var(--su-titulo-interletra, .04em);
  color: var(--su-texto-tenue);
  margin: var(--su-esp-4) 0 var(--su-esp-2);
}
.su-desplegable > summary {
  cursor: pointer;
  font-size: var(--su-texto-xs);
  font-weight: 600;
  color: var(--su-acento);
  user-select: none;
}

/* En alto contraste el filo de 3px no basta para distinguir un tono de
   otro, así que el borde entero se tiñe. */
[data-contraste='alto'] .su-kpi--exito   { border-color: var(--su-exito); }
[data-contraste='alto'] .su-kpi--aviso   { border-color: var(--su-aviso); }
[data-contraste='alto'] .su-kpi--peligro { border-color: var(--su-peligro); }
[data-contraste='alto'] .su-kpi--info    { border-color: var(--su-info); }

@media (max-width: 640px) {
  /* En un teléfono el minigráfico no aporta: son 7 puntos en 4 cm. Se
     quita para dejar sitio al número, que es lo que se viene a ver. */
  .su-chispa { display: none; }
  .su-kpis { grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: var(--su-esp-2); }
}

/* ============================================================
   v73 — FASES 2, 3 y 4 del Centro de Operaciones
   ============================================================ */

/* --- Barra de filtros (FASE 3) ---------------------------------------- */
.su-barra-filtros {
  display: flex;
  flex-direction: column;
  gap: var(--su-esp-3);
  padding: var(--su-esp-3);
  background: var(--su-fondo-elevado);
  border: 1px solid var(--su-borde-sutil);
  border-radius: var(--su-radio-tarjeta);
  margin-bottom: var(--su-esp-4);
  /* Se queda arriba al bajar: los filtros dejan de servir en cuanto hay
     que subir a buscarlos. */
  position: sticky;
  top: var(--su-esp-2);
  z-index: 5;
  box-shadow: var(--su-sombra-1);
}
.su-barra-filtros__periodo,
.su-barra-filtros__favoritos { display: flex; flex-wrap: wrap; gap: var(--su-esp-2); }
.su-barra-filtros__fechas { display: flex; gap: var(--su-esp-3); flex-wrap: wrap; }
.su-barra-filtros__ejes {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(clamp(140px, 18vw, 180px), 1fr));
  gap: var(--su-esp-2);
}
.su-barra-filtros__acciones {
  display: flex; align-items: center; gap: var(--su-esp-2); flex-wrap: wrap;
  border-top: 1px solid var(--su-borde-sutil);
  padding-top: var(--su-esp-2);
}
.su-campo--sm { padding: calc(4px * var(--su-densidad, 1)) var(--su-esp-2); font-size: var(--su-texto-xs); }

/* --- Ranking (FASE 2) ------------------------------------------------- */
.su-ranking { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--su-esp-2); }
.su-ranking__fila {
  display: grid;
  grid-template-columns: auto minmax(90px, 1.2fr) minmax(60px, 2fr) auto;
  grid-template-areas: 'puesto nombre barra cifra' '. extra extra extra';
  align-items: center;
  gap: calc(2px * var(--su-densidad, 1)) var(--su-esp-2);
}
.su-ranking__puesto {
  grid-area: puesto;
  width: calc(22px * var(--su-densidad, 1));
  text-align: center;
  font-size: var(--su-texto-2xs); font-weight: 800;
  color: var(--su-texto-tenue);
  font-variant-numeric: var(--su-num-tabular);
}
.su-ranking__nombre {
  grid-area: nombre;
  text-align: left; font: inherit; cursor: pointer;
  font-size: var(--su-texto-xs); font-weight: 700; color: var(--su-texto);
  background: none; border: 0; padding: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.su-ranking__nombre:hover { color: var(--su-acento); text-decoration: underline; }
.su-ranking__barra {
  grid-area: barra;
  height: calc(8px * var(--su-densidad, 1));
  background: var(--su-fondo-hundido);
  border-radius: 999px; overflow: hidden;
}
.su-ranking__barra > span {
  display: block; height: 100%;
  background: var(--su-acento); border-radius: inherit;
  transition: width var(--su-trans-normal) var(--su-curva);
}
.su-ranking__cifra {
  grid-area: cifra;
  font-weight: 800; font-size: var(--su-texto-sm);
  font-variant-numeric: var(--su-num-tabular);
  color: var(--su-texto);
}
.su-ranking__extra { grid-area: extra; font-size: var(--su-texto-2xs); color: var(--su-texto-tenue); }

/* --- Comparativa de períodos ------------------------------------------ */
.su-comparativa { display: flex; flex-direction: column; gap: var(--su-esp-2); }
.su-comparativa__fila {
  display: grid;
  grid-template-columns: minmax(90px, 1fr) minmax(60px, 2.4fr) auto;
  grid-template-areas: 'etiqueta barra cifra' '. extra extra';
  align-items: center; gap: calc(2px * var(--su-densidad, 1)) var(--su-esp-2);
}
.su-comparativa__etiqueta { grid-area: etiqueta; font-size: var(--su-texto-xs); font-weight: 600; color: var(--su-texto-suave); }
.su-comparativa__barra {
  grid-area: barra; height: calc(10px * var(--su-densidad, 1));
  background: var(--su-fondo-hundido); border-radius: 999px; overflow: hidden;
}
.su-comparativa__barra > span { display: block; height: 100%; background: var(--su-info); border-radius: inherit; }
.su-comparativa__cifra { grid-area: cifra; font-weight: 800; font-variant-numeric: var(--su-num-tabular); }
.su-comparativa__extra { grid-area: extra; font-size: var(--su-texto-2xs); color: var(--su-texto-tenue); }

/* --- Mapa de calor ----------------------------------------------------- */
.su-calor { display: flex; flex-direction: column; gap: calc(2px * var(--su-densidad, 1)); overflow-x: auto; }
.su-calor__fila { display: flex; gap: calc(2px * var(--su-densidad, 1)); align-items: center; }
/* La regla de horas no es un día más. Sin separarla, el ojo la lee como la
   primera fila de datos y el mapa entero se corre un renglón. */
.su-calor__fila--cabecera { margin-bottom: calc(4px * var(--su-densidad, 1)); }
.su-calor__dia {
  width: calc(34px * var(--su-densidad, 1)); flex: 0 0 auto;
  font-size: var(--su-texto-2xs); color: var(--su-texto-tenue); font-weight: 600;
}
.su-calor__hora {
  flex: 1 1 0; min-width: 14px; text-align: center;
  font-size: var(--su-texto-2xs); color: var(--su-texto-tenue);
}
.su-calor__celda {
  flex: 1 1 0; min-width: 14px;
  height: calc(18px * var(--su-densidad, 1));
  border-radius: var(--su-radio-micro);
  /* La intensidad va en una variable puesta desde JS. Así el color sale
     igual del token del acento y sigue al tema oscuro. */
  background: color-mix(in srgb, var(--su-acento) calc(var(--i, 0) * 100%), var(--su-fondo-hundido));
}
/* Blanco, no "el color más bajo": "cero actividad" y "nunca hubo
   actividad a esa hora" son cosas distintas. */
.su-calor__celda--vacia { background: transparent; border: 1px dashed var(--su-borde-sutil); }

/* --- Tendencia --------------------------------------------------------- */
.su-tendencia svg { width: 100%; height: clamp(90px, 14vw, 140px); display: block; }
.su-tendencia__area { fill: var(--su-acento); opacity: .12; }
.su-tendencia__linea { fill: none; stroke: var(--su-acento); stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.su-tendencia__pie {
  display: flex; justify-content: space-between;
  font-size: var(--su-texto-2xs); color: var(--su-texto-tenue);
  margin-top: var(--su-esp-1);
}

/* --- Barras verticales (día de la semana) ------------------------------ */
.su-barras { display: flex; align-items: flex-end; gap: var(--su-esp-2); height: clamp(120px, 18vw, 170px); }
.su-barras__col { flex: 1 1 0; display: flex; flex-direction: column; align-items: center; height: 100%; gap: calc(2px * var(--su-densidad, 1)); }
.su-barras__valor { font-size: var(--su-texto-2xs); font-weight: 700; color: var(--su-texto-suave); font-variant-numeric: var(--su-num-tabular); }
.su-barras__barra { flex: 1 1 auto; width: 100%; display: flex; align-items: flex-end; }
.su-barras__barra > span {
  width: 100%; background: var(--su-acento);
  border-radius: var(--su-radio-chip) var(--su-radio-chip) 0 0;
  transition: height var(--su-trans-normal) var(--su-curva);
  min-height: 2px;
}
.su-barras__etiqueta { font-size: var(--su-texto-2xs); color: var(--su-texto-tenue); }

/* --- Línea de tiempo (FASE 4) ------------------------------------------ */
.su-timeline { display: flex; flex-direction: column; gap: calc(4px * var(--su-densidad, 1)); }
.su-timeline__fila {
  display: grid;
  grid-template-columns: minmax(80px, 1fr) 4fr auto;
  align-items: center; gap: var(--su-esp-2);
}
.su-timeline__nombre {
  font-size: var(--su-texto-2xs); font-weight: 600; color: var(--su-texto-suave);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.su-timeline__pista {
  position: relative; height: calc(14px * var(--su-densidad, 1));
  background: var(--su-fondo-hundido); border-radius: 999px;
}
.su-timeline__barra {
  position: absolute; top: 0; bottom: 0;
  background: var(--su-acento); border-radius: 999px;
}
/* Rayada: sigue abierta. Puede ser que la persona siga en campo o que se
   le olvidara marcar la salida — y las dos cosas se ven igual en los
   datos, así que no se puede afirmar cuál es. */
.su-timeline__barra--abierta {
  background: repeating-linear-gradient(45deg,
    var(--su-aviso), var(--su-aviso) 4px,
    color-mix(in srgb, var(--su-aviso) 55%, var(--su-fondo-elevado)) 4px,
    color-mix(in srgb, var(--su-aviso) 55%, var(--su-fondo-elevado)) 8px);
}
.su-timeline__horas { font-size: var(--su-texto-2xs); color: var(--su-texto-tenue); font-variant-numeric: var(--su-num-tabular); }

/* --- Feed y agenda ----------------------------------------------------- */
.su-feed, .su-agenda { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
.su-feed__linea, .su-agenda__linea {
  display: grid; align-items: baseline; gap: var(--su-esp-2);
  padding: var(--su-esp-2) 0;
  border-bottom: 1px solid var(--su-borde-sutil);
  font-size: var(--su-texto-xs);
}
.su-feed__linea { grid-template-columns: auto auto 1fr; }
.su-agenda__linea { grid-template-columns: auto 1fr auto; }
.su-feed__linea:last-child, .su-agenda__linea:last-child { border-bottom: 0; }
.su-feed__icono { color: var(--su-texto-tenue); }
.su-feed__hora, .su-agenda__hora, .su-agenda__fecha {
  font-size: var(--su-texto-2xs); color: var(--su-texto-tenue);
  font-variant-numeric: var(--su-num-tabular);
}
.su-feed__texto, .su-agenda__texto { color: var(--su-texto-suave); }
.su-feed__texto strong, .su-agenda__texto strong { color: var(--su-texto); }

/* --- Accesos rápidos --------------------------------------------------- */
.su-accesos {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(clamp(130px, 16vw, 170px), 1fr));
  gap: var(--su-esp-2);
}
.su-acceso {
  display: flex; flex-direction: column; align-items: flex-start; gap: var(--su-esp-1);
  padding: var(--su-esp-3);
  border: 1px solid var(--su-borde-sutil);
  border-radius: var(--su-radio-tarjeta);
  background: var(--su-fondo-elevado);
  color: var(--su-texto);
  text-decoration: none;
  transition: border-color var(--su-trans-normal) var(--su-curva),
              box-shadow var(--su-trans-normal) var(--su-curva);
}
.su-acceso:hover { border-color: var(--su-acento); box-shadow: var(--su-sombra-2); }
.su-acceso__icono { color: var(--su-acento); font-size: var(--su-texto-sm); }
.su-acceso__texto { font-size: var(--su-texto-xs); font-weight: 600; }

.su-bloque__fila { display: flex; align-items: center; justify-content: space-between; gap: var(--su-esp-3); flex-wrap: wrap; }
.su-pestanas--sm .su-pestana { font-size: var(--su-texto-2xs); padding: calc(4px * var(--su-densidad, 1)) var(--su-esp-2); }

@media (max-width: 640px) {
  /* La barra pegajosa se suelta: en un teléfono se comería media
     pantalla. */
  .su-barra-filtros { position: static; }
  .su-ranking__fila { grid-template-columns: auto 1fr auto; grid-template-areas: 'puesto nombre cifra' '. barra barra' '. extra extra'; }
  .su-timeline__fila { grid-template-columns: minmax(70px, 1fr) 2fr auto; }
}

/* ------------------------------------------------------------
   v66.6 — BARRA DE ACCIONES DE UNA SECCIÓN
   ------------------------------------------------------------
   La composición "rótulo a la izquierda, acciones a la derecha" aparece
   encima de casi todas las tablas del sistema. Estaba resuelta a mano en
   cada pantalla, con separaciones distintas.
   ------------------------------------------------------------ */
.su-barra {
  display: flex;
  align-items: center;
  gap: var(--su-esp-3);
  flex-wrap: wrap;
  padding: var(--su-esp-3) var(--su-esp-4);
  border-bottom: 1px solid var(--su-borde-sutil);
  background: var(--su-fondo-cabecera);
}
.su-barra__titulo {
  font-size: var(--su-texto-sm);
  font-weight: 600;
  color: var(--su-texto-fuerte);
  margin: 0;
}
.su-barra__acciones { margin-left: auto; display: flex; align-items: center; gap: var(--su-btn-separacion); flex-wrap: wrap; }
/* Debajo de 640 px las acciones bajan a su propia línea en vez de
   apretujarse contra el título hasta partirlo en dos renglones. */
@media (max-width: 639px) {
  .su-barra__acciones { margin-left: 0; width: 100%; }
}

/* ------------------------------------------------------------
   v66.7 — SEPARADOR CON RÓTULO
   ------------------------------------------------------------ */
.su-separador {
  display: flex;
  align-items: center;
  gap: var(--su-esp-3);
  margin-block: var(--su-esp-5);
  font-size: var(--su-texto-2xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--su-texto-tenue);
}
.su-separador::before,
.su-separador::after {
  content: '';
  flex: 1;
  height: 1px;
  background: var(--su-borde);
}
.su-separador--izq::before { flex: 0 0 var(--su-esp-4); }

/* ------------------------------------------------------------
   v66.8 — DOS PIEZAS QUE EL JAVASCRIPT REPETÍA
   ------------------------------------------------------------
   Estas dos aparecían escritas a mano, con utilidades de Tailwind fijas,
   en equipo.js, vista.js, vehiculos.js, kpi.js, base-datos.js,
   comunicados.js, centro-sincronizacion.js y otros. Al no ser
   componentes, no seguían el tema oscuro ni el tamaño de letra.
   ------------------------------------------------------------ */

/* Muestra de color: el círculo que se pulsa para asignar un color a un
   programa, un estado o un tipo de vehículo. Se conserva redonda a
   propósito — aquí el círculo NO significa "botón de consumo", significa
   "esto es una muestra de pintura", que es una convención distinta y
   correcta. Lo que se corrige es el borde de selección: antes era
   `ring-2` (un halo que se salía del elemento y descuadraba la fila) y
   ahora es un anillo por fuera del trazo, que no ocupa espacio. */
.su-muestra {
  width: 28px;
  height: 28px;
  padding: 0;
  border: 2px solid var(--su-fondo-elevado);
  border-radius: 999px;
  cursor: pointer;
  box-shadow: 0 0 0 1px var(--su-borde);
  transition: transform var(--su-trans-rapida) var(--su-curva),
              box-shadow var(--su-trans-rapida) var(--su-curva);
}
.su-muestra:hover { transform: scale(1.12); }
.su-muestra--activa {
  box-shadow: 0 0 0 2px var(--su-acento);
}

/* Acción escrita como texto (no como botón con relleno). Se usa dentro de
   una fila de tabla o de una tarjeta, donde un botón completo pesaría
   demasiado. Sigue siendo un <button> de verdad: mantiene el foco de
   teclado y el área de toque mínima. */
.su-enlace-btn {
  display: inline-flex;
  align-items: center;
  gap: var(--su-esp-1);
  min-height: 28px;
  padding: 0;
  border: 0;
  background: none;
  font-family: inherit;
  font-size: var(--su-texto-xs);
  font-weight: 600;
  color: var(--su-acento);
  cursor: pointer;
  text-align: left;
  transition: color var(--su-trans-rapida) var(--su-curva);
}
.su-enlace-btn:hover { color: var(--su-acento-fuerte); text-decoration: underline; }
.su-enlace-btn--peligro { color: var(--su-peligro-texto); }
.su-enlace-btn--peligro:hover { color: var(--su-peligro); }
.su-enlace-btn--tenue { color: var(--su-texto-tenue); font-weight: 500; }
.su-enlace-btn--tenue:hover { color: var(--su-peligro); }

/* ============================================================
   v66.9 — RESPONSIVE: CUATRO TAMAÑOS, NO DIECINUEVE
   ============================================================

   La auditoría contó 19 puntos de corte distintos repartidos entre
   `ui-sistema.css`, los 17 bloques `<style>` de las pantallas y el CSS
   del módulo GPS: 1600, 1180, 1023, 1024, 880, 860, 767, 768, 639, 560,
   480, 420, 360...

   El problema no es que sean muchos, es que no coinciden: una tabla se
   reacomodaba a 860 px y la barra de filtros que tiene encima a 880. En
   la franja intermedia la pantalla quedaba a medio camino entre dos
   diseños — exactamente el ancho de una tableta en horizontal.

   Se fijan CUATRO, que son los cuatro aparatos reales con los que se usa
   este sistema:

     móvil     < 640 px    teléfono
     tableta   640-1023    tableta, y ventana angosta en un portátil
     portátil  1024-1439   el caso mayoritario en la oficina
     escritorio ≥ 1440     monitor grande

   1024 es el corte que ya usaba el panel lateral (deja de ser cajón y se
   queda fijo), así que se respeta: mover ese número habría cambiado
   comportamiento de navegación, y eso no se toca.

   Estas reglas NO reemplazan las media queries que ya existen: las
   completan, cubriendo la franja de tableta que estaba sin atender.
   ============================================================ */

/* --- TABLETA (640-1023 px) --------------------------------------
   Es el tamaño peor servido hoy: demasiado ancho para la vista de
   teléfono y demasiado angosto para la de escritorio. */
@media (min-width: 640px) and (max-width: 1023px) {
  /* Las cuadrículas de indicadores caían a 4 columnas de 90 px, con las
     cifras partidas en dos renglones. Dos columnas se leen. */
  .su-cuadricula--indicadores { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* Un encabezado de pantalla con título largo + 3 botones no cabe en
     una línea; en vez de comprimir el título, los botones bajan. */
  .su-encabezado__acciones { width: 100%; }
  /* El contenido pega contra los bordes en tableta porque el relleno
     estaba pensado para teléfono. */
  .su-pagina { padding-inline: var(--su-esp-5); }
}

/* --- ESCRITORIO GRANDE (≥ 1440 px) ------------------------------
   En un monitor de 27" el contenido se queda en una columna angosta con
   dos franjas grises enormes a los lados. Se le da algo más de aire
   interno a las tarjetas en vez de estirar el texto: una línea de 200
   caracteres no se lee mejor por ser más larga. */
@media (min-width: 1440px) {
  .su-tarjeta { padding: var(--su-esp-6); }
  .su-cuadricula--indicadores { grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); }
}

/* --- MÓVIL (< 640 px) -------------------------------------------
   Ajustes que faltaban y que se veían a diario. */
@media (max-width: 639px) {
  /* Un modal a pantalla completa desde abajo es lo que espera alguien en
     un teléfono; centrado y con márgenes, se pierde alto útil. */
  .su-modal { padding: 0; align-items: flex-end; }
  .su-modal__caja {
    max-width: 100%;
    max-height: 92vh;
    border-radius: var(--su-radio-tarjeta) var(--su-radio-tarjeta) 0 0;
    border-bottom: 0;
  }
  /* El pie de un modal con dos botones los dejaba a 40 % de ancho cada
     uno, apretados contra el borde derecho. Apilados y a lo ancho son
     tocables sin apuntar. */
  .su-modal__pie { flex-direction: column-reverse; align-items: stretch; }
  .su-modal__pie .su-btn { width: 100%; }
  /* Los filtros en columna: en una fila de 360 px, tres campos quedan de
     100 px y no se lee ni el marcador de posición. */
  .su-filtros { flex-direction: column; align-items: stretch; }
  .su-filtros__acciones { margin-left: 0; }
  /* Las pestañas se deslizan en vez de partirse en tres renglones. */
  .su-pestanas {
    flex-wrap: nowrap;
    overflow-x: auto;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
  }
  .su-pestanas::-webkit-scrollbar { display: none; }
  /* La paginación: el resumen arriba, los controles centrados abajo. */
  .su-paginacion { flex-direction: column; align-items: stretch; gap: var(--su-esp-2); }
  .su-paginacion__controles { margin-left: 0; justify-content: center; }
}

/* --- Utilidades de visibilidad por tamaño -----------------------
   Para no repetir la media query en cada pantalla. */
@media (max-width: 639px)  { .su-no-movil     { display: none !important; } }
@media (min-width: 640px)  { .su-solo-movil-x { display: none !important; } }
@media (max-width: 1023px) { .su-solo-escritorio { display: none !important; } }

/* --- IMPRESIÓN --------------------------------------------------
   No estaba contemplada, y los reportes de este sistema se imprimen. Sin
   esto, una hoja impresa salía con el menú lateral, la cabecera fija
   encima del primer párrafo y los botones de acción. */
@media print {
  .su-lateral,
  .su-cabecera,
  .su-velo-lateral,
  .su-encabezado__acciones,
  .su-botonera,
  .su-filtros__acciones,
  .su-paginacion__controles { display: none !important; }

  body { background: #fff; color: #000; }
  .su-pagina { max-width: none; padding: 0; padding-top: 0; }
  body.su-con-lateral { --su-desplazamiento: 0; }

  /* Una tarjeta partida entre dos hojas es ilegible. */
  .su-tarjeta, .su-indicador, .su-tabla-envoltorio {
    box-shadow: none;
    border: 1px solid #ccc;
    break-inside: avoid;
  }
  /* El encabezado de la tabla se repite en cada hoja: sin esto, a partir
     de la página 2 son columnas de números sin nombre. */
  .su-tabla thead { display: table-header-group; }
  .su-tabla tbody tr { break-inside: avoid; }
  .su-tabla-envoltorio { overflow: visible; }
}

/* ============================================================
   v66.10 — CONTROL DE MOVIMIENTO POR PREFERENCIA
   ============================================================
   El sistema ya respetaba `prefers-reduced-motion` del sistema
   operativo. El problema práctico: para usarlo hay que cambiar una
   opción de Windows que afecta a TODAS las aplicaciones. Mucha gente
   quiere menos movimiento sólo aquí — normalmente porque pasa ocho horas
   frente a este panel, no frente a las demás.

   Esto NO anula la preferencia del sistema: la media query de más arriba
   sigue existiendo y sigue ganando. Esto solo añade una forma de pedir
   lo mismo desde dentro de la aplicación.
   ============================================================ */

/* "Suaves": se conservan las transiciones (son las que confirman que algo
   respondió al clic) y se quitan las animaciones de entrada, que son las
   que de verdad producen sensación de movimiento en la pantalla. */
[data-animaciones='suaves'] .su-anim-entrar,
[data-animaciones='suaves'] .su-anim-aparecer,
[data-animaciones='suaves'] .anim-fade-in {
  animation: none;
}
[data-animaciones='suaves'] {
  --su-trans-rapida: 80ms;
  --su-trans-normal: 120ms;
}

/* "Ninguna": todo instantáneo. Se dejan fuera los indicadores de espera
   (`.su-girando`, `.su-cargador`, `.su-esqueleto`) a propósito: si se
   congelan, la persona no tiene forma de saber que el sistema está
   trabajando y va a pensar que se trabó. */
[data-animaciones='ninguna'] *:not(.su-girando):not(.su-cargador):not(.su-esqueleto),
[data-animaciones='ninguna'] *:not(.su-girando):not(.su-cargador):not(.su-esqueleto)::before,
[data-animaciones='ninguna'] *:not(.su-girando):not(.su-cargador):not(.su-esqueleto)::after {
  animation-duration: 0.01ms !important;
  animation-iteration-count: 1 !important;
  transition-duration: 0.01ms !important;
}

/* v66 — Fila de resultado del buscador global (nav-comun.js).
   Estaba escrita con utilidades fijas, así que el buscador era una de las
   pocas piezas del marco que no seguía el tema oscuro: fondo blanco y
   texto gris claro sobre una aplicación oscura. */
.su-resultado-busqueda {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--su-esp-3);
  width: 100%;
  min-height: var(--su-btn-alto-min);
  padding: var(--su-esp-2) var(--su-esp-5);
  border: 0;
  border-bottom: 1px solid var(--su-borde-sutil);
  background: transparent;
  font-family: inherit;
  font-size: var(--su-texto-sm);
  color: var(--su-texto);
  text-align: left;
  cursor: pointer;
  transition: background var(--su-trans-rapida) var(--su-curva);
}
.su-resultado-busqueda:last-child { border-bottom: 0; }
.su-resultado-busqueda:hover { background: var(--su-fondo-hover); }

/* ============================================================
   v66.11 — CALENDARIO
   ============================================================
   Las 9 piezas que quedaron fuera del sistema en la primera pasada, y la
   razón por la que se dejaron: llevan color según el estado de cada día,
   calculado en tiempo de dibujado, así que no encajaban en ninguno de los
   componentes que ya existían.

   La salida no es forzarlas dentro de `.su-btn` —una celda de día no es
   un botón de acción y no debe medir 44 px— sino declararlas como lo que
   son. Son cuatro:

     .su-dia         el número de día en la rejilla del mes
     .su-dia-agregar el "+" que aparece en la esquina de cada celda
     .su-mini-dia    la celda del calendario anual, de 12 px
     .su-dia-fila    la cabecera de día en la vista de agenda

   Lo que ganan al ser componentes: siguen el tema oscuro, la escala de
   letra y la densidad, en vez de traer `text-slate-300` y `py-1` fijos.
   ============================================================ */

/* Número de día en la rejilla mensual. Cuadrado y compacto a propósito:
   dentro de una celda que ya lleva actividades, el número es una
   referencia, no la acción principal. */
.su-dia {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.75rem;
  height: 1.75rem;
  padding: 0;
  border: 0;
  border-radius: var(--su-radio-chip);
  background: transparent;
  font-family: inherit;
  font-size: var(--su-texto-xs);
  font-weight: 700;
  font-variant-numeric: var(--su-num-tabular);
  color: var(--su-texto-titulo);
  cursor: pointer;
  transition: background var(--su-trans-rapida) var(--su-curva),
              color var(--su-trans-rapida) var(--su-curva);
}
.su-dia:hover { background: var(--su-acento-suave); }
/* Los días del mes anterior o siguiente: presentes pero apagados. No se
   ocultan porque la rejilla necesita las 7 columnas completas. */
.su-dia--fuera { color: var(--su-texto-tenue); font-weight: 500; }
.su-dia--hoy {
  background: var(--su-acento);
  color: var(--su-acento-contraste);
}
.su-dia--hoy:hover { background: var(--su-acento-fuerte); }

/* El "+" de cada celda. Discreto en reposo y claro al pasar por encima:
   si estuviera siempre marcado, 42 signos de más competirían con las
   actividades, que es lo que de verdad hay que leer. */
.su-dia-agregar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.25rem;
  height: 1.25rem;
  padding: 0;
  border: 0;
  border-radius: var(--su-radio-pastilla);
  background: transparent;
  font-family: inherit;
  font-size: var(--su-texto-sm);
  font-weight: 700;
  line-height: 1;
  color: var(--su-texto-tenue);
  cursor: pointer;
  transition: background var(--su-trans-rapida) var(--su-curva),
              color var(--su-trans-rapida) var(--su-curva);
}
.su-dia-agregar:hover { background: var(--su-acento-suave); color: var(--su-acento); }
/* Sobre el día de hoy, que ya es azul sólido, el "+" tiene que invertirse
   o se pierde. */
.su-dia--hoy-contexto .su-dia-agregar { color: rgba(255, 255, 255, 0.8); }
.su-dia--hoy-contexto .su-dia-agregar:hover { background: rgba(255, 255, 255, 0.22); color: #fff; }

/* Cabecera de columna en la vista de semana: día de la semana arriba,
   número abajo, todo pulsable como un bloque. */
.su-dia-col {
  display: block;
  width: 100%;
  padding: var(--su-esp-1) 0;
  border: 0;
  border-radius: var(--su-radio-control);
  background: transparent;
  font-family: inherit;
  color: var(--su-texto-titulo);
  cursor: pointer;
  transition: background var(--su-trans-rapida) var(--su-curva);
}
.su-dia-col:hover { background: var(--su-acento-suave); }
.su-dia-col--hoy { background: var(--su-acento); color: var(--su-acento-contraste); }
.su-dia-col--hoy:hover { background: var(--su-acento-fuerte); }
.su-dia-col__nombre {
  display: block;
  font-size: var(--su-texto-2xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--su-texto-tenue);
}
.su-dia-col--hoy .su-dia-col__nombre { color: rgba(255, 255, 255, 0.85); }
.su-dia-col__numero {
  display: block;
  font-size: var(--su-texto-sm);
  font-weight: 800;
  line-height: 1.15;
  font-variant-numeric: var(--su-num-tabular);
}

/* Celda del calendario anual: 12 casillas de un mes entero por pantalla.
   Aquí el color de fondo lo pone quien dibuja (dice cuántas actividades
   hay ese día), así que el componente solo define forma y comportamiento
   — nunca color, o pisaría el dato. */
.su-mini-dia {
  display: block;
  width: 100%;
  padding: var(--su-esp-1) 0;
  border: 0;
  border-radius: var(--su-radio-chip);
  background: transparent;
  font-family: inherit;
  font-size: var(--su-texto-2xs);
  font-weight: 600;
  font-variant-numeric: var(--su-num-tabular);
  line-height: 1;
  color: inherit;
  cursor: pointer;
  transition: opacity var(--su-trans-rapida) var(--su-curva),
              background var(--su-trans-rapida) var(--su-curva);
}
.su-mini-dia:hover { opacity: 0.8; }
/* Un día sin actividades no tiene color propio, así que necesita su
   propia señal al pasar por encima. */
.su-mini-dia--vacio:hover { opacity: 1; background: var(--su-fondo-hundido); }
/* Los días que no son del mes se dibujan como texto, no como botón: no
   llevan a ninguna parte. */
.su-mini-dia--fuera {
  width: 100%;
  padding: var(--su-esp-1) 0;
  font-size: var(--su-texto-2xs);
  line-height: 1;
  text-align: center;
}

/* Cabecera de día en la vista de agenda: pastilla con el número + el
   nombre del día + el recuento. */
.su-dia-fila {
  display: flex;
  align-items: center;
  gap: var(--su-esp-2);
  margin-bottom: var(--su-esp-1);
  padding: 0;
  border: 0;
  background: transparent;
  font-family: inherit;
  text-align: left;
  cursor: pointer;
}
.su-dia-fila__numero {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 2.25rem;
  height: 2.25rem;
  border-radius: var(--su-radio-tarjeta);
  background: var(--su-acento-suave);
  color: var(--su-acento);
  font-size: var(--su-texto-sm);
  font-weight: 800;
  font-variant-numeric: var(--su-num-tabular);
}
.su-dia-fila--hoy .su-dia-fila__numero { background: var(--su-acento); color: var(--su-acento-contraste); }
.su-dia-fila__nombre {
  font-size: var(--su-texto-sm);
  font-weight: 700;
  color: var(--su-texto-titulo);
  transition: color var(--su-trans-rapida) var(--su-curva);
}
.su-dia-fila:hover .su-dia-fila__nombre { color: var(--su-acento); }
.su-dia-fila__conteo { font-size: var(--su-texto-xs); color: var(--su-texto-suave); }

/* Cabecera de mes en el calendario anual. */
.su-mes-cabecera {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  margin-bottom: var(--su-esp-2);
  padding: 0;
  border: 0;
  background: transparent;
  font-family: inherit;
  cursor: pointer;
}
.su-mes-cabecera__nombre {
  font-size: var(--su-texto-sm);
  font-weight: 800;
  letter-spacing: var(--su-titulo-interletra);
  color: var(--su-texto-titulo);
  transition: color var(--su-trans-rapida) var(--su-curva);
}
.su-mes-cabecera:hover .su-mes-cabecera__nombre { color: var(--su-acento); }

/* Celda de día del calendario de vehículos: una tarjeta pequeña con las
   reservas dentro. */
.su-dia-celda {
  display: block;
  width: 100%;
  padding: var(--su-esp-2);
  border: 1px solid var(--su-borde-sutil);
  border-radius: var(--su-radio-control);
  background: transparent;
  font-family: inherit;
  text-align: left;
  cursor: pointer;
  transition: border-color var(--su-trans-rapida) var(--su-curva),
              background var(--su-trans-rapida) var(--su-curva);
}
.su-dia-celda:hover { border-color: var(--su-borde-fuerte); background: var(--su-fondo-hover); }
.su-dia-celda--hoy { border-color: var(--su-acento); background: var(--su-acento-suave); }
.su-dia-celda__numero {
  font-size: var(--su-texto-xs);
  font-weight: 700;
  font-variant-numeric: var(--su-num-tabular);
  color: var(--su-texto-suave);
  margin-bottom: var(--su-esp-1);
}
.su-dia-celda--hoy .su-dia-celda__numero { color: var(--su-acento); }

/* ============================================================
   v68 · FASE 1 — LOS COMPONENTES QUE FALTABAN
   ============================================================
   Cuatro piezas que el encargo pide y que el sistema no tenía. Se
   construyen con el mismo criterio que las demás: el borde define, la
   sombra insinúa, el color sale de tokens y nada mide menos de lo que
   se puede tocar con el dedo.
   ============================================================ */

/* ------------------------------------------------------------
   v68.1 — LÍNEA DE TIEMPO
   ------------------------------------------------------------
   Para historiales: marcajes, sincronizaciones, cambios de estado. La
   app tiene varios y cada uno los dibujaba a su manera.

   La línea vertical se hace con un pseudo-elemento y NO con un borde en
   cada punto: así el último elemento no arrastra media línea colgando
   por debajo, que es el defecto clásico de esta pieza.
   ------------------------------------------------------------ */
.su-timeline { position: relative; margin: 0; padding: 0; list-style: none; }
.su-timeline__item {
  position: relative;
  padding-left: var(--su-esp-6);
  padding-bottom: var(--su-esp-5);
}
/* La línea va del punto hacia abajo, y el último item no la dibuja. */
.su-timeline__item::before {
  content: '';
  position: absolute;
  left: 7px;
  top: 18px;
  bottom: 0;
  width: 2px;
  background: var(--su-borde);
}
.su-timeline__item:last-child { padding-bottom: 0; }
.su-timeline__item:last-child::before { display: none; }

.su-timeline__punto {
  position: absolute;
  left: 0;
  top: 5px;
  width: 16px;
  height: 16px;
  border-radius: 999px;
  background: var(--su-fondo-elevado);
  border: 3px solid var(--su-borde-fuerte);
  box-sizing: border-box;
}
.su-timeline__item--exito   .su-timeline__punto { border-color: var(--su-exito); }
.su-timeline__item--aviso   .su-timeline__punto { border-color: var(--su-aviso); }
.su-timeline__item--peligro .su-timeline__punto { border-color: var(--su-peligro); }
.su-timeline__item--activo  .su-timeline__punto { border-color: var(--su-acento); background: var(--su-acento); }

.su-timeline__hora {
  font-size: var(--su-texto-2xs);
  font-variant-numeric: var(--su-num-tabular);
  color: var(--su-texto-tenue);
  margin-bottom: calc(2px * var(--su-densidad, 1));
}
.su-timeline__titulo { font-size: var(--su-texto-sm); font-weight: 600; color: var(--su-texto-fuerte); }
.su-timeline__detalle { font-size: var(--su-texto-xs); color: var(--su-texto-suave); margin-top: calc(2px * var(--su-densidad, 1)); }

/* ------------------------------------------------------------
   v68.2 — MIGAS DE PAN
   ------------------------------------------------------------
   Ya existía una en `nav-comun.js`, pero armada con utilidades sueltas.
   Al ser componente, sigue el tema y la escala como todo lo demás.

   El separador es un pseudo-elemento con `aria-hidden` implícito: un
   lector de pantalla no debe leer "mayor que" entre cada nivel.
   ------------------------------------------------------------ */
.su-migas {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--su-esp-1);
  font-size: var(--su-texto-xs);
  color: var(--su-texto-suave);
  margin-bottom: var(--su-esp-4);
}
.su-migas__enlace {
  color: var(--su-texto-suave);
  text-decoration: none;
  padding: calc(2px * var(--su-densidad, 1)) calc(4px * var(--su-densidad, 1));
  border-radius: var(--su-radio-chip);
  transition: background var(--su-trans-rapida) var(--su-curva),
              color var(--su-trans-rapida) var(--su-curva);
}
.su-migas__enlace:hover { color: var(--su-acento); background: var(--su-fondo-hover); }
.su-migas__item + .su-migas__item::before {
  content: '›';
  margin-inline: var(--su-esp-1);
  color: var(--su-texto-tenue);
}
.su-migas__actual { font-weight: 600; color: var(--su-texto-fuerte); }

/* ------------------------------------------------------------
   v68.3 — NOTIFICACIONES (panel, no aviso flotante)
   ------------------------------------------------------------
   Distinto de `.su-aviso`: el aviso flotante confirma una acción que
   acabas de hacer y se va solo. Esto es una BANDEJA — cosas que pasaron
   mientras no mirabas y que siguen ahí hasta que las leas.
   ------------------------------------------------------------ */
.su-notif-panel {
  position: fixed;
  top: var(--su-cabecera-alto);
  right: 0;
  bottom: 0;
  z-index: var(--su-z-lateral);
  width: min(380px, 100vw);
  display: flex;
  flex-direction: column;
  background: var(--su-fondo-elevado);
  border-left: 1px solid var(--su-borde);
  box-shadow: var(--su-sombra-3);
  transform: translateX(100%);
  transition: transform var(--su-trans-normal) var(--su-curva);
}
.su-notif-panel--abierto { transform: translateX(0); }
.su-notif-panel__cabecera {
  display: flex;
  align-items: center;
  gap: var(--su-esp-2);
  padding: var(--su-esp-4);
  border-bottom: 1px solid var(--su-borde);
  font-weight: 600;
  color: var(--su-texto-fuerte);
}
.su-notif-panel__lista { flex: 1; min-height: 0; overflow-y: auto; }

.su-notif {
  display: flex;
  gap: var(--su-esp-3);
  padding: var(--su-esp-3) var(--su-esp-4);
  border-bottom: 1px solid var(--su-borde-sutil);
  transition: background var(--su-trans-rapida) var(--su-curva);
}
.su-notif:hover { background: var(--su-fondo-hover); }
/* Sin leer: una barra a la izquierda, no un fondo de color. El fondo
   teñido en una lista larga cansa y hace que lo leído parezca apagado
   en vez de que lo nuevo parezca nuevo. */
.su-notif--nueva { border-left: 3px solid var(--su-acento); padding-left: calc(var(--su-esp-4) - 3px); }
.su-notif__icono { flex-shrink: 0; margin-top: calc(2px * var(--su-densidad, 1)); color: var(--su-texto-tenue); }
.su-notif--exito   .su-notif__icono { color: var(--su-exito); }
.su-notif--aviso   .su-notif__icono { color: var(--su-aviso); }
.su-notif--peligro .su-notif__icono { color: var(--su-peligro); }
.su-notif__texto { flex: 1; min-width: 0; font-size: var(--su-texto-sm); color: var(--su-texto); }
.su-notif__hora { font-size: var(--su-texto-2xs); color: var(--su-texto-tenue); margin-top: calc(2px * var(--su-densidad, 1)); }

/* Contador sobre la campana. `min-width` en vez de ancho fijo: con 3
   cifras no se recorta. */
.su-notif-contador {
  position: absolute;
  top: 2px;
  right: 2px;
  min-width: 16px;
  height: 16px;
  padding-inline: calc(4px * var(--su-densidad, 1));
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 999px;
  background: var(--su-peligro);
  color: #fff;
  font-size: 10px;
  font-weight: 700;
  font-variant-numeric: var(--su-num-tabular);
  line-height: 1;
}

/* ------------------------------------------------------------
   v68.4 — ESTADO VACÍO CON ACCIÓN
   ------------------------------------------------------------
   `.su-vacio` ya existía pero sólo mostraba un mensaje. Un estado vacío
   bueno responde tres cosas: qué falta, por qué, y qué hacer ahora. Sin
   la tercera es un callejón sin salida.
   ------------------------------------------------------------ */
.su-vacio__accion { margin-top: var(--su-esp-4); }
.su-vacio--tarjeta {
  background: var(--su-fondo-elevado);
  border: 1px dashed var(--su-borde-fuerte);
  border-radius: var(--su-radio-tarjeta);
}

/* ------------------------------------------------------------
   v68.5 — BUSCADOR GLOBAL (paleta de comandos)
   ------------------------------------------------------------
   Se abre con Ctrl+K. Va centrado y arriba, no en medio: al escribir,
   la lista crece hacia abajo y un cuadro centrado se movería con cada
   tecla.
   ------------------------------------------------------------ */
.su-paleta {
  position: fixed;
  inset: 0;
  z-index: var(--su-z-modal);
  display: none;
  align-items: flex-start;
  justify-content: center;
  padding: 12vh var(--su-esp-4) var(--su-esp-4);
  background: var(--su-velo);
}
.su-paleta--abierta { display: flex; animation: su-aparecer var(--su-trans-rapida) var(--su-curva); }
.su-paleta__caja {
  width: 100%;
  max-width: 560px;
  max-height: 60vh;
  display: flex;
  flex-direction: column;
  background: var(--su-fondo-elevado);
  border: 1px solid var(--su-borde);
  border-radius: var(--su-radio-tarjeta);
  box-shadow: var(--su-sombra-3);
  overflow: hidden;
}
.su-paleta__campo {
  width: 100%;
  padding: var(--su-esp-4) var(--su-esp-5);
  border: 0;
  border-bottom: 1px solid var(--su-borde);
  background: transparent;
  font-family: inherit;
  font-size: var(--su-texto-md);
  color: var(--su-texto-fuerte);
}
.su-paleta__campo:focus { outline: none; }
.su-paleta__campo::placeholder { color: var(--su-texto-tenue); }
.su-paleta__lista { flex: 1; min-height: 0; overflow-y: auto; padding: var(--su-esp-2); }
.su-paleta__item {
  display: flex;
  align-items: center;
  gap: var(--su-esp-3);
  width: 100%;
  min-height: var(--su-btn-alto-min);
  padding: var(--su-esp-2) var(--su-esp-3);
  border: 0;
  border-radius: var(--su-radio-control);
  background: transparent;
  font-family: inherit;
  font-size: var(--su-texto-sm);
  color: var(--su-texto);
  text-align: left;
  cursor: pointer;
}
/* El resaltado va por clase, no por :hover: quien navega con flechas
   necesita ver dónde está sin mover el ratón. */
.su-paleta__item--activo { background: var(--su-acento-suave); color: var(--su-acento-fuerte); }
.su-paleta__item__grupo { margin-left: auto; font-size: var(--su-texto-2xs); color: var(--su-texto-tenue); }
.su-paleta__pie {
  display: flex;
  gap: var(--su-esp-4);
  padding: var(--su-esp-2) var(--su-esp-5);
  border-top: 1px solid var(--su-borde-sutil);
  background: var(--su-fondo-cabecera);
  font-size: var(--su-texto-2xs);
  color: var(--su-texto-tenue);
}
.su-tecla {
  display: inline-block;
  min-width: 18px;
  padding: calc(1px * var(--su-densidad, 1)) calc(5px * var(--su-densidad, 1));
  border: 1px solid var(--su-borde);
  border-bottom-width: 2px;
  border-radius: var(--su-radio-chip);
  background: var(--su-fondo-elevado);
  font-family: var(--su-familia);
  font-size: 10px;
  font-weight: 600;
  color: var(--su-texto-suave);
  text-align: center;
}

/* ============================================================
   v68 · FASE 2 — CENTRO DE PERSONALIZACIÓN
   ============================================================
   El CSS que hace efectivas las preferencias nuevas. Todo son atributos
   en <html> o variables: ningún componente cambia.
   ============================================================ */

:root {
  /* Superficies personalizables. Por omisión valen lo del tema; el
     panel las sobrescribe en línea sólo si la persona elige un color. */
  --su-lateral-fondo:  var(--su-fondo);
  --su-cabecera-fondo: var(--su-fondo-elevado);
  --su-tarjeta-fondo:  var(--su-fondo-elevado);
  --su-tarjeta-sombra: var(--su-sombra-1);
  --su-escala-titulos: 1;
  --su-escala-tablas:  1;
}

/* Las superficies leen de las variables nuevas. */
.su-lateral  { background: var(--su-lateral-fondo); }
.su-tarjeta  { background: var(--su-tarjeta-fondo); box-shadow: var(--su-tarjeta-sombra); }
.su-indicador { background: var(--su-tarjeta-fondo); }

/* Tipografía por elemento: los títulos y las tablas se escalan aparte
   del resto. Es lo que pide quien lee bien el cuerpo pero necesita
   títulos más grandes para orientarse, o al revés: tablas más pequeñas
   para que quepan más filas. */
.su-titulo          { font-size: calc(var(--su-texto-xl) * var(--su-escala-titulos)); }
.su-seccion__titulo,
.su-tarjeta__titulo { font-size: calc(var(--su-texto-md) * var(--su-escala-titulos)); }
.su-modal__titulo   { font-size: calc(var(--su-texto-lg) * var(--su-escala-titulos)); }
.su-tabla           { font-size: calc(var(--su-texto-sm) * var(--su-escala-tablas)); }

/* --- Tarjetas --- */
[data-tarjeta-borde='no'] .su-tarjeta,
[data-tarjeta-borde='no'] .su-indicador { border-color: transparent; }
[data-tarjeta-hover='no'] .su-tarjeta--interactiva:hover {
  transform: none;
  box-shadow: var(--su-tarjeta-sombra);
  border-color: var(--su-borde);
}

/* --- Cabecera --- */
[data-cabecera-translucida='no'] .su-cabecera {
  background: var(--su-cabecera-fondo);
  backdrop-filter: none;
}
[data-cabecera-translucida='si'] .su-cabecera {
  background: color-mix(in srgb, var(--su-cabecera-fondo) 88%, transparent);
}

/* --- Tablas --- */
[data-tabla-lineas='ninguna'] .su-tabla tbody td { border-bottom: 0; }
[data-tabla-lineas='completas'] .su-tabla tbody td { border-right: 1px solid var(--su-borde-sutil); }
[data-tabla-lineas='completas'] .su-tabla tbody td:last-child { border-right: 0; }
[data-tabla-rayada='si'] .su-tabla tbody tr:nth-child(even) { background: var(--su-fondo-hundido); }
[data-tabla-hover='no'] .su-tabla tbody tr:hover { background: transparent; }
[data-tabla-cabecera-fija='no'] .su-tabla thead th { position: static; }

/* --- Formularios --- */
[data-campo-tamano='compacto'] .su-campo { min-height: 34px; padding-block: calc(2px * var(--su-densidad, 1)); font-size: var(--su-texto-xs); }
[data-campo-tamano='grande']   .su-campo { min-height: 52px; padding-block: var(--su-esp-3); font-size: var(--su-texto-md); }

/* ------------------------------------------------------------
   ACCESIBILIDAD
   ------------------------------------------------------------ */

/* ALTO CONTRASTE.
   No es "el tema oscuro pero más". Es un modo aparte con un objetivo
   medible: contraste texto/fondo por encima de 7:1, que es el nivel AAA
   de las pautas WCAG. Para llegar ahí hacen falta tres cosas a la vez:

     · Texto casi negro sobre blanco puro (o al revés en oscuro).
     · Bordes más marcados: quien tiene baja visión distingue un
       contenedor por su contorno, no por una sombra de 4 % de opacidad.
     · Cero transparencias. Un velo translúcido baja el contraste de
       todo lo que hay debajo, que es justo lo contrario de lo que se
       está pidiendo.

   Por eso también se desactivan la cabecera translúcida y las sombras:
   en este modo estorban. */
[data-contraste='alto'] {
  --su-texto:        #000000;
  --su-texto-fuerte: #000000;
  --su-texto-suave:  #1f2937;
  --su-texto-tenue:  #374151;
  --su-fondo:            #ffffff;
  --su-fondo-elevado:    #ffffff;
  --su-fondo-hundido:    #f0f0f0;
  --su-borde:            #000000;
  --su-borde-sutil:      #767676;
  --su-borde-fuerte:     #000000;
  --su-acento:           #003F8A;
  --su-acento-fuerte:    #002a5e;
  --su-acento-suave:     #dbe9fb;
  --su-sombra-1: none;
  --su-sombra-2: none;
  --su-sombra-3: 0 0 0 2px #000000;
}
[data-contraste='alto'][data-tema='oscuro'] {
  --su-texto:        #ffffff;
  --su-texto-fuerte: #ffffff;
  --su-texto-suave:  #e5e7eb;
  --su-texto-tenue:  #d1d5db;
  --su-fondo:            #000000;
  --su-fondo-elevado:    #000000;
  --su-fondo-hundido:    #1a1a1a;
  --su-borde:            #ffffff;
  --su-borde-sutil:      #a0a0a0;
  --su-borde-fuerte:     #ffffff;
  --su-acento:           #7fb2ff;
  --su-acento-fuerte:    #a8ccff;
  --su-acento-suave:     #0a2647;
}
[data-contraste='alto'] .su-cabecera { backdrop-filter: none; }
[data-contraste='alto'] .su-tarjeta,
[data-contraste='alto'] .su-indicador,
[data-contraste='alto'] .su-campo,
[data-contraste='alto'] .su-btn { border-width: 2px; }
/* El estado deja de depender de la opacidad: un botón deshabilitado al
   50 % es ilegible para quien necesita este modo. */
[data-contraste='alto'] .su-btn:disabled { opacity: 1; background: var(--su-fondo-hundido); color: var(--su-texto-tenue); border-style: dashed; }

/* MODO DALTÓNICO.
   Se aplica un filtro que SEPARA los tonos que cada tipo confunde. No
   "corrige" el color — eso no existe: lo que hace es desplazar el
   espectro para que dos colores antes indistinguibles dejen de serlo.

   Importante y por eso está escrito aquí: el sistema NUNCA depende sólo
   del color. Todo estado lleva icono o texto al lado (es la razón de
   `.su-punto` y de los iconos en los avisos). Esto es un apoyo, no el
   arreglo — el arreglo ya está en cómo se diseñaron los estados.

   Los `<img>`, los mapas y las fotos se excluyen: alterar una fotografía
   de un carnet o el color real de un mapa sería empeorar el dato. */
[data-daltonismo='protanopia']   body > *:not(script) { filter: url(#su-filtro-protanopia); }
[data-daltonismo='deuteranopia'] body > *:not(script) { filter: url(#su-filtro-deuteranopia); }
[data-daltonismo='tritanopia']   body > *:not(script) { filter: url(#su-filtro-tritanopia); }
[data-daltonismo]:not([data-daltonismo='no']) img,
[data-daltonismo]:not([data-daltonismo='no']) video,
[data-daltonismo]:not([data-daltonismo='no']) canvas,
[data-daltonismo]:not([data-daltonismo='no']) .leaflet-container { filter: none !important; }

/* CURSOR GRANDE. Para pantallas grandes o de alta densidad, donde el
   puntero por omisión se pierde. */
[data-cursor-grande='si'] { cursor: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='48' height='48' viewBox='0 0 24 24'%3E%3Cpath d='M5 2l14 9-6 1.5L16 20l-3 1-3-8-5 3z' fill='%23000' stroke='%23fff' stroke-width='1.5'/%3E%3C/svg%3E") 4 2, auto; }
[data-cursor-grande='si'] a,
[data-cursor-grande='si'] button,
[data-cursor-grande='si'] [role='button'] { cursor: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='48' height='48' viewBox='0 0 24 24'%3E%3Cpath d='M9 3v9l-2-2-2 2 5 8h8l2-8-4-1V3z' fill='%23000' stroke='%23fff' stroke-width='1.5'/%3E%3C/svg%3E") 8 2, pointer; }

/* FOCO REFORZADO. El anillo por omisión (2 px) cumple, pero quien
   navega sólo con teclado agradece uno que se vea desde lejos. */
[data-foco-reforzado='si'] :where(a, button, input, select, textarea, [tabindex]):focus-visible {
  outline: 4px solid var(--su-acento);
  outline-offset: 3px;
  box-shadow: 0 0 0 8px color-mix(in srgb, var(--su-acento) 25%, transparent);
}

/* SUBRAYAR ENLACES. Un enlace que sólo se distingue por el color no
   existe para quien no distingue ese color. Va aparte del modo
   daltónico porque también sirve a quien simplemente prefiere verlos. */
[data-subrayar-enlaces='si'] a:not(.su-btn):not(.su-lateral__enlace):not(.su-migas__enlace) {
  text-decoration: underline;
  text-underline-offset: 2px;
}

/* v68 — Selector de color del Centro de Personalización.
   El `<input type="color">` nativo se ve distinto en cada navegador; se
   le quita el marco propio y se le pone el del sistema para que no
   desentone dentro del panel. */
.su-color {
  width: 44px;
  height: 32px;
  padding: calc(2px * var(--su-densidad, 1));
  border: 1px solid var(--su-borde);
  border-radius: var(--su-radio-control);
  background: var(--su-fondo-elevado);
  cursor: pointer;
}
.su-color::-webkit-color-swatch-wrapper { padding: 0; }
.su-color::-webkit-color-swatch { border: 0; border-radius: var(--su-radio-chip); }
.su-color::-moz-color-swatch { border: 0; border-radius: var(--su-radio-chip); }

/* ------------------------------------------------------------
   v69 — DASHBOARD PERSONALIZABLE
   ------------------------------------------------------------ */

/* La tarjeta fijada. Una línea de acento arriba, no un fondo teñido:
   con dos o tres fijadas, el fondo convertiría medio dashboard en una
   mancha de color y dejaría de significar "ésta primero". */
.su-tarjeta--fijada { box-shadow: inset 0 3px 0 0 var(--su-acento), var(--su-tarjeta-sombra); }

/* La lista de ordenación dentro del panel de preferencias. */
.su-dash-lista { list-style: none; margin: 0; padding: 0; }
.su-dash-fila {
  display: flex;
  align-items: center;
  gap: var(--su-esp-3);
  padding: var(--su-esp-2) var(--su-esp-3);
  border: 1px solid var(--su-borde-sutil);
  border-radius: var(--su-radio-control);
  background: var(--su-fondo-elevado);
}
.su-dash-fila + .su-dash-fila { margin-top: var(--su-esp-2); }
.su-dash-fila__nombre {
  font-size: var(--su-texto-sm);
  font-weight: 600;
  color: var(--su-texto-fuerte);
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Oculta: se atenúa pero SIGUE ahí y sigue siendo pulsable. Quitarla de
   la lista dejaría a la persona sin forma de volver a mostrarla. */
.su-dash-fila--oculta { opacity: 0.55; }
.su-dash-fila--oculta .su-dash-fila__nombre { text-decoration: line-through; }
.su-dash-btn--activo { color: var(--su-acento); }

/* v70 — Widgets del menú principal.
   Dos columnas en pantalla ancha, una en móvil. Van ANTES de la rejilla
   de módulos: son atajos, y un atajo que aparece después de aquello a lo
   que atajas no sirve de nada. */
.su-widgets {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--su-esp-4);
  width: 100%;
  max-width: 56rem;
  margin-bottom: var(--su-esp-6);
}
@media (min-width: 768px) {
  .su-widgets { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* Con un solo bloque ocupa el ancho completo: media tarjeta con medio
     hueco al lado se lee como algo que falta. */
  .su-widgets > :only-child { grid-column: 1 / -1; }
}

/* v70 — Cifras del resumen del día.
   Alineadas a la izquierda y con dígitos de ancho fijo: las tres se
   comparan de un vistazo y no bailan cuando cambian de una a dos cifras. */
.su-resumen-cifras {
  display: flex;
  gap: var(--su-esp-5);
  flex-wrap: wrap;
}
.su-resumen-cifra { display: flex; flex-direction: column; min-width: 0; }
.su-resumen-cifra__n {
  font-size: var(--su-texto-xl);
  font-weight: 800;
  line-height: 1.1;
  letter-spacing: var(--su-titulo-interletra-lg);
  font-variant-numeric: var(--su-num-tabular);
  color: var(--su-texto-titulo);
}
.su-resumen-cifra__t { font-size: var(--su-texto-2xs); color: var(--su-texto-suave); }

/* ============================================================
   v75 — EL ATRIBUTO `hidden` VUELVE A OCULTAR
   ============================================================
   Se encontró primero en el APK, buscándole la causa a un reporte suyo:
   "el botón cerrar no sirve, hay que cerrar la app". El panel tenía la
   misma clase de fallo, y aquí sí se ve a simple vista.

   `hidden` no es una propiedad: es la regla `[hidden] { display: none }`
   de la hoja del NAVEGADOR, y cualquier regla nuestra le gana por
   cascada. En el panel no existía ninguna regla global que la
   restituyera — sólo una acotada a `.oc-panel`.

   LO QUE ESTABA ROTO
   ------------------
   · `operaciones-campo.html` tiene tres pestañas (Marcajes,
     Carnetización, Jornadas Médicas) y un botón "Actualizar" por cada
     una, de los que sólo debe verse el de la pestaña activa. El JS los
     apaga con `el.hidden = (t !== tab)`, pero `.su-btn` declara
     `display:inline-flex` y ganaba: LOS TRES se veían a la vez. Los
     `<span>` de estado de al lado sí se ocultaban —no llevan `.su-btn`—
     así que el resultado era un estado y tres botones idénticos.
   · En la consola de administración de GPS, `#consolaBadge`,
     `.status-pill` y `.badge-count`: el contador se apaga con
     `badge.hidden = total === 0`, así que enseñaba un "0" permanente en
     vez de desaparecer.

   Va en ui-sistema.css porque es la única hoja que cargan las 31
   pantallas del panel Y las de monitoreo-gps. El `!important` es
   justamente el punto: hay que ganarle a las reglas que declaran
   `display`. Se comprobó antes que ningún archivo muestre con
   `style.display` un elemento que lleve el ATRIBUTO `hidden` — los 60
   sitios del código lo hacen con `.hidden`.

   OJO al leer esto: `hidden` es también una clase de Tailwind, que aquí
   se usa mucho. Esta regla NO la toca; sólo el atributo.
   ============================================================ */
[hidden] { display: none !important; }

/* ============================================================
   v76 — AVISO DE PRIORIDAD URGENTE (pantalla de Comunicados)
   ============================================================
   Desde el APK v1.70, "Urgente" dejó de ser una etiqueta de color: tapa
   la pantalla del ejecutivo hasta que confirme la lectura. Cambiar la
   consecuencia de un control sin decírselo a quien lo opera es la mejor
   forma de que alguien bloquee a cuarenta personas sin querer.

   El filo, no el fondo: un bloque entero teñido se lee como "error del
   sistema", y esto es una advertencia sobre lo que va a pasar.
   ============================================================ */
.com-aviso-urgente {
  margin: calc(8px * var(--su-densidad, 1)) 0 0;
  padding: calc(10px * var(--su-densidad, 1)) calc(12px * var(--su-densidad, 1));
  border-left: 3px solid var(--su-aviso, var(--su-acento));
  border-radius: var(--su-radio-chip);
  background: var(--su-fondo-hundido);
  font-size: var(--su-texto-2xs);
  line-height: 1.5;
  color: var(--su-texto-suave);
}

/* ============================================================
   v123 — CALENDARIO DE FECHA
   ============================================================

   Sustituye al <select> de un día por línea en el filtro de Fecha de
   Planificación Canales Alternos. Ver el encabezado de
   `calendario-fecha.js` para el porqué.

   Todo va por token. No es sólo la regla de la casa: sin tokens, el tema
   oscuro y el modo de alto contraste dejan de funcionar en esta rejilla —
   y una rejilla de 31 celdas donde no se distingue la elegida no sirve
   para nada. Hay pruebas que lo vigilan.

   El espaciado se escribe `calc(Npx * var(--su-densidad, 1))`. El `, 1`
   no es opcional: sin él, una densidad indefinida haría inválida la
   declaración entera y el relleno se iría a cero.
   ============================================================ */

.su-cal {
  width: calc(268px * var(--su-densidad, 1));
  font-family: var(--su-familia);
  user-select: none;
}

.su-cal__barra {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: calc(8px * var(--su-densidad, 1));
}

.su-cal__mes {
  font-size: var(--su-texto-sm);
  font-weight: 700;
  color: var(--su-texto-titulo);
}

.su-cal__nav {
  width: calc(26px * var(--su-densidad, 1));
  height: calc(26px * var(--su-densidad, 1));
  border: 1px solid var(--su-borde);
  border-radius: var(--su-radio-control);
  background: var(--su-fondo-elevado);
  color: var(--su-texto-suave);
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
}
.su-cal__nav:hover { background: var(--su-acento-suave); color: var(--su-acento-fuerte); }
.su-cal__nav:focus-visible { outline: 2px solid var(--su-acento); outline-offset: 1px; }

.su-cal__cabecera,
.su-cal__rejilla {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: calc(2px * var(--su-densidad, 1));
}

.su-cal__cabecera {
  margin-bottom: calc(4px * var(--su-densidad, 1));
}
.su-cal__cabecera > span {
  text-align: center;
  font-size: var(--su-texto-2xs);
  font-weight: 700;
  color: var(--su-texto-tenue);
  text-transform: uppercase;
}

.su-cal__hueco { display: block; }

/* La celda es un botón cuadrado. El punto de actividad va debajo del
   número, no al lado: al lado desplazaba el número y las columnas dejaban
   de alinearse en cuanto un día tenía actividades y el de al lado no. */
.su-cal__dia {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  aspect-ratio: 1;
  min-height: calc(32px * var(--su-densidad, 1));
  border: 1px solid transparent;
  border-radius: var(--su-radio-control);
  background: transparent;
  font-size: var(--su-texto-xs);
  font-weight: 600;
  color: var(--su-texto);
  cursor: pointer;
  transition: background .12s, color .12s, border-color .12s;
}
.su-cal__dia:hover { background: var(--su-acento-suave); }
.su-cal__dia:focus-visible { outline: 2px solid var(--su-acento); outline-offset: 1px; }

/* Un día sin actividades NO está deshabilitado: se puede elegir, y
   entonces la pantalla contesta "no hay actividades ese día". Con el
   <select> viejo esa pregunta no se podía ni hacer, porque el día no
   existía en la lista. Se atenúa, no se bloquea. */
.su-cal__dia--vacio { color: var(--su-texto-tenue); font-weight: 500; }

.su-cal__dia--con-datos { color: var(--su-texto-fuerte); }

/* HOY se marca con borde, no con relleno: el relleno es del día ELEGIDO,
   y si los dos usaran relleno no se distinguiría "el día que estoy
   mirando" de "el día que es". */
.su-cal__dia--hoy { border-color: var(--su-acento); }

.su-cal__dia--elegido {
  background: var(--su-acento);
  color: var(--su-acento-contraste);
  border-color: var(--su-acento);
}
.su-cal__dia--elegido:hover { background: var(--su-acento-fuerte); }

.su-cal__punto {
  position: absolute;
  bottom: calc(3px * var(--su-densidad, 1));
  width: calc(4px * var(--su-densidad, 1));
  height: calc(4px * var(--su-densidad, 1));
  border-radius: 50%;
  background: var(--su-exito);
}
/* Sobre el relleno del día elegido, el verde no se ve. Se pinta del color
   de contraste del acento, que es el mismo que el número. */
.su-cal__dia--elegido .su-cal__punto { background: var(--su-acento-contraste); }

.su-cal__pie {
  display: flex;
  gap: calc(6px * var(--su-densidad, 1));
  margin-top: calc(8px * var(--su-densidad, 1));
  padding-top: calc(8px * var(--su-densidad, 1));
  border-top: 1px solid var(--su-borde-sutil);
}

.su-cal__accion {
  flex: 1;
  padding: calc(5px * var(--su-densidad, 1)) calc(8px * var(--su-densidad, 1));
  border: 1px solid var(--su-borde);
  border-radius: var(--su-radio-control);
  background: var(--su-fondo-elevado);
  font-size: var(--su-texto-2xs);
  font-weight: 600;
  color: var(--su-acento);
  cursor: pointer;
}
.su-cal__accion:hover { background: var(--su-acento-suave); }
.su-cal__accion:focus-visible { outline: 2px solid var(--su-acento); outline-offset: 1px; }
.su-cal__accion--activa {
  background: var(--su-acento);
  border-color: var(--su-acento);
  color: var(--su-acento-contraste);
}

/* v124 — La ayuda del rango, al pie de la rejilla. Va en pequeño y en
   gris: es una pista, no una instrucción que haya que leer para poder
   usar el calendario. */
.su-cal__ayuda {
  margin-top: calc(6px * var(--su-densidad, 1));
  font-size: var(--su-texto-2xs);
  color: var(--su-texto-tenue);
  text-align: center;
}
.su-cal__ayuda kbd {
  padding: 0 calc(4px * var(--su-densidad, 1));
  border: 1px solid var(--su-borde);
  border-radius: var(--su-radio-micro);
  background: var(--su-fondo-hundido);
  font-family: var(--su-familia-mono);
  font-size: .9em;
}

/* El disparador: el botón que enseña la fecha elegida y abre la rejilla.

   Es un `.su-btn.su-btn--secundario` — no una pieza aparte. La regla de la
   casa dice que todo <button> usa un componente del sistema, y ésta no es
   la excepción que la rompe: abrir un selector es una acción como
   cualquier otra. Esta clase sólo añade lo que un botón normal no puede
   saber: que el texto de dentro es variable (una fecha larga), que por eso
   necesita un ancho mínimo para no bailar al cambiar de día, y que se
   alinea a la izquierda como un campo, no centrado como una acción. */
.su-cal-disparador {
  min-width: calc(180px * var(--su-densidad, 1));
  text-align: left;
  color: var(--su-texto-fuerte);
}
.su-cal-disparador__texto { flex: 1; }

.su-cal-popover {
  position: absolute;
  z-index: 30;
  top: calc(100% + 4px);
  left: 0;
  padding: calc(12px * var(--su-densidad, 1));
  border: 1px solid var(--su-borde);
  border-radius: var(--su-radio-tarjeta);
  background: var(--su-fondo-elevado);
  box-shadow: 0 10px 30px rgba(14, 20, 32, .12);
}

/* En teléfono la rejilla ocupa el ancho disponible: 268px fijos obligaban
   a desplazar horizontalmente dentro de un popover, que es la peor forma
   de mirar un mes. */
@media (max-width: 480px) {
  .su-cal { width: 100%; }
  .su-cal-popover { left: 0; right: 0; }
  .su-cal-disparador { width: 100%; }
}

/* ============================================================
   v123 — EDITOR DEL REPORTE EJECUTIVO (Versión 2)

   Sólo el panel: el reporte en sí se dibuja con estilos en línea, a
   propósito. html2canvas clona el documento y resuelve los estilos ya
   computados; con estilos en línea no hay ninguna hoja que pueda faltar
   ni ninguna cascada que se aplique distinto dentro del clon.

   Aquí se estila el editor, que sí vive en la pantalla y sí debe
   respetar el tema, la densidad y el alto contraste como todo lo demás.
   ============================================================ */

.rv2-fondo {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: calc(16px * var(--su-densidad, 1));
  background: rgba(14, 20, 32, .55);
}

.rv2-caja {
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: 1320px;
  height: 100%;
  max-height: 94vh;
  border-radius: var(--su-radio-tarjeta);
  background: var(--su-fondo);
  box-shadow: 0 24px 60px rgba(14, 20, 32, .28);
  overflow: hidden;
}

.rv2-cabecera,
.rv2-pie {
  display: flex;
  align-items: center;
  gap: calc(10px * var(--su-densidad, 1));
  padding: calc(14px * var(--su-densidad, 1)) calc(18px * var(--su-densidad, 1));
  background: var(--su-fondo-elevado);
}
.rv2-cabecera { border-bottom: 1px solid var(--su-borde); }
.rv2-pie { border-top: 1px solid var(--su-borde); flex-wrap: wrap; }

.rv2-titulo {
  font-size: var(--su-texto-lg);
  font-weight: 800;
  color: var(--su-texto-titulo);
}
.rv2-sub {
  font-size: var(--su-texto-2xs);
  color: var(--su-texto-suave);
  margin-top: calc(2px * var(--su-densidad, 1));
}

/* Dos columnas: controles a la izquierda, previsualización a la derecha.
   No es una rejilla por capricho — la previsualización tiene que estar A
   LA VISTA mientras se toca un control. Un editor donde hay que
   desplazarse para ver el efecto es el ciclo lento que este apartado
   viene a eliminar, sólo que dentro de la misma pantalla. */
.rv2-cuerpo {
  display: flex;
  flex: 1;
  min-height: 0;
}

.rv2-controles {
  width: calc(360px * var(--su-densidad, 1));
  flex-shrink: 0;
  padding: calc(14px * var(--su-densidad, 1));
  overflow-y: auto;
  border-right: 1px solid var(--su-borde);
  background: var(--su-fondo);
}

.rv2-vista {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  background: var(--su-fondo-hundido);
}

.rv2-vista-barra {
  display: flex;
  align-items: center;
  gap: calc(8px * var(--su-densidad, 1));
  padding: calc(8px * var(--su-densidad, 1)) calc(14px * var(--su-densidad, 1));
  border-bottom: 1px solid var(--su-borde);
  background: var(--su-fondo-elevado);
}

.rv2-lienzo {
  flex: 1;
  overflow: auto;
  padding: calc(12px * var(--su-densidad, 1));
}

/* En teléfono las dos columnas se apilan y la previsualización va arriba:
   es lo que se vino a mirar. */
@media (max-width: 900px) {
  .rv2-cuerpo { flex-direction: column-reverse; }
  .rv2-controles {
    width: 100%;
    border-right: none;
    border-top: 1px solid var(--su-borde);
    max-height: 45vh;
  }
  .rv2-vista { min-height: 40vh; }
}

/* La casilla de color y su valor en texto. La casilla sola no deja pegar
   un color corporativo exacto, que es justo lo que hace falta para que el
   reporte case con la marca; el texto solo no deja tantear. Van juntos. */
.rv2-color {
  width: calc(38px * var(--su-densidad, 1));
  height: calc(30px * var(--su-densidad, 1));
  border: 1px solid var(--su-borde);
  border-radius: var(--su-radio-chip);
  padding: 0;
  background: none;
  cursor: pointer;
}
/* El valor hexadecimal se lee mejor con ancho fijo: `#0b1f3a` y `#0d9488`
   se comparan de un vistazo cuando los dígitos se alinean. */
.rv2-hex {
  flex: 1;
  font-family: var(--su-familia-mono);
}

/* ============================================================
   v124 — CENTRO DE CONSULTA Y REPORTERÍA

   Los cuatro niveles de la barra de filtros de Planificación. Ver el
   encabezado de `centro-filtros.js` para qué hace cada uno y por qué el
   bloque clásico se oculta en vez de borrarse.

   Todo por token, como el resto: sin ellos, el tema oscuro y el alto
   contraste dejan de funcionar. El espaciado lleva
   `calc(Npx * var(--su-densidad, 1))`; el `, 1` no es opcional — sin él
   una densidad indefinida haría inválida la declaración y el relleno se
   iría a cero.
   ============================================================ */

/* El bloque de filtros anterior. No se borra: quince partes de vista.js
   leen sus ids. Se oculta de forma accesible — `display:none` lo sacaría
   del árbol y los controles dejarían de poder recibir valor. */
.cf-oculto {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

/* v138 — EL CENTRO DE FILTROS MANDA SOBRE LO QUE VIENE DESPUÉS.

   EL FALLO: "filtros avanzados no sirve". El panel SÍ se abría y SÍ estaba
   bien cableado — se veía en pantalla, encima del contenido. Lo que pasaba
   es que la barra de vistas (Por Día / Por Ejecutivo / ...) y la fila de
   chips se pintaban ENCIMA de él, así que la mitad del panel quedaba
   tapada y lo que se podía tocar era el botón de detrás.

   La causa es sutil y no se ve leyendo el marcado: esos bloques llevan
   `.anim-fade-in`, que era una `animation` con `both`. Una animación que
   desplaza crea un CONTEXTO DE APILADO propio y —con `both`— lo conserva al
   terminar. Dos hermanos con contexto propio y sin z-index se ordenan por
   posición en el documento — y los de abajo ganan. El `z-30` del panel no
   servía de nada porque competía dentro de su propio contexto, no contra
   ellos.

   v158 — ESA CAUSA YA NO EXISTE: `.anim-fade-in` pasó a `backwards`, así que
   al terminar no queda ningún `transform` y no queda ningún contexto de
   apilado de más (ver la nota junto a `.su-anim-entrar`). Este z-index se
   QUEDA de todos modos, por dos motivos: durante los 220 ms de la animación
   el contexto sí existe, y el orden de las capas de esta pantalla tiene que
   estar declarado en un sitio aunque nadie lo esté peleando. El mapa de
   abajo sigue siendo la referencia.

   Por eso el z-index va AQUÍ, en el contenedor del centro de filtros, y no
   subiéndole el número al panel: lo que tiene que quedar por encima es
   todo el bloque de filtros, incluidos el menú de fechas y cualquier
   desplegable que se le añada después. Subírselo sólo al panel habría
   arreglado este caso y dejado el siguiente igual de roto.

   Este es el mismo susto que ya se llevó "Filtros avanzados" cuando se
   abría dentro de una caja de 1×1 píxel: bien cableado, invisible, y
   reportado como "no funciona". */
/* v156 — Y POR DEBAJO DE LOS MODALES. La otra mitad de la regla.

   EL FALLO, reportado por Carlos con el modal de Opciones abierto: "está
   mal". Y lo estaba: la barra de filtros se pintaba ENCIMA del modal y lo
   partía por la mitad. Se veía el título y el primer bloque, luego la barra
   tapando el centro, y más abajo el resto del modal asomando.

   La causa es este mismo z-index. v138 lo puso en 60 para que el bloque de
   filtros quedara por encima de la barra de vistas y los chips — correcto —
   pero **los modales del sistema son `z-40`** (la clase `z-40` de Tailwind
   que llevan `#modal-opciones`, `#modal-detalle` y `#modal-popup`). Con 60,
   la barra tapaba a los tres. No era un fallo del modal de Opciones: era de
   todos los modales de esta pantalla, y llevaba ahí desde v138.

   60 era además innecesario. Lo que había que ganar eran hermanos con
   `.anim-fade-in`, que crean contexto de apilado pero se quedan en
   `z-index: auto`; contra ésos gana CUALQUIER z-index positivo, porque los
   positivos se pintan en una capa posterior. El número grande no aportaba
   nada y sí colisionaba.

   LAS CAPAS, para que no haya que volver a adivinar:

       10  botones flotantes
       20  el centro de filtros  ← aquí
       40  los modales del sistema
       60  los diálogos propios del centro (`.cf-modal-fondo`)

   El panel de "Filtros avanzados" no entra en esta cuenta: se muda DENTRO
   de `#centro-filtros`, así que su `z-index: 70` compite sólo con el menú
   de fechas, su único hermano. */
#centro-filtros {
  position: relative;
  z-index: 20;
}

.su-centro-filtros {
  display: flex;
  flex-direction: column;
  gap: calc(10px * var(--su-densidad, 1));
  padding: calc(14px * var(--su-densidad, 1));
  border: 1px solid var(--su-borde);
  border-radius: var(--su-radio-tarjeta);
  background: var(--su-fondo-elevado);
}

.cf-nivel {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: calc(8px * var(--su-densidad, 1));
}

/* Nivel 1: los campos crecen para repartirse el ancho, pero nunca por
   debajo de lo que hace falta para leer un municipio largo. */
.cf-nivel--1 .cf-campo { flex: 1 1 calc(150px * var(--su-densidad, 1)); min-width: calc(130px * var(--su-densidad, 1)); }
.cf-nivel--1 .cf-campo--fecha { flex: 1 1 calc(210px * var(--su-densidad, 1)); }
.cf-nivel--1 .cf-campo--buscar { flex: 2 1 calc(260px * var(--su-densidad, 1)); }

.cf-campo { display: flex; flex-direction: column; gap: calc(3px * var(--su-densidad, 1)); }

.cf-etiqueta {
  font-size: var(--su-texto-2xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--su-texto-tenue);
}
.cf-etiqueta--inline { align-self: center; margin-right: calc(4px * var(--su-densidad, 1)); }

.cf-nivel--2 { border-top: 1px solid var(--su-borde-sutil); padding-top: calc(9px * var(--su-densidad, 1)); }
.cf-nivel--3 { border-top: 1px solid var(--su-borde-sutil); padding-top: calc(9px * var(--su-densidad, 1)); align-items: center; }

/* Los atajos son pastillas, no botones: dicen "esto se enciende y se
   apaga", que es justamente lo que hacen. Se distinguen a propósito de
   los botones de acción del nivel 4. */
.cf-rapido {
  padding: calc(4px * var(--su-densidad, 1)) calc(11px * var(--su-densidad, 1));
  border: 1px solid var(--su-borde);
  border-radius: 999px;
  background: var(--su-fondo);
  font-size: var(--su-texto-2xs);
  font-weight: 600;
  color: var(--su-texto-suave);
  cursor: pointer;
  transition: background .12s, color .12s, border-color .12s;
}
.cf-rapido:hover { border-color: var(--su-acento); color: var(--su-acento-fuerte); }
.cf-rapido:focus-visible { outline: 2px solid var(--su-acento); outline-offset: 1px; }
.cf-rapido--activo {
  background: var(--su-acento);
  border-color: var(--su-acento);
  color: var(--su-acento-contraste);
}

.cf-disparador { width: 100%; justify-content: flex-start; text-align: left; }
.cf-disparador__texto { flex: 1; }

.cf-menu-caja { position: relative; }
.cf-menu {
  position: absolute;
  z-index: 40;
  top: calc(100% + 4px);
  left: 0;
  width: calc(240px * var(--su-densidad, 1));
  max-height: calc(360px * var(--su-densidad, 1));
  overflow-y: auto;
  padding: calc(6px * var(--su-densidad, 1));
  border: 1px solid var(--su-borde);
  border-radius: var(--su-radio-tarjeta);
  background: var(--su-fondo-elevado);
  box-shadow: 0 10px 30px rgba(14, 20, 32, .14);
}

.cf-menu-grupo {
  padding: calc(6px * var(--su-densidad, 1)) calc(8px * var(--su-densidad, 1)) calc(2px * var(--su-densidad, 1));
  font-size: var(--su-texto-2xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--su-texto-tenue);
}

.cf-menu-op {
  display: block;
  width: 100%;
  padding: calc(6px * var(--su-densidad, 1)) calc(8px * var(--su-densidad, 1));
  border: none;
  border-radius: var(--su-radio-control);
  background: none;
  font-size: var(--su-texto-sm);
  color: var(--su-texto);
  text-align: left;
  cursor: pointer;
}
.cf-menu-op:hover { background: var(--su-acento-suave); color: var(--su-acento-fuerte); }
.cf-menu-op:focus-visible { outline: 2px solid var(--su-acento); outline-offset: -2px; }
.cf-menu-op--activa { background: var(--su-acento); color: var(--su-acento-contraste); font-weight: 700; }

.cf-menu-fechas {
  display: flex;
  gap: calc(6px * var(--su-densidad, 1));
  padding: calc(4px * var(--su-densidad, 1)) calc(8px * var(--su-densidad, 1)) calc(8px * var(--su-densidad, 1));
}
.cf-menu-fechas input { flex: 1; min-width: 0; }

.cf-separador { flex: 1; }

.cf-resumen {
  font-size: var(--su-texto-sm);
  color: var(--su-texto-suave);
  font-variant-numeric: var(--su-num-tabular);
}
.cf-resumen strong { color: var(--su-texto-fuerte); }

/* ---------- Los dos diálogos ---------- */
.cf-modal-fondo {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: calc(16px * var(--su-densidad, 1));
  background: rgba(14, 20, 32, .55);
}

.cf-modal {
  width: 100%;
  max-width: calc(520px * var(--su-densidad, 1));
  max-height: 88vh;
  overflow-y: auto;
  padding: calc(18px * var(--su-densidad, 1));
  border-radius: var(--su-radio-tarjeta);
  background: var(--su-fondo-elevado);
  box-shadow: 0 24px 60px rgba(14, 20, 32, .28);
}

.cf-modal-cab {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: calc(10px * var(--su-densidad, 1));
  margin-bottom: calc(6px * var(--su-densidad, 1));
}
.cf-modal-titulo { font-size: var(--su-texto-lg); font-weight: 800; color: var(--su-texto-titulo); }

.cf-guardados-lista {
  display: flex;
  flex-direction: column;
  gap: calc(4px * var(--su-densidad, 1));
  margin: calc(10px * var(--su-densidad, 1)) 0;
}

.cf-guardado {
  display: flex;
  align-items: center;
  gap: calc(6px * var(--su-densidad, 1));
  padding: calc(4px * var(--su-densidad, 1));
  border: 1px solid var(--su-borde-sutil);
  border-radius: var(--su-radio-control);
}
.cf-guardado-nombre {
  flex: 1;
  padding: calc(4px * var(--su-densidad, 1)) calc(6px * var(--su-densidad, 1));
  border: none;
  background: none;
  font-size: var(--su-texto-sm);
  font-weight: 600;
  color: var(--su-acento-fuerte);
  text-align: left;
  cursor: pointer;
}
.cf-guardado-nombre:hover { text-decoration: underline; }

.cf-guardar-nuevo {
  display: flex;
  gap: calc(6px * var(--su-densidad, 1));
  margin-top: calc(10px * var(--su-densidad, 1));
  padding-top: calc(10px * var(--su-densidad, 1));
  border-top: 1px solid var(--su-borde-sutil);
}
.cf-guardar-nuevo input { flex: 1; min-width: 0; }

.cf-estados, .cf-formatos {
  display: flex;
  flex-wrap: wrap;
  gap: calc(8px * var(--su-densidad, 1));
  margin-top: calc(4px * var(--su-densidad, 1));
}
.cf-chk {
  display: inline-flex;
  align-items: center;
  gap: calc(5px * var(--su-densidad, 1));
  font-size: var(--su-texto-sm);
  color: var(--su-texto);
  cursor: pointer;
}

/* En teléfono, el nivel 1 se apila y los atajos se desplazan en
   horizontal: doce pastillas apiladas ocuparían la pantalla entera antes
   de llegar a las tarjetas, que es lo que se vino a ver. */
@media (max-width: 720px) {
  .cf-nivel--1 .cf-campo,
  .cf-nivel--1 .cf-campo--fecha,
  .cf-nivel--1 .cf-campo--buscar { flex: 1 1 100%; }
  .cf-nivel--2 { flex-wrap: nowrap; overflow-x: auto; padding-bottom: calc(4px * var(--su-densidad, 1)); }
  .cf-nivel--2 .cf-rapido { flex: 0 0 auto; }
  .cf-menu { width: calc(100vw - 48px); }
}

/* ==================================================================
   v160 — MOVER LAS TARJETAS DE LA PLANIFICACIÓN
   ==================================================================

   Carlos: "me gustaría una opción para mover los cuadritos de información,
   porque hoy se acoplan según se va creando la visita pero no hay forma de
   moverlos."

   Arrastrar ya existía desde v58 y se queda. Lo que faltaba era que se
   VIERA y que funcionara con el dedo: el arrastre nativo de HTML5 no
   funciona en táctil —lo dice el propio comentario de v58— y la única
   pista de que se podía arrastrar era el cursor del ratón, que en una
   tableta no existe.

   DÓNDE SE VEN Y DÓNDE NO. Sólo dentro de `[data-zona-orden]`, que es la
   vista de Días — la única que guarda un orden. En Ejecutivos, Semana o
   Lista un botón de mover no tendría dónde guardar lo movido, y un botón
   que no cumple lo que promete es peor que no tenerlo.

   POR QUÉ NO SÓLO AL PASAR EL RATÓN. Porque en táctil no hay "pasar el
   ratón": el control se quedaría invisible justo en los dispositivos donde
   el arrastre tampoco funciona. Se ven siempre, discretos, y ganan
   contraste al acercarse.
   ------------------------------------------------------------------ */
.card-mover {
  position: absolute;
  top: calc(6px * var(--su-densidad, 1));
  left: calc(6px * var(--su-densidad, 1));
  z-index: 3;
  display: flex;
  gap: calc(2px * var(--su-densidad, 1));
  opacity: 0.35;
  transition: opacity var(--su-trans-rapida) var(--su-curva);
}
/* Fuera de la vista de Días no hay orden que guardar, así que no se
   ofrecen: escondidos por defecto, visibles sólo dentro de una zona. */
.card-actividad .card-mover { display: none; }
[data-zona-orden] .card-actividad .card-mover { display: flex; }

.card-actividad:hover .card-mover,
.card-mover:focus-within { opacity: 1; }

.card-mover-btn {
  /* 28 px es lo más pequeño que se puede tocar con comodidad sin comerse la
     esquina de la tarjeta; el área táctil se amplía con el padding. */
  width: calc(28px * var(--su-densidad, 1));
  height: calc(28px * var(--su-densidad, 1));
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--su-borde);
  border-radius: var(--su-radio-control);
  background: var(--su-fondo-elevado);
  color: var(--su-texto-suave);
  cursor: pointer;
  padding: 0;
  line-height: 0;
}
.card-mover-btn:hover {
  background: var(--su-acento);
  border-color: var(--su-acento);
  color: var(--su-acento-contraste);
}
.card-mover-btn svg { width: 15px; height: 15px; }

/* En la imagen exportada no van: son un control, no información. */
[data-reporte-v2] .card-mover,
.exportando .card-mover { display: none !important; }

/* ------------------------------------------------------------------
   v160 — Dos variantes de `.su-campo` para el panel del mapa.

   El sistema pide que TODO campo lleve `.su-campo` (lo vigila
   redisenoUiFase2). Estos dos no quieren el 100 % de ancho que trae por
   defecto: un número de dos cifras y un botón de color no se leen mejor
   por ocupar la fila entera, y al lado de su etiqueta quedan raros.
   ------------------------------------------------------------------ */
.su-campo--angosto { width: calc(72px * var(--su-densidad, 1)); }
.su-campo--color {
  width: calc(44px * var(--su-densidad, 1));
  min-height: calc(30px * var(--su-densidad, 1));
  padding: calc(2px * var(--su-densidad, 1));
  cursor: pointer;
}

/* ------------------------------------------------------------------
   v179 — LAS AGENCIAS DE UN CANAL
   ------------------------------------------------------------------
   Una fila por agencia: el nombre y su municipio a la izquierda, las
   acciones a la derecha. En pantalla estrecha las acciones bajan solas —
   son tres botones y en 390 px no caben al lado del nombre.
   ------------------------------------------------------------------ */
.ag-fila {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--su-esp-2);
  padding: var(--su-esp-2) var(--su-esp-3);
  border: 1px solid var(--su-borde);
  border-radius: var(--su-radio-tarjeta);
  background: var(--su-fondo-elevado);
}
/* Dada de baja: se apaga, no se esconde. Una agencia con visitas
   registradas tiene que seguir viéndose para poder reactivarla. */
.ag-fila--baja { opacity: .62; }
.ag-fila--editando { background: var(--su-fondo-hundido); }
