/*
 * ╔══════════════════════════════════════════════════════════════════════════╗
 * ║  ARCHIVO GENERADO — NO EDITAR A MANO                                     ║
 * ║                                                                          ║
 * ║  Lo arma `bin/build-css` pegando las partes de `assets/css/components/`  ║
 * ║  por orden de nombre. Para cambiar un componente, se edita su parte y    ║
 * ║  se vuelve a correr el script. Un cambio hecho acá se pierde en el       ║
 * ║  próximo armado, y `CssBundleTest` lo detecta antes.                     ║
 * ║                                                                          ║
 * ║  El índice de las partes está en `assets/css/components/00-indice.css`.  ║
 * ╚══════════════════════════════════════════════════════════════════════════╝
 */

/* ══ 00-indice.css ══ */

/*
 * Gestor Comercial — componentes de interfaz.
 * Lineamiento Gráfico §7 y §8.
 *
 * Esta carpeta es la fuente de verdad de `../components.css`, que es el archivo
 * que las pantallas enlazan. `components.css` **no se edita a mano**: lo arma
 * `bin/build-css` concatenando las partes de acá por orden de nombre, y
 * `tests/Unit/Design/CssBundleTest.php` falla si las dos cosas se despegan.
 *
 * ── Por qué está partido ───────────────────────────────────────────────────
 *
 * Porque 4.700 líneas en un archivo no se navegan. La auditoría de la Fase 12
 * lo anotó como deuda T4: para tocar el chip de estado había que recorrer el
 * desglose de precio, y dos componentes con nombres parecidos terminaban
 * definidos a mil líneas de distancia sin que nadie lo notara. El corte no
 * cambia una sola declaración —el orden de la cascada es exactamente el de
 * antes— y por eso se hizo así y no reordenando nada: el día que algo se vea
 * distinto, no fue esto.
 *
 * ── Por qué sigue existiendo un solo archivo servido ───────────────────────
 *
 * El Lineamiento §1 prohíbe el paso de compilación y §12 fija cuatro hojas en
 * un orden: tokens, base, components, layout. Enlazar veinticinco archivos
 * desde cada pantalla contradice las dos cosas y además empeora la carga:
 * veinticinco pedidos bloqueantes antes del primer dibujo. La concatenación
 * en el repositorio respeta las dos reglas y no agrega herramientas: `bin/build-css`
 * es un `cat` con control de orden, no un empaquetador.
 *
 * Minificar queda afuera por el mismo §1 —el archivo servido tiene que ser
 * legible—. El ahorro de bytes lo da `mod_deflate` en el servidor, que
 * comprime estas hojas al vuelo.
 *
 * ── El orden, y por qué importa ────────────────────────────────────────────
 *
 * Dos reglas de igual especificidad las resuelve la que viene después. El
 * número del nombre fija ese «después» y no es decorativo: los modificadores
 * de un componente tienen que poder sobreescribir su regla base.
 *
 *   00-indice                  este archivo
 *   01-iconos-y-marca          sprite de íconos, logotipo y lockup
 *   02-botones                 las cinco variantes del Lineamiento §7
 *   03-formularios             campos, color de acento, archivo
 *   04-tarjetas                tarjeta y ofrecimiento de instalar la app
 *   05-mensajes-y-chips        avisos, chips y chips de estado semánticos
 *   06-estado-del-presupuesto  tarjeta de estado y cambio de estado
 *   07-tablas                  tabla de datos y su versión en tarjetas
 *   08-listados                barra de filtros, paginación, pestañas
 *   09-fichas                  tarjeta del cliente y lista de datos
 *   10-superposiciones         modales, hojas inferiores, notificaciones
 *   11-indicadores             muestras, barras de progreso, vigencia
 *   12-filas-y-celdas          acciones de fila, formulario en línea, importes
 *   13-controles-en-tabla      casillas, galería, publicación masiva, plegables
 *   14-actividad               línea de tiempo y observaciones
 *   15-presupuesto             desglose, ítem, descuento, aprobación por ítem
 *   16-revisiones              banda histórica y comparación
 *   17-paneles                 cifra destacada, prioridades, pipeline, visor
 *   18-catalogo                importación y buscador con sugerencias
 *
 * Las seis últimas entraron con la Etapa 2 de la V2 (Lote B) y cierran la deuda
 * T2 —cada pantalla resolvía a mano lo mismo— con los bloques que no existían:
 *
 *   19-estados-vacios          qué pasó y qué hacer cuando no hay nada
 *   20-esqueletos-de-carga     el hueco con la medida final de lo que llega
 *   21-pasos-y-progreso        dónde estoy y cuánto falta
 *   22-barra-de-accion         el dato que importa y la acción, al pie
 *   23-riel-de-filtros         filtros aplicados en un renglón arrastrable
 *   24-control-segmentado      alternar vista con radios y sin JavaScript
 *
 * ── Qué no va en esta carpeta ──────────────────────────────────────────────
 *
 * El armazón de la pantalla (barra superior, navegación, grilla) vive en
 * `layout.css`. Las dos pantallas con estilos propios y pesados —el panel
 * ejecutivo y el configurador— tienen su hoja aparte y se enlazan solo donde se
 * usan. Y ningún color se escribe acá: todos vienen de `tokens.css`.
 *
 * Tampoco entra un componente antes de que exista la pantalla que lo use. Los
 * bloques de panel y gráficos (`.stat`, `.barlist`, `.bars`, `.legend`), la
 * grilla de catálogo (`.product-grid`) y la lista de opciones del configurador
 * (`.option-list`) son de los lotes D y G, y traerlos antes de rediseñar su
 * pantalla deja CSS sin usar: justo la deuda que esta fase vino a pagar.
 */

/* ══ 01-iconos-y-marca.css ══ */

/*
 * Parte de `components.css`. Las partes se concatenan por orden de nombre;
 * el índice y el motivo del corte están en `components/00-indice.css`.
 */

/* ── Íconos (Lineamiento §9) ────────────────────────────────────────────── */
/*
 * Un único set lineal, servido como sprite desde `assets/brand/icons.svg`:
 *
 *   <svg class="icon" aria-hidden="true"><use href="…/icons.svg#i-home"/></svg>
 *
 * El trazo se define acá y no dentro del sprite justamente para poder fijarlo
 * en los 1,75 px que pide el Lineamiento: Lucide dibuja con 2. Como el ícono
 * hereda `currentColor`, toma el color del texto que acompaña y nunca hace
 * falta pintarlo aparte.
 */

.icon {
    width: 20px;
    height: 20px;
    flex: none;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.75;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.icon--sm { width: 16px; height: 16px; }   /* dentro de chips */
.icon--lg { width: 24px; height: 24px; }   /* navegación móvil */

/* ── Marca (Lineamiento §2) ─────────────────────────────────────────────── */
/*
 * El logotipo se sirve como SVG desde `assets/brand/`, generado a partir del
 * arte vectorial original del cliente. El alto lo fija el contenedor; el ancho
 * lo resuelve el propio SVG por su `viewBox`.
 *
 * Área de seguridad: el manual la fija en X = 25 pt proporcionales, que en
 * pantalla es un margen libre igual a la altura de la «M» del logotipo en los
 * cuatro lados. De ahí el padding, que además es lo que impide que el logo
 * quede pegado al buscador de la barra superior.
 */

.brand-logo {
    display: inline-flex;
    align-items: center;
    padding: var(--sp-3) var(--sp-4);
}

.brand-logo__image {
    display: block;
    height: 32px;
    width: auto;
}

/*
 * El Lineamiento §2.2 fija 200 px para el ingreso. A ese ancho la bajada
 * «— since 1960 —» del arte original queda por debajo del tamaño en que se lee
 * y aparece como una mancha gris; 240 px es lo mínimo que la deja legible. Es
 * una desviación deliberada del número del Lineamiento, tomada por el mismo
 * criterio que lo ordena: primero que se lea. Vale igual para el ingreso y para
 * las pantallas de error, que muestran el mismo bloque.
 */
.brand-logo--login .brand-logo__image {
    height: auto;
    width: 240px;
}

/*
 * La variante apilada mide el doble de alto que la horizontal, así que fijarle
 * 32 px de alto la dejaba en unos 60 px de ancho: por debajo del mínimo de
 * 132 px del Lineamiento §2.2, con el logotipo y la bajada superpuestos e
 * ilegibles. Se le fija el ancho —160 px, con holgura sobre el mínimo— y el
 * alto sale de la proporción del archivo.
 */
.brand-logo--stacked .brand-logo__image {
    height: auto;
    width: 160px;
}

/* El monograma solo aparece en la barra superior angosta; el logotipo
 * horizontal, solo cuando hay ancho para respetar su área de seguridad. */
.brand-logo--responsive .brand-logo__image--mono {
    display: none;
    width: 32px;
    height: 32px;
}

@media (max-width: 767.98px) {
    .brand-logo--responsive {
        padding: var(--sp-2) var(--sp-3);
    }

    .brand-logo--responsive .brand-logo__image--wide {
        display: none;
    }

    .brand-logo--responsive .brand-logo__image--mono {
        display: block;
    }
}

/*
 * Lockup tipográfico de reserva (Lineamiento §2.2).
 *
 * Ya tenemos los vectoriales, así que dejó de ser la marca por defecto. Se
 * conserva por dos motivos concretos: es lo que se muestra si el SVG no carga,
 * y es lo que se usará si el cliente pide una variante que el arte recibido no
 * trae. Respeta la estructura del logotipo real.
 */

.mys-lockup {
    display: inline-flex;
    flex-direction: column;
    line-height: 1.15;
    padding: var(--sp-3) var(--sp-4);
    min-width: 132px;
}

.mys-lockup__wordmark {
    font-weight: 700;
    letter-spacing: -0.01em;
    color: var(--n-900);
    font-size: var(--fs-amount);
}

.mys-lockup__tld {
    font-weight: 300;
    color: var(--brand-yellow);
}

.mys-lockup__since {
    font-weight: 300;
    font-size: 0.34em;
    letter-spacing: 0.18em;
    text-align: center;
    color: var(--brand-taupe);
}

.mys-lockup--negative .mys-lockup__wordmark { color: var(--n-000); }
.mys-lockup--negative .mys-lockup__since { color: var(--brand-sand); }

.mys-lockup--large .mys-lockup__wordmark { font-size: var(--fs-figure-xl); }

/*
 * La bajada se oculta en todo el tramo de teléfono.
 *
 * El Lineamiento §2.2 la manda esconder cuando el lockup baja de 132 px de
 * ancho, y hasta la Etapa 1 el corte estaba escrito en 400 px de pantalla:
 * ese era el ancho de ventana donde el bloque llegaba a los 132 px en la barra
 * superior. Al unificar la escala no quedó un corte en 400 px —el del teléfono
 * es 599.98 px—, así que entre 400 y 599 px la bajada entra y ahora igual se
 * esconde. Es una pérdida chica y deliberada: son dos renglones decorativos y
 * la alternativa era un séptimo punto de corte para ellos solos.
 *
 * El escondido vuelve a atarse al ancho del bloque, que es lo que el
 * Lineamiento realmente nombra, cuando la Etapa 2 rearme el lockup como
 * componente con consulta de contenedor.
 */
@media (max-width: 599.98px) {
    .mys-lockup__since { display: none; }
}

/* Monograma cuadrado para la barra superior colapsada. El favicon y el ícono de
 * aplicación usan el archivo SVG equivalente, no esta clase. */
.mys-monogram {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    border-radius: var(--r-md);
    background-color: var(--n-900);
    color: var(--brand-yellow);
    font-weight: 700;
    font-size: var(--fs-body);
}

/* ══ 02-botones.css ══ */

/*
 * Parte de `components.css`. Las partes se concatenan por orden de nombre;
 * el índice y el motivo del corte están en `components/00-indice.css`.
 */

/* ── Botones (Lineamiento §7) ───────────────────────────────────────────── */

.button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--sp-2);
    /* El alto lo manda la densidad: 44 px en cómoda, 34 px en compacta
     * (Lineamiento §5). No se escribe el número acá. */
    height: var(--control-h);
    padding: 0 var(--pad-button-x);
    border: 1px solid transparent;
    border-radius: var(--r-md);
    font-family: inherit;
    font-size: var(--fs-body);
    font-weight: 600;
    cursor: pointer;
    text-decoration: none;
    transition: background-color .12s ease;
}

.button--primary {
    background-color: var(--brand-yellow);
    color: var(--n-900);
}

.button--primary:hover,
.button--primary:active {
    background-color: var(--brand-yellow-dark);
}

.button--secondary {
    background-color: var(--n-000);
    color: var(--n-900);
    border-color: var(--n-300);
}

.button--secondary:hover {
    background-color: var(--n-100);
}

.button--text {
    background: none;
    border: none;
    color: var(--n-700);
    padding: 0 var(--sp-2);
}

.button--text:hover {
    text-decoration: underline;
}

/*
 * Destructivo: fuera de un modal se muestra contenido —borde y texto rojos
 * sobre blanco—, para que el rojo lleno quede reservado a la confirmación. Que
 * el botón de borrar sea idéntico en la lista y en el modal invita a apretarlo
 * de memoria.
 */
.button--danger {
    background-color: var(--n-000);
    color: var(--err);
    border-color: var(--err);
}

.button--danger:hover {
    background-color: var(--err-bg);
}

.modal__actions .button--danger,
.button--danger-confirm {
    background-color: var(--err);
    border-color: var(--err);
    color: var(--n-000);
}

/*
 * Botón de WhatsApp.
 *
 * Lleva el verde del servicio y no el amarillo de la casa, a pedido expreso del
 * operador: lo que tiene que reconocerse de un vistazo no es la marca de la
 * mueblería sino a dónde lleva el botón. Vale la misma excepción que un «Entrar
 * con Google».
 *
 * Tres cosas lo mantienen dentro del Lineamiento aunque el color venga de
 * afuera: la tinta es --n-900 y no el blanco de WhatsApp, que sobre este verde
 * da 2,1:1 y no se lee (§10); el verde no compite con el amarillo porque nunca
 * es la acción principal de la pantalla, es una acción de fila (§7); y no se
 * usa el logotipo de WhatsApp, porque el Lineamiento §9 fija un único set de
 * iconos lineal y no se mezcla —alcanza con la palabra y el color—.
 */
.button--whatsapp {
    background-color: var(--whatsapp);
    border-color: var(--whatsapp);
    color: var(--n-900);
}

.button--whatsapp:hover,
.button--whatsapp:active {
    background-color: var(--whatsapp-dark);
    border-color: var(--whatsapp-dark);
}

/* Botón de solo ícono: cuadrado y con el nombre de la acción en `aria-label`,
 * nunca sin nombre accesible. */
.button--icon {
    width: var(--control-h);
    padding: 0;
}

/*
 * Acción principal de contexto en móvil (Lineamiento §6.2). Se ubica sobre la
 * barra inferior y respeta el área segura del dispositivo, para no quedar
 * debajo del gesto de inicio.
 */
.fab {
    position: fixed;
    right: var(--sp-4);
    bottom: calc(var(--bottomnav-h) + var(--sp-4) + env(safe-area-inset-bottom, 0px));
    z-index: var(--z-bottomnav);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 56px;
    height: 56px;
    border: none;
    border-radius: var(--r-pill);
    background-color: var(--brand-yellow);
    color: var(--n-900);
    box-shadow: var(--sh-2);
    cursor: pointer;
}

.fab:hover {
    background-color: var(--brand-yellow-dark);
}

@media (min-width: 768px) {
    .fab { display: none; }
}

/* Un botón que el usuario no puede usar no se oculta: se deshabilita y se
 * explica por qué (Lineamiento §7). Esconderlo hace que nadie entienda que la
 * función existe ni a quién pedírsela. */
.button:disabled,
.button[aria-disabled="true"] {
    background-color: var(--n-200);
    border-color: var(--n-200);
    color: var(--n-400);
    cursor: not-allowed;
}

.button__reason {
    display: block;
    font-size: var(--fs-caption);
    line-height: var(--lh-caption);
    color: var(--n-500);
    margin-top: var(--sp-1);
    /* Cuando el botón apagado convive con otros en una fila (`.row-actions`),
       el motivo no se mete entre medio: se lleva su propio renglón. Fuera de
       un contenedor flex esta línea no hace nada. */
    flex-basis: 100%;
}

/* ── Variantes agregadas en la Etapa 2 (Lote B) ─────────────────────────────
 *
 * Tres huecos que las pantallas venían tapando a mano: la acción secundaria
 * dentro de una fila ya cargada, el botón chico de una barra de herramientas y
 * el ancho completo del formulario en celular. Los tres existían escritos como
 * estilo en línea o como clase de la pantalla, que es la deuda T2 de la
 * auditoría: lo mismo resuelto distinto catorce veces.
 */

/*
 * Discreto: sin fondo ni borde a la vista, pero con caja.
 *
 * La diferencia con `--text` no es de intensidad, es de naturaleza. `--text` es
 * un enlace disfrazado de botón: no tiene caja, se subraya al pasar el puntero
 * y se usa dentro de un párrafo o al pie de un campo, donde una caja de 44 px
 * rompería el renglón. `--ghost` sí es un botón —conserva el alto de la
 * densidad y el radio de `.button`— y por eso se puede alinear con un
 * `--primary` o un `--secondary` en la misma fila sin que la fila quede
 * desparejo. Lo único que no tiene es peso visual mientras está en reposo: el
 * fondo aparece al pasar el puntero, que es la confirmación de que es apretable.
 *
 * Dicho al revés: si la acción vive en una fila de botones, va `--ghost`; si
 * vive dentro de texto, va `--text`.
 */
.button--ghost {
    background-color: transparent;
    border-color: transparent;
    color: var(--n-700);
}

.button--ghost:hover {
    background-color: var(--n-100);
}

/*
 * Tamaño chico, 34 px: SOLO para barras de herramientas de escritorio.
 *
 * No se usa en celular ni en tablet, y no es una preferencia estética: 34 px
 * está por debajo del área táctil de 44 px que el Lineamiento §10 exige en
 * cualquier control que se toque con el dedo. Es el mismo alto que la densidad
 * compacta le da a `.button`, y la densidad compacta está declarada —en
 * `tokens.css`— como exclusiva de las pantallas de administración de
 * escritorio, donde el usuario está sentado y con puntero.
 *
 * Quien necesite un botón chico en una barra que también se ve en celular tiene
 * que dejar que el alto lo siga mandando `var(--control-h)` y bajar el tamaño
 * de letra solo, o mover la acción a una hoja «Más acciones».
 */
.button--sm {
    height: 34px;
    padding: 0 var(--sp-4);
    font-size: var(--fs-label);
}

/* El botón de solo ícono sigue siendo cuadrado cuando además es chico. El
 * relleno se repite porque `.button--sm` está escrito después de
 * `.button--icon` y con el mismo peso le ganaba: con las dos clases juntas el
 * ícono volvía a quedar descentrado dentro de una caja ancha. */
.button--icon.button--sm {
    width: 34px;
    padding: 0;
}

/* Ancho completo. Es la forma que toma el botón de enviar de un formulario en
 * celular y la del único botón de un estado vacío angosto. */
.button--block {
    width: 100%;
}

/* ══ 03-formularios.css ══ */

/*
 * Parte de `components.css`. Las partes se concatenan por orden de nombre;
 * el índice y el motivo del corte están en `components/00-indice.css`.
 */

/* ── Campos de formulario ───────────────────────────────────────────────── */

.field {
    display: block;
    margin-bottom: var(--sp-4);
}

.field__label {
    display: block;
    font-size: var(--fs-label);
    line-height: var(--lh-label);
    font-weight: 600;
    letter-spacing: .01em;
    color: var(--n-700);
    margin-bottom: var(--sp-1);
}

.field__required {
    color: var(--err);
}

.field__control {
    display: block;
    width: 100%;
    height: var(--control-h);
    padding: 0 var(--sp-3);
    border: 1px solid var(--n-300);
    border-radius: var(--r-md);
    background-color: var(--n-000);
    color: var(--n-900);
    font-family: inherit;
    font-size: var(--fs-body);
}

.field__control::placeholder {
    color: var(--n-400);
}

/* Reemplazo equivalente del contorno, no su eliminación: el borde cambia de
 * color y aparece el halo (Lineamiento §10). */
.field__control:focus {
    outline: none;
    border-color: var(--focus);
    box-shadow: var(--focus-ring);
}

.field__control--invalid {
    border-color: var(--err);
}

/*
 * Desplegables.
 *
 * Un `<select>` sin tocar usa el control del sistema operativo: mide entre 36 y
 * 40 px, tiene el radio del navegador y su propia flecha. Al lado de un
 * `<input>` de 44 px y radio 8 px se nota que son de dos sistemas distintos, y
 * en el Panel —nueve filtros, todos desplegables— la pantalla entera quedaba
 * fuera del Lineamiento §7.
 *
 * `appearance: none` apaga el control nativo y con él la flecha, así que hay
 * que dibujar una propia: va como fondo, a 12 px del borde derecho, y el
 * relleno de ese lado sube a 36 px para que el texto largo de una opción no se
 * meta debajo. El alto sale de `--control-h`, el mismo token que usan los
 * campos de texto, así que sigue a la densidad sin repetir el número.
 */
select.field__control {
    appearance: none;
    -webkit-appearance: none;
    padding-right: 36px;
    background-image: var(--select-chevron);
    background-repeat: no-repeat;
    background-position: right 12px center;
    background-size: 12px 12px;
    cursor: pointer;
}

/* Una opción del desplegable se dibuja con el tema del sistema, que en modo
 * oscuro es texto claro sobre fondo claro si no se lo fija. */
select.field__control option {
    background-color: var(--n-000);
    color: var(--n-900);
}

/* ── Campo de color de acento ───────────────────────────────────────────── */
/*
 * Un hexadecimal de siete caracteres no necesita —ni tolera— el ancho de la
 * tarjeta: se lo acota y se le pone al lado la muestra pintada, que es lo
 * único que permite reconocer el color de un vistazo. La muestra es
 * decorativa (`aria-hidden`): el valor sigue leyéndose en el campo.
 */
.accent-field {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
}

/* 24 × 24 px por indicación del diseño. No es un control: no se toca, no recibe
 * foco y no le corresponde el área táctil de 44 px del Lineamiento §10. */
.accent-field__swatch {
    flex: 0 0 auto;
    width: 24px;
    height: 24px;
    border: 1px solid var(--n-300);
    border-radius: var(--r-sm);
}

.accent-field__control {
    max-width: 160px;
}

/* Un texto de varias líneas se lee en renglones cortos: a ancho de tarjeta la
 * línea pasa de los 100 caracteres y el ojo pierde el renglón al volver. */
.field__control--narrow {
    max-width: 640px;
}

/* ── Campo de archivo ───────────────────────────────────────────────────── */
/*
 * El control nativo queda escondido pero presente: la etiqueta hace de botón y
 * el nombre del archivo se escribe al lado. El anillo de foco se traslada al
 * botón, porque el control real no se ve y sin esto el foco desaparecería
 * (Lineamiento §10).
 */
.file-field {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    flex-wrap: wrap;
}

.file-field__button {
    cursor: pointer;
}

.file-field__input:focus-visible + .file-field__button {
    outline: var(--bw-3) solid var(--focus);
    outline-offset: 2px;
}

.file-field__name {
    color: var(--n-500);
    font-size: var(--fs-body);
    overflow-wrap: anywhere;
}

/* Un área de texto crece; no puede tener el alto de un control de una línea. */
textarea.field__control {
    height: auto;
    min-height: calc(var(--control-h) * 2);
    padding: var(--sp-3);
    line-height: var(--lh-body);
    resize: vertical;
}

.field__hint {
    display: block;
    margin-top: var(--sp-1);
    font-size: var(--fs-caption);
    line-height: var(--lh-caption);
    color: var(--n-500);
}

/* El error nunca se comunica solo con color: lleva icono y texto. */
.field__error {
    display: flex;
    align-items: flex-start;
    gap: var(--sp-1);
    margin-top: var(--sp-1);
    font-size: var(--fs-caption);
    line-height: var(--lh-caption);
    color: var(--err);
}

.field--inline {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    margin-bottom: var(--sp-4);
}

.field--inline .field__label {
    margin-bottom: 0;
}

/* ══ 04-tarjetas.css ══ */

/*
 * Parte de `components.css`. Las partes se concatenan por orden de nombre;
 * el índice y el motivo del corte están en `components/00-indice.css`.
 */

/* ── Tarjetas ───────────────────────────────────────────────────────────── */

/*
 * Lineamiento §7: 16 px de aire adentro de la tarjeta, 24 px en escritorio.
 *
 * Los 24 px faltaban y se notaba sobre todo con un botón al final de una fila
 * de campos: quedaba a diez píxeles del borde, como si se hubiera escapado de
 * la tarjeta.
 */
.card {
    background-color: var(--n-000);
    border: 1px solid var(--n-200);
    border-radius: var(--r-lg);
    padding: var(--sp-4);
}

@media (min-width: 768px) {
    .card {
        padding: var(--sp-5);
    }
}

.card__title {
    margin-bottom: var(--sp-3);
}

/*
 * La bajada de una tarjeta: una línea que resume lo que se está mirando, entre
 * el título y el contenido. Va en texto de cuerpo y no en `caption` a propósito
 * —es información que hay que leer, no una aclaración al pie— y por eso lleva la
 * tinta de cuerpo y no el gris secundario.
 */
.card__lead {
    margin-bottom: var(--sp-3);
    color: var(--n-700);
}

/*
 * Subtítulo dentro de una tarjeta que ya tiene título: separa un segundo bloque
 * —«Cambiar el estado» debajo de la ficha del presupuesto— sin abrir otra
 * tarjeta. Lleva aire arriba, que es lo único que lo distingue del título.
 */
.card__subtitle {
    margin-top: var(--sp-5);
    margin-bottom: var(--sp-3);
}

/*
 * Tarjeta destacada: meta cumplida, campaña vigente. Borde marcado y una franja
 * superior de 3 px en amarillo (Lineamiento §7). La franja se dibuja con
 * `border-top` y no con un elemento aparte para que no haya nada que el lector
 * de pantalla tenga que ignorar.
 *
 * El borde era de arena. Desde que la arena pasó a ser un gris frío (20/10/2026)
 * daba 1,35:1 contra la tarjeta blanca —un borde que no se ve no destaca nada—,
 * así que usa el borde de control del sistema, que es el que sí se distingue.
 */
.card--featured {
    border-color: var(--n-300);
    border-top: var(--bw-3) solid var(--brand-yellow);
}

/* ── Ofrecimiento de instalar la aplicación ─────────────────────────────────
 *
 * La tarjeta viaja siempre en el HTML con `hidden` y la revela `pwa.js`. Dentro
 * hay dos caminos excluyentes y es el atributo `data-install-mode` el que elige:
 * `prompt` para el navegador que sabe instalar solo, `ios` para Safari, que no
 * puede y necesita que le expliquen los tres pasos. Se resuelve con CSS y no
 * escribiendo HTML desde JavaScript: así los dos caminos se pueden leer en la
 * plantilla y traducir sin tocar el script.
 */
.install-card__steps {
    display: none;
    margin: var(--sp-3) 0 0;
    padding-left: var(--sp-5);
}

.install-card[data-install-mode="ios"] .install-card__prompt {
    display: none;
}

.install-card[data-install-mode="ios"] .install-card__steps {
    display: block;
}

.install-card__steps li + li {
    margin-top: var(--sp-1);
}

/* ── Encabezado, pie y tarjeta sin relleno (Etapa 2, Lote B) ────────────────
 *
 * «Título a la izquierda, una acción a la derecha» aparece en catorce pantallas
 * y cada una lo armaba distinto: unas con una `<div>` flexible escrita en la
 * vista, otras con el botón flotado, otras con el título y el botón en dos
 * renglones sin querer. Las tres se veían parecidas en escritorio y ninguna
 * aguantaba un título largo en un teléfono de 360 px.
 */

/*
 * El encabezado de la tarjeta.
 *
 * `flex-wrap` y no `nowrap`: cuando el título y las acciones no entran juntos,
 * lo que tiene que pasar es que las acciones bajen un renglón, no que el título
 * se encoja hasta cortarse. Un título recortado deja al usuario sin saber qué
 * está mirando; un botón un renglón más abajo no le cuesta nada.
 */
.card__head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--sp-3);
    flex-wrap: wrap;
    margin-bottom: var(--sp-3);
}

/* El título adentro del encabezado no lleva su propio aire abajo: el que separa
 * del contenido ya lo pone el encabezado, y sumados daban 24 px donde el
 * Lineamiento §7 pide 12. */
.card__head .card__title {
    margin-bottom: 0;
}

/*
 * El pie de la tarjeta: la fila de botones que cierra lo que la tarjeta ofrece.
 *
 * Va alineado al comienzo y no a la derecha —al contrario que `.modal__actions`—
 * porque una tarjeta no es un paso que se confirma y se cierra: es un bloque de
 * la pantalla, y el ojo que bajó leyendo por el borde izquierdo encuentra ahí el
 * botón sin tener que cruzar la tarjeta.
 */
.card__actions {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    flex-wrap: wrap;
    margin-top: var(--sp-4);
}

/*
 * El mismo bloque sirve para agrupar las acciones del encabezado —es lo que
 * hace `x-card` con su ranura `actions`— y ahí el aire de arriba sobra: el
 * encabezado ya alinea sus dos mitades, y 16 px de margen dejaban los botones
 * un renglón más abajo que el título con el que tienen que estar a la par.
 */
.card__head > .card__actions {
    margin-top: 0;
}

@media (max-width: 599.98px) {
    /* En celular el encabezado se apila: con 360 px de ancho, el título y el
     * botón uno al lado del otro dejan al título en dos palabras por renglón. */
    .card__head {
        flex-direction: column;
        align-items: stretch;
    }

    /* Y los botones del pie van a ancho completo, uno por renglón. Dos botones
     * a medio ancho quedan en 150 px y el rótulo se corta; apilados, además,
     * el pulgar no apunta al límite entre los dos. */
    .card__actions .button {
        width: 100%;
    }
}

/*
 * Tarjeta sin relleno.
 *
 * Es la tarjeta de un listado: la tabla, la lista de filas o la grilla llega
 * hasta el borde interno y las líneas divisorias cruzan de lado a lado, que es
 * lo que se espera de una lista. Con el relleno de la tarjeta, cada divisor
 * quedaba flotando con 16 px de blanco a cada punta y la lista se leía como un
 * montón de bloques sueltos.
 *
 * El `padding: 0` se repite en el tramo de escritorio porque ahí `.card` sube a
 * 24 px con el mismo peso de especificidad: sin repetirlo, la tarjeta se queda
 * sin relleno en celular y lo recupera a 768 px, que es el peor de los dos
 * mundos. El encabezado y el título, que siguen necesitando aire, lo recuperan
 * por su cuenta y solo como hijos directos: una tabla anidada adentro no tiene
 * por qué heredar el relleno de nadie.
 */
.card--flush {
    padding: 0;
}

.card--flush > .card__head,
.card--flush > .card__title {
    padding: var(--sp-4) var(--sp-4) 0;
}

@media (min-width: 768px) {
    .card--flush {
        padding: 0;
    }

    .card--flush > .card__head,
    .card--flush > .card__title {
        padding: var(--sp-5) var(--sp-5) 0;
    }
}

/* ══ 05-mensajes-y-chips.css ══ */

/*
 * Parte de `components.css`. Las partes se concatenan por orden de nombre;
 * el índice y el motivo del corte están en `components/00-indice.css`.
 */

/* ── Mensajes de estado ─────────────────────────────────────────────────── */

/*
 * Bloque, no caja flexible. Un aviso casi siempre es un párrafo con algo
 * resaltado adentro, y en `display: flex` cada trozo de texto suelto y cada
 * <strong> se convierten en un ítem distinto: el mensaje sale partido en
 * columnas y deja de leerse como una oración.
 */
.notice {
    display: block;
    padding: var(--sp-3) var(--sp-4);
    border-radius: var(--r-md);
    border-left: var(--bw-3) solid transparent;
    margin-bottom: var(--sp-4);
    font-size: var(--fs-body-dense);
    line-height: var(--lh-body-dense);
}

.notice--success {
    background-color: var(--ok-bg);
    border-left-color: var(--ok);
    color: var(--ok);
}

.notice--error {
    background-color: var(--err-bg);
    border-left-color: var(--err);
    color: var(--err);
}

.notice--info {
    background-color: var(--info-bg);
    border-left-color: var(--info);
    color: var(--info);
}

/*
 * Advertencia: algo quedó a medio hacer y va a dar un resultado raro, pero
 * nada falló todavía. No es un error —no hay nada que corregir para que la
 * pantalla ande— y por eso no usa el rojo, que en este sistema significa «esto
 * no se pudo hacer».
 */
.notice--warn {
    background-color: var(--warn-bg);
    border-left-color: var(--warn);
    color: var(--warn);
}

/* ── Chip ───────────────────────────────────────────────────────────────── */

.chip {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-2);
    height: 32px;
    padding: 0 var(--sp-3);
    border: 1px solid var(--n-300);
    border-radius: var(--r-md);
    background-color: var(--n-000);
    color: var(--n-900);
    font-size: var(--fs-label);
    font-weight: 600;
    /*
     * El chip tiene alto fijo, así que un texto que se parte en dos renglones
     * se desborda hacia arriba y hacia abajo del borde. Pasó de verdad con
     * «Se crea» en la columna angosta de la vista previa de importación. Un
     * chip es una etiqueta corta de un renglón: si no entra, lo que hay que
     * ensanchar es la columna, no partir la palabra.
     */
    white-space: nowrap;
}

/*
 * El Lineamiento (§7) fija 24 px de alto y una sola línea, y acá se cumplen las
 * dos cosas: `white-space: nowrap`. Un chip partido en dos o en cuatro líneas
 * deja de ser un chip —la cápsula se estira y se lee como un error de dibujo—,
 * así que la solución no es dejarlo crecer sino no meter en un chip una
 * etiqueta que no entra: quien tenga textos largos abrevia la etiqueta y deja
 * el texto completo en el `title` (ver `AuditAction::shortLabel()`) o ensancha
 * su columna.
 */
.chip--tag {
    display: inline-flex;
    align-items: center;
    height: 24px;
    padding: 0 var(--pad-chip-x);
    border-radius: var(--r-pill);
    white-space: nowrap;
    text-align: center;
    /* Mismo tratamiento que `.overline`: la etiqueta la escribe el
     * administrador y forzarle mayúsculas convierte un nombre en una sigla
     * (adenda de paleta y estética, punto 6). */
    font-size: var(--fs-caption);
    font-weight: 600;
    letter-spacing: .02em;
    background-color: var(--n-100);
    border-color: var(--n-200);
    color: var(--n-500);
}

/*
 * Tabla de auditoría.
 *
 * Es la tabla más ancha del sistema —seis columnas, una de ellas con texto
 * técnico de largo impredecible— y sin anchos declarados el navegador repartía
 * a ojo: «Detalle» se llevaba todo y «IP y sesión» quedaba cortada contra el
 * filo de la tarjeta, con los identificadores partidos al medio.
 *
 * `table-layout: fixed` reparte según lo que digan estos anchos y no según el
 * contenido, así que la última columna deja de depender de cuán largo sea el
 * hash de la fila de más arriba. «Detalle» es la única sin ancho: se queda con
 * lo que sobra, que es justo lo que se quiere.
 */
.data-table--audit {
    width: 100%;
    table-layout: fixed;
}

@media (min-width: 768px) {
    /* Fecha arriba, hora abajo: en un renglón la marca de tiempo mide más que
     * la columna y se desbordaba sobre el nombre del actor. */
    .data-table--audit th:nth-child(1),
    .data-table--audit td:nth-child(1) {
        width: 110px;
    }

    .data-table--audit th:nth-child(2),
    .data-table--audit td:nth-child(2) {
        width: 150px;
    }

    /* La cápsula de la acción no se parte en dos renglones (`nowrap`), así que
     * la columna tiene que medir lo que mide la etiqueta más larga de
     * `AuditAction::shortLabel()` en mayúsculas de 11 px. */
    .data-table--audit th:nth-child(3),
    .data-table--audit td:nth-child(3) {
        width: 190px;
    }

    .data-table--audit th:nth-child(5),
    .data-table--audit td:nth-child(5) {
        width: 110px;
    }

    /* La columna del origen: 150 px de caja menos los 16 px de relleno de cada
     * lado dejan 118 px útiles, que es lo que miden «170.245.234.207» en 12 px
     * y la etiqueta «Petición …XXXXXXXX» debajo. El relleno derecho es el
     * mismo de cualquier celda y es lo que impide que el dato toque el filo
     * interno de la tarjeta. */
    .data-table--audit th:nth-child(6),
    .data-table--audit td:nth-child(6) {
        width: 150px;
        padding-right: var(--sp-4);
    }
}

/*
 * Con la tabla de ancho fijo, lo que no entra en su columna no la agranda: se
 * desborda sobre la de al lado. La IP, el identificador de petición y los
 * códigos internos no son palabras y no tienen dónde cortar, así que se los
 * autoriza a partirse en cualquier letra antes que salirse.
 */
.data-table--audit td {
    overflow-wrap: anywhere;
}

/* «Fecha y hora» no entra en 110 px en una sola línea, y los encabezados de la
 * tabla no se parten por omisión: acá sí, antes que cortarse. */
.data-table.data-table--audit th {
    white-space: normal;
}

.audit-time {
    display: block;
    font-family: var(--font-data);
    font-variant-numeric: tabular-nums;
}

.audit-time__clock {
    display: block;
    color: var(--n-500);
    font-size: var(--fs-caption);
    line-height: var(--lh-caption);
}

/*
 * Tabla de estados configurables.
 *
 * El reparto va fijo: sin `table-layout: fixed` el navegador le da a cada
 * columna lo que su contenido pide, y la de edición —tres controles en un
 * renglón— terminaba con el nombre en 90 px y el desplegable de color
 * mostrando una sola letra. Los 430 px de esa columna son los que necesitan el
 * campo del nombre, el color y el botón uno al lado del otro; la descripción
 * del tipo es la que más cede, porque es texto de ayuda que se lee una vez y
 * no un dato que se compare entre filas.
 */
@media (min-width: 768px) {
    .data-table--statuses {
        table-layout: fixed;
    }

    /* 240 px es lo que mide la cápsula más larga que puede existir —«Aprobado
     * parcialmente»— sin partirse en dos renglones, más el aire de la celda. */
    /* El relleno lateral baja de 16 a 12 px en toda la tabla: son seis columnas
     * y cada píxel de más en el borde es uno de menos en el campo del nombre. */
    .data-table.data-table--statuses th,
    .data-table.data-table--statuses td {
        padding-right: var(--sp-3);
        padding-left: var(--sp-3);
    }

    /* Con los controles en un solo renglón y sin etiqueta por fila, el alto de
     * la celda lo fija el control de 44 px: 8 px arriba y abajo dan una fila de
     * 60 px, dentro de los 56–64 px pedidos. */
    /* Dos clases del mismo elemento por la misma razón que la ayuda recortada:
     * el `padding` general de `.data-table td` está escrito después y con un
     * selector del mismo peso le ganaba a este. */
    .data-table.data-table--statuses td {
        padding-top: var(--sp-2);
        padding-bottom: var(--sp-2);
        vertical-align: middle;
    }

    /*
     * Cinco anchos fijos y uno libre.
     *
     * Declarar los seis en píxeles parece más prolijo y es peor: si la suma no
     * coincide con el ancho real de la tarjeta —y no coincide nunca, porque
     * depende de la ventana— el navegador reparte la diferencia entre todas
     * las columnas por igual, encoge también las que no pueden encogerse y la
     * cápsula más larga o el botón «Desactivar» terminan saliéndose de la
     * tarjeta. Acá se fija lo que tiene medida propia y la columna de los
     * controles se queda con lo que sobre, sea cuanto sea.
     */
    /* 240 px es lo que mide la cápsula más larga que trae el sistema —«Aprobado
     * parcialmente», 208 px— más el relleno de los dos lados de la celda. Se
     * probó bajarla a 200 para darle aire al campo del nombre y la cápsula se
     * montaba sobre la columna de al lado: una cápsula no se parte en dos
     * renglones, así que el ancho lo manda ella. */
    .data-table--statuses th:nth-child(1),
    .data-table--statuses td:nth-child(1) {
        width: 240px;
    }

    .data-table--statuses th:nth-child(2),
    .data-table--statuses td:nth-child(2) {
        width: 120px;
    }

    /*
     * La descripción del tipo no se muestra en la tabla de escritorio.
     *
     * Era ella —no el campo de 44 px— la que fijaba el alto de la fila: con dos
     * renglones la fila llegaba a 70 px, y recortada a uno quedaba en «El
     * cliente…», que no dice nada. Una descripción que no se puede leer entera
     * ocupa lugar sin informar. El texto completo sigue en el `title` de la
     * celda, y en celular, donde la fila se vuelve tarjeta y hay ancho de
     * sobra, la descripción vuelve a verse.
     */
    /* Dos clases del mismo elemento, no una: más abajo `.data-table
     * .caption--clamp-2` le devuelve el `display` a la ayuda recortada, y con
     * el mismo peso gana la que está escrita después. */
    .data-table.data-table--statuses .caption--clamp-2 {
        display: none;
    }

    /* «Presup.» es una sola palabra y no se parte: la columna mide lo que mide
     * el encabezado, no el número, que nunca pasa de tres cifras. */
    .data-table--statuses th:nth-child(4),
    .data-table--statuses td:nth-child(4) {
        width: 80px;
    }

    /* Dos botones de ícono de 32 px con 8 px entre ellos. */
    .data-table--statuses th:nth-child(5),
    .data-table--statuses td:nth-child(5) {
        width: 88px;
    }

    /* «Desactivar» es el botón más ancho de la tabla y no se abrevia: la
     * columna mide lo que mide él. */
    .data-table--statuses th:nth-child(6),
    .data-table--statuses td:nth-child(6) {
        width: 140px;
    }

    /* El mínimo de la primera columna deja de mandar: acá el ancho lo fija el
     * reparto de la tabla. */
    .data-table--statuses .status-preview-column {
        min-width: 0;
    }
}

/* Con las columnas angostas, «Presupuestos» y «Cómo se ve» no entran en un
 * renglón, y un encabezado de tabla no se parte por omisión. */
.data-table.data-table--statuses th {
    white-space: normal;
}

/*
 * Pares clave/valor del detalle técnico.
 *
 * Van en el tipo de datos a 12 px y en gris terciario porque son el segundo
 * plano de la celda: lo que se lee primero es el resumen en prosa que está
 * arriba. Se muestran en línea, separados por un punto medio, y el bloque se
 * corta a dos renglones —antes una sola fila de auditoría ocupaba tres—. El
 * valor completo de cada par está en su `title`.
 */
.audit-detail__values {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    overflow: hidden;
    margin-top: var(--sp-1);
    font-family: var(--font-data);
    font-size: var(--fs-caption);
    line-height: var(--lh-caption);
    color: var(--n-500);
}

.audit-detail__pair {
    overflow-wrap: anywhere;
}

.audit-detail__pair:not(:last-child)::after {
    content: " · ";
    color: var(--n-300);
}

/* «antes» y «después» encabezan su grupo de pares una sola vez, con un poco más
 * de peso para que se vea dónde empieza cada uno. */
.audit-detail__prefix {
    margin-right: var(--sp-1);
    font-weight: 600;
    color: var(--n-400);
}

.audit-detail__prefix:not(:first-child) {
    margin-left: var(--sp-2);
}

/* El identificador de pedido va recortado a sus últimos ocho caracteres y no se
 * parte: el valor completo está en el `title`. */
/*
 * Última columna de la auditoría: la dirección IP arriba y el identificador de
 * la petición abajo.
 *
 * No es una columna de importes, así que no va alineada a la derecha ni en el
 * cuerpo de 14 px de la tabla: la IP va en el tipo de datos a 12 px, que es lo
 * que la vuelve comparable de un renglón a otro, y el identificador debajo en
 * Caption, dicho con todas las letras. Antes se leía «…XVPENNAK» a secas y no
 * había forma de saber qué era ese texto.
 */
.audit-origin__ip {
    display: block;
    font-family: var(--font-data);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-caption);
    line-height: var(--lh-caption);
    color: var(--n-700);
    white-space: nowrap;
}

/*
 * El identificador de la petición.
 *
 * Se muestran los últimos ocho caracteres, no porque no entren los treinta y
 * dos, sino porque son los que cambian de una línea a otra y los que alcanzan
 * para buscar la petición en el registro del servidor. Eso, escrito a secas
 * como «…9FWHZEM9», se lee como un texto cortado por falta de lugar. Por eso
 * la palabra «Petición» va delante, visible: la abreviatura pasa a ser una
 * decisión declarada y no un accidente. El valor entero sigue en el `title`.
 *
 * `break-all` en lugar de `nowrap`: si algún día el identificador cambia de
 * largo, se parte dentro de su columna en vez de desbordarse sobre la tarjeta.
 */
.audit-request {
    display: block;
    word-break: break-all;
}

/*
 * La palabra «Petición» ocupa su propio renglón.
 *
 * En línea con el identificador, la columna de 150 px no alcanzaba para los
 * dos y el corte caía dentro del identificador: quedaba una última letra
 * suelta abajo, que es exactamente lo que se lee como «acá se cortó algo».
 * Separados, la etiqueta entra sola arriba y el identificador entero abajo.
 */
.audit-request__tag {
    display: block;
    color: var(--n-400);
    word-break: normal;
}

/* Regla de alcance de una campaña: el modo arriba, el destino abajo. */
.scope-rule {
    display: block;
}

.scope-rule + .scope-rule {
    margin-top: var(--sp-2);
}

.scope-rule .overline {
    display: block;
    color: var(--n-500);
}

.scope-rule .caption {
    display: block;
    color: var(--n-700);
}

/*
 * Columna «Cómo se ve» de la configuración de estados. Lleva ancho mínimo
 * propio porque el chip que muestra ya no se parte en dos líneas: sin reservarle
 * lugar, «APROBADO PARCIALMENTE» empujaría al resto de la tabla.
 */
.status-preview-column {
    min-width: 180px;
}

/* ── Chips de estado semánticos (Lineamiento §7) ────────────────────────── */
/*
 * El color refuerza, nunca reemplaza a la palabra: el punto y el fondo
 * acompañan a un texto que ya dice de qué se trata. Por eso el chip no tiene
 * variante "solo color".
 */

.chip--info { background-color: var(--info-bg); border-color: var(--info-bg); color: var(--info); }
.chip--ok { background-color: var(--ok-bg); border-color: var(--ok-bg); color: var(--ok); }
.chip--warn { background-color: var(--warn-bg); border-color: var(--warn-bg); color: var(--warn); }
.chip--err { background-color: var(--err-bg); border-color: var(--err-bg); color: var(--err); }

/* Punto de 6 px del color semántico, a la izquierda de la palabra. Hereda
 * `currentColor`, así que sirve para cualquiera de los tonos sin declarar una
 * regla por variante. */
.chip__dot {
    width: 6px;
    height: 6px;
    flex: none;
    border-radius: var(--r-pill);
    background-color: currentColor;
}

/* Punto semirrelleno para «Aprobado parcialmente»: la forma distingue el estado
 * intermedio del aprobado pleno incluso en blanco y negro. */
.chip__dot--half {
    background: linear-gradient(90deg, currentColor 50%, transparent 50%);
    box-shadow: inset 0 0 0 1px currentColor;
}

/* Borde del propio color: lo piden «Aprobado parcialmente», «Vencido» y
 * «Cancelado», que necesitan distinguirse de un estado de la misma familia. */
.chip--outline { border-color: currentColor; }

/* Estado pleno: fondo sólido y texto blanco. Reservado a los estados
 * terminales favorables —«Aprobado», «Entregado»—, que son los únicos que
 * conviene que salten a la vista en una lista larga. */
.chip--ok-solid {
    background-color: var(--ok);
    border-color: var(--ok);
    color: var(--n-000);
}

/*
 * Paleta cerrada para los estados que crea el administrador (Lineamiento §7).
 * Elige uno de estos ocho tonos y no un color libre: así ningún estado
 * inventado rompe el contraste. Cada clase se genera desde el mismo par de
 * variables, de modo que agregar un noveno tono es agregarlo en `tokens.css`.
 */
.chip--state-1 { background-color: var(--state-1-bg); border-color: var(--state-1-bg); color: var(--state-1); }
.chip--state-2 { background-color: var(--state-2-bg); border-color: var(--state-2-bg); color: var(--state-2); }
.chip--state-3 { background-color: var(--state-3-bg); border-color: var(--state-3-bg); color: var(--state-3); }
.chip--state-4 { background-color: var(--state-4-bg); border-color: var(--state-4-bg); color: var(--state-4); }
.chip--state-5 { background-color: var(--state-5-bg); border-color: var(--state-5-bg); color: var(--state-5); }
.chip--state-6 { background-color: var(--state-6-bg); border-color: var(--state-6-bg); color: var(--state-6); }
.chip--state-7 { background-color: var(--state-7-bg); border-color: var(--state-7-bg); color: var(--state-7); }
.chip--state-8 { background-color: var(--state-8-bg); border-color: var(--state-8-bg); color: var(--state-8); }

/*
 * Variante plena de cada tono, para los estados terminales favorables
 * —«Aprobado», «Entregado»— que el Lineamiento §7 pide que salten a la vista en
 * una lista larga. Va después de `.chip--ok-solid` y con dos clases de
 * especificidad, así que gana sobre el fondo suave del mismo tono sin depender
 * del orden en que la plantilla las escriba.
 *
 * Los ocho pares texto/fondo dan 4.6:1 o más con blanco encima, así que
 * cualquier estado que el administrador cree en cualquier tono cumple el
 * contraste mínimo del Lineamiento §10 sin que él tenga que verificar nada.
 */
.chip--state-1.chip--solid { background-color: var(--state-1); border-color: var(--state-1); color: var(--n-000); }
.chip--state-2.chip--solid { background-color: var(--state-2); border-color: var(--state-2); color: var(--n-000); }
.chip--state-3.chip--solid { background-color: var(--state-3); border-color: var(--state-3); color: var(--n-000); }
.chip--state-4.chip--solid { background-color: var(--state-4); border-color: var(--state-4); color: var(--n-000); }
.chip--state-5.chip--solid { background-color: var(--state-5); border-color: var(--state-5); color: var(--n-000); }
.chip--state-6.chip--solid { background-color: var(--state-6); border-color: var(--state-6); color: var(--n-000); }
.chip--state-7.chip--solid { background-color: var(--state-7); border-color: var(--state-7); color: var(--n-000); }
.chip--state-8.chip--solid { background-color: var(--state-8); border-color: var(--state-8); color: var(--n-000); }

/*
 * Estado desactivado, solo en la pantalla de configuración: conserva su color
 * para que el administrador lo reconozca, atenuado para que se lea que ya no se
 * ofrece. Nunca va solo: la fila dice «Inactivo» con todas las letras, porque el
 * color por sí mismo no comunica un estado (Lineamiento §10). La atenuación es
 * suave a propósito —el texto sigue por encima de 4.5:1 sobre el fondo blanco de
 * la fila— así que no es un elemento deshabilitado, es uno en reposo.
 */
.chip--muted { opacity: 0.7; }

/*
 * Tono neutro con nombre propio, para los estados que no dicen ni bien ni mal.
 * Hoy lo usa el pedido a la fábrica que el vendedor dio de baja: no es un error
 * —nadie hizo nada mal— ni una advertencia —no hay nada que atender—, y pintarlo
 * de cualquiera de los dos colores semánticos haría que la bandeja pareciera
 * tener un problema que no tiene.
 *
 * Es el par `--n-500` sobre `--n-100` que el Lineamiento §7 asigna a
 * «Cancelado», escrito una sola vez.
 */
.chip--neutral {
    background-color: var(--n-100);
    border-color: var(--n-300);
    color: var(--n-500);
}

/* ── Aviso con título y aviso de contenido generado por IA (Etapa 2) ────────
 *
 * `.notice` nació como un párrafo suelto y alcanzaba mientras el aviso fuera
 * una oración. Dejó de alcanzar cuando el aviso tiene que decir dos cosas —qué
 * pasó y qué hacer—: escritas en un solo bloque, el ojo las lee como una sola
 * frase larga y se saltea la segunda.
 */

/*
 * El aviso con ícono y cuerpo es una fila; el aviso de una sola oración sigue
 * siendo un bloque.
 *
 * `.notice` es `display: block` a propósito —el motivo está arriba, en su propia
 * regla: en `flex` cada trozo de texto suelto y cada `<strong>` del mensaje se
 * vuelve un ítem y la oración sale partida en columnas—. Pero `x-notice` pone el
 * ícono como hermano del cuerpo, y en un bloque ese ícono se va solo a su propio
 * renglón, arriba del texto y alineado con nada.
 *
 * La fila se enciende entonces únicamente cuando el aviso trae la estructura de
 * la Etapa 2, que es cuando hay exactamente dos ítems que ordenar y ningún texto
 * suelto que partir. `:has()` es lo que permite preguntarlo sin tocar `.notice`
 * ni agregarle una clase al aviso heredado, que son decenas en las vistas
 * actuales: el que no tiene `.notice__body` no cambia en nada.
 *
 * `flex-start` y no `center`: el ícono acompaña a la primera línea, y en un
 * aviso de tres renglones centrarlo verticalmente lo deja flotando al medio,
 * lejos del texto que califica.
 */
.notice:has(> .notice__body) {
    display: flex;
    align-items: flex-start;
    gap: var(--sp-2);
}

/*
 * El ícono se alinea con la primera línea, y lo hace sin un margen de
 * corrección.
 *
 * Arriba a secas el ícono queda pegado al borde de su caja y el texto de al
 * lado empieza medio renglón más abajo, porque la línea de 20 px deja aire
 * arriba y abajo del glifo de 16. La corrección obvia sería un `margin-top` de
 * 2 px, pero 2 px no está en la escala de espaciado y la prueba
 * `SpacingScaleTest` lo rechaza —con razón: una vez que se admite un número
 * suelto, se admiten todos—.
 *
 * Así que en lugar de empujar el ícono se le da a su caja el alto de la línea
 * que acompaña: el dibujo sigue midiendo 16 px de lado y se centra solo dentro
 * de los 20 px de alto, que es exactamente donde está el centro óptico de la
 * primera línea. La medida sale del mismo token que fija esa línea, así que si
 * la interlínea cambia, el ícono la sigue.
 *
 * Quien conserva la proporción es el `<symbol viewBox="0 0 24 24">` del sprite,
 * no el `<svg>` que lo usa: ese `<svg>` no lleva `viewBox` propio, así que su
 * `preserveAspectRatio` no interviene. Vale anotarlo porque es lo que hace que
 * estirar la caja a 16 × 20 no deforme el glifo.
 */
.notice:has(> .notice__body) > .icon {
    height: var(--lh-body-dense);
}

/* La primera línea del aviso: el hecho, en una oración corta. No sube de
 * tamaño, sube de peso. Un aviso es ya un bloque con fondo propio y borde de
 * acento; agregarle un nivel de título lo convierte en una sección. */
.notice__title {
    display: block;
    margin-bottom: var(--sp-1);
    font-weight: 600;
}

/* El cuerpo hereda la tinta del aviso —la del tono, no una propia— para que el
 * verde, el rojo y el ámbar sigan gobernando el bloque entero. */
.notice__body {
    display: block;
    color: inherit;
}

/*
 * Contenido generado por IA (Lineamiento §8).
 *
 * El rótulo es obligatorio y no es un adorno: el sistema usa el asistente para
 * redactar descripciones, sugerir precios y resumir actividad, y en todos esos
 * casos el texto que el vendedor lee es una propuesta, no un dato del sistema.
 * La IA asiste; no es fuente de verdad. Si el bloque no dice de dónde salió lo
 * que se está leyendo, el vendedor le va a dar el mismo crédito que a un total
 * calculado, y eso es exactamente lo que el §8 prohíbe.
 *
 * Usa el tono informativo y no uno propio: no hay nada que celebrar ni que
 * advertir en que un texto venga del asistente, y un sexto color para esto
 * vuelve el aviso de IA más llamativo que un error de verdad.
 */
.notice--ai {
    background-color: var(--info-bg);
    border-left-color: var(--info);
}

/*
 * El rótulo mismo. Lleva la pinta de `.overline` escrita acá y no la clase,
 * porque tiene una diferencia deliberada: conserva las mayúsculas y el
 * interletrado ancho que `.overline` dejó de usar cuando la adenda de paleta
 * pidió «menos mayúsculas innecesarias» (punto 6).
 *
 * La excepción se sostiene en que ahí el argumento era otro: el rótulo que se
 * ablandó es el que escribe el administrador —un nombre propio en versalitas se
 * lee como una sigla—, y este es un texto fijo del sistema, de tres palabras,
 * que tiene que leerse como una marca de procedencia y no como la primera línea
 * del mensaje. A 12 px sigue por encima del mínimo legible del Lineamiento §4.
 */
.notice__ai-tag {
    display: block;
    margin-bottom: var(--sp-1);
    font-size: var(--fs-overline);
    line-height: var(--lh-overline);
    font-weight: 700;
    letter-spacing: .10em;
    text-transform: uppercase;
    color: var(--info);
}

/*
 * Chip de campaña vigente.
 *
 * Reusa tal cual el par del estado 6 —el ámbar oscuro sobre crema que lleva «En
 * producción»— y no un tono nuevo. Dos razones: ese par ya está medido por
 * `PaletteContrastTest` (4,5:1 o más, igual que los otros siete), y la campaña
 * pertenece a la misma familia de significado que «En producción» —algo que
 * está corriendo ahora y tiene fecha de fin—, así que compartir el color dice
 * algo en lugar de ocupar un lugar más en el vocabulario.
 *
 * Se escribe como regla propia y no como `.chip--state-6` en la plantilla
 * porque una campaña no es un estado configurable: el administrador no la eligió
 * de la paleta cerrada y no puede cambiarle el tono.
 */
.chip--campaign {
    background-color: var(--state-6-bg);
    border-color: var(--state-6-bg);
    color: var(--state-6);
}

/* ══ 06-estado-del-presupuesto.css ══ */

/*
 * Parte de `components.css`. Las partes se concatenan por orden de nombre;
 * el índice y el motivo del corte están en `components/00-indice.css`.
 */

/* ── La tarjeta de estado del presupuesto (Especificación §14) ──────────── */
/*
 * El estado es el dato que ordena todo lo demás de la ficha: de él dependen si
 * los ítems se editan, si se puede generar el pedido y qué se le puede ofrecer
 * al cliente. Era un chip de 24 px en una fila compartida con el número de
 * revisión y tres botones, y en una pantalla que sigue con la vigencia, los
 * ítems, el desglose y la aprobación, se perdía.
 *
 * Ahora tiene tarjeta propia. No se agranda el chip —el Lineamiento §7 lo fija
 * en 24 px y en Overline de 11 px, y agrandarlo acá lo desalinearía del que se
 * ve en el listado, que es el mismo estado— : lo que cambia es el contexto. El
 * chip queda solo en su renglón, con una etiqueta arriba que dice de qué se
 * está hablando y una frase abajo que explica qué significa estar ahí.
 */
.status-card {
    border-left: var(--bw-3) solid var(--n-300);
}

/*
 * La franja izquierda toma el tono del estado. Es refuerzo y nunca información:
 * el nombre está escrito en el chip y la explicación debajo, así que quien no
 * distinga el color no se pierde nada (Lineamiento §10). Son los ocho tonos de
 * la paleta cerrada que el administrador puede asignar, y ninguno más.
 */
.status-card--tone-1 { border-left-color: var(--state-1); }
.status-card--tone-2 { border-left-color: var(--state-2); }
.status-card--tone-3 { border-left-color: var(--state-3); }
.status-card--tone-4 { border-left-color: var(--state-4); }
.status-card--tone-5 { border-left-color: var(--state-5); }
.status-card--tone-6 { border-left-color: var(--state-6); }
.status-card--tone-7 { border-left-color: var(--state-7); }
.status-card--tone-8 { border-left-color: var(--state-8); }

.status-card__head {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-2);
    margin-top: var(--sp-2);
}

/*
 * La frase que dice qué significa el estado. Va en cuerpo de texto y no en
 * `caption`: es lo primero que lee alguien que abre el presupuesto sin haberlo
 * armado él, y un gris de 12 px lo trataría como una nota al pie.
 */
.status-card__meaning {
    margin-top: var(--sp-2);
    color: var(--n-700);
    max-width: 68ch;
}

/* ── Cambio de estado (Especificación §14 y §18) ────────────────────────── */
/*
 * Una lista de destinos, uno por fila, cada uno con el chip que va a quedar y
 * una línea que dice qué implica. Reemplaza al desplegable «Pasar a»: el
 * vendedor ve las opciones sin abrir nada y sin tener que adivinar qué hace
 * cada nombre.
 *
 * Se apila siempre, también en escritorio. Poner los destinos en dos columnas
 * entraría, pero acá el orden importa —«Enviado» antes que «Perdido»— y una
 * grilla de dos columnas lo lee mal: la mirada baja por la izquierda y se
 * saltea la mitad de las opciones.
 */
.status-actions {
    display: grid;
    gap: var(--sp-2);
}

/*
 * El disparador del desplegable. Es el `x-disclosure-button` de siempre; lo
 * único que agrega esta clase es el aire de arriba, para que no quede pegado a
 * la explicación del estado, y el ancho completo en el celular: un control que
 * abre media pantalla no puede medir 180 px y quedar perdido a la izquierda.
 */
.status-change__picker {
    margin-top: var(--sp-5);
}

@media (max-width: 767.98px) {
    .status-change__picker > .disclosure__summary {
        display: flex;
        width: 100%;
        justify-content: space-between;
    }
}

/*
 * Es un `<button type="submit">`, así que hay que deshacerle el aspecto de
 * botón nativo y volver a armarlo: el texto va alineado a la izquierda porque
 * son dos renglones de distinta longitud, y centrarlos haría zigzaguear la
 * lectura.
 *
 * `min-height: 56px` deja el área táctil bien por encima de los 44 px que pide
 * el Lineamiento §10, que es lo que corresponde a un control que dispara un
 * cambio de estado con el cliente al lado.
 */
.status-action {
    display: grid;
    grid-template-columns: 1fr auto;
    align-items: center;
    gap: var(--sp-3);
    width: 100%;
    min-height: 56px;
    background-color: var(--n-000);
    border: 1px solid var(--n-300);
    border-radius: var(--r-md);
    padding: var(--sp-3) var(--sp-4);
    font-family: var(--font-ui);
    text-align: left;
    cursor: pointer;
    transition: background-color 0.12s ease, border-color 0.12s ease;
}

.status-action:hover {
    background-color: var(--n-100);
    border-color: var(--n-400);
}

.status-action__body {
    display: grid;
    gap: var(--sp-2);
    justify-items: start;
}

.status-action__hint {
    color: var(--n-500);
    font-size: var(--fs-label);
    line-height: var(--lh-label);
}

/*
 * La flecha dice «esto lleva a algún lado» sin agregar una palabra más. Es
 * decorativa: el nombre del estado y la línea de abajo ya cuentan todo, así que
 * no aporta nada a un lector de pantalla.
 */
.status-action__chevron {
    color: var(--n-400);
    flex-shrink: 0;
}

/*
 * Los destinos que dan de baja se marcan con el borde de error, no con fondo
 * rojo: el Lineamiento §7 reserva el fondo pleno para la confirmación dentro
 * del modal. El color nunca va solo —el chip dice «Cancelado» con todas las
 * letras y la línea de abajo explica la consecuencia—, así que cumple §10.
 */
.status-action--danger {
    border-color: var(--err);
}

.status-action--danger:hover {
    background-color: var(--err-bg);
    border-color: var(--err);
}

/*
 * El motivo es opcional y casi nunca se escribe, así que arranca plegado. El
 * `<details>` nativo ya trae el manejo de teclado y el estado expandido para el
 * lector de pantalla.
 */
.status-actions__note {
    margin-top: var(--sp-3);
}

.status-actions__summary {
    color: var(--n-700);
    font-family: var(--font-ui);
    font-size: var(--fs-label);
    font-weight: 600;
    letter-spacing: 0.01em;
    padding: var(--sp-2) 0;
    cursor: pointer;
}

.status-actions__note[open] .status-actions__summary {
    margin-bottom: var(--sp-2);
}

/* ══ 07-tablas.css ══ */

/*
 * Parte de `components.css`. Las partes se concatenan por orden de nombre;
 * el índice y el motivo del corte están en `components/00-indice.css`.
 */

/* ── Tablas de datos (Lineamiento §7) ───────────────────────────────────── */
/*
 * Encabezado Overline sobre --n-100 y pegado al hacer scroll, filas separadas
 * por borde y sin franjas alternas. El realce al pasar el puntero ayuda a
 * seguir la fila de punta a punta en pantallas anchas.
 */

/*
 * `overflow-x: auto` y no `hidden`.
 *
 * Con `hidden`, una tabla más ancha que su contenedor no se acomoda: se corta, y
 * lo que queda del otro lado del borde no se puede leer ni tocar. En el catálogo
 * pasaba exactamente eso en una notebook de 1366 px: el botón de la última
 * columna quedaba partido al medio y era inalcanzable. Cortado en silencio es la
 * peor de las opciones —nada avisa de que falta algo—; con `auto`, en el peor
 * caso hay una barra de desplazamiento, que sí se ve.
 *
 * El redondeo de las esquinas se sigue respetando igual.
 */
.data-table__wrap {
    background-color: var(--n-000);
    border: 1px solid var(--n-200);
    border-radius: var(--r-lg);
    overflow-x: auto;
}

.data-table {
    width: 100%;
    border-collapse: collapse;
    font-family: var(--font-data);
    font-size: var(--fs-body-dense);
    line-height: var(--lh-body-dense);
}

.data-table th {
    position: sticky;
    top: 0;
    z-index: 1;
    background-color: var(--n-100);
    border-bottom: 1px solid var(--n-300);
    padding: var(--sp-3) var(--sp-4);
    text-align: left;
    color: var(--n-500);
    font-family: var(--font-ui);
    font-size: var(--fs-overline);
    line-height: var(--lh-overline);
    font-weight: 700;
    letter-spacing: .10em;
    text-transform: uppercase;
    white-space: nowrap;
}

.data-table td {
    border-bottom: 1px solid var(--n-200);
    padding: var(--sp-3) var(--sp-4);
    vertical-align: top;
    color: var(--n-700);
}

/*
 * Encabezado de fila (`<th scope="row">`): es la celda que nombra la fila —el
 * cliente, el producto— y para un lector de pantalla vale como encabezado. Lo
 * que no puede es verse como el encabezado de columna: en mayúsculas de 11 px y
 * sobre fondo gris, el dato principal de la fila quedaría ilegible. Se lo
 * devuelve al aspecto de una celda común, con un poco más de peso porque es el
 * dato por el que se busca la fila con la vista.
 */
.data-table tbody th {
    position: static;
    z-index: auto;
    background-color: transparent;
    border-bottom: 1px solid var(--n-200);
    padding: var(--sp-3) var(--sp-4);
    vertical-align: top;
    color: var(--n-900);
    font-family: var(--font-data);
    font-size: var(--fs-body-dense);
    line-height: var(--lh-body-dense);
    font-weight: 600;
    letter-spacing: normal;
    text-transform: none;
    white-space: normal;
}

.data-table tbody tr:last-child td,
.data-table tbody tr:last-child th {
    border-bottom: 0;
}

.data-table tbody tr:hover {
    background-color: var(--row-hover);
}

.data-table__empty {
    padding: var(--sp-6) var(--sp-4);
    text-align: center;
    color: var(--n-500);
}

/*
 * En móvil la tabla no se desplaza en horizontal: cada fila se apila como una
 * tarjeta con etiqueta y valor (Lineamiento §7). La etiqueta sale del atributo
 * `data-label` de cada celda, así que no hay que repetir el texto en el HTML.
 *
 * Y son tarjetas de verdad, sueltas, no renglones apilados dentro de un mismo
 * recuadro. Antes lo único que separaba un presupuesto del siguiente era una
 * línea de 1 px, y en una pantalla de 390 px —donde cada fila ocupa siete
 * renglones— dos presupuestos consecutivos se leían como uno solo: el vendedor
 * no sabía dónde terminaba el total de uno y empezaba el número del otro. Con
 * fondo propio, borde propio y aire entre medio, cada uno es un objeto y la
 * lista se recorre de un vistazo. Sigue siendo una `<table>` con sus
 * encabezados: el lector de pantalla no pierde nada.
 */
@media (max-width: 767.98px) {
    .data-table thead {
        display: none;
    }

    /* El recuadro común desaparece: el borde ahora lo lleva cada tarjeta. */
    .data-table__wrap {
        background-color: transparent;
        border: 0;
        border-radius: 0;
        overflow-x: visible;
    }

    .data-table,
    .data-table tbody,
    .data-table td,
    .data-table tbody th {
        display: block;
        width: 100%;
    }

    /* Grilla y no bloque para poder reordenar: en la tarjeta el estado va
     * pegado al número y no en el medio de la lista de datos. */
    .data-table tr {
        display: grid;
        width: 100%;
        margin-bottom: var(--sp-3);
        background-color: var(--n-000);
        border: 1px solid var(--n-200);
        border-radius: var(--r-lg);
        padding: var(--sp-3) 0;
    }

    .data-table tbody tr:last-child {
        margin-bottom: 0;
    }

    /*
     * El resalte al pasar por encima no existe en una pantalla táctil: el dedo
     * lo deja pegado en la última fila tocada, que queda marcada como si fuera
     * la seleccionada. La tarjeta se distingue por su borde, no por su fondo.
     */
    .data-table tbody tr:hover {
        background-color: var(--n-000);
    }

    /*
     * `overflow-wrap: anywhere` porque hay datos que no tienen dónde cortarse y
     * en 390 px no entran: un email como
     * `compras@amoblamientosdelsur.demo.local` es una sola palabra de treinta y
     * ocho caracteres y se salía de la tarjeta, tapado por el borde. Cortarlo
     * donde caiga es feo; perderlo es peor.
     */
    .data-table td,
    .data-table tbody th {
        display: grid;
        grid-template-columns: 40% 1fr;
        gap: var(--sp-3);
        border-bottom: 0;
        padding: var(--sp-2) var(--sp-4);
        text-align: left;
        overflow-wrap: anywhere;
    }

    /*
     * Todo el contenido de la celda vive en la segunda columna, debajo de sí
     * mismo. Sin esto, la celda que trae dos cosas —el local y su vendedor, la
     * barra de vigencia y su fecha— mandaba la segunda a la fila siguiente
     * empezando en la columna de la etiqueta: el dato quedaba corrido a la
     * izquierda, alineado con nada.
     */
    .data-table td > *,
    .data-table tbody th > * {
        grid-column: 2;
    }

    /*
     * Tres celdas se salen del par etiqueta/valor porque no son un dato más:
     *
     * - `--title` es el nombre de la tarjeta —el número del presupuesto, el
     *   código del producto—. Va arriba de todo, sin etiqueta y con cuerpo de
     *   título: es lo que el vendedor busca con la vista.
     * - `--badge` es el estado. El chip ya dice la palabra completa, así que
     *   una etiqueta «ESTADO» al lado repite. Se sube junto al título.
     * - `--actions` es el botón de abrir. Va al pie, separado por una línea y a
     *   todo el ancho, que es el gesto natural del pulgar y el área táctil de
     *   44 px que pide el Lineamiento §10.
     *
     * En el escritorio estas clases no hacen nada: la tabla sigue siendo una
     * tabla.
     */
    .data-table td.data-table__cell--title,
    .data-table td.data-table__cell--badge,
    .data-table td.data-table__cell--actions {
        display: block;
    }

    .data-table td.data-table__cell--title::before,
    .data-table td.data-table__cell--badge::before,
    .data-table td.data-table__cell--actions::before {
        content: none;
    }

    .data-table td.data-table__cell--title {
        order: -2;
        padding-bottom: 0;
        color: var(--n-900);
        font-family: var(--font-ui);
        font-size: var(--fs-h3);
        line-height: var(--lh-h3);
        font-weight: 600;
    }

    .data-table td.data-table__cell--badge {
        order: -1;
        padding-top: var(--sp-2);
        padding-bottom: var(--sp-2);
    }

    .data-table td.data-table__cell--actions {
        margin-top: var(--sp-2);
        border-top: 1px solid var(--n-200);
        padding-top: var(--sp-3);
    }

    .data-table td.data-table__cell--actions .button {
        width: 100%;
        justify-content: center;
    }

    /*
     * Con más de una acción en el pie de la tarjeta —escribirle por WhatsApp y
     * abrir la ficha— la fila se apila: dos botones a medio ancho quedan por
     * debajo del área táctil de 44 px que pide el Lineamiento §10.
     */
    .data-table td.data-table__cell--actions .row-actions {
        flex-direction: column;
        align-items: stretch;
    }

    /*
     * El renglón de «no hay resultados» no es un dato con etiqueta: es un
     * mensaje. Apilado como el resto quedaba corrido al 60 % del ancho, con una
     * columna de etiqueta vacía a la izquierda. Ocupa la tarjeta entera.
     */
    .data-table td.data-table__empty {
        display: block;
        text-align: center;
    }

    .data-table td.data-table__empty::before {
        content: none;
    }

    .data-table td::before,
    .data-table tbody th::before {
        content: attr(data-label);
        color: var(--n-500);
        font-family: var(--font-ui);
        font-size: var(--fs-overline);
        line-height: var(--lh-body-dense);
        font-weight: 700;
        letter-spacing: .10em;
        text-transform: uppercase;
    }
}

/* ══ 08-listados.css ══ */

/*
 * Parte de `components.css`. Las partes se concatenan por orden de nombre;
 * el índice y el motivo del corte están en `components/00-indice.css`.
 */

/* ── Barra de filtros ───────────────────────────────────────────────────── */

/*
 * Grilla y no flex: con flex, cada fila reparte el sobrante entre los campos que
 * le tocaron y las columnas terminan de anchos distintos fila por fila. Con
 * nueve filtros —los cinco de la Especificación §23 más familia, producto,
 * año y período— eso se ve como una escalera. `auto-fit` mantiene la columna
 * quieta y `align-items: end` deja todos los controles apoyados en la misma
 * línea aunque uno tenga texto de ayuda debajo de la etiqueta.
 */
/*
 * Cuatro columnas y no seis. La medida no es estética: con 180 px de mínimo, en
 * una pantalla de 1440 entraban seis desplegables por fila y el navegador
 * recortaba el texto de la opción elegida —«Todos los vendedo», «Todas las
 * campañ»—. Un filtro cuyo valor no se puede leer no es un filtro. Con 220 px
 * de mínimo y un espacio de 24 px, el valor por omisión de cada filtro entra
 * entero.
 */
.filter-bar {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
    align-items: start;
    row-gap: var(--sp-5);
    column-gap: var(--sp-5);
    margin-bottom: var(--sp-4);
}

/*
 * Cuatro columnas exactas, para la barra de nueve filtros del panel.
 *
 * `auto-fit` decide la cantidad de columnas según el ancho disponible, y eso
 * está bien donde los filtros son pocos. Con nueve, la última fila quedaba con
 * un solo desplegable y el botón a media pantalla. Fijando cuatro columnas la
 * grilla da 4 + 4 + 1, y el botón se manda a la última columna de la tercera
 * fila: queda alineado con el borde derecho del bloque, a la misma altura que
 * el filtro que cierra la lista.
 *
 * Cuatro columnas fijas recién en escritorio. Estaban declaradas desde 769 px,
 * donde el área de contenido mide 521 px y cada filtro quedaba en 130 px: más
 * angosto que el `minmax(190px)` que la regla venía a mejorar, con los nombres
 * de los estados cortados. A 1024 px cada columna mide 194 px y la mejora es
 * real.
 */
@media (min-width: 1024px) {
    .filter-bar--fixed-4 {
        grid-template-columns: repeat(4, minmax(0, 1fr));
    }

    .filter-bar--fixed-4 .filter-bar__actions {
        grid-column: 4;
        justify-content: flex-end;
    }
}

.filter-bar .field {
    min-width: 0;
    margin-bottom: 0;
}

/*
 * Todos los filtros ocupan el mismo alto, tengan o no texto de ayuda.
 *
 * Sin esto, el único filtro con ayuda —«Producto»— estiraba su fila entera y la
 * separación entre filas pasaba de 24 px a 94 px en un solo salto: la barra se
 * leía como tres bloques sueltos en lugar de una grilla. El espacio de la ayuda
 * se reserva en las nueve celdas, con el alto de dos renglones de 12 px, y las
 * filas vuelven a estar todas a la misma distancia.
 */
.filter-bar .field__hint {
    min-height: 32px;
    margin-top: var(--sp-2);
}

.filter-bar .field:not(:has(.field__hint))::after {
    content: "";
    display: block;
    min-height: 32px;
    margin-top: var(--sp-2);
}

/* El control ocupa toda su columna: si se encoge al ancho de su contenido,
 * vuelve el recorte que esta regla existe para evitar. */
.filter-bar .field__control {
    width: 100%;
    height: var(--control-h);
}

/*
 * Los filtros se alinean por arriba y no por abajo: alguno tiene texto de ayuda
 * debajo del control y, alineados por abajo, ese campo levantaba su desplegable
 * por encima de los demás. El botón compensa a mano el alto de una etiqueta
 * —18 px de línea más 4 px de separación— para quedar apoyado en la misma
 * línea que los desplegables de su fila.
 */
/*
 * Los botones se apilan antes que desbordar. Una celda de la barra mide 220 px
 * de mínimo y en el tramo del armazón compacto hay dos: con el cuerpo de 16 px
 * la pareja «Buscar» + «Cliente nuevo» pide 23 px más de los que tiene la
 * celda, y sin `wrap` esos píxeles salían del documento —7 px de barra
 * horizontal medidos en `/presupuestos/nuevo` a 768 px—. Encogerlos no es
 * opción: el rótulo de un botón recortado deja al vendedor adivinando.
 */
.filter-bar__actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-2);
    margin-top: calc(18px + var(--sp-1));
}

/* ── Paginación ─────────────────────────────────────────────────────────── */
/*
 * Se arma a mano y no con el paginador del framework: el que trae Laravel emite
 * marcado con clases de Tailwind, que este proyecto no usa.
 */

.pagination {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-3);
    margin-top: var(--sp-4);
}

.pagination__pages {
    color: var(--n-500);
    font-size: var(--fs-caption);
    line-height: var(--lh-caption);
}

.pagination__links {
    display: flex;
    gap: var(--sp-2);
}

/* Los datos secundarios de una celda (roles del actor, contexto, número de
 * petición) van debajo del principal, no al lado. */
.data-table .caption {
    display: block;
}

/* La ayuda recortada a dos renglones necesita su `display` propio, y acá dentro
 * pierde contra la regla de arriba si no se la repite con el mismo alcance. */
.data-table .caption--clamp-2 {
    display: -webkit-box;
}

/* Densidad compacta: la fila baja de 48 a 36 px acortando el relleno, no el
 * tamaño del texto, que sigue teniendo un mínimo de 12 px (Lineamiento §4). */
.density-compact .data-table th,
.density-compact .data-table td {
    padding: var(--sp-2) var(--sp-3);
}

/*
 * Tabla de muchas columnas.
 *
 * Ocho columnas con 16 px de aire a cada lado son 256 px de relleno: en una
 * notebook de 1366 px, descontada la navegación lateral, no entran, y la tabla
 * se va del ancho disponible. Se le baja el relleno horizontal a 12 px, que
 * sigue estando en la escala del Lineamiento §5 y no toca el alto de la fila ni
 * el tamaño del texto: la densidad vertical, que es la que decide cuántas filas
 * se ven, no cambia.
 *
 * Es un modificador y no una regla general para que las tablas de cuatro o cinco
 * columnas, que entran de sobra, conserven su aire.
 */
.data-table--wide th,
.data-table--wide td {
    padding-left: var(--sp-3);
    padding-right: var(--sp-3);
}

/*
 * Acciones de fila de una pantalla de administración.
 *
 * La columna de acciones es angosta, así que los botones se apilan uno debajo
 * del otro. Con cuatro acciones a 44 px cada una la fila del producto pasaba de
 * 200 px de alto y en una pantalla entraban cuatro productos: un listado que no
 * deja comparar dejó de ser un listado.
 *
 * No se escribe un alto nuevo: se redefine la variable de densidad en este
 * ámbito, que es exactamente para lo que existe.
 *
 * El mínimo táctil de 44 px del Lineamiento §10 sigue en pie donde importa. Esta
 * clase la pone únicamente la pantalla de administración —el vendedor, que es
 * quien trabaja de pie y sobre una tablet, nunca la recibe—, y el Lineamiento §5
 * es explícito en que la densidad compacta «está disponible solo en las
 * pantallas de administración de escritorio». Con el armazón compacto la tabla se apila
 * como tarjetas y los controles vuelven a 44 px, así que un administrador que
 * entre desde el teléfono tampoco los ve chicos.
 *
 * Etapa 1: el umbral pasó de 768 a 1024 px. A 768 px la pantalla sigue siendo
 * de dedo —es una tablet en vertical— y 34 px de alto queda por debajo del área
 * táctil mínima. «De escritorio» ahora quiere decir 1024 px, que es la banda de
 * escritorio de la escala.
 */
@media (min-width: 1024px) {
    .row-actions--admin {
        --control-h: 34px;
    }
}

/* ── Pestañas (Lineamiento §8, revisiones de presupuesto) ───────────────── */
/*
 * Se marcan con `role="tablist"` y `aria-selected`, y la activa lleva un
 * subrayado de 3 px en amarillo. El amarillo acá es indicador, no fondo: sobre
 * claro no se lee como texto (2,05:1) pero sí funciona como barra.
 */

.tabs {
    display: flex;
    gap: var(--sp-1);
    border-bottom: 1px solid var(--n-200);
    margin-bottom: var(--sp-4);
    overflow-x: auto;
}

.tabs__tab {
    flex: none;
    appearance: none;
    background: none;
    border: none;
    border-bottom: var(--bw-3) solid transparent;
    padding: var(--sp-3) var(--sp-4);
    font-family: inherit;
    font-size: var(--fs-body);
    font-weight: 600;
    color: var(--n-500);
    cursor: pointer;
    white-space: nowrap;
}

.tabs__tab:hover {
    color: var(--n-900);
}

.tabs__tab[aria-selected="true"] {
    color: var(--n-900);
    border-bottom-color: var(--brand-yellow);
}

.tabs__panel[hidden] {
    display: none;
}

/*
 * Variante enlazada: las mismas pestañas, pero cuando cada una es una dirección
 * propia y no un panel que se muestra y se esconde. Es lo que usa el historial
 * del cliente (Especificación §12), donde cada solapa va a cargar su propia
 * lista paginada: traerlas todas juntas para esconder cuatro sería trabajo de
 * servidor tirado a la basura, y además la solapa abierta se puede compartir por
 * enlace y sobrevive sin JavaScript.
 *
 * La activa se marca con `aria-current="page"`, que es lo correcto para
 * navegación —`aria-selected` pertenece a un `tablist` real—, y se ve igual que
 * la del `tablist`: mismo subrayado amarillo, misma tinta.
 */
.tabs__tab--link {
    display: inline-block;
    text-decoration: none;
}

.tabs__tab[aria-current="page"] {
    color: var(--n-900);
    border-bottom-color: var(--brand-yellow);
}

/*
 * Solapa cuyo módulo todavía no existe: se muestra apagada y no navega, como
 * un botón sin permiso (Lineamiento §7). Se deja a la vista a propósito, para
 * que el mapa de la ficha no cambie de forma cuando el módulo llegue.
 */
.tabs__tab[aria-disabled="true"] {
    color: var(--n-400);
    cursor: not-allowed;
}

/* ══ 09-fichas.css ══ */

/*
 * Parte de `components.css`. Las partes se concatenan por orden de nombre;
 * el índice y el motivo del corte están en `components/00-indice.css`.
 */

/* ── Tarjeta del cliente del documento (Lineamiento §7 y §8) ────────────── */
/*
 * El encabezado de un presupuesto o de un pedido dice cuatro cosas: quién es el
 * cliente, en qué local, quién vende y de qué día es. Las cuatro estaban en el
 * mismo renglón de 12 px gris, y la primera —la única que el vendedor mira
 * mientras habla con la persona— quedaba tan apagada como la fecha.
 *
 * Se resuelve con la tarjeta que pidió el operador, que además de agrandar el
 * nombre hace algo que el renglón suelto no podía: **encerrar**. Dentro de este
 * borde está el cliente y lo que se hace con él —llamarlo, escribirle, abrir su
 * ficha—; afuera, el documento. Esa frontera es la que vuelve legible el
 * nombre, más que los seis píxeles que ganó de tamaño.
 *
 * Es la tarjeta estándar del §7 —fondo blanco, borde de 1 px --n-200, radio de
 * 12 px, 16 px de aire— y no una variante propia: bordes antes que sombras.
 */
.customer-card {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-3) var(--sp-5);
    margin-top: var(--sp-3);
    padding: var(--sp-4);
    background-color: var(--n-000);
    border: 1px solid var(--n-200);
    border-radius: var(--r-lg);
}

/* Que la identidad crezca y la acción no: el nombre largo se lleva el ancho
 * sobrante y el botón conserva el suyo hasta que ya no entra y baja. */
.customer-card__identity {
    flex: 1 1 240px;
    min-width: 0;
}

.customer-card__name {
    margin: 0;
    color: var(--n-900);
    font-size: var(--fs-h2);
    font-weight: 600;
    line-height: var(--lh-h2);
}

.customer-card__name a {
    color: inherit;
    text-decoration-color: var(--n-300);
    text-underline-offset: 3px;
}

.customer-card__name a:hover {
    text-decoration-color: currentcolor;
}

.customer-card__contact {
    display: flex;
    flex-wrap: wrap;
    gap: 0 var(--sp-5);
    margin: var(--sp-1) 0 0;
    padding: 0;
    list-style: none;
}

.customer-card__item {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-2);
    color: var(--n-700);
    font-size: var(--fs-body);
    line-height: var(--lh-body);
}

.customer-card__item .icon {
    flex: none;
    color: var(--n-400);
}

.customer-card__item a {
    color: inherit;
    overflow-wrap: anywhere;
}

.customer-card__action {
    flex: none;
}

/* El local, el vendedor y la fecha: son del documento, no de la persona. Van
 * abajo de la línea y en tamaño de ayuda, que es exactamente la jerarquía que
 * antes tenía el nombre. */
.customer-card__meta {
    flex-basis: 100%;
    margin: 0;
    padding-top: var(--sp-3);
    border-top: 1px solid var(--n-200);
}

/*
 * En el teléfono y en la tablet, llamar y escribir son controles y no adornos:
 * cada uno se lleva su renglón y su área táctil de 44 px (Lineamiento §10).
 * Es además el dispositivo donde esto se usa de verdad, con el cliente al lado.
 */
@media (max-width: 767.98px) {
    .customer-card__contact {
        gap: 0;
        width: 100%;
    }

    .customer-card__item {
        min-height: 44px;
        width: 100%;
    }

    .customer-card__action,
    .customer-card__action .button {
        width: 100%;
    }
}

/* ── Lista de datos (ficha del cliente) ─────────────────────────────────── */
/*
 * Etiqueta y valor en dos columnas, que es como se lee una ficha: nadie busca
 * "Villa Crespo", busca la fila "Localidad". En pantalla angosta las dos
 * columnas se apilan, igual que las tablas (Lineamiento §7).
 */

.detail-list {
    display: grid;
    grid-template-columns: minmax(160px, 240px) 1fr;
    gap: var(--sp-2) var(--sp-4);
    margin: 0;
}

.detail-list__term {
    margin: 0;
    color: var(--n-500);
    font-size: var(--fs-label);
    font-weight: 600;
    line-height: var(--lh-body-dense);
}

.detail-list__value {
    margin: 0;
    color: var(--n-900);
    font-size: var(--fs-body);
    line-height: var(--lh-body-dense);
    overflow-wrap: anywhere;
}

/*
 * Un dato que todavía nadie cargó se dice con la palabra, no con un guion ni
 * con una nota interna: mismo tamaño que el resto para no descolgar el
 * renglón, en gris secundario para que se lea como ausencia.
 */
.detail-list__empty {
    color: var(--n-500);
    font-size: var(--fs-body);
}

/* La ficha de dos columnas necesita ancho real, no ancho de pantalla. En el
 * tramo del armazón compacto (768–1023 px) el menú lateral se queda con 248 px
 * fijos y las fichas se muestran de dos en fila, así que cada una recibe unos
 * 250 px: la columna del rótulo pide `minmax(160px, 240px)` y el valor más
 * largo que guarda el sistema —un CUIT, una dirección— mide 106 px. No entran
 * juntas, y el resultado medido era un desborde horizontal de 59 px en
 * `/inicio`. Por eso el apilado llega hasta 1023.98 px y no hasta el teléfono.
 * El caso correcto es una consulta de contenedor: queda anotado para la
 * Etapa 2, donde las fichas pasan a la biblioteca de componentes. */
@media (max-width: 1023.98px) {
    .detail-list {
        grid-template-columns: 1fr;
        gap: var(--sp-1) 0;
    }

    .detail-list__value {
        margin-bottom: var(--sp-3);
    }
}

/* ══ 10-superposiciones.css ══ */

/*
 * Parte de `components.css`. Las partes se concatenan por orden de nombre;
 * el índice y el motivo del corte están en `components/00-indice.css`.
 */

/* ── Modales (Lineamiento §7) ───────────────────────────────────────────── */
/*
 * Se usa `<dialog>` nativo: trae la trampa de foco, el cierre con Escape y el
 * velo del navegador sin JavaScript propio. `ui.js` solo lo abre y lo cierra.
 */

.modal {
    width: min(560px, calc(100vw - var(--sp-6)));
    max-height: calc(100vh - var(--sp-7));
    padding: 0;
    border: none;
    border-radius: var(--r-lg);
    box-shadow: var(--sh-3);
    background-color: var(--n-000);
    color: var(--n-700);
}

.modal::backdrop {
    background-color: var(--scrim);
}

.modal__header,
.modal__body,
.modal__actions {
    padding: var(--sp-4) var(--sp-5);
}

.modal__header {
    border-bottom: 1px solid var(--n-200);
}

.modal__body {
    overflow-y: auto;
}

/* El primario va último: en castellano se lee de izquierda a derecha y la
 * confirmación es el final del recorrido. */
.modal__actions {
    display: flex;
    justify-content: flex-end;
    gap: var(--sp-2);
    border-top: 1px solid var(--n-200);
}

/*
 * En móvil el modal se convierte en hoja inferior a ancho completo, con la
 * barra de arrastre que indica que se puede cerrar hacia abajo.
 */
@media (max-width: 767.98px) {
    .modal {
        width: 100vw;
        max-width: none;
        margin: 0 0 0 0;
        position: fixed;
        inset: auto 0 0 0;
        border-radius: var(--r-xl) var(--r-xl) 0 0;
        max-height: 85vh;
        padding-bottom: env(safe-area-inset-bottom, 0px);
    }

    .modal::before {
        content: "";
        display: block;
        width: 36px;
        height: 4px;
        margin: var(--sp-2) auto 0;
        border-radius: var(--r-pill);
        background-color: var(--n-300);
    }

    .modal__actions {
        flex-direction: column-reverse;
    }

    .modal__actions .button {
        width: 100%;
    }
}

/* ── Notificaciones efímeras (Lineamiento §7) ───────────────────────────── */
/*
 * Abajo a la derecha en escritorio, arriba en móvil. Nunca se usan para errores
 * de validación de formulario: esos van junto al campo, porque un aviso que
 * desaparece a los cinco segundos no sirve para corregir un dato.
 *
 * La región es `aria-live="polite"`, así que el lector de pantalla anuncia el
 * mensaje sin interrumpir lo que la persona esté haciendo.
 */

.toast-region {
    position: fixed;
    right: var(--sp-4);
    bottom: var(--sp-4);
    z-index: var(--z-toast);
    display: flex;
    flex-direction: column;
    gap: var(--sp-2);
    width: min(360px, calc(100vw - var(--sp-6)));
}

.toast {
    display: flex;
    align-items: flex-start;
    gap: var(--sp-3);
    padding: var(--sp-3) var(--sp-4);
    border-radius: var(--r-md);
    background-color: var(--n-900);
    color: var(--n-000);
    box-shadow: var(--sh-3);
    font-size: var(--fs-body-dense);
    line-height: var(--lh-body-dense);
}

.toast__icon--ok { color: var(--ok); }
.toast__icon--err { color: var(--err); }
.toast__icon--info { color: var(--brand-yellow); }

.toast__message {
    flex: 1;
}

/* «Deshacer» y el cierre van sobre fondo oscuro: acá el amarillo sí se puede
 * usar como texto, porque sobre --n-900 da 10,2:1. */
.toast__action {
    appearance: none;
    background: none;
    border: none;
    padding: 0;
    color: var(--brand-yellow);
    font-family: inherit;
    font-size: var(--fs-body-dense);
    font-weight: 600;
    text-decoration: underline;
    cursor: pointer;
}

/* La acción propia del aviso —«Actualizar», «Deshacer»— se distingue de
 * «Cerrar», que siempre está. Sobre el fondo oscuro del toast el amarillo de
 * marca da 10,2:1, que es de las pocas combinaciones donde puede ser texto
 * (Lineamiento §3.1). */
.toast__action--primary {
    padding: var(--sp-1) var(--sp-3);
    border: 1px solid currentcolor;
    border-radius: var(--r-sm);
    text-decoration: none;
    min-height: 32px;
}

@media (max-width: 767.98px) {
    .toast-region {
        top: calc(var(--topbar-h) + var(--sp-2));
        bottom: auto;
        left: var(--sp-4);
        right: var(--sp-4);
        width: auto;
    }
}

/* ── Hoja inferior (Etapa 2, Lote B) ────────────────────────────────────────
 *
 * Estaba anunciada en el índice desde el paso 7 y nunca se había implementado:
 * lo que existía era un modal que en celular se disfrazaba de hoja. Alcanzaba
 * mientras el contenido fuera una confirmación, porque una confirmación sí es
 * un modal. No alcanza para los filtros avanzados ni para la configuración de
 * una pantalla, que no son modales: no interrumpen nada, no hay nada que
 * confirmar y la persona vuelve de ellos a lo que estaba haciendo.
 *
 * Va sobre `<dialog>` nativo por el mismo motivo que `.modal`: trae la trampa
 * de foco, el cierre con Escape y el velo sin una línea de JavaScript propia.
 * `ui.js` solo la abre y la cierra. Una hoja armada con `<div>` y `position:
 * fixed` obliga a reimplementar las tres cosas, y la que siempre falta es la
 * trampa de foco —se tabula hacia la pantalla de atrás sin que nada avise—.
 *
 * Se escribe primero la forma de celular y la de escritorio la releva desde
 * 768 px, porque en celular es donde la hoja es la forma correcta y en
 * escritorio es una concesión.
 */

.sheet {
    position: fixed;
    inset: auto 0 0 0;
    width: 100vw;
    max-width: none;
    max-height: 85vh;
    margin: 0;
    padding: 0;
    border: none;
    /* Solo arriba, y en el radio grande: 16 px es el gesto que dice «esto subió
     * desde el borde de la pantalla» (Lineamiento §7). Abajo apoya en el filo,
     * así que no hay nada que redondear. */
    border-radius: var(--r-xl) var(--r-xl) 0 0;
    box-shadow: var(--sh-3);
    background-color: var(--n-000);
    color: var(--n-700);
    /* El pie de la hoja queda sobre la franja del gesto de inicio del teléfono:
     * sin esto, el último botón de la hoja es el que el sistema se come. */
    padding-bottom: env(safe-area-inset-bottom, 0px);
}

.sheet::backdrop {
    background-color: var(--scrim);
}

/*
 * La manija.
 *
 * Es un elemento de verdad y no el `::before` de la hoja, al contrario que en
 * `.modal`. La diferencia es que acá se puede: un elemento real acepta
 * `aria-hidden="true"`, y la manija es puro dibujo —no se arrastra, el cierre
 * es el botón de la cabecera y la tecla Escape—, así que lo correcto es que el
 * lector de pantalla no la anuncie. Un pseudoelemento no se puede marcar y
 * queda a merced de cómo lo interprete cada lector.
 */
.sheet__grab {
    display: block;
    width: 36px;
    height: 4px;
    margin: var(--sp-2) auto 0;
    border-radius: var(--r-pill);
    background-color: var(--n-300);
}

.sheet__header,
.sheet__body,
.sheet__actions {
    padding: var(--sp-4) var(--sp-5);
}

.sheet__header {
    border-bottom: 1px solid var(--n-200);
}

/* El que se desplaza es el cuerpo y no la hoja entera: así la cabecera con el
 * título y el botón de cerrar no se va de la pantalla cuando la lista de
 * filtros es larga. */
.sheet__body {
    overflow-y: auto;
}

/*
 * El pie. El primario va último por lo mismo que en el modal: en castellano se
 * lee de izquierda a derecha y la confirmación es el final del recorrido.
 *
 * En celular los botones van apilados a ancho completo y en `column-reverse`,
 * de modo que el que está último en el HTML —el primario— queda arriba en la
 * pantalla, debajo del pulgar y lejos del borde inferior, que es donde el dedo
 * roza sin querer.
 */
.sheet__actions {
    display: flex;
    flex-direction: column-reverse;
    gap: var(--sp-2);
    border-top: 1px solid var(--n-200);
}

.sheet__actions .button {
    width: 100%;
}

/*
 * Desde 768 px la hoja deja de ser una hoja.
 *
 * Una hoja pegada al pie de una pantalla de 1440 px pone la pregunta a un metro
 * del lugar donde estaba el ojo y obliga a bajar la vista para contestar. Ahí
 * lo correcto es lo mismo que hace el modal: centrarse. Se queda el componente
 * —el mismo HTML, la misma apertura, el mismo contenido— y cambia la forma.
 */
@media (min-width: 768px) {
    .sheet {
        inset: 0;
        width: min(560px, calc(100vw - var(--sp-6)));
        max-height: calc(100vh - var(--sp-7));
        /* `inset: 0` más `margin: auto` es lo que centra un `<dialog>` fijo en
         * los dos ejes sin transformaciones ni cuentas. */
        margin: auto;
        border-radius: var(--r-lg);
        padding-bottom: 0;
    }

    /* Y la manija se oculta: no hay ningún borde desde el cual la hoja haya
     * subido, así que la manija dejaría de significar algo. */
    .sheet__grab {
        display: none;
    }

    .sheet__actions {
        flex-direction: row;
        justify-content: flex-end;
    }

    .sheet__actions .button {
        width: auto;
    }
}

/* ══ 11-indicadores.css ══ */

/*
 * Parte de `components.css`. Las partes se concatenan por orden de nombre;
 * el índice y el motivo del corte están en `components/00-indice.css`.
 */

/* ── Muestra de material (Lineamiento §8 y §9) ──────────────────────────── */
/*
 * El cuadrado de 40 px con la textura de la madera o del tapizado. Es el único
 * lugar de la interfaz, junto con el login y el encabezado del PDF, donde las
 * texturas del manual tienen lugar: de fondo de pantalla recargan y arruinan la
 * legibilidad de las tablas.
 */

/*
 * El swatch se usa de dos maneras: como `<span>` pintado con `background`
 * —un color de acento, una textura del manual— y como `<img>` real con la
 * muestra del material en el configurador. Por eso conviven las dos reglas de
 * ajuste.
 *
 * El `object-fit` faltaba, y sin él el `<img>` se estiraba hasta llenar los
 * 40 × 40: una muestra de madera rectangular salía deformada, que es lo peor
 * que le puede pasar a una imagen cuyo único trabajo es mostrar cómo se ve un
 * material. Acá sí va `cover` y no `contain`: la muestra de un material es un
 * recorte por definición —interesa la veta, no el contorno del recorte— y
 * `contain` dejaría franjas blancas dentro de un cuadrado de 40 px.
 */
.swatch {
    width: 40px;
    height: 40px;
    flex: none;
    border-radius: var(--r-sm);
    border: 1px solid var(--n-300);
    background-position: center;
    background-size: cover;
    object-fit: cover;
    object-position: center;
}

/*
 * La versión chica, para acompañar un valor de texto dentro de una lista de
 * definiciones: el color de acento con el que la empresa imprime sus documentos.
 * El color se pinta con un estilo en línea porque es un dato de la empresa
 * (Especificación §30) y no un token del sistema de diseño.
 */
.swatch--inline {
    display: inline-block;
    width: 20px;
    height: 20px;
    vertical-align: text-bottom;
    margin-right: var(--sp-2);
}

/* ── Vista previa del logo del documento ────────────────────────────────── */
/*
 * Sobre fondo claro y con borde, porque un logo suele venir con fondo
 * transparente y sin encuadre no se sabe dónde termina la imagen.
 */

.document-logo-preview {
    display: block;
    width: auto;
    max-width: 240px;
    height: auto;
    max-height: 80px;
    padding: var(--sp-3);
    border: 1px solid var(--n-200);
    border-radius: var(--r-md);
    background-color: var(--n-000);
}

/* ── Barra de progreso ──────────────────────────────────────────────────── */
/*
 * Sirve para el avance de meta del vendedor y para la vigencia del presupuesto.
 * El valor va siempre además en texto al lado: una barra sola no dice cuánto
 * falta, y el color por sí solo no comunica un estado (Lineamiento §10).
 */

/*
 * Una sola forma de barra en todo el sistema: carril `--n-200` de 4 px, radio
 * de cápsula y relleno amarillo de marca.
 *
 * Antes cada pantalla elegía su color —azul en «Presupuestos por estado»,
 * marrón en los rankings, verde o arena en las metas— y el mismo gesto visual
 * significaba cosas distintas según dónde estuviera. El amarillo es el color de
 * «esto es lo medido» (Lineamiento §3.1) y el verde queda reservado para un
 * único hecho: la meta cumplida.
 */
.progress {
    height: 4px;
    border-radius: var(--r-pill);
    background-color: var(--n-200);
    overflow: hidden;
}

.progress__value {
    display: block;
    height: 100%;
    background-color: var(--brand-yellow);
}

/* Se conserva el modificador porque hay marcado que lo escribe, pero ya no
 * cambia nada: 4 px es el alto de todas las barras. */
.progress--thin { height: 4px; }

/* Separación mínima cuando la barra va justo debajo de un texto: pegada, se lee
 * como un subrayado del texto y no como una medida de avance. */
.progress--spaced { margin-top: var(--sp-2); }

/*
 * Barra de medida dentro de una celda de tabla.
 *
 * Con el ancho de la celda y 4 px de alto, la barra de «Metas del período»
 * quedaba a pocos píxeles del borde inferior de la fila —que es otra línea de
 * 1 px del mismo gris— y las dos se leían como una sola: el separador de la
 * tabla. Con 120 px de ancho propio y 6 px de alto deja de parecer un borde y
 * pasa a parecer lo que es. La línea que separa las filas sigue siendo el
 * `border-bottom` de la celda, aparte.
 */
.progress--gauge {
    width: 120px;
    height: 6px;
}
.progress--ok .progress__value { background-color: var(--ok); }
.progress--warn .progress__value { background-color: var(--warn); }
.progress--err .progress__value { background-color: var(--err); }

/* ── Vigencia del presupuesto ───────────────────────────────────────────── */
/*
 * La barra de los tres tramos del Lineamiento §8: verde mientras faltan más de
 * cinco días, ámbar en los últimos cinco, roja una vez vencida. Es la misma
 * barra de progreso, con el color decidido por el dominio
 * (`QuoteValidityUrgency::barClass()`) y no por la plantilla: el umbral de cinco
 * días es una regla de negocio y no puede quedar escrita en una hoja de estilos.
 *
 * El color nunca va solo: al lado siempre está el texto «Vence el 14/09 ·
 * faltan 6 días», porque bajo la luz de un salón la diferencia entre verde y
 * ámbar puede no verse (Lineamiento §10).
 */

.validity-bar { max-width: 240px; }
.validity-bar--vigente .progress__value { background-color: var(--ok); }
.validity-bar--por-vencer .progress__value { background-color: var(--warn); }
.validity-bar--vencido .progress__value { background-color: var(--err); }

/* ══ 12-filas-y-celdas.css ══ */

/*
 * Parte de `components.css`. Las partes se concatenan por orden de nombre;
 * el índice y el motivo del corte están en `components/00-indice.css`.
 */

/* ── Acciones dentro de una fila ────────────────────────────────────────── */
/*
 * En las pantallas de estructura del catálogo conviven varias acciones en la
 * misma celda —subir, bajar, activar— y cada una es su propio formulario,
 * porque cada una es un envío distinto. El contenedor solo las alinea y las
 * deja bajar de renglón en pantallas angostas: no aporta color, borde ni fondo.
 */

/*
 * Encabezado de un listado: a la izquierda cuántos resultados hay, a la derecha
 * la acción de alta. Los dos datos que se buscan al llegar a una pantalla de
 * lista, en el mismo renglón y sin nada en el medio.
 *
 * Existe porque el conteo iba suelto debajo del bloque de la acción, y cuando
 * la acción aparece deshabilitada con su explicación de permiso, el «3
 * productos» quedaba pegado a esa frase y se leía como su continuación.
 */
.list-header {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--sp-2) var(--sp-4);
    margin: var(--sp-5) 0 var(--sp-3);
}

/* El conteo manda su propio margen: el del párrafo lo desalineaba del botón. */
.list-header__count {
    margin: 0;
}

.row-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    gap: var(--sp-2);
}

/* Un botón que sigue a una lista de datos —«WhatsApp» debajo del contacto del
   cliente, en la ficha— necesita aire: pegado al último renglón se lee como una
   fila más de la lista. */
.detail-list + .row-actions {
    margin-top: var(--sp-4);
}

/*
 * El motivo de un botón apagado se va al final de la fila y ocupa su propio
 * renglón. Si no, se mete entre los botones y empuja al de al lado —«Abrir», en
 * el listado de clientes— dos renglones más abajo que el de las otras filas.
 */
.row-actions .button__reason {
    order: 1;
}

/*
 * Variante para cuando la fila no son botones sino una lectura corrida —un chip
 * de estado, el nombre del cliente, la fecha—: ahí el arranque en el borde
 * superior desalinea el chip respecto del texto, porque el chip es más alto que
 * un renglón. Centrar verticalmente los deja sobre la misma línea óptica.
 */
.row-actions--aligned {
    align-items: center;
}

/*
 * Los dos botones que reordenan una fila.
 *
 * En escritorio son dos flechas cuadradas de 32 px, una al lado de la otra: con
 * las palabras «Subir» y «Bajar» escritas no entraban en la columna «Orden» y
 * se apilaban, y la fila de la tabla de estados pasaba de 60 a 120 px de alto.
 * La palabra no se borra —sigue en el `title` y en la etiqueta oculta—, se deja
 * de dibujar.
 *
 * 32 px es más chico que el área táctil de 44 px que pide el Lineamiento §10, y
 * por eso la reducción vale únicamente de 1024 px para arriba, que es donde se
 * apunta con un puntero fino —antes era 769 px, que incluía a la tablet en
 * vertical, donde se apunta con el dedo—. En celular la tabla se vuelve
 * tarjetas apiladas, y ahí los botones conservan su palabra y su alto
 * completo.
 */
@media (min-width: 1024px) {
    .row-actions--move {
        flex-wrap: nowrap;
        align-items: center;
    }

    .row-actions--move .button {
        width: 32px;
        height: 32px;
        min-height: 0;
        padding: 0;
    }

    .row-actions--move .button__label {
        position: absolute;
        width: 1px;
        height: 1px;
        margin: -1px;
        padding: 0;
        overflow: hidden;
        clip-path: inset(50%);
        white-space: nowrap;
    }
}

/* ── Menú secundario de una fila ────────────────────────────────────────── */
/*
 * Lo que se hace una vez en la vida de un registro —archivar un producto, por
 * ejemplo— vive acá y no como un botón más de la fila. No es para esconderlo:
 * es para que las dos o tres acciones que sí se usan todos los días se
 * distingan, cosa que cinco botones iguales en veinte filas no permiten.
 *
 * `<details>` nativo, igual que el menú de usuario: abre con Enter, cierra con
 * Escape, se anuncia solo al lector de pantalla y no necesita una línea de
 * JavaScript. El triángulo del navegador se saca y se reemplaza por el estilo
 * de botón terciario, porque lo que se ve tiene que leerse como una acción.
 *
 * El panel se dibuja absoluto para no ensanchar la fila al abrirse; se ancla a
 * la derecha porque la columna de acciones es la última de la tabla y un panel
 * anclado a la izquierda se sale de la pantalla en una notebook.
 */

.row-menu {
    position: relative;
}

.row-menu__trigger {
    display: inline-flex;
    align-items: center;
    min-height: var(--control-h);
    padding: 0 var(--sp-3);
    border-radius: var(--r-md);
    font-size: var(--fs-body);
    font-weight: 600;
    color: var(--n-700);
    cursor: pointer;
    list-style: none;
}

.row-menu__trigger::-webkit-details-marker {
    display: none;
}

.row-menu__trigger:hover {
    background-color: var(--n-100);
    text-decoration: underline;
}

.row-menu__panel {
    position: absolute;
    top: calc(100% + var(--sp-1));
    right: 0;
    z-index: 1;
    width: max-content;
    max-width: 280px;
    padding: var(--sp-3);
    background-color: var(--n-000);
    border: 1px solid var(--n-200);
    border-radius: var(--r-md);
    box-shadow: var(--sh-2);
}

/*
 * Dentro del panel las acciones se estiran a todo el ancho y se apilan. Es un
 * menú, no una barra de herramientas: cada renglón es un blanco completo y se
 * apunta sin buscar, que es lo que uno espera de una lista de opciones.
 */
.row-menu__panel .button {
    width: 100%;
    justify-content: center;
}

.row-menu__form {
    margin-top: var(--sp-2);
}

.row-menu__panel .caption {
    margin-top: var(--sp-2);
    margin-bottom: 0;
}

/*
 * Apilada como tarjeta, la fila ya no tiene una columna angosta que proteger y
 * el panel absoluto se superpone al contenido de abajo. En celular se abre en
 * el flujo, empujando lo que sigue.
 */
@media (max-width: 767.98px) {
    .row-menu__panel {
        position: static;
        max-width: none;
        width: auto;
    }
}

/* ── Lista para elegir uno ──────────────────────────────────────────────── */
/*
 * Media docena de renglones donde cada uno se lee de un vistazo —un rótulo a la
 * izquierda, una acción a la derecha— y de los que se elige exactamente uno: el
 * presupuesto de destino en el configurador es el caso que la trajo.
 *
 * No es una tabla y no debe parecerlo: no hay columnas que comparar. Tampoco es
 * `.row-actions`, que alinea botones sueltos sin rótulo. Se separa con líneas de
 * 1 px como el resto de las superficies (Lineamiento §5: bordes antes que
 * sombras) y cada renglón respeta el alto táctil mínimo.
 */

.pick-list {
    list-style: none;
    margin: var(--sp-3) 0;
    padding: 0;
}

.pick-list__item {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-3);
    min-height: 48px;
    padding: var(--sp-2) 0;
    border-bottom: 1px solid var(--n-200);
}

.pick-list__item:last-child {
    border-bottom: none;
}

/* ── Formulario en línea ────────────────────────────────────────────────── */
/*
 * Un alta corta —un nombre, a lo sumo un desplegable y un botón— no merece una
 * pantalla aparte ni un campo por renglón. Los campos crecen, el botón no, y
 * por debajo del punto de quiebre todo se apila solo sin una regla adicional.
 */

/*
 * Alineación por arriba y no por abajo. Alineados por abajo, un campo con texto
 * de ayuda debajo del control empujaba su etiqueta hacia arriba —«Tipo» quedaba
 * más alto que «Nombre del estado» y que «Color»— y el botón de crear terminaba
 * apoyado en la última línea de esa ayuda, o sea a media altura de la fila.
 *
 * Con `start`, todas las etiquetas arrancan en la misma línea y todos los
 * controles quedan a la misma altura. El botón compensa a mano el alto de una
 * etiqueta —18 px de línea más 4 px de separación— para apoyarse en la base de
 * los controles y no en la de los textos de ayuda. Es el mismo criterio que usa
 * la barra de filtros.
 */
.form-inline {
    display: flex;
    flex-wrap: wrap;
    align-items: start;
    gap: var(--sp-4);
}

.form-inline .field {
    flex: 1 1 220px;
    margin-bottom: 0;
}

.form-inline__actions {
    display: flex;
    gap: var(--sp-2);
    flex: none;
    margin-top: calc(18px + var(--sp-1));
}

/*
 * Edición en línea dentro de una celda: nombre, color y botón en un renglón.
 *
 * Sin esto, el formulario envolvía dentro de la celda angosta y cada fila de la
 * tabla de estados medía 145 px apilando los tres controles uno debajo del
 * otro. Ocho estados así son una pantalla entera de alto para editar dos datos.
 *
 * Los anchos son fijos —el nombre es el dato largo, el color es un desplegable
 * de ocho opciones— y el botón se va al extremo derecho con `margin-left:auto`,
 * que es donde el ojo lo busca después de tocar los campos de su izquierda.
 */
.form-inline--row {
    flex-wrap: nowrap;
    align-items: start;
    gap: var(--sp-3);
}

/*
 * El nombre cede antes que el color. Los dos tienen un ancho de referencia
 * —200 y 130 px—, pero solo el primero puede achicarse: si la ventana aprieta,
 * un campo de texto más corto sigue siendo usable, mientras que un desplegable
 * angosto empieza a cortar el nombre del color, que es justamente el dato.
 */
.form-inline--row > .field:first-of-type {
    flex: 1 1 200px;
    min-width: 0;
}

.form-inline--row > .field:nth-of-type(2) {
    flex: 0 0 120px;
    min-width: 0;
}

.form-inline--row .form-inline__actions {
    margin-left: auto;
}

/*
 * Dentro de la tabla, el encabezado de la columna es la etiqueta.
 *
 * El Lineamiento §7 exige etiqueta visible y prohíbe el marcador como único
 * texto; lo que no dice es que esa etiqueta tenga que repetirse por fila. En
 * una tabla de edición hay un `<th scope="col">` arriba —«Nombre y color»— que
 * nombra los dos controles de todas las filas a la vez, y volver a escribir
 * «Nombre» y «Color» treinta veces suma 22 px por fila para decir lo que ya
 * está dicho. Con las etiquetas visibles la fila no bajaba de 120 px.
 *
 * Las etiquetas no se borran: se esconden a la vista y siguen ahí para el
 * lector de pantalla, que las lee antes de cada campo. Y se esconden solo en
 * escritorio: con el armazón compacto la tabla se vuelve tarjetas apiladas, el
 * encabezado de columna desaparece y entonces la etiqueta de cada campo es lo
 * único que lo nombra.
 */
@media (min-width: 768px) {
    .form-inline--row .field__label {
        position: absolute;
        width: 1px;
        height: 1px;
        margin: -1px;
        padding: 0;
        overflow: hidden;
        clip-path: inset(50%);
        white-space: nowrap;
    }

    /* Sin etiqueta arriba, el botón ya no tiene que compensar su alto. */
    .form-inline--row .form-inline__actions {
        margin-top: 0;
    }
}

/* En celular la celda se vuelve una tarjeta angosta y los tres controles
 * vuelven a apilarse: ahí sí es la única forma de que entren. */
@media (max-width: 767.98px) {
    .form-inline--row {
        flex-wrap: wrap;
    }

    .form-inline--row > .field:first-of-type,
    .form-inline--row > .field:nth-of-type(2) {
        flex: 1 1 100%;
    }
}

/* ── Miniatura de producto ──────────────────────────────────────────────── */
/*
 * Caja de 96 × 72 con la foto centrada y completa.
 *
 * La caja mantiene la relación 4:3 que pide el Lineamiento §9, porque una
 * columna de miniaturas de altura variable hace ilegible la tabla. Lo que se
 * cambió es el ajuste: `object-fit: contain` en lugar de `cover`. Con `cover`,
 * una foto cuadrada —la forma más común en las que carga el proveedor— perdía
 * un 12,5 % arriba y otro tanto abajo, y en una miniatura de 72 px de alto eso
 * alcanza para que el mueble no se reconozca. Preferimos una franja blanca a
 * los costados antes que un producto decapitado.
 *
 * El fondo es blanco porque la foto de producto va sobre blanco puro (§9); el
 * `--n-100` queda reservado para la variante vacía, donde sí hay que ver que
 * falta algo.
 *
 * El alto y el ancho reales van además en los atributos `width` y `height` del
 * `<img>`; el CSS los sobreescribe, pero el navegador ya reservó el espacio.
 */

.product-thumb {
    display: block;
    width: 96px;
    /*
     * `min-width` además del `width`, y no es redundante.
     *
     * El reset le pone `max-width: 100%` a toda imagen para que una foto suelta
     * no rompa el ancho de la columna. Dentro de una tabla de ancho automático
     * eso se vuelve en contra: el navegador entiende que la miniatura puede
     * achicarse hasta donde haga falta y le da a la columna «Foto» el mínimo que
     * quiera, con lo que la foto queda una tira angosta y recortada. Con el
     * mínimo declarado, la columna reserva sus 96 px y el `max-width` sigue
     * cubriendo el caso para el que se escribió.
     */
    min-width: 96px;
    height: 72px;
    border-radius: var(--r-md);
    border: 1px solid var(--n-200);
    background-color: var(--n-000);
    object-fit: contain;
    object-position: center;
}

.product-thumb--empty {
    background-color: var(--n-100);
}

/*
 * Aviso sobre la miniatura: hoy, fotos sin aprobar. Va con ícono y con texto,
 * nunca solo con el color (Lineamiento §10), y en el tono de advertencia —no en
 * el amarillo de marca, que en este sistema significa acción y no alerta.
 */
.thumb-flag {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-1);
    margin-top: var(--sp-1);
    font-size: var(--fs-caption);
    line-height: var(--lh-caption);
    color: var(--warn);
}

/* ── Lista de chips dentro de una celda ─────────────────────────────────── */

.chip-list {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-1);
    margin-top: var(--sp-1);
}

/* ── Columnas de importe ────────────────────────────────────────────────── */
/*
 * Los números se alinean a la derecha para que las unidades queden una debajo
 * de la otra (Lineamiento §4). En móvil la tabla se apila como tarjeta y la
 * alineación a la derecha deja de tener sentido: ahí vuelve a la izquierda.
 */

.data-table__cell--numeric {
    text-align: right;
    /*
     * Un importe no se parte en dos renglones (Lineamiento §4). En el listado de
     * presupuestos, con siete columnas en una notebook, «$ 1.727.550,00» se
     * cortaba después del signo: el símbolo quedaba solo arriba y la cifra
     * abajo, y la columna dejaba de cuadrar verticalmente, que es lo único que
     * hace comparables dos totales. Si no entra, la tabla se desplaza; el
     * número no se rompe.
     */
    white-space: nowrap;
}

.field__control--numeric {
    font-family: var(--font-data);
    font-variant-numeric: tabular-nums;
    text-align: right;
}

@media (max-width: 767.98px) {
    .data-table__cell--numeric {
        text-align: left;
    }
}

/* ── Agrupación de campos ───────────────────────────────────────────────── */
/*
 * `<fieldset>` de verdad, con su `<legend>`: es lo que le anuncia al lector de
 * pantalla que las casillas que siguen son un grupo y no campos sueltos.
 */

.fieldset {
    border: 1px solid var(--n-200);
    border-radius: var(--r-md);
    padding: var(--sp-4);
    margin: 0 0 var(--sp-4);
}

.fieldset__legend {
    font-size: var(--fs-h3);
    line-height: var(--lh-h3);
    font-weight: 600;
    padding: 0 var(--sp-2);
}

/* ══ 13-controles-en-tabla.css ══ */

/*
 * Parte de `components.css`. Las partes se concatenan por orden de nombre;
 * el índice y el motivo del corte están en `components/00-indice.css`.
 */

/* ── Casilla con etiqueta ───────────────────────────────────────────────── */
/*
 * El área táctil mínima es 44 px de alto (Lineamiento §10): la casilla sola
 * mide 16 px, así que la que se toca es la fila entera.
 */

.choice {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-2);
    min-height: 44px;
    padding-right: var(--sp-3);
    cursor: pointer;
}

/*
 * `flex: 0 0 auto` no es decorativo: sin él, una etiqueta que ocupa dos
 * renglones —las confirmaciones de la importación las ocupan— comprime la
 * casilla y queda de 14 px al lado de otras de 20. Una casilla más chica que
 * la de al lado se lee como si fuera otra cosa, y encima achica el blanco que
 * hay que acertar con el dedo.
 *
 * El `margin: 0` saca el margen que el navegador le pone por su cuenta a una
 * casilla (en Chromium, 3 px arriba y abajo, 4 px a la izquierda y 3 a la
 * derecha, y en otros navegadores otro valor). Con ese margen puesto, la
 * separación real entre la casilla y su texto deja de ser la que declara `gap`
 * y pasa a depender del navegador, y cualquier cosa que se quiera alinear
 * debajo del texto queda corrida por unos pocos píxeles que no figuran en
 * ninguna regla. Sin él, la geometría es la que dice esta hoja y nada más.
 */
.choice input[type="checkbox"] {
    flex: 0 0 auto;
    margin: 0;
    width: 20px;
    height: 20px;
    accent-color: var(--n-900);
    cursor: pointer;
}

/* ── Galería de imágenes del producto ───────────────────────────────────── */
/*
 * El borde punteado ámbar y el chip "Pendiente de aprobación" son obligatorios
 * y no decorativos: la Especificación §25 prohíbe mostrarle al cliente o
 * imprimir una imagen que todavía no aprobó una persona. La etiqueta
 * "Generado por IA" responde al Lineamiento §8, que la exige en toda salida de
 * IA del sistema.
 */

.image-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
    gap: var(--sp-4);
    list-style: none;
    margin: var(--sp-4) 0 0;
    padding: 0;
}

.image-card {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--sp-2);
    border: 1px solid var(--n-200);
    border-radius: var(--r-lg);
    padding: var(--sp-3);
    background-color: var(--n-000);
}

.image-card--pending {
    border: var(--bw-2) dashed var(--warn);
}

/*
 * La grilla del Image Studio conserva la caja 4:3 —son fotos de distintas
 * proporciones puestas una al lado de la otra, y sin una caja común la grilla
 * queda dentada—, pero la foto entra entera y centrada. Acá el recorte era
 * especialmente caro: es la pantalla donde alguien decide si una foto está bien
 * o si hay que aprobarla, y no se puede aprobar lo que no se ve completo.
 */
.image-card__figure {
    width: 100%;
    height: auto;
    aspect-ratio: 4 / 3;
    object-fit: contain;
    object-position: center;
    border-radius: var(--r-md);
    background-color: var(--n-000);
}

.image-card .overline {
    color: var(--info);
}

/*
 * En la pantalla de revisión cada foto viene de un producto distinto, así que
 * la tarjeta tiene que decir de cuál. Es un enlace y no un texto: mirando una
 * foto dudosa, lo primero que se quiere es abrir la ficha.
 */
.image-card__product {
    font-size: var(--fs-label);
    line-height: var(--lh-label);
    font-weight: 600;
    color: var(--n-700);
}

/*
 * Barra de acción de una selección múltiple.
 *
 * Va al pie de la lista y no flotando sobre ella: una barra fija tapa la última
 * fila de fotos justo cuando hay que mirarla, y en una tablet se lleva puesto
 * el pulgar. El borde superior la separa de la grilla sin sombra, como pide el
 * Lineamiento §5.
 */
.bulk-bar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-3);
    margin-top: var(--sp-5);
    padding-top: var(--sp-4);
    border-top: 1px solid var(--n-300);
}

.bulk-bar .caption {
    margin: 0;
}

/*
 * La misma barra, pero arriba de la lista.
 *
 * En el catálogo va antes de la tabla y no después: quien marca casillas mira
 * hacia abajo, y el contador y el botón tienen que estar donde empieza la
 * lista. Se invierte el borde para que siga separando la barra del contenido y
 * no del encabezado de la pantalla.
 */
.bulk-bar--leading {
    margin-top: var(--sp-4);
    margin-bottom: var(--sp-2);
    padding-top: 0;
    padding-bottom: var(--sp-4);
    border-top: 0;
    border-bottom: 1px solid var(--n-300);
}

.bulk-bar__whole {
    display: flex;
    align-items: flex-start;
    gap: var(--sp-2);
    /* 44 px de área táctil, Lineamiento §10: esta casilla se marca en tablet. */
    min-height: 44px;
    padding: var(--sp-2) 0;
    font-size: var(--fs-body-dense);
    line-height: var(--lh-body-dense);
    color: var(--n-700);
    cursor: pointer;
}

.bulk-bar__whole input {
    width: 20px;
    height: 20px;
    margin-top: 1px;
    flex: 0 0 auto;
    accent-color: var(--brand-yellow);
}

/* ── Casilla dentro de una celda de tabla ───────────────────────────────── */
/*
 * La casilla es de 20 px, pero el área que responde al dedo es la etiqueta
 * entera, de 44 × 44. Sin esto, seleccionar veinte productos en una tablet es
 * veinte intentos de acertarle a un cuadradito.
 */
.check-cell {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 44px;
    min-height: 44px;
    cursor: pointer;
}

.check-cell input[type="checkbox"] {
    width: 20px;
    height: 20px;
    accent-color: var(--brand-yellow);
}

.check-cell input[type="checkbox"]:disabled {
    cursor: not-allowed;
}

.data-table th.data-table__cell--check,
.data-table td.data-table__cell--check {
    width: 48px;
    text-align: center;
}

/* ── Revisión previa de la publicación masiva ───────────────────────────── */
/*
 * Cada grupo —se publican, necesitan confirmación, quedan bloqueados— es una
 * tarjeta con borde y sin sombra, como pide el Lineamiento §5. El color no es
 * lo único que los distingue: cada uno lleva su título escrito y su chip con la
 * palabra completa, porque un estado no se comunica solo con color
 * (Lineamiento §10).
 */
.publish-group {
    margin-top: var(--sp-5);
    padding: var(--sp-4);
    background-color: var(--n-000);
    border: 1px solid var(--n-200);
    border-radius: var(--r-lg);
}

.publish-group h2 {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    margin: 0 0 var(--sp-2);
}

.publish-group p {
    margin: 0 0 var(--sp-2);
}

.publish-group--confirm { border-left: var(--bw-3) solid var(--warn); }
.publish-group--blocked { border-left: var(--bw-3) solid var(--err); }

.publish-list {
    margin: var(--sp-3) 0 0;
    padding: 0;
    list-style: none;
    border-top: 1px solid var(--n-200);
}

.publish-list__item {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-2);
    padding: var(--sp-2) 0;
    border-bottom: 1px solid var(--n-200);
}

.publish-list__item .caption {
    display: block;
    margin: 0;
}

.publish-list__name {
    display: block;
    font-weight: 600;
    color: var(--n-900);
}

.publish-list__item--confirm .check-cell {
    align-items: flex-start;
    justify-content: flex-start;
    gap: var(--sp-3);
    min-height: 44px;
    text-align: left;
}

.publish-actions {
    margin-top: var(--sp-5);
    padding-top: var(--sp-4);
    border-top: 1px solid var(--n-300);
}

/*
 * El detalle de lo que quedó en borrador, en el listado al que vuelve la
 * operación. Va adentro de un `.notice--warn`, así que no repite fondo ni
 * borde: solo ordena las filas.
 */
.publish-result {
    margin: var(--sp-3) 0 0;
    padding: 0;
    list-style: none;
}

.publish-result__item {
    padding: var(--sp-2) 0;
    border-top: 1px solid var(--n-200);
}

.publish-result__item .caption {
    display: block;
    margin: 0;
}

/* ── Bloque plegable ────────────────────────────────────────────────────── */
/*
 * `<details>` nativo. Se usa cuando una pantalla tiene muchos formularios que
 * hacen lo mismo sobre cosas distintas —editar cada opción de un atributo, por
 * ejemplo—: mostrarlos todos abiertos convierte la pantalla en un muro de
 * campos donde no se encuentra nada.
 *
 * Es un elemento nativo y no un desplegable hecho a mano porque abre y cierra
 * con teclado, se anuncia solo al lector de pantalla y no necesita JavaScript;
 * además el navegador lo abre al buscar texto adentro.
 *
 * El formulario que tiene errores se dibuja abierto desde el servidor: un
 * mensaje de error escondido dentro de un bloque cerrado es un mensaje que no
 * existe.
 */

.disclosure {
    border: 1px solid var(--n-200);
    border-radius: var(--r-md);
    padding: var(--sp-2) var(--sp-3);
    margin-top: var(--sp-3);
    background-color: var(--n-000);
}

.disclosure__summary {
    display: flex;
    align-items: center;
    min-height: 44px;
    font-size: var(--fs-label);
    line-height: var(--lh-label);
    font-weight: 600;
    letter-spacing: 0.01em;
    color: var(--n-700);
    cursor: pointer;
}

.disclosure[open] > .disclosure__summary {
    border-bottom: 1px solid var(--n-200);
    margin-bottom: var(--sp-3);
}

/* ── Bloque plegable con forma de botón ─────────────────────────────────── */
/*
 * Variante para las pantallas que tienen muchos plegables juntos —la
 * configuración de un producto tiene uno por atributo y dos por opción—. La
 * barra a todo el ancho del `.disclosure` de arriba sirve cuando hay uno o dos;
 * cuando hay doce, la pantalla queda convertida en una pila de recuadros
 * blancos iguales donde no se ve qué se puede tocar. Acá el disparador toma la
 * forma del botón secundario del Lineamiento §7 —mismo alto, mismo borde, mismo
 * radio— y la flecha dice que despliega algo.
 *
 * El marcador propio del navegador se apaga: el triángulo de Safari no es el
 * ícono del sistema y el §9 no admite mezclar sets.
 *
 * Abierto, el disparador se pinta con el borde oscuro de «esto está
 * seleccionado» y el panel aparece debajo con su propio recuadro. Que se note
 * cuál está abierto importa: los formularios de dos opciones distintas son
 * idénticos salvo por lo que dicen sus campos.
 */

.disclosure--inline {
    border: 0;
    border-radius: 0;
    padding: 0;
    margin: 0;
    background: none;
}

.disclosure--inline > .disclosure__summary {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-2);
    height: var(--control-h);
    min-height: 0;
    padding: 0 var(--sp-4);
    border: 1px solid var(--n-300);
    border-radius: var(--r-md);
    background-color: var(--n-000);
    color: var(--n-900);
    font-size: var(--fs-body);
    line-height: var(--lh-body-dense);
    letter-spacing: 0;
    list-style: none;
    transition: background-color .12s ease;
}

.disclosure--inline > .disclosure__summary::-webkit-details-marker {
    display: none;
}

.disclosure--inline > .disclosure__summary:hover {
    background-color: var(--n-100);
}

.disclosure--inline[open] > .disclosure__summary {
    border-color: var(--n-900);
    background-color: var(--n-100);
    margin-bottom: 0;
}

.disclosure__count {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 22px;
    height: 22px;
    padding: 0 var(--sp-1);
    border-radius: var(--r-pill);
    background-color: var(--n-200);
    color: var(--n-700);
    font-family: var(--font-data);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-caption);
    font-weight: 600;
}

.disclosure__chevron {
    color: var(--n-500);
    transition: transform .12s ease;
}

.disclosure--inline[open] > .disclosure__summary .disclosure__chevron {
    transform: rotate(180deg);
}

.disclosure__panel {
    margin-top: var(--sp-3);
    padding: var(--sp-4);
    border: 1px solid var(--n-200);
    border-radius: var(--r-md);
    background-color: var(--n-000);
}

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

/* ══ 14-actividad.css ══ */

/*
 * Parte de `components.css`. Las partes se concatenan por orden de nombre;
 * el índice y el motivo del corte están en `components/00-indice.css`.
 */

/* ── Línea de tiempo de actividad ───────────────────────────────────────── */
/*
 * El historial del cliente (Especificación §13). Es una lista ordenada real
 * —`<ol>`—: lo que se está mostrando es una secuencia de hechos, y esa es la
 * información que el lector de pantalla tiene que recibir.
 *
 * El hilo vertical se dibuja con un borde izquierdo sobre el ítem y no con un
 * pseudo-elemento absoluto por ítem: así el hilo se corta solo en el último,
 * sin depender de contar filas, y sigue al contenido cuando el navegador
 * reflowea por zoom.
 *
 * Los hechos automáticos se distinguen del contacto cargado a mano por el
 * fondo del ícono, no por el color del texto: el color no puede ser la única
 * señal (Lineamiento §10) y la etiqueta de tipo, que va escrita, ya lo dice.
 */

.timeline {
    list-style: none;
    margin: var(--sp-4) 0 0;
    padding: 0;
}

.timeline__item {
    display: flex;
    gap: var(--sp-3);
    padding: 0 0 var(--sp-4) var(--sp-4);
    margin-left: var(--sp-4);
    border-left: 1px solid var(--n-200);
}

.timeline__item:last-child {
    border-left-color: transparent;
    padding-bottom: 0;
}

.timeline__icon {
    display: flex;
    align-items: center;
    justify-content: center;
    flex: none;
    width: 32px;
    height: 32px;
    margin-left: calc(-1 * var(--sp-4) - 17px);
    border: 1px solid var(--n-300);
    border-radius: var(--r-pill);
    background-color: var(--n-000);
    color: var(--n-700);
}

.timeline__item--automatic .timeline__icon {
    background-color: var(--n-100);
    border-color: var(--n-200);
    color: var(--n-500);
}

.timeline__body {
    min-width: 0;
    padding-top: var(--sp-1);
}

.timeline__title {
    margin: 0;
    font-size: var(--fs-body);
    line-height: var(--lh-body);
    color: var(--n-900);
}

.timeline__title .overline {
    display: block;
    color: var(--n-500);
}

.timeline__detail {
    margin: var(--sp-1) 0 0;
    font-size: var(--fs-body-dense);
    line-height: var(--lh-body-dense);
    color: var(--n-700);
    white-space: pre-line;
}

.timeline__meta {
    margin: var(--sp-1) 0 0;
}

/* ── Observaciones del presupuesto ──────────────────────────────────────── */
/*
 * El texto que el vendedor escribió y que sale impreso en el papel del cliente,
 * cuando ya no se puede editar. `pre-line` no es un detalle: el vendedor escribe
 * una condición por renglón y el PDF los respeta, así que la pantalla tiene que
 * mostrar exactamente lo mismo que el cliente va a leer. El fondo apenas
 * distinto y el borde izquierdo lo separan de las aclaraciones al pie sin
 * convertirlo en un aviso de color.
 */

.quote-notes__text {
    margin: 0 0 var(--sp-3);
    padding: var(--sp-3);
    border-left: var(--bw-3) solid var(--n-400);
    background-color: var(--n-100);
    border-radius: var(--r-sm);
    color: var(--n-700);
    white-space: pre-line;
}

/* ══ 15-presupuesto.css ══ */

/*
 * Parte de `components.css`. Las partes se concatenan por orden de nombre;
 * el índice y el motivo del corte están en `components/00-indice.css`.
 */

/* ── Desglose de precio (Lineamiento §8) ────────────────────────────────── */
/*
 * Lista vertical, no tabla: son conceptos encadenados en un orden que importa,
 * no filas comparables entre sí. El orden es el del motor de precios y no se
 * altera por presentación.
 *
 * El frontend no calcula nada de esto: lo muestra. La autoridad del número es
 * del backend (Especificación §10 y §35).
 */

.price-breakdown {
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--sp-1);
}

.price-breakdown__line {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--sp-4);
}

.price-breakdown__concept {
    font-family: var(--font-data);
    font-size: var(--fs-body-dense);
    line-height: var(--lh-body-dense);
    color: var(--n-700);
    margin: 0;
}

.price-breakdown__amount {
    font-family: var(--font-data);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-body-dense);
    line-height: var(--lh-body-dense);
    text-align: right;
    margin: 0;
    /*
     * Un importe no se parte en dos renglones. Con conceptos largos —el de una
     * combinación lo es— el signo quedaba arriba y el número abajo, que es
     * exactamente el tipo de lectura ambigua que el Lineamiento §4 prohíbe en
     * una pantalla operativa. El que se acomoda es el concepto, no la cifra.
     */
    white-space: nowrap;
}

.price-breakdown__line--strong {
    border-top: 1px solid var(--n-300);
    padding-top: var(--sp-2);
    margin-top: var(--sp-1);
}

.price-breakdown__line--strong .price-breakdown__concept,
.price-breakdown__line--strong .price-breakdown__amount {
    font-weight: 600;
}

.price-breakdown__line--total .price-breakdown__amount {
    font-family: var(--font-ui);
    font-size: var(--fs-amount);
    line-height: var(--lh-h2);
    font-weight: 700;
    color: var(--n-900);
}

/*
 * El resumen del presupuesto es el mismo desglose, un escalón más arriba: no
 * tiene los pasos del motor, tiene los dos totales. Se separa del último ítem
 * con un borde para que no se lea como un renglón más de ese ítem.
 */
.price-breakdown--summary {
    margin-top: var(--sp-5);
    padding-top: var(--sp-4);
    border-top: 1px solid var(--n-300);
}

/* ── Ítem cotizado (Lineamiento §8, Especificación §16) ─────────────────── */
/*
 * Cada ítem es una tarjeta dentro de la tarjeta: se separa con un borde y no
 * con sombra, como todo lo demás. El total de la línea va arriba a la derecha y
 * en el mismo tamaño del precio final del desglose, porque es lo que el
 * vendedor busca con la vista cuando el cliente pregunta «¿y ese cuánto sale?».
 *
 * La configuración se muestra como pares atributo/opción y no como una línea
 * corrida: el cliente los lee de a uno y pregunta por uno.
 */

.quote-item {
    border: 1px solid var(--n-200);
    border-radius: var(--r-md);
    padding: var(--sp-4);
    margin-top: var(--sp-4);
    display: flex;
    flex-direction: column;
    gap: var(--sp-3);
}

.quote-item__header {
    display: flex;
    /*
     * En un teléfono los tres bloques —casilla de aprobación, nombre del
     * producto e importe— no entran en un renglón: sin `wrap`, el importe se
     * salía del borde derecho y quedaba cortado justo en el dato que el
     * vendedor le está mostrando al cliente.
     */
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--sp-3) var(--sp-4);
}

/*
 * La foto del producto cotizado: caja 4:3 sin sombra propia (Lineamiento §9),
 * con la imagen entera y centrada adentro. Va primero en el
 * encabezado y con `flex: none`: es una referencia para reconocer el mueble de
 * un vistazo, no la protagonista de la fila, así que no crece ni se encoge
 * mientras el nombre y el importe se acomodan alrededor.
 *
 * `align-self: flex-start` la despega de la línea de base del encabezado: sin
 * eso, la imagen apoya su borde inferior sobre el mismo renglón que el nombre y
 * empuja el bloque entero hacia abajo.
 */
.quote-item__photo {
    flex: none;
    display: block;
    align-self: flex-start;
    width: 96px;
    height: 72px;
    border-radius: var(--r-md);
    border: 1px solid var(--n-200);
    background-color: var(--n-000);
    object-fit: contain;
    object-position: center;
}

/*
 * El hueco cuando el ítem no tiene foto. La clase se usaba en el Blade y no
 * estaba definida acá, así que el hueco salía sin borde ni fondo y la fila se
 * corría hacia la izquierda respecto de las que sí tienen foto. Va en `--n-100`
 * —el tono de estado vacío— para que se lea como «falta la foto» y no como una
 * foto blanca.
 */
.quote-item__photo--empty {
    background-color: var(--n-100);
}

@media (max-width: 767.98px) {
    /* En el teléfono la foto cede tamaño antes que el precio: 64 px alcanzan
     * para reconocer el producto y dejan el nombre completo en el renglón. */
    .quote-item__photo {
        width: 64px;
        height: 48px;
    }
}

/* El nombre es lo que cede: se encoge y parte en varios renglones antes que
 * empujar al importe fuera de la tarjeta. */
.quote-item__identity {
    flex: 1 1 200px;
    min-width: 0;
}

@media (max-width: 767.98px) {
    /* Con el armazón compacto la casilla se queda con el renglón entero, para que el
     * nombre y el importe compartan el siguiente y no quede cada cosa en una
     * línea distinta. */
    .quote-item__header .approval-check {
        flex: 1 0 100%;
    }
}

.quote-item__name {
    margin: 0;
    font-size: var(--fs-h3);
    line-height: var(--lh-h3);
    font-weight: 600;
    color: var(--n-900);
}

.quote-item__total {
    flex: none;
    margin: 0;
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-amount);
    line-height: var(--lh-h2);
    font-weight: 700;
    color: var(--n-900);
    white-space: nowrap;
}

.quote-item__configuration {
    margin: 0;
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
    gap: var(--sp-2);
}

.quote-item__attribute {
    display: flex;
    gap: var(--sp-2);
    font-size: var(--fs-body-dense);
    line-height: var(--lh-body-dense);
}

.quote-item__attribute dt {
    color: var(--n-500);
}

.quote-item__attribute dd {
    margin: 0;
    color: var(--n-900);
    font-weight: 600;
}

/*
 * El código interno de un atributo o de una opción, junto al nombre que
 * identifica.
 *
 * Va en la familia de datos —Archivo, la del Lineamiento §4 para columnas y
 * códigos— porque es una cadena que se compara carácter por carácter contra una
 * planilla de fábrica, y en esa familia el 1, el 7 y el 9 no se confunden. El
 * `letter-spacing` positivo es por lo mismo: «TAP-PAN-01» leído de un renglón
 * apretado se copia mal.
 *
 * Peso normal y color secundario: acompaña al nombre, no compite con él. Quien
 * vende lee «Pana premium»; quien fabrica busca el código y lo encuentra
 * pegado, sin tener que cruzar dos listas.
 */
.config-code {
    font-family: var(--font-data);
    font-size: var(--fs-caption);
    line-height: var(--lh-caption);
    font-weight: 400;
    letter-spacing: .02em;
    color: var(--n-500);
    white-space: nowrap;
}

/*
 * Dentro de la configuración del ítem, el código baja a su propio renglón.
 *
 * Al lado del nombre quedaba a merced del ancho: «Patas PAT» entraba en la
 * línea y «Tapizado TAP» no, así que dos atributos de la misma lista se dibujaban
 * distinto según cuántas letras tuviera cada uno. Debajo del nombre siempre cae
 * en el mismo lugar, y los códigos de la columna quedan alineados entre sí, que
 * es como se leen cuando hay que copiarlos a una orden de fábrica.
 */
.quote-item__attribute .config-code {
    display: block;
}

/*
 * El artículo de fábrica del renglón, debajo del nombre del producto.
 *
 * Es el único código del ítem que alguien va a copiar a una orden de trabajo, y
 * por eso se dibuja más fuerte que la referencia interna que va abajo: la
 * etiqueta en peso 600 y el código en la familia de datos, a 13 px en lugar de
 * los 12 del `caption`. La diferencia de tamaño es chica a propósito —no compite
 * con el nombre del producto ni con el total— pero suficiente para que el ojo
 * caiga primero acá y no en el `INT-…`, que es exactamente el error que esta
 * pantalla arrastraba.
 */
.quote-item__article {
    margin: var(--sp-1) 0 0;
    font-size: var(--fs-label);
    line-height: var(--lh-label);
    font-weight: 600;
    color: var(--n-700);
}

.quote-item__article .config-code {
    font-size: var(--fs-label);
    line-height: var(--lh-label);
    color: var(--n-900);
}

.quote-item__actions {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: var(--sp-4);
    padding-top: var(--sp-3);
    border-top: 1px solid var(--n-200);
}

.quote-item__actions form {
    display: flex;
    align-items: flex-end;
    gap: var(--sp-2);
}

.quote-item__actions .field {
    margin: 0;
}

.quote-item__actions .field__control {
    max-width: 96px;
}

/* ── Descuento del vendedor (Especificación §20, Lineamiento §8) ────────── */
/*
 * El campo vive pegado al desglose porque el descuento se decide mirando el
 * total, y debajo va siempre la ayuda con el máximo autorizado: el vendedor
 * tiene que ver cuánto más puede descontar sin probar hasta que el sistema le
 * diga que no.
 *
 * La ayuda no se achica ni se recorta cuando el bloque se apila en el celular:
 * un techo a medio leer es peor que no mostrarlo.
 */

.quote-item__discount {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    gap: var(--sp-3) var(--sp-4);
    margin-top: var(--sp-3);
    padding-top: var(--sp-3);
    border-top: 1px solid var(--n-200);
}

.quote-item__discount .field {
    margin: 0;
    max-width: 320px;
}

.quote-item__discount .field__control {
    max-width: 96px;
}

.quote-item__discount .button {
    margin-top: 22px;
}

.quote-item__override {
    margin: 22px 0 0;
}

.quote-item__discount-block {
    margin-top: var(--sp-3);
}

/* ── Producto especial cotizado por la fábrica (decisión D-22) ──────────── */
/*
 * Es la única línea del presupuesto que puede no tener precio, y la pantalla
 * tiene que decirlo sin disimularlo. Por eso la ficha se parece a un ítem
 * —mismo borde, mismo radio, misma caja— pero no es igual: el borde izquierdo
 * de 3 px lleva el color del estado, y donde iría el importe hay una frase.
 *
 * Un ítem a medias disfrazado de ítem normal es exactamente cómo un vendedor
 * termina leyéndole al cliente un total que todavía no incluye el ropero.
 */

.special-item {
    border: 1px solid var(--n-200);
    border-left: var(--bw-3) solid var(--n-300);
    border-radius: var(--r-md);
    padding: var(--sp-4);
    margin-top: var(--sp-4);
    display: flex;
    flex-direction: column;
    gap: var(--sp-3);
}

.special-item--pendiente { border-left-color: var(--warn); }
.special-item--cotizado { border-left-color: var(--ok); }
.special-item--rechazado { border-left-color: var(--err); }
.special-item--cancelado { border-left-color: var(--n-300); }

.special-item__header {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--sp-2) var(--sp-3);
}

.special-item__name {
    margin: 0;
    font-size: var(--fs-h3);
    line-height: var(--lh-h3);
    font-weight: 600;
}

/*
 * El importe cotizado, cuando lo hay, en la familia de datos densos y con
 * cifras tabulares, igual que cualquier otro dinero del sistema
 * (Lineamiento §4). Que este número lo haya escrito una persona y no el motor no
 * cambia cómo se lee.
 */
.special-item__amount {
    margin-left: auto;
    font-family: var(--font-data);
    font-variant-numeric: tabular-nums;
    font-weight: 600;
    color: var(--n-900);
}

.special-item__detail {
    margin: 0;
    display: grid;
    gap: var(--sp-2);
}

.special-item__detail div {
    display: grid;
    grid-template-columns: 148px 1fr;
    gap: var(--sp-3);
}

.special-item__detail dt {
    font-size: var(--fs-label);
    line-height: var(--lh-label);
    font-weight: 600;
    color: var(--n-500);
}

.special-item__detail dd {
    margin: 0;
    /* Las características vienen escritas a mano en el mostrador, con saltos de
     * línea y todo: se respetan, porque una lista de medidas convertida en un
     * párrafo corrido es la forma de que la fábrica lea mal una medida. */
    white-space: pre-line;
}

/*
 * En el teléfono la característica y su valor se apilan: en dos columnas, el
 * valor se queda con la mitad de una pantalla angosta y una medida escrita a
 * mano se parte.
 *
 * Etapa 1: el corte venía escrito en 640 px y ahora cierra en 599.98 px. De 600
 * a 639 px la ficha vuelve a las dos columnas; no se midió en pantalla propia
 * —no hay teléfono de ese ancho— y queda anotado con el estrechamiento gemelo
 * del configurador para verificarlo en la Etapa 2.
 */
@media (max-width: 599.98px) {
    .special-item__detail div {
        grid-template-columns: 1fr;
        gap: var(--sp-1);
    }
}

.special-item__actions {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: var(--sp-3);
}

.special-item__actions .field {
    margin: 0;
    min-width: 180px;
}

/*
 * La bandeja de la fábrica es una lista de fichas, no una tabla: cada pedido
 * trae dos textos largos y un formulario, y eso en una celda no entra. El
 * ancho se limita para que las características no se lean a 1.400 px de largo.
 */
.factory-inbox {
    max-width: 880px;
}

/* ── Aprobación por ítem (Especificación §17, Lineamiento §8) ───────────── */
/*
 * El cliente aprueba parte del presupuesto y el resto lo sigue pensando. La
 * pantalla tiene que mostrar las dos cosas al mismo tiempo, y por eso el ítem
 * aprobado se marca —borde izquierdo verde y fondo apenas teñido— pero el no
 * aprobado NO se atenúa: sigue siendo un ítem vivo, editable, sobre el que el
 * cliente todavía puede decir que sí.
 *
 * La casilla es un cuadrado real de 24 px dentro de un área táctil de 44, como
 * pide el Lineamiento §10: se toca con el dedo, de pie, con el cliente al lado.
 */

.quote-item--aprobado {
    border-left: var(--bw-3) solid var(--ok);
    background: var(--ok-tint);
}

.approval-check {
    flex: none;
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    min-height: var(--control-h);
    margin: 0;
}

.approval-check__box {
    flex: none;
    width: 24px;
    height: 24px;
    margin: 0;
    accent-color: var(--ok);
    cursor: pointer;
}

.approval-check__label {
    /* El área táctil se agranda desde la etiqueta, no desde la casilla: así el
     * dedo tiene 44 px de alto sin que el cuadrado se deforme. */
    display: flex;
    align-items: center;
    min-height: var(--control-h);
    font-size: var(--fs-label);
    line-height: var(--lh-label);
    font-weight: 600;
    letter-spacing: .01em;
    color: var(--n-700);
    cursor: pointer;
}

/*
 * Con JavaScript, `ui.js` esconde este botón y manda el formulario al tildar.
 * Sin JavaScript queda visible y es el único modo de guardar: una casilla que no
 * guarda nada sería una promesa falsa.
 */
.approval-check__submit {
    padding: 0 var(--sp-3);
}

.approval-check--registrada {
    align-items: center;
    gap: var(--sp-1);
    color: var(--ok);
    font-size: var(--fs-label);
    line-height: var(--lh-label);
    font-weight: 600;
}

/*
 * La barra de aprobación (Lineamiento §8).
 *
 * Pegajosa al pie del contenido: mientras el vendedor recorre veinte ítems con
 * el cliente, los dos importes que importan —cotizado y aprobado— no se le van
 * de la pantalla. En móvil se levanta por encima de la barra de navegación
 * inferior, que es fija, para no quedar tapada.
 */
.approval-bar {
    position: sticky;
    bottom: 0;
    z-index: 1;
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    justify-content: space-between;
    gap: var(--sp-4);
    margin-top: var(--sp-5);
    padding: var(--sp-4);
    background: var(--n-000);
    border: 1px solid var(--n-200);
    border-top: var(--bw-3) solid var(--brand-yellow);
    border-radius: var(--r-lg);
    box-shadow: var(--sh-2);
}

.approval-bar__totals {
    margin: 0;
    font-family: var(--font-data);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-h3);
    line-height: var(--lh-h3);
    font-weight: 600;
    color: var(--n-900);
}

.approval-bar__figures {
    display: flex;
    flex-direction: column;
    gap: var(--sp-1);
}

.approval-bar__actions {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: var(--sp-4);
}

.approval-bar__actions form {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: var(--sp-2);
}

.approval-bar__actions .field {
    margin: 0;
    min-width: 220px;
}

.approval-bar__actions .caption {
    flex-basis: 100%;
    margin: 0;
}

.approval-bar__next {
    display: flex;
    flex-direction: column;
    gap: var(--sp-1);
    max-width: 280px;
}

@media (max-width: 767.98px) {
    .approval-bar {
        bottom: calc(var(--bottomnav-h) + env(safe-area-inset-bottom, 0px));
    }
}

/* ══ 16-revisiones.css ══ */

/*
 * Parte de `components.css`. Las partes se concatenan por orden de nombre;
 * el índice y el motivo del corte están en `components/00-indice.css`.
 */

/* ── Revisiones del presupuesto (Especificación §15) ────────────────────── */
/*
 * Una revisión histórica es el documento tal como se emitió. Se lee igual que el
 * presupuesto vigente —mismo parcial de ítem, mismo desglose— y lo único que
 * cambia es el marco: fondo más apagado y una banda arriba que lo dice con
 * palabras.
 *
 * La banda no se resuelve solo con color: dice «solo lectura» y lleva
 * `role="status"`, porque quien navega con lector de pantalla también tiene que
 * enterarse de que está mirando algo viejo (Lineamiento §10).
 */

.historic-band {
    display: flex;
    align-items: center;
    min-height: 32px;
    margin: 0 0 var(--sp-4);
    padding: 0 var(--sp-4);
    background: var(--n-200);
    border-radius: var(--r-md);
    color: var(--n-700);
    font-size: var(--fs-overline);
    font-weight: 700;
    letter-spacing: 0.1em;
    text-transform: uppercase;
}

.card--historic {
    background: var(--n-100);
}

/* ── Comparación entre revisiones (Lineamiento §8) ──────────────────────── */
/*
 * «Las diferencias resaltadas: aumento con fondo #FBEDE3, baja con fondo
 * #E6F2EC» — que son `--warn-bg` y `--ok-bg`, ya declarados en tokens.css.
 *
 * El fondo acompaña; no informa solo. Cada fila escribe además qué se movió, y
 * el borde izquierdo da una segunda pista de forma para quien no distingue esos
 * dos tonos. Un renglón sin cambios queda sin marcar a propósito: si todo se
 * pinta, nada resalta.
 */

.compare-row--agregado,
.compare-row--quitado,
.compare-row--modificado {
    border-left: var(--bw-3) solid transparent;
}

.compare-row--agregado {
    background: var(--ok-bg);
    border-left-color: var(--ok);
}

.compare-row--quitado {
    background: var(--err-bg);
    border-left-color: var(--err);
}

.compare-row--modificado {
    background: var(--warn-bg);
    border-left-color: var(--warn);
}

/*
 * El importe de la diferencia. Va en el color semántico y con el signo siempre
 * explícito (Lineamiento §4): un «+ $ 42.000» se entiende sin mirar el color.
 */

.compare-amount--aumento {
    color: var(--warn);
    font-weight: 600;
}

.compare-amount--baja {
    color: var(--ok);
    font-weight: 600;
}

.compare-amount--igual {
    color: var(--n-500);
}

/* ══ 17-paneles.css ══ */

/*
 * Parte de `components.css`. Las partes se concatenan por orden de nombre;
 * el índice y el motivo del corte están en `components/00-indice.css`.
 */

/* ── Paneles: cifra destacada ───────────────────────────────────────────── */
/*
 * La grilla de cifras del Smart Panel. Son tarjetas dentro de una tarjeta, así
 * que no llevan borde propio: se separan por el aire, que es lo que el
 * Lineamiento §5 pide antes que una sombra.
 *
 * El número va en Montserrat 700 a 32 px —el tamaño Display— porque es lo único
 * que alguien mira de lejos; la etiqueta va arriba en Overline y la aclaración
 * abajo en Caption, de modo que la cifra nunca aparezca sin decir de qué es.
 */

.metric-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
    gap: var(--sp-4);
    margin-top: var(--sp-4);
    /* Las cifras de una misma fila se leen juntas: si una arranca cuatro
     * píxeles más abajo que sus vecinas, la fila deja de leerse como fila. */
    align-items: baseline;
}

.metric {
    display: flex;
    flex-direction: column;
    gap: var(--sp-1);
}

.metric__label {
    color: var(--n-500);
}

/*
 * La cifra grande con su estado al lado: «25,0 %» y el chip «Atrasada».
 *
 * Van en la misma línea porque se leen juntos —el porcentaje sin la lectura del
 * calendario no dice si la meta viene bien— y se alinean por la base para que el
 * chip no flote respecto de la cifra. En pantalla angosta el chip baja solo.
 */
.metric-headline {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--sp-2) var(--sp-3);
    margin-bottom: var(--sp-2);
}

.metric__value {
    margin: 0;
    color: var(--n-900);
    font-size: var(--fs-figure-xl);
    font-weight: 700;
    line-height: var(--lh-figure-xl);
}

/*
 * El dinero va un punto más chico que un porcentaje y no se parte nunca.
 *
 * El Lineamiento §4 prohíbe abreviar importes —nada de «$ 1,2 M»—, así que un
 * total de siete cifras ocupa lo que ocupa. A 32 px la columna no le alcanza y
 * el navegador corta por el espacio que separa el símbolo del número: se lee
 * «$» arriba y «2.266.000,00» abajo, que es exactamente el tipo de cifra
 * ambigua que esta pantalla no puede permitirse. Un porcentaje, en cambio,
 * entra siempre y se queda en Display.
 */
.metric__value.numeric {
    font-size: var(--fs-figure-lg);
    line-height: var(--lh-figure-lg);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
    /*
     * `.numeric` alinea a la derecha porque nació para columnas de tabla, donde
     * eso es lo que hace cuadrar las cifras. Acá no hay columna que cuadrar: hay
     * una etiqueta arriba a la izquierda y su número debajo, y separarlos a los
     * dos extremos de la tarjeta obliga a leer en diagonal.
     */
    text-align: left;
}

/* En pantalla angosta el Display se achica: 32 px por seis cifras no entra. */
@media (max-width: 599.98px) {
    .metric__value {
        font-size: var(--fs-figure-md);
        line-height: var(--lh-figure-md);
    }

    .metric__value.numeric {
        font-size: var(--fs-figure-sm);
        line-height: var(--lh-h2);
    }
}

/* ── Prioridades de hoy ─────────────────────────────────────────────────── */
/*
 * Las filas de 56 px del Lineamiento §8: ícono, cliente, motivo y una acción
 * explícita a la derecha. La fila entera es clickeable —el enlace del título
 * ocupa su área con un pseudoelemento— pero el botón de la derecha sigue siendo
 * un objetivo propio, para que en una tablet nadie tenga que apuntar fino.
 */

.priority-list {
    list-style: none;
    margin: var(--sp-3) 0 0;
    padding: 0;
}

.priority-row {
    position: relative;
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    min-height: 56px;
    padding: var(--sp-2) 0;
    border-bottom: 1px solid var(--n-200);
}

.priority-row:last-child {
    border-bottom: 0;
}

.priority-row__icon {
    display: flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 40px;
    width: 40px;
    height: 40px;
    border-radius: var(--r-md);
    background-color: var(--n-100);
    color: var(--n-500);
}

.priority-row__body {
    display: flex;
    flex-direction: column;
    gap: 2px;
    flex: 1 1 auto;
    min-width: 0;
}

/*
 * El enlace del título cubre la fila. El botón de la derecha se levanta con
 * `position: relative` para quedar por encima y seguir siendo clickeable por su
 * cuenta.
 */
.priority-row__link::after {
    content: '';
    position: absolute;
    inset: 0;
}

.priority-row__meta {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: var(--sp-1);
    flex: 0 0 auto;
}

.priority-row__action {
    position: relative;
    flex: 0 0 auto;
}

/*
 * En celular y en tablet la fila se apila, pero el ícono no se va solo a un
 * renglón.
 *
 * Con `flex-wrap`, el cuerpo —cuyo texto más largo mide más que el ancho que
 * le queda al lado del ícono— se pasaba entero al renglón siguiente y el ícono
 * quedaba flotando arriba, sin nada al lado y sin decir de qué era. Con grilla
 * la posición se declara en vez de negociarse: ícono y título comparten el
 * primer renglón, el detalle y el botón ocupan los de abajo a todo el ancho.
 *
 * El ícono baja a 32 px: al lado de un título de 16 px, los 40 px de escritorio
 * pesaban más que el texto. No es un control —no se toca—, así que no le
 * corresponde el área táctil de 44 px del Lineamiento §10; el botón de la fila,
 * que sí se toca, la conserva entera.
 *
 * El apilado llega hasta 1023.98 px y no hasta el teléfono. La receta de
 * escritorio —ícono 40 px, cuerpo elástico, fecha y botón sin encoger— pide
 * unos 700 px de fila, y en el tramo del armazón compacto la tarjeta mide
 * 488 px a 768 px de pantalla porque el menú lateral se queda con 248 px
 * fijos. Con esa cuenta al cuerpo le quedaban 90 px y «Presupuesto
 * PRE-CC-2026-00007» se partía letra por letra. Medido a 768 px, pero el
 * defecto ya estaba de 769 a 1023 px antes de la Etapa 1.
 */
@media (max-width: 1023.98px) {
    .priority-row {
        display: grid;
        grid-template-columns: auto minmax(0, 1fr);
        align-items: center;
        gap: var(--sp-2) var(--sp-3);
        padding: var(--sp-3) 0;
    }

    .priority-row__icon {
        grid-column: 1;
        flex: none;
        width: 32px;
        height: 32px;
    }

    .priority-row__body {
        grid-column: 2;
    }

    /* Título de la fila en H3 (Lineamiento §4): es el dato por el que se elige
     * el atajo y a 15 px competía con su propia línea de ayuda. */
    .priority-row__body > strong {
        font-size: var(--fs-h3);
        line-height: var(--lh-h3);
    }

    /* El detalle y la acción no se alinean con el título: arrancan en el borde
     * de la tarjeta y ocupan el ancho entero, que es lo que hace que el botón
     * sea un objetivo cómodo. */
    .priority-row__meta {
        grid-column: 1 / -1;
        align-items: flex-start;
        flex-direction: row;
        flex-wrap: wrap;
    }

    .priority-row__action {
        grid-column: 1 / -1;
    }

    .priority-row__action .button {
        width: 100%;
    }
}

/* ── Ver el resto ───────────────────────────────────────────────────────── */
/*
 * Lo que no entra en las primeras filas de una lista corta queda acá, a un
 * toque: el resto de la agenda en Inicio, el resto de los presupuestos abiertos
 * en el configurador.
 *
 * Es un `<details>` nativo por lo mismo que el resto de los desplegables del
 * sistema: abre con teclado, se anuncia solo y no depende de JavaScript. El
 * marcador propio del navegador se apaga —el triángulo de Safari no es el ícono
 * del sistema y el Lineamiento §9 no admite mezclar sets— y la flecha es la del
 * sprite, que gira al abrir.
 *
 * La etiqueta cambia de «Ver 4 compromisos más» a «Ver menos» ocultando una de
 * las dos con `display: none`, que además la saca del árbol de accesibilidad:
 * el lector de pantalla lee una sola, la que corresponde al estado.
 */

.reveal-more {
    border-top: 1px solid var(--n-200);
    margin-top: var(--sp-2);
}

.reveal-more__summary {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    min-height: 44px;
    font-size: var(--fs-label);
    line-height: var(--lh-label);
    font-weight: 600;
    letter-spacing: 0.01em;
    color: var(--n-700);
    cursor: pointer;
    list-style: none;
}

.reveal-more__summary::-webkit-details-marker {
    display: none;
}

.reveal-more__summary:hover {
    color: var(--n-900);
}

.reveal-more__chevron {
    color: var(--n-500);
    transition: transform 0.12s ease;
}

.reveal-more[open] .reveal-more__chevron {
    transform: rotate(180deg);
}

.reveal-more__label--open,
.reveal-more[open] .reveal-more__label--closed {
    display: none;
}

.reveal-more[open] .reveal-more__label--open {
    display: inline;
}

/* La lista desplegada arranca pegada al resumen: ya hay un borde que la separa. */
.reveal-more .priority-list,
.reveal-more .pick-list {
    margin-top: 0;
    border-top: 1px solid var(--n-200);
}

@media (prefers-reduced-motion: reduce) {
    .reveal-more__chevron {
        transition: none;
    }
}

/* ── Pipeline por estado ────────────────────────────────────────────────── */
/*
 * Una fila por estado: el chip con el nombre configurado, la barra con la
 * proporción de operaciones, el conteo y el importe. La barra es decorativa
 * —`aria-hidden`— porque el número está escrito al lado: un lector de pantalla
 * no gana nada leyendo un ancho.
 */

.pipeline {
    list-style: none;
    margin: var(--sp-3) 0 0;
    padding: 0;
}

.pipeline__row {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    padding: var(--sp-2) 0;
    border-bottom: 1px solid var(--n-200);
}

.pipeline__row:last-child {
    border-bottom: 0;
}

.pipeline__row .chip {
    flex: 0 0 auto;
}

.pipeline__row .numeric {
    margin-left: auto;
}

.pipeline__bar {
    flex: 1 1 auto;
    min-width: 40px;
    height: 4px;
    border-radius: var(--r-pill);
    background-color: var(--n-200);
    overflow: hidden;
}

/* La barra del pipeline mide plata cotizada: es dato actual, y va en el azul
 * de la paleta semántica como el resto de las medidas del sistema. */
.pipeline__value {
    display: block;
    height: 100%;
    background-color: var(--data-blue);
}

/* ── Visor de documento (Especificación §30) ────────────────────────────── */
/*
 * La pantalla que muestra el PDF sin sacar a nadie de la aplicación. El
 * problema que resuelve es del celular: el documento abierto a secas se lo
 * queda el visor de PDF del teléfono, que no tiene ninguna manera de volver.
 * Acá el documento es contenido de una pantalla y las salidas —volver, enviar,
 * descargar— están siempre arriba de él y siempre visibles.
 */
.document-viewer__back {
    margin-bottom: var(--sp-3);
}

.document-viewer__actions {
    margin-bottom: var(--sp-3);
}

.document-viewer__hint {
    margin-top: 0;
    margin-bottom: var(--sp-3);
}

/*
 * El documento se dibuja dentro de una caja de alto fijo y no a lo largo de la
 * página: si creciera con el PDF, las acciones quedarían a diez pantallas de
 * scroll de distancia y volveríamos al problema original. El alto es relativo a
 * la ventana para que en una notebook se lea una hoja entera y en un teléfono
 * quede lugar para los botones.
 */
.document-viewer__frame {
    border: 1px solid var(--n-200);
    border-radius: var(--r-md);
    background-color: var(--n-100);
    height: min(72vh, 900px);
    overflow: hidden;
}

.document-viewer__object {
    display: block;
    width: 100%;
    height: 100%;
    border: 0;
}

/* Lo que ve quien no puede ver el PDF incrustado. No es un error: es la mitad
 * de los teléfonos, y por eso se ve como una tarjeta terminada y no como un
 * hueco. */
.document-viewer__fallback {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--sp-3);
    height: 100%;
    padding: var(--sp-5);
    text-align: center;
}

.document-viewer__fallback-icon {
    color: var(--n-400);
}

/*
 * La pestaña aparte se ofrece solo donde no hace daño. En una notebook es
 * cómoda y el navegador conserva la pantalla anterior detrás; en un teléfono es
 * exactamente el camino de ida que este visor vino a cerrar. Lo que se pierde
 * ahí no se pierde: descargar y enviar hacen lo mismo y están al lado.
 */
@media (max-width: 767.98px) {
    .document-viewer__external {
        display: none;
    }

    .document-viewer__frame {
        height: min(60vh, 560px);
    }
}

/* ══ 18-catalogo.css ══ */

/*
 * Parte de `components.css`. Las partes se concatenan por orden de nombre;
 * el índice y el motivo del corte están en `components/00-indice.css`.
 */

/* ── Importación de catálogo ────────────────────────────────────────────── */
/*
 * La pantalla de importación tiene dos mitades con trabajos opuestos: arriba,
 * el resumen de lo que trae el paquete, que se lee de un vistazo; abajo, la
 * lista de problemas, que se lee línea por línea con el archivo al lado.
 *
 * El resumen usa cifras grandes porque la primera pregunta de quien sube un
 * catálogo es «¿entró todo?», y esa pregunta se contesta comparando dos números
 * —21 configuraciones acá, 21 filas en la planilla— y no leyendo un párrafo.
 */
.import-summary {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
    gap: var(--sp-4);
    margin: 0;
}

.import-summary__item {
    display: flex;
    flex-direction: column;
    gap: var(--sp-1);
    padding: var(--sp-3) var(--sp-4);
    border: 1px solid var(--n-200);
    border-radius: var(--r-md);
    background-color: var(--n-000);
}

.import-summary__value {
    font-family: var(--font-data);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-figure-md);
    line-height: var(--lh-figure-md);
    font-weight: 600;
    color: var(--n-900);
}

.import-summary__label {
    font-size: var(--fs-caption);
    line-height: var(--lh-caption);
    color: var(--n-500);
}

/*
 * Cada problema es una fila y no una celda de tabla: trae cuatro datos de largo
 * muy distinto —gravedad, lugar, motivo y corrección— y en una tabla la columna
 * del motivo se comía a las demás. La barra de color a la izquierda repite lo
 * que ya dice el chip; el color nunca va solo (Lineamiento §10).
 */
.import-issues {
    display: flex;
    flex-direction: column;
    gap: var(--sp-2);
    margin: 0;
    padding: 0;
    list-style: none;
}

.import-issue {
    display: flex;
    flex-direction: column;
    gap: var(--sp-1);
    padding: var(--sp-3) var(--sp-4);
    border: 1px solid var(--n-200);
    border-left: var(--bw-3) solid var(--n-300);
    border-radius: var(--r-md);
    background-color: var(--n-000);
}

.import-issue--blocking { border-left-color: var(--err); }
.import-issue--pending { border-left-color: var(--info); }
.import-issue--warning { border-left-color: var(--warn); }

.import-issue__head {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--sp-2);
}

.import-issue__location {
    font-family: var(--font-data);
    font-size: var(--fs-caption);
    line-height: var(--lh-caption);
    color: var(--n-500);
    word-break: break-word;
}

.import-issue__message {
    font-size: var(--fs-body-dense);
    line-height: var(--lh-body-dense);
    color: var(--n-700);
}

.import-issue__suggestion {
    font-size: var(--fs-caption);
    line-height: var(--lh-caption);
    color: var(--n-500);
}

/*
 * La consecuencia de una decisión, alineada con el texto de su casilla.
 *
 * En las confirmaciones del paso 4 la consecuencia es un párrafo hermano de la
 * etiqueta, no está adentro: tiene que estarlo, porque `aria-describedby` lo
 * asocia a la casilla y meterlo dentro del `<label>` lo volvería parte del
 * texto que se lee al enfocar. El costo es que arranca pegado al margen
 * izquierdo, debajo de la casilla, y se lee como si fuera un texto suelto de la
 * tarjeta en vez de la letra chica de esa afirmación. La sangría es el ancho de
 * la casilla (20 px) más su separación (--sp-2), que es exactamente donde
 * empieza el texto de arriba.
 */
.import-issue .choice + .import-issue__suggestion {
    padding-inline-start: calc(20px + var(--sp-2));
}

/* ── Buscador del catálogo: sugerencias y motivos ───────────────────────── */
/*
 * La lista desplegable del buscador.
 *
 * Es de las poquísimas superficies del sistema que llevan sombra: el
 * Lineamiento §5 la reserva para menús desplegables y popovers, y esto es
 * exactamente eso. Flota sobre la tabla, así que sin sombra no se entendería
 * dónde termina.
 */
.field--search {
    position: relative;
}

.suggest {
    position: absolute;
    z-index: 30;
    inset-inline: 0;
    top: 100%;
    max-height: 60vh;
    margin: var(--sp-1) 0 0;
    padding: var(--sp-1);
    overflow-y: auto;
    list-style: none;
    background: var(--n-000);
    border: 1px solid var(--n-200);
    border-radius: var(--r-md);
    box-shadow: var(--sh-2);
}

.suggest[hidden] {
    display: none;
}

/*
 * Cada sugerencia es un enlace real, no un `div` con un manejador de clic:
 * abre en otra pestaña con el botón del medio, se puede copiar la dirección y
 * el teclado la alcanza sin que haya que programar nada.
 */
.suggest__item > a {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    /* 48 px de alto real: el Lineamiento §10 exige área táctil de 44 px como
       mínimo, y esta lista se usa sobre todo en tablet. */
    min-height: 48px;
    padding: var(--sp-2) var(--sp-3);
    border-radius: var(--r-sm);
    color: var(--n-900);
    text-decoration: none;
}

.suggest__item > a:hover {
    background: var(--n-100);
}

/*
 * El renglón resaltado con las flechas. Lleva fondo **y** una barra a la
 * izquierda: el Lineamiento §10 no admite que un estado se comunique solo con
 * color.
 */
.suggest__item--active > a {
    background: var(--n-100);
    box-shadow: inset var(--bw-3) 0 0 var(--brand-yellow);
}

.suggest__thumb {
    flex: 0 0 auto;
    width: 48px;
    height: 36px;
    object-fit: cover;
    background: var(--n-100);
    border-radius: var(--r-sm);
}

.suggest__text {
    min-width: 0;
}

.suggest__name {
    display: block;
    font-weight: 600;
    font-size: var(--fs-body-dense);
    line-height: var(--lh-body-dense);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.suggest__meta {
    display: block;
    font-family: Archivo, Roboto, system-ui, sans-serif;
    font-size: var(--fs-caption);
    line-height: var(--lh-caption);
    color: var(--n-500);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.suggest__empty {
    padding: var(--sp-3);
    font-size: var(--fs-label);
    color: var(--n-500);
}

/*
 * El aviso de qué entendió el buscador.
 *
 * Informativo, con la barra izquierda del tono `info`. No usa el amarillo de
 * marca: el amarillo es acción, no aclaración (Lineamiento §3.1).
 */
.search-note {
    display: flex;
    align-items: flex-start;
    gap: var(--sp-2);
    margin-top: var(--sp-4);
    padding: var(--sp-3) var(--sp-4);
    background: var(--info-bg);
    border-inline-start: var(--bw-3) solid var(--info);
    border-radius: var(--r-sm);
    color: var(--n-700);
}

.search-note .icon {
    flex: 0 0 auto;
    margin-top: 2px;
    color: var(--info);
}

.search-note__body > .caption {
    margin: 0;
}

.search-note__body > .caption + .caption {
    margin-top: var(--sp-1);
}

/*
 * El motivo por el que una fila coincidió.
 *
 * Sin tono semántico a propósito: no es un estado del producto, y gastar uno de
 * los ocho tonos de la paleta cerrada acá le restaría significado a los chips
 * que sí dicen algo del producto.
 */
.chip--reason {
    display: inline-flex;
    align-items: center;
    height: 20px;
    padding: 0 var(--sp-2);
    border: 1px solid var(--n-300);
    border-radius: var(--r-pill);
    background: var(--n-100);
    color: var(--n-500);
    font-size: var(--fs-overline);
    font-weight: 700;
    letter-spacing: .04em;
    white-space: nowrap;
}

.chip-list--reasons {
    margin-top: var(--sp-2);
}

/* Los filtros puestos cuando la búsqueda no encontró nada. */
.empty-filters {
    margin: var(--sp-2) 0 0;
    padding: 0;
    list-style: none;
    text-align: start;
}

.empty-filters > li {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-2);
    padding: var(--sp-1) 0;
    font-size: var(--fs-label);
    color: var(--n-700);
}

/* ══ 19-estados-vacios.css ══ */

/*
 * Parte de `components.css`. Las partes se concatenan por orden de nombre;
 * el índice y el motivo del corte están en `components/00-indice.css`.
 */

/* ── Estados vacíos ─────────────────────────────────────────────────────────
 *
 * La auditoría encontró nueve maneras distintas de decir «no hay nada acá»:
 * desde un `<p>` con «Sin resultados.» hasta una fila de tabla con un guión. Lo
 * que tienen en común no es el estilo, es lo que les falta: ninguna explica por
 * qué está vacío ni qué hacer. «Sin resultados» a secas deja al vendedor sin
 * saber si el presupuesto no existe, si el filtro lo está escondiendo o si la
 * pantalla se rompió.
 *
 * Un estado vacío de este sistema dice tres cosas en este orden: qué pasó, por
 * qué, y cuál es el próximo paso. El ícono es el último en importancia y por eso
 * es lo primero que se cae en la variante compacta.
 *
 * No se usa para errores: un error va en `.notice--error`, porque hay algo que
 * salió mal y el tono tiene que decirlo. Acá nada falló.
 */

.empty-state {
    display: flex;
    flex-direction: column;
    align-items: center;
    padding: var(--sp-6) var(--sp-4);
    text-align: center;
}

/*
 * El círculo del ícono.
 *
 * Gris y no amarillo: el amarillo es el color de la acción en este sistema
 * (Lineamiento §7), y un estado vacío no es una acción —la acción es el botón
 * que está más abajo—. Pintar el círculo de amarillo le roba la atención al
 * único elemento de este bloque que se puede apretar.
 *
 * Los 48 px son la medida del ícono de 24 px con su aire alrededor; no son un
 * paso de la escala de espaciado y por eso van escritos.
 *
 * La clase se aplica sobre el propio `<svg>` del ícono —`<x-icon
 * class="empty-state__icon">`— y no sobre una `<div>` que lo envuelva, así que
 * el círculo y el dibujo son el mismo elemento: de ahí los 48 px de caja con
 * 12 px de relleno, que con `border-box` dejan el trazo en los 24 px de
 * `.icon--lg`. Sin el relleno, el ícono se estira hasta el filo del círculo y el
 * círculo deja de leerse como un círculo.
 */
.empty-state__icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: none;
    width: 48px;
    height: 48px;
    padding: var(--sp-3);
    margin-bottom: var(--sp-4);
    border-radius: var(--r-pill);
    background-color: var(--n-100);
    color: var(--n-400);
}

/* El nivel de la tarjeta y no el de la pantalla: un estado vacío vive dentro de
 * un bloque, y un `h1` ahí compite con el título de la pantalla que está
 * arriba. */
.empty-state__title {
    margin-bottom: var(--sp-2);
    font-size: var(--fs-h3);
    line-height: var(--lh-h3);
    font-weight: 600;
    color: var(--n-900);
}

/*
 * La explicación. Va en gris secundario porque se lee después del título, y con
 * ancho máximo medido en caracteres y no en píxeles: lo que cansa al leer un
 * renglón largo es la cantidad de letras que el ojo tiene que recorrer antes de
 * volver al margen, no los píxeles. Sesenta es el techo habitual para texto
 * centrado, que ya de por sí es más difícil de seguir que el alineado a la
 * izquierda. `ch` acompaña al cuerpo cuando cambia de 16 a 15 px en escritorio.
 */
.empty-state__text {
    max-width: 56ch;
    margin-bottom: 0;
    color: var(--n-500);
}

/* Las acciones: una principal y, a lo sumo, una alternativa. `flex-wrap` para
 * que en un teléfono angosto la segunda baje de renglón en lugar de encogerse
 * hasta cortar su rótulo. */
.empty-state__actions {
    display: flex;
    justify-content: center;
    align-items: center;
    gap: var(--sp-2);
    flex-wrap: wrap;
    margin-top: var(--sp-4);
}

/*
 * Versión compacta: la tarjeta chica de un panel y la celda que ocupa el ancho
 * de una tabla sin filas.
 *
 * Pierde el círculo y la mitad del aire. En esos dos lugares el bloque entero
 * mide menos que el estado vacío completo: con el círculo de 48 px adentro, la
 * tarjeta de un panel se estiraba al doble del alto de sus vecinas y la fila
 * vacía de la tabla quedaba más alta que cinco filas con datos.
 */
.empty-state--compact {
    padding: var(--sp-4) var(--sp-3);
}

.empty-state--compact .empty-state__icon {
    display: none;
}

.empty-state--compact .empty-state__title {
    font-size: var(--fs-body);
    line-height: var(--lh-body);
}

/* ══ 20-esqueletos-de-carga.css ══ */

/*
 * Parte de `components.css`. Las partes se concatenan por orden de nombre;
 * el índice y el motivo del corte están en `components/00-indice.css`.
 */

/* ── Esqueletos de carga ────────────────────────────────────────────────────
 *
 * Un hueco con la medida final de lo que va a llegar. No es un adorno de espera
 * —para eso basta un texto— sino la forma de que la pantalla no salte: si el
 * lugar se reserva recién cuando el dato llega, el contenido que ya estaba
 * visible se desplaza hacia abajo y el dedo que iba a un botón termina en otro.
 *
 * ── Qué le toca al HTML ───────────────────────────────────────────────────
 *
 * El esqueleto es decorativo y no se anuncia: cada bloque de acá viaja con
 * `aria-hidden="true"`, porque un lector de pantalla leyendo «blanco, blanco,
 * blanco» no informa nada. Lo que sí se anuncia es la espera, y eso no lo hace
 * el esqueleto: quien lo use pone `aria-busy="true"` en el contenedor real —la
 * tabla, la lista, la tarjeta— y lo quita cuando el contenido llega. Las dos
 * cosas juntas: el hueco se ve y no se escucha, la espera se escucha y no se
 * dibuja.
 *
 * ── Y por qué el brillo se apaga ──────────────────────────────────────────
 *
 * El brillo es lo que distingue un hueco que está cargando de un bloque gris
 * que se dibujó mal. Pero es también una animación que se repite sin fin en
 * toda la pantalla, y eso es exactamente lo que `prefers-reduced-motion` viene
 * a desactivar: para quien tiene sensibilidad vestibular, un movimiento
 * continuo en el borde de la visión marea. Apagado, el hueco sigue siendo un
 * hueco gris con la medida correcta, que es su trabajo principal.
 */

.skeleton {
    display: block;
}

/*
 * El renglón, que es la unidad de todo lo demás.
 *
 * Los 12 px de alto no son un tamaño de letra: es el alto de la barra gris que
 * ocupa el lugar de un renglón de 16/24, centrada en su caja por el margen de
 * abajo. Escribirlo como `var(--fs-body)` sería mentir sobre lo que es.
 *
 * El brillo se dibuja con un degradado más ancho que la caja y se anima su
 * posición, no su opacidad: con `opacity` el hueco entero se desvanece y vuelve,
 * y la pantalla parece estar fallando. Moviendo el degradado, lo que recorre el
 * hueco es un reflejo.
 */
.skeleton__line {
    /* El renglón se escribe como `<span>` —no aporta semántica y el bloque
     * entero es `aria-hidden`—, así que el `display` tiene que estar acá: en un
     * elemento en línea, el alto y los márgenes verticales no se aplican y los
     * renglones salían todos pegados en una sola fila de cero píxeles. */
    display: block;
    height: 12px;
    border-radius: var(--r-sm);
    margin-bottom: var(--sp-2);
    background-color: var(--n-200);
    background-image: linear-gradient(
        90deg,
        var(--n-200) 0%,
        var(--n-100) 50%,
        var(--n-200) 100%
    );
    background-size: 200% 100%;
    background-repeat: no-repeat;
    animation: skeleton-sheen 1.4s ease-in-out infinite;
}

/* El último renglón no agrega aire abajo: el que separa del contenido lo pone
 * el bloque que contiene al esqueleto. */
.skeleton__line:last-child {
    margin-bottom: 0;
}

/*
 * Un párrafo real no termina con el renglón completo, y un esqueleto de
 * renglones todos iguales se lee como una tabla y no como un texto. El renglón
 * corto va al final del grupo.
 */
.skeleton__line--short {
    width: 40%;
}

/* El título del bloque: más alto y más corto que el cuerpo, con un poco más de
 * aire debajo, igual que un título de verdad. */
.skeleton__line--title {
    height: 20px;
    width: 60%;
    margin-bottom: var(--sp-3);
}

/*
 * No hay una variante «bloque macizo»: la forma por omisión del componente
 * —`variant="block"`— es el grupo de renglones y es la que el noventa por
 * ciento de los huecos necesita. El día que una foto o un gráfico pida un
 * rectángulo entero, se agrega acá con su consumidor al lado; un modificador sin
 * nadie que lo escriba es CSS que nadie mantiene.
 */

/*
 * Dentro de una tarjeta el esqueleto no dibuja otra superficie: la tarjeta ya
 * tiene fondo, borde y relleno, y un segundo recuadro adentro se lee como una
 * tarjeta anidada. Lo único que cambia es el aire entre renglones, que sube al
 * de un texto de tarjeta: el contenido de una tarjeta se lee de corrido y no es
 * una lista densa. El renglón de título lo pone el componente en el primero.
 *
 * El último vuelve a cerrar sin aire, y hace falta decirlo con las dos clases
 * porque `.skeleton__line:last-child` tiene menos peso que este par y perdía.
 */
.skeleton--card .skeleton__line {
    margin-bottom: var(--sp-3);
}

.skeleton--card .skeleton__line:last-child {
    margin-bottom: 0;
}

/*
 * Filas de tabla.
 *
 * Cada renglón mide una fila entera —`var(--row-h)`, el mismo token que la
 * tabla de verdad— porque es ahí donde el salto se nota más: ocho filas que
 * aparecen de golpe mueven el pie de la pantalla medio metro. El radio se apaga:
 * una fila de tabla no tiene esquinas redondeadas.
 *
 * El ancho completo está escrito a propósito y con las dos clases, para ganarle
 * a `--short`: el componente le pone el renglón corto al último de la serie sin
 * mirar la variante, y una última fila de tabla al 40 % no se lee como una fila
 * que está por llegar sino como una tabla mal dibujada. En un párrafo el
 * renglón corto es lo natural; en una grilla, no.
 */
.skeleton--table .skeleton__line {
    height: var(--row-h);
    width: 100%;
    border-radius: 0;
    margin-bottom: var(--sp-1);
}

/*
 * Lista de tarjetas o de filas: varios esqueletos, uno por elemento previsto.
 * Se separan con el mismo aire que la lista real, de modo que la altura total
 * del bloque sea la que va a tener cuando llegue el contenido.
 */
.skeleton-list {
    display: flex;
    flex-direction: column;
    gap: var(--sp-3);
}

@keyframes skeleton-sheen {
    from { background-position: 200% 0; }
    to { background-position: -200% 0; }
}

/*
 * Movimiento reducido: se apaga el brillo y se quita el degradado.
 *
 * `base.css` ya recorta la duración de toda animación del sistema a 0,01 ms,
 * que alcanza para que nada se mueva. Esto se escribe igual y para este
 * componente en particular porque con el degradado congelado en una posición
 * cualquiera el hueco queda con una franja clara al medio, que se lee como un
 * dibujo roto. Sin degradado es un rectángulo gris parejo.
 */
@media (prefers-reduced-motion: reduce) {
    .skeleton__line {
        animation: none;
        background-image: none;
    }
}

/* ══ 21-pasos-y-progreso.css ══ */

/*
 * Parte de `components.css`. Las partes se concatenan por orden de nombre;
 * el índice y el motivo del corte están en `components/00-indice.css`.
 */

/* ── Pasos ──────────────────────────────────────────────────────────────────
 *
 * «Dónde estoy y cuánto falta», para el configurador y para los formularios
 * largos. Sin esto, un recorrido de cuatro pantallas se siente infinito: la
 * persona no abandona porque sea difícil, abandona porque no sabe si está a
 * mitad de camino o recién empezando.
 *
 * Va sobre un `<ol>` y no sobre `<div>`: los pasos tienen orden y el orden es
 * parte del dato. Un lector de pantalla anuncia «lista de cuatro elementos,
 * elemento 2» y con eso ya dijo lo mismo que el dibujo. El estado del paso
 * actual lo lleva el HTML en `aria-current="step"`, no el color: el color de
 * acá refuerza y nunca comunica solo (Lineamiento §10).
 *
 * La barra de progreso con porcentaje —`.progress`— no vive acá sino en
 * `11-indicadores`, donde ya estaba. Son dos cosas distintas: una mide una
 * fracción continua, la otra cuenta etapas discretas con nombre.
 */

.steps {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    margin: 0;
    padding: 0;
    list-style: none;
}

/* Cada paso reparte con sus hermanos el ancho de la fila y es su conector el
 * que cede: el último, que no lleva conector, se queda con lo que mide su
 * etiqueta. */
.steps__item {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    flex: 1 1 auto;
    min-width: 0;
}

.steps__item:last-child {
    flex: 0 0 auto;
}

/*
 * El marcador: el círculo con el número del paso.
 *
 * El número va en la familia de datos y con cifras tabulares por el mismo
 * motivo que un importe: el «1» y el «4» tienen que ocupar lo mismo o los
 * círculos de la fila quedan con el número descentrado de a uno.
 */
.steps__marker {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: none;
    width: 24px;
    height: 24px;
    border-radius: var(--r-pill);
    background-color: var(--n-200);
    color: var(--n-700);
    font-family: var(--font-data);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-caption);
    line-height: var(--lh-caption);
    font-weight: 700;
}

/* Los pasos que todavía no llegaron van en gris secundario: están, se leen, y
 * no compiten con el que hay que atender. */
.steps__label {
    font-size: var(--fs-label);
    line-height: var(--lh-label);
    color: var(--n-500);
}

/*
 * El paso actual.
 *
 * El amarillo de marca entra acá como relleno del círculo con la tinta `--n-900`
 * encima, que da 4,8:1 y es su único uso legal (Lineamiento §3.1). Nunca como
 * color de la etiqueta: sobre blanco el amarillo da 2,05:1 y la palabra
 * desaparece. Es la misma regla que gobierna el botón primario, y está
 * comprobada en `PaletteContrastTest`.
 *
 * La etiqueta se distingue por peso y por tinta, no por color de marca.
 */
.steps__item--current .steps__marker {
    background-color: var(--brand-yellow);
    color: var(--n-900);
}

.steps__item--current .steps__label {
    color: var(--n-900);
    font-weight: 600;
}

/* Los pasos cumplidos: el verde de «esto salió bien». El marcador sigue
 * mostrando el número y no una tilde, porque el número es lo que permite volver
 * a un paso anterior sabiendo cuál es. */
.steps__item--done .steps__marker {
    background-color: var(--ok);
    color: var(--n-000);
}

.steps__item--done .steps__label {
    color: var(--n-700);
}

/*
 * El conector entre un paso y el siguiente.
 *
 * Es un `::after` del propio paso y no un elemento aparte: una línea decorativa
 * en el HTML es un elemento más que el lector de pantalla tiene que ignorar, y
 * la lista `<ol>` dejaría de tener solo `<li>` adentro. Se dibuja como un ítem
 * flexible más dentro de la fila, así que estira o encoge lo que sobre sin
 * ninguna cuenta de posición absoluta. El último paso no lleva: una línea que
 * sale del último círculo hacia la nada sugiere un paso que no existe.
 */
.steps__item:not(:last-child)::after {
    content: "";
    flex: 1 1 var(--sp-4);
    min-width: var(--sp-4);
    height: 1px;
    background-color: var(--n-300);
}

/*
 * En celular los pasos se comprimen a los marcadores.
 *
 * Cuatro etiquetas en 360 px dan 90 px por paso y «Medidas y acabados» entra en
 * cuatro renglones de dos palabras: la fila de pasos termina midiendo más alto
 * que el campo que hay que llenar. Queda la etiqueta del paso actual, que es la
 * única que la persona necesita leer —las demás las va a leer cuando le toquen—,
 * y los otros marcadores siguen contando cuántos son y cuántos faltan.
 */
@media (max-width: 599.98px) {
    .steps__item {
        flex: 0 0 auto;
    }

    .steps__item .steps__label {
        display: none;
    }

    .steps__item--current {
        flex: 1 1 auto;
    }

    .steps__item--current .steps__label {
        display: block;
    }
}

/* ══ 22-barra-de-accion.css ══ */

/*
 * Parte de `components.css`. Las partes se concatenan por orden de nombre;
 * el índice y el motivo del corte están en `components/00-indice.css`.
 */

/* ── Barra de acción ────────────────────────────────────────────────────────
 *
 * El dato que importa y la acción principal, juntos y siempre a la vista. Es
 * para la pantalla larga de celular: el vendedor arma un presupuesto de
 * dieciocho ítems con el cliente al lado, y el total y el botón de guardar
 * quedan a dos pantallazos de distancia del lugar donde está mirando.
 *
 * ── Lo que esta barra nunca hace ──────────────────────────────────────────
 *
 * No se superpone a un total ni a un botón de confirmación. Es la regla que la
 * gobierna y la que hay que leer antes de usarla: una barra fija que tapa el
 * importe que el vendedor está leyendo en voz alta al cliente es peor que no
 * tener barra, y una que tapa el botón «Confirmar» del pie convierte un
 * recorrido terminado en un recorrido imposible.
 *
 * De eso se siguen dos cosas concretas:
 *
 *   1. La pantalla que la use reserva lugar al pie con `.has-action-bar`. No
 *      alcanza con que la barra sea fija: el último bloque del contenido queda
 *      debajo de ella y no hay desplazamiento que lo saque, porque la página ya
 *      terminó.
 *   2. No convive con `.approval-bar` —la barra de aprobación del presupuesto,
 *      que ya muestra cotizado y aprobado— ni con el pie de un `.modal` o de un
 *      `.sheet`. Donde ya hay un total pegajoso, la barra no entra: sobra.
 *
 * Y el amarillo no aparece en el fondo de la barra: la barra es una superficie,
 * el botón de adentro es la acción. Si la barra entera fuera amarilla, el botón
 * primario dejaría de distinguirse de ella.
 */

.action-bar {
    position: fixed;
    inset: auto 0 0 0;
    z-index: var(--z-bottomnav);
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-3);
    flex-wrap: wrap;
    padding: var(--sp-3) var(--sp-4);
    /* La franja del gesto de inicio del teléfono se suma al relleno de abajo:
     * sin esto el botón queda en la zona donde el sistema se come el toque. */
    padding-bottom: calc(var(--sp-3) + env(safe-area-inset-bottom, 0px));
    border-top: 1px solid var(--n-200);
    background-color: var(--n-000);
    /* `--sh-2` es la sombra de un bloque elevado del sistema y es la que va, aun
     * siendo una sombra descendente en una barra que está abajo: su trabajo acá
     * no es dibujar una dirección de luz sino despegar la barra del contenido
     * que pasa por debajo. El borde superior es el que hace el trabajo visible
     * (Lineamiento §5: bordes antes que sombras). */
    box-shadow: var(--sh-2);
}

/*
 * Por encima de la barra de navegación inferior, pero solo donde esa barra
 * existe, y por eso es un modificador y no el valor por omisión. La barra
 * inferior aparece hasta 767,98 px y únicamente en el armazón de vendedor: en
 * una pantalla sin ella, levantar la barra de acción 56 px deja una franja de
 * contenido asomando por abajo, que se lee como un error de dibujo. Suponerlo
 * es lo que hoy obliga a cada pantalla a corregirlo a mano.
 *
 * El área segura la resuelve la barra inferior con su propio relleno, igual que
 * hace `.approval-bar`: acá se suma una sola vez y no dos.
 */
.action-bar--above-nav {
    bottom: calc(var(--bottomnav-h) + env(safe-area-inset-bottom, 0px));
}

.action-bar__data {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

/* Qué es el número que está debajo. Sin esta línea, «$ 482.300,00» en una barra
 * fija puede ser el total, el saldo o la seña, y la diferencia importa. */
.action-bar__label {
    font-size: var(--fs-caption);
    line-height: var(--lh-caption);
    color: var(--n-500);
}

/*
 * El importe. Nunca abreviado —no existe «$ 1,2 M» en este sistema— y con
 * cifras tabulares, que es lo que permite comparar el total de antes con el de
 * después cuando cambia al tocar un ítem: sin ellas el número «salta» de ancho
 * y parece que se movió la pantalla.
 *
 * No declara interlineado porque `--fs-amount` no tiene uno: es el único nivel
 * de la escala que se usa en una sola línea y toma el del contexto, igual que
 * `.amount` en `base.css`.
 */
.action-bar__value {
    font-family: var(--font-data);
    font-size: var(--fs-amount);
    font-variant-numeric: tabular-nums;
    font-weight: 700;
    color: var(--n-900);
    white-space: nowrap;
}

.action-bar__actions {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    margin-left: auto;
}

/*
 * Desde 768 px la barra deja de ser fija.
 *
 * Ahí no hay nada que tapar: la pantalla entra casi completa, el total está a la
 * vista y una barra pegada al pie de la ventana se despega del bloque al que
 * pertenece. Se integra al pie de la tarjeta, separada por su borde superior, y
 * suelta la sombra: adentro de una tarjeta que ya tiene borde, la sombra la
 * hace parecer un segundo plano flotante.
 */
@media (min-width: 768px) {
    .action-bar {
        position: static;
        box-shadow: none;
        margin-top: var(--sp-4);
        padding: var(--sp-4) 0 0;
        padding-bottom: 0;
    }
}

/*
 * Versión no fija forzada.
 *
 * Para la pantalla que quiere el bloque «dato + acción» pero no lo quiere
 * anclado: el pie de un formulario corto, una tarjeta de resumen en una lista.
 * Va escrito después del tramo de escritorio a propósito: una consulta de medios
 * no agrega especificidad, así que si estuviera antes perdería contra las
 * declaraciones de arriba en las pantallas anchas.
 */
.action-bar--static {
    position: static;
    box-shadow: none;
    margin-top: var(--sp-4);
    padding: var(--sp-4) 0 0;
    padding-bottom: 0;
}

/*
 * El lugar reservado al pie del contenido.
 *
 * Es una utilidad y no una regla del componente porque el que tiene que ceder el
 * espacio es el contenido, y el contenido no sabe nada de la barra: lo único que
 * hace es llevar la clase cuando la barra está presente. Solo en celular,
 * porque desde 768 px la barra ya no es fija y no tapa nada.
 *
 * ── Por qué se reserva de más ─────────────────────────────────────────────
 *
 * La reserva cubre DOS filas de control y no una. No es prolijidad: en un
 * teléfono de 360 px el bloque de dato y el botón no entran lado a lado y la
 * barra se parte en dos filas —medida: 121 px, contra los 68 que ocupa en una—.
 * Con la reserva de una fila, la barra tapaba el final del contenido, que es
 * exactamente lo que esta barra promete no hacer.
 *
 * Y se reserva de más porque CSS no puede preguntarle a la barra cuánto mide:
 * el rótulo, el importe y el texto del botón los escribe cada pantalla. Sobrar
 * unos píxeles de aire al pie no lo nota nadie; faltar esconde el botón de
 * confirmar. El que no se puede equivocar es el que esconde.
 *
 * La medida se deriva de los mismos tokens que dibujan la barra —dos controles
 * y el aire de `--sp-7`— y se suma el área segura por si está apoyada en el
 * filo. La comprobación de navegador `tools/ui/verificar-componentes.js` mide
 * en la página que la barra no se superponga al último bloque, así que si
 * alguien ajusta esta cuenta de menos, la prueba lo dice.
 *
 * ── Dónde va la clase ─────────────────────────────────────────────────────
 *
 * En el bloque al que la barra pertenece —el `<form>`, la lista, la tarjeta—,
 * NO en `.app-main`. Por dos razones: `.app-main` ya reserva en `layout.css` el
 * alto de la barra de navegación inferior, así que lo único que falta reservar
 * es la barra de acción y nada más; y `layout.css` se carga después de esta
 * hoja, de modo que su `padding-bottom` le ganaría a este con la misma
 * especificidad. De ahí también que la variante levantada sobre la navegación
 * (`--above-nav`) no necesite reservar los 56 px del menú: eso ya está hecho
 * más arriba.
 */
@media (max-width: 767.98px) {
    .has-action-bar {
        padding-bottom: calc(var(--control-h) * 2 + var(--sp-7) + env(safe-area-inset-bottom, 0px));
    }
}

/* ══ 23-riel-de-filtros.css ══ */

/*
 * Parte de `components.css`. Las partes se concatenan por orden de nombre;
 * el índice y el motivo del corte están en `components/00-indice.css`.
 */

/* ── Riel de filtros ────────────────────────────────────────────────────────
 *
 * Una fila de filtros aplicados que se arrastra de costado, en lugar de cinco
 * desplegables apilados. En un teléfono, la barra de filtros de la V1 ocupa toda
 * la primera pantalla: el vendedor abre la lista de presupuestos y lo primero
 * que ve son los filtros, no los presupuestos. El riel entra en un renglón, dice
 * qué está filtrado y se quita de un toque.
 *
 * ── El único desplazamiento horizontal del sistema ───────────────────────
 *
 * Y está acotado al riel, nunca a la página. La diferencia no es de grado:
 * una página que se desplaza de costado esconde contenido sin avisar —es el
 * defecto que `cero desborde horizontal` persigue en las seis pantallas de
 * prueba—, mientras que un riel que se desplaza es un control que la persona
 * reconoce porque ve la cápsula cortada en el borde. Ninguna otra parte de este
 * sistema puede declarar `overflow-x: auto` en algo que no sea un riel: una
 * tabla ancha se convierte en tarjetas (`07-tablas`), un panel ancho se apila.
 *
 * Lo que no entra en el riel no se encoge ni se abrevia: vive en la hoja
 * «Filtros», que abre `.filter-rail__more`.
 */

.filter-rail {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    overflow-x: auto;
    /*
     * `proximity` y no `mandatory`: con el ajuste obligatorio, un riel de tres
     * cápsulas cortas se niega a quedar quieto donde la persona lo soltó y
     * vuelve de un salto a la cápsula más cercana, incluso cuando ya estaba
     * entera a la vista. Con `proximity` el ajuste solo actúa cuando el gesto
     * terminó cerca de un borde, que es cuando ayuda.
     */
    scroll-snap-type: x proximity;
    scroll-padding-inline: var(--sp-4);
    scroll-behavior: smooth;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: thin;
    /* El aire de abajo es para la barra de desplazamiento en escritorio: sin
     * él, la barra se dibuja encima del borde inferior de las cápsulas. */
    padding-bottom: var(--sp-1);
}

/* El tren de cápsulas. Va como elemento propio y no como los hijos directos del
 * riel para que el botón «Filtros» pueda ser hermano del tren y quedarse quieto
 * mientras el tren se mueve. */
.filter-rail__track {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    flex: 0 0 auto;
}

/*
 * El botón que abre la hoja «Filtros».
 *
 * `sticky` al borde derecho: tiene que estar siempre a mano, porque es la salida
 * para todo lo que el riel no puede mostrar, y un botón que hay que ir a buscar
 * arrastrando el riel hasta el final no es una salida. No se encoge
 * (`flex: none`) y lleva su propia superficie, de modo que las cápsulas pasen
 * por debajo sin asomar.
 */
.filter-rail__more {
    position: sticky;
    right: 0;
    flex: 0 0 auto;
    margin-left: auto;
    background-color: var(--n-000);
}

/*
 * La cápsula de filtro.
 *
 * Alto de 44 px, que es el área táctil real que el Lineamiento §10 exige, y no
 * un alto de 36 px con un pseudoelemento que lo estire: acá la cápsula es
 * grande de verdad porque hay lugar, y un área táctil invisible más grande que
 * lo que se ve hace que dos cápsulas vecinas se roben el toque.
 *
 * `flex: 0 0 auto` y `nowrap`: en un riel que se desplaza, una cápsula que se
 * encoge para entrar es lo contrario de lo que se quiere —se desplaza
 * justamente para no tener que encogerla— y una que se parte en dos renglones
 * estira el riel al doble de alto.
 */
.filter-chip {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--sp-2);
    flex: 0 0 auto;
    scroll-snap-align: start;
    height: 44px;
    padding: 0 var(--pad-chip-x);
    border: 1px solid var(--n-300);
    border-radius: var(--r-pill);
    background-color: var(--n-000);
    color: var(--n-900);
    font-family: inherit;
    font-size: var(--fs-label);
    font-weight: 600;
    line-height: 1;
    white-space: nowrap;
    text-decoration: none;
    cursor: pointer;
}

.filter-chip:hover {
    background-color: var(--n-100);
}

/*
 * El filtro aplicado.
 *
 * Se elige el relleno `--n-900` con tinta `--n-000` —15,45:1— y no el amarillo
 * de marca. El amarillo da 4,8:1 con `--n-900` encima, así que cumpliría el
 * contraste; el motivo es otro: en este sistema el amarillo significa «esta es
 * la acción de la pantalla» (Lineamiento §7) y está reservado al botón primario.
 * Una fila de cuatro cápsulas amarillas compite con el botón «Nuevo
 * presupuesto» que está a diez píxeles y le roba el único lugar donde el ojo
 * tiene que ir primero. El filtro aplicado no es una acción: es un estado.
 *
 * El tono oscuro es además el mismo que usa el segmento elegido del control
 * segmentado (`24-control-segmentado`): un solo indicador de selección para
 * todo el sistema.
 */
.filter-chip--active {
    background-color: var(--n-900);
    border-color: var(--n-900);
    color: var(--n-000);
}

.filter-chip--active:hover {
    background-color: var(--n-700);
}

/*
 * La cruz que quita el filtro.
 *
 * Lleva área táctil propia y no se conforma con la de la cápsula, porque son dos
 * acciones distintas: la cápsula lleva a editar el filtro y la cruz lo quita. Si
 * las dos comparten el toque, el filtro se borra cuando la persona quería
 * cambiarlo.
 *
 * Los 24 px que se ven se estiran con un pseudoelemento —la misma técnica del
 * chip de local en la barra superior— en lugar de agrandar el botón: una cruz de
 * 44 px dibujada haría la cápsula más alta que el riel entero.
 *
 * Doce píxeles para cada lado llevan los 24 a 48, por encima de los 44 que pide
 * el Lineamiento §10. Se sale dos píxeles por arriba y por abajo de la cápsula,
 * que mide 44, y eso no molesta a nadie: arriba y abajo del riel no hay otro
 * control. Lo que sí pisa son los 12 px de rótulo que tiene a la izquierda: es
 * el precio conocido de esta técnica y es el lado correcto para equivocarse,
 * porque quien apunta al borde derecho de una cápsula quiere quitar el filtro.
 */
.filter-chip__remove {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: none;
    width: 24px;
    height: 24px;
    margin-right: calc(-1 * var(--sp-1));
    padding: 0;
    border: none;
    border-radius: var(--r-pill);
    background: none;
    color: inherit;
    cursor: pointer;
}

.filter-chip__remove::before {
    content: "";
    position: absolute;
    inset: calc(-1 * var(--sp-3));
}

.filter-chip__remove:hover {
    background-color: var(--n-200);
}

.filter-chip--active .filter-chip__remove:hover {
    background-color: var(--n-700);
}

/* En escritorio la cápsula baja a 36 px: ahí se apunta con el puntero y el
 * mínimo táctil no aplica. El corte es el del armazón (768 px) y no el del
 * teléfono, porque una tablet de 700 px se toca con el dedo igual que un
 * celular. */
@media (min-width: 768px) {
    .filter-chip {
        height: 36px;
    }
}

/* En celular la barra de desplazamiento del riel se oculta: en una pantalla
 * táctil no se puede arrastrar y lo único que hace es comerse cuatro píxeles de
 * alto. La cápsula cortada en el borde derecho es la que indica que hay más. */
@media (max-width: 767.98px) {
    .filter-rail {
        scrollbar-width: none;
    }

    .filter-rail::-webkit-scrollbar {
        display: none;
    }
}

/* Movimiento reducido: el riel deja de deslizarse y salta a la posición. El
 * desplazamiento suave es lo que hace entender que el riel se movió de costado,
 * pero es también un movimiento lateral del contenido, que es justo lo que
 * marea a quien pidió menos movimiento. */
@media (prefers-reduced-motion: reduce) {
    .filter-rail {
        scroll-behavior: auto;
    }
}

/* ══ 24-control-segmentado.css ══ */

/*
 * Parte de `components.css`. Las partes se concatenan por orden de nombre;
 * el índice y el motivo del corte están en `components/00-indice.css`.
 */

/* ── Control segmentado ─────────────────────────────────────────────────────
 *
 * Alternar entre dos o tres vistas del mismo dato: «Mes / Trimestre / Año»,
 * «Tabla / Tarjetas», «Pendientes / Todos». Dos o tres opciones excluyentes y
 * siempre una elegida: si pueden estar todas apagadas, lo que corresponde es un
 * grupo de filtros y no esto.
 *
 * ── Sin una línea de JavaScript ──────────────────────────────────────────
 *
 * Es un `<fieldset>` de radios. No es un atajo: un grupo de radios ya trae todo
 * lo que este control necesita y que habría que reimplementar a mano sobre
 * botones —una sola opción elegida, recorrido con las flechas del teclado, el
 * valor que viaja en el `<form>` sin un campo oculto, y el anuncio «opción 2 de
 * 3, seleccionada» en el lector de pantalla—. Sobre `<button>` y un `click`,
 * las cuatro cosas se pierden y la tercera obliga a un campo oculto que hay que
 * mantener sincronizado.
 *
 * La leyenda del `<fieldset>` es obligatoria y va con `.visually-hidden`: el
 * grupo tiene que decir de qué elige —«Período», «Vista»— aunque en pantalla
 * eso se entienda por el contexto. Al ser absoluta, no participa de la fila.
 */

.segmented {
    display: inline-flex;
    align-items: stretch;
    margin: 0;
    padding: 0;
    border: 1px solid var(--n-300);
    border-radius: var(--r-md);
    background-color: var(--n-000);
    /* El relleno del segmento elegido llega hasta el filo interno del borde: sin
     * recortar, las esquinas cuadradas del primer y del último segmento asoman
     * por fuera del radio del grupo. */
    overflow: hidden;
}

/*
 * El segmento: la caja de 44 px, el fondo y el separador.
 *
 * No lleva relleno ni `cursor`, y eso es deliberado: el `<span>` no activa nada.
 * Lo apretable es la `<label>` de adentro, así que es ella la que se estira
 * hasta los cuatro filos del segmento y la que se queda con el relleno
 * horizontal (ver `.segmented__label`). Si el relleno viviera acá, los 16 px de
 * cada lado y la franja vertical que le sobra a la caja serían superficie muerta
 * con forma de botón: el puntero prometería un toque que no elige nada.
 */
.segmented__option {
    display: inline-flex;
    align-items: stretch;
    justify-content: center;
    flex: 1 1 auto;
    height: var(--control-h);
    background-color: var(--n-000);
    color: var(--n-700);
}

/* El separador va entre opciones y no alrededor de cada una: un borde por
 * segmento duplica la línea del medio y la deja de 2 px mientras las de las
 * puntas quedan de 1. */
.segmented__option + .segmented__option {
    border-left: 1px solid var(--n-300);
}

/*
 * El radio, oculto a la vista y presente para todo lo demás.
 *
 * No `display: none` ni `visibility: hidden`: los dos lo sacan del recorrido del
 * teclado y del árbol de accesibilidad, y con eso se pierde exactamente lo que
 * se vino a buscar: el control deja de ser enfocable y el lector de pantalla
 * deja de anunciarlo. Es la receta de `.visually-hidden` escrita acá para que no
 * dependa de que la plantilla se acuerde de agregar esa clase también.
 */
.segmented__input {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
}

/*
 * El rótulo es el control.
 *
 * Una `<label for>` sin medidas es una caja en línea que abraza su texto: con el
 * alto de 44 px declarado en el `<span>` de afuera, el toque útil terminaba
 * siendo el de las letras —unos 20 px de alto— y el resto del segmento no
 * respondía. Por eso se estira a los cuatro filos y se trae el relleno: la
 * superficie que se ve como un botón es, entera, la que elige la opción.
 *
 * `align-self: stretch` toma el alto del segmento en lugar de repetir
 * `--control-h`: la medida se declara una sola vez, en `.segmented__option`.
 */
.segmented__label {
    display: flex;
    align-items: center;
    justify-content: center;
    flex: 1 1 auto;
    align-self: stretch;
    padding: 0 var(--sp-4);
    font-size: var(--fs-label);
    line-height: var(--lh-label);
    font-weight: 600;
    white-space: nowrap;
    cursor: pointer;
}

/*
 * La opción elegida.
 *
 * Se resuelve con `:has()` y no con `:checked + …` porque lo que hay que pintar
 * es el segmento entero —la caja de 44 px— y no solo su texto: el hermano del
 * radio es el rótulo, y pintarlo deja una cápsula oscura flotando dentro de un
 * segmento blanco. `:has()` ya se usa en esta misma carpeta (`08-listados`) y
 * está en todos los navegadores de las tablets del salón.
 *
 * El relleno es `--n-900` con tinta `--n-000`, el mismo par que el filtro
 * aplicado del riel: un solo indicador de selección para todo el sistema. No el
 * amarillo de marca, que significa «esta es la acción de la pantalla» y está
 * reservado al botón primario (Lineamiento §7); elegir una vista no es la acción
 * de la pantalla, es el estado desde el cual se la mira.
 */
.segmented__option:has(.segmented__input:checked) {
    background-color: var(--n-900);
    color: var(--n-000);
}

/*
 * El foco se proyecta del radio al rótulo.
 *
 * El control real no se ve, así que su anillo de foco tampoco: sin esto, quien
 * recorre la pantalla con el teclado pierde el cursor al llegar al grupo, que
 * es lo que el Lineamiento §10 prohíbe. Es el mismo patrón del selector de
 * archivo (`.file-field__input:focus-visible + .file-field__button`).
 *
 * Va sobre el rótulo porque el rótulo es el control: es la caja que se aprieta y
 * es la que tiene que mostrarse enfocada.
 *
 * Y el anillo va hacia adentro —`outline-offset` negativo— porque el rótulo
 * llega a los filos del segmento y el grupo recorta con `overflow: hidden`: un
 * anillo dibujado por fuera se perdería entero en el borde y, en la primera y la
 * última opción, también en el radio de las esquinas. Hacia adentro se ve
 * completo en las tres posiciones.
 *
 * El desplazamiento es exactamente el grosor del trazo, y por eso sale del
 * mismo token en vez de un número suelto. El `outline` se pinta hacia afuera
 * desde la caja que el desplazamiento define: con −2 px y un trazo de 3 px el
 * anillo iría de −2 px a +1 px y ese último píxel cae fuera del recorte del
 * grupo, así que el foco se veía de 2 px —un tercio más fino que el del resto
 * del sistema, que es `--bw-3`—. Medido en navegador, no deducido. Con el
 * desplazamiento igual al trazo, el anillo ocupa de −3 px a 0 y se ve entero.
 */
.segmented__input:focus-visible + .segmented__label {
    outline: var(--bw-3) solid var(--focus);
    outline-offset: calc(-1 * var(--bw-3));
    border-radius: var(--r-sm);
}

/*
 * En celular ocupa el ancho entero con las opciones repartidas en partes
 * iguales.
 *
 * Con el ancho del contenido, «Trimestre» hace su segmento el doble de ancho que
 * «Mes» y la fila queda desprolija; peor, el segmento angosto baja de los 44 px
 * de ancho táctil. Repartido en tres, cada opción se lleva un tercio de la
 * pantalla, que en 360 px son 120 px: de sobra. El relleno horizontal baja a
 * 8 px porque el ancho ya no lo da él.
 */
@media (max-width: 599.98px) {
    .segmented {
        display: flex;
        width: 100%;
    }

    .segmented__option {
        flex: 1 1 0;
    }

    /* El relleno se ajusta donde vive: en el rótulo, que es el que lo lleva. */
    .segmented__label {
        padding: 0 var(--sp-2);
    }
}

