/*
 * ╔══════════════════════════════════════════════════════════════════════════╗
 * ║  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
 *   25-sugerencias             la lista que aparece mientras se escribe
 *   26-asistente               el asistente de voz y su tarjeta de uso
 *   27-aviso-de-entorno        la franja que avisa que la instalación es de
 *                              prueba (el piloto de experiencia de usuario)
 *
 * ── 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á. */
    min-height: var(--control-h);
    height: auto;
    max-width: 100%;
    white-space: normal;
    text-align: center;
    overflow-wrap: normal;
    padding: var(--sp-2) 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.
 */
/*
 * `position: relative` no mueve nada: está por los textos de lector de pantalla
 * que viven dentro de la tabla. `.visually-hidden` es `position: absolute`, y un
 * elemento absoluto se recorta en el ancestro posicionado más cercano, no en el
 * que tiene `overflow`. Sin esta línea, su caja quedaba referida al documento:
 * la tabla de pedidos —ocho columnas— empujaba el ancho del documento a 1183 px
 * en una pantalla de 1024 y aparecía la barra horizontal de toda la página, la
 * que el Lineamiento §10 prohíbe, mientras la tabla sí se desplazaba bien por
 * dentro. Con el ancestro posicionado acá, el recorte vuelve a ser de la tarjeta.
 */
.data-table__wrap {
    position: relative;
    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.
 *
 * El marcado sigue siendo una `<table>` con sus encabezados, pero conviene no
 * decir que el lector de pantalla no pierde nada, porque sí pierde: pasar
 * `table`, `tbody`, `tr` y `td` a `block`/`grid` saca los roles de tabla, fila y
 * celda del árbol de accesibilidad en WebKit y Blink, y la lista se lee como
 * texto corrido con las etiquetas de `data-label` adelante. Se entiende, pero no
 * se navega por columnas. Cerrarlo es agregar `role="table"`/`role="row"`/
 * `role="cell"` en el marcado de las vistas; está pendiente.
 */
@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;
    }
}

/*
 * Modo tarjeta en tablet: `.data-table--cards`
 * ============================================
 *
 * El problema que resuelve, medido en el servidor el 02/11/2026 con el listado
 * de demostración:
 *
 *   ancho de ventana    área de contenido   ancho que pide la tabla
 *   768 px                   486 px         886 px (Presupuestos, 7 columnas)
 *   810 px                   528 px         886 px (Presupuestos)
 *   820 px                   538 px         886 px (Presupuestos)
 *   1024 px                  742 px         883 px (Presupuestos)
 *   1024 px                  742 px         975 px (Pedidos, 8 columnas)
 *   1180 px                  898 px         975 px (Pedidos)
 *   1280 px                  998 px         975 px (Pedidos) — ya entra
 *   1440 px                 1158 px         886 / 975 px — entran las dos
 *
 * El área de contenido no es el ancho de la ventana: es el ancho menos 282 px
 * —248 px del menú lateral fijo, 1 px de su borde y 32 px de relleno de
 * `.app-main`—. Esa resta vale para toda la columna del medio: 1024 − 282 =
 * 742, 1280 − 282 = 998. Un iPad horizontal de 1024 px deja 742 px útiles,
 * menos que los 975 px que pide Pedidos, así que la tabla se desplazaba por
 * dentro del recuadro y el vendedor tenía que arrastrarla para ver el total. Es
 * la corrección que pidió el cliente.
 *
 * Que la cuenta sea sobre la VENTANA y no sobre el dispositivo tiene una
 * consecuencia que conviene tener presente: un navegador de escritorio con la
 * ventana en 1152 px deja 870 px y también ve tarjetas. No es un efecto no
 * deseado —a 870 px la tabla de Pedidos desborda igual— pero sí es algo que
 * alguien de administración puede reportar como «me cambió la pantalla».
 *
 * Por qué el corte está en 1280 px y no en 1024 px
 * -----------------------------------------------
 * Porque lo que decide no es la pantalla sino lo que sobra después del menú:
 * a 1280 px el área de contenido llega a ~998 px y recién ahí entran las ocho
 * columnas de Pedidos. Cortar en 1024 px —como estaba— devolvía la tabla de
 * escritorio justo en los dos anchos de iPad horizontal, que es donde el
 * cliente vio el desborde.
 *
 * Esto agrega un cuarto tramo a la escala de puntos de corte, que tenía tres.
 * Es una decisión deliberada y está anotada como tal en el encabezado de
 * `tests/Unit/Design/BreakpointScaleTest.php`, que es la prueba que existe
 * justamente para que un corte nuevo no entre sin que nadie lo note.
 *
 * Por qué una clase y no `.data-table` a secas
 * --------------------------------------------
 * Solo Presupuestos y Pedidos tienen siete y ocho columnas. Los demás listados
 * entran de sobra en tablet y volverlos tarjetas sería desperdiciar la pantalla
 * del administrador, además de cambiar pantallas que nadie pidió tocar. La
 * clase se pone en las dos tablas que la necesitan y en ningún otro lado.
 *
 * Por qué el modo tarjeta va acotado y no por defecto
 * ---------------------------------------------------
 * Se probó al revés —tarjeta como estado base y tabla devuelta en un solo
 * `min-width: 1280px`— para agregar un literal en lugar de dos. Se descartó:
 * esa forma obliga a deshacer en escritorio las cincuenta y cuatro
 * declaraciones del modo tarjeta, y varias chocan con reglas base por
 * especificidad (el borde que la última fila no lleva, el relleno holgado de
 * «sin resultados», la columna numérica alineada a la derecha, el color del
 * número). Son cuatro maneras de romper el escritorio para ahorrar una línea.
 * Acotado, en cambio, a 1280 px y más no se aplica nada de esto y la tabla
 * vuelve sola a las reglas base.
 *
 * Las reglas repiten las del bloque de celular de más arriba porque el alcance
 * es distinto —ahí son todas las tablas, acá solo dos— y sin paso de
 * compilación no hay forma de compartir un cuerpo entre dos reglas con
 * selectores diferentes. La tarjeta que el vendedor ve en el celular es la misma
 * que ve en la tablet, con dos diferencias a propósito:
 *
 *   - Acá la columna de valores es `minmax(0, 1fr)` y en el celular `1fr`. Son
 *     distintas: `1fr` equivale a `minmax(auto, 1fr)` y un dato sin punto por
 *     donde cortar —el email largo del comentario de más arriba— puede estirar
 *     la columna; `minmax(0, 1fr)` no la deja. Esta versión es la correcta. No
 *     se retro-portó al celular en esta corrección porque ese diseño ya está
 *     validado y el pedido era no tocarlo; queda anotado como lo que conviene
 *     unificar la próxima vez que se trabaje ese tramo.
 *   - El celular pasa también `td` y `tbody th` a bloque antes de la grilla;
 *     acá no hace falta y el resultado es el mismo.
 *
 * Una cosa que el modo tarjeta sí cuesta, en los dos tramos: pasar `table`,
 * `tbody`, `tr` y `td` a `block`/`grid` saca los roles de tabla, fila y celda
 * del árbol de accesibilidad en WebKit y Blink. El marcado sigue siendo una
 * `<table>` con sus encabezados, pero el lector de pantalla ya no la anuncia
 * como tabla: lee las etiquetas de `data-label` como texto. Es una limitación
 * que el tramo de celular ya tenía y que esta corrección extiende a la tablet;
 * se cierra con `role="table"`/`role="row"`/`role="cell"` en el marcado de las
 * dos vistas, que es trabajo aparte y no entra en una corrección urgente.
 */
@media (min-width: 768px) and (max-width: 1279.98px) {
    /* El recuadro común desaparece: el borde lo lleva cada tarjeta. */
    .data-table__wrap--cards {
        background-color: transparent;
        border: 0;
        border-radius: 0;
        overflow-x: visible;
    }

    .data-table--cards thead {
        display: none;
    }

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

    .data-table--cards 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--cards tbody tr:last-child {
        margin-bottom: 0;
    }

    /* En pantalla táctil el resalte queda pegado en la última fila tocada. */
    .data-table--cards tbody tr:hover {
        background-color: var(--n-000);
    }

    .data-table--cards td,
    .data-table--cards tbody th {
        display: grid;
        grid-template-columns: 40% minmax(0, 1fr);
        gap: var(--sp-3);
        border-bottom: 0;
        padding: var(--sp-2) var(--sp-4);
        text-align: left;
        overflow-wrap: anywhere;
    }

    .data-table--cards td > *,
    .data-table--cards tbody th > * {
        grid-column: 2;
    }

    /*
     * Las tres celdas que no son un dato más: el número encabeza la tarjeta, el
     * estado va pegado a él y el botón de abrir va al pie, a todo el ancho. Mismo
     * trato que en el celular, y por el mismo motivo: son el título, la etiqueta de
     * situación y la acción de la tarjeta, no tres datos en una lista.
     */
    .data-table--cards td.data-table__cell--title,
    .data-table--cards td.data-table__cell--badge,
    .data-table--cards td.data-table__cell--actions {
        display: block;
    }

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

    .data-table--cards 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--cards td.data-table__cell--badge {
        order: -1;
        padding-top: var(--sp-2);
        padding-bottom: var(--sp-2);
    }

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

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

    .data-table--cards td.data-table__cell--actions .row-actions {
        flex-direction: column;
        align-items: stretch;
    }

    /* «No hay resultados» es un mensaje, no un dato: ocupa la tarjeta entera. */
    .data-table--cards td.data-table__empty {
        display: block;
        text-align: center;
    }

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

    .data-table--cards td::before,
    .data-table--cards 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;
    }
}

/*
 * iPad horizontal: dos tarjetas por renglón.
 *
 * Con 742 px de ancho útil una sola columna de tarjetas deja la mitad de la
 * pantalla vacía y obliga a recorrer siete presupuestos a fuerza de scroll
 * vertical. Dos columnas entran con holgura (~360 px cada una, por encima de
 * los 320 px que necesita el par etiqueta/valor) y dejan la lista de un
 * vistazo, que es para lo que el vendedor abre el listado.
 *
 * `align-items: start` para que una tarjeta con dos acciones no estire a su
 * vecina, y `grid-column: 1 / -1` en el renglón de «no hay resultados» para que
 * el mensaje no quede en media pantalla con un hueco al lado.
 */
@media (min-width: 1024px) and (max-width: 1279.98px) {
    .data-table--cards tbody {
        display: grid;
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: var(--sp-3);
        align-items: start;
    }

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

    .data-table--cards tbody tr:has(.data-table__empty) {
        grid-column: 1 / -1;
    }
}

/*
 * Escritorio (desde 1280 px): sigue siendo una tabla.
 *
 * El modo tarjeta de arriba termina en 1279.98 px, así que acá no hace falta
 * deshacer nada: la tabla vuelve sola a las reglas base y la pantalla queda
 * igual que antes de esta corrección —comprobado comparando las capturas de
 * 1440 px byte a byte—.
 *
 * Las dos líneas que siguen no cambian nada hoy, y están elegidas justamente
 * así: repiten el valor por omisión del navegador, así que no pueden romper
 * nada. El bloque existe porque la segunda prueba de `BreakpointScaleTest`
 * exige que todo `max-width` tenga su `min-width` dos centésimas más arriba en
 * alguna hoja, y por lo tanto el literal `(min-width: 1280px)` es obligatorio y
 * el bloque no se puede borrar.
 *
 * Lo que este bloque NO hace, para que nadie se confíe: si alguien subiera el
 * `max-width` del modo tarjeta por encima de 1280 px, estas dos declaraciones
 * devolverían la tabla y su encabezado pero las otras cincuenta y dos seguirían
 * aplicándose, y el resultado sería un híbrido roto —encabezado de columnas
 * sobre filas que ya no son filas— peor que el modo tarjeta entero. El borde se
 * mueve en un solo lugar: el `max-width` de arriba.
 */
@media (min-width: 1280px) {
    .data-table--cards {
        display: table;
    }

    .data-table--cards thead {
        display: table-header-group;
    }
}

/* ══ 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));
}

/*
 * Los filtros plegados del catálogo del vendedor (Etapa 4).
 *
 * El `details` es una celda más de la grilla, y una celda mide 220 px: adentro
 * no entraban cuatro desplegables. Ocupa todo el ancho y arma su propia grilla
 * en el panel, con el mismo mínimo de 220 px, así los filtros se acomodan solos
 * —uno por fila en el celular, cuatro en escritorio— sin reglas por pantalla.
 */
.filter-bar__more {
    grid-column: 1 / -1;
}

.filter-bar__more .disclosure__panel {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
    column-gap: var(--sp-5);
}

/*
 * En la barra con filtros plegados no se reserva el alto del texto de ayuda.
 *
 * Ese hueco de 32 px existe para que las nueve celdas de la barra del panel
 * queden a la misma altura, y ahí hace falta. Acá hay una sola columna: el
 * resultado eran 32 px en blanco entre el buscador y «Filtros» —medidos a
 * 390 px— que se leen como un error de dibujo, no como aire.
 */
.filter-bar:has(.filter-bar__more) .field:not(:has(.field__hint))::after {
    content: none;
}

/*
 * Y tampoco se compensa el alto de la etiqueta en el botón.
 *
 * Esos 22 px existen para que «Buscar» quede apoyado en la misma línea que los
 * desplegables de su fila. Acá nunca comparte fila: el bloque plegado ocupa el
 * ancho entero de la grilla y empuja el botón a una fila propia, así que la
 * compensación es hueco y nada más —46 px entre «Filtros» y «Buscar»—.
 */
.filter-bar:has(.filter-bar__more) .filter-bar__actions {
    margin-top: 0;
}

/* ── 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;
}

/* Desde el 28/10 acá puede haber más de un botón: el chat de WhatsApp y la
 * salida explícita a WhatsApp Web, que el operador pidió separar en escritorio.
 * Sin contenedor flexible quedaban pegados —el único aire era el espacio en
 * blanco del HTML, que no está en la escala— y, al ser `flex: none`, el bloque
 * exigía el ancho de los dos juntos aunque la tarjeta no lo tuviera. */
.customer-card__action {
    display: flex;
    flex: none;
    flex-wrap: wrap;
    gap: var(--sp-2);
}

/* 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;
}

/*
 * `max-width: 100%` es lo que evita que el bloque de acciones empuje la página
 * a lo ancho, y no es cosmético.
 *
 * Con `flex: none` el bloque se dimensiona por su contenido, y el contenido es
 * un botón cuyo rótulo puede ser largo: «Crear revisión con precios actuales»
 * pide 328 px en un renglón. Dentro de una tarjeta de 294 px de ancho útil
 * —un teléfono de 360 px— el bloque se salía 34 px y la pantalla del
 * presupuesto quedaba con desplazamiento lateral (medido en Chromium a 360 px
 * antes de este arreglo: la página pedía 361 px de ancho).
 *
 * Con el tope en 100 % el bloque nunca supera a su formulario y el que cede es
 * el botón, que ya trae su propio `max-width: 100%` y sabe partir el rótulo en
 * dos renglones (`white-space: normal` en `02-botones.css`): esos dos topes en
 * cadena son lo que hace entrar la fila.
 *
 * No hace falta `min-width: 0`: el tamaño mínimo automático del ítem flexible
 * queda acotado por el `max-width` declarado, y el mínimo del contenido es la
 * palabra más larga del rótulo, muy por debajo del tope. Se midió en Chromium
 * a 360 px sin esa línea y la pantalla del presupuesto pide 360 px, no 361.
 */
.form-inline__actions {
    display: flex;
    gap: var(--sp-2);
    flex: none;
    max-width: 100%;
    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;
}

/*
 * En móvil la barra no puede quedarse con media pantalla.
 *
 * Verificado en 390 × 844: apilada, la barra mide 450 px —los importes, el
 * campo de comentario con su ayuda, el botón de registrar y el de generar el
 * pedido, cada uno con su aclaración—. Pegada al pie y sumada a la barra
 * superior y a la navegación inferior, al vendedor le quedaban 265 px de lista:
 * la casilla del ítem que acababa de centrar en pantalla caía **debajo** de la
 * barra y el toque se lo comía el párrafo de arriba. Una barra que tapa
 * justamente lo que hay que tocar es peor que una barra que no se ve.
 *
 * La solución no es sacarle lo pegajoso —los dos importes tienen que seguir a la
 * vista mientras se recorren veinte ítems con el cliente (Lineamiento §8)— sino
 * ponerle un techo y dejar que lo que no entra se desplace adentro, con los
 * importes anclados arriba de ese desplazamiento. Así los importes nunca se van,
 * nada de la barra queda inalcanzable y la lista recupera unos 475 px.
 *
 * El techo va en `vh` y no en píxeles a propósito: lo que importa no es cuánto
 * mide la barra sino qué proporción de la pantalla se queda, y eso se dice en
 * proporción de la pantalla.
 */
@media (max-width: 767.98px) {
    .approval-bar {
        bottom: calc(var(--bottomnav-h) + env(safe-area-inset-bottom, 0px));
        flex-direction: column;
        align-items: stretch;
        /* Imprescindible junto al `max-height` de abajo, y no es un detalle de
         * estilo: la regla base trae `flex-wrap: wrap` para que en escritorio
         * los bloques pasen de renglón. Una columna **con altura acotada** y
         * `wrap` no envuelve de renglón sino de **columna**, así que los
         * botones se iban a una segunda columna fuera del recuadro y solo
         * aparecían desplazando la barra en horizontal. Verificado en
         * 390 × 844: con `wrap`, `scrollWidth` 628 sobre `clientWidth` 322. */
        flex-wrap: nowrap;
        max-height: 35vh;
        overflow-y: auto;
        /* Al llegar al final de la barra, el gesto no sigue arrastrando la
         * página de atrás: el vendedor quería leer la barra, no perderla. */
        overscroll-behavior: contain;
        padding-top: 0;
    }

    .approval-bar__figures {
        position: sticky;
        top: 0;
        z-index: 1;
        padding-top: var(--sp-4);
        padding-bottom: var(--sp-2);
        background: var(--n-000);
    }
}

/* ── Aprobación del producto (28/10) ─────────────────────────────────────────
 *
 * La sección que envuelve al panel de decisión. Nace de una observación del
 * operador en la primera validación web: la acción «Decidir por cantidad»
 * estaba «escondida en una franja gris» y no la encontraba. El arreglo no fue
 * pintarla de amarillo —el amarillo es la acción principal de la pantalla y acá
 * hay una sola, aprobar no compite con ella— sino darle a la sección un nombre
 * visible y poner las cantidades afuera del plegado, donde se leen sin abrir
 * nada.
 *
 * La franja amarilla superior de 3 px es la de la tarjeta destacada del
 * Lineamiento §7: marca que esta parte del ítem es la que tiene una decisión
 * pendiente, sin usar el amarillo como fondo ni como texto (§3.1).
 */

.item-approval {
    margin-top: var(--sp-4);
    padding: var(--sp-4);
    border: 1px solid var(--brand-sand);
    border-top: var(--bw-3) solid var(--brand-yellow);
    border-radius: var(--r-md);
    background: var(--n-000);
}

.item-approval__title {
    margin: 0 0 var(--sp-3);
    font-size: var(--fs-h3);
    line-height: var(--lh-h3);
    font-weight: 600;
    color: var(--n-900);
}

/*
 * Las cuatro cantidades, en columnas y no en una frase.
 *
 * `auto-fit` con mínimo de 104 px: en escritorio entran las cinco en una fila y
 * en un teléfono de 360 px caen a dos por renglón sin que haya que declarar el
 * punto de quiebre. Los números van en la familia de datos con cifras
 * tabulares, que es la regla del Lineamiento §4 para todo lo que se compara
 * verticalmente.
 */
.item-approval__counts {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(104px, 1fr));
    gap: var(--sp-2);
    margin: 0;
}

.item-approval__count {
    padding: var(--sp-2) var(--sp-3);
    border: 1px solid var(--n-200);
    border-radius: var(--r-sm);
    background: var(--n-100);
}

.item-approval__label {
    margin: 0;
    font-size: var(--fs-overline);
    line-height: var(--lh-overline);
    font-weight: 700;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--n-500);
}

.item-approval__value {
    margin: var(--sp-1) 0 0;
    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);
}

/*
 * El color refuerza la lectura pero nunca la reemplaza: cada recuadro lleva su
 * palabra completa arriba (Lineamiento §10). Se tiñe el borde izquierdo y no el
 * fondo, para que los cinco números se sigan comparando como una fila.
 */
.item-approval__count--ok {
    border-left: var(--bw-3) solid var(--ok);
}

.item-approval__count--ordered {
    border-left: var(--bw-3) solid var(--info);
}

.item-approval__count--pending {
    border-left: var(--bw-3) solid var(--warn);
}

.item-approval__count--rejected {
    border-left: var(--bw-3) solid var(--err);
}

/* La aclaración de que lo aprobado y lo pedido no se suman. */
.item-approval__note {
    margin: var(--sp-3) 0 0;
    font-size: var(--fs-caption);
    line-height: var(--lh-caption);
    color: var(--n-500);
}

/* ── El panel de decisión por cantidad (subfase 13.2) ─────────────────────────
 *
 * Plegado por defecto y abierto solo cuando la línea ya tiene algo decidido en
 * parte. El `<details>` nativo hace el plegado sin una línea de JavaScript y
 * ya trae el teclado y el anuncio de estado resueltos; reemplazarlo por un
 * botón con `aria-expanded` sería escribir a mano lo que el navegador hace
 * mejor.
 *
 * El resumen dejó de ser una franja gris y pasó a tener forma de botón
 * secundario (fondo blanco y borde de 1 px, como `button--secondary`): es la
 * acción «Gestionar aprobación» y tiene que leerse como una acción.
 */

.item-decision {
    margin-top: var(--sp-4);
    border: 1px solid var(--n-300);
    border-radius: var(--r-md);
    background: var(--n-000);
}

.item-decision__summary {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-1) var(--sp-3);
    align-items: baseline;
    /* 44 px de área táctil real contando el padding (Lineamiento §10). */
    padding: var(--sp-3) var(--sp-4);
    cursor: pointer;
    border-radius: var(--r-md);
}

.item-decision__summary:focus-visible {
    outline: var(--bw-3) solid var(--focus);
    outline-offset: 2px;
}

.item-decision__summary:hover {
    background: var(--n-100);
}

.item-decision__title {
    font-weight: 600;
    font-size: var(--fs-body);
    line-height: var(--lh-body);
    letter-spacing: 0.01em;
    color: var(--n-900);
}

/* El recuento se lee aunque el panel esté cerrado: lo que se pliega es el
 * formulario, no el estado de la línea. */
.item-decision__tally {
    font-family: var(--font-data);
    font-size: var(--fs-label);
    line-height: var(--lh-label);
    font-variant-numeric: tabular-nums;
    color: var(--n-500);
}

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

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

/*
 * Qué hace cada botón, dicho antes de los botones.
 *
 * `width: 100%` porque el formulario es una caja flexible y, sin eso, el
 * párrafo se acomodaría al lado de los campos en una columna de 40 caracteres.
 * Lo pide el operador —«explicar el efecto antes de registrar»— y sirve
 * doblemente: ninguna de estas decisiones se puede deshacer borrándola.
 */
.item-decision__effect {
    width: 100%;
    margin: 0;
    font-size: var(--fs-caption);
    line-height: var(--lh-caption);
    color: var(--n-500);
}

.item-decision__effect strong {
    color: var(--n-700);
}

/* El aviso de «aprobada en parte». Ámbar y no rojo: no es un error del
 * vendedor, es un estado legítimo de la venta que tiene una consecuencia que
 * hay que saber. El icono acompaña al texto porque el color nunca comunica
 * solo (Lineamiento §10). */
.item-decision__warning {
    display: flex;
    gap: var(--sp-2);
    align-items: flex-start;
    width: 100%;
    margin: 0 var(--sp-4) var(--sp-3);
    padding: var(--sp-3);
    border-left: var(--bw-3) solid var(--warn);
    border-radius: var(--r-sm);
    background: var(--warn-bg);
    color: var(--warn);
    font-size: var(--fs-label);
    line-height: var(--lh-label);
}

.item-decision__form .item-decision__warning {
    margin-inline: 0;
}

@media (max-width: 767.98px) {
    /* En el teléfono los campos y los botones van uno por renglón: tres
     * botones en fila a 360 px dejan cada uno por debajo del área táctil
     * mínima, y «Retirar aprobación» se corta a la mitad. */
    .item-decision__form,
    .item-decision__actions {
        flex-direction: column;
        align-items: stretch;
    }
}

/* ── Pedidos e historial de la venta (corrección 6 del 28/10) ──────────────
 *
 * El operador probó un presupuesto con dos pedidos y la pantalla mostraba uno
 * solo. La sección que lo corrige tiene que resolver tres lecturas distintas y
 * cada una pide una forma:
 *
 *  · los cuatro totales de plata, que se comparan de un vistazo → una grilla
 *    de tarjetas, con la franja de color a la izquierda indicando de qué
 *    número se trata;
 *  · los pedidos, que son pocos y cada uno tiene su propio juego de acciones →
 *    una lista de tarjetas, y no una tabla: a 360 px una tabla de cinco
 *    columnas obliga a desplazar en horizontal;
 *  · el historial, que es un relato en orden → una lista ordenada con una
 *    línea de tiempo a la izquierda.
 *
 * El color nunca es el único portador: cada tarjeta dice con todas las letras
 * qué número muestra y cada entrada del historial empieza con el verbo escrito
 * («Aprobación de 4 unidades»), como exige el Lineamiento §10. */
.sale-totals {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
    gap: var(--sp-3);
    margin: 0 0 var(--sp-3);
}

.sale-totals__item {
    padding: var(--sp-3);
    border: 1px solid var(--n-200);
    border-left: var(--bw-3) solid var(--n-300);
    border-radius: var(--r-sm);
    background: var(--n-100);
}

.sale-totals__item--ok {
    border-left-color: var(--ok);
}

.sale-totals__item--ordered {
    border-left-color: var(--info);
}

.sale-totals__item--pending {
    border-left-color: var(--warn);
}

.sale-totals__label {
    margin: 0;
    color: var(--n-500);
    font-size: var(--fs-overline);
    font-weight: 700;
    line-height: var(--lh-overline);
    letter-spacing: 0.1em;
    text-transform: uppercase;
}

.sale-totals__value {
    /* El tamaño y la familia los pone `.amount`, que es la clase del importe en
     * todo el sistema (Lineamiento §4). Acá solo se corrige el aire. */
    margin: var(--sp-1) 0 0;
}

/* Los subtítulos de la sección. Separan tres bloques dentro de una misma
 * tarjeta, así que necesitan aire arriba y no una línea divisoria más: la
 * tarjeta ya tiene borde. */
.sale-history__heading {
    margin: var(--sp-5) 0 var(--sp-3);
    color: var(--n-900);
    font-size: var(--fs-h3);
    font-weight: 600;
    line-height: var(--lh-h3);
}

.order-trail {
    display: grid;
    gap: var(--sp-3);
    margin: 0;
    padding: 0;
    list-style: none;
}

.order-trail__item {
    padding: var(--sp-4);
    border: 1px solid var(--n-200);
    border-radius: var(--r-md);
    background: var(--n-000);
}

.order-trail__head {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-3);
    align-items: center;
}

/* El número del pedido es el nombre de la tarjeta y a la vez el camino al
 * pedido: se lee con la tipografía de datos porque es un código, y se subraya
 * solo al pasar por encima para no competir con el chip del estado. */
.order-trail__number {
    color: var(--n-900);
    font-family: var(--font-data);
    font-size: var(--fs-h3);
    font-weight: 600;
    line-height: var(--lh-h3);
    text-decoration: none;
}

.order-trail__number:hover,
.order-trail__number:focus-visible {
    text-decoration: underline;
}

.order-trail__facts {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-2) var(--sp-5);
    margin: var(--sp-3) 0 0;
}

.order-trail__label {
    margin: 0;
    color: var(--n-500);
    font-size: var(--fs-overline);
    font-weight: 700;
    line-height: var(--lh-overline);
    letter-spacing: 0.1em;
    text-transform: uppercase;
}

.order-trail__value {
    margin: var(--sp-1) 0 0;
    color: var(--n-700);
    font-size: var(--fs-label);
    line-height: var(--lh-label);
}

.order-trail__actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-2);
    margin-top: var(--sp-3);
}

/* El historial, como línea de tiempo. La barra vertical sale del borde
 * izquierdo de cada entrada: con `border-left` en los renglones y sin hueco
 * entre ellos, la línea se dibuja sola y sigue estando si el navegador no
 * soporta algo más moderno. */
.sale-history {
    margin: 0;
    padding: 0 0 0 var(--sp-4);
    border-left: 1px solid var(--n-200);
    list-style: none;
    counter-reset: none;
}

.sale-history__entry {
    position: relative;
    padding: 0 0 var(--sp-4) var(--sp-4);
}

.sale-history__entry:last-child {
    padding-bottom: 0;
}

/* El punto de la línea de tiempo. Es decorativo: el verbo de la decisión está
 * escrito al lado. */
.sale-history__entry::before {
    position: absolute;
    top: 6px;
    left: calc(-1 * var(--sp-4) - 5px);
    width: 10px;
    height: 10px;
    border: var(--bw-2) solid var(--n-000);
    border-radius: var(--r-pill);
    background: var(--n-300);
    content: '';
}

.sale-history__entry--aprobacion::before {
    background: var(--ok);
}

.sale-history__entry--rechazo::before {
    background: var(--err);
}

.sale-history__entry--retiro::before {
    background: var(--warn);
}

.sale-history__entry--reapertura::before {
    background: var(--info);
}

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

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

.sale-history__who,
.sale-history__reason,
.sale-history__note {
    margin: var(--sp-1) 0 0;
    color: var(--n-500);
    font-size: var(--fs-caption);
    line-height: var(--lh-caption);
}

.sale-history__reason {
    color: var(--n-700);
}

@media (max-width: 767.98px) {
    /* En el teléfono las acciones del pedido van una por renglón: dos botones
     * en fila a 360 px dejan cada uno por debajo del área táctil mínima. */
    .order-trail__actions .button {
        width: 100%;
    }
}

/* Ronda web 2: acciones del carrito, alineadas por su base. */
.cart-item__actions {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: var(--sp-3);
}
.cart-item__actions .form-inline { flex: 0 1 auto; flex-wrap: nowrap; align-items: flex-end; margin: 0; }
.cart-item__actions .form-inline__actions { margin-top: 0; }
.cart-item__actions .field { flex: 0 1 8rem; width: 8rem; min-width: 0; }
.cart-item__actions > form { margin: 0; }
.sale-history__details > summary { cursor: pointer; padding-block: var(--sp-3); }
.approval-bar__actions > form { display: flex; flex-wrap: wrap; align-items: flex-end; gap: var(--sp-3); }
.approval-bar__actions > form > .field { flex: 1 1 18rem; }
#agregar-producto,
[id^="cart-item-"] { scroll-margin-top: 5rem; }
@media (max-width: 767.98px) {
    .cart-item__actions { align-items: stretch; }
    .cart-item__actions .form-inline { width: 100%; }
}

/* ══ 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;
}

/*
 * La fila se parte en dos renglones antes que estirar la página.
 *
 * La fila casi no tiene por dónde encogerse: el chip lleva `white-space: nowrap`
 * porque un estado partido en dos no se lee, el importe no se abrevia nunca
 * (Lineamiento §4: no se muestra «$ 1,2 M» en ninguna pantalla operativa) y lo
 * único elástico —la barra— cede sólo hasta su piso de 40 px. Pasado ese piso,
 * con un total de nueve cifras —«$ 150.394.141,02» en la
 * base de demostración— la fila reclama 315 px, y en un teléfono de 360 px la
 * tarjeta sólo tiene 294 px de ancho útil: el ancho mínimo del contenido le
 * ganaba a la columna de la grilla, la tarjeta se iba a 348 px y la pantalla de
 * Inicio quedaba con 4 px de desplazamiento lateral (medido en Chromium a
 * 360 px antes de este arreglo).
 *
 * Con `flex-wrap: wrap` el importe baja a un segundo renglón y sigue alineado a
 * la derecha por su `margin-left: auto`. En cualquier ancho donde la fila entra
 * —de 390 px para arriba, y todo el escritorio— la regla no cambia nada.
 *
 * La misma regla la usa la lista de pedidos del período, que no lleva barra:
 * ahí hay más lugar todavía y el ajuste tampoco se nota.
 */
.pipeline__row {
    display: flex;
    flex-wrap: wrap;
    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));
}

/* ── Motivos y avisos del buscador ──────────────────────────────────────── */
/*
 * La lista desplegable del buscador se mudó a `25-sugerencias.css` al pasar a
 * ser un componente de tres pantallas. Acá quedó lo que sí es del catálogo: el
 * aviso de qué entendió el buscador y el chip con el motivo de cada
 * coincidencia.
 */

/*
 * 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);
}

/* ── Catálogo del vendedor: grilla de tarjetas (Etapa 4) ────────────────── */
/*
 * El vendedor y el administrador del catálogo miran la misma pantalla con dos
 * trabajos distintos, y por eso no la ven igual.
 *
 * El administrador necesita comparar renglones: estado, disponibilidad, precio
 * base, cuántas fotos faltan. Eso es una tabla y sigue siendo una tabla.
 *
 * El vendedor está de pie, con el celular en la mano y el cliente al lado, y lo
 * que necesita es la foto grande y el nombre: le muestra el producto a alguien.
 * Una tabla de ocho columnas apilada en 360 px de ancho no es eso. La grilla de
 * tarjetas con la foto protagonista sí.
 *
 * Mobile-first de verdad: una columna, y recién se agregan columnas cuando hay
 * ancho para que la foto no se vuelva una estampilla.
 */
.catalog-grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--sp-4);
    margin: 0;
    padding: 0;
    list-style: none;
}

@media (min-width: 600px) {
    .catalog-grid {
        grid-template-columns: repeat(2, 1fr);
    }
}

/*
 * Tres columnas recién en escritorio, y no cuatro: la escala de puntos de corte
 * del proyecto tiene tres tramos —teléfono, armazón compacto, escritorio— y
 * agregar un cuarto ancho para apretar una columna más dejaría a la próxima
 * regla sin saber cuál le toca (Etapa 1, docs/fase-12/01). Con el área de
 * contenido de escritorio, tres tarjetas dan unos 300 px cada una: la foto se
 * sigue viendo como foto.
 */
@media (min-width: 1024px) {
    .catalog-grid {
        grid-template-columns: repeat(3, 1fr);
    }
}

.catalog-card {
    display: flex;
    flex-direction: column;
    min-width: 0;
    border: 1px solid var(--n-200);
    border-radius: var(--r-lg);
    background-color: var(--n-000);
    overflow: hidden;
}

/*
 * La foto manda. 4:3 y `cover` acá sí: en una grilla lo que importa es que
 * todas las tarjetas midan lo mismo para poder barrerlas con la vista
 * (Lineamiento §9). El recorte sin pérdida se mira en el configurador, donde la
 * foto se ve entera y grande.
 */
.catalog-card__figure {
    display: block;
    width: 100%;
    aspect-ratio: 4 / 3;
    object-fit: cover;
    object-position: center;
    background-color: var(--n-100);
    border-bottom: 1px solid var(--n-200);
}

/*
 * El hueco de la foto que falta se dibuja con la misma proporción: sin esto la
 * grilla queda despareja y parece que la pantalla cargó a medias.
 */
.catalog-card__figure--empty {
    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--sp-3);
    color: var(--n-500);
    font-size: var(--fs-label);
    text-align: center;
}

.catalog-card__body {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    gap: var(--sp-2);
    padding: var(--sp-3) var(--sp-4);
    min-width: 0;
}

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

.catalog-card__code {
    margin: 0;
}

/* El precio, con el mismo peso que tiene en el desglose: es un dato, no un título. */
.catalog-card__price {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--sp-1);
    margin: 0;
}

/*
 * Las acciones quedan pegadas al pie de la tarjeta —`margin-top: auto`— así
 * todos los botones de la fila se alinean aunque un nombre ocupe dos renglones.
 */
.catalog-card__actions {
    display: flex;
    margin-top: auto;
    padding-top: var(--sp-3);
}

.catalog-card__actions .button {
    width: 100%;
}

/* ══ 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);
    }
}

/* ══ 25-sugerencias.css ══ */

/* ── Buscador con sugerencias ───────────────────────────────────────────── */
/*
 * La lista desplegable que aparece mientras alguien escribe. La usan el listado
 * del catálogo y los dos buscadores del recorrido de venta —cliente y
 * producto—; el marcado lo arma `x-suggest-field` y el comportamiento, la
 * función `initSuggest()` de `ui.js`.
 *
 * Vivía dentro de `18-catalogo.css`, cuando era del buscador del catálogo y de
 * nadie más. Se mudó acá al convertirse en componente de tres pantallas: un
 * componente compartido escondido en la hoja de una pantalla es cómo empiezan
 * las dos copias que se van separando.
 *
 * ── Qué manda en el diseño: el celular con el teclado abierto ──────────────
 *
 * Es la situación real de uso: el vendedor de pie, el cliente al lado, el
 * teclado virtual ocupando la mitad de abajo de la pantalla. De ahí salen las
 * tres decisiones que importan acá: la lista no pasa de unos cinco renglones
 * visibles y el resto se desplaza adentro (no empuja la pantalla ni tapa el
 * campo), cada renglón mide 60 px de alto real —el Lineamiento §10 exige 44 px
 * de área táctil y acá entra una miniatura— y el nombre llega hasta dos líneas
 * mientras que lo secundario se recorta con puntos suspensivos, para que el
 * alto de la lista siga siendo predecible sin esconder lo que distingue a un
 * producto del de al lado.
 */
.field--search {
    position: relative;
}

/*
 * 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.
 */
.suggest {
    position: absolute;
    z-index: 30;
    inset-inline: 0;
    top: 100%;
    /* Unos cinco renglones. `dvh` y no `vh` porque con el teclado abierto `vh`
       sigue midiendo la pantalla entera: la lista se dibujaría por debajo del
       teclado y los últimos renglones no se podrían tocar. El `max-height` de
       arriba queda como respaldo para los navegadores sin `dvh`. */
    max-height: 60vh;
    max-height: min(52dvh, 320px);
    margin: var(--sp-1) 0 0;
    padding: var(--sp-1);
    overflow-y: auto;
    /* El desplazamiento de la lista no arrastra la pantalla de atrás. */
    overscroll-behavior: contain;
    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;
}

/*
 * El renglón. Son dos cosas distintas con la misma pinta, y tienen que verse
 * igual porque para quien lo usa son lo mismo: un enlace, cuando la sugerencia
 * lleva a algún lado, y el botón de un formulario cuando la sugerencia agrega
 * el producto al presupuesto —eso escribe en la base y por lo tanto va por
 * POST—.
 */
.suggest__row {
    display: flex;
    /* Si el nombre y la pista no entran juntos —un celular de 320 px, o un
       panel angosto al lado del menú lateral—, la pista baja a su propio
       renglón en vez de comerse el ancho del nombre. */
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-3);
    width: 100%;
    min-height: 60px;
    padding: var(--sp-2) var(--sp-3);
    border: 0;
    border-radius: var(--r-sm);
    background: none;
    color: var(--n-900);
    font: inherit;
    text-align: start;
    text-decoration: none;
    cursor: pointer;
}

.suggest__row:hover {
    background: var(--n-100);
}

.suggest__form {
    margin: 0;
}

/*
 * 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 > .suggest__row,
.suggest__item--active > .suggest__form > .suggest__row {
    background: var(--n-100);
    box-shadow: inset var(--bw-3) 0 0 var(--brand-yellow);
}

/*
 * La miniatura del producto, cuadrada. El Lineamiento §9 pide 4:3 en las
 * grillas de catálogo; acá se recorta a un cuadrado de 52 px con `cover`
 * porque en una lista de renglones el cuadrado deja el nombre más cerca del
 * borde y gana ancho de texto, que a 360 px es lo escaso.
 */
.suggest__thumb {
    flex: 0 0 auto;
    width: 52px;
    height: 52px;
    object-fit: cover;
    background: var(--n-100);
    border-radius: var(--r-sm);
}

/*
 * El redondel con las iniciales del cliente. No es una foto: es para que el ojo
 * tenga dónde apoyarse al recorrer seis renglones de texto, y para que el
 * renglón del cliente tenga el mismo alto que el del producto.
 */
.suggest__avatar {
    display: flex;
    flex: 0 0 auto;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    background: var(--brand-cream);
    border: 1px solid var(--brand-sand);
    border-radius: var(--r-pill);
    /* `--n-700` y no `--brand-brown`: el marrón sobre la crema da 4,31:1 y el
       Lineamiento §10 pide 4,5:1 para texto —13 px en negrita no cuenta como
       texto grande—. Este par ya está probado en `PaletteContrastTest`. */
    color: var(--n-700);
    font-size: var(--fs-label);
    font-weight: 700;
}

/*
 * El ancho de base son 7rem: es lo que necesita un nombre de producto para
 * decir algo en dos líneas. Si no entra junto con la pista, es la pista la que
 * baja —ver `flex-wrap` en el renglón—, nunca el nombre el que se achica hasta
 * volverse una columna de sílabas.
 */
.suggest__text {
    flex: 1 1 7rem;
    min-width: 0;
}

/*
 * El nombre, hasta en dos líneas.
 *
 * En una sola línea no alcanzaba y el recorte mentía: a 390 px, «Sillón Cúbico
 * 1 Cuerpo» y «Sillón Cúbico 3 Cuerpos» se dibujaban los dos como «Sillón
 * Cúbi…». Lo que distingue a un producto del siguiente está casi siempre al
 * final del nombre —la medida, la cantidad de cuerpos, el color—, que es
 * justamente lo que el recorte se come. Dos renglones indistinguibles no son
 * una lista incómoda: son el producto equivocado cargado en el presupuesto.
 *
 * Dos y no más: el alto de la lista tiene que seguir siendo previsible para que
 * entren cuatro o cinco sugerencias con el teclado abierto.
 */
.suggest__name {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    font-weight: 600;
    font-size: var(--fs-body-dense);
    line-height: var(--lh-body-dense);
    overflow: hidden;
    overflow-wrap: anywhere;
}

.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;
}

/*
 * Qué va a pasar al tocar el renglón: «Agregar», «Elegir opciones», «Elegir».
 * Está escrito y no insinuado con un ícono: un producto que entra de un toque y
 * otro que abre una pantalla de configuración no pueden verse iguales.
 */
/*
 * Se acota y se parte en dos renglones en lugar de empujar al nombre: en una
 * sola línea, «Elegir opciones» se llevaba un tercio del ancho de un celular de
 * 390 px y lo pagaba el único texto que importa, el del producto.
 */
.suggest__hint {
    /* El tope de 4rem es lo que la deja escrita y corta a la vez: «Elegir
       opciones» se parte en dos renglones cortos —se sigue leyendo entero— en
       vez de llevarse un tercio del ancho del celular. Sin el tope, en un panel
       angosto la pista baja de línea con su ancho natural y el renglón crece de
       72 px a más de 100: se ven dos sugerencias donde entraban cuatro. */
    flex: 0 0 auto;
    max-width: 4rem;
    margin-inline-start: auto;
    font-size: var(--fs-overline);
    font-weight: 700;
    letter-spacing: .06em;
    text-transform: uppercase;
    text-align: end;
    color: var(--n-500);
}

.suggest__row:hover .suggest__hint,
.suggest__item--active .suggest__hint {
    color: var(--n-900);
}

/*
 * Las dos salidas del final de la lista: «Ver todos los que coinciden» y
 * «Cargar X como cliente nuevo». Separadas con una línea porque no son
 * sugerencias: son lo que se hace cuando ninguna sirvió.
 */
.suggest__item--aside {
    border-top: 1px solid var(--n-200);
}

.suggest__item--aside > .suggest__row {
    min-height: 44px;
}

.suggest__aside {
    font-size: var(--fs-label);
    font-weight: 600;
    color: var(--info);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

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

/* ══ 26-asistente.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`.
 */

/* ── Asistente de voz ──────────────────────────────────────────────────────
 *
 * La pantalla del vendedor que habla con el asistente, y la tarjeta de uso de
 * su configuración. Es la única pantalla del sistema cuyo control principal no
 * es un formulario: es un botón y un micrófono.
 *
 * ── Qué manda en el diseño: el celular en la mano, de pie, hablando ───────
 *
 * El vendedor no está mirando la pantalla mientras usa esto: está mirando al
 * cliente. La pantalla es el respaldo —qué entendió el asistente, qué precio
 * calculó el Gestor— y el control es uno solo, grande, que se encuentra sin
 * buscarlo. De ahí las tres decisiones que gobiernan esta hoja:
 *
 *   1. El botón es de 72 px de lado como mínimo y crece a todo el ancho de la
 *      tarjeta hasta un tope. El Lineamiento §10 pide 44 px de área táctil;
 *      acá sobra a propósito, porque el toque se da sin apuntar.
 *   2. Ningún estado se comunica solo con color (Lineamiento §10). Cada
 *      indicador lleva la palabra escrita y un ícono: «Micrófono concedido»,
 *      «Conectado», «Escuchando». El color es el tercer refuerzo, no el
 *      primero.
 *   3. Nada de lo que escribe el asistente se muestra sin el rótulo «GENERADO
 *      POR IA» (Lineamiento §8). La transcripción del asistente es texto de un
 *      modelo, no un dato del sistema, y tiene que leerse distinto del importe
 *      que calculó el Gestor Comercial.
 *
 * Los importes de las tarjetas de apoyo los formatea el servidor y acá se
 * dibujan tal como llegan: reusan `.price-breakdown` de `15-presupuesto.css`,
 * que es el desglose de precio del sistema. Un segundo desglose con otra pinta
 * sería otra fuente de verdad.
 */

.asistente {
    display: flex;
    flex-direction: column;
    gap: var(--sp-5);
}

/* ── El control de la conversación ───────────────────────────────────────── */

/*
 * La columna centrada del botón y sus indicadores. Centrada y no alineada a la
 * izquierda como el resto del sistema: es el único control de la tarjeta y no
 * compite con nada, así que el centro es donde el pulgar lo encuentra sin
 * mirar.
 */
.asistente__conversacion {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--sp-4);
    padding: var(--sp-4) 0;
}

/*
 * El botón grande.
 *
 * Hereda de `.button` el foco, la tipografía y el radio; lo que agrega es
 * tamaño y una disposición en columna —ícono arriba, texto abajo— que a 360 px
 * deja el rótulo entero en dos renglones en vez de recortarlo.
 *
 * El tope de 22rem existe porque en escritorio un botón de 900 px de ancho no
 * se lee como un botón: se lee como una franja. Y el mínimo de 72 px de lado
 * es el que fija la especificación de esta pantalla; `min-height` y no `height`
 * para que el texto pueda crecer sin desbordar.
 */
.asistente__boton {
    min-width: 72px;
    min-height: 72px;
    width: 100%;
    max-width: 22rem;
    flex-direction: column;
    gap: var(--sp-2);
    padding: var(--sp-4) var(--sp-5);
    border-radius: var(--r-lg);
    font-size: var(--fs-h3);
    line-height: var(--lh-h3);
}

/*
 * El ícono del botón, un escalón más grande que el `--lg` del sistema. A 24 px
 * dentro de un botón de 72 queda como una viñeta; a 32 se lee como el símbolo
 * de la acción, que es lo que es.
 */
.asistente__boton .icon {
    width: 32px;
    height: 32px;
}

/*
 * El latido mientras la conversación está abierta.
 *
 * Es la única señal animada de la pantalla y su trabajo es decir «esto está
 * grabando» a quien tiene el teléfono apoyado en el mostrador y no lo está
 * mirando de frente. No reemplaza al texto: el botón ya dice «Terminar la
 * conversación» y el indicador de turno ya dice qué está pasando.
 */
@keyframes asistente-latido {
    from {
        box-shadow: 0 0 0 0 var(--err-bg);
    }

    to {
        box-shadow: 0 0 0 14px transparent;
    }
}

.asistente__boton--activo {
    animation: asistente-latido 1.6s ease-out infinite;
}

/*
 * Quien pidió menos movimiento no recibe ninguno (Lineamiento §10). El estado
 * sigue escrito en el botón y en los indicadores, así que apagar la animación
 * no esconde nada.
 */
@media (prefers-reduced-motion: reduce) {
    .asistente__boton--activo {
        animation: none;
    }
}

/*
 * La fila de indicadores: micrófono, conexión y turno.
 *
 * Envuelve y se centra: a 360 px entran dos y el tercero baja; a 768 entran los
 * tres. Cada uno es un `.chip` del sistema con la palabra completa adentro, así
 * que el tono —`--ok`, `--warn`, `--err`, `--neutral`— lo pone el JavaScript
 * cambiando el modificador y no hay un vocabulario de color nuevo acá.
 */
.asistente__indicadores {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: var(--sp-2);
    margin: 0;
    padding: 0;
    list-style: none;
}

/*
 * Único lugar del sistema donde un chip puede partirse en dos renglones, y es
 * deliberado.
 *
 * `.chip` fija 32 px de alto y `nowrap` porque una etiqueta corta que se parte
 * se lee como un error de dibujo. Acá el texto no es una etiqueta corta: es un
 * estado escrito con palabras completas —«El asistente está hablando»— porque
 * el Lineamiento §10 no admite comunicarlo solo con color. Con `nowrap`, a
 * 360 px el chip más largo se pasa del ancho de la tarjeta y aparece el
 * desplazamiento horizontal que esta pantalla no puede tener; con zoom al
 * 200 % el ancho útil es la mitad y no entra ninguno.
 *
 * Así que el que cede es el alto: `min-height` conserva los 32 px del chip
 * cuando el texto entra en un renglón —que es el caso normal— y lo deja crecer
 * cuando no. Abreviar el estado no es opción: «Hablando» no dice quién.
 */
.asistente__indicador {
    height: auto;
    min-height: 32px;
    padding: var(--sp-1) var(--sp-3);
    white-space: normal;
    text-align: start;
}

/* ── La transcripción de apoyo ───────────────────────────────────────────── */

/*
 * No es un chat: no hay campo de texto ni botón de enviar. Es el reflejo
 * escrito de lo que se está diciendo en voz alta, para que el vendedor pueda
 * confirmar de un vistazo que el asistente entendió «tres cuerpos» y no «tres
 * cuerdas».
 *
 * Seis turnos y el resto se va: una conversación de veinte turnos convierte la
 * pantalla en un rollo donde las tarjetas de apoyo —lo que sí hay que leer—
 * quedan a dos pantallazos de distancia.
 */
.asistente__turnos {
    display: flex;
    flex-direction: column;
    gap: var(--sp-3);
    margin: 0;
    padding: 0;
    list-style: none;
}

.asistente__turno {
    padding: var(--sp-3);
    border-radius: var(--r-md);
    background-color: var(--n-100);
}

/*
 * El turno del asistente se despega del fondo gris del vendedor con el tono
 * informativo, el mismo que usa `.notice--ai`: lo que dice el asistente es una
 * propuesta, y la pantalla lo tiene que decir antes de que se lea.
 */
.asistente__turno--asistente {
    background-color: var(--info-bg);
}

.asistente__turno-quien {
    display: block;
    margin-bottom: var(--sp-1);
    font-size: var(--fs-overline);
    line-height: var(--lh-overline);
    font-weight: 700;
    letter-spacing: .06em;
    text-transform: uppercase;
    color: var(--n-500);
}

/*
 * El rótulo obligatorio del Lineamiento §8. Conserva las mayúsculas y el
 * interletrado ancho por el mismo motivo que `.notice__ai-tag`: es una marca de
 * procedencia fija del sistema, no un título que alguien escribió.
 */
.asistente__turno-marca {
    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);
}

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

.asistente__turnos-vacio {
    margin: 0;
    font-size: var(--fs-label);
    line-height: var(--lh-label);
    color: var(--n-500);
}

/* ── Las tarjetas de apoyo ───────────────────────────────────────────────── */

/*
 * Lo que el asistente encontró o calculó, dibujado por el JavaScript a medida
 * que el servidor contesta cada herramienta. Borde y no sombra, como todo lo
 * demás del sistema (Lineamiento §5).
 *
 * Son datos del Gestor Comercial y no texto de un modelo: por eso NO llevan el
 * rótulo de IA. El asistente pidió el dato; el número lo calculó el sistema, y
 * poner «GENERADO POR IA» arriba de un precio real diría exactamente lo
 * contrario de lo que pasó.
 */
.asistente__tarjetas {
    display: flex;
    flex-direction: column;
    gap: var(--sp-4);
}

.asistente__tarjetas:empty {
    display: none;
}

.asistente__tarjeta {
    padding: var(--sp-4);
    border: 1px solid var(--n-200);
    border-radius: var(--r-lg);
    background-color: var(--n-000);
}

.asistente__tarjeta-titulo {
    margin: 0 0 var(--sp-3);
    font-size: var(--fs-h3);
    line-height: var(--lh-h3);
    font-weight: 600;
    color: var(--n-900);
}

.asistente__lista {
    display: flex;
    flex-direction: column;
    gap: var(--sp-2);
    margin: 0;
    padding: 0;
    list-style: none;
}

/*
 * El pie de confirmación de una propuesta.
 *
 * Separado por una línea y con su propia leyenda: lo que está arriba es lo que
 * se va a guardar y abajo está el gesto que lo autoriza. La línea no es
 * decoración —marca dónde termina la información y empieza la decisión—, y la
 * leyenda dice en palabras que sin ese toque no se guarda nada, porque el color
 * del botón no alcanza para decirlo (Lineamiento §10).
 */
.asistente__confirmacion {
    margin-top: var(--sp-4);
    padding-top: var(--sp-3);
    border-top: 1px solid var(--n-200);
}

/*
 * Los dos botones se parten en celular antes que encogerse: «Confirmar» con
 * medio texto cortado, en una pantalla donde el toque escribe en el negocio, es
 * peor que dos renglones. Y a ancho de teléfono ocupan el renglón completo, que
 * es el blanco de toque más grande posible.
 */
.asistente__confirmacion-acciones {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-3);
    margin-top: var(--sp-3);
}

.asistente__confirmacion-acciones > .button {
    flex: 1 1 10rem;
    justify-content: center;
}

/*
 * El renglón de un producto o de un ítem del resumen. Dos columnas que se
 * parten: el nombre crece y el dato de la derecha —código, cantidad, total— se
 * va a su propio renglón antes que achicar el nombre, por el mismo motivo que
 * `.suggest__name` llega hasta dos líneas: lo que distingue un producto del de
 * al lado está al final del nombre.
 */
.asistente__item {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--sp-2);
    padding-bottom: var(--sp-2);
    border-bottom: 1px solid var(--n-200);
}

.asistente__item:last-child {
    padding-bottom: 0;
    border-bottom: 0;
}

.asistente__item-nombre {
    flex: 1 1 10rem;
    min-width: 0;
    font-size: var(--fs-body-dense);
    line-height: var(--lh-body-dense);
    font-weight: 600;
    color: var(--n-900);
    overflow-wrap: anywhere;
}

.asistente__item-dato {
    flex: 0 0 auto;
    font-family: var(--font-data);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-body-dense);
    line-height: var(--lh-body-dense);
    color: var(--n-700);
    white-space: nowrap;
}

.asistente__item-nota {
    flex-basis: 100%;
    font-size: var(--fs-caption);
    line-height: var(--lh-caption);
    color: var(--n-500);
}

/*
 * Un atributo configurable con sus opciones. El nombre del atributo arriba y
 * las opciones debajo como chips, que es como se leen cuando el cliente
 * pregunta «¿y en roble cuánto sale?»: de un barrido, no renglón por renglón.
 */
.asistente__atributo + .asistente__atributo {
    margin-top: var(--sp-3);
}

.asistente__atributo-nombre {
    display: block;
    margin-bottom: var(--sp-1);
    font-size: var(--fs-label);
    line-height: var(--lh-label);
    font-weight: 600;
    color: var(--n-900);
}

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

/* ── El detalle técnico de una falla del micrófono ───────────────────────── */

/*
 * Aparece solo cuando el micrófono no se pudo abrir, debajo del aviso que ya le
 * dijo al vendedor qué hacer. Va plegado: el vendedor no necesita leerlo, lo
 * necesita cuando llama a soporte y le preguntan «¿qué dice ahí?».
 *
 * El fondo es neutro y no de alerta a propósito. El aviso de arriba ya es rojo;
 * dos bloques rojos seguidos hacen que el segundo parezca un segundo problema.
 */
.asistente__diagnostico {
    margin-top: var(--sp-3);
    padding: var(--sp-3);
    border: 1px solid var(--n-200);
    border-radius: var(--r-md);
    background: var(--n-100);
}

.asistente__diagnostico > summary {
    font-size: var(--fs-label);
    line-height: var(--lh-label);
    font-weight: 600;
    color: var(--n-700);
    cursor: pointer;
}

/* El foco del teclado tiene que verse: el resumen es un control. */
.asistente__diagnostico > summary:focus-visible {
    outline: var(--bw-2) solid var(--focus);
    outline-offset: var(--bw-2);
    border-radius: var(--r-sm);
}

.asistente__diagnostico > p {
    margin: var(--sp-2) 0 0;
    color: var(--n-500);
}

/*
 * Las cuatro comprobaciones del entorno: etiqueta y resultado. En dos columnas
 * desde `--bp-phone` —se leen como una tabla—, y apiladas abajo de ese ancho,
 * donde dos columnas dejarían el resultado partido en tres renglones.
 */
.asistente__diagnostico-lista {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--sp-1) var(--sp-3);
    margin: var(--sp-3) 0 0;
    font-size: var(--fs-caption);
    line-height: var(--lh-caption);
}

.asistente__diagnostico-lista dt {
    color: var(--n-500);
}

.asistente__diagnostico-lista dd {
    margin: 0 0 var(--sp-2);
    font-weight: 600;
    color: var(--n-900);
}

@media (min-width: 600px) {
    .asistente__diagnostico-lista {
        grid-template-columns: auto 1fr;
        align-items: baseline;
    }

    .asistente__diagnostico-lista dd {
        margin-bottom: 0;
    }
}

/* ── Los avisos permanentes del pie ──────────────────────────────────────── */

/*
 * Las dos frases que no se sacan nunca: que los importes los calcula el Gestor
 * Comercial y que el audio no se guarda. Van en Caption y al pie, no en un
 * aviso con fondo: un `.notice` acá se leería como una alerta del momento y
 * esto no es del momento, es una propiedad del asistente.
 */
.asistente__avisos {
    display: flex;
    flex-direction: column;
    gap: var(--sp-1);
    margin: 0;
    padding: 0;
    list-style: none;
}

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

/* ── Tarjeta de uso (pantalla de configuración) ──────────────────────────── */

/*
 * Las cuatro métricas de consumo. Grilla que se auto-ajusta: dos columnas en un
 * celular de 360 px, cuatro desde el ancho de una tablet. Sin consultas de
 * medios porque no hace falta ninguna: `auto-fit` con un mínimo de 8rem es lo
 * que resuelve el salto, y 8rem es el ancho de «Sesiones del mes» en dos
 * renglones.
 */
.asistente-uso {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
    gap: var(--sp-4);
    margin: 0;
}

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

/*
 * La cifra. `--fs-figure-sm` y no `--fs-display`: son cuatro números juntos y
 * en el nivel grande la tarjeta se convierte en un tablero, que no es lo que
 * esto es —un control de consumo al pie de una configuración—.
 */
.asistente-uso__valor {
    margin: 0;
    font-family: var(--font-data);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-figure-sm);
    line-height: var(--lh-figure-sm);
    font-weight: 700;
    color: var(--n-900);
}

/* La última conversación es una fecha y no una cantidad: en el cuerpo de cifra
   se parte en renglones de dígitos y se lee peor que en texto normal. */
.asistente-uso__valor--texto {
    font-family: var(--font-ui);
    font-size: var(--fs-body-dense);
    line-height: var(--lh-body-dense);
    font-weight: 600;
}

/* ══ 27-aviso-de-entorno.css ══ */

/* ==========================================================================
   AVISO DE ENTORNO
   ==========================================================================
   Franja que encabeza todas las pantallas de una instalación que no es la de
   verdad —hoy, el piloto de experiencia de usuario—. La dibuja el componente
   `components/environment-notice.blade.php`, que incluyen el armazón de la
   aplicación, la pantalla de ingreso y el armazón de las pantallas de error,
   sólo cuando `APP_ENVIRONMENT_NOTICE` está declarada: en producción no existe
   y esta regla no se usa.

   Va en los colores de advertencia y no en el amarillo de marca: el amarillo es
   acción, no aviso (Lineamiento §3.1). No es sticky a propósito —se lee una vez
   al entrar y después no le roba altura a la pantalla de un celular—, y queda
   antes de la barra superior, que sí es sticky y se pega sola al subir.
   ========================================================================== */

.environment-notice {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--sp-2);
    margin: 0;
    padding: var(--sp-2) var(--sp-4);
    background-color: var(--warn-bg);
    border-bottom: 1px solid var(--warn);
    color: var(--warn);
    font-size: var(--fs-label);
    font-weight: 600;
    line-height: var(--lh-label);
    letter-spacing: 0.01em;
    text-align: center;
}

.environment-notice__icon {
    flex: none;
    color: var(--warn);
}

/* La pantalla de ingreso y las de error centran su panel con flex sobre el
   propio `body`: ahí la franja sería un hermano más del panel y se dibujaría *al
   lado*, no arriba. Estas dos reglas la sacan de ese centrado y la pegan al
   borde superior, de ancho completo, y le hacen lugar para que no se superponga
   con el panel en una pantalla baja.

   Las tres van con `:has()` o con el hijo presente a propósito: si
   `APP_ENVIRONMENT_NOTICE` no está declarada el elemento no existe, ninguna
   regla engancha y las dos pantallas quedan exactamente como estaban en V1 y en
   V2. */
.login:has(> .environment-notice),
.error-page:has(> .environment-notice) {
    position: relative;
    padding-block-start: calc(var(--sp-5) + 3rem);
}

.login > .environment-notice,
.error-page > .environment-notice {
    position: absolute;
    top: 0;
    inset-inline: 0;
}

