/* =====================================================================
   MOVQRA
   Hoja de estilos unica - mobile first
   Etapa 1 del rediseno: sistema de tokens.

   Reglas de este archivo:
   1. Ningun color, medida, radio o sombra se escribe a mano fuera del
      bloque :root. Si necesitas un valor nuevo, se agrega como token.
   2. Los nombres de clase de la version anterior se conservan todos.
   3. Los alias de variables viejas (--azul, --texto-2, --borde...) ya
      no existen: en la Etapa 5 se eliminaron los 551 style="..." que
      los usaban. Si algo se ve sin color, busca una variable con el
      nombre viejo.
   ===================================================================== */

/* =====================================================================
   1. TOKENS
   ===================================================================== */

:root {

  /* ---------- Marca ---------- */
  /* Se conserva el azul actual: ya vive en manifest.json y en el
     theme-color de layout.php. Cambiarlo tiraria trabajo hecho. */
  /* Los controles nativos del navegador -barra de scroll, selector de
     fecha, autofill- siguen a color-scheme, no al CSS. Sin esto el tema
     oscuro sale con una barra de scroll blanca. */
  color-scheme: light;

  --brand-600: #1d4ed8;
  --brand-700: #1e3a8a;
  --brand-50:  #eff6ff;

  /* Fondo del panel de marca de las pantallas de acceso. Es el UNICO
     sitio del sistema con letra blanca fija sobre color de marca a
     pantalla completa, y por eso no puede tirar de --brand-700: ese
     token se ACLARA en el tema oscuro -que es lo correcto para un chip
     o un enlace activo- y dejaba blanco sobre #60a5fa, o sea 2.54:1.
     Aqui el fondo tiene que oscurecerse en oscuro, no aclararse.
     Claro: blanco sobre #1e3a8a = 10.36:1. */
  --acceso-marca-bg: #1e3a8a;


  /* Acento: unicamente dato positivo y confirmacion. Nunca accion.
     Existe para que "esto va bien" y "haz clic aqui" dejen de ser
     el mismo azul compitiendo entre si. */
  --accent-600: #0d9488;
  --accent-50:  #f0fdfa;

  /* ---------- Superficies ---------- */
  /* Grises ligeramente calidos, no el gris azulado de plantilla.
     Es lo que ya le daba caracter propio al sistema; se conserva. */
  --bg:        #f7f7f6;
  --surface:   #ffffff;
  --surface-2: #faf9f6;
  --border:    #e5e3dd;
  --border-2:  #d8d5cd;

  /* ---------- Texto ---------- */
  --text:   #1c1c1a;
  --text-2: #6b6b66;
  --text-3: #9a9a94;
  --text-inv: #ffffff;

  /* ---------- Boton primario ----------
     El primario es el UNICO componente que obliga a texto blanco, y por
     eso tiene tokens propios en vez de tirar de --text-inv. --text-inv
     significa "lo que se lee encima de un color solido" y en oscuro es
     casi negro, porque ahi los colores de marca y de estado son claros;
     eso vale para el chip, para la insignia y para el enlace activo del
     menu, pero deja el boton fuerte con letra oscura en un tema y
     blanca en el otro. Aqui el color del texto es FIJO y lo que se
     mueve es el fondo, que es lo que tiene que ceder para que el par
     cumpla AA en los dos temas.

     Claro: #ffffff sobre #1d4ed8 = 6.70:1 (hover #1e3a8a = 10.36:1).
     No cambia nada: es exactamente el par que ya se pintaba. */
  --btn-primario-bg:       var(--brand-600);
  --btn-primario-bg-hover: var(--brand-700);
  --btn-primario-texto:    #ffffff;

  /* ---------- Estados ---------- */
  /* La escala sigue a stock_status() de includes/functions.php:
     normal -> critico -> bajo -> agotado, de menos a mas grave. */
  --ok:     #15803d;  --ok-bg:    #dcfce7;   /* normal, exito           */
  --warn:   #b45309;  --warn-bg:  #fef3c7;   /* critico: va a caer      */
  --crit:   #c2410c;  --crit-bg:  #ffedd5;   /* bajo: cruzo el minimo   */
  --error:  #b91c1c;  --error-bg: #fee2e2;   /* agotado, error          */
  --info:   #1d4ed8;  --info-bg:  #eff6ff;

  /* ---------- Espaciado ---------- */
  /* OLA 4: --s0 es el escalon que faltaba por abajo. 2px aparecia 105
     veces escrito a mano y no es un numero suelto: es LA separacion
     minima, la que separa dos renglones de la misma idea -un valor de
     su unidad, un nombre de su codigo- sin que lleguen a ser dos cosas.
     Se adopta donde 2px va solo (59 sitios); en un atajo compuesto
     -padding: 2px 6px- se queda literal, porque mezclar token y numero
     en la misma declaracion se lee peor que dejar los dos numeros. */
  --s0: 2px;
  --s1: 4px;  --s2: 8px;  --s3: 12px; --s4: 16px;
  --s5: 24px; --s6: 32px; --s7: 48px;

  /* ---------- Tipografia ---------- */
  --font: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;

  /* Las cifras llevan su propia pila. La diferencia no es el capricho de
     otra fuente: es que en la pila de sistema los digitos de Segoe UI y
     los de -apple-system no tienen el mismo ancho, y una columna de
     dinero con digitos de ancho variable baila al actualizarse. Con
     tabular-nums cada digito ocupa lo mismo y $1,999.00 no se mueve
     cuando pasa a $2,000.00. */
  --font-num: var(--font);

  /* OLA 4: LA GRAMATICA DE LO PEQUENO.
     Por debajo de --fs-xs la escala se acababa y cada pantalla elegia
     su numero: habia 9.5, 10, 10.5, 11, 11.5, 12 y 12.5px escritos a
     mano, 86 veces. Siete tamanos para tres papeles.

       3xs 10  el dato AUXILIAR: la unidad de una cifra, la hora bajo
               la fecha, el sufijo de un valor. Nunca informacion que
               haya que buscar.
       2xs 11  el ROTULO: lo que nombra a una cifra o a una fila.
       xs  12  el texto micro NORMAL: meta, ayuda, estado, tabla.
               Sigue siendo la base de todo lo pequeno.

     10px es el suelo. Los cuatro 9.5px que habia SUBEN a 10: en un
     tamano asi medio pixel no compone nada y si resta legibilidad. */
  --fs-3xs:  10px;
  --fs-2xs:  11px;
  --fs-xs:   12px;
  --fs-sm:   13px;
  --fs-base: 15px;
  --fs-lg:   17px;
  --fs-xl:   20px;
  --fs-2xl:  26px;
  --fs-3xl:  34px;   /* cifra protagonista de una pantalla        */
  --fs-4xl:  44px;   /* cifra protagonista del dashboard          */

  /* Display: solo para la landing. Fluido, para no necesitar cuatro
     media queries por cada titular. */
  --fs-display:    clamp(30px, 5.2vw, 56px);
  --fs-display-lg: clamp(36px, 6.4vw, 68px);

  --lh:      1.5;
  --lh-tight: 1.25;
  --lh-titulo: 1.1;

  /* Espaciado entre letras: los titulares grandes necesitan cerrarse,
     las etiquetas en mayusculas necesitan abrirse. */
  --ls-titulo: -0.02em;
  --ls-display: -0.03em;
  --ls-etiqueta: 0.06em;

  /* Pesos, para no escribir 700 a mano por todos lados */
  --peso-medio: 500;
  --peso-fuerte: 600;
  --peso-max: 700;

  /* ---------- Radios ---------- */
  --r-sm: 6px; --r-md: 10px; --r-lg: 14px; --r-full: 999px;

  /* ---------- Elevacion ---------- */
  /* Cinco niveles y una regla: la altura dice QUE TAN TEMPORAL es algo.
     Lo que siempre esta ahi va pegado al fondo; lo que aparece encima
     de todo y se va, vuela. Nada flota "porque se ve bonito". */
  --sh-0: none;                                            /* pegado    */
  --sh-1: 0 1px 2px rgba(28,28,26,.05);                    /* fila      */
  --sh-2: 0 1px 3px rgba(28,28,26,.07),
          0 4px 12px rgba(28,28,26,.05);                   /* tarjeta   */
  --sh-3: 0 8px 28px rgba(28,28,26,.14);                   /* menu      */
  --sh-4: 0 2px 6px rgba(28,28,26,.05),
          0 18px 44px -12px rgba(28,28,26,.20);            /* modal     */
  --sh-5: 0 4px 10px rgba(28,28,26,.05),
          0 30px 70px -20px rgba(28,28,26,.28);            /* landing   */

  /* Sombra de color para el boton primario: la luz que tira un objeto
     de ese color, no una mancha gris debajo. */
  --sh-marca: 0 2px 6px rgba(29,78,216,.16), 0 10px 24px -8px rgba(29,78,216,.34);

  /* Halo de un pixel del boton al pasar por encima. Estaba escrito a
     mano en .btn:hover; el tema oscuro necesita el azul claro. */
  --brand-halo: rgba(29,78,216,.10);

  /* Sombra de la accion dominante del punto de venta. */
  --sh-cobro: 0 6px 18px rgba(29,78,216,.25);

  /* OLA 6: la tercera intensidad de luz de marca, y la mas discreta.
     --sh-marca es la del boton primario del sistema y --sh-cobro la de
     la accion dominante del punto de venta; esta es la de un primario
     que vive DENTRO de un bloque ya tenido -la llamada del tablero,
     los botones de Caja- y que solo necesita despegarse un poco.

     En claro no existe: ahi el boton ya se separa por color. Es una
     idea del tema oscuro, donde una sombra negra sobre casi negro no
     dice nada y hay que sustituirla por luz. Se declara aqui en cero
     para que el token exista en los dos temas y nadie tenga que
     acordarse de que solo vale en uno. Las tres reglas que lo leen
     siguen estando dentro del tema oscuro: en claro este valor no se
     lee nunca, y por eso ponerlo a none no apaga ninguna sombra. */
  --sh-btn-luz: none;

  /* OLA 6: EL LIENZO DE UNA PANTALLA.
     Diez pantallas -Categorias, Existencias, Alertas, Movimientos,
     Transferencias, Caja, Reportes, Sucursales, Usuarios y el tablero
     de telefono- tinen su fondo con un 4% de marca para que el
     contenido se lea sobre algo y no sobre el blanco pelado. Las diez
     escribian la misma mezcla a mano y las diez la deshacian a mano en
     oscuro, donde un 4% de azul sobre casi negro se lee como una
     mancha y no como luz.

     Veinte reglas para una idea. Ahora es un token: el valor claro
     aqui, el oscuro en su bloque, y el papel en print con los demas. */
  --lienzo: color-mix(in srgb, var(--brand-600) 4%, var(--bg));
  /* La red de color-mix tambien se escribia once veces, una por
     pantalla. Ahora es una: si el navegador no sabe mezclar, el lienzo
     es el fondo y ya. Va justo debajo para que se lea con el token y
     no en otro sitio del archivo. */

  /* OLA 6: LO QUE SE HUNDE DENTRO DE UN BLOQUE TENIDO.
     El azulejo de un estado, la pastilla de "aqui", el icono de una
     lamina: piezas que van DENTRO de un bloque que ya tiene color y
     que no pueden aclararse mas -encima del tinte no queda sitio-, asi
     que se hunden con negro translucido. Estaba escrito a mano cuatro
     veces, dos al .28 y dos al .3, que es la misma idea con dos
     numeros. En claro no existe: ahi el bloque tenido es palido y la
     pieza se separa aclarando. */
  --hundido: transparent;

  /* Sombra que apunta HACIA ARRIBA: barras ancladas abajo (acciones de
     formulario, barra de cobro). Sobre un fondo oscuro una sombra negra
     al 7% no se ve, asi que el tema la sube. */
  --sh-arriba: 0 -6px 18px rgba(28,28,26,.07);

  /* Velo de modales, cajon de menu y hoja de filtros. */
  --velo: rgba(28,28,26,.5);

  /* Realce claro dentro de una pastilla ya tenida (la marca de un
     movimiento). En oscuro tiene que oscurecer, no aclarar. */
  --marca-realce: rgba(255,255,255,.65);

  /* Rojo mas fuerte que --error: solo el hover de la accion destructiva
     solida. En oscuro va al reves, se ACLARA. */
  --error-fuerte: #991b1b;

  --ring: 0 0 0 3px rgba(29,78,216,.22);
  --ring-error: 0 0 0 3px rgba(185,28,28,.18);
  --ring-ok:    0 0 0 3px rgba(21,128,61,.18);

  /* ---------- Z-index ---------- */
  /* Se elimina el "z-index: 10" magico de .nav */
  --z-nav: 100; --z-dropdown: 200; --z-modal: 300; --z-toast: 400;

  /* ---------- Movimiento ---------- */
  /* Tres duraciones y tres curvas. Lo que responde a un dedo o a un
     raton es rapido (si tarda, se siente roto). Lo que aparece de la
     nada puede tomarse su tiempo. Lo que se va, se va rapido: nadie
     quiere esperar a que algo termine de desaparecer. */
  --t-rapida: 120ms;
  --t-media:  180ms;
  --t-lenta:  320ms;
  --t-entrada: 480ms;

  --ease:        cubic-bezier(.4, 0, .2, 1);    /* general              */
  --ease-salida: cubic-bezier(.4, 0, 1, 1);     /* algo que se va       */
  --ease-suave:  cubic-bezier(.16, 1, .3, 1);   /* algo que aparece     */

  /* ---------- Interaccion ---------- */
  /* 44px: alto minimo para que un dedo acierte al primer intento. */
  --tap: 44px;

  /* OLA 4: EL PULSADO, EN DOS INTENSIDADES.
     Habia seis escalas distintas para el mismo gesto -.9, .94, .96,
     .97, .985 y .995- repartidas por seis pantallas. La diferencia
     real no era de pantalla, era de TAMANO: lo grande se encoge poco
     porque el ojo mide el desplazamiento del borde, y lo pequeno tiene
     que encogerse mas para que el gesto se vea.

       --pulsa     una superficie: tarjeta, fila, bloque que se toca.
       --pulsa-lg  un objetivo chico: boton de icono, atajo, paso.

     El boton NO usa ninguna de las dos: se HUNDE -translateY(1px)- y
     su scale(.995) solo acompana al hundimiento. Es otro gesto, y esta
     en una sola regla. */
  --pulsa:    .985;
  --pulsa-lg: .96;

  /* OLA 4: la identidad del foco. 23 reglas escribian este mismo
     contorno a mano. El desplazamiento NO entra en el token: hay dos
     -2px por fuera, -2px por dentro cuando el contenedor recorta- y
     esa eleccion es de cada componente, no del sistema. */
  --foco: 2px solid var(--brand-600);

  /* Shell de escritorio (Etapa 3) */
  --ancho-lateral:     244px;
  --ancho-lateral-min:  64px;
  --ancho-contenido:       1100px;  /* sin barra lateral */

  /* ---------- G1: los dos topes del shell autenticado ----------
     El riel de iconos (64px) llega hasta 1279 y la lateral con
     etiquetas (244px) empieza en 1280. En el salto, la lateral se
     lleva 180px de golpe mientras la ventana solo gana 1: si el
     contenido del riel no tuviera tope, el area util CAERIA en ese
     punto. Es exactamente lo que pasaba antes en 1024:

         900  -> 773px utiles
         1024 -> 732px utiles     (la ventana crece, el contenido baja)

     --ancho-contenido-riel es ese tope. Se calcula para que el area
     util a un lado y otro del salto sea LA MISMA:

         a 1279 con riel:     1279 - 64  = 1215 de main,
                              tope 1036  ->  1036 - 48 = 988 utiles
         a 1280 con lateral:  1280 - 244 = 1036 de main,
                              sin tope   ->  1036 - 48 = 988 utiles

     LOS 17px SON LA BARRA DE DESPLAZAMIENTO, y no sobran. Con el tope
     en 1036 clavado, MEDIDO en Windows: a 1279 el tope absorbia la
     barra y salian 988 utiles, pero a 1280 la lateral ya no tiene tope
     que la absorba y salian 973. Un bache de 15px justo en el salto,
     que es lo unico que G1 existe para evitar. Restando el ancho
     maximo de barra (17px en Windows, 0 con barras superpuestas) los
     dos lados se encuentran o crecen, nunca bajan, en los dos casos.

     Cambiar uno de los tres numeros obliga a recalcular el tope:
         riel = punto-de-salto - ancho-lateral - barra                */
  --ancho-contenido-riel: calc(1280px - var(--ancho-lateral) - 17px);
  --ancho-contenido-amplio: 1600px;  /* con lateral abierta (>=1280)  */
  --ancho-landing:         1200px;  /* landing publica   */
  --ancho-texto:            68ch;   /* parrafo legible   */

  /* ===================================================================
     G2. PLANOS, INTERACCION Y HOJAS   (UI 2.0)
     -------------------------------------------------------------------
     Lo que faltaba para que "movil" deje de ser "web estrecha".

     1. UN PLANO MAS. Habia --bg / --surface / --surface-2. Faltaba el
        plano de lo que se LEVANTA por encima del panel y no es un
        hueco: el menu desplegable, la fila seleccionada, la cabecera
        pegajosa de una hoja. En claro sube (mas blanco que el fondo del
        panel no existe, asi que sube por SOMBRA y por borde); en oscuro
        sube de verdad, con un gris mas claro.

     2. LOS ESTADOS SON TOKENS. --hover, --activo y --marcado estaban
        escritos a mano como var(--surface-2) en cuarenta sitios. Son
        tres PAPELES distintos que hoy coinciden en claro y que en
        oscuro y en tactil no pueden coincidir: en un telefono no hay
        hover, y el "pulsado" tiene que notarse mas que en un raton.

     3. LAS HOJAS. Un panel que sube desde abajo no es una tarjeta: es
        una superficie temporal que tapa la aplicacion. Tiene su propio
        color, su propio radio y su propia sombra, y en oscuro tiene que
        separarse del fondo MAS que un panel normal.
     =================================================================== */

  /* Plano 3: lo que se levanta por encima del panel */
  --surface-3: #ffffff;

  /* La superficie de una hoja / un modal. Coincide con --surface en
     claro, pero NO es lo mismo: en oscuro se separa. */
  --sheet:     #ffffff;

  /* Estados de interaccion. Tres papeles, no un gris repetido. */
  --hover:     rgba(28,28,26,.035);   /* raton por encima            */
  --pressed:   rgba(28,28,26,.075);   /* dedo o boton abajo          */
  --selected:  var(--brand-50);       /* elegido / activo            */
  --selected-borde: rgba(29,78,216,.28);

  /* Borde de pelo: separa sin dibujar una caja. Es lo que sustituye a
     una tarjeta cuando la tarjeta no aporta nada (regla 16). */
  --border-sutil: rgba(28,28,26,.07);

  /* ---------- Hojas inferiores ---------- */
  --r-hoja:    20px;    /* esquina superior de una hoja               */
  --hoja-max:  90dvh;   /* nunca tapa del todo: se ve que hay algo detras */
  --sh-hoja:   0 -2px 8px rgba(28,28,26,.06), 0 -20px 50px -12px rgba(28,28,26,.28);
  --z-hoja:    310;     /* por encima de --z-modal, debajo del toast  */

  /* Alto de una fila tactil de lista. Mas que --tap: una fila con dos
     renglones de texto necesita aire, no solo acertar con el dedo. */
  --fila-tap:  56px;

  /* ===================================================================
     G6. SISTEMA VISUAL MAESTRO   (tokens que faltaban)
     -------------------------------------------------------------------
     No es un segundo sistema de tokens. Es EL MISMO, terminado.

     Lo que habia cubria color, texto, espacio, radio, sombra, z y
     movimiento. Lo que faltaba eran cuatro familias que las pantallas
     estaban resolviendo cada una por su cuenta:

       1. LOS PAPELES QUE NO TENIAN NOMBRE. --surface-3 existia como
          "plano 3", pero no habia forma de decir "la superficie de un
          control" ni "la superficie de un modal de escritorio". Se
          escribian a mano como var(--surface-2) y var(--surface), que
          es lo mismo hoy y deja de serlo en cuanto uno de los dos se
          mueve.

       2. LA MEDIDA DEL SHELL. El alto de la barra superior y el de la
          barra inferior estaban escritos en numero suelto en las diez
          reglas que necesitan descontarlos (posicion pegajosa, alto de
          una hoja, relleno del cuerpo). Cambiar la barra obligaba a
          buscar el numero por todo el archivo.

       3. EL MARGEN DE PANTALLA. Cada seccion movil elegia el suyo:
          14, 16 y 20px conviviendo en la misma columna. El ojo lo lee
          como "esta pantalla no es del mismo producto".

       4. EL AZULEJO DE ICONO. El cuadro tenido que identifica una
          pantalla, una metrica o una fila es UNA pieza y aparece en
          seis sitios. Sus tres medidas viven aqui.

     Los puntos de corte tambien se anotan. No se pueden usar dentro de
     @media -CSS no interpola variables ahi- pero anotarlos evita que
     aparezca un cuarto punto de corte inventado a las tres semanas:

         telefono   hasta 767px
         tablet     768px  - 1023px
         portatil   1024px - 1279px
         escritorio 1280px en adelante
     =================================================================== */

  /* ---------- Papeles de superficie ---------- */
  /* Lo que se LEVANTA sobre el panel y se queda (cabecera pegajosa de
     una hoja, fila elegida, menu desplegable). Hoy coincide con el
     plano 3; tiene nombre propio porque es un papel, no un plano. */
  --elevated: var(--surface-3);

  /* La superficie de un control en reposo: input, boton secundario,
     azulejo, pastilla. En claro es un hueco; en oscuro, un realce. */
  --interactive: var(--surface-2);

  /* La superficie de un modal de escritorio. En telefono todo modal es
     una hoja y usa --sheet; a partir de tablet se separa, porque un
     dialogo centrado sobre un velo no necesita el mismo color que un
     panel que sube desde el borde. */
  --modal: var(--sheet);
  --sh-modal: var(--sh-4);

  /* ---------- Alias en el vocabulario del sistema ----------
     MISMO valor, no un token nuevo. Existen para que un componente
     nuevo pueda escribirse con el nombre del papel -"esto va en el
     borde sutil", "esto es peligroso"- sin tener que recordar cual de
     las dos lenguas usa el archivo. Cambiar el valor se sigue haciendo
     UNA vez, arriba. */
  --border-subtle: var(--border-sutil);
  --text-secondary: var(--text-2);
  --text-muted:     var(--text-3);
  --success: var(--ok);
  --warning: var(--warn);
  --danger:  var(--error);
  --accent:  var(--accent-600);

  /* OLA 4: se contaron los usos de los 161 tokens del archivo. Doce
     salieron con cero usos EN ESTE ARCHIVO y ninguno se retira:

       --ancho-landing, --fs-display-lg, --sh-5   los usa landing.css.
           Contar solo app.css los daba por muertos. No lo estan.

       --text-secondary, --text-muted, --warning, --accent   son la
           mitad sin estrenar de este bloque de alias. Sus hermanos
           -border-subtle, success, danger- si se usan. Un vocabulario
           con la mitad de las palabras no es medio vocabulario: es
           uno en el que hay que acordarse de cuales existen.

       --ease-salida, --ring-ok, --sh-0, --z-dropdown, --elevated
           son el escalon sin estrenar de una escala completa. Una
           escala con un hueco es peor que una con un escalon de
           reserva: el hueco se rellena con un literal.

     Ninguno pesa nada -una variable CSS sin leer no se resuelve- y
     retirarlos solo empeoraria lo que viniera despues. Si algun dia
     sobra alguno de verdad, sera porque se retiro su escala entera. */

  /* ---------- Radios: la escala completa ----------
     Cuatro pasos y dos casos especiales. La regla es el TAMANO de lo
     que se redondea: cuanto mas grande la superficie, mas radio, para
     que la curva se lea igual de cerrada en todas.

       xs  4   filo de un chip diminuto, barra de progreso
       sm  6   control pequeno, celda
       md 10   input, boton, pestana
       lg 14   panel, tarjeta
       xl 18   azulejo grande, bloque destacado
       hoja 20 la esquina de una hoja (ya existia)
       full    la pastilla */
  --r-xs: 4px;
  --r-xl: 18px;

  /* ---------- Objetivos tactiles ----------
     44px es el minimo. Hay dos escalones mas porque no todo lo que se
     toca pesa lo mismo: un icono suelto en una barra puede bajar a 40
     si tiene aire alrededor, y la accion dominante de una pantalla
     sube, porque se busca sin mirar. */
  --tap-sm: 40px;
  --tap-lg: 52px;

  /* Fila de lista con marcador grande (foto, iniciales, azulejo). */
  --fila-tap-lg: 68px;

  /* ---------- Medidas del shell ----------
     NO se declaran aqui: --barra-inf y --topbar-movil ya existen en el
     :root de la seccion 27 y estan en uso en diez reglas. Duplicarlos
     con otro nombre seria exactamente lo que este archivo prohibe en su
     primera linea.

     Lo unico que falta es el alto REAL de la barra inferior -el suyo
     mas el area segura del dispositivo-, que es lo que tiene que
     descontar cualquier cosa que se ancle por encima de ella. Estaba
     escrito como calc() a mano en cada sitio que lo necesitaba. */
  --barra-inf-total: calc(var(--barra-inf, 58px) + env(safe-area-inset-bottom));


  /* ---------- Margen de pantalla ----------
     La distancia del contenido al borde de la ventana. UNA por
     dispositivo, no una por seccion. Se sube en 15/27b/27c. */
  --gutter: var(--s4);

  /* ---------- Azulejo de icono ----------
     El cuadro tenido que identifica algo: la pantalla en su cabecera,
     la metrica en su tarjeta, la categoria en su fila. Tres medidas y
     un radio, para que los seis sitios donde aparece sean el mismo
     objeto y no seis cuadros parecidos. */
  --azulejo:    44px;
  --azulejo-sm: 36px;
  --azulejo-lg: 52px;
  --azulejo-r:  var(--r-lg);


  /* ---------- La cabecera de pantalla (Ola 1) ----------
     LA ESCALERA VIVE AQUI Y EN NINGUN OTRO SITIO.

     Antes habia cuatro: la del sistema y tres que se escribieron por
     separado cuando siete pantallas midieron -cada una por su cuenta-
     que un azulejo de 52 y un titulo de 24 no caben al lado de dos
     botones en un telefono de 390. Todas llegaron a la misma
     conclusion y ninguna la subio aqui, asi que la fase siguiente
     volvia a medir. El :has() que hacia segura cada correccion es lo
     que las hacia invisibles entre si.

     Cuatro tokens y dos puntos de corte. Una pantalla que de verdad
     necesite otra medida -hoy solo Existencias, que es la unica con
     dos acciones de texto en la fila del titulo- reescribe ESTOS
     tokens en una linea, no cuatro reglas. Asi la excepcion se declara
     en el mismo vocabulario que la norma y se ve que es una excepcion.

     24 y 18 no salen de --fs-*: la escala de texto sube 20 -> 26 y el
     titulo de una pantalla necesita los dos pasos intermedios. Por eso
     son tokens propios y no numeros sueltos repetidos por el archivo,
     que es justo lo que habia. */
  --cab-azulejo: var(--azulejo-lg);   /* 52 */
  --cab-icono:   24px;                /* el que emite ui_azulejo(,'lg') */
  --cab-titulo:  24px;
  --cab-desc:    var(--fs-sm);

  /* ---------- Icono pequeno ----------
     El icono que acompana a un texto de --fs-xs dentro de un control
     compacto. Estaba escrito como 13, 14 y 15px en las siete copias
     del boton de cabecera. La escala completa de iconos es de la
     Ola 4; aqui entra solo el escalon que este componente necesita. */
  --ico-sm: 14px;


  /* ===================================================================
     LA PALETA CATEGORICA   (Ola 2)
     -------------------------------------------------------------------
     SEIS COLORES QUE DICEN "QUE ES ALGO", NUNCA "COMO ESTA ALGO".

     Es la segunda paleta del sistema y la regla que la separa de la
     primera no admite excepciones:

       SEMANTICA  --ok --warn --crit --error --info
                  responde a "como va esto": verde correcto, ambar
                  advertencia, rojo critico. Un color de estado NUNCA
                  identifica una categoria.

       CATEGORICA --cat-0 .. --cat-5
                  responde a "cual de ellos es este": el marcador de un
                  producto sin foto, la barra de una sucursal, el tramo
                  de una categoria en el anillo. Un color categorico
                  NUNCA indica un estado.

     NO son colores nuevos. Son EXACTAMENTE los que ya se pintaban a
     mano en tres sitios -.prod-img-cN, .inv-suc-barra-cN y el bloque de
     impresion-, 36 valores literales que ahora se declaran una vez.
     Indigo, esmeralda, naranja, rosa, cielo y amarillo: seis matices
     bien separados en el circulo cromatico y ninguno de ellos es el
     verde de --ok ni el ambar de --warn.

     Tres papeles por color:
       --cat-N        la tinta: el trazo del anillo, la barra llena,
                      las iniciales de un producto
       --cat-N-bg     el fondo hundido detras de esa tinta
       --cat-N-borde  el filo de ese fondo

     En oscuro se invierten -tinta clara sobre fondo hundido- y esos
     valores tambien existian ya, escritos a mano. Un color, un sitio,
     dos temas.
     =================================================================== */
  --cat-0: #4338ca;  --cat-0-bg: #eef2ff;  --cat-0-borde: #e0e7ff;  /* indigo    */
  --cat-1: #047857;  --cat-1-bg: #ecfdf5;  --cat-1-borde: #d1fae5;  /* esmeralda */
  --cat-2: #c2410c;  --cat-2-bg: #fff7ed;  --cat-2-borde: #ffedd5;  /* naranja   */
  --cat-3: #be185d;  --cat-3-bg: #fdf2f8;  --cat-3-borde: #fce7f3;  /* rosa      */
  --cat-4: #0369a1;  --cat-4-bg: #f0f9ff;  --cat-4-borde: #e0f2fe;  /* cielo     */
  --cat-5: #a16207;  --cat-5-bg: #fefce8;  --cat-5-borde: #fef9c3;  /* amarillo  */

}

/* La red del lienzo: una, no once. Si el navegador no sabe mezclar
   colores, el lienzo es el fondo de la aplicacion y ya. */
@supports not (background: color-mix(in srgb, red 4%, white)) {
  :root { --lienzo: var(--bg); }
}


/* ---------------------------------------------------------------------
   La escalera de la cabecera, por ancho.

   Se declara sobre :root -y no sobre .pagina-cab- para que cualquier
   regla del archivo pueda leer la medida vigente sin volver a escribir
   un punto de corte. Los dos @media son los del sistema: el de
   telefono y el de telefono pequeno.
   --------------------------------------------------------------------- */
@media (max-width: 767px) {
  :root {
    --cab-azulejo: var(--azulejo);      /* 44 */
    --cab-icono:   22px;
    --cab-titulo:  var(--fs-xl);        /* 20 */
    --cab-desc:    var(--fs-xs);        /* 12 */
  }
}

@media (max-width: 379px) {
  :root {
    --cab-azulejo: var(--azulejo-sm);   /* 36 */
    --cab-icono:   19px;
    --cab-titulo:  18px;
  }
}

/* =====================================================================
   1b. TEMA OSCURO
   ---------------------------------------------------------------------
   UN SOLO bloque, y casi todo el son TOKENS. No hay .panel-oscuro ni
   .tabla-oscura: los componentes no saben que existe un tema. Una
   pantalla nueva que use los componentes de siempre sale en oscuro sola.

   Como se enciende
   ----------------
   El tema vive en un atributo del <html>: data-tema="oscuro". Lo pone
   includes/vistas/cabeza_preferencias.php ANTES del primer pintado, que
   es el mismo sitio y el mismo patron con el que ya se decide si la
   barra lateral arranca colapsada. Y solo hay una razon para que lo
   ponga:

       el usuario eligio el tema oscuro y esa eleccion quedo guardada

   No hay segunda razon. Sin eleccion guardada -primera visita,
   almacenamiento bloqueado, valor corrupto- se queda el tema claro.
   EL SISTEMA OPERATIVO NO DECIDE: aqui no se consulta
   (prefers-color-scheme) ni se hace con @media en este archivo. Un
   mostrador con Windows en oscuro no tiene por que abrir el producto
   en negro sin haberlo pedido.

   Se resuelve en el script y NO con dos copias de este bloque -una en
   @media y otra en [data-tema]- porque duplicar cuarenta tokens es
   garantizar que un dia se toque una copia y no la otra.

   Por que no es "blanco a negro"
   ------------------------------
   El tema claro es de neutros CALIDOS (#f7f7f6, #faf9f6, #e5e3dd). El
   oscuro conserva esa temperatura en vez de irse a un gris azulado: es
   lo que hace que se reconozca como el mismo producto.

   Y hay profundidad real, tres planos separados de verdad:

       --bg        #14140f   el fondo de la aplicacion
       --surface   #1e1e1a   el panel, la tarjeta, la barra lateral
       --surface-2 #292924   lo que va DENTRO del panel y tiene que
                             distinguirse: inputs, cabecera de tabla,
                             fila al pasar por encima, deshabilitado

   En claro --surface-2 es un hueco (mas oscuro que el panel blanco); en
   oscuro es un realce (mas claro que el panel). Es el mismo PAPEL en los
   dos temas -"lo que va dentro y se tiene que ver"- y por eso ningun
   componente necesita cambiar.
   ===================================================================== */

:root[data-tema="oscuro"] {
  color-scheme: dark;

  /* ---------- Marca ---------- */
  /* En claro la marca se OSCURECE al pasar por encima; en oscuro se
     ACLARA. Es la misma idea -mas energia- leida al reves. */
  --brand-600: #3b82f6;
  --brand-700: #60a5fa;
  --brand-50:  #172554;

  /* Blanco sobre #17245c = 13.9:1. Ver la nota del tema claro. */
  --acceso-marca-bg: #17245c;


  --accent-600: #2dd4bf;
  --accent-50:  #04302e;

  /* ---------- Superficies ---------- */
  --bg:        #14140f;
  --surface:   #1e1e1a;
  --surface-2: #292924;
  --border:    #383731;
  --border-2:  #4a4941;

  /* ---------- Texto ---------- */
  /* --text-inv es el texto que va ENCIMA de un color solido de marca o
     de estado. En oscuro esos colores son claros, asi que el invertido
     es casi negro: #14140f sobre #3b82f6 da 6.0:1, mientras que blanco
     sobre ese mismo azul se queda en 3.1:1. */
  --text:     #f2f1ec;
  --text-2:   #adaca3;
  --text-3:   #7e7d75;
  --text-inv: #14140f;

  /* El azul de marca del tema oscuro (#3b82f6) es demasiado claro para
     llevar letra blanca: se queda en 3.68:1. En vez de dar marcha atras
     y volver a la letra oscura -que es lo que hacia que el mismo boton
     se leyera distinto segun el tema- se oscurece SOLO el fondo del
     boton primario, lo justo para cumplir AA sin salirse del tono:
     #ffffff sobre #2563eb = 5.17:1, hover #276af5 = 4.70:1.

     --brand-600 y --brand-700 NO se tocan: el chip, la barra de
     progreso, el enlace activo y el foco siguen con el azul claro que
     necesitan sobre fondo oscuro. */
  --btn-primario-bg:       #2563eb;
  --btn-primario-bg-hover: #276af5;

  /* ---------- Estados ---------- */
  /* El color se aclara y el fondo se oscurece: el par --tono/--tono-bg
     sigue siendo legible sin que ningun chip ni aviso cambie una linea. */
  --ok:     #4ade80;  --ok-bg:    #0b2f1a;
  --warn:   #fbbf24;  --warn-bg:  #3a2a06;
  --crit:   #fb923c;  --crit-bg:  #3f2410;
  --error:  #f87171;  --error-bg: #401616;
  --info:   #60a5fa;  --info-bg:  #17243f;

  --error-fuerte: #ef4444;

  /* ---------- Elevacion ---------- */
  /* Sobre un fondo casi negro una sombra negra al 5% no existe. Se
     suben las opacidades, y ademas la elevacion se apoya en el salto de
     superficie y en el borde, que es lo que de verdad se ve. */
  --sh-0: none;
  --sh-1: 0 1px 2px rgba(0,0,0,.45);
  --sh-2: 0 1px 3px rgba(0,0,0,.5), 0 4px 12px rgba(0,0,0,.4);
  --sh-3: 0 8px 28px rgba(0,0,0,.65);
  --sh-4: 0 2px 6px rgba(0,0,0,.45), 0 1px 2px rgba(0,0,0,.35);
  --sh-5: 0 4px 10px rgba(0,0,0,.45), 0 12px 28px -10px rgba(0,0,0,.6);

  /* La marca ya no tira una sombra: tira LUZ. */
  --sh-marca:  0 2px 6px rgba(59,130,246,.28), 0 10px 24px -8px rgba(59,130,246,.45);
  --sh-cobro:  0 6px 18px rgba(59,130,246,.35);
  --brand-halo: rgba(96,165,250,.24);

  /* OLA 6: los tres valores que en oscuro dejan de ser sombra y pasan
     a ser otra cosa. Ver sus notas en el tema claro. */
  --sh-btn-luz: 0 2px 8px rgba(59,130,246,.18);
  --hundido:    rgba(0, 0, 0, .3);
  --lienzo:     var(--bg);
  --sh-arriba: 0 -6px 18px rgba(0,0,0,.5);

  /* El foco tiene que verse MAS, no menos, sobre un fondo oscuro. */
  --ring:       0 0 0 3px rgba(96,165,250,.45);
  --ring-error: 0 0 0 3px rgba(248,113,113,.40);
  --ring-ok:    0 0 0 3px rgba(74,222,128,.38);

  --velo: rgba(0,0,0,.62);

  /* OLA 4: EL FILO DE LUZ.
     En oscuro una superficie elevada no se separa con sombra -sobre un
     casi negro no hay sombra que valga- sino con una linea de luz en el
     borde que mira hacia arriba. Estaba escrito a mano en doce sitios,
     en dos geometrias que son dos ideas distintas:

       --filo-luz      solo el borde de arriba. La superficie se apoya
                       en la pagina y recibe la luz por su canto.
       --contorno-luz  el contorno entero. Para lo que FLOTA y no tiene
                       una pagina debajo de la que apoyarse.

     En papel no hay luz que venga de arriba: el bloque de print los
     apaga con los demas. */
  --filo-luz:     inset 0 1px 0 rgba(255,255,255,.05);
  --contorno-luz: inset 0 0 0 1px rgba(255,255,255,.05);

  /* OLA 4: EL AZUL DE MARCA QUE PASA AA CON TEXTO BLANCO.
     El azul de marca en oscuro (#3b82f6) da 3.68:1 con blanco y falla
     AA. Donde se pide texto blanco -los primarios de Caja- hay que
     bajar el azul un punto: #2f6fdc da 4.74:1 y es el tono MAS CERCANO
     al original que cumple. Estaba escrito dos veces como literal con
     su calculo al lado; el propio comentario decia que el sitio de
     esto era el token. Aqui esta.

     NO es --brand-600 con otro nombre: son colores distintos a
     proposito, y sustituir uno por el otro rompe el contraste. */
  --brand-600-aa: #2f6fdc;

  /* El realce dentro de una pastilla ya tenida: en claro aclara, en
     oscuro oscurece. */
  --marca-realce: rgba(0,0,0,.32);

  /* ---------- G2: planos, estados y hojas en oscuro ----------
     Aqui es donde el tema oscuro deja de ser "el claro invertido".

     En claro un panel blanco sobre un fondo hueso ya se lee como
     elevado y --surface-3 no tiene a donde subir. En oscuro SI: cada
     plano que se levanta es un gris mas claro, porque la luz viene de
     arriba y lo que esta mas cerca recibe mas. Los cinco planos:

         --bg        #14140f   la aplicacion
         --surface   #1e1e1a   el panel
         --surface-2 #292924   lo que va DENTRO del panel (input, th)
         --surface-3 #32322c   lo que se levanta SOBRE el panel
         --sheet     #24241f   la hoja: separada del fondo, no del panel

     La hoja NO usa --surface-3. Una hoja tapa la aplicacion entera con
     un velo detras; si fuera del gris mas claro de todos competiria con
     su propio contenido. Se queda un punto por encima del panel, y lo
     que la separa del fondo es el velo, no el color.

     Los estados tampoco se invierten: se REESCRIBEN. En claro se
     oscurece con negro translucido; en oscuro se ACLARA con blanco
     translucido, porque manchar de negro un gris oscuro no se ve. */

  --surface-3: #32322c;
  --sheet:     #24241f;

  --hover:     rgba(255,255,255,.05);
  --pressed:   rgba(255,255,255,.10);
  --selected:  rgba(59,130,246,.16);
  --selected-borde: rgba(96,165,250,.38);

  /* El borde de pelo en oscuro no puede ser negro translucido: seria
     invisible. Es blanco muy bajo, que es como se lee una junta entre
     dos superficies oscuras. */
  --border-sutil: rgba(255,255,255,.07);

  /* En oscuro la sombra de una hoja casi no se ve; lo que la despega es
     el velo y un filo de luz en el borde superior. */
  --sh-hoja: 0 -1px 0 rgba(255,255,255,.06), 0 -20px 50px -12px rgba(0,0,0,.75);

  /* ---------- G6: los papeles que si cambian en oscuro ----------
     --elevated y --interactive NO se repiten aqui: los dos apuntan a
     --surface-3 y --surface-2, que este bloque ya reescribe, y la
     sustitucion se resuelve contra el valor que gana en el elemento.
     Repetirlos seria tener dos sitios donde tocar un mismo color.

     --modal SI se separa. En claro la hoja y el modal son el mismo
     blanco porque no hay a donde subir. En oscuro un dialogo centrado
     flota sobre el velo con todo el fondo detras y necesita despegarse
     mas que una hoja, que se apoya en el borde de la pantalla. */
  --modal: #2b2b25;

  /* La sombra de un dialogo centrado tira hacia todos lados, no hacia
     arriba: no se puede reusar --sh-hoja. */
  --sh-modal: 0 0 0 1px rgba(255,255,255,.06), 0 24px 60px -12px rgba(0,0,0,.8);
}

/* ---------------------------------------------------------------------
   Las TRES excepciones que no son un token.
   Van aqui, juntas y justificadas, en vez de repartidas por el archivo.
   --------------------------------------------------------------------- */

/* 1. Los controles de formulario. En claro el input es blanco sobre un
      panel blanco y se distingue por el borde; en oscuro hace falta que
      el control sea MAS claro que el panel para que se lea como un
      hueco donde se escribe. */
:root[data-tema="oscuro"] .campo input,
:root[data-tema="oscuro"] .campo select,
:root[data-tema="oscuro"] .campo textarea,
:root[data-tema="oscuro"] input[type="search"],
:root[data-tema="oscuro"] input[type="date"],
:root[data-tema="oscuro"] input[type="number"],
:root[data-tema="oscuro"] input[type="text"],
:root[data-tema="oscuro"] input[type="tel"],
:root[data-tema="oscuro"] input[type="email"],
:root[data-tema="oscuro"] input[type="password"],
:root[data-tema="oscuro"] select,
:root[data-tema="oscuro"] textarea { background: var(--surface-2); }

/*    ...y por eso mismo, el campo que NO se puede escribir tiene que
      volver a bajar. En claro un control deshabilitado se distingue
      porque es MAS gris que el input blanco; en oscuro, si se quedara
      en --surface-2, seria identico a un campo activo -que ahi tambien
      es --surface-2- y no habria forma de ver que esta bloqueado.
      Baja al plano del panel: hundido y plano, con su texto en
      --text-3 y su borde de rayas, que ya los pone G4. */
:root[data-tema="oscuro"] input:disabled,
:root[data-tema="oscuro"] select:disabled,
:root[data-tema="oscuro"] textarea:disabled,
:root[data-tema="oscuro"] input[type="text"]:read-only,
:root[data-tema="oscuro"] input[type="number"]:read-only,
:root[data-tema="oscuro"] input[type="email"]:read-only,
:root[data-tema="oscuro"] input[type="tel"]:read-only,
:root[data-tema="oscuro"] textarea:read-only { background: var(--surface); }

/* 2. LA PALETA CATEGORICA EN OSCURO.
      Su color ES su identidad -el mismo producto sale siempre del mismo
      color- asi que no puede caer en un token de estado. Se recalculan
      las seis: fondo hundido y tinta clara del mismo matiz.

      OLA 2: deja de ser una lista de clases y pasa a ser la misma lista
      de TOKENS que el tema claro. Los valores no cambian ni un digito;
      lo que cambia es que ahora los leen los tres sitios que pintan
      categorias -las iniciales de un producto, la barra de una sucursal
      y el anillo de Reportes- en vez de tener cada uno su copia. */
:root[data-tema="oscuro"] {
  --cat-0: #a5b4fc;  --cat-0-bg: #1e1b4b;  --cat-0-borde: #312e81;
  --cat-1: #6ee7b7;  --cat-1-bg: #052e21;  --cat-1-borde: #065f46;
  --cat-2: #fdba74;  --cat-2-bg: #3f1d0a;  --cat-2-borde: #7c2d12;
  --cat-3: #f9a8d4;  --cat-3-bg: #3f0f28;  --cat-3-borde: #831843;
  --cat-4: #7dd3fc;  --cat-4-bg: #082f49;  --cat-4-borde: #075985;
  --cat-5: #fde047;  --cat-5-bg: #3b2c05;  --cat-5-borde: #854d0e;
}

/* 3. Las fotos de producto que suben los negocios estan hechas sobre
      fondo claro y en un panel oscuro deslumbran. Se bajan un punto.
      NO se invierten: invertir una foto de producto la estropea. */
:root[data-tema="oscuro"] .prod-img img,
:root[data-tema="oscuro"] .imagen-previa { filter: brightness(.9); }


/* ---------------------------------------------------------------------
   EL TEMA OSCURO NO SE IMPRIME
   ---------------------------------------------------------------------
   El papel es blanco. Sin esto, un negocio que trabaja en oscuro sacaba
   el corte de caja con los paneles en negro: ilegible y un cartucho de
   tinta por hoja.

   NO se rediseña la impresion: se devuelven los TOKENS al tema claro y
   las reglas de @media print que ya existian vuelven a hacer justo lo
   que hacian antes de que hubiera un tema oscuro.
   --------------------------------------------------------------------- */

@media print {
  :root[data-tema="oscuro"] {
    color-scheme: light;

    --brand-600: #1d4ed8;  --brand-700: #1e3a8a;  --brand-50: #eff6ff;
    --accent-600: #0d9488; --accent-50: #f0fdfa;

    --bg: #ffffff;  --surface: #ffffff;  --surface-2: #faf9f6;
    --border: #e5e3dd;  --border-2: #d8d5cd;

    --text: #1c1c1a;  --text-2: #6b6b66;  --text-3: #9a9a94;  --text-inv: #ffffff;

    --btn-primario-bg: #1d4ed8;  --btn-primario-bg-hover: #1e3a8a;

    --ok:    #15803d;  --ok-bg:    #dcfce7;
    --warn:  #b45309;  --warn-bg:  #fef3c7;
    --crit:  #c2410c;  --crit-bg:  #ffedd5;
    --error: #b91c1c;  --error-bg: #fee2e2;
    --info:  #1d4ed8;  --info-bg:  #eff6ff;
    --error-fuerte: #991b1b;

    /* En papel no hay elevacion. */
    --sh-0: none; --sh-1: none; --sh-2: none; --sh-3: none;
    --sh-4: none; --sh-5: none; --sh-marca: none;
    --sh-arriba: none; --sh-cobro: none; --sh-hoja: none;
    --marca-realce: rgba(255,255,255,.65);

    /* G2: los planos y los estados nuevos vuelven tambien al tema
       claro. Sin esto, un negocio que trabaja en oscuro sacaba el corte
       de caja con las juntas invisibles (--border-sutil en blanco sobre
       papel blanco) y los realces de fila en gris. */
    --surface-3: #ffffff;
    --sheet:     #ffffff;
    --hover:     transparent;
    --pressed:   transparent;
    --selected:  #eff6ff;
    --selected-borde: rgba(29,78,216,.28);
    --border-sutil: rgba(28,28,26,.12);

    /* OLA 2: la paleta categorica vuelve al tema claro tambien.

       Cierra un agujero del contrato de impresion: el bloque restauraba
       .prod-img-cN a mano y no .inv-suc-barra-cN, asi que cada sitio
       nuevo que pintara una categoria habia que acordarse de anadirlo
       aqui. Ahora se restauran los TOKENS y con ellos los tres sitios
       que los leen -las iniciales de un producto, la barra de una
       sucursal y el anillo de Reportes- mas cualquiera que venga. */
    --cat-0: #4338ca;  --cat-0-bg: #eef2ff;  --cat-0-borde: #e0e7ff;
    --cat-1: #047857;  --cat-1-bg: #ecfdf5;  --cat-1-borde: #d1fae5;
    --cat-2: #c2410c;  --cat-2-bg: #fff7ed;  --cat-2-borde: #ffedd5;
    --cat-3: #be185d;  --cat-3-bg: #fdf2f8;  --cat-3-borde: #fce7f3;
    --cat-4: #0369a1;  --cat-4-bg: #f0f9ff;  --cat-4-borde: #e0f2fe;
    --cat-5: #a16207;  --cat-5-bg: #fefce8;  --cat-5-borde: #fef9c3;

    /* OLA 4: los ocho que todavia se colaban.
       El bloque restauraba superficies, estados y categorias pero no
       estos, que el tema oscuro tambien redefine. El que de verdad
       importa es --modal: #2b2b25 es casi negro, y un dialogo impreso
       habria salido como un rectangulo relleno. Los demas -velo,
       anillos de foco, halo, filo del panel de acceso- no llegan hoy
       al papel, pero dejarlos fuera es dejar el contrato a medias: la
       regla de esta seccion es que en print se restauran los TOKENS,
       no los sitios que los leen. */
    --modal:     #ffffff;
    --sh-modal:  none;
    --velo:      rgba(28,28,26,.5);
    --ring:       0 0 0 3px rgba(29,78,216,.22);
    --ring-error: 0 0 0 3px rgba(185,28,28,.18);
    --ring-ok:    0 0 0 3px rgba(21,128,61,.18);
    --brand-halo: rgba(29,78,216,.10);
    --acceso-marca-bg: #1e3a8a;

    /* El filo de luz es una idea del tema oscuro: en papel no existe. */
    /* OLA 6: los tres que nacieron en esta ola. --lienzo es el unico
       que se ve de verdad en papel -es un FONDO de pantalla- y vuelve
       al blanco; los otros dos son ideas del tema oscuro (luz de un
       boton, pieza hundida) y en papel no existen. */
    --lienzo:     #ffffff;
    --sh-btn-luz: none;
    --hundido:    transparent;

    --filo-luz: none;
    --contorno-luz: none;
  }
  /* El filo de luz de las superficies elevadas es una idea del tema
     oscuro. En papel no hay luz que venga de arriba. */
  :root[data-tema="oscuro"] .panel,
  :root[data-tema="oscuro"] .tarjeta,
  :root[data-tema="oscuro"] .bloque-sup,
  :root[data-tema="oscuro"] .modal,
  :root[data-tema="oscuro"] .toast { box-shadow: none; }
  :root[data-tema="oscuro"] .panel .panel,
  :root[data-tema="oscuro"] .panel .tarjeta { background: #ffffff; }
  :root[data-tema="oscuro"] .campo input,
  :root[data-tema="oscuro"] .campo select,
  :root[data-tema="oscuro"] .campo textarea,
  :root[data-tema="oscuro"] input,
  :root[data-tema="oscuro"] select,
  :root[data-tema="oscuro"] textarea { background: #ffffff; }
  :root[data-tema="oscuro"] .prod-img img,
  :root[data-tema="oscuro"] .imagen-previa { filter: none; }
  /* OLA 2: las seis clases de categoria salen de aqui. Los tokens
     --cat-* restaurados arriba ya las devuelven al tema claro. */
}


/* =====================================================================
   2. BASE
   ===================================================================== */

* { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  font-family: var(--font);
  background: var(--bg);
  color: var(--text);
  font-size: var(--fs-base);
  line-height: var(--lh);
  overflow-x: hidden;
  -webkit-font-smoothing: antialiased;
}

a { color: var(--brand-600); }
a:hover { color: var(--brand-700); }

/* Ningun elemento debe desbordar la pantalla */
img, table, pre { max-width: 100%; }

/* Foco visible para teclado, sin anillo al hacer clic con el raton.
   Antes solo existia :focus, que marcaba tambien el clic. */
:focus-visible {
  outline: var(--foco);
  outline-offset: 2px;
}

/* El atributo hidden manda sobre cualquier display de este archivo.
   Sin esto, .btn (inline-block), .modal-fondo (flex) o .fila (flex)
   siguen mostrando el elemento aunque el HTML diga hidden. Es la
   forma de ocultar cosas desde JavaScript sin tocar estilos en linea. */
[hidden] { display: none !important; }

/* Objetivo tactil minimo garantizado en cualquier accion */
button, .btn, .nav a { touch-action: manipulation; }


/* =====================================================================
   3. LOGIN
   ===================================================================== */



/* =====================================================================
   4. FORMULARIOS
   ===================================================================== */

.campo { margin-bottom: var(--s4); }
.campo label {
  display: block;
  font-size: var(--fs-sm);
  font-weight: 600;
  margin-bottom: var(--s1);
}

/* 16px es obligatorio: con menos, iOS hace zoom automatico al enfocar
   un campo y descuadra toda la pantalla.

   G4: casilla y radio dejan de heredar de aqui, pero NO con un :not()
   en este selector. Se probo y salio mal: dos :not([type=...]) suben
   la especificidad a (0,3,1) y a partir de ahi esta regla le ganaba a
   :focus (0,2,1), a :disabled (0,1,1) y al borde rojo del error. Medido
   en el navegador: el campo enfocado se quedaba sin anillo y sin borde
   de marca, y el campo con error salia con el borde de siempre.

   La forma correcta es no tocar esta regla y describir la casilla mas
   abajo: input[type="checkbox"] pesa lo mismo que .campo input y gana
   por ir despues, sin !important y sin desordenar los estados. */
.campo input, .campo select, .campo textarea,
input[type="search"], input[type="date"], input[type="number"],
input[type="text"], input[type="tel"], input[type="email"],
input[type="password"], select {
  width: 100%;
  padding: var(--s3);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  font-size: 16px;
  font-family: inherit;
  background: var(--surface);
  color: var(--text);
  min-height: var(--tap);
  transition: border-color var(--t-rapida) var(--ease),
              box-shadow var(--t-rapida) var(--ease);
}
.campo textarea { min-height: 80px; }

/* El foco se marca con :focus y no solo con :focus-visible a proposito:
   en una caja de texto interesa ver donde se esta escribiendo aunque se
   haya llegado con el raton. El anillo es el token del sistema. */
.campo input:focus, .campo select:focus, .campo textarea:focus,
input:focus, select:focus {
  outline: none;
  border-color: var(--brand-600);
  box-shadow: var(--ring);
}

input::placeholder, textarea::placeholder { color: var(--text-3); }

/* Campos deshabilitados: antes se veian identicos a los activos */
input:disabled, select:disabled, textarea:disabled {
  background: var(--surface-2);
  color: var(--text-3);
  cursor: not-allowed;
}

/* Solo lectura NO es lo mismo que deshabilitado: el dato se puede leer,
   copiar y enfocar, pero no escribir. Se veia igual que un campo
   normal, asi que invitaba a teclear en el. */
input[type="text"]:read-only,
input[type="number"]:read-only,
input[type="email"]:read-only,
input[type="tel"]:read-only,
textarea:read-only {
  background: var(--surface-2);
  border-style: dashed;
  cursor: default;
}

/* ---------- Casilla y radio ----------
   G4: no tenian ni tamano propio, ni foco, ni area de toque; se
   apoyaban en el tamano por omision del navegador (13px) dentro de una
   etiqueta sin altura minima. Ahora miden 18px, se tinen con el color
   de marca y su foco es el mismo anillo del resto. */
input[type="checkbox"],
input[type="radio"] {
  width: 18px;
  height: 18px;
  min-height: 0;
  flex: none;
  margin: 0;
  accent-color: var(--brand-600);
  cursor: pointer;
}
input[type="checkbox"]:focus-visible,
input[type="radio"]:focus-visible {
  outline: var(--foco);
  outline-offset: 2px;
}
input[type="checkbox"]:disabled,
input[type="radio"]:disabled { cursor: not-allowed; }

/* El area de toque no la da la casilla -18px no se acierta con el
   pulgar- sino la etiqueta que la envuelve, que es ademas lo que ya
   activa el control al pulsarla.

   Se agrupa aqui .casilla-legal, que es la que el registro publico ya
   usaba para lo mismo: un nombre viejo y uno del sistema, una sola
   definicion. */
.campo-casilla,
.casilla-legal {
  display: flex;
  align-items: flex-start;
  gap: var(--s2);
  min-height: var(--tap);
  cursor: pointer;
  font-size: var(--fs-sm);
  line-height: var(--lh);
}
.campo-casilla input,
.casilla-legal input { margin-top: var(--s0); }

/* ---------- Campo con error ----------
   El error no se dice SOLO con color: el control cambia de borde y de
   anillo, y el mensaje lleva peso propio y su icono. Asi sigue siendo
   legible en escala de grises y para quien no distingue el rojo.

   input.tiene-error, que era un segundo mecanismo para lo mismo escrito
   mil lineas mas abajo, queda unificado aqui. */
.campo.tiene-error input,
.campo.tiene-error select,
.campo.tiene-error textarea,
input.tiene-error {
  border-color: var(--error);
}
.campo.tiene-error input:focus,
.campo.tiene-error select:focus,
.campo.tiene-error textarea:focus,
input.tiene-error:focus {
  border-color: var(--error);
  box-shadow: var(--ring-error);
}

/* La clase del mensaje es .error-msg, que es la que ya usaban las tres
   pantallas que tenian error por campo. */
.error-msg {
  display: flex;
  align-items: flex-start;
  gap: var(--s1);
  margin: var(--s1) 0 0;
  font-size: var(--fs-xs);
  font-weight: var(--peso-fuerte);
  color: var(--error);
  line-height: var(--lh);
}
.error-msg::before {
  content: "!";
  flex: none;
  display: inline-grid;
  place-items: center;
  width: 14px;
  height: 14px;
  margin-top: 1px;
  border-radius: var(--r-full);
  background: var(--error);
  color: var(--surface);
  font-size: var(--fs-3xs);
  font-weight: var(--peso-max);
}

/* ---------- Grupo de campos ----------
   G4: un <fieldset> del navegador trae un marco de 1990 y un
   min-width:auto que rompe cualquier rejilla que lo contenga; por eso
   nadie lo usaba y habia 4 en todo el sistema para 146 campos. Esta
   regla lo deja utilizable, y a partir de ahi agrupar deja de tener
   coste.

   El rotulo del grupo NO es un titulo mas: va en versalitas y en color
   de segundo nivel, para que ordene sin competir con los rotulos de
   los campos que hay debajo. Es la misma forma que Configuracion se
   habia hecho por su cuenta -.admconfig-grupo/.admconfig-legend-, que
   pasa a usar esta y deja de tener las suyas. */
.campo-grupo {
  border: none;
  margin: 0 0 var(--s5);
  padding: 0;
  min-width: 0;
}
.campo-grupo:last-of-type { margin-bottom: var(--s4); }
.campo-grupo > legend {
  padding: 0;
  margin-bottom: var(--s3);
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: var(--ls-etiqueta);
  font-weight: var(--peso-fuerte);
  color: var(--text-2);
}

/* ---------- Dos campos en un renglon ----------
   UNA rejilla para todo el sistema, con UN corte. Antes cada
   formulario elegia el suyo: Configuracion y Avisos coincidieron en
   560 por casualidad y el resto usaba .fila, que es flex y reparte por
   contenido -dos campos del mismo renglon acababan con 17px de
   diferencia entre ellos, que es el defecto que Configuracion tuvo que
   corregir a mano-.

   560 es el ancho por debajo del cual dos campos de texto dejan de
   caber sin estrujar el valor. No es un breakpoint nuevo: es el que ya
   estaba en el archivo. */
.campos-rejilla {
  display: grid;
  gap: 0 var(--s4);
  align-items: start;
}
@media (min-width: 560px) {
  .campos-rejilla { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .campos-rejilla-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}


/* =====================================================================
   5. BOTONES
   ===================================================================== */

/* G3: EL BOTON MIDE LO QUE MIDE SU CONTENIDO.
   ---------------------------------------------------------------------
   Hasta aqui la base decia width:100%. Un boton no es ancho por
   naturaleza -lo es por el sitio donde esta-, asi que cada pantalla que
   ponia uno tenia que desdecir la regla. Contado antes de tocar nada:
   31 declaraciones "width: auto" repartidas por el archivo, incluidas
   tres variantes de COLOR (.btn-sec, .btn-plano, .btn-peligro-plano)
   que llevaban el ancho metido dentro, y comentarios en seis secciones
   distintas explicando el mismo tropiezo ("como hijo de un flex se iba
   a un renglon el solo", "el boton primario media 532px", "se estiraba
   a 721px por toda la columna").

   Invertida la base, esas 31 desaparecen y lo que queda son las reglas
   que de verdad QUIEREN el boton a lo ancho -pie de formulario en
   telefono, barra del punto de venta, cierre de caja-, que ahora se
   leen como lo que son: una decision del contexto y no una correccion.
   Para pedirlo desde una plantilla existe .ancho-total, que ya estaba
   en las utilidades y ya usaban cuatro vistas; no se inventa un
   .btn-bloque nuevo para decir lo mismo.

   Y de paso el boton se vuelve flex aqui, una vez. La alineacion del
   icono con su texto estaba escrita tres veces (.btn-con-icono,
   .btn-icono, .btn[data-espera]) y cada rincon que centraba un boton
   repetia justify-content:center. */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s2);
  width: auto;
  padding: var(--s3) var(--s4);
  min-height: var(--tap);
  background: var(--btn-primario-bg);
  color: var(--btn-primario-texto);
  border: 1px solid transparent;
  border-radius: var(--r-sm);
  font-size: var(--fs-base);
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
  text-align: center;
  text-decoration: none;
  transition: background var(--t-rapida) var(--ease),
              box-shadow  var(--t-rapida) var(--ease),
              transform   var(--t-rapida) var(--ease);
}
.btn:hover  { background: var(--btn-primario-bg-hover); color: var(--btn-primario-texto); }

.btn-sec {
  background: var(--surface);
  color: var(--text);
  border: 1px solid var(--border);
}
.btn-sec:hover { background: var(--surface-2); color: var(--text); border-color: var(--border-2); }

/* La variante primaria es la BASE: un .btn a secas ya es el boton
   fuerte. .btn-primario existia igualmente en el marcado -el "Cobrar"
   del punto de venta y su gemelo de telefono- sin una sola regla que
   la definiera; funcionaba de milagro, porque no pintar nada encima de
   la base da justo el aspecto que se buscaba. Se declara aqui, aunque
   no anada propiedades, para que la clase deje de estar suelta: si
   manana alguien cambia el color del primario, este es el sitio donde
   ira a buscarlo. */
.btn-primario { background: var(--btn-primario-bg); color: var(--btn-primario-texto); }
.btn-primario:hover { background: var(--btn-primario-bg-hover); color: var(--btn-primario-texto); }

/* Accion destructiva. Antes se pintaba a mano con style="..."
   El color del texto se declara AQUI. Antes lo heredaba de .btn, que
   desde esta pasada trae el blanco fijo del primario; el rojo del tema
   oscuro (#f87171) es claro y con letra blanca no se leeria. Se queda
   con --text-inv, que es justo lo que ya se pintaba: 6.47:1 en claro
   (#ffffff sobre #b91c1c) y 6.68:1 en oscuro (#14140f sobre #f87171).
   Sin cambio visible: solo deja de depender de la base. */
.btn-peligro { background: var(--error); color: var(--text-inv); border-color: transparent; }
.btn-peligro:hover { background: var(--error-fuerte); color: var(--text-inv); }

/* OLA 1: EL BOTON COMPACTO.
   ---------------------------------------------------------------------
   La accion que comparte renglon con un titulo de pantalla en un
   telefono. Existia siete veces -una por pantalla, acotada con :has()-
   y las siete no coincidian: 12 y 12.5px de letra, --s2, --s3 y 10px
   de relleno, 13, 14 y 15px de icono, gap de 4 y de 5.

   Define ESTRUCTURA Y ESCALA, no color. Se combina con la variante que
   toque -.btn a secas es el primario, .btn-sec el contorno,
   .btn-plano el fantasma, .btn-peligro el destructivo- y no cambia
   ninguna de ellas. El alto se queda en --tap: se aprieta lo que se
   puede apretar, no el objetivo tactil.

   Vive AQUI, con su familia, y no dentro de la seccion de la pantalla
   que lo necesito primero. La cabecera lo adopta por selector en 57b
   porque son 27 vistas; cualquier otro sitio que quiera lo mismo lo
   pide por esta clase. */
.btn-compacto {
  min-height: var(--tap);
  padding-inline: var(--s3);
  gap: var(--s1);
  font-size: var(--fs-xs);
  white-space: nowrap;
}
.btn-compacto .icono { width: var(--ico-sm); height: var(--ico-sm); }

/* Estado deshabilitado real, para toda variante */
.btn:disabled, .btn[aria-disabled="true"] {
  background: var(--surface-2);
  color: var(--text-3);
  border-color: var(--border);
  cursor: not-allowed;
  transform: none;
  box-shadow: none;
}


/* =====================================================================
   6. AVISOS
   ===================================================================== */

.aviso {
  padding: var(--s3);
  border-radius: var(--r-sm);
  border-left: 3px solid currentColor;
  font-size: var(--fs-base);
  margin-bottom: var(--s4);
}
.aviso-error  { background: var(--error-bg); color: var(--error); }
.aviso-ok     { background: var(--ok-bg);    color: var(--ok); }
.aviso-alerta { background: var(--warn-bg);  color: var(--warn); }
/* Faltaba el tono informativo: movimientos_lista.php lo pedia desde
   hace etapas y salia sin color, porque la clase no existia. */
.aviso-info   { background: var(--info-bg);   color: var(--info); }

/* Aviso que ademas ofrece la accion para resolverlo */
.aviso-accion {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s3);
  flex-wrap: wrap;
}


/* =====================================================================
   7. BARRA SUPERIOR
   ===================================================================== */

.topbar {
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  padding: var(--s3) var(--s4);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s3);
}
.topbar .marca { font-weight: 700; font-size: var(--fs-base); min-width: 0; }
.topbar .marca span {
  display: block; font-weight: 400; font-size: var(--fs-xs); color: var(--text-2);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 55vw;
}
.topbar .sesion { font-size: var(--fs-sm); color: var(--text-2); text-align: right; }
.topbar .sesion strong {
  display: block; color: var(--text);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 35vw;
}
.topbar a.salir { font-size: var(--fs-sm); text-decoration: none; }
.topbar a.salir:hover { text-decoration: underline; }


/* =====================================================================
   8. NAVEGACION
   ---------------------------------------------------------------------
   Se conserva intacta la tira de pildoras: funciona bien en movil y es
   lo que hay hoy. La barra lateral de escritorio llega en la Etapa 3;
   esta capa no cambia ninguna estructura HTML.
   ===================================================================== */

.nav {
  display: flex;
  gap: var(--s1);
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
  padding: var(--s2) var(--s4);
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  position: sticky;
  top: 0;
  z-index: var(--z-nav);
}
.nav::-webkit-scrollbar { display: none; }
.nav a {
  padding: var(--s2) var(--s4);
  border-radius: var(--r-full);
  font-size: var(--fs-sm);
  text-decoration: none;
  color: var(--text-2);
  white-space: nowrap;
  transition: background var(--t-rapida) var(--ease),
              color      var(--t-rapida) var(--ease);
}
.nav a:hover { background: var(--surface-2); color: var(--text); }
.nav a.activo { background: var(--brand-600); color: var(--text-inv); font-weight: 600; }
.nav a.activo:hover { background: var(--brand-700); color: var(--text-inv); }


/* =====================================================================
   8b. SHELL DE APLICACION  (Etapa 3)
   ---------------------------------------------------------------------
   Movil primero: hasta 1023px la barra lateral no existe (display:none)
   y la pagina se ve exactamente igual que antes. El shell de escritorio
   se enciende en el bloque de 1024px que esta mas abajo.
   ===================================================================== */

.app { display: block; }
.app-cuerpo { min-width: 0; }

/* La barra lateral no se pinta en celular ni en tablet */
.lateral { display: none; }

/* En celular la miga de pan de la barra superior estorba: el titulo
   de la pagina ya dice donde estas y el espacio es escaso. */
.topbar .miga { display: none; }


/* =====================================================================
   9. LAYOUT
   ===================================================================== */

.contenido {
  padding: var(--s5) var(--s4) 70px;
  max-width: var(--ancho-contenido);
  margin: 0 auto;
}
.contenido h2 {
  font-size: var(--fs-xl);
  line-height: var(--lh-tight);
  margin: 0 0 var(--s4);
}
.contenido h3 { font-size: var(--fs-lg); line-height: var(--lh-tight); margin: 0 0 var(--s3); }


/* =====================================================================
   10. TARJETAS
   ===================================================================== */

.tarjetas {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: var(--s3);
  margin-bottom: var(--s5);
}
.tarjeta {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  padding: var(--s4);
  box-shadow: var(--sh-1);
}
.tarjeta .etiqueta {
  font-size: var(--fs-xs);
  color: var(--text-2);
  text-transform: uppercase;
  letter-spacing: .4px;
}
.tarjeta .valor {
  font-size: var(--fs-2xl);
  font-weight: 700;
  margin-top: var(--s1);
  line-height: var(--lh-tight);
  overflow-wrap: break-word;
  font-variant-numeric: tabular-nums;
}


/* =====================================================================
   11. TABLAS
   ===================================================================== */

.tabla-wrap {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-1);
}
table { width: 100%; border-collapse: collapse; }
th, td {
  padding: var(--s3);
  text-align: left;
  border-bottom: 1px solid var(--border);
  font-size: var(--fs-base);
}
th {
  background: var(--surface-2);
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: .4px;
  color: var(--text-2);
  font-weight: 600;
}
tr:last-child td { border-bottom: none; }

/* Cifras alineadas: 1.250,00 y 980,00 comparables de un vistazo */
td.num, th.num { text-align: right; font-variant-numeric: tabular-nums; }

/* Realce de fila al pasar el raton. No aplica en movil: ahi la tabla
   ya no es tabla, son fichas. */
@media (hover: hover) and (min-width: 768px) {
  tbody tr:hover td, .tabla-wrap table tr:hover td { background: var(--surface-2); }
}


/* =====================================================================
   12. CHIPS DE ESTADO
   ---------------------------------------------------------------------
   La escala la manda stock_status() en includes/functions.php:

     agotado   stock <= 0                 rojo
     bajo      stock <= minimo            naranja  (cruzo el minimo)
     critico   stock <= minimo * 2        ambar    (va a caer pronto)
     normal    resto                      verde

   OJO con el nombre: "critico" es MENOS grave que "bajo", no mas.
   Antes 'bajo' y 'critico' compartian exactamente el mismo color y el
   usuario no podia distinguirlos (C8 de la auditoria). Ya no.
   ===================================================================== */

.chip {
  display: inline-block;
  padding: var(--s1) var(--s3);
  border-radius: var(--r-full);
  font-size: var(--fs-xs);
  font-weight: 600;
  line-height: 1.4;
  white-space: nowrap;
}
.chip-normal  { background: var(--ok-bg);    color: var(--ok); }
.chip-critico { background: var(--warn-bg);  color: var(--warn); }
.chip-bajo    { background: var(--crit-bg);  color: var(--crit); }
.chip-agotado { background: var(--error-bg); color: var(--error); }

/* Alias por si alguna pantalla escribe el estado en otras palabras */
.chip-activo, .chip-ok, .chip-completada { background: var(--ok-bg);    color: var(--ok); }
.chip-pendiente                          { background: var(--warn-bg);  color: var(--warn); }
.chip-inactivo, .chip-cancelada          { background: var(--error-bg); color: var(--error); }
.chip-info                               { background: var(--info-bg);  color: var(--info); }
.chip-neutro                             { background: var(--surface-2); color: var(--text-2); }
/* Tonos semanticos: valen para cualquier pantalla, no solo para
   estados de stock o de venta. */
.chip-alerta                             { background: var(--warn-bg);  color: var(--warn); }
.chip-error                              { background: var(--error-bg); color: var(--error); }


/* =====================================================================
   13. ESTADO VACIO
   ===================================================================== */

.vacio {
  padding: var(--s6) var(--s4);
  text-align: center;
  color: var(--text-2);
  font-size: var(--fs-base);
}


/* =====================================================================
   13b. COMPONENTES DE includes/ui.php  (Etapa 2)
   ---------------------------------------------------------------------
   Todo lo de esta seccion es aditivo: ninguna pagina existente usa
   estas clases todavia, asi que nada puede romperse. Las paginas se
   van pasando a los componentes en las Etapas 4 a 7.
   ===================================================================== */

/* ---------- Iconos ---------- */
/* Heredan el color del texto y se alinean con la linea base. */
.icono {
  display: inline-block;
  vertical-align: -.18em;
  flex-shrink: 0;
}

/* ---------- KPI ---------- */
/* Rejilla propia, mas ancha que .tarjetas: un importe como
   $318,905.50 a 26px no cabe en una tarjeta de 140px y se parte a
   mitad del numero. No se toca .tarjetas para no mover las paginas
   que todavia no usan componentes. */

/* Tono: colorea el numero, no el fondo. El fondo de color a pantalla
   completa cansa la vista de quien mira el tablero todo el dia. */

/* KPI que ademas es enlace (p. ej. "6 alertas" -> pagina de alertas) */

/* ---------- Boton con icono ---------- */
/* G3: la base .btn ya es inline-flex con gap, asi que esta clase no
   anade nada. Se conserva DEFINIDA -en vez de borrarla- porque la
   escriben doce plantillas y la pone ui_boton() cuando hay icono, y
   una clase escrita sin definicion es justo lo que G3 vino a quitar.
   Retirarla del marcado es G9. Su duplicado exacto, que estaba mil
   lineas mas abajo repitiendo el mismo gap, si desaparece. */
.btn-con-icono { gap: var(--s2); }

/* ---------- Encabezado de seccion ---------- */
.seccion-cab {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--s4);
  flex-wrap: wrap;
  margin-bottom: var(--s4);
}
.seccion-cab h2 { margin: 0; font-size: var(--fs-xl); line-height: var(--lh-tight); }
.seccion-desc { margin: var(--s1) 0 0; color: var(--text-2); font-size: var(--fs-sm); }
.seccion-acciones { display: flex; gap: var(--s2); flex-wrap: wrap; }

/* ---------- Conmutador de tema ----------
 El boton lleva en el HTML las DOS mitades -luna/"Tema oscuro" y
 sol/"Tema claro"- y el CSS ensena la que corresponde a la ACCION,
 nunca al estado: en claro se ofrece pasar a oscuro. Se decide con
 data-tema, que ya esta puesto antes del primer pintado, asi que el
 rotulo sale correcto sin esperar a JavaScript.
 Las etiquetas van dentro de .lateral-txt para que el riel de iconos
 y la barra contraida las escondan con la MISMA regla que al resto
 del menu, sin una excepcion propia. */
.tema-icono,
.tema-txt { display: none; }
.tema-icono-oscuro,
.tema-txt-oscuro { display: inline-flex; }
:root[data-tema="oscuro"] .tema-icono-oscuro,
:root[data-tema="oscuro"] .tema-txt-oscuro { display: none; }
:root[data-tema="oscuro"] .tema-icono-claro,
:root[data-tema="oscuro"] .tema-txt-claro { display: inline-flex; }

/* ---------- Miga de pan ---------- */
.miga {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--s1);
  font-size: var(--fs-sm);
  color: var(--text-2);
  margin-bottom: var(--s3);
}
.miga a { color: var(--text-2); text-decoration: none; }
.miga a:hover { color: var(--brand-600); text-decoration: underline; }
.miga [aria-current="page"] { color: var(--text); font-weight: 600; }
.miga-sep { display: inline-flex; color: var(--text-3); }

/* ---------- Estado vacio ---------- */
.estado-vacio {
  text-align: center;
  padding: var(--s6) var(--s4);
  color: var(--text-2);
}
.estado-vacio-icono { color: var(--text-3); margin-bottom: var(--s3); }
.estado-vacio-titulo {
  margin: 0;
  font-size: var(--fs-lg);
  font-weight: 600;
  color: var(--text);
}
.estado-vacio-texto {
  margin: var(--s2) auto 0;
  max-width: 44ch;
  font-size: var(--fs-sm);
}
.estado-vacio-cta { margin-top: var(--s4); }
/* Dentro de una tabla ya hay padding de celda: no se duplica. */
td.vacio .estado-vacio { padding: var(--s5) 0; }

/* ---------- Panel ---------- */
.panel {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-1);
  margin-bottom: var(--s4);
  overflow: hidden;
}
.panel-cab {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s3);
  flex-wrap: wrap;
  padding: var(--s4);
  border-bottom: 1px solid var(--border);
}
.panel-cab h3 { margin: 0; font-size: var(--fs-base); font-weight: 600; }
.panel-desc { margin: 2px 0 0; font-size: var(--fs-xs); color: var(--text-2); }
.panel-acciones { display: flex; gap: var(--s2); align-items: center; flex-wrap: wrap; }
/* G3: decia min-height:0 y el boton salia a 38px. Un boton de
   cabecera de panel pesa menos por COLOR y por tamano de letra; el
   area de toque no es donde se ahorra. Con los 40 aqui caen los tres
   parches identicos que se habian escrito en Categorias, Negocios y
   Permisos. */
.panel-acciones .btn { padding: var(--s2) var(--s3); min-height: 40px; font-size: var(--fs-sm); }

/* En telefono ese min-height: 0 deja el boton en 37px de alto. Que
   pese menos que la accion principal es cuestion de color y de
   tamano de letra, no de quitarle area de toque al dedo.

   El corte va en 1023 y no en 767: una tableta se toca con el dedo
   igual que un telefono, y todas las correcciones de area de toque
   se estaban cortando en 767 sin motivo. */
@media (max-width: 1023px) {
  .panel-acciones .btn { padding: var(--s2) var(--s4); }
}
/* ---------- La cabecera con azulejo   (Fase C) ----------
   El azulejo entra COMO PRIMER HIJO del flex que ya existia, y el
   bloque de titulo+descripcion pasa a quedarse con el sitio que sobra.
   El justify-content: space-between de la regla base sigue empujando
   las acciones al otro extremo, asi que no hay nada mas que decir:
   una linea de flex-grow y ya esta.

   Solo lo llevan los paneles que piden icono, y hoy solo los pide el
   tablero. Los demas paneles del sistema no ven esta regla. */
.panel-cab.con-azulejo > div:not(.panel-acciones) { flex: 1 1 auto; min-width: 0; }
.panel-cab.con-azulejo .azulejo { align-self: center; }

.panel-cuerpo { padding: var(--s4); }
/* Una tabla dentro de un panel no necesita su propio marco */
.panel-cuerpo.sin-espacio { padding: 0; }
.panel-cuerpo.sin-espacio .tabla-wrap { border: none; border-radius: 0; box-shadow: none; }

/* ---------- Rejillas de dashboard ---------- */
/* Movil primero: todo en una columna. Las variantes se activan cuando
   de verdad hay ancho, no antes. */
.rejilla { display: grid; grid-template-columns: 1fr; gap: var(--s4); margin-bottom: var(--s4); }

@media (min-width: 768px) {
  .rejilla-2 { grid-template-columns: 1fr 1fr; }
}
@media (min-width: 1024px) {
  /* Grafica ancha + panel lateral. La grafica necesita el doble de
     ancho para que las barras se puedan comparar entre si. */
  .rejilla-2-1 { grid-template-columns: 2fr 1fr; }

  /* Cada panel mide lo que necesita. Estirandolos a la altura del
     mas alto, uno con pocas filas se queda con un palmo de blanco
     dentro y se lee como un panel roto. */
  .rejilla-2, .rejilla-2-1 { align-items: start; }
  .rejilla-3   { grid-template-columns: repeat(3, 1fr); }
}

/* ---------- Barra de progreso ---------- */
/* El canal se ve. Con --bg sobre una tarjeta blanca era invisible, y
   una barra al 0% desaparecia entera: la fila de un producto agotado
   perdia la estructura que si tenian las demas. */
.barra {
  background: var(--border);
  border-radius: var(--r-full);
  height: 8px;
  overflow: hidden;
}
.barra span { display: block; height: 100%; border-radius: var(--r-full); background: var(--brand-600); }
.barra-ok span     { background: var(--ok); }
.barra-warn span   { background: var(--warn); }
.barra-crit span   { background: var(--crit); }
.barra-acento span { background: var(--accent-600); }
/* Faltaba el tono de error: la barra ya tenia ok, warn, crit y
   acento, pero no habia forma de pintar "agotado". */
.barra-error span  { background: var(--error); }

/* ---------- Grafica de barras (SVG en linea) ---------- */
.grafica-wrap { width: 100%; }
.grafica {
  width: 100%;
  height: auto;
  display: block;
  overflow: visible;
}
.grafica-rejilla { stroke: var(--border); stroke-width: 1; }
.grafica-base    { stroke: var(--border-2); stroke-width: 1; }
.grafica-eje,
.grafica-etiqueta {
  font-family: var(--font);
  font-size: var(--fs-2xs);
  fill: var(--text-2);
}
/* MEDIDO a 390px: el SVG se escala a 0.473, asi que la letra de 11
   unidades del viewBox acababa en 5.2 PIXELES de pantalla. Ilegible.
   El texto vive en unidades del viewBox, no en pixeles, asi que
   subirlo aqui lo agranda sin deformar nada: a 22 unidades sale a
   ~10px reales. Va en el COMPONENTE porque le pasa en cualquier
   pantalla que lo use, no solo en Reportes. */
/* La letra mas grande ocupa mas sitio, asi que caben MENOS etiquetas.
   El helper marca cada etiqueta con la pantalla mas estrecha en la que
   todavia cabe; aqui se esconden las que sobran. Sin esto, subir el
   cuerpo de la letra en telefono hacia que las etiquetas de un periodo
   largo se pisaran unas con otras. */
@media (max-width: 767px) {
  .grafica-eje,
  .grafica-etiqueta { font-size: 22px; }
  .grafica-etiqueta.etiq-md,
  .grafica-etiqueta.etiq-lg { display: none; }

  /* Con la letra de 22, la etiqueta de los extremos de una serie
     densa se sale del lienzo y el navegador la recorta. Anclarla
     hacia dentro la mete entera. El helper solo marca las que de
     verdad se salen, asi que una serie corta no se descentra. */
  .grafica-etiqueta.es-primera { text-anchor: start; }
  .grafica-etiqueta.es-ultima  { text-anchor: end; }
}
@media (min-width: 768px) and (max-width: 1023px) {
  .grafica-eje,
  .grafica-etiqueta { font-size: 14px; }
  .grafica-etiqueta.etiq-lg { display: none; }
}

/* ---------- Tono de la grafica ----------
   El tono NO se pinta barra por barra: cada grafica declara su color
   una sola vez en --graf-color y las barras lo consumen. Asi una
   grafica nueva solo necesita su clase de tono, y el dia que el tema
   oscuro se encienda basta con que el token cambie.

   La taxonomia es la misma que ya usan chip, metrica y barra de
   progreso (brand / acento / ok / warn / crit / error / info): no se
   inventa vocabulario nuevo para este componente. */
.grafica         { --graf-color: var(--brand-600); }
.grafica-brand   { --graf-color: var(--brand-600); }
.grafica-acento  { --graf-color: var(--accent-600); }
.grafica-ok      { --graf-color: var(--ok); }
.grafica-warn    { --graf-color: var(--warn); }
.grafica-crit    { --graf-color: var(--crit); }
.grafica-error   { --graf-color: var(--error); }
.grafica-info    { --graf-color: var(--info); }

/* La jerarquia se hace con opacidad sobre el MISMO color del tono, no
   con un segundo color por tono: un tono = un token. La base a .85
   deja sitio para que el hover suba a lleno y se note. */
.grafica-barra rect {
  fill: var(--graf-color);
  fill-opacity: .85;
  transition: fill-opacity var(--t-rapida) var(--ease);
}
.grafica-barra:hover rect { fill-opacity: 1; }

/* Barra sin valor: no lleva el tono, lleva el gris del canal. Si
   heredara la opacidad de arriba se leeria como un dato pequenio en
   vez de como "aqui no hubo nada". */
.grafica-barra.vacia rect { fill: var(--border); fill-opacity: 1; }

/* La barra destacada (hoy) va a color lleno y las demas del periodo
   bajan a contexto. Solo pasa cuando la grafica declara que TIENE una
   barra destacada: en una grafica sin "hoy", bajar todas seria apagar
   la grafica entera sin ganar ninguna jerarquia. */
.grafica-destaca .grafica-barra rect       { fill-opacity: .45; }
.grafica-destaca .grafica-barra.vacia rect { fill-opacity: 1; }
.grafica-barra.es-hoy rect,
.grafica-destaca .grafica-barra:hover rect { fill-opacity: 1; }
.grafica-etiqueta.es-hoy { fill: var(--text); font-weight: var(--peso-max); }

/* ---------- Lista de ranking ---------- */
.ranking-fila { padding: var(--s3) 0; border-bottom: 1px solid var(--border); }
.ranking-fila:first-child { padding-top: 0; }
.ranking-fila:last-child  { border-bottom: none; padding-bottom: 0; }
.ranking-cab {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--s3);
  margin-bottom: var(--s2);
  font-size: var(--fs-sm);
}
.ranking-cab strong { font-weight: 600; }
.ranking-cifra { white-space: nowrap; color: var(--text-2); font-variant-numeric: tabular-nums; }
.ranking-pie { font-size: var(--fs-xs); color: var(--text-2); margin-top: var(--s1); }

/* ---------- Modal ---------- */
.modal-fondo {
  position: fixed;
  inset: 0;
  z-index: var(--z-modal);
  background: var(--velo);
  display: flex;
  align-items: flex-end;
  justify-content: center;
  padding: var(--s4);
  overflow-y: auto;
}
/* Sin esto el display:flex de arriba gana sobre el hidden del navegador
   y el modal se queda visible aunque el atributo diga lo contrario. */
.modal-fondo[hidden] { display: none; }

/* OLA 4: el modal y la hoja estaban CAMBIADOS DE SITIO.
   El sistema tiene dos superficies temporales con nombre propio:
   --sheet, el color de algo apoyado en el borde de la pantalla, y
   --modal, el de un dialogo centrado que tiene toda la pagina detras.
   La seccion 57f hace el cambio correctamente para .hoja-panel: hoja
   por debajo de 768, dialogo por encima. El .modal no lo hacia nunca.

   Resultado en escritorio y en oscuro: la HOJA se pintaba de dialogo y
   el DIALOGO se pintaba de hoja. En claro no se notaba porque los dos
   son blancos, que es como llevaba tiempo pasando desapercibido.

   Se arregla aqui y en el bloque de 768 de mas abajo. Tambien se juntan
   los tres valores que decian lo mismo -92vh aqui, 90vh en telefono y
   --hoja-max (90dvh) en la hoja- en el token que ya existia para eso.

   La superficie base se declaraba --surface y la reescribia doce mil
   lineas mas abajo un `.modal { background: var(--sheet) }` suelto. Se
   escribe una vez, aqui. */
.modal {
  background: var(--sheet);
  border-radius: var(--r-lg) var(--r-lg) 0 0;
  box-shadow: var(--sh-3);
  width: 100%;
  max-width: 520px;
  max-height: var(--hoja-max);
  display: flex;
  flex-direction: column;
}
/* Modal cuyo contenido no usa cab/cuerpo/pie: lleva su propio margen */
.modal-simple { padding: var(--s4); overflow-y: auto; }

.modal-cerrar {
  background: none;
  border: none;
  color: var(--text-2);
  cursor: pointer;
  padding: var(--s2);
  margin: calc(var(--s2) * -1);
  border-radius: var(--r-sm);
  display: inline-flex;
  min-width: var(--tap);
  min-height: var(--tap);
  align-items: center;
  justify-content: center;
}
.modal-cerrar:hover { background: var(--surface-2); color: var(--text); }
.modal-cuerpo { padding: var(--s4); overflow-y: auto; }
.modal-pie {
  display: flex;
  gap: var(--s2);
  justify-content: flex-end;
  flex-wrap: wrap;
  padding: var(--s4);
  border-top: 1px solid var(--border);
}

/* En celular el modal sube desde abajo, que es donde esta el pulgar.
   Desde tablet se centra. */
@media (min-width: 768px) {
  .modal-fondo { align-items: center; }
  .modal { border-radius: var(--r-lg); }
}


/* =====================================================================
   13c. UTILIDADES  (Etapa 5)
   ---------------------------------------------------------------------
   Estas clases NO son un framework de utilidades al estilo Tailwind.
   Salieron de contar los 551 style="..." que habia repartidos en 29
   archivos y quedarse solo con los patrones que de verdad se repetian:
   texto secundario, filas flexibles, listas de dato/valor y botones
   chicos de tabla.

   Todo lo que aqui aparece existe porque estaba escrito a mano muchas
   veces. Si algo se usa una sola vez, no merece una clase: merece un
   componente en ui.php.
   ===================================================================== */

/* ---------- Texto ---------- */
.txt-2  { color: var(--text-2); }
.txt-3  { color: var(--text-3); }
.txt-ok    { color: var(--ok); }
.txt-error { color: var(--error); }

.txt-xs   { font-size: var(--fs-xs); }
.txt-sm   { font-size: var(--fs-sm); }
.txt-base { font-size: var(--fs-base); }
.txt-lg   { font-size: var(--fs-lg); }
.txt-xl   { font-size: var(--fs-xl); }

.negrita  { font-weight: 600; }
.fuerte   { font-weight: 700; }
.oculto   { display: none; }
/* Para lectores de pantalla: el texto existe y se lee, pero no se ve.
   Distinto de .oculto, que lo quita para todo el mundo. Nace con el
   fieldset del corte de caja, que necesita su legend sin ensenarla. */
.solo-lectura-pantalla {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}
.ancho-auto { width: auto; }
.centrado { text-align: center; }
.derecha  { text-align: right; }
.cifra    { font-variant-numeric: tabular-nums; white-space: nowrap; }

/* ---------- Filas flexibles ---------- */
/* El patron mas repetido del sistema: dos cosas en una linea que en
   celular se parten solas. */
.fila {
  display: flex;
  align-items: center;
  gap: var(--s3);
  flex-wrap: wrap;
}
.fila-entre  { justify-content: space-between; }
.fila-arriba { align-items: flex-start; }
.crece       { flex: 1; min-width: 150px; }
.crece-libre { flex: 1; min-width: 0; }
.en-linea    { display: inline; }
.bloque      { display: block; }
.ancho-total { width: 100%; }
.ancho-lectura { max-width: 640px; }

/* ---------- Espaciado ---------- */
.m0 { margin: 0; }
.mt-1 { margin-top: var(--s1); }     .mb-2 { margin-bottom: var(--s2); }
.mt-2 { margin-top: var(--s2); }     .mb-3 { margin-bottom: var(--s3); }
.mt-3 { margin-top: var(--s3); }     .mb-4 { margin-bottom: var(--s4); }
                                     .mb-5 { margin-bottom: var(--s5); }

/* ---------- Boton chico (acciones dentro de tablas) ---------- */
/* Un boton pequeno lo es para que la accion de una FILA no pese como la
   accion de la PANTALLA. Eso es cuestion de color y de tamano de letra;
   el area de toque NO es donde se ahorra.

   G3: median 34px en escritorio y subian a 40 por debajo de 1024. Eran,
   junto con las pestanas, los unicos controles del sistema por debajo
   del minimo comodo -34px es medio dedo- y ademas hacian falta DOS
   reglas para decirlo. Ahora arrancan en 40 y la segunda sobra. Con
   puntero grueso siguen subiendo a 44, unas lineas mas abajo. */
.btn-sm {
  min-height: 40px;
  padding: var(--s1) var(--s3);
  font-size: var(--fs-sm);
}

/* ---------- Rotulo ---------- */
/* Etiqueta chica sobre un dato, fuera de un formulario. */
.rotulo {
  display: block;
  font-size: var(--fs-xs);
  font-weight: 600;
  color: var(--text-2);
  margin-bottom: var(--s1);
  text-transform: uppercase;
  letter-spacing: .3px;
}

/* ---------- Lista de dato y valor ---------- */
/* Resumenes de venta, corte de caja y ticket: etiqueta a la izquierda,
   cifra a la derecha. Estaba escrito a mano 14 veces. */
.datos { margin: 0; }
.dato {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--s3);
  padding: var(--s2) 0;
  border-bottom: 1px solid var(--border);
  font-size: var(--fs-base);
}
.dato:last-child { border-bottom: none; }
/* Renglon de lista sin linea divisoria */
.linea { padding: var(--s2) 0; margin: 0; }

/* ---------- Piezas de pantalla reutilizables ---------- */
.min0 { min-width: 0; }
.fila-junta { display: flex; gap: var(--s2); flex-wrap: wrap; }

/* OLA 1: .buscador y .buscador-estado se declaran una sola vez, en la
   seccion 57d, junto al resto del componente. Aqui solo quedaban dos
   reglas sueltas de colocacion que ninguna vista llegaba a usar. */

/* Miniatura de producto en una fila de tabla */
.miniatura {
  width: 36px;
  height: 36px;
  object-fit: cover;
  border-radius: var(--r-sm);
  border: 1px solid var(--border);
  display: block;
}

/* Bloque de datos destacado dentro de un modal o tarjeta */
.caja-info {
  background: var(--bg);
  border-radius: var(--r-sm);
  padding: var(--s3);
  margin: var(--s4) 0;
}

/* Control de cantidad: menos, numero, mas */
.contador { display: flex; gap: var(--s2); align-items: center; }
.contador .btn { padding: 0 var(--s5); font-size: var(--fs-xl); }
.contador input {
  text-align: center;
  font-size: var(--fs-xl);
  font-weight: 700;
}

/* ---------- Barra de filtros ---------- */
/* =====================================================================
   G5 — LA BARRA DE FILTROS
   ---------------------------------------------------------------------
   Habia seis barras haciendo lo mismo con seis nombres: .filtros,
   .inv-filtros (movimientos e historial), .prod-filtros (catalogo),
   .admin-barra (negocios y usuarios), .act-filtro (actividad) y
   .rep-rango (reportes). Las seis eran flex, envolvian, y separaban con
   el mismo hueco; discrepaban solo en el alineado vertical, que es lo
   unico que de verdad cambia entre ellas.

   NO se unifican poniendoles a todas la clase .filtros en el marcado, y
   la razon importa: app.js engancha su hoja inferior de telefono a
   'form.filtros'. Ninguna plantilla usa hoy esa clase -las tres que
   filtran llevan id="filtros" y clase .inv-filtros o .prod-filtros-,
   asi que esa hoja nunca llega a ejecutarse. Ponersela ahora activaria
   de golpe un boton inyectado, una hoja, un velo y dos botones nuevos:
   seria cambiar el comportamiento, no consolidarlo. Queda anotado como
   deuda de app.js.

   Se consolida donde si se puede: una sola declaracion compartida por
   las seis. Cada pantalla conserva su nombre -y con el sus ganchos de
   JS y sus reglas propias-, pero la composicion se escribe una vez.
   ===================================================================== */
.inv-filtros,
.inv-filtros-sec,
.prod-filtros,
.act-filtro,
.rep-rango {
  display: flex;
  gap: var(--s3);
  flex-wrap: wrap;
  align-items: flex-end;
}
/* El catalogo y actividad alinean al centro: sus controles no llevan
   rotulo encima, asi que no hay linea de base que compartir. */
.prod-filtros,
.act-filtro { align-items: center; }

/* Un campo dentro de una barra de filtros no arrastra el margen que
   lleva dentro de un formulario, y reparte el ancho sobrante. */
.inv-filtros .campo,
.inv-filtros-sec .campo { margin-bottom: 0; flex: 1; min-width: 150px; }



/* =====================================================================
   14. CELULAR: LAS TABLAS SE APILAN COMO FICHAS
   ---------------------------------------------------------------------
   Una tabla de 8 columnas en una pantalla de 360px es ilegible aunque
   se pueda deslizar. app.js copia el nombre de cada columna al atributo
   data-label de sus celdas; aqui se usa como etiqueta de cada renglon.

   Esto ya funcionaba y es la mejor decision tecnica del proyecto.
   Se conserva sin un solo cambio de comportamiento.
   ===================================================================== */

@media (max-width: 767px) {

  .tabla-datos { border: none; background: none; overflow: visible; box-shadow: none; }
  .tabla-datos table, .tabla-datos tbody, .tabla-datos tr, .tabla-datos td { display: block; width: 100%; }

  /* La fila de titulos sobra: cada celda ya lleva su etiqueta en
     data-label. Se oculta por las dos vias porque solo 3 de las 24
     tablas del sistema usan <thead>; el resto escribe <tr><th> suelto
     y app.js les marca la fila con .fila-encabezado. */
  .tabla-datos thead,
  .tabla-datos .fila-encabezado { display: none; }

  .tabla-datos tr {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--r-md);
    margin-bottom: var(--s3);
    padding: var(--s1) 0;
    box-shadow: var(--sh-1);
  }

  .tabla-datos td {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: var(--s4);
    border: none;
    border-bottom: 1px solid var(--border);
    padding: var(--s2) var(--s3);
    text-align: right;
  }
  .tabla-datos tr td:last-child { border-bottom: none; }

  .tabla-datos td::before {
    content: attr(data-label);
    font-size: var(--fs-2xs);
    text-transform: uppercase;
    letter-spacing: .4px;
    color: var(--text-2);
    font-weight: 600;
    text-align: left;
    flex-shrink: 0;
  }
  /* Celdas sin encabezado (columna de acciones): sin etiqueta y a lo ancho */
  .tabla-datos td[data-label=""]::before { content: none; }
  .tabla-datos td[data-label=""] { justify-content: flex-end; flex-wrap: wrap; }

  .tabla-datos td.vacio { display: block; text-align: center; }
  .tabla-datos td.vacio::before { content: none; }

  /* Los formularios dentro de una celda no deben romper el ancho */
  .tabla-datos td form { display: inline-block; }

  .contenido h2 { font-size: var(--fs-lg); }
  .tarjeta .valor { font-size: var(--fs-xl); }
  .tarjetas { grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: var(--s3); }
}

/* Pantallas muy angostas */
@media (max-width: 379px) {
  .tarjetas { grid-template-columns: 1fr 1fr; }
  .topbar .marca { font-size: 14px; }
}


/* =====================================================================
   15. TABLET Y ESCRITORIO
   ===================================================================== */

@media (min-width: 768px) {
  .contenido { padding: var(--s6) var(--s5) 60px; }
  .topbar, .nav { padding-left: var(--s5); padding-right: var(--s5); }
  .campo input, .campo select, .campo textarea { font-size: var(--fs-base); }
  .tarjeta:hover { box-shadow: var(--sh-2); }
}


/* =====================================================================
   15b. ESCRITORIO: BARRA LATERAL  (Etapa 3 -> G1)
   ---------------------------------------------------------------------
   Desde 1280px aparece la barra lateral con etiquetas y desaparece la
   tira de pildoras. Debajo de 1280px queda el riel de iconos (bloque
   27b), y debajo de 768 el cajon con barra inferior.

   G1: este corte estaba en 1024 y era el origen del principal defecto
   de composicion del sistema. Ver la nota de --ancho-contenido-riel en
   la seccion de TOKENS y la seccion GLOBAL G1 al final del archivo.
   ===================================================================== */

@media (min-width: 1280px) {

  .app {
    display: grid;
    grid-template-columns: var(--ancho-lateral) minmax(0, 1fr);
    min-height: 100vh;
    min-height: 100dvh;
  }

  /* La tira de pildoras del menu deja su lugar a la barra lateral.
     Ojo: .nav-periodos reutiliza la misma clase para los filtros de
     Reportes y Solicitudes, y esos SI se quedan en escritorio. */
  .nav { display: none; }

  .lateral {
    display: flex;
    flex-direction: column;
    background: var(--surface);
    border-right: 1px solid var(--border);
    position: sticky;
    top: 0;
    height: 100vh;
    height: 100dvh;
    z-index: var(--z-nav);
    overflow: hidden;
  }

  /* ---------- Marca ---------- */
  .lateral-marca {
    display: flex;
    align-items: center;
    gap: var(--s3);
    padding: var(--s4);
    border-bottom: 1px solid var(--border);
    text-decoration: none;
    color: var(--text);
    min-height: 62px;
  }
  .lateral-logo {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 34px;
    height: 34px;
    flex-shrink: 0;
  }
  .lateral-marca-txt { min-width: 0; }
  .lateral-marca-txt strong {
    display: block;
    font-size: var(--fs-sm);
    line-height: 1.3;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .lateral-marca-txt small {
    display: block;
    font-size: var(--fs-xs);
    color: var(--text-2);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  /* ---------- Enlaces ---------- */
  .lateral-nav {
    flex: 1;
    overflow-y: auto;
    padding: var(--s3) var(--s2);
  }
  .lateral-grupo { margin-bottom: var(--s4); }
  .lateral-titulo {
    margin: 0 0 var(--s1);
    padding: 0 var(--s3);
    font-size: var(--fs-3xs);
    font-weight: 700;
    letter-spacing: .8px;
    color: var(--text-3);
  }
  .lateral-enlace {
    display: flex;
    align-items: center;
    gap: var(--s3);
    padding: var(--s2) var(--s3);
    margin-bottom: 1px;
    border-radius: var(--r-sm);
    color: var(--text-2);
    text-decoration: none;
    font-size: var(--fs-sm);
    font-weight: 500;
    white-space: nowrap;
    transition: background var(--t-rapida) var(--ease),
                color      var(--t-rapida) var(--ease);
  }
  .lateral-enlace:hover { background: var(--surface-2); color: var(--text); }
  .lateral-enlace .icono { color: var(--text-3); flex-shrink: 0; }
  .lateral-enlace:hover .icono { color: var(--text-2); }

  /* Estado activo: fondo suave y barra de color a la izquierda.
     El azul solido de las pildoras es demasiado peso repetido 11 veces
     en una columna. */
  .lateral-enlace.activo {
    background: var(--brand-50);
    color: var(--brand-600);
    font-weight: 600;
    box-shadow: inset 3px 0 0 var(--brand-600);
  }
  .lateral-enlace.activo .icono { color: var(--brand-600); }

  .lateral-txt {
    overflow: hidden;
    text-overflow: ellipsis;
  }

  .lateral-badge {
    margin-left: auto;
    background: var(--crit);
    color: var(--text-inv);
    font-size: var(--fs-2xs);
    font-weight: 700;
    line-height: 1;
    padding: 3px 7px;
    border-radius: var(--r-full);
  }

  /* ---------- Pie / boton de contraer y conmutador de tema ---------- */
  .lateral-pie { border-top: 1px solid var(--border); padding: var(--s2); }
  .lateral-tema,
  .lateral-toggle {
    display: flex;
    align-items: center;
    gap: var(--s3);
    width: 100%;
    padding: var(--s2) var(--s3);
    background: none;
    border: none;
    border-radius: var(--r-sm);
    color: var(--text-2);
    font-family: inherit;
    font-size: var(--fs-sm);
    cursor: pointer;
    white-space: nowrap;
  }
  .lateral-tema:hover,
  .lateral-toggle:hover { background: var(--surface-2); color: var(--text); }

  /* El area de toque del conmutador es de 44px en las tres formas del
     shell: es un control que se pulsa, no un rotulo. */
  .lateral-tema { min-height: var(--tap); }

  /* ---------- Contraida a 64px ---------- */
  .lateral-colapsada .app { grid-template-columns: var(--ancho-lateral-min) minmax(0, 1fr); }
  .lateral-colapsada .lateral-txt,
  .lateral-colapsada .lateral-marca-txt,
  .lateral-colapsada .lateral-titulo { display: none; }
  .lateral-colapsada .lateral-marca,
  .lateral-colapsada .lateral-enlace,
  .lateral-colapsada .lateral-tema,
  .lateral-colapsada .lateral-toggle { justify-content: center; padding-left: 0; padding-right: 0; }
  .lateral-colapsada .lateral-grupo { margin-bottom: var(--s3); }
  /* Sin titulos de grupo, una linea separa las secciones */
  .lateral-colapsada .lateral-grupo + .lateral-grupo {
    border-top: 1px solid var(--border);
    padding-top: var(--s3);
  }
  .lateral-colapsada .lateral-badge {
    position: absolute;
    margin: -18px 0 0 18px;
    padding: 2px 5px;
    font-size: var(--fs-3xs);
  }
  .lateral-colapsada .lateral-enlace { position: relative; }

  /* ---------- Barra superior con la lateral puesta ---------- */
  /* La marca ya vive en la barra lateral: aqui sobra y su lugar lo ocupa
     la miga de pan, que es lo que responde "donde estoy". */
  .topbar .marca { display: none; }
  .topbar .miga { display: flex; margin-bottom: 0; flex: 1; }
  .topbar {
    position: sticky;
    top: 0;
    z-index: calc(var(--z-nav) - 1);
  }

  /* El contenido puede respirar: 1100px dejaba media pantalla vacia en
     un monitor de 1440. */
  .contenido { max-width: var(--ancho-contenido-amplio); }
}


/* =====================================================================
   16. MOVIMIENTO REDUCIDO
   ---------------------------------------------------------------------
   Hoy casi no hay animaciones, pero las Etapas 7 y 8 las agregan.
   La regla queda puesta desde ahora para que nazcan ya respetando la
   preferencia del sistema operativo del usuario.
   ===================================================================== */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }

  /* Poner la duracion a .01ms no cancela un transform: solo hace que
     ocurra de golpe. Una tarjeta que salta al pasar el raton sigue
     siendo movimiento para quien pidio que no lo hubiera. */
  a.metrica:hover,
  .lp-beneficio:hover,
  .tarjeta:hover { transform: none !important; }
}


/* =====================================================================
   17. IMPRESION
   ===================================================================== */

@media print {
  .topbar, .nav, .lateral, .btn, .aviso { display: none !important; }
  .app { display: block !important; }
  body { background: #fff; color: #000; }
  .contenido { padding: 0; max-width: 100%; }
  .tarjeta, .tabla-wrap { border: 1px solid #ccc; box-shadow: none; }
}


/* =====================================================================
   18. PANTALLAS CON NECESIDADES PROPIAS  (Etapa 5)
   ---------------------------------------------------------------------
   Estas reglas vivian dentro de un <style> a mitad de su archivo PHP.
   No son "estilos en linea" pero tenian el mismo problema: el diseno
   del sistema repartido en varios sitios. Se traen aqui tal cual, sin
   cambiar comportamiento, y se agrupan por pantalla.
   ===================================================================== */

/* .rejilla-lateral y .ancho-ticket vivian aqui. Eran los dos unicos
   sitios donde se usaban y los usaba SOLO la pantalla del ticket, que
   ahora tiene su propia composicion (seccion 33). Se retiran en vez de
   quedarse como reglas muertas que la siguiente pantalla copiaria. */

/* ---------- Productos: botones rapidos de entrada y salida ---------- */
/* Registrar un movimiento es lo que mas se repite en el dia. Estos dos
   botones estan en cada fila para que no haya que abrir el producto. */
.btn-mov {
  width: 34px;
  height: 34px;
  padding: 0;
  border: 1px solid var(--border);
  background: var(--ok-bg);
  color: var(--ok);
  border-radius: var(--r-sm);
  font-size: 19px;
  font-weight: 700;
  line-height: 1;
  cursor: pointer;
  transition: filter var(--t-rapida) var(--ease);
}
.btn-mov:hover { filter: brightness(.95); }
.btn-mov-menos { background: var(--error-bg); color: var(--error); margin-left: var(--s1); }
.mTipo.activo { background: var(--brand-600); color: var(--text-inv); border-color: var(--brand-600); }

/* OLA 3: este bloque estaba a 640 y hacia tres cosas. Dos ya las hace
   el sistema, mejor y en el punto de corte correcto:

     #modalFondo { padding: 0; align-items: flex-end }
        .modal-fondo ya nace con align-items: flex-end y la seccion 42o
        le quita el relleno por debajo de 767.
     #modalCaja { max-height: 92vh; border-radius: --r-lg --r-lg 0 0 }
        la seccion 27a pone 90vh y 42o el radio de HOJA (--r-hoja) con
        su sombra, que es lo que hace que el modal se lea igual que el
        cajon y que las hojas. Los 92vh de aqui contradecian a los 90.

   Lo unico que hacia falta era el objetivo tactil de los dos botones
   de movimiento del catalogo, y baja con el resto al nivel de telefono.
   Entre 641 y 767 median 34px: por debajo del minimo, en un shell que
   ya era el de un telefono. */
@media (max-width: 767px) {
  .btn-mov { width: 40px; height: 40px; font-size: 21px; }
}

/* ---------- Punto de venta ---------- */


/* =====================================================================
   19. IMPRESION DE TICKET Y CORTE
   ---------------------------------------------------------------------
   Se imprime solo el documento: sin menu, sin barra lateral, sin
   botones y sin el formulario que lo genero.
   ===================================================================== */

@media print {
  .contenido { padding: 0; }
  a { color: #000; text-decoration: none; }

  /* Corte de caja */
  #areaCorte .tarjeta,
  #areaCorte .tabla-wrap,
  #areaCorte .panel {
    border: 1px solid #ccc;
    box-shadow: none;
    max-width: 100%;
  }

  /* ---------- Ticket ----------
     Lo que sale por la impresora sigue siendo el TEXTO GUARDADO de 32
     caracteres, NO el comprobante de la pantalla: es lo que entiende
     una termica de 58mm y es el documento que se archivo el dia de la
     venta. El comprobante es la vista web y no se imprime.

     La regla es una sola y va en el marcado, no aqui: todo lo que no
     sea el bloque del texto lleva .no-imprimir. Asi no hay que
     "resucitar" nada con !important ni con :has(), que es como acaban
     rompiendose las hojas de impresion. */
  .no-imprimir { display: none !important; }

  /* El titulo de la pagina no va en un ticket de papel. :has() no
     existe en navegadores viejos; si falla, solo se imprime el titulo. */
  .contenido:has(#areaTicket) h2 { display: none; }

  /* El texto sale solo, sin la tarjeta que lo presenta en pantalla. */
  .ticket-layout,
  .ticket-rail { display: block; }

  .ticket-impresion,
  .ticket-impresion .panel-cuerpo,
  .ticket-papel {
    margin: 0;
    padding: 0;
    border: none;
    background: none;
    box-shadow: none;
    max-width: 100%;
    overflow: visible;            /* el auto de pantalla cortaria el papel */
  }
  .ticket-texto { color: #000; }
}

@page { margin: 6mm; }

/* ---------- Punto de venta: piezas ---------- */
.sin-resultados { padding: var(--s4); color: var(--text-2); font-size: var(--fs-base); }
.totales {
  border-top: 1px solid var(--border);
  margin-top: var(--s4);
  padding-top: var(--s4);
}
/* Campo de cifra: angosto y alineado a la derecha, como en una caja */
.input-cifra    { width: 120px !important; text-align: right; }
.input-cantidad { width: 72px  !important; text-align: right; }

/* Selector de periodo: la misma tira de pildoras, sin marco propio */

/* Anchos de lectura por tipo de contenido */
/* Campo donde se teclea una cifra que hay que ver de lejos (caja) */

/* ---------- Ultimas piezas (Etapa 5) ---------- */
.ancho-formulario { max-width: 600px; }
.crece-medio      { flex: 1; min-width: 240px; }
.crece-corto      { flex: 1; min-width: 180px; }
.buscador-corto   { position: relative; flex: 1; min-width: 190px; }
.separador        { border: none; border-top: 1px solid var(--border); margin: var(--s5) 0; }
.lista-guia       { margin: 0; padding-left: var(--s5); font-size: var(--fs-base); color: var(--text-2); line-height: 1.8; }
.imagen-previa    { width: 80px; height: 80px; object-fit: cover; border-radius: var(--r-sm); border: 1px solid var(--border); display: block; }
/* .casilla-legal se define una sola vez, mas abajo. Esta copia
   existia para desdecir con !important la regla base que alcanzaba a
   las casillas; desde G4 la base ya no las alcanza. */

/* Sugerencias del buscador de inventario */

/* .ticket-texto se mudo a la seccion 33, con el resto del ticket. */


/* Botones de cantidad rapida (+1 +5 +10 +20) del modal de movimiento */
.btn-rapido {
  flex: 1;
  min-height: 36px;
  padding: var(--s2) 0;
  font-size: var(--fs-base);
}

/* OLA 3: aqui habia un bloque a 640px que ponia .filtros > select, >
   input, .campo y .btn a ancho completo. Se retira por dos motivos, y
   cualquiera de los dos basta:

     1. NO ALCANZA A NADIE. Ninguna vista escribe class="filtros".
        El plegado de filtros que si funciona va por data-plegar-filtros
        y por las clases propias de cada pantalla (.prod-filtros,
        .inv-filtros, .mov-barra).
     2. Su punto de corte era 640 y el de la hoja de filtros que lo
        sustituyo es 767, asi que ademas contradecia al sistema.

   El resto de reglas huerfanas de .filtros (seccion 27e) se quedan
   anotadas para la Ola 4: son 128 lineas y no son un punto de corte. */

/* =====================================================================
   20. AVISOS FLOTANTES (TOASTS)  (Etapa 7)
   ---------------------------------------------------------------------
   Sustituyen al <div class="aviso"> estatico que empujaba el contenido
   hacia abajo y se quedaba ahi hasta la siguiente recarga.

   Se pintan desde el servidor, no desde JavaScript: si el navegador no
   ejecuta scripts el mensaje se ve igual, solo que sin cerrarse solo.
   Eso importa porque los mensajes de flash confirman operaciones que ya
   ocurrieron ("producto eliminado") y perderlos seria peor que verlos
   de mas.
   ===================================================================== */

.toast-zona {
  position: fixed;
  z-index: var(--z-toast);
  right: var(--s4);
  bottom: var(--s4);
  left: var(--s4);
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: var(--s2);
  pointer-events: none;
}

@media (min-width: 768px) {
  /* En escritorio arriba a la derecha, que es donde se mira despues de
     hacer clic en Guardar. En celular abajo, cerca del pulgar. */
  .toast-zona {
    left: auto;
    /* Por debajo de la barra superior: si se monta encima tapa el nombre
       de usuario y el enlace de cerrar sesion. */
    top: 74px;
    bottom: auto;
    width: 380px;
    max-width: calc(100vw - var(--s6));
  }
}

.toast {
  pointer-events: auto;
  display: flex;
  align-items: flex-start;
  gap: var(--s3);
  padding: var(--s3) var(--s4);
  border-radius: var(--r-md);
  background: var(--surface);
  border: 1px solid var(--border);
  border-left: 3px solid var(--text-3);
  box-shadow: var(--sh-3);
  font-size: var(--fs-base);
  animation: toast-entra var(--t-media) var(--ease);
}
.toast.saliendo { animation: toast-sale var(--t-media) var(--ease) forwards; }

.toast-ok     { border-left-color: var(--ok); }
.toast-error  { border-left-color: var(--error); }
.toast-alerta { border-left-color: var(--warn); }
.toast-info   { border-left-color: var(--brand-600); }

.toast-ok     .toast-icono { color: var(--ok); }
.toast-error  .toast-icono { color: var(--error); }
.toast-alerta .toast-icono { color: var(--warn); }
.toast-info   .toast-icono { color: var(--brand-600); }

.toast-icono { flex-shrink: 0; margin-top: 1px; }
.toast-texto { flex: 1; min-width: 0; }
.toast-cerrar {
  flex-shrink: 0;
  background: none;
  border: none;
  padding: var(--s1);
  margin: calc(var(--s1) * -1);
  color: var(--text-3);
  cursor: pointer;
  border-radius: var(--r-sm);
  line-height: 0;
}
.toast-cerrar:hover { color: var(--text); background: var(--surface-2); }

@keyframes toast-entra {
  from { opacity: 0; transform: translateY(8px) scale(.98); }
  to   { opacity: 1; transform: none; }
}
@keyframes toast-sale {
  to { opacity: 0; transform: translateX(12px); height: 0; padding-top: 0; padding-bottom: 0; margin: 0; }
}


/* =====================================================================
   21. ESTADOS DE CARGA (SKELETON)
   ---------------------------------------------------------------------
   Mientras llega la respuesta del servidor se dibuja la forma de lo que
   va a aparecer, no un texto de "cargando". El usuario entiende que la
   pagina esta trabajando y no le cambia el alto de golpe al llegar.
   ===================================================================== */

.esqueleto {
  display: block;
  height: 12px;
  border-radius: var(--r-sm);
  background: linear-gradient(
    90deg,
    var(--surface-2) 25%,
    var(--border) 37%,
    var(--surface-2) 63%
  );
  background-size: 400% 100%;
  animation: esqueleto 1.2s ease-in-out infinite;
}
.esqueleto-corto { width: 45%; }
.esqueleto-medio { width: 70%; }

@keyframes esqueleto {
  from { background-position: 100% 50%; }
  to   { background-position: 0 50%; }
}

/* Fila de tabla en espera */
.fila-esqueleto td { padding: var(--s4) var(--s3); }

/* Con movimiento reducido el degradado se detiene: queda una barra
   gris fija, que sigue comunicando "aqui va a aparecer algo". */
@media (prefers-reduced-motion: reduce) {
  .esqueleto { animation: none; background: var(--surface-2); }
}


/* =====================================================================
   22. ENTRADA DE CONTENIDO
   ---------------------------------------------------------------------
   Solo las primeras filas y solo una vez. Nada que se repita mientras
   el usuario trabaja.
   ===================================================================== */

.aparece { animation: aparece var(--t-media) var(--ease) both; }

@keyframes aparece {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: none; }
}

/* El icono del estado vacio respira muy despacio: da sensacion de
   pantalla viva sin llamar la atencion. */
.estado-vacio-icono { animation: respira 4s ease-in-out infinite; }

@keyframes respira {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-4px); }
}

/* El escalonado va aqui y no en JavaScript: el retraso de cada fila es
   una propiedad de presentacion, no una decision de logica. */
.aparece:nth-child(1)  { animation-delay: 0ms; }
.aparece:nth-child(2)  { animation-delay: 22ms; }
.aparece:nth-child(3)  { animation-delay: 44ms; }
.aparece:nth-child(4)  { animation-delay: 66ms; }
.aparece:nth-child(5)  { animation-delay: 88ms; }
.aparece:nth-child(6)  { animation-delay: 110ms; }
.aparece:nth-child(7)  { animation-delay: 132ms; }
.aparece:nth-child(8)  { animation-delay: 154ms; }
.aparece:nth-child(9)  { animation-delay: 176ms; }
.aparece:nth-child(10) { animation-delay: 198ms; }
.aparece:nth-child(11) { animation-delay: 220ms; }
.aparece:nth-child(12) { animation-delay: 242ms; }


/* =====================================================================
   23. PANTALLA DE ENTRADA  (Etapa 8)
   ---------------------------------------------------------------------
   Un cubo isometrico que gira despacio mientras carga la primera
   pantalla de la sesion.

   Reglas que se cumplen sin excepcion:
   1. Solo la primera vez de cada sesion. Verla en cada clic seria un
      peaje, no una experiencia.
   2. Se quita en cuanto el DOM esta listo, y como maximo a los 1.1 s.
   3. El desvanecido lo hace el CSS, NO el JavaScript. Si un script
      falla, la pantalla se va igual: nunca puede dejar el sistema
      tapado.
   4. Con movimiento reducido no gira ni entra: se va y ya.

   Peso: ~2 KB de CSS y SVG. Sin WebGL, sin librerias, sin descargas.
   ===================================================================== */

.carga {
  position: fixed;
  inset: 0;
  z-index: 9999;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--s5);
  background: var(--bg);
  animation: carga-sale 320ms var(--ease) 1100ms forwards;
}

/* Ya se vio en esta sesion: no se pinta nunca. La clase la pone un
   script minimo en el <head>, antes del primer pintado. */
.carga-lista .carga { display: none; }

/* 96x96 era la caja del cubo que habia antes aqui. El isotipo no es
   cuadrado: se le da su proporcion (2.29:1) para que ocupe la caja
   entera y el vaiven de rotateY gire la MARCA, no un rectangulo con la
   marca dentro y aire alrededor. */
.carga-caja {
  width: 150px;
  height: 66px;
  perspective: 420px;
}
.carga-cubo {
  width: 100%;
  height: 100%;
  animation: carga-gira 3.2s ease-in-out infinite;
  transform-style: preserve-3d;
}
.carga-cubo svg { width: 100%; height: 100%; display: block; }

.carga-marca {
  font-size: var(--fs-sm);
  font-weight: 600;
  letter-spacing: .3px;
  color: var(--text-2);
}

.carga-barra {
  width: 130px;
  height: 3px;
  border-radius: var(--r-full);
  background: var(--border);
  overflow: hidden;
}
.carga-barra span {
  display: block;
  height: 100%;
  width: 40%;
  border-radius: var(--r-full);
  background: var(--brand-600);
  animation: carga-avanza 1.1s var(--ease) infinite;
}

@keyframes carga-gira {
  0%, 100% { transform: rotateY(-18deg) rotateX(4deg); }
  50%      { transform: rotateY(18deg)  rotateX(-4deg); }
}
@keyframes carga-avanza {
  from { transform: translateX(-120%); }
  to   { transform: translateX(340%); }
}
@keyframes carga-sale {
  to { opacity: 0; visibility: hidden; }
}

/* Sin animaciones: la pantalla se va enseguida y el cubo se queda
   quieto. Sigue siendo una espera con marca, no una pantalla en blanco. */
@media (prefers-reduced-motion: reduce) {
  .carga { animation: carga-sale 1ms linear 400ms forwards; }
  .carga-cubo, .carga-barra span { animation: none; }
  .carga-barra span { width: 100%; }
}


/* =====================================================================
   24. MICROINTERACCIONES
   ---------------------------------------------------------------------
   Cada una responde a la pregunta del brief: mejora la experiencia o
   solo se ve bonita. Las que quedaron son las que confirman que el
   sistema recibio la accion.
   ===================================================================== */

/* El boton se hunde al pulsarlo: confirma el clic antes de que llegue
   la respuesta del servidor.

   OLA 4: esta es LA regla, y ahora es la unica. Habia tres mas que
   decian exactamente lo mismo -.btn:active suelto arriba, el de la
   tarjeta de acceso y el de la llamada del tablero- y las tres
   apuntaban a elementos que ya son .btn. La de arriba ademas hundia
   los botones DESHABILITADOS, que es justo lo que no debe moverse.

   El scale(.995) no usa --pulsa ni --pulsa-lg a proposito: aqui no se
   encoge nada, se HUNDE. El medio pixel de escala solo acompana al
   translateY para que el hundimiento no se lea plano. Es un gesto, no
   una intensidad de la escala de pulsado. */
.btn:active:not(:disabled) { transform: translateY(1px) scale(.995); }

/* Los botones + y - de stock crecen un poco: son objetivos chicos que
   se usan muchas veces al dia. */
.btn-mov { transition: transform var(--t-rapida) var(--ease), filter var(--t-rapida) var(--ease); }
.btn-mov:hover  { transform: scale(1.06); }
.btn-mov:active { transform: scale(var(--pulsa-lg)); }

/* La tarjeta de KPI que ademas es enlace se levanta al pasar por
   encima: avisa de que se puede pulsar. */

/* Las filas de ranking y los renglones de dato marcan el foco del
   raton sin llegar a parpadear. */
.ranking-fila { transition: background var(--t-rapida) var(--ease); border-radius: var(--r-sm); }
.ranking-fila:hover { background: var(--surface-2); }

/* Las cifras que se animan al cargar reservan su ancho para que el
   texto de al lado no se mueva mientras cuentan. */
.valor { font-variant-numeric: tabular-nums; }


/* =====================================================================
   25. PANTALLA DE ACCESO  (reescrita en la Etapa 2)
   ---------------------------------------------------------------------
   El login es la pieza de marketing mas vista del producto: la abre
   todo el mundo, todos los dias, incluso quien ya compro. La version
   anterior era un panel azul plano con una ilustracion de cajas al lado
   de un formulario suelto en un campo blanco. Se veia limpio y no se
   veia caro, y sobre todo no se parecia a la portada.

   Ahora es la continuacion de la landing:
     - el mismo lavado de color de marca
     - el mismo logotipo
     - el MISMO producto de la portada, a escala, en el panel izquierdo
     - el formulario dentro de una tarjeta elevada, no flotando

   En telefono el panel de marca desaparecia entero y quedaba una
   tarjeta generica en un campo gris: la persona perdia de golpe todo
   el discurso. Ahora se conserva una cabecera de marca compacta.

   Sirve para login.php, registro.php y recuperar.php.
   ===================================================================== */

.acceso {
  min-height: 100vh;
  min-height: 100dvh;
  display: grid;
  grid-template-columns: 1fr;
  background: var(--bg);
  position: relative;
  isolation: isolate;
}

/* El mismo lavado del hero de la portada. Es lo que hace que login y
   landing se lean como la misma cosa. */
.acceso::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background:
    radial-gradient(46% 38% at 88% 6%, rgba(29,78,216,.10), transparent 66%),
    radial-gradient(40% 34% at 4% 96%, rgba(13,148,136,.08), transparent 64%);
  pointer-events: none;
}


/* =====================================================================
   PANEL DE MARCA (izquierda, desde 900px)
   ===================================================================== */

.acceso-marca {
  display: none;                 /* en telefono no aparece */
  flex-direction: column;
  justify-content: center;
  gap: var(--s6);
  padding: var(--s7) clamp(32px, 4vw, 64px);
  background: var(--brand-700);
  color: #fff;
  position: relative;
  overflow: hidden;
}

.acceso-marca::before {
  content: "";
  position: absolute;
  inset: 0;
  background:
    radial-gradient(54% 46% at 84% 4%,  rgba(13,148,136,.40), transparent 62%),
    radial-gradient(48% 44% at 6% 100%, rgba(59,130,246,.34), transparent 60%);
  pointer-events: none;
}

.acceso-logo {
  display: flex;
  align-items: center;
  gap: var(--s3);
  font-weight: var(--peso-max);
  font-size: var(--fs-lg);
  letter-spacing: var(--ls-titulo);
  position: relative;
  z-index: 1;
}
/* Sin azulejo: el isotipo se pinta directamente sobre el panel. Llevaba
   un cuadro translucido con borde porque dentro habia un icono de trazo
   en currentColor y sin caja no se sostenia. El asset oficial es una
   imagen a color con su propio alfa y no necesita montura; dejarsela
   seria pintar un rectangulo alrededor de un logotipo transparente. */
.acceso-logo span {
  display: grid;
  place-items: center;
  width: 38px; height: 38px;
  flex: none;
}

.acceso-lema {
  position: relative;
  z-index: 1;
  margin: 0;
  font-size: clamp(24px, 2.4vw, 32px);
  line-height: var(--lh-titulo);
  letter-spacing: var(--ls-display);
  font-weight: var(--peso-max);
  text-wrap: balance;
}

.acceso-puntos, .acceso-pasos {
  position: relative;
  z-index: 1;
  margin: var(--s4) 0 0;
  padding: 0;
  display: grid;
  gap: var(--s3);
  list-style: none;
  color: rgba(255,255,255,.86);
  font-size: var(--fs-base);
}
.acceso-puntos li {
  display: flex;
  align-items: flex-start;
  gap: var(--s3);
  line-height: 1.5;
}
/* Mismo motivo que en .acceso-pasos-claros: bloque con sangria, no
   flex, para que el contenido pueda llevar etiquetas dentro. */
.acceso-pasos li {
  position: relative;
  padding-left: 36px;
  min-height: 24px;
  line-height: 1.5;
}
.acceso-puntos .icono { flex: none; margin-top: var(--s0); opacity: .9; }

/* Los pasos del registro van numerados */
.acceso-pasos { counter-reset: ap; }
.acceso-pasos li { counter-increment: ap; }
.acceso-pasos li::before {
  content: counter(ap);
  position: absolute;
  left: 0;
  top: 0;
  display: grid;
  place-items: center;
  width: 24px; height: 24px;
  border-radius: var(--r-full);
  background: rgba(255,255,255,.18);
  border: 1px solid rgba(255,255,255,.24);
  font-size: var(--fs-xs);
  font-weight: var(--peso-max);
  color: #fff;
}


/* =====================================================================
   EL PRODUCTO DENTRO DEL PANEL
   ---------------------------------------------------------------------
   La misma ventana de la portada, reducida. No es una ilustracion de
   cajas: es el sistema. Quien entra todos los dias ve su herramienta,
   no un dibujo.
   ===================================================================== */

.acceso-producto {
  position: relative;
  z-index: 1;
  background: rgba(255,255,255,.10);
  border: 1px solid rgba(255,255,255,.18);
  border-radius: var(--r-lg);
  padding: var(--s4);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  display: grid;
  gap: var(--s3);
}

.acceso-prod-cab {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  font-size: var(--fs-xs);
  color: rgba(255,255,255,.72);
}
.acceso-prod-cab strong { color: #fff; font-size: var(--fs-sm); }

.acceso-prod-cifra {
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-3xl);
  font-weight: var(--peso-max);
  letter-spacing: var(--ls-titulo);
  line-height: 1;
  color: #fff;
}
.acceso-prod-delta {
  font-size: var(--fs-xs);
  font-weight: var(--peso-fuerte);
  color: #86efac;
}

.acceso-prod-barras {
  display: flex;
  align-items: flex-end;
  gap: 5px;
  height: 46px;
}
.acceso-prod-barras i {
  flex: 1;
  border-radius: 3px 3px 0 0;
  background: rgba(255,255,255,.34);
  min-height: 4px;
}
.acceso-prod-barras i:last-child { background: rgba(255,255,255,.92); }

.acceso-prod-filas { display: grid; gap: var(--s2); }
.acceso-prod-fila {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s3);
  font-size: var(--fs-xs);
  color: rgba(255,255,255,.86);
  padding-top: var(--s2);
  border-top: 1px solid rgba(255,255,255,.14);
}
.acceso-prod-fila b { color: #fff; font-variant-numeric: tabular-nums; }
.acceso-prod-etq {
  padding: 2px 8px;
  border-radius: var(--r-full);
  font-size: var(--fs-3xs);
  font-weight: var(--peso-fuerte);
  background: rgba(255,255,255,.16);
  color: #fff;
}
.acceso-prod-etq-bajo { background: rgba(251,146,60,.28); color: #fed7aa; }


/* =====================================================================
   PANEL DEL FORMULARIO
   ===================================================================== */

.acceso-panel {
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: var(--s5) var(--s4);
  min-width: 0;
}
.acceso-panel-alto { justify-content: flex-start; padding-top: var(--s6); }

/* Antes el formulario iba suelto sobre el fondo. Una tarjeta con
   elevacion dice "esto es un objeto en el que se escribe" y es lo que
   separa una pantalla de acceso de un documento. */
.acceso-caja {
  width: 100%;
  max-width: 400px;
  margin-inline: auto;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-4);
  padding: clamp(24px, 3vw, 36px);
}
.acceso-caja-ancha { max-width: 520px; }

/* Marca en telefono: sin ella la pantalla no dice de que producto es */
.acceso-marca-movil {
  display: flex;
  align-items: center;
  gap: var(--s3);
  margin-bottom: var(--s5);
  padding-bottom: var(--s4);
  border-bottom: 1px solid var(--border);
}
/* Sin azulejo y SIN SOMBRA. La sombra levantaba un cuadrado solido del
   papel; debajo de un logotipo transparente lo unico que levantaria es
   un rectangulo vacio, que es justo lo que no puede pasar. */
.acceso-marca-movil > span {
  display: grid;
  place-items: center;
  width: 40px; height: 40px;
  flex: none;
}
.acceso-marca-movil strong {
  display: block;
  font-size: var(--fs-base);
  line-height: 1.2;
  letter-spacing: var(--ls-titulo);
}
.acceso-marca-movil small { color: var(--text-2); font-size: var(--fs-xs); }

.acceso-caja h1 {
  margin: 0 0 var(--s1);
  font-size: var(--fs-2xl);
  line-height: var(--lh-titulo);
  letter-spacing: var(--ls-titulo);
}
.acceso-caja .sub {
  margin: 0 0 var(--s5);
  color: var(--text-2);
  font-size: var(--fs-sm);
}

/* G3: la accion de estas pantallas ocupa el ancho de la caja. Antes lo
   heredaba del width:100% de .btn, que era un accidente afortunado: la
   base ya no lo hace y aqui si se quiere, asi que se dice. Medido sin
   esta regla, en una caja de 335px: "Entrar" pasaba de 335 a 138 y
   "Continuar" del registro de 370 a 167. */
.acceso-caja form .btn { margin-top: var(--s2); width: 100%; }
/* La fila de decision del ultimo paso del registro es una FILA: dos
   botones uno al lado del otro, no dos bloques apilados.

   EL SELECTOR LLEVA `form` A PROPOSITO, Y ESE ES EL ARREGLO.
   ---------------------------------------------------------------------
   Esta regla existia y NO se estaba aplicando. Decia
   `.registro-acciones .btn`, que son dos clases (0,2,0), y justo encima
   `.acceso-caja form .btn` son dos clases y un elemento (0,2,1). Gana la
   de arriba aunque este antes: la cascada mira la especificidad primero
   y el orden solo despues. El `width:100%` seguia puesto en los dos
   botones.

   MEDIDO en una tarjeta de 350px, antes de tocar nada: "Atras" se
   quedaba con los 291px de la fila entera -es flex:none, asi que usa su
   width como base y no encoge- y "Enviar solicitud" se iba a 920..1050
   cuando el borde derecho de la tarjeta esta en 938. Ciento doce pixeles
   por fuera, colgando sobre el fondo. En telefono es lo mismo: no es un
   problema de ancho de pantalla, es que un boton al 100% dentro de una
   fila de dos no deja sitio para el otro.

   Con `width: auto` de verdad aplicado, cada uno vuelve a lo suyo:
   "Atras" mide lo que ocupa su palabra (flex:none) y "Enviar solicitud"
   se queda con el resto (flex:1). min-width:0 es el cinturon: un hijo de
   flex no baja de su contenido minimo salvo que se le diga, y sin eso la
   fila volveria a desbordar en cuanto el rotulo creciera. */
.acceso-caja form .registro-acciones .btn { width: auto; }
.registro-acciones .btn:not(.btn-sec) { min-width: 0; }

.acceso-pie {
  margin: var(--s5) 0 0;
  text-align: center;
  font-size: var(--fs-sm);
  color: var(--text-2);
}
.acceso-pie a { font-weight: var(--peso-fuerte); }

.acceso-nota {
  margin: var(--s4) 0 0;
  padding-top: var(--s4);
  border-top: 1px solid var(--border);
  text-align: center;
  font-size: var(--fs-xs);
  color: var(--text-3);
  line-height: 1.5;
}

/* Volver a la portada: presente pero discreto */
.acceso-volver {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  margin-bottom: var(--s4);
  font-size: var(--fs-sm);
  color: var(--text-2);
  text-decoration: none;
}
.acceso-volver:hover { color: var(--brand-600); }
.acceso-volver .icono { transform: rotate(180deg); }


/* =====================================================================
   ERROR DE ACCESO
   ---------------------------------------------------------------------
   Era una franja rosa plana con el texto dentro. Ahora tiene icono,
   jerarquia y un filo de color: se ve que algo fallo antes de leerlo.
   ===================================================================== */

.acceso-error {
  display: flex;
  align-items: flex-start;
  gap: var(--s3);
  margin-bottom: var(--s4);
  padding: var(--s3) var(--s4);
  border-radius: var(--r-md);
  background: var(--error-bg);
  border: 1px solid rgba(185,28,28,.18);
  border-left: 3px solid var(--error);
  color: var(--error);
  font-size: var(--fs-sm);
  line-height: 1.45;
}
.acceso-error .icono { color: var(--error); flex: none; margin-top: 1px; }
.acceso-error strong { display: block; margin-bottom: var(--s0); color: var(--error); }

.acceso-aviso {
  display: flex;
  align-items: flex-start;
  gap: var(--s3);
  margin-bottom: var(--s4);
  padding: var(--s3) var(--s4);
  border-radius: var(--r-md);
  background: var(--warn-bg);
  border: 1px solid rgba(180,83,9,.16);
  border-left: 3px solid var(--warn);
  color: var(--warn);
  font-size: var(--fs-sm);
  line-height: 1.45;
}
.acceso-aviso .icono { color: var(--warn); flex: none; margin-top: 1px; }



/* =====================================================================
   CAMPO DE CONTRASENA
   ===================================================================== */

.campo-clave { position: relative; }
.campo-clave input { padding-right: 46px; }

.ver-clave {
  position: absolute;
  right: 6px;
  bottom: 6px;
  width: 38px;
  height: 38px;
  display: grid;
  place-items: center;
  background: none;
  border: none;
  border-radius: var(--r-sm);
  color: var(--text-3);
  cursor: pointer;
  transition: color var(--t-rapida) var(--ease),
              background var(--t-rapida) var(--ease);
}
.ver-clave:hover { color: var(--text); background: var(--surface-2); }
.ver-clave[aria-pressed="true"] { color: var(--brand-600); }


.clave-aviso { margin: var(--s1) 0 0; font-size: var(--fs-xs); }

/* Lo unico propio del bloque legal del registro: su color y su
   margen. La composicion y el area de toque las pone .campo-casilla,
   con la que esta agrupada arriba. */
.casilla-legal { gap: var(--s3); margin: var(--s4) 0; color: var(--text-2); }

.separador {
  border: none;
  border-top: 1px solid var(--border);
  margin: var(--s5) 0 var(--s4);
}


/* =====================================================================
   DOS PANELES DESDE 900px
   ===================================================================== */

@media (min-width: 900px) {
  .acceso { grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr); }
  .acceso-marca { display: flex; }
  .acceso-marca-movil { display: none; }
  .acceso-panel { padding: var(--s6); }
  .acceso-panel-alto { justify-content: center; padding-block: var(--s6); }
}

/* G1 NO toca este valor a proposito: la pantalla de acceso tiene una
   identidad visual propia y deliberada, y su composicion de dos
   columnas esta calibrada contra 1400, no contra el shell de la
   aplicacion autenticada -que aqui ni siquiera existe-. Queda como
   excepcion documentada. */
@media (min-width: 1400px) {
  /* En pantallas muy anchas el panel de marca no crece sin limite: el
     texto se separaria tanto del formulario que dejarian de leerse
     como una sola pantalla. */
  .acceso { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
  .acceso-caja { max-width: 420px; }
  .acceso-caja-ancha { max-width: 560px; }
  .acceso-marca > * { max-width: 520px; width: 100%; margin-inline: auto; }
}

/* Telefono muy pequeno: la tarjeta pierde el borde y ocupa todo */
@media (max-width: 379px) {
  .acceso-panel { padding: var(--s3); }
  .acceso-caja { padding: var(--s4); }
}




/* ---------------------------------------------------------------------
   25c. REGISTRO EN DOS PASOS  (Etapa 2)
   ---------------------------------------------------------------------
   Nueve campos apilados en una sola columna es la forma mas rapida de
   que alguien abandone un formulario: no se ve el final, no se sabe
   cuanto queda, y todo pesa lo mismo.
   --------------------------------------------------------------------- */

/* El indicador de avance viene con hidden desde el servidor y solo lo
   ensena el JavaScript. Si el script no carga, no se ve un indicador
   que no indica nada, y los dos pasos quedan visibles. */
.registro-avance {
  display: flex;
  align-items: center;
  gap: var(--s2);
  margin: 0 0 var(--s5);
  padding: 0;
  list-style: none;
}
.registro-avance li {
  display: flex;
  align-items: center;
  gap: var(--s2);
  flex: 1;
  min-width: 0;
  font-size: var(--fs-sm);
  color: var(--text-3);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.registro-avance li span {
  display: grid;
  place-items: center;
  flex: none;
  width: 26px; height: 26px;
  border-radius: var(--r-full);
  border: 1px solid var(--border-2);
  background: var(--surface);
  font-size: var(--fs-xs);
  font-weight: var(--peso-max);
  transition: background var(--t-media) var(--ease),
              color var(--t-media) var(--ease),
              border-color var(--t-media) var(--ease);
}
.registro-avance .es-actual { color: var(--text); font-weight: var(--peso-fuerte); }
.registro-avance .es-actual span {
  background: var(--brand-600);
  border-color: transparent;
  color: var(--text-inv);
  box-shadow: var(--sh-marca);
}
.registro-avance .es-hecho { color: var(--text-2); }
.registro-avance .es-hecho span {
  background: var(--ok-bg);
  border-color: transparent;
  color: var(--ok);
}

.registro-paso-titulo {
  margin: 0 0 var(--s4);
  font-size: var(--fs-xs);
  font-weight: var(--peso-fuerte);
  text-transform: uppercase;
  letter-spacing: var(--ls-etiqueta);
  color: var(--text-3);
}

.registro-acciones {
  display: flex;
  gap: var(--s3);
  margin-top: var(--s2);
}
.registro-acciones .btn-sec { flex: none; }
.registro-acciones .btn:not(.btn-sec) { flex: 1; }

.campo-opcional {
  font-weight: 400;
  color: var(--text-3);
  font-size: var(--fs-xs);
}
/* G4: UNA familia de ayuda. Configuracion y Avisos se habian
   escrito la suya -.admconfig-ayuda y .admavisos-ayuda- con el mismo
   aspecto y distinto nombre; sus cinco usos pasan a .campo-ayuda en
   esta misma pasada y aquellas dos reglas desaparecen.

   La ayuda es secundaria al rotulo y no compite con el valor: mismo
   cuerpo que el texto menudo del sistema, color de tercer nivel y la
   interlinea de lectura. */
.campo-ayuda {
  display: block;
  margin: var(--s1) 0 0;
  font-size: var(--fs-xs);
  /* G9: era --text-3 y daba 2.83:1 sobre el panel blanco, por debajo del
     minimo legible. La ayuda de un campo es TEXTO que hay que leer, no un
     adorno: le toca el color de segundo nivel (5.6:1). El token global no
     se toca -lo consumen 121 reglas, casi todas iconos, separadores y
     marcas de agua, donde --text-3 es justo lo que se quiere-. La
     jerarquia se mantiene: sigue siendo mas menuda y mas clara que su
     rotulo, que va en --text a 600. */
  color: var(--text-2);
  line-height: var(--lh);
}

/* Cada error en su renglon: cuatro errores seguidos en un parrafo se
   leen como uno solo y largo. */
.registro-error-linea { display: block; margin-top: var(--s0); }

/* Confirmacion del envio */
.registro-listo { text-align: center; margin-bottom: var(--s5); }
.registro-listo-icono {
  display: grid;
  place-items: center;
  width: 60px; height: 60px;
  margin: 0 auto var(--s4);
  border-radius: var(--r-full);
  background: var(--ok-bg);
  color: var(--ok);
}
.registro-listo h1 { margin-bottom: var(--s2); }
.registro-listo .sub { margin-bottom: 0; }




/* ---------------------------------------------------------------------
   25d. FASE 3: LO QUE FALTABA DESPUES DE MEDIR
   ---------------------------------------------------------------------
   Cinco cosas encontradas midiendo la pantalla a 360, 390, 768, 1024 y
   escritorio. Ninguna se veia en la captura de escritorio.
   --------------------------------------------------------------------- */


/* ---- 1. AREAS DE TOQUE DE LOS ENLACES -------------------------------
   Medido: "Volver al inicio" 20px de alto, "Registra tu negocio" 17px,
   "Olvidaste tu contrasena" 16px. En un telefono eso es un blanco que
   se falla, y el ultimo es justamente el que pulsa alguien que ya esta
   teniendo un mal momento.

   El area crece sin que el enlace cambie de aspecto: padding vertical
   compensado con un margen negativo, para que no mueva el texto de
   alrededor. */
.acceso-caja a,
.acceso-pie a,
.acceso-nota a {
  display: inline-block;
  padding-block: 11px;
  margin-block: -11px;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
  transition: color var(--t-rapida) var(--ease);
}
.acceso-volver { padding-block: 11px; margin-block: -11px 6px; }

/* En pantallas de dedo el blanco es todavia mayor */
@media (pointer: coarse) {
  .acceso-caja a,
  .acceso-pie a,
  .acceso-nota a { padding-block: 14px; margin-block: -14px; }
}


/* ---- 2. LA TARJETA NUNCA SE QUEDA SIN ALCANCE -----------------------
   .acceso-panel centraba con justify-content: center. Cuando el
   contenido pasa de alto (teclado abierto en un telefono corto, o un
   error de validacion que anade cuatro renglones), centrar recorta por
   ARRIBA y esa parte no se puede alcanzar ni con scroll: es un fallo
   clasico de flexbox.

   Con margin auto el sobrante se reparte solo mientras cabe, y en
   cuanto no cabe el contenido se apoya arriba y la pagina se desplaza
   con normalidad. */
.acceso-panel { justify-content: flex-start; }
.acceso-panel > .acceso-caja { margin-block: auto; }


/* ---- 3. MICROINTERACCIONES ------------------------------------------
   Discretas a proposito. Una pantalla de acceso que hace aspavientos no
   parece cara, parece que se esta luciendo. */

/* La tarjeta entra una vez, al cargar. */
@keyframes acceso-entra {
  from { opacity: 0; transform: translateY(10px) scale(.995); }
  to   { opacity: 1; transform: none; }
}
.acceso-caja { animation: acceso-entra var(--t-entrada) var(--ease-suave) both; }

/* El panel de marca entra un pelo despues: primero se ve donde hay que
   escribir, luego el discurso. */
@keyframes acceso-marca-entra {
  from { opacity: 0; }
  to   { opacity: 1; }
}
.acceso-marca { animation: acceso-marca-entra 620ms var(--ease-suave) 80ms both; }

/* La etiqueta se enciende con su campo: dice cual esta activo sin
   necesidad de mirar el borde. */
.campo label { transition: color var(--t-rapida) var(--ease); }
.campo:focus-within label { color: var(--brand-700); }

/* El campo enfocado se levanta muy poco del fondo. */
.acceso-caja .campo input {
  transition: border-color var(--t-rapida) var(--ease),
              box-shadow   var(--t-rapida) var(--ease),
              background   var(--t-rapida) var(--ease);
}
.acceso-caja .campo input:hover:not(:focus) { border-color: var(--border-2); }


/* El ojo de la contrasena gira un poco al cambiar de estado. */
.ver-clave svg { transition: transform var(--t-media) var(--ease); }
.ver-clave[aria-pressed="true"] svg { transform: scale(1.06); }

@media (prefers-reduced-motion: reduce) {
  .acceso-caja, .acceso-marca { animation: none; }
  .acceso-caja .btn:not(:disabled):active { transform: none; }
}


/* ---- 4. RELLENO AUTOMATICO DEL NAVEGADOR ----------------------------
   Chrome pinta los campos autocompletados de amarillo con su propia
   tipografia y se salta cualquier background. Es de los detalles que
   mas delatan a una interfaz: la pantalla que acabas de disenar
   aparece con dos cuadros amarillos.

   No hay forma de desactivarlo; lo que si se puede es empujar el color
   con una sombra interior enorme y una transicion larguisima, que es el
   truco que usa todo el mundo porque es el unico que funciona. */
.acceso-caja input:-webkit-autofill,
.acceso-caja input:-webkit-autofill:hover,
.acceso-caja input:-webkit-autofill:focus {
  -webkit-text-fill-color: var(--text);
  -webkit-box-shadow: 0 0 0 60px var(--surface) inset;
  box-shadow: 0 0 0 60px var(--surface) inset;
  caret-color: var(--text);
  transition: background-color 100000s ease 0s;
}
.acceso-caja input:-webkit-autofill:focus {
  -webkit-box-shadow: 0 0 0 60px var(--surface) inset, var(--ring);
  box-shadow: 0 0 0 60px var(--surface) inset, var(--ring);
}


/* ---- 5. EL OJO: AREA DE TOQUE COMPLETA -------------------------------
   Medía 38x38, por debajo del minimo comodo. El campo mide 46 de alto
   y arranca a 23 del borde del bloque, asi que alineando el boton con
   el campo se gana toda el area sin ocupar un pixel mas.

   Se ancla por ARRIBA y no por abajo a proposito: el mismo valor vale
   para el campo suelto del login y para el del registro, que lleva un
   texto de ayuda debajo. Asi no hace falta distinguirlos con :has(). */
.campo-clave { position: relative; }
.campo-clave .ver-clave {
  top: 23px;
  bottom: auto;
  right: 1px;
  width: 46px;
  height: 46px;
}


/* =====================================================================
   25e. TABLETA: NI TELEFONO GRANDE NI ESCRITORIO PEQUENO
   ---------------------------------------------------------------------
   El problema medido a 768x1024: una tarjeta de 400px flotando con
   226px de vacio arriba y otros 226 abajo. Tecnicamente correcto y
   comercialmente pobre: la tableta se llevaba el tratamiento del
   telefono y la pantalla se veia vacia.

   El panel de marca no cabe AL LADO a este ancho (quedarian 384px por
   columna), pero si cabe ENCIMA. Asi que a partir de 640px el mismo
   <aside> deja de ser una columna y se convierte en una banda
   horizontal sobre la tarjeta.

   No hay marcado nuevo: es la misma vista, colocada de otra manera.
   ===================================================================== */

@media (min-width: 768px) and (max-width: 899px) {

  .acceso {
    grid-template-rows: auto 1fr;
    align-content: start;
  }

  /* De columna lateral a banda superior */
  .acceso-marca {
    display: block;
    padding: var(--s6) var(--s5) var(--s5);
    border-radius: 0;
  }
  .acceso-marca > * {
    max-width: 560px;
    margin-inline: auto;
    width: 100%;
  }
  .acceso-logo { margin-bottom: var(--s5); }

  .acceso-lema {
    font-size: var(--fs-2xl);
    margin-bottom: var(--s4);
  }

  /* Los tres puntos en fila: ocupan el ancho y no alargan la banda */
  .acceso-puntos {
    display: flex;
    flex-wrap: wrap;
    gap: var(--s2) var(--s5);
    margin-top: 0;
    font-size: var(--fs-sm);
  }
  .acceso-puntos li { flex: 1 1 240px; }

  /* Los pasos numerados del registro, tambien en fila */
  .acceso-pasos {
    display: flex;
    flex-wrap: wrap;
    gap: var(--s4) var(--s5);
    margin-top: 0;
    font-size: var(--fs-sm);
  }
  .acceso-pasos li { flex: 1 1 200px; padding-left: 32px; }

  /* La ventana del producto se queda, pero TUMBADA: la cifra y la
     grafica una al lado de la otra en vez de apiladas. Asi entra en la
     banda sin alargarla el doble, y la pantalla se llena con producto
     en vez de con vacio. */
  .acceso-producto {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    align-items: center;
    gap: var(--s3) var(--s5);
    margin-top: var(--s5);
    max-width: 560px;
  }
  .acceso-producto > .acceso-prod-cab { grid-column: 1 / -1; }
  .acceso-prod-barras { height: 40px; }

  /* Las dos filas de la version con cifra alargarian la banda un
     renglon por cada una y no aportan a este tamano. */
  .acceso-prod-filas { display: none; }

  /* La version en LISTA es otra cosa: esas filas son todo su
     contenido. Sin ellas quedaba una caja con titulo y nada dentro.
     Aqui se conservan, repartidas en dos columnas. */
  .acceso-producto-lista { grid-template-columns: minmax(0, 1fr); }
  .acceso-producto-lista .acceso-prod-filas {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
    gap: var(--s2) var(--s5);
  }
  .acceso-producto-lista .acceso-prod-fila {
    border-top: none;
    padding-top: 0;
  }

  /* La marca de la tarjeta sobra: ya esta arriba, a lo grande */
  .acceso-marca-movil { display: none; }

  .acceso-panel { padding: var(--s6) var(--s5); }
  .acceso-caja { max-width: 460px; }
  .acceso-caja-ancha { max-width: 560px; }
}


/* =====================================================================
   26. PANTALLA DE ERROR  (Etapa 9)
   ---------------------------------------------------------------------
   Un error no deja de ser una pantalla del sistema. Antes eran texto
   negro sobre blanco sin salida; ahora tienen el mismo lenguaje que el
   resto y siempre ofrecen a donde ir.
   ===================================================================== */

.error-pantalla {
  min-height: 100vh;
  min-height: 100dvh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--s5) var(--s4);
  background: var(--bg);
}
.error-caja {
  width: 100%;
  max-width: 460px;
  text-align: center;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-2);
  padding: var(--s6) var(--s5);
}
.error-icono {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 64px;
  height: 64px;
  border-radius: var(--r-full);
  background: var(--warn-bg);
  color: var(--warn);
  margin-bottom: var(--s4);
}
.error-codigo {
  margin: 0 0 var(--s1);
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: .8px;
  text-transform: uppercase;
  color: var(--text-3);
}
.error-caja h1 {
  margin: 0 0 var(--s3);
  font-size: var(--fs-xl);
  line-height: var(--lh-tight);
}
.error-texto {
  margin: 0 auto;
  max-width: 42ch;
  color: var(--text-2);
  font-size: var(--fs-base);
}
.error-salidas {
  display: flex;
  gap: var(--s2);
  flex-wrap: wrap;
  justify-content: center;
  margin-top: var(--s5);
}

/* ---------- Bienvenida del primer acceso ---------- */
.bienvenida { max-width: 480px; }
.bienvenida-cab {
  padding: var(--s5) var(--s5) var(--s4);
  text-align: center;
  border-bottom: 1px solid var(--border);
}
/* Misma razon que en .acceso-hero-icono: la caja toma la proporcion del
   isotipo (2.29:1) en vez de imponerle un cuadrado. */
.bienvenida-icono {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 96px;
  height: 42px;
  margin-bottom: var(--s3);
}
.bienvenida-cab h2 { margin: 0 0 var(--s2); font-size: var(--fs-xl); line-height: var(--lh-tight); }
.bienvenida-cab p  { margin: 0 auto; max-width: 40ch; color: var(--text-2); font-size: var(--fs-sm); }

.bienvenida-pasos { margin: 0; padding: 0; list-style: none; }
.bienvenida-pasos li + li { margin-top: var(--s2); }
.bienvenida-pasos a {
  display: flex;
  align-items: center;
  gap: var(--s3);
  padding: var(--s3);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  text-decoration: none;
  color: var(--text);
  transition: border-color var(--t-rapida) var(--ease),
              background var(--t-rapida) var(--ease);
}
.bienvenida-pasos a:hover { border-color: var(--brand-600); background: var(--brand-50); color: var(--text); }
.bienvenida-num {
  flex-shrink: 0;
  width: 26px;
  height: 26px;
  border-radius: var(--r-full);
  background: var(--brand-600);
  color: var(--text-inv);
  font-size: var(--fs-xs);
  font-weight: 700;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.bienvenida-txt { flex: 1; min-width: 0; }
.bienvenida-txt strong { display: block; font-size: var(--fs-base); }
.bienvenida-txt small  { display: block; color: var(--text-2); font-size: var(--fs-xs); margin-top: var(--s0); }
.bienvenida-pasos .icono { color: var(--text-3); flex-shrink: 0; }


/* =====================================================================
   27. ARQUITECTURA RESPONSIVE
   =====================================================================
   Tres experiencias, un solo marcado. Lo que cambia es el CSS, nunca el
   HTML ni la logica:

     TELEFONO   < 768px   Barra superior compacta con boton de menu,
                          cajon deslizable sobre un velo, barra inferior
                          con los cuatro destinos mas usados.

     TABLET   768-1023px  Riel lateral de iconos de 64px, siempre a la
                          vista. Sin cajon y sin barra inferior: hay
                          ancho de sobra y el pulgar no manda.

     ESCRITORIO  1024px+  La barra lateral de 244px de siempre. No
                          cambia nada de lo que ya funcionaba.

   Las medidas tactiles salen de un token, no de numeros sueltos
   repartidos por el archivo.
   ===================================================================== */

:root {
  --barra-inf: 58px;      /* alto de la barra inferior, sin area segura */
  --topbar-movil: 56px;   /* alto de la barra superior en telefono      */
}


/* =====================================================================
   27a. TELEFONO  (hasta 767px)
   ===================================================================== */

@media (max-width: 767px) {

  /* ---------- Barra superior compacta ---------- */
  .topbar {
    position: sticky;
    top: 0;
    z-index: var(--z-nav);
    display: grid;
    grid-template-columns: auto 1fr auto auto;
    align-items: center;
    gap: var(--s2);
    min-height: var(--topbar-movil);
    padding: var(--s2) var(--s3);
    padding-top: calc(var(--s2) + env(safe-area-inset-top));
  }

  .topbar-menu {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 42px;
    height: 42px;
    margin-left: calc(var(--s1) * -1);
    background: none;
    border: none;
    border-radius: var(--r-md);
    color: var(--text);
    cursor: pointer;
  }
  .topbar-menu:active { background: var(--surface-2); }

  .topbar .marca { font-size: var(--fs-sm); min-width: 0; }
  .topbar .marca span { max-width: none; font-size: var(--fs-2xs); }

  /* El nombre completo no cabe. Esta en el cajon, con su correo. */
  .topbar .sesion { display: none; }

  .topbar-avisos {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 42px;
    height: 42px;
    border-radius: var(--r-md);
    color: var(--text-2);
    text-decoration: none;
  }
  .topbar-avisos:active { background: var(--surface-2); }
  .topbar-punto {
    position: absolute;
    top: 5px;
    right: 4px;
    min-width: 17px;
    height: 17px;
    padding: 0 4px;
    border-radius: var(--r-full);
    background: var(--crit);
    color: var(--text-inv);
    font-size: var(--fs-3xs);
    font-weight: 700;
    line-height: 17px;
    text-align: center;
  }

  /* ---------- Cajon del menu ---------- */
  .velo {
    position: fixed;
    inset: 0;
    z-index: calc(var(--z-modal) - 10);
    background: var(--velo);
    animation: velo-entra var(--t-media) var(--ease);
  }

  .lateral {
    display: flex;
    flex-direction: column;
    position: fixed;
    top: 0;
    bottom: 0;
    left: 0;
    z-index: var(--z-modal);
    width: min(310px, 86vw);
    background: var(--surface);
    border-right: 1px solid var(--border);
    box-shadow: var(--sh-3);
    overflow: hidden;
    padding-top: env(safe-area-inset-top);
    padding-bottom: env(safe-area-inset-bottom);

    /* Cerrado = display:none. NO se usa transform ni visibility con
       transicion para el estado abierto/cerrado.

       Motivo: si la transicion no llega a correr (un WebView que las
       estrangula, una pestana en segundo plano, un equipo saturado)
       el cajon se quedaria fuera de pantalla PARA SIEMPRE y el menu
       seria inalcanzable. Con display, el peor caso es que aparezca
       de golpe sin deslizarse: feo, pero funciona.

       display:none ademas lo saca del orden de tabulacion mientras
       esta cerrado, que es lo correcto. */
    display: none;
  }

  .menu-abierto .lateral {
    display: flex;
    animation: cajon-entra var(--t-media) var(--ease);
  }

  .lateral-cerrar {
    position: absolute;
    top: calc(var(--s3) + env(safe-area-inset-top));
    right: var(--s3);
    width: 38px;
    height: 38px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: none;
    border: none;
    border-radius: var(--r-md);
    color: var(--text-2);
    cursor: pointer;
    z-index: 1;
  }
  .lateral-cerrar:active { background: var(--surface-2); }

  .lateral-marca {
    display: flex;
    align-items: center;
    gap: var(--s3);
    padding: var(--s4);
    padding-right: 56px;
    border-bottom: 1px solid var(--border);
    text-decoration: none;
    color: var(--text);
  }
  .lateral-logo {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    flex-shrink: 0;
  }
  .lateral-marca-txt { min-width: 0; }
  .lateral-marca-txt strong { display: block; font-size: var(--fs-base); line-height: 1.25; }
  .lateral-marca-txt small  { display: block; font-size: var(--fs-xs); color: var(--text-2); }

  .lateral-nav {
    flex: 1;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    padding: var(--s3) var(--s3) var(--s5);
  }
  .lateral-grupo { margin-bottom: var(--s4); }
  .lateral-titulo {
    margin: 0 0 var(--s2);
    padding: 0 var(--s3);
    font-size: var(--fs-3xs);
    font-weight: 700;
    letter-spacing: .8px;
    color: var(--text-3);
  }
  .lateral-enlace {
    display: flex;
    align-items: center;
    gap: var(--s3);
    min-height: 48px;   /* el dedo acierta sin apuntar */
    padding: var(--s2) var(--s3);
    border-radius: var(--r-md);
    color: var(--text);
    text-decoration: none;
    font-size: var(--fs-base);
  }
  .lateral-enlace .icono { color: var(--text-3); flex-shrink: 0; }
  .lateral-enlace:active { background: var(--surface-2); }
  .lateral-enlace.activo {
    background: var(--brand-50);
    color: var(--brand-600);
    font-weight: 600;
  }
  .lateral-enlace.activo .icono { color: var(--brand-600); }
  .lateral-badge {
    margin-left: auto;
    background: var(--crit);
    color: var(--text-inv);
    font-size: var(--fs-2xs);
    font-weight: 700;
    line-height: 1;
    padding: 4px 8px;
    border-radius: var(--r-full);
  }

  /* En el cajon, el pie es la cuenta: quien soy y como salgo. */
  .lateral-pie { border-top: 1px solid var(--border); padding: var(--s3); }
  .lateral-toggle { display: none; }

  /* En el cajon el conmutador es una fila mas del pie, con el mismo
     alto y la misma forma que Cerrar sesion. Aqui SI se ve: contraer
     la barra no tiene sentido en telefono, cambiar el tema si. */
  .lateral-tema {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--s2);
    width: 100%;
    min-height: var(--tap);
    margin-top: var(--s2);
    padding: 0 var(--s3);
    background: none;
    border: none;
    border-radius: var(--r-md);
    color: var(--text-2);
    font-family: inherit;
    font-size: var(--fs-sm);
    font-weight: 600;
    cursor: pointer;
  }
  .lateral-tema:active { background: var(--surface-2); color: var(--text); }

  .lateral-cuenta {
    display: flex;
    align-items: center;
    gap: var(--s3);
    padding: var(--s2) var(--s3);
  }
  .lateral-cuenta-ini {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 38px;
    height: 38px;
    flex-shrink: 0;
    border-radius: var(--r-full);
    background: var(--surface-2);
    color: var(--text-2);
    font-weight: 700;
    font-size: var(--fs-sm);
  }
  .lateral-cuenta-txt { min-width: 0; flex: 1; }
  .lateral-cuenta-txt strong {
    display: block;
    font-size: var(--fs-sm);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .lateral-cuenta-txt small { display: block; font-size: var(--fs-xs); color: var(--text-2); }
  .lateral-salir {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--s2);
    min-height: 44px;
    margin-top: var(--s2);
    border-radius: var(--r-md);
    color: var(--error);
    text-decoration: none;
    font-size: var(--fs-sm);
    font-weight: 600;
  }
  .lateral-salir:active { background: var(--error-bg); color: var(--error); }

  /* ---------- Barra inferior ---------- */
  .barra-inf {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: var(--z-nav);
    display: grid;
    grid-template-columns: repeat(5, 1fr);
    background: var(--surface);
    border-top: 1px solid var(--border);
    padding-bottom: env(safe-area-inset-bottom);
  }
  .barra-inf-item {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 3px;
    min-height: var(--barra-inf);
    padding: var(--s1) 2px;
    background: none;
    border: none;
    font-family: inherit;
    font-size: var(--fs-3xs);
    color: var(--text-2);
    text-decoration: none;
    cursor: pointer;
  }
  .barra-inf-item .icono { color: var(--text-3); }
  .barra-inf-item span {
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .barra-inf-item:active { background: var(--surface-2); }
  .barra-inf-item.activo { color: var(--brand-600); font-weight: 600; }
  .barra-inf-item.activo .icono { color: var(--brand-600); }

  /* El contenido deja sitio a la barra: nunca queda nada tapado. */
  .contenido {
    padding: var(--s4) var(--s3);
    padding-bottom: calc(var(--barra-inf-total) + var(--s5));
  }

  /* Los avisos flotantes suben por encima de la barra inferior */
  .toast-zona {
    bottom: calc(var(--barra-inf-total) + var(--s3));
  }

  /* Con el cajon abierto, el fondo no se desplaza */
  body.menu-abierto { overflow: hidden; }
}

@keyframes velo-entra  { from { opacity: 0; } to { opacity: 1; } }
@keyframes cajon-entra { from { transform: translateX(-102%); } }


/* =====================================================================
   27b. TABLET Y PORTATIL ESTRECHO  (768px - 1279px)
   ---------------------------------------------------------------------
   Riel de iconos siempre visible. Ni cajon ni barra inferior: hay ancho
   de sobra y no se usa con una sola mano.

   G1: este bloque llegaba hasta 1023. Se extiende a 1279 para que la
   lateral con etiquetas no se abra donde todavia no cabe. Ver la nota
   de --ancho-contenido-riel en TOKENS.
   ===================================================================== */

@media (min-width: 768px) {
  .velo,
  .barra-inf,
  .topbar-menu,
  .lateral-cerrar,
  .lateral-cuenta,
  .lateral-salir { display: none !important; }
}

@media (min-width: 768px) and (max-width: 1279.98px) {

  .app {
    display: grid;
    grid-template-columns: var(--ancho-lateral-min) minmax(0, 1fr);
    min-height: 100vh;
    min-height: 100dvh;
  }

  .lateral {
    display: flex;
    flex-direction: column;
    position: sticky;
    top: 0;
    height: 100vh;
    height: 100dvh;
    background: var(--surface);
    border-right: 1px solid var(--border);
    overflow: hidden;
    z-index: var(--z-nav);
    transform: none;
    visibility: visible;
  }

  /* Solo iconos: el texto no cabe a este ancho */
  .lateral-txt,
  .lateral-marca-txt,
  .lateral-titulo { display: none; }

  .lateral-marca,
  .lateral-enlace,
  .lateral-tema,
  .lateral-toggle {
    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--s2) 0;
  }
  .lateral-marca { border-bottom: 1px solid var(--border); min-height: 58px; }
  .lateral-logo {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 36px;
    height: 36px;
  }

  .lateral-nav { flex: 1; overflow-y: auto; padding: var(--s3) var(--s2); }
  .lateral-grupo { margin-bottom: var(--s3); }
  .lateral-grupo + .lateral-grupo {
    border-top: 1px solid var(--border);
    padding-top: var(--s3);
  }

  .lateral-enlace {
    position: relative;
    min-height: 44px;
    border-radius: var(--r-sm);
    color: var(--text-2);
    text-decoration: none;
  }
  .lateral-enlace .icono { color: var(--text-3); }
  .lateral-enlace:hover { background: var(--surface-2); }
  .lateral-enlace.activo {
    background: var(--brand-50);
    color: var(--brand-600);
    box-shadow: inset 3px 0 0 var(--brand-600);
  }
  .lateral-enlace.activo .icono { color: var(--brand-600); }

  .lateral-badge {
    position: absolute;
    top: 4px;
    right: 10px;
    min-width: 16px;
    padding: 1px 4px;
    border-radius: var(--r-full);
    background: var(--crit);
    color: var(--text-inv);
    font-size: var(--fs-3xs);
    font-weight: 700;
    line-height: 14px;
    text-align: center;
  }

  .lateral-pie { border-top: 1px solid var(--border); padding: var(--s2); }
  .lateral-tema,
  .lateral-toggle {
    width: 100%;
    min-height: 40px;
    background: none;
    border: none;
    border-radius: var(--r-sm);
    color: var(--text-2);
    cursor: pointer;
  }
  .lateral-tema { min-height: var(--tap); }
  .lateral-tema:hover,
  .lateral-toggle:hover { background: var(--surface-2); }

  /* La barra superior ya no repite la marca: esta en el riel */
  .topbar { position: sticky; top: 0; z-index: calc(var(--z-nav) - 1); }
  .topbar .marca { display: none; }
  .topbar .miga { display: flex; margin-bottom: 0; flex: 1; }
  .topbar-avisos { color: var(--text-2); }

  /* G1: antes era max-width:100%. Sin tope, el area util del riel
     crecia hasta 1215 a 1279 y se desplomaba a 988 en cuanto la
     lateral se abria. Con el tope, los dos regimenes se encuentran
     exactamente en 988 y la curva no baja nunca. */
  .contenido { max-width: var(--ancho-contenido-riel); padding: var(--s5) var(--s5) var(--s6); }
}


/* =====================================================================
   27c. ESCRITORIO  (1280px+)
   ---------------------------------------------------------------------
   La barra lateral vuelve a su sitio. Se anulan los desplazamientos que
   el telefono le habia puesto.

   G1: el corte era 1024; sube a 1280 junto con el resto del shell.
   ===================================================================== */

@media (min-width: 1280px) {
  .lateral {
    position: sticky;
    transform: none;
    visibility: visible;
    box-shadow: none;
    width: auto;
    padding-top: 0;
    padding-bottom: 0;
  }
  .topbar-avisos {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 38px;
    height: 38px;
    border-radius: var(--r-md);
    color: var(--text-2);
    text-decoration: none;
  }
  .topbar-avisos:hover { background: var(--surface-2); color: var(--text); }
  .topbar-punto {
    position: absolute;
    top: 2px;
    right: 0;
    min-width: 16px;
    height: 16px;
    padding: 0 4px;
    border-radius: var(--r-full);
    background: var(--crit);
    color: var(--text-inv);
    font-size: var(--fs-3xs);
    font-weight: 700;
    line-height: 16px;
    text-align: center;
  }
}


/* =====================================================================
   27d. CATALOGO DE PRODUCTOS
   ---------------------------------------------------------------------
   El problema medido antes de tocar nada:

     1024px  la tabla pedia 859px dentro de un area de 765. "Editar" y
             "Eliminar" quedaban FUERA de la pantalla, alcanzables solo
             desplazando la tabla a lo ancho.
     1440px  la columna mas ancha era la de los BOTONES (260px), mas
             que la del producto (174px). La jerarquia al reves.
     768px   dos columnas se ocultaban con width:0 y quedaban 352px de
             blanco debajo de la tabla.

   Solucion: seis columnas en vez de nueve, agrupando lo que se lee
   junto (precios en una, stock con su estado en otra), y el listado
   dentro de un panel con cabecera en vez de flotando sobre el fondo.
   ===================================================================== */

/* ---------------------------------------------------------------------
   Buscador y filtros
   El buscador es la accion real de esta pantalla: ocupa el ancho.
   --------------------------------------------------------------------- */

/* Cabecera libre del panel: la usa el modulo del catalogo para meter
   dentro pestanas, contador, buscador y filtros. Sin la reja de
   titulo + acciones de .panel-cab, y sin su alto minimo. */
.panel-cab-libre {
  display: block;
  padding: var(--s4) var(--s5);
}

/* Renglon de pestanas + contador. */
.prod-barra-sup {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s3);
  flex-wrap: wrap;
  margin-bottom: var(--s3);
}
/* Dentro de la barra las pestanas se ajustan a su contenido: sueltas
   eran una tarjeta de ancho completo con 1100px de vacio al lado. */
.prod-barra-sup .tabs { margin-bottom: 0; }

.prod-contador {
  margin: 0;
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--text-2);
  font-variant-numeric: tabular-nums;
}

/* El buscador manda y los filtros van a su lado: en el mismo renglon
   mientras quepan, y debajo cuando no.

   OLA 1: .prod-buscador desaparece. Era el buscador del catalogo y, por
   el camino, la base de los de Alertas, Movimientos y Existencias, que
   lo pedian por marcado. El componente esta ahora en la seccion 57d y
   se llama .buscador, que es lo que es. El reparto dentro de la barra
   -que crezca hasta 280px y no se desborde- lo lleva el propio
   componente. */

.prod-filtros-sel {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2);
  align-items: center;
}
/* padding-right generoso: la flecha del select se dibuja dentro de la
   caja y con el padding normal quedaba encima de la ultima letra. */
.prod-filtros-sel select {
  min-height: 42px;
  width: auto;
  min-width: 150px;
  padding-right: var(--s6, 32px);
}

/* El selector de orden solo existe en telefono: en escritorio se
   ordena pulsando la columna, y tener las dos cosas a la vez
   confunde sobre cual manda. */
/* Ojo al selector: el de orden vive en .prod-filtros-orden, NO en
   .prod-filtros-sel. Cuando lo saque de ese grupo para poder plegar
   los filtros, esta regla dejo de alcanzarlo y el selector aparecio
   en TODOS los anchos, incluido escritorio, donde ya se ordena
   pulsando la columna. Ahora cuelga de .prod-filtros, que es el
   padre de los dos grupos. */
.prod-filtros .solo-telefono { display: none; }

/* Grupos de la barra: en escritorio van los dos en linea. */
.prod-filtros-orden { display: flex; flex-wrap: wrap; gap: var(--s2); align-items: center; }
/* G5: media 42px. El control del sistema mide --tap (44) desde G4 y
   no hay motivo para que un select de orden mida menos. */
.prod-filtros-orden select { padding-right: var(--s6, 32px); }
.prod-filtros-boton { display: none; }

/* ---------------------------------------------------------------------
   Columnas de escritorio
   --------------------------------------------------------------------- */

/* Los anchos de columna van dentro del media query de escritorio: en la
   ficha de telefono no hay columnas que repartir, y ahi `width` deja de
   ser una proporcion para convertirse en el tamano del elemento. Con
   `width: 1%` las acciones median DOS pixeles. */
@media (min-width: 768px) {
  /* El producto es lo que se busca: se lleva el ancho que sobra. */
  .tabla-fichas .c-prod    { width: 40%; min-width: 0; }
  .tabla-fichas .c-cat     { width: 14%; }
  .tabla-fichas .c-precios { width: 14%; }
  .tabla-fichas .c-stock   { width: 14%; }
  .tabla-fichas .c-mov     { width: 92px; }
  .tabla-fichas .c-acciones { width: 1%; text-align: right; }
}

.tabla-fichas .c-cat { color: var(--text-2); }
.tabla-fichas .c-precios { white-space: nowrap; }
.prod-venta {
  display: block;
  font-weight: var(--peso-max);
  font-variant-numeric: tabular-nums;
}
.prod-compra {
  display: block;
  font-size: var(--fs-xs);
  color: var(--text-3);
  font-variant-numeric: tabular-nums;
  margin-top: 1px;
}

/* Stock: cantidad grande, unidad pequena, chip debajo. */
.tabla-fichas .c-stock { white-space: nowrap; }
.prod-stock-num {
  font-size: var(--fs-lg);
  font-weight: var(--peso-max);
  font-variant-numeric: tabular-nums;
}
.prod-stock-unit { font-size: var(--fs-xs); color: var(--text-3); }
.prod-stock-chip { display: block; margin-top: var(--s1); }

.tabla-fichas .c-mov { white-space: nowrap; }
.tabla-fichas .c-acciones { white-space: nowrap; }
.tabla-fichas .c-acciones .en-linea { display: inline-block; }
.tabla-fichas .c-acciones > * + * { margin-left: var(--s2); }

/* ---------------------------------------------------------------------
   Accion destructiva de la fila: fantasma, no roja maciza
   ---------------------------------------------------------------------
   Con el boton relleno habia un bloque rojo POR FILA gritando desde el
   catalogo, y "Eliminar" pesaba visualmente lo mismo que "Editar".
   Sigue siendo un boton de verdad y no un icono decorativo: tiene su
   caja, se tine de rojo al pasar el raton y al recibir el foco, y dice
   lo que hace en su title y en su aria-label.
   --------------------------------------------------------------------- */
/* G3: median 38px de lado, por debajo del minimo comodo, y hacia
   falta una segunda regla para subirlos a 44 en telefono. El lado lo
   pone ahora el componente .btn-icono-solo, que vale --tap en todos
   los anchos; aqui solo queda lo que es de ESTA tabla: la caja
   transparente y el rojo al apuntar. */
.tabla-fichas .c-acciones .btn-icono-solo {
  background: transparent;
  border-color: transparent;
  color: var(--text-3);
  box-shadow: none;
}
.tabla-fichas .c-acciones .btn-icono-solo span { display: none; }
/* El icono hereda del boton. Sin esto lo pintaba de rojo la regla
   .btn-peligro.btn-sm .icono de una etapa anterior, y el boton
   seguia gritando en rojo EN TODOS LOS ANCHOS aunque su caja ya
   fuera transparente. Medir el color del boton no bastaba: el
   color que se ve es el del svg. */
.tabla-fichas .c-acciones .btn-icono-solo .icono { color: inherit; }
.tabla-fichas .c-acciones .btn-icono-solo .icono { margin: 0; }
.tabla-fichas .c-acciones .btn-icono-solo:hover,
.tabla-fichas .c-acciones .btn-icono-solo:focus-visible {
  background: var(--error-bg);
  border-color: var(--error-bg);
  color: var(--error);
}

/* ---------------------------------------------------------------------
   Cabeceras que ordenan
   ---------------------------------------------------------------------
   Un catalogo de inventario que no se puede ordenar por stock obliga a
   mirar fila por fila para saber que hay que comprar. La flecha solo se
   ve en la columna activa; en las demas aparece al pasar el raton, para
   que se note que se puede pulsar sin llenar la cabecera de flechas.
   --------------------------------------------------------------------- */

.th-orden {
  /* flex y no inline-flex: el enlace ocupa la celda entera, asi que
     se puede pulsar en cualquier punto de la cabecera y no solo
     sobre las siete letras del rotulo. */
  display: flex;
  align-items: center;
  gap: var(--s1);
  width: 100%;
  color: inherit;
  text-decoration: none;
  border-radius: var(--r-sm);
  cursor: pointer;
  transition: color var(--t-rapida) var(--ease);
}
.th-orden:focus-visible {
  outline: var(--foco);
  outline-offset: 2px;
}
.th-orden:hover { color: var(--text); }
.th-orden.activo { color: var(--text); }

.th-orden-flecha {
  opacity: 0;
  transition: opacity var(--t-rapida) var(--ease),
              transform var(--t-rapida) var(--ease);
}
.th-orden:hover .th-orden-flecha { opacity: .45; }
.th-orden.activo .th-orden-flecha { opacity: 1; }

/* La flecha apunta hacia abajo (descendente) y se gira para ascendente:
   un solo icono para los dos sentidos. */
[aria-sort="ascending"] .th-orden-flecha { transform: rotate(180deg); }

/* En la columna de precios el rotulo va pegado a la derecha, asi que el
   enlace tiene que ir con el. */
.th-precios .th-orden { flex-direction: row-reverse; justify-content: flex-start; }

/* Sin filas no se ensena la cabecera de columnas: encabezaba una
   tabla que no existe, justo encima de "Ningun producto coincide". */
.tabla-fichas.sin-filas thead { display: none; }

/* Papelera: la columna de movimiento no tiene contenido posible. */
.tabla-fichas.sin-mov .th-mov,
.tabla-fichas.sin-mov .c-mov { display: none; }

/* La imagen del producto en la tabla */
.tabla-fichas .c-prod .prod-linea-nom {
  font-size: var(--fs-base);
  /* Dos renglones como maximo. Con nombres largos de verdad las
     filas iban de 71px a 114px y el listado perdia su ritmo; el
     nombre completo sigue estando en el title. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  /* break-word parte solo si la palabra entera no cabe; anywhere parte
     siempre, y con la columna estrecha salia "Coca-Cola 600m / l". */
  overflow-wrap: break-word;
}

/* ---------------------------------------------------------------------
   Escritorio estrecho: 768 a 1279
   ---------------------------------------------------------------------
   Medido a 1024px: la tabla pedia 743px en un contenedor de 700. Las
   acciones con su texto se llevaban 222px y a la columna del producto
   le quedaban 118, con el nombre partido letra a letra.

   En este rango las acciones se quedan en su icono (conservan title y
   aria-label, asi que siguen diciendo lo que hacen) y la categoria se
   oculta: es el dato menos consultado de la fila y sigue estando en el
   filtro, en la ficha de telefono y en el detalle del producto.
   --------------------------------------------------------------------- */

@media (min-width: 768px) and (max-width: 1279px) {

  .tabla-fichas .c-cat,
  .tabla-fichas .th-cat { display: none; }

  .tabla-fichas .c-acciones .btn span { display: none; }
  .tabla-fichas .c-acciones .btn {
    width: 38px;
    min-width: 38px;
    padding: 0;
    justify-content: center;
  }
  .tabla-fichas .c-acciones .btn .icono { margin: 0; }

  .tabla-fichas .c-prod { width: auto; }
}


/* =====================================================================
   27d-bis. TELEFONO: CADA FILA ES UNA FICHA
   ---------------------------------------------------------------------
   No es la tabla apilada generica (esa sigue existiendo para el resto).
   Aqui cada fila se convierte en una ficha disenada, con areas de
   rejilla: la foto y el nombre mandan, el stock es lo que se consulta
   de un vistazo, y las acciones quedan abajo separadas del contenido.

   El HTML sigue siendo UNA tabla. No hay un segundo marcado para movil
   que se pueda desincronizar: la misma fila que pinta el servidor y la
   que pinta la busqueda en vivo valen para los dos tamanos.
   ===================================================================== */

@media (max-width: 767px) {

  /* Uno por renglon. MEDIDO a 390px: "Todas las categorias" pedia
     141px de texto y el select solo dejaba 139 utiles, asi que la
     ultima letra se perdia bajo la flecha. */
  .prod-filtros-sel { flex-wrap: wrap; }
  .prod-filtros-sel select { flex: 1 1 100%; min-width: 0; }
  .prod-filtros .solo-telefono { display: block; }
  .prod-filtros-orden { flex: 1 1 100%; }
  .prod-filtros-orden select { flex: 1 1 100%; width: 100%; }
  /* "Quitar filtros" a ancho completo quedaba como un texto suelto
     pegado a la izquierda, sin aspecto de que se pueda pulsar. */
  .prod-filtros-orden .btn {
    flex: 1 1 100%;
    justify-content: center;
    min-height: 44px;
  }
  /* El boton solo se ve si el JavaScript le ha quitado el hidden. */
  .prod-filtros-boton:not([hidden]) {
    display: inline-flex;
    flex: 1 1 100%;
    justify-content: center;
    gap: var(--s2);
  }
  /* Con filtros puestos el boton lo dice, para que no queden
     escondidos actuando sin que se vean. */
  .prod-filtros-boton.activo {
    border-color: var(--brand-600);
    color: var(--brand-600);
  }
  .prod-filtros-sel { flex: 1 1 100%; }

  .tabla-fichas {
    background: none;
    border: none;
    box-shadow: none;
    overflow: visible;
  }
  .tabla-fichas table,
  .tabla-fichas tbody { display: block; width: 100%; }
  .tabla-fichas thead { display: none; }

  .tabla-fichas tbody tr {
    display: grid;
    /* minmax(0, 1fr): sin el minimo en cero, la columna de la izquierda
       no puede encogerse por debajo de su contenido y el nombre del
       producto se partia en dos renglones al lado del marcador. */
    grid-template-columns: minmax(0, 1fr) auto;
    /* Una fila por idea. Antes los precios abarcaban dos filas y
       apretaban a la categoria y al stock, que salian cortados. */
    grid-template-areas:
      "prod    prod"
      "stock   precios"
      "mov     acciones";
    gap: var(--s2) var(--s3);
    align-items: center;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--r-lg);
    box-shadow: var(--sh-1);
    padding: var(--s4);
    margin-bottom: var(--s3);
  }
  .tabla-fichas tbody td {
    display: block;
    border: none;
    padding: 0;
    width: auto;
  }

  .tabla-fichas .c-prod { grid-area: prod; min-width: 0; }
  .tabla-fichas .c-prod .prod-img { width: 48px; height: 48px; font-size: var(--fs-base); }
  .tabla-fichas .c-prod .prod-linea-nom { font-size: var(--fs-lg); line-height: 1.25; }

  .tabla-fichas .c-cat {
    grid-area: cat;
    font-size: var(--fs-xs);
    color: var(--text-3);
  }

  
  /* Area tactil: en telefono los botones de la fila llegan a 44px. */
  .tabla-fichas .c-acciones .btn { min-height: 44px; }
  .tabla-fichas .c-precios {
    grid-area: precios;
    text-align: right;
    align-self: center;
    white-space: nowrap;
  }
  /* La categoria ocupaba un renglon entero de la ficha para una
     palabra, con el resto vacio. Se va al final del subtitulo, con
     la marca y el SKU, y la ficha pierde una fila entera.
     Con ::after y no metiendola en la celda del producto: probe a
     darle a .c-cat el mismo grid-area y las dos cosas se pintaban
     UNA ENCIMA DE OTRA ("Ab-ZZQA-007"). Dos celdas no comparten
     area sin superponerse. */
  .tabla-fichas .c-cat { display: none; }
  .tabla-fichas .prod-linea-sub[data-cola]::after {
    content: ' · ' attr(data-cola);
  }
  .tabla-fichas .prod-venta { font-size: var(--fs-xl); }

  .tabla-fichas .c-stock {
    grid-area: stock;
    display: flex;
    align-items: center;
    gap: var(--s2);
    flex-wrap: wrap;
  }
  .tabla-fichas .prod-stock-num { font-size: var(--fs-xl); }
  .tabla-fichas .prod-stock-chip { display: inline-block; margin-top: 0; }

  /* Las acciones se separan del contenido con una linea */
  .tabla-fichas .c-mov,
  .tabla-fichas .c-acciones {
    display: flex;
    align-items: center;
    gap: var(--s2);
    padding-top: var(--s3);
    margin-top: var(--s1);
    /* SIN border-top. Estaba en las DOS celdas de abajo, asi que no
       era una linea: eran dos trozos con el hueco de la rejilla
       (12px) abierto justo bajo el centro de la ficha. Una linea
       rota llama mas la atencion que ninguna, y la ficha ya tiene
       su propio borde y su ritmo vertical. */
  }
  .tabla-fichas .c-mov      { grid-area: mov; justify-content: flex-start; }
  .tabla-fichas .c-acciones { grid-area: acciones; justify-content: flex-end; text-align: initial; }
  .tabla-fichas .c-acciones > * + * { margin-left: 0; }

  /* Los botones + y - crecen: se usan decenas de veces al dia */
  .tabla-fichas .btn-mov { width: 44px; height: 44px; font-size: 22px; }
  .tabla-fichas .c-acciones .btn { min-height: 40px; }

  /* La fila de estado vacio no es una ficha: es un mensaje */
  .tabla-fichas tbody tr:has(td.vacio) {
    display: block;
    padding: 0;
    background: none;
    border: none;
    box-shadow: none;
  }
  .tabla-fichas td.vacio { display: block; padding: var(--s5) var(--s4); }
}

/* =====================================================================
   27e. TELEFONO: LOS FILTROS SON UNA HOJA INFERIOR   (RETIRADA, Ola 5)
   ---------------------------------------------------------------------
   Aqui vivian 134 lineas que convertian una barra de filtros en una
   hoja inferior: .filtros, .filtros-btn, .filtros-btn-num,
   .filtros-acciones, .velo-filtros, body.filtros-abiertos y su
   @keyframes hoja-entra.

   Se retira entera porque NADA de esto llegaba a existir. Las seis
   clases las creaba en tiempo de ejecucion un modulo de app.js que
   empezaba por querySelectorAll('form.filtros') y salia en la linea
   siguiente si no habia ninguno. Ninguna vista escribe class="filtros"
   -comprobado en las 90-, asi que el modulo salia siempre y el CSS
   esperaba a un marcado que nunca se pintaba. El modulo se retira en
   la misma ola; la nota esta en app.js, donde estaba.

   CUIDADO AL BUSCAR "filtros" EN ESTE PROYECTO:

     class="filtros"        MUERTA, es la que se va aqui
     id="filtros"           VIVA, cuatro formularios
     .filtros-plegar-btn    VIVA, es el plegado que si funciona
     data-plegar-filtros    VIVA, el atributo que lo enciende
     .prod-filtros          VIVAS, las barras propias de cada
     .inv-filtros                  pantalla, que son las que de verdad
     .mov-filtros-sec              componen los filtros hoy
     .rep-filtros-hoja

   Son cosas distintas con la misma palabra. Ninguna se toco.
   ===================================================================== */


/* =====================================================================
   27f. TELEFONO: TARJETAS DE RESUMEN COMPACTAS
   ---------------------------------------------------------------------
   Tres cifras de contexto no deberian costar media pantalla antes de
   llegar al contenido. En telefono se ponen en una sola franja.
   ===================================================================== */

@media (max-width: 767px) {

  /* Los KPI normales van de dos en dos y mas ajustados */
  .tarjetas { gap: var(--s2); margin-bottom: var(--s4); }
  .tarjeta  { padding: var(--s3); }
  .kpi .valor { font-size: var(--fs-xl); }
}


/* =====================================================================
   27g. TELEFONO: MOVIMIENTOS COMO LISTA COMPACTA
   ---------------------------------------------------------------------
   Un movimiento se lee de un vistazo: que producto, si entro o salio,
   cuanto, y como quedo el stock. Todo eso cabe en cuatro lineas.

   Antes eran ocho renglones de etiqueta y valor por cada movimiento:
   dos movimientos ya no cabian en una pantalla.
   ===================================================================== */

/* 1023 y no 767: MEDIDO a 768px, las cinco columnas pedian mas de los
   641px del panel y "Quien y cuando" se salia fuera. Una tableta no
   necesita ver una tabla comprimida: le sirve la misma ficha que al
   telefono, que es legible. .tabla-lista solo la usa el historial de
   movimientos, comprobado con grep. */
@media (max-width: 1023px) {

  .tabla-lista {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--r-lg);
    overflow: hidden;
  }
  .tabla-lista table,
  .tabla-lista tbody { display: block; width: 100%; }
  .tabla-lista thead { display: none; }

  /* Solo el armazon: que la fila deje de ser un renglon de tabla y
     pase a ser una rejilla, con su relleno y su junta. EL REPARTO DE
     LA REJILLA LO PONE LA SECCION 68e, que es la que conoce las cinco
     columnas de hoy.

     OLA 3: aqui se declaraban ademas tres renglones de areas -"mov
     stock", "prod prod", "motivo quien"- y los minimos de cada celda
     (.m-stock 68px, .m-quien 104px, .m-mov min-content). Esa
     composicion era de la tabla ANTERIOR y 68e la sustituye entera en
     este mismo punto de corte, asi que se pintaba y se tiraba en la
     misma pasada. Una de sus reglas apuntaba a .m-motivo, una celda
     que ya no existe en el marcado.

     Se retira. Las areas, los minimos y los tamanos de cada celda
     viven en 68e y en ningun otro sitio. */
  .tabla-lista tbody tr {
    display: grid;
    gap: var(--s2) var(--s3);
    align-items: center;
    padding: var(--s3) var(--s4);
    border-bottom: 1px solid var(--border);
  }
  .tabla-lista tbody tr:last-child { border-bottom: none; }
  .tabla-lista tbody td {
    display: block;
    border: none;
    padding: 0;
    font-size: var(--fs-base);
  }
  .tabla-lista td.vacio { display: block; grid-area: auto; padding: var(--s4); }
  .tabla-lista tbody tr:has(td.vacio) { display: block; padding: 0; }
}


/* =====================================================================
   27h. TELEFONO: CABECERA DEL DASHBOARD
   ---------------------------------------------------------------------
   En escritorio el dashboard es un tablero: doce cifras a la vez, todas
   igual de disponibles. Eso funciona con 1400px de ancho.

   En telefono la pregunta al abrir la app es otra: "como va el dia y
   que tengo que hacer". Por eso arriba va el saludo, UNA cifra grande
   y las tres acciones que de verdad se pulsan. El resto del tablero
   sigue debajo, sin quitar nada.
   ===================================================================== */

/* ---------------------------------------------------------------------
   EL SALUDO
   --------------------------------------------------------------------- */

.tablero-saludo { margin-bottom: var(--s5); }
.tablero-saludo h2 {
  margin: 0;
  font-size: var(--fs-xl);
  line-height: var(--lh-titulo);
  letter-spacing: var(--ls-titulo);
}
.tablero-saludo p {
  margin: var(--s1) 0 0;
  color: var(--text-2);
  font-size: var(--fs-sm);
}

/* ---------------------------------------------------------------------
   SEPARADOR DE SECCION DEL TABLERO
   Lo que separa "el dia" de "el mes" no puede ser solo un hueco: sin un
   rotulo, las cuatro metricas de abajo parecen mas de lo mismo.
   --------------------------------------------------------------------- */

.tablero-seccion {
  display: flex;
  align-items: baseline;
  gap: var(--s3);
  margin: var(--s6) 0 var(--s4);
}
/* En mayusculas y con el interletraje abierto: a igual tamano pesa mas
   y se lee como un rotulo de seccion, no como el titulo de una tarjeta
   mas. Antes era texto normal de 15px con una linea de 1px y no
   separaba nada: las cuatro metricas de abajo parecian sueltas. */
.tablero-seccion h3 {
  margin: 0;
  font-size: var(--fs-xs);
  font-weight: var(--peso-max);
  text-transform: uppercase;
  letter-spacing: var(--ls-etiqueta);
  color: var(--text-2);
  flex: none;
}
.tablero-seccion span { font-size: var(--fs-xs); color: var(--text-3); flex: none; }
/* La linea llega hasta el borde: ata el rotulo a lo que viene debajo */
.tablero-seccion::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--border);
}

/* ---------------------------------------------------------------------
   ESTADO DE LA CAJA
   Una condicion, no una cifra: por eso no es una metrica. Ocupa el
   ancho, dice en que estado esta y lleva su propia accion.
   --------------------------------------------------------------------- */

.caja-estado {
  display: flex;
  align-items: center;
  gap: var(--s4);
  padding: var(--s4) var(--s5);
  border-radius: var(--r-lg);
  border: 1px solid var(--border);
  background: var(--surface);
  box-shadow: var(--sh-1);
  margin-bottom: var(--s5);
}
.caja-estado-icono {
  display: grid;
  place-items: center;
  width: 44px; height: 44px;
  border-radius: var(--r-md);
  flex: none;
}
.caja-estado-txt { min-width: 0; flex: 1; }
.caja-estado-txt strong {
  display: block;
  font-size: var(--fs-base);
  line-height: 1.25;
}
.caja-estado-txt span {
  display: block;
  font-size: var(--fs-sm);
  color: var(--text-2);
  margin-top: var(--s0);
}
.caja-estado-accion { flex: none; }

/* Abierta: verde discreto. Es el estado NORMAL de un dia de trabajo,
   asi que no debe gritar. */
.caja-estado-abierta { border-left: 3px solid var(--ok); }
.caja-estado-abierta .caja-estado-icono { background: var(--ok-bg); color: var(--ok); }

/* Cerrada: ambar. No es un error (se puede vender igual), pero las
   ventas no entraran en ningun corte y eso hay que saberlo. */
.caja-estado-cerrada { border-left: 3px solid var(--warn); background: var(--warn-bg); }
.caja-estado-cerrada .caja-estado-icono { background: rgba(180,83,9,.12); color: var(--warn); }
.caja-estado-cerrada .caja-estado-txt span { color: var(--warn); }

/* ---------------------------------------------------------------------
   PIE DE LA GRAFICA
   La grafica dibujaba siete rectangulos y ya. El total del periodo y el
   mejor dia estaban ahi, pero habia que deducirlos mirando barra por
   barra.
   --------------------------------------------------------------------- */

.grafica-pie {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2) var(--s5);
  margin-top: var(--s3);
  padding-top: var(--s3);
  border-top: 1px solid var(--border);
  font-size: var(--fs-sm);
  color: var(--text-2);
}
.grafica-pie b {
  color: var(--text);
  font-variant-numeric: tabular-nums;
  font-weight: var(--peso-max);
}

/* ---------------------------------------------------------------------
   ACCIONES DEL TABLERO EN TELEFONO
   --------------------------------------------------------------------- */

.tablero-acciones-movil { display: none; }


/* =====================================================================
   27h. TELEFONO: EL TABLERO
   ---------------------------------------------------------------------
   En telefono la pregunta al abrir la aplicacion no es "dame doce
   cifras", es "como va el dia y que tengo que hacer".

   El orden es: cifra del dia -> accion principal -> el resto. Es el
   mismo que ya funcionaba, pero ahora la cifra del dia es LA MISMA
   pieza que en escritorio (.metrica-heroe) en vez de un bloque aparte
   que repetia el dato.
   ===================================================================== */

@media (max-width: 767px) {

  .tablero-saludo { margin-bottom: var(--s4); }
  .tablero-saludo h2 { font-size: var(--fs-lg); }

  /* El titulo de la pantalla pesa menos en telefono.

     OLA 1: .pagina-cab h1 sale de aqui. Estaba MUERTA desde la etapa 2
     -la regla base de la seccion 34d va despues en el archivo y gana a
     igual especificidad- y desde la Ola 1 el tamano del titulo lo pone
     --cab-titulo en un solo sitio. Se queda .seccion-cab h2, que si
     sigue haciendo su trabajo. */
  .seccion-cab h2 { font-size: var(--fs-lg); }

  /* NOTA: el reparto de .metricas en telefono NO se define aqui.
     Vive en la seccion 34c, junto al componente. Puesto aqui no
     aplicaba: la 27h va antes que la 34c en el archivo y, a igual
     especificidad, gana la ultima. */

  /* Las acciones: la de vender ocupa el ancho y es alta; las otras dos
     se reparten la fila de abajo. */
  .tablero-acciones-movil {
    display: grid;
    gap: var(--s2);
    margin-bottom: var(--s4);
  }
  .tablero-acciones-movil > .btn {
    width: 100%;
    min-height: 52px;
    font-size: var(--fs-lg);
  }
  .tablero-acciones-sec { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s2); }
  .tablero-acciones-sec .btn { width: 100%; min-height: 46px; }

  /* Caja: el boton baja a su propia linea para no apretar el texto */
  .caja-estado {
    flex-wrap: wrap;
    padding: var(--s4);
    gap: var(--s3);
  }
  .caja-estado-accion { width: 100%; }
  .caja-estado-accion .btn { width: 100%; }

  .tablero-seccion { margin-top: var(--s5); }
}

/* =====================================================================
   27i. TELEFONO: PUNTO DE VENTA
   ---------------------------------------------------------------------
   Es la pantalla mas critica del sistema y la que mas se usa de pie,
   con una mano y con prisa.

   El total y el boton de cobrar quedan SIEMPRE a la vista, pegados
   abajo: no hay que desplazarse para saber cuanto se cobra ni para
   cobrar.
   ===================================================================== */

@media (max-width: 767px) {

  /* Los resultados de la busqueda no se comen la pantalla */
}


/* =====================================================================
   27j. TELEFONO: CAJA
   ---------------------------------------------------------------------
   Lo primero es el estado del turno y el efectivo esperado. Las
   acciones van despues, a lo ancho.
   ===================================================================== */

@media (max-width: 767px) {
  .ancho-form,
  .ancho-lectura,
  .ancho-panel,
  .ancho-formulario,
  .ancho-campo,
  .ancho-busqueda { max-width: 100%; }

  /* Los botones de una fila de acciones ocupan el ancho completo */
  .fila > .btn { flex: 1; min-height: 46px; }

  /* Los datos de dinero se leen mas grandes */
}


/* =====================================================================
   27k. TELEFONO: AJUSTES GENERALES
   ===================================================================== */

@media (max-width: 767px) {

  /* Un panel no necesita marco doble dentro de una pantalla estrecha */
  .panel-cuerpo { padding: var(--s3); }
  .panel-cab    { padding: var(--s3) var(--s3) var(--s2); }
  .panel-cab h3 { font-size: var(--fs-base); }

  /* Las acciones del encabezado de un panel bajan a su propia linea */
  .panel-acciones { width: 100%; }
  .panel-acciones .btn { flex: 1; }

  /* Formularios: una sola columna, siempre */
  .rejilla, .rejilla-2, .rejilla-3, .rejilla-2-1 { grid-template-columns: 1fr; }
  .fila > .crece,
  .fila > .crece-medio,
  .fila > .crece-corto,
  .fila > .crece-libre { flex: 1 1 100%; min-width: 0; }

  /* Ningun modal de escritorio encogido: hoja que sube desde abajo.
     max-width: none es de la OLA 3. El velo se queda sin relleno por
     debajo de 767 (42o) pero el modal seguia topado en 520: entre 641
     y 767 quedaba una hoja de 520 flotando en el centro del borde
     inferior. Lo tapaba un #modalCaja { max-width: 100% } que solo
     llegaba a 640 y solo cubria un modal de los que hay. */
  .modal { border-radius: var(--r-lg) var(--r-lg) 0 0; max-height: 90vh; max-width: none; }
  .modal-pie { padding-bottom: calc(var(--s4) + env(safe-area-inset-bottom)); }
  .modal-pie .btn { flex: 1; }

  /* Las graficas nunca se salen */
  .grafica-wrap { overflow-x: auto; }

  /* Los avisos flotantes no tapan el contenido */
  .toast { font-size: var(--fs-sm); }
}

/* UNICA pantalla que puede plegar sus acciones en telefono: la que ya
   las repite mas abajo. La condicion es la presencia del duplicado
   (.tablero-acciones-movil), no el nombre de la pantalla, asi que no
   hay forma de esconder una accion que no este tambien en otro sitio.

   Antes esto escondia .seccion-cab ENTERA para cualquier pantalla con
   saludo, y la regla hermana escondia .seccion-acciones en TODAS: por
   eso "Nuevo producto", "Nueva venta" y "Nuevo negocio" no existian en
   telefono. El titulo se queda siempre: es el h1 de la pagina. */
@media (max-width: 767px) {
  .contenido:has(.tablero-acciones-movil) .pagina-cab-acciones { display: none; }
}


/* =====================================================================
   27l. TELEFONO: ALERTAS CON PRIORIDAD VISUAL
   ---------------------------------------------------------------------
   Esta pantalla existe para responder una pregunta: que hay que
   resurtir. Cada alerta es una ficha con una franja de color a la
   izquierda, la existencia frente a su minimo, y la accion al lado.

   La franja es lo que permite recorrer la lista sin leer: rojo es
   agotado, naranja es bajo.
   ===================================================================== */

@media (max-width: 767px) {

  /* Aqui vivian ~90 lineas para .tabla-alertas, la tabla de la
     bandeja de alertas. Esa tabla ya no existe en ningun archivo
     del proyecto: la lista de alertas paso a ser una lista de
     fichas (.inv-lista). Comprobado con grep antes de quitarlas. */
}

/* =====================================================================
   27m. TELEFONO: OBJETIVOS TACTILES
   ---------------------------------------------------------------------
   Un enlace de texto suelto mide lo que mide su tipografia: 16 o 17px
   de alto. Con el dedo eso se falla. Aqui se les da area suficiente sin
   cambiar como se ven.
   ===================================================================== */

@media (max-width: 767px) {
  /* Enlaces SIN clase: en este proyecto son los de texto suelto.
     Cualquier componente que sea un enlace (.btn, .metrica,
     .prod-linea, .lateral-enlace...) lleva clase, asi que queda
     fuera por construccion. Antes esto era una lista negra de
     nombres y caduco al renombrar un componente. */
  .contenido a:not([class]) {
    display: inline-block;
    min-height: 32px;
    padding: var(--s2) 0;
    line-height: 1.5;
  }
  /* Dentro de una ficha o una fila, el area va a los lados tambien */
  .ranking-pie a,
  .a-acc a,
  .c-acciones a { padding: var(--s2) var(--s1); }
}

/* =====================================================================
   27n. TABLET: LAS TABLAS MUESTRAN MENOS COLUMNAS
   ---------------------------------------------------------------------
   A 820px una tabla de nueve columnas cabe, pero obliga a desplazarse
   de lado para ver el precio. En vez de encoger la letra se quitan las
   columnas que no deciden nada en esa pantalla: la categoria y el
   precio de compra siguen estando en el detalle del producto.

   En escritorio vuelven todas. No se pierde informacion, se prioriza.
   ===================================================================== */

@media (min-width: 768px) and (max-width: 1023px) {

  /* Productos ya NO se toca aqui: lo resuelve la seccion del catalogo,
     que oculta la categoria por su clase. Estas reglas escondian
     th:nth-child(3) y (4) pensando en la tabla de nueve columnas; al
     pasar a seis dejaron sin PRECIOS ni STOCK a todo portatil entre
     768 y 1023px, que es justo lo que un catalogo no puede perder. */

  /* El motivo YA NO se oculta aqui. Tenia sentido con la tabla de
     ocho columnas; ahora son cinco y el motivo es la que dice por que
     se registro el movimiento. El th:nth-child(7) tampoco existe ya. */


  /* Las celdas respiran menos para ganar ancho util */
  th, td { padding: var(--s2) var(--s3); }
}


/* =====================================================================
   28. JERARQUIA DE ACCIONES  (Etapa 1 de flujo)
   ---------------------------------------------------------------------
   Reutiliza los componentes que ya existen (.btn, .modal, .chip). Aqui
   solo se agregan las piezas que faltaban para distinguir:
   accion en fila, pestanas, paginacion y confirmacion.
   ===================================================================== */

/* ---------- Acciones dentro de una fila ---------- */

/* ---------- Pestanas (elegir vista, no navegar) ---------- */
/* =====================================================================
   LA PESTANA Y EL CARRIL   (Ola 2)
   ---------------------------------------------------------------------
   UN CONTROL, UNA IDENTIDAD DE SELECCIONADO, DOS FORMAS DE AGRUPARLO.

   Habia seis interpretaciones del mismo objeto -un filtro de una sola
   eleccion- y, lo que importa, DOS respuestas distintas a "cual esta
   puesto":

     .tab            blanca sobre un carril gris, con sombra y filo
     .alr-pest-btn   blanca sobre el panel, con borde y sombra
     .mov-pest-btn   azul claro con filo de marca al 35%
     .rep-pest-btn   azul claro con filo de marca al 35%
     .cat-tab        azul claro con --selected-borde
     .prod-chip      azul claro con --selected-borde

   El peor efecto no era que la activa cambiara de color: era que la
   pestana EN REPOSO de Movimientos -blanca, con borde, sobre el panel-
   es identica a la pestana ACTIVA de Alertas. Quien pasaba de una
   pantalla a la otra veia cinco filtros que parecian todos puestos.

   Ahora hay uno: .tab. Y el seleccionado se dice de UNA manera, la que
   el sistema ya usaba para "esto es lo elegido" en el menu lateral y en
   las listas: --selected y --selected-borde. No es un tono nuevo; es
   dejar de inventar uno por pantalla.

   NINGUN estado usa color semantico. El verde sigue significando "va
   bien", el ambar "atencion" y el rojo "critico", y ninguno de los tres
   aparece aqui: una pestana elegida no es una alerta.

   COMO SE AGRUPAN. Dos casos, y lo decide cuantas opciones hay, no que
   pantalla es:

     .tabs    POCAS opciones cerradas (un periodo, catalogo/papelera).
              Se ven todas a la vez, envuelven si hace falta y van
              dentro de un carril tenido. La pestana descansa
              TRANSPARENTE ahi dentro -el carril ya pone la superficie-
              y eso lo dice el propio carril con dos variables, sin
              pelearse por especificidad con el estado activo.

     .carril  MUCHAS opciones abiertas (tipos de movimiento, categorias,
              secciones de un informe). Una sola fila que se desliza de
              lado, nunca dos: envolver convierte una tira de opciones
              en un parrafo de botones.

   La pastilla -radio completo- es la forma del filtro en este sistema,
   y la separa de un boton, que lleva --r-sm. Antes cuatro de las seis
   ya lo eran; las otras dos andaban por --r-md y --r-sm.
   ===================================================================== */

/* ---------- El carril desplazable ----------
   Siete propiedades que estaban copiadas en .nav, .alr-pest, .mov-pest,
   .rep-pest, .cat-tabs y .prod-chips. El relleno de 2px con el margen
   negativo que lo compensa no es decorativo: sin el, el anillo de foco
   y el filo de la pestana activa los recorta el propio overflow. */
.carril {
  display: flex;
  flex-wrap: nowrap;
  gap: var(--s2);
  min-width: 0;
  overflow-x: auto;
  overflow-y: hidden;
  padding: 2px;
  margin-inline: -2px;
  scroll-snap-type: x proximity;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}
.carril::-webkit-scrollbar { display: none; }

/* Lo que va dentro no se encoge y se alinea al soltar. */
.carril > * { flex: none; scroll-snap-align: start; }


/* ---------- La pestana ----------
   El fondo y el filo de REPOSO salen de dos variables con valor por
   defecto. Es lo que permite que .tabs las reescriba para sus hijos sin
   subir la especificidad por encima del estado activo: la agrupacion
   decide como descansa la pestana, nunca como se ve elegida. */
.tab {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s1);
  min-height: var(--tap-sm);
  padding: 0 var(--s4);
  background: var(--tab-fondo, var(--surface));
  border: 1px solid var(--tab-borde, var(--border-subtle));
  border-radius: var(--r-full);
  color: var(--text-2);
  font-family: inherit;
  font-size: var(--fs-sm);
  font-weight: var(--peso-medio);
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: background var(--t-rapida) var(--ease),
              border-color var(--t-rapida) var(--ease),
              color var(--t-rapida) var(--ease),
              box-shadow var(--t-rapida) var(--ease);
}

/* El icono acompana y no compite: hereda el color del texto y descansa
   apagado. Antes tres pantallas lo bajaban con opacity (.75 y .8) y dos
   con un color propio (--text-3). */
.tab .icono {
  flex: none;
  opacity: .75;
  transition: opacity var(--t-rapida) var(--ease);
}

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

/* ---------- EL SELECCIONADO, UNA SOLA VEZ ----------
   Tres disparadores porque el marcado ya usa tres y ninguno se toca:
   .activo (ui_tabs, Categorias, Productos), .es-activa (Alertas,
   Movimientos, Reportes) y aria-selected, que es el que de verdad lo
   anuncia. El JavaScript sigue encendiendo exactamente la clase que
   encendia antes.

   Cuatro senales, no una: fondo tenido, filo de marca, letra de marca y
   peso. Con cinco pastillas en fila, solo el color de la letra obliga a
   recorrerlas todas para saber cual esta puesta. */
.tab.activo,
.tab.es-activa,
.tab[aria-selected="true"] {
  background: var(--selected);
  border-color: var(--selected-borde);
  color: var(--brand-600);
  font-weight: var(--peso-fuerte);
  box-shadow: var(--sh-1);
}
.tab.activo .icono,
.tab.es-activa .icono,
.tab[aria-selected="true"] .icono { opacity: 1; }

/* Pasar el raton por encima de la que ya esta puesta no la apaga. */
.tab.activo:hover,
.tab.es-activa:hover,
.tab[aria-selected="true"]:hover { background: var(--selected); }

/* Pulsado y foco: los del sistema, iguales para las seis pantallas.
   Van despues del estado activo para que el gesto se note tambien
   sobre la pestana ya elegida. */
.tab:active { background: var(--pressed); }
.tab:focus-visible { outline: var(--foco); outline-offset: 2px; }


/* ---------- Pocas opciones: el carril tenido ----------
   Dentro del carril la pestana no lleva superficie propia: la pone el
   carril. Se dice con las dos variables de reposo, no con un selector
   mas fuerte. */
.tabs {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s1);
  margin-bottom: var(--s4);
  padding: var(--s1);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r-full);

  --tab-fondo: transparent;
  --tab-borde: transparent;
}

/* Dentro del carril el anillo de foco con desfase se salia por arriba y
   empujaba a las vecinas. Pegado al filo queda dentro. */
.tabs > .tab:focus-visible { outline-offset: 0; }


/* ---------- La densidad, por PUNTERO y no por pantalla ----------
   Dos valores para las seis pantallas. Lo que decide no es cual es la
   pantalla sino con que se toca: un portatil tactil tambien se toca y
   un telefono con raton no necesita 44.

   Antes habia cinco densidades repartidas -36, 38, 40 y 44 segun la
   pantalla y el ancho- y ninguna sabia de las otras. La banda de ancho
   se conserva ademas del puntero porque el navegador solo declara
   puntero grueso por debajo de 768. */
@media (pointer: coarse) { .tab { min-height: var(--tap); } }
@media (max-width: 767px) { .tab { min-height: var(--tap); } }

/* ---------- Paginacion ---------- */
.paginacion {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--s3);
  flex-wrap: wrap;
  margin-top: var(--s4);
}
.paginacion .btn { display: inline-flex; align-items: center; gap: var(--s1); }

/* Dentro de un panel sin espacio (los listados), la paginacion es el
   PIE del panel: sin esto quedaba pegada al borde de abajo, sin aire
   y sin nada que la separase de la ultima fila.
   Va justo detras de .paginacion a proposito: en este archivo ya me
   ha pasado tres veces escribir el ajuste ANTES de la regla que
   queria ajustar y perder por orden a igual especificidad. */
/* Con una sola pagina el contenedor se queda vacio: sin esto dejaria
   un borde y un hueco fantasma al pie del listado. */
.paginacion:empty { display: none; }

.panel-cuerpo.sin-espacio > .paginacion {
  margin-top: 0;
  padding: var(--s4) var(--s5);
  border-top: 1px solid var(--border);
}
/* Un extremo sin a donde ir se ve apagado, pero sigue ocupando su sitio:
   asi el control no se mueve al cambiar de pagina. */
.paginacion [aria-disabled="true"] {
  opacity: .45;
  pointer-events: none;
}
.paginacion-pos {
  font-size: var(--fs-sm);
  color: var(--text-2);
  font-variant-numeric: tabular-nums;
}
.icono-voltea { transform: rotate(180deg); }

/* ---------- Dialogo de confirmacion ---------- */
.confirmar { max-width: 420px; }
.confirmar-cab {
  display: flex;
  align-items: flex-start;
  gap: var(--s3);
  padding: var(--s5) var(--s5) var(--s3);
}
.confirmar-cab h3 { margin: 0; font-size: var(--fs-lg); line-height: var(--lh-tight); }
.confirmar-icono {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 42px;
  height: 42px;
  flex-shrink: 0;
  border-radius: var(--r-full);
  background: var(--warn-bg);
  color: var(--warn);
}
.confirmar-icono.es-peligro { background: var(--error-bg); color: var(--error); }
.confirmar .modal-cuerpo { padding: 0 var(--s5) var(--s4); }
.confirmar .modal-pie { border-top: none; padding-top: 0; }

body.dialogo-abierto { overflow: hidden; }

/* Un boton ocupado no invita a volver a pulsarlo */
[aria-busy="true"] { cursor: progress; }

/* ---------- Acciones al pie de un formulario ----------
   La principal manda, la salida acompana. En telefono se reparten:
   Guardar se lleva el espacio libre porque es la accion que se busca;
   Cancelar solo el suyo.

   G4: esta regla existia y la usaba UN formulario. Los otros seis se
   habian escrito la misma barra con otro nombre -.neg-form-acciones,
   .negocio-acciones, .admconfig-acciones, .admavisos-acciones,
   .cat-form-acciones, .admconfig-mant-form- repitiendo entre cinco y
   siete declaraciones identicas cada uno, y discrepando solo en cosas
   que no deberian variar: unos ponian min-height 40 y otros 44 a unos
   botones cuyo alto ya decide G3.

   Ahora las seis llevan tambien .form-acciones y de sus reglas solo
   queda lo que de verdad es suyo. La linea de separacion se hereda
   aqui, que es lo que cinco de las siete querian; el unico que no la
   tenia -el alta de producto- la gana, y es justo el sentido de G4:
   que un formulario no se distinga de otro por accidente. */
.form-acciones {
  display: flex;
  gap: var(--s3);
  align-items: center;
  flex-wrap: wrap;
  margin-top: var(--s5);
  padding-top: var(--s4);
  border-top: 1px solid var(--border);
}
/* Sin linea: cuando la barra ya va dentro de una caja con su propio
   borde y la raya saldria doble. */
.form-acciones-sin-linea { padding-top: 0; border-top: none; }

@media (max-width: 767px) {
  .form-acciones { align-items: stretch; gap: var(--s2); }
  .form-acciones .btn { min-height: 48px; }
  /* La salida no crece: ni la secundaria ni la terciaria. Antes solo
     se nombraba .btn-sec y en dos formularios el "Cancelar" es
     .btn-plano, asi que crecia igual que Guardar. */
  .form-acciones .btn-sec,
  .form-acciones .btn-plano { flex: 0 0 auto; }
  .form-acciones .btn:not(.btn-sec):not(.btn-plano) { flex: 1; }
}

/* Una nota dentro de la barra ocupa su propio renglon: en una fila de
   botones se quedaria estrujada entre ellos. */
.form-acciones > p { flex: 1 0 100%; margin: 0; }

/* ---------- Destructiva dentro de una fila ---------- */
/* Un boton rojo solido por cada fila convierte la tabla en un semaforo
   y le quita peso a lo que de verdad importa: el producto. La accion
   sigue siendo reconocible (rojo), pero el rojo lleno se reserva para
   el boton final del dialogo de confirmacion, donde si hay que dudar. */
.btn-peligro.btn-sm {
  background: var(--surface);
  color: var(--error);
  border-color: var(--border);
}
.btn-peligro.btn-sm:hover {
  background: var(--error-bg);
  border-color: var(--error);
  color: var(--error);
}
.btn-peligro.btn-sm .icono { color: var(--error); }


/* =====================================================================
   29. TELEFONO: SALIDA Y ACCIONES SIEMPRE ALCANZABLES  (Etapa 1.5)
   ---------------------------------------------------------------------
   Dos problemas reales medidos en el telefono:

   1. Desde una pagina hija (editar producto, detalle de venta, corte)
      la unica salida era el boton Atras del navegador. Ahora la barra
      superior muestra una flecha de volver a su seccion.

   2. En un formulario largo, Guardar quedaba a varias pantallas de
      distancia del campo que se acababa de editar. Ahora las acciones
      del formulario se quedan pegadas encima de la barra inferior.
   ===================================================================== */

@media (max-width: 767px) {

  /* La flecha de volver ocupa el sitio del boton de menu */
  .topbar-volver {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    text-decoration: none;
    color: var(--text);
  }

  /* ---------- Acciones de formulario ancladas ---------- */
  /* Se quedan a la vista sin tapar nada: son el ultimo elemento del
     formulario, asi que por encima no hay contenido que ocultar. */
  .form-acciones {
    position: sticky;
    bottom: var(--barra-inf-total);
    z-index: 3;
    margin: var(--s5) calc(var(--s3) * -1) 0;
    padding: var(--s3);
    padding-bottom: var(--s3);
    background: var(--surface);
    border-top: 1px solid var(--border);
    box-shadow: var(--sh-arriba);
  }

  /* Un formulario dentro de una tarjeta: la barra se pega a los bordes
     de la tarjeta, no del contenido. */
  .tarjeta .form-acciones,
  .panel-cuerpo .form-acciones {
    margin-left: calc(var(--s3) * -1);
    margin-right: calc(var(--s3) * -1);
    border-radius: 0 0 var(--r-lg) var(--r-lg);
  }
}


/* =====================================================================
   30. TELEFONO: EL PUNTO DE VENTA VA AL GRANO  (Etapa 1.5)
   ---------------------------------------------------------------------
   Medido en un telefono de 844px de alto: el boton COBRAR quedaba a
   815px del inicio, es decir, fuera de la primera pantalla. Antes del
   buscador habia 187px de contenido que no sirve para vender.

   No se quita ninguna funcion: el historial sigue en el menu y el aviso
   de caja sigue estando, solo que en una linea.
   ===================================================================== */

@media (max-width: 767px) {


  /* El aviso de caja cerrada dice lo mismo en la mitad de espacio */
  #pos ~ .aviso,
  .contenido > .aviso-alerta {
    padding: var(--s3);
    font-size: var(--fs-sm);
    line-height: 1.4;
  }
  .contenido > .aviso-alerta .btn,
  .contenido > .aviso-alerta a { font-size: var(--fs-sm); }

}



/* =====================================================================
   31. TELEFONO: EL HISTORIAL DE VENTAS ES UNA LISTA  (Etapa 1.5)
   ---------------------------------------------------------------------
   Una venta ocupaba la pantalla entera: ocho renglones de etiqueta y
   valor. Lo que se busca en un historial es el folio, cuanto fue y si
   sigue en pie; el resto esta en el detalle, a un toque.
   ===================================================================== */

@media (max-width: 767px) {

  /* Aqui vivian ~60 lineas para la tabla de NUEVE columnas del
     historial: .v-fecha, .v-vend, .v-partidas y .v-pago, que ya no
     existen, y una que ocultaba .v-cliente, que en la tabla nueva
     de cinco SI se quiere ver. La ficha de telefono se define
     ahora en la seccion 39, con los nombres nuevos. */
}


/* =====================================================================
   32. DETALLE DE UN MOVIMIENTO  (Etapa 1.6)

   El historial contesta "que paso". Esta ficha contesta "por que
   quedaron 47". Se lee de arriba abajo: primero la cifra que cambio,
   luego de cuanto a cuanto, y al final los datos que solo se consultan
   cuando algo no cuadra.

   Un solo marcado para las tres medidas: la ficha ya es de ancho de
   lectura, asi que en tableta y escritorio no necesita reglas propias.
   ===================================================================== */

.mov-ficha { display: grid; gap: var(--s4); }

/* ---------- Encabezado: que producto y cuanto se movio ---------- */
.mov-cab {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--s4);
}
.mov-cab-txt { min-width: 0; }

.mov-producto {
  font-size: var(--fs-xl);
  line-height: 1.25;
  margin: var(--s2) 0 0;
  overflow-wrap: anywhere;
}
.mov-producto a { color: inherit; text-decoration: none; }
.mov-producto a:hover { text-decoration: underline; }

.mov-sku {
  margin: var(--s1) 0 0;
  font-size: var(--fs-sm);
  color: var(--text-2);
}

/* La cifra es el dato que se viene a buscar: se lee sin acercarse */
.mov-cifra {
  flex: none;
  text-align: right;
  font-size: var(--fs-2xl);
  font-weight: 700;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}
.mov-unidad {
  display: block;
  margin-top: var(--s1);
  font-size: var(--fs-xs);
  font-weight: 500;
  color: var(--text-3);
}

/* ---------- De cuanto habia a cuanto quedo ---------- */
.mov-stock {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--s5);
}
.mov-stock-paso { text-align: center; }
.mov-stock-rotulo {
  display: block;
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--text-3);
}
.mov-stock-num {
  display: block;
  margin-top: var(--s1);
  font-size: var(--fs-2xl);
  font-variant-numeric: tabular-nums;
  color: var(--text-2);
}
.mov-stock-num.fuerte { color: var(--text); }
.mov-stock-flecha { color: var(--text-3); display: flex; }

/* ---------- Los datos que solo se miran cuando algo no cuadra ---------- */
.ficha-datos {
  display: grid;
  grid-template-columns: 150px 1fr;
  gap: 0;
  margin: 0;
}
.ficha-datos dt,
.ficha-datos dd {
  margin: 0;
  padding: var(--s3) 0;
  border-bottom: 1px solid var(--border);
  font-size: var(--fs-base);
}
.ficha-datos dt { color: var(--text-2); }
.ficha-datos dd { overflow-wrap: anywhere; }
.ficha-datos dt:last-of-type,
.ficha-datos dd:last-of-type { border-bottom: none; }

/* ---------- Los movimientos vecinos del mismo producto ---------- */
.mov-vecinos { list-style: none; margin: 0; padding: 0; }
.mov-vecinos li + li { border-top: 1px solid var(--border); }
.mov-vecinos a {
  display: grid;
  grid-template-columns: 1fr auto auto;
  align-items: center;
  gap: var(--s3);
  padding: var(--s3) var(--s1);
  color: inherit;
  text-decoration: none;
  border-radius: var(--r-sm);
}
.mov-vecinos a:hover { background: var(--surface-2); }
.mov-vecino-fecha { color: var(--text-2); font-size: var(--fs-sm); }
.mov-vecino-cant  { font-weight: 700; font-variant-numeric: tabular-nums; }
.mov-vecino-stock {
  min-width: 44px;
  text-align: right;
  color: var(--text-3);
  font-size: var(--fs-sm);
  font-variant-numeric: tabular-nums;
}

/* ---------- El enlace desde el historial ---------- */
/* En escritorio es el nombre del producto, un enlace normal. */
.tabla-lista .mov-enlace { color: inherit; text-decoration: none; }
.tabla-lista .mov-enlace:hover strong { text-decoration: underline; }

@media (max-width: 767px) {
  /* En telefono se abre tocando la fila entera: el dedo no apunta a una
     palabra. Sigue siendo UN enlace de verdad (no un onclick), asi que
     el teclado y el lector de pantalla lo ven igual que en escritorio.
     El ::after estirado es lo que agranda el area, no un div encima. */
  .tabla-lista tbody tr { position: relative; }
  .tabla-lista .mov-enlace::after { content: ""; position: absolute; inset: 0; }

  /* Marca de "esto se abre": una punta de flecha al margen derecho */
  .tabla-lista tbody tr:has(.mov-enlace) { padding-right: var(--s5); }
  .tabla-lista tbody tr:has(.mov-enlace)::after {
    content: "";
    position: absolute;
    right: var(--s3);
    top: 50%;
    width: 7px; height: 7px;
    margin-top: -4px;
    border-right: 2px solid var(--text-3);
    border-bottom: 2px solid var(--text-3);
    transform: rotate(-45deg);
    pointer-events: none;
  }

  .mov-cifra { font-size: var(--fs-xl); }
  .ficha-datos { grid-template-columns: 1fr; }
  .ficha-datos dt { padding-bottom: 0; border-bottom: none; color: var(--text-3); font-size: var(--fs-xs); }
  .ficha-datos dd { padding-top: var(--s1); }
}


/* =====================================================================
   33. TICKET: EL COMPROBANTE  (Etapa 1.6 -> Etapa 2)

   MEDIDO ANTES DE TOCAR NADA, a 1440x900 y a 1920x950:
     - La rejilla estaba topada a 900px y alineada a la IZQUIERDA. El
       area de contenido son 1196px a 1440 y 1670px a 1920: sobraban
       296px y 770px de nada a la derecha.
     - El "ticket" era un <pre> monoespaciado de 340x342 con el texto
       guardado tal cual. Sin jerarquia: el total, 12.5px, igual que
       "SUBTOTAL" y que la linea de guiones.
     - Al lado, una tarjeta "Resumen" con Folio / Fecha / Total / Estado:
       los mismos cuatro datos que ya estaban en el texto, otra vez.
     - Los tres botones de accion median 158x71 porque "Descargar PDF"
       partia en dos lineas y la rejilla igualaba la altura de la fila.
     - Los enlaces del pie median 34px de alto: por debajo del minimo
       tactil de 40px.

   AHORA: un documento a la izquierda con el total de protagonista, y a
   la derecha lo que se puede HACER con el. La composicion se centra en
   vez de pegarse al borde: un comprobante es un documento, y un
   documento centrado a 1040px no deja zona muerta, deja margen.
   ===================================================================== */

.ticket-layout {
  display: grid;
  gap: var(--s4);
  align-items: start;
  max-width: 1040px;
  margin-inline: auto;
}

/* El corte a dos columnas NO puede mirar solo al viewport: la barra
   lateral de la aplicacion se come 244px a partir de 1024px. Medido:

     viewport   ancho util de .contenido
     ---------  ------------------------
       768px       689px
       900px       821px
      1024px       765px   <- MENOS que a 900: la lateral se abre aqui
      1100px       841px
      1280px      1021px
      1440px      1181px

   Con el corte en 900px, a 1024 el documento quedaba en 353px al lado
   de un carril de 340: el protagonista medido igual que su apoyo. El
   corte va donde el documento puede quedarse en sus 520px. */
@media (min-width: 1180px) {
  .ticket-layout {
    grid-template-columns: minmax(0, 1fr) 340px;
    gap: var(--s5);
  }
}
@media (min-width: 1440px) {
  .ticket-layout {
    grid-template-columns: minmax(0, 1fr) 372px;
    max-width: 1120px;
  }
}

.ticket-rail { display: grid; gap: var(--s4); align-content: start; }

/* Tablet y portatil pequeno: una sola columna, pero el carril no se
   estira en paneles de 840px de ancho. Se reparte en dos, que es una
   composicion de tablet y no un movil estirado. */
@media (min-width: 600px) and (max-width: 1179px) {
  .ticket-rail {
    grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
    align-items: start;
  }
}

/* ---------- La barra de acciones ---------- */
/* Estado a la izquierda, acciones a la derecha. Se alinea con la
   composicion del documento, no con el ancho de la pantalla. */
.ticket-barra {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s3);
  flex-wrap: wrap;
  max-width: 1040px;
  margin: 0 auto var(--s4);
}
.ticket-barra-btns { display: flex; gap: var(--s2); flex-wrap: wrap; }
.ticket-barra-btns .btn { min-height: 44px; }

@media (min-width: 1440px) { .ticket-barra { max-width: 1120px; } }

@media (max-width: 599px) {
  /* En telefono los dos botones se reparten la fila. Caben: a 360px son
     dos de 164px y ni "Imprimir" ni "Descargar PDF" parten linea.
     Apilados ocupaban 139px de alto antes del documento, que es la
     mitad de lo que se ve al abrir la pantalla. */
  .ticket-barra { gap: var(--s2); }
  .ticket-barra-btns {
    display: grid;
    grid-template-columns: 1fr 1fr;
    width: 100%;
  }
  .ticket-barra-btns .btn {
    width: 100%;
    justify-content: center;
    padding-inline: var(--s2);
  }
}

/* ---------------------------------------------------------------------
   EL DOCUMENTO
   Todo por tokens, sin un solo color escrito a mano: en modo oscuro el
   comprobante se oscurece como cualquier otra tarjeta del sistema. Una
   hoja blanca sobre una interfaz oscura se ve como un fallo, no como
   papel; el papel de verdad ya sale por la impresora.
   --------------------------------------------------------------------- */

.doc {
  position: relative;
  overflow: hidden;               /* contiene el sello girado */
  max-width: 520px;
  margin-inline: auto;
  padding: var(--s6) var(--s5) var(--s5);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-2);
}

/* ---------- Quien cobra ---------- */
.doc-cab { text-align: center; }

/* Doble clase a proposito: es un <h3> y `.contenido h3` (0,1,1) le
   impondria 17px y su margen. `.doc .doc-negocio` (0,2,0) gana. */
.doc .doc-negocio {
  margin: 0;
  font-size: var(--fs-xl);
  font-weight: var(--peso-max);
  line-height: var(--lh-tight);
  letter-spacing: var(--ls-titulo);
  overflow-wrap: anywhere;        /* nombres de negocio muy largos */
}
.doc-negocio-dato {
  margin: var(--s1) 0 0;
  font-size: var(--fs-xs);
  color: var(--text-2);
  overflow-wrap: anywhere;
}

/* ---------- Que documento es ---------- */
.doc-rotulo {
  display: block;
  font-size: var(--fs-3xs);
  font-weight: var(--peso-fuerte);
  letter-spacing: var(--ls-etiqueta);
  text-transform: uppercase;
  color: var(--text-3);
}

.doc-folio {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: var(--s4);
  margin-top: var(--s5);
  padding: var(--s3) 0;
  border-top: 1px dashed var(--border-2);
  border-bottom: 1px dashed var(--border-2);
}
.doc-folio-der { text-align: right; }

.doc-folio-num {
  display: block;
  font-family: var(--font-num);
  font-size: var(--fs-lg);
  font-weight: var(--peso-max);
  font-variant-numeric: tabular-nums;
  letter-spacing: .02em;
}
.doc-folio-dato {
  display: block;
  font-size: var(--fs-sm);
  font-weight: var(--peso-fuerte);
  font-variant-numeric: tabular-nums;
}
.doc-folio-hora {
  display: block;
  font-size: var(--fs-xs);
  color: var(--text-2);
  font-variant-numeric: tabular-nums;
}

/* ---------- Que se llevo ---------- */
.doc-items-cab {
  display: flex;
  justify-content: space-between;
  gap: var(--s3);
  margin-top: var(--s4);
  font-size: var(--fs-3xs);
  font-weight: var(--peso-fuerte);
  letter-spacing: var(--ls-etiqueta);
  text-transform: uppercase;
  color: var(--text-3);
}

.doc-items { list-style: none; margin: var(--s2) 0 0; padding: 0; }

.doc-item {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  gap: var(--s3);
  align-items: baseline;
  padding: var(--s3) 0;
  border-bottom: 1px solid var(--border);
}
.doc-item:last-child { border-bottom: none; }

/* La cantidad delante: es lo que se discute cuando se revisa un ticket. */
.doc-item-cant {
  min-width: 26px;
  padding: 2px var(--s2);
  border-radius: var(--r-sm);
  background: var(--surface-2);
  border: 1px solid var(--border);
  font-size: var(--fs-xs);
  font-weight: var(--peso-max);
  font-variant-numeric: tabular-nums;
  text-align: center;
  color: var(--text-2);
}
.doc-item-txt { min-width: 0; }
.doc-item-nom {
  display: block;
  font-size: var(--fs-base);
  font-weight: var(--peso-medio);
  line-height: var(--lh-tight);
  overflow-wrap: anywhere;        /* nombres de producto largos */
}
.doc-item-det {
  display: block;
  margin-top: var(--s0);
  font-size: var(--fs-xs);
  color: var(--text-2);
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}
.doc-item-imp {
  font-size: var(--fs-base);
  font-weight: var(--peso-fuerte);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* ---------- Cuanto ---------- */
.doc-sumas {
  margin-top: var(--s3);
  padding-top: var(--s3);
  border-top: 1px dashed var(--border-2);
}

.doc-desglose { list-style: none; margin: 0; padding: 0; }
.doc-desglose li {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--s3);
  padding: var(--s1) 0;
  font-size: var(--fs-sm);
  color: var(--text-2);
}
.doc-desglose strong {
  color: var(--text);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.doc-desglose .doc-resta { color: var(--error); }

/* LA cifra. Es por lo que se abre esta pantalla. */
.doc-total {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--s3);
  margin-top: var(--s3);
  padding-top: var(--s3);
  border-top: 2px solid var(--text);
}
.doc-total-rot {
  font-size: var(--fs-sm);
  font-weight: var(--peso-fuerte);
  letter-spacing: var(--ls-etiqueta);
  text-transform: uppercase;
}
.doc-total-num {
  font-family: var(--font-num);
  /* Fluido: a 360px un total de siete cifras a 34px se sale del papel. */
  font-size: clamp(24px, 6.4vw, 34px);
  font-weight: var(--peso-max);
  line-height: 1;
  letter-spacing: var(--ls-titulo);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.doc-pago {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--s3);
  margin: var(--s3) 0 0;
}
.doc-pago .doc-rotulo { display: inline; }
.doc-pago-val {
  font-size: var(--fs-sm);
  font-weight: var(--peso-fuerte);
}

/* ---------- Con quien ---------- */
.doc-pie {
  margin-top: var(--s5);
  padding-top: var(--s4);
  border-top: 1px dashed var(--border-2);
}

.doc-pie-datos {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--s1) var(--s3);
  margin: 0 0 var(--s4);
  font-size: var(--fs-xs);
}
.doc-pie-datos dt { color: var(--text-3); }
.doc-pie-datos dd { margin: 0; color: var(--text-2); overflow-wrap: anywhere; }

.doc-gracias {
  margin: 0;
  text-align: center;
  font-size: var(--fs-sm);
  color: var(--text-2);
  overflow-wrap: anywhere;
}

.doc-nota-anulado {
  margin: 0;
  padding: var(--s3);
  border-radius: var(--r-sm);
  background: var(--error-bg);
  color: var(--error);
  font-size: var(--fs-xs);
  line-height: var(--lh);
}

/* ---------- Venta cancelada ---------- */
/* El sello es lo que hace que se lea "cancelado" antes que ninguna otra
   cosa. Lo que CUENTA ya esta escrito -el chip de arriba y la nota del
   pie-, asi que va aria-hidden y no se lleva contraste que haga falta
   para leer los importes. */
.doc-anulado { border-color: var(--error); }

.doc-sello {
  position: absolute;
  top: 42%;
  left: 50%;
  transform: translate(-50%, -50%) rotate(-13deg);
  padding: var(--s2) var(--s5);
  border: 3px solid var(--error);
  border-radius: var(--r-sm);
  font-size: clamp(26px, 8vw, 44px);
  font-weight: var(--peso-max);
  letter-spacing: var(--ls-etiqueta);
  text-transform: uppercase;
  color: var(--error);
  opacity: .16;
  white-space: nowrap;
  pointer-events: none;
}

/* ---------------------------------------------------------------------
   LA COLUMNA DE ACCIONES
   --------------------------------------------------------------------- */

/* Un boton por renglon: son acciones distintas y ninguna se pulsa por
   error si tiene su propio espacio. Es tambien lo que evita el
   "Descargar PDF" partido en dos lineas que estiraba la fila a 71px. */
.ticket-archivo,
.ticket-siguiente { display: grid; gap: var(--s2); }
.ticket-archivo { margin-top: var(--s4); }
.ticket-archivo .btn,
.ticket-siguiente .btn {
  width: 100%;
  justify-content: flex-start;
  min-height: 44px;
}

.ticket-enviado {
  display: flex;
  align-items: flex-start;
  gap: var(--s2);
  margin: 0 0 var(--s4);
  padding: var(--s2) var(--s3);
  border-radius: var(--r-sm);
  background: var(--ok-bg);
  color: var(--ok);
  font-size: var(--fs-xs);
  line-height: var(--lh);
}
.ticket-enviado svg { flex: none; margin-top: var(--s0); }

/* ---------- La vista de impresion ---------- */
/* El texto guardado no se esconde: es lo que sale por la impresora, lo
   que se copia y lo que se manda por WhatsApp. Se ensena como lo que
   es, una hoja de papel termico. */
.ticket-papel {
  padding: var(--s4) var(--s3);
  border: 1px dashed var(--border-2);
  border-radius: var(--r-sm);
  background: var(--surface-2);
  overflow-x: auto;
}

.ticket-texto {
  font-family: "Courier New", Courier, monospace;
  font-size: var(--fs-xs);
  line-height: 1.5;
  white-space: pre;               /* 32 columnas exactas, sin reflujo */
  margin: 0;
  color: var(--text-2);
}

/* Entre 600 y 900px la pantalla es de una columna, pero el documento NO
   se estira a lo ancho: a 768px medido daba un comprobante de 641px, que
   ya no se lee como un comprobante sino como una tabla. Se queda en sus
   520px y se centra, que es lo que hace una hoja sobre una mesa. */
@media (max-width: 599px) {
  .doc { max-width: 100%; padding: var(--s5) var(--s4) var(--s4); }
}


/* =====================================================================
   34. SISTEMA DE PRODUCTO  (Etapa 2 - Design System)
   ---------------------------------------------------------------------
   Todo lo de aqui abajo es del sistema, no de una pantalla. Si algo
   solo sirve para una pantalla, no va en esta seccion.
   ===================================================================== */


/* ---------------------------------------------------------------------
   34a. TIPOGRAFIA DE PRODUCTO
   --------------------------------------------------------------------- */

/* Las cifras son el contenido, no la decoracion. Un inventario y un
   corte de caja se leen buscando un numero, asi que los numeros llevan
   su propio tratamiento: ancho fijo por digito para que no bailen al
   actualizarse, y el interletraje cerrado que piden los numeros grandes. */
.cifra {
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
  letter-spacing: var(--ls-titulo);
  line-height: 1;
}

.display    { font-size: var(--fs-display);    line-height: var(--lh-titulo); letter-spacing: var(--ls-display); font-weight: var(--peso-max); }


/* Rotulo: el texto pequeno en mayusculas que nombra un dato. En
   mayusculas y a 12px las letras se pegan, por eso se abren. */


/* ---------------------------------------------------------------------
   34b. BOTONES: LAS VARIANTES QUE FALTABAN
   ---------------------------------------------------------------------
   Habia tres (primario, sec, peligro) y ningun estado de carga. Un
   boton sin estado de carga obliga al usuario a adivinar si le hizo
   caso, y es exactamente el momento en el que un producto se siente
   barato.
   --------------------------------------------------------------------- */

/* El primario tira luz de su propio color, no una mancha gris. */
.btn { box-shadow: var(--sh-marca); }
.btn:hover:not(:disabled) { box-shadow: var(--sh-marca), 0 0 0 1px var(--brand-halo); }
.btn-sec, .btn-plano, .btn-peligro-plano { box-shadow: var(--sh-1); }

/* Terciario: se comporta como boton pero no compite. Para salidas y
   acciones opcionales que no quieres que la vista escanee primero. */
.btn-plano {
  background: transparent;
  color: var(--text-2);
  border-color: transparent;
  box-shadow: none;
}
.btn-plano:hover { background: var(--surface-2); color: var(--text); }

/* Destructivo discreto: rojo solo en el texto hasta que se apunta. */
.btn-peligro-plano {
  background: var(--error-bg);
  color: var(--error);
  border-color: transparent;
  box-shadow: none;
}
.btn-peligro-plano:hover { background: var(--error); color: var(--text-inv); }

/* Solo icono. Cuadrado y con el area de toque completa aunque el icono
   mida 18px.

   G3 agrupa aqui .btn-icono-solo, que significaba exactamente lo mismo
   y solo tenia forma cuadrada DENTRO de .tabla-fichas: fuera de esa
   tabla era un boton normal con el texto oculto, o sea un blanco
   estrecho e imprevisible. Dos nombres, un componente, una regla. */
.btn-icono,
.btn-icono-solo {
  width: var(--tap);
  min-width: var(--tap);
  padding: 0;
}

/* ---------- Tamanos ----------
   Tres, y solo tres: sm (40) para acciones de fila, el de base (44,
   --tap) para todo lo demas y lg (52) para la accion protagonista de
   una pantalla.

   .btn-grande era un cuarto tamano con su propio criterio -letra
   grande, relleno uniforme, sin altura minima ni radio propio- que
   significaba lo mismo que lg. Las cinco plantillas que lo pedian
   pasan a .btn-lg en esta misma pasada; la clase se queda agrupada
   aqui para que nada se quede sin estilo si se me escapo alguna, y
   queda anotada para retirarla en G9. */
.btn-lg,
.btn-grande {
  min-height: 52px;
  padding: var(--s4) var(--s6);
  font-size: var(--fs-lg);
  border-radius: var(--r-md);
}

/* ---- Estado de carga ----
   app.js ya cambiaba el texto del boton por "Entrando...", "Guardando..."
   y lo desactivaba. Lo que faltaba era la senal de que algo esta
   pasando AHORA: un texto quieto puede ser un boton roto.

   El giro SUSTITUYE al icono del boton en vez de anadirse, asi el boton
   no cambia de ancho a media pulsacion ni desplaza lo que tenga al lado.

   El atributo es data-espera y no data-cargando porque data-cargando ya
   significa otra cosa: el TEXTO que hay que poner mientras se espera.
   Un atributo, un significado. */
.btn[data-espera="1"] {
  pointer-events: none;
  /* Conserva su color. Un primario que se vuelve gris al pulsarlo
     parece que se desactivo, no que esta trabajando. */
  background: var(--btn-primario-bg);
  color: var(--btn-primario-texto);
  border-color: transparent;
  opacity: .92;
}
.btn-sec[data-espera="1"],
.btn-plano[data-espera="1"] {
  background: var(--surface);
  color: var(--text-2);
  border-color: var(--border);
}

.btn[data-espera="1"] .icono { display: none; }

.btn[data-espera="1"]::before {
  content: "";
  flex: none;
  width: 16px; height: 16px;
  border: 2px solid currentColor;
  border-top-color: transparent;
  border-radius: var(--r-full);
  animation: btn-girar 620ms linear infinite;
}

/* Con movimiento reducido no gira: se queda el circulo partido, quieto,
   que igual dice "esto no es el estado normal". */
@media (prefers-reduced-motion: reduce) {
  .btn[data-espera="1"]::before { animation: none; }
}

@keyframes btn-girar { to { transform: rotate(360deg); } }

/* ---- Estado de exito ----
   Dura un instante y se quita solo. Confirma la accion en el mismo
   sitio donde se hizo, sin mover nada de la pantalla. */
.btn[data-listo="1"] {
  background: var(--ok);
  border-color: transparent;
  color: var(--text-inv);
  box-shadow: none;
}

/* En una pantalla de dedo, un boton de 38px es un blanco que se falla.
   .btn-sm existe para que la accion de una fila no pese como la
   accion de la pantalla, y eso es cuestion de COLOR y de peso, no
   de tener menos area de toque. */
@media (pointer: coarse) {
  .btn-sm { min-height: 44px; }
}



/* ---------------------------------------------------------------------
   34c. METRICAS CON JERARQUIA
   ---------------------------------------------------------------------
   El problema que resuelve: habia 52 bloques de KPI escritos a mano en
   12 archivos, todos del mismo tamano. Ocho cajas iguales en fila no
   son ocho datos: son ninguno, porque el ojo no sabe donde caer.
   Ahora una metrica declara su peso.
   --------------------------------------------------------------------- */

/* =====================================================================
   G2 — EL REPARTO DE LAS METRICAS
   ---------------------------------------------------------------------
   MEDIDO ANTES DE TOCAR NADA, sobre admin/reportes (heroe + 3 compactas)
   y admin/dashboard (cuatro metricas iguales):

     ventana  util   HEROE: cols x filas   compacta   HUECO
       768     641         3 x 2             206       206
       900     773         3 x 2             250       250
      1024     912         3 x 2             296       296
      1180     971         5 x 1             185         0
      1440    1148         5 x 1             220         0
      1920    1552         5 x 1             301         0

     ventana  util   IGUALES: cols   tarjeta
       768     641         2             315
       900     773         2             381
      1024     912         2             450
      1180     971         4             234
      1440    1148         4             278
      1920    1552         4             379

   Son dos defectos y por debajo son el mismo:

   1. EL HUECO. El heroe ocupa dos celdas y le acompanan tres compactas:
      cinco celdas. En tres columnas eso cae en dos filas de tres -SEIS
      celdas- y la sexta se quedaba vacia. De 768 a 1179 habia entre 206
      y 296px de nada al lado de la ultima compacta.

   2. EL SALTO. Las dos rejillas cambiaban de reparto en 1180, un numero
      de VENTANA que no sabe nada del sitio que hay de verdad. Al
      cruzarlo la tarjeta pasaba de 296 a 185 y de 450 a 234: la ventana
      crece un pixel y el contenido encoge un 40%.

   LO QUE HACE G2
   --------------
   a) LA REJILLA SE MIDE CONTRA EL ANCHO UTIL, NO CONTRA LA VENTANA.
      No son lo mismo: a 1280 con la lateral abierta hay 988px utiles y
      con la lateral plegada 1168, y la ventana mide igual en los dos
      casos. Antes, en el segundo caso, la rejilla desaprovechaba 180px
      porque preguntaba a quien no lo sabia. Ahora pregunta a
      .metricas-zona, que es lo unico para lo que esa capa existe.

   b) SOLO SE USAN REPARTOS QUE LLENAN LA ULTIMA FILA. El helper escribe
      cuantas metricas hay en data-metricas y el reparto se elige
      sabiendolo. Si un reparto dejaria una celda suelta: o no se usa, o
      la ultima tarjeta ocupa el hueco. No hay tercera opcion, y por eso
      el hueco no puede volver.

   c) EL ESCALON DE 1180 DESAPARECE DE LAS METRICAS. Los dos que quedan
      en el archivo con ese numero son del ticket y del POS, que no son
      metricas.

   Los numeros de los escalones no son gusto: 190px es el ancho por
   debajo del cual "$4,820.00" y su rotulo dejan de convivir en la misma
   tarjeta. Tres columnas necesitan 3x190 + 2 huecos = 594; cuatro,
   4x190 + 3 huecos = 796. Cambiar el minimo obliga a recalcular ambos.
   ===================================================================== */

/* La capa de consulta. No pinta, no tiene margenes y no cambia el flujo:
   existe para que las reglas de abajo puedan preguntar cuanto ancho hay
   AQUI en lugar de cuanto mide la ventana. */
.metricas-zona { container: metricas / inline-size; }

.metricas {
  --cols: 1;
  display: grid;
  gap: var(--s3);
  margin-bottom: var(--s5);
  align-items: stretch;
  grid-template-columns: repeat(var(--cols), minmax(0, 1fr));
}

/* Red de seguridad para una rejilla escrita a mano, sin pasar por el
   helper: se reparte por ancho. Funciona, pero no garantiza la fila
   llena; el camino bueno es el helper. */
.metricas:not([data-metricas]) {
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
}

/* ---------- Reparto: metricas del mismo peso ----------
   El :not() no es defensivo: heroe y apoyo comparten la clase .metricas
   -de ella sacan tarjeta, hueco y margen- pero NO su reparto. Sin el,
   las tres familias se pisarian por orden de aparicion en el archivo. */
.metricas[data-metricas="2"]:not(.metricas-heroe):not(.metricas-apoyo),
.metricas[data-metricas="4"]:not(.metricas-heroe):not(.metricas-apoyo),
.metricas[data-metricas="6"]:not(.metricas-heroe):not(.metricas-apoyo) { --cols: 2; }

@container metricas (min-width: 480px) {
  .metricas[data-metricas="3"]:not(.metricas-heroe):not(.metricas-apoyo) { --cols: 3; }
}
@container metricas (min-width: 594px) {
  .metricas[data-metricas="4"]:not(.metricas-heroe):not(.metricas-apoyo),
  .metricas[data-metricas="6"]:not(.metricas-heroe):not(.metricas-apoyo) { --cols: 3; }

  /* Cuatro en tres columnas dejan una celda suelta, asi que la ultima
     ocupa el hueco. Solo pasa en la franja 594-795 de ancho util, y es
     justo la que convierte el salto de 450->234 en 250->219. */
  .metricas[data-metricas="4"]:not(.metricas-heroe):not(.metricas-apoyo) > .metrica:last-child {
    grid-column: 1 / -1;
  }
}
@container metricas (min-width: 796px) {
  .metricas[data-metricas="4"]:not(.metricas-heroe):not(.metricas-apoyo) { --cols: 4; }
  .metricas[data-metricas="4"]:not(.metricas-heroe):not(.metricas-apoyo) > .metrica:last-child {
    grid-column: auto;
  }
}
@container metricas (min-width: 1000px) {
  .metricas[data-metricas="5"]:not(.metricas-heroe):not(.metricas-apoyo) { --cols: 5; }
  .metricas[data-metricas="6"]:not(.metricas-heroe):not(.metricas-apoyo) { --cols: 6; }
}

/* ---------- Reparto: con protagonista ----------
   El heroe no es una tarjeta mas grande: es la cifra que da nombre a la
   pantalla. Nunca se queda en una sola celda -o fila entera, o dos
   columnas- y el numero de columnas se elige para que las compactas
   cierren la fila:

     hasta 479   2 columnas. Heroe a lo ancho, compactas de dos en dos.
     480-899     3 columnas. Heroe a lo ancho, 3 compactas debajo.
     desde 900   5 columnas. Todo en una fila, heroe de dos.

   Las tres llenan la ultima fila. La de 3 columnas es la que sustituye
   al hueco de 206-296px: el heroe se lleva la fila entera en lugar de
   dejar media suelta. */
.metricas-heroe { --cols: 2; --heroe: 2; }
.metricas-heroe > .metrica-heroe { grid-column: span var(--heroe); }

/* Con el heroe ocupando fila entera, las compactas van de dos en dos; si
   son impares, la ultima se quedaria sola y ocupa el hueco. El heroe es
   el hijo 1, asi que "ultimo hijo en posicion par" es exactamente
   "numero impar de compactas": la regla vale para cualquier cantidad y
   no hay que escribir una por pantalla. */
.metricas-heroe > .metrica:last-child:nth-child(even) { grid-column: span 2; }

@container metricas (min-width: 480px) {
  .metricas-heroe[data-metricas="4"] { --cols: 3; --heroe: 3; }
  .metricas-heroe[data-metricas="4"] > .metrica:last-child { grid-column: auto; }
}
@container metricas (min-width: 640px) {
  /* Heroe + 2 compactas son cuatro celdas y cuatro columnas cierran la
     fila. Es el caso de la ventana de ejemplo de la portada. */
  .metricas-heroe[data-metricas="3"] { --cols: 4; --heroe: 2; }
}
/* 800 no es un numero redondo elegido a ojo: es el punto donde los dos
   repartos se tocan por sus extremos. Con tres columnas la compacta
   llega ahi a (800-24)/3 = 259, el tope a partir del cual una compacta
   deja de leerse como apoyo; con cinco arranca en (800-48)/5 = 150, su
   minimo. Cambiar de reparto siempre estrecha la tarjeta -cinco
   columnas caben menos que tres-, y este es el ancho donde ese escalon
   cuesta lo menos posible. Antes el cambio pasaba en 1180 de ventana,
   con la compacta ya en 296. */
@container metricas (min-width: 800px) {
  .metricas-heroe[data-metricas="4"] { --cols: 5; --heroe: 2; }
}

/* ---------- Reparto: metricas de APOYO ----------
   Misma maquinaria que las demas -mismo --cols, mismo contenedor, misma
   tarjeta- y una sola diferencia, que es semantica y no de layout: el
   apoyo NO se estira a lo ancho de la pantalla. Medido en el catalogo:
   tres metricas de 375px para ensenar un numero de una cifra pesaban
   como si fueran el contenido de la pantalla, y ahi lo que se viene a
   hacer es BUSCAR. Por eso la columna tiene tope y la rejilla se alinea
   a la izquierda; lo que queda a la derecha no es una celda vacia, es el
   sitio que este bloque decide no ocupar. */
.metricas-apoyo {
  --tope: 220px;
  grid-template-columns: repeat(var(--cols), minmax(0, var(--tope)));
  justify-content: start;
}
.metricas-apoyo[data-metricas="2"] { --cols: 2; }
.metricas-apoyo[data-metricas="3"] { --cols: 3; }
/* Cuatro de apoyo en tres columnas dejaban la cuarta sola: en poco ancho
   van dos y dos, y las cuatro en fila en cuanto caben. */
.metricas-apoyo[data-metricas="4"] { --cols: 2; }
@container metricas (min-width: 560px) {
  .metricas-apoyo[data-metricas="4"] { --cols: 4; }
}

.metrica {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--s1);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  padding: var(--s4);
  box-shadow: var(--sh-1);
  min-width: 0;
  overflow: hidden;

  /* G2: la tarjeta tambien es contenedor de consulta, y esto es lo que
     le permite funcionar igual en 2, en 3 y en 4 columnas. La cifra se
     mide contra SU tarjeta -no contra la ventana, no contra la rejilla-
     asi que una tarjeta de 190px baja el cuerpo en lugar de partir el
     valor, y una de 450 no lo agranda mas de la cuenta. Sin nombre a
     proposito: las consultas de reparto piden "metricas" por su nombre
     y no se cuelan aqui. */
  container-type: inline-size;
}

.metrica-cab {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s2);
}
.metrica-rotulo {
  font-size: var(--fs-xs);
  font-weight: var(--peso-fuerte);
  text-transform: uppercase;
  letter-spacing: var(--ls-etiqueta);
  color: var(--text-2);
  min-width: 0;
  overflow-wrap: anywhere;
}
.metrica-icono { color: var(--text-3); flex: none; }

.metrica-valor {
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  font-weight: var(--peso-max);
  letter-spacing: var(--ls-titulo);
  line-height: 1;
  /* El tope sigue siendo el de siempre; el minimo entra en juego solo
     cuando la tarjeta se estrecha. 16cqi llega a --fs-2xl con 195px de
     tarjeta, asi que de 195 en adelante NADA cambia respecto de antes.
     Por debajo, la cifra baja en vez de partirse: es lo que hace que la
     misma tarjeta valga para 2, 3 y 4 columnas. */
  font-size: clamp(var(--fs-xl), 16cqi, var(--fs-2xl));
  margin-top: var(--s1);
  overflow-wrap: break-word;
}
.metrica-sub {
  font-size: var(--fs-xs);
  color: var(--text-2);
  margin-top: auto;
  padding-top: var(--s2);
}

/* ---- Peso: heroe ----
   La cifra que da nombre a la pantalla. Ocupa mas, pesa mas y lleva un
   fondo propio para que no haya duda de cual se lee primero. */
.metrica-heroe {
  padding: var(--s5);
  background: linear-gradient(180deg, var(--brand-50) 0%, var(--surface) 62%);
}
.metrica-heroe .metrica-valor  { font-size: var(--fs-4xl); }
.metrica-heroe .metrica-rotulo { color: var(--brand-700); }
.metrica-heroe .metrica-icono  { color: var(--brand-600); }

/* ---- Peso: compacta ----
   Dato de apoyo. Se lee cuando ya se leyo lo importante. */
.metrica-compacta { padding: var(--s3) var(--s4); gap: 0; }
.metrica-compacta .metrica-valor { font-size: clamp(var(--fs-lg), 14cqi, var(--fs-xl)); margin-top: var(--s0); }
.metrica-compacta .metrica-sub   { font-size: var(--fs-xs); padding-top: var(--s1); }

/* ---- Tonos ----
   El color lo pone el DATO, no la decoracion: una cifra en rojo tiene
   que significar algo malo de verdad. */
.metrica-ok    .metrica-valor { color: var(--ok); }
.metrica-warn  .metrica-valor { color: var(--warn); }
.metrica-crit  .metrica-valor { color: var(--crit); }
.metrica-error .metrica-valor { color: var(--error); }
.metrica-marca .metrica-valor { color: var(--brand-600); }

/* Un filo de color a la izquierda cuando la metrica avisa de algo. */
.metrica-warn::before, .metrica-crit::before, .metrica-error::before {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: 3px;
}
.metrica-warn::before  { background: var(--warn); }
.metrica-crit::before  { background: var(--crit); }
.metrica-error::before { background: var(--error); }

/* Metrica que lleva a algun lado */
a.metrica {
  color: inherit;
  text-decoration: none;
  transition: box-shadow var(--t-rapida) var(--ease),
              transform var(--t-rapida) var(--ease),
              border-color var(--t-rapida) var(--ease);
}
a.metrica:hover {
  box-shadow: var(--sh-2);
  border-color: var(--border-2);
  transform: translateY(-1px);
}

/* ---- Poca anchura: DENSIDAD ----
   Va con el componente y, como el reparto, se mide contra el ancho util
   y no contra la ventana: una rejilla de metricas dentro de un panel
   estrecho tiene el mismo problema que en un telefono, y antes solo se
   enteraba la del telefono.

   Aqui NO se toca el reparto -eso ya esta resuelto arriba-: solo baja la
   densidad, que es lo unico que de verdad depende de tener poco sitio.
   El corte en 559 es el ancho util de un telefono de 430 con holgura;
   por encima hay sitio para la tarjeta completa. */
@container metricas (max-width: 559px) {
  .metricas { gap: var(--s2); }

  /* Sin estirar: con stretch, una fila de compactas copiaba la altura de
     la mas alta y quedaban tarjetas medio vacias de 300px. */
  .metricas { align-items: start; }

  .metrica-heroe { padding: var(--s4); }

  /* La cifra del dia se mide contra SU tarjeta, que aqui ocupa la fila
     entera. Antes era 9vw, o sea la ventana; el resultado es el mismo en
     un telefono -32px a 390, los mismos que antes- y ademas sigue siendo
     correcto dentro de un panel estrecho, donde 9vw no lo era. */
  .metricas-heroe .metrica-heroe .metrica-valor { font-size: clamp(30px, 10.5cqi, 40px); }

  .metricas-apoyo .metrica { padding: var(--s3); }
  .metricas-apoyo .metrica-rotulo { font-size: var(--fs-3xs); letter-spacing: .04em; }
  .metricas-apoyo .metrica-sub { display: none; }
  .metrica-compacta { min-height: 84px; }
}

/* Variacion contra el periodo anterior */
.metrica-delta {
  display: inline-flex;
  align-items: center;
  gap: var(--s1);
  font-size: var(--fs-xs);
  font-weight: var(--peso-fuerte);
  font-variant-numeric: tabular-nums;
}
.metrica-delta-sube  { color: var(--ok); }
.metrica-delta-baja  { color: var(--error); }
.metrica-delta-igual { color: var(--text-3); }

/* Tono de la linea de apoyo. Es donde va el color cuando lo bueno o
   malo es la VARIACION y no el valor. */
.metrica-sub-ok    { color: var(--ok); }
.metrica-sub-warn  { color: var(--warn); }
.metrica-sub-crit  { color: var(--crit); }
.metrica-sub-error { color: var(--error); }


/* ---------------------------------------------------------------------
   34c-bis. LA METRICA CON CARA   (Fase C)
   ---------------------------------------------------------------------
   El tablero de escritorio tenia todo el contenido y ninguna cara: ocho
   rectangulos con un rotulo en versalitas y un numero. El de telefono,
   en cambio, ya habia resuelto el lenguaje -azulejo con tono, cifra,
   pastilla de estado, curva- y ese lenguaje estaba encerrado dentro de
   .tm, que por encima de 768 no se pinta.

   Esto NO copia la pantalla de telefono a escritorio: trae sus PIEZAS.
   El azulejo es el mismo componente, con los mismos tonos y la misma
   tabla icono->tono; la curva es la misma idea que ui_grafica_linea()
   en version de reojo.

   DOS PIEZAS, DOS ALCANCES, Y ESTO IMPORTA:

     · el azulejo y la curva solo aparecen si quien llama los pide, y
       hoy solo los pide el tablero. Ninguna otra de las ~40 metricas
       del sistema cambia un pixel, en ningun ancho.
     · el relieve al pasar el raton SI alcanza a cualquier metrica, y
       por eso vive dentro de (min-width: 768px) and (hover: hover):
       en un telefono no existe el raton y no existe la regla.
   --------------------------------------------------------------------- */

/* ---------- La minigrafica ----------
   Va a sangre contra el borde de abajo: asi se lee como el SUELO del
   dato y no como un tercer renglon. La tarjeta ya recorta
   (overflow:hidden), asi que las esquinas de abajo la cortan solas. */
.metrica-chispa {
  position: relative;
  display: block;
  height: 30px;
  margin-top: var(--s3);
  /* El sangrado se calcula con el MISMO relleno que declara la tarjeta,
     asi que si el relleno cambia, esto cambia con el. */
  width: calc(100% + var(--s4) * 2);
  margin-inline: calc(var(--s4) * -1);
  margin-bottom: calc(var(--s4) * -1);
  color: var(--brand-600);
  pointer-events: none;
}
.metrica-chispa svg { display: block; width: 100%; height: 100%; }

.metrica-chispa-area  { fill: currentColor; fill-opacity: .14; }
.metrica-chispa-linea {
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* El punto de hoy. Es HTML y no SVG a proposito: ver ui_chispa(). */
.metrica-chispa-punta {
  position: absolute;
  right: 3px;
  bottom: var(--cy, 50%);
  width: 6px;
  height: 6px;
  margin: -3px -3px 0 0;
  border-radius: 50%;
  background: currentColor;
  box-shadow: 0 0 0 2px var(--surface);
}

.metrica-chispa-ok   { color: var(--ok); }
.metrica-chispa-crit { color: var(--crit); }
.metrica-chispa-warn { color: var(--warn); }

/* Los dos pesos declaran otro relleno, asi que declaran otro sangrado. */
.metrica-compacta .metrica-chispa {
  height: 26px;
  margin-top: var(--s2);
  margin-bottom: calc(var(--s3) * -1);
}
.metrica-heroe .metrica-chispa {
  height: 44px;
  width: calc(100% + var(--s5) * 2);
  margin-inline: calc(var(--s5) * -1);
  margin-bottom: calc(var(--s5) * -1);
}
/* La heroe tiene fondo propio y el punto se recorta contra el, no
   contra la superficie lisa. */
.metrica-heroe .metrica-chispa-punta { box-shadow: 0 0 0 2px var(--brand-50); }

/* En una tarjeta muy estrecha la curva estorba mas de lo que cuenta:
   28px de alto sobre 84 de tarjeta son un tercio para un adorno. Se
   retira con el mismo criterio -y en el mismo sitio- con el que ya se
   retiraba la linea de apoyo. */
@container metricas (max-width: 559px) {
  .metricas-apoyo .metrica-chispa { display: none; }
}


/* ---------------------------------------------------------------------
   34c-ter. EL RELIEVE DE ESCRITORIO   (Fase C)
   ---------------------------------------------------------------------
   Una metrica que no lleva a ningun lado no tiene por que responder al
   raton... salvo que este en un tablero, donde lo que se hace es
   recorrer ocho tarjetas con la vista. Un levantamiento de 2px y el
   azulejo que gira un poco es lo que convierte una cuadricula en una
   superficie de objetos.

   (hover: hover) y no un ancho a secas: una tableta tactil de 1024 no
   tiene raton, y ahi este efecto se quedaria pegado al primer toque.
   --------------------------------------------------------------------- */
@media (min-width: 768px) and (hover: hover) {
  .metrica {
    transition: transform var(--t-rapida) var(--ease),
                box-shadow var(--t-rapida) var(--ease),
                border-color var(--t-rapida) var(--ease);
  }
  /* a.metrica:hover ya existia con -1px y gana por especificidad
     (elemento + clase). Se nombra aqui para que las dos clases de
     metrica -la que lleva a algun lado y la que no- se levanten lo
     mismo: dos alturas distintas en la misma fila se notan. */
  .metrica:hover,
  a.metrica:hover {
    transform: translateY(-2px);
    box-shadow: var(--sh-2);
    border-color: var(--border-2);
  }
  .metrica .azulejo {
    transition: transform var(--t-rapida) var(--ease-suave);
  }
  .metrica:hover .azulejo { transform: scale(1.07) rotate(-5deg); }

  /* La curva se enciende un punto al pasar por encima: el dato de
     apoyo se vuelve legible justo cuando lo estas mirando. */
  .metrica .metrica-chispa-area { transition: fill-opacity var(--t-rapida) var(--ease); }
  .metrica:hover .metrica-chispa-area { fill-opacity: .26; }
}


/* ---------------------------------------------------------------------
   34d. CABECERA DE PANTALLA
   ---------------------------------------------------------------------
   El patron "titulo + acciones + descripcion" estaba escrito a mano en
   25 archivos, cada uno con su propio margen.
   --------------------------------------------------------------------- */

.pagina-cab {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--s4);
  flex-wrap: wrap;
  margin-bottom: var(--s5);
}
.pagina-cab-txt { min-width: 0; }

/* OLA 1: el tamano sale de la escalera de :root y se declara UNA vez,
   aqui, para las 28 pantallas -con azulejo y sin el-. Antes lo decidian
   cuatro sitios distintos: esta regla (20), la 43b en telefono (24), la
   57b en escritorio (26) y siete copias acotadas con :has() (20/17/15).
   Un formulario y el destino al que pertenece abren ahora con el mismo
   titulo, que es lo que hace que se lean como la misma pantalla. */
.pagina-cab h1 {
  font-size: var(--cab-titulo);
  line-height: var(--lh-titulo);
  letter-spacing: var(--ls-titulo);
  margin: 0;
}
.pagina-cab-desc {
  margin: var(--s1) 0 0;
  color: var(--text-2);
  font-size: var(--cab-desc);
  max-width: var(--ancho-texto);
}
.pagina-cab-acciones {
  display: flex;
  gap: var(--s2);
  flex-wrap: wrap;
  flex: none;
}

/* TELEFONO: la accion de la pantalla NO desaparece, se acomoda.
   El encabezado se apila y la fila de acciones toma el ancho completo,
   que es la forma en la que una aplicacion de telefono presenta su
   accion principal. Con dos botones se reparten la fila; con uno, ocupa
   la fila entera. La altura la sigue poniendo .btn (44px, G3), asi que
   el area de toque no cambia.

   Este es el UNICO sitio donde se decide como se ven las acciones de
   una pantalla en telefono: no hay una regla por pantalla. */
@media (max-width: 767px) {
  .pagina-cab {
    flex-direction: column;
    align-items: stretch;
    gap: var(--s3);
    margin-bottom: var(--s4);
  }
  .pagina-cab-acciones { width: 100%; }
  .pagina-cab-acciones > .btn { flex: 1 1 auto; }
}


/* ---------------------------------------------------------------------
   34e. IMAGEN DE PRODUCTO
   ---------------------------------------------------------------------
   Las fotos ya se suben y se guardan, pero solo se pintaban en el
   formulario de edicion. Cuando no hay foto NO se deja un hueco gris:
   se pintan las iniciales sobre un color derivado del propio nombre,
   asi dos productos distintos nunca se ven iguales.
   --------------------------------------------------------------------- */

.prod-img {
  flex: none;
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  border-radius: var(--r-md);
  overflow: hidden;
  background: var(--surface-2);
  border: 1px solid var(--border);
  font-size: var(--fs-sm);
  font-weight: var(--peso-max);
  color: var(--text-2);
  text-transform: uppercase;
  user-select: none;
}
.prod-img img { width: 100%; height: 100%; object-fit: cover; display: block; }

.prod-img-sm { width: 30px; height: 30px; font-size: var(--fs-xs); border-radius: var(--r-sm); }
.prod-img-lg { width: 56px; height: 56px; font-size: var(--fs-lg); }
.prod-img-xl { width: 88px; height: 88px; font-size: var(--fs-2xl); border-radius: var(--r-lg); }

/* Las seis variantes de color del marcador. La elige el PHP a partir
   del nombre, asi que un producto siempre sale del mismo color. */
/* OLA 2: los mismos seis colores, ahora desde la paleta categorica.
   El tema oscuro ya no necesita seis reglas gemelas: los tokens se
   invierten solos. */
.prod-img-c0 { background: var(--cat-0-bg); color: var(--cat-0); border-color: var(--cat-0-borde); }
.prod-img-c1 { background: var(--cat-1-bg); color: var(--cat-1); border-color: var(--cat-1-borde); }
.prod-img-c2 { background: var(--cat-2-bg); color: var(--cat-2); border-color: var(--cat-2-borde); }
.prod-img-c3 { background: var(--cat-3-bg); color: var(--cat-3); border-color: var(--cat-3-borde); }
.prod-img-c4 { background: var(--cat-4-bg); color: var(--cat-4); border-color: var(--cat-4-borde); }
.prod-img-c5 { background: var(--cat-5-bg); color: var(--cat-5); border-color: var(--cat-5-borde); }

/* Producto: foto + nombre + apoyo, en una celda de tabla o en una ficha */
.prod-linea {
  display: flex;
  align-items: center;
  gap: var(--s3);
  min-width: 0;
}
.prod-linea-txt { min-width: 0; }
.prod-linea-nom {
  display: block;
  font-weight: var(--peso-fuerte);
  line-height: 1.3;
  overflow-wrap: anywhere;
}
.prod-linea-sub {
  display: block;
  font-size: var(--fs-xs);
  color: var(--text-3);
  margin-top: 1px;
}


/* ---------------------------------------------------------------------
   34f. CAMPO DE FECHA
   ---------------------------------------------------------------------
   El input nativo se conserva (en telefono no hay nada mejor: rueda del
   sistema, teclado correcto, accesible), pero se le repintan las partes
   que si se pueden estilar y se le pone delante nuestro propio icono.
   El indicador nativo se estira invisible por encima de todo el campo,
   asi que al tocar en cualquier punto se abre el calendario, no solo en
   su esquina.
   --------------------------------------------------------------------- */

.campo-fecha { min-width: 0; }

.campo-fecha .rotulo {
  display: block;
  font-size: var(--fs-xs);
  font-weight: var(--peso-fuerte);
  text-transform: uppercase;
  letter-spacing: var(--ls-etiqueta);
  color: var(--text-2);
  margin-bottom: var(--s1);
}

.campo-fecha-caja {
  position: relative;
  display: flex;
  align-items: center;
}
.campo-fecha-icono {
  position: absolute;
  left: var(--s3);
  color: var(--text-3);
  pointer-events: none;
  z-index: 1;
}

.campo-fecha input[type="date"] {
  width: 100%;
  min-height: var(--tap);
  padding: var(--s2) var(--s3) var(--s2) 36px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  font-family: var(--font);
  font-size: var(--fs-base);
  font-variant-numeric: tabular-nums;
  color: var(--text);
  appearance: none;
}
.campo-fecha input[type="date"]:hover { border-color: var(--border-2); }
.campo-fecha input[type="date"]:focus {
  outline: none;
  border-color: var(--brand-600);
  box-shadow: var(--ring);
}

/* El campo vacio no debe gritar "mm/dd/aaaa" en negro */
.campo-fecha input[type="date"]:not(:focus):invalid,
.campo-fecha input[type="date"]:not(:focus)[value=""] { color: var(--text-3); }

/* Partes internas del control nativo (WebKit / Chromium) */
.campo-fecha input[type="date"]::-webkit-datetime-edit { padding: 0; }
.campo-fecha input[type="date"]::-webkit-datetime-edit-fields-wrapper { padding: 0; }
.campo-fecha input[type="date"]::-webkit-datetime-edit-text { color: var(--text-3); padding: 0 1px; }
.campo-fecha input[type="date"]::-webkit-inner-spin-button { display: none; }
.campo-fecha input[type="date"]::-webkit-clear-button { display: none; }

/* El icono nativo se estira, invisible, sobre todo el campo: cualquier
   punto abre el calendario. */
.campo-fecha input[type="date"]::-webkit-calendar-picker-indicator {
  position: absolute;
  inset: 0;
  width: auto;
  height: auto;
  margin: 0;
  padding: 0;
  opacity: 0;
  cursor: pointer;
}


/* ---------------------------------------------------------------------
   25b. PASOS DE LA PANTALLA DE RECUPERAR
   --------------------------------------------------------------------- */

.acceso-pasos-claros {
  margin: 0 0 var(--s5);
  padding: 0;
  list-style: none;
  counter-reset: pc;
  display: grid;
  gap: var(--s3);
}
/* El <li> es un BLOQUE con sangria, no una rejilla. En una rejilla cada
   hijo es un elemento propio y el texto que sigue a un <strong> cuenta
   como otro: acababa metido en la columna de 26px, una palabra por
   renglon. Con el numero fuera del flujo da igual lo que lleve dentro. */
.acceso-pasos-claros li {
  counter-increment: pc;
  position: relative;
  padding-left: 38px;
  min-height: 26px;
  font-size: var(--fs-sm);
  color: var(--text-2);
  line-height: 1.5;
}
.acceso-pasos-claros li::before {
  content: counter(pc);
  position: absolute;
  left: 0;
  top: 0;
  display: grid;
  place-items: center;
  width: 26px; height: 26px;
  border-radius: var(--r-full);
  background: var(--brand-50);
  color: var(--brand-700);
  border: 1px solid rgba(29,78,216,.14);
  font-size: var(--fs-xs);
  font-weight: var(--peso-max);
}
.acceso-pasos-claros strong { color: var(--text); font-weight: var(--peso-fuerte); }


/* =====================================================================
   35. PUNTO DE VENTA
   ---------------------------------------------------------------------
   Medido antes de tocar nada, a 1440px:
     - Rejilla 558 + 558, mitad y mitad. El lado del buscador dejaba
       492px de VACIO debajo porque no tenia nada que ensenar.
     - "Vaciar carrito" media lo mismo que "Cobrar" y estaba 8px por
       debajo: un toque desviado borra la venta que se esta armando.
     - Ni una imagen de producto, aunque la API ya las devolvia.

   Ahora: catalogo que se toca a la izquierda, carrito fijo a la
   derecha. En telefono, una barra de cobro siempre a mano.

   El CSS del punto de venta estaba repartido en SIETE bloques de cuatro
   secciones distintas del archivo. Aqui esta junto.
   ===================================================================== */

.pos {
  display: grid;
  gap: var(--s4);
  align-items: start;
}

/* Dos columnas solo cuando el carrito cabe sin estrujar el catalogo.
   MEDIDO a 1024px con el umbral puesto ahi: el catalogo se quedaba
   con 321px y el carrito con 380, o sea que la lista de productos
   era MAS ESTRECHA que el carrito y caia a UNA columna: 33 tarjetas
   en fila india y 4032px de pagina. El umbral sube a 1180, que es
   donde de verdad caben dos tarjetas al lado del carrito.
   Por debajo, el catalogo ocupa todo el ancho y el carrito va
   debajo, que es como se trabaja en una tableta. */
@media (min-width: 1180px) {
  .pos {
    grid-template-columns: minmax(0, 1fr) 380px;
  }
  /* El carrito acompana al desplazamiento: al agregar el producto
     numero quince, el total y el boton siguen a la vista. */
  .pos-carrito {
    position: sticky;
    top: var(--s4);
    max-height: calc(100vh - var(--s6));
    display: flex;
    flex-direction: column;
  }
  .pos-carrito .panel-cuerpo { overflow-y: auto; flex: 1 1 auto; min-height: 0; }
}

@media (min-width: 1440px) {
  .pos { grid-template-columns: minmax(0, 1fr) 420px; }
}

/* ---------- Buscador ----------
   OLA 1: el del sistema (.buscador, seccion 57d). Aqui vivia el ultimo
   46px literal del producto: el mismo numero que la seccion 57d citaba
   como el defecto que venia a corregir, en la pantalla donde mas
   importa. Con el se va tambien la letra de 15px, que era la que hacia
   que iOS diera un zoom al tocar el campo -en el punto de venta, con un
   cliente delante-.

   La LOGICA DE FOCO no se toca: sigue en pos.js y sigue decidiendo por
   tipo de puntero. Esto es presentacion. */

.pos-cab-pie {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s3);
  flex-wrap: wrap;
  margin-top: var(--s3);
}
.pos-contador {
  margin: 0;
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--text-2);
  font-variant-numeric: tabular-nums;
}
.pos-hoy { font-size: var(--fs-sm); color: var(--text-3); }

/* ---------- Cuadricula de productos ---------- */
/* auto-fill y no auto-fit: con tres productos, auto-fit los estira
   hasta ocupar toda la fila y quedan tres tarjetas gigantes. */
.pos-rejilla {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(168px, 1fr));
  gap: var(--s3);
}

.pos-item {
  position: relative;
  display: grid;
  grid-template-columns: 40px minmax(0, 1fr);
  grid-template-areas:
    "img  txt"
    "pie  pie";
  gap: var(--s2) var(--s3);
  padding: var(--s3);
  text-align: left;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  cursor: pointer;
  font-family: inherit;
  transition: border-color var(--t-rapida) var(--ease),
              box-shadow var(--t-rapida) var(--ease),
              transform var(--t-rapida) var(--ease);
}
.pos-item:hover {
  border-color: var(--brand-600);
  box-shadow: var(--sh-2);
}
.pos-item:active { transform: scale(var(--pulsa)); }
.pos-item:focus-visible {
  outline: var(--foco);
  outline-offset: 2px;
}

.pos-item .prod-img { grid-area: img; }
.pos-item-txt { grid-area: txt; min-width: 0; display: grid; gap: var(--s0); }
.pos-item-nom {
  font-weight: 600;
  font-size: var(--fs-sm);
  color: var(--text);
  line-height: 1.3;
  /* Dos renglones como maximo: con nombres largos de verdad las
     tarjetas se descuadraban unas respecto a otras. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.pos-item-sub {
  font-size: var(--fs-xs);
  color: var(--text-3);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.pos-item-pie {
  grid-area: pie;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s2);
}
.pos-item-precio { font-weight: 700; font-size: var(--fs-base); color: var(--text); }
.pos-item-stock { font-size: var(--fs-xs); color: var(--text-3); }
.pos-item-stock.es-agotado { color: var(--error); font-weight: 600; }

/* Agotado: no se puede tocar y se nota que no se puede. */
.pos-item-agotado {
  cursor: not-allowed;
  background: var(--surface-2);
  opacity: .62;
}
.pos-item-agotado:hover { border-color: var(--border); box-shadow: none; }
.pos-item-agotado:active { transform: none; }

/* Cuantas unidades lleva ya este producto en el carrito. */
.pos-item-marca {
  position: absolute;
  top: -8px;
  right: -8px;
  min-width: 24px;
  height: 24px;
  padding: 0 6px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--brand-600);
  color: var(--text-inv);
  border-radius: var(--r-full);
  font-size: var(--fs-xs);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  box-shadow: var(--sh-2);
}

.pos-sin-resultados {
  margin: 0;
  padding: var(--s6) var(--s4);
  text-align: center;
  color: var(--text-2);
}

/* ---------- Carrito ---------- */
.pos-carrito-cab { align-items: flex-start; }
.pos-carrito-cab h3 { margin: 0; }

/* Discreto y LEJOS del boton de cobrar: antes media lo mismo de ancho
   que "Cobrar" y estaba a 8px por debajo. */
.pos-vaciar { color: var(--text-3); }
.pos-vaciar:hover { color: var(--error); background: var(--error-bg); }

.pos-lineas { list-style: none; margin: 0; padding: 0; }

.pos-linea {
  display: grid;
  grid-template-columns: 40px minmax(0, 1fr) auto;
  grid-template-areas:
    "img  txt   quitar"
    "img  cant  total";
  gap: var(--s2) var(--s3);
  align-items: center;
  padding: var(--s3) var(--s4);
  border-bottom: 1px solid var(--border);
}
.pos-linea:last-child { border-bottom: none; }
.pos-linea .prod-img { grid-area: img; align-self: start; }
.pos-linea-txt { grid-area: txt; min-width: 0; }
.pos-linea-nom {
  display: block;
  font-weight: 600;
  font-size: var(--fs-sm);
  line-height: 1.3;
}
.pos-linea-sub { display: block; font-size: var(--fs-xs); color: var(--text-3); }

.pos-cant { grid-area: cant; display: inline-flex; align-items: center; gap: var(--s0); }
.pos-cant-btn {
  width: 30px;
  height: 30px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  color: var(--text);
  font-size: var(--fs-base);
  font-weight: 700;
  cursor: pointer;
  transition: background var(--t-rapida) var(--ease);
}
.pos-cant-btn:hover:not(:disabled) { background: var(--border); }
.pos-cant-btn:disabled { opacity: .4; cursor: not-allowed; }
.pos-linea .input-cantidad {
  width: 46px !important;
  min-height: 30px;
  text-align: center;
  padding: 0 var(--s1);
}

.pos-linea-sub-total {
  grid-area: total;
  justify-self: end;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.pos-quitar {
  grid-area: quitar;
  justify-self: end;
  width: 30px;
  height: 30px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: none;
  border: 1px solid transparent;
  border-radius: var(--r-sm);
  color: var(--text-3);
  cursor: pointer;
  transition: background var(--t-rapida) var(--ease),
              color var(--t-rapida) var(--ease);
}
.pos-quitar:hover,
.pos-quitar:focus-visible {
  background: var(--error-bg);
  color: var(--error);
}

.pos-vacio {
  padding: var(--s7) var(--s4);
  text-align: center;
}

/* ---------- Pie: totales y cobro ---------- */
.pos-pie {
  padding: var(--s4);
  border-top: 1px solid var(--border);
}

.pos-linea-total {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s3);
  font-size: var(--fs-sm);
  color: var(--text-2);
  margin-bottom: var(--s2);
}
.pos-linea-total strong { color: var(--text); font-variant-numeric: tabular-nums; }

.pos-descuento-abrir {
  display: inline-flex;
  align-items: center;
  gap: var(--s1);
  padding: var(--s1) 0;
  margin-bottom: var(--s2);
  background: none;
  border: 0;
  color: var(--brand-600);
  font-family: inherit;
  font-size: var(--fs-sm);
  font-weight: 600;
  cursor: pointer;
}
.pos-descuento-abrir:hover { text-decoration: underline; }
.pos-descuento-abrir[aria-expanded="true"] { display: none; }

.pos-total {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s3);
  padding-top: var(--s3);
  margin-bottom: var(--s4);
  border-top: 1px solid var(--border);
}
.pos-total span:first-child {
  font-size: var(--fs-sm);
  font-weight: 700;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--text-2);
}
.pos-total span:last-child {
  font-size: 30px;
  font-weight: 800;
  letter-spacing: -.02em;
  font-variant-numeric: tabular-nums;
  color: var(--text);
}

/* ---------- Forma de pago ---------- */
/* Cuatro pildoras y no un desplegable: en un cobro se elige de un
   toque, no abriendo una lista. */
.pos-pago {
  border: 0;
  padding: 0;
  margin: 0 0 var(--s3);
}
.pos-pago legend {
  padding: 0;
  margin-bottom: var(--s2);
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--text-3);
}
.pos-pago {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s2);
}
.pos-pago legend { grid-column: 1 / -1; }

.pos-pago-op { position: relative; display: block; }
.pos-pago-op input {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
}
.pos-pago-op span {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 42px;
  padding: var(--s2);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--text-2);
  cursor: pointer;
  transition: border-color var(--t-rapida) var(--ease),
              background var(--t-rapida) var(--ease),
              color var(--t-rapida) var(--ease);
}
.pos-pago-op:hover span { border-color: var(--border-2); }
.pos-pago-op input:checked + span {
  border-color: var(--brand-600);
  background: var(--brand-50, var(--surface-2));
  color: var(--brand-600);
}
.pos-pago-op input:focus-visible + span {
  outline: var(--foco);
  outline-offset: 2px;
}

.pos-cliente { margin-bottom: var(--s3); }
.pos-cliente summary {
  font-size: var(--fs-sm);
  color: var(--text-2);
  cursor: pointer;
  padding: var(--s2) 0;
}
.pos-cliente .campo { margin-bottom: var(--s2); }

/* La accion dominante: ancho completo, alta, y sola en su renglon. */
.pos-cobrar {
  width: 100%;
  min-height: 54px;
  font-size: var(--fs-lg);
}
.pos-cobrar:not(:disabled) { box-shadow: var(--sh-cobro); }

/* ---------- Barra de cobro de telefono ---------- */
/* En un telefono el carrito puede tener veinte lineas y el boton queda
   a tres pantallas de distancia. Esta barra lleva el total y el cobro
   siempre encima de la barra de navegacion. */
/* En TODOS los anchos, no solo en telefono: la ensena pos.js cuando el
   boton "Cobrar" del carrito no esta a la vista. Medido a 1440x900 con
   cuatro lineas, ese boton caia en el pixel 1034 de una pantalla de
   900: fuera. */
.pos-barra { display: none; }

.pos-barra:not([hidden]) {
  display: flex;
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 40;
  align-items: center;
  gap: var(--s3);
  padding: var(--s3) var(--s4);
  background: var(--surface);
  border-top: 1px solid var(--border);
  box-shadow: var(--sh-arriba);
}
.pos-barra-datos {
  display: grid;
  gap: 1px;
  min-width: 0;
  padding: var(--s1) var(--s2);
  background: none;
  border: 1px solid transparent;
  border-radius: var(--r-sm);
  text-align: left;
  font-family: inherit;
  color: inherit;
  cursor: pointer;
}
.pos-barra-datos:hover { border-color: var(--border); }
.pos-barra-datos:focus-visible { outline: var(--foco); outline-offset: 2px; }
.pos-barra-art { font-size: var(--fs-xs); color: var(--text-3); }
.pos-barra-total {
  font-size: var(--fs-xl);
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  line-height: 1.1;
}
.pos-barra .btn { min-height: 48px; }

/* Sitio para que la barra no tape la ultima linea del carrito. */
.pos:has(~ .pos-barra:not([hidden])) { padding-bottom: 84px; }

/* En escritorio la barra respeta el menu lateral y el boton no se
   estira de lado a lado: seria una franja azul de metro y medio.

   G1: sube de 1024 a 1280 CON el shell, y no por gusto. Es la unica
   regla de todo el sistema fuera del shell que lee --ancho-lateral
   (comprobado con grep). Si se quedaba en 1024, entre 1024 y 1279 la
   barra empezaria en 244px mientras el riel acaba en 64: 180px de
   hueco azul. Entre 1024 y 1279 la barra se comporta como ya lo hacia
   entre 768 y 1023, que es el mismo regimen de riel. */
@media (min-width: 1280px) {
  .pos-barra:not([hidden]) {
    /* El lateral tiene su propio ancho y ademas se puede colapsar:
       la barra empieza donde acaba el menu, no debajo de el. */
    left: var(--ancho-lateral);
    justify-content: space-between;
    padding: var(--s3) var(--s5);
  }
  /* width auto: hay una regla base que deja los botones a ancho
     completo y aqui salia una franja azul de metro y medio. */
  .pos-barra .btn { flex: 0 0 auto; min-width: 240px; }
  .lateral-colapsada .pos-barra:not([hidden]) { left: var(--ancho-lateral-min); }
}

/* G1: era max-width:1023. Sube a 1279 para seguir siendo el
   complemento exacto del bloque de arriba y no dejar la franja
   1024-1279 sin ninguna de las dos reglas. Aqui solo queda el REPARTO
   DE LOS BOTONES, que es lo que de verdad complementa al bloque de
   1280: por debajo se estiran, por encima miden lo suyo. */
@media (max-width: 1279px) {
  .pos-barra .btn { flex: 1 1 auto; }
}

/* LA SEPARACION DE ABAJO NO ES DEL MISMO ESCALON, y esto lo destapo la
   regresion de G1. La barra se levanta 58px para no taparse con la
   barra de navegacion inferior; esa barra existe SOLO por debajo de
   768. Estaba escrita contra 1023 -y G1 la habria arrastrado hasta
   1279-, asi que entre 768 y 1279 la barra de cobro flotaba 58px por
   encima del borde sin nada debajo que esquivar.

   MEDIDO, con la barra visible y .barra-inf en display:none:
     390   hueco 58px, con barra inferior  -> correcto
     900   hueco 58px, sin barra inferior  -> vacio (ya pasaba antes de G1)
     1024  hueco 58px, sin barra inferior  -> vacio (lo habria traido G1)
     1180  hueco 58px, sin barra inferior  -> vacio (lo habria traido G1)
     1280  hueco 0                         -> correcto

   El numero correcto es 767, el mismo con el que aparece y desaparece
   .barra-inf. Es exactamente el trabajo de G1: que las piezas del
   shell no discutan sobre donde esta la frontera. */
@media (max-width: 767px) {
  /* Encima de la barra de navegacion del telefono. */
  .pos-barra:not([hidden]) {
    bottom: var(--barra-inf-total);
  }
}

@media (max-width: 767px) {
  /* En telefono el catalogo es de dos columnas: mas estrecho no se
     lee, y de una sola se ven tres productos por pantalla. */
  .pos-rejilla { grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); }

  /* Area tactil: el catalogo y el carrito se usan con el pulgar.
     MEDIDO: los botones de cantidad y el de quitar se quedaban en
     38px, por debajo del minimo comodo. */
  .pos-item { min-height: 104px; }
  .pos-cant-btn { width: 44px; height: 44px; }
  .pos-linea .input-cantidad { width: 54px !important; min-height: 44px; }
  .pos-quitar { width: 44px; height: 44px; }

  /* El boton del pie sobra: ya esta en la barra fija de abajo. */
  .pos-cobrar { display: none; }

  .pos-total span:last-child { font-size: var(--fs-2xl); }
}

/* El aviso de caja, dentro del punto de venta y en telefono, en UNA
   fila. MEDIDO a 390x844: apilado ocupaba 153px, el 18% de la
   pantalla, y el primer producto no aparecia hasta el pixel 401.
   Aqui se viene a vender; el estado de la caja es contexto.
   Va al final del archivo a proposito: la regla generica que apila
   este bloque en telefono esta antes, y a igual especificidad
   ganaria ella. */
@media (max-width: 767px) {
  .contenido .caja-estado:has(~ .pos) {
    flex-direction: row;
    align-items: center;
    text-align: left;
    gap: var(--s3);
    padding: var(--s3);
    margin-bottom: var(--s4);
  }
  .caja-estado:has(~ .pos) .caja-estado-icono { width: 34px; height: 34px; }
  .caja-estado:has(~ .pos) .caja-estado-icono svg { width: 18px; height: 18px; }
  .caja-estado:has(~ .pos) .caja-estado-txt strong { font-size: var(--fs-sm); }
  .caja-estado:has(~ .pos) .caja-estado-txt span { font-size: var(--fs-xs); }
  .caja-estado:has(~ .pos) .caja-estado-accion { width: auto; }
  .caja-estado:has(~ .pos) .caja-estado-accion .btn {
    width: auto;
    padding: var(--s2) var(--s3);
    font-size: var(--fs-sm);
    white-space: nowrap;
  }
}

@media (max-width: 379px) {
  .pos-rejilla { grid-template-columns: repeat(auto-fill, minmax(128px, 1fr)); }
  .pos-pago { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* =====================================================================
   36. CORTE DE CAJA
   ---------------------------------------------------------------------
   Medido antes de tocar nada, a 1440px:
     - Caja cerrada: el formulario de apertura medi­a 440px y dejaba
       717px de VACIO a su derecha, el 61% del ancho de contenido.
     - Caja abierta: CUATRO metricas identicas de 274x110 con la misma
       letra de 26px, asi que el efectivo esperado -la cifra de la que
       va todo el corte- pesaba igual que el fondo inicial.
     - "$1,500.00" aparecia CUATRO veces en la misma pantalla.
     - "Cerrar caja y hacer el corte" era AZUL PRIMARIO, del mismo peso
       que "Registrar movimiento" y que "Abrir caja".
     - El estado era una banda de 47px, no un estado.

   Orden de la pantalla: estado, dinero, acciones, movimientos, corte.
   ===================================================================== */

/* ---------- Caja cerrada: apertura ---------- */
.caja-apertura {
  display: grid;
  gap: var(--s4);
  margin-bottom: var(--s5);
}
@media (min-width: 900px) {
  /* El formulario manda; la referencia acompana. */
  .caja-apertura { grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr); }
}

.caja-pasos {
  margin: 0;
  padding-left: var(--s5);
  display: grid;
  gap: var(--s3);
  font-size: var(--fs-sm);
  color: var(--text-2);
}
.caja-pasos strong { color: var(--text); }

.caja-ultimo {
  margin-top: var(--s5);
  padding-top: var(--s4);
  border-top: 1px solid var(--border);
}
.caja-ultimo-rot {
  margin: 0 0 2px;
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--text-3);
}
.caja-ultimo-fecha { margin: 0 0 var(--s3); font-size: var(--fs-sm); color: var(--text-2); }

/* ---------- Campo de dinero ---------- */
/* El importe es la cifra de la pantalla: se escribe grande y con el
   signo fuera del campo, como en una caja registradora. */
.campo-dinero label { display: block; margin-bottom: var(--s2); }

.dinero-caja {
  display: flex;
  align-items: center;
  gap: var(--s2);
  padding: var(--s2) var(--s4);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  transition: border-color var(--t-rapida) var(--ease),
              box-shadow var(--t-rapida) var(--ease);
}
.dinero-caja:focus-within {
  border-color: var(--brand-600);
  box-shadow: var(--ring);
}
.dinero-signo {
  font-size: var(--fs-2xl);
  font-weight: 700;
  color: var(--text-3);
  line-height: 1;
}
.dinero-caja input {
  flex: 1 1 auto;
  min-width: 0;
  width: 100%;
  border: 0;
  background: none;
  padding: var(--s2) 0;
  font-family: inherit;
  font-size: 30px;
  font-weight: 800;
  letter-spacing: -.02em;
  font-variant-numeric: tabular-nums;
  color: var(--text);
}
.dinero-caja input:focus { outline: none; }
.dinero-caja-sm .dinero-signo { font-size: var(--fs-xl); }
.dinero-caja-sm input { font-size: 22px; }

/* ---------- Caja abierta: dos columnas ---------- */
.caja-cols {
  display: grid;
  gap: var(--s4);
  align-items: start;
  margin-bottom: var(--s5);
}
@media (min-width: 1024px) {
  .caja-cols { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* ---------- Desglose ---------- */
.caja-desglose { list-style: none; margin: 0; padding: 0; }
.caja-desglose li {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s3);
  padding: var(--s3) 0;
  border-bottom: 1px solid var(--border);
  font-size: var(--fs-sm);
  color: var(--text-2);
}
.caja-desglose li:last-child { border-bottom: none; }
.caja-desglose strong {
  color: var(--text);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.caja-desglose .es-suma  { color: var(--ok); }
.caja-desglose .es-resta { color: var(--error); }

.caja-desglose .es-total {
  margin-top: var(--s2);
  padding-top: var(--s3);
  border-top: 2px solid var(--border);
  border-bottom: none;
  font-weight: 700;
  color: var(--text);
}
.caja-desglose .es-total strong { font-size: var(--fs-lg); }

.caja-desglose-num {
  display: inline-block;
  min-width: 20px;
  padding: 0 5px;
  margin-left: var(--s1);
  background: var(--surface-2);
  border-radius: var(--r-full);
  font-size: var(--fs-xs);
  text-align: center;
  color: var(--text-3);
}

.caja-otros {
  margin-top: var(--s5);
  padding-top: var(--s4);
  border-top: 1px solid var(--border);
}
.caja-otros-rot {
  margin: 0 0 2px;
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--text-3);
}
.caja-otros-nota { margin: 0 0 var(--s2); font-size: var(--fs-xs); color: var(--text-3); }

/* ---------- Entrada o salida ---------- */
/* Dos pastillas y no un desplegable: son dos opciones y la diferencia
   entre ellas es el signo del dinero. */
.caja-tipo {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s2);
  border: 0;
  padding: 0;
  margin: 0 0 var(--s3);
}
.caja-tipo-op { position: relative; display: block; }
.caja-tipo-op input { position: absolute; opacity: 0; width: 0; height: 0; }
.caja-tipo-op span {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--s2);
  min-height: 44px;
  padding: var(--s2);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--text-2);
  cursor: pointer;
  transition: border-color var(--t-rapida) var(--ease),
              background var(--t-rapida) var(--ease),
              color var(--t-rapida) var(--ease);
}
.caja-tipo-op:hover span { border-color: var(--border-2); }
.caja-tipo-op input:focus-visible + span {
  outline: var(--foco);
  outline-offset: 2px;
}
/* Verde entra, rojo sale: el mismo codigo que el resto del sistema. */
.caja-tipo-op:first-child input:checked + span {
  border-color: var(--ok);
  background: var(--ok-bg);
  color: var(--ok);
}
.caja-tipo-op:last-child input:checked + span {
  border-color: var(--error);
  background: var(--error-bg);
  color: var(--error);
}

/* ---------- Movimientos del turno ---------- */
.caja-mov-lista {
  margin-top: var(--s5);
  padding-top: var(--s4);
  border-top: 1px solid var(--border);
}
.caja-mov-rot {
  display: flex;
  align-items: center;
  gap: var(--s2);
  margin: 0 0 var(--s3);
  font-size: var(--fs-sm);
  font-weight: 700;
  color: var(--text);
}
.caja-mov-num {
  min-width: 20px;
  padding: 0 6px;
  background: var(--surface-2);
  border-radius: var(--r-full);
  font-size: var(--fs-xs);
  font-weight: 600;
  color: var(--text-3);
  text-align: center;
}
.caja-mov-vacio { margin: 0; font-size: var(--fs-sm); color: var(--text-3); }

.caja-movs { list-style: none; margin: 0; padding: 0; }
.caja-mov {
  display: grid;
  grid-template-columns: 26px minmax(0, 1fr) auto;
  gap: var(--s3);
  align-items: center;
  padding: var(--s3) 0;
  border-bottom: 1px solid var(--border);
}
.caja-mov:last-child { border-bottom: none; }
.caja-mov-icono {
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  border-radius: var(--r-full);
}
.caja-mov.es-ingreso .caja-mov-icono { background: var(--ok-bg); color: var(--ok); }
.caja-mov.es-retiro  .caja-mov-icono { background: var(--error-bg); color: var(--error); }

.caja-mov-txt { min-width: 0; display: grid; gap: 1px; }
.caja-mov-motivo {
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.caja-mov-meta { font-size: var(--fs-xs); color: var(--text-3); }
.caja-mov-monto {
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.caja-mov.es-ingreso .caja-mov-monto { color: var(--ok); }
.caja-mov.es-retiro  .caja-mov-monto { color: var(--error); }

/* ---------- Cierre ---------- */
/* Panel aparte y con su propio tono: es la accion irreversible del
   modulo, no una mas. */
.caja-cierre { border-color: var(--error-bg); }
.caja-cierre .panel-cab { border-bottom-color: var(--error-bg); }

.caja-cierre-campos {
  display: grid;
  gap: var(--s4);
  margin-bottom: var(--s4);
}
@media (min-width: 768px) {
  .caja-cierre-campos { grid-template-columns: minmax(0, 320px) minmax(0, 1fr); align-items: end; }
}

.caja-cierre-btn { width: 100%; }
@media (min-width: 768px) {
  /* No de lado a lado: una accion irreversible no necesita ser una
     franja de metro y medio, necesita estar clara. */
  .caja-cierre-btn { width: auto; min-width: 300px; }
}

/* Adelanto de la diferencia */
.caja-dif {
  display: grid;
  gap: var(--s0);
  padding: var(--s3) var(--s4);
  margin-bottom: var(--s4);
  border-radius: var(--r-md);
  border-left: 3px solid transparent;
  font-size: var(--fs-sm);
}
.caja-dif strong { font-size: var(--fs-base); }
.caja-dif span { color: inherit; opacity: .85; }
.caja-dif.es-ok    { background: var(--ok-bg);    border-left-color: var(--ok);    color: var(--ok); }
.caja-dif.es-sobra { background: var(--warn-bg);  border-left-color: var(--warn);  color: var(--warn); }
.caja-dif.es-falta { background: var(--error-bg); border-left-color: var(--error); color: var(--error); }

/* ---------- Historial de cortes ---------- */
.caja-historial.sin-filas thead { display: none; }

.caja-historial .ch-fecha {
  display: block;
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--text);
}
.caja-historial .ch-sub {
  display: block;
  font-size: var(--fs-xs);
  color: var(--text-3);
}
.caja-historial .ch-num {
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

@media (min-width: 768px) {
  .caja-historial .ch-turno  { width: 28%; }
  .caja-historial .ch-ventas { width: 14%; }
  .caja-historial .ch-acc    { width: 1%; white-space: nowrap; }
}

/* En telefono el historial se convierte en fichas: seis columnas de
   dinero no se leen en 360px. */
@media (max-width: 767px) {
  .caja-historial thead { display: none; }
  .caja-historial table,
  .caja-historial tbody,
  .caja-historial tr,
  .caja-historial td { display: block; width: auto; }

  .caja-historial tbody tr {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    gap: var(--s2) var(--s3);
    padding: var(--s3) var(--s4);
    border-bottom: 1px solid var(--border);
  }
  .caja-historial .ch-turno { grid-column: 1; }
  .caja-historial .ch-dif   { grid-column: 2; grid-row: 1; justify-self: end; }
  .caja-historial .ch-ventas,
  .caja-historial .ch-num {
    display: inline-flex;
    align-items: baseline;
    gap: var(--s2);
    font-size: var(--fs-xs);
    color: var(--text-3);
  }
  /* El rotulo lo pone data-label, que ya usa el resto del sistema. */
  .caja-historial .ch-num::before {
    content: attr(data-label);
    color: var(--text-3);
  }
  .caja-historial .ch-acc { grid-column: 1 / -1; }
  .caja-historial .ch-acc .btn { width: 100%; min-height: 44px; }

  /* ---------- Afinado de la ficha (fase de Caja movil) ----------
     La composicion ya era correcta; lo que faltaba era JERARQUIA dentro
     de la ficha. Los cuatro datos -fecha, ventas, esperado y contado-
     pesaban lo mismo, asi que la ficha se leia entera para encontrar la
     fecha, que es por lo que se busca un corte.

     Y la accion se pegaba al ultimo dato sin nada que la separase. */
  /* EL FALLO QUE ESTABA AQUI DESDE EL PRINCIPIO.

     Este bloque convierte la tabla en rejilla, pero NO apagaba las
     etiquetas que la ficha apilada generica (seccion 14) pinta con
     `td::before { content: attr(data-label) }`. Resultado medido a
     390px: cada celda salia con su nombre de columna delante y el valor
     empujado a la derecha por el `text-align: right` de la misma regla
     generica. La ficha se leia "TURNO — 05/09/26 20:32", "DIFERENCIA —
     Abierta", "VENTAS — 6", una etiqueta por renglon.

     Con la rejilla puesta esas etiquetas ya no hacen falta: la posicion
     dice lo que es cada cosa. Las unicas que se quedan son las de los
     importes -esperado y contado-, que sin nombre no se distinguen, y
     esas ya las pinta la regla de .ch-num que hay unas lineas mas
     arriba, con mas especificidad que esta.

     Se apagan UNA A UNA y no con un selector de td general: apagarlas
     todas se lleva por delante tambien las de esperado y contado, y
     entonces la ficha ensena "6   $312.00   —   —", cuatro numeros
     sueltos sin decir de que son. Probado: es peor que el problema
     original. */
  .caja-historial .ch-turno::before,
  .caja-historial .ch-dif::before,
  .caja-historial .ch-ventas::before,
  .caja-historial .ch-acc::before { display: none; }

  .caja-historial .ch-turno,
  .caja-historial .ch-dif,
  .caja-historial .ch-ventas,
  .caja-historial .ch-acc { text-align: left; }

  .caja-historial tbody tr {
    padding: var(--s4);
    border-bottom: 0;
    border-radius: var(--r-lg);
  }
  .caja-historial .ch-turno .ch-fecha {
    font-size: var(--fs-base);
    font-weight: var(--peso-fuerte);
    color: var(--text);
  }
  /* Al apagar la etiqueta de columna, "6" se quedaba solo y no decia de
     que. El nombre del dato se le pega detras: "6 ventas · $312.00". */
  .caja-historial .ch-ventas .ch-fecha { color: var(--text); font-weight: var(--peso-fuerte); }
  .caja-historial .ch-ventas .ch-fecha::after {
    content: " ventas";
    font-weight: 400;
    color: var(--text-2);
  }
  .caja-historial .ch-ventas .ch-sub::before { content: "· "; color: var(--text-3); }
  .caja-historial .ch-acc {
    margin-top: var(--s1);
    padding-top: var(--s3);
    border-top: 1px solid var(--border-sutil);
  }
}

/* ---------- Telefono ---------- */
@media (max-width: 767px) {
  .dinero-caja input { font-size: var(--fs-2xl); }
  .dinero-signo { font-size: 22px; }

  /* Area tactil comoda en los campos y acciones de dinero. */
  .caja-tipo-op span { min-height: 46px; }
  .caja-cierre-btn { min-height: 50px; }
}

/* ---------------------------------------------------------------------
   Detalle de un corte
   ---------------------------------------------------------------------
   Antes todo el corte cabia en una tarjeta de lectura estrecha pegada a
   la izquierda, y el resultado -si falto o sobro dinero- era una fila
   mas de la lista, del mismo tamano que "Fondo inicial". Es LA cifra
   por la que se abre esta pantalla.
   --------------------------------------------------------------------- */

.corte-cab {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--s4);
  flex-wrap: wrap;
  margin-bottom: var(--s4);
}
.corte-cab-txt { min-width: 0; display: grid; gap: var(--s2); justify-items: start; }
.corte-cab-fechas { margin: 0; font-size: var(--fs-sm); color: var(--text-2); }
.corte-cab-fechas strong { color: var(--text); }
.corte-cab-nota {
  margin: var(--s3) 0 0;
  font-size: var(--fs-sm);
  color: var(--text-3);
  font-style: italic;
}
.corte-cab-acciones { display: flex; gap: var(--s2); flex-wrap: wrap; }

/* La fila del resultado dentro del desglose lleva su color. */
.caja-desglose .es-total.es-ok strong     { color: var(--ok); }
.caja-desglose .es-total.es-warn strong { color: var(--warn); }
.caja-desglose .es-total.es-error strong  { color: var(--error); }

.corte-folio { font-weight: 700; font-variant-numeric: tabular-nums; }
.corte-ventas.sin-filas thead { display: none; }

@media (max-width: 767px) {
  .corte-cab-acciones { width: 100%; }
  .corte-cab-acciones .btn { flex: 1 1 auto; min-height: 44px; }
}

/* Al imprimir el corte sobra todo lo que no es el corte. */
@media print {
  .lateral, .topbar, .barra-inf, .corte-cab-acciones,
  .toast-zona, .seccion-acciones, .pagina-cab-acciones { display: none !important; }
  .app, .app-cuerpo, .contenido { display: block !important; padding: 0 !important; }
  .panel { break-inside: avoid; border-color: #ccc !important; box-shadow: none !important; }
  .caja-cols { grid-template-columns: 1fr 1fr !important; }
}

/* =====================================================================
   37. INVENTARIO
   ---------------------------------------------------------------------
   Medido antes de tocar nada, a 1440x900:
     - Alertas: CUATRO tarjetas identicas de 278x110. "Necesitan
       atencion" -lo unico accionable- pesaba igual que "valor a costo".
     - Los productos con problema eran una tabla de SIETE columnas.
     - Movimientos: otras CUATRO tarjetas identicas de 278x88 y una
       tabla de OCHO columnas para contar una sola cosa.
     - Ni una imagen de producto en todo el modulo.
   ===================================================================== */

/* ---------- Marca de tipo de movimiento ---------- */
/* Verde entra, rojo sale, azul se corrige. El mismo codigo de color que
   ya usan el catalogo y la caja: no se inventa uno nuevo. */
.mov-marca {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  padding: 3px var(--s2) 3px 3px;
  border-radius: var(--r-full);
  font-size: var(--fs-xs);
  font-weight: 700;
  white-space: nowrap;
}
.mov-marca-icono {
  display: grid;
  place-items: center;
  width: 20px;
  height: 20px;
  border-radius: var(--r-full);
  background: var(--marca-realce);
}
.mov-marca-ok    { background: var(--ok-bg);    color: var(--ok); }
.mov-marca-error { background: var(--error-bg); color: var(--error); }
.mov-marca-info  { background: var(--info-bg);  color: var(--info); }

/* ---------- Lista de productos que requieren atencion ---------- */
/* Una ficha por producto y no una fila de hoja de calculo: foto, cuanto
   queda respecto al minimo, y que hacer. */
.inv-lista {
  list-style: none;
  margin: 0 0 var(--s5);
  padding: 0;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-1);
  overflow: hidden;
}

.inv-item {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 200px auto;
  gap: var(--s3) var(--s4);
  align-items: center;
  padding: var(--s4) var(--s5);
  border-bottom: 1px solid var(--border);
}
.inv-item:last-child { border-bottom: none; }

.inv-item-stock { display: grid; gap: var(--s1); }
.inv-item-cifras { display: flex; align-items: baseline; gap: var(--s2); flex-wrap: wrap; }
.inv-item-num {
  font-size: var(--fs-lg);
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  line-height: 1;
}
.inv-item-unidad { font-size: var(--fs-xs); color: var(--text-3); }
.inv-item-minimo { font-size: var(--fs-xs); color: var(--text-3); }

.inv-item-acciones { display: flex; align-items: center; gap: var(--s2); }

/* La lista de aviso temprano pesa menos que la de alertas abiertas. */
.inv-lista-sec .inv-item { padding-top: var(--s3); padding-bottom: var(--s3); }
.inv-lista-sec .inv-item-num { font-size: var(--fs-base); }


.inv-pie {
  margin: var(--s5) 0 0;
  font-size: var(--fs-sm);
  color: var(--text-3);
}
.inv-pie-btn {
  display: inline-block;
  min-width: 20px;
  padding: 0 5px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  font-weight: 700;
  color: var(--text-2);
  text-align: center;
}

/* ---------- Historial de movimientos ---------- */
/* Ocho columnas para contar una sola cosa. Ahora cinco, y "antes" y
   "despues" se juntan en una: lo que importa es el salto, no los dos
   numeros por separado. */
.mov-lista .m-mov   { width: 15%; }
.mov-lista .m-stock { width: 16%; }
.mov-lista .m-quien { width: 18%; }

.mov-cant {
  display: block;
  font-size: var(--fs-base);
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  margin-top: var(--s0);
}
.mov-cant.es-mas   { color: var(--ok); }
.mov-cant.es-menos { color: var(--error); }

/* El salto de stock: de cuanto a cuanto. */
.mov-salto {
  display: inline-flex;
  align-items: center;
  gap: var(--s1);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.mov-salto-antes { color: var(--text-3); }
.mov-salto-flecha { color: var(--text-3); }
.mov-salto-despues { font-weight: 700; color: var(--text); }

.mov-motivo {
  display: block;
  font-size: var(--fs-sm);
  color: var(--text-2);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.mov-origen {
  display: inline-block;
  margin-top: var(--s0);
  padding: 1px var(--s2);
  background: var(--surface-2);
  border-radius: var(--r-sm);
  font-size: var(--fs-xs);
  color: var(--text-3);
}
.mov-quien { display: block; font-size: var(--fs-sm); color: var(--text); }
.mov-cuando { display: block; font-size: var(--fs-xs); color: var(--text-3); }

/* ---------- Barra de filtros ---------- */
/* G5: composicion compartida arriba. */
.inv-filtros-busca { flex: 1 1 240px; min-width: 0; }
/* G5: median 42px; heredan los 44 del sistema. */
.inv-filtros-acciones { display: flex; gap: var(--s2); align-items: center; }

/* Grupo plegable de filtros. El boton lo crea app.js, asi que sin
   JavaScript no existe y se ve todo. */
.filtros-plegar-btn { display: none; }
.filtros-plegar-btn.activo { border-color: var(--brand-600); color: var(--brand-600); }

/* =====================================================================
   Telefono
   ===================================================================== */
@media (max-width: 767px) {

  /* La ficha de producto se apila: nombre arriba, stock y accion
     debajo. Comprimir tres columnas en 360px no se lee. */
  /* Tres renglones y no dos. Con "stock" y "acciones" lado a lado,
     MEDIDO a 360px: los botones se llevaban 212 de 334 y al stock le
     quedaban 74, con la barra de progreso reducida a un guion. El
     stock es el dato por el que el producto esta en esta lista. */
  .inv-item {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas:
      "prod"
      "stock"
      "acciones";
    padding: var(--s4);
  }
  .inv-item > .prod-linea    { grid-area: prod; }
  .inv-item-stock            { grid-area: stock; }
  .inv-item-acciones {
    grid-area: acciones;
    align-items: stretch;
  }
  .inv-item-acciones .btn,
  .inv-item-acciones form { flex: 1 1 auto; }
  .inv-item-acciones form .btn { width: 100%; }
  .inv-item-acciones .btn    { min-height: 44px; }

  .inv-filtros { flex-direction: column; align-items: stretch; }
  /* flex: none. En columna, el 'flex: 1 1 240px' pensado para la
     fila crece por el eje VERTICAL: el campo de buscar se estiraba
     y dejaba 200px de blanco antes del siguiente filtro. */
  .inv-filtros .campo,
  .inv-filtros-busca { width: 100%; flex: none; }
  .inv-filtros-acciones .btn { flex: 1 1 auto; min-height: 44px; }


  .inv-filtros-sec { flex-direction: column; align-items: stretch; }
  .inv-filtros-sec .campo { width: 100%; flex: none; }
  .filtros-plegar-btn:not([hidden]) {
    display: inline-flex;
    width: 100%;
    justify-content: center;
    gap: var(--s2);
    min-height: 44px;
  }
}

/* =====================================================================
   38. REPORTES
   ---------------------------------------------------------------------
   Medido antes de tocar nada, a 1440x900:
     - DOCE tarjetas identicas de 26px: ocho de ventas y cuatro de
       inventario. Ninguna mandaba, asi que ninguna se leia.
     - Las ocho primeras caian 7 + 1 y "Canceladas" se quedaba SOLA en
       su fila: la tarjeta huerfana de manual.
     - Las pestanas de periodo, el rango de fechas y "Exportar CSV" eran
       tres bloques sueltos sobre el fondo.
     - "Mas vendidos" tenia barras y "menos vendidos" no: dos formas de
       pintar lo mismo, una al lado de la otra.
     - Ni una imagen de producto en los rankings.
   ===================================================================== */

/* ---------- Cabecera del informe ---------- */
.rep-cab { margin-bottom: var(--s5); }

.rep-cab-sup {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s3);
  flex-wrap: wrap;
}
.rep-cab-sup .tabs { margin-bottom: 0; }

.rep-cab-pie {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s3);
  flex-wrap: wrap;
  margin-top: var(--s3);
}
.rep-periodo {
  margin: 0;
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--text-2);
}

.rep-rango-abrir {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  padding: var(--s1) 0;
  background: none;
  border: 0;
  color: var(--brand-600);
  font-family: inherit;
  font-size: var(--fs-sm);
  font-weight: 600;
  cursor: pointer;
}
.rep-rango-abrir:hover { text-decoration: underline; }
.rep-rango-abrir[aria-expanded="true"] { display: none; }

/* G5: composicion compartida arriba; lo propio es la linea que la
   separa de las pestanas de periodo. */
.rep-rango {
  margin-top: var(--s3);
  padding-top: var(--s4);
  border-top: 1px solid var(--border);
}
.rep-rango .campo { margin: 0; }

/* ---------- Dos columnas de rankings ---------- */
.rep-cols {
  display: grid;
  gap: var(--s4);
  align-items: start;
  margin-bottom: var(--s5);
}
@media (min-width: 1024px) {
  .rep-cols { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* ---------- Tres columnas de desgloses ---------- */
/* auto-fit con un minimo real: con tres paneles y poco ancho, dos
   arriba y uno solo abajo se ve como un descuido. Aqui son tres o uno,
   nunca dos y medio. */
.rep-cols-3 {
  display: grid;
  gap: var(--s4);
  align-items: start;
  margin-bottom: var(--s5);
}
@media (min-width: 900px) {
  .rep-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

/* ---------- Ranking de productos ---------- */
.rep-ranking { list-style: none; margin: 0; padding: 0; }

.rep-item {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-areas:
    "prod  dato"
    "barra barra";
  gap: var(--s2) var(--s3);
  align-items: center;
  padding: var(--s3) var(--s5);
  border-bottom: 1px solid var(--border);
}
.rep-item:last-child { border-bottom: none; }
.rep-item > .prod-linea { grid-area: prod; min-width: 0; }

.rep-item-dato {
  grid-area: dato;
  display: flex;
  align-items: baseline;
  gap: 3px;
  white-space: nowrap;
}
.rep-item-num {
  font-size: var(--fs-lg);
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  line-height: 1;
}
.rep-item-num.es-cero { color: var(--error); }
.rep-item-unidad { font-size: var(--fs-xs); color: var(--text-3); }

.rep-item-barra { grid-area: barra; display: grid; gap: 3px; }
.rep-item-pie { font-size: var(--fs-xs); color: var(--text-3); }

/* El de "no se mueve" pesa menos: no lleva barra. */
.rep-item-sec { padding-top: var(--s2); padding-bottom: var(--s2); }
.rep-item-sec .rep-item-num { font-size: var(--fs-base); }

.rep-vacio { padding: var(--s5) var(--s4); }
.rep-sin-datos { margin: 0; font-size: var(--fs-sm); color: var(--text-3); }

/* =====================================================================
   Telefono
   ===================================================================== */
@media (max-width: 767px) {
  .rep-cab-sup { flex-direction: column; align-items: stretch; }
  .rep-cab-sup .btn { width: 100%; min-height: 44px; }

  .rep-rango { flex-direction: column; align-items: stretch; }
  .rep-rango .campo { width: 100%; }
  .rep-rango .btn { width: 100%; min-height: 44px; }

  .rep-item { padding: var(--s3) var(--s4); }
}

/* =====================================================================
   39. VENTAS: HISTORIAL Y DETALLE
   ---------------------------------------------------------------------
   Auditoria transversal: el estado de la venta se pintaba de TRES
   formas distintas en tres pantallas, y dos usaban vocabulario de stock
   ('chip-agotado') para una venta cancelada. Ahora lo resuelve
   ui_chip_venta() una sola vez.

   Historial: NUEVE columnas -folio, fecha, partidas, cliente, pago,
   total, estado, vendedor y acciones- para contar una venta. Ahora
   cinco: la venta con su fecha y quien la hizo, el cliente con su forma
   de pago, el total, el estado y las acciones.

   Detalle: todo cabia en una tarjeta de lectura estrecha pegada a la
   izquierda, y el TOTAL era una linea mas de la lista.
   ===================================================================== */

/* ---------- Historial ---------- */
.tabla-ventas.sin-filas thead { display: none; }

.v-folio {
  font-size: var(--fs-base);
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  color: var(--text);
}
.v-enlace { text-decoration: none; }
.v-enlace:hover .v-folio { text-decoration: underline; }

.v-meta {
  display: block;
  font-size: var(--fs-xs);
  color: var(--text-3);
}
.v-cliente-nom {
  display: block;
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.v-total strong { font-variant-numeric: tabular-nums; white-space: nowrap; }

@media (min-width: 768px) {
  .tabla-ventas .v-venta   { width: 26%; }
  .tabla-ventas .v-cliente { width: 26%; }
  .tabla-ventas .v-total   { width: 14%; }
  .tabla-ventas .v-estado  { width: 1%; white-space: nowrap; }
  .tabla-ventas .v-acc     { width: 1%; white-space: nowrap; }
}

/* ---------- Detalle: cabecera ---------- */
.venta-cab {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--s4);
  flex-wrap: wrap;
  margin-bottom: var(--s4);
}
.venta-cab-txt { min-width: 0; }
.venta-folio { display: flex; align-items: center; gap: var(--s3); flex-wrap: wrap; }
.venta-folio-num {
  font-size: var(--fs-xl);
  font-weight: 800;
  letter-spacing: -.01em;
}
.venta-cab-meta { margin: var(--s1) 0 0; font-size: var(--fs-sm); color: var(--text-2); }
.venta-cab-acciones { display: flex; gap: var(--s2); flex-wrap: wrap; }
/* width auto: la regla base deja los botones a ancho completo y
   "Ver ticket" salia de 290px para dos palabras. Es el mismo detalle
   que ya aparecio en la barra de cobro del punto de venta. */
@media (min-width: 768px) {
}

/* ---------- Detalle: lo que se vendio ---------- */
.venta-items { list-style: none; margin: 0; padding: 0; }

.venta-item {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto;
  gap: var(--s3);
  align-items: center;
  padding: var(--s3) var(--s5);
  border-bottom: 1px solid var(--border);
}
.venta-item:last-child { border-bottom: none; }
.venta-item-txt { min-width: 0; }
.venta-item-nom {
  display: block;
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--text);
}
.venta-item-sub { display: block; font-size: var(--fs-xs); color: var(--text-3); }

.venta-item-cant {
  min-width: 34px;
  padding: 2px var(--s2);
  background: var(--surface-2);
  border-radius: var(--r-sm);
  font-size: var(--fs-sm);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  text-align: center;
  color: var(--text-2);
}
.venta-item-sub-total {
  min-width: 78px;
  text-align: right;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.venta-totales {
  padding: var(--s3) var(--s5) var(--s4);
  border-top: 1px solid var(--border);
  background: var(--surface-2);
}

.venta-enlaces {
  display: flex;
  gap: var(--s2);
  flex-wrap: wrap;
  margin-top: var(--s4);
  padding-top: var(--s4);
  border-top: 1px solid var(--border);
}

.venta-cancelar { display: grid; gap: var(--s4); }
@media (min-width: 768px) {
  .venta-cancelar {
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: end;
  }
  .venta-cancelar .campo { margin: 0; }
}

/* =====================================================================
   Telefono
   ===================================================================== */
@media (max-width: 767px) {

  /* El historial se convierte en fichas: cinco columnas de venta no se
     leen en 360px. Un solo marcado, como en el resto del sistema. */
  .tabla-ventas thead { display: none; }
  .tabla-ventas table,
  .tabla-ventas tbody,
  .tabla-ventas td { display: block; width: auto; }
  /* Sin borde ni relleno en las celdas: al pasar a bloque conservaban
     el borde inferior de la tabla y dentro de cada ficha aparecian
     lineas divisorias sueltas. El separador es el de la fila. */
  .tabla-ventas tbody td { border: none; padding: 0; }

  .tabla-ventas tbody tr {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas:
      "venta   total"
      "cliente estado"
      "acc     acc";
    gap: var(--s2) var(--s3);
    align-items: center;
    padding: var(--s3) var(--s4);
    border-bottom: 1px solid var(--border);
  }
  .tabla-ventas tbody tr:last-child { border-bottom: none; }

  .tabla-ventas .v-venta   { grid-area: venta; }
  .tabla-ventas .v-cliente { grid-area: cliente; }
  .tabla-ventas .v-total   { grid-area: total; justify-self: end; text-align: right; }
  .tabla-ventas .v-estado  { grid-area: estado; justify-self: end; }
  .tabla-ventas .v-acc     { grid-area: acc; display: flex; gap: var(--s2); }
  .tabla-ventas .v-acc .btn { flex: 1 1 auto; min-height: 44px; }

  .tabla-ventas tbody tr:has(td.vacio) { display: block; padding: 0; }
  .tabla-ventas td.vacio { display: block; padding: var(--s4); }

  .venta-cab-acciones { width: 100%; }
  .venta-cab-acciones .btn { flex: 1 1 auto; min-height: 44px; }

  .venta-item { padding: var(--s3) var(--s4); }
  .venta-totales { padding: var(--s3) var(--s4) var(--s4); }
  .venta-enlaces .btn { flex: 1 1 auto; min-height: 44px; }
  .venta-cancelar .btn { width: 100%; min-height: 48px; }
}

/* =====================================================================
   39. MI NEGOCIO
   ---------------------------------------------------------------------
   MEDIDO ANTES DE TOCAR NADA:

     viewport   .contenido   tarjeta   sobra a la derecha
     ---------  -----------  --------  -----------------
       768px       689px      560px         105px
      1440px      1181px      560px         597px
      1920px      1440px      560px         856px

   Siete <input> identicos de 526x46 en fila india dentro de esa
   tarjeta, y "Guardar cambios" de 526px de ancho para dos palabras. A
   1920 mas de la mitad del area util estaba vacia.

   La pantalla que representa la IDENTIDAD del negocio no ensenaba
   ninguna identidad: el nombre solo existia dentro de un campo de
   texto, y el estado de la cuenta, la moneda y la fecha de alta -que ya
   estaban en la misma fila que se leia- no se pintaban.
   ===================================================================== */

.negocio { display: block; }

/* ---------- Quien eres ---------- */
/* flex-start, no center: con un nombre comercial que ocupa tres
   renglones en telefono, centrado deja las iniciales flotando a media
   altura y el nombre empezando por encima de ellas. Arriba las dos
   cosas empiezan donde empieza la ficha. */
.negocio-cab {
  display: flex;
  align-items: flex-start;
  gap: var(--s4);
  margin-bottom: var(--s5);
}

.negocio-avatar {
  flex: none;
  display: grid;
  place-items: center;
  width: 64px;
  height: 64px;
  border-radius: var(--r-lg);
  border: 1px solid var(--border);
  font-size: var(--fs-xl);
  font-weight: var(--peso-max);
  letter-spacing: .01em;
  /* El color lo pone .prod-img-cN, la misma paleta del catalogo. */
}

.negocio-cab-txt { min-width: 0; }

.negocio-nombre {
  margin: 0;
  font-size: var(--fs-2xl);
  font-weight: var(--peso-max);
  line-height: var(--lh-tight);
  letter-spacing: var(--ls-titulo);
  overflow-wrap: anywhere;        /* nombres comerciales muy largos */
}
.negocio-razon {
  margin: 2px 0 0;
  font-size: var(--fs-sm);
  color: var(--text-2);
  overflow-wrap: anywhere;
}

.negocio-cab-chips {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--s2) var(--s3);
  margin-top: var(--s2);
}
.negocio-dato-sec {
  font-size: var(--fs-xs);
  color: var(--text-3);
  white-space: nowrap;
}

/* ---------- Las dos columnas ---------- */
/* Igual que en el ticket, el corte NO puede mirar solo al viewport: la
   lateral de la aplicacion se lleva 244px desde 1024px.

   El corte esta en 1280 y no antes por una medida concreta: a 1200 el
   area util son 941px, y a dos columnas la del formulario se quedaba en
   529px. La tarjeta ORIGINAL median 560. Repartir la pantalla para
   acabar con un formulario mas estrecho que el que se venia a arreglar
   no es aprovechar el espacio. A 1280 la columna del formulario mide
   633px y respira mas que antes. */
.negocio-cols {
  display: grid;
  gap: var(--s4);
  align-items: start;
}
.negocio-principal { display: grid; gap: var(--s4); align-content: start; }
/* La columna lateral de un formulario de Admin. La comparte con
   .neg-form-carril (seccion 41d). Ojo: NO es el .carril de la Ola 2,
   que es un riel horizontal con scroll-snap. Misma palabra, ejes
   opuestos; se documento en la Ola 5 y no se renombra. */
.negocio-carril,
.neg-form-carril { display: grid; gap: var(--s4); align-content: start; }

@media (min-width: 1280px) {
  .negocio-cols {
    grid-template-columns: minmax(0, 1fr) 340px;
    gap: var(--s5);
  }
}
@media (min-width: 1440px) {
  /* En pantalla ancha el carril crece un poco en vez de dejar el hueco
     al lado del formulario. */
  .negocio-cols { grid-template-columns: minmax(0, 1fr) 380px; }
}

/* Tablet y portatil: una columna, pero el carril se reparte en dos
   paneles a lo ancho en vez de estirarse. Composicion propia, no un
   movil estirado. */
@media (min-width: 768px) and (max-width: 1279px) {
  .negocio-carril {
    grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
    align-items: start;
  }
}

/* Los pares de campos van en .campos-rejilla, la rejilla UNICA del
   sistema (seccion 4), y no en .fila: el flex de .fila centra
   verticalmente dos campos de distinta altura y deja el que lleva
   mensaje de error desalineado respecto a su companero. La rejilla los
   alinea por arriba siempre.

   El ultimo grupo no arrastra su margen: el aire de abajo lo pone el
   cuerpo del panel. */
.negocio > .campo-grupo:last-of-type { margin-bottom: var(--s4); }
/* Solo el ULTIMO campo del formulario suelta su margen. Escrito como
   .campo-grupo .campo:last-child se lo quitaba tambien al segundo campo
   de cada .campos-rejilla -que es el ultimo hijo de SU rejilla- y el
   rotulo del campo siguiente se pegaba al control de arriba. */
.negocio .campo-grupo:last-of-type .campo:last-child { margin-bottom: 0; }

/* ---------- Vista previa del ticket ---------- */
.negocio-vista {
  padding: var(--s4) var(--s3);
  border: 1px dashed var(--border-2);
  border-radius: var(--r-sm);
  background: var(--surface-2);
  overflow-x: auto;
}
.negocio-vista-txt {
  font-family: "Courier New", Courier, monospace;
  font-size: var(--fs-xs);
  line-height: 1.5;
  white-space: pre;               /* 32 columnas exactas, sin reflujo */
  margin: 0;
  color: var(--text-2);
}

/* ---------- La accion ---------- */
/* G4: composicion heredada de .form-acciones. */
.negocio-acciones-nota {
  margin: 0;
  flex: 1;
  min-width: 200px;
  font-size: var(--fs-xs);
  color: var(--text-3);
}

/* ---------- Piezas de campo que faltaban en el sistema ---------- */
/* El asterisco de obligatorio se escribia suelto dentro del <label>,
   con el mismo peso que la palabra. .campo-opcional ya existia; su
   pareja no. */
.campo-req {
  color: var(--error);
  font-weight: var(--peso-max);
}
/* El RFC se guarda en mayusculas (negocio_actualizar hace strtoupper).
   El campo lo ensena asi tambien sin JavaScript. */
.campo-mayus { text-transform: uppercase; }
.campo-mayus::placeholder { text-transform: none; }

@media (max-width: 767px) {
  /* Telefono: la ficha se lee de arriba abajo y la accion ocupa el
     ancho, que es donde el pulgar la alcanza. */
  .negocio-cab { gap: var(--s3); margin-bottom: var(--s4); }
  .negocio-avatar { width: 52px; height: 52px; font-size: var(--fs-lg); }
  .negocio-nombre { font-size: var(--fs-xl); }

  .negocio-acciones-nota { order: 3; }
}


/* =====================================================================
   40. CATEGORIAS
   ---------------------------------------------------------------------
   MEDIDO ANTES DE TOCAR NADA, a 1440x950:
     - Formulario: tarjeta de 520px pegada a la izquierda, 652px de nada
       a su derecha.
     - Tabla a ancho completo pero mal repartida:

         Categoria   Descripcion   Productos   Acciones
           211px       236px         219px      481px

       El 42% de la tabla para dos botones, y el nombre -que es por lo
       que se recorre la lista- con menos sitio que la columna vacia.
     - Editar 86x34 y Eliminar 99x34: por debajo del minimo tactil.

   La lista se queda en TABLA. Son cuatro datos alineados que se
   comparan entre filas; convertirlos en tarjetas seria perder la
   comparacion, que es para lo que se abre la pantalla. En telefono el
   sistema ya las apila como fichas (seccion 14) y eso no se toca.
   ===================================================================== */

.categorias-cols {
  display: grid;
  gap: var(--s4);
  align-items: start;
}

/* El corte mira al ancho UTIL, no al viewport: la lateral de la
   aplicacion se lleva 244px desde 1024px.

   Estuvo en 1024 y era demasiado pronto. MEDIDO ahi: area util 765px,
   menos 340 del carril y 24 del hueco, quedaban 401px para una tabla de
   cuatro columnas cuya columna de acciones ya pide 246 fijos. El
   reparto salia 92/104/97/246 y las filas crecian a 205px de alto.
   Pasaba lo mismo a 1100 y a 1200.

   A 1280 el area util son 1021px y la lista se queda con 657: la tabla
   respira y el carril sigue cabiendo. */
@media (min-width: 1280px) {
  .categorias-cols {
    grid-template-columns: minmax(0, 1fr) 340px;
    gap: var(--s5);
  }
}
@media (min-width: 1440px) {
  .categorias-cols { grid-template-columns: minmax(0, 1fr) 380px; }
}

.categorias-form { display: grid; gap: var(--s4); align-content: start; }

/* En telefono y tableta el formulario va PRIMERO: son dos campos, es la
   accion de la pantalla, y es donde aterriza el flujo de editar. */
@media (max-width: 1279px) {
  .categorias-form { order: -1; }
}

/* ---------- Reparto de la tabla ---------- */
/* Antes el navegador le regalaba el sobrante a la columna sin texto: la
   de acciones se quedaba con 481px y el nombre con 211.

   Al fijarla en 1% pasaba lo contrario: se encogia a 139px, los dos
   botones no cabian en fila y las filas crecian a 120px de alto. El
   ancho de esa columna no lo decide un porcentaje, lo deciden sus
   botones: se le prohibe partir linea y mide lo que miden ellos. */
@media (min-width: 768px) {
  .categorias-lista th:nth-child(1) { width: 30%; }
  .categorias-lista th:nth-child(3) { width: 120px; }
  .categorias-lista th:nth-child(4),
  .categorias-lista td.cat-acciones { width: 1%; white-space: nowrap; }
  .categorias-lista .cat-acciones-caja { flex-wrap: nowrap; }
}


.cat-nombre {
  font-weight: var(--peso-fuerte);
  overflow-wrap: anywhere;        /* nombres de categoria largos */
}
.cat-desc { color: var(--text-2); font-size: var(--fs-sm); }
.cat-sin-desc { color: var(--text-3); font-style: italic; }

/* La descripcion admite 255 caracteres. MEDIDO: una de 200 caracteres en
   una columna de 171px daba una fila de 220px de alto, contra 73 de las
   demas: una sola categoria descuadraba la lista entera. Se corta en dos
   renglones y el texto completo va en el title. */
.cat-desc-txt {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  overflow-wrap: anywhere;
}

/* La cuenta lleva a los productos de esa categoria. productos.php?cat=N
   ya filtra: no es un enlace inventado. */
.cat-cuenta {
  display: inline-flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--s2);
  /* 40px de alto: medido en 31 y por debajo del minimo tactil. El
     margen negativo deja que el area de toque crezca sin mover el
     texto ni empujar el alto de la fila. */
  min-height: 40px;
  padding: 0 var(--s2);
  margin: -4px -8px;
  border-radius: var(--r-sm);
  font-weight: var(--peso-max);
  font-variant-numeric: tabular-nums;
  color: var(--brand-700);
  text-decoration: none;
  transition: background var(--t-rapida) var(--ease);
}
a.cat-cuenta:hover { background: var(--brand-50); }
.cat-cuenta-txt {
  font-size: var(--fs-xs);
  font-weight: 400;
  color: var(--text-2);
}
.cat-cuenta-cero { color: var(--text-3); font-weight: var(--peso-medio); }

/* ---------- Acciones de fila ---------- */
.cat-acciones-caja {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--s2);
  flex-wrap: wrap;
}
.cat-acciones-caja .btn { min-height: 40px; }

/* Categoria en uso: no se ofrece Eliminar porque el servidor lo rechaza.
   Esto no es un boton desactivado, es la razon de que no haya boton. */
.cat-en-uso {
  display: inline-flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--s1);
  padding: 0 var(--s2);
  /* Ocupa un hueco parecido al del boton que sustituye para que
     "Editar" caiga a la misma altura en todas las filas. En em, no en
     px: si crece la tipografia, crece con ella. */
  min-width: 7.5em;
  min-height: 40px;
  font-size: var(--fs-xs);
  color: var(--text-3);
  white-space: nowrap;
}
.cat-en-uso svg { flex: none; }

/* ---------- La fila que se esta editando ---------- */
/* El formulario esta en la otra columna: sin esto no habia forma de
   saber cual de las filas se esta tocando. */
.cat-fila-activa > td { background: var(--brand-50); }
.cat-editando {
  display: inline-block;
  margin-left: var(--s2);
  padding: 1px var(--s2);
  border-radius: var(--r-full);
  background: var(--brand-600);
  color: var(--text-inv);
  font-size: var(--fs-3xs);
  font-weight: var(--peso-max);
  letter-spacing: var(--ls-etiqueta);
  text-transform: uppercase;
  vertical-align: middle;
}

/* Panel en modo edicion: el mismo aviso, del lado del formulario. */
.panel-activo { border-color: var(--brand-600); }

/* ---------- Formulario ---------- */
/* G4: la composicion la pone .form-acciones. Lo propio de esta
   pantalla es que los dos botones se repartan el panel a partes
   iguales: es una columna estrecha al lado de la tabla. */
.cat-form-acciones { gap: var(--s2); }
.cat-form-acciones .btn { flex: 1 1 auto; }

.cat-nota {
  margin: 0 0 var(--s4);
  font-size: var(--fs-sm);
  color: var(--text-2);
  line-height: var(--lh);
}

@media (max-width: 767px) {
  /* La tabla ya es una ficha por fila (seccion 14). Aqui solo se coloca
     lo que dentro de la ficha no se reparte igual que en una fila.

     Rejilla de dos y no flex: 'justify-content: stretch' NO existe en
     flexbox -es de grid- y los botones se quedaban a media anchura,
     apilados y pegados a la derecha. Con grid ocupan la ficha entera y
     caen uno al lado del otro. */
  .categorias-lista .cat-acciones-caja {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--s2);
    width: 100%;
  }
  .categorias-lista .cat-acciones-caja .btn {
    justify-content: center;
    min-height: 44px;
    width: 100%;
  }
  /* Cuando no hay boton de eliminar, "Editar" no se queda a medias. */
  .categorias-lista .cat-acciones-caja:has(.cat-en-uso) {
    grid-template-columns: 1fr auto;
  }
  .cat-en-uso { min-width: 0; }

  .cat-cuenta { margin: 0; }
}


/* =====================================================================
   41. ADMIN: LISTADO Y GESTION  (Negocios)
   ---------------------------------------------------------------------
   Primera pantalla del area de administracion que se migra al sistema
   visual de cliente. NO es un segundo sistema: aqui solo viven las
   piezas que el listado administrativo necesita y que no existian.

   MEDIDO ANTES DE TOCAR NADA, a 1440x950:
     - El buscador se estiraba a 1196px, todo el ancho util, para una
       caja de texto de una palabra.
     - Tabla de 7 columnas sin <thead> real.
     - SEIS controles por debajo de los 40px tactiles.
     - A 768px la tabla medía 788px dentro de un contenedor de 639 y se
       desplazaba de lado sin ninguna senal.

   Los nombres van con prefijo .admin- cuando la pieza se espera comun a
   varias pantallas del area, y con .neg- cuando es de esta. Si Usuarios
   demuestra que .admin-barra se repite igual, se queda; si no, se
   renombra. No se crea el helper de PHP hasta tener la segunda prueba.
   ===================================================================== */

/* ---------- La barra: buscar a la izquierda, alta a la derecha ---------- */
.admin-barra {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--s3);
  flex-wrap: wrap;
  margin-bottom: var(--s4);
}

/* Fila propia y NO .filtros. .filtros es la barra de VARIOS controles
   que en telefono se pliega en una hoja, y para conseguirlo aplica
   "> * { display: none }" devolviendo a la vista solo lo que reconoce
   como buscador. Una busqueda de un solo campo montada sobre ella
   desaparece entera por debajo de 767px.

   Lo que si se corrige de la version anterior: el buscador medía 1196px,
   todo el ancho util, para una caja de texto de una palabra. */
.admin-barra-buscar {
  display: flex;
  align-items: center;
  gap: var(--s2);
  flex: 1 1 320px;
  max-width: 520px;
}
.admin-barra-buscar input[type="search"] {
  flex: 1;
  width: auto;
  min-width: 0;
}
.admin-barra-buscar .btn { flex: none; }

/* G3: aqui habia un "width: auto" para desdecir el width:100% de la
   base, y con el un comentario explicando por que "Nuevo negocio" se
   iba a un renglon propio. La base ya mide por contenido: sobran los
   dos. Lo que si sigue haciendo falta es el minimo tactil de abajo. */

/* Los botones del sistema no llegan al minimo tactil por si mismos.
   Medido en esta barra: 38px. */
.admin-barra .btn { min-height: 40px; }

@media (max-width: 599px) {
  /* En telefono la barra es una columna: el buscador arriba con su
     boton, y el alta abajo a lo ancho, que es donde llega el pulgar.

     El flex del buscador se reinicia: en direccion columna, flex-grow y
     flex-basis miden ALTO, no ancho, y el "1 1 320px" de escritorio
     estiraba la fila de busqueda hasta 381px de alto con el hueco
     repartido arriba y abajo. */
  /* flex-wrap: nowrap NO es decoracion.
     ------------------------------------------------------------------
     Medido a 320px: la barra quedaba en 273px y el buscador dentro en
     319px, con 30px de barra horizontal en TODA la pagina.

     La causa es una regla de flexbox que no se ve: en una columna que
     ADEMAS envuelve (flex-wrap: wrap, heredado de la barra de
     escritorio), 'align-items: stretch' deja de estirar los elementos
     al ancho del contenedor. El tamano de la linea lo fija el elemento
     mas grande, y el buscador -campo + boton en una fila que no se
     parte- pedia 319px. Con nowrap el stretch vuelve a mandar y el
     campo se encoge hasta lo que quede (160px) mientras el boton
     conserva su tamano tactil.

     En columna y con dos elementos, envolver no servia para nada: no
     se pierde ningun comportamiento. */
  .admin-barra { flex-direction: column; align-items: stretch; flex-wrap: nowrap; }
  .admin-barra-buscar { flex: none; max-width: none; min-width: 0; }
  .admin-barra > .btn { width: 100%; min-height: 44px; }
}

/* ---------- La celda de identidad del negocio ---------- */
/* Nombre, RFC y contacto eran DOS columnas ("Negocio" y "Contacto") y
   son un solo sujeto. Plegados aqui, como el catalogo pliega el SKU
   bajo el nombre del producto. */
.neg-nombre {
  display: block;
  font-weight: var(--peso-fuerte);
  overflow-wrap: anywhere;
}
.neg-rfc,
.neg-contacto {
  display: block;
  margin-top: var(--s0);
  font-size: var(--fs-xs);
  color: var(--text-2);
  overflow-wrap: anywhere;
}
.neg-rfc { color: var(--text-3); }

.neg-acceso { font-size: var(--fs-sm); white-space: nowrap; }
.neg-nunca  { color: var(--text-3); }

/* ---------- Acciones de fila ---------- */
.neg-acciones-caja {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--s2);
  flex-wrap: wrap;
}
.neg-acciones-caja .btn { min-height: 40px; }

/* ---------- Reparto de la tabla ---------- */
/* Las reglas de COLUMNAS van con la clase de la pantalla
   (.lista-negocios, .lista-usuarios) y no con .admin-lista, que es la
   compartida. Cuando Usuarios estreno .admin-lista, estas reglas de
   Negocios se le aplicaron encima y a 768px Usuarios PERDIA su columna
   de Estado, que es justo la que se administra ahi. Lo comun se comparte;
   el reparto de columnas depende de que columnas tiene cada tabla.

   Prohibir a las acciones partir linea SOLO funciona donde caben. Con
   tres botones ("Revisar" aparece en los pendientes) la columna pide
   354px, y MEDIDO con nowrap en todo el rango >=641 el nombre se
   quedaba en 124px a 1200 y en 80px a 1024 y a 1100: filas de 357px y
   de 650px de alto, y la tabla desbordando su contenedor. Es el mismo
   error que ya aparecio en Categorias, un ancho mas abajo.

   Asi que el nowrap vive solo donde hay sitio. */
@media (min-width: 1280px) {
  .lista-negocios th:nth-child(1) { width: 34%; }
  .lista-negocios th:nth-child(3),
  .lista-negocios th:nth-child(4) { width: 92px; }
  .lista-negocios th:last-child,
  .lista-negocios td.neg-acciones { width: 1%; white-space: nowrap; }
  .lista-negocios .neg-acciones-caja { flex-wrap: nowrap; }
}

/* Banda 641-1279: la critica. La lateral de la aplicacion se lleva
   244px desde 1024, asi que el area util a 1024 (765px) es MENOR que a
   900 (821px). Aqui la tabla no cabe entera:

     - "Ultimo acceso" cede el sitio. La fecha exacta no decide nada en
       un portatil estrecho y sigue estando en la ficha del negocio.
     - Las acciones se reparten en dos renglones dentro de una columna
       acotada, en vez de comerse el ancho del nombre. La fila crece
       unos 40px; con nowrap crecia 570.
     - Las celdas respiran menos para devolverle ancho al nombre. */
@media (min-width: 768px) and (max-width: 1279px) {
  .lista-negocios th, .lista-negocios td { padding: var(--s2) var(--s3); }

  .lista-negocios th:nth-child(5),
  .lista-negocios td:nth-child(5) { display: none; }

  .lista-negocios th:nth-child(3),
  .lista-negocios th:nth-child(4) { width: 76px; }

  .lista-negocios th:last-child,
  .lista-negocios td.neg-acciones { width: 196px; }
  .lista-negocios .neg-acciones-caja { flex-wrap: wrap; justify-content: flex-end; }

  /* Sin iconos en los botones de fila. Con ellos "Editar" mide 100px y
     "Bloquear" 115, y en 196px no caben los dos: se apilaban uno por
     renglon y la fila subia a 206px. Sin ellos miden 70 y 85, caben en
     una linea y la fila vuelve a ~74px. El icono aqui acompana a una
     etiqueta que ya dice lo mismo; el ancho del nombre del negocio no
     es negociable. En escritorio vuelven. */
  .lista-negocios .neg-acciones-caja .btn > svg { display: none; }
  .lista-negocios .neg-acciones-caja .btn { gap: 0; padding-inline: var(--s3); }
}

/* Por debajo de 900px tambien ceden las dos cuentas. MEDIDO a 768: con
   ellas el nombre se quedaba en 139px y un negocio de nombre largo
   ocupaba una fila de 332px. Cuantos usuarios y cuantos productos tiene
   es contexto; cual es el negocio y en que estado esta es la identidad,
   y la identidad no se recorta. Los dos numeros siguen en la ficha del
   negocio y en Inventarios. */
@media (min-width: 768px) and (max-width: 899px) {
  .lista-negocios th:nth-child(3),
  .lista-negocios td:nth-child(3),
  .lista-negocios th:nth-child(4),
  .lista-negocios td:nth-child(4) { display: none; }
}

@media (max-width: 767px) {
  /* La tabla ya es una ficha por fila (seccion 14). Aqui solo se coloca
     lo que dentro de la ficha no se reparte igual que en una fila.

     Rejilla y no flex: 'justify-content: stretch' no existe en flexbox
     y los botones se quedarian a media anchura, apilados a la derecha. */
  .lista-negocios .neg-acciones-caja {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--s2);
    width: 100%;
  }
  .lista-negocios .neg-acciones-caja .btn {
    justify-content: center;
    min-height: 44px;
    width: 100%;
  }
  /* Con tres acciones (los pendientes llevan "Revisar"), la primera
     ocupa el renglon entero y las otras dos se reparten el siguiente. */
  .lista-negocios .neg-acciones-caja > :first-child:nth-last-child(3) {
    grid-column: 1 / -1;
  }
  /* La identidad manda dentro de la ficha: el nombre a la izquierda y
     con su tamano, no como un valor mas alineado a la derecha. */
  .lista-negocios td:first-child { display: block; text-align: left; }
  .lista-negocios td:first-child::before { content: none; }
  .lista-negocios .neg-nombre { font-size: var(--fs-base); }
}


/* ---------------------------------------------------------------------
   41b. ADMIN: USUARIOS
   Reutiliza .admin-barra, .admin-barra-buscar y .admin-lista de Negocios
   sin un solo cambio. Aqui solo van las piezas propias de la fila de
   una persona.

   MEDIDO ANTES DE TOCAR NADA, a 1440x950: sin metricas, sin <thead>,
   los tres "Bloquear" a 34px de alto, chips de stock para el estado y
   CERO confirmaciones. En telefono, ademas, no habia buscador.
   --------------------------------------------------------------------- */

.usr-nombre {
  display: block;
  font-weight: var(--peso-fuerte);
  overflow-wrap: anywhere;
}
.usr-correo {
  display: block;
  margin-top: var(--s0);
  font-size: var(--fs-xs);
  color: var(--text-2);
  overflow-wrap: anywhere;
}


.usr-negocio { font-size: var(--fs-sm); overflow-wrap: anywhere; }
.usr-plataforma { color: var(--text-3); font-style: italic; }

.usr-acceso { font-size: var(--fs-sm); white-space: nowrap; }
.usr-nunca  { color: var(--text-3); }

/* Dos chips apilados: "Bloqueado" y "Frenado por intentos" son cosas
   distintas y pueden darse a la vez. En fila se salian de la columna. */
.usr-estado { display: flex; flex-direction: column; align-items: flex-start; gap: var(--s1); }

.usr-acciones-caja {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--s2);
  flex-wrap: wrap;
}
/* La etiqueta no parte dentro del boton: "Limpiar intentos" salia en
   dos renglones dentro de su propia caja. Que se reparta la FILA, no la
   palabra. */
.usr-acciones-caja .btn { min-height: 40px; white-space: nowrap; }

/* La cuenta propia no lleva boton porque el servidor no deja bloquearla.
   Ocupa un hueco parecido al del boton que sustituye para que la columna
   no baile de fila en fila. */
.usr-yo {
  display: inline-flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--s1);
  min-width: 7.5em;
  min-height: 40px;
  padding: 0 var(--s2);
  font-size: var(--fs-xs);
  color: var(--text-3);
  white-space: nowrap;
}
.usr-yo svg { flex: none; }

/* ---------- Reparto de la tabla ---------- */
/* Mismo criterio que Negocios: el nowrap de las acciones solo donde hay
   sitio. "Limpiar intentos" convierte la columna en dos botones anchos,
   asi que por debajo de 1280 se le deja partir linea. */
/* Ancho FIJO, no "1% + nowrap" como en Negocios. Aqui una fila puede
   llevar DOS acciones ("Limpiar intentos" + "Bloquear") y con nowrap la
   columna se quedaba con 323px: medido, "Negocio" caía a 80px y
   "COSMETICA" se partia a media palabra. Con la columna acotada, las
   dos acciones se reparten en dos renglones SOLO en las filas que las
   tienen, y el resto de la tabla recupera su ancho. */
@media (min-width: 1280px) {
  .lista-usuarios th:last-child,
  .lista-usuarios td.usr-acciones { width: 230px; }
  .lista-usuarios th:nth-child(2) { width: 15%; }
}

/* Banda 900-1279: "Ultimo acceso" cede el sitio. Cuando entro por
   ultima vez es contexto; quien es y si puede entrar es lo que se
   administra aqui. Sigue estando en la ficha de su negocio. */
@media (min-width: 768px) and (max-width: 1279px) {
  .lista-usuarios th, .lista-usuarios td { padding: var(--s2) var(--s3); }
  .lista-usuarios th:nth-child(5),
  .lista-usuarios td.usr-acceso { display: none; }
  .lista-usuarios td.usr-acciones { width: 200px; }
  .lista-usuarios .usr-acciones-caja { flex-wrap: wrap; justify-content: flex-end; }
  /* Sin iconos en los botones de fila: con ellos no caben dos por
     renglon y la fila crece. La etiqueta ya dice lo mismo. */
  .lista-usuarios .usr-acciones-caja .btn > svg { display: none; }
  .lista-usuarios .usr-acciones-caja .btn { gap: 0; padding-inline: var(--s3); }
}

/* En toda la banda critica cede tambien el ROL. MEDIDO a 1024 con el
   rol presente: la columna del nombre se quedaba en 123px y la fila
   subia a 262px. El rol vive en Permisos, que es la pantalla que existe
   para verlo y cambiarlo; lo que se decide AQUI es si la persona entra
   o no entra, y para eso hacen falta su nombre y su estado. */
@media (min-width: 768px) and (max-width: 1279px) {
  .lista-usuarios th:nth-child(3),
  .lista-usuarios td:nth-child(3) { display: none; }
}

@media (max-width: 767px) {
  /* La tabla ya es una ficha por fila (seccion 14). */
  .lista-usuarios .usr-acciones-caja {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--s2);
    width: 100%;
  }
  .lista-usuarios .usr-acciones-caja .btn {
    justify-content: center;
    min-height: 44px;
    width: 100%;
  }
  .lista-usuarios .usr-acciones-caja form { width: 100%; }
  .usr-yo { justify-content: center; width: 100%; min-width: 0; }

  /* La identidad manda dentro de la ficha: el nombre a la izquierda y
     con su tamano, no como un valor mas alineado a la derecha. */
  .lista-usuarios td:first-child { display: block; text-align: left; }
  .lista-usuarios td:first-child::before { content: none; }
  .lista-usuarios .usr-nombre { font-size: var(--fs-base); }

  /* Los dos chips de estado, en fila dentro de la ficha: ahi si caben
     y ocupan un renglon en vez de dos. */
  .lista-usuarios .usr-estado { flex-direction: row; flex-wrap: wrap; justify-content: flex-end; }
}


/* ---------------------------------------------------------------------
   41c. ADMIN: SOLICITUDES  (cola de trabajo)
   ---------------------------------------------------------------------
   NO reutiliza .admin-barra ni .admin-lista: esta pantalla no tiene ni
   buscador ni tabla. Es una bandeja de entrada, y cada solicitud es una
   ficha con dos acciones al final.

   MEDIDO ANTES DE TOCAR NADA:
     - Cada solicitud era una tabla clave/valor. La columna de valores
       medía 949px a 1440 y 1208px a 1920 para escribir "ALMA".
     - Tarjeta de 1133x428 para siete datos cortos.
     - Rechazar no confirmaba y su motivo era un <input> sin etiqueta
       apretado al lado del boton.
   --------------------------------------------------------------------- */


/* La cola se lee en columna y se topa al ancho de lectura: una ficha de
   decision de 1400px de ancho obliga a barrer la cabeza de lado a lado
   para juntar el nombre con su boton. */
.sol-cola {
  display: grid;
  gap: var(--s4);
  max-width: 900px;
}

.sol { scroll-margin-top: var(--s5); }

/* Lo que espera decision se distingue de lo ya resuelto sin gritar. */
.sol-espera { border-color: var(--border-2); box-shadow: var(--sh-2); }

/* ---------- Cabecera de la ficha ---------- */
.sol-cab {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--s3);
  flex-wrap: wrap;
}
.sol-cab-txt { min-width: 0; }

.panel .sol-nombre {
  margin: 0;
  font-size: var(--fs-lg);
  font-weight: var(--peso-max);
  line-height: var(--lh-tight);
  overflow-wrap: anywhere;        /* nombres comerciales muy largos */
}
.sol-fecha {
  margin: 2px 0 0;
  font-size: var(--fs-xs);
  color: var(--text-2);
}

/* ---------- Los datos de la solicitud ---------- */
/* Rejilla por CONTENIDO, no tabla. auto-fit con un minimo razonable:
   cada dato ocupa lo que necesita y a partir de dos columnas deja de
   estirarse. Asi "ALMA" mide lo que mide "ALMA". */
.sol-datos {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: var(--s3) var(--s5);
  margin: 0;
}
.sol-dato { min-width: 0; }
.sol-dato dt {
  font-size: var(--fs-3xs);
  font-weight: var(--peso-fuerte);
  letter-spacing: var(--ls-etiqueta);
  text-transform: uppercase;
  color: var(--text-3);
}
.sol-dato dd {
  margin: 2px 0 0;
  font-size: var(--fs-sm);
  overflow-wrap: anywhere;
}
/* Razon social y direccion son frases, no etiquetas: ocupan la fila. */
.sol-dato-ancho { grid-column: 1 / -1; }
/* La IP es dato tecnico: se lee, no se destaca. */
.sol-dato-tec dd { font-family: "Courier New", Courier, monospace; font-size: var(--fs-xs); color: var(--text-2); }

.sol-sin-datos {
  display: flex;
  align-items: flex-start;
  gap: var(--s2);
  margin: var(--s4) 0 0;
  padding: var(--s2) var(--s3);
  border-radius: var(--r-sm);
  background: var(--warn-bg);
  color: var(--warn);
  font-size: var(--fs-xs);
  line-height: var(--lh);
}
.sol-sin-datos svg { flex: none; margin-top: var(--s0); }

/* ---------- Lo ya decidido ---------- */
.sol-historial {
  margin-top: var(--s4);
  padding: var(--s3);
  border-radius: var(--r-sm);
  background: var(--surface-2);
  border-left: 3px solid var(--ok);
}
.sol-historial-no { border-left-color: var(--error); }
.sol-historial-cab {
  margin: 0;
  font-size: var(--fs-xs);
  font-weight: var(--peso-fuerte);
  color: var(--text-2);
}
/* El motivo es lo que explica la decision: se lee, no se esconde. */
.sol-motivo {
  margin: var(--s2) 0 0;
  font-size: var(--fs-sm);
  line-height: var(--lh);
  color: var(--text);
}

/* ---------- Decidir ---------- */
/* Autorizar y Rechazar no son dos botones de una fila: uno es un boton
   y el otro es un boton CON un campo. Se separan del resto y entre si. */
.sol-decidir {
  display: grid;
  gap: var(--s4);
  margin-top: var(--s5);
  padding-top: var(--s4);
  border-top: 1px solid var(--border);
}
.sol-decidir .btn { min-height: 44px; }
.sol-decidir .campo { margin-bottom: var(--s3); }

@media (min-width: 768px) {
  /* Autorizar arriba, a la izquierda; el bloque de rechazo debajo y
     separado. No van lado a lado: la decision de rechazar pide escribir
     antes de pulsar, y ponerla al lado del boton de autorizar invita a
     pulsar el que no era. */
  .sol-decidir { justify-items: start; }
  .sol-rechazar { width: 100%; max-width: 520px; }
}

@media (max-width: 767px) {
  .sol-decidir .btn { width: 100%; min-height: 48px; }
}

.sol-decidir-una { padding-top: var(--s4); }


/* ---------------------------------------------------------------------
   41d. ADMIN: ALTA Y EDICION DE NEGOCIO
   ---------------------------------------------------------------------
   MEDIDO ANTES DE TOCAR NADA, a 1440x950:
     - DOCE campos apilados, cada <input> de 1099px de ancho. Un RFC de
       13 caracteres en una caja de 1099px.
     - Alta: una sola tarjeta de 1133px con los datos del negocio y los
       del usuario administrador seguidos, sin nada que los separe.
     - Edicion: tarjetas de 1133x679 y 1133x548, y una tabla de usuarios
       sin <thead>, con "Bloquear" a 34px.
     - "Volver a negocios": enlace de 20px de alto.

   Prefijos propios (.neg-form-*, .nfu-*) para no chocar con .neg-* del
   LISTADO de negocios, que son otra pantalla y otras clases.
   --------------------------------------------------------------------- */

.neg-form-cols {
  display: grid;
  gap: var(--s4);
  align-items: start;
}
.neg-form-principal { display: grid; gap: var(--s4); align-content: start; }
/* OLA 7: la base la comparte con .negocio-carril (seccion 41, linea
   ~8946): la columna lateral de un formulario de Admin es la misma
   pieza en las dos pantallas. Lo que NO se comparte es el reparto de
   dentro, que abajo es distinto a proposito: aqui el carril se acota
   a 420px y alli se estira. Y ninguno de los dos es el .carril de la
   Ola 2, que es un riel HORIZONTAL que se desliza. */

/* El corte a dos columnas mira al ancho UTIL: la lateral de la
   aplicacion se lleva 244px desde 1024px, asi que el area util a 1024
   (765px) es MENOR que a 900 (821px). A 1280 la columna del formulario
   se queda con ~640px, que es donde dos campos emparejados caben sin
   apretarse. Es el mismo corte que ya validaron Mi negocio, Categorias,
   Negocios y Usuarios. */
@media (min-width: 1280px) {
  .neg-form-cols {
    grid-template-columns: minmax(0, 1fr) 340px;
    gap: var(--s5);
  }
}
@media (min-width: 1440px) {
  .neg-form-cols { grid-template-columns: minmax(0, 1fr) 380px; }
}

/* Tablet y portatil: una columna, y el carril NO se estira. Con
   minmax(280px, 1fr) un carril de un solo panel ocupaba los 893px de
   la fila para cuatro datos cortos. Con un maximo, el panel mide lo
   que necesita y los que haya se colocan desde la izquierda. */
@media (min-width: 768px) and (max-width: 1279px) {
  .neg-form-carril {
    grid-template-columns: repeat(auto-fit, minmax(280px, 420px));
    justify-content: start;
    align-items: start;
  }
}

/* El selector de rol no se estira al ancho del panel: es un menu de
   dos opciones, no una frase. Va solo en su renglon y acotado.

   NO hace falta apilar las filas por media query: .crece-medio pide 240px
   minimos y .crece-corto 180, asi que en cuanto no caben dos, flex-wrap
   los baja solo. El problema medido -campos de 194px a 768 y de 220 a
   1024- venia de meter TRES campos en una fila, y eso se corrigio en el
   marcado, no aqui. */
.neg-form-rol { max-width: 320px; }

/* Un campo dentro de una .fila no arrastra su margen inferior: lo pone
   el gap de la fila. Sin esto, dos campos emparejados quedan
   descuadrados en cuanto uno lleva mensaje de error o texto de ayuda y
   el otro no. Mismo arreglo que en Mi negocio, acotado a esta pantalla. */
.neg-form .fila > .campo { margin-bottom: 0; }
.neg-form .fila { margin-bottom: var(--s4); }
.neg-form .panel-cuerpo > *:last-child { margin-bottom: 0; }

/* El panel de acceso no es un dato mas del negocio: es la cuenta con
   contrasena. Se marca sin gritar. */
.neg-form-acceso { border-color: var(--border-2); }

.neg-form-ir { margin-top: var(--s4); }
.neg-form-ir .btn { width: 100%; min-height: 44px; }

/* ---------- Acciones del formulario ---------- */
/* G4: la composicion la pone .form-acciones. Aqui no queda nada
   propio; la clase se conserva porque .neg-form-acciones-sub la usa
   para quitarse la linea de separacion. */
.neg-form-nota {
  margin: 0;
  flex: 1;
  min-width: 180px;
  font-size: var(--fs-xs);
  color: var(--text-3);
}
/* Las acciones de un panel interior no llevan raya ni margen grande:
   ya estan dentro de su propia caja. */
.neg-form-acciones-sub { margin-top: var(--s4); padding-top: 0; border-top: none; }

/* ---------- Tabla de usuarios del negocio ---------- */
.neg-form-usuarios { margin-top: var(--s5); }

.nfu-nombre {
  display: block;
  font-weight: var(--peso-fuerte);
  overflow-wrap: anywhere;
}
.nfu-correo {
  display: block;
  margin-top: var(--s0);
  font-size: var(--fs-xs);
  color: var(--text-2);
  overflow-wrap: anywhere;
}
.nfu-acceso { font-size: var(--fs-sm); white-space: nowrap; }
.nfu-nunca  { color: var(--text-3); }

.nfu-acciones-caja {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--s2);
  flex-wrap: wrap;
}
.nfu-acciones-caja .btn { min-height: 40px; white-space: nowrap; }

/* Reparto propio: esta tabla tiene CINCO columnas y las suyas, asi que
   no hereda nada de .lista-negocios ni de .lista-usuarios. */
@media (min-width: 768px) {
  .neg-form-usuarios th:nth-child(1) { width: 38%; }
  .neg-form-usuarios th:last-child,
  .neg-form-usuarios td.nfu-acciones { width: 1%; white-space: nowrap; }
  .neg-form-usuarios .nfu-acciones-caja { flex-wrap: nowrap; }
}
/* Entre 641 y 1023 "Ultimo acceso" cede el sitio: quien es y si puede
   entrar es lo que se administra aqui. */
@media (min-width: 768px) and (max-width: 1023px) {
  .neg-form-usuarios th, .neg-form-usuarios td { padding: var(--s2) var(--s3); }
  .neg-form-usuarios th:nth-child(4),
  .neg-form-usuarios td.nfu-acceso { display: none; }
}

@media (max-width: 767px) {
  /* La tabla ya es una ficha por fila (seccion 14). */
  .neg-form-usuarios .nfu-acciones-caja { width: 100%; }
  .neg-form-usuarios .nfu-acciones-caja form { width: 100%; }
  .neg-form-usuarios .nfu-acciones-caja .btn {
    width: 100%;
    justify-content: center;
    min-height: 44px;
  }
  .neg-form-usuarios td:first-child { display: block; text-align: left; }
  .neg-form-usuarios td:first-child::before { content: none; }
  .neg-form-usuarios .nfu-nombre { font-size: var(--fs-base); }

  /* El pie del formulario a lo ancho, que es donde llega el pulgar. */
  .neg-form-nota { order: 3; }
}


/* ---------------------------------------------------------------------
   41e. ADMIN: ACTIVIDAD  (registro de eventos)
   ---------------------------------------------------------------------
   MEDIDO ANTES DE TOCAR NADA:

     A 1440x950
       - El <select> del filtro medía 1047px de ancho.
       - Tabla de 6 columnas sin <thead>. "IP" ocupaba una columna
         entera de 45px para escribir "::1".
       - Anterior/Siguiente a 34px de alto.

     A 390px
       - 12.408px de alto: 50 registros x 242px, seis renglones
         etiquetados cada uno. Unas 32 pantallas de telefono.
       - Y el filtro estaba en display:none: .filtros oculta todo lo que
         no reconoce como buscador, y aqui son un <select> y un <button>
         sueltos.

   No reutiliza .admin-barra ni .admin-lista de Negocios/Usuarios: esta
   pantalla no busca ni administra filas, recorre un registro. Prefijo
   propio .act-*.
   --------------------------------------------------------------------- */

/* ---------- Filtro ---------- */
.act-barra { margin-bottom: var(--s4); }

/* G5: composicion compartida arriba. El hueco de esta barra es mas
   corto porque son dos controles pegados a su rotulo. */
.act-filtro { gap: var(--s2); }
.act-filtro-rotulo {
  font-size: var(--fs-sm);
  font-weight: var(--peso-fuerte);
  color: var(--text-2);
}
/* El desplegable medía 1047px. Ni es el contenido de la pagina ni hace
   falta tanto para un nombre de accion. */
.act-filtro select {
  width: auto;
  flex: 1 1 240px;
  max-width: 340px;
  min-height: 40px;
}
.act-filtro .btn { min-height: 40px; }

@media (max-width: 599px) {
  /* En telefono el desplegable ocupa su renglon y los botones el suyo:
     lo importante es que EXISTAN, que es lo que no pasaba. */
  .act-filtro select { flex: 1 1 100%; max-width: none; }
  .act-filtro .btn { flex: 1; min-height: 44px; }
}

/* ---------- Una fila del registro ---------- */
/* Marca de tipo. La FORMA del icono cambia con el tipo, no solo el
   color: triangulo de aviso, circulo de informacion y palomita. Asi el
   tipo se distingue sin depender de la vista del color. */
.act-marca {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 22px;
  height: 22px;
  border-radius: var(--r-full);
  background: var(--surface-2);
  color: var(--text-3);
}
.act-peligro  .act-marca { background: var(--error-bg); color: var(--error); }
.act-atencion .act-marca { background: var(--warn-bg);  color: var(--warn); }

.act-accion { white-space: normal; }
.act-nombre { font-weight: var(--peso-medio); overflow-wrap: anywhere; }

/* Los eventos de riesgo se distinguen tambien por una marca en el
   borde, ademas del icono: dos canales, no solo el color. */
.act-fila.act-peligro > td:first-child  { box-shadow: inset 3px 0 0 var(--error); }
.act-fila.act-atencion > td:first-child { box-shadow: inset 3px 0 0 var(--warn); }

.act-usuario { display: block; font-size: var(--fs-sm); overflow-wrap: anywhere; }
.act-negocio { display: block; font-size: var(--fs-xs); color: var(--text-2); overflow-wrap: anywhere; }

/* El detalle es texto libre y puede ser largo: dos renglones y el resto
   en el title, para que una entrada no descuadre el alto de la lista. */
.act-detalle { color: var(--text-2); font-size: var(--fs-sm); }
.act-detalle-txt {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  overflow-wrap: anywhere;
}

.act-cuando { white-space: nowrap; }
.act-fecha { display: block; font-size: var(--fs-sm); font-variant-numeric: tabular-nums; }
.act-hora  { display: block; font-size: var(--fs-xs); color: var(--text-3); font-variant-numeric: tabular-nums; }

/* La IP es dato tecnico: se lee cuando se busca, no compite. */
.act-ip {
  font-family: "Courier New", Courier, monospace;
  font-size: var(--fs-xs);
  color: var(--text-3);
  overflow-wrap: anywhere;
}

/* ---------- Reparto de la tabla ---------- */
@media (min-width: 768px) {
  .act-lista th:nth-child(1) { width: 24%; }
  .act-lista th:nth-child(2) { width: 20%; }
  .act-lista th:nth-child(4) { width: 90px; }
  .act-lista th:nth-child(5) { width: 120px; }

  /* El inset del borde vive en el <td>, asi que la celda sigue siendo
     celda de tabla y participa en el reparto de columnas. */
  .act-lista td.act-accion > .act-marca { vertical-align: middle; margin-right: var(--s2); }
  .act-lista td.act-accion > .act-nombre { vertical-align: middle; }

  /* UN renglon por evento. Al partir "quien" y "cuando" en dos lineas
     cada uno, la fila medía 63px contra los 48 de antes y la pagina
     crecia de 2.747px a 3.559: el rediseno estaba empeorando la
     densidad justo donde sobra ancho. En una sola linea la fila baja a
     ~44px. */
  .act-lista .act-usuario,
  .act-lista .act-negocio,
  .act-lista .act-fecha,
  .act-lista .act-hora { display: inline; }

  .act-lista .act-negocio::before {
    content: "·";
    margin: 0 var(--s1);
    color: var(--text-3);
  }
  .act-lista .act-hora { margin-left: var(--s1); }
}

/* Entre 641 y 1279 la IP cede el sitio. El corte NO esta en 1023: la
   lateral de la aplicacion se abre en 1024 y se lleva 244px, asi que el
   area util a 1024 (765px) es MENOR que a 900 (821px). MEDIDO con la IP
   presente a 1024: la columna de Detalle caía a 152px, las filas subian
   a 70-73px y la pagina a 3.952. Es la misma banda critica que ya
   aparecio en Negocios, Usuarios y Negocio form. */
@media (min-width: 768px) and (max-width: 1279px) {
  .act-lista th, .act-lista td { padding: var(--s2) var(--s3); }
  .act-lista th:nth-child(5),
  .act-lista td.act-ip { display: none; }
  .act-lista th:nth-child(1) { width: 30%; }
}

/* ---------- Telefono: tres niveles, no seis renglones ---------- */
@media (max-width: 767px) {
  /* Se anula el apilado generico de la seccion 14 SOLO en esta tabla.
     Ahi cada celda se convierte en un renglon con su etiqueta, y aqui
     eso daba 242px por registro y 12.408px de pagina.

     La fila pasa a ser una rejilla de dos columnas y tres renglones:

        accion .......... cuando
        quien .................. (a lo ancho)
        detalle ............ ip
  */
  .act-lista .tabla-datos tr {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: baseline;
    gap: 2px var(--s3);
    padding: var(--s3);
    margin-bottom: var(--s2);
  }
  .act-lista .tabla-datos td {
    display: block;
    border: none;
    padding: 0;
    text-align: left;
  }
  /* Fuera las etiquetas: en tres niveles la frase se lee sola. */
  .act-lista .tabla-datos td::before { content: none !important; }

  /* justify-content: flex-start es OBLIGATORIO aqui. La regla generica
     de la seccion 14 pone "display:flex; justify-content: space-between"
     en cada celda para separar la etiqueta del valor. Al quitar la
     etiqueta y dejar el flex, el nombre de la accion se iba al borde
     derecho de la ficha con un hueco de 96px detras del icono. */
  .act-lista td.act-accion  {
    grid-column: 1; grid-row: 1;
    display: flex; justify-content: flex-start; align-items: center;
    gap: var(--s2);
  }
  .act-lista td.act-cuando  { grid-column: 2; grid-row: 1; text-align: right; }
  .act-lista td.act-quien   { grid-column: 1 / -1; grid-row: 2; }
  .act-lista td.act-detalle { grid-column: 1; grid-row: 3; }
  .act-lista td.act-ip      { grid-column: 2; grid-row: 3; text-align: right; align-self: end; }

  /* El renglon del detalle desaparece cuando no hay ni detalle ni IP. */
  .act-lista td.act-detalle:empty,
  .act-lista td.act-ip:empty { display: none; }

  /* Tambien en telefono: quien y donde en UNA linea, fecha y hora en
     una. Con .act-negocio en su propio renglon la ficha subia a cuatro
     niveles, y el objetivo eran tres. */
  .act-lista .act-fecha, .act-lista .act-hora,
  .act-lista .act-usuario, .act-lista .act-negocio { display: inline; }
  .act-lista .act-hora { margin-left: 4px; }
  .act-lista .act-negocio::before { content: "·"; margin: 0 var(--s1); color: var(--text-3); }

  /* La IP no puede estirar su columna. MEDIDO: una IPv6 de 39
     caracteres se llevaba el ancho de la ficha y esa entrada subia a
     239px contra los 92 de las demas. Se recorta con puntos suspensivos
     y el valor completo queda en el title y en escritorio. */
  .act-lista td.act-ip {
    /* 130px: una IPv4 completa (255.255.255.255, 15 caracteres en
       monoespaciada de 12px) cabe entera; una IPv6 de 39 no, y se
       recorta. En porcentaje se recortaba tambien "127.0.0.1", que
       cabia de sobra. */
    max-width: 130px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  /* La marca de riesgo pasa al borde de la ficha entera. */
  .act-lista .tabla-datos tr.act-peligro  { box-shadow: inset 3px 0 0 var(--error), var(--sh-1); }
  .act-lista .tabla-datos tr.act-atencion { box-shadow: inset 3px 0 0 var(--warn),  var(--sh-1); }
  .act-lista td.act-accion { box-shadow: none; }

  .act-lista td.act-detalle { font-size: var(--fs-xs); }
}

/* ---------- Paginacion ---------- */
.act-paginacion { margin-top: var(--s4); }
/* .paginacion usa .btn-sm, que se queda en 34px. Aqui son los dos
   unicos controles para moverse por el registro. Acotado a esta
   pantalla: .btn-sm lo comparten muchas tablas ya cerradas. */
.act-paginacion .paginacion .btn { min-height: 40px; }


/* ---------------------------------------------------------------------
   41f. ADMIN: PERMISOS  (matriz de comparacion)
   ---------------------------------------------------------------------
   MEDIDO ANTES DE TOCAR NADA:

     A 1440x950
       - Dos tablas sin <thead> real.
       - "Accion" median 478px y "Empleado" 181: las tres columnas que se
         COMPARAN, con anchos distintos entre si.
       - Los 22 permisos concedidos usaban chip-normal (vocabulario de
         stock) y los denegados un guion gris suelto.
       - Los tres "Aplicar" a 34px de alto.

     A 390px
       - 3.534px de alto. La matriz se apilaba en 13 fichas de cuatro
         renglones y comparar los tres roles se volvia imposible: es lo
         unico para lo que existe una matriz.

   LA DECISION DE FONDO: la matriz NO se apila nunca. Scrollea en
   horizontal DENTRO de su caja, con la columna de la accion fija. Vale
   mas una matriz que se desplaza y se entiende que tres columnas
   microscopicas que caben pero no se leen.
   --------------------------------------------------------------------- */

/* ---------- Introduccion ---------- */
.perm-intro {
  display: flex;
  align-items: flex-start;
  gap: var(--s2);
  margin: 0 0 var(--s4);
  padding: var(--s3);
  border-radius: var(--r-sm);
  background: var(--surface-2);
  border: 1px solid var(--border);
  font-size: var(--fs-sm);
  line-height: var(--lh);
  color: var(--text-2);
}
.perm-intro svg { flex: none; margin-top: var(--s0); color: var(--text-3); }

.perm-matriz { margin-bottom: var(--s5); }

/* ---------- La matriz ---------- */
/* El scroll pertenece a ESTA caja, nunca al <body>. .tabla-wrap ya trae
   overflow-x: auto; aqui solo se le da a la tabla un ancho minimo para
   que las columnas no se aplasten, y entonces el scroll aparece solo
   cuando de verdad hace falta. */
.perm-tabla {
  /* 240 de la accion + 3 x 132 de rol. Por debajo de eso la comparacion
     deja de leerse, asi que a partir de ahi se desplaza. */
  min-width: 636px;
  /* separate y no collapse: con border-collapse: collapse los bordes de
     una celda sticky no se pintan al desplazarse y la columna fija
     queda sin su linea de separacion. */
  border-collapse: separate;
  border-spacing: 0;
}
.perm-tabla th,
.perm-tabla td { border-bottom: 1px solid var(--border); }
.perm-tabla tbody tr:last-child th,
.perm-tabla tbody tr:last-child td { border-bottom: none; }


/* Cabeceras de rol: nombre y para que sirve, centradas sobre su columna.
   min-width y no width: asi las tres reparten el sobrante en pantalla
   ancha en vez de dejarselo todo a la columna de la accion, que MEDIDA
   se quedaba con 735px para escribir "Entrar a su panel". */
.perm-col-rol {
  min-width: 132px;
  text-align: center;
  vertical-align: bottom;
}
.perm-rol-nombre {
  display: block;
  font-size: var(--fs-xs);
  font-weight: var(--peso-max);
  letter-spacing: var(--ls-etiqueta);
  text-transform: uppercase;
  color: var(--text);
}
.perm-rol-desc {
  display: block;
  margin-top: var(--s0);
  font-size: var(--fs-3xs);
  font-weight: 400;
  letter-spacing: 0;
  text-transform: none;
  color: var(--text-3);
}

/* ---------- La columna fija ---------- */
/* Es lo que hace que la matriz siga siendo una matriz cuando hay que
   desplazarla: el nombre de la accion no se pierde nunca de vista. */
.perm-col-accion {
  position: sticky;
  left: 0;
  z-index: 2;
  min-width: 240px;
  width: 40%;
  text-align: left;
  /* Fondo SOLIDO y por tokens: sin el, las celdas de rol se leerian
     por debajo al desplazarse, y en modo oscuro tiene que oscurecer
     con el resto. */
  background: var(--surface);
  /* Linea de separacion propia: marca donde acaba lo fijo y empieza lo
     que se mueve. Sin sombras grandes. */
  box-shadow: 1px 0 0 var(--border);
}
/* La cabecera de la columna fija se cruza con las dos direcciones: va
   por encima de todo lo demas y conserva el fondo de cabecera. */
.perm-tabla thead .perm-col-accion {
  z-index: 3;
  background: var(--surface-2);
}
.perm-tabla tbody .perm-col-accion {
  font-size: var(--fs-sm);
  font-weight: var(--peso-medio);
  color: var(--text);
  text-transform: none;
  letter-spacing: 0;
}

/* ---------- Celdas de permiso ---------- */
.perm-celda { text-align: center; }
.perm-marca {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border-radius: var(--r-full);
}
/* Concedido y denegado se distinguen por la FORMA -palomita o guion- y
   ademas por el color y el fondo. No depende solo del color. */
.perm-si .perm-marca { background: var(--ok-bg); color: var(--ok); }
.perm-no .perm-marca { color: var(--text-3); font-weight: var(--peso-max); }

/* Realce de la fila entera al pasarla: en una matriz de 13x3 ayuda a no
   perder el renglon al mirar la tercera columna. */
.perm-tabla tbody tr:hover th,
.perm-tabla tbody tr:hover td { background: var(--surface-2); }

/* ---------- Tabla de roles por usuario ---------- */
.perm-usr-nombre { display: block; font-weight: var(--peso-fuerte); overflow-wrap: anywhere; }
.perm-usr-correo { display: block; margin-top: var(--s0); font-size: var(--fs-xs); color: var(--text-2); overflow-wrap: anywhere; }
.perm-usr-negocio { font-size: var(--fs-sm); overflow-wrap: anywhere; }

.perm-usr-form {
  display: flex;
  align-items: center;
  gap: var(--s2);
  flex-wrap: wrap;
}
/* Etiqueta real para el <select>: antes no tenia ninguna y en una tabla
   de varias filas "Cambiar a" no basta para saber de quien es. Se pinta
   solo para lectores de pantalla. */
.perm-usr-rotulo {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
.perm-usr-form select { width: auto; flex: 1 1 150px; min-width: 130px; min-height: 40px; }
.perm-usr-form .btn { min-height: 40px; }

.perm-usr-yo {
  display: inline-flex;
  align-items: center;
  gap: var(--s1);
  min-height: 40px;
  font-size: var(--fs-xs);
  color: var(--text-3);
  white-space: nowrap;
}
.perm-usr-yo svg { flex: none; }

.perm-nota {
  margin: var(--s4) 0 0;
  font-size: var(--fs-xs);
  color: var(--text-3);
  line-height: var(--lh);
}

/* ---------- Reparto de la tabla de usuarios ---------- */
@media (min-width: 768px) {
  .perm-usuarios th:nth-child(1) { width: 32%; }
  .perm-usuarios th:last-child { width: 260px; }
}
/* Banda critica: la lateral se abre en 1024 y el area util a 1024
   (765px) es menor que a 900 (821px). El negocio cede el sitio: esta
   pantalla decide ROLES, y el negocio ya se ve en Usuarios. */
@media (min-width: 768px) and (max-width: 1279px) {
  .perm-usuarios th:nth-child(2),
  .perm-usuarios td.perm-usr-negocio { display: none; }
}

@media (max-width: 767px) {
  /* La tabla de usuarios SI se apila en fichas: son registros que se
     administran de uno en uno, no datos que se comparen entre si. Es
     justo lo contrario que la matriz. */
  .perm-usuarios td:first-child { display: block; text-align: left; }
  .perm-usuarios td:first-child::before { content: none; }
  .perm-usuarios .perm-usr-nombre { font-size: var(--fs-base); }
  .perm-usuarios .perm-usr-form { width: 100%; }
  .perm-usuarios .perm-usr-form select { flex: 1 1 auto; min-height: 44px; }
  .perm-usuarios .perm-usr-form .btn { min-height: 44px; }
  .perm-usr-yo { width: 100%; justify-content: flex-end; }

  /* La matriz mantiene sus medidas: aqui es donde el scroll horizontal
     hace su trabajo. Las celdas respiran un poco menos para que quepa
     mas matriz por pantalla antes de tener que desplazar. */
  .perm-tabla th, .perm-tabla td { padding: var(--s2); }
  .perm-col-accion { min-width: 190px; }
  .perm-col-rol { width: 108px; }
  .perm-tabla { min-width: 514px; }
}


/* ---------------------------------------------------------------------
   41g. ADMIN: INVENTARIOS  (lectura comparativa)
   Reutiliza .admin-lista de Negocios sin un solo cambio. NO usa
   .admin-barra: esta pantalla no tiene buscador ni acciones, y no se
   le inventan.

   MEDIDO ANTES DE TOCAR NADA (3 negocios, 6 productos):
     - Ninguna columna numerica llevaba .num. Las seis cifras de cada
       fila salian a la IZQUIERDA y sin tabular-nums: $6,331.00 y
       $648.00 no compartian ni el punto decimal, en una tabla cuyo
       unico trabajo es comparar cifras entre negocios.
     - Reparto plano: a 1440 el nombre del negocio se quedaba en 227px
       (20% de 1146) mientras "Valor de venta" se llevaba 197px y
       "Alertas" 117px para pintar una pastilla de 31px.
     - A 768 el nombre caia a 118px y la fila de PAPELERIA JUAN subia a
       74px por partir el nombre en dos renglones.
     - EN TELEFONO la barra de valor medía 0px de ancho: dentro de la
       celda apilada (display:flex; justify-content:space-between) un
       <div> sin base flexible colapsa. Ocupaba 8px de alto y no
       pintaba nada. Medido a 430, 390 y 360.
     - 286px de alto por negocio en telefono: siete renglones de
       etiqueta+valor del mismo peso, uno de ellos "ALERTAS: -".

   El area util NO crece con el ancho de la ventana. Medido:
     768 -> 704px    900 -> 836px    1024 -> 780px
     1280 -> 1036px  1440 -> 1196px
   A 1024 la lateral se abre y se lleva 244px, asi que hay MENOS sitio
   que a 900. Por eso el regimen de columnas se corta en 1280 y no en
   1024.

   EL PREFIJO ES .invp- Y NO .inv-. El area de cliente ya usa .inv-
   para el inventario de UN negocio (.inv-lista, .inv-item, .inv-pie,
   .inv-filtros, seccion 33). Son dos pantallas distintas que hablan de
   inventario y comparten media palabra; reutilizar el prefijo era
   pedir el mismo accidente que ya ocurrio cuando las reglas de
   columnas de Negocios se le aplicaron encima a Usuarios. La "p" es de
   plataforma: aqui se ve el inventario DE TODOS los negocios.
   --------------------------------------------------------------------- */

/* ---------- La celda de identidad ---------- */
.invp-nombre {
  display: block;
  font-weight: var(--peso-fuerte);
  overflow-wrap: anywhere;
}

/* Una fila entera de ceros sin explicacion parece un fallo de la
   pantalla. Con esto se lee como lo que es. */
.invp-sin-catalogo {
  display: block;
  margin-top: var(--s0);
  font-size: var(--fs-xs);
  color: var(--text-3);
}

/* ---------- Dinero ---------- */
/* La cifra a costo es la que ordena la tabla: es la que manda en la
   fila. La de venta es el mismo stock a otro precio, asi que acompana
   sin competir. */
.invp-costo-cifra {
  font-weight: var(--peso-fuerte);
  white-space: nowrap;
}
.invp-venta {
  color: var(--text-2);
  white-space: nowrap;
}

/* La barra, debajo de su propio numero.
   width:100% y display:block explicitos: es exactamente lo que le
   faltaba en telefono, donde heredaba un ancho de contenido nulo
   dentro de una celda flexible y se pintaba en 0px. */
.invp-barra {
  display: block;
  width: 100%;
  margin-top: 6px;
}
.invp-barra .barra { max-width: none; }

/* Minimo visible para el negocio que SI tiene valor. Es una regla de
   pintado, no de calculo: el ancho lo sigue decidiendo el porcentaje
   real. Sin esto, con un negocio que acapara el valor de la plataforma
   el resto de las barras caen por debajo de un pixel y un negocio con
   $28,120 se ve igual que uno con $0.00. */
.invp-barra-con-valor .barra span { min-width: 3px; }

/* ---------- Alertas ---------- */
/* El flex va en un <span> DENTRO de la celda, no en la celda: un <td>
   con display:flex deja de participar en el reparto de columnas y su
   contenido se sale sobre la vecina (ya paso en Negocios).

   Con dos avisos y la columna en su ancho minimo (132px) se apilan.
   Es la degradacion que se quiere: dos avisos se leen igual uno debajo
   de otro, y ese ancho lo necesita el nombre del negocio. */
.invp-alertas-caja {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s1);
}
/* "Sin alertas" se escribe, no se pinta como pastilla: es la AUSENCIA
   de un aviso. Con el tamano y el peso de un chip, veinte filas
   tranquilas gritarian mas que las dos que piden atencion. */
.invp-sin {
  color: var(--text-3);
  font-size: var(--fs-xs);
  white-space: nowrap;
}

/* ---------- La nota del pie ---------- */
/* Era un <p> suelto fuera de todo contenedor. No es decoracion:
   explica por que esta pantalla no tiene ni una accion. */
.invp-nota {
  display: flex;
  align-items: flex-start;
  gap: var(--s2);
  margin: var(--s4) 0 0;
  padding: var(--s3) var(--s4);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  font-size: var(--fs-xs);
  color: var(--text-2);
  line-height: var(--lh);
}
.invp-nota svg { flex: none; margin-top: 1px; color: var(--text-3); }

/* ---------- Reparto de la tabla ---------- */
/* Las reglas de COLUMNAS van con la clase de la pantalla y nunca con
   .admin-lista, que es la compartida: cuando Usuarios estreno
   .admin-lista, las reglas de Negocios se le aplicaron encima y perdia
   su columna de Estado.

   Se fijan las seis columnas de ancho conocido y el nombre se queda con
   el resto. Antes el nombre iba a un 20% plano: 227px a 1440 y 196px a
   1280, con "Valor de venta" llevandose 197 y "Alertas" 117 para
   pintar una pastilla de 31px.

   "Alertas" se queda en el minimo que pide su encabezado (132px) y NO
   en los 176 que necesitarian dos pastillas en una linea. Medido: con
   176 el nombre bajaba a 212px a 1280. Es mejor que los dos avisos se
   apilen -son dos avisos, se leen igual uno debajo de otro- y que ese
   ancho se lo quede la identidad del negocio, que es lo que no se
   recorta. Con un solo tipo de alerta, que es el caso corriente, la
   fila no crece nada. */
@media (min-width: 1280px) {
  .lista-inventarios th:nth-child(2) { width: 104px; }
  .lista-inventarios th:nth-child(3) { width: 88px; }
  .lista-inventarios th:nth-child(4) { width: 96px; }
  .lista-inventarios th:nth-child(5) { width: 152px; }
  .lista-inventarios th:nth-child(6) { width: 140px; }
  .lista-inventarios th:nth-child(7) { width: 132px; }
}

/* Banda 641-1279: la critica. Con 780px de area util a 1024 no caben
   siete columnas sin recortar el nombre a la mitad.

   Cede "Valor de venta". De los dos importes es el secundario: el
   ORDEN de la tabla lo decide el valor a costo, el total de venta esta
   arriba en las metricas y el dato por negocio vuelve en cuanto hay
   ancho. Ceder una columna de dinero es preferible a partir el nombre
   del negocio en tres renglones, que es lo que pasaba a 768. */
@media (min-width: 768px) and (max-width: 1279px) {
  .lista-inventarios th, .lista-inventarios td { padding: var(--s2) var(--s3); }

  .lista-inventarios th:nth-child(6),
  .lista-inventarios td.invp-venta { display: none; }

  .lista-inventarios th:nth-child(2) { width: 104px; }
  .lista-inventarios th:nth-child(3) { width: 84px; }
  .lista-inventarios th:nth-child(4) { width: 92px; }
  .lista-inventarios th:nth-child(5) { width: 150px; }
  .lista-inventarios th:nth-child(7) { width: 132px; }
}

/* "Productos" cede en los DOS tramos donde el area util baja de 800px,
   que no son contiguos:

     768 -> 704px     900 -> 836px     1024 -> 780px

   A 1024 la lateral se abre y se lleva 244px, asi que hay menos sitio
   que a 900. Entre 900 y 1023 la columna cabe y se queda; a los lados
   no. Medido con la columna puesta: el nombre se quedaba en 140px a
   1024 y "PAPELERIA JUAN" salia partido en dos renglones.

   Cuantos articulos distintos tiene el catalogo es contexto; cuanto
   dinero hay parado y si algo esta agotado es el motivo de la
   pantalla. El total sigue arriba, en las metricas. */
@media (min-width: 768px) and (max-width: 899px),
       (min-width: 1024px) and (max-width: 1279px) {
  .lista-inventarios th:nth-child(3),
  .lista-inventarios td:nth-child(3) { display: none; }
}

/* ---------- Telefono ---------- */
/* La tabla ya se apila en fichas (seccion 14). Lo que aqui no servia
   era el reparto DENTRO de la ficha: siete renglones del mismo peso,
   286px por negocio, y uno de esos renglones decia "ALERTAS: -".

   La fila pasa a rejilla de dos columnas. No es una tarjeta nueva ni
   otro marcado: es la misma ficha de .tabla-datos colocada por pares,
   porque los datos vienen de dos en dos (cuantos productos / cuantas
   piezas, cuanto a costo / cuanto a venta).

     nombre        | estado
     productos     | unidades
     valor a costo | valor de venta
     alertas (solo si las hay) */
@media (max-width: 767px) {
  /* minmax(0, 1fr) y no 1fr a secas: "1fr" es en realidad
     minmax(auto, 1fr), asi que una celda con contenido largo empuja su
     columna por encima de la mitad. MEDIDO a 360 con un importe de
     $169,095,200.00: la columna de venta se llevaba 175px y la de
     costo se quedaba en 158. Las dos mitades tienen que ser mitades. */
  .lista-inventarios .tabla-datos tr {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  /* La identidad manda dentro de la ficha: a la izquierda y con su
     tamano, no como un valor mas alineado a la derecha. */
  .lista-inventarios .tabla-datos td:first-child {
    display: block;
    text-align: left;
  }
  .lista-inventarios .tabla-datos td:first-child::before { content: none; }
  .lista-inventarios .invp-nombre { font-size: var(--fs-base); }

  /* El estado comparte renglon con el nombre: es su estado, no un dato
     aparte, y el chip ya dice lo que la etiqueta repetiria. */
  .lista-inventarios .tabla-datos td:nth-child(2) { justify-content: flex-end; }
  .lista-inventarios .tabla-datos td:nth-child(2)::before { content: none; }

  /* El importe a costo y su barra: etiqueta y cifra en la primera
     linea, barra a lo ancho en la segunda. flex-basis del 100% y no
     un salto de linea: dentro de un contenedor flexible es la unica
     forma de que un hijo ocupe su propio renglon.

     Aqui es donde la barra deja de medir 0px. */
  .lista-inventarios .tabla-datos td.invp-costo { flex-wrap: wrap; }
  .lista-inventarios .tabla-datos td.invp-costo .invp-barra {
    flex: 0 0 100%;
    margin-top: 6px;
  }

  /* Los dos importes comparten renglon, asi que cada uno dispone de la
     mitad de 366px menos el relleno: 159px. MEDIDO con el encabezado
     entero: "VALOR A COSTO" y "$6,331.00" no caben en una linea, la
     cifra se iba al renglon de abajo -y ademas a la izquierda, porque
     un unico elemento en una linea con space-between se pega al
     principio- y la celda crecia a tres lineas contra una de su
     vecina.

     En telefono la columna no esta ahi arriba para dar contexto, asi
     que la etiqueta tiene que decir lo justo. Es la MISMA celda con el
     mismo data-label; lo que cambia es lo que se pinta. */
  .lista-inventarios .tabla-datos td.invp-costo,
  .lista-inventarios .tabla-datos td.invp-venta,
  .lista-inventarios .tabla-datos td:nth-child(3),
  .lista-inventarios .tabla-datos td:nth-child(4) { gap: var(--s2); }

  .lista-inventarios .tabla-datos td.invp-costo::before { content: 'Costo'; }
  .lista-inventarios .tabla-datos td.invp-venta::before { content: 'Venta'; }

  /* Con la etiqueta corta el importe corriente cabe en una linea. El
     que NO cabe en ningun ancho de telefono es el extremo: MEDIDO a
     360, "$130,276,864.00" pide 111px y en la mitad de la ficha
     quedan 86 despues de la etiqueta. Ese importe tiene que partir
     renglon, y cuando parte no puede irse a la izquierda.

     space-between reparte los HUECOS, y en una linea con un solo
     elemento no hay hueco que repartir: la cifra se pegaba al
     principio, debajo de su etiqueta y en el lado contrario al resto
     de las cifras de la ficha. Con flex-end la linea termina a la
     derecha siempre, y el margen automatico de la etiqueta mantiene el
     reparto de la primera linea exactamente como estaba. */
  .lista-inventarios .tabla-datos td.invp-costo,
  .lista-inventarios .tabla-datos td.invp-venta { justify-content: flex-end; }
  .lista-inventarios .tabla-datos td.invp-costo::before,
  .lista-inventarios .tabla-datos td.invp-venta::before { margin-right: auto; }
  .lista-inventarios .tabla-datos td.invp-venta { flex-wrap: wrap; }

  /* "ALERTAS: —" gastaba un renglon entero de 39px para no decir nada,
     en la mayoria de las fichas. Cuando hay alertas, se ve y ocupa el
     ancho completo. */
  .lista-inventarios .tabla-datos td.invp-alertas-cero { display: none; }
  .lista-inventarios .tabla-datos td.invp-alertas {
    grid-column: 1 / -1;
    justify-content: space-between;
  }
  /* En el renglon completo si caben en linea, agrupados a la derecha
     junto a su etiqueta. */
  .lista-inventarios .tabla-datos .invp-alertas-caja {
    flex-wrap: nowrap;
    justify-content: flex-end;
  }

  /* Con la fila de alertas oculta, el ultimo renglon visible es el de
     los importes: son ellos los que se quedan sin linea inferior.
     :has() ya se usa en el reparto de .metricas, no se estrena aqui. */
  .lista-inventarios .tabla-datos tr:has(.invp-alertas-cero) td.invp-costo,
  .lista-inventarios .tabla-datos tr:has(.invp-alertas-cero) td.invp-venta {
    border-bottom: none;
  }

  /* El estado vacio ocupa la ficha entera: sin esto, la rejilla de dos
     columnas lo dejaria en la mitad izquierda. */
  .lista-inventarios .tabla-datos td.vacio { grid-column: 1 / -1; }
}


/* ---------------------------------------------------------------------
   41h. ADMIN: REPORTES  (analisis, no CRUD)
   Reutiliza .admin-lista de Negocios, .caja-desglose de Caja y el
   componente de grafica, sin cambiar ninguno.

   MEDIDO ANTES DE TOCAR NADA (3 negocios, 11 ventas completadas):
     - Cuatro metricas del mismo peso: a 1440, cuatro cajas de 278px.
       "Ventas: 11" pesaba igual que "$897.00".
     - Cero graficas, teniendo ui_grafica_barras() en el sistema.
     - Reparto de columnas absurdo: a 1440 "Estado" se llevaba 205px
       -el 18% de la tabla- para pintar una pastilla de 58, mientras el
       nombre del negocio se quedaba en 359. A 1024, 130 contra 229.
     - Ninguna columna numerica con .num: "11", "$897.00" y "$158.00"
       alineados a la izquierda y sin tabular-nums, en una tabla que
       existe para comparar cifras entre negocios.
     - LAS CINCO PESTANAS DE PERIODO MEDIAN 38px de alto. Son los
       unicos controles de la pantalla y estaban bajo el minimo tactil.
     - LA BARRA DE INGRESOS MEDIA 0px EN TELEFONO: 3 de 3 a 390px.
       Mismo colapso que se corrigio en Inventarios.
     - Filas de 208px por negocio a 390px.

   EL PREFIJO ES .admrep-. No .rep-, que ya es del informe de CLIENTE
   (.rep-cols, .rep-ranking, .rep-item, .rep-rango, seccion 38), ni
   .inv-/.invp-, que son de inventario. Tres pantallas distintas hablan
   de reportes en este sistema y no pueden compartir nombres de clase.
   --------------------------------------------------------------------- */

/* ---------- La barra de periodo ---------- */
.admrep-periodo {
  display: flex;
  align-items: center;
  gap: var(--s3);
  flex-wrap: wrap;
  margin-bottom: var(--s4);
}
.admrep-periodo-rotulo {
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: .4px;
  font-weight: 600;
  color: var(--text-2);
  flex: none;
}
.admrep-periodo-tabs { min-width: 0; }
.admrep-periodo-tabs .tabs { margin: 0; }


@media (max-width: 767px) {
  /* En telefono el rotulo va en su propio renglon y las cinco pestanas
     se reparten en dos. NO se ponen a desplazarse de lado: .tabs es
     flex-wrap:wrap en todo el sistema, y en un selector de periodo
     interesa ver las cinco opciones a la vez, no descubrirlas
     arrastrando. Cuesta 128px de alto y es el control principal de la
     pantalla. */
  .admrep-periodo { gap: var(--s2); }
  .admrep-periodo-rotulo { flex: 1 0 100%; }
  .admrep-periodo-tabs { width: 100%; }
}

/* ---------- Tendencia + indicadores ---------- */
/* Dos columnas desiguales: la grafica necesita ancho para que las
   etiquetas del eje no se pisen, y el desglose de estados son cuatro
   renglones de texto.

   Se abre en 1024 y no antes porque por debajo el area util cae a
   704px (medido a 768) y dos columnas dejarian la grafica en 350px.
   A 1024 el area util es de 780: 2fr son ~500px de grafica, que es
   donde el componente ya se lee bien. */
.admrep-cols {
  display: grid;
  gap: var(--s4);
  align-items: start;
  margin-bottom: var(--s5);
}
@media (min-width: 1024px) {
  .admrep-cols { grid-template-columns: minmax(0, 2fr) minmax(0, 1fr); }
}

/* Pie de texto de la grafica: lo que el <title> de cada barra dice al
   pasar el raton, dicho tambien en texto. La grafica no puede ser la
   unica forma de leer el dato. */
.admrep-gr-pie {
  margin: var(--s3) 0 0;
  font-size: var(--fs-xs);
  color: var(--text-2);
}

/* El desglose de estados reutiliza .caja-desglose tal cual. Lo unico
   propio es que aqui la etiqueta es una pastilla, no texto, y una
   pastilla no se alinea sobre la linea base como una palabra. */
.admrep-estados li { align-items: center; }
.admrep-estados .es-total { align-items: baseline; }

/* ---------- La tabla de detalle ---------- */
.admrep-nombre {
  display: block;
  font-weight: var(--peso-fuerte);
  overflow-wrap: anywhere;
}
.admrep-sin-ventas {
  display: block;
  margin-top: var(--s0);
  font-size: var(--fs-xs);
  color: var(--text-3);
}

.admrep-ingresos-cifra {
  font-weight: var(--peso-fuerte);
  white-space: nowrap;
}
.admrep-utilidad { white-space: nowrap; color: var(--text-2); }
/* Una utilidad negativa no es un dato mas: se vendio por debajo del
   costo. El color no va solo: la cifra ya lleva el signo menos. */
.admrep-utilidad.es-negativa { color: var(--error); font-weight: var(--peso-fuerte); }

/* La barra, debajo de su propio numero. width y display explicitos:
   es exactamente lo que le faltaba en telefono, donde heredaba un
   ancho de contenido nulo dentro de una celda flexible y se pintaba
   en 0px. */
.admrep-barra {
  display: block;
  width: 100%;
  margin-top: 6px;
}
.admrep-barra .barra { max-width: none; }
/* Minimo visible para el negocio que SI facturo. Regla de pintado, no
   de calculo: el ancho lo sigue decidiendo el porcentaje real. Sin
   esto, con un negocio que acapara la facturacion los demas caen por
   debajo de un pixel y facturar poco se ve igual que no facturar. */
.admrep-barra-con-valor .barra span { min-width: 3px; }

/* ---------- Reparto de columnas ---------- */
/* Las reglas de COLUMNAS van con la clase de la pantalla y nunca con
   .admin-lista, que es la compartida.

   Cinco columnas y solo tres numericas: cabe todo en los diez anchos y
   no hace falta que ninguna ceda. Lo que si hacia falta era acotar
   "Estado", que con reparto automatico se llevaba 205px a 1440 para
   pintar una pastilla de 58. */
@media (min-width: 1280px) {
  .lista-admrep th:nth-child(2) { width: 116px; }
  .lista-admrep th:nth-child(3) { width: 96px; }
  .lista-admrep th:nth-child(4) { width: 190px; }
  .lista-admrep th:nth-child(5) { width: 150px; }
}

/* Banda 641-1279: la critica. El area util NO crece con la ventana:
     768 -> 704px    900 -> 836px    1024 -> 780px    1280 -> 1036px
   A 1024 la lateral se abre y se lleva 244px, asi que hay menos sitio
   que a 900. Las cinco columnas siguen cabiendo; solo respiran menos y
   la de ingresos se ajusta al ancho de su cifra. */
@media (min-width: 768px) and (max-width: 1279px) {
  .lista-admrep th, .lista-admrep td { padding: var(--s2) var(--s3); }
  .lista-admrep th:nth-child(2) { width: 104px; }
  .lista-admrep th:nth-child(3) { width: 84px; }
  .lista-admrep th:nth-child(4) { width: 160px; }
  .lista-admrep th:nth-child(5) { width: 128px; }
}

/* Por debajo de 900 el area util cae a 689px y "Ventas" cede el sitio.
   MEDIDO con la columna puesta: el nombre se quedaba en 148px a 768 y
   "Sin ventas en el periodo" partia en dos renglones, subiendo la fila
   de 54 a 78px. Sin ella el nombre sube a ~247.

   Cuantos tickets cobro cada negocio es contexto; cuanto facturo y
   cuanta utilidad dejo es el motivo de la pantalla. El total de ventas
   del periodo sigue arriba, en las metricas. */
@media (min-width: 768px) and (max-width: 899px) {
  .lista-admrep th:nth-child(3),
  .lista-admrep td:nth-child(3) { display: none; }
}

/* ---------- Telefono ---------- */
/* La tabla ya se apila en fichas (seccion 14). Lo que no servia era el
   reparto DENTRO de la ficha: cinco renglones del mismo peso y 208px
   por negocio.

   La fila pasa a rejilla de dos columnas, como en Inventarios:

     nombre    | estado
     ingresos  | utilidad
     ventas (renglon entero)

   "Ventas" se manda al final con order. Es el unico dato que no tiene
   pareja -los dos importes van juntos, el nombre va con su estado- y
   en el DOM viene en medio. No hay nada enfocable en estas celdas y
   cada una lleva su propio data-label, asi que el orden de lectura no
   se pierde: solo se coloca donde no deja un hueco. */
@media (max-width: 767px) {
  .lista-admrep .tabla-datos tr {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  /* La identidad manda dentro de la ficha: a la izquierda y con su
     tamano, no como un valor mas alineado a la derecha. */
  .lista-admrep .tabla-datos td:first-child {
    display: block;
    text-align: left;
  }
  .lista-admrep .tabla-datos td:first-child::before { content: none; }
  .lista-admrep .admrep-nombre { font-size: var(--fs-base); }

  /* El estado comparte renglon con el nombre: es su estado, y el chip
     ya dice lo que la etiqueta repetiria. */
  .lista-admrep .tabla-datos td:nth-child(2) { justify-content: flex-end; }
  .lista-admrep .tabla-datos td:nth-child(2)::before { content: none; }

  /* "Ventas" al final, a lo ancho. */
  .lista-admrep .tabla-datos td:nth-child(3) {
    order: 5;
    grid-column: 1 / -1;
    border-bottom: none;
  }

  /* Etiquetas cortas: con "INGRESOS" entera y "$130,276,864.00" al
     lado no cabe nada en media ficha. Es la MISMA celda con el mismo
     data-label; lo que cambia es lo que se pinta. */
  .lista-admrep .tabla-datos td.admrep-ingresos,
  .lista-admrep .tabla-datos td.admrep-utilidad { gap: var(--s2); }

  /* space-between reparte los HUECOS, y en una linea con un solo
     elemento no hay hueco que repartir: un importe que parte renglon
     se pegaba al principio, en el lado contrario al resto de las
     cifras. Con flex-end la linea termina a la derecha siempre, y el
     margen automatico de la etiqueta deja la primera linea igual. */
  .lista-admrep .tabla-datos td.admrep-ingresos,
  .lista-admrep .tabla-datos td.admrep-utilidad { justify-content: flex-end; flex-wrap: wrap; }
  .lista-admrep .tabla-datos td.admrep-ingresos::before,
  .lista-admrep .tabla-datos td.admrep-utilidad::before { margin-right: auto; }

  .lista-admrep .tabla-datos td.admrep-ingresos .admrep-barra {
    flex: 0 0 100%;
    margin-top: 6px;
  }

  /* El estado vacio ocupa la ficha entera: sin esto, la rejilla de dos
     columnas lo dejaria en la mitad izquierda. */
  .lista-admrep .tabla-datos td.vacio { grid-column: 1 / -1; }
}

/* ---------- La nota del pie ---------- */
.admrep-nota {
  display: flex;
  align-items: flex-start;
  gap: var(--s2);
  margin: var(--s4) 0 0;
  padding: var(--s3) var(--s4);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  font-size: var(--fs-xs);
  color: var(--text-2);
  line-height: var(--lh);
}
.admrep-nota svg { flex: none; margin-top: 1px; color: var(--text-3); }


/* ---------------------------------------------------------------------
   41i. ADMIN: SEGURIDAD  (veredicto + revisiones)
   Reutiliza el panel, la barra de progreso y los chips semanticos sin
   cambiar ninguno. NO usa .admin-lista: aqui no hay ni una tabla.

   MEDIDO ANTES DE TOCAR NADA (22 revisiones: 18 correctas, 0 fallas,
   4 avisos):
     - Cuatro metricas del mismo peso: a 1440, cuatro cajas de 274px.
       "Revisiones 22" pesaba igual que "Fallas 0".
     - El veredicto era un <div class="aviso"> de una linea, el CUARTO
       bloque de la pagina.
     - SIETE tablas de maquetacion sin una sola <th>. Como app.js apila
       una tabla en telefono solo si encuentra fila de encabezados,
       estas NO se apilaban: a 390px quedaban tres columnas estrechas,
       el nombre de la prueba partido en cuatro renglones, filas de
       86px de media y hasta 161px, y una pagina de 3.144px.
     - Chips de STOCK para estados de seguridad: 18 'chip-normal' y
       4 'chip-bajo'.
     - "Lista para publicar": enlace de 23px en escritorio, 18px en
       telefono. Unica accion de la pantalla.

   El prefijo es .admseg-. No .seg-, que no existe pero es demasiado
   corto para no chocar con nada en un archivo de 9.700 lineas.
   --------------------------------------------------------------------- */

/* ---------- El veredicto ---------- */
/* Tres zonas: marca de estado, lectura, y entorno. En escritorio el
   entorno va a la derecha con su propio borde; por debajo de 900 se
   pone debajo, porque partir 700px entre dos bloques de texto deja los
   dos ilegibles. */
.admseg-veredicto {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--s3) var(--s4);
  align-items: start;
  background: var(--surface);
  border: 1px solid var(--border);
  border-left: 4px solid var(--border-2);
  border-radius: var(--r-lg);
  padding: var(--s5);
  margin-bottom: var(--s4);
  box-shadow: var(--sh-1);
}

/* El filo de color y el icono los pone el ESTADO. El color nunca va
   solo: al lado hay un titulo escrito y, en cada revision, un chip con
   la palabra. */
.admseg-ok    { border-left-color: var(--ok); }
.admseg-warn  { border-left-color: var(--warn); }
.admseg-error { border-left-color: var(--error); }

.admseg-veredicto-icono {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 48px;
  border-radius: var(--r-md);
  flex: none;
}
.admseg-ok    .admseg-veredicto-icono { background: var(--ok-bg);    color: var(--ok); }
.admseg-warn  .admseg-veredicto-icono { background: var(--warn-bg);  color: var(--warn); }
.admseg-error .admseg-veredicto-icono { background: var(--error-bg); color: var(--error); }

.admseg-veredicto-txt { min-width: 0; }

.admseg-veredicto-titulo {
  margin: 0;
  font-size: var(--fs-2xl);
  line-height: 1.15;
  letter-spacing: var(--ls-titulo);
  color: var(--text);
}
.admseg-veredicto-desc {
  margin: var(--s1) 0 0;
  font-size: var(--fs-sm);
  color: var(--text-2);
  line-height: var(--lh);
}

.admseg-veredicto-barra { margin-top: var(--s3); max-width: 420px; }
.admseg-veredicto-barra .barra { max-width: none; }
.admseg-veredicto-pie {
  display: block;
  margin-top: var(--s1);
  font-size: var(--fs-xs);
  color: var(--text-3);
}

/* ---------- El entorno, dentro del veredicto ---------- */
/* Antes eran DOS <div class="aviso"> apilados, uno con el entorno y
   otro con el resultado. El entorno decide con que severidad se juzgan
   la mitad de las revisiones: pertenece al veredicto, no a un bloque
   suelto encima de el. */
.admseg-entorno {
  grid-column: 1 / -1;
  display: flex;
  align-items: center;
  gap: var(--s2);
  flex-wrap: wrap;
  padding-top: var(--s4);
  border-top: 1px solid var(--border);
}
.admseg-entorno-rotulo {
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: .4px;
  font-weight: 600;
  color: var(--text-2);
}
.admseg-entorno-desc {
  flex: 1 1 260px;
  min-width: 0;
  margin: 0;
  font-size: var(--fs-xs);
  color: var(--text-2);
  line-height: var(--lh);
}
/* Ya no hace falta devolverle el ancho al boton -la base mide por
   contenido desde G3-, pero si evitar que el flex lo estire. */
.admseg-entorno .btn { min-height: 40px; flex: none; }

@media (min-width: 900px) {
  /* Con sitio, el entorno se va a su propia columna a la derecha. */
  .admseg-veredicto { grid-template-columns: auto minmax(0, 1fr) minmax(0, 300px); }
  .admseg-entorno {
    grid-column: 3;
    grid-row: 1;
    align-content: start;
    align-items: flex-start;
    padding-top: 0;
    padding-left: var(--s5);
    border-top: none;
    border-left: 1px solid var(--border);
  }
  .admseg-entorno-desc { flex-basis: 100%; }
  .admseg-entorno .btn { margin-top: var(--s1); }
}

@media (max-width: 767px) {
  .admseg-veredicto { padding: var(--s4); gap: var(--s3); }
  .admseg-veredicto-titulo { font-size: var(--fs-xl); }
  .admseg-veredicto-icono { width: 40px; height: 40px; }
  /* La accion a lo ancho, que es donde llega el pulgar. */
  .admseg-entorno .btn { width: 100%; min-height: 44px; }
}

/* ---------- Lista de revisiones ---------- */
/* NO es una tabla y no se le pone <thead>: icono, nombre y detalle no
   son tres dimensiones que se comparen entre filas, son una afirmacion
   con su estado. Ademas, como lista se apila sola en telefono; como
   tabla sin <th> no se apilaba en absoluto. */
.admseg-lista { list-style: none; margin: 0; padding: 0; }

.admseg-check {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: start;
  gap: var(--s3);
  padding: var(--s3) var(--s4);
  border-bottom: 1px solid var(--border);
}
.admseg-check:last-child { border-bottom: none; }

.admseg-check-icono {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border-radius: var(--r-full);
  flex: none;
}
.es-ok    .admseg-check-icono { background: var(--ok-bg);    color: var(--ok); }
.es-aviso .admseg-check-icono { background: var(--warn-bg);  color: var(--warn); }
.es-falla .admseg-check-icono { background: var(--error-bg); color: var(--error); }

.admseg-check-txt { min-width: 0; }

.admseg-check-titulo {
  display: block;
  font-weight: var(--peso-fuerte);
  line-height: 1.35;
  overflow-wrap: anywhere;
}
.admseg-check-detalle {
  display: block;
  margin-top: var(--s0);
  font-size: var(--fs-sm);
  color: var(--text-2);
  line-height: var(--lh);
  overflow-wrap: anywhere;
}
/* Solo aparece en el bloque de "Requiere atencion", donde la revision
   se ha sacado de su grupo y hay que decir de cual venia. */
.admseg-check-grupo {
  display: inline-block;
  margin-top: var(--s1);
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: .4px;
  color: var(--text-3);
}

.admseg-check-estado { flex: none; }

/* Una revision que no esta bien no se distingue solo por el chip: el
   renglon entero cambia de fondo. Es el mismo dato dicho tres veces
   -fondo, icono y palabra- para que no dependa del color. */
.es-aviso { background: var(--warn-bg); }
.es-falla { background: var(--error-bg); }

@media (max-width: 767px) {
  /* En telefono el chip comparte renglon con el TITULO y el detalle
     pasa por debajo a lo ancho. Las tres columnas se mantienen, pero
     solo el titulo compite con el chip; el detalle, que es el texto
     largo, se queda con la ficha entera.

     MEDIDO con el chip en su propio renglon: cada revision subia a
     111px de media y la pagina a 4.634px en 390, un 47% mas larga que
     la version anterior. Con el chip arriba, 26 revisiones dejan de
     gastar un renglon cada una.

     display:contents sube el titulo y el detalle a la rejilla de la
     ficha: sin eso van dentro de .admseg-check-txt y no se pueden
     colocar en filas distintas. */
  .admseg-check {
    grid-template-columns: auto minmax(0, 1fr) auto;
    gap: var(--s1) var(--s3);
    padding: var(--s3);
  }
  .admseg-check-txt { display: contents; }
  .admseg-check-titulo { grid-column: 2; grid-row: 1; }
  .admseg-check-estado { grid-column: 3; grid-row: 1; }
  .admseg-check-detalle { grid-column: 2 / -1; grid-row: 2; margin-top: 0; }
  .admseg-check-grupo   { grid-column: 2 / -1; grid-row: 3; margin-top: 0; }
}

/* ---------- Los siete grupos ---------- */
/* Dos columnas desde 1280, NO desde 1024.

   MEDIDO con el corte en 1024: a esa anchura el area util es de 765px
   -la lateral se abre y se lleva 244- asi que cada panel se quedaba en
   350px y el renglon de una revision subia de 74 a 145px de alto, con
   el detalle partido en cuatro lineas. A 1280 el area util es de
   1.021px y cada panel sale a 478.

   1024 no significa "mas espacio": significa que se abrio la lateral.

   align-items:start para que un grupo de dos revisiones no se estire a
   la altura del de cinco que tiene al lado. */
.admseg-grupos {
  display: grid;
  gap: var(--s4);
  align-items: start;
  margin-bottom: var(--s5);
}
@media (min-width: 1280px) {
  .admseg-grupos { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* ---------- Limites de la revision ---------- */
.admseg-limites {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--s3);
}
.admseg-limites li {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--s2);
  align-items: start;
  font-size: var(--fs-sm);
  color: var(--text-2);
  line-height: var(--lh);
}
.admseg-limites svg { margin-top: var(--s0); color: var(--text-3); }
.admseg-limites code {
  font-size: var(--fs-xs);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  padding: 1px var(--s1);
}


/* ---------------------------------------------------------------------
   41j. ADMIN: PRODUCCION  (lista para publicar)
   Hermana de 41i: las dos pantallas contestan la misma pregunta desde
   dos lados -como esta el sistema hoy, que falta para publicarlo- y por
   eso comparten composicion: veredicto dominante, cifras de apoyo y el
   detalle agrupado debajo.

   MEDIDO ANTES DE TOCAR NADA (9 puntos, 0 cubiertos en este equipo):
     - NUEVE tarjetas EXACTAMENTE IGUALES: a 1440, nueve cajas de
       1133 x 102px, con el mismo chip y el mismo peso.
     - LINEAS DE 1133px. El parrafo de detalle ocupaba el ancho util
       entero; a esa medida el ojo pierde el principio del renglon
       siguiente.
     - Tres KPIs de 370px, uno de ellos ("Puntos: 9") no era un
       indicador sino el tamano de la lista.
     - Los NUEVE chips eran 'chip-bajo', vocabulario de STOCK para
       estados administrativos.
     - "Volver a la auditoria de seguridad": enlace de 20px de alto,
       unica accion de la pantalla.

   No se repite el CSS de 41i con otro nombre porque las piezas no son
   iguales: aqui cada punto lleva ademas una linea de "estado actual"
   que en Seguridad no existe, y el bloque va en dos paneles y no en
   siete. Cuando la pasada global decida extraer el patron, se unifican
   los dos.
   --------------------------------------------------------------------- */

/* ---------- El veredicto ---------- */
.admprod-veredicto {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--s3) var(--s4);
  align-items: start;
  background: var(--surface);
  border: 1px solid var(--border);
  border-left: 4px solid var(--border-2);
  border-radius: var(--r-lg);
  padding: var(--s5);
  margin-bottom: var(--s4);
  box-shadow: var(--sh-1);
}

/* El filo y el icono los pone el ESTADO, nunca solo el color: al lado
   hay un titulo escrito y en cada punto un chip con la palabra. */
.admprod-ok    { border-left-color: var(--ok); }
.admprod-info  { border-left-color: var(--info); }
.admprod-error { border-left-color: var(--error); }

.admprod-veredicto-icono {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 48px;
  border-radius: var(--r-md);
  flex: none;
}
.admprod-ok    .admprod-veredicto-icono { background: var(--ok-bg);    color: var(--ok); }
.admprod-info  .admprod-veredicto-icono { background: var(--info-bg);  color: var(--info); }
.admprod-error .admprod-veredicto-icono { background: var(--error-bg); color: var(--error); }

.admprod-veredicto-txt { min-width: 0; }

.admprod-veredicto-titulo {
  margin: 0;
  font-size: var(--fs-2xl);
  line-height: 1.15;
  letter-spacing: var(--ls-titulo);
  color: var(--text);
}
/* La explicacion del veredicto es texto seguido: se acota a una medida
   de lectura. Sin esto heredaba los 1.133px del contenedor. */
.admprod-veredicto-desc {
  margin: var(--s1) 0 0;
  max-width: 62ch;
  font-size: var(--fs-sm);
  color: var(--text-2);
  line-height: var(--lh);
}

.admprod-veredicto-barra { margin-top: var(--s3); max-width: 420px; }
.admprod-veredicto-barra .barra { max-width: none; }
.admprod-veredicto-pie {
  display: block;
  margin-top: var(--s1);
  font-size: var(--fs-xs);
  color: var(--text-3);
}

/* ---------- El entorno, dentro del veredicto ---------- */
.admprod-entorno {
  grid-column: 1 / -1;
  display: flex;
  align-items: center;
  gap: var(--s2);
  flex-wrap: wrap;
  padding-top: var(--s4);
  border-top: 1px solid var(--border);
}
.admprod-entorno-rotulo {
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: .4px;
  font-weight: 600;
  color: var(--text-2);
}
.admprod-entorno-desc {
  flex: 1 1 260px;
  min-width: 0;
  margin: 0;
  font-size: var(--fs-xs);
  color: var(--text-2);
  line-height: var(--lh);
}
/* Ya no hace falta devolverle el ancho al boton -la base mide por
   contenido desde G3-, pero si evitar que el flex lo estire. */
.admprod-entorno .btn { min-height: 40px; flex: none; }

@media (min-width: 900px) {
  .admprod-veredicto { grid-template-columns: auto minmax(0, 1fr) minmax(0, 300px); }
  .admprod-entorno {
    grid-column: 3;
    grid-row: 1;
    align-content: start;
    align-items: flex-start;
    padding-top: 0;
    padding-left: var(--s5);
    border-top: none;
    border-left: 1px solid var(--border);
  }
  .admprod-entorno-desc { flex-basis: 100%; }
  .admprod-entorno .btn { margin-top: var(--s1); }
}

@media (max-width: 767px) {
  .admprod-veredicto { padding: var(--s4); gap: var(--s3); }
  .admprod-veredicto-titulo { font-size: var(--fs-xl); }
  .admprod-veredicto-icono { width: 40px; height: 40px; }
  .admprod-entorno .btn { width: 100%; min-height: 44px; }
}

/* ---------- Los dos bloques ---------- */
/* Dos columnas desde 1280 y no desde 1024: a 1024 el area util es de
   765px -la lateral se abre y se lleva 244- y cada bloque se quedaria
   en 350px para un parrafo de detalle de tres lineas. Es la misma
   medida que decidio el corte en Seguridad. */
.admprod-bloques {
  display: grid;
  gap: var(--s4);
  align-items: start;
  margin-bottom: var(--s5);
}
@media (min-width: 1280px) {
  .admprod-bloques { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* ---------- Un punto de la lista ---------- */
.admprod-lista { list-style: none; margin: 0; padding: 0; }

.admprod-punto {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: start;
  gap: var(--s3);
  padding: var(--s4);
  border-bottom: 1px solid var(--border);
}
.admprod-punto:last-child { border-bottom: none; }

.admprod-punto-icono {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border-radius: var(--r-full);
  flex: none;
}
.es-listo     .admprod-punto-icono { background: var(--ok-bg);   color: var(--ok); }
.es-pendiente .admprod-punto-icono { background: var(--warn-bg); color: var(--warn); }
.es-manual    .admprod-punto-icono { background: var(--info-bg); color: var(--info); }

.admprod-punto-txt { min-width: 0; }

.admprod-punto-titulo {
  display: block;
  font-weight: var(--peso-fuerte);
  line-height: 1.35;
  overflow-wrap: anywhere;
}

/* LA CORRECCION DE LAS LINEAS DE 1.133px. El detalle es prosa, no un
   dato: por encima de unos 70 caracteres el ojo pierde el principio
   del renglon siguiente. 66ch lo deja en ~620px a 1440, que es donde
   se lee de corrido. */
.admprod-punto-detalle {
  margin: var(--s1) 0 0;
  max-width: 66ch;
  font-size: var(--fs-sm);
  color: var(--text-2);
  line-height: var(--lh);
  overflow-wrap: anywhere;
}

/* El estado actual es un dato medido, no prosa: va aparte y en
   monoespacio para que "America/Mexico_City" o "APP_ENV" se lean como
   lo que son. */
.admprod-punto-estado {
  display: inline-block;
  margin-top: var(--s2);
  padding: 2px var(--s2);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  font-size: var(--fs-xs);
  color: var(--text-2);
  overflow-wrap: anywhere;
}

.admprod-punto-chip { flex: none; }

/* Un punto pendiente no se distingue solo por el chip: el renglon
   entero cambia de fondo. El mismo dato dicho tres veces -fondo, icono
   y palabra- para que no dependa del color. Los de "a mano" NO se
   tinen: no son un problema, son trabajo que no vive aqui. */
.es-pendiente { background: var(--warn-bg); }

@media (max-width: 767px) {
  /* El chip comparte renglon con el TITULO y el detalle pasa por debajo
     a lo ancho. Con el chip en su propio renglon cada punto gastaba una
     linea de mas; medido en Seguridad: 111px por fila contra 90.

     display:contents sube titulo, detalle y estado a la rejilla de la
     ficha, que es la unica forma de colocarlos en filas distintas. */
  .admprod-punto { gap: var(--s1) var(--s3); padding: var(--s3); }
  .admprod-punto-txt { display: contents; }
  .admprod-punto-titulo  { grid-column: 2; grid-row: 1; }
  .admprod-punto-chip    { grid-column: 3; grid-row: 1; }
  .admprod-punto-detalle { grid-column: 2 / -1; grid-row: 2; margin-top: 0; }
  .admprod-punto-estado  { grid-column: 2 / -1; grid-row: 3; margin-top: var(--s1); justify-self: start; }
}


/* ---------------------------------------------------------------------
   41k. ADMIN: CONFIGURACION  (ajustes / informacion / mantenimiento)

   MEDIDO ANTES DE TOCAR NADA, a 1440x1250:
     - CERO helpers: 0 .panel, 0 .metrica, 0 .chip. Dos <div
       class="tarjeta"> con un <p class="negrita"> haciendo de titulo.
     - CAMPOS DESALINEADOS: en la segunda .fila el <input> de
       "Registros por IP" caia en y=512 y el de "Minutos de inactividad"
       en y=495. DIECISIETE PIXELES de desfase entre dos campos del
       mismo renglon, porque uno lleva ayuda y el otro no y el flex los
       centra verticalmente.
     - Las dos acciones de mantenimiento sin confirmacion: 0 elementos
       con data-confirmar. Una de ellas borra la tabla login_attempts
       entera.
     - Una <table> sin <thead> usada como lista de clave/valor.
     - NUEVE bloques .dato para los conteos, sin tabular-nums.
     - Boton primario de 532px de ancho.

   El prefijo es .admconfig-. Se reutilizan .campo, .ficha-datos y
   .caja-desglose tal cual: son piezas del sistema y no se tocan.
   --------------------------------------------------------------------- */

/* ---------- Composicion ---------- */
/* Una columna hasta 1280. A 1024 el area util es de 765px -la lateral
   se abre y se lleva 244- y dos columnas dejarian el formulario en
   ~380px: un <input> de numero a esa anchura con su etiqueta y su
   ayuda se lee peor que apilado. Es la misma medida que fijo el corte
   en Seguridad y Produccion.

   Desde 1280 el formulario manda (3fr) y la informacion acompana (2fr):
   no son dos bloques del mismo peso. */
.admconfig-cols {
  display: grid;
  gap: var(--s4);
  align-items: start;
  margin-bottom: var(--s5);
}
@media (min-width: 1280px) {
  .admconfig-cols { grid-template-columns: minmax(0, 3fr) minmax(0, 2fr); }
}

.admconfig-principal { min-width: 0; }
.admconfig-lateral   { min-width: 0; display: grid; gap: var(--s4); align-items: start; }

/* ---------- El formulario ---------- */
/* fieldset y legend de verdad: los cuatro numericos son un grupo con
   nombre, y un lector de pantalla lo anuncia al entrar en el primero.
   Se le quitan los estilos de serie del navegador, que no encajan. */
/* G4: el grupo y su rotulo son ahora .campo-grupo + <legend> del
   sistema. Esta pantalla fue la que invento la forma; ahora la usa
   todo el mundo y sus dos reglas propias sobran. */

/* LA CORRECCION DE LOS 17px. Rejilla y no flex: en una rejilla los
   campos de una misma fila se alinean por su borde superior aunque uno
   sea mas alto que el otro, que es justo lo que pasa cuando solo uno
   lleva texto de ayuda. Con .fila (flex, centrado) los <input> del
   mismo renglon salian a distinta altura. */
.admconfig-rejilla {
  display: grid;
  gap: var(--s3) var(--s4);
  align-items: start;
}
@media (min-width: 560px) {
  .admconfig-rejilla { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

.admconfig-rejilla .campo { margin-bottom: 0; }

/* El texto de ayuda es parte del campo, no un parrafo suelto: mismo
   sitio, mismo tono y misma separacion en los seis. */

/* El error va pegado al campo que falla, no solo en el banner de
   arriba. Icono ademas de color: el estado no depende del tono.

   .admconfig-campo-error se retira: era un segundo mecanismo para lo
   que .campo.tiene-error (seccion 4) ya hacia en todo el sistema, con
   la unica diferencia de que ademas pintaba la ETIQUETA de rojo -algo
   que no hace ningun otro formulario-. La pantalla pasa a usar
   ui_campos_con_error(), que es de donde salen esas clases. */

/* El boton primario medía 532px porque .btn es width:100%. Aqui se
   queda con su tamano y a la izquierda, separado por una linea del
   formulario que cierra. */
/* G4: composicion heredada de .form-acciones. */

@media (max-width: 767px) {
  /* En telefono si va a lo ancho: es donde llega el pulgar. */
}

/* ---------- Informacion del sistema ---------- */
/* .ficha-datos ya existe (cliente/movimiento.php) y se usa tal cual.
   Lo unico propio: la columna de etiquetas se estrecha, porque aqui
   las claves son cortas y el panel es la columna lateral. */
.admconfig-ficha { grid-template-columns: minmax(0, 130px) minmax(0, 1fr); }
.admconfig-ficha dt,
.admconfig-ficha dd { padding: var(--s2) 0; font-size: var(--fs-sm); }
.admconfig-ficha dd.num { font-variant-numeric: tabular-nums; }

@media (max-width: 379px) {
  /* Con 130px de etiqueta y 200 de valor, "Fecha del servidor" partia
     en tres renglones. Por debajo de 400 cada dato ocupa dos lineas. */
  .admconfig-ficha { grid-template-columns: minmax(0, 1fr); }
  .admconfig-ficha dt { padding-bottom: 0; border-bottom: none; }
  .admconfig-ficha dd { padding-top: 2px; }
}

/* ---------- Conteos de registros ---------- */
/* Nueve cifras del mismo tipo que SI se comparan entre si: alineadas a
   la derecha y con tabular-nums, para que el ojo las lea en columna.
   Antes eran nueve .dato sin alinear. */
.admconfig-conteos li { align-items: baseline; }
/* Monoespacio literal y no var(--font-mono): ese token NO existe en
   este sistema -comprobado- y declararlo aqui seria inventar una pieza
   del tema que nadie mas usa. Son nombres de tabla, se leen mejor con
   ancho fijo. */
.admconfig-tabla {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: var(--fs-xs);
  color: var(--text-2);
  overflow-wrap: anywhere;
}
.admconfig-conteos strong {
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* ---------- Mantenimiento ---------- */
/* Zona propia y marco propio. No son ajustes: se ejecutan de inmediato
   sobre toda la plataforma y no se deshacen. Mezclarlas con el resto es
   lo que permitia que "borrar todos los intentos de acceso" fuera un
   boton gris igual que cualquier otro. */
.admconfig-mant {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-1);
  overflow: hidden;
}

.admconfig-mant-cab {
  display: flex;
  align-items: flex-start;
  gap: var(--s3);
  padding: var(--s4) var(--s5);
  border-bottom: 1px solid var(--border);
  background: var(--surface-2);
}
.admconfig-mant-icono {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border-radius: var(--r-md);
  background: var(--surface);
  border: 1px solid var(--border);
  color: var(--text-2);
  flex: none;
}
.admconfig-mant-titulo {
  margin: 0;
  font-size: var(--fs-lg);
  letter-spacing: var(--ls-titulo);
  color: var(--text);
}
.admconfig-mant-desc {
  margin: 2px 0 0;
  max-width: 68ch;
  font-size: var(--fs-sm);
  color: var(--text-2);
  line-height: var(--lh);
}

.admconfig-mant-item {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--s4);
  padding: var(--s4) var(--s5);
  border-bottom: 1px solid var(--border);
}
.admconfig-mant-item:last-child { border-bottom: none; }

/* La destructiva lleva filo rojo a la izquierda ADEMAS del boton de
   peligro y del texto: tres senales, ninguna sola de color. */
.admconfig-mant-item.es-peligro { border-left: 3px solid var(--error); }

.admconfig-mant-txt { min-width: 0; }
.admconfig-mant-nombre {
  display: block;
  font-weight: var(--peso-fuerte);
  line-height: 1.35;
}
.admconfig-mant-detalle {
  margin: var(--s1) 0 0;
  max-width: 70ch;
  font-size: var(--fs-sm);
  color: var(--text-2);
  line-height: var(--lh);
}

.admconfig-mant-form { flex: none; }
.admconfig-mant-form .btn { min-height: 40px; }

/* El corte es 767 y no 640: es el ancho de telefono del sistema, y
   entre esos dos numeros el boton se quedaba con min-height 40px, por
   debajo del minimo tactil, en un aparato que se toca con el dedo. */
@media (max-width: 767px) {
  .admconfig-mant-cab,
  .admconfig-mant-item { padding: var(--s4); }
  /* La accion baja debajo de su explicacion y ocupa el ancho: con dos
     columnas a 360px el boton se quedaba en 96px y el texto en 190. */
  .admconfig-mant-item { grid-template-columns: minmax(0, 1fr); }
  .admconfig-mant-form .btn { width: 100%; min-height: 44px; }
}


/* ---------------------------------------------------------------------
   41l. ADMIN: AVISOS  (anuncios de la plataforma)
   Ultima pantalla del area admin. Reutiliza .admin-lista de Negocios,
   .caja-desglose de Caja y .campo del sistema, sin tocar ninguno.

   MEDIDO ANTES DE TOCAR NADA, a 1440x1250 (1 aviso publicado):
     - EL BOTON PRIMARIO MEDIA 721px. Escrito a mano en vez de con
       ui_boton(), heredaba el width:100% de .btn y se estiraba por
       toda la columna del formulario.
     - La columna "AVISO" se llevaba 659px de 1.196 -el 55% de la
       tabla- y dentro imprimia el mensaje entero: hasta 500
       caracteres de pared de texto en una celda de 83px.
     - CUATRO .ranking-fila para el panel de alcance: ese patron es el
       del ranking de productos de Reportes y aqui son cuatro cifras
       fijas.
     - Chips de STOCK para el tono ('chip-bajo' para una advertencia)
       y para el estado ('chip-normal' para "Activo").
     - Confirmacion con cuatro data-confirmar-* tecleados a mano.

   El prefijo es .admavisos- para la pantalla y .avi- para las piezas
   de una fila del historial, igual que Negocios usa .neg- dentro de
   .lista-negocios.
   --------------------------------------------------------------------- */

/* ---------- Que se esta mostrando ahora ---------- */
.admavisos-estado {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--s3);
  align-items: start;
  background: var(--surface);
  border: 1px solid var(--border);
  border-left: 4px solid var(--border-2);
  border-radius: var(--r-lg);
  padding: var(--s4) var(--s5);
  margin-bottom: var(--s4);
  box-shadow: var(--sh-1);
}
/* El color nunca va solo: al lado hay un titulo escrito y, en cada
   aviso, un chip con la palabra del tono. */
.admavisos-estado.es-activo { border-left-color: var(--brand-600); }
.admavisos-estado.es-quieto { border-left-color: var(--border-2); }

.admavisos-estado-icono {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 38px;
  border-radius: var(--r-md);
  flex: none;
}
.es-activo .admavisos-estado-icono { background: var(--info-bg);   color: var(--info); }
.es-quieto .admavisos-estado-icono { background: var(--surface-2); color: var(--text-3); }

.admavisos-estado-txt { min-width: 0; }
.admavisos-estado-titulo {
  margin: 0;
  font-size: var(--fs-lg);
  letter-spacing: var(--ls-titulo);
  color: var(--text);
}
.admavisos-estado-desc {
  margin: 2px 0 0;
  max-width: 70ch;
  font-size: var(--fs-sm);
  color: var(--text-2);
  line-height: var(--lh);
}

.admavisos-estado-lista {
  list-style: none;
  margin: var(--s3) 0 0;
  padding: 0;
  display: grid;
  gap: var(--s2);
}
.admavisos-estado-lista li {
  display: flex;
  align-items: center;
  gap: var(--s2);
  flex-wrap: wrap;
  min-width: 0;
}
.admavisos-estado-nombre {
  font-weight: var(--peso-fuerte);
  overflow-wrap: anywhere;
}
.admavisos-estado-publico {
  font-size: var(--fs-xs);
  color: var(--text-3);
  white-space: nowrap;
}

/* ---------- Composicion ---------- */
/* Una columna hasta 1280. A 1024 el area util es de 765px -la lateral
   se abre y se lleva 244- y dos columnas dejarian el <textarea> del
   mensaje en ~440px: se escribe peor de lo que se lee. Es la misma
   medida que fijo el corte en Seguridad, Produccion y Configuracion.

   Desde 1280 el formulario manda (3fr) y el alcance acompana (2fr). */
.admavisos-cols {
  display: grid;
  gap: var(--s4);
  align-items: start;
  margin-bottom: var(--s4);
}
@media (min-width: 1280px) {
  .admavisos-cols { grid-template-columns: minmax(0, 3fr) minmax(0, 2fr); }
}
.admavisos-principal,
.admavisos-lateral { min-width: 0; }

/* ---------- El formulario ---------- */
/* Rejilla y no .fila: hoy los dos campos miden lo mismo, pero el flex
   los centraria en cuanto uno creciera. En Configuracion ese mismo
   patron costaba 17px de desfase entre dos <input> del mismo renglon. */
.admavisos-rejilla {
  display: grid;
  gap: var(--s3) var(--s4);
  align-items: start;
}
@media (min-width: 560px) {
  .admavisos-rejilla { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
.admavisos-rejilla .campo { margin-bottom: 0; }

.admavisos-form textarea { min-height: 120px; }

.admavisos-req { color: var(--error); }


/* El error del formulario, dentro del panel del formulario. Icono
   ademas de color: el estado no depende del tono. */
.admavisos-error {
  display: flex;
  align-items: flex-start;
  gap: var(--s2);
  margin: 0 0 var(--s4);
  padding: var(--s3);
  background: var(--error-bg);
  border: 1px solid var(--error);
  border-radius: var(--r-md);
  font-size: var(--fs-sm);
  color: var(--error);
  line-height: var(--lh);
}
.admavisos-error svg { flex: none; margin-top: var(--s0); }

/* El boton de publicar llego a medir 721px porque la base era
   width:100% y este bloque no lo desdecia. Desde G3 la base mide por
   contenido y aqui solo queda la fila que los coloca. */
/* G4: composicion heredada de .form-acciones. */

@media (max-width: 767px) {
}

/* ---------- Alcance ---------- */
.admavisos-alcance strong {
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.admavisos-nota {
  display: flex;
  align-items: flex-start;
  gap: var(--s2);
  margin: var(--s4) 0 0;
  font-size: var(--fs-xs);
  color: var(--text-3);
  line-height: var(--lh);
}
.admavisos-nota svg { flex: none; margin-top: var(--s0); }

/* ---------- Una fila del historial ---------- */
.avi-cab {
  display: flex;
  align-items: center;
  gap: var(--s2);
  flex-wrap: wrap;
}
.avi-titulo {
  font-weight: var(--peso-fuerte);
  overflow-wrap: anywhere;
}

/* LA CORRECCION DE LA PARED DE TEXTO. El mensaje llega a 500
   caracteres y se volcaba entero en la celda. Se acota a tres
   renglones; el texto completo sigue accesible en el title del
   parrafo, asi que no se pierde nada, solo deja de empujar la fila. */
.avi-mensaje {
  margin: var(--s1) 0 0;
  font-size: var(--fs-sm);
  color: var(--text-2);
  line-height: var(--lh);
  overflow-wrap: anywhere;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.avi-publico { font-size: var(--fs-sm); }
.avi-vistos  { font-variant-numeric: tabular-nums; }

.avi-fecha { font-size: var(--fs-sm); }
.avi-fecha-dia { display: block; white-space: nowrap; }
.avi-autor {
  display: block;
  margin-top: var(--s0);
  font-size: var(--fs-xs);
  color: var(--text-3);
  overflow-wrap: anywhere;
}

.avi-sin-accion { color: var(--text-3); }
.avi-acciones .btn { min-height: 40px; }

/* ---------- Reparto de la tabla ---------- */
/* Las reglas de COLUMNAS van con la clase de la pantalla y nunca con
   .admin-lista, que es la compartida.

   Antes la columna del aviso se llevaba el 55% (659px de 1.196) para
   volcar el mensaje entero. Acotando el mensaje a tres renglones y
   fijando las otras cinco, el aviso se queda con lo que sobra y la
   fila deja de depender de lo largo que alguien escriba. */
@media (min-width: 1280px) {
  .lista-avisos th:nth-child(2) { width: 150px; }
  .lista-avisos th:nth-child(3) { width: 100px; }
  .lista-avisos th:nth-child(4) { width: 140px; }
  .lista-avisos th:nth-child(5) { width: 110px; }
  .lista-avisos th:nth-child(6) { width: 120px; }
}

/* Banda 641-1279: la critica. El area util NO crece con la ventana:
     768 -> 689px    900 -> 821px    1024 -> 765px    1280 -> 1021px
   A 1024 la lateral se abre y hay menos sitio que a 900. Cede
   "Publicado": la fecha exacta no decide nada aqui y el orden de la
   tabla ya es cronologico. */
@media (min-width: 768px) and (max-width: 1279px) {
  .lista-avisos th, .lista-avisos td { padding: var(--s2) var(--s3); }
  .lista-avisos th:nth-child(4),
  .lista-avisos td.avi-fecha { display: none; }
  .lista-avisos th:nth-child(2) { width: 130px; }
  .lista-avisos th:nth-child(3) { width: 88px; }
  .lista-avisos th:nth-child(5) { width: 100px; }
  .lista-avisos th:nth-child(6) { width: 110px; }
}

/* Por debajo de 900 tambien cede "Lo vieron": cuantas personas lo han
   abierto es contexto, cual es el aviso y si sigue en circulacion es
   la identidad. El dato vuelve en cuanto hay ancho. */
@media (min-width: 768px) and (max-width: 899px) {
  .lista-avisos th:nth-child(3),
  .lista-avisos td.avi-vistos { display: none; }
}

/* ---------- Telefono ---------- */
/* La tabla ya se apila en fichas (seccion 14). Aqui solo se coloca lo
   que dentro de la ficha no se reparte igual que en una fila. */
@media (max-width: 767px) {
  .lista-avisos .tabla-datos tr {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  /* El aviso -chip de tono, titulo y mensaje- ocupa la ficha entera y
     manda: es la identidad, no un valor mas alineado a la derecha. */
  .lista-avisos .tabla-datos td.avi-aviso {
    grid-column: 1 / -1;
    display: block;
    text-align: left;
  }
  .lista-avisos .tabla-datos td.avi-aviso::before { content: none; }
  .lista-avisos .avi-titulo { font-size: var(--fs-base); }

  /* Publico y estado comparten renglon; vistos y fecha, el siguiente.
     LA FILA SE DECLARA ADEMAS DE LA COLUMNA, y no es un adorno: con
     solo grid-column, la colocacion automatica de la rejilla no
     retrocede nunca. En el orden del DOM -aviso, publico, vistos,
     fecha, estado, acciones- eso dejaba "estado" solo en un cuarto
     renglon y dos medias celdas vacias. MEDIDO a 390: celdas de 253px
     y fichas de 561px de alto. */
  .lista-avisos .tabla-datos td.avi-publico { grid-column: 1; grid-row: 2; }
  .lista-avisos .tabla-datos td.avi-estado  { grid-column: 2; grid-row: 2; justify-content: flex-end; }
  .lista-avisos .tabla-datos td.avi-vistos  { grid-column: 1; grid-row: 3; }
  .lista-avisos .tabla-datos td.avi-fecha   { grid-column: 2; grid-row: 3; }

  /* La celda de fecha lleva DOS lineas -momento y autor- dentro de un
     <td> que en telefono es flexible: como hijos de un flex, el
     display:block de los <span> no cuenta y se ponian uno al lado del
     otro, empujando al autor 6px fuera del viewport (medido: right=396
     con 390 de ancho). Con flex-basis del 100% el autor baja a su
     propio renglon dentro de la celda. */
  /* El hueco entre etiqueta y valor baja de s4 a s1 SOLO en esta
     celda. Con 16px de separacion, "Publicado" (63px) y
     "05/09/26 12:14" (87px) sumaban 166 en los 157 disponibles y la
     fecha se iba a un tercer renglon; ademas ese mismo hueco se
     aplicaba ENTRE renglones al envolver. MEDIDO: la celda pasaba de
     58px a 103. */
  .lista-avisos .tabla-datos td.avi-fecha {
    flex-wrap: wrap;
    justify-content: flex-end;
    gap: var(--s1);
  }
  .lista-avisos .tabla-datos td.avi-fecha::before { margin-right: auto; }
  .lista-avisos .tabla-datos td.avi-fecha .avi-autor {
    flex: 1 0 100%;
    text-align: right;
    margin-top: 0;
  }

  /* La accion, a lo ancho y donde llega el pulgar. */
  .lista-avisos .tabla-datos td.avi-acciones {
    grid-column: 1 / -1;
    justify-content: stretch;
  }
  .lista-avisos .tabla-datos td.avi-acciones form { display: block; width: 100%; }
  .lista-avisos .tabla-datos td.avi-acciones .btn {
    width: 100%;
    justify-content: center;
    min-height: 44px;
  }

  /* El estado vacio ocupa la ficha entera: sin esto, la rejilla de dos
     columnas lo dejaria en la mitad izquierda. */
  .lista-avisos .tabla-datos td.vacio { grid-column: 1 / -1; }
}


/* =====================================================================
   GLOBAL G1 — SHELL / BREAKPOINTS
   ---------------------------------------------------------------------
   Esta seccion NO pinta nada. Es la escala de anchos del sistema puesta
   por escrito en un solo sitio, porque hasta ahora cada pantalla la
   descubria por su cuenta y el archivo acumulaba 23 valores distintos
   de breakpoint.

   Los media query de CSS no aceptan var(): los numeros hay que
   escribirlos. Lo que se puede hacer -y es lo que se hace- es que haya
   UNA lista de numeros y que todo el archivo se refiera a ella.

   LA ESCALA
   ---------
     SM   640   La tabla deja de ser tabla y pasa a ficha.
     MD   768   Aparece el riel de iconos; se va la barra inferior.
     LG  1024   Hay sitio para dos columnas de contenido.
     XL  1280   La lateral se abre con etiquetas.
     2XL 1440   Pantalla amplia: composiciones de dos columnas holgadas.

   Cada escalon se escribe de sus dos lados: max-width usa N-1
   (639, 767, 1023, 1279, 1439) y min-width usa N. No son diez
   breakpoints: son cinco fronteras vistas desde los dos lados.

   POR QUE 640 Y 768 NO SON EL MISMO ESCALON
   -----------------------------------------
   Eran los dos valores que mas se pisaban del archivo (20 y 27 usos) y
   la auditoria los marco como "familias en competencia". No lo son, y
   la diferencia es real:

     768  es CROMO: por debajo hay cajon y barra inferior, por encima
          hay riel. Lo decide el shell.
     640  es DENSIDAD: por debajo una tabla no cabe como tabla y se
          apila en fichas. Lo decide el contenido.

   Entre 641 y 767 se tiene cromo de telefono con tablas todavia como
   tablas, y a 700px de ancho una tabla de cinco columnas se lee bien.
   Fundirlos significaria decidir que pasa con las tablas en esa
   franja, y eso es G5. Aqui quedan documentados como dos escalones
   distintos con dos responsabilidades distintas.

   EXCEPCIONES QUE SE CONSERVAN, Y POR QUE
   ---------------------------------------
     380   Telefono muy estrecho. Tres reglas (tarjetas, acceso, POS)
           que solo existen para que nada se rompa por debajo de 390.
     400   Ficha de datos de Configuracion. Pendiente para G4.
     560   Rejilla de dos campos de Configuracion y Avisos. G4.
     599 / 600   Ticket y filtro de Actividad. G5/G6.
     699 / 700   Botones de decision de Solicitudes. G3.
     1179 / 1180  Rejillas de metricas y rail del ticket. Es el corte
           que G2 tiene que revisar; se deja intacto para no cambiar
           dos cosas a la vez.
     1400  Pantalla de acceso. Tiene identidad propia y su composicion
           esta calibrada contra ese numero; el shell de la aplicacion
           autenticada ni siquiera existe ahi.

   LO QUE G1 CONSOLIDA
   -------------------
     1400 y 1500  ->  1440   Seis reglas que abrian dos columnas en
                             "pantalla amplia" con tres numeros
                             distintos para la misma intencion:
                             ticket (x3), Mi negocio, Categorias y
                             alta de negocio. Ahora comparten escalon.
     1023 / 1024  ->  1279 / 1280   El shell deja de cambiar ahi.
                             1024 sigue siendo escalon de CONTENIDO
                             (dos columnas) en una docena de reglas;
                             lo que se movio es la geometria, no ellas.

   LA GEOMETRIA, MEDIDA
   --------------------
   Area util = ancho de .contenido menos sus 48px de relleno lateral.

     ancho   lateral   main    util ANTES   util AHORA
      430      -        430       406          406
      768      64       704       656          656
      900      64       836       788          788
     1024     244->64   780->960  732          912
     1180     244->64   936->1116 888          988
     1280     244      1036      988          988
     1440     244      1196     1148         1148
     1600     244      1356     1308         1308
     1920     244      1440->1600 1392        1552

   La regla que esta seccion existe para garantizar:
   EL AREA UTIL NO DISMINUYE NUNCA AL CRECER LA VENTANA.
   Antes bajaba 41px entre 900 y 1024. Ahora la serie es monotona.
   ===================================================================== */


/* =====================================================================
   V1 COMERCIAL - SUCURSALES, USUARIOS, TRANSFERENCIAS Y ONBOARDING
   ---------------------------------------------------------------------
   Solo tokens del sistema (--surface, --border, --text-2, --brand-600,
   --ok, --warn, --error, --s*, --fs-*, --r-*). Asi el tema oscuro y el
   claro salen solos y nada de aqui inventa un color nuevo.
   ===================================================================== */

/* ---------- Selector de sucursal de trabajo (barra superior) ---------- */
.topbar-sucursal {
  display: flex;
  align-items: center;
  gap: var(--s1);
  min-width: 0;
  max-width: 220px;
  margin-left: auto;
}
.topbar-sucursal-icono {
  display: inline-flex;
  color: var(--text-3);
  flex: none;
}
.topbar-sucursal select {
  min-height: 36px;
  padding: var(--s1) var(--s5, 28px) var(--s1) var(--s2);
  font-size: var(--fs-sm);
  border-radius: var(--r-full);
  background-color: var(--surface-2);
  border-color: var(--border);
  color: var(--text);
  width: auto;
  max-width: 100%;
}
.topbar-sucursal-ir {
  min-height: 36px;
  padding: 0 var(--s2);
  border: 1px solid var(--border);
  border-radius: var(--r-full);
  background: var(--surface);
  color: var(--text-2);
  font: inherit;
  font-size: var(--fs-xs);
  cursor: pointer;
}
.topbar-sucursal-ir:hover { color: var(--text); background: var(--surface-2); }
@media (max-width: 767px) {
  .topbar-sucursal { max-width: 150px; margin-left: 0; }
  .topbar-sucursal-icono, .topbar-sucursal-ir { display: none; }
  .topbar-sucursal select { font-size: var(--fs-sm); }
}

/* ---------- Paneles a dos columnas: listado + formulario ---------- */
.suc-cols, .usr-cols, .trf-cols {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--s5, 24px);
  align-items: start;
}
@media (min-width: 1024px) {
  .suc-cols, .usr-cols { grid-template-columns: minmax(0, 1.6fr) minmax(300px, 1fr); }
  .trf-cols            { grid-template-columns: minmax(300px, 1fr) minmax(0, 1.6fr); }
}
.suc-form .panel + .panel, .usr-form .panel + .panel { margin-top: var(--s4); }

.suc-nombre strong, .usr-nombre { display: block; }
.suc-nombre .chip { margin-left: var(--s1); vertical-align: middle; }
.suc-meta {
  display: block;
  font-size: var(--fs-xs);
  color: var(--text-2);
  margin-top: var(--s0);
}
.suc-actual {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  margin-left: var(--s1);
  font-size: var(--fs-xs);
  color: var(--ok);
  font-weight: 600;
}
.suc-acciones-caja, .usr-acciones-caja {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s1);
  justify-content: flex-end;
}
.usr-sucs { font-size: var(--fs-sm); color: var(--text-2); max-width: 220px; }
.usr-acceso-mini { font-size: var(--fs-xs); color: var(--text-3); }
.tabla-sucursales-hoy tr.es-actual td { background: var(--brand-50); }

/* ---------- Casillas (sucursales y permisos del usuario) ---------- */
.usr-ayuda-arriba { margin-top: 0; margin-bottom: var(--s3); }
.usr-casillas {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--s2);
}
@media (min-width: 560px) {
  .usr-casillas { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
.usr-casilla {
  display: flex;
  align-items: flex-start;
  gap: var(--s2);
  padding: var(--s2) var(--s3);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--surface);
  cursor: pointer;
  font-size: var(--fs-sm);
  min-height: var(--tap);
}
.usr-casilla:hover { border-color: var(--border-2); }
.usr-casilla input { margin-top: 3px; flex: none; }
.usr-casilla span { display: flex; flex-direction: column; min-width: 0; }
.usr-casilla strong { font-weight: 600; color: var(--text); }
.usr-casilla small { color: var(--text-3); font-size: var(--fs-xs); line-height: 1.35; }
.usr-casilla.es-inactiva { opacity: .7; }
.usr-perm-grupo + .usr-perm-grupo { margin-top: var(--s4); }
.usr-perm-titulo {
  margin: 0 0 var(--s2);
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: .6px;
  text-transform: uppercase;
  color: var(--text-3);
}
.usr-enlace-btn {
  display: inline-flex;
  align-items: center;
  gap: var(--s1);
  padding: 0;
  border: none;
  background: none;
  color: var(--brand-600);
  font: inherit;
  font-size: inherit;
  cursor: pointer;
  text-decoration: underline;
}
.usr-enlace-btn:hover { color: var(--brand-700); }

.inv-matriz td.inv-m-celda { position: relative; font-variant-numeric: tabular-nums; }
.inv-matriz .inv-m-num { font-weight: 600; }
.inv-matriz .inv-m-agotado .inv-m-num { color: var(--error); }
.inv-matriz .inv-m-bajo .inv-m-num    { color: var(--crit, var(--warn)); }
.inv-matriz .inv-m-marca {
  display: inline-block;
  width: 7px; height: 7px;
  margin-left: 5px;
  border-radius: 50%;
  background: currentColor;
  vertical-align: middle;
}
.inv-matriz .inv-m-agotado .inv-m-marca { color: var(--error); }
.inv-matriz .inv-m-bajo .inv-m-marca    { color: var(--crit, var(--warn)); }
.inv-matriz .inv-m-total { background: var(--surface-2); }
.inv-matriz tr.es-inactivo td { opacity: .6; }

/* ---------- Transferencias ---------- */
.trf-ruta { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s1); font-size: var(--fs-sm); }
.trf-flecha { color: var(--text-3); }
.trf-nota { flex-basis: 100%; font-size: var(--fs-xs); color: var(--text-3); }

/* ---------- Filtro de sucursal de reportes ---------- */
/* Filtro de sucursal de Reportes. En Alertas esta clase murio en la
   Fase 7: el ambito subio a la cabecera (seccion 67a). */
.rep-sucursal {
  display: flex;
  align-items: center;
  gap: var(--s2);
  margin-bottom: var(--s4);
}
.rep-sucursal select {
  width: auto;
  min-width: 200px;
  min-height: 40px;
  padding-top: var(--s1);
  padding-bottom: var(--s1);
  font-size: var(--fs-sm);
}
.rep-sucursal { margin-bottom: 0; }
.rep-periodo-suc { color: var(--text-2); font-weight: 400; }
@media (min-width: 1024px) {
  .rep-cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}

/* ---------- Onboarding ----------
   Aqui vivian treinta lineas para .onb-cab, .onb-titulo, .onb-sub,
   .onb-avance, .onb-explica, .onb-omitir, .onb-productos y .onb-pie:
   una cabecera, un panel y una tabla de cinco columnas.

   El asistente se rehizo entero y ninguna de esas clases existe ya
   en el marcado. No se dejan "por si acaso": una regla que no pinta
   nada es una pista falsa para quien venga a buscar por que algo se
   ve como se ve.

   Lo que hay ahora esta en la SECCION 79, al final del archivo. */


/* =====================================================================
   42. CAPA DE APLICACION  (UI 2.0)
   =====================================================================
   Todo lo anterior de este archivo resuelve "la misma pantalla, mas
   estrecha". Esta seccion resuelve otra cosa: que en un telefono el
   sistema se COMPORTE como una aplicacion y en una computadora siga
   siendo una herramienta de administracion densa.

   La regla que la ordena entera:

       Mismo marcado, mismo dato, misma logica, misma seguridad.
       Distinta PRESENTACION segun el dispositivo.

   No hay una plantilla movil y otra de escritorio. Hay componentes que
   saben presentarse de dos formas. Por eso ninguna pantalla necesita
   duplicar HTML y ninguna se puede desincronizar.

   Que entra aqui y que no
   -----------------------
   ENTRA: piezas que aparecen en varias pantallas (la hoja inferior, la
   lista tactil, el bloque, la barra de accion anclada).
   NO ENTRA: el arreglo de una sola pantalla. Eso vive en su seccion.
   ===================================================================== */


/* =====================================================================
   42a. LA HOJA INFERIOR
   ---------------------------------------------------------------------
   Es la pieza que mas cambia la sensacion del producto en telefono.

   POR QUE EXISTE
   Un <select> nativo, un menu que entra desde la izquierda y un modal
   centrado son tres formas distintas de decir lo mismo: "elige algo sin
   salir de donde estas". En un telefono las tres se sienten a web.
   Una hoja que sube desde abajo se siente a aplicacion, y ademas cae
   donde esta el pulgar.

   QUE NO ES
   No es un modal con otro borde. Un modal INTERRUMPE: pregunta algo y
   no te deja seguir. Una hoja OFRECE: se cierra deslizandola hacia
   abajo, tocando fuera o con Escape, y no hay ninguna decision que
   tomar. Por eso el sistema conserva ui_confirmar() -que es un modal de
   verdad- y no lo convierte en hoja.

   EN ESCRITORIO
   El MISMO marcado es un dialogo centrado. Una hoja pegada al borde
   inferior de un monitor de 27" no tiene sentido: no hay pulgar y el
   recorrido del raton seria absurdo. Cambia la presentacion; el HTML,
   el foco y el comportamiento son los mismos.

   SIN JAVASCRIPT
   Todo lo que se abre en una hoja tiene que existir tambien de otra
   forma: el selector de sucursal conserva su <select> con su boton Ir,
   el menu conserva la barra lateral. La hoja acelera; no es el unico
   camino.
   ===================================================================== */

.hoja[hidden] { display: none; }

.hoja {
  position: fixed;
  inset: 0;
  z-index: var(--z-hoja);
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
}

.hoja-velo {
  position: absolute;
  inset: 0;
  background: var(--velo);
  border: 0;
  padding: 0;
  cursor: pointer;
  animation: velo-entra var(--t-media) var(--ease);
}

.hoja-panel {
  position: relative;
  display: flex;
  flex-direction: column;
  width: 100%;
  max-height: var(--hoja-max);
  background: var(--sheet);
  border-radius: var(--r-hoja) var(--r-hoja) 0 0;
  box-shadow: var(--sh-hoja);
  animation: hoja-sube var(--t-lenta) var(--ease-suave);
  /* El arrastre lo escribe app.js en esta variable. Sin JavaScript
     vale 0 y no hace nada. */
  transform: translateY(var(--hoja-arrastre, 0px));
  overscroll-behavior: contain;
}

/* Mientras el dedo esta encima no hay transicion: la hoja tiene que ir
   pegada al dedo. Al soltar, app.js quita esta clase y la hoja vuelve o
   se va con la curva de siempre. */
.hoja-panel:not(.arrastrando) { transition: transform var(--t-media) var(--ease); }
.hoja-panel.arrastrando { animation: none; transition: none; }

@keyframes hoja-sube { from { transform: translateY(100%); } }

/* El asa. No es decoracion: es la unica pista de que esto se desliza.
   Su area tactil es de 28px de alto aunque la barra mida 4. */
.hoja-asa {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  height: 26px;
  cursor: grab;
  touch-action: none;
}
.hoja-asa::before {
  content: "";
  width: 40px;
  height: 4px;
  border-radius: var(--r-full);
  background: var(--border-2);
}
.hoja-panel.arrastrando .hoja-asa { cursor: grabbing; }

.hoja-cab {
  flex: none;
  display: flex;
  align-items: flex-start;
  gap: var(--s3);
  padding: 0 var(--s4) var(--s3);
}
.hoja-cab h2 {
  flex: 1;
  min-width: 0;
  margin: 0;
  font-size: var(--fs-lg);
  font-weight: var(--peso-fuerte);
  letter-spacing: var(--ls-titulo);
  line-height: var(--lh-tight);
}
.hoja-cab p {
  margin: 2px 0 0;
  font-size: var(--fs-sm);
  color: var(--text-2);
}

.hoja-cerrar {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  margin: -4px -6px 0 0;
  background: var(--surface-2);
  border: 0;
  border-radius: var(--r-full);
  color: var(--text-2);
  cursor: pointer;
}
.hoja-cerrar:active { background: var(--pressed); }

.hoja-cuerpo {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
  padding: 0 var(--s4);
  padding-bottom: calc(var(--s5) + env(safe-area-inset-bottom));
}
/* Una hoja que es SOLO una lista no lleva margenes laterales: las filas
   llegan de borde a borde, como en una aplicacion. */
.hoja-cuerpo.a-sangre { padding-left: 0; padding-right: 0; }
.hoja-cuerpo.a-sangre > .lista { padding-left: var(--s2); padding-right: var(--s2); }

/* Pie de accion: cuando la hoja tiene una decision principal. Se queda
   pegado; el cuerpo se desplaza por debajo. */
.hoja-pie {
  flex: none;
  display: grid;
  gap: var(--s2);
  padding: var(--s3) var(--s4);
  padding-bottom: calc(var(--s3) + env(safe-area-inset-bottom));
  background: var(--sheet);
  border-top: 1px solid var(--border);
}
.hoja-pie .btn { width: 100%; min-height: 48px; }
.hoja-pie-fila { display: flex; gap: var(--s2); }
.hoja-pie-fila .btn { flex: 1; }
/* Con pie propio, el cuerpo ya no tiene que reservar el area segura. */
.hoja-panel:has(.hoja-pie) .hoja-cuerpo { padding-bottom: var(--s3); }

body.hoja-abierta { overflow: hidden; }

/* ---------- La misma hoja, en pantalla grande, es un dialogo ---------- */
@media (min-width: 768px) {
  .hoja { justify-content: center; align-items: center; padding: var(--s5); }
  .hoja-panel {
    width: min(480px, 100%);
    max-height: min(76vh, 640px);
    border-radius: var(--r-lg);
    box-shadow: var(--sh-4);
    animation: hoja-aparece var(--t-media) var(--ease-suave);
    transform: none;
  }
  /* Un dialogo no se arrastra: el asa deja de ser una pista honesta. */
  .hoja-asa { display: none; }
  .hoja-cab { padding-top: var(--s4); }
  .hoja-cuerpo { padding-bottom: var(--s4); }
  .hoja-pie { padding-bottom: var(--s4); }
  .hoja-pie-fila { justify-content: flex-end; }
  .hoja-pie-fila .btn { flex: 0 0 auto; min-width: 140px; }
}
@keyframes hoja-aparece {
  from { opacity: 0; transform: translateY(12px) scale(.98); }
}

@media (prefers-reduced-motion: reduce) {
  .hoja-panel, .hoja-velo { animation: none; }
  .hoja-panel { transition: none; }
}


/* =====================================================================
   42b. LA LISTA TACTIL
   ---------------------------------------------------------------------
   El sustituto honesto de una tabla en un telefono.

   El sistema ya sabia apilar una tabla como fichas (seccion 14): cada
   celda se convierte en "Etiqueta: valor". Funciona y se queda, porque
   es lo que salva a las tablas de nueve columnas del panel de
   administrador sin tocarlas.

   Pero para las pantallas que se usan de pie -productos, sucursales,
   alertas, movimientos- una ficha de siete renglones no es lo que hace
   falta. Lo que hace falta es una LINEA: quien es, el dato que decide,
   y a un toque el resto.

       Cafe molido 1 kg
       CAF-001 - 42 unidades              [Normal]  >

   Tres reglas de la lista:

   1. TODA la fila es el toque, no un enlace de 90px dentro de ella.
   2. Como mucho DOS renglones de texto. Si hace falta un tercero, es
      que ese dato pertenece al detalle.
   3. Lo que va a la derecha es el dato que DECIDE (el estado, la
      cifra), no un adorno.

   En escritorio esta misma lista se usa dentro de paneles estrechos
   (rankings, resumenes) y no intenta parecerse a una tabla: cuando lo
   que se quiere es comparar y ordenar columnas, se usa una tabla.
   ===================================================================== */

.lista {
  list-style: none;
  margin: 0;
  padding: 0;
}

.lista-fila { position: relative; }

/* La junta entre filas es un pelo, no un borde. Una lista con bordes de
   1px solidos se lee como una rejilla de celdas. */
.lista-fila + .lista-fila::before {
  content: "";
  position: absolute;
  top: 0;
  left: var(--s4);
  right: 0;
  height: 1px;
  background: var(--border-sutil);
}
/* Con marcador a la izquierda la junta arranca despues de el: es lo que
   alinea la lista con el texto y no con la foto. */
.lista-fila.con-marca + .lista-fila.con-marca::before {
  left: calc(var(--s4) + 40px + var(--s3));
}

.lista-toque {
  display: flex;
  align-items: center;
  gap: var(--s3);
  width: 100%;
  min-height: var(--fila-tap);
  padding: var(--s3) var(--s4);
  background: none;
  border: 0;
  border-radius: var(--r-md);
  color: inherit;
  font: inherit;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.lista-toque:hover { background: var(--hover); }
.lista-toque:active { background: var(--pressed); }
.lista-toque:focus-visible { outline: var(--foco); outline-offset: -2px; }

/* Fila elegida: la marca es un filo de color a la izquierda y un fondo
   muy bajo. NO se pinta el fondo entero de azul: en una lista de ocho
   sucursales eso es una mancha, no una marca. */
.lista-toque[aria-current="true"],
.lista-fila.elegida > .lista-toque {
  background: var(--selected);
  box-shadow: inset 3px 0 0 var(--brand-600);
}

.lista-marca {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border-radius: var(--r-md);
  background: var(--surface-2);
  color: var(--text-2);
  font-weight: var(--peso-fuerte);
  overflow: hidden;
}
.lista-marca img { width: 100%; height: 100%; object-fit: cover; }

/* Punto de eleccion para las listas donde se ELIGE una opcion */
.lista-punto {
  flex: none;
  width: 20px;
  height: 20px;
  border: 2px solid var(--border-2);
  border-radius: var(--r-full);
}
.lista-toque[aria-current="true"] .lista-punto,
.lista-fila.elegida .lista-punto {
  border-color: var(--brand-600);
  background: radial-gradient(circle at center, var(--brand-600) 0 5px, transparent 5.5px);
}

.lista-txt { flex: 1; min-width: 0; display: grid; gap: var(--s0); }
.lista-titulo {
  display: block;
  font-size: var(--fs-base);
  font-weight: var(--peso-fuerte);
  line-height: var(--lh-tight);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* OLA 7: EL NOMBRE CUANDO LLEVA UN DISTINTIVO AL LADO.

   .lista-titulo es el nombre SOLO: se recorta el solo y no comparte
   renglon con nadie. Cuando al nombre le acompana una pastilla en la
   misma linea -"Principal", "(tu)", un estado- el titulo ya no puede
   recortarse el solo: quien tiene que ceder es el NOMBRE, no la
   pastilla, o el dato que la pastilla lleva se va con el recorte.

   Por eso hay dos y no uno:

       .lista-titulo   el nombre, solo
       .lista-nom      el nombre + su distintivo, en una linea

   El recorte baja al <span> de dentro a proposito: el contenedor es
   flex y tiene que dejar que la pastilla se quede entera.

       <span class="lista-nom">
         <span>NOMBRE QUE SE RECORTA</span>
         <em class="...">Distintivo</em>
       </span>

   Existencias, Sucursales y Usuarios escribieron esto por separado
   -.inv-suc-nom, .suc-nom, .usr-nom- con las ocho declaraciones
   IDENTICAS, palabra por palabra, y el mismo marcado. Tres veces la
   misma pieza porque no tenia nombre en el sistema. Ahora lo tiene.

   Cada pantalla conserva ADEMAS su clase propia: la usan sus reglas de
   telefono, que NO son compartibles -dos de las tres disuelven el
   nombre en la rejilla de la fila con display:contents y la tercera
   no-. Eso es composicion de cada pantalla, no del componente. */
.lista-nom {
  display: flex;
  align-items: center;
  gap: var(--s2);
  min-width: 0;
  font-size: var(--fs-base);
  font-weight: var(--peso-fuerte);
  line-height: var(--lh-tight);
}
.lista-nom > span {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ---------------------------------------------------------------------
   42b-bis. EL TRIO DE RESUMEN   (Ola 8)
   ---------------------------------------------------------------------
   Tres cifras que encabezan una pantalla de gestion y contestan de un
   vistazo "cuantos hay, cuantos estan bien, y el dato que distingue".
   Cada caja lleva su azulejo, su cifra, su rotulo y una linea de apoyo,
   y se tine con el tono que le corresponde.

   Sucursales y Usuarios lo escribieron por separado -33 y 29 reglas- y
   salio IGUAL: mismo marcado, misma rejilla, mismos cuatro tonos, mismo
   reparto de telefono y de escritorio, mismas reglas de tema oscuro. La
   unica diferencia real es de CONTENIDO: la tercera caja de Sucursales
   lleva un NOMBRE donde las demas llevan un numero.

   Lo que se queda fuera de esta base, en la seccion de cada pantalla:

     .suc-trio-nom   el <strong> que lleva texto en vez de cifra
     .suc-trio a 379 el paso a dos columnas, que existe por ese nombre
     el borde de .suc-trio .trio-caja  (ver la nota alli)

   El marcado es:

       <ul class="trio X-trio">
         <li><span class="trio-caja trio-TONO">
               <span class="azulejo azulejo-TONO azulejo-sm">...</span>
               <strong>CIFRA</strong>
               <span class="trio-rot">ROTULO</span>
               <span class="trio-sub">apoyo</span>
         </span></li>
       </ul>

   La clase de pantalla -.suc-trio, .usr-trio- se queda en el <ul> para
   lo que sea de esa pantalla y solo de esa. */
.trio {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--s2);
  margin: 0 0 var(--s4);
  padding: 0;
  list-style: none;
}
.trio li { display: flex; min-width: 0; }

.trio-caja {
  flex: 1;
  display: grid;
  gap: 1px;
  min-width: 0;
  padding: var(--s3) var(--s2);
  border-radius: var(--r-lg);
}
.trio-caja > .azulejo { margin-bottom: var(--s2); }
.trio-caja strong {
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-xl);
  font-weight: var(--peso-max);
  line-height: 1.1;
}
.trio-rot {
  font-size: var(--fs-2xs);
  font-weight: var(--peso-fuerte);
  color: var(--text-2);
}
.trio-sub {
  font-size: var(--fs-3xs);
  line-height: 1.25;
  color: var(--text-3);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Los cuatro tonos. Reposo, y sobre --surface: es un bloque estatico,
   no una tarjeta que se pulsa -esa es la de Categorias, que necesita
   el continuo reposo/hover y por eso usa otra gramatica-. */
.trio-marca { background: color-mix(in srgb, var(--brand-600) 10%, var(--surface)); }
.trio-ok    { background: color-mix(in srgb, var(--ok) 10%, var(--surface)); }
.trio-warn  { background: color-mix(in srgb, var(--warn) 12%, var(--surface)); }
.trio-info  { background: color-mix(in srgb, var(--accent-600) 10%, var(--surface)); }
@supports not (background: color-mix(in srgb, red 10%, white)) {
  .trio-marca { background: var(--brand-50); }
  .trio-ok    { background: var(--ok-bg); }
  .trio-warn  { background: var(--warn-bg); }
  .trio-info  { background: var(--accent-50); }
}

@media (max-width: 767px) {
  .trio-caja { padding: var(--s2); }
  .trio-caja strong { font-size: var(--fs-lg); }
  .trio-caja > .azulejo { width: 28px; height: 28px; margin-bottom: var(--s1); }
  .trio-caja > .azulejo .icono { width: 15px; height: 15px; }
}

@media (min-width: 768px) {
  .trio { gap: var(--s3); }
  .trio-caja { padding: var(--s4); }
  .trio-caja strong { font-size: var(--fs-2xl); }
  .trio-rot { font-size: var(--fs-sm); }
  .trio-sub { font-size: var(--fs-xs); }
}

/* Oscuro: la mezcla al 10% no se ve sobre un panel oscuro, asi que
   sube; ok y warn tienen token -bg propio y lo usan, marca y acento no
   lo tienen y se mezclan. Es la regla que cerro la Ola 7. */
:root[data-tema="oscuro"] .trio-marca { background: color-mix(in srgb, var(--brand-600) 18%, var(--surface)); }
:root[data-tema="oscuro"] .trio-ok    { background: var(--ok-bg); }
:root[data-tema="oscuro"] .trio-warn  { background: var(--warn-bg); }
:root[data-tema="oscuro"] .trio-info  { background: color-mix(in srgb, var(--accent-600) 18%, var(--surface)); }

/* ---------------------------------------------------------------------
   42b-ter. EL DISTINTIVO DE UNA FILA   (Ola 8)
   ---------------------------------------------------------------------
   La pastilla que acompana al nombre dentro de .lista-nom: "Principal".
   Existencias y Sucursales la escribieron por separado con las mismas
   ocho declaraciones salvo el tamano de letra.

   La base se queda en 10px, que es lo que MIDEN LAS DOS donde conviven
   -por debajo de 768-. Existencias la sube a 12 de 768 arriba, que es
   donde su fila tiene sitio; Sucursales no la sube porque su lista solo
   existe en telefono. El tamano es composicion de cada pantalla, no del
   distintivo: por eso no hay aqui ningun token nuevo. */
.lista-ppal {
  flex: none;
  padding: 1px 6px;
  background: var(--brand-50);
  border-radius: var(--r-full);
  color: var(--brand-600);
  font-size: var(--fs-3xs);
  font-style: normal;
  font-weight: var(--peso-fuerte);
}
.lista-sub {
  display: block;
  font-size: var(--fs-sm);
  color: var(--text-2);
  line-height: 1.35;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.lista-sub .txt-error,
.lista-sub .txt-ok { font-weight: var(--peso-medio); }

.lista-fin {
  flex: none;
  display: flex;
  align-items: center;
  gap: var(--s2);
  text-align: right;
  color: var(--text-2);
  font-size: var(--fs-sm);
}
.lista-cifra {
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-base);
  font-weight: var(--peso-fuerte);
  color: var(--text);
  line-height: 1.2;
}
.lista-cifra small {
  display: block;
  font-size: var(--fs-xs);
  font-weight: 400;
  color: var(--text-3);
}

/* La punta de flecha dice "esto abre algo". Solo en filas que navegan. */
.lista-flecha {
  flex: none;
  color: var(--text-3);
  transition: transform var(--t-rapida) var(--ease);
}
.lista-toque:active .lista-flecha { transform: translateX(2px); }

/* Encabezado de tramo dentro de una lista larga */
.lista-tramo {
  padding: var(--s4) var(--s4) var(--s2);
  font-size: var(--fs-xs);
  font-weight: var(--peso-fuerte);
  letter-spacing: var(--ls-etiqueta);
  text-transform: uppercase;
  color: var(--text-3);
}


/* =====================================================================
   42c. EL BLOQUE
   ---------------------------------------------------------------------
   Regla 16 del encargo: "cada contenedor debe justificar su existencia".

   El sistema tenia UNA sola forma de agrupar: .panel, que es una caja
   blanca con borde y sombra. Cuando en una pantalla hay seis paneles,
   el borde deja de significar "esto va junto" y pasa a ser ruido: seis
   cajas iguales no jerarquizan nada.

   El bloque es la otra forma: un titulo, una linea de aire y el
   contenido. Sin caja. Se usa cuando lo que agrupa es el TEMA, no la
   superficie. Es lo que da el aire editorial que pedia el encargo, y es
   gratis: no dibuja nada.

   Cuando SI hace falta panel: si el contenido tiene que separarse del
   fondo porque es interactivo (un formulario, una tabla), o si hay dos
   grupos en paralelo que compiten. Si no, bloque.
   ===================================================================== */

.bloque { margin-bottom: var(--s5); }
.bloque:last-child { margin-bottom: 0; }

.bloque-cab {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s3);
  margin-bottom: var(--s3);
}
.bloque-cab h2,
.bloque-cab h3 {
  margin: 0;
  font-size: var(--fs-xs);
  font-weight: var(--peso-fuerte);
  letter-spacing: var(--ls-etiqueta);
  text-transform: uppercase;
  color: var(--text-3);
}
.bloque-cab .bloque-accion {
  font-size: var(--fs-sm);
  font-weight: var(--peso-fuerte);
  color: var(--brand-600);
  text-decoration: none;
  white-space: nowrap;
}
.bloque-cab .bloque-accion:hover { text-decoration: underline; }

/* Superficie del bloque: la usan los bloques cuyo contenido SI necesita
   despegarse (una lista tactil). Es un panel sin cabecera ni ruido. */
.bloque-sup {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  overflow: hidden;
}
.bloque-sup > .lista { padding: var(--s1) 0; }


/* =====================================================================
   42d. DATOS EN LINEA
   ---------------------------------------------------------------------
   "Etiqueta a la izquierda, valor a la derecha" aparecia escrito a mano
   en el detalle de venta, el corte, el detalle de movimiento y el
   perfil del negocio, cada uno con su clase. Es UN patron.
   ===================================================================== */

.datos { display: grid; gap: 0; margin: 0; }
.dato {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s4);
  padding: var(--s3) 0;
  font-size: var(--fs-sm);
}
.dato + .dato { border-top: 1px solid var(--border-sutil); }
.dato dt,
.dato .dato-rotulo { margin: 0; color: var(--text-2); }
.dato dd,
.dato .dato-valor {
  margin: 0;
  font-weight: var(--peso-fuerte);
  color: var(--text);
  text-align: right;
  font-variant-numeric: tabular-nums;
}
.dato-fuerte .dato-valor,
.dato-fuerte dd { font-size: var(--fs-lg); }


/* =====================================================================
   42e. NAVEGACION DE TELEFONO
   ---------------------------------------------------------------------
   Dos cambios, y los dos son de SENSACION, no de funcion.

   1. LA BARRA INFERIOR SE MARCA COMO UNA APLICACION.
      Antes la seccion activa se distinguia solo por el color del texto
      y del icono. En una barra de cinco destinos de 10px de letra, un
      azul sobre gris es una diferencia que hay que buscar. Ahora el
      icono activo va dentro de una pastilla tenida: se ve desde el otro
      lado de la mesa, que es como se mira un telefono en un mostrador.

      La barra ademas se translucida y difumina lo que pasa por debajo.
      No es decoracion: es lo que dice que la barra ESTA ENCIMA del
      contenido y que el contenido sigue ahi detras. Donde el navegador
      no sepa difuminar, queda opaca y se ve igual de bien.

   2. EL MENU COMPLETO SUBE DESDE ABAJO, NO ENTRA DESDE LA IZQUIERDA.
      Un cajon lateral es un gesto de sitio web. En un telefono el menu
      se abre desde donde esta el dedo que lo pidio: el boton "Mas" de
      la barra inferior, abajo a la derecha.

      Es EL MISMO elemento .lateral, con el mismo HTML, el mismo foco y
      el mismo JavaScript de siempre. Solo cambia de donde viene y que
      forma tiene. No hay un segundo menu que se pueda desincronizar.
   ===================================================================== */

@media (max-width: 767px) {

  /* ---------- Barra inferior ---------- */
  .barra-inf {
    background: color-mix(in srgb, var(--surface) 86%, transparent);
    -webkit-backdrop-filter: saturate(180%) blur(18px);
    backdrop-filter: saturate(180%) blur(18px);
    border-top: 1px solid var(--border-sutil);
  }
  /* Sin color-mix ni difuminado, opaca. Nunca translucida sin blur:
     seria texto encima de texto. */
  @supports not (backdrop-filter: blur(1px)) {
    .barra-inf { background: var(--surface); }
  }

  .barra-inf-item {
    gap: var(--s0);
    padding: 6px 2px calc(6px);
    font-size: var(--fs-3xs);
    font-weight: var(--peso-medio);
    color: var(--text-3);
    -webkit-tap-highlight-color: transparent;
  }

  /* La pastilla del icono. Existe SIEMPRE (transparente) para que al
     activarse no se mueva ni un pixel de la maqueta. */
  .barra-inf-item .icono {
    box-sizing: content-box;
    padding: 4px 14px;
    border-radius: var(--r-full);
    background: transparent;
    color: var(--text-3);
    transition: background var(--t-rapida) var(--ease),
                color var(--t-rapida) var(--ease),
                transform var(--t-rapida) var(--ease);
  }

  .barra-inf-item:active { background: none; }
  .barra-inf-item:active .icono { transform: scale(.9); background: var(--pressed); }

  .barra-inf-item.activo { color: var(--brand-600); font-weight: var(--peso-fuerte); }
  .barra-inf-item.activo .icono {
    background: var(--selected);
    color: var(--brand-600);
  }
  .barra-inf-item:focus-visible {
    outline: var(--foco);
    outline-offset: -3px;
    border-radius: var(--r-md);
  }

  /* Mientras el menu esta abierto, "Mas" se marca como activo: el
     usuario tiene que ver de donde salio lo que esta mirando. */
  body.menu-abierto #abrirMenuInf { color: var(--brand-600); }
  body.menu-abierto #abrirMenuInf .icono {
    background: var(--selected);
    color: var(--brand-600);
  }

  /* ---------- El menu, como hoja inferior ---------- */
  .lateral {
    top: auto;
    right: 0;
    left: 0;
    bottom: 0;
    width: 100%;
    max-height: 88dvh;
    padding-top: 0;
    border-right: 0;
    border-radius: var(--r-hoja) var(--r-hoja) 0 0;
    background: var(--sheet);
    box-shadow: var(--sh-hoja);
  }
  .menu-abierto .lateral {
    animation: hoja-sube var(--t-lenta) var(--ease-suave);
    /* El arrastre lo escribe app.js. Sin JavaScript vale 0. */
    transform: translateY(var(--hoja-arrastre, 0px));
  }
  .menu-abierto .lateral:not(.arrastrando) { transition: transform var(--t-media) var(--ease); }
  .menu-abierto .lateral.arrastrando { animation: none; transition: none; }

  /* El asa. Mismo lenguaje que .hoja: lo que sube desde abajo se baja
     deslizando, y eso se dice con una barra, no con un texto. */
  .lateral::before {
    content: "";
    flex: none;
    width: 40px;
    height: 4px;
    margin: 10px auto 2px;
    border-radius: var(--r-full);
    background: var(--border-2);
  }

  /* La cruz de cerrar sobra: la hoja se cierra deslizandola, tocando
     fuera o con el mismo boton "Mas" que la abrio. Se deja en el HTML
     -es el ancla de foco y el camino con teclado- pero se recoloca
     donde no compita con la marca del negocio. */
  .lateral-cerrar { top: var(--s2); right: var(--s2); }

  .lateral-marca {
    padding: var(--s3) var(--s4) var(--s4);
    padding-right: 52px;
    border-bottom: 1px solid var(--border-sutil);
  }

  .lateral-nav { padding: var(--s3) var(--s2) var(--s4); }
  .lateral-grupo { margin-bottom: var(--s3); }
  .lateral-titulo {
    padding: 0 var(--s3);
    margin-bottom: var(--s1);
    letter-spacing: var(--ls-etiqueta);
  }

  /* Cada destino: pastilla de icono a la izquierda, como una fila de
     ajustes de aplicacion. El icono suelto sobre el fondo se perdia. */
  .lateral-enlace {
    gap: var(--s3);
    min-height: 52px;
    padding: var(--s2) var(--s3);
    font-size: var(--fs-base);
    font-weight: var(--peso-medio);
    -webkit-tap-highlight-color: transparent;
  }
  .lateral-enlace .icono {
    box-sizing: content-box;
    padding: 7px;
    border-radius: 9px;
    background: var(--surface-2);
    color: var(--text-2);
  }
  .lateral-enlace:active { background: var(--pressed); }
  .lateral-enlace.activo { background: var(--selected); font-weight: var(--peso-fuerte); }
  .lateral-enlace.activo .icono { background: var(--brand-600); color: var(--text-inv); }

  .lateral-pie {
    border-top: 1px solid var(--border-sutil);
    padding: var(--s3) var(--s3) calc(var(--s3) + env(safe-area-inset-bottom));
    background: var(--sheet);
  }

  /* El velo del menu comparte capa con las hojas: si alguna vez se
     abren las dos, la ultima que se abre queda encima. */
  .velo { z-index: calc(var(--z-hoja) - 1); }
  .lateral { z-index: var(--z-hoja); }
}


/* =====================================================================
   42f. LA SUCURSAL, EN LA BARRA SUPERIOR
   ---------------------------------------------------------------------
   Cambiar de sucursal es la tercera cosa mas frecuente en telefono
   (despues de ver el dia y ver el inventario), y estaba resuelta con un
   <select> nativo dentro de la barra superior. Un desplegable del
   sistema no puede ensenar cuantas unidades tiene cada sucursal ni si
   su caja esta abierta, que es justo lo que se necesita saber ANTES de
   cambiar.

   Ahora en telefono hay una pastilla con el nombre de la sucursal que
   abre una hoja con esa informacion. El <select> sigue existiendo en el
   HTML y es lo que se ve en tableta y escritorio -donde un desplegable
   es lo correcto- y tambien en telefono si no hay JavaScript.

   LA SEGURIDAD NO CAMBIA NI UN PASO. La hoja pinta un <form> POST con
   su csrf_field() al MISMO cliente/sucursal_cambiar.php de siempre, que
   comprueba en el servidor que la sucursal este entre las autorizadas.
   La hoja solo ofrece las sucursales que sucursales_autorizadas() ya
   devolvio; escribir otra a mano sigue dando error, no acceso.
   ===================================================================== */

/* La pastilla solo existe en telefono y solo con JavaScript. */
.topbar-suc-chip { display: none; }

@media (max-width: 767px) {
  .con-js .topbar-suc-chip {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    max-width: 42vw;
    min-height: 34px;
    padding: 0 var(--s2) 0 var(--s3);
    background: var(--surface-2);
    border: 1px solid var(--border-sutil);
    border-radius: var(--r-full);
    color: var(--text);
    font-family: inherit;
    font-size: var(--fs-sm);
    font-weight: var(--peso-fuerte);
    line-height: 1;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
  }
  .con-js .topbar-suc-chip:active { background: var(--pressed); }
  .topbar-suc-chip-txt {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .topbar-suc-chip .icono { flex: none; color: var(--text-3); }

  /* Con la pastilla puesta, el desplegable de siempre se retira. Sin
     JavaScript (.con-js ausente) esta regla no aplica y el <select>
     sigue siendo el camino. */
  .con-js .topbar-sucursal { display: none; }

  /* Con la pastilla puesta, el subtitulo de la marca cede el sitio: el
     nombre del negocio ya esta en la hoja del menu y en el saludo del
     tablero, y aqui compite con el dato que si cambia -en que sucursal
     estas-. El reparto de columnas se decide en 42q, que es donde se
     retira la hamburguesa y se sabe cuantos huecos quedan. */
  .con-js .topbar:has(.topbar-suc-chip) .marca span { display: none; }
}

/* ---------- Filas de la hoja de sucursales ---------- */
.suc-hoja-form { margin: 0; }
.suc-hoja-estado {
  display: inline-flex;
  align-items: center;
  gap: 5px;
}
.suc-hoja-punto {
  width: 7px;
  height: 7px;
  border-radius: var(--r-full);
  background: var(--text-3);
}
.suc-hoja-punto.abierta { background: var(--ok); }
.suc-hoja-nota {
  margin: var(--s3) var(--s4) 0;
  padding-top: var(--s3);
  border-top: 1px solid var(--border-sutil);
  font-size: var(--fs-xs);
  color: var(--text-3);
}


/* =====================================================================
   42g. EL TABLERO, EN UN TELEFONO   (reconstruido)
   =====================================================================
   El tablero de escritorio contesta "como va el negocio": cuatro
   metricas, la grafica de la semana, dos rankings, el mes y las ultimas
   ventas. Con 1440px delante eso es exactamente lo que hace falta y no
   se toca.

   El de telefono contesta otras cinco preguntas, en tres segundos, con
   el aparato en una mano y alguien esperando enfrente:

       1. cuanto vendi hoy
       2. cuantas ventas hice
       3. que puedo hacer ahora
       4. como esta el negocio    (caja, inventario, alertas)
       5. que ha pasado           (ultimas ventas)

   QUE CAMBIO RESPECTO DE LA VERSION ANTERIOR
   ------------------------------------------
   La anterior ya priorizaba bien, pero lo hacia con CAJAS: una tarjeta
   para la cifra, otra para la lista, otra para el estado de la caja y
   un panel entero para "Tus sucursales hoy". Cinco rectangulos
   consecutivos con borde, sombra y esquina redondeada. Eso es lo que
   hace que una pantalla se lea a panel de administracion aunque los
   datos esten en el orden correcto.

   Ahora la composicion es la de una aplicacion:

       la cifra y la accion    van sobre el fondo, SIN superficie
       lo operativo            en dos laminas a sangre, sin radio ni
                               sombra, separadas por un hueco
       lo que agrupa           el aire y la junta de un pixel

   Tarjetas en la pantalla: NINGUNA. Y no por capricho: un borde
   redondeado alrededor de un dato dice "esto es un objeto aparte". La
   cifra del dia, la accion y el estado del negocio no son tres objetos
   aparte: son una lectura continua.

   QUE SE RETIRA DEL TELEFONO Y POR QUE
   ------------------------------------
   Nada de esto desaparece del producto; deja de repetirse aqui.

     la sucursal en el saludo   la pastilla de la barra superior la dice
                                a cuatro centimetros de distancia
     "Tus sucursales hoy"       seis columnas por sucursal es analisis.
                                Vive en Sucursales y en el selector.
     el estado de la caja       pasa a ser una fila de la lista, con su
                                efectivo esperado, que es mas util que
                                la barra de 80px que ocupaba
     la rejilla de metricas     sus cuatro cifras son las de arriba
     la grafica, el mes, los rankings   siguen en escritorio

   Todos los destinos siguen a un toque: desde la lista o desde la barra
   inferior. No se esconde ninguna funcion.
   ===================================================================== */

.tm { display: none; }

@media (max-width: 767px) {

  .tm { display: block; }

  /* -------------------------------------------------------------------
     LO QUE EL TABLERO DE TELEFONO RELEVA
     -------------------------------------------------------------------
     Se apunta a .metricas-zona y no a .metricas-heroe porque
     ui_metricas_*() envuelve SIEMPRE la rejilla en esa zona: la rejilla
     no es hermana de nada y un selector contra ella no encuentra nada.
     Con :has() se ataca al envoltorio, y solo cuando lleva dentro la
     rejilla de protagonista; las metricas del mes se siguen viendo mas
     abajo, que es donde toca.

     .tablero-acciones-movil se queda en el HTML aunque no se pinte: la
     regla que oculta las acciones de la cabecera de pagina lo usa como
     senal con :has(), y :has() lo sigue encontrando.

     Y AQUI ESTA LA DECISION MAS FUERTE DE TODO EL TABLERO.

     Medido: con la composicion nueva puesta pero el resto todavia
     debajo, la pagina seguia midiendo 3.954px en un telefono y contenia
     CATORCE superficies con borde. Debajo de la lamina de ultimas
     ventas venian la grafica de la semana, "Necesitan compra", el
     separador del mes, cuatro metricas mensuales, dos rankings, otra
     lista de ultimas ventas y los productos sin salida.

     Todo eso es ANALISIS: se mira sentado, comparando periodos. En un
     telefono, detras de la operacion del dia, son tres mil pixeles de
     panel-panel-panel que nadie desplaza y que ademas convierten la
     pantalla en lo que este trabajo venia a dejar de ser.

     Se relevan del telefono. NO se pierde nada:

       la grafica y el mes      Reportes  (ademas con filtro de periodo)
       "Necesitan compra"       Alertas
       las ultimas ventas       la lamina de arriba, y el Historial
       los rankings             Reportes

     Y el tablero cierra con un enlace explicito a Reportes, para que
     "no lo veo aqui" no se confunda con "ya no esta".

     En escritorio no cambia ni una linea: alli el analisis es
     exactamente lo que se viene a hacer.
     ------------------------------------------------------------------- */
  .tm ~ .metricas-zona,
  .tm ~ .tablero-acciones-movil,
  .tm ~ .caja-estado,
  .tm ~ .tablero-seccion,
  .tm ~ .rejilla,
  .tm ~ .panel { display: none; }

  /* -------------------------------------------------------------------
     EL SALUDO, DE VUELTA A SU SITIO
     -------------------------------------------------------------------
     En la version anterior el saludo era el encabezado visible a 24px,
     porque el h1 "Inicio" se habia retirado y alguien tenia que
     encabezar la pantalla. Ahora quien encabeza es la cifra, y el
     saludo vuelve a ser lo que es: una linea de cortesia con la fecha.

     Si el saludo pesara 24px competiria con los 46 de la cifra y la
     mirada tendria que elegir. El objetivo es justo el contrario: que
     no haya nada que elegir.
     ------------------------------------------------------------------- */
  /* El contenido arranca con aire de verdad. MEDIDO: el saludo empezaba
     16px por debajo de la barra superior, que es el relleno normal de
     una pantalla de listado. Aqui no vale: lo primero que hay debajo es
     una cifra de 47px, y una cifra grande pegada al borde de la barra
     se lee apretada aunque tenga sitio de sobra a los lados.

     28px es lo que hace que la pantalla ABRA en vez de empezar. */
  .contenido:has(.tm) { padding-top: 28px; }

  .contenido:has(.tm) > .tablero-saludo { margin-bottom: var(--s5); }
  .contenido:has(.tm) > .tablero-saludo h2 {
    font-size: var(--fs-base);
    font-weight: var(--peso-fuerte);
    letter-spacing: 0;
    line-height: 1.3;
    color: var(--text);
  }
  .contenido:has(.tm) > .tablero-saludo p {
    margin-top: 1px;
    font-size: var(--fs-xs);
    color: var(--text-3);
  }
  /* La sucursal ya esta en la pastilla de arriba. */
  .tablero-saludo-suc { display: none; }

  /* -------------------------------------------------------------------
     LO QUE VIVIA AQUI Y YA NO   (retirado en la Fase 2.3)
     -------------------------------------------------------------------
     .tm-hero*, .tm-accion*, .tm-grupo*, .tm-rotulo* y .tm-atencion.

     Componian el tablero de telefono en LAMINAS A SANGRE. La Fase 2.2 lo
     recompuso en tarjetas (seccion 60) y ninguna de esas clases quedo en
     el marcado. Comprobado por busqueda en todo el proyecto -php, js y
     html- antes de borrar: cero referencias.

     Lo que SI sigue vivo de esta seccion y no se toca: .tm-venta,
     .tm-vacio, .tm-pie, .tm-pie-enlace y .tablero-saludo-suc.
     ------------------------------------------------------------------- */




  /* -------------------------------------------------------------------
     LA FILA DE UNA VENTA
     -------------------------------------------------------------------
     Un renglon, no dos: folio, separador y hora a la izquierda; el
     importe a la derecha. Sin subtitulo que rellenar.

     El folio va en cifras tabulares y el separador en gris: asi los
     folios de tres ventas seguidas caen en columna y la vista los
     recorre sin leerlos uno por uno, que es justo lo que se hace en una
     lista de "lo que acaba de pasar".
     ------------------------------------------------------------------- */
  .tm-venta .lista-titulo {
    font-family: var(--font-num);
    font-variant-numeric: tabular-nums;
    font-weight: var(--peso-medio);
    letter-spacing: .01em;
    color: var(--text-2);
  }





  .tm-vacio {
    margin: 0;
    padding: var(--s2) var(--s4) var(--s5);
    font-size: var(--fs-sm);
    color: var(--text-3);
    line-height: 1.5;
  }

  /* El camino al analisis. Discreto y centrado: no es una accion del
     dia, es una salida. */
  .tm-pie { margin: 0; text-align: center; }
  .tm-pie-enlace {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--s1);
    min-height: var(--tap);
    padding-inline: var(--s3);
    color: var(--text-2);
    font-size: var(--fs-sm);
    font-weight: var(--peso-medio);
    text-decoration: none;
  }
  .tm-pie-enlace .icono { color: var(--text-3); }
  .tm-pie-enlace:active { color: var(--text); }

  /* El boton primario del tablero mide 54px y ocupa el ancho entero. En
     oscuro, --sh-marca sobre una superficie de ese tamano deja de ser
     una sombra de color y se convierte en un halo: exactamente el
     "glow" que el encargo pide evitar. Se recorta a la mitad del radio
     y a un tercio de la opacidad; sigue habiendo luz, deja de haber
     efecto.

     Fase 2.3: la regla apuntaba a `.tm-accion .btn`, que dejo de
     existir. NO se borro con el resto del bloque muerto porque el
     problema que corrige sigue ahi: solo cambio de nombre el boton.
     Ahora apunta a .tm-cta. */
  :root[data-tema="oscuro"] .tm-cta {

    box-shadow: var(--sh-btn-luz);
  }
}


/* =====================================================================
   42h. EL PUNTO DE VENTA, EN UN TELEFONO
   ---------------------------------------------------------------------
   Es la pantalla que mas se usa, de pie, con una mano y con alguien
   esperando enfrente. Todo lo demas del sistema puede tardar un segundo
   mas; esta no.

   EL FLUJO, EN CUATRO PASOS Y SIN VIAJES
   --------------------------------------
       1. buscar o tocar el producto     el catalogo es la pantalla
       2. agregar                        un toque, con acuse de recibo
       3. revisar el carrito             sube como hoja, no hay que ir
       4. cobrar                         boton permanente abajo

   QUE ESTABA MAL
   El carrito vivia DESPUES del catalogo. Con sesenta productos en una
   cuadricula de dos columnas, "ver el carrito" era desplazar treinta
   filas, y volver a vender era desplazarlas otra vez hacia arriba. La
   barra de cobro lo tapaba a medias con un boton que llevaba alli.

   Ahora ese mismo carrito -el mismo elemento, el mismo formulario, el
   mismo JavaScript- sube como hoja al tocar el total. Se revisa, se
   ajusta una cantidad, se elige la forma de pago y se cobra sin salir
   nunca del catalogo. Al cerrarla, el catalogo esta donde se dejo.

   EL BUSCADOR SE QUEDA ARRIBA
   Un lector de codigos de barras teclea y manda Enter: si el campo no
   esta a la vista, no tiene el foco, y la lectura se pierde. Ahora el
   buscador se pega bajo la barra superior y esta siempre disponible.
   ===================================================================== */

@media (max-width: 767px) {

  /* ---------- El buscador, siempre a mano ---------- */
  .pos-catalogo > .panel-cab {
    position: sticky;
    top: var(--topbar-movil);
    z-index: 5;
    background: var(--surface);
    /* El panel tiene esquinas redondeadas y el borde superior del
       sticky tiene que morder ahi, no en el aire. */
    border-radius: var(--r-lg) var(--r-lg) 0 0;
  }
  /* OLA 1: el alto y la letra del buscador los pone .buscador (57d).
     Aqui vivia el segundo 46px literal del punto de venta. */

  /* La linea de "60 productos / Hoy: 8 ventas" es contexto y se come
     40px de la primera pantalla del que viene a vender. Se queda, mas
     baja de tono y en una sola linea. */
  .pos-cab-pie {
    gap: var(--s2);
    font-size: var(--fs-xs);
    color: var(--text-3);
  }

  /* ---------- Tocar un producto tiene acuse de recibo ----------
     Sin esto, en un telefono no hay forma de saber si el toque entro:
     no hay hover y la cuadricula no se mueve. El globo con la cantidad
     ya existia; lo que faltaba era el instante del toque. */
  .pos-item { transition: transform var(--t-rapida) var(--ease); }
  .pos-item:active:not([disabled]) { transform: scale(var(--pulsa-lg)); }

  /* ---------- El carrito, como hoja ---------- */

  /* Cerrado no se pinta: en telefono el carrito se abre desde la barra
     de cobro. Sigue en el HTML -es el formulario de la venta- y sigue
     funcionando sin JavaScript, porque sin JavaScript no hay barra de
     cobro y esta regla se anula abajo. */
  .con-js .pos-carrito { display: none; }

  .con-js .pos-carrito.como-hoja {
    display: flex;
    flex-direction: column;
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    top: auto;
    z-index: var(--z-hoja);
    max-height: var(--hoja-max);
    margin: 0;
    background: var(--sheet);
    border: 0;
    border-radius: var(--r-hoja) var(--r-hoja) 0 0;
    box-shadow: var(--sh-hoja);
    animation: hoja-sube var(--t-lenta) var(--ease-suave);
    transform: translateY(var(--hoja-arrastre, 0px));
    overscroll-behavior: contain;
  }
  .con-js .pos-carrito.como-hoja:not(.arrastrando) {
    transition: transform var(--t-media) var(--ease);
  }
  .con-js .pos-carrito.como-hoja.arrastrando { animation: none; transition: none; }

  .pos-carrito .hoja-asa { display: none; }
  .pos-carrito.como-hoja .hoja-asa { display: flex; }
  .pos-carrito-cerrar { display: none; }
  .pos-carrito.como-hoja .pos-carrito-cerrar { display: inline-flex; }

  .pos-carrito.como-hoja .pos-carrito-cab {
    padding-top: 0;
    padding-bottom: var(--s3);
    border-bottom: 1px solid var(--border-sutil);
  }
  /* EL REPARTO DEL ALTO. Medido a 375x812 con un producto en el
     carrito: la hoja mide 594px y el pie -subtotal, descuento, total,
     forma de pago, datos del cliente y cobrar- se llevaba 391. A las
     lineas del carrito les quedaban 150px, o sea dos.

     El reparto correcto no es dar un alto fijo a cada parte, es decir
     QUIEN CEDE:

       las lineas    crecen con lo que haya, pero nunca por debajo de
                     96px: siempre se ve que hay algo dentro.
       el pie        se queda con lo que necesita y, si no cabe, se
                     desplaza por dentro.
       cobrar        no cede NUNCA. Se queda pegado al borde inferior
                     del pie aunque el pie se desplace. */
  .pos-carrito.como-hoja > .panel-cuerpo {
    flex: 1 1 auto;
    min-height: 96px;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    overscroll-behavior: contain;
  }

  .pos-carrito.como-hoja .pos-pie {
    flex: 0 1 auto;
    min-height: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
    padding: var(--s3) var(--s4) 0;
    background: var(--sheet);
    border-top: 1px solid var(--border);
  }

  /* Dentro de la hoja el boton de cobrar SI se ve: es la accion
     principal de la hoja. La barra de abajo se retira mientras tanto,
     porque cobrar dos veces desde dos sitios a la vez no es una
     comodidad, es una duda. */
  .pos-carrito.como-hoja .pos-cobrar {
    display: flex;
    position: sticky;
    bottom: 0;
    z-index: 1;
    width: 100%;
    min-height: 52px;
    margin-top: var(--s3);
    margin-bottom: calc(var(--s3) + env(safe-area-inset-bottom));
    font-size: var(--fs-lg);
  }
  /* El boton pegado necesita fondo propio por debajo: si no, las lineas
     que se desplazan por detras asoman por el hueco del area segura. */
  .pos-carrito.como-hoja .pos-pie::after {
    content: "";
    position: sticky;
    bottom: 0;
    display: block;
    height: calc(var(--s3) + env(safe-area-inset-bottom));
    margin-top: calc((var(--s3) + env(safe-area-inset-bottom)) * -1);
    background: var(--sheet);
  }
  body.hoja-abierta .pos-barra:not([hidden]) { display: none; }

  /* Formas de pago: cuatro pastillas grandes en dos filas. Antes eran
     cuatro radios de sistema de 20px con su etiqueta al lado.
     Se aprieta lo que se puede sin bajar del area tactil: el alto de
     este bloque es lo que le quitaba sitio a las lineas del carrito. */
  .pos-carrito.como-hoja .pos-pago {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--s2);
    margin-top: var(--s3);
  }
  .pos-carrito.como-hoja .pos-pago legend { margin-bottom: var(--s1); }
  .pos-pago-op { min-height: var(--tap); }

  /* El total, en el tamano en el que se lee sin acercarse. */
  .pos-carrito.como-hoja .pos-total span:last-child { font-size: var(--fs-2xl); }
}

/* Velo de los paneles que se presentan como hoja (el carrito). Lo crea
   app.js una sola vez y lo comparten todos. */
.hoja-velo-suelto {
  position: fixed;
  inset: 0;
  z-index: calc(var(--z-hoja) - 1);
}
.hoja-velo-suelto[hidden] { display: none; }


/* =====================================================================
   42i. FORMULARIOS DE APLICACION
   ---------------------------------------------------------------------
   Tres cosas, y las tres se notan en el primer segundo de uso.

   1. NINGUN CAMPO POR DEBAJO DE 16px EN TELEFONO.
      No es una decision tipografica: es la unica forma de que iOS no
      haga zoom al enfocar un campo. Cuando hace zoom, la pagina se
      descoloca, el teclado tapa el campo siguiente y el usuario tiene
      que pellizcar para volver. Es EL detalle que mas delata a una web
      metida en un telefono, y se arregla con un numero.

      El sistema usa --fs-base = 15px, que en escritorio es correcto y
      no se toca. En telefono los CONTROLES suben a 16. El resto de la
      tipografia no cambia.

   2. EL BOTON PRIMARIO NO SE SEPARA DE SU FORMULARIO.
      Ya existia .form-acciones pegada abajo (seccion 29). Lo que
      faltaba era el remate: sin fondo propio el contenido se veia pasar
      por detras, y el borde superior recto sobre una tarjeta redondeada
      dejaba dos esquinas al aire. Aqui se cierra.

   3. LOS CAMPOS SE LEEN COMO CAMPOS.
      Alto de 48px -no 44: un campo no es un boton, hay que ver el texto
      dentro- y foco con anillo, no solo con cambio de borde.
   ===================================================================== */

@media (max-width: 767px) {

  .campo input,
  .campo select,
  .campo textarea,
  input[type="text"],
  input[type="email"],
  input[type="tel"],
  input[type="password"],
  input[type="number"],
  input[type="search"],
  input[type="date"],
  /* El campo de fecha se ESCAPABA de esta regla: .campo-fecha
     input[type="date"] (seccion 34) pesa 0,1,1 y la lista de arriba
     0,0,1, asi que ganaba con font-size: var(--fs-base) -15px- y
     min-height: 44. Quince pixeles es justo por debajo del umbral en
     el que iOS hace zoom al enfocar, que es la razon de ser de esta
     regla. Se le nombra con su misma especificidad para que la cumpla. */
  .campo-fecha input[type="date"],
  select,
  textarea {
    font-size: 16px;
    min-height: 48px;
  }
  textarea { min-height: 96px; }

  /* El rotulo, mas cerca de su campo que del campo de arriba: es lo que
     hace que un formulario se lea como pares y no como una lista. */
  .campo { margin-bottom: var(--s4); }
  .campo > label {
    display: block;
    margin-bottom: 6px;
    font-size: var(--fs-sm);
    font-weight: var(--peso-fuerte);
  }

  /* Remate de la barra de acciones anclada. La regla que la ancla vive
     en la seccion 29 y no se toca; aqui solo se termina de dibujar. */
  .form-acciones {
    padding-bottom: calc(var(--s3) + env(safe-area-inset-bottom));
    border-top: 1px solid var(--border-sutil);
  }
  .form-acciones .btn { min-height: 50px; }
  /* Dos acciones: la principal manda y la otra acompana. Nunca dos
     botones del mismo ancho, que es como se toca el equivocado. */
  .form-acciones:has(> .btn + .btn) { display: flex; gap: var(--s2); }
  .form-acciones > .btn:first-child { flex: 2; }
  .form-acciones > .btn + .btn { flex: 1; }
}


/* =====================================================================
   42j. ENTRAR Y REGISTRARSE, EN UN TELEFONO
   ---------------------------------------------------------------------
   En escritorio la pantalla de acceso son dos mitades: la marca a la
   izquierda y una tarjeta elevada a la derecha. Esta bien y se queda.

   En telefono esa tarjeta era una caja blanca de 349px flotando con
   13px de margen sobre un fondo con lavado de color. Una tarjeta que
   ocupa casi toda la pantalla y deja un marco de trece pixeles no se
   lee como una tarjeta: se lee como una web que no supo que hacer con
   el ancho sobrante.

   Una aplicacion no pone su pantalla de entrada dentro de una tarjeta.
   Aqui la tarjeta desaparece y el formulario se apoya directamente
   sobre el fondo de marca, que asi se ve entero. Es el mismo marcado:
   solo se apaga el borde, la sombra y el fondo.
   ===================================================================== */

@media (max-width: 767px) {

  .acceso-panel { padding: 0; }

  .acceso-caja,
  .acceso-caja-ancha {
    max-width: none;
    padding: var(--s5) var(--s4) var(--s6);
    background: none;
    border: 0;
    border-radius: 0;
    box-shadow: none;
  }

  /* El aire de abajo lo pone el area segura, no un numero fijo. */
  .acceso-panel-alto { padding-bottom: env(safe-area-inset-bottom); }

  /* ---------- Registro: el avance se dice, no se dibuja ----------
     Dos circulos numerados con su etiqueta al lado costaban 26px de
     alto y no decian cuanto queda. Una barra de dos tramos si: se ve
     de un vistazo que son dos pasos y en cual estas. */
  .registro-avance {
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: 1fr;
    gap: var(--s2);
    margin-bottom: var(--s5);
  }
  .registro-avance li {
    display: block;
    flex: none;
    padding-top: var(--s2);
    border-top: 3px solid var(--border);
    font-size: var(--fs-xs);
    color: var(--text-3);
    white-space: normal;
  }
  /* El numero del circulo ya no hace falta: el tramo ES el numero. */
  .registro-avance li span { display: none; }
  .registro-avance .es-actual {
    border-top-color: var(--brand-600);
    color: var(--text);
    font-weight: var(--peso-fuerte);
  }
  .registro-avance .es-hecho { border-top-color: var(--ok); color: var(--text-2); }

  /* Las acciones del paso, a lo ancho y con el peso repartido. */
  .registro-siguiente { width: 100%; min-height: 52px; }
  .registro-acciones { gap: var(--s2); }
  .registro-acciones .btn { min-height: 52px; }
  .registro-acciones .btn-sec { flex: 0 0 auto; padding-inline: var(--s4); }

  /* La casilla legal es un objetivo tactil, no una linea de texto con
     un cuadradito. Toda la fila responde al toque. */
  .casilla-legal { align-items: flex-start; gap: var(--s3); padding: var(--s2) 0; }
  .casilla-legal input[type="checkbox"] { width: 22px; height: 22px; margin-top: 1px; }
}


/* =====================================================================
   42k. REPARACIONES DE LA FICHA APILADA
   ---------------------------------------------------------------------
   El sistema generico que apila una tabla como fichas (seccion 14)
   convierte cada celda en una fila flexible: la etiqueta a la izquierda
   y el valor a la derecha. Funciona para el 90% de las celdas, que
   llevan UN dato.

   Se rompe cuando una celda lleva varios elementos. MEDIDO a 360px en
   Sucursales: la celda del nombre lleva cuatro (nombre, distintivo,
   "trabajando aqui" y la direccion) y, sin permiso para partirse, la
   direccion terminaba en el pixel 427 de una pantalla de 360. Texto
   fuera de la pantalla, sin barra de desplazamiento que lo alcance.

   Dos reglas lo arreglan para TODAS las tablas del sistema, no solo
   para esta:

     el renglon se puede partir
     las lineas de apoyo se llevan su propio renglon
   ===================================================================== */

@media (max-width: 767px) {

  .tabla-datos td {
    flex-wrap: wrap;
    row-gap: var(--s0);
  }
  /* Un elemento flexible no baja de su contenido salvo que se le diga.
     Sin esto, un nombre largo empuja a sus vecinos fuera de la ficha. */
  .tabla-datos td > * { min-width: 0; overflow-wrap: anywhere; }

  /* Las lineas de apoyo -direccion, valor a costo, ultimo acceso- son
     un segundo renglon de la celda, no un vecino del dato principal. */
  .tabla-datos td .suc-meta,
  .tabla-datos td .usr-acceso-mini { flex: 1 0 100%; text-align: right; }
}

/* =====================================================================
   42l. SUCURSALES: LA FICHA DE TELEFONO   (RETIRADA en la Ola 3)
   ---------------------------------------------------------------------
   Aqui vivia una ficha de telefono compuesta con areas de rejilla SOBRE
   la tabla .tabla-sucursales, a 640px.

   Ya no alcanza a nadie. La Fase 12 partio la pantalla en dos piezas:
   .suc-tactil con su lista de filas propia y .suc-escritorio con la
   tabla, y apaga la segunda por debajo de 768 (seccion 48). O sea que
   la tabla que este bloque recomponia no esta en la pantalla cuando el
   bloque entraba.

   Se retira entera, con las tres reglas de orden de .suc-cols que la
   acompanaban: la rejilla base de .suc-cols ya es de UNA columna hasta
   1024 y el listado ya va antes que el formulario en el marcado, asi
   que el display:flex con order no cambiaba nada.

   Es la segunda composicion de esta pantalla que caduca en silencio al
   sustituirse el componente; queda anotada para que no se copie.
   ===================================================================== */


/* =====================================================================
   42m. OBJETIVOS TACTILES QUE SE QUEDARON CORTOS
   ---------------------------------------------------------------------
   Medido a 360px con un barrido por las doce pantallas del panel del
   negocio: cuatro controles por debajo de 32px de alto.

       .v-enlace           20px   el folio, en el historial de ventas
       .rep-rango-abrir    25px   abrir el selector de rango
       .usr-enlace-btn     18px   "volver a los del rol"
       casillas            18px   sucursales y permisos de un usuario

   Los cuatro se alcanzan con el raton sin problema y por eso nadie los
   habia visto. Con el pulgar, no.

   No se agrandan a lo bruto: se les da area de toque. Un enlace de
   texto sigue pareciendo un enlace de texto; lo que crece es la zona
   que responde, que es lo que importa.
   ===================================================================== */

@media (max-width: 767px) {

  /* El folio de una venta: toda su zona responde, no las 20px del
     texto. Sigue siendo un enlace, no se convierte en un boton. */
  .v-enlace {
    display: inline-flex;
    align-items: center;
    min-height: var(--tap);
    padding-right: var(--s2);
  }

  .rep-rango-abrir { min-height: var(--tap); padding-inline: var(--s2); }

  .usr-enlace-btn {
    min-height: 34px;
    padding-inline: 2px;
  }

  /* Las casillas del sistema se dibujan a 13px y con el dedo se falla.
     La etiqueta que las envuelve ya es el area de toque -y ya mide
     --tap-, pero la casilla tiene que VERSE bastante para saber si
     esta marcada de un vistazo. */
  input[type="checkbox"],
  input[type="radio"] {
    width: 20px;
    height: 20px;
    accent-color: var(--brand-600);
  }
  .usr-casilla input { margin-top: 1px; }
}


/* =====================================================================
   42n. ESTADOS DE INTERACCION UNIFICADOS
   ---------------------------------------------------------------------
   Hasta aqui, "algo esta reaccionando al raton o al dedo" se escribia
   siempre igual: background: var(--surface-2).

   En claro funciona por casualidad: --surface-2 es un hueco calido y
   pasa por realce. En oscuro no, y ademas mezcla tres papeles que no
   son el mismo:

       --surface-2   lo que va DENTRO de un panel  (input, cabecera)
       --hover       el raton esta encima
       --pressed     el dedo o el boton estan abajo
       --selected    esto es lo elegido

   Al ser translucidos, --hover y --pressed funcionan sobre CUALQUIER
   fondo -panel, hoja, fila tenida- sin tener que saber cual es. Eso es
   lo que --surface-2 no podia hacer: era un color opaco y borraba el
   fondo que tuviera debajo.

   Que NO se hace aqui: pasar las cuarenta reglas del archivo a los
   tokens nuevos de una vez. Se cambian las superficies que se tocan de
   verdad -filas, enlaces del menu, botones sin fondo, pestanas- y las
   demas se quedan como estan hasta que se toquen por otro motivo.
   Reescribir cuarenta reglas para no ver ninguna diferencia en claro
   es la forma mas rapida de romper algo a cambio de nada.

   Y ADEMAS: en un dispositivo tactil no existe :hover. Los navegadores
   lo simulan y lo dejan PEGADO despues del toque, asi que una fila que
   se toco se queda resaltada hasta que se toque otra cosa. Por eso el
   hover se acota con (hover: hover).
   ===================================================================== */

@media (hover: hover) {
  .tabla-datos tbody tr:hover td { background: var(--hover); }
  .ranking-fila:hover            { background: var(--hover); }
  .modal-cerrar:hover            { background: var(--hover); }
  .lateral-enlace:hover          { background: var(--hover); }
}

.lateral-enlace:active,
.modal-cerrar:active { background: var(--pressed); }
/* OLA 2: .tabs .tab:active sale de aqui. El pulsado de la pestana lo
   declara .tab:active en la seccion 28, para las seis pantallas. */


/* =====================================================================
   42o. SUPERFICIES TEMPORALES: UN SOLO LENGUAJE
   ---------------------------------------------------------------------
   El sistema tiene tres cosas que se levantan por encima de la pagina y
   se van: el modal de confirmacion, el cajon del menu y las hojas
   nuevas. Los tres son la MISMA idea -una superficie temporal sobre un
   velo- y hasta ahora cada uno usaba su propio color de fondo.

   En claro daba igual: los tres eran blancos. En oscuro no, y es justo
   ahi donde se nota si un tema oscuro esta pensado o simplemente
   invertido: una superficie temporal tiene que separarse del fondo MAS
   que un panel, porque encima de ella no hay nada y debajo hay un velo.
   ===================================================================== */

/* OLA 4: el `.modal { background: var(--sheet) }` que vivia aqui esta
   ahora en la declaracion del componente, arriba. Era la misma regla
   escrita dos veces a doce mil lineas de distancia. */

/* En telefono el modal ya sube desde abajo (seccion 27a). Se le pone el
   mismo radio y la misma sombra que a una hoja para que las tres cosas
   que suben desde abajo se lean como la misma pieza. */
@media (max-width: 767px) {
  .modal {
    border-radius: var(--r-hoja) var(--r-hoja) 0 0;
    box-shadow: var(--sh-hoja);
  }
  .modal-fondo { padding: 0; }
}


/* =====================================================================
   42p. PROFUNDIDAD EN EL TEMA OSCURO
   ---------------------------------------------------------------------
   En claro, un panel se separa del fondo porque es MAS CLARO que el
   fondo y ademas tira una sombra. Las dos cosas trabajan.

   En oscuro la sombra no trabaja: una sombra negra sobre un fondo casi
   negro no existe. Si solo se cambian los colores, todo queda plano y
   el tema se siente a "los mismos componentes con otra paleta", que es
   exactamente lo que el encargo pedia evitar.

   Lo que de verdad da profundidad en oscuro es lo mismo que en el mundo
   fisico: EL FILO. El borde superior de una superficie recibe la luz
   que viene de arriba y se ve un punto mas claro que el resto. Es una
   linea de un pixel y es lo que hace que un panel parezca una lamina
   apoyada en vez de un rectangulo pintado.

   Se aplica solo a lo que de verdad se levanta -paneles, hojas,
   modales, barras ancladas- y nunca a lo que esta hundido (un input, la
   cabecera de una tabla), que tiene que seguir leyendose como un hueco.
   ===================================================================== */

/* OLA 6: .como-hoja entra aqui, y con el se van SEIS reglas.

   Un panel que por debajo de 768 se convierte en hoja lleva la clase
   .como-hoja -se la pone app.js-, y seis pantallas escribian cada una
   su propia regla de tema oscuro con esta misma declaracion, palabra
   por palabra: el formulario de Sucursales, el de Usuarios, los
   filtros de Reportes, el de Mi negocio, el de Configuracion y el de
   Avisos. No eran seis decisiones: era una, repetida.

   .pos-carrito.como-hoja ya estaba aqui y sobra la parte de delante:
   basta con .como-hoja. */
:root[data-tema="oscuro"] .panel,
:root[data-tema="oscuro"] .tarjeta,
:root[data-tema="oscuro"] .bloque-sup,
:root[data-tema="oscuro"] .modal,
:root[data-tema="oscuro"] .hoja-panel,
:root[data-tema="oscuro"] .como-hoja,
:root[data-tema="oscuro"] .toast {
  box-shadow:
    var(--filo-luz),
    var(--sh-2);
}

/* Las barras ancladas reciben la luz por el borde de ARRIBA, que es el
   que mira a la pagina. */
:root[data-tema="oscuro"] .barra-inf,
:root[data-tema="oscuro"] .pos-barra:not([hidden]),
:root[data-tema="oscuro"] .form-acciones {
  box-shadow:
    var(--filo-luz),
    var(--sh-arriba);
}

/* La barra superior no se levanta: es el borde de la ventana. Su unica
   separacion es la junta de abajo, y en oscuro tiene que ser una junta
   de luz, no de sombra. */
:root[data-tema="oscuro"] .topbar { border-bottom-color: var(--border-sutil); }

/* Los huecos NO reciben filo: si un input brillara por arriba dejaria
   de leerse como un hueco donde se escribe. Se anula por si alguna de
   las reglas de arriba los alcanza a traves de un panel. */
:root[data-tema="oscuro"] .campo input,
:root[data-tema="oscuro"] .campo select,
:root[data-tema="oscuro"] .campo textarea,
:root[data-tema="oscuro"] thead th { box-shadow: none; }

/* Un panel dentro de otro panel no puede tener el mismo color que su
   padre: en oscuro se pierde la junta. Sube al plano de lo elevado. */
:root[data-tema="oscuro"] .panel .panel,
:root[data-tema="oscuro"] .panel .tarjeta { background: var(--surface-3); }

/* La marca de "esto es lo elegido" en oscuro: --brand-50 es un azul
   MUY oscuro (#172554) y sobre el panel casi no se distingue. El token
   --selected es un azul translucido que si se ve sobre cualquier
   fondo. Se cambia donde marca eleccion, no donde es decoracion. */
/* OLA 6: "ESTOY AQUI" / "ESTE SOY YO", EN UN SOLO SITIO.

   Cinco sitios marcan la fila o el enlace que le corresponde a quien
   mira: el menu lateral, las sucursales del tablero, la tabla de
   sucursales, la de usuarios del negocio y la de permisos. Los cinco
   usan --brand-50 en claro, y los cinco tenian que dejar de usarlo en
   oscuro por el mismo motivo: ahi --brand-50 es #172554, casi el mismo
   color que el panel, y la marca desaparecia.

   Era una regla repetida cuatro veces en cuatro secciones distintas.
   Ahora es una, y --selected es justo el token que nombra esto. */
:root[data-tema="oscuro"] .lateral-enlace.activo,
:root[data-tema="oscuro"] .tabla-sucursales-hoy tr.es-actual td,
:root[data-tema="oscuro"] .tabla-sucursales tr.es-actual td,
:root[data-tema="oscuro"] .tabla-usuarios-neg tr.es-yo td,
:root[data-tema="oscuro"] .perm-usuarios tr.es-yo td {
  background: var(--selected);
}


/* =====================================================================
   42q. LA BARRA SUPERIOR DEL TELEFONO
   ---------------------------------------------------------------------
   UN SOLO CAMINO AL MENU.

   Habia dos: el boton de hamburguesa arriba a la izquierda y el boton
   "Mas" abajo a la derecha. Los dos abren EXACTAMENTE lo mismo. Dos
   controles para una funcion no es comodidad: es que el usuario tiene
   que aprender que son el mismo y, mientras tanto, uno de los dos le
   sobra en cada pantalla.

   Se queda el de abajo, por tres razones:
     - esta al alcance del pulgar; el de arriba no
     - lleva etiqueta ("Mas"), el de arriba solo un icono
     - ahora el menu SUBE desde abajo, asi que el gesto y el sitio del
       que sale coinciden

   Lo que NO se toca: la flecha de volver. En una pagina hija ese mismo
   hueco es la salida, y esa si es unica. Por eso se oculta el boton por
   su id (#abrirMenu) y no por su clase, que la comparte con la flecha.

   Y la barra se translucida como la de abajo. Las dos barras del shell
   son la misma idea -algo que flota sobre el contenido- y tienen que
   comportarse igual con la luz.
   ===================================================================== */

@media (max-width: 767px) {

  #abrirMenu { display: none; }

  /* EL REPARTO DE LA BARRA, en un solo sitio.

     Sin hamburguesa quedan tres huecos: marca, sucursal (si la hay) y
     campana. La marca arranca en el borde, alineada con el titulo de la
     pantalla que viene debajo: dos elementos en la misma columna se
     leen como una sola cosa.

     En una pagina hija vuelve a haber cuatro, porque el primero es la
     flecha de volver. Se declara asi -y no con :has(.topbar-suc-chip)-
     porque lo que decide el numero de columnas es la FLECHA, que existe
     o no existe, no la pastilla, que ocupa un hueco de ancho automatico
     y desaparece sin dejar rastro cuando el negocio tiene una sucursal.

     La regla se escribe contra .con-js .topbar para pesar lo mismo que
     las de 42f y ganarles por orden, no por casualidad. */
  .con-js .topbar { grid-template-columns: minmax(0, 1fr) auto auto; }
  .con-js .topbar:has(.topbar-volver) { grid-template-columns: auto minmax(0, 1fr) auto auto; }

  .topbar {
    background: color-mix(in srgb, var(--surface) 86%, transparent);
    -webkit-backdrop-filter: saturate(180%) blur(18px);
    backdrop-filter: saturate(180%) blur(18px);
    border-bottom: 1px solid var(--border-sutil);
  }
  @supports not (backdrop-filter: blur(1px)) {
    .topbar { background: var(--surface); }
  }

  /* La flecha de volver gana area de toque y se pega al borde, que es
     donde la busca el pulgar. */
  .topbar-volver { width: 44px; height: 44px; }
}


/* =====================================================================
   43. PULIDO VISUAL  (segundo pase)
   =====================================================================
   El primer pase resolvio la ARQUITECTURA movil: navegacion, hojas,
   listas, prioridad del tablero. Este resuelve el ACABADO.

   El diagnostico no se hizo a ojo. Se midio la composicion de las doce
   pantallas del panel del negocio a 390x844, y salieron cuatro numeros
   que explican por que el sistema todavia se sentia a web:

   1. LA ESCALA ERA PLANA. En el tablero, de 158 elementos de texto
      visibles, 143 median entre 11 y 15px. Un solo elemento pasaba de
      20px. Cuando todo pesa lo mismo, el ojo no tiene donde caer
      primero, y eso se lee como "pagina", no como "aplicacion".

   2. HABIA DEMASIADAS CAJAS. 24 superficies con borde y esquina
      redondeada en el tablero; 31 en Usuarios; 23 en Historial. El
      borde dejo de significar "esto va junto".

   3. LA REJILLA DE PROTAGONISTA PERDIA SU PROTAGONISTA. En telefono,
      .metricas-heroe se repartia en 2x2 y quedaban CUATRO tarjetas de
      125px iguales. La pieza que existe para decir "esta cifra manda"
      hacia justo lo contrario. Medido en Alertas, Movimientos, Caja y
      Reportes: 341-359px de primera pantalla gastados en eso.

   4. EL CONTENIDO EMPEZABA TARDE. En Movimientos, el primer movimiento
      aparecia en el pixel 454 de 844.

   Ninguno de los cuatro se arregla agregando informacion. Los cuatro se
   arreglan con composicion.
   ===================================================================== */


/* ---------------------------------------------------------------------
   43a. LAS METRICAS DEJAN DE SER CUATRO TARJETAS
   ---------------------------------------------------------------------
   Cuando hay poco sitio, la rejilla de metricas deja de ser un grupo de
   tarjetas sueltas y pasa a ser UN SOLO BLOQUE dividido por juntas de
   un pixel. Es el patron de las aplicaciones de banco, y no es una
   preferencia estetica: resuelve las cuatro cosas de arriba a la vez.

     - una superficie en lugar de cuatro        (menos cajas)
     - el heroe arriba, a lo ancho, con su cifra grande, y los datos de
       apoyo debajo en una tira                 (jerarquia recuperada)
     - de 341px a ~180px                        (el contenido sube)

   COMO SE HACE LA JUNTA. La rejilla se tine del color del borde y se
   le pone un hueco de 1px; las celdas llevan el fondo del panel. Lo que
   se ve entre celda y celda es el fondo de la rejilla asomando. Asi la
   junta aparece sola donde toque, con dos, tres o cinco datos, y sin
   una sola regla por pantalla.

   POR QUE @container Y NO @media. Todo el sistema de metricas se mide
   contra SU contenedor, no contra la ventana: la misma rejilla vive en
   una pantalla completa y dentro de un panel estrecho de escritorio. Si
   esto se escribiera con @media, una rejilla apretada dentro de un
   panel de 400px en un monitor de 1920 seguiria pintando cuatro
   tarjetas encajadas. El tramo 559 es el que ya usaba el archivo para
   "aqui hay poco sitio"; se reutiliza, no se inventa otro.
   --------------------------------------------------------------------- */

@container metricas (max-width: 559px) {

  /* Un bloque, no cuatro tarjetas. */
  .metricas-heroe,
  .metricas-apoyo {
    gap: 1px;
    background: var(--border);
    border: 1px solid var(--border);
    border-radius: var(--r-lg);
    overflow: hidden;
    box-shadow: var(--sh-1);
  }

  .metricas-heroe > .metrica,
  .metricas-apoyo > .metrica {
    border: 0;
    border-radius: 0;
    box-shadow: none;
  }

  /* El heroe ocupa la fila entera y las tres de apoyo cierran la
     siguiente. --heroe se iguala a --cols para que "fila entera" siga
     siendo cierto si algun dia cambia el reparto. */
  .metricas-heroe[data-metricas="4"] { --cols: 3; --heroe: 3; }
  .metricas-heroe[data-metricas="3"] { --cols: 2; --heroe: 2; }

  /* La regla general manda a la ultima compacta ocupar dos columnas
     cuando quedaria sola. Con el reparto de arriba la fila ya cierra,
     y ese span la partiria. */
  .metricas-heroe[data-metricas="3"] > .metrica:last-child,
  .metricas-heroe[data-metricas="4"] > .metrica:last-child { grid-column: auto; }

  /* Dentro del bloque, la tira de apoyo respira menos que el heroe: es
     lo que hace que se lea como acompanamiento y no como cuatro datos
     del mismo rango. */
  .metricas-heroe > .metrica-compacta {
    padding: var(--s3) var(--s2);
    min-height: 0;
    text-align: center;
  }
  .metricas-heroe > .metrica-compacta .metrica-cab { justify-content: center; }
  .metricas-heroe > .metrica-compacta .metrica-icono { display: none; }
  .metricas-heroe > .metrica-compacta .metrica-rotulo {
    font-size: var(--fs-3xs);
    letter-spacing: .04em;
    line-height: 1.25;
    color: var(--text-3);
  }
  .metricas-heroe > .metrica-compacta .metrica-valor {
    font-size: var(--fs-lg);
    margin-top: 3px;
  }
  .metricas-heroe > .metrica-compacta .metrica-sub {
    font-size: var(--fs-3xs);
    padding-top: 2px;
    line-height: 1.3;
  }

  /* El filo de color de una metrica que avisa: dentro del bloque va
     ARRIBA, no a la izquierda. A la izquierda solo se veria en la
     primera columna de la tira. */
  .metricas-heroe > .metrica-warn::before,
  .metricas-heroe > .metrica-crit::before,
  .metricas-heroe > .metrica-error::before,
  .metricas-apoyo > .metrica-warn::before,
  .metricas-apoyo > .metrica-crit::before,
  .metricas-apoyo > .metrica-error::before {
    inset: 0 0 auto 0;
    width: auto;
    height: 3px;
  }

  /* La tira de apoyo pura -contexto de la pantalla, sin protagonista-
     va centrada y sin sub: es una lectura de un vistazo, no una ficha. */
  .metricas-apoyo > .metrica {
    padding: var(--s3) var(--s2);
    min-height: 0;
    text-align: center;
  }
  .metricas-apoyo > .metrica .metrica-cab { justify-content: center; }
  /* El icono se retira donde no hay sitio, y el azulejo es el mismo
     papel con otra caja: si se retira uno se retiran los dos, o el dia
     que una pantalla cambie de icono a azulejo esta regla dejaria de
     valer sin que nadie se entere. Es lo que mantiene el telefono
     exactamente igual cuando un resumen gana azulejo en escritorio. */
  .metricas-apoyo > .metrica .metrica-icono,
  .metricas-apoyo > .metrica .azulejo { display: none; }
  .metricas-apoyo > .metrica .metrica-valor { font-size: var(--fs-lg); margin-top: 3px; }

  /* El heroe conserva su lavado de marca: es lo que separa "la cifra de
     la pantalla" de "los datos que la acompanan" dentro del mismo
     bloque. Sin el, el bloque seria una lista de cuatro cosas iguales
     otra vez. */
  .metricas-heroe > .metrica-heroe {
    padding: var(--s4) var(--s4) var(--s5);
    border-bottom: 0;
  }
}

/* ---------------------------------------------------------------------
   43a-bis. LA TIRA TIENE QUE ESTAR CUADRADA
   ---------------------------------------------------------------------
   Dos detalles que solo aparecen al medir el bloque ya montado, y que
   son exactamente la diferencia entre "bien resuelto" y "casi":

   1. LA JUNTA ASOMANDO. El bloque se tine del color del borde y lo que
      se ve entre celdas es ese fondo. El tramo estrecho pone
      align-items:start -correcto cuando son tarjetas sueltas- y ahi las
      celdas mas cortas dejaban de tapar el fondo: bajo la celda corta
      asomaba una franja del color del borde. Dentro del bloque las
      celdas se estiran.

   2. LOS VALORES BAILABAN. Medido en Movimientos a 390: "Unidades
      entradas" ocupa dos renglones y "Unidades salidas" uno, asi que
      +64 y -19 quedaban a distinta altura. En una tira de tres datos
      eso se ve al instante. El rotulo reserva dos renglones siempre y
      las cifras caen en la misma linea.
   --------------------------------------------------------------------- */

@container metricas (max-width: 559px) {
  .metricas-heroe,
  .metricas-apoyo { align-items: stretch; }

  .metricas-heroe > .metrica-compacta .metrica-rotulo,
  .metricas-apoyo > .metrica .metrica-rotulo {
    display: block;
    min-height: 2.5em;      /* dos renglones a 1.25 de interlinea */
  }
}


/* ---------------------------------------------------------------------
   43b. LA ESCALA TIPOGRAFICA DEL TELEFONO
   ---------------------------------------------------------------------
   EL NUMERO QUE LO DESTAPO: de los 158 elementos de texto visibles del
   tablero a 390px, 143 median entre 11 y 15px. Uno solo pasaba de 20.

   No falta contenido: falta CONTRASTE. Cuando el titulo de la pantalla,
   el titulo de una seccion y el nombre de una fila miden los tres 15 o
   17px, el ojo no puede recorrer la pagina de un vistazo y tiene que
   LEERLA entera para saber donde esta. Eso es lo que hace que algo se
   sienta a documento y no a aplicacion.

   La escala nueva del telefono tiene tres escalones bien separados, no
   cinco pegados:

       24 / 700    el titulo de la pantalla     (era 17)
       17 / 600    la seccion y el panel        (era 17 tambien)
       15 / 500    el contenido

   Los tokens NO cambian: --fs-lg sigue siendo 17 y --fs-xl sigue siendo
   20 en todo el sistema. Lo que cambia es QUE token usa cada cosa en
   telefono. En escritorio la escala ya funcionaba -hay ancho para que
   la jerarquia la marque la composicion- y no se toca.
   --------------------------------------------------------------------- */

@media (max-width: 767px) {

  /* El titulo de la pantalla manda de verdad. Con tracking cerrado, que
     es lo que evita que un titular grande se lea "inflado".

     OLA 1: el TAMANO sale de aqui -lo pone --cab-titulo, una vez, para
     todos los anchos-. Lo que se queda es el TRATAMIENTO de telefono,
     que no es lo mismo: peso, tracking y interlineado propios de un
     titular que ocupa el ancho de la pantalla. */
  .pagina-cab h1 {
    font-weight: var(--peso-max);
    letter-spacing: -0.021em;
    line-height: 1.15;
  }

  /* La seccion se separa del titulo por tamano Y por peso. */
  .seccion-cab h2,
  .tablero-seccion h3 {
    font-size: var(--fs-lg);
    font-weight: var(--peso-fuerte);
    letter-spacing: var(--ls-titulo);
  }

  /* El titulo de un panel es el tercer escalon: no compite con la
     seccion que lo contiene. */
  .panel-cab h3 {
    font-size: var(--fs-base);
    font-weight: var(--peso-fuerte);
    letter-spacing: -0.006em;
  }

  /* La descripcion de un panel es apoyo, no un segundo titulo. */
  .panel-desc { font-size: var(--fs-sm); color: var(--text-2); }
}


/* ---------------------------------------------------------------------
   43c. EL TABLERO NO SE PRESENTA DOS VECES
   ---------------------------------------------------------------------
   Medido a 390: el tablero abria con "Inicio" (h1, 22px de alto) y
   justo debajo con "Buenas tardes, Demo" (h2, 42px). Dos encabezados
   seguidos, uno encima del otro, diciendo lo mismo: donde estas.

   Y el h1 es el que menos aporta de los dos. La barra inferior ya
   ensena "Inicio" marcado en azul; el saludo, en cambio, trae la fecha
   y la sucursal, que si son datos.

   Se queda el saludo como encabezado VISIBLE y el h1 pasa a lectura de
   pantalla. No se borra: el documento sigue teniendo su h1 y un lector
   de pantalla lo sigue anunciando. Lo unico que cambia es que no se
   pinta dos veces la misma idea.

   Solo en el tablero, y solo en telefono. En el resto de pantallas el
   h1 es el unico encabezado y ahi manda.

   ACTUALIZADO EN LA RECONSTRUCCION DEL TABLERO: el h1 sigue retirado a
   lectura de pantalla, pero el saludo YA NO hereda su peso. Ahora quien
   encabeza la pantalla es la cifra del dia, y un saludo a 24px
   competiria con ella. El tamano del saludo se decide en 42g, junto al
   resto de la composicion del tablero; aqui solo queda lo del h1, que
   es lo unico que esta regla tenia que resolver.
   --------------------------------------------------------------------- */

@media (max-width: 767px) {

  .contenido:has(.tm) > .pagina-cab h1 {
    position: absolute;
    width: 1px; height: 1px;
    margin: -1px; padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
  .contenido:has(.tm) > .pagina-cab { margin-bottom: 0; }
}


/* ---------------------------------------------------------------------
   43d. EL ESTADO DE LA CAJA ES UNA CONDICION, NO UN BLOQUE
   ---------------------------------------------------------------------
   MEDIDO: 139px en Caja y 154px en el tablero para decir "caja abierta
   desde las 14:32". Es una linea de estado ocupando el sitio de un
   bloque de contenido.

   El punto de venta ya lo habia resuelto para si mismo (seccion 35):
   una sola fila, icono chico, accion al lado. Ahi estaba escrito con
   :has(~ .pos) y solo valia para esa pantalla. Se generaliza: la barra
   de estado de la caja se comporta igual en las tres pantallas donde
   aparece, que es lo que pide la regla de consistencia.
   --------------------------------------------------------------------- */

@media (max-width: 767px) {

  .caja-estado {
    flex-direction: row;
    align-items: center;
    text-align: left;
    gap: var(--s3);
    padding: var(--s3);
    margin-bottom: var(--s4);
  }
  .caja-estado-icono { width: 36px; height: 36px; }
  .caja-estado-icono svg { width: 19px; height: 19px; }
  .caja-estado-txt strong { font-size: var(--fs-sm); }
  .caja-estado-txt span { font-size: var(--fs-xs); }
  .caja-estado-accion { width: auto; flex: none; }
  .caja-estado-accion .btn {
    width: auto;
    min-height: 38px;
    padding: var(--s2) var(--s3);
    font-size: var(--fs-sm);
    white-space: nowrap;
  }
}

/* ---------------------------------------------------------------------
   43a-ter. LA REGLA VALE PARA LAS TRES FAMILIAS
   ---------------------------------------------------------------------
   43a lo aplico a heroe y apoyo, que eran las que mas dano hacian. Pero
   dejar fuera a .metricas a secas rompe justo lo que este pase venia a
   arreglar: en el tablero, "Este mes" seguia siendo cuatro tarjetas de
   131px -312px de pantalla- al lado de un bloque unificado. Dos
   lenguajes para la misma pieza en la misma pantalla.

   La regla, sin excepciones: EN POCO SITIO, UNA REJILLA DE METRICAS ES
   UN BLOQUE CON JUNTAS. Da igual si tiene protagonista, si es de apoyo
   o si son cuatro datos del mismo rango.

   Aqui las celdas conservan su composicion normal -rotulo a la
   izquierda, icono a la derecha, sub debajo- porque son datos del mismo
   rango y se comparan leyendolos, no de un vistazo. Lo unico que
   pierden es el borde propio.
   --------------------------------------------------------------------- */

@container metricas (max-width: 559px) {
  .metricas:not(.metricas-heroe):not(.metricas-apoyo) {
    gap: 1px;
    align-items: stretch;
    background: var(--border);
    border: 1px solid var(--border);
    border-radius: var(--r-lg);
    overflow: hidden;
    box-shadow: var(--sh-1);
  }
  .metricas:not(.metricas-heroe):not(.metricas-apoyo) > .metrica {
    border: 0;
    border-radius: 0;
    box-shadow: none;
    padding: var(--s3);
  }
  /* El filo de aviso, arriba y no a la izquierda: dentro del bloque, a
     la izquierda solo se veria en la primera columna. */
  .metricas:not(.metricas-heroe):not(.metricas-apoyo) > .metrica-warn::before,
  .metricas:not(.metricas-heroe):not(.metricas-apoyo) > .metrica-crit::before,
  .metricas:not(.metricas-heroe):not(.metricas-apoyo) > .metrica-error::before {
    inset: 0 0 auto 0;
    width: auto;
    height: 3px;
  }
}


/* ---------------------------------------------------------------------
   43e. LAS CAJAS QUE QUEDABAN
   ---------------------------------------------------------------------
   Con las metricas resueltas, el recuento de superficies con borde en
   telefono queda asi:

       Usuarios     29, de las cuales 18 son casillas de permiso
       Inventario   10, de las cuales  2 son tarjetas de sucursal

   Los dos casos son el MISMO problema y tienen la MISMA solucion, que
   ademas es la que este sistema ya eligio en el primer pase: cuando
   varias cajas iguales se apilan en una columna, no son tarjetas, son
   una LISTA. Una lista se agrupa con una superficie y se separa con
   juntas de un pixel.

   Se resuelve por composicion sobre el marcado que ya existe. No hay
   HTML nuevo, no hay clases nuevas y en escritorio -donde las casillas
   van a dos columnas y las tarjetas de sucursal caben de cuatro en
   fila- no cambia nada.
   --------------------------------------------------------------------- */

@media (max-width: 767px) {

  /* ---------- Permisos y sucursales de un usuario ---------- */
  /* Dieciocho rectangulos con borde apilados no comunican "elige los
     que quieras": comunican ruido. Un bloque con juntas si. */
  .usr-casillas {
    display: block;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--r-lg);
    overflow: hidden;
  }
  .usr-casilla {
    border: 0;
    border-radius: 0;
    background: none;
    padding: var(--s3) var(--s4);
    min-height: 52px;
  }
  .usr-casilla + .usr-casilla { border-top: 1px solid var(--border-sutil); }
  .usr-casilla:hover { border-color: transparent; }
  /* Marcada: se tine la fila entera, que es lo que se ve de un vistazo
     al repasar dieciocho permisos. El borde ya no esta para hacerlo. */
  .usr-casilla:has(input:checked) { background: var(--selected); }
  .usr-casilla:active { background: var(--pressed); }

}


/* ---------------------------------------------------------------------
   43f. ACABADO DEL SHELL DE TELEFONO
   ---------------------------------------------------------------------
   Tres cosas medidas sobre la pantalla ya montada a 390px.

   1. LA MARCA SE PARTIA EN DOS RENGLONES. "Sistema Universal de
      Inventario" en 144px de hueco a 13px se rompe, y la barra superior
      pasaba de 56 a 59px de alto. Es lo primero que se ve al abrir la
      aplicacion y se veia descuidado.

      Y ademas arrastraba un fallo silencioso: --topbar-movil vale 56 y
      es el numero con el que se pegan el buscador del punto de venta y
      la cabecera de las listas. Con la barra midiendo 59, todo lo que
      se pega debajo lo hacia 3px por dentro. Una linea de texto
      cortandose mal descuadraba el shell entero.

      El nombre de la plataforma se recorta con puntos suspensivos, como
      ya hacia el logotipo de la portada por la misma razon.

      LA MEDIDA ES DE ANTES DEL REBRANDING y se deja tal cual porque es
      lo que justifica la regla. Hoy la marca es MOVQRA y en 144px no se
      parte, pero el recorte sigue haciendo falta: el ajuste
      'plataforma_nombre' admite hasta 100 caracteres y el que decide
      cuantos se escriben no es esta hoja de estilos.

   2. EL CONTENIDO IBA A 12px DEL BORDE. En una aplicacion el margen
      lateral es el que marca la caja del contenido, y 12px deja el
      texto pegado al canto de la pantalla. Sube a 16, que es el valor
      con el que ya respiran las hojas, la barra superior y las fichas.
      Ancho util a 360: 328px, de sobra para todo lo que hay medido.

   3. EL PANEL DE SUCURSALES DEL TABLERO abria con dos botones a ancho
      completo antes de ensenar un solo dato. En telefono la accion
      principal de ese panel ya esta arriba, en la fila "Sucursal" de la
      lista de prioridad; aqui son atajos y pesan como atajos.
   --------------------------------------------------------------------- */

@media (max-width: 767px) {

  .topbar .marca {
    display: block;
    min-width: 0;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
    line-height: 1.25;
  }

  .contenido {
    padding-inline: var(--s4);
  }

  /* Las cabeceras de panel dentro del contenido conservan su propio
     margen: el que cambia es el de la pagina. */
  .panel-acciones .btn { min-height: 40px; }

  /* Aqui vivian dos reglas que apretaban los botones del panel "Tus
     sucursales hoy" del tablero. Ese panel ya no se pinta en telefono
     -lo releva la lista del tablero nuevo, ver 42g- asi que las reglas
     no tenian a quien aplicarse y se retiran en vez de quedarse
     apuntando a un selector muerto. */
}

/* ---------------------------------------------------------------------
   43g. LA BARRA SUPERIOR MIDE LO QUE DICE QUE MIDE
   ---------------------------------------------------------------------
   --topbar-movil vale 56px y es el numero con el que se pegan debajo el
   buscador del punto de venta y cualquier cabecera pegajosa. La barra
   real medía 59: la campana son 42px y con los 8+8 de relleno salen 58,
   mas el borde. Tres pixeles de mentira que meten todo lo pegado por
   debajo del borde de la barra.

   Se arregla haciendo que el token SEA la altura, no un minimo. Y se
   anade --topbar-total, que es lo que hay que usar para pegar cosas
   debajo: en un telefono con muesca la barra ademas baja lo que ocupe
   el area segura, y quien se pegue tiene que bajar lo mismo.

   Los hijos ya van centrados por la rejilla, asi que fijar el alto no
   descoloca nada: solo deja de estirarse.
   --------------------------------------------------------------------- */

:root { --topbar-total: calc(var(--topbar-movil) + env(safe-area-inset-top)); }

@media (max-width: 767px) {
  .topbar {
    height: var(--topbar-total);
    min-height: 0;
    padding-block: 0;
    padding-top: env(safe-area-inset-top);
    box-sizing: border-box;
  }
  .topbar-avisos { width: 40px; height: 40px; }

  /* Lo que se pega debajo de la barra usa su altura REAL. */
  .pos-catalogo > .panel-cab { top: var(--topbar-total); }
}

/* ---------------------------------------------------------------------
   43h. LA TIRA NO EXPLICA, ENSENA
   ---------------------------------------------------------------------
   Medido en Alertas a 390: las tres celdas de la tira llevaban su linea
   de apoyo -"Lo que costó lo que hay", "Si vendieras todo a precio
   actual", "Piezas contadas en el catálogo"- a 10px y repartida en dos
   y tres renglones dentro de 120px de ancho. Eso alargaba la tira de 62
   a 97px y, a ese cuerpo y en esa columna, no se lee: se adivina.

   En una tira de tres datos el rotulo YA dice que es cada cifra. La
   linea de apoyo es una explicacion, y una explicacion no cabe en una
   tira: cabe en la pantalla a la que lleva.

   SE CONSERVA la linea de apoyo cuando lleva TONO, porque entonces no
   explica: informa. "+8.6% vs ayer" en verde es un dato de estado, y
   ese es justo el que no se puede perder.

   metricas-apoyo ya ocultaba su sub desde antes por esta misma razon;
   aqui solo se aplica el mismo criterio a la tira del protagonista.
   --------------------------------------------------------------------- */

@container metricas (max-width: 559px) {
  .metricas-heroe > .metrica-compacta .metrica-sub:not(.metrica-sub-ok):not(.metrica-sub-warn):not(.metrica-sub-crit):not(.metrica-sub-error) {
    display: none;
  }
}


/* ---------------------------------------------------------------------
   43j. EL BUSCADOR PEGAJOSO DEL PUNTO DE VENTA ESTABA ROTO
   ---------------------------------------------------------------------
   FALLO INTRODUCIDO EN EL PRIMER PASE, destapado al mirar la pantalla
   en vez de medir solo el desbordamiento.

   MEDIDO a 390 con la pagina SIN desplazar:

       panel        arriba en 211
       cabecera     arriba en 268   <- 57px de hueco blanco
       cuerpo       arriba en 313
       cabecera     abajo  en 369   <- tapa 56px de la primera fila

   O sea: un hueco blanco encima del buscador y las dos primeras
   tarjetas de producto cortadas por arriba. En la pantalla que mas se
   usa del sistema.

   LA CAUSA. `.panel` lleva `overflow: hidden` -lo necesita para que el
   contenido no se salga por sus esquinas redondeadas-, y un ancestro
   con overflow distinto de visible ES un contenedor de desplazamiento
   para position:sticky. Asi que la cabecera no se pegaba a 56px de la
   VENTANA: se pegaba a 56px del PANEL, que no se desplaza nunca. El
   resultado es un elemento empujado 56px hacia abajo, para siempre.

   LA CORRECCION. El panel del catalogo renuncia al recorte en telefono.
   Puede permitirselo: su contenido lleva relleno propio y no llega a
   tocar las esquinas, asi que no hay nada que recortar. Y a cambio el
   sticky vuelve a medirse contra la ventana, que es lo que se queria.

   Se acota a .pos-catalogo y a telefono. Ningun otro panel del sistema
   pierde su recorte.
   --------------------------------------------------------------------- */

@media (max-width: 767px) {
  .pos-catalogo { overflow: visible; }
  /* Sin el recorte del panel, la cabecera pegajosa tiene que traer su
     propio redondeo arriba y su propia junta abajo. */
  .pos-catalogo > .panel-cab {
    border-radius: var(--r-lg) var(--r-lg) 0 0;
    border-bottom: 1px solid var(--border-sutil);
  }
}

/* ---------------------------------------------------------------------
   43k. EL PRECIO CAE SIEMPRE A LA MISMA ALTURA
   ---------------------------------------------------------------------
   En la cuadricula del punto de venta, "Coca-Cola 600ml" ocupa dos
   renglones y "Agua Ciel 1L" uno. Las dos tarjetas de la fila miden lo
   mismo -eso ya estaba resuelto- pero el pie con el precio arrancaba
   justo debajo de su nombre, asi que en la misma fila un precio caia
   18px mas abajo que el de al lado.

   Es el detalle que separa una cuadricula de una lista de cajas: al
   recorrer precios con la vista, la columna tiene que estar recta. El
   pie se ancla al fondo de la tarjeta y el hueco lo absorbe el nombre.

   Vale para las dos formas de la tarjeta -la que pinta PHP y la que
   repinta pos.js al buscar-, porque las dos usan estas clases.
   --------------------------------------------------------------------- */

.pos-item { grid-template-rows: auto 1fr; }
.pos-item-pie { align-self: end; }

/* ---------------------------------------------------------------------
   43l. UN MARCADOR DE POSICION QUE NO CABE SE CORTA CON PUNTOS
   ---------------------------------------------------------------------
   "Buscar por nombre, SKU o código de barras…" en un campo de 340px a
   16px se corta a hueso: la ultima palabra queda partida contra el
   borde y parece un fallo de maquetacion.

   No se acorta el texto -en escritorio cabe entero y ahi es util-: se
   corta con puntos suspensivos, que es como se corta cualquier otro
   texto del sistema que no cabe.
   --------------------------------------------------------------------- */

input::placeholder { text-overflow: ellipsis; }

/* ---------------------------------------------------------------------
   43m. LA BARRA DEL CATALOGO EN DOS FILAS, NO EN CUATRO
   ---------------------------------------------------------------------
   MEDIDO a 390: antes del primer producto habia CINCO renglones de
   controles: pestanas, contador, buscador, boton Filtros y selector de
   orden. Los tres ultimos a ancho completo, uno debajo de otro.

   Buscar y refinar no son la misma accion, pero FILTRAR y ORDENAR si:
   las dos toman la lista que ya hay y la recortan o la reordenan. Que
   ocupen un renglon cada una es tratarlas como si fueran decisiones
   distintas, y ademas cuesta 56px de primera pantalla.

       renglon 1   buscar          (la accion propia)
       renglon 2   filtrar | ordenar   (refinar lo que hay)

   El boton de filtros lo inyecta app.js y puede no existir; cuando no
   esta, el selector de orden se queda con la fila entera y no hay ningun
   hueco. Por eso el reparto se escribe con flex y no con columnas
   fijas.
   --------------------------------------------------------------------- */

@media (max-width: 767px) {
  .prod-filtros-boton:not([hidden]) { flex: 1 1 45%; }
  .prod-filtros-orden { flex: 1 1 45%; }
  .prod-filtros-orden select { min-width: 0; }

  /* "Quitar filtros" si merece su propia fila: es una accion, no un
     control, y aparece solo cuando hay algo que quitar. */
  .prod-filtros-orden .btn { flex: 1 1 100%; }

  /* La cabecera del panel y la primera ficha se estaban tocando. */
  .tabla-fichas tbody tr:first-child { margin-top: var(--s1); }
}

/* ---------------------------------------------------------------------
   43n. EL REGISTRO NO ANUNCIA EL PASO DOS VECES
   ---------------------------------------------------------------------
   La pantalla decia "Tu negocio" en el indicador de avance y, doce
   pixeles mas abajo, "DATOS DEL NEGOCIO" como rotulo de seccion. Es la
   misma informacion dos veces seguidas, y la segunda ademas en
   mayusculas, que pesa mas de lo que aporta.

   Es el mismo caso que el "Inicio" + "Buenos dias" del tablero: cuando
   dos encabezados consecutivos dicen lo mismo, se queda el que trae
   mas informacion. Aqui el indicador, porque ademas dice cuantos pasos
   quedan.

   Solo en telefono: en escritorio el indicador y el rotulo estan mas
   separados y el rotulo si ordena la columna del formulario.
   --------------------------------------------------------------------- */

@media (max-width: 767px) {
  .registro-paso-titulo { display: none; }

  /* El asterisco de campo obligatorio es una marca, no una palabra: no
     tiene que pesar como el rotulo al que acompana.

     Se le baja el PESO, no el color. El rojo es la convencion que dice
     "este campo es obligatorio" y quitarlo seria quitar informacion,
     no ruido; lo que sobraba era el 700, que lo ponia al mismo nivel
     que el nombre del campo. */
  .campo-req { font-weight: var(--peso-medio); }
}

/* ---------------------------------------------------------------------
   43o. LA PANTALLA DE ACCESO SE ANCLA ARRIBA
   ---------------------------------------------------------------------
   MEDIDO a 390x844: el formulario de entrar ocupa 490px y el panel lo
   centra verticalmente, asi que quedaban 155px de vacio arriba y 205
   abajo. Dos huecos grandes y ninguno de los dos hace nada.

   Ese es el ejemplo exacto de ESPACIO MUERTO frente a ESPACIO
   INTENCIONAL: no separa dos cosas, no da respiro a nada, solo sobra
   por partida doble. Y ademas empuja los campos al centro exacto de la
   pantalla, que en un telefono grande queda por encima de donde llega
   el pulgar comodo.

   La correccion no es rellenar el hueco -no hay nada que meter ahi- ni
   quitarlo: es REPARTIRLO. El contenido se ancla arriba con un margen
   proporcional, el titulo cae en el tercio superior, los campos en la
   zona comoda, y todo el aire sobrante queda abajo, donde no estorba.

   8vh y no un numero fijo: en un telefono de 667px de alto son 53px y
   en uno de 932 son 75, que es lo que hace falta para que la
   proporcion se mantenga en los dos.
   --------------------------------------------------------------------- */

@media (max-width: 767px) {
  .acceso-panel {
    justify-content: flex-start;
    padding-top: 8vh;
    padding-bottom: var(--s6);
  }
  .acceso-panel-alto { padding-top: var(--s5); }

  /* OJO: la tarjeta lleva `margin-block: auto`, y NO es decoracion.
     Esta ahi para que, cuando el contenido pasa de alto -teclado
     abierto, o un error de validacion que anade cuatro renglones- la
     tarjeta se apoye arriba y la pagina se desplace, en vez de
     recortarse por arriba, que con justify-content:center es un fallo
     clasico de flexbox del que no se sale ni con scroll.

     Asi que el auto de ARRIBA se sustituye por cero y el de ABAJO se
     conserva. El sobrante sigue teniendo donde irse, la garantia sigue
     en pie, y el contenido deja de flotar en el centro. */
  .acceso-panel > .acceso-caja { margin-top: 0; margin-bottom: auto; }

  /* Los dos caminos que no son "entrar" -crear cuenta y recuperar la
     contrasena- son la misma familia: cosas que hacer si esto no es lo
     tuyo. Iban separados por una linea que los presentaba como dos
     bloques distintos y ademas dibujaba una raya en medio del aire. */
  .acceso-nota {
    margin-top: var(--s3);
    padding-top: 0;
    border-top: 0;
  }
}

/* ---------------------------------------------------------------------
   43p. EL LAVADO DE MARCA, EN OSCURO, LAVA MENOS
   ---------------------------------------------------------------------
   La metrica protagonista lleva un degradado de --brand-50 al color del
   panel. En claro eso es un lavado muy suave (#eff6ff sobre blanco) y
   hace justo lo que tiene que hacer: decir "esta cifra manda" sin
   levantar la voz.

   En oscuro --brand-50 es #172554, un azul marino saturado, y sobre un
   panel de #1e1e1a el salto es brusco: el bloque entero se lee como una
   mancha azul y la cifra, que es lo que importa, pasa a segundo plano.
   Es el error clasico de dar por bueno en oscuro un color pensado para
   claro solo porque el token existe en los dos temas.

   En oscuro el lavado se hace con azul TRANSLUCIDO sobre el propio
   panel: mismo gesto, misma intencion, una decima parte del volumen.
   Y el borde inferior del heroe recibe un filo de marca muy bajo, que
   es lo que separa al protagonista de su tira sin dibujar una raya.
   --------------------------------------------------------------------- */

:root[data-tema="oscuro"] .metrica-heroe {
  background:
    linear-gradient(180deg, rgba(59,130,246,.14) 0%, rgba(59,130,246,0) 70%),
    var(--surface);
}

/* Dentro del bloque unificado, el heroe se apoya en la superficie de la
   hoja/panel que lo contiene, no en la suya propia. */
@container metricas (max-width: 559px) {
  :root[data-tema="oscuro"] .metricas-heroe > .metrica-heroe {
    background:
      linear-gradient(180deg, rgba(59,130,246,.16) 0%, rgba(59,130,246,.02) 78%),
      var(--surface);
  }
}

/* ---------------------------------------------------------------------
   43q. EL BOTON "IR" DEL SELECTOR DE SUCURSAL
   ---------------------------------------------------------------------
   Visto en tableta a 820: al lado del desplegable de sucursal aparecia
   un boton diminuto que solo dice "Ir". Es la red de seguridad para un
   navegador sin JavaScript -el <select> se envia solo con onchange- y
   con JavaScript no hace absolutamente nada, porque el formulario ya se
   mando al elegir.

   Es exactamente el mismo caso que la pastilla de sucursal del telefono
   y se resuelve igual: con .con-js. Con JavaScript no se pinta; sin el,
   sigue ahi y sigue siendo el unico camino.

   En telefono ya estaba oculto por otra regla; ahora lo esta en los
   tres tamanos y por el mismo motivo, que es lo que pedia la regla de
   consistencia.
   --------------------------------------------------------------------- */

.con-js .topbar-sucursal-ir { display: none; }

/* ---------------------------------------------------------------------
   43r. TABLETA: LOS TRES FILTROS DEL CATALOGO  (PROBADO Y DESCARTADO)
   ---------------------------------------------------------------------
   A 768 los selectores de sucursal, categoria y estado se reparten en
   dos renglones y el tercero queda solo. Parecia un fallo de
   composicion y se intento meterlos en una fila. NO CABEN, y las dos
   formas de forzarlo salen peor que dejarlo:

     flex-basis 0     tres de 197px. "Stock: Sucursal principal" pide
                      331 y un <select> corta el texto a hueso, sin
                      puntos suspensivos. Se pierde la etiqueta.
     flex-basis auto  los dos primeros llenan la fila (331 + 264) y el
                      tercero cae solo Y ADEMAS se estira a 606px.
                      Peor que el reparto natural.

   El ancho util a 768 es 604px y solo el primer selector necesita 331.
   Dos renglones NO es un fallo aqui: es lo que cabe. Queda anotado
   para que nadie vuelva a "arreglarlo".

   Si algun dia molesta de verdad, lo que hay que acortar es la
   ETIQUETA del primer selector, no el hueco.
   --------------------------------------------------------------------- */


/* ---------------------------------------------------------------------
   43s. LAS ACCIONES DE LA CABECERA TIENEN JERARQUIA
   ---------------------------------------------------------------------
   En telefono, `.pagina-cab-acciones > .btn { flex: 1 1 auto }` estira
   TODOS los botones por igual. Con dos eso funciona -"Nuevo producto" y
   "Categorias" se reparten la fila-, pero con uno solo el resultado es
   que cualquier accion, sea la que sea, ocupa el ancho entero.

   Visto en Caja: "Cortes de todas" -que es consultar el historico, una
   accion de paso- abria la pantalla como una barra de 358px, con mas
   peso que el estado de la caja que viene justo debajo.

   La correccion es la jerarquia que ya usa el resto del sistema: manda
   el primario. El secundario mide lo suyo y se queda a la derecha.

       una accion primaria      ocupa el ancho    (Nuevo producto)
       una accion secundaria    mide lo suyo      (Cortes de todas)
       primaria + secundaria    se reparten, la primaria mas

   No se esconde nada y no cambia ningun marcado: cambia el reparto.
   --------------------------------------------------------------------- */

@media (max-width: 767px) {
  .pagina-cab-acciones { justify-content: flex-end; }
  .pagina-cab-acciones > .btn { flex: 1 1 auto; }
  /* Un secundario no crece; solo cede si hace falta. */
  .pagina-cab-acciones > .btn-sec,
  .pagina-cab-acciones > .btn-plano { flex: 0 1 auto; }
  /* ...salvo que sea el unico y ademas no haya primario al lado: ahi
     manda el ancho de su texto, que es justo lo que se busca. */
}

/* ---------------------------------------------------------------------
   43t. LOS CONTROLES DE LA BARRA SUPERIOR LLEGAN A 44px
   ---------------------------------------------------------------------
   MEDIDO en el barrido de siete tamanos de telefono: la pastilla de
   sucursal responde en 34px de alto y la campana en 40. Los dos se ven
   BIEN -ese es su tamano visual correcto dentro de una barra de 56- pero
   la zona que responde al dedo se queda corta.

   PRIMER INTENTO, DESCARTADO: relleno vertical con margen negativo, que
   es lo que funciona en los enlaces de texto. Aqui no sirve, y por dos
   razones distintas:

     la pastilla   tiene fondo y esquina redondeada. Crecer el relleno
                   engorda la pildora de 34 a 44px y cambia el aspecto
                   de la barra, que es justo lo que no se quiere.
     la campana    tiene alto fijo, asi que el relleno no la mueve.

   LO QUE SI FUNCIONA: un pseudo-elemento transparente que se sale por
   arriba y por abajo. El area que responde llega a 44px, el fondo y el
   borde se quedan donde estaban, y el :active sigue pintando la forma
   de siempre. Ni un pixel de la maqueta se mueve.
   --------------------------------------------------------------------- */

@media (max-width: 767px) {
  .con-js .topbar-suc-chip,
  .topbar-avisos { position: relative; }

  .con-js .topbar-suc-chip::after,
  .topbar-avisos::after {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    /* (44 - alto real) / 2, hacia fuera por los dos lados. */
    top: calc((44px - 100%) / -2);
    bottom: calc((44px - 100%) / -2);
  }
}


/* =====================================================================
   44. LA CAJA, EN UN TELEFONO
   =====================================================================
   Misma filosofia que el tablero movil: composicion editorial, ninguna
   tarjeta, laminas a sangre separadas por aire, y una cifra que manda.

   MEDIDO antes de tocar nada, a 390x844:

       alto de la pagina        2.141px
       superficies con borde    16
       bloques                  barra de estado, rejilla de metricas,
                                dos paneles en columna, panel de cierre
                                de 392px y la tabla del historial

   El formulario de cierre ocupaba 392px de forma PERMANENTE al final de
   la pantalla: casi media pantalla reservada a una accion que se hace
   una vez al dia, y ademas la ultima que se quiere tener a mano por
   error. Los dos formularios -movimiento y cierre- pasan a HOJA, que es
   el patron que este sistema ya usa para el carrito del punto de venta:
   el MISMO formulario, el mismo POST y el mismo csrf_field(), con otra
   presentacion.

   ORDEN DE LA PANTALLA EN TELEFONO, que es el del encargo:

       estado y cifra   ->  .cj-hero
       accion           ->  .cj-acciones
       resumen          ->  "De donde sale esa cifra", como lamina
       movimientos      ->  la lista del turno, como lamina
       corte            ->  un boton que abre la hoja de cierre
       historial        ->  cortes anteriores, como lamina

   En escritorio NO cambia nada: el hero y las acciones no se pintan, la
   barra de estado y las metricas vuelven, los dos formularios son
   columnas de su panel y la tabla del historial es una tabla.
   ===================================================================== */

/* Todo lo que existe SOLO para la presentacion de telefono se apaga
   aqui, fuera de la consulta de medios. Si se declarase dentro, en
   escritorio no habria regla que lo apagara y se veria.

   Paso exactamente eso con .cj-hoja-cab: la cabecera de la hoja -titulo
   y cruz de cerrar- aparecia en el panel de escritorio, con una cruz
   suelta debajo del titulo del formulario. Se detecto al revisar la
   regresion de escritorio, no en el barrido de medidas: no desbordaba
   ni cortaba nada, solo sobraba. */
.cj-hero,
.cj-acciones,
.cj-abrir-cierre,
.cj-hoja-cab,
.caja-form-mov > .hoja-asa,
.caja-cierre-form > .hoja-asa { display: none; }

@media (max-width: 767px) {

  /* -------------------------------------------------------------------
     LO QUE EL BLOQUE DE TELEFONO RELEVA
     -------------------------------------------------------------------
     La barra de estado y la rejilla de metricas dicen lo mismo que el
     hero, y con menos jerarquia: cuatro cifras del mismo rango donde
     solo una es la pregunta de la pantalla. Se quedan en el HTML -son
     lo que se ve en escritorio- y dejan de pintarse aqui.
     ------------------------------------------------------------------- */
  .cj-hero ~ .caja-estado,
  .cj-hero ~ .metricas-zona { display: none; }

  /* ---------- El estado y la cifra ---------- */
  .cj-hero { display: block; margin-bottom: var(--s5); }

  .cj-hero-rot {
    display: flex;
    align-items: center;
    gap: 7px;
    margin: 0;
    font-size: var(--fs-2xs);
    font-weight: var(--peso-fuerte);
    letter-spacing: var(--ls-etiqueta);
    text-transform: uppercase;
    color: var(--text-3);
  }
  /* El punto es todo el indicador de estado que hace falta. Verde es
     "operando"; gris es "sin turno". Siete pixeles bastan porque no
     compite con nada. */
  .cj-punto {
    width: 7px;
    height: 7px;
    border-radius: var(--r-full);
    background: var(--ok);
    flex: none;
  }
  .cj-hero-cerrada .cj-punto { background: var(--text-3); }

  .cj-hero-cifra {
    margin: 6px 0 0;
    font-family: var(--font-num);
    font-size: clamp(38px, 12vw, 48px);
    font-weight: var(--peso-max);
    letter-spacing: -0.03em;
    line-height: 1;
    font-variant-numeric: tabular-nums;
    color: var(--text);
    white-space: nowrap;
  }
  /* Sin turno no hay cifra: manda la frase. */
  .cj-hero-titulo {
    margin: 6px 0 0;
    font-size: 24px;
    font-weight: var(--peso-max);
    letter-spacing: -0.021em;
    line-height: 1.15;
    color: var(--text);
  }
  .cj-hero-sub {
    margin: var(--s2) 0 0;
    font-size: var(--fs-sm);
    color: var(--text-2);
  }
  .cj-hero-meta {
    margin: var(--s1) 0 0;
    font-size: var(--fs-xs);
    color: var(--text-3);
  }
  .cj-hero-cerrada .cj-hero-meta { margin-top: var(--s2); font-size: var(--fs-sm); }

  /* ---------- La accion ---------- */
  .cj-acciones { display: block; margin-bottom: var(--s6); }
  .cj-acciones .btn {
    width: 100%;
    min-height: 54px;
    font-size: var(--fs-lg);
    font-weight: var(--peso-fuerte);
    border-radius: var(--r-md);
  }
  .cj-enlace {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--s0);
    min-height: var(--tap);
    margin-top: var(--s1);
    color: var(--brand-600);
    font-size: var(--fs-sm);
    font-weight: var(--peso-fuerte);
    text-decoration: none;
    -webkit-tap-highlight-color: transparent;
  }
  .cj-enlace:active { opacity: .6; }

  /* -------------------------------------------------------------------
     LOS PANELES SE CONVIERTEN EN LAMINAS
     -------------------------------------------------------------------
     Mismo recurso que el tablero: se comen el relleno lateral del
     contenido, llegan a los dos bordes, pierden radio y sombra, y se
     separan por una junta de un pixel y un hueco.

     La cabecera del panel pasa a ser el rotulo de la seccion: 11px en
     mayusculas, como "TU NEGOCIO AHORA".
     ------------------------------------------------------------------- */
  .caja-cols { display: block; }

  .caja-cols > .panel,
  .panel.caja-cierre,
  .caja-apertura > .panel,
  .caja-apertura > .caja-referencia {
    margin: 0 calc(var(--s4) * -1) var(--s5);
    background: var(--surface);
    border: 0;
    border-top: 1px solid var(--border-sutil);
    border-bottom: 1px solid var(--border-sutil);
    border-radius: 0;
    box-shadow: none;
    overflow: visible;
  }

  .caja-cols > .panel > .panel-cab,
  .panel.caja-cierre > .panel-cab,
  .caja-apertura > .panel > .panel-cab,
  .caja-apertura > .caja-referencia > .panel-cab {
    display: block;
    padding: var(--s4) var(--s4) 0;
    border: 0;
    background: none;
  }
  .caja-cols > .panel > .panel-cab h3,
  .panel.caja-cierre > .panel-cab h3,
  .caja-apertura > .panel > .panel-cab h3,
  .caja-apertura > .caja-referencia > .panel-cab h3 {
    font-size: var(--fs-2xs);
    font-weight: var(--peso-fuerte);
    letter-spacing: var(--ls-etiqueta);
    text-transform: uppercase;
    color: var(--text-3);
  }
  .caja-cols > .panel .panel-desc,
  .panel.caja-cierre .panel-desc,
  .caja-apertura .panel-desc {
    margin-top: var(--s1);
    font-size: var(--fs-sm);
    color: var(--text-2);
    text-transform: none;
    letter-spacing: 0;
  }
  .caja-cols > .panel > .panel-cuerpo,
  .panel.caja-cierre > .panel-cuerpo,
  .caja-apertura > .panel > .panel-cuerpo,
  .caja-apertura > .caja-referencia > .panel-cuerpo { padding: var(--s3) var(--s4) var(--s4); }

  /* ---------- El resumen se lee como una cuenta ---------- */
  .caja-desglose li { padding-block: var(--s3); border-bottom-color: var(--border-sutil); }
  .caja-desglose .es-total {
    margin-top: var(--s2);
    padding-top: var(--s3);
    border-top: 1px solid var(--border-2);
  }
  .caja-desglose .es-total strong { font-size: var(--fs-xl); }

  /* ---------- Los movimientos son una lista, no fichas ---------- */
  .caja-mov-lista { margin-top: 0; padding-top: 0; border-top: 0; }
  .caja-mov {
    padding-block: var(--s3);
    border-radius: 0;
  }
  .caja-mov + .caja-mov { border-top: 1px solid var(--border-sutil); }
  .caja-mov-monto { font-size: var(--fs-base); }

  /* -------------------------------------------------------------------
     LOS DOS FORMULARIOS, COMO HOJA
     -------------------------------------------------------------------
     Cerrados no se pintan; abiertos suben desde abajo con el mismo
     lenguaje que el carrito del punto de venta y el menu.

     .con-js: sin JavaScript no hay hoja que abrir, asi que los
     formularios se quedan donde estaban y la pantalla funciona igual
     que siempre. Es la misma garantia del resto del sistema.
     ------------------------------------------------------------------- */
  .con-js .caja-form-mov,
  .con-js .caja-cierre-form { display: none; }

  .con-js .caja-form-mov.como-hoja,
  .con-js .caja-cierre-form.como-hoja {
    display: flex;
    flex-direction: column;
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    top: auto;
    z-index: var(--z-hoja);
    max-height: var(--hoja-max);
    margin: 0;
    padding: 0 var(--s4) calc(var(--s4) + env(safe-area-inset-bottom));
    background: var(--sheet);
    border-radius: var(--r-hoja) var(--r-hoja) 0 0;
    box-shadow: var(--sh-hoja);
    overflow-y: auto;
    overscroll-behavior: contain;
    animation: hoja-sube var(--t-lenta) var(--ease-suave);
    transform: translateY(var(--hoja-arrastre, 0px));
  }
  .con-js .caja-form-mov.como-hoja:not(.arrastrando),
  .con-js .caja-cierre-form.como-hoja:not(.arrastrando) {
    transition: transform var(--t-media) var(--ease);
  }
  .con-js .caja-form-mov.como-hoja.arrastrando,
  .con-js .caja-cierre-form.como-hoja.arrastrando { animation: none; transition: none; }

  /* El asa se sale del relleno lateral para quedar centrada de verdad.
     Su apagado vive fuera de esta consulta, con el resto de piezas que
     solo existen para telefono. */
  .como-hoja > .hoja-asa { display: flex; margin-inline: calc(var(--s4) * -1); }

  .como-hoja > .cj-hoja-cab {
    display: flex;
    align-items: flex-start;
    gap: var(--s3);
    padding-bottom: var(--s3);
    margin-bottom: var(--s4);
    border-bottom: 1px solid var(--border-sutil);
  }
  .cj-hoja-cab h3 {
    flex: 1;
    margin: 0;
    font-size: var(--fs-lg);
    font-weight: var(--peso-fuerte);
    letter-spacing: var(--ls-titulo);
  }

  /* El boton de envio de cada hoja: grande y al final. */
  .como-hoja .btn[type="submit"] {
    width: 100%;
    min-height: 52px;
    margin-top: var(--s4);
    font-size: var(--fs-lg);
  }

  /* ---------- El disparador del cierre ---------- */
  .con-js .cj-abrir-cierre {
    display: flex;
    width: 100%;
    min-height: 50px;
  }

  /* ---------- Apertura de turno ---------- */
  /* El fondo inicial es el unico dato de la pantalla: su campo se lee
     como la cifra que es. */
  .caja-apertura { display: block; }
  .caja-apertura .dinero-caja input { font-size: 30px; }
  .caja-apertura .dinero-signo { font-size: 24px; }

  /* La guia de cuatro pasos es contexto, no contenido: baja de tono. */
  .caja-pasos { font-size: var(--fs-sm); }
}

/* ---------------------------------------------------------------------
   44b. CORTES ANTERIORES  (YA ESTABA RESUELTO)
   ---------------------------------------------------------------------
   Se escribio aqui un bloque para convertir el historial en fichas y se
   RETIRO al descubrir que la seccion 36 ya lo hacia, y bien: rejilla de
   dos columnas, turno a la izquierda, diferencia a la derecha, los
   importes con su rotulo puesto por data-label y la accion a lo ancho.

   Duplicarlo no habria anadido nada y si habria creado dos juegos de
   reglas peleandose por las mismas celdas: display:block contra
   display:inline-flex sobre el mismo td, decidido por especificidad.
   Es exactamente el CSS contradictorio que este trabajo tenia que
   evitar.

   Lo que si faltaba -jerarquia dentro de la ficha- se anade AHI, junto
   al bloque original, y no aqui. Ver "afinado de la ficha" en la
   seccion 36.
   --------------------------------------------------------------------- */

/* ---------------------------------------------------------------------
   44c. DOS AJUSTES DE ACABADO
   ---------------------------------------------------------------------
   1. EL ROTULO DUPLICADO. Al irse el formulario a la hoja, la lamina
      "Entradas y salidas" se quedo con su descripcion -que describe el
      formulario que ya no esta ahi- y con un segundo rotulo interno,
      "Movimientos del turno", debajo del rotulo de la lamina. Tres
      encabezados para una lista.

   2. EL BOTON DE CIERRE. Rojo solido, a ancho completo y de 50px al
      final de la pantalla: se lleva la mirada por encima de la cifra del
      turno, que es la que manda. El encargo lo dice: accesible y
      evidente, pero no el protagonista absoluto.

      Pasa a contorno con texto de peligro. Sigue midiendo lo mismo -no
      se pierde ni un pixel de area tactil- y sigue llevando su
      confirmacion. Lo que baja es el VOLUMEN, no la importancia.
   --------------------------------------------------------------------- */

@media (max-width: 767px) {
  .con-js .caja-cols > .panel:has(.caja-mov-lista) .panel-desc,
  .con-js .caja-mov-rot { display: none; }

  .con-js .cj-abrir-cierre {
    background: none;
    border: 1px solid var(--error);
    color: var(--error);
    box-shadow: none;
  }
  .con-js .cj-abrir-cierre .icono { color: var(--error); }
  .con-js .cj-abrir-cierre:active { background: var(--error-bg); }
}

/* ---------------------------------------------------------------------
   44d. EL BOTON DE UNA HOJA ES LA ACCION DE ESA HOJA
   ---------------------------------------------------------------------
   "Registrar movimiento" se pinta como secundario, y en escritorio esta
   bien: comparte pantalla con el cierre y con "Ir a vender", asi que no
   puede ir gritando. Dentro de la hoja NO comparte pantalla con nada, es
   lo unico que hay ahi, y un boton de contorno como unica accion de una
   hoja se lee a "quiza".

   Se cambia el peso, no el marcado: el mismo boton, el mismo submit y el
   mismo formulario. Solo dentro de la hoja y solo en telefono.
   --------------------------------------------------------------------- */

@media (max-width: 767px) {
  .caja-form-mov.como-hoja .btn[type="submit"] {
    background: var(--btn-primario-bg);
    border-color: var(--btn-primario-bg);
    color: var(--btn-primario-texto);
    box-shadow: var(--sh-marca);
  }
  .caja-form-mov.como-hoja .btn[type="submit"] .icono { color: currentColor; }
  /* El halo de color en oscuro se contiene, como en el tablero. */
  :root[data-tema="oscuro"] .caja-form-mov.como-hoja .btn[type="submit"] {
    box-shadow: var(--sh-btn-luz);
  }
}

/* ---------------------------------------------------------------------
   44e. UNA CONFIRMACION SIEMPRE VA POR ENCIMA DE UNA HOJA
   ---------------------------------------------------------------------
   FALLO ENCONTRADO AL PROBAR EL CIERRE DE CAJA, y de los serios.

   El formulario de cierre pasa a ser una hoja, y una hoja vive en
   --z-hoja (310). El dialogo de confirmacion vive en --z-modal (300).
   Resultado: se pulsa "Cerrar caja y hacer el corte", el dialogo se
   monta -esta en el DOM, se puede leer con el inspector- y NO SE VE,
   porque la hoja lo tapa entero. Desde fuera parece que el boton no
   hace nada. En la accion irreversible del modulo.

   El mismo defecto estaba ya en el punto de venta desde que el carrito
   se presenta como hoja: "Vaciar" pide confirmacion y esa confirmacion
   quedaba debajo. Ahi no se habia visto porque vaciar no destruye nada.

   LA REGLA, y vale para todo el sistema: una HOJA ofrece y se descarta
   deslizandola; un MODAL interrumpe y exige una respuesta. Lo que
   interrumpe va siempre por encima de lo que ofrece. Los avisos
   flotantes se quedan por encima de los dos, que es donde estaban.
   --------------------------------------------------------------------- */

.modal-fondo { z-index: calc(var(--z-hoja) + 10); }

/* El halo del boton primario de Caja se contiene en oscuro, con el mismo
   criterio y el mismo valor que el del tablero: sobre una superficie de
   54px a ancho completo, --sh-marca deja de ser sombra de color y pasa a
   ser glow. */
:root[data-tema="oscuro"] .cj-acciones .btn { box-shadow: var(--sh-btn-luz); }

/* ---------------------------------------------------------------------
   44f. TEMA OSCURO: LOS BOTONES PRIMARIOS DE CAJA VAN EN BLANCO
   ---------------------------------------------------------------------
   El sistema pinta el texto de un boton primario con --text-inv, que en
   oscuro es #14140f (casi negro). No es un descuido: sobre el azul claro
   del tema oscuro (#3b82f6) el texto oscuro da 5.02:1 y el blanco solo
   3.68:1, por debajo del 4.5:1 que pide AA para un texto de 17px a 600.

   Se pide texto BLANCO en Caja. Poner blanco sobre el azul de siempre
   dejaria el boton en 3.68:1, asi que para tener las dos cosas -blanco Y
   contraste adecuado- lo que se ajusta es el AZUL, un solo punto:

       #3b82f6  con blanco  3.68:1   falla AA
       #2f6fdc  con blanco  4.74:1   pasa AA          <- el elegido
       #2563eb  con blanco  5.17:1   pasa, pero se aleja mas del tono

   #2f6fdc es el azul MAS CERCANO al original que cumple: el desvio de
   tono es el minimo posible. Y sigue separandose del fondo de la
   aplicacion en 3.90:1, por encima del 3:1 que pide AA para un
   componente, asi que el boton no se hunde en el fondo.

   No cambia nada mas: mismo tamano, misma geometria, misma sombra,
   mismo comportamiento. El icono hereda el color del texto porque el
   componente ya lo pinta con currentColor.

   ALCANCE: solo los botones primarios de Caja, que es lo que se pidio.
   El resto del sistema -entrar, nueva venta, cobrar- conserva el texto
   oscuro y sus 5.02:1. Si algun dia se quiere unificar, el sitio es el
   token, no esta regla.

   OJO CON EL SELECTOR: ui_boton() NO emite la clase .btn-primario. El
   primario es la variante por omision del componente, asi que un boton
   primario sale como `class="btn ..."` a secas y solo las OTRAS
   variantes anaden su clase. Por eso "Abrir caja" se apunta por
   descarte -un .btn que no es secundario, ni plano, ni de peligro- y no
   por una clase que no existe. Se intento con .btn-primario primero y
   no encontraba nada. */

:root[data-tema="oscuro"] .cj-acciones .btn,
:root[data-tema="oscuro"] .caja-form-mov .btn[type="submit"],
:root[data-tema="oscuro"] .caja-abrir .btn:not(.btn-sec):not(.btn-plano):not(.btn-peligro) {
  background: var(--brand-600-aa);
  border-color: var(--brand-600-aa);
  color: #ffffff;
}


/* =====================================================================
   47. EXISTENCIAS EN TELEFONO
   ---------------------------------------------------------------------
   Lo que se medio antes de tocar nada, a 390x844 con dos sucursales:

     - La pantalla abria con DOS tarjetas de sucursal de 117px cada una
       o con TRES metricas del mismo peso. Con la barra superior y el
       titulo, la primera existencia empezaba en el pixel 612 de 844:
       tres cuartos de pantalla sin ver un solo producto.

     - Debajo venia una MATRIZ: producto x sucursal x total. Cuatro
       columnas en 328px utiles. La columna del producto se quedaba en
       118px -"Cuaderno profesional 100 h" en dos renglones y medio- y
       las cifras en 46px cada una. Con tres sucursales se salia de la
       pantalla y habia que deslizar la tabla a lo ancho para saber
       cuanto quedaba de algo.

   Lo que hay ahora:

     1. UNA CIFRA. Cuantas unidades hay, en grande, con el ambito encima
        y el valor a costo debajo. Sin caja: son tres parrafos.

     2. LA LISTA TACTIL del sistema. Nombre, codigo, existencia; el chip
        solo cuando algo va mal; toda la fila es el toque.

     3. LA MATRIZ SE QUEDA para tableta y escritorio, que es donde
        comparar sucursales lado a lado tiene sentido y sitio.

   El reparto es .inv-tactil / .inv-escritorio. No hay dos modulos: hay
   un modulo con dos formas, las dos pintadas del mismo $productos.

   FASE 6: el heroe (.inv-heroe*) y las tarjetas de sucursal
   (.inv-suc-tarjeta*) ya no existen. La cifra vive ahora en la tarjeta
   de resumen y el reparto en una lista, los dos en la SECCION 66. Sus
   reglas se borraron de aqui en vez de dejarlas muertas: apuntaban a
   clases que ningun PHP escribe.
   ===================================================================== */

/* ---------- Reparto por ancho ---------- */
/* La lista tactil solo existe en telefono; la matriz, de 768 arriba.
   Se hace por CSS y no por PHP porque el servidor no sabe -ni debe
   adivinar- el ancho de la ventana. */
.inv-tactil { display: none; }

@media (max-width: 767px) {
  .inv-tactil { display: block; }
  .inv-escritorio { display: none; }
}

/* La existencia dentro del subtitulo de la fila. Es el dato por el que
   se entra a esta pantalla, asi que se lee como cifra y no como texto
   corrido, y toma el color del estado. Normal se queda NEUTRO: si todo
   fuera de color, el color dejaria de significar nada. */
.inv-n {
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  font-weight: var(--peso-fuerte);
  color: var(--text);
}
.inv-n-critico { color: var(--warn); }
.inv-n-bajo    { color: var(--crit); }
.inv-n-agotado { color: var(--error); }

/* Fila de lista que NO navega: la cuenta no tiene permiso de
   movimientos. Se ve igual, pero no finge ser un boton. */
.lista-estatica {
  cursor: default;
  display: flex;
  align-items: center;
  gap: var(--s3);
  width: 100%;
  min-height: var(--fila-tap);
  padding: var(--s3) var(--s4);
}
.lista-estatica:hover { background: none; }

/* El vacio dentro del panel, sin la cabecera de una tabla que no hay */
.inv-vacio { padding: var(--s5) var(--s4); }

/* ---------- Barra de herramientas en telefono ---------- */
@media (max-width: 767px) {

  /* Dos renglones y no cuatro. En columna -que es lo que heredaba de
     .inv-filtros- el buscador, el boton de filtros, "Filtrar" y
     "Limpiar" caian uno debajo de otro: 4 controles y 212px de barra
     antes del primer producto. Ahora el buscador manda en el primer
     renglon y los controles comparten el segundo. */
  .inv-filtros-ex {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    gap: var(--s2);
    align-items: center;
  }
  .inv-filtros-ex .inv-buscador {
    grid-column: 1 / -1;
    width: 100%;
  }
  /* El grupo plegable ocupa su propio renglon, DESPUES de los botones:
     se abre hacia abajo, que es de donde viene el dedo. */
  .inv-filtros-ex .inv-filtros-sec {
    grid-column: 1 / -1;
    order: 9;
    flex-direction: column;
    align-items: stretch;
  }
  .inv-filtros-ex .inv-filtros-sec .campo { width: 100%; flex: none; }

  .inv-filtros-ex .filtros-plegar-btn:not([hidden]) {
    grid-column: 1;
    justify-self: start;
    width: auto;
    min-width: 120px;
  }
  /* Sin JavaScript el boton "Filtros" no llega a existir -lo crea
     app.js- y el grupo se ve entero. Ahi las acciones se quedan con la
     fila completa; solo comparten renglon con el boton cuando el boton
     esta de verdad en la pagina. */
  .inv-filtros-ex .inv-filtros-acciones {
    grid-column: 1 / -1;
    display: flex;
    gap: var(--s2);
  }
  .inv-filtros-ex .filtros-plegar-btn:not([hidden]) ~ .inv-filtros-acciones {
    grid-column: 2;
    justify-self: end;
  }
  .inv-filtros-ex .inv-filtros-acciones .btn { min-height: var(--tap); flex: 1 1 auto; }


  /* La lista va a sangre dentro del panel: el aire lateral ya lo pone
     cada fila con su padding, y sumarle el del cuerpo dejaba el texto
     empezando en el pixel 32. */
  .panel-cuerpo.sin-espacio .inv-tactil { margin: 0; }
  .inv-tactil .lista-tramo { padding-bottom: var(--s1); }
}

/* ---------- Escritorio: el heroe es una banda, no un titular ---------- */
@media (min-width: 768px) {
  /* El buscador se lleva el ancho que sobra de la barra, igual que en
     el catalogo. Sin esto se queda en su ancho intrinseco y los dos
     selectores se comen la fila. */
  .inv-filtros-ex .inv-buscador { flex: 1 1 280px; min-width: 0; }
}


/* =====================================================================
   48. SUCURSALES: GESTION DE UBICACIONES
   ---------------------------------------------------------------------
   Lo que se midio antes de tocar nada, a 390x844 con dos sucursales:

     - La pantalla abria con TRES tarjetas de metrica del mismo peso
       ("Sucursales 2", "Unidades 99", "Valor 1,121") y despues un panel
       con la tabla apilada como ficha. Con la barra superior, el
       titulo, las dos acciones secundarias y las metricas, la primera
       sucursal empezaba en el pixel 400 de 844.

     - Cada sucursal era una FICHA de 117px con borde, esquina y un
       boton "Editar" de 44px a ancho completo dentro. Dos sucursales
       ya eran dos paneles dentro de un panel dentro de la pagina.

     - Debajo de todo, un formulario de alta PERMANENTE de 300px que se
       ve siempre aunque no se vaya a crear nada, y un panel de ayuda de
       cinco renglones detras de el.

     - No habia forma de PASAR A TRABAJAR en otra sucursal desde aqui:
       para eso habia que subir a la pastilla de la barra. La pantalla
       de sucursales no servia para lo que mas se hace con sucursales.

   Lo que hay ahora:

     1. UN TITULAR de texto: cuantas ubicaciones hay, cuantas activas,
        cuantas unidades y cuanto valen. Los mismos cuatro datos, sin
        tres cajas.

     2. UNA LISTA tactil del sistema: nombre, estado de la caja y
        existencias. Toda la fila es el toque y abre la HOJA de esa
        sucursal, que es su detalle: como esta, "Trabajar aqui" pegado
        abajo, y la administracion agrupada aparte.

     3. LA TABLA SE QUEDA de 768 arriba, que es donde comparar seis
        sucursales columna a columna tiene sentido y sitio. Ahi tambien
        aparece "Trabajar aqui", con el MISMO POST.

     4. EL FORMULARIO deja de estar siempre a la vista en telefono: sube
        como hoja al pulsar "Nueva sucursal". Es el mismo form, el mismo
        POST y el mismo CSRF; solo cambia la presentacion. Sin
        JavaScript -o con una edicion en curso, o con errores- se queda
        fijo donde estaba y la pantalla funciona igual que siempre.

   El reparto es .suc-tactil / .suc-escritorio. No hay dos modulos: hay
   un modulo con dos formas, las dos pintadas del mismo $filas.

   NADA DE ESTO AUTORIZA NADA. El cambio de sucursal sigue siendo un
   POST a cliente/sucursal_cambiar.php con su CSRF, y ese archivo vuelve
   a comprobar en el servidor que la sucursal este autorizada y activa.
   ===================================================================== */

/* ---------- El titular ---------- */
.suc-heroe { margin: 0 0 var(--s5); }
.suc-heroe p { margin: 0; }

.suc-heroe-ambito {
  display: flex;
  align-items: center;
  gap: var(--s2);
  font-size: var(--fs-xs);
  font-weight: var(--peso-fuerte);
  letter-spacing: var(--ls-etiqueta);
  text-transform: uppercase;
  color: var(--text-3);
}
.suc-heroe-ambito .icono { flex: none; }

.suc-heroe-cifra {
  display: flex;
  align-items: baseline;
  gap: var(--s2);
  margin-top: var(--s2) !important;
  line-height: 1;
}
.suc-heroe-cifra strong {
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-3xl);
  font-weight: var(--peso-max);
  letter-spacing: -0.02em;
  color: var(--text);
}
.suc-heroe-cifra span {
  font-size: var(--fs-lg);
  color: var(--text-2);
}
.suc-heroe-apoyo {
  margin-top: var(--s2) !important;
  font-size: var(--fs-sm);
  color: var(--text-2);
}

/* ---------- Reparto por ancho ---------- */
/* La lista existe hasta 1023; la tabla, de 1024 arriba. Se hace por CSS
   y no por PHP porque el servidor no sabe -ni debe adivinar- el ancho de
   la ventana. Las hojas de cada sucursal viven DENTRO de .suc-tactil: de
   1024 arriba su disparador no se pinta, asi que no hay dialogos
   huerfanos que se puedan abrir sin querer.

   EL CORTE SUBE DE 768 A 1023, Y ESTA MEDIDO.
   Esta tabla tiene SEIS columnas -sucursal, existencias, usuarios, caja,
   estado y acciones- y en el tramo de tableta no caben:

       768   la fila de UNA sucursal mide 184px
       820   170
       912   130
      1024    93   <- desde aqui la tabla respira

   A 820 eso significaba el nombre en tres renglones, "832 unidades /
   $10.991,00 a costo" en tres, "1 asignados / mas el / propietario" en
   tres y los dos botones apilados. La lista tactil pone esa misma
   sucursal en UN renglon limpio, y lo que no cabe en la fila -direccion,
   telefono, editar, desactivar, trabajar aqui- esta en su hoja, que de
   768 arriba se presenta como dialogo centrado. Comprobado a 820: abre
   y trae mas datos de los que daba la fila de la tabla.

   No es una regla nueva, es la misma con otro numero: una tableta se
   toca con el dedo igual que un telefono. */
.suc-tactil { display: none; }

/* 1023.98 y no 1023: con la pantalla a 1.25 de densidad el ancho real
   del area util sale fraccionario -1023.2- y `max-width: 1023px` NO lo
   cubre, asi que en ese pelo se colaba la tabla. Comprobado en el
   navegador. Es el hueco N/N+1 de siempre. */
@media (max-width: 1023.98px) {
  .suc-tactil     { display: block; }
  .suc-escritorio { display: none; }
  /* La guia de cinco renglones seria una quinta superficie en una
     pantalla que ya dice lo que hay que saber. Sus enlaces no se
     pierden: estan en .suc-pie-ayuda, en una sola frase, y esa frase
     vive DENTRO de .suc-tactil, asi que aparece justo donde y cuando
     esta regla retira la guia. */
  .suc-guia { display: none; }
}

/* ---------- El punto de estado de la caja ---------- */
/* La UNICA marca de color de la fila, y solo cuando la caja esta
   abierta. Si todo llevara color, el color dejaria de significar algo.
   Es el mismo punto que ya usa la hoja de sucursal de la barra. */
.suc-punto {
  display: inline-block;
  width: 7px;
  height: 7px;
  margin-right: 6px;
  border-radius: var(--r-full);
  background: var(--text-3);
  vertical-align: middle;
}
.suc-punto.abierta { background: var(--ok); }
.suc-punto-off     { background: var(--border-2); }

/* ---------- La hoja de una sucursal ---------- */
/* El apunte que acompana al dato dentro de un <dd> que es fuerte: se
   destensa a peso normal. No hay token para 400 -la escala empieza en
   500- asi que se escribe igual que en las otras siete apariciones. */
.suc-hoja-desde { color: var(--text-3); font-weight: 400; }
.suc-hoja-surtir {
  display: block;
  margin-top: var(--s0);
  font-size: var(--fs-xs);
  font-weight: var(--peso-fuerte);
  color: var(--crit);
}

/* Administrar es lo secundario de la hoja: se separa con una junta y un
   rotulo, para que "Desactivar" no comparta peso con "Trabajar aqui". */
.suc-hoja-admin {
  margin-top: var(--s4);
  padding-top: var(--s4);
  border-top: 1px solid var(--border-sutil);
  display: grid;
  gap: var(--s2);
}
.suc-hoja-admin-rot {
  margin: 0;
  font-size: var(--fs-xs);
  font-weight: var(--peso-fuerte);
  letter-spacing: var(--ls-etiqueta);
  text-transform: uppercase;
  color: var(--text-3);
}
.suc-hoja-estado-form { margin: 0; }
.suc-hoja-estado-form .btn { width: 100%; }

.suc-hoja-aqui {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--s2);
  margin: 0;
  min-height: var(--tap);
  font-size: var(--fs-sm);
  font-weight: var(--peso-fuerte);
  color: var(--ok);
}
.suc-hoja-nota-pie {
  margin: 0;
  min-height: var(--tap);
  display: flex;
  align-items: center;
  font-size: var(--fs-sm);
  color: var(--text-2);
}

/* Los enlaces que en escritorio viven en el panel de ayuda, en una
   frase, al final de la lista. */
.suc-pie-ayuda {
  margin: var(--s4) 0 0;
  padding: 0 var(--s1);
  font-size: var(--fs-sm);
  line-height: 1.5;
  color: var(--text-2);
}

.suc-vacio { padding: var(--s5) var(--s4); }

/* ---------- La fila de la sucursal en la que se trabaja ---------- */
/* En la tabla de escritorio, la misma marca que ya usa el panel de
   sucursales del tablero. En la lista tactil la pone .lista-fila.elegida
   del sistema, que no hace falta repetir aqui. */
.tabla-sucursales tr.es-actual td { background: var(--brand-50); }

/* =====================================================================
   48b. TELEFONO: EL FORMULARIO SUBE COMO HOJA
   ---------------------------------------------------------------------
   Es el segundo caso de hoja de app.js: un panel que YA ESTA en la
   pagina y que en telefono se presenta como hoja. No hay una segunda
   copia del formulario, ni un segundo POST, ni un segundo CSRF.

   .con-js: sin JavaScript no hay hoja que abrir, asi que el formulario
   se queda donde estaba y la pantalla funciona igual que siempre. Es la
   misma garantia del resto del sistema.

   .suc-form-hoja lo escribe el PHP solo cuando NO hay nada que atender:
   con una edicion en curso o con errores de validacion el formulario
   nace fijo, porque tiene que verse sin que nadie pulse nada.
   ===================================================================== */

/* El asa y la cruz solo existen cuando el panel ES una hoja. Se apagan
   contra .suc-form-panel -que el panel lleva siempre- y no contra
   .suc-form-hoja, que solo esta cuando el formulario se esconde: en una
   edicion el panel nace fijo y el asa se quedaba a la vista, prometiendo
   un arrastre que ahi no existe. */
.suc-form-panel > .hoja-asa,
.suc-form-cerrar { display: none; }

/* El disparador no se pinta sin JavaScript: sin el no abre nada. */
.suc-nueva-btn { display: none; }
.con-js .suc-nueva-btn { display: inline-flex; }

@media (max-width: 767px) {
  .con-js .suc-form-hoja { display: none; }

  .con-js .suc-form-hoja.como-hoja {
    display: flex;
    flex-direction: column;
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    top: auto;
    z-index: var(--z-hoja);
    max-height: var(--hoja-max);
    margin: 0;
    padding: 0 var(--s4) calc(var(--s4) + env(safe-area-inset-bottom));
    background: var(--sheet);
    border: 0;
    border-radius: var(--r-hoja) var(--r-hoja) 0 0;
    box-shadow: var(--sh-hoja);
    overflow-y: auto;
    overscroll-behavior: contain;
    animation: hoja-sube var(--t-lenta) var(--ease-suave);
    transform: translateY(var(--hoja-arrastre, 0px));
  }
  .con-js .suc-form-hoja.como-hoja:not(.arrastrando) {
    transition: transform var(--t-media) var(--ease);
  }
  .con-js .suc-form-hoja.como-hoja.arrastrando { animation: none; transition: none; }

  /* El asa se sale del relleno lateral para quedar centrada de verdad. */
  .suc-form-hoja.como-hoja > .hoja-asa {
    display: flex;
    margin-inline: calc(var(--s4) * -1);
  }
  .suc-form-hoja.como-hoja .suc-form-cerrar { display: inline-flex; }

  /* Dentro de la hoja, la cabecera del panel deja de ser cabecera de
     panel: es el encabezado del dialogo. */
  .suc-form-hoja.como-hoja > .panel-cab {
    display: flex;
    align-items: flex-start;
    gap: var(--s3);
    /* Sin nowrap el bloque de texto se lleva el renglon entero -es lo
       que hace .panel-cab en un panel- y la cruz cae debajo del
       parrafo, que es donde nadie la busca. */
    flex-wrap: nowrap;
    padding: 0 0 var(--s3);
    margin-bottom: var(--s4);
    border-bottom: 1px solid var(--border-sutil);
    background: none;
  }
  .suc-form-hoja.como-hoja > .panel-cab > div { flex: 1; min-width: 0; }
  .suc-form-hoja.como-hoja > .panel-cab h3 { font-size: var(--fs-lg); }
  .suc-form-hoja.como-hoja > .panel-cab .panel-desc { font-size: var(--fs-sm); }
  .suc-form-hoja.como-hoja > .panel-cuerpo { padding: 0; }
  .suc-form-hoja.como-hoja .btn[type="submit"] {
    width: 100%;
    min-height: 52px;
    font-size: var(--fs-lg);
  }
  .suc-form-hoja.como-hoja .form-acciones {
    position: static;
    margin: var(--s4) 0 0;
    padding: 0;
    border: 0;
    box-shadow: none;
    background: none;
  }

  /* El titular respira un poco menos: en 844px de alto cada 8px cuenta. */
  .suc-heroe { margin-bottom: var(--s4); }
  .suc-heroe-cifra strong { font-size: var(--fs-2xl); }

  /* La lista va a sangre dentro de su superficie: el aire lateral ya lo
     pone cada fila con su padding. */
  .suc-tactil .lista { margin: 0; }

  /* El chip de la derecha no puede comerse el nombre en 360px. */
  .suc-tactil .lista-fin .chip { max-width: 108px; }

  /* Cuando el formulario nace fijo -edicion en curso o errores de
     validacion- es lo que hay que atender, asi que va PRIMERO. Al final
     de la pagina obligaba a bajar toda la lista para leer un error que
     acababa de aparecer. En el estado normal el orden es el de siempre:
     primero se mira lo que hay y despues se agrega. */
  .suc-cols-form { display: flex; flex-direction: column; }
  .suc-cols-form .suc-form  { order: 0; }
  .suc-cols-form .suc-lista { order: 1; }
}

/* ---------- Escritorio: la tabla necesita el ancho que le falta ------ */
/* MEDIDO a 1280 con el menu lateral abierto: la columna del listado se
   quedaba en 567px y la tabla pide 597, asi que la pantalla de
   administrar sucursales abria con desplazamiento lateral dentro del
   panel. El reparto 1.6/1 lo hereda de una regla que comparte con
   Usuarios; aqui se afina SOLO para .suc-cols, porque es la unica de
   las dos que lleva seis columnas.

   Dos cambios, los dos SOLO para .suc-cols:

   1. La segunda columna no empieza en 1024 sino en 1400. Entre esos dos
      anchos -una tableta apaisada, un portatil de 13", un monitor de
      1280- el contenido util son entre 873 y 950px: la tabla PIDE 597
      solo para no cortarse, asi que al ponerle el formulario al lado se
      quedaba en 567 -desplazamiento lateral a 1024- o en 617, donde
      "Sucursal principal" se parte en dos renglones y la columna del
      nombre pesa menos que la de usuarios. A una sola columna la tabla
      se lleva el ancho entero y el formulario baja, que es el orden en
      el que se usa: primero se mira lo que hay y despues se agrega.

   2. De 1400 arriba, donde si sobra sitio para las dos, el reparto es
      2fr/1fr y no 1.6fr/1fr: a 1440 eso es 740/370, con la tabla
      holgada y el formulario con espacio de sobra para tres campos. */
@media (min-width: 1024px) and (max-width: 1399px) {
  .suc-cols { grid-template-columns: minmax(0, 1fr); }
}
/* Mientras el formulario va DEBAJO de la tabla no toma su ancho: tres
   campos estirados a 900px se leen como un formulario roto, y el ojo
   tiene que recorrer media pantalla para ir del rotulo al hueco. */
@media (min-width: 768px) and (max-width: 1399px) {
  .suc-cols .suc-form { max-width: 560px; }
}
@media (min-width: 1400px) {
  .suc-cols { grid-template-columns: minmax(0, 2fr) minmax(280px, 1fr); }
}

/* ---------- De 768 arriba el titular es una banda ---------- */
@media (min-width: 768px) {
  .suc-heroe {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--s2) var(--s4);
  }
  .suc-heroe-ambito { flex: 1 0 100%; }
  .suc-heroe-cifra,
  .suc-heroe-apoyo { margin-top: 0 !important; }
}

/* ---------- Tema oscuro ---------- */
/* La hoja del formulario se levanta como cualquier otra superficie:
   filo de luz arriba y la sombra del sistema. La marca de "aqui es
   donde trabajo" usa --selected, que se ve sobre cualquier fondo;
   --brand-50 en oscuro es casi el mismo color que el panel. */


/* =====================================================================
   49. USUARIOS: LA GENTE DEL NEGOCIO
   ---------------------------------------------------------------------
   Lo que se midio antes de tocar nada, a 390x844 con el negocio de
   pruebas:

     - La pantalla abria con TRES tarjetas de metrica del mismo peso
       ("Personas 1", "Encargados 0", "Empleados 0") que ademas son
       partes de la MISMA cifra: la primera persona empezaba en el
       pixel 400 de 844.

     - Cada persona era una ficha "ETIQUETA: valor" de la seccion 14:
       el nombre salia ALINEADO A LA DERECHA, en la celda de un campo
       llamado "USUARIO", con el correo debajo pesando casi lo mismo, y
       cuatro renglones mas de rotulos en versalitas. Una persona no es
       un registro con cinco campos: es alguien con un nombre.

     - Debajo, un formulario de alta PERMANENTE con dieciseis casillas
       de permisos abiertas. Medido: 1 940px de formulario que casi
       nunca se usa, siempre a la vista.

   Lo que hay ahora:

     1. UN TITULAR de texto: cuanta gente hay y como se reparte. Los
        mismos datos de las tres tarjetas, sin tres cajas.

     2. UNA LISTA de PERSONAS: iniciales, nombre, "rol · sucursales", y
        el estado a la derecha. Toda la fila es el toque y abre la HOJA
        de esa persona -su detalle y su administracion-. El correo se
        va a la hoja, que es donde se consulta.

     3. LA TABLA SE QUEDA de 768 arriba, que es donde comparar seis
        cuentas columna a columna tiene sentido y sitio.

     4. EL FORMULARIO deja de estar siempre a la vista en telefono: sube
        como hoja al pulsar "Nuevo usuario". Es el mismo form, el mismo
        POST y el mismo CSRF; solo cambia la presentacion. Y los
        permisos van PLEGADOS en un <details> nativo: siguen dentro del
        formulario y se envian igual, pero dejan de abrirse solos.

   El reparto es .usr-tactil / .usr-escritorio, el mismo patron que
   Existencias y Sucursales: un modulo con dos formas, las dos pintadas
   del mismo $filas.

   NADA DE ESTO AUTORIZA NADA. Quien puede tocar a quien lo decide
   cliente/usuarios.php con las mismas reglas de siempre, y lo vuelve a
   comprobar en cada POST.
   ===================================================================== */

/* ---------- El titular ---------- */
.usr-heroe { margin: 0 0 var(--s5); }
.usr-heroe p { margin: 0; }

.usr-heroe-ambito {
  display: flex;
  align-items: center;
  gap: var(--s2);
  font-size: var(--fs-xs);
  font-weight: var(--peso-fuerte);
  letter-spacing: var(--ls-etiqueta);
  text-transform: uppercase;
  color: var(--text-3);
}
.usr-heroe-ambito .icono { flex: none; }

.usr-heroe-cifra {
  display: flex;
  align-items: baseline;
  gap: var(--s2);
  margin-top: var(--s2) !important;
  line-height: 1;
}
.usr-heroe-cifra strong {
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-3xl);
  font-weight: var(--peso-max);
  letter-spacing: -0.02em;
  color: var(--text);
}
.usr-heroe-cifra span {
  font-size: var(--fs-lg);
  color: var(--text-2);
}
.usr-heroe-apoyo {
  margin-top: var(--s2) !important;
  font-size: var(--fs-sm);
  color: var(--text-2);
}

/* ---------- Reparto por ancho ---------- */
/* La lista solo existe en telefono; la tabla, de 768 arriba. Las hojas
   de cada persona viven DENTRO de .usr-tactil: de 768 arriba su
   disparador no se pinta, asi que no hay dialogos huerfanos. */
.usr-tactil { display: none; }

@media (max-width: 767px) {
  .usr-tactil     { display: block; }
  .usr-escritorio { display: none; }
}

/* ---------- Las iniciales de una persona ---------- */
/* Reutiliza las SEIS variantes de color de .prod-img-c0..c5 -las mismas
   que ya identifican a un producto sin foto, con su version oscura ya
   resuelta- y solo cambia la forma: un producto es un cuadrado
   redondeado, una persona es un circulo. No hay paleta nueva.

   El marcador de la lista pierde su fondo propio para no dibujar una
   caja detras del circulo. */
.usr-tactil .lista-marca {
  background: none;
  border-radius: 0;
  overflow: visible;
}
/* El elemento lleva .prod-img (tamano, borde, tipografia y las seis
   variantes de color, oscuro incluido) y .usr-ini SOLO le cambia la
   forma. Escribir aqui el fondo volveria a pisar .prod-img-cN, que se
   declara mil lineas antes y pesa lo mismo: el marcador salia gris. */
.usr-ini { border-radius: var(--r-full); }

/* Una cuenta bloqueada no se tacha ni se pinta de rojo: se apaga. El
   chip gris de la derecha es el que lo dice con palabras. */
.usr-bloqueada .usr-ini { opacity: .55; }
.usr-bloqueada .lista-titulo { color: var(--text-2); }

/* ---------- La hoja de una persona ---------- */
/* El apunte que acompana al dato dentro de un <dd> que es fuerte: se
   destensa a peso normal y baja de tono. */
.usr-hoja-nota {
  display: block;
  margin-top: var(--s0);
  font-size: var(--fs-xs);
  font-weight: 400;
  color: var(--text-3);
}
/* Un correo largo parte donde haga falta antes que salirse del dialogo. */
.usr-hoja-correo { overflow-wrap: anywhere; }

/* Bloquear es lo secundario de la hoja: se separa con una junta y un
   rotulo, para que no comparta peso con "Editar datos". */
.usr-hoja-admin {
  margin-top: var(--s4);
  padding-top: var(--s4);
  border-top: 1px solid var(--border-sutil);
  display: grid;
  gap: var(--s2);
}
.usr-hoja-admin-rot {
  margin: 0;
  font-size: var(--fs-xs);
  font-weight: var(--peso-fuerte);
  letter-spacing: var(--ls-etiqueta);
  text-transform: uppercase;
  color: var(--text-3);
}
.usr-hoja-estado-form { margin: 0; }
.usr-hoja-estado-form .btn { width: 100%; }

.usr-hoja-nota-pie {
  margin: 0;
  min-height: var(--tap);
  display: flex;
  align-items: center;
  font-size: var(--fs-sm);
  color: var(--text-2);
}

.usr-pie-ayuda {
  margin: var(--s4) 0 0;
  padding: 0 var(--s1);
  font-size: var(--fs-sm);
  line-height: 1.5;
  color: var(--text-2);
}

/* Todavia no hay equipo: la cuenta propia si esta -siempre esta- pero
   no hay a quien administrar. Se dice y se ofrece el siguiente paso,
   dentro de la misma superficie de la lista y detras de su junta. */
.usr-invita {
  padding: var(--s4) var(--s4) var(--s5);
  border-top: 1px solid var(--border-sutil);
}

/* La fila de la cuenta propia en la tabla de escritorio: la misma marca
   discreta que usa la sucursal en la que se trabaja. */
.tabla-usuarios-neg tr.es-yo td { background: var(--brand-50); }

/* =====================================================================
   49b. LOS PERMISOS, PLEGADOS
   ---------------------------------------------------------------------
   Dieciseis casillas con su descripcion son casi tres pantallas de
   telefono ANTES del boton de guardar, y en la mayoria de las altas no
   se tocan: vienen los del rol, que es lo correcto.

   Es un <details> NATIVO. Sin JavaScript se abre igual, las casillas
   siguen dentro del mismo formulario y se envian esten a la vista o no.
   No cambia el catalogo, ni el POST, ni permisos_guardar(): el modulo
   de permisos no se rediseña aqui, solo deja de abrirse solo.
   ===================================================================== */

.usr-plegable {
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  padding: 0;
  overflow: hidden;
}
.usr-plegable-cab {
  display: flex;
  align-items: center;
  gap: var(--s3);
  min-height: var(--tap);
  padding: var(--s3) var(--s4);
  cursor: pointer;
  list-style: none;
  -webkit-tap-highlight-color: transparent;
}
/* La flecha de serie del navegador se apaga en los dos dialectos. */
.usr-plegable-cab::-webkit-details-marker { display: none; }
.usr-plegable-cab::marker { content: ""; }
.usr-plegable-cab:hover { background: var(--hover); }
.usr-plegable-cab:active { background: var(--pressed); }
.usr-plegable-cab:focus-visible { outline: var(--foco); outline-offset: -2px; }

.usr-plegable-txt { flex: 1; min-width: 0; display: grid; gap: var(--s0); }
.usr-plegable-txt strong {
  font-size: var(--fs-base);
  font-weight: var(--peso-fuerte);
  color: var(--text);
}
.usr-plegable-txt small {
  font-size: var(--fs-xs);
  color: var(--text-2);
  line-height: 1.35;
}
.usr-plegable-flecha {
  flex: none;
  display: inline-flex;
  color: var(--text-3);
  transition: transform var(--t-media) var(--ease);
}
.usr-plegable[open] .usr-plegable-flecha { transform: rotate(180deg); }
.usr-plegable[open] .usr-plegable-cab { border-bottom: 1px solid var(--border-sutil); }

/* El contenido respira dentro de la caja plegada. */
.usr-plegable > *:not(.usr-plegable-cab) {
  margin-left: var(--s4);
  margin-right: var(--s4);
}
.usr-plegable > .usr-perm-grupo:last-child { margin-bottom: var(--s4); }
.usr-plegable .usr-ayuda-arriba { margin-top: var(--s3); }

@media (prefers-reduced-motion: reduce) {
  .usr-plegable-flecha { transition: none; }
}

/* =====================================================================
   49c. TELEFONO: EL FORMULARIO SUBE COMO HOJA
   ---------------------------------------------------------------------
   El mismo mecanismo que en Sucursales: un panel que YA ESTA en la
   pagina y que en telefono se presenta como hoja (el segundo caso de
   hoja de app.js). No hay una segunda copia del formulario, ni un
   segundo POST, ni un segundo CSRF.

   .con-js: sin JavaScript no hay hoja que abrir, asi que el formulario
   se queda donde estaba y la pantalla funciona igual que siempre.

   .usr-form-hoja lo escribe el PHP solo cuando NO hay nada que atender:
   con una edicion en curso o con errores de validacion el formulario
   nace fijo, porque tiene que verse sin que nadie pulse nada.
   ===================================================================== */

/* El asa y la cruz solo existen cuando el panel ES una hoja. */
.usr-form-panel > .hoja-asa,
.usr-form-cerrar { display: none; }

/* El disparador no se pinta sin JavaScript: sin el no abre nada. */
.usr-nuevo-btn { display: none; }
.con-js .usr-nuevo-btn { display: inline-flex; }

@media (max-width: 767px) {
  .con-js .usr-form-hoja { display: none; }

  .con-js .usr-form-hoja.como-hoja {
    display: flex;
    flex-direction: column;
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    top: auto;
    z-index: var(--z-hoja);
    max-height: var(--hoja-max);
    margin: 0;
    padding: 0 var(--s4) calc(var(--s4) + env(safe-area-inset-bottom));
    background: var(--sheet);
    border: 0;
    border-radius: var(--r-hoja) var(--r-hoja) 0 0;
    box-shadow: var(--sh-hoja);
    overflow-y: auto;
    overscroll-behavior: contain;
    animation: hoja-sube var(--t-lenta) var(--ease-suave);
    transform: translateY(var(--hoja-arrastre, 0px));
  }
  .con-js .usr-form-hoja.como-hoja:not(.arrastrando) {
    transition: transform var(--t-media) var(--ease);
  }
  .con-js .usr-form-hoja.como-hoja.arrastrando { animation: none; transition: none; }

  .usr-form-hoja.como-hoja > .hoja-asa {
    display: flex;
    margin-inline: calc(var(--s4) * -1);
  }
  .usr-form-hoja.como-hoja .usr-form-cerrar { display: inline-flex; }

  /* Dentro de la hoja, la cabecera del panel deja de ser cabecera de
     panel: es el encabezado del dialogo. Sin nowrap el bloque de texto
     se lleva el renglon entero y la cruz cae debajo del parrafo. */
  .usr-form-hoja.como-hoja > .panel-cab {
    display: flex;
    align-items: flex-start;
    gap: var(--s3);
    flex-wrap: nowrap;
    padding: 0 0 var(--s3);
    margin-bottom: var(--s4);
    border-bottom: 1px solid var(--border-sutil);
    background: none;
  }
  .usr-form-hoja.como-hoja > .panel-cab > div { flex: 1; min-width: 0; }
  .usr-form-hoja.como-hoja > .panel-cab h3 { font-size: var(--fs-lg); }
  .usr-form-hoja.como-hoja > .panel-cab .panel-desc { font-size: var(--fs-sm); }
  .usr-form-hoja.como-hoja > .panel-cuerpo { padding: 0; }
  .usr-form-hoja.como-hoja .btn[type="submit"] {
    width: 100%;
    min-height: 52px;
    font-size: var(--fs-lg);
  }
  /* La barra de acciones anclada de la seccion 29 no se ancla DENTRO de
     una hoja que ya se desplaza sola: ahi se quedaria flotando sobre su
     propio contenido. */
  .usr-form-hoja.como-hoja .form-acciones {
    position: static;
    margin: var(--s4) 0 0;
    padding: 0;
    border: 0;
    box-shadow: none;
    background: none;
  }

  /* El titular respira un poco menos: en 844px de alto cada 8px cuenta. */
  .usr-heroe { margin-bottom: var(--s4); }
  .usr-heroe-cifra strong { font-size: var(--fs-2xl); }

  .usr-tactil .lista { margin: 0; }

  /* MEDIDO a 390 con "Empleado / cajero · 2 sucursales": al subtitulo le
     faltaban 5px. Los tres ajustes de aqui le devuelven 20 sin quitar
     nada de la fila ni bajar de los 44px de toque:

       el circulo de iniciales   40 -> 36   (+4)
       las dos juntas de la fila 12 -> 8    (+8)
       el relleno lateral del chip 12 -> 8  (+8)

     Con eso entra entero de 375 arriba. En 360 el ultimo caso largo se
     corta con puntos suspensivos, que es lo que hace cualquier
     subtitulo del sistema. */
  .usr-tactil .lista-toque { gap: var(--s2); }
  .usr-tactil .usr-ini { width: 36px; height: 36px; }
  .usr-tactil .lista-marca { width: 36px; height: 36px; }
  .usr-tactil .lista-fila.con-marca + .lista-fila.con-marca::before {
    left: calc(var(--s4) + 36px + var(--s2));
  }
  .usr-tactil .lista-fin .chip { padding-inline: var(--s2); max-width: 96px; }

  /* Cuando el formulario nace fijo -edicion en curso o errores de
     validacion- es lo que hay que atender, asi que va PRIMERO. Al final
     de la pagina obligaba a bajar toda la lista para leer un error que
     acababa de aparecer. */
  .usr-cols-form { display: flex; flex-direction: column; }
  .usr-cols-form .usr-form  { order: 0; }
  .usr-cols-form .usr-lista { order: 1; }
}

/* ---------- Escritorio: la tabla necesita el ancho que le falta ------ */
/* MEDIDO a 1280 con el menu lateral abierto: la columna del listado se
   quedaba en 567px para CINCO columnas, una de ellas un correo entero.
   Resultado: "Demo Negocio (tú)" en tres renglones y "demo@demo.com"
   partido a media palabra. El reparto 1.6/1 lo hereda de una regla que
   comparte con Sucursales; aqui se afina SOLO para .usr-cols.

   Entre 1024 y 1599 la tabla se lleva el ancho entero y el formulario
   baja, que es el orden en el que se usa: primero se mira quien hay y
   despues se agrega. De 1600 arriba, donde si caben las dos, el reparto
   es 2fr/1fr.

   El corte va en 1600 y no en 1400 como en Sucursales, y la razon esta
   MEDIDA: esta tabla tiene una columna mas y una de ellas es un CORREO
   ENTERO. A 1440 con el formulario al lado, la columna del nombre se
   quedaba en 167px y "qa.temporal.borrar@example.invalid" salia en tres
   renglones: la fila subia a 109px. Con la tabla a ancho completo esa
   misma fila mide 72. */
@media (min-width: 1024px) and (max-width: 1599px) {
  .usr-cols { grid-template-columns: minmax(0, 1fr); }
}
@media (min-width: 1600px) {
  .usr-cols { grid-template-columns: minmax(0, 2fr) minmax(280px, 1fr); }
}
/* Mientras el formulario va DEBAJO de la tabla no toma su ancho: los
   campos estirados a 900px se leen como un formulario roto. */
@media (min-width: 768px) and (max-width: 1599px) {
  .usr-cols .usr-form { max-width: 560px; }
}

/* ---------- De 768 arriba el titular es una banda ---------- */
@media (min-width: 768px) {
  .usr-heroe {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--s2) var(--s4);
  }
  .usr-heroe-ambito { flex: 1 0 100%; }
  .usr-heroe-cifra,
  .usr-heroe-apoyo { margin-top: 0 !important; }
}

/* ---------- Tema oscuro ---------- */
/* La hoja del formulario se levanta como cualquier otra superficie, y
   la marca de "esta soy yo" usa --selected, que se ve sobre cualquier
   fondo; --brand-50 en oscuro es casi el mismo color que el panel. */


/* =====================================================================
   50. PERMISOS: QUE PUEDE HACER CADA ROL
   ---------------------------------------------------------------------
   Lo que se midio antes de tocar nada, a 390x844:

     - La pantalla abria con la MATRIZ: 21 acciones x 4 roles. La tabla
       pide 718px y la caja mide 357: para leer la columna "Empleado"
       habia que desplazar en horizontal en CADA UNO de los 21
       renglones. Una matriz que no se puede recorrer entera deja de ser
       una matriz y pasa a ser una lista con la mitad tapada.

     - 2.840px de alto en total. El unico apartado que de verdad se
       administra -el rol de cada usuario- empezaba a 1.900px de scroll.

     - El grupo y la descripcion de cada accion YA EXISTIAN en
       permisos_catalogo() y la pantalla los descartaba: 21 renglones
       planos, sin un solo separador, y sin decir que hace cada permiso.

   Lo que hay ahora:

     1. MANDA LA PERSONA. Primero quien es cada quien y que rol tiene
        -que es lo unico que esta pantalla guarda-, y la matriz pasa a
        ser la referencia que viene despues.

     2. EN TELEFONO SE ELIGE UN ROL. Cada rol es un <details> nativo con
        su cuenta ("16 de 21"), y dentro sus acciones por bloques
        -Plataforma, Ventas, Caja, Productos...- con el nombre, su
        descripcion y si puede o no. Sin desplazamiento lateral.

     3. LA MATRIZ SE QUEDA de 768 arriba, que es donde comparar cuatro
        roles tiene sitio, ahora separada por grupos y con la
        descripcion debajo de cada accion.

   El reparto es .perm-tactil / .perm-escritorio, el mismo patron de
   Existencias, Sucursales y Usuarios: un modulo con dos formas, las dos
   pintadas del mismo $matriz.

   EL ESTADO NO DEPENDE DEL COLOR. Cada celda dice "Permitido" o "No
   permitido" con la FORMA (palomita o guion), con el color, y ademas
   con TEXTO para lectores de pantalla. Quitando el color, la pantalla
   se sigue leyendo.
   ===================================================================== */

/* ---------- El titular ---------- */
.perm-heroe { margin: 0 0 var(--s4); }
.perm-heroe p { margin: 0; }

.perm-heroe-ambito {
  display: flex;
  align-items: center;
  gap: var(--s2);
  font-size: var(--fs-xs);
  font-weight: var(--peso-fuerte);
  letter-spacing: var(--ls-etiqueta);
  text-transform: uppercase;
  color: var(--text-3);
}
.perm-heroe-ambito .icono { flex: none; }

.perm-heroe-cifra {
  display: flex;
  align-items: baseline;
  gap: var(--s2);
  margin-top: var(--s2) !important;
  line-height: 1;
}
.perm-heroe-cifra strong {
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-3xl);
  font-weight: var(--peso-max);
  letter-spacing: -0.02em;
  color: var(--text);
}
.perm-heroe-cifra span {
  font-size: var(--fs-lg);
  color: var(--text-2);
}
.perm-heroe-apoyo {
  margin-top: var(--s2) !important;
  font-size: var(--fs-sm);
  color: var(--text-2);
}

/* ---------- Reparto por ancho ---------- */
/* La lista y los roles plegables solo existen en telefono; la tabla de
   usuarios y la matriz, de 768 arriba. Las hojas de cada persona viven
   DENTRO de .perm-tactil: de 768 arriba su disparador no se pinta, asi
   que no hay dialogos huerfanos ni dos formularios vivos a la vez. */
.perm-tactil { display: none; }

@media (max-width: 767px) {
  .perm-tactil     { display: block; }
  .perm-escritorio { display: none; }
}

/* LA MATRIZ TIENE SU PROPIO CORTE, Y ES MAS ALTO.
   ------------------------------------------------
   La tabla de usuarios son cuatro columnas de texto y cabe en una
   tableta. La matriz no: son CUATRO roles mas la columna de la accion
   -que ahora lleva tambien su descripcion- y pide 788px. MEDIDO con la
   barra lateral abierta:

     768   641px utiles     no cabe
     1024  765px utiles     no cabe
     1280  971px utiles     cabe con holgura

   Por debajo de 1280 se lee la MISMA informacion como lista por rol,
   que es comoda a cualquier ancho, y la matriz aparece solo donde
   comparar cuatro columnas de verdad se puede. Es lo que pide el
   encargo: en tableta, ni matriz apretada ni desplazamiento lateral. */
.perm-matriz { display: none; }

@media (min-width: 1280px) {
  .perm-matriz { display: block; }
  .perm-roles  { display: none; }
}

/* ---------- La introduccion ---------- */
/* Deja de ser un recuadro con borde y fondo propio: es una frase de
   apoyo debajo del titular. Una superficie menos en la pantalla. */
.perm-intro {
  display: flex;
  align-items: flex-start;
  gap: var(--s2);
  margin: 0 0 var(--s5);
  padding: 0;
  border: 0;
  border-radius: 0;
  background: none;
  font-size: var(--fs-sm);
  line-height: var(--lh);
  color: var(--text-2);
}

/* ---------- Las iniciales de una persona ---------- */
/* Lleva .prod-img (tamano, borde, tipografia y las seis variantes de
   color, oscuro incluido) y .perm-ini solo le cambia la forma. Escribir
   aqui el fondo pisaria a .prod-img-cN y el marcador saldria gris. */
.perm-ini { border-radius: var(--r-full); }
.perm-tactil .lista-marca {
  background: none;
  border-radius: 0;
  overflow: visible;
}

/* ---------- La hoja de una persona ---------- */
.perm-hoja-nota {
  display: block;
  margin-top: var(--s0);
  font-size: var(--fs-xs);
  font-weight: 400;
  color: var(--text-3);
}
.perm-hoja-correo { overflow-wrap: anywhere; }
.perm-hoja-form { margin: 0; }
.perm-hoja-form .campo { margin-bottom: var(--s3); }
.perm-hoja-form select { width: 100%; }

.perm-hoja-nota-pie {
  display: flex;
  align-items: center;
  gap: var(--s2);
  margin: 0;
  min-height: var(--tap);
  font-size: var(--fs-sm);
  color: var(--text-2);
}
.perm-hoja-nota-pie svg { flex: none; color: var(--text-3); }

.perm-vacio { padding: var(--s5) var(--s4); }

/* ---------- Un rol, plegado ---------- */
/* Es un <details> NATIVO: sin JavaScript se abre igual. No hay estado
   que guardar ni parametro en la URL. */
.perm-roles { margin-bottom: var(--s5); }

.perm-rol {
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  background: var(--surface);
  overflow: hidden;
}
.perm-rol + .perm-rol { margin-top: var(--s3); }

.perm-rol-cab {
  display: flex;
  align-items: center;
  gap: var(--s3);
  min-height: var(--tap);
  padding: var(--s3) var(--s4);
  cursor: pointer;
  list-style: none;
  -webkit-tap-highlight-color: transparent;
}
.perm-rol-cab::-webkit-details-marker { display: none; }
.perm-rol-cab::marker { content: ""; }
.perm-rol-cab:hover { background: var(--hover); }
.perm-rol-cab:active { background: var(--pressed); }
.perm-rol-cab:focus-visible { outline: var(--foco); outline-offset: -2px; }

.perm-rol-txt { flex: 1; min-width: 0; display: grid; gap: var(--s0); }
.perm-rol-txt strong {
  font-size: var(--fs-base);
  font-weight: var(--peso-fuerte);
  color: var(--text);
}
.perm-rol-txt small {
  font-size: var(--fs-xs);
  color: var(--text-2);
  line-height: 1.35;
}
/* La cuenta es el resumen del rol: cuantas de las acciones comparadas
   puede. Se lee como cifra, no como texto corrido. */
.perm-rol-cuenta {
  flex: none;
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-xs);
  font-weight: var(--peso-fuerte);
  color: var(--text-2);
  white-space: nowrap;
}
.perm-rol-flecha {
  flex: none;
  display: inline-flex;
  color: var(--text-3);
  transition: transform var(--t-media) var(--ease);
}
.perm-rol[open] .perm-rol-flecha { transform: rotate(180deg); }
.perm-rol[open] .perm-rol-cab { border-bottom: 1px solid var(--border-sutil); }

@media (prefers-reduced-motion: reduce) {
  .perm-rol-flecha { transition: none; }
}

/* ---------- Los bloques por tema ---------- */
.perm-grupo-rot {
  margin: var(--s4) 0 var(--s1);
  padding: 0 var(--s4);
  font-size: var(--fs-xs);
  font-weight: var(--peso-fuerte);
  letter-spacing: var(--ls-etiqueta);
  text-transform: uppercase;
  color: var(--text-3);
}

.perm-acciones {
  list-style: none;
  margin: 0;
  padding: 0;
}
.perm-acciones:last-child { padding-bottom: var(--s3); }

.perm-accion {
  display: flex;
  align-items: center;
  gap: var(--s3);
  padding: var(--s2) var(--s4);
  min-height: 48px;
}
.perm-accion + .perm-accion { border-top: 1px solid var(--border-sutil); }

.perm-accion-txt { flex: 1; min-width: 0; display: grid; gap: var(--s0); }
.perm-accion-txt strong {
  font-size: var(--fs-sm);
  font-weight: var(--peso-medio);
  line-height: var(--lh-tight);
  color: var(--text);
}
.perm-accion-txt small {
  font-size: var(--fs-xs);
  color: var(--text-3);
  line-height: 1.35;
}
/* Lo que NO se puede hacer se apaga; no se tacha ni se pinta de rojo.
   "No permitido" es un estado normal, no un error. */
.perm-accion-no .perm-accion-txt strong { color: var(--text-2); }

/* ---------- La marca de estado, en las dos formas ---------- */
/* El mismo componente en la lista de telefono y en la matriz de
   escritorio, para que las dos digan lo mismo con la misma forma. */
.perm-marca-si,
.perm-marca-no {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border-radius: var(--r-full);
}
.perm-marca-si { background: var(--ok-bg); color: var(--ok); }
.perm-marca-no { color: var(--text-3); font-weight: var(--peso-max); }

/* ---------- La matriz: separadores de grupo ---------- */
/* El renglon del grupo ocupa la tabla entera y deja de ser sticky: si
   heredara el position:sticky de .perm-col-accion se quedaria pegado a
   la izquierda y taparia las columnas de rol al desplazar. */
/* La especificidad va contra `.perm-tabla tbody .perm-col-accion`, que
   pone `text-transform: none` para los nombres de accion. Sin el
   `.perm-tabla tbody` delante, el rotulo del grupo salia en caja normal
   y se confundia con una accion mas. */
.perm-tabla tbody .perm-fila-grupo > .perm-col-accion {
  position: static;
  width: auto;
  min-width: 0;
  box-shadow: none;
  padding-top: var(--s3);
  padding-bottom: var(--s3);
  background: var(--surface-2);
  font-size: var(--fs-xs);
  font-weight: var(--peso-fuerte);
  letter-spacing: var(--ls-etiqueta);
  text-transform: uppercase;
  color: var(--text-3);
}

/* La accion, con su descripcion debajo. La descripcion es apoyo: no
   compite con el nombre ni engorda la fila mas de un renglon. */
.perm-accion-nom {
  display: block;
  font-size: var(--fs-sm);
  font-weight: var(--peso-medio);
  color: var(--text);
}
.perm-accion-desc {
  display: block;
  margin-top: var(--s0);
  font-size: var(--fs-xs);
  font-weight: 400;
  letter-spacing: 0;
  text-transform: none;
  color: var(--text-3);
}

/* La cuenta de cada rol, en la cabecera de su columna. El <th> viene en
   versalitas del sistema; la cifra no las quiere. */
.perm-col-rol .perm-rol-cuenta {
  display: block;
  margin-top: var(--s1);
  font-weight: var(--peso-max);
  letter-spacing: 0;
  text-transform: none;
  color: var(--text-3);
}

/* La fila de la cuenta propia, con la misma marca discreta que ya usan
   Sucursales y Usuarios. */
.perm-usuarios tr.es-yo td { background: var(--brand-50); }

/* El <select> de rol y su boton, en un renglon. MEDIDO a 768 y a 1280:
   la columna valia 260px y el conjunto pide 282 -select de 150, junta de
   8, boton de 100 y el relleno de la celda-, asi que "Aplicar" caia
   debajo y la fila subia a 103px. Desde 768, donde la columna Negocio ya
   cede el sitio, sobran los 40px que faltaban. */
@media (min-width: 768px) {
  .perm-usuarios th:last-child { width: 300px; }
}

.perm-nota { margin-top: var(--s5); }

/* ---------- Telefono: remates ---------- */
@media (max-width: 767px) {
  .perm-heroe-cifra strong { font-size: var(--fs-2xl); }
  .perm-intro { margin-bottom: var(--s4); }
  .perm-tactil .lista { margin: 0; }
  /* El chip de rol de la derecha no puede comerse el nombre en 360px. */
  .perm-tactil .lista-fin .chip { padding-inline: var(--s2); max-width: 104px; }
  .perm-usuarios { margin-bottom: var(--s5); }
}

/* ---------- De 768 arriba el titular es una banda ---------- */
@media (min-width: 768px) {
  .perm-heroe {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--s2) var(--s4);
  }
  .perm-heroe-ambito { flex: 1 0 100%; }
  .perm-heroe-cifra,
  .perm-heroe-apoyo { margin-top: 0 !important; }

  /* La matriz solo se pinta de 1280 arriba, donde hay 971px utiles:
     260 para la accion -con su descripcion- y 132 por cada uno de los
     cuatro roles son 788, asi que entra entera y sin desplazamiento. */
  .perm-tabla { min-width: 788px; }
  .perm-col-accion { min-width: 260px; width: 34%; }
}

/* ---------- Tema oscuro ---------- */
/* Solo tokens. El bloque de un rol se levanta como cualquier superficie
   y la fila propia usa --selected, que se ve sobre cualquier fondo;
   --brand-50 en oscuro es casi el mismo color que el panel. */
:root[data-tema="oscuro"] .perm-rol {
  box-shadow:
    var(--filo-luz),
    var(--sh-1);
}


/* =====================================================================
   51. REPORTES EN UN TELEFONO
   ---------------------------------------------------------------------
   FASE 11: el reparto .rep-tactil / .rep-escritorio de esta seccion YA
   NO EXISTE — ahora hay una sola composicion, en la SECCION 72. Lo que
   sigue vivo de aqui es la HOJA DE FILTROS (.rep-filtros-hoja y sus
   remates), que no cambio, y las piezas sueltas que la 72 reutiliza:
   .rep-dato-num, .rep-dato-sub, .rep-unid y .rep-sin-datos.

   Las reglas que apuntan a .rep-tactil / .rep-escritorio se quedan
   escritas -no cuelgan de nada y no pintan- porque estan entreveradas
   con las de la hoja; separarlas a mano era mas riesgo que provecho.

   MEDIDO antes de tocar nada, a 390x844:

     - La cabecera de filtros media 320px: siete pestanas en DOS
       renglones, un "Exportar CSV" a ancho completo -una accion de paso
       con el peso de la accion principal-, la linea del periodo, el
       selector de sucursal y el enlace del rango. La primera cifra del
       informe empezaba en el pixel 460 de 844.

     - DOCE metricas y OCHO paneles: veinte superficies. Los rotulos y
       los apuntes de las metricas compactas salian a 10px REALES, que
       en un telefono no se leen.

     - 2.692px de alto.

   Lo que hay ahora:

     1. LOS FILTROS SUBEN COMO HOJA desde el boton "Periodo" de la
        cabecera. Es el MISMO panel, con las mismas pestanas, el mismo
        selector y el mismo formulario de rango: en escritorio sigue
        siendo la cabecera del informe.

     2. UNA CIFRA MANDA. Ingresos en grande, con el periodo encima y la
        comparacion debajo. Sin caja.

     3. EL RESTO SE LEE COMO LISTA. Las siete cifras de apoyo, los dos
        rankings, los cuatro desgloses y el inventario de hoy son
        bloques con una sola superficie cada uno, no veinte tarjetas.

     4. EN ESCRITORIO NO CAMBIA NADA: la rejilla de metricas, la
        grafica, los rankings en dos columnas y los desgloses en tres o
        cuatro siguen igual. Ahi la densidad es la funcion.

   El reparto es .rep-tactil / .rep-escritorio, el mismo patron de
   Existencias, Sucursales, Usuarios y Permisos: un modulo con dos
   formas, las dos pintadas de los mismos datos.
   ===================================================================== */

/* ---------- Reparto por ancho ---------- */
.rep-tactil { display: none; }

@media (max-width: 767px) {
  .rep-tactil     { display: block; }
  .rep-escritorio { display: none; }
}

/* ---------- La cifra que manda ---------- */
.rep-heroe { margin: 0 0 var(--s4); }
.rep-heroe p { margin: 0; }

.rep-heroe-ambito {
  display: flex;
  align-items: center;
  gap: var(--s2);
  font-size: var(--fs-xs);
  font-weight: var(--peso-fuerte);
  letter-spacing: var(--ls-etiqueta);
  text-transform: uppercase;
  color: var(--text-3);
}
.rep-heroe-ambito .icono { flex: none; }
.rep-heroe-ambito span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.rep-heroe-cifra {
  margin-top: var(--s2) !important;
  line-height: 1;
}
.rep-heroe-cifra strong {
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-3xl);
  font-weight: var(--peso-max);
  letter-spacing: -0.02em;
  color: var(--text);
}

.rep-heroe-apoyo {
  margin-top: var(--s2) !important;
  font-size: var(--fs-sm);
  color: var(--text-2);
}
/* La comparacion con el periodo anterior es el unico color del titular,
   y solo aparece cuando hay con que comparar. */
.rep-heroe-delta { font-weight: var(--peso-fuerte); }
.rep-heroe-delta.es-sube { color: var(--ok); }
.rep-heroe-delta.es-baja { color: var(--error); }

.rep-heroe-suc {
  display: flex;
  align-items: center;
  gap: var(--s1);
  margin-top: var(--s2) !important;
  font-size: var(--fs-xs);
  color: var(--text-3);
}
.rep-heroe-suc .icono { flex: none; }

/* ---------- Las cifras, como lista ---------- */
/* Siete metricas de 10px pasan a siete renglones de "etiqueta a la
   izquierda, cifra a la derecha" con su apunte debajo: el mismo
   componente ui_datos_* que ya usan el corte de caja, el detalle de una
   venta y las hojas de Sucursales y Usuarios. */
.rep-datos { padding: 0 var(--s4); }
.rep-datos .datos { margin: 0; }
.rep-dato-num {
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-base);
}
.rep-dato-sub {
  display: block;
  margin-top: var(--s0);
  font-size: var(--fs-xs);
  font-weight: 400;
  color: var(--text-3);
}

/* ---------- La grafica ---------- */
/* A sangre dentro de su bloque: el SVG se escala al ancho que le den, y
   cada pixel que gana es letra de eje que se lee mejor. */
.rep-gr { padding: var(--s3) var(--s2) var(--s4); }

/* ---------- Rankings ---------- */
/* La cifra de unidades es el dato por el que se mira un ranking, asi que
   se lee como cifra y no como texto corrido. */
.rep-unid {
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-lg);
  font-weight: var(--peso-max);
  color: var(--text);
}
.rep-unid.es-cero { color: var(--error); }
.rep-unid-u { font-size: var(--fs-xs); color: var(--text-3); }

/* ---------- Desgloses ---------- */
/* Cuatro paneles con borde pasan a cuatro apartados de UNA superficie,
   separados por su rotulo y una junta. */
.rep-desgloses { padding: 0 var(--s4) var(--s3); }
.rep-desglose-rot {
  margin: var(--s4) 0 var(--s2);
  font-size: var(--fs-xs);
  font-weight: var(--peso-fuerte);
  letter-spacing: var(--ls-etiqueta);
  text-transform: uppercase;
  color: var(--text-3);
}
.rep-desgloses > .rep-desglose-rot:first-child { margin-top: var(--s3); }
.rep-desgloses .caja-desglose + .rep-desglose-rot,
.rep-desgloses .rep-sin-datos + .rep-desglose-rot {
  padding-top: var(--s4);
  border-top: 1px solid var(--border-sutil);
}

.rep-pie-nota {
  margin: var(--s3) 0 0;
  padding: 0 var(--s1);
  font-size: var(--fs-xs);
  color: var(--text-3);
  line-height: 1.5;
}

/* =====================================================================
   51b. TELEFONO: LOS FILTROS SUBEN COMO HOJA
   ---------------------------------------------------------------------
   El mismo mecanismo que en Sucursales y Usuarios: un panel que YA ESTA
   en la pagina y que en telefono se presenta como hoja (el segundo caso
   de hoja de app.js). No hay una segunda copia de los filtros, ni un
   segundo formulario, ni un segundo enlace de CSV.

   .con-js: sin JavaScript no hay hoja que abrir, asi que el panel se
   queda donde estaba y la pantalla funciona igual que siempre.

   .rep-filtros-hoja lo escribe el PHP solo cuando NO hay un rango a
   medida en uso: con uno puesto el panel nace fijo, porque esconder el
   control que esta actuando confunde.
   ===================================================================== */

/* El asa y la cabecera de dialogo solo existen cuando el panel ES hoja. */
.rep-filtros-panel > .hoja-asa,
.rep-hoja-cab { display: none; }

/* El disparador no se pinta sin JavaScript: sin el no abre nada. */
.rep-periodo-btn { display: none; }
.con-js .rep-periodo-btn { display: inline-flex; }

/* El CSV, con peso de enlace. */
.rep-cab-csv { margin: var(--s4) 0 0; }
.rep-cab-csv .rep-csv-enlace {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  min-height: var(--tap);
  font-size: var(--fs-sm);
  font-weight: var(--peso-medio);
  color: var(--brand-600);
  text-decoration: none;
}
.rep-cab-csv .rep-csv-enlace:hover { text-decoration: underline; }
.rep-cab-csv svg { flex: none; }

@media (max-width: 767px) {
  .con-js .rep-filtros-hoja { display: none; }

  .con-js .rep-filtros-hoja.como-hoja {
    display: flex;
    flex-direction: column;
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    top: auto;
    z-index: var(--z-hoja);
    max-height: var(--hoja-max);
    margin: 0;
    padding: 0 var(--s4) calc(var(--s4) + env(safe-area-inset-bottom));
    background: var(--sheet);
    border: 0;
    border-radius: var(--r-hoja) var(--r-hoja) 0 0;
    box-shadow: var(--sh-hoja);
    overflow-y: auto;
    overscroll-behavior: contain;
    animation: hoja-sube var(--t-lenta) var(--ease-suave);
    transform: translateY(var(--hoja-arrastre, 0px));
  }
  .con-js .rep-filtros-hoja.como-hoja:not(.arrastrando) {
    transition: transform var(--t-media) var(--ease);
  }
  .con-js .rep-filtros-hoja.como-hoja.arrastrando { animation: none; transition: none; }

  .rep-filtros-hoja.como-hoja > .hoja-asa {
    display: flex;
    margin-inline: calc(var(--s4) * -1);
  }
  .rep-filtros-hoja.como-hoja .rep-hoja-cab {
    display: flex;
    align-items: center;
    gap: var(--s3);
    padding-bottom: var(--s3);
    margin-bottom: var(--s3);
    border-bottom: 1px solid var(--border-sutil);
  }
  .rep-hoja-cab h3 {
    flex: 1;
    margin: 0;
    font-size: var(--fs-lg);
    font-weight: var(--peso-fuerte);
    letter-spacing: var(--ls-titulo);
  }
  .rep-filtros-hoja.como-hoja > .panel-cab { padding: 0; }

  /* Las pestanas del periodo, dentro de la hoja, son la eleccion
     principal: se reparten en dos columnas y cada una llega a 44px. */
  .rep-filtros-panel .tabs {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--s2);
    background: none;
    padding: 0;
    border: 0;
  }
  .rep-filtros-panel .tabs .tab {
    justify-content: center;
    min-height: var(--tap);
    border: 1px solid var(--border);
    border-radius: var(--r-md);
  }

  .rep-cab-pie { flex-direction: column; align-items: stretch; gap: var(--s3); }
  .rep-sucursal select { width: 100%; min-height: var(--tap); }
  .rep-rango-abrir { justify-content: flex-start; min-height: var(--tap); }

  /* El titular respira un poco menos: en 844px de alto cada 8px cuenta. */
  .rep-heroe-cifra strong { font-size: var(--fs-2xl); }
}

/* ---------- Tema oscuro ---------- */
/* =====================================================================
   52. ALERTAS: LA HOJA DE UNA ALERTA
   ---------------------------------------------------------------------
   De esta seccion solo queda la hoja. Toda la composicion de la
   pantalla -el reparto .alr-tactil / .alr-escritorio, el titular, las
   bandejas, el selector suelto de sucursal- se rehizo en la SECCION 67
   (Fase 7), que trae UNA sola composicion para todos los anchos. Las
   reglas de aquello se borraron en vez de dejarlas muertas: apuntaban a
   clases que ningun PHP escribe ya.

   La hoja NO cambio: sigue siendo donde viven las dos acciones de una
   alerta -reabastecer y descartar-, con el mismo POST y el mismo CSRF.
   ===================================================================== */

/* ---------- La hoja de una alerta ---------- */
.alr-hoja-nota {
  display: block;
  margin-top: var(--s0);
  font-size: var(--fs-xs);
  font-weight: 400;
  color: var(--text-3);
}
.alr-hoja-admin {
  margin-top: var(--s4);
  padding-top: var(--s4);
  border-top: 1px solid var(--border-sutil);
  display: grid;
  gap: var(--s2);
}
.alr-hoja-admin-rot {
  margin: 0;
  font-size: var(--fs-xs);
  font-weight: var(--peso-fuerte);
  letter-spacing: var(--ls-etiqueta);
  text-transform: uppercase;
  color: var(--text-3);
}
.alr-hoja-form { margin: 0; }
.alr-hoja-form .btn { width: 100%; }
.alr-hoja-nota-pie {
  margin: 0;
  font-size: var(--fs-xs);
  line-height: 1.5;
  color: var(--text-3);
}

/* =====================================================================
   53. MOVIMIENTOS: VACIADA EN LA FASE 8
   ---------------------------------------------------------------------
   Aqui vivian el reparto de las metricas en telefono y la ficha de un
   movimiento por areas de rejilla. Las dos cosas se rehicieron en la
   SECCION 68, que trae la composicion de la referencia -pestanas de
   tipo, rango de fechas a la vista, cuatro cifras y una fila que cuenta
   un movimiento- para todos los anchos.

   Las reglas de aquello se borraron en vez de dejarlas muertas:
   apuntaban a un componente de metricas que esta pantalla ya no usa y a
   un reparto de celdas que ya no existe.
   ===================================================================== */



/* =====================================================================
   54. MI NEGOCIO EN UN TELEFONO
   ---------------------------------------------------------------------
   MEDIDO A 390x844 ANTES DE TOCAR NADA:

     - La pantalla ABRIA EN MODO EDICION. Lo primero que habia bajo la
       identidad era un <input> con el nombre del negocio dentro. La
       pregunta con la que se entra aqui -"como esta configurado mi
       negocio"- no se podia contestar leyendo: habia que interpretar
       siete campos de formulario.
     - CINCO TARJETAS APILADAS -Identidad, Contacto, Ticket, la vista
       previa y Tu cuenta- para siete datos. 3.400px de recorrido, casi
       cuatro pantallas y media.
     - "Guardar cambios" anclado abajo DESDE EL PRIMER PIXEL, sin que
       hubiera un solo cambio que guardar, tapando 96px de contenido en
       todas y cada una de esas pantallas.

   AHORA:

     .neg-resumen     lo que se LEE. Etiqueta, valor y separador -.datos
                      del sistema-, y una sola accion debajo.
     .neg-form-panel  lo que se EDITA. El MISMO formulario: hoja en
                      telefono, columna principal de 768 arriba.

   Es el tercer caso del mismo mecanismo, detras de Sucursales (48b) y
   Usuarios (49c): un panel que YA ESTA en la pagina y que en telefono
   se presenta como hoja. No hay un segundo formulario, ni un segundo
   POST, ni un segundo CSRF, ni una linea nueva de JavaScript.

   La composicion de escritorio no se toca: dos columnas desde 1280,
   formulario a la izquierda y carril de solo lectura a la derecha, tal
   y como quedo en la etapa anterior.
   ===================================================================== */

/* La ficha de lectura es SOLO de telefono. De 768 arriba el formulario
   entra entero en la pantalla y ensena esos mismos siete valores dentro
   de sus campos: repetirlos arriba seria decir dos veces lo mismo. */
.neg-resumen { display: none; }

/* El asa y la cruz solo existen cuando el panel ES una hoja. Se apagan
   contra .neg-form-panel -que el panel lleva siempre- y no contra
   .neg-form-hoja, que solo esta cuando el formulario se esconde: con
   errores de validacion el panel nace fijo y el asa se quedaria a la
   vista prometiendo un arrastre que ahi no existe. */
.neg-form-panel > .hoja-asa,
.neg-form-cerrar { display: none; }

/* El disparador no se pinta sin JavaScript: sin el no abre nada, y sin
   el tampoco hace falta, porque el formulario se queda a la vista. */
.neg-editar-btn { display: none; }
.con-js .neg-editar-btn { display: inline-flex; }

@media (max-width: 767px) {

  .neg-resumen { display: block; margin-bottom: var(--s5); }

  /* Entre la ficha y su unica accion, aire; no un borde mas. */
  .neg-resumen .bloque { margin-bottom: var(--s4); }

  /* .dato no lleva relleno lateral -en una hoja lo pone el cuerpo del
     dialogo-, asi que dentro de una superficie hay que ponerlo aqui o
     el valor se pega al borde. Y un correo o una direccion larga parte
     por donde haga falta antes que desbordar la caja. */
  .neg-resumen .dato { padding-inline: var(--s4); }
  .neg-resumen .dato dd { overflow-wrap: anywhere; }

  /* El nombre comercial es el dato que da nombre a todo lo demas: se
     lee primero y con mas peso. Lo hace .dato-fuerte, que ya existe. */
  .neg-resumen .dato-fuerte dd { line-height: var(--lh-tight); }

  .con-js .neg-form-hoja { display: none; }

  .con-js .neg-form-hoja.como-hoja {
    display: flex;
    flex-direction: column;
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    top: auto;
    z-index: var(--z-hoja);
    max-height: var(--hoja-max);
    margin: 0;
    padding: 0 var(--s4) calc(var(--s4) + env(safe-area-inset-bottom));
    background: var(--sheet);
    border: 0;
    border-radius: var(--r-hoja) var(--r-hoja) 0 0;
    box-shadow: var(--sh-hoja);
    overflow-y: auto;
    overscroll-behavior: contain;
    animation: hoja-sube var(--t-lenta) var(--ease-suave);
    transform: translateY(var(--hoja-arrastre, 0px));
  }
  .con-js .neg-form-hoja.como-hoja:not(.arrastrando) {
    transition: transform var(--t-media) var(--ease);
  }
  .con-js .neg-form-hoja.como-hoja.arrastrando { animation: none; transition: none; }

  .neg-form-hoja.como-hoja > .hoja-asa {
    display: flex;
    margin-inline: calc(var(--s4) * -1);
  }
  .neg-form-hoja.como-hoja .neg-form-cerrar { display: inline-flex; }

  /* Dentro de la hoja, la cabecera del panel deja de ser cabecera de
     panel: es el encabezado del dialogo. Sin nowrap el bloque de texto
     se lleva el renglon entero y la cruz cae debajo del parrafo. */
  .neg-form-hoja.como-hoja > .panel-cab {
    display: flex;
    align-items: flex-start;
    gap: var(--s3);
    flex-wrap: nowrap;
    padding: 0 0 var(--s3);
    margin-bottom: var(--s4);
    border-bottom: 1px solid var(--border-sutil);
    background: none;
  }
  .neg-form-hoja.como-hoja > .panel-cab > div { flex: 1; min-width: 0; }
  .neg-form-hoja.como-hoja > .panel-cab h3 { font-size: var(--fs-lg); }
  .neg-form-hoja.como-hoja > .panel-cab .panel-desc { font-size: var(--fs-sm); }
  .neg-form-hoja.como-hoja > .panel-cuerpo { padding: 0; }
  .neg-form-hoja.como-hoja .btn[type="submit"] {
    width: 100%;
    min-height: 52px;
    font-size: var(--fs-lg);
  }
  /* La barra de acciones anclada de la seccion 29 no se ancla DENTRO de
     una hoja que ya se desplaza sola: ahi flotaria sobre su propio
     contenido. */
  .neg-form-hoja.como-hoja .form-acciones {
    position: static;
    margin: var(--s4) 0 0;
    padding: 0;
    border: 0;
    box-shadow: none;
    background: none;
  }
  /* Dentro de la hoja, Descartar es el "no, dejalo como estaba" del
     dialogo: debajo de Guardar y a todo lo ancho, no a su derecha.

     La barra se apila. .form-acciones es flex en fila, y con tres
     hijos -Guardar, Descartar y la nota- el width:100% del boton
     principal se repartia: MEDIDO, Guardar salia de 175px de los 358
     disponibles. En una hoja de telefono la accion principal ocupa el
     ancho, que es lo que dice la seccion 45. */
  .neg-form-hoja.como-hoja .form-acciones {
    flex-direction: column;
    align-items: stretch;
    gap: var(--s2);
  }
  .neg-form-hoja.como-hoja .form-acciones .btn { width: 100%; }
  .neg-form-hoja.como-hoja .negocio-acciones-nota {
    min-width: 0;
    text-align: center;
    margin-top: var(--s1);
  }

  /* El carril de solo lectura -ticket y cuenta- no compite con la
     ficha: va detras y con menos aire entre sus dos piezas. */
  .negocio-carril { gap: var(--s4); }
}

/* Sin JavaScript no hay hoja, asi que en telefono el formulario se
   queda donde estaba y la ficha de lectura sobra: seria repetir arriba
   lo que el formulario ya ensena justo debajo. */
@media (max-width: 767px) {
  html:not(.con-js) .neg-resumen { display: none; }
}

/* ---------- Tema oscuro ---------- */
/* La hoja se levanta como cualquier otra superficie del sistema. */


/* =====================================================================
   55. CONFIGURACION EN UN TELEFONO
   ---------------------------------------------------------------------
   MEDIDO A 390x844 ANTES DE TOCAR NADA:

     - 2.681px de alto: 3,2 pantallas para SEIS ajustes.
     - Entre el formulario y el mantenimiento se cruzaban DIECISEIS
       renglones de solo lectura -siete de la ficha del sistema y nueve
       de conteos de tablas- que no se cambian, no se guardan y no se
       consultan a diario. Las dos acciones destructivas de la pantalla
       quedaban detras de ellos, en la cuarta pantalla.
     - Las dos acciones de mantenimiento se apilaban a partir de 640px,
       no de 767: entre esos dos anchos el boton medía 40px de alto, por
       debajo del minimo tactil.

   AHORA, en telefono:

     ajustes        el formulario de siempre, con su Guardar anclado.
     .cfg-tactil    dos filas con lo que se mira de un vistazo -version
                    y entorno, y el total de filas-; a un toque suben
                    como hoja con el detalle completo.
     mantenimiento  su zona propia, ya en la segunda pantalla.

   Es el mismo mecanismo de Sucursales (48b), Usuarios (49c) y Mi
   negocio (54): un panel que YA ESTA en la pagina. No hay un segundo
   marcado de esos datos, ni una consulta nueva, ni una linea nueva de
   JavaScript.

   De 768 arriba no cambia nada: los dos paneles son la columna lateral
   de siempre y la lista tactil no se pinta.
   ===================================================================== */

/* La lista de acceso es SOLO de telefono: de 768 arriba los dos paneles
   estan a la vista y una fila que los abre seria un rodeo. */
.cfg-tactil { display: none; }

/* El asa y la cruz solo existen cuando el panel ES una hoja. */
.cfg-hoja-panel > .hoja-asa,
.cfg-hoja-cerrar { display: none; }

@media (max-width: 767px) {

  .cfg-tactil { display: block; margin-top: var(--s5); }

  /* La columna lateral se queda sin nada que pintar -sus dos paneles
     son hojas- y no debe seguir aportando el hueco de la rejilla. */
  .con-js .admconfig-lateral { display: contents; }
  .con-js .cfg-hoja { display: none; }

  .con-js .cfg-hoja.como-hoja {
    display: flex;
    flex-direction: column;
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    top: auto;
    z-index: var(--z-hoja);
    max-height: var(--hoja-max);
    margin: 0;
    padding: 0 var(--s4) calc(var(--s4) + env(safe-area-inset-bottom));
    background: var(--sheet);
    border: 0;
    border-radius: var(--r-hoja) var(--r-hoja) 0 0;
    box-shadow: var(--sh-hoja);
    overflow-y: auto;
    overscroll-behavior: contain;
    animation: hoja-sube var(--t-lenta) var(--ease-suave);
    transform: translateY(var(--hoja-arrastre, 0px));
  }
  .con-js .cfg-hoja.como-hoja:not(.arrastrando) {
    transition: transform var(--t-media) var(--ease);
  }
  .con-js .cfg-hoja.como-hoja.arrastrando { animation: none; transition: none; }

  .cfg-hoja.como-hoja > .hoja-asa {
    display: flex;
    margin-inline: calc(var(--s4) * -1);
  }
  .cfg-hoja.como-hoja .cfg-hoja-cerrar { display: inline-flex; }

  /* Dentro de la hoja, la cabecera del panel es el encabezado del
     dialogo. Sin nowrap la cruz cae debajo del parrafo. */
  .cfg-hoja.como-hoja > .panel-cab {
    display: flex;
    align-items: flex-start;
    gap: var(--s3);
    flex-wrap: nowrap;
    padding: 0 0 var(--s3);
    margin-bottom: var(--s4);
    border-bottom: 1px solid var(--border-sutil);
    background: none;
  }
  .cfg-hoja.como-hoja > .panel-cab > div { flex: 1; min-width: 0; }
  .cfg-hoja.como-hoja > .panel-cab h3 { font-size: var(--fs-lg); }
  .cfg-hoja.como-hoja > .panel-cab .panel-desc { font-size: var(--fs-sm); }
  .cfg-hoja.como-hoja > .panel-cuerpo { padding: 0; }

  /* En la hoja hay ancho de sobra para la etiqueta: la ficha vuelve a
     sus dos columnas aunque el telefono sea estrecho. */
  .cfg-hoja.como-hoja .admconfig-ficha { grid-template-columns: minmax(0, 140px) minmax(0, 1fr); }

  /* El formulario y la lista no se pegan al mantenimiento. */
  .admconfig-cols { margin-bottom: var(--s5); }
}

/* Tablet y portatil: una sola columna -las dos de verdad empiezan en
   1280-, pero las dos piezas de solo lectura se reparten a lo ancho en
   vez de estirarse una debajo de otra. Es la misma composicion
   intermedia que ya usa el carril de Mi negocio. */
@media (min-width: 768px) and (max-width: 1279px) {
  .admconfig-lateral {
    grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
    align-items: start;
  }
}

/* Sin JavaScript no hay hoja que abrir: la lista no se pinta y los dos
   paneles se quedan donde estaban, uno detras de otro. Es la misma
   garantia del resto del sistema. */
@media (max-width: 767px) {
  html:not(.con-js) .cfg-tactil { display: none; }
}

/* ---------- Tema oscuro ---------- */


/* =====================================================================
   56. AVISOS EN UN TELEFONO
   ---------------------------------------------------------------------
   MEDIDO A 390x844 ANTES DE TOCAR NADA (1 aviso publicado):

     - LA PANTALLA ABRIA CON UN FORMULARIO DE ALTA. Bajo el titular, lo
       primero eran cinco campos para ESCRIBIR un aviso; los avisos
       -que es a lo que se entra- quedaban los ultimos, ya en la segunda
       pantalla, detras del formulario y del panel de alcance.
     - CADA AVISO SE APILABA COMO FICHA DE TABLA (seccion 14): los
       rotulos de las seis columnas en versalitas mezclados con sus
       valores -PUBLICO Todos ESTADO Retirado LO VIERON 3 PUBLICADO...-
       y, como la celda de acciones esta vacia en un aviso retirado,
       cada ficha terminaba con una raya suelta.
     - El mensaje, que es lo que hay que LEER, salia acotado a tres
       renglones dentro de la celda, con el texto completo escondido en
       un title que en un telefono no se puede ver.

   AHORA hay UN modulo con DOS FORMAS, las dos pintadas de los mismos
   datos -no hay marcado duplicado que se desincronice-:

     .avi-tactil      telefono. Los avisos primero, en una lista: tono,
                      titulo, publico y fecha, y el estado a la derecha.
                      Toda la fila es el toque y abre la hoja de ese
                      aviso, donde el mensaje se lee entero y donde vive
                      "Retirar". Debajo, la unica accion: Nuevo aviso.
     .avi-escritorio  de 768 arriba. La tabla de siempre, sin un cambio.

   Y el formulario de alta es el mismo de siempre: en telefono sube como
   hoja, de 768 arriba es la columna de la izquierda. Cuarto caso del
   mecanismo de Sucursales (48b), Usuarios (49c), Mi negocio (54) y
   Configuracion (55).
   ===================================================================== */

.avi-tactil { display: none; }

/* El asa y la cruz solo existen cuando el panel ES una hoja. */
.avi-form-panel > .hoja-asa,
.avi-form-cerrar { display: none; }

/* El disparador no se pinta sin JavaScript: sin el no abre nada, y sin
   el tampoco hace falta, porque el formulario se queda a la vista. */
.avi-nuevo-btn { display: none; }
.con-js .avi-nuevo-btn { display: inline-flex; }

/* El punto de tono. Acompana a la PALABRA del tono, no la sustituye:
   es el mismo recurso que el punto de caja de la lista de sucursales. */
.avi-punto {
  display: inline-block;
  width: 7px;
  height: 7px;
  margin-right: 6px;
  border-radius: var(--r-full);
  background: var(--text-3);
  vertical-align: middle;
}
.avi-punto-info   { background: var(--info); }
.avi-punto-ok     { background: var(--ok); }
.avi-punto-alerta { background: var(--warn); }
.avi-punto-error  { background: var(--error); }

/* ---------- La hoja de un aviso ---------- */
/* El mensaje llega a 500 caracteres y aqui se lee entero, con medida de
   parrafo y no dentro de una celda. */
.avi-hoja-mensaje {
  margin: 0 0 var(--s4);
  font-size: var(--fs-base);
  line-height: var(--lh);
  color: var(--text);
  white-space: pre-line;          /* respeta los saltos que se escribieron */
  overflow-wrap: anywhere;
}
.avi-hoja-autor { color: var(--text-3); font-weight: 400; }
.avi-hoja-nota-pie {
  margin: 0;
  text-align: center;
  font-size: var(--fs-sm);
  color: var(--text-3);
}

@media (max-width: 767px) {

  /* Los avisos van PRIMERO. El formulario de alta y el panel de alcance
     son lo que se hace despues de mirar, no antes. */
  .admavisos-cuerpo { display: flex; flex-direction: column; }
  .lista-avisos    { order: 1; }
  .admavisos-cols  { order: 2; margin-top: var(--s5); }

  .avi-tactil     { display: block; }
  .avi-escritorio { display: none; }

  .avi-tactil .lista { margin: 0; }
  .avi-tactil .bloque-sup { margin-bottom: var(--s4); }
  .avi-vacio { padding: var(--s2) var(--s4) var(--s5); }

  .con-js .avi-form-hoja { display: none; }

  .con-js .avi-form-hoja.como-hoja {
    display: flex;
    flex-direction: column;
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    top: auto;
    z-index: var(--z-hoja);
    max-height: var(--hoja-max);
    margin: 0;
    padding: 0 var(--s4) calc(var(--s4) + env(safe-area-inset-bottom));
    background: var(--sheet);
    border: 0;
    border-radius: var(--r-hoja) var(--r-hoja) 0 0;
    box-shadow: var(--sh-hoja);
    overflow-y: auto;
    overscroll-behavior: contain;
    animation: hoja-sube var(--t-lenta) var(--ease-suave);
    transform: translateY(var(--hoja-arrastre, 0px));
  }
  .con-js .avi-form-hoja.como-hoja:not(.arrastrando) {
    transition: transform var(--t-media) var(--ease);
  }
  .con-js .avi-form-hoja.como-hoja.arrastrando { animation: none; transition: none; }

  .avi-form-hoja.como-hoja > .hoja-asa {
    display: flex;
    margin-inline: calc(var(--s4) * -1);
  }
  .avi-form-hoja.como-hoja .avi-form-cerrar { display: inline-flex; }

  .avi-form-hoja.como-hoja > .panel-cab {
    display: flex;
    align-items: flex-start;
    gap: var(--s3);
    flex-wrap: nowrap;
    padding: 0 0 var(--s3);
    margin-bottom: var(--s4);
    border-bottom: 1px solid var(--border-sutil);
    background: none;
  }
  .avi-form-hoja.como-hoja > .panel-cab > div { flex: 1; min-width: 0; }
  .avi-form-hoja.como-hoja > .panel-cab h3 { font-size: var(--fs-lg); }
  .avi-form-hoja.como-hoja > .panel-cab .panel-desc { font-size: var(--fs-sm); }
  .avi-form-hoja.como-hoja > .panel-cuerpo { padding: 0; }
  .avi-form-hoja.como-hoja .btn[type="submit"] {
    width: 100%;
    min-height: 52px;
    font-size: var(--fs-lg);
  }
  /* La barra anclada de la seccion 29 no se ancla DENTRO de una hoja
     que ya se desplaza sola. */
  .avi-form-hoja.como-hoja .form-acciones {
    position: static;
    margin: var(--s4) 0 0;
    padding: 0;
    border: 0;
    box-shadow: none;
    background: none;
  }

  /* El titular respira un poco menos: en 844px de alto cada 8px cuenta. */
  .admavisos-estado { margin-bottom: var(--s4); }
}

/* Sin JavaScript no hay hoja que abrir: el formulario se queda donde
   estaba, a la vista, y la pantalla funciona igual que siempre. */

@media (min-width: 768px) {
  .avi-escritorio { display: block; }
}

/* ---------- Tema oscuro ---------- */


/* =====================================================================
   57. SISTEMA VISUAL MAESTRO
   =====================================================================
   Esta seccion NO rediseña ninguna pantalla. Sube las piezas que TODAS
   las pantallas comparten al nivel que ya tenian las mejores, para que
   despues cada pantalla se rehaga contra una base y no contra su propia
   improvisacion.

   Las piezas que se suben, y por que cada una:

     57a  EL AZULEJO DE ICONO. El cuadro tenido que identifica algo.
          Existia dibujado a mano en seis sitios con seis radios.
     57b  LA CABECERA DE PANTALLA. Deja de ser un h1 suelto y pasa a ser
          identidad + titulo + para que sirve + accion, que es como
          abre una pantalla de aplicacion.
     57c  LA BARRA SUPERIOR DEL TELEFONO. Marca, sucursal y cuenta en el
          reparto de una aplicacion, no de una web.
     57d  EL BUSCADOR. Un control principal, no un campo de formulario.
     57e  LA BARRA DE FILTROS DEL TELEFONO. Dos controles, una fila.
     57f  LAS PESTAÑAS QUE NO CABEN se deslizan de lado.
     57g  LA BARRA INFERIOR mide desde tokens y respeta la safe-area.
     57h  LAS HOJAS se centran en tablet y son modal en escritorio.
     57i  OBJETIVOS TACTILES: el ultimo barrido.
     57j  TABLET: composicion propia, ni telefono grande ni escritorio
          pequeño.

   Va al final del archivo A PROPOSITO. Todo lo que hay aqui tiene que
   ganar a la regla equivalente de su seccion original sin subir la
   especificidad a base de selectores encadenados: gana por orden, que
   es la unica forma de que dentro de seis meses se entienda quien
   manda.
   ===================================================================== */


/* ---------------------------------------------------------------------
   57a. EL AZULEJO DE ICONO
   ---------------------------------------------------------------------
   Un icono suelto sobre el fondo no identifica nada: se lee como
   decoracion. Dentro de un cuadro tenido pasa a ser la MARCA de eso
   que acompaña, y es lo que permite reconocer una pantalla, una
   metrica o una categoria antes de leer una sola palabra.

   Tres medidas, un radio, seis tonos. Los tonos NO son una paleta
   nueva: son los cinco estados que el sistema ya tiene mas un neutro.
   El reparto icono -> tono lo decide ui_tono_de_icono() en PHP, en un
   mapa unico, para que el mismo icono no salga verde en una pantalla y
   azul en otra.

   El azulejo es aria-hidden: lo que dice ya esta escrito al lado.
   --------------------------------------------------------------------- */

.azulejo {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width:  var(--azulejo);
  height: var(--azulejo);
  border-radius: var(--azulejo-r);
  color: var(--text-2);

  /* EL TINTE SALE DEL PROPIO TONO, no de una tabla de fondos.

     Con --ok-bg / --warn-bg / --error-bg el azulejo heredaba fondos
     pensados para un CHIP -una pastilla pequeña con texto encima- y a
     52px se leian lavados: el de acento (#f0fdfa) es practicamente
     blanco sobre un panel blanco y el cuadro desaparecia.

     Mezclando el color del tono con la superficie, los seis azulejos
     salen con la MISMA saturacion sin escribir seis fondos a mano, y
     en oscuro la mezcla se hace contra el panel oscuro, asi que el
     tono se oscurece solo. Un valor, seis tonos, dos temas.

     13% es donde el cuadro se ve a un metro sin competir con el icono
     que lleva dentro (medido contra el par de la referencia). */
  background: color-mix(in srgb, currentColor 13%, var(--surface));
}

/* Donde no haya color-mix, los fondos de estado de siempre. Se ve peor
   -es justo lo que se esta corrigiendo- pero se ve. */
@supports not (background: color-mix(in srgb, red 13%, white)) {
  .azulejo         { background: var(--interactive); }
  .azulejo-marca   { background: var(--brand-50); }
  .azulejo-ok      { background: var(--ok-bg); }
  .azulejo-warn    { background: var(--warn-bg); }
  .azulejo-error   { background: var(--error-bg); }
  .azulejo-acento  { background: var(--accent-50); }
}

.azulejo-sm { width: var(--azulejo-sm); height: var(--azulejo-sm); border-radius: var(--r-md); }
.azulejo-lg { width: var(--azulejo-lg); height: var(--azulejo-lg); }

/* El icono no se deforma nunca, pase lo que pase con la caja. */
.azulejo .icono { flex: none; }

/* Cada tono solo declara SU COLOR. El fondo lo deriva la regla de
   arriba. Es lo que garantiza que los seis pesen igual. */
.azulejo-marca   { color: var(--brand-600); }
.azulejo-ok      { color: var(--ok); }
.azulejo-warn    { color: var(--warn); }
.azulejo-error   { color: var(--error); }
.azulejo-acento  { color: var(--accent-600); }
.azulejo-pizarra { color: var(--text-2); }


/* En oscuro los fondos de estado ya son oscuros y el color ya es claro:
   el par funciona sin tocar nada. Lo unico que hace falta es un filo,
   porque sobre --surface un cuadro de #0b2f1a se lee como un agujero.
   Va por inset y no por border para no cambiar la medida del azulejo. */
/* OLA 7: aqui habia un rgba(255,255,255,.06) escrito a mano, y el
   token que nombra esto -.contorno-luz- va al .05.

   Medido sobre --surface en oscuro: .05 da rgb(41,41,37) y .06 da
   rgb(44,44,40). TRES niveles de 255, en una linea de un pixel. No es
   una intensidad distinta: es deriva.

   El .06 que SI se queda es el de --sh-hoja y --sh-modal, y por un
   motivo: esas dos son superficies que FLOTAN sobre un velo, sin
   pagina detras de la que tomar contraste, y ahi el punto extra si
   hace un trabajo. Un azulejo esta apoyado dentro de un panel, que es
   exactamente la situacion para la que existe el token. */
:root[data-tema="oscuro"] .azulejo {
  box-shadow: var(--contorno-luz);
}


/* ---------------------------------------------------------------------
   57b. LA CABECERA DE PANTALLA
   ---------------------------------------------------------------------
   Antes: un h1 y, con suerte, una linea debajo.
   Ahora: azulejo + titulo + para que sirve + accion.

   Los cuatro datos los pone layout_header() para las 27 pantallas de
   una vez. Ninguna pantalla se ha tocado.

   EL REPARTO ES UNA REJILLA, NO UNA CAJA FLEXIBLE, y esa es la
   decision de fondo. Con flex el azulejo es un hermano mas y al apilar
   en telefono se va ARRIBA del titulo, que es justo lo que no puede
   pasar: la identidad va AL LADO del nombre, siempre, o deja de leerse
   como una sola cosa. Con rejilla el azulejo tiene su columna y lo que
   baja de fila cuando no cabe es la accion, que es lo que sobra.

   Y cuando baja se decide por CUANTAS acciones hay, no por el ancho:

     una accion   -> se queda en la fila del titulo (es lo que hace una
                     aplicacion: "Categorias        + Nueva")
     dos o mas    -> bajan a su propia fila y se reparten el ancho

   Es determinista. Un titulo largo con dos botones no puede desbordar
   porque nunca comparten fila, y un titulo corto con un boton no
   desperdicia 44px de alto en una fila para el solo.

   La cabecera SIN azulejo (formularios, detalles: pantallas que no son
   un destino del menu) conserva exactamente el comportamiento de la
   seccion 34d. No se toca ni una linea de aquello.
   --------------------------------------------------------------------- */

.pagina-cab.con-azulejo {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--s1) var(--s4);
}

/* El azulejo abarca los dos renglones: se centra contra el bloque
   entero -titulo y descripcion-, no contra la primera linea. */
.pagina-cab.con-azulejo > .azulejo { grid-row: 1 / span 2; grid-column: 1; }

/* OLA 1: y mide lo que diga la escalera, en los tres anchos. El icono
   de dentro baja con el: un glifo de 24 dentro de un cuadro de 36 se
   come el aire que hace que el azulejo se lea como un cuadro y no como
   un boton. Gana a .azulejo-lg por especificidad, que es lo correcto:
   'lg' dice el papel -es la identidad de una PANTALLA- y la escalera
   dice la medida de ese papel en este ancho. */
.pagina-cab.con-azulejo > .azulejo {
  width:  var(--cab-azulejo);
  height: var(--cab-azulejo);
}
.pagina-cab.con-azulejo > .azulejo .icono {
  width:  var(--cab-icono);
  height: var(--cab-icono);
}

.pagina-cab.con-azulejo > .pagina-cab-txt      { grid-row: 1; grid-column: 2; min-width: 0; }
.pagina-cab.con-azulejo > .pagina-cab-acciones { grid-row: 1; grid-column: 3; }

/* La descripcion arranca bajo el titulo y llega al borde derecho. */
.pagina-cab.con-azulejo > .pagina-cab-desc {
  grid-row: 2;
  grid-column: 2 / -1;
  margin: 0;
}

/* Sin descripcion la segunda fila no existe y la rejilla la colapsa
   sola: no hace falta declarar nada para ese caso. */


@media (max-width: 767px) {

  /* OLA 1: 2px de hueco entre renglones y --s3 de separacion con lo que
     viene debajo. No es un valor nuevo: es el que las ocho pantallas de
     las fases 6 a 13 eligieron por separado, cada una en su copia. Se
     sube aqui y las ocho copias caen. Con el titulo y la descripcion a
     la escala de telefono, 12px de hueco entre las dos lineas las
     separaba en dos bloques distintos. */
  .pagina-cab.con-azulejo {
    gap: 2px var(--s3);
    margin-bottom: var(--s3);
  }

  /* Con dos o mas acciones, la fila del titulo se queda con dos
     columnas y las acciones bajan a un renglon propio, DEBAJO de la
     descripcion: primero se lee que es esta pantalla y luego que se
     puede hacer en ella. */
  .pagina-cab.con-azulejo:has(.pagina-cab-acciones > * + *) {
    grid-template-columns: auto minmax(0, 1fr);
  }
  .pagina-cab.con-azulejo:has(.pagina-cab-acciones > * + *) > .pagina-cab-acciones {
    grid-row: 3;
    grid-column: 1 / -1;
    width: 100%;
    margin-top: var(--s2);
    justify-content: flex-end;
  }


  /* Y NO se estiran. La regla de 34d manda a los botones ocupar la fila
     entera, que es lo correcto cuando la cabecera se apila entera y la
     accion es la unica cosa de su renglon. Aqui no: son dos botones
     bajo un titulo que ya se leyo, y dos bloques a pantalla completa
     pesan mas que la pantalla que presentan. Se quedan del tamano de su
     texto, pegados a la derecha, como en la referencia. */
  .pagina-cab.con-azulejo .pagina-cab-acciones > .btn { flex: 0 0 auto; }


  /* El tamano de la descripcion lo pone --cab-desc en la regla base.
     Aqui solo el interlineado: al lado de un cuadro de 44 una linea de
     12px con el interlineado de parrafo se lee suelta. */
  .pagina-cab.con-azulejo .pagina-cab-desc { line-height: 1.35; }


  /* ---------- La accion de la cabecera se compacta sola ----------
     Mismas cinco declaraciones que .btn-compacto (seccion 5). Se
     aplican por SELECTOR y no por clase a proposito: son 27 vistas y
     ninguna tiene por que saber que su boton se aprieta en un telefono.
     Ponerselo a mano fue lo que produjo siete copias con tres juegos de
     valores distintos -12 y 12.5px de letra, --s2, --s3 y 10px de
     relleno, 13, 14 y 15px de icono-, que es lo que esta Ola cierra.

     La VARIANTE no se toca: un primario sigue siendo azul y un
     secundario sigue siendo un contorno. Esto es escala, no color. */
  .pagina-cab.con-azulejo .pagina-cab-acciones > .btn {
    min-height: var(--tap);
    padding-inline: var(--s3);
    gap: var(--s1);
    font-size: var(--fs-xs);
    white-space: nowrap;
  }
  .pagina-cab.con-azulejo .pagina-cab-acciones > .btn .icono {
    width:  var(--ico-sm);
    height: var(--ico-sm);
  }
}

/* El tablero es la excepcion de siempre: en telefono su h1 se retira a
   lectura de pantalla (43c) porque quien encabeza es la cifra del dia.
   Con el h1 fuera, el azulejo y la descripcion se quedarian solos
   presentando una pantalla que ya se presenta sola. Se van con el. */
@media (max-width: 767px) {
  .contenido:has(.tm) > .pagina-cab .azulejo,
  .contenido:has(.tm) > .pagina-cab .pagina-cab-desc { display: none; }
}

/* En escritorio el azulejo sube a la altura del bloque de texto y la
   cabecera gana aire: es la primera cosa que se ve al entrar. */
/* OLA 1: el titulo ya NO se declara aqui. Lo pone --cab-titulo, que en
   este tramo vale 24. Antes valia --fs-2xl (26) para la cabecera con
   azulejo y --fs-xl (20) para la de un formulario: dos pantallas del
   mismo producto abriendo con seis pixeles de diferencia. */
@media (min-width: 768px) {
  .pagina-cab.con-azulejo { align-items: center; gap: var(--s4); }
}


/* ---------------------------------------------------------------------
   57c. LA BARRA SUPERIOR DEL TELEFONO
   ---------------------------------------------------------------------
   Una aplicacion instalada no abre con una linea de texto truncado.
   Abre con SU ICONO, el sitio donde estas trabajando, lo que hay que
   atender y tu cuenta. Cuatro cosas, en cuatro sitios fijos:

       [icono]  Nombre del negocio          [campana]  [tu]
                SUCURSAL ACTUAL  v

   Lo que cambia respecto a lo que habia:

   1. LA SUCURSAL BAJA A SU PROPIO RENGLON. Estaba compitiendo por el
      ancho con el nombre del negocio, y a 360px los dos salian
      truncados: "Sistema Universal d..." al lado de "Sucursal princi...".
      (Esa captura es de antes del rebranding; hoy ahi pone MOVQRA. El
      reparto en dos renglones no depende de eso: el nombre del NEGOCIO
      lo escribe el cliente y puede ser tan largo como quiera.)
      Dos datos a medias no son informacion. En dos renglones el negocio
      cabe entero y la sucursal tambien, y ademas se leen en el orden en
      el que importan: primero de quien es, luego donde estas.

   2. APARECE LA CUENTA. El avatar abre la hoja de cuenta. No es un
      segundo menu: no lleva a ninguna pantalla. Ver la razon en
      includes/vistas/cuenta_hoja.php.

   3. APARECE EL ICONO DE LA APLICACION. Es identidad y NO es un
      control: no lleva enlace. "Inicio" ya esta en la barra inferior.

   EL REPARTO ES UNA REJILLA DE DOS RENGLONES y la pastilla de sucursal
   se COLOCA en la segunda fila de la columna del texto, sin moverla del
   HTML. Es la razon de usar rejilla y no caja flexible: el orden del
   documento se queda como esta -importa para el lector de pantalla y
   para el recorrido con tabulador- y el que cambia es el dibujo.

   Sin JavaScript no hay pastilla ni avatar: manda el <select> de
   siempre y el pie del cajon. El reparto de una fila sigue valiendo,
   porque las columnas se declaran contra .con-js.
   --------------------------------------------------------------------- */

/* Ni el icono ni el avatar existen fuera del telefono: en tableta y
   escritorio la barra lateral ya lleva la marca y la cuenta. */
.topbar-logo,
.topbar-cuenta { display: none; }

@media (max-width: 767px) {

  /* ---------- El reparto ----------
     Dos renglones y cuatro columnas. Las columnas de los extremos
     abarcan los dos renglones: el icono y los controles se centran
     contra el bloque entero, no contra la primera linea. */
  .con-js .topbar {
    grid-template-columns: auto minmax(0, 1fr) auto auto;
    grid-template-rows: auto auto;
    row-gap: 1px;
    align-items: center;
    padding-left: var(--gutter);
    padding-right: var(--s2);
  }

  .con-js .topbar .topbar-logo   { grid-row: 1 / -1; grid-column: 1; }
  .con-js .topbar .marca         { grid-row: 1; grid-column: 2; }
  .con-js .topbar .topbar-avisos { grid-row: 1 / -1; }
  .con-js .topbar .topbar-cuenta { grid-row: 1 / -1; }

  /* La pastilla de sucursal baja al segundo renglon de la columna del
     texto. Si el negocio tiene una sola sucursal la pastilla no existe,
     la fila 2 se queda sin contenido y la rejilla la colapsa: la barra
     vuelve a medir lo de siempre sin una sola regla extra. */
  .con-js .topbar .topbar-suc-chip { grid-row: 2; grid-column: 2; justify-self: start; }

  /* En una pagina hija el primer hueco es la flecha de volver, y el
     icono de la aplicacion SE RETIRA.

     MEDIDO a 390: con flecha, icono, marca, campana y avatar en la
     misma fila, el nombre salia truncado -"Sistema Universal de
     Inv...", que era la marca de entonces- para dejar sitio a un cuadro
     que no aporta nada nuevo: la pantalla anterior ya dijo de que
     aplicacion es esto. Con "MOVQRA" sobra ancho, pero el argumento no
     era el ancho: era que el logotipo ahi no hace ningun trabajo.

     La flecha ocupa ese sitio y hace un trabajo, que es salir. El icono
     no hace ninguno. Se queda la flecha. */
  .con-js .topbar:has(.topbar-volver) .topbar-logo { display: none; }

  .con-js .topbar:has(.topbar-volver) {
    grid-template-columns: auto minmax(0, 1fr) auto auto;
  }
  .con-js .topbar:has(.topbar-volver) .topbar-volver   { grid-row: 1 / -1; grid-column: 1; }
  .con-js .topbar:has(.topbar-volver) .marca           { grid-row: 1; grid-column: 2; }
  .con-js .topbar:has(.topbar-volver) .topbar-suc-chip { grid-row: 2; grid-column: 2; }


  /* La barra crece con el segundo renglon en vez de recortarlo: el token
     dice el MINIMO, no el alto fijo. */
  .topbar { min-height: var(--topbar-movil); height: auto; }

  /* ---------- El isotipo de la aplicacion ----------
     Sin azulejo. Llevaba relleno solido de marca porque dentro habia un
     icono de trazo y sin caja no se leia como identidad. Ahora dentro
     hay el asset oficial, que es una imagen a color: encerrarlo en un
     cuadro azul le pondria un fondo que el isotipo no tiene. */
  .con-js .topbar-logo {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: none;
    width: 38px;
    height: 38px;
  }

  /* ---------- El nombre del negocio ----------
     Recupera el tamano de un titulo. Con la sucursal en su renglon ya
     no compite con nadie por el ancho y puede leerse. */
  .con-js .topbar .marca {
    font-size: var(--fs-base);
    font-weight: var(--peso-fuerte);
    line-height: 1.25;
    letter-spacing: -0.01em;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  /* El subtitulo del bloque de marca sigue retirado cuando hay
     pastilla: lo dice 42f y sigue siendo verdad. Con UNA sucursal no
     hay pastilla y el subtitulo es el que informa; se le da el tamano
     que le toca en el segundo renglon. */
  .con-js .topbar:not(:has(.topbar-suc-chip)) .marca span {
    font-size: var(--fs-xs);
    font-weight: 400;
    color: var(--text-2);
    letter-spacing: 0;
  }

  /* ---------- La pastilla de sucursal ----------
     Deja de ser una pastilla gris flotando a la derecha y pasa a ser
     el segundo renglon de la identidad: sin fondo, en mayusculas
     pequenas, con la punta que dice que se puede cambiar. Es lo que
     hace que se lea como "estas AQUI" y no como un filtro mas.

     Pierde el fondo pero NO el area de toque: se extiende con relleno
     hacia los lados, alineada con el nombre de arriba, y el pulsado se
     ve igual. */
  .con-js .topbar-suc-chip {
    position: relative;
    max-width: 100%;
    min-height: 26px;
    margin-left: calc(var(--s2) * -1);
    padding: 0 var(--s2);
    background: none;
    border: 0;
    color: var(--text-2);
    font-size: var(--fs-2xs);
    font-weight: var(--peso-fuerte);
    letter-spacing: var(--ls-etiqueta);
    text-transform: uppercase;
    border-radius: var(--r-sm);
  }

  /* EL DIBUJO MIDE 26; EL OBJETIVO, 44.

     El segundo renglon de la identidad no puede medir 44px de alto: se
     comeria la barra entera y separaria el nombre del negocio de su
     sucursal, que es justo lo que tienen que leerse juntos. Pero un
     control de 26px falla el minimo tactil, y este se toca varias veces
     al dia.

     Se separan las dos cosas. La pastilla se ve de 26 y su AREA se
     extiende con un pseudoelemento hasta 44 de alto, repartidos arriba
     y abajo. El area invade el renglon del nombre del negocio, que no
     es un control y por tanto no le quita el toque a nadie.

     Es la misma solucion que ya usa el asa de la hoja: 4px de barra,
     26 de area. */
  .con-js .topbar-suc-chip::after {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    top: 50%;
    height: var(--tap);
    transform: translateY(-50%);
  }

  .con-js .topbar-suc-chip:active { background: var(--pressed); }
  /* El icono de tienda sobra: el de la barra ya dijo de que va esto. */
  .con-js .topbar-suc-chip > .icono:first-child { display: none; }
  .con-js .topbar-suc-chip .icono { color: currentColor; }

  /* ---------- La cuenta ----------
     Iniciales sobre un tinte de marca. Mismo lenguaje que el marcador
     de un producto o de un usuario sin foto: en este sistema, cuando
     no hay imagen, hay iniciales sobre color. */
  .con-js .topbar-cuenta {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: none;
    width: var(--tap-sm);
    height: var(--tap-sm);
    padding: 0;
    background: none;
    border: 0;
    border-radius: var(--r-full);
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
  }
  .topbar-cuenta-ini {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    border-radius: var(--r-full);
    background: color-mix(in srgb, var(--brand-600) 15%, var(--surface));
    color: var(--brand-600);
    font-size: var(--fs-xs);
    font-weight: var(--peso-max);
    letter-spacing: 0.02em;
    transition: transform var(--t-rapida) var(--ease),
                background var(--t-rapida) var(--ease);
  }
  @supports not (background: color-mix(in srgb, red 15%, white)) {
    .topbar-cuenta-ini { background: var(--brand-50); }
  }
  .con-js .topbar-cuenta:active .topbar-cuenta-ini { background: var(--selected); transform: scale(var(--pulsa-lg)); }
  .con-js .topbar-cuenta:focus-visible { outline: var(--foco); outline-offset: 2px; }

  /* La campana se alinea con el avatar: mismo cuadro, mismo centro. */
  .topbar-avisos { width: var(--tap-sm); height: var(--tap-sm); }
}


/* ---------------------------------------------------------------------
   57c-bis. LA HOJA DE LA CUENTA
   ---------------------------------------------------------------------
   Tres bloques: quien eres, tu rol y las dos cosas que puedes hacer.
   Las dos acciones usan el MISMO dibujo -azulejo + texto- que las filas
   del menu, porque son el mismo gesto: tocar una fila entera.
   --------------------------------------------------------------------- */

.cuenta-hoja-quien {
  display: flex;
  align-items: center;
  gap: var(--s3);
  padding-bottom: var(--s3);
}
.cuenta-hoja-ini {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--azulejo-lg);
  height: var(--azulejo-lg);
  border-radius: var(--r-full);
  background: color-mix(in srgb, var(--brand-600) 15%, var(--surface));
  color: var(--brand-600);
  font-size: var(--fs-lg);
  font-weight: var(--peso-max);
}
@supports not (background: color-mix(in srgb, red 15%, white)) {
  .cuenta-hoja-ini { background: var(--brand-50); }
}
.cuenta-hoja-txt { min-width: 0; display: grid; }
.cuenta-hoja-txt strong {
  font-size: var(--fs-lg);
  letter-spacing: var(--ls-titulo);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.cuenta-hoja-txt small {
  color: var(--text-2);
  font-size: var(--fs-sm);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.cuenta-hoja-rol { margin: 0 0 var(--s3); }

/* La fila se sale del relleno de la hoja hacia los dos lados para que
   el pulsado llegue al borde, como en una lista de ajustes. */
.cuenta-hoja-fila {
  display: flex;
  align-items: center;
  gap: var(--s3);
  width: calc(100% + var(--s4));
  min-height: var(--fila-tap);
  margin: 0 calc(var(--s2) * -1);
  padding: var(--s2);
  background: none;
  border: 0;
  border-top: 1px solid var(--border-subtle);
  border-radius: var(--r-md);
  color: var(--text);
  font: inherit;
  font-weight: var(--peso-medio);
  text-align: left;
  text-decoration: none;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.cuenta-hoja-fila:active { background: var(--pressed); }
.cuenta-hoja-fila-txt { display: grid; min-width: 0; }
.cuenta-hoja-fila-txt small {
  color: var(--text-2);
  font-size: var(--fs-xs);
  font-weight: 400;
}
.cuenta-hoja-salir { color: var(--error); }


/* ---------------------------------------------------------------------
   57d. EL BUSCADOR ES UN CONTROL PRINCIPAL   (reescrito en la Ola 1)
   ---------------------------------------------------------------------
   En seis pantallas el buscador NO es un campo mas de un formulario: es
   la forma en la que se usa la pantalla. En un mostrador nadie recorre
   400 productos con el dedo, escribe tres letras.

   Lo que habia: NUEVE implementaciones. Cinco alturas (44, 46, el alto
   del input de formulario, 52 y 52-que-bajaba-a-44), tres radios (6, 10
   y 14) y dos cuerpos de letra (15 y 16). La version anterior de esta
   seccion decia que lo arreglaba, y normalizaba .buscador,
   .buscador-corto y .admin-barra-buscar -tres clases que las seis
   pantallas de las que hablaba el comentario no usaban-. El 46px
   literal que ese mismo comentario citaba como el defecto a corregir
   seguia en el punto de venta, que es la unica pantalla donde alguien
   busca de pie delante de un cliente.

   Ahora es UN componente y las seis lo usan por marcado.

   LAS DECISIONES, Y POR QUE:

   1. --tap-lg (52) EN TODOS LOS ANCHOS. No es solo el minimo comodo de
      un dedo: es el peso que corresponde al control con el que se opera
      la pantalla. Un buscador del alto de un select de filtro se lee
      como un filtro mas.

   2. 16px DE LETRA, SIN EXCEPCION. Por debajo de ese cuerpo iOS hace
      zoom al enfocar y descoloca la maqueta entera. Cuatro de los nueve
      buscadores estaban a 15 -Productos, POS, Alertas y Movimientos-,
      es decir, justo las cuatro pantallas que mas se usan desde un
      telefono. No es estetica: es un defecto.

   3. EL HUECO DEL ICONO SE CALCULA. Margen + lupa + margen, sin numero
      suelto: si el icono cambia de tamano, el hueco le sigue.

   4. LA LUPA SE ENCIENDE AL ENFOCAR. Lo hacia solo Categorias y es la
      pieza que confirma que el teclado va a escribir AHI.

   Las clases por pantalla que sobreviven -.alr-buscador, .mov-buscador,
   .inv-buscador- ya no declaran ni un color ni una medida: solo COLOCAN
   el componente dentro de la rejilla de su barra de filtros, que si es
   asunto de cada pantalla.
   --------------------------------------------------------------------- */

.buscador {
  position: relative;
  display: flex;
  align-items: center;
  flex: 1 1 280px;
  min-width: 0;
}

.buscador-icono {
  position: absolute;
  left: var(--s4);
  color: var(--text-3);
  pointer-events: none;
  transition: color var(--t-rapida) var(--ease);
}
.buscador:focus-within .buscador-icono { color: var(--brand-600); }

.buscador input[type="search"],
.buscador input[type="text"] {
  width: 100%;
  min-height: var(--tap-lg);

  /* margen + lupa (18) + margen. El unico numero es el tamano del
     icono, que es el que pide ui_icono() en la vista. */
  padding: var(--s2) var(--s4) var(--s2) calc(var(--s4) * 2 + 18px);

  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  font-family: inherit;
  font-size: 16px;
  color: var(--text);
  box-shadow: var(--sh-1);
  transition: border-color var(--t-rapida) var(--ease),
              box-shadow var(--t-rapida) var(--ease);
}
.buscador input:hover { border-color: var(--border-2); }
.buscador input:focus {
  outline: none;
  border-color: var(--brand-600);
  box-shadow: var(--ring);
}

/* En oscuro el control tiene que ser MAS claro que el panel para leerse
   como un hueco donde se escribe. Es la excepcion 1 de la seccion 1b,
   que este componente tapaba al declarar --surface despues. Una regla
   para los seis buscadores, no un parche por pantalla. */
:root[data-tema="oscuro"] .buscador input[type="search"],
:root[data-tema="oscuro"] .buscador input[type="text"] { background: var(--surface-2); }

/* "Buscando..." dentro del propio campo. */
.buscador-estado {
  position: absolute;
  right: var(--s4);
  font-size: var(--fs-xs);
  color: var(--text-3);
  pointer-events: none;
}

/* El boton de limpiar. Nace oculto y solo aparece con texto escrito:
   sobre un campo vacio no limpiaria nada. Mide --tap entero -antes
   Alertas lo dibujaba a 32, por debajo del suelo tactil del sistema- y
   no lleva fondo propio: dentro de un campo ya hay una superficie. */
.buscador-limpiar {
  position: absolute;
  right: var(--s1);
  display: grid;
  place-items: center;
  width: var(--tap);
  height: var(--tap);
  border: 0;
  border-radius: var(--r-full);
  background: none;
  color: var(--text-3);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.buscador-limpiar:hover { color: var(--text); background: var(--hover); }
.buscador-limpiar:active { background: var(--pressed); }
.buscador-limpiar[hidden] { display: none; }

/* Con boton de limpiar el texto no puede llegar hasta el borde. Se
   reserva el hueco SIEMPRE -aunque el boton este oculto- para que el
   texto no salte al aparecer. */
.buscador:has(.buscador-limpiar) input {
  padding-right: calc(var(--tap) + var(--s1));
}

@media (max-width: 767px) {
  /* El buscador manda en su fila: nunca comparte renglon con un filtro
     en telefono. Es la diferencia entre "un campo mas" y "el control". */
  .buscador,
  .buscador-corto { flex: 1 1 100%; min-width: 0; }

  /* El panel de administracion no entra en la Ola 1 -su densidad es
     otra fase- pero su campo de busqueda si cumplia ya lo minimo y se
     queda como estaba. */
  .buscador-corto input[type="search"],
  .buscador-corto input[type="text"],
  .admin-barra-buscar input[type="search"] {
    min-height: var(--tap-lg);
    font-size: 16px;
    border-radius: var(--r-md);
  }
}


/* ---------------------------------------------------------------------
   57e. LA BARRA INFERIOR MIDE DESDE TOKENS
   ---------------------------------------------------------------------
   Lo que se ancla por encima de la barra inferior -la barra de cobro
   del punto de venta, las acciones pegadas de un formulario, un aviso
   flotante- tenia que descontar su alto MAS el area segura del
   dispositivo, y eso estaba escrito como un calc() a mano cada vez.

   Ahora es --barra-inf-total y se escribe una vez.

   Y se sube el objetivo tactil: 58px de alto reparte 21 de icono, 3 de
   hueco y 13 de etiqueta, y deja el area util de cada destino en 58 de
   alto por un quinto del ancho. A 360px eso son 72x58, comodo. Lo que
   faltaba era el relleno inferior en un dispositivo SIN area segura -un
   Android con botones en pantalla-, donde la barra quedaba pegada al
   borde vivo.
   --------------------------------------------------------------------- */

@media (max-width: 767px) {
  .barra-inf {
    /* Suelo minimo aunque el dispositivo no declare area segura. */
    padding-bottom: max(env(safe-area-inset-bottom), 2px);
  }

  /* Cada destino cumple el minimo por si solo, sin depender de que la
     etiqueta ocupe dos lineas. */
  .barra-inf-item { min-height: var(--barra-inf); }
}


/* ---------------------------------------------------------------------
   57f. LAS HOJAS: UN COMPORTAMIENTO POR DISPOSITIVO
   ---------------------------------------------------------------------
   La misma hoja tiene que sentirse distinta segun donde se abre, y esto
   ya estaba a medias: en telefono sube desde abajo y se arrastra, y a
   partir de 768 se centra como dialogo. Faltaban dos cosas.

   1. EL DIALOGO NO USABA SU PROPIA SUPERFICIE. Se pintaba con --sheet,
      que es el color de algo apoyado en el borde de la pantalla. Un
      dialogo centrado tiene TODO el fondo detras y en tema oscuro se
      confundia con el panel que hay debajo. Ahora usa --modal y
      --sh-modal, que existen justo para esto.

   2. TABLET Y ESCRITORIO NO SON LO MISMO. Un dialogo de 480px en un
      portatil de 1440 se ve pequeno para lo que suele llevar dentro
      (una lista de sucursales, un juego de filtros). En tablet 480 es
      la medida correcta; a partir de 1280 sube a 560.
   --------------------------------------------------------------------- */

/* OLA 4: .modal entra aqui con .hoja-panel, que es donde tenia que
   haber estado desde el principio.

   De 768 arriba las dos piezas dejan de estar apoyadas en el borde y
   pasan a flotar centradas sobre el velo, asi que las dos piden la
   misma superficie: --modal y --sh-modal, que existen exactamente para
   eso. Hasta ahora solo se lo aplicaba la hoja; el modal se quedaba en
   --sheet, el color de lo que se apoya en un borde del que ya no se
   apoya. En oscuro eso era un dialogo dos tonos por debajo del que le
   toca y sin el filo de luz que lo separa del fondo.

   Comparten superficie y sombra. NO comparten lo demas: la hoja sube
   desde abajo y se arrastra, el modal aparece centrado; la hoja lleva
   asa, el modal no. Siguen siendo dos componentes. */
@media (min-width: 768px) {
  .hoja-panel,
  .modal {
    background: var(--modal);
    box-shadow: var(--sh-modal);
  }
}

@media (min-width: 1280px) {
  .hoja-panel { width: min(560px, 100%); max-height: min(80vh, 720px); }
}


/* ---------------------------------------------------------------------
   57g. OBJETIVOS TACTILES: EL ULTIMO BARRIDO
   ---------------------------------------------------------------------
   El sistema ya subio los suyos en 42m y 27m. Lo que quedaba son los
   controles que nacieron despues o que solo existen en una pantalla, y
   que por eso nunca entraron en un barrido:

     - el icono de la barra superior (campana, avatar): 40, con hueco
       alrededor, que es el escalon --tap-sm y es suficiente para un
       objetivo aislado
     - la cruz de cerrar de una hoja
     - las filas de la hoja de cuenta

   La regla se escribe contra pointer:coarse y no contra el ancho: un
   portatil con pantalla tactil tambien se toca con el dedo, y un
   telefono conectado a un raton no necesita 52px.
   --------------------------------------------------------------------- */

@media (pointer: coarse) {
  .hoja-cerrar { min-width: var(--tap); min-height: var(--tap); }
  .cuenta-hoja-fila { min-height: var(--tap-lg); }

  /* Una fila de lista tactil con marcador grande (foto o iniciales) no
     puede medir lo mismo que una de dos renglones de texto: el
     marcador ya ocupa 40 y deja 8 de aire arriba y abajo. */
  .lista-fila.con-marca .lista-toque { min-height: var(--fila-tap-lg); }

}

/* La accion de la barra de estado de la caja se quedo en 38px cuando
   43a la comprimio para que la barra cupiera en una fila. La compresion
   era correcta -esa barra ocupaba 139px para decir una cosa- pero se
   llevo por delante el minimo tactil, y este boton es el que abre y
   cierra el turno: se toca dos veces al dia, siempre con prisa y a
   veces con una sola mano.

   Vuelve a 44 de ALTO sin deshacer nada de 43a: el relleno lateral y el
   tamano de letra se quedan comprimidos, que es de donde salia el
   ahorro de ancho. La barra crece seis pixeles.

   Va por ANCHO y no por pointer:coarse porque la regla que lo comprimio
   tambien va por ancho. Un minimo tactil que solo se cumple cuando el
   navegador declara puntero grueso no se cumple: hay telefonos que se
   declaran finos y tablets que se declaran gruesas. */
@media (max-width: 767px) {
  .caja-estado-accion .btn { min-height: var(--tap); }
}




/* ---------------------------------------------------------------------
   57h. TABLET: NI TELEFONO GRANDE NI ESCRITORIO PEQUENO
   ---------------------------------------------------------------------
   Entre 768 y 1023 el shell ya hace lo correcto: riel de iconos de
   64px, sin barra inferior y sin cajon. Lo que no hacia era usar el
   ancho que gana.

   Tres decisiones, y las tres van en la misma direccion -aprovechar el
   ancho sin convertirse en un escritorio estrecho-:

   1. EL MARGEN CRECE. 16px de margen son los correctos pegado al borde
      de un telefono; en 820 de ancho dejan el texto pegado al riel.

   2. LA CABECERA DE PANTALLA RECUPERA SU FILA. En telefono dos acciones
      bajan a su renglon porque no caben. En tablet caben: vuelven
      arriba, al lado del titulo, que es donde se buscan.

   3. LA HOJA SE CENTRA (ya lo hacia) Y LA LISTA GANA AIRE, no columnas.
      Una lista tactil de dos renglones en 820px no se convierte en una
      tabla: se convierte en la misma lista con mas respiracion. Meterle
      columnas seria empezar a construir el escritorio aqui.
   --------------------------------------------------------------------- */

@media (min-width: 768px) and (max-width: 1023px) {

  :root { --gutter: var(--s5); }

  /* La cabecera vuelve a una sola fila: hay ancho para el titulo y las
     acciones juntos. */
  .pagina-cab.con-azulejo:has(.pagina-cab-acciones > * + *) {
    grid-template-columns: auto minmax(0, 1fr) auto;
  }
  .pagina-cab.con-azulejo:has(.pagina-cab-acciones > * + *) > .pagina-cab-acciones {
    grid-row: 1;
    grid-column: 3;
    width: auto;
    margin-top: 0;
  }


  /* La fila de lista respira. Mismo dibujo, mas aire: es lo que separa
     "tablet" de "telefono estirado". */
  .lista-toque { min-height: var(--fila-tap-lg); padding-left: var(--s4); padding-right: var(--s4); }
}


/* ---------------------------------------------------------------------
   57i. EL MARGEN DE PANTALLA, EN UN SOLO SITIO
   ---------------------------------------------------------------------
   --gutter vale --s4 en telefono, --s5 en tablet (arriba) y --s5 en
   escritorio. Se declara el escalon de escritorio aqui para que el
   token no se quede a medias: quien lo use en un componente nuevo
   recibe la medida correcta en los tres tamanos sin escribir una media
   query propia.
   --------------------------------------------------------------------- */

@media (min-width: 1024px) {
  :root { --gutter: var(--s5); }
}

/* ---------------------------------------------------------------------
   57j. TABLET: LO QUE SE TOCA, SE TOCA IGUAL QUE EN UN TELEFONO
   ---------------------------------------------------------------------
   Una tablet es una pantalla grande QUE SE TOCA. El sistema tenia los
   objetivos tactiles subidos por debajo de 768 y por pointer:coarse,
   y entre 768 y 1023 se quedaban con la medida de raton:

       campana de la barra superior      23px
       desplegable de sucursal           36px
       mas / menos del catalogo          34px

   Medido en la sonda de QA, no a ojo. Veintitres pixeles es la mitad
   del minimo, y la campana es lo que se toca para ir a lo que hay que
   atender.

   No se arregla ampliando el pointer:coarse de 57g: hay tablets que se
   declaran de puntero fino -las que tienen teclado y lapiz conectados-
   y seguirian fallando. Se arregla por ancho, que es lo que de verdad
   describe el dispositivo aqui.
   --------------------------------------------------------------------- */

@media (min-width: 768px) and (max-width: 1023px) {

  .topbar-avisos {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: var(--tap);
    min-height: var(--tap);
    border-radius: var(--r-md);
  }
  .topbar-avisos:active { background: var(--pressed); }

  /* Los desplegables: el de sucursal de la barra y los de cualquier
     barra de filtros. Un select de 36 en una tablet es un fallo al
     primer intento de cada dos. */
  .topbar-sucursal select,
  .inv-filtros select,
  .prod-filtros select,
  .rep-rango select { min-height: var(--tap); }

  /* Sumar y restar existencias desde el catalogo. */
  .btn-mov { min-width: var(--tap); min-height: var(--tap); }

  /* La cruz de cerrar de una hoja. 57g ya la sube por pointer:coarse;
     aqui se sube tambien por ancho, por la misma razon que arriba: una
     tablet con lapiz se declara de puntero fino y se toca igual. */
  .hoja-cerrar { min-width: var(--tap); min-height: var(--tap); }

  /* "Cerrar sesion" de la barra superior medía 18px de alto: es texto
     suelto, sin area propia. Se le da la suya sin cambiar como se ve,
     con relleno y no con alto fijo, para que no empuje la barra. */
  .topbar a.salir {
    display: inline-flex;
    align-items: center;
    min-height: 32px;
    padding: 0 var(--s1);
    margin-right: calc(var(--s1) * -1);
    border-radius: var(--r-xs);
  }
  .topbar a.salir:active { background: var(--pressed); }
}


/* ---------------------------------------------------------------------
   57k. LA CAMPANA, Y LO QUE SE TOCA EN UNA PANTALLA GRANDE
   ---------------------------------------------------------------------
   Dos huecos que dejo 57j y que la sonda de QA destapo a 1024x768, que
   es una tablet apaisada:

   1. LA CAMPANA SOLO TENIA MEDIDA EN DOS SITIOS. Por debajo de 768 mide
      40 (57c) y a partir de 1280 mide 38 (seccion 27c). Entre medias
      -tablet apaisada y portatil estrecho- no tenia ninguna: se quedaba
      del alto de su icono, 23px. Es el control que lleva a lo que hay
      que atender y estaba a la mitad del minimo en toda esa banda.

      Se le da su medida en TODA la banda de 768 en adelante, con el
      mismo cuadro que ya usa arriba y abajo. La regla de 1280 sigue
      donde estaba y sigue mandando alli, porque va despues.

   2. LOS OBJETIVOS TACTILES NO PUEDEN DEPENDER SOLO DEL ANCHO. 57j
      arregla la banda 768-1023, pero una tablet apaisada mide 1024 y se
      toca igual. Se repiten las mismas reglas contra pointer:coarse sin
      tope de ancho: asi cualquier dispositivo que declare dedo las
      recibe, mida lo que mida.

      No es duplicar el criterio: son las dos caras del mismo. Por ancho
      se cubre el dispositivo que no se declara; por puntero, el que se
      declara y no cabe en la banda.
   --------------------------------------------------------------------- */

@media (min-width: 768px) {
  .topbar-avisos {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: var(--tap-sm);
    min-height: var(--tap-sm);
    border-radius: var(--r-md);
  }
}

@media (pointer: coarse) and (min-width: 768px) {
  .topbar-avisos { min-width: var(--tap); min-height: var(--tap); }

  .topbar-sucursal select,
  .inv-filtros select,
  .prod-filtros select,
  .rep-rango select { min-height: var(--tap); }

  .btn-mov { min-width: var(--tap); min-height: var(--tap); }
}


/* =====================================================================
   58. ENTRAR Y REGISTRAR   (Fase 1)
   =====================================================================
   Estas dos pantallas son las unicas del sistema que ve alguien que
   todavia no es cliente, y las unicas que ve TODOS los dias alguien que
   ya lo es. Tenian el problema contrario al del resto del sistema: no
   les sobraba informacion, les sobraba VACIO. Medido a 390x844, por
   debajo del ultimo enlace quedaban 280px de fondo liso.

   Lo que se hace aqui, en orden de arriba abajo:

     58a  EL FONDO deja de ser un gris plano y pasa a tener luz.
     58b  LA COLUMNA: una sola medida para todo lo que va apilado.
     58c  VOLVER: de enlace olvidado a control de 44px.
     58d  LA PORTADA: icono, nombre, de que va y una frase.
     58e  LA TARJETA: se queda solo con el formulario.
     58f  LOS CAMPOS: icono a la izquierda, estado a la derecha.
     58g  LA SEGUNDA ACCION y su separador.
     58h  EL PIE: tres promesas y una firma.
     58i  REGISTRO: lo propio de esa pantalla.
     58j  TABLET Y ESCRITORIO.
     58k  TEMA OSCURO.

   NO se toca nada de la seccion 25, que es la que monta el panel de
   marca de escritorio y sigue siendo correcta. Esto va DESPUES y gana
   por orden donde tiene que ganar, que es lo que evita ir a reescribir
   reglas que otras pantallas comparten.
   ===================================================================== */


/* ---------------------------------------------------------------------
   58a. EL FONDO
   ---------------------------------------------------------------------
   El lavado de la seccion 25 son dos manchas radiales al 10% y al 8%.
   Sobre el hueso del tema claro eso es casi nada: la pantalla se leia
   como un folio.

   Se sube a cuatro focos, se reparten por las cuatro esquinas y se les
   da forma. No es un degradado de plantilla: son las dos familias de
   color del sistema -marca y acento- entrando por donde no hay
   contenido, que es justo lo que hace que el vacio deje de parecer un
   descuido y empiece a parecer aire.

   Van en el mismo pseudoelemento que ya existia. No se anade una capa.
   --------------------------------------------------------------------- */

.acceso::before {
  background:
    radial-gradient(38% 30% at 92% -4%,  rgba(29,78,216,.16),  transparent 70%),
    radial-gradient(30% 26% at 100% 34%, rgba(13,148,136,.09), transparent 72%),
    radial-gradient(42% 32% at -6% 88%,  rgba(29,78,216,.11),  transparent 70%),
    radial-gradient(26% 22% at 12% 4%,   rgba(13,148,136,.07), transparent 74%);
}


/* ---------------------------------------------------------------------
   58b. LA COLUMNA
   ---------------------------------------------------------------------
   Antes la unica medida de la pantalla era el ancho de la tarjeta, y
   todo lo demas -el enlace de volver, la marca- vivia dentro de ella.
   Ahora hay cuatro bloques apilados y los cuatro tienen que compartir
   el mismo carril, o la pantalla se lee torcida.

   El carril es .acceso-col y mide lo que medía la tarjeta. La tarjeta
   pasa a ocupar el 100% de el.
   --------------------------------------------------------------------- */

.acceso-col {
  width: 100%;
  max-width: 400px;
  margin-inline: auto;
  display: flex;
  flex-direction: column;
  gap: var(--s5);
}

.acceso-col .acceso-caja {
  max-width: none;
  margin-inline: 0;
  padding: clamp(22px, 5vw, 30px);
  border-radius: var(--r-xl);

  /* LA TARJETA TENIA QUE VERSE Y NO SE VEIA.

     Medido: --surface es #ffffff y --bg es #f7f7f6. Cuatro puntos de
     diferencia. Con el lavado de color por encima, el borde de la
     tarjeta desaparecia y el formulario se leia flotando sobre el
     papel: exactamente la sensacion que esta fase venia a quitar.

     No se arregla oscureciendo el fondo -eso afectaria a la portada y
     al resto de pantallas de acceso- sino dando a la tarjeta lo que le
     falta para leerse como un OBJETO: un filo de un pixel que no
     dependa del hueso, y una sombra que caiga de verdad.

     El filo va por inset y no por border para no sumar al ancho. */
  border-color: transparent;
  box-shadow:
    inset 0 0 0 1px color-mix(in srgb, var(--text) 8%, transparent),
    0 1px 2px rgba(28,28,26,.04),
    0 12px 32px -8px rgba(28,28,26,.10),
    0 32px 64px -24px rgba(28,28,26,.12);
}
@supports not (box-shadow: inset 0 0 0 1px color-mix(in srgb, red 8%, transparent)) {
  .acceso-col .acceso-caja { border-color: var(--border); }
}


/* El panel ya no centra a la fuerza: lo dice la seccion 25 en la linea
   3324 y sigue siendo verdad. Aqui no se repite. */



/* ---------------------------------------------------------------------
   58c. VOLVER
   ---------------------------------------------------------------------
   Era un enlace de 15px con una flecha, sin area propia: 17px de alto
   en una pantalla que se toca. Ahora es una pastilla con superficie,
   borde de pelo y 44 de alto. Sigue siendo una accion SECUNDARIA -no
   compite con nada- pero se puede tocar sin apuntar.

   La flecha se voltea con .icono-voltea, que ya existe en el sistema y
   es lo que usa la flecha de volver de la barra superior. Antes esta
   pantalla lo hacia con un rotate(180deg) propio.
   --------------------------------------------------------------------- */

.acceso-cab {
  display: flex;
  align-items: center;
  margin-bottom: calc(var(--s5) * -1 + var(--s2));
}

.acceso-col .acceso-volver {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  min-height: var(--tap);
  margin: 0;
  padding: 0 var(--s4) 0 var(--s3);
  background: color-mix(in srgb, var(--surface) 70%, transparent);
  border: 1px solid var(--border-subtle);
  border-radius: var(--r-full);
  color: var(--text-2);
  font-size: var(--fs-sm);
  font-weight: var(--peso-medio);
  text-decoration: none;
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  transition: background var(--t-rapida) var(--ease),
              color var(--t-rapida) var(--ease),
              border-color var(--t-rapida) var(--ease);
}
.acceso-col .acceso-volver:hover {
  background: var(--surface);
  border-color: var(--border);
  color: var(--text);
}
.acceso-col .acceso-volver:active { background: var(--pressed); }
/* La flecha la gira .icono-voltea, la clase del sistema. NO se anula la
   regla de la seccion 25 -que gira lo mismo- porque transform no se
   acumula: gana una declaracion y se aplica una vez. Un reset aqui
   dejaria la flecha apuntando a la derecha, que es lo contrario de lo
   que hace el control. */



/* ---------------------------------------------------------------------
   58d. LA PORTADA
   ---------------------------------------------------------------------
   Cuatro piezas y una regla: cada una es mas pequena y mas clara que la
   anterior. La jerarquia la marca el ESCALON, no el tamano suelto.

       icono      52px de caja, relleno de marca
       nombre     22px, peso maximo, tracking cerrado
       que es     11px, mayusculas, muy espaciado, gris
       filo       28x3, color de marca
       lema       15px, gris medio

   El filo es la unica pieza que no dice nada: existe para separar la
   identidad -quien soy- del argumento -para que sirvo-. Un hueco solo
   no lo consigue; una linea de tres pixeles, si.
   --------------------------------------------------------------------- */

.acceso-hero {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: var(--s2);
}

/* LA CAJA SIGUE LA PROPORCION DEL ISOTIPO, NO AL REVES.
   Medía 60x60 porque dentro había un azulejo cuadrado. El isotipo mide
   2.29:1, así que un cuadro de 60 lo dejaba pintándose a 60x26 con 34px
   de aire muerto debajo: la marca se veía pequeña y descolgada del
   nombre. Ahora la caja ES la proporción del asset -112 de ancho por 49
   de alto, los mismos 2.29- y no sobra ni falta un píxel.
   Esto NO es deformar la M: es dejar de pedirle que quepa en un cuadro
   que no es el suyo. */
.acceso-hero-icono {
  display: grid;
  place-items: center;
  width: 112px;
  height: 49px;
  margin-bottom: var(--s2);
}

.acceso-hero-nombre {
  margin: 0;
  font-size: 22px;
  font-weight: var(--peso-max);
  line-height: var(--lh-titulo);
  letter-spacing: -0.022em;
  text-wrap: balance;
}

.acceso-hero-que {
  margin: 0;
  font-size: var(--fs-2xs);
  font-weight: var(--peso-fuerte);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-3);
}

.acceso-hero-filo {
  width: 28px;
  height: 3px;
  margin: var(--s2) 0;
  border-radius: var(--r-full);
  background: var(--brand-600);
}

.acceso-hero-lema {
  margin: 0;
  font-size: var(--fs-base);
  color: var(--text-2);
}

/* Desde 900px manda el panel de marca de la izquierda: la portada
   sobra y decir dos veces lo mismo en la misma pantalla es peor que no
   decirlo. La cabecera de volver tambien, porque alli el panel ocupa la
   mitad izquierda y el enlace se queda sin sitio propio. */
@media (min-width: 900px) {
  .acceso-hero { display: none; }
}


/* ---------------------------------------------------------------------
   58e. LA TARJETA
   ---------------------------------------------------------------------
   Se queda con el formulario y nada mas. La marca salio arriba y el
   enlace de volver salio del todo. Lo que gana la tarjeta al vaciarse
   es lo que hace que se lea como un objeto y no como un contenedor.
   --------------------------------------------------------------------- */

.acceso-titulo {
  margin: 0 0 var(--s1);
  font-size: 28px;
  font-weight: var(--peso-max);
  line-height: 1.15;
  letter-spacing: -0.024em;
}
.acceso-col .acceso-caja .sub {
  margin: 0 0 var(--s5);
  color: var(--text-2);
  font-size: var(--fs-base);
}

/* El enlace de recuperar, pegado al campo del que habla. */
.acceso-olvido {
  margin: calc(var(--s3) * -1) 0 0;
  text-align: right;
}
.acceso-olvido a {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap-sm);
  font-size: var(--fs-sm);
  font-weight: var(--peso-medio);
  text-decoration: none;
}
.acceso-olvido a:hover { text-decoration: underline; }

/* La accion principal separada del ultimo control por un hueco que se
   ve: es el final del formulario, no un campo mas. */
.acceso-col .acceso-caja form .btn { margin-top: var(--s4); }


/* ---------------------------------------------------------------------
   58f. LOS CAMPOS
   ---------------------------------------------------------------------
   Tres cambios, y los tres tienen que convivir con el <input> tal como
   estaba: mismo type, mismo name, mismo required.

   1. ICONO A LA IZQUIERDA. Un sobre y un candado. Dicen que campo es
      antes de leer la etiqueta, que es como se lee un formulario en un
      telefono: de un vistazo y con prisa.

   2. ESTADO A LA DERECHA. El campo del correo se tine cuando el
      formato es valido y cuando no. Lo enciende acceso.js poniendo
      data-estado en el .campo. Es VISUAL: la validacion de verdad la
      sigue haciendo el servidor, y el navegador sigue teniendo su
      required y su type=email.

   3. FOCO. El anillo del sistema mas un borde de marca. Antes solo
      cambiaba el borde y en un telefono no se veia cual estaba activo.

   El tinte de los estados se hace con color-mix contra la superficie,
   igual que el azulejo de la Fase 0: asi el verde y el rojo salen igual
   de suaves en los dos temas sin escribir cuatro fondos a mano.
   --------------------------------------------------------------------- */

.campo-con-icono { position: relative; }

.campo-icono {
  position: absolute;
  left: 14px;
  top: 50%;
  transform: translateY(calc(-50% + 12px));
  display: grid;
  place-items: center;
  color: var(--text-3);
  pointer-events: none;
  transition: color var(--t-rapida) var(--ease);
}
/* El +12px sube del centro del BLOQUE al centro del INPUT: la etiqueta
   va encima y ocupa alto. Se calcula desde el hueco real de .campo en
   vez de clavarse a un pixel concreto. */

.campo-con-icono input { padding-left: 44px; }
.campo-con-icono.campo-clave input { padding-right: 46px; }

.campo-con-icono input:focus ~ .campo-icono,
.campo-con-icono input:focus + .campo-icono { color: var(--brand-600); }

/* La marca de validacion. Ocupa sitio siempre para que el campo no
   cambie de ancho al encenderse. */
.campo-marca {
  position: absolute;
  right: 14px;
  top: 50%;
  transform: translateY(calc(-50% + 12px));
  display: grid;
  place-items: center;
  pointer-events: none;
  opacity: 0;
  transition: opacity var(--t-rapida) var(--ease);
}
.campo-marca .icono { grid-area: 1 / 1; }
.campo-marca-ok  { color: var(--success); opacity: 0; }
.campo-marca-mal { color: var(--danger);  opacity: 0; }

.campo[data-estado="bien"] .campo-marca,
.campo[data-estado="mal"]  .campo-marca { opacity: 1; }
.campo[data-estado="bien"] .campo-marca-ok  { opacity: 1; }
.campo[data-estado="mal"]  .campo-marca-mal { opacity: 1; }

/* Con marca a la derecha, el texto no llega a pisarla. */
.campo[data-valida] input { padding-right: 42px; }

/* ---- Estado valido ---- */
.campo[data-estado="bien"] input {
  border-color: color-mix(in srgb, var(--success) 55%, var(--border));
  background: color-mix(in srgb, var(--success) 7%, var(--surface));
}
.campo[data-estado="bien"] .campo-icono { color: var(--success); }

/* ---- Estado invalido ---- */
.campo[data-estado="mal"] input {
  border-color: color-mix(in srgb, var(--danger) 55%, var(--border));
  background: color-mix(in srgb, var(--danger) 7%, var(--surface));
}
.campo[data-estado="mal"] .campo-icono { color: var(--danger); }

/* Donde no haya color-mix, borde de color y fondo de estado de siempre.
   Se pierde la suavidad, no la informacion. */
@supports not (background: color-mix(in srgb, red 7%, white)) {
  .campo[data-estado="bien"] input { border-color: var(--success); background: var(--ok-bg); }
  .campo[data-estado="mal"]  input { border-color: var(--danger);  background: var(--error-bg); }
}

/* El foco manda sobre el estado: mientras escribes, lo que importa es
   donde estas, no si lo que llevas escrito ya vale. */
.campo[data-estado] input:focus {
  border-color: var(--brand-600);
  box-shadow: var(--ring);
}

/* El mensaje de estado, cuando lo hay. */
.campo-estado {
  display: none;
  margin: var(--s1) 0 0;
  font-size: var(--fs-xs);
  font-weight: var(--peso-medio);
  line-height: 1.4;
}
.campo[data-estado="mal"] .campo-estado { display: block; color: var(--danger); }

/* El ojo de la contrasena: deja de ser un icono gris suelto y gana su
   area y su color. Es el unico control DENTRO de un campo en todo el
   sistema y tenia 32px. */
/* El ojo de la contrasena: deja de ser un icono gris suelto y gana su
   area y su color. Es el unico control DENTRO de un campo en todo el
   sistema y medía 32px.

   Mide --tap completo. Cabe: el campo de estas pantallas mide 52 de
   alto, asi que un control de 44 deja cuatro pixeles arriba y abajo y
   no toca el borde. El dibujo del ojo sigue siendo de 18px; lo que
   crece es la superficie que lo rodea, que es la que se toca. */
.acceso-col .ver-clave,
.acceso-caja .ver-clave {
  display: grid;
  place-items: center;
  width: var(--tap);
  height: var(--tap);
  border-radius: var(--r-sm);
  color: var(--text-3);
  transition: color var(--t-rapida) var(--ease),
              background var(--t-rapida) var(--ease);
}

.acceso-col .ver-clave:hover { color: var(--text); background: var(--hover); }
.acceso-col .ver-clave:active { background: var(--pressed); }
.acceso-col .ver-clave[aria-pressed="true"] { color: var(--brand-600); }


/* ---------------------------------------------------------------------
   58g. LA SEGUNDA ACCION
   ---------------------------------------------------------------------
   "Registra tu negocio" era media linea de texto gris al pie. Es la
   accion que abre el producto a quien todavia no lo tiene: es la
   segunda cosa mas importante de la pantalla y estaba por debajo del
   enlace de recuperar contrasena.

   Ahora es un boton de la familia SUAVE: relleno tenido de marca, sin
   borde, letra de marca. Ni compite con el primario -que es solido- ni
   se confunde con el secundario del sistema, que es blanco con borde y
   significa "la alternativa a lo que acabas de hacer".

   .btn-suave es nueva y vive AQUI, no en la seccion de botones: hoy la
   usa una sola pantalla. Cuando la use una segunda, sube.
   --------------------------------------------------------------------- */

.acceso-o {
  display: flex;
  align-items: center;
  gap: var(--s3);
  margin: var(--s5) 0;
  color: var(--text-3);
  font-size: var(--fs-sm);
}
.acceso-o::before,
.acceso-o::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--border-subtle);
}

.btn-suave {
  width: 100%;
  background: color-mix(in srgb, var(--brand-600) 10%, var(--surface));
  color: var(--brand-600);
  border-color: transparent;
  box-shadow: none;
}
.btn-suave:hover {
  background: color-mix(in srgb, var(--brand-600) 16%, var(--surface));
  color: var(--brand-600);
}
.btn-suave:active { background: color-mix(in srgb, var(--brand-600) 22%, var(--surface)); }
@supports not (background: color-mix(in srgb, red 10%, white)) {
  .btn-suave { background: var(--brand-50); }
}


/* ---------------------------------------------------------------------
   58h. EL PIE
   ---------------------------------------------------------------------
   Los 280px de vacio de abajo. No se rellenan con adorno: se contesta
   la pregunta que tiene delante quien va a escribir una contrasena en
   un sitio que no conoce.

   Tres columnas iguales, sin tarjeta y sin borde. La superficie aqui
   seria ruido: lo que separa las tres es el hueco y el circulo tenido,
   no una caja. Es la regla de "no card spam" aplicada donde mas tienta
   romperla.

   Y debajo, la firma: un filo, el nombre y una linea. Cierra la
   pantalla en vez de dejarla desangrandose hacia el borde.
   --------------------------------------------------------------------- */

.acceso-sellos {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--s3);
  margin: var(--s2) 0 0;
  padding: 0;
  list-style: none;
}
.acceso-sellos li {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  text-align: center;
}
.acceso-sello-icono {
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  margin-bottom: var(--s1);
  border-radius: var(--r-full);
  background: color-mix(in srgb, currentColor 13%, var(--surface));
}
@supports not (background: color-mix(in srgb, red 13%, white)) {
  .acceso-sello-icono { background: var(--surface); }
}
.acceso-sello-marca  { color: var(--brand-600); }
.acceso-sello-acento { color: var(--accent-600); }
.acceso-sello-ok     { color: var(--success); }

.acceso-sellos strong {
  font-size: var(--fs-sm);
  font-weight: var(--peso-fuerte);
  color: var(--text);
}
.acceso-sellos small {
  font-size: var(--fs-xs);
  color: var(--text-3);
  line-height: 1.35;
}

.acceso-firma {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s0);
  padding-bottom: calc(var(--s4) + env(safe-area-inset-bottom));
  text-align: center;
}
.acceso-firma-filo {
  width: 22px;
  height: 2px;
  margin-bottom: var(--s3);
  border-radius: var(--r-full);
  background: var(--brand-600);
  opacity: .5;
}
.acceso-firma p {
  margin: 0;
  font-size: var(--fs-3xs);
  font-weight: var(--peso-fuerte);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-3);
}
.acceso-firma p + p { color: var(--text-3); opacity: .72; font-weight: var(--peso-medio); }

/* Desde 900px el panel de marca ya lleva los argumentos y la identidad.
   Los sellos se retiran; la firma se queda, mucho mas discreta. */
@media (min-width: 900px) {
  .acceso-sellos { display: none; }
  .acceso-firma { padding-bottom: 0; }
}


/* ---------------------------------------------------------------------
   58e-bis. LAS TRES REGLAS DE LA FASE 0 QUE HAY QUE CONTESTAR
   ---------------------------------------------------------------------
   Ninguna de las tres estaba mal cuando se escribio. Las tres decidian
   sobre un marcado que ya no es el que hay, y por eso se contestan aqui
   -en la seccion de estas dos pantallas- en vez de ir a editarlas: la
   pantalla de recuperar contrasena sigue usando aquel marcado y tiene
   que seguir viendose exactamente igual. Todo lo de aqui cuelga de
   .acceso-col, que solo existe en entrar y en registrar.

   1. LA TARJETA. 42j la apaga por debajo de 639px -fondo, borde, radio
      y sombra a cero- con un argumento que era correcto: una tarjeta de
      349px con trece pixeles de marco no se lee como tarjeta, se lee
      como una web que no supo que hacer con el ancho.

      Lo que cambia es que ya no hay solo una tarjeta. Ahora hay una
      portada encima y un pie debajo, y la tarjeta es UNA de cuatro
      piezas apiladas. En esa composicion la superficie ya no es un
      marco sobrante: es lo que separa "donde se escribe" de "lo que
      acompana". Vuelve, y vuelve con margen de verdad a los lados, no
      con trece pixeles.

   2. EL RELLENO DEL PANEL. 42j lo pone a cero para que la tarjeta
      llegue a los bordes. Como la tarjeta vuelve a tener margen, el
      aire lateral se lo pone la columna.

   3. LOS ENLACES DE LA TARJETA. La regla de 25d convierte todo <a> de
      dentro en un bloque con relleno vertical y margen negativo, para
      darle area tactil a un enlace de texto. Es correcta para "Inicia
      sesion" y para "Olvidaste tu contrasena".

      Deja de serlo cuando el <a> es un BOTON: le quita el inline-flex,
      con lo que el icono se pega al texto, y le mete un margen negativo
      que lo sube sobre el separador. La regla no se toca: se le dice
      que un .btn no es uno de esos enlaces.
   --------------------------------------------------------------------- */

.acceso-col {
  padding-inline: var(--gutter);
}

.acceso-col .acceso-caja {
  background: var(--surface);
  border: 0;
}

/* Un enlace con pinta de boton se comporta como boton.

   La regla se escribe contra .acceso-caja y NO contra .acceso-col, o
   sea que alcanza tambien a recuperar.php, que conserva el marcado
   antiguo. Es deliberado: no es rediseñar esa pantalla, es que el
   defecto es del componente compartido y alli tambien se ve.

   MEDIDO en recuperar.php a 390px: el boton "Volver a entrar" salia con
   display:inline-block y margin-block:-14px y se montaba encima del
   parrafo anterior, tapandole la ultima linea. La regla de 25d le
   estaba dando a un BOTON el tratamiento de area tactil de un enlace de
   texto -relleno vertical compensado con margen negativo-, que para un
   .btn no hace falta: ya mide 44 o 52 de alto por si solo. */
.acceso-caja a.btn {
  display: inline-flex;
  padding-block: var(--s3);
  margin-block: 0;
}
.acceso-caja a.btn.btn-lg {
  padding-block: var(--s4);
}
@media (pointer: coarse) {
  .acceso-caja a.btn { padding-block: var(--s3); margin-block: 0; }
  .acceso-caja a.btn.btn-lg { padding-block: var(--s4); }
}


/* La columna se separa del borde de la ventana arriba y abajo. En
   telefono el panel no pone relleno vertical propio (42j), asi que lo
   pone ella, y respeta las areas seguras del dispositivo. */
@media (max-width: 767px) {
  .acceso-panel { padding-block: 0; }
  .acceso-col {
    padding-top: calc(var(--s4) + env(safe-area-inset-top));
    padding-bottom: var(--s4);
  }
}

/* ---------------------------------------------------------------------
   58l. TABLET Y LO QUE SE TOCA
   ---------------------------------------------------------------------
   Dos cosas que destapo la sonda de QA, las dos en la banda de 768 a
   899: ahi todavia manda la composicion de una columna -el panel de
   marca no entra hasta 900- pero el dispositivo ya no es un telefono.

   1. LA LETRA DE LOS INPUTS BAJABA A 15px. Por debajo de 640 hay una
      regla que la sube a 16 justo para que iOS no haga zoom al enfocar.
      A partir de ahi vuelve a 15, y un iPad es exactamente el sitio
      donde ese zoom sigue existiendo. Se ata al PUNTERO y no al ancho:
      un iPad se declara de puntero grueso mida lo que mida, y un
      escritorio de 800px de ancho no necesita esto.

   2. LA COLUMNA SE QUEDABA EN 400px EN UNA PANTALLA DE 820. No se
      ensancha la tarjeta -un campo de texto de 700px de ancho es peor
      de leer, no mejor- sino que se le da aire alrededor y se sube la
      portada, que es la que puede ocupar el sitio sin perder nada.
   --------------------------------------------------------------------- */

@media (pointer: coarse) {
  .acceso-col .campo input,
  .acceso-col .campo select { font-size: 16px; }
}

@media (min-width: 768px) and (max-width: 899px) {

  .acceso-col { max-width: 440px; gap: var(--s6); }
  .acceso-col-ancha { max-width: 560px; }

  /* La portada crece: es lo unico de la pantalla que gana algo con el
     ancho de una tablet. */
  .acceso-hero-icono { width: 134px; height: 59px; }
  .acceso-hero-nombre { font-size: var(--fs-2xl); }
  .acceso-hero-lema { font-size: var(--fs-lg); }

  /* Los sellos se separan: en 768 el hueco de 12px los dejaba pegados. */
  .acceso-sellos { gap: var(--s5); }

  /* Y la tarjeta respira por dentro. */
  .acceso-col .acceso-caja { padding: var(--s6); }
}

/* ---------------------------------------------------------------------
   58m. ESCRITORIO: LA COLUMNA SE CENTRA
   ---------------------------------------------------------------------
   La seccion 25 centraba la tarjeta en la mitad derecha con
   `.acceso-panel > .acceso-caja { margin-block: auto }`. Esa regla mira
   al HIJO DIRECTO del panel, y el hijo directo ya no es la tarjeta: es
   la columna. Sin nadie que la centre, el bloque se quedaba pegado
   arriba y dejaba un tercio de la mitad derecha vacio por debajo de la
   firma.

   Se le da a la columna lo que aquella regla le daba a la tarjeta. No
   se toca la de la seccion 25: la sigue necesitando recuperar.php, que
   conserva el marcado antiguo.
   --------------------------------------------------------------------- */

@media (min-width: 900px) {
  .acceso-col { margin-block: auto; }

  /* Arriba del todo y pegado al carril de la tarjeta: en escritorio
     "volver" es una salida, no un titulo. */
  .acceso-cab { margin-bottom: 0; }
}

/* ---------------------------------------------------------------------
   58n. EL PANEL DE MARCA EN TEMA OSCURO
   ---------------------------------------------------------------------
   MEDIDO: blanco sobre el fondo del panel daba 2.54:1 en oscuro. El
   minimo para texto es 4.5:1. Las tres lineas de argumentos, que van a
   rgba(255,255,255,.86), estaban aun peor.

   La causa: el panel se pintaba con --brand-700, y ese token se ACLARA
   en el tema oscuro. Es lo correcto para todo lo demas que lo usa -un
   enlace activo, el hover del boton- porque sobre un fondo oscuro la
   marca tiene que subir de luminosidad para verse. Pero este panel es
   al reves: lleva letra blanca fija a pantalla completa, asi que su
   fondo tiene que BAJAR.

   Se resuelve con un token propio, no cambiando --brand-700, que
   romperia el chip, el foco y la barra de progreso de todo el sistema.
   Ahora: 10.36:1 en claro y 13.9:1 en oscuro.
   --------------------------------------------------------------------- */

.acceso-marca { background: var(--acceso-marca-bg); }


/* =====================================================================
   59. EL TABLERO ES UN CENTRO DE MANDO   (Fase 2)
   =====================================================================
   Solo el tablero. Ninguna regla de aqui alcanza a otra pantalla: todo
   cuelga de .grafica-linea, de .tm o de .tablero-*.

     59a  LA GRAFICA DE LINEA. La pieza nueva.
     59b  LA GRAFICA EN EL TABLERO DE TELEFONO.
     59c  LOS ROTULOS DE SECCION.
     59d  LA BARRA INFERIOR SE NOTA MAS.

   La grafica de barras NO se toca: sigue viva y la siguen usando los
   reportes, donde comparar dos dias entre si SI es el trabajo.
   ===================================================================== */


/* ---------------------------------------------------------------------
   59a. LA GRAFICA DE LINEA
   ---------------------------------------------------------------------
   Hereda de .grafica todo el vocabulario que ya existia: --graf-color
   por tono, .grafica-rejilla, .grafica-eje y .grafica-etiqueta. Lo unico
   que se agrega es lo que una linea necesita y una barra no.

   EL TRAZO NO SE DEFORMA. El SVG lleva preserveAspectRatio="none"
   -queremos que el lienzo se estire al ancho disponible- y eso escala
   el grosor del trazo con el. Sin corregirlo, la misma grafica sale con
   una linea de 2px en escritorio y de 5px en telefono.
   vector-effect: non-scaling-stroke desacopla el grosor de la escala:
   2px son 2px en cualquier ancho. Lo mismo para los puntos, que se
   dibujan como circulos y se ovalarian.
   --------------------------------------------------------------------- */

.grafica-linea { overflow: visible; }

.grafica-trazo {
  fill: none;
  stroke: var(--graf-color);
  stroke-width: 2.5;
  stroke-linecap: round;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}

/* El area es apoyo, no dato: dice hacia donde cae la linea y se apaga
   antes de llegar a la base. Con mas opacidad se convierte en una
   mancha de color que compite con la propia curva. */
.grafica-area-alta { stop-color: var(--graf-color); stop-opacity: .22; }
.grafica-area-baja { stop-color: var(--graf-color); stop-opacity: 0; }

/* Los puntos. El de acierto es transparente y grande -22 unidades de
   radio- porque un punto de 5 no se toca con el dedo; el visible es
   pequeno y va relleno del color del panel, para que la linea parezca
   pasar POR DETRAS y no cortarse en cada dato. */
.grafica-punto-toque { fill: transparent; }
.grafica-punto-dot {
  fill: var(--surface);
  stroke: var(--graf-color);
  stroke-width: 2.5;
  vector-effect: non-scaling-stroke;
  transition: r var(--t-rapida) var(--ease);
}

/* Con un punto marcado -el dia de hoy- los demas bajan a contexto. Sin
   el, apagarlos todos no crearia ninguna jerarquia. */
.grafica-punto { cursor: pointer; }
.grafica-punto.es-hoy .grafica-punto-dot {
  fill: var(--graf-color);
  r: 6;
}
.grafica-punto:hover .grafica-punto-dot { r: 7; }

/* En una serie larga los puntos se amontonan y dejan de ser datos para
   ser ruido. La marca la pone PHP -ver la nota de ui_grafica_linea()-
   porque el CSS no puede contar solo los puntos entre los hijos del
   <svg>: delante van el degradado, la rejilla y los ejes. */
.grafica-densa .grafica-punto-dot { opacity: 0; }
.grafica-densa .grafica-punto.es-hoy .grafica-punto-dot,
.grafica-densa .grafica-punto:hover .grafica-punto-dot { opacity: 1; }


/* En el tema oscuro el relleno del punto tiene que ser el del panel en
   el que esta, no el fondo de la aplicacion, o queda un agujero negro
   en medio de la curva. */
:root[data-tema="oscuro"] .grafica-punto-dot { fill: var(--surface); }

/* La rejilla de una linea puede ser mas discreta que la de unas barras:
   la curva ya da la referencia de altura y cuatro lineas fuertes por
   detras la ensucian. */
.grafica-linea .grafica-rejilla { stroke: var(--border-subtle); }

@media (prefers-reduced-motion: reduce) {
  .grafica-punto-dot { transition: none; }
}


/* ---------------------------------------------------------------------
   59b. LA GRAFICA EN EL TABLERO DE TELEFONO
   ---------------------------------------------------------------------
   En telefono la grafica no lleva eje vertical ni rejilla: la version
   compacta del componente ya no los pinta. Lo que queda es la curva,
   sus puntos y los dias, que es lo que se mira de pie.

   El bloque va A SANGRE como los demas del tablero, y por eso el lienzo
   se recorta con un alto fijo en vez de con proporcion: en un telefono
   una grafica de 720x200 escalada a 358 de ancho saldria de 99px de
   alto, que es una raya. Se le da altura real y el viewBox se estira,
   que es justo para lo que lleva preserveAspectRatio="none".
   --------------------------------------------------------------------- */

.tm-grafica-cifra {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s3);
  padding: 0 var(--s4) var(--s3);
}
.tm-grafica-cifra b {
  font-size: var(--fs-xl);
  font-weight: var(--peso-max);
  letter-spacing: var(--ls-titulo);
  font-variant-numeric: tabular-nums;
}
.tm-grafica-cifra span {
  font-size: var(--fs-sm);
  color: var(--text-2);
}

.grafica-wrap-compacta { padding: 0 var(--s2); }
.grafica-wrap-compacta .grafica { height: 150px; width: 100%; }

@media (min-width: 768px) {
  .grafica-wrap-compacta .grafica { height: 180px; }
}


/* ---------------------------------------------------------------------
   59c. LOS ROTULOS DE SECCION   (retirada en la Fase 2.3)
   ---------------------------------------------------------------------
   Esta seccion daba personalidad a .tm-rotulo: un filo de color delante,
   mas peso y el enlace de la derecha en color de marca.

   La Fase 2.2 recompuso el tablero en tarjetas y esos rotulos pasaron a
   ser cabeceras de tarjeta -.tm-cab con su azulejo, su titulo y su
   enlace-, que viven en la seccion 60. La clase .tm-rotulo desaparecio
   del marcado y sus reglas se han borrado.

   Se deja la nota, no las reglas: quien busque "donde esta el filo de
   color de los rotulos" tiene que encontrar el camino a la 60, no un
   hueco.
   --------------------------------------------------------------------- */



/* ---------------------------------------------------------------------
   59d. LA BARRA INFERIOR SE NOTA MAS
   ---------------------------------------------------------------------
   La arquitectura NO cambia: los mismos cinco destinos, el mismo alto,
   la misma safe-area y la misma pastilla detras del icono activo que se
   decidio en 42e.

   Lo que cambia es que la pastilla ENTRA, en vez de aparecer de golpe.
   Al tocar un destino la barra se queda dibujada un instante mientras
   la pagina carga, y ese instante es el unico feedback que hay de que
   el toque se registro. Un cambio de color instantaneo no se ve; un
   crecimiento de 140ms, si.

   Y el destino activo gana un filo de 2px arriba. Con cinco iconos
   pequenos, la pastilla sola obliga a comparar los cinco para saber
   cual esta encendido; una marca en el borde se ve sin comparar nada.
   --------------------------------------------------------------------- */

@media (max-width: 767px) {

  .barra-inf-item { position: relative; }

  /* El filo del destino activo. Existe siempre, a escala cero, para que
     al encenderse no empuje ni un pixel de la maqueta. */
  .barra-inf-item::before {
    content: "";
    position: absolute;
    top: 0;
    left: 50%;
    width: 26px;
    height: 2px;
    border-radius: 0 0 var(--r-full) var(--r-full);
    background: var(--brand-600);
    transform: translateX(-50%) scaleX(0);
    transform-origin: center;
    transition: transform var(--t-media) var(--ease-suave);
  }
  .barra-inf-item.activo::before,
  body.menu-abierto #abrirMenuInf::before { transform: translateX(-50%) scaleX(1); }

  /* La pastilla del icono crece al activarse en vez de aparecer. */
  .barra-inf-item .icono {
    transition: background var(--t-media) var(--ease-suave),
                color var(--t-rapida) var(--ease),
                transform var(--t-rapida) var(--ease);
  }

  @media (prefers-reduced-motion: reduce) {
    .barra-inf-item::before,
    .barra-inf-item .icono { transition: none; }
  }
}


/* =====================================================================
   60. EL TABLERO DE TELEFONO, RECONSTRUIDO   (Fase 2.2)
   =====================================================================
   Cambia la decision de fondo de la seccion 42g.

   42g compuso esta pantalla con LAMINAS A SANGRE: bloques sin
   superficie, sin radio y sin margen, que se comen el relleno lateral y
   llegan a los dos bordes. El argumento era bueno y esta escrito alli:
   una lamina dice "soy una seccion de esta pantalla" y una tarjeta dice
   "soy un objeto dentro de una pagina".

   Ese argumento vale cuando los bloques son LO MISMO repetido -listas de
   ajustes, filas de datos- y lo unico que hace falta es separarlas. Deja
   de valer aqui, porque los cinco bloques de este tablero son cinco
   COSAS distintas: un saludo, una cifra con su accion, una grafica, tres
   estados y una lista. Sin superficie, el ojo no encuentra donde acaba
   uno y empieza el siguiente, y la pantalla se lee como una columna
   continua de cosas sueltas.

   Asi que aqui cada bloque es una tarjeta con su superficie, su radio y
   su sombra de un pixel. No es "card spam": son CINCO en toda la
   pantalla, una por pregunta, y dentro de cada una no hay mas tarjetas.

   Lo que NO cambia: los datos, los destinos, los permisos y la grafica.

     60a  EL LIENZO
     60b  LA TARJETA Y SU CABECERA
     60c  LA BIENVENIDA
     60d  INGRESOS DE HOY
     60e  TU NEGOCIO AHORA
     60f  ULTIMAS VENTAS Y EL PIE
     60g  TEMA OSCURO
   ===================================================================== */

@media (max-width: 767px) {

/* ---------------------------------------------------------------------
   60a. EL LIENZO
   ---------------------------------------------------------------------
   El fondo de la aplicacion sube un punto de azul. Con tarjetas blancas
   encima, el hueso de --bg (#f7f7f6) las dejaba casi invisibles: cuatro
   puntos de diferencia entre la tarjeta y el papel. El azul muy claro es
   lo que hace que lo blanco se lea como levantado, y es exactamente lo
   que hace la referencia.

   Se hace mezclando la marca con el fondo del sistema, no escribiendo un
   gris nuevo: asi sigue el tema y sigue el color de marca si cambia.
   --------------------------------------------------------------------- */

  .contenido:has(.tm) {
    background: var(--lienzo);
  }

  .tm {
    display: flex;
    flex-direction: column;
    gap: var(--s3);
    /* El relleno lateral lo pone la propia seccion: las tarjetas ya no
       se salen a sangre y necesitan su margen. */
    margin: 0;
  }


/* ---------------------------------------------------------------------
   60b. LA TARJETA Y SU CABECERA
   ---------------------------------------------------------------------
   Una sola definicion para los cinco bloques. La cabecera es siempre la
   misma pieza -azulejo, titulo y, opcionalmente, un dato o un enlace a
   la derecha- porque cinco cabeceras distintas en una pantalla es lo que
   hace que cinco bloques parezcan cinco pantallas.
   --------------------------------------------------------------------- */

  .tm-tarjeta {
    background: var(--surface);
    border-radius: var(--r-xl);
    padding: var(--s4);
    box-shadow:
      inset 0 0 0 1px color-mix(in srgb, var(--text) 6%, transparent),
      0 1px 2px rgba(28,28,26,.04),
      0 6px 16px -8px rgba(28,28,26,.10);
  }
  @supports not (box-shadow: inset 0 0 0 1px color-mix(in srgb, red 6%, transparent)) {
    .tm-tarjeta { border: 1px solid var(--border); }
  }

  .tm-cab {
    display: flex;
    align-items: center;
    gap: var(--s3);
    margin-bottom: var(--s4);
  }
  .tm-cab-titulo {
    margin: 0;
    font-size: var(--fs-lg);
    font-weight: var(--peso-max);
    letter-spacing: var(--ls-titulo);
    color: var(--text);
    min-width: 0;
    /* El titulo no se parte en dos renglones. Es vocabulario fijo y
       corto; quien tiene que ceder ancho es el nombre de la sucursal,
       que es variable y puede llegar a treinta caracteres. */
    white-space: nowrap;
  }


  /* El dato de la derecha de una cabecera: cifra arriba, contexto
     debajo, alineados al borde. */
  .tm-cab-cifra {
    margin-left: auto;
    display: grid;
    justify-items: end;
    text-align: right;
    min-width: 0;
  }
  .tm-cab-cifra b {
    font-family: var(--font-num);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-lg);
    font-weight: var(--peso-max);
    letter-spacing: var(--ls-titulo);
  }
  .tm-cab-cifra small { font-size: var(--fs-xs); color: var(--text-2); }

  .tm-cab-enlace {
    margin-left: auto;
    display: inline-flex;
    align-items: center;
    gap: var(--s0);
    flex: none;
    color: var(--brand-600);
    font-size: var(--fs-sm);
    font-weight: var(--peso-fuerte);
    text-decoration: none;
    /* Area de toque sin descolgar la linea de base, igual que en 42g. */
    padding-block: 13px;
    margin-block: -13px;
  }
  .tm-cab-enlace .icono { flex: none; }

  /* La pastilla de sucursal de la cabecera: es CONTEXTO, no un control.
     No lleva enlace porque cambiar de sucursal ya tiene su sitio en la
     barra superior, a cuatro centimetros. */
  .tm-suc {
    margin-left: auto;
    flex: 0 1 auto;
    min-width: 0;
    padding: 4px var(--s2);
    border-radius: var(--r-full);
    background: var(--brand-50);
    color: var(--brand-600);
    font-size: var(--fs-3xs);
    font-weight: var(--peso-max);
    letter-spacing: .05em;
    text-transform: uppercase;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }



/* ---------------------------------------------------------------------
   60c. LA BIENVENIDA
   ---------------------------------------------------------------------
   La unica tarjeta tenida de la pantalla, y a proposito: es la unica que
   no informa de nada. Sirve para decir que la aplicacion sabe quien eres
   y que dia es, y para que la pantalla no abra con una cifra a bocajarro.

   La escena se sale por la derecha. No es un descuido: un dibujo que
   termina dentro del marco se lee como una ilustracion pegada; uno que
   sigue mas alla del borde se lee como una escena que estaba ahi antes.
   --------------------------------------------------------------------- */

  .tm-bienvenida {
    position: relative;
    overflow: hidden;
    display: flex;
    align-items: center;
    gap: var(--s3);
    min-height: 108px;
    padding: var(--s4);
    border-radius: var(--r-xl);
    background: color-mix(in srgb, var(--brand-600) 9%, var(--surface));
    color: var(--text);
  }
  @supports not (background: color-mix(in srgb, red 9%, white)) {
    .tm-bienvenida { background: var(--brand-50); }
  }

  .tm-bien-icono {
    flex: none;
    display: grid;
    place-items: center;
    width: var(--azulejo);
    height: var(--azulejo);
    border-radius: var(--r-lg);
    background: var(--surface);
    color: var(--warn);
    box-shadow: 0 1px 2px rgba(28,28,26,.06);
  }

  .tm-bien-txt { position: relative; z-index: 1; min-width: 0; }
  .tm-bien-saludo {
    margin: 0;
    font-size: var(--fs-xl);
    font-weight: var(--peso-max);
    line-height: 1.2;
    letter-spacing: -0.02em;
  }
  .tm-bien-saludo b { color: var(--brand-600); font-weight: var(--peso-max); }
  .tm-bien-fecha,
  .tm-bien-nota {
    margin: 2px 0 0;
    font-size: var(--fs-sm);
    color: var(--text-2);
    line-height: 1.35;
  }
  .tm-bien-nota { color: var(--text-3); }

  /* La escena. Se ancla al borde derecho y se sale por el: el recorte lo
     hace el overflow de la tarjeta. Va detras del texto, no al lado, para
     que en 360px el saludo pueda pasarle por encima sin comprimirse. */
  .tm-bien-escena {
    position: absolute;
    right: -14px;
    bottom: 0;
    width: 132px;
    height: 96px;
    color: var(--brand-600);
    pointer-events: none;
  }
  .tm-bien-escena svg { width: 100%; height: 100%; display: block; }

  /* Todo el dibujo se separa por OPACIDAD sobre un solo color, que es lo
     que le da el aire de estar al fondo y lo que hace que funcione igual
     en los dos temas. */
  .esc-suelo  { stroke: currentColor; stroke-width: 1.5; opacity: .16; stroke-linecap: round; }
  .esc-arbol path { stroke: currentColor; stroke-width: 2; opacity: .22; stroke-linecap: round; }
  .esc-copa   { fill: currentColor; opacity: .14; }
  .esc-muro   { fill: currentColor; opacity: .12; }
  .esc-toldo  { fill: currentColor; opacity: .26; }
  .esc-vidrio { fill: currentColor; opacity: .2; }
  .esc-marco  { stroke: currentColor; stroke-width: 1.2; opacity: .3; }
  .esc-puerta { fill: currentColor; opacity: .2; }
  .esc-pomo   { fill: currentColor; opacity: .4; }

  /* Por debajo de 380px la escena se retira: a partir de ahi le quita
     ancho al saludo, que es lo unico que ese bloque tiene que decir. */
  @media (max-width: 379px) {
    .tm-bien-escena { display: none; }
  }


/* ---------------------------------------------------------------------
   60d. INGRESOS DE HOY
   ---------------------------------------------------------------------
   El protagonista. Cuatro pisos dentro de una sola tarjeta:

       cabecera
       la cifra   |   el juicio del dia
       ---------------------------------
       ventas hoy | articulos
       ---------------------------------
       [ + Nueva venta                > ]

   La cifra y el juicio se reparten la fila con una junta vertical de un
   pixel en medio. Es la unica division interna de la tarjeta que no es
   horizontal, y existe porque son dos lecturas distintas del mismo dato:
   cuanto llevo, y si eso es bueno.
   --------------------------------------------------------------------- */

  .tm-ing-cuerpo {
    display: flex;
    align-items: flex-start;
    gap: var(--s4);
  }
  .tm-ing-cifra-col { min-width: 0; flex: 1 1 auto; }

  .tm-ing-cifra {
    margin: 0;
    font-family: var(--font-num);
    font-variant-numeric: tabular-nums;
    font-size: clamp(30px, 9.2vw, 38px);
    font-weight: var(--peso-max);
    line-height: 1.05;
    letter-spacing: -0.035em;
    color: var(--text);
  }
  .tm-ing-delta { margin: var(--s2) 0 0; font-size: var(--fs-sm); }

  /* El juicio del dia. Solo se pinta cuando hay comparacion real: lo
     decide el PHP, no el CSS. */
  .tm-ing-juicio {
    flex: 0 1 40%;
    min-width: 0;
    padding-left: var(--s4);
    border-left: 1px solid var(--border-subtle);
  }
  .tm-ing-juicio p {
    margin: var(--s2) 0 0;
    font-size: var(--fs-xs);
    line-height: 1.4;
    color: var(--text-2);
  }

  /* La pastilla de estado. Mismo lenguaje que el chip del sistema, pero
     con su icono delante y sin el peso de una etiqueta de estado: aqui
     acompana a una cifra, no clasifica una fila. */
  .tm-pastilla {
    display: inline-flex;
    align-items: center;
    gap: var(--s1);
    padding: 4px var(--s2);
    border-radius: var(--r-full);
    font-size: var(--fs-xs);
    font-weight: var(--peso-fuerte);
    line-height: 1.2;
  }
  .tm-pastilla .icono { flex: none; }
  .tm-pastilla-ok     { background: var(--ok-bg);   color: var(--ok); }
  .tm-pastilla-crit   { background: var(--crit-bg); color: var(--crit); }

  .tm-pastilla-neutro { background: var(--interactive); color: var(--text-2); }

  /* Los dos datos de apoyo. Se reparten la fila en dos mitades iguales
     con una junta en medio: son del mismo rango y tienen que leerse como
     una pareja. */
  .tm-ing-mini {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--s3);
    margin: var(--s4) 0;
    padding: var(--s4) 0 0;
    border-top: 1px solid var(--border-subtle);
  }
  .tm-mini { display: flex; align-items: center; gap: var(--s2); min-width: 0; }
  .tm-mini-txt { display: grid; min-width: 0; }
  .tm-mini-txt b {
    font-family: var(--font-num);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-lg);
    font-weight: var(--peso-max);
    line-height: 1.1;
  }
  .tm-mini-txt small {
    font-size: var(--fs-xs);
    color: var(--text-2);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  /* La accion. La punta se va al borde derecho: dice "esto lleva a otro
     sitio" sin robarle sitio al texto, que se queda centrado. */
  .tm-cta {
    position: relative;
    width: 100%;
    min-height: 54px;
    font-size: var(--fs-lg);
    font-weight: var(--peso-fuerte);
    border-radius: var(--r-md);
    box-shadow: var(--sh-marca);
  }
  .tm-cta .tm-cta-punta {
    position: absolute;
    right: var(--s4);
    opacity: .8;
  }


/* ---------------------------------------------------------------------
   60e. TU NEGOCIO AHORA
   ---------------------------------------------------------------------
   Tres bloques, uno por condicion. En la referencia van en una fila de
   tres columnas; aqui van APILADOS, y la razon es medible: a 360px, tres
   columnas dejan 104px por bloque, y "Todo el stock está en orden" en
   104px son cuatro renglones de dos palabras. Apilados, cada uno tiene
   el ancho entero y una sola linea.

   Se conserva todo lo demas de la referencia: la superficie tenida por
   estado, el azulejo, el titulo, el subtitulo, el dato a la derecha y la
   punta. Es la misma pieza; lo que cambia es como se reparte el ancho, y
   eso lo decide el ancho que hay, no la imagen.
   --------------------------------------------------------------------- */

  /* -------------------------------------------------------------------
     TRES BLOQUES, NO TRES FILAS   (corregido en la Fase 2.3)
     -------------------------------------------------------------------
     La version anterior los apilaba, con el argumento de que a 360px
     tres columnas dejan 104px por bloque y "Todo el stock está en orden"
     no cabe en una fila de 104.

     El argumento medía bien y concluia mal: el problema no era el ancho,
     era que cada bloque estaba compuesto EN HORIZONTAL -icono, titulo,
     dato y flecha en una linea- y esa composicion si necesita 300px.
     Compuesto en VERTICAL, el mismo contenido cabe en 104: el icono
     arriba, el titulo debajo, el estado en dos renglones y la flecha en
     la esquina.

     Es lo que hace la referencia, y es lo que hace cualquier aplicacion
     con tres estados que se miran juntos: se miran JUNTOS, y apilados
     dejan de mirarse juntos para mirarse en fila india.
     ------------------------------------------------------------------- */

  .tm-estado {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--s2);
    align-items: stretch;
  }

  .tm-estado-item {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 3px;
    min-height: 0;
    /* Sitio abajo a la derecha para la punta. */
    padding: var(--s3) var(--s3) var(--s5);
    border-radius: var(--r-lg);
    background: var(--interactive);
    color: var(--text);
    text-decoration: none;
    -webkit-tap-highlight-color: transparent;
    transition: background var(--t-rapida) var(--ease);
  }
  .tm-estado-item:active { background: var(--pressed); }

  .tm-estado-item > .azulejo { margin-bottom: var(--s1); }

  .tm-estado-tit {
    font-size: var(--fs-sm);
    font-weight: var(--peso-max);
    line-height: 1.2;
  }
  .tm-estado-sub {
    font-size: var(--fs-xs);
    color: var(--text-2);
    line-height: 1.3;
    /* Como mucho tres renglones: mas que eso deja de ser un estado y
       empieza a ser un parrafo. Lo que sobra se corta con puntos. */
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
  }

  /* El dato y el chip van al fondo del bloque, alineados entre los tres
     aunque los subtitulos midan distinto. */
  .tm-estado-dato,
  .tm-estado-chip { margin-top: auto; padding-top: var(--s1); }
  .tm-estado-dato { display: grid; }
  .tm-estado-dato b {
    font-family: var(--font-num);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-sm);
    font-weight: var(--peso-max);
    line-height: 1.15;
  }
  .tm-estado-dato small { font-size: var(--fs-3xs); color: var(--text-2); }

  /* La punta, anclada a la esquina inferior derecha: en un bloque
     vertical no puede ir en linea con nada. */
  .tm-estado-punta {
    position: absolute;
    right: var(--s2);
    bottom: var(--s2);
    color: var(--text-3);
  }

  /* Los tres tonos, y cada uno significa una cosa:
       verde  el estado es bueno         (caja abierta, stock en orden)
       azul   es informacion, no estado  (el inventario ni va bien ni mal)
       rojo   hay algo que atender       (productos bajo minimo)
     El neutro se queda como respaldo del que no encaja en ninguno, que
     hoy es la caja cerrada: no es un problema, es que no hay turno. */
  .tm-estado-item.es-ok {
    background: color-mix(in srgb, var(--ok) 8%, var(--surface));
  }
  .tm-estado-item.es-info {
    background: color-mix(in srgb, var(--brand-600) 8%, var(--surface));
  }
  .tm-estado-item.es-atencion {
    background: color-mix(in srgb, var(--error) 8%, var(--surface));
  }
  @supports not (background: color-mix(in srgb, red 8%, white)) {
    .tm-estado-item.es-ok       { background: var(--ok-bg); }
    .tm-estado-item.es-info     { background: var(--brand-50); }
    .tm-estado-item.es-atencion { background: var(--error-bg); }
  }


  /* Por debajo de 375px el bloque se aprieta un poco: menos relleno,
     menos hueco y el chip a cuerpo mas chico. Los tres SIGUEN JUNTOS,
     que es lo que hay que conservar. */
  @media (max-width: 379px) {
    .tm-estado { gap: 6px; }
    .tm-estado-item { padding: var(--s2) var(--s2) var(--s5); }
    .tm-estado-tit { font-size: var(--fs-xs); }
    .tm-estado-sub { font-size: var(--fs-3xs); }
    .tm-estado-chip .chip { font-size: var(--fs-3xs); padding: 2px 6px; }
  }



/* ---------------------------------------------------------------------
   60f. ULTIMAS VENTAS Y EL PIE
   ---------------------------------------------------------------------
   La lista pierde el relleno lateral de .lista -ya lo pone la tarjeta- y
   cada fila lleva su azulejo, que es lo que convierte tres renglones de
   texto en tres objetos.
   --------------------------------------------------------------------- */

  .tm-tarjeta .lista { padding: 0; }
  .tm-ventas .lista-toque { padding-left: 0; padding-right: 0; min-height: var(--fila-tap); }
  .tm-ventas .lista-titulo {
    font-family: var(--font-num);
    font-variant-numeric: tabular-nums;
    font-weight: var(--peso-fuerte);
    letter-spacing: .01em;
    color: var(--text);
  }
  .tm-ventas .lista-sub { font-size: var(--fs-xs); }

  /* La fila-enlace del pie de una tarjeta: misma altura y mismo dibujo
     que una fila de lista, separada por la junta de arriba. */
  .tm-pie-fila {
    display: flex;
    align-items: center;
    gap: var(--s3);
    min-height: var(--tap);
    margin-top: var(--s3);
    padding-top: var(--s3);
    border-top: 1px solid var(--border-subtle);
    color: var(--brand-600);
    font-size: var(--fs-base);
    font-weight: var(--peso-fuerte);
    text-decoration: none;
    -webkit-tap-highlight-color: transparent;
  }
  /* El texto crece y el azulejo NO. `> span` los cogia a los dos -el
     azulejo tambien es un <span>- y le daba flex:1, con lo que el cuadro
     tenido se estiraba a todo el ancho y la fila parecia una caja vacia
     con un enlace al lado. */
  .tm-pie-fila > span:not(.azulejo) { flex: 1; min-width: 0; }
  .tm-pie-fila > .azulejo { flex: none; }

  .tm-pie-fila > .icono { flex: none; color: var(--text-3); }
  .tm-pie-fila:active { opacity: .6; }

  .tm-vacio { margin: 0; padding: var(--s4) 0; color: var(--text-2); font-size: var(--fs-sm); }

  .tm-pie { margin: var(--s2) 0 0; text-align: center; }

}


/* ---------------------------------------------------------------------
   60g. TEMA OSCURO
   ---------------------------------------------------------------------
   La composicion no cambia; cambian las superficies.

   En claro la tarjeta blanca se levanta del fondo azulado por el color.
   En oscuro no hay a donde subir con el color -la tarjeta ya es
   --surface- asi que lo que la levanta es el FILO: un pixel de luz en el
   borde superior, que es como se lee una superficie elevada en un tema
   oscuro. La sombra negra ahi no se ve.
   --------------------------------------------------------------------- */

@media (max-width: 767px) {
  :root[data-tema="oscuro"] .tm-tarjeta {
    box-shadow:
      var(--filo-luz),
      var(--contorno-luz),
      0 2px 8px rgba(0,0,0,.4);
  }

  /* El fondo de la aplicacion no se tine de azul en oscuro: sobre un
     casi negro, un 4% de azul se lee como una mancha, no como luz. */
  
  /* La bienvenida si conserva su tinte, mas contenido. */
  :root[data-tema="oscuro"] .tm-bienvenida {
    background: color-mix(in srgb, var(--brand-600) 14%, var(--surface));
    box-shadow: var(--contorno-luz);
  }
  :root[data-tema="oscuro"] .tm-bien-icono {
    background: var(--surface-3);
    box-shadow: none;
  }
}

/* ---------------------------------------------------------------------
   60h. EL SALUDO NO SE DICE DOS VECES
   ---------------------------------------------------------------------
   .tablero-saludo es el bloque compartido con escritorio: el saludo, la
   fecha y la sucursal. En telefono ese trabajo lo hace ahora la tarjeta
   de bienvenida, que dice lo mismo y ademas lo compone.

   Los dos a la vez daban "Buenas noches / 8 de septiembre" dos veces
   seguidas, una encima de la otra. Se retira el de escritorio SOLO en
   telefono y SOLO en la pantalla que tiene tablero movil -de ahi el
   :has(.tm)-, porque el mismo bloque lo usa el panel de administrador,
   que no tiene tarjeta de bienvenida y lo sigue necesitando.

   Se oculta con display:none y no con la retirada a lectura de
   pantalla: no es informacion que se pierda, es la MISMA informacion
   que esta cuatro pixeles mas abajo, y anunciarla dos veces a un lector
   de pantalla es peor que no anunciarla.

   La regla de 42g que le daba margen inferior (linea 12987) queda sin
   efecto por si sola: un elemento oculto no tiene margen. */
@media (max-width: 767px) {
  .contenido:has(.tm) > .tablero-saludo { display: none; }
}

/* =====================================================================
   61. LA GRAFICA, AFINADA   (Fase 2.3)
   =====================================================================
   La curva ya no se dispara: eso se arreglo en el PHP, con la
   interpolacion monotona y la banda de dibujo. Lo que queda aqui es el
   ACABADO, y va despues de la 59 para ganarle por orden.

   Tres cosas, y las tres van en la misma direccion: que la protagonista
   sea la LINEA.

   1. EL AREA PESABA DEMASIADO. Empezaba en un 22% de opacidad y bajaba
      a cero en todo el alto, asi que a media altura seguia habiendo un
      11% de azul solido debajo de la curva. Con un pico alto eso es un
      bloque de color que compite con el trazo. Ahora arranca mas baja y
      se apaga en el primer tercio: queda como una sombra bajo la linea,
      no como un relleno.

   2. LA LINEA BAJA DE 2.5 A 2.2. Con el area mas ligera ya no necesita
      pelear por hacerse ver, y 2.2 se lee mas afilado.

   3. LOS PUNTOS TIENEN JERARQUIA. Todos median lo mismo, asi que la
      serie parecia un collar de cuentas y habia que buscar cual era hoy.
      Ahora los normales bajan a 3.6 y el de hoy se queda en 5.5 con un
      halo del color de la marca. El area de acierto NO cambia: sigue
      siendo el circulo invisible de 22, que es lo que se toca.
   --------------------------------------------------------------------- */

.grafica-trazo { stroke-width: 2.2; }

/* Tres paradas en vez de dos: la de en medio es la que hace que el
   degradado se apague pronto en vez de repartirse por todo el alto. */
.grafica-area-alta  { stop-color: var(--graf-color); stop-opacity: .16; }
.grafica-area-media { stop-color: var(--graf-color); stop-opacity: .04; }
.grafica-area-baja  { stop-color: var(--graf-color); stop-opacity: 0; }

.grafica-punto-dot { r: 3.6; stroke-width: 2.2; }
.grafica-punto.es-hoy .grafica-punto-dot { r: 5.5; }
.grafica-punto:hover .grafica-punto-dot { r: 5.5; }
.grafica-punto.es-hoy:hover .grafica-punto-dot { r: 6.5; }

/* El halo del punto de hoy. Es un tercer circulo, detras del punto y
   sin trazo: dice "estas aqui" sin engordar el dato. Solo existe en el
   destacado, asi que no hay que apagarlo en los demas. */
.grafica-halo {
  fill: var(--graf-color);
  opacity: .16;
  pointer-events: none;
}

/* En el tema oscuro el area se ve MENOS que en claro a la misma
   opacidad -un azul translucido sobre casi negro se apaga antes- asi
   que sube un punto para conservar la misma presencia. */
:root[data-tema="oscuro"] .grafica-area-alta  { stop-opacity: .24; }
:root[data-tema="oscuro"] .grafica-area-media { stop-opacity: .07; }
:root[data-tema="oscuro"] .grafica-halo { opacity: .22; }


/* =====================================================================
   62. PRODUCTOS: LA BARRA DE HERRAMIENTAS   (Fase 3)
   =====================================================================
   SOLO la zona de arriba: buscador, categorias, orden, filtro, contador
   y pestanas. La lista de fichas -de CARTULINA hacia abajo- NO se toca:
   esta aprobada y su CSS vive en 27d y 27d-bis.

   Todo lo de aqui cuelga de .prod-barra, de .prod-chips o de
   .prod-barra-sup, que solo existen en esta pantalla.

     62a  EL LIENZO Y LA BARRA
     62b  EL BUSCADOR ES EL CONTROL PRINCIPAL
     62c  LAS CATEGORIAS, EN UN CARRIL QUE SE DESLIZA
     62d  ORDEN A LA IZQUIERDA, FILTRO A LA DERECHA
     62e  CONTADOR Y PESTANAS
     62f  TABLETA Y ESCRITORIO
   ===================================================================== */


/* ---------------------------------------------------------------------
   62a. EL LIENZO Y LA BARRA
   ---------------------------------------------------------------------
   Mismo fondo azulado del tablero: con superficies blancas encima, el
   hueso de --bg las dejaba casi invisibles.

   Las tres metricas se retiran en telefono. Lo que se viene a hacer a
   esta pantalla es buscar un producto, y en 390px esas tres cifras
   costaban 90px por encima del buscador. El dato no se pierde: el filtro
   de estado los lista y cada ficha lleva su distintivo. En escritorio,
   donde sobra sitio, se quedan.
   --------------------------------------------------------------------- */

@media (max-width: 767px) {

  .contenido:has(.prod-barra) {
    background: var(--lienzo);
  }

  .contenido:has(.prod-barra) .metricas-apoyo { display: none; }

  /* La barra deja de ser una fila flexible con hueco entre controles y
     pasa a ser una PILA: buscador, categorias y la fila de orden. Cada
     uno manda en su renglon. */
  .prod-barra {
    display: flex;
    flex-direction: column;
    gap: var(--s3);
    align-items: stretch;
    margin-bottom: var(--s4);
  }


/* ---------------------------------------------------------------------
   62b. EL BUSCADOR
   ---------------------------------------------------------------------
   OLA 1: el alto, la letra, el radio, la lupa y su encendido al enfocar
   los pone el componente del sistema (.buscador, seccion 57d). Esta
   seccion describia exactamente ese comportamiento -52px, 16px, lupa
   que se tine- escrito por tercera vez en el archivo.

   Lo unico que queda es la COLOCACION dentro de la barra: aqui el
   buscador ocupa su fila entera y no reparte ancho con nadie.
   --------------------------------------------------------------------- */

  .prod-barra .buscador { flex: none; width: 100%; }


/* ---------------------------------------------------------------------
   62c. LAS CATEGORIAS
   ---------------------------------------------------------------------
   UNA SOLA FILA QUE SE DESLIZA. Nunca dos renglones: con ocho o diez
   categorias, envolver empuja la primera ficha media pantalla hacia
   abajo, y ademas convierte una tira de opciones en un parrafo de
   botones.

   El aviso de que hay mas a la derecha es un DESVANECIDO en el borde,
   no una barra de desplazamiento. Se hace con mask, que apaga el pixel
   en vez de pintar un degradado encima: asi funciona sobre el fondo que
   sea -claro, oscuro, el azulado de esta pantalla- sin tener que saber
   de que color es lo que hay detras.

   scroll-snap deja cada pastilla alineada al soltar; -webkit-overflow-
   scrolling da la inercia de iOS.
   --------------------------------------------------------------------- */

  .prod-chips-carril {
    position: relative;
    /* El carril se sale a los dos lados del relleno de la pantalla: las
       pastillas tienen que poder deslizarse DESDE el borde, no desde 16
       pixeles dentro, o parece que hay una a medias cortada. */
    margin-inline: calc(var(--gutter) * -1);

    /* ESTAS DOS LINEAS SON LO QUE HACE QUE EL CARRIL SE DESLICE Y NO
       DESBORDE LA PANTALLA, y no son opcionales.

       .prod-barra es una caja flexible, y un elemento flexible tiene
       min-width: auto: puede CRECER hasta el tamano de su contenido por
       mucho overflow-x que se le ponga dentro. MEDIDO: a 360px el
       documento se iba a 430 y la pagina entera se desplazaba de lado en
       vez de desplazarse la tira de pastillas.

       Con el ancho declarado y min-width en cero, el carril mide lo que
       tiene que medir y el desbordamiento se queda dentro de el. */
    width: calc(100% + var(--gutter) * 2);
    min-width: 0;
  }


  /* OLA 2: el carril y la pastilla son los del sistema (.carril y .tab,
     seccion 28). Lo unico propio de Productos es que este carril SE VA A
     SANGRE: las pastillas se deslizan desde el borde vivo de la pantalla
     y por eso el relleno lateral es el margen de pantalla en vez de los
     2px del sistema.

     El desvanecido del borde derecho es el aviso de que hay mas a la
     derecha. Se hace con mask -que apaga el pixel en vez de pintar un
     degradado encima- para que funcione sobre el fondo que sea. Solo en
     telefono, que es donde el carril se desplaza de verdad. */
  .prod-chips {
    padding: 2px var(--gutter);
    margin-inline: 0;
    -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 28px), transparent 100%);
    mask-image: linear-gradient(to right, #000 calc(100% - 28px), transparent 100%);
  }
  .prod-chips[hidden] { display: none; }

  /* Diez pastillas en una tira piden un poco menos de relleno que tres
     en una fila. Es densidad: lo unico que una variante puede cambiar. */
  .prod-chip { padding: 0 var(--s3); }

  /* Con las pastillas encendidas, el desplegable de categoria se retira:
     seria el mismo filtro dos veces. Sigue en el HTML -es lo que se
     envia y lo que lee el JavaScript- y vuelve a verse si no hay
     JavaScript, porque entonces las pastillas no se encienden. */
  .con-js .prod-barra:has(.prod-chips:not([hidden])) #cat { display: none; }


/* ---------------------------------------------------------------------
   62d. ORDEN A LA IZQUIERDA, FILTRO A LA DERECHA
   ---------------------------------------------------------------------
   Dos controles y una fila. El orden ocupa lo que sobra porque su texto
   es largo ("Stock: menos primero"); el filtro se queda del tamano de su
   contenido, con el numero de filtros puestos en una pastilla.
   --------------------------------------------------------------------- */

  .prod-barra .prod-filtros-orden {
    display: flex;
    align-items: center;
    gap: var(--s2);
    width: 100%;
  }

  /* El selector de orden, envuelto para poder ponerle el icono delante:
     un <select> no admite pseudoelementos. */
  .prod-orden {
    position: relative;
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    align-items: center;
  }
  .prod-orden-icono {
    position: absolute;
    left: var(--s3);
    color: var(--text-3);
    pointer-events: none;
  }
  .prod-barra .prod-orden select {
    width: 100%;
    min-height: var(--tap);
    padding-left: 38px;
    font-size: var(--fs-sm);
    font-weight: var(--peso-medio);
    background-color: var(--surface);
    border-radius: var(--r-md);
  }

  .prod-barra .prod-filtros-boton {
    flex: none;
    min-height: var(--tap);
    gap: var(--s2);
    background: var(--surface);
    border-radius: var(--r-md);
  }
  .prod-filtros-num {
    display: inline-grid;
    place-items: center;
    min-width: 20px;
    height: 20px;
    padding: 0 5px;
    border-radius: var(--r-full);
    background: var(--interactive);
    color: var(--text-2);
    font-size: var(--fs-2xs);
    font-weight: var(--peso-max);
    line-height: 1;
  }
  .prod-barra .prod-filtros-boton.activo {
    border-color: var(--selected-borde);
    color: var(--brand-600);
  }
  .prod-barra .prod-filtros-boton.activo .prod-filtros-num {
    background: var(--brand-600);
    color: var(--text-inv);
  }
  .prod-barra .prod-filtros-boton.activo .icono { color: var(--brand-600); }

  /* Los dos desplegables plegados: cuando se abren, en su propia fila. */
  .prod-barra .prod-filtros-sel {
    display: flex;
    flex-direction: column;
    gap: var(--s2);
    width: 100%;
  }
  .prod-barra .prod-filtros-sel select { width: 100%; min-height: var(--tap); }


/* ---------------------------------------------------------------------
   62e. CONTADOR Y PESTANAS
   ---------------------------------------------------------------------
   El contador ARRIBA y las pestanas debajo, que es el orden de la
   referencia y ademas el que tiene sentido: primero cuantos hay, y luego
   con que vista los estas mirando.
   --------------------------------------------------------------------- */

  .prod-barra-sup {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: var(--s3);
  }
  .prod-barra-sup .prod-contador {
    order: -1;
    margin: 0;
    font-size: var(--fs-sm);
    color: var(--text-2);
  }
  .prod-barra-sup .tabs { width: 100%; margin-bottom: 0; }
  .prod-barra-sup .tab { flex: 1; }
}


/* ---------------------------------------------------------------------
   62f. TABLETA Y ESCRITORIO
   ---------------------------------------------------------------------
   La barra vuelve a ser una fila: hay ancho para el buscador y los
   controles juntos, y partirlos en tres renglones seria desperdiciarlo.

   Las pastillas se quedan -son mas rapidas que un desplegable tambien
   con raton- pero sin el carril deslizante: con 900px caben, y si no
   caben, envuelven, que en escritorio no cuesta media pantalla.
   --------------------------------------------------------------------- */

@media (min-width: 768px) {

  .prod-barra {
    display: grid;
    grid-template-columns: minmax(240px, 1fr) auto auto;
    align-items: center;
    gap: var(--s3);
  }
  /* OLA 1: solo colocacion. El buscador mide lo mismo en escritorio que
     en un telefono -es el control con el que se usa la pantalla en los
     dos sitios- y eso lo decide el componente, no esta rejilla. */
  .prod-barra .buscador { grid-column: 1; }

  /* Las pastillas ocupan su propia fila, a todo el ancho. */
  /* OLA 2: en escritorio caben todas, asi que el carril deja de
     desplazarse y envuelve. Es el caso A del sistema -pocas opciones, se
     ven enteras- y es lo unico que cambia: la pastilla es la misma.
     Antes esto era una SEGUNDA copia completa de .prod-chip, con su
     propio alto (36), su propio filo y su propio estado activo. */
  .prod-chips-carril { grid-column: 1 / -1; grid-row: 2; }
  .prod-chips {
    flex-wrap: wrap;
    overflow: visible;
    padding: 2px;
    -webkit-mask-image: none;
    mask-image: none;
  }
  .prod-chips[hidden] { display: none; }

  /* Con las pastillas puestas, el desplegable de categoria sobra tambien
     aqui: es el mismo filtro dos veces en la misma fila. */
  .con-js .prod-barra:has(.prod-chips:not([hidden])) #cat { display: none; }

  .prod-barra .prod-filtros-sel {
    grid-column: 2;
    display: flex;
    gap: var(--s2);
  }
  .prod-barra .prod-filtros-orden { grid-column: 3; display: flex; gap: var(--s2); align-items: center; }
  .prod-orden { position: relative; display: none; }
}

/* ---------------------------------------------------------------------
   62g. LA CABECERA: UNA SOLA ACCION EN TELEFONO
   ---------------------------------------------------------------------
   La pantalla manda DOS acciones a la cabecera: "Nuevo producto" y
   "Categorias". Con dos, la regla de la seccion 57b las baja a su propio
   renglon a todo el ancho, que es lo correcto en general y aqui deja el
   CTA principal compartiendo peso con un enlace de navegacion.

   En telefono se retira "Categorias" y el primario sube a la fila del
   titulo, que es la composicion de la referencia y la que hace que la
   accion protagonista se lea como protagonista.

   NO SE PIERDE NINGUN CAMINO: Categorias esta en el menu -grupo
   CATALOGO, a un toque desde "Mas"- y sigue en la cabecera en tableta y
   escritorio, donde hay ancho para las dos. Aqui solo deja de ocupar el
   renglon mas caro de la pantalla.

   La segunda regla hace falta porque `:has(> * + *)` cuenta hijos del
   DOM, no hijos visibles: con el boton oculto la cabecera seguiria
   creyendo que tiene dos acciones.
   --------------------------------------------------------------------- */

@media (max-width: 767px) {

  .contenido:has(.prod-barra) .pagina-cab-acciones .btn-sec { display: none; }

  .contenido:has(.prod-barra) .pagina-cab.con-azulejo {
    grid-template-columns: auto minmax(0, 1fr) auto;
  }
  .contenido:has(.prod-barra) .pagina-cab.con-azulejo > .pagina-cab-acciones {
    grid-row: 1;
    grid-column: 3;
    width: auto;
    margin-top: 0;
  }
}

/* ---------------------------------------------------------------------
   62h. LA PASTILLA SE TOCA
   ---------------------------------------------------------------------
   36px de alto esta bien para un raton y se queda corto para un dedo. En
   telefono ya mide 40; a partir de 768 sube a 44 SOLO cuando el
   dispositivo declara puntero grueso, que es como se distingue una
   tablet de un portatil sin adivinar por el ancho.
   --------------------------------------------------------------------- */

/* OLA 2: lo hace .tab por puntero, para las seis pantallas. */


/* =====================================================================
   63. PUNTO DE VENTA: EL CARRITO SE VE   (Fase 4)
   =====================================================================
   Intervencion acotada. La composicion del punto de venta se queda como
   esta: la cuadricula de productos, el carrito y el cobro no se tocan.

   Lo que se arregla son dos cosas concretas:

     1. LA BARRA NO DECIA QUE FUERA UN CARRITO. Eran dos cifras y un
        boton -"3 artículos / $420.00 / Cobrar"- y nada indicaba que
        debajo hubiera un carrito que se puede abrir para cambiar una
        cantidad o quitar algo. Ahora lleva el icono con su contador, el
        recuento de productos y la punta de "esto abre".

     2. NO HABIA SENAL DE QUE EL PRODUCTO ENTRARA. Con el foco fuera del
        buscador -que es el otro arreglo de esta fase, y va en pos.js-
        tocar una ficha no producia nada visible cerca del dedo.

     63a  LA BARRA DEL CARRITO
     63b  LA SENAL DE "ENTRO"
     63c  TEMA OSCURO
   ===================================================================== */


/* ---------------------------------------------------------------------
   63a. LA BARRA DEL CARRITO
   ---------------------------------------------------------------------
   Cuatro piezas en una fila, y cada una contesta una pregunta:

       icono + globo   que es esto y cuanto llevo
       dos renglones   que hay dentro y cuanto suma
       punta           esto se abre
       Cobrar          la accion

   La zona de la izquierda entera -icono, texto y punta- es UN SOLO
   boton, el mismo #barraVerCarrito de siempre, con la misma accion. No
   hay un segundo carrito ni un segundo camino al cobro.
   --------------------------------------------------------------------- */

.pos-barra-datos {
  display: flex;
  align-items: center;
  gap: var(--s3);
  min-width: 0;
}

/* El icono con su contador encima. El globo se posiciona contra el
   icono, no contra la barra, para que no baile cuando el texto de al
   lado cambia de ancho. */
.pos-barra-icono {
  position: relative;
  flex: none;
  display: grid;
  place-items: center;
  width: var(--azulejo-sm);
  height: var(--azulejo-sm);
  border-radius: var(--r-md);
  background: color-mix(in srgb, var(--brand-600) 12%, var(--surface));
  color: var(--brand-600);
}
@supports not (background: color-mix(in srgb, red 12%, white)) {
  .pos-barra-icono { background: var(--brand-50); }
}
.pos-barra-icono .icono { flex: none; }

.pos-barra-globo {
  position: absolute;
  top: -5px;
  right: -6px;
  display: grid;
  place-items: center;
  min-width: 19px;
  height: 19px;
  padding: 0 5px;
  border-radius: var(--r-full);
  background: var(--btn-primario-bg);
  color: var(--btn-primario-texto);
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-2xs);
  font-weight: var(--peso-max);
  line-height: 1;
  /* Un filo del color de la barra separa el globo del icono cuando los
     dos caen sobre el mismo azul. */
  box-shadow: 0 0 0 2px var(--surface);
}

.pos-barra-txt {
  display: grid;
  min-width: 0;
  text-align: left;
}
.pos-barra-art {
  font-size: var(--fs-xs);
  color: var(--text-2);
  line-height: 1.25;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.pos-barra-total {
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-lg);
  font-weight: var(--peso-max);
  line-height: 1.15;
  letter-spacing: var(--ls-titulo);
  color: var(--text);
}

/* La punta. Es lo unico de la barra que dice "esto se abre", asi que no
   se retira nunca por falta de sitio: lo que cede es el texto, que ya
   lleva sus puntos suspensivos. */
.pos-barra-punta {
  flex: none;
  color: var(--text-3);
  transition: transform var(--t-rapida) var(--ease);
}
.pos-barra-datos:active .pos-barra-punta { transform: translateX(2px); }
.pos-barra-datos:active { opacity: .75; }


/* ---------------------------------------------------------------------
   63b. LA SENAL DE "ENTRO"
   ---------------------------------------------------------------------
   Dos pulsos de 200ms. Ni uno mas: esto ocurre una vez por producto y en
   una venta de veinte articulos son veinte animaciones. Lo que se busca
   es confirmar, no celebrar.

   La ficha se hunde y el globo del carrito da un golpe de escala. Las
   dos clases las pone y las quita pos.js.
   --------------------------------------------------------------------- */

.pos-item.es-agregado { animation: pos-entro 200ms var(--ease); }
@keyframes pos-entro {
  50% { transform: scale(.955); }
}

.pos-barra-globo.es-agregado { animation: pos-globo 220ms var(--ease-suave); }
@keyframes pos-globo {
  40% { transform: scale(1.35); }
}

@media (prefers-reduced-motion: reduce) {
  .pos-item.es-agregado,
  .pos-barra-globo.es-agregado { animation: none; }
  /* Sin movimiento, la confirmacion la da el color: el globo se queda un
     instante en el tono de exito. */
  .pos-barra-globo.es-agregado { background: var(--ok); color: var(--text-inv); }
}


/* ---------------------------------------------------------------------
   63c. TEMA OSCURO
   ---------------------------------------------------------------------
   El filo del globo tiene que ser del color de la barra, no blanco: en
   oscuro un anillo blanco alrededor de un globo azul se lee como un
   error de pintado.
   --------------------------------------------------------------------- */

:root[data-tema="oscuro"] .pos-barra-globo {
  box-shadow: 0 0 0 2px var(--surface);
}
:root[data-tema="oscuro"] .pos-barra-icono {
  background: color-mix(in srgb, var(--brand-600) 20%, var(--surface));
}

/* ---------------------------------------------------------------------
   63d. LOS CONTROLES DEL CARRITO, EN UNA TABLETA
   ---------------------------------------------------------------------
   Por debajo de 768 los botones de cantidad y el de quitar ya miden 44:
   lo resolvio la seccion 35 para el telefono. A partir de ahi vuelven a
   30, porque esa regla va por ANCHO y da por hecho que mas de 768 es un
   raton.

   Una tableta no es un raton. MEDIDO en la sonda de QA: a 768, 820 y
   1024 los tres botones se quedaban en 30px, y son los que se tocan para
   corregir una cantidad antes de cobrar -o sea, con el cliente delante y
   con prisa-.

   Se ata a DOS condiciones, y las dos hacen falta:

     - el PUNTERO, que es lo que de verdad distingue una tableta de un
       portatil. Cubre cualquier dispositivo tactil, mida lo que mida.
     - el ANCHO de 768 a 1024, que es la banda de tableta que hay que
       cubrir si o si. Existe porque la condicion de puntero NO SE PUDO
       COMPROBAR: el navegador de trabajo solo emula tactil por debajo de
       768, asi que a 820 declara puntero fino y la regla no entraba. Una
       regla de accesibilidad que no se ha visto funcionar no se da por
       buena.

   Un portatil de 1024 con raton se lleva tambien los 44px. No es un
   problema: es un punto de venta, y ahi un boton grande nunca sobra.
   --------------------------------------------------------------------- */

@media (pointer: coarse) and (min-width: 768px) {
  .pos-cant-btn { width: var(--tap); height: var(--tap); }
  .pos-quitar   { width: var(--tap); height: var(--tap); }
  .pos-linea .input-cantidad { min-height: var(--tap); }
}


/* La banda por ANCHO no es un duplicado de la de arriba ni un limite de
   nivel: es el suelo que garantiza el objetivo tactil en la tableta
   cuando el navegador declara puntero fino. Por eso llega a 1024
   inclusive -que es una de las medidas comprobadas- y por eso se
   solapa a proposito con las reglas de min-width: 1024px. */
@media (min-width: 768px) and (max-width: 1024px) {
  .pos-cant-btn { width: var(--tap); height: var(--tap); }
  .pos-quitar   { width: var(--tap); height: var(--tap); }
  .pos-linea .input-cantidad { min-height: var(--tap); }
}


/* =====================================================================
   64. CATEGORIAS   (Fase 5)
   =====================================================================
   Dos piezas, y las dos solo existen en esta pantalla:

     64a  EL TRIO DE RESUMEN, con color
     64b  LA FILA DE CATEGORIA EN TELEFONO
     64c  TABLETA Y ESCRITORIO
     64d  TEMA OSCURO

   Lo que NO se toca: el formulario de crear y editar, los botones de
   accion, el panel de "sin clasificar" y toda la logica. Son las mismas
   rutas, el mismo POST y los mismos permisos.
   ===================================================================== */


/* ---------------------------------------------------------------------
   64a. EL TRIO DE RESUMEN
   ---------------------------------------------------------------------
   Tres bloques con superficie tenida, azulejo, cifra grande, nombre y
   una linea de apoyo. Reemplazan a tres cifras grises sobre blanco que
   obligaban a leer el rotulo de cada una para saber cual era cual.

   EL COLOR ES EL DATO, no adorno:

       azul   informacion   cuantas categorias hay
       verde  esta bien     cuantos productos ya estan clasificados
       ambar  hay que hacer productos sin clasificar

   El tinte es del 7%: lo justo para distinguir los tres de un vistazo
   sin que la pantalla se convierta en tres manchas de color. Se mezcla
   contra la superficie, no se escribe a mano, asi que el mismo valor
   sirve en los dos temas.

   TRES COLUMNAS SIEMPRE, tambien a 360. Cabe porque el bloque esta
   compuesto en VERTICAL -azulejo arriba, cifra, nombre, apoyo- y no en
   horizontal. Es la misma leccion del "Tu negocio ahora" del tablero:
   el ancho no era el problema, la composicion si.
   --------------------------------------------------------------------- */

.cat-trio {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--s3);
  margin: 0 0 var(--s5);
  padding: 0;
  list-style: none;
}
.cat-trio-item { display: flex; min-width: 0; }

.cat-trio-caja {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--s0);
  width: 100%;
  padding: var(--s3);
  border-radius: var(--r-lg);
  background: var(--interactive);
  color: var(--text);
  text-decoration: none;
  transition: background var(--t-rapida) var(--ease),
              transform var(--t-rapida) var(--ease);
}
.cat-trio-caja > .azulejo { margin-bottom: var(--s2); }

.cat-trio-cifra {
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-2xl);
  font-weight: var(--peso-max);
  line-height: 1.05;
  letter-spacing: -0.03em;
}
.cat-trio-titulo {
  font-size: var(--fs-sm);
  font-weight: var(--peso-fuerte);
  line-height: 1.25;
}
.cat-trio-sub {
  font-size: var(--fs-xs);
  color: var(--text-2);
  line-height: 1.3;
}

/* Los tres tonos. Cada uno tine SOLO el fondo: la cifra y el nombre se
   quedan en el color del texto, que es lo que los mantiene legibles.
   Pintar la cifra del color del bloque la haria mas bonita y menos
   legible, que es un mal cambio.

   Fase 5.2: del 7% al 10%. Al 7% los tres se distinguian mirando pero
   no de un vistazo, que es justo lo que estos bloques tienen que hacer.
   El 10% sigue estando muy por debajo de un fondo de estado -los chips
   usan --ok-bg, --warn-bg, que son mucho mas fuertes- asi que ni compite
   con el texto ni convierte la pantalla en tres manchas de color.

   El ambar sube dos puntos mas que los otros dos: el naranja quemado de
   --warn (#b45309) es mas oscuro que el verde y el azul, y a la misma
   proporcion se lee mas apagado. */
.cat-trio-marca { background: color-mix(in srgb, var(--brand-600) 10%, var(--surface)); }
.cat-trio-ok    { background: color-mix(in srgb, var(--ok) 10%, var(--surface)); }
.cat-trio-warn  { background: color-mix(in srgb, var(--warn) 12%, var(--surface)); }

@supports not (background: color-mix(in srgb, red 7%, white)) {
  .cat-trio-marca { background: var(--brand-50); }
  .cat-trio-ok    { background: var(--ok-bg); }
  .cat-trio-warn  { background: var(--warn-bg); }
}

/* Solo el bloque que lleva destino responde al toque, y el hover se
   tine de SU color, no de un ambar fijo: cuando no quedan productos sin
   clasificar ese bloque cambia a verde, y un hover ambar sobre un fondo
   verde se leia como un fallo de pintado. */
a.cat-trio-caja:active { transform: scale(var(--pulsa)); }
a.cat-trio-caja.cat-trio-warn:hover  { background: color-mix(in srgb, var(--warn) 14%, var(--surface)); }
a.cat-trio-caja.cat-trio-ok:hover    { background: color-mix(in srgb, var(--ok) 13%, var(--surface)); }
a.cat-trio-caja.cat-trio-marca:hover { background: color-mix(in srgb, var(--brand-600) 13%, var(--surface)); }
a.cat-trio-caja:focus-visible { outline: var(--foco); outline-offset: 2px; }


/* A 360 el nombre largo se aprieta un poco en vez de partirse en tres
   renglones y descuadrar los tres bloques. */
@media (max-width: 379px) {
  .cat-trio { gap: var(--s2); }
  .cat-trio-caja { padding: var(--s2); }
  .cat-trio-cifra { font-size: var(--fs-xl); }
  .cat-trio-titulo { font-size: var(--fs-xs); }
  .cat-trio-sub { font-size: var(--fs-3xs); }
}


/* ---------------------------------------------------------------------
   64b. LA FILA DE CATEGORIA EN TELEFONO
   ---------------------------------------------------------------------
   La tabla se apila como ficha (27d-bis) y cada celda sale con su
   rotulo delante: "CATEGORIA / Abarrotes", "DESCRIPCION / Sin
   descripcion", "PRODUCTOS / 0". Tres rotulos en mayusculas por
   categoria para decir lo que ya se sabe: que un nombre es un nombre.

   Aqui la fila se recompone como una fila de lista, que es lo que la
   referencia hace y lo que se lee de un vistazo:

       [iniciales]  Nombre                          2
                    Descripcion                  productos
       -------------------------------------------------
       [ Editar ]                              [ En uso ]

   Se hace con rejilla sobre el <tr>, sin tocar el marcado: la tabla
   sigue siendo una tabla, los rotulos se retiran y las celdas se
   colocan. Las acciones se quedan en su renglon, con su area de toque
   entera.
   --------------------------------------------------------------------- */

@media (max-width: 767px) {

  .contenido:has(.cat-trio) .tabla-datos tbody tr {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto;
    align-items: center;
    column-gap: var(--s3);
    row-gap: var(--s0);
    padding: var(--s3);
  }

  /* Los rotulos de columna sobran: el dato se explica solo. */
  .contenido:has(.cat-trio) .tabla-datos tbody td::before { content: none; }

  .contenido:has(.cat-trio) .tabla-datos tbody td {
    display: block;
    width: auto;
    padding: 0;
    border: 0;
    text-align: left;
    min-width: 0;
  }

  /* Nombre: la celda desaparece de la maqueta con display:contents y sus
     dos hijos -azulejo y texto- pasan a ser casillas de la rejilla del
     <tr>. Es lo que permite que el azulejo ocupe la columna 1 de los dos
     renglones sin sacarlo del marcado ni empujarlo con margenes
     negativos, que fue el primer intento y dejaba el azulejo fuera de la
     pantalla. */
  .contenido:has(.cat-trio) .tabla-datos tbody td:nth-child(1) {
    display: contents;
  }
  .contenido:has(.cat-trio) .tabla-datos tbody .cat-ini {
    grid-column: 1;
    grid-row: 1 / span 2;
    align-self: center;
  }
  .contenido:has(.cat-trio) .tabla-datos tbody .cat-nombre-caja {
    grid-column: 2;
    grid-row: 1;
    display: flex;
    align-items: center;
    gap: var(--s2);
    min-width: 0;
  }

  .cat-nombre { font-size: var(--fs-base); font-weight: var(--peso-fuerte); }

  /* Descripcion: columna 2, segundo renglon. Las celdas heredan de
     .tabla-datos un reparto de flex con space-between que, sin el rotulo
     delante, empujaba el dato al borde derecho. */
  .contenido:has(.cat-trio) .tabla-datos tbody td:nth-child(2) {
    grid-column: 2; grid-row: 2;
    justify-content: flex-start;
    font-size: var(--fs-sm);
    color: var(--text-2);
  }


  /* Cuantos productos: columna 3, los dos renglones, alineado a la
     derecha. */
  .contenido:has(.cat-trio) .tabla-datos tbody td:nth-child(3) {
    grid-column: 3; grid-row: 1 / span 2;
    text-align: right;
  }
  .cat-cuenta {
    display: grid;
    justify-items: end;
    font-family: var(--font-num);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-xl);
    font-weight: var(--peso-max);
    line-height: 1.1;
    text-decoration: none;
  }
  .cat-cuenta-txt {
    font-family: var(--font);
    font-size: var(--fs-xs);
    font-weight: 400;
    color: var(--text-2);
  }

  /* Acciones: renglon propio, a lo ancho, separadas por la junta. */
  .contenido:has(.cat-trio) .tabla-datos tbody td:nth-child(4) {
    grid-column: 1 / -1; grid-row: 3;
    margin-top: var(--s3);
    padding-top: var(--s3);
    border-top: 1px solid var(--border-subtle);
  }
  .cat-acciones-caja { display: flex; align-items: center; gap: var(--s2); }
  .cat-acciones-caja .btn { flex: 1; }
  .cat-acciones-caja .en-linea { flex: 1; display: flex; }
  .cat-acciones-caja .en-linea .btn { width: 100%; }
  .cat-en-uso { margin-left: auto; flex: none; }
}


/* ---------------------------------------------------------------------
   64c. TABLETA Y ESCRITORIO
   ---------------------------------------------------------------------
   La tabla se queda como tabla: cuatro datos alineados que se comparan
   entre filas, que es justo para lo que sirve una tabla. Lo unico que
   cambia es que el nombre lleva su azulejo delante.

   El trio gana aire: con 1440px de ancho, tres bloques de 100px de alto
   pegados unos a otros se leen como una barra, no como tres datos.
   --------------------------------------------------------------------- */

@media (min-width: 768px) {
  .cat-trio { gap: var(--s4); }
  .cat-trio-caja { padding: var(--s4); }

  /* El azulejo y el nombre, en la misma linea.

     .prod-img es display:grid, o sea de bloque, asi que dentro de la
     celda se llevaba el nombre al renglon de abajo. Los dos pasan a
     EN LINEA y se alinean por el centro. Se hace asi y no poniendo la
     celda en flex porque un <td> con display:flex deja de participar en
     el reparto de columnas de la tabla y el contenido se sale sobre la
     columna de al lado -esta anotado en la propia vista, ya paso una
     vez-. */
  .cat-ini {
    display: inline-grid;
    vertical-align: middle;
    margin-right: var(--s2);
  }
  .cat-nombre-caja {
    display: inline-flex;
    align-items: center;
    gap: var(--s2);
    vertical-align: middle;
  }
}



/* ---------------------------------------------------------------------
   64d. TEMA OSCURO
   ---------------------------------------------------------------------
   El tinte del 7% sobre un panel oscuro casi no se ve: un color
   translucido pierde fuerza contra un fondo oscuro. Sube al 14% y los
   tres vuelven a distinguirse sin encenderse.
   --------------------------------------------------------------------- */

:root[data-tema="oscuro"] .cat-trio-marca { background: color-mix(in srgb, var(--brand-600) 14%, var(--surface)); }
:root[data-tema="oscuro"] .cat-trio-ok    { background: color-mix(in srgb, var(--ok) 12%, var(--surface)); }
:root[data-tema="oscuro"] .cat-trio-warn  { background: color-mix(in srgb, var(--warn) 12%, var(--surface)); }
:root[data-tema="oscuro"] a.cat-trio-caja.cat-trio-warn:hover  { background: color-mix(in srgb, var(--warn) 20%, var(--surface)); }
:root[data-tema="oscuro"] a.cat-trio-caja.cat-trio-ok:hover    { background: color-mix(in srgb, var(--ok) 18%, var(--surface)); }
:root[data-tema="oscuro"] a.cat-trio-caja.cat-trio-marca:hover { background: color-mix(in srgb, var(--brand-600) 20%, var(--surface)); }

/* =====================================================================
   65. CATEGORIAS: LA BARRA DE EXPLORACION   (Fase 5.1)
   =====================================================================
   La pantalla deja de abrir con un formulario y abre con la lista.
   Crear pasa a la hoja, que es el patron que el sistema ya usa para el
   selector de sucursal y para la cuenta.

   Todo lo de aqui cuelga de .cat-barra o de .cat-contador, que solo
   existen en esta pantalla.
   ===================================================================== */

.cat-barra {
  display: flex;
  flex-direction: column;
  gap: var(--s3);
  margin-bottom: var(--s4);
}
.cat-barra[hidden] { display: none; }

/* ---------- Las pestanas ----------
   Una fila que se desliza, nunca dos: con tres opciones cabe casi
   siempre, pero "Con productos" y "Sin productos" a 360px no caben con
   "Todas" delante, y envolver convierte una tira de opciones en un
   parrafo de botones. Mismo carril y mismo desvanecido que las
   categorias de Productos. */
/* OLA 2: pestana = .tab, contenedor = .carril. Categorias ya era una de
   las dos pantallas que mejor lo tenia -pastilla, filo de --selected-
   borde, letra de marca- y por eso el componente comun se construyo
   sobre su forma. No queda nada propio que declarar.

   .cat-tab se conserva en el marcado: es el gancho de categorias.js. */

/* ---------- El buscador ----------
   OLA 1: el del sistema (.buscador, seccion 57d). Categorias era la
   pantalla que MEJOR lo tenia -52px, 16px de letra, lupa que se
   enciende al enfocar y un boton de limpiar del tamano de un dedo- y
   por eso el componente global se construyo sobre su geometria. Lo
   unico que cambia aqui es el radio, que baja de --r-lg a --r-md: 14px
   de curva era el unico buscador del producto con esa esquina, y el
   radio de un control en este sistema es --r-md.

   La busqueda en el navegador, el limpiar, las pestanas y el orden no
   se tocan: viven en categorias.js y siguen igual. */

/* ---------- El orden ----------
   Envuelto para poder ponerle el icono delante: un <select> no admite
   pseudoelementos. Ocupa la fila entera porque no tiene companero: no
   hay un boton "Filtros" al lado como en la referencia, y la razon esta
   escrita en la vista -el unico filtro que estos datos admiten son los
   tres estados, y esos ya estan en las pestanas-. */
.cat-orden {
  position: relative;
  display: flex;
  align-items: center;
  min-width: 0;
}
.cat-orden-icono {
  position: absolute;
  left: var(--s3);
  color: var(--text-3);
  pointer-events: none;
}
.cat-orden select {
  width: 100%;
  min-height: var(--tap);
  padding-left: 38px;
  font-size: var(--fs-sm);
  font-weight: var(--peso-medio);
  background-color: var(--surface);
  border-radius: var(--r-md);
}

/* ---------- Cuantas se estan viendo ---------- */
.cat-contador {
  margin: 0 0 var(--s3);
  font-size: var(--fs-sm);
  color: var(--text-2);
}

.cat-sin-resultados {
  margin: 0;
  padding: var(--s6) var(--s4);
  text-align: center;
  color: var(--text-2);
  font-size: var(--fs-sm);
}
.cat-sin-resultados[hidden] { display: none; }

/* El aviso de productos sin clasificar deja de vivir en el carril y
   pasa a ir debajo de la lista, que es donde se lee despues de haber
   mirado las categorias. */
.cat-aviso { margin-top: var(--s4); }


/* ---------------------------------------------------------------------
   65b. TABLETA Y ESCRITORIO
   ---------------------------------------------------------------------
   La fila unica NO empieza en 768, empieza en 1024. MEDIDO: a 768 la
   rejilla dejaba a las pestanas una columna de ~300px y las tres se
   partian en TRES renglones; a 820, en dos. Las tres pastillas juntas
   necesitan unos 380px, y hasta 1024 no los hay una vez descontados el
   buscador y el orden.

   Entre 768 y 1023 se conserva la pila del telefono, con las pestanas
   deslizandose de lado. En una tableta eso es lo correcto de todas
   formas: se toca.
   --------------------------------------------------------------------- */

@media (min-width: 1024px) {
  .cat-barra {
    display: grid;
    grid-template-columns: auto minmax(220px, 1fr) auto;
    align-items: center;
    gap: var(--s3);
  }
  /* OLA 2: en escritorio caben las tres y el carril deja de desplazarse. */
  /* OLA 2: con 1024 de ancho caben las tres y el carril deja de
     desplazarse. Es el caso A del sistema, pedido por ancho. */
  .cat-tabs { overflow: visible; flex-wrap: nowrap; }
  /* OLA 1: el buscador NO se encoge en escritorio. Era la unica pantalla
     que lo hacia y por eso Categorias y Productos se veian distintos a
     partir de 1024. */
  .cat-orden { flex: none; }
  .cat-orden select { width: auto; min-width: 190px; }
}

@media (min-width: 768px) {
  /* La lista ya no comparte fila con el formulario: ocupa el ancho
     entero, que es lo que pedia una tabla de cuatro columnas. */
  .categorias-lista { width: 100%; }
}

/* OLA 2: la densidad la decide el puntero, en la seccion 28, para las
   seis pantallas a la vez. Estas tres reglas decian lo mismo solo para
   Categorias. */


/* ---------------------------------------------------------------------
   65c. EL LIENZO DE CATEGORIAS   (Fase 5.2)
   ---------------------------------------------------------------------
   El fondo de la aplicacion es --bg, un hueso calido (#f7f7f6). Con las
   tres metricas tenidas y las superficies blancas encima, ese hueso las
   dejaba a todas leyendose casi igual: cuatro puntos de diferencia entre
   una tarjeta blanca y el papel.

   Sube un punto de azul, igual que en el tablero y en Productos. No es
   un gris nuevo escrito a mano: es la marca mezclada con el fondo del
   sistema, asi que sigue al tema y sigue al color de marca si cambia.

   ACOTADO A ESTA PANTALLA con :has(.cat-trio), que solo existe aqui. La
   variable global --bg NO se toca: la usan las quince pantallas.
   --------------------------------------------------------------------- */

/* Sin media query: el lienzo azulado vale en TODOS los anchos. Estaba
   acotado a telefono y en escritorio .contenido se quedaba transparente,
   asi que se veia el hueso calido de --bg y las metricas tenidas y las
   superficies blancas volvian a leerse casi igual. */
.contenido:has(.cat-trio) {
  background: var(--lienzo);
}

/* En oscuro el fondo NO se tine: sobre un casi negro, un 4% de azul se
   lee como una mancha, no como luz. Es la misma decision que en el
   tablero. */



/* =====================================================================
   66. EXISTENCIAS   (Fase 6)
   =====================================================================
   La pantalla se reordena para que conteste, de arriba abajo, las tres
   preguntas con las que se entra a ella:

     66a  EL LIENZO
     66b  CUANTO HAY            .inv-resumen
     66c  DONDE ESTA            .inv-seccion + .inv-sucs
     66d  QUE HAY               el buscador, los filtros y la lista
     66e  LA CABECERA DE ESTA PANTALLA
     66f  TABLETA Y ESCRITORIO
     66g  TEMA OSCURO

   FASE 6.1: DENSIDAD. La primera version tenia la composicion bien y
   el ritmo mal. Medido a 390x844: la cabecera se comia 196px porque los
   dos botones caian a un renglon propio con 40px de hueco, cada fila de
   sucursal median 88px, y el buscador iba DENTRO del panel, que sumaba
   su carton alrededor de tres controles que ya tienen superficie
   propia. Con eso, "Productos" empezaba en el pixel 700 de 844 y no se
   veia un solo producto sin desplazar.

   Lo que se corrigio, en px reales a 390:

     cabecera        196 -> ~96   (una fila: azulejo, titulo, acciones)
     resumen         128 ->  ~96  (menos relleno, vuelve el reparto)
     fila sucursal    88 ->  ~62
     buscador        dentro del panel -> sobre el lienzo
     fila producto    64 ->  ~58

   Ninguna de esas cifras toca un area de toque: los botones, las filas
   y los campos siguen midiendo 44px o mas. Lo que se quita es relleno
   vacio, no sitio para el dedo.

   Todo lo de aqui cuelga de .inv-resumen, .inv-seccion, .inv-sucs o
   .inv-filtros-ex, que solo existen en esta pantalla. Lo unico que se
   toca de fuera va acotado con :has(.inv-resumen).

   Gana a la seccion 47 por ORDEN, no por especificidad. Si algo de aqui
   deja de aplicarse es que alguien escribio CSS despues.

   Lo que NO se toca: la matriz producto x sucursal (47), la lista
   tactil del sistema (42b), el buscador del catalogo (62b) y el panel.
   ===================================================================== */


/* ---------------------------------------------------------------------
   66a. EL LIENZO
   ---------------------------------------------------------------------
   Mismo criterio que Categorias: sobre el hueso calido de --bg, una
   tarjeta blanca y una lista blanca se leen casi igual y la pantalla se
   aplana. Un 4% de azul en el fondo basta para que cada superficie se
   despegue. Acotado con :has(.inv-resumen), que solo existe aqui: la
   variable global --bg NO se toca.
   --------------------------------------------------------------------- */
.contenido:has(.inv-resumen) {
  background: var(--lienzo);
}
/* En oscuro el fondo no se tine: sobre un casi negro un 4% de azul se
   lee como mancha, no como luz. Misma decision que en el tablero. */


/* ---------------------------------------------------------------------
   66b. CUANTO HAY
   ---------------------------------------------------------------------
   UNA tarjeta con cuatro lecturas del mismo dato: el ambito, la cifra,
   lo que vale y si hay algo que atender. No son cuatro tarjetas: cuatro
   cajas del mismo peso vuelven a dejar al ojo sin donde caer, que es lo
   que el rediseno movil ya habia quitado.
   --------------------------------------------------------------------- */
.inv-resumen {
  margin: 0 0 var(--s3);
  padding: var(--s3) var(--s4);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-1);
}
.inv-resumen p { margin: 0; }

.inv-resumen-cab {
  display: flex;
  align-items: center;
  gap: var(--s2);
  min-width: 0;
  font-size: var(--fs-xs);
  font-weight: var(--peso-fuerte);
  letter-spacing: var(--ls-etiqueta);
  text-transform: uppercase;
  color: var(--text-3);
}
.inv-resumen-cab > .icono { flex: none; }
.inv-resumen-cab > span {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Volver al consolidado. Solo existe cuando se esta viendo UNA
   sucursal: sin filtro no hay nada de donde volver. */
.inv-resumen-todas {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: var(--s0);
  margin-left: auto;
  padding: var(--s1) 0 var(--s1) var(--s2);
  color: var(--brand-600);
  text-decoration: none;
  text-transform: none;
  letter-spacing: 0;
}
.inv-resumen-todas:hover { text-decoration: underline; }

.inv-resumen-fila {
  display: flex;
  align-items: center;
  gap: var(--s2);
  margin-top: var(--s2);
}
.inv-resumen-txt { flex: 1 1 auto; min-width: 0; }

.inv-resumen-cifra {
  display: flex;
  align-items: baseline;
  gap: var(--s2);
  line-height: 1;
}
.inv-resumen-cifra strong {
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-3xl);
  font-weight: var(--peso-max);
  letter-spacing: -0.02em;
  color: var(--text);
}
.inv-resumen-cifra span {
  font-size: var(--fs-lg);
  color: var(--text-2);
}
.inv-resumen-apoyo {
  margin-top: var(--s1) !important;
  font-size: var(--fs-sm);
  color: var(--text-2);
}

/* El reparto en miniatura: una barra por sucursal, a escala de la mayor.
   Es el MISMO dato que la lista de abajo, no una grafica de relleno, y
   por eso se puede quedar sin rotulos: quien quiera el numero lo tiene
   dos bloques mas abajo. */
.inv-chispa {
  flex: 0 1 auto;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  gap: 3px;
  height: 30px;
  opacity: .5;
  overflow: hidden;
}
.inv-chispa i {
  display: block;
  flex: none;
  width: 5px;
  min-height: 4px;
  background: var(--brand-600);
  border-radius: 3px;
}

/* El estado: la unica zona con color de la tarjeta, y solo dice una
   cosa -si hay que hacer algo o no-. Verde cuando no hay nada, ambar
   cuando algo cruzo el minimo, rojo solo cuando algo se agoto. */
.inv-estado {
  flex: none;
  display: flex;
  align-items: center;
  gap: var(--s2);
  min-height: var(--tap);
  max-width: 58%;
  padding: var(--s2) 10px;
  border-radius: var(--r-md);
  text-decoration: none;
  transition: background var(--t-rapida) var(--ease),
              transform var(--t-rapida) var(--ease);
}
.inv-estado:active { transform: scale(var(--pulsa)); }
.inv-estado:focus-visible { outline: var(--foco); outline-offset: 2px; }

.inv-estado-icono {
  flex: none;
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  border-radius: var(--r-full);
  background: var(--surface);
}
.inv-estado-txt { min-width: 0; display: grid; gap: 1px; }
.inv-estado-txt strong {
  font-size: var(--fs-sm);
  font-weight: var(--peso-fuerte);
  line-height: 1.2;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.inv-estado-txt small {
  font-size: var(--fs-xs);
  color: var(--text-2);
  line-height: 1.25;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.inv-estado-flecha { flex: none; opacity: .7; }

.inv-estado-ok    { background: color-mix(in srgb, var(--ok) 10%, var(--surface));    color: var(--ok); }
.inv-estado-crit  { background: color-mix(in srgb, var(--crit) 11%, var(--surface));  color: var(--crit); }
.inv-estado-error { background: color-mix(in srgb, var(--error) 11%, var(--surface)); color: var(--error); }
@supports not (background: color-mix(in srgb, red 10%, white)) {
  .inv-estado-ok    { background: var(--ok-bg); }
  .inv-estado-crit  { background: var(--crit-bg); }
  .inv-estado-error { background: var(--error-bg); }
}
.inv-estado-ok:hover    { background: color-mix(in srgb, var(--ok) 14%, var(--surface)); }
.inv-estado-crit:hover  { background: color-mix(in srgb, var(--crit) 15%, var(--surface)); }
.inv-estado-error:hover { background: color-mix(in srgb, var(--error) 15%, var(--surface)); }


/* ---------------------------------------------------------------------
   66c. DONDE ESTA
   ---------------------------------------------------------------------
   Encabezado de seccion -azulejo, que es, para que sirve, cuantos hay- y
   una LISTA. En todos los anchos: son tres o cuatro filas de datos
   comparables, y una tabla de cuatro filas es una lista con bordes.
   --------------------------------------------------------------------- */
.inv-seccion { margin: 0 0 var(--s3); }

.inv-seccion-cab {
  display: flex;
  align-items: center;
  gap: var(--s3);
  padding: 0 var(--s1) var(--s2);
}
.inv-seccion-cab > .azulejo { flex: none; }
.inv-seccion-txt { flex: 1; min-width: 0; }
.inv-seccion-txt h2 {
  margin: 0;
  font-size: var(--fs-lg);
  font-weight: var(--peso-max);
  line-height: var(--lh-tight);
  letter-spacing: var(--ls-titulo);
}
.inv-seccion-txt p {
  margin: 2px 0 0;
  font-size: var(--fs-sm);
  color: var(--text-2);
  line-height: 1.3;
}
.inv-seccion-num {
  flex: none;
  font-size: var(--fs-xs);
  color: var(--text-2);
  white-space: nowrap;
}

.inv-sucs {
  list-style: none;
  margin: 0;
  padding: 0;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-1);
  overflow: hidden;
}
.inv-suc-fila + .inv-suc-fila { border-top: 1px solid var(--border-sutil); }

.inv-suc {
  display: flex;
  align-items: center;
  gap: var(--s3);
  padding: var(--s2) var(--s3);
  min-height: 62px;
  color: inherit;
  text-decoration: none;
  -webkit-tap-highlight-color: transparent;
}
.inv-suc:hover  { background: var(--hover); }
.inv-suc:active { background: var(--pressed); }
.inv-suc:focus-visible { outline: var(--foco); outline-offset: -2px; }
.inv-suc[aria-current="true"] { box-shadow: inset 3px 0 0 var(--brand-600); }
.inv-suc.es-inactiva { opacity: .6; }

/* El azulejo reusa .prod-img y su color estable por nombre: ni paleta
   nueva, ni color guardado en la base. */
.inv-suc-azulejo { width: 38px; height: 38px; border-radius: var(--r-md); }

.inv-suc-txt { flex: 1; min-width: 0; display: grid; gap: var(--s0); }

/* El nombre y su distintivo: .lista-nom, el componente del sistema
   (seccion 42b). Aqui vivian sus ocho declaraciones, identicas a las
   de Sucursales y Usuarios. La clase propia se queda en el marcado
   porque la usan las reglas de telefono de mas abajo. */
/* Lo demas lo pone .lista-ppal (seccion 42b-ter). Aqui solo la letra
   de escritorio: esta fila se ve a todos los anchos y de 768 arriba
   tiene sitio para 12. Por debajo vuelve a los 10 de la base, que es
   lo que ya hacia su regla de telefono de mas abajo. */
@media (min-width: 768px) {
  .inv-suc-ppal { font-size: var(--fs-xs); }
}

/* El punto lleva el color; el texto se queda neutro mientras no haya
   nada que hacer. Cuando lo hay, el texto tambien se tine: eso es lo
   que hace que "3 por surtir" salte y "Sin alertas" no moleste. */
.inv-suc-estado {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: var(--fs-xs);
  color: var(--text-2);
}
.inv-suc-estado i {
  flex: none;
  width: 7px;
  height: 7px;
  border-radius: var(--r-full);
  background: currentColor;
}
.inv-punto-ok    i { color: var(--ok); }
.inv-punto-crit    { color: var(--crit); font-weight: var(--peso-medio); }
.inv-punto-error   { color: var(--error); font-weight: var(--peso-medio); }

/* La barra ES el dato: la parte que esa sucursal pone del total de
   unidades. Si no hay total, el PHP no la pinta. */
.inv-suc-barra {
  display: block;
  width: 100%;
  max-width: 240px;
  height: 4px;
  margin-top: 3px;
  background: var(--surface-2);
  border-radius: var(--r-full);
  overflow: hidden;
}
.inv-suc-barra i {
  display: block;
  height: 100%;
  border-radius: var(--r-full);
  background: var(--brand-600);
  transition: width var(--t-lenta) var(--ease-suave);
}
/* Mismo color que su azulejo: la fila se lee como una sola pieza. Son
   los seis tonos que ya existen para .prod-img-cN, no una paleta nueva.

   OLA 2: la tinta sale de la paleta categorica y el tema oscuro deja de
   necesitar sus seis gemelas.

   PERO ESTAS SEIS REGLAS NO LLEGAN A PINTAR, Y NO ES DE ESTA OLA.
   MEDIDO en el navegador: `.inv-suc-barra i` pesa (0,1,1) -clase mas
   elemento- y estas pesan (0,1,0), asi que la barra sale de
   --brand-600 y siempre salio. Con los colores escritos a mano pasaba
   exactamente lo mismo: el defecto es de la Fase 6, no de aqui.

   Se deja tal cual A PROPOSITO. Encenderlas cambiaria el aspecto de
   Existencias, y esta ola tiene escrito que Existencias no cambia.
   Queda anotado para la Ola 3, donde es una linea: subir el selector a
   `.inv-suc-barra i.inv-suc-barra-cN` o bajar el de la barra base. */
.inv-suc-barra-c0 { background: var(--cat-0); }
.inv-suc-barra-c1 { background: var(--cat-1); }
.inv-suc-barra-c2 { background: var(--cat-2); }
.inv-suc-barra-c3 { background: var(--cat-3); }
.inv-suc-barra-c4 { background: var(--cat-4); }
.inv-suc-barra-c5 { background: var(--cat-5); }

/* Columna FIJA a la derecha. Fija y no elastica porque el nombre de la
   sucursal es lo que tiene que crecer: con la columna del importe
   repartiendose el sobrante, "PAPELERIA JUAN ROMA NTE" se quedaba en
   "PAPELERIA JUA..." en un telefono de 390. */
.inv-suc-cifras {
  flex: none;
  display: grid;
  justify-items: end;
  gap: 1px;
  text-align: right;
}
.inv-suc-cifras .lista-cifra { font-size: var(--fs-lg); line-height: 1.15; }
.inv-suc-cifras .lista-cifra small {
  display: inline;
  margin-left: 3px;
  font-size: var(--fs-xs);
}
.inv-suc-costo {
  font-size: var(--fs-xs);
  color: var(--text-2);
  white-space: nowrap;
}


/* ---------------------------------------------------------------------
   66d. QUE HAY
   ---------------------------------------------------------------------
   El encabezado "Productos" ya dice cuantos son y de que ambito, asi
   que la barra del panel se queda solo con lo que se OPERA: buscar,
   filtrar y elegir sucursal.

   UNA sola accion de filtro. Antes convivian "Filtros" (pliega) y
   "Filtrar" (envia): dos botones con el mismo nombre y distinto
   trabajo. Ahora "Filtros" abre el pliegue y "Ver resultados" -que vive
   DENTRO- aplica.
   --------------------------------------------------------------------- */
.inv-seccion-prod { margin-bottom: var(--s2); }

/* El buscador y los dos controles viven sobre el LIENZO, no dentro de
   la cabecera del panel: cada uno ya trae su superficie, su borde y su
   sombra, y meterlos en una caja blanca era envolver tres objetos
   acabados en un carton de 28px. El panel empieza en la primera fila de
   producto. */
.inv-filtros-ex { margin: 0 0 var(--s3); }
.inv-panel { margin-bottom: var(--s4); }

/* El ambito: el filtro que cambia el significado de todas las cifras de
   la pantalla, asi que se queda a la vista y no dentro del pliegue.
   Es el <select> de siempre, vestido de pastilla. */
.inv-ambito {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--s2);
  min-width: 0;
  min-height: var(--tap);
  padding: 0 var(--s3);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  box-shadow: var(--sh-1);
  transition: border-color var(--t-rapida) var(--ease),
              box-shadow var(--t-rapida) var(--ease);
}
.inv-ambito:hover { border-color: var(--border-2); }
.inv-ambito:focus-within {
  border-color: var(--brand-600);
  box-shadow: var(--ring);
}
.inv-ambito-icono,
.inv-ambito-punta { flex: none; color: var(--text-3); pointer-events: none; }

.inv-ambito select {
  flex: 1;
  min-width: 0;
  width: 100%;
  min-height: 0;
  padding: 0;
  background: none;
  border: 0;
  box-shadow: none;
  appearance: none;
  -webkit-appearance: none;
  font-family: inherit;
  font-size: var(--fs-base);
  font-weight: var(--peso-medio);
  color: var(--text);
  text-overflow: ellipsis;
  cursor: pointer;
}
.inv-ambito select:focus { outline: none; box-shadow: none; }

/* La lista de productos: la cifra a la derecha es el dato por el que se
   entra aqui, asi que se lee como cifra y toma el color del estado.

   La fila baja de 64 a 58px: el minimo de la lista del sistema son 56
   -que es area de toque de sobra- y lo que sobraba eran 8px de relleno
   arriba y abajo. Acotado a .inv-lista-prod: la lista del sistema la
   comparten seis pantallas y ninguna se mueve por esto. */
/* La fila de con-marca sube a 68px en pantallas de dedo (57e), pensando
   en un marcador de 40. El de aqui mide 38 y el texto son dos renglones
   cortos: con 58 sobran 4px de aire arriba y abajo y el area de toque
   sigue muy por encima de 44. El selector repite .con-marca para GANARLE
   a aquella regla, que es mas especifica que una clase suelta. */
.inv-lista-prod .lista-toque,
.inv-lista-prod .lista-fila.con-marca .lista-toque {
  min-height: 58px;
  padding: var(--s2) var(--s3);
  gap: var(--s3);
}
.inv-lista-prod .lista-marca { width: 38px; height: 38px; }
.inv-lista-prod .lista-fila.con-marca + .lista-fila.con-marca::before {
  left: calc(var(--s3) + 38px + var(--s3));
}
.inv-lista-prod .lista-cifra { font-size: var(--fs-lg); line-height: 1.15; }
.inv-lista-prod .lista-cifra small {
  font-size: var(--fs-xs);
  color: var(--text-3);
}
.inv-lista-prod .lista-fin { gap: var(--s1); }
.inv-cant .inv-n { font-family: var(--font-num); font-variant-numeric: tabular-nums; }

@media (max-width: 767px) {

  /* Dos renglones: el buscador manda en el primero y los dos controles
     -abrir filtros y elegir ambito- se reparten el segundo a la mitad,
     que es la composicion de la referencia. */
  .inv-filtros-ex {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: var(--s2);
    align-items: center;
  }
  .inv-filtros-ex .inv-buscador { grid-column: 1 / -1; width: 100%; }

  .inv-filtros-ex .filtros-plegar-btn:not([hidden]) {
    grid-column: 1;
    width: 100%;
    min-width: 0;
    min-height: var(--tap);
    justify-content: center;
  }
  .inv-filtros-ex .inv-ambito { grid-column: 2; }
  /* Sin JavaScript el boton "Filtros" no llega a existir -lo crea
     app.js- y el grupo se ve entero. Ahi el ambito se queda con la fila
     completa, que es mejor que media fila vacia al lado. */
  .inv-filtros-ex .inv-ambito:first-child,
  html:not(.con-js) .inv-filtros-ex .inv-ambito { grid-column: 1 / -1; }

  /* El pliegue ocupa su propio renglon, DESPUES de los botones: se abre
     hacia abajo, que es de donde viene el dedo. */
  .inv-filtros-ex .inv-filtros-sec {
    grid-column: 1 / -1;
    order: 9;
    flex-direction: column;
    align-items: stretch;
    gap: var(--s3);
  }
  .inv-filtros-ex .inv-filtros-sec .campo { width: 100%; flex: none; }
  .inv-filtros-ex .inv-filtros-acciones {
    display: flex;
    gap: var(--s2);
  }
  .inv-filtros-ex .inv-filtros-acciones .btn { flex: 1 1 auto; min-height: var(--tap); }

  /* 16px en los controles que se tocan: por debajo de eso el navegador
     de iOS hace zoom al enfocarlos. */
  .inv-filtros-ex .inv-buscador input,
  .inv-ambito select { font-size: 16px; }

  /* El reparto en miniatura SE QUEDA en telefono: es el detalle que
     dice, sin leer un numero, que el inventario no esta repartido por
     igual. Ocupa 30px de alto entre la cifra y el estado, que ya
     estaban ahi. */
  .inv-chispa { height: 26px; }

  /* Los ultimos 30px del bloque de arriba, quitados de donde no se ven:
     el relleno de la tarjeta, el hueco bajo el rotulo y la altura de los
     dos encabezados de seccion. Ni una letra baja de 10px ni un area de
     toque de 44. */
  .inv-resumen { padding: 10px var(--s3); }
  .inv-resumen-cab { font-size: var(--fs-2xs); }
  .inv-resumen-fila { margin-top: 6px; }
  .inv-seccion-cab { gap: var(--s2); padding-bottom: var(--s2); }
  .inv-seccion-cab > .azulejo { width: 32px; height: 32px; }
  .inv-seccion-cab > .azulejo .icono { width: 16px; height: 16px; }
  .inv-seccion-txt h2 { font-size: var(--fs-base); }
  .inv-suc-txt { gap: 1px; }
  .inv-suc-barra { margin-top: var(--s0); }

  /* Ancho FIJO, no un porcentaje: la pastilla tiene que caber "Sin
     alertas de stock" entero -si se recorta deja de decir nada- y lo
     que sobra es para la cifra, que se lee sola. */
  .inv-estado { max-width: none; width: 176px; padding: var(--s2); gap: 6px; }
  .inv-estado-icono { width: 22px; height: 22px; }
  .inv-estado-icono .icono { width: 14px; height: 14px; }
  .inv-estado-txt strong { font-size: var(--fs-sm); }
  .inv-estado-txt small { font-size: var(--fs-3xs); }
  .inv-estado-flecha { margin-left: -2px; }
  .inv-resumen-apoyo { font-size: var(--fs-xs); }
  .inv-seccion-txt p { font-size: var(--fs-2xs); }

  /* ---------- La fila de sucursal en telefono ----------
     Tres renglones apretados -nombre, estado, barra- y la columna del
     importe con ancho FIJO. El nombre se lleva todo lo que sobra, que
     es lo que hace que "PAPELERIA JUAN ROMA NTE" quepa entero. */
  /* 10px de relleno lateral y 6 de hueco, no 12 y 8: son 8px que van
     enteros al nombre, y con ellos el mas largo de este negocio -189px
     de texto- cabe sin puntos suspensivos. El relleno de arriba y abajo
     no se toca: eso si es aire que se ve. */
  .inv-suc { padding: var(--s2) 10px; gap: 6px; min-height: 58px; }
  .inv-suc-azulejo { width: 34px; height: 34px; }
  .inv-suc-azulejo .icono { width: 17px; height: 17px; }

  /* EL NOMBRE SE LLEVA EL RENGLON ENTERO y "Principal" baja a compartir
     el segundo con el estado. Medido a 390: el nombre mas largo del
     negocio pide 152px y la caja de texto tiene 181; con la pastilla al
     lado hacian falta 202 y salia "PAPELERIA JUA...". El dato que se
     busca en esta lista es el nombre, asi que es lo ultimo que se
     recorta.

     Se recompone con display:contents -el mismo recurso que la fila de
     Categorias- para no duplicar marcado: los hijos de .inv-suc-nom
     pasan a ser casillas de la rejilla de .inv-suc-txt. */
  .inv-suc-txt {
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas:
      "nom  nom"
      "est  ppal"
      "barra barra";
    align-items: center;
  }
  .inv-suc-nom { display: contents; }
  .inv-suc-nom > span { grid-area: nom; font-size: 14px; font-weight: var(--peso-fuerte);
                        overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .inv-suc-estado { grid-area: est; }
  .inv-suc-ppal   { grid-area: ppal; }
  .inv-suc-barra  { grid-area: barra; }
  .inv-suc-ppal { padding: 1px 5px; }   /* el tamano ya es el de la base */
  .inv-suc-estado { font-size: var(--fs-2xs); }
  .inv-suc-estado i { width: 6px; height: 6px; }
  .inv-suc-barra { max-width: none; height: 4px; }
  .inv-suc-cifras .lista-cifra { font-size: var(--fs-base); }
  .inv-suc-cifras .lista-cifra small { font-size: var(--fs-3xs); }
  .inv-suc-costo { font-size: var(--fs-3xs); }
  .inv-suc .lista-flecha { margin-left: -2px; }

  /* La lista va a sangre dentro del panel: el aire lateral ya lo pone
     cada fila con su padding. */
  .panel-cuerpo.sin-espacio .inv-tactil { margin: 0; }
}

/* Por debajo de 380 el nombre largo y el importe ya no caben en la
   misma linea sin recortar el nombre a la mitad. Lo que cede es el
   TAMANO del importe, no el nombre: el nombre es lo que se busca. */
@media (max-width: 379px) {
  .inv-resumen-cifra strong { font-size: var(--fs-2xl); }
  .inv-suc-cifras .lista-cifra small { display: none; }
  .inv-suc-costo { font-size: var(--fs-3xs); }
  .inv-chispa { display: none; }
}


/* ---------------------------------------------------------------------
   66e. LA CABECERA DE ESTA PANTALLA
   ---------------------------------------------------------------------
   El sistema manda las acciones a un renglon propio cuando hay dos o
   mas (57b), y esa regla es buena: primero se lee QUE pantalla es y
   luego que se puede hacer en ella. En Existencias sale cara.

   Medido a 390: la cabecera ocupaba 196px -titulo de 24px, descripcion,
   y 44px de botones con 8 de margen sobre un renglon que ademas
   arrastraba el hueco de la rejilla- para presentar una pantalla cuyo
   contenido empieza justo despues. Con los botones al lado del titulo
   son 96px. Los 100px que se ahorran son, exactamente, las dos primeras
   filas de producto.

   Se hace como en Productos (62g): acotado con :has(.inv-resumen), sin
   tocar la regla general. Cabe en TODOS los telefonos porque lo que se
   ajusta por ancho es el tamano del titulo y el de los botones, no la
   composicion: a 360 el titulo mide 15px y a 412 vuelve a 20.
   --------------------------------------------------------------------- */

/* OLA 1: LA UNICA EXCEPCION DECLARADA, Y SE ESCRIBE EN EL VOCABULARIO
   DEL SISTEMA.

   Existencias es la unica pantalla con DOS acciones de texto que se
   quedan en la fila del titulo. El sistema manda dos o mas acciones a
   un renglon propio; aqui eso cuesta 50px de alto en la pantalla que
   mas filas necesita ver, asi que la composicion se conserva.

   Y no cabe con la escalera general: MEDIDO a 390, utiles 358. Los dos
   botones compactos piden 233 y los dos huecos 24; con un azulejo de 44
   quedan 57 para el titulo y "Existencias" a 20px mide 110. Es
   aritmetica, no gusto.

   Lo que cambia respecto a antes: ya no se reescriben ocho reglas con
   sus propios numeros. Se reescriben los TOKENS de la escalera, que es
   una linea por escalon, y todo lo demas -el azulejo, el icono de
   dentro, el titulo, la descripcion, el boton compacto- lo sigue
   leyendo la regla general del sistema. La excepcion se lee como una
   excepcion y no como una cuarta escalera.

   Es la misma clase de excepcion que el Tablero, que no lleva cabecera
   en telefono, y se declara igual: en un solo sitio y explicada. */
@media (max-width: 767px) {

  .contenido:has(.inv-resumen) {
    --cab-azulejo: var(--tap-sm);   /* 40 */
    --cab-icono:   21px;
    --cab-titulo:  var(--fs-lg);    /* 17 */
  }

  /* Las dos acciones se quedan en la fila del titulo, centradas contra
     el bloque entero -titulo y descripcion-, igual que el azulejo del
     otro lado. Esto es lo que el sistema no hace por defecto. */
  .contenido:has(.inv-resumen) .pagina-cab.con-azulejo > .pagina-cab-acciones {
    grid-row: 1 / span 2;
    grid-column: 3;
    align-self: center;
    width: auto;
    margin-top: 0;
    flex-wrap: nowrap;
    gap: var(--s2);
  }

  /* Y aprietan su relleno: son dos, no uno. El alto y la letra se
     quedan en los del boton compacto del sistema. */
  .contenido:has(.inv-resumen) .pagina-cab-acciones > .btn { padding-inline: var(--s2); }
}

/* De 412 para arriba sobra ancho: el titulo y el azulejo recuperan la
   medida del sistema sin que los botones se muevan de sitio. */
@media (min-width: 412px) and (max-width: 767px) {
  .contenido:has(.inv-resumen) {
    --cab-azulejo: var(--azulejo);   /* 44 */
    --cab-icono:   22px;
    --cab-titulo:  var(--fs-xl);     /* 20 */
  }
}

/* A 360 quedan 328 utiles y la fila sigue cabiendo, pero solo si ceden
   las dos piezas que pueden hacerlo sin perder nada: el titulo baja a
   15px -sigue siendo lo mas grande y lo mas negro de la cabecera- y los
   botones aprietan mas. La alternativa era mandarlos a un renglon
   propio, que en un telefono de 800 de alto cuesta una fila entera. */
@media (max-width: 379px) {
  .contenido:has(.inv-resumen) {
    --cab-azulejo: var(--azulejo-sm);  /* 36 */
    --cab-icono:   19px;
    --cab-titulo:  var(--fs-base);     /* 15 */
  }
  .contenido:has(.inv-resumen) .pagina-cab.con-azulejo { gap: 2px 6px; }
  .contenido:has(.inv-resumen) .pagina-cab.con-azulejo > .pagina-cab-acciones { gap: 6px; }
  .contenido:has(.inv-resumen) .pagina-cab-acciones > .btn { padding-inline: 6px; }
  .contenido:has(.inv-resumen) .pagina-cab-acciones > .btn .icono { width: 13px; height: 13px; }
}


/* ---------------------------------------------------------------------
   66f. TABLETA Y ESCRITORIO
   ---------------------------------------------------------------------
   Mas sitio, no otra pantalla. El resumen se estira y pone la cifra, el
   reparto y el estado en una sola linea; las sucursales siguen siendo
   filas -mas anchas- y la lista de productos cede el sitio a la matriz,
   que es donde comparar sucursales lado a lado tiene sentido.
   --------------------------------------------------------------------- */
@media (min-width: 768px) {
  .inv-resumen { padding: var(--s4) var(--s5); }
  .inv-resumen-fila { gap: var(--s5); margin-top: var(--s3); }
  .inv-resumen-cifra strong { font-size: var(--fs-3xl); }
  .inv-chispa { height: 38px; }
  .inv-chispa i { width: 7px; }
  .inv-estado { max-width: 320px; padding: var(--s2) var(--s3); }

  .inv-seccion-txt h2 { font-size: var(--fs-xl); }

  .inv-suc { padding: var(--s3) var(--s5); min-height: 74px; gap: var(--s4); }
  .inv-suc-azulejo { width: 44px; height: 44px; }
  .inv-suc-barra { max-width: 320px; }
  .inv-suc-cifras .lista-cifra { font-size: var(--fs-xl); }

  /* La barra vuelve a ser una fila: buscador elastico, filtros y ambito
     con su ancho. El boton "Filtros" lo esconde app.js en este ancho. */
  .inv-filtros-ex { align-items: center; }
  .inv-filtros-ex .inv-buscador { flex: 1 1 280px; min-width: 0; }
  .inv-filtros-ex .inv-filtros-sec { flex: 0 1 auto; align-items: center; }
  .inv-filtros-ex .inv-filtros-sec .campo { min-width: 190px; }
  .inv-filtros-ex .inv-ambito { flex: 0 1 260px; }
}

@media (min-width: 1024px) {
  /* Con sitio de sobra, el resumen y el reparto conviven a lo ancho.
     Sigue siendo la misma tarjeta y la misma lista: cambia el reparto
     del espacio, no la composicion. */
  .inv-resumen-cifra strong { font-size: var(--fs-4xl); }
  .inv-suc-cifras { min-width: 140px; }
}


/* ---------------------------------------------------------------------
   66g. TEMA OSCURO
   ---------------------------------------------------------------------
   No es el claro invertido. Lo que cambia:

     - Los fondos tenidos del estado bajan a los pares --tono-bg, que ya
       estan calculados para fondo oscuro. Un color-mix del 10% sobre
       #1e1e1a se lee como suciedad, no como color.
     - Las barras de sucursal usan el tono CLARO de cada color, el mismo
       que .prod-img-cN usa para su letra en oscuro: los tonos oscuros
       desaparecen sobre el carril.
   --------------------------------------------------------------------- */
:root[data-tema="oscuro"] .inv-estado-ok    { background: var(--ok-bg); }
:root[data-tema="oscuro"] .inv-estado-crit  { background: var(--crit-bg); }
:root[data-tema="oscuro"] .inv-estado-error { background: var(--error-bg); }
:root[data-tema="oscuro"] .inv-estado-ok:hover    { background: color-mix(in srgb, var(--ok) 22%, var(--surface)); }
:root[data-tema="oscuro"] .inv-estado-crit:hover  { background: color-mix(in srgb, var(--crit) 22%, var(--surface)); }
:root[data-tema="oscuro"] .inv-estado-error:hover { background: color-mix(in srgb, var(--error) 22%, var(--surface)); }

/* El circulo del icono se apoya en el plano de dentro del panel, no en
   el del panel: sobre el fondo tenido tiene que seguir leyendose. */
:root[data-tema="oscuro"] .inv-estado-icono { background: var(--hundido); }

/* OLA 2: las seis gemelas oscuras de la barra salen de aqui. La tinta
   la pone --cat-N, que ya se invierte en el bloque de tema oscuro. */

/* OLA 6: EL DISTINTIVO DE MARCA, EN UN SOLO SITIO.
   Tres pastillas dicen lo mismo -"esta es la principal", "este rol es
   el de marca"- y las tres se tenian igual en oscuro, con la misma
   mezcla al 22% escrita a mano en tres secciones distintas. Sobre
   transparent y no sobre --surface a proposito: estas van encima de
   un bloque que ya puede tener su propio color. */
:root[data-tema="oscuro"] .inv-suc-ppal,
:root[data-tema="oscuro"] .suc-ppal,
:root[data-tema="oscuro"] .usr-rol-info {
  background: color-mix(in srgb, var(--brand-600) 22%, transparent);
}
:root[data-tema="oscuro"] .inv-chispa { opacity: .7; }


/* =====================================================================
   67. ALERTAS   (Fase 7)
   =====================================================================
   UNA pantalla con DOS ESTADOS, no dos pantallas:

     67a  EL LIENZO Y EL AMBITO
     67b  EL ESTADO            .alr-estado
     67c  LAS TRES METRICAS    .alr-metricas   (solo sin alertas)
     67d  PESTANAS Y BUSCADOR  (solo con alertas)
     67e  LA LISTA             .alr-lista
     67f  ACCIONES Y NOTA
     67g  STOCK PARADO
     67h  TABLETA Y ESCRITORIO
     67i  TEMA OSCURO

   Todo cuelga de .alr-*, que solo existe en esta pantalla, salvo la
   cabecera, que se recompone acotada con :has(.alr-estado) igual que
   Productos y Existencias hacen con las suyas.

   EL COLOR ES EL DATO. Verde cuando no hay nada que hacer, ambar cuando
   algo cruzo su minimo o esta por caer, rojo SOLO cuando algo ya se
   agoto. Una pantalla que se llama "Alertas" y siempre esta roja no
   avisa de nada: es el fondo de pantalla.

   Se sustituye la seccion 52 entera (el reparto .alr-tactil /
   .alr-escritorio). Ahora hay UNA composicion para todos los anchos:
   dos marcados que hay que tocar por duplicado terminan
   desincronizados, y ya habia empezado a pasar -las metricas de
   escritorio contaban otra cosa que el titular de telefono-.
   ===================================================================== */


/* ---------------------------------------------------------------------
   67a. EL LIENZO Y EL AMBITO
   --------------------------------------------------------------------- */

/* Mismo lienzo azulado que Existencias y Categorias: sobre el hueso
   calido de --bg, una tarjeta blanca y una lista blanca se leen casi
   igual. Acotado con :has(.alr-estado), que solo existe aqui. */
.contenido:has(.alr-estado) {
  background: var(--lienzo);
}

/* El selector de sucursal vive en la cabecera, no suelto sobre el
   fondo: es el AMBITO de todas las cifras de la pantalla. Es el mismo
   <select> con el mismo GET; lo unico nuevo es el marco. */
.alr-ambito {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--s2);
  min-width: 0;
  min-height: var(--tap);
  margin: 0;
  padding: 0 var(--s3);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  box-shadow: var(--sh-1);
  transition: border-color var(--t-rapida) var(--ease),
              box-shadow var(--t-rapida) var(--ease);
}
.alr-ambito:hover { border-color: var(--border-2); }
.alr-ambito:focus-within { border-color: var(--brand-600); box-shadow: var(--ring); }
.alr-ambito-icono,
.alr-ambito-punta { flex: none; color: var(--text-3); pointer-events: none; }
.alr-ambito select {
  flex: 1;
  min-width: 0;
  width: 100%;
  min-height: 0;
  padding: 0;
  background: none;
  border: 0;
  box-shadow: none;
  appearance: none;
  -webkit-appearance: none;
  font-family: inherit;
  font-size: var(--fs-sm);
  font-weight: var(--peso-medio);
  color: var(--text);
  text-overflow: ellipsis;
  cursor: pointer;
}
.alr-ambito select:focus { outline: none; box-shadow: none; }

/* La cabecera de esta pantalla: el ambito al lado del titulo, no debajo
   de la descripcion. Es UN solo hijo de .pagina-cab-acciones, asi que la
   regla del sistema que manda las acciones a un renglon propio -la de
   dos o mas- no llega a aplicarse; lo unico que hace falta es que la
   pastilla no se estire. */
/* OLA 1: la escalera de la cabecera -azulejo, titulo, descripcion y el
   hueco entre los dos renglones- sale de aqui. La ponia esta pantalla
   por su cuenta, igual que otras seis, y ahora la pone :root una vez.
   Lo que se queda es lo UNICO que de verdad es de Alertas: cuanto puede
   medir el selector de ambito antes de empujar al titulo. */
@media (max-width: 767px) {
  .contenido:has(.alr-estado) .alr-ambito { max-width: 190px; }
}
@media (max-width: 379px) {
  .contenido:has(.alr-estado) .alr-ambito { max-width: 160px; padding-inline: var(--s2); }
  .contenido:has(.alr-estado) .alr-ambito select { font-size: var(--fs-xs); }
}


/* ---------------------------------------------------------------------
   67b. EL ESTADO
   ---------------------------------------------------------------------
   Lo primero y lo mas grande de la pantalla, porque es la pregunta con
   la que se entra: ¿tengo algo que atender?

   Tres piezas: el sello redondo con el icono, la frase, y una escena
   pequena a la derecha. La escena NO dice ningun dato -por eso es
   aria-hidden-: esta para que el estado se reconozca antes de leer.
   --------------------------------------------------------------------- */
.alr-estado {
  display: flex;
  align-items: center;
  gap: var(--s3);
  margin: 0 0 var(--s3);
  padding: var(--s3) var(--s4);
  border: 1px solid transparent;
  border-radius: var(--r-lg);
}

.alr-estado-icono {
  flex: none;
  display: grid;
  place-items: center;
  width: 46px;
  height: 46px;
  border-radius: var(--r-full);
  background: var(--surface);
}

.alr-estado-txt { flex: 1; min-width: 0; }
.alr-estado-txt h2 {
  margin: 0;
  font-size: var(--fs-xl);
  font-weight: var(--peso-max);
  line-height: var(--lh-tight);
  letter-spacing: var(--ls-titulo);
}
.alr-estado-txt p {
  margin: 4px 0 0;
  font-size: var(--fs-sm);
  color: var(--text-2);
  line-height: 1.35;
}

/* Los cuatro tonos. El fondo se mezcla contra la superficie, asi que el
   mismo valor sirve en los dos temas; el borde es el mismo color al 20%
   para que la tarjeta tenga filo sin llevar una linea de color solido. */
.alr-estado-ok    { background: color-mix(in srgb, var(--ok) 10%, var(--surface));
                    border-color: color-mix(in srgb, var(--ok) 22%, transparent); }
.alr-estado-warn  { background: color-mix(in srgb, var(--warn) 11%, var(--surface));
                    border-color: color-mix(in srgb, var(--warn) 24%, transparent); }
.alr-estado-crit  { background: color-mix(in srgb, var(--crit) 11%, var(--surface));
                    border-color: color-mix(in srgb, var(--crit) 24%, transparent); }
.alr-estado-error { background: color-mix(in srgb, var(--error) 11%, var(--surface));
                    border-color: color-mix(in srgb, var(--error) 24%, transparent); }
@supports not (background: color-mix(in srgb, red 10%, white)) {
  .alr-estado-ok    { background: var(--ok-bg); }
  .alr-estado-warn  { background: var(--warn-bg); }
  .alr-estado-crit  { background: var(--crit-bg); }
  .alr-estado-error { background: var(--error-bg); }
}

.alr-estado-ok    .alr-estado-icono { color: var(--ok); }
.alr-estado-warn  .alr-estado-icono { color: var(--warn); }
.alr-estado-crit  .alr-estado-icono { color: var(--crit); }
.alr-estado-error .alr-estado-icono { color: var(--error); }

/* El titular SOLO se tine cuando hay trabajo. "Todo en orden" en verde
   sobre un fondo verde es un bloque de color; en negro es una frase. */
.alr-estado-warn  .alr-estado-txt h2 { color: var(--warn); }
.alr-estado-crit  .alr-estado-txt h2 { color: var(--crit); }
.alr-estado-error .alr-estado-txt h2 { color: var(--error); }

/* ---------- La escena ---------- */
.alr-escena {
  flex: none;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s0);
  width: 88px;
}
.alr-escena svg { width: 72px; height: 56px; }
.alr-escena small {
  font-size: var(--fs-3xs);
  line-height: 1.2;
  text-align: center;
  color: var(--text-2);
}

.alr-escena-caja,
.alr-escena-linea,
.alr-escena-brillo {
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.alr-estado-ok    .alr-escena { color: var(--ok); }
.alr-estado-warn  .alr-escena { color: var(--warn); }
.alr-estado-crit  .alr-escena { color: var(--crit); }
.alr-estado-error .alr-escena { color: var(--error); }

.alr-escena-caja   { opacity: .45; }
.alr-escena-linea  { opacity: .35; }
.alr-escena-brillo { opacity: .5; stroke-width: 1.6; }
.alr-escena-sello  { fill: currentColor; opacity: .9; }
.alr-escena-check  { fill: none; stroke: var(--surface); stroke-width: 2.2;
                     stroke-linecap: round; stroke-linejoin: round; }
.alr-escena-barra  { fill: currentColor; opacity: .35; }
.alr-escena-barra-alta { opacity: .8; }


/* ---------------------------------------------------------------------
   67c. LAS TRES METRICAS
   ---------------------------------------------------------------------
   Solo existen en el estado sano. Cuando hay alertas, los recuentos de
   las pestanas dicen exactamente lo mismo dos bloques mas abajo, y un
   dato repetido no es el doble de claro: es la mitad.
   --------------------------------------------------------------------- */
.alr-metricas {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--s2);
  margin: 0 0 var(--s3);
  padding: 0;
  list-style: none;
}
.alr-metricas li {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: center;
  column-gap: var(--s2);
  padding: var(--s3) var(--s2);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-1);
}
.alr-metricas .azulejo { grid-row: 1 / span 2; }
.alr-metricas strong {
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-xl);
  font-weight: var(--peso-max);
  line-height: 1.1;
}
.alr-metricas span:not(.azulejo) {
  font-size: var(--fs-2xs);
  line-height: 1.25;
  color: var(--text-2);
}


/* ---------------------------------------------------------------------
   67d. PESTANAS Y BUSCADOR
   ---------------------------------------------------------------------
   Los dos filtran en el navegador sobre las filas ya pintadas y los dos
   nacen con hidden: los enciende alertas.js. Sin JavaScript no
   aparecen y se ve la lista entera.
   --------------------------------------------------------------------- */
/* OLA 2: la pestana es .tab y el contenedor es .carril. Lo que queda
   aqui es lo unico propio de Alertas: su separacion con la lista y que
   el grupo entero nace oculto -las pestanas solo aparecen si hay mas de
   un tipo de alerta, y eso lo decide alertas.js-.

   .alr-pest-btn SE CONSERVA en el marcado: es el selector con el que
   alertas.js encuentra los botones y conmuta .es-activa. Ya no pinta
   nada. */
.alr-pest { margin: 0 0 var(--s3); }
.alr-pest[hidden] { display: none; }

/* La activa es la unica con superficie: se lee como "estas aqui" sin
   necesidad de subrayados ni de un segundo color. */
/* OLA 2: el estado activo lo pone .tab, igual que en las otras cinco
   pantallas. Este era el unico del producto que se pintaba BLANCO, y es
   la razon de que la pestana en reposo de Movimientos -blanca, con
   borde- se leyera como la pestana elegida de Alertas. */

/* OLA 1: solo colocacion y el poder ocultarse. El campo, la lupa y el
   boton de limpiar los pone .buscador (57d); ese boton medía 32px aqui,
   por debajo del suelo tactil que el propio sistema declara. */
.alr-buscador { margin: 0 0 var(--s3); }
.alr-buscador[hidden] { display: none; }


/* ---------------------------------------------------------------------
   67e. LA LISTA
   ---------------------------------------------------------------------
   Cada alerta es una tarjeta con cuatro datos y un estado: quien es, su
   codigo, cuanto queda y cual es su minimo. Las dos cifras van juntas y
   ROTULADAS: un "3" suelto no dice si es mucho o poco; al lado de
   "mínimo 10", si.
   --------------------------------------------------------------------- */
.alr-lista {
  display: grid;
  gap: var(--s2);
  margin: 0 0 var(--s3);
  padding: 0;
  list-style: none;
}
.alr-fila {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-1);
  overflow: hidden;
}
.alr-fila[hidden] { display: none; }

.alr-toque {
  display: flex;
  align-items: center;
  gap: var(--s3);
  width: 100%;
  padding: var(--s3);
  background: none;
  border: 0;
  color: inherit;
  font: inherit;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.alr-toque:hover  { background: var(--hover); }
.alr-toque:active { background: var(--pressed); }
.alr-toque:focus-visible { outline: var(--foco); outline-offset: -2px; }

.alr-img { flex: none; display: inline-flex; }
.alr-cuerpo { flex: 1; min-width: 0; display: grid; gap: 3px; }

.alr-nom {
  font-size: var(--fs-base);
  font-weight: var(--peso-fuerte);
  line-height: var(--lh-tight);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.alr-meta {
  font-size: var(--fs-xs);
  color: var(--text-3);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.alr-cifras {
  display: flex;
  gap: var(--s4);
  margin-top: var(--s0);
}
.alr-cifra {
  display: grid;
  grid-template-columns: auto auto;
  align-items: baseline;
  column-gap: var(--s1);
}
.alr-cifra small {
  grid-column: 1 / -1;
  font-size: var(--fs-3xs);
  line-height: 1.2;
  color: var(--text-3);
}
.alr-cifra strong {
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-base);
  font-weight: var(--peso-max);
}
.alr-cifra em {
  font-style: normal;
  font-size: var(--fs-xs);
  color: var(--text-2);
}

/* El color va SOLO en la cifra que esta mal y en el chip. La fila se
   queda blanca: una bandeja entera de rojo no jerarquiza nada. */
.alr-n.agotado { color: var(--error); }
.alr-n.bajo    { color: var(--crit); }
.alr-n.porcaer { color: var(--warn); }

.alr-fin {
  flex: none;
  display: flex;
  align-items: center;
  gap: var(--s2);
}
.alr-fin .lista-flecha { color: var(--text-3); }

.alr-sin-coincidencias {
  margin: 0 0 var(--s3);
  padding: var(--s5) var(--s4);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  text-align: center;
  color: var(--text-2);
}
.alr-sin-coincidencias[hidden] { display: none; }


/* ---------------------------------------------------------------------
   67f. ACCIONES Y NOTA
   --------------------------------------------------------------------- */
.alr-acciones {
  display: grid;
  gap: var(--s2);
  margin: 0 0 var(--s3);
}
.alr-recalcular { margin: 0; }
.alr-recalcular .btn { min-height: var(--tap); }

.alr-info {
  display: flex;
  align-items: flex-start;
  gap: var(--s2);
  margin: 0 0 var(--s4);
  padding: var(--s3);
  background: color-mix(in srgb, var(--info) 7%, var(--surface));
  border-radius: var(--r-md);
  font-size: var(--fs-xs);
  line-height: 1.4;
  color: var(--text-2);
}
@supports not (background: color-mix(in srgb, red 7%, white)) {
  .alr-info { background: var(--info-bg); }
}
.alr-info .icono { flex: none; color: var(--info); margin-top: 1px; }

.alr-pie-nota {
  margin: 0 0 var(--s3);
  font-size: var(--fs-xs);
  line-height: 1.5;
  color: var(--text-2);
}
.alr-pie-nota:last-child { margin-bottom: 0; }


/* ---------------------------------------------------------------------
   67g. STOCK PARADO
   ---------------------------------------------------------------------
   No es una alerta -no hay nada que reponer- pero es dinero quieto, y
   esta pantalla es la unica que lo ensena. Se queda al final, con el
   peso de un apunte.
   --------------------------------------------------------------------- */
.alr-seccion { margin: 0 0 var(--s4); }
.alr-seccion-cab {
  display: flex;
  align-items: center;
  gap: var(--s2);
  padding: 0 var(--s1) var(--s2);
}
.alr-seccion-cab > .azulejo { flex: none; }
.alr-seccion-txt { flex: 1; min-width: 0; }
.alr-seccion-txt h2 {
  margin: 0;
  font-size: var(--fs-base);
  font-weight: var(--peso-max);
  line-height: var(--lh-tight);
}
.alr-seccion-txt p {
  margin: 2px 0 0;
  font-size: var(--fs-2xs);
  color: var(--text-2);
  line-height: 1.3;
}
.alr-seccion-num {
  flex: none;
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-sm);
  font-weight: var(--peso-fuerte);
  color: var(--text-2);
  white-space: nowrap;
}
.alr-parados {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-1);
  overflow: hidden;
}


/* ---------- Telefono: la densidad de la referencia ----------
   MEDIDO a 390: con el titular a 20px, "3 productos requieren atención"
   caia en TRES renglones y el heroe se iba a 150px. A 17 son dos
   renglones y 112px, que es la proporcion de la referencia. Las tres
   pestanas juntas piden 376px a tamano normal y solo hay 358: bajan a
   12.5px y caben las tres sin tener que deslizar para descubrir que
   existe la tercera. */
@media (max-width: 767px) {
  .alr-estado { padding: var(--s3); gap: var(--s2); }
  .alr-estado-icono { width: 40px; height: 40px; }
  .alr-estado-icono .icono { width: 22px; height: 22px; }
  .alr-estado-txt h2 { font-size: var(--fs-lg); }
  .alr-estado-txt p { font-size: var(--fs-xs); }
  .alr-escena { width: 72px; }
  .alr-escena svg { width: 60px; height: 46px; }
  .alr-escena small { font-size: var(--fs-3xs); }

  /* Densidad: a 360 las tres pestanas con su cuenta no caben con el
     relleno del sistema. Es lo unico que una variante puede cambiar. */
  .alr-pest .tab { font-size: var(--fs-xs); padding: 0 var(--s2); }
  .alr-pest .tab .icono { width: 14px; height: 14px; }

  /* En 390 cada metrica mide 110px. Con el azulejo de 32 y 8 de hueco,
     al rotulo le quedaban 62 y "Por agotarse pronto" caia en TRES
     renglones. Con 28 y 6 caben dos, que es lo que hace la referencia. */
  .alr-metricas li { padding: 10px var(--s2); column-gap: 6px; }
  .alr-metricas .azulejo { width: 28px; height: 28px; }
  .alr-metricas .azulejo .icono { width: 15px; height: 15px; }
  .alr-metricas strong { font-size: var(--fs-lg); }
  .alr-metricas span:not(.azulejo) { font-size: var(--fs-3xs); }

  .alr-toque { padding: var(--s2) var(--s3); gap: var(--s2); }
  .alr-cifras { gap: var(--s3); }
}

@media (max-width: 379px) {
  /* A 360 la escena es lo primero que sobra: el sello de color y la
     frase ya dicen el estado. */
  .alr-escena { display: none; }
  .alr-metricas li { padding: var(--s2); }
  .alr-metricas strong { font-size: var(--fs-lg); }
}


/* ---------------------------------------------------------------------
   67h. TABLETA Y ESCRITORIO
   ---------------------------------------------------------------------
   La misma composicion con mas aire. Lo unico que cambia de forma es el
   boton de recalcular: a pantalla completa sobre 1000px de ancho no es
   un boton, es una banda.
   --------------------------------------------------------------------- */
@media (min-width: 768px) {
  .alr-estado { padding: var(--s4) var(--s5); gap: var(--s4); }
  .alr-estado-icono { width: 56px; height: 56px; }
  .alr-estado-icono .icono { width: 30px; height: 30px; }
  .alr-estado-txt h2 { font-size: var(--fs-2xl); }
  .alr-estado-txt p { font-size: var(--fs-base); }
  .alr-escena { width: 118px; }
  .alr-escena svg { width: 96px; height: 74px; }
  .alr-escena small { font-size: var(--fs-xs); }

  .alr-metricas { gap: var(--s3); }
  .alr-metricas li { padding: var(--s4); column-gap: var(--s3); }
  .alr-metricas strong { font-size: var(--fs-2xl); }
  .alr-metricas span:not(.azulejo) { font-size: var(--fs-sm); }

  .alr-toque { padding: var(--s3) var(--s4); gap: var(--s4); }
  .alr-cifras { gap: var(--s5); }
  .alr-cifra strong { font-size: var(--fs-lg); }

  .alr-acciones {
    grid-auto-flow: column;
    justify-content: start;
    align-items: center;
  }
  .alr-acciones .btn { width: auto; }

  .alr-seccion-txt h2 { font-size: var(--fs-lg); }
  .alr-seccion-txt p { font-size: var(--fs-sm); }
}

@media (min-width: 1024px) {
  /* La lista deja de ocupar 1600px de ancho para tres datos: se queda en
     una medida legible y el aire sobrante se va a los lados. */
  .alr-lista,
  .alr-pest,
  .alr-buscador,
  .alr-sin-coincidencias { max-width: 900px; }
  .alr-estado { align-items: center; }
}


/* ---------------------------------------------------------------------
   67i. TEMA OSCURO
   ---------------------------------------------------------------------
   Los fondos tenidos bajan a los pares --tono-bg, que ya estan
   calculados para fondo oscuro: un color-mix del 10% sobre #1e1e1a se
   lee como suciedad, no como color. El sello del icono se apoya en un
   negro translucido, no en la superficie, para que se recorte contra el
   fondo tenido.
   --------------------------------------------------------------------- */
:root[data-tema="oscuro"] .alr-estado-ok    { background: var(--ok-bg); }
:root[data-tema="oscuro"] .alr-estado-warn  { background: var(--warn-bg); }
:root[data-tema="oscuro"] .alr-estado-crit  { background: var(--crit-bg); }
:root[data-tema="oscuro"] .alr-estado-error { background: var(--error-bg); }
:root[data-tema="oscuro"] .alr-estado-icono { background: var(--hundido); }
:root[data-tema="oscuro"] .alr-escena-check { stroke: var(--bg); }
:root[data-tema="oscuro"] .alr-info { background: var(--info-bg); }
/* OLA 2: el parche oscuro de la pestana activa desaparece con el blanco
   que lo hacia falta. --selected es translucido y funciona en los dos
   temas sin una segunda declaracion. */


/* =====================================================================
   68. MOVIMIENTOS   (Fase 8)
   =====================================================================
   El historial se lee de arriba abajo como una pregunta encadenada:
   que tipo, de cuando a cuando, de que sucursal, buscando que, cuantos
   son y cual es cada uno.

     68a  EL LIENZO Y LA CABECERA
     68b  LAS PESTANAS DE TIPO      .mov-pest
     68c  LA BARRA                  .mov-barra
     68d  LAS CUATRO CIFRAS         .mov-mets
     68e  LA FILA DE UN MOVIMIENTO  .mov-lista
     68f  LA PAGINACION             .mov-paginacion
     68g  TABLETA Y ESCRITORIO
     68h  TEMA OSCURO

   Sustituye a la seccion 53, que quedo vacia.

   POR QUE LA LISTA SIGUE SIENDO UNA <table>
   -----------------------------------------
   public/js/movimientos.js REESCRIBE el <tbody id="filas"> entero cada
   vez que se filtra, con su propia fila(). Una lista tactil pintada
   aparte por PHP se quedaria congelada en el primer resultado. Asi que
   la fila de telefono se construye SOBRE LAS MISMAS CELDAS con areas de
   rejilla: el JS repinta cuantas veces quiera y la composicion se rehace
   sola. Es la misma decision de la seccion 53, que funciono.

   EL COLOR ES EL DATO: verde entra, rojo sale, azul se corrige o se
   mueve entre sucursales. Va en el azulejo del icono y en la cantidad,
   no en la fila entera.
   ===================================================================== */


/* ---------------------------------------------------------------------
   68a. EL LIENZO Y LA CABECERA
   --------------------------------------------------------------------- */
.contenido:has(.mov-pest) {
  background: var(--lienzo);
}

/* OLA 1: la cabecera de esta pantalla ya no se declara. Una sola accion
   -"Exportar CSV"- que el sistema deja en la fila del titulo, con la
   escalera de :root y el boton compacto de 57b. Eran once declaraciones
   para llegar exactamente a donde llega ahora la regla general. */


/* ---------------------------------------------------------------------
   68b. LAS PESTANAS DE TIPO
   ---------------------------------------------------------------------
   Cinco enlaces de verdad: cuatro por m.type y uno por
   m.reference_type. Los cinco filtros ya existian en el servidor; lo
   unico que cambia es que se ven, en vez de estar en un desplegable
   dentro del pliegue.

   Se deslizan a lo ancho en vez de partirse en dos renglones: una
   segunda fila de pestanas se lee como dos grupos distintos.
   --------------------------------------------------------------------- */
/* OLA 2: pestana = .tab, contenedor = .carril. Queda la separacion con
   lo que viene debajo, que es lo unico de esta pantalla.

   Se van tres cosas que rompian la gramatica comun:
     - el filo de marca al 35% en color-mix, tercera version de un filo
       que ya tenia token (--selected-borde)
     - el :active con scale(.98), el unico gesto de pulsado del producto
       que encogia la pestana en vez de hundirla
     - el reposo BLANCO CON BORDE, que hacia que los cinco filtros de
       Movimientos parecieran cinco filtros puestos

   .mov-pest-btn se conserva en el marcado: es el gancho de
   movimientos.js. */
.mov-pest { margin: 0 0 var(--s3); }


/* ---------------------------------------------------------------------
   68c. LA BARRA
   ---------------------------------------------------------------------
   En un historial, "de cuando a cuando" es la pregunta principal, asi
   que el rango sale del pliegue y se queda a la vista junto a la
   sucursal. Detras de "Filtros" queda lo unico que sobra de la primera
   lectura: quien lo hizo.
   --------------------------------------------------------------------- */
.mov-barra {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--s2);
  margin: 0 0 var(--s3);
  align-items: center;
}

/* El rango: dos <input type="date"> dentro de un mismo marco, para que
   se lean como UN control. El formato de dentro lo decide el navegador
   y el idioma del sistema; esto no lo cambia. */
.mov-rango,
.mov-ambito {
  display: flex;
  align-items: center;
  gap: var(--s2);
  min-width: 0;
  min-height: var(--tap);
  padding: 0 var(--s3);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  box-shadow: var(--sh-1);
  transition: border-color var(--t-rapida) var(--ease),
              box-shadow var(--t-rapida) var(--ease);
}
.mov-rango:hover,
.mov-ambito:hover { border-color: var(--border-2); }
.mov-rango:focus-within,
.mov-ambito:focus-within { border-color: var(--brand-600); box-shadow: var(--ring); }

.mov-rango-icono,
.mov-ambito-icono,
.mov-ambito-punta { flex: none; color: var(--text-3); pointer-events: none; }
.mov-rango-sep { flex: none; color: var(--text-3); }

.mov-rango input[type="date"] {
  flex: 1 1 0;
  min-width: 0;
  width: auto;
  min-height: 0;
  padding: 0;
  background: none;
  border: 0;
  box-shadow: none;
  font-family: inherit;
  font-size: var(--fs-sm);
  color: var(--text);
}
.mov-rango input[type="date"]:focus { outline: none; box-shadow: none; }
/* Sin valor, el campo dice "aaaa" en gris y ocupa lo mismo: no hace
   falta un texto de ayuda que repita lo que el campo ya ensena. */
.mov-rango input[type="date"]:invalid { color: var(--text-3); }

.mov-ambito select {
  flex: 1;
  min-width: 0;
  width: 100%;
  min-height: 0;
  padding: 0;
  background: none;
  border: 0;
  box-shadow: none;
  appearance: none;
  -webkit-appearance: none;
  font-family: inherit;
  font-size: var(--fs-sm);
  font-weight: var(--peso-medio);
  color: var(--text);
  text-overflow: ellipsis;
  cursor: pointer;
}
.mov-ambito select:focus { outline: none; box-shadow: none; }

.mov-buscador { grid-column: 1; min-width: 0; }

/* El boton "Filtros" lo inyecta app.js. En telefono se queda en
   cuadrado con solo el icono -como en la referencia- pero su texto
   sigue en el HTML para el lector de pantalla: un boton sin nombre no
   se puede usar sin ver. */
.mov-barra .filtros-plegar-btn:not([hidden]) {
  grid-column: 2;
  width: var(--tap);
  min-width: var(--tap);
  min-height: var(--tap);
  padding: 0;
  justify-content: center;
}
.mov-barra .filtros-plegar-btn:not([hidden]) span {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}
/* Con filtros puestos el boton se tine: sin el numero al lado, el
   estado tiene que verse de otra forma. */
.mov-barra .filtros-plegar-btn.activo {
  border-color: color-mix(in srgb, var(--brand-600) 40%, transparent);
  background: var(--brand-50);
  color: var(--brand-600);
}

.mov-filtros-sec {
  grid-column: 1 / -1;
  order: 9;
  display: grid;
  gap: var(--s3);
}
.mov-filtros-sec[hidden] { display: none; }
.mov-filtros-sec .campo { margin-bottom: 0; }
.mov-filtros-acciones { display: flex; gap: var(--s2); }
.mov-filtros-acciones .btn { flex: 1 1 auto; min-height: var(--tap); }

@media (max-width: 767px) {
  /* De 380 arriba el rango y la sucursal comparten renglon, como en la
     referencia. MEDIDO a 390: el rango pide 238px y la pastilla de
     sucursal 118; con los 358 utiles caben justos. Por debajo se
     apilan, que es mejor que dos controles ilegibles. */
  .mov-rango { grid-column: 1; }
  .mov-ambito { grid-column: 2; max-width: 148px; }
  .mov-buscador { grid-column: 1; }
  .mov-barra .filtros-plegar-btn:not([hidden]) { grid-column: 2; }

  /* El icono de calendario se retira en telefono: cada <input type=date>
     ya trae el suyo -el del navegador- y con tres iconos en el mismo
     marco las fechas se quedaban en "09/01/2..." recortadas. Son 22px
     que van enteros a los dos campos. */
  .mov-rango-icono { display: none; }
  .mov-rango { gap: var(--s1); padding-inline: var(--s2); }
  .mov-rango input[type="date"],
  .mov-ambito select { font-size: var(--fs-xs); }
}

@media (max-width: 379px) {
  .mov-rango,
  .mov-ambito { grid-column: 1 / -1; }
  .mov-ambito { max-width: none; }
}


/* ---------------------------------------------------------------------
   68d. LAS CUATRO CIFRAS
   ---------------------------------------------------------------------
   Son las que movimientos_listar() YA calcula sobre el filtro completo
   -no sobre la pagina visible-: cuantos movimientos, cuantas unidades
   entraron, cuantas salieron y cuanto costo reponer. No hay una quinta
   consulta para adornar, y por eso no hay porcentajes de variacion: el
   sistema no guarda el periodo anterior de esta pantalla.

   Cuatro columnas tambien a 360, porque el bloque esta compuesto en
   VERTICAL -azulejo, cifra, rotulo-; es la misma leccion del trio de
   Categorias.
   --------------------------------------------------------------------- */
.mov-mets {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--s2);
  margin: 0 0 var(--s3);
  padding: 0;
  list-style: none;
}
.mov-met {
  display: grid;
  gap: var(--s0);
  padding: var(--s3) var(--s2);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-1);
}
.mov-met .azulejo { margin-bottom: var(--s1); }
.mov-met-valor {
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-xl);
  font-weight: var(--peso-max);
  line-height: 1.1;
  letter-spacing: -0.01em;
}
.mov-met-valor.es-mas   { color: var(--ok); }
.mov-met-valor.es-menos { color: var(--error); }
.mov-met-dinero { font-size: var(--fs-base); }
.mov-met-rot {
  font-size: var(--fs-2xs);
  line-height: 1.2;
  color: var(--text-2);
}

@media (max-width: 767px) {
  .mov-met { padding: var(--s2); }
  .mov-met .azulejo { width: 26px; height: 26px; margin-bottom: var(--s0); }
  .mov-met .azulejo .icono { width: 14px; height: 14px; }
  .mov-met-valor { font-size: var(--fs-lg); }
  .mov-met-dinero { font-size: var(--fs-xs); }
  .mov-met-rot { font-size: var(--fs-3xs); }
}


/* ---------------------------------------------------------------------
   68e. LA FILA DE UN MOVIMIENTO
   ---------------------------------------------------------------------
   Tres columnas en telefono: que paso y cuando, a que producto y por
   que, y cuanto. El salto de stock y el usuario se quedan para tableta
   y escritorio; en telefono viven en el detalle, que es a donde lleva
   la fila entera.
   --------------------------------------------------------------------- */
.mov-barra-sup {
  display: flex;
  /* flex-direction explicito: .prod-barra-sup se apila en columna en
     telefono (seccion 62e) y con align-items:center eso centraba el
     contador en mitad de la fila. */
  flex-direction: row;
  align-items: center;
  justify-content: flex-start;
  gap: var(--s3);
  margin-bottom: var(--s2);
  padding-inline: var(--s1);
}
.mov-barra-sup .prod-contador { margin: 0; }
.mov-panel { margin-bottom: var(--s4); }

/* La marca del tipo deja de ser una pastilla y se reparte en la celda:
   el azulejo redondo a la izquierda y la palabra encima de la fecha.
   display:contents sube sus dos hijos a la rejilla de la celda, asi que
   NO hay que tocar ui_movimiento_marca() ni la fila que pinta el JS. */
@media (max-width: 1023px) {
  .mov-lista tbody tr {
    grid-template-columns: auto minmax(0, 1fr) auto;
    grid-template-areas: "mov prod cant";
    gap: var(--s2);
    align-items: center;
    padding: var(--s2) var(--s3);
  }

  .mov-lista .m-mov {
    grid-area: mov;
    display: grid;
    grid-template-columns: auto auto;
    align-items: center;
    column-gap: 6px;
    row-gap: 0;
    min-width: 0;
  }
  .mov-lista .m-mov .mov-marca { display: contents; }
  .mov-lista .m-mov .mov-marca-icono {
    grid-column: 1;
    grid-row: 1 / span 3;
    width: 30px;
    height: 30px;
    border-radius: var(--r-full);
    background: color-mix(in srgb, currentColor 14%, var(--surface));
  }
  .mov-lista .m-mov .mov-marca-txt {
    grid-column: 2;
    font-size: var(--fs-sm);
    font-weight: var(--peso-fuerte);
    color: var(--text);
    white-space: nowrap;
  }
  .mov-lista .mov-fecha,
  .mov-lista .mov-hora {
    grid-column: 2;
    font-size: var(--fs-3xs);
    line-height: 1.25;
    color: var(--text-3);
    white-space: nowrap;
  }

  .mov-lista .m-prod {
    grid-area: prod;
    min-width: 0;
    display: grid;
    gap: var(--s0);
  }
  .mov-lista .m-prod .prod-img { width: 34px; height: 34px; }
  /* El nombre se recorta en vez de partirse: dos renglones de nombre
     estiran la fila y desalinean la cifra de la derecha. */
  .mov-lista .m-prod .prod-linea-nom {
    font-size: var(--fs-sm);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .mov-lista .m-prod .prod-linea-sub { font-size: var(--fs-3xs); }
  .mov-lista .m-prod .mov-motivo {
    display: block;
    font-size: var(--fs-3xs);
    color: var(--text-3);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  .mov-lista .m-cant {
    grid-area: cant;
    display: grid;
    justify-items: end;
    gap: 0;
    text-align: right;
    min-width: 0;
  }
  .mov-lista .m-cant .mov-cant {
    font-family: var(--font-num);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-lg);
    font-weight: var(--peso-max);
    line-height: 1.15;
    white-space: nowrap;
  }
  .mov-lista .m-cant .mov-cant-u {
    font-size: var(--fs-3xs);
    color: var(--text-3);
    white-space: nowrap;
  }

  /* En telefono no caben y no hacen falta: el detalle los tiene. */
  .mov-lista .m-stock,
  .mov-lista .m-quien { display: none; }
}

/* La cantidad toma el color de la direccion. Va aqui y no en la fila
   entera: una lista con la mitad de los renglones en rojo no dice
   nada. */
.mov-cant.es-mas   { color: var(--ok); }
.mov-cant.es-menos { color: var(--error); }
.mov-cant-u { display: block; }

/* De 768 a 1023 hay sitio para el resto del movimiento sin apretar:
   el usuario y el salto de stock vuelven, en un segundo renglon. */
@media (min-width: 768px) and (max-width: 1023px) {
  .mov-lista tbody tr {
    /* La tercera columna la dimensionaba la cantidad -55px- y el salto
       de stock, que va debajo, se quedaba en "200" con el resto
       recortado. Con un minimo de 76 caben los dos. */
    grid-template-columns: auto minmax(0, 1fr) minmax(76px, auto);
    grid-template-areas:
      "mov prod  cant"
      "mov quien stock";
    row-gap: var(--s1);
    padding: var(--s3) var(--s4);
  }
  /* min-width: max-content y nowrap. Con justify-self:end el <td> se
     encoge a su contenido, y el contenido es un flex que se dejaba
     partir: la celda se quedaba en 12px y "200 → 700" salia por el
     borde del panel. */
  .mov-lista .m-stock {
    display: block;
    grid-area: stock;
    justify-self: end;
    min-width: max-content;
    white-space: nowrap;
  }
  /* OLA 3: el tamano del salto lo ponia la seccion 27g junto al resto
     de la composicion fantasma que se retiro. Vive aqui, que es donde
     el salto de stock se ensena de verdad. */
  .mov-lista .m-stock .mov-salto { font-size: var(--fs-sm); }
  /* FASE D - EL MISMO TROPIEZO QUE LA CELDA DEL TIPO, EN TABLETA.
     Este <td> vive dentro de una fila en rejilla, y un td en rejilla no
     recibe el ancho de su area a menos que se le diga: se queda en su
     contenido y, con overflow:hidden + nowrap, en lo que quepa.

     MEDIDO a 820: la pista del medio mide 453px y la celda de al lado
     -el producto- las ocupa enteras; esta se quedaba en 82 para 346 de
     contenido, asi que "JUAN RODRIGUEZ · 15/09/26 14:53 · PAPELERIA
     JUAN ROMA NTE" se leia como "JUAN ROD...". Treinta filas iguales.
     justify-self:stretch NO lo arregla -probado-: el td no es un item
     de rejilla normal. width:100% si. Despues: 453 de 453, cero
     recortes, y la fila sigue midiendo lo mismo.

     Solo alcanza a 768-1023. En telefono esta celda no se pinta
     (display:none mas arriba), asi que ahi no hay nada que cambiar. */
  .mov-lista .m-quien {
    display: block;
    grid-area: quien;
    justify-self: start;
    width: 100%;
    text-align: left;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .mov-lista .m-quien .mov-quien,
  .mov-lista .m-quien .mov-cuando { display: inline; font-size: var(--fs-xs); color: var(--text-3); }
  .mov-lista .m-quien .mov-quien::after { content: " · "; }
  .mov-lista .m-prod .prod-img { width: 40px; height: 40px; }
  .mov-lista .m-prod .prod-linea-nom { font-size: var(--fs-base); }
}


/* ---------------------------------------------------------------------
   68f. LA PAGINACION
   ---------------------------------------------------------------------
   Los datos son los de siempre -pagina y paginas de la misma consulta-;
   lo que cambia es como se presentan: numeros que se pueden saltar en
   vez de avanzar de uno en uno. Sin JavaScript se queda la del
   servidor, que es el componente compartido y no se toca.
   --------------------------------------------------------------------- */
.mov-paginacion {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: var(--s1);
  padding: var(--s3) var(--s2);
}
.mov-paginacion:empty { display: none; }

.pag-btn {
  display: inline-grid;
  place-items: center;
  min-width: 36px;
  height: 36px;
  padding: 0 var(--s2);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  color: var(--text-2);
  font: inherit;
  font-size: var(--fs-sm);
  font-variant-numeric: tabular-nums;
  cursor: pointer;
  transition: background var(--t-rapida) var(--ease),
              border-color var(--t-rapida) var(--ease);
}
.pag-btn:hover:not(:disabled) { border-color: var(--border-2); color: var(--text); }
.pag-btn:active:not(:disabled) { background: var(--pressed); }
.pag-btn:focus-visible { outline: var(--foco); outline-offset: 2px; }
.pag-btn:disabled { opacity: .45; cursor: default; }
.pag-btn.es-actual {
  background: var(--btn-primario-bg);
  border-color: var(--btn-primario-bg);
  color: var(--btn-primario-texto);
  font-weight: var(--peso-fuerte);
}
.pag-puntos { padding: 0 2px; color: var(--text-3); }

/* Los minimos de toque de un dedo: 36 esta bien para un raton. */
@media (pointer: coarse) {
  .pag-btn { min-width: var(--tap); height: var(--tap); }
}


/* ---------------------------------------------------------------------
   68g. TABLETA Y ESCRITORIO
   ---------------------------------------------------------------------
   De 1024 arriba vuelve la TABLA de cinco columnas: ahi comparar
   movimientos entre si -y leer el salto de stock de una tirada- es
   exactamente para lo que sirve. Lo unico que cambia respecto a antes
   es que la cantidad tiene columna propia y la marca del tipo lleva su
   fecha debajo.
   --------------------------------------------------------------------- */
@media (min-width: 768px) {
  /* FASE D - LOS CUATRO FILTROS NO COMPARTIAN NINGUN BORDE.
     Medido a 1440: cuatro bloques de tres alturas distintas -fechas 44,
     sucursal 44, buscador 52 y el de usuario 71, que es el unico con
     rotulo visible encima- centrados entre si. Con align-items:center
     cada uno quedaba a una altura: arriba en 274, 274, 270 y 260. Una
     fila de filtros que no comparte ni el borde de arriba ni el de
     abajo se lee desordenada aunque cada pieza este bien.

     Se alinean por ABAJO, que es la convencion de una fila de formulario
     con rotulos: el rotulo flota encima de su control y los controles
     apoyan todos en la misma linea. Medido despues: los cuatro cierran
     en 331. Una linea, y no se toca ni una altura ni un rotulo. */
  .mov-barra {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
  }
  .mov-rango { flex: 0 1 300px; }
  .mov-ambito { flex: 0 1 240px; }
  .mov-buscador { flex: 1 1 260px; }
  .mov-filtros-sec {
    flex: 0 1 auto;
    grid-auto-flow: column;
    align-items: end;
    gap: var(--s3);
  }
  .mov-filtros-sec .campo { min-width: 180px; }
  .mov-filtros-acciones .btn { flex: 0 0 auto; }

  .mov-mets { gap: var(--s3); }
  .mov-met { padding: var(--s4); }
  .mov-met-valor { font-size: var(--fs-2xl); }
  .mov-met-dinero { font-size: var(--fs-xl); }
  .mov-met-rot { font-size: var(--fs-sm); }
}

@media (min-width: 1024px) {
  /* La celda del tipo: la marca de siempre y, debajo, cuando paso.
     Rejilla de dos columnas para que el dia y la hora compartan
     renglon: en una columna flex cada <span> se lleva su propia linea y
     la fila crecia a tres renglones. */
  .mov-lista .m-mov {
    display: grid;
    grid-template-columns: auto auto;
    justify-content: start;
    column-gap: var(--s1);
    row-gap: 3px;
  }
  .mov-lista .m-mov .mov-marca { grid-column: 1 / -1; }
  .mov-lista .mov-fecha { grid-column: 1; }
  .mov-lista .mov-hora  { grid-column: 2; }
  .mov-lista .mov-fecha,
  .mov-lista .mov-hora {
    font-size: var(--fs-xs);
    color: var(--text-3);
    white-space: nowrap;
  }

  .mov-lista .m-prod .mov-motivo {
    display: block;
    margin-top: var(--s0);
    font-size: var(--fs-xs);
    color: var(--text-3);
  }

  .mov-lista .m-cant .mov-cant {
    font-family: var(--font-num);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-lg);
    font-weight: var(--peso-max);
  }
  .mov-lista .m-cant .mov-cant-u { font-size: var(--fs-xs); color: var(--text-3); }

  /* FASE D - LA CELDA DEL TIPO SE HABIA SALIDO DE LA TABLA.
     ---------------------------------------------------------------
     Un <td> al que se le cambia el display a grid DEJA de participar
     en el reparto de columnas de la tabla: ya no es una celda para el
     algoritmo, es una caja suelta que vive dentro de una fila. Dos
     consecuencias, las dos medidas a 1440 con el menu abierto:

       · la caja de la celda se quedaba en 24px con 126 de contenido.
         No se veia roto porque el desbordamiento es visible y el
         navegador pinta fuera de la caja, pero cualquier cosa que
         dependa de la caja -el fondo al pasar el raton, el relleno,
         el borde- se aplicaba a 24px.
       · y el "width: 14%" de esta misma linea NO HACIA NADA, porque
         un porcentaje sobre esa caja no resuelve contra la tabla. El
         ancho de la columna lo decidia el <th> el solo, sin contar
         con el contenido que tiene debajo: una marca mas larga se
         habria metido en la columna del producto.

     El arreglo es "width: 100%": la celda vuelve a llenar la columna
     que le toca -medido: 138px de caja para 138 de columna, igual que
     las otras cuatro- y el reparto pasa a contar con su contenido.
     Es la misma leccion que ya estaba escrita para las filas en
     rejilla: un td en rejilla necesita que alguien le diga el ancho.

     Las otras tres celdas SI son table-cell y su porcentaje si manda,
     asi que se quedan como estaban. */
  .mov-lista .m-mov   { width: 100%; }
  .mov-lista .m-cant  { width: 12%; }
  .mov-lista .m-stock { width: 14%; }
  .mov-lista .m-quien { width: 18%; }

  /* FASE D - Y UN SUELO EN PIXELES PARA LAS TRES DE LA DERECHA.
     Los porcentajes de arriba son una PREFERENCIA: la tabla es de
     reparto automatico y ahi el navegador atiende primero al contenido
     que no cabe de otra forma. La celda del producto lleva debajo el
     motivo entero -"Transferencia #6 PAPELERIA JUAN ROMA NTE ->
     Papeleria Gustavo A. Madero"- y con ese texto pidiendo sitio, a
     1024 se quedaba con 502px y dejaba a las tres siguientes en 87, 95
     y 94, o sea el 9% donde el porcentaje pedia 18.

     MEDIDO a 1024: "Quien y cuando" en 94px partia el nombre, la fecha,
     la hora y la sucursal en seis renglones y la fila entera media
     154px. Con el suelo puesto, 98. Un tercio menos de alto por fila,
     que en una lista de 51 movimientos son casi tres mil pixeles.

     De 1200 arriba no cambia nada: alli las tres ya miden mas que su
     minimo (134, 156 y 200 a 1440), asi que estas reglas no llegan a
     aplicarse nunca. Es un suelo, no una medida. */
  .mov-lista .m-cant  { min-width: 92px; }
  .mov-lista .m-stock { min-width: 110px; }
  .mov-lista .m-quien { min-width: 150px; }
}


/* ---------------------------------------------------------------------
   68h. TEMA OSCURO
   ---------------------------------------------------------------------
   El azulejo del tipo se mezcla contra la superficie del panel, asi que
   en oscuro sale oscuro por si solo. Lo que si cambia es la pestana
   activa: --brand-50 en oscuro es un azul muy oscuro y el texto de
   marca es claro, asi que el par funciona al reves de lo que parece y
   hay que darle mas cuerpo.
   --------------------------------------------------------------------- */
/* OLA 2: el parche oscuro de la pestana activa desaparece. --selected y
   --selected-borde ya traen su version oscura y la traen para las seis
   pantallas; esta regla era el tercer filo de marca del archivo (45%). */
:root[data-tema="oscuro"] .mov-barra .filtros-plegar-btn.activo {
  background: color-mix(in srgb, var(--brand-600) 22%, var(--surface));
}
:root[data-tema="oscuro"] .mov-lista .m-mov .mov-marca-icono {
  background: color-mix(in srgb, currentColor 22%, var(--surface));
}


/* =====================================================================
   69. TRANSFERENCIAS   (Fase 9)
   =====================================================================
   Una transferencia deja de ser un formulario y pasa a ser un proceso
   con principio, medio y final:

     69a  EL LIENZO Y LA CABECERA
     69b  LO QUE ES UNA TRANSFERENCIA   .trf-intro
     69c  LOS CINCO PASOS               .trf-pasos
     69d  LOS CAMPOS DE UN PASO
     69e  EL BOTON
     69f  LA LAMINA DE "EN CURSO"       .trf-curso
     69g  LA FICHA DE RESULTADO         .trf-hecha
     69h  TABLETA Y ESCRITORIO
     69i  TEMA OSCURO

   NADA de esto valida: el servidor vuelve a comprobar sucursales,
   autorizacion, stock y CSRF. El numerito que se pone en verde es una
   ayuda para llenar, no un permiso.

   UNA TRANSFERENCIA = UN PRODUCTO, porque branch_transfers guarda
   product_id y quantity en la propia fila: el modelo no tiene lineas.
   El paso 3 lo dice con palabras en vez de insinuar lo contrario.
   ===================================================================== */


/* ---------------------------------------------------------------------
   69a. EL LIENZO Y LA CABECERA
   --------------------------------------------------------------------- */
.contenido:has(.trf-pasos) {
  background: var(--lienzo);
}

/* OLA 1: cabecera desde el sistema. Una accion ("Historial"), escalera
   de :root, boton compacto de 57b. */

/* Los dos formularios existen solo para agrupar: sus campos viven
   dentro de los pasos, enganchados con form=. */
.trf-oculto { display: none; }


/* ---------------------------------------------------------------------
   69b. LO QUE ES UNA TRANSFERENCIA
   ---------------------------------------------------------------------
   Explica la operacion antes de pedir nada: de donde sale, a donde
   entra y que rastro deja. Es la unica parte de la pantalla con fondo
   de color, y por eso el resto puede quedarse en blanco.
   --------------------------------------------------------------------- */
.trf-intro {
  display: flex;
  align-items: center;
  gap: var(--s3);
  margin: 0 0 var(--s4);
  padding: var(--s3);
  background: color-mix(in srgb, var(--brand-600) 8%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--brand-600) 18%, transparent);
  border-radius: var(--r-lg);
}
@supports not (background: color-mix(in srgb, red 8%, white)) {
  .trf-intro { background: var(--brand-50); }
}
.trf-intro-icono {
  flex: none;
  display: grid;
  place-items: center;
  width: 42px;
  height: 42px;
  border-radius: var(--r-md);
  background: var(--surface);
  color: var(--brand-600);
}
.trf-intro-txt { flex: 1; min-width: 0; }
.trf-intro-txt h2 {
  margin: 0;
  font-size: var(--fs-base);
  font-weight: var(--peso-max);
  line-height: var(--lh-tight);
}
.trf-intro-txt p {
  margin: 3px 0 0;
  font-size: var(--fs-2xs);
  line-height: 1.35;
  color: var(--text-2);
}

/* Dos cajas y un arco. La de destino se ilumina en verde: es la que
   recibe. */
.trf-escena { flex: none; width: 96px; }
.trf-escena svg { width: 96px; height: 60px; }
/* A 360 la escena cede 20px para que "Nueva transferencia" quepa en un
   renglon: el titulo dice mas que el dibujo. */
@media (max-width: 379px) {
  .trf-escena { width: 76px; }
  .trf-escena svg { width: 76px; height: 48px; }
  .trf-intro { gap: var(--s2); }
}
.trf-escena-caja path {
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linejoin: round;
}
.trf-escena-caja-a { color: var(--brand-600); opacity: .55; }
.trf-escena-caja-b { color: var(--ok); opacity: .75; }
.trf-escena-arco,
.trf-escena-punta {
  fill: none;
  stroke: var(--brand-600);
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
  opacity: .5;
}
.trf-escena-arco { stroke-dasharray: 4 5; }


/* ---------------------------------------------------------------------
   69c. LOS CINCO PASOS
   ---------------------------------------------------------------------
   Una lista ordenada de verdad -<ol>-, con la linea que une los
   circulos dibujada por el propio paso. El estado se lee de un vistazo:

     pendiente  circulo hueco con su numero
     activo     circulo de marca con halo: es lo que toca ahora
     resuelto   circulo lleno con palomita y la linea de debajo tenida
   --------------------------------------------------------------------- */
.trf-pasos {
  list-style: none;
  margin: 0 0 var(--s4);
  padding: 0;
  counter-reset: trf;
}

.trf-paso {
  position: relative;
  display: grid;
  grid-template-columns: 30px minmax(0, 1fr);
  column-gap: var(--s3);
  padding-bottom: var(--s4);
}
.trf-paso:last-child { padding-bottom: 0; }

/* La linea que baja al siguiente paso. Nace gris y se tine cuando el
   paso queda resuelto: eso es el progreso. */
.trf-paso:not(:last-child)::before {
  content: "";
  position: absolute;
  left: 14px;
  top: 32px;
  bottom: 4px;
  width: 2px;
  background: var(--border);
  transition: background var(--t-media) var(--ease);
}
.trf-paso.es-resuelto:not(:last-child)::before {
  background: color-mix(in srgb, var(--brand-600) 45%, var(--border));
}

.trf-num {
  position: relative;
  z-index: 1;
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  border-radius: var(--r-full);
  background: var(--surface);
  border: 2px solid var(--border);
  color: var(--text-3);
  font-size: var(--fs-sm);
  font-weight: var(--peso-max);
  font-variant-numeric: tabular-nums;
  transition: background var(--t-media) var(--ease),
              border-color var(--t-media) var(--ease),
              color var(--t-media) var(--ease),
              box-shadow var(--t-media) var(--ease);
}
.trf-num b { grid-area: 1 / 1; }
.trf-num-ok { grid-area: 1 / 1; opacity: 0; transform: scale(.6); }

/* Activo: el que toca ahora. Halo, no relleno: todavia no esta hecho. */
.trf-paso.es-activo .trf-num {
  border-color: var(--brand-600);
  color: var(--brand-600);
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--brand-600) 14%, transparent);
}

/* Resuelto: lleno y con palomita. */
.trf-paso.es-resuelto .trf-num {
  background: var(--btn-primario-bg);
  border-color: var(--btn-primario-bg);
  color: var(--btn-primario-texto);
  box-shadow: none;
}
.trf-paso.es-resuelto .trf-num b { opacity: 0; }
.trf-paso.es-resuelto .trf-num-ok {
  opacity: 1;
  transform: scale(1);
  transition: opacity var(--t-media) var(--ease), transform var(--t-media) var(--ease-suave);
}

.trf-paso-cuerpo { min-width: 0; padding-top: 3px; }
.trf-paso-titulo {
  margin: 0;
  font-size: var(--fs-base);
  font-weight: var(--peso-max);
  line-height: var(--lh-tight);
}
.trf-paso-titulo span { font-weight: var(--peso-medio); color: var(--text-2); }
.trf-paso-sub {
  margin: 2px 0 var(--s2);
  font-size: var(--fs-2xs);
  color: var(--text-2);
  line-height: 1.3;
}
.trf-ayuda {
  margin: var(--s1) 0 0;
  font-size: var(--fs-2xs);
  color: var(--text-3);
}


/* ---------------------------------------------------------------------
   69d. LOS CAMPOS DE UN PASO
   ---------------------------------------------------------------------
   El mismo marco para todos: icono a la izquierda, control elastico y
   punta a la derecha cuando despliega. 48px de alto, que es lo que pide
   un dedo, y 16px de letra para que el navegador no haga zoom.
   --------------------------------------------------------------------- */
.trf-campo {
  display: flex;
  align-items: center;
  gap: var(--s2);
  min-width: 0;
  min-height: 48px;
  padding: 0 var(--s3);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  box-shadow: var(--sh-1);
  transition: border-color var(--t-rapida) var(--ease),
              box-shadow var(--t-rapida) var(--ease);
}
.trf-campo:hover { border-color: var(--border-2); }
.trf-campo:focus-within { border-color: var(--brand-600); box-shadow: var(--ring); }
.trf-campo-icono,
.trf-campo-punta { flex: none; color: var(--text-3); pointer-events: none; }

.trf-campo select,
.trf-campo input {
  flex: 1;
  min-width: 0;
  width: 100%;
  min-height: 0;
  padding: 0;
  background: none;
  border: 0;
  box-shadow: none;
  appearance: none;
  -webkit-appearance: none;
  font-family: inherit;
  font-size: var(--fs-base);
  color: var(--text);
  text-overflow: ellipsis;
}
.trf-campo select { cursor: pointer; }
.trf-campo select:focus,
.trf-campo input:focus { outline: none; box-shadow: none; }
.trf-campo:has(select:disabled) { opacity: .6; }

/* El aviso de que los grises no tienen existencia. */
.trf-nota-info {
  display: flex;
  align-items: flex-start;
  gap: var(--s2);
  margin: var(--s2) 0 0;
  padding: var(--s2) var(--s3);
  background: color-mix(in srgb, var(--info) 7%, var(--surface));
  border-radius: var(--r-md);
  font-size: var(--fs-2xs);
  line-height: 1.4;
  color: var(--text-2);
}
@supports not (background: color-mix(in srgb, red 7%, white)) {
  .trf-nota-info { background: var(--info-bg); }
}
.trf-nota-info .icono { flex: none; color: var(--info); margin-top: 1px; }

/* ---------- La cantidad ---------- */
.trf-cantidad {
  display: flex;
  align-items: center;
  gap: var(--s2);
  min-height: 48px;
  padding: 0 var(--s2) 0 var(--s2);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  box-shadow: var(--sh-1);
}
.trf-cantidad:focus-within { border-color: var(--brand-600); box-shadow: var(--ring); }

.trf-paso-btn {
  flex: none;
  display: grid;
  place-items: center;
  width: 36px;
  height: 36px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  color: var(--text);
  cursor: pointer;
  transition: background var(--t-rapida) var(--ease), transform var(--t-rapida) var(--ease);
}
.trf-paso-btn:hover { background: var(--hover); border-color: var(--border-2); }
.trf-paso-btn:active { transform: scale(var(--pulsa-lg)); }
.trf-paso-btn:focus-visible { outline: var(--foco); outline-offset: 2px; }

.trf-cantidad input {
  flex: 1;
  min-width: 0;
  width: 100%;
  min-height: 0;
  padding: 0;
  background: none;
  border: 0;
  box-shadow: none;
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-xl);
  font-weight: var(--peso-max);
  color: var(--text);
  text-align: center;
  -moz-appearance: textfield;
}
.trf-cantidad input::-webkit-outer-spin-button,
.trf-cantidad input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.trf-cantidad input:focus { outline: none; box-shadow: none; }

.trf-disponible {
  flex: none;
  display: grid;
  justify-items: end;
  gap: 0;
  padding-right: var(--s1);
  text-align: right;
}
.trf-disponible small { font-size: var(--fs-3xs); color: var(--text-3); line-height: 1.2; }
.trf-disponible strong {
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-sm);
  color: var(--text-2);
  white-space: nowrap;
}
.trf-disponible.es-vacio strong { color: var(--text-3); }


/* ---------------------------------------------------------------------
   69e. EL BOTON
   ---------------------------------------------------------------------
   Nace ACTIVO: sin JavaScript el servidor es quien valida, y un boton
   apagado que nadie puede encender es una pantalla rota. El JS lo apaga
   hasta que los cuatro pasos obligatorios estan resueltos.
   --------------------------------------------------------------------- */
.trf-cta { margin: 0 0 var(--s5); }
.trf-cta .btn { width: 100%; }
.trf-cta .btn:disabled { opacity: .5; cursor: not-allowed; box-shadow: none; }


/* ---------------------------------------------------------------------
   69f. LA LAMINA DE "EN CURSO"
   ---------------------------------------------------------------------
   Tapa la pantalla mientras el POST viaja. Los cuatro apuntes son las
   cosas que la transaccion HACE, en su orden; el reloj que los enciende
   es del navegador, porque el servidor responde al final. El pie lo
   dice: es UNA operacion.

   La barra llega al 92% y se queda: fingir el 100% antes de saber el
   resultado seria mentir. El 100% es la pagina siguiente.
   --------------------------------------------------------------------- */
.trf-curso {
  position: fixed;
  inset: 0;
  z-index: var(--z-modal);
  display: grid;
  place-items: center;
  padding: var(--s4);
  background: color-mix(in srgb, var(--bg) 92%, transparent);
  backdrop-filter: blur(3px);
  animation: trf-entra var(--t-media) var(--ease-suave);
}
.trf-curso[hidden] { display: none; }
body.trf-enviando { overflow: hidden; }

@keyframes trf-entra { from { opacity: 0; } to { opacity: 1; } }

.trf-curso-caja {
  width: min(420px, 100%);
  padding: var(--s5) var(--s4);
  text-align: center;
}
.trf-curso-escena { margin: 0 auto var(--s4); width: min(260px, 100%); }
.trf-curso-escena svg { width: 100%; height: auto; }

.trf-ruta-linea {
  fill: none;
  stroke: var(--brand-600);
  stroke-width: 2;
  stroke-dasharray: 5 6;
  opacity: .45;
  animation: trf-ruta 1.4s linear infinite;
}
@keyframes trf-ruta { to { stroke-dashoffset: -22; } }

.trf-pin path { fill: currentColor; }
.trf-pin-ojo { fill: var(--surface); }
.trf-pin-a { color: var(--brand-600); }
.trf-pin-b { color: var(--ok); }

/* El camion avanza, bota un poco y deja estela. Es la unica animacion
   larga del sistema y solo existe mientras se espera. */
.trf-camion { animation: trf-avanza 2.4s var(--ease) infinite; }
@keyframes trf-avanza {
  0%   { transform: translateX(-14px); }
  55%  { transform: translateX(10px); }
  100% { transform: translateX(-14px); }
}
.trf-caja-camion { fill: color-mix(in srgb, var(--brand-600) 22%, var(--surface)); stroke: var(--brand-600); stroke-width: 2; }
.trf-cabina { fill: var(--surface); stroke: var(--brand-600); stroke-width: 2; stroke-linejoin: round; }
.trf-rueda { fill: var(--text); opacity: .8; }
.trf-estela {
  fill: none;
  stroke: var(--brand-600);
  stroke-width: 3;
  stroke-linecap: round;
  opacity: .35;
  animation: trf-estela .9s linear infinite;
}
@keyframes trf-estela {
  0%   { opacity: 0; transform: translateX(6px); }
  40%  { opacity: .45; }
  100% { opacity: 0; transform: translateX(-10px); }
}

.trf-curso-titulo {
  margin: 0 0 var(--s2);
  font-size: var(--fs-xl);
  font-weight: var(--peso-max);
  letter-spacing: var(--ls-titulo);
}
.trf-curso-sub {
  margin: 0 0 var(--s4);
  font-size: var(--fs-sm);
  line-height: 1.5;
  color: var(--text-2);
}
.trf-curso-sub strong { color: var(--text); }

.trf-barra {
  height: 8px;
  margin: 0 auto var(--s4);
  background: var(--surface-2);
  border-radius: var(--r-full);
  overflow: hidden;
}
.trf-barra i {
  display: block;
  height: 100%;
  width: var(--trf-pct, 8%);
  border-radius: var(--r-full);
  background: var(--btn-primario-bg);
  transition: width var(--t-lenta) var(--ease);
}

.trf-checks {
  list-style: none;
  margin: 0 0 var(--s4);
  padding: var(--s3) var(--s4);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  text-align: left;
}
.trf-checks li {
  display: flex;
  align-items: center;
  gap: var(--s3);
  padding: var(--s2) 0;
  font-size: var(--fs-sm);
  color: var(--text-3);
  transition: color var(--t-media) var(--ease);
}
.trf-check-marca {
  flex: none;
  width: 20px;
  height: 20px;
  border: 2px solid var(--border-2);
  border-radius: var(--r-full);
  transition: background var(--t-media) var(--ease),
              border-color var(--t-media) var(--ease);
}
.trf-checks .es-hecho { color: var(--text); font-weight: var(--peso-medio); }
.trf-check-marca { position: relative; }
.trf-checks .es-hecho .trf-check-marca {
  background: var(--ok);
  border-color: var(--ok);
}
/* La palomita: dos lados de un borde girados 45 grados. Sin SVG y sin
   degradados -el primer intento los uso y dibujaba un acento, no una
   palomita-. */
.trf-checks .es-hecho .trf-check-marca::after {
  content: "";
  position: absolute;
  left: 5px;
  top: 1px;
  width: 5px;
  height: 9px;
  border: solid var(--surface);
  border-width: 0 2.2px 2.2px 0;
  transform: rotate(45deg);
}
.trf-checks .es-ahora .trf-check-marca::after { content: none; }
/* El que esta pasando ahora: azul y latiendo. */
.trf-checks .es-ahora { color: var(--brand-600); }
.trf-checks .es-ahora .trf-check-marca {
  background: var(--btn-primario-bg);
  border-color: var(--btn-primario-bg);
  background-image: none;
  animation: trf-late 1.1s var(--ease) infinite;
}
@keyframes trf-late {
  0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--brand-600) 40%, transparent); }
  50%      { box-shadow: 0 0 0 6px color-mix(in srgb, var(--brand-600) 0%, transparent); }
}

.trf-curso-pie {
  margin: 0;
  font-size: var(--fs-xs);
  line-height: 1.5;
  color: var(--text-3);
}

/* Con movimiento reducido, nada se mueve: el estado se entiende igual
   por el color y por los apuntes. */
@media (prefers-reduced-motion: reduce) {
  .trf-camion,
  .trf-estela,
  .trf-ruta-linea,
  .trf-checks .es-ahora .trf-check-marca { animation: none; }
  .trf-curso { animation: none; }
}


/* ---------------------------------------------------------------------
   69g. LA FICHA DE RESULTADO
   ---------------------------------------------------------------------
   Se pinta al volver del POST con ?hecha=ID, con la fila que el
   historial ya traia. Si el POST falla no se pinta nada de esto: queda
   el aviso rojo del sistema y el formulario vuelve lleno con lo que se
   intento.
   --------------------------------------------------------------------- */
.trf-hecha {
  margin: 0 0 var(--s4);
  padding: var(--s5) var(--s4);
  background: var(--surface);
  border: 1px solid color-mix(in srgb, var(--ok) 28%, transparent);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-2);
  text-align: center;
  animation: trf-sube var(--t-lenta) var(--ease-suave);
}
@keyframes trf-sube {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: none; }
}

.trf-hecha-sello {
  display: grid;
  place-items: center;
  width: 68px;
  height: 68px;
  margin: 0 auto var(--s3);
  border-radius: var(--r-full);
  background: var(--ok);
  color: var(--text-inv);
  animation: trf-sello var(--t-lenta) var(--ease-suave);
}
@keyframes trf-sello {
  0%   { transform: scale(.4); opacity: 0; }
  60%  { transform: scale(1.08); opacity: 1; }
  100% { transform: scale(1); }
}
.trf-hecha h2 {
  margin: 0 0 var(--s1);
  font-size: var(--fs-xl);
  font-weight: var(--peso-max);
  letter-spacing: var(--ls-titulo);
}
.trf-hecha-sub {
  margin: 0 0 var(--s4);
  font-size: var(--fs-sm);
  color: var(--text-2);
}

.trf-hecha-ficha {
  margin: 0 0 var(--s4);
  padding: var(--s3);
  background: var(--surface-2);
  border-radius: var(--r-md);
  text-align: left;
}
.trf-hecha-prod {
  display: flex;
  align-items: center;
  gap: var(--s3);
  padding-bottom: var(--s3);
  margin-bottom: var(--s3);
  border-bottom: 1px solid var(--border-sutil);
}
.trf-hecha-prod > span { display: grid; min-width: 0; }
.trf-hecha-prod strong {
  font-size: var(--fs-base);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.trf-hecha-prod small { font-size: var(--fs-xs); color: var(--text-3); }

.trf-hecha-datos { display: grid; gap: var(--s2); margin: 0; }
.trf-hecha-datos > div {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s3);
}
.trf-hecha-datos dt { font-size: var(--fs-sm); color: var(--text-2); }
.trf-hecha-datos dd {
  margin: 0;
  font-size: var(--fs-sm);
  font-weight: var(--peso-fuerte);
  text-align: right;
  min-width: 0;
}

.trf-hecha-acciones { display: grid; gap: var(--s2); }

/* La fila recien creada se senala un momento en el historial. */
.tabla-transferencias tbody tr.es-nueva {
  background: color-mix(in srgb, var(--ok) 8%, transparent);
}

@media (prefers-reduced-motion: reduce) {
  .trf-hecha, .trf-hecha-sello { animation: none; }
}


/* ---------------------------------------------------------------------
   69h. TABLETA Y ESCRITORIO
   ---------------------------------------------------------------------
   La misma composicion con mas aire. De 1024 arriba el proceso y el
   historial conviven a dos columnas, que es lo que ya hacia .trf-cols.
   --------------------------------------------------------------------- */
@media (min-width: 768px) {
  .trf-intro { padding: var(--s4); gap: var(--s4); }
  .trf-intro-icono { width: 48px; height: 48px; }
  .trf-intro-txt h2 { font-size: var(--fs-lg); }
  .trf-intro-txt p { font-size: var(--fs-sm); }

  .trf-paso { grid-template-columns: 34px minmax(0, 1fr); }
  .trf-paso:not(:last-child)::before { left: 16px; }
  .trf-num { width: 34px; height: 34px; font-size: var(--fs-base); }
  .trf-paso-titulo { font-size: var(--fs-lg); }
  .trf-paso-sub { font-size: var(--fs-sm); }

  .trf-cta .btn { width: auto; min-width: 280px; }

  .trf-hecha { padding: var(--s6) var(--s5); }
  .trf-hecha h2 { font-size: var(--fs-2xl); }
  .trf-hecha-acciones {
    grid-auto-flow: column;
    justify-content: center;
  }
  .trf-hecha-acciones .btn { width: auto; min-width: 220px; }
}

@media (min-width: 1024px) {
  /* El proceso pide 360 de ancho para que el paso no se estreche; el
     resto es para la tabla del historial, que tiene cinco columnas. */
  .trf-cols { grid-template-columns: minmax(360px, 1fr) minmax(0, 1.6fr); }
  .trf-hecha-ficha { max-width: 520px; margin-inline: auto; }
}


/* ---------------------------------------------------------------------
   69i. TEMA OSCURO
   --------------------------------------------------------------------- */
:root[data-tema="oscuro"] .trf-intro {
  background: color-mix(in srgb, var(--brand-600) 16%, var(--surface));
}
:root[data-tema="oscuro"] .trf-intro-icono { background: var(--hundido); }
:root[data-tema="oscuro"] .trf-nota-info { background: var(--info-bg); }
:root[data-tema="oscuro"] .trf-curso {
  background: color-mix(in srgb, var(--bg) 88%, transparent);
}
/* El sello verde del final: en oscuro --ok es claro y --text-inv casi
   negro, asi que el par ya funciona; lo que hay que corregir es la
   palomita de los apuntes, que se dibuja con el color de superficie. */
:root[data-tema="oscuro"] .trf-checks .es-hecho .trf-check-marca::after {
  border-color: var(--bg);
}
:root[data-tema="oscuro"] .trf-pin-ojo { fill: var(--bg); }
:root[data-tema="oscuro"] .trf-cabina { fill: var(--surface-2); }
:root[data-tema="oscuro"] .trf-rueda { fill: var(--text-2); }


/* =====================================================================
   70. TRANSFERENCIAS DE VARIOS PRODUCTOS   (Fase 9B)
   =====================================================================
   La migracion sql/009 separo la cabecera de las lineas, asi que el
   paso 3 deja de ser "un producto" y pasa a ser una LISTA:

     70a  ELEGIR Y AGREGAR      .trf-elegir / .trf-agregar
     70b  LA LINEA              .trf-lineas / .trf-linea
     70c  EL RESUMEN            .trf-resumen
     70d  EL RESULTADO Y EL HISTORIAL
     70e  EL VIAJE, CON MAS PRESENCIA
     70f  TABLETA Y ESCRITORIO
     70g  TEMA OSCURO

   Todo lo demas de la seccion 69 sigue igual: los cinco pasos, sus
   estados, la lamina de "en curso" y la ficha del final.
   ===================================================================== */


/* ---------------------------------------------------------------------
   70a. ELEGIR Y AGREGAR
   ---------------------------------------------------------------------
   El selector y su cantidad no envian nada por su cuenta cuando el
   agregador esta encendido: son "que producto y cuanto meto". El boton
   nace oculto y lo ensena transferencias.js, porque sin JavaScript no
   hay donde guardar la linea y el formulario vuelve a ser de un
   producto.
   --------------------------------------------------------------------- */
.trf-elegir { display: grid; gap: var(--s2); }
.trf-agregar {
  width: 100%;
  min-height: var(--tap);
  justify-content: center;
}
.trf-agregar[hidden] { display: none; }


/* ---------------------------------------------------------------------
   70b. LA LINEA
   ---------------------------------------------------------------------
   Quien es, cuanto hay en el origen y cuanto se lleva. La cantidad se
   edita AQUI y en ningun otro sitio: el paso 4 solo la resume.
   --------------------------------------------------------------------- */
.trf-lineas {
  list-style: none;
  display: grid;
  gap: var(--s2);
  margin: var(--s2) 0 0;
  padding: 0;
}
.trf-lineas:empty { display: none; }

.trf-linea {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--s2);
  padding: var(--s2);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  box-shadow: var(--sh-1);
  animation: trf-linea-entra var(--t-media) var(--ease-suave);
}
@keyframes trf-linea-entra {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: none; }
}
/* Cada pieza en su casilla: el aspa a la derecha del nombre, no debajo.
   Sin declararlas, la cantidad -que ocupa las dos columnas- empujaba el
   aspa a un tercer renglon propio. */
.trf-linea .prod-img { grid-column: 1; grid-row: 1; width: 34px; height: 34px; }
.trf-linea .trf-linea-txt  { grid-column: 2; grid-row: 1; }
.trf-linea .trf-quitar     { grid-column: 3; grid-row: 1; }
.trf-linea .trf-linea-cant { grid-column: 1 / -1; grid-row: 2; }

.trf-linea-txt { min-width: 0; display: grid; gap: 1px; }
.trf-linea-txt strong {
  font-size: var(--fs-sm);
  font-weight: var(--peso-fuerte);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.trf-linea-txt small { font-size: var(--fs-3xs); color: var(--text-3); }

/* La cantidad de la linea baja al segundo renglon en telefono: con el
   nombre al lado se quedaba en 40px y no cabia ni el numero. */
.trf-linea-cant {
  grid-column: 1 / -1;
  display: flex;
  align-items: center;
  gap: var(--s2);
}
.trf-linea-cant input {
  flex: 1;
  min-width: 0;
  min-height: 38px;
  padding: 0 var(--s2);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-base);
  font-weight: var(--peso-max);
  color: var(--text);
  text-align: center;
  -moz-appearance: textfield;
}
.trf-linea-cant input::-webkit-outer-spin-button,
.trf-linea-cant input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.trf-linea-cant input:focus {
  outline: none;
  border-color: var(--brand-600);
  box-shadow: var(--ring);
}
.trf-linea-cant .trf-paso-btn { width: 38px; height: 38px; }

.trf-quitar {
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  background: none;
  border: 0;
  border-radius: var(--r-full);
  color: var(--text-3);
  cursor: pointer;
  transition: background var(--t-rapida) var(--ease), color var(--t-rapida) var(--ease);
}
.trf-quitar:hover { background: var(--error-bg); color: var(--error); }
.trf-quitar:focus-visible { outline: 2px solid var(--error); outline-offset: 2px; }


/* ---------------------------------------------------------------------
   70c. EL RESUMEN
   ---------------------------------------------------------------------
   El paso 4 no repite controles: ENSENA lo que se va a mover. Si la
   cantidad se editara tambien aqui, habria dos sitios para el mismo
   dato y ninguno seria el bueno.
   --------------------------------------------------------------------- */
.trf-resumen {
  padding: var(--s3);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  box-shadow: var(--sh-1);
}
.trf-resumen-vacio {
  margin: 0;
  font-size: var(--fs-sm);
  color: var(--text-3);
}
.trf-resumen-vacio[hidden] { display: none; }

.trf-resumen-datos {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2) var(--s4);
  padding-bottom: var(--s2);
  margin-bottom: var(--s2);
  border-bottom: 1px solid var(--border-sutil);
}
.trf-resumen-datos[hidden] { display: none; }
.trf-resumen-datos strong {
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-lg);
  font-weight: var(--peso-max);
}
.trf-resumen-datos small { font-size: var(--fs-xs); color: var(--text-2); margin-left: 3px; }

.trf-resumen-lista {
  list-style: none;
  display: grid;
  gap: var(--s1);
  margin: 0;
  padding: 0;
}
.trf-resumen-lista:empty { display: none; }
.trf-resumen-lista li {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s3);
  font-size: var(--fs-sm);
  color: var(--text-2);
}
.trf-resumen-lista span {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.trf-resumen-lista b {
  flex: none;
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  color: var(--text);
}


/* ---------------------------------------------------------------------
   70d. EL RESULTADO Y EL HISTORIAL
   ---------------------------------------------------------------------
   La ficha del final lista TODAS las lineas, no la primera. En el
   historial, una transferencia de varios productos se despliega en su
   propia fila: el detalle no necesita pantalla nueva ni otra consulta,
   porque las lineas ya vienen con la lista.
   --------------------------------------------------------------------- */
.trf-hecha-lineas {
  list-style: none;
  display: grid;
  gap: var(--s2);
  margin: 0 0 var(--s3);
  padding: 0 0 var(--s3);
  border-bottom: 1px solid var(--border-sutil);
}
.trf-hecha-lineas li {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--s3);
  text-align: left;
}
.trf-hecha-nom { min-width: 0; display: grid; }
.trf-hecha-nom strong {
  font-size: var(--fs-sm);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.trf-hecha-nom small { font-size: var(--fs-xs); color: var(--text-3); }
.trf-hecha-cant {
  flex: none;
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-base);
  font-weight: var(--peso-max);
  white-space: nowrap;
}
.trf-hecha-cant small { font-size: var(--fs-xs); font-weight: 400; color: var(--text-2); }

.trf-detalle summary {
  display: flex;
  align-items: baseline;
  gap: var(--s2);
  min-height: 32px;
  cursor: pointer;
  list-style: none;
}
.trf-detalle summary::-webkit-details-marker { display: none; }
.trf-detalle summary::after {
  content: "";
  width: 7px;
  height: 7px;
  border-right: 2px solid var(--text-3);
  border-bottom: 2px solid var(--text-3);
  transform: rotate(45deg) translateY(-2px);
  transition: transform var(--t-rapida) var(--ease);
}
.trf-detalle[open] summary::after { transform: rotate(-135deg) translateY(-2px); }
.trf-detalle summary strong { font-size: var(--fs-sm); }
.trf-detalle summary span { font-size: var(--fs-xs); color: var(--text-3); }
.trf-detalle ul {
  list-style: none;
  display: grid;
  gap: 3px;
  margin: var(--s2) 0 0;
  padding: 0 0 0 var(--s2);
  border-left: 2px solid var(--border);
}
.trf-detalle li {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s3);
  font-size: var(--fs-xs);
  color: var(--text-2);
}
.trf-detalle li b {
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  color: var(--text);
  white-space: nowrap;
}


/* ---------------------------------------------------------------------
   70e. EL VIAJE, CON MAS PRESENCIA
   ---------------------------------------------------------------------
   La primera version dejaba el camion meciendose en el sitio: se movia
   28px sobre un lienzo de 260 y la ruta apenas se notaba. Ahora se lee
   de un vistazo ORIGEN -> CAMION -> DESTINO:

     - el camion RECORRE el camino de punta a punta, 2.6s, y entra y
       sale con una atenuacion para que el bucle no de un salto;
     - la ruta punteada corre hacia el destino, con mas trazo y mas
       contraste;
     - el arco se desplaza un poco, como si el aire lo empujara;
     - la estela se enciende y se apaga detras del camion;
     - los dos pines laten muy despacio, cada uno a su tiempo, y siguen
       siendo lo que ancla las dos puntas del viaje.

   Sigue sin librerias: SVG y CSS. Con prefers-reduced-motion no se
   mueve nada y el estado se entiende igual.
   --------------------------------------------------------------------- */

/* La ruta: mas gruesa, mas visible y corriendo hacia el destino. */
.trf-ruta-linea {
  stroke-width: 2.6;
  stroke-dasharray: 6 8;
  opacity: .6;
  animation: trf-ruta 1.1s linear infinite, trf-arco 3.2s var(--ease) infinite;
}
@keyframes trf-ruta { to { stroke-dashoffset: -28; } }
/* El arco respira: sube y baja un pelo, nada mas. */
@keyframes trf-arco {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-3px); }
}

/* El camion recorre el camino. Empieza pegado al pin de origen y sale
   por el de destino; la opacidad cierra el bucle sin costura. */
.trf-camion {
  animation: trf-avanza 2.6s cubic-bezier(.45, .05, .35, 1) infinite;
}
/* El camion viaja de verdad: sale de debajo del pin de origen y se va
   por el de destino. Se dibuja al 72% para que le quepa el camino -a
   tamano completo ocupaba un tercio del lienzo y solo podia mecerse- y
   el translateY lo devuelve al suelo despues de encoger. El pequeno
   vaiven vertical es el bache de la carretera, nada mas. */
@keyframes trf-avanza {
  0%   { transform: translate(-4px, 22px)   scale(.72); opacity: 0; }
  9%   { transform: translate(10px, 22px)   scale(.72); opacity: 1; }
  33%  { transform: translate(48px, 20.5px) scale(.72); opacity: 1; }
  58%  { transform: translate(84px, 22px)   scale(.72); opacity: 1; }
  80%  { transform: translate(118px, 20.5px) scale(.72); opacity: 1; }
  93%  { transform: translate(146px, 22px)  scale(.72); opacity: 1; }
  100% { transform: translate(158px, 22px)  scale(.72); opacity: 0; }
}

/* La estela: se enciende cuando el camion va lanzado y se apaga al
   final, para no dejar rayas sueltas en el bucle. */
.trf-estela {
  stroke-width: 3.4;
  animation: trf-estela 2.6s var(--ease) infinite;
}
@keyframes trf-estela {
  0%, 100% { opacity: 0; transform: translateX(0); }
  18%      { opacity: .5; transform: translateX(4px); }
  60%      { opacity: .35; transform: translateX(10px); }
  85%      { opacity: 0; transform: translateX(14px); }
}

/* Los pines marcan las dos puntas: el de origen late al arrancar y el
   de destino cuando el camion se acerca. */
.trf-pin { animation: trf-pin 2.6s var(--ease) infinite; }
.trf-pin-b { animation-delay: 1.1s; }
@keyframes trf-pin {
  0%, 55%, 100% { transform: translateY(0); }
  8%            { transform: translateY(-3px); }
  16%           { transform: translateY(0); }
}

@media (prefers-reduced-motion: reduce) {
  .trf-pin,
  .trf-linea { animation: none; }
}


/* ---------------------------------------------------------------------
   70f. TABLETA Y ESCRITORIO
   ---------------------------------------------------------------------
   AQUI VIVIA UN FALLO REAL, Y ESTA ES SU HISTORIA.

   Estas dos filas -elegir producto, y cada linea agregada- se tumbaban
   con @media (min-width: 768px). La regla daba por hecho que "pantalla
   ancha" significa "esta fila es ancha", y en esta pantalla eso es
   FALSO: desde 1024 la pagina se parte en dos columnas (.trf-cols,
   minmax(300px,1fr) para el formulario y minmax(0,1.6fr) para el
   historial), asi que cuanto MAS ancha es la ventana, mas contenido
   cabe al lado y la columna del formulario se queda igual de estrecha.

   Lo medido a 1280 con el menu lateral abierto:

       .trf-elegir           380.45px de ancho total
       columnas resueltas    0px | 190px | 121.15px
                             ^^^^
                             el <select> del producto

   Cero pixeles. El campo existia, tenia sus siete opciones y ninguna
   deshabilitada, pero no se podia ver ni pulsar: minmax(0, 1fr) puede
   encogerse hasta cero, y las otras dos pistas -la cantidad, fija en
   190px, y el boton- se lo comian entero. En telefono no pasaba nunca
   porque por debajo de 768 la fila se apila.

   EL ARREGLO NO ES OTRO PUNTO DE CORTE, ES CAMBIAR DE INSTRUMENTO.
   Lo que decide si estas dos filas caben tumbadas no es el ancho de la
   PANTALLA sino el suyo propio, asi que se pregunta por el suyo propio.
   El contenedor es .trf-paso-cuerpo -la columna del paso, ya con
   min-width:0- y las dos filas consultan su ancho.

   Y si un navegador no entiende @container, no pasa nada grave: se
   queda la version apilada, que es justamente la que SI funciona.
   --------------------------------------------------------------------- */
.trf-paso-cuerpo { container-type: inline-size; container-name: trfpaso; }

/* 620 = 190 (cantidad) + 180 (boton) + 16 (dos huecos) + 234 de sitio
   real para el nombre del producto, que es lo que mide el mas largo de
   la lista con su SKU y su existencia. Por debajo de eso, apilado: mas
   vale un selector ancho debajo que uno de 130px al lado. */
@container trfpaso (min-width: 620px) {
  .trf-elegir { grid-template-columns: minmax(0, 1fr) auto auto; align-items: center; }
  .trf-elegir .trf-cantidad { width: 190px; }
  .trf-agregar { width: auto; }
}

/* 420 = 34 (miniatura) + ~170 (la cantidad con sus dos botones) + 32
   (el aspa) + 24 de huecos + 160 para el nombre y su SKU. */
@container trfpaso (min-width: 420px) {
  .trf-linea {
    grid-template-columns: auto minmax(0, 1fr) auto auto;
    padding: var(--s2) var(--s3);
  }
  .trf-linea .trf-linea-cant { grid-column: 3; grid-row: 1; }
  .trf-linea .trf-quitar     { grid-column: 4; grid-row: 1; }
  .trf-linea-cant input { width: 74px; flex: none; }
}

@media (min-width: 768px) {
  .trf-hecha-lineas { max-width: 520px; margin-inline: auto; }
}


/* ---------------------------------------------------------------------
   70g. TEMA OSCURO
   --------------------------------------------------------------------- */
:root[data-tema="oscuro"] .trf-linea-cant input { background: var(--surface-3); }
:root[data-tema="oscuro"] .trf-quitar:hover { background: var(--error-bg); }
:root[data-tema="oscuro"] .trf-ruta-linea { opacity: .75; }


/* =====================================================================
   71. CAJA   (Fase 10)
   =====================================================================
   La pantalla es UN SISTEMA DE ESTADOS y se lee igual en los dos:

     CERRADA   ambar · "Lista para comenzar" · abrir turno
     ABIERTA   verde · cuanto hay que tener en el cajon · operar y cerrar

     71a  EL LIENZO Y LA CABECERA
     71b  EL ESTADO                .cj-estado
     71c  ABRIR CAJA               .caja-abrir
     71d  LOS ATAJOS DEL FONDO     .caja-atajos
     71e  TABLETA Y ESCRITORIO
     71f  TEMA OSCURO

   AMBAR Y NO ROJO cuando esta cerrada: una caja sin turno no es un
   error, es el punto de partida del dia. El rojo se reserva para lo que
   de verdad va mal -un faltante al cerrar-, que ya lo pinta el corte.

   La barra ui_estado_caja() deja de usarse EN ESTA PANTALLA: decia lo
   mismo que el bloque de estado un renglon mas abajo. El componente no
   se toca, lo siguen usando el tablero y el punto de venta.
   ===================================================================== */


/* ---------------------------------------------------------------------
   71a. EL LIENZO Y LA CABECERA
   --------------------------------------------------------------------- */
.contenido:has(.cj-estado) {
  background: var(--lienzo);
}

/* OLA 1: cabecera desde el sistema. El protagonismo de Caja no esta en
   su cabecera -esta en .cj-estado y en la cifra del fondo, que no se
   tocan-, asi que aqui no queda nada propio que declarar. */


/* ---------------------------------------------------------------------
   71b. EL ESTADO
   ---------------------------------------------------------------------
   Rotulo con punto, titular y una linea de apoyo; a la derecha, una
   escena que no dice ningun dato -por eso es aria-hidden- y esta para
   que el estado se reconozca antes de leer.

   En TODOS los anchos. Antes solo existia en telefono (.cj-hero) y en
   escritorio lo contaba una barra distinta: dos formas de decir lo
   mismo que habia que mantener a la vez.
   --------------------------------------------------------------------- */
.cj-estado {
  display: flex;
  align-items: center;
  gap: var(--s3);
  margin: 0 0 var(--s4);
  padding: var(--s4);
  border: 1px solid transparent;
  border-radius: var(--r-lg);
}
.cj-estado-txt { flex: 1; min-width: 0; }

.cj-estado-rot {
  display: flex;
  align-items: center;
  gap: var(--s2);
  margin: 0 0 var(--s2);
  font-size: var(--fs-xs);
  font-weight: var(--peso-fuerte);
  letter-spacing: var(--ls-etiqueta);
  text-transform: uppercase;
}
.cj-punto {
  flex: none;
  width: 9px;
  height: 9px;
  border-radius: var(--r-full);
  background: currentColor;
}

.cj-estado h2 {
  margin: 0;
  font-size: var(--fs-xl);
  font-weight: var(--peso-max);
  line-height: var(--lh-tight);
  letter-spacing: var(--ls-titulo);
  color: var(--text);
}
.cj-estado-cifra {
  margin: 0;
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-3xl);
  font-weight: var(--peso-max);
  line-height: 1.05;
  letter-spacing: -0.02em;
  color: var(--text);
}
.cj-estado-sub {
  margin: 2px 0 0;
  font-size: var(--fs-sm);
  color: var(--text-2);
}
.cj-estado-meta {
  margin: var(--s2) 0 0;
  font-size: var(--fs-sm);
  line-height: 1.35;
  color: var(--text-2);
}

/* Los dos tonos. Ambar para cerrada -atencion, no error- y verde para
   abierta. El fondo se mezcla contra la superficie, asi que el mismo
   valor sirve en los dos temas. */
.cj-estado-cerrada {
  background: color-mix(in srgb, var(--warn) 11%, var(--surface));
  border-color: color-mix(in srgb, var(--warn) 24%, transparent);
}
.cj-estado-abierta {
  background: color-mix(in srgb, var(--ok) 10%, var(--surface));
  border-color: color-mix(in srgb, var(--ok) 22%, transparent);
}
@supports not (background: color-mix(in srgb, red 10%, white)) {
  .cj-estado-cerrada { background: var(--warn-bg); }
  .cj-estado-abierta { background: var(--ok-bg); }
}
.cj-estado-cerrada .cj-estado-rot { color: var(--warn); }
.cj-estado-abierta .cj-estado-rot { color: var(--ok); }

/* ---------- La escena ---------- */
.cj-escena { flex: none; width: 86px; }
.cj-escena svg { width: 86px; height: 72px; }
.cj-escena-caja,
.cj-escena-tapa,
.cj-escena-linea,
.cj-escena-cierre,
.cj-escena-billete,
.cj-escena-brillo {
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.cj-estado-cerrada .cj-escena { color: var(--warn); }
.cj-estado-abierta .cj-escena { color: var(--ok); }

.cj-escena-caja    { opacity: .5; }
.cj-escena-tapa    { opacity: .7; }
.cj-escena-linea   { opacity: .35; }
.cj-escena-billete { opacity: .55; }
.cj-escena-brillo  { opacity: .55; stroke-width: 1.6; }
.cj-escena-candado { fill: currentColor; opacity: .85; stroke: none; }
.cj-escena-cierre  { opacity: .85; }
.cj-escena-moneda  { fill: currentColor; opacity: .8; }

/* Las acciones del turno abierto, debajo del estado. */
.cj-acciones {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s3);
  margin: 0 0 var(--s4);
}
.cj-acciones .btn { flex: 1 1 220px; min-height: var(--tap-lg); }
.cj-enlace {
  display: inline-flex;
  align-items: center;
  gap: var(--s1);
  min-height: var(--tap);
  color: var(--brand-600);
  font-weight: var(--peso-fuerte);
  text-decoration: none;
}
.cj-enlace:hover { text-decoration: underline; }


/* ---------------------------------------------------------------------
   71c. ABRIR CAJA
   ---------------------------------------------------------------------
   El campo del fondo es la pieza principal: cuanto mas grande, menos
   dudas al teclear una cifra de dinero. El aviso azul explica para que
   sirve ese numero, y no es una alerta: es contexto.
   --------------------------------------------------------------------- */
.caja-abrir-cab {
  display: flex;
  align-items: center;
  gap: var(--s3);
}
.caja-abrir-icono {
  flex: none;
  display: grid;
  place-items: center;
  width: 42px;
  height: 42px;
  border-radius: var(--r-full);
  background: var(--btn-primario-bg);
  color: var(--btn-primario-texto);
  box-shadow: var(--sh-marca);
}
.caja-abrir-cab h3 {
  font-size: var(--fs-base);
  letter-spacing: var(--ls-etiqueta);
  text-transform: uppercase;
  color: var(--brand-600);
}

.caja-nota-info {
  display: flex;
  align-items: flex-start;
  gap: var(--s2);
  margin: var(--s3) 0 var(--s4);
  padding: var(--s3);
  background: color-mix(in srgb, var(--info) 7%, var(--surface));
  border-radius: var(--r-md);
  font-size: var(--fs-sm);
  line-height: 1.4;
  color: var(--text-2);
}
@supports not (background: color-mix(in srgb, red 7%, white)) {
  .caja-nota-info { background: var(--info-bg); }
}
.caja-nota-info .icono { flex: none; color: var(--info); margin-top: 1px; }

/* El CTA: la accion del dia, a lo ancho y con altura de accion
   dominante. */
.caja-abrir .btn-lg { min-height: var(--tap-lg); }


/* ---------------------------------------------------------------------
   71d. LOS ATAJOS DEL FONDO
   ---------------------------------------------------------------------
   Cinco botones que escriben en el MISMO campo. No guardan nada, no
   envian nada y no cambian la validacion: son una forma de teclear. El
   activo se marca cuando el importe coincide, asi que sigue siendo el
   campo el que manda.
   --------------------------------------------------------------------- */
.caja-atajos {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2);
  margin-top: var(--s3);
}
.caja-atajos[hidden] { display: none; }

.caja-atajo {
  flex: 1 1 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 5px;
  min-height: var(--tap);
  padding: 0 var(--s3);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  color: var(--text);
  font: inherit;
  font-size: var(--fs-sm);
  font-weight: var(--peso-medio);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  cursor: pointer;
  transition: background var(--t-rapida) var(--ease),
              border-color var(--t-rapida) var(--ease),
              color var(--t-rapida) var(--ease),
              transform var(--t-rapida) var(--ease);
}
.caja-atajo:hover { border-color: var(--border-2); background: var(--hover); }
.caja-atajo:active { transform: scale(var(--pulsa-lg)); }
.caja-atajo:focus-visible { outline: var(--foco); outline-offset: 2px; }

.caja-atajo.es-activo {
  background: var(--brand-50);
  border-color: color-mix(in srgb, var(--brand-600) 40%, transparent);
  color: var(--brand-600);
  font-weight: var(--peso-fuerte);
}
.caja-atajo-otro { color: var(--text-2); }
.caja-atajo-otro .icono { flex: none; opacity: .8; }


/* ---------------------------------------------------------------------
   71e. TABLETA Y ESCRITORIO
   ---------------------------------------------------------------------
   Mas aire y dos columnas donde ya las habia. Lo que NO cambia es la
   composicion: el estado arriba, el formulario debajo y la referencia
   al lado.
   --------------------------------------------------------------------- */
@media (max-width: 767px) {
  /* ---------- El bloque de abrir vuelve a ser una TARJETA ----------
     La seccion 44 convirtio los paneles de Caja en laminas a sangre, y
     para los bloques de apoyo esta bien. Este no: es el formulario del
     dia, la unica cosa que se puede hacer en la pantalla, y la
     referencia lo pinta como una tarjeta con su borde. Los selectores
     repiten clase para ganarle a los de aquella seccion, que estan
     escritos con cuatro. */
  .caja-apertura > .panel.caja-abrir {
    margin: 0 0 var(--s4);
    border: 1px solid var(--border);
    border-radius: var(--r-lg);
    box-shadow: var(--sh-1);
    overflow: hidden;
  }
  .caja-apertura > .panel.caja-abrir > .panel-cab.caja-abrir-cab {
    display: flex;
    align-items: center;
    flex-wrap: nowrap;
    gap: var(--s3);
    padding: var(--s4) var(--s4) 0;
  }
  /* Sin esto el bloque de texto pide su ancho intrinseco -325px por la
     descripcion- y el circulo azul se va a un renglon propio. */
  .caja-apertura > .panel.caja-abrir > .panel-cab.caja-abrir-cab > div {
    flex: 1 1 auto;
    min-width: 0;
  }

  /* Los cinco atajos en UN renglon, como la referencia: reparto a
     partes iguales y relleno justo. */
  .caja-atajos { gap: 6px; }
  .caja-atajo {
    flex: 1 1 0;
    min-width: 0;
    padding: 0 6px;
    font-size: var(--fs-xs);
  }
  .caja-atajo-otro .icono { width: 14px; height: 14px; }
  .caja-apertura > .panel.caja-abrir > .panel-cab.caja-abrir-cab h3 {
    font-size: var(--fs-base);
    font-weight: var(--peso-max);
    letter-spacing: var(--ls-etiqueta);
    text-transform: uppercase;
    color: var(--brand-600);
  }

  .cj-estado { padding: var(--s3); }
  .cj-estado h2 { font-size: var(--fs-lg); }
  .cj-estado-cifra { font-size: var(--fs-2xl); }
  .cj-estado-meta { font-size: var(--fs-xs); }
  .cj-escena { width: 72px; }
  .cj-escena svg { width: 72px; height: 60px; }

  /* En telefono la rejilla de metricas se retira: el estado ya lleva la
     cifra que manda y el desglose de abajo cuenta el resto. Es la misma
     decision de la seccion 44, ahora colgada del bloque nuevo. */
  .cj-estado ~ .metricas-zona { display: none; }
  .cj-acciones .btn { flex: 1 1 100%; }
}

@media (max-width: 379px) {
  /* La escena es lo primero que sobra: el punto y la frase ya dicen el
     estado. */
  .cj-escena { display: none; }
}

@media (min-width: 768px) {
  .cj-estado { padding: var(--s4) var(--s5); gap: var(--s5); }
  .cj-estado h2 { font-size: var(--fs-2xl); }
  .cj-estado-cifra { font-size: var(--fs-3xl); }
  .cj-escena { width: 104px; }
  .cj-escena svg { width: 104px; height: 88px; }
  .caja-abrir-cab h3 { font-size: var(--fs-lg); }
}


/* ---------------------------------------------------------------------
   71f. TEMA OSCURO
   ---------------------------------------------------------------------
   Los fondos tenidos bajan a los pares --tono-bg, calculados para fondo
   oscuro: un color-mix del 10% sobre #1e1e1a se lee como suciedad.
   --------------------------------------------------------------------- */
:root[data-tema="oscuro"] .cj-estado-cerrada { background: var(--warn-bg); }
:root[data-tema="oscuro"] .cj-estado-abierta { background: var(--ok-bg); }
:root[data-tema="oscuro"] .caja-nota-info { background: var(--info-bg); }
:root[data-tema="oscuro"] .caja-atajo.es-activo {
  background: color-mix(in srgb, var(--brand-600) 22%, var(--surface));
}


/* =====================================================================
   72. REPORTES   (Fase 11)
   =====================================================================
   El informe se lee de arriba abajo como un tablero de analisis:

     72a  EL LIENZO Y LA CABECERA
     72b  PESTANAS Y PASTILLAS      .rep-pest / .rep-barra
     72c  LAS CUATRO CIFRAS         .rep-mets
     72d  LOS BLOQUES               .rep-bloque / .rep-caja
     72e  TOP Y CATEGORIAS          .rep-top / .rep-anillo
     72f  LOS INFORMES QUE HAY      .rep-informes
     72g  TABLETA Y ESCRITORIO
     72h  TEMA OSCURO

   UNA sola composicion: se retira el reparto .rep-tactil /
   .rep-escritorio, que eran dos marcados completos de los mismos datos.

   BARRAS Y NO LINEA en la grafica: aqui se comparan dias entre si. La
   linea del tablero cuenta hacia donde va la semana, que es otra
   pregunta. Las dos pantallas no tienen por que dibujar igual.
   ===================================================================== */


/* ---------------------------------------------------------------------
   72a. EL LIENZO Y LA CABECERA
   --------------------------------------------------------------------- */
.contenido:has(.rep-mets) {
  background: var(--lienzo);
}

/* OLA 1: cabecera desde el sistema. Una accion ("Periodo"), que abre la
   hoja de filtros. */


/* ---------------------------------------------------------------------
   72b. PESTANAS Y PASTILLAS
   ---------------------------------------------------------------------
   Las pestanas reparten lo que YA esta pintado; las pastillas dicen que
   periodo y que sucursal se miran y abren el panel de filtros de
   siempre. Ninguna de las dos hace una peticion.
   --------------------------------------------------------------------- */
/* OLA 2: pestana = .tab, contenedor = .carril. Las cuatro secciones del
   informe se eligen con el mismo control que los tipos de movimiento y
   los estados de alerta, y se ven elegidas de la misma manera.

   .rep-pest-btn se conserva en el marcado: es el gancho de reportes.js,
   que es quien muestra y oculta los bloques del informe. */
.rep-pest { margin: 0 0 var(--s3); }
.rep-pest[hidden] { display: none; }

.rep-barra {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--s2);
  margin: 0 0 var(--s3);
}
.rep-pastilla {
  display: flex;
  align-items: center;
  gap: var(--s2);
  min-width: 0;
  min-height: var(--tap);
  padding: 0 var(--s3);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  box-shadow: var(--sh-1);
  color: var(--text);
  font: inherit;
  font-size: var(--fs-xs);
  font-weight: var(--peso-medio);
  cursor: pointer;
  transition: border-color var(--t-rapida) var(--ease), box-shadow var(--t-rapida) var(--ease);
}
.rep-pastilla:hover { border-color: var(--border-2); }
.rep-pastilla:focus-visible { border-color: var(--brand-600); box-shadow: var(--ring); outline: none; }
.rep-pastilla > .icono { flex: none; color: var(--text-3); }
.rep-pastilla > span {
  flex: 1;
  min-width: 0;
  text-align: left;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.rep-pastilla-punta { flex: none; color: var(--text-3); }

/* De 768 arriba el panel de filtros esta desplegado: las pastillas
   dirian dos veces lo mismo. */
@media (min-width: 768px) {
  .rep-barra { display: none; }
}


/* ---------------------------------------------------------------------
   72c. LAS CUATRO CIFRAS
   ---------------------------------------------------------------------
   Dos por dos en telefono y cuatro en linea de 768 arriba. Cada una con
   su azulejo, su rotulo, la cifra y -solo si existe- la comparacion con
   el periodo anterior.
   --------------------------------------------------------------------- */
.rep-mets {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s2);
  margin: 0 0 var(--s4);
  padding: 0;
  list-style: none;
}
.rep-met {
  display: grid;
  gap: var(--s0);
  padding: var(--s3);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-1);
}
.rep-met .azulejo { margin-bottom: var(--s1); }
.rep-met-rot {
  font-size: var(--fs-xs);
  color: var(--text-2);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.rep-met-valor {
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-xl);
  font-weight: var(--peso-max);
  line-height: 1.1;
  letter-spacing: -0.01em;
}
.rep-met-pie { display: grid; gap: 1px; margin-top: var(--s0); }
.rep-met-pie small { font-size: var(--fs-3xs); color: var(--text-3); }

.rep-met-var {
  display: inline-flex;
  align-items: center;
  gap: var(--s0);
  font-size: var(--fs-xs);
  font-weight: var(--peso-fuerte);
  font-variant-numeric: tabular-nums;
}
.rep-met-var.es-sube { color: var(--ok); }
.rep-met-var.es-baja { color: var(--error); }
.rep-met-var .icono { width: 13px; height: 13px; }


/* ---------------------------------------------------------------------
   72d. LOS BLOQUES
   ---------------------------------------------------------------------
   Rotulo fuera y superficie dentro: el titulo pertenece a la pantalla y
   la caja al dato. Es el mismo patron de Existencias y Alertas.
   --------------------------------------------------------------------- */
.rep-bloque { margin: 0 0 var(--s4); }
.rep-bloque[hidden] { display: none; }

.rep-bloque-cab { padding: 0 var(--s1) var(--s2); }
.rep-bloque-cab h2 {
  margin: 0;
  font-size: var(--fs-base);
  font-weight: var(--peso-max);
  line-height: var(--lh-tight);
}
.rep-bloque-cab p {
  margin: 2px 0 0;
  font-size: var(--fs-2xs);
  color: var(--text-2);
  line-height: 1.3;
}

.rep-caja {
  padding: var(--s3);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-1);
}
.rep-caja-lista { padding: 0; overflow: hidden; }
.rep-caja-datos { padding: var(--s2) var(--s3); }
.rep-caja-grafica { padding: var(--s3) var(--s2) var(--s2); }

.rep-sin-datos {
  margin: 0;
  padding: var(--s3) 0;
  font-size: var(--fs-sm);
  color: var(--text-3);
  text-align: center;
}

.rep-desglose-rot {
  margin: var(--s3) 0 var(--s2);
  font-size: var(--fs-xs);
  font-weight: var(--peso-fuerte);
  letter-spacing: var(--ls-etiqueta);
  text-transform: uppercase;
  color: var(--text-3);
}
.rep-desgloses > .rep-desglose-rot:first-child { margin-top: 0; }

.rep-unid {
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-lg);
  font-weight: var(--peso-max);
}
.rep-unid.es-cero { color: var(--text-3); }
.rep-unid-u { font-size: var(--fs-xs); color: var(--text-3); }


/* ---------------------------------------------------------------------
   72e. TOP Y CATEGORIAS
   --------------------------------------------------------------------- */
.rep-top {
  list-style: none;
  display: grid;
  gap: var(--s2);
  margin: 0;
  padding: 0;
  counter-reset: top;
}
.rep-top li {
  display: grid;
  grid-template-columns: auto auto minmax(0, 1fr);
  align-items: center;
  gap: var(--s3);
}
.rep-top-pos {
  width: 20px;
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-sm);
  font-weight: var(--peso-max);
  color: var(--text-3);
  text-align: center;
}
.rep-top .prod-img { width: 36px; height: 36px; }
.rep-top-txt { min-width: 0; display: grid; gap: 1px; }
.rep-top-txt strong {
  font-size: var(--fs-sm);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.rep-top-txt small { font-size: var(--fs-2xs); color: var(--text-3); }

/* ---------- El anillo ---------- */
.rep-anillo-caja {
  position: relative;
  display: grid;
  place-items: center;
  margin: var(--s2) auto var(--s3);
  width: 168px;
  height: 168px;
}
.rep-anillo { width: 168px; height: 168px; transform: rotate(-90deg); }
.rep-anillo circle {
  fill: none;
  stroke-width: 15;
}
.rep-anillo-base { stroke: var(--surface-2); }
.rep-anillo-tramo { transition: stroke-dasharray var(--t-lenta) var(--ease); }

.rep-anillo-centro {
  position: absolute;
  display: grid;
  justify-items: center;
  gap: 0;
  text-align: center;
}
.rep-anillo-centro strong {
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-lg);
  font-weight: var(--peso-max);
  line-height: 1.1;
}
.rep-anillo-centro small { font-size: var(--fs-3xs); color: var(--text-3); }

/* Cinco colores del sistema, en orden. No es una paleta nueva: son los
   mismos tonos de estado e informacion que usa el resto. */
/* OLA 2: LOS CINCO TRAMOS SON CATEGORIAS, NO ESTADOS.

   Antes se pintaban con --brand-600, --ok, --warn, --accent-600 y
   --text-3. Es decir: la segunda categoria por volumen salia VERDE DE
   EXITO y la tercera AMBAR DE ADVERTENCIA, sin que ninguna de las dos
   tuviera estado alguno. En un producto donde el ambar significa "va a
   caer pronto" y el verde "todo en orden", eso es leer una alerta donde
   solo hay una categoria que vende bien.

   Y la quinta se pintaba con --text-3, que es el gris del texto
   desactivado: la categoria mas pequena parecia estar apagada.

   Los cinco pasan a la paleta categorica. No hay color nuevo: es la
   misma que ya identificaba a los productos sin foto y a las sucursales
   en Existencias, que es justo lo que el anillo hace -decir CUAL es
   cada tramo, no como va-.

   Un solo token por tramo sirve para el trazo del SVG y para el cuadro
   de la leyenda, que es el mismo color en dos formas. */
.rep-anillo-c0 { stroke: var(--cat-0); background: var(--cat-0); }
.rep-anillo-c1 { stroke: var(--cat-1); background: var(--cat-1); }
.rep-anillo-c2 { stroke: var(--cat-2); background: var(--cat-2); }
.rep-anillo-c3 { stroke: var(--cat-3); background: var(--cat-3); }
.rep-anillo-c4 { stroke: var(--cat-4); background: var(--cat-4); }

.rep-leyenda {
  list-style: none;
  display: grid;
  gap: var(--s2);
  margin: 0;
  padding: 0;
}
.rep-leyenda li {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--s2);
  font-size: var(--fs-sm);
}
.rep-leyenda-color {
  width: 10px;
  height: 10px;
  border-radius: var(--r-full);
}
.rep-leyenda-nom {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--text-2);
}
.rep-leyenda b {
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  font-weight: var(--peso-fuerte);
}


/* ---------------------------------------------------------------------
   72f. LOS INFORMES QUE HAY
   ---------------------------------------------------------------------
   Navegacion, no informe: por eso se queda a la vista con cualquier
   pestana. Cada fila lleva a una pantalla que ya existe.
   --------------------------------------------------------------------- */
.rep-informes {
  list-style: none;
  margin: 0;
  padding: 0;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-1);
  overflow: hidden;
}
.rep-informes li + li { border-top: 1px solid var(--border-sutil); }
.rep-informes .rep-informe {
  display: flex;
  align-items: center;
  gap: var(--s3);
  min-height: 64px;
  padding: var(--s2) var(--s3);
  color: inherit;
  text-decoration: none;
  -webkit-tap-highlight-color: transparent;
}
.rep-informes .rep-informe:hover { background: var(--hover); }
.rep-informes .rep-informe:active { background: var(--pressed); }
.rep-informes .rep-informe:focus-visible { outline: var(--foco); outline-offset: -2px; }
.rep-informe-txt { flex: 1; min-width: 0; display: grid; gap: 1px; }
.rep-informe-txt strong { font-size: var(--fs-sm); font-weight: var(--peso-fuerte); }
.rep-informe-txt small {
  font-size: var(--fs-2xs);
  color: var(--text-3);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}


/* ---------------------------------------------------------------------
   72g. TABLETA Y ESCRITORIO
   --------------------------------------------------------------------- */
.rep-duo { display: grid; gap: 0; }

@media (min-width: 768px) {
  .rep-mets { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--s3); }
  .rep-met { padding: var(--s4); }
  .rep-met-valor { font-size: var(--fs-2xl); }
  .rep-met-rot { font-size: var(--fs-sm); }
  .rep-met-pie small { font-size: var(--fs-xs); }

  .rep-bloque-cab h2 { font-size: var(--fs-lg); }
  .rep-bloque-cab p { font-size: var(--fs-sm); }
  .rep-caja { padding: var(--s4); }

  /* Los dos bloques analiticos, en paralelo. */
  .rep-duo {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: var(--s4);
    align-items: start;
  }

  .rep-anillo-caja,
  .rep-anillo { width: 190px; height: 190px; }
  .rep-informes .rep-informe { min-height: 72px; padding: var(--s3) var(--s4); }
  .rep-informe-txt strong { font-size: var(--fs-base); }
}

@media (min-width: 1280px) {
  /* Con sitio de sobra, los desgloses y el inventario tambien caben a
     dos columnas sin apretarse. */
  .rep-duo { gap: var(--s5); }
  .rep-caja-grafica { padding: var(--s4); }
}


/* ---------------------------------------------------------------------
   72h. TEMA OSCURO
   --------------------------------------------------------------------- */
/* OLA 2: el parche oscuro de la pestana activa desaparece; lo resuelve
   --selected para las seis pantallas. */
:root[data-tema="oscuro"] .rep-anillo-base { stroke: var(--surface-3); }


/* =====================================================================
   73. SUCURSALES   (Fase 12)
   =====================================================================
   La pantalla deja de leerse como un CRUD y pasa a leerse como
   "administrar mis ubicaciones":

     73a  EL LIENZO Y LA CABECERA
     73b  EL TRIO DE RESUMEN        .suc-trio
     73c  LA FILA DE UNA SUCURSAL   .suc-filas / .suc-fila
     73d  LA SUCURSAL ACTIVA
     73e  TABLETA Y ESCRITORIO
     73f  TEMA OSCURO

   Lo que NO se toca: la hoja de cada sucursal, el formulario de alta y
   edicion, la tabla de escritorio y todos los POST -crear, editar,
   activar, desactivar y cambiar de sucursal- con su CSRF.
   ===================================================================== */


/* ---------------------------------------------------------------------
   73a. EL LIENZO Y LA CABECERA
   --------------------------------------------------------------------- */
.contenido:has(.suc-trio) {
  background: var(--lienzo);
}

/* OLA 1: cabecera desde el sistema. Una accion ("Nueva sucursal"). */


/* ---------------------------------------------------------------------
   73b. EL TRIO DE RESUMEN
   ---------------------------------------------------------------------
   Tres cifras que no compiten: cuantas hay, cuantas operan y cual es la
   principal. El tinte es el DATO -ambar si alguna esta parada, verde si
   todas operan-, no decoracion. Mismo recurso que el trio de Categorias.
   --------------------------------------------------------------------- */
/* OLA 8: lo unico propio de esta caja es el borde, y es LEGADO: no
   hay una sola regla en el archivo que le ponga color. Medido a 390:
   con el la caja mide 100.3px de alto y sin el 98.7, porque un borde
   transparente con box-sizing:border-box se come 2px por dentro.

   Se QUEDA. Es invisible, pero quitarlo baja el trio de Sucursales
   1.6px en una pantalla cerrada, y esta es la ultima ola de
   implementacion: la prioridad es cerrar el sistema, no empatar los
   numeros. Queda anotado por si alguien quiere ese pixel y medio. */
.suc-trio .trio-caja { border: 1px solid transparent; }
/* El nombre de la principal no es una cifra: se lee como texto y se
   recorta si hace falta, sin partir la caja.

   FASE D - ESTA REGLA NO SE APLICABA NUNCA, Y ESE ERA EL FALLO.
   .suc-trio-nom es una clase sola (0,1,0) y .trio-caja strong es clase
   + elemento (0,1,1), asi que la del componente ganaba SIEMPRE y el
   nombre se pintaba con el cuerpo de una cifra:

       declarado   13px (--fs-sm)
       aplicado    20px por debajo de 768  ·  26px de 768 arriba

   Consecuencia MEDIDA a 1440 con el menu lateral abierto: la caja da
   336px de contenido y "PAPELERIA JUAN ROMA NTE" pide 353 a 26px, o
   sea que el nombre de la sucursal principal salia CORTADO con puntos
   suspensivos en la propia pantalla de sucursales.

   Se corrige solo de 768 arriba, y se corrige subiendo la especificidad
   al mismo nivel que la regla del componente -.trio-caja strong- en vez
   de con !important: el problema era de peso, no de urgencia.

   EN TELEFONO NO SE TOCA. Ahi el mismo fallo existe (20px en vez de 13)
   y por debajo de 380 la tercera caja ni se pinta, asi que el tramo
   afectado es 380-767. Queda ANOTADO, no corregido: la composicion de
   telefono esta aprobada y este arreglo le cambiaria el alto al trio. */
.suc-trio-nom {
  font-family: var(--font);
  font-size: var(--fs-sm);
  line-height: 1.25;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

@media (min-width: 768px) {
  /* Dos renglones y no uno: con 336px de caja, dos renglones de 17px
     entran 46 caracteres, que cubre cualquier nombre de sucursal real.
     El recorte sigue estando para el caso extremo -line-clamp deja los
     puntos suspensivos al final del segundo renglon- asi que la caja no
     puede crecer sin limite por mucho que alguien escriba. */
  .trio-caja strong.suc-trio-nom {
    font-size: var(--fs-lg);
    white-space: normal;
    overflow-wrap: anywhere;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
  }
}


/* ---------------------------------------------------------------------
   73c. LA FILA DE UNA SUCURSAL
   ---------------------------------------------------------------------
   Azulejo con su color estable -ui_color_de_nombre(), el mismo helper
   del catalogo: nada nuevo en la base-, el nombre con su distintivo,
   como esta y cuanto tiene. Toda la fila abre la hoja.
   --------------------------------------------------------------------- */
.suc-filas {
  list-style: none;
  margin: 0;
  padding: 0;
}
.suc-fila { position: relative; }
.suc-fila + .suc-fila { border-top: 1px solid var(--border-sutil); }

.suc-toque {
  display: flex;
  align-items: center;
  gap: var(--s3);
  width: 100%;
  min-height: 76px;
  padding: var(--s2) var(--s3);
  background: none;
  border: 0;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: background var(--t-rapida) var(--ease);
}
.suc-toque:hover  { background: var(--hover); }
.suc-toque:active { background: var(--pressed); }
.suc-toque:focus-visible { outline: var(--foco); outline-offset: -2px; }

.suc-azulejo { flex: none; width: 42px; height: 42px; border-radius: var(--r-md); }

.suc-txt { flex: 1; min-width: 0; display: grid; gap: var(--s0); }

/* El nombre y su distintivo: .lista-nom, el componente del sistema
   (seccion 42b). Aqui vivian sus ocho declaraciones, identicas a las
   de Sucursales y Usuarios. La clase propia se queda en el marcado
   porque la usan las reglas de telefono de mas abajo. */

/* ---------------------------------------------------------------------
   73a-bis. LA TABLA NECESITA EL ANCHO QUE LE FALTA   (arreglo)
   ---------------------------------------------------------------------
   Es LA MISMA correccion que ya tiene Usuarios unas secciones mas
   arriba, y por la misma razon, solo que aqui la tabla tiene SEIS
   columnas en vez de cinco. El comentario de Usuarios dice "el corte va
   en 1600 y no en 1400 como en Sucursales", pero esa regla de Sucursales
   no existe en el archivo: se quedo escrita en la nota y nunca en el
   CSS, asi que el reparto 1.6/1 de la regla compartida seguia mandando.

   MEDIDO a 1440 con el menu lateral abierto: el listado se quedaba con
   739px para seis columnas -170, 111, 103, 90, 81 y 182- y ahi TODO se
   partia: el nombre de la sucursal en dos renglones, "832 unidades /
   $10.991,00 a costo" en tres, "1 asignados / mas el propietario" en
   tres. La fila de UNA sucursal media 138px.

   Hasta 1800 la tabla se lleva el ancho entero y el formulario baja,
   que ademas es el orden en el que se usa: primero se mira que
   sucursales hay y despues se da de alta otra. Medido despues: 304,
   158, 155, 117, 105 y 292, y la fila en 93px. Un tercio menos.

   EL CORTE VA EN 1800 Y NO EN 1600 COMO EN USUARIOS, y esta medido con
   las dos columnas puestas a la fuerza para ver desde donde caben:

       1600   la tabla se queda en 781px   fila de 138
       1700   842                          115
       1800   904                           93   <- desde aqui
       1900   940                           93

   Esa es la diferencia de tener SEIS columnas en vez de cinco. De 1800
   arriba vuelve el reparto a dos columnas de la regla compartida, que
   ahi si tiene sitio para las dos cosas a la vez.

   El maximo es 1799.98 y no 1799: entre 1799 y 1800 hay anchos reales
   -una ventana a 1799.5, un zoom del navegador- y sin la fraccion no
   los cubre ninguna de las dos reglas. */
@media (min-width: 1024px) and (max-width: 1799.98px) {
  .suc-cols { grid-template-columns: minmax(0, 1fr); }
  /* Mientras el formulario va DEBAJO no toma el ancho de la tabla: los
     campos estirados a 1100px se leen como un formulario roto. Misma
     medida que en Usuarios. */
  .suc-cols .suc-form { max-width: 560px; }
}


/* ---------------------------------------------------------------------
   73a-ter. .suc-estado ERAN DOS COSAS CON EL MISMO NOMBRE   (arreglo)
   ---------------------------------------------------------------------
   La clase de aqui abajo es el indicador de estado de la FILA DE
   TELEFONO: un punto de color y una palabra, en flex. Pero la tabla de
   escritorio marcaba su celda de estado con la misma clase, asi que a
   ese <td> le caia un display:flex y dejaba de ser una celda.

   Consecuencia MEDIDA a 1440: la celda media 50px de alto mientras sus
   cinco hermanas median 138, o sea que la pastilla "Activa" flotaba
   pegada arriba y el resto de la fila iba centrado. Era lo que hacia
   que la tabla se viera descuadrada aunque cada dato estuviera bien.

   No habia NI UNA regla escrita para esa celda: se estaba llevando el
   estilo de otro componente por accidente. El arreglo es de nombre, no
   de estilo: el <td> pasa a .suc-col-estado -sin regla ninguna- y
   vuelve a ser table-cell, que es lo que era. Verificado: las seis
   celdas miden ahora lo mismo de alto.

   ESTO ES POR QUE UN NOMBRE DE CLASE GENERICO DENTRO DE UNA PANTALLA
   ES CARO: "estado" describe dos objetos distintos de la misma
   pantalla, y el que llego despues se llevo el estilo del primero. */

/* El punto lleva el color; el texto se queda neutro. Verde solo cuando
   la caja esta ABIERTA, que es lo unico que de verdad esta pasando. */
.suc-estado {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: var(--fs-2xs);
  color: var(--text-2);
}
.suc-estado i {
  flex: none;
  width: 7px;
  height: 7px;
  border-radius: var(--r-full);
  background: currentColor;
}
.suc-estado-ok i     { color: var(--ok); }
.suc-estado-neutro i { color: var(--text-3); }
.suc-estado-off      { color: var(--text-3); }
.suc-estado-off i    { color: var(--text-3); }

.suc-meta-fila {
  font-size: var(--fs-2xs);
  color: var(--text-3);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.suc-fin {
  flex: none;
  display: flex;
  align-items: center;
  gap: var(--s2);
}
.suc-fin .lista-flecha { color: var(--text-3); }

/* Inactiva: se apaga, pero no desaparece. Sigue siendo administrable. */
.suc-fila.es-inactiva .suc-azulejo,
.suc-fila.es-inactiva .suc-txt { opacity: .62; }


/* ---------------------------------------------------------------------
   73d. LA SUCURSAL ACTIVA
   ---------------------------------------------------------------------
   En la que se esta trabajando ahora. Se distingue SIN leer: filo azul
   a la izquierda, fondo muy bajo y una marca corta a la derecha. No se
   pinta la fila entera de azul: con tres sucursales eso es una mancha.
   --------------------------------------------------------------------- */
.suc-fila.es-actual .suc-toque {
  background: var(--selected);
  box-shadow: inset 3px 0 0 var(--brand-600);
}
.suc-fila.es-actual .suc-toque:hover { background: var(--selected); }

.suc-aqui {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  padding: 2px var(--s2);
  background: var(--surface);
  border-radius: var(--r-full);
  color: var(--brand-600);
  font-size: var(--fs-3xs);
  font-weight: var(--peso-fuerte);
  white-space: nowrap;
}
/* OLA 5: aqui habia un `.suc-aqui .icono { width: 13px; height: 13px }`
   que repetia lo que el PHP ya emite -ui_icono('check', 13)- sin media
   query que lo justificara: la unica regla de tamano de icono del
   archivo que no cambiaba nada. Las otras trece SI existen por un
   motivo: son el ajuste de telefono sobre el tamano base que pone el
   PHP, y se quedan. */


/* ---------------------------------------------------------------------
   73e. TABLETA Y ESCRITORIO
   ---------------------------------------------------------------------
   De 768 arriba manda la TABLA que ya existia: ahi administrar es
   comparar seis sucursales columna a columna. Lo unico que cambia es
   que la fila en la que se trabaja se marca con el mismo filo azul que
   en telefono, para que las dos formas digan lo mismo.
   --------------------------------------------------------------------- */
@media (max-width: 767px) {
  .suc-toque { min-height: 70px; padding: var(--s2); gap: var(--s2); }
  .suc-azulejo { width: 38px; height: 38px; }

  /* EL NOMBRE SE LLEVA EL RENGLON ENTERO y "Principal" baja a compartir
     el segundo con el estado. Con la pastilla al lado, el nombre mas
     largo del negocio se quedaba en "PAPELERIA JUAN ROM..." a 390: el
     dato que se busca en esta lista es el nombre, asi que es lo ultimo
     que se recorta. Mismo recurso que la fila de Existencias. */
  .suc-txt {
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas:
      "nom  nom"
      "est  ppal"
      "meta meta";
    align-items: center;
  }
  .suc-nom { display: contents; }
  .suc-nom > span {
    grid-area: nom;
    font-size: 14px;
    font-weight: var(--peso-fuerte);
    line-height: var(--lh-tight);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .suc-ppal      { grid-area: ppal; }
  .suc-estado    { grid-area: est; }
  .suc-meta-fila { grid-area: meta; }
}

@media (max-width: 379px) {
  /* A 360 el tercer bloque se queda sin sitio para un nombre: el trio
     pasa a dos y la principal se lee en la lista, que es donde lleva su
     distintivo. */
  .suc-trio { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .suc-trio li:last-child { display: none; }

  /* 6px de relleno y de hueco en vez de 8: son los 8px que le faltaban
     al nombre mas largo para caber entero a 360. El area de toque no
     cambia, la sigue marcando el alto de la fila. */
  .suc-toque { padding: 6px; gap: 6px; }
  .suc-fin { gap: var(--s1); }
}

@media (min-width: 768px) {
  .suc-trio-nom { font-size: var(--fs-lg); }

  /* La misma marca de "estas aqui" que en telefono. */
  .tabla-sucursales tbody tr.es-actual > td:first-child {
    box-shadow: inset 3px 0 0 var(--brand-600);
  }
}


/* ---------------------------------------------------------------------
   73f. TEMA OSCURO
   --------------------------------------------------------------------- */
:root[data-tema="oscuro"] .suc-aqui { background: var(--hundido); }


/* =====================================================================
   74. USUARIOS: TU EQUIPO   (Fase 13)
   =====================================================================
   La pantalla deja de leerse como un registro de cuentas y pasa a
   leerse como "esta es mi gente y puedo administrarla":

     74a  EL LIENZO Y LA CABECERA
     74b  EL TRIO DE RESUMEN        .usr-trio
     74c  LA FILA DE UNA PERSONA    .usr-filas / .usr-fila
     74d  ROLES Y ESTADO
     74e  TABLETA Y ESCRITORIO
     74f  TEMA OSCURO

   Lo que NO se toca: la hoja de cada persona, el formulario de alta y
   edicion con su bloque de permisos, la tabla de escritorio y todos los
   POST con su CSRF. Y sobre todo: quien puede tocar a quien lo sigue
   decidiendo el servidor; aqui solo se pinta lo que la pantalla ya
   habia decidido en $gestion.
   ===================================================================== */


/* ---------------------------------------------------------------------
   74a. EL LIENZO Y LA CABECERA
   --------------------------------------------------------------------- */
.contenido:has(.usr-trio) {
  background: var(--lienzo);
}

/* OLA 1: cabecera desde el sistema. Una accion ("Nuevo usuario"). */


/* ---------------------------------------------------------------------
   74b. EL TRIO DE RESUMEN
   ---------------------------------------------------------------------
   Cuanta gente hay, cuanta puede entrar y como se reparte por rol. El
   segundo bloque es el unico que puede pedir atencion, y por eso es el
   unico que cambia de tono.
   --------------------------------------------------------------------- */


/* ---------------------------------------------------------------------
   74c. LA FILA DE UNA PERSONA
   ---------------------------------------------------------------------
   Tres renglones: quien es, que puede hacer y donde, y con que cuenta
   entra. El correo vuelve a la fila -estaba solo en la hoja- pero en el
   tercer renglon y en gris: identifica sin competir con el nombre.
   --------------------------------------------------------------------- */
.usr-filas { list-style: none; margin: 0; padding: 0; }
.usr-fila { position: relative; }
.usr-fila + .usr-fila { border-top: 1px solid var(--border-sutil); }

.usr-toque {
  display: flex;
  align-items: center;
  gap: var(--s3);
  width: 100%;
  min-height: 80px;
  padding: var(--s2) var(--s3);
  background: none;
  border: 0;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: background var(--t-rapida) var(--ease);
}
.usr-toque:hover  { background: var(--hover); }
.usr-toque:active { background: var(--pressed); }
.usr-toque:focus-visible { outline: var(--foco); outline-offset: -2px; }

/* Las iniciales heredan el azulejo de color del catalogo: el mismo
   nombre da siempre el mismo color y no se guarda nada en la base. */
.usr-ini {
  flex: none;
  width: 44px;
  height: 44px;
  border-radius: var(--r-full);
  font-size: var(--fs-sm);
  font-weight: var(--peso-max);
}

.usr-txt { flex: 1; min-width: 0; display: grid; gap: 3px; }

/* El nombre y su distintivo: .lista-nom, el componente del sistema
   (seccion 42b). Aqui vivian sus ocho declaraciones, identicas a las
   de Sucursales y Usuarios. La clase propia se queda en el marcado
   porque la usan las reglas de telefono de mas abajo. */

.usr-linea {
  display: flex;
  align-items: center;
  gap: var(--s2);
  min-width: 0;
}
.usr-suc {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  min-width: 0;
  font-size: var(--fs-2xs);
  color: var(--text-2);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.usr-suc .icono { flex: none; color: var(--text-3); }
.usr-suc.es-sin { color: var(--error); }
.usr-suc.es-sin .icono { color: var(--error); }

.usr-correo {
  font-size: var(--fs-2xs);
  color: var(--text-3);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.usr-fila .lista-flecha { flex: none; color: var(--text-3); }


/* ---------------------------------------------------------------------
   74d. ROLES Y ESTADO
   ---------------------------------------------------------------------
   El rol es una pastilla tenida, no una palabra suelta: es lo que
   contesta "que puede hacer esta persona" de un vistazo. Los tonos son
   los que la pantalla YA decidia en $rolTono, con el vocabulario de
   estado del sistema.

     propietario  azul   manda en todo
     encargado    verde  administra
     empleado     gris   opera

   El estado solo se dice cuando NO es el normal: una lista entera de
   "Activo" es ruido, "Bloqueado" hay que verlo.
   --------------------------------------------------------------------- */
.usr-rol {
  flex: none;
  padding: 1px var(--s2);
  border-radius: var(--r-full);
  font-size: var(--fs-3xs);
  font-weight: var(--peso-fuerte);
  white-space: nowrap;
}
.usr-rol-info   { background: var(--brand-50); color: var(--brand-600); }
.usr-rol-ok     { background: var(--ok-bg);    color: var(--ok); }
.usr-rol-neutro { background: var(--surface-2); color: var(--text-2); }

.usr-bloq {
  flex: none;
  padding: 1px 6px;
  background: var(--surface-2);
  border-radius: var(--r-full);
  color: var(--text-2);
  font-size: var(--fs-3xs);
  font-style: normal;
  font-weight: var(--peso-fuerte);
}
/* La cuenta bloqueada se apaga, pero no desaparece: se sigue pudiendo
   administrar, que es justo lo que se viene a hacer aqui. */
.usr-fila.es-bloqueada .usr-ini,
.usr-fila.es-bloqueada .usr-txt { opacity: .6; }

/* La propia cuenta se reconoce sin leer: el mismo filo azul con el que
   Sucursales marca donde estas trabajando. */
.usr-fila.es-yo .usr-toque { box-shadow: inset 3px 0 0 var(--brand-600); }


/* ---------------------------------------------------------------------
   74e. TABLETA Y ESCRITORIO
   ---------------------------------------------------------------------
   De 768 arriba manda la TABLA que ya existia: administrar seis cuentas
   en columnas es exactamente para lo que sirve.
   --------------------------------------------------------------------- */
@media (max-width: 767px) {

  .usr-toque { min-height: 74px; padding: var(--s2); gap: var(--s2); }
  .usr-ini { width: 40px; height: 40px; }
  .usr-nom { font-size: 14px; }
}

@media (max-width: 379px) {
  /* A 360 el correo es lo primero que sobra: identifica, pero el nombre
     y el rol ya dicen quien es, y la hoja lo lleva entero. */
  .usr-correo { display: none; }
  .usr-toque { padding: 6px; gap: 6px; min-height: 66px; }
}

@media (min-width: 768px) {
}


/* ---------------------------------------------------------------------
   74f. TEMA OSCURO
   --------------------------------------------------------------------- */
:root[data-tema="oscuro"] .usr-rol-neutro { background: var(--surface-3); }
:root[data-tema="oscuro"] .usr-bloq       { background: var(--surface-3); }


/* =====================================================================
   75. EL TABLERO DE ESCRITORIO   (Fase C)
   =====================================================================
   El tablero de telefono (.tm, seccion 60) se resolvio como una pila de
   TARJETAS CON CARA: azulejo con tono, cifra grande, pastilla de
   estado, curva, escena. El de escritorio se quedo donde estaba: ocho
   rectangulos correctos y mudos.

   Esta seccion no copia aquella pantalla -a 1440px lo que se viene a
   hacer es comparar, y para eso la rejilla, la grafica y los rankings
   son lo correcto-. Lo que trae son sus PIEZAS y su ritmo.

   TODO LO DE AQUI VIVE DENTRO DE .contenido:has(.tm), O SEA SOLO EN EL
   TABLERO. .tm es la seccion de telefono, que en el HTML esta siempre
   -aunque por encima de 768 no se pinte-, asi que es la firma mas
   barata y mas fiable de "esta pantalla es el tablero". Ya se usaba
   asi en las secciones 60 y 62.

   Y TODO VA DENTRO DE (min-width: 768px). Por debajo manda .tm y todo
   esto -metricas, paneles, rejillas- esta en display:none desde la
   seccion 60. Aun asi se acota: una regla que no puede verse en
   telefono pero podria empezar a verse el dia que alguien cambie
   aquella lista es una trampa esperando.
   ===================================================================== */

@media (min-width: 768px) {

  /* ---------------------------------------------------------------
     75a. LA ENTRADA
     ---------------------------------------------------------------
     El tablero es la pantalla que mas veces se abre al dia, y hasta
     ahora aparecia de golpe, entera y plana. Ahora se COMPONE: las
     tarjetas del dia suben una detras de otra y los bloques grandes
     las siguen.

     Es CSS puro, sin JavaScript y sin observador. Motivo: aqui no hace
     falta saber si algo entro en pantalla -el tablero empieza arriba y
     lo que se anima esta arriba-, y una animacion que depende de un
     archivo que puede no cargar es una pantalla que puede quedarse en
     blanco. Con `both` y sin JavaScript, el estado final esta
     garantizado por el propio navegador.

     Nada se mueve mas de 14px ni dura mas de medio segundo: esto tiene
     que sentirse como que la pantalla se asienta, no como una
     presentacion.
     --------------------------------------------------------------- */
  @keyframes tb-entra {
    from { opacity: 0; transform: translate3d(0, 14px, 0); }
    to   { opacity: 1; transform: none; }
  }

  .contenido:has(.tm) .metricas > .metrica,
  .contenido:has(.tm) > .caja-estado,
  .contenido:has(.tm) > .panel,
  .contenido:has(.tm) > .rejilla > .panel {
    animation: tb-entra 460ms var(--ease-suave) both;
  }

  /* El escalonado de la primera fila, que es la que se mira. Se numera
     con nth-child y no con una variable escrita en el PHP: la rejilla
     de metricas cambia de 2 a 4 columnas segun el ancho y el orden de
     documento es el mismo en los dos casos. */
  .contenido:has(.tm) .metricas > .metrica:nth-child(1) { animation-delay: 40ms; }
  .contenido:has(.tm) .metricas > .metrica:nth-child(2) { animation-delay: 100ms; }
  .contenido:has(.tm) .metricas > .metrica:nth-child(3) { animation-delay: 160ms; }
  .contenido:has(.tm) .metricas > .metrica:nth-child(4) { animation-delay: 220ms; }

  .contenido:has(.tm) > .caja-estado      { animation-delay: 260ms; }
  .contenido:has(.tm) > .panel            { animation-delay: 300ms; }
  .contenido:has(.tm) > .rejilla > .panel { animation-delay: 340ms; }

  /* La curva de cada metrica se dibuja sola, DESPUES de que la tarjeta
     este puesta. Es un trazo que se descubre de izquierda a derecha con
     clip-path; no hay dasharray que medir ni longitud que calcular. */
  @keyframes tb-traza {
    from { clip-path: inset(0 100% 0 0); }
    to   { clip-path: inset(0 0 0 0); }
  }
  .contenido:has(.tm) .metrica-chispa svg {
    animation: tb-traza 900ms var(--ease-suave) 420ms both;
  }
  @keyframes tb-punto {
    from { opacity: 0; transform: scale(.2); }
    to   { opacity: 1; transform: none; }
  }
  .contenido:has(.tm) .metrica-chispa-punta {
    animation: tb-punto 320ms var(--ease-suave) 1240ms both;
  }


  /* ---------------------------------------------------------------
     75b. EL RELIEVE
     ---------------------------------------------------------------
     Un panel del tablero es un objeto que se consulta, no un marco.
     Al pasar por encima se levanta lo justo y su azulejo responde: es
     el mismo gesto que ya tiene la metrica, aplicado al bloque grande.

     Acotado al tablero a proposito: en una pantalla de listado -donde
     el panel es el lienzo de trabajo y ocupa todo- levantarlo al pasar
     el raton seria un tic nervioso.
     --------------------------------------------------------------- */
  @media (hover: hover) {
    .contenido:has(.tm) .panel {
      transition: transform var(--t-rapida) var(--ease),
                  box-shadow var(--t-rapida) var(--ease),
                  border-color var(--t-rapida) var(--ease);
    }
    .contenido:has(.tm) .panel:hover {
      transform: translateY(-2px);
      box-shadow: var(--sh-2);
      border-color: var(--border-2);
    }
    .contenido:has(.tm) .panel-cab .azulejo {
      transition: transform var(--t-rapida) var(--ease-suave);
    }
    .contenido:has(.tm) .panel:hover .panel-cab .azulejo {
      transform: scale(1.07) rotate(-5deg);
    }
  }

  /* La fila de sucursales tambien es una lista de objetos: la que
     estas mirando se separa del resto. */
  @media (hover: hover) {
    .contenido:has(.tm) .tabla-sucursales-hoy tbody tr {
      transition: background var(--t-rapida) var(--ease);
    }
    .contenido:has(.tm) .tabla-sucursales-hoy tbody tr:hover { background: var(--hover); }
  }
}


/* Menos movimiento: la entrada desaparece y la pantalla se pinta
   puesta. El relieve al pasar el raton se queda -no es movimiento
   decorativo, es la respuesta a un gesto- pero sin desplazamiento. */
@media (prefers-reduced-motion: reduce) {
  .contenido:has(.tm) .metricas > .metrica,
  .contenido:has(.tm) > .caja-estado,
  .contenido:has(.tm) > .panel,
  .contenido:has(.tm) > .rejilla > .panel,
  .contenido:has(.tm) .metrica-chispa svg,
  .contenido:has(.tm) .metrica-chispa-punta { animation: none; }

  .contenido:has(.tm) .panel:hover,
  .metrica:hover { transform: none; }
  .metrica:hover .azulejo,
  .contenido:has(.tm) .panel:hover .panel-cab .azulejo { transform: none; }
}



/* =====================================================================
   76. REGISTRO: LA PANTALLA DE SIGUIENTE PASO
   =====================================================================
   La confirmacion del alta decia "ya esta en su lista; no tienes que
   hacer nada mas" y remataba con un boton de "Ir a entrar". Las dos
   cosas eran falsas: MOVQRA V1 se cobra a mano -hay una transferencia
   de por medio- y la cuenta que se acaba de crear todavia no puede
   entrar.

   Ahora esa pantalla tiene un trabajo: conseguir que la persona avise
   al administrador por WhatsApp. Todo lo de esta seccion existe para
   que ese boton sea lo unico que se pueda confundir con el siguiente
   movimiento.

     76a  LOS DOS ESTADOS. Registrada y pendiente, a la vez.
     76b  EL RESUMEN. Que negocio y con que correo.
     76c  LA ACTIVACION. La unica superficie de la tarjeta.
     76d  LAS SALIDAS. Dos enlaces que no compiten.
     76e  LA ESCALERA DEL PANEL DE MARCA (desde 900px).
     76f  TABLETA Y ESCRITORIO.

   TODO cuelga de .reg-ok-* o de .acceso-pasos-marcados, que solo
   existen en public/registro.php. Ni una regla de aqui alcanza a
   entrar, a recuperar contrasena ni a ninguna pantalla de dentro.

   NO SE TOCA nada de la seccion 25c ni de la 58: .registro-listo,
   .registro-avance y .acceso-caja siguen siendo las de siempre y las
   sigue usando el formulario, que no cambia.

   NO HAY BLOQUE DE TEMA OSCURO, y es a proposito. Todo lo de aqui se
   pinta con tokens semanticos y con color-mix sobre --surface, que es
   justo lo que la Ola 6 dejo montado para que una pantalla nueva salga
   en oscuro sola. Un bloque 76g con seis reglas repetidas seria empezar
   a deshacer aquello.
   ===================================================================== */


/* ---------------------------------------------------------------------
   76a. LOS DOS ESTADOS
   ---------------------------------------------------------------------
   La pantalla tiene que decir DOS cosas que parecen contradictorias y
   no lo son: la solicitud llego bien Y la cuenta no esta abierta. Un
   solo check verde decia la primera y dejaba suponer la segunda, que es
   como se llega a "ya puedo entrar".

   Por eso son dos renglones y por eso uno es verde y el otro ambar. El
   ambar no es decoracion: es el mismo color con el que el resto del
   sistema dice "esto sigue abierto" -el chip de negocio pendiente, la
   caja sin cerrar- y es lo que impide leer la pantalla como un final.

   No son una lista de pasos. Son el estado, y por eso no van numerados.
   --------------------------------------------------------------------- */

.reg-ok-estados {
  display: flex;
  flex-direction: column;
  gap: var(--s2);
  margin: 0 0 var(--s4);
  padding: 0;
  list-style: none;
}

.reg-ok-estado {
  display: flex;
  align-items: flex-start;
  gap: var(--s3);
  padding: var(--s3);
  border-radius: var(--r-md);
}

/* El disco del icono toma el color del estado por currentColor, igual
   que .acceso-sello-icono en la seccion 58h. Un token por estado aqui
   serian dos tokens para dos usos. */
.reg-ok-estado-ico {
  display: grid;
  place-items: center;
  flex: none;
  width: 22px;
  height: 22px;
  margin-top: 1px;
  border-radius: var(--r-full);
  background: currentColor;
}
.reg-ok-estado-ico .icono { color: var(--surface); }

.reg-ok-estado-txt { display: block; min-width: 0; }

.reg-ok-estado strong {
  display: block;
  font-size: var(--fs-sm);
  font-weight: var(--peso-fuerte);
  color: var(--text);
  line-height: 1.35;
}
.reg-ok-estado small {
  display: block;
  margin-top: 1px;
  font-size: var(--fs-xs);
  color: var(--text-2);
  line-height: 1.4;
}

/* El color vive en el <li> y baja al disco por currentColor; el texto
   lo recupera con sus propias declaraciones de arriba. */
.reg-ok-estado-si     { color: var(--ok); }
.reg-ok-estado-espera { color: var(--warn); }

.reg-ok-estado-si {
  background: color-mix(in srgb, var(--ok) 8%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--ok) 22%, transparent);
}
.reg-ok-estado-espera {
  background: color-mix(in srgb, var(--warn) 8%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--warn) 22%, transparent);
}
@supports not (background: color-mix(in srgb, red 8%, white)) {
  .reg-ok-estado-si     { background: var(--ok-bg);   border-color: transparent; }
  .reg-ok-estado-espera { background: var(--warn-bg); border-color: transparent; }
}


/* ---------------------------------------------------------------------
   76b. EL RESUMEN
   ---------------------------------------------------------------------
   Dos datos y ni uno mas: cual de los negocios es -alguien puede estar
   dando de alta el segundo- y con que correo va a entrar, que es el que
   se teclea mal y el que hay que recordar dentro de tres dias.

   Rotulo arriba y valor abajo, no en dos columnas: el correo es largo y
   una rejilla de dos columnas en un telefono lo parte por la mitad o
   deja el rotulo con un renglon para el suyo. El valor se puede romper
   por donde haga falta antes que desbordar la tarjeta.

   --surface-2 en los dos temas. Es el papel de "lo que va DENTRO del
   panel y tiene que distinguirse", y ese papel no cambia con el tema:
   en claro es un hueco, en oscuro un realce, y en los dos se separa de
   la tarjeta sin necesitar una regla por tema.
   --------------------------------------------------------------------- */

.reg-ok-datos {
  display: grid;
  gap: var(--s3);
  margin: 0 0 var(--s5);
  padding: var(--s3) var(--s4);
  border-radius: var(--r-md);
  background: var(--surface-2);
  border: 1px solid var(--border);
}

.reg-ok-datos dt {
  font-size: var(--fs-2xs);
  font-weight: var(--peso-fuerte);
  text-transform: uppercase;
  letter-spacing: var(--ls-etiqueta);
  color: var(--text-2);
}
.reg-ok-datos dd {
  margin: 2px 0 0;
  font-size: var(--fs-base);
  font-weight: var(--peso-fuerte);
  color: var(--text);
  line-height: 1.35;
  /* break-word y no anywhere: anywhere ademas afecta al tamaño
     min-content de la celda, y en la rejilla de dos columnas de 768
     arriba eso hacia que el correo se partiera ANTES de necesitarlo.
     Con break-word solo se rompe cuando no cabe de ninguna otra forma. */
  overflow-wrap: break-word;
}


/* ---------------------------------------------------------------------
   76c. LA ACTIVACION
   ---------------------------------------------------------------------
   La UNICA superficie con marco dentro de la tarjeta, y se la ha
   ganado: un precio, un paso y un boton son literalmente un objeto -una
   oferta- y encerrarlo es lo que le da el peso para cerrar. Es la misma
   decision que la escena 06 de la portada, en el tamaño de esta
   pantalla.

   El filo de marca a la izquierda es de 3px, como el de .acceso-aviso y
   .acceso-error: en esta pantalla un bloque con filo de color ya
   significa "esto es lo que hay que mirar", y aqui se reutiliza esa
   gramatica en vez de inventar otra.

   El fondo NO es --lienzo. Ese token significa "el fondo de una
   pantalla de trabajo" y su valor cambia de papel entre temas; aqui
   hace falta lo contrario, un tinte de marca CONSTANTE sobre la
   superficie de la tarjeta, que es lo que color-mix resuelve en los dos
   temas con una sola linea.
   --------------------------------------------------------------------- */

.reg-ok-activa {
  margin: 0 0 var(--s4);
  padding: var(--s4);
  border-radius: var(--r-lg);
  background: color-mix(in srgb, var(--brand-600) 6%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--brand-600) 14%, transparent);
  border-left: 3px solid var(--brand-600);
}
@supports not (background: color-mix(in srgb, red 6%, white)) {
  .reg-ok-activa { background: var(--brand-50); border-color: var(--border); }
}

.reg-ok-eti {
  margin: 0;
  font-size: var(--fs-2xs);
  font-weight: var(--peso-max);
  text-transform: uppercase;
  letter-spacing: var(--ls-etiqueta);
  color: var(--brand-600);
}

.reg-ok-paso {
  margin: var(--s1) 0 0;
  font-size: var(--fs-lg);
  font-weight: var(--peso-fuerte);
  color: var(--text);
  line-height: var(--lh-tight);
  letter-spacing: var(--ls-titulo);
}

/* EL PRECIO NO SE ESCONDE.
   Cifra grande y la nota al lado, en linea de base. No lleva tarjeta
   propia ni color de fondo: dentro de un bloque que ya esta enmarcado,
   un segundo marco solo resta. Lo que lo hace protagonista es el
   tamaño, que es la herramienta correcta para un numero. */
.reg-ok-precio {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--s2);
  margin: var(--s4) 0 0;
}
.reg-ok-precio strong {
  font-family: var(--font-num);
  font-size: var(--fs-3xl);
  font-weight: var(--peso-max);
  line-height: 1;
  letter-spacing: var(--ls-display);
  color: var(--text);
  font-variant-numeric: tabular-nums;
}
.reg-ok-precio span {
  font-size: var(--fs-sm);
  font-weight: var(--peso-fuerte);
  color: var(--text-2);
}

.reg-ok-que {
  margin: var(--s3) 0 var(--s4);
  font-size: var(--fs-sm);
  color: var(--text-2);
  line-height: var(--lh);
}

/* EL BOTON.
   Es el primario del sistema: no redefine color, alto ni radio. Lo
   unico que se le añade es que ocupe el ancho -aqui es la accion
   protagonista y no comparte renglon con nadie- y la sombra de marca,
   que es la misma que llevan el "Cobrar" del punto de venta y el
   "Entrar" del acceso.

   Ni verde de WhatsApp ni logotipo: esa marca es de Meta, y un boton
   verde de otro sistema dentro de esta tarjeta se lee como un anuncio
   pegado encima. Lo que identifica a WhatsApp aqui es el globo de
   conversacion -el mismo que ya usa la portada- y la palabra. */
.reg-ok-wa {
  width: 100%;
  box-shadow: var(--sh-marca);

  /* MEDIDO A 900px: el rotulo salia en DOS renglones. .btn-lg reparte
     32px de relleno a cada lado -que es lo correcto para un boton que
     se ajusta a su texto- y aqui el boton ocupa el ancho entero, asi
     que esos 64px no separan de nada: se los estaban quitando al
     rotulo. Medido: hacian falta 213.9px y habia 214.7.

     Las bandas donde se partia eran 320-360 (telefono estrecho) y
     900-1023, que es donde el panel de marca ya se ha llevado la mitad
     de la pantalla y la columna todavia no ha crecido. Con 12 a cada
     lado cabe en un renglon en las dos. El alto no se toca: los 52px de
     .btn-lg son los de la accion protagonista del sistema. */
  padding-inline: var(--s3);
}
.reg-ok-wa .icono { flex: none; }

/* LAS DOS BANDAS DONDE EL CARRIL ES MAS ESTRECHO.
   Medido con el banco de _qa, ancho por ancho, despues de quitar el
   relleno de arriba. Quedaban dos sitios con el rotulo en dos
   renglones, y no son los que se esperaria:

     320px        el telefono mas estrecho que se atiende.
     900..1023    NO es un telefono: es el ancho en el que el panel de
                  marca acaba de aparecer y se ha llevado la mitad de la
                  pantalla, mientras la columna todavia no ha crecido.
                  Es el sitio mas apretado de toda la pantalla, y es un
                  portatil pequeño o una tableta apaisada.

   El rotulo baja de --fs-lg a --fs-base, que es el cuerpo del boton
   normal del sistema: sigue siendo el boton grande -52px de alto, la
   sombra de marca, el ancho entero- solo que su texto deja de pelearse
   con el sitio que hay. Un CTA partido en dos renglones se lee como un
   descuido justo donde hay que confiar. */
@media (max-width: 359px), (min-width: 900px) and (max-width: 1023px) {
  .reg-ok-wa { font-size: var(--fs-base); }
}

/* La letra pequeña que sigue al boton: que va a pasar al pulsarlo y que
   pasa despues. Sin esto el boton pide un salto a otra aplicacion sin
   decir a que. */
.reg-ok-nota {
  display: flex;
  align-items: flex-start;
  gap: var(--s2);
  margin: var(--s3) 0 0;
  font-size: var(--fs-xs);
  color: var(--text-2);
  line-height: 1.45;
}
.reg-ok-nota .icono { flex: none; margin-top: 1px; color: var(--text-3); }

/* Sin numero configurado. Reutiliza .acceso-aviso entero -es el ambar
   de "falta algo" que ya existe en esta pantalla- y solo se le quita el
   margen de abajo, porque aqui es el ultimo hijo del bloque. */
.reg-ok-sinwa { margin: 0; }
.reg-ok-sinwa strong { display: block; margin-bottom: var(--s0); }


/* ---------------------------------------------------------------------
   76d. LAS SALIDAS
   ---------------------------------------------------------------------
   "Entrar" NO puede ser un boton en esta pantalla: la cuenta esta
   pendiente, asi que llevaria derecho a un rechazo. Y si fuera un boton
   competiria con el unico que si hay que pulsar.

   Dos enlaces de texto, centrados, separados de la tarjeta por un filo.
   El area tactil se la da sola la regla de 25d, que convierte todo <a>
   de dentro de .acceso-caja en un blanco de 44px.
   --------------------------------------------------------------------- */

.reg-ok-salidas {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s1);
  padding-top: var(--s4);
  border-top: 1px solid var(--border);
  text-align: center;
}
.reg-ok-salidas a {
  font-size: var(--fs-sm);
  font-weight: var(--peso-medio);
  color: var(--text-2);
  text-decoration: none;
}
.reg-ok-salidas a:first-child {
  color: var(--brand-600);
  font-weight: var(--peso-fuerte);
}
.reg-ok-salidas a:hover { color: var(--text); text-decoration: underline; }
.reg-ok-salidas a:first-child:hover { color: var(--brand-700); }


/* ---------------------------------------------------------------------
   76e. LA ESCALERA DEL PANEL DE MARCA
   ---------------------------------------------------------------------
   El panel de la izquierda contaba tres pasos -llenas, autorizan,
   entras- que se saltaban el aviso y el pago. Ahora cuenta los cuatro
   de verdad, los mismos de la portada, y marca en cual vas. Eso es lo
   que convierte la pantalla de exito en escritorio en las dos zonas que
   pedia: a la izquierda donde estas, a la derecha que hacer.

   La seccion 25 numeraba con un contador de CSS (.acceso-pasos
   li::before). Aqui el numero tiene que poder convertirse en un check
   cuando el paso ya esta hecho, y un contador no puede hacer eso. Se
   apaga el ::before SOLO en la lista marcada -el resto del sistema no
   se entera- y el numero pasa a ser un elemento de verdad.

   Esto vive dentro de .acceso-marca, que ya es display:none por debajo
   de 900px: en telefono manda el orden lineal de la columna, que cuenta
   lo mismo con frases.
   --------------------------------------------------------------------- */

.acceso-pasos-marcados { gap: var(--s4); }

.acceso-pasos-marcados li {
  display: flex;
  align-items: flex-start;
  gap: var(--s3);
  padding-left: 0;             /* el hueco del contador ya no hace falta */
  transition: opacity var(--t-media) var(--ease);
}
.acceso-pasos-marcados li::before { content: none; }

.acceso-paso-num {
  display: grid;
  place-items: center;
  flex: none;
  width: 24px;
  height: 24px;
  border-radius: var(--r-full);
  background: rgba(255,255,255,.18);
  border: 1px solid rgba(255,255,255,.24);
  font-size: var(--fs-xs);
  font-weight: var(--peso-max);
  color: #fff;
}

.acceso-paso-txt { display: block; min-width: 0; }
.acceso-paso-txt strong {
  display: block;
  font-size: var(--fs-base);
  font-weight: var(--peso-fuerte);
  color: #fff;
  line-height: 1.3;
}
.acceso-paso-txt small {
  display: block;
  margin-top: 2px;
  font-size: var(--fs-sm);
  color: rgba(255,255,255,.72);
  line-height: 1.45;
}

/* EL PASO HECHO. Se apaga, pero no desaparece: sigue contando de donde
   vienes. El numero se sustituye por un check en el PHP. */
.acceso-pasos-marcados .es-hecho { opacity: .55; }
.acceso-pasos-marcados .es-hecho .acceso-paso-num {
  background: rgba(255,255,255,.9);
  border-color: transparent;
  color: var(--acceso-marca-bg);
}

/* EL PASO ACTUAL. Es lo unico encendido del panel: disco blanco solido
   y un halo que lo separa de los otros tres. No se usa el azul de marca
   -el panel YA es azul de marca- sino el blanco, que aqui es el color
   que avanza. */
.acceso-pasos-marcados .es-actual .acceso-paso-num {
  background: #fff;
  border-color: transparent;
  color: var(--acceso-marca-bg);
  box-shadow: 0 0 0 4px rgba(255,255,255,.18);
}
.acceso-pasos-marcados .es-actual .acceso-paso-txt small {
  color: rgba(255,255,255,.88);
}

@media (prefers-reduced-motion: reduce) {
  .acceso-pasos-marcados li { transition: none; }
}


/* ---------------------------------------------------------------------
   76f. TABLETA Y ESCRITORIO
   ---------------------------------------------------------------------
   De 768 en adelante la columna ya es mas ancha (58l) y la tarjeta
   respira. Solo cambia lo que gana algo real con el ancho:

     - los dos estados se ponen en fila. Son dos hechos SIMULTANEOS, y
       uno encima de otro se leen como una secuencia.
     - el resumen reparte sus dos datos en dos columnas.
     - las dos salidas comparten renglon, separadas por un punto medio.

   El boton sigue ocupando el ancho del bloque. La tarjeta no pasa de
   560px (58l/58m), asi que aqui "ancho completo" no es el boton
   gigante de una pagina de 1440: es la accion protagonista de una
   columna estrecha, que es exactamente lo que es.
   --------------------------------------------------------------------- */

/* MEDIDO A 1440 ANTES DE DAR ESTO POR BUENO.
   Desde 900px la columna vale 400px (58b) porque comparte pantalla con
   el panel de marca, y ese ancho esta calibrado para un formulario de
   una sola columna. La pantalla de exito mete ahi dos estados en fila y
   dos datos en rejilla: a 400px salia "Solicitud registrada" partido en
   dos renglones y el correo roto por la mitad de una palabra.

   Se ensancha a 520, que es lo que la mitad derecha puede dar sin
   apretar: a 1440 el panel mide unos 720 y pone 32 de relleno a cada
   lado. Y se ensancha SOLO en la pantalla de exito, por una clase que
   el PHP pone unicamente cuando $enviado; el formulario sigue midiendo
   exactamente lo que medía. */
@media (min-width: 900px) {
  .acceso-col.reg-ok-col { max-width: 520px; }
}

@media (min-width: 768px) {

  .reg-ok-estados { flex-direction: row; }
  .reg-ok-estado  { flex: 1 1 0; min-width: 0; }

  .reg-ok-datos { grid-template-columns: repeat(2, minmax(0, 1fr)); }

  .reg-ok-activa { padding: var(--s5); }

  .reg-ok-salidas {
    flex-direction: row;
    justify-content: center;
    gap: var(--s4);
  }
  /* El separador entre las dos salidas. Va en ::before del segundo y no
     como caracter en el HTML: no es texto, es puntuacion visual, y un
     lector de pantalla no tiene por que leerlo. */
  .reg-ok-salidas a + a::before {
    content: "";
    display: inline-block;
    width: 3px; height: 3px;
    margin-right: var(--s4);
    vertical-align: middle;
    border-radius: var(--r-full);
    background: var(--border-2);
  }
}


/* =====================================================================
   77. LA MARCA: EL ISOTIPO Y SUS MONTURAS
   =====================================================================
   El sistema pinta el logotipo en siete sitios y los siete son el mismo
   objeto a distinto tamano:

     .lateral-logo             barra lateral (tablet y escritorio)
     .topbar-logo              barra superior (solo telefono)
     .acceso-logo > span       panel de marca de login y registro
     .acceso-hero-icono        portada de la tarjeta de acceso
     .acceso-marca-movil>span  cabecera de recuperar el acceso
     .bienvenida-icono         modal del primer acceso
     .carga-cubo               pantalla de entrada

   Los siete llevan dentro ui_marca(), que emite el asset OFICIAL
   -public/assets/movqra-iso-320.png, los mismos pixeles que entrego
   diseno- como <img class="marca-app">. No hay un solo contorno, una
   sola mascara ni un solo degradado de la M declarado en esta hoja.

   ---------------------------------------------------------------------
   POR QUE LAS SIETE MONTURAS SE QUEDARON SIN AZULEJO
   ---------------------------------------------------------------------
   Las siete pintaban un cuadrado de color -brand-50 palido en la
   lateral, azul solido en la barra superior, translucido en el panel de
   acceso- con un icono de TRAZO encima en currentColor. Ese azulejo era
   necesario: un trazo de 1.8px suelto sobre el fondo de la aplicacion
   no se lee como identidad, se lee como un icono mas de la interfaz.

   El isotipo oficial no es un trazo. Es una imagen a color con su
   propio alfa, pensada para ir sobre cualquier superficie. Encerrarla
   en un cuadro seria ponerle al logotipo un fondo que NO TIENE, y en
   dos de los siete sitios ese fondo ademas llevaba sombra: un
   rectangulo levantado del papel detras de una imagen transparente.

   Por eso el azulejo se quito EN CADA UNA DE LAS SIETE REGLAS, en su
   sitio, y no con una capa de excepciones aqui abajo. Una regla que
   apaga lo que otra pinta funciona mientras nadie mire; dos anos
   despues es un fondo fantasma que nadie entiende por que no se ve.
   Las monturas conservan lo unico suyo que sigue teniendo trabajo: su
   tamano y su sitio.

   ---------------------------------------------------------------------
   LA PROPORCION
   ---------------------------------------------------------------------
   Las siete monturas son CUADRADAS y el isotipo mide 2.29 de ancho por
   1 de alto. object-fit:contain es lo que garantiza que la M nunca se
   estire para llenar el cuadro: se ajusta al ancho y deja aire arriba y
   abajo. Como la montura ya no pinta nada, ese aire es invisible.

   Es la unica linea de esta seccion que protege la identidad, y por eso
   no se toca aunque parezca que no hace nada.
   --------------------------------------------------------------------- */

.marca-app {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
  /* Ni se estira, ni se recorta, ni se deforma. */
  object-position: center;
  -webkit-user-select: none;
  user-select: none;
}


/* =====================================================================
   78. LAS CIFRAS QUE NACEN EN CERO
   =====================================================================
   Aqui no se anima nada: la cuenta la lleva public/js/app.js
   (window.animarCifras). Esta seccion existe por UNA razon mecanica.

   Mientras una cifra sube de 0 a 4,000 cambia de digitos veinte veces
   por segundo. Con una tipografia proporcional el 1 es mas estrecho que
   el 8, asi que el ancho del texto baila en cada fotograma y con el
   baila la fila entera. tabular-nums hace que todos los digitos midan lo
   mismo y el numero solo crezca cuando de verdad gana una cifra.

   La mayoria de los sitios que llevan data-cifra ya lo tenian por su
   propio componente -.metrica-valor, .mov-met-valor, .rep-met-valor-.
   Esta regla es para los que no: el <b> de un dato del tablero de
   telefono, el <strong> de las tres metricas de Alertas. Se pone por el
   ATRIBUTO y no por cada clase, que es justo lo que hace que un marcado
   nuevo con data-cifra no tenga que acordarse de nada.

   El motor ademas reserva el ancho con min-width en linea mientras dura
   la cuenta, y lo quita al terminar: eso cubre el salto de 999 a 1,000,
   que es un digito mas y ninguna tipografia puede evitar.

   NO se toca el tamano, el color ni el peso. La cifra se ve exactamente
   igual que antes; lo unico que cambia es que ahora llega contando.
   --------------------------------------------------------------------- */

[data-cifra] { font-variant-numeric: tabular-nums; }


/* =====================================================================
   79. EL ASISTENTE DE ACTIVACION
   =====================================================================
   Cinco pantallas y dos formas.

   Las dos de los extremos -la bienvenida y el "todo listo"- son a pagina
   entera y centradas: son momentos, no trabajo. Las tres de en medio son
   un CARRIL y una OBRA.

       ≥1024    carril 280px | obra
       768-1023 carril arriba en banda, obra debajo
       ≤767     lo mismo, con el avance como barra segmentada

   TODO SALE DEL SISTEMA. Ni un color, ni un radio, ni una sombra nueva:
   superficies (--surface, --surface-2, --lienzo), estados (--ok, --error,
   --brand-600), la escala de espacio (--s1..--s7), la de letra
   (--fs-*), los radios (--r-*) y el azulejo de siempre. Un asistente que
   se viera distinto del producto estaria ensenando otro producto.

   ---------------------------------------------------------------------
   EL AVANCE: UN MARCADO, TRES COMPORTAMIENTOS
   ---------------------------------------------------------------------
   La misma <ol class="onb-ruta"> es:

     ≥1024     una COLUMNA con la linea vertical que une las marcas
     768-1023  una FILA de tres, marca arriba y rotulo debajo
     ≤767      una BARRA SEGMENTADA: la marca desaparece y cada paso es
               un trozo de borde con su rotulo

   El tercero no es un invento de esta pantalla: es exactamente lo que ya
   hace .registro-avance en telefono, que es el avance del registro. Dos
   asistentes del mismo producto no pueden ensenar el progreso de dos
   maneras distintas en el mismo tamano de pantalla.

   ---------------------------------------------------------------------
   LA FICHA DE PRODUCTO
   ---------------------------------------------------------------------
   Aqui habia una tabla de cinco columnas con data-no-apilar, o sea con
   el apilado automatico de tablas DESACTIVADO a mano para que en un
   telefono se empujara de lado. Ya no hay tabla.

   Cada producto es una ficha y sus tres campos van en una rejilla que a
   partir de 560px se lee como un renglon -nombre elastico, precio 140,
   stock 110- y por debajo se apila. Es UN marcado: en el telefono no hay
   una version distinta que mantener, hay la misma con las columnas
   desarmadas.

   El SKU y el precio de compra viven en un <details>. No es esconderlos:
   es que quien abre MOVQRA el primer dia no tiene un SKU a mano -el
   servidor lo genera si se deja vacio- y el costo solo sirve para leer
   la utilidad, que es una pregunta de despues.
   --------------------------------------------------------------------- */

.onb { max-width: 980px; margin: 0 auto; }

/* La entrada de cada pantalla. Un solo desplazamiento corto: lo justo
   para que se lea como un paso que llega y no como una pagina nueva. */
@keyframes onb-entra {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: none; }
}
.onb-entra { animation: onb-entra var(--t-lenta) ease-out both; }

/* Los formularios de apoyo -salir del asistente, "lo hare despues"- no
   se ven: sus botones los envian con form="id" desde donde toque. Un
   <form> dentro de otro no es HTML valido y aqui conviven dos. */
.onb-oculto { display: none; }


/* ---------------------------------------------------------------------
   PANTALLA 0 - LA BIENVENIDA
   --------------------------------------------------------------------- */
.onb-portada {
  max-width: 560px;
  margin: 0 auto;
  text-align: center;
  padding: var(--s5) 0 var(--s6);
}

/* El isotipo dentro de un halo de marca. La caja tiene medida propia
   porque .marca-app se estira al 100% de su contenedor (seccion 77). */
.onb-portada-sello {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 84px;
  height: 84px;
  margin-bottom: var(--s4);
  border-radius: var(--r-full);
  background: var(--brand-halo);
}
.onb-portada-sello .marca-app { width: 44px; height: auto; }

.onb-portada-tit {
  margin: 0 0 var(--s2);
  font-size: var(--fs-2xl);
  font-weight: var(--peso-max);
  letter-spacing: var(--ls-titulo);
  line-height: var(--lh-titulo);
}
.onb-portada-lema {
  margin: 0 0 var(--s3);
  font-size: var(--fs-lg);
  color: var(--text-2);
  line-height: var(--lh-tight);
}
.onb-portada-lema strong { color: var(--text); font-weight: var(--peso-fuerte); }

.onb-portada-tiempo {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  margin: 0 0 var(--s6);
  padding: var(--s1) var(--s3);
  border-radius: var(--r-full);
  background: var(--surface-2);
  border: 1px solid var(--border);
  font-size: var(--fs-xs);
  color: var(--text-2);
}
.onb-portada-tiempo svg { color: var(--text-3); }

/* La promesa: que sera verdad cuando esto termine. Alineada a la
   izquierda dentro de una portada centrada, porque son cuatro frases
   que se LEEN en columna, no un eslogan. */
.onb-promesa {
  list-style: none;
  margin: 0 0 var(--s6);
  padding: var(--s2) 0;
  text-align: left;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-1);
}
.onb-promesa li {
  display: flex;
  align-items: center;
  gap: var(--s3);
  padding: var(--s3) var(--s4);
}
.onb-promesa li + li { border-top: 1px solid var(--border-sutil); }
.onb-promesa span { display: grid; gap: 1px; min-width: 0; }
.onb-promesa strong { font-size: var(--fs-sm); font-weight: var(--peso-fuerte); }
.onb-promesa small  { font-size: var(--fs-xs); color: var(--text-2); }

.onb-portada-acciones {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s2);
}
.onb-portada-acciones .btn-lg { min-width: 240px; }


/* ---------------------------------------------------------------------
   PANTALLA 4 - TODO LISTO
   --------------------------------------------------------------------- */
.onb-final {
  max-width: 520px;
  margin: 0 auto;
  text-align: center;
  padding: var(--s5) 0 var(--s6);
}
.onb-final-sello {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 78px;
  height: 78px;
  margin-bottom: var(--s4);
  border-radius: var(--r-full);
  background: var(--ok-bg);
  color: var(--ok);
}
.onb-final-tit {
  margin: 0 0 var(--s2);
  font-size: var(--fs-3xl);
  font-weight: var(--peso-max);
  letter-spacing: var(--ls-titulo);
  line-height: var(--lh-titulo);
}
.onb-final-lema {
  margin: 0 0 var(--s6);
  font-size: var(--fs-lg);
  color: var(--text-2);
}

.onb-logros {
  list-style: none;
  margin: 0 0 var(--s6);
  padding: 0;
  text-align: left;
  display: grid;
  gap: var(--s2);
}
.onb-logros li {
  display: flex;
  align-items: center;
  gap: var(--s3);
  padding: var(--s3) var(--s4);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  box-shadow: var(--sh-1);

  /* Los cuatro logros aparecen en fila, uno detras de otro. Es la unica
     animacion escalonada de la pantalla y esta justo donde toca: es el
     momento de recompensa, y verlos marcarse uno a uno es la diferencia
     entre una lista y un cierre. */
  animation: onb-entra 360ms ease-out both;
}
.onb-logros li:nth-child(1) { animation-delay: 140ms; }
.onb-logros li:nth-child(2) { animation-delay: 240ms; }
.onb-logros li:nth-child(3) { animation-delay: 340ms; }
.onb-logros li:nth-child(4) { animation-delay: 440ms; }

.onb-logro-marca {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border-radius: var(--r-full);
  background: var(--ok);
  color: #fff;
}
/* Lo que quedo pendiente NO se pinta de verde. Decir "productos
   agregados" cuando no se agrego ninguno seria inventar un exito. */
.onb-logros .es-pend .onb-logro-marca { background: var(--surface-2); color: var(--text-3); border: 1px solid var(--border); }

.onb-logros span { display: grid; gap: 1px; min-width: 0; }
.onb-logros strong { font-size: var(--fs-sm); font-weight: var(--peso-fuerte); }
.onb-logros small  { font-size: var(--fs-xs); color: var(--text-2); }

.onb-final-pie {
  margin: var(--s4) 0 0;
  font-size: var(--fs-xs);
  color: var(--text-3);
  line-height: var(--lh);
}


/* ---------------------------------------------------------------------
   PANTALLAS 1..3 - CARRIL Y OBRA
   --------------------------------------------------------------------- */
.onb-cuerpo { display: grid; gap: var(--s5); align-items: start; }

@media (min-width: 1024px) {
  .onb-cuerpo { grid-template-columns: 260px minmax(0, 1fr); gap: var(--s6); }

  /* El carril acompana al desplazamiento: en un paso largo -diez fichas
     de producto- saber cuanto falta no puede quedarse arriba del todo. */
  .onb-carril { position: sticky; top: var(--s5); }
}

.onb-cuenta {
  margin: 0 0 var(--s3);
  font-size: var(--fs-2xs);
  font-weight: var(--peso-max);
  letter-spacing: var(--ls-etiqueta);
  text-transform: uppercase;
  color: var(--text-3);
}
.onb-cuenta b { color: var(--brand-600); }

/* ---- El avance ---- */
.onb-ruta { list-style: none; margin: 0 0 var(--s5); padding: 0; }
.onb-ruta li { position: relative; }

.onb-ruta-marca {
  position: relative;
  z-index: 1;
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border-radius: var(--r-full);
  background: var(--surface);
  border: 2px solid var(--border);
  color: var(--text-3);
  font-size: var(--fs-xs);
  font-weight: var(--peso-max);
  font-variant-numeric: tabular-nums;
}
.onb-ruta .es-hecho .onb-ruta-marca {
  background: var(--ok);
  border-color: var(--ok);
  color: #fff;
}
.onb-ruta .es-actual .onb-ruta-marca {
  background: var(--brand-600);
  border-color: var(--brand-600);
  color: #fff;
  box-shadow: 0 0 0 4px var(--brand-halo);
}

.onb-ruta-txt { display: grid; gap: 1px; min-width: 0; }
.onb-ruta-txt strong { font-size: var(--fs-sm); font-weight: var(--peso-fuerte); color: var(--text-2); }
.onb-ruta-txt small  { font-size: var(--fs-xs); color: var(--text-3); }
.onb-ruta .es-actual .onb-ruta-txt strong { color: var(--text); }

/* El contexto: una linea que cuenta que hace MOVQRA con lo que se esta
   escribiendo. Es lo unico que cambia en el carril de un paso a otro. */
.onb-contexto {
  display: flex;
  align-items: flex-start;
  gap: var(--s3);
  margin: 0 0 var(--s4);
  padding: var(--s3);
  background: var(--lienzo);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  font-size: var(--fs-xs);
  color: var(--text-2);
  line-height: var(--lh);
}

/* La salida. Un seguro, no una accion: texto plano y sin caja, para que
   no compita con Continuar ni un pixel. */
.onb-salir {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap, 44px);
  padding: 0;
  background: none;
  border: 0;
  color: var(--text-3);
  font: inherit;
  font-size: var(--fs-xs);
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}
.onb-salir:hover { color: var(--text-2); }


/* ---- Los bloques de la obra ---- */
.onb-bloque {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-1);
  padding: var(--s5);
  margin-bottom: var(--s4);
}
.onb-bloque-cab {
  display: flex;
  align-items: flex-start;
  gap: var(--s3);
  margin-bottom: var(--s5);
}
.onb-bloque-cab h2 {
  display: flex;
  align-items: center;
  gap: var(--s2);
  margin: 0;
  font-size: var(--fs-lg);
  font-weight: var(--peso-max);
  letter-spacing: var(--ls-titulo);
}
.onb-bloque-cab p { margin: var(--s0) 0 0; font-size: var(--fs-sm); color: var(--text-2); line-height: var(--lh); }

/* Una nota dentro del bloque: contexto que NO es un aviso. Por eso no
   usa .aviso, que es para algo que ha pasado o que hay que atender. */
.onb-nota {
  display: flex;
  align-items: flex-start;
  gap: var(--s2);
  margin: 0 0 var(--s4);
  font-size: var(--fs-xs);
  color: var(--text-2);
  line-height: var(--lh);
}
.onb-nota svg { flex: none; margin-top: 1px; color: var(--text-3); }

/* ---- "Asi aparecera tu negocio" ---- */
.onb-muestra { display: grid; gap: var(--s2); margin: 0 0 var(--s4); }
.onb-muestra-rot {
  font-size: var(--fs-2xs);
  font-weight: var(--peso-fuerte);
  letter-spacing: var(--ls-etiqueta);
  text-transform: uppercase;
  color: var(--text-3);
}
.onb-muestra-caja {
  display: flex;
  align-items: center;
  gap: var(--s3);
  padding: var(--s3) var(--s4);
  background: var(--lienzo);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  min-width: 0;
}
.onb-muestra-marca { flex: none; width: 26px; }
.onb-muestra-caja b {
  font-size: var(--fs-lg);
  font-weight: var(--peso-max);
  letter-spacing: var(--ls-titulo);
  overflow-wrap: anywhere;
}

/* ---- La cabecera de "direccion y telefono" de la sucursal ---- */
.onb-sub-cab {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s3);
  margin-bottom: var(--s2);
}
.onb-sub-cab > span {
  font-size: var(--fs-2xs);
  font-weight: var(--peso-fuerte);
  letter-spacing: var(--ls-etiqueta);
  text-transform: uppercase;
  color: var(--text-3);
}
.onb-copiar {
  display: inline-flex;
  align-items: center;
  gap: var(--s1);
  padding: var(--s1) var(--s2);
  background: none;
  border: 1px solid var(--border);
  border-radius: var(--r-full);
  color: var(--brand-600);
  font: inherit;
  font-size: var(--fs-xs);
  cursor: pointer;
  transition: background var(--t-rapida), border-color var(--t-rapida), color var(--t-rapida);
}
.onb-copiar:hover { background: var(--brand-50); border-color: var(--brand-600); }
.onb-copiar.es-hecho { color: var(--ok); border-color: var(--ok); background: var(--ok-bg); }


/* ---- Las fichas de producto ---- */
.onb-fichas { list-style: none; margin: 0 0 var(--s3); padding: 0; }

.onb-ficha {
  padding: var(--s3) var(--s4) var(--s4);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
}
.onb-ficha + .onb-ficha { margin-top: var(--s3); }
.onb-ficha.tiene-error { border-color: var(--error); box-shadow: var(--ring-error); }

.onb-ficha-cab {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s2);
  margin-bottom: var(--s2);
}
.onb-ficha-n {
  font-size: var(--fs-2xs);
  font-weight: var(--peso-fuerte);
  letter-spacing: var(--ls-etiqueta);
  text-transform: uppercase;
  color: var(--text-3);
}
.onb-ficha-n b { color: var(--text-2); }

.onb-ficha-quitar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  background: none;
  border: 0;
  border-radius: var(--r-sm);
  color: var(--text-3);
  cursor: pointer;
  transition: background var(--t-rapida), color var(--t-rapida);
}
.onb-ficha-quitar:hover:not(:disabled) { background: var(--error-bg); color: var(--error); }
.onb-ficha-quitar:disabled { opacity: .3; cursor: default; }

/* Los tres campos que hacen falta para vender. Se apilan por debajo de
   560px y de ahi para arriba se leen como un renglon. El mismo corte
   que usa .campos-rejilla en todo el sistema: no hay uno nuevo. */
.onb-ficha-campos { display: grid; gap: 0 var(--s3); }
.onb-ficha-campos .campo { margin-bottom: var(--s2); }
.onb-ficha-campos label { font-size: var(--fs-xs); color: var(--text-2); }

@media (min-width: 560px) {
  .onb-ficha-campos { grid-template-columns: minmax(0, 1fr) 140px 110px; }
}

/* El simbolo de moneda dentro del campo: dice de que se esta hablando
   sin gastar un renglon de ayuda debajo. */
.onb-moneda { position: relative; }
.onb-moneda > span {
  position: absolute;
  left: var(--s3);
  top: 50%;
  transform: translateY(-50%);
  color: var(--text-3);
  font-size: var(--fs-sm);
  pointer-events: none;
}
.onb-moneda input { padding-left: 24px; }

.onb-ficha-mas { margin-top: var(--s1); }
/* El mismo galon que abre el detalle de una transferencia: geometria,
   giro y duracion identicos. Dos plegables del mismo producto no
   pueden abrirse con dos flechas distintas. */
.onb-ficha-mas > summary {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  min-height: 32px;
  font-size: var(--fs-xs);
  color: var(--text-2);
  cursor: pointer;
  list-style: none;
}
.onb-ficha-mas > summary::-webkit-details-marker { display: none; }
.onb-ficha-mas > summary::after {
  content: "";
  width: 7px;
  height: 7px;
  border-right: 2px solid var(--text-3);
  border-bottom: 2px solid var(--text-3);
  transform: rotate(45deg) translateY(-2px);
  transition: transform var(--t-rapida) var(--ease);
}
.onb-ficha-mas[open] > summary::after { transform: rotate(-135deg) translateY(-2px); }
.onb-ficha-mas > summary:hover { color: var(--text); }
.onb-ficha-mas[open] > summary { margin-bottom: var(--s2); }
.onb-ficha-mas .campo { margin-bottom: var(--s2); }
.onb-ficha-mas label { font-size: var(--fs-xs); color: var(--text-2); }

.onb-ficha-error { margin: var(--s2) 0 0; }

.onb-mas {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  min-height: var(--tap, 44px);
  padding: 0 var(--s4);
  background: var(--surface);
  border: 1px dashed var(--border-2);
  border-radius: var(--r-md);
  color: var(--brand-600);
  font: inherit;
  font-size: var(--fs-sm);
  font-weight: var(--peso-fuerte);
  cursor: pointer;
  transition: background var(--t-rapida), border-color var(--t-rapida);
}
.onb-mas:hover { background: var(--brand-50); border-color: var(--brand-600); }
.onb-tope { margin: var(--s2) 0 0; font-size: var(--fs-xs); color: var(--text-3); }


/* ---- El ticket en pequeno ---- */
.onb-ticket {
  max-width: 250px;
  margin: var(--s5) 0 0;
  padding: var(--s4);
  display: grid;
  gap: var(--s1);
  justify-items: center;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  box-shadow: var(--sh-2);
  font-size: var(--fs-2xs);
  color: var(--text-2);
  text-align: center;
}
.onb-ticket-neg { font-size: var(--fs-sm); color: var(--text); overflow-wrap: anywhere; }
.onb-ticket-suc { color: var(--text-3); }
.onb-ticket-linea {
  width: 100%;
  height: 0;
  margin: var(--s1) 0;
  border-top: 1px dashed var(--border-2);
}
.onb-ticket-fila { display: flex; justify-content: space-between; width: 100%; }
.onb-ticket-fila i { font-style: normal; }
.onb-ticket-total { color: var(--text); font-weight: var(--peso-fuerte); }
.onb-ticket em { font-style: normal; color: var(--text); overflow-wrap: anywhere; }


/* ---- Las acciones del paso ---- */
.onb-acciones {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--s3);
  margin-top: var(--s5);
}
/* Con dos, la secundaria se va al otro extremo: asi no se leen como una
   pareja de opciones equivalentes. La principal siempre a la derecha. */
.onb-acciones-dos { justify-content: space-between; }
.onb-acciones-pie {
  margin: var(--s3) 0 0;
  font-size: var(--fs-xs);
  color: var(--text-3);
  text-align: right;
}
.onb-form .btn.es-enviando { pointer-events: none; }


/* ---------------------------------------------------------------------
   768 - 1023: EL CARRIL ES UNA BANDA
   --------------------------------------------------------------------- */
@media (max-width: 1023px) {
  .onb-carril {
    padding: var(--s4);
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--r-lg);
    box-shadow: var(--sh-1);
  }
  .onb-cuerpo { gap: var(--s4); }
  .onb-ruta { margin-bottom: var(--s4); }
  .onb-contexto { margin-bottom: var(--s3); }
}

@media (min-width: 1024px) {
  /* En columna, la linea baja de una marca a la siguiente. Se dibuja en
     el <li> y no entre ellos porque asi el ultimo la apaga solo. */
  .onb-ruta li {
    display: grid;
    grid-template-columns: 26px minmax(0, 1fr);
    gap: var(--s3);
    padding-bottom: var(--s5);
  }
  .onb-ruta li:last-child { padding-bottom: 0; }
  .onb-ruta li::before {
    content: "";
    position: absolute;
    left: 12px;
    top: 26px;
    bottom: 4px;
    width: 2px;
    background: var(--border);
  }
  .onb-ruta li:last-child::before { content: none; }
  .onb-ruta .es-hecho::before { background: var(--ok); }
}

@media (min-width: 768px) and (max-width: 1023px) {
  /* En fila: marca arriba, rotulo debajo, y la linea de una marca a la
     de al lado. Es la misma pieza girada. */
  .onb-ruta { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .onb-ruta li {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--s2);
    text-align: center;
    padding: 0 var(--s2);
  }
  .onb-ruta li::before {
    content: "";
    position: absolute;
    left: calc(50% + 18px);
    width: calc(100% - 36px);
    top: 12px;
    height: 2px;
    background: var(--border);
  }
  .onb-ruta li:last-child::before { content: none; }
  .onb-ruta .es-hecho::before { background: var(--ok); }
  .onb-ruta-txt { justify-items: center; }
}


/* ---------------------------------------------------------------------
   ≤767: EL TELEFONO
   ---------------------------------------------------------------------
   El avance pasa a barra segmentada -sin marcas, sin subtitulos- porque
   tres circulos con dos renglones cada uno no caben en 375px sin
   estrujarse. Es el mismo gesto que .registro-avance.

   Y las acciones se apilan en column-reverse: la principal ARRIBA y a
   todo lo ancho, la secundaria debajo. Poner "Lo hare despues" encima
   de "Continuar" seria ofrecer primero la salida.
   --------------------------------------------------------------------- */
@media (max-width: 767px) {
  .onb-carril { padding: var(--s3) var(--s4); }
  .onb-cuenta { margin-bottom: var(--s2); }

  .onb-ruta {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--s2);
    margin-bottom: var(--s3);
  }
  .onb-ruta li {
    padding: var(--s2) 0 0;
    border-top: 3px solid var(--border);
    border-radius: 2px;
  }
  .onb-ruta li::before { content: none; }
  .onb-ruta-marca { display: none; }
  .onb-ruta-txt small { display: none; }
  .onb-ruta-txt strong { font-size: var(--fs-2xs); }
  .onb-ruta .es-hecho  { border-top-color: var(--ok); }
  .onb-ruta .es-actual { border-top-color: var(--brand-600); }

  .onb-bloque { padding: var(--s4); border-radius: var(--r-md); }
  .onb-bloque-cab { margin-bottom: var(--s4); }

  /* 320px: EL TITULO NO PUEDE ENSANCHAR LA PAGINA.
     ------------------------------------------------------------------
     Medido en el paso 3 a 320px de ancho: "Personalizacion" es UNA
     palabra de 195px. Junto al azulejo y su hueco, la cabecera del
     bloque declaraba un min-content de 259px; con el relleno del
     bloque son 292px, y dentro de los 273px que deja el carril a ese
     ancho la rejilla de .onb-cuerpo se ensanchaba hasta esos 292 y la
     pagina acababa con 4px de barra horizontal. Era el unico sitio de
     las 16 medidas donde pasaba.

     'anywhere' y no 'break-word' a proposito: break-word rompe al
     pintar pero NO baja el min-content, que es justo lo que aqui
     ensancha la rejilla. Con anywhere el min-content pasa a ser el de
     un caracter y la rejilla vuelve a caber.

     No cambia nada por encima de 320: una palabra solo se parte cuando
     no cabe entera, y a 360 en adelante cabe. Comprobado en los 16
     anchos. */
  .onb-bloque-cab h2 { overflow-wrap: anywhere; }

  .onb-acciones { flex-direction: column-reverse; align-items: stretch; margin-top: var(--s4); }
  .onb-acciones .btn { width: 100%; justify-content: center; }
  .onb-acciones-pie { text-align: center; }

  .onb-portada, .onb-final { padding-top: var(--s3); }
  .onb-final-tit { font-size: var(--fs-2xl); }
  .onb-portada-acciones .btn { width: 100%; min-width: 0; }

  .onb-ticket { max-width: 100%; }

  /* La ficha a sangre del bloque: en 375px, un borde dentro de otro
     borde con relleno a los dos lados deja el campo en 250px. */
  .onb-ficha { padding: var(--s3); }
  .onb-mas { width: 100%; justify-content: center; }
}


/* ---------------------------------------------------------------------
   MOVIMIENTO REDUCIDO
   ---------------------------------------------------------------------
   Las dos animaciones de la pantalla son entradas. Sin ellas se ve
   exactamente lo mismo, puesto de una vez: por eso se apagan enteras y
   no se acortan.
   --------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  .onb-entra,
  .onb-logros li {
    animation: none;
    opacity: 1;
    transform: none;
  }
  .onb-copiar, .onb-mas, .onb-ficha-quitar,
  .onb-ficha-mas > summary::after { transition: none; }
}


/* =====================================================================
   80. LAS PAGINAS LEGALES
   =====================================================================
   Tres documentos -/privacidad.php, /terminos.php y /cookies.php- que
   comparten UNA plantilla (includes/vistas/legal.php) y por tanto UNA
   hoja de estilo.

   NO HAY NADA NUEVO AQUI. Ni un color, ni un radio, ni una sombra, ni
   un tamano de letra fuera de escala: superficies (--bg, --surface,
   --border), texto (--text, --text-2, --text-3), espacio (--s1..--s7),
   letra (--fs-*), radios (--r-*) y pesos (--peso-*) son los del
   sistema. El tema oscuro sale gratis por eso mismo: todos los tokens
   que se usan aqui ya se redefinen en la seccion del tema oscuro, asi
   que estas paginas acompanan sin una sola regla extra.

   LO UNICO QUE ESTA SECCION APORTA ES LA MEDIDA DE LECTURA.
   El producto no tiene documentos: tiene tablas, fichas y formularios,
   y ninguno de ellos necesita un ancho de parrafo. Un texto legal a
   1440px de ancho se lee fatal -el ojo pierde el renglon al volver a la
   izquierda-, asi que el carril se limita a 68ch, que es la medida de
   lectura de siempre. Todo lo demas es jerarquia: un h2 que se separa
   de lo anterior mas de lo que se separa de lo suyo.
   --------------------------------------------------------------------- */

.legal-body { background: var(--bg); }

.legal {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  min-height: 100dvh;
}

/* El carril. Uno solo para la cabecera, el documento y el pie: si cada
   uno eligiera el suyo, los tres se desalinearian por la izquierda. */
.legal-caja {
  width: 100%;
  max-width: 68ch;
  margin: 0 auto;
  padding-inline: var(--s5);
}


/* ---------------------------------------------------------------------
   80a. LA CABECERA
   ---------------------------------------------------------------------
   Marca a la izquierda, salida a la derecha. Las dos llevan al mismo
   sitio -la portada- y eso es correcto: quien pulsa un logotipo espera
   ir al inicio, y quien busca "volver" quiere leerlo con palabras.
   --------------------------------------------------------------------- */
.legal-cab {
  border-bottom: 1px solid var(--border);
  background: var(--surface);
}
.legal-cab-fila {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s4);
  min-height: 62px;
}

.legal-logo {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  color: var(--text);
  text-decoration: none;
  font-weight: var(--peso-max);
  letter-spacing: -0.01em;
}
.legal-logo:hover { color: var(--brand-600); }

/* LA MONTURA DEL ISOTIPO. No es decorativa y no se puede quitar:
   ui_marca() emite el asset oficial como <img class="marca-app">, que es
   width:100%/height:100% con object-fit:contain (seccion 77). La imagen
   mide LO QUE MIDA SU CONTENEDOR, asi que sin este cuadrado el <img> se
   estira a todo el hueco del flex.
   MEDIDO al implementar esto: sin la montura, la M ocupaba la cabecera
   entera, unos 250px de alto. Es la octava montura del sistema y sigue
   la misma regla que las otras siete: cuadrada, sin azulejo. */
.legal-logo-icono {
  flex: none;
  display: inline-flex;
  width: 34px;
  height: 34px;
}

.legal-volver {
  display: inline-flex;
  align-items: center;
  gap: var(--s1);
  min-height: 38px;
  color: var(--text-2);
  text-decoration: none;
  font-size: var(--fs-sm);
  font-weight: var(--peso-medio);
}
.legal-volver:hover { color: var(--brand-600); }


/* ---------------------------------------------------------------------
   80b. EL DOCUMENTO
   --------------------------------------------------------------------- */
.legal-doc {
  flex: 1 1 auto;
  padding-block: var(--s6) var(--s7);
}

.legal-tipo {
  margin: 0 0 var(--s2);
  font-size: var(--fs-3xs);
  font-weight: var(--peso-fuerte);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-3);
}

.legal-tit {
  margin: 0;
  font-size: var(--fs-2xl);
  font-weight: var(--peso-max);
  line-height: 1.2;
  letter-spacing: -0.02em;
  color: var(--text);
}

.legal-entradilla {
  margin: var(--s3) 0 0;
  font-size: var(--fs-lg);
  line-height: 1.55;
  color: var(--text-2);
}

/* La fecha cierra el encabezado. Va separada por una linea porque es lo
   que convierte el bloque de arriba en "la portada del documento" y lo
   de abajo en el documento. */
.legal-fecha {
  margin: var(--s4) 0 0;
  padding-bottom: var(--s5);
  border-bottom: 1px solid var(--border);
  font-size: var(--fs-xs);
  color: var(--text-3);
}

/* El cuerpo. Los selectores cuelgan de .legal-doc y no de clases
   propias a proposito: el HTML de los tres documentos es prosa -h2, p,
   ul, a- y obligarle a llevar una clase por etiqueta convertiria
   escribir un parrafo en un ejercicio de CSS. */
.legal-doc h2 {
  margin: var(--s6) 0 var(--s3);
  font-size: var(--fs-lg);
  font-weight: var(--peso-max);
  line-height: 1.3;
  letter-spacing: -0.01em;
  color: var(--text);
}

.legal-doc p {
  margin: 0 0 var(--s3);
  font-size: var(--fs-base);
  line-height: 1.7;
  color: var(--text-2);
}
.legal-doc p strong { color: var(--text); font-weight: var(--peso-fuerte); }

.legal-doc a {
  color: var(--brand-600);
  font-weight: var(--peso-medio);
  text-decoration: underline;
  text-underline-offset: 2px;
}
.legal-doc a:hover { color: var(--brand-700); }

.legal-lista {
  margin: 0 0 var(--s4);
  padding-left: var(--s5);
  list-style: disc;
}
.legal-lista li {
  margin-bottom: var(--s2);
  font-size: var(--fs-base);
  line-height: 1.7;
  color: var(--text-2);
}
.legal-lista li strong { color: var(--text); font-weight: var(--peso-fuerte); }

/* Una aclaracion, no un parrafo mas. Mismo texto en menor tamano y con
   un filo a la izquierda: se lee como una nota al margen que se quedo
   dentro del renglon. */
.legal-doc .legal-nota {
  margin: 0 0 var(--s4);
  padding-left: var(--s4);
  border-left: 2px solid var(--border-2);
  font-size: var(--fs-sm);
  line-height: 1.65;
  color: var(--text-3);
}

/* El unico bloque con tono del sistema: lo que FALTA por configurar.
   Se usa --info porque es un dato pendiente, no un error de nadie. */
.legal-doc .legal-pendiente {
  display: flex;
  align-items: flex-start;
  gap: var(--s3);
  margin: 0 0 var(--s4);
  padding: var(--s3) var(--s4);
  border: 1px solid color-mix(in srgb, var(--info) 22%, transparent);
  border-radius: var(--r-md);
  background: var(--info-bg);
  font-size: var(--fs-sm);
  line-height: 1.6;
  color: var(--text-2);
}
.legal-doc .legal-pendiente svg { flex: none; margin-top: 2px; color: var(--info); }
.legal-doc .legal-pendiente strong { display: block; color: var(--text); }

/* EL CONTACTO. Una pieza HUNDIDA, no un panel elevado: el documento es
   una hoja plana y lo unico que tiene que hacer este bloque es separar
   la pregunta del texto corrido. Por eso --surface-2, que en claro es
   hueco y en oscuro es realce, y ni una sombra.

   Los tres documentos lo pintan con includes/vistas/legal_contacto.php.
   Aqui no hay nada especifico de una pagina. */
.legal-contacto {
  margin: 0 0 var(--s4);
  padding: var(--s4);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--surface-2);
}
.legal-doc .legal-contacto-tit {
  margin: 0 0 var(--s2);
  font-size: var(--fs-base);
  font-weight: var(--peso-fuerte);
  color: var(--text);
}
.legal-doc .legal-contacto-txt,
.legal-doc .legal-contacto-sin {
  margin: 0;
  font-size: var(--fs-sm);
  line-height: 1.65;
  color: var(--text-2);
}
.legal-doc .legal-contacto-txt { margin-bottom: var(--s4); }

/* El boton es el .btn del sistema. Lo unico que hace falta aqui es
   deshacer lo que '.legal-doc a' le hace a CUALQUIER enlace del
   documento -color de marca y subrayado-, que tiene mas especificidad
   que .btn y le ganaria. */
.legal-doc .legal-contacto-btn,
.legal-doc .legal-contacto-btn:hover {
  color: var(--btn-primario-texto);
  text-decoration: none;
  font-weight: 600;
}
.legal-doc .legal-contacto-btn svg { flex: none; }

.legal-doc code {
  padding: 1px 5px;
  border: 1px solid var(--border);
  border-radius: var(--r-xs);
  background: var(--surface-2);
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: .92em;
  color: var(--text);
}

/* La tabla de cookies. Una sola fila hoy, y aun asi es una tabla: lo
   que se compara son tres atributos del MISMO objeto, y el dia que haya
   una segunda cookie la lista ya esta hecha.

   El envoltorio con desplazamiento horizontal es lo que evita que a
   320px la tabla ensanche la pagina entera. */
.legal-tabla-envoltura {
  margin: 0 0 var(--s4);
  overflow-x: auto;
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--surface);
}
.legal-tabla {
  width: 100%;
  min-width: 400px;
  border-collapse: collapse;

  /* Un escalon por debajo de la prosa. No es para que quepa mas -que
     tambien-: es que una tabla de datos tecnicos leida DENTRO de un
     documento no compite con el texto que la presenta. */
  font-size: var(--fs-xs);
}
.legal-tabla th,
.legal-tabla td {
  padding: var(--s3);
  text-align: left;
  vertical-align: top;
  line-height: 1.6;
  border-bottom: 1px solid var(--border);
}
.legal-tabla thead th {
  font-size: var(--fs-2xs);
  font-weight: var(--peso-fuerte);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-3);
  background: var(--surface-2);
}
/* El reparto de las tres columnas. MEDIDO a 800px de ventana, donde el
   carril de lectura deja unos 600px de tabla: sin repartir, el
   navegador daba a "Nombre" -una sola palabra, INVUNIV_SESSID- casi el
   mismo ancho que a la descripcion, y la descripcion, que es el unico
   texto largo de la fila, caia en ocho renglones con la mitad de la
   tabla vacia al lado. La columna que tiene que respirar es la de en
   medio. */
.legal-tabla th:nth-child(1), .legal-tabla td:nth-child(1) { width: 24%; }
.legal-tabla th:nth-child(3), .legal-tabla td:nth-child(3) { width: 24%; }

.legal-tabla tbody td { color: var(--text-2); }
.legal-tabla tbody td code { white-space: nowrap; }
.legal-tabla tbody tr:last-child th,
.legal-tabla tbody tr:last-child td { border-bottom: 0; }


/* ---------------------------------------------------------------------
   80c. EL PIE
   --------------------------------------------------------------------- */
.legal-pie {
  border-top: 1px solid var(--border);
  background: var(--surface);
  padding-block: var(--s5) calc(var(--s5) + env(safe-area-inset-bottom));
}
.legal-pie-enlaces {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2) var(--s5);
}
.legal-pie-enlaces a {
  min-height: 34px;
  display: inline-flex;
  align-items: center;
  font-size: var(--fs-sm);
  color: var(--text-2);
  text-decoration: none;
}
.legal-pie-enlaces a:hover { color: var(--brand-600); text-decoration: underline; }

.legal-pie-firma {
  margin: var(--s3) 0 0;
  font-size: var(--fs-xs);
  color: var(--text-3);
}


/* ---------------------------------------------------------------------
   80d. TELEFONO
   ---------------------------------------------------------------------
   El breakpoint canonico del proyecto. Solo se ajusta el respiro
   lateral y el tamano del titulo: la medida de lectura ya no manda por
   debajo de 68ch, la manda el ancho de la pantalla.
   --------------------------------------------------------------------- */
@media (max-width: 767px) {
  .legal-caja { padding-inline: var(--s4); }
  .legal-doc  { padding-block: var(--s5) var(--s6); }
  .legal-tit  { font-size: var(--fs-xl); }
  .legal-entradilla { font-size: var(--fs-base); }
  .legal-doc h2 { margin-top: var(--s5); }

  /* "Volver al inicio" pierde las palabras y se queda en una sola: a
     360px la cabecera con el nombre de la marca y cuatro palabras mas
     se parte en dos renglones. */
  .legal-volver span { display: none; }
  .legal-volver::after {
    content: "Inicio";
    font-size: var(--fs-sm);
  }

  /* -------------------------------------------------------------------
     LA TABLA SE APILA
     -------------------------------------------------------------------
     MEDIDO a 375px: el envoltorio deja 343px y la tabla pide 400 de
     minimo, asi que la tercera columna quedaba CORTADA -"Has / hora /
     hast / cier / sesi"- detras de un desplazamiento horizontal que
     nadie descubre. Una columna de 96px no es una columna: es una
     palabra partida cinco veces.

     Apilada, cada celda es un renglon con su rotulo delante, que es
     como el resto del producto ensena una tabla en telefono. El
     <thead> sobra entonces, y se esconde de forma accesible: sigue
     existiendo para un lector de pantalla que lea la tabla como tabla.
     ------------------------------------------------------------------- */
  .legal-tabla-envoltura { overflow-x: visible; }

  .legal-tabla { min-width: 0; }

  .legal-tabla thead {
    position: absolute;
    width: 1px; height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  .legal-tabla, .legal-tabla tbody, .legal-tabla tr, .legal-tabla td { display: block; }

  .legal-tabla tr { padding: var(--s2) 0; }

  /* width:100% porque una celda en bloque dentro de una tabla no hereda
     el ancho de su fila: es la misma trampa que ya documenta la seccion
     de movimientos. Sin esto, cada renglon mide lo que mida su texto. */
  .legal-tabla td {
    width: 100%;
    padding: var(--s2) var(--s3);
    border-bottom: 0;
  }

  /* El reparto en porcentajes de la version de escritorio hay que
     ANULARLO aqui, y no basta con el width:100% de arriba: los
     selectores :nth-child pesan mas que .legal-tabla td y seguian
     mandando. MEDIDO: sin esta regla, "Cuánto dura" se quedaba en el
     24% -unos 80px- y apilada seguia partiendo "Hasta 2 / horas, o /
     hasta que...". Se repite el selector, con la misma especificidad y
     mas abajo, que es la unica forma limpia de ganarle. */
  .legal-tabla th:nth-child(1), .legal-tabla td:nth-child(1),
  .legal-tabla th:nth-child(3), .legal-tabla td:nth-child(3) { width: 100%; }
  .legal-tabla td::before {
    content: attr(data-etq);
    display: block;
    margin-bottom: var(--s0);
    font-size: var(--fs-2xs);
    font-weight: var(--peso-fuerte);
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--text-3);
  }
  .legal-tabla tbody tr + tr { border-top: 1px solid var(--border); }
}


/* =====================================================================
   81. EL AVISO DE COOKIES
   =====================================================================
   Una franja abajo. NO es un modal: no hay capa oscura, no bloquea, no
   captura el foco y no impide leer ni pulsar nada de la pagina que hay
   detras. Informar no es pedir permiso (el porque esta en
   includes/vistas/aviso_cookies.php).

   Vive en app.css y no en landing.css porque lo pintan CINCO paginas
   -la portada, entrar, el registro, la pantalla de exito y recuperar- y
   landing.css solo lo carga la primera.
   --------------------------------------------------------------------- */

.cookie-aviso {
  position: fixed;
  inset: auto 0 0 0;

  /* --z-toast y no un numero suelto. El proyecto tiene una escala
     -nav 100, dropdown 200, modal 300, hoja 310, toast 400- y este
     aviso es exactamente un toast: flota sobre todo, no pertenece a
     ninguna pantalla y se va cuando se le dice.

     Aqui habia escrito un 60 a mano. COMPROBADO con elementFromPoint,
     con 60 el aviso seguia recibiendo los clics en la portada, asi que
     no estaba tapado hoy; pero 60 queda por debajo del 120, el 190 y el
     200 que landing.css usa en su cabecera y en su menu de telefono, y
     por debajo de --z-modal y --z-hoja del producto. Era un numero que
     funcionaba por casualidad: el dia que el aviso coincidiera con una
     hoja abierta, se pintaria detras. */
  z-index: var(--z-toast);
  padding: var(--s3) var(--s4) calc(var(--s3) + env(safe-area-inset-bottom));
  pointer-events: none;   /* solo la caja recibe clics, no la franja */
}
.cookie-aviso[hidden] { display: none; }

.cookie-aviso-caja {
  pointer-events: auto;
  display: flex;
  align-items: center;
  gap: var(--s3);
  width: 100%;
  max-width: 760px;
  margin: 0 auto;
  padding: var(--s3) var(--s4);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  background: var(--surface);
  box-shadow: var(--sh-3);

  /* El estado de partida de la entrada. La clase que lo anula la pone
     public/js/cookies.js en el fotograma siguiente. */
  opacity: 0;
  transform: translateY(12px);
  transition: opacity .24s ease, transform .24s ease;
}
.cookie-aviso-dentro .cookie-aviso-caja {
  opacity: 1;
  transform: none;
}

.cookie-aviso-icono {
  flex: none;
  display: inline-flex;
  color: var(--info);
}

.cookie-aviso-txt {
  flex: 1 1 auto;
  margin: 0;
  font-size: var(--fs-sm);
  line-height: 1.55;
  color: var(--text-2);
}
.cookie-aviso-txt a {
  color: var(--brand-600);
  font-weight: var(--peso-medio);
  text-decoration: underline;
  text-underline-offset: 2px;
}
.cookie-aviso-txt a:hover { color: var(--brand-700); }

.cookie-aviso-ok { flex: none; }


/* Telefono: la caja se apila y el boton ocupa el ancho. Medido a 360:
   en fila, el texto se quedaba en 150px y "Entendido" caia en dos
   renglones. */
@media (max-width: 767px) {
  .cookie-aviso { padding: var(--s2) var(--s3) calc(var(--s2) + env(safe-area-inset-bottom)); }
  .cookie-aviso-caja {
    align-items: flex-start;
    flex-wrap: wrap;
    gap: var(--s2) var(--s3);
  }
  .cookie-aviso-txt { flex: 1 1 100%; order: 2; }
  .cookie-aviso-icono { order: 1; margin-top: 2px; }
  .cookie-aviso-ok { order: 3; width: 100%; justify-content: center; }
}

/* Sin movimiento: aparece puesto. La entrada no cuenta nada que no se
   vea igual de bien sin ella. */
@media (prefers-reduced-motion: reduce) {
  .cookie-aviso-caja { transition: none; transform: none; }
}


/* =====================================================================
   82. LOS ENLACES LEGALES DEL PIE
   =====================================================================
   includes/vistas/legal_enlaces.php. Tres enlaces discretos al final de
   las pantallas publicas.

   DISCRETO ES EL REQUISITO, no una consecuencia: --fs-3xs y --text-3,
   que es el mismo par que ya usa .acceso-firma, justo encima de la cual
   se pintan en entrar y en el registro. Si se vieran mas que el boton
   de entrar, estarian mal.
   --------------------------------------------------------------------- */
.legal-enlaces {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--s1) var(--s4);
  margin-top: var(--s4);
}
.legal-enlaces a {
  display: inline-flex;
  align-items: center;
  min-height: 32px;
  font-size: var(--fs-3xs);
  font-weight: var(--peso-medio);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-3);
  text-decoration: none;
}
.legal-enlaces a:hover { color: var(--brand-600); text-decoration: underline; }


/* ---------------------------------------------------------------------
   82a. EL BLOQUE LEGAL DEL REGISTRO
   ---------------------------------------------------------------------
   La casilla de siempre (.casilla-legal, que ya existia y no se toca) y
   los dos enlaces que la acompanan, en una sola pieza con un fondo muy
   tenue. El fondo no es decoracion: es lo que impide que el paso legal
   se lea como un renglon mas del formulario justo antes del boton de
   enviar.

   Los enlaces van en su propio renglon y con area de pulsado propia. En
   un telefono, dos enlaces incrustados en una frase de --fs-sm son dos
   objetivos de 13px pegados a una casilla: o se falla el enlace o se
   marca la casilla sin querer.
   --------------------------------------------------------------------- */
.registro-legal {
  margin: var(--s4) 0;
  padding: var(--s3) var(--s4);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--surface-2);
}

/* La casilla ya trae su margen vertical de .casilla-legal; dentro de la
   pieza sobra, porque el relleno de la pieza ya separa. */
.registro-legal .casilla-legal { margin: 0; }

.registro-legal-enlaces {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s1) var(--s4);
  margin: var(--s2) 0 0;
  padding-left: calc(18px + var(--s3));   /* alineado con el texto de la casilla */
}
.registro-legal-enlaces a {
  display: inline-flex;
  align-items: center;
  min-height: 34px;
  font-size: var(--fs-xs);
  font-weight: var(--peso-fuerte);
  color: var(--brand-600);
  text-decoration: underline;
  text-underline-offset: 2px;
}
.registro-legal-enlaces a:hover { color: var(--brand-700); }

@media (max-width: 767px) {
  /* En telefono la casilla mide 22px (ver la regla de .casilla-legal en
     la seccion movil), asi que la sangria de los enlaces la sigue. */
  .registro-legal-enlaces { padding-left: calc(22px + var(--s3)); }
}
