/* ===========================================================
   ERP - Sistema de diseño base
   Filosofía: densidad de información > estética decorativa.
   Tipografía legible (~14-16px), radios mínimos, espaciado ajustado.
   Esto tiene que sentirse como Tango/Bejerman/SAP, no como una landing.
   =========================================================== */

:root {
    /* =====================================================================
       LA PALETA CLARA VIVE ACÁ Y NO EN `temas.css`, Y ESTÁ MEDIDO POR QUÉ.

       Se probó mudarla entera: queda más lindo tener los dos valores de cada
       color —el claro y el oscuro— uno al lado del otro. NO SE PUEDE, y el
       motivo son cinco pantallas que cargan ESTA hoja sin la otra:

         · `public/login.php`                 (la primera que ve cualquiera)
         · `public/ayuda/index.php`
         · `core/requerirPermiso.php`         (el "no tenés permiso")
         · `public/backup/descargar.php`
         · `public/reportes/exportar.php`

       Con los valores del otro lado, las cinco salen SIN UN SOLO COLOR: las
       reglas están pero los tokens no resuelven, así que cada declaración que
       los usa es inválida y el navegador cae al valor inicial. El login sale
       en blanco y negro sin bordes.

       Se podría agregarles el `<link>` a las cinco, y se probó: anda. Pero la
       regla que queda es peor —"toda pantalla nueva tiene que acordarse de
       cargar las DOS hojas o se ve sin color"— y el modo de fallar es de los
       que no se reportan, porque el que la escribe copia de una que sí las
       tiene. Es más barato que los VALORES vivan donde están las REGLAS.

       Así que la división es la que `temas.css` dice en su cabecera desde el
       primer día: acá se DECLARA cada token con su valor claro, y allá se lo
       REASIGNA para el modo oscuro y para cada marca. Que este bloque tenga
       hex no es deuda de migración: es la definición.
       ===================================================================== */
    /* =====================================================================
       LA PALETA CLARA. Vino entera de `app.css` y no se le cambió un dígito:
       una instalación que hoy está andando no cambia ni un píxel de color.
       Está acá para que cada valor tenga a la vista su pareja oscura, que se
       declara más abajo en el bloque de `@media screen`. Cambiar uno solo de
       los dos es el error que esta vecindad viene a hacer difícil.
       ===================================================================== */
    /* Paleta: neutros para el 90% de la UI, color solo para estado/acción */
    --color-fondo: #eef1f4;
    --color-superficie: #ffffff;
    --color-superficie-alt: #f6f8fa;
    --color-texto: #1c2430;
    /* EL SECUNDARIO ES `#626d7b` Y ESTÁ MEDIDO CONTRA LOS NUEVE FONDOS CLAROS.
       Era `#66707c`, que llega a 4.5:1 sobre el blanco (5.03) pero NO sobre los
       cinco fondos tenues donde este texto vive la mitad del tiempo: el fondo de
       página 4.44, exito-fondo 4.43, primario-suave 4.34, info-fondo 4.34 y
       error-fondo 4.32. Eran cinco incumplimientos y se leían como dos casos
       sueltos —la nota al pie del reporte y el hover del encabezado ordenable—
       porque son los dos que alguien llegó a medir.
       `#626d7b` pasa en los nueve (el peor es error-fondo, 4.52) y está a ΔE2000
       1.50 del anterior: no se distingue ni lado a lado. `dev/test_tema.php` lo
       verifica contra los nueve y ya no declara ninguna deuda de este par. */
    --color-texto-suave: #626d7b;
    --color-texto-tenue: #99a1ab;
    --color-borde: #d7dce2;
    --color-borde-fuerte: #b9c1c9;

    --color-primario: #1c4e73;
    --color-primario-hover: #163d5a;
    --color-primario-suave: #e8eff5;

    /* LA BARRA SUPERIOR Y SU HOVER, con el color de fábrica.

       Cada marca los reasigna en `temas.css` —y son los ÚNICOS que el bloque
       oscuro NO redefine, porque la barra es la misma en los dos temas— pero el
       valor base tiene que estar acá igual que el de `--color-primario`: la
       ayuda dibuja su propia barra con `--color-barra` y carga esta hoja sola.
       Sin esto la barra de la ayuda sale transparente.

       Los cazó la guarda de más abajo el día que se escribió, que es para lo
       que existe: no estaban en la lista de los quince porque no se declaraban
       en NINGÚN `:root` base, sino sólo adentro de cada bloque de marca. */
    --color-barra: #1c4e73;
    --color-barra-hover: #163d5a;

    --color-exito: #1c6b2e;
    --color-exito-fondo: #e6f4e8;
    --color-error: #a3231c;
    --color-error-fondo: #fbeae9;
    --color-alerta: #8a5a00;
    --color-alerta-fondo: #fdf3e0;
    --color-info: #1c4e73;
    --color-info-fondo: #e8eff5;
    /* Un escalón más oscuro que el anterior, para el panel que hay que
       encontrar de una mirada entre otros paneles. */
    --color-info-fondo-fuerte: #d8e2eb;

    /* El fondo del recuadro de campos. Es un beige apenas tibio: lo que tiene
       que lograr es que el campo BLANCO de adentro se recorte contra él, así
       que el contraste vive entre estos dos y no en el borde del campo. Con el
       gris de `--color-superficie-alt` el input blanco se lee como un hueco;
       con el beige se lee como un campo. */
    --color-campos-fondo: #faf6ea;
    --color-campos-borde: #e6ddc4;

    /**
     * =====================================================================
     * LOS COLORES QUE NO SIGUEN AL TEMA, Y NO ES QUE FALTE MIGRARLOS
     * =====================================================================
     *
     * Hay superficies en el sistema que son oscuras SIEMPRE y otras que son
     * claras siempre, en modo claro y en modo oscuro por igual: la barra
     * superior (que lleva el color de la empresa), el velo de espera, el
     * aviso flotante, el fondo de un modal y el mapa, que son mosaicos que
     * nos manda otro servidor. Lo que va ENCIMA de ellas tampoco puede
     * cambiar, porque no cambió aquello sobre lo que se apoya.
     *
     * Por eso estos tokens se declaran una sola vez y NO se redefinen en el
     * bloque oscuro. Que un token exista no significa que tenga dos valores;
     * significa que el valor está dicho en un solo lugar. Sin ellos estos
     * colores eran literales sueltos, y el censo no podía distinguir "está
     * escrito a mano porque nadie lo migró" de "está escrito a mano porque
     * tiene que ser así" — que es justo lo que un censo tiene que separar.
     *
     * `dev/test_tema.php` verifica que ninguno de los seis se redefina en un
     * bloque oscuro: el día que alguien lo haga, la barra o el velo se
     * romperían en un solo tema y eso no se ve hasta que alguien lo abre.
     */
    --color-sobre-oscuro: #ffffff;
    --color-sobre-claro:  #1c2430;

    /* El rojo del icono de "sin señal" de la barra. NO es `--color-error`:
       aquél (`#a3231c`) está pensado para texto sobre papel blanco y sobre la
       barra queda casi negro. Éste es el mismo rojo aclarado para fondo
       oscuro y da 3.49:1 contra la marca de fábrica — no llega a 4.5:1 y no
       tiene que llegar: es un INDICADOR gráfico y no texto, así que el piso
       que le corresponde es el 3:1 de WCAG para contenido no textual. */
    --color-error-sobre-oscuro: #ff7b72;

    /* EL VELO QUE ACLARA UNA SUPERFICIE QUE YA ES OSCURA. Es el relleno y el
       borde de los chips de la barra, y el de la rueda de espera. Blanco en
       los dos temas, por lo mismo que los dos de arriba. OJO: no confundir
       con `--velo-rgb`, que SÍ cambia —ése tapa una superficie DEL TEMA, como
       la miniatura de una foto, y en oscuro tiene que oscurecer. */
    --velo-barra-rgb: 255, 255, 255;

    /* EL FONDO DEL MODAL. Oscurece en los dos temas: lo que dice es "esto de
       atrás está inactivo", y en oscuro apenas se nota porque el fondo ya es
       casi negro — eso está bien, ahí el que separa es el borde de la caja. */
    --scrim-rgb: 20, 26, 33;

    /* LA TINTA DE LAS SOMBRAS. Negra en los dos temas, y no es un olvido: en
       oscuro una sombra negra sobre un fondo casi negro no se ve, y está bien
       que no se vea — ahí la elevación la da la superficie, que es MÁS CLARA
       que el fondo. Un token que le inventara otro valor al modo oscuro
       estaría fingiendo una profundidad que ese tema no usa.
       Había dos tintas conviviendo, `0,0,0` y `20,26,33`; convergieron a la
       primera, que es la mayoría. Medido a los alfas donde se usaban, el
       corrimiento va de ΔE2000 1.30 a 1.92: no se ve. */
    --sombra-rgb: 0, 0, 0;

    /* Un velo gris neutro, el único que no es ni blanco ni la tinta: lo usa el
       ojo de "ver la contraseña", que vive adentro de un campo y por lo tanto
       no sabe si tiene fondo claro u oscuro debajo. Gris medio al 18% se ve
       sobre los dos. */
    --velo-neutro-rgb: 128, 128, 128;

    /**
     * =====================================================================
     * LA PALETA DE DATOS: NO SIGUE A LA MARCA, Y NO TIENE QUE SEGUIRLA
     * =====================================================================
     *
     * Son los colores con los que se dibuja un DATO y no una pieza de la
     * interfaz: las barras de un gráfico y los puntos del mapa de reparto.
     * Están elegidos por una razón que el color de la empresa no puede
     * respetar: tienen que distinguirse ENTRE SÍ, incluso para alguien que no
     * distingue el rojo del verde. La del gráfico está validada para
     * protanopía con separación ΔE 17,3, y la del mapa es una escala
     * categórica de cinco motivos. Atarlas a `--color-primario` haría que en
     * una empresa con marca verde el gráfico tuviera dos series verdes.
     *
     * Entonces, ¿por qué son tokens? Por lo mismo que los seis de arriba: que
     * no sigan al tema no significa que tengan que estar escritos a mano en
     * tres archivos. Acá se los ve juntos, que es la única forma de comprobar
     * que se distinguen, y el censo puede decir "no hay ningún literal suelto"
     * sin asteriscos.
     *
     * SÍ cambian con el tema los dos del gráfico que NO son datos —la grilla y
     * el eje—, porque ésos son cromo y usan `--color-borde`.
     */
    --dato-serie:        #2f7fb5;   /* la serie principal del gráfico */
    --dato-base:         #a8b0b8;   /* el período anterior, gris a propósito */
    --dato-sin-motivo:   #9e9e9e;   /* el punto del mapa que no es un reclamo */
    --dato-motivo-1:     #c62828;   /* debe y está atrasado: hay que ir hoy */
    --dato-motivo-2:     #ef6c00;   /* debe: esta semana */
    --dato-motivo-3:     #6a1b9a;   /* el reclamo que se pidió */
    --dato-motivo-4:     #1565c0;   /* sin visitar */
    --dato-motivo-5:     #00838f;   /* sin comprar */
    /* Un recorrido por vendedor en la comparación del mapa (2026-09): seis
       colores que no se confunden con los de los motivos de los puntos. */
    --dato-recorrido-1:  #d81b60;
    --dato-recorrido-2:  #2e7d32;
    --dato-recorrido-3:  #f9a825;
    --dato-recorrido-4:  #4527a0;
    --dato-recorrido-5:  #6d4c41;
    --dato-recorrido-6:  #37474f;

    /* Hereda del de arriba en vez de repetir el valor: son el mismo blanco y
       lo que cambia es para qué se lo nombra. Dos tokens con el mismo hex
       escrito dos veces es como nacieron `#b8dcc0` y `#bfdcc4`, que terminaron
       a ΔE 2.16 uno del otro sin que nadie lo decidiera. */
    --color-barra-texto: var(--color-sobre-oscuro);

    /* =====================================================================
       LOS QUINCE QUE ESTABAN DEL LADO EQUIVOCADO

       Estos tokens se declaraban en el `:root` de `temas.css` en vez de acá, y
       eso rompía CINCO pantallas: las que cargan esta hoja sin la otra —
       `login.php`, `ayuda/index.php`, `core/requerirPermiso.php`,
       `backup/descargar.php` y `reportes/exportar.php`—. Ahí el `var()` no
       resuelve, la declaración entera queda inválida, y la propiedad cae al
       valor heredado o al inicial.

       No es un problema teórico: el botón Ingresar del login decía
       `color: var(--color-primario-texto)` y salía con el texto OSCURO sobre
       el relleno petróleo, porque el color caía al heredado. Y con él caían el
       `::placeholder`, que volvía al gris del navegador, y el fondo crema del
       campo enfocado, que quedaba transparente. Tres cosas rotas en la primera
       pantalla que ve cualquiera, y ninguna se veía desde una pantalla con
       layout, que es donde se prueba.

       A ninguno hubo que inventarle un valor: los quince ya tenían el suyo, y
       lo único que cambió es dónde está declarado. Así que la regla vuelve a
       ser la que dice la cabecera de `temas.css`: acá se DECLARA cada token con
       su valor claro, y allá se lo REASIGNA para el modo oscuro y para cada
       marca. `dev/test_tema.php` ahora lo exige: todo token que `app.css` USA
       tiene que estar declarado en ESTE bloque.
       ===================================================================== */

    /* Que los controles del sistema operativo —barras de scroll, selects,
       calendarios— acompañen al tema. Sin esto quedan blancos sobre el fondo
       oscuro y se ven como un agujero en la pantalla. */
    color-scheme: light;

    /* ---------------------------------------------------------------------
       TOKENS NUEVOS. Son cuatro y ninguno cambia nada en modo claro: los
       valores de acá abajo son exactamente los literales que hoy están
       escritos a mano en las reglas que los van a usar.
       --------------------------------------------------------------------- */

    /* Qué color lleva el texto que va ENCIMA del relleno de la marca. En claro
       es blanco; en oscuro la marca se aclara y el texto pasa a ser oscuro.
       Sin este token, en modo oscuro el botón primario queda blanco sobre
       celeste: 1.7:1, o sea ilegible. */
    --color-primario-texto: #ffffff;

    /* Lo mismo para el rojo de error: `.boton-peligro` y el globo de novedades
       llevan texto encima. */
    --color-error-texto: #ffffff;
    --color-error-hover: #7e1b16;

    /* Lo mismo para el verde: el POS móvil tiene tres botones rellenos de
       `--color-exito` —GUARDAR, enviar el audio, la cinta de confirmación— y en
       oscuro ese verde se aclara a `#4ade80`, donde el blanco encima da 1.6:1. */
    --color-exito-texto: #ffffff;

    /* LA BARRA SUPERIOR NO CAMBIA DE COLOR CON EL TEMA, y es a propósito.
       La barra es una franja de ancho completo: pintada con el tono claro de
       la marca (el que se usa en modo oscuro) sería una banda celeste brillante
       arriba de una pantalla oscura, que es justo lo que el modo oscuro viene a
       evitar. Con el tono saturado se ve igual que hoy en los dos temas, y
       todos los velos `rgba(255,255,255,…)` de sus botones —que están escritos
       en `app.css` y no se tocan— siguen valiendo.
       El valor lo pone cada bloque de marca, una sola vez, y el bloque oscuro
       NO lo redefine. */

    /* ---------------------------------------------------------------------
       LA CELDA QUE SE PUEDE ESCRIBIR, Y EL CAMPO QUE TIENE EL FOCO.
       Son dos tokens y no uno: el amarillo dice "acá se escribe" ESTÉ O NO
       el cursor adentro, y la crema dice "el cursor está acá". En una
       planilla de carga los dos se ven a la vez, en celdas distintas.
       --------------------------------------------------------------------- */

    /* EL AMARILLO NO ES DECORACIÓN, ES EL AFFORDANCE. En una grilla de carga
       es lo único que distingue una celda que se escribe de una que sólo se
       lee, así que en oscuro no se saca: se traduce. Y el salto que hace
       contra la fila es chico a propósito —medido, 1.07:1 en claro— porque no
       está gritando "botón": está tiñendo. */
    --color-celda-editable: lightgoldenrodyellow;   /* #fafad2 */

    /* SU PAREJA: "esto se calcula solo". En `partials/grilla_items.php` las dos
       conviven EN LA MISMA FILA —se cargan cantidad y precio, y el subtotal
       aparece al lado— así que lo que importa no es sólo que cada una se lea:
       es que se distingan ENTRE SÍ.
       Y la distinción NUNCA FUE POR LUMINANCIA: medido, los dos amarillos del
       modo claro están a 1.02:1 entre ellos. Lo que los separa es el CROMA —el
       editable tiene 40 y el calculado 21, la mitad— más la negrita y la
       alineación a la derecha. El bloque oscuro copia esa relación y no el
       número de contraste; el porqué está escrito allá abajo. */
    --color-celda-calculada: #fdfbe8;

    /* La crema del campo enfocado. Estaba escrita trece veces a mano en la
       hoja —el formulario, el buscador, la planilla, el POS y los campos de
       fecha— y es una sola cosa. En oscuro era la que rompía de verdad: un
       `#fffdf0` con el texto claro encima da 1.29:1, o sea que al entrar en
       un campo el contenido DESAPARECE. */
    --color-campo-foco: #fffdf0;

    /* El hover de la fila perdida, que en reposo ya usa `--color-error-fondo`.
       No es un estado nuevo: es el mismo un escalón más marcado. */
    --color-error-fondo-fuerte: #f7dedc;

    /* EL TEXTO DE AYUDA DEL CAMPO. `#757575` es EXACTAMENTE el gris con el que
       Chrome lo venía dibujando, así que declararlo no cambia nada en claro:
       lo que agrega es un valor para el modo oscuro, donde el navegador seguía
       usando ese mismo gris —`color-scheme` no lo toca— y daba 3.59:1. */
    --color-campo-placeholder: #757575;

    /**
     * EL BORDE DE UNA FRANJA DE ESTADO, QUE NO ES NI EL FONDO NI EL TEXTO.
     *
     * Las franjas del POS móvil —la visita en curso, el permiso de ubicación
     * denegado, la propuesta de visita— se cierran abajo con una línea de 1px
     * del mismo color pero un escalón más marcada que el fondo. Con los tokens
     * que había sólo se podía elegir entre el fondo (invisible) y el texto
     * (una raya que grita), así que la línea estaba escrita a mano con el
     * verde, el rojo y el ámbar de Material.
     *
     * HABÍA DOS FAMILIAS DE ESTE COLOR Y SE ELIGIÓ UNA, MIDIENDO:
     *
     *   · la del POS móvil, que son los pasteles de Material (`#a5d6a7`,
     *     `#ef9a9a`, `#ffe082`): 3 usos, más marcados;
     *   · la del POS de escritorio y las tarjetas de cuenta corriente
     *     (`#b8dcc0`, `#edc4c1`, `#f0d9a6`, `#c3d5e3`, más un `#bfdcc4` a ΔE
     *     2.16 del primero): 9 usos, elegidos contra los `--color-*-fondo` de
     *     este sistema y no contra los de Material.
     *
     * Con un solo token una de las dos se mueve, y están lejos: ΔE2000 5.98 el
     * verde, 12.00 el rojo, 8.09 el ámbar. Gana la segunda porque son nueve
     * usos contra tres y porque ya está afinada contra nuestros fondos. Lo que
     * se corre, entonces, son los tres bordes de las franjas del POS móvil, que
     * quedan un punto más suaves: el verde pasa de 1.45:1 a 1.32:1 contra su
     * fondo, el rojo de 1.85:1 a 1.36:1 y el ámbar de 1.17:1 a 1.26:1.
     *
     * No se les pide 4.5:1 porque no llevan texto encima; se les pide verse.
     */
    --color-exito-borde:  #b8dcc0;
    --color-error-borde:  #edc4c1;
    --color-alerta-borde: #f0d9a6;
    --color-info-borde:   #c3d5e3;

    /**
     * LOS CANALES DEL ROJO, para el latido del botón de pánico.
     *
     * Misma razón que `--velo-rgb`: la animación necesita el color con tres
     * alfas distintas dentro de un `box-shadow`, y `rgba()` quiere canales.
     * En claro son los de `--color-error` (el literal que había, `#b3261e`,
     * está a ΔE 3.34 de él: no se nota). En oscuro son los del rojo claro,
     * porque un halo rojo oscuro sobre fondo oscuro no es un halo.
     */
    --color-error-rgb: 163, 35, 28;

    /**
     * LOS CANALES DEL VELO, Y NO UN COLOR ENTERO.
     *
     * El velo que tapa una foto mientras se sube es la MISMA superficie en
     * tres opacidades distintas (0,55 el de la miniatura · 0,86 el del modal ·
     * 0,92 el de la píldora de estado), y cada una está elegida para lo que
     * tapa. Con un token por color harían falta tres, y el día que alguien
     * cambie uno los otros dos quedarían contando otra historia.
     *
     * Guardando los CANALES, cada regla conserva su alfa tal cual y lo único
     * que cambia con el tema es sobre qué se compone: en claro es el blanco
     * exacto que ya estaba escrito —o sea que la pantalla clara no se mueve— y
     * en oscuro es el casi negro del fondo, así que el velo oscurece en vez de
     * dejar un parche blanco sobre una miniatura.
     */
    --velo-rgb: 255, 255, 255;

    /* Radio mínimo: esto no es una app de consumo */
    --radio: 3px;
    --radio-chico: 6px;

    /* Escala de espaciado en base 4px */
    --e1: 0.25rem;  /* 4px */
    --e2: 0.5rem;   /* 8px */
    --e3: 0.75rem;  /* 12px */
    --e4: 1rem;     /* 16px */
    --e5: 1.5rem;   /* 24px */

    --fuente-base: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
    --fuente-mono: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;

    /* EL BOTÓN SE MIDE EN PÍXELES Y NO EN LA ESCALA `rem`.

       Todo lo demás de esta hoja es relativo a los 13,5px del `html`, pero el
       botón es la pieza que se repite en las ciento y pico de pantallas y su
       medida se decidió mirándolo, no calculándolo: a `1.16rem` la letra salía
       15,7px y el relleno 6,75 × 10,1 —un botón más alto que el input de 30px
       que casi siempre tiene al lado en la fila de filtros—. Escrito en píxeles,
       el alto queda clavado en ~30 y la fila cierra. */
    --boton-relleno-y: 6px;
    --boton-relleno-x: 10px;
    --boton-cuerpo: 13px;
    /* El chico va un escalón abajo del normal, y por eso se toca junto con el
       de arriba: dejarlo en `rem` mientras el común pasa a píxeles lo deja con
       la LETRA MÁS GRANDE que la del botón normal (14px contra 13). */
    --boton-chico-relleno-y: 4px;
    --boton-chico-relleno-x: 8px;
    --boton-chico-cuerpo: 12px;

    /* EL ALTO DE UN CONTROL DE UNA LÍNEA: input, select y campo de fecha.
       Los 30px ya eran la medida de la fila de filtros (`.form-erp`), del
       buscador de entidades y de los filtros del kardex; están todos acá para
       que no se separen, y el botón la alcanza con su relleno. El formulario
       de ABM era el único que iba por afuera —37px los campos y 36 el select—,
       así que un campo y el botón de al lado no cerraban en ningún renglón. */
    --alto-control: 30px;

    /* 49 y no 44: la barra subió 5px a pedido, para que lo que lleva adentro
       respire. NO SE TOCA NADA MÁS, y ése es el punto de que sea un token: de
       acá lo sacan el alto de la barra, el `top` y el alto del menú lateral,
       el `top` del encabezado congelado de las grillas y el alto de la
       pantalla del POS móvil (`100dvh -` esto). Escrito a mano en cualquiera
       de esos lugares, subir la barra dejaría un hueco o taparía un renglón. */
    --altura-header: 49px;
    --ancho-sidebar: 200px;
    --altura-fila: 30px;
}

/* =====================================================================
   EN UN CELULAR LO QUE SE TOCA TIENE QUE SER MÁS ALTO
   =====================================================================
   Las medidas de arriba se decidieron mirando una pantalla con MOUSE: 30px
   de alto es cómodo para un puntero de un píxel y es lo que hace que la fila
   de filtros cierre contra el botón de al lado. Con el DEDO no alcanza —un
   renglón de 30px en una grilla de clientes se toca mal, y se toca el de al
   lado—, y es lo que `public/pos-movil/_estilos.php` ya tenía escrito para
   sus propias pantallas: «48px de alto en todo lo que se toca».

   SE TOCA ACÁ, EN LOS TOKENS, Y NO REGLA POR REGLA. Son tres medidas que
   tienen que moverse JUNTAS o la pantalla se descose:

   - `--altura-fila` es el alto del renglón de `.tabla-datos`;
   - `--alto-control` es el de un input, un select y un campo de fecha;
   - `--boton-relleno-y` es lo que le da su alto al botón, que no declara
     ninguno.

   Subiendo sólo el botón, la fila de filtros queda con un control de 30 al
   lado de uno de 44 —que es exactamente el desajuste que el comentario de
   `--alto-control` cuenta que hubo que arreglar una vez—. Subiendo sólo los
   controles, pasa lo mismo al revés.

   POS MÓVIL HEREDA ESTO Y CASI NO LO NECESITA: sus pantallas cargan
   `app.css` y encima `_estilos.php`, donde todo lo que se toca ya está
   declarado en 48px con reglas más específicas. Lo que sí le llega es lo
   que no está redeclarado allá — el `.boton-primario` de la ficha de
   diagnóstico de visitas, por ejemplo.

   Y NO SE TOCA LA LETRA. El cuerpo del botón decide su ANCHO, y una barra
   con tres botones que hoy entra justo empezaría a envolver en dos
   renglones. Lo que se pidió es alto, no tamaño de texto.
   ===================================================================== */
@media (max-width: 700px) {
    :root {
        --altura-fila: 44px;
        --alto-control: 44px;
        --boton-relleno-y: 13px;
        --boton-chico-relleno-y: 9px;
    }
}

* {
    box-sizing: border-box; user-select: none;
}

html {
    font-size: 13.5px; /* base del sistema: todo se mide relativo a esto */
}

body {
    margin: 0;
    font-family: var(--fuente-base);
    background: var(--color-fondo);
    color: var(--color-texto);
    font-size: 1.12rem; /* ~15px */
    line-height: 1.4;
    -webkit-font-smoothing: antialiased;
}

h1, h2, h3, h4 {
    margin: 0 0 var(--e3);
    font-weight: 600;
    color: var(--color-texto);
    line-height: 1.3;
}
h1 { font-size: 1.36rem; }
h2 { font-size: 1.18rem; margin-top: var(--e4); }
h3 { font-size: 1.12rem; text-transform: uppercase; letter-spacing: 0.03em; color: var(--color-texto-suave); }

p { margin: 0 0 var(--e3); }
a { color: var(--color-primario); }
small { font-size: 1rem; color: var(--color-texto-suave); }
hr { border: none; border-top: 1px solid var(--color-borde); margin: var(--e4) 0; }

/**
 * =====================================================================
 * EL TEXTO DE UN CAMPO SE DECLARA, NO SE HEREDA DEL NAVEGADOR
 * =====================================================================
 * Los controles de formulario NO heredan `color` del `body`: se lo pone la
 * hoja del USUARIO. O sea que hasta acá el texto de TODO campo del sistema
 * —el negro en claro, el blanco en oscuro— lo decidía el navegador, y lo
 * único que lo daba vuelta con el tema era `color-scheme: dark`.
 *
 * Funciona en todo navegador moderno y aun así no puede quedar así: la
 * legibilidad de cada importe que se tipea colgaba de UNA declaración, y un
 * motor que la ignore deja texto negro sobre el ámbar de la celda editable
 * (1.46:1). Es la misma regla que el resto del sistema ya cumple —el color
 * sale de un token— aplicada al único lugar donde no se cumplía.
 *
 * Va con especificidad de elemento (0,0,1), o sea la más baja: todo lo que ya
 * declara su color —`.form-erp input[readonly]`, los campos de fecha— le sigue
 * ganando sin tocar nada.
 *
 * Medido: en claro el texto pasa del negro puro del navegador (21:1) a
 * `--color-texto` (15.62:1), que es el mismo con el que está escrito el resto
 * de la pantalla.
 */
input, select, textarea { color: var(--color-texto); }

/**
 * Y EL TEXTO DE AYUDA DEL CAMPO TAMPOCO LO PONE `color-scheme`.
 *
 * Medido: Chrome dibuja el `::placeholder` en `rgb(117,117,117)` en los DOS
 * temas —el `color-scheme: dark` no lo toca— así que sobre la superficie
 * oscura daba **3.59:1** y no llegaba. Es texto que el operador lee para saber
 * qué va en el campo.
 *
 * En claro el token vale exactamente ese mismo `#757575`, así que la pantalla
 * clara no cambia ni un píxel: lo único que esta regla hace es darle un valor
 * al modo oscuro, donde no lo había.
 */
::placeholder { color: var(--color-campo-placeholder); opacity: 1; }

/* ===========================================================
   LOGIN
   =========================================================== */

.pagina-login {
    min-height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--e4);
    background: var(--color-fondo);
}

.contenedor-login {
    background: var(--color-superficie);
    border: 1px solid var(--color-borde);
    border-radius: var(--radio);
    padding: var(--e5);
    width: 100%;
    max-width: 320px;
}

.contenedor-login h1 {
    font-size: 1.24rem;
    color: var(--color-primario);
    margin: 0 0 var(--e4);
    text-align: center;
}

.contenedor-login label {
    display: block;
    font-size: 1rem;
    color: var(--color-texto-suave);
    margin-bottom: var(--e1);
    margin-top: var(--e3);
}

.contenedor-login input {
    width: 100%;
    padding: var(--e2) var(--e2);
    border: 1px solid var(--color-borde);
    border-radius: var(--radio-chico);
    font-size: 1.12rem;
    background: var(--color-superficie);
}

.contenedor-login input:focus {
    outline: none;
    border-color: var(--color-primario);
    box-shadow: 0 0 0 2px var(--color-primario-suave);
}

.contenedor-login button {
    width: 100%;
    margin-top: var(--e4);
    padding: var(--e2);
    background: var(--color-primario);
    color: var(--color-primario-texto);
    border: none;
    border-radius: var(--radio-chico);
    font-size: 1.12rem;
    font-weight: 600;
    cursor: pointer;
}
.contenedor-login button:hover { background: var(--color-primario-hover); }

/* ===========================================================
   ALERTAS
   =========================================================== */

.alerta {
    padding: var(--e2) var(--e3);
    border-radius: var(--radio-chico);
    font-size: 1.06rem;
    margin-bottom: var(--e3);
    border: 1px solid transparent;
}
.alerta-error   { background: var(--color-error-fondo);  color: var(--color-error);  border-color: var(--color-error); }
.alerta-exito   { background: var(--color-exito-fondo);  color: var(--color-exito);  border-color: var(--color-exito); }
.alerta-alerta  { background: var(--color-alerta-fondo); color: var(--color-alerta); border-color: var(--color-alerta); }
.alerta-info    { background: var(--color-info-fondo);   color: var(--color-info);   border-color: var(--color-info); }

/* ===========================================================
   LAYOUT INTERNO (header + sidebar + contenido)
   =========================================================== */

/**
 * El header queda fijo al hacer scroll.
 *
 * `sticky` y no `fixed` por dos motivos: `fixed` lo saca del flujo y habría
 * que empujar el contenido con un padding igual a --altura-header, que es un
 * segundo lugar donde está escrita la misma medida y se desincroniza; y
 * @media print ya lo oculta, cosa que con `fixed` puede dejar un hueco
 * arriba en la primera hoja.
 *
 * El z-index tiene que ganarle al `thead` pegajoso de las grillas (z-index 10)
 * y perder contra los diálogos y modales, que van en 1000.
 */
.barra-superior {
    height: var(--altura-header);
    background: var(--color-barra);
    color: var(--color-barra-texto);
    padding: 0 var(--e4);
    display: flex;
    align-items: center;
    justify-content: space-between;
    font-size: 1.06rem;
    position: sticky;
    top: 0;
    z-index: 60;
}

.barra-superior a { color: var(--color-barra-texto); text-decoration: none; opacity: 0.85; }
.barra-superior a:hover { opacity: 1; text-decoration: underline; }

.marca { font-weight: 700; font-size: 1.12rem; letter-spacing: 0.02em; display: inline-flex; align-items: center; gap: var(--e3); white-space: nowrap; }

/* EL NOMBRE DEL SISTEMA ARRIBA DEL CAJÓN DEL CELULAR, que es a donde se mudó
   desde la barra. Se dibuja como un rótulo y no como la marca de la barra: no
   es un botón ni lleva a ningún lado, así que no puede parecer la primera
   opción del menú — abajo suyo está «Inicio», que sí lo es. */
.menu-marca {
    font-weight: 700;
    font-size: 1.12rem;
    letter-spacing: 0.02em;
    color: var(--color-texto);
    padding: var(--e3) var(--e3) var(--e2);
    border-bottom: 1px solid var(--color-borde);
    margin-bottom: var(--e2);
}
.info-usuario { display: flex; align-items: center; gap: var(--e3); white-space: nowrap; }

/* La marca y la navegación rápida van juntas a la izquierda; el usuario, a la
   derecha. `min-width: 0` para que la lista de recientes pueda recortarse en
   vez de empujar el nombre del usuario fuera de la barra. */
.barra-superior .grupo-izquierda {
    display: flex;
    align-items: center;
    gap: var(--e3);
    min-width: 0;
}

/* Accesos rápidos de la barra: "+ Cliente" y "+ Venta".
 *
 * Se dibujan como botones y no como los enlaces de al lado a propósito: son
 * ACCIONES —abren un alta— y no navegación, y con la misma pinta que "Mi
 * cuenta" o "Cerrar sesión" se leerían como una opción más de la barra.
 *
 * `white-space: nowrap` porque un "+ Cliente" partido en dos renglones rompería
 * el alto de la barra, que es fijo (`--altura-header`). */
.accesos-rapidos { display: flex; align-items: center; gap: var(--e2); }

.barra-superior a.acceso-rapido {
    background: rgba(var(--velo-barra-rgb), 0.14);
    border: 1px solid rgba(var(--velo-barra-rgb), 0.35);
    border-radius: var(--radio);
    padding: 3px var(--e3);
    font-weight: 600;
    font-size: 0.98rem;
    opacity: 1;
    white-space: nowrap;
}
/* EL HOVER DE LA BARRA OSCURECE, NO ACLARA, Y ES UNA DECISIÓN TOMADA.

   Los cuatro hover de la barra —este, `.boton-barra`, `.boton-ayuda` y
   `.nav-boton`— eran un blanco translúcido encima del color de marca. Eso no
   se puede tematizar: un velo blanco ACLARA, así que el contraste que le queda
   al texto blanco de encima depende de cuán oscura sea la marca. Con `petroleo`
   daba 4.43:1 al 26% y 4.69:1 al 24%, pero con `verde` 3.27:1 y con `naranja`
   3.48:1 — siete de las once por debajo del piso, y NO HAY ALFA QUE LAS SALVE:
   bajarlo mejora el número y apaga el hover hasta que deja de notarse.

   `--color-barra-hover` es la variante OSCURA de cada marca, la misma que usa
   el botón primario, y pasa en las once con 7.07:1 en el peor caso (`verde`).
   El costo, que el usuario aceptó: en modo claro el hover de la barra pasó a
   oscurecer en vez de aclarar. Es el gesto que ya tenía cualquier otro botón
   del sistema, así que además quedó consistente.

   Y es un token PROPIO y no `--color-primario-hover` por un motivo que se ve
   recién en oscuro: la barra es el único elemento que NO cambia con el tema
   —los bloques oscuros de marca no redefinen `--color-barra`—, mientras que
   `--color-primario-hover` sí invierte y pasa a ser un celeste. Usarlo crudo
   habría puesto un parche claro sobre la barra oscura, con el texto blanco
   encima: exactamente el problema que se venía a arreglar, del otro lado. */
.barra-superior a.acceso-rapido:hover {
    background: var(--color-barra-hover);
    text-decoration: none;
}

@media print { .accesos-rapidos { display: none !important; } }

/* Navegación rápida: atrás, adelante y las últimas pantallas (partials/navegacion_rapida.php) */
.nav-rapida { display: flex; align-items: center; gap: var(--e1); min-width: 0; }

/* Mismo cuadrado de 26px que .btn-accion, redondeado: los dos son botones de
   icono sin texto y conviene que midan igual en toda la aplicación. */
.nav-boton {
    width: 26px;
    height: 26px;
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    border-radius: 50%;
    border: 1px solid rgba(var(--velo-barra-rgb), 0.4);
    background: rgba(var(--velo-barra-rgb), 0.12);
    color: var(--color-barra-texto);
    cursor: pointer;
}
.nav-boton:hover:not(:disabled) { background: var(--color-barra-hover); }
/* Más apagado que el 0.5 general de los botones: sobre el fondo oscuro de la
   barra, medio opaco todavía se lee como habilitado. */
.nav-boton:disabled { opacity: 0.3; cursor: default; }
.nav-boton svg {
    width: 15px;
    height: 15px;
    fill: none;
    stroke: currentColor;
    stroke-width: 2.5;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.nav-recientes {
    display: flex;
    align-items: center;
    gap: var(--e2);
    min-width: 0;
    margin-left: var(--e2);
}
.nav-recientes a {
    font-size: 0.95rem;
    max-width: 150px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.nav-recientes .separador-nav { opacity: 0.45; }

/* En pantallas angostas las flechas se quedan y los rótulos se van: son lo
   primero que sobra y lo que más lugar ocupa. */
@media (max-width: 1000px) {
    .nav-recientes { display: none; }
}

/* Distintivo de pantalla en la barra superior (hoy: el punto de venta en el POS).
   Grande y legible: el cajero tiene que saber de un vistazo en qué caja está. */
.badge-cabecera {
    background: rgba(var(--velo-barra-rgb), 0.18);
    border: 1px solid rgba(var(--velo-barra-rgb), 0.4);
    border-radius: var(--radio);
    padding: 3px var(--e3);
    font-size: 1.06rem;
    font-weight: 600;
    letter-spacing: 0;
    white-space: nowrap;
}

.layout-app {
    display: flex;
    min-height: calc(100vh - var(--altura-header));
}

.menu-lateral {
    background: var(--color-superficie);
    border-right: 1px solid var(--color-borde);
    width: var(--ancho-sidebar);
    flex-shrink: 0;
    padding: var(--e2) 0;
    display: flex;
    flex-direction: column;
}

.menu-lateral a {
    padding: var(--e2) var(--e4);
    color: var(--color-texto);
    text-decoration: none;
    font-size: 1.06rem;
    border-left: 2px solid transparent;
    white-space: nowrap;
}
.menu-lateral a:hover { background: var(--color-superficie-alt); }
.menu-lateral a.activo {
    border-left-color: var(--color-primario);
    background: var(--color-primario-suave);
    font-weight: 600;
    color: var(--color-primario);
}

.menu-lateral .separador-menu {
    font-size: 0.88rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--color-texto-tenue);
    padding: var(--e3) var(--e4) var(--e1);
}

.layout-app .contenido {
    flex: 1;
    margin: 0;
    max-width: none;
    padding: var(--e4);
    overflow-x: auto;
}

/* ===========================================================
   BARRA DE ACCIONES DE PANTALLA (título + botones principales)
   =========================================================== */

.barra-acciones {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: var(--e3);
    flex-wrap: wrap;
    gap: var(--e2);
}
.barra-acciones h1 { margin: 0; }

/* ===========================================================
   BOTONES
   =========================================================== */

.boton-primario,
.boton-secundario,
.boton-peligro,
.boton-fantasma {
    display: inline-flex;
    align-items: center;
    gap: var(--e1);
    padding: var(--boton-relleno-y) var(--boton-relleno-x);
    border-radius: var(--radio-chico);
    text-decoration: none;
    font-size: var(--boton-cuerpo);
    font-family: var(--fuente-base);
    font-weight: 600;
    border: 1px solid transparent;
    cursor: pointer;
    line-height: 1.2;
}

.boton-primario { background: var(--color-primario); color: var(--color-primario-texto); width: max-content;}
.boton-primario:hover { background: var(--color-primario-hover); }

.boton-secundario { background: var(--color-superficie); color: var(--color-primario); border-color: var(--color-borde-fuerte); }
.boton-secundario:hover { background: var(--color-superficie-alt); }

.boton-peligro { background: var(--color-error); color: var(--color-error-texto); }
.boton-peligro:hover { background: var(--color-error-hover); }

.boton-fantasma { background: transparent; color: var(--color-texto-suave); border-color: transparent; }
.boton-fantasma:hover { background: var(--color-superficie-alt); color: var(--color-texto); }

.boton-chico { padding: var(--boton-chico-relleno-y) var(--boton-chico-relleno-x); font-size: var(--boton-chico-cuerpo); }

/**
 * UN BOTÓN QUE SE VE COMO UN ENLACE, para lo que se toca adentro de una celda.
 *
 * Es un <button> y no un <a> porque NO NAVEGA —abre un modal— y un <a href="#">
 * ensucia el historial y se lleva el Ctrl+clic para nada. Pero un botón con su
 * recuadro adentro de una columna numérica desalinea la grilla entera, así que
 * se dibuja como el enlace que el usuario espera.
 *
 * Hereda el cuerpo y la familia del contexto (`font: inherit`): sin eso toma
 * los del sistema operativo y el número sale con otra letra que la fila.
 */
.boton-enlace {
    background: none;
    border: none;
    padding: 0;
    font: inherit;
    color: var(--color-primario);
    cursor: pointer;
    text-decoration: none;
}
.boton-enlace:hover { text-decoration: underline; }


/* Un enlace que hace de botón y todavía no puede usarse.
   `disabled` no existe para un `<a>` —es un atributo de los controles de
   formulario— así que lo que dice que está apagado es `aria-disabled`, que es
   además lo único que un lector de pantalla puede anunciar. El clic lo frena su
   propio JavaScript: `pointer-events: none` lo dejaría mudo para el teclado. */
a[aria-disabled="true"] {
    opacity: 0.5;
    cursor: not-allowed;
}
button:disabled, .boton-primario[disabled] { opacity: 0.5; cursor: not-allowed; }

/* ===========================================================
   FORMULARIOS (ABM)
   =========================================================== */

.formulario-abm {
    background: var(--color-superficie);
    border: 1px solid var(--color-borde);
    border-radius: var(--radio);
    padding: var(--e4);
    max-width: 520px;
}

.formulario-abm.ancho { max-width: none; }

.campo {
    margin-bottom: var(--e3);
}

.formulario-abm label,
.campo label {
    display: block;
    font-size: 1.09rem;
    color: var(--color-texto-suave);
    margin-bottom: var(--e1);
    font-weight: 600;
}

.formulario-abm label .opcional {
    font-weight: 400;
    color: var(--color-texto-tenue);
    text-transform: none;
}

.requerido::after {
    content: " *";
    color: var(--color-error);
}

.formulario-abm input,
.formulario-abm select,
.formulario-abm textarea,
.campo input,
.campo select,
.campo textarea {
    width: 100%;
    padding: var(--e2);
    border: 1px solid var(--color-borde);
    border-radius: var(--radio-chico);
    font-size: 1.16rem;
    font-family: var(--fuente-base);
    background: var(--color-superficie);
    color: var(--color-texto);
}

.formulario-abm select, .campo select{
    padding: 0 var(--e2); height: var(--alto-control);
}

/* =====================================================================
   EL ALTO DE UN CONTROL DE UNA LÍNEA ES EL MISMO EN TODAS PARTES
   =====================================================================
   30px es lo que ya medían los campos de la fila de filtros (`.form-erp`) y
   es lo que mide el botón: un formulario con los campos de 37 y el select de
   36 al lado de un botón de 30 no tenía dos renglones iguales en ninguna
   pantalla. Sale de `--alto-control`, que es de donde lo sacan también la fila
   de filtros, el buscador de entidades y los filtros del kardex.

   LOS TRES QUE QUEDAN AFUERA, Y POR QUÉ:

   - `checkbox` y `radio`: el alto se lo da el dibujo del navegador (13px). Con
     30px forzados sale un rectángulo de 13 de ancho por 30 de alto, que no se
     lee como una casilla. `.campo-check` ya les saca el `width: 100%` por el
     mismo motivo; esto es la otra mitad.
   - `file`: adentro lleva el botón «Elegir archivo» que dibuja el navegador y
     a 30px queda recortado —se midió: 3px de sobra—. Es un control que aparece
     solo y no en una fila, así que no hay nada con qué alinearlo.
   - `textarea`: es de VARIOS renglones. El de `.formulario-abm` se salva por su
     `min-height: 70px`, pero el de un `.campo` suelto no lo tiene y con la
     altura puesta arriba se desplomaba a un renglón, con el texto cortado.
   ===================================================================== */
.formulario-abm input:not([type="checkbox"]):not([type="radio"]):not([type="file"]),
.campo input:not([type="checkbox"]):not([type="radio"]):not([type="file"]) {
    height: var(--alto-control);
}
.formulario-abm input:focus,
.formulario-abm select:focus,
.formulario-abm textarea:focus,
.campo input:focus,
.campo select:focus,
.campo textarea:focus {
    outline: none;
    border-color: var(--color-primario);
    box-shadow: 0 0 0 2px var(--color-primario-suave);
}

.formulario-abm input[readonly],
.formulario-abm input:disabled {
    background: var(--color-superficie-alt);
    color: var(--color-texto-suave);
}

/* =====================================================================
   EL ID DEL REGISTRO, GRANDE Y COPIABLE
   =====================================================================
   El número interno de la ficha, mostrado para que se lea de un vistazo
   y se pueda copiar: es con lo que se habla del mismo registro en otra
   pantalla o por teléfono, y hasta ahora sólo estaba en la URL.

   Es un `input[readonly]` y no un texto porque el `user-select: none`
   global impide seleccionar un <div>, y copiarlo es justamente para lo
   que se lo mira. El ancho lo da el atributo `size` del HTML —tantos
   caracteres como dígitos tenga el id—, por eso acá va `width: auto`.

   El selector repite los tres `:not()` del alto de control: sin ellos esa
   regla gana por especificidad, el campo queda clavado en los 30px de un
   control de una línea y el número grande sale recortado.
   ===================================================================== */
.formulario-abm input.campo-id-grande:not([type="checkbox"]):not([type="radio"]):not([type="file"]),
.campo input.campo-id-grande:not([type="checkbox"]):not([type="radio"]):not([type="file"]) {
    width: auto;
    height: auto;
    padding: var(--e1) var(--e2);
    font-size: 1.8rem;
    font-weight: 700;
    line-height: 1.2;
    text-align: center;
    color: var(--color-texto);
    background: var(--color-superficie-alt);
}

.formulario-abm input.campo-error,
.campo input.campo-error {
    border-color: var(--color-error);
}
.texto-error-campo {
    font-size: 0.94rem;
    color: var(--color-error);
    margin-top: var(--e1);
}

.formulario-abm textarea { resize: vertical; min-height: 70px; }

/* Fila de campos en columnas (2 o 3 por línea) */
.fila-campos {
    display: grid;
    gap: 0 var(--e3);
}
.fila-campos.cols-2 { grid-template-columns: 1fr 1fr; }
.fila-campos.cols-3 { grid-template-columns: 1fr 1fr 1fr; }

fieldset {
    border: 1px solid var(--color-borde);
    border-radius: var(--radio);
    padding: var(--e3);
    margin: 0 0 var(--e3);
}
fieldset legend {
    font-size: 12px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--color-texto-suave);
    padding: 0 var(--e1);
}

/* Checkbox / radio en línea con su etiqueta */
.campo-check {
    display: flex;
    align-items: center;
    gap: var(--e1);
    margin-bottom: var(--e2);
}
.campo-check input[type="checkbox"],
.campo-check input[type="radio"] {
    width: auto;
    margin: 0;
}
.campo-check label {
    margin: 0;
    font-weight: 400;
    color: var(--color-texto);
    font-size: 1.06rem;
}
/* Un dato que acompaña a la casilla sin ser parte de ella: hoy, la fecha del
   último cambio de precio pegada a «Activo» en la ficha del artículo. La barra
   de la izquierda lo separa de la etiqueta — sin ella, «Activo Última
   modificación…» se lee como una sola frase. */
.campo-check-nota {
    padding-left: var(--e2);
    border-left: 1px solid var(--color-borde);
    white-space: nowrap;
}

/* =====================================================================
   SWITCH ON/OFF, con la forma de Material Design 3
   =====================================================================
   Un switch se usa donde el cambio vale SOLO —sin apretar Guardar— o donde
   decide qué va a hacer el guardado; una casilla común se usa para un campo
   más del formulario. Por eso no reemplaza a `.campo-check`: son dos cosas.

   TRES PIEZAS Y NINGUNA SOBRA. El `<input>` es el control de verdad —tapa el
   riel entero y es lo que reciben el clic, el foco y la barra espaciadora—, el
   riel es la pista, y la perilla es el círculo que se mueve. La perilla tiene
   que ser un ELEMENTO y no un `::before`: adentro va un ícono, y un
   pseudoelemento no puede contener marcado.

   LA PERILLA CRECE AL PRENDERSE (16px → 24px), que es lo que distingue a este
   switch de un interruptor cualquiera: apagado se lee como un hueco con un
   punto, prendido como una perilla llena con su ícono adentro. Las dos quedan
   a la misma distancia del centro del riel (±10px), así que el movimiento se
   ve parejo en los dos sentidos.

   TODO SALE DE LOS TOKENS —apagado usa el borde y la superficie, prendido el
   color de la empresa y lo que va encima— así que el modo oscuro y los once
   colores de empresa lo siguen sin una sola regla más. */
.interruptor {
    position: relative;
    display: inline-flex;
    flex: none;
    width: 52px;
    height: 32px;
    vertical-align: middle;
}
/* El input tapa el riel entero: el clic, el foco y la barra espaciadora son
   suyos, que es lo que hace que esto se opere con el teclado sin una línea de
   JavaScript. Invisible, nunca `display:none` —eso lo sacaría del tabulador—. */
.interruptor input {
    position: absolute;
    inset: 0;
    width: 100%; height: 100%;
    margin: 0;
    opacity: 0;
    cursor: pointer;
    z-index: 1;
}
.interruptor input:disabled { cursor: default; }
.interruptor .rail {
    position: absolute; inset: 0;
    background: var(--color-superficie-alt);
    border: 2px solid var(--color-borde-fuerte);
    border-radius: 999px;
    transition: background 0.15s, border-color 0.15s;
}
.interruptor .perilla {
    position: absolute;
    top: 50%; left: 8px;
    width: 16px; height: 16px;
    margin-top: -8px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--color-borde-fuerte);
    border-radius: 50%;
    transition: left 0.15s, width 0.15s, height 0.15s, margin-top 0.15s, background 0.15s;
}
/* El ícono sólo existe prendido: apagado la perilla mide 16px y adentro no
   entra nada legible. Se va con `opacity` y no con `display`, para que el
   crecimiento de la perilla no dé un salto. */
.interruptor .perilla svg {
    width: 16px; height: 16px;
    opacity: 0;
    color: var(--color-primario);
    transition: opacity 0.15s;
}
.interruptor input:checked + .rail {
    background: var(--color-primario);
    border-color: var(--color-primario);
}
.interruptor input:checked + .rail .perilla {
    left: 24px;
    width: 24px; height: 24px;
    margin-top: -12px;
    background: var(--color-primario-texto);
}
.interruptor input:checked + .rail .perilla svg { opacity: 1; }
/* El foco se ve en el riel, porque el input es invisible. */
.interruptor input:focus-visible + .rail {
    outline: 2px solid var(--color-primario);
    outline-offset: 2px;
}
.interruptor input:disabled + .rail { opacity: 0.5; }

/* Un campo con su switch al lado: el campo se estira y el switch mide lo suyo.
   El `min-width:0` del campo no es decorativo —un hijo de flex no baja de su
   ancho de contenido sin él— y sin eso el switch se sale de la columna. */
.campo-con-interruptor {
    display: flex;
    align-items: center;
    gap: var(--e2);
}
.campo-con-interruptor input { flex: 1 1 auto; min-width: 0; }

.acciones-formulario {
    display: flex;
    gap: var(--e2);
    margin-top: var(--e4);
    padding-top: var(--e3);
    border-top: 1px solid var(--color-borde);
}

.texto-ayuda {
    font-size: 1.04rem;
    color: var(--color-texto-tenue);
    margin-top: var(--e1);
}

/* ===========================================================
   TABLAS DE DATOS
   =========================================================== */

/**
 * OJO CON PONERLE `overflow` A ESTA TABLA.
 *
 * Tenía `overflow: hidden`, puesto para que el `border-radius` recortara las
 * esquinas. No servía para eso —con `border-collapse: collapse` las esquinas no
 * se recortan igual— y encima rompía el encabezado congelado: `overflow`
 * distinto de `visible` convierte a la tabla en un scrollport, y entonces el
 * `sticky` del `th` se resuelve contra ELLA, que no scrollea nunca, en vez de
 * contra `.tabla-contenedor`, que sí. Medido en Chrome: al scrollear la grilla
 * 400px el encabezado se iba 399px con las filas.
 *
 * El redondeo lo hace ahora el contenedor, que tiene su `border-radius` y un
 * `overflow` que sí recorta.
 */
.tabla-datos {
    width: 100%;
    border-collapse: collapse;
    background: var(--color-superficie);
    border: 1px solid var(--color-borde);
    border-radius: var(--radio);
    font-size: 14px;
}

.tabla-datos th,
.tabla-datos td {
    padding: 0 8px;
    text-align: left;
    border-bottom: 1px solid var(--color-borde);
    height: var(--altura-fila);
    white-space: nowrap;
}

/**
 * El encabezado de la grilla se pega en `top: 0`, Y NO EN LA ALTURA DEL HEADER.
 *
 * Es contraintuitivo y ya se rompió una vez, así que queda escrito:
 *
 * `.tabla-contenedor` lleva `overflow-x: auto` para que una tabla ancha se
 * pueda correr a lo largo. Por especificación, cuando un eje del overflow deja
 * de ser `visible` el otro computa a `auto`, así que ESE DIV ES UN SCROLLPORT
 * —de los dos ejes— y es él, y no la página, el ancestro contra el que se
 * resuelve el `sticky` de adentro.
 *
 * Consecuencia: `top` acá no significa "a tanta distancia de la barra
 * superior", significa "a tanta distancia del borde de arriba de la tabla". Con
 * `var(--altura-header)` el encabezado se corre 44px hacia ABAJO dentro de su
 * propia caja y termina flotando encima de las dos primeras filas de datos, que
 * es exactamente lo que se vio en pantalla.
 *
 * O sea que este sticky NO pega el encabezado al hacer scroll de la página: el
 * contenedor no tiene alto limitado, así que nunca scrollea a lo alto y el
 * sticky no llega a activarse nunca. Es así desde antes de fijar el header y no
 * lo cambia ponerle otro `top`. Para que se pegue de verdad habría que darle
 * alto a `.tabla-contenedor` y que scrollee la grilla en vez de la página, que
 * es una decisión de cómo se usan las trece pantallas y no un ajuste de CSS.
 */
.tabla-datos th {
    background: var(--color-superficie-alt);
    font-weight: 600;
    color: var(--color-texto-suave);
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    position: sticky;
    top: -1px;
    z-index: 10;
    /**
     * La línea de abajo va como sombra y NO como borde.
     *
     * `.tabla-datos` es `border-collapse: collapse`, y con bordes colapsados el
     * borde entre el encabezado y la primera fila lo pinta LA TABLA, no la
     * celda: al scrollear se va con las filas y el encabezado queda flotando sin
     * ninguna línea que lo separe de lo que le pasa por debajo. La sombra la
     * pinta la celda, así que viaja con ella.
     */
    box-shadow: inset 0 -1px 0 var(--color-borde);
}

.tabla-datos tbody tr:hover { background: var(--color-superficie-alt); }
.tabla-datos tr:last-child td { border-bottom: none; }


.tabla-datos td.num,
.tabla-datos th.num { text-align: right; font-variant-numeric: tabular-nums; font-family: var(--fuente-mono); }

/**
 * El pie de totales queda pegado abajo.
 *
 * Es la contraparte del encabezado congelado: con la grilla scrolleando sola, un
 * `tfoot` normal se va de la vista y el total —que es lo que se mira— hay que ir
 * a buscarlo hasta el fondo.
 *
 * La línea de arriba va como sombra por lo mismo que la del encabezado: con
 * `border-collapse: collapse` el borde lo pinta la tabla y se despega al
 * scrollear. El `border-top` se queda igual, para el papel: en la impresión el
 * sticky no corre y la sombra no siempre sale.
 *
 * El `z-index` es uno menos que el del encabezado: si en una grilla de una sola
 * fila llegaran a cruzarse, el que tiene que quedar arriba es el que dice qué
 * columna es.
 */
.tabla-datos tfoot td {
    font-weight: 700;
    background: var(--color-superficie-alt);
    border-top: 2px solid var(--color-borde-fuerte);
    position: sticky;
    bottom: 0;
    z-index: 9;
    box-shadow: inset 0 1px 0 var(--color-borde-fuerte);
}

/**
 * LA GRILLA SCROLLEA SOLA, PARA QUE EL ENCABEZADO QUEDE CONGELADO.
 *
 * En un listado largo, a la tercera pantalla ya nadie se acuerda de qué columna
 * es cada una. La única forma de que el `sticky` del `thead` se active es que
 * ALGO scrollee a lo alto, y este div es el ancestro contra el que ese sticky se
 * resuelve —lleva `overflow-x: auto` para las tablas anchas, y por
 * especificación eso hace que el otro eje compute a `auto` también, o sea que ya
 * era un scrollport—. Lo que le faltaba era alto: sin límite nunca scrollea, y
 * un sticky que nunca scrollea no hace nada.
 *
 * EL ALTO ES UNA ESTIMACIÓN, Y ESTÁ BIEN QUE LO SEA. Se descuenta lo que hay
 * arriba de la grilla en un listado típico (solapas, título, buscador, la línea
 * de ayuda y la de teclas). Las dos formas de errarle degradan suave:
 *
 *   · si sobra alto, queda aire abajo de la grilla y no pasa nada;
 *   · si falta, el fondo de la grilla queda abajo del pliegue, se scrollea la
 *     página hasta que entra —el header y el menú están fijos— y a partir de
 *     ahí toma el scroll de adentro.
 *
 * Una pantalla con más o menos cosas arriba lo corrige con `--alto-grilla`, sin
 * tocar esta regla.
 *
 * El `max()` es el piso: en una ventana baja, `100vh - 17rem` se va a un número
 * chiquito o negativo y la grilla se colapsaría a una fila y media.
 */
.tabla-contenedor {
    overflow: auto;
    max-height: var(--alto-grilla, max(18rem, calc(100vh - 8rem)));
    border-radius: var(--radio);
}

/* Sin scroll interno (2026-10): la tabla crece con su contenido y se lee con el
   scroll de la página. Lo usa la planilla de la rendición, donde dos barras
   —una adentro de otra— hacían perder dónde se estaba. */
.tabla-contenedor.sin-scroll-interno { max-height: none; overflow-y: visible; }

/**
 * El carrito del POS queda AFUERA.
 *
 * Tiene su propio alto mínimo puesto en la pantalla, comparte fila con el panel
 * de totales y su alto está pensado para que la venta entre entera sin
 * scrollear. Además el POS es la pantalla que no se toca: su encabezado sigue
 * como estaba.
 */
.pos-layout .tabla-contenedor { max-height: none; }

/**
 * Impresa, la grilla va ENTERA.
 *
 * Sin esto un reporte sale cortado en una pantalla de filas, que es el bug más
 * caro que puede dejar este cambio: los informes se imprimen por
 * `imprimirConDialogo()` y lo que se manda al papel es lo que está a la vista.
 */
@media print {
    .tabla-contenedor { max-height: none; overflow: visible; }
}

/* ===========================================================
   LA PLANILLA DE INVENTARIO (stock/masivo.php)
   =========================================================== */

/**
 * ACÁ LA GRILLA NO SCROLLEA SOLA: SCROLLEA LA PÁGINA.
 *
 * Es la decisión contraria a la de `.tabla-contenedor`, y tiene su motivo. Un
 * listado se MIRA, así que conviene que la grilla scrollee adentro de su caja y
 * que el resto de la pantalla —los filtros, el título— quede quieto. Una
 * planilla se LLENA: se recorre con el estante delante, tecleando, y una caja
 * con su propio scroll obliga a llevar el mouse adentro para bajar y deja media
 * pantalla de aire abajo. Por eso la tabla va suelta, sin contenedor.
 *
 * Para que el `thead` igual se congele hace falta que el scrollport sea LA
 * VENTANA, y `.contenido` no lo puede ser: su `overflow-x: auto` lo convierte en
 * scrollport de los dos ejes (cuando un eje deja de ser `visible` el otro
 * computa a `auto`) y como no tiene alto limitado nunca scrollea, así que el
 * sticky de adentro no se activa jamás. La pantalla lo suelta con
 * `$claseContenido` — ver el comentario del `<main>` en `layout_inicio.php`.
 *
 * CONSECUENCIA CON LA QUE HAY QUE CONTAR: sin ese `overflow-x`, una tabla más
 * ancha que la ventana la scrollea a lo ancho la PÁGINA, no el `<main>`. Se
 * acepta porque esta planilla tiene seis columnas y la más ancha es un nombre de
 * artículo; si algún día se le suman columnas, lo que corresponde es sacar una,
 * no reponer la caja.
 */
.contenido.scroll-de-pagina {
    overflow-x: visible;
    /* Lo que se lleva la barra de guardado, que es `fixed` y no empuja nada.
       Sin esto tapa la última fila justo cuando se llega al final de la
       planilla, que es cuando se la está por guardar. */
    padding-bottom: 92px;
}

/**
 * Y ACÁ EL `top` DEL ENCABEZADO SÍ ES LA ALTURA DE LA BARRA SUPERIOR.
 *
 * En las trece grillas es `-1px` y este archivo ya explica por qué: ahí el
 * sticky se resuelve contra `.tabla-contenedor` y `top` significa "a tanta
 * distancia del borde de arriba de la tabla", así que `--altura-header` corría
 * el encabezado hacia abajo encima de las dos primeras filas.
 *
 * Acá el scrollport es la ventana, y arriba de todo está la barra superior, que
 * es `sticky` y ocupa `--altura-header`. Con `top: 0` el encabezado se pegaría
 * al borde de la ventana, o sea DEBAJO de la barra, y quedaría tapado. No es
 * una excepción a la regla de arriba: es la misma regla con otro scrollport.
 *
 * Va con las dos clases (0,2,1) y no con `.tabla-planilla th` a secas (0,1,1),
 * que empata con `.tabla-datos th` y ganaría sólo por estar más abajo en el
 * archivo. La misma lección del logo de la tienda: una regla que gana por orden
 * deja de ganar el día que alguien mueve un bloque.
 */
.tabla-datos.tabla-planilla th {
    top: var(--altura-header);
}

/* La descripción es lo único que puede ser largo; el resto de la fila mide lo
   que mide. `white-space: normal` sólo acá: en el resto de la grilla el `nowrap`
   es lo que mantiene las filas de un renglón. */
.tabla-planilla td.celda-nombre { white-space: normal; min-width: 18rem; }

/* Los campos de carga. `text-align: right` porque son números que se comparan
   de un vistazo entre filas, igual que las columnas `.num`.

   VAN LOS DOS TIPOS, y no es por las dudas: un campo de PLATA no puede ser
   `type="number"` —el navegador invalida el punto de miles de la máscara
   argentina y `value` vuelve vacío— así que la planilla de precios los
   dibuja como texto. Con el selector sólo en `number`, esa grilla salía con
   el dibujo por defecto del navegador en medio de una pantalla diseñada. */
.tabla-planilla input[type="number"],
.tabla-planilla input[type="text"] {
    width: 100%;
    max-width: 7rem;
    text-align: right;
    font-family: var(--fuente-mono);
    padding: 2px 6px;
    height: 26px;
}

/* La fila que se tocó, marcada mientras no se guarde. Es lo que hace que al
   llegar al final de 500 filas se pueda ver qué se cambió sin volver a subir.

   El fondo solo NO alcanza: medido, `--color-primario-suave` sobre el blanco de
   la grilla da rgb(232,239,245), que a media pantalla de distancia no se
   distingue de una fila normal. La barra de la izquierda es la que se ve, y es
   la misma señal con la que el menú lateral marca la opción activa. Va como
   `box-shadow` y no como `border-left` porque la tabla es
   `border-collapse: collapse` y ahí el borde lo pinta la tabla, no la celda. */
.tabla-planilla tr.fila-cambiada { background: var(--color-primario-suave); }
.tabla-planilla tr.fila-cambiada:hover { background: var(--color-primario-suave); }
.tabla-planilla tr.fila-cambiada td:first-child {
    box-shadow: inset 3px 0 0 var(--color-primario);
}

/**
 * La barra de guardado, pegada abajo.
 *
 * `fixed` y no `sticky`: sticky se resuelve contra el scrollport de su ancestro
 * y esta barra tiene que quedar quieta contra la ventana pase lo que pase con el
 * layout. Y va SIEMPRE a la vista, incluso sin cambios: con 500 filas, una barra
 * que aparece recién al tocar algo hace que quien abre la pantalla no sepa que
 * hay un botón de guardar en ninguna parte.
 *
 * El z-index le gana al `thead` (10) y pierde contra el MENÚ (50), el header
 * (60), los modales (100) y los diálogos (200): con `left: 0` esta barra corre
 * por debajo del menú lateral, y en 55 tapaba su panel desplegable —que vive
 * DENTRO del contexto de apilamiento del menú, así que compite con esta barra
 * como un solo bloque a 50 y no como sus 120 internos—. En 49 el menú siempre
 * gana, panel incluido, y esta barra nunca puede quedar encima de una
 * confirmación.
 *
 * El padding-left de 85px dejaba lugar al menú compacto (70px) con margen: sin
 * él, el "resumen-cambios" —pegado a la izquierda por el `margin-right: auto`
 * de más abajo— quedaría tapado por el propio menú apenas la barra pasa a
 * correr por detrás.
 */
.barra-planilla {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 49;
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: var(--e3);
    padding: var(--e3) var(--e4) var(--e3) 85px;
    background: var(--color-superficie);
    border-top: 1px solid var(--color-borde-fuerte);
    box-shadow: 0 -2px 8px rgba(var(--sombra-rgb), 0.08);
}

.barra-planilla .resumen-cambios { margin-right: auto; font-size: 1.06rem; }
.barra-planilla .resumen-cambios strong { font-variant-numeric: tabular-nums; }

/* En modo táctil el menú compacto está OCULTO por defecto (translateX fuera de
   la pantalla) salvo que se abra el cajón: ahí no hay nada a la izquierda que
   el hueco de 85px tenga que esquivar, y en un celular ese espacio hace falta
   para el propio contenido de la barra. */
html.es-movil .barra-planilla { padding-left: var(--e4); }

/**
 * ===========================================================
 * LA GRILLA DE PRECIOS: LA FECHA Y LAS EXISTENCIAS
 * ===========================================================
 * `col-cambio` no lleva `nowrap` propio: la planilla ya lo pone en todas sus
 * celdas menos la del nombre, y "hace 12 min" entra en un renglón. Lo que sí
 * necesita es un ancho de arranque, o la columna se achica hasta el largo del
 * texto más corto de la página y baila entre una carga y la siguiente.
 */
.tabla-planilla th.col-cambio,
.tabla-planilla td.col-cambio { min-width: 7.5rem; }

/**
 * ===========================================================
 * LA CABECERA QUE ORDENA
 * ===========================================================
 * Es un `<a>` de verdad —ordenar es navegar, y así anda con el clic del medio,
 * con Ctrl+clic y sin JavaScript— pero adentro de un `<th>` tiene que verse como
 * la cabecera y no como un enlace: sin esto sale azul y subrayado, y una fila de
 * cabeceras azules se lee como una barra de opciones.
 *
 * El subrayado aparece al pasar el mouse, que es lo que anuncia que se puede
 * apretar. Un gesto que no se anuncia no lo descubre nadie — es el criterio de
 * `.celda-ficha` con el doble clic que abre la ficha.
 */
.th-orden {
    color: inherit;
    text-decoration: none;
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    white-space: nowrap;
}
.th-orden:hover { text-decoration: underline; }
.th-orden.activa { font-weight: 700; }

/* La flecha de la columna activa. Más chica que el rótulo: dice la dirección,
   no compite con el nombre de la columna. */
.orden-flecha { font-size: 0.72em; line-height: 1; opacity: 0.75; }

/**
 * La columna del ojo: el mismo cuadrado de 26px que `.btn-accion` usa en los
 * demás listados. El ancho fijo es lo que hace que las filas SIN botón —las
 * presentaciones que cuelgan del artículo— no corran el nombre hacia la
 * izquierda: es el motivo por el que existe `accionHueco()`, resuelto acá desde
 * la columna porque la celda vacía ya alcanza.
 */
.tabla-planilla th.col-ojo,
.tabla-planilla td.col-ojo {
    width: 34px;
    padding-left: var(--e1);
    padding-right: var(--e1);
    text-align: center;
}

/**
 * Las existencias que cuelgan del nombre. Van con `tabular-nums` porque son
 * números que se comparan entre filas —es lo único que hace legible una columna
 * de cantidades— y el rótulo del local va apenas más apagado que la cifra: lo
 * que se busca de un vistazo es el número, y la sigla es su etiqueta.
 */
.stock-suc { font-variant-numeric: tabular-nums; white-space: nowrap; }
.stock-suc-rotulo { color: var(--color-texto-tenue); font-weight: 600; }

/**
 * ===========================================================
 * UNA CASILLA CON SU RÓTULO AL LADO
 * ===========================================================
 * `.checkbox` se venía usando en tres lugares —dos en la grilla de precios y uno
 * en Configuración → Cajas— Y NO EXISTÍA EN ESTA HOJA, así que el `<label>` salía
 * con el dibujo por defecto del navegador: la casilla arriba y el texto abajo
 * cuando el rótulo no entra, y sin separación entre las dos. Es el modo de fallar
 * de una clase que no existe (el de `.barra-pedido` en la tienda): ninguna regla
 * está mal escrita, simplemente no hay ninguna.
 *
 * El `align-items: center` y no `baseline`: el input no tiene línea de base
 * propia y los navegadores la ubican distinto.
 */
.checkbox {
    display: inline-flex;
    align-items: center;
    gap: var(--e2);
    cursor: pointer;
    font-weight: 400;
    white-space: nowrap;
}
.checkbox input[type="checkbox"] { margin: 0; cursor: pointer; }

/* EL RADIO Y LA CASILLA MIDEN LO SUYO, NO EL ANCHO DE LA COLUMNA.

   `.campo input { width: 100% }` alcanza a TODOS los inputs, incluidos los de
   tipo `radio` y `checkbox`: adentro de un `.campo` el círculo del radio se
   estira a la columna entera —se dibuja centrado, porque así lo pinta el
   navegador— y el texto de la etiqueta se va al borde derecho. Se ve como si
   el formulario estuviera desalineado y no hay ninguna regla mal escrita.

   Va con `.campo` adelante para ganarle por especificidad (0,2,1 contra 0,1,1)
   y no por posición en el archivo, que es lo que se rompe el día que alguien
   mueva un bloque. Es la lección del logo de la tienda. */
.campo .checkbox input[type="checkbox"],
.campo .checkbox input[type="radio"],
.checkbox input[type="radio"] {
    width: auto;
    height: auto;
    flex: 0 0 auto;
    margin: 0;
    cursor: pointer;
}

/* Impresa, la planilla va entera y sin la barra: se imprime para salir a contar
   con el papel en la mano, y ahí el botón de guardar no significa nada. */
@media print {
    .barra-planilla { display: none; }
    .contenido.scroll-de-pagina { padding-bottom: 0; }
    .tabla-datos.tabla-planilla th { position: static; }
}

.tabla-vacia {
    text-align: center;
    padding: var(--e5);
    color: var(--color-texto-tenue);
    font-size: 1.06rem;
}

/* ===========================================================
   ETIQUETAS / ESTADOS
   =========================================================== */

.etiqueta {
    display: inline-block;
    padding: 0px var(--e2);
    border-radius: 8px;
    font-size: 11px;
    font-weight: 600;
    line-height: 1.5;
    border: 1px solid transparent;
}
.etiqueta-activo   { background: var(--color-exito-fondo);  color: var(--color-exito);  border-color: var(--color-exito-borde); }
.etiqueta-inactivo { background: var(--color-error-fondo);  color: var(--color-error);  border-color: var(--color-error-borde); }
.etiqueta-pendiente{ background: var(--color-alerta-fondo); color: var(--color-alerta); border-color: var(--color-alerta-borde); }
.etiqueta-neutro   { background: var(--color-superficie-alt); color: var(--color-texto-suave); border-color: var(--color-borde); }
/* El saldo del cliente en la grilla del padrón (200): naranja si debe, rojo si
   algo ya venció. Es un recuadro y no sólo texto coloreado para que el rojo y
   el naranja se distingan de un vistazo recorriendo doscientas filas. */
.saldo-celda {
    display: inline-block;
    padding: 0 var(--e2);
    border-radius: 8px;
    border: 1px solid transparent;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}
.saldo-celda.debe    { background: var(--color-alerta-fondo); color: var(--color-alerta); border-color: var(--color-alerta-borde); }
.saldo-celda.vencida { background: var(--color-error-fondo);  color: var(--color-error);  border-color: var(--color-error-borde); }
.saldo-celda.a-favor { color: var(--color-exito); padding: 0; }
/* La promoción que le puso el descuento a un renglón de la grilla, y la
   oferta que además sale a la tienda. Van con el color de acento y no con
   el de "activo": no son un estado del registro, son de dónde salió un
   número — y en la grilla conviven con la etiqueta de oferta del POS. */
.etiqueta-promo    { margin-left: 6px; background: var(--color-info-fondo); color: var(--color-primario); border-color: var(--color-info-borde); }

/**
 * El badge de la presentación, pegado al nombre en la grilla de precios. Sólo se
 * dibuja cuando el artículo tiene más de una, así que no es un estado del
 * registro: es lo que distingue esta fila de su hermana.
 *
 * VA ACÁ Y NO EN EL BLOQUE DE LA PLANILLA, aunque sea la única pantalla que lo
 * use: escrito allá queda ANTES de `.etiqueta` en el archivo, y como los dos
 * selectores son de una sola clase (0,1,0), el `font-weight: 600` de la base
 * ganaría por posición. Es la trampa del logo de la tienda.
 *
 * `vertical-align` porque el nombre es texto normal y el badge tiene su propio
 * `line-height`: sin eso queda medio renglón abajo del nombre al que acompaña.
 */
.etiqueta-presentacion {
    margin-left: 6px;
    vertical-align: 1px;
    font-weight: 700;
    letter-spacing: 0.02em;
}

/* ===========================================================
   TARJETAS / KPI (dashboard)
   =========================================================== */

.grilla-kpi {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
    gap: var(--e3);
    margin-bottom: var(--e4);
}
.tarjeta-kpi {
    background: var(--color-superficie);
    border: 1px solid var(--color-borde);
    border-radius: var(--radio);
    padding: var(--e3);
}
.tarjeta-kpi .etiqueta-kpi {
    font-size: 0.99rem;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--color-texto-suave);
    margin-bottom: var(--e1);
}
.tarjeta-kpi .valor-kpi {
    font-size: 1.89rem;
    font-weight: 700;
    color: var(--color-texto);
    font-variant-numeric: tabular-nums;
}
.tarjeta-kpi .variacion-positiva { color: var(--color-exito); font-size: 0.94rem; }
.tarjeta-kpi .variacion-negativa { color: var(--color-error); font-size: 0.94rem; }

/* El renglón chico de abajo de la cifra: qué la compone o de qué habla.
   Es lo que evita gastar una tarjeta entera en un dato que sólo se lee
   junto al número grande —la cantidad de comprobantes, el ticket
   promedio— y lo que hace que una cifra ambigua pueda decir de qué es
   sin alargar el rótulo de arriba. `tabular-nums` porque casi siempre
   lleva un importe adentro. */
.tarjeta-kpi .detalle-kpi {
    font-size: 0.9rem;
    line-height: 1.35;
    color: var(--color-texto-suave);
    margin-top: var(--e1);
    font-variant-numeric: tabular-nums;
}

.panel {
    background: var(--color-superficie);
    border: 1px solid var(--color-borde);
    border-radius: var(--radio);
    padding: var(--e4);
    margin-bottom: var(--e4);
}
.panel-titulo {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: var(--e3);
    padding-bottom: var(--e2);
    border-bottom: 1px solid var(--color-borde);
}
.panel-titulo h2, .panel-titulo h3 { margin: 0; }

/* ===========================================================
   PANEL PLEGABLE

   Un panel de DATOS que se abre a pedido, para el detalle que
   se mira de vez en cuando y que desplegado siempre empujaría
   media pantalla hacia abajo lo que se vino a ver.

   Es otra cosa que `.ayuda-plegada`, que es para el texto de
   ayuda de un formulario: aquélla usa el cuerpo y el color de
   una nota, y ésta la caja de un panel.

   La línea de abajo del título va SÓLO con `[open]`: plegado,
   un borde suelto abajo de un renglón se lee como una tabla
   vacía.
   =========================================================== */
details.panel-plegable > summary {
    cursor: pointer;
    font-weight: 600;
    color: var(--color-texto);
    /* El triangulito lo dibuja el navegador; `list-item` es lo que
       lo conserva sin tener que dibujar uno propio. */
    display: list-item;
    list-style-position: inside;
}
details.panel-plegable[open] > summary {
    margin-bottom: var(--e3);
    padding-bottom: var(--e2);
    border-bottom: 1px solid var(--color-borde);
}

/* ===========================================================
   UN PEDIDO PLEGADO (facturas/sin-enviar.php)

   Se ve el cliente y nada más; tocarlo despliega el cuerpo.
   Es el `<details>` del navegador: anda con el teclado y sin
   JavaScript. El renglón mide lo que una fila de grilla, así
   que en el celular también crece con `--altura-fila`.
   =========================================================== */
details.pedido-sin-enviar {
    border-top: 1px solid var(--color-borde);
}
details.pedido-sin-enviar > summary {
    cursor: pointer;
    font-weight: 600;
    color: var(--color-texto);
    display: list-item;
    list-style-position: inside;
    min-height: var(--altura-fila);
    padding: var(--e2) 0;
}
details.pedido-sin-enviar > summary:hover,
details.pedido-sin-enviar[open] > summary {
    color: var(--color-primario);
}
.pedido-cuerpo {
    padding: 0 0 var(--e3) var(--e4);
}
.pedido-datos {
    margin: 0 0 var(--e2);
}
.total-vendedor {
    display: flex;
    justify-content: space-between;
    gap: var(--e3);
    border-top: 2px solid var(--color-borde);
    padding-top: var(--e2);
    font-weight: 600;
}

/* ===========================================================
   TABS
   =========================================================== */

/* LAS SOLAPAS SE DESLIZAN, NO ESTIRAN LA PÁGINA.

   Reportes tiene once, Tienda nueve y el catálogo ocho: en un
   teléfono eso no entra, y sin `overflow-x` el flex las deja
   sobresalir y ES LA PÁGINA ENTERA la que se puede arrastrar a
   la derecha. Ahí el problema no son las solapas —se llega a
   ellas— sino que todo lo demás se corre con ellas: el
   encabezado, la grilla y los botones quedan desplazados y la
   pantalla se lee como si estuviera rota.

   Son TRES reglas y hacen falta las tres:
     - `overflow-x: auto` en el contenedor, que es lo que hace que
       scrollee él y no el documento;
     - `nowrap`, o en vez de deslizarse se parten en tres renglones
       y el encabezado se come media pantalla;
     - `flex-shrink: 0` en cada una, sin lo cual el flex las
       comprime hasta que entren y los rótulos se cortan.

   `scrollbar-width: none` esconde la barra: acá se desliza con el
   dedo, y en escritorio las solapas entran de sobra. */
.tabs {
    display: flex;
    border-bottom: 1px solid var(--color-borde);
    margin-bottom: var(--e4);
    gap: var(--e1);
    overflow-x: auto;
    flex-wrap: nowrap;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
}
.tabs::-webkit-scrollbar { display: none; }
.tabs a {
    padding: var(--e2) var(--e3);
    font-size: 1.18rem;
    text-decoration: none;
    color: var(--color-texto-suave);
    border-bottom: 2px solid transparent;
    margin-bottom: -1px;
    flex-shrink: 0;
    white-space: nowrap;
}
.tabs a:hover { color: var(--color-texto); }
.tabs a.activo {
    color: var(--color-primario);
    border-bottom-color: var(--color-primario);
    font-weight: 600;
}
/* Las solapas no dibujan el anillo de foco: pedido así. Vale para las
   tres familias (la tira común, la de pastillas y las dos filas de
   Reportes, que es `.tabs` también). */
.tabs a:focus-visible,
.tabs-pastilla a:focus-visible { outline: none; }

/* ===========================================================
   UNA ACCIÓN AL FINAL DE LA FILA DE SOLAPAS

   `margin-left: auto` la empuja contra el borde derecho sin tocar a
   ninguna solapa: las de la izquierda quedan exactamente donde estaban.

   `align-self: center` es lo que le devuelve su alto natural al select y
   al botón. `.tabs` es un flex sin `align-items`, o sea `stretch`, así que
   sin esto los dos se estiran al alto de la solapa más alta.

   Y `flex-shrink: 0` como las solapas: la fila scrollea a lo ancho, y sin
   eso el select es lo primero que se aplasta cuando no entra.
   =========================================================== */
.tabs-accion {
    display: flex;
    align-items: center;
    gap: var(--e1);
    margin-left: auto;
    align-self: center;
    flex-shrink: 0;
    padding-left: var(--e3);
}
.tabs-accion select {
    /* Sin alto propio: el que le da el navegador a un select alcanza, y
       fijarlo acá lo desalinearía del botón de al lado apenas cambie el
       cuerpo de la tipografía. */
    max-width: 15rem;
}

/* ===========================================================
   SOLAPAS EN PASTILLA (`.tabs-pastilla`)

   LA SEGUNDA TIRA DE UNA PANTALLA NO PUEDE DIBUJARSE COMO LA PRIMERA.
   En Empresa conviven dos: arriba las de Configuración —General ·
   Sucursales · Puntos de venta · Localidades · Padrón · Importar datos—,
   que son las de TODO el sistema y navegan a otra pantalla; y abajo las
   de la ficha, que sólo alternan paneles de ésta. Dibujadas iguales, dos
   filas de subrayados pegadas se leen como una sola tira de trece
   solapas, y no hay nada que diga cuál sale de la pantalla y cuál no.

   Así que éstas son un CONTROL SEGMENTADO: un riel hundido con las
   opciones adentro y la elegida rellena con el color de la empresa. Es
   la forma con la que un sistema operativo dibuja «elegí una de estas»
   —no «andá a otro lado»—, que es exactamente la diferencia.

   NO REEMPLAZA A `.tabs` NI COMPITE CON ELLA. Vale para la segunda tira
   de una pantalla que ya tiene una; una pantalla con una sola sigue
   usando la de siempre, o habría dos maneras de dibujar lo mismo.

   El riel scrollea a lo ancho como `.tabs`, sin barra a la vista: con
   siete opciones en un celular no entran, y envolverlas en dos filas
   partiría el riel al medio.
   =========================================================== */
.tabs-pastilla {
    display: flex;
    gap: var(--e1);
    padding: var(--e1);
    margin-bottom: var(--e4);
    background: var(--color-superficie-alt);
    border: 1px solid var(--color-borde);
    border-radius: 999px;
    overflow-x: auto;
    flex-wrap: nowrap;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
}
.tabs-pastilla::-webkit-scrollbar { display: none; }
.tabs-pastilla a {
    display: flex;
    align-items: center;
    gap: var(--e1);
    padding: var(--e2) var(--e3);
    border-radius: 999px;
    font-size: 1.1rem;
    font-weight: 500;
    text-decoration: none;
    color: var(--color-texto-suave);
    white-space: nowrap;
    flex-shrink: 0;
    transition: background 0.12s, color 0.12s;
}
.tabs-pastilla a:hover { background: var(--color-primario-suave); color: var(--color-texto); }
.tabs-pastilla a.activo {
    background: var(--color-primario);
    color: var(--color-primario-texto);
    font-weight: 600;
}
/* El hover NO puede pisar a la elegida: sin esto, pasar el mouse por
   encima de la solapa abierta la apaga y parece que se deseligió. */
.tabs-pastilla a.activo:hover {
    background: var(--color-primario);
    color: var(--color-primario-texto);
}
.tabs-pastilla a svg {
    width: 17px; height: 17px;
    flex: none;
    stroke-width: 2;
}
/* Abajo de 700px los rótulos no entran y el riel se vuelve una fila de
   íconos: el ícono es lo que identifica cada pestaña en un celular, y el
   rótulo de la abierta se lee igual en el título del panel. */
@media (max-width: 700px) {
    .tabs-pastilla a span { display: none; }
    .tabs-pastilla a.activo span { display: inline; }
    .tabs-pastilla a { padding: var(--e2); }
}

/* ===========================================================
   EL LOGO DE LA EMPRESA: la muestra al lado de sus controles

   La vista previa va PRIMERO y a la izquierda porque es la respuesta
   a la pregunta con la que se entra («¿cuál está puesto?»), y el que
   ya tiene uno cargado no viene a subir nada.

   El recuadro tiene un alto fijo y fondo a cuadros: un logo con
   transparencia sobre un fondo liso no deja ver que la tiene, y eso es
   justo lo que hay que saber antes de imprimirlo sobre papel blanco.
   =========================================================== */
.logo-config {
    display: flex;
    gap: var(--e3);
    align-items: flex-start;
    flex-wrap: wrap;
    margin-bottom: var(--e3);
}
.logo-muestra {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 220px;
    height: 110px;
    flex: none;
    padding: var(--e2);
    border: 1px solid var(--color-borde);
    border-radius: var(--radio);
    background-color: var(--color-superficie);
    background-image:
        linear-gradient(45deg, var(--color-superficie-alt) 25%, transparent 25%),
        linear-gradient(-45deg, var(--color-superficie-alt) 25%, transparent 25%),
        linear-gradient(45deg, transparent 75%, var(--color-superficie-alt) 75%),
        linear-gradient(-45deg, transparent 75%, var(--color-superficie-alt) 75%);
    background-size: 14px 14px;
    background-position: 0 0, 0 7px, 7px -7px, -7px 0;
    text-align: center;
}
.logo-muestra img {
    max-width: 100%;
    max-height: 100%;
    object-fit: contain;
}
.logo-controles { flex: 1 1 260px; min-width: 0; }

/* Solapas del módulo Reportes en dos filas (evita scroll horizontal en escritorio) */
.tabs-reportes {
    margin-bottom: var(--e4);
}
.tabs-reportes .tabs-reportes-fila {
    margin-bottom: 0;
}
.tabs-reportes .tabs-reportes-fila a {
    padding: var(--e2) var(--e2);
    font-size: 1.12rem;
}
.tabs-reportes .tabs-reportes-fila a.activo {
    position: relative;
    z-index: 1;
}

/* ===========================================================
   BREADCRUMBS
   =========================================================== */

.migas {
    font-size: 1.06rem;
    color: var(--color-texto-tenue);
    margin-bottom: var(--e3);
}
.migas a { color: var(--color-texto-suave); text-decoration: none; }
.migas a:hover { text-decoration: underline; }
.migas .separador { margin: 0 var(--e1); }

/* Migas con las flechas de anterior/siguiente al otro extremo (la ficha del
   artículo). Van acá y no en la barra de acciones de abajo porque son POSICIÓN
   dentro del catálogo —lo mismo que dice la miga— y no una operación sobre el
   registro que se está mirando. */
.migas-navegable {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--e2);
}
.migas-navegable .nav-ficha {
    display: inline-flex;
    align-items: center;
    gap: var(--e1);
    flex-shrink: 0;
}
/* El subrayado del hover es de los enlaces de texto de la miga: en un botón de
   icono no se ve más que como una raya suelta abajo del dibujo. */
.migas-navegable .nav-ficha a:hover { text-decoration: none; }

/* El buscador de la ficha de la entidad, al otro extremo de las migas: lleva al
   padrón con la búsqueda puesta. Angosto a propósito —es un atajo, no el
   buscador del listado— y en un celular pasa abajo de la miga entera. */
.migas-con-buscar { display: flex; align-items: center; flex-wrap: wrap; gap: var(--e2); }
.migas-buscar { display: inline-flex; align-items: center; gap: var(--e1); }
/* El campo lleva el MISMO dibujo que los de `.campo` —borde, radio, alto,
   fondo y foco—: sin eso sale con el del navegador, que en una barra de migas
   no se lee como un campo. */
.migas-buscar input[type="search"] {
    width: 15rem; max-width: 100%; height: var(--alto-control);
    padding: 0 var(--e2); border: 1px solid var(--color-borde); border-radius: var(--radio-chico);
    font-size: .95rem; font-family: var(--fuente-base);
    background: var(--color-superficie); color: var(--color-texto);
}
.migas-buscar input[type="search"]:focus {
    outline: none; border-color: var(--color-primario); box-shadow: 0 0 0 2px var(--color-primario-suave);
}
@media (max-width: 700px) {
    .migas-buscar { flex: 1 1 100%; }
    .migas-buscar input[type="search"] { flex: 1; width: auto; }
}

/* ===========================================================
   PAGINACIÓN (partials/paginacion.php)
   =========================================================== */

/**
 * ESTE BLOQUE ESTUVO ESCRITO Y SIN USAR DESDE SIEMPRE.
 *
 * Ninguna pantalla dibujaba un `.paginacion`: los trece listados cortan con un
 * tope y lo dicen, y el del catálogo se resolvió con "los últimos 50" (ver la
 * 041). Era capacidad muerta, de la que este proyecto ya sacó tres —`sync_status`
 * en la 056, `id_offset` en la 060, `pedidos_web.venta_id` en la 081—: o se usaba,
 * o había que borrarla. La planilla de inventario le dio su primer uso.
 *
 * Lo que se le agregó es lo que le faltaba para servir de verdad: el tramo con el
 * total, el hueco de los puntos suspensivos y que el salto apagado no se pueda
 * tocar. Lo que NO se hizo fue escribir un segundo bloque al lado, que es lo que
 * salió primero: dos definiciones de `.paginacion` en la misma hoja y ganando la
 * de más abajo es el modo de fallar de la 034 con CSS, y el síntoma fue
 * inmediato —el total salía con recuadro de botón, porque el selector de acá
 * alcanza a TODO `span` de adentro—.
 */
.paginacion {
    display: flex;
    gap: var(--e1);
    align-items: center;
    flex-wrap: wrap;
    margin: var(--e3) 0;
    font-size: 1rem;
}

/* Los saltos y los números miden igual y tienen recuadro. El tramo NO: es el
   único que no lleva a ningún lado, y con recuadro se lee como un botón más en
   una fila donde todo lo demás sí se puede tocar. */
.paginacion a, .paginacion .paginacion-num, .paginacion .paginacion-salto {
    padding: var(--e1) var(--e2);
    border: 1px solid var(--color-borde);
    border-radius: var(--radio-chico);
    text-decoration: none;
    color: var(--color-texto);
    min-width: 24px;
    text-align: center;
}
.paginacion a:hover { background: var(--color-superficie-alt); text-decoration: none; }

.paginacion .activo {
    background: var(--color-primario);
    color: var(--color-primario-texto);
    border-color: var(--color-primario);
    font-weight: 600;
}

/**
 * El salto que no lleva a ningún lado va APAGADO Y NO AUSENTE: sacándolo, los
 * números se corren al pasar de la página 1 a la 2 y la fila entera salta. Es la
 * regla del hueco de `accionHueco()` en la columna de acciones.
 *
 * `pointer-events: none` porque un `<span>` no navega, pero sin eso el cursor
 * cambiaría igual al pasarle por encima y diría que sí.
 */
.paginacion .deshabilitado {
    color: var(--color-texto-tenue);
    background: var(--color-superficie-alt);
    opacity: 0.6;
    pointer-events: none;
}

/* El tramo va primero y empuja al resto a la derecha: se lee antes de decidir si
   hay que pasar de página. */
.paginacion .paginacion-total {
    margin-right: auto;
    padding: 0;
    border: none;
    color: var(--color-texto-suave);
}

.paginacion .paginacion-hueco { border: none; padding: 0 2px; min-width: 0; color: var(--color-texto-tenue); }

/* Impresa no sirve para nada: el papel no tiene a dónde ir. */
@media print { .paginacion { display: none; } }

/* ===========================================================
   MODAL / DIÁLOGO
   =========================================================== */

.fondo-modal {
    position: fixed; inset: 0;
    background: rgba(var(--scrim-rgb), 0.45);
    display: flex;
    align-items: flex-start;
    justify-content: center;
    padding-top: 8vh;
    z-index: 100;
}
.modal {
    background: var(--color-superficie);
    border-radius: var(--radio);
    width: 100%;
    max-width: 460px;
    box-shadow: 0 8px 24px rgba(var(--sombra-rgb),0.18);
}

/* ===========================================================
   ESPERA CON PANTALLA TAPADA

   Para lo que tarda y NO se puede cancelar: la lectura de una factura con IA
   son entre cinco y treinta segundos contra un servicio de afuera, y en el
   medio la pantalla se ve exactamente igual que antes de apretar. Sin esto, lo
   que hace el usuario es apretar otra vez — y cada lectura consume del tope y
   se cobra.

   Tapa la pantalla a propósito: no es un spinner decorativo en un rincón, es
   que no hay nada que se pueda hacer hasta que conteste. Va en z-index 150,
   arriba de los modales (100) y abajo de los diálogos (200), que es donde
   corresponde algo que bloquea pero no pregunta nada.
   =========================================================== */
.espera {
    position: fixed; inset: 0;
    background: rgba(var(--scrim-rgb), 0.55);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--e3);
    z-index: 150;
    color: var(--color-sobre-oscuro);
    text-align: center;
    padding: var(--e3);
}
.espera[hidden] { display: none; }

.espera-rueda {
    width: 54px; height: 54px;
    border: 5px solid rgba(var(--velo-barra-rgb), 0.25);
    border-top-color: var(--color-sobre-oscuro);
    border-radius: 50%;
    animation: espera-girar 0.9s linear infinite;
}
@keyframes espera-girar { to { transform: rotate(360deg); } }

.espera-titulo { font-size: 1.24rem; font-weight: 600; }
.espera-detalle { font-size: 1rem; opacity: 0.85; max-width: 30rem; line-height: 1.5; }

/* Con `prefers-reduced-motion` la rueda no gira: se queda como un anillo y el
   texto sigue diciendo lo mismo. Lo que informa es el cartel, no la animación. */
@media (prefers-reduced-motion: reduce) {
    .espera-rueda { animation: none; opacity: 0.6; }
}

/* No se imprime: es estado de la pantalla, no del documento. */
@media print { .espera { display: none !important; } }

/* ===========================================================
   EL OVERLAY DE NAVEGACIÓN: "CARGANDO…"

   El hermano claro de `.espera`, y la diferencia entre los dos es de qué
   habla cada uno:

   - `.espera` tapa la pantalla mientras una operación LARGA corre en ESTA
     página (la lectura con IA, treinta segundos contra un servicio de
     afuera). Es oscuro y explica qué está pasando, porque hay que leerlo.
   - `.cargando-fondo` es el acuse de recibo de un clic: la pantalla
     siguiente ya viene en camino y esta se está yendo. Dura lo que tarda el
     servidor, casi siempre menos de un segundo, y no hay nada que leer.

   De ahí que sea claro y con desenfoque en vez de oscuro: lo que hay abajo
   sigue siendo la pantalla en la que se estaba, y se tiene que seguir
   reconociendo — apagarla del todo se lee como que se perdió el trabajo. El
   desenfoque es lo que dice "esto ya no se toca" sin esconderlo.

   Va en z-index 300, arriba de todo (diálogos 200): cuando aparece, la página
   se está yendo y no hay nada que pueda ganarle. Ver partials/cargando.php.
   =========================================================== */
.cargando-fondo {
    position: fixed; inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 300;

    background: rgba(var(--velo-barra-rgb), 0.62);
    backdrop-filter: blur(2.5px);
    -webkit-backdrop-filter: blur(2.5px);

    /* Oculto NO es `display: none`: la aparición se atenúa, y con display no
       hay nada que atenuar. `visibility` y `pointer-events` son los que hacen
       que mientras está apagado no exista para el mouse — sin ellos, una capa
       invisible a pantalla completa se come todos los clics del sistema. */
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transition: opacity 0.14s ease, visibility 0s linear 0.14s;
}
.cargando-fondo.visible {
    opacity: 1;
    visibility: visible;
    /* Encendido SÍ se come los clics, y es a propósito: es lo que evita que
       quien no vio respuesta apriete Guardar tres veces. */
    pointer-events: auto;
    transition: opacity 0.14s ease;
}

.cargando-caja {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--e3);
    padding: var(--e5) var(--e5) var(--e4);
    background: var(--color-superficie);
    border: 1px solid var(--color-borde);
    border-radius: var(--radio-chico);
    box-shadow: 0 6px 24px rgba(var(--sombra-rgb), 0.14);
}

/* Reusa `espera-girar`, la animación que ya existe unas líneas más arriba:
   dos ruedas girando a velocidades distintas en el mismo sistema se notan. */
.cargando-rueda {
    width: 34px; height: 34px;
    border: 4px solid var(--color-primario-suave);
    border-top-color: var(--color-primario);
    border-radius: 50%;
    animation: espera-girar 0.9s linear infinite;
}

.cargando-texto {
    font-size: 0.95rem;
    font-weight: 600;
    color: var(--color-texto-suave);
    letter-spacing: 0.02em;
}

@media (prefers-reduced-motion: reduce) {
    .cargando-rueda { animation: none; opacity: 0.6; }
    .cargando-fondo { transition: none; }
}

/* No se imprime: es estado de la pantalla, no del documento. Y con el papel
   de por medio importa más que en `.espera` — los reportes se imprimen con
   imprimirConDialogo(), que manda al papel lo que está a la vista. */
@media print { .cargando-fondo { display: none !important; } }

/* ===========================================================
   EL BUSCADOR, EN VERSIÓN PETISA

   `.pos-buscador` está dimensionado para EL MOSTRADOR: campo de 1,36rem y
   borde de 2px, porque ahí ocupa el ancho de la pantalla, es lo primero que se
   mira y se lee de lejos. Adentro de una tabla —una fila por renglón de la
   factura— esa misma caja mide el doble que la fila y empuja la tabla a lo
   largo de dos pantallas.

   VA SCOPEADO POR UN ANCESTRO Y NO TOCA `.pos-buscador`, que lo comparten el
   punto de venta, el buscador de artículos y el de entidades. Es el mismo
   recurso con el que la grilla tipo planilla achica el suyo
   (`.grilla-planilla .celda-buscador .pos-buscador`): la pantalla que lo
   necesita chico se lo pide, y las que no, no se enteran.
   =========================================================== */
.buscador-compacto .pos-buscador {
    padding: 2px var(--e2);
}
.buscador-compacto .pos-buscador input {
    padding: 1px var(--e3);
    font-size: 1rem;
    border-width: 1px;
}
.modal-header {
    padding: var(--e3) var(--e4);
    border-bottom: 1px solid var(--color-borde);
    display: flex;
    justify-content: space-between;
    align-items: center;
}
.modal-header h2 { margin: 0; font-size: 1.12rem; }
.modal-cuerpo { padding: var(--e4); }
.modal-pie {
    padding: var(--e3) var(--e4);
    border-top: 1px solid var(--color-borde);
    display: flex;
    justify-content: flex-end;
    gap: var(--e2);
}

/* ===========================================================
   EL SELECTOR DE CAMPOS DEL LISTADO (164, rehecho por la 165)

   Dos columnas: a la izquierda QUÉ se imprime —uno por fila, con su scroll— y
   a la derecha EN QUÉ ORDEN y cómo sale la hoja. Son dos preguntas distintas y
   mezcladas obligan a tildar en el orden correcto, que es justo lo que nadie
   puede hacer con veintiocho casillas.

   Lo dibuja `partials/campos_listado.php` en los dos lados donde se usa: a la
   vista al armar el listado y adentro de un modal al cambiarle los campos.
   =========================================================== */

.campos-listado {
    display: flex;
    gap: var(--e4);
    align-items: flex-start;
}

/* `min-width: 0` no es defensa en profundidad acá: el mínimo `auto` de un flex
   item es su contenido, y los rótulos largos ("Precio LISTA MAYORISTA")
   empujan la columna y parten el selector. */
.campos-listado > div { min-width: 0; }
.campos-listado-disponibles { flex: 1 1 46%; }
.campos-listado-orden       { flex: 1 1 54%; }

/* EL SCROLL VA EN LA LISTA Y NO EN EL MODAL: la lista crece con cada lista de
   precios y cada sucursal, y scrolleando el modal entero el panel de la derecha
   se va de la vista justo cuando se está eligiendo qué tildar. */
.campos-listado-scroll {
    max-height: 22rem;
    overflow-y: auto;
    padding-right: var(--e2);
}

.campos-grupo {
    border: 1px solid var(--color-borde);
    border-radius: var(--radio);
    padding: var(--e2) var(--e3);
    margin: 0 0 var(--e3) 0;
    min-inline-size: 0;   /* el default es `min-content` y desborda el panel */
}
.campos-grupo legend {
    font-size: 0.78rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--color-texto-suave);
    padding: 0 var(--e1);
}

/* UNO POR FILA. `.checkbox` es `inline-flex`, así que sin esto los campos se
   acomodan de a varios por renglón y con nombres desparejos —"IVA %" al lado de
   "Stock CASA CENTRAL"— la grilla queda rota y hay que barrer con la vista de a
   saltos. */
.campos-grupo .campo-fila { display: flex; width: 100%; }

.titulo-bloque {
    margin: 0 0 var(--e2) 0;
    font-size: 0.86rem;
    font-weight: 600;
}

/* ===========================================================
   LA LISTA DE COLUMNAS: se arrastra
   =========================================================== */

.lista-orden {
    list-style: none;
    margin: 0;
    padding: 0;
    border: 1px solid var(--color-borde);
    border-radius: var(--radio);
    max-height: 22rem;
    overflow-y: auto;
    /* `touch-action: none` es lo que permite arrastrar CON EL DEDO: sin él, el
       navegador se queda con el gesto para scrollear y el `pointermove` no
       llega nunca. Esta pantalla se abre en una tablet en el depósito. */
    touch-action: none;
}

.lista-orden-item {
    display: flex;
    align-items: center;
    gap: var(--e2);
    padding: 5px var(--e2);
    font-size: 0.86rem;
    border-bottom: 1px solid var(--color-borde);
    background: var(--color-fondo);
    cursor: grab;
    user-select: none;
}
.lista-orden-item:last-child { border-bottom: 0; }
.lista-orden-item:focus-visible { outline: 2px solid var(--color-primario); outline-offset: -2px; }

/* El que se está arrastrando se atenúa: es lo que dice cuál se está moviendo
   cuando el hueco ya está en otro lado. */
.lista-orden-item.arrastrando { opacity: 0.4; cursor: grabbing; }

.lista-orden-asa { color: var(--color-texto-suave); cursor: grab; flex-shrink: 0; }

.lista-orden-texto {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.lista-orden-sacar { padding: 0 6px; line-height: 1.4; flex-shrink: 0; }

/* EL HUECO ES AMPLIO Y DICE LO QUE ES. Una línea fina de dos píxeles se pierde
   entre los bordes de los renglones y hay que adivinar dónde va a caer; un
   bloque del alto de una fila, punteado y con su texto, se ve de una. */
.lista-orden-hueco {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 2.4rem;
    margin: 3px var(--e2);
    border: 2px dashed var(--color-primario);
    border-radius: var(--radio);
    background: var(--color-primario-suave);
    color: var(--color-primario);
    font-size: 0.8rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

.lista-orden-vacio {
    padding: var(--e3);
    font-size: 0.85rem;
    color: var(--color-texto-suave);
    text-align: center;
}

/* ===========================================================
   LA FLECHITA DE ORDEN EN EL ENCABEZADO (165)
   =========================================================== */

.orden-columna { display: inline-flex; gap: 1px; margin-left: 3px; vertical-align: middle; }

.flecha-orden {
    border: 0;
    background: none;
    padding: 0 1px;
    cursor: pointer;
    font-size: 0.62rem;
    line-height: 1;
    /* Apagadas por default y con color al pasar: dos flechas negras en cada una
       de doce columnas se leen como ruido y tapan el rótulo, que es lo que hay
       que poder leer de un vistazo. */
    color: var(--color-texto-suave);
    opacity: 0.45;
}
.flecha-orden:hover { opacity: 1; }
.flecha-orden.activa { opacity: 1; color: var(--color-primario); }

/* ===========================================================
   LA BARRA DE RESUMEN DEL LISTADO
   =========================================================== */

/* El texto a la izquierda y el botón pegado a la derecha, en la misma línea.
   `align-items: center` porque el texto puede envolver en dos renglones con
   muchos filtros puestos y el botón tiene que quedar centrado contra ellos. */
.barra-resumen {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--e3);
    padding: var(--e2) var(--e3);
    font-size: 0.9rem;
}
.barra-resumen-texto { min-width: 0; }

/* ===========================================================
   LA BARRA DE FILTROS QUE MIDE LO QUE OCUPA
   =========================================================== */

/* ===========================================================
   `.campo label.checkbox`: UNA CASILLA NO ES UN RÓTULO
   ===========================================================

   `.campo label { display: block }` es (0,1,1) y `.checkbox { display:
   inline-flex }` es (0,1,0), así que adentro de un `.campo` **gana el bloque**:
   la etiqueta de la casilla se estira a la columna entera y pierde el
   `align-items: center` y el `gap` con los que el texto se apoya contra el
   control. Ninguna regla está mal escrita — es una que le gana a la otra, la
   trampa del logo de la tienda.

   Va con `.campo` adelante para empatar en (0,2,0) y ganar; escrita suelta más
   abajo dependería del orden del archivo. */
.campo label.checkbox {
    display: inline-flex;
    align-items: center;
    gap: var(--e2);
    font-size: 1.09rem;
    font-weight: 400;
    text-transform: none;
    letter-spacing: 0;
    margin-bottom: var(--e1);
}

/* Y el control de al lado mide lo que mide el dato y no la columna: un
   desplegable de tres opciones estirado a 600px al lado de una casilla de 13
   se lee como si el formulario estuviera roto. */
.campos-listado-orden .campo select,
.campos-listado-orden .campo input[type="text"] { max-width: 22rem; }

/* Y LA ETIQUETA LARGA ENVUELVE. `.checkbox` trae `white-space: nowrap`, que
   está bien para «Activo» o «Vertical» y no para «Columna en blanco para anotar
   a mano»: en un celular esa línea no entra y **desborda la página entera**, con
   la barra horizontal apareciendo en una pantalla que por lo demás entra. Se
   acota a este selector para no tocar las casillas del resto del sistema, y el
   control se alinea arriba —centrado contra dos renglones queda flotando. */
.campos-listado-orden .campo label.checkbox {
    white-space: normal;
    align-items: flex-start;
}
.campos-listado-orden .campo label.checkbox input { margin-top: 4px; }

/* `.filtros-linea` reparte el ancho de la pantalla entre sus campos, que es lo
   que hace falta cuando son seis y hay que meterlos en un renglón. Acá se pide
   lo contrario: que cada campo mida lo que necesita y no se estire.

   =====================================================================
   NO VA `width: max-content`, Y ESO COSTÓ UNA ENTREGA
   =====================================================================
   Es lo primero que uno escribe para «que la caja mida lo que ocupa», y sobre
   un flex que ENVUELVE hace justo lo contrario: el ancho max-content de un flex
   multilínea es el del ITEM MÁS ANCHO —puede cortar en cualquier lado— así que
   la barra se encoge al ancho de un desplegable y los ocho filtros salen **uno
   abajo del otro**, en una columna angosta pegada a la izquierda.

   No hay forma de pedir «lo que ocupa envolviendo» con tamaño intrínseco: o se
   envuelve, o se mide el contenido. Lo que se hace es dejar el ancho libre —el
   formulario no tiene fondo, así que no se ve ninguna caja estirada— y que los
   campos no crezcan. */
/* SON NUEVE CAJAS Y TIENEN QUE ENTRAR EN UN RENGLÓN, así que el `flex-basis`
   es más chico que el de `.filtros-linea` —que está pensado para seis—. Le gana
   por especificidad (0,5,0 contra 0,4,0) y no por posición en el archivo.

   Con el basis de seis, los ocho filtros más el botón suman 1.246px contra los
   1.161 disponibles y la barra se parte en dos renglones: cada uno empuja el
   selector de campos —que es lo que se vino a usar— media pantalla más abajo. */
.form-erp .grupo.filtros-linea.filtros-compactos .c       { flex: 1 1 108px; }
.form-erp .grupo.filtros-linea.filtros-compactos .c.crece { flex: 2 1 150px; }
.filtros-compactos .c input[type="text"] { width: 14rem; }
.filtros-compactos .c select { width: auto; min-width: 8rem; }

@media (max-width: 700px) {
    /* En un celular los campos se apilan de a dos, como en el resto del
       sistema: de a uno la barra ocupa media pantalla antes de la grilla.

       VA CON LA MISMA ESPECIFICIDAD QUE LA REGLA DE ARRIBA y no suelta: aquélla
       es (0,5,0) para ganarle a `.filtros-linea`, así que un `.filtros-compactos
       .c` pelado —(0,2,0)— pierde y en el celular seguirían entrando de a tres.
       El orden en el archivo no decide nada. */
    .form-erp .grupo.filtros-linea.filtros-compactos .c,
    .form-erp .grupo.filtros-linea.filtros-compactos .c.crece { flex: 1 1 45%; }
    .filtros-compactos .c input[type="text"],
    .filtros-compactos .c select { width: 100%; }
}

/* En un celular las dos columnas del selector se apilan: con 54% de 360px, la
   lista de columnas queda tan angosta que no entra ni un rótulo. */
@media (max-width: 700px) {
    .campos-listado { flex-direction: column; }
    .campos-listado-disponibles,
    .campos-listado-orden { flex: 1 1 100%; width: 100%; }
    .campos-listado-scroll { max-height: 16rem; }
    .lista-orden { max-height: 14rem; }
}

/* El modal del selector: ancho, y con el cuerpo scrolleando —no la caja— para
   que el título y la ✕ queden fijos. */
.modal-campos { max-width: 980px; width: 96vw; }

/* «Cómo configurar» de la solapa Mercado Pago: texto largo para leer, así que
   la caja es más ancha y el cuerpo scrollea ADENTRO — el pie con «Entendido»
   queda siempre a la vista. */
.modal-como-mp { max-width: 760px; width: 96vw; }
.como-mp-cuerpo { max-height: 70vh; overflow-y: auto; line-height: 1.5; }
.como-mp-cuerpo h3 { margin: var(--e4) 0 var(--e2); font-size: 1rem; }
.como-mp-cuerpo h3:first-child { margin-top: 0; }
.como-mp-cuerpo ol, .como-mp-cuerpo ul { margin: 0 0 var(--e2); padding-left: 1.4rem; }
.como-mp-cuerpo li { margin-bottom: var(--e1); }
.como-mp-condiciones { color: var(--color-texto-suave); }
.como-mp-modos dt { font-weight: 600; margin-top: var(--e2); }
.como-mp-modos dd { margin: var(--e1) 0 var(--e2) var(--e3); }
.modal-campos .modal-cuerpo { max-height: 76vh; overflow-y: auto; }
/* ===========================================================
   DROPDOWN / MENÚ CONTEXTUAL
   =========================================================== */

.menu-contextual {
    position: relative;
    display: inline-block;
}
.menu-contextual .lista {
    display: none;
    position: absolute;
    right: 0;
    top: 100%;
    background: var(--color-superficie);
    border: 1px solid var(--color-borde);
    border-radius: var(--radio-chico);
    box-shadow: 0 4px 12px rgba(var(--sombra-rgb),0.12);
    min-width: 140px;
    z-index: 50;
    padding: var(--e1) 0;
}
.menu-contextual.abierto .lista { display: block; }
.menu-contextual .lista a {
    display: block;
    padding: var(--e2) var(--e3);
    font-size: 1.04rem;
    color: var(--color-texto);
    text-decoration: none;
}
.menu-contextual .lista a:hover { background: var(--color-superficie-alt); }
.menu-contextual .lista a.peligro { color: var(--color-error); }
.menu-contextual .lista a:focus-visible {
    outline: 2px solid var(--color-primario);
    outline-offset: -2px;
}

/* ===========================================================
   EL BOTÓN «IMPRIMIR…» Y SU MINIMODAL (186)

   Reusa `.menu-contextual`, que estaba escrita desde siempre y no la usaba
   nadie. Lo único propio es el botón —icono y rótulo en la misma línea— y que
   la lista se abra PEGADA A LA IZQUIERDA del botón: los menús de acción de la
   casa se alinean a la derecha porque viven al final de una fila, y éste vive
   en una barra de acciones donde a su derecha hay más botones.
   =========================================================== */
/* PEGADA A LA IZQUIERDA Y CORRIDA 75px: el botón es angosto —un icono y dos
   palabras— y la lista mide 210 como mínimo, así que alineada con su borde
   izquierdo queda colgando muy a la derecha del botón. Los 75 la centran
   debajo sin taparlo. Si con eso se sale de la pantalla, `acomodarDesplegable()`
   la vuelve a meter dejando 10px de margen (186). */
.menu-imprimir .lista { right: auto; left: -75px; min-width: 210px; }
.menu-imprimir .lista a {
    display: flex;
    align-items: center;
    gap: var(--e2);
    white-space: nowrap;
}
/* El SVG hereda el color del renglón, como en la columna de acciones. */
.menu-imprimir .lista a svg {
    width: 16px;
    height: 16px;
    flex: 0 0 16px;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.7;
    stroke-linecap: round;
    stroke-linejoin: round;
}

/* ===========================================================
   EL DESPLEGABLE DE VARIAS OPCIONES (186)

   Reusa `.menu-contextual` para abrir y cerrar. Lo propio es el BOTÓN, que
   tiene que verse como un `<select>` del sistema y no como un botón: lo que
   reemplaza es un desplegable, así que en una barra de filtros al lado de dos
   campos de fecha cualquier otra cosa se lee como una acción.
   =========================================================== */
.select-multiple { display: inline-block; }

.select-multiple-boton {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--e2);
    width: 100%;
    /* LAS MISMAS MEDIDAS QUE `select`, y no un número escrito al lado: tres
       controles de altura distinta en la misma barra se leen como tres cosas
       sueltas. Es lo que ya se midió para los campos de fecha del kardex. */
    padding: 4px var(--e2);
    height: var(--alto-control);
    border: 1px solid var(--color-borde);
    border-radius: var(--radio-chico);
    background: var(--color-superficie);
    color: var(--color-texto);
    font-size: 1.1rem;
    font-family: var(--fuente-base);
    text-align: left;
    cursor: pointer;
}
.select-multiple-boton:focus-visible {
    outline: none;
    border-color: var(--color-primario);
    background: var(--color-campo-foco);
}

/* El resumen se recorta con puntos suspensivos: «3 zonas» entra siempre, pero
   el nombre de una sola puede ser largo y sin esto empuja la barra entera. */
.select-multiple .resumen {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* La punta de flecha del `<select>`, dibujada: es lo que dice que esto se
   despliega. Va con bordes y no con un glifo por lo mismo que no hay emojis en
   la columna de acciones — cada sistema operativo dibuja el suyo distinto. */
.select-multiple .flecha {
    flex: 0 0 auto;
    width: 0;
    height: 0;
    border-left: 4px solid transparent;
    border-right: 4px solid transparent;
    border-top: 5px solid var(--color-texto-suave);
}

/* La lista se abre PEGADA A LA IZQUIERDA, como el minimodal de impresión: vive
   en una barra de filtros donde a su derecha hay más campos. El tope de alto es
   lo que evita que un padrón de cuarenta zonas se vaya abajo del pliegue. */
.select-multiple .lista {
    right: auto;
    left: 0;
    min-width: 100%;
    max-height: 18rem;
    overflow-y: auto;
    padding: var(--e1);
}

.select-multiple .opcion {
    display: flex;
    align-items: center;
    gap: var(--e2);
    padding: var(--e1) var(--e2);
    margin: 0;
    font-size: 1.04rem;
    white-space: nowrap;
    cursor: pointer;
}
.select-multiple .opcion:hover { background: var(--color-superficie-alt); }
/* El `width: 100%` global de los campos no puede alcanzar a estas casillas: las
   dejaría del ancho de la lista y el rótulo se iría abajo. */
.select-multiple .opcion input[type="checkbox"] {
    width: auto;
    flex: 0 0 auto;
    margin: 0;
}


/* ===========================================================
   ELEGIR UNA OPCIÓN: EL BOTÓN Y SU MINIMODAL (187)
   ===========================================================
   El cuarto de la familia, y el que faltaba: `menu_imprimir` elige una acción,
   `select_multiple` elige varias, éste elige UNA y la deja como valor. El
   marcado y el porqué están en `partials/select_unico.php`.

   ANCLADO EN ESCRITORIO Y HOJA EN CELULAR, y eso no es una preferencia: una
   lista `position: absolute` **la recorta el contenedor con scroll** en el que
   viva —el modal del pedido del POS móvil—, que es exactamente el problema del
   `<select>` nativo que este componente vino a sacar. Movido de lugar, no
   arreglado. Fija y a lo ancho no la puede recortar nadie.
   =========================================================== */
.select-unico { display: inline-block; }

.select-unico-boton {
    display: inline-flex;
    align-items: center;
    gap: var(--e2);
    width: 100%;
    min-height: var(--alto-control);
    padding: 4px var(--e2);
    border: 1px solid var(--color-borde);
    border-radius: var(--radio-chico);
    background: var(--color-superficie);
    color: var(--color-texto);
    font-family: var(--fuente-base);
    font-size: 1.1rem;
    text-align: left;
    cursor: pointer;
}
.select-unico-boton:focus-visible {
    outline: 2px solid var(--color-primario);
    outline-offset: 1px;
}
/* La cara ocupa lo que sobra y se recorta: una opción larga no puede empujar la
   flecha afuera del botón, que es lo único que dice que esto se despliega. */
.select-unico .resumen {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.select-unico .flecha {
    flex: 0 0 auto;
    width: 0;
    height: 0;
    border-left: 5px solid transparent;
    border-right: 5px solid transparent;
    border-top: 5px solid var(--color-texto-suave);
}

.select-unico .lista {
    right: auto;
    left: 0;
    min-width: 100%;
    max-height: 18rem;
    overflow-y: auto;
    padding: var(--e1);
}
.select-unico.abierto .lista { display: block; }

.select-unico-titulo {
    display: block;
    padding: var(--e1) var(--e2);
    font-size: 0.94rem;
    font-weight: 600;
    color: var(--color-texto-suave);
}

.select-unico .opcion {
    display: block;
    position: relative;
    padding: var(--e2);
    margin: 0;
    cursor: pointer;
}
.select-unico .opcion:hover { background: var(--color-superficie-alt); }

/* EL RADIO SE ESCONDE CON `opacity` Y NUNCA CON `display:none` (186): un control
   que no se dibuja no es enfocable, y ahí el teclado deja de llegarle y un
   `required` hace que el navegador se niegue a enviar sin decir nada. */
.select-unico .opcion input {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    margin: 0;
    opacity: 0;
    cursor: pointer;
}
.select-unico .opcion .texto { display: block; font-size: 1.04rem; }
.select-unico .opcion .detalle {
    display: block;
    margin-top: 2px;
    font-size: 0.92rem;
    line-height: 1.3;
    color: var(--color-texto-suave);
}
/* La elegida se ve de un vistazo con la lista abierta: sin esto hay que leer las
   cinco para saber en cuál se está. */
.select-unico .opcion input:checked ~ .texto { font-weight: 700; color: var(--color-primario); }
.select-unico .opcion input:focus-visible ~ .texto {
    outline: 2px solid var(--color-primario);
    outline-offset: 2px;
}

/* El velo sólo existe en la hoja del celular. */
.select-unico-velo { display: none; }

/* ===========================================================
   EN CELULAR: UNA HOJA QUE SUBE DESDE ABAJO
   =========================================================== */
html.es-movil .select-unico { display: block; }

html.es-movil .select-unico-boton {
    min-height: 48px;          /* lo que se toca, como todo en esas pantallas */
    font-size: 16px;           /* abajo de 16 iOS hace zoom solo al enfocar */
}

html.es-movil .select-unico.abierto .select-unico-velo {
    display: block;
    position: fixed;
    inset: 0;
    z-index: 1200;
    width: 100%;
    padding: 0;
    border: 0;
    border-radius: 0;
    background: rgba(var(--sombra-rgb), 0.45);
}

html.es-movil .select-unico .lista {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    top: auto;
    z-index: 1201;
    /* Hasta 70% de la ventana: más que eso tapa la pantalla entera y se pierde de
       qué se estaba eligiendo. */
    max-height: 70vh;
    min-width: 0;
    border-radius: var(--radio) var(--radio) 0 0;
    padding: var(--e2) var(--e2) var(--e4);
    box-shadow: 0 -6px 20px rgba(var(--sombra-rgb), 0.25);
    /* El área segura del iPhone: sin esto la última opción queda abajo de la
       barra de gestos y no se puede tocar. */
    padding-bottom: calc(var(--e4) + env(safe-area-inset-bottom, 0px));
}

html.es-movil .select-unico-titulo {
    font-size: 15px;
    padding: var(--e1) var(--e2) var(--e2);
}
html.es-movil .select-unico .opcion {
    display: flex;
    flex-direction: column;
    justify-content: center;
    min-height: 48px;
    padding: var(--e2);
    border-bottom: 1px solid var(--color-borde);
}
html.es-movil .select-unico .opcion:last-child { border-bottom: 0; }
html.es-movil .select-unico .opcion .texto { font-size: 16px; }
html.es-movil .select-unico .opcion .detalle { font-size: 13px; }

.select-multiple-todas {
    display: block;
    width: 100%;
    padding: var(--e1) var(--e2);
    margin-bottom: var(--e1);
    border: 0;
    border-bottom: 1px solid var(--color-borde);
    border-radius: 0;
    background: none;
    color: var(--color-primario);
    font-size: 1.02rem;
    font-family: var(--fuente-base);
    text-align: left;
    cursor: pointer;
}
.select-multiple-todas:hover { background: var(--color-superficie-alt); }

/* Un botón normal con su icono adelante. No es `.btn-accion`: aquél es un
   cuadrado de 26px sin texto, y acá el rótulo es la mitad del botón. */
.boton-con-icono {
    display: inline-flex;
    align-items: center;
    gap: var(--e2);
}
.boton-con-icono svg {
    width: 16px;
    height: 16px;
    flex: 0 0 16px;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.7;
    stroke-linecap: round;
    stroke-linejoin: round;
}

/* ===========================================================
   TOOLTIP simple
   =========================================================== */

.tooltip {
    position: relative;
    border-bottom: 1px dotted var(--color-texto-tenue);
    cursor: help;
}
.tooltip::after {
    content: attr(data-tooltip);
    position: absolute;
    bottom: 125%; left: 50%;
    transform: translateX(-50%);
    background: var(--color-texto);
    color: var(--color-superficie);
    padding: var(--e1) var(--e2);
    border-radius: var(--radio-chico);
    font-size: 0.92rem;
    white-space: nowrap;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.12s;
}
.tooltip:hover::after { opacity: 1; }

/* ===========================================================
   ESTADOS VACÍOS / CARGA
   =========================================================== */

.estado-vacio {
    text-align: center;
    padding: var(--e5) var(--e4);
    color: var(--color-texto-tenue);
}
.estado-vacio .icono { font-size: 2.12rem; margin-bottom: var(--e2); }

.girador {
    width: 16px; height: 16px;
    border: 2px solid var(--color-borde);
    border-top-color: var(--color-primario);
    border-radius: 50%;
    animation: girar 0.6s linear infinite;
    display: inline-block;
}
@keyframes girar { to { transform: rotate(360deg); } }

/* ===========================================================
   UTILIDADES
   =========================================================== */

.text-right   { text-align: right; }
.text-center  { text-align: center; }
.text-suave   { color: var(--color-texto-suave); }
.text-tenue   { color: var(--color-texto-tenue); }
.text-exito   { color: var(--color-exito); }
.text-error   { color: var(--color-error); }
.text-mono    { font-family: var(--fuente-mono); font-variant-numeric: tabular-nums; }
.oculto       { display: none !important; }

/* Una aclaración chica debajo del rótulo de una columna («sólo visitas
   presenciales»): en su propio renglón, sin la negrita ni las mayúsculas del
   encabezado, para que se lea como nota y no como otro rótulo. */
.th-nota {
    display: block;
    font-size: 0.78em;
    font-weight: normal;
    text-transform: none;
    letter-spacing: normal;
    color: var(--color-texto-suave);
}

/* El número de la entidad adelante del nombre en los listados: «123 CLIENTE SA».
   Uno solo para todas las pantallas que lo muestran. */
.id-entidad { font-weight: 700; margin-right: 0.35em; font-variant-numeric: tabular-nums; }

/* El atributo `hidden` gana SIEMPRE.
 *
 * El navegador lo aplica con `[hidden] { display: none }` desde su hoja de
 * usuario, que pierde contra cualquier clase de acá: un `<div class="flex-gap-2"
 * hidden>` queda a la vista igual, porque `.flex-gap-2` dice `display: flex`. Se
 * ve en cuanto una pantalla esconde una fila de botones (los dos juegos de la
 * ficha del artículo) y es de las cosas que uno da por sentado que funcionan.
 *
 * Va acá arriba y para todo el sistema porque `hidden` es la forma en que este
 * proyecto pliega cosas —las variantes del listado agrupado, los paneles de las
 * pestañas, la edición en línea de proveedores— y ninguna de esas pantallas
 * tendría por qué acordarse de esto. */
[hidden] { display: none !important; }

/* Lo que se ve sólo en el celular y lo que se ve sólo en la PC.
 *
 * La clase la estampa `partials/movil.php` en `<html>`, y NO se resuelve con
 * `@media` directamente por dos motivos:
 *
 *   · La pregunta no es sólo el ancho: es `(max-width: 820px) and
 *     (pointer: coarse)`, o sea que una notebook con la ventana achicada NO es
 *     un celular. Escrita como `@media` en cada regla, esa condición quedaría
 *     copiada por todos lados y el día que se ajuste habría que buscarla.
 *   · El override `?movil=1` no existe para el CSS. Sin la clase, la única
 *     forma de probar la pantalla del salón sería con un celular en la mano.
 *
 * `!important` por lo mismo que `[hidden]`: esconden cosas que casi siempre
 * traen su propio `display` (una fila de botones con `.flex-gap-2`, una opción
 * de menú), y sin él quedan a la vista igual. */
html:not(.es-movil) .solo-movil      { display: none !important; }
html.es-movil       .solo-escritorio { display: none !important; }

.flex         { display: flex; }
.flex-entre   { display: flex; justify-content: space-between; align-items: center; }
.flex-gap-1   { display: flex; gap: var(--e1); }
.flex-gap-2   { display: flex; gap: var(--e2); }
.flex-wrap    { flex-wrap: wrap; }

.mt-1 { margin-top: var(--e1); } .mt-2 { margin-top: var(--e2); } .mt-3 { margin-top: var(--e3); } .mt-4 { margin-top: var(--e4); }
.mb-1 { margin-bottom: var(--e1); } .mb-2 { margin-bottom: var(--e2); } .mb-3 { margin-bottom: var(--e3); } .mb-4 { margin-bottom: var(--e4); }

/* ===========================================================
   RESPONSIVE
   =========================================================== */

@media (max-width: 700px) {
    .barra-superior { flex-direction: column; height: auto; padding: var(--e2); gap: var(--e1); align-items: flex-start; }
    .layout-app { flex-direction: column; }
    .menu-lateral { width: 100%; flex-direction: row; overflow-x: auto; border-right: none; border-bottom: 1px solid var(--color-borde); padding: var(--e1); }
    .menu-lateral a { border-left: none; border-bottom: 2px solid transparent; }
    .menu-lateral a.activo { border-left-color: transparent; border-bottom-color: var(--color-primario); }
    .fila-campos.cols-2, .fila-campos.cols-3 { grid-template-columns: 1fr; }
    .layout-app .contenido { padding: var(--e3); }
}

/* El acceso al pedido, arriba del tablero y sólo en el celular.
 *
 * Grande a propósito: es lo primero que hace el vendedor del salón cuando abre
 * la app, y en un teléfono lo que no ocupa media pantalla se busca. */
.acceso-pos-movil {
    display: flex;
    align-items: center;
    gap: var(--e3);
    min-height: 72px;
    padding: var(--e3);
    margin-bottom: var(--e3);
    background: var(--color-primario);
    color: var(--color-primario-texto);
    border-radius: 12px;
    text-decoration: none;
    box-shadow: 0 2px 10px rgba(var(--sombra-rgb), .16);
}
.acceso-pos-movil:hover { text-decoration: none; color: var(--color-primario-texto); }
.acceso-pos-movil .icono { font-size: 30px; line-height: 1; flex-shrink: 0; }
.acceso-pos-movil .texto { flex: 1 1 auto; min-width: 0; }
.acceso-pos-movil .texto strong { display: block; font-size: 17px; }
.acceso-pos-movil .texto small { display: block; font-size: 13px; opacity: .85; margin-top: 2px; color: var(--color-primario-texto); }
.acceso-pos-movil .flecha { font-size: 28px; opacity: .8; flex-shrink: 0; }

@media print { .acceso-pos-movil { display: none !important; } }

/* ===========================================================
   EL MENÚ EN EL CELULAR: burger y panel deslizante
   ===========================================================

   EL MENÚ DE ESCRITORIO NO SE TOCA. Todo lo de acá cuelga de `html.es-movil`,
   que estampa `partials/movil.php` sólo cuando la pantalla es angosta Y el
   puntero es grueso. Una notebook con la ventana achicada sigue viendo el menú
   de siempre —incluido el bloque de `max-width: 700px` de más arriba, que se
   escribió para eso y sigue valiendo—.

   POR QUÉ NO ALCANZABA CON AQUEL BLOQUE. Ahí el menú pasa a una tira
   horizontal que scrollea, y con nueve secciones eso son nueve íconos en una
   franja de la que sólo se ven tres: hay que arrastrar para encontrar la
   sección y encima el panel de cada una se despliega al pasar el mouse, que en
   un teléfono no existe. El burger muestra las nueve de una y se cierra solo.

   EL PANEL VA `fixed` Y NO `sticky`, al revés que el de escritorio. Acá se
   monta ENCIMA del contenido en vez de convivir con él, así que no hay flujo
   del que salirse ni contenido que empujar: es un cajón que tapa la pantalla
   mientras está abierto. */

/* El botón. Fuera de `es-movil` lo esconde `.solo-movil` (más arriba). */
.boton-burger {
    background: rgba(var(--velo-barra-rgb), 0.14);
    border: 1px solid rgba(var(--velo-barra-rgb), 0.35);
    color: inherit;
    border-radius: var(--radio);
    /* 44px es el mínimo con el que un dedo acierta sin pensarlo. Todo lo que se
       toca en el celular respeta ese piso; ver también `.pos-movil` más abajo. */
    min-width: 44px; min-height: 44px;
    display: flex; align-items: center; justify-content: center;
    cursor: pointer;
    padding: 0 var(--e2);
    flex-shrink: 0;
}
.boton-burger svg { width: 22px; height: 22px; stroke: currentColor; stroke-width: 2;
                    fill: none; stroke-linecap: round; }

/* El velo. Existe para dos cosas: oscurecer lo de atrás —que deja de ser
   tocable— y ser el blanco del toque que cierra. Sin él habría que adivinar si
   un toque cayó "afuera" mirando coordenadas. */
.velo-menu {
    position: fixed; inset: 0;
    background: rgba(var(--scrim-rgb), 0.5);
    z-index: 49;                 /* justo abajo del menú, que está en 50 */
    opacity: 0;
    transition: opacity .18s ease;
}
.velo-menu[hidden] { display: none; }
.velo-menu.visible { opacity: 1; }

/* LA BARRA DEL CELULAR ES UNA SOLA FILA DE `--altura-header`, Y NO ES ESTÉTICA.
 *
 * El bloque de `max-width: 700px` de más arriba la pasa a COLUMNA con
 * `height: auto`. Se escribió para una ventana angosta de escritorio, antes de
 * que existiera el burger, y sigue valiendo ahí; pero en un teléfono aplica
 * también —360px es menos que 700— y la deja MÁS ALTA que eso —cuánto, depende de qué tenga adentro—.
 *
 * De esa diferencia salían dos cosas que se veían como problemas distintos:
 *
 *   · el cajón del menú arranca en `top: var(--altura-header)`, o sea POR ARRIBA
 *     del borde de abajo de la barra — y la barra lo tapa, porque está
 *     en z-index 60 contra 50. Se ve como que el menú "queda por debajo".
 *   · `.pos-movil` mide `100dvh - var(--altura-header)`, así que su pie —el
 *     botón de guardar, el único irreversible— cae abajo del pliegue y
 *     hay que scrollear para llegar.
 *
 * Los dos se arreglan con que la barra mida lo que dice medir. Entra en una
 * fila porque el usuario se fue al cajón y la navegación rápida no se dibuja:
 * quedan el burger y la marca. */
html.es-movil .barra-superior {
    flex-direction: row;
    align-items: center;
    height: var(--altura-header);
    padding: 0 var(--e2);
    gap: var(--e2);
}

html.es-movil .layout-app { display: block; }

html.es-movil .menu-lateral {
    position: fixed;
    top: var(--altura-header);
    left: 0;
    bottom: 0;
    width: min(78vw, 300px);
    /* Acá SÍ scrollea, y tiene que hacerlo: nueve secciones desplegadas no
       entran en la pantalla de un teléfono. Es lo contrario del de escritorio,
       donde `overflow: visible` es obligatorio para que el panel de cada
       sección se pueda dibujar por fuera del menú. */
    overflow-y: auto;
    overflow-x: hidden;
    z-index: 50;
    border-right: 1px solid var(--color-borde);
    border-bottom: none;
    flex-direction: column;
    padding: var(--e2) 0;
    /* Cerrado: corrido a la izquierda. Se mueve con `transform` y no con `left`
       para que la animación no obligue al navegador a recalcular el layout en
       cada cuadro, que en un teléfono se nota. */
    transform: translateX(-100%);
    transition: transform .2s ease;
    box-shadow: 2px 0 14px rgba(var(--sombra-rgb), .18);
}
html.es-movil .menu-lateral.abierto { transform: translateX(0); }

/* ADENTRO DEL CAJÓN LAS SECCIONES SON UN ACORDEÓN: se ven las nueve y las
 * opciones de una se despliegan al tocarla.
 *
 * Antes iban todas desplegadas de una. Con nueve secciones y sesenta y pico de
 * opciones eso es una lista larguísima en la que hay que scrollear para
 * encontrar la sección —que es exactamente lo que el burger vino a resolver— y
 * en la que el toque cae fácil en la opción de al lado. Desplegadas de a una,
 * las nueve entran sin scroll y llegar a cualquier pantalla son dos toques.
 *
 * LA CLASE ES `desplegado` Y NO `abierto`, A PROPÓSITO. `abierto` es la del
 * menú de ESCRITORIO —la que pone y saca el manejador del hover— y compartirla
 * sería que dos manejadores distintos escriban el mismo estado: un `mouseenter`
 * sintético de los que dispara un toque en algunos navegadores abriría la
 * sección justo antes de que el clic la cierre, y el resultado sería que tocar
 * no hace nada. Son dos comportamientos del mismo elemento con reglas
 * distintas, como el teclado del POS y el de los listados. */
html.es-movil .menu-lateral .panel-grupo {
    position: static;
    display: none;
    box-shadow: none;
    border: none;
    min-width: 0;
    background: transparent;
    padding: 0 0 var(--e2) 0;
}
html.es-movil .menu-lateral .grupo-menu.desplegado .panel-grupo { display: block; }

/* La flecha dice si la sección está abierta, que es lo único que lo dice
   cuando está cerrada y no se ve ninguna opción. Se dibuja desde el CSS y no
   con un carácter en el marcado, así el menú de escritorio no la hereda. */
html.es-movil .menu-lateral .grupo-menu > .cabecera-grupo::after {
    content: "";
    margin-left: auto;
    width: 8px; height: 8px;
    border-right: 2px solid currentColor;
    border-bottom: 2px solid currentColor;
    opacity: .55;
    transform: rotate(45deg) translate(-2px, -2px);
    transition: transform .15s ease;
    flex-shrink: 0;
}
html.es-movil .menu-lateral .grupo-menu.desplegado > .cabecera-grupo::after {
    transform: rotate(-135deg) translate(-2px, -2px);
}
/* Inicio y Pedido son enlaces sueltos y no tienen nada que desplegar. */
html.es-movil .menu-lateral a.cabecera-grupo::after { content: none; }

/* EL USUARIO VIVE AL FONDO DEL CAJÓN, Y ESE LUGAR ES LA DECISIÓN.
 *
 * Estaba en la barra superior, que en un teléfono son tres renglones de texto
 * chico con "Cerrar sesión" a un toque de distancia del pulgar — y cerrar la
 * sesión en medio de un pedido es perderlo. Acá hay que abrir el menú, bajar
 * hasta el fondo y recién ahí está: nadie llega sin querer.
 *
 * `margin-top: auto` lo empuja al fondo del cajón, que ya es un flex en
 * columna. Con el menú más alto que la pantalla no empuja nada y queda después
 * de la última sección, que es lo correcto: sigue estando al final. */
html.es-movil .menu-usuario {
    margin-top: auto;
    border-top: 1px solid var(--color-borde);
    padding: var(--e3) var(--e3) var(--e2);
    display: flex;
    flex-direction: column;
    gap: var(--e1);
}
html.es-movil .menu-usuario .quien {
    font-size: 13px;
    color: var(--color-texto-suave);
    line-height: 1.35;
}
html.es-movil .menu-usuario .quien strong { display: block; font-size: 15px; color: var(--color-texto); }
html.es-movil .menu-usuario a { min-height: 44px; display: flex; align-items: center; }
html.es-movil .menu-usuario a.salir { color: var(--color-error); }
html.es-movil .menu-lateral .cabecera-grupo { padding: var(--e2) var(--e3); }
html.es-movil .menu-lateral .panel-grupo a { padding: var(--e2) var(--e3) var(--e2) var(--e4); }
/* El piso del dedo, otra vez: una opción de 28px se falla y se entra a la de al lado. */
html.es-movil .menu-lateral a { min-height: 44px; display: flex; align-items: center; }

@media (prefers-reduced-motion: reduce) {
    .menu-lateral, .velo-menu { transition: none; }
}

@media print { .velo-menu, .boton-burger { display: none !important; } }

/* ===========================================================
   IMPRESIÓN (para cuando lleguemos a tickets/comprobantes)
   =========================================================== */

@media print {
    .barra-superior, .menu-lateral, .barra-acciones .boton-primario,
    .paginacion, .no-imprimir { display: none !important; }
    .layout-app .contenido { padding: 0; }
    body { background: white; }
}

/* ===========================================================
   ESTILO ERP: campos densos, inputs de grilla sin bordes
   =========================================================== */

/* Formulario denso: etiqueta arriba, poco margen, ancho por tipo de dato */
.form-erp {
    background: var(--color-superficie);
    border: 1px solid var(--color-borde);
    border-radius: var(--radio);
    padding: var(--e3);
}

/* ===========================================================
   LA FILA DE CAMPOS NO ALINEA POR ABAJO, Y ES A PROPÓSITO

   Tenía `align-items: flex-end`, que hace coincidir los INPUTS cuando los
   rótulos de una fila miden distinto. Suena bien y en la práctica no pasaba
   nunca: se midió en Chrome, fila por fila, en las 30 pantallas con
   `.form-erp` que se pueden abrir, y en NINGUNA los hijos tenían altos
   distintos — o sea que la regla no alineaba nada. Lo que sí hacía era
   empujar hacia abajo a cualquier hijo más bajo que el más alto de la fila:
   un campo suelto al lado de un recuadro quedaba pegado al piso, con el
   hueco arriba.

   Si alguna fila de verdad la necesita, se la pone ELLA en su `style` —hay
   seis que ya lo hacen— en vez de pagarla en las setenta y nueve.
   =========================================================== */
.form-erp .grupo {
    display: flex;
    flex-wrap: wrap;
    gap: var(--e2) var(--e3);
    margin-bottom: var(--e2);
}

.form-erp .c {
    display: flex;
    flex-direction: column;
    flex: 0 0 auto;
}
.form-erp .c.crece { flex: 1 1 200px; min-width: 160px; }

.form-erp label {
    font-size: 12px;
    font-weight: 600;
    color: var(--color-texto-suave);
    margin-bottom: 2px;
    white-space: nowrap;
    text-transform: uppercase;
    letter-spacing: 0.02em;
}

/* La planilla de rendición (`repartos/_planilla_rendicion.php`) no vive adentro de un
   `.form-erp`: sus campos apuntan al formulario con `form=`, así que sin sumarla acá
   salían con la caja del navegador y sólo el amarillo de `.campo-carga`. */
.form-erp input[type="text"],
.form-erp input[type="number"],
.form-erp input[type="email"],
.tabla-rendicion input[type="text"],
.form-erp select,
select {
    padding: 4px var(--e2);
    border: 1px solid var(--color-borde);
    border-radius: var(--radio-chico);
    font-size: 1.1rem;
    font-family: var(--fuente-base);
    background: var(--color-superficie);
    height: var(--alto-control);
    width: 100%;
}

/* El desplegable de «Ordenar los ítems» de la hoja de carga (`repartos/carga-ver.php`)
   mide lo que mide su texto: con el `width: 100%` de arriba se estiraba a todo el
   ancho de la pantalla por dos opciones cortas. `max-width` lo deja achicarse en el
   celular sin salirse. */
.orden-carga select { width: auto; max-width: 100%; min-width: 0; }
.orden-carga label  { white-space: nowrap; }

.form-erp input:focus, .form-erp select:focus,
.tabla-rendicion input[type="text"]:focus {
    outline: none;
    border-color: var(--color-primario);
    background: var(--color-campo-foco);
}

.form-erp input[readonly] {
    background: var(--color-superficie-alt);
    color: var(--color-texto-suave);
    border-color: transparent;
}

/**
 * UN CAMPO QUE SE CARGA, con el amarillo de trabajo del sistema.
 *
 * `--color-campo-foco` es el mismo que el ERP usa al enfocar —lo leen trece
 * reglas de esta hoja— y acá se pone SIEMPRE y no sólo con el foco: adentro de
 * una tabla, un input con el mismo fondo que la celda no se lee como un campo,
 * y lo que hay que decir es "acá se escribe" ANTES de que alguien lo toque.
 *
 * (Era el literal `#fffdf0` escrito trece veces. Se volvió token porque en
 * modo oscuro era el que rompía: crema con el texto claro encima da 1.29:1 y
 * el contenido del campo desaparece justo al entrar en él.)
 *
 * =====================================================================
 * VA ACÁ ABAJO, Y CON `.form-erp` ADELANTE, POR ESPECIFICIDAD
 * =====================================================================
 * `.form-erp input[type="text"]` es (0,2,1) y `.campo-carga` sola es (0,1,0):
 * declarada arriba y sin el contexto, PIERDE y el campo sale blanco — sin
 * ninguna regla rota y sin ningún error, que es la trampa del logo de la tienda.
 * Lo agarró la medición, no la lectura.
 *
 * El selector empata en (0,2,1) y gana por estar después; la forma suelta queda
 * para los campos que no viven en un `.form-erp`.
 */
.form-erp input.campo-carga,
.tabla-rendicion input.campo-carga,
.campo-carga {
    background: var(--color-campo-foco);
}
.form-erp input.campo-carga:focus,
.tabla-rendicion input.campo-carga:focus,
.campo-carga:focus {
    outline: none;
    border-color: var(--color-primario);
    background: var(--color-campo-foco);
}

/* El "(opcional)" / "(informativo)" del rótulo no es parte del rótulo: sin esto
   sale en mayúsculas y en negrita como él. Ya existía para .formulario-abm. */
.form-erp label .opcional {
    font-weight: 400;
    text-transform: none;
    letter-spacing: 0;
    color: var(--color-texto-tenue);
}

/* Anchos por tipo de dato: el campo mide lo que mide el dato */
.a-mini   { width: 60px  !important; }
.a-corto  { width: 90px  !important; }
.a-medio  { width: 140px !important; }
.a-largo  { width: 220px !important; }
.a-full   { width: 100%  !important; }

/* ===========================================================
   UNA BARRA DE FILTROS QUE ENTRA EN UN RENGLÓN (`.filtros-linea`)

   La usa Catálogo → Cambio masivo, donde arriba van los filtros, en el medio
   la acción y abajo la grilla: dos renglones de filtros empujan la grilla —que
   es lo que se vino a mirar— media pantalla hacia abajo, todos los días.

   =====================================================================
   LOS CAMPOS SE REPARTEN EL ANCHO EN VEZ DE MEDIR SU CONTENIDO
   =====================================================================
   `.form-erp .c` es `flex: 0 0 auto` y el `<select>` de adentro es `width:100%`,
   así que cada caja termina midiendo su opción más larga: alcanza una categoría
   con nombre largo para que esa sola se lleve media fila y el resto se vaya al
   renglón de abajo. Acá el `flex-basis` es chico y todas se reparten lo que
   sobra, así que el nombre que alguien escriba no decide el ancho de nada.

   El buscador pesa el doble: es texto libre y es donde se escribe.

   EL `min-width: 0` ES DEFENSA EN PROFUNDIDAD Y SU CONTRASTE ES MUDO: con
   los `flex-basis` de acá los campos entran igual, así que sacarlo no pone
   nada en rojo hoy. Se queda porque es el mínimo de un flex item —`auto`,
   o sea su contenido— lo que parte la fila en cuanto un desplegable crezca,
   y lo que no se puede es fingir que una prueba lo cubre. Es el mismo caso
   que el `min-width: 0` de la tarjeta de estilo de los flyers.

   EN UN CELULAR pasan a dos por renglón —tres filas—, y no a uno como el resto
   del sistema: son campos cortos —desplegables y un buscador— y apilados de a
   uno la barra ocupa media pantalla antes de llegar a la grilla.
   =========================================================== */
.form-erp .grupo.filtros-linea .c        { flex: 1 1 140px; min-width: 0; }
.form-erp .grupo.filtros-linea .c.crece  { flex: 2 1 170px; min-width: 0; }
.form-erp .grupo.filtros-linea .c-boton  { flex: 0 0 auto; }

/* -----------------------------------------------------------
   `.filtros-muchos`: LA MISMA BARRA CON MÁS DE SEIS CAMPOS

   El `flex-basis` de arriba está pensado para SEIS cajas —así lo dice el
   comentario— y Caja → Movimientos tiene OCHO filtros más el botón. Medido a
   1280px, que es la notebook con la que se reportó: los basis suman 1.286px
   contra los 1.161 disponibles, así que la barra se parte en dos renglones.

   Y ahí se ve peor de lo que es: al envolver, «Comprobante» queda solo en su
   línea con `flex-grow: 1` y se estira a 1.094px — un desplegable del ancho de
   la pantalla arriba de la grilla.

   LO QUE DECIDE SI ENVUELVE ES EL `flex-basis`, NO EL ANCHO FINAL. Los campos
   ya podían encogerse (`min-width: 0`), pero el motor reparte en líneas ANTES
   de encoger: mira el tamaño base de cada ítem, y recién dentro de cada línea
   aplica el `flex-shrink`. Por eso lo que hace lugar es bajar la base y no
   tocar anchos.

   NO SE REUSA `.filtros-compactos`, que tiene estos mismos dos números: aquella
   clase además pide que cada campo MIDA SU CONTENIDO (`select { width: auto }`),
   que es lo contrario de lo que esta barra necesita — su propio comentario dice
   que alcanza un punto de venta con nombre largo para que ese solo desplegable
   se lleve media fila. Son dos intenciones distintas que coinciden en el basis.

   Medido después: a 1280px entra en UN renglón con 119px de sobra, y a 1440px
   también. Abajo de ~1160px vuelve a envolver, que es correcto —ahí de verdad
   no entra— y el botón ya no se estira porque lleva `c-boton`.
   ----------------------------------------------------------- */
.form-erp .grupo.filtros-linea.filtros-muchos .c       { flex: 1 1 108px; }
.form-erp .grupo.filtros-linea.filtros-muchos .c.crece { flex: 2 1 150px; }
/* Y EL BOTÓN SE REPITE ACÁ, QUE ES LA TRAMPA DE SIEMPRE.
   La regla de arriba es (0,5,0) y `.filtros-linea .c-boton` es (0,4,0), así que
   sobre un `<div class="c c-boton">` **le gana la de arriba** y el botón vuelve
   a crecer — medido: 115px a 1280px y 1.041px cuando la barra envuelve. Ninguna
   de las dos reglas está mal escrita: es una que le gana a la otra, la trampa
   del logo de la tienda. Empata en (0,5,0) y gana por ir después. */
.form-erp .grupo.filtros-linea.filtros-muchos .c-boton { flex: 0 0 auto; }

/* UN CAMPO DE FECHA NO SE PUEDE ANGOSTAR COMO UN DESPLEGABLE.
   Un `<select>` recortado sigue siendo legible —«Ingresos y eg…» se entiende— y
   además se abre para ver el resto. Un `input[type="date"]` no: el navegador
   dibuja dd/mm/aaaa más su ícono de calendario, y a 121px **el año sale
   cortado** («01/09/202»). Un filtro de fechas que no deja leer el año no sirve
   para nada, y es lo único que se rompió al angostar la barra.

   Y NO SE PUEDE MEDIR CON `scrollWidth`: los campos internos de un `date` viven
   en su shadow DOM, así que el navegador informa que no recorta nada. Se vio
   mirando la pantalla a 2x, que es lo que hay. */
.form-erp .grupo.filtros-linea.filtros-muchos .c-fecha { flex: 1 1 132px; }

@media (max-width: 700px) {
    /* Le gana por especificidad (0,4,0) a la regla de abajo que apila los
       campos de todo `.form-erp` —(0,2,0)—, así que el orden en el archivo no
       decide nada. */
    .form-erp .grupo.filtros-linea .c,
    .form-erp .grupo.filtros-linea .c.crece { flex: 1 1 45%; }
    /* Y el botón sigue midiendo lo que dice: se sienta al lado del
       último campo en vez de gastar un cuarto renglón para él solo. */
    .form-erp .grupo.filtros-linea .c-boton { flex: 0 0 auto; }

    /* `.filtros-muchos` TIENE QUE REPETIRSE ACÁ, y por lo mismo que arriba:
       las reglas de esta media query son (0,4,0) y (0,5,0), y las del bloque de
       `.filtros-muchos` son (0,5,0) y (0,6,0) — o sea que **le ganan al
       apilado** y en un celular esa barra seguiría con los 108px, entrando de a
       tres por renglón en vez de a dos. El orden en el archivo no decide nada.

       Es exactamente la nota que `.filtros-compactos` ya tiene escrita para su
       propia media query, con el mismo motivo. */
    .form-erp .grupo.filtros-linea.filtros-muchos .c,
    .form-erp .grupo.filtros-linea.filtros-muchos .c.crece,
    .form-erp .grupo.filtros-linea.filtros-muchos .c-fecha { flex: 1 1 45%; }
    .form-erp .grupo.filtros-linea.filtros-muchos .c-boton { flex: 0 0 auto; }
}

/* -----------------------------------------------------------
   BUSCADOR DE CLIENTE / PROVEEDOR (partials/buscador_entidades.php)

   Tiene que verse EXACTAMENTE como el <select> que reemplaza: es el mismo
   renglón del mismo formulario y no puede leerse como un campo de otra clase.
   Por eso repite las medidas de `.form-erp select` en vez de heredar las del
   buscador del POS, que son las de un campo que ocupa la pantalla.
   ----------------------------------------------------------- */
.buscador-entidad {
    display: flex;
    align-items: center;
    gap: var(--e1);
    width: 100%;
}
.buscador-entidad .pos-buscador {
    flex: 1 1 auto;
    min-width: 0;
    margin: 0;
    padding: 0;
    border: none;
    border-radius: 0;
    background: transparent;
}
.buscador-entidad .pos-buscador input[type="text"] {
    width: 100%;
    height: var(--alto-control);
    padding: 4px var(--e2);
    font-size: 1.1rem;
    font-family: var(--fuente-base);
    border: 1px solid var(--color-borde);
    border-radius: var(--radio-chico);
    background: var(--color-superficie);
}
.buscador-entidad .pos-buscador input[type="text"]:focus {
    outline: none;
    border-color: var(--color-primario);
    background: var(--color-campo-foco);
}

.form-erp .checks {
    display: flex;
    gap: var(--e4);
    align-items: center;
    padding: var(--e2) 0;
    border-top: 1px solid var(--color-borde);
    margin-top: var(--e2);
}
.form-erp .checks label {
    text-transform: none;
    letter-spacing: 0;
    font-weight: 400;
    color: var(--color-texto);
    margin: 0;
    cursor: pointer;
}
.form-erp .checks .campo-check { margin: 0; }

.barra-guardar {
    margin-top: var(--e3);
}
.form-erp .barra-guardar{
    display: flex;
    gap: var(--e2);
    padding-top: var(--e2);
    border-top: 1px solid var(--color-borde);
    margin-top: var(--e2);
}

/* ==========================================================================
   SWITCH SEGMENTADO — elegir entre dos (o tres) cosas excluyentes
   ==========================================================================
   SON RADIOS DE VERDAD debajo. El input se esconde con `position: absolute` y
   `opacity: 0` y NO con `display: none`: escondido de esa otra manera el
   navegador lo saca del recorrido de Tab y deja el control sin teclado, que es
   justamente lo que se gana usando radios.

   El foco se dibuja sobre el `<span>` con `:has()`... NO: este proyecto no usa
   `:has()` en ningún lado (ver la nota de `color-mix()` en la tienda), así que
   va con el selector de hermano, que es el que existe desde siempre.
   ========================================================================== */
.segmentado {
    display: inline-flex;
    padding: 3px;
    gap: 3px;
    background: var(--color-superficie-alt);
    border: 1px solid var(--color-borde);
    border-radius: 999px;
}

.segmentado-opcion {
    position: relative;
    margin: 0;
}

/* Escondido para el ojo, presente para el teclado y el lector de pantalla. */
.segmentado-opcion input {
    position: absolute;
    opacity: 0;
    width: 100%;
    height: 100%;
    margin: 0;
    cursor: pointer;
}

.segmentado-opcion span {
    display: block;
    padding: 6px 18px;
    border-radius: 999px;
    font-size: 0.92rem;
    font-weight: 600;
    color: var(--color-texto-suave);
    white-space: nowrap;
    user-select: none;
    cursor: pointer;
    transition: background 0.12s, color 0.12s;
}

.segmentado-opcion input:checked + span {
    background: var(--color-primario);
    color: var(--color-primario-texto);
}

/* El foco del teclado se ve, que es la mitad de por qué esto son radios. */
.segmentado-opcion input:focus-visible + span {
    outline: 2px solid var(--color-primario);
    outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
    .segmentado-opcion span { transition: none; }
}

/* --------------------------------------------------------------------------
   La barra de botones partida: lo que hace avanzar a la izquierda, lo que
   termina a la derecha.

   Va con las DOS clases (0,2,0) para empatarle a `.form-erp .barra-guardar` y
   ganarle por posición: escrita con una sola clase, en un formulario `.form-erp`
   pierde el `justify-content` y los cuatro botones quedan pegados a la
   izquierda. Es la trampa del logo de la tienda.
   -------------------------------------------------------------------------- */
.barra-guardar.barra-guardar-partida {
    display: flex;
    flex-wrap: wrap;
    gap: var(--e2);
    justify-content: space-between;
    align-items: center;
}

/* --------------------------------------------------------------------------
   El modal de cobro: más ancho que el de un aviso, porque adentro va una
   tabla de medios de pago con sus importes.
   -------------------------------------------------------------------------- */
.modal-cobro {
    max-width: 620px;
    width: 100%;
    /* --------------------------------------------------------------------
       EL PIE NO SE PUEDE IR DE LA PANTALLA, y por eso esta ventana acota su
       alto y la de un aviso no. Adentro tiene una tabla que crece con los
       medios de pago que tenga cargados el comercio, así que en una notebook
       —y con ocho medios— el botón de EMITIR quedaba abajo del borde: la
       ventana se veía entera salvo justo aquello para lo que se abre, y no
       hay ningún indicio de que haya más abajo.
       Con el alto acotado, lo que scrollea es el CUERPO y el pie se queda
       pegado abajo, siempre visible. El `8vh` es el `padding-top` con el que
       `.fondo-modal` la despega de arriba.
       -------------------------------------------------------------------- */
    display: flex;
    flex-direction: column;
    max-height: calc(100vh - 8vh - var(--e4));
}
.modal-cobro .modal-cuerpo {
    /* --------------------------------------------------------------------
       ES EL CUERPO EL QUE SCROLLEA, no la ventana: así el pie se queda pegado
       abajo y siempre a la vista.
       Y NO LLEVA `min-height: 0`, que es lo que uno escribe acá de memoria —el
       mínimo automático de un hijo de flex es el alto de su contenido, y eso
       le impediría encogerse—. NO HACE FALTA porque esa regla sólo vale para
       los hijos con `overflow: visible`: en cuanto este pasó a `auto`, su
       mínimo automático ya es cero. Está verificado con el contraste, que salió
       MUDO al sacarlo — y el comentario que decía lo contrario estaba mal.
       -------------------------------------------------------------------- */
    overflow-y: auto;
}
.modal-cobro .modal-pie { flex-shrink: 0; }

/* --------------------------------------------------------------------------
   La fila de arriba de la ventana de cobro: el punto de venta a la izquierda
   y el switch de Factura/X a la derecha. Son dos elecciones cortas y en una
   sola fila ocupan lo que ocupaba una — cada renglón de acá arriba es alto
   que le sacan a la tabla de medios de pago.
   -------------------------------------------------------------------------- */
.fila-emision {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--e3);
    flex-wrap: wrap;
    margin-bottom: var(--e3);
}
/* El punto de venta va con el rótulo AL LADO y no arriba, por lo mismo: es UN
   dato y no un formulario, y una etiqueta arriba agrega un renglón. */
.pv-emision {
    display: flex;
    align-items: center;
    gap: var(--e2);
    flex-wrap: wrap;
}
.pv-emision label,
.pv-emision-rotulo {
    margin: 0;
    font-size: 0.85rem;
    font-weight: 600;
    color: var(--color-texto-suave);
}
/* Inputs dentro de tablas: sin bordes, se ven como celdas editables (estilo planilla) */
.tabla-datos input.celda {
    border: none;
    background: var(--color-celda-editable);
    padding: 2px var(--e1);
    font-size: 1.16rem;
    font-family: var(--fuente-mono);
    text-align: right;
    width: 100%;
    height: 28px;
    border-bottom: 1px dotted var(--color-borde);
}
.tabla-datos input.celda:hover { background: var(--color-superficie-alt); }
.tabla-datos input.celda:focus {
    outline: none;
    background: var(--color-campo-foco);
    border-bottom: 1px solid var(--color-primario);
}
.tabla-datos td.editable { padding: 0 var(--e2); }

/* Celda de resultado calculado (no editable) */
.tabla-datos td.calculado {
    background: var(--color-celda-calculada);
    font-weight: 700;
    font-family: var(--fuente-mono);
    text-align: right;
}

/* Selector lateral de presentaciones (master-detail) */
.selector-lateral {
    width: 170px;
    flex-shrink: 0;
    background: var(--color-superficie);
    border: 1px solid var(--color-borde);
    border-radius: var(--radio);
    overflow: hidden;
}
.selector-lateral .item {
    display: block;
    padding: var(--e2) var(--e3);
    text-decoration: none;
    color: var(--color-texto);
    border-bottom: 1px solid var(--color-borde);
    font-size: 1.04rem;
}
.selector-lateral .item:hover { background: var(--color-superficie-alt); }
.selector-lateral .item.activo {
    background: var(--color-primario-suave);
    color: var(--color-primario);
    font-weight: 700;
    border-left: 3px solid var(--color-primario);
}
.selector-lateral .item small { display: block; color: var(--color-texto-tenue); font-size: 0.88rem; }

.detalle-lado {
    display: flex;
    gap: var(--e3);
    align-items: flex-start;
}
.detalle-lado > .cuerpo { flex: 1; min-width: 0; }

/* Ficha de solo lectura compacta: pares etiqueta/valor en grilla */
.ficha-datos {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
    gap: var(--e2) var(--e3);
}
.ficha-datos .dato small {
    display: block;
    font-size: 0.94rem;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--color-texto-tenue);
    line-height: 1.2;
}
.ficha-datos .dato strong { font-size: 1.2rem; font-weight: 600; }

/* ===========================================================
   LOS PAPELES DEL REPARTO EN LA FICHA DE LA VENTA (183)
   ===========================================================
   Una factura puede estar en más de una hoja de carga —reimprimirla es legítimo,
   ver la 170— así que la celda tiene que poder llevar varios badges y bajarlos de
   renglón en vez de estirar su columna de la grilla.

   Y el `font-size` se repone: `.ficha-datos .dato strong` los dibuja a 1.2rem,
   que arriba de un badge queda enorme al lado de los otros datos. */
.insignias-reparto {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
    font-size: 1rem;
    font-weight: 400;
}
/* El que ENLAZA se tiene que ver enlazable: el badge hereda el color de su estado
   y sin esto no hay ninguna señal de que se pueda tocar. */
.insignias-reparto a { text-decoration: none; }
.insignias-reparto a:hover { text-decoration: underline; }

/* ===========================================================
   FICHA CON DOS MODOS (articulos/ver.php)

   Los mismos campos se ven y se editan; lo único que cambia es si están
   habilitados. Por eso la diferencia visual no puede ser sutil: un formulario
   abierto y uno cerrado se parecen demasiado, y sin una señal grande se termina
   escribiendo en una pantalla que no guarda nada.
   =========================================================== */

.cabecera-articulo {
    padding: var(--e3);
    /* El cambio de modo es instantáneo (no recarga), así que la transición es
       lo que hace que se lea como "esto se abrió" y no como un parpadeo. */
    transition: background-color 0.12s ease, border-color 0.12s ease, box-shadow 0.12s ease;
}

.cabecera-articulo.editando {
    background: var(--color-primario-suave);
    border-color: var(--color-primario);
    box-shadow: inset 3px 0 0 var(--color-primario);
}

/* El formulario de la cabecera vive DENTRO del panel: sin esto son dos cajas,
   una adentro de la otra, con dos bordes y dos fondos. */
.form-cabecera {
    background: transparent;
    border: none;
    border-radius: 0;
    padding: 0;
    flex: 1;
    min-width: 0;
}

/* Los campos de la cabecera se pueden seleccionar y copiar aunque estén en modo
   lectura: el `* { user-select: none }` global —que existe para que arrastrar el
   mouse por una grilla densa no pinte media pantalla— dejaba sin copiar
   justamente el código de fábrica, que se copia todos los días para buscarlo en
   la lista del proveedor. Va acotado a esta cabecera y no al sistema entero. */
.form-cabecera input { user-select: text; }

/* Un select o una casilla en modo lectura tienen que verse como el
   `input[readonly]` de al lado, o la fila queda con tres estilos distintos
   diciendo todos lo mismo. */
.form-cabecera select:disabled {
    background: var(--color-superficie-alt);
    color: var(--color-texto-suave);
    border-color: transparent;
    /* Sin esto, Safari y Chrome le bajan la opacidad y el valor deja de leerse:
       el dato está en modo lectura, no apagado. */
    opacity: 1;
    -webkit-text-fill-color: var(--color-texto-suave);
}
.form-cabecera input[type="checkbox"]:disabled + label { color: var(--color-texto-suave); cursor: default; }

/* Imagen del artículo: la misma caja con foto y sin foto, para que el bloque no
   cambie de alto al subir la primera y el resto de la cabecera no salte. */
.cabecera-imagen { width: 170px; flex-shrink: 0; text-align: center; }

/* El cuadrado de la ficha del artículo.
   Pasó de ser el `<img>` a ser SU CONTENEDOR: adentro van la imagen, la
   leyenda de "sin imagen" y la caja del error, y las tres tienen que poder
   ocupar el mismo lugar. Sigue midiendo lo que medía. */
.imagen-articulo {
    position: relative;
    width: 100%;
    height: 120px;
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
    background: var(--color-superficie);
    border: 1px solid var(--color-borde);
    border-radius: var(--radio-chico);
}
.imagen-articulo img {
    width: 100%;
    height: 100%;
    /* `contain` y no `cover`: en un catálogo la foto suele venir con fondo
       blanco y recortarla le come el producto. */
    object-fit: contain;
    display: block;
}
.imagen-articulo img[hidden] { display: none; }
.imagen-articulo.vacio {
    background: var(--color-superficie-alt);
    border-style: dashed;
    color: var(--color-texto-tenue);
    font-size: 0.8rem;
}
.imagen-articulo [data-sin-foto][hidden] { display: none; }


@media (max-width: 700px) {
    .detalle-lado { flex-direction: column; }
    .selector-lateral { width: 100%; }
    .form-erp .c, .form-erp .c.crece { flex: 1 1 100%; }
    .a-mini, .a-corto, .a-medio, .a-largo { width: 100% !important; }
}

/* ===========================================================
   GRILLA TIPO PLANILLA (facturas con borrador)

   Se carga con el teclado, celda por celda. No es el carrito del POS —que
   navega por FILA y tiene sus F1–F10— y no comparte nada con él.
   =========================================================== */

/* La grilla es alta a propósito: se entra a esta pantalla a cargar cuarenta
   renglones, y una ventana de seis filas obliga a scrollear todo el tiempo. El
   pie con los totales queda pegado abajo (el `tfoot` sticky de la grilla), así
   que el total se ve siempre sin ir a buscarlo. */
.grilla-caja { --alto-grilla: max(24rem, calc(100vh - 26rem)); }

.grilla-planilla td { vertical-align: middle; }

/* EL FOCO TIENE QUE VERSE. En una planilla, "dónde estoy parado" es la mitad de
   la información: sin un borde marcado, moverse con las flechas es a ciegas. */
.grilla-planilla input.celda:focus {
    outline: 2px solid var(--color-primario);
    outline-offset: -2px;
    background: var(--color-campo-foco);
}

/* La fila que se acaba de cargar. Importa con el lector: se pasan diez seguidos
   y la única confirmación de que entró es verla aparecer. */
.grilla-planilla tr.recien-cargada td { animation: grilla-destello 1.2s ease-out; }
@keyframes grilla-destello {
    from { background: var(--color-exito-fondo); }
    to   { background: transparent; }
}

/* La factura que LLEGÓ SOLA al listado de borradores (salón → oficina).
   NO es un destello como el de arriba, y la diferencia no es estética: esta
   fila aparece cuando nadie está mirando la pantalla —de eso se trata—, así que
   una animación de un segundo se pierde. El fondo se queda hasta que alguien
   recargue, que es cuando ya la vio. */
/* La fila que acaba de llegar sola al listado de borradores.
 *
 * SE DESVANECE en unos tres segundos en vez de quedarse pintada. Con el color
 * fijo, después de media mañana medio listado está en verde y el resaltado deja
 * de significar "esto es nuevo" — que es lo único para lo que existe. Y va como
 * animación y no como transición porque no hay dos estados entre los que pasar:
 * la fila nace resaltada.
 *
 * `background-color` y no `background`: la fila ya tiene el rayado de la tabla y
 * el hover, y pisar la propiedad entera se los llevaría puestos. */
@keyframes fila-nueva-se-apaga {
    0%   { background-color: var(--color-exito-fondo); }
    60%  { background-color: var(--color-exito-fondo); }
    100% { background-color: transparent; }
}
tr.fila-nueva td { animation: fila-nueva-se-apaga 3s ease-out forwards; }

@media (prefers-reduced-motion: reduce) {
    tr.fila-nueva td { animation: none; background-color: var(--color-exito-fondo); }
}

/* Un renglón que pide más de lo que hay. Se avisa, no se bloquea: el faltante
   se resuelve cargando la compra, y el número exacto lo da el servidor arriba. */
.grilla-planilla tr.sin-stock td { background: var(--color-error-fondo); }

/* Un renglón sin precio conocido (una orden de compra a un proveedor al que
   nunca se le compró ese artículo). Se carga igual —el precio de verdad se
   conoce al recibir la factura— pero tiene que verse: un $0 que pasa
   desapercibido se le manda al proveedor. En alerta y no en error, porque no
   está mal: falta un dato. */
.grilla-planilla tr.sin-precio td { background: var(--color-alerta-fondo); }

/* EL PRECIO QUE NO ES EL DE LISTA.
   Casi siempre es un pedido del salón: el vendedor le pasó un precio al cliente
   a la mañana y la lista se movió antes de que la oficina lo facture. Se cobra
   el que se le dijo —eso lo decide `PosMovilService`— y esto es para que quien
   factura lo vea.

   SE MARCA LA CELDA Y NO LA FILA, al revés que las dos de arriba: ahí lo que
   pasa es del renglón entero (no hay stock, no hay precio) y acá está distinto
   UN dato concreto, así que pintarlo puntualmente dice cuál mirar. Y de paso no
   se pelea por el fondo con `sin-stock`, que puede tocarle a la misma fila.

   El borde de abajo es lo que se ve con la celda enfocada, donde el fondo lo
   pisa `.celda:focus`: sin él, la marca desaparece justo mientras alguien la
   está corrigiendo. */
.grilla-planilla tr.precio-pactado [data-col="precio"] {
    background: var(--color-alerta-fondo);
    border-bottom: 2px solid var(--color-alerta);
}

/* CELDAS QUE NO LLEVAN NÚMEROS: el lote es una cadena y la presentación un
   desplegable. `input.celda` viene alineado a la derecha y en monoespaciada,
   que es lo correcto para una columna de importes y lo contrario para un
   nombre. Se corrige acá y no en `.celda` para no tocar las tres pantallas que
   ya andaban. */
.tabla-datos .celda-texto {
    text-align: left;
    font-family: var(--fuente-base);
}
.tabla-datos select.celda {
    border: none;
    background: var(--color-celda-editable);
    padding: 2px var(--e1);
    font-size: 1.09rem;
    width: 100%;
    height: 28px;
    border-bottom: 1px dotted var(--color-borde);
}
.tabla-datos select.celda:focus {
    outline: none;
    background: var(--color-campo-foco);
    border-bottom: 1px solid var(--color-primario);
}

/* UNA COLUMNA QUE NO APLICA A ESTA FILA (el lote de un artículo que no maneja
   lotes). Va en gris y centrada para que se lea como "acá no va nada" y no como
   un dato que falta cargar — que es lo que sí dice el ámbar de abajo. */
.grilla-planilla td.num-no { text-align: center; }

/* LO QUE LEYÓ LA IA, hasta que alguien lo toque.
   Un lote mal leído deja el stock trazado con un lote que no existe, así que la
   celda tiene que distinguirse de la que tipeó una persona. El ámbar se va al
   editarla: tocar la celda ES verificarla. */
.tabla-datos .leido-ia {
    background: var(--color-alerta-fondo);
    box-shadow: inset 0 0 0 1px var(--color-alerta);
}
.tabla-datos .leido-ia:focus { box-shadow: inset 0 0 0 1px var(--color-primario); }

/* LA FILA EN BLANCO ES UNA FILA MÁS, y por eso mide lo mismo que las de datos.
   El buscador que lleva adentro es el mismo componente que en el POS, donde la
   caja con borde y el campo de 1,36rem son lo correcto —ahí ocupa el ancho de la
   pantalla y es lo primero que se mira—; acá abajo de una planilla, esa caja
   metía una fila del doble de alto y con otro fondo, que se lee como si la tabla
   se hubiera terminado. Se le saca la caja y el campo pasa a medir lo que mide
   una celda editable. */
.grilla-planilla tr[data-fila-blanca] td { background: var(--color-superficie-alt); }
.grilla-planilla .celda-buscador { padding: 0 var(--e2); }
.grilla-planilla .celda-buscador .pos-buscador {
    margin: 0;
    padding: 0;
    border: none;
    border-radius: 0;
    background: transparent;
}
.grilla-planilla .celda-buscador input {
    width: 100%;
    height: 28px;
    padding: 2px var(--e1);
    font-size: 1.16rem;
    font-family: var(--fuente-base);
    text-align: left;
    border: none;
    /* Punteada y en el color primario: es el único renglón donde se escribe
       texto, y tiene que distinguirse de una celda numérica sin gritar. */
    border-bottom: 1px dashed var(--color-primario);
    border-radius: 0;
    background: var(--color-celda-editable);
}
.grilla-planilla .celda-buscador input:focus {
    outline: 2px solid var(--color-primario);
    outline-offset: -2px;
    background: var(--color-campo-foco);
}

/* El pie de totales queda pegado abajo, así que la fila en blanco tiene que
   dejarle lugar: sin esto, al enfocarla el navegador la mete justo debajo del
   pie y se escribe a ciegas. */
.grilla-planilla tr[data-fila-blanca] { scroll-margin-bottom: 8rem; }

/**
 * EN PANTALLA CHICA LA GRILLA NO SE CONVIERTE EN TARJETAS, Y ES A PROPÓSITO.
 *
 * Esta pantalla existe para cargar cuarenta renglones CON EL TECLADO, y un
 * teléfono no tiene ese flujo: no hay Tab, no hay flechas y no hay lector.
 * Convertir cada fila en una tarjeta rompería lo único que hace legible una
 * planilla —que la columna de precios esté siempre en el mismo lugar— y encima
 * daría la impresión de que ahí se puede cargar cómodo.
 *
 * Entonces se hace lo honesto: la tabla se scrollea a lo ancho, con la
 * descripción fija a la izquierda para no perder de vista qué renglón se está
 * mirando. En el teléfono se LEE y se confirma un borrador; se carga desde una
 * computadora, y la pantalla lo dice.
 */
@media (max-width: 900px) {
    .grilla-planilla { min-width: 760px; }
    .grilla-planilla td:nth-child(2),
    .grilla-planilla th:nth-child(2) {
        position: sticky;
        left: 0;
        background: var(--color-superficie);
        box-shadow: 1px 0 0 var(--color-borde);
    }
    .grilla-planilla thead th:nth-child(2) { background: var(--color-superficie-alt); }
}

/* ===========================================================
   POS / PUNTO DE VENTA
   Pantalla de trabajo intensivo: teclado primero, todo a la vista.
   =========================================================== */

.pos-layout {
    display: flex;
    gap: var(--e3);
    align-items: flex-start;
}
.pos-izquierda { flex: 1; min-width: 0; }
.pos-derecha { width: 330px; flex-shrink: 0; }

.pos-buscador {
    background: var(--color-superficie);
    border: 1px solid var(--color-borde);
    border-radius: var(--radio);
    padding: var(--e2);
    margin-bottom: var(--e2);
    position: relative;
}
.pos-buscador input {
    width: 100%;
    padding: var(--e3);
    font-size: 1.36rem;
    border: 2px solid var(--color-primario);
    border-radius: var(--radio-chico);
    font-family: var(--fuente-base);
}
.pos-buscador input:focus { outline: none; background: var(--color-campo-foco); }

.pos-resultados {
    position: absolute;
    top: 100%;
    left: var(--e2);
    right: var(--e2);
    background: var(--color-superficie);
    border: 1px solid var(--color-borde-fuerte);
    border-radius: var(--radio-chico);
    box-shadow: 0 6px 18px rgba(var(--sombra-rgb),0.15);
    max-height: 320px;
    overflow-y: auto;
    z-index: 60;
    display: none;
}
.pos-resultados.visible { display: block; }
.pos-resultado {
    padding: var(--e2) var(--e3);
    border-bottom: 1px solid var(--color-borde);
    cursor: pointer;
    display: flex;
    justify-content: space-between;
    gap: var(--e2);
    font-size: 1.18rem;
}
.pos-resultado:hover, .pos-resultado.seleccionado { background: var(--color-primario-suave); }
.pos-resultado .desc { flex: 1; min-width: 0; }

/**
 * LA LISTA COMPACTA: la que se despliega desde la fila en blanco de una grilla.
 *
 * Dos diferencias con la del POS, y las dos salen de dónde aparece. En el
 * mostrador la lista se abre debajo de un campo que ocupa la pantalla y se mira
 * de lejos; acá se abre encima de una planilla que se está cargando, así que
 * cada renglón tiene que medir lo que mide una fila de la grilla —si no, tapa
 * cinco filas para mostrar tres resultados— y va en UNA línea.
 *
 * `position: fixed` (la coloca el JavaScript del componente) porque la celda
 * vive dentro de `.tabla-contenedor`, que tiene `overflow: auto` para poder
 * congelar el `thead`: una lista `absolute` ahí adentro queda recortada, y
 * justo para abajo, que es donde se despliega.
 */
.pos-resultados.compacto {
    position: fixed;
    left: 0;
    right: auto;
    max-height: 260px;
    font-size: 1.02rem;
}
.pos-resultados.compacto .pos-resultado {
    padding: 3px var(--e2);
    font-size: inherit;
    align-items: baseline;
    white-space: nowrap;
}
.pos-resultados.compacto .pos-resultado .desc {
    overflow: hidden;
    text-overflow: ellipsis;
}
.pos-resultados.compacto .pos-resultado .cod { font-size: 0.86rem; }
.pos-resultado .cod { font-family: var(--fuente-mono); font-size: 0.94rem; color: var(--color-texto-tenue); }
.pos-resultado .precio { font-family: var(--fuente-mono); font-weight: 700; white-space: nowrap; }
.pos-resultado .stock-ok { color: var(--color-exito); font-size: 0.92rem; }
.pos-resultado .stock-no { color: var(--color-error); font-size: 0.92rem; font-weight: 700; }

.pos-tabla { width: 100%; border-collapse: collapse; background: var(--color-superficie); font-size: 1.18rem; }
.pos-tabla thead th {
    background: var(--color-superficie-alt);
    padding: var(--e2);
    text-align: left;
    font-size: 0.99rem;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--color-texto-suave);
    border-bottom: 1px solid var(--color-borde);
    /* En cero, por lo mismo que `.tabla-datos th`: el carrito vive adentro de
       un `.tabla-contenedor`, y ese div es el scrollport contra el que se
       resuelve el sticky. Un `top` distinto de cero no lo pega más arriba, lo
       corre hacia abajo encima de las primeras líneas de la venta. */
    position: sticky; top: 0; z-index: 10;
}
.pos-tabla td { padding: var(--e1) var(--e2); border-bottom: 1px solid var(--color-borde); height: 34px; }
.pos-tabla tbody tr:hover { background: var(--color-superficie-alt); }
.pos-tabla input {
    border: none;
    border-bottom: 1px dotted var(--color-borde);
    background: transparent;
    text-align: right;
    font-family: var(--fuente-mono);
    font-size: 1.16rem;
    padding: 3px;
    width: 100%;
}
.pos-tabla input:focus { outline: none; background: var(--color-campo-foco); border-bottom-color: var(--color-primario); }

.pos-totales {
    background: var(--color-superficie);
    border: 1px solid var(--color-borde);
    border-radius: var(--radio);
    padding: var(--e3);
}
.pos-totales .linea {
    display: flex;
    justify-content: space-between;
    padding: 4px 0;
    font-size: 1.2rem;
}
.pos-totales .linea.total {
    border-top: 2px solid var(--color-borde-fuerte);
    margin-top: var(--e2);
    padding-top: var(--e2);
    font-size: 1.77rem;
    font-weight: 700;
    color: var(--color-primario);
}
.pos-totales .valor { font-family: var(--fuente-mono); }

.pos-pagos .fila-pago {
    display: flex;
    gap: 4px;
    align-items: center;
    margin-bottom: 4px;
}
.pos-pagos .fila-pago select { flex: 1; min-width: 0; }
.pos-pagos .fila-pago input { width: 90px; text-align: right; font-family: var(--fuente-mono); }

/* Estado del cobro: se actualiza con cada tecla del monto. El vuelto va grande
   porque es el número que el cajero tiene que leer de un vistazo. */
.pos-estado-pago {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: var(--e2);
    padding: 4px 0;
    font-weight: 700;
    font-size: 1.2rem;
}
.pos-estado-pago .valor { font-family: var(--fuente-mono); }
.pos-estado-pago.falta  { color: var(--color-error); }
.pos-estado-pago.justo  { color: var(--color-exito); }
.pos-estado-pago.vuelto { color: var(--color-exito); }
.pos-estado-pago.vuelto .valor { font-size: 2.2rem; line-height: 1.1; }
.pos-estado-nota {
    font-size: 0.82rem;
    color: var(--color-texto-suave);
    padding-bottom: 2px;
}

.pos-atajos {
    font-size: 0.92rem;
    color: var(--color-texto-tenue);
    display: flex;
    gap: var(--e3);
    flex-wrap: wrap;
    padding: var(--e2) 0;
}
.pos-atajos kbd {
    background: var(--color-superficie-alt);
    border: 1px solid var(--color-borde);
    border-bottom-width: 2px;
    border-radius: 3px;
    padding: 1px 5px;
    font-family: var(--fuente-mono);
    font-size: 0.88rem;
}

/* ==========================================================================
   CONSULTOR DE PRECIOS (Ctrl+Espacio)

   Tiene que ser IMPOSIBLE de confundir con la venta. Por eso no es un modal
   más —blanco, chico, centrado— sino una pantalla completa de color fuerte:
   el resto del sistema es gris y azul apagado, así que este violeta no se
   parece a ninguna otra pantalla y se reconoce de un vistazo desde el otro
   lado del mostrador.

   Y se lee de lejos: el precio va en `clamp()` contra el ancho de la ventana,
   así que en el monitor de la caja sale grande y no depende de que alguien
   haya elegido bien un tamaño en píxeles.

   Va acá y no en un <style> de la pantalla porque son estilos de PANTALLA,
   como el resto del POS. Los bloques que viven aparte son los de PAPEL —el
   ticket de 80mm, el recibo, las etiquetas—, que se miden en milímetros.

   ==========================================================================
   ESTE BLOQUE NO ENTRA AL SISTEMA DE TEMA, Y NO ES QUE FALTE MIGRARLO
   ==========================================================================
   Sus colores están escritos a mano a propósito y así se quedan. El párrafo
   de arriba es el motivo entero: esta pantalla existe para NO parecerse a
   ninguna otra, y un color que sigue a la marca de la empresa se parece por
   definición a todas. Un comercio que elija violeta tendría un consultor de
   precios idéntico al resto del sistema — o sea que tematizarlo lo ROMPE.

   Es la misma categoría que `.espera`, el aviso flotante y `core/Corte.php`:
   superficies cuyo color ES su significado y que ya se ven igual en los dos
   temas. Lo único de acá adentro que sí es del tema es el fondo del campo
   enfocado, que ya usa `--color-campo-foco`.

   Está declarado como excepción en `dev/test_tema.php` (el barrido de la
   capa 3 cuenta estos literales y los espera), y el porqué está también en
   la memoria del proyecto. Si alguien viene a "terminar la migración": NO.
   ========================================================================== */
.pos-consulta {
    position: fixed;
    inset: 0;
    /* Entre medio a propósito: por encima del modal de cliente (100) y del
       menú desplegado (120), pero POR DEBAJO de los diálogos del sistema
       (.dlg-fondo, 200). Un diálogo que quede tapado es un diálogo que el
       usuario no puede contestar. */
    z-index: 150;
    display: none;
    align-items: center;
    justify-content: center;
    padding: var(--e4);
    background: #3b1d6e;
    color: #ffffff;
}
.pos-consulta-caja {
    width: 100%;
    max-width: 900px;
    max-height: 100%;
    display: flex;
    flex-direction: column;
    gap: var(--e4);
}

/* El cartel que dice que esto NO es una venta. Va arriba de todo y con el
   color de aviso, no con el del fondo: tiene que despegarse. */
.pos-consulta-cinta {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: var(--e2);
    background: #ffd54a;
    color: #2b1650;
    border-radius: var(--radio);
    padding: var(--e2) var(--e3);
    font-weight: 800;
    font-size: 1.3rem;
    letter-spacing: 0.04em;
}
.pos-consulta-aclaracion {
    font-weight: 600;
    font-size: 0.95rem;
    letter-spacing: 0;
    text-transform: none;
}

.pos-consulta-entrada label {
    display: block;
    font-size: 1rem;
    font-weight: 600;
    margin-bottom: var(--e1);
    opacity: 0.85;
}
.pos-consulta-entrada input {
    width: 100%;
    font-size: 1.6rem;
    font-family: var(--fuente-mono);
    padding: var(--e2) var(--e3);
    border: 3px solid #ffd54a;
    border-radius: var(--radio);
    background: #ffffff;
    color: var(--color-texto);
}
.pos-consulta-entrada input:focus { outline: 3px solid #ffffff; outline-offset: 1px; }

.pos-consulta-resultado {
    background: rgba(255, 255, 255, 0.08);
    border: 1px solid rgba(255, 255, 255, 0.25);
    border-radius: var(--radio);
    padding: var(--e4);
    min-height: 220px;          /* alto fijo: el panel no salta entre escaneos */
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: var(--e2);
    text-align: center;
}
.pos-consulta-espera {
    margin: 0;
    font-size: 1.3rem;
    opacity: 0.6;
}
.pos-consulta-descripcion {
    font-size: clamp(1.4rem, 3.2vw, 2.4rem);
    font-weight: 700;
    line-height: 1.15;
}
.pos-consulta-precio {
    font-size: clamp(3rem, 9vw, 6rem);
    font-weight: 800;
    line-height: 1;
    font-family: var(--fuente-mono);
}
.pos-consulta-detalle {
    font-size: 1.1rem;
    opacity: 0.75;
}
.pos-consulta-stock {
    font-size: 1.5rem;
    font-weight: 700;
}
.pos-consulta-stock.hay { color: #7ce6a0; }
.pos-consulta-stock.no  { color: #ffd54a; }
.pos-consulta-resultado.con-error { background: rgba(0, 0, 0, 0.25); }

/* La aclaración de abajo del precio. Hoy la usa la etiqueta de balanza, para
   que el número grande se pueda verificar: "1,235 kg × $8.000 por kg". */
.pos-consulta-nota {
    font-size: 1.15rem;
    font-weight: 600;
    color: #ffd54a;
}

/* --------------------------------------------------------------------------
   La lista de parecidos (búsqueda por nombre o por código interno)

   SCROLLEA ELLA Y NO LA PANTALLA. El consultor es `position: fixed` y ocupa la
   ventana entera: si la caja creciera con la lista, el pie —donde dice que Esc
   vuelve a la venta— se iría abajo del borde y NO habría cómo alcanzarlo. Lo
   que lo evita es el `max-height: 100%` de la caja: medido, en una ventana de
   560px de alto el pie termina 10px afuera sin esa línea.

   Y la que se achica para que eso alcance es ESTA, porque el `overflow-y` la
   deja: el mínimo automático de un ítem flex es su contenido, salvo cuando el
   ítem tiene overflow propio — que es justo el caso. Un `min-height: 0` acá
   sería una línea que no puede hacer nada (verificado: da lo mismo con y sin).

   Escaneando no se ve nunca: el código de barras exacto devuelve UN artículo.
   -------------------------------------------------------------------------- */
.pos-consulta-opciones {
    max-height: 32vh;
    overflow-y: auto;
    background: rgba(255, 255, 255, 0.08);
    border: 1px solid rgba(255, 255, 255, 0.25);
    border-radius: var(--radio);
}
.pos-consulta-opcion {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--e3);
    padding: var(--e2) var(--e3);
    border-bottom: 1px solid rgba(255, 255, 255, 0.15);
    cursor: pointer;
}
.pos-consulta-opcion:last-child { border-bottom: 0; }

/* La marcada va con el amarillo de la cinta: es el color de acento de esta
   pantalla, y es lo único que dice a qué producto corresponde el precio grande
   de arriba. */
.pos-consulta-opcion.marcada {
    background: #ffd54a;
    color: #2b1650;
}
.pos-consulta-opcion-que { min-width: 0; }
.pos-consulta-opcion-nombre {
    font-size: 1.25rem;
    font-weight: 700;
    line-height: 1.2;
}
.pos-consulta-opcion-detalle {
    font-size: 0.95rem;
    opacity: 0.75;
}
.pos-consulta-opcion-precio {
    font-size: 1.5rem;
    font-weight: 800;
    font-family: var(--fuente-mono);
    white-space: nowrap;
}

.pos-consulta-pie {
    display: flex;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: var(--e3);
    font-size: 1rem;
    opacity: 0.85;
}
.pos-consulta-pie kbd {
    background: rgba(255, 255, 255, 0.18);
    border: 1px solid rgba(255, 255, 255, 0.4);
    border-radius: 3px;
    padding: 1px 6px;
    font-family: var(--fuente-mono);
    margin-right: 3px;
}
.pos-consulta-lista { font-weight: 600; }

.pos-cliente {
    background: var(--color-superficie);
    border: 1px solid var(--color-borde);
    border-radius: var(--radio);
    padding: var(--e2) var(--e3);
    margin-bottom: var(--e2);
    font-size: 1.06rem;
}
.pos-cliente .nombre { font-weight: 700; }
.pos-cliente .datos { font-size: 0.94rem; color: var(--color-texto-suave); }
/* Lista de precios al lado del nombre: en Consumidor Final queda vacío */
.pos-cliente .lista { font-weight: 400; font-size: 0.9rem; color: var(--color-texto-suave); }

/* Descuento general en una sola línea: en el POS cada píxel de alto cuenta */
.pos-descuento {
    background: var(--color-superficie);
    border: 1px solid var(--color-borde);
    border-radius: var(--radio);
    padding: var(--e2) var(--e3);
    margin-bottom: var(--e2);
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--e2);
}
.pos-descuento label { font-size: 0.9rem; color: var(--color-texto-suave); margin: 0; }
.pos-descuento input {
    width: 70px;
    height: 28px;
    padding: 2px var(--e2);
    border: 1px solid var(--color-borde);
    border-radius: var(--radio-chico);
    background: var(--color-superficie);
    font-size: 1.06rem;
    font-family: var(--fuente-mono);
    text-align: right;
}
.pos-descuento input:focus {
    outline: none;
    border-color: var(--color-primario);
    background: var(--color-campo-foco);
}

@media (max-width: 900px) {
    .pos-layout { flex-direction: column; }
    .pos-derecha { width: 100%; }
}

/* ===========================================================
   DIÁLOGOS DEL SISTEMA (reemplazan alert/confirm del navegador)
   =========================================================== */

.dlg-fondo {
    position: fixed;
    inset: 0;
    background: rgba(var(--scrim-rgb), 0.5);
    display: flex;
    align-items: flex-start;
    justify-content: center;
    /* El relleno lateral despega la caja del borde en una pantalla angosta: sin
       él, el diálogo del cobro —que llega a 700px— queda pegado a los dos lados. */
    padding: 12vh var(--e3) 0;
    z-index: 200;
    opacity: 0;
    transition: opacity 0.12s ease;
}
.dlg-fondo.visible { opacity: 1; }

.dlg-caja {
    background: var(--color-superficie);
    border-radius: var(--radio);
    width: 100%;
    max-width: 400px;
    box-shadow: 0 10px 32px rgba(var(--sombra-rgb),0.25);
    transform: translateY(-8px);
    transition: transform 0.12s ease;
    overflow: hidden;
}
.dlg-fondo.visible .dlg-caja { transform: translateY(0); }

.dlg-encabezado {
    display: flex;
    align-items: center;
    gap: var(--e2);
    padding: var(--e3) var(--e4);
    border-bottom: 1px solid var(--color-borde);
}
.dlg-icono {
    width: 30px; height: 30px;
    border-radius: 50%;
    display: flex; align-items: center; justify-content: center;
    font-weight: 700; font-size: 17.7px;
    flex-shrink: 0;
}
.dlg-icono.aviso   { background: var(--color-alerta-fondo); color: var(--color-alerta); }
.dlg-icono.error   { background: var(--color-error-fondo);  color: var(--color-error); }
.dlg-icono.exito   { background: var(--color-exito-fondo);  color: var(--color-exito); }
.dlg-icono.pregunta{ background: var(--color-primario-suave); color: var(--color-primario); }

.dlg-titulo { font-size: 1.16rem; font-weight: 700; margin: 0; }

.dlg-cuerpo {
    padding: var(--e4);
    font-size: 1.12rem;
    line-height: 1.5;
}
/**
 * EL `pre-line` VA EN LOS DOS QUE RECIBEN TEXTO, NO EN EL CUERPO.
 *
 * El mensaje y el detalle llegan con `\n` adentro y hay que respetarlos. Puesto
 * en `.dlg-cuerpo`, esa regla alcanza además a los saltos de línea del MARCADO
 * —los que separan a `#dlg-mensaje` de `.dlg-montos` y de `.detalle` en el
 * partial— y cada uno se dibuja como un renglón vacío. Con la letra chica se
 * ve como un poco de aire de más; con la del cierre de venta son 30px por
 * salto, o sea un hueco entre el título y la primera cifra que no explica nada.
 */
#dlg-mensaje,
.dlg-cuerpo .detalle { white-space: pre-line; }

.dlg-cuerpo .detalle {
    margin-top: var(--e2);
    padding: var(--e2) var(--e3);
    background: var(--color-superficie-alt);
    border-left: 3px solid var(--color-borde-fuerte);
    border-radius: var(--radio-chico);
    font-size: 1.04rem;
    color: var(--color-texto-suave);
}

/**
 * -----------------------------------------------------------------
 * LAS TRES FILAS DE IMPORTES DEL CIERRE DE VENTA (.dlg-montos)
 * -----------------------------------------------------------------
 * Total arriba, Recibido debajo y Vuelto al final: es el orden en que se hace la
 * cuenta en el mostrador, así que es el orden en que se lee. Cada fila es un
 * rótulo chico y un número grande —2rem contra 3rem—, porque lo que hay que leer
 * de lejos es el número: el rótulo sólo dice de cuál se trata.
 *
 * El número va alineado a la DERECHA y con `tabular-nums`, que es lo que hace
 * que las tres cifras queden una abajo de la otra con las comas en la misma
 * columna. Sin eso, tres importes de distinto largo se leen como una lista y no
 * como una cuenta.
 *
 * El VUELTO lleva `destacado`: línea arriba y color, como el total de un pie de
 * factura. Es la conclusión —lo que hay que devolverle al cliente— y sin la
 * línea los tres renglones se ven iguales y hay que leer los rótulos para saber
 * cuál es cuál.
 */
.dlg-montos {
    display: flex;
    flex-direction: column;
    gap: var(--e2);
}
/* El faltante de stock viene abajo de la cuenta: sin este aire queda pegado al
   vuelto y se lee como un cuarto renglón de la cuenta. */
.dlg-montos + .detalle { margin-top: var(--e4); }
.dlg-monto {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--e4);
}
.dlg-monto-rotulo {
    font-size: 2rem;
    color: var(--color-texto-suave);
    white-space: nowrap;
}
.dlg-monto-valor {
    font-size: 3rem;
    font-weight: 700;
    line-height: 1.15;
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}
.dlg-monto.destacado {
    border-top: 1px solid var(--color-borde-fuerte);
    margin-top: var(--e2);
    padding-top: var(--e2);
}
.dlg-monto.destacado .dlg-monto-rotulo,
.dlg-monto.destacado .dlg-monto-valor { color: var(--color-primario); }

/**
 * En una pantalla angosta los tres renglones no entran a este cuerpo de letra
 * —"Total:" mide 85px y un importe de siete cifras 290—, y lo que pasaría es que
 * el número se va abajo del rótulo: la cuenta deja de leerse como una cuenta.
 * Se achica lo justo para que la fila siga siendo una fila.
 */
@media (max-width: 620px) {
    .dlg-monto-rotulo { font-size: 1.5rem; }
    .dlg-monto-valor  { font-size: 2.2rem; }
}

.dlg-pie {
    padding: var(--e3) var(--e4);
    border-top: 1px solid var(--color-borde);
    display: flex;
    justify-content: flex-end;
    gap: var(--e2);
    background: var(--color-superficie-alt);
}

/**
 * -----------------------------------------------------------------
 * EL DIÁLOGO DEL CIERRE DE VENTA SE LEE DE PIE Y DE LEJOS (.grande)
 * -----------------------------------------------------------------
 * Lo pide `grande: true` y hoy lo usan los dos diálogos del cobro del POS: el
 * vuelto y el "falta cubrir". Ahí el cajero está parado, con el cliente
 * enfrente y contando plata, así que el número tiene que leerse de un vistazo
 * y sin acercarse a la pantalla. El resto del sistema se lee sentado y a medio
 * metro, y por eso esto NO es el default: a este cuerpo de letra, un aviso de
 * dos renglones ocuparía media pantalla en cualquier otra pantalla.
 *
 * Los importes van en `.dlg-montos`, que tiene su propio bloque más abajo: acá
 * lo que se agranda es el marco —título, detalle, botones— y el ancho, que a
 * 700px es lo que hace que las tres filas de la cuenta entren cada una en un
 * renglón, con el rótulo a la izquierda y el número a la derecha.
 *
 * EL CUERPO LLEVA SU PROPIO SCROLL, y no es decorativo: con varios artículos
 * sin stock el detalle crece, y a esta letra la caja se iría abajo del borde
 * de la pantalla. El diálogo es `fixed`, así que lo que se sale NO se alcanza
 * scrolleando la página: quedaría un botón "Confirmar venta" invisible.
 */
.dlg-caja.grande { max-width: 700px; }
.dlg-caja.grande .dlg-icono { width: 40px; height: 40px; font-size: 24px; }
.dlg-caja.grande .dlg-titulo { font-size: 1.55rem; }
.dlg-caja.grande .dlg-cuerpo {
    font-size: 1.75rem;
    line-height: 1.35;
    max-height: 55vh;
    overflow-y: auto;
}
.dlg-caja.grande .dlg-cuerpo .detalle {
    font-size: 1.3rem;
    padding: var(--e3);
}
.dlg-caja.grande .dlg-pie button {
    font-size: 1.35rem;
    padding: var(--e3) var(--e4);
}

/* Los botones uno debajo del otro y a lo ancho (`elegirDialogo(…, {apilado:
   true})`): tres salidas en un teléfono no entran en un renglón. Van en el
   orden en que se pasan; el que no tiene vuelta, al final. */
.dlg-caja.apilado .dlg-pie {
    flex-direction: column;
    align-items: stretch;
}
.dlg-caja.apilado .dlg-pie button { width: 100%; }

/* ===========================================================
   MENÚ COMPACTO CON DESPLIEGUE LATERAL
   Solo se ven las secciones; al pasar el mouse aparecen sus
   opciones a la derecha, sin empujar el contenido.
   =========================================================== */

/**
 * El menú también queda fijo al hacer scroll.
 *
 * Tres cosas que no son decorativas:
 *
 *  - `align-self: flex-start`. `.layout-app` es un flex y por defecto estira a
 *    sus hijos a la altura del más alto: el menú mediría lo mismo que el
 *    contenido y no le quedaría ningún margen donde pegarse, así que el
 *    sticky no haría nada. Con esto mide lo que se le dice y puede pegarse.
 *  - `top` es la altura del header, no cero: arriba está la barra superior,
 *    que también quedó fija.
 *  - `overflow: visible` SE QUEDA. El panel de cada sección se dibuja por
 *    fuera del menú; con `overflow-y: auto` —lo que uno pondría para un menú
 *    más alto que la pantalla— el panel se cortaría al costado y el menú
 *    dejaría de funcionar. Si algún día no entran las secciones, la salida es
 *    achicarlas, no recortar el panel.
 */
.menu-lateral.compacto {
    width: 70px;
    padding: var(--e2) 0;
    overflow: visible;   /* imprescindible: el panel se dibuja por fuera */
    transition: width 0.12s ease;
    position: sticky;
    top: var(--altura-header);
    align-self: flex-start;
    height: calc(100vh - var(--altura-header));
    z-index: 50;
}

/* Cada sección es un contenedor con su panel oculto al costado */
.menu-lateral.compacto .grupo-menu {
    position: relative;
}

/* El botón visible de la sección */
.menu-lateral.compacto .cabecera-grupo {
    overflow: hidden;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 2px;
    padding: var(--e2) var(--e1);
    margin: 2px var(--e1);
    border-radius: var(--radio);
    cursor: default;
    color: var(--color-texto-suave);
    border-left: 3px solid transparent;
    transition: background 0.1s, color 0.1s;
}

/* EL ÍCONO DE LA SECCIÓN ES UN SVG DIBUJADO POR NOSOTROS.
 *
 * Eran emojis (⚙ 📦 🛒), o sea texto, y por eso esta regla decía `font-size`.
 * El problema del emoji no es estético: lo dibuja la fuente del SISTEMA
 * OPERATIVO, así que el mismo carácter mide distinto en Windows, en Android y
 * en iOS —y con distinto ancho— y la columna de 70px quedaba con el rótulo
 * corrido para arriba o para abajo según desde dónde se mirara. Es la misma
 * decisión que ya estaba tomada en `partials/acciones.php` y en la barra
 * superior; el menú era lo único que faltaba. El marcado vive en
 * `partials/iconos_menu.php`.
 *
 * LA MEDIDA VA EN PÍXELES Y NO EN `rem`, con el mismo criterio que el botón:
 * es una pieza que se mira todo el día y su tamaño se eligió mirándola —21px,
 * arriba de los 20 que daba el `1.48rem` de antes— y no calculándola. */
.menu-lateral .cabecera-grupo .icono {
    display: flex;
    align-items: center;
    justify-content: center;
    line-height: 1;
}
.menu-lateral .cabecera-grupo .icono svg {
    width: 21px;
    height: 21px;
    display: block;
    stroke: currentColor;
    fill: none;
    stroke-width: 1.5;
    stroke-linecap: round;
    stroke-linejoin: round;
}

/* EL SEGUNDO COLOR SE DERIVA DEL PRIMERO, Y NO PUEDE SER DE OTRA FORMA.
 *
 * La cabecera se invierte entera según el estado: en reposo es
 * `--color-texto-suave` sobre la superficie del menú y activa pasa a
 * `--color-primario-texto` sobre un relleno de `--color-primario`. Y
 * `--color-primario` depende además del modo (claro/oscuro) y de cuál de las
 * once marcas eligió la empresa (179): son 22 combinaciones. Un hex acertaría
 * en una. Derivado de `currentColor`, las 22 salen bien solas.
 *
 * `fill-opacity` Y NO `opacity`: `opacity` apaga el elemento entero, así que
 * también aclararía el TRAZO de esa misma figura y el contorno del engranaje o
 * de la rueda quedaría más claro que el resto del dibujo. */
.menu-lateral .cabecera-grupo .icono svg .relleno {
    fill: currentColor;
    fill-opacity: 0.25;
}

.menu-lateral.compacto .cabecera-grupo .rotulo {
    font-size: 0.76rem;
    text-transform: uppercase;
    letter-spacing: 0;
    text-align: center;
    line-height: 1.15;
    font-weight: 600;
    /* Los rótulos largos (CONFIGURACIÓN) tienen que poder cortarse */
    white-space: normal;
    overflow-wrap: anywhere;
    max-width: 100%;
}

/* Hover bien marcado sobre la sección */
.menu-lateral.compacto .grupo-menu:hover .cabecera-grupo,
.menu-lateral.compacto .grupo-menu.abierto .cabecera-grupo,
.menu-lateral.compacto .grupo-menu.tiene-activo .cabecera-grupo {
    background: var(--color-primario);
    color: var(--color-primario-texto);
}

/* El panel que se despliega a la derecha */
/**
 * El panel se abre al costado del grupo, arrancando a su misma altura
 * (`top: 0` respecto de `.grupo-menu`).
 *
 * El techo de alto y el scroll propio son la mitad de la solución al panel que
 * no entra en la pantalla: con el menú fijo, el de un grupo de abajo —Reportes—
 * se iba abajo del pliegue y el último ítem quedaba inalcanzable, porque no hay
 * nada que scrollear para llegar. Esto garantiza que NUNCA sea más alto que la
 * pantalla; que además esté *dentro* de ella lo resuelve el JavaScript del menú
 * (partials/layout_inicio.php), que lo sube lo justo cuando se pasa.
 *
 * Van los dos: el techo solo no alcanza —un panel corto que arranca abajo se
 * sigue yendo— y el corrimiento solo tampoco, porque a un panel más alto que la
 * pantalla no hay a dónde subirlo.
 */
.menu-lateral.compacto .panel-grupo {
    position: absolute;
    left: 100%;
    top: 0;
    min-width: 210px;
    max-height: calc(100vh - var(--altura-header) - var(--e4));
    overflow-y: auto;
    background: var(--color-superficie);
    border: 1px solid var(--color-borde-fuerte);
    border-left: 3px solid var(--color-primario);
    border-radius: 0 var(--radio) var(--radio) 0;
    box-shadow: 6px 6px 20px rgba(var(--sombra-rgb),0.18);
    padding: var(--e2) 0;
    z-index: 120;
    display: none;
}

.menu-lateral.compacto .grupo-menu:hover .panel-grupo,
.menu-lateral.compacto .grupo-menu.abierto .panel-grupo {
    display: block;
}

/* Título dentro del panel, para no perder contexto */
.menu-lateral.compacto .panel-grupo .titulo-panel {
    padding: var(--e1) var(--e3) var(--e2);
    margin-bottom: var(--e1);
    font-size: 0.92rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--color-primario);
    border-bottom: 1px solid var(--color-borde);
}

.menu-lateral.compacto .panel-grupo a {
    display: block;
    padding: var(--e2) var(--e4);
    font-size: 1.18rem;
    color: var(--color-texto);
    text-decoration: none;
    border-left: 3px solid transparent;
    white-space: nowrap;
}

/* La opción con atajo: el rótulo a la izquierda y la tecla a la derecha.
   El <a> pasa a ser flex para poder separarlos; con un solo hijo —o sea sin
   atajo— `space-between` lo deja al principio, así que las opciones que no lo
   declaran no se mueven. El ancho del panel lo sigue fijando la opción más
   ancha, que ahora es una con tecla. */
.menu-lateral.compacto .panel-grupo a {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--e4);
}

/* EN EL CELULAR LA TECLA NO SE DIBUJA: no hay teclado que la apriete y en el
   cajón le roba lugar al rótulo, que ahí ocupa el renglón entero. Es el mismo
   criterio con el que la navegación rápida no se dibuja en el celular. */
html.es-movil .menu-atajo { display: none; }

/* La tecla es un dato de la opción, no un botón: se lee al pasar y no compite
   con el rótulo. Por eso va chica y apagada. */
.menu-atajo {
    flex: 0 0 auto;
    font-family: var(--fuente-mono);
    font-size: 0.78rem;
    color: var(--color-texto-suave);
    background: var(--color-superficie-alt);
    border: 1px solid var(--color-borde);
    border-radius: 3px;
    padding: 0 4px;
}

/* Sobre el hover y sobre la activa, el fondo cambia: la tecla hereda el color
   del renglón o queda ilegible sobre el azul. */
.menu-lateral.compacto .panel-grupo a:hover .menu-atajo,
.menu-lateral.compacto .panel-grupo a.activo .menu-atajo {
    color: inherit;
    background: transparent;
    border-color: currentColor;
    opacity: 0.75;
}

/* Hover marcado sobre cada opción */
.menu-lateral.compacto .panel-grupo a:hover {
    background: var(--color-primario);
    color: var(--color-primario-texto);
    border-left-color: var(--color-alerta);
}

.menu-lateral.compacto .panel-grupo a.activo {
    background: var(--color-primario-suave);
    color: var(--color-primario);
    font-weight: 700;
    border-left-color: var(--color-primario);
}

/* Puente invisible: evita que el panel se cierre al mover el mouse
   desde el botón hacia el panel (el hueco de 1px cortaba el hover) */
.menu-lateral.compacto .panel-grupo::before {
    content: "";
    position: absolute;
    left: -8px;
    top: 0;
    bottom: 0;
    width: 8px;
}

/* En pantallas chicas se mantiene la columna de iconos (ocupa poco y
   funciona igual al tocar). No se cambia a fila con scroll horizontal
   porque eso hacía crecer el ancho de la página sin control. */
@media (max-width: 700px) {
    .layout-app { flex-direction: row; }
    .menu-lateral.compacto {
        width: 56px;
        flex-direction: column;
        border-right: 1px solid var(--color-borde);
        border-bottom: none;
    }
    .menu-lateral.compacto .cabecera-grupo { padding: var(--e2) 2px; }
    .menu-lateral.compacto .cabecera-grupo .rotulo { font-size: 0.71rem; }
    .menu-lateral.compacto .panel-grupo { min-width: 180px; }
}

/* ===========================================================
   BARRA DE USO DE CRÉDITO (ficha del cliente)
   =========================================================== */

.barra-credito {
    height: 10px;
    background: var(--color-superficie-alt);
    border: 1px solid var(--color-borde);
    border-radius: 999px;
    overflow: hidden;
    margin-bottom: var(--e2);
}
.barra-credito .relleno {
    height: 100%;
    background: var(--color-exito);
    transition: width 0.2s ease;
}
.barra-credito .relleno.alerta   { background: var(--color-alerta); }
.barra-credito .relleno.excedido { background: var(--color-error); }

/* ===========================================================
   POS: NAVEGACIÓN CON TECLADO
   =========================================================== */

/* Fila del carrito seleccionada con las flechas */
.pos-tabla tbody tr.fila-sel {
    background: var(--color-primario-suave) !important;
    box-shadow: inset 3px 0 0 var(--color-primario);
}
.pos-tabla tbody tr.fila-sel td { font-weight: 600; }

/* Etiqueta de oferta dentro de la línea */
.pos-oferta {
    display: inline-block;
    margin-top: 2px;
    padding: 1px 6px;
    border-radius: 999px;
    background: var(--color-exito-fondo);
    color: var(--color-exito);
    border: 1px solid var(--color-exito-borde);
    font-size: 0.92rem;
    font-weight: 600;
    white-space: nowrap;
}
/* Vender sin stock (042): la línea se carga igual y queda marcada con el saldo
   en el que va a quedar el depósito.

   Va en el color de ERROR y no en el de alerta a propósito: en esta misma celda
   ya conviven la etiqueta de oferta (verde) y la de balanza (azul), y esta
   tiene que poder leerse de un vistazo entre las tres. No es un aviso más: es
   mercadería que el sistema no tiene y se está entregando igual. */
.pos-sin-stock {
    display: inline-block;
    margin-top: 2px;
    padding: 1px 6px;
    border-radius: 999px;
    background: var(--color-error-fondo);
    color: var(--color-error);
    border: 1px solid var(--color-error-borde);
    font-size: 0.92rem;
    font-weight: 700;
    white-space: nowrap;
}
/* Etiqueta de balanza dentro de la línea: dice que la cantidad no la tipeó
   nadie, la trajo el código. Va en el color de información y NO en el de
   oferta: son dos cosas distintas y en la misma celda se leen juntas —una
   línea puede tener las dos—, así que si compartieran color el cajero no
   podría distinguir un descuento de un peso leído. */
.pos-balanza {
    display: inline-block;
    margin-top: 2px;
    padding: 1px 6px;
    border-radius: 999px;
    background: var(--color-info-fondo);
    color: var(--color-info);
    border: 1px solid var(--color-info-borde);
    font-size: 0.92rem;
    font-weight: 600;
    white-space: nowrap;
}
.pos-precio-tachado {
    text-decoration: line-through;
    color: var(--color-texto-tenue);
    font-size: 0.97rem;
    margin-right: 4px;
}

/* Barra de atajos fija abajo, siempre visible */
.pos-barra-atajos {
    position: sticky;
    bottom: 0;
    background: var(--color-texto);
    color: var(--color-superficie);
    display: flex;
    flex-wrap: wrap;
    gap: var(--e3);
    padding: var(--e2) var(--e3);
    border-radius: var(--radio);
    margin-top: var(--e2);
    font-size: 1rem;
    z-index: 40;
}
.pos-barra-atajos span { white-space: nowrap; }
.pos-barra-atajos kbd {
    background: var(--color-superficie);
    color: var(--color-texto);
    border-radius: 2px;
    padding: 0 5px;
    font-weight: 700;
    font-family: var(--fuente-mono);
    margin-right: 3px;
}

/* Panel de ayuda de teclas (F1) */
.pos-ayuda-teclas {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
    gap: var(--e2) var(--e4);
}
.pos-ayuda-teclas .fila {
    display: flex;
    justify-content: space-between;
    gap: var(--e2);
    padding: 3px 0;
    border-bottom: 1px dotted var(--color-borde);
}
.pos-ayuda-teclas kbd,
.texto-ayuda kbd {
    background: var(--color-superficie-alt);
    border: 1px solid var(--color-borde);
    border-bottom-width: 2px;
    border-radius: 3px;
    padding: 0 6px;
    font-family: var(--fuente-mono);
    font-weight: 700;
    white-space: nowrap;
}

/* Resultado de búsqueda / cliente resaltado por teclado */
.pos-resultado.seleccionado {
    background: var(--color-primario) !important;
    color: var(--color-primario-texto);
}
/**
 * LA FILA SELECCIONADA PIERDE EL COLOR DE ESTADO, Y ES A PROPÓSITO.
 *
 * Esta fila se pinta con el RELLENO DE MARCA, y el relleno de marca INVIERTE
 * su luminancia entre los dos temas: `petroleo` es `#1c4e73` en claro y
 * `#6fa8d6` en oscuro. O sea que lo que va encima cambia de polaridad, y los
 * tres colores que había acá —un blanco al 80%, un verde menta y un rosa—
 * estaban escritos para el fondo oscuro. En oscuro daban 2.16:1, 1.77:1 y
 * 1.49:1: el código y el stock de la fila que el cajero tiene seleccionada
 * DESAPARECÍAN. Fue lo único que el modo oscuro dejó peor que antes de existir.
 *
 * NO ALCANZA CON ELEGIR MEJOR EL VERDE Y EL ROJO. Se buscó un par que se leyera
 * sobre los once rellenos oscuros y NO EXISTE: el mejor verde probado
 * (`#14532d`) llega a 2.88:1 con `vino` y pasa en una marca de once. El relleno
 * oscuro es un pastel claro, y pedirle a un verde y a un rojo que se distingan
 * entre sí Y del fondo sobre once pasteles distintos no cierra.
 *
 * Así que MIENTRAS LA FILA ESTÁ SELECCIONADA el estado va en el color del
 * texto, y lo que lo distingue es lo que ya lo distinguía sin mirar el color:
 * las PALABRAS —"stock 12" contra "SIN STOCK", "a favor" contra "debe"— más la
 * mayúscula y la negrita que `.stock-no` ya traía. Es una fila a la vez, el
 * color vuelve apenas la selección se mueve, y la diferencia sobrevive en
 * escala de grises, que es más de lo que hacía el par verde/rojo.
 *
 * El 0.85 del código: es el velo que lo mantiene en segundo plano sin romperlo.
 * Con 0.80 —el valor que tenía— `vino` se queda en 4.43:1. Con 0.85 la peor de
 * las once es 4.79:1. En claro pasa de 6.32:1 a 6.85:1, que no se ve.
 */
.pos-resultado.seleccionado .cod,
.pos-resultado.seleccionado .text-suave {
    color: var(--color-primario-texto);
    opacity: 0.85;
}
.pos-resultado.seleccionado .stock-ok,
.pos-resultado.seleccionado .stock-no,
/* `.text-tenue` es el tercer estado, "sin control": misma historia y peor
   número —1.03:1 en oscuro, y 3.37:1 en claro, o sea que acá NUNCA se leyó. */
.pos-resultado.seleccionado .text-tenue { color: var(--color-primario-texto); }

/* Situación de cuenta corriente del cliente en el POS */
.pos-cc {
    margin-top: var(--e2);
    padding: var(--e2);
    border-radius: var(--radio-chico);
    font-size: 1rem;
    border: 1px solid transparent;
}
.pos-cc.bien    { background: var(--color-exito-fondo);  border-color: var(--color-exito-borde); color: var(--color-exito); }
.pos-cc.aviso   { background: var(--color-alerta-fondo); border-color: var(--color-alerta-borde); color: var(--color-alerta); }
.pos-cc.peligro { background: var(--color-error-fondo);  border-color: var(--color-error-borde); color: var(--color-error); }
.pos-cc .linea-cc { display: flex; justify-content: space-between; }


/* =====================================================================
   SITUACIÓN DE CUENTA CORRIENTE EN LA CARGA DE UNA FACTURA
   (`partials/credito_cliente.php`)

   Es el hermano del `.pos-cc` de arriba y no la misma clase, porque el sitio
   donde vive es otro: aquél es un panel de la barra lateral del mostrador y
   éste es un item más de una fila de formulario, al lado de la observación.
   Comparte los colores de estado —los cortes tienen que ser los mismos— y suma
   el neutro, que es el caso normal de un cliente de cuenta corriente: deber
   plata dentro del límite no es una buena noticia ni una mala.

   `flex: 0 1 260px` es lo que angosta a la observación sin dejarla inservible:
   la observación es `.c.crece` (`flex: 1 1 200px`), así que se queda con lo que
   sobra y este bloque se encoge antes de partir la fila.
   ===================================================================== */

.cc-cliente {
    flex: 0 1 470px;
    min-width: 350px;
    padding: var(--e2);
    border: 1px solid var(--color-borde);
    border-radius: var(--radio-chico);
    background: var(--color-superficie-alt);
    font-size: 1rem;
    line-height: 1.4;
}

.cc-cliente .cc-titulo {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: var(--e2);
    font-size: 12px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.02em;
    opacity: 0.85;
    margin-bottom: var(--e1);
}
/* "ver detalle" es un BOTÓN y no un enlace, porque abre un modal y no navega
   a ningún lado (ver `partials/modal_deuda.php`). Se dibuja como un enlace
   igual: es lo que se busca con la vista cuando se quiere ver más, y un botón
   con marco ahí adentro compite con los importes, que son lo que hay que leer. */
.cc-cliente .cc-enlace {
    background: none;
    border: 0;
    padding: 0;
    font: inherit;
    color: inherit;
    text-transform: none;
    letter-spacing: 0;
    text-decoration: underline;
    cursor: pointer;
}

/* Las dos mitades del recuadro: el saldo y el crédito, una al lado de la otra.
   `min-width: 0` y no el `auto` que trae un item de flex: sin eso, un importe
   largo ensancha su columna y la otra se come el espacio del vecino en vez de
   partir el renglón. */
.cc-cliente .cc-cols { display: flex; gap: var(--e4); align-items: flex-start; }
.cc-cliente .cc-col  { flex: 1 1 0; min-width: 0; }

/* La última aclaración de cada columna no lleva el margen de abajo: es lo que
   hace que las dos mitades terminen a la misma altura que el borde del recuadro. */
.cc-cliente .cc-col > .cc-nota:last-child { margin-bottom: 0; }

.cc-cliente .cc-linea {
    display: flex;
    justify-content: space-between;
    gap: var(--e2);
    font-variant-numeric: tabular-nums;
}
.cc-cliente .cc-monto { font-weight: 700; }
/* Un dato que no es un importe ("sin límite cargado"). Se apaga con opacidad
   y NO con `.text-suave`, que fija un gris: adentro de un recuadro rojo o ámbar
   ese gris pelea con el color del estado. */
.cc-cliente .cc-tenue { opacity: 0.75; }

/* La aclaración de abajo de cada importe: cuántos comprobantes, cuánto del
   límite. Va tenue para que el número siga siendo lo que se lee primero. */
.cc-cliente .cc-nota {
    font-size: 0.95rem;
    opacity: 0.75;
    margin-bottom: var(--e1);
}

.cc-cliente .cc-aviso { font-weight: 700; margin-top: var(--e1); }

/* `neutro` NO tiene regla y no es un olvido: ES el recuadro pelado de arriba.
   Es el caso normal —deber plata dentro del límite— y lo que corresponde ahí
   es no pintar nada, porque no hay nada que avisar. */
.cc-cliente.bien    { background: var(--color-exito-fondo);  border-color: var(--color-exito-borde); color: var(--color-exito); }
.cc-cliente.aviso   { background: var(--color-alerta-fondo); border-color: var(--color-alerta-borde); color: var(--color-alerta); }
.cc-cliente.peligro { background: var(--color-error-fondo);  border-color: var(--color-error-borde); color: var(--color-error); }

/* =====================================================================
   REPORTES
   ===================================================================== */

.filtro-reporte { margin-bottom: var(--e4); }

.periodo-reporte {
    color: var(--color-texto-suave);
    font-size: 1.02rem;
}

/* Atajos de período debajo del filtro */
.atajos-periodo {
    display: flex;
    align-items: center;
    gap: var(--e2);
    margin-top: var(--e3);
    font-size: 0.98rem;
    color: var(--color-texto-suave);
}
.chip-periodo {
    background: var(--color-superficie);
    border: 1px solid var(--color-borde);
    border-radius: 999px;
    padding: var(--e1) var(--e3);
    cursor: pointer;
    font-size: 0.98rem;
    color: var(--color-primario);
}
.chip-periodo:hover {
    background: var(--color-primario-suave);
    border-color: var(--color-borde-fuerte);
}
/* El atajo que está puesto. Mismo dibujo que `.chip.activo`, que es el chip
   del tablero y el del kardex: un solo lenguaje para "esto es lo elegido". */
.chip-periodo.activo {
    background: var(--color-primario);
    border-color: var(--color-primario);
    color: var(--color-primario-texto);
}
/* El hover no puede borrar el estado: sin esta regla, pasar el mouse por
   encima del chip prendido lo devuelve al fondo claro y parece que se apagó. */
.chip-periodo.activo:hover {
    background: var(--color-primario);
    border-color: var(--color-primario);
}

/* Tarjetas con los totales del reporte */
/* ===================================================================
   EL TABLERO DEL REPARTIDOR: CINCO TARJETAS DE ANCHO COMPLETO
   ===================================================================
   Se tocan de parado, con una mano y con el camión cargado atrás, así que
   cada una ocupa el ancho entero y mide más que el mínimo de 48px: apuntarle
   a una tarjeta chica es el gesto que falla con el celular en la mano.

   VAN APILADAS SIEMPRE, también en el escritorio. Es una lista de cinco
   destinos y no un panel de indicadores: en una grilla de tres columnas la
   última fila queda coja y el orden —entregados, a entregar, todos, cobros,
   planilla— deja de leerse de arriba a abajo.

   Y SON `<a>` Y NO `<div>` CON `onclick`: se abren en una pestaña nueva con
   el gesto del navegador, el teclado las alcanza con Tab y un lector de
   pantalla las anuncia como lo que son. */
.tarjetas-reparto {
    display: flex;
    flex-direction: column;
    gap: var(--e2);
    margin-bottom: var(--e4);
}

.tarjeta-reparto {
    display: flex;
    align-items: center;
    gap: var(--e3);
    min-height: 64px;
    padding: var(--e3) var(--e4);
    background: var(--color-superficie);
    border: 1px solid var(--color-borde);
    border-radius: var(--radio);
    color: inherit;
    text-decoration: none;
    -webkit-tap-highlight-color: transparent;
}
.tarjeta-reparto:hover,
.tarjeta-reparto:active { background: var(--color-superficie-alt); }

.tarjeta-reparto .icono { font-size: 24px; line-height: 1; }

/* El rótulo se lleva el espacio sobrante, así que el número queda pegado al
   borde derecho sin necesidad de un margen calculado. */
.tarjeta-reparto .rotulo {
    flex: 1 1 auto;
    min-width: 0;
    font-weight: 700;
    letter-spacing: 0.03em;
}

/* EL NÚMERO SE LEE A UN METRO, que es a la distancia a la que se mira un
   celular apoyado en el tablero de la camioneta. `tabular-nums` para que no
   baile al cambiar de 9 a 10. */
.tarjeta-reparto .valor {
    font-size: 22px;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}
.tarjetas-resumen {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
    gap: var(--e3);
    margin-bottom: var(--e4);
}
.tarjeta {
    background: var(--color-superficie);
    border: 1px solid var(--color-borde);
    border-radius: var(--radio);
    padding: var(--e3) var(--e4);
    display: flex;
    flex-direction: column;
    gap: var(--e1);
}
.tarjeta .etiqueta {
    font-size: 0.95rem;
    color: var(--color-texto-suave);
    text-transform: uppercase;
    letter-spacing: 0.03em;
}
.tarjeta .valor {
    font-size: 1.5rem;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    color: var(--color-texto);
}
.tarjeta .detalle {
    font-size: 0.95rem;
    color: var(--color-texto-tenue);
}
/* El rótulo y su aclaración en el MISMO renglón.
   Son dos elementos y no uno solo porque `.etiqueta` lleva `text-transform:
   uppercase`: la aclaración adentro saldría gritada. Y la fila es un elemento
   propio para que la tarjeta —que es una columna flex— siga contando un solo
   hijo ahí y el resto de su contenido no se corra.
   `align-items: baseline` los apoya sobre la misma línea aunque midan distinto,
   y el `wrap` los parte en dos renglones en una tarjeta angosta antes que
   desbordarla. */
.tarjeta .linea-etiqueta {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: var(--e2);
}
/* El botón de acción de una tarjeta va abajo de todo, despegado del importe:
   el `gap` de la tarjeta está pensado para renglones de texto y deja el botón
   pegado al número. No hace falta tocarle el ancho —`.boton-primario` ya trae
   `width: max-content`, así que el `stretch` de la columna no lo estira— pero
   sí el `align-self`, porque sin él la caja del botón se sigue alineando al
   comienzo por su ancho y no por la regla, y eso se rompe el día que alguien
   use acá un `.boton-secundario`, que no lo declara. */
.tarjeta .boton-primario,
.tarjeta .boton-secundario {
    align-self: flex-start;
    margin-top: var(--e2);
}
.tarjeta.negativa .valor { color: var(--color-error); }

/* Encabezados de tabla ordenables */
.tabla-datos th.ordenable {
    cursor: pointer;
    user-select: none;
    position: sticky;
    padding-right: var(--e4);
}
.tabla-datos th.ordenable:hover { background: var(--color-primario-suave); }
.tabla-datos th.ordenable:focus-visible { outline: 2px solid var(--color-primario); outline-offset: -2px; }
.tabla-datos th.ordenable::after {
    content: '';
    position: absolute;
    right: var(--e2);
    top: 50%;
    transform: translateY(-50%);
    opacity: 0.25;
    border-left: 4px solid transparent;
    border-right: 4px solid transparent;
    border-bottom: 5px solid currentColor;
}
.tabla-datos th.orden-asc::after  { opacity: 1; }
.tabla-datos th.orden-desc::after {
    opacity: 1;
    border-bottom: none;
    border-top: 5px solid currentColor;
}

/* Importes negativos en cualquier tabla de reportes */
.tabla-datos td.negativo { color: var(--color-error); }

/* Participación sobre el total: número con barra proporcional detrás */
.participacion {
    display: inline-flex;
    align-items: center;
    justify-content: flex-end;
    gap: var(--e2);
    position: relative;
    min-width: 90px;
}
.participacion .barra {
    position: absolute;
    left: 0;
    top: 50%;
    transform: translateY(-50%);
    height: 14px;
    background: var(--color-primario-suave);
    border-radius: var(--radio-chico);
    z-index: 0;
}
.participacion .cifra { position: relative; z-index: 1; }

/* Nota al pie de un reporte, explicando el criterio de cálculo */
.nota-reporte {
    margin-top: var(--e4);
    padding: var(--e3);
    background: var(--color-info-fondo);
    border-left: 3px solid var(--color-info);
    color: var(--color-texto-suave);
    font-size: 1rem;
}

.titulo-seccion {
    margin-top: var(--e5);
    margin-bottom: var(--e3);
    font-size: 1.2rem;
}

.etiqueta-chica {
    display: inline-block;
    padding: 0 var(--e2);
    border-radius: var(--radio-chico);
    background: var(--color-superficie-alt);
    border: 1px solid var(--color-borde);
    color: var(--color-texto-suave);
    font-size: 0.85rem;
    vertical-align: middle;
}

/* Tramo intermedio de antigüedad: llama la atención sin ser alarma */
.tabla-datos td.tramo-viejo { color: var(--color-alerta); }

/* Ficha del cliente arriba del extracto de cuenta corriente */
.ficha-cliente {
    background: var(--color-superficie);
    border: 1px solid var(--color-borde);
    border-left: 3px solid var(--color-primario);
    border-radius: var(--radio);
    padding: var(--e3) var(--e4);
    margin-bottom: var(--e4);
    display: flex;
    flex-direction: column;
    gap: var(--e1);
}

/* Fila de arrastre del extracto: es un punto de partida, no un movimiento */
.tabla-datos tr.fila-arrastre td {
    background: var(--color-superficie-alt);
    font-style: italic;
    color: var(--color-texto-suave);
}

/* Saldo a favor del cliente */
.tabla-datos td.a-favor { color: var(--color-exito); }

/* =====================================================================
   GRÁFICOS (SVG generado en el servidor)
   ===================================================================== */

.grafico-contenedor {
    background: var(--color-superficie);
    border: 1px solid var(--color-borde);
    border-radius: var(--radio);
    padding: var(--e4);
    margin-bottom: var(--e4);
}
.grafico {
    width: 100%;
    height: auto;
    display: block;
}
.grafico-eje {
    font-family: var(--fuente-base);
    font-size: 13px;
    fill: var(--color-texto-suave);
}
.grafico-pico {
    font-family: var(--fuente-base);
    font-size: 13px;
    font-weight: 600;
    fill: var(--color-texto);
}
.grafico-leyenda {
    display: flex;
    gap: var(--e4);
    justify-content: center;
    margin-top: var(--e3);
    font-size: 1rem;
    color: var(--color-texto-suave);
}
.grafico-leyenda .item { display: inline-flex; align-items: center; gap: var(--e2); }
.grafico-leyenda .muestra {
    width: 12px;
    height: 12px;
    border-radius: 2px;
    display: inline-block;
}

/* Fila de un artículo vendido por debajo del costo */
.tabla-datos tr.fila-perdida { background: var(--color-error-fondo); }
.tabla-datos tr.fila-perdida:hover { background: var(--color-error-fondo-fuerte); }

/* Tablero: dos columnas que se apilan en pantallas angostas */
.tablero-columnas {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(340px, 1fr));
    gap: var(--e5);
    align-items: start;
}

/* Variación contra el período anterior */
.variacion { font-weight: 600; }
.variacion.sube   { color: var(--color-exito); }
.variacion.baja   { color: var(--color-error); }
.variacion.neutra { color: var(--color-texto-tenue); font-weight: 400; }

/* =====================================================================
   CAMPOS DE FECHA (date / datetime-local / time / month)
   El navegador dibuja el contenido del campo: lo único que se puede
   controlar es la caja, la tipografía y el ícono del calendario.
   Por eso el ancho se fija por tipo de dato — dd/mm/aaaa mide siempre lo
   mismo — en vez de dejarlo al 100% como los campos de texto.
   Ojo: NO se usa appearance:none. En Chrome saca el botón de limpiar (✕),
   y sin él una fecha opcional cargada por error no se puede borrar.
   ===================================================================== */

/* Ancho y tipografía por tipo de dato, en cualquier formulario.
   Va después de .campo/.formulario-abm a propósito: pisa el width:100%
   que heredan de ahí, pero cede ante los .a-* (que llevan !important). */
input[type="date"]           { width: 150px; }
input[type="datetime-local"] { width: 195px; }
input[type="time"]           { width: 95px;  }
input[type="month"]          { width: 140px; }

input[type="date"],
input[type="datetime-local"],
input[type="time"],
input[type="month"] {
    max-width: 100%;
    color: var(--color-texto);
    font-family: var(--fuente-base);
    font-variant-numeric: tabular-nums;
}

/* En el formulario denso quedaban con el alto por defecto del navegador,
   desalineados contra los select de 30px de al lado. */
.form-erp input[type="date"],
.form-erp input[type="datetime-local"],
.form-erp input[type="time"],
.form-erp input[type="month"] {
    padding: 4px var(--e2);
    border: 1px solid var(--color-borde);
    border-radius: var(--radio-chico);
    font-size: 1.16rem;
    background: var(--color-superficie);
    height: var(--alto-control);
}

/* Separadores (las barras de dd/mm/aaaa) más tenues que los números */
input[type="date"]::-webkit-datetime-edit-text,
input[type="datetime-local"]::-webkit-datetime-edit-text {
    color: var(--color-texto-tenue);
    padding: 0 1px;
}

/* Ícono del calendario: apagado hasta que se lo apunta */
input[type="date"]::-webkit-calendar-picker-indicator,
input[type="datetime-local"]::-webkit-calendar-picker-indicator,
input[type="month"]::-webkit-calendar-picker-indicator {
    cursor: pointer;
    opacity: 0.45;
    margin-left: var(--e1);
    padding: 0;
}
input[type="date"]:hover::-webkit-calendar-picker-indicator,
input[type="datetime-local"]:hover::-webkit-calendar-picker-indicator,
input[type="month"]:hover::-webkit-calendar-picker-indicator { opacity: 0.8; }
input[type="date"]:focus::-webkit-calendar-picker-indicator,
input[type="datetime-local"]:focus::-webkit-calendar-picker-indicator,
input[type="month"]:focus::-webkit-calendar-picker-indicator { opacity: 1; }

/* Foco: mismo amarillo de trabajo que el resto de los campos del ERP */
input[type="date"]:focus,
input[type="datetime-local"]:focus,
input[type="time"]:focus,
input[type="month"]:focus {
    outline: none;
    border-color: var(--color-primario);
    background: var(--color-campo-foco);
}

/* Solo lectura / deshabilitado. El readonly en un date no impide abrir el
   calendario en todos los navegadores: si tiene que ser inamovible, va
   disabled + un hidden con el valor, o directamente texto. */
input[type="date"][readonly],
input[type="datetime-local"][readonly],
input[type="date"]:disabled,
input[type="datetime-local"]:disabled,
input[type="time"]:disabled {
    background: var(--color-superficie-alt);
    color: var(--color-texto-suave);
}
input[type="date"]:disabled::-webkit-calendar-picker-indicator,
input[type="datetime-local"]:disabled::-webkit-calendar-picker-indicator { display: none; }

/* Fecha con error de validación (se agrega desde PHP, igual que en texto) */
input[type="date"].campo-error,
input[type="datetime-local"].campo-error {
    border-color: var(--color-error);
    background: var(--color-error-fondo);
}

/* Clases explícitas: mismo ancho, pero puestas a mano cuando el input está
   adentro de algo que ya le fijó el ancho (grilla, flex, celda). */
input.fecha       { width: 150px; }
input.fecha-hora  { width: 195px; }
input.fecha-mes   { width: 140px; }
input.solo-hora   { width: 95px;  }
input.fecha-larga { width: 100%;  }  /* pantallas angostas / modales */

/* Par desde–hasta: es el patrón que más se repite (ventas, cobranzas,
   notas de crédito, libro IVA, reportes). Queda como un solo control. */
.rango-fechas {
    display: inline-flex;
    align-items: center;
    gap: var(--e1);
    flex-wrap: nowrap;
}
.rango-fechas .sep {
    color: var(--color-texto-tenue);
    font-size: 1rem;
    padding: 0 2px;
    user-select: none;
}
.rango-fechas label {
    margin: 0;
    white-space: nowrap;
    font-size: 1rem;
    color: var(--color-texto-suave);
}
/* Rango inválido (desde > hasta): lo marca el PHP o el JS con la clase */
.rango-fechas.invalido input[type="date"] { border-color: var(--color-error); }
.rango-fechas .aviso-rango {
    font-size: 0.94rem;
    color: var(--color-error);
    white-space: nowrap;
}

/* Fecha dentro de una tabla editable: mismo estilo planilla que .celda */
.tabla-datos input.fecha-celda,
.pos-tabla input.fecha-celda {
    border: none;
    background: transparent;
    border-bottom: 1px dotted var(--color-borde);
    padding: 2px var(--e1);
    height: 28px;
    width: 100%;
    font-size: 1.09rem;
    text-align: left;
}
.tabla-datos input.fecha-celda:hover { background: var(--color-superficie-alt); }
.tabla-datos input.fecha-celda:focus,
.pos-tabla input.fecha-celda:focus {
    outline: none;
    background: var(--color-campo-foco);
    border-bottom: 1px solid var(--color-primario);
}

/* Fecha de solo lectura mostrada como dato (extractos, fichas): no es input */
.fecha-texto {
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}
.fecha-texto .hora { color: var(--color-texto-suave); font-size: 0.94rem; }
/* Vencida / por vencer, para listados de comprobantes y cheques */
.fecha-texto.vencida    { color: var(--color-error); font-weight: 600; }
.fecha-texto.por-vencer { color: var(--color-alerta); }

@media (max-width: 700px) {
    /* En el celular el calendario nativo ocupa la pantalla: el campo va ancho */
    input[type="date"],
    input[type="datetime-local"],
    input[type="month"],
    input.fecha,
    input.fecha-hora { width: 100%; }
    .rango-fechas { flex-wrap: wrap; }
}

@media print {
    /* Impreso, un campo de fecha tiene que leerse como un dato, no como un input */
    input[type="date"],
    input[type="datetime-local"],
    input[type="month"],
    input[type="time"] {
        border: none;
        padding: 0;
        background: transparent;
        color: var(--color-texto);
    }
    input[type="date"]::-webkit-calendar-picker-indicator,
    input[type="datetime-local"]::-webkit-calendar-picker-indicator { display: none; }
}

/* Comprobante que le corresponde al cliente elegido, en el POS */
.pos-comprobante {
    margin-top: var(--e1);
    font-size: 0.86rem;
    color: var(--color-texto-suave);
}
.pos-comprobante.con-error {
    color: var(--color-error);
    font-weight: 600;
}

/* ===========================================================
   BOTONES DE ACCIÓN DE LOS LISTADOS

   Un solo tamaño para todos: el problema que resuelven es que la
   columna "Acciones" quedaba con botones de distinto ancho según
   el largo del texto ("Ver" contra "Desactivar"), y entre filas ni
   siquiera arrancaban en la misma x cuando una fila tenía tres
   acciones y la otra cuatro.

   Los iconos van como SVG inline (partials/acciones.php): la app de
   escritorio funciona sin internet, así que no hay fuente de iconos
   ni CDN. Es el mismo motivo por el que el QR se dibuja en el
   proyecto (core/QrCode.php).
   =========================================================== */

/* Contenedor de la celda de acciones. Los <form> del POST quedan como
   ítems del flex, por eso llevan display:contents: sin eso cada form
   sería una caja aparte y el gap entre botones no sería parejo. */
.acciones-fila {
    align-items: center;
    gap: var(--e1);
    white-space: nowrap;
}
.acciones-fila form { display: contents; }

.btn-accion {
    /* Cuadrado fijo: es lo que hace que las columnas se alineen entre
       filas aunque una tenga 2 acciones y la otra 4. */
    width: 26px;
    height: 26px;
    flex: 0 0 26px;
    padding: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--color-borde-fuerte);
    border-radius: var(--radio-chico);
    background: var(--color-superficie);
    color: var(--color-primario);
    cursor: pointer;
    line-height: 1;
    text-decoration: none;
    font-family: var(--fuente-base);
}
.btn-accion:hover {
    background: var(--color-primario-suave);
    border-color: var(--color-primario);
}
.btn-accion:focus-visible {
    outline: 2px solid var(--color-primario);
    outline-offset: 1px;
}

/* El SVG hereda el color del botón: una sola regla de color por variante. */
.btn-accion svg {
    width: 15px;
    height: 15px;
    display: block;
    stroke: currentColor;
    fill: none;
    stroke-width: 1.8;
    stroke-linecap: round;
    stroke-linejoin: round;
}

/* El renglón más alto del celular no sirve de nada si lo que hay que tocar
   adentro sigue midiendo 26px: el cuadrado ES el blanco, y el aire de más
   queda alrededor. Crece con la fila, y el dibujo con él —a 15px adentro de
   34 el botón se ve vacío—.

   DOS COSAS QUE PARECEN DETALLE Y NO LO SON:

   - LOS TRES VALORES DEL CUADRADO VAN JUNTOS. Hoy la celda de acciones es un
     `<td>` y no un contenedor flex, así que el `flex-basis` está inerte y
     cambiarlo no se puede medir — pero donde SÍ hay un flex alrededor (las
     secciones de la orden del menú) un basis de 26 le gana al `width` de 34 y
     el botón se dibuja de 34 ocupando 26. Va por eso, y está dicho que es
     defensa y no algo que una prueba cubra.
   - Y ESTE BLOQUE VA DESPUÉS DE `.btn-accion svg`, no antes. Las dos reglas
     pesan lo mismo (0,1,1) —una `@media` no agrega especificidad— así que
     gana la última que se lee. Puesto arriba, el cuadrado crecía y el dibujo
     se quedaba en 15px; lo cazó la medición, no la lectura. */
@media (max-width: 700px) {
    .btn-accion {
        width: 34px;
        height: 34px;
        flex: 0 0 34px;
    }
    .btn-accion svg { width: 18px; height: 18px; }
}

/* Destructivas (eliminar, anular). Apagado hasta el hover: en una
   grilla de 200 filas, 200 papeleras rojas tapan todo lo demás. */
.btn-accion.peligro { color: var(--color-error); }
.btn-accion.peligro:hover {
    background: var(--color-error-fondo);
    border-color: var(--color-error);
}

/* Activar/Desactivar: el ojo tachado (inactivo) va tenue, así se lee
   de un vistazo qué filas están apagadas sin leer la columna Estado. */
.btn-accion.apagado { color: var(--color-texto-suave); }

.btn-accion:disabled,
.btn-accion.deshabilitado {
    opacity: 0.35;
    cursor: not-allowed;
    pointer-events: none;
}

/* Hueco del mismo ancho que un botón, para cuando una fila no tiene
   una acción que las demás sí (una sucursal que no se puede desactivar
   por ser el depósito central). Sin esto, la papelera de esa fila se
   correría a la izquierda y la columna dejaría de alinear. */
.btn-accion-hueco {
    width: 26px;
    height: 26px;
    flex: 0 0 26px;
    display: inline-block;
}

/* -----------------------------------------------------------
   MARCA DE PUBLICADO EN LA TIENDA (articulos/index.php)
   -----------------------------------------------------------
   Comparte el DIBUJO con los botones de acción —`iconoAccion('web')`, para
   que un icono exista en un solo lugar— y no la CAJA: acá no es algo que se
   aprieta sino un dato de la fila, así que no lleva el cuadrado de 26px ni el
   borde de `.btn-accion`.

   La columna tiene ancho fijo por el mismo motivo que `.btn-accion`: es lo que
   hace que las filas alineen aunque unas tengan el icono y otras no.

   EL SELECTOR VA CON `.tabla-datos` ADELANTE Y NO SUELTO: `.tabla-datos th, td`
   ya declara `text-align: left` con una clase MÁS un elemento, así que un
   `.col-web` pelado pierde y la columna queda alineada a la izquierda sin que
   ninguna regla esté mal escrita. Es la trampa del logo de la tienda. */

/* ===========================================================
   EL LISTADO DEL CATÁLOGO: QUE LOS BOTONES DE ACCIÓN SE VEAN SIEMPRE

   Con una columna de stock por sucursal, la grilla crece a lo ancho y la
   columna de acciones se va del borde: hay que scrollear a la derecha para
   llegar a los botones que se usan en cada fila. Son tres reglas y hacen falta
   las tres, cada una atacando una causa distinta.
   =========================================================== */

/* 1. LAS COLUMNAS DE TEXTO SE ACOTAN Y SE RECORTAN.
      Una categoría o una marca con nombre largo estiraban la tabla sola. El
      nombre entero queda en el `title` de cada celda, que es lo que hace que
      recortar no pierda información. */
.tabla-datos th.col-angosta,
.tabla-datos td.col-angosta {
    max-width: 130px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* 2. LAS DE STOCK SON ANGOSTAS Y DE ANCHO FIJO.
      Son una por sucursal, así que cada píxel se multiplica. El ancho fijo
      además las alinea entre filas aunque una tenga tres dígitos y otra seis —
      es el mismo motivo por el que `.btn-accion` es un cuadrado fijo. */
.tabla-datos th.col-stock,
.tabla-datos td.col-stock { width: 62px; padding-left: var(--e1); padding-right: var(--e1); }

/* La celda de stock se abre con doble clic (el kardex de ese local), y el
   cursor lo tiene que decir: una celda que responde y se ve igual que una que
   no, no la toca nadie. */
.tabla-datos td.col-stock[data-kardex-articulo] { cursor: pointer; }
.tabla-datos td.col-stock[data-kardex-articulo]:hover { background: var(--color-superficie-alt); }

/* LA COLUMNA DE LA SUCURSAL DE REFERENCIA (Stock -> Existencias).
   De ella salen el mínimo que muestra la columna de al lado y la sucursal a la
   que ajusta el botón de la fila, así que hay que poder distinguirla de un
   vistazo: con tres columnas iguales no hay cómo saber cuál de las tres es la
   que manda, y el usuario ajusta la equivocada.
   Se marca con los bordes y no con un fondo lleno: el fondo compite con el rojo
   del negativo y del bajo mínimo, que es lo que de verdad hay que ver. */
.tabla-datos th.col-stock-ref,
.tabla-datos td.col-stock-ref {
    border-left: 2px solid var(--color-primario);
    border-right: 2px solid var(--color-primario);
    font-weight: 600;
}
.tabla-datos th.col-stock-ref { border-top: 2px solid var(--color-primario); }

/* 3. Y LA COLUMNA DE ACCIONES QUEDA FIJA CONTRA EL BORDE DERECHO.
      Es lo que resuelve el pedido de fondo —que los botones estén siempre a la
      vista— sin depender de que los anchos den justo: el día que haya seis
      sucursales, la tabla scrollea y los botones siguen ahí.

      El fondo es OPACO y no puede ser `transparent`: lo que pasa por debajo al
      scrollear tiene que quedar tapado, o los números de stock se leerían
      encima de los iconos. Por eso también va en el `th`, que tiene el suyo. */
.tabla-datos th.col-acciones,
.tabla-datos td.col-acciones {
    position: sticky;
    right: 0;
    z-index: 2;
    background: var(--color-superficie);
    box-shadow: -6px 0 6px -6px rgba(var(--sombra-rgb), 0.18);
}
/* El encabezado está fijo por arriba Y por la derecha a la vez, así que su
   esquina tiene que ganarle a las dos: el `thead` va en 10 (ver el bloque del
   encabezado congelado) y esta celda queda por encima. */
.tabla-datos thead th.col-acciones { z-index: 11; background: var(--color-superficie-alt); }
/* La fila marcada por el teclado y la del hover pintan su fondo: sin esto, la
   celda fija seguiría con el suyo y se vería como un recorte pegado. */
.tabla-datos tbody tr:hover td.col-acciones,
.tabla-datos tbody tr.fila-sel td.col-acciones { background: var(--color-superficie-alt); }
.tabla-datos tbody tr.fila-resaltada td { background: var(--color-alerta-fondo); }

/* ===========================================================
   EL ARTÍCULO INACTIVO SE LEE EN LA FILA, NO EN UNA COLUMNA

   La columna "Estado" se fue: era un ancho fijo en todas las filas para decir
   algo que en el 99% de ellas es "Activo". Ahora el que NO lo está se ve de un
   vistazo — en rojo y tachado, que es como se lee "esto ya no va".

   EL TACHADO NO ALCANZA A LA COLUMNA DE ACCIONES, y no es un detalle: los
   botones son SVG con `currentColor`, así que heredarían el rojo, y una línea
   atravesando el ojo y el lápiz los hace ilegibles justo en la fila donde hay
   que apretar "activar".
   =========================================================== */
.tabla-datos tr.fila-inactiva > td:not(.col-acciones) {
    color: var(--color-error);
    text-decoration: line-through;
}
/* El texto suave de adentro (el guion del stock, el id) también: sin esto queda
   gris sobre una fila roja y se lee como si esa celda fuera de otra fila. */
.tabla-datos tr.fila-inactiva > td:not(.col-acciones) .text-suave { color: inherit; }
/* La opacidad se va: era lo que decía "inactivo" antes y ahora lo dice el
   color. Las dos juntas dejan un rojo lavado que no se distingue del gris. */

/* La tabla de la ayuda de atajos: dos columnas, la tecla y qué hace.
   Los <th> son separadores de sección y no encabezados de columna — por eso van
   con colspan y alineados a la izquierda, como un subtítulo. */
/* La tecla dibujada como tecla. El POS ya tenía el suyo acotado a su panel de
   ayuda; esto es el mismo dibujo para el resto del sistema, y aquel se queda
   como está porque su panel tiene otro contexto de color. */
.tabla-atajos kbd {
    background: var(--color-superficie-alt);
    border: 1px solid var(--color-borde);
    border-bottom-width: 2px;
    border-radius: 3px;
    padding: 1px 5px;
    font-family: var(--fuente-mono);
    font-size: 0.86rem;
}
.tabla-atajos { width: 100%; border-collapse: collapse; }
.tabla-atajos td { padding: 3px var(--e2); vertical-align: top; }
.tabla-atajos td:first-child { white-space: nowrap; width: 42%; }
.tabla-atajos th {
    text-align: left;
    padding: var(--e3) var(--e2) 2px;
    font-size: 0.82rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--color-texto-suave);
    font-weight: 600;
}
.tabla-atajos tr:first-child th { padding-top: 0; }

/* ===========================================================
   LA VENTANA MODAL ARRASTRABLE (partials/modal_ventana.php)

   Carga una pantalla del sistema en un iframe, tapa el fondo y se mueve de su
   barra de título. Ver el comentario de ese partial para el porqué de que sea un
   modal y no una ventana del sistema operativo.
   =========================================================== */
.fondo-modal-ventana {
    /* Se alinea arriba y no al centro vertical: la ventana es alta y centrada
       deja la barra de título a media pantalla, que es donde nadie la busca. */
    align-items: flex-start;
    padding: var(--e4);
}
.modal-ventana {
    background: var(--color-superficie);
    border-radius: var(--radio);
    box-shadow: 0 12px 40px rgba(var(--sombra-rgb),0.28);
    width: min(1100px, 96vw);
    height: min(88vh, 900px);
    display: flex;
    flex-direction: column;
    overflow: hidden;      /* recorta el iframe contra las esquinas redondeadas */
}
/* Al arrastrarla sale del centrado del flex y pasa a posicionarse sola. Sin
   `position: fixed`, el `left/top` que escribe el JavaScript se resolvería
   contra el velo y la ventana quedaría corrida por su padding. */
.modal-ventana.movida { position: fixed; margin: 0; }

.modal-ventana-barra {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--e2);
    padding: var(--e2) var(--e3);
    border-bottom: 1px solid var(--color-borde);
    background: var(--color-superficie-alt);
    cursor: move;
    /* Sin esto, arrastrar selecciona el texto del título y la ventana se mueve
       con media frase resaltada atrás. */
    user-select: none;
}
.modal-ventana-barra h2 {
    margin: 0;
    font-size: 1.02rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.modal-ventana iframe {
    flex: 1 1 auto;
    width: 100%;
    border: 0;
    display: block;
}

/* Con la ventana abierta, el fondo no scrollea: la rueda del mouse tiene que
   mover lo que se está mirando, no la grilla de atrás. */
body.con-modal-ventana { overflow: hidden; }

/* ===========================================================
   UNA PANTALLA SERVIDA DENTRO DEL MODAL (`?marco=1`)

   El layout no dibuja la barra superior ni el menú, así que lo único que queda
   es acomodar el contenido: sin el menú al costado, el margen de la izquierda
   sobra, y sin la barra fija arriba tampoco hace falta el hueco que la
   compensaba.
   =========================================================== */
body.en-marco .layout-app { display: block; }
body.en-marco .contenido { padding: var(--e3); }
/* Las migas y el botón "Volver" llevan a la ventana grande desde adentro de un
   iframe: el destino se abriría ACÁ, sin menú y sin barra, y el usuario quedaría
   encerrado en un modal navegando un sistema sin navegación. */
body.en-marco .migas,
body.en-marco .nav-rapida { display: none; }
/* Lo que sobra adentro del modal porque la barra de la ventana ya lo dice (el
   nombre del cliente en Cobrar, por ejemplo). Fuera del modal se ve. */
body.en-marco .solo-fuera-de-marco { display: none; }

/* La deuda del cliente en Cobrar, en un renglón arriba de la tabla del reparto
   y no en una tarjeta a lo ancho que empujaba el formulario hacia abajo. */
.deuda-cobro {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: var(--e1) var(--e2);
    margin-bottom: var(--e2);
}
.deuda-cobro .rotulo {
    font-size: 0.78rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.02em;
    color: var(--color-texto-suave);
}
.deuda-cobro strong { font-size: 1.15rem; font-variant-numeric: tabular-nums; }
.deuda-cobro strong.debe { color: var(--color-error); }
.deuda-cobro .separador { color: var(--color-texto-suave); }

/* La barra y el menú, por si el servidor no llegó a enterarse (187).

   Con `?marco=1` puesto el layout directamente no los dibuja, y esto no hace
   falta. Pero ese parámetro SE PIERDE en la primera navegación de adentro
   —ningún enlace lo arrastra, y el `Location` de un POST tampoco— así que al
   primer clic el iframe recargaba con el layout entero: menú, barra, y una
   segunda ventana modal esperando un Ctrl+clic. `partials/modal_ventana.php`
   detecta el marco en tiempo de ejecución y estampa esta misma clase, que es
   lo que deja la pantalla igual que si el servidor lo hubiera sabido. */
body.en-marco .barra-superior,
body.en-marco .menu-lateral { display: none; }

/* ===========================================================
   EL FILTRO DEL MODAL DE KARDEX: chips + dos fechas

   Los chips y los campos son UN solo control: un chip escribe las dos fechas y
   tocar una fecha apaga los chips. Por eso van juntos en la misma franja, con el
   fondo de la cabecera del modal — separados, se leerían como dos filtros que
   compiten.
   =========================================================== */
.kardex-filtro {
    display: flex;
    flex-wrap: wrap;
    gap: var(--e3);
    align-items: center;
    justify-content: space-between;
    padding: var(--e2) var(--e4);
    border-bottom: 1px solid var(--color-borde);
    background: var(--color-superficie-alt);
}
.kardex-chips { display: flex; gap: var(--e1); flex-wrap: wrap; }
.kardex-fechas { display: flex; gap: var(--e1); align-items: center; font-size: 0.88rem; }

/* LOS DOS CAMPOS DE FECHA VAN COMO LOS DEL SISTEMA, y no con un padding y un
   cuerpo propios.

   Estaban con `padding: 2px` y `font-size: 0.88rem` y nada más: sin borde, sin
   fondo y sin alto, o sea con el dibujo por defecto del navegador en medio de un
   modal diseñado — y además más chicos que cualquier otro campo del sistema, que
   es justo lo que hace que un campo no parezca un campo.

   Es la misma regla que `.form-erp input[type="date"]`, que existe por lo mismo:
   en el formulario denso quedaban con el alto por defecto, desalineados contra
   los select de al lado. Acá al lado hay un select —el de sucursal— así que el
   alto tiene que ser el mismo. */
.kardex-fechas input[type="date"] {
    padding: 4px var(--e2);
    border: 1px solid var(--color-borde);
    border-radius: var(--radio-chico);
    background: var(--color-superficie);
    font-size: 0.88rem;
    height: var(--alto-control);
}
.kardex-fechas input[type="date"]:focus,
.kardex-sucursal select:focus {
    outline: none;
    border-color: var(--color-primario);
    box-shadow: 0 0 0 2px var(--color-primario-suave);
}

/* El select de sucursal del modal. Va PRIMERO en la fila y con su rótulo: es lo
   que decide QUÉ se está mirando, mientras que los chips y las fechas deciden
   de cuándo.

   Y con la MISMA caja que los campos de fecha de al lado: son un solo control
   repartido en tres piezas, y tres alturas distintas se leen como tres cosas
   sueltas. */
.kardex-sucursal { display: flex; gap: var(--e1); align-items: center; font-size: 0.88rem; }
.kardex-sucursal select {
    padding: 4px var(--e2);
    border: 1px solid var(--color-borde);
    border-radius: var(--radio-chico);
    background: var(--color-superficie);
    color: var(--color-texto);
    font-size: 0.88rem;
    height: var(--alto-control);
    max-width: 14rem;
}

/* ===========================================================
   LA COLUMNA DEL LOTE (Facturación)

   La celda se dibuja SIEMPRE —la fila la arman el listado y el endpoint del
   refresco automático, y aquél no sabe en qué modo está la pantalla— y lo que
   la muestra es la clase de la tabla. Con la celda condicionada en el PHP, una
   factura que llega sola mientras se está eligiendo entraría con una celda
   menos y la grilla quedaría corrida.
   =========================================================== */
.tabla-datos .col-lote { display: none; }
.tabla-datos.con-lote .col-lote { display: table-cell; width: 34px; text-align: center; }

/* Y LA DE «DOS COPIAS» (186), con el mismo mecanismo y por el mismo motivo.
   Va más ancha porque su encabezado son dos palabras: con 34px el rótulo se
   parte en tres renglones y la fila del `<thead>` crece el doble. */
.tabla-datos .col-copias { display: none; }
.tabla-datos.con-copias .col-copias { display: table-cell; width: 82px; text-align: center; }

/* El pie del lote: aparece con el modo y se lee como un bloque aparte de la
   grilla, que es lo que es — ahí se elige qué se hace con lo elegido. */
.pie-lote {
    border: 1px solid var(--color-borde);
    border-radius: var(--radio);
    background: var(--color-superficie-alt);
    padding: var(--e3) var(--e4);
    margin-bottom: var(--e3);
}
.pie-lote .pie-lote-linea {
    display: flex;
    gap: var(--e3);
    align-items: center;
    flex-wrap: wrap;
}
.pie-lote .pie-lote-linea + .pie-lote-linea { margin-top: var(--e2); }
.pie-lote .campo-check { margin-bottom: 0; }

/* El chip: un botón chato que se prende. `currentColor` en el borde para que
   herede el color del tema, como los iconos de la columna de acciones. */
.chip {
    border: 1px solid var(--color-borde);
    background: var(--color-superficie);
    color: var(--color-texto);
    border-radius: 999px;
    padding: 3px var(--e3);
    font-size: 0.86rem;
    cursor: pointer;
    line-height: 1.4;
}
.chip:hover { background: var(--color-superficie-alt); }
.chip.activo {
    background: var(--color-primario);
    border-color: var(--color-primario);
    color: var(--color-primario-texto);
}
/* Un chip que es un ENLACE (los estados de Hojas de ruta): navega de verdad,
   así que sin esto sale subrayado y con el color de los enlaces. */
a.chip { display: inline-block; text-decoration: none; }
.chips-estado { flex-wrap: wrap; align-items: center; }
/* La cantidad adentro del chip: una pastilla que se lee aparte del rótulo. */
.chip-cantidad {
    display: inline-block;
    min-width: 1.5em;
    margin-left: 4px;
    padding: 0 5px;
    border-radius: 999px;
    background: var(--color-superficie-alt);
    color: var(--color-texto);
    font-size: 0.78rem;
    font-weight: 600;
    text-align: center;
}
.chip.activo .chip-cantidad {
    background: var(--color-primario-texto);
    color: var(--color-primario);
}
/* OTRA FECHA de `partials/chips_fecha.php`: un `<details>` cuyo `<summary>` es
   el chip, así despliega los dos campos sin una línea de JavaScript. Abierto se
   pone en fila, para que las fechas queden al lado del chip y no debajo. */
.chip-otra-fecha > summary { list-style: none; display: inline-block; }
.chip-otra-fecha > summary::-webkit-details-marker { display: none; }
.chip-otra-fecha[open] { display: inline-flex; align-items: center; gap: var(--e2); flex-wrap: wrap; }
.chip-otra-fecha form { display: inline-flex; align-items: center; gap: var(--e1); flex-wrap: wrap; margin: 0; }
.chip-otra-fecha input[type="date"] { width: auto; }
.chip-otra-fecha label { font-size: 0.86rem; color: var(--color-texto-suave); }

/* ===========================================================
   UN CHIP QUE ES UN RADIO (186)

   Se ve igual que `.chip` y por dentro sigue siendo un `<input type="radio">`,
   así que el teclado, el valor del POST y el `required` del navegador salen
   gratis — nada de eso se reescribe en JavaScript.

   EL RADIO SE ESCONDE CON `opacity` Y NUNCA CON `display:none` NI
   `visibility:hidden`: un control `required` que no se dibuja **no es
   enfocable**, y ahí Chrome se niega a enviar el formulario con un «invalid
   form control is not focusable» en la consola y NADA en la pantalla. Estirado
   sobre la etiqueta además hace clickeable el chip entero.
   =========================================================== */
.chip-opcion {
    position: relative;
    display: inline-flex;
    align-items: center;
    margin-bottom: 0;
}
.chip-opcion input {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    margin: 0;
    opacity: 0;
    cursor: pointer;
}
.chip-opcion span {
    border: 1px solid var(--color-borde);
    background: var(--color-superficie);
    color: var(--color-texto);
    border-radius: 999px;
    padding: 3px var(--e3);
    font-size: 0.86rem;
    line-height: 1.4;
    cursor: pointer;
}
.chip-opcion:hover span { background: var(--color-superficie-alt); }
.chip-opcion input:checked + span {
    background: var(--color-primario);
    border-color: var(--color-primario);
    color: var(--color-primario-texto);
}
/* El foco se dibuja sobre el chip, que es lo que se ve: el radio es invisible. */
.chip-opcion input:focus-visible + span {
    outline: 2px solid var(--color-primario);
    outline-offset: 2px;
}
.tabla-datos th.col-web,
.tabla-datos td.col-web { width: 44px; text-align: center; }
.icono-web {
    width: 15px;
    height: 15px;
    display: inline-block;
    vertical-align: -2px;
    stroke: currentColor;
    fill: none;
    stroke-width: 1.8;
    stroke-linecap: round;
    stroke-linejoin: round;
    color: var(--color-exito);
}

/* ===========================================================
   EL NOMBRE QUE ABRE LA FICHA
   (partials/modal_ventana.php)

   Doble clic sobre el nombre abre la ficha en la ventana modal. El subrayado al
   pasar el mouse es lo ÚNICO que lo anuncia: un gesto que no se ve no lo
   descubre nadie, y el que lo descubre de casualidad no sabe dónde más sirve.

   Se subraya al pasar y no siempre: subrayado fijo en doscientas filas se lee
   como una tabla llena de enlaces —que no llevan a ninguna página— y le saca
   peso justamente a la columna que hay que poder recorrer de un vistazo.

   Va sobre la CLASE y no sobre `[data-ficha-url]`, porque son dos cosas
   distintas: el atributo dice DESDE DÓNDE se dispara el gesto —en el catálogo,
   la fila entera— y la clase dice DÓNDE se anuncia. Puesto sobre el atributo, en
   el catálogo se subrayaría la fila completa.
   =========================================================== */
.celda-ficha { cursor: pointer; }
.celda-ficha:hover { text-decoration: underline; }

/* Y LO MISMO PARA LOS ENLACES DE UNA CELDA, EN TODOS LOS LISTADOS.

   El navegador subraya los <a> por defecto y `a { color: ... }` no lo desactiva,
   así que los tres listados que dibujan el nombre del artículo como enlace
   —Tienda web, Stock en negativo, Vencimientos— lo mostraban subrayado SIEMPRE.
   Con doscientas filas eso es una columna entera rayada, que le saca peso justo
   a lo que hay que poder recorrer de un vistazo, y además no se parece en nada a
   la misma columna de las otras pantallas.

   El subrayado se gana al pasar el mouse: hasta entonces alcanza con el color.

   EL HOVER SE ACOTA A LOS ENLACES SIN CLASE, que son los de texto: los que
   llevan una son botones (`.boton-secundario`, `.btn-accion`) y tienen su propio
   estilo — subrayarlos al pasar los haría parecer otra cosa. El
   `text-decoration: none` en cambio va para todos, porque ninguno tiene que
   quedar subrayado fijo. */
.tabla-datos td a { text-decoration: none; }
.tabla-datos td a:not([class]):hover { text-decoration: underline; }

/* ===========================================================
   NAVEGACIÓN POR TECLADO EN LOS LISTADOS
   (partials/navegacion_listado.php)
   =========================================================== */

/* La fila marcada con ↑ ↓. Es el mismo criterio visual que .fila-sel
   del POS: barra a la izquierda y fondo suave, sin mover el layout. */
.tabla-datos tbody tr.fila-sel {
    background: var(--color-primario-suave);
    box-shadow: inset 3px 0 0 var(--color-primario);
}
.tabla-datos tbody tr.fila-sel td { font-weight: 600; }
/* Gana sobre el :hover, si no la fila marcada se "pierde" al pasar el mouse */
.tabla-datos tbody tr.fila-sel:hover { background: var(--color-primario-suave); }

/* La tabla recibe el foco para poder navegarla, pero el anillo del
   navegador alrededor de toda la grilla es ruido: lo que marca dónde
   está el usuario es la fila, no el borde de la tabla. */
.tabla-datos[data-nav-listado]:focus { outline: none; }

/* Ayuda de teclas del listado. Discreta: se lee una vez y se olvida. */
.ayuda-teclas {
    font-size: 0.92rem;
    color: var(--color-texto-tenue);
    margin: 0 0 var(--e2);
    display: flex;
    gap: var(--e3);
    flex-wrap: wrap;
    align-items: center;
}
.ayuda-teclas kbd {
    font-family: var(--fuente-mono);
    font-size: 0.88rem;
    background: var(--color-superficie);
    border: 1px solid var(--color-borde);
    border-bottom-width: 2px;
    border-radius: var(--radio-chico);
    padding: 0 var(--e1);
    margin-right: 2px;
    color: var(--color-texto-suave);
}

@media print {
    .acciones-fila, .ayuda-teclas { display: none; }
}

/* ===========================================================
   VARIANTES DE PRODUCTO (migración 041)

   Cada variante es un artículo propio; lo de acá es la capa de
   presentación que los muestra juntos.
   =========================================================== */

/* --- La muestra de color ---
   Cuadrada y con borde propio: sin el borde, un valor blanco (#FFFFFF)
   sobre la superficie blanca de la tabla es invisible, y "sin color
   cargado" y "el color es blanco" tienen que poder distinguirse. */
.muestra-color {
    display: inline-block;
    width: 18px;
    height: 18px;
    border-radius: var(--radio);
    border: 1px solid var(--color-borde-fuerte);
    vertical-align: middle;
    flex-shrink: 0;
}
.muestra-color-chica { width: 12px; height: 12px; }

/* Sin color cargado: la diagonal dice "no hay dato", que no es un color. */
.muestra-color-vacia {
    background: repeating-linear-gradient(45deg,
        var(--color-superficie-alt) 0 3px, var(--color-borde) 3px 5px);
}

/* --- El generador: elegir valores de un eje --- */
.eje-bloque {
    border-left: 3px solid var(--color-borde);
    padding-left: var(--e3);
}

.chip-valor {
    display: inline-flex;
    align-items: center;
    gap: var(--e1);
    padding: 2px var(--e2);
    border: 1px solid var(--color-borde);
    border-radius: var(--radio);
    background: var(--color-superficie);
    cursor: pointer;
    white-space: nowrap;
    user-select: none;
}
.chip-valor:hover { background: var(--color-superficie-alt); }
.chip-valor input { margin: 0; }
/* :has() no está en todos lados; el fondo del marcado es una ayuda visual,
   no la señal: la señal es la casilla, que se ve igual sin esto. */
.chip-valor:has(input:checked) {
    background: var(--color-primario-suave);
    border-color: var(--color-primario);
}

/* --- La matriz de stock --- */
.tabla-matriz td, .tabla-matriz th { vertical-align: middle; }

.tabla-matriz .matriz-encabezado-fila {
    background: var(--color-superficie-alt);
    font-weight: 600;
    text-align: left;
    white-space: nowrap;
    position: sticky;
    left: 0;
    z-index: 1;
}

/**
 * La esquina: la celda que cruza el encabezado de arriba con el de la izquierda.
 *
 * Es la única que tiene que quedarse quieta en LOS DOS ejes. Sin esto, al correr
 * la matriz a lo ancho el rótulo del eje de las filas se va de la pantalla y
 * quedan cinco colores sin decir de qué son. Va con el z-index más alto de la
 * grilla porque le pasan por debajo las dos tiras de encabezado.
 */
.tabla-matriz thead th:first-child {
    position: sticky;
    left: 0;
    top: 0;
    z-index: 11;
}

/* La celda es alta a propósito: adentro va el stock grande, el código
   chico y a veces una nota. Con la altura de fila normal los tres
   renglones se pisan. */
.matriz-celda {
    height: auto;
    padding: 3px !important;
    text-align: center;
    min-width: 92px;
}

.celda-variante {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 1px;
    padding: var(--e1) var(--e2);
    border-radius: var(--radio);
    text-decoration: none;
    color: inherit;
}
.celda-variante:hover { outline: 2px solid var(--color-primario); }

/* El stock es lo más grande de la celda porque es la pregunta que la
   pantalla contesta: qué falta reponer. */
.celda-stock  { font-size: 1.24rem; font-weight: 700; line-height: 1.1; font-variant-numeric: tabular-nums; }
.celda-codigo { font-size: 0.82rem; color: var(--color-texto-tenue); }
.celda-nota   { font-size: 0.78rem; color: var(--color-error); text-transform: uppercase; letter-spacing: 0.03em; }

.text-upper {text-transform: uppercase;}

/* El semáforo son las etiquetas de estado que ya existen y no una paleta
   nueva para tres casos, por lo mismo que el semáforo de vencimiento vive
   una sola vez en partials/vencimiento.php. */
.celda-con-stock   { background: var(--color-exito-fondo); }
.celda-bajo-minimo { background: var(--color-alerta-fondo); }
.celda-sin-stock   { background: var(--color-error-fondo); }
.celda-eliminada   { background: var(--color-superficie-alt); opacity: 0.6; }

/* Vacía = la combinación NO existe. Se distingue del cero, que existe y
   está en cero: una se resuelve creándola, la otra comprándola. */
.celda-inexistente { background: transparent; }
.celda-vacia { color: var(--color-texto-tenue); }

.leyenda-matriz {
    display: flex;
    flex-wrap: wrap;
    gap: var(--e4);
    font-size: 0.95rem;
    color: var(--color-texto-suave);
}
.leyenda-matriz span { display: inline-flex; align-items: center; gap: var(--e1); }
.cuadro-leyenda {
    display: inline-block;
    width: 14px;
    height: 14px;
    border: 1px solid var(--color-borde-fuerte);
    border-radius: var(--radio-chico);
}

/* --- El listado de artículos agrupado --- */

/* La fila del grupo: es un encabezado, no un artículo. */
.fila-grupo > td {
    background: var(--color-primario-suave);
    font-weight: 600;
}
.fila-grupo .desplegar-grupo {
    background: none;
    border: none;
    padding: 0;
    font: inherit;
    color: var(--color-primario);
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    gap: var(--e2);
}
/* El triángulo gira al abrir. Es CSS y no dos iconos porque el estado lo
   lleva el atributo aria-expanded, que es el que además lee un lector
   de pantalla. */
.fila-grupo .flecha-grupo { transition: transform 0.12s ease; display: inline-block; }
.fila-grupo .desplegar-grupo[aria-expanded="true"] .flecha-grupo { transform: rotate(90deg); }

/* La variante colgando de su grupo: sangrada, para que se lea la jerarquía. */
.fila-variante td:first-child { padding-left: var(--e5); }
.fila-variante .valores-variante { color: var(--color-texto-suave); }

@media print {
    /* Impresa, la jerarquía se pierde igual y las filas ocultas no van. */
    .fila-variante[hidden] { display: none; }
}

/* ===========================================================
   CARGA MASIVA DE FOTOS (Catálogo → Fotos de productos)
   ===========================================================
   Una tarjeta por artículo: el cuadrado de la foto arriba, los
   datos, el archivo y la URL. Cinco por fila en una PC.

   El ancho lo decide `auto-fill` y no un número de columnas
   fijo: la pantalla vive adentro del layout con el menú al
   costado, así que "cinco" depende de cuánto mide el contenido y
   no de cuánto mide la ventana. Con `repeat(5, 1fr)` a 1280px
   entran cinco justas y a 1024 salen cinco apretadas ilegibles.
   =========================================================== */

.fotos-grilla {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
    gap: var(--e3);
}

.foto-tarjeta {
    background: var(--color-superficie);
    border: 1px solid var(--color-borde);
    border-radius: 10px;
    padding: 8px;
}
/* ---- Una tarjeta de fotos SUELTA ----
   La ficha del artículo dibuja UNA tarjeta de `articulos/fotos.php` para
   reusar su JavaScript entero —el velo, la rueda, el pegar-y-sube—, pero
   fuera de la grilla no es una tarjeta: es una columna de la cabecera de un
   panel que ya tiene su propio recuadro. Con el de `.foto-tarjeta` puesto
   quedarían dos bordes, uno adentro del otro.
   El `position: relative` y el velo SÍ se quedan: son lo que hace que la
   tarjeta se pueda tapar mientras la foto viaja. */
.foto-tarjeta-suelta {
    border: 0;
    padding: 0;
    background: none;
}
.foto-tarjeta-suelta .foto-velo { border-radius: var(--radio-chico); }

/* El contenido de la tarjeta va en su propia caja: es la que se desenfoca
   mientras sube, sin llevarse puestos al velo ni a la rueda. */
.foto-envoltorio {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

/* ---- La tarjeta mientras sube ----
   El velo va sobre la TARJETA ENTERA y no sólo sobre el cuadrado: lo que
   está pasando es que ese producto está ocupado, y con los campos de abajo
   nítidos parecen usables mientras el JavaScript ya los deshabilitó.

   El desenfoque va sobre el CONTENIDO (`.foto-envoltorio`) y no sobre la
   tarjeta: `filter` en el padre desenfocaría también al velo y a la rueda,
   que son justo lo que tiene que verse nítido. */
.foto-tarjeta { position: relative; }

.foto-velo {
    position: absolute;
    inset: 0;
    z-index: 2;
    display: none;
    align-items: center;
    justify-content: center;
    flex-direction: column;
    gap: 10px;
    border-radius: 10px;
    background: rgba(var(--velo-rgb), 0.55);
    text-align: center;
    padding: 10px;
}
.foto-tarjeta.ocupada .foto-velo { display: flex; }

/* Un toque nada más: con más, la tarjeta deja de reconocerse y no se sabe
   cuál de las cuarenta está trabajando. */
.foto-tarjeta.ocupada .foto-envoltorio {
    filter: blur(2px);
    pointer-events: none;
}

/* La misma animación que el velo de espera del sistema: dos ruedas girando
   a velocidades distintas en la misma app se notan. */
.foto-rueda {
    width: 30px;
    height: 30px;
    border: 3px solid var(--color-borde-fuerte);
    border-top-color: var(--color-texto);
    border-radius: 50%;
    animation: espera-girar 0.9s linear infinite;
}
.foto-velo-texto { font-size: 11.5px; color: var(--color-texto); line-height: 1.3; }

@media (prefers-reduced-motion: reduce) {
    .foto-rueda { animation: none; }
}

/* ---- El cuadrado ----
   `aspect-ratio` y no un alto fijo: la tarjeta tiene que ser casi
   cuadrada a cualquier ancho de columna. Y va SIEMPRE, con foto o
   sin ella — un hueco que aparece y desaparece reacomodaría la
   grilla entera cada vez que sube una imagen, y con cuarenta
   tarjetas eso es perder de vista en cuál se estaba. */
.foto-cuadro {
    position: relative;
    aspect-ratio: 1 / 1;
    border-radius: 8px;
    overflow: hidden;
    background: var(--color-fondo);
    display: flex;
    align-items: center;
    justify-content: center;
}

.foto-cuadro img {
    width: 100%;
    height: 100%;
    /* `contain` y no `cover`: en un catálogo la foto suele venir con
       fondo blanco y recortarla le come el producto. */
    object-fit: contain;
    display: block;
}

.foto-cuadro [data-sin-foto] {
    font-size: 12px;
    color: var(--color-texto-tenue);
    letter-spacing: 0.03em;
    text-transform: uppercase;
}

/* El estado tapa el cuadrado mientras la foto viaja: es donde el
   ojo ya está mirando, así que no hay que buscarlo en otro lado. */
.foto-estado {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    text-align: center;
    padding: 8px;
    font-size: 12px;
    line-height: 1.35;
    background: rgba(var(--velo-rgb), 0.92);
}
.foto-estado[hidden] { display: none !important; }
.foto-estado.trabajando { color: var(--color-texto); }
.foto-estado.error { color: var(--color-error); background: var(--color-error-fondo); }
/* Ámbar y no rojo: la foto SÍ se guardó, sólo que en la versión chica porque el
   sitio de origen no deja bajar la grande. En rojo se leería como que falló y el
   usuario la borraría; en gris no se leería. */
.foto-estado.aviso { color: var(--color-alerta); background: var(--color-alerta-fondo); }

/* ---- Los datos ---- */

/* Alto FIJO, no mínimo. Es lo mismo que hace `.btn-accion` con sus 26px
   cuadrados: lo que alinea una grilla es que la caja mida siempre igual.
   Con `min-height`, la tarjeta sin marca sube el botón y la fila deja de
   leerse como una fila. */
.foto-datos {
    height: 64px;
    overflow: hidden;
}

.foto-codigo {
    font-size: 11px;
    color: var(--color-texto-tenue);
    font-family: monospace;
}

/* Dos renglones y corta: con nombres de largo distinto, las
   tarjetas de una fila dejarían de tener la misma altura y el
   ojo pierde la grilla. */
.foto-nombre {
    font-size: 12.5px;
    font-weight: 600;
    line-height: 1.25;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.foto-marca {
    font-size: 11px;
    color: var(--color-texto-tenue);
    /* Un renglón: una marca larga empujaría el botón fuera de la caja. */
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* ---- Los dos caminos de carga ---- */

/* El `<input type="file">` de verdad se esconde y el `<label>` hace
   de botón: el control nativo trae el texto "Sin archivos
   seleccionados" en el idioma del navegador y no se puede acortar,
   así que en una tarjeta de 190px desborda siempre. */
.foto-archivo {
    display: block;
    text-align: center;
    font-size: 12px;
    font-weight: 600;
    padding: 7px 8px;
    border: 1px solid var(--color-borde);
    border-radius: 8px;
    cursor: pointer;
    background: var(--color-superficie-alt);
}
.foto-archivo:hover { border-color: var(--color-texto-tenue); }
.foto-archivo input { display: none; }

.foto-url {
    width: 100%;
    font-size: 12px;
    padding: 6px 8px;
    border: 1px solid var(--color-borde);
    border-radius: 8px;
    /* Una URL larga no puede ensanchar la tarjeta. */
    min-width: 0;
}

.foto-quitar {
    border: 0;
    background: none;
    color: var(--color-texto-tenue);
    font-size: 11.5px;
    text-decoration: underline;
    cursor: pointer;
    padding: 2px;
}
.foto-quitar:hover { color: var(--color-error); }
.foto-quitar[hidden] { display: none !important; }

/* ---- La lupa: buscar la foto en internet ----

   El botón de archivo y la lupa comparten renglón. El de archivo
   se lleva lo que sobra y LA LUPA TIENE ANCHO FIJO: repartiéndose
   el espacio, una tarjeta con lupa y otra sin ella —que es lo que
   pasa mientras la función está apagada— dejarían el botón de
   archivo de dos tamaños distintos en la misma fila.

   CUADRADA CON `aspect-ratio` Y NO CON `stretch`, y está medido:
   con `stretch` mide el alto del label, y en una tarjeta de
   celular "Elegir archivo" se parte en dos renglones — o sea que
   el botón salía 44×50. El alto sale del ancho y no de lo que
   haga el vecino, así que no hay ningún largo de texto ni cuerpo
   de letra que lo pueda descalzar. */
.foto-carga {
    display: flex;
    gap: 6px;
    align-items: center;
}
.foto-carga .foto-archivo { flex: 1 1 auto; min-width: 0; }

.foto-lupa {
    flex: 0 0 auto;
    width: 33px;
    aspect-ratio: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    border: 1px solid var(--color-borde);
    border-radius: 8px;
    background: var(--color-superficie-alt);
    color: var(--color-texto-suave);
    cursor: pointer;
}
.foto-lupa:hover { border-color: var(--color-texto-tenue); color: var(--color-primario); }
/* El trazo desde el CSS y el color heredado con `currentColor`, como los
   iconos de la columna de acciones: así el hover pinta el dibujo sin que el
   SVG sepa de colores. */
.foto-lupa svg {
    width: 16px; height: 16px;
    fill: none;
    stroke: currentColor;
    stroke-width: 2;
    stroke-linecap: round;
}

/* ---- El modal de la búsqueda ---- */

/* `relative` es lo que hace del modal el contexto del velo: sin esto, el velo
   se cuelga del primer ancestro posicionado —que puede ser el `<body>`— y
   taparía la pantalla entera en vez de la caja. */
.mbf { position: relative; }

/* EL SCROLL VA EN EL CUERPO Y NO EN `.mbf`, así el título y la ✕ quedan fijos
   arriba cuando la lista no entra. Con una pantalla baja —una notebook con la
   barra de tareas puesta— las cuatro opciones más el buscador no entran, y sin
   esto la caja crece hacia abajo y la última fila queda inalcanzable: el modal
   no scrollea y la página de atrás tampoco lo mueve. Es lo que le pasaba al
   panel del menú abajo del pliegue.

   El descuento son el alto del encabezado más el margen del velo de fondo. */
.mbf-cuerpo { max-height: calc(88vh - 4rem); overflow-y: auto; }

/* ---- El velo del modal: lo que tarda se dice EN EL MEDIO ---- */
.mbf-velo {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 10px;
    /* Claro y con desenfoque, como el de la tarjeta: lo que hay abajo sigue
       siendo el modal en el que se estaba y se tiene que reconocer. */
    background: rgba(var(--velo-rgb), 0.86);
    backdrop-filter: blur(1.5px);
    border-radius: var(--radio);
    z-index: 1;
}
/* `[hidden]` viene de la hoja de usuario del navegador y pierde contra el
   `display: flex` de acá arriba — la misma trampa que dejaba pegado el velo
   del panel de administración. */
.mbf-velo[hidden] { display: none !important; }
.mbf-velo-texto { font-size: 13px; color: var(--color-texto); }

.mbf-buscador { display: flex; gap: 8px; margin-bottom: 10px; }
.mbf-buscador input {
    flex: 1 1 auto;
    min-width: 0;
    padding: 8px 10px;
    font-size: 14px;
    border: 1px solid var(--color-borde);
    border-radius: 8px;
}

.mbf-articulo {
    margin: 0 0 10px;
    font-size: 12px;
    color: var(--color-texto-suave);
    /* Un renglón: una descripción larga empujaría la grilla hacia abajo y
       la cuarta opción quedaría fuera de la pantalla. */
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

.mbf-estado {
    padding: 10px 0;
    font-size: 13px;
    color: var(--color-texto);
}
.mbf-estado.error { color: var(--color-error); }
.mbf-estado[hidden] { display: none !important; }

/* DOS POR DOS, también en el celular: con una sola columna hay que
   scrollear para comparar, y comparar es exactamente lo que se
   viene a hacer acá. */
.mbf-grilla {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 10px;
}

.mbf-op {
    display: flex;
    flex-direction: column;
    gap: 4px;
    padding: 6px;
    border: 1px solid var(--color-borde);
    border-radius: 10px;
    background: var(--color-superficie);
    cursor: pointer;
    text-align: center;
}
.mbf-op:hover  { border-color: var(--color-primario); background: var(--color-primario-suave); }
.mbf-op:focus-visible { outline: 2px solid var(--color-primario); outline-offset: 2px; }
.mbf-op:disabled { opacity: 0.5; cursor: default; }

/* LA MINIATURA LLEVA ALTO FIJO Y NO `aspect-ratio`.
   Medido: con `aspect-ratio: 1` en una grilla de dos columnas, cada opción
   quedaba de ~340px de lado y LA SEGUNDA FILA CAÍA FUERA DE LA PANTALLA — o
   sea que el 2×2 no se veía entero, que es justamente para lo que existe.
   Con el alto fijo, las cuatro entran juntas y se pueden comparar de un
   vistazo, que es lo único que se viene a hacer acá. */
.mbf-op img {
    width: 100%;
    height: 150px;
    /* `contain` y no `cover`: la foto de catálogo casi siempre viene con
       fondo blanco y recortarla le come el producto — que es lo único que
       hay que mirar para elegir. Es el mismo criterio que `.foto-cuadro`. */
    object-fit: contain;
    background: var(--color-superficie-alt);
    border-radius: 6px;
}

.mbf-op-pie {
    font-size: 10.5px;
    color: var(--color-texto-tenue);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* ---- Celular ----
   Dos por fila: con una sola, cargar veinte fotos son veinte
   pantallas de scroll; con tres, el nombre no se lee. */
@media (max-width: 700px) {
    .fotos-grilla { grid-template-columns: repeat(2, 1fr); gap: var(--e2); }
    /* 16px o iOS hace zoom solo al enfocar el campo. */
    .foto-url { font-size: 16px; }
    .mbf-buscador input { font-size: 16px; }
    /* La lupa es lo único que se toca con el dedo en esta fila: 44px es el
       piso con el que no se erra el botón de al lado. */
    .foto-lupa { width: 44px; }
}

/* ===========================================================
   LOS SELECTORES DE LA TIENDA, CON VISTA PREVIA (088)
   ===========================================================
   Eran dos desplegables que decían "Grafito" y "Cormorant +
   Montserrat": la única forma de saber cómo quedaba era guardar
   y abrir la tienda, y con ocho paletas eso son ocho viajes.

   Son radios y no un `<select>` porque un desplegable sólo
   puede mostrar TEXTO: la muestra tiene que estar a la vista al
   lado de cada opción, no adentro de una lista que se cierra al
   elegir. Y así la pantalla sigue andando sin JavaScript.
   =========================================================== */
.tienda-opciones {
    display: flex;
    flex-wrap: wrap;
    gap: var(--e2);
    margin-top: var(--e1);
}

/* El radio de verdad sigue existiendo —es lo que viaja en el POST y lo
   que mueve el teclado— pero no se dibuja: lo que se ve es la tarjeta.
   `opacity: 0` y no `display: none`, que lo sacaría del recorrido con
   Tab y dejaría el selector inalcanzable sin mouse. */
.tienda-opcion { position: relative; cursor: pointer; }

.tienda-opcion input[type="radio"] {
    position: absolute;
    opacity: 0;
    width: 0;
    height: 0;
}

.tienda-opcion-caja {
    display: flex;
    flex-direction: column;
    gap: 6px;
    padding: 10px;
    border: 2px solid var(--color-borde);
    border-radius: var(--radio-chico);
    background: var(--color-superficie);
    transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.tienda-opcion:hover .tienda-opcion-caja { border-color: var(--color-borde-fuerte); }

.tienda-opcion input:checked + .tienda-opcion-caja {
    border-color: var(--color-primario);
    box-shadow: 0 0 0 3px var(--color-primario-suave);
}

/* El anillo de foco va en la CAJA y no en el radio escondido: si no,
   quien navega con Tab no ve dónde está parado. */
.tienda-opcion input:focus-visible + .tienda-opcion-caja {
    outline: 2px solid var(--color-primario);
    outline-offset: 2px;
}

.tienda-opcion-nombre {
    font-size: 0.82rem;
    color: var(--color-texto-suave);
    text-align: center;
}

.tienda-opcion input:checked + .tienda-opcion-caja .tienda-opcion-nombre {
    color: var(--color-texto);
    font-weight: 600;
}

/* ---- La muestra de colores ----
   Los colores salen de la hoja del modelo, leída en el servidor: por eso
   van como `style` inline y no como clases. */
.tienda-muestra {
    display: flex;
    width: 108px;
    height: 34px;
    border-radius: 6px;
    overflow: hidden;
}

.tienda-color { flex: 1; }

/* ---- La muestra de tipografía ---- */
.tienda-opciones-letras .tienda-opcion-caja { width: 168px; }

.tienda-letras-aa {
    font-size: 26px;
    line-height: 1.1;
    color: var(--color-texto);
}

.tienda-letras-texto {
    font-size: 12px;
    color: var(--color-texto-suave);
    /* Un nombre largo no puede ensanchar la tarjeta y desalinear la fila. */
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* ---- La muestra de la variante ----
   Tres rectangulitos que son las tarjetas del catálogo: es donde se nota
   la diferencia entre las dos, borde y esquina recta contra sombra y
   esquina redonda. */
.tienda-variante-muestra {
    display: flex;
    gap: 5px;
    width: 108px;
    height: 34px;
}

.tienda-variante-muestra span {
    flex: 1;
    background: var(--color-superficie-alt);
}

.tienda-variante-muestra.variante-clasico span {
    border: 1px solid var(--color-borde-fuerte);
    border-radius: 2px;
}

.tienda-variante-muestra.variante-moderno span {
    border: 0;
    border-radius: 8px;
    box-shadow: 0 1px 2px rgba(var(--sombra-rgb), 0.10), 0 3px 8px rgba(var(--sombra-rgb), 0.07);
}

/**
 * LA PASTILLA DEL LOGO DEL COMERCIO.
 *
 * El archivo lo manda el cliente y puede venir de cualquier forma: con fondo
 * blanco opaco o con transparencia, con tinta oscura o clara. En modo oscuro
 * los dos casos fallan si el logo va suelto, y fallan al revés uno del otro —el
 * de fondo blanco queda como un parche, y el transparente con tinta oscura
 * directamente desaparece—. No hay archivo que arregle los dos, así que lo que
 * se garantiza es el fondo.
 *
 * Claro en los DOS temas, por eso `--color-sobre-oscuro` y no
 * `--color-superficie`: es la misma familia que lo que va sobre la barra.
 */
.pastilla-logo {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 6px 10px;
    background: var(--color-sobre-oscuro);
    border: 1px solid var(--color-borde);
    border-radius: var(--radio-chico);
}

/* ---- El logo y la portada que ya están cargados ---- */
.tienda-logo-actual,
.tienda-hero-actual {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    margin-bottom: var(--e1);
    padding: 8px;
    border: 1px solid var(--color-borde);
    border-radius: var(--radio-chico);
    /* Un tablero de ajedrez claro: es lo que deja ver que un logo con
       fondo transparente DE VERDAD lo tiene. Sobre blanco liso, un PNG
       con fondo blanco y uno transparente se ven idénticos. */
    background-image:
        linear-gradient(45deg, var(--color-fondo) 25%, transparent 25%),
        linear-gradient(-45deg, var(--color-fondo) 25%, transparent 25%),
        linear-gradient(45deg, transparent 75%, var(--color-fondo) 75%),
        linear-gradient(-45deg, transparent 75%, var(--color-fondo) 75%);
    background-size: 12px 12px;
    background-position: 0 0, 0 6px, 6px -6px, -6px 0;
}

.tienda-logo-actual img { max-height: 56px; max-width: 220px; width: auto; }
.tienda-hero-actual img { max-height: 90px; max-width: 320px; width: auto; border-radius: 4px; }


/* ===========================================================
   EL PANEL DE OPINIONES (094)
   ===========================================================
   Tarjetas y no una grilla: cada opinión es un texto que hay
   que LEER antes de decidir, y una tabla con el comentario
   metido en una celda no se lee. Es el mismo criterio por el
   que el detalle de un pedido no es una fila más.
   =========================================================== */

.opiniones-panel {
    display: grid;
    /* Una columna hasta que la pantalla dé para dos: un comentario a lo ancho
       de 1400px se lee peor que uno acotado. */
    grid-template-columns: repeat(auto-fill, minmax(380px, 1fr));
    gap: var(--e3);
}

.opinion-tarjeta {
    background: var(--color-superficie);
    border: 1px solid var(--color-borde);
    border-radius: var(--radio);
    padding: var(--e3) var(--e4);
    display: flex;
    flex-direction: column;
    gap: var(--e2);
}

.opinion-tarjeta > header {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--e2);
}

.opinion-puntajes {
    display: flex;
    flex-wrap: wrap;
    gap: var(--e4);
    font-size: 0.95rem;
    color: var(--color-texto-suave);
}

.opinion-texto {
    margin: 0;
    /* Se despega con una línea al costado y no con comillas dibujadas: es lo
       que dice "esto lo escribió otra persona" sin agregar tipografía. */
    border-left: 3px solid var(--color-borde-fuerte);
    padding-left: var(--e3);
}

.opinion-productos {
    margin: 0;
    padding: 0;
    list-style: none;
    display: flex;
    flex-direction: column;
    gap: var(--e1);
    font-size: 0.95rem;
}

.opinion-productos li {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--e2);
}

.opinion-productos span { color: var(--color-texto-suave); }

/* Las estrellas del panel: las llenas en el color de alerta —que es el ámbar
   del sistema— y las vacías atenuadas dentro del mismo texto, así el ancho no
   cambia entre una de 2 y una de 5 y la columna sigue alineando. */
.estrellas-panel {
    color: var(--color-alerta);
    letter-spacing: 0.06em;
    white-space: nowrap;
}

.estrellas-panel span { color: var(--color-borde-fuerte); }

/* =====================================================================
   CAMPOS BLOQUEADOS: un <fieldset> usado SÓLO por su `disabled`
   =====================================================================
   `disabled` en un fieldset apaga todos los campos de adentro de una —y hace
   que NO VIAJEN en el POST, que es defensa de más—, pero el elemento trae su
   propio dibujo: borde, relleno y márgenes. Esta clase lo saca.

   `min-inline-size: 0` NO ES OPCIONAL: el valor de fábrica es `min-content`, y
   con una tabla ancha adentro —la grilla de precios— el fieldset se niega a
   achicarse y desborda el panel.

   Se usa donde la pantalla muestra datos que hay que poder LEER pero no tocar,
   como la ficha del artículo sin permiso de edición. Para esconder algo va
   `hidden`; esto es para dejarlo a la vista y apagado.
   ===================================================================== */
.campos-bloqueados {
    border: 0;
    padding: 0;
    margin: 0;
    min-inline-size: 0;
}

/* =====================================================================
   EL ALTA DE ARTÍCULOS POR AUDIO  (public/articulos/_audio.php)
   =====================================================================
   Un panel arriba del formulario, no un modal: lo que hace es PRECARGAR los
   campos de abajo, así que el usuario tiene que poder verlos mientras dicta y
   leer la transcripción al lado de lo que quedó cargado. En un modal habría que
   cerrarlo para comprobar si acertó.

   Reusa `espera-girar`, la animación que ya existe: dos ruedas girando a
   velocidades distintas en el mismo sistema se notan. */

.audio-alta {
    border: 1px solid var(--color-borde);
    border-left: 3px solid var(--color-primario);
    border-radius: var(--radio-chico);
    background: var(--color-superficie-alt);
    padding: var(--e3) var(--e4);
    margin-bottom: var(--e4);
}

.audio-alta-barra {
    display: flex;
    align-items: center;
    gap: var(--e3);
    flex-wrap: wrap;
}

.audio-alta-boton {
    display: inline-flex;
    align-items: center;
    gap: var(--e2);
}

.audio-alta-icono { display: inline-flex; }

/* Grabando: el botón se pone rojo. Es el único color de esta pantalla que
   significa algo —"esto está tomando audio ahora"— y por eso no sale de la
   paleta del panel. */
.audio-alta-boton.audio-alta-grabando {
    background: var(--color-error);
    border-color: var(--color-error);
}

.audio-alta-estado {
    display: flex;
    align-items: center;
    gap: var(--e2);
    font-variant-numeric: tabular-nums;
    color: var(--color-texto-suave);
}

.audio-alta-punto {
    width: 9px;
    height: 9px;
    border-radius: 50%;
    background: var(--color-error);
    animation: audio-alta-latir 1s ease-in-out infinite;
}

@keyframes audio-alta-latir { 50% { opacity: 0.25; } }

/* El medidor de nivel es un adorno con una función: dice que el micrófono está
   tomando algo. Sin él, un micrófono silenciado se ve igual que uno que anda, y
   el usuario se entera cuando la transcripción vuelve vacía. */
.audio-alta-nivel {
    display: block;
    width: 70px;
    height: 5px;
    border-radius: 3px;
    background: var(--color-borde);
    overflow: hidden;
}

.audio-alta-nivel i {
    display: block;
    height: 100%;
    width: 0;
    background: var(--color-primario);
    transition: width 0.08s linear;
}

.audio-alta-ayuda {
    margin: 0;
    font-size: 12px;
    color: var(--color-texto-suave);
    flex: 1 1 260px;
}

.audio-alta-listo,
.audio-alta-esperando {
    display: flex;
    align-items: center;
    gap: var(--e2);
    margin-top: var(--e3);
}

.audio-alta-duracion {
    color: var(--color-texto-suave);
    font-variant-numeric: tabular-nums;
    margin-right: auto;
}

.audio-alta-rueda {
    width: 16px;
    height: 16px;
    border: 2px solid var(--color-borde-fuerte);
    border-top-color: var(--color-primario);
    border-radius: 50%;
    animation: espera-girar 0.9s linear infinite;
}

.audio-alta-aviso {
    margin: var(--e3) 0 0;
    color: var(--color-alerta);
    background: var(--color-alerta-fondo);
    border-radius: var(--radio);
    padding: var(--e2) var(--e3);
}

.audio-alta-resultado { margin-top: var(--e3); }

.audio-alta-transcripcion {
    margin: 0 0 var(--e2);
    font-style: italic;
    color: var(--color-texto-suave);
}

.audio-alta-avisos {
    margin: 0 0 var(--e2);
    padding-left: var(--e5);
    color: var(--color-alerta);
    font-size: 13px;
}

.audio-alta-parecidos {
    border: 1px solid var(--color-alerta);
    background: var(--color-alerta-fondo);
    border-radius: var(--radio);
    padding: var(--e2) var(--e3);
}

.audio-alta-parecidos-titulo {
    margin: 0 0 var(--e1);
    font-weight: 600;
    color: var(--color-alerta);
}

.audio-alta-parecidos ul {
    margin: 0;
    padding-left: var(--e5);
    font-size: 13px;
}

/* =====================================================================
   LOS BLOQUES OPCIONALES DEL ALTA (precio de venta y stock inicial)
   ===================================================================== */

.bloque-alta {
    border: 1px solid var(--color-borde);
    border-radius: var(--radio-chico);
    padding: var(--e2) var(--e4) var(--e3);
    margin-bottom: var(--e4);
    /* El valor por defecto de un fieldset es `min-content`, así que sin esto se
       niega a achicarse y desborda el formulario. Es la misma trampa que ya
       documenta `.campos-bloqueados`. */
    min-inline-size: 0;
}

.bloque-alta legend {
    font-weight: 600;
    color: var(--color-texto-suave);
    padding: 0 var(--e2);
}

/* Apagado por una regla del propio formulario —maneja lotes, o no controla
   stock—. Se atenúa en vez de esconderse: un bloque que desaparece al tildar
   una casilla se lee como que la pantalla se rompió, y acá lo que hay que
   entender es que ese stock se carga en otro lado. */
.bloque-alta.bloque-apagado { opacity: 0.55; }

/* Lo que precargó el audio, hasta que alguien lo toca. Es lo único que
   distingue "esto lo escuchó el sistema" de "esto lo escribí yo", y se limpia
   al primer tecleo: una marca que se queda pegada deja de decir nada. */
.campo-dictado {
    background: var(--color-primario-suave);
    border-color: var(--color-primario);
}

/* ===========================================================
   EL SIGNO DE PREGUNTA DE LA BARRA
   Abre la ayuda de la pantalla en la que se está parado.

   Hermano del burger: mismo tratamiento sobre la barra oscura,
   mismo piso de 44px —que es con lo que un dedo acierta sin
   pensarlo— y `flex-shrink: 0`, sin el cual la fila lo
   comprime en una pantalla angosta y el círculo sale ovalado.
   =========================================================== */
/* --- Un botón de icono de la barra superior (119) ---
 *
 * Es el molde de `.boton-ayuda` —mismo cuadrado, mismo trazo— y no la misma
 * clase, porque se esconden en anchos OPUESTOS: aquélla desaparece en el
 * celular (el media query de acá abajo) y ésta en escritorio, con
 * `.solo-movil` puesta por quien la usa. Compartirlas obligaría a deshacer una
 * de las dos con una excepción, que es como una hoja se vuelve ilegible.
 *
 * SIRVE PARA ENLACES Y PARA BOTONES: `text-decoration: none` y `color:
 * inherit` porque un `<a>` los trae de la hoja de USUARIO del navegador, que
 * gana sobre lo que no se declare. Sin eso el icono sale subrayado y azul
 * sobre la barra oscura, que es donde casi no se ve. */
.boton-barra {
    background: rgba(var(--velo-barra-rgb), 0.14);
    border: 1px solid rgba(var(--velo-barra-rgb), 0.35);
    color: inherit;
    text-decoration: none;
    border-radius: var(--radio);
    min-width: 34px; min-height: 34px;
    display: flex; align-items: center; justify-content: center;
    cursor: pointer;
    padding: 0;
    flex-shrink: 0;
    margin-left: var(--e2);
    /* El ancla de los dos avisos que se dibujan ENCIMA del icono: el punto de
       "queda algo sin subir" y el globo con el número de novedades. Ver
       `.punto-pendientes` y `.globo-novedades`. */
    position: relative;
}
.boton-barra:hover { background: var(--color-barra-hover); }
.boton-barra svg {
    width: 20px; height: 20px;
    stroke: currentColor; stroke-width: 1.8;
    fill: none; stroke-linecap: round; stroke-linejoin: round;
}

/* EL PUNTO DE "QUEDA ALGO SIN SUBIR", sobre el icono de sincronizar.
 *
 * Reemplaza a la cinta celeste que vivía arriba del catálogo del POS móvil: era
 * lo único que esa franja decía y que no estuviera dicho en otro lado, y un
 * renglón entero para un dato binario es caro en una pantalla de 390 píxeles.
 *
 * VA CON `border` DEL COLOR DE LA BARRA y no pelado: el punto se dibuja sobre el
 * borde claro del botón, y sin el anillo los dos se tocan y se lee como una
 * mancha. Es lo mismo que hace el globo de notificaciones de cualquier app.
 *
 * `--color-error` y no el ámbar: lo que avisa es que hay trabajo que la
 * oficina todavía no tiene, y eso en este sistema siempre se dijo en rojo. */
.punto-pendientes {
    position: absolute;
    top: -3px; right: -3px;
    width: 10px; height: 10px;
    border-radius: 50%;
    background: var(--color-error);
    border: 2px solid var(--color-barra);
    box-sizing: content-box;
}

/* EL GLOBO CON EL NÚMERO DE NOVEDADES SIN LEER, sobre el icono de la campana.
 *
 * LLEVA EL NÚMERO Y NO UN PUNTO, al revés que el de al lado, y la diferencia es
 * lo que cada uno dice: "queda algo sin subir" es binario —hay que apretar
 * sincronizar, sean uno o catorce— y acá el número ES la información: tres
 * cambios de precio se miran cuando se puede y veinte se miran ahora.
 *
 * `min-width` con `padding` horizontal para que el 1 salga redondo y el 12
 * ovalado, sin dos reglas. De 100 en adelante dice "99+": el globo no puede
 * crecer más que el icono que lo lleva.
 *
 * MISMO `border` DEL COLOR DE LA BARRA que el punto de al lado: se dibuja sobre
 * el borde claro del botón y sin el anillo los dos se tocan y se lee como una
 * mancha. */
.globo-novedades {
    position: absolute;
    top: -6px; right: -6px;
    min-width: 16px; height: 16px;
    padding: 0 4px;
    border-radius: 9px;
    background: var(--color-error);
    border: 2px solid var(--color-barra);
    box-sizing: content-box;
    color: var(--color-error-texto);
    font-size: 11px; font-weight: 700; line-height: 16px;
    text-align: center;
    white-space: nowrap;
}
.boton-ayuda {
    background: rgba(var(--velo-barra-rgb), 0.14);
    border: 1px solid rgba(var(--velo-barra-rgb), 0.35);
    color: inherit;
    border-radius: var(--radio);
    min-width: 34px; min-height: 34px;
    display: flex; align-items: center; justify-content: center;
    cursor: pointer;
    padding: 0;
    flex-shrink: 0;
    margin-right: var(--e2);
}
.boton-ayuda:hover { background: var(--color-barra-hover); }
.boton-ayuda svg {
    width: 20px; height: 20px;
    stroke: currentColor; stroke-width: 1.8;
    fill: none; stroke-linecap: round; stroke-linejoin: round;
}
/* El punto del signo se pinta, no se traza: a este tamaño un círculo de 0,9 de
   radio dibujado con línea sale como un anillo hueco y no se lee como un punto. */
.boton-ayuda svg .punto { fill: currentColor; stroke: none; }

/* En el celular la barra tiene apenas el ancho para el burger y la marca, así
   que el `?` va al fondo del cajón del menú, junto al usuario. Es la misma
   decisión que ya se tomó con los accesos rápidos. */
@media (max-width: 700px) {
    .barra-superior .boton-ayuda { display: none; }
}

/* --- El icono de "sin señal" de la barra superior ---
 *
 * ES UN INDICADOR Y NO UN BOTÓN, así que NO lleva el recuadro de `.boton-barra`
 * ni el de `.boton-ayuda`: sobre la barra oscura un cuadrado con borde se lee
 * como algo que se puede apretar, y esto no hace nada al tocarlo.
 *
 * EL ROJO ES PROPIO Y NO `--color-error`, y no es un gusto: aquél es `#a3231c`,
 * un rojo pensado para TEXTO SOBRE PAPEL BLANCO, y sobre el `#1c4e73` de la
 * barra queda casi negro — o sea que el icono dejaría de leerse como una
 * alerta, que es lo único que tiene que hacer. Éste es el mismo rojo aclarado
 * para fondo oscuro.
 *
 * `flex-shrink: 0` por lo mismo que sus dos hermanos: sin él, una pantalla
 * angosta lo comprime y el dibujo sale ovalado.
 *
 * El alto de 34px lo iguala con los otros dos iconos de la barra, así que
 * aparecer y desaparecer no le cambia la altura a la fila.
 */
.icono-sin-senal {
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--color-error-sobre-oscuro);
    flex-shrink: 0;
    min-width: 34px;
    min-height: 34px;
    margin-left: var(--e2);
}
.icono-sin-senal svg {
    width: 22px; height: 22px;
    stroke: currentColor; stroke-width: 1.9;
    fill: none; stroke-linecap: round; stroke-linejoin: round;
}
/* El punto se pinta y no se traza: a este tamaño un círculo de 1,1 de radio
   dibujado con línea sale como un anillo hueco. Es lo mismo que ya hace el
   punto del signo de pregunta, tres reglas más arriba. */
.icono-sin-senal svg .punto { fill: currentColor; stroke: none; }
/* Y el tachado va más grueso: es LO ÚNICO que distingue "sin señal" de "con
   señal", y a 22px una diagonal del mismo trazo que los arcos se pierde entre
   ellos. */
.icono-sin-senal svg .tachado { stroke-width: 2.4; }

/* ===========================================================
   FORMULARIO A DOS COLUMNAS

   Para las pantallas de configuración largas —Empresa es la
   primera— donde el formulario angosto de 520px obliga a
   scrollear ocho secciones que se miran de a una.

   ES UN SOLO `<form>`: las columnas son dos contenedores
   adentro, así que se guarda todo junto como siempre y no hay
   dos POST que puedan quedar a medias.

   `align-items: start` no es opcional: sin él, el grid estira
   las dos columnas a la altura de la más alta y la corta queda
   con su recuadro vacío colgando hasta abajo.
   =========================================================== */
.form-2col {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--e5);
    align-items: start;
}

/* Abajo de 900px las dos columnas se apilan y queda el orden de
   siempre: primero toda la izquierda, después toda la derecha.
   Por eso lo que va en cada una se elige por temas y no para
   emparejar alturas — apilado, el orden se tiene que seguir
   leyendo. */
@media (max-width: 900px) {
    .form-2col { grid-template-columns: 1fr; gap: var(--e4); }
}

/* La explicación larga, plegada.

   Los textos de ayuda de estas pantallas son la mitad del alto y
   se leen UNA vez —cuando se configura— y nunca más. Plegados
   siguen estando: `<details>` no los esconde, los guarda. */
.ayuda-plegada {
    margin: calc(var(--e2) * -1) 0 var(--e3);
    font-size: 1.06rem;
    color: var(--color-texto-suave);
}
.ayuda-plegada > summary {
    cursor: pointer;
    color: var(--color-primario);
    padding: var(--e1) 0;
    /* El marcador propio del navegador cambia de forma entre uno y
       otro; con `list-style` en none y el triángulo escrito, se ve
       igual en todos. */
    list-style: none;
}
.ayuda-plegada > summary::-webkit-details-marker { display: none; }
.ayuda-plegada > summary::before {
    content: '▸ ';
    display: inline-block;
    transition: transform .15s;
}
.ayuda-plegada[open] > summary::before { transform: rotate(90deg); }
.ayuda-plegada p { margin: var(--e2) 0 0; }

/* ===========================================================
   FLYERS PUBLICITARIOS (Catálogo → Flyers)

   ESTO SE PARECE A `.tienda-opcion` Y NO LO REUSA, a propósito:
   comparten el mecanismo —el radio escondido, la caja, la marca
   del elegido— y no comparten NINGÚN dato ni el layout (allá son
   cuadraditos de muestra en una fila que envuelve, acá son
   tarjetas de texto en una grilla). Es el mismo criterio con el
   que conviven las dos fichas de dos modos: dos cosas que se
   parecen. EL DÍA QUE APAREZCA UNA TERCERA hay que sacar el
   mecanismo a una clase neutra y hacer que las tres la usen.
   =========================================================== */

.flyer-estilos {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(170px, 1fr));
    gap: var(--e2);
    margin: var(--e1) 0 var(--e4);
}

/* El radio de verdad sigue existiendo —es lo que viaja, lo que
   mueve el teclado y lo que hace que esto funcione sin una línea
   de JavaScript— pero no se dibuja. `opacity: 0` y NO
   `display: none`, que lo sacaría del recorrido con Tab y dejaría
   el estilo inalcanzable sin mouse. */
/* VA CON `.form-erp` ADELANTE Y NO SUELTA: `.form-erp label` es
   (0,2,0) y `.flyer-estilo` sola es (0,1,0), así que pierde y las
   dos reglas de abajo no hacen NADA — sin ningún error, con la
   tarjeta saliendo en mayúscula y desbordada. Empatando en
   especificidad gana por posición, porque este bloque va al final
   de la hoja. Es la trampa del logo de la tienda y la de
   `.campo-carga`. */
.form-erp .flyer-estilo,
.flyer-estilo {
    position: relative;
    cursor: pointer;

    /* ---------------------------------------------------------
       LA TARJETA ES UN `<label>` ADENTRO DE UN `.form-erp`, y eso
       le trae puestas DOS reglas que la rompen — ninguna de las
       dos está mal escrita, las dos ganan:

         `.form-erp label { white-space: nowrap }`     y
         `.form-erp label { text-transform: uppercase }`

       Con la primera, la descripción del estilo no envuelve: sale
       en una línea, desborda la tarjeta y se monta sobre la de al
       lado. Con la segunda, esa frase sale en MAYÚSCULA SOSTENIDA
       y no se lee.

       Aquel rótulo es una palabra ("PRODUCTO", "FORMATO") y por eso
       allá las dos reglas están bien. Acá el `<label>` no es un
       rótulo: es la tarjeta entera.
       --------------------------------------------------------- */
    white-space: normal;
    text-transform: none;
    letter-spacing: normal;
    font-size: 1rem;
    color: var(--color-texto);
    font-weight: 400;
    margin-bottom: 0;

    /* Un ítem de grid arranca en `min-width: auto`, o sea que NO se
       achica por debajo del ancho de su contenido: sin esto, una
       descripción larga estira su columna y la grilla se sale del
       panel. */
    min-width: 0;
}

.flyer-estilo input[type="radio"] {
    position: absolute;
    opacity: 0;
    width: 0;
    height: 0;
}

.flyer-estilo-caja {
    display: flex;
    flex-direction: column;
    gap: var(--e1);
    height: 100%;
    padding: var(--e3);
    border: 2px solid var(--color-borde);
    border-radius: var(--radio-chico);
    background: var(--color-superficie);
    transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

/* `height: 100%` arriba más esto: las tarjetas de una fila miden
   lo mismo aunque una descripción entre en un renglón y otra en
   tres. Sin eso la grilla se ve como una fila de escalones. */
.flyer-estilo-caja .text-suave { font-size: 0.94rem; }

.flyer-estilo:hover .flyer-estilo-caja { border-color: var(--color-borde-fuerte); }

.flyer-estilo input:checked + .flyer-estilo-caja {
    border-color: var(--color-primario);
    box-shadow: 0 0 0 3px var(--color-primario-suave);
}

/* El anillo de foco va en la CAJA y no en el radio escondido: si
   no, quien navega con Tab no ve dónde está parado. */
.flyer-estilo input:focus-visible + .flyer-estilo-caja {
    outline: 2px solid var(--color-primario);
    outline-offset: 2px;
}

/* ---- El resultado ----
   La imagen a la izquierda y las acciones al costado. El flyer se
   mira grande —es lo que se va a publicar— así que se le da todo
   el ancho que sobre, con un tope para que un vertical de 9:16 no
   empuje los botones abajo del pliegue. */
.flyer-resultado {
    display: flex;
    flex-wrap: wrap;
    gap: var(--e4);
    align-items: flex-start;
}

.flyer-resultado img {
    max-width: min(420px, 100%);
    max-height: 70vh;
    border-radius: var(--radio-chico);
    border: 1px solid var(--color-borde);
    /* El flyer puede ser claro y sobre el papel del sistema se
       perdería el borde; y mientras carga, el fondo dice dónde va
       a aparecer en vez de dejar un hueco que salta. */
    background: var(--color-superficie-alt);
}

.flyer-acciones {
    display: flex;
    flex-direction: column;
    gap: var(--e2);
    align-items: flex-start;
    min-width: 200px;
}

/* CADA BOTÓN MIDE LO QUE DICE. Estirados al ancho de la columna
   —que es lo que hacían con `width: 100%`— dejan de leerse como
   botones y pasan a leerse como dos franjas. */
.flyer-acciones .boton-primario,
.flyer-acciones .boton-secundario { width: max-content; }

/* ---- La galería ----
   Los últimos que se generaron. Cuadradas y todas iguales aunque
   los flyers tengan tres formatos distintos: lo que se busca acá
   es reconocer cuál es cuál, no verlos enteros — para eso está el
   enlace, que abre el original en otra pestaña. */
.flyer-galeria {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
    gap: var(--e3);
}

.flyer-galeria figure { margin: 0; }

.flyer-galeria img {
    width: 100%;
    aspect-ratio: 1;
    object-fit: cover;
    border-radius: var(--radio-chico);
    border: 1px solid var(--color-borde);
    background: var(--color-superficie-alt);
    display: block;
}

.flyer-galeria figcaption {
    font-size: 0.92rem;
    line-height: 1.25;
    margin-top: var(--e1);
}

/* En un celular el flyer ocupa el ancho entero y las acciones van
   abajo: al costado quedarían dos columnas de 150px donde no
   entra ni el rótulo del botón. */
@media (max-width: 700px) {
    .flyer-resultado img { max-width: 100%; }
    .flyer-acciones { width: 100%; }
}

/* ---- Los packs de créditos (Flyers → Comprar) ----
   Cada pack es su PROPIO `<form>`, así que la grilla los
   acomoda como tarjetas: con un formulario único habría que
   mandar además cuál se eligió, y el clic dejaría de decir
   por sí solo qué se está comprando. */
.flyer-packs {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
    gap: var(--e3);
}

.flyer-pack {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 2px;
    padding: var(--e4);
    border: 2px solid var(--color-borde);
    border-radius: var(--radio-chico);
    background: var(--color-superficie);
    text-align: center;
    /* Un ítem de grid arranca en `min-width: auto` y no se achica
       por debajo de su contenido: sin esto un precio de siete
       cifras estira su columna y la grilla se sale del panel. */
    min-width: 0;
}

.flyer-pack:hover { border-color: var(--color-primario); }

/* El número grande es LA CANTIDAD y no el precio: lo que se
   compara entre packs es cuántos créditos trae cada uno, y el
   precio es la consecuencia. */
.flyer-pack-creditos {
    font-size: 2.4rem;
    font-weight: 700;
    line-height: 1.1;
    color: var(--color-primario);
}

.flyer-pack-precio {
    font-size: 1.24rem;
    font-weight: 600;
    margin: var(--e2) 0 var(--e3);
    /* `tabular-nums` alinea las comas entre tarjetas: sin eso dos
       importes de distinto largo se leen como una lista y no como
       una comparación. */
    font-variant-numeric: tabular-nums;
}

/* El botón mide lo que dice, como los del resultado. */
.flyer-pack .boton-primario { width: max-content; }


/* ===========================================================
   LA PLANILLA DE PRODUCTOS DEL DEPÓSITO
   =========================================================== */

/* EL ENCABEZADO SÍ SE IMPRIME: es lo único que distingue una hoja
   de la de otro vendedor o de otro día. Va con `wrap` porque en
   una pantalla angosta cinco datos no entran en un renglón, y
   apilados se siguen leyendo. */
.planilla-encabezado {
    display: flex;
    flex-wrap: wrap;
    gap: var(--e3) var(--e5);
    padding: var(--e3);
    margin-bottom: var(--e3);
    border: 1px solid var(--color-borde);
    border-radius: var(--radio);
    background: var(--color-superficie);
}

.planilla-encabezado .rotulo {
    display: block;
    font-size: 0.78rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--color-texto-suave);
}

.planilla-encabezado strong { font-size: 1.1rem; }

/* LA LEYENDA DE LA COLUMNA CANTIDAD, Y SE IMPRIME (no lleva
   `no-imprimir` a propósito): "1c + 3u" no se entiende sin que
   alguien lo explique una vez, y el que arma tiene la hoja en la
   mano —no la pantalla— y no tiene a quién preguntarle. */
.planilla-leyenda {
    margin: 0 0 var(--e3);
    font-size: 0.9rem;
    color: var(--color-texto-suave);
}

/* EL CASILLERO PARA TILDAR A MANO, como el de la hoja de ruta: el
   depósito arma con la hoja en la mano y va marcando. Es un
   cuadrado dibujado con el borde de la celda y no un carácter,
   así sale igual en cualquier impresora. */
.tabla-planilla-productos .casillero::before {
    content: "";
    display: block;
    width: 15px;
    height: 15px;
    border: 1.5px solid var(--color-texto-suave);
    border-radius: 2px;
}

@media print {
    /* Que la grilla salga ENTERA ya lo resuelve la regla general de
       impresión, que le suelta el `max-height` y el `overflow` a
       `.tabla-contenedor`. Acá lo único que se toca es el cuerpo: una
       planilla de doscientos renglones a 1rem son cuatro hojas. */
    .tabla-planilla-productos { font-size: 0.92rem; }

    /* Que una fila no se parta entre dos hojas: media fila arriba y
       media abajo es justo la que el depósito saltea. */
    .tabla-planilla-productos tr { break-inside: avoid; }

    .planilla-encabezado { border: 1px solid #000; }
}

/* =====================================================================
   EL ARMADOR DE LA HOJA DE CARGA A DOS COLUMNAS (178)
   =====================================================================
   Izquierda las facturas del filtro con su flechita, derecha la hoja que
   se está armando. `repartos/_editor_carga.php`.

   LA DERECHA ES MÁS ANCHA porque es el resultado: la izquierda son seis
   columnas cortas —número, cliente, fecha, total, ítems y zona— y la
   derecha lleva descripciones de artículo, que es lo que se lee caminando
   el pasillo. */
/* MITAD Y MITAD, y eso ALCANZA desde que cada factura ocupa dos renglones:
   con una columna por dato hacía falta darle más lugar a la izquierda para
   que el nombre del cliente no se partiera en tres. Repartido parejo, las
   dos mitades se leen igual de bien y la hoja —que es el resultado— no
   queda arrinconada. */
.editor-carga {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: var(--e3);
    align-items: start;
}

.editor-carga-col { min-width: 0; }

.editor-carga-cabecera {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--e2);
    flex-wrap: wrap;
    margin-bottom: var(--e2);
}
.editor-carga-cabecera h2 { margin: 0; font-size: 1rem; }

/* La lista puede tener 300 filas y la hoja crece con lo que se elige: sin
   un tope, elegir la última obliga a subir media pantalla para mirar el
   resultado. Las dos cajas se desplazan por su cuenta. */
.editor-carga-tabla,
.editor-carga-contenido {
    max-height: calc(100vh - 22rem);
    min-height: 12rem;
    overflow: auto;
}

/* =====================================================================
   LA COLUMNA DE FACTURAS NO SE DESPLAZA DE COSTADO
   =====================================================================
   `.tabla-contenedor` es `overflow: auto` para las grillas anchas del
   sistema, y acá eso es justo lo que no puede pasar: lo primero que se
   va de la vista al desplazarse es la flecha, que es lo único que esta
   columna existe para tocar. Son tres reglas y hacen falta las tres —el
   corte del eje, la letra más chica y que el nombre largo se parta—:
   sola, cualquiera de ellas deja volver el scroll con una razón social
   de sesenta caracteres. */
.editor-carga-tabla { overflow-x: hidden; }

.editor-carga-tabla .tabla-datos { font-size: 0.82rem; }

.editor-carga-tabla .tabla-datos td,
.editor-carga-tabla .tabla-datos th { overflow-wrap: anywhere; }

/* El segundo renglón de cada celda: la fecha, los ítems, el vendedor y la
   zona. Va más chico y suave para que la fila se lea como UNA factura y
   no como dos. */
.editor-carga-abajo {
    font-size: 0.88em;
    color: var(--color-texto-suave);
    margin-top: 1px;
}

/* LA FILA QUE YA ESTÁ EN LA HOJA. El borde de la izquierda es lo que se
   ve de reojo recorriendo la lista; el fondo solo, con filas alternadas,
   se confunde con el rayado. */
.editor-carga tr.fila-en-hoja > td {
    background: var(--color-exito-fondo);
}
.editor-carga tr.fila-en-hoja > td:first-child {
    box-shadow: inset 3px 0 0 var(--color-exito);
}

/* Mientras el viaje va y vuelve. No se tapa ni se bloquea nada: son
   milisegundos, y un velo por clic haría parpadear la pantalla entera. */
.editor-carga-esperando { opacity: 0.7; }
.editor-carga-esperando .btn-accion { pointer-events: none; }

/* En un monitor angosto se apilan, con las facturas arriba: es por donde
   se empieza. */
@media (max-width: 1100px) {
    .editor-carga { grid-template-columns: minmax(0, 1fr); }
    .editor-carga-tabla,
    .editor-carga-contenido { max-height: none; }
}

@media print {
    /* Se imprime la HOJA y no el armador: la columna de las facturas ya
       lleva `no-imprimir`, y acá lo que se saca es la grilla —que en el
       papel dejaría la hoja ocupando media página— y los topes de alto,
       que cortarían la tabla a la altura de la pantalla. */
    .editor-carga { display: block; }
    .editor-carga-contenido { max-height: none; overflow: visible; }
    .editor-carga-cabecera { display: none; }
}

/* =====================================================================
   EL AVISO FLOTANTE (partials/toast.php)
   =====================================================================
   Confirma algo que ya pasó y se va solo. Va ABAJO y centrado: arriba
   competiría con la barra del sistema y con los avisos de la pantalla, y en
   el celular el pulgar tapa justo esa esquina.

   `pointer-events: none` NO ES UN DETALLE: el aviso queda flotando sobre la
   pantalla tres segundos, y sin esto se come el toque que caiga ahí — que en
   el POS es el pie con el botón de guardar.

   Y el `[hidden]` va con `!important` porque la clase declara `display`: el
   `[hidden] { display:none }` del navegador sale de su hoja de USUARIO, que
   pierde contra cualquier declaracion del autor. Es lo que ya pagaron el velo
   del panel y el icono de sin senal. */
.taga-toast {
    position: fixed;
    left: 50%;
    bottom: calc(16px + env(safe-area-inset-bottom, 0px));
    transform: translate(-50%, 12px);
    z-index: 200;
    max-width: min(90vw, 26rem);
    padding: 10px 16px;
    border-radius: 999px;
    background: rgba(var(--scrim-rgb), .92);
    color: var(--color-sobre-oscuro);
    font-size: 14px;
    text-align: center;
    box-shadow: 0 6px 20px rgba(var(--sombra-rgb), .25);
    opacity: 0;
    transition: opacity .2s ease, transform .2s ease;
    pointer-events: none;
}
.taga-toast.visible { opacity: 1; transform: translate(-50%, 0); }
.taga-toast[hidden] { display: none !important; }

/* =====================================================================
   EL MAPA DE CLIENTES (reportes/mapa.php, etapa 4 de la 125)
   ===================================================================== */

/* El resumen accionable: cada tarjeta es el botón de su capa. */
.mapa-resumen {
    display: flex;
    flex-wrap: wrap;
    gap: var(--e2);
    margin-bottom: var(--e3);
}

.mapa-tarjeta {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 8.5rem;
    padding: var(--e2) var(--e3);
    background: var(--color-superficie);
    border: 1px solid var(--color-borde);
    /* La franja de color es lo que ata la tarjeta con su punto en el mapa. */
    border-left: 4px solid var(--color-motivo, var(--color-borde-fuerte));
    border-radius: var(--radio);
    cursor: pointer;
    user-select: none;
}

/* La casilla no se dibuja pero SIGUE SIENDO la casilla: es la que recibe el
   foco con Tab y la que anuncia el estado al lector de pantalla. Escondida con
   `display: none` no se podría llegar con el teclado. */
.mapa-tarjeta input[type="checkbox"] {
    position: absolute;
    opacity: 0;
    width: 1px;
    height: 1px;
    margin: 0;
}

.mapa-tarjeta:focus-within {
    outline: 2px solid var(--color-primario);
    outline-offset: 1px;
}

.mapa-tarjeta-numero {
    font-size: 1.5rem;
    font-weight: 600;
    line-height: 1.1;
    color: var(--color-motivo, var(--color-texto));
}

.mapa-tarjeta-rotulo {
    font-size: 0.86rem;
    color: var(--color-texto-suave);
}

/* APAGADA = capa escondida. Es el único estado que el usuario cambia, así que
   tiene que verse de lejos: se va el color y queda el contorno. */
.mapa-tarjeta:has(input:not(:checked)) {
    background: var(--color-superficie-alt);
    border-left-color: var(--color-borde);
}
.mapa-tarjeta:has(input:not(:checked)) .mapa-tarjeta-numero {
    color: var(--color-texto-tenue);
}

/* En cero se dibuja igual —"Sin visitar: 0" es una respuesta— pero no se puede
   apagar una capa vacía, así que tampoco se ofrece el gesto. */
.mapa-tarjeta-vacia,
.mapa-tarjeta-quieta {
    cursor: default;
    border-left-color: var(--color-borde);
}
.mapa-tarjeta-vacia .mapa-tarjeta-numero,
.mapa-tarjeta-quieta .mapa-tarjeta-numero {
    color: var(--color-texto-tenue);
}

/* El cartel de los que el mapa no puede dibujar. Va con el fondo de aviso
   porque es una advertencia sobre lo que NO se está viendo, no un dato más. */
.mapa-sin-ubicar {
    margin-bottom: var(--e3);
    padding: var(--e2) var(--e3);
    background: var(--color-alerta-fondo);
    border: 1px solid var(--color-borde);
    border-radius: var(--radio);
}
.mapa-sin-ubicar > summary {
    cursor: pointer;
    color: var(--color-alerta);
}
.mapa-sin-ubicar > summary > .text-suave { display: block; font-size: 0.86rem; }
.mapa-sin-ubicar .tabla-contenedor { margin-top: var(--e2); }

/* El motivo escrito, para la lista de los que no están en el mapa. */
.mapa-pastilla {
    display: inline-block;
    padding: 1px var(--e2);
    font-size: 0.82rem;
    color: var(--color-sobre-oscuro);
    background: var(--color-motivo, var(--color-texto-suave));
    border-radius: var(--radio-chico);
}

/**
 * EL LIENZO NECESITA UN ALTO EXPLÍCITO. Leaflet mide su contenedor al arrancar
 * y un div vacío mide CERO: sin esto el mapa se inicializa, no falla, y no se
 * ve nada — el modo de fallar más caro, porque no hay ningún error que mirar.
 */
.mapa-lienzo {
    height: min(68vh, 620px);
    min-height: 20rem;
    border: 1px solid var(--color-borde);
    border-radius: var(--radio);

    /**
     * =================================================================
     * EL MAPA SE ENCIERRA EN SU PROPIO CONTEXTO DE APILAMIENTO
     * =================================================================
     * Leaflet reparte a sus hijos entre z-index 200 y **1000** —las capas
     * van de 200 a 700 y los controles a 800 y 1000— y esos números
     * compiten DIRECTO contra los del sistema si nadie los encierra. El
     * menú es 50 y el header 60, así que el mapa les ganaba: el panel
     * desplegable de una sección quedaba DEBAJO del plano.
     *
     * Con `position` + `z-index` el contenedor abre un contexto propio, y
     * todo lo de adentro pasa a ordenarse PUERTAS ADENTRO: el mapa entero
     * vale 40 contra la página, o sea menos que el menú.
     *
     * LAS DOS PROPIEDADES HACEN FALTA. Un `z-index` sobre un elemento
     * estático no hace nada, y acá el `position: relative` lo pone Leaflet
     * por JavaScript al arrancar: escrito acá, el contexto existe desde
     * que se pinta y no desde que el script corre.
     */
    position: relative;
    z-index: 40;
}

/* Un mapa en papel sale como una mancha gris y sin las calles, así que la
   planilla es el camino para llevárselo. Es el criterio de `.no-imprimir`. */
@media print {
    .mapa-lienzo { display: none; }
}

/**
 * El número de la parada, dibujado como tooltip permanente encima del círculo.
 * Se le saca TODO el vestido del tooltip de Leaflet —fondo, borde, sombra y la
 * flechita— porque acá no es un globo: es el número adentro del punto.
 */
.leaflet-tooltip.mapa-parada-numero {
    background: none;
    border: none;
    box-shadow: none;
    padding: 0;
    margin: 0;
    font-size: 0.7rem;
    font-weight: 700;
    color: var(--color-sobre-claro);
    /* Que no se coma el clic del círculo que tiene debajo. */
    pointer-events: none;
}
.leaflet-tooltip.mapa-parada-numero::before { display: none; }

/* La muestra del color de cada recorrido en la tabla de la comparación. */
.mapa-comparacion-color {
    display: inline-block;
    width: 0.8rem;
    height: 0.8rem;
    border-radius: 50%;
    margin-right: 0.35rem;
    vertical-align: -0.1rem;
}
.mapa-comparacion caption { font-weight: 600; padding: 0.4rem 0; }

/**
 * EL MAPA DE LA FICHA DEL CLIENTE (etapa 5)
 *
 * Más bajo que el de reportes: acá el mapa es UN control adentro de un panel que
 * además tiene los dos campos, el botón y el historial de visitas. Con el alto
 * del informe empujaría todo eso abajo del pliegue.
 */
.mapa-lienzo-ficha {
    height: min(42vh, 380px);
    min-height: 15rem;
}

/**
 * =====================================================================
 * «DÓNDE ESTÁ EL LOCAL»: LOS DATOS A LA IZQUIERDA, EL PLANO A LA DERECHA
 * =====================================================================
 * Apilado, el bloque medía la suma de las dos cosas —unos 400px de texto más
 * los 380 del lienzo— y por eso el mapa vivía adentro de un `<details>` que
 * nacía cerrado. En dos columnas mide el MÁS ALTO de los dos, que es casi el
 * mismo número, así que el plano deja de costar alto y se puede mostrar
 * siempre.
 *
 * `align-items: start` no es opcional, y es la misma razón que en `.form-2col`:
 * sin él el grid estira las dos columnas al alto de la más alta y la corta
 * queda con su fondo colgando hasta abajo.
 *
 * `minmax(0, 1fr)` en vez de `1fr`: el mínimo de una columna de grid es el
 * ancho de su contenido, y el lienzo de Leaflet arrastra teselas y controles
 * posicionados. Con `1fr` a secas, un ancho mínimo más grande que la mitad
 * empuja la otra columna y la pantalla termina con scroll horizontal.
 */
.posicion-2col {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: var(--e4);
    align-items: start;
}

.posicion-2col > * { min-width: 0; }

/* La (i) con toda la explicación va al lado de los dos campos, a la altura del
   campo y no del rótulo: el `.c` se baja al fondo de la fila. */
.form-erp .grupo .c.posicion-ayuda { align-self: flex-end; padding-bottom: 9px; flex: 0 0 auto; }
.posicion-ayuda .ayuda-campo { width: 24px; height: 24px; margin-left: 0; }
.posicion-ayuda .ayuda-campo svg { width: 22px; height: 22px; }

/* Los tres botones en UNA fila, debajo de los campos. Los formularios de
   borrar y de buscar en el mapa son ítems de la fila como cualquier botón. */
.posicion-botones {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--e2);
    margin: var(--e2) 0;
}
.posicion-botones form { margin: 0; }

/* El historial de visitas, debajo de los botones y en la misma columna. */
.posicion-visitas { margin-top: var(--e3); }
.posicion-visitas h3 { font-size: 1rem; }
.posicion-visitas .tabla-contenedor { max-height: 320px; }

/* Abajo de 900px se apilan, con los datos arriba y el plano abajo: es el orden
   en que se lee. Y el lienzo se acorta, que es lo único que el plegado cuidaba
   en una pantalla angosta — ahí sí el mapa se lleva por delante el historial de
   visitas, que es la otra mitad de esta pestaña. */
@media (max-width: 900px) {
    .posicion-2col { grid-template-columns: minmax(0, 1fr); gap: var(--e3); }
    .mapa-lienzo-ficha { height: min(38vh, 300px); }
}

/**
 * EL MARCADOR ARRASTRABLE, que es un `<div>` y no una imagen.
 *
 * Tiene que ser un `L.marker` con `divIcon` y NO un `circleMarker`: el arrastre
 * de Leaflet engancha sobre el `_icon` del marcador —un elemento del DOM— y un
 * círculo se dibuja adentro del SVG de la capa, así que `draggable` se acepta
 * y no hace nada. El `className` propio reemplaza a `.leaflet-div-icon`, que
 * trae fondo blanco y borde gris.
 */
.mapa-marcador {
    width: 22px;
    height: 22px;
    background: var(--color-primario);
    border: 3px solid var(--color-sobre-oscuro);
    border-radius: 50%;
    box-shadow: 0 1px 4px rgba(var(--sombra-rgb), .45);
    /* El cursor es lo único que anuncia que esto se puede agarrar. */
    cursor: grab;
}
.leaflet-marker-draggable.mapa-marcador:active { cursor: grabbing; }

/* El foco del teclado tiene que verse: el marcador es `keyboard: true`, así que
   se llega con Tab y se mueve con las flechas. */
.mapa-marcador:focus-visible {
    outline: 3px solid var(--color-alerta);
    outline-offset: 2px;
}

/* =====================================================================
   EL DETALLE DE LA JORNADA (reportes/mapa.php)
   ===================================================================== */

/**
 * ES ANCHO PORQUE SON DIEZ COLUMNAS. El `.modal` de siempre mide 460px, que es
 * lo que necesita un formulario de tres campos; una tabla con cliente,
 * dirección, dos horas y tres importes ahí adentro se envuelve entera y deja de
 * poder leerse de un vistazo, que es para lo que existe.
 */
.modal-jornada {
    max-width: 1180px;
    /* Con muchas paradas la tabla es más alta que la pantalla: el panel se
       queda adentro del alto disponible y lo que scrollea es la grilla. */
    max-height: 84vh;
    display: flex;
    flex-direction: column;
}
.modal-jornada .modal-cuerpo { overflow-y: auto; }

/* El `thead` pegajoso de `.tabla-datos` se resuelve contra este contenedor, así
   que el alto va acá y no en la tabla. Ver la sección del encabezado congelado
   en `.claude/rules/interfaz.md`. */
.modal-jornada .tabla-contenedor { max-height: none; }

/**
 * LA FILA DE ENTRE MEDIO SE TIENE QUE VER DISTINTA DE UNA PARADA, o la tabla se
 * lee como si el vendedor hubiera visitado el doble de clientes. Va con el fondo
 * alterno y la letra más chica: está subordinada a las dos filas que separa.
 */
.jornada-entre > td {
    background: var(--color-superficie-alt);
    color: var(--color-texto-suave);
    font-size: 0.88rem;
    /* Sin borde arriba: pertenece al espacio ENTRE las dos filas, no a una. */
    border-top: 0;
}

/* La dirección es lo único que puede ser larguísimo y lo que menos se mira:
   se recorta a un renglón para que las diez columnas sigan entrando. */
.tabla-jornada td:nth-child(3) {
    max-width: 16rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* ===========================================================
   EL BOTONCITO DE AYUDA DE UN CAMPO, Y SU GLOBO
   ===========================================================
   La explicación de un campo no vive más al pie del campo: vive detrás de una
   (i) que se aprieta cuando hace falta. El porqué está en
   `partials/_ayuda_campo.php`; acá está cómo se ve.
   =========================================================== */

/**
 * EL BOTÓN VA EN EL `<label>`, PEGADO AL RÓTULO.
 *
 * `vertical-align: middle` y no `baseline`: el SVG es una caja y con baseline
 * queda dos píxeles alto respecto del texto del rótulo, que en una fila de ocho
 * campos se lee como que uno está desalineado.
 */
.ayuda-campo {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 17px;
    height: 17px;
    padding: 0;
    margin-left: 4px;
    vertical-align: middle;
    border: 0;
    background: none;
    color: var(--color-texto-tenue);
    cursor: pointer;
    border-radius: 50%;
    /* El color es lo único que se anima: un botón de 17px que crece al pasar
       por encima hace saltar el renglón entero del rótulo. */
    transition: color 0.12s ease;
}

.ayuda-campo:hover,
.ayuda-campo[aria-expanded="true"] { color: var(--color-primario); }

/* Cuando la (i) va pegada a la DERECHA del control y no en el rótulo (el
   «Declara como» de la compra): el control y el botón en una fila, centrados. */
.campo-con-ayuda { display: inline-flex; align-items: center; gap: 2px; }

.ayuda-campo:focus-visible {
    outline: 2px solid var(--color-primario);
    outline-offset: 1px;
}

.ayuda-campo svg {
    width: 15px;
    height: 15px;
    display: block;
    stroke: currentColor;
    fill: none;
    stroke-width: 1.7;
    stroke-linecap: round;
    stroke-linejoin: round;
}

/**
 * EL GLOBO.
 *
 * `position: absolute` y NO `fixed`: con fixed se queda clavado en la pantalla
 * mientras el formulario scrollea por debajo, separándose del campo que explica
 * —que es lo único que tiene que hacer—. Lo posiciona el JavaScript en
 * coordenadas de documento.
 *
 * EL ANCHO ES CHICO A PROPÓSITO (`max-width: 17rem`, dos o tres renglones). Un
 * globo ancho se lee como un párrafo y vuelve a ser lo que se vino a sacar; y
 * pegado al campo, uno angosto se relaciona solo con lo que está explicando.
 */
.ayuda-globo {
    position: absolute;
    z-index: 210;
    max-width: 17rem;
    padding: var(--e2) var(--e3);
    background: var(--color-superficie);
    color: var(--color-texto);
    border: 1px solid var(--color-borde);
    border-radius: var(--radio);
    box-shadow: 0 6px 18px rgba(var(--sombra-rgb), 0.16);
    font-size: 0.95rem;
    line-height: 1.4;
    /* Que el clic de afuera pueda cerrarlo: el globo no se come sus propios
       clics, pero tampoco necesita recibir ninguno. */
    text-align: left;
}

/**
 * EL `z-index` ES 210, ARRIBA DE LOS DIÁLOGOS (200) Y DEBAJO DEL VELO DE
 * CARGANDO (300).
 *
 * Arriba de los diálogos porque un formulario puede vivir DENTRO de la ventana
 * modal —la ficha de una entidad se abre ahí con Ctrl+Enter— y un globo por
 * debajo quedaría tapado justo donde más falta hace. Debajo del velo porque
 * cuando la página se está yendo no hay nada que pueda ganarle.
 */

.ayuda-globo-flecha {
    position: absolute;
    width: 10px;
    height: 10px;
    background: var(--color-superficie);
    border-left: 1px solid var(--color-borde);
    border-top: 1px solid var(--color-borde);
    transform: translateX(-50%) rotate(45deg);
    top: -6px;
}

/* Con el globo arriba del botón, la flecha se da vuelta y se va al pie. */
.ayuda-globo.arriba .ayuda-globo-flecha {
    top: auto;
    bottom: -6px;
    border-left: 0;
    border-top: 0;
    border-right: 1px solid var(--color-borde);
    border-bottom: 1px solid var(--color-borde);
}

/* No se imprime: los informes salen por `imprimirConDialogo()`, que manda al
   papel lo que está a la vista, y un globo abierto saldría encima del campo. */
@media print {
    .ayuda-campo,
    .ayuda-globo { display: none !important; }
}

/* ===========================================================
   EL PADRÓN COMPACTO: la ficha de la entidad y su alta

   Va SCOPEADO en `.entidad-compacta` y no en `.form-erp`, que lo usan setenta
   y nueve pantallas: lo que acá se decidió es que ESTA ficha entre en la
   altura de la pantalla, y eso es una decisión de cómo se usan estas dos
   pantallas —se abren todo el día y se leen de un vistazo— y no una mejora
   general de densidad que haya que pagar en todas.

   Las dos comparten la clase a propósito: el alta y la ficha son los mismos
   campos, y con dos densidades distintas cargar un cliente y abrirlo después
   se sienten como dos sistemas. Lo que el alta NO tiene son pestañas —al
   crear hay que ver TODO lo que hay que cargar sin navegar—, así que las
   reglas de `.tabs` de acá abajo simplemente no encuentran nada allá.
   =========================================================== */

:root {
    /* El fondo del recuadro de campos. Es un beige apenas tibio: lo que tiene
       que lograr es que el campo BLANCO de adentro se recorte contra él, así
       que el contraste vive entre estos dos y no en el borde del campo. Con el
       gris de `--color-superficie-alt` el input blanco se lee como un hueco;
       con el beige se lee como un campo. */
    --color-campos-fondo: #faf6ea;
    --color-campos-borde: #e6ddc4;
}

/* --- La caja general: menos aire en cada junta --- */
.entidad-compacta .migas        { margin-bottom: var(--e1); }
.entidad-compacta .barra-acciones { margin-bottom: var(--e2); }
.entidad-compacta .panel        { padding: var(--e3); margin-bottom: var(--e3); }
.entidad-compacta .tabs         { margin-bottom: var(--e2); }
.entidad-compacta .tabs a       { padding: var(--e1) var(--e3); font-size: 1.08rem; }

/* El recuadro de campos: beige, y pegado al de al lado.
   `--e2` arriba y abajo en vez de `--e3`: con tres recuadros apilados, ese
   escalón solo son 24px, que es casi una fila de campos. */
.entidad-compacta fieldset {
    background: var(--color-campos-fondo);
    border-color: var(--color-campos-borde);
    padding: var(--e2) var(--e3) var(--e3);
    margin-bottom: var(--e2);
}
.entidad-compacta fieldset legend { color: var(--color-texto-suave); }

/* El campo se recorta contra el beige, EN LOS DOS MODOS.

   `.form-erp input[readonly]` pinta el campo de gris y le saca el borde, que
   sobre fondo blanco se lee como "esto no se toca". Sobre el beige se lee como
   un hueco: el dato queda flotando sin caja y la fila pierde la grilla, que es
   justo lo que un ERP usa para que el ojo baje derecho. Acá el campo en modo
   lectura se queda BLANCO y con su borde; lo que dice que no se puede escribir
   es el cursor, el chip «Editando» y la barra de botones, que ya lo decían. */
.entidad-compacta .form-erp input[readonly],
.entidad-compacta .form-cabecera input[readonly],
.entidad-compacta .form-cabecera select:disabled {
    background: var(--color-superficie);
    border-color: var(--color-borde);
    color: var(--color-texto);
    -webkit-text-fill-color: var(--color-texto);
}

/* El modo edición sigue tiñendo el panel entero, pero por DEBAJO del beige:
   los recuadros se quedan como están y lo que cambia es el marco. La barra
   vertical de la izquierda es la señal fuerte y ya estaba. */
.entidad-compacta .cabecera-articulo.editando { background: var(--color-primario-suave); }

/* ===========================================================
   LA TIRA DE IDENTIFICACIÓN: UNA fila, 40px como tope

   Es lo que se lee sin entrar a ninguna pestaña: documento, condición de IVA,
   teléfono, dónde está y quién lo atiende. Existe porque los datos se fueron a
   su pestaña, y sin ella mirar un CUIT obligaría a volver a Datos.

   NO CRECE NUNCA: `white-space: nowrap` + `overflow: hidden` y cada pieza con
   su `text-overflow`. Sin eso, un domicilio largo la parte en dos renglones y
   deja de ser una tira: pasa a ser un párrafo que empuja las pestañas fuera de
   la pantalla, que es exactamente lo que esto vino a evitar.
   =========================================================== */
.ficha-tira {
    display: flex;
    align-items: center;
    gap: var(--e2);
    max-height: 40px;
    padding: var(--e1) var(--e3);
    margin-bottom: var(--e2);
    background: var(--color-superficie);
    border: 1px solid var(--color-borde);
    border-radius: var(--radio);
    font-size: 0.98rem;
    color: var(--color-texto-suave);
    white-space: nowrap;
    overflow: hidden;
}
.ficha-tira .dato {
    overflow: hidden;
    text-overflow: ellipsis;
    min-width: 0;
}
.ficha-tira .dato strong {
    color: var(--color-texto);
    font-weight: 600;
}
/* El separador es un elemento y no un `::before`: así el que sobra al final
   —cuando una pieza no existe— no se dibuja, porque directamente no se emite. */
.ficha-tira .sep { color: var(--color-borde-fuerte); flex: 0 0 auto; }
/* Los campos de la tira se copian: el CUIT se pega en el sitio de ARCA todos
   los días. Es la misma excepción al `user-select: none` global que ya tiene
   `.form-cabecera input`, y por el mismo motivo. */
.ficha-tira { user-select: text; }

/* ===========================================================
   EL SALDO, AL LADO DEL NOMBRE

   Verde si tiene a favor, rojo si debe, gris si está en cero. Vive en la
   `.barra-acciones` para que se vea desde CUALQUIER pestaña: las tarjetas se
   fueron adentro de Cuenta corriente y sin esto habría que abrirla para saber
   si el que está enfrente debe plata.

   El corte se hace sobre `deuda` y `a_favor`, que `situacionCredito()` ya
   devuelve como dos POSITIVOS separados. Por eso no hay acá ningún literal
   negativo, que es la comparación que MySQL 8.4 resuelve mal.
   =========================================================== */
/**
 * EL SALDO NO PUEDE MEDIR MÁS QUE UN BOTÓN.
 *
 * Sus vecinos —Emitir, Editar, Volver— viven en un `.flex-gap-2`, que es un
 * flex SIN `align-items`: o sea `stretch`. Así que el más alto de la fila
 * estira a todos los demás, y con el importe en 1.32rem más su relleno el
 * más alto era éste. Los botones perdían su alto natural sin que nada en su
 * propia regla lo explicara.
 *
 * Se arregla por los dos lados y hacen falta los dos: acá el cuerpo y el
 * relleno bajan a los de un botón, y el contenedor pasa a `align-items:
 * center` —abajo, en `.barra-acciones .flex-gap-2`— para que ningún vecino
 * futuro vuelva a estirarlos.
 *
 * El importe sigue siendo lo más grande del recuadro: es el número que se
 * mira ANTES de venderle a crédito, y por eso este recuadro existe.
 */
.saldo-cabecera {
    display: inline-flex;
    align-items: baseline;
    gap: var(--e1);
    padding: 0 var(--e2);
    border: 1px solid var(--color-borde);
    border-radius: var(--radio);
    background: var(--color-superficie);
}
.saldo-cabecera .rotulo {
    font-size: 0.82rem;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--color-texto-suave);
}
.saldo-cabecera .importe {
    font-size: 1.05rem;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    color: var(--color-texto-suave);
}
/* Y el contenedor deja de estirar a sus hijos: es la otra mitad, y sin ella
   alcanza con que alguien agregue un vecino más alto para que los botones
   vuelvan a deformarse. */
.barra-acciones .flex-gap-2 { align-items: center; flex-wrap: wrap; }

/* ===========================================================
   LA DESCARGA CON VARIANTES: un desplegable y su botón (183)
   ===========================================================
   `select { width: 100% }` es global —lo quiere todo formulario de carga, donde
   cada campo ocupa su columna— y adentro de una barra de acciones eso lo
   convierte en un elemento elástico que se come el renglón: el desplegable salía
   estirado de punta a punta y al botón de al lado le quedaba tan poco que
   **partía su rótulo en dos líneas** («Descargar / PDF…»).

   Son las dos mitades y hacen falta las dos: que el desplegable MIDA SU OPCIÓN
   MÁS LARGA (`width: auto`) y que el botón no se encoja por debajo de su texto
   (`flex: 0 0 auto` + `nowrap`). Con `nowrap` solo, el botón no parte el rótulo
   pero se desborda; con `width: auto` solo, el botón sigue siendo encogible.

   NO SE TOCA LA REGLA GLOBAL, que es la decisión: alcanza a los cientos de
   `<select>` de los formularios de carga, donde estirarse es exactamente lo
   correcto. Es el mismo criterio con el que `.filtros-linea` se le sacó a la
   barra de armar en vez de cambiar la clase. */
.descarga-variantes select {
    width: auto;
    /* Un desplegable que mide su contenido puede quedar ridículamente angosto si
       sus opciones son cortas; este piso lo mantiene apretable. */
    min-width: 11rem;
}
.descarga-variantes button {
    flex: 0 0 auto;
    white-space: nowrap;
}

.saldo-cabecera.debe        { border-color: var(--color-error-borde); background: var(--color-error-fondo); }
.saldo-cabecera.debe .importe   { color: var(--color-error); }
.saldo-cabecera.a-favor     { border-color: var(--color-exito-borde); background: var(--color-exito-fondo); }
.saldo-cabecera.a-favor .importe { color: var(--color-exito); }

/* ===========================================================
   LAS TARJETAS CHICAS

   Mismo dibujo que `.tarjeta`, un escalón más bajo. Es la variante que hace
   que las tres del crédito —límite, saldo, disponible— entren en una fila
   junto con el botón de cobrar sin comerse media pantalla, que es lo que
   hacían las `.tarjeta-kpi` de 1,89rem que estaban arriba de la ficha.
   =========================================================== */
.tarjetas-resumen.chicas { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: var(--e2); margin-bottom: var(--e2); }
.tarjetas-resumen.chicas .tarjeta { padding: var(--e2) var(--e3); gap: 0; }
.tarjetas-resumen.chicas .tarjeta .etiqueta { font-size: 0.88rem; }
.tarjetas-resumen.chicas .tarjeta .valor    { font-size: 1.25rem; }
.tarjetas-resumen.chicas .tarjeta .detalle  { font-size: 0.88rem; }
/* La tarjeta que además lleva el botón de la acción: el botón va abajo del
   importe y separado, no pegado, o se lee como parte de la cifra. */
.tarjetas-resumen.chicas .tarjeta .boton-primario,
.tarjetas-resumen.chicas .tarjeta .boton-secundario { margin-top: var(--e2); align-self: flex-start; }
.tarjeta.negativa { border-color: var(--color-error-borde); background: var(--color-error-fondo); }
.tarjeta.negativa .valor { color: var(--color-error); }
.tarjeta.positiva { border-color: var(--color-exito-borde); background: var(--color-exito-fondo); }
.tarjeta.positiva .valor { color: var(--color-exito); }

/* ===========================================================
   LA GRILLA SCROLLEA ELLA, NO LA PÁGINA

   Ésta es la pieza que hace posible "todo en la altura de la pantalla", y es
   la decisión que el comentario de `.tabla-datos th` dejaba pendiente: el
   `position: sticky` del encabezado se resuelve contra `.tabla-contenedor`
   —que ya es un scrollport de los dos ejes— y hasta hoy no se activaba nunca
   porque ese div no tenía alto limitado. Dándoselo, el encabezado se pega de
   verdad y las filas corren por debajo.

   El `calc` descuenta lo que hay arriba de la tabla (header, migas, barra,
   tira, pestañas y el pie de la tabla). El `min-height` es la red: en un
   monitor bajo, o con el zoom del navegador subido, el `calc` puede dar un
   número ridículo y dejar la grilla en dos filas y media.
   =========================================================== */
.entidad-compacta .panel-pestania .tabla-contenedor {
    max-height: calc(100vh - 310px);
    min-height: 180px;
    overflow-y: auto;
}
/* El pie de cada tabla es una línea y va pegado: separado por un párrafo
   entero, se come justo el espacio que la grilla acaba de ganar. */
.entidad-compacta .panel-pestania .texto-ayuda { margin: var(--e2) 0 0; }

/* ===========================================================
   LOS CHIPS DE PERÍODO DE UN LISTADO DE LA FICHA

   La franja que los tiene: los chips a la izquierda, los dos campos de fecha
   —que aparecen recién con OTRO RANGO— pegados a ellos, y a la derecha lo que
   el listado quiera decir del período. Es UN control, así que va en una sola
   franja con su fondo, igual que el filtro del modal de kardex.
   =========================================================== */
.franja-periodo {
    display: flex;
    flex-wrap: wrap;
    gap: var(--e2) var(--e3);
    align-items: center;
    padding: var(--e2) var(--e3);
    margin-bottom: var(--e2);
    background: var(--color-superficie-alt);
    border: 1px solid var(--color-borde);
    border-radius: var(--radio);
}
.franja-periodo .chips { display: flex; gap: var(--e1); flex-wrap: wrap; }
.franja-periodo .fechas { display: flex; gap: var(--e1); align-items: center; font-size: 0.92rem; }
/* Los dos campos con la caja del sistema, y no con el dibujo por defecto del
   navegador: al lado hay chips de 26px y tres alturas distintas se leen como
   tres controles sueltos. Es la misma regla que `.kardex-fechas input`. */
.franja-periodo .fechas input[type="date"] {
    padding: 4px var(--e2);
    border: 1px solid var(--color-borde);
    border-radius: var(--radio-chico);
    background: var(--color-superficie);
    font-size: 0.92rem;
    height: var(--alto-control);
    width: auto;
}
.franja-periodo .fechas input[type="date"]:focus {
    outline: none;
    border-color: var(--color-primario);
    box-shadow: 0 0 0 2px var(--color-primario-suave);
}
.franja-periodo .resumen { margin-left: auto; font-size: 0.92rem; color: var(--color-texto-suave); }

/* ===========================================================
   LA PESTAÑA DE VISITAS: EL MAPA PLEGADO

   Es la única que no entra en la altura de la pantalla, porque el mapa mide
   lo que mide. Se pliega cuando la posición YA ESTÁ PUESTA: ahí el mapa sirve
   para CORREGIRLA, que es algo que se hace una vez, mientras que el historial
   —que es la mitad por la que la pestaña existe— se mira siempre.
   =========================================================== */
.plegable > summary {
    cursor: pointer;
    font-size: 12px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--color-primario);
    padding: var(--e1) 0;
    user-select: none;
}
.plegable > summary:hover { text-decoration: underline; }
.plegable[open] > summary { margin-bottom: var(--e2); }

/* ===========================================================
   LA LISTA CON LA QUE CADA UNO SE ACOMODA EL MENÚ (migración 180)
   ===========================================================
   Vive en Mi cuenta, que es la pantalla que alcanza todo el mundo.

   ES UNA LISTA Y NO UNA GRILLA a propósito: son nueve renglones como mucho
   —las secciones del menú— y lo que hay que poder hacer es agarrar uno y
   moverlo, no leer columnas. Una `.tabla-datos` traería su encabezado
   congelado, su navegación por teclado y su columna de acciones, que acá no
   significan nada. */
.orden-secciones {
    list-style: none;
    margin: 0;
    padding: 0;
    max-width: 26rem;
}
.orden-secciones li {
    display: flex;
    align-items: center;
    gap: var(--e2);
    padding: var(--e2) var(--e2);
    margin-bottom: var(--e1);
    background: var(--color-superficie);
    border: 1px solid var(--color-borde);
    border-radius: var(--radio);
    cursor: grab;
}
/* El renglón se puede enfocar con Tab para moverlo con Alt+↑/↓, así que el
   foco tiene que VERSE: sin esto, quien navega con el teclado no sabe cuál
   está por mover. Es el mismo anillo de `.btn-accion:focus-visible`. */
.orden-secciones li:focus-visible {
    outline: 2px solid var(--color-primario);
    outline-offset: 1px;
}
.orden-secciones li.arrastrando {
    opacity: 0.45;
    cursor: grabbing;
}
.orden-secciones .rotulo { flex: 1; }

/* El asa y el ícono de la sección comparten el trazo de la familia
   (`partials/iconos_menu.php`): `currentColor`, nada de hex. */
.orden-secciones .agarre svg,
.orden-secciones .icono svg {
    width: 18px;
    height: 18px;
    display: block;
    stroke: currentColor;
    fill: none;
    stroke-width: 1.5;
    stroke-linecap: round;
    stroke-linejoin: round;
}
.orden-secciones .agarre { color: var(--color-texto-suave); display: flex; }
/* Los seis puntos del asa son círculos: van RELLENOS y sin trazo, porque a
   1.4 de radio un anillo es una mancha. */
.orden-secciones .agarre svg circle { fill: currentColor; stroke: none; }
.orden-secciones .icono { color: var(--color-primario); display: flex; }
.orden-secciones .icono svg .relleno { fill: currentColor; fill-opacity: 0.25; }

/* Los dos botones de mover: el mismo cuadrado de 26px de `.btn-accion`, así
   que no hacen falta reglas propias — sólo que no se estiren. */
.orden-secciones .acciones-fila { flex: 0 0 auto; }

/* Los cobros del camión en la rendición (194): la casilla pegada a su importe,
   y la del encabezado pegada a su rótulo, alineadas a la derecha con la columna. */
.check-monto,
.check-encabezado {
    display: inline-flex;
    align-items: center;
    gap: var(--e2);
    cursor: pointer;
    margin: 0;
}

/* =====================================================================
   PUNTO DE VENTA Y NÚMERO DEL COMPROBANTE, EN DOS CAMPOS (195)
   =====================================================================
   `partials/numero_comprobante.php`. Van pegados y con el guion en el medio
   porque así se lee en el papel del proveedor. El ancho es el de los dígitos:
   más ancho, el campo invita a escribir el número entero en el primero. */
.numero-comprobante { display: inline-flex; align-items: center; gap: var(--e1, 4px); }
/* Con `.form-erp` y el `input` adelante, (0,3,1): escritas como `.numero-comprobante .nc-pv`
   (0,2,0) perdían contra el `width: 100%` de `.form-erp input[type="text"]` (0,2,1) y los dos
   campos salían anchos, sin la letra monoespaciada. El ancho es de 7 y 10 caracteres MÁS el
   relleno y el borde de la caja, que el `ch` no cuenta. */
.form-erp .numero-comprobante input.nc-pv,
.numero-comprobante input.nc-pv  { width: calc(7ch + 2 * var(--e2) + 2px); text-align: center; font-family: var(--fuente-mono, monospace); }
.form-erp .numero-comprobante input.nc-nro,
.numero-comprobante input.nc-nro { width: calc(10ch + 2 * var(--e2) + 2px); text-align: center; font-family: var(--fuente-mono, monospace); }
.numero-comprobante .nc-guion { color: var(--color-texto-suave); }

/* El selector de hoja de un Excel (importar datos). La primera fila de cada
   hoja va apagada: es para reconocerla, no un dato que se vaya a cargar. Los
   rótulos son clickeables porque cada celda es el <label> de su radio. */
.tabla-hojas .col-eleccion { width: 36px; text-align: center; }
.tabla-hojas .muestra-hoja { color: var(--color-texto-suave); }
.tabla-hojas label { cursor: pointer; }
.tabla-hojas .etiqueta { margin-left: 6px; }

/* ===========================================================
   EL MAPEO DE COLUMNAS DE UNA IMPORTACIÓN (importacion/nueva.php)
   ===========================================================
   Dos columnas: la tabla «dato → columna del archivo → ejemplo» a la
   izquierda y las tarjetas de decisiones a la derecha. Abajo de 1000px, una.
   Todo va con `.form-erp` adelante: `.form-erp select` (0,1,1) y
   `.form-erp label` (0,1,1) les ganarían a las reglas sueltas. */
.mapeo-columnas {
    display: grid;
    grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
    gap: var(--e3);
    align-items: start;
}
@media (max-width: 1000px) {
    .mapeo-columnas { grid-template-columns: minmax(0, 1fr); }
}
.mapeo-columnas .mapeo-col { display: flex; flex-direction: column; gap: var(--e3); min-width: 0; }
.mapeo-columnas .mapeo-tarjeta { margin: 0; padding: var(--e3); }
.mapeo-columnas .mapeo-tarjeta h3 { margin: 0 0 var(--e2); }
.mapeo-columnas .mapeo-tarjeta .grupo { margin-bottom: 0; }
.mapeo-columnas .mapeo-nota { margin: var(--e2) 0 0; }
.mapeo-columnas .mapeo-casilla {
    display: flex; align-items: center; gap: 6px; margin-top: var(--e2);
    text-transform: none; letter-spacing: 0; font-weight: 400; white-space: normal;
    font-size: 1rem; color: var(--color-texto);
}

/* La tabla: renglones finos, sin rejilla, el dato a la izquierda. */
.tabla-mapeo { width: 100%; border-collapse: collapse; table-layout: fixed; }
.tabla-mapeo thead th {
    text-align: left; font-size: 11px; font-weight: 600; text-transform: uppercase;
    letter-spacing: 0.03em; color: var(--color-texto-tenue); padding: 0 6px 4px;
}
/* Los anchos van en el <colgroup> y no en el encabezado: la tabla de precios no tiene. */
.tabla-mapeo col.mapeo-c-dato { width: 36%; }
.tabla-mapeo col.mapeo-c-columna { width: 40%; }
.tabla-mapeo td, .tabla-mapeo tbody th {
    padding: 1px 6px; border-bottom: 1px solid var(--color-borde); vertical-align: middle;
    text-align: left;
}
.tabla-mapeo tr.grupo-mapeo th {
    padding: var(--e2) 6px 2px; border-bottom: 0; font-size: 11px; font-weight: 700;
    text-transform: uppercase; letter-spacing: 0.03em; color: var(--color-primario);
}
.form-erp .tabla-mapeo tbody th label {
    font-size: 1rem; font-weight: 500; text-transform: none; letter-spacing: 0;
    color: var(--color-texto); white-space: normal; margin: 0;
}

/* EL SELECT SIN CAJA: se ve como texto con su flecha, y la caja aparece al
   pasar o al entrar. Con los trece bordes a la vista la tabla era una rejilla. */
.form-erp .tabla-mapeo select.select-celda {
    width: 100%; height: 30px; padding: 2px 4px;
    border: 1px solid transparent; background-color: transparent;
    font-size: 1rem; color: var(--color-texto); cursor: pointer;
}
.form-erp .tabla-mapeo select.select-celda:hover { border-color: var(--color-borde); }
.form-erp .tabla-mapeo select.select-celda:focus { border-color: var(--color-primario); background-color: var(--color-campo-foco); }

/* Lo que todavía no tiene columna, apagado: lo elegido es lo que se lee. */
.tabla-mapeo tr.sin-mapear th label,
.form-erp .tabla-mapeo tr.sin-mapear select.select-celda { color: var(--color-texto-tenue); }
.form-erp .tabla-mapeo tr.sin-mapear th label { color: var(--color-texto-suave); }

/* El ejemplo sale del archivo: gris, en un renglón, recortado con «…». */
.tabla-mapeo .ejemplo-mapeo {
    color: var(--color-texto-suave); font-size: 0.95rem;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* ===== Configuración → Mercado Pago (197): un renglón por punto de venta ===== */
/* Los selects llenan su celda; sin esto quedan del ancho del texto y la tabla baila. */
.mp-puntos-venta select { width: 100%; min-width: 9rem; }
.mp-puntos-venta td { vertical-align: top; }
/* Crear una caja es lo excepcional: un renglón chico debajo del select, no otro formulario. */
.mp-crear-caja { display: flex; align-items: center; gap: var(--e1); margin-top: var(--e1); }
.mp-crear-caja select { width: auto; min-width: 0; flex: 1; }

/* ===== POS: el cobro con QR de Mercado Pago (197) ===== */
/* Se lee de lejos: el que mira es el cliente, con el teléfono en la mano. */
.modal.pos-qr { max-width: 520px; text-align: center; }
.pos-qr-monto { font-size: 2.6rem; font-weight: 700; font-variant-numeric: tabular-nums; margin-bottom: var(--e2); }
/* El QR va sobre blanco en los dos temas: con fondo oscuro, muchas apps no lo leen. */
.pos-qr-codigo { display: flex; justify-content: center; }
.pos-qr-codigo svg { width: min(320px, 70vw); height: auto; background: #fff; border-radius: var(--radio); }
.pos-qr-estado { margin-top: var(--e2); font-size: 1.15rem; font-weight: 600; min-height: 1.5em; }
.pos-qr-estado.ok { color: var(--color-exito); font-size: 1.6rem; }
.pos-qr-estado.error { color: var(--color-error); }
.pos-qr-reloj { color: var(--color-texto-suave); font-variant-numeric: tabular-nums; min-height: 1.3em; }
/* Los cobros acreditados sin venta: la plata ya está en la cuenta y hay que verlos. */
.pos-cobros-pendientes { margin: var(--e1) 0; }
.pos-cobro-pendiente { display: flex; align-items: center; justify-content: space-between; gap: var(--e2); padding: var(--e1) var(--e2); margin-bottom: var(--e1); background: var(--color-exito-fondo); color: var(--color-exito); border: 1px solid var(--color-exito-borde); border-radius: var(--radio); font-size: 0.88rem; }
.pos-pago-qr { flex-basis: 100%; color: var(--color-exito); font-size: 0.8rem; font-weight: 600; }
.fila-pago.pago-acreditado { flex-wrap: wrap; }
/* La transferencia (fase 3): el alias grande, para dictarlo, y las que llegaron por ese monto. */
.pos-tr-datos { display: grid; grid-template-columns: auto 1fr; gap: var(--e1) var(--e3); text-align: left; margin: var(--e2) auto; max-width: 420px; }
.pos-tr-datos dt { color: var(--color-texto-suave); }
.pos-tr-datos dd { margin: 0; font-weight: 700; font-size: 1.3rem; word-break: break-all; }
.pos-tr-candidatos { margin-top: var(--e2); text-align: left; }
.pos-tr-candidato { display: flex; align-items: center; justify-content: space-between; gap: var(--e2); padding: var(--e2); margin-bottom: var(--e1); border: 1px solid var(--color-exito-borde); background: var(--color-exito-fondo); border-radius: var(--radio); }

/* =====================================================================
   LA PLANILLA DE LA RENDICIÓN (204): public/repartos/_planilla_rendicion.php
   ===================================================================== */
.tabla-rendicion input[type="text"] { width: 100%; min-width: 0; text-align: right; font-size: .95rem; height: 28px; padding: 2px 6px; }
.tabla-rendicion tr.fila-seccion td { background: var(--color-superficie-alt); font-weight: 600; }
.tabla-rendicion tr.fila-vendedor td { font-size: .85em; font-weight: 600; color: var(--color-texto-suave); padding-top: .6rem; }
.tabla-rendicion tr.renglon-confirmado td { background: var(--color-exito-fondo); }
/* «Cambió desde que se guardó» lo dice el punto rojo del renglón (212): sin franja ámbar. */
.tabla-rendicion tfoot td { font-weight: 600; }
.resumen-rendicion { display: grid; grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr)); gap: var(--e3); margin-top: var(--e3); }
.resumen-rendicion table { width: 100%; }
.resumen-rendicion td.num { text-align: right; white-space: nowrap; }
.resumen-rendicion tr.total-linea td { border-top: 1px solid var(--color-borde-fuerte); font-weight: 700; }
.diferencia-rendicion { margin-top: var(--e2); font-weight: 700; }
.diferencia-rendicion.cuadra { color: var(--color-exito); }
.diferencia-rendicion.no-cuadra { color: var(--color-error); }
/* La planilla con secciones y la balanza (2026-10). */
.tabla-rendicion tr.fila-subtotal td { font-weight: 600; border-top: 1px solid var(--color-borde-fuerte); background: var(--color-superficie-alt); }
.nota-antes-despues { margin-top: .25rem; font-size: .85em; padding: .2rem .45rem; border-left: 3px solid var(--color-alerta); background: var(--color-superficie-alt); }
.balanza-explicacion { margin: var(--e2) 0 0; line-height: 1.5; }
#modal-nc-rendicion .modal-cuerpo label { margin: .2rem 0; }

/* ---------------------------------------------------------------------
   UNA FILA POR COMPROBANTE, EN UN RENGLÓN Y SIN SCROLL LATERAL (212)
   ---------------------------------------------------------------------
   Las columnas medidas y las celdas que recortan (`nowrap` + puntos
   suspensivos): eso es lo que hace que no haya scroll a lo ancho ni filas de
   dos renglones. `table-layout: fixed` va de respaldo —medido, con `auto` entra
   igual— para que el ancho de las columnas no dependa del contenido. El
   cliente se queda con lo que sobra y se recorta con puntos suspensivos —el
   nombre entero va en el `title`—; lo que el renglón tiene para decir va en la
   marca (el punto rojo o la (i)), no en renglones de más. */
.tabla-rendicion-caja { overflow-x: hidden; }
.tabla-rendicion { table-layout: fixed; width: 100%; }
/* LOS IMPORTES NUNCA SE RECORTAN (2026-10): un número con puntos suspensivos
   no sirve para nada. Las cuatro columnas de plata se midieron contra el peor
   caso —«9.999.999,99» en negrita, que es el subtotal y el total general, y el
   mismo número adentro del campo— y lo que se achicó para pagarlo es lo que SÍ
   se puede recortar: el comprobante (sólo el número desde la 212), las
   acciones (el desplegable con rótulos cortos) y el cliente, que se queda con
   lo que sobra. */
.tabla-rendicion col.c-comprobante { width: 10rem; }
.tabla-rendicion td.celda-comprobante { font-size: .85rem; }
.tabla-rendicion col.c-importe { width: 8.5rem; }
.tabla-rendicion col.c-campo { width: 8.5rem; }
.tabla-rendicion col.c-ok { width: 4.25rem; }
.tabla-rendicion col.c-acciones { width: 10.5rem; }
.tabla-rendicion td, .tabla-rendicion th { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.tabla-rendicion .celda-linea { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* El segundo renglón de la celda del cliente: el monto original y la nota. */
.tabla-rendicion .celda-sub { font-size: .78rem; line-height: 1.2; color: var(--color-texto-suave); }
.tabla-rendicion tr.fila-rebote td { background: var(--color-superficie-alt); }
.tabla-rendicion .celda-estado-rebote { font-size: .85em; color: var(--color-texto-suave); }

/* LA MARCA: un punto rojo grande para lo que hay que resolver, una (i) roja
   para lo que hay que leer, y un hueco del mismo ancho cuando no hay nada —sin
   él, el número de esa fila arranca más a la izquierda que el de las demás—. */
.marca-renglon {
    display: inline-flex; align-items: center; justify-content: center;
    width: 13px; height: 13px; margin-right: .35rem; padding: 0;
    vertical-align: -1px; border: 0; border-radius: 50%; background: transparent;
    font: italic 700 9px/1 Georgia, var(--fuente-base); color: var(--color-error);
}
.marca-renglon.vacia { visibility: hidden; }
.marca-renglon.alerta { background: var(--color-error); cursor: pointer; }
.marca-renglon.info { border: 1.5px solid var(--color-error); cursor: pointer; }
.marca-renglon.muestra { cursor: default; display: inline-flex; vertical-align: -2px; margin: 0 2px; }
.marca-renglon:focus-visible { outline: 2px solid var(--color-primario); outline-offset: 2px; }

/* LOS BOTONES CUADRADOS NC Y SM: sin fondo de color, con la abreviatura. */
.boton-cuadrado {
    display: inline-flex; align-items: center; justify-content: center;
    width: 30px; height: 28px; padding: 0; margin-right: 4px; vertical-align: middle;
    border: 1px solid var(--color-borde-fuerte); border-radius: var(--radio-chico);
    background: var(--color-superficie); color: var(--color-texto);
    font: 700 .72rem/1 var(--fuente-base); letter-spacing: .02em;
    text-decoration: none; cursor: pointer;
}
.boton-cuadrado:hover { border-color: var(--color-primario); color: var(--color-primario); text-decoration: none; }
.boton-cuadrado.con-aviso { border-color: var(--color-error); color: var(--color-error); }
.boton-cuadrado.apagado { opacity: .45; cursor: not-allowed; }
.tabla-rendicion .decision-fila { display: inline-block; vertical-align: middle; width: 6.5rem; }
.tabla-rendicion .form-decision { margin: 0; }
.tabla-rendicion select.sel-decision { height: 28px; width: 100%; padding: 0 2px; font-size: .8rem; }
.tabla-rendicion td.celda-acciones-planilla .etiqueta { max-width: 6.5rem; overflow: hidden; text-overflow: ellipsis; vertical-align: middle; }
.tabla-rendicion select.sel-decision.sin-decidir { border-color: var(--color-error); color: var(--color-error); }

/* El modal de «Sale mañana»: una tarjeta por hoja, con su aviso abajo. */
.sm-hojas { display: grid; gap: var(--e1); }
.sm-hoja { display: block; padding: var(--e2); border: 1px solid var(--color-borde); border-radius: var(--radio); cursor: pointer; }
.sm-hoja:has(input:checked) { border-color: var(--color-primario); background: var(--color-superficie-alt); }
.sm-hoja-cabeza { display: inline-flex; flex-wrap: wrap; align-items: center; gap: var(--e2); }
.sm-hoja-aviso { display: block; margin: 2px 0 0 1.6rem; }

/* =====================================================================
   EL TABLERO: CÓMO VA EL DÍA, EL AGENTE Y LOS VENDEDORES (205)
   public/dashboard.php y public/partials/agente_tablero.php
   ===================================================================== */
/* Las dos de arriba: el día a la izquierda y el agente a la derecha. En el
   celular se apilan solas, porque `.tablero-columnas` ya es auto-fit. */
.tablero-hoy { margin-bottom: var(--e4); }
.tablero-hoy .tarjetas-resumen { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); margin-bottom: var(--e3); }
.tablero-hoy .tarjeta .valor { font-size: 1.3rem; }
.tablero-hoy .tarjeta a { color: inherit; }

/* El agente: la charla tiene alto fijo y scrollea, para que preguntar diez
   cosas no empuje el tablero entero hacia abajo. */
.agente-charla { max-height: 420px; overflow-y: auto; display: flex; flex-direction: column; gap: var(--e2); margin-bottom: var(--e2); }
.agente-charla:empty { display: none; }
.agente-mensaje { padding: var(--e2) var(--e3); border-radius: var(--radio-chico); max-width: 100%; }
.agente-usuario { align-self: flex-end; background: var(--color-primario-suave); color: var(--color-texto); max-width: 85%; }
.agente-agente { align-self: stretch; background: var(--color-superficie-alt); border: 1px solid var(--color-borde); }
.agente-pensando { color: var(--color-texto-suave); font-style: italic; }
.agente-error { color: var(--color-error); }
.agente-texto { margin: 0; }
.agente-periodo { margin: var(--e1) 0 0; color: var(--color-texto-suave); font-size: 0.9rem; }
.agente-entendida { margin: 0 0 var(--e1); color: var(--color-texto-suave); font-size: 0.85rem; font-style: italic; }
.agente-detalle { display: grid; grid-template-columns: auto 1fr; gap: 2px var(--e3); margin: var(--e2) 0 0; font-size: 0.95rem; }
.agente-detalle dt { color: var(--color-texto-suave); }
.agente-detalle dd { margin: 0; font-variant-numeric: tabular-nums; }
.agente-mensaje .tabla-contenedor { margin-top: var(--e2); max-height: 260px; }
.agente-opciones, .agente-sugerencias { display: flex; flex-wrap: wrap; gap: var(--e2); margin-top: var(--e2); }
.agente-sugerencias { margin: 0 0 var(--e3); }
.agente-enlace { display: inline-block; margin-top: var(--e2); }
.agente-form { display: flex; gap: var(--e2); }
.agente-form input { flex: 1; min-width: 0; }
.agente-pie { font-size: 0.88rem; margin: var(--e2) 0 0; }
/* En el celular el campo tiene que ser del alto de un dedo, como el POS móvil. */
html.es-movil .agente-form input, html.es-movil .agente-form button { min-height: 48px; }

/* El botón «Abrir» de cada respuesta, con lo que abre escrito al lado. */
.agente-abrir { display: flex; align-items: center; gap: var(--e2); flex-wrap: wrap; margin-top: var(--e2); }
.agente-abrir-rotulo { color: var(--color-texto-suave); font-size: 0.9rem; }
.agente-abrir .agente-enlace { margin-top: 0; }
/* Las pantallas del mapa del sistema: una por renglón, con su línea de qué es abajo. */
.agente-pantallas { display: flex; flex-direction: column; gap: var(--e2); margin-top: var(--e2); }
.agente-pantallas .agente-abrir { margin-top: 0; }
.agente-pantalla-resumen { flex-basis: 100%; color: var(--color-texto-suave); font-size: 0.82rem; line-height: 1.35; }

/* ===========================================================
   EL ASISTENTE TAGA FLOTANTE: el botón fijo abajo a la derecha y el panel
   que abre. Arriba de los menús (200/210) y debajo del velo de «cargando»
   (300) y de los diálogos (1200): un diálogo abierto desde el chat tiene que
   verse encima.
   =========================================================== */
/* UN CÍRCULO DE 44px Y SEMITRANSPARENTE: tapaba los saldos de la columna de la
   derecha. Opaco al apuntarlo, con el foco del teclado y con el panel abierto;
   y opaco SIEMPRE donde no hay mouse (`hover: none`), porque en el celular no
   hay forma de «apuntarlo» y quedaría desvaído para siempre. Lleva las dos
   clases en el selector para ganarle al relleno y al ancho de `.boton-primario`. */
.asistente-boton.boton-primario {
    position: fixed; right: var(--e4); bottom: var(--e4); z-index: 250;
    width: 44px; height: 44px; padding: 0;
    justify-content: center;
    border-radius: 50%;
    box-shadow: 0 6px 18px rgba(var(--sombra-rgb), 0.22);
    opacity: 0.55;
    transition: opacity 0.15s ease;
}
.asistente-boton svg { width: 24px; height: 24px; fill: currentColor; }
.asistente-boton.boton-primario:hover,
.asistente-boton.boton-primario:focus-visible,
.asistente-boton.boton-primario.asistente-boton-abierto { opacity: 1; }
.asistente-boton.boton-primario.asistente-boton-abierto { box-shadow: none; }
@media (hover: none) { .asistente-boton.boton-primario { opacity: 1; } }
.asistente-panel {
    position: fixed; right: var(--e4); bottom: calc(var(--e4) + 3.4rem); z-index: 250;
    width: min(440px, calc(100vw - 2 * var(--e4)));
    max-height: calc(100vh - 6rem); overflow-y: auto;
    box-shadow: 0 10px 32px rgba(var(--sombra-rgb), 0.24);
    border-radius: var(--radio);
    background: var(--color-superficie);
}
.asistente-panel .agente { margin: 0; }
.asistente-panel .agente-charla { max-height: 46vh; }
@media (max-width: 600px) {
    .asistente-boton.boton-primario { right: var(--e3); bottom: var(--e3); }
    .asistente-panel { right: var(--e3); left: var(--e3); width: auto; bottom: calc(var(--e3) + 3.4rem); }
}
@media print { .asistente-boton, .asistente-panel { display: none !important; } }

/* ===========================================================
   MIS METAS (206): el mes del vendedor
   Lo primero que se lee es el número propio, grande; abajo lo que falta.
   La barra es la de crédito (`.barra-credito`): es la misma idea —cuánto
   de un total ya se usó— y una segunda barra sería otra forma de dibujarla.
   =========================================================== */
.premio-programa { margin-bottom: var(--e4); }
.premio-numero {
    font-size: 1.25rem;
    margin: var(--e2) 0;
    font-variant-numeric: tabular-nums;
}
.premio-numero strong { font-size: 1.6rem; }
.premio-barra { height: 14px; margin: var(--e2) 0 var(--e3); }
.premio-puesto { font-size: 1.1rem; }
.premio-podio {
    list-style: none;
    padding: 0;
    margin: var(--e3) 0;
    border: 1px solid var(--color-borde);
    border-radius: var(--radio);
}
.premio-podio li {
    display: flex;
    gap: var(--e3);
    align-items: baseline;
    padding: var(--e2) var(--e3);
    border-top: 1px solid var(--color-borde);
}
.premio-podio li:first-child { border-top: 0; }
.premio-podio li.soy-yo { background: var(--color-superficie-alt); font-weight: 700; }
.premio-podio-puesto { width: 2.5em; font-weight: 700; }
.premio-podio-nombre { flex: 1 1 auto; }
.premio-podio-valor { font-variant-numeric: tabular-nums; white-space: nowrap; }
.premio-lista { margin: var(--e1) 0 var(--e3); padding-left: 1.2em; }
.premio-lista li { margin-bottom: var(--e1); }

/* =====================================================================
   LA BANDEJA DE WHATSAPP (207): public/whatsapp/index.php
   ===================================================================== */
/* Dos columnas: la lista angosta y la charla ancha. En el celular se ve UNA:
   la lista, o la charla elegida con su botón para volver. */
.wa-bandeja { display: grid; grid-template-columns: minmax(260px, 340px) 1fr; gap: var(--e3); align-items: start; }
.wa-lista { padding: var(--e2); max-height: calc(100vh - 11rem); overflow-y: auto; }
.wa-buscar input { width: 100%; margin-bottom: var(--e2); }
.wa-vacio { padding: var(--e3); }
.wa-item { display: block; padding: var(--e2); border-radius: var(--radio-chico); color: var(--color-texto); text-decoration: none; border-bottom: 1px solid var(--color-borde); }
.wa-item:hover { background: var(--color-superficie-alt); }
.wa-item.activo { background: var(--color-primario-suave); }
.wa-item-fila { display: flex; justify-content: space-between; align-items: baseline; gap: var(--e2); }
.wa-vista { color: var(--color-texto-suave); font-size: 0.9rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.wa-no-leidos { background: var(--color-exito); color: var(--color-exito-texto); border-radius: 999px; min-width: 1.4rem; padding: 0 6px; text-align: center; font-size: 0.8rem; font-weight: 700; }
.wa-charla { display: flex; flex-direction: column; gap: var(--e2); min-height: 60vh; }
.wa-cabecera { display: flex; justify-content: space-between; align-items: flex-start; gap: var(--e2); border-bottom: 1px solid var(--color-borde); padding-bottom: var(--e2); }
.wa-atar summary { cursor: pointer; color: var(--color-primario); }
.wa-mensajes { flex: 1; display: flex; flex-direction: column; gap: var(--e2); max-height: calc(100vh - 20rem); min-height: 12rem; overflow-y: auto; padding: var(--e2); background: var(--color-superficie-alt); border-radius: var(--radio-chico); }
.wa-mensaje { max-width: 75%; padding: var(--e2) var(--e3); border-radius: var(--radio-chico); background: var(--color-superficie); border: 1px solid var(--color-borde); word-wrap: break-word; }
.wa-entrante { align-self: flex-start; }
.wa-saliente { align-self: flex-end; background: var(--color-primario-suave); }
.wa-pie { display: flex; gap: var(--e2); justify-content: flex-end; font-size: 0.78rem; color: var(--color-texto-suave); margin-top: 2px; }
.wa-estado { letter-spacing: -2px; }
.wa-leido { color: var(--color-primario); }
.wa-fallido { color: var(--color-error); letter-spacing: 0; font-weight: 600; }
.wa-error { color: var(--color-error); font-size: 0.85rem; }
.wa-imagen { max-width: 260px; max-height: 260px; border-radius: var(--radio-chico); display: block; }
.wa-escribir { display: flex; gap: var(--e2); align-items: flex-end; }
.wa-escribir textarea { flex: 1; min-width: 0; resize: vertical; }
/* La respuesta sugerida por el agente (216): arriba del campo, con el borde de
   la marca para que se lea como una propuesta y no como un mensaje ya enviado. */
.wa-sugerencia {
    border: 1px dashed var(--color-primario);
    background: var(--color-primario-suave);
    border-radius: var(--radio);
    padding: var(--e2) var(--e3);
    margin-bottom: var(--e2);
}
.wa-sugerencia[hidden] { display: none; }
.wa-sugerencia-titulo { font-size: 0.8rem; font-weight: 600; color: var(--color-primario); margin-bottom: 2px; }
.wa-sugerencia-texto { white-space: pre-line; margin-bottom: var(--e1); }
.wa-sugerencia-motivo { font-size: 0.8rem; margin-bottom: var(--e1); }
.wa-sugerencia-texto[hidden], .wa-sugerencia-botones[hidden] { display: none; }
@media (max-width: 820px) {
    .wa-bandeja { grid-template-columns: 1fr; }
    .wa-bandeja.wa-con-charla .wa-lista { display: none; }
    .wa-bandeja:not(.wa-con-charla) .wa-charla { display: none; }
    .wa-mensaje { max-width: 90%; }
}

/* Los gráficos del agente (torta y barras), dibujados en el navegador. */
.agente-grafico { margin: var(--e2) 0 0; }
.agente-grafico-titulo { font-size: 0.9rem; color: var(--color-texto-suave); margin-bottom: var(--e1); }
.agente-grafico-torta { display: grid; grid-template-columns: minmax(120px, 200px) 1fr; gap: var(--e3); align-items: center; }
.agente-grafico-torta .agente-grafico-titulo { grid-column: 1 / -1; }
.agente-grafico svg { width: 100%; height: auto; display: block; }
.agente-grafico-leyenda { list-style: none; margin: var(--e1) 0 0; padding: 0; font-size: 0.9rem; font-variant-numeric: tabular-nums; }
.agente-grafico-leyenda li { display: flex; align-items: center; gap: var(--e2); margin: 2px 0; }
.agente-grafico-muestra { width: 12px; height: 12px; border-radius: 3px; flex: 0 0 12px; }
@media (max-width: 520px) { .agente-grafico-torta { grid-template-columns: 1fr; } .agente-grafico-torta svg { max-width: 200px; } }

/* El agente: el tacho de cada mensaje y el renglón que queda al eliminarlo. */
.agente-mensaje { position: relative; }
.agente-borrar { position: absolute; top: 4px; right: 4px; width: 24px; height: 24px; padding: 3px; border: 0; border-radius: var(--radio-chico); background: transparent; color: var(--color-texto-suave); cursor: pointer; opacity: 0.55; }
.agente-borrar:hover, .agente-borrar:focus-visible { opacity: 1; background: var(--color-superficie); color: var(--color-error); }
.agente-borrar svg { width: 100%; height: 100%; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.agente-mensaje:has(.agente-borrar) { padding-right: 34px; }
.agente-eliminado { color: var(--color-texto-suave); font-style: italic; background: transparent; border: 1px dashed var(--color-borde); }
/* En el celular no hay hover: el tacho se ve entero y del alto de un dedo. */
html.es-movil .agente-borrar { opacity: 0.9; width: 32px; height: 32px; padding: 6px; }
html.es-movil .agente-mensaje:has(.agente-borrar) { padding-right: 42px; }

/* ===========================================================
   VER ACUMULADO: el mes de un vendedor, día por día
   (vendedores/acumulado.php y vendedores/mi-acumulado.php)
   =========================================================== */
.acumulado-cabecera {
    display: flex; justify-content: space-between; align-items: center;
    gap: var(--e2); flex-wrap: wrap; margin: var(--e3) 0 var(--e2);
}
.acumulado-cabecera h2 { margin: 0; }
.acumulado-comision-arriba { margin-right: auto; padding: 2px var(--e2); border: 1px solid var(--color-exito-borde); background: var(--color-exito-fondo); color: var(--color-exito); border-radius: var(--radio-chico); white-space: nowrap; }
/* EL PIE TIENE DOS FILAS Y SÓLO LA DE TOTALES QUEDA PEGADA. Las dos con el
   `bottom: 0` compartido de `.tabla-datos tfoot td` se pegarían en el mismo
   lugar y la comisión taparía los totales mientras se scrollea. */
.tabla-acumulado tfoot .acumulado-comision td { position: static; box-shadow: none; border-top: 1px solid var(--color-borde); }
/* En el teléfono son cuatro columnas de plata en 390px: un punto menos de letra
   y menos relleno, y entran sin scroll de costado. */
html.es-movil .tabla-acumulado { font-size: 0.85rem; }
html.es-movil .tabla-acumulado th, html.es-movil .tabla-acumulado td { padding-left: 6px; padding-right: 6px; }
/* Y el encabezado se parte en dos renglones: «TOTAL DEVUELTO» de corrido es lo
   que fija el ancho de la columna, y sin partirlo la tabla entra con un píxel
   de sobra — el primer importe de siete cifras la desbordaría. */
html.es-movil .tabla-acumulado th { white-space: normal; }
/* El detalle de las devoluciones (2026-10): una tarjeta por nota de crédito,
   agrupadas por día. Sin tabla: con producto e importe en un renglón entra en
   390px sin scroll de costado, y al imprimir una nota no se parte entre dos hojas. */
/* De dónde salen los números (2026-10): arriba de la tabla, corto. */
.acumulado-origen { margin: 0 0 var(--e3); padding: var(--e2) var(--e3); border-left: 3px solid var(--color-primario); background: var(--color-superficie-alt); border-radius: var(--radio-chico); font-size: .9rem; }
.acumulado-origen ul { margin: 4px 0 0; padding-left: 1.1rem; }
.acumulado-origen li { margin: 2px 0; }
.acumulado-origen-aviso { color: var(--color-alerta); }
.acumulado-devoluciones { margin-top: var(--e4); }
.acumulado-devoluciones h3 { margin: 0 0 var(--e2); }
.acumulado-dev-dia { margin-bottom: var(--e3); }
.acumulado-dev-dia h4 { margin: 0 0 var(--e1); color: var(--color-texto-suave); }
.acumulado-dev-nota {
    border: 1px solid var(--color-borde); border-radius: var(--radio, 6px);
    padding: var(--e2); margin-bottom: var(--e2); background: var(--color-superficie);
    break-inside: avoid;
}
.acumulado-dev-cabecera { display: flex; flex-wrap: wrap; gap: 4px var(--e1); align-items: baseline; }
.acumulado-dev-total { margin-left: auto; font-weight: 600; white-space: nowrap; }
.acumulado-dev-renglones { list-style: none; margin: var(--e1) 0 0; padding: 0; }
.acumulado-dev-renglones li {
    display: flex; justify-content: space-between; gap: var(--e2);
    padding: 2px 0; border-top: 1px dashed var(--color-borde);
}
.acumulado-dev-renglones li span:last-child { white-space: nowrap; }
@media print {
    /* La tira de Repartos es navegación: en el papel no dice nada. */
    body:has(.tabla-acumulado) .tabs { display: none !important; }
}
