/* ============================================================
   Flow — Carcasa de aplicación (navegación y estructura)
   Requiere design-tokens.css y base.css. CSS vainilla.
   Un solo punto de quiebre: 720px (§8.1).
   ============================================================ */

.app-body { display: flex; flex-direction: column; min-height: 100dvh; }

/* ---------- Navegación de escritorio ---------- */
/* `flex-wrap` NO ES COSMÉTICO. Sin envolver, los siete enlaces medían 837 px en
 una línea y empujaban el documento a 1184 px: cualquier pantalla entre 721 y
 1184 —tablets, y el móvil en horizontal— arrastraba scroll horizontal en TODA
 la página, no solo en la tabla. Por debajo de 721 este menú no se dibuja. */
.nav-escritorio { display: flex; align-items: center; gap: var(--space-4); flex-wrap: wrap; }
.nav-escritorio a {
  color: var(--text-secondary); font-weight: var(--fw-medium);
  padding: 8px 10px; border-radius: var(--radius-md); text-decoration: none;
}
.nav-escritorio a:hover { background: var(--surface-hover); color: var(--text); text-decoration: none; }
.nav-escritorio a[aria-current="page"] { color: var(--text); background: var(--brand-soft); }

/* ---------- Destinos pendientes ----------
   Un destino cuyo módulo aún no existe se muestra y se explica, en vez de
   quedarse como un enlace que no lleva a ninguna parte. La razón NO va solo en
   el color ni solo en el `title` (§8.5): va escrita en la propia etiqueta, que
   es lo único que lee tanto quien ve la pantalla como quien la escucha. */
.nav-escritorio .nav-pendiente,
.nav-movil .nav-pendiente {
  color: var(--text-muted); cursor: default; text-decoration: none;
}
.nav-escritorio .nav-pendiente { padding: 8px 10px; font-weight: var(--fw-medium); }
.etiqueta-pendiente {
  font-size: var(--fs-caption); font-weight: var(--fw-medium);
  color: var(--text-secondary);
  border: 1px solid var(--border-strong); border-radius: var(--radius-sm);
  padding: 0 var(--space-micro); margin-inline-start: var(--space-micro);
  white-space: nowrap;
}
/* En alto contraste el atenuado deja de distinguir, así que la marca es el
   trazo: el borde sube de grosor y el texto vuelve al color normal. */
:root[data-contrast="high"] .nav-escritorio .nav-pendiente,
:root[data-contrast="high"] .nav-movil .nav-pendiente { color: var(--text); }
:root[data-contrast="high"] .etiqueta-pendiente {
  border-width: 2px; color: var(--text);
}

/* ---------- Contenido ---------- */
.app-main { flex: 1 1 auto; }

/* Dos utilidades, y NO se reusa `.solo-movil`: aquella lleva medidas de
   objetivo táctil de 44 px encima, así que aplicarla a un texto lo
   convertiría en un botón. `revert` devuelve el `display` propio del
   elemento —`inline` en un span, `block` en un div— sin fijarlo aquí. */
.solo-estrecho { display: none; }

/* ---------- Barra inferior (móvil, §8.2) ---------- */
.nav-movil { display: none; }

@media (max-width: 720px) {
  .nav-escritorio { display: none; }

  /* EL RELLENO NO PUEDE CRECER CON EL TEXTO EN UNA PANTALLA ESTRECHA.
     Los espaciados van en `rem`, así que a escala máxima la raíz mide 29,75 px
     y el margen de la página (1rem) más el de la tarjeta (1,5rem) sumaban 148
     px de los 367 disponibles: el 41 % de la pantalla era hueco, justo cuando
     el texto necesita más sitio. De ahí venía el scroll horizontal a escalas
     grandes, no de la tabla. Se topa solo el eje horizontal: el vertical sigue
     escalando, porque ahí no hay límite que respetar.
     No se fija en `px` a secas para que a escala normal el diseño no cambie. */
  .app-shell { padding-inline: min(var(--space-4), 14px); }
  .card      { padding-inline: min(var(--space-5), 14px); }

  /* Y lo que aun así no quepa, que se parta en vez de empujar la pantalla. */
  /* DOS VALORES DISTINTOS, Y NO ES UN DETALLE. `anywhere` además de partir
     reduce el ancho MÍNIMO del elemento, así que en una fila flex el título se
     encogía hasta partirse a mitad de palabra —«Registra / r»— aunque hubiera
     sitio. `break-word` parte solo cuando la palabra no cabe de verdad.
     En los importes sí se quiere el encogido: es lo que les permite ceder. */
  h1, h2 { overflow-wrap: break-word; }
  .amount { overflow-wrap: anywhere; }  /* `.neto-cifra` ya lo lleva sin condición */

  /* Los pares «etiqueta + importe» de los totales se apilan si no caben. En
     escritorio van en línea a propósito —se leen de un vistazo—, pero a escala
     máxima «S/ 21,531.02» mide 263 px él solo y no hay línea que lo aguante.
     `min-width: 0` es lo que permite encoger: un hijo flex no baja de su
     contenido mínimo sin él, y `overflow-wrap` por sí solo no se aplica. */
  .total-bloque { flex-wrap: wrap; }
  .total-bloque .amount, .total-bloque .caption { min-width: 0; }

  /* ─── EN MÓVIL, SOLO EL MENÚ · Kike, 20/08/2026 ────────────────────────
   *
   * *«En móvil no debe aparecer ninguno, todo se soluciona desde el menú
   * hamburguesa.»*
   *
   * Y la medida le da la razón antes de discutirla: los accesos son **seis**
   * y la rejilla repartía **cinco**, así que el sexto caía a un segundo
   * renglón. Medido a 380 px con su propio estilo: **2 filas y 114 px** —el
   * 14 % de la altura de un teléfono— gastados antes de enseñar un solo dato.
   *
   * Se DESACTIVA, no se borra: la barra sigue en el armazón y vuelve
   * quitando este `display: none`. Los seis siguen alcanzándose por el menú,
   * que sale en las dos anchuras y lleva la MISMA lista —`accesoRapido()`—,
   * así que móvil y escritorio no pueden divergir.
   *
   * Desaparecen **a partir de 720 px**, que es el mismo corte en el que la
   * barra de escritorio se retira: no hay ningún ancho con las dos ni
   * ninguno sin ninguna de las dos formas de navegar. */
  .nav-movil { display: none; }

  /* Y así estaba, guardado entero para poder volver sin reconstruirlo —y sin
     inventar una clase que ningún marcado usa, que es lo que caza
     `auditar_clases`—:

     .nav-movil {
       display: grid;
       grid-template-columns: repeat(5, 1fr);
       position: sticky; bottom: 0; z-index: 20;
       background: var(--surface);
       border-top: 1px solid var(--border);
       padding-bottom: env(safe-area-inset-bottom, 0);
     }
  */
  .nav-movil a {
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    /* ─── EL MÍNIMO TÁCTIL SON 44 px FIJOS ────────────────────────────
       Decisión de Kike, 6 de agosto de 2026. Ponía `max(44px, 3.5rem)`, que
       a escala máxima exige 104 px y convierte en «incumplimiento» algo que
       cumple de sobra. `min-height` es un SUELO, no un techo: si la letra
       crece, la caja crece con ella igual. */
    gap: var(--space-micro); min-height: var(--tactil-minimo);
    font-size: var(--fs-caption); color: var(--text-secondary);
    text-decoration: none; padding: var(--space-mini) var(--space-micro);
    /* Cinco etiquetas en 375px: sin esto, la más larga desborda su celda
       incluso en nivel normal. Se permite segunda línea antes que recortar. */
    text-align: center; line-height: 1.15;
    overflow-wrap: anywhere; hyphens: auto;
    min-width: 0;
  }
  .nav-movil a:hover { text-decoration: none; }
  .nav-movil a[aria-current="page"] { color: var(--brand); }
  .nav-movil a[aria-current="page"] .nav-icono { transform: translateY(-1px); }
  /* El pendiente ocupa su celda igual que un enlace: si encogiera, las otras
     cuatro se recolocarían y la barra cambiaría de forma al construirse el
     módulo. En la barra inferior no cabe la etiqueta «pendiente», así que la
     distinción es el color atenuado más el `aria-disabled` que sí se anuncia. */
  .nav-movil .nav-pendiente {
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: var(--space-micro); min-height: var(--tactil-minimo);
    font-size: var(--fs-caption); padding: var(--space-mini) var(--space-micro);
    text-align: center; line-height: 1.15;
    overflow-wrap: anywhere; hyphens: auto; min-width: 0;
  }
  .nav-movil .nav-pendiente .nav-icono { opacity: .55; }
  :root[data-contrast="high"] .nav-movil .nav-pendiente .nav-icono { opacity: 1; }
  /* `font-size` es el tamaño del icono: el sprite mide en `em`. La caja se
     centra en rejilla para que el ajuste óptico de `.ico` no descuadre el SVG. */
  .nav-icono { font-size: 1.3rem; line-height: 1; display: grid; place-items: center; }
  .nav-icono .ico { vertical-align: baseline; }

  /* Destino central destacado: la acción más frecuente */
  .nav-movil a.nav-principal .nav-icono {
    /* ─── ESTO NO ES LA REGLA TÁCTIL, ES CABER ────────────────────────
       Aquí `max()` se queda, y no por inercia: no es un objetivo táctil sino
       un círculo que CONTIENE un icono de `1.4rem`, y ese icono crece con la
       escala. Fijarlo en 44 px dejaría el contenido apretado contra el borde
       a escala máxima. El mínimo táctil sigue cumpliéndose por el suelo de
       44; lo que manda por encima es el contenido. */
    width: max(var(--tactil-minimo), 2.75rem); height: max(var(--tactil-minimo), 2.75rem); border-radius: var(--radius-pill);
    background: var(--brand); color: var(--on-brand);
    display: grid; place-items: center; font-size: 1.4rem; font-weight: var(--fw-bold);
    margin-top: -14px; box-shadow: var(--shadow-2);
  }
  .nav-movil a.nav-principal { color: var(--text); font-weight: var(--fw-semibold); }

  /* Tablas → tarjetas: sin scroll horizontal en datos financieros (§8.1) */
  .movs.responsiva thead { display: none; }
  /* `minmax(0, 1fr)` Y NO `1fr`. Una columna `1fr` no baja de su contenido
     mínimo, así que a escala muy grande la fila medía 642 px en un viewport de
     371 y arrastraba scroll horizontal a toda la pantalla — §8.1 lo prohíbe
     expresamente en datos financieros. Con `minmax(0, …)` la primera columna
     puede encoger y el concepto se parte. Medido: 642 px → 295 px.
     Es anterior al reparto de columnas de escritorio: `table-layout: fixed`
     vive en el media query de arriba y aquí no llega. */
  .movs.responsiva tbody tr {
    display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: var(--space-1) var(--space-3);
    padding: var(--space-3) var(--space-4); border-bottom: 1px solid var(--border);
  }
  .movs.responsiva tbody td { display: block; padding: 0; border: 0; min-width: 0; overflow-wrap: anywhere; }
  .movs.responsiva tbody td[data-etiqueta]::before {
    content: attr(data-etiqueta); display: block;
    font-size: var(--fs-caption); color: var(--text-muted);
    letter-spacing: var(--tracking-label); text-transform: uppercase;
  }
  .movs.responsiva tbody td.col-amt { grid-column: 2; text-align: right; align-self: center; }
}

/* ---------- Preferencias ---------- */
.preferencias { display: flex; flex-wrap: wrap; gap: var(--space-3); align-items: center; }

/* ---------- Aviso sin conexión (§8.1: sin escritura offline) ---------- */
.aviso-sin-conexion {
  display: none;
  background: var(--warning-bg); color: var(--warning-ink);
  border: 1px solid var(--warning); border-radius: var(--radius-md);
  padding: var(--space-3) var(--space-4); margin-bottom: var(--space-4);
  font-weight: var(--fw-medium);
}
.aviso-sin-conexion::before { content: "⚠ "; }
body.sin-conexion .aviso-sin-conexion { display: block; }
body.sin-conexion .btn--primary { opacity: .5; pointer-events: none; }

/* ---------- Barra comprometido/libre (§8.3) ---------- */
.barra-proporcion {
  display: flex; height: 10px; border-radius: var(--radius-pill);
  overflow: hidden; background: var(--bg-sunken); border: 1px solid var(--border);
}
.barra-proporcion .parte-comprometido { background: var(--warning); }
.barra-proporcion .parte-libre { background: var(--income); }

/* ---------- Visibilidad por dispositivo ---------- */
.solo-movil { display: none; }
@media (max-width: 720px) {
  .solo-movil { display: inline-flex; min-width: 44px; min-height: 44px; align-items: center; justify-content: center; }
}

/* ---------- Escalas altas: menos destinos, nunca menos texto ----------
   A 150% y 175% no caben cinco etiquetas en 375px. La respuesta NO es quitar
   las etiquetas: quien amplía el texto tiene dificultad para ver, y un icono
   pelado le exige reconocer un glifo y recordar qué significa. Necesita la
   etiqueta MÁS que quien usa 100%, no menos.
   Así que se reducen los destinos —Facturas y Cuentas pasan al menú— y los
   tres que quedan conservan su texto. Se pierde un atajo, no la legibilidad. */
@media (max-width: 720px) {
  :root[data-escala="muy_grande"] .nav-movil,
  :root[data-escala="maximo"]     .nav-movil { grid-template-columns: repeat(3, 1fr); }

  :root[data-escala="muy_grande"] .nav-movil .nav-secundario,
  :root[data-escala="maximo"]     .nav-movil .nav-secundario { display: none; }

  /* El número grande tampoco puede exceder el ancho de pantalla:
     los rem escalan con la preferencia, así que se acota por viewport. */
  /* El número grande tampoco puede exceder el ancho de pantalla:
     los rem escalan con la preferencia, así que se acota por viewport.
     REVERTIDO a esto el 15/08/2026 — ver la nota del final del fichero. */
  /* Era `min(--fs-display, 13vw)` y daba **61,93 px** frente a 63,75: una
     segunda fórmula para lo mismo. Ahora el token, y uno solo. */
  .stat-value { font-size: var(--fs-cifra-widget); }
}

/* NOTA: aquí había un segundo bloque, duplicado, que en escalas altas ocultaba
   TODAS las etiquetas de la barra y dejaba solo iconos. Contradecía punto por
   punto el argumento del bloque de arriba —quien amplía el texto necesita la
   etiqueta más, no menos— y, por ir después, era el que ganaba. Se eliminó junto
   con la repetición de `.solo-movil`. Queda una sola regla: en escalas altas se
   reducen los DESTINOS, nunca el texto. */

/* ============================================================
   Componentes de catálogo (listado, formulario, árbol, avisos)
   ============================================================ */

/* ---------- Avisos de resultado ---------- */
.aviso {
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-md);
  border: 1px solid transparent;
  font-weight: var(--fw-medium);
}
.aviso::before { font-weight: var(--fw-bold); margin-right: .4em; }
.aviso--exito  { background: var(--income-bg);  color: var(--income-ink);  border-color: var(--income);  }
.aviso--exito::before  { content: "✓"; }
.aviso--error  { background: var(--expense-bg); color: var(--expense-ink); border-color: var(--expense); }
.aviso--error::before  { content: "✕"; }
.aviso--alerta { background: var(--warning-bg); color: var(--warning-ink); border-color: var(--warning); }
.aviso--alerta::before { content: "!"; }
/* ─── UN ENLACE DENTRO DE UN AVISO LLEVA EL TINTE DEL AVISO · 03/09/2026 ─────
 *
 * El color de enlace de la casa es el de marca, pensado sobre el fondo normal.
 * Puesto sobre el rojo de un aviso de error pierde contraste justo donde más
 * falta hace. Hereda el tinte del aviso —que sí está pensado para ese fondo— y
 * se distingue **por el subrayado y el grosor**, no por el color. */
.aviso a { color: inherit; text-decoration: underline; text-underline-offset: .18em;
           font-weight: var(--fw-bold); }
.aviso a:hover { text-decoration-thickness: 2px; }
/* ─── EL CUARTO DE LA FAMILIA: EL QUE NO ES BUENO NI MALO ────────────────────
 *
 * Estaba escrito en `cliente/paneles.php` —«forma parte de un grupo de 3 razones
 * sociales, encabezado por…»— y no existía en ninguna hoja: era un aviso sin
 * fondo, sin borde y sin símbolo, o sea un párrafo con relleno.
 *
 * Y no sobraba: eso no es un éxito, ni un error, ni una alerta. Pintarlo de
 * `--warning` habría dicho que hay algo que vigilar en pertenecer a un grupo.
 *
 * NADA DE ESTO ES NUEVO. El trío neutro —`--surface-2`, `--border`, `--text`—
 * es el que ya usa `.badge--neutral` para lo mismo, y la «i» es la que ya usa
 * `.marca-incompleto__simbolo`, donde está escrito por qué: «i» explica de dónde
 * sale algo, «!» advierte de un peligro. */
.aviso--info { background: var(--surface-2); color: var(--text); border-color: var(--border); }
.aviso--info::before { content: "i"; }

/* ---------- Estado vacío ---------- */
.estado-vacio {
  display: flex; flex-direction: column; align-items: center; gap: var(--space-3);
  padding: var(--space-7) var(--space-5); text-align: center;
}
.estado-vacio p { max-width: 44ch; }

/* ---------- Paginación ---------- */
.paginacion {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-3); padding: var(--space-4); flex-wrap: wrap;
  border-top: 1px solid var(--border);
}

/* ─── MIENTRAS SE BUSCA, LO QUE HAY EN LA TABLA ES LO DE ANTES ─────────────
 *
 * `buscar-servidor.js` deja la lista vieja puesta mientras llega la nueva —
 * vaciarla y volverla a llenar se lee como «no hay nada» y encima salta la
 * página entera— y marca `aria-busy="true"` en el bloque. Su cabecera decía
 * que la tabla se queda «atenuada y con su rótulo», y **el rótulo estaba y la
 * atenuación no**: medido el 13 de agosto de 2026 con una respuesta retrasada
 * a propósito, `opacity` valía `1` y no había ninguna regla de `aria-busy` en
 * ninguna de las tres hojas. O sea: una lista que ya no corresponde a lo
 * escrito, con el mismo aspecto que un resultado.
 *
 * Se atenúa el bloque, NO el rótulo: éste vive fuera —`buscar-servidor.js` lo
 * inserta antes del bloque justo por esto—, así que «Buscando «AIXA»…» se
 * sigue leyendo a plena fuerza mientras lo de debajo se apaga.
 *
 * ─── Y EN ALTO CONTRASTE NO SE ATENÚA ────────────────────────────────────
 *
 * Bajar la opacidad baja el contraste, y ahí es justo lo que no se puede
 * tocar. Es el mismo trato que ya recibe `.nav-pendiente .nav-icono`. El
 * aviso no se pierde: el rótulo es texto y lo lee todo el mundo, que además
 * es lo que exige §8.4 —no comunicar por apariencia sola—. La atenuación
 * refuerza; no es el único portador del mensaje.
 *
 * EL SELECTOR ES GENÉRICO A PROPÓSITO, y no `#listado-catalogo[aria-busy]`:
 * `aria-busy` ya significa «lo que ves aquí está a medio hacer», y atarlo a un
 * identificador obligaría a acordarse de venir aquí cada vez que otra pantalla
 * lo use. Hoy lo usa un solo sitio; el día que lo use un segundo, se comporta
 * igual sin que nadie lo pida. */
[aria-busy="true"] { opacity: .55; }
:root[data-contrast="high"] [aria-busy="true"] { opacity: 1; }

/* ---------- Errores por campo ---------- */
.error-campo {
  color: var(--expense-ink); font-size: var(--fs-caption); font-weight: var(--fw-medium);
}
.error-campo::before { content: "✕ "; }
.field[data-error] .input,
.field[data-error] .select { border-color: var(--expense); }

/* ---------- Un filtro puesto se ve ----------
   Tres señales, y NINGUNA es solo el color (§8.5): borde de marca más grueso,
   texto en negrita y un fondo tenue. En alto contraste el fondo desaparece —ahí
   estorba— y quedan el borde y el peso, que son los que sobreviven.
   El borde se compensa con el relleno para que el control NO se mueva al
   activarse: una fila de filtros que salta al elegir se lee como un fallo. */
.filtro-activo {
  border-width: 2px; border-color: var(--brand);
  padding-inline: calc(var(--space-3) - 1px);
  font-weight: var(--fw-medium);
  background: var(--brand-soft);
}
[data-contrast="high"] .filtro-activo { background: transparent; }

.sr-only {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap;
}

/* ---------- Controles ---------- */
.interruptor { display: inline-flex; align-items: center; gap: var(--space-2); min-height: 44px; cursor: pointer; }
.interruptor input { width: var(--casilla); height: var(--casilla); }
.input-color { width: 4rem; height: var(--tactil-minimo); padding: var(--space-micro); border: 1px solid var(--border-strong);
               border-radius: var(--radius-md); background: var(--bg-sunken); }
.muestra-color { width: 1rem; height: 1rem; border-radius: var(--radius-xs); border: 1px solid var(--border-strong); display: inline-block; }

/* ---------- Diálogo de confirmación ---------- */
.dialogo {
  border: 1px solid var(--border-strong); border-radius: var(--radius-lg);
  background: var(--superficie-ventana); color: var(--text);
  padding: var(--space-5); max-width: min(34rem, 92vw); box-shadow: var(--shadow-2);
  /* `margin: auto` es lo que centra un `<dialog>` modal, y el reset `* { margin: 0 }`
     de base.css lo mataba: el diálogo salía pegado a la esquina superior
     izquierda, encima del encabezado. Afectaba también al de archivar de los
     catálogos, que nunca había mirado abierto. */
  margin: auto;
  /* ─── TOPE DE ALTURA, O SE SALE DE LA PANTALLA ────────────────────────
     Medido el 6 de agosto de 2026 con el alta de proveedor, que ganó el
     selector de origen, el país, la moneda y la web: **1.130 px a escala
     máxima**. En la ventana donde se midió —1.320 px— cabía; en un portátil
     de 800 se habría salido por arriba y por abajo, y sin tope no hay
     desplazamiento que lo rescate: el `<dialog>` simplemente crece.
     Con el tope, lo que no cabe se desplaza dentro en vez de quedar fuera
     de la pantalla. `dvh` y no `vh` porque en el móvil la barra del
     navegador se recoge y `vh` mide la ventana que no está. */
  /* ─── Y EL PIE NO SE VA CON EL CONTENIDO ──────────────────────────────
     Con `overflow` en el propio diálogo desplazaban los tres a la vez, así
     que en cuanto no cabía **el botón «Crear» quedaba por debajo del corte**:
     medido, `pieVisibleSinDesplazar: false` en las doce simulaciones que
     desplazaban. Un diálogo cuyo botón de aceptar hay que ir a buscar es peor
     que uno que no cabe, porque parece que no se puede terminar.
     Columna flexible: cabecera y pie fijos, y lo que se desplaza es el cuerpo,
     que ya sabe hacerlo. */
  max-block-size: 90dvh;
}

/* ─── UN DIÁLOGO CERRADO NO PUEDE DEPENDER DE UNA REGLA AJENA ───────────────
 *
 * Aquí estaba `display: flex` sin condicionar, y **abría dos diálogos solos** en
 * Compras: el visor y el de eliminar, con sus tres campos vacíos. No estaban
 * abiertos —`open` valía `false` y `:modal` no casaba— sino DIBUJADOS, porque un
 * `display` de autor pisa el `dialog:not([open]) { display: none }` del
 * navegador: los estilos de autor ganan por ORIGEN, no por especificidad.
 *
 * Es el «diálogo fantasma» que ya costó cuatro turnos, y volvió por la misma
 * puerta el 6 de agosto de 2026 al darle al diálogo columna flexible para que el
 * pie no se fuera con el contenido.
 *
 * El arreglo no es devolverle el `display` bueno: es que **el estado cerrado no
 * dependa de que sobreviva la regla del navegador**. Por eso hay dos reglas de
 * autor, no una:
 *
 *   · `[open]` pone la maquetación, así que un diálogo cerrado nunca recibe un
 *     `display` que lo saque de la nada;
 *   · `:not([open])` lo apaga **explícitamente**, para que no haga falta que
 *     nadie recuerde de dónde venía el ocultamiento.
 *
 * Es la misma lección del panel del menú, que nace con `hidden` en el marcado en
 * vez de esconderse con estilos: lo que se oculta por CSS sigue estando ahí. */
.dialogo[open] { display: flex; flex-direction: column; overflow: hidden; }
.dialogo:not([open]) { display: none; }
.dialogo > .dlg-cabecera, .dialogo > .dlg-pie { flex: 0 0 auto; }
.dialogo::backdrop { background: rgba(0,0,0,.55); }

/* ---------- Tarjetas del índice ---------- */
.tarjeta-modulo { text-decoration: none; display: flex; flex-direction: column; gap: var(--space-2); }
.tarjeta-modulo:hover { background: var(--surface-hover); text-decoration: none; }

/* ---------- Árbol de categorías ---------- */
.arbol, .rama { list-style: none; padding: 0; margin: 0; }
.rama { margin-left: var(--space-5); border-left: 2px solid var(--border); padding-left: var(--space-4); }
/* Con el meta en línea (ver `.nodo-info`), el aire vertical sobra: la fila va
   apretada a `--space-2`. */
.nodo-fila {
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-3);
  padding: var(--space-2) 0; border-bottom: 1px solid var(--border); flex-wrap: wrap;
}
/* Las etiquetas van EN LA MISMA LÍNEA del nombre, a su derecha. Antes `.nodo-info`
   era una columna, así que cada categoría ocupaba dos o tres líneas y con setenta
   y nueve el árbol se volvía interminable. En escritorio hay ancho de sobra. */
.nodo-info {
  display: flex; flex-direction: row; align-items: center; flex-wrap: wrap;
  gap: var(--space-1) var(--space-3); min-width: 0; flex: 1 1 14rem;
}
.nodo-nombre {
  font-weight: var(--fw-medium); overflow-wrap: anywhere;
  display: inline-flex; align-items: center; gap: var(--space-2);
}
.nodo-meta { display: flex; gap: var(--space-2); flex-wrap: wrap; align-items: center; }

@media (max-width: 720px) {
  /* En 375px las etiquetas no caben junto al nombre: ahí sí bajan de línea. */
  .nodo-info { flex-direction: column; align-items: flex-start; gap: var(--space-1); }
}
.nodo-acciones { display: flex; gap: var(--space-2); flex-wrap: wrap; }

@media (max-width: 720px) {
  /* Cada nivel de sangría robaría ancho al nombre, que es lo único que importa
     en 375px. La guía se reduce y el árbol nunca desborda. */
  .rama { margin-left: var(--space-2); padding-left: var(--space-3); }
  .nodo-acciones { width: 100%; }
  .nodo-acciones .btn { flex: 1 1 auto; }
}
/* En escala alta la sangría desaparece del todo: el ancho es para el texto. */
:root[data-escala="muy_grande"] .rama,
:root[data-escala="maximo"] .rama { margin-left: 0; padding-left: var(--space-2); }

/* ---------- Plantillas de texto ---------- */
.frase-muestra {
  background: var(--bg-sunken); border-left: 3px solid var(--brand);
  padding: var(--space-3) var(--space-4); border-radius: var(--radius-sm);
  overflow-wrap: anywhere;
}
.marcador {
  font-family: var(--font-mono); font-size: .92em;
  background: var(--brand-soft); color: var(--text);
  padding: 1px var(--space-mini); border-radius: var(--radius-xs); margin-right: var(--space-1); white-space: nowrap;
}


/* ============================================================
   Escritorio primero: el 99% del trabajo ocurre aquí (§8.1).
   El móvil es para registrar un gasto y mirar indicadores; todo lo
   demás necesita ancho, y el ancho hay que usarlo.
   ============================================================ */

/* Las pantallas de lectura y captura se acotan: un renglón de 1700px no se
   lee. Las de datos usan todo el ancho. */
.app-main.medida-texto { max-width: var(--maxw-texto); }

/* Columnas que solo caben en escritorio. No es lo mismo «no mostrar nunca»
   que «no cabe en un teléfono»: antes se confundían las dos cosas. */
@media (max-width: 720px) {
  .col-escritorio { display: none !important; }
}

/* ---------- Árbol: expandido en escritorio, plegable en móvil ---------- */
.nodo > details { border: 0; }
.nodo > details > summary {
  list-style: none; cursor: pointer;
  display: flex; align-items: center; gap: var(--space-2);
  /* En escritorio el control de plegado es un detalle discreto: la rama ya está
     abierta y el gesto es secundario. En móvil sí es un objetivo táctil real. */
  min-height: var(--space-5); padding-left: var(--space-2);
}
/* En escritorio el árbol va siempre expandido, así que el control de plegado no
   tiene función y quedaba como un punto suelto en su propia línea. Se oculta y
   la rama se muestra directamente; en móvil sí aparece, como objetivo táctil. */
.nodo > details > summary { display: none; }
@media (max-width: 720px) {
  .nodo > details > summary {
    display: flex; min-height: var(--tactil-minimo);
  }
  .nodo > details > summary::after {
    content: "Subcategorías"; font-size: var(--fs-caption); color: var(--text-secondary);
  }
  .nodo > details[open] > summary::after { content: "Ocultar subcategorías"; }
}
.nodo > details > summary::-webkit-details-marker { display: none; }
.nodo > details > summary .caret {
  display: inline-block; transition: transform .15s ease;
  color: var(--text-secondary); font-size: .8em;
}
.nodo > details[open] > summary .caret { transform: rotate(90deg); }
@media (prefers-reduced-motion: reduce) {
  .nodo > details > summary .caret { transition: none; }
}

/* ---------- Encabezado de módulo ----------
   Aquí vivían las reglas de la descripción plegable y de su botón «Qué es
   esto». Se fueron el 6 de agosto de 2026 con el control, que no respondía:
   el manejador buscaba el párrafo en el padre equivocado, así que
   `[data-abierta]` no se ponía nunca y la descripción no se veía en ningún
   ancho. Comprobado pulsándolo en /admin/clientes antes de retirarlo. */


/* ---------- Casilla de filtro ----------
   Estaba escrita como `style="width:20px"` en dos vistas: §8.5 no admite
   valores visuales fuera de las variables, ni siquiera los pequeños. */
.filtro-casilla { display: flex; align-items: center; gap: var(--space-2); min-height: var(--tactil-minimo); }
/* Los radios entran por la misma puerta que las casillas: son el mismo gesto y
   el mismo tamaño de dedo. Antes solo cubría `checkbox`, y las casillas de los
   formularios se escribieron con una clase `check` que **no existe en ninguna
   hoja** — sin caja de 20 px, sin separación y sin los 44 px táctiles. Kike no
   encontró «el último día del mes» porque a su lado había un campo numérico con
   etiqueta y marcador de posición, y la opción de al lado era una casilla del
   navegador de 13 px pegada al texto. */
.filtro-casilla input[type="checkbox"],
.filtro-casilla input[type="radio"] { width: var(--casilla); height: var(--casilla); flex: none; }
/* Apilado, para cuando son opciones de una misma pregunta y no un filtro suelto. */
.opciones-apiladas { display: flex; flex-direction: column; gap: var(--space-1); }
.opciones-apiladas .filtro-casilla { align-items: flex-start; padding-block: var(--space-1); }
.opciones-apiladas .filtro-casilla > span { padding-block-start: 2px; }

/* ---------- Pestañas de listado ----------
   Enlaces, no botones con JS: funcionan sin scripts, se pueden compartir y el
   navegador las trata como navegación real. */
.pestanas {
  display: flex; gap: var(--space-1); flex-wrap: wrap;
  margin-top: var(--space-4);
  border-bottom: 1px solid var(--border);
}
.pestana {
  display: inline-flex; align-items: center; gap: var(--space-2);
  padding: var(--space-3) var(--space-4);
  min-height: var(--tactil-minimo);
  color: var(--text-secondary); text-decoration: none;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  font-weight: var(--fw-medium);
}
.pestana:hover { color: var(--text); background: var(--surface-hover); text-decoration: none; }
.pestana--activa {
  color: var(--text); border-bottom-color: var(--brand);
}
/* El conteo no depende del color para leerse: es texto dentro de una cápsula. */
.pestana-conteo {
  font-size: var(--fs-caption); font-variant-numeric: tabular-nums;
  background: var(--surface-2); color: var(--text-secondary);
  padding: 0 var(--space-2); border-radius: var(--radius-pill);
  min-width: 1.75em; text-align: center;
}
.pestana--activa .pestana-conteo { background: var(--brand-soft); color: var(--text); }

/* ============================================================
   Paleta curada (§8.5). Radios reales: navegables con teclado y
   funcionales sin JavaScript.
   ============================================================ */
.paleta { display: flex; flex-wrap: wrap; gap: var(--space-2); }

.paleta-muestra {
  position: relative; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  min-width: var(--tactil-minimo); min-height: var(--tactil-minimo);
}
/* El radio real queda invisible pero presente: el foco y el teclado siguen
   siendo los del navegador, sin reimplementar nada. */
.paleta-muestra input {
  position: absolute; opacity: 0; width: 100%; height: 100%; margin: 0; cursor: pointer;
}
.paleta-color {
  width: 1.75rem; height: 1.75rem; border-radius: var(--radius-sm);
  border: 2px solid transparent; box-shadow: 0 0 0 1px var(--border) inset;
}
/* La selección no se marca solo con color —eso sería invisible para quien no lo
   distingue— sino con un anillo y una marca de verificación (§8.5). */
.paleta-muestra input:checked + .paleta-color {
  border-color: var(--text);
  box-shadow: 0 0 0 2px var(--surface), 0 0 0 4px var(--text);
}
.paleta-muestra input:checked + .paleta-color::after {
  content: "✓"; display: grid; place-items: center;
  width: 100%; height: 100%; color: var(--bg); font-weight: var(--fw-bold);
  font-size: var(--fs-caption); text-shadow: 0 0 2px var(--text);
}
.paleta-muestra input:focus-visible + .paleta-color {
  outline: var(--focus-width) solid var(--focus-color); outline-offset: 3px;
}
.paleta-muestra--ninguno span[aria-hidden] {
  width: 1.75rem; height: 1.75rem; border-radius: var(--radius-sm);
  /* `--text-muted` daba 4.42:1 en oscuro: por debajo del 4.5. El guion es la
     unica marca de «sin color», asi que va con tinta de texto secundario. */
  border: 1px dashed var(--border-strong); color: var(--text-secondary);
  display: grid; place-items: center;
}
.paleta-muestra--ninguno input:checked ~ span[aria-hidden] {
  border-style: solid; border-color: var(--text); color: var(--text);
}

/* ============================================================
   Selector de icono (2.2). Mismo patrón que la paleta: radios reales,
   navegables con teclado y funcionales sin JavaScript.
   ============================================================ */
.selector-icono { display: grid; gap: var(--space-2); }
.icono-grupo { margin: var(--space-3) 0 0; font-weight: var(--fw-semibold); }

/* `auto-fill` con mínimo en `rem`: al subir la escala de texto las fichas crecen
   y la rejilla pierde columnas sola, sin una media query por escala. */
.icono-rejilla {
  display: grid; gap: var(--space-2);
  grid-template-columns: repeat(auto-fill, minmax(7.25rem, 1fr));
}
.icono-opcion {
  position: relative; cursor: pointer;
  display: grid; gap: var(--space-1); justify-items: center; align-content: start;
  padding: var(--space-2); border-radius: var(--radius-md);
  border: 2px solid var(--border);
  min-height: var(--tactil-minimo);
  text-align: center;
}
.icono-opcion:hover { background: var(--surface-hover); }
/* El radio real queda invisible pero presente: foco y teclado son los del
   navegador, sin reimplementar nada. */
.icono-opcion input {
  position: absolute; inset: 0; opacity: 0; margin: 0; cursor: pointer;
}
.icono-caja { display: grid; place-items: center; min-height: 2em; color: var(--text); }
.icono-ninguno {
  display: grid; place-items: center; inline-size: 2em; block-size: 2em;
  border: 1px dashed var(--border-strong); border-radius: var(--radius-sm);
  color: var(--text-secondary);
}
/* El nombre es parte del control, no un adorno: un icono cuyo significado solo
   aparece al pasar el ratón no existe para el teclado ni para una pantalla
   táctil. Y no se recorta (§8.5): si no cabe, parte de línea. */
/* `break-word` y no `anywhere`: con `anywhere` el navegador partía «Transferencia»
   por donde le convenía y salía «Transferenci / a». Prefiere el hueco entre
   palabras y solo corta si una palabra sola no cabe. */
.icono-nombre {
  font-size: var(--fs-caption); color: var(--text-secondary);
  line-height: var(--lh-tight); overflow-wrap: break-word;
}
/* La selección se marca con borde, fondo Y una marca de verificación: tres
   señales, ninguna de ellas solo color (§8.5). */
/* Las dos reglas siguientes usan el selector de hermano, que funciona en todo
   navegador: si `:has()` no estuviera disponible, la selección se sigue viendo.
   Marcar el estado con una sola regla moderna habría dejado el control sin
   señal visible allí donde falle. */
.icono-opcion input:checked ~ .icono-nombre { color: var(--text); font-weight: var(--fw-semibold); }
.icono-opcion input:checked ~ .icono-caja::after {
  content: "✓"; position: absolute; top: 2px; right: 5px;
  font-size: var(--fs-caption); font-weight: var(--fw-bold); color: var(--brand);
}
.icono-opcion:has(input:checked) {
  border-color: var(--brand); background: var(--brand-soft);
}
.icono-opcion:has(input:focus-visible) {
  outline: var(--focus-width) solid var(--focus-color); outline-offset: 2px;
}

/* Icono pegado al nombre en un listado: no gasta una columna entera. */
.celda-con-icono { display: inline-flex; align-items: center; gap: var(--space-2); }
.celda-con-icono .ico { color: var(--text-secondary); }

/* ---------- Badges: estilo definitivo (2.3) ----------
   Antes eran cápsulas con punto y borde de color, todas del mismo peso, así que
   un estado importante y una etiqueta informativa competían por atención.
   Ahora hay dos pesos: los de ESTADO llevan color de fondo; los informativos
   son discretos. La palabra siempre lleva el significado, nunca el color solo. */
.badge { border-width: 1px; }
.badge--neutral {
  background: transparent; border-color: var(--border);
  color: var(--text-secondary); font-weight: var(--fw-regular);
}
.badge--neutral::before { display: none; }   /* el punto no aporta en los informativos */
.badge--in, .badge--out, .badge--warn { font-weight: var(--fw-semibold); }

/* ─── UN NEUTRO QUE SÍ ES UN ESTADO ─────────────────────────────────────────
 *
 * La regla de arriba separa bien dos cosas —estado contra etiqueta
 * informativa—, pero deja un hueco: un estado que **no es ni bueno ni un
 * aviso**. «Corresponderá» y «Sin calcular» son eso, y al caer en el peso
 * informativo salían como texto plano gris junto a dos pastillas de color, así
 * que la columna de Estado se leía a dos niveles distintos (Kike, 12 de agosto
 * de 2026).
 *
 * Este modificador les devuelve la FORMA de estado —relleno, punto y peso— sin
 * inventar un color: usa el neutro que la paleta ya traía en `base.css`,
 * `--surface-2` con borde `--border`. Se declara como modificador y no
 * cambiando `badge--neutral`, porque esa clase la usan las etiquetas
 * informativas de toda la aplicación y ahí el peso discreto es lo correcto. */
.badge--neutral.badge--estado {
  background: var(--surface-2); border-color: var(--border-strong);
  color: var(--text); font-weight: var(--fw-semibold);
}
.badge--neutral.badge--estado::before { display: inline-block; }
/* En alto contraste el relleno suave deja de separarse del fondo, así que la
   marca vuelve a ser el trazo, como en el resto de la hoja. */
[data-contrast="high"] .badge--neutral.badge--estado { border-width: 2px; }

/* ---------- La marca de costo asignado: indicador, no botón ----------
   Kike, 11 de agosto de 2026: a la izquierda del de editar, con separador, «que
   se lea como indicador y no invite a pulsarlo».

   Lo que invita a pulsar en una columna de acciones no es el color: es estar
   dentro de una caja con contorno, al lado de otra igual. Así que esto NO lleva
   contorno, ni fondo, ni relleno de botón — y en el marcado es un `<span>`, no
   un `<a>` disfrazado, para que no salga en el recorrido del tabulador ni se
   anuncie como enlace.

   Y NO INFORMA POR COLOR. Son tres iconos distintos con tres textos distintos;
   el tono acompaña. Quien no distingue verde de ámbar lee lo mismo, y quien no
   ve nada lo oye entero por el `sr-only`. */
.marca-costo {
  display: inline-flex; align-items: center; justify-content: center;
  vertical-align: middle;
  /* El mismo alto que el botón de al lado, para que la fila no baile.
     `--tactil-minimo` es el token que ya gobierna el tamaño de un control
     pulsable; aquí se usa como MEDIDA, no como promesa: esto no se pulsa, pero
     tiene que ocupar lo mismo que su vecino o la fila queda torcida. */
  inline-size: var(--tactil-minimo); block-size: var(--tactil-minimo);
}
.marca-costo .ico { inline-size: 1.15rem; block-size: 1.15rem; }
.marca-costo--sin    .ico { color: var(--text-secondary); }
/* `--income` y `--warning` son los dos tokens que Flow ya usa para «esto suma»
   y «esto pide atención». No se inventan nombres nuevos: un token que solo
   existe en un archivo es un token que nadie mantiene, y el auditor de estilos
   lo caza — cazó estos cuatro antes de que llegaran a la pantalla. */
.marca-costo--con    .ico { color: var(--income); }
.marca-costo--pasado .ico { color: var(--warning); }

/* El separador es del oficio de la columna: dice que a un lado hay información
   y al otro acciones. Un pelo vertical, no un borde de caja — un borde volvería
   a sugerir que la marca es pulsable, que es justo lo que se está evitando. */
.acciones-separador {
  display: inline-block; vertical-align: middle;
  inline-size: 1px; block-size: 1.25rem;
  margin-inline: var(--space-2);
  background: var(--border);
}

/* En alto contraste el pelo tiene que verse: `--border` está calculado para
   descansar la vista, y aquí lo que hace falta es que separe. */
@media (prefers-contrast: more) {
  .acciones-separador { background: currentColor; opacity: .6; }
  /* Y los tres estados dejan de fiarse del tono: el icono ya es distinto, pero
     el peso ayuda a que se distinga de un vistazo. */
  .marca-costo .ico { stroke-width: 2.25; }
}

/* ============================================================
   Movimientos (Fase 1 · §9)
   ============================================================ */

/* ---------- Selector de tipo: tres conjuntos, no tres casillas ---------- */
.tipos-mov {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--space-2);
  margin-bottom: var(--space-4);
}
.tipo-opcion {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: var(--space-1); padding: var(--space-3) var(--space-2);
  min-height: max(var(--tactil-minimo), 4rem);
  border: 2px solid var(--border); border-radius: var(--radius-md);
  color: var(--text-secondary); text-decoration: none; text-align: center;
  font-weight: var(--fw-semibold); line-height: var(--lh-tight);
}
.tipo-opcion:hover { background: var(--surface-hover); color: var(--text); text-decoration: none; }
/* El activo no se marca solo con color: borde grueso, fondo y peso (§8.5). */
.tipo-opcion.es-activo {
  border-color: var(--brand); background: var(--brand-soft); color: var(--text);
}
.tipo-opcion:focus-visible { outline: var(--focus-width) solid var(--focus-color); outline-offset: 2px; }

/* ---------- Monto: lo más prominente de la pantalla (§9.1) ---------- */
.campo-monto { margin-bottom: var(--space-4); }
.monto-caja { display: flex; align-items: stretch; gap: var(--space-2); }
/* El «S/» va pegado al campo del monto y forma parte de su caja: con el `input`
   a `--border-strong` y el prefijo a `--border` (1,36:1) el grupo se veía roto
   por la mitad — el campo con marco y su símbolo casi sin él. Mismo token. */
.monto-simbolo {
  display: grid; place-items: center; padding-inline: var(--space-3);
  background: var(--surface-2); border: 1px solid var(--border-strong);
  border-radius: var(--radius-md); color: var(--text-secondary);
  font-weight: var(--fw-semibold); min-width: 3rem;
}
.input-monto {
  font-size: var(--fs-xl); font-weight: var(--fw-semibold);
  font-variant-numeric: tabular-nums; text-align: right;
}
/* ─── EL QUE LLEGA, ENTRE MEDIAS ────────────────────────────────────────
   Kike lo pidió «con un tamaño intermedio entre el actual y el destacado de
   Monto». Y tiene sentido más allá del gusto: en una transferencia son dos
   importes y no son iguales de importantes —lo que sale lo escribe quien
   registra; lo que llega suele venir calculado y solo se corrige— pero el que
   llega es el que acaba en la otra cuenta, así que tampoco puede quedarse
   como un campo de texto cualquiera. */
.input-monto--medio {
  font-size: var(--fs-lg); font-weight: var(--fw-semibold);
  font-variant-numeric: tabular-nums; text-align: right;
}

/* ---------- Selector de categoría, dos zonas ---------- */
.cat-zona { margin: 0 0 var(--space-2); font-weight: var(--fw-semibold); }
/* `min(9rem, 100%)` y no `9rem` a secas: el suelo va en `rem`, así que crece con
   la escala, y a «grande» son 191 px dentro de una columna de 122. Medido el 8
   de agosto de 2026 en «Registrar»: las fichas se salían de la pantalla en vez
   de estrecharse. Con `min()` el suelo nunca pide más de lo que hay — es el
   mismo fallo, y el mismo arreglo, que en la fila del monto. */
.cat-fichas {
  display: grid; gap: var(--space-2);
  grid-template-columns: repeat(auto-fill, minmax(min(9rem, 100%), 1fr));
  margin-bottom: var(--space-3);
}
.cat-ficha { position: relative; cursor: pointer; }
.cat-ficha input { position: absolute; inset: 0; opacity: 0; margin: 0; cursor: pointer; }
/* ─── EL MARCO DE UN CONTROL NO ES UN HILO ENTRE FILAS ─────────────────────
 *
 * Era `--border`, y ese token es para hairlines: 1,36:1 sobre la tarjeta. Con
 * las tarjetas ya en `--border-strong` (3,74:1) la comparación quedó al
 * descubierto — medido el 7 de agosto de 2026 en «Registrar»:
 *
 *     input · select · textarea · botón secundario · tarjeta   3,74:1
 *     ficha de categoría (el control que hay que pulsar)       1,36:1
 *
 * Y la ficha de categoría **es todo borde**: su único límite visible son estos
 * 2 px. La norma pide 3:1 para el límite de un control, y es además el control
 * de un campo obligatorio. Que tenga icono, nombre y anillo de foco no sustituye
 * a ver dónde empieza y dónde acaba la caja que se pulsa.
 *
 * `--border-strong` la deja en 3,74:1 y no toca el estado marcado, que sigue
 * siendo `--brand` con fondo y el «✓». */
.cat-ficha-cara {
  display: flex; align-items: center; gap: var(--space-2);
  padding: var(--space-3); min-height: var(--tactil-minimo);
  border: 2px solid var(--border-strong); border-radius: var(--radius-md);
  background: var(--surface-2);
}
.cat-ficha:hover .cat-ficha-cara { background: var(--surface-hover); }
.cat-ficha-nombre { overflow-wrap: break-word; line-height: var(--lh-tight); }
/* ─── `.es-actual` VA JUNTO A `:checked`, Y NO ES UN DUPLICADO ──────────────
 *
 * Las dos zonas —fichas y árbol— son **un solo grupo de radios**, así que la
 * misma categoría aparece dos veces con el mismo `name` y solo una puede estar
 * marcada. Al editar gana la última del marcado, que es la del árbol: la ficha
 * de esa misma categoría salía **sin marcar**, y el árbol vive plegado dentro
 * de «Ver todas».
 *
 * Resultado medido el 5 de agosto de 2026 abriendo un gasto con categoría
 * «Dominios»: arriba, cinco fichas y ninguna marcada — indistinguible de un
 * movimiento sin categoría, en un campo obligatorio.
 *
 * `movimiento.js` refleja la marcada en su gemela con esta clase. Sin
 * JavaScript no cambia nada respecto de antes: la marca la sigue llevando el
 * árbol, que es donde estaba. */
.cat-ficha input:checked ~ .cat-ficha-cara,
.cat-ficha-cara.es-actual {
  border-color: var(--brand); background: var(--brand-soft); font-weight: var(--fw-semibold);
}
.cat-ficha input:checked ~ .cat-ficha-cara::after,
.cat-ficha-cara.es-actual::after {
  content: "✓"; margin-left: auto; color: var(--brand); font-weight: var(--fw-bold);
}
.cat-ficha input:focus-visible ~ .cat-ficha-cara {
  outline: var(--focus-width) solid var(--focus-color); outline-offset: 2px;
}

/* `inline-flex` y no `flex`: así el área pulsable ocupa el ancho del texto y no
   toda la fila. */
.cat-arbol > summary {
  cursor: pointer; padding: var(--space-2) 0; min-height: var(--tactil-minimo);
  display: inline-flex; align-items: center; gap: var(--space-2);
  font-weight: var(--fw-semibold); color: var(--text-secondary);
}
.cat-arbol > summary:hover { color: var(--text); }
.cat-buscar { margin: var(--space-2) 0; }
.cat-lista { list-style: none; margin: 0; padding: 0; max-height: 22rem; overflow-y: auto; }
.cat-item { border-bottom: 1px solid var(--border); }
.cat-item.nivel-1 { padding-left: var(--space-4); }
.cat-item.nivel-2 { padding-left: var(--space-6); }
.cat-opcion { position: relative; display: block; cursor: pointer; }
.cat-opcion input { position: absolute; inset: 0; opacity: 0; margin: 0; cursor: pointer; }
/* SIN `flex-wrap`, y se fue con `.cat-camino`.
 *
 * Existía para que la ruta se llevara una línea entera (`flex-basis: 100%`).
 * Retirada esa clase, lo único que hacía era dejar caer el texto DEBAJO del
 * icono cuando le faltaba un pelo de ancho: medido a escala grande, a
 * «Proveedores / Reparaciones y Mantenimiento» le faltaban **0,6 px** y su fila
 * pasaba de 56 a 89 px con el texto en una sola línea. Sin envoltura, un nombre
 * largo se parte dentro de su propio hueco, que es lo que se espera. */
.cat-opcion-cara {
  display: flex; align-items: center; gap: var(--space-2);
  padding: var(--space-2) var(--space-3); min-height: var(--tactil-minimo);
}
/* ─── «SE PARTE DENTRO DE SU HUECO» NECESITA PERMISO ───────────────────────
 *
 * La nota de arriba dice que un nombre largo se parte dentro de su propio
 * hueco, y **no lo hacía**: un hijo de flex nace con `min-width: auto`, que es
 * su contenido mínimo, así que en vez de partirse empujaba. Medido el 8 de
 * agosto de 2026 a escala grande, con el panel de categorías en 122 px:
 * «Caja chica / Alimentación» medía 127 y salía 11 px fuera de la pantalla.
 *
 * `min-width: 0` le devuelve el permiso de encoger. Y `anywhere` es para la
 * palabra suelta que no cabe ni sola —«Alimentación» a esa escala—: partir una
 * palabra se lee mal, pero mandarla fuera de la pantalla no se lee. */
.cat-opcion-cara > :not(.ico):not(.rama) { min-width: 0; overflow-wrap: anywhere; }
.cat-opcion:hover .cat-opcion-cara { background: var(--surface-hover); }
/* Ver la nota de `.cat-ficha-cara.es-actual`: el reflejo va en las dos
   direcciones, porque elegir en una ficha deja el árbol sin marca. */
.cat-opcion input:checked ~ .cat-opcion-cara,
.cat-opcion-cara.es-actual {
  background: var(--brand-soft); font-weight: var(--fw-semibold);
}
.cat-opcion input:checked ~ .cat-opcion-cara::after,
.cat-opcion-cara.es-actual::after {
  content: "✓"; margin-left: auto; color: var(--brand); font-weight: var(--fw-bold);
}
.cat-opcion input:focus-visible ~ .cat-opcion-cara {
  outline: var(--focus-width) solid var(--focus-color); outline-offset: -2px;
}
/* ─── LA RAMA, APAGADA Y EN LA MISMA LÍNEA ─────────────────────────────────
 *
 * Sustituye a `.cat-camino`, que ocupaba una línea entera (`flex-basis: 100%`)
 * y por eso solo se enseñaba en los nombres repetidos: a sesenta entradas, esa
 * segunda línea doblaba la altura de la lista. En línea y delante del nombre no
 * cuesta alto ninguno.
 *
 * NO se distingue solo por color: va también a menor tamaño, y la barra `/` la
 * separa del nombre para quien no perciba el contraste de tono. */
.cat-rama { color: var(--text-muted); font-size: var(--fs-caption); font-weight: var(--fw-regular); }
/* Lo que cuesta, dicho: a escala «grande» UNA fila de 57 pasa a dos líneas
   —«Proveedores / Reparaciones y Mantenimiento»— porque el nombre ya es largo
   de por sí. Es envoltura normal de texto, no la lista entera doblando de alto,
   que es lo que la versión anterior evitaba escondiendo la rama. */
/* En la ficha la rama va ENCIMA, porque la ficha es estrecha. Se recorta con
   puntos suspensivos en vez de partir: una ficha que crece hacia abajo desalinea
   la fila entera, y la rama es contexto —el nombre completo sigue entero. */
.cat-ficha-rama {
  display: block; color: var(--text-muted); font-size: var(--fs-caption);
  line-height: var(--lh-tight); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* La cara sigue siendo UNA fila: icono al lado del texto. Lo que se apila es el
   texto —rama sobre nombre—, y por eso van dentro de su propio bloque en vez de
   sueltos: sueltos, el flex los pondría en fila y la rama saldría al lado del
   nombre en una ficha que no tiene ancho para los dos. */
.cat-ficha-texto { display: block; min-width: 0; }
/* En escala alta la sangría desaparece: el ancho es para el nombre.
 *
 * PROBÉ A METER TAMBIÉN «grande» y lo retiré: medido con la sangría original y
 * con la reducida, envuelve exactamente la misma fila —«Proveedores /
 * Reparaciones y Mantenimiento», 42 caracteres— y ninguna otra. Un cambio que
 * no cambia lo que dice arreglar no se queda. */
:root[data-escala="muy_grande"] .cat-item.nivel-1,
:root[data-escala="maximo"] .cat-item.nivel-1,
:root[data-escala="muy_grande"] .cat-item.nivel-2,
:root[data-escala="maximo"] .cat-item.nivel-2 { padding-left: var(--space-2); }

/* ---------- Lo guardado con «Guardar y continuar» ---------- */
.recien { margin-top: var(--space-4); border-top: 1px solid var(--border); padding-top: var(--space-3); }
.recien-lista { list-style: none; margin: var(--space-2) 0 0; padding: 0; display: grid; gap: var(--space-1); }
.recien-lista li {
  padding: var(--space-2) var(--space-3); background: var(--income-bg);
  border-left: 3px solid var(--income); border-radius: var(--radius-sm);
  font-size: var(--fs-caption);
}

/* ---------- Campo con error: nunca solo color ---------- */
[data-campo][data-error] .input,
[data-campo][data-error] .select { border-color: var(--expense); }
[data-campo][data-error] .error-campo {
  display: block; color: var(--expense-ink); font-weight: var(--fw-medium);
  font-size: var(--fs-caption); margin-top: var(--space-1);
}
[data-campo][data-error] .error-campo::before { content: "⚠ "; }

/* ---------- Listado agrupado por día ---------- */
/* ─── EN LÍNEA, NO APILADO ─────────────────────────────────────────────────
 *
 * Era `display: block`, y con un solo hijo daba igual. Al sacar el buscador del
 * plegado pasó a tener dos, así que «Más filtros» cayó DEBAJO del campo y la
 * tarjeta creció **50 px** — medido: el buscador arriba a 111 y el resumen a 161.
 * Kike lo vio antes que yo.
 *
 * Se le da la MISMA disposición que a `.barra-filtros` y no una propia: son la
 * misma cosa —una fila de controles de filtro— y dos formas de lo mismo divergen
 * en cuanto alguien toca una (§8.10).
 *
 * Aquí es seguro porque el panel de este plegado está posicionado en absoluto
 * (ver `.encabezado-listado .filtros-finos > .form-grid`): al abrirse flota y no
 * empuja la línea. */
.filtros-mov {
  margin-top: var(--space-4);
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-3);
}
.btn-atajo.es-activo { border-color: var(--brand); background: var(--brand-soft); color: var(--text); }
/* EL NETO, PROTAGONISTA. Entró y salió son su desglose, no sus iguales: los
   tres pesaban lo mismo y por eso ninguno destacaba. Esta es la fila que
   responde «¿cómo voy?» antes de leer una línea de la lista. */
.total-neto { display: flex; flex-direction: column; gap: var(--space-micro); }
/* ─── EL TAMAÑO ES EL DEL TOKEN, Y NO DEPENDE DE LO QUE HAYA DEBAJO ────────
 *
 * Aquí había un multiplicador, `--k-neto`, que ponía `neto-ancho.js` para que
 * la cifra **rematara donde remata su desglose**. La idea era de maquetación y
 * el efecto era que **el tamaño del número dependía del ancho de otra cosa**.
 *
 * Medido el 6 de agosto de 2026, con la misma clase y el mismo peso en las dos
 * pantallas:
 *
 *     Facturas · «Por cobrar»        57,80 px   (k = 1)
 *     Movimientos · «Neto del periodo»  32,89 px   (k = 0,569)
 *
 * La diferencia no venía de ninguna decisión: Facturas **no tiene desglose**,
 * así que el guion tomaba como referencia el propio bloque —tan ancho como la
 * cifra— y no encogía nada. Movimientos sí lo tiene, y encogía al ancho de
 * «Entró / Salió». La misma magnitud, en dos pantallas hermanas, a dos tamaños,
 * y el de Movimientos cambiaba cada vez que cambiaba el desglose de debajo.
 *
 * Kike lo pidió **estable e igual**, y eso solo se consigue quitando la
 * dependencia. La cifra vale lo que declara su token.
 *
 * **Y el freno no frenaba nada.** Su razón escrita era que «con ocho cifras el
 * número no cabe». Comprobado al tamaño declarado y en las cuatro escalas, con
 * −S/ 123.456.789,01: **cabe, no se recorta y no desborda** — el bloque es
 * `fit-content` con `max-inline-size: 100%` y la cifra lleva `overflow-wrap`,
 * así que un número largo envuelve en vez de salirse. */
.neto-cifra {
  /* La cifra principal de un widget: **un solo token para todo Flow**. */
  font-size: var(--fs-cifra-widget); font-weight: var(--fw-semibold);
  font-variant-numeric: tabular-nums; line-height: 1.1;
}
/* El negativo NO se dice solo con color (§8.5): el signo va delante del número
   y se dibuja igual en alto contraste, donde el matiz desaparece. */
.neto-cifra.es-negativo { color: var(--expense-ink); }
/* APILADOS, no en fila. Entró y salió son el desglose del neto: puestos uno
   debajo del otro a su derecha se leen como lo que son, y el bloque deja de
   estirarse a lo ancho. */
.totales-desglose { display: flex; flex-direction: column; gap: var(--space-1); min-width: 0; }
.total-neto { min-width: 0; }
/* ─── LA CIFRA NO SE PARTE, Y LA CAJA SE HACE CARGO ──────────────────────
 *
 * Aquí decía `overflow-wrap: anywhere`, que es lo que partía «S/ 4.399,91»
 * con el símbolo arriba y el número abajo. Se cambió a `nowrap` y **eso solo
 * movió el daño**: medido por el Tester, a `muy_grande` el número se salía
 * **73 px** de su tarjeta y solapaba 10 px con la de al lado, y en el diálogo
 * provocaba barra horizontal a escala normal.
 *
 * Detener el síntoma no es arreglarlo. La regla de Kike era la otra: **si no
 * cabe, el problema es el ancho de la caja**. Así que la cifra no envuelve
 * —eso se queda— y quien cede es la rejilla: ver `.tercios--tres`. */
/* ─── UN IMPORTE NO SE PARTE, Y ESO SE QUEDA ──────────────────────────────
 *
 * Probé a dejarlo envolver para que la frase de un widget de Gastos fijos —«4
 * fuera de plazo»— cupiera en su tarjeta. **Mal**: `S/ 1.409,05` lleva un
 * espacio, así que al permitir el salto el símbolo se iba arriba y el número
 * abajo — el defecto que este `nowrap` existe para evitar, medido el 7 de
 * agosto. *Un importe roto en dos se lee dos veces.*
 *
 * Así que se queda como estaba, y el desbordamiento de esa frase se dice en el
 * informe: no es un importe, es un widget que puso una frase donde va una
 * cifra, y eso se arregla en Gastos fijos. */
.neto-cifra { overflow-wrap: normal; }
/* `flex-wrap` porque el rótulo y su importe son dos piezas que no encogen: sin
   él, «Entró S/ 23.227,xx» pedía 368,4 px dentro de una tarjeta de 332 y se
   salía 18,3 px por el borde a máximo@1165 (medido el 12 de agosto de 2026).
   Con él, el importe baja de renglón en esa combinación y en ninguna otra. */
.totales-desglose .total-bloque { display: flex; align-items: baseline; gap: var(--space-2); flex-wrap: wrap; }
/* La etiqueta con ancho fijo para que las dos cifras formen columna: el mismo
   criterio que las columnas de la lista, a otra escala. */
.totales-desglose .caption { min-width: 5ch; }
/* Y el desglose en segundo nivel: mismo dato, menos peso. */
.totales-desglose .amount { font-size: var(--fs-md); }

.totales-mov {
  display: flex; flex-wrap: wrap; gap: var(--space-4);
  margin-top: var(--space-4); padding-top: var(--space-3);
  border-top: 1px solid var(--border); align-items: baseline;
}
.total-bloque { display: grid; gap: var(--space-micro); }
.total-bloque .amount { font-size: var(--fs-md); }
.total-nota { flex-basis: 100%; margin: 0; }

.bloque-dia { margin-top: var(--space-4); }
.dia-cabecera {
  display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-4);
  align-items: baseline; justify-content: space-between;
  padding: var(--space-3) var(--space-4);
  background: var(--bg-sunken); border-bottom: 1px solid var(--border);
}
.dia-titulo { font-size: var(--fs-md); margin: 0; display: flex; gap: var(--space-2); align-items: baseline; flex-wrap: wrap; }
/* ─── UNO POR LÍNEA, Y LOS IMPORTES EN COLUMNA ───────────────────────────
   Kike, 6 de agosto de 2026: «Entró: XXX Salió: XXX» en dos líneas y con los
   montos alineados entre sí. En una sola fila los tres importes empiezan
   donde acabe su rótulo, así que no hay forma de compararlos de un vistazo —
   que es justo para lo que están: un día de 6.000 y 5.800 no es uno de 200.
   Rejilla de dos columnas: el rótulo a la izquierda, el número a la derecha
   con `tabular-nums`, de modo que las unidades caen unas sobre otras.
   `max-content` en la primera columna para que la rejilla no se coma la fila. */
.dia-numeros {
  display: grid; grid-template-columns: max-content auto;
  gap: var(--space-1) var(--space-3); justify-content: end;
}
.dia-numeros > .caption { display: contents; }
.dia-numeros .amount { text-align: right; font-variant-numeric: tabular-nums; }
/* DOS LÍNEAS, NO UNA, y está medido: los tres números piden 354 px y en un
   teléfono de 371 hay 273. Una línea solo se consigue quitando uno de los
   tres, y eso es contenido — la cabecera de este archivo explica por qué son
   tres: un día con 6.000 de ingreso y 5.800 de gasto no es lo mismo que uno
   con 200, aunque el neto coincida.
   Con el hueco de escritorio eran TRES líneas y 113 px; con el hueco corto son
   dos y 53. `nowrap` en cada número para que ninguno se parta por dentro. */
@media (max-width: 720px) {
  .dia-numeros {
    flex-wrap: wrap; gap: 0 var(--space-3); min-width: 0;
    font-variant-numeric: tabular-nums;
  }
  .dia-numeros > * { white-space: nowrap; }
  .dia-cabecera { padding: var(--space-2) var(--space-3); }
}
.mov-nota { display: block; }

.zona-borrar .caption { max-width: 44ch; }

/* ---------- Acciones de fila con iconos (2.1) ---------- */
/* DOS COLUMNAS INDEPENDIENTES. Antes era una rejilla que ataba las filas: cada
   una se estiraba al campo más alto de las dos columnas y sobraban 230 px de
   hueco. Ahora cada columna fluye por su cuenta y solo comparten el ancho. */
.rejilla-mov { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-5); align-items: start; }
.col-mov { display: flex; flex-direction: column; gap: var(--space-4); min-width: 0; }
/* La categoría es el control alto: segunda columna, estirada, para que cuenta y
   nota se acomoden a su izquierda en lugar de empujarla hacia abajo. */
/* `div.zona-categoria` es el ÁRBOL; en transferencia esa zona es un párrafo de
   una línea —la categoría la pone el motor— y no debe ocupar tres filas ni
   reservar alto. Estirarlo repartía 97 px de aire entre cada campo de la primera
   columna, que es de dónde salían los huecos entre «Sale de» y «Entra a». */
/* Sin colocación explícita: cada campo vive en la columna que le corresponde en
   el marcado, y el criterio —lo que se lee junto va junto— queda a la vista al
   leer la plantilla en vez de escondido en dos reglas de rejilla. */
div.zona-categoria { min-width: 0; }

/* Un importe de cuatro o cinco dígitos no necesita el ancho de la pantalla:
   prominente es grande en tipografía. */
.campo-monto .monto-caja { max-width: 22rem; }
/* El tamaño lo fija el bloque de densidad de más abajo, con un token que
   existe: aquí había `--fs-2xl`, que no está definido, así que el número
   heredaba el tamaño del texto normal. De ahí que «prominente» no se viera. */

/* Las acciones, siempre a la vista. */
.acciones-mov {
  position: sticky; bottom: 0; z-index: 2;
  padding: var(--space-3) 0; margin-top: var(--space-2);
  background: var(--surface); border-top: 1px solid var(--border);
}

@media (max-width: 720px) {
  /* En móvil vuelve a una columna: dos columnas en 375 px recortan las etiquetas. */
  .rejilla-mov { grid-template-columns: 1fr; }
  .rejilla-mov .zona-categoria,

  .campo-monto .monto-caja { max-width: none; }
}

/* ---------- Diálogo de registro sobre el listado (§9.1) ---------- */
/* No ocupa el ancho completo: el formulario está en dos columnas y no lo necesita,
   y dejando aire al costado el bloque del día de atrás queda a la vista — que es
   justamente la confirmación de que se guardó. */
/* ALTURA FIJA, NO MÍNIMA. Un mínimo iguala por abajo y deja crecer por arriba:
   transferencia se quedaba en el suelo —no tiene árbol de categorías— y gasto e
   ingreso lo superaban. Y con el diálogo centrado se movían los DOS bordes, así
   que el salto se notaba el doble.

   Con alto fijo el diálogo mide igual siempre y lo que varía es cuánto se
   desplaza por dentro. Acotado por la ventana para que en pantallas cortas no se
   salga. */
/* ┌──────────────────────────────────────────────────────────────────────────┐
   │ EL `display` VA SOLO CUANDO ESTÁ ABIERTO. NO LO QUITES DE `[open]`.      │
   │                                                                          │
   │ Un `<dialog>` cerrado se oculta con `dialog:not([open]) { display:none }`│
   │ del NAVEGADOR. Una regla de autor lo pisa —los estilos de autor ganan a  │
   │ los del navegador POR ORIGEN, no por especificidad—, así que declarar    │
   │ `display: flex` sin condición deja el diálogo CERRADO pero VISIBLE.      │
   │                                                                          │
   │ Eso produjo el «diálogo fantasma»: un marco vacío encima de todo, sin    │
   │ backdrop y del que no se podía salir —la equis llama a `close()` y       │
   │ cerrar algo ya cerrado no hace nada—. Costó cuatro turnos porque el      │
   │ elemento nunca estuvo abierto: los arreglos iban al camino de apertura.  │
   └──────────────────────────────────────────────────────────────────────────┘ */
/* EL ALTO FIJO ES DEL FORMULARIO DE MOVIMIENTO, no de todos los diálogos.
   Allí existe para que la barra de botones no cambie de sitio entre gasto,
   ingreso y transferencia —tres formularios de alto distinto en el mismo
   diálogo—: el control que más se usa tiene que estar siempre donde la mano
   lo espera.

   Un diálogo con UN SOLO formulario no tiene nada que estabilizar, y el alto
   fijo le deja 367 px vacíos bajo los botones. Ésos usan `--ajustado`. */
/* ═══ UNA VENTANA CON UN CONTENEDOR DENTRO NECESITA ANCHO PROPIO ═══════════
 *
 * Kike, 28/08/2026: *«al editar y al visionar los datos de la factura desde
 * Billing, y en ambas al darle clic a anular aparece la ventana deforme»*, con
 * el texto en vertical, una letra por renglón.
 *
 * ─── LO QUE SE MIDIÓ, ABRIÉNDOLA ──────────────────────────────────────────
 *
 * `#dlg-anular` medía **52,6 px de ancho** y **cada campo, 0**. Y las dos puntas
 * del A/B lo cierran: quitándole el `container-type` al formulario → 578 px;
 * dándole al diálogo un ancho explícito → 578 px; devolviendo las dos → 52,6.
 *
 * ─── LA CAUSA ─────────────────────────────────────────────────────────────
 *
 * `form.form-etiquetado` declara `container-type: inline-size`. Eso trae
 * contención de tamaño en el eje en línea: **su ancho intrínseco cuenta como
 * cero** para quien lo contiene. Un `<dialog>` sin ancho propio se dimensiona
 * ajustándose a su contenido, y ese contenido dice cero. Los 52,6 px que
 * quedaban son el resto —la barra de botones, que no está contenida—.
 *
 * ─── Y NO ERA EL TERCER NIVEL, AUNQUE LO PARECÍA ──────────────────────────
 *
 * La sospecha razonable era que el arreglo de «crear cliente» cubrió el segundo
 * nivel y no el tercero. **Medido, es falsa**: el mismo diálogo mide 52,6 px
 * abierto solo en primer nivel, y 53 desde la pantalla de editar, donde su botón
 * no está dentro de ninguna ventana. No depende de dónde se abra. Depende de que
 * lo de dentro sea un contenedor de consulta.
 *
 * ─── POR LA FORMA, NO POR LISTA ───────────────────────────────────────────
 *
 * `:has()` pregunta por la forma: *un diálogo que contiene un formulario
 * etiquetado*. El que se escriba mañana con esa forma entra solo. Y el ancho es
 * el mismo `34rem` que `.dialogo` ya tenía de techo, así que no cambia nada de
 * lo que hoy ya llega a su techo: solo impide caer por debajo.
 *
 * `:not(.dialogo--formulario)` para no pelearse con el que ya trae su ancho. */
.dialogo:not(.dialogo--formulario):has(form.form-etiquetado) {
  inline-size: min(34rem, 92vw);
}

.dialogo--formulario {
  width: min(60rem, 92vw); max-width: 92vw;
  height: min(56rem, 90vh);
  padding: 0; overflow: hidden;          /* el que se desplaza es el cuerpo */
}
.dialogo--formulario[open] { display: flex; flex-direction: column; }
/* Se ajusta a su contenido, con techo para que no se salga de la pantalla. */
/* `fit-content`, NO `auto`. Con `auto` el diálogo modal se estiraba al techo
   —1188 px para 584 de contenido— y medirlo fue la única forma de verlo: la
   regla se aplicaba, la suma de sus hijos daba 584, y aun así medía el máximo.
   `fit-content` da 586.

   PENDIENTE DE ENTENDER, no resuelto. Por qué un `<dialog>` modal con
   `height: auto` se estira y con `fit-content` no, no lo sabemos: la medida
   basta hoy y el comportamiento no está explicado. Quien vuelva a tocar este
   diálogo tiene que saber que se apoya en algo sin explicación, y que
   cambiarlo exige volver a medir en vez de razonar. */
.dialogo--formulario.dialogo--ajustado { height: fit-content; max-height: 90vh; }
/* Y el CUERPO no se estira: con `flex: 1 1 auto` crecía hasta el techo del
   diálogo aunque su tarjeta midiera la mitad — el hueco no estaba bajo el
   formulario, estaba dentro del cuerpo. Se desplaza igual si hace falta,
   porque conserva su `overflow`. */
.dialogo--formulario.dialogo--ajustado .dlg-cuerpo { flex: 0 1 auto; }
.dialogo--formulario.dialogo--ajustado .dlg-cuerpo > .card { min-height: 0; }
/* Encabezado fijo arriba, fuera de la zona que se desplaza. */
.dialogo--formulario .dlg-barra {
  flex: 0 0 auto; z-index: 3;
  display: flex; justify-content: flex-end;
  padding: var(--space-2) var(--space-3) 0;
  background: var(--surface);
}
/* El cuerpo es lo único que se desplaza. */
.dialogo--formulario .dlg-cuerpo {
  flex: 1 1 auto; min-height: 0; overflow-y: auto;
  padding: 0 var(--space-4);
}
/* La tarjeta de dentro ya no necesita su propio marco: el diálogo es el marco. */
.dialogo--formulario .dlg-cuerpo > .card { border: 0; box-shadow: none; padding: 0; }
.dialogo--formulario::backdrop { background: rgb(0 0 0 / 0.55); }

@media (max-width: 720px) {
  /* No debería abrirse en móvil, pero si un cambio futuro lo permite, que al
     menos no quede una ventanita ilegible. */
  .dialogo--formulario { width: 100vw; max-width: 100vw; max-height: 100vh; border-radius: 0; }
}

/* La falta de configuración va arriba de todo y con espacio propio: es lo primero
   que hay que resolver, no una nota al pie. */
.config-minima { display: flex; flex-direction: column; gap: var(--space-2); }
.config-minima .aviso { display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap; }
.config-minima .aviso a { flex: 0 0 auto; margin-inline-start: auto; }
.btn--pequeno { padding: var(--space-2) var(--space-3); min-height: var(--tactil-minimo); }

/* ---------- Densidad del formulario de captura (§8.1) ---------- */
/* CADA CAMPO MIDE SEGÚN LO QUE RECIBE. El ancho sobrante no es neutro: separa los
   campos entre sí y obliga a recorrer la pantalla con la vista para leer una
   fila. Una tasa de cambio son cuatro caracteres; una fecha, diez. */
@media (min-width: 721px) {
  /* EL MONTO ES EL ELEMENTO MÁS GRANDE DE LA PANTALLA. Es el único dato que se
     mira antes de guardar, y «prominente» es tipografía grande — no solo campo
     angosto, que fue la lectura corta de la vez pasada. */
  .input-monto {
    /* EL CRITERIO NO ES UN TAMAÑO, ES QUE QUEPA EL MÁXIMO Y SIGA DOMINANDO.
       Con `--fs-display` el número medía 63 px y se cortaba desde CUATRO dígitos:
       999,999.99 pedía 386 px en una caja de 245. Un importe que no se puede leer
       entero no es prominente, es inservible.
       A ~44 px sigue siendo dos veces y media el cuerpo —18 px— y domina igual,
       con sitio para el máximo. Medido escribiendo el número, no calculando. */
    font-size: var(--fs-monto);
    font-weight: var(--fw-bold);
    line-height: var(--lh-tight);
    padding-block: var(--space-2);
  }
  /* En escritorio el principal sube a ~44 px; el que llega se queda a mitad
     de camino entre el cuerpo del formulario y ese, para que se lea la
     jerarquía sin que ninguno de los dos parezca un campo secundario. */
  .input-monto--medio { font-size: 1.8rem; }
  .campo-monto .monto-caja { max-width: 24rem; }
  .campo-monto .monto-simbolo { font-size: var(--fs-lg); align-self: center; }

  /* Anchos por contenido. `ch` mide en caracteres, que es exactamente el criterio. */
  .rejilla-mov [data-campo="fecha"] .input        { max-width: 11rem; }
  .rejilla-mov [data-campo="monto_destino"] .monto-caja { max-width: 14rem; }
  .rejilla-mov [data-campo="cuenta_id"] .select,
  .rejilla-mov [data-campo="cuenta_destino_id"] .select { max-width: 24rem; }
}

/* La nota ocupa el ancho que le queda: es el único campo de texto libre.
   Mide dos filas, no tres: se bajó de 5.5rem para dejar MARGEN, no solo para
   caber (§8.1). */
.input-nota { min-height: 4rem; resize: vertical; line-height: 1.45; }

/* En móvil todo vuelve a ancho completo: ahí el problema es el contrario. */
@media (max-width: 720px) {
  .rejilla-mov .input, .rejilla-mov .select, .campo-monto .monto-caja { max-width: none; }
  /* En móvil el ancho disponible manda: se baja un escalón para que
     999,999.99 quepa también en 375 px. */
  .input-monto { font-size: var(--fs-monto-movil); font-weight: var(--fw-bold); }
}

/* ---------- Filtros finos, plegados (§8.1) ---------- */
.filtros-finos > .form-grid { margin-top: var(--space-3); }
.filtros-resumen {
  display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap;
  cursor: pointer; padding: var(--space-2) 0;
  font-weight: var(--fw-semibold);
  min-height: var(--tactil-minimo);
}
.filtros-resumen::marker { color: var(--text-muted); }
.filtros-resumen:focus-visible { outline: var(--focus-width) solid var(--focus-color); outline-offset: 2px; }

/* ---------- Altura estable del formulario de captura (§8.1) ---------- */
/* LA CAJA NO CAMBIA DE ALTO. Gasto e Ingreso medían 738 px y Transferencia 833:
   95 px de salto al cambiar de tipo. Y abrir el árbol de categorías sumaba otros
   290. En el diálogo cada salto mueve la ventana y el ojo pierde el sitio; en la
   pantalla completa mueve los botones de guardar.

   Se resuelve reservando: un mínimo que cubre al más alto de los tres, y una zona
   de categorías con alto propio para que su árbol se desplace dentro en lugar de
   empujar lo de abajo. */
@media (min-width: 721px) {
  /* 53rem cubre al más alto de los tres —transferencia, 897 px— con margen.
     Medido, no estimado: con 52rem quedaban 13 px de salto. */
  /* SIN mínimo en el formulario. Lo tuvo, y era el enfoque equivocado: igualaba
     por abajo pero dejaba crecer por arriba, que es justo lo que hacía saltar el
     diálogo. Ahora el tamaño lo fija el diálogo y el contenido se desplaza dentro;
     en pantalla completa la página se desplaza como cualquier otra. */

  .rejilla-mov div.zona-categoria {
    /* El alto se reserva esté el árbol abierto o plegado: plegarlo no mueve nada.
       Solo el `div`, que es el que lleva el árbol. */
    min-height: 30rem;
    display: flex; flex-direction: column;
  }
  /* La lista CONSERVA su tope y su desplazamiento propio: quitárselo la dejó
     crecer a 3285 px con 58 categorías, que es el problema al revés. Lo que
     hace la zona es RESERVAR el hueco para que plegar o desplegar no mueva
     nada de lo que hay debajo. */
  .cat-arbol { display: flex; flex-direction: column; min-height: 0; }
  .cat-lista { max-height: 22rem; overflow-y: auto; }
}

/* ---------- El sobrante va al final, no entre los campos (§8.1) ---------- */
/* RESERVAR ESPACIO ES ESPACIO INVISIBLE, NO HUECOS ENTRE CAMPOS. El formulario
   tiene un alto mínimo para que la caja no salte al cambiar de tipo, y la rejilla
   estaba repartiendo ese sobrante entre sus filas: 97 px de aire entre «Sale de»
   y «Entra a», y la nota empujada a la otra columna.

   `align-content: start` deja las filas juntas y acumula el sobrante al final,
   que es donde no molesta. Cada tipo se ve compacto; lo único constante es el
   alto total. */
@media (min-width: 721px) {


  /* Y las acciones se anclan al pie del diálogo, no al final del contenido: es el
     control que más se usa y no debe cambiar de sitio según el tipo. Medido por la
     posición del botón, no por el alto del contenedor — el contenedor medía igual
     en los tres y el botón se movía 23 px igualmente. */
  #form-movimiento { display: flex; flex-direction: column; }
  #form-movimiento .acciones-mov { margin-top: auto; }
}

/* ---------- Borrar, al extremo opuesto de la fila (§8.7) ---------- */
/* Ni junto a Guardar —donde se toca por inercia— ni en una sección aparte al
   final —donde había que desplazar para encontrarlo—. Al otro extremo de la
   misma fila: se alcanza sin buscar y no se pulsa sin querer.
   La consecuencia se lee en el diálogo, no en pantalla permanente. */
.acciones-mov { align-items: center; }
.acciones-mov .spacer { flex: 1 1 auto; }
.btn--borrar-extremo { margin-inline-start: var(--space-5); }

@media (max-width: 720px) {
  /* En móvil la fila se parte y «al otro extremo» deja de significar nada:
     se separa con una línea para que siga sin quedar pegado a Guardar. */
  .btn--borrar-extremo {
    margin-inline-start: 0; margin-top: var(--space-4);
    width: 100%; border-top: 1px solid var(--border); padding-top: var(--space-4);
  }
}

/* ---------- Acciones de fila con iconos (2.1) ---------- */
/* ─── NO SE MONTABAN: SE PARTÍAN EN DOS LÍNEAS ────────────────────────────
   Medido el 6 de agosto de 2026 en los diez catálogos con flechas y en las
   cuatro escalas: **cero solapes**. Lo que pasa es que la columna envuelve y
   el corte cae entre «bajar» y el lápiz, que quedan uno **exactamente encima**
   del otro —misma `x`, mismo ancho— con **4,25 px** de aire. Apilado se lee
   como montado, y con razón: nada dice que sean cosas distintas.

   Se ataca por donde está: que quepan en una línea. Las flechas encogen a la
   vista —son las menos usadas— y **el área de toque se mantiene en 44 px con
   un pseudoelemento**, como ya se hizo con la marca de incompleto: la zona
   pulsable crece sin empujar a nadie. El tamaño visual puede bajar; el
   objetivo táctil no (§8.5). */
/* `nowrap`: envolver es lo que ponía la flecha de bajar justo encima del
   lápiz. La columna pedía 212 px y tenía 187 —dos de sus seis controles son
   huecos de alineación—, así que partía. En una tabla, una columna que no
   envuelve pide su ancho y se lo quita a las de texto, que sí pueden ceder. */
.acciones-fila {
  display: flex; gap: var(--space-1); flex-wrap: nowrap;
  justify-content: flex-end; align-items: center;
}
/* Y que NO encojan: con `nowrap` y `flex-shrink` por defecto, la flecha se
   aplastaba a **2 px** de ancho en vez de partir la línea. Un control que se
   estruja hasta desaparecer es peor que uno que envuelve. */
.acciones-fila > * { flex: 0 0 auto; }

/* La raya que separa el orden de lo que actúa sobre la fila. */
.acciones-raya {
  inline-size: 1px; block-size: 1.5em; background: var(--border);
  margin-inline: var(--space-1); flex: 0 0 auto;
}
[data-contrast="high"] .acciones-raya { background: var(--border-strong); inline-size: 2px; }

/* ─── LAS FLECHAS: MÁS PEQUEÑAS A LA VISTA, IGUAL DE PULSABLES ───────────── */
/* El dibujo NO escala: su tamaño es jerarquía, no texto, y con `rem` acababa
   midiendo casi lo mismo que el lápiz a escala máxima —60 contra 63— con lo
   que la distinción se perdía justo donde más sitio hace falta. El icono de
   dentro sí escala, y por eso el mínimo del hueco lo acompaña. */
.acciones-fila .js-mover {
  min-inline-size: 0; min-block-size: 0;
  inline-size: 28px; block-size: 28px; padding: 0;
  position: relative;
}
/* El pseudoelemento lleva la zona pulsable a 44 px sin ocupar sitio en la
   maquetación: es lo que permite encoger el dibujo sin tocar §8.5. */
.acciones-fila .js-mover::after {
  content: ''; position: absolute; inset: 50% auto auto 50%;
  inline-size: var(--tactil-minimo); block-size: var(--tactil-minimo);
  transform: translate(-50%, -50%);
}
/* El objetivo táctil se mantiene aunque el contenido sea un icono de 1em. */
/* El mínimo táctil se subió a `.btn--icon` en base.css: lo cumplía aquí y en la
   cabecera de los diálogos, y no en el lápiz de Movimientos. */

/* ---------- Selector de icono colapsado (2.4) ---------- */
.selector-icono > .icono-resumen {
  cursor: pointer; list-style: none;
  display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--border); border-radius: var(--radius-md);
  background: var(--surface-2); min-height: var(--tactil-minimo);
}
.selector-icono > .icono-resumen::-webkit-details-marker { display: none; }
.selector-icono > .icono-resumen:hover { background: var(--surface-hover); }
.selector-icono > .icono-resumen:focus-visible {
  outline: var(--focus-width) solid var(--focus-color); outline-offset: 2px;
}
.icono-actual { display: inline-flex; align-items: center; gap: var(--space-2); }
.icono-cambiar { margin-left: auto; pointer-events: none; }
.selector-icono[open] > .icono-resumen { border-bottom-left-radius: 0; border-bottom-right-radius: 0; }
/* La parrilla abierta CRECE hacia abajo. Tenía `max-height` con scroll propio y
   eso es un scroll dentro de otro: incómodo con ratón y peor con teclado. Ya está
   colapsada por defecto, así que su alto no compite con nada, y si queda larga la
   salida es el buscador que lleva dentro, no encerrarla. */
.selector-icono[open] > div[role="radiogroup"] {
  border: 1px solid var(--border); border-top: 0;
  border-radius: 0 0 var(--radius-md) var(--radius-md);
  padding: var(--space-3);
}
.icono-buscar { margin-bottom: var(--space-3); }
.icono-grupo-bloque + .icono-grupo-bloque { margin-top: var(--space-3); }

/* ---------- Predeterminado desde el listado (2.5) ---------- */
.js-predeterminado { color: var(--text-muted); }
.js-predeterminado:hover { color: var(--text); border-color: var(--brand); }

/* ---------- Campo de una sola línea (casillas) ----------
   Una casilla con su texto al lado no necesita rótulo encima ni ayuda debajo: eran
   cuatro líneas para un sí o un no. El dato que la ayuda daba —«hay una por
   tipo»— se movió al aviso que aparece al marcarla, donde importa. */
/* `flex-direction: row` es imprescindible, no decorativo: `.field` es una columna
   flex, y sin resetear la dirección el `align-items: center` actúa sobre el eje
   TRANSVERSAL —que en una columna es el horizontal— y la casilla salía centrada en
   medio del formulario en vez de alineada con los demás campos.
   Detectado midiendo: el `justify-content` no hacía nada porque operaba sobre el
   eje vertical, y un `margin-right: auto` sí la movía. Eso delató la dirección. */
.field--linea { display: flex; flex-direction: row; align-items: center; min-height: var(--tactil-minimo); }
.field--linea .interruptor { margin: 0; }

/* ---------- Estrella de predeterminado ----------
   ATENUADA: se repite en decenas de filas para una acción que se usa dos veces al
   año. Disponible sin pesar. Al pasar el cursor y al enfocar sube a tinta normal,
   así que no se pierde: solo deja de competir. */
.btn--tenue { color: var(--text-muted); border-color: transparent; }
.btn--tenue:hover { color: var(--text); border-color: var(--border-strong); background: var(--surface-hover); }
.btn--tenue:focus-visible { color: var(--text); }

/* La etiqueta de estado, junto al nombre. */
.marca-predet {
  display: inline-flex; align-items: center; gap: var(--space-1);
  color: var(--warning-ink); font-size: var(--fs-caption); font-weight: var(--fw-semibold);
  white-space: nowrap;
}

/* NOTA sobre las flechas de reordenar: se consideró moverlas al margen izquierdo,
   que es la convención del asa de arrastre. Se descarta por ahora — partiría las
   acciones en dos lugares, y el margen izquierdo ya lo ocupan la sangría del árbol
   y el icono de la categoría. Es el lugar natural del asa SI algún día se agrega
   arrastrar y soltar. */
/* Hueco que reserva la posición de una acción ausente, para que la columna cuadre. */
/* ─── EL HUECO MEDÍA 44 FIJOS MIENTRAS EL BOTÓN CRECÍA ────────────────────
   Existe para que la primera y la última fila no bailen —ahí falta una
   flecha— y con la letra grande dejaba de cumplir su función: el desfase
   llegaba a **19,36 px** a escala máxima, así que la flecha de subir de la
   última fila quedaba casi veinte píxeles a la derecha de las demás. El
   zigzag que el hueco venía a evitar, causado por el hueco. Medido en
   `/admin/cuentas`. Ahora mide lo mismo que la flecha que sustituye. */
.accion-hueco { display: inline-block; inline-size: 28px; block-size: 28px; flex: 0 0 auto; }

/* ---------------------------------------------------------------- adjuntos */
/* Miniatura fija para que la lista no baile según la proporción de cada foto:
   una columna de alturas distintas obliga a rastrear el nombre con la vista. */
.lista-adjuntos { list-style: none; display: flex; flex-direction: column; gap: var(--space-3); }
.adjunto-fila {
  display: flex; align-items: flex-start; gap: var(--space-3);
  padding: var(--space-3); border: 1px solid var(--border); border-radius: var(--radius-md);
  background: var(--surface-2);
}
.adjunto-vista {
  flex: 0 0 auto; width: 4.5rem; height: 4.5rem; overflow: hidden;
  border-radius: var(--radius-sm); background: var(--surface-hover);
  display: flex; align-items: center; justify-content: center;
}
.adjunto-vista img { width: 100%; height: 100%; object-fit: cover; }
.adjunto-icono { font-size: 2rem; color: var(--text-secondary); }

/* ─── EL ENLACE DEL COMPROBANTE, CON SUS 44 px ──────────────────────────────
 *
 * Medido al construir el archivo de tributos: el enlace salía `display:inline`
 * y **21 px de alto**, dentro de una celda de 55. La celda no es el área
 * táctil — lo que se pulsa es el enlace—, así que eran 21 px para el requisito
 * de 44. Con `inline-flex` y el mínimo puesto, el área es la que dice la regla
 * sin que la fila crezca: 44 cabe en los 55 que la celda ya medía. */
.enlace-comprobante { display: inline-flex; align-items: center; gap: var(--space-1);
                      min-height: 44px; }
/* ─── SU SEÑAL NO PUEDE SER SOLO EL COLOR ───────────────────────────────────
 *
 * Medido en las cuatro combinaciones: 5,59 · 4,62 · 5,17 · 5,17. Los cuatro
 * pasan el 4,5, y el más bajo —oscuro alto contraste— **ya estaba decidido y
 * medido**: `design-tokens.css` excluye esa combinación a propósito y lo dice
 * con estas palabras, «Kike usa alto contraste, así que ese es el suyo y no se
 * toca». Así que **el color no se toca aquí tampoco**: una pantalla nueva no
 * revoca una decisión tomada para todas.
 *
 * Lo que sí falta es la otra señal. Un enlace que solo se distingue por su tono
 * incumple el requisito permanente, y en una tabla de 1.432 filas la columna
 * entera se vuelve un bloque de azul. Subrayado: se ve sin ver el matiz, y en
 * alto contraste engorda, que es la forma de subir sin tocar el color. */
.enlace-comprobante { text-decoration: underline; }
[data-contrast="high"] .enlace-comprobante { text-decoration-thickness: 2px; }

/* ─── LAS CUATRO COLUMNAS DEL ARCHIVO DE TRIBUTOS ───────────────────────────
 *
 * Cada una con su clase y su regla, que es lo que pide §8.57: una clase que no
 * está en ninguna hoja no pinta, y una columna sin clase no se puede medir ni
 * por una prueba ni por una hoja — es lo que dejó dos comprobaciones midiendo
 * sobre cero celdas en el listado de facturas.
 *
 * El periodo y el importe **no se parten**: son un dato de ancho fijo, y la
 * regla es que lo de largo libre cede y una cifra o una fecha nunca. El que
 * cede es el tributo, que es el único texto largo de la fila. */
.tabla-archivo .col-periodo     { width: 11ch; white-space: nowrap; }
.tabla-archivo .col-tributo     { max-width: 34ch; overflow: hidden;
                                  text-overflow: ellipsis; white-space: nowrap; }
.tabla-archivo .col-comprobante { white-space: nowrap; }

/* ─── Y A TODO EL ANCHO DE LA TARJETA ───────────────────────────────────────
 *
 * Kike: *«que además respete el ancho de la tarjeta para aprovechar espacio»*,
 * como ya se hizo con la tabla de impuestos por pagar. Misma pieza que allí:
 * `width: 100%` sobre la tabla, y la envoltura conserva su `overflow-x`, que es
 * la válvula de §16 — a anchos pequeños la tabla se desplaza dentro de su
 * tarjeta en vez de romper la pantalla.
 *
 * El comprobante a la derecha, que es donde acaba la fila y donde está la
 * acción: la misma colocación que la última columna de impuestos por pagar. */
.tabla-archivo { width: 100%; }

/* ─── LA TABLA DE AUTODETRACCIÓN ────────────────────────────────────────────
 * Al ancho de su tarjeta, como la del archivo y la de impuestos por pagar. El
 * cliente cede —es el único texto largo— y las cifras y el plazo no se parten. */
.tabla-autodetraccion { width: 100%; }
.tabla-autodetraccion .col-cliente { max-width: 26ch; overflow: hidden;
                                     text-overflow: ellipsis; white-space: nowrap; }
.tabla-autodetraccion .col-monto,
.tabla-autodetraccion .col-fecha { white-space: nowrap; }
.tabla-archivo th.col-comprobante,
.tabla-archivo td.col-comprobante { text-align: end; }

/* ─── EL PIE DEL ARCHIVO · UN TOTAL QUE NO SE LEE COMO UN SALDO ─────────────
 *
 * La cifra manda y por eso crece —es la respuesta a «cuánto suma esto»—, pero
 * va **con su rótulo en palabras delante**: sin él, una cifra grande al pie de
 * una tabla se lee como un saldo, y esto son tributos ya pagados. *Las bolsas
 * no se suman.*
 *
 * `flex-wrap` y no `nowrap`: a escala máxima el rótulo y la cifra no caben en
 * una línea, y **primero crece la caja** — la cifra no se encoge jamás. Al
 * envolver, cada trozo cae entero en su renglón, que no es envolver palabra por
 * palabra. */
.pie-archivo { border-block-start: 2px solid var(--border-strong);
               margin-block-start: var(--space-3); padding-block-start: var(--space-3); }
.pie-archivo__total { display: flex; flex-wrap: wrap; align-items: baseline;
                      gap: var(--space-2); margin: 0; }
.pie-archivo__rotulo { font-weight: 600; }
.pie-archivo__cifra { font-size: var(--fs-lg); font-weight: 700;
                      font-variant-numeric: tabular-nums; white-space: nowrap; }
.pie-archivo__sobre { color: var(--text-secondary); }
[data-contrast="high"] .pie-archivo { border-block-start-width: 3px; }
.adjunto-datos { flex: 1 1 auto; display: flex; flex-direction: column; gap: var(--space-1); min-width: 0; }
.adjunto-datos > a { word-break: break-word; }
.aviso--linea { padding: var(--space-2) var(--space-3); margin-top: var(--space-1); }
.estado-vacio-linea { color: var(--text-secondary); padding: var(--space-3) 0; }

/* Bajo el punto de quiebre la fila se apila: a 375 px una miniatura, un nombre
   largo y un botón no caben en una línea sin recortar el nombre. */
@media (max-width: 720px) {
  .adjunto-fila { flex-wrap: wrap; }
  .adjunto-datos { flex-basis: calc(100% - 6rem); }
}

/* ---------- Formulario de movimiento: densidad (§8.7 «cada elemento se gana su espacio») ---------- */

/* El tipo, como control segmentado. Tres tarjetas de 4 rem para elegir entre tres
   opciones ocupaban como un bloque de contenido; esto hace lo mismo en un tercio
   del alto, con el icono en línea en vez de encima de la palabra. */
.tipos-mov.segmentado {
  display: inline-flex; gap: 0; margin-bottom: var(--space-4);
  border: 1px solid var(--border); border-radius: var(--radius-md); overflow: hidden;
  max-width: 100%;
}
.tipos-mov.segmentado .tipo-opcion {
  flex: 1 1 auto; flex-direction: row; gap: var(--space-2);
  min-height: var(--tactil-minimo); padding: var(--space-2) var(--space-4);
  border: 0; border-radius: 0; white-space: nowrap;
}
/* El separador va entre segmentos, no alrededor: un borde por opción sumaba
   grosores y desalineaba el conjunto. */
.tipos-mov.segmentado .tipo-opcion + .tipo-opcion { border-inline-start: 1px solid var(--border); }
/* El activo se distingue por fondo Y peso Y marca de estado, no solo por color. */
.tipos-mov.segmentado .tipo-opcion.es-activo {
  background: var(--brand-soft); color: var(--text); font-weight: var(--fw-bold);
  box-shadow: inset 0 -3px 0 var(--brand);
}

/* Dos columnas en escritorio. El ancho estaba y no se usaba. */
/* ---------- Borrar va aparte (§8.7) ---------- */
/* La única acción irreversible de la pantalla no comparte fila con Guardar y
   Cancelar: ahí se llega por inercia. Se separa con una línea, lleva su propio
   encabezado y su consecuencia al lado, y queda al final. Quien la pulse tiene
   que haber ido a buscarla. */
.zona-borrar {
  margin-top: var(--space-6);
  padding-top: var(--space-4);
  border-top: 1px solid var(--border);
  display: flex; flex-direction: column; gap: var(--space-2);
  align-items: flex-start;
}
.zona-borrar-titulo {
  font-size: var(--fs-label); letter-spacing: var(--tracking-label);
  text-transform: uppercase; color: var(--text-secondary); margin: 0;
}
.zona-borrar .caption { max-width: 44ch; }

/* El monto se tiñe según el tipo. Refuerza; no informa solo (§8.4): el selector
   de tipo ya dice cuál está activo. Transferencia sin regla = blanco puro. */
#form-movimiento[data-tipo="ingreso"] .input-monto { color: var(--monto-ingreso); }
#form-movimiento[data-tipo="gasto"]   .input-monto { color: var(--monto-gasto); }

/* La barra de botones, fija al pie del diálogo y SIN TAPAR NADA — pero no por
   reservarle sitio. El formulario LLENA el cuerpo (ver más abajo) y la barra es
   el último elemento del flujo: no hay nada debajo que pueda taparse.

   Aquí vivía un `padding-bottom: 0` con un comentario que decía lo contrario
   —«el relleno inferior reserva su altura»—. La regla no hacía nada: el atajo
   de más arriba ya dejaba el relleno inferior en cero. El comentario era lo
   grave: describía el diseño anterior, cuando la barra sí flotaba sobre
   contenido que se desplazaba por debajo.

   Comprobado ejercitándolo, no leyéndolo: con el cuerpo forzado a 312 px sobre
   638 px de contenido y desplazado hasta el final, quedan 26 px entre lo último
   del formulario y la barra. */
.dialogo--formulario #form-movimiento { min-height: 0; }
.dialogo--formulario .acciones-mov {
  position: sticky; bottom: 0; z-index: 2;
  margin-top: var(--space-5);
  padding: var(--space-3) 0;
  background: var(--surface);
  border-top: 1px solid var(--border);
}
/* Y el árbol de categorías deja de reservar alto dentro del diálogo: el que
   manda el tamaño es el diálogo, y la lista ya se desplaza sola. */
.dialogo--formulario .rejilla-mov div.zona-categoria { min-height: 0; }

/* El formulario LLENA el cuerpo del diálogo, para que la barra pegajosa quede
   abajo aunque el contenido sea corto. Sin esto, transferencia —que no lleva
   árbol de categorías— dejaba el botón 87 px más arriba que los otros dos: el
   diálogo medía igual y el control que más se usa cambiaba de sitio igualmente.
   Es justo lo que se pierde midiendo solo el alto del diálogo. */
.dialogo--formulario .dlg-cuerpo > .card {
  min-height: 100%; display: flex; flex-direction: column;
}
.dialogo--formulario #form-movimiento { flex: 1 1 auto; display: flex; flex-direction: column; }
.dialogo--formulario #form-movimiento .acciones-mov { margin-top: auto; }

/* ---------- Aplicar facturas desde el alta de un ingreso ----------
 *
 * CERRADO CUESTA UNA LÍNEA, y esa es la razón de que sea un `<details>` y no
 * una sección más. No todo ingreso es un cobro —un préstamo, una devolución o
 * un aporte no llevan factura detrás—, así que quien registra uno de esos no
 * puede tener que sortear una lista de facturas para llegar a «Guardar».
 *
 * El marco propio lo separa de los campos del movimiento: son dos cosas
 * distintas —el apunte y a qué se imputa—, y sin marco el bloque abierto se
 * lee como más campos del formulario. */
.aplicar-cobro {
  border: 1px solid var(--border); border-radius: var(--radius-md);
  background: var(--surface);
}
.aplicar-cobro__titulo {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2);
  /* El resumen es lo que se pulsa para abrir, así que es un objetivo táctil de
     pleno derecho (§8.5) y no un título con un triángulo delante. */
  min-height: var(--tactil-minimo);
  padding: var(--space-2) var(--space-3);
  cursor: pointer; font-weight: var(--fw-semibold);
}
.aplicar-cobro__pista { font-weight: var(--fw-regular); }
.aplicar-cobro__cuerpo {
  display: flex; flex-direction: column; gap: var(--space-3);
  padding: 0 var(--space-3) var(--space-3);
}
/* ── EN EL DIÁLOGO NO CABE, Y LA SALIDA NO ES APRETAR ──────────────────────
 *
 * Medido el 7 de agosto de 2026 con el cliente que más debe —la Universidad,
 * **9 facturas**—: el bloque abierto pide **601 px** y el cuerpo del diálogo da
 * **898**, de los que el formulario base ya gasta 600. Faltan ~300 a escala
 * normal y 789 a la máxima. En `/nueva`, a pantalla completa, **cabe entero y
 * no se desplaza nada** en las cuatro escalas.
 *
 * Ensanchar no arregla esto: la tabla ya cabe a lo ancho —comprobado, no
 * desborda en ninguna escala, y el diálogo mide de 1.020 a 1.785 px— y lo que
 * falta es ALTO, que está topado en `90vh` por el techo de la pantalla.
 *
 * Primero se probó acotar la lista a `32vh` con su cabecera fija. Se retiró:
 * dejaba **una barra de desplazamiento dentro de otra** —la lista y el cuerpo
 * del diálogo— y eso es peor que la que ya había. Una sola, la del diálogo.
 *
 * Lo que sí se hace es que desplazarse no cueste perder de vista lo que se está
 * decidiendo: «queda por repartir» se queda fijo arriba, y «Guardar» ya estaba
 * fijo abajo por `margin-top: auto`. */
.aplicar-cobro__grupo { overflow-x: auto; }
.dialogo--formulario .aplicar-cobro__resto {
  position: sticky; inset-block-start: 0; z-index: 2;
  background: var(--surface); padding-block: var(--space-1);
}
/* `12ch` y no `8ch`: con ocho, «10329.00» —ocho caracteres justos— se cortaba
   en «10329.0» porque el relleno del campo también cuenta. El importe mayor que
   hay hoy es de cinco cifras con decimales; doce deja sitio a seis. */
.aplicar-cobro__grupo .js-cobro-monto { inline-size: 12ch; }

/* ─── EL PANEL DE LA COMBINACIÓN QUE CUADRA · 21/08/2026 ───────────────────
 *
 * Va pegado al abono y antes de las facturas, que es el orden en que se lee:
 * cuánto entró, qué lo explica, y la lista. Sin color propio: lo que dice si
 * cuadra o no es la palabra, no el tono —el color nunca es la única señal—.
 *
 * El borde de la izquierda lo separa del resto sin pintar una caja más: dentro
 * de un diálogo, una tarjeta dentro de otra dentro de otra deja de leerse. */
.aplicar-cobro__combina {
  border-inline-start: 2px solid var(--border-strong);
  padding-inline-start: var(--space-3);
  margin-block: var(--space-2);
}
.aplicar-cobro__combina .js-cobro-combina-lista { display: grid; gap: var(--space-1); }
/* Los botones a 44 px, que es lo que se pulsa con el dedo (§8.5). */
.aplicar-cobro__combina .btn { min-block-size: var(--tactil-minimo); }
/* (Aquí se probó apretar el aire de las celdas para acortar la fila. Se retiró
   porque la MEDIDA lo desmintió: la celda ya venía con 1 px de relleno y la
   regla se lo SUBÍA — la fila pasó de 78 px a 85. Lo que ocupa la fila es el
   campo de importe, no el aire de su celda. Se deja escrito para que nadie
   vuelva a intentarlo por el mismo camino.) */
.aplicar-cobro__grupo input[type="checkbox"] { inline-size: var(--casilla); block-size: var(--casilla); }
/* La casilla sola no llega al mínimo táctil, así que el objetivo lo pone su
   celda: se pulsa la celda entera, no el cuadradito. */
.aplicar-cobro__grupo td { min-height: var(--tactil-minimo); }
/* ── LA DETRACCIÓN VA DEBAJO DEL IMPORTE, Y ANTES SOLO LO PARECÍA ──────────
 *
 * Era `inline-flex`, así que compartía renglón con el campo del importe y lo
 * empujaba fuera de su columna. Medido el 7 de agosto de 2026 en el alta, con
 * la Universidad seleccionada: al marcar una fila, su campo saltaba de
 * x=1759 a x=1410 — **349 px a la izquierda** de la columna donde están los
 * ocho importes restantes. El número que se acaba de teclear se sale de la
 * columna de números justo cuando se mira.
 *
 * Y pasaba desapercibido porque **depende de otro elemento**: cuando la pista
 * de coincidencia está visible —es `display:block`— parte el renglón y la
 * casilla cae bien. O sea que la fila se ve correcta cuando el importe casa al
 * total o al 88 %, y torcida el resto de las veces, que son la mayoría.
 *
 * `flex` y no `inline-flex`: ocupa su renglón. `flex-end` para que la casilla
 * quede alineada con la columna de importes, que va a la derecha. */
.aplicar-cobro__detr {
  display: flex; justify-content: flex-end; align-items: center; gap: var(--space-2);
  min-height: var(--tactil-minimo); cursor: pointer;
  font-size: var(--fs-caption); text-align: start; white-space: normal;
}
/* ─── Y LOS CONTROLES NO SE ESTRUJAN ──────────────────────────────────────
 *
 * Es el mismo defecto que este archivo ya documenta en `.acciones-fila`, en
 * otra rama: con `flex-shrink` por defecto, el reparto del sobrante iba a
 * favor del campo de importe y a costa de la casilla.
 *
 * Medido el 7 de agosto de 2026 en la rama «sin cadena» del atajo del alta:
 * la casilla salía de **13 px de ancho** contra los 20 declarados en
 * `--casilla` —un 35 % menos, y en las cuatro escalas, porque el problema no
 * es el tamaño de letra sino el reparto—, mientras el campo del importe se
 * estiraba a **776 px para seis caracteres**. La casilla de la rama con
 * cadena, que no comparte renglón con ningún campo, medía sus 20 px: la
 * comparación entre las dos ramas es la prueba.
 *
 * La frase de ayuda sí puede ceder: envuelve y no pierde nada. */
.aplicar-cobro__detr > input { flex: 0 0 auto; }
/* Se llamaba `.js-cobro-detr-monto`: un prefijo `js-` significa «gancho de
   JavaScript, no lo estilices», y este no tenía JavaScript detrás y sí tenía
   esta regla. El nombre mentía en las dos direcciones. */
.aplicar-cobro__detr .aplicar-cobro__detr-monto { inline-size: 12ch; }
.aplicar-cobro__detr > span { flex: 0 1 auto; min-inline-size: 0; }
/* La coincidencia va DEBAJO del importe y no al lado: al lado competía por el
   mismo ancho que ya estaba justo, que es el patrón que este archivo lleva
   documentado en las barras de categoría. */
.aplicar-cobro__pista-coincide {
  display: block; font-size: var(--fs-caption); color: var(--text-secondary);
  white-space: normal;
}
/* ─── LA DIFERENCIA QUE SUGIERE UN ERROR DEL CLIENTE · Kike, 31/08/2026 ────
 *
 * *«la diferencia es de $ 1.40 — posible error del cliente»*, y **no se parte
 * en dos líneas**: la cifra y lo que significa se leen juntas o no se leen.
 *
 * `nowrap` lo garantiza; el tope de ancho con desplazamiento propio es lo que
 * impide que, siendo `nowrap`, empuje la columna de acciones y estire la fila.
 * *Lo de largo libre cede; una cifra, nunca* — y aquí lo de largo libre es la
 * frase, que se desplaza; la cifra va delante y siempre se ve.
 *
 * No lleva color de alarma: no es un error, es una diferencia de un tamaño que
 * **puede** serlo. Quien pinte esto de rojo estará afirmando lo que la palabra
 * «posible» dice expresamente que no se afirma. */
.aplicar-cobro__difiere {
  display: block; max-inline-size: 100%; overflow-x: auto;
  font-size: var(--fs-caption); color: var(--text-secondary);
  white-space: nowrap;
}
.aplicar-cobro__difiere[hidden] { display: none; }
.aplicar-cobro__resto { margin: 0; }

/* ─── EL IMPORTE DE UNA DETRACCIÓN SIN CADENA, EN COBROS ──────────────────
 *
 * Va APILADO y no en línea, por la misma razón que la pista de coincidencia de
 * aquí arriba: `.acciones-fila` es `nowrap` y sus hijos no encogen, así que un
 * rótulo, un campo, un botón y una frase de ayuda puestos en fila le habrían
 * pedido a la columna de acciones un ancho que le quitaría a la razón social —
 * que es justo la columna que no se puede recortar, porque hay dieciséis
 * repetidas en la base.
 *
 * `align-items: flex-end` para que el campo caiga bajo la columna de importes,
 * que va a la derecha. La ayuda envuelve y tiene tope: sin él, la frase pediría
 * su ancho entero en una sola línea. */
.detr-sin-cadena {
  display: flex; flex-direction: column; align-items: flex-end;
  gap: var(--space-1); text-align: end;
}
.detr-sin-cadena .input { inline-size: 12ch; }
/* `pretty` evita la palabra huérfana en la última línea: sin él la frase
   terminaba con un «tú.» solo, que en una columna estrecha se lee como si
   faltara texto. Si el navegador no lo entiende, envuelve como siempre. */
.detr-sin-cadena .caption {
  white-space: normal; max-inline-size: 18rem; text-wrap: pretty;
}

/* ---------- Comprobante compacto y árbol más bajo (§8.1) ---------- */
/* Con la altura del diálogo fija, lo que no cabe se desplaza por dentro — y para
   usar, eso es peor que el salto que resolvió. Los dos culpables medidos en una
   ventana de 800 px eran el selector de archivo a todo lo ancho y el árbol. */
.campo-comprobante { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); }
.campo-comprobante .js-archivo-elegido { display: inline-flex; align-items: center; gap: var(--space-2); }
.btn--compacto { padding: var(--space-2) var(--space-3); min-height: var(--tactil-minimo); }
/* La etiqueta hace de botón: necesita comportarse como uno también con teclado. */
label.btn--compacto { cursor: pointer; }
label.btn--compacto:focus-within { outline: var(--focus-width) solid var(--focus-color); outline-offset: 2px; }

/* Cinco filas visibles. El árbol ya tiene su propio desplazamiento, así que
   mostrar ocho o cinco no cambia lo que se puede elegir — solo cuánto empuja al
   resto. Y con el buscador encima casi nunca hace falta recorrerlo. */
.cat-lista { max-height: 15rem; }

/* ---------- El árbol de categorías, superpuesto (§8.1) ---------- */
/* SE SUPERPONE, NO EMPUJA. Abierto en el flujo se llevaba 497 px y el formulario
   no cabía en una ventana de 800. Sacándolo del flujo, abrirlo no cambia el alto
   del diálogo ni mueve nada de lo que hay debajo.
   Sin JavaScript el `<details>` sigue funcionando: se abre en su sitio y empuja,
   que es correcto y es el peor caso — no queda un control muerto. */
.selector-cat { position: relative; }
.cat-arbol > summary:focus-visible {
  outline: var(--focus-width) solid var(--focus-color); outline-offset: 2px;
}
.cat-arbol[open] > .cat-panel {
  position: absolute; z-index: 5; inset-inline: 0; top: 100%;
  background: var(--surface); border: 1px solid var(--border-strong);
  border-radius: var(--radius-md); padding: var(--space-3);
  box-shadow: 0 12px 32px rgb(0 0 0 / 0.45);
  display: flex; flex-direction: column; gap: var(--space-2);
  /* Cabe en el hueco que queda bajo el control DENTRO del diálogo. Medido: con
     22rem se salía 130 px por debajo y con 17rem seguía saliéndose entre 52 y
     73 según el tipo. A 13rem entra entero, y con el buscador encima cuatro
     filas visibles alcanzan — recorrer no es el camino previsto. */
  max-height: 12rem;
}
/* Dentro del panel la lista usa el alto que quede, no uno propio. */
.cat-arbol[open] > .cat-panel .cat-lista { max-height: none; flex: 1 1 auto; min-height: 0; }

/* Y la zona deja de reservar alto: el panel ya no ocupa sitio en el flujo. */
.rejilla-mov div.zona-categoria { min-height: 0; }

@media (max-width: 720px) {
  /* En móvil no hay «al lado»: el panel ocupa el ancho y empuja, que es lo
     razonable cuando la pantalla es una sola columna. */
  .cat-arbol[open] > .cat-panel { position: static; box-shadow: none; max-height: 18rem; }
}

/* ---------- Encabezado de listado en dos bandas (§8.1) ---------- */
/* Eran cinco: título, atajos, etiqueta del panel, panel y botones. La mayoría no
   era contenido sino estructura. Ahora una línea de controles y una de datos. */
.encabezado-listado {
  display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap;
}
.encabezado-listado h1 { margin: 0; flex: 0 0 auto; }
.controles-listado {
  display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap;
  flex: 1 1 auto; min-width: 0;
}
/* El formulario deja de ser un bloque: es un control más de la fila, y su panel
   se despliega debajo sin sacar a los demás de sitio. */
/* La referencia del panel NO es el formulario sino la CABECERA, que abarca el
   ancho de la tarjeta. Anclado al formulario, el panel nacía donde nace la fila
   —a 612 px— y se salía 213 por la derecha; anclado a su final se salía 445 por
   la izquierda a escala muy grande. Las dos veces por lo mismo: **el ancla
   estaba a mitad de la pantalla y el panel es más ancho que lo que queda a
   cualquiera de los dos lados**.

   Contra la cabecera, `inset-inline: 0` lo deja dentro por construcción, sin
   depender de dónde caiga la fila al reflujo. */
.encabezado-listado { position: relative; }
.encabezado-listado .filtros-mov { margin: 0; }
/* Solo la rejilla flota. El `<div>` del botón era hermano y también absoluto,
   así que se dibujaba encima de los campos: ahora vive DENTRO de la rejilla. */
.encabezado-listado .filtros-finos > .form-grid {
  position: absolute; z-index: 5; top: calc(100% + var(--space-2));
  /* ─── ANCLADO AL FINAL, NO AL PRINCIPIO ───────────────────────────────
     Estaba en `inset-inline-start: 0`, y el ancho ya se había acotado por
     este mismo motivo —lo dice el comentario de abajo—. Pero acotar el ancho
     no arregla el anclaje: el formulario empieza a 612 px, así que un panel
     de 646 llegaba a 1258 en una ventana de 1045. **213 px fuera**, medido, y
     con barra horizontal en la página.

     No lo causó sacar el buscador del plegado: comprobado apagando esa regla,
     el desborde es idéntico. Solo hacía falta abrir el plegado para verlo.

     Anclado al final, el panel crece hacia dentro y su borde derecho coincide
     con el del formulario. */
  inset-inline: 0;
  /* `80vw` se medía desde la izquierda DEL PANEL, no de la pantalla: anclado a
     mitad de la fila, 80vw se salían por la derecha. Se descuenta el margen. */
  min-inline-size: 0;
  max-inline-size: 100%;
  background: var(--surface); border: 1px solid var(--border-strong);
  border-radius: var(--radius-md); padding: var(--space-3);
  box-shadow: 0 12px 32px rgb(0 0 0 / 0.45);
}
.encabezado-listado .filtros-finos > .row { top: auto; }
/* El botón de registrar al extremo, que es donde se busca la acción principal. */
.encabezado-listado .js-registrar { margin-inline-start: auto; flex: 0 0 auto; }

/* ---------- Una sola retícula para toda la lista de movimientos ---------- */
/* CADA BLOQUE DE DÍA ES SU PROPIA TABLA, y con `table-layout: auto` cada una
   calculaba sus anchos con sus propias filas: los importes de «Ayer» caían 81 px
   a la izquierda de los del día anterior. En una lista financiera eso es lo que
   más cuesta — el ojo no puede bajar en vertical comparando cifras, tiene que
   buscar cada una.

   `fixed` con anchos declarados una vez hace que todos los bloques coincidan.

   POR QUÉ ASÍ Y NO CON UNA SOLA TABLA: una tabla única disolvería la tarjeta por
   día, que es lo que separa los bloques visualmente. Y una rejilla CSS obligaría
   a rehacer el camino responsivo entero — que es el argumento decisivo, porque
   bajo 720 px §8.1 convierte estas tablas en tarjetas y ahí no hay columnas que
   alinear. Todo esto es de escritorio, y `fixed` es lo único que no lo toca.

   EL CONCEPTO ES LA COLUMNA ELÁSTICA. Los otros tres tienen ancho previsible; el
   concepto no —«renovación medicalproductsdelperu.com» ya ocupa media línea— y
   fijarlo lo recortaría. Se le da el resto. */
@media (min-width: 721px) {
  .movs.responsiva { table-layout: fixed; }
  /* ─── LA COLUMNA ELÁSTICA SE QUEDABA SIN «RESTO» ──────────────────────────
   *
   * §8.95, medido el 12 de agosto de 2026 a 1165 px de ancho. Las otras tres
   * columnas están en `ch`/`rem` y CRECEN con la preferencia de texto; el
   * ancho de la tarjeta no. Lo que le queda al concepto:
   *
   *     normal 507 px · grande 362 · muy_grande 216 · **máximo 62**
   *
   * Con 62 px y tipo de 29,8, el `overflow-wrap: anywhere` de más abajo parte
   * la nota **una letra por renglón**: «SERVICIO DE HOSTING…» medía 25,1 px de
   * ancho por **3.550,4 px de alto, 77 renglones**, y la fila entera 4.250,7.
   *
   * El propio bloque de abajo ya había diagnosticado este mecanismo para la
   * columna de acciones —«al subir la escala las demás crecen y ésta se queda
   * con menos»— y lo resolvió allí con `11rem`. Faltaba la elástica.
   *
   * La salida no es quitarle ancho a nadie: es que la tabla pueda medir lo que
   * sus columnas piden y se desplace, que es lo que hacen las otras veinte
   * tablas de Flow. `72ch` es la suma de las cuatro columnas (22+18+12 y 20
   * para el concepto); por debajo de eso no muerde, así que **nueve de las
   * doce combinaciones salen idénticas** y las tres rotas pasan de 62/216/297
   * px de concepto a 383/329/383, y la nota de 3.550 px de alto a 231.
   *
   * Va con `#lista-movimientos` porque las tablas de catálogo comparten clase
   * y tienen otro número de columnas: un suelo de 72ch en una tabla de tres
   * las obligaría a desplazarse sin motivo. */
  #lista-movimientos .movs.responsiva { min-inline-size: 72ch; }
  /* ─── Y LOS CATÁLOGOS SE REPARTÍAN A PARTES IGUALES ───────────────────────
   *
   * El comentario de más abajo ya lo había medido —«las nueve columnas se
   * repartían a partes iguales, 148 px cada una»— y solo se corrigió la de
   * acciones. En Clientes, 12 de agosto de 2026, máximo@1165: ocho columnas de
   * **132 px** en una tabla de 1.059 y **la fila más alta en 1.093 px** —una
   * fila por pantalla—, con el nombre y el RUC partidos en ocho renglones.
   *
   * `12ch` por columna es la misma medida que ya usan las otras: a escala
   * normal ocho columnas piden 1.058 px y la tabla mide 1.059, así que **no
   * cambia nada** (comprobado a 1656 y a 1165); a escalas grandes la tabla
   * crece y `.table-wrap` la desplaza, que para eso está. La fila de 1.093 px
   * pasa a 554. */
  .movs.responsiva thead th { width: 12ch; }
  .movs.responsiva tbody td[data-etiqueta="Concepto"] { width: auto; }
  .movs.responsiva tbody td[data-etiqueta="Cuenta"]   { width: 22ch; }
  .movs.responsiva tbody td[data-etiqueta="Monto"]    { width: 18ch; }
  .movs.responsiva tbody td.col-acciones              { width: 12ch; }
  /* Y LOS MISMOS ANCHOS EN LA CABECERA. Con `table-layout: fixed` el reparto
     lo fija la PRIMERA FILA de la tabla. Movimientos no tiene `<thead>` —su
     primera fila es del cuerpo— y por eso las reglas de arriba funcionan ahí.
     Los catálogos SÍ lo tienen, así que para ellos esas cuatro declaraciones
     no hacían nada: las nueve columnas se repartían a partes iguales, 148 px
     cada una, y la de acciones se quedaba corta para cuatro botones.
     Medido, no deducido: la columna daba 148 px calculados y ninguna regla
     declaraba ese número. */
  /* CON FLECHAS SON CUATRO CONTROLES, Y SU ANCHO NO SE MIDE EN CARACTERES.

     `12ch` es una porción del reparto de la tabla, y al subir la escala de
     texto las demás columnas crecen y ésta se queda con menos: medido, pasaba
     de 114 px a 84 mientras cada botón crecía de 44 a 63. Dos tendencias
     opuestas en el mismo eje — el patrón de §8.5 otra vez, aquí invertido:
     no es una medida de texto en un eje escaso, es una medida de TACTO
     compitiendo con una de texto. Resultado: cuatro botones en cuatro líneas
     y la fila de 141 px a 327.

     El ancho se declara desde el propio objetivo táctil: caben DOS por línea
     en cualquier escala, así que son siempre dos filas de dos y el alto deja
     de depender de la preferencia de texto. */
  .movs.responsiva.con-flechas thead th.col-acciones,
  .movs.responsiva.con-flechas tbody td.col-acciones { width: 11rem; }
  /* Con `fixed`, lo que no cabe desborda en vez de ensanchar: lo que no quepa
     se parte en vez de salirse de la celda.

     ─── VALÍA PARA TODAS LAS COLUMNAS, NO SOLO PARA EL CONCEPTO ────────────

     Estaba puesto solo en «Concepto» porque fue ahí donde se vio. El 5 de
     agosto de 2026, tecleando en Clientes, apareció en otra: la columna del
     CORREO. Un correo es **una sola palabra** —no tiene por dónde partirse—, y
     la celda mide 213 px. Medido en el listado real: `dpomajulca@grupokio.com.pe`
     ocupa 262 px, y 52 de los 450 clientes de hoy pasan de 30 caracteres. El
     texto salía de su columna, pasaba por encima de la de acciones y quedaba
     **cortado en el borde de la tabla sin ninguna señal de que faltaba algo**.

     El conteo no lo veía: `document.scrollWidth` seguía igual al del viewport,
     porque la tarjeta recorta. Solo se ve mirando la pantalla.

     La versión móvil ya lo hacía para todas las celdas desde el principio
     (`.movs.responsiva tbody td` arriba). Era escritorio el que llevaba media
     regla. */
  .movs.responsiva tbody td { overflow-wrap: anywhere; }
}

/* ---------- Combo con búsqueda (§8.11) ---------- */
/* UN SOLO CONTROL. Antes eran dos —filtro arriba, desplegable debajo— y
   ocupaban dos filas del formulario, rompiendo la retícula de dos columnas.
   Ahora el botón ocupa una fila como cualquier campo y el panel flota. */
.combo-buscable { position: relative; }

/* ─── EL COMBO OBLIGATORIO QUE SE ENVÍA VACÍO ────────────────────────────────
 *
 * El aviso lo escribe `combo.js` cuando el navegador declara inválido el
 * `<select>` oculto —ver allí el porqué—. Aquí solo se marca el borde, **y el
 * borde no es la señal**: la señal es el texto que aparece debajo. Quien no
 * distinga el color lee exactamente lo mismo. */
.combo--invalido > .combo-boton { border-color: var(--expense); border-width: 2px; }
.combo-nativo-oculto { display: none; }

.combo-boton {
  width: 100%; min-height: var(--tactil-minimo);
  display: flex; align-items: center; gap: var(--space-3);
  padding: var(--space-2) var(--space-3);
  background: var(--surface-2); color: var(--text);
  border: 1px solid var(--border-strong); border-radius: var(--radius-md);
  font: inherit; text-align: start; cursor: pointer;
}
.combo-boton:focus-visible {
  /* Mismo anillo que los campos, y el outline transparente por los colores
     forzados de Windows: ver la nota de `.input:focus` en base.css. */
  border-color: var(--brand);
  outline: var(--focus-width) solid transparent; outline-offset: 2px;
  box-shadow: 0 0 0 3px var(--brand-ring);
}
.combo-elegido { flex: 1 1 auto; overflow-wrap: anywhere; }
/* El detalle acompaña, no compite: §8.5 pone piso de tamaño, así que se
   distingue por color y no encogiéndolo por debajo del mínimo legible. */
.combo-detalle { color: var(--text-secondary); font-size: var(--fs-caption); }

/* EL PANEL FLOTA. Si empujara, abrir el combo movería el resto del formulario
   y el campo siguiente cambiaría de sitio bajo el dedo. */
/* ─── EL PANEL NO HEREDA EL ANCHO DE SU DISPARADOR ────────────────────────
 *
 * Llevaba `inset-inline: 0`, que lo clava a los dos bordes de su caja: el panel
 * medía exactamente lo que el botón. Kike lo vio en dos sitios distintos —el
 * cliente con deuda de Registrar y el «con qué gasto» de Comisiones—, y eso ya
 * decía que era del componente y no de una pantalla.
 *
 * Medido el 8 de agosto de 2026, y **alcanza a los cinco combos**, también donde
 * el disparador es ancho:
 *
 *     cliente con deuda ...... botón 311 · el contenido pide  903
 *     cliente de factura ..... botón 612 · el contenido pide 1.155
 *
 * ─── EL ANCHO LO DECIDE LO QUE TIENE QUE CABER ───────────────────────────
 *
 * Y lo que tiene que caber no cabe en una línea: **la etiqueta sola pide 1.013
 * px** —hay razones sociales de sesenta caracteres—, así que ningún ancho
 * razonable la mete de un tirón. La respuesta no es un panel gigante ni recortar
 * el nombre: es que **el nombre envuelva**. Recortarlo haría dudar de si se está
 * leyendo entero, que es justo lo que el ancho de un campo promete.
 *
 * Así que: al menos tan ancho como su disparador, todo lo que el contenido pida,
 * y con tope. 34 rem crecen con la letra, así que a escala máxima el tope sube
 * con ella; el `92vw` es el que impide que se salga de la ventana. */
.combo-panel {
  position: absolute; z-index: 20;
  inset-inline-start: 0; inset-inline-end: auto;
  inline-size: max-content; min-inline-size: 100%;
  max-inline-size: min(34rem, 92vw);
  top: calc(100% + var(--space-1));
  background: var(--surface); border: 1px solid var(--border-strong);
  border-radius: var(--radius-md); box-shadow: var(--shadow-2);
  padding: var(--space-2); display: flex; flex-direction: column; gap: var(--space-2);
}
/* `max-height`, no `height`: con tres opciones mide tres, no dieciocho. Y el
   panel que la contiene no fija alto, así que encoge con ella. */
.combo-lista { list-style: none; margin: 0; padding: 0; max-height: 18rem; height: auto; overflow-y: auto; }
.combo-panel { height: fit-content; }
/* ─── Y EL PANEL TIENE QUE PODER ENCOGER CUANDO NO HAY SITIO ──────────────
 *
 * `acomodar()` ya le pone un tope de alto en píxeles —el hueco que de verdad
 * queda—, pero un tope no sirve si dentro nadie cede: la lista mide su
 * contenido y el panel se desborda igual. Quien cede es la lista, que para eso
 * ya tiene `overflow-y: auto`. El filtro y el recuento nunca ceden: si el
 * hueco es mínimo, lo que hay que ver es dónde escribir y cuántas quedan.
 *
 * `min-block-size: 0` explícito: el mínimo automático de un elemento flexible
 * es su contenido, y sin esto la lista se niega a bajar de sus 31 opciones. */
.combo-panel { min-block-size: 0; }
.combo-panel > .combo-filtro, .combo-panel > .combo-vacio { flex: 0 0 auto; }
.combo-lista { flex: 1 1 auto; min-block-size: 0; }
.combo-grupo { padding: var(--space-2) var(--space-2) var(--space-1); color: var(--text-secondary); font-size: var(--fs-caption); }
/* ETIQUETA ARRIBA Y DETALLE DEBAJO, no en la misma línea. Juntas pedían 1.155
   px; apiladas, la etiqueta envuelve y el panel cabe. El detalle —lo que debe,
   lo que le queda— se lee igual de bien bajo el nombre que a su derecha. */
.combo-opcion {
  display: flex; flex-direction: column; align-items: flex-start; gap: var(--space-micro);
  min-height: var(--tactil-minimo); padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-md); cursor: pointer;
}
.combo-opcion > * { min-inline-size: 0; overflow-wrap: anywhere; }
/* La resaltada se distingue por FONDO y por borde, no solo por color de
   texto: §8.4 dice que el significado no se comunica por color. */
.combo-opcion.es-resaltada { background: var(--surface-2); outline: 2px solid var(--brand); outline-offset: -2px; }
.combo-opcion[aria-selected="true"] { font-weight: var(--fw-semibold); }
.combo-vacio { margin: 0; color: var(--text-secondary); }

/* ---------- El buscador del encabezado de catálogo ---------- */
/* Ancho por contenido, no por hueco disponible: con `flex:1 1 16rem` el campo
   se comía la fila entera, quedaba pegado al título y no dejaba sitio para el
   filtro por sector. `ch` mide en caracteres, que es el criterio real. */
/* LA MISMA REGLA QUE FACTURAS, no una nueva: allí `.barra-filtros` va con
   `margin: 0; flex: 1 1 auto` y por eso los filtros arrancan pegados a su
   separador. Aquí había además un `margin-inline-start: var(--space-5)` propio
   que los empujaba hacia dentro, y era lo que rompía la unidad con Movimientos
   y Facturas: el hueco entre el título y los filtros era distinto en Clientes
   que en los módulos de al lado. El `gap` del contenedor ya separa. */
.buscador-catalogo { margin: 0; flex: 1 1 auto; }
/* `flex-shrink: 0`: con `1` la base de 44ch era solo una sugerencia y el campo
   encogía a 255 px, que es menos de lo que mide su propio texto de ayuda. */
/* `min(29ch, 100%)` y no `29ch` a secas: `ch` crece con la escala, y al
   alargar el texto de ejemplo de Clientes —para que nombre los cinco campos que
   busca— la barra se salió **11 px** a escala máxima. Medido. El suelo no puede
   pedir más de lo que hay; es la misma corrección que en la fila del monto y en
   las fichas de categoría.

   Y `flex-shrink` vuelve a 1 CON SUELO. Estaba en 0 para que la base no fuera
   «solo una sugerencia», y el efecto a escala máxima era que el campo medía 556
   px y **empujaba el botón «Buscar» 11 px fuera de la pantalla**. Con `1` cede
   cuando hace falta, y `min-inline-size` impide que ceda hasta ser inútil —que
   era el miedo original—. */
.buscador-campo { flex: 1 1 min(29ch, 100%); min-inline-size: 12rem; }
/* ═══ LA MISMA ALTURA PARA TODOS LOS HERMANOS DE LA FILA ═══
   El campo de búsqueda medía 50 px y el selector 45, con 3 px de desfase. No
   era el reparto: eran dos controles hermanos con alturas distintas, y en una
   fila se nota aunque cada uno por separado parezca correcto.

   `--tactil-minimo` y no un número nuevo: es la altura que ya usa el resto de
   la aplicación para lo que se pulsa, y crece con la escala del usuario.

   `min-height` NO bastaba, y se vio midiendo: seguían saliendo 44, 50 y 45.
   Una altura mínima no encoge al que ya es más alto, y el campo de búsqueda lo
   era por su propio relleno. Hace falta fijar la altura, no un suelo. */
.buscador-catalogo .input,
.buscador-catalogo .select,
.buscador-catalogo .btn,
.buscador-catalogo .filtro-casilla {
  min-height: var(--tactil-minimo);
  height: var(--tactil-minimo);
}
.buscador-catalogo .field { display: flex; align-items: center; }

@media (max-width: 720px) {
  /* En móvil no hay a qué separarse: el encabezado se apila. */
  /* En móvil ocupa la fila entera; el margen ya no existe en la base. */
  .buscador-catalogo { flex: 1 1 100%; }
  .buscador-campo { flex: 1 1 100%; }
}

/* El conteo se pega al final de los totales en lugar de ocupar su propia línea.
   SIN `align-self`: el contenedor alinea por línea base y este rótulo llevaba un
   `center` propio que lo dejaba flotando 17 px por encima de las tres cifras.
   Era invisible mientras el contenedor también centraba. */
.total-conteo { margin: 0; }

@media (max-width: 720px) {
  /* En móvil la fila se parte sola y el panel vuelve al flujo: no hay «al lado». */
  /* En móvil el panel no flota ni impone ancho: a escala máxima `min(38rem,
     80vw)` pedía más de lo que hay y arrastraba scroll. */
  .encabezado-listado .filtros-finos > .form-grid,
  .encabezado-listado .filtros-finos > .row { position: static; box-shadow: none; min-width: 0; }
  /* `overflow-wrap` no basta si el elemento no puede encoger: el título es
     `flex: 0 0 auto` unas líneas más arriba y a 320 px con escala máxima pedía
     300 px de ancho fijo. Tiene que ir DESPUÉS de aquella regla, no antes:
     misma especificidad, gana la última. */
  .encabezado-listado h1 { flex: 1 1 100%; min-width: 0; }

  /* EL SELECTOR DE TIPO NO SE PODÍA USAR ENTERO EN UN TELÉFONO.
     La adaptación móvil ya existía —`display: flex; width: 100%`— pero se
     declara en el bloque de la línea 703 y `.tipos-mov.segmentado` se declara
     DESPUÉS, con la misma especificidad: ganaba la de abajo. Así seguían
     activos `white-space: nowrap` (la palabra no puede partirse) y
     `overflow: hidden` (lo que sobra se tapa, y no hay scroll que lo alcance).
     Medido a 371 px: el control necesitaba 413 px y tenía 292, así que
     «Transferencia» quedaba fuera y era INALCANZABLE. No era un recorte
     estético: en el móvil no se podía registrar una transferencia.
     Dos líneas es peor diseño que una; una opción que no existe es peor
     que las dos líneas. */
  .tipos-mov.segmentado { display: flex; flex-wrap: wrap; width: 100%; }
  .tipos-mov.segmentado .tipo-opcion {
    white-space: normal; min-width: 0;
    /* Venían de un bloque móvil anterior que nunca se aplicó; se mudan aquí. */
    padding-inline: var(--space-2); justify-content: center;
  }

  /* La cabecera de tarjeta —título a la izquierda, acción a la derecha— no
     envolvía: en «Registrar» a escala máxima los dos hijos pedían 430 px en
     294 y el botón «Ver movimientos» se salía de la pantalla. En una sola
     columna la acción cae debajo del título, que es donde se la busca cuando
     no hay ancho. Es la cabecera común de todas las tarjetas, no solo de ésta. */
  .card-head { flex-wrap: wrap; }

  /* ---- La cabecera dejaba el primer movimiento a 881 px de una pantalla
     de 736: ninguno se veía al cargar, en la vista que existe para verlos.
     Los tres cambios de abajo son eso, y solo eso. ---- */

  /* 1. Los atajos de periodo se retiran de la fila y se dibujan dentro del
        panel; el resumen pasa a decir el periodo vigente. */
  .solo-ancho    { display: none; }
  .solo-estrecho { display: revert; }
  .atajos-periodo.solo-estrecho { display: flex; flex-wrap: wrap; gap: var(--space-2); }

  /* 2. «Registrar» sale de la cabecera: ya está en la barra inferior como
        «Nueva», y §8.1 pone la acción primaria al alcance del pulgar. Arriba
        era un duplicado que costaba 44 px de los primeros que se leen. */
  .encabezado-listado .js-registrar { display: none; }

  /* 3. El neto conserva su tamaño —es el protagonista— y su desglose pasa a
        una sola línea en vez de dos bloques apilados. */
  .totales-desglose { flex-direction: row; flex-wrap: wrap; gap: 0 var(--space-3); }
  .totales-desglose .total-bloque { flex-direction: row; align-items: baseline; gap: 0 var(--space-1); }
  /* El importe del desglose baja al tamaño de su etiqueta: es apoyo del neto,
     no su igual, y a tamaño de cifra ocupaba una línea entera cada uno. El
     neto NO baja: sigue siendo el protagonista. */
  .totales-desglose .amount { font-size: var(--fs-caption); }
  .totales-mov .total-conteo { margin: 0; }
  /* El formulario ocupa su propia línea: si comparte fila, el panel estático
     hereda el desplazamiento del botón y se sale por la derecha. */
  .encabezado-listado .filtros-mov { flex: 1 1 100%; min-width: 0; }
  .encabezado-listado .js-registrar { margin-inline-start: 0; width: 100%; }
}

/* Las dos tasas, enfrentadas. Son campos angostos con etiquetas paralelas —«del
   USD», «del EUR»— así que lado a lado se comparan de un vistazo; apiladas
   costaban 115 px cada una en el tipo que menos espacio tiene. */
@media (min-width: 721px) {
  .rejilla-mov [data-campo="tasa_origen"] .input,
  .rejilla-mov [data-campo="tasa_destino"] .input { max-width: 100%; }
}

/* Monto y fecha en su propia fila, sobre las dos columnas. Con ellos fuera, las
   columnas arrancan a la misma altura y los pares —«Sale de» ↔ «Entra a», tipo de
   cambio ↔ monto que llega— quedan enfrentados por posición, sin atar ninguna
   otra fila. */
@media (min-width: 721px) {
  .fila-superior {
    display: grid; grid-template-columns: 1fr 1fr;
    gap: var(--space-5); align-items: start;
    margin-bottom: var(--space-4);
  }
}
@media (max-width: 720px) {
  .fila-superior { display: flex; flex-direction: column; gap: var(--space-4); margin-bottom: var(--space-4); }
}

/* ---------- Totales del periodo y saldos, una sola tarjeta ---------- */
/* Dos bloques con una LÍNEA VERTICAL entre ellos, y esa línea no es adorno:
   es lo que queda de la separación en dos tarjetas. A la izquierda lo que
   responde al filtro, a la derecha lo que no. */
/* ─── Y ES UNA TARJETA, PORQUE AHORA ESTÁ SOLA ─────────────────────────────
 *
 * Vivía DENTRO de la tarjeta de filtros y por eso no necesitaba fondo: lo
 * heredaba. Al sacarla —para que la franja de filtros se lea aparte, como en
 * las otras cinco pantallas— se quedó flotando sobre el fondo de la página,
 * medio acabada al lado de Facturas, donde cada indicador es su propia tarjeta.
 *
 * Así que recupera lo que tenía, ahora por derecho propio. El resultado es el
 * mismo patrón en las siete: **franja de filtros · indicadores · área de
 * trabajo**, tres bloques que se leen como tres cosas porque son tres cosas. */
.totales-mov {
  display: flex; flex-wrap: wrap; align-items: stretch;
  gap: var(--space-4) var(--space-5);
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius-lg); box-shadow: var(--shadow-1);
  padding: var(--space-4) var(--space-5);
}
.bloque-periodo {
  display: flex; align-items: stretch; gap: var(--space-4);
  /* `0 0 auto` y no `1 1 auto`: medido, el bloque se estiraba a 801 px de los
     1157 disponibles y empujaba las cuentas a la línea de abajo, que es
     justamente lo que la fusión venía a evitar. Ocupa lo que miden sus
     cifras y el resto es para las cuentas. */
  /* ─── LA TARJETA CRECE; LA CIFRA NO ENCOGE ────────────────────────────────
   *
   * Kike, 11 de agosto de 2026: «al filtrar egresos el negativo no cabe.
   * **Agranda la tarjeta y mantén el tamaño de los números** — encoger la cifra
   * para que quepa no es la solución, es el problema.» Tiene baja visión, y una
   * cifra que se achica cuando hay más dígitos se achica justo cuando más
   * importa leerla.
   *
   * `min-width: 0` era lo que permitía que el bloque bajara de su contenido: con
   * el signo delante, «−S/ 12.345,67» mide un carácter más que el positivo y ahí
   * se rompía. `max-content` pone el suelo en lo que el número mide de verdad, y
   * el resto de la fila se reparte lo que queda o baja de línea.
   *
   * Se conserva `flex: 0 0 auto`: el bloque no se estira —eso ya empujó las
   * cuentas a la línea de abajo— pero tampoco se encoge por debajo de su cifra.
   * Crecer y estirarse no son lo mismo.
   *
   * ─── PERO EL SUELO ES DE LA CIFRA, NO DEL BLOQUE ──────────────────────────
   *
   * `min-inline-size: max-content` estaba aquí, en el bloque, y `max-content` de
   * un bloque es **lo que mide su hijo más ancho sin envolver** — que no es la
   * cifra sino la explicación de debajo. Medido el 12 de agosto de 2026 en el
   * widget «Crédito fiscal» de Compras: el bloque exigía **717 px** dentro de
   * una tarjeta de 521 y **se salía 222**.
   *
   * NO ES EL CASO DE §8.95, es el contrario. Allí una columna sin suelo
   * colapsaba a cero; aquí un bloque con el suelo puesto en «lo que mida mi
   * texto más largo en una línea» se niega a encoger. Dos extremos del mismo
   * eje, y el mismo síntoma —texto fuera de la tarjeta— por causas opuestas.
   *
   * El suelo pasa a la cifra, que es de quien era la regla: la cifra no encoge,
   * y la explicación envuelve, que es lo que una explicación sabe hacer. */
  flex: 0 0 auto; min-inline-size: 0;
}
.bloque-periodo > .neto-cifra,
.bloque-periodo > .stat-value { min-inline-size: max-content; }
.bloque-periodo .total-neto { flex: 0 0 auto; }
/* ─── EL SEPARADOR VERTICAL SOBRA · Kike, 27/08/2026 ────────────────────────
 * Era la raya que separaba «Neto del periodo» de los saldos **cuando iban uno
 * al lado del otro**. Los saldos bajaron a una fila propia a todo el ancho, así
 * que la raya quedó suelta a la izquierda, separando de nada. *Lo que separaba
 * ya no está: la raya tampoco.* */
.bloque-saldos { min-width: 0; }
/* La zona central solo existe cuando hay ancho para tres: en móvil las
   categorías no se dibujan, como pide §8.8 para los cortes por dimensión. */
.zona-categorias { display: none; }

/* TRES TERCIOS, y solo cuando hay ancho para tres. El reparto es 1fr/1fr/1fr
   y no «lo que ocupe cada uno»: el operador pidió proporción, y con anchos
   automáticos el nombre de la cuenta y su importe quedaban separados por un
   hueco muerto que era justo lo que se veía mal. */
@media (min-width: 1144px) {
  /* 30 / 40 / 30, y no tres tercios. El centro lleva DOS bloques de
     categorías uno junto al otro y necesita el doble; el neto necesita
     menos, porque su cifra se ensancha a lo que le den y con un tercio
     entero dominaba la cabecera. El tamaño del neto NO se toca a mano: lo
     resuelve el ancho del contenedor, y fijarlo devolvería el problema de
     que se parte con importes largos. */
  /* ─── LO QUE ERA EL SEPARADOR SE FUE CON LA CAJA ────────────────────────
   *
   * Estas tres reglas —el reparto 30/40/30, el borde izquierdo y la sangría—
   * existían porque los tres widgets vivían **dentro de una sola tarjeta** y
   * había que separarlos a mano. Desde que cada uno tiene la suya, la tarjeta
   * pone el borde y el relleno, y esto solo restaba ancho.
   *
   * Y restarlo tenía un precio medido: en el widget del centro la etiqueta
   * **se pisaba con el importe** —«Email & A/S/ 5.191,89»— porque los dos
   * grupos de barras van uno al lado del otro y se quedaban sin sitio. Se vio
   * en la captura, no en los números: ninguna medida de desborde lo caza,
   * porque nada desborda la página.
   *
   * Se queda solo lo que sigue siendo cierto: que a partir de este ancho las
   * categorías se dibujan. */
  .zona-categorias { display: block; }
  /* ─── LOS DOS LADOS, UNO JUNTO AL OTRO · Y DE DÓNDE SALE EL SITIO ───────
   *
   * «De dónde entró» y «a dónde se fue» se comparan mirándolos a la vez: es
   * para lo que se dibujó el widget. Apilados, el de ingresos queda arriba y
   * corto y parece que solo se grafican las salidas.
   *
   * Estuvieron apilados un rato y **la razón era buena**: con las tres tarjetas
   * a tercios iguales, la del centro daba 558 px, los grupos se repartían 244
   * cada uno, y en la fila «Email & Apps» la columna del importe medía 123 px
   * para un contenido de 161 — el importe se salía encima del rótulo.
   *
   * Kike decidió el 7 de agosto de 2026 la salida correcta: **el sitio no se
   * quita del gráfico, se le quita a los vecinos.** Las tarjetas 1 y 3 llevan
   * una cifra y una lista corta y sobreviven estrechas; la del centro lleva dos
   * gráficos y no. Ver `.tercios--centro-ancho`. */
  /* ─── DOS COLUMNAS SI HAY DOS GRUPOS, NO SIEMPRE ──────────────────────
   *
   * `repeat(2, …)` reserva dos columnas **haya lo que haya dentro**. Con un
   * solo grupo —Compras: el dinero solo sale— el gráfico se metía en la
   * primera y dejaba la mitad derecha de la tarjeta vacía, con los nombres
   * recortados a «Servid…» y «Contabilid…» teniendo sitio de sobra al lado.
   * Kike lo vio en pantalla: *«este tercer widget luce mal, debe ocupar el
   * ancho de la tarjeta»*.
   *
   * Con `auto-fit` la pista vacía **se colapsa**: un grupo ocupa el ancho
   * entero y dos se reparten como antes. El suelo de 16rem conserva la
   * decisión de arriba —dos columnas solo si de verdad caben— y va en `rem`
   * para que crezca con la preferencia de tamaño. */
  .grafico-categorias { grid-template-columns: repeat(auto-fit, minmax(min(16rem, 100%), 1fr)); }
  /* ─── EL SITIO SE LE QUITA A LOS VECINOS, NO AL GRÁFICO ─────────────────
   *
   * Solo en Movimientos, que es el único con dos grupos de barras en la
   * tarjeta del centro. Las otras pantallas con tres tarjetas —Cobros,
   * Registro de ventas, Clientes— siguen a tercios iguales, porque ahí las
   * tres pesan lo mismo.
   *
   * `auto-fit` con `minmax` no admite reparto desigual, así que la variante
   * nombra las tres columnas. Se conserva `minmax(0, …)` en las tres: sin el
   * cero, una columna de rejilla no baja de su contenido mínimo y el texto
   * largo la ensancha por su cuenta, que es como se rompen estos repartos. */
  /* `.tercios.tercios--centro-ancho` y no solo la segunda clase: `.tercios--tres`
     está MÁS ABAJO en la hoja y con la misma especificidad ganaba por orden.
     Medido: las tarjetas seguían en 558 iguales con la regla escrita. */
  .tercios.tercios--centro-ancho {
    grid-template-columns: minmax(0, 26fr) minmax(0, 48fr) minmax(0, 26fr);
  }
  /* PERO NO A ESCALAS ALTAS. Con el texto a 1,5× o 1,75×, partir el bloque en
     dos deja los dos lados sin sitio para nombre, importe y porcentaje en una
     línea. Apilados, cada uno dispone del ancho entero — y ahí sí se vuelve a
     tercios iguales, porque la tarjeta del centro ya no necesita el doble.
     Se condiciona por el ATRIBUTO y no por una media query, porque `em` y
     `rem` dentro de una media query se miden contra el tamaño inicial del
     navegador y NO siguen a `data-escala` — está escrito en §8.1. */
  :root[data-escala="muy_grande"] .tercios.tercios--centro-ancho,
  :root[data-escala="maximo"] .tercios.tercios--centro-ancho {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
  :root[data-escala="muy_grande"] .grafico-categorias,
  :root[data-escala="maximo"] .grafico-categorias { grid-template-columns: minmax(0, 1fr); }
  /* (Aquí hubo una regla que bajaba el importe a su propia línea SOLO en la
     escala máxima. Estaba atada a la escala y no a si cabía: se pisaba antes
     en Cobros y engordaba de más en Movimientos. La condición correcta vive
     ahora en `.barra-fila`, con `flex-wrap`.) */
  /* LA CIFRA BAJA UN ESCALÓN DENTRO DE LOS TERCIOS, y es la decisión correcta
     frente a la alternativa. Con `--fs-display` el número se partía en DOS
     líneas por debajo de 1402 px —medido—, y un neto partido a la mitad es
     peor que un neto algo menor. Atar el diseño a ese 1402 tampoco valía: el
     umbral depende del IMPORTE, así que un mes con siete cifras lo rompería.
     Con `--fs-monto` cabe en una línea sea cual sea la suma, sigue siendo con
     diferencia el texto mayor de la tarjeta, y sigue en `rem` —escala con la
     preferencia de texto, §8.5—. */
  /* +30% EN ESCRITORIO. `--fs-monto` (2,6rem) dejaba la magnitud del tamaño de
     un titular cualquiera; es la cifra que responde la pregunta de la pantalla
     y tiene que ganarle a todo lo demás. 3,4rem son esos 2,6 más un tercio, y
     sigue en `rem` —escala con la preferencia de texto (§8.5)—.
     Va en el COMPONENTE: Movimientos y Facturas la heredan a la vez, y desde
     que se retiró `--k-neto` la heredan **al mismo tamaño**: antes esta misma
     regla daba 57,80 px en Facturas y 32,89 en Movimientos, porque el
     multiplicador la encogía según el ancho del desglose de debajo. */
  /* Sin tamaño propio: era `3.4rem` y daba **54,25 px** donde el resto daba
     63,75. La cifra principal de un widget es una sola en todo Flow. */
}
/* Los rótulos de los dos lados son pareja: «Neto del periodo» frente a
   «Ahora mismo». Uno dice que depende del filtro y el otro que no, en dos
   palabras y sin una frase que lo explique. */
.rotulo-bloque { margin: 0 0 var(--space-2); }
/* EL DESGLOSE OCUPA EL ALTO DE LA CIFRA, que era el hueco que sobraba:
   Entró, Salió y la moneda repartidos en la altura del número grande. */
/* TODO ARRANCA EN EL MISMO BORDE IZQUIERDO, y el bloque se CIÑE a lo que mide
   su contenido. Esas dos cosas juntas son lo que hace que la cifra y la línea
   de abajo rematen cerca: el bloque mide lo que miden ellas.

   LO HICE AL REVÉS EN EL INTENTO ANTERIOR: empujé la cifra a la derecha con
   `text-align: end` y estiré el desglose con `space-between`. El desvío medía
   0 px —la comprobación era correcta— pero el resultado era peor que el
   problema: el rótulo se quedaba solo a la izquierda, la cifra flotaba en el
   centro sin relación con nada, y «Entró» y «Salió» se separaban a los
   extremos. Medir bien no salva de alinear hacia el lado equivocado. */
.bloque-periodo {
  flex-direction: column; align-items: flex-start;
  /* `fit-content` no basta dentro de una rejilla: un elemento de rejilla se
     estira por omisión (`justify-self: stretch`), así que el bloque medía los
     439 px del tercio y la cifra remataba hasta 169 px antes que el desglose.
     Con `justify-self: start` el ancho vuelve a mandarlo el contenido. */
  inline-size: fit-content; max-inline-size: 100%; justify-self: start;
}
/* ─── ENTRÓ Y SALIÓ, UNO DEBAJO DEL OTRO Y EN COLUMNA ──────────────────────
 *
 * Kike lo pidió en dos líneas con los importes alineados entre sí, y el cambio
 * SÍ se hizo: `.totales-desglose` es `flex-direction: column` desde entonces.
 * Lo que pasaba es que **esta regla lo pisaba**, y gana por ser más específica
 * —`.bloque-periodo .totales-desglose` contra `.totales-desglose`—. Medido el
 * 6 de agosto de 2026: los dos importes en el mismo `top`, a 311 px y 523 px.
 *
 * Es el caso de siempre: dos reglas decidiendo lo mismo, y la que se corrigió
 * no era la que mandaba. Se corrige la que manda y la otra ya lo decía.
 *
 * La razón que tenía esta regla —«al lado se llevaban 655 px de los 1157»— se
 * cumple mejor apilados: en columna el bloque mide lo que el más ancho de los
 * dos, no la suma. Lo que crece es el alto, y son dos líneas.
 *
 * `min-width: 5ch` en la etiqueta —regla de arriba— es lo que pone los dos
 * importes en la misma columna en vez de cada uno donde caiga. */
.bloque-periodo .totales-desglose {
  display: flex; flex-direction: column; flex-wrap: nowrap;
  gap: var(--space-1); min-width: 0;
  margin-block-start: var(--space-2);
}
.bloque-periodo .total-conteo { margin: 0; }
.bloque-periodo .total-bloque { flex-direction: row; align-items: baseline; gap: 0 var(--space-2); }

/* ---- Cuando no caben lado a lado, se apilan y el divisor gira ----
   1143 px NO ES UN PUNTO DE QUIEBRE NUEVO —§8.1 sigue teniendo uno solo, en
   720— sino el ancho MEDIDO por bisección al que estos dos bloques dejan de
   caber en una línea: a 1144 van al lado, a 1143 el navegador ya los envuelve.
   Sin esta regla, entre 721 y 1143 los bloques se apilaban pero el divisor
   seguía siendo el borde IZQUIERDO, que apilado no separa nada: se lee como
   una sangría. Aquí gira a horizontal y vuelve a decir lo que tiene que decir.
   ---- Y en móvil ----
   Medido antes de decidir: con la cifra del neto a la izquierda y las siete
   cuentas a la derecha no hay ancho para dos columnas en un teléfono; la
   lista de cuentas quedaría en una tira de 8rem. Se apilan, y la línea
   divisoria gira con ellos: pasa de vertical a horizontal, que es lo que
   mantiene la distinción entre lo que responde al filtro y lo que no.

   ESTE BLOQUE VA DEBAJO DE LA REGLA BASE A PROPÓSITO. Lo escribí arriba
   primero y el auditor de orden lo marcó: `gap` pisado. Es el fallo que ese
   auditor existe para encontrar, cometido en el mismo turno en que se
   instaló y encontrado por él. */
@media (max-width: 1143px) {
  .totales-mov { flex-direction: column; gap: var(--space-3); }
  .bloque-saldos {
    padding-inline-start: 0; border-inline-start: 0;
    padding-block-start: var(--space-3); border-block-start: 1px solid var(--border);
  }
}

/* Una marca, no una frase: solo donde hay algo que explicar. */
/* ─── «FUERA DE LA VENTANA», EN LA LISTA DE COBROS ─────────────────────────
 *
 * Un ingreso que no se puede cuadrar **porque su factura es anterior al corte**
 * no es lo mismo que uno que falta por cuadrar, y en una lista se ven igual.
 * Esto los distingue.
 *
 * §8.5: **la palabra lo dice entero**. El recuadro solo la separa del resto de
 * la celda; quitando todo el color, «fuera de la ventana» sigue leyéndose y
 * sigue significando lo mismo. Por eso no lleva ni verde ni rojo: no es bueno
 * ni malo, es otra cosa.
 *
 * Escrita porque la primera versión usó una clase `.marca` que **no existe**:
 * salía como texto suelto, sin recuadro y sin separación. Medido en pantalla
 * —fondo transparente y `padding` a cero— antes de darla por buena. */
.marca-ventana {
  display: inline-block; padding: var(--space-micro) var(--space-2);
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  font-size: var(--fs-caption); color: var(--text-secondary);
  white-space: nowrap;
}
.marca-revisar {
  display: inline-flex; align-items: center; justify-content: center;
  inline-size: 1.15em; block-size: 1.15em; margin-inline-start: .35em;
  border: 1px solid currentColor; border-radius: 50%;
  font-size: var(--fs-caption); line-height: 1; cursor: help;
}

/* ---------- Saldo actual de las cuentas (§8.10) ---------- */
/* Tarjeta aparte, no una fila más de totales: el neto depende del filtro y el
   saldo no. Verlos en la misma fila invitaría a leer el segundo como
   consecuencia del primero. */
.lista-saldos { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-2); }
/* ─── LO QUE CEDE ES EL NOMBRE, Y CEDE DE UNA PIEZA ─────────────────────────
 *
 * Kike, 12 de agosto de 2026: esta lista estiraba la fila de Movimientos a
 * **1.146 px**. Medido en Chrome antes de tocar nada, con la tarjeta a 228 px:
 *
 *   BCP CC Soles   nombre 48 px de ancho → 84 px de alto  (3 renglones)
 *   BN Detracciones             48 px    → 112 px         (4 renglones)
 *   AMEX Soles     nombre **0 px**       → **252 px**     (letra por letra)
 *
 * La causa exacta: era una rejilla `minmax(0, 1fr) auto auto`. La cifra y la
 * moneda son `auto`, así que se llevan lo que necesitan —149 + 43 px más los
 * huecos, o sea 217 de los 228— y el `minmax(0, …)` del nombre **colapsaba a
 * cero**. Con `overflow-wrap: anywhere` encima, un nombre en una columna de
 * cero píxeles se parte en una letra por renglón.
 *
 * QUÉ CEDE, DECIDIDO: **el nombre**. El importe no se corta —es la cifra— y la
 * moneda tampoco, que ya costó cinco veces distinguir una suma de una mezcla.
 *
 * Y CEDE DE DOS FORMAS, EN ESTE ORDEN:
 *
 *  1. Si no cabe entero, se recorta con puntos suspensivos **en una sola
 *     línea**, con el nombre completo en `title`. Nunca se parte por dentro.
 *  2. Si ni así caben los tres, `flex-wrap` baja la cifra y la moneda **juntas**
 *     al segundo renglón, en vez de estrangular al nombre. Dos renglones, no
 *     siete, y la cifra sigue entera y alineada al final por `margin-inline-
 *     start: auto`.
 *
 * En la pantalla de Kike —tarjeta de 408 px— **no cambia nada**: nombre 100 +
 * cifra 149 + moneda 43 + huecos 26 son 318 de 408, así que sigue en una línea
 * igual que antes. Esta tarjeta ya se rompió una vez cuando una regla pensada
 * para cifras sueltas se le aplicó a una lista de pares; aquí la regla se
 * escribe para lo que esto es —pares que se leen en línea— y se comprobaron las
 * ocho cuentas antes y después. */
/* ─── UNA LÍNEA SIEMPRE · Kike, 19/08/2026 ─────────────────────────────────
 *
 * *«El widget de movimientos de las cuentas se está desalineando, se partió en
 * dos líneas; siempre debe medir una línea.»*
 *
 * Era `flex-wrap: wrap`, así que en cuanto el nombre y el importe no cabían el
 * importe bajaba de renglón. Con `nowrap` **cede el nombre**, que es lo de largo
 * libre: ya tiene `flex: 1 1 8ch` con puntos suspensivos y su `title` con el
 * nombre entero. *Lo de largo libre cede; una cifra, nunca* — el importe sigue
 * con `flex: 0 0 auto` y no se encoge.
 *
 * Medido antes: a 150 %% las dos AMEX se partían al ancho real de la tarjeta
 * (471 px), y a 175 %% se partían seis de ocho. */
.saldo-cuenta {
  display: flex; flex-wrap: nowrap; align-items: baseline;
  gap: var(--space-1) var(--space-3);
  padding-block: var(--space-1);
  border-bottom: 1px solid var(--border);
}
.saldo-cuenta:last-child { border-bottom: 0; }
.saldo-nombre {
  flex: 1 1 8ch; min-inline-size: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Cifras tabulares para que las comas caigan una debajo de otra: una columna de
   importes que no alinea obliga a leer cada número entero. */
/* ─── EL IMPORTE ES UNA PIEZA: LA CIFRA Y SU MONEDA ─────────────────────────
 * Sueltas se separaban al envolver —la moneda caía sola a un tercer renglón por
 * 3,75 px— y una moneda lejos de su número no dice de qué número habla. Juntas,
 * o caben las dos o bajan las dos. Solo en el caso extremo, cuando el importe
 * entero no cabe en un renglón, se permite que se parta por dentro: mejor tres
 * renglones acotados que el letra-por-letra que había. */
.saldo-importe {
  display: inline-flex; align-items: baseline; flex-wrap: wrap;
  gap: var(--space-1) var(--space-2);
  flex: 0 0 auto;
  /* ─── PERO NUNCA MÁS ANCHO QUE SU RENGLÓN ────────────────────────────────
   *
   * `flex: 0 0 auto` dice «no me encojas», y sin techo eso significa «me salgo».
   * Medido el 12 de agosto de 2026 en Movimientos: la pareja cifra+moneda pedía
   * 346,7 px dentro de una lista de 196,9 y **se salía 105 px por el borde de
   * la tarjeta** a máximo@1165 —y 43,4 a grande@1165, 36 a muy_grande@1165 y
   * 27 a máximo@1400—. El caso «se niega a encoger», no §8.95.
   *
   * `max-inline-size: 100%` no encoge nada mientras quepa —las nueve
   * combinaciones que estaban bien salen bit a bit iguales— y solo en las
   * cuatro que desbordaban obliga a la pareja a partirse por dentro, que es
   * justo la salida que el comentario de arriba ya dejaba abierta para el caso
   * extremo. Quedan 18 px a máximo@1165: los de la cifra sola, que es `nowrap`
   * y no se parte a propósito. */
  max-inline-size: 100%;
}
/* AL FINAL POR EL NOMBRE QUE CRECE, no por un margen automático. Con
   `margin-inline-start: auto` el importe se pegaba al borde derecho **también
   cuando bajaba de renglón**, y un margen automático se come el hueco antes que
   el `flex-grow`: quitarlo deja que el hueco se lo lleve el nombre, que es
   quien tiene que crecer. */
.saldo-cifra { font-variant-numeric: tabular-nums; white-space: nowrap; }
.saldo-cifra.es-negativo { color: var(--expense-ink); }
/* El suelo de `4ch` se retira: los códigos que se muestran son de tres letras
   (PEN, USD), así que solo añadía relleno, y ese relleno era lo que empujaba la
   pareja 2,8 px fuera de su lista a normal@1165 — el mismo widget, la escala en
   la que todo lo demás está bien. Sin él, esa combinación queda exactamente
   como estaba (alto de tarjeta 704 px antes y después). */
.saldo-moneda { min-width: 0; text-align: end; }

/* La regla que aquí había —volver la rejilla de tres columnas a dos por debajo
   de 720 px— se retira: era el parche del mismo defecto mirado desde el móvil,
   y se apoyaba en el ancho de la VENTANA cuando lo que estrangulaba el nombre
   era el ancho de la TARJETA. Con `flex-wrap` el reparto lo decide el sitio que
   hay de verdad, así que vale igual en el teléfono y en una columna estrecha de
   escritorio, que es donde la ventana no avisaba de nada. */


/* ---------- Escala de anchos de campo (§9.1) ---------- */
/* CINCO ESCALONES, no anchos a ojo. Un campo tan ancho como su vecino dice que
   espera lo mismo que él: una fecha en un control de treinta caracteres invita a
   escribir algo que no cabe. El ancho es la primera pista de qué se espera.

   TODOS TOPADOS CON `min(<rem>, 100%)`, y ese `100%` es la parte que importa:
   los escalones van en `rem` y escalan con la preferencia de texto, así que a
   1,75× el escalón «largo» pide 32rem = 952 px. Sin el tope forzaría scroll
   horizontal — el fallo que §8.5 describe y que costó un turno entero perseguir.
   El tope no quita accesibilidad: el texto sigue escalando dentro del control. */
/* ─── Y `.combo-boton` ES EL CONTROL, NO UN ADORNO ────────────────────────
   Los cuatro escalones nombraban `.input` y `.select`. En una referencia el
   `<select>` se esconde en cuanto hay JavaScript —`.combo-nativo-oculto`— y lo
   que se ve y se pulsa es `.combo-boton`, que no estaba aquí: **el escalón
   declarado no llegaba a ningún selector de referencia**.

   Medido el 9 de agosto de 2026 en Recursos, con las tres hojas reales dentro
   de un `<iframe srcdoc>` del ancho a medir:

       una columna, 700 px    «Moneda» (medio, tope 340)    627 px
       dos columnas, 1.280    «Moneda» (medio)              351 px, igual
                              que «Nombre» (largo, 190 car.)

   Es decir: la moneda —tres opciones de una palabra— salía tan ancha o más que
   el nombre. El ancho es «la primera pista de qué se espera» y estaba diciendo
   lo contrario de lo que el descriptor declara.

   Se nombran los tres controles en los cuatro escalones y no se escribe una
   regla aparte para el combo: una segunda lista de topes es la forma segura de
   que un día digan cosas distintas. */
.field[data-ancho="minimo"]   .input,
.field[data-ancho="minimo"]   .select,
.field[data-ancho="minimo"]   .combo-boton { max-width: min(8rem, 100%); }
.field[data-ancho="corto"]    .input,
.field[data-ancho="corto"]    .select,
.field[data-ancho="corto"]    .combo-boton { max-width: min(12rem, 100%); }
.field[data-ancho="medio"]    .input,
.field[data-ancho="medio"]    .select,
.field[data-ancho="medio"]    .combo-boton { max-width: min(20rem, 100%); }
.field[data-ancho="largo"]    .input,
.field[data-ancho="largo"]    .select,
.field[data-ancho="largo"]    .combo-boton { max-width: min(32rem, 100%); }
/* «completo» no declara ancho: ocupa lo que le dé su columna. */

/* ---------- La acción de la fila: palabra con ancho, icono sin él ---------- */
/* Con ancho manda la palabra: «Editar» se lee sin aprender un símbolo. */
/* ─── AQUÍ VIVÍA UN INTERRUPTOR QUE YA NO INTERRUMPE NADA ─────────────────
   `.icono-accion` nacía en `display: none` y se revelaba en dos sitios: en las
   acciones de catálogo y —en móvil— en el lápiz de Movimientos, que en
   escritorio enseñaba la palabra «Editar» en su lugar.

   Al igualar ese lápiz al resto el 6 de agosto de 2026 quité la regla móvil
   por muerta. No lo estaba: **era la única que lo hacía visible**, así que el
   botón quedó de 44×44 con NADA dentro — un cuadrado vacío. El sprite y el
   `<use>` estaban perfectos; lo que faltaba era que se dibujara.

   Y mi comprobación de ayer midió la CAJA y no el contenido, que es la misma
   zona ciega que este proyecto lleva anotada cinco veces.

   Sin palabra que alternar, el interruptor sobra: un icono de acción se ve
   siempre. Se retiran las dos reglas y la clase del marcado. */

@media (max-width: 720px) {
  /* TRES COLUMNAS, no dos filas. La acción pasa a la derecha del importe y la
     segunda fila de la rejilla —66 px en normal, 100 en máxima— desaparece.
     §8.1 pide exactamente esto: en móvil las acciones van dentro del registro. */
  /* LA TABLA TIENE QUE DEJAR DE SER TABLA. Las filas ya eran rejilla, pero el
     `<table>` seguía repartiendo el ancho con su propio algoritmo: con dos
     columnas coincidía por suerte, y al añadir la tercera el concepto se
     quedó en 36 px con la insignia partida en 296 px de alto — la fila SUBIÓ
     de 210 a 456 en vez de bajar. En bloque, la rejilla reparte de verdad. */
  .movs.responsiva, .movs.responsiva tbody { display: block; width: 100%; }
  /* LA ACCIÓN SALE DEL FLUJO, y ésta es la tercera forma que probé: las dos
     anteriores están medidas y descartadas.

       · En su propia fila de rejilla —como estaba— costaba 66 px en escala
         normal y 100 en máxima, para un botón de 44.
       · Como TERCERA COLUMNA dejaba al concepto en 83 px de los 294, y en
         CERO a escala máxima: la fila subía a 219 px en vez de bajar. En un
         teléfono no hay ancho para concepto, importe y acción en línea.

     Absoluta no ocupa ni columna ni fila: se apoya en la esquina que ya
     estaba vacía. La rejilla vuelve a sus dos columnas y el concepto
     recupera su ancho. §8.1 pide la acción dentro del registro, y ahí está.

     El importe reserva su hueco con `padding` en vez de esquivarlo a ojo:
     así nunca queda debajo del botón por larga que sea la cifra. */
  .movs.responsiva tbody tr { position: relative; }
  /* PROPORCIÓN FIJA, NO `auto`. Con la segunda columna en `auto`, el importe
     crecía con la escala de texto y se quedaba con el ancho: el concepto caía
     de 140 px a 103, 64 y 25 según se subía el tamaño. Justo al revés de lo
     que hace falta — quien amplía el texto necesita MÁS sitio para leer el
     concepto, no menos. Es el mismo error de eje que §8.5 describe.
     Con 1,3fr contra 1fr el reparto no depende del largo de la cifra. */
  .movs.responsiva tbody tr { grid-template-columns: minmax(0, 1.3fr) minmax(0, 1fr); }
  .movs.responsiva tbody td.col-acciones {
    position: absolute; inset-block-start: var(--space-3); inset-inline-end: var(--space-4);
  }
  /* EL IMPORTE SE VA ABAJO Y LA ACCIÓN ARRIBA: no coinciden nunca, así que no
     hay que reservarle hueco con `padding`. Reservarlo fue el intento previo y
     salió peor —el `padding` engorda la columna `auto`, que se llevó 164 px de
     los 294 y dejó al concepto en 83—. Dos elementos que no comparten franja
     no necesitan negociar el ancho. */
  .movs.responsiva tbody td.col-amt { align-self: end; }
  /* FUERA LOS RÓTULOS DE ESAS DOS CELDAS. En modo tarjeta cada celda lleva su
     `data-etiqueta` encima, y con tres columnas eso salía carísimo: «ACCIONES»
     medía 91 px sobre un botón de 44, y «MONTO» ensanchaba su columna 33 px.
     Entre las dos dejaban al concepto en 36 px de los 294.
     Se pueden quitar porque ninguna de las dos lo necesita: un importe lleva
     su símbolo de moneda delante y se reconoce solo, y la acción lleva su
     nombre en `aria-label`. El rótulo del concepto SE QUEDA. */
  .movs.responsiva tbody td.col-amt::before,
  .movs.responsiva tbody td.col-acciones::before { content: none; }
  /* Ya no hay palabra que esconder: el control es un lápiz en todos los anchos
     desde el 6 de agosto de 2026, como en los demás módulos. Se conserva solo
     el objetivo táctil, que es lo que esta regla aportaba de verdad. */
  .movs.responsiva .js-editar-mov {
    min-width: var(--tactil-minimo); min-height: var(--tactil-minimo);
    padding-inline: var(--space-2); justify-content: center;
  }
}

@media (max-width: 720px) {
  /* EN MÓVIL EL SALDO VIVE EN INICIO, NO AQUÍ.
     §8.1 marca «ver saldo» como tarea primaria de móvil y le da su pantalla:
     Inicio, a un toque en la barra inferior. Repetirlo en Movimientos costaba
     570 px de cabecera —medidos— en la pantalla que existe para ver movimientos,
     y por ese coste no se veía ninguno al cargar.
     En escritorio se queda: allí ocupa un tercio que estaba vacío. */
  .totales-mov .bloque-saldos { display: none; }
}

/* ---------- Reparto por categoría: barras horizontales (§8.8) ---------- */
/* La etiqueta va PEGADA a su barra y en texto, no en una leyenda: §8.4 no admite
   que el significado dependa del color, y un texto escala con la preferencia de
   tamaño mientras que un ángulo no. Ésa fue la razón que decidió la forma. */
.grafico-categorias { display: grid; gap: var(--space-4); min-width: 0; }
.grafico-grupo { min-width: 0; }
.barras-categoria { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-2); }
/* DOS COLUMNAS Y DOS FILAS, no tres columnas. Con el importe y el porcentaje
   en la misma línea que el rótulo, a 221 px por lado a la etiqueta le
   quedaban 60 y se partía a mitad de palabra: «Impue/stos», «Alimenta/ción».
   Es la misma hambre de ancho de la fila-tarjeta, en otro sitio: dos cosas
   que crecen con el texto compitiendo por una franja que no crece.
   El porcentaje baja a la línea de la barra, donde había hueco de sobra. */
/* ─── SE PARTE CUANDO NO CABE, NO CUANDO LA ESCALA ES ALTA ─────────────────
 *
 * Esto era una rejilla de dos columnas en proporción fija —1fr y 1,1fr—, y la
 * proporción estaba puesta para que el importe no se comiera al rótulo. Lo
 * conseguía, pero **el reparto no sabe cuánto mide el contenido**: el importe
 * lleva `nowrap`, así que cuando su texto pedía más de lo que la proporción le
 * daba, no se recortaba, se **salía por la izquierda encima de la etiqueta**.
 * Medido en «Email & Apps»: 161 px de contenido en una columna de 123, y en
 * pantalla se leía «Email & A/S/ 5.191,89».
 *
 * El primer arreglo fue bajar el importe a su propia línea a partir de la
 * escala máxima. Estaba mal atado: en Cobros ya se pisaba a `muy_grande` —2
 * filas de 4— y en Movimientos, donde no hacía falta, la tarjeta engordaba de
 * 1.007 px a 1.260 por partir ocho filas que cabían.
 *
 * La condición correcta no es la escala, es **si cabe**. En flexbox con
 * `wrap` la pregunta la responde el navegador fila por fila: si el importe no
 * entra al lado del rótulo, baja solo esa fila. El rótulo tiene un suelo de
 * `6rem` —en `rem`, así que crece con la preferencia de tamaño— porque sin él
 * se encogería hasta cero antes de empujar nada, que es justo el defecto que
 * la proporción fija venía a evitar. */
.barra-fila {
  display: flex; flex-wrap: wrap;
  align-items: baseline; gap: 0 var(--space-2); min-width: 0;
}
/* ─── EL IMPORTE Y EL PORCENTAJE, EN LA MISMA LÍNEA ─────────────────────────
 *
 * El porcentaje vivía en una segunda fila de la rejilla, bajo el importe. Se
 * había bajado ahí para que la etiqueta no se estrujara, y resolvía eso a costa
 * de **una línea de alto por barra**: con cinco barras, el widget del centro
 * crecía cinco líneas más que sus vecinos y el bloque entero se estiraba.
 *
 * Ahora van juntos en la misma celda, alineados a la derecha. La etiqueta
 * conserva su columna y sigue recortándose con puntos suspensivos si no cabe
 * —el nombre completo sigue en el DOM y en `title`—, así que la protección que
 * motivó el cambio original se mantiene sin pagar el alto. */
.barra-valores {
  /* `margin-inline-start: auto` lo pega a la derecha cuando comparte línea con
     el rótulo, y cuando baja a la suya lo deja igualmente a la derecha: la
     columna de importes se sigue barriendo en vertical, que es para lo que
     existe el gráfico. `flex: 0 0 auto` es lo que hace que NO se encoja por
     debajo de su texto — encogerse era lo que lo hacía desbordar. */
  flex: 0 0 auto; margin-inline-start: auto;
  display: flex; align-items: baseline; gap: var(--space-2);
  white-space: nowrap;
  /* Pero «no me encojas» sin techo es «me salgo»: medido el 12 de agosto de
     2026 en Movimientos, el par cifra+porcentaje pedía 296,8 px en una tarjeta
     de 332 con relleno y se salía 9,4 px a máximo@1165. El techo del 100 % y el
     `wrap` dejan que el porcentaje baje bajo la cifra en esa combinación —cada
     pieza sigue sin partirse por dentro— y no tocan ninguna otra. */
  max-inline-size: 100%; flex-wrap: wrap;
}
/* UNA LÍNEA, CON RECORTE. No es estética: si un nombre largo ocupa dos
   líneas, esa barra empuja las de abajo y las columnas de importe y
   porcentaje dejan de alinearse. Con cinco barras por lado, una desalineada
   rompe el barrido vertical entero — que es para lo que existe el gráfico.
   El nombre completo no se pierde: sigue en el DOM y en `title`. */
.barra-etiqueta {
  /* El suelo de `6rem` es lo que decide el salto: hasta ahí el rótulo cede
     ancho y se recorta con puntos; de ahí para abajo no cede más y es el
     importe el que baja de línea. En `rem` para que el suelo crezca con la
     preferencia de tamaño, no contra ella (§8.1). */
  flex: 1 1 auto; min-width: 6rem;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.barra-cifra { font-variant-numeric: tabular-nums; white-space: nowrap; }
.barra-pct { color: var(--text-secondary); font-size: var(--fs-caption); white-space: nowrap; }
/* La pista ocupa toda la fila debajo del rótulo: así la longitud se compara
   entre barras aunque las etiquetas midan distinto. */
/* LA PISTA CRUZA LA FILA ENTERA. Antes ocupaba solo la primera columna, y el
   comentario decía que así se comparaba entre barras — pero con el porcentaje
   fuera de esa fila, ya no hay nada que la limite: ahora sí ocupa todo el
   ancho, que es lo que aquel comentario quería. */
.barra-pista {
  flex: 0 0 100%; block-size: .6em; align-self: center; margin-block-start: 2px;
  background: var(--surface-hover); border-radius: var(--radius-sm); overflow: hidden;
}
.barra-tramo { display: block; block-size: 100%; background: var(--brand); }
.barra-tramo.barra--in { background: var(--income); }
.barra-tramo.barra--out { background: var(--expense); }
/* «Otras» en neutro: no es una categoría, es el resto que cierra el total. */
.barra-tramo.es-otras { background: var(--text-muted); }


.grafico-frase { margin: 0; }

/* Qué se borra, destacado sobre la explicación: el error que de verdad ocurre
   es el de fila, y se detecta leyendo el concepto y el importe, no el aviso. */
.neto-cifra-chica { font-size: var(--fs-md); font-weight: var(--fw-semibold); margin: 0; }

/* ---------- Acceso (§8.5) ----------
   CENTRADA EN LOS DOS EJES, PERO CON `margin: auto` Y NO CON `align-items`.
   No es lo mismo aunque se vea igual cuando sobra sitio: con `align-items:
   center`, si el contenido es más alto que la ventana el sobrante se sale por
   ARRIBA y no se puede alcanzar —el navegador no deja desplazarse por encima
   del inicio del contenedor—. Con `margin: auto` el navegador reparte el hueco
   solo cuando lo hay, y cuando no lo hay la caja se comporta como un bloque
   normal y se desplaza entera.

   `100dvh` y no `100vh`: en móvil la barra del navegador hace que `100vh` se
   pase de largo y deje la caja fuera de cuadro. */
.acceso-marco {
  display: flex; min-height: 100dvh;
  padding: var(--space-4);
}
.acceso-caja { margin: auto; width: 100%; max-width: 26rem; }   /* el centrado que no recorta */

.acceso-marca { text-align: center; margin-block-end: var(--space-5); }
.acceso-isotipo { display: inline-block; }
/* Se cambia la pieza, no el color: en alto contraste la caja azul resta el
   contraste que ese modo existe para dar. */
.acceso-isotipo--mono { display: none; color: var(--text); }
:root[data-contrast="high"] .acceso-isotipo--caja { display: none; }
:root[data-contrast="high"] .acceso-isotipo--mono { display: inline-block; }

.acceso-nombre {
  margin: var(--space-3) 0 0; font-size: var(--fs-xl);
  font-weight: var(--fw-medium); letter-spacing: 0.015em;
}
.acceso-lema { margin: var(--space-micro) 0 0; font-size: var(--fs-caption); }

/* La casilla y su texto son UN solo objetivo táctil: en un teléfono acertarle a
   un cuadrito de 13 px no es razonable, y la etiqueta ya es parte del control. */
.acceso-recordar {
  display: flex; align-items: center; gap: var(--space-3);
  min-height: var(--tactil-minimo);
  cursor: pointer;
}
.acceso-recordar input { width: 1.25rem; height: 1.25rem; flex: none; accent-color: var(--brand); }
/* El acento de color no puede ser la única señal de marcado: en alto contraste
   se le añade trazo, que sí se ve sin distinguir colores. */
:root[data-contrast="high"] .acceso-recordar input { outline: 2px solid var(--text); outline-offset: 1px; }

.acceso-entrar { width: 100%; min-height: var(--tactil-minimo); }

/* ---------- Los requisitos de la contraseña ----------
   La marca cambia de SÍMBOLO y de texto, no solo de color: §8.5 dice que el
   color acompaña y no informa, y esta lista la lee alguien que quizá no
   distinga verde de gris. El «✓» y el «·» los pone el guion; aquí solo el
   color, que es la redundancia. */
.requisitos-clave {
  list-style: none; margin: 0; padding: 0;
  display: grid; gap: var(--space-micro);
  font-size: var(--fs-caption); color: var(--text-secondary);
}
.requisitos-clave li { display: flex; align-items: baseline; gap: var(--space-2); }
.requisitos-clave .requisito-marca {
  /* Ancho fijo para que el texto no baile al cambiar «·» por «✓». */
  inline-size: 1em; flex: none; text-align: center; font-weight: var(--fw-medium);
}
.requisitos-clave .requisito--cumple,
.requisitos-clave li.requisito--cumple { color: var(--income-ink); }
.requisitos-clave li.requisito--cumple .requisito-marca { color: var(--income); }

/* La columna del estado del acceso lleva una frase, no una palabra: sin un
   ancho mínimo la tabla se la reparte a una letra por línea. `ch` y no `px`
   porque lo que tiene que caber es texto, y escala con la letra de quien mira. */
.col-estado-acceso { min-inline-size: 26ch; }

/* Un `<form>` alrededor de un botón dentro de una fila de acciones mete una caja
   de bloque en medio del `flex` y rompe la fila. `display: contents` deja al
   botón donde debe estar sin quitarle su formulario, que es lo que lleva el
   testigo y el identificador. */
.acciones-en-fila { display: contents; }

/* ---------- Facturas ----------
   LAS DOS CIFRAS EN CAJAS SEPARADAS Y SIN TOTAL (§5.8). No es una decisión
   visual: facturado y base imponible son magnitudes distintas del mismo hecho y
   una caja común invitaría a leerlas como partes de una suma. */
.factura-cifras {
  display: grid; gap: var(--space-4);
  grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
  margin-block-start: var(--space-4);
}
/* «sin calcular» NO se disfraza de cifra: si se pareciera a un número, un cero
   se leería como «no lleva impuestos» (§4.10). */
.stat-valor--ausente { font-weight: var(--fw-regular); color: var(--text-muted); font-style: italic; }
.factura-nota { margin-block-start: var(--space-3); }

.filtros summary {
  cursor: pointer; min-height: var(--tactil-minimo);
  display: flex; align-items: center; font-weight: var(--fw-medium);
}
.filtros-rejilla {
  display: grid; gap: var(--space-4);
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
  margin-block-start: var(--space-3);
}
.filtros-acciones { display: flex; gap: var(--space-3); align-items: end; flex-wrap: wrap; }

/* Anchos en `ch`, que crecen con la escala. La tabla NO fija `table-layout`:
   con `<thead>` las medidas se toman de la cabecera y no de la primera fila,
   que fue lo que estranguló la columna del concepto en los catálogos. */
/* La misma forma que `.col-com-factura`, y el mismo dato: **el número de la
   factura**, que es el único de ancho libre de los ocho selectores de columna
   con ancho fijo y `nowrap`. Los otros seis llevan fecha o iconos, de largo
   conocido. Aquí no se ha visto pisar todavía —hoy solo hay UNA factura de más
   de 12 caracteres en todo Flow— pero la celda mide 12ch y ese número tiene 17:
   *en cuanto llegue otra, vuelve*. */
.tabla-facturas .col-numero    { width: 12ch; white-space: nowrap; overflow: hidden; }
.tabla-facturas .col-numero > a {
  display: block; max-width: 100%;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.tabla-facturas .col-fecha     { width: 11ch; white-space: nowrap; }
.tabla-facturas .col-categoria { max-width: 20ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* «Cobrada a mano» son 14 caracteres y NO puede partirse en dos renglones:
   el distintivo se lee de una pieza o no separa nada. Es la misma regla que
   `col-numero` y `col-fecha`, y la columna no la tenía porque no tenía clase. */
.tabla-facturas .col-estado    { white-space: nowrap; }
/* ─── EL RECORTE LO HACE PHP, NO LA HOJA ────────────────────────────────────
 *
 * Este `max-width` cortaba por el FINAL, que es justo donde una razón social
 * peruana lleva lo que la distingue —el nombre comercial tras el guion, el
 * número romano—. Medido sobre los 451 clientes reales: **9 clientes distintos
 * salían con el mismo texto, en 4 grupos**. «JUNTA DE PROPIETARIOS DEL CE…»
 * eran EL POLO I y EL POLO II.
 *
 * Ahora la celda llega ya acortada por el medio desde `TextoPlano::porElMedio()`
 * —24 caracteres, cero ambigüedad medida—, así que un segundo recorte aquí solo
 * podría volver a comerse la cola. Se quita el tope y se deja `nowrap`: la fila
 * sigue en una línea y el ancho lo gobierna un límite que se puede contar.
 *
 * DOS MECANISMOS PARA LO MISMO ERA EL PROBLEMA: el de la hoja mide píxeles y no
 * se puede contar desde aquí; el de PHP mide caracteres y sí. */
.tabla-facturas .col-cliente   { white-space: nowrap; }
/* El monto va alineado a la derecha y el sector viene justo después: sin este
   respiro las dos columnas se tocaban y la cifra parecía parte del sector.
   El hueco es del MONTO, no del sector, porque es el borde que se pega. */
/* ─── LA ALINEACIÓN ES DE LA COLUMNA, NO DE LA TABLA QUE LA CONTIENE ───────
 *
 * Estaba escrita como `.tabla-facturas .col-monto, .tabla-notas .col-monto`, y
 * **Compras no tiene modificador**: su tabla es `class="tabla"` a secas. Así
 * que sus columnas «Impuesto» y «Total» salían alineadas al principio mientras
 * las de Facturas iban a la derecha, con las dos llamándose `col-monto`.
 *
 * Medido el 6 de agosto de 2026 sobre la primera compra real de Kike. Y nombrar
 * a Compras aquí habría sido **la tercera copia** de la misma regla — el mismo
 * error que este bloque ya corrigió una vez cuando notas repetía su versión.
 *
 * `col-monto` ya dice lo que la columna es: una cifra. Una cifra se alinea a la
 * derecha para que los dígitos cuadren, y eso no depende de en qué listado
 * esté. Alcanza también al registro de ventas, a la ficha de cliente y a los
 * contadores de las llaves de API — comprobado que las seis columnas son
 * numéricas.
 *
 * El REALCE se queda abajo con su alcance: subir de cuerpo y poner en negrita
 * sí depende de la pantalla —en Facturas la cifra es lo que se viene a leer; en
 * el registro de ventas es una columna más de una tabla densa—. */
.tabla .col-monto {
  text-align: right; white-space: nowrap;
  padding-inline-end: var(--space-5);
}
/* Lo mismo con las acciones: iban a la derecha solo en Facturas. */
.tabla .col-acciones { width: 1%; white-space: nowrap; text-align: right; }
/* La cifra es lo que se viene a leer en este listado: sube de cuerpo y va en
   negrita. El desglose en moneda original NO la sigue —se quedaría gritando
   dos veces— y mantiene su tamaño pequeño y su color secundario. */
.tabla-facturas .col-monto,
.tabla-notas .col-monto           { font-size: 1.05rem; font-weight: 700; }
.tabla-facturas .col-monto .caption,
.tabla-notas .col-monto .caption   { font-size: var(--fs-caption); font-weight: 400; }
/* «sin derivar» NO es una cifra: no se pone en negrita ni sube de cuerpo, o
   parecería un importe grande cuando es la ausencia de uno (§4.10). */
.tabla-notas .col-monto .text-secondary { font-size: var(--fs-caption); font-weight: 400; }
.tabla-envoltura { overflow-x: auto; }
.paginacion {
  display: flex; align-items: center; gap: var(--space-4);
  justify-content: center; margin-block-start: var(--space-4); flex-wrap: wrap;
}

/* ---------- Ficha de factura ---------- */
/* DOS COLUMNAS COMO MÁXIMO, y una cuando dos no caben. El porqué de la
   expresión está donde se midió, en `.dlg-ficha .ficha-datos`. Se declara aquí
   para que la ficha se reparta igual en la página y en el diálogo: el mismo
   contenido con dos repartos son dos pantallas que hay que aprender dos veces.
   `max-inline-size` es lo que evita el otro extremo: en la página caben 2.100 px
   y dos columnas de mil dejan la etiqueta y su valor perdidos a la izquierda. */
/* EL `min(…, 100%)` DE FUERA NO ES ADORNO. Sin él, el suelo de 18 rem no encoge
   y a partir de cierto punto la rejilla es MÁS ANCHA que su caja: medido, con el
   cuerpo del diálogo a 383 px salía barra horizontal, que es el caso del móvil.
   Un suelo nunca puede pasar del 100 % del contenedor. */
.ficha-datos {
  display: grid; gap: var(--space-4);
  grid-template-columns: repeat(auto-fit, minmax(min(max(18rem, 48%), 100%), 1fr));
  max-inline-size: 76rem;
  margin-block-start: var(--space-4);
}
.ficha-datos dt { font-size: var(--fs-caption); color: var(--text-secondary); text-transform: uppercase; letter-spacing: .04em; }
.ficha-datos dd { margin: var(--space-micro) 0 0; }
/* Un campo que se lleva el resto de su fila. Ver el porqué en
   `factura/ficha.php`: la descripción envolvía en cinco líneas con tres
   columnas vacías al lado. `1 / -1` no supone cuántas columnas hay. */
.ficha-datos .campo-destacado--ancho { grid-column: 1 / -1; }
/* ─── UN BLOQUE DENTRO DE UNA TARJETA ──────────────────────────────────────
   El comprobante vive dentro de «Datos de la factura», y ahí hace falta decir
   dónde acaban los datos y empieza el archivo SIN meter una segunda caja: dos
   marcos anidados leen como dos áreas y la idea era la contraria.
   Una regla arriba y aire hacen esa separación con menos tinta. `--border-strong`
   y no `--border` por lo mismo que la tarjeta: es un límite de área, no un hilo
   entre filas. */
.ficha-bloque {
  margin-block-start: var(--space-5);
  padding-block-start: var(--space-4);
  border-block-start: 1px solid var(--border-strong);
}
.ficha-bloque .card-head { margin-bottom: var(--space-3); }
/* ─── UN CAMPO DE TEXTO PLANO CON SALTOS DENTRO ────────────────────────────
 *
 * `pre-line` y no `pre`: respeta los saltos y **sigue colapsando** los espacios
 * seguidos y envolviendo las líneas largas. Con `pre`, una descripción de 1.340
 * bytes sin saltos empujaría la ficha a lo ancho hasta desbordar.
 *
 * Hace falta desde que las descripciones se limpiaron (6 de agosto de 2026):
 * antes traían `<p>` y `<br>`, que el navegador tampoco pintaba —el texto se
 * escapaba y salían las etiquetas o, con `strip_tags`, se pegaban las
 * palabras—. Ahora el salto es un salto de verdad, y 1.218 de 3.176
 * descripciones tienen alguno. */
/* ── Y `pre-line` NO PARTE LO QUE NO TRAE ESPACIOS ─────────────────────────
 *
 * `pre-line` envuelve donde hay una oportunidad de corte, y una tirada larga
 * sin espacios no tiene ninguna. Medido el 7 de agosto de 2026 en la ficha de
 * cobro del cliente, con una nota de gestión de 1.000 caracteres seguidos: el
 * cuerpo del diálogo pasó a **10.102 px de ancho** contra 780 visibles, con
 * barra horizontal y el resto de la ficha arrastrado con él.
 *
 * Mil caracteres seguidos no es prosa, pero **una dirección web sí lo es**: las
 * descripciones que llegaron de Money ya traen `https://…` de 47 caracteres, y
 * una nota que apunte un enlace de pago o una referencia bancaria pasa de ahí
 * sin esfuerzo. Y el campo admite 1.000 sin exigir un solo espacio.
 *
 * `anywhere` y no `break-word`: `break-word` no reduce el ancho mínimo de la
 * caja, así que en una rejilla o una celda estrecha sigue desbordando. */
.texto-con-saltos { white-space: pre-line; overflow-wrap: anywhere; }
.lista-marcas { list-style: none; padding: 0; margin: var(--space-3) 0 0; display: grid; gap: var(--space-3); }
.lista-marcas li { display: flex; gap: var(--space-3); align-items: baseline; flex-wrap: wrap; }

/* El número sin comprobante es un BOTÓN, no un enlace: no lleva a ninguna
   parte, avisa. Se ve como texto normal y no como enlace, porque parecer
   enlace y no serlo es el engaño que §8.5 evita. Conserva foco y teclado. */
.enlace-falso {
  background: none; border: 0; padding: 0; font: inherit; color: var(--text);
  cursor: help; text-align: left; text-decoration: underline dotted;
  text-underline-offset: 3px;
}
.tabla-facturas .col-sector { max-width: 16ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ─── EL PROPIETARIO SE QUEDA EN 13ch, Y LOS 3 QUE SUELTA VAN A LA RAZÓN SOCIAL
 *
 * Kike, 12 de agosto de 2026. Medido sobre las 3.177 facturas: la columna
 * reservaba 16ch y ni así enseñaba el nombre —«Enrique Mesta Arellano» son 22
 * caracteres y sale en 2.575 filas—. Con el primer nombre, el valor más largo
 * que existe es «ADMINISTRADOR» (13) y la palabra de la cabecera son 11, así
 * que 13ch es lo mínimo que enseña todos los primeros nombres enteros sin caer
 * por debajo del rótulo.
 *
 * Es `max-width` y no `width`: un tope no gasta lo que no necesita. El día que
 * «ADMINISTRADOR VIS» deje de ser propietario, la columna baja sola a los 11 de
 * la cabecera sin que nadie vuelva aquí. Por eso el 13 no envejece.
 *
 * Los 3 caracteres que suelta se los queda la razón social —de 24 a 27 en
 * `factura/listado.php`—, así que la tabla pide exactamente el mismo ancho
 * total que antes. No es sitio nuevo: es sitio movido de donde no se leía a
 * donde se lee. */
.tabla-facturas .col-propietario { max-width: 13ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* `.col-acciones` subió a `.tabla` — ver el bloque del monto. */
.tabla-facturas { width: 100%; }

/* El alta en dos columnas cuando hay ancho: doce campos en una sola columna
   obligan a desplazar para ver la cadena, que es justo lo que hay que mirar
   antes de guardar. Se pliega a una sola columna cuando no cabe. */
/* ---------- El formulario de factura: UNA COLUMNA ----------
   Decidido por el operador y con la ventaja de ser la MISMA forma en escritorio
   y en móvil — una sola disposición que verificar en vez de dos.

   `--form-max` en REM y no en píxeles: crece con la preferencia de escala, que
   es lo que §8.5 exige. 52rem son 832 px a escala normal — ancho de lectura
   cómodo, sin las 1.840 px vacías que dejaba estirarse a 2.560, y sin estirar
   las líneas de texto, que es lo que el límite evita. */
:root { --form-max: 52rem; }

/* ═══════════ EL REPARTO ES UNA CLASE, NO EL FORMULARIO DE FACTURAS ══════
   Todo esto colgaba de `#form-factura`. El reparto que costó cuatro escalas
   medir —una columna, anchos por contenido como máximos, dos por línea solo
   desde 1.100 px, `largo` y `completo` a fila entera— valía para cualquier
   formulario, pero por el identificador solo lo tenía uno. El de notas se
   dibujaba con la rejilla genérica y NADIE lo veía: no falla, reparte peor.

   Con la clase, el siguiente formulario lo hereda escribiendo una palabra. */
.form-reparte { max-width: var(--form-max); margin-inline: auto; }

/* ─── EL SUELO NO PUEDE SER MAYOR QUE EL HUECO · `D-96` · 16/08/2026 ─────────
 *
 * Era `minmax(15rem, 1fr)`, y **`15rem` crece con la escala de texto**: al
 * máximo son 446 px, impuestos como mínimo de pista aunque el hueco mida 365.
 * La rejilla no podía encoger, así que **se salía de la pantalla en vez de bajar
 * a una columna**.
 *
 * Medido en el navegador antes de tocar nada, a 380 px, sobre las **14 rutas que
 * la dibujan de verdad** —pedidas al servidor, no contadas en el código— por
 * cuatro escalas y cuatro temas: **44 de 224 combinaciones desbordaban**, hasta
 * 110 px, en 7 rutas. Las otras 7 iban dentro de `.form-reparte`, que ya fuerza
 * una columna.
 *
 * `min(15rem, 100%)` es el mismo remedio de `D-48` y `D-57`, que la rejilla de
 * la carga fiscal ya llevaba: *el suelo deja de ser absoluto y pasa a ser «15rem
 * o lo que haya, lo que sea menor».* Sin él, quien registra un movimiento desde
 * el teléfono con la letra grande —que es una de las dos cosas que Kike hace
 * desde el móvil— tiene que arrastrar la pantalla de lado para ver los campos. */
.rejilla-formulario {
  display: grid; gap: var(--space-4);
  grid-template-columns: repeat(auto-fit, minmax(min(15rem, 100%), 1fr));
}
/* ─── Y EL HIJO TIENE QUE PODER ENCOGER ─────────────────────────────────────
 *
 * `min()` en la pista **no bastó**: con la pista ya a 307 px los campos seguían
 * midiendo 393. Un hijo de rejilla lleva `min-width: auto`, que le impide bajar
 * de su contenido mínimo, así que **se salía del carril en vez de ajustarse**.
 * Medido: con la pista arreglada quedaban 40 desbordes de 224; con esto, 28.
 *
 * *La teoría decía que el suelo de la pista era todo el problema, y no lo era.*
 * Se deja escrito porque el siguiente que lea `D-48` pensará lo mismo. */
.rejilla-formulario > * { min-width: 0; }

/* ─── EL PIE DE DOS BOTONES ENVUELVE ANTES QUE SALIRSE ───────────────────────
 *
 * `.row` es `display:flex` sin `flex-wrap`, así que «Guardar la declaración» y
 * «Cancelar» uno al lado del otro **se salían de la pantalla a 380 px con la
 * letra al máximo** — el pie que puse el 15 de agosto de 2026. Envolviendo, cada
 * botón conserva sus 44 px y se leen los dos.
 *
 * Va acotado a `form` a propósito: `.row` la usa media aplicación y ensanchar
 * la regla sin medir esas pantallas sería cambiar lo que no se ha mirado. */
form .row { flex-wrap: wrap; }
/* Dentro del formulario de factura la rejilla es de UNA columna: los anchos por
   contenido siguen valiendo como máximos, no como repartos. */
.form-reparte .rejilla-formulario { grid-template-columns: 1fr; }
/* Y `span 2` CREA UNA SEGUNDA COLUMNA IMPLÍCITA aunque la plantilla declare
   una: la rejilla salía con dos pistas —688 px y 179— con la regla puesta y
   aplicándose. Medido en pantalla; leyendo el CSS parecía correcto. */
/* ─── LA REJILLA DE CATÁLOGO TENÍA EL MISMO PROBLEMA Y NINGUNA REGLA ──────
 *
 * `.form-grid` es de dos columnas desde 721 px, y encima cada campo reparte su
 * media columna entre la etiqueta —12rem— y el control. El resultado es que el
 * escalón declarado deja de mandar y **todos los campos miden lo mismo**.
 * Medido el 9 de agosto de 2026 en «Agregar recurso», con las tres hojas
 * reales dentro de un `<iframe srcdoc>` del ancho a medir:
 *
 *     ventana 1.280   nombre (largo, 190 car.) 351 px = moneda (medio) 351
 *     ventana 1.060   nombre 241 px = tipo = proveedor = cliente = moneda
 *     ventana   900   nombre 161 px = TODOS los demás, incluida la fecha
 *
 * Un nombre de 190 caracteres en 161 px enseña veinte. La regla de al lado ya
 * dice por qué esto se arregla dando fila propia —«un campo largo emparejado
 * deja una de las dos columnas medio vacía, que es peor que una fila propia»—;
 * lo único que faltaba era que alcanzara a la rejilla de los catálogos.
 * Con la fila propia, ese nombre pasa de 161/241/351 a sus 544 px en las tres.
 *
 * VA SOLO `largo`, NO `completo`, y esa es la diferencia con Facturas: aquí
 * `completo` es lo que devuelve la derivación para las CASILLAS, que ya viven
 * en una línea con su texto al lado (`.field--linea`). Darles la fila entera
 * añadiría una fila por cada sí/no sin ensanchar nada. Los `textarea`, el
 * icono y el color ya la piden desde la plantilla, con estilo en línea. */
.form-reparte .field[data-ancho="largo"],
.form-reparte .field[data-ancho="completo"],
form.form-etiquetado > .form-grid > .field[data-ancho="largo"] { grid-column: 1 / -1; }

/* ---------- Los tres que importan al emitir ----------
   Cliente, descripción y monto. «Más presencia» NO es solo cuerpo de letra: son
   tres señales combinadas —una marca lateral, la etiqueta más marcada y aire
   alrededor— porque el tamaño solo agranda y no jerarquiza. Y ninguna es color
   a secas (§8.5): la marca es geometría y el peso es peso.

   La marca va a la IZQUIERDA y fuera del control, de modo que no altera el
   ancho de los campos ni deja los bordes irregulares que cansan. */
.campo-destacado {
  border-inline-start: 3px solid var(--brand);
  padding-inline-start: var(--space-4);
  margin-block: var(--space-2);
}
.campo-destacado > .label {
  font-size: var(--fs-md); font-weight: var(--fw-semibold); color: var(--text);
}
[data-contrast="high"] .campo-destacado { border-inline-start-width: 5px; }

/* El monto es el que más se nota, y crece con la escala del usuario porque va
   en rem. Tabular para que las cifras no bailen al teclear, y `min-width: 0`
   para que pueda encogerse en vez de desbordar cuando la escala sube. */
/* ─── Y VALE SUELTA, NO SOLO DENTRO DE `campo-destacado` · 16/08/2026 ────────
 *
 * Kike: *«que los campos de números financieros siempre tengan números
 * grandes.»* Medido pidiendo las 45 pantallas: **4 campos de importe la tenían y
 * 44 no**, porque la regla estaba acotada al campo destacado y solo dos vistas
 * lo usan.
 *
 * Son **campos de escribir, no cifras de leer**: es donde Kike teclea un
 * importe, y equivocarse de dígito ahí cuesta dinero. Por eso la unidad es `rem`
 * y no un tamaño fijo: **grande de partida y creciendo con la escala**, como
 * todo lo demás.
 *
 * El destacado se queda **un punto por encima**: dentro de un formulario largo
 * hay un importe que manda —el total— y los demás lo acompañan. Que todos midan
 * exactamente igual quitaría esa jerarquía, que no es adorno: es lo que dice
 * dónde mirar primero. */
/* `1.5rem` a mano y **no `--fs-monto`**: ese vale 2,6rem —es para las cifras
   grandes de los widgets— y habría dejado el campo corriente MÁS grande que el
   destacado, que es justo al revés de la jerarquía que se quiere. */
/* ─── Y ALINEADO A LA DERECHA, COMO LOS OTROS DOS · `D-215` ─────────────────
 *
 * `.input-monto` y `.input-monto--medio` lo llevaban y éste no: **dos criterios
 * sobre los mismos 22 campos de importe**, que son *dos verdades esperando*. Y
 * en cifras la alineación a la derecha no es gusto: es lo que pone las unidades
 * bajo las unidades y permite compararlas de un vistazo, que es justo lo que
 * hace `tabular-nums` en la otra mitad del problema. */
.input-monto-grande {
  font-size: 1.5rem; font-weight: var(--fw-semibold);
  font-variant-numeric: tabular-nums; text-align: right;
  min-width: 0; width: 100%;
  padding-block: var(--space-2);
}
.campo-destacado .input-monto-grande {
  font-size: 2rem;
  padding-block: var(--space-3);
}

/* ---------- Bloques de un formulario largo ----------
   Dieciséis campos seguidos son una columna en la que nadie encuentra nada. El
   separador es SIGNIFICADO y no adorno (§16): agrupa lo que se responde junto.
   Es un `<fieldset>` de verdad —no un div con borde— porque quien navega con
   lector de pantalla salta de agrupación en agrupación.
   Sin borde alrededor: la línea del título ya separa, y cuatro marcos anidados
   dentro de la tarjeta serían ruido. */
.bloque-campos {
  border: 0; padding: 0; margin: 0;
  margin-block-start: var(--space-5);
  min-width: 0;                      /* si no, la rejilla no puede encoger */
}
.bloque-campos:first-of-type { margin-block-start: 0; }
/* ─── LO QUE VA DESPUÉS DEL ÚLTIMO BLOQUE TAMBIÉN NECESITA AIRE ────────────
 *
 * `.bloque-campos` se separa a sí mismo. Las filas de acciones que cierran una
 * ficha —archivar, editar, eliminar— no son `.bloque-campos` y no traen margen,
 * y su envoltura (`.ficha-compra`) es un bloque plano sin `gap`: el resultado es
 * CERO. Medido el 7 de agosto de 2026 en la ficha de la compra 26080701, dentro
 * del diálogo y también como página:
 *
 *     «El comprobante»  →  fila de «Archivar»                 0 px
 *     fila de «Archivar» →  «Editar / Eliminar compra»         0 px
 *
 * Tres botones pegados al pie del último dato y pegados entre sí. La misma
 * pareja de componentes dentro de la ficha de FACTURA sí queda separada, porque
 * allí el cuerpo del diálogo es una rejilla con `gap`. Dos fichas equivalentes
 * con dos resultados es lo que esto cierra.
 *
 * `--space-5` antes de la primera —es el salto de sección, el mismo que separa
 * los bloques— y `--space-3` entre las dos, que son el mismo grupo de acciones
 * y no dos secciones. */
.ficha-compra > .row { margin-block-start: var(--space-5); }
.ficha-compra > .filtros-acciones { margin-block-start: var(--space-3); }
/* ─── Y LO MISMO DENTRO DE UN BLOQUE, EN LA FICHA DE COBRO ────────────────
 *
 * Ahí el `<legend>` sí separa —13 px— pero **lo que viene después no separa
 * nada**. Medido el 7 de agosto de 2026 en el diálogo de cobro del INSTITUTO DE
 * EDUCACION SUPERIOR TECNOLOGICO PRIVADO DEL SUR:
 *
 *     «S/ 17.685,95»  →  tabla de sus facturas pendientes      0 px
 *     «Todavía no hay ninguna nota» →  campo «Anotar»           0 px
 *     campo «Anotar»  →  botón «Guardar la nota»                0 px
 *
 * El total pegado a la cabecera de la tabla se lee como si fuera parte de ella,
 * y el botón pegado al área de texto parece su borde inferior.
 *
 * Se nombran los cuatro y no se usa `* + *`: ninguno de ellos va nunca justo
 * detrás del `<legend>`, así que la separación del título sigue siendo 13 y no
 * se duplica. */
.ficha-cobro .bloque-campos > .tabla-envoltura,
.ficha-cobro .bloque-campos > .notas-gestion,
.ficha-cobro .bloque-campos > .field,
.ficha-cobro .bloque-campos > .filtros-acciones { margin-block-start: var(--space-3); }
.bloque-campos__titulo {
  display: block; width: 100%; padding: 0 0 var(--space-2);
  margin-block-end: var(--space-3);
  border-block-end: 1px solid var(--border-strong);
  font-size: var(--fs-md); font-weight: var(--fw-semibold);
  color: var(--text);
}
/* En alto contraste la línea sube de grosor: es la única señal de agrupación y
   un hairline puede desaparecer. */
[data-contrast="high"] .bloque-campos__titulo { border-block-end-width: 2px; }

/* Los anchos por contenido SON LOS DE §16 —`data-ancho` con la escala
 mínimo · corto · medio · largo · completo— y no una escala propia. Facturas
 tenía sus `campo-ancho-*`, que decían lo mismo con otras palabras: dos
 escalas es media escala, y era justo lo que el operador veía como
 incoherencia entre módulos (§8.10).
 Aquí solo queda la excepción del formulario de factura: el `completo` de la
 rejilla de dos columnas. */

/* ---------- El número: prefijo y correlativo, pegados ----------
   Dos controles que se leen como uno. En campos separados el prefijo NO se
   pierde al seleccionar y reescribir el correlativo, que es lo que Money evita
   y funciona. El guion es decorativo: el lector oye dos etiquetas propias. */
.numero-compuesto { display: flex; align-items: center; gap: var(--space-2); min-width: 0; }
.numero-compuesto__serie        { flex: 0 0 6.5rem; text-align: center; font-weight: var(--fw-medium); }
.numero-compuesto__correlativo  { flex: 1 1 auto; min-width: 0; }
.numero-compuesto__guion        { color: var(--text-secondary); font-weight: var(--fw-semibold); }

/* El aviso de que el cliente cambió el sector. Lleva marca ADEMÁS del color
   —el signo y la negrita— porque el color solo no comunica (§8.5). */
.aviso-derivado { color: var(--brand-strong); font-weight: var(--fw-medium); }
.aviso-derivado::before { content: "→ "; }

/* La cadena fiscal, pegada al pie: con dieciséis campos, arriba quedaría fuera
   de pantalla al pulsar Guardar, y una comprobación que no se ve cuando se
   decide no es una comprobación. */
/* ---------- La cadena fiscal, resaltada ----------
   Es lo ÚNICO que dice qué va a pasar con la factura antes de guardarla —el IGV
   extraído, la detracción propuesta, lo que está sin configurar— y competía en
   peso con dieciséis campos. Se le da marco propio y encabezado mayor.

   RESALTAR NO PUEDE APLANAR LA DISTINCIÓN que ya existe: «sin configurar» no es
   un cero, y por eso el realce es del BLOQUE y no de sus celdas. Las filas
   siguen diciendo «—» y «sin configurar» con sus propias palabras. */
.cadena-al-pie {
  margin-block-start: var(--space-5);
  /* NO `margin-inline: auto`: en un hijo flex, un margen automático lo encoge
     a su contenido en vez de estirarlo —medía 595 de los 884 disponibles—. Ya
     va dentro del formulario, que es quien limita el ancho. */
  width: 100%;
  border: 2px solid var(--brand);
  background: var(--surface-2);
}
.cadena-al-pie .card-head h2 { font-size: var(--fs-lg); }
[data-contrast="high"] .cadena-al-pie { border-width: 3px; background: transparent; }

/* Los botones acompañan a la cadena: si se ve una, se ve el otro. */
.form-reparte .filtros-acciones { width: 100%; }

/* ---------- Cabecera del listado (igual que Movimientos) ---------- */
.barra-filtros {
  display: flex; flex-wrap: wrap; align-items: end; gap: var(--space-3);
  margin-block-start: var(--space-4);
}
.field--compacto { min-width: 11rem; }
/* «Más filtros» va EN la línea, no bajo ella: su resumen es un control más.
   Lo que se despliega sí ocupa el ancho, y para eso el detalle abierto pasa a
   base completa — así al abrirse empuja hacia abajo sin descolocar la línea. */
.mas-filtros > summary { list-style: none; }
.mas-filtros[open] { flex-basis: 100%; }
.mas-filtros summary {
  cursor: pointer; min-height: var(--tactil-minimo);
  display: flex; align-items: center; font-weight: var(--fw-medium);
}
/* ─── `.tercios` SE DECLARA UNA SOLA VEZ, MÁS ABAJO ────────────────────────
 *
 * Aquí había una segunda declaración con `repeat(auto-fit, minmax(15rem, 1fr))`
 * que **no se aplicaba nunca**: la de «Las tres posiciones de la cabecera»
 * tiene la misma especificidad, viene después y gana siempre.
 *
 * Se retira en vez de dejarla: una regla muerta que parece viva es peor que
 * ninguna. Quien leyera esta creería que la cabecera es de columnas
 * automáticas, y es `1fr 2fr` — y ajustaría aquí sin que cambiara nada.
 *
 * Medido antes de quitarla: las cuatro cabeceras con widgets siguen en una
 * sola fila y con las mismas medidas. */
/* Destacado por TRAZO y peso, no solo por color: §8.5 no admite que el color
   sea la única señal. */
.stat--destacado { border-color: var(--brand); border-width: 2px; }
.stat--destacado .stat-valor { font-size: var(--fs-lg); }

/* ---------- Visor de comprobante ----------
   EL ALTO SE AJUSTA AL CONTENIDO, NO ES FIJO: un comprobante apaisado y uno
   vertical piden alturas distintas, y una caja fija deja franjas vacías en uno
   y recorta el otro. `max-height` acota; el contenido decide dentro. */
/* EL `display` VA SOLO CON `[open]`. Un `<dialog>` cerrado lo esconde el
   navegador con `display:none`, y una regla de autor con `display:flex` lo
   PISA por origen — el diálogo se dibujaba en la página al cargar, encima de
   todo y sin que nadie lo abriera. Es el mismo tropiezo que `dialogo.js` ya
   documenta para otro selector. */
/* UN COMPROBANTE ES UNA HOJA, y una hoja se lee grande. 80 % de la ventana en
   los dos ejes: bastante para leer una factura sin ampliar, y con margen
   suficiente alrededor para que se vea que es una ventana sobre Flow y no otra
   pantalla. */
/* LA VENTANA TIENE LA FORMA DE UNA HOJA, no la de la pantalla.
   MANDA EL ALTO: 90 % de la ventana, y el ancho es el que pide la proporción —
   **1,2 de ancho por 1 de alto**, que es la forma en que se ven la mayoría de
   los comprobantes con el lector y sus márgenes. Si el documento es más alto,
   desplaza dentro, que es lo que hace una hoja larga y no una ventana mal
   medida.
   `max-width` acota el caso contrario: en una pantalla baja y ancha, 90 % de
   alto podría pedir más ancho del que hay. */
.dlg-comprobante {
  height: 90dvh; max-height: 90dvh;
  width: calc(90dvh * 1.2); max-width: 95vw;
}
.dlg-comprobante[open] {
  display: flex; flex-direction: column;
  padding: 0; border: 1px solid var(--border); border-radius: var(--radius-md);
  background: var(--superficie-ventana); color: var(--text);
}
.dlg-comprobante::backdrop { background: rgba(0, 0, 0, 0.6); }
.dlg-cabecera, .dlg-pie {
  display: flex; align-items: center; gap: var(--space-3);
  padding: var(--space-4); flex: none;
}
.dlg-cabecera { border-bottom: 1px solid var(--border); }
.dlg-cabecera h2 { margin: 0; font-size: var(--fs-md); flex: 1; }
.dlg-pie { border-top: 1px solid var(--border); justify-content: flex-end; }
/* ─── EL DIÁLOGO NO ESTABA APRETADO: ESTABA SIN HUECO ──────────────────────
   Medido el 6 de agosto de 2026 sobre el alta de proveedor, que llegó a siete
   campos. La separación entre un campo y el siguiente era **0,0 px en las
   cuatro escalas y en los dos caminos**, y el borde de un `select` llegaba a
   tocar la línea del rótulo de abajo: 4,7 px de tinta.

   El mismo contenido en `/admin/proveedores/nuevo` respira **17 px**, y crece
   con la escala (17 → 21,3 → 25,5 → 29,8). Los controles son idénticos en los
   dos sitios: **la diferencia entera era el hueco**, porque este contenedor
   nació para uno o dos elementos y se quedó sin `gap` cuando pasó a llevar
   formularios enteros.

   `gap` en `rem` para que acompañe a la letra —un hueco fijo con letra un 75 %
   más grande vuelve a apretar—, y `align-content: start` para que las filas no
   se repartan el alto sobrante y se separen entre sí sin criterio. */
.dlg-cuerpo {
  padding: var(--space-4); overflow: auto; min-height: 0; flex: 1 1 auto;
  display: flex; gap: var(--space-4); align-content: start;
}
.dlg-cuerpo > * { flex: 1 1 auto; }
/* ─── UN AVISO NO ES UNA COLUMNA ──────────────────────────────────────────
 *
 * `.dlg-cuerpo` reparte en fila **todo hijo directo**. Con dos campos al lado
 * se ve bien y parece intencionado; en cuanto un aviso deja de estar oculto,
 * **se convierte en una tercera columna** y estruja los campos: en el alta
 * rápida de proveedor, el nombre que acababa de poner SUNAT quedó reducido a
 * «COCINA DE».
 *
 * No se vio midiendo —nada desbordaba, el reparto era correcto— sino mirando
 * el diálogo con el aviso encendido. Es lo mismo que pasó con los filtros del
 * buscador: la regla era razonable y el elemento que entró no era un campo.
 *
 * Un aviso habla del formulario entero, así que ocupa una línea entera. */
.dlg-cuerpo { flex-wrap: wrap; }
.dlg-cuerpo > .aviso { flex: 1 0 100%; }
/* ─── Y UNA FRASE TAMPOCO ES UNA COLUMNA ──────────────────────────────────
 *
 * La regla de arriba protegía `.aviso` porque fue el aviso el que dolió. Pero
 * lo que la hace cierta no es la clase: es que **un párrafo suelto en el cuerpo
 * de un diálogo habla del formulario entero**, nunca de la casilla que le toca
 * al lado. Protegiendo solo la clase, el mismo defecto vuelve por cualquier
 * `<p>` que no la lleve — y ya había vuelto en dos sitios.
 *
 * Medido el 14 de agosto de 2026 en un `<iframe>` de 1440 px con las tres
 * hojas reales, ANTES de tocar nada:
 *
 *   · `#dlg-eliminar-compra` · «Se va a eliminar:» —diecisiete caracteres—
 *     ocupaba **339 px de columna** (x 373,5 → 712,5) **al lado** de la lista
 *     de Proveedor/Documento/Importe (x 729,5, 322 px). La frase que presenta
 *     la lista estaba junto a ella, no encima, y la lista se quedaba con el
 *     45 % del cuerpo. Lo mismo en `#dlg-eliminar-factura`.
 *   · `#dlg-com-gasto` · la pista se salvaba **por casualidad**: su frase pedía
 *     más ancho del que había y envolvía sola. Al ensanchar el diálogo a 884 px
 *     dejaba de envolver y se ponía al lado del desplegable.
 *
 * Un párrafo ocupa la línea entera. Lo que sí es un campo va en `.field`, que
 * es un `<div>`, así que las columnas de verdad no se pierden. */
.dlg-cuerpo > p { flex: 1 0 100%; }
.comprobante-medio { display: block; width: 100%; max-width: 100%; }
/* El documento necesita alto propio —una imagen lo trae, un PDF no—, pero
   proporcional a la ventana y no en píxeles fijos. */
/* El documento ocupa TODO el cuerpo disponible: con un alto propio en `dvh` se
   sumaba al de la cabecera y el pie y desbordaba la ventana. */
.comprobante-medio--doc { height: 100%; min-height: 0; }
/* En móvil el visor ocupa casi todo: una miniatura de un comprobante no se lee,
   que es exactamente para lo que se abre (§8.5). */
@media (max-width: 640px) {
  .dlg-comprobante { width: 100vw; max-width: 100vw; height: 100dvh; max-height: 100dvh; border-radius: 0; }
}

/* La envoltura tiene que poder ENCOGER. Sin `min-width: 0` una caja dentro de
   un contenedor flex o grid toma como mínimo el ancho de su contenido, así que
   `overflow-x: auto` no llega a activarse nunca: en vez de desplazar la tabla,
   ensancha la página entera. Es el desbordamiento horizontal de §8.5 con otra
   causa — no lo produce un valor grande, lo produce una caja que no cede. */
.card { min-width: 0; }

/* ─── LA FRANJA DE FILTROS: UNA TARJETA QUE SOLO LLEVA SU CABECERA ─────────
 *
 * Kike, 7 de agosto de 2026: que las siete pantallas enseñen su barra de
 * filtros como una tarjeta aparte, y que ocupe menos alto.
 *
 * ─── QUÉ ES «LA FRANJA», SIN INVENTAR UNA CLASE ────────────────────────────
 *
 * Es exactamente **una tarjeta cuyo único hijo es su cabecera**: no hay tabla
 * debajo ni widgets dentro, solo el título y los controles con que se acota lo
 * que se va a mirar. Las siete quedaron así al sacar los widgets de Movimientos
 * y Cobros, así que la regla se escribe UNA vez y no siete clases nuevas —que
 * era el otro camino y el que acaba divergiendo.
 *
 * ─── DE DÓNDE SALEN LOS PÍXELES ────────────────────────────────────────────
 *
 * Medido antes de tocar, en Facturas: **115 px** = 25,5 de relleno arriba + 64
 * de contenido + 25,5 abajo. Y de esos 64, **17 son el margen inferior de la
 * cabecera** — un hueco reservado para lo que viniera después, y aquí no viene
 * nada. El control más alto mide 52.
 *
 * Así que se quitan las dos cosas que no sostienen nada: ese margen, y parte
 * del relleno vertical. **El horizontal no se toca**: es lo que alinea esta
 * tarjeta con las de abajo, y recortarlo las desalinearía entre sí.
 *
 * `:only-child` no necesita `:has()`; el relleno sí, y `:has()` ya se usa en
 * este archivo. Si un navegador no lo soportara, la tarjeta se queda con el
 * relleno de siempre: más alta, nunca rota. */
.card > .card-head:only-child { margin-bottom: 0; }
.card:has(> .card-head:only-child) {
  padding-block: var(--space-3);
}
.tabla-envoltura { min-width: 0; max-width: 100%; }

/* LA CADENA DE `min-width` TIENE QUE LLEGAR HASTA ARRIBA.
   `main` es un elemento flex y su `min-width` calculado era `auto`, que en un
   flex item significa «no encojas por debajo de tu contenido». Con eso, el
   `overflow-x: auto` de la tabla no llegaba a activarse: en vez de desplazar la
   tabla, la página entera se ensanchaba. Poner `min-width: 0` en la envoltura y
   en la tarjeta no bastaba — con que UN eslabón de la cadena no ceda, no cede
   ninguno. Es global a propósito: cualquier módulo con una tabla ancha tiene el
   mismo problema. */
.app-main { min-width: 0; }

/* LA ENVOLTURA TIENE QUE SER EL BLOQUE CONTENEDOR DE SUS ABSOLUTOS.
   Un descendiente `position: absolute` NO lo recorta un ancestro con
   `overflow: auto` si ese ancestro no está posicionado: busca el bloque
   contenedor más arriba y se sale del recorte. Aquí lo hacía el `.sr-only` del
   encabezado de acciones —invisible, de 1 px— que a escala máxima quedaba a
   2.543 px y estiraba el documento 380 px de puro vacío.
   Por eso el desborde no tenía nada dentro: lo reservaba algo que no se ve. */
.tabla-envoltura { position: relative; }

/* ---------- Las tres posiciones de la cabecera (§8.10) ---------- */
.tercios { display: grid; gap: var(--space-4); grid-template-columns: 1fr 2fr; margin-block-end: var(--space-4); }
.tercios-par { display: grid; gap: var(--space-4); grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr)); }
.tercios-detalle > summary { display: none; }
/* ─── LA ALTURA · lo que se aprieta y lo que NO · Kike, 01/09/2026 ─────────
 *
 * *«Que trate de que no quede tan alto respecto a los otros widgets.»* Con la
 * barra en su propia línea cada fila pasa de una línea a dos, y estos widgets
 * van uno al lado de otro.
 *
 * **Se aprieta por el hueco entre filas, no por la barra.** Este widget ya tenía
 * `--space-mini` (0,35rem) contra los `--space-2` (0,5rem) del componente, así
 * que se conserva el suyo: **0,15rem menos por fila, seis filas, sin tocar nada
 * que se lea**.
 *
 * **Y la barra NO se encoge.** El `.6em` de `.barra-pista` es el del componente
 * que ya vive en «En qué se va», en producción y sin queja. *Kike tiene baja
 * visión: una barra tan fina que no se distingue deja de informar, y entonces el
 * widget no sirve para lo que existe.* Ese es el suelo y no se cruza para que
 * quepa.
 *
 * **Lo que no se pudo comprobar:** la altura en píxeles contra la de los
 * widgets vecinos, en las cuatro escalas. Este arnés no ejecuta CSS. Va dicho
 * en el informe como «no pudo comprobarse», no como verde. */.barras-comparativo { list-style: none; margin: var(--space-3) 0 0; padding: 0; display: grid; gap: var(--space-mini); }
/* ─── EL RIEL MIDE LO MISMO EN TODAS LAS FILAS · Kike, 01/09/2026 ──────────
 *
 * *«Se acordó que las barras tendrían siempre el ancho fijo; ahora se están
 * adaptando a cada línea, lo cual no refleja estadísticamente lo adecuado.»*
 *
 * **La causa, leída en esta misma línea:** cada `li` es **su propia rejilla**, y
 * la primera columna era `minmax(6rem, auto)`. `auto` crece con el contenido de
 * ESA fila, así que «Septiembre 2026» se llevaba más ancho que «Mayo 2023» y el
 * `1fr` del riel —lo que sobra— quedaba más corto. **Rejillas independientes,
 * columnas distintas, rieles distintos.**
 *
 * **Y no es cosmético:** una barra medio llena en un riel corto parece lo mismo
 * que una menos llena en uno largo, y comparar de un vistazo es justo para lo
 * que existe un gráfico de barras.
 *
 * ─── EL ARREGLO ES QUITARLE EL `auto` A LA COLUMNA, NO TOCAR LA BARRA ──────
 *
 * Con la etiqueta en un ancho **fijo**, las tres columnas miden lo mismo en las
 * seis filas **por construcción**, sin depender de qué texto lleve cada una. Y
 * sigue funcionando el día que alguien vuelva a poner «Septiembre 2026»: la
 * etiqueta se recorta, el riel no.
 *
 * El ancho va en `rem` para que crezca con las cuatro escalas de texto: en
 * píxeles se quedaría quieto mientras la letra crece, que es el patrón que ya
 * rompió esta misma barra una vez. */

/* CLASES PROPIAS, NO PRESTADAS. `.barra-pista` ya existía: es del widget de
   categorías, donde la barra va DEBAJO de su rótulo a propósito —`grid-column: 1`
   con su razón escrita—. Aquí hace falta lo contrario: etiqueta, barra y cifra
   en la misma línea. Reusar el nombre traía la colocación ajena y ponía cada
   periodo en dos líneas; seis periodos, seis líneas de más, y de ahí el alto.
   Dos widgets pueden compartir aspecto sin compartir maquetación. */
/* Con la columna fija, una etiqueta larga tiene que CEDER ella: se recorta con
   sus puntos suspensivos en vez de empujar el riel. *Lo de largo libre cede; una
   medida que se compara, nunca.* El texto entero sigue en el `title`. */
.comparativo-etq { font-size: var(--fs-caption); color: var(--text-secondary);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.comparativo-pista { background: var(--bg-sunken); border: 1px solid var(--border); border-radius: var(--radius-pill); height: 10px; overflow: hidden; }
.comparativo-relleno { display: block; height: 100%; background: var(--text-muted); }
/* El periodo en curso se distingue por COLOR Y por su etiqueta, nunca solo por
   color: la cifra va escrita al lado en las seis barras. */
.comparativo-relleno--actual { background: var(--brand); }
.comparativo-cifra { font-size: var(--fs-caption); }

/* ─── LA FLECHA DEL COMPARATIVO · Kike, 02/09/2026 ─────────────────────────
   `▼`/`▲` dicen la dirección **con su forma**, así que se entienden impresos en
   blanco y negro. No llevan color propio a propósito: si el color fuera la
   señal, quitarlo la borraría, y aquí quitarlo no quita nada. El tamaño baja un
   poco porque el glifo del triángulo se dibuja más alto que la línea. */
.comparativo-flecha { font-size: 0.85em; line-height: 1; }

/* ─── LO QUE DICE EL PAPEL, TAL CUAL · Inbox, 02/09/2026 ───────────────────
   Va en monoespaciada y con su barra porque es **lo leído sin retocar**: si se
   maquetara parecería un dato de Flow, y es una cita del PDF. Se recorta en
   alto para que no empuje el formulario, y **se puede desplazar**: recortar sin
   dejar cómo ver el resto no acorta, esconde. */
/* ─── REVISAR LO QUE LLEGÓ POR CORREO · Kike, 02/09/2026 ───────────────────
   *«A la izquierda el documento que llegó. A la derecha los datos a ingresar.»*
   En vertical por debajo de 60rem, **con el documento primero**: dos columnas
   en una pantalla estrecha dan dos columnas ilegibles, y lo que hay que mirar
   antes de decidir es el papel. */
.inbox-revisar { display: grid; gap: var(--space-3); align-items: start; }
@media (min-width: 60rem) { .inbox-revisar { grid-template-columns: 1fr 1fr; } }
/* Las dos mitades. El papel puede ser alto y los datos cortos, así que no se
   estiran a la misma altura: cada uno mide lo suyo. */
.inbox-revisar__papel { min-width: 0; }
.inbox-revisar__datos { min-width: 0; }
.inbox-revisar__visor { width: 100%; height: min(70vh, 40rem); border: 1px solid var(--border);
  border-radius: var(--radius-md); background: var(--bg-sunken); }
img.inbox-revisar__visor { object-fit: contain; height: auto; max-height: min(70vh, 40rem); }
/* Rótulo encima del dato y no al lado: en una columna estrecha, dos columnas
   dentro de otra columna dejan el valor partido en tres líneas. */
.inbox-revisar__campos { display: grid; gap: var(--space-1) var(--space-3); margin: 0; }
@media (min-width: 30rem) { .inbox-revisar__campos { grid-template-columns: auto 1fr; } }
.inbox-revisar__campos dt { font-size: var(--fs-caption); color: var(--text-secondary); }
.inbox-revisar__campos dd { margin: 0 0 var(--space-1); }

.pre-lectura { max-height: 16rem; overflow: auto; white-space: pre-wrap; word-break: break-word;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: var(--fs-caption);
  background: var(--bg-sunken); border: 1px solid var(--border); border-radius: var(--radius-md);
  padding: var(--space-2); margin: var(--space-1) 0 0; }


/* En móvil los tercios se apilan y los dos de comparación se pliegan tras un
   control que DICE qué hay detrás. */
@media (max-width: 640px) {
  .tercios { grid-template-columns: 1fr; }
  .tercios-detalle > summary {
    display: flex; align-items: center; cursor: pointer;
    min-height: var(--tactil-minimo); font-weight: var(--fw-medium);
  }
}

/* Cada dato del widget en su línea: los `span` sueltos se corrían en una sola
   frase y «Por cobrar S/ 1.234.567,89 1 factura(s)» se leía como un párrafo, no
   como una magnitud con su contexto. */
.widget-magnitud, .widget-comparativo, .widget-posicion { display: grid; gap: var(--space-micro); }
/* Etiqueta, barra y cifra en la MISMA línea: apiladas ocupan el triple y la
   comparación deja de leerse de un vistazo, que es para lo que existe. */
/* ─── LOS DOS SUELOS EN `rem` CRECÍAN Y LA TARJETA NO ──────────────────────
 *
 * Medido el 12 de agosto de 2026 en Facturas, muy_grande@1400: el `li` exigía
 * 411,8 px —140,25 (etiqueta, 5,5rem) + 76,5 (pista, 3rem) + 156,8 (cifra,
 * `nowrap`) + 2 huecos de 19,1— dentro de 336,4 px de tarjeta, y el widget
 * entero **se salía 37,1 px por el borde**; el de «2026 vs 5 anteriores»,
 * 54,8 px a máximo@1656. En `rem` los suelos crecen con la preferencia de
 * texto y el ancho de la tarjeta no: dos tendencias opuestas en el mismo eje,
 * el patrón de §8.5.
 *
 * Es el caso «se niega a encoger» —el contrario de §8.95—, el mismo que el 11
 * de agosto se corrigió en «Crédito fiscal» de Compras y que aquí seguía vivo.
 *
 * Los suelos se quedan, pero acotados a una fracción de la tarjeta: en `min()`
 * con un porcentaje, el suelo vale lo que decía mientras quepa y cede cuando
 * no. La que cede primero es la PISTA, que es lo único sin texto: la etiqueta
 * y la cifra se leen; una barra de 2 px se ve peor pero no se lee peor. Con
 * esto los 6 desbordes medidos pasan a 0 en las doce combinaciones. */
/* ─── Y EL SUELO DE LA PISTA, QUE ANTES ERA CERO ───────────────────────────
 *
 * Kike, 13 de agosto de 2026: «suelo en `ch` o relativo, no en píxeles».
 *
 * Aquella decisión de arriba —que la pista fuera la que cede— quitó los 6
 * desbordes, y cobró un precio que el Tester midió: en 7 de 12 pistas la
 * barra quedaba en **0 px**. Y no porque el relleno fuera pequeño: porque la
 * PISTA medía 2 px, que son sus dos bordes. Una barra de cero no compara nada.
 *
 * ─── EL COSTE DE LA ETIQUETA Y LA CIFRA ES CONSTANTE, MEDIDO ──────────────
 *
 * Etiqueta + cifra + dos huecos cuestan **13,0–13,3 em, igual en las cuatro
 * escalas** (13,34 · 13,23 · 13,18 · 12,93 · 12,98 · 12,99). Por eso el umbral
 * va en `em` del contenedor y no en píxeles de ventana: lo que decide no es
 * cuánto mide la pantalla, es cuánta letra cabe en la tarjeta. Y `1ch` mide
 * 0,61 em en esta tipografía, también en las cuatro.
 *
 * El umbral se midió dos veces porque la primera estaba mal: una consulta de
 * contenedor pregunta por la caja de CONTENIDO, no por el borde, y la tarjeta
 * lleva 2,82 em de relleno. Con el borde salía 18 em y partía 6 de 12 filas
 * que no hacía falta partir.
 *
 * El suelo son **2,5ch** —28 px a escala normal, 48 a máxima— y cabe siempre
 * que el interior pase de 13,3 + 1,53 = **14,9 em**. Por debajo, el `li` se
 * parte: etiqueta y cifra arriba, barra entera abajo. Y se parte SOLO ahí, por
 * consulta de contenedor — la vez que la barra se puso en su propia fila
 * siempre, seis periodos costaron seis líneas de más.
 *
 * El umbral es 15 em. Los interiores medidos caen en 12,7–12,9 o en
 * 15,8–26,0: ninguno empata con el límite, y en el más justo de los que NO
 * parten quedan 4,1ch para una pista que pide 2,5. */
/* En estrecho, el mismo trato: ancho fijo para la etiqueta —más pequeño,
   porque hay menos sitio— y el riel se queda con lo que sobra. Sin `auto`, que
   es lo que hacía los rieles desiguales. */

/* ─── AL PARTIR, LA BARRA SE QUEDABA CON EL PERIODO DE ABAJO ────────────────
 *
 * Tester, 13 de agosto de 2026. Partir en dos líneas funcionaba —ninguna barra
 * a cero, nada fuera de la tarjeta—, pero **la barra quedaba más cerca de la
 * etiqueta del periodo SIGUIENTE que de la suya**. No es una impresión:
 *
 *     hueco DENTRO del periodo (etiqueta → su barra)  `li` gap  = 0,75rem
 *     hueco ENTRE periodos     (barra → etiqueta de abajo)  `ul` gap = 0,35rem
 *
 * Justo al revés de lo que hace falta. Medido en las 54 anchuras en que parte
 * —tres escalas por dos widgets—: 12,8 px contra 5,9 a escala normal, 22,3
 * contra 10,4 a la máxima, siempre 2,15 veces. Y en la pantalla se ve: la barra
 * azul de Agosto 2026, que es la más larga porque es el máximo, se lee pegada a
 * «Agosto 2025», cuyo importe es la mitad.
 *
 * El `li` declara `gap` para su columna —etiqueta, barra, cifra— y ese mismo
 * valor le caía al hueco de FILA en cuanto la fila existía. Se separan: la
 * columna se queda como estaba y el hueco de fila baja por debajo del de la
 * lista, que sube. Queda 3:1 a favor de la separación entre periodos.
 *
 * Los dos en `rem`, así que la proporción se mantiene en las cuatro escalas —un
 * valor fijo se quedaría quieto mientras la letra crece (§8.5). Y no cuesta
 * alto: cinco huecos de lista suben 0,4rem y seis de fila bajan 0,5rem, un rem
 * MENOS que antes en total. */
@container (max-inline-size: 15em) {
  .barras-comparativo { gap: var(--space-3); }
  .barras-comparativo li { grid-template-columns: minmax(0, 1fr) auto; row-gap: var(--space-1); }
  .barras-comparativo .comparativo-pista { grid-column: 1 / -1; grid-row: 2; }
}
.barras-comparativo .comparativo-etq,
.barras-comparativo .comparativo-cifra { white-space: nowrap; }
/* A escala máxima la comparación se apila sola: seis barras con etiqueta y
   cifra no caben en una línea cuando el texto crece, y partirlas es mejor que
   desbordar. La regla de escritorio va primero (§8.1). */
/* NO se coloca la barra explícitamente. Una regla anterior le ponía
   `grid-column: 1 / -1` pensando en la escala máxima y acababa aplicándose
   siempre: la barra caía en su propia fila y cada periodo ocupaba DOS líneas.
   Seis periodos, seis líneas de más — y el alto de la cabecera se disparó por
   una regla que se aplicaba y no servía, que es el tercer caso del mismo
   patrón en tres turnos. La rejilla coloca sola: etiqueta, barra y cifra. */

/* La acción que no se deshace, separada de la que no hace nada. */
.pie-acciones { display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap; margin-block-start: var(--space-4); }

/* EL COLOR ACOMPAÑA, NUNCA DECIDE SOLO (§8.5). Cada barra lleva su cifra escrita
   y el periodo actual va rotulado arriba en grande: quien no distinga estos dos
   tonos lee exactamente la misma información. Y salen de la paleta curada — se
   guarda la clave, no el hexadecimal. */
.comparativo-relleno--mejor { background: var(--paleta-4); }
.comparativo-relleno--peor  { background: var(--paleta-8); }

/* Los campos de la línea no llevan rótulo visible: se identifican por su
   primera opción. Ancho propio para que dos quepan con lo demás. */
.field--enlinea { min-width: 11rem; }
.field--enlinea .label { position: absolute; }
.comparativo-titulo { font-size: var(--fs-caption); font-weight: var(--fw-semibold); margin: 0 0 var(--space-micro); text-transform: none; letter-spacing: 0; }
.comparativo-titulo strong { color: var(--brand-strong); }

/* ─── TODOS CENTRADOS, POR DECISIÓN DE KIKE ────────────────────────────────
   Aquí ponía que el título se centraba SOLO en los comparativos, y el motivo
   escrito era bueno: en el widget de magnitud la cifra va grande y a la
   izquierda, así que un rótulo centrado no cae sobre lo que describe.

   Kike lo revocó el 6 de agosto de 2026: los quiere centrados y resaltados,
   IGUALES en todos los módulos. Es su decisión y es defendible — tres widgets
   uno al lado del otro con el rótulo a distinta altura se leen como tres
   pantallas pegadas, y esa incoherencia se nota más que el desajuste de uno.
   La regla anterior queda escrita aquí para que nadie la vuelva a deducir
   desde cero y la cambie sin saber que ya se decidió.

   El modificador se conserva para no romper el marcado que ya lo usa. */
.comparativo-titulo, .comparativo-titulo--centrado { text-align: center; }

/* Los atajos, agrupados por lo que filtran: sin el hueco entre grupos, siete
   botones seguidos se leen como una lista sola y no como «estado» y «quién». */
.grupo-atajos { display: inline-flex; gap: var(--space-mini); flex-wrap: wrap; }
.barra-filtros .grupo-atajos + .grupo-atajos { margin-inline-start: var(--space-4); }

/* LA BARRA LA ENGORDABA «Más filtros», no los campos.
   Un `<select>` mide lo que un botón —los dos llevan `padding: 10px`—, pero el
   resumen pedía `3.5rem` de objetivo táctil y ARRASTRABA a toda la fila: la
   barra medía 60 px por un solo elemento. Con el mínimo táctil de 44 px sigue
   cumpliendo §8.5 y la línea queda del alto de sus controles, como en
   Movimientos. La altura de una fila la fija su contenido más alto, y por eso
   basta con uno que se pase. */
.barra-filtros .mas-filtros > summary { min-height: var(--tactil-minimo); }
.barra-filtros { align-items: center; }

/* ---------- Título y filtros en la misma línea ----------
   El separador es una REGLA VERTICAL, no un margen: un hueco grande se lee como
   descuido y uno pequeño no separa nada. La línea dice que son dos cosas —dónde
   estás y con qué lo acotas— sin gastar una franja entera. */
.cabecera-con-filtros { flex-wrap: wrap; gap: var(--space-3); }
.cabecera-con-filtros .barra-filtros { margin: 0; flex: 1 1 auto; }
.separador-cabecera {
  flex: none; align-self: stretch; width: 1px; min-height: var(--tactil-minimo);
  /* `--border` es para hilos DENTRO de una superficie y aquí no se distingue
     del fondo de la tarjeta: el separador estaba dibujado y era invisible.
     `--border-strong` es el que se ve, que es lo único que hace separador a
     una línea. */
  background: var(--border-strong);
}
/* En estrecho la regla vertical no separa nada porque las cosas ya van una
   debajo de otra: pasa a ser horizontal y ocupa el ancho. */
@media (max-width: 640px) {
  .separador-cabecera { width: 100%; min-height: 0; height: 1px; }
}

/* ---------- El impuesto por pagar ----------
   Es un DERIVADO y va en el pie de la tabla, no entre los ajustes: ponerlo en
   el cuerpo lo haría parecer una fila más de `tipo_ajuste`, y no lo es.
   Lleva peso porque es el número que se busca, y NUNCA solo color: el signo se
   escribe y el negativo cambia además de borde. */
.fila-por-pagar {
  border-block-start: 2px solid var(--border-strong);
  font-weight: var(--fw-semibold); font-size: var(--fs-md);
}
.fila-por-pagar th { text-align: end; }
.fila-por-pagar--negativa { border-block-start-color: var(--expense); }
.fila-por-pagar--negativa td { color: var(--expense-ink); }
[data-contrast="high"] .fila-por-pagar { border-block-start-width: 3px; }

/* ═══════════ LA ETIQUETA AL LADO DEL CAMPO ═══════════════════════════════
   Dieciséis etiquetas encima son dieciséis líneas. Al lado, el formulario se
   acorta a la mitad sin quitar nada — y la etiqueta SIGUE VISIBLE, que es lo
   que §8.5 exige y lo que un `placeholder` no cumple.

   VA EN `.field`, no en un módulo: es la forma de todos los formularios y §8.10
   dice que un cambio de forma toca todos o no se hace. Clientes, Movimientos y
   Facturas la reciben a la vez.

   TRES CONDICIONES PARA QUE SE APLIQUE, y las tres son de §8.5/§8.1:
     · desde 720 px — por debajo no hay sitio para dos cosas en línea (§8.1);
     · hasta escala «grande» — a muy grande y máximo la etiqueta se comería el
       campo, y la etiqueta gana: vuelve arriba;
     · la columna de la etiqueta va en REM, así que crece con el usuario.

   ═══ SE PIDE, NO SE HEREDA — Y ESE ES EL CAMBIO QUE IMPORTA ═══

   Antes se aplicaba a TODO formulario y se escribían excepciones cuando
   estorbaba. Llegaron a ser dos —`.barra-filtros` y `.buscador-catalogo`— y la
   segunda faltó durante CUATRO TURNOS con los filtros de Clientes torcidos: sus
   etiquetas son `sr-only` —invisibles— y la rejilla les daba igualmente su
   columna, así que el campo de búsqueda medía 108 px dentro de una etiqueta de
   329 y los controles salían repartidos por toda la fila.

   Al invertirla apareció una TERCERA que llevaba el mismo tiempo torcida sin
   que nadie lo dijera: los cinco filtros de `.filtros-mov`, en Movimientos.

   **Lo que cambia es de qué lado cae el fallo.** Heredada, un formulario que no
   la quería repartía peor y NADIE LO VEÍA —no falla: reserva sitio—. Pedida, a
   un formulario que la quiera y se le olvide la clase se le va la etiqueta
   arriba, y eso se ve al abrir la pantalla. En §8.5 un fallo visible vale mucho
   más que uno silencioso.                                                      */
@media (min-width: 720px) {
  :root:not([data-escala="muy_grande"]):not([data-escala="maximo"]) form.form-etiquetado .field:not(.field--enlinea):not(.field--compacto) {
    display: grid;
    grid-template-columns: minmax(7rem, 12rem) minmax(0, 1fr);
    align-items: start;
    column-gap: var(--space-4);
  }
  /* La etiqueta alineada al final: el ojo baja por una sola línea vertical
     junto a los controles en vez de por un borde dentado. */
  :root:not([data-escala="muy_grande"]):not([data-escala="maximo"]) form.form-etiquetado .field:not(.field--enlinea):not(.field--compacto) > .label {
    grid-column: 1; text-align: end;
    padding-block-start: var(--space-3);
    margin-block-end: 0;
  }
  :root:not([data-escala="muy_grande"]):not([data-escala="maximo"]) form.form-etiquetado .field:not(.field--enlinea):not(.field--compacto) > :not(.label) {
    grid-column: 2;
  }
  /* La casilla que ya vivía en una línea no se toca: su texto ES la etiqueta. */
  :root:not([data-escala="muy_grande"]):not([data-escala="maximo"]) .field--linea {
    display: flex;
  }

  /* ═══ EN LA FILA DEL MONTO CEDE LA ETIQUETA, NO EL NÚMERO ═══════════════
   *
   * `minmax(7rem, 12rem)` le da a la etiqueta sus 12rem —204 px— siempre que
   * quepan, y al monto le queda lo que sobre. En la pantalla `/nueva` sobran
   * 640 px y no se nota. **En el diálogo de Registrar sobran 258**, y ahí el
   * número más grande de la pantalla deja de caber.
   *
   * Medido el 5 de agosto de 2026 escribiendo en el diálogo, con la letra de
   * 44 px que §8.5 le da al monto:
   *
   *     1234,56       208 px en una caja de 197  → se corta
   *     1.234,56      219 px                     → se corta
   *     999.999,99    277 px                     → se corta
   *
   * Es decir: **cualquier importe de mil para arriba se teclea a ciegas**, en
   * el único campo que existe para mirarlo antes de guardar. La regla de
   * `.input-monto` decía «el criterio no es un tamaño, es que quepa el máximo»
   * y estaba medida sobre `/nueva`, que es la disposición ancha.
   *
   * Aquí se invierte quién cede: el control pide 21rem y la etiqueta se queda
   * con el resto. Son 21 y no 18 porque **la caja no es solo el campo**: el
   * símbolo de la moneda se lleva 48 px y el hueco 8 más, así que 18rem dejaban
   * el número en 245 px y `999.999,99` seguía sin caber. Medido otra vez con el
   * arreglo puesto, que es la única forma de saberlo.
   *
   * Va sobre TODA `.fila-superior`, no solo sobre el monto, para que «Monto» y
   * «Fecha» sigan alineadas: son dos rejillas distintas y hoy coinciden porque
   * sus columnas miden lo mismo. */
  :root:not([data-escala="muy_grande"]):not([data-escala="grande"]):not([data-escala="maximo"]) form.form-etiquetado .fila-superior > .field:not(.field--enlinea):not(.field--compacto) {
    grid-template-columns: minmax(0, 12rem) minmax(21rem, 1fr);
  }

  /* ═══ Y A ESCALA «GRANDE» LA ETIQUETA VUELVE ARRIBA, UNA ANTES ══════════
   *
   * Ese `minmax(21rem, …)` es un suelo, y **un suelo en `rem` crece con el
   * usuario**. A escala grande 21rem son 446 px y la media columna mide 414:
   * la rejilla no puede ceder, así que se sale. Medido el 8 de agosto de 2026:
   * **75 px fuera de pantalla**, con «MONTO» recortado a «M», «FECHA» a «FE» y
   * el campo de la fecha cortado por el borde derecho. Solo en `/nueva` y solo
   * en esta escala — las otras cuatro pantallas y las otras tres escalas dan 0.
   *
   * No es cosmético: es la escala de quien no ve bien, que es exactamente
   * quien no puede permitirse una etiqueta de una letra.
   *
   * La salida ya la tenía escrita la regla de al lado —«a muy grande y máximo
   * la etiqueta se comería el campo, y la etiqueta gana: vuelve arriba»—. Lo
   * único que hacía falta era ver que **en media columna eso pasa una escala
   * antes**: el resto del formulario tiene el ancho entero y aguanta, esta fila
   * no. Así que aquí se apila y el número recupera sus 828 px.
   *
   * Se restaura el estado natural del campo, el mismo que tiene a muy grande,
   * en vez de inventar una tercera disposición (§8.10). */
  :root[data-escala="grande"] form.form-etiquetado .fila-superior > .field:not(.field--enlinea):not(.field--compacto) {
    display: flex; flex-direction: column; gap: var(--space-mini);
  }
  :root[data-escala="grande"] form.form-etiquetado .fila-superior > .field:not(.field--enlinea):not(.field--compacto) > .label {
    text-align: start; padding-block-start: 0;
  }

  /* ═══ Y LO MISMO EN FACTURAS Y COMPRAS, QUE ES DONDE SEGUÍA CORTANDO ════
   *
   * La regla de arriba se escribió para el diálogo de Movimientos y se quedó
   * ahí. Facturas y Compras ponen su importe en `.rejilla-formulario`, que
   * reparte la tarjeta en dos columnas de 425 px; dentro de cada una la
   * etiqueta se lleva 204 y **al número le quedan 182**.
   *
   * Medido el 5 de agosto de 2026 con la letra de 34 px de esta caja, sobre
   * los 158 px de texto que dejan los rellenos:
   *
   *     73.885,00  —el importe más alto de la historia—   ok
   *     99.999,99                                          163 px → se corta
   *     999.999,99                                         185 px → se corta
   *     2360.0000  —lo que metía la edición—                        se corta
   *
   * Y no cortaba con puntos suspensivos: el dígito desaparece y no queda
   * nada que lo diga. Tecleado `999999.99` y salido del campo, la caja lee
   * `999999.9`. Es el único sitio donde el importe se compara con el papel
   * antes de guardar.
   *
   * Aquí cede la etiqueta —8rem, que sigue dando dos líneas como antes— y el
   * control pasa de 182 a 250 px, con los 226 de texto que necesita
   * `1,234,567.89` (208 px) y 18 de margen. No se toca `.fila-superior` ni
   * los otros `campo-destacado` —cliente, descripción, la nota—, que no
   * llevan número grande y cuya etiqueta debe seguir alineada con el resto.
   *
   * VA POR `.rejilla-formulario >` A PROPÓSITO. El campo de Movimientos lleva
   * el mismo `data-campo="monto"` —es el mismo campo— y vive en
   * `.fila-superior`, con su propia regla de arriba medida en el diálogo. Sin
   * acotar, esta sería la última y pisaría aquella: el arreglo de un módulo
   * rompiendo el del otro, que es exactamente lo que se está corrigiendo. */
  :root:not([data-escala="muy_grande"]):not([data-escala="maximo"]) form.form-etiquetado .rejilla-formulario > .field[data-campo="monto_funcional"]:not(.field--enlinea):not(.field--compacto),
  :root:not([data-escala="muy_grande"]):not([data-escala="maximo"]) form.form-etiquetado .rejilla-formulario > .field[data-campo="monto_total"]:not(.field--enlinea):not(.field--compacto) {
    grid-template-columns: minmax(0, 8rem) minmax(0, 1fr);
  }
}

/* ═══════════ DOS CAMPOS POR LÍNEA CUANDO LOS DOS SON CORTOS ══════════════
   Solo desde 1.100 px y solo para los anchos pequeños de la escala de §16: un
   campo `largo` o `completo` emparejado deja una de las dos columnas medio
   vacía, que es peor que una fila propia. */
@media (min-width: 1100px) {
  :root:not([data-escala="muy_grande"]):not([data-escala="maximo"]) .form-reparte .rejilla-formulario {
    grid-template-columns: 1fr 1fr;
    column-gap: var(--space-6);
  }
  .form-reparte .field[data-ancho="largo"],
  .form-reparte .field[data-ancho="completo"] { grid-column: 1 / -1; }
}

/* ═══════════ LA AYUDA OCUPA TODO EL ANCHO, Y UNA LÍNEA ═══════════════════
   Heredaba la columna del control, así que se envolvía en tres o cuatro líneas
   y ocupaba más alto que el campo que explica. Va de la etiqueta al final del
   control —las dos columnas— y ahí cabe en una.

   NO SE RECORTA CON PUNTOS SUSPENSIVOS: un texto de ayuda a medias es peor que
   ninguno. Lo que se hizo fue ACORTARLO en origen; esta regla solo le da el
   sitio que le corresponde. */
@media (min-width: 720px) {
  :root:not([data-escala="muy_grande"]):not([data-escala="maximo"]) form.form-etiquetado .field:not(.field--enlinea):not(.field--compacto) > .caption {
    grid-column: 1 / -1;
    margin-block-start: var(--space-1);
  }
}

/* ═══════════ EL CORRELATIVO DA PARA LOS NÚMEROS QUE EXISTEN ══════════════
   Se midió el histórico antes de elegir el ancho: los correlativos llegan a
   DIEZ caracteres —`E001240126`, `2021021201`—, no a cinco. 1.150 facturas
   tienen cuatro dígitos, 590 tienen seis y 156 tienen ocho.
   `ch` y no `rem`: la unidad es el ancho de un dígito, que es exactamente lo
   que hay que contar, y escala con la fuente del usuario igual. */
.numero-compuesto__correlativo { min-width: 11ch; }
.numero-compuesto__serie       { flex: 0 0 7ch; }

/* ─── LA PAREJA ENVUELVE ANTES QUE SALIRSE · `D-97` · 16/08/2026 ───────────
 *
 * **Se midió primero para qué existía ese suelo**, y no era para alinear una
 * columna: es para que quepan los correlativos que existen. El más largo de hoy
 * es `2510270101`, de **diez caracteres**, y 23 facturas los tienen. Así que
 * bajarlo tenía un coste real, no estético.
 *
 * ─── Y EL REMEDIO DE LA CASA NO ERA EL DE AQUÍ ───────────────────────────
 *
 * `min(11ch, 100%)` —el de `D-48`, `D-57` y `D-96`— **no arregló nada**: medido,
 * seguían desbordando las mismas 12 combinaciones de 224. El `100%` se resuelve
 * contra `.numero-compuesto`, que mide 307 px, así que el suelo seguía siendo
 * 211; lo que no cabía era **serie + guion + correlativo en una sola fila**.
 * *El problema no era que el campo no pudiera encoger, era que la fila no
 * cabía.* Y de paso encogía el campo por debajo de los diez caracteres que el
 * suelo existe para dar.
 *
 * Envolviendo, a 380 px con la letra grande la serie y el correlativo se ponen
 * en dos líneas, **cada uno con su ancho entero**: 0 desbordes de 224, y los
 * once caracteres intactos donde hay sitio. */
.numero-compuesto { flex-wrap: wrap; }

/* La ayuda arranca donde arranca la ETIQUETA, no donde arranca el control.
   Con `grid-column: 1/-1` ya ocupaba las dos columnas, pero el `column-gap` la
   empujaba: el texto empezaba con un escalón respecto al rótulo que explica, y
   ese escalón corta la lectura. Se anula el hueco solo para ella. */
@media (min-width: 720px) {
  :root:not([data-escala="muy_grande"]):not([data-escala="maximo"]) form.form-etiquetado .field:not(.field--enlinea):not(.field--compacto) > .caption {
    grid-column: 1 / -1;
    justify-self: stretch;
    margin-inline-start: 0;
  }
}

/* ═══════════ LA FILA RECIÉN CREADA O EDITADA ════════════════════════════
   Se desvanece sola, sin prisa —seis segundos— y NO es el único rastro: la
   etiqueta escrita «nuevo» o «editado» se queda mientras dure la visita. Un
   resalte que se apaga y encima distingue por tono sería dos veces invisible
   para quien no percibe el matiz (§8.5).
   El borde lateral es geometría, no color: sobrevive al desvanecido. */
[data-destacado] {
  animation: destacar 6s ease-out forwards;
}
[data-destacado] > *:first-child { box-shadow: inset 3px 0 0 0 var(--brand); }

@keyframes destacar {
  0%, 55% { background: var(--brand-soft); }
  100%    { background: transparent; }
}

.marca-destacado {
  display: inline-block; margin-inline-start: var(--space-2);
  padding: 0 var(--space-2); border-radius: var(--radius-sm);
  border: 1px solid var(--brand);
  font-size: var(--fs-caption); font-weight: var(--fw-medium);
  /* ─── LA PALABRA VA EN COLOR DE TEXTO, NO EN EL DE MARCA · 16/08/2026 ────
   *
   * Estaba en `--brand-strong` y **medido sobre la fila resaltada daba 4,18:1
   * en los dos temas oscuros** —por debajo de 4,5— y el modo de alto contraste
   * **no lo cambiaba en absoluto**: 4,18 con y sin él.
   *
   * Y es justo la señal que no es color: el resalte se apaga a los 6 s y la
   * palabra es lo que queda. *Una marca escrita que no se lee deja el aviso en
   * manos del tono, que es lo que §8.5 prohíbe.* La marca sigue siendo azul —
   * el borde—; lo que se lee, no. */
  color: var(--text); white-space: nowrap;
}
[data-contrast="high"] .marca-destacado { border-width: 2px; }

/* QUIEN PIDIÓ NO VER ANIMACIONES NO LAS VE, y no por eso pierde la señal: se
   queda el fondo puesto, sin transición. Una animación que ignora esa
   preferencia no es un detalle — marea a quien la configuró por eso. */
@media (prefers-reduced-motion: reduce) {
  [data-destacado] { animation: none; background: var(--brand-soft); }
}

/* ---------- La ficha en diálogo ----------
   ALTO AJUSTADO AL CONTENIDO, no fijo: la cadena fiscal tiene de dos a cuatro
   líneas según lo que aplique, y una altura fija dejaría hueco o cortaría. El
   tope es del 90 % de la ventana, y ahí dentro el cuerpo hace su propio scroll.

   `[open]` EN LA REGLA DE `display`, y esto no es una precaución teórica: sin
   ello el diálogo se dibuja sobre el listado sin que nadie lo abra — pasó ya
   una vez con el del comprobante. */
/* ─── 56 rem Y NO 46, ELEGIDO MIDIENDO ─────────────────────────────────────
   Kike pidió ensanchar. Lo que faltaba NO era ancho para que cupiera —a lo ancho
   nunca desbordó, medido a las cuatro escalas— sino **alto**: el cuerpo siempre
   desplaza. Así que ensanchar se juzga por el alto que ahorra.

   Medido en la E001-2339, alto del contenido contra los 1.107 px visibles:

       ancho    una columna   dos columnas
       46 rem      2.247          2.022
       56 rem      2.042          1.817     ← 430 px menos, un 19 %
       64 rem      2.042          1.817     ← idéntico: la curva ya es plana
       80 rem      1.835          1.611

   A escala máxima la misma forma: 3.756 → 3.012. **De 56 a 64 rem no se gana
   nada** y la línea de lectura se alarga, así que el ancho se queda donde la
   mejora acaba. Sigue desplazando —una ficha larga es larga— pero un quinto
   menos.

   ALCANCE: `.dlg-ficha` la usan **seis diálogos** —Movimientos, Facturas, Notas,
   Compras, Clientes y Cobros por cliente—. `.dlg-editar` declara su propio ancho
   a partir de `--form-max` y no se mueve. Se ensancha ESTA familia y no el
   diálogo en general: `.dialogo` son confirmaciones de 34 rem, que no necesitan
   sitio, y `.dialogo--formulario` ya mide 60. */
/* ─── UNA SOLA BARRA, Y ES LA DEL CUERPO · Kike, 16/08/2026 ────────────────
 *
 * Kike: *«observo doble navegador vertical en nivel 3 de ventanas»*. Medido en
 * el recorrido real —Tributos → Declaraciones → una declaración—: la ventana de
 * dentro tenía **dos contenedores con desplazamiento**, el `<dialog>` con 1.022
 * px de exceso y `.dlg-cuerpo` con 1.087.
 *
 * La causa: **esta familia no declaraba `overflow`**, así que el `<dialog>` se
 * quedaba con el `auto` del navegador. `.dialogo[open]` sí lo cierra desde
 * siempre; `.dlg-ficha` no, y con `height` fijo eso son dos barras.
 *
 * **Y la que se quita es la que no llevaba a ninguna parte**: los dos hijos
 * caben exactos en el alto (79 + 688 = 767), así que el `<dialog>` no tenía nada
 * que enseñar. Lo largo está dentro del cuerpo, y el cuerpo sigue
 * desplazándose entero. *Si el contenido no cabe, tiene que poder desplazarse.* */
.dlg-ficha[open] {
  display: flex; flex-direction: column;
  overflow: hidden;
  width: min(56rem, 95vw);
  max-height: 90dvh;
  padding: 0;
  border: 1px solid var(--border-strong); border-radius: var(--radius-lg);
  background: var(--superficie-ventana); color: var(--text);
}
.dlg-ficha .dlg-cabecera {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-3); padding: var(--space-4);
  border-block-end: 1px solid var(--border-strong);
}
.dlg-ficha .dlg-cuerpo { padding: var(--space-4); overflow: auto; min-height: 0; }

/* ─── LA TARJETA CONSERVA SU MARCO CUANDO NO ESTÁ SOLA ─────────────────────
   Esta regla nació cuando la ficha era UNA tarjeta: entonces el marco del
   diálogo era su marco y repetirlo era un doble borde. Con seis secciones lo
   que hacía era **borrar las seis divisiones a la vez**, que es exactamente lo
   que Kike vio —«no se distinguen las áreas»— y lo que el borde a 1,48:1
   agravaba.

   La condición se declara y no se supone: **si es la única tarjeta, el diálogo
   es su marco; si hay varias, cada una lleva el suyo.** No hay que acordarse de
   poner una clase al añadir la sexta.

   `:only-of-type` PARECÍA decir eso y decía otra cosa: cuenta por ETIQUETA. Una
   ficha con un `<section class="card">` y un `<div class="card">` deja al
   `section` como único de su tipo y le quita el marco mientras el `div` conserva
   el suyo — justo el reparto incoherente que esto viene a evitar, y hay fichas
   mezcladas así. `:has(> .card ~ .card)` pregunta lo que de verdad importa:
   **¿hay más de una tarjeta colgando del cuerpo?**, sea cual sea la etiqueta.

   Y si un navegador no entendiera `:has()`, la regla entera se descarta y las
   tarjetas conservan su marco: el fallo cae del lado bueno. */
.dlg-ficha .dlg-cuerpo:not(:has(> .card ~ .card)) > .card {
  border: 0; padding: 0; background: transparent;
}

/* MÓVIL: a 375 px una ficha completa tiene que seguir leyéndose, así que ocupa
   casi todo y no se queda en una columna estrecha con márgenes de adorno. */
@media (max-width: 480px) {
  .dlg-ficha[open] { width: 96vw; max-height: 94dvh; }
  .dlg-ficha .dlg-cabecera,
  .dlg-ficha .dlg-cuerpo { padding: var(--space-3); }
}

/* ---------- La fila que abre el detalle ----------
   El cursor y el realce al pasar dicen que es pulsable ANTES de pulsarla: sin
   señal, una fila que reacciona al clic es una sorpresa.
   El realce NO es la única pista —el enlace de la razón social está subrayado
   al pasar y es lo que se anuncia—, así que quien no perciba el fondo tiene
   igualmente dónde pulsar. */
tr.fila-pulsable { cursor: pointer; }
tr.fila-pulsable:hover > td { background: var(--surface-2); }
tr.fila-pulsable:focus-within > td { background: var(--surface-2); }

/* El enlace de la razón social se lee como texto hasta que se apunta: es el
   contenido de la celda, no un botón dentro de ella. */
.enlace-fila { color: inherit; text-decoration: none; }
.enlace-fila:hover,
.enlace-fila:focus-visible { color: var(--brand-strong); text-decoration: underline; }
[data-contrast="high"] .enlace-fila { text-decoration: underline; }

/* ─── UN ENLACE DE FILA QUE SE PUEDE TOCAR · `D-271` ────────────────────────
 *
 * `.enlace-fila` solo cambia el color: dentro de una celda, un enlace de texto
 * mide **21 px de alto** —medido el 18/08/2026 en «Impuestos pagados»— y el
 * mínimo táctil de la casa son 44. Con baja visión y en pantalla táctil, 21 px
 * es un blanco que se falla.
 *
 * ─── Y POR QUÉ NO SE LE PONE A `.enlace-fila` DIRECTAMENTE ─────────────────
 *
 * Porque la usan una veintena de tablas y darle un alto mínimo **crecería todas
 * las filas de Flow a la vez**, sin que nadie las haya medido. *No se toca lo
 * que no se ha medido.* Queda apuntado en `docs/DEUDA.md` para medirlo entero.
 *
 * Se compone con `.enlace-fila`: aquélla pone el color, ésta el tamaño. */
.enlace-tactil {
  display: inline-flex; align-items: center;
  min-block-size: var(--tactil-minimo);
}

/* ---------- El diálogo de la ficha, corregido tras verlo ----------
   SE VIO EN PANTALLA Y ESTABA MAL DE DOS FORMAS. Ninguna se deducía del CSS:
   la regla parecía correcta y el resultado no lo era.

   1 · CLAVADO ARRIBA A LA IZQUIERDA. Un `<dialog>` modal se centra por el
       `margin: auto` de la hoja del navegador, y algo lo ponía a cero; con
       `inset: 0` y posición fija, eso lo pega a la esquina. Se declara el
       centrado en vez de confiar en heredarlo.

   2 · EL CONTENIDO PARTIDO LETRA POR LETRA. `.ficha-datos` es una rejilla de
       columnas de 16 rem mínimo; dentro de un diálogo de 46 rem con relleno,
       las columnas se estrangulaban y el texto caía en vertical. Dentro del
       diálogo va a UNA columna: la ficha se lee de arriba abajo, que es como
       se lee un detalle. */
.dlg-ficha[open] {
  inset: 0;
  margin: auto;
}
/* 3 · Y LA CAUSA DE FONDO: `.dlg-cuerpo` es `display: flex` por la regla del
       visor de comprobantes —allí hace falta para centrar un PDF— y mi regla
       no lo sobrescribía. Las cuatro tarjetas de la ficha se ponían EN FILA,
       cada una estrangulada a 60-130 px, y de ahí el texto en vertical.
       Reusar una clase trae su `display` con ella.

   ─── Y AQUÍ, NO ARRIBA, PORQUE ESTA REGLA ES LA QUE GANA ──────────────────
   Era `display: block`, y con eso las secciones quedaban a **0 px unas de
   otras**: en la página las separa la pila del contenido, que dentro del
   diálogo no existe, y una tarjeta no trae margen propio. Con cinco áreas eso
   es justo lo que Kike vio — «no se distinguen».

   La rejilla lo resuelve mejor que un `margin`: `gap` no se colapsa, no depende
   del orden de los hijos y no hay que acordarse de quitárselo al primero. Y
   `minmax(0, 1fr)` es lo que impide que una tabla ancha o una tirada sin
   espacios estire el cuerpo — la misma pareja de siempre.

   Declarada AQUÍ y no doscientas líneas más arriba: la del visor viene después
   en la hoja, así que una `display` puesta antes no la habría sobrescrito. Lo
   mismo que ya costó una vez con el texto en vertical.

   `--space-5` y no `--space-4`: es el mismo hueco que separa las secciones en
   la página —medido, de 26 a 45 px según la escala—. Con `--space-4` daba 17, y
   la misma ficha con dos ritmos distintos es la pantalla que hay que aprender
   dos veces. */
.dlg-ficha .dlg-cuerpo {
  display: grid; grid-template-columns: minmax(0, 1fr);
  gap: var(--space-5); align-content: start;
}

/* ═══════════ UN DATO QUE SE MUESTRA Y NO SE EDITA ═══════════════════════
   Va en un `<p>`, no en un `input` apagado: un control deshabilitado tiene
   marco y fondo, invita a pulsarlo, y además no se envía. Aquí la forma dice
   la verdad —esto se lee— antes de que nadie lo intente.

   Comparte `.field` con los campos editables A PROPÓSITO: así hereda la
   etiqueta al lado de §8.5, los anchos por contenido y la subida de la
   etiqueta a partir de «muy grande». Un bloque nuevo con su propio reparto
   sería el cuarto sitio donde algo se dibuja distinto sin motivo.

   `min-height` iguala su alto al de un `input` para que las filas mixtas no
   queden a distinta altura; `align-content` lo centra dentro de ese alto. */
/* Con TOKENS QUE EXISTEN. La primera versión usaba `--control-alto` y `--fs-h3`,
   que no están en `design-tokens.css`: funcionaban por el valor de respaldo del
   `var()`, y eso es lo que las hacía difíciles de ver — no fallaban, quedaban a
   la espera de que alguien creara ese nombre con otro significado. */
/* UN VALOR SIN ESPACIOS NO PUEDE EMPUJAR LA PÁGINA ENTERA.
   Medido el 5 de agosto de 2026 con una razón social del largo máximo del campo
   —190 caracteres— en la ficha de cliente: el texto salía de la tarjeta, cruzaba
   la ventana y dejaba **barra horizontal en toda la página**, con lo demás
   desplazado. El caso realista es más corto pero existe en la base de hoy: hay
   un correo de 77 caracteres sin un solo espacio, y los correos de esta columna
   admiten 190.
   `min-width: 0` va con `overflow-wrap` a propósito: dentro de un contenedor
   flex, la caja no puede encoger por debajo de su contenido mínimo y la regla
   de partir palabras no llega a aplicarse — es la misma pareja que ya usan la
   tabla responsiva y el combo. */
.dato-fijo {
  min-height: 2.5rem;
  display: flex; align-items: center;
  padding-block: var(--space-2);
  color: var(--text);
  min-width: 0; overflow-wrap: anywhere;
}
/* LO AUSENTE SE VE DISTINTO SIN SER SOLO COLOR: además del tono más apagado va
   en cursiva, que se distingue en alto contraste y en escala de grises. */
.dato-fijo--ausente { color: var(--text-secondary); font-style: italic; }
.dato-fijo--fuerte  { font-weight: var(--fw-semibold); font-size: var(--fs-lg); }
/* Las cifras alineadas a la derecha se comparan de un vistazo; el texto no. */
.dato-fijo.num { font-variant-numeric: tabular-nums; }

/* ═══════════ LA VENTANA DE EDICIÓN ES MÁS ANCHA QUE LA DE FICHA ═════════
   46 rem es medida de ficha —una columna de etiqueta y valor—. Un formulario
   con etiqueta AL LADO del campo necesita el mismo `--form-max` que en su
   página, o el reparto que se midió a cuatro escalas se estrangula y cae a
   una columna justo donde había sitio para dos.

   `[open]` otra vez en la regla: sin él la ventana se dibuja sobre el listado
   sin que nadie la abra. Van tres veces. */
/* ─── RETIRADA · el ancho fijo que Kike vio ────────────────────────────────
   `min(--form-max + 4rem, 95vw)` daba **952 px en cualquier pantalla**. Se
   sustituye por la proporción de más abajo. No se borra el porqué: es la
   medida que hizo que en la pantalla de Kike la ventana ocupara el 55 %. */

/* ═══════════ LA CONFIRMACIÓN DE ELIMINAR UNA FACTURA ════════════════════
   Más ancha que el diálogo base —34 rem— porque no solo pregunta: enseña los
   datos de lo que se va a perder y explica en qué se diferencia de anular. A
   34 rem esa explicación se parte en seis líneas y se lee como relleno, justo
   cuando es lo único que evita elegir mal.

   No lleva `display` propio: `.dialogo` ya lo resuelve, y redeclararlo aquí
   sin `[open]` la dibujaría sobre la pantalla sin que nadie la abra. */
.dlg-eliminar { max-width: min(42rem, 94vw); padding: 0; }
.dlg-eliminar .dlg-cabecera,
.dlg-eliminar .dlg-cuerpo,
.dlg-eliminar .dlg-pie { padding: var(--space-4); }
.dlg-eliminar .dlg-cuerpo > * + * { margin-block-start: var(--space-4); }
/* Los tres datos EN COLUMNA aunque `ficha-datos` reparta en rejilla: son la
   única cosa que hay que comparar con la pantalla, y en fila se ojean. */
.datos-eliminar { grid-template-columns: 1fr; gap: var(--space-2); margin-block-start: var(--space-3); }
.datos-eliminar dd { font-weight: var(--fw-semibold); }
/* El botón que destruye NO va pegado al que cancela: el hueco es parte de la
   separación por orden, igual que en la ficha. */
.dlg-eliminar .dlg-pie { display: flex; gap: var(--space-4); justify-content: flex-end; flex-wrap: wrap; }
/* Dentro de la ventana el formulario ya está centrado por ella: `margin-inline:
   auto` sobre un hijo flexible lo encoge al contenido en vez de estirarlo —el
   mismo fallo de la barra de filtros—, así que aquí se anula.

   ─── Y VALE PARA LAS SEIS, NO SOLO PARA LA DE EDITAR ─────────────────────
   Decía `.dlg-editar`, y el mismo `margin-inline: auto` lo llevan **la ficha de
   cliente y la de compra**, que también son `.form-reparte`. Mientras
   `.dlg-cuerpo` era `display: block` no se notaba: un bloque con `max-width` y
   márgenes automáticos se queda centrado a su ancho máximo. Al pasar a
   `display: grid` (7 de agosto de 2026) los márgenes automáticos **apagan el
   estirado** de la rejilla y el hijo se encoge a su contenido.

   Medido ese mismo día, con el cuerpo del diálogo dando 901 px:

       ficha de cliente (UNIVERSIDAD ANTONIO RUIZ DE MONTOYA)   412 px
       ficha de compra  (E001 91)                              463 px
       ficha de compra  (26080701)                             884 px  ← tope

   Es decir: **el diálogo se ensanchó de 46 a 56 rem y estas dos fichas se
   quedaron con menos de la mitad**, con el resto en blanco a los lados. El
   ancho resultante dependía de lo largo que fuera el texto de dentro, que es el
   síntoma exacto de `fit-content`.

   Se anula en `.dlg-ficha`, que es la familia entera —`.dlg-editar` la lleva
   también—: el límite de ancho aquí lo pone la ventana, no `--form-max`. */
.dlg-ficha .dlg-cuerpo .form-reparte { max-width: none; margin-inline: 0; }
/* El botón de guardar no se va con el desplazamiento del cuerpo: en un
   formulario de siete campos la acción tiene que verse sin buscarla. */
.dlg-editar .filtros-acciones {
  position: sticky; bottom: 0;
  background: var(--surface); padding-block: var(--space-3);
  border-top: 1px solid var(--border);
}
/* ─── DOS COLUMNAS, ATADAS A SI CABEN Y NO A LA ESCALA ─────────────────────
   Estaba clavada a UNA columna, y con motivo: a 46 rem las columnas de 16 rem se
   estrangulaban y el texto caía en vertical. Pero la respuesta a «se estrangula»
   no es «una siempre» — es pedir el ancho que hace falta y dejar que la rejilla
   decida. Atarlo a la escala sería la condición equivocada de siempre.

   `minmax(max(18rem, 48%), 1fr)` dice justo eso: mientras el 48 % del cuerpo
   pase de 18 rem, la pista mide medio contenedor y **caben dos exactamente**;
   cuando el cuerpo se estrecha —móvil, o el diálogo al 95 % de una ventana
   pequeña—, el suelo de 18 rem manda, dos no caben y **cae a una sola** sin que
   nadie lo declare. Ni tres: medio contenedor no admite un tercero.

   Medido en la E001-2339, columnas resultantes: 351 px a normal, 443 a grande,
   535 a muy grande, 627 a máxima con 46 rem; de 436 a 776 con los 56 de ahora.
   Dos columnas en las cuatro, ninguna estrangulada. */
.dlg-ficha .ficha-datos { gap: var(--space-3) var(--space-4); }
/* Lo que dentro de la página va en fila, aquí se apila: no hay ancho para dos. */
.dlg-ficha .dlg-cuerpo .row { flex-wrap: wrap; }
.dlg-ficha .dlg-cuerpo > .card > .card-head { padding-inline: 0; }

/* ---------- Listado de notas de crédito ----------
   Los mismos anchos que el de facturas: si dos listados del mismo módulo se
   dibujan distinto, el operador lo nota antes que nosotros (§8.10).
   `nowrap` en los números porque se veían partidos —`E001-` arriba y `249`
   abajo—, y un número de comprobante cortado deja de ser un número. */
.tabla-notas .col-fecha     { width: 11ch; white-space: nowrap; }
.tabla-notas .col-numero    { white-space: nowrap; }
/* Igual que en Facturas: el recorte llega hecho por el medio desde PHP, así que
   el tope de aquí solo podría cortar dos veces. Ver la nota de
   `.tabla-facturas .col-cliente`. */
.tabla-notas .col-cliente   { white-space: nowrap; }
.tabla-notas .col-categoria { max-width: 34ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ---------- La tabla ocupa su sitio ----------
   `.tabla` NO TENÍA NINGUNA REGLA: la clase estaba en el marcado de los tres
   listados y nadie la estilizaba. Facturas llenaba su envoltura por casualidad
   —nueve columnas cuyos anchos sumaban más que el contenedor— y notas, con
   siete, se quedaba en 1.321 de 1.656 y parecía «más estrecha».

   El síntoma era de notas y la causa, de la clase compartida. Declararlo aquí
   lo arregla para las tres y para la siguiente. */
.tabla { width: 100%; border-collapse: collapse; }

/* ═══════════ EL ENCABEZADO VA SOBRE SU COLUMNA, NO EN MEDIO ══════════════

   `<th>` viene centrado por el navegador y las celdas van alineadas al
   principio, así que en una tabla con una columna ancha el rótulo flota lejos
   de lo que nombra. Medido en la cadena de ajustes de una factura: la columna
   «Ajuste» ocupa de 261 a 1020 px, los nombres empiezan en 261 y el rótulo
   caía en 640. Nada desbordaba y aun así no se leía como una tabla.

   Y `.num` solo ponía cifras tabulares —que alinean los dígitos entre sí— sin
   alinear la celda, así que las cantidades quedaban pegadas a la izquierda de
   columnas anchas y no formaban columna. Se alinea a la derecha DENTRO de
   `.tabla` y no en la clase global: `.num` también viste importes sueltos de
   fichas y formularios, donde la derecha no significa nada. */
.tabla th               { text-align: start; }
.tabla th.num,
.tabla td.num           { text-align: end; }

/* ─── Y LAS CELDAS NECESITAN AIRE, QUE NADIE LES HABÍA DADO ────────────────
 *
 * `.tabla` no declaraba relleno horizontal en ninguna parte, así que valía el
 * del navegador: **1 px**. Con columnas anchas no se nota —el texto no llega al
 * borde— y por eso llevaba tiempo pasando sin verse.
 *
 * Se vio en Comisiones el 8 de agosto de 2026, y se vio porque ahí una columna
 * queda justa: la última lleva un desplegable y un botón y se come 857 px, de
 * modo que «Comisión» (alineada a la derecha) acaba pegada a «Estado»
 * (alineada a la izquierda) con **2 px** entre las dos. En pantalla se leía
 *
 *     ComisiónEstado          y          S/ 281.811 en el mes
 *
 * Un importe fundido con un conteo: «S/ 281.811». Ninguna medida de desbordamiento
 * lo detecta —nada desborda— y aun así la cifra que se lee no es la que hay.
 *
 * Va en `.tabla` y no solo en Comisiones porque el hueco es de `.tabla`: las
 * demás se libran por el ancho de sus columnas, no por tener otra regla. El
 * envoltorio ya trae `overflow-x: auto`, así que una tabla que crezca se
 * desplaza en vez de romperse. */
.tabla th,
.tabla td               { padding-inline: var(--space-3); }
/* La primera y la última a ras del borde de la tarjeta, como estaban. */
.tabla th:first-child,
.tabla td:first-child   { padding-inline-start: 0; }
.tabla th:last-child,
.tabla td:last-child    { padding-inline-end: 0; }

/* ─── EL CORTE DE MES TIENE QUE VERSE COMO CORTE ───────────────────────────
 *
 * Comisiones agrupa por mes con un `<tbody>` por grupo y un
 * `<th scope="colgroup">` — la estructura es correcta y el lector de pantalla
 * anuncia la agrupación. **Pero no había ni una regla de estilo para
 * `.grupo-mes` ni para `.fila-mes`**: comprobado el 8 de agosto de 2026, cero
 * declaraciones en las tres hojas.
 *
 * Así, lo único que distinguía «Agosto de 2026» de una comisión más era la
 * negrita que el navegador le da a cualquier `<th>` — el mismo peso que la
 * cabecera de la tabla, la misma altura de fila y ningún separador. Con dos
 * grupos se adivina; con doce meses en pantalla es una lista corrida.
 *
 * Se separa con una regla superior y aire, no con color: §8.5 no admite
 * significado solo por color, y el corte tiene que leerse en blanco y negro. */
.tabla .grupo-mes + .grupo-mes .fila-mes > * {
  border-block-start: 1px solid var(--border);
  padding-block-start: var(--space-4);
}
/* ─── LA LÍNEA QUE SE LEE Y AHORA TAMBIÉN SEPARA · `D-752`, 27/08/2026 ─────
 *
 * Medido antes de tocarla: su texto contrastaba **15,22** —se lee perfectamente—
 * pero **el fondo era el mismo, el borde 0 px y el alto igual al de una fila
 * normal**. Entre treinta y tres filas, leerla exigía buscarla.
 *
 * Se le ponen **tres señales, no una**, porque el color no puede ser la única:
 *
 *   · **aire**: la fila mide **75 px contra los 62** de una normal;
 *   · **una raya propia arriba**, de 3 px. Con `--border` se quedaba en **2,73
 *     · 2,99 · 2,77 · 2,96** contra el fondo nuevo —medido, por debajo del 3:1
 *     que pide un elemento gráfico—, porque `--border` está calibrado contra el
 *     fondo de la tarjeta y aquí va sobre `--surface-2`, que es más claro. Se
 *     usa `--text-secondary`, que da **7,37 · 6,57 · 12,07 · 11,72**;
 *   · **un fondo distinto**, `--surface-2`, que es el mismo tono que Flow usa
 *     para las filas alternas y las tarjetas anidadas.
 *
 * Y la palabra sigue ahí: «Hasta aquí este mes». *Nada se encoge* — la fila
 * crece con el aire, no se aprieta para caber. */
.tabla .fila-mes > * {
  padding-block: var(--space-4) var(--space-3);
  background: var(--surface-2);
  border-block-start: 3px solid var(--text-secondary);
}
.tabla .fila-mes th       { font-size: var(--fs-body); letter-spacing: .01em; }


/* ═══════════ LOS TRES TOP DE CLIENTES, EN EL MOLDE APROBADO ═════════════
   `.tercios` ya reparte uno grande y dos pequeños en Facturas. Aquí los tres
   pesan igual, así que la variante solo cambia el reparto: las clases de
   tarjeta, título y alto son las MISMAS, no unas parecidas. */
/* ─── 23 REM, Y EL NÚMERO LO DECIDE ────────────────────────────────────────
 *
 * Eran 18 rem. Medido el 7 de agosto de 2026 sobre la E001-2339: «S/ 4.399,91»
 * con el relleno de la tarjeta pide **23,3 rem**, así que en tres columnas de
 * 18 no cabía y se partía —o, con `nowrap`, se salía—.
 *
 * Con 23 rem, tres columnas solo aparecen cuando de verdad caben; donde no
 * —el diálogo, que da unos 750 px— bajan a menos columnas y la cifra se lee
 * entera. Es la misma decisión que los widgets: **atarlo a si cabe, no a la
 * escala**.
 *
 * Un importe de siete cifras pediría 28,1 rem y en el diálogo seguiría
 * entrando, porque a una columna dispone del ancho completo. */
/* ─── UN SUELO NO PUEDE PASAR DEL 100 % DE SU CAJA ────────────────────────
 *
 * Era `minmax(23rem, 1fr)` a secas, y 23 rem son 391 px que **no encogen**.
 * Medido el 7 de agosto de 2026 con el marco a anchos reales de teléfono:
 *
 *     ancho    contenedor   pista    barra horizontal en TODA la página
 *     414 px      367        391     sí   (405 contra 395)
 *     375 px      328        391     sí   (405 contra 356)
 *     360 px      313        391     sí   (405 contra 341)
 *
 * En Movimientos y en Cobros la página entera se desplazaba a lo ancho, con la
 * barra de menú y la tabla arrastradas — no era la tarjeta la que se salía, era
 * la rejilla la que medía más que su caja. `@media (max-width: 480px)` ya baja
 * `.tercios` a una columna, pero esta regla va después y con la misma
 * especificidad, así que ganaba.
 *
 * Es el MISMO arreglo que `.ficha-datos`: `min(23rem, 100%)` dice que la pista
 * pide 23 rem **mientras quepan**, y cuando no, pide lo que hay. La rejilla cae
 * a una columna sola, que es lo que hacía falta. Medido después: sin barra a
 * 320, 360, 375 y 414. */
.tercios--tres { grid-template-columns: repeat(auto-fit, minmax(min(23rem, 100%), 1fr)); }

/* ─── Y A ESCALA GRANDE SE PARTÍA · `D-702` ────────────────────────────────
 *
 * `auto-fit` decide cuántas columnas caben comparando el ancho disponible con
 * **23 rem**, y `rem` sigue a la escala de texto: a «muy grande» y a «máximo»
 * esos 23 rem miden casi el doble, dejan de caber tres, y la tercera tarjeta
 * **cae a una segunda fila**. Medido el 25 de agosto de 2026 en una ventana de
 * 3440 px, que es todo lo ancha que se quiera: Tributos y el panel pasaban de
 * `[558, 558, 558]` en una fila a `[829, 829, 829]` en dos.
 *
 * No es un defecto del panel: es de esta regla, y por eso se arregla aquí y no
 * en una clase nueva de una pantalla. *Se arregla en un sitio y el de al lado
 * sigue mal* — ya pasó con el símbolo, con el retorno y con los buscadores.
 *
 * El umbral pasa a ser **el ancho de la ventana en píxeles**, que es lo que de
 * verdad decide si caben tres tarjetas. El tamaño del texto no puede decidirlo:
 * agrandar la letra no estrecha la pantalla. Por debajo de 1000 px sigue
 * mandando la regla de arriba —la que quitó la barra horizontal a 320, 360, 375
 * y 414—, y ahí apilarse es lo correcto.
 *
 * `minmax(0, 1fr)` y no `1fr` a secas: sin el cero, una cifra larga impide que
 * su columna se encoja y la rejilla mide más que su caja, que es exactamente el
 * desborde que costó los cuatro anchos de móvil. */
@media (min-width: 1000px) {
  .tercios--tres { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

/* ─── Y EL CONTENIDO SE CENTRA EN EL ALTO QUE LE TOCA ────────────────────
 *
 * Las tarjetas de una fila miden todas lo que mide la más alta. La que lleva
 * barras la llena; la que lleva una cifra, no. Medido a escala normal: en
 * Facturas la de «Por cobrar» dejaba **122 px muertos** debajo contra los 27
 * de sus dos vecinas, y en Cobros pasaba lo mismo con las dos primeras.
 *
 * Centrar en vertical no inventa contenido ni estira nada: reparte el hueco
 * arriba y abajo en vez de amontonarlo al pie, que es lo que hacía que la
 * tarjeta pareciese a medio llenar. Donde el contenido ya llega abajo —las de
 * barras— `center` no mueve un píxel, así que la regla es segura para todas.
 *
 * Va en el CONTENEDOR y no en `.card` a secas: una tarjeta suelta de ficha o
 * de formulario crece con su contenido y ahí centrar no significaría nada. */
/* ─── PERO LAS CIFRAS TIENEN QUE QUEDAR A LA MISMA ALTURA ────────────────
 *
 * Kike, 10 de agosto de 2026: *«los montos de los widgets 1 y 2 deben estar
 * alineados horizontalmente; que cada uno se muestre a diferente altura causa
 * desorden»*. Y tiene razón contra el argumento de arriba: centrar reparte el
 * hueco, pero **cada tarjeta lo reparte en su propio alto**, así que dos
 * tarjetas con distinto número de líneas de pie ponen su cifra en dos sitios
 * distintos. En Compras la del crédito lleva cuatro líneas de explicación y la
 * de compras una: la diferencia se ve a simple vista.
 *
 * Una fila de cifras se lee **barriendo en horizontal**, y ese barrido es lo
 * primero que se pierde. El hueco muerto al pie es un defecto menor; dos
 * cifras a distinta altura rompen la comparación, que es para lo que la fila
 * existe.
 *
 * Así que arriba, no centrado: todas las tarjetas empiezan en el mismo borde y
 * su rótulo mide una línea, luego la cifra cae a la misma altura en todas. Lo
 * que sobra queda abajo, que es donde el ojo ya no está. */
.tercios .card.stat { display: flex; flex-direction: column; justify-content: flex-start; }
/* El rótulo, una línea de alto reservada aunque el texto sea corto: si en una
   tarjeta envuelve a dos líneas y en la vecina no, la cifra vuelve a bailar. */
.tercios .card.stat > .widget-magnitud > .rotulo-bloque { min-block-size: 1.5em; }

/* UNA LÍNEA POR PUESTO, que es lo que hace que quepan cinco en el alto del
   molde. Con nombre y monto en líneas separadas pedían ~310 px y la tarjeta
   da 306 menos su título. */
/* El aire se ajusta a lo que el molde da, no al revés: con `--space-3` arriba
   y `--space-2` entre puestos el contenido pedía 331 px contra los 313 de la
   tarjeta. Veinte píxeles medidos, no estimados. */
.widget-top__lista { margin: var(--space-2) 0 0; padding-inline-start: 1.5em; }
.widget-top__lista li {
  display: flex; align-items: baseline; gap: var(--space-2);
  margin-block-end: var(--space-mini);
}
/* EL NOMBRE CEDE Y EL MONTO NO: la cifra es corta y comparable entre filas, y
   recortarla la haría ilegible. El nombre completo sigue en el enlace. */
.widget-top__nombre {
  flex: 1 1 auto; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.widget-top__monto { flex: 0 0 auto; font-weight: var(--fw-semibold); font-variant-numeric: tabular-nums; }

/* Una línea y sin partirse: es lo que hace que los cinco puestos quepan. */
.widget-top__nota { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* ═══════════ LA MARCA DE «NETO INCOMPLETO» ══════════════════════════════
   Es lo único que impide que un puesto ordenado con un monto por encima del
   real pase por bueno. Se reduce de tamaño; no se reduce a invisible.

   Es un `<details>` y no un globo con `:hover`: aquel cumplía §8.5 sobre el
   papel y no se pudo comprobar —sin foco de sistema, ni `:hover` ni `:focus`
   casan—. Un control de verdad se abre con ratón, teclado y dedo, y su estado
   se puede leer. */
/* El símbolo es «i» —información— y no «!»: lo que hay detrás explica de dónde
   sale el número, no advierte de un peligro. */
.marca-incompleto { flex: 0 0 auto; position: relative; display: inline-block; }
.marca-incompleto__resumen {
  display: inline-flex; align-items: center; gap: .25em;
  font-size: var(--fs-caption); color: var(--text-secondary);
  border: 1px solid var(--border-strong); border-radius: var(--radius-sm);
  padding: 0 .35em; cursor: pointer; list-style: none;
  /* SIN `min-height`: los 44 px del objetivo táctil hacían la fila con marca
     casi el doble de alta que las demás, y en una lista de cinco puestos ese
     escalón se ve antes que la marca. Se vio en la captura, no en la medición:
     los números decían que nada desbordaba, y era cierto. */
  position: relative;
}
/* EL ÁREA QUE SE PULSA SÍ LLEGA A LOS 44 px, con un pseudoelemento que la
   agranda sin empujar nada: la caja se queda pequeña y el dedo acierta igual. */
.marca-incompleto__resumen::after {
  content: ""; position: absolute; inset: 50% 0 auto 0;
  height: var(--tactil-minimo); transform: translateY(-50%);
}
/* El triángulo por omisión de `summary` sobra: la marca ya lleva su símbolo. */
.marca-incompleto__resumen::-webkit-details-marker { display: none; }
/* NO SOLO COLOR (§8.5): el símbolo y la palabra dicen lo mismo que el tono, y
   sobreviven al alto contraste y a la escala de grises. */
.marca-incompleto__simbolo { font-weight: var(--fw-semibold); }
.marca-incompleto[open] .marca-incompleto__resumen { border-color: var(--text); }
/* ANCLADO A LA DERECHA porque la marca vive al final de su línea: abierto hacia
   la izquierda se saldría de la tarjeta. */
.marca-incompleto__detalle {
  position: absolute; inset-inline-end: 0; inset-block-start: calc(100% + .3em);
  /* `display: none` en reposo. Con `display: block` se pisaba la regla con la
     que el navegador oculta el contenido de un `details` cerrado, y el globo
     medía 159 px estando cerrado: invisible pero ocupando. */
  z-index: 5; width: max(16rem, 100%); display: none;
  background: var(--surface); color: var(--text);  /* NO es una ventana: un desplegable dentro de la pantalla. Se queda donde estaba. */
  border: 1px solid var(--border-strong); border-radius: var(--radius-md);
  box-shadow: var(--shadow-2); padding: var(--space-3);
  font-size: var(--fs-caption); font-style: normal; font-weight: 400;
}
/* SE ABRE AL PULSAR —`[open]`— Y TAMBIÉN AL PASAR POR ENCIMA.

   El paso del ratón es un añadido de comodidad, no el camino principal: no se
   puede ejercitar en este navegador —la pestaña no tiene el foco del sistema y
   `:hover` no casa— y por eso queda DECLARADO SIN COMPROBAR. Si algún día
   fallara, la marca seguiría abriéndose al pulsar, que es lo que se probó y lo
   que la hace existir con teclado y en táctil. */
.marca-incompleto[open] .marca-incompleto__detalle,
.marca-incompleto:hover .marca-incompleto__detalle { display: block; }
.marca-incompleto__resumen:focus-visible {
  outline: var(--focus-width) solid var(--focus-color); outline-offset: 2px;
}

/* ═══════════ UNA CELDA QUE NO ESTIRA LA FILA ════════════════════════════
   A escala máxima una razón social de 100 caracteres partía la fila en ocho
   líneas —de 58 px a 247— y con treinta facturas la tabla dejaba de leerse.
   El texto completo sigue en el `title` y en la factura enlazada. */
.celda-recortada { max-width: 22rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ═══════════ UN CAMPO QUE LO RELLENA EL PADRÓN ══════════════════════════
   `readonly` y no `disabled`: un campo deshabilitado NO se envía, así que
   llegaría vacío al servidor y se guardaría como nulo — el fallo que estuvo a
   punto de borrar 210 importes de notas.

   Y se ve distinto de uno editable, porque si no la única señal de que no se
   puede escribir es descubrirlo intentándolo. No es solo el color (§8.5): el
   cursor cambia y el borde también. */
.input--del-padron {
  background: var(--surface-2);
  border-style: dashed;
  cursor: not-allowed;
}

/* ─── UNA FILA ARCHIVADA NO SE PARECE A UNA VIVA ─────────────────────────────
 *
 * Solo se ven cuando se pide verlas, así que el listado entero es de
 * archivadas y no hay mezcla. Aun así se marca la fila, por dos razones que no
 * son la misma: la pestaña puede quedarse abierta y alguien vuelve a ella
 * media hora después, y las capturas de pantalla viajan sin su barra de
 * filtros.
 *
 * **La marca no es el color.** El color de fondo apoya; lo que dice qué es la
 * fila es la palabra «Archivada» en su insignia, que se lee igual en alto
 * contraste, en escala de grises y en un lector de pantalla (§8.4).
 */
tr.fila-archivada > td {
  background: var(--surface-2);
  border-inline-start: 3px solid var(--text-secondary);
}
tr.fila-archivada > td:not(:first-child) { border-inline-start: 0; }

/* El texto pierde peso pero NO contraste: atenuar hasta que cueste leerlo
   convertiría «está archivada» en «no la mires», y estas son las filas que
   alguien está mirando a propósito. */
tr.fila-archivada .enlace-fila { font-style: italic; }

[data-contrast="high"] tr.fila-archivada > td {
  border-inline-start-width: 5px;
  outline: 1px dashed var(--text-secondary);
  outline-offset: -3px;
}

/* ─── LA CELDA QUE LLEVA LA MARCA DE ARCHIVADA DEJA DE RECORTAR ─────────────
 *
 * Y esto no es estética: **el botón «Restaurar» quedaba fuera del recorte y no
 * se podía pulsar.** Medido el 5 de agosto de 2026 sobre `/facturas?archivadas=1`
 * con `getBoundingClientRect()`, en el viewport de 2178 px del arnés:
 *
 *     td.col-cliente          486 px de ancho · overflow:hidden · nowrap
 *     contenido que necesita  821 px
 *     <a> razón social        547 → 863   dentro
 *     <span> «Archivada»      867 → 965   dentro
 *     <span> «por … el …»     969 → 1257  se sale 225 px
 *     <button> Restaurar     1257 → 1366  FUERA ENTERO, recortado
 *
 * El botón estaba en el DOM —`document.querySelectorAll('.js-archivar-documento')`
 * devolvía 1— y en pantalla no había ninguno. Es el caso que ya dejó el nombre
 * de un proveedor en «COCINA DE»: la medida decía que nada desbordaba porque la
 * celda recorta a propósito, y lo recortado era la acción.
 *
 * Se relaja SOLO en la fila archivada, que es una vista aparte —hay que pedirla
 * con `?archivadas=1`— y donde la densidad importa mucho menos que poder
 * accionar. El listado normal no cambia ni un píxel.
 *
 * Los tres selectores van explícitos y con la misma forma que la regla que
 * anulan: `.celda-con-archivada` a secas pierde en especificidad contra
 * `.tabla-facturas .col-cliente`, y una regla que no gana es una regla que no
 * existe. */
.tabla-facturas .col-cliente.celda-con-archivada,
.tabla-notas    .col-categoria.celda-con-archivada,
.tabla          .celda-recortada.celda-con-archivada {
  overflow: visible;
  white-space: normal;
}

/* ─── SE MANTIENE EL TOPE DE ANCHO, A PROPÓSITO ────────────────────────────
 *
 * La primera versión puso `max-width: none` y el botón sí quedó dentro… porque
 * la celda creció de 486 a 779 px y **estrujó las otras siete columnas**: los
 * rótulos «Categoría» y «Estado» quedaron pegados, y el importe contra el
 * sector. Cambiar un recorte por un apretujón no es arreglarlo.
 *
 * Con el tope puesto y `white-space: normal`, la marca y el botón bajan a su
 * propia línea DENTRO del ancho de siempre: la fila archivada crece hacia
 * abajo, que es la dirección donde sobra sitio. */
.celda-con-archivada .enlace-fila { white-space: normal; }

/* ─── EL SEPARADOR DEL MENÚ, Y EL TÍTULO DE GRUPO ───────────────────────────
 *
 * Lo que hay bajo el separador es de otra naturaleza: ajustar el sistema y
 * salir de él, no trabajar. La regla lo dice a la vista y el `<hr>` lo dice al
 * lector de pantalla, que es la misma división por dos vías (§8.4).
 */
.separador-menu {
  border: 0;
  border-block-start: 1px solid var(--border);
  margin-block: var(--space-5) var(--space-4);
}
[data-contrast="high"] .separador-menu { border-block-start-width: 2px; }

/* El nombre del grupo es una etiqueta, no un encabezado de sección: pesa menos
   que el título de la pantalla y más que un enlace. */
.menu-grupo {
  font-size: var(--fs-label);
  font-weight: var(--fw-semibold);
  color: var(--text-secondary);
  text-transform: uppercase;
  letter-spacing: .04em;
  margin-block: var(--space-4) var(--space-2);
}

/* ─── EL MENÚ, COMO PANEL SOBRE EL CONTENIDO ────────────────────────────────
 *
 * Lo que había llevaba a otra pantalla: navegar para poder navegar. Esto se
 * abre encima y se cierra sin moverse de donde uno está.
 *
 * **Nada depende de `:hover`.** Se abre pulsando, y se cierra pulsando, con
 * Escape o pulsando fuera — el táctil no tiene «encima», y ningún auditor de
 * este proyecto mira ese estado.
 */
.js-menu-boton { display: inline-flex; align-items: center; gap: var(--space-2); }

/* Las tres líneas. En SVG habría que meterlo en el sprite; con tres `<span>`
   se dibuja solo y se colorea con la tipografía, así que sigue al tema y al
   alto contraste sin una regla más. */
.menu-icono { display: inline-flex; flex-direction: column; justify-content: center; gap: 4px; width: 18px; }
.menu-icono > span { display: block; height: 2px; background: currentColor; border-radius: 1px; }
[data-contrast="high"] .menu-icono > span { height: 3px; }

.menu-panel {
  position: absolute; inset-inline-end: var(--space-4); inset-block-start: 100%;
  z-index: 60; min-width: 16rem; max-width: min(92vw, 22rem);
  /* ─── QUE QUEPA EN LAS CUATRO ESCALAS ─────────────────────────────────────
   * Nueve opciones a escala máxima no caben en una pantalla baja. Se le pone
   * techo y se desplaza DENTRO: cortarse escondería opciones sin decirlo, que
   * es la única salida que no vale. */
  max-height: calc(100vh - 5rem); overflow-y: auto; overscroll-behavior: contain;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius-md); box-shadow: var(--shadow-2);
  padding: var(--space-3);
}
[data-contrast="high"] .menu-panel { border-width: 2px; }

.menu-panel__grupo { list-style: none; margin: 0; padding: 0; }
/* La nota de cuándo se recalculó el orden por uso (§8.112): un rótulo pequeño
   al pie de su grupo, no una fila más del menú. */
.menu-orden-nota { margin: var(--space-1) 0 var(--space-2); padding-inline: var(--space-3); }
.menu-panel__grupo > li > a {
  display: flex; align-items: center; gap: var(--space-3);
  /* ÁREA DE TOQUE: el mínimo táctil, no el alto del texto. */
  min-height: var(--tactil-minimo); padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm); color: var(--text); text-decoration: none;
}
.menu-panel__grupo > li > a:focus-visible { outline: var(--focus-width) solid var(--focus-color); outline-offset: 2px; }
.menu-panel__grupo > li > a[aria-current="page"] { font-weight: var(--fw-semibold); background: var(--surface-2); }

/* La cabecera necesita ser el ancla del panel, que se posiciona contra ella. */
.topbar { position: relative; }

/* ─── EL TIPO DE CAMBIO EN LA BARRA DE MOVIMIENTOS ──────────────────────────
 *
 * Va al lado de «Registrar» y **no puede empujarlo**: `flex-wrap` en la barra
 * deja que baje de línea antes que partirla, que es lo que pasaría a escala
 * máxima con seis palabras y dos números.
 */
/* ─── DOS LÍNEAS, PORQUE EL PROBLEMA ERA EL ANCHO ──────────────────────────
   En una sola fila medía 419 px tras recortarle el texto — **3,8 veces el
   botón «Registrar»**, que mide 111—. Apilado, el ancho pasa a ser el de la
   línea más larga en vez de la suma de las dos, y no cuesta información: la
   fecha sigue arriba y los dos números con su nombre abajo.
   `align-items: flex-end` para que el bloque remate a la derecha junto al
   botón, en vez de dejar un diente. */
/* VUELVE A UNA LÍNEA. Se partió en dos porque el texto no cabía; retirado el
   texto —«T.C.», «compra», «venta»— cabe de sobra y una barra con un control
   de una línea y un dato de dos se lee torcida. */
.tc-breve {
  display: inline-flex; flex-direction: row; align-items: baseline;
  gap: var(--space-2); line-height: 1.25;
  font-size: var(--fs-caption); color: var(--text-secondary);
  white-space: nowrap;
}
/* ─── EL AVISO VA DEBAJO DE LAS CIFRAS, CENTRADO · Kike, 16/08/2026 ────────
 *
 * Kike, con captura: *«el texto explicativo del tipo de cambio empuja el botón
 * del módulo Movimientos a la segunda línea»*. Medido en su ancho:
 *
 *     .tc-breve entero        398 px
 *       las dos cifras        183
 *       «15/08 · pendiente de SUNAT»  215   ← más de la mitad
 *     «Registrar»             111 px, y caía a la segunda línea
 *
 * Kike dijo cómo lo quiere: *«esa línea informativa, de ser necesario que
 * aparezca, que aparezca centrada debajo de los montos»*. Se hace envolviendo
 * dentro del propio componente, así que **la barra recupera 183 px de ancho**
 * sin quitar el dato de la vista.
 *
 * **Y no toca `D-100`**: por debajo de sus cortes el aviso sigue yéndose a
 * `sr-only` —que va con `position: absolute`, así que no participa de esta
 * envoltura— y el lector lo sigue oyendo. *Una frase que desaparece de la
 * vista tiene que seguir estando para el lector.* */
.tc-breve { flex-direction: column; align-items: center; }
.tc-breve__cifras { display: inline-flex; align-items: baseline;
  gap: var(--space-2); white-space: nowrap; }
.tc-breve__aviso { text-align: center; }

.tc-par { display: inline-flex; align-items: baseline; gap: var(--space-micro); }
.tc-breve .num { color: var(--text); font-weight: var(--fw-semibold); }
/* La letra, en el tono secundario: es la etiqueta del número, no el número. */
.tc-letra { font-size: var(--fs-label); color: var(--text-secondary); }
.tc-breve__sep { opacity: .6; }
/* ─── LA FLECHA ────────────────────────────────────────────────────────────
   §8.5: el color NO es la única señal. La forma ya distingue —el triángulo
   apunta arriba o abajo— y el nombre completo con «subió»/«bajó» y la
   diferencia va en el texto accesible de al lado. El tono solo refuerza.
   Va en cuerpo pequeño para no competir con la cifra, que es el dato. */
.tc-flecha { font-size: var(--fs-label); line-height: 1; }
.tc-flecha--sube { color: var(--income-ink); }
.tc-flecha--baja { color: var(--expense-ink); }

/* NO ES SOLO EL COLOR: al lado va la palabra «no es de hoy». El tono apoya. */
.tc-breve--viejo .tc-breve__aviso { color: var(--expense-ink); font-weight: var(--fw-semibold); }
/* ─── ESPERAR NO ES FALLAR ────────────────────────────────────────────────
 *
 * Kike, 12 de agosto de 2026. El rojo salía 9 h 17 min al día de mediana —desde
 * medianoche hasta que el cron trae el del día— y el día entero los sábados,
 * domingos y feriados. Un aviso que se enciende siempre deja de leerse, y
 * cuando de verdad falle nadie lo va a mirar.
 *
 * Así que «pendiente de SUNAT» y «sin publicar hoy» van en el tono del texto
 * secundario: se leen, no gritan. El rojo se queda para lo que sí es un fallo.
 * La diferencia de verdad la lleva LA PALABRA, no el color (§8.5). */
.tc-breve--espera .tc-breve__aviso { color: var(--text-secondary); }
.tc-breve--sin { font-style: italic; }

/* ═══ LA CIFRA SOLA EN ESTRECHO, LA FRASE A PARTIR DE CIERTO ANCHO · `D-100` ══
 *
 * **Decidido por Kike**, tercera de las tres formas propuestas: es la única que
 * no pierde información en ningún ancho.
 *
 * ─── EL PROBLEMA, MEDIDO ───────────────────────────────────────────────────
 *
 * `/movimientos` desbordaba en **16 de 16 combinaciones a 380 px**, y ya en
 * escala normal. Es una de las dos pantallas que Kike abre desde el teléfono.
 * Dentro de `.tc-breve` todo va en una línea, y lo que no cabe es la frase:
 *
 *     escala        raíz      las dos cifras     la frase     total
 *     normal        17 px       158 px            176 px      358
 *     grande        21,25       193               213         438
 *     muy_grande    25,5        231               256         525
 *     máximo        29,75       270               300         612
 *
 * **Las cifras caben siempre; la frase no cabe nunca** en 365 px de hueco.
 *
 * ─── UN CORTE POR ESCALA, Y NO UNO EN `em` · PREMISA FALSA, MEDIDA ─────────
 *
 * La primera versión puso **un solo corte en `em`**, dando por hecho que así
 * escalaría con el texto. **Es falso, y se midió:** en una *consulta de medio*
 * `em` se resuelve contra el tamaño inicial del navegador —16 px—, **no contra
 * la raíz**, así que `data-escala` no la mueve. `33.999em` son 544 px siempre.
 *
 * El agujero que dejaba era real: a **545 px con la letra al máximo la frase
 * volvía a entrar y desbordaba 111 px**, y a 600 px, 56.
 *
 * La otra vía que sí escala sería una consulta de contenedor, y eso pide
 * declarar un contenedor nuevo: `container-type: inline-size` hace que el
 * elemento **deje de dimensionarse por su contenido** y ya rompió tres
 * pantallas de uso diario el 14 de agosto. No por aquí.
 *
 * Así que **un umbral por escala, cada uno medido** buscando el ancho donde
 * deja de desbordar con la frase puesta:
 *
 *     escala        raíz        cabe a partir de
 *     normal        17 px         402 px    →  se corta por debajo de 26em
 *     grande        21,25         481       →  30em
 *     muy_grande    25,5          574       →  36em
 *     máximo        29,75         660       →  41,5em
 *
 * Justo por debajo del corte se ve **la cifra sola**; justo por encima entra la
 * frase entera. Comprobado en los cuatro bordes.
 *
 * ─── Y LA FRASE NO SE PIERDE: SE OCULTA A LA VISTA, NO AL LECTOR ───────────
 *
 * `sr-only` y no `display: none`. Quien usa lector de pantalla sigue oyendo
 * «15/08 · sin publicar hoy», que es el dato que dice si el tipo es de hoy. *Lo
 * de largo libre cede; una cifra o una fecha, nunca* — y aquí lo que cede es la
 * frase, no el número. */
@media (max-width: 25.999em) {
  .tc-breve__aviso { position: absolute; width: 1px; height: 1px; padding: 0;
    margin: -1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }
}
/* 30,124em = 481,98 px. **No era redondeo**: medido, a 480 px la frase entraba
   y desbordaba 2 px, y a 481 uno. Cabe a partir de 482, así que el corte va
   ahí y no en los 480 que daban los 30em redondos. */
@media (max-width: 30.124em) {
  :root[data-escala="grande"] .tc-breve__aviso,
  :root[data-escala="muy_grande"] .tc-breve__aviso,
  :root[data-escala="maximo"] .tc-breve__aviso {
    position: absolute; width: 1px; height: 1px; padding: 0;
    margin: -1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }
}
@media (max-width: 35.999em) {
  :root[data-escala="muy_grande"] .tc-breve__aviso,
  :root[data-escala="maximo"] .tc-breve__aviso {
    position: absolute; width: 1px; height: 1px; padding: 0;
    margin: -1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }
}
@media (max-width: 41.499em) {
  :root[data-escala="maximo"] .tc-breve__aviso {
    position: absolute; width: 1px; height: 1px; padding: 0;
    margin: -1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }
}
[data-contrast="high"] .tc-breve--viejo .tc-breve__aviso { text-decoration: underline; }

/* ─── LOS MONTOS DE LOS WIDGETS, EN UN SOLO PESO ───────────────────────────
   Kike, 6 de agosto de 2026: montos en negrita y uniformes, «salvo los grandes
   del primer widget, que se quedan como están». Esas dos excepciones son
   `.neto-cifra` y `.saldo-cifra`: tienen tamaño y peso propios porque son EL
   dato de su tarjeta, no uno más de una lista.
   Movimientos y Facturas son la referencia aprobada; esto iguala al resto. */
/* Las dos grandes del primer widget se EXCLUYEN, no se reasignan: `.neto-cifra`
   declara su propio `--fw-semibold` en su bloque, y ponerle `inherit` aquí
   abajo lo habría pisado por orden de fichero — cambiándola justo cuando lo
   pedido era dejarla igual. Comprobado antes de dar por bueno el cambio. */
.widget-magnitud .amount:not(.neto-cifra):not(.saldo-cifra):not(.barra-cifra),
.widget-comparativo .amount:not(.neto-cifra):not(.saldo-cifra),
.widget-posicion .amount:not(.neto-cifra):not(.saldo-cifra),
/* `.barra-cifra` sale de aquí: su peso lo decide el bloque del final, que es
   donde vive el juego de tonos y pesos de los dos gráficos. Dos sitios
   decidiendo el mismo peso es cómo se llegó a «todo en negrita». */
.comparativo-cifra, .widget-top__monto {
  font-weight: var(--fw-semibold); font-variant-numeric: tabular-nums;
}

/* ─── UNA CASILLA CON SU TEXTO AL LADO ─────────────────────────────────────
   No había clase compartida: la única casilla con etiqueta era la de
   «mantener la sesión abierta», con estilo propio del acceso. El camino del
   extranjero en el alta de proveedor necesitaba otra, y copiar aquel estilo
   habría sido la segunda copia de lo mismo.
   `align-items: center` y no `baseline`: con el texto en dos líneas —que
   pasa a escala máxima— la casilla se quedaba pegada a la primera. */
/* `min-block-size` táctil: la etiqueta es el objetivo real del radio —lo
   envuelve— y medía **28 px** en escala normal, 35 en grande y 42 en muy
   grande, por debajo de los 44 de §8.5. El radio suelto mide 19,5. */
.casilla {
  display: flex; align-items: center; gap: var(--space-2); cursor: pointer;
  min-block-size: var(--tactil-minimo);
}
.casilla input { inline-size: 1.15rem; block-size: 1.15rem; flex: 0 0 auto; }

/* El botón de cerrar de un diálogo medía 32,8 px de ANCHO en escala normal y
   40,5 en grande: cumplía en alto y no en el otro eje, que para un dedo es el
   mismo problema. `--tactil-minimo` en los dos lados. */

/* La raya corta que separa «volver» de «agregar»: uno navega, el otro crea.
   Más baja que la del encabezado porque separa dos botones, no dos zonas. */
.separador-cabecera--corto { block-size: 1.5em; align-self: center; }

/* La tarjeta sigue siendo un enlace entero; el lápiz se superpone en su
   esquina, fuera del enlace —un botón dentro de un enlace es marcado
   inválido y un objetivo que a veces navega y a veces edita—. */
.tarjeta-modulo-caja { position: relative; display: flex; }
.tarjeta-modulo-caja > .tarjeta-modulo { flex: 1 1 auto; }
.js-rotular-tarjeta { position: absolute; inset-block-start: var(--space-2); inset-inline-end: var(--space-2); }

/* ---------- Comisiones ----------
 *
 * EL CONTROL DE PAGO NO PUEDE ENSANCHAR LA COLUMNA. Con el desplegable a todo
 * lo ancho que le da `.input`, la celda de estado empujaba a las demás y las
 * cabeceras «Comisión» y «Estado» se tocaban — el mismo defecto que ya tuvimos
 * con «Se aplicaResto» en el reparto de cobros.
 *
 * Se acota el desplegable y el control baja a su propia línea: el estado se lee
 * de un vistazo arriba, y lo que se hace con él va debajo. */
.tabla .js-comision-mov { inline-size: min(24ch, 100%); }
.tabla .js-comision-pagar { white-space: nowrap; }
/* Y la fila del control se separa del badge, que si no se leen como uno. */
.tabla td .row.gap-2 { margin-block-start: var(--space-2); flex-wrap: wrap; }

/* ─── «CON QUÉ GASTO» PIDE MÁS ANCHO QUE UN «¿SEGURO?» ────────────────────
 *
 * `.dialogo` vale `min(34rem, 92vw)` y ese tope nació para un diálogo de
 * confirmación: un título, dos frases y dos botones. Éste no es eso. Dentro
 * lleva una lista de movimientos donde cada línea es
 * `fecha · importe · concepto`, y el concepto lo escribe una persona.
 *
 * Medido el 14 de agosto de 2026 en un `<iframe>` de 1440 px con las hojas
 * reales y los 30 gastos que devuelve el servidor:
 *
 *   · el diálogo medía **578 px** y dejaba **862 px de pantalla sin usar**;
 *   · al texto de cada opción le quedaban **425,5 px**;
 *   · y **10 de las 30 opciones envolvían** a dos o tres líneas — la más
 *     larga pedía 877,8 px de una tirada.
 *
 * Una lista donde un tercio de las filas ocupa el doble de alto que las demás
 * no se recorre con la vista: se lee una a una.
 *
 * El tope se sube a `52rem`, que a escala normal son **884 px** y dejan
 * **731,5 px** de texto. Va por `id` para no mover ningún otro diálogo, y con
 * `min()` para que la ventana siga mandando cuando es más estrecha que el
 * tope: en 375 px el diálogo mide 345 —el 92 %— exactamente igual que antes.
 *
 * En `rem` y no en `px` A PROPÓSITO: con la letra al 175 % el mismo texto pide
 * más ancho, así que el tope tiene que crecer con ella. La caja crece primero;
 * aquí no se encoge ninguna cifra para que quepa. */
#dlg-com-gasto { max-width: min(52rem, 92vw); }

/* ─── Y EL ALTO NO ESTABA MEDIDO ──────────────────────────────────────────
 *
 * El ancho de arriba se midió a fondo el 14 de agosto de 2026 y **el alto no se
 * midió ni una vez**. Kike lo vio a la primera: al desplegar la lista de gastos
 * no se veía NINGUNA opción sin desplazar. Medido después, con el combo
 * ABIERTO, en las doce combinaciones: **0 de 31 opciones visibles en las doce**.
 *
 * ─── Y EL TOPE QUE MANDABA NO ERA `max-block-size` ───────────────────────
 *
 * `.dialogo` ya traía `max-block-size: 90dvh` —810 px en una ventana de 900—
 * pero **no traía alto**, así que el diálogo se dimensionaba por su contenido
 * CERRADO: pista + botón. Medido a 1440x900 en escala normal:
 *
 *     diálogo .................  361,7 px   (de 810 px permitidos: sobraban 448)
 *     cuerpo, alto visible ....  151 px     y `scrollHeight` 530
 *     panel del desplegable ...  392 px, del que se veían **60,1**
 *
 * El panel es `position: absolute` y cuelga del campo, que vive dentro de
 * `.dlg-cuerpo`, que es `overflow: auto`. **Lo que recortaba era el cuerpo**,
 * no el diálogo ni el panel: el panel no cuenta para el alto automático del
 * diálogo —los absolutos no lo hacen— así que la caja se dimensionaba sin él y
 * luego lo cortaba. Subir `max-block-size` no habría cambiado nada.
 *
 * Por eso aquí va **alto**, no un tope de alto. Con alto propio el cuerpo
 * —`flex: 1 1 auto`— recibe una caja de verdad y el panel cabe dentro.
 *
 * `min(50rem, 90dvh)`: la misma forma que el ancho. `rem` para que crezca con
 * la letra del usuario, y `dvh` —no `vh`— porque en el móvil la barra del
 * navegador se recoge y `vh` mide una ventana que no está; es la razón que ya
 * estaba escrita en `.dialogo`.
 *
 * 50rem son 850 px a escala normal, y en una ventana de 900 manda el 90 %:
 * **810 px, +124 %** sobre los 361,7 de antes. El 30 % que pidió Kike daba
 * 470 px, y medido con ese valor se veían **dos** filas de las que sólo una era
 * un gasto: el 30 % era su suelo, no el objetivo. En una pantalla más alta
 * —1080 px— manda el `50rem` y el diálogo mide 850.
 *
 * ─── EL HUECO CUANDO EL DESPLEGABLE ESTÁ CERRADO ES A PROPÓSITO ──────────
 *
 * Con la lista cerrada el diálogo mide igual y queda sitio libre bajo el botón.
 * Es el sitio de la lista, y ésta es una ventana que existe **para elegir de
 * una lista**: que el diálogo cambiara de tamaño al desplegar movería el botón
 * bajo el cursor de quien lo acaba de pulsar. Es el mismo criterio que
 * `.dialogo--formulario` ya tiene escrito para su alto fijo. */
/* ─── Y SUBE A 56rem · Kike, 1 de septiembre de 2026 ──────────────────────
 *
 * Con `min(50rem, 90dvh)` la lista enseñaba **9 opciones de 81** en escala
 * normal, y el panel ya se quedaba a **8 px de los botones**: no le sobraba
 * hueco, le sobraba al diálogo — **336 px de ventana sin usar**.
 *
 * Kike eligió **11 opciones** con la tabla de las cuatro escalas delante, y
 * descartó las 13 de `min(64rem, 92dvh)`.
 *
 * ─── Y QUE NADIE QUITE `max-block-size: none` DE AQUÍ ABAJO ──────────────
 *
 * Se ha malinterpretado una vez. **Esa regla no es lo que limita la lista: es
 * lo que la deja crecer.** Sin ella la corta el `18rem` del componente —306 px,
 * cuatro opciones—. Quitarla creyendo que estorba deja la lista MÁS corta. */
#dlg-com-gasto { block-size: min(56rem, 92dvh); }
/* Y el techo de la ventana, también el suyo. `.dialogo` trae
   `max-block-size: 90dvh` para todos, y con él el `92dvh` de Kike no llegaba
   a aplicarse nunca: medido, en escala «muy grande» el diálogo se quedaba en
   1.164,6 px —el 90 %— y no en los 1.190,5 del 92 %. Se sube **solo aquí**:
   cambiarlo en `.dialogo` movería los diez diálogos de la casa por una
   decisión que era de éste. */
#dlg-com-gasto { max-block-size: 92dvh; }
/* Y la lista puede usar el sitio que ahora hay: sin esto la corta `18rem` del
   componente —306 px, cuatro opciones— aunque el diálogo mida 748. El techo lo
   sigue poniendo `acomodar()`, que mide el hueco real y nunca deja que el panel
   se salga; ver la nota vertical de `combo.js`. Va por `id`: los otros cuatro
   combos de la aplicación conservan sus 18rem. */
#dlg-com-gasto .combo-lista { max-block-size: none; }
/* ─── Y EL HUECO DE LA BARRA SE RESERVA SIEMPRE ──────────────────────────
 *
 * Sin esto, el panel —que es absoluto y cuenta para el `scrollHeight` del
 * cuerpo— hacía aparecer la barra del cuerpo mientras se calculaba su sitio;
 * el contenido perdía ~15 px de ancho, la pista envolvía una línea más y el
 * campo bajaba 45,8 px entre el cálculo y el resultado. Reservar el hueco
 * quita la causa: el ancho del cuerpo no depende de si hay barra o no.
 *
 * Va por `id` porque es aquí donde se midió; los demás diálogos no cambian. */
#dlg-com-gasto .dlg-cuerpo { scrollbar-gutter: stable; }
/* ─── EN MÓVIL, LA VENTANA ES LA PANTALLA ────────────────────────────────
 *
 * A 375x667 el tope del 90 % dejaba el diálogo en 600,3 px, y de ésos la
 * cabecera y el pie se llevaban 472 con la letra al máximo —el título envuelve
 * en cinco líneas y los dos botones en tres—: al cuerpo le quedaban **60 px**.
 * No hay reparto que salve eso; lo único que queda es no regalar los 66,7 px
 * del margen ni los 88 del relleno.
 *
 * Es exactamente lo que `.dlg-comprobante` ya hace por debajo de 640 px, y por
 * la misma razón: en una pantalla pequeña, una ventana flotante con marco es
 * una miniatura de lo que se venía a leer. La caja crece; ningún texto encoge.
 * `--space-3` en vez de `--space-5` es RELLENO, no tamaño de letra. */
@media (max-width: 640px) {
  /* `100%` y no `100vw`/`100dvh`. Las unidades de ventana INCLUYEN la barra de
     desplazamiento, y esta pantalla tiene una a 375 px con la letra al máximo
     —la barra de filtros se sale 41 px, defecto anterior y ajeno a este
     diálogo—. Con `100vw` el diálogo medía 375 en una ventana útil de 360 y
     con `100dvh` se salía **7,5 px por arriba**. Un porcentaje se resuelve
     contra el bloque contenedor del modal, que es el sitio que de verdad hay. */
  #dlg-com-gasto {
    inline-size: 100%; max-inline-size: 100%;
    block-size: 100%; max-block-size: 100%;
    border-radius: 0; padding: var(--space-3);
  }
}

/* ---------- UN IMPORTE NO SE PARTE ----------
 *
 * Medido el 7 de agosto de 2026 contando los rectángulos del nodo de texto —que
 * es un rectángulo por línea—: a escala máxima, «S/ 1.409,05» y «S/ 281,81»
 * salían en **dos líneas**, el símbolo arriba y el número abajo. Un importe
 * roto en dos se lee dos veces.
 *
 * La regla la fijó Kike: **si no cabe, el problema es el ancho de la caja y no
 * el número.** Así que la celda no envuelve, y cuando la tabla ya no entra, se
 * desplaza a lo ancho — que es lo que estas tablas ya hacen con muchas columnas.
 *
 * Y va atado a que NO QUEPA, no a la escala: los widgets enseñaron que
 * condicionar por `data-escala` es la condición equivocada — se pisaba antes en
 * una pantalla y sobraba en otra. `nowrap` no pregunta por la escala. */
.tabla .num,
.neto-cifra { white-space: nowrap; }

/* ---------- LA TERCERA TARJETA NO SE QUEDA A MEDIA FILA ----------
 *
 * Medido el 7 de agosto de 2026 en la ficha de la E001-2339: a `muy_grande` y
 * `maximo` las tres tarjetas del margen caen 2 + 1 y la última deja **805 px
 * vacíos** a su derecha — la «media fila vacía» que señaló Kike.
 *
 * Una rejilla `auto-fit` no sabe estirar la última fila: reparte columnas
 * iguales y lo que sobra, sobra. Flex sí — los elementos de la fila final se
 * reparten el ancho disponible, así que tres caben en fila cuando caben, y
 * cuando no, la que baja **ocupa lo suyo entera**.
 *
 * Se acota a este bloque a propósito: Movimientos usa `.tercios--centro-ancho`,
 * que reparte 26/48/26 con `grid-template-columns`, y esa pantalla está
 * aprobada. Cambiar la rejilla global la habría roto sin que nadie lo pidiera.
 *
 * El `23rem` es el mismo número medido: lo que pide «S/ 4.399,91» con su
 * relleno. Atado a si cabe, no a la escala. */
#costos-factura .tercios--tres {
  display: flex; flex-wrap: wrap;
}
#costos-factura .tercios--tres > .card { flex: 1 1 23rem; min-inline-size: 0; }

/* ═══════════ LA CIFRA SE MIDE CONTRA SU TARJETA, NO CONTRA LA VENTANA ═════
 *
 * `--fs-display` es `clamp(2.5rem, 6vw, 3.75rem)`: pregunta por la VENTANA. Y
 * la tarjeta del margen no mide lo que mide la ventana — mide lo que le deja el
 * diálogo después de repartirse entre tres. De ahí salían los dos defectos que
 * el Tester vio el 7 de agosto de 2026, y ninguno se deducía del CSS:
 *
 *   1 · EN EL TELÉFONO. Con la ventana a 360 px el `clamp` se queda en su suelo
 *       —2,5 rem, 42,5 px— y la cifra pide 232 px dentro de una caja de 181:
 *       «S/ 4.399,91» **salía de su tarjeta y quedaba cortada por el borde del
 *       diálogo**. Visto en captura, con la ventana de verdad a 360 y no con el
 *       diálogo encogido: encoger el diálogo deja el `6vw` a tope y no reproduce
 *       el caso —el instrumento contaba otra cosa—.
 *
 *   2 · EN EL ESCRITORIO, con un importe de cinco dígitos. A 63,75 px, doce
 *       caracteres piden 386 px en una caja de 363. Hay **153 facturas de
 *       3.176 por encima de S/ 10.000**.
 *
 * `cqi` es el 1 % del ancho de la tarjeta, así que la división da el cuerpo más
 * grande que cabe: `100cqi / (caracteres × 0,52)`. El 0,52 es el ancho medio
 * medido de un carácter de esta cifra en `em` —0,493 con once caracteres, 0,514
 * con dieciséis— con un pelo de margen.
 *
 * Y `min(--fs-display, …)` conserva lo aprobado: con once caracteres en el
 * diálogo de escritorio el cociente sale por encima de `--fs-display`, así que
 * manda `--fs-display` y **la cifra mide exactamente lo que medía en las cuatro
 * escalas**. Solo baja cuando de verdad no cabe.
 *
 * `container-type` va en la TARJETA, que es la caja contra la que hay que
 * medir; y acotado a `#costos-factura` porque las otras `.neto-cifra` —la del
 * periodo en Movimientos— viven en otro reparto y `cqi` sin contenedor se
 * resolvería contra la ventana, que es justo el error que se está quitando. */
/* ─── Y DEJA DE ESTAR ACOTADO A UNA PANTALLA ──────────────────────────────
 *
 * Estaba encerrado en `#costos-factura` por una razón buena —`cqi` sin
 * contenedor se resuelve contra la ventana, que es el error que se quitaba— y
 * la conclusión fue la equivocada: en vez de acotar la regla había que poner el
 * contenedor donde estaba la caja.
 *
 * Medido el 10 de agosto de 2026: **once pantallas tienen widgets y solo una
 * llevaba esto**. Kike lo vio como «el primer widget de Deudas tiene el importe
 * más pequeño que los demás» y lo mismo en Gastos fijos. La caja es siempre la
 * misma —`.card.stat`—, así que el contenedor va ahí y la regla vale para
 * todas. Las `.neto-cifra` que viven fuera de una tarjeta —la del periodo en
 * Movimientos— no entran, que era lo que había que proteger.
 *
 * Sin `--cifra-caracteres` se usan once, que es lo aprobado: una pantalla que
 * no lo declare se comporta como hasta hoy, no peor. */
.card.stat {
  container-type: inline-size;
  /* ─── PRIMERO SE ENSANCHA LA TARJETA ──────────────────────────────────────
   *
   * Kike, 11 de agosto de 2026. Encoger la cifra era la única respuesta que
   * había, y él la nombró como el problema: con baja visión, el número se achica
   * justo cuando tiene más dígitos y más falta hace leerlo.
   *
   * El suelo sale del propio dato —cuántos caracteres mide la cifra— por el
   * ancho medio de un dígito de esta tipografía, más el relleno de la tarjeta.
   * Como las rejillas de widgets usan `auto-fit` con `minmax`, subir el suelo
   * hace que **caigan a menos por fila en vez de estrujarse**, que es
   * exactamente lo que pidió. */
  min-inline-size: min(100%, calc(var(--cifra-caracteres, 11) * 0.55em
                                  + var(--space-5) * 2));
}
.card.stat .neto-cifra {
  /* Y el encogido queda como ÚLTIMO recurso, no como primera respuesta: solo
     puede morder cuando la caja ya no puede crecer más —un teléfono estrecho con
     la escala de texto al máximo—, donde la alternativa es que el número se
     salga de la pantalla. */
  /* ─── EL SUELO ES UN TOKEN QUE CADA WIDGET DECLARA, NO UNA REGLA NUEVA ────
   *
   * Kike, 12 de agosto de 2026. El 11 escribí una regla aparte para «los cinco
   * widgets con cifra grande» y rompió dos: el de saldos —que no es una cifra
   * suelta sino una lista de pares nombre/importe— partía los nombres de cuenta
   * letra por letra, y el del neto salió MÁS PEQUEÑO que antes, porque `11cqi`
   * sustituía a `100cqi / (caracteres × 0,52)` y con once caracteres eso es
   * 11 frente a 17,5 — un 63 % del tamaño que tenía.
   *
   * La clasificación fue el error: los agrupé por tener «cifra grande», que es
   * la FORMA, cuando lo que comparten o no es el COMPORTAMIENTO.
   *
   * Así que el suelo no se generaliza: se ofrece. `--cifra-suelo` vale `0px`
   * por omisión —los otros cuatro quedan bit a bit como estaban, y eso se puede
   * comprobar— y solo lo declara el widget que lo necesita. Es el mismo
   * mecanismo que `--cifra-caracteres`: un token del widget con un valor por
   * omisión que no cambia a nadie.
   *
   * Y es un MÍNIMO, no un tamaño: `max()` por fuera del `min()`, así que solo
   * aparece cuando el cociente ya bajó por debajo de él. Si con el suelo tampoco
   * cabe, desborda — y desbordar se ve, que es lo que se puede arreglar. */
  /* ─── ERA LA QUE ENCOGÍA · MEDIDO EL 22/08/2026 ────────────────────────
   *
   * `100cqi / caracteres` daba **53,48 px** en una tarjeta y **63,20** en la de
   * al lado, en la misma pantalla: la cifra se encogía para caber, que es
   * justo lo que la regla prohíbe. *Una cifra nunca se encoge para que quepa:
   * primero crece la caja.*
   *
   * Ahora el token, plano. Si no cabe, la caja tiene que crecer — y eso se ve
   * y se puede arreglar, mientras que encogerse no se ve. */
  font-size: var(--fs-cifra-widget);
}

/* ═══════════ EL PIE DE TOTALES, FUERA DE LA TABLA ═══════════════════════════
   Kike, 7 de agosto de 2026: el total del listado de compras parecía una fila
   más. Vivía en un `<tfoot>` —mismo alto, mismas columnas, misma tipografía— y
   una suma no es un registro: no se abre, no se edita, no se archiva.

   Fuera de la tabla y con la cifra en grande, que es el número por el que se
   entra a la pantalla cuando se viene a cuadrar el mes. Y el mismo bloque en
   Compras y en el Registro de ventas, porque §8.10 pide una sola forma.

   `auto-fit` con suelo acotado al 100 %: en el teléfono caen en columna sin que
   la rejilla mida más que su caja — la pareja que ya costó una barra horizontal
   en `.ficha-datos`. */
.pie-totales {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(14rem, 100%), 1fr));
  gap: var(--space-3) var(--space-5);
  margin-block-start: var(--space-4);
  padding-block-start: var(--space-4);
  border-block-start: 1px solid var(--border-strong);
}
.pie-totales__grupo { display: flex; flex-direction: column; gap: var(--space-micro); min-inline-size: 0; }
.pie-totales__rotulo {
  font-size: var(--fs-caption); color: var(--text-secondary);
  text-transform: uppercase; letter-spacing: .04em;
}
/* La cifra no se parte: es un importe, y si no cabe el problema es la caja. */
.pie-totales__cifra {
  font-family: var(--font-display); font-size: var(--fs-cifra-widget);
  font-variant-numeric: tabular-nums; white-space: nowrap; overflow-wrap: normal;
}
/* ─── EL TOTAL SE DISTINGUE POR COLOR, Y EL TAMAÑO SE FUE HACE TIEMPO ────
 *
 * Este comentario decía «se distingue por tamaño y no por color», y medido
 * el 13 de agosto de 2026 **las cinco cifras median exactamente lo mismo**:
 * la declaración de más abajo las pone a todas en el mismo cuerpo y ésta
 * repetía el valor, así que lo único que distinguía al total era el color —
 * justo lo que el texto decía que no. Un comentario que sobrevivió al cambio
 * que lo desmiente.
 *
 * Se quita el `font-size` repetido, que ya no dice nada y **habría dejado al
 * total en 29,75 mientras sus partes suben a 2,375rem**: la jerarquía al
 * revés por una línea que parecía inofensiva. El color se queda.
 *
 * Y §8.5 sigue cumpliéndose sin el tamaño: el total lleva su rótulo escrito
 * —«Total», «Total de 17 facturas»— así que quien no distinga los dos tonos
 * lee exactamente la misma información. */
.pie-totales__grupo--principal .pie-totales__cifra { color: var(--text); }
[data-contrast="high"] .pie-totales { border-block-start-width: 2px; }

/* ═══════════ COMISIONES · LO QUE KIKE VIO EN PANTALLA ═══════════════════════
   Ocho ajustes de su revisión del 8 de agosto de 2026. Los dos primeros eran
   cosas que di por hechas mirando el marcado, y el marcado estaba: lo que no
   pasaba era el montaje del combo. Se arreglan en `combo.js`; aquí va el resto.

   ─── EL LISTADO OCUPA SU TARJETA ─────────────────────────────────────────
   «Todo tirado a la izquierda». La tabla medía 853 px de los 904 de su tarjeta
   porque el ancho lo fijaba el contenido. Una tabla de listado ocupa lo suyo:
   si sobra sitio, lo reparte entre las columnas que lo aprovechan.

   EL `table-layout` YA NO SE DECLARA AQUÍ. Lo declara `.tabla-comisiones` más
   abajo, y tenía que ser así: este selector lleva un `id`, así que ganaba por
   especificidad y dejaba muerta la regla de la otra. Una propiedad escrita en
   dos sitios con distinto peso es una que no se puede cambiar donde parece. */
#comisiones .tabla { width: 100%; }

/* ─── SE FUERON CON EL LISTADO DE DOS LÍNEAS ───────────────────────────────
   `.col-venta*`, `.col-comision .caption` y `.comision-pagar` describían el
   marcado anterior de esta pantalla —la celda con número, cliente y descripción
   apilados, y la fila con combo y botón—. Ese marcado ya no existe: el listado
   es de una fila con siete columnas y el combo vive en su diálogo. Se borran
   con él; una regla sin marcado no se puede comprobar y estorba al leer. */

/* ─── LOS MONTOS, IGUALES Y CON PESO, EN TODAS LAS TARJETAS ────────────────
   Kike: los tamaños tienen que ser los mismos entre tarjetas y el monto pesar
   más que el texto que lo rodea. `--fs-display` lleva un término `vw`, así que
   la misma clase daba cifras de distinto tamaño según lo ancha que fuera su
   caja — que es justo lo que él vio como «sin concordancia». Se fija el cuerpo
   y se deja que la caja se encargue de que quepa. */
/* ─── LAS CINCO CIFRAS DEL PIE SUBEN A UN TAMAÑO INTERMEDIO ────────────────
 *
 * Kike, 13 de agosto de 2026, aprobado con la medida delante. Antes de
 * proponerlo hubo que deshacer dos cosas que yo mismo había anotado mal:
 *
 *   · **no eran «29,75 fijos»**. `--fs-xl` sí crece con la escala de texto —
 *     29,75 · 37,19 · 44,63 · 52,06—; lo que no hace es crecer con su contenido;
 *   · **los «63,75 de la fórmula» no son la respuesta de la fórmula**: son lo
 *     que sale cuando `100cqi` **no tiene contenedor** y se resuelve contra la
 *     ventana. Es la fórmula sin anclar.
 *
 * Y la fórmula NO se puede aplicar aquí: con `container-type: inline-size` en
 * `.pie-totales__grupo`, el grupo colapsa a **0 px** y la cifra con él, porque
 * ese recuadro lo dimensiona la propia cifra (§8.97). Así que sube un escalón
 * medido, no una fórmula.
 *
 * Dónde se rompe, probado en el navegador sobre las dos pantallas que las
 * llevan: a **3rem** aparecen 3 solapes reales; a 2,6rem todavía ninguno. Se
 * elige **2,375rem** —40,4 px a escala normal, 70,7 a máxima—, que deja un
 * escalón entero por debajo de donde se rompe: un valor que empata con el
 * límite no cumple el límite.
 *
 * Lo que cuesta: el pie pasa de **138 a 159 px** de alto de media.
 *
 * `.tercios--tres .stat-value` NO sube. Comparte esta declaración por
 * tipografía, pero vive dentro de una tarjeta con `container-type`, donde la
 * cifra ya se mide contra su caja. Subirle el cuerpo aquí sería cambiar cinco
 * widgets que nadie miró para arreglar dos pies de tabla. */
.tercios--tres .stat-value {
  /* Sin tamaño propio: la cifra principal de un widget es una sola en todo
     Flow, y esta regla tenía el suyo. */
  font-family: var(--font-display); font-size: var(--fs-cifra-widget);
  font-variant-numeric: tabular-nums; white-space: nowrap; overflow-wrap: normal;
  line-height: 1.1;
}
.pie-totales__cifra {
  /* Era `2.375rem` = 40,38 px, el tercer tamaño para lo mismo. */
  font-family: var(--font-display); font-size: var(--fs-cifra-widget);
  font-variant-numeric: tabular-nums; white-space: nowrap; overflow-wrap: normal;
  line-height: 1.1;
}
  /* ─── EL RÓTULO DE UN WIDGET NO VA EN MAYÚSCULAS · Kike, 28/08/2026 ────────
   *
   * Gastos fijos y Deudas los llevaban **en MAYÚSCULAS y apagados**; Money y
   * Billing no. *Lo que se llama igual se ve igual en todo Flow*, y aquí eran
   * dos gramáticas para lo mismo: el rótulo que nombra la cifra de un widget.
   *
   * Se igualan a los de Money y Billing. **El pie de una tabla se queda como
   * estaba**: no es el rótulo de un widget sino la etiqueta de una columna, y
   * tenerlos en la misma regla era lo que los confundía. */
  .tercios--tres .stat-label {
    font-size: var(--fs-caption); color: var(--texto-apagado); letter-spacing: 0;
  }
  .pie-totales__rotulo {
    font-size: var(--fs-caption); color: var(--text-secondary);
    text-transform: uppercase; letter-spacing: .04em;
  }

/* ─── EL TOTAL, ALINEADO CON LA COLUMNA QUE SUMA ───────────────────────────
   Sacar la suma de la tabla la separó de su columna, y así no se lee como suma
   de esa columna. Las cifras del pie van a la derecha, que es donde están las
   de arriba: alineadas al mismo borde, la lectura vertical vuelve a funcionar. */
#comisiones .pie-totales,
.card > .pie-totales { justify-items: end; text-align: right; }
.pie-totales__grupo { align-items: flex-end; }
@media (max-width: 640px) {
  /* En estrecho cae en columna y a la izquierda: alinear a la derecha una
     columna única deja el texto flotando lejos de su rótulo. */
  #comisiones .pie-totales,
  .card > .pie-totales { justify-items: stretch; text-align: left; }
  .pie-totales__grupo { align-items: flex-start; }
}

/* ═══════════ EL SALDO PENDIENTE, EN ROJO Y CON PALABRA ══════════════════════
   Kike, 8 de agosto de 2026: cuando el total difiere de lo cobrado, ese importe
   va en rojo — «es extraño que nos paguen en partes».

   Y §8.5 manda que el color no sea la única señal. Van TRES juntas y cada una
   basta por sí sola: la palabra «pendiente», el peso de la letra y el color.
   Quien no distinga el rojo lee lo mismo; quien mire de reojo ve el peso. */
.saldo-pendiente { color: var(--expense-ink, var(--expense)); font-weight: var(--fw-semibold); }
.saldo-pendiente__marca {
  display: inline-block; margin-inline-end: var(--space-2);
  font-size: var(--fs-caption); font-weight: var(--fw-medium);
  text-transform: uppercase; letter-spacing: .04em;
  border: 1px solid currentColor; border-radius: var(--radius-sm);
  padding: 0 var(--space-2);
}
/* En alto contraste el marco engorda, como el resto de las marcas. */
[data-contrast="high"] .saldo-pendiente__marca { border-width: 2px; }

/* ---------- Archivo de documentos ---------- */
/* ─── EL ASUNTO ES LO QUE SE LEE, ASÍ QUE SE LE DA SITIO ────────────────────
 *
 * Es la columna por la que alguien reconoce el papel. Sin un ancho mínimo se
 * quedaba con lo que sobraba y partía «ASPEFAM – Gestión Administrativa» en
 * renglones de dos palabras. */
.celda-asunto { min-width: 14rem; }

/* ─── Y EL FICHERO PENDIENTE SE APILA ───────────────────────────────────────
 *
 * «Pendiente de descarga» y el nombre del PDF son dos cosas distintas —el estado
 * y el papel— y en línea sumaban 304 px que empujaban la tabla 66 px fuera de su
 * envoltura. Medido el 8 de agosto de 2026. Apilados caben, y de paso se leen
 * como lo que son: un estado, y debajo de qué.
 *
 * `anywhere` porque estos nombres no tienen espacios donde partir:
 * «CLIENTE UARM EMPRESA OC 50335 - VIRTUAL INTERNET SOLUTIONS S.A.C..pdf». */
.celda-archivo > * { display: block; }
.celda-archivo .caption {
  overflow-wrap: anywhere; margin-block-start: var(--space-mini);
  /* A DOS LÍNEAS, porque 187 filas de cinco renglones son 43.000 px de página.
     El nombre completo sigue en el `title` y la búsqueda es del servidor, así
     que recortar lo que se ve no recorta lo que se encuentra. */
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}

/* ---------- La cabecera de filtros, compacta ---------- */
/* ─── DÓNDE ESTÁ EL ESPACIO, MEDIDO ANTES DE TOCAR ──────────────────────────
 *
 * Kike la quiere más compacta, y con una condición que decide el cambio:
 * **reducir el control no es reducir la letra**. Con su vista, unos filtros con
 * texto más pequeño serían un mal cambio disfrazado de mejora.
 *
 * Medido el 8 de agosto de 2026 sobre la cabecera de Facturas filtrada, 250 px:
 *
 *     quitando relleno y hueco, sin tocar la letra ......  13 px
 *     reduciendo la letra a 0,85rem .....................  63 px
 *     ACOTANDO EL ANCHO DE LOS CONTROLES ................  57 px
 *
 * **La premisa no se sostiene: el relleno solo gana 13 de 250.** El alto no lo
 * pone el relleno, lo ponen las LÍNEAS — y las líneas las pone el ancho: siete
 * controles pedían 1.053 px donde hay 941, así que la barra se partía en cinco.
 *
 * Acotarlos gana casi lo mismo que encoger el texto **sin encogerlo**: la letra
 * sigue en 18 px y el control en 45. Ese es el cambio bueno.
 *
 * `11rem` y no un número de píxeles: crece con la escala, así que a «grande» los
 * controles siguen siendo proporcionales en vez de estrangularse.
 *
 * El `max-width` va en el `.field` y no en el control, para que el combo
 * buscable mida su panel contra el hueco real y no contra un ancho que ya no
 * tiene (§8.53). */
.barra-filtros > .field { max-width: 11rem; }
.barra-filtros > .field .input,
.barra-filtros > .field .select,
.barra-filtros > .field .combo-boton { max-width: 100%; }

/* ─── Y SE DISTINGUE POR FORMA, NO POR TAMAÑO ───────────────────────────────
 *
 * «Que se vea como una zona distinta del contenido» se resuelve con un borde,
 * que no cuesta un píxel de alto ni obliga a nadie a forzar la vista. El token
 * es `--border-strong`, el mismo que marca dónde empieza una superficie desde
 * que subió a 3:1 — no uno nuevo.
 *
 * Solo por debajo: encerrar la barra en una caja propia dentro de la tarjeta
 * sería una segunda caja, y §8.10 dice una sola forma para lo mismo. */
.card-head.cabecera-con-filtros {
  /* Solo el borde. Le añadí `padding-block-end` y la medición lo desmintió:
     costaba 13 px de alto y no aportaba nada que el borde no diera ya —justo lo
     contrario de lo que se pedía—. El borde cuesta 1 px. */
  border-block-end: 1px solid var(--border-strong);
}

/* ─── EL ÁREA DE TOQUE NO SE TOCA ───────────────────────────────────────────
 *
 * Son 44 px y siguen siéndolo: aquí no se ha reducido ni el alto del control ni
 * su relleno, solo el ancho. No hace falta el pseudoelemento que rescató las
 * flechas de orden — se anota porque la próxima vez que alguien quiera bajar de
 * 44 px de alto, esa es la salida y no recortar el control. */

/* ---------- Correo y WhatsApp como icono en el listado ---------- */
/* El dibujo mide lo que un icono, y la zona pulsable la lleva el pseudoelemento
   hasta los 44 px — el mismo camino que las flechas de orden. Dos iconos en una
   celda es exactamente donde el área de toque se pierde si no se cuida: al lápiz
   de Movimientos le pasó, y se quedó en 19 px. */
.celda-icono {
  position: relative;
  display: inline-flex; align-items: center; justify-content: center;
  inline-size: 1.75rem; block-size: 1.75rem;
  color: var(--text-secondary);
}
.celda-icono::after {
  content: ''; position: absolute; inset: 50% auto auto 50%;
  inline-size: var(--tactil-minimo); block-size: var(--tactil-minimo);
  transform: translate(-50%, -50%);
}
.celda-icono:hover, .celda-icono:focus-visible { color: var(--brand); }

/* ═══════════ GASTOS FIJOS · LA CIFRA CLAVE Y LA COLUMNA DE ACCIÓN ═══════════
   Kike vio la pantalla desordenada, y el desorden tenía dos causas medibles:
   las tres cifras pesaban igual —«Debe», «Pagado» y «Falta», siendo «Falta» la
   única que dice si hay que hacer algo—, y lo que se HACE con el gasto vivía en
   una segunda fila a todo lo ancho, con la misma frase repetida seis veces.

   `.cifra-clave` da peso Y tamaño, no color: en alto contraste el matiz
   desaparece y el énfasis tiene que seguir ahí (§8.5).

   `.col-accion` separa con un filete lo que se hace de lo que el compromiso
   dice. Es un borde, no un fondo: los fondos se pierden en alto contraste. */
.cifra-clave {
  /* La cifra principal de un widget: el token, no `--fs-lg`. Daba **24,44 px**
     en Gastos fijos frente a los 63,75 de los demás — la diferencia más grande
     de las nueve que se midieron. */
  font-size: var(--fs-cifra-widget);
  font-weight: var(--fw-bold);
  font-variant-numeric: tabular-nums;
}
/* ─── EL FILETE SE QUEDA VERTICAL TAMBIÉN EN EL MÓVIL ───────────────────────
 *
 * Había un `@media (max-width: 720px)` que lo pasaba a horizontal «porque en
 * móvil la tabla se apila». **Esa tabla no se apila**: el apilado es de
 * `.movs.responsiva` —`display:block` y `data-etiqueta`—, y esta es una
 * `.tabla` a secas dentro de `.tabla-envoltura`, que lo que hace es
 * desplazarse a lo ancho.
 *
 * Medido el 9 de agosto de 2026 a 390 px: `border-inline-start` valía 0 y
 * `border-block-start` 1 px, o sea **la columna de acción se quedaba sin
 * separación** y ganaba una raya encima que no separa nada. El filete vertical
 * sigue haciendo su trabajo mientras haya columnas, y aquí siempre las hay. */
.col-accion { border-inline-start: 1px solid var(--border); }

/* ─── Y LA FORMA DE LA CELDA SE DECLARA DONDE GANA ─────────────────────────
 *
 * `.forma-en-celda { flex-wrap: nowrap }` era **código muerto**: `.tabla td
 * .row.gap-2` (0,3,1) —escrita para el control de pago de Comisiones, que hoy
 * ya no usa ese marcado— pisaba a `.forma-en-celda` (0,1,0). Medido: el valor
 * calculado era `wrap`, y además llegaba un `margin-block-start` ajeno que
 * subía cada fila 9 px sin que nadie lo hubiera pedido.
 *
 * Se nombra la celda para poder decidir, en vez de subir la especificidad a
 * ciegas. Y lo que se decide se decidió **midiendo**, no por gusto:
 *
 *   · Con `nowrap` a secas el campo se encoge a **26 px** —no cabe ni un
 *     importe de cuatro cifras—, porque la columna no da para campo + botón.
 *   · Apilados, el campo se lee bien pero la fila mide 104 px y la tabla de
 *     veinte gastos ocupa 3.087 px a 1.440.
 *   · Uno al lado del otro **cuando la ventana da para ello** deja la fila en
 *     58 px y la página en 2.303 px —un 29 % menos— sin barra horizontal.
 *
 * El corte está en 1.200 px porque es donde la tabla de siete columnas todavía
 * cabe entera con la acción en línea (medido: 1.161/1.161 a 1.280, y a 1.199
 * la acción ya no cabía). Por debajo se apilan, que es lo que cabe. */
.tabla td.col-accion .forma-en-celda { align-items: center; margin-block-start: 0; }
.tabla td.col-accion .forma-en-celda .input { max-inline-size: 100%; }
.tabla td.col-accion .forma-en-celda .btn   { inline-size: 100%; }
@media (min-width: 1200px) {
  .tabla td.col-accion .forma-en-celda { flex-wrap: nowrap; }
  .tabla td.col-accion .forma-en-celda .input { min-inline-size: 9ch; max-inline-size: 12ch; }
  .tabla td.col-accion .forma-en-celda .btn   { inline-size: auto; }
}

/* ═══════════ LA BARRA: NOMBRE E IMPORTE EN UNA LÍNEA, POR CONSTRUCCIÓN ══════
   Antes compartían línea «si cabían» —`flex-wrap` en `.barra-fila` con un suelo
   de 6rem en la etiqueta—, así que en una tarjeta estrecha el importe bajaba y
   cada barra pasaba de dos renglones a tres. Kike lo vio en Compras, donde el
   tercer widget es el más estrecho de los tres.

   La cabecera ocupa la fila entera, así que la pista sigue cayendo debajo y
   cruzándola —que es para lo que existe: comparar longitudes entre barras—, y
   cuando el ancho aprieta cede la etiqueta con puntos suspensivos, no el
   importe. El nombre completo sigue en el DOM y en `title`. */
.barra-cabeza {
  flex: 0 0 100%;
  display: flex; align-items: baseline; gap: var(--space-2); min-width: 0;
  /* ─── «CEDER» NO PUEDE SER «DESAPARECER» ─────────────────────────────────
   *
   * La línea de abajo decía «aquí el que cede es el rótulo, y ceder es
   * recortarse». Medido el 12 de agosto de 2026 en Movimientos, widget «De
   * dónde entró»: con `min-width: 0` y un importe que no encoge
   * (`.barra-valores { flex: 0 0 auto }`), el rótulo cedía hasta **9,2 px con
   * «SaaS» a máximo@1400 —medio carácter— y hasta 0 px a muy_grande@1165 y
   * máximo@1165**. No quedaba ni un punto suspensivo: ocho rótulos borrados.
   *
   * El suelo vuelve, pero en `ch` —crece con la preferencia de texto, §8.1— y
   * con `flex-wrap` para que la salida cuando ni así cabe sea la de siempre en
   * esta casa: el importe baja de renglón, que es lo que `.barra-fila` ya hace
   * arriba. Medido después: el rótulo más estrecho pasa de 9,2 px a 320,8 y de
   * 0 a 242,5, y los ocho rótulos «estrujados a nada» quedan en cero. Se paga
   * en alto de tarjeta a escalas grandes (1.226 → 1.379 px a máximo@1165), que
   * es el renglón de más que aquel comentario quería evitar: entre un renglón
   * y un nombre invisible, el renglón. */
  flex-wrap: wrap;
}
.barra-cabeza .barra-etiqueta { min-width: 6ch; }

/* (Aquí había una repetición de `.barras-categoria` que, por venir después,
   le quitaba el `display: grid` y la separación entre barras que declara su
   regla original. Una copia parcial de una regla no la refuerza: la rompe en
   lo que no repite. El ancho del gráfico se arregla donde estaba el defecto
   —la rejilla de dos columnas—, no aquí.) */

/* ---------- El motivo que encabeza su grupo, en Comisiones ----------
   Kike, 11 de agosto de 2026: quince filas con la misma frase tapaban las tres
   que sí necesitaban algo de él. El motivo pasa a encabezar su grupo y deja de
   repetirse por fila.

   Se ve como un encabezado y no como un dato: alineado a la izquierda aunque la
   fila lleve números, con el fondo hundido que ya usan las cabeceras de tabla.
   Y NO por color solo: es un `<th scope="colgroup">`, así que un lector de
   pantalla lo anuncia como título de lo que viene. */
.tabla th.agrupa-motivo {
  text-align: start;
  background: var(--bg-sunken);
  font-weight: var(--fw-semibold);
  padding-block: var(--space-2);
  border-block-start: 1px solid var(--border);
}

/* ============================================================
   Trasladar la detracción al BN · un formulario dentro de una celda
   ============================================================ */

/* ─── POR QUÉ PLEGADO ───────────────────────────────────────────────────────
   El traslado pide dos datos —de qué cuenta sale y qué día— y la fila de la
   detracción ya lleva siete columnas. Desplegado siempre, la tabla de nueve
   pendientes se vuelve ilegible; escondido detrás de JavaScript, deja de existir
   sin JavaScript. `<details>` es la única de las tres opciones que funciona con
   el teclado, con el lector de pantalla y con el navegador tal cual viene. */
.desplegable-compacto > summary {
  cursor: pointer;
  padding: var(--space-2) 0;
  min-height: var(--tactil-minimo);
  display: flex; align-items: center;
  font-size: var(--fs-caption);
  font-weight: var(--fw-semibold);
  color: var(--brand-strong);
}
/* El foco se ve. Un desplegable al que se llega con Tab y no se nota dónde está
   es un control que solo sirve con ratón. */
.desplegable-compacto > summary:focus-visible {
  outline: var(--focus-width) solid var(--focus-color);
  outline-offset: 2px; border-radius: var(--radius-sm);
}
.desplegable-compacto[open] > summary { margin-block-end: var(--space-1); }

.apilado-compacto {
  display: flex; flex-direction: column; gap: var(--space-2);
  min-width: 12rem; max-width: 18rem;
  padding: var(--space-2);
  /* ─── EL BORDE TENÍA QUE VERSE Y NO SE VEÍA ──────────────────────────────
     Medido el 11 de agosto de 2026 sobre los tokens: `--border` sobre la
     tarjeta da **1,36:1** y el relleno `--bg-sunken` sobre la misma tarjeta
     **1,13:1**. Ni el borde ni el fondo llegaban a 3:1, así que el panel no
     estaba delimitado por nada perceptible — que es exactamente lo que este
     borde existe para hacer, según la nota de aquí al lado.
     No es cosmético: dentro va el botón que mueve el dinero al Banco de la
     Nación, y sin límite visible se lee como si perteneciera a otra fila.
     `--border-strong` da 3,94:1 en oscuro y 5,95:1 en claro. */
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-md);
  background: var(--bg-sunken);
}
.apilado-compacto .field { margin: 0; }
.apilado-compacto .label { font-size: var(--fs-caption); }

@media (prefers-contrast: more) {
  /* El borde del panel y el subrayado del resumen: en alto contraste el fondo
     hundido puede quedar igual que el de la tabla, y entonces el panel se
     confunde con la celda. La forma lo dice sin depender del tono. */
  .apilado-compacto { border-width: 2px; }
  .desplegable-compacto > summary { text-decoration: underline; }
}

/* ============================================================
   .lista-contactos · la cuarta hermana de una familia unánime
   ============================================================ */

/* ─── POR QUÉ ÉSTA SÍ SE PUEDE DEDUCIR ───────────────────────────────────────
 *
 * Sus tres hermanas hacen **lo mismo** y ninguna discrepa:
 *
 *   .lista-adjuntos  list-style:none · display:flex column · gap --space-3
 *   .lista-saldos    list-style:none · margin:0 padding:0 · grid · gap --space-2
 *   .lista-marcas    list-style:none · padding:0 · grid · gap --space-3
 *
 * Las tres quitan la viñeta, quitan la sangría del navegador y apilan con
 * hueco. Sin regla, ESTA `<ul>` sale con **viñeta de disco y 40 px de sangría**
 * —el valor por omisión del navegador— mientras las otras tres, en la misma
 * aplicación, salen sin ninguna de las dos cosas. No es que le falte un adorno:
 * es que se está pintando de una manera que nadie eligió.
 *
 * Se toma `--space-3`, que es el hueco de dos de las tres y el de la lista que
 * más se le parece —`.lista-adjuntos`, también fichas de una entidad con varias
 * líneas por elemento—. No se copia una regla a medias: se escriben las cuatro
 * declaraciones que las hermanas comparten, porque una copia parcial rompe lo
 * que no repite (le pasó a `.barras-categoria`, que perdió su `display: grid`).
 *
 * NO toca tamaño de letra, color ni área táctil: no hay nada en la familia que
 * lo justifique, y un estilo que nadie decidió es peor que el auditor en rojo. */
.lista-contactos {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--space-3);
}

/* ============================================================
   El combo de compras · tres columnas que sí quedan alineadas
   ============================================================ */

/* ─── POR QUÉ TRES COLUMNAS Y NO UNA FRASE ──────────────────────────────────
 *
 * Kike, 12 de agosto de 2026: la fecha delante, porque una compra se identifica
 * antes por fecha que por proveedor. Y alineadas — con el proveedor de por medio
 * las fechas caían en sitios distintos y había que leer la línea entera.
 *
 * ─── LOS ANCHOS SALEN DE MEDIR, NO DE PROBAR ────────────────────────────────
 *
 * Medido sobre las 3.424 compras vivas:
 *
 *     el número más largo que existe    15 caracteres   (504-04-00020479)
 *     el 95,8 % cabe en                 13 caracteres
 *     sin serie ni número                    3 compras  (salen como #id)
 *
 * Se reserva `15ch` y NO se corta: cortar habría ahorrado dos caracteres a
 * cambio de esconder el final de 144 documentos, y el final es justo lo que los
 * distingue. La fecha son `8ch` fijos —`dd/mm/yy`— y el resto se lleva lo que
 * quede.
 *
 * ─── Y LOS DÍGITOS TIENEN QUE MEDIR LO MISMO ────────────────────────────────
 *
 * `tabular-nums`: con la fuente normal un `1` es más estrecho que un `8`, así
 * que dos fechas de igual longitud acaban en columnas distintas y la rejilla no
 * salva nada. Va en las dos primeras columnas, que son las que llevan cifras.
 */
/* ─── LA CUARTA COLUMNA · EL MONTO ──────────────────────────────────────────
 *
 * Kike, 12 de agosto de 2026: el monto es lo que dice de un vistazo si la
 * compra corresponde al gasto. Iba pegado al proveedor —de largo libre— así
 * que los importes no formaban columna.
 *
 * Medido sobre las 3.424 compras vivas:
 *
 *     el importe más largo con símbolo   12 caracteres   (S/ 23,571.00)
 *     el 99,9 % cabe en                  11
 *     llegan a 12                         3 compras
 *
 * Se reservan `12ch`: cuesta UN carácter sobre lo que cubriría al 99,9 % y
 * evita que tres filas rompan la columna de las 3.421 restantes. Alineado a
 * la derecha, que es como se comparan cifras. */
.combo-opcion--columnas {
  display: grid;
  /* ─── LA TERCERA CRECE CON LO QUE LLEVA ─────────────────────────────────
   *
   * Eran `12ch` fijos, medidos cuando esa columna llevaba **un** importe. El
   * 13 de agosto de 2026 pasó a llevar dos —«$ 323.10 · falta $ 20.47», 24
   * caracteres— para que una compra a medias no se elija creyendo que se paga
   * entera, y el texto **se montó encima del proveedor**. Kike lo vio en
   * pantalla: «$ 323.10 · falta $ 2OVH4US».
   *
   * `minmax(12ch, auto)` conserva el suelo de los 12 caracteres —para que las
   * filas de un solo importe sigan alineadas— y deja que la columna crezca
   * hasta el contenido más largo cuando alguna fila trae los dos. La cuarta,
   * que es la flexible, cede el sitio: es la que ya cedía. */
  grid-template-columns: 8ch 15ch minmax(12ch, auto) minmax(0, 1fr);
  gap: var(--space-3);
  align-items: baseline;
}
.combo-opcion--columnas .combo-col--1,
.combo-opcion--columnas .combo-col--2,
.combo-opcion--columnas .combo-col--3 {
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
/* El importe a la derecha: es la única de las cuatro que se compara con la de
   arriba, y comparar cifras pide que los dígitos de menor peso coincidan. */
.combo-opcion--columnas .combo-col--3 { text-align: right; }
/* El tercero cede: el proveedor es lo único de largo libre, y recortarlo es
   preferible a que la fila envuelva y rompa la columna de las de abajo. */
.combo-opcion--columnas .combo-col--4 {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* En pantalla estrecha no caben tres columnas de ancho fijo: se apilan en dos
   líneas antes que recortar la fecha, que es lo que Kike mira primero. */
@media (max-width: 32rem) {
  /* ─── QUÉ BAJA DE LÍNEA PRIMERO ────────────────────────────────────────
     El PROVEEDOR, que es el único de largo libre y el que Kike mira último:
     fecha, número y monto son los tres que identifican la compra y se quedan
     arriba en su columna. */
  .combo-opcion--columnas {
    grid-template-columns: 8ch minmax(0, 1fr) 12ch;
  }
  .combo-opcion--columnas .combo-col--4 { grid-column: 1 / -1; }
}

/* ============================================================
   Vencimientos · amarillo a 3 días, rosa vencido
   ============================================================ */

/* ─── TRES SEÑALES, PORQUE EL COLOR NO PUEDE SER LA ÚNICA ───────────────────
 *
 * Kike tiene baja visión, y **amarillo y rosa son justo el par que peor se
 * distingue**: en un déficit rojo-verde los dos caen cerca, y en alto contraste
 * los fondos tenues se van. Así que cada estado lleva:
 *
 *   1. **color de fondo** — el que pidió Kike;
 *   2. **la palabra**, en un distintivo dentro de la fila —«Vencido», «Vence
 *      hoy», «Vence pronto»—, que ya la pone la vista;
 *   3. **la forma del borde izquierdo**: CONTINUO y grueso lo vencido,
 *      DISCONTINUO y fino lo que aprieta. La forma sobrevive al alto contraste,
 *      a la impresión en blanco y negro y a no distinguir los dos tonos.
 *
 * Los colores salen de `--warning` y `--expense`, que ya son los de aviso y
 * salida de Flow: dos tonos nuevos para esto serían dos que nadie aprobó.
 *
 * ─── Y NO TAPA LO QUE LA FILA YA PINTA ─────────────────────────────────────
 *
 * El fondo va al 8 % y el distintivo de estado de la fila se dibuja encima sin
 * competir. Dos señales del mismo peso encima se anulan — por eso una es fondo
 * y la otra borde, que ocupan sitios distintos. */
.vence-vencido,
.vence-aprieta { position: relative; }

/* ─── Y APUNTABA A `td`, CUANDO LA PRIMERA CELDA ES UN `th` ─────────────────
 *
 * Medido el 13 de agosto de 2026 fabricando la condición en Gastos fijos:
 * **las cinco filas marcadas empiezan por `<th scope="row">`**, así que
 * `td:first-child` no encontraba nada y el borde medía **0 px**. La regla
 * llevaba escrita desde que se decidió y no se aplicó nunca — que es peor
 * que no tenerla, porque parece cubierto.
 *
 * Y donde más falta hacía es donde menos se veía: **en alto contraste el
 * tinte desaparece a propósito** —está decidido más abajo—, así que ahí el
 * borde era la única marca visual que quedaba, y no existía. Medido: tinte
 * 1,00 en las dos combinaciones de alto contraste.
 *
 * Se apunta a `:first-child` a secas. La fila sigue diciéndolo con palabras
 * —«Venció hace 3 días»—, así que §8.5 se cumplía antes y se cumple ahora:
 * lo que cambia es que quien mira de lejos también lo ve. */
.vence-vencido > :first-child,
.vence-aprieta > :first-child { border-inline-start-width: 4px; border-inline-start-style: solid; }

.vence-vencido { background: color-mix(in srgb, var(--expense) 8%, transparent); }
.vence-vencido > :first-child { border-inline-start-color: var(--expense); }

.vence-aprieta { background: color-mix(in srgb, var(--warning) 8%, transparent); }
.vence-aprieta > :first-child {
  border-inline-start-color: var(--warning);
  border-inline-start-style: dashed;
  border-inline-start-width: 3px;
}

/* ─── EN ALTO CONTRASTE MANDA LA FORMA ──────────────────────────────────────
 * El fondo tenue desaparece —ahí estorba y baja el contraste del texto— y
 * quedan el borde y la palabra, que son los que sobreviven. Es lo mismo que ya
 * hace `.filtro-activo`. */
[data-contrast="high"] .vence-vencido,
[data-contrast="high"] .vence-aprieta { background: transparent; }
[data-contrast="high"] .vence-vencido > :first-child { border-inline-start-width: 6px; }
[data-contrast="high"] .vence-aprieta > :first-child { border-inline-start-width: 5px; }

/* ============================================================
   Comisiones · el listado por vendedor y su gráfico de barras
   ============================================================ */

/* ─── UNA FILA POR FACTURA, Y LA TABLA A TODO EL ANCHO ─────────────────────
 *
 * Kike, 12 de agosto de 2026: las dos líneas por factura dejaban espacio sin
 * usar a los lados. Una sola fila con siete columnas aprovecha el ancho, y la
 * descripción —que era la segunda línea— se recorta a 48 caracteres con el
 * texto entero en el `title` y en el modal.
 *
 * ─── POR QUÉ `fixed` Y NO `auto`, QUE ES LO QUE PIDIÓ COMPROBAR ────────────
 *
 * Cada vendedor tiene SU PROPIA TABLA dentro de su tarjeta. Con `auto`, cada
 * una calcula los anchos con sus propias filas: las de Enrique y las de Pablo
 * quedan en sitios distintos, y las cifras dejan de poder compararse bajando en
 * vertical. Es literalmente lo que ya pasó con los bloques por día de
 * Movimientos —81 px de desfase entre «Ayer» y el día anterior— y la solución
 * es la misma que se escribió allí: `fixed` con los anchos declarados una vez.
 *
 * Y es también lo que hace que el ancho NO dependa del contenido de la fila:
 * con `fixed` las medidas salen de la cabecera, así que una tabla de facturas
 * «Pagada» —sin botones— reparte exactamente igual que una de «Por depositar»
 * con sus dos iconos. La columna de acciones existe en todas las filas aunque
 * vaya vacía y tiene su ancho reservado; si apareciera solo donde hay botones,
 * la tabla se recolocaría al pagar una comisión.
 *
 * ─── LOS ANCHOS VAN EN PORCENTAJE, Y ESO SE MIDIÓ EN UN NAVEGADOR ─────────
 *
 * Kike, 12 de agosto de 2026, con captura: seguía saliendo barra horizontal, y
 * en la tabla de Enrique, **que tiene el `<tbody>` vacío**.
 *
 * Estuvieron escritos como `width: min(<ch>, <%>)`, con la idea de que el `ch`
 * ajustara la columna al dato mientras hubiera sitio y el porcentaje pusiera el
 * techo. **No funciona**, y medido en Chrome sobre la pantalla real:
 *
 *     fecha=185 factura=185 cliente=185 desc=185
 *     monto=185 comisión=185 estado=185 acciones=185
 *
 * Las OCHO iguales — 1.481 / 8. El algoritmo de tabla fija no resuelve un
 * `min()` con porcentaje dentro y trata la columna como automática, así que se
 * repartían a partes iguales. Y como `min()` **sí parsea**, la declaración es
 * válida y se comía la reserva `width: <%>` que iba delante: la reserva no
 * protege de un valor que el navegador acepta y luego ignora.
 *
 * Eso explicaba la captura entera: Cliente y Descripción estrangulados a 185 px
 * mientras Estado y Acciones sobraban de ancho. Y el desborde no era de
 * presupuesto: **medía 1 px**, de redondeo, y bastaba para pintar la barra.
 *
 * Ahora van en PORCENTAJE a secas, que es lo que el algoritmo de tabla fija
 * lleva resolviendo desde siempre. Se pierde el ajuste al `ch`; se gana que lo
 * declarado sea lo que ocurre, que es la condición para poder afinarlo.
 *
 * Los siete suman **66 %**, así que además de no desbordar dejan **34 % para la
 * descripción**, que es la única sin declarar y la que se lleva lo que sobre.
 * El reparto sale de medir lo que cada columna necesita de verdad, no de
 * repartir a ojo: los dos importes y la fecha son cortos, y lo que pedía sitio
 * —cliente y descripción— es lo que lo recibe.
 *
 * Y AHÍ EL RECORTE ES DE ESTA HOJA, no de PHP. Decisión de Kike del 12 de
 * agosto de 2026, tomada con el intercambio delante: la columna salía cortada a
 * 48 caracteres fijos —el mismo texto en cualquier monitor— y prefirió una
 * pantalla ordenada aunque el recorte cambie de una pantalla a otra. El texto
 * llega entero al marcado y aquí lo corta `ellipsis` según el hueco. Que sea la
 * ÚNICA sin ancho declarado es lo que hace que ceda ella y no las demás, que es
 * lo que mantiene alineadas las tablas de los distintos vendedores. */
.tabla-comisiones { width: 100%; table-layout: fixed; }
/* ─── MENOS MARGEN LATERAL EN LAS CELDAS ────────────────────────────────────
   Kike, 12 de agosto de 2026: «reduce los márgenes laterales de las columnas
   para ganar más espacio para empresa y descripción». El relleno general de
   `.tabla` son `--space-3` a cada lado; aquí baja a `--space-2`. Son ocho
   columnas, así que devuelve unos 60 px al renglón, y el reparto en porcentaje
   los pone donde hacen falta. Y el «respiro» de `--space-5` que `.tabla
   .col-monto` deja tras cada cifra —para que no se pegue a la columna
   siguiente— baja a `--space-3`: aquí son DOS columnas de cifra seguidas, así
   que ese hueco se pagaba dos veces. */
.tabla-comisiones th,
.tabla-comisiones td { padding-inline: var(--space-2); }
.tabla-comisiones th:first-child,
.tabla-comisiones td:first-child { padding-inline-start: 0; }
.tabla-comisiones th:last-child,
.tabla-comisiones td:last-child  { padding-inline-end: 0; }
.tabla-comisiones .col-monto { padding-inline-end: var(--space-3); }
/* La fecha y sus días caben en una línea: «22/07/26 · 21 d». */
.tabla-comisiones .col-com-fecha   { width: 8%; white-space: nowrap; }
/* ─── UN NÚMERO LARGO NO PUEDE PINTARSE SOBRE EL CLIENTE · `D-1125` ──────────
 *
 * Medido el 06/09/2026 en `/comisiones`: la celda mide **116 px** y el número
 * `2RT797010J737453C` —una factura de PayPal— ocupa **186 px**. Con `nowrap`,
 * `overflow: visible` y `text-overflow: clip`, el texto **se metía 78 px dentro
 * de la celda «Cliente»** y se imprimía encima de su nombre;
 * `elementFromPoint` en ese punto devolvía el nombre del cliente. **Dos datos
 * superpuestos, y ninguno legible** — y para quien ve poco eso no es estética.
 *
 * Se recorta con puntos suspensivos. El número entero sigue estando: el enlace
 * ya llevaba `title`, así que se lee al pasar por encima, y la ficha se abre
 * igual. *Recortar enseña que falta algo; pisar no.*
 *
 * El recorte va en el `<a>` y no en el `<td>`: en una celda de tabla
 * `text-overflow` solo funciona con `max-width`, y fijarlo aquí pelearía con el
 * `width: 7%` que reparte la tabla. */
.tabla-comisiones .col-com-factura { width: 7%; white-space: nowrap; overflow: hidden; }
.tabla-comisiones .col-com-factura > a {
  display: block; max-width: 100%;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* 27 caracteres es lo que corta `porElMedio()` en Facturas, y el ancho se
   declara con holgura sobre esa cuenta: una razón social va en mayúsculas y una
   mayúscula es más ancha que el `0` con el que se mide un `ch`. Sin holgura, el
   recorte de la hoja se comería la cola que el de PHP conservó a propósito. */
.tabla-comisiones .col-com-cliente {
  width: 19%;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* LA ÚNICA SIN `width`, y por eso la única elástica: en `table-layout: fixed`
   la columna sin ancho declarado se queda con lo que sobre. Aquí sí manda el
   `ellipsis`, porque aquí es donde se decidió que ocurriera el recorte. */
.tabla-comisiones .col-com-desc {
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.tabla-comisiones .col-monto       { width: 8%; }
/* ─── LA COMISIÓN PESA MÁS QUE EL MONTO ─────────────────────────────────────
   Kike, 12 de agosto de 2026: es la cifra que el vendedor busca primero. El
   énfasis es el que ya usan Facturas y Notas para su columna de importe —el
   mismo cuerpo y el mismo peso—, no uno inventado para esta pantalla. Y es de
   TIPOGRAFÍA, no de color: §8.5, el color no puede ser la única señal. Por eso
   pide un poco más de ancho que Monto: la negrita ocupa más. */
.tabla-comisiones .col-com-comision {
  width: 9%;
  font-size: 1.05rem; font-weight: 700;
}
.tabla-comisiones .col-com-comision .caption,
.tabla-comisiones .col-com-comision .text-secondary { font-size: var(--fs-caption); font-weight: 400; }
/* La pastilla más larga es «Por depositar», y con su punto y su relleno. */
.tabla-comisiones .col-com-estado  { width: 10%; }
/* Y LA PASTILLA ENVUELVE ANTES QUE DESBORDAR. Medido en Chrome: a escala
   «máximo» en una ventana de ~1.100 px, «Corresponderá» se salía 18 px de su
   columna y volvía a pintar la barra. Una pastilla es TEXTO con relleno: crece
   con la escala mientras el 10 % de la tabla no. Que parta en dos renglones en
   ese caso extremo es preferible a que la fila entera se desplace — y la
   palabra, que es lo que lleva el significado (§8.5), se sigue leyendo. */
.tabla-comisiones .col-com-estado .badge {
  max-inline-size: 100%; white-space: normal; text-align: start;
}
/* Los dos iconos caben con sus 44 px de objetivo táctil, su separación y el
   respiro de la celda. NO se mide en `ch`: es una medida de tacto, y en `ch`
   encogería al subir la escala de texto justo mientras los botones crecen —
   las dos tendencias opuestas de §8.5. Antes reservaba `--space-2` y hacían
   falta `--space-1` entre los dos botones más `--space-3` de relleno de la
   celda, así que el segundo botón se salía de su columna por unos 9 px. */
.tabla-comisiones .col-com-acciones {
  /* ─── LA ÚNICA QUE NO VA EN PORCENTAJE ──────────────────────────────────
   * Medido en Chrome a las cuatro escalas de texto: con un 8 % los dos iconos
   * se salían **20 px en «muy grande» y 43 en «máximo»**. Los botones crecen
   * con la escala —su relleno va en `rem`— y el porcentaje de la tabla no;
   * peor aún, la tarjeta encoge un poco al crecer sus márgenes, así que las
   * dos medidas se separan. Es §8.5 otra vez: una medida de TACTO no se
   * expresa como una fracción de un ancho de texto.
   * `min-inline-size` no sirve aquí: se probó y el reparto de tabla fija lo
   * ignora. Lo que manda es el `width`, así que el `width` tiene que decir la
   * verdad — dos objetivos táctiles y su separación, con un suelo en `em` que
   * acompaña a los botones cuando el texto crece. */
  width: max(calc(var(--tactil-minimo) * 2 + var(--space-1) + var(--space-2)), 4.9em);
  white-space: nowrap; text-align: end;
  /* ─── EL RÓTULO QUE NO SE VE TAMBIÉN OCUPA SITIO ─────────────────────────
   * El `<th>` de acciones lleva un `<span class="sr-only">Acciones</span>`
   * para quien no ve la pantalla. Es `position: absolute` y mide 1 px, pero su
   * posición estática es el FINAL de la celda —la columna va alineada al
   * final—, así que ese píxel caía justo fuera del borde derecho de la tabla y
   * bastaba para que la envoltura pintara barra de desplazamiento. Medido en
   * Chrome: `right` del span 1.534 contra 1.533 de la envoltura, y en la tabla
   * de Enrique, sin ninguna fila, era el ÚNICO culpable.
   *
   * Es el mismo defecto que ya se pagó y se escribió arriba: «el desborde no
   * tenía nada dentro, lo reservaba algo que no se ve». Se ancla al principio
   * de la celda, donde no hay borde que rebasar. */
  position: relative;
}
.tabla-comisiones .col-com-acciones .sr-only { inset-inline-start: 0; }
/* Con `fixed`, lo que no cabe se derrama; estas dos son las que traen texto
   largo debajo de su dato —el ajuste de tres cifras y «sin movimiento
   enlazado»— y envuelven en vez de empujar la columna. */
.tabla-comisiones .col-monto .caption,
.tabla-comisiones .col-com-estado .caption { display: block; white-space: normal; }
/* ─── EL CONCEPTO DEL GASTO CEDE; LA FECHA NO ──────────────────────────────
 *
 * Desde que la fila pagada dice **con qué** se pagó, esta celda lleva un texto
 * que escribe una persona y que no tiene tope de largo. En el 10 % de tabla que
 * le toca —115 px útiles a 1440 px— eso no cabe, y lo que hacía era crecer:
 * medido el 14 de agosto de 2026 sobre las filas reales, la comisión #14 pedía
 * **10 renglones y 263,4 px** sólo para el concepto, y su fila entera medía
 * **297,8 px** contra los **60,7 px** de una fila sin pagar. Tres filas así en
 * la tabla y el barrido visual se pierde.
 *
 * Dos renglones y puntos suspensivos. El texto íntegro sigue en el `title`, y
 * la fecha queda fuera del recorte —va en el nodo de arriba— porque es el dato
 * que identifica el movimiento y es corto: recortarlo sería recortar
 * exactamente lo que hay que leer.
 *
 * `-webkit-line-clamp` es la propiedad con soporte real en los tres motores; se
 * declara junto a `line-clamp`, que es la estándar, para que el día que ésta
 * mande no haya que volver. El `max-block-size` es el suelo del recorte para el
 * motor que no entienda ninguna de las dos: sin él, un navegador sin soporte
 * volvería a los diez renglones sin avisar. */
.tabla-comisiones .col-com-estado .com-pago-concepto {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
  max-block-size: calc(2 * 1.55em);
  overflow-wrap: anywhere;
}
.tabla-comisiones .col-com-acciones .btn--icon + .btn--icon { margin-inline-start: var(--space-1); }
/* LA FECHA Y SUS DÍAS, EN UNA LÍNEA (Kike, 12 de agosto de 2026). Antes iban
   uno debajo del otro para gastar menos ancho; con la columna en `min(15ch,
   12%)` caben en el renglón y la fila baja de alto, que es lo que se buscaba.
   El `caption` vuelve a ser en línea — la regla que lo ponía en bloque era de
   la versión de dos renglones y ahora estorbaría. */
.tabla-comisiones .col-com-fecha .caption { display: inline; }

/* ─── LAS DOS TARJETAS DE CIFRA, CENTRADAS ─────────────────────────────────
 *
 * Kike, 12 de agosto de 2026. Rótulo, cifra y explicación centrados **como
 * bloque**: los tres comparten eje, que es lo que hace que se lean como una
 * sola cosa y no como tres apiladas.
 *
 * `justify-content: center` además las centra en vertical, así que las dos
 * tarjetas se ven iguales aunque sus textos ocupen distinto — sin eso, la de
 * dos líneas de explicación empujaría su cifra más arriba que la de al lado.
 *
 * La tercera —el gráfico— NO lleva esto: sus barras se comparan desde una
 * línea común a la izquierda, y centrarlas quitaría justo esa referencia. */
.card.stat.stat--centrada {
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  text-align: center;
}
.card.stat.stat--centrada .caption { max-width: 30ch; }
/* ─── Y LO DE DEBAJO DE LA CIFRA TAMBIÉN ────────────────────────────────────
 *
 * Kike, 12 de agosto de 2026: «la tarjeta se lee partida en dos». Medido: el
 * bloque interior es una columna con `align-items: flex-start`, así que el
 * rótulo (127 px) y el desglose (212 px) quedaban pegados al borde izquierdo
 * mientras la cifra —que ocupa los 346 px del bloque— parecía centrada solo
 * porque su texto lo está. Centrar la tarjeta sin centrar esto deja dos ejes.
 *
 * Se centran como BLOQUES, no cada renglón por su cuenta: dentro del desglose,
 * «Entró» y «Salió» conservan su rótulo de 5ch para que las dos cifras formen
 * columna, y centrar cada renglón por separado rompería justo eso. Alcanza a
 * los cuatro widgets de cifra grande, que comparten `.bloque-periodo`. */
.card.stat.stat--centrada .bloque-periodo { align-items: center; }

/* ─── CUANDO LOS MONTOS DE LA FILA TIENEN QUE ALINEARSE ─────────────────────
 *
 * Kike pidió las cuatro de Deudas centradas **y con los montos a la misma
 * altura**. Medido: las dos cosas se contradicen, y no por el rótulo.
 *
 *   «Se debe»        rótulo 1 línea · cifra a 88 px del borde
 *   «Cuotas del mes» rótulo 1 línea · cifra a 88 px
 *   «Crece al mes»   rótulo 1 línea · cifra a **61 px**
 *
 * Los cuatro rótulos miden lo mismo. Lo que mueve la cifra es el CENTRADO
 * VERTICAL: cada tarjeta centra su propio bloque, y como las explicaciones de
 * debajo miden distinto —53 px en una, 105 en otra—, el bloque entero sube o
 * baja y la cifra con él. Centrar en vertical y alinear entre tarjetas son
 * incompatibles por construcción, no por un ajuste que falte.
 *
 * Y Flow ya lo había decidido una vez, con su razón escrita unas líneas más
 * arriba: «arriba, no centrado: todas las tarjetas empiezan en el mismo borde
 * … luego la cifra cae a la misma altura en todas». Así que en las filas donde
 * la alineación manda se recupera esa decisión y se conserva el centrado
 * HORIZONTAL, que es el que no estorba.
 *
 * No es altura fija: nadie declara un alto. Si mañana un rótulo crece a dos
 * líneas, las cuatro cifras bajan juntas — que es lo que se pedía. */
.tercios--montos-alineados .card.stat.stat--centrada { justify-content: flex-start; }

/* ─── EL GRÁFICO ES DE BARRAS CON SU CIFRA ESCRITA ─────────────────────────
 *
 * No es una librería: una barra sin número obliga a estimar por el largo, y
 * quien tiene baja visión estima peor. El dato va en texto al lado, y la barra
 * solo lo acompaña — si el color o el ancho no se distinguen, la cifra sigue
 * ahí. Es la misma regla que impide decir un estado solo con color (§8.5). */
.grafico-barras { list-style: none; margin: var(--space-2) 0 0; padding: 0; }
/* ─── LA PISTA, EN SU PROPIA LÍNEA · 01/09/2026 ────────────────────────
   Tenía tres columnas y la tercera era `max-content`: **el importe crecía con su
   contenido y le quitaba ancho a la pista de esa fila**. La pista medía `5rem`
   fijos, así que aquí no se encogía por la etiqueta — pero la fila entera se
   repartía distinto en cada línea.

   Hoy esta clase solo la usa una vista retirada; se arregla igual **para no
   dejar la trampa puesta** a quien la reviva. */
.grafico-barras__fila {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-areas: "nombre cifra"
                       "pista  pista";
  align-items: center;
  gap: var(--space-micro) var(--space-2);
  padding-block: 2px;
}
.grafico-barras__nombre { grid-area: nombre; }
.grafico-barras__cifra  { grid-area: cifra; justify-self: end; }
.grafico-barras__pista  { grid-area: pista; }
.grafico-barras__nombre {
  font-size: var(--fs-caption); color: var(--text-secondary);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.grafico-barras__pista {
  block-size: .55rem; background: var(--surface-2);
  border: 1px solid var(--border); border-radius: var(--radius-sm); overflow: hidden;
}
/* ─── EL AZUL NO SIGNIFICABA NADA · Kike, 28/08/2026 ────────────────────────
 *
 * «Categorías más vendidas» pintaba sus barras con el azul de marca, y en Money
 * **el azul no es ninguna de las dos cosas**: las entradas van verdes y las
 * salidas rojas. Un tercer color para «dinero que entra» obliga a aprender una
 * escala que no existe.
 *
 * Estas categorías son **ventas** — dinero que entra —, así que llevan el mismo
 * tono que las entradas de Money. *Lo que se llama igual se ve igual en todo
 * Flow.* */
.grafico-barras__valor { display: block; block-size: 100%; background: var(--income); }
.grafico-barras__cifra {
  font-size: var(--fs-caption); font-variant-numeric: tabular-nums; white-space: nowrap;
}
/* En alto contraste la barra pierde el relleno de marca y gana borde: el tono
   deja de distinguirse y la forma tiene que sostenerse sola. */
[data-contrast="high"] .grafico-barras__valor { background: var(--text); }
[data-contrast="high"] .grafico-barras__pista { border-width: 2px; }

/* ─── EL ENLACE DE UNA TABLA USA EL AZUL DE ENLACE ──────────────────────────
   Y no el de marca: sobre una fila teñida el de marca daba 3,97:1 en oscuro
   normal. Alcanza a los enlaces DE TABLA, que son los que caen sobre tinte;
   los botones y el isotipo siguen con `--brand`, que es de identidad. */
.tabla a:not(.btn), table a:not(.btn) { color: var(--brand-enlace); }

/* ═══════════ LAS DOS FILAS DE TRIBUTOS CABEN EN UNA FILA ══════════════════
 *
 * Kike, 13 de agosto de 2026: «vio dos filas partidas». Son dos casos
 * distintos, y solo uno era un fallo.
 *
 * ─── LA PRIMERA: LA CUENTA ESTABA MAL ──────────────────────────────────────
 *
 * `.tercios` declara `1fr 2fr` —DOS columnas— y ahí hay TRES tarjetas. Caía
 * siempre una sola abajo, con hasta 1.040 px de hueco al lado, **incluso
 * sobrando sitio**. No era estrechez: era la cuenta.
 *
 * ─── EL SUELO DE ESTAS TARJETAS, MEDIDO, NO SUPUESTO ───────────────────────
 *
 * `.tercios--tres` pide `min(23rem, 100%)`, y ese 23rem se midió para OTRAS
 * tarjetas —las de costos de factura, con «S/ 4.399,91» y su rótulo—. Estas
 * son más pobres: título, cifra y una frase. Estrechándolas hasta que algo se
 * saliera o se apilara:
 *
 *     escala        aguanta          se rompe
 *     normal        114 px  6,71rem   111 px  6,51rem
 *     grande        139 px  6,56rem   136 px  6,40rem
 *     muy grande    168 px  6,59rem   165 px  6,46rem
 *     máximo        200 px  6,72rem   197 px  6,61rem
 *
 * El suelo es el MISMO en las cuatro: ~6,5 rem. Por eso el mínimo va en `rem`
 * y no en píxeles — el suelo escala con la letra, y el número lo demuestra.
 * Se elige `9rem`: 38 % por encima de donde se rompe, que no es empatar con el
 * límite. Con 9rem las tres caben en las 12 combinaciones de los tres anchos
 * por las cuatro escalas.
 *
 * ─── LA SEGUNDA NO TENÍA LO MISMO ──────────────────────────────────────────
 *
 * `.tercios-par` ya reparte con `auto-fit`: cuando parte, es porque tres NO
 * caben. Pero su `16rem` también estaba por encima de su suelo real, medido
 * igual: se rompe a 10,8–11,4 rem, y quien se rompe es la frase «Todavía no se
 * ha cargado…». Se baja a `13rem` —14 % de margen sobre su suelo—, que devuelve
 * la fila entera en 9 de las 12 en vez de 6. Medido después de aplicarlo, no
 * calculado antes: la cuenta decía 8 y la pantalla dice 9.
 *
 * En las tres restantes seguirá partiendo, y está bien que parta: a escala
 * máxima con la ventana estrecha hay 10,4 rem por tarjeta y el suelo son 10,9.
 * No cabe. Estrujarlas hasta que la frase se apile sería peor que bajarla.
 *
 * Las dos reglas van acotadas por `id`: `.tercios--tres` la usan Movimientos y
 * Comisiones, y `.tercios-par` media docena de pantallas más. Se cambia esta
 * pantalla, no la clase de todos. */
#umbrales-anuales.tercios--tres {
  grid-template-columns: repeat(auto-fit, minmax(min(9rem, 100%), 1fr));
}
#bolsas-fisco.tercios-par {
  grid-template-columns: repeat(auto-fit, minmax(min(13rem, 100%), 1fr));
}
/* ─── Y EN EL TELÉFONO VUELVEN A APILARSE ──────────────────────────────────
 *
 * Tester, 13 de agosto de 2026. Bajar el mínimo a `9rem` arregló el escritorio
 * y **rompió el teléfono**, por una razón que no se ve leyendo la regla: el
 * `min(9rem, 100%)` solo cede cuando el contenedor mide MENOS de 9rem, y un
 * teléfono mide mucho más. Con `23rem` el `100%` sí mordía —a 362 px de ancho
 * útil el mínimo pasaba a 362 y quedaba una columna—, así que la pila era un
 * efecto secundario del número grande, no una regla escrita.
 *
 * Y la regla que SÍ estaba escrita para eso no llegaba: `.tercios` se pone a
 * `1fr` en `max-width: 640px`, pero `#umbrales-anuales.tercios--tres` lleva un
 * `id` y gana por especificidad esté donde esté la consulta de medio. Una
 * regla que no se aplica nunca es peor que una que falta, porque parece que
 * está cubierto.
 *
 * Medido con el ancho útil del teléfono —390 px menos los 14 de relleno que la
 * hoja ya fija por debajo de 720 px—: quedaban DOS tarjetas de 157 px arriba y
 * una debajo, a escala normal. A las otras tres escalas seguía apilando, que
 * es lo que despista: fallaba solo en una de las cuatro.
 *
 * Se repite el `id` para empatar la especificidad y ganar por orden. No se
 * toca `#bolsas-fisco`: con `16rem` y con `13rem` da una sola columna en el
 * teléfono, así que ahí no cambió nada. */
@media (max-width: 640px) {
  #umbrales-anuales.tercios--tres { grid-template-columns: 1fr; }
}

/* ─── LO QUE SIGNIFICA LA CIFRA VA PEGADO A LA CIFRA ───────────────────────
 *
 * Tester, 13 de agosto de 2026, sobre el widget «Crece al mes» de Deudas: la
 * frase que dice qué es esa cifra y la nota de «medido / estimado» llevaban la
 * misma clase y el mismo hueco, así que se leían como dos notas al pie
 * seguidas. Una dice **qué significa el número** —sin ella el widget no se
 * entiende— y la otra es una salvedad sobre cómo se calculó.
 *
 * Se distinguen por su SITIO y no por su tamaño ni su color: pegada a la cifra
 * la primera, con su aire la segunda. El tamaño se queda porque bajarlo la
 * haría menos legible, y el color porque ya es el secundario — §8.5 no admite
 * significado solo por color, y aquí ni siquiera hace falta: lo dice la
 * distancia, que es la misma señal que usa cualquier pie de tabla.
 *
 * Va en `rem` y no en píxeles: el hueco tiene que crecer con la letra, o a
 * escala máxima las dos frases volverían a pegarse. */
.stat .stat-significado { margin-block-start: calc(var(--space-micro) * -1); }
.stat .stat-significado + .caption { margin-block-start: 0.5rem; }

/* ═══════════ COMPRAS · EL AIRE MUERTO ENTRE LA FECHA Y EL LÁPIZ ════════════
 *
 * Kike, 13 de agosto de 2026, con el listado delante: «hay un espacio entre el
 * botón de editar de cada fila y las fechas; redúcelo y aprovecha ese espacio».
 *
 * Medido: del final de «el 13/08» al borde del lápiz había **61 px** —14 de aire
 * en Pago, 12,75 de relleno en Acciones y 35 de columna vacía—, mientras
 * **Proveedor salía recortado**: «SCHREIBER BUSINESS CENTER SOCIEDAD A…».
 *
 * ─── Y RECUPERARLO NO BASTA: HAY QUE DÁRSELO A ALGUIEN ─────────────────────
 *
 * Estrechando solo Acciones se ganan **5 px** y no los toma nadie: las demás ya
 * tienen lo que piden, y Proveedor no crece porque `.celda-recortada` lo topa a
 * 22rem. Con las dos cosas a la vez, **Proveedor pasa de 376 a 476 px** —Pago
 * cede los 61 que sobraban y Documento 32— y los proveedores recortados bajan de
 * **2 a 1** en las doce combinaciones de ancho y escala.
 *
 * El tope sube solo aquí: `.celda-recortada` la usan más tablas y Concepto, que
 * ya tiene 121 px de aire, no necesita nada.
 *
 * ─── Y EL BOTÓN NO CEDE ────────────────────────────────────────────────────
 *
 * Lo que se recorta es el hueco a su lado, no el control: medido, sigue en
 * **44 × 44** a escala normal y sube a 63 en la máxima. En una tabla el `width`
 * es una sugerencia y el contenido manda, así que el objetivo táctil no puede
 * quedar por debajo de lo que el botón pida.
 *
 * (Y una corrección: el 13 por la tarde dije que este hueco «no se podía
 * repartir». Lo había probado con `#compras .movs.responsiva`, y esta tabla es
 * `.tabla` dentro de `.tabla-envoltura` — la regla no llegaba a aplicarse. No
 * medí un reparto imposible: medí una regla que no existía.) */
/* ─── EL LÁPIZ SE QUEDA DONDE ESTABA ──────────────────────────────────────
 *
 * Se probó ponerlo al principio de su celda: el hueco entre la fecha del pago
 * y el botón bajaba de 63 a 29 px. **Kike lo rechazó**: la posición del botón
 * de editar estaba bien como estaba, y esa alineación al final es la misma en
 * Facturas, Notas y los catálogos.
 *
 * Queda escrito para que no se vuelva a proponer: el hueco se reparte
 * ensanchando la columna que lo necesita —Proveedor—, no moviendo controles de
 * sitio. Lo que sigue debajo es esa parte, que sí se queda. */
#tabla-compras th.col-acciones,
#tabla-compras td.col-acciones { width: 3.5rem; padding-inline: 0.5rem 0; }
#tabla-compras .col-cliente.celda-recortada { max-width: 28rem; }

/* ─── EL IMPORTE DE UN PAGO SE CORRIGE EN SU FILA ──────────────────────────
 *
 * Un campo y un botón, en línea, dentro de la celda. Nace de la compra #77 de
 * Kike: llega con los 290,47 que da convertir 985,87 soles al tipo de SUNAT y
 * lo cierto son 270,10, porque el BCP le cobró a 3,65. Sin esto, corregir esa
 * cifra sería quitar el pago y volver a ponerlo — y de paso perder el orden.
 *
 * `flex-wrap` para que en móvil el botón caiga debajo del campo en vez de
 * estrujarlo: el campo es donde se escribe y no puede quedarse en cuatro
 * caracteres. Y no baja de `--tactil-minimo` de alto, que es la medida que ya
 * llevan los demás controles. */
.fila-importe {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  align-items: center;
  justify-content: flex-end;
}
.fila-importe .input {
  flex: 1 1 7ch;
  min-width: 7ch;
  max-width: 12ch;
  min-height: var(--tactil-minimo);
}
.fila-importe .btn { flex: 0 0 auto; }

/* ─── LA CARGA FISCAL DEL MES ────────────────────────────────────────────────
 *
 * `auto-fit` con un mínimo en `rem`: la rejilla se reordena sola al crecer la
 * letra, en vez de apretar seis columnas hasta que las cifras no se lean. A 375 px
 * con la letra al máximo queda una sola columna, que es lo correcto.
 *
 * `minmax(min(18rem, 100%), 1fr)`: el `min()` es lo que impide que el mínimo
 * intrínseco desborde el contenedor estrecho — el mismo defecto de `.field--enlinea`.
 */
.rejilla-carga {
  display: grid;
  gap: var(--space-4);
  grid-template-columns: repeat(auto-fit, minmax(min(var(--ancho-minimo-widget), 100%), 1fr));
}

/* ─── LOS SEIS EN TRES, DE DOS FILAS · 19/08/2026 ─────────────────────────
 *
 * Kike: «los seis pasan a tres, de dos filas cada uno». Cada tarjeta lleva dos
 * tributos, uno debajo del otro, y **no hay total**: son dos cifras, no una
 * suma. La separación entre las dos filas es lo que lo dice sin decirlo — sin
 * ella se leerían como un bloque y el ojo buscaría el total que no está. */
.carga-fila + .carga-fila {
  margin-top: var(--space-3);
  padding-top: var(--space-3);
  border-top: 1px solid var(--border);
}
.carga-fila__rotulo { margin: 0 0 var(--space-1); font-weight: 600; }

/* Con tres tarjetas en vez de seis, caben más anchas: el mínimo sube para que
   las dos filas de cada una respiren y no se parta el rótulo del tributo. */
.rejilla-carga--grupos { grid-template-columns: repeat(auto-fit, minmax(min(var(--ancho-minimo-widget), 100%), 1fr)); }

/* La cifra NO se encoge para caber. Si no cabe, se desplaza dentro de su caja:
   una cifra ilegible no es una cifra. */
.cifra-carga {
  /* Era `--fs-monto`: la segunda definición de la misma clase, con otro
     tamaño que la primera. */
  font-size: var(--fs-cifra-widget);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  overflow-x: auto;
  white-space: nowrap;
}
.cifra-carga--vacia { color: var(--text-secondary); font-weight: 400; }

/* El borde acompaña al rótulo escrito; NO es la señal. Quien no distinga el color
   lee «Falta el dato» en palabras, que es lo que manda. */
.card--interna[data-estado="falta"],
.card--interna[data-estado="parcial"] { border-left: 4px solid var(--warning); }
.card--interna[data-estado="no_aplica"] { border-left: 4px solid var(--border-strong); }

.lista-falta { margin: 0; padding-left: 1.2em; }
.lista-falta li { font-size: var(--fs-caption); color: var(--text-secondary); }
.caption--origen { border-top: 1px solid var(--border); padding-top: var(--space-2); }

/* ─── LA CIFRA DE LA CARGA FISCAL, MEDIDA A 375 px ───────────────────────────
 *
 * Medido el 14 de agosto de 2026 en el navegador, 48 combinaciones (tres anchos ×
 * cuatro escalas × cuatro temas). **Doce salieron con la cifra recortada**: las
 * tres escalas mayores a 375 px, en los cuatro temas. Y la recortada era
 * justamente «S/ 466,74», la de SIS y EsSalud.
 *
 * ─── Y LA CAUSA NO ERA LA FUENTE SOLA ──────────────────────────────────────
 *
 * A 375 px con la letra al máximo:
 *
 *     contenedor            375 px
 *     − relleno de sección   89 px  (44,6 × 2)
 *     − relleno de tarjeta   89 px  (44,6 × 2)
 *     ────────────────────────────
 *     para la cifra         103 px   ·  y necesita 349
 *
 * **178 de los 375 son relleno**, porque el relleno está en `rem` y crece con la
 * letra igual que el texto. La rejilla estaba bien —una sola columna, correcta—.
 *
 * ─── SE ARREGLA EN EL ORDEN DE LA DOCTRINA ─────────────────────────────────
 *
 * *Lo de largo libre cede; una cifra o una fecha, nunca.* Así que **primero cede
 * el relleno**, que es hueco, y solo cuando ya no queda hueco cede la cifra —y
 * **con suelo**, que es la condición—.
 *
 * `min()` y no una consulta de medio: el relleno deja de crecer cuando el ancho
 * disponible es poco, sea cual sea el motivo. Una consulta de medio miraría el
 * ancho de la ventana y este defecto lo produce el ancho del contenedor. */
.rejilla-carga { padding-inline: 0; }

/* Y la tarjeta que ENVUELVE la rejilla cede su relleno con la misma regla. A 375 px
   con la letra al máximo se comía otros 89 px —44,6 por lado—, y ese hueco es lo
   que le faltaba a la cifra. Solo esta sección: `.card` es compartida y estrechar
   el relleno de todas las tarjetas de Flow es otra faena, medida aparte. */
.card:has(> .rejilla-carga), .card:has(.rejilla-carga) { padding: min(var(--space-5, 1.5rem), 18px); }

.rejilla-carga > .card--interna {
  /* Tope de relleno: sigue creciendo con la letra, pero nunca más de lo que la
     caja se pueda permitir. 4,5vw a 375 px son 17 px por lado, no 44,6. */
  /* `vw` NO sirve aquí: mira la ventana, y este defecto lo produce el ancho de la
     TARJETA. Medido: con `4.5vw` en una ventana de 1785 px el tope no llegaba a
     morder nunca y la cifra seguía recortada igual. El tope va en píxeles, que es
     lo único que no depende de la letra ni de la ventana. */
  padding: min(var(--space-4), 18px);
  /* Y la tarjeta se declara contenedor, para que lo de dentro pueda medirse
     contra ELLA y no contra la ventana. */
  container-type: inline-size;
}

.cifra-carga {
  /* Y la cifra, con SUELO. `--fs-lg` es el suelo: por debajo no baja aunque la
     caja se estreche más, porque una cifra ilegible no es una cifra. El término
     de en medio la deja crecer con el ancho disponible; el techo es el tamaño de
     monto de siempre, que es lo que se ve en cuanto hay sitio. */
  /* `cqi` es el ancho de la tarjeta, no el de la ventana. El suelo es `--fs-lg`:
     por debajo no baja aunque la caja se estreche más, porque una cifra ilegible
     no es una cifra. El techo es el tamaño de monto de siempre. */
  /* El término de en medio se reparte entre los caracteres que hay: 190/9 son los
     21cqi que se calibraron para «S/ 466,74», y una cifra de once caracteres pide
     17,3 sola. `--largo` lo pone la vista; el 9 de reserva es por si falta. */
  /* Era un `clamp` con unidades de contenedor y daba **44,20 px** donde los
     demás widgets daban 63,75. La cifra principal de un widget no depende de
     lo ancha que sea su tarjeta: *primero crece la caja*. */
  font-size: var(--fs-cifra-widget);
}

/* ─── LA SEGUNDA CIFRA DE UNA TARJETA · MENOR POR JERARQUÍA, NO POR CABER ───
 *
 * «SIS y EsSalud» sabe dos cosas: lo declarado y lo que abona VIS. Van las dos,
 * y la segunda **es una lectura de la primera**, no otra magnitud — dos cifras
 * del mismo tamaño en la misma tarjeta compiten y ninguna manda.
 *
 * Esto NO es encoger una cifra para que quepa, que es lo que no se hace: es la
 * misma fórmula con el techo un escalón más abajo, **con el mismo suelo**. Por
 * debajo de `--fs-lg` no baja ninguna de las dos. */
.cifra-carga--segunda {
  font-size: clamp(var(--fs-lg), calc(150cqi / var(--largo, 9)), var(--fs-lg));
  font-weight: 600;
}

/* La fila del selector de periodo. `flex-wrap` para que a 375 px con la letra
   grande el botón caiga debajo en vez de estrechar el desplegable: lo de largo
   libre cede, y aquí lo que cede es la disposición, no el control. */
.fila-periodo {
  display: flex; flex-wrap: wrap; gap: var(--space-2); align-items: flex-end;
}
.fila-periodo .control { min-height: 44px; flex: 1 1 12rem; }
.fila-periodo .btn { min-height: 44px; min-width: 44px; }

/* ─── LOS LISTADOS FISCALES ──────────────────────────────────────────────────
 *
 * Ocho columnas con cuatro cifras. En escritorio e iPad —que es donde Kike los va
 * a mirar— caben; a 375 px no, y **eso ya no gobierna**: los listados largos no se
 * abren en móvil. Aun así no se rompe nada, porque la envoltura desplaza en
 * horizontal en vez de encoger las cifras.
 */
/* ─── `.nowrap` EXISTÍA EN NUEVE SITIOS Y NO PINTABA NADA ────────────────────
 *
 * La escribían las celdas de fecha y de número de nueve vistas —Facturación,
 * Retenciones, Declaraciones, Compras, Facturas— y **no estaba en ninguna hoja**:
 * `auditar_clases` la contaba entre las inventadas. *Una clase que no existe no
 * falla, no pinta*, así que esas fechas se podían partir por la mitad y nadie lo
 * habría llamado defecto.
 *
 * Y la regla de la casa es explícita: lo de largo libre cede; **una cifra o una
 * fecha, nunca**. Una regla de una línea, en el sitio donde ya viven las tablas
 * fiscales que la usan. */
.nowrap { white-space: nowrap; }

.tabla-fiscal { width: 100%; border-collapse: collapse; }
.tabla-fiscal th, .tabla-fiscal td { padding: var(--space-2) var(--space-3); text-align: left; }
.tabla-fiscal .num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.tabla-fiscal thead th { border-bottom: 2px solid var(--border-strong); }
.tabla-fiscal tbody tr + tr td { border-top: 1px solid var(--border); }

/* La cifra dentro de la tabla, con la misma regla que la de los widgets: el
   tamaño se reparte entre los caracteres de ESA cifra, con suelo. Sin ella, una
   de quince aprieta igual en un monitor ancho con la letra al máximo. */
.tabla-fiscal .cifra { font-size: clamp(var(--fs-caption), calc(90cqi / var(--largo, 9)), var(--fs-body)); }
.tabla-envoltura { overflow-x: auto; container-type: inline-size; }

/* ─── LOS TOTALES: SEPARADOS DE LA TABLA Y RESALTADOS ───────────────────────
   Una suma no es un registro. El espacio de arriba es lo que la separa; el fondo
   y el peso, lo que la resalta. Y NO solo el color: además va en negrita y con su
   nombre escrito debajo. */
.totales-fiscales tr:first-child th,
.totales-fiscales tr:first-child td {
  border-top: 3px solid var(--border-strong);
  padding-top: var(--space-4);
  background: var(--surface-2);
  font-weight: 700;
}
.totales-fiscales .pie-nombres td {
  padding-top: 0; font-size: var(--fs-caption); color: var(--text-secondary);
  background: var(--surface-2);
}

/* «no aplica» no es una cifra, así que no se lee como una: sin números tabulares
   y en el color secundario. Y NO se distingue solo por color — la palabra lo dice. */
.tabla-fiscal .celda-fuera { color: var(--text-secondary); font-style: italic; }
.tabla-fiscal .aviso-inline { font-weight: 400; font-style: italic; color: var(--text-secondary); }

/* ─── REVERTIDO EL 15 DE AGOSTO DE 2026 ──────────────────────────────────────
 *
 * Aquí se declaraba `container-type: inline-size` en `.stat`, `.tercios > *` y
 * `.bloque-periodo`, para que el `cqi` de `.stat-value` midiera su tarjeta.
 *
 * **Rompió tres pantallas de uso diario** —Movimientos, Facturas y Compras—: los
 * rótulos empezaron a envolver palabra por palabra y las cifras invirtieron su
 * importancia. Lo vio Kike, no el arnés.
 *
 * `container-type: inline-size` aplica `contain: layout style inline-size`, así
 * que **el elemento deja de dimensionarse por su contenido**. Reproducido en un
 * documento propio: con la declaración, dos tarjetas que medían 604 y 660 px por
 * su contenido pasan las dos a 632 — el reparto deja de mirar lo que hay dentro.
 * Y donde había un `min-inline-size: max-content` —lo hay en `.bloque-periodo >
 * .stat-value`— eso se queda sin nada contra lo que resolver.
 *
 * **No se revierte por diagnóstico completo, sino por no poder medirlo.** La
 * sesión de Flow no está disponible para el arnés, así que no se pudo comprobar
 * en las pantallas reales, y *un cambio sin medir es un cambio sin medir*. Tres
 * pantallas de uso diario no se dejan rotas esperando a poder medir.
 *
 * Vuelve a plantearse con la medición hecha: la idea —repartir el tamaño entre
 * los caracteres— sigue siendo buena, y `--cifra-caracteres` ya está en el DOM. */

/* ═══ LA BOTONERA, A LA DERECHA · `§17.5` CAMBIADA · 16/08/2026 ═══════════════
 *
 * **Kike cambia la convención**, con sus palabras: *«Alinear la botonera a la
 * derecha del formulario, colocando el botón de acción principal (Grabar) al
 * extremo derecho y el botón secundario (Cancelar) a su izquierda.»*
 *
 * Contradice la §17.5 anterior —«guardar antes que cancelar, siempre»— con la
 * que ya se habían uniformado cuatro pantallas. **No es una excepción: es un
 * cambio de la regla**, y va entero o no va: *media convención cambiada es peor
 * que ninguna*, porque quien aprende una recibe la otra en la pantalla
 * siguiente — que es justo el defecto que §17.5 nació para cerrar.
 *
 * Y tiene razón de ser: **es el orden de macOS**, que es lo que Kike usa a
 * diario y por tanto el que ya tiene en la cabeza.
 *
 * ─── EL ORDEN DEL DOM ES EL ORDEN VISUAL, A PROPÓSITO ──────────────────────
 *
 * Cancelar se escribe **primero en el marcado** y Grabar después. Así el
 * tabulador los recorre izquierda→derecha igual que se leen: *si el orden
 * visual y el del tabulador no coinciden, quien tabula pulsa el que no ve*. No
 * se usa `order` de flex, que los desalinearía.
 *
 * Los diálogos ya iban a la derecha —`.dlg-pie`—; lo que faltaba era el pie del
 * formulario. Se marca con su propia clase para que se lea en el marcado qué es
 * una botonera y qué es una fila cualquiera. */
.botonera {
  display: flex; align-items: center; flex-wrap: wrap;
  gap: var(--space-3);
  justify-content: flex-end;
}
/* ─── EN ESTRECHO ENVUELVE, Y NO SE INVIERTE ────────────────────────────────
 *
 * La primera versión apilaba con `column-reverse` para dejar Grabar arriba. **Eso
 * era inventarse un orden que Kike no pidió**: él habló del eje horizontal —«al
 * extremo derecho»— y nada del apilado.
 *
 * Envolviendo, el orden se mantiene: Cancelar primero, Grabar después, que es el
 * mismo que se lee y el mismo que recorre el tabulador. Y **no se encoge ningún
 * control**: siguen midiendo sus 44 px, medido a 380 px con la letra al máximo. */
@media (max-width: 29.999em) {
  .botonera { align-items: stretch; }
  .botonera > .btn { flex: 1 1 auto; }
}



/* ═══════════════════════════════════════════════════════════════════════════
   EL TABLERO DE TRIBUTOS · los tres widgets de Kike · 16 de agosto de 2026
   ═══════════════════════════════════════════════════════════════════════════

   Casi todo lo hereda de `.tercios--tres` y `.card.stat`, que ya usan otros
   seis módulos. Lo único propio es el área táctil de los enlaces que van
   DENTRO de una lista plegada.

   ─── POR QUÉ HACE FALTA ESTA REGLA ──────────────────────────────────────

   **Medido a 380 px y escala máxima: 35 px de alto**, contra los 44 que exige
   la accesibilidad de Flow. Un enlace dentro de un `<li>` de `.caption` hereda
   la altura de línea del párrafo, y ahí ni `.btn` ni `.enlace-fila` llegan.

   Va sobre el enlace y no sobre el `<li>`: lo que se toca es el enlace, y
   agrandar la fila entera dejaría zona muerta alrededor que parece tocable. */
#tributos-tablero .desplegable-compacto li > a {
  display: inline-flex; align-items: center;
  min-height: var(--tactil-minimo);
}

/* ═══════════════════════════════════════════════════════════════════════════
   LA DIAGRAMACIÓN DE LOS TRES WIDGETS · bocetos de Kike · 16 de agosto de 2026
   ═══════════════════════════════════════════════════════════════════════════

   Los bocetos venían en claro y con otra tipografía **porque eran un dibujo**.
   Lo que se toma de ellos es la ESTRUCTURA: qué va grande, qué es rótulo y qué
   es cifra. **Ni un color ni una fuente ni un token salen de ahí** — todo lo
   visual sigue siendo el de Flow, y por eso aquí no hay ni un valor de color
   escrito a mano.

   Dos cifras grandes por widget, apiladas y centradas, con sus rótulos encima y
   las dos líneas de datos en medio. */
.tablero-widget { align-items: center; text-align: center; gap: var(--space-2); }

/* El rótulo: un nombre corto, no una frase. Va pegado a su cifra —encima— y por
   eso el hueco de abajo es menor que el de arriba. */
/* ─── TODOS LOS RÓTULOS MIDEN LO MISMO, LLEVEN ⓘ O NO ──────────────────
 Medido el 16 de agosto de 2026, después de darle su rótulo a «Lo urgente»:
 la fila de arriba quedó alineada —318, 318, 318— pero **la de abajo no**:
 560, 560 y 546. Catorce píxeles.
 La causa no era la cifra: **«Por cobrar ⓘ» mide 44 px porque el botón de la
 ⓘ tiene el área táctil de Flow, y «vence el 21/08/2026», que no lleva ⓘ,
 mide 26**. El rótulo más bajo subía su cifra.
 El suelo es el mismo `--tactil-minimo`, así que un rótulo con ⓘ y otro sin
 ella ocupan lo mismo. **No cambia la diagramación**: no añade ni quita nada,
 iguala la caja — *lo que se iguala es la caja, no el número*. */
.tablero-rotulo {
  margin: var(--space-3) 0 0; min-block-size: var(--tactil-minimo);
  font-size: var(--fs-caption);
  color: var(--text-secondary); font-weight: 600;
  display: inline-flex; align-items: center; gap: var(--space-micro);
  justify-content: center; flex-wrap: wrap;
}
.tablero-widget > .neto-cifra { margin-block-start: 0; }

/* ─── EL RÓTULO DE LA SEGUNDA CIFRA TAMBIÉN BAJA DE PESO · 28/08/2026 ──────
 *
 * Kike: *«cada widget tiene dos cifras grandes con el mismo peso. Decir cuál
 * manda y bajar la otra a segundo peso, como en Money»*.
 *
 * ─── LO QUE LA MEDIDA DIJO, Y NO ERA LO ESPERADO ─────────────────────────
 *
 * **Las cifras ya no pesaban igual**: medidas en los dos widgets, 63,8 px la
 * primera y 38,3 la segunda —`neto-cifra--segunda` lleva desde el 18/08 al
 * 60 %—. Lo que sí pesaba igual eran **los rótulos**: los dos a 17 px y peso
 * 600. Y el rótulo es lo que anuncia; con dos anuncios idénticos el widget
 * dice que tiene dos titulares aunque los números ya digan que no.
 *
 * ─── MANDA «DISPONIBLE», Y NO ES UNA OPINIÓN NUEVA ───────────────────────
 *
 * Manda el disponible en cuenta: es **dinero que está**, y la otra es una
 * previsión —lo dice su propia marca debajo, «previsión», y lo dice su ⓘ:
 * *«no dinero que esté»*—. Además el código ya lo daba por decidido: a la
 * segunda cifra se le pasa `--cifra-principal` **sacado del saldo**, o sea que
 * se dimensiona respecto de él. Esto no elige un mandante nuevo: **termina de
 * decir en el rótulo el que ya mandaba en la cifra**.
 *
 * Baja de peso, no de tamaño: el suelo de Flow es `--fs-caption` y el rótulo
 * ya está en él. *Nada se encoge para cuadrar* — lo que cede es el trazo. */
.tablero-rotulo--segundo { font-weight: 400; }

/* Las dos líneas de datos del boceto: rótulo a la izquierda, cifra a la derecha,
   una por renglón. Es una lista de definición porque eso es: pares nombre-valor,
   y así el lector los lee emparejados en vez de como seis textos sueltos. */
/* ─── Y SE APILAN CUANDO DEJAN DE CABER · medido ────────────────────────
 Con `1fr auto` fijas, a 380 px y escala muy grande el rótulo se quedaba en
 154 px y **«Usado para pagos SUNAT» caía en tres y cuatro renglones, uno por
 palabra** — 16 casos de 16 en las dos escalas mayores. *Un texto que envuelve
 palabra por palabra cabe perfectamente.*
 `auto-fit` con un mínimo en `ch` colapsa a una columna en cuanto los dos
 nombres no caben, y **el mínimo crece con la letra** porque `ch` es el ancho
 de un carácter de esta tipografía: se apila justo cuando hace falta y no
 antes. Un valor en píxeles se habría quedado corto a escala máxima. */
.tablero-datos {
  margin: var(--space-2) 0 0; width: 100%;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(15ch, 100%), auto));
  gap: var(--space-micro) var(--space-3);
  font-size: var(--fs-caption); text-align: start;
}
/* ─── LA ⓘ SE QUEDA CON SU RÓTULO · `D-3xx` ────────────────────────────────
 *
 * Kike, sobre la captura del mínimo: *«debajo de "Crédito Cuota BCP Amex" hay
 * una ⓘ sin rótulo al lado»*. No sobraba y no era de otra fila: **era la de esa
 * misma línea**, caída a un renglón propio. Medido el 18/08/2026 a 1425 px:
 *
 *     columna de la rejilla   200 px
 *     el rótulo ocupa         190 px  (de x=449 a x=639)
 *     la ⓘ mide                44 px  · el mínimo táctil, y no cede
 *     → no cabía detrás, y el navegador la bajaba: texto en y=1626, ⓘ en y=1649
 *
 * De las cinco filas del tablero era la única a la que le pasaba, así que se leía
 * como un resto de algo. Con `flex` el rótulo cede —envuelve dentro de lo que le
 * queda— y la ⓘ se queda **a su lado**, que es de donde no se puede separar: una
 * explicación que no toca a su dato explica otra cosa.
 *
 * `align-items: start` la deja a la altura del primer renglón, no centrada
 * respecto de dos. Y el rótulo lleva `min-inline-size: 0` para poder encoger:
 * sin eso, el mínimo automático de un elemento flexible es su contenido y
 * volvería a empujar. */
.tablero-datos dt {
  color: var(--text-secondary);
  display: flex; align-items: start; gap: var(--space-1);
  /* `nowrap` es lo que resuelve el caso: con `wrap`, el navegador prefiere bajar
     la ⓘ entera antes que partir el rótulo, que es justo el defecto. Sin envolver,
     el rótulo se queda con el hueco que sobra —200 menos los 44 de la ⓘ— y
     envuelve DENTRO de él, con la ⓘ quieta a su lado. */
  flex-wrap: nowrap; min-inline-size: 0;
}
.tablero-datos dt > .pista-boton { flex: 0 0 auto; }
.tablero-datos dd { margin: 0; text-align: end; font-variant-numeric: tabular-nums; }

/* ─── LAS PAREJAS, EN SU CAJA · Kike, 20/08/2026 ─────────────────────────
 *
 * *«Las dos columnas se leen como una sola frase corrida.»* Medido en la
 * pantalla que mandó: cuatro cajas en la misma fila —DT 94→311, DD 324→484,
 * DT 497→714, DD 726→887— con **13 px entre el nombre y su cifra y los mismos
 * 13 px entre esa cifra y el nombre siguiente**. Con el mismo hueco dentro y
 * fuera de la pareja, no hay nada que diga dónde acaba una y empieza otra.
 *
 * Se arregla en la estructura y no con un hueco mayor: cada pareja va en su
 * `.tablero-par`, así que **el navegador ya no puede partirlas** ni mezclar el
 * final de una con el principio de la siguiente. Entre columnas va un pelo
 * vertical —el mismo de `.acciones-raya`— y el hueco crece a `--space-5`.
 *
 * Dos columnas fijas y no `auto-fit`: con `auto-fit` no se puede saber cuál
 * es la segunda, y el pelo tiene que ir exactamente ahí. Por debajo de 34ch
 * de tarjeta pasa a una, y el pelo desaparece con ella. */
/* ─── Y NO SON DOS COLUMNAS: NO CABEN · medido el 20/08/2026 ─────────────
 *
 * Kike preguntó si poner un separador entre las dos columnas. Al medirlo
 * salió que el separador no era la respuesta: **las dos columnas nunca
 * cupieron**. Una pareja entera —nombre sin recortar + ⓘ + estado + cifra—
 * pide, en una tarjeta de 846 px:
 *
 *     normal      478 px  ·  dos piden  956  de  846
 *     grande      575 px  ·  dos piden 1150  de  838
 *     muy grande  679 px  ·  dos piden 1358  de  829
 *     máximo      786 px  ·  dos piden 1572  de  821
 *
 * Cabían porque el nombre se recortaba: «Crédito Cuota BCP Amex» pedía 190 px
 * y tenía 96 en escala normal, y **0 px a muy grande y a máximo** — el nombre
 * desaparecía entero. Eso no es ceder, es borrarse.
 *
 * Una pareja por fila, entonces. El nombre se lleva todo el hueco que sobra,
 * la cifra queda al final, y el pelo pasa a ser **horizontal**: separa filas,
 * que es lo que hay que distinguir cuando solo hay una columna. */
.tablero-datos--pares {
  grid-template-columns: minmax(0, 1fr);
  column-gap: var(--space-5); row-gap: 0;
}
.tablero-par {
  display: grid; grid-template-columns: minmax(0, 1fr) auto;
  /* El hueco entre el nombre y la cifra, ancho a propósito: es lo que dice
     que son dos cosas y no una frase. */
  column-gap: var(--space-5); padding-block: var(--space-mini);
  /* `baseline`: lo que se alinea es el texto, no la caja. Con `start`, una
     cifra grande y un rótulo pequeño quedan pegados por arriba y se leen
     torcidos; con la línea base se leen como una fila. */
  align-items: baseline;
}
/* El pelo entre filas, con `--border-strong` y no con `--border`: medido contra
   el fondo de la tarjeta, el de `--border` no se distingue. Un separador que no
   se ve no separa, y aquí es la pieza que arregla la frase corrida. */
.tablero-datos--pares > .tablero-par + .tablero-par {
  border-block-start: 1px solid var(--border-strong);
}
[data-contrast="high"] .tablero-datos--pares > .tablero-par + .tablero-par {
  border-block-start-width: 2px;
}
/* ─── EL NOMBRE ES LA ÚNICA PIEZA QUE CEDE ────────────────────────────────
   `min-inline-size: 0` es lo que le permite encogerse dentro de la fila flex;
   sin eso el contenido mínimo manda y el nombre empuja a la cifra. Y cede
   recortándose con puntos suspensivos, no envolviendo: envolver es lo que
   desalineaba las filas entre sí. El nombre entero sigue en el DOM —el lector
   lo lee completo— y en `title` para el ratón. */
.tablero-datos dt > .par-nombre {
  min-inline-size: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* El estado no cede: lleva una cifra dentro. */
.tablero-datos dt > .par-estado { flex: 0 0 auto; display: inline-flex; align-items: center; gap: var(--space-micro); }
.par-estado .ico { inline-size: 1em; block-size: 1em; }
.par-estado--ok .ico    { color: var(--income); }
.par-estado--falta .ico { color: var(--warning); }
/* ─── Y LA ⓘ SE ALINEA CON LA PRIMERA LÍNEA DEL RÓTULO ────────────────────
 *
 * Kike: *«con un nombre de tres renglones la ⓘ queda a media altura»*. La
 * causa es que el botón mide 44 px de alto —el mínimo táctil— y su dibujo se
 * centra en esos 44: al lado de una línea de 21 px queda 11 px más abajo.
 *
 * Se separa **lo que mide de lo que se pulsa**: la caja pasa a medir una línea
 * y los 44 px se recuperan con un pseudoelemento centrado, que no ocupa sitio
 * en la maquetación. Es la misma técnica que ya usa `.acciones-fila .js-mover`,
 * y §8.5 se sigue cumpliendo: el objetivo táctil no baja de 44. */
.tablero-datos dt > .pista-boton {
  block-size: 1.25rem; inline-size: 1.25rem; position: relative;
  align-self: start; margin-block-start: .1em;
}
.tablero-datos dt > .pista-boton::after {
  content: ''; position: absolute; inset: 50% auto auto 50%;
  inline-size: var(--tactil-minimo); block-size: var(--tactil-minimo);
  transform: translate(-50%, -50%);
}

@container (max-width: 30ch) {
  /* Ya apilados: la cifra al principio, como su rótulo. */
  .tablero-datos dd { text-align: start; }
}

/* «previsión» · la palabra que no se pliega. Va debajo de su cifra, y NO se
   dice solo con color: es la palabra entera, en negrita, con su propio renglón. */
.tablero-marca {
  margin: 0; font-size: var(--fs-caption); color: var(--text-secondary);
  text-transform: lowercase;
  /* ─── LA CAJA MIDE LO MISMO ESTÉ LLENA O VACÍA ──────────────────────────
     «Lo urgente» lleva su marca entre la cifra y los datos; las otras dos no
     llevan nada ahí, y por eso su segunda cifra caía a distinta altura. Con el
     suelo puesto aquí, la marca escrita y el hueco reservado miden igual, así
     que la segunda cifra cae a la misma altura en las tres.
     Es proporcional y no un píxel fijo: sale de `--fs-caption`, que crece con
     las cuatro escalas. Y no cambia la diagramación —no añade ni quita nada—:
     *lo que se iguala es la caja, no el número*. */
  min-block-size: calc(var(--fs-caption) * 1.5);
}

/* El hueco no tiene texto: existe solo para sostener la fila, así que se le
   quita de la vista del lector de pantalla —`aria-hidden` en la vista— y no
   arrastra ningún margen propio. */
.tablero-marca--hueco { padding: 0; }

/* «falta el dato» ocupa el sitio de la cifra, no un renglón debajo: *un cero
   donde falta un dato es una mentira que se lee como una cifra*, y un hueco
   vacío se lee como que aún no cargó. */
.tablero-falta { color: var(--text-secondary); font-style: normal; }

/* Lo vencido lleva LA PALABRA —«vencida hace N días» va dentro de la propia
 cifra grande— y el color viene detrás. Nunca al revés. Usa `--warning-ink`,
 que es el token que ya pinta `.aviso--alerta` y está comprobado en las cuatro
 combinaciones; inventar uno nuevo aquí sería un color sin auditar. */
.tablero-vencida { color: var(--warning-ink); }

/* ─── LA ⓘ ────────────────────────────────────────────────────────────────
   Un botón de verdad: entra en el tabulador solo y el lector dice que lo es.
   El área táctil es la de Flow, no una menor por ser pequeño el glifo. */
/* ─── Y NO SE ESTRUJA · 21/08/2026 ────────────────────────────────────────
 *
 * `inline-size` es una preferencia, no un suelo: dentro de un contenedor flex
 * apretado —una celda de tabla con su rótulo al lado— el navegador la encoge.
 * Medido el 21/08 en «Gastos fijos», con la ⓘ del aviso metida en su celda:
 * **44 px en normal y grande, 32 en muy grande y 37 en máxima**. Las de los
 * widgets, con sitio de sobra, se quedaban en 44 y no lo delataban.
 *
 * `min-inline-size` pone el suelo y `flex-shrink: 0` impide que la caja ceda:
 * es la misma pareja que ya resolvió `.acciones-fila`. *El área táctil mínima no
 * es una preferencia.* */
.pista-boton {
  display: inline-flex; align-items: center; justify-content: center;
  inline-size: var(--tactil-minimo); block-size: var(--tactil-minimo);
  min-inline-size: var(--tactil-minimo); min-block-size: var(--tactil-minimo);
  flex: 0 0 auto;
  padding: 0; border: 0; background: none; cursor: help;
  color: var(--text-secondary); font: inherit; line-height: 1;
}
.pista-boton > span {
  display: inline-flex; align-items: center; justify-content: center;
  inline-size: 1.25rem; block-size: 1.25rem; border-radius: var(--radius-pill);
  border: 1px solid currentColor; font-size: var(--fs-caption); font-weight: 700;
  font-style: italic;
}
.pista-boton:hover, .pista-boton:focus-visible { color: var(--text); }

/* ─── LA ⓘ DENTRO DE UNA FILA · «Pagada» y su marca, juntas ────────────────
 *
 * Kike, 31 de agosto de 2026: la insignia «Pagada sin movimiento» tenía que
 * volverse discreta. Lo que se ve ahora es «Pagada» y una ⓘ al lado, y esa ⓘ
 * es lo único que la distingue de una compra pagada normal, así que **no puede
 * separarse de su rótulo**: una ⓘ caída al renglón de abajo, o en la fila
 * siguiente, es una marca puesta sobre la compra equivocada.
 *
 * `nowrap` las mantiene juntas. `align-items: center` las alinea por el medio,
 * que es donde el ojo las une; sin él la ⓘ —44 px— y la insignia —32— quedaban
 * apoyadas en la línea base y la marca parecía colgar.
 *
 * Y `margin-block: -6px`: el botón mide 44 px y la fila de esta tabla mide 46
 * en su forma corta. Sin recoger ese margen, las cinco filas con marca
 * crecerían y volverían a ser *las filas raras*, que es justo lo que se venía
 * a quitar. El área que se puede pulsar **no se toca**: sigue siendo de 44 px,
 * solo se le pide al renglón que no la cuente entera para calcular su alto. */
.pago-sin-mov {
  display: inline-flex; align-items: center; gap: var(--space-micro);
  white-space: nowrap;
}
.pago-sin-mov > .pista-boton { margin-block: -6px; }
/* ─── Y EL GLOBO NO HEREDA EL `nowrap` · medido, no razonado ───────────────
 *
 * `Pista::boton()` devuelve el botón **y su globo** como hermanos, así que el
 * globo cae dentro de esta envoltura y heredaba su `white-space: nowrap`. Visto
 * en la captura antes que en el código: la explicación salía en **una sola
 * línea de 1.251 px dentro de una caja de 508** y se leía «vale para el crédito
 * fisc…». Dos tercios del texto, cortados.
 *
 * El `nowrap` es para el rótulo y su marca, que tienen que ir juntos; el globo
 * es texto corrido y se parte donde toque. */
.pago-sin-mov > .pista-globo { white-space: normal; }
/* El globo. `position: fixed` porque `pista.js` lo coloca contra la ventana:
   dentro de una tarjeta con `overflow` propio quedaría recortado, y una
   explicación a medias es peor que ninguna. */
/* ─── EL GLOBO SE TOPA EN ALTO, Y SE DESPLAZA ─────────────────────────
   Medido a 380 px: sin tope, **40 globos de 64 combinaciones se salían de la
   ventana** y 8 tapaban la cifra que venían a explicar. El texto es largo y en
   una caja estrecha crece hacia abajo hasta no caber en ninguna parte.
   Con el tope, `pista.js` siempre encuentra sitio encima o debajo del bloque, y
   lo que sobra se lee desplazando — *lo de largo libre cede; una cifra, nunca*. */
.pista-globo {
  position: fixed; z-index: 60; max-inline-size: min(30rem, calc(100vw - 2rem));
  max-block-size: 45vh; overflow-y: auto; overscroll-behavior: contain;
  padding: var(--space-3); border-radius: var(--radius-md);
  background: var(--surface-2); color: var(--text);
  border: 1px solid var(--border); box-shadow: var(--shadow-2);
  font-size: var(--fs-caption); text-align: start; font-weight: 400;
  text-transform: none;
}
.pista-globo[hidden] { display: none; }

/* ═══════════════════════════════════════════════════════════════════════════
   LOS AJUSTES DE KIKE AL TABLERO · 16 de agosto de 2026
   ═══════════════════════════════════════════════════════════════════════════ */

/* ─── EL «DÓNDE» DEL TÍTULO ────────────────────────────────────────────────
   «Detracciones · en el BN» e «IGV · en tus cuentas». El nombre pesa y el sitio
   acompaña, así que el sitio va más ligero — pero **no más pequeño que el piso
   duro de §8.5**: se distingue por color, no por tamaño. */
.titulo-donde { color: var(--text-secondary); font-weight: var(--fw-regular, 400); }

/* ─── LA SEGUNDA CIFRA, AL 60 % DE LA PRIMERA ──────────────────────────────
 *
 * Kike: *«los montos de la segunda línea del widget 1 y 2 deben ser de menor
 * tamaño, al menos el 60 % del tamaño del monto principal.»* Hoy salían iguales
 * —59 y 59, 64 y 64—.
 *
 * ─── EN PROPORCIÓN, NUNCA EN PÍXELES ─────────────────────────────────────
 *
 * *Un tamaño absoluto ignora la escala, y Kike tiene baja visión.* Así que se
 * repite **la misma fórmula que dimensiona la cifra grande** y se multiplica por
 * 0,6: crece y decrece con las cuatro escalas exactamente igual que ella.
 *
 * ─── Y EL 60 % ES DE LA DE ARRIBA, NO DE SÍ MISMA ────────────────────────
 *
 * Cada cifra se dimensiona con SUS caracteres, así que una segunda más corta
 * saldría **más grande** que su principal si se calculara sola. Por eso la vista
 * le pasa `--cifra-principal`: cuántos mide la de encima.
 *
 * El segundo `min()` es su propio techo de caja: si con el 60 % aún no cupiera
 * en el ancho, cede — *lo de largo libre cede; una cifra, nunca*, y aquí la
 * alternativa a ceder es desbordar.
 *
 * ─── CON SUELO ───────────────────────────────────────────────────────────
 *
 * `--fs-caption` es el piso duro de Flow (§8.5). Al 60 % de la escala más
 * pequeña la cifra sigue por encima de él, y si algún día no lo estuviera, el
 * suelo manda: **una cifra nunca se encoge hasta dejar de leerse**. */
.card.stat .neto-cifra--segunda {
  font-size: max(var(--fs-caption),
             min(
               calc(0.6 * min(var(--fs-display),
                              calc(100cqi / (var(--cifra-principal, 11) * 0.52)))),
               calc(100cqi / (var(--cifra-caracteres, 11) * 0.52))
             ));
}

/* ─── SUBGRID NO SE PUEDE AQUÍ, Y ESTÁ MEDIDO ──────────────────────────────
 *
 * La primera versión repartía las filas con `subgrid`. **Chrome lo descarta**:
 * `.card.stat` declara `container-type: inline-size`, que trae contención de
 * disposición, y *un elemento con contención de disposición no puede ser
 * subgrid* — `grid-template-rows` computaba a `none` y las cifras seguían a
 * 4 px unas de otras. Es el mismo `container-type` que ya rompió tres pantallas
 * el 14 de agosto, y **no se quita**: de él depende que la cifra se ajuste al
 * ancho de su tarjeta con `100cqi`.
 *
 * ─── LO QUE SÍ ALINEA, SIN TOCAR NADA DE ESO ─────────────────────────────
 *
 * Las cifras se desalineaban porque **una era más pequeña que la otra** —59
 * contra 64— y su caja medía menos, empujando distinto lo de debajo. Así que se
 * le da a la caja el alto de la cifra **más grande posible**, que es
 * `--fs-display`: entonces las dos cajas miden lo mismo aunque el número de
 * dentro se haya encogido para caber.
 *
 * Es proporcional y no un píxel fijo: `--fs-display` crece con las cuatro
 * escalas, así que la alineación crece con ellas. Y **la cifra no se toca**:
 * lo que se iguala es la caja, no el número — *primero crece la caja*. */
#tributos-tablero .tablero-widget--saldo > .neto-cifra:not(.neto-cifra--segunda) {
  min-block-size: calc(var(--fs-display) * 1.1);
  display: flex; align-items: center;
}
#tributos-tablero .tablero-widget--saldo > .neto-cifra--segunda {
  min-block-size: calc(var(--fs-display) * 0.6 * 1.1);
  display: flex; align-items: center;
}
/* Y las dos líneas de datos, que son dos renglones en los dos widgets, ocupan
   el mismo alto por construcción. El tercero se queda como estaba. */
.tablero-widget { align-items: center; text-align: center; }
/* ─── LA TABLA DE IMPUESTOS POR PAGAR ──────────────────────────────────────
 *
 * Kike: *«debe aprovechar el espacio horizontal de la tarjeta, el botón
 * “registrar un pago” debe alinearse a la derecha de la tarjeta.»*
 *
 * `width: 100%` sobre la tabla y la última columna a la derecha. La envoltura
 * sigue teniendo su `overflow-x`, que es la válvula de §16: a anchos pequeños la
 * tabla se desplaza en vez de romper la pantalla. */
#tributos-vivos .tabla { width: 100%; }
#tributos-vivos .tabla th:last-child,
#tributos-vivos .tabla td:last-child { text-align: end; }
/* El botón no se parte en dos renglones aunque la columna se estreche: una
   acción cortada por la mitad no se lee como una acción. */
#tributos-vivos .tabla td:last-child .btn { white-space: nowrap; }

/* ─── Y LAS CIFRAS DE UNA MISMA FILA, A LA MISMA ALTURA ─────────────────────
 *
 * Kike: *«los 2 widgets deben tener los montos grandes alineados horizontalmente
 * para no romper armonía.»*
 *
 * Medido antes de tocar: a escala normal la primera cifra caía a 122 px del
 * borde en los dos primeros —ya coincidían— pero **la segunda a 333 y 337**,
 * porque la cifra de arriba del segundo widget es más grande (64 contra 59) y
 * empuja todo lo que viene detrás. *Cuatro píxeles bastan para que dos cifras
 * dejen de leerse como una fila.*
 *
 * ─── POR QUÉ `subgrid` Y NO ALTURAS A MANO ─────────────────────────────────
 *
 * Cada tarjeta era una rejilla independiente, así que cada fila medía lo de SU
 * contenido. Con `subgrid` las seis filas las reparte **el contenedor**, y las
 * tarjetas de una misma línea comparten el reparto: lo que alinea a 17 px sigue
 * alineado a 52, porque no depende de que los rótulos midan lo mismo.
 *
 * Va sobre el identificador y no sobre `.tercios--tres`, que la usan otros seis
 * módulos: una clase compartida no se cambia para arreglar una pantalla. */

/* ═══════════════════════════════════════════════════════════════════════════
   LAS CAPAS DE VENTANAS · Kike, 16 de agosto de 2026
   ═══════════════════════════════════════════════════════════════════════════

   *«Abre ventana grande, otra ventana un poquito más pequeña.»* Se apilan, y
   **cada nivel es más estrecho que el de debajo** para que se vea el borde del
   anterior y se entienda que hay algo detrás.

   ─── SOLO EL ANCHO · Kike lo decidió así ─────────────────────────────────

   **El alto se queda entero**, que es lo que deja sitio a los formularios
   largos. El borde se ve a los lados.

   ─── EL 90 %, Y POR QUÉ NO EL 85 % ───────────────────────────────────────

   Con un módulo de 1.280 px: 1.152 · 1.037 · 933 · 840, y **64 px de borde a
   cada lado en el primer nivel** — visible de sobra. Al 85 %, acumulando cuatro
   capas la última cae a 668 px, la mitad del módulo, y ahí **no cabe un
   formulario de compra con siete controles**.

   ─── CON SUELO, Y NO ES OPCIONAL ─────────────────────────────────────────

   *Reducir es el último recurso y con suelo* — también el ancho de una ventana.
   `--capa-suelo` es el ancho por debajo del cual un formulario de los que hay
   deja de caber: se mide, no se elige a ojo, y su cifra está en `D-143`.

   Sin él, con la letra al máximo la cuarta capa desbordaría por lo mismo que ya
   pasó tres veces: *un suelo que crece con el texto en un hueco que no crece*. */
/* ─── EL ANCHO ES PROPORCIÓN, NO PÍXELES · Kike, 16 de agosto de 2026 ──────
 *
 * *«Mira estas ventanas, no están ocupando el ancho acordado, las veo más
 * pequeñas.»* **Tenía razón, y la causa estaba en mi propio informe**: «capa 1 ·
 * 952 px». Eso no era el 90 % de nada — era `--form-max + 4rem`, **un ancho
 * fijo**, y un ancho fijo *acierta en la pantalla donde se midió y falla en las
 * demás*, igual que un tamaño de letra en píxeles.
 *
 *     módulo de 1.280 px   952 px  →  74 %
 *     pantalla de Kike     952 px  →  55 %
 *
 * Cuanto más grande su pantalla, **más pequeña** se veía la ventana.
 *
 * ─── EL ANCHO ÚTIL DEL MÓDULO, REPLICADO DE DONDE SALE ───────────────────
 *
 * `.app-shell` es `min(100vw, --maxw-app)` menos su relleno a los dos lados. Se
 * repite esa expresión y no un número: si el shell cambia, esto cambia con él.
 * *Dos copias de la misma regla acaban discrepando*, así que se copia la
 * fórmula, no el resultado.
 *
 * El tope de `95vw` se queda: en una ventana estrecha el 90 % del módulo puede
 * pasarse del hueco real. */
.dlg-editar[open] {
  --capa-util: calc(min(100vw, var(--maxw-app)) - 2 * var(--space-5));
  --capa-factor: 0.9;
  width: max(var(--capa-suelo),
             min(calc(var(--capa-util) * var(--capa-factor)), 95vw));
}
.dlg-editar[data-capa="2"] { --capa-factor: 0.81; }
.dlg-editar[data-capa="3"] { --capa-factor: 0.729; }
.dlg-editar[data-capa="4"] { --capa-factor: 0.6561; }

/* ─── Y EL BORDE DE LA DE ATRÁS NO ES LA ÚNICA SEÑAL ───────────────────────
   En alto contraste el matiz desaparece, así que lo que separa una capa de la
   de debajo es **el borde declarado**, que en ese modo se refuerza en vez de
   perderse. *El color no puede ser la única señal — tampoco el borde de una
   capa*: el escalón de ancho se ve aunque el borde no. */
.dlg-editar[data-capa] { border: 2px solid var(--border-strong); }

/* ─── A 380 px NO SE REDUCE ────────────────────────────────────────────────
   Reducir cada capa en una pantalla estrecha deja la de dentro sin sitio. En
   móvil van a ancho completo, todas.
   El umbral va en PÍXELES y no en `em`: en una consulta de medio, `em` se
   resuelve contra 16 px y no contra la raíz — ya costó 111 px de desborde. */
@media (max-width: 480px) {
  .dlg-editar[data-capa] { width: 95vw; }
}

/* ─── EL ALTO VA ENTERO, Y NO LO ESTABA · Kike, 16 de agosto de 2026 ────────
 *
 * Se decidió que **el alto se quedara entero** justamente para los formularios
 * largos, y en la captura de Kike la ventana **ocupaba media pantalla**. La
 * causa: `.dlg-ficha[open]` declara `max-height: 90dvh` pero **el alto real lo
 * pone el contenido** — un formulario corto deja la ventana pequeña y colgando.
 *
 * Ahora el alto es el acordado **siempre**, con el contenido desplazándose por
 * dentro cuando es más largo. Y **centrada**: `<dialog>` modal ya se centra por
 * su margen automático, pero con `height` fijo hay que decirlo, porque si no se
 * queda arriba.
 *
 * El cuerpo ya tenía `overflow: auto` y `min-height: 0`: sin la segunda, un hijo
 * flex no se deja encoger y el desplazamiento no aparece nunca. */
.dlg-editar[open] {
  height: 90dvh;
  max-height: 90dvh;
  margin: auto;
}
/* En móvil, casi todo: los márgenes de adorno quitan sitio donde no sobra. */
@media (max-width: 480px) {
  .dlg-editar[open] { height: 94dvh; max-height: 94dvh; }
}

/* ─── Y DEL TERCER NIVEL EN ADELANTE, TAMBIÉN EN VERTICAL · Kike, 16/08/2026 ─
 *
 * Kike, sobre sus tres capturas: **verticalmente los niveles empiezan y acaban a
 * la misma altura**, así que *lo que solo se distingue por los lados no se
 * distingue por arriba*. Del tercer nivel en adelante se recorta también el alto.
 *
 * **Fija y pequeña, no proporcional.** Proporcional sobre 810 px se comería 81 px
 * en la capa 3 y 154 en la 4, y el alto entero está puesto justamente para los
 * formularios largos. Con 20 px por nivel, en cuatro capas se pierden **40 px de
 * 810**, menos de los 100 que Kike puso como techo.
 *
 * **Por qué 20 y no menos.** La ventana va centrada (`margin: auto`), así que un
 * recorte de 20 px deja **10 px arriba y 10 abajo** de la capa de debajo: sus
 * 2 px de borde más 8 px de superficie. Menos de eso y la franja es el borde
 * solo — *y un borde a secas ya es la señal lateral, no una nueva*.
 *
 * **Los dos primeros niveles se quedan con el alto entero**, como se acordó. */
.dlg-editar[data-capa="3"] { --capa-recorte: 20px; }
.dlg-editar[data-capa="4"] { --capa-recorte: 40px; }
.dlg-editar[data-capa="3"][open],
.dlg-editar[data-capa="4"][open] {
  height:     calc(90dvh - var(--capa-recorte));
  max-height: calc(90dvh - var(--capa-recorte));
}
/* En móvil no se recorta: las capas ya van a ancho completo y el alto es lo
   único que queda para el formulario. */
@media (max-width: 480px) {
  .dlg-editar[data-capa="3"][open],
  .dlg-editar[data-capa="4"][open] { height: 94dvh; max-height: 94dvh; }
}

/* ─── Y EL FONDO SE DISTINGUE POR TRES COSAS, NO POR UNA ────────────────────
 *
 * Kike: en la captura *«las cifras del tablero se leen perfectamente detrás»*, y
 * con eso cuesta saber qué está activo — en una pantalla llena de números eso
 * pesa más que en otra.
 *
 * Tres señales, porque *el color no puede ser la única* y **un borde que
 * desaparece en un tema deja la capa sin marcar**:
 *
 *   1 · el fondo se oscurece detrás;
 *   2 · la ventana tiene su propio fondo opaco —`--surface`, no translúcido—;
 *   3 · y su borde, que en alto contraste se refuerza en vez de perderse.
 *
 * El velo se declara con un token propio y no con un negro suelto: en tema claro
 * un 60 % de negro apagaría la pantalla entera, y en alto contraste tiene que
 * subir, no bajar. */
.dlg-editar::backdrop { background: var(--velo-dialogo); }

/* ─── Y LOS CONTROLES DE DENTRO SUBEN CON ELLA · D-148 ─────────────────────
 *
 * `.combo-boton` se rellena con `--surface-2`, que es **el valor al que sube la
 * ventana en oscuro normal**. Sin esto el botón del combo se quedaría del mismo
 * color que la ventana y perdería su relleno — *subir una cosa rompiendo otra*.
 *
 * Conserva **el mismo escalón que tiene hoy** (1,11 sobre su fondo): no mejora
 * ni empeora, que es lo que se pretendía. Su caja la sigue dibujando el borde,
 * que es lo que de verdad la marca.
 *
 * `.combo-panel` flota por encima y se queda en `--surface`: **más oscuro que la
 * ventana**, con su borde a 3,74. Un panel que flota se distingue por quedar por
 * delante, y el borde lo cierra. */
.dlg-ficha .combo-boton { background: var(--surface-hover); }

/* ─── LOS CONTADORES DE DÍAS · cuatro tramos, y el color solo acompaña ──────
 *
 * Kike cerró los tramos el 16 de agosto de 2026: **11 o más blanco, 10 a 6
 * amarillo, 5 a 2 naranja, y 1 / hoy / vencido rojo**. Sin huecos y sin solapes;
 * quién cae en cuál lo decide `Flow\Core\Vencimiento::tramo()` **y solo él**.
 *
 * **El color no informa, refuerza.** La frase ya dice «En 5 días» o «Venció hace
 * 3 días», y son los dos rojos: *uno se puede pagar todavía y el otro ya cuesta
 * intereses*, y eso lo distingue el texto, no el tono. Por eso el contador va
 * además en negrita — **el peso es la segunda señal, y no depende del color**.
 *
 * ─── DE DÓNDE SALE CADA TONO · nada inventado ─────────────────────────────
 *
 *   holgado   `--text`         la tinta normal: no es un aviso, es lo corriente
 *   atento    `--warning-ink`  la tinta de aviso, pensada ya para texto
 *   aprieta   `--alerta-ink`  el naranja de aviso, con nombre propio desde que
 *                              Kike aprobó `D-153`. Antes era `--paleta-7`, un
 *                              tono de gráfico: *recalibrar las barras habría
 *                              cambiado el aviso sin que nadie lo pretendiera*.
 *   urgente   `--expense-ink`  la tinta de lo que resta, ya usada para deuda
 *
 * `holgado` no se declara con color propio a propósito: hereda la tinta del sitio
 * donde esté. *La mayoría de las filas están así, y pintarlas todas convierte el
 * color en fondo.* */
.contador-dias { font-weight: var(--fw-semibold); }
.contador-dias--atento  { color: var(--warning-ink); }
.contador-dias--aprieta { color: var(--alerta-ink); }
.contador-dias--urgente { color: var(--expense-ink); }

/* ─── LA RUTA DE LA CATEGORÍA · se recorta el contexto, no el dato ──────────
 *
 * Kike, 16 de agosto de 2026: donde decía «Terceros» tiene que decir
 * **«Servidores/Terceros»** — *una subcategoría repetida en dos categorías no
 * dice qué es sin su ruta*.
 *
 * **Y cuando no entra se recorta la categoría, nunca la subcategoría.** La
 * subcategoría es el dato y la categoría el contexto: «Serv…/Terceros» se
 * entiende y «Servidores/Terc…» no.
 *
 * Cómo se consigue: la ruta es una caja `flex` en la que **solo el padre puede
 * encogerse** (`min-width: 0` + `text-overflow`), mientras la barra y la hoja
 * declaran `flex: 0 0 auto`. Sin el `min-width: 0` un hijo flex no baja de su
 * contenido y **el que se partiría sería el de al lado** — la misma trampa que
 * ya costó los desbordes de la rejilla.
 *
 * Los puntos suspensivos los pone el navegador: *son la señal de que falta
 * texto*, y el `title` del enlace lleva la ruta entera para quien la necesite. */
.ruta-categoria { display: inline-flex; align-items: baseline; max-width: 100%; min-width: 0; }
/* Con `min-width: 0` a secas el padre **llegaba a 0 px y desaparecía**: medido,
   se recortaba bien hasta 150 px de caja y a 110 ya no quedaba nada de él. Un
   contexto que se va del todo no es un recorte, es una ausencia — *y los puntos
   son la señal de que falta texto*, así que tiene que quedar sitio para ellos.
   Con `3ch` siempre se lee al menos «Se…». */
.ruta-categoria__padre {
  min-width: 3ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* La barra y la hoja no ceden **mientras el padre pueda ceder**: son el dato.

   Pero «no ceder nunca» no era sostenible, y se midió: a 340 px con la letra al
   máximo **la hoja sola se salía 148 px de la pantalla**. Un dato que se sale no
   se lee mejor que uno recortado — se lee peor, porque además rompe la fila.

   `max-width: 100%` no la hace ceder antes: con `flex-shrink: 0` el padre sigue
   encogiéndose primero y entero. Solo cuando **la hoja sola no cabe** se recorta
   ella, con sus puntos. La prioridad se mantiene; lo que se quita es el
   absoluto. `D-167` */
.ruta-categoria__barra { flex: 0 0 auto; padding-inline: 0.1em; color: var(--text-muted); }
.ruta-categoria__hoja  {
  flex: 0 0 auto; max-width: 100%;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* ─── Y LA CADENA TIENE QUE DEJAR ENCOGER · medido, no supuesto ────────────
 *
 * La regla de arriba no se activaba: estrechando la celda hasta 90 px **no se
 * recortaba nada**. La causa está por encima — el enlace es `display: inline` y
 * el `span` intermedio lleva `min-width: auto`, así que **el tope nunca baja
 * hasta la ruta**. *Un hijo que no puede encoger hace que se parta el de al
 * lado*, y aquí ni eso: se salía sin más.
 *
 * Se abre el paso solo para la ruta, con `:has`, y no para todos los enlaces de
 * fila: cambiar el `display` de un enlace cualquiera movería filas que hoy están
 * bien. */
.celda-con-icono > span:has(.ruta-categoria) { min-width: 0; max-width: 100%; }
a:has(> .ruta-categoria) { display: block; min-width: 0; max-width: 100%; }

/* ─── LA EQUIS VA A LA DERECHA, EN TODAS · Kike, 17/08/2026 ────────────────
 *
 * Aquí vivía una regla que ponía la equis de las capas 2+ a la izquierda, para
 * romper la vecindad con la de la capa 1 —medida: 93 px entre centros y 35 de
 * hueco en escala máxima—.
 *
 * **La retiró su propio motivo.** Kike: *«no importa, porque son ventanas
 * diferentes, no hay forma de presionar la X del segundo nivel desde el
 * tercero»*. Y estaba medido en su día, dicho como si fuera otra cosa: la equis
 * de la capa 1 **no se puede enfocar ni pulsar mientras la 2 está abierta**,
 * porque el diálogo modal de encima atrapa el foco y los clics. Se anotó
 * entonces como «la gestión de foco funcionando, no un defecto», y nadie ató
 * las dos cosas.
 *
 * *Lo que no se puede pulsar no es un riesgo de clic*, así que los 35 px no eran
 * un peligro: eran una cifra sin consecuencia. **Una regla que se queda sin su
 * motivo se queda sin regla.**
 *
 * Todas a la derecha, que es la posición natural. `D-172` */

/* ─── LA FILA DEL WIDGET, UNA LÍNEA POR CONSTRUCCIÓN · Kike, 16/08/2026 ────
 *
 * Kike: *«la ruta de la categoría es tan larga que empujó el monto a la siguiente
 * línea»*. Medido en su ancho: de 9 barras, **una se partía** —«Servidores/Email &
 * Apps»—, con el monto 36 px más abajo. Y la etiqueta **no estaba recortada**:
 * `.barra-cabeza` llevaba `flex-wrap: wrap`, así que **cedía el monto bajando de
 * línea en vez de ceder la ruta**.
 *
 * *Un recorte no se activa si el texto puede envolver antes*: nunca llegaba al
 * ancho en que mordería, y el suelo de `3ch` de la ruta no servía de nada.
 *
 * Con `nowrap` la línea es una **por construcción y no por casualidad** —que es lo
 * que ya decía el comentario de esta caja— y el recorte tiene algo que hacer.
 * *Lo de largo libre cede; una cifra, nunca.* */
.barra-cabeza { flex-wrap: nowrap; }
/* Ni el monto ni el porcentaje se encogen para hacerle sitio a la ruta. */
.barra-cabeza > .barra-valores { flex: 0 0 auto; }
/* Y dentro de la etiqueta, la ruta ocupa lo que hay y cede por el padre. */
.barra-etiqueta > .ruta-categoria { max-width: 100%; }

/* ─── «CREAR UN CLIENTE NUEVO» EN ESTRECHO · Kike, 16/08/2026 · D-131 ───────
 *
 * Medido a 380 px, dentro de la ventana de Facturas abierta de verdad:
 *
 *     escala        ventana   ancho del texto   renglones
 *     normal         323 px       260 px            2
 *     grande         314          241               3
 *     muy_grande     305          221               3
 *     máximo         296          201               4
 *
 * **El texto no se toca** —acortarlo sería cambiar algo que Kike no pidió—, así
 * que se le da sitio y se le enseña a partir mejor:
 *
 *   · **ancho entero**: usaba 260 de 323 px, y los 63 restantes eran margen que
 *     en una ventana estrecha no hace falta;
 *   · **`text-wrap: balance`**: reparte 22 caracteres en líneas parejas en vez
 *     de dejar una palabra suelta al final.
 *
 * ─── Y ACOTADO, QUE ES LA LECCIÓN DE LA EQUIS ─────────────────────────────
 *
 * Va **por su identificador, dentro de una ventana y solo en estrecho**. Un
 * `.btn` a ancho entero en la consulta de móvil habría alcanzado a todos los
 * botones de todas las ventanas: *un arreglo aplicado más allá del defecto rompe
 * lo que estaba bien*, y eso ya costó la equis de la capa 1. */
@media (max-width: 480px) {
  /* El campo que lo contiene cede su relleno lateral: son los 24 px que le
     faltan en `grande` para bajar de tres renglones a dos. Va por `:has` con
     el identificador exacto, así que **ningún otro `.field` se entera**. */
  .dlg-editar .field:has(> #btn-nuevo-cliente) { padding-inline: 0; }
  .dlg-editar #btn-nuevo-cliente {
    width: 100%;
    text-wrap: balance;
    /* Su propio relleno lateral baja al del vertical: con el campo ya cedido
       quedaban 262 px y hacían falta 265 en `grande`. Se recorta el relleno,
       **no el texto**. */
    padding-inline: var(--space-2);
  }
}
/* ─── Y LO QUE NO SE PUEDE, DICHO CON SU CIFRA ─────────────────────────────
 *
 * El objetivo era **dos renglones en las cuatro**. Medido a 380 px:
 *
 *     escala      ventana  caja  botón  renglones  ancho para 2  faltan
 *     normal        323     280    260      2          260          0
 *     grande        314     265    241      3          265         24   ← cabe
 *     muy_grande    305     250    221      3          317         96   ← NO cabe
 *     máximo        296     234    201      4          369        168   ← NO cabe
 *
 * En las dos escalas mayores **el ancho que haría falta es mayor que la ventana
 * entera**: 317 px en una de 305, y 369 en una de 296. *Dos renglones ahí es
 * imposible sin tocar el texto*, y el texto no se toca. `text-wrap: balance` se
 * queda porque reparte esos tres o cuatro renglones en líneas parejas, que es
 * lo único que mejora sin cambiar nada más. `D-170` */

/* ─── LA ACCIÓN DE UN LISTADO DENTRO DE UNA VENTANA · D-173 ────────────────
 *
 * Cuando un listado se abre en ventana se apaga su cabecera —el título y el
 * retorno los pone la ventana—, pero **el botón de alta no es cabecera**: es lo
 * que la pantalla ofrece hacer. Necesita su propio renglón, y **a la derecha**,
 * que es donde va la acción principal en toda la casa.
 *
 * Se declara en vez de escribir `justify-end` en la vista: *una clase que no
 * existe se ve idéntica a que no hubieras escrito nada*, y el guardián la cazó
 * en el mismo minuto. */
.acciones-en-ventana {
  display: flex; justify-content: flex-end; align-items: center;
  gap: var(--space-3); flex-wrap: wrap;
  margin-block-end: var(--space-3);
}

/* ─── EL CLIENTE CEDE, LA CIFRA NO · Kike, 18 de agosto de 2026 ─────────────
 *
 * Medido a escala máxima: la tabla de autodetracción se salía **501 px** de su
 * tarjeta, y «Cliente» ocupaba 498 de los 1.961. Es el único campo de largo libre
 * de la fila —una razón social llega a 99 caracteres—, así que *lo de largo libre
 * cede; una cifra o una fecha, nunca*.
 *
 * Cede a un renglón acotado con puntos suspensivos, y **el nombre entero sigue
 * ahí**: va en el `title`, que es lo que el navegador enseña al posarse y lo que
 * el lector de pantalla anuncia. No se pierde el dato, se pierde el ancho.
 *
 * El tope va en `ch` y no en píxeles: así crece con las cuatro escalas de texto en
 * vez de apretar la letra a escala grande, que es justo lo contrario de lo que
 * hace falta. */
.tabla-autodetraccion .cliente-recorte {
  display: inline-block;
  max-inline-size: 18ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  vertical-align: bottom;
}

/* ─── Y CEDE MÁS DONDE HAY MENOS SITIO · medido ─────────────────────────────
 *
 * Con el tope en `18ch` la tabla cabe entera a 17, 21 y 25 px, y a **30 px se
 * salía 49 px**. El `ch` crece con la escala —que es lo que se quería para que la
 * letra no se apriete— pero a la escala máxima la tarjeta ya no da más de sí.
 *
 * Así que desde `muy_grande` el nombre cede un poco más. **Es el único campo que
 * puede**: el importe a depositar, el plazo y el botón no se pliegan, y el nombre
 * entero sigue entero en el `title`. */
:root[data-escala="muy_grande"] .tabla-autodetraccion .cliente-recorte,
:root[data-escala="maximo"] .tabla-autodetraccion .cliente-recorte {
  max-inline-size: 12ch;
}

/* ─── QUÉ CLASE DE SALDO ES, JUNTO AL NOMBRE ────────────────────────────────
 *
 * ─── RETIRADA · Kike, 19/08/2026 ───────────────────────────────────────────
 *
 * La regla se queda escrita aquí porque la clase puede volver: una tarjeta
 * llevaba «deuda» detrás de su nombre para que su cifra negativa no se leyera
 * como dinero que hay. **Cumplió su función y partía la fila** —medido: las dos
 * AMEX se parten a partir de 380 px de ancho de tarjeta con ella, y aguantan
 * hasta 300 sin ella—, así que Kike la retiró: «siempre debe medir una línea».
 *
 * Lo que decía no se pierde: el signo menos va delante de la cifra y la flecha ↓
 * sigue en su sitio. *El color no es la única señal.* */
.saldo-que-es {
  margin-inline-start: var(--space-1);
  text-transform: lowercase;
  white-space: nowrap;
}

/* ── LA PROPUESTA DEL CRONOGRAMA · debajo del campo, no encima ─────────────
 *
 * Va después del `input` y con su borde a la izquierda, como el panel de las
 * combinaciones del cobro: la misma forma para la misma idea —«Flow sugiere,
 * decides tú»—, así no hay que aprender dos.
 *
 * El botón a 44 px, que es lo que se pulsa con el dedo, y el texto **no se
 * encoge** para caber al lado: la propuesta y su motivo se leen enteros o no
 * sirven de nada. */
.cron-propuesta {
  border-inline-start: 2px solid var(--border-strong);
  padding-inline-start: var(--space-3);
  margin-block-start: var(--space-2);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
}
.cron-propuesta .caption { margin: 0; flex: 1 1 16ch; }
.cron-propuesta .btn { min-block-size: var(--tactil-minimo); flex: 0 0 auto; }

/* ── EL SELECTOR DE LA TARJETA · dos botones, no un desplegable ────────────
 *
 * Se ven los dos y se sabe en cuál se está sin abrir nada. Se envuelven cuando
 * no caben —nunca se encogen— y cada uno mantiene el mínimo táctil: son la
 * puerta a media tarjeta, no un adorno.
 *
 * Cuál está puesto NO lo dice solo el realce: el propio rótulo añade «· es lo
 * que ves» y lleva `aria-current`. *El color nunca puede ser la única señal.* */
.selector-tarjeta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}
.selector-tarjeta .btn { min-block-size: var(--tactil-minimo); }

/* ─── LOOP · LA CELDA QUE TIENE UN DATO PRINCIPAL Y UNO DE APOYO ────────────
 *
 * En la tabla de servicios cada celda lleva dos cosas: el dato que se lee de un
 * vistazo y el que solo hace falta al detenerse — el nombre y su descripción, la
 * fecha y los días que faltan, el cliente y las pistas que lo proponen.
 *
 * Apilarlas sin decirlo dejaba las dos con el mismo peso y obligaba a leerlas
 * enteras. `celda-principal` es lo que se lee; lo demás ya va en `.caption`,
 * que es más pequeño y más apagado. **La jerarquía no es el color: es el
 * tamaño y el orden.** */
.celda-principal { display: block; font-weight: 600; }

/* La botonera de una fila: en línea mientras quepa, apilada cuando no.
 * `wrap` y no `nowrap` a propósito — a escala máxima dos botones de 44 px no
 * caben al lado, y forzarlos encogería el área táctil, que es lo único que
 * aquí no se negocia. */
.fila-acciones {
  display: flex; flex-wrap: wrap; gap: var(--space-2);
  align-items: center;
}
.fila-acciones > form { display: contents; }

/* La fila que está eligiendo cliente. Lleva BORDE, no solo fondo: sobre una
 * tabla larga un tinte suave se pierde, y en alto contraste el tinte
 * desaparece a propósito. */
.fila-elegida > * { background: var(--surface-2, transparent); }
.fila-elegida > :first-child { box-shadow: inset 3px 0 0 0 var(--brand-boton); }

/* ─── LOOP · EL RÓTULO DE UN BOTÓN NO SE PARTE ──────────────────────────────
 *
 * Medido a escala máxima: «Es correcto» caía en dos líneas, una palabra en
 * cada una. Un rótulo envuelto palabra por palabra se lee peor que uno largo,
 * y a la escala que Kike usa es donde más pasa — justo donde menos puede pasar.
 *
 * La salida no es acortar el rótulo hasta que quepa: es que **la columna de
 * acciones no ceda**. Lo que cede es la de largo libre —el nombre del servicio,
 * su descripción—, que sí puede partirse sin perder nada. *La fila no se
 * duplica: cede lo de largo libre.*
 *
 * Y la tabla ya vive dentro de `.tabla-envoltura`, que desplaza en horizontal
 * cuando de verdad no cabe: nadie pierde una columna, se llega a ella. */
.col-accion { white-space: nowrap; }
.col-accion .btn { white-space: nowrap; }

/* ─── LOOP · LA TABLA APROVECHA EL ANCHO DE SU TARJETA ──────────────────────
 *
 * Como la de impuestos por pagar. Sin esto la tabla se encoge al ancho de su
 * contenido y deja media tarjeta en blanco, que con ocho columnas y nombres
 * largos obliga a partir lo que sí cabría. */
.tabla--ancha { inline-size: 100%; }

/* ─── EL SEPARADOR DE MES ───────────────────────────────────────────────────
 *
 * No es una fila de datos: es un rótulo con su recuento. Se distingue por
 * **peso y línea**, no por color — el color del tramo va aparte y es refuerzo.
 * Una fila de separación que solo se distinguiera por tono desaparecería en
 * alto contraste, que es donde más falta hace. */
.fila-separador > * {
  border-block-start: 2px solid var(--border-strong);
  padding-block-start: var(--space-3);
  font-weight: 600;
}
.fila-separador .badge { font-weight: 400; }

/* El tramo del mes: refuerzo del texto, nunca su sustituto. La fecha ya lleva
 * «3 días» al lado, y eso es lo que manda. */
tr[data-tramo="este"] .contador-dias,
tr[data-tramo="pasado"] .contador-dias { font-weight: 600; }
.fila-separador[data-tramo="este"] > :first-child { box-shadow: inset 3px 0 0 0 var(--expense); }
.fila-separador[data-tramo="siguiente"] > :first-child { box-shadow: inset 3px 0 0 0 var(--warning); }

/* `.cifra-clave--loop` se retiró: la vista dejó de usarla cuando la cifra pasó
   a `cifra-carga`, y una regla que no pinta nada es una regla que engaña al
   siguiente que la lea. */

/* ─── EL MONTO DE LA TABLA, DOS PUNTOS MÁS ──────────────────────────────────
 * También en `em`, por lo mismo. La clase de monto ya lo alinea a la derecha
 * y le da las cifras tabulares; esto solo lo hace más legible. */
.tabla--ancha td.num .amount { font-size: 1.12em; font-weight: 600; }

/* ─── DOS LÍNEAS POR SERVICIO, Y LO QUE CEDE ES LO DE LARGO LIBRE ───────────
 *
 * Kike: dos líneas como máximo. Con siete columnas, lo único que puede pasarse
 * es el texto — un nombre de cliente o una descripción larga. Así que **lo de
 * largo libre cede** con puntos suspensivos y el texto entero en el `title`, y
 * *una cifra o una fecha, nunca*: sus columnas llevan `no-cede`.
 *
 * `max-inline-size: 0` con `width: 100%` en la celda es lo que obliga a la
 * columna flexible a encogerse en vez de empujar a las demás: sin eso, el
 * `text-overflow` no llega a activarse nunca porque la tabla crece. */
.tabla--ancha td.cede, .tabla--ancha th.cede { max-inline-size: 22ch; }
.tabla--ancha td.no-cede, .tabla--ancha th.no-cede { white-space: nowrap; }
.recorta {
  display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  max-inline-size: 100%;
}

/* ─── LA FILA QUE ABRE SU FICHA ─────────────────────────────────────────────
 * El puntero y el realce dicen que se puede pulsar; el `aria-label` lo dice a
 * quien no ve el puntero. El foco se marca aparte del hover: el hover no existe
 * con teclado. */
.fila-abrible { cursor: pointer; }
.fila-abrible:hover > * { background: var(--surface-2); }
.fila-abrible:focus-visible { outline: var(--focus-width) solid var(--focus-color); outline-offset: -3px; }

/* ─── LA FICHA · DOS COLUMNAS QUE SE APILAN CUANDO NO CABEN ─────────────────*/
.ficha-rejilla {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
  gap: var(--space-3); margin: 0;
}
.ficha-dato dt { margin-block-end: .15rem; }
.ficha-dato dd { margin: 0; font-weight: 600; }
.ficha-bitacora { white-space: pre-wrap; }

/* ─── LOOP · LOS CUATRO BLOQUES DEL FORMULARIO, DE DOS EN DOS ───────────────
 *
 * `auto-fit` con un mínimo: caben dos mientras cada columna no baje de 22rem, y
 * **se apilan solas** cuando el texto crece. No hay `order` en ninguna parte, y
 * por eso el orden del tabulador sigue al visual sin hacer nada — es el mismo
 * orden del marcado. Resolverlo con `order` habría dejado el foco saltando de
 * un bloque a otro, que es la clase de fallo que solo nota quien navega con
 * teclado. */
.bloques-pares {
  display: grid;
  /* El mínimo es 34rem y no 22 para que **no quepan tres**: Kike pidió dos por
     fila, y con tres los cuatro bloques quedan 3+1, que descuadra la lectura.
     Con 34rem entran dos mientras el sitio dé, y **al subir la escala el `rem`
     crece y se apilan solos** — que es lo que hay que medir y decir. */
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 34rem), 1fr));
  gap: var(--space-4);
  align-items: start;
}
.bloques-pares > fieldset { min-inline-size: 0; }

/* El monto del formulario, con el tamaño de los demás campos financieros y
 * creciendo con la escala. La caja crece con `--largo`: nunca se encoge la
 * cifra para que quepa. */
.campo-monto {
  text-align: end; font-variant-numeric: tabular-nums; font-size: 1.12em;
  min-inline-size: calc(var(--largo, 8) * 1ch + 2.5rem);
}

/* ─── LOOP · EL SEPARADOR DE MES ────────────────────────────────────────────
 *
 * Kike pidió el nombre del mes **cuatro puntos más grande** y el importe que
 * queda **tres más**. Los dos en `em`: en píxeles crecerían una vez y se
 * quedarían quietos al subir la escala, que es lo contrario de lo pedido.
 *
 * El importe usa la clase de monto, que ya lo alinea a la derecha y le da las
 * cifras tabulares; aquí solo se le sube el cuerpo. */
/* ─── EL NOMBRE DEL MES · MUY CLARO QUE ES OTRO MES ────────────────────────
 *
 * Kike: *«agrandar aún más el nombre del mes, en otro tipo de letra de ser
 * necesario; la idea es que quede MUY claro que es otro mes»*. Pasa de 1,22em
 * a **1,6em** —de +4 a +11 puntos sobre el cuerpo— y gana su propia familia:
 * la de titulares, que es la que ya distingue una cabecera de un dato en el
 * resto de Flow. **Dos señales, ninguna de color.** */
.nombre-mes {
  /* Y **en otra letra**, como ofreció Kike: `--font-display`, la que Flow ya
     usa para titulares. No es un token inventado — existe en la paleta— y suma
     una cuarta señal al cuerpo, el peso y el espaciado. Ninguna es de color. */
  display: block; font-size: 1.6em; font-weight: 800;
  font-family: var(--font-display);
  letter-spacing: .01em; line-height: 1.15;
}
.fila-separador td.num .amount { font-size: 1.3em; font-weight: 700; }

/* ─── EL HUECO ENTRE MESES, Y EL MARGEN JUNTO A LA BARRA DE COLOR ──────────
 *
 * Kike: *«espaciar el bloque entre meses para que quede claro el final de uno y
 * el inicio de otro»* y *«darle un margen al lado del color rojo o amarillo que
 * está completamente pegado»*.
 *
 * El hueco se hace con relleno en la fila separadora y no con `margin`: en una
 * tabla, `margin` sobre un `tr` no hace nada — se ve bien en el navegador y no
 * pasa. Y la barra de color pasa de `inset` pegado al texto a un borde
 * izquierdo con su propio relleno detrás. */
.fila-separador > * {
  padding-block-start: var(--space-6);
  padding-block-end: var(--space-3);
}
.fila-separador:first-child > * { padding-block-start: var(--space-3); }
.fila-separador[data-tramo="este"] > :first-child,
.fila-separador[data-tramo="siguiente"] > :first-child { box-shadow: none; }
.fila-separador[data-tramo="este"] > :first-child { border-inline-start: 6px solid var(--expense); }
.fila-separador[data-tramo="siguiente"] > :first-child { border-inline-start: 6px solid var(--warning); }
.fila-separador[data-tramo="este"] > :first-child,
.fila-separador[data-tramo="siguiente"] > :first-child { padding-inline-start: var(--space-4); }

/* ═══ LOOP · LOS TRES WIDGETS, EN UNA SOLA FILA ═════════════════════════════
 *
 * Kike: *«el tercero debe entrar en una sola fila de widgets»* y *«sobra
 * muchísimo espacio en el primero»*. El primero pesa 32 % de la fila —por
 * encima del 30 % que pidió— y los otros dos se reparten el resto. Se apilan
 * cuando cada columna bajaría de 21rem, que a escala máxima ocurre. */
.loop-widgets {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, var(--ancho-minimo-widget)), 1fr));
  gap: var(--space-4);
  align-items: stretch;
}
/* El umbral va en `rem`, que en Flow **crece con la escala de texto**: a la
 * escala máxima 86rem son 2.718 px y la ventana no llega, así que los tres
 * pasan a repartirse a partes iguales y la cifra deja de desbordar su caja.
 * Medido: con 68rem, «S/ 44.914,06» pedía 465 px y tenía 424. *Primero crece
 * la caja*, y la caja crece dándole más columna. */
/* El primero nunca por debajo de lo que mide su cifra. `ch` crece con la letra,
 * así que el suelo sube con la escala igual que el número: es la forma de que
 * **primero crezca la caja** en vez de que la cifra se desborde. Medido: a la
 * escala máxima «S/ 44.914,06» pide 465 px y la columna daba 424. */
/* ─── Y LAS DOS DE LA DERECHA TIENEN QUE PODER ENCOGER · 28/08/2026 ────────
 *
 * Decía `34fr 34fr`. Un `fr` a secas **no baja del mínimo de su contenido**, así
 * que las dos columnas de los gráficos se plantaban en lo que pedían sus filas
 * y lo que sobraba se salía de la rejilla. Medido a escala máxima: la fila daba
 * `773,5px 418,875px 418,875px` en un contenedor de 1.670,8 y el `scrollWidth`
 * subía a 1.789 — **338 px fuera**, y con la barra horizontal que eso trae.
 *
 * El apilado de las filas ya bajó ese desborde a 118 px, porque un nombre que
 * envuelve y una cifra que envuelve piden mucho menos que tres columnas rígidas.
 * `minmax(0, 34fr)` cierra el resto: **les da permiso para encoger**, y lo de
 * dentro ya no lo necesita para caber.
 *
 * La primera columna no se toca: su mínimo es lo que mide su cifra, y ése es el
 * caso donde *primero crece la caja* — una cifra nunca cede. */
@media (min-width: 86rem) { .loop-widgets { grid-template-columns: minmax(var(--ancho-minimo-widget), 32fr) minmax(0, 34fr) minmax(0, 34fr); } }

/* El contenido llena la tarjeta: sin esto, el primero dejaba la mitad en
 * blanco debajo de su cifra. `1fr` en la fila de la cifra la centra vertical
 * y empuja el pie abajo. */
/* ─── Y LA TARJETA TAMBIÉN NECESITA PERMISO PARA ENCOGER · 28/08/2026 ──────
 * Dar `minmax(0, 34fr)` a la columna no bastaba: **la tarjeta es a su vez un
 * elemento de rejilla**, y un elemento de rejilla nace con `min-width: auto`,
 * o sea que no baja de su tamaño mínimo de contenido. La columna medía 418,9 y
 * la tarjeta seguía plantada en 491,3, saliéndose por la derecha.
 * Es la misma regla dos niveles seguidos, y las dos hacían falta. */
.loop-w { display: grid; grid-template-rows: auto auto 1fr auto auto; gap: var(--space-2);
  min-inline-size: 0; }

/* ─── EL HUECO DEL PRIMERO · MEDIDO ANTES Y DESPUÉS ────────────────────────
 *
 * Kike: *«sobra muchísimo espacio en el primero»*. Medido: la tarjeta ocupaba
 * **513 px** y su contenido **253** — 260 px en blanco, porque las vecinas
 * traen ocho y siete barras y la fila se estira a la más alta.
 *
 * La salida no es encoger las vecinas ni dejar la tarjeta corta y desalineada:
 * es **usar el sitio para lo que se mira**. La cifra del mes pasa de 1,25em a
 * 2,6em —de 24 a 47 px— y la de «por cobrar estimado» de 1,35 a 1,8em. El
 * hueco se convierte en cifra, que es de lo que hablaba el widget. */
/* Sin tamaño propio: la cifra principal de este widget es la misma que la de
   todos, y `cifra-clave` ya la trae del token. */
.loop-w--mes .cifra-clave { align-self: center; line-height: 1.05; }

/* ─── «POR COBRAR ESTIMADO» · SU PROPIA CIFRA ──────────────────────────────
 * Separado por una línea, no por un tono: en alto contraste el tinte
 * desaparece y la separación tiene que seguir estando. */
.loop-w__pie {
  border-block-start: 2px solid var(--border-strong);
  padding-block-start: var(--space-3);
  display: grid; gap: .15rem;
}
/* ─── «POR COBRAR ESTIMADO» ES LA SEGUNDA CIFRA, NO LA PRINCIPAL ───────────
 * Una tarjeta tiene **una** cifra principal; ésta la acompaña. Se nombra como
 * segunda —igual que `neto-cifra--segunda`— para que quede dicho en el nombre
 * y no en un comentario, y va al 60 % del token, que es la proporción que Flow
 * ya usa para lo mismo. */
.loop-w__rotulo { font-weight: 600; }
.loop-w__cifra--segunda {
  font-size: calc(0.6 * var(--fs-cifra-widget));
  font-weight: 700; text-align: start;
}

/* ─── LAS BARRAS EN LÍNEAS · MESES Y CATEGORÍAS ────────────────────────────
 *
 * Kike: *«puede ser en líneas, como el tercero, para que entre más
 * información»*. En columnas cabían seis meses; en líneas caben ocho en el
 * mismo alto, porque el nombre va al lado y no debajo.
 *
 * ─── Y EL GRIS ES DATO · `D-465` ──────────────────────────────────────────
 *
 * *«Mis ojos no notaron el gris»*. El carril **no** usa `--surface-2`, que es
 * un fondo y por eso casi no se separa de la tarjeta: usa `--barra-pendiente`,
 * un tono propio elegido para que se vea contra la tarjeta **y** contra lo
 * renovado. Y lo renovado lleva un borde a su derecha, así que dónde acaba se
 * ve aunque los dos tonos se acercaran. */
/* Las dos locales se DECLARAN aquí con su valor de partida: una `var()` que
 * solo existe en el `style` del marcado es una variable sin dueño, y deja la
 * barra sin dibujar el día que ese `style` no llegue (`D-463`). */
.barra-lin__carril--cat { --ancho: 100%; }
.barra-lin__lleno { --pct: 0%; }

/* ─── EL CARRIL MIDE LO MISMO EN TODAS LAS FILAS · Kike, 01/09/2026 ────────
 *
 * Las tres reglas de abajo tenían la primera columna en `minmax(…, auto)`, y
 * **cada `.barra-lin` es su propia rejilla**: `auto` crece con el nombre de ESA
 * fila, así que «VM Máquina virtual» dejaba un carril más corto que «SaaS» y
 * *una barra medio llena en un carril corto parece lo mismo que una menos llena
 * en uno largo*.
 *
 * Es el mismo defecto que Kike vio en el comparativo de Billing, **y aquí lo
 * encontró el barrido, no él**: se buscaron todas las rejillas de barras del
 * proyecto en vez de arreglar solo las dos que se sabían.
 *
 * El ancho va en `rem` para que crezca con las cuatro escalas de texto, y el
 * nombre ya cede —`text-overflow: ellipsis`— en vez de empujar el carril. */.barras-lin { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-2); }
/* Las categorías necesitan más sitio para el nombre que los meses —«VM Máquina
 * virtual» contra «ago 26»— y su cifra lleva dos renglones. */
.barras-lin--cat .barra-lin { grid-template-columns: 7rem minmax(0, 1fr) auto; }
/* Los meses necesitan menos sitio para el nombre —«ago 26» contra «VM Máquina
 * virtual»— y su cifra lleva la palabra del tramo debajo. */
.barras-lin--meses .barra-lin { grid-template-columns: 4.5rem minmax(0, 1fr) auto; }
.barra-lin {
  /* ─── DOS RENGLONES TAMBIÉN AQUÍ · 01/09/2026 ────────────────────────
     Las variantes `--meses` y `--cat` llevan esta forma desde el 28/08, cuando
     Kike la pidió; la base se quedó con tres columnas. Y con tres columnas **la
     del importe crece con su contenido**, así que le quita ancho al carril de su
     fila — que es el defecto que él volvió a ver el 01/09.

     Con el carril en su propia línea el riel no compite con ningún texto y los
     carriles miden lo mismo **por construcción**. */
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-areas: "nombre cifra"
                       "carril carril";
  gap: var(--space-micro) var(--space-2); align-items: center;
}
.barra-lin__nombre {
  grid-area: nombre;
  font-size: .92em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* El carril cruza la fila entera: es lo que lo hace comparable entre filas. */
.barra-lin__carril { grid-area: carril; }
.barra-lin__cifra { grid-area: cifra; justify-self: end; }
/* ─── EL CARRIL DE LOS MESES NO ES GRIS · Y ES A PROPÓSITO ─────────────────
 *
 * Medido en pantalla: con el mismo gris en los dos gráficos, **el mismo tono
 * significaba dos cosas**. En las categorías el gris es *lo que falta por
 * renovar* —una cifra—; en los meses sería solo *lo que le falta a este mes
 * para llegar al mayor*, que no es ninguna cifra de nadie.
 *
 * *Si dos tonos se parecen, la barra deja de decir nada* — y aquí era peor: se
 * parecían y decían cosas distintas. Así que el carril de los meses se queda
 * sin relleno: solo la barra es tinta, y su largo es el dato. */
.barra-lin__carril {
  block-size: 1.35rem; inline-size: 100%;
  background: transparent;
  border-block-end: 1px solid var(--border);
  border-radius: 0; overflow: hidden;
}
/* El de categorías SÍ es gris, porque ahí el gris es lo que falta por renovar. */
.barra-lin__carril--cat {
  inline-size: var(--ancho, 100%);
  /* El gris de siempre, que es de esta pantalla y no de la tarjeta: al
     renombrar mis tokens en `D-730` el reemplazo se aplicó a todo el fichero y
     esta regla ajena se llevó mi crema. *Un reemplazo global toca lo que no
     mira.* */
  background: var(--barra-pendiente);
  border: 1px solid var(--barra-pendiente-borde);
  border-radius: var(--radius-sm);
}
.barra-lin__lleno {
  display: block; inline-size: var(--pct, 0%); block-size: 100%;
  background: var(--brand-boton);
  border-radius: var(--radius-sm) 0 0 var(--radius-sm);
}
/* Solo en categorías: el borde marca DÓNDE ACABA lo renovado y empieza lo que
 * falta. En los meses no hay nada al otro lado que separar. */
.barra-lin__carril--cat .barra-lin__lleno {
  border-inline-end: 3px solid var(--barra-separador);
  border-radius: var(--radius-sm) 0 0 var(--radius-sm);
}
.barra-lin__cifra {
  font-variant-numeric: tabular-nums; text-align: end; font-size: .92em; white-space: nowrap;
}
.barra-lin__cifra .caption { display: block; }

/* El mes en curso, en otro color · Kike: *«para que el gráfico no sea tan plano
 * y aburrido»*. Va con su palabra al lado —«a medias»— porque el color no
 * puede ser la única señal. */
.barra-lin.es-en-curso .barra-lin__lleno { background: var(--barra-en-curso); }
.barra-lin.es-en-curso .barra-lin__nombre { font-weight: 700; }

/* ═══ LOOP · LOS AJUSTES DE KIKE · 22/08/2026 ══════════════════════════════
 *
 * ─── LA ETIQUETA A SU TAMAÑO ──────────────────────────────────────────────
 * Kike: *«el label amarillo del primer widget no tiene por qué ocupar el 100 %
 * de la tarjeta, debe ocupar su tamaño»*. Es un `<p>` dentro de una rejilla, y
 * una rejilla estira a sus hijos por omisión: `justify-self: start` lo deja
 * medir lo que mide su texto. */
.loop-w .badge { justify-self: start; }

/* ─── EL MONTO, CENTRADO Y DEL TAMAÑO DE LOS DEMÁS MÓDULOS ────────────────
 * `cifra-carga` ya trae el tamaño de Tributos y la carga fiscal; aquí solo se
 * centra. Sin `font-size` propio a propósito: el día que se toque el de allá,
 * éste va detrás. */
.loop-w__monto { text-align: center; align-self: center; }
.loop-w--mes .loop-w__pie { text-align: center; }
.loop-w--mes .loop-w__cifra--segunda { text-align: center; }

/* ─── LOS TRES TONOS DE LOS MESES ─────────────────────────────────────────
 *
 * Azul lo renovado, naranja el mes en curso, gris lo que se espera. Y el gris
 * es el MISMO que el de las categorías —`--barra-pendiente`, el que se subió
 * hasta verse— porque en los dos sitios quiere decir lo mismo: dinero que
 * todavía no ha entrado. */
.barra-lin__carril--mes {
  background: transparent; border: 0;
  border-block-end: 1px solid var(--border);
  border-radius: 0;
  display: flex; align-items: stretch;
}
/* Un tramo que existe tiene que verse: 2.070 sobre 129.613 son el 1,6 %, y
 * sin suelo eso es un pelo. `min-inline-size` le da cuerpo sin mentir sobre su
 * tamaño — la cifra va al lado. */
.barra-lin__previsto {
  display: block; inline-size: var(--pct, 0%); block-size: 100%;
  min-inline-size: 8px;
  /* El gris de siempre, que es de esta pantalla y no de la tarjeta: al
     renombrar mis tokens en `D-730` el reemplazo se aplicó a todo el fichero y
     esta regla ajena se llevó mi crema. *Un reemplazo global toca lo que no
     mira.* */
  background: var(--barra-pendiente);
  border-inline-start: 3px solid var(--barra-separador);
}
/* El separador del mes en curso se elige contra el NARANJA, no contra el azul:
 * en oscuro normal el naranja es clarísimo y el separador claro del azul
 * desaparecía sobre él a 1,31:1. Medido, no supuesto. */
.barra-lin--actual .barra-lin__previsto { border-inline-start-color: var(--barra-separador-actual); }
.barra-lin--futuro .barra-lin__previsto { border-inline-start: 0; }
.barra-lin--actual .barra-lin__lleno { background: var(--barra-en-curso); }
.barra-lin--actual .barra-lin__nombre { font-weight: 700; }

/* ─── MÁS AIRE ENTRE MESES · Kike: *«añadir aún un poquito más»* ───────────
 *
 * Y con la especificidad que hace falta: `.fila-separador > *` es (0,1,0) y
 * `.tabla td` es (0,1,1), así que la regla de la tabla ganaba y el relleno se
 * quedaba en **12,75 px** por mucho que aquí pusiera `--space-8`. Medido en el
 * navegador, no leído. */
.tabla .fila-separador > * { padding-block-start: var(--space-8); }
.tabla .fila-separador:first-child > * { padding-block-start: var(--space-3); }

/* ─── LOOP · EL TÍTULO DE LOS WIDGETS COMPARATIVOS, CENTRADO ───────────────
 * Kike lo pidió para el segundo, y se aplica también al tercero: dos tarjetas
 * gemelas con el título en sitios distintos se leen como dos cosas. */
.loop-w__titulo { text-align: center; }
.loop-w__titulo .pista-boton { vertical-align: middle; }

/* El monto del mes actual, resaltado · Kike. Peso y no color: el color ya lo
 * lleva la barra, y una segunda señal en el mismo lenguaje no añade nada. */
.barra-lin--actual .barra-lin__cifra { font-weight: 800; }

/* ─── LOOP · WIDGET 2 · EL MES ACTUAL, MÁS GRANDE ──────────────────────────
 * Kike: el nombre y el monto del mes actual, al menos tres puntos más que los
 * demás. En `em`, para que la diferencia se mantenga en las cuatro escalas. */
.barra-lin--actual .barra-lin__nombre,
.barra-lin--actual .barra-lin__cifra { font-size: 1.18em; font-weight: 800; }

/* ─── LOOP · WIDGET 3 · TODAS LAS BARRAS DESDE EL MISMO BORDE ──────────────
 *
 * Kike: *«las líneas de color deben partir desde el mismo punto; no ayuda en el
 * comparativo que cada una parta de diferente inicio»*. Tenía razón y era
 * literal: la columna del nombre era `minmax(7rem, auto)`, así que «Sin
 * categoría» empujaba su barra mucho antes que «VM Máquina virtual» y los
 * largos dejaban de poder compararse. *Barras que arrancan de sitios distintos
 * no se pueden comparar.*
 *
 * Ancho fijo en `ch` —crece con la letra— y el nombre que no quepa se corta con
 * puntos suspensivos, con el entero en el `title`. */
.barras-lin--cat .barra-lin { grid-template-columns: 16ch minmax(0, 1fr) auto; }
.barras-lin--cat .barra-lin__nombre {
  inline-size: 16ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* ─── UN BOTÓN QUE ES LA ÚNICA PUERTA A ALGO · `D-490` ──────────────────────
 *
 * Kike, 23 de agosto de 2026: «siempre botones de icono. Con mi baja visión,
 * necesito limpieza; texto extenso, áreas no claramente delimitadas, botones
 * gigantes por todos lados son cosas que me afectan».
 *
 * Así que la palabra que acompaña al icono es UNA, corta, y no engorda la fila:
 * el botón crece de ancho —la columna de acciones es de largo libre— y conserva
 * el alto de 44 px que ya tenía. Un `btn--icon` mide 44×44; éste mide 44 de alto
 * y lo que pida su palabra.
 *
 * El icono se queda: es lo que Kike reconoce de un vistazo. La palabra es para
 * encontrarlo la primera vez. */
.accion-con-nombre {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-block-size: 44px;         /* el mismo suelo táctil que `btn--icon` */
  padding-inline: var(--space-3);
  white-space: nowrap;          /* la palabra no parte la fila en dos líneas */
}
.accion-con-nombre > span { line-height: 1.1; }

/* ─── LAS FACTURAS QUE PAGA UN INGRESO · `D-494` ────────────────────────────
 *
 * La ficha del movimiento decía «aplicado entero a sus facturas» sin decir a
 * cuáles. Ahora las enseña, y cada línea lleva su importe alineado a la derecha
 * para que dos cifras se comparen de un vistazo. */
.lista-aplicadas { list-style: none; margin: 0 0 var(--space-3); padding: 0; }
.lista-aplicadas li {
  display: flex; align-items: baseline; gap: var(--space-3);
  padding-block: var(--space-2);
  border-bottom: 1px solid var(--border);
}
.lista-aplicadas li:last-child { border-bottom: 0; }
/* El nombre del cliente cede; el número y la cifra, nunca. */
.lista-aplicadas li > .recorta { flex: 1 1 auto; min-inline-size: 0; }
.lista-aplicadas li > .num { margin-inline-start: auto; white-space: nowrap; }

/* ─── LOOP · LA COLUMNA DE FACTURACIÓN · `D-510` ────────────────────────────
 *
 * La etiqueta y la cifra de lo que falta, una debajo de otra y sin partir la
 * fila: el ancho lo cede lo de largo libre —Cliente, Servicio, Categoría y
 * Vendedor—, que ya lo hace. Medido en las cuatro escalas: la fila pasa de 58 a
 * 100 px, que es lo que crece el texto, y ni una se parte en dos renglones. */
.col-facturacion { white-space: nowrap; }
.col-facturacion .caption { display: block; margin-block-start: var(--space-1); }

/* ─── UN TIP QUE TAMBIÉN SE VE CON EL TABULADOR · `D-518` ───────────────────
 *
 * Kike, 25 de agosto de 2026: los botones vuelven a ser de icono con su tip,
 * porque lo que pide es **limpieza visual**. Pero el `title` del navegador
 * **no aparece al enfocar con el teclado** y desaparece en cuanto se mueve el
 * ratón — así que un icono con solo `title` deja fuera a quien no apunta.
 *
 * Este tip sale en `:hover` **y en `:focus-visible`**, y su texto es el mismo
 * `aria-label` del botón: una sola fuente, así que no pueden discrepar.
 *
 * `pointer-events: none` para que el propio globo no se coma el clic del botón
 * que lo abrió, y `z-index` sobre la fila para que no lo tape la siguiente.
 * En una tabla el globo se sale de la celda a propósito: `overflow` recortado
 * lo dejaría en una raya de dos píxeles, que es lo que ya pasó con el diálogo. */
/* ─── Y VALE PARA TODOS, SIN LISTA · `D-524` ────────────────────────────────
 *
 * Medido: **177 botones de icono con `title`** y solo 12 con tip alcanzable.
 * Ponérselo uno a uno sería tocar 22 pantallas y olvidar el siguiente que
 * nazca, así que la regla se engancha a lo que ya los identifica: **ser de
 * icono y tener nombre**. `.btn--icon[aria-label]` los coge todos, y a los
 * que vengan mañana también.
 *
 * Y no engorda ninguna fila: el globo es `position: absolute`, así que **no
 * ocupa sitio en el flujo**. Medido antes y después en las cuatro escalas. */
.btn-tip,
.btn--icon[aria-label] { position: relative; }
.btn-tip::after,
.btn--icon[aria-label]::after {
  content: attr(aria-label);
  position: absolute; inset-block-end: calc(100% + var(--space-1));
  inset-inline-end: 0;
  z-index: 40;
  min-inline-size: max-content; max-inline-size: 22rem;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  background: var(--surface-2); color: var(--text);
  font-size: var(--fs-caption); font-weight: 400; line-height: 1.3;
  text-align: start; white-space: normal;
  box-shadow: var(--shadow-2);
  /* Sin transición a propósito: un tip que tarda en aparecer es un tip que se
     pierde, y con `display` conmutado no hay estado a medias que medir. */
  display: none;
  pointer-events: none;
}
.btn-tip:hover::after,
.btn-tip:focus-visible::after,
.btn--icon[aria-label]:hover::after,
.btn--icon[aria-label]:focus-visible::after { display: block; }
/* Y Escape lo cierra: `tip.js` quita el foco, que es lo que lo mantiene abierto. */
.btn--icon[aria-label].tip-callado::after { display: none !important; }

/* ─── LOOP · LOS DOS TOTALES DEL SEPARADOR DE MES · `D-535` ─────────────────
 *
 * Soles y dólares, uno debajo del otro y **sin convertir**. Cada uno con su
 * símbolo y, cuando hay dos, con cuántos servicios lo componen.
 *
 * Las cifras **no ceden**: `nowrap` en cada una y la celda a `no-cede`. Lo que
 * cede es el nombre del mes, que es lo de largo libre. Y no se encoge ninguna
 * para que quepan las dos: la segunda ocupa su propio renglón. */
.col-sep-totales { white-space: nowrap; }
.sep-total { display: block; white-space: nowrap; }
.sep-total + .sep-total { margin-block-start: var(--space-1); }
/* El número de servicios va pegado a su cifra, no en otra columna: así se lee
   «S/ 1.429,00 · 1» de una vez. */
.sep-total .caption { margin-inline-start: var(--space-2); }

/* ─── UN IMPORTE EN UNA CELDA NO ES LA CIFRA DE UN WIDGET · `D-542` ─────────
 *
 * Kike, 30/08/2026, sobre Gastos fijos: *«puso el tamaño del monto de los
 * widgets en el listado; en el caso del listado no aplica. Si bien tienen que
 * estar un poco resaltados, no así: esto es un listado»*.
 *
 * La regla de `D-508` se hizo para **la cifra principal de un widget** —una por
 * tarjeta, y es lo que se lee primero— y se aplicó a una columna, donde hay
 * quince a la vez. Medido: el campo a 25,5 px con la cabecera a 18,06, así que
 * **el importe pesaba más que su propio rótulo** y la tabla se leía peor.
 *
 * *Una regla aplicada más allá de su sitio rompe lo que estaba bien.*
 *
 * Aquí el importe se resalta por **peso y cifras tabulares**, no por tamaño:
 * un punto por encima del texto de la fila, no cinco. */
.input-monto-celda {
  font-size: 1.06em;
  font-weight: var(--fw-semibold);
  font-variant-numeric: tabular-nums;
  text-align: right;
  min-inline-size: 0;
}

/* Y la cifra **de texto** de una columna, hermana de `.input-monto-celda`
 * · `D-542`. `.cifra-clave` es, por su propia definición, «la cifra principal
 * de un widget» a `--fs-cifra-widget` = **63,75 px**. En una celda de Gastos
 * fijos daba **tres veces y media la cabecera** (18,06), que es el caso más
 * flagrante de lo que Kike señaló — y más grande que el campo de 25,5.
 *
 * Mantiene lo que `§8.5` exige —destacar **por peso y tamaño, no por color**,
 * porque en alto contraste el matiz desaparece— pero con el tamaño de una
 * columna, no el de una tarjeta. */
.cifra-columna {
  font-size: 1.06em;
  font-weight: var(--fw-bold);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* El aviso de que la deuda está en otra moneda · `D-562`. En palabras, con su
 * propio hueco: **no cambia el color del campo**, porque el color nunca es la
 * única señal y un borde de otro tono no dice qué pasa. */
.aviso-moneda {
  display: block;
  margin-block-start: var(--space-2);
  color: var(--text-secondary);
  font-weight: var(--fw-semibold);
  text-wrap: pretty;
}

/* ─── EL AVISO DE MONEDA, DEBAJO DEL CAMPO Y SIN EMPUJARLO · `D-565` ────────
 * Kike: *«debería aparecer abajo del campo como un tooltip con equis para
 * cerrar; está presionando el campo»*. Flota anclado al campo, así que no
 * ocupa alto y no estrecha lo que viene a explicar. Sigue siendo texto. */
.field.tiene-aviso-moneda { position: relative; }
.aviso-moneda {
  position: absolute;
  inset-inline: 0;
  inset-block-start: 100%;
  z-index: 30;
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  margin-block-start: var(--space-1);
  padding: var(--space-3);
  border: 2px solid var(--border-strong);
  border-radius: var(--radius-md);
  background: var(--surface-2);
  color: var(--text);
  font-weight: var(--fw-semibold);
  text-wrap: pretty;
  box-shadow: var(--shadow-2);
}
.aviso-moneda__texto { flex: 1 1 auto; min-inline-size: 0; }
/* 44 px de área táctil, que es el mínimo de esta casa. */
.aviso-moneda__cerrar {
  flex: 0 0 auto;
  inline-size: 44px;
  block-size: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 2px solid transparent;
  border-radius: var(--radius-sm);
  background: transparent;
  color: inherit;
  font-size: 1.5em;
  line-height: 1;
  cursor: pointer;
}
.aviso-moneda__cerrar:hover { border-color: currentColor; }
.aviso-moneda__cerrar:focus-visible { outline: 3px solid currentColor; outline-offset: 2px; }

/* El lápiz de una tarjeta de Administración no se estira · `D-585`. Al quitarle
 * la palabra quedó como único hijo alto de su caja y crecía hasta los 377 px en
 * la escala máxima: sigue siendo pulsable, pero un objetivo de media tarjeta no
 * dice dónde hay que pulsar. Se ancla arriba y conserva sus 44 px. */
/* `.btn--icon[aria-label]` lo pone en `relative` y gana por especificidad —un
   selector con atributo pesa más que una clase—, así que esta regla lleva la
   suya para empatar y ganar por orden. Ese detalle costó dos intentos. */
.js-rotular-tarjeta[aria-label] {
  /* `position: absolute` viene de su regla original y **tiene que quedarse**:
     al quitarle la palabra (`D-585`) esta regla lo dejó participando en el
     flex de la caja, y entonces empujaba a la tarjeta 44 px —su propio ancho—
     hacia dentro, así que el lápiz quedaba fuera del borde que se ve. Es lo
     que Kike vio · `D-612`. */
  position: absolute;
  min-inline-size: 44px;
  min-block-size: 44px;
  block-size: 44px;
}

/* La casilla de confirmar un emparejamiento · `D-589`. El cuadro nativo mide
 * 13 px y el mínimo de esta casa es 44: se agranda el cuadro y su celda se
 * vuelve área de toque, para que acertar no dependa de la puntería. */
.tabla td > input[type="checkbox"] {
  inline-size: 24px;
  block-size: 24px;
  margin: 10px;
  cursor: pointer;
}

/* ─── LOS DOS CATÁLOGOS, ENFRENTADOS · `D-590` ─────────────────────────────*/
.dos-catalogos {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
  gap: var(--space-5);
}
.catalogo-lado { min-inline-size: 0; }
.catalogo-arbol { list-style: none; margin: 0; padding: 0; }
.catalogo-arbol .es-hija { padding-inline-start: var(--space-5); }
/* La fila entera es el objetivo: acertar no depende de la puntería. */
.catalogo-fila {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-block-size: 44px;
  padding: var(--space-2);
  border-block-end: 1px solid var(--border);
  cursor: pointer;
  flex-wrap: wrap;
}
.catalogo-fila input[type="checkbox"] { inline-size: 24px; block-size: 24px; flex: 0 0 auto; }
.catalogo-nombre { flex: 1 1 auto; min-inline-size: 0; font-weight: var(--fw-semibold); }
.catalogo-peso { font-variant-numeric: tabular-nums; color: var(--text-secondary); white-space: nowrap; }
.marca-catalogo { color: var(--text-secondary); }
/* La huérfana se dice con palabra y signo; el borde solo acompaña · §8.5. */
.marca-catalogo--huerfana {
  color: var(--text);
  font-weight: var(--fw-bold);
  border: 2px solid currentColor;
  border-radius: var(--radius-sm);
  padding-inline: var(--space-2);
}

/* El número de lo que falta, arriba y legible · `D-594`. */
.cifra-pendiente {
  font-size: 1.12em;
  font-weight: var(--fw-semibold);
  color: var(--text);
}
.catalogo-fila select.input { flex: 1 1 14rem; min-inline-size: 0; }

/* ─── EL LÁPIZ, DENTRO DE SU TARJETA · `D-612` ─────────────────────────────
 *
 * Kike: *«el lápiz sale fuera de cada tarjeta de opciones en Administración»*.
 * Medido: **los 27 se salían**, hasta 36 px en la escala normal y **48 en la
 * máxima**.
 *
 * La causa: el lápiz se posiciona respecto a `.tarjeta-modulo-caja`, y la
 * tarjeta de dentro no la llenaba —la caja se estira con la rejilla y la
 * tarjeta se quedaba a su tamaño—, así que el borde al que se anclaba no era el
 * borde que se ve.
 *
 * Se hace que coincidan. **No se le devuelve la palabra** —se quitó por una
 * razón— y **no se estira**: conserva sus 44 px. */
.tarjeta-modulo-caja > .tarjeta-modulo { flex: 1 1 100%; min-inline-size: 0; }

/* Y el título de la tarjeta deja libre la esquina del lápiz · `D-612`: con el
 * botón flotando, reservar el hueco es lo que impide que el texto pase por
 * debajo el día que un nombre sea largo. */
.tarjeta-modulo-caja > .tarjeta-modulo > h3 { padding-inline-end: 52px; }

/* Las flechas del orden del menú · `D-630`: 44 px cada una y el número al lado,
 * para que se pueda mover con el ratón o escribiendo el puesto. */
.flechas-orden { display: flex; align-items: center; gap: var(--space-2); }
/* Y con el fondo de la casa, no el que salga: medido en tema claro, estas dos
 * salían con fondo negro y texto casi negro —contraste 1,27—, o sea invisibles.
 * Transparente con borde, como los demás botones de icono · `D-630`. */
.flechas-orden .btn--icon {
  min-inline-size: 44px;
  min-block-size: 44px;
  background: transparent;
  color: var(--text);
  border: 2px solid var(--border-strong);
}
.flechas-orden .btn--icon:hover { background: var(--surface-hover); }
.input-orden { inline-size: 5rem; text-align: center; font-variant-numeric: tabular-nums; }

/* ─── EL PRIMER WIDGET DEL PANEL · lo facturado contra los años ─────────────
 *
 * Kike: *«mi interés es, al mirar, saber en qué estamos»*. El gráfico aprovecha
 * el ancho de su tarjeta —prioridad escritorio e iPad, que es donde lo mira— y
 * el `viewBox` hace el resto sin recalcular nada.
 *
 * Los tonos salen de la paleta de Flow, puestos 12, 3, 7 y 10, elegidos con el
 * validador: son los únicos cuatro que pasan la separación para daltonismo en
 * las cuatro combinaciones de tema y contraste · `D-659`. */
/* ─── EL DIBUJO Y SU LEYENDA, EN DOS COLUMNAS · `D-668` ─────────────────────
 * Kike: *«en Money la leyenda va al lado, en su columna»*. Antes los rótulos
 * iban sueltos DENTRO del dibujo, pisando las líneas. El gráfico se queda con
 * el ancho que sobra —prioridad escritorio e iPad— y la leyenda con lo justo. */
.grafico-con-leyenda {
  display: flex; gap: var(--space-5); align-items: stretch;
  margin-top: var(--space-3);
}
/* ─── PRIMERO CRECE LA CAJA · `D-681` ──────────────────────────────────────
 * El tip creció al llevar la línea comparativa y más aire, y a escala grande se
 * salía 80 px por abajo, tapando cuatro cifras del eje y la tabla. *Un tip que
 * tapa lo que explica estorba más de lo que ayuda* — y **una cifra nunca se
 * encoge para que quepa**: crece la caja.
 *
 * El mínimo va en `em`, así que sube con el texto: a escala máxima la caja es
 * más alta porque el tip también lo es. */
.grafico-con-leyenda .grafico-envoltura {
  position: relative; flex: 1 1 auto; min-width: 0;
  min-height: 26em;
}
.grafico-anos { width: 100%; height: auto; display: block; }

.grafico-rejilla { stroke: var(--border); stroke-width: 1; opacity: .45; }
/* ─── EL EJE, MEDIDO EN PANTALLA Y NO EN EL `viewBox` · 07/09/2026 ──────────
 *
 * Estaba en 22 px y **se veía a 14,0**: el `viewBox` escala el dibujo al ancho
 * de su tarjeta, así que el número declarado no es el que se lee. *Se mide en
 * pantalla, que es donde está el ojo.* 26 px devuelven 16,6 px a 1440 de ancho
 * —el piso son 16,5—, medido con el navegador antes y después. */
.grafico-eje     { fill: var(--text-secondary); font-size: 26px; }

/* ─── EN EL TELÉFONO, MENOS RÓTULOS Y MÁS GRANDES · Kike, 07/09/2026 ────────
 *
 * *«Menos rótulos en pantalla estrecha.»* A 390 px de ancho los 26 px del
 * `viewBox` se veían a **10,0**, porque el dibujo escala con el ancho de su
 * tarjeta. **Quitar rótulos no agranda los que quedan**: les deja sitio para
 * agrandarlos sin que se pisen, y por eso las dos reglas van juntas.
 *
 * 44 px devuelven 16,9 px a 390 —medido—, y con uno de cada dos meses fuera no
 * se toca ninguno con el de al lado. Kike mira Flow desde el teléfono: *un
 * gráfico que no se lee ahí es un gráfico que no se lee.*
 *
 * El corte en 640 px y no en 390: entre los dos anchos el problema es el mismo
 * y menor, y una regla que solo vale para un teléfono concreto deja fuera al
 * siguiente. */
@media (max-width: 640px) {
  .grafico-eje { font-size: 44px; }
  .grafico-eje--alterno { display: none; }
}
/* Y un escalón más para las pantallas de 320-380 px, donde 44 px se veían a
   14,0. No es un teléfono raro: es el ancho de los que siguen en uso, y §8.5 no
   admite «por debajo del piso salvo en pantallas pequeñas». Medido a 320 px de
   ancho: con 54 px se quedaban en 16,0 —por debajo—, y con 58 llegan a 17,2 sin
   que los ocho rótulos se toquen. *El número sale de la medida, no del cálculo.* */
@media (max-width: 380px) {
  .grafico-eje { font-size: 58px; }
}
.grafico-rotulo  { font-size: 24px; font-weight: 600; }
.grafico-rotulo--actual { font-size: 28px; font-weight: 700; }

/* La zona sensible cubre la columna del mes: acertar un punto de 6 px con el
   ratón es una puntería que nadie tiene que tener. */
/* Los doce meses son BOTONES de HTML encima del dibujo · `D-661`: un `<g>` con
   `tabindex` no movía el foco en Chrome, medido. Ocupan todo el alto del área
   de datos, así que acertar uno con el ratón es fácil y con el dedo también. */
.grafico-meses {
  position: absolute; inset: 0; pointer-events: none;
}
.grafico-mes {
  position: absolute; top: 6%; height: 82%;
  min-width: 44px; margin: 0; padding: 0;
  background: transparent; border: 0; cursor: pointer;
  pointer-events: auto; border-radius: var(--radius-md);
}
/* ─── LA BANDA DEL MES, TRANSLÚCIDA Y DEBAJO · `D-673` ──────────────────────
 *
 * Kike: *«al posicionar el ratón sobre los puntos de los meses aparece una barra
 * gris totalmente opaca y sobre la caja de los montos»*. Y era exacto: la banda
 * usaba un color OPACO y la capa de botones va **encima** del dibujo, así que
 * tapaba las líneas y las cifras del eje.
 *
 * *Un tip que tapa lo que explica estorba más de lo que ayuda* — y la banda es
 * parte del tip. Ahora es una mezcla al 12 %: se ve dónde está el ratón y se
 * sigue leyendo lo que hay debajo. */
.grafico-mes:hover,
.grafico-mes--activo {
  background: color-mix(in srgb, var(--text) 12%, transparent);
}
/* El foco se ve con un borde, no con el color: el color nunca es la única señal. */
.grafico-mes:focus-visible {
  outline: 3px solid var(--text); outline-offset: -3px;
}
.grafico-punto { stroke: var(--surface); stroke-width: 2; }

/* Junto al punto, no en una esquina: lo coloca `panel-facturado.js` · `D-668`. */
.grafico-tip {
  position: absolute; top: 0; left: 0; z-index: 2;
  background: var(--surface); border: 2px solid var(--border-strong);
  border-radius: var(--radius-md);
  /* ─── MÁS AIRE · `D-678` ────────────────────────────────────────────────
     Kike: *«darle un poquito más de margen a la cajita para que no se vea todo
     tan apiñado»*. Del relleno 3 al 4, y las líneas de la lista separadas. */
  padding: var(--space-4) var(--space-5);
  min-width: 15ch; box-shadow: var(--shadow-2);
  /* NUNCA más ancho que su caja: se coloca junto al punto, y sin este tope un
     tip largo en diciembre empujaba la tarjeta hacia fuera · `D-668`. */
  max-width: min(28ch, 100%);
}
.grafico-tip strong { display: block; margin-bottom: var(--space-2); }
.grafico-tip .lista-falta li { padding-block: 2px; }

/* ─── LA LÍNEA COMPARATIVA · `D-677` ────────────────────────────────────────
   Discreta, debajo de las cifras y separada por una raya. El triángulo lleva
   color, pero **la palabra y el porcentaje van siempre**: el color no puede ser
   la única señal, y una forma tampoco. */
.tip-comparativa {
  margin: var(--space-3) 0 0; padding-top: var(--space-2);
  border-top: 1px solid var(--border);
  font-size: var(--fs-caption); color: var(--text-secondary);
  /* ─── EN UNA SOLA LÍNEA · `D-685` ──────────────────────────────────────
     Sin envolver: si a alguna escala no cupiera, la línea se partiría en dos y
     es justo lo que Kike pidió quitar. Se comprueba en las cuatro. */
  white-space: nowrap;
}
/* El porcentaje es EL DATO: en negrita y con el color del texto normal, no con
   el del triángulo — el color no distingue nada aquí. */
.tip-comparativa strong { display: inline; color: var(--text); }
.tip-triangulo { font-size: 1.1em; }
/* El par que Flow ya usa para «entra» y «sale»: no se inventa un verde y un rojo
   nuevos, y así el triángulo dice lo mismo que dicen las cifras en el resto. */
.tip-comparativa--encima .tip-triangulo { color: var(--income-ink); }
.tip-comparativa--debajo .tip-triangulo { color: var(--expense-ink); }
/* El año en curso, dentro del tip: más grande y más grueso · `D-672`. Y con su
   año escrito en la misma línea, así que el resalte no es la única señal. */
.grafico-tip .tip-actual { font-size: var(--fs-lg); }
.grafico-tip .tip-actual strong { display: inline; font-weight: 700; }
/* El tip nunca tapa lo que explica: va encima del dibujo pero deja ver el fondo
   de la tarjeta, no las líneas · `D-673`. */
.grafico-tip { backdrop-filter: none; }

/* En columna, al lado del dibujo: una fila por año, con su cifra debajo. */
.leyenda-anos {
  list-style: none; margin: 0; padding: 0;
  display: flex; flex-direction: column; gap: var(--space-4);
  flex: 0 0 auto; min-width: 14ch;
  /* CENTRADA EN VERTICAL · `D-687`: el bloque se reparte el alto del dibujo y
     queda a media altura, ni pegado arriba ni desbordando por abajo. */
  justify-content: center;
}
/* ─── A LA DERECHA Y CENTRADA EN VERTICAL · `D-679`, `D-687` ───────────────
   Kike las quiere alineadas al lado derecho, y el bloque entero centrado en
   vertical dentro de la tarjeta. `text-align: right` no basta —alinea el texto
   dentro de su caja, no las cajas— así que se alinean las cajas al final. */
/* Cada fila: el año con su marca al lado, y la cifra debajo · `D-686`. Se
   alinean las CAJAS al final —no solo el texto— así que los cuatro bordes
   derechos caen en el mismo píxel. */
.leyenda-ano {
  display: flex; flex-direction: column; align-items: flex-end;
  text-align: right; gap: 2px;
}
.leyenda-cabeza {
  display: flex; align-items: center; gap: var(--space-2);
}
.leyenda-marca {
  width: 28px; height: 8px; flex: 0 0 auto; margin-top: .45em;
  display: inline-flex; align-items: center;
}
.leyenda-trazo { width: 28px; height: 8px; display: block; }
.leyenda-ano--actual strong { font-size: var(--fs-lg); }

/* El área del año en curso, translúcida: separa sin tapar los grises. */
.grafico-area { opacity: .14; }

/* A partir de aquí no caben dos columnas: la leyenda va debajo, en fila. */
@media (max-width: 900px) {
  .grafico-con-leyenda { flex-direction: column; }
  .leyenda-anos { flex-direction: row; flex-wrap: wrap; gap: var(--space-2) var(--space-5); }
}

.grafico-tabla { margin-top: var(--space-4); }
.grafico-tabla summary { cursor: pointer; min-height: 44px; display: flex; align-items: center; }

/* A partir de aquí el tip no cabe flotando encima: va debajo, a lo ancho. */
@media (max-width: 720px) {
  /* En estrecho el tip flotante taparía media columna: va debajo, a lo ancho. */
  .grafico-tip { position: static; margin-top: var(--space-3); }
}

/* ─── EL TÍTULO DEL COMPARADOR, AL CENTRO · `D-680` ─────────────────────────
 * Kike lo pidió centrado en su tarjeta. La ⓘ se queda pegada al título —explica
 * lo que el título nombra— así que se centran los dos juntos. */
.card-head--centrada {
  position: relative;
  justify-content: center; text-align: center;
}
/* El título va al CENTRO DE LA TARJETA, no al centro de lo que sobra: con la ⓘ
   en el flujo el título se iba 28 px a la izquierda, medido. La ⓘ se saca del
   flujo y se queda a la derecha, sin empujar nada. */
.card-head--centrada h2 { margin-inline: auto; }
.card-head--centrada .pista-boton {
  position: absolute; right: 0; top: 50%; transform: translateY(-50%);
}

/* ─── EL MOTIVO, AL PIE DEL GRÁFICO · `D-689` ───────────────────────────────
 *
 * Kike: *«la pregunta es por qué»*. Centrado y legible, con su cifra y su
 * porcentaje. La caja **reserva su alto** aunque esté vacía: sin eso, el
 * gráfico daría un salto cada vez que el ratón entra y sale.
 *
 * El color dice el sentido, pero **nunca solo el color**: la palabra «más» o
 * «menos» y el porcentaje van siempre escritos. */
.motivo-pie {
  margin: var(--space-4) 0 0;
  /* El alto reservado sube con el texto: la tarjeta no puede saltar al entrar y
     salir el ratón, y con letra más grande hacen falta más em · `D-697`. */
  min-height: 3.6em;
  text-align: center;
  /* MÁS GRANDE, que lo pidió Kike: es la frase que contesta «por qué». */
  font-size: var(--fs-lg);
  line-height: 1.5;
}
.motivo-vacio { color: var(--text-secondary); }
.motivo-parte { color: var(--text); }
.motivo-parte--mas strong { color: var(--income-ink); }
.motivo-parte--menos strong { color: var(--expense-ink); }

/* ============================================================
   EL PANEL · la fila de análisis y la fila de lo que se debe
   ============================================================ */

/* ─── EL 66 / 33, QUE ES `.tercios` AL REVÉS ───────────────────────────────
 *
 * `.tercios` reparte `1fr 2fr`: el pequeño a la izquierda, que es lo que
 * necesitan Facturas y Movimientos. Aquí manda lo contrario —Kike pidió la
 * comparación al 66 % y el reparto al 33 %—, así que se invierte, y **solo se
 * invierte eso**: el hueco, el margen y el plegado a una columna en estrecho
 * los sigue poniendo `.tercios`. Una clase nueva que redeclarara todo acabaría
 * discrepando el día que se toque el original. */
.tercios--ancho-izquierda { grid-template-columns: 2fr 1fr; }

/* ─── Y SE APILAN POR DEBAJO DE 1000 px, NO DE 640 · `D-703` ──────────────
 *
 * Con el corte en 640 el iPad vertical —820 px— seguía repartiendo 66/33, y el
 * tercio de la derecha se quedaba en **229 px**. Medido a esa anchura y escala
 * máxima: la cifra del reparto se salía **145 px** de la página.
 *
 * 1000 px es el mismo umbral con el que `.tercios--tres` decide si caben tres
 * tarjetas. Un solo número para las dos filas del panel: *dos umbrales
 * distintos para la misma pregunta acaban discrepando*, y aquí la pregunta es
 * la misma —cuánta pantalla hay—. */
@media (max-width: 999.98px) {
  .tercios--ancho-izquierda { grid-template-columns: minmax(0, 1fr); }
}

/* ---------- 1 · La comparación por categoría ---------- */

.cat-comparativa { list-style: none; margin: var(--space-2) 0 0; padding: 0;
  display: grid; gap: var(--space-3); }

/* Nombre · barras · variación. La columna de las barras es la que cede, porque
   es la única cuyo contenido se puede recortar sin perder un dato: su cifra va
   escrita al lado. */
.cat-comparativa__fila {
  display: grid; gap: var(--space-2) var(--space-3);
  grid-template-columns: minmax(7rem, auto) minmax(0, 1fr) minmax(7rem, auto);
  align-items: center;
}
.cat-comparativa__nombre { font-weight: var(--fw-medium); min-inline-size: 0; }

.cat-comparativa__barras { display: grid; gap: var(--space-micro); min-inline-size: 0; }

/* Año · pista · cifra. El año va escrito en cada barra: sin él, cuál es cuál
   dependería del color. */
.cat-barra {
  display: grid; gap: var(--space-2); align-items: center;
  grid-template-columns: 3.2em minmax(0, 1fr) max-content;
}
.cat-barra__etq { font-size: var(--fs-caption); color: var(--text-secondary);
  font-variant-numeric: tabular-nums; }
.cat-barra__pista {
  block-size: .55rem; background: var(--surface-2);
  border: 1px solid var(--border); border-radius: var(--radius-sm); overflow: hidden;
}
.cat-barra__valor { display: block; block-size: 100%; background: var(--brand); }

/* ─── EL AÑO ANTERIOR SE DISTINGUE POR FORMA, NO SOLO POR TONO ─────────────
 *
 * Un tono más claro del mismo color es justo lo que peor se distingue con baja
 * visión. La barra de referencia lleva **rayas**, que es una diferencia de
 * forma y sobrevive al alto contraste y a la escala de grises. Y su año va
 * escrito al lado, que sigue siendo la señal que informa. */
.cat-barra__valor--antes {
  background: var(--text-muted);
  background-image: repeating-linear-gradient(
    45deg, transparent 0 3px, var(--surface-2) 3px 6px);
}
.cat-barra__cifra { font-size: var(--fs-caption); font-variant-numeric: tabular-nums;
  white-space: nowrap; }

/* La variación: palabra arriba, cifra debajo. */
.cat-comparativa__var { display: grid; gap: 0; text-align: end; font-size: var(--fs-caption); }
.cat-comparativa__var .num { font-variant-numeric: tabular-nums; white-space: nowrap; }
.var--sube  { color: var(--income-ink); }
.var--baja  { color: var(--expense-ink); }
.var--igual { color: var(--text-secondary); }

/* En alto contraste el color de la variación cede y se queda la palabra, que es
   la que informa: «subió 22,1 %» se lee igual sin tinte. */
[data-contrast="high"] .var--sube,
[data-contrast="high"] .var--baja,
[data-contrast="high"] .var--igual { color: inherit; }

.cat-total { margin-block-start: var(--space-3); }

/* ---------- 2 · El reparto ---------- */

/* Cifra y porcentaje van juntos y en dos renglones cuando no caben: el
   porcentaje dice el peso y la cifra dice cuánto es, y ninguno de los dos se
   encoge para que quepa el otro. */
/* ─── LA CIFRA DEL REPARTO CEDE, Y NO SE ENCOGE · `D-703` ─────────────────
 *
 * `.grafico-barras__fila` reparte `minmax(0, 1fr) 5rem max-content`, y
 * `max-content` **no cede**: en una tarjeta estrecha con texto grande, «S/
 * 250.853,53» pedía más de lo que había y empujaba la página entera.
 *
 * Se acota a `.cat-reparto` y no se toca `.grafico-barras`: Comisiones lo usa
 * en una tarjeta más ancha y con cifras más cortas, y cambiarlo allí sin
 * medirlo sería arreglar a ciegas una pantalla que hoy está bien.
 *
 * La cifra no se achica: **se parte en dos renglones**, con su porcentaje
 * debajo. Primero crece la caja, luego se reparte en más renglones, y reducir
 * es el último recurso — que aquí no hace falta. */
/* La forma la pone ahora `.grafico-barras__fila`: dos renglones, la pista a lo
   ancho. Lo que se conserva de aquí es que la cifra pueda partirse en dos
   renglones —`D-703`—, que es lo que esta regla venía a proteger. */
.cat-reparto .grafico-barras__cifra { white-space: normal; text-align: end; overflow-wrap: anywhere; }
.cat-reparto__pct { display: block; color: var(--text-secondary); }
.cat-reparto__cuantas { color: var(--text-secondary); }

/* «Otras» no es una categoría: es un grupo. Se distingue por el punteado de su
   barra —forma, no color— y por el recuento que lleva escrito al lado. */
.cat-reparto__otras .grafico-barras__valor {
  background: var(--text-muted);
  background-image: repeating-linear-gradient(
    45deg, transparent 0 3px, var(--surface-2) 3px 6px);
}

/* ---------- 3 · Por vencer ---------- */

.por-vencer { list-style: none; margin: var(--space-2) 0 0; padding: 0; }

/* Fecha · concepto · importe · cuándo. El concepto es el único que cede.
   `align-items: baseline` alinea los cuatro por el texto y no por la caja, que
   es lo que descuadraba las marcas. */
.por-vencer__fila {
  display: grid; gap: var(--space-1) var(--space-2);
  grid-template-columns: max-content minmax(0, 1fr) max-content;
  align-items: baseline;
  padding: var(--space-1) var(--space-2);
  border-block-end: 1px solid var(--border);
}
.por-vencer__fila:last-child { border-block-end: 0; }
.por-vencer__fecha { font-variant-numeric: tabular-nums; color: var(--text-secondary);
  font-size: var(--fs-caption); }
.por-vencer__concepto { min-inline-size: 0; overflow-wrap: anywhere; }
.por-vencer__importe { font-variant-numeric: tabular-nums; white-space: nowrap;
  text-align: end; }
/* El «cuándo» ocupa la fila de abajo, alineado con el concepto: en un widget al
   33 % una cuarta columna dejaría el importe en dos renglones. */
.por-vencer__cuando { grid-column: 2 / -1; text-align: end; font-size: var(--fs-caption); }

/* Las marcas son palabras, no colores: «deuda», «previsto», «parcial». */
.por-vencer__marca {
  font-size: var(--fs-caption); color: var(--text-secondary);
  border: 1px solid var(--border); border-radius: var(--radius-pill);
  padding: 0 var(--space-1); margin-inline-start: var(--space-1);
  white-space: nowrap;
}

/* ─── EL SEPARADOR DE MES ──────────────────────────────────────────────────
 *
 * Kike: *«pones un separador y lo indicas»*. Las dos cosas: una línea que se
 * ve y **el nombre del mes escrito**, porque una línea sola no dice de qué mes
 * es lo que viene debajo. Sin `text-transform`: «Ya en septiembre de 2026» se
 * lee entero. */
.por-vencer__separador {
  display: flex; align-items: center; gap: var(--space-2);
  margin-block: var(--space-2) var(--space-1);
}
.por-vencer__separador::after {
  content: ""; flex: 1 1 auto; block-size: 2px; background: var(--border-strong);
}
.por-vencer__separador-txt {
  font-size: var(--fs-caption); font-weight: var(--fw-semibold); color: var(--text-secondary);
}

.por-vencer__totales { margin-block-start: var(--space-3); }

/* ─── Y EN ESTRECHO LA FILA SE APILA · `D-703` ─────────────────────────────
 *
 * Medido el 25 de agosto de 2026 a 600 px de ventana y escala máxima: la página
 * **se salía 32 px** y la barra horizontal arrastraba el menú entero. El
 * culpable estaba localizado —`.cat-barra`, con su cifra en `max-content`: 175
 * px que no ceden— dentro de una columna central que a esa escala se quedaba
 * con unos 100, porque las dos laterales piden 7 rem cada una y 7 rem crecen
 * con el texto.
 *
 * ─── POR QUÉ POR CONTENEDOR Y NO POR VENTANA ───────────────────────────────
 *
 * Lo que decide si estas tres columnas caben **no es el ancho de la ventana,
 * es el de la tarjeta**: la misma tarjeta al 66 % de una pantalla ancha y al
 * 100 % de una estrecha puede medir lo mismo. `.card.stat` ya declara
 * `container-type: inline-size` —de él depende que las cifras se ajusten con
 * `100cqi`—, así que el contenedor ya existe y no hay que inventarlo.
 *
 * Y el umbral va en `em` a propósito, al revés que el de `.tercios--tres`:
 * allí se pregunta cuánta pantalla hay —píxeles— y aquí cuánto texto cabe
 * —caracteres—. *Son dos preguntas distintas y por eso llevan dos unidades
 * distintas.* */
@container (max-width: 34em) {
  .cat-comparativa__fila { grid-template-columns: minmax(0, 1fr); }
  /* La variación deja de ir a la derecha: en una sola columna, alineada al
     final quedaría suelta debajo de las barras y lejos de su nombre. */
  .cat-comparativa__var { text-align: start; }
  /* Año arriba con su cifra, pista debajo: la cifra ya no compite por el ancho
     con la barra, y **no se encoge para caber** — se cambia de sitio, que es lo
     que manda §8.5 cuando algo no entra. */
  .cat-barra { grid-template-columns: 3.2em minmax(0, 1fr); }
  .cat-barra__cifra { grid-column: 2; text-align: end; }
}

/* ============================================================
   EL REPARTO EN PIE · porciones, etiquetas y la lista de debajo
   ============================================================ */

.reparto-envoltura { margin-block-start: var(--space-3); }
.reparto-lienzo { position: relative; }
.reparto-pie { inline-size: 100%; block-size: auto; display: block; }

/* ─── LAS PORCIONES SE SEPARAN POR FORMA, NO SOLO POR COLOR ────────────────
 *
 * En un pie las porciones **se tocan**, así que no basta con que cada una se
 * vea contra el fondo: dos vecinas de tonos parecidos se leen como una sola
 * porción grande. El trazo del color de la tarjeta abre un surco entre ellas
 * —una separación de forma— que sobrevive al alto contraste y a la escala de
 * grises, donde el color no sobrevive. */
.reparto-porcion {
  stroke: var(--surface);
  stroke-width: 3;
  stroke-linejoin: round;
  cursor: default;
}
/* En alto contraste el surco se ensancha: ahí el color pierde peso a propósito
   y la forma es la que tiene que hacer el trabajo. */
[data-contrast="high"] .reparto-porcion { stroke-width: 5; }

/* Las porciones NO reciben foco —no son controles, y «Otras» mide 2 px— así que
   no llevan regla de foco. Lo único que hacen con el ratón es aclararse, para
   que se vea cuál está diciendo su monto. */
.reparto-porcion:hover { filter: brightness(1.08); }

/* ─── LAS ETIQUETAS SON HTML, NO `<text>` DE SVG · `D-710` ────────────────
 *
 * Un `<text>` de SVG va en unidades del `viewBox`, así que **no sigue a
 * `data-escala`**. Y como el dibujo se estrecha cuando el resto del contenido
 * crece, al pedir letra más grande el pie daba letra **más pequeña**: medido,
 * 15 px a escala normal y **12 a la máxima**. Justo lo contrario de lo que hace
 * falta, y en el widget de quien menos puede permitírselo.
 *
 * En HTML heredan el cuerpo de la página como todo lo demás. Se colocan en
 * porcentaje sobre la misma caja que escala el dibujo, así que siguen a su
 * porción igual que antes. */
.reparto-lienzo .reparto-pie { position: relative; z-index: 0; }
.reparto-etqs { position: absolute; inset: 0; pointer-events: none; }
.reparto-etq {
  position: absolute;
  display: grid;
  gap: 0;
  line-height: 1.15;
  font-size: var(--fs-caption);
  white-space: nowrap;
}
.reparto-etq__n { font-weight: var(--fw-semibold); color: var(--text); }
.reparto-etq__p { color: var(--text-secondary); font-variant-numeric: tabular-nums; }

.reparto-etq--dentro { transform: translate(-50%, -50%); text-align: center; }
/* Fuera, ancladas a su banda: la de la derecha empieza donde acaba su guía y la
   de la izquierda acaba donde empieza la suya. */
.reparto-etq--der { transform: translate(0, -50%); text-align: start; }
.reparto-etq--izq { transform: translate(-100%, -50%); text-align: end; }
/* El nombre no se recorta ni se encoge: si no cabe en una línea, se parte. Es
   lo que manda §8.5 — primero crece la caja, y aquí la caja es la banda. */
/* 28 % y no 29: con 29 el redondeo del `translate(-100%)` sacaba la de la
   izquierda **2 px fuera del lienzo**, medido. */
.reparto-etq--der, .reparto-etq--izq {
  inline-size: 28.5%;
  white-space: normal;
  overflow-wrap: anywhere;
}

/* ─── LA LEYENDA, EN SU COLUMNA · `D-711` ─────────────────────────────────
 *
 * Las etiquetas sueltas alrededor del pie **no caben**: medido con ocho
 * porciones, seis pares se pisaban y cuatro se salían del lienzo. Es la misma
 * lección de `D-668` en el comparador de años, y se resuelve igual: en columna,
 * al lado, donde no puede pisarse nada porque no flota.
 *
 * Al lado mientras quepa, y debajo cuando la tarjeta se estrecha: el pie no
 * puede encogerse más para hacerle sitio a un rótulo. */
/* ─── SOLO EL CÍRCULO, AL 85 % · Kike, 25/08/2026 · segundo turno ─────────
 *
 * La versión con etiquetas fuera medía **431 × 707 px para un círculo de 173**,
 * y Kike lo llamó por su nombre: *«eso es aire, no gráfico»*. Fuera las ocho
 * etiquetas, fuera las ocho guías, fuera las bandas laterales.
 *
 * Ahora **el 85 % es el círculo**, no el conjunto: el `viewBox` es cuadrado y el
 * círculo lo llena, así que darle el 85 % a la caja se lo da al círculo. Quién
 * es cada porción lo dice la lista de abajo, con su cuadrado de color · `D-720`. */
.reparto-envoltura { inline-size: 85%; margin-inline: auto; }
.reparto-leyenda { list-style: none; margin: 0; padding: 0;
  display: grid; gap: var(--space-2); }
.reparto-leyenda__fila {
  display: grid; gap: var(--space-2);
  grid-template-columns: max-content minmax(0, 1fr) max-content;
  align-items: baseline;
}
/* La marca de color es cuadrada y con borde: sin borde, la de «Otras» se pierde
   contra la tarjeta en alto contraste, que es donde el relleno se apaga. */
.reparto-leyenda__marca {
  inline-size: 0.85em; block-size: 0.85em; border-radius: var(--radius-sm);
  border: 1px solid var(--border-strong); align-self: center;
}
.reparto-leyenda__nombre { min-inline-size: 0; overflow-wrap: anywhere; }
.reparto-leyenda__pct { color: var(--text-secondary); font-variant-numeric: tabular-nums;
  white-space: nowrap; }

/* ─── EL PORCENTAJE SE PINTA CONTRA SU PROPIA PORCIÓN ─────────────────────
 *
 * Con ocho colores debajo, unos son claros y otros oscuros: **un solo color de
 * texto dejaría alguno por debajo del 4,5:1**. La regla lo dice —el porcentaje
 * se pinta del tono que contraste con SU porción, no todos del mismo— y
 * `contrast-color()` no está disponible, así que se resuelve con un fondo
 * propio del color de la tarjeta: **el fondo efectivo del texto es siempre el
 * mismo**, y su contraste se mide una vez y vale para las ocho.
 *
 * Es lo mismo que hacía antes, y ahora está dicho por qué se elige esto y no
 * ocho colores de texto distintos: ocho tonos calculados a mano son ocho sitios
 * donde equivocarse, y uno solo medido vale para todos. */
.reparto-etq--dentro {
  background: var(--surface);
  border: 1px solid var(--border);   /* el color lo pone cada etiqueta: es el de SU porción */
  border-radius: var(--radius-sm);
  padding: 2px var(--space-1);
}
.reparto-guia { stroke: var(--border-strong); stroke-width: 1.5; }

/* ─── «EL MONTO» SE RETIRÓ EL 25/08/2026 ──────────────────────────────────
 *
 * Era el rótulo que nombraba la porción bajo el ratón con su importe. Kike lo
 * vio suelto entre el gráfico y la lista y preguntó qué era; la respuesta es que
 * **no decía nada que la lista no diga mejor** —los ocho importes a la vez, con
 * su color, sin pedir que se pose nada— y que su rótulo vacío era una frase
 * incompleta fuera de la ⓘ. Sus reglas se van con él, y su guion también. */

/* ---------- La lista de debajo: todas, de menos a más ---------- */

.reparto-lista__tit {
  font-size: var(--fs-body); margin: var(--space-4) 0 var(--space-2);
}
.reparto-lista { list-style: none; margin: 0; padding: 0; }
.reparto-lista__fila {
  display: grid; gap: var(--space-1) var(--space-3);
  grid-template-columns: max-content minmax(0, 1fr) max-content max-content;
  align-items: baseline;
  padding: var(--space-1) 0;
  border-block-end: 1px solid var(--border);
}
/* El color de su categoría, el mismo que su porción. Cuadrado y con borde: sin
   borde, el de «Otras» se pierde contra la tarjeta en alto contraste, que es
   donde el relleno se apaga. */
.reparto-lista__marca-color {
  inline-size: 0.9em; block-size: 0.9em; border-radius: var(--radius-sm);
  border: 1px solid var(--border-strong); align-self: center;
}
.reparto-lista__fila:last-child { border-block-end: 0; }
.reparto-lista__nombre { min-inline-size: 0; overflow-wrap: anywhere; }
.reparto-lista__cifra, .reparto-lista__pct {
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
/* Un punto más grandes que antes, que es lo que pidió Kike: el porcentaje es lo
   que se compara de un vistazo entre filas. */
.reparto-lista__pct { color: var(--text-secondary); min-inline-size: 4.5em; text-align: end;
  font-size: var(--fs-lg); }
/* Cuál no está en el pie, dicho en palabra: quien la busque en el dibujo y no
   la encuentre, aquí lee por qué. */
.reparto-lista__marca {
  font-size: var(--fs-caption); color: var(--text-secondary);
  border: 1px solid var(--border); border-radius: var(--radius-pill);
  padding: 0 var(--space-1); margin-inline-start: var(--space-1); white-space: nowrap;
}

/* En una tarjeta estrecha la cifra y el porcentaje bajan a su propia línea en
   vez de encogerse: primero se reparte en más renglones, reducir es el último
   recurso. */
@container (max-width: 26em) {
  .reparto-lista__fila { grid-template-columns: max-content minmax(0, 1fr); }
  .reparto-lista__cifra, .reparto-lista__pct { grid-column: 2; }
}

/* ============================================================
   UN FORMULARIO SE REPARTE POR SU CAJA, NO POR LA VENTANA
   ============================================================ */

/* ─── EL DEFECTO, MEDIDO · `D-715` ─────────────────────────────────────────
 *
 * Kike abrió `Billing › Nueva factura › Cliente › crear al vuelo` y trajo la
 * captura: los tres combos leyéndose en vertical —«— ninguno —» ocupando doce
 * renglones—, cuatro campos de un carácter de ancho, la botonera cortada por el
 * borde y barra horizontal al pie.
 *
 * Medido antes de tocar nada, abriendo las dos aperturas:
 *
 *     el mismo formulario en `/admin/clientes/nuevo`   control 340 y 551 px  ✓
 *     el mismo formulario en el diálogo de Billing     control  26 px        ✗
 *                                                      combo 0 × 243 px      ✗
 *
 * **El formulario no tiene nada malo: lo tiene la caja.** Dos rejillas
 * encajadas mantienen su reparto donde no cabe, y las dos deciden mirando **el
 * ancho de la ventana**:
 *
 *   · `.form-grid` sigue en dos columnas —`base.css`, `@media (max-width: 720px)`
 *     es lo único que la baja a una—, así que parte 423 px en dos de 203;
 *   · y dentro, cada `.field` sigue repartiendo etiqueta y control en línea
 *     —`@media (min-width: 720px)`, más arriba en esta hoja—, así que de esos
 *     203 px la etiqueta se lleva **186** y al control le quedan **0**.
 *
 * La ventana del navegador mide 1.512 px y las dos condiciones se cumplen. **El
 * formulario vive en un diálogo de 578.** *Lo que decide si caben dos cosas en
 * línea es el ancho de la caja, no el de la pantalla* — y es la lección de
 * `D-702` al revés: allí un umbral en `rem` decidía algo que dependía de
 * píxeles; aquí un umbral en píxeles decide algo que depende del contenedor.
 *
 * ─── Y EN FLOW ESO NO ES UN CASO RARO: ES LA NORMA · §108 ──────────────────
 *
 * Kike: *«Flow maneja todo en ventanas, el contenedor es el módulo que lo llama,
 * maneja hasta 3 niveles.»* Un formulario casi nunca vive en la ventana del
 * navegador: vive dos o tres ventanas adentro. Preguntar por el contenedor no
 * es una precaución para un caso concreto — **es la única pregunta que
 * significa algo aquí**.
 *
 * ─── Y LA CAUSA SE DEMOSTRÓ ROMPIÉNDOLA ────────────────────────────────────
 *
 * A escala «muy grande» y «máximo» la rejilla del `.field` **ya está apagada**
 * —la regla lleva `:root:not([data-escala="muy_grande"])` desde que se escribió,
 * porque a esas escalas la etiqueta se comería el campo—. Medido en el mismo
 * diálogo, sin tocar nada:
 *
 *     normal      186px 0px    control  26 px · combo 243 px de alto · desborda
 *     grande      235px 0px    control  26 px · combo 303 px de alto · desborda
 *     muy_grande  apilado      control 309 px · combo  69 px         · NO desborda
 *     maximo      apilado      control 362 px · combo  81 px         · NO desborda
 *
 * Quitada la causa, el defecto desaparece. No hacía falta suponerlo.
 *
 * ─── EL ARREGLO ────────────────────────────────────────────────────────────
 *
 * El formulario declara su propia caja como contenedor, y las dos rejillas
 * preguntan **por ella**. El umbral va en `em` y no en píxeles a propósito: lo
 * que tiene que caber es *una etiqueta al lado de un control*, y la etiqueta
 * crece con la letra del usuario. En píxeles, el umbral acertaría a escala
 * normal y fallaría a la máxima.
 *
 * 46 em son 782 px a escala normal. Medido: el formulario mide **423 px en el
 * diálogo** y **1.561 px en su página**, así que el escalón cae holgado entre
 * los dos y ninguno queda al borde.
 *
 * Y no se toca la regla de arriba: sigue valiendo tal cual para la página. Lo
 * que se añade es el caso que no contemplaba. */
form.form-etiquetado { container: form-etq / inline-size; }

@container form-etq (max-width: 46em) {
  /* Una sola columna: dos de 203 px no son dos columnas, son dos campos rotos. */
  form.form-etiquetado > .form-grid { grid-template-columns: minmax(0, 1fr); }

  /* Y la etiqueta vuelve arriba, que es exactamente lo que ya hace esta misma
     hoja a escala «muy grande» — por la misma razón, y ahora también cuando lo
     estrecho es la caja. La cabeza del selector repite la de la regla original
     para no depender del orden entre dos especificidades distintas. */
  :root:not([data-escala="muy_grande"]):not([data-escala="maximo"])
  form.form-etiquetado .field:not(.field--enlinea):not(.field--compacto) {
    display: block;
  }
  :root:not([data-escala="muy_grande"]):not([data-escala="maximo"])
  form.form-etiquetado .field:not(.field--enlinea):not(.field--compacto) > .label {
    text-align: start;
    padding-block-start: 0;
    margin-block-end: var(--space-1);
  }
  /* Un campo que pedía la fila entera ya la tiene: en una columna sobra. */
  form.form-etiquetado > .form-grid > .field[data-ancho="largo"] { grid-column: auto; }
}

/* ─── Y LA BOTONERA NO SE VA CON EL CONTENIDO · `D-715` ────────────────────
 *
 * Kike: *«la botonera queda cortada por el borde inferior: se ve la mitad
 * superior de Cancelar y Guardar»*. Con el ancho arreglado el diálogo ya solo
 * necesita 88 px de desplazamiento para llegar a ellos —eran 734—, pero **88 px
 * siguen siendo un botón que no está donde se mira**.
 *
 * `.dialogo` ya sabe dejar quieto un pie: `.dlg-pie` va fuera del cuerpo y no se
 * desplaza. Aquí no se puede usar, y por una razón que conviene no olvidar: **la
 * botonera llega dentro del formulario traído**, porque el diálogo sirve el
 * mismo parcial que el catálogo en vez de copiar sus campos. Sacarla exigiría
 * partir el parcial en dos, y entonces el diálogo y la página dejarían de ser el
 * mismo formulario — que es lo que este diálogo existe para evitar.
 *
 * Así que se queda donde está y se pega al pie de su propio desplazamiento. El
 * fondo es obligatorio: sin él, el contenido pasa por debajo y se lee encima de
 * los botones. */
/* `form >` no vale: entre el cuerpo y el formulario hay un `section.card` que
   pone el parcial, y el selector no casaba con nada. Medido: la botonera seguía
   `static` con la regla puesta. *Una regla que no casa no falla: no pinta.* */
.dialogo .dlg-cuerpo form > .botonera {
  position: sticky;
  /* Pegada al borde inferior de su propio desplazamiento, y no a un valor
     negativo: con `-space-4` se quedaba **17 px por debajo del cuerpo**, medido.
     Un sticky que se pasa del borde no está pegado: está escondido. */
  inset-block-end: 0;
  background: var(--superficie-ventana);
  padding-block: var(--space-3);
  border-block-start: 1px solid var(--border);
  z-index: 1;
}

/* ─── CUÁNTOS PORCENTAJES CABEN DENTRO DEPENDE DE LA ESCALA · `D-721` ──────
 *
 * Al subir la escala el rótulo crece **y el círculo encoge**, así que las dos
 * cosas van en contra. Medido el 25/08/2026, porción a porción, comparando lo
 * que mide el texto con el arco que le toca:
 *
 *     normal      diámetro 406 px   caben 55,3 · 12,3 · 9,6 · 7,2
 *     grande               391 px   caben 55,3 · 12,3 · 9,6
 *     muy_grande           375 px   caben 55,3 · 12,3
 *     máximo               359 px   cabe  55,3
 *
 * Cada porción llega marcada con **hasta qué escala cabe la suya**, y aquí se
 * esconden las que ya no. *Ninguna se encoge y ninguna se sale*: la que no cabe
 * no se pinta, y su cifra está en la lista de debajo. */
:root[data-escala="grande"] .reparto-etq--n1,
:root[data-escala="muy_grande"] .reparto-etq--n1,
:root[data-escala="muy_grande"] .reparto-etq--n2,
:root[data-escala="maximo"] .reparto-etq--n1,
:root[data-escala="maximo"] .reparto-etq--n2,
:root[data-escala="maximo"] .reparto-etq--n3 { display: none; }

/* ============================================================
   TRES COMPARATIVOS POR CATEGORÍA EN UNA TARJETA
   ============================================================ */

/* En compacto el gráfico no es una tarjeta: es un bloque dentro de la que los
   agrupa. Tres tarjetas serían tres marcos y tres sombras para una sola idea. */
.grafico-bloque { min-inline-size: 0; }
.grafico-bloque + .grafico-bloque {
  margin-block-start: var(--space-5);
  padding-block-start: var(--space-5);
  border-block-start: 1px solid var(--border);
}
.grafico-bloque > .card-head { margin-block-end: var(--space-2); }
/* Sin leyenda al lado, el dibujo se queda con todo el ancho. */
.grafico-con-leyenda--solo { grid-template-columns: minmax(0, 1fr); }

/* ─── LA TIRA DE AÑOS · ocupa el sitio de la columna retirada ──────────────
 *
 * Decisión de Kike, 25/08/2026. Cada año con **su muestra de trazo dibujada**,
 * el año, y su total en soles. Cuatro por gráfico, doce en la tarjeta.
 *
 * `auto-fit` con un mínimo en `ch`: mientras quepan los cuatro van en una fila,
 * y cuando no, bajan a dos de dos. **Ninguna cifra se encoge para que quepa** —
 * lo que cambia es cuántas caben por fila, que es agrandar la caja, no apretar
 * el contenido. */
/* ─── LA TIRA ENVUELVE, NO REPARTE EN COLUMNAS IGUALES · 28/08/2026 ────────
 *
 * Era `repeat(auto-fit, minmax(min(14ch,100%), 1fr))`, y `auto-fit` **exige un
 * mínimo definido**: no admite `max-content`. Así que las columnas salían todas
 * del mismo ancho aunque una de ellas lleve más texto —la del año en curso, que
 * añade «hasta Ago»—, y esa se salía de su celda.
 *
 * Medido y **no es de este turno**: en el panel, a escala máxima, las tres tiras
 * desbordaban su celda **101,5 · 82,3 · 82,3 px**, con la cifra de un año encima
 * de la del siguiente. En la ventana de Billing pasaba ya a escala normal, 93 px.
 * *Una cifra encima de otra cifra no se lee, y las dos parecen decir algo.*
 *
 * En fila que envuelve cada año ocupa lo suyo y baja de renglón cuando no cabe.
 * *Nada se encoge para que quepa*: lo que cede es la fila, no el número.
 *
 * Toca el panel, y por eso se dice: **arregla un solape que ya estaba ahí**. */
.tira-anos {
  list-style: none; margin: 0 0 var(--space-3); padding: 0;
  display: flex; flex-wrap: wrap; gap: var(--space-1) var(--space-4);
}
.tira-ano {
  display: flex; align-items: baseline; gap: var(--space-2);
  font-size: var(--fs-caption); white-space: nowrap;
}
/* La muestra: el mismo `<svg>` de la leyenda lateral, no un dibujo nuevo. */
.tira-marca { flex: 0 0 auto; inline-size: 28px; align-self: center; }
.tira-marca .leyenda-trazo { display: block; inline-size: 28px; block-size: 8px; }
.tira-ano__n { color: var(--text-secondary); }
.tira-ano--actual .tira-ano__n { color: var(--text); font-weight: var(--fw-semibold); }
/* «hasta Ago» pegado a su año: comparar un periodo entero con uno a medias
   engaña, y sin esto el 2026 parecería un año completo que cayó. */
.tira-hasta { color: var(--text-secondary); font-weight: var(--fw-regular, 400); }
.tira-ano__c { margin-inline-start: auto; font-variant-numeric: tabular-nums; }
.tira-ano--actual .tira-ano__c { font-weight: var(--fw-semibold); }

/* ─── EL AIRE DE UNA TARJETA CRECE, PERO CON TECHO · `D-724` ───────────────
 *
 * Kike lo vio en la medida del informe anterior: el círculo pasa de 406 px a
 * escala normal a 359 a la máxima **manteniéndose en el 84,8 %**. Lo que encoge
 * no es el círculo: es la tarjeta. Su ancho de contenido va de **479 px a 424**.
 *
 * Medido, son tres cosas y las tres en `rem`, que es como está declarado todo el
 * espaciado de Flow:
 *
 *     el relleno de `.app-shell`      25,5 → 44,63 px   (−38 de interior)
 *     el hueco de la fila             17   → 29,75 px   (−13 en el reparto)
 *     el relleno de la propia tarjeta 25,5 → 44,63 px   (−38 de contenido)
 *
 * **No es un fallo puntual: es el sistema de espaciado.** Y en general está
 * bien —el aire alrededor de un texto tiene que crecer con el texto, o a escala
 * máxima las letras se pegan al borde—. Es `D-702` girado: allí un umbral en
 * `rem` decidía algo que dependía de píxeles; aquí algo en `rem` estrecha una
 * caja cuando crece la letra, y **agrandar la letra no estrecha la pantalla**.
 *
 * ─── POR QUÉ SOLO EN EL PANEL ──────────────────────────────────────────────
 *
 * Poner techo a `.card` tocaría todas las pantallas de Flow, y eso no lo ha
 * pedido nadie. Aquí sí importa y por una razón que no vale en general: **el
 * contenido de estas dos tarjetas es un dibujo**, y un dibujo no se lee por
 * líneas. El aire protege la lectura de un texto; a un círculo solo le quita
 * sitio.
 *
 * `clamp` conserva el crecimiento donde importa —de 20 a 32 px— y le pone un
 * tope. Medido después: el ancho de contenido a escala máxima sube de 424 a
 * 437 px y el círculo de 359 a 370. */
#panel-analisis > .card,
#panel-reparto { padding: clamp(20px, var(--space-5), 32px); }

/* El total bajo la lista, para que la tarjeta no quede desbalanceada frente a
   los tres gráficos de al lado. Se separa con una línea, no con aire: el aire
   solo lo alejaría de la lista de la que sale. */
.reparto-total {
  margin-block-start: var(--space-4);
  padding-block-start: var(--space-3);
  border-block-start: 2px solid var(--border-strong);
}

/* ============================================================
   LOS DOS PIES DE LA TARJETA · categorías y tipo de cliente
   ============================================================ */

.reparto-pieza + .reparto-pieza { margin-block-start: var(--space-5); }
.reparto-pieza__tit { font-size: var(--fs-body); margin: 0; }

/* ─── EL TOTAL, COMO REMATE DE LA COLUMNA · Kike, 26/08/2026 ───────────────
 *
 * *«Así quedará claro que es el resultado de la sumatoria de todas.»* Es una
 * fila más de la lista y usa **su misma rejilla**, así que el importe cae en la
 * misma columna que los de las categorías. Centrado o suelto no se leería como
 * el remate de nada.
 *
 * Y **crece** respecto a las filas: no se encoge nada para hacerle sitio. */
.reparto-lista--remate { margin-block-start: 0; }
.reparto-total__fila {
  border-block-start: 2px solid var(--border-strong);
  border-block-end: 0;
  padding-block-start: var(--space-2);
  font-size: var(--fs-lg);
}
/* El hueco de la marca de color se reserva aunque esté vacío: sin él la fila se
   corre a la izquierda y el importe deja de caer bajo los demás — que es
   exactamente lo que este remate existe para conseguir. */
.reparto-total__hueco { border: 0; background: none !important; }
/* El porcentaje no se pinta —un total es el 100 % y decirlo no informa— pero su
   columna se reserva, o el importe se movería respecto a las filas de arriba. */

/* ─── EL SEPARADOR QUE ABRE EL SEGUNDO PIE ────────────────────────────────
 * Un elemento gráfico, no un adorno: `--border-strong` es el token que ya se
 * mide a 3:1 contra el fondo en las cuatro combinaciones. *Un separador que no
 * se ve no separa.* */
.reparto-separador {
  border: 0;
  border-block-start: 2px solid var(--border-strong);
  margin-block: var(--space-5) 0;
}

/* ══════════════════════════════════════════════════════════════════════════
   CÓMO ESTAMOS · el anaquel
   ──────────────────────────────────────────────────────────────────────────
   Dos zonas separadas por una línea, y columnas dentro de cada una. Sin discos
   ni iconos en ninguna parte.

   NINGUNA CIFRA PARTE EN DOS LÍNEAS: todas las cajas de cifra llevan `nowrap`, y
   las columnas se dimensionan con `minmax(…, auto)` para que la caja crezca
   antes que partirse. Con baja visión, una cifra partida obliga a recomponerla
   justo cuando más dígitos tiene.

   EL UMBRAL VA EN PÍXELES DE VENTANA, NO EN `rem` (`D-702`).
   ══════════════════════════════════════════════════════════════════════════ */
/* `--sit-ancho-barra` es el MÍNIMO de la columna, no su medida: la barra se
   estira con la tarjeta y nunca baja de ahí. */
.situacion { grid-column: 1 / -1; --sit-ancho-barra: 12rem; }
.sit-vsep { background: var(--border-strong); inline-size: 1px; }
.sit-hsep { display: block; block-size: 1px; background: var(--border-strong);
            margin-block: var(--space-5); }
.sit-falta { color: var(--text-secondary); font-size: var(--fs-lg);
             padding-block: var(--space-4); margin: 0; }

/* ── Fila 1 · las tres preguntas, con el mismo peso ─────────────────────── */
.sit-fila1 {
  display: grid; grid-template-columns: 1fr 1px 1fr 1px 1fr;
  padding-block-end: var(--space-6);
  border-block-end: 1px solid var(--border-strong);
}
.sit-b { display: flex; flex-direction: column; padding-inline: var(--space-6); min-inline-size: 0; }
.sit-b:first-child { padding-inline-start: 0; }
.sit-b:last-child  { padding-inline-end: 0; }
.sit-b__preg { color: var(--text-muted); font-size: var(--fs-caption); }
.sit-b__rot  { color: var(--text-secondary); font-size: var(--fs-caption);
               letter-spacing: .04em; text-transform: uppercase;
               margin-block-start: 2px; }
/* La cifra nunca se encoge ni se parte: o cabe, o crece la caja. */
.sit-b__n {
  font-size: var(--fs-cifra-widget); line-height: 1.08; white-space: nowrap;
  margin-block-start: var(--space-1); font-variant-numeric: tabular-nums;
}
.sit-b__n--pendiente { color: var(--expense-ink); }
.sit-b__n--verde { color: var(--income-ink); }
.sit-b__n--rojo  { color: var(--expense-ink); }
.sit-b__n--falta { color: var(--text-secondary); }
.sit-b__sub { color: var(--text-secondary); font-size: var(--fs-caption);
              margin-block-start: var(--space-2); white-space: nowrap; }
/* La palabra cambia con el color: el color no es la única señal. */
.sit-estado {
  align-self: flex-start; margin-block-start: var(--space-2);
  font-size: var(--fs-caption); font-weight: var(--fw-semibold);
  border: 1px solid; border-radius: var(--radius-sm); padding: 1px var(--space-2);
  white-space: nowrap;
}
.sit-estado--balance { color: var(--income-ink); border-color: var(--income); }
.sit-estado--falta   { color: var(--expense-ink); border-color: var(--expense); }

/* ── Fila 2 ─────────────────────────────────────────────────────────────── */
.sit-fila2 { display: grid; grid-template-columns: 1.55fr 1px 1fr;
             padding-block-start: var(--space-5); align-items: stretch; }
.sit-izq { padding-inline-end: var(--space-6); min-inline-size: 0; }
/* La derecha se estira hasta la altura de la izquierda. */
.sit-der { display: flex; flex-direction: column;
           padding-inline-start: var(--space-6); min-inline-size: 0; }

.sit-h { display: flex; justify-content: space-between; align-items: baseline;
         gap: var(--space-4); margin-block-end: var(--space-3); flex-wrap: wrap; }
.sit-h--centrada { justify-content: center; }
.sit-h__t { font-size: var(--fs-md); font-weight: var(--fw-semibold); margin: 0; }
.sit-leyenda { list-style: none; display: flex; gap: var(--space-4); margin: 0; padding: 0;
               color: var(--text-secondary); font-size: var(--fs-caption); }
.sit-leyenda li { display: inline-flex; align-items: center; white-space: nowrap; }

/* Las muestras de la leyenda y las partes de la barra comparten tono y trama:
   son la misma cosa dibujada en pequeño. */
.sit-m { flex: none; inline-size: 0.75em; block-size: 0.75em;
         margin-inline-end: 0.4em; border-radius: var(--radius-xs); }
.sit-m--cubierto  { background: var(--salida-cubierto); }
.sit-m--pagado    { background: var(--salida-pagado); }
.sit-m--pendiente { background: repeating-linear-gradient(45deg,
                      var(--salida-rayado) 0 3px, var(--salida-canal) 3px 6px);
                    border: 1px solid var(--border-strong); }

/* ── Las cinco salidas y el cierre ──────────────────────────────────────── */
/* ─── LAS COLUMNAS SON DE LA LISTA, NO DE CADA FILA · `D-735` otra vez ───
   Con una rejilla por `<li>`, la columna de las cifras se dimensiona con el
   contenido de SU fila —«S/ 12.679,91 pendiente» es más ancho que «S/ 0,00
   pendiente»—, la del nombre absorbe la diferencia y **la barra se desplaza**.
   Medido antes de arreglarlo: el ancho era 255 px en las seis, pero empezaban
   en **500, 521 y 524 px**. Un ancho igual no basta: tienen que empezar y
   acabar en el mismo sitio para poder compararlas.
   Las columnas se declaran una vez en la lista y cada fila las hereda. */
.sit-salidas {
  list-style: none; margin: 0; padding: 0;
  display: grid;
  /* ─── LA BARRA SE LLEVA EL HUECO, NO EL NOMBRE ────────────────────────
     El `1fr` estaba en la columna del nombre, así que se quedaba con todo el
     espacio sobrante y **empujaba la barra hasta la mitad de la tarjeta**. Ahora
     el nombre y las cifras ocupan lo que necesitan y la barra se estira entre
     los dos: **arranca donde acaba el monto y acaba donde empiezan las cifras**.
     Como las columnas son de la lista, esos dos bordes son los mismos en las
     seis filas. */
  grid-template-columns: minmax(11ch, auto) minmax(var(--sit-ancho-barra), 1fr) minmax(15ch, auto);
}
.sit-sal {
  display: grid; grid-template-columns: subgrid; grid-column: 1 / -1;
  gap: var(--space-4); align-items: center;
  padding-block: var(--space-3);
  border-block-end: 1px solid var(--border);
}
/* Las notas ocupan el ancho entero de la rejilla, bajo su fila. */
.sit-nota { grid-column: 1 / -1; }
/* Donde no haya subgrid, columnas a medida fija: también coinciden entre filas,
   se pierde el reparto elástico y nunca la alineación. */
@supports not (grid-template-columns: subgrid) {
  .sit-salidas { display: block; }
  .sit-sal { grid-template-columns: minmax(11ch, auto) minmax(var(--sit-ancho-barra), 1fr) 22ch; }
}
.sit-sal--cierre { border-block-end: none; border-block-start: 1px solid var(--border-strong);
                   padding-block-start: var(--space-4); }
.sit-sal__np { display: flex; flex-direction: column; min-inline-size: 0; }
.sit-sal__np b { font-weight: var(--fw-semibold); }
.sit-sal__np i { font-style: normal; font-weight: var(--fw-semibold);
                 font-size: var(--fs-lg); white-space: nowrap;
                 font-variant-numeric: tabular-nums; margin-block-start: 2px; }
.sit-sal--cierre .sit-sal__np i { font-size: var(--fs-xl); }
/* Todo a la derecha, para que las seis filas compartan borde. */
.sit-sal__cif { text-align: end; font-variant-numeric: tabular-nums; }
/* Un punto más que antes: es la cifra que se lee primero de cada fila. */
.sit-sal__p { font-size: var(--fs-xl); font-weight: var(--fw-bold);
              line-height: 1.2; white-space: nowrap; }
.sit-sal--cubierto .sit-sal__p { color: var(--income-ink); }
.sit-sal--pagado   .sit-sal__p { color: var(--warning-ink); }
.sit-sal--falta    .sit-sal__p { color: var(--text-secondary); }
.sit-sal__l { color: var(--text-secondary); font-size: var(--fs-caption);
              line-height: 1.5; white-space: nowrap; }
.sit-sal__l strong { color: var(--text); }
/* Lo pendiente en rojo, el mismo de «cuánto debo»: lo que se llama igual se ve
   igual en todo Flow. */
.sit-sal__l--pendiente strong { color: var(--expense-ink); }
.sit-nota { color: var(--text-secondary); font-size: var(--fs-caption);
            padding-block: 0 var(--space-3); white-space: nowrap; grid-column: 1 / -1; }
.sit-nota strong { color: var(--text); }
.sit-marca { font-size: var(--fs-caption); color: var(--text-secondary);
             white-space: nowrap; font-weight: var(--fw-regular); }
.sit-b__n .sit-marca { display: block; }

/* ── La barra · el color dice el estado ─────────────────────────────────── */
.sit-barra { display: flex; block-size: 14px; border-radius: var(--radius-pill);
             background: var(--salida-canal); overflow: hidden;
             border: 1px solid var(--border-strong); }
/* EL SURCO entre las dos partes: en tema oscuro el amarillo y el rayado se
   quedan en 2,80 y no hay gris que lo arregle, así que los separa una línea del
   color del canal — lo mismo que sostiene las porciones de los pies. */
.sit-barra > span { flex: 0 0 auto; min-inline-size: 0; }
/* Del color del FONDO DE LA TARJETA, no del canal: el rayado ya lleva el canal
   entre sus rayas, así que un surco del mismo tono se fundía con él —medido,
   2,75—. Contra el fondo las dos partes se ven: 8,53 el amarillo y 3,05 el
   rayado. Es el mismo surco que sostiene las porciones de los pies. */
.sit-barra__pagado + .sit-barra__pendiente {
  border-inline-start: 3px solid var(--surface);
}
.sit-barra__cubierto { background: var(--salida-cubierto); }
.sit-barra__pagado   { background: var(--salida-pagado); }
/* Rayado: la forma sobrevive a cualquier daltonismo. */
.sit-barra__pendiente { background: repeating-linear-gradient(45deg,
                          var(--salida-rayado) 0 4px, var(--salida-canal) 4px 8px); }

/* ── El arco de lo cobrado ──────────────────────────────────────────────── */
.sit-arco { position: relative; display: grid; place-items: center; }
.sit-arco__svg { inline-size: 100%; max-inline-size: 340px; block-size: auto; }
.sit-arco__canal   { fill: none; stroke: var(--salida-canal); stroke-width: 26; stroke-linecap: round; }
/* ─── LOS DOS ARCOS NO SE ANIDAN · Kike, 03/09/2026 ─────────────────────────
 *
 * *«Cobrado: todo el dinero que entró este mes, venga de donde venga»*, así que
 * **lo cobrado puede pasar a lo facturado**: medido, en agosto de 2026 el 30 % de
 * lo cobrado eran facturas de julio.
 *
 * Comparten origen y canal, y **el facturado va más ancho y debajo**: así se ven
 * los dos sea cual sea el más largo. *Un arco que se pinta suponiendo que cabe
 * dentro del otro se rompe el primer mes con cobros atrasados.*
 *
 * Y **el tono no es la única diferencia**: cada uno lleva su nombre y su cifra
 * escritos al lado — *palabra y forma, nunca color*. */
.sit-arco__facturado { fill: none; stroke: var(--paleta-1); stroke-width: 34;
                       stroke-linecap: round; opacity: .5; }
.sit-arco__hecho   { fill: none; stroke: var(--brand); stroke-width: 26; stroke-linecap: round; }
.sit-arco__esperado{ stroke: var(--warning); stroke-width: 6; stroke-linecap: round; }
/* El texto, en HTML y no en `<text>`: un `<text>` va en unidades del `viewBox` y
   no crecería con la escala (`D-710`). */
.sit-arco__dentro { position: absolute; inset-block-end: 4%; text-align: center;
                    display: flex; flex-direction: column; align-items: center; }
.sit-arco__v { font-size: var(--fs-lg); font-weight: var(--fw-bold);
               white-space: nowrap; font-variant-numeric: tabular-nums; }
.sit-arco__d { color: var(--text-secondary); font-size: var(--fs-caption); white-space: nowrap; }

.sit-esperado { display: flex; justify-content: space-between; align-items: baseline;
                gap: var(--space-4); margin-block: var(--space-4) 0;
                color: var(--text-secondary); font-size: var(--fs-caption); }
.sit-esperado strong { color: var(--text); font-size: var(--fs-md); white-space: nowrap; }
/* De qué está hecho el esperado: los soles, los dólares y el tipo con su fecha.
   Mismo tono y mismo cuerpo que el desglose de «Disponible en cuentas», que
   contesta lo mismo unas líneas más arriba: *lo que se llama igual se ve igual*.
   Se alinea al final porque va debajo de la cifra, que va a la derecha. */
.sit-esperado__de { color: var(--text-secondary); font-size: var(--fs-caption);
                    margin-block: var(--space-1) 0; text-align: end; }
/* El salto solo puede caer ENTRE los trozos: dentro de uno partiría una cifra
   o una fecha, y a escala máxima esta línea ocupa dos renglones. */
.sit-esperado__de > span { white-space: nowrap; }
.sit-pista { position: relative; block-size: 5px; background: var(--salida-canal);
             border-radius: var(--radius-pill); margin: var(--space-5) 11px var(--space-3); }
.sit-pista__hecho { position: absolute; inset-inline-start: 0; inset-block-start: 0;
                    block-size: 5px; background: var(--brand); border-radius: var(--radius-pill); }
.sit-pista__hito { position: absolute; inset-block-start: -8px; inline-size: 21px; block-size: 21px;
                   border-radius: 50%; transform: translateX(-10.5px);
                   background: var(--surface); border: 2px solid var(--border-strong); }
.sit-pista__hito--hecho    { background: var(--brand); border-color: var(--brand); }
.sit-pista__hito--esperado { border-color: var(--warning); }
.sit-ejes { display: flex; justify-content: space-between; margin: 0;
            color: var(--text-secondary); font-size: var(--fs-caption); }

/* ── El disponible por cuenta · centrado en lo que sobre, no estirado ───── */
.sit-cuentas-caja { flex: 1; display: flex; flex-direction: column; justify-content: center; }
.sit-cuentas { list-style: none; margin: 0; padding: 0; }
.sit-cuentas__r { display: flex; justify-content: space-between; align-items: baseline;
                  gap: var(--space-4); padding-block: var(--space-3);
                  border-block-end: 1px solid var(--border); }
.sit-cuentas__r:last-child { border-block-end: none; }
.sit-cuentas__k { color: var(--text-secondary); }
.sit-cuentas__v { font-weight: var(--fw-bold); white-space: nowrap;
                  font-variant-numeric: tabular-nums; }

/* Por debajo de 1000 px de VENTANA las dos zonas se apilan. Nada se recorta y
   nada se encoge: la tarjeta crece. */
@media (max-width: 1000px) {
  .sit-fila1 { grid-template-columns: 1fr; gap: var(--space-5); }
  .sit-fila1 .sit-vsep { block-size: 1px; inline-size: auto; }
  .sit-b { padding-inline: 0; }
  .sit-fila2 { grid-template-columns: 1fr; gap: var(--space-5); }
  .sit-fila2 .sit-vsep { block-size: 1px; inline-size: auto; }
  .sit-izq, .sit-der { padding-inline: 0; }
  .sit-sal { grid-template-columns: 1fr 1fr; }
  .sit-barra { grid-column: 1 / -1; order: 3; }
}

/* ── «Próximos vencimientos» · los dos primeros, solo más grandes ────────
   Kike, 26/08/2026: con los vencimientos agolpados en el mismo día, marcar «el
   más próximo» no distingue nada. Se resaltan **los primeros de la lista** y
   **solo con el tamaño** — se retiraron el texto y la raya vertical.

   LOS DEMÁS NO SE ENCOGEN: aquí solo hay reglas que AGRANDAN. La caja crece con
   ellas. *Una cifra nunca se encoge para que quepa.* */
.por-vencer__fila--destacada { padding-block: var(--space-3); }
/* EN PROPORCIÓN, NO EN UN SEGUNDO TOKEN. Lo cazó `auditar_cifra_widget`: un
   `var(--fs-xl)` aquí es un tamaño **independiente** del de la cifra del widget, y
   el día que uno cambie el otro no se entera. Un `em` es una proporción de lo
   heredado, así que se mueve con él y no puede discreparle.
   Las proporciones no se eligieron a ojo: reproducen al píxel lo ya medido y
   aceptado — 24,44 px el concepto y la fecha, 29,75 px el importe, sobre los
   18,06 px de la fila. */
.por-vencer__fila--destacada .por-vencer__concepto,
.por-vencer__fila--destacada .por-vencer__fecha { font-size: 1.353em; }
.por-vencer__fila--destacada .por-vencer__importe {
  font-size: 1.647em; font-weight: var(--fw-bold);
}

/* «previsto», pegado a la cifra del mes por venir: una previsión no es dinero
   que esté, y el gris solo no lo dice.
   *Devuelta el 26/08/2026*: al sustituir el bloque de resaltado de arriba me
   llevé esta regla por delante, que vive pegada a él y no tiene nada que ver.
   Es la segunda vez que un reemplazo en `app.css` se come una regla de Loop
   —`D-759` fue la primera—, y por eso ahora hay `D-762`. */
.barra-lin__prevision {
  display: block; font-size: var(--fs-caption); font-weight: var(--fw-regular);
  color: var(--text-secondary); letter-spacing: .02em;
}

/* ═══ LAS CABECERAS DE MONEY Y BILLING · 27/08/2026 ═══════════════════════════
 *
 * Tres zonas, y cada una con su pregunta escrita encima. Lo que sigue no
 * inventa ni un color: todo sale de los tokens, y las medidas que deciden si
 * algo cabe van en píxeles de ventana —no en `rem`— porque en `rem` el mismo
 * ancho de pantalla daría un reparto distinto según el tamaño de letra (`D-702`).
 */

/* ─── LA CABECERA DE PANTALLA ─────────────────────────────────────────────── */
.encabezado-pantalla { margin-block-end: var(--space-4); }
/* ─── LA FILA DE LA CABECERA MIDE LO MISMO EN TODOS · Kike, 28/08/2026 ──────
 *
 * *«Todos los módulos deberían tener la línea en la misma posición vertical.»*
 * No lo estaban, y la causa es que **lo que va en esa fila varía**: unos llevan
 * un subtítulo, otros un selector de mes, otros nada, y la fila medía lo que
 * midiera su contenido.
 *
 * **Lo que decide el alto es la fila, no lo que lleve dentro.** Se le fija un
 * mínimo y el contenido se centra: así la línea cae a la misma altura tenga la
 * pantalla lo que tenga.
 *
 * Y el mínimo va en `em` para que **crezca con las cuatro escalas**: en píxeles,
 * a escala máxima el contenido no cabría y habría que encogerlo — *y nada se
 * encoge para cuadrar*. Si algún día un contenido no cabe, **crece la fila en
 * los nueve**, que es lo que este `min-block-size` hace por construcción. */
.encabezado-pantalla__linea {
  display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap;
  /* 3em y no 2,6: medido, la fila de Money llevaba 51 px —su selector de tipo de
     cambio— contra los 47 de Gastos fijos, y la línea caía 4,1 px más abajo. *Nada
     se encoge para cuadrar*: **crece la fila en los nueve**, que es lo que este
     mínimo hace. En `em`, así que sube con las cuatro escalas. */
  min-block-size: 3em;
}
.encabezado-pantalla__linea h1 { margin: 0; }
.encabezado-pantalla__sub { margin: 0; }
/* La banda no lleva información: solo dice dónde estás, y el nombre está
   escrito justo encima. Por eso puede ser color a secas sin romper §8.4. */
.encabezado-pantalla__banda {
  display: block; block-size: 5px; border-radius: var(--radius-sm);
  margin-block-start: var(--space-2);
}
/* ─── LOS CUATRO TONOS, Y LA HOJA ESCOGE ────────────────────────────────────
 * Los emite la vista como cuatro variables; aquí solo se decide cuál toca según
 * la combinación. La banda **no lleva información**: el nombre del módulo está
 * escrito justo encima, así que si el color no se distinguiera no se pierde
 * nada. Por eso puede ser color a secas sin romper §8.4. */
.encabezado-pantalla__banda { background: var(--mod-dark); }
:root[data-contrast="high"] .encabezado-pantalla__banda { background: var(--mod-dark-alto); }
:root[data-theme="light"] .encabezado-pantalla__banda { background: var(--mod-light); }
:root[data-theme="light"][data-contrast="high"] .encabezado-pantalla__banda { background: var(--mod-light-alto); }
/* Sin título, la barra de filtros no necesita el separador ni el hueco de la
   izquierda: empieza donde empiezan sus campos. */
.cabecera-con-filtros--sola { padding-block-start: var(--space-3); }

/* ─── LA PREGUNTA DE CADA ZONA ────────────────────────────────────────────── */
.zona-pregunta {
  /* El tono NO se teclea aquí: sale de `--texto-apagado`, arriba. Estaba
     escrito en los dos sitios y ganaba éste por venir después — que es
     exactamente cómo dos copias de la misma regla acaban discrepando. */
  margin: 0 0 var(--space-1);
  /* No se encoge: es texto, y el texto de esta pantalla sigue la preferencia de
     tamaño como el resto. */
}
.rotulo-lig { font-weight: 400; }   /* el tono, de `--texto-apagado` */

/* ─── LAS DOS BARRAS DEL NETO ─────────────────────────────────────────────── */
/* La de «entró» va llena y la de «salió» ocupa su parte DE LO QUE ENTRÓ: medidas
   cada una contra sí misma estarían las dos al 100 % y no dirían nada. */
.pista-neto {
  display: block; block-size: .55em; margin-block-start: var(--space-1);
  background: var(--surface-hover); border-radius: var(--radius-sm); overflow: hidden;
}
.pista-neto__tramo { display: block; block-size: 100%; }
.pista-neto__tramo.es-entro { background: var(--income); }
.pista-neto__tramo.es-salio { background: var(--expense); }
.marca-proporcion { margin: var(--space-2) 0 0; }

/* ─── LA PASTILLA DE «OTRAS» Y LA DE UNA CUENTA ───────────────────────────── */
/* Rótulo y cifra, no una oración: fuera de la ⓘ solo van rótulos y cifras. */
.etiqueta-otras, .etiqueta-cuenta {
  display: inline-block; border: 1px solid var(--border); border-radius: var(--radius-sm);
  padding: 0 .4em; margin-inline-start: .4em;   /* el tono, de `--texto-apagado` */
  font-weight: 400; white-space: nowrap;
  /* ─── SIN `font-size` · Kike ve poco, y el piso son 17 px · 07/09/2026 ─────
     Esta insignia iba a `.8em` y salía a 13,6 px en unas pantallas y 14,4 en
     otras, por debajo del piso duro de §8.5. **Se distingue igual**: tiene
     borde, relleno y su propio color. *El tamaño no era lo que la separaba del
     texto; era lo que la hacía ilegible.* */
}

/* ─── LAS CUENTAS, EN REJILLA DE TARJETAS ─────────────────────────────────── */
/* `auto-fit` con un mínimo en píxeles: el número de columnas lo decide el ancho
   de la ventana, no el tamaño de letra. Y con `minmax` ninguna tarjeta baja de
   ese mínimo, así que **la cifra nunca se parte**: primero crece la caja. */
/* ─── NINGUNA FILA CON UNA SOLA · 27/08/2026 ────────────────────────────────
 * `auto-fit` llena cuanto puede y deja el resto: con ocho cuentas daba **siete
 * arriba y una debajo**. El número de columnas lo decide ahora la vista, que
 * sabe cuántas tarjetas hay, y aquí solo se escoge el tope que cabe por ancho
 * de ventana — **en píxeles, no en `rem`** (`D-702`). */
.rejilla-saldos {
  list-style: none; margin: 0; padding: 0;
  display: grid; grid-template-columns: 1fr; gap: var(--space-3);
}
@media (min-width:  620px) { .rejilla-saldos { grid-template-columns: repeat(var(--c2, 2), 1fr); } }
@media (min-width:  900px) { .rejilla-saldos { grid-template-columns: repeat(var(--c3, 3), 1fr); } }
@media (min-width: 1240px) { .rejilla-saldos { grid-template-columns: repeat(var(--c4, 4), 1fr); } }
.saldo-tarjeta {
  display: flex; flex-direction: column; gap: var(--space-1);
  border: 1px solid var(--border); border-radius: var(--radius-md);
  padding: var(--space-3); background: var(--fondo-tarjeta-saldo);
}
/* ─── EL NOMBRE ENTERO, AUNQUE OCUPE DOS LÍNEAS ─────────────────────────────
 * *Truncar un nombre es perder información, no ahorrar espacio.* En una tarjeta
 * hay dos dimensiones: se envuelve. La etiqueta baja a su línea si estorba. */
.saldo-tarjeta .saldo-nombre {
  color: var(--texto-apagado); display: flex; flex-wrap: wrap; gap: .35em .5em;
  align-items: baseline;
}
.saldo-nombre__texto { overflow-wrap: anywhere; }
.saldo-tarjeta .saldo-importe { display: flex; align-items: baseline; gap: .4em; }
.saldo-tarjeta .saldo-cifra { font-weight: 700; white-space: nowrap; }

/* ─── EL REPARTO DE LAS ZONAS ─────────────────────────────────────────────── */
/* Apiladas por omisión. Solo se ponen en fila cuando hay ancho de ventana para
   las dos: **si no caben se apilan, no se encogen**. */
.zonas-cabecera {
  display: grid; grid-template-columns: 1fr; gap: var(--space-4);
  margin-block-end: var(--space-4);
}
.zona-cuentas { margin-block-end: var(--space-4); }
@media (min-width: 900px) {
  /* El centro lleva dos columnas de categorías una junto a otra; la izquierda,
     una cifra que se ensancha sola. De ahí 0,85 contra 2. */
  .zonas-cabecera--money { grid-template-columns: 0.85fr 2fr; }
}

/* ─── LA CURVA ACUMULADA ──────────────────────────────────────────────────── */
/* El dibujo lleva SOLO líneas; todo rótulo es HTML alrededor, así que crece con
   la preferencia de tamaño (`D-710`). */
.acumulado__marco {
  display: grid; grid-template-columns: auto 1fr; gap: var(--space-2) var(--space-3);
  align-items: stretch;
}
.acumulado__ejeY {
  display: flex; flex-direction: column; justify-content: space-between;
  text-align: end; padding-block-end: 1.6em;  /* el alto del eje de meses */
}
.acumulado__lienzo { min-inline-size: 0; }
/* ─── EL DIBUJO BAJA DE ALTO · Kike, 28/08/2026 ─────────────────────────────
 *
 * *«Debería lograrse reducir al menos una línea o dos el alto de toda el área de
 * widgets de Billing.»* Medido: **el alto de la fila lo fija la tercera zona**
 * —481,5 px contra 297,3 y 377,1 de las otras dos— y dentro de ella lo fija **el
 * dibujo, 294,8 px**. Quitar iconos de las otras dos no baja la fila ni un
 * píxel: solo baja lo que manda.
 *
 * El techo pasa de 240 a 190. **No se toca ningún texto**: los rótulos del eje y
 * la leyenda son HTML y siguen creciendo con las cuatro escalas. Lo que se
 * recorta es el dibujo, que es lo que puede ceder sin perder nada — la curva
 * dice lo mismo con menos alto porque lo que se compara es la forma. */
.acumulado__svg { inline-size: 100%; block-size: clamp(130px, 15vh, 170px); display: block; }
.acumulado__rejilla { stroke: var(--border); stroke-width: 1; opacity: .45; }
/* La marca del mes en curso: el mismo ámbar del año en curso, y rayada para que
   no se confunda con una serie. */
.acumulado__hoy { stroke: var(--serie-1); stroke-width: 2; stroke-dasharray: 4 4; opacity: .7; }
.acumulado__serie { fill: none; stroke-width: 2.5; stroke-linecap: round; stroke-linejoin: round; }
.acumulado__serie.es-actual { stroke-width: 4; }
.acumulado__ejeX {
  display: grid; grid-template-columns: repeat(12, 1fr);
  text-align: center; margin-block-start: var(--space-1);
}
.acumulado__ejeX .es-hoy { color: var(--serie-1); font-weight: 700; }
/* LA LEYENDA EN SU COLUMNA, no suelta sobre el dibujo (`D-668`, `D-711`). */
.acumulado__leyenda {
  grid-column: 1 / -1; list-style: none; margin: 0; padding: 0;
  display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-4);
}
.acumulado__leyenda li { display: flex; align-items: center; gap: .5em; white-space: nowrap; }
.acumulado__anio.es-actual { font-weight: 700; }
.acumulado__cierre { margin: var(--space-3) 0 0; }

/* ─── LOS TRAMOS DE ANTIGÜEDAD ────────────────────────────────────────────── */
.tramos-antiguedad { list-style: none; margin: var(--space-4) 0 0; padding: 0; }
/* Solo el vencido va en rojo, Y lleva la palabra: el tono no carga solo. */
.barra-fila.es-vencido .barra-etiqueta,
.barra-fila.es-vencido .barra-cifra { color: var(--expense-ink); }
.etiqueta-vencido {
  display: inline-block; border: 1px solid var(--expense); border-radius: var(--radius-sm);
  padding: 0 .4em; margin-inline-start: .4em; color: var(--expense-ink);
  font-weight: 600; white-space: nowrap;
  /* ─── SIN `font-size` · Kike ve poco, y el piso son 17 px · 07/09/2026 ─────
     Esta insignia iba a `.8em` y salía a 13,6 px en unas pantallas y 14,4 en
     otras, por debajo del piso duro de §8.5. **Se distingue igual**: tiene
     borde, relleno y su propio color. *El tamaño no era lo que la separaba del
     texto; era lo que la hacía ilegible.* */
}
.tramos-vacios { margin-block-start: var(--space-2); }

/* ─── EL REPARTO DE BILLING ───────────────────────────────────────────────── */
@media (min-width: 1100px) {
  /* Tres zonas solo cuando hay ventana para las tres. Por debajo se apilan —no
     se encogen—, y el umbral va en píxeles de ventana (`D-702`). */
  .zonas-cabecera--billing { grid-template-columns: 1.05fr 1fr 1.7fr; }
}
/* El envoltorio de la curva: `auditar_clases` lo pidió, y con razón — una clase
   que no está en ninguna hoja no falla, no pinta, y nadie se entera. */
.acumulado { display: block; }


/* ═══ UN SOLO JUEGO DE TONOS DE TEXTO · 27/08/2026 ════════════════════════════
 *
 * Medido en la pantalla: el porcentaje de las barras salía en un gris —#a7b6c2—
 * y el rótulo y lo apagado en otro —#8495a1—, **dos tonos para el mismo papel**;
 * y alguna cifra se pintaba con el gris de rótulo, que es un tercer uso.
 *
 * *Dos copias de la misma regla acaban discrepando*, y en tonos discrepan igual
 * que en cifras. Los cuatro papeles viven aquí y **ninguna vista teclea el
 * suyo**. Es lo mismo que ya se hizo con el formato de los números. */
/* Se unifican en `--text-secondary` y no en `--text-muted`: el segundo va a
   ~5:1 y su propia ficha dice **«solo no-esencial»**, y un porcentaje no es
   no-esencial — es el dato que dice la proporción. */
:root {
  --texto-rotulo:  var(--text-secondary);  /* el rótulo de un bloque */
  --texto-cifra:   var(--text);    /* un número que se lee */
  --texto-apagado: var(--text-secondary);  /* la pregunta, la coletilla, la nota */
  --texto-pct:     var(--text-secondary);  /* la proporción, a la derecha */
}
.rotulo-bloque, .zona-pregunta, .rotulo-lig,
.etiqueta-otras, .etiqueta-cuenta { color: var(--texto-apagado); }
.barra-pct { color: var(--texto-pct); }
.barra-etiqueta, .barra-cifra { color: var(--texto-cifra); }

/* ─── SIN NEGRITAS EN LAS CATEGORÍAS ───────────────────────────────────────
 * En el boceto eran cinco líneas sueltas y la negrita las separaba del fondo.
 * En pantalla son diez, y **todo en negrita es como nada en negrita**: no hay
 * jerarquía, solo peso. Queda solo donde significa algo — la hoja del rótulo
 * compuesto frente a su padre, que es el dato. */
.barras-categoria .barra-etiqueta,
.barras-categoria .barra-cifra { font-weight: 400; }
.barras-categoria .ruta-categoria__hoja { font-weight: 600; }
.tramos-antiguedad .barra-etiqueta,
.tramos-antiguedad .barra-cifra { font-weight: 400; }

/* Un trozo que no se parte por dentro: el salto cae ENTRE las piezas. */
.nowrap { white-space: nowrap; }

/* ─── LA FILA DE TRAMOS NO PUEDE SER MÁS ANCHA QUE SU TARJETA ───────────────
 *
 * Medido a escala máxima: la pista salía **670 px dentro de una tarjeta de
 * 451** — se desbordaba. La causa es que el rótulo lleva dos pastillas que no
 * envuelven, así que la fila crecía hasta caber**los**, y la pista al 100 % de
 * una fila crecida crece con ella.
 *
 * *Una fila no se parte, y una rejilla no mide más que su caja.* Se deja
 * envolver el rótulo —tiene dos dimensiones— y la pista se mide contra la
 * tarjeta, no contra la fila. **Ninguna cifra se encoge**: lo que cede es la
 * disposición del rótulo. */
.tramos-antiguedad .barra-cabeza { flex-wrap: wrap; }
.tramos-antiguedad .barra-etiqueta {
  min-width: 0; overflow-wrap: anywhere; white-space: normal;
}
.tramos-antiguedad .barra-pista { flex: 0 0 100%; inline-size: 100%; max-inline-size: 100%; }
.tramos-antiguedad .barra-fila { min-width: 0; max-inline-size: 100%; }

/* Y las cifras de los dos gráficos, en peso normal. Va al final del fichero a
   propósito: escrito antes, `.barra-cifra` de la línea 5131 lo pisaba — el
   mismo cuento de las dos copias, esta vez en la cascada. */
.barras-categoria .barra-cifra,
.barras-categoria .barra-etiqueta,
.tramos-antiguedad .barra-cifra,
.tramos-antiguedad .barra-etiqueta { font-weight: 400; }
.barras-categoria .ruta-categoria__hoja { font-weight: 600; }

/* ─── Y LO QUE NO HACÍA FALTA TOCAR ─────────────────────────────────────────
 *
 * Aquí se llegó a añadir una cadena de `min-inline-size: 0` porque la fila de
 * tramos parecía medir **670 px en una tarjeta de 362**. No era así: *el
 * medidor comparaba contra la tarjeta y no contra la celda de rejilla que de
 * verdad la contiene*. La pista llena su celda a las cuatro escalas —386, 479,
 * 574 y 670 sobre celdas de la misma medida— y **no hay barra horizontal en
 * ninguna**. Los parches se retiran: *se prueba el medidor antes de creerle.* */
.tramos-antiguedad .barra-valores { margin-inline-start: auto; }

/* ═══ LA GEOMETRÍA DE LAS CABECERAS · 27/08/2026 ═══════════════════════════════
 *
 * El encargo que las construyó **describió el contenido y no la geometría**, y
 * sin medida escrita el hueco lo resuelve cada regla a su manera. Aquí van los
 * números, medidos antes y después.
 */

/* ─── A1 · LA TARJETA DE CUENTA · SU ALTO SALE DE SU CONTENIDO ──────────────
 *
 * Medido antes: la tarjeta medía **150,2 px** y su nombre **90,8** para una
 * línea de texto de 18. No era un mínimo forzado: la rejilla estira todas las
 * tarjetas al alto de la más alta —la que parte el nombre en dos— y dentro de
 * una tarjeta estirada **el nombre se estiraba con ella**.
 *
 * Se arregla donde estaba: el contenido se pega arriba y el nombre no crece.
 * *El alto sale del contenido, no de un mínimo.* */
.saldo-tarjeta { justify-content: flex-start; gap: var(--space-1); }
.saldo-tarjeta > * { flex: 0 0 auto; }
/* El nombre es el rótulo y la cifra es la cifra: hasta ahora medían lo mismo,
   así que la tarjeta no tenía jerarquía. La etiqueta va con el nombre. */
/* ─── LA CIFRA NO DECLARA CUERPO PROPIO ─────────────────────────────────────
 *
 * Se le puso `--fs-md` para que fuera «la cifra de la tarjeta» y **dos guardianes
 * lo cazaron**: `auditar_cifra_widget` y `ejercer_cifra_widgets` exigen que
 * ninguna cifra de widget declare su tamaño, porque *lo que se llama igual se ve
 * igual en todo Flow* — y un cuerpo local es exactamente cómo dejan de verse
 * igual.
 *
 * Y el encargo pedía **«al menos el tamaño de cifra de widget, el mismo que usan
 * las cifras de las líneas de composición»**: ésas van al cuerpo heredado, así
 * que el saldo ya lo cumple sin declarar nada.
 *
 * La jerarquía sale de lo que sí se puede mover: **el rótulo baja** y la cifra
 * lleva el peso. Ninguna cifra se encoge — la de arriba se queda donde estaba. */
.saldo-tarjeta .saldo-nombre { font-size: var(--fs-caption); }
.saldo-tarjeta .saldo-cifra  {
  font-size: var(--fs-cifra-saldo); font-weight: var(--fw-semibold);
}

/* ─── A2 · LAS DOS ZONAS MIDEN LO MISMO, Y SU CONTENIDO LLENA ───────────────
 *
 * Medido antes: las dos zonas medían 436,1 px —el alto lo fija la más alta, eso
 * ya estaba bien— y la de composición dejaba **118,1 px muertos al pie**.
 *
 * Lo que crece para llenarlos es **la separación entre líneas**, nunca la barra
 * ni la cifra: estirar una barra cambiaría lo que dice, y estirar una cifra está
 * prohibido. */
.zonas-cabecera > .card { display: flex; flex-direction: column; }
.grafico-categorias { flex: 1 1 auto; min-block-size: 0; }
.grafico-grupo { display: flex; flex-direction: column; }
.grafico-grupo .barras-categoria {
  flex: 1 1 auto; display: flex; flex-direction: column; justify-content: space-between;
}
.bloque-periodo { flex: 1 1 auto; display: flex; flex-direction: column; }
.bloque-periodo .totales-desglose { margin-block-start: auto; }

/* ─── A3 · ENTRÓ Y SALIÓ, MITAD Y MITAD ─────────────────────────────────────
 * Cada uno con su rótulo, su cifra y su barra **del ancho de su columna**: dos
 * barras del mismo ancho se comparan; una encima de otra, no. */
.totales-desglose--par {
  display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-3); align-items: start;
}
.totales-desglose--par .total-bloque { min-inline-size: 0; }
.totales-desglose--par .pista-neto { inline-size: 100%; }

/* ─── A4 · EL PADRE SE LEE, Y SE DISTINGUE DEL HIJO ─────────────────────────
 *
 * Medido: padre e hijo salían **del mismo tono** —`rgb(238,243,247)` los dos—,
 * así que la ruta no distinguía nada. Fue la unificación de tonos del 27/08: al
 * darle a `.barra-etiqueta` un solo color, el padre perdió el suyo.
 *
 * Vuelve a tener el de lo apagado —**7,25:1 medido, por encima del 4,5:1 que
 * pide un texto**— y menos peso que el hijo. *Un rótulo que no se puede leer no
 * distingue nada; uno idéntico al de al lado, tampoco.* */
.ruta-categoria__padre { color: var(--texto-apagado); font-weight: 400; }
.barras-categoria .ruta-categoria__hoja { font-weight: 600; }

/* ─── A5 · LA CABECERA APROVECHA LO ALTO ────────────────────────────────────
 * Se recorta **el aire**, nunca el texto: ni una cifra ni un rótulo cambian de
 * tamaño aquí. */
.encabezado-pantalla { margin-block-end: var(--space-2); }
.encabezado-pantalla__banda { margin-block-start: var(--space-1); }
.zonas-cabecera { margin-block-end: var(--space-3); }
.zona-cuentas { margin-block-end: var(--space-3); }

/* ─── A1 (cont.) · CADA TARJETA MIDE LO SUYO ────────────────────────────────
 * A escala máxima quedaban **51,3 px muertos** dentro de una tarjeta: la
 * rejilla las estira todas al alto de la más alta —la que parte el nombre en
 * dos—. Con `start` cada una mide su contenido y **no hay hueco a ninguna
 * escala**; las cimas siguen alineadas, que es lo que hace legible la fila. */
.rejilla-saldos { align-items: start; }

/* ─── A5 (cont.) · EL AIRE DE ARRIBA, MEDIDO PIEZA A PIEZA ──────────────────
 *
 * Del borde a la primera cifra había **322,5 px**, repartidos así: 71,4 la barra
 * de la aplicación —que no es de esta pantalla—, 25,5 el respiro de `main`, 53,3
 * el rótulo del módulo con su banda, 25,5 el relleno de la tarjeta y **99,5 la
 * pregunta y el rótulo de la zona**.
 *
 * Se recorta **aire**: el respiro de la pregunta, el del rótulo y el relleno
 * superior de las tarjetas de cabecera. *Ni una cifra ni un rótulo cambian de
 * tamaño* — se comprueba midiendo que el cuerpo de letra no baje. */
.zonas-cabecera > .card { padding-block-start: var(--space-3); }
.zona-pregunta { margin-block-end: 0; line-height: 1.3; }
.bloque-periodo .rotulo-bloque,
.widget-magnitud .rotulo-bloque { margin-block-end: var(--space-1); }
.bloque-periodo .zona-pregunta + .rotulo-bloque { margin-block-start: 0; }

/* ─── B3 · LA LÍNEA DEL MÓDULO BAJO LA ENTRADA ACTIVA ───────────────────────
 *
 * Del mismo grosor que la banda del título —5 px—: es el mismo idioma dos veces.
 * La caja del activo se queda: *el activo no puede depender solo del color*.
 *
 * Y **44 px de área táctil**, que es el mínimo sin excepciones para un control:
 * el relleno de arriba y abajo se calcula para llegar ahí sin tocar el texto. */
.nav-escritorio a {
  position: relative; min-block-size: 44px; display: inline-flex; align-items: center;
}
.nav-escritorio a[aria-current="page"]::after {
  content: ""; position: absolute; inset-inline: 10px; inset-block-end: 2px;
  block-size: 5px; border-radius: var(--radius-sm); background: var(--mod-dark, var(--brand));
}
:root[data-contrast="high"] .nav-escritorio a[aria-current="page"]::after { background: var(--mod-dark-alto, var(--brand)); }
:root[data-theme="light"] .nav-escritorio a[aria-current="page"]::after { background: var(--mod-light, var(--brand)); }
:root[data-theme="light"][data-contrast="high"] .nav-escritorio a[aria-current="page"]::after { background: var(--mod-light-alto, var(--brand)); }

/* ─── B4 · EL BOTÓN DE ACCIÓN NO SE TIÑE · SU ARO DE FOCO SÍ ────────────────
 *
 * El azul de ese botón **no dice «Flow»: dice «esto es la acción principal»**.
 * Es lo único saturado de la pantalla y por eso se encuentra sin buscarlo. Si en
 * Billing fuera ámbar y en Fiscal verde, dejaría de haber un color que
 * signifique «pulsa aquí».
 *
 * Y hay un problema medible encima: **un ámbar con texto a 4,5:1 pide texto
 * oscuro y un azul pide texto claro**, así que saldrían botones de dos familias
 * distintas — lo contrario de que *lo que se llama igual se vea igual*.
 *
 * Lo que sí lleva el color del módulo es **el aro de foco**: ahí identifica sin
 * quitarle al azul su trabajo. */
.encabezado-pantalla .btn--primary:focus-visible {
  outline: 3px solid var(--mod-dark, var(--brand)); outline-offset: 2px;
}
:root[data-theme="light"] .encabezado-pantalla .btn--primary:focus-visible {
  outline-color: var(--mod-light, var(--brand));
}

/* ─── BILLING · LOS TRES WIDGETS, AFINADOS · Kike, 27/08/2026 ────────────────
 *
 * Los tres títulos van **centrados** y el contenido reparte el alto de su
 * tarjeta. Lo que crece para llenar es **la separación**, nunca la barra ni la
 * cifra: *estirar una barra cambiaría lo que dice.* */
.zonas-cabecera--billing .zona-pregunta { text-align: center; }

/* 6 · El primero, centrado en los dos ejes. */
.zonas-cabecera--billing .widget-magnitud {
  display: flex; flex-direction: column; justify-content: center; align-items: center;
  flex: 1 1 auto; text-align: center;
}
.zonas-cabecera--billing .tramos-antiguedad { inline-size: 100%; text-align: start; }
/* El dato que acompaña a la cifra: rótulo y cifra, con peso de dato y no de
   coletilla — con dos tramos es lo único que distingue 35 días de 119. */
.dato-acompana { margin: var(--space-1) 0 0; color: var(--texto-apagado); }
.dato-acompana .amount { color: var(--texto-cifra); }

/* ─── LOS MONTOS DEL COMPARATIVO, DOS PUNTOS MÁS · Kike, 28/08/2026 ─────────
 * `.comparativo-cifra` no es la cifra principal de un widget —hay una por fila—
 * así que su cuerpo no sale del token de la cifra grande. Va en `rem` para que
 * crezca con las cuatro escalas, como todo lo demás. */
.zonas-cabecera--billing .comparativo-cifra { font-size: calc(var(--fs-body) + 0.167rem); }

/* 9 · El segundo reparte el alto entre sus seis filas.
 * Lo que crece es **la separación entre filas**, nunca la barra ni la cifra:
 * estirar una barra cambiaría lo que dice. */
.zonas-cabecera--billing .widget-comparativo {
  flex: 1 1 auto; display: flex; flex-direction: column;
}
.zonas-cabecera--billing .barras-comparativo {
  flex: 1 1 auto; display: flex; flex-direction: column; justify-content: space-between;
}

/* 10 · Y el tercero, con su título centrado y su dibujo llenando. */
.zonas-cabecera--billing .acumulado { display: flex; flex-direction: column; flex: 1 1 auto; }
.zonas-cabecera--billing .acumulado__marco { flex: 1 1 auto; }

/* ─── LA CURVA REUTILIZA EL COMPONENTE, PERO NO SU ALTO MÍNIMO ──────────────
 * `.grafico-con-leyenda .grafico-envoltura` reserva 26em porque el del panel
 * lleva su leyenda dentro. Aquí la leyenda va debajo, en su columna, así que ese
 * mínimo dejaría la tarjeta enorme. Se acota **solo dentro de la curva**: el del
 * panel no se toca. */
.acumulado .grafico-envoltura { min-height: 0; }

/* ─── Y LA VENTANA DE CINCO MESES TAMPOCO NECESITA LOS 26em ────────────────
 *
 * Mismo caso y misma columna que la curva de arriba, un turno después. Los 26em
 * reservan sitio para que el globo del tip no tape el eje ni lo de debajo
 * (`D-681`), y ese motivo **sigue vivo**: no se quita el mínimo, se le da el que
 * corresponde a esta tarjeta.
 *
 * Medido: reservaba **469,6 px** para un dibujo que aquí mide unos 250, y esa
 * diferencia es la que empujaba el listado de Billing fuera de la pantalla —el
 * mismo defecto que este encargo viene a corregir en Loop—. 16em siguen dejando
 * el globo dentro, y está comprobado abriéndolo, no supuesto.
 *
 * Acotado a esta tarjeta: **el del panel no se toca**, y se comprobó que sale
 * byte por byte igual. */
.zonas-cabecera--billing .widget-facturado .grafico-envoltura { min-height: 16em; }

/* Las dos pistas que llevan a la ⓘ: sin regla propia no pintan nada, y
   `auditar_clases` lo pidió — *una clase que no existe no falla, no pinta*. */
.conversion-pista, .acumulado__pista { margin: var(--space-2) 0 0; }
/* Rótulo y código, no una frase: dice en qué moneda están sumadas las cifras. */
.conversion-moneda { color: var(--texto-apagado); }

/* ─── LA FILA DE UN MES DE LOOP PARTE ENTRE MONEDAS, NUNCA DENTRO ───────────
 * Cada importe va en su `.nowrap`; lo único que puede quedar en otra línea es
 * la moneda entera, con su símbolo. */
.barra-lin__cifra { white-space: normal; }
.barra-lin__cifra .nowrap { white-space: nowrap; }

/* ─── UNA CIFRA QUE REPORTA UN PROBLEMA · 28/08/2026 ────────────────────────
 * El rojo **no carga solo**: al lado va siempre su palabra. Se usa el tono de
 * tinta de gasto, que ya está medido por encima de 4,5:1 en las cuatro
 * combinaciones — y se vuelve a medir aquí, que es donde se lee. */
.stat-value.es-alerta { color: var(--expense-ink); }
.etiqueta-alerta {
  display: inline-block; border: 1px solid var(--expense); border-radius: var(--radius-sm);
  padding: 0 .4em; margin-inline-start: .4em; color: var(--expense-ink);
  font-weight: 600; white-space: nowrap;
  /* ─── SIN `font-size` · Kike ve poco, y el piso son 17 px · 07/09/2026 ─────
     Esta insignia iba a `.8em` y salía a 13,6 px en unas pantallas y 14,4 en
     otras, por debajo del piso duro de §8.5. **Se distingue igual**: tiene
     borde, relleno y su propio color. *El tamaño no era lo que la separaba del
     texto; era lo que la hacía ilegible.* */
}

/* ─── LOS WIDGETS DE FILAS REPARTEN SU ALTO · Kike, 28/08/2026 ──────────────
 * «En qué se va» agrupaba sus cinco filas arriba y dejaba el resto de la
 * tarjeta vacío. **Lo que crece para llenar es la separación entre filas**,
 * nunca la barra ni la cifra: *estirar una barra cambia lo que dice.* */
.tercios--tres > .card.stat { display: flex; flex-direction: column; }
.tercios--tres .barras-categoria,
.tercios--tres .grafico-barras {
  flex: 1 1 auto; display: flex; flex-direction: column; justify-content: space-between;
}

/* ═══ LOS DOS GRÁFICOS DE LOOP, APILADOS · Kike, 28 de agosto de 2026 ═══════
 *
 * *«Podría ir la barra hasta el extremo, y debajo los montos, y hacer crecer los
 * tamaños de texto de los montos y del mes.»*
 *
 * ─── QUÉ MEDÍA ANTES ──────────────────────────────────────────────────────
 *
 * Tres columnas: nombre · carril · cifra. En categorías el carril se quedaba en
 * **87,8 px de 499** —el nombre se llevaba 181,7 y la cifra 230—, o sea que la
 * pieza que lleva el dato era la más estrecha de las tres. En meses, 247,4.
 * Y al pie de las dos tarjetas sobraban ~100 px sin nada.
 *
 * ─── LA FILA AHORA SON TRES RENGLONES ─────────────────────────────────────
 *
 * Nombre arriba a la izquierda · la barra debajo, de extremo a extremo · los
 * importes debajo de la barra. La barra pasa de 87,8 a todo el ancho de la
 * tarjeta, que es lo que hace comparables dos barras cortas.
 *
 * ─── LO QUE CRECE ES EL TEXTO Y LA SEPARACIÓN ─────────────────────────────
 *
 * Kike lo dijo así, y así se hace: `.barra-lin__nombre` y `.barra-lin__cifra`
 * suben de 0,92em a 1,06em, y la separación entre filas de `--space-2` a
 * `--space-3`. **La barra no se estira para llenar**: `block-size` sigue en
 * 1,35rem, el mismo de antes. Un carril más gordo no dice más; un número más
 * grande sí se lee mejor, y es lo que Kike necesita.
 *
 * Todo en `em` y en tokens de espacio, así que sube con las cuatro escalas.
 * ══════════════════════════════════════════════════════════════════════════ */
/* ─── DOS RENGLONES, NO TRES · Kike, 28/08/2026 (mismo día, tarde) ─────────
 *
 * Los tres renglones fueron míos por leer dos instrucciones que empujaban al
 * mismo sitio —«que ocupen el alto de su tarjeta» y «la barra a todo el ancho y
 * los importes debajo»— y sumarlas. **Defecto propio.** El resultado: la tarjeta
 * de 489 a 738 px, y con eso el listado más abajo todavía.
 *
 * Kike: *«el mes a la izquierda y su importe a la derecha, en la misma línea. La
 * barra debajo, a todo el ancho.»* Se gana el renglón sin perder nada de lo que
 * el turno anterior sí acertó: la barra sigue llegando al extremo —que es lo que
 * la hace comparable— y el texto sigue crecido.
 *
 * `grid-template-areas` y no un apaño de columnas: dice la forma en dos líneas
 * y se lee igual que el boceto de Kike. */
.barras-lin--meses,
.barras-lin--cat { gap: var(--space-3); }
.barras-lin--meses .barra-lin,
.barras-lin--cat .barra-lin {
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-areas: "nombre cifra"
                       "carril carril";
  gap: var(--space-micro) var(--space-3);
  align-items: center;
}
.barras-lin--meses .barra-lin__nombre,
.barras-lin--cat .barra-lin__nombre {
  grid-area: nombre; font-size: 1.06em; justify-self: start;
}
.barras-lin--meses .barra-lin__cifra,
.barras-lin--cat .barra-lin__cifra {
  grid-area: cifra; font-size: 1.06em; text-align: end; white-space: normal;
}
.barras-lin--meses .barra-lin__carril,
.barras-lin--cat .barra-lin__carril { grid-area: carril; }
/* ─── Y EL `--ancho` DE CATEGORÍAS NO SE TOCA ──────────────────────────────
 *
 * Estuve a punto de forzar el carril a `100%`. **Habría roto la comparación**:
 * en categorías `--ancho` es *cuánto pesa esa categoría frente a la mayor*, y
 * ponerlas todas al 100 % deja seis barras iguales que ya no dicen nada.
 *
 * No hace falta: apilada, la fila entera es el bloque contenedor del carril, así
 * que su `--ancho` se resuelve contra el ancho de la tarjeta y no contra los
 * 87,8 px de la columna vieja. La mayor llega al extremo —que es lo que Kike
 * pidió— y las demás quedan proporcionadas. *La barra llega al extremo porque
 * la caja creció, no porque se la haya estirado.* */

/* ─── Y EL NOMBRE DE CATEGORÍA YA NO SE CORTA A 16ch ───────────────────────
 *
 * Esa regla existía por una razón que sigue siendo cierta —Kike: *«las líneas de
 * color deben partir desde el mismo punto; no ayuda en el comparativo que cada
 * una parta de diferente inicio»*—: con el nombre en su propia columna, «Sin
 * categoría» empujaba su barra mucho antes que «VM Máquina virtual».
 *
 * **Apiladas, todas las barras arrancan del borde izquierdo de su fila.** El
 * problema no se afloja: deja de poder existir, porque ya no hay ninguna columna
 * delante de la barra. Y entonces el corte a 16ch pasa a ser solo daño: recortaba
 * un nombre que ahora tiene la tarjeta entera para él.
 *
 * El `title` con el nombre completo se queda, que no estorba a nadie. */
.barras-lin--cat .barra-lin__nombre {
  inline-size: auto; overflow: visible; text-overflow: clip; white-space: normal;
}

/* ═══ LA FRONTERA DE LO QUE NO HA PASADO · Kike, 28 de agosto de 2026 ═══════
 *
 * En la ventana de cinco meses de Billing, los dos de la derecha **no tienen
 * dato del año en curso**: lo que se ve ahí son los años anteriores. Sin
 * marcarlo se lee como pronóstico.
 *
 * ─── TRES SEÑALES, NO UNA ─────────────────────────────────────────────────
 *
 * 1. **La raya vertical**, que es la que tiene que verse: es un elemento
 *    gráfico y por eso va contra `--text-secondary`, que en Flow ya está muy
 *    por encima del 3:1 que pide un elemento gráfico. Medida en pantalla y
 *    anotada en el informe.
 * 2. **El fondo del área**, deliberadamente flojo: acompaña, no informa. Si
 *    fuera lo único, sería *color como única señal*.
 * 3. **El rótulo en HTML**, que es el que de verdad lo dice. Va fuera del SVG
 *    porque un texto dentro no crece con la escala (`D-710`).
 *
 * El fondo usa `currentColor` a través de una variable de texto y no un tono
 * inventado, así que sigue al tema y al contraste alto sin una regla por
 * combinación. ══════════════════════════════════════════════════════════════ */
.grafico-sin-pasar {
  fill: color-mix(in srgb, var(--text-secondary) 10%, transparent);
}
.grafico-frontera {
  stroke: var(--text-secondary);
  stroke-width: 2;
  stroke-dasharray: 6 5;
}
/* En contraste alto la raya se cierra y engorda: es la señal, no el adorno. */
:root[data-contrast="high"] .grafico-frontera,
[data-contrast="high"] .grafico-frontera { stroke-width: 3; stroke-dasharray: none; }
:root[data-contrast="high"] .grafico-sin-pasar,
[data-contrast="high"] .grafico-sin-pasar {
  fill: color-mix(in srgb, var(--text-secondary) 16%, transparent);
}
.grafico-frontera-rotulo {
  margin-block-start: var(--space-2);
  text-align: center;
  color: var(--text-secondary);
}

/* ─── UN CAMPO BLOQUEADO SE VE BLOQUEADO, Y DICE POR QUÉ AL LADO ───────────
 *
 * El desglose de una compra cuyo tipo no lleva IGV se puede leer y no se puede
 * escribir. Va `readonly` y no `disabled` —un campo deshabilitado no se envía—,
 * así que **hay que verlo**: sin marca, un campo que no acepta lo que tecleas y
 * no lo explica es peor que uno que desaparece.
 *
 * El fondo no es la única señal: al lado va el rótulo con la razón, y el lector
 * de pantalla oye `aria-readonly`. *El color nunca puede ser la única señal.* */
.input--bloqueado {
  background: color-mix(in srgb, var(--text-secondary) 10%, var(--superficie-ventana));
  cursor: not-allowed;
}

/* ─── LA PÍLDORA DE LA CUENTA QUE SE ACABA DE MOVER · Kike, 28/08/2026 ──────
 *
 * Misma caja, mismo tamaño y mismo sitio que «deuda» y «solo tributos» — hereda
 * `.etiqueta-cuenta` entera y **no cambia ninguna medida**: eso era la condición
 * de Kike. Lo único que aporta es el tono, y el tono no va solo: la palabra
 * «ingresó» o «salió» está escrita dentro (`D-669`).
 *
 * No parpadea ni se desvanece: *un destello se pierde si estás mirando otra
 * parte de la pantalla*. Se queda hasta que se guarde otro movimiento. */
/* ─── LA PÍLDORA VA DEL COLOR DE SU ESTADO · Kike, 29/08/2026 ──────────────
 *
 * *«El label de entró y salió, ¿puede ser verde y rojo?»*
 *
 * **La palabra manda y el color se suma.** «ingresó», «salió» y «eliminado» lo
 * dicen enteras: el color no lleva nada que no esté escrito, que es la única
 * forma de que pueda llevar color (`D-669`).
 *
 * ─── EL TEXTO NO SE TIÑE, SE TIÑE EL FONDO ────────────────────────────────
 *
 * La letra se queda en `--text`, que ya cumple su 4,5:1 contra el fondo de la
 * píldora. Teñir la letra de rojo la habría dejado a merced de dos fondos
 * distintos —el de la píldora y el de la tarjeta— y con el rojo de la cifra de
 * un saldo negativo a un palmo. *Se tiñe lo que rodea, no lo que se lee.*
 *
 * ─── Y EL TONO NO ES EL DEL BORDE ─────────────────────────────────────────
 *
 * En una cuenta con «salió» hay **dos rojos tocándose**: el anillo alrededor y
 * la píldora dentro. Se mezcla el tono con el fondo de la tarjeta, así que el de
 * la píldora queda claramente más apagado que el del anillo, que va puro. El ΔE
 * entre los dos está medido en el informe. */
/* ─── EL RESPALDO VA EN EL `var()`, NO EN UNA DECLARACIÓN PROPIA ──────────
 * Primero puse `.etiqueta-cuenta--movida { --tono-estado: var(--brand-boton) }`
 * como respaldo, y **la píldora se quedó azul**: una propiedad declarada en el
 * propio elemento gana a la que hereda de su tarjeta, así que el tono del estado
 * no llegaba nunca. Medido: la píldora salía en `srgb .231 .510 .965`, que es
 * `--brand-boton`, con el anillo ya en rojo al lado. Defecto propio.
 * El respaldo va **dentro del `var()`**, que solo actúa si nadie la ha puesto. */
.etiqueta-cuenta--movida {
  background: color-mix(in srgb, var(--tono-estado, var(--brand-boton)) 30%, transparent);
  border-color: var(--tono-estado, var(--brand-boton));
  color: var(--text);
}
:root[data-contrast="high"] .etiqueta-cuenta--movida,
[data-contrast="high"] .etiqueta-cuenta--movida {
  background: color-mix(in srgb, var(--tono-estado, var(--brand-boton)) 42%, transparent);
  border-color: var(--tono-estado, var(--brand-boton));
}
/* ─── UN SOLO NOMBRE PARA EL TONO · lo leen la píldora y el anillo ────────
 * Estuvieron un rato con dos nombres —`--tono-estado` para la píldora y
 * `--aviso-borde` para el anillo— y **divergieron**: el ajuste de oscuro con
 * contraste alto tocaba solo uno, así que el anillo iba en `#f4756d` y la
 * píldora en `#ff8f88`. Medido: ΔE 11 entre dos cosas que dicen lo mismo.
 * Un tono, un nombre. */
.saldo-tarjeta[data-movida="ingresó"]  { --tono-estado: var(--income-ink); }
.saldo-tarjeta[data-movida="salió"]    { --tono-estado: var(--expense); }
.saldo-tarjeta[data-movida="eliminado"]{ --tono-estado: var(--warning-ink); }


/* ═══ EL BORDE QUE AVISA · Kike, 28 de agosto de 2026 ═══════════════════════
 *
 * *«Se podría hacer que el borde de la tarjeta se ponga de color verde como
 * notificación unos segundos y desaparezca en fade.»*
 *
 * ─── POR QUÉ ESTE SÍ PUEDE DESVANECERSE, Y LA PÍLDORA NO ──────────────────
 *
 * Hace dos días se decidió que la píldora no se apagara sola, porque *un
 * destello se pierde si estás mirando otra parte de la pantalla*. **No se
 * contradice**: hacen trabajos distintos. El borde **llama la atención** y se
 * va; la píldora **dice qué pasó** y se queda. Si Kike mira a otro lado y se
 * pierde el destello, no pierde nada: la palabra sigue ahí.
 *
 * Y por eso el color no carga con nada: *no es la única señal, ni siquiera es
 * la principal.*
 *
 * ─── NO EMPUJA NADA, Y ESO ES POR CONSTRUCCIÓN ────────────────────────────
 *
 * `.saldo-tarjeta` **ya tiene** `border: 1px solid var(--border)`. El sitio
 * estaba reservado desde siempre, así que aquí solo cambia el *color* del borde
 * que ya está. Medido: ninguna tarjeta se mueve ni crece.
 *
 * ─── DOS COLORES, PORQUE SI LLEVA SIGNIFICADO NO PUEDE SER SIEMPRE VERDE ──
 *
 * Kike lo pidió expresamente. **Verde para lo que entra, ámbar para lo que
 * sale.** Ámbar y no rojo: en Flow el rojo es error, vencido y saldo negativo, y
 * que salga dinero de una cuenta **no es un error** — llamarlo rojo enseñaría a
 * ignorar los rojos de verdad. El ámbar es el tono de «mira esto», el mismo de
 * «vence pronto».
 * ══════════════════════════════════════════════════════════════════════════ */
.saldo-tarjeta[data-movida] {
  /* El borde arranca en su color y vuelve al de siempre: `border-color` es lo
     único que se anima, así que la caja no se entera. */
  animation: aviso-borde 7s ease-out 1 forwards;
}
/* ─── Y CUANDO YA SONÓ, NO VUELVE A SONAR ─────────────────────────────────
 * Lo pone `destello-cuenta.js` cuando la huella de las marcas ya está en la
 * sesión. La píldora **no se toca**: sigue diciendo qué pasó. */
.saldo-tarjeta--ya-destello { animation: none; }
/* ─── EL BORDE SIGUE A LA PÍLDORA · Kike, 29 de agosto de 2026 ─────────────
 *
 * *«La cuenta siempre se marca de verde, así sea un gasto.»* Tenía razón por dos
 * motivos, y los dos eran míos:
 *
 * 1. La palabra salía de `monto < 0`, y **los gastos se guardan en positivo**
 *    —medido: de 0,35 a 5.774,40, ni uno negativo—. Así que la píldora decía
 *    «ingresó» siempre, y el borde la seguía.
 * 2. Y el 28 elegí **ámbar** para «salió» razonando que el rojo es error. Kike
 *    lo ha decidido de otra forma: **rojo**. Es su pantalla y es coherente con lo
 *    que ya hace la cifra de un saldo negativo.
 *
 * El borde **no decide nada**: sigue a la palabra, y la palabra sale del efecto
 * en el saldo. Un solo sitio decide, y es `Movimientos::guardar()`. */
/* ─── Y LOS TONOS SON LOS DEL DINERO, NO DOS DE LA PALETA ─────────────────
 * `--income` y `--expense` son los que Flow ya usa para lo que entra y lo que
 * sale, y están definidos en las cuatro combinaciones. Y **no son los mismos que
 * `--income-ink` / `--expense-ink`**, que son los del texto: eso importa aquí,
 * porque en una cuenta en negativo el borde rojo va a tocar una cifra roja, y
 * *dos cosas que se tocan se miden entre ellas* (`D-708`, `D-727`). El ΔE está
 * medido en el informe.
 *
 * Los tres tonos se declaran **una sola vez**, arriba, junto a la píldora. */
/* ─── Y EL ÁMBAR DE «ELIMINADO» · Kike, 29/08/2026 ────────────────────────
 * *«Si una operación se elimina, ¿podría ser amarilla y el label indicar algo
 * corto que lo represente?»* — y eligió la palabra: **«eliminado»**.
 *
 * Y el ámbar está bien elegido por una razón que conviene dejar escrita: **al
 * borrar un gasto la cuenta sube y al borrar un ingreso baja**. No es entrada ni
 * salida: es que algo dejó de estar. Verde o rojo serían verdad en un caso y
 * mentira en el otro, y «entró»/«salió» también.
 *
 * ─── Y ES `--warning-ink`, NO `--warning` ────────────────────────────────
 * Medido contra el fondo de la tarjeta: `--warning` en tema claro da **2,84 :
 * 1**, por debajo del 3:1 que pide un elemento gráfico. `--warning-ink` da
 * **5,79** ahí y no baja de 5,79 en ninguna de las cuatro combinaciones. Es la
 * misma corrección que ya se hizo con el verde, y por la misma razón. */

/* ─── POR QUÉ EL VERDE NO ES `--income` Y EL ROJO SÍ ES `--expense` ────────
 *
 * Parece una asimetría y tiene su razón medida, las dos.
 *
 * **El verde.** `--income` en tema claro da **2,82 : 1** contra el fondo de la
 * tarjeta — por debajo del 3:1 que pide un elemento gráfico. `--income-ink` da
 * **5,33** ahí y no baja de 5 en ninguna de las cuatro combinaciones.
 *
 * **El rojo se queda en `--expense`** justamente porque `--expense-ink` es **el
 * color exacto de la cifra de un saldo negativo**, y esos dos se tocan: usarlo
 * sería poner el mismo rojo a dos cosas que significan distinto. *Dos cosas que
 * se tocan se miden entre ellas* (`D-708`, `D-727`).
 *
 * ─── Y EN OSCURO CON CONTRASTE ALTO, EL ROJO NO SIGUE AL DE LA CIFRA ──────
 *
 * Medido: ahí `--expense` sube a `#ff8f88` y la cifra negativa está en
 * `#ff9a94` — **ΔE 6,0**, que es demasiado poco para dos cosas que significan
 * distinto y se tocan. En las otras tres combinaciones la distancia es 14,5 a
 * 16,9.
 *
 * Así que en esa combinación el borde **se queda con el rojo gráfico de siempre**
 * en vez de seguir al de contraste alto: la distancia vuelve a 16,9 y el
 * contraste contra el fondo sigue en 6,71 : 1, muy por encima del 3:1. *Subir el
 * contraste de una cosa no puede acercarla a otra que significa lo contrario.* */
:root[data-theme="dark"][data-contrast="high"] .saldo-tarjeta[data-movida="salió"],
[data-theme="dark"][data-contrast="high"] .saldo-tarjeta[data-movida="salió"] {
  --tono-estado: #f4756d;
}

/* ─── MÁS GRUESO, Y SIN EMPUJAR NADA · Kike, 29/08/2026 ────────────────────
 *
 * *«El borde sobre la cuenta afectada, ¿puede ser más grueso?»*
 *
 * ─── DE 2 PX A 4 · y por qué con `box-shadow` y no engordando el borde ────
 *
 * La tarjeta lleva `border: 1px`, y **ese píxel es todo el sitio reservado**.
 * Engordar el `border` de verdad ensancharía la caja y **empujaría el
 * contenido**, que es justo lo que Kike prohibió.
 *
 * `box-shadow: 0 0 0 Npx` dibuja un anillo **fuera** de la caja y **no ocupa
 * sitio en la disposición**: nada se mueve, ni al aparecer ni al irse. El grosor
 * que se ve es el borde de 1 px más el anillo:
 *
 *     antes   1 px de borde + 1 de anillo = **2 px**
 *     ahora   1 px de borde + 3 de anillo = **4 px**
 *
 * ─── Y CRECE CON LA ESCALA, COMO TODO ─────────────────────────────────────
 *
 * El anillo va en `em`, no en píxeles fijos: a escala normal son esos 3 px y a
 * la máxima sube con la letra. *Un realce que no crece con la letra se pierde
 * justo para quien subió la letra.*
 *
 * El anillo cabe en el hueco de la rejilla —`gap: var(--space-3)`— así que no
 * toca la tarjeta de al lado. Medido a las cuatro escalas. */
@keyframes aviso-borde {
  0%   { border-color: var(--tono-estado); box-shadow: 0 0 0 0.19em var(--tono-estado); }
  80%  { border-color: var(--tono-estado); box-shadow: 0 0 0 0.19em var(--tono-estado); }
  100% { border-color: var(--border);      box-shadow: 0 0 0 0.19em transparent; }
}

/* ─── Y QUIEN PIDE MENOS MOVIMIENTO NO VE EL FUNDIDO ───────────────────────
 *
 * `prefers-reduced-motion` no es una preferencia estética: hay quien se marea.
 * **Y no pierde nada**, que es la razón por la que se puede apagar sin más: la
 * información la lleva la píldora, con su palabra. Aquí el borde se queda
 * puesto, sin animación — y como el aviso ya no se desvanece, tampoco hace
 * falta que llame la atención moviéndose. */
/* ─── Y EL ANILLO SE QUEDA, QUE ESO NO ES MOVIMIENTO ──────────────────────
 * Medido: sin animación **no hay `box-shadow`**, porque el anillo lo dibujan
 * los fotogramas. Quien pide menos movimiento se quedaba con 1 px de borde en
 * vez de los 4 que Kike pidió — el grosor se le iba junto con el fundido, y
 * *pedir que algo no se mueva no es pedir que se vea menos*. Se repite aquí,
 * quieto. */
@media (prefers-reduced-motion: reduce) {
  .saldo-tarjeta[data-movida] {
    animation: none;
    border-color: var(--tono-estado);
    box-shadow: 0 0 0 0.19em var(--tono-estado);
  }
}

/* ─── LA SALIDA «EL PAPEL NO LO DICE», AL PIE DE SU CAMPO · 05/09/2026 ────────
 *
 * `salida-del-papel.js` mueve la casilla desde el recuadro amarillo hasta el
 * campo del que habla. Al llegar tiene que **seguir leyéndose como lo que es**:
 * una salida ofrecida por un aviso, no una opción más del formulario.
 *
 * Se marca con el mismo token de alerta que ya pinta `.aviso--alerta`, con
 * **borde además de fondo** —el color solo no comunica—, y el texto queda en
 * `--warning-ink`, que es el par comprobado en alto contraste. */
.salida-del-papel {
  margin-top: .5rem;
  padding: .35rem .6rem;
  border: 1px solid var(--warning);
  border-radius: var(--radius-sm);
  background: var(--warning-bg);
  color: var(--warning-ink);
  align-items: center;
}
.salida-del-papel input[type="checkbox"] { accent-color: var(--warning); }
.salida-del-papel:focus-within { outline: var(--focus-width) solid var(--focus-color); outline-offset: 2px; }

/* ─── LO QUE ESTÁ EN EL CAMPO DEL CORREO Y NO ES UN CORREO · `D-1127` ────────
 * No se pinta como enlace —no lo es— y no se distingue solo por color: lleva la
 * palabra en `aria-label` y el texto a la vista. En alto contraste el borde
 * punteado sigue estando, que es forma y no color. */
.celda-correo-malo {
  display: inline-block; max-width: 18ch;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: var(--fs-caption); color: var(--text-secondary);
  border-block-end: 1px dotted currentColor;
}

/* ─── UN «MES» NATIVO TIENE QUE CABER ENTERO · `D-1130` · 06/09/2026 ─────────
 *
 * En `/registro-ventas` el filtro enseñaba **«septiembre de 2(»**: el año no se
 * leía y el icono del calendario se montaba encima. Medido ese día: el texto
 * más largo —«septiembre de 2026»— ocupa **164 px**, y con el relleno, los
 * bordes y el icono del selector hacen falta **218**; el campo medía **187**.
 * Treinta y un píxeles de menos.
 *
 * **Y esto no lo caza el DOM:** `scrollWidth − clientWidth` da **0**, porque el
 * control nativo recorta por dentro, en su propia caja. Se vio ampliando la
 * captura, no midiendo. Es una forma de fallo que ningún guardián de los que
 * hay coge, y queda dicho aquí para que no se vuelva a buscar por el lado que
 * no responde.
 *
 * Va en `em` y no en píxeles **a propósito**: Kike puede agrandar el texto
 * desde Configuración, y un ancho fijo volvería a cortarlo en cuanto lo haga.
 * `min-inline-size` solo crece: donde ya cabía, no cambia nada. */
input[type="month"] { min-inline-size: 13em; }

/* ─── EL VEREDICTO DEL RUC, EN LA BANDEJA · KIKE, 06/09/2026 ─────────────────
 *
 * *«Colocar los datos, pero un label que diga "no legible" en rojo.»*
 *
 * Va con **palabra y con símbolo**, nunca solo con el color: en alto contraste
 * el rojo puede no distinguirse del gris, y esto es lo que decide si una compra
 * se usa o se vuelve a leer. El `✕` y el `✓` los pone la plantilla, dentro del
 * texto, para que un lector de pantalla los diga.
 *
 * Los tintes son los que la casa ya usa para lo mismo —`--expense-*` para lo
 * que va mal, `--income-*` para lo que está bien—, así que no se inventa una
 * paleta nueva y siguen valiendo los dos temas y el alto contraste. */
.badge--error {
  background: var(--expense-bg); color: var(--expense-ink);
  border-color: var(--expense); font-weight: var(--fw-semibold);
}
.badge--ok {
  background: var(--income-bg); color: var(--income-ink);
  border-color: var(--income); font-weight: var(--fw-semibold);
}
[data-contrast="high"] .badge--error,
[data-contrast="high"] .badge--ok { border-width: 2px; }
