/* ═══════════════════════════════════════════════════════════════════════════════
   ⛔ COPIA DE APP · ADAPTACIÓN DECLARADA (2026-08-18, resincronizada 2026-08-25 y 2026-08-28)
   Original: design-system/patrones/pantalla-lista/pantalla-lista.css
   Cambio declarado: los selectores que empiezan por `.srch` se ACOTARON a `.exds-pl .srch`.
   Verificado con `diff` contra el original el 2026-08-28: devuelve SÓLO esas líneas (y
   `.exds-pl-bar .srch[data-corto]` se queda igual: no empieza por `.srch`, así que no entra
   en esta adaptación).

   POR QUÉ. El README del kit manda «BORRA el bloque `.srch` si tu app ya lo carga (el
   backoffice lo hace)». Borrarlo dejaría la × de limpiar SIN ESTILO: `backoffice.css`
   define `.srch`, `:focus-within`, `> svg`, `input` y `::placeholder`, pero **no**
   `.srch button`. Y dejarlo tal cual reescribiría el buscador de las OTRAS DOS pantallas
   que ya montan `.srch` (el historial de Ayuda y el buscador de contexto del asistente):
   relleno derecho 12 → 7, borde en reposo `control` → `default`, hover nuevo y × con caja.
   Acotar es la tercera vía: el patrón se lleva el buscador del catálogo ENTERO y ninguna
   otra pantalla se mueve. *Añadir una sección no puede repintar dos que nadie miró.*

   ⚠ Y LO QUE ESTO DEJA APUNTADO, que no es de esta tarea: `backoffice.css` va DETRÁS del
   canon en esas cuatro propiedades. Homologarlo es un cambio de componente (GOBIERNO §4),
   con su lint de cobertura y su medición — no un efecto colateral de montar una lista.

   ⛔ AQUÍ DECÍA «UNA SOLA Y ESCRITA AQUÍ», Y DEJÓ DE SER CIERTO SIN QUE LA CABECERA LO
   AVISARA (revisor-qa, 2026-08-28 · NO PASA). Esta hoja se fue quedando ATRÁS del canon en
   TRES propiedades más, sin que nadie las declarara aquí: `.exds-pl-bar > .exds-pl-bar-vis`
   (hijo directo) donde el canon ya usaba el descendiente, y dos `margin-left`
   (`.exds-pl-vistas-n`, `[data-pl-vista-chev]`) que el canon ya había retirado. *Una
   cabecera que promete «UNA SOLA línea» deja de vigilarse sola en cuanto aparece una
   segunda: el `diff` que la sostiene nadie vuelve a correrlo, porque el comentario ya dice
   que no hace falta.* Las tres se homologaron al canon el 2026-08-28 (comentarios
   `corregido 2026-08-28` y `Ricardo 2026-08-28` en el cuerpo de esta hoja) y el `diff`
   verificado esa misma fecha vuelve a devolver SÓLO la línea de `.srch` declarada arriba —
   pero ese cero es el estado medido HOY, no una garantía permanente: la próxima vez que el
   canon cambie sin que esta copia se resincronice, la cabecera puede volver a mentir sin que
   nada la delate. Correr el `diff` sigue siendo la única prueba real; esta nota no lo
   sustituye.
   ═══════════════════════════════════════════════════════════════════════════════ */
/* ═══════════════════════════════════════════════════════════════════════════════
   ▓ PATRÓN · PANTALLA DE LISTA — GEMELO VANILLA (hoja autocontenida) ▓
   2026-08-16 · implementa `design-system/PATRON_PANTALLA_LISTA.md` v0.1.

   ⛔ ESTA HOJA NO INVENTA REGLAS. Cada medida, cada color y cada conducta sale de la
   ficha o de la pieza de catálogo que el patrón MONTA (regla dura 5: no dibuja
   controles, los monta). Donde algo no estaba escrito, se dice en el README bajo
   «Lo que la ficha no resolvió» — no se decide aquí en silencio.

   ⛔ EL SEGMENTADO ES COPIA FIEL DE `.mc-seg` (backoffice.css 5305-5314), incluido el
   KNOB QUE SE DESLIZA. La ficha lo dice sin ambigüedad: *el blanco del activo lo pinta
   un knob por debajo, y el botón activo es transparente.* Un fondo pintado en el botón
   se ve igual quieto y no se mueve nunca.

   ⛔ NO ES RESPONSIVE DE TELÉFONO. Portal y backoffice son de ESCRITORIO (CLAUDE.md §7,
   Ricardo 2026-07-29). No hay un solo `@media (max-width: …)` aquí, y su ausencia es
   deliberada: la ficha §Móvil dice «NO APLICA».

   DEPENDE de los tokens del canon (`design-system/colors_and_type.css`). No trae ni un
   valor de color, sombra, duración ni curva propio: si un token falta, la pieza se ve
   mal — y eso es correcto, el patrón no puede inventarse el sistema de la app anfitriona.

   → REQUISITO DE MONTAJE (el mismo que el acordeón guiado, y por lo mismo): la app debe
     traer un reinicio con `box-sizing: border-box` y `margin/padding: 0` que **nombre
     `::before` y `::after`**. Sin él la tarjeta suma su borde por fuera y los dos bordes
     dejan de coincidir — que es justo lo único que este patrón existe para garantizar.

   CÓMO SE MONTA: esta hoja + pantalla-lista.js + el marcado del README.md.
   ═══════════════════════════════════════════════════════════════════════════════ */

/* ═══════════════════════════════════════════════════════════════════════════════
   ▓ 1 · EL CONTENEDOR DE ANCHO — la regla dura 1 del patrón, y toda su razón de ser ▓

   `DECISION-FUENTE: Ricardo 2026-08-16 — «regla: ancho de toolbar y de tabla deben ser
   idénticos (visualmente hablando)».`

   ⛔ HAY **UN** ELEMENTO QUE MIDE, Y ES ÉSTE. La barra y la tarjeta son sus hijos
   directos y **no declaran ancho propio en ninguna parte**: ni `max-width`, ni margen
   lateral, ni relleno lateral. Los dos toman el 100 % de lo mismo, así que sus bordes
   izquierdo y derecho coinciden POR CONSTRUCCIÓN, no por dos cuentas que dan parecido.

   *Dos anchos que se calculan aparte acaban casi iguales, y «casi igual» no se lee como
   una decisión: se lee como un error, y el ojo lo nota antes de saber por qué.*

   Quien monta ajusta el ancho **en un solo sitio** — `--exds-pl-ancho` — y las dos
   piezas lo siguen. No existe un segundo mando.
   ═══════════════════════════════════════════════════════════════════════════════ */
.exds-pl {
  --exds-pl-ancho: none;      /* el anfitrión lo fija UNA vez; ambas piezas lo heredan */
  --exds-pl-cols: 1fr;        /* la rejilla de columnas · la comparten cabecera y filas */
  --exds-pl-fila: 67px;       /* alto de fila del canon de Tabla (modo acción) */

  width: 100%;
  max-width: var(--exds-pl-ancho);
  margin-left: auto;
  margin-right: auto;
  font-family: var(--font-sans);
  color: var(--fg1);
}

/* ⛔ LA REGLA DEL ANCHO, EN UNA SOLA DECLARACIÓN.
   Está escrita así a propósito: si algún día alguien quiere darle otro ancho a una de
   las piezas, tiene que sacarla de este selector — y sacarla de aquí se ve al leer el
   archivo. *Un defecto que exige un cambio visible es un defecto que no ocurre solo.*

   ⛔ ESTA LÍNEA SE HA MOVIDO TRES VECES Y SE ESCRIBE ENTERA PARA QUE NO HAYA UNA CUARTA
   SIN SABERLO. Nació FUERA (barra y tarjeta, dos cajas hermanas sobre el lienzo) · el
   2026-08-16 Ricardo la metió DENTRO («la barra pasa dentro de la tarjeta») · el
   2026-08-19 la volvió a sacar, primero la banda de acciones (17:03) y después la de
   filtros (18:17) · y el 2026-08-20 lo cerró sin ambigüedad:
   `DECISION-FUENTE: Ricardo 2026-08-20 — «debe de ir fuera, tanto toolbar como barra de`
   `filtros van fuera de la card que contiene la tabla».`

   **LO VIGENTE: LA TARJETA CONTIENE ÚNICAMENTE LA TABLA** — cabecera, filas y pie. La
   barra y sus dos bandas son HERMANAS de la tarjeta, hijas directas de `.exds-pl`, así que
   vuelven a medir `100%` del mismo padre y sus bordes coinciden POR CONSTRUCCIÓN.

   *Una decisión que se revierte sin saber que se revierte vuelve a revertirse en un mes.*
   Por eso aquí está el recorrido y no sólo el destino. Y la factura de la última vuelta,
   que es la que hay que evitar: el 19-ago la decisión se aplicó **sólo a los archivos que
   esa sesión tenía abiertos** —gemelo y kit— y seis sitios más se quedaron un día entero
   diciendo lo contrario. *Una decisión de anatomía no se aplica donde se está mirando: se
   aplica donde vive la anatomía, y eso es una lista que hay que enumerar.* */
.exds-pl > .exds-pl-card {
  width: 100%;
  max-width: none;
  margin-left: 0;
  margin-right: 0;
}

/* ═══════════════════════════════════════════════════════════════════════════════
   ▓ 2 · LA BARRA — va FUERA de la tarjeta, sobre el lienzo, y tiene DOS BANDAS ▓

   `DECISION-FUENTE: Ricardo 2026-08-20 — «debe de ir fuera, tanto toolbar como barra de`
   `filtros van fuera de la card que contiene la tabla».`

   ⛔ QUÉ SUSTITUYE, PORQUE ESTE BLOQUE DECÍA LO CONTRARIO. Decía «LA BARRA ENTRA EN LA
   TARJETA» (Ricardo, 2026-08-16) con este argumento: *barra, vistas y tabla son un solo
   objeto —la lista y sus controles—, y separarlos en dos superficies obliga al ojo a
   reconstruir la relación.* El argumento no era malo; lo desempata la PARTICIÓN POR
   SIGNIFICADO: fuera va lo que **ACTÚA sobre** la lista (buscar, elegir colección, filtrar,
   crear); dentro, lo que **ES** la lista. Y el caso que lo cierra sin recurrir al gusto:
   **«Nuevo» no pertenece a la lista**, porque crea un registro que todavía no está en ella.

   ⛔ Y ESO CAMBIA QUÉ SIGNIFICA LA REGLA DURA 1 — otra vez, y en la dirección contraria.
   Metida DENTRO, la barra heredaba el ancho de la tarjeta y la comparación se cumplía sola
   (*un gate que deja de ver lo que mide no se pone rojo: se pone verde*), así que la regla
   había bajado un nivel: contenido de la banda contra texto de la celda. FUERA aparece una
   referencia que dentro no existía —el **BORDE VISIBLE** de la tarjeta—, y ésa es la línea
   con la que compara el ojo: Ricardo lo dijo mirando el render **con el gate en 0,00**.

   **Lo vigente:** el contenido de las bandas arranca y termina EXACTAMENTE en el borde de
   la tarjeta → **relleno lateral 0**, declarado abajo. Que el texto de las columnas quede
   13 px más adentro es CORRECTO: está dentro de una tarjeta, y el contenido de una tarjeta
   se sangra respecto a su borde.

   ⛔ LA DESVIACIÓN DEL HANDOFF QUE VIVÍA AQUÍ YA NO APLICA, y se retira NOMBRADA en vez de
   borrarse: el handoff ponía las bandas a 14 (`--space-3-5`) y se habían fijado en 12
   (`--space-3`) para igualarlas a las celdas. Fuera de la tarjeta la celda dejó de ser la
   referencia, así que el número no es 14 ni 12: es **0**. `--space-3` se queda para el PIE
   y las CELDAS, que son lo que sí vive dentro.

   ⛔ DOS BANDAS, UN SOLO CONTENEDOR: arriba las ACCIONES (buscar · vistas · filtrar ·
   secundarios · la acción principal · «⋯»), abajo la banda B (filtros sueltos). **Las dos,
   fuera.** *Una tercera banda colgada aparte sería un tercer relleno que nadie mide.*
   ═══════════════════════════════════════════════════════════════════════════════ */
.exds-pl-bar {
  display: flex;
  flex-direction: column;   /* las bandas se apilan; dentro de cada una se manda en fila */
  align-items: stretch;
  gap: 0;                   /* la separación entre bandas la pone su propio relleno */
  background: transparent;
  /* ⛔ EL AIRE HASTA LA TARJETA: **16, y declarado en UN solo sitio** (Ricardo, 2026-08-19:
     «gap toolbar-tabla un poco grande»).

     **Se veían 28 y nadie los había decidido:** salían de sumar el relleno inferior de la banda
     (14) con este margen (14), dos números elegidos por separado y en momentos distintos. Es el
     mismo defecto que apareció en el kit la misma tarde (12 declarados + 14 heredados = 26).
     *Un aire que sale de sumar dos números que nadie sumó no lo decidió nadie — y por eso
     siempre acaba siendo «un poco grande».*

     **Por qué 16:** el alto de una fila es 39, así que la separación entre las dos superficies
     pesa menos que una fila y no compite con la lectura de la tabla; y es mayor que el relleno
     interior (12), así que separar DOS superficies no se lee igual que separar dentro de una.
     Es un escalón de la escala, no un número afinado a ojo.
     El relleno inferior de la banda baja a 0: ahí ya no separa nada. */
  margin-bottom: var(--space-4);   /* 16 — el ÚNICO sitio donde se declara este aire */
  /* ⛔ SIN HAIRLINE INFERIOR desde el 2026-08-19. Lo llevaba para separarse de la cabecera
     cuando vivía DENTRO de la tarjeta; fuera, quien separa es el borde de la propia tarjeta y
     dos líneas seguidas a 14 px se leen como un error, no como dos separaciones.
     *Cada línea que se quita es una zona menos que reconstruir.* */
}

/* ⛔ EL RELLENO LATERAL, EN UNA SOLA DECLARACIÓN PARA TODA LA COLUMNA — es el lado de la
   barra en la regla dura 1, y el token es EL MISMO que el de las celdas a propósito.
   Si alguien le da otro valor a una banda, tiene que sacarla de este selector, y sacarla
   de aquí se ve al leer el archivo.

   ⛔ EL PIE ENTRÓ AQUÍ EL 2026-08-17, y es el bloqueante 1 de la auditoría del handoff.
   Hasta ese día el pie declaraba su propio `padding: … var(--space-4)` (16) mientras las
   bandas iban a 12 y las celdas a 12: **la misma tarjeta tenía dos rellenos laterales, y
   el gate del ancho no lo veía porque sólo mira la banda contra la celda.** Es el defecto
   exacto que el handoff traía por triplicado (14 · 11 · 16). *Un gate mide la pregunta que
   le hicieron, no la regla entera: la regla dice UN relleno, y ahora hay un solo sitio
   donde escribirlo.* */
/* ⛔ DOS RELLENOS, PORQUE AHORA HAY DOS SUPERFICIES (2026-08-19).
   Hasta hoy todo compartía uno solo: la barra vivía DENTRO de la tarjeta y lo único comparable
   era contenido contra contenido. Al salir la barra, apareció una referencia que antes no
   existía —el BORDE de la tarjeta— y Ricardo la vio en el render **con el gate en 0,00**:
   *«el ancho de la toolbar no coincide con el de la tabla»*. Los dos tenían razón sobre cosas
   distintas; él miraba la línea que se ve.
   *Una regla no envejece por estar mal escrita: envejece porque cambia la pantalla que
   describía.* */
.exds-pl-bar > .exds-pl-bar-acc {
  /* FUERA de la tarjeta: el contenido arranca y termina en el BORDE de la tarjeta. */
  padding-left: 0;
  padding-right: 0;
}
/* ⛔ DESCENDIENTE, NO HIJO DIRECTO (corregido 2026-08-28). Con el combinador `>` este
   selector dejó de emparejar el día que la banda ganó su envoltorio de nacer/morir:
   `.exds-pl-bar-vis` vive ahora dentro de `.exds-pl-bandawrap > .exds-pl-bandawrap-in`, dos
   niveles más abajo que hijo directo de `.exds-pl-bar` — así en Facturación desde el
   2026-08-19/25 como en Contactos desde el 2026-08-28. No producía un defecto VISIBLE: el
   reinicio que exige el anfitrión ya deja el relleno lateral en 0 por defecto, así que la
   regla era un cero pisando otro cero. Pero dejaba de IMPONER la regla dura 1 por sí sola —
   la sostenía la casualidad del reset, no la hoja. Con el descendiente, la regla vuelve a
   mandar aunque el día de mañana el reset cambie. */
.exds-pl-bar .exds-pl-bar-vis {
  /* FUERA también, desde el 2026-08-19: los filtros ACTÚAN sobre la lista, igual que buscar.
     Alinean con el borde de la tarjeta, como la banda A. */
  padding-left: 0;
  padding-right: 0;
}
.exds-pl-foot {
  /* DENTRO de la tarjeta: el relleno de siempre, el mismo que el de las celdas. */
  padding-left: var(--space-3);
  padding-right: var(--space-3);
}

/* ⛔ SI LA BANDA A LA PINTA LA PIEZA `Toolbar` DEL CATÁLOGO, SE LE ANULA EL AIRE INFERIOR
   (2026-08-20). La banda A de ESTE gemelo es `.exds-pl-bar-acc`, que ya declara
   `padding-bottom: 0` para que el aire hasta la tarjeta lo ponga `.exds-pl-bar` y sólo él. Pero
   la banda A también puede ser la `Toolbar` canónica (`.exds-tbar`), que trae su propio ritmo
   vertical — `padding: 12px 28px 14px` —, y ese **14 se suma al 16** sin que nadie lo declare:
   el aire real pasa a 30.
   No es hipotético: es exactamente lo que le pasaba al KIT React, que sí monta la `Toolbar`, y
   por eso su aire valía 30 mientras la ficha lo daba por cerrado en 16 desde el 19-ago. La regla
   se escribe **también aquí** para que la hoja del patrón sea correcta con CUALQUIERA de las dos
   bandas A, no sólo con la suya.
   ⛔ Va ACOTADA a `.exds-pl` a propósito: los 14 son el ritmo PROPIO de la `Toolbar` y son
   correctos donde vive sola. Aquí no se corrige la Toolbar — se le pide que ceda el control del
   aire inferior al patrón que la monta, que es quien conoce a su vecino de abajo. Es la mitad
   vertical de lo que `marco=ninguno` ya hace con el eje X, y que nunca se escribió.
   *Una hoja que sólo funciona con el montaje que su propio ejemplo usa no es la hoja del patrón:
   es la hoja de ese ejemplo.* */
.exds-pl [data-pl="toolbar-banda"] .exds-tbar { padding-bottom: 0; }

/* ── BANDA A · LAS ACCIONES ────────────────────────────────────────────────────── */
.exds-pl-bar-acc {
  display: flex;
  align-items: center;
  gap: var(--space-3-5);
  /* Lateral: lo pone el selector compartido de arriba (regla dura 1). Aquí sólo el eje
     vertical, que es propio de la banda. */
  padding-top: var(--space-3);
  /* ⛔ SIN relleno inferior desde el 2026-08-19: el aire hasta la tarjeta lo declara
     `.exds-pl-bar` y sólo él. Dejarlo aquí lo sumaba en silencio (ver arriba). */
  padding-bottom: 0;
  /* ═════════════════════════════════════════════════════════════════════════════════
     ⛔ LA BANDA DE ACCIONES **NUNCA TIENE DOS RENGLONES** — regla fija (2026-08-21)
     `DECISION-FUENTE: Ricardo 2026-08-21 — «Nunca debe tener dos filas la toolbar, esa es
     una regla fija.»` Lo dijo mirando Finanzas › Facturación con el cajón de detalle
     abierto, donde la banda se partía en dos (52 → 106 px de alto).

     ⭐ Y AUN ASÍ EL `flex-wrap: wrap` **SE QUEDA**, que es lo contrario de lo que uno
     escribiría primero. Se intentó quitarlo y **se midió el resultado**: con `nowrap` la
     banda deja de partirse, sí — pero porque **DESBORDA en silencio**. A 1440 con el cajón
     abierto siguió midiendo 52 px de alto con 924 px de contenido dentro de 700, y el
     plegado **no replegó ni un botón**.

     LA RAZÓN ES QUE `wrap` NO ES EL DEFECTO: ES EL SENSOR. `plegado.js` sabe que la barra no
     cabe comparando el ALTO de la banda con el de una fila (`partida()`), o sea *envolver es
     la señal que dispara el repliegue*. Sin `wrap` la señal nunca se emite, el mecanismo se
     queda dormido y el contenido se sale de la pantalla por un lado. **Quitarlo no cumple la
     regla de Ricardo: la esconde.**

     *Una propiedad que produce el síntoma puede ser el instrumento que lo detecta; borrarla
     apaga la alarma, no el incendio.*

     ⛔ QUIÉN CUMPLE LA REGLA, ENTONCES: `plegado.js`, montado por cada pantalla — repliega
     las utilidades al «⋯», cede el texto de la acción principal y encoge el buscador, en ese
     orden. **Una banda que se ve envuelta es una banda a la que no le montaron el plegado**,
     y ése era exactamente el defecto de Facturación: el mecanismo existía en el catálogo,
     Contactos lo usaba, y la pantalla recién homologada no lo cableó.
     Lo mide `design-system/gate_toolbar_un_renglon.js`, en un navegador de verdad y con el
     cajón abierto y cerrado. *Antes no lo veía ningún gate: el del ancho mira si los bordes
     coinciden, no si la barra cabe.*

     ⚠ LO QUE ESTA REGLA **NO** ALCANZA: la banda B (`.exds-pl-bar-vis`). Ahí vive el recibo
     de lo filtrado, cuyo largo lo decide el usuario al poner filtros; obligarlo a un renglón
     sería esconder filtros puestos. Es una banda distinta con un contrato distinto, y se
     declara para que nadie la «homologue» por parecido. */
  flex-wrap: wrap;
  /* ⛔ SI LA BANDA LLEGA A PARTIRSE, LAS ACCIONES SIGUEN A LA DERECHA. Con todo en una línea
     esto NO HACE NADA —el muelle (`flex: 1 1 0`) se queda todo el hueco libre, así que no
     queda espacio que repartir—; sólo actúa en la segunda línea, donde el muelle ya no está.
     Sin esto, al no caber, las acciones caían pegadas al borde IZQUIERDO y la barra se leía
     como dos barras distintas.
     ⛔ DESDE EL 2026-08-21 ESA SEGUNDA LÍNEA YA NO ES UN ESTADO EN EL QUE LA PANTALLA SE
     QUEDE: es (a) el fotograma que el plegado usa para medir y corregir, y (b) el suelo por
     debajo del cual ya no queda nada que ceder sin perder una función — el caso que el canon
     ya acepta con su número (Contactos a 1180 px: piden 427, hay 374). Para los dos, bajar
     por la derecha sigue siendo lo correcto.
     Se descubrió MIDIENDO, no leyendo: al añadir los secundarios la barra pasó de 66 a 120
     px de alto en los cuatro anchos, y el gate del ancho seguía en verde — porque mide si
     los bordes coinciden, no si la barra cabe. *Un verde no dice que la pantalla esté bien:
     dice que la pregunta que ese gate hace tiene buena respuesta.* */
  justify-content: flex-end;
}

/* ── BANDA B · LAS VISTAS ───────────────────────────────────────────────────────
   Va SEPARADA DE LA DE ARRIBA POR UN HAIRLINE (así está en la captura) y arranca por la
   izquierda: es un selector, no una fila de acciones. El hairline es el mismo `subtle`
   que separa las filas de la tabla — dos grises distintos para dos separaciones del mismo
   peso se leen como un descuido. */
/* ══════════════════════════════════════════════════════════════════════════════════════
   LA BANDA DE FILTROS SE HACE SITIO · papel 2 (Ricardo, 2026-08-19 · opción A: «empuja»)
   ──────────────────────────────────────────────────────────────────────────────────────
   Antes la franja aparecía y desaparecía en un fotograma y la tabla pegaba un brinco de
   39 px. *Un salto se lee como un fallo, no como una respuesta a lo que acabas de hacer.*

   EL MECANISMO ES EL DEL CANON: `grid-template-rows: 0fr → 1fr` — el mismo que la Toolbar
   usa para desplegar su resumen de cifras. Anima el ALTO de verdad, así que lo de abajo se
   mueve solo: ni un píxel tecleado. Papel 2 (apertura/cierre) para el hueco, papel 4
   (sube y se enciende) para la pastilla, con 120 ms de retardo — *si entrara mientras el
   hueco crece se vería aplastada contra el borde.*

   ⛔ ESTADO EN EL GEMELO, dicho para que nadie lo dé por hecho: estas reglas están aquí y
   son el espejo del kit, pero **el ejemplo de Cartera no las ejercita** — su banda lleva un
   filtro rápido permanente («Por aplicar»), así que nunca nace ni muere entera. Quien monte
   una lista SIN filtros rápidos tiene que envolver la banda en
   `.exds-pl-bandawrap > .exds-pl-bandawrap-in`, igual que hace `PantallaLista.jsx`.

   ✅ Y DESDE EL 2026-08-20 YA HAY UN EJEMPLO QUE SÍ LAS EJERCITA, porque *una regla que
   ningún ejemplo ejercita es una regla que nadie ha visto funcionar: existe en el archivo y
   no en el producto.* Vive en `ejemplos/banda-que-nace.html` — una lista SIN filtros
   rápidos, que es el caso donde la banda nace de cero — y se mide con `_prueba_banda.js`.

   ⛔ Y LO QUE ESA PRUEBA MIDE NO ES «ANTES Y DESPUÉS», que es lo que uno escribiría primero:
   comprobar que la banda pasa de 0 a 58 px demuestra que APARECE, y aparecer es justo lo que
   hacía antes —de un fotograma al siguiente—, que era el defecto. Lo que se mide es el
   CAMINO: se muestrea MIENTRAS se mueve y se exige que pase por alturas intermedias.
   *Un estado final idéntico puede venir de un salto o de un viaje; sólo mirando en medio se
   distinguen, y es justo en medio donde vive la diferencia que Ricardo pidió.*
   Medido: 11 cuadros intermedios de 20 · la tabla baja **58,0** px, exactamente lo que crece
   la banda (empuja, no tapa) · entrada ≈217 ms, salida ≈133 ms (la salida no se hace
   esperar) · y con «reducir movimiento» abre igual, a los 60 ms y sin viaje.
   ══════════════════════════════════════════════════════════════════════════════════════ */
.exds-pl-bandawrap {
  display: grid;
  grid-template-rows: 0fr;
  /* La SALIDA (120 ms) va más rápida que la entrada (250): por eso la transición se declara
     dos veces. Una sola regla daría el mismo reloj en los dos sentidos — y aparecer se
     anuncia, desaparecer no se hace esperar. */
  transition: grid-template-rows var(--motion-fast) var(--ease-exit);
}
.exds-pl-bandawrap[data-on="true"] {
  grid-template-rows: 1fr;
  transition: grid-template-rows var(--motion-base) var(--ease-default);
}
.exds-pl-bandawrap-in { overflow: hidden; min-height: 0; }

/* ⛔ CORREGIDO 2026-08-23 (`gate_gemelos_conducta.js`) — ESTO ERA UN DEFECTO REAL, no sólo una
   divergencia de texto. La versión anterior animaba la ENTRADA con una `transition` desde
   `opacity:0` puesto en la regla base — exactamente el error que el propio kit deja escrito
   en su comentario: *«con una transición la pastilla salía YA ENCENDIDA (opacidad 1 a los
   60ms, debiendo ser 0) — un elemento que NACE con su estado final puesto no tiene desde
   dónde transicionar»*. La entrada necesita `@keyframes` (algo que APARECE); la salida sigue
   siendo `transition` (el elemento YA estaba en la página). Las pruebas de `banda-que-nace`
   miden el ALTO del envoltorio, no la pastilla, así que este defecto no lo cazaban. */
@keyframes exds-pl-pastilla-in { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
.exds-pl-bandawrap[data-on="true"] .exds-fpill-k {
  animation: exds-pl-pastilla-in var(--motion-base, 250ms) var(--ease-out, cubic-bezier(.16,1,.3,1)) 120ms both;
}
.exds-pl-bandawrap:not([data-on="true"]) .exds-fpill-k {
  opacity: 0;
  transform: translateY(6px);
  transition: opacity var(--motion-fast, 120ms) var(--ease-exit),
              transform var(--motion-fast, 120ms) var(--ease-exit);
}

@media (prefers-reduced-motion: reduce) {
  .exds-pl-bandawrap,
  .exds-pl-bandawrap[data-on="true"] { transition: none; }
  .exds-pl-bandawrap[data-on="true"] .exds-fpill-k { animation: none; opacity: 1; transform: none; }
  .exds-pl-bandawrap:not([data-on="true"]) .exds-fpill-k { transition: none; }
}

.exds-pl-bar-vis {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  /* Lateral: selector compartido de arriba (regla dura 1). */
  /* ⛔ SIN DIVISORIAS, Y EL AIRE RECALCULADO ENTERO (Ricardo, 2026-08-20).
     `DECISION-FUENTE: Ricardo 2026-08-20 — «A y vamos más allá. Ajustar el aire para no`
     `dejar huecos visuales».`

     **(a) Fuera las dos líneas.** Esta banda vive sobre el LIENZO, no dentro de la tarjeta, y
     ahí quien separa es el aire, no una raya. Es la misma lógica con la que el 2026-08-19 se
     retiró el hairline inferior de `.exds-pl-bar`: *fuera de una caja, dos líneas seguidas se
     leen como un error, no como dos separaciones.* La versión anterior (Ricardo, 2026-08-17)
     las quería «de borde a borde» para separar dos ZONAS — y era correcta **mientras la banda
     vivía dentro de la tarjeta**, donde una línea es cromo del contenedor. Al salir, el
     contenedor desapareció y la línea se quedó sin nada que dividir.

     **(b) Y el relleno inferior baja a 0, que es la mitad que de verdad había que pensar.**
     Al quitar las líneas, el relleno que existía PARA SEPARARSE DE ELLAS se queda huérfano y
     deja un hueco. Medido antes de tocar nada, sumando lo que se acumulaba:

     | Tramo | Antes | Ahora |
     |---|---|---|
     | banda A → banda B | 0 (A no tiene relleno inferior) + 1 (línea) + 12 = **13** | **12** |
     | banda B → tarjeta | 12 + 1 (línea) + 16 (margen de `.exds-pl-bar`) = **29** | **16** |

     Los 29 eran EXACTAMENTE el defecto que Ricardo cazó el 2026-08-19 («gap toolbar-tabla un
     poco grande»: 14+14=28), reaparecido por otra puerta. La regla dura 1 lo dio por cerrado
     poniendo a 0 el relleno inferior de la banda A **y se olvidó de que hay una segunda
     banda**, que es la última cuando existe. *Una regla que dice «el aire se declara en un
     solo sitio» sólo se cumple si TODOS los vecinos de ese sitio están a cero — y enumerar los
     vecinos es la parte que se salta.*

     **Los dos números que quedan, y no hay más:** 12 aquí, que separa DENTRO de la barra (dos
     bandas de la misma superficie), y 16 en `.exds-pl-bar { margin-bottom }`, que separa DOS
     superficies. Es el escalón que la propia regla dura 1 ya razonaba: separar dentro de una
     superficie no puede leerse igual que separar dos. */
  padding-top: var(--space-3);
  padding-bottom: 0;
  flex-wrap: wrap;
}

/* ── LA BANDA C SE FUNDIÓ EN LA B (Ricardo, 2026-08-17) ─────────────────────────
   `DECISION-FUENTE: Ricardo 2026-08-17 — «junto a vistas».`
   El recibo de lo filtrado —las `PastillaFiltro`— **no es una tercera banda**: vive en la
   misma fila que las vistas. Se le enseñaron las dos opciones renderizadas
   (`plans/decisiones/_decision_barra_montada_2026-08-17.html`, decisión 1).
   Con esto la barra vuelve a tener **DOS bandas**, que es lo que la ficha decía desde el
   principio, y desaparece una divisoria: *cada línea que se quita es una zona menos que el
   ojo tiene que reconstruir.*
   Aquí vivía `.exds-pl-bar-pills`. Se deja nombrada y no borrada en silencio. */

/* ═══════════════════════════════════════════════════════════════════════════════
   ▓ EL BUSCADOR · ES `.srch`, LA PIEZA DEL CATÁLOGO — NO UNA COPIA CON OTRO NOMBRE ▓
   `COMPONENTE_SEARCH.md` § «Gemelo vanilla de los dos tamaños (2026-07-29)».
   Instancia viva de referencia: `admin/js/backoffice/asistente-v2.js` › `cascaraPop()`.

   ⛔ QUÉ HABÍA AQUÍ ANTES, porque el error tiene nombre y conviene que se lea:
   este bloque declaraba `.exds-pl-busc` + `.exds-pl-busc-ic` + `.exds-pl-input` +
   `.exds-pl-busc-x`, es decir, **un buscador reinventado con el aspecto correcto**.
   Copiar el aspecto es fácil y sale bien; lo que se pierde al reinventar son las
   CONDUCTAS, que nadie recuerda que existían: el `type="text"` del canon (y con él la
   cruz nativa del navegador, que llegó DUPLICADA), el nombre accesible exacto
   («Limpiar búsqueda», sin el «la»), el tamaño de la caja de la × y el ícono a 15.
   *Un componente reinventado no pierde su aspecto —eso se copia—: pierde sus conductas.*

   Las cinco reglas de `.srch` van VERBATIM de `admin/css/backoffice.css` (líneas
   2616-2627). Se copian y no se enlazan porque esta hoja es AUTOCONTENIDA por diseño
   —igual que `.exds-pl-seg`, que es copia fiel de `.mc-seg`— y el original vive dentro
   de una hoja de aplicación de 200 KB que este patrón no puede cargar. Si algún día
   `.srch` se muda a una hoja compartida del design system, este bloque se BORRA y se
   enlaza: no se mantienen dos.
   ═══════════════════════════════════════════════════════════════════════════════ */
/* ═══════════════════════════════════════════════════════════════════════════════
   ⛔ `data-busca` ES OBLIGATORIO · UN BUSCADOR DECLARA SU TRABAJO O ES UN DEFECTO
   `DECISION-FUENTE: Ricardo 2026-08-16 — «C, e impón la regla de uso para que siempre
    se use, no sólo sea regla».`

   | trabajo               | qué busca                                 | conducta               |
   |-----------------------|-------------------------------------------|------------------------|
   | data-busca="lista"    | sólo lo que la lista YA contiene          | filtra filas + marca   |
   |                       |                                           | ⛔ NUNCA desplegable   |
   | data-busca="sistema"  | cosas que NO están en pantalla            | ABRE desplegable con   |
   |                       | (leads, contratos, personas)              | <mark> y teclado ↑↓↵esc|

   EL TEST: *¿lo que busco puede estar fuera de la pantalla?* Sí → `sistema`. No → `lista`.
   **No es estética: es de dónde salen los resultados.**

   Y NO SE DEDUCE DEL TAMAÑO (`is-sm` / default): el tamaño dice cuánto PESA el buscar en
   esa pantalla, el trabajo dice DE DÓNDE salen sus resultados. Medido el 2026-08-16: el
   buscador de contexto del asistente es tamaño `md` **y trabajo `sistema`**; el del
   historial es `sm` y `lista`. Hoy correlacionan; son ejes distintos.
   ═══════════════════════════════════════════════════════════════════════════════ */
.exds-pl .srch { display: flex; align-items: center; gap: var(--space-2, 8px); height: 40px;
  /* ⛔ RELLENO DERECHO = 8 MENOS EL BORDE (homologado a React el 2026-08-16). La ficha declara
     la × a `right: 8px` **del canto exterior del campo**, que es lo que mide el gemelo React
     (su borde lo lleva el input, y el `right:8` cuelga de la caja del input). Aquí el borde lo
     lleva el CONTENEDOR, así que el relleno tiene que descontarlo o la × queda a 9.
     Los 12 de antes eran residuo del flex: la × heredaba el relleno del contenedor y caía a
     12-13 px. El izquierdo SÍ es 12 limpio: ahí no hay nada que descontar, es la lupa.
     Medido tras el cambio: 8.00 px en los dos gemelos. */
  padding: 0 calc(var(--space-2, 8px) - 1px) 0 var(--space-3, 12px);
  background: var(--color-surface);
  border: 1px solid var(--color-border-default); border-radius: var(--radius-md);
  transition: border-color var(--motion-fast) var(--ease-out); }
/* HOVER · homologado a React el 2026-08-16: un control responde al ratón antes de pulsarlo.
   ⚠ El párrafo que iba aquí —«residuo declarado: hover y foco comparten valor porque no
   existe un token de paso»— se RETIRÓ el 2026-08-16: describía el estado anterior y el de
   abajo dice que ya se resolvió. *Dos comentarios seguidos contando versiones distintas de
   lo mismo no documentan: obligan a adivinar cuál es el vigente.*
   ⛔ EL BORDE EN REPOSO VA CLARO (`--color-border-default`) — corregido por Ricardo el
   2026-08-16. En reposo un buscador es un contenedor; sólo al tocarlo pesa como control. */
/* ⛔ 2026-08-16 · el peldaño de hover ya EXISTE (Ricardo: «sí» al token de paso).
   Antes hover y foco compartían valor y enfocar con el ratón no se notaba: el borde ya
   se había oscurecido al pasar por encima. Escalera regular: 3.12 → 3.87 → 4.79. */
.exds-pl .srch:hover { border-color: var(--color-border-control); }
.exds-pl .srch:focus-within { border-color: var(--color-border-focus); }
.exds-pl .srch > svg { width: 16px; height: 16px; color: var(--fg3); flex-shrink: 0; }
.exds-pl .srch input { flex: 1; min-width: 0; border: 0; outline: 0; background: none;
  font: 400 var(--fs-body) var(--font-sans); color: var(--fg1); }
.exds-pl .srch input::placeholder { color: var(--fg3); }

/* ⛔ LA CONDUCTA CUELGA DEL ATRIBUTO, no de la buena voluntad de quien monta.
   Un buscador de LISTA no tiene superficie de resultados propia: filtra lo que ya está.
   Si alguien le cuelga una, la hoja la apaga — la regla deja de depender de que se lea. */
.exds-pl .srch[data-busca="lista"] + [role="listbox"],
.exds-pl .srch[data-busca="lista"] ~ .srch-pop,
.exds-pl .srch[data-busca="lista"] [role="listbox"],
.exds-pl .srch[data-busca="lista"] .srch-pop { display: none; }

/* ⛔ Y UN BUSCADOR SIN TRABAJO DECLARADO NO SE PINTA COMO BUSCADOR: SE PINTA COMO DEFECTO.
   Es la diferencia entre una regla escrita y una que no se puede incumplir. El gate
   (`design-system/gate_buscador_trabajo.js`) lo caza al leer el archivo; esto lo caza a
   quien lo mira. *Un olvido que se ve no sobrevive a la primera captura de pantalla.*
   Si esta hoja se funde algún día con la de una app, ESTE selector se queda: un buscador
   sin trabajo declarado es un defecto en producción exactamente igual que aquí. */
.exds-pl .srch:not([data-busca="lista"]):not([data-busca="sistema"]) {
  outline: 2px dashed var(--color-error);
  outline-offset: 2px;
}

/* LA × DE LIMPIAR · anatomía de la ficha (§ Anatomía canónica): 24×24, `--radius-sm`,
   tinta `--fg3`, hover con fondo `--color-bg-subtle`, glifo a 15. La ficha declara que
   el gemelo vanilla estiliza sus hijos POR ETIQUETA (`.srch svg`, `.srch input`,
   `.srch button`) y sin nombrarlos: esto es ese `.srch button`, no una clase nueva.
   ⛔ Aparece SÓLO con texto, y el interruptor es el atributo `hidden` —igual que en la
   instancia viva del asistente—, no una clase: `hidden` ya significa «no está» para el
   lector de pantalla, y una clase sólo lo esconde a la vista. */
.exds-pl .srch button {
  flex: 0 0 auto;
  width: 24px; height: 24px;
  border: 0; background: transparent;
  color: var(--fg3);
  cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: var(--radius-sm);
  transition: background-color var(--motion-fast) var(--ease-out),
              color var(--motion-fast) var(--ease-out);
}
.exds-pl .srch button[hidden] { display: none; }
.exds-pl .srch button:hover { background: var(--color-bg-subtle); color: var(--fg1); }
.exds-pl .srch button:focus-visible { outline: 2px solid var(--color-border-focus); outline-offset: 1px; }
.exds-pl .srch button svg { width: 15px; height: 15px; display: block; }

/* EL ANCHO ES DE LA BARRA, NO DE LA PIEZA · mide `--w-buscador` (340 px) y ahí para, con
   el muelle a su derecha (Toolbar v0.3, Ricardo 2026-08-12). No se estira: su elasticidad
   era lo que hacía que su ancho cambiara en cada pantalla.

   ⛔ DECIDIDO, NO PENDIENTE (Ricardo, 2026-08-16): la captura de Cartera dibuja el buscador
   ocupando casi toda la banda y **gana el canon**. `«Lo que preserva siempre es el catálogo
   y el canon.»` Aquí el canon define un MECANISMO —que la barra se vea igual en las 18
   pantallas—, no un acabado, y una captura es la foto de un momento.

   Va en un selector DESCENDIENTE (`.exds-pl-bar .srch`) y no dentro de `.srch`, que es lo
   que mantiene la pieza intacta — exactamente como el backoffice hace
   `.dk-pop-top .srch { width: 100% }`. */
.exds-pl-bar .srch { flex: 0 0 auto; width: var(--w-buscador); max-width: 100%; min-width: 0; }

/* ⛔ EL CUARTO ESCALÓN DEL PLEGADO · el buscador encoge, y ESTA regla es todo su ancho.
   `DECISION-FUENTE: Ricardo 2026-08-18 — «2: si a tu recomendacion»` sobre «encoger el
    buscador» (`plans/decisiones/_decision_balance_contactos_2026-08-18.html`).

   Quién escribe `data-corto`: `componentes/toolbar/plegado.js`, y sólo cuando la barra sigue
   partida con las utilidades ya en el «⋯» y el texto de la acción principal ya cedido. **Es lo
   último que cede y lo primero que se recupera.** Nadie pone este atributo a mano.

   ⛔ Y EL PX VIVE AQUÍ, NO EN EL JS. La conducta decide CUÁNDO; la medida la decide el canon
   (`--w-buscador-corto`, 200). *Un ancho tecleado dentro de un mecanismo es un valor de diseño
   escondido en una conducta, y ahí no lo encuentra ni el gate ni quien lo busque.*
   La especificidad basta —(0,2,1) contra (0,2,0)— así que no hace falta `!important`: el
   escalón gana por ser más específico, no por gritar más. */
.exds-pl-bar .srch[data-corto] { width: var(--w-buscador-corto); }

/* ⛔ EL CINTURÓN: EL TACHE DEL NAVEGADOR, APAGADO — aunque ya no pueda salir.
   La causa de la doble cruz era `type="search"`: el navegador le mete DENTRO sus propios
   controles, y en Chromium `::-webkit-search-cancel-button` llega con
   `-webkit-appearance: auto`, así que la cruz del sistema se pintaba PEGADA a la nuestra.
   **El campo pasó a `type="text"`, que es lo que dice el canon, y con eso el defecto
   desaparece POR CONSTRUCCIÓN.** Este bloque se conserva igualmente: cuesta tres líneas y
   protege del día en que alguien vuelva a escribir `type="search"` sin saber por qué no
   debía. *Un arreglo que depende de que nadie toque un atributo no es un arreglo.* */
.exds-pl .srch input::-webkit-search-cancel-button,
.exds-pl .srch input::-webkit-search-decoration,
.exds-pl .srch input::-webkit-search-results-button,
.exds-pl .srch input::-webkit-search-results-decoration {
  -webkit-appearance: none;
  appearance: none;
  display: none;
}
.exds-pl .srch input::-ms-clear { display: none; width: 0; height: 0; }

/* EL MUELLE · el hueco sobrante empuja las acciones a la derecha, no estira el buscador. */
.exds-pl-spring { flex: 1 1 0; min-width: 0; }

/* ═══════════════════════════════════════════════════════════════════════════════
   ▓ 3 · EL SEGMENTADO — ⛔ RETIRADO DE ESTE PATRÓN (Ricardo, 2026-08-17) ▓

   `DECISION-FUENTE: Ricardo 2026-08-17 — confirma que el handoff retiró el segmentado y
    dejó sólo las vistas.` Se le enseñaron las dos versiones renderizadas en
   `plans/decisiones/_decision_barra_y_registro_2026-08-17.html`, decisión 2.

   POR QUÉ: la barra tenía DOS maneras de elegir a la vez —el segmentado de pista gris y
   las pastillas de vista (§3-ter)— y las dos contestan la misma pregunta con el dibujo
   invertido. *Dos controles que resuelven lo mismo en la misma barra no dan más opciones:
   dan una duda por cada uso.* Queda el selector de vistas, que es el que pedía la captura.

   ⛔ SE RETIRA DEL PATRÓN, **NO DEL SISTEMA**, y la diferencia importa: `.mc-seg` está VIVO
   en producción —15 usos medidos en el CSS del backoffice, fuera de pantallas de lista— y
   la `Toolbar` conserva su ranura `view`. Por eso **no entra a `patrones_retirados.json`**:
   esa lista es de formas que el canon prohíbe en TODAS partes, y su propia cabecera exige
   no ampliarla por sospecha. *Retirar de más cuesta igual que retirar de menos: rompe
   pantallas que nadie miró.*

   Aquí vivían `.exds-pl-seg`, `-knob`, `-btn` y `-n`. Se dejan NOMBRADAS y no borradas en
   silencio: quien busque por qué desapareció el segmentado tiene que encontrar el motivo
   donde estaba el código, no en un commit.
   ═══════════════════════════════════════════════════════════════════════════════ */

/* ═══════════════════════════════════════════════════════════════════════════════
   ▓ 3-ter · EL SELECTOR DE VISTAS — ⛔ NO ES EL SEGMENTADO DE ARRIBA ▓
   (2026-08-16 · captura del módulo Cartera/contratos)

   ⛔ SON DOS PIEZAS DISTINTAS Y LA CONFUSIÓN ES FÁCIL, porque las dos son «una fila de
   opciones con contador». Se distinguen por el dibujo, y el dibujo va INVERTIDO:

   |                   | pista | el ACTIVO           | los demás              |
   |-------------------|-------|---------------------|------------------------|
   | **Segmentado** §3 | gris (`--color-bg-subtle`), con borde | **BLANCO**, lo pinta un knob que se desliza | transparentes, sin borde |
   | **Selector de vistas** (esto) | **NINGUNA** — las pastillas van sueltas sobre el lienzo | **OSCURO** (`--color-action-ink`), tinta invertida | **BLANCOS con borde** |

   *Un segmentado sin pista no es un segmentado: es otra pieza.* Y al revés — pintar el
   activo de este selector en blanco lo dejaría indistinguible de los inactivos, porque
   aquí los inactivos YA son blancos. Los dos dibujos son coherentes cada uno consigo
   mismo y **no se pueden mezclar**.

   ⛔ CUÁNDO SE USA CADA UNO: **PENDIENTE DE RICARDO** — es una decisión de diseño (regla
   0-bis: «qué componente se usa para un caso» es suya). Lo que SÍ está medido y escrito
   es lo de arriba: cómo se ve cada uno. La hipótesis que se le lleva —y que NO se aplica
   hasta que la confirme— está en el README › «La frontera». **Mientras tanto conviven las
   dos piezas y ninguna sustituye a la otra.**

   NO SE INVENTÓ NINGÚN VALOR: el oscuro es el mismo `--color-action-ink` de la acción
   principal, y el blanco-con-borde es el mismo acabado del secundario (§3-bis). Lo único
   propio de esta pieza es la FORMA: pastilla (`--radius-pill`) en vez de radio de botón.
   ═══════════════════════════════════════════════════════════════════════════════ */

/* La etiqueta «VISTAS» · micro-tipografía de índice, igual que la cabecera de la tabla:
   dice qué son las pastillas de al lado y no compite con ellas. */
/* ⛔ EL RÓTULO «VISTAS» SE RETIRÓ (Ricardo, 2026-08-17): «quita el eyebrow "vistas" que
   está en esa fila». Con el recibo de filtros en la misma fila, una etiqueta que nombra a
   la mitad de sus vecinas etiqueta mal. *Un rótulo que sólo cubre parte de la fila no
   ordena: confunde sobre dónde acaba lo que nombra.*
.exds-pl-vistas-lbl { RETIRADA · no reintroducir sin decisión suya } */

/* EL SEPARADOR VERTICAL · lo que hace legible que la pastilla suelta de la izquierda
   NO es una vista. Es un hairline, no un borde: separa, no encierra. */
.exds-pl-bar-sep {
  width: 1px;
  align-self: stretch;
  min-height: 20px;
  background: var(--color-border-subtle);
  flex: none;
}

/* ⛔ DESDE EL 2026-08-19 ESTA CAJA ENVUELVE UN DESPLEGABLE, NO TRES PASTILLAS
   (Ricardo · opción B). Ya no necesita `flex-wrap`: envolver era justamente el defecto —
   con la cuarta opción las pastillas se iban a un segundo renglón y la barra crecía de
   alto sola. *Un contenedor que puede envolver esconde que su contenido no cabe: se
   acomoda en silencio y nadie mide el problema.* */
.exds-pl-vistas {
  display: inline-flex;
  align-items: center;
  flex: none;
  /* ⛔ SIN FONDO, SIN BORDE Y SIN RELLENO. Si esta caja pintara algo, volvería a ser una
     pista — y entonces sería el segmentado de §3 con otros colores. */
}

/* LA CIFRA DE LA VISTA PUESTA, sobre el disparador. Va en el gris de apoyo y NO en una
   pastilla: es un dato del control, no un estado propio. Tabular para que el ancho del
   disparador no baile al cambiar de vista — si bailara, la barra entera se recolocaría
   con cada elección. */
.exds-pl-vistas-n {
  /* ⛔ SIN margin-left PROPIO (Ricardo, 2026-08-28): el `gap: var(--space-2)` de
     `.exds-pl-btn2` ya separa a los tres hermanos (etiqueta · cifra · chevrón) por
     igual. Un margen aquí encima del gap sumaba 8+6=14px medidos donde el CSS
     declaraba 8 — un número que nadie decidió. Homologado al catálogo, que ya lo
     tenía retirado (`design-system/patrones/pantalla-lista/pantalla-lista.css:651`). */
  color: var(--fg3);
  font-variant-numeric: tabular-nums;
}

/* El chevrón hereda el gris de apoyo, igual que en el kit: es el afford de «esto abre»,
   no un icono de categoría — si fuera a la tinta principal competiría con la etiqueta. */
.exds-pl-vistas [data-pl-vista-chev] {
  /* ⛔ SIN margin-left PROPIO — misma razón que `.exds-pl-vistas-n` arriba: sumaba
     8+4=12px donde el `gap` del disparador ya declara 8. */
  flex: none;
  width: 15px;
  height: 15px;
  color: var(--fg3);
}

/* ⛔ EL ESTADO ACTIVO DE UNA OPCIÓN DEL DESPLEGABLE, que NO existía en ninguna hoja.
   El kit lo señala con `role="menuitemradio"` + `aria-checked` —semántica, sin clase— y
   eso lo lee un lector de pantalla… pero no lo pinta nadie. *Un estado que sólo existe
   en el árbol de accesibilidad es invisible para quien mira, que son casi todos.*
   Se pinta desde el atributo, no desde una clase, para que la señal siga siendo UNA. */
/* ⛔ 2026-08-27 · RETIRADO — este patrón no redefine «lo elegido»: lo hereda del Menú.
   `DECISION-FUENTE: Ricardo, 2026-08-27 — decisión 4.1: gana el dibujo del Menú.`
   No era una desviación de la app: la app copiaba bien a su gemelo, y eran los DOS GEMELOS del
   catálogo los que se contradecían. Ganaba `menu.css` por el orden de carga (`index.html:84`
   antes que `:85`), o sea por accidente. Estas dos reglas no pintaban nada en ninguna
   superficie viva: cero píxeles de cambio. */

/* LA PASTILLA · una sola declaración para la vista y para el filtro suelto («Por aplicar»),
   porque son el mismo control con distinta conducta: una elige de un grupo (exclusiva) y la
   otra se enciende y se apaga sola. *Un componente que sólo se distingue de otro por su
   conducta no necesita un segundo dibujo* (`GOBIERNO.md` §2-bis). */
.exds-pl-vpill {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  flex: none;
  height: 32px;
  padding: 0 var(--space-3-5);
  border: 1px solid var(--color-border-default);
  border-radius: var(--radius-pill);
  background: var(--color-surface);
  color: var(--fg1);
  box-shadow: var(--shadow-none);   /* ⛔ NINGUNA · misma razón que el secundario (§3-bis) */
  font: var(--fw-medium) var(--fs-button-sm) var(--font-sans);
  cursor: pointer;
  white-space: nowrap;
  transition: background var(--motion-fast) var(--ease-default),
              border-color var(--motion-fast) var(--ease-default),
              color var(--motion-fast) var(--ease-default);
}
.exds-pl-vpill:hover { background: var(--color-bg-subtle); }
.exds-pl-vpill:focus-visible { outline: 2px solid var(--color-border-focus); outline-offset: 2px; }

/* ⛔ LA ACTIVA VA OSCURA — y el borde se pone del color del relleno, no se quita: quitarlo
   haría que la pastilla ENCOGIERA 2 px al activarse y las demás se moverían de sitio. */
.exds-pl-vpill[aria-pressed="true"] {
  background: var(--color-action-ink);
  border-color: var(--color-action-ink);
  color: var(--color-action-text-on);
}
.exds-pl-vpill[aria-pressed="true"]:hover { background: var(--color-action-ink-hover); border-color: var(--color-action-ink-hover); }

/* EL CONTADOR · sólo texto, como en el segmentado. Sin pastilla dentro de la pastilla. */
.exds-pl-vpill-n {
  font-variant-numeric: tabular-nums;
  color: var(--fg3);
}
.exds-pl-vpill[aria-pressed="true"] .exds-pl-vpill-n { color: var(--color-ink-on-dark-hint); }

/* ⛔ MISMO DEFECTO, MISMA CURA — y aquí estaba VIVO en el backoffice, no en un catálogo.
   `--color-ink-on-dark-hint` mide 4,83:1 sobre el relleno ink en reposo (pasa AA), pero el
   hover aclara el fondo a #31363B y ahí cae a 3,91:1 (reprueba). Lo destapó el 2026-08-20 al
   dar hover al chip de filtro, su pieza hermana: se midió el chip, y el número obligó a mirar
   aquí. Se sube un peldaño a `--color-ink-on-dark-muted` (6,47:1), sin estrenar ningún valor.
   *Un defecto encontrado en una pieza se busca en sus hermanas antes de darlo por arreglado —
   si la causa es compartida, arreglar sólo una es dejarlo vivo y creer que no.* */
.exds-pl-vpill[aria-pressed="true"]:hover .exds-pl-vpill-n { color: var(--color-ink-on-dark-muted); }


/* ═══════════════════════════════════════════════════════════════════════════════
   ▓ 3-bis · EL BOTÓN SECUNDARIO — 0..n, entre el segmentado y la acción principal ▓

   `DECISION-FUENTE: Ricardo 2026-08-16 — «además del botón de acción, también deben de
   haber botones secundarios, que en este caso el catálogo los contempla de color
   blanco».`

   ⛔ ES GHOST CON BORDE, Y SE DEFINE POR SU BORDE (`COMPONENTE_BOTON.md` § Variantes):
   fondo `--color-surface`, borde `--color-border-default`, tinta `--fg1`. El borde es
   levísimo a propósito (1,47:1, revertido por Ricardo el 2026-07-18 a sabiendas): lo que
   identifica al botón es su TEXTO en ink (18,51:1), no su filo.

   ⛔ Y NO LLEVA SOMBRA. NINGUNA — está DESACOPLADO de `--relieve-control` a propósito
   (corrección 2026-07-22, que Ricardo cazó en Claude Design). Una sombra bajo un botón
   BLANCO se percibe más fuerte que bajo uno oscuro —el oscuro absorbe la suya—, así que
   ponérsela haría que el secundario se viera **más elevado que el primario** e
   invertiría la jerarquía: es el defecto del no-negociable #7-bis, y ya ocurrió de
   verdad en mi-exante. *El relieve es de los botones que MANDAN: relleno sólido = esta
   acción manda = relieve. El contorno es plano.*

   ⛔ EL ORDEN DE LA BARRA ES FIJO Y NO SE NEGOCIA:
       buscador → muelle → segmentado → **SECUNDARIOS** → acción principal → «⋯»
   **La acción principal es la ÚLTIMA antes del «⋯»**: ésa es la posición de mayor peso y
   no la comparte. *Un secundario a su derecha le roba el final de la lectura, que es lo
   único que la posición aporta.*

   ⛔ NUNCA DOS PRIMARIOS COMPITIENDO: por muchos secundarios que haya, el ink es UNO.
   Un segundo `.exds-pl-accion` en la misma barra es un defecto, no una variante.

   ES LA MISMA PIEZA que la salida de los tres estados con mensaje (Reintentar · Limpiar
   filtros): **UNA declaración para las dos, no una copia** — como `.btn-primary,
   .btn-dark` en el canon. *Un componente que sólo se distingue de otro por su acabado no
   es un componente: es herencia* (`GOBIERNO.md` §2-bis).
   ═══════════════════════════════════════════════════════════════════════════════ */
.exds-pl-btn2,
.exds-pl-estado-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  flex: none;
  height: 40px;                        /* el mismo escalón `md` que el primario */
  padding: 0 var(--space-4);
  border: 1px solid var(--color-border-default);
  border-radius: var(--radius-button);
  background: var(--color-surface);
  color: var(--fg1);
  box-shadow: var(--shadow-none);      /* ⛔ NINGUNA · NO es `--relieve-control` (ver arriba) */
  font: var(--fw-medium) var(--fs-button) var(--font-sans);
  cursor: pointer;
  white-space: nowrap;
  transition: background var(--motion-fast) var(--ease-default),
              transform  var(--motion-fast) var(--ease-default);
}
.exds-pl-btn2:hover,
.exds-pl-estado-btn:hover  { background: var(--color-bg-subtle); }
.exds-pl-btn2:active,
.exds-pl-estado-btn:active { background: var(--color-bg-subtle); transform: scale(.97); }
.exds-pl-btn2:focus-visible,
.exds-pl-estado-btn:focus-visible { outline: 2px solid var(--color-border-focus); outline-offset: 2px; }
.exds-pl-btn2 svg,
.exds-pl-estado-btn svg { width: 16px; height: 16px; display: block; flex-shrink: 0; }

/* ═══════════════════════════════════════════════════════════════════════════════
   EL GLIFO QUE GIRA · «Actualizar» acusa recibo girando SU PROPIO ícono
   (`COMPONENTE_BOTON` § refrescando · `Button.jsx` › `.exds-btn-refrescando
   .exds-btn-refresh-ic`) — nunca un anillo de espera al lado del rótulo.

   ⛔ POR QUÉ ESTA REGLA EXISTE, que es lo que hay que leer antes de tocarla: hasta el
   2026-08-21 la pantalla de Facturación pedía este giro con `class="mc-refresh-ic"`, la
   clase de **Mesa de control**, y la animación `mcSpin` de `backoffice.css`. Era
   vocabulario de otra pantalla dentro de una ya homologada, y su único motivo era que **el
   patrón no tenía dónde pedir un giro**. El remedio no era renombrarla al prefijo del
   módulo —eso deja el mismo movimiento escrito dos veces en la misma app— sino que el
   patrón lo declare una vez, aquí. *Una clase prestada no se cura cambiándole el nombre:
   se cura dándole casa al comportamiento que la hacía falta.*

   ⛔ `.7s` NO SE ELIGE AQUÍ: es el valor que `Button.jsx` ya usa para el glifo de refrescar
   (`exds-btn-spin .7s linear`). El catálogo no tiene un token de duración de 700 ms —los
   tres son 120/250/400— y el que existiera se quedaría corto para una vuelta completa, que
   a 400 ms se lee como un tirón.

   ⚠ Y LA DIFERENCIA CON EL KIT, DECLARADA PORQUE ES REAL: `Button.jsx` gira **`infinite`**
   («sigo esperando») y esto gira **UNA vuelta** («te oí»). Son dos mensajes distintos, los
   dos legítimos, y **cuál le toca al botón de recargar de una lista es una decisión de
   diseño y es de Ricardo** (regla 0-bis). Se conserva lo que esta pantalla ya hacía —una
   vuelta— para que la cosecha no cambie de paso una conducta que nadie pidió cambiar.
   Disparador: el día que él elija, se cambia esta línea y las dos coinciden.

   ⛔ EL RE-DISPARO ES DEL JS, y aquí se explica por qué la regla es como es: una animación
   de una sola pasada no se vuelve a lanzar poniendo la marca dos veces. Quien monta hace
   «quitar → forzar cálculo → poner» (y en un `<svg>` el cálculo se fuerza con
   `getBoundingClientRect()`, porque `SVGSVGElement` no tiene `offsetWidth`).
   ═══════════════════════════════════════════════════════════════════════════════ */
.exds-pl-btn2-ic { transform-origin: 50% 50%; }
.exds-pl-btn2-ic[data-girando] { animation: exds-pl-giro .7s linear; }  /* LINT-ALLOW: .7s · una VUELTA COMPLETA no cabe en la escala de duraciones: los tres tokens (120/250/400) miden transiciones de estado, no un giro de 360°, y a 400 ms el glifo se lee como un tirón. El valor sale del kit — `Button.jsx` › `exds-btn-spin .7s linear` para este mismo glifo—, así que NO es un número elegido aquí: es el del catálogo, que todavía no tiene nombre. Se retira el día que la escala declare una duración de gesto largo. */
@keyframes exds-pl-giro { to { transform: rotate(360deg); } }

/* SÓLO ÍCONO — el «⋯» de las utilidades, que en el canon de la Toolbar es un secundario
   `iconOnly`. Cuadrado del MISMO alto que los demás: un control más bajo en la misma
   barra rompe la línea de base y se lee como otro nivel de acción, que no lo es. */
.exds-pl-btn2[data-icono] { width: 40px; padding: 0; }

/* LA ACCIÓN PRINCIPAL de la barra · botón primario del canon (alto 40 = escalón `md`).
   ⛔ VA LA ÚLTIMA ANTES DEL «⋯». Si te ves montando un secundario a su derecha, has
   deshecho la única cosa que la posición comunica.

   ⛔ Y COMPARTE DECLARACIÓN CON LA ACCIÓN DE FILA (`.exds-pl-fila-accion`, §4-bis), que es
   el mismo botón primario un escalón más pequeño. **Una definición, no una copia**: es la
   misma lección que dejó el botón de los estados, que al escribirse dos veces divergió y
   acabó pidiendo prestada una sombra que el canon le prohíbe. Lo único que cambia entre
   los dos es el TAMAÑO, y eso se escribe abajo en tres líneas. */
.exds-pl-accion,
.exds-pl-fila-accion {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  flex: none;
  height: 40px;
  padding: 0 var(--space-4);
  border: 0;
  border-radius: var(--radius-button);
  background: var(--color-action-ink);
  color: var(--color-action-text-on);
  box-shadow: var(--relieve-btn);
  font: var(--fw-medium) var(--fs-button) var(--font-sans);
  cursor: pointer;
  white-space: nowrap;
  transition: background var(--motion-fast) var(--ease-default),
              transform  var(--motion-fast) var(--ease-default);
}
.exds-pl-accion:hover,
.exds-pl-fila-accion:hover  { background: var(--color-action-ink-hover); }
.exds-pl-accion:active,
.exds-pl-fila-accion:active { background: var(--color-action-ink-press); transform: scale(.97); }
.exds-pl-accion:focus-visible,
.exds-pl-fila-accion:focus-visible { outline: 2px solid var(--color-border-focus); outline-offset: 2px; }
.exds-pl-accion svg,
.exds-pl-fila-accion svg { width: 16px; height: 16px; display: block; flex-shrink: 0; }

/* ⛔ EL CONTADOR DE LA ACCIÓN · ES EL CONTADOR DEL CATÁLOGO, MONTADO — no un dibujo nuevo.
   `DECISION-FUENTE: Ricardo 2026-08-16 — «ya está el componente en el catálogo designado,
    es cajita pero circular».`

   La pieza es el **Contador (badge numérico)** de `COMPONENTE_BADGE.md` § Contador, con su
   forma «stadium» decretada el 2026-07-13. Sus cuatro medidas son las del canon, copiadas
   de la ficha y no elegidas aquí:
       · forma   `border-radius: 999px` (`--radius-pill`) — ⛔ **NUNCA 50%**: con 2+ dígitos
                 la caja se hace más ancha que alta y el 50% la deforma en ELIPSE.
       · ancho   `min-width` = el alto → 1 dígito sale CÍRCULO PERFECTO, 2+ sale píldora.
       · relleno `0 6px` lateral, para que respire con 2–3 dígitos.
       · cifras   tabulares, y **tope «99+»**, que se aplica en el render.

   ✅ EL HUECO DEL CATÁLOGO QUEDÓ CERRADO (Ricardo, 2026-08-16). Los tres énfasis del
   Contador —`ambiente` (gris suave), `respuesta` (oscuro sólido) y `atencion` (verde)—
   están pensados para FONDO CLARO, y aquí el contador vive DENTRO del botón oscuro:
   `ambiente` desaparecería y `respuesta` sería oscuro sobre oscuro. La solución provisional
   fue montar la pareja INVERSA del botón (pastilla blanca, número ink) y dejar la variante
   sobre oscuro como decisión suya. **Ya la tomó: gris oscuro.** El detalle, con sus
   medidas, va junto a la declaración de abajo. Sigue sin estrenarse un solo color. */
.exds-pl-cnt {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 20px;                 /* = el alto → 1 dígito círculo, 2+ píldora */
  height: 20px;
  padding: 0 6px;
  border-radius: var(--radius-pill);   /* stadium · NUNCA 50% */
  /* ⛔ DECIDIDO Y CERRADO: PASTILLA BLANCA CON NÚMERO INK.
     Es la variante canónica **`sobre-oscuro`** del Contador (`COMPONENTE_BADGE.md`
     § Contador · kit: `Badge variant="count" enfasis="sobre-oscuro"`) — aquí sólo se monta.
     `DECISION-FUENTE: Ricardo 2026-08-17 — «vamos a regresar al contador blanco original».`

     ⛔ ESTO NO ES «LO QUE HABÍA»: es lo que Ricardo ELIGIÓ, y la diferencia importa.
     Antes era provisional —un hueco declarado del canon, sin variante para fondo oscuro—.
     El 16-ago pidió gris oscuro, se construyó y se le enseñó pintado con el CSS real
     (`plans/decisiones/_decision_cosecha_filtros_2026-08-17.html`); **viéndolo, volvió al
     blanco**. Ahora el blanco es canon, no inercia.

     *Y ésa es la factura que paga la hoja de decisión: el gris era defendible sobre el
     papel —y sus números mejores: 1,24:1 la pastilla frente a 15,09:1 del blanco— y aun así
     se ve peor. Un número no decide un diseño; sólo evita decidirlo a ciegas.*

     Medido en el navegador: pastilla **15,09:1** contra el botón · número **15,09:1** sobre
     la pastilla. Los dos tokens ya eran canónicos y ya estaban emparejados con ink, así que
     esta variante **no estrena un solo color** — que era la condición para cosecharla. */
  background: var(--color-surface);
  color: var(--color-action-ink);
  font: var(--fw-semibold) var(--fs-eyebrow) var(--font-sans);
  line-height: 1;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  flex-shrink: 0;
}

/* ⛔ EL CONTADOR EN OTRO ÉNFASIS — `ambiente` (2026-08-21).
   `COMPONENTE_BADGE.md` declara TRES énfasis para fondo claro —ambiente · respuesta ·
   atención— más `sobre-oscuro`, y la pregunta que los ordena es sobre el DATO, no sobre el
   sitio: **«¿el usuario tiene que hacer algo con este número?»**. La regla de arriba pinta
   `sobre-oscuro`, que es el caso del selector de vistas (pastilla dentro de un botón ink).

   `ambiente` = *cuántos hay, no pide nada*. Es el más común —pestañas, módulos, y la solapa
   de una sección de acordeón— y hasta hoy **no existía en vanilla**: cada pantalla que lo
   necesitaba lo dibujaba a mano, que es como el canon llegó a tener cuatro tratamientos sin
   que nadie los decidiera.

   El aro interior de medio píxel NO es un adorno: es lo que lo separa del fondo cuando el
   contador cae sobre una superficie ya teñida (hover, fila activa). Los dos valores salen
   del kit tal cual — es el único sitio del sistema donde esta pareja se escribe. */
.exds-pl-cnt[data-enfasis="ambiente"] {
  background: rgba(15, 20, 25, .06);
  box-shadow: inset 0 0 0 .5px rgba(15, 20, 25, .08);
  color: var(--fg2);
}

/* ═══════════════════════════════════════════════════════════════════════════════
   ▓ 4-bis · LA ACCIÓN DE FILA — condicional, y por eso hay que decir cuándo NO está ▓
   (2026-08-16 · captura de Cartera: unas filas llevan «Aplicar» y otras no)

   ⛔ ES EL MISMO PRIMARIO, UN ESCALÓN MÁS PEQUEÑO (32 en vez de 40). No es un tercer
   nivel de botón: es el nivel «esta acción manda» dentro del renglón. Por eso hereda todo
   —color, relieve, curva, foco— y sólo se le cambia la talla.

   ⛔ LA CONDICIONALIDAD ES DEL NEGOCIO, NO DE LA HOJA. Aquí no hay ninguna regla que
   decida en qué filas sale: quien monta pinta el botón donde toca y **deja la celda vacía
   donde no**. La celda existe siempre —es una columna de la rejilla—, así que las filas
   que no lo llevan no se descuadran. *Un botón que aparece y desaparece moviendo lo demás
   de sitio hace que el ojo lo persiga en vez de leerlo.*

   ⛔ Y SU CLIC NO ABRE LA FILA. Lo garantiza el JS: la rama de `data-pl-accion` va ANTES
   que la de la fila y sale con `return`. Sin eso, pulsar «Aplicar» abriría además el
   detalle — dos cosas por un clic, y la segunda tapando a la primera.
   ═══════════════════════════════════════════════════════════════════════════════ */
.exds-pl-fila-accion {
  height: 32px;
  padding: 0 var(--space-3-5);
  font-size: var(--fs-button-sm);
}

/* ═══════════════════════════════════════════════════════════════════════════════
   ▓ 4 · LA TARJETA — cabecera, cuerpo y pie ▓
   La tabla vive DENTRO de una tarjeta; la barra, sobre el lienzo. Que una tenga marco y
   la otra no NO cambia la regla 1: lo que se alinea son sus bordes izquierdo y derecho.
   ═══════════════════════════════════════════════════════════════════════════════ */
.exds-pl-card {
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-container);
  background: var(--color-surface);
  overflow: hidden;

  /* ⛔ 2026-08-20 · LA TARJETA REPARTE EL ALTO. Ver el bloque 4-bis, justo debajo. */
  display: flex;
  flex-direction: column;
  min-height: 0;
}

/* ═══════════════════════════════════════════════════════════════════════════════
   ▓ 4-quater · EL ALTO Y EL DESPLAZAMIENTO — LA LISTA SE MUEVE POR DENTRO ▓ (2026-08-20)

   `DECISION-FUENTE: Ricardo 2026-08-20 — «el usuario puede scrollear verticalmente sobre`
   `la pantalla que contiene este patrón. Esto no debería de poderse hacer. Lo único`
   `escroleable verticalmente es la tabla, y todo el patrón completo debe de caber en la`
   `pantalla».`
   Antecedente: `Ricardo 2026-08-12 — «debe haber una lista que quede ajustada de acuerdo a`
   `tamaño de pantalla, y como es scroll infinito el número es variable»`.

   ⛔ ESTO ENTRA AL CATÁLOGO **COSECHADO DE LA APP**, y esa dirección es la anómala. El
   backoffice lo construyó por su cuenta el 19-ago —`.exds-pl-scroll` y la cabecera fija— y
   durante un día vivió en producción sin existir en el catálogo: quien montara una pantalla
   nueva DESDE el catálogo heredaba una lista que crece hacia abajo. *El canon debe ir por
   delante de las apps, no ir a recogerlas después; cuando pasa al revés, se recoge —que es
   esto— y se deja escrito que pasó.*

   ── LA REGLA, EN UNA FRASE ───────────────────────────────────────────────────────
   **EL PATRÓN OCUPA EL ALTO QUE LE DA SU ANFITRIÓN Y NO GENERA DESPLAZAMIENTO DE PÁGINA.**
   Lo único que se mueve en vertical es `.exds-pl-scroll`. Barra, cabecera y pie se quedan:
   son los tres referentes que dicen dónde estás, y un referente que se va deja de serlo.

   ── EL CONTRATO, en dos mitades ──────────────────────────────────────────────────
   **El ANFITRIÓN da el alto.** La vista que hospeda la lista tiene que ser una columna con
   alto real (`display:flex; flex-direction:column; height:100%`), **y toda la cadena de
   envoltorios hasta `.exds-pl` también**: uno solo que no sea columna corta el reparto y no
   avisa — la lista simplemente vuelve a crecer hacia abajo. El patrón NO se inventa el alto
   con un `calc(100vh - …)`: ese calc lleva dentro el alto de la barra superior, el del
   relleno y el de cualquier aviso que aparezca encima — **tres números que envejecen por
   separado**. Con flexbox, una franja que aparece resta su propio alto sola.

   **El PATRÓN lo reparte.** Barra, cabecera y pie rígidos; el cuerpo se queda con lo que
   sobre y es el único que se mueve.

   ⛔ **Y LA TARJETA NO CRECE MÁS QUE SU CONTENIDO** (Ricardo, 2026-08-19):
   `DECISION-FUENTE: «cuando el contenido de la tabla es menor o igual al tamaño de la`
   `tabla standard, la tabla se ajusta al tamaño del contenido para evitar este error de ui».`
   El anfitrión da el alto DISPONIBLE, que es un techo — no una cuota que haya que gastar.
   Se cumple con el `flex: 0 1 auto` por defecto de la tarjeta (crece 0, encoge 1, base el
   contenido), y por eso **no se declara**: escribirlo invita a «arreglarlo» a `1 1 auto`.
   *Este contrato nació resolviendo la lista LARGA y por eso su primera versión estiraba
   siempre: con tres filas dejaba 567 px de rectángulo vacío enmarcado en Mesa de control.*

   ── POR QUÉ LA CABECERA VA DENTRO DEL DESPLAZAMIENTO Y NO FUERA ──────────────────
   Fuera quedaría fija sin esfuerzo, pero **no acompañaría el desplazamiento LATERAL**: en
   una tabla que no cabe a lo ancho, los rótulos se quedarían quietos mientras las columnas
   se van, que es peor que no tener cabecera. Dentro, y `sticky`, se queda fija en vertical
   y viaja en horizontal.

   ⛔ Y AQUÍ ESTÁ LA TRAMPA QUE HAY QUE NOMBRAR, PORQUE YA MORDIÓ (censo del 2026-08-19):
   **`position: sticky` no se pega a la ventana: se pega a su ancestro con desplazamiento
   más cercano.** Y `overflow-x: auto` YA CREA uno —el eje Y computa a `auto` aunque no se
   escriba—, así que una cabecera `sticky` dentro de una caja con `overflow-x: auto` y sin
   alto acotado **no se pega a nada y no avisa**. La vista de Tabla de Operación llevaba su
   `thead { position: sticky; top: 0 }` escrito desde hacía tiempo y **no hacía nada** por eso
   exacto: al desplazar, la cabecera se iba 600 px y desaparecía. *Un `sticky` mal anclado no
   da error, no se ve en el CSS y sale verde en toda revisión que se haga leyendo: sólo
   aparece si alguien desplaza.*

   **Corolario, y es la regla que impide repetirlo:** quien escriba `position: sticky` en una
   cabecera tiene que declarar EN EL MISMO SITIO cuál es el contenedor con alto acotado que
   la sostiene. Sin eso no es una cabecera fija: es una intención.

   ⚠ ESTE BLOQUE SE LLAMABA §4-bis, Y ESE NÚMERO YA LO TENÍA «LA ACCIÓN DE FILA», más
   arriba (2026-08-20). Se renumera a **4-quater** —4-ter también estaba ocupado, por el
   vocabulario de celda—. *Un número de sección repetido no rompe nada y por eso sobrevive:
   sólo estorba el día que alguien sigue una referencia y aterriza en el bloque de al lado.*
   ═══════════════════════════════════════════════════════════════════════════════ */

/* La envoltura ocupa el alto que le dé el anfitrión y lo pasa hacia dentro. `min-height:0`
   está en los tres niveles a propósito: sin él, un hijo flex se niega a encoger por debajo
   de su contenido y el desplazamiento se va al ancestro de más arriba — el fallo silencioso
   clásico de este reparto. */
.exds-pl {
  display: flex;
  flex-direction: column;
  min-height: 0;
}

/* EL CUERPO CON DESPLAZAMIENTO · contiene la cabecera Y las filas, en ese orden.
   Los dos ejes en la misma caja: el vertical lo pide la lista larga, el lateral lo pide
   una tabla que no cabe a lo ancho. */
.exds-pl-scroll {
  flex: 1 1 auto;
  min-height: 0;
  overflow: auto;
  overscroll-behavior: contain;   /* al llegar al fondo no arrastra la página de detrás */
}

/* Barra, cabecera y pie NO se encogen: son los tres puntos de referencia que dicen dónde
   estás, y un referente que se comprime deja de serlo.
   ⛔ EL SELECTOR DE LA BARRA CUELGA DE `.exds-pl`, NO DE LA TARJETA (2026-08-20). Cuando la
   barra vivía dentro, este selector decía `.exds-pl > .exds-pl-card > .exds-pl-bar`; al
   sacarla, ese selector deja de casar y la barra **recupera en silencio el `flex: 0 1 auto`
   por defecto** — o sea, puede comprimirse. *Un selector que deja de casar no da error ni
   pinta distinto: devuelve el valor por defecto, que es justo lo que nadie va a mirar.* */
.exds-pl > .exds-pl-bar,
.exds-pl-foot { flex: 0 0 auto; }

/* ── LA CABECERA ─────────────────────────────────────────────────────────────────
   Micro-tipografía: 11 px, 600, versalitas. Es un índice para escanear, no un título. */
.exds-pl-head {
  display: grid;
  grid-template-columns: var(--exds-pl-cols);
  /* ⛔ VUELVE AL GRIS CLARO (Ricardo, 2026-08-20). REVIERTE el hundido del 19-ago.
     `DECISION-FUENTE: Ricardo 2026-08-20 — «regresamos al gris de relleno que el encabezado de`
     `la tabla tenía anteriormente (era más claro). Y a partir de ahí, decide tú el color del`
     `texto, chevron y (?). Debe ser legible pero no cuasi-negro».`

     **Lo del 19-ago no estaba mal razonado: estaba razonado sobre OTRA pantalla.** Aquel día
     `bg-subtle` daba 1,028:1 contra el lienzo y el BLANCO de las filas destacaba más (1,073)
     que el propio cromo, así que se bajó a `bg-sunken` (1,145). Pero ese mismo día —y otra vez
     el 20— la barra salió de la tarjeta: **el encabezado ya no comparte superficie con nada
     con lo que compita**, y la tarjeta tiene su propio borde, que es quien la separa del
     lienzo. *Un valor elegido por una relación deja de estar justificado cuando esa relación
     se rompe — y no avisa: sigue pintando.*

     ⛔ Y LA CONSECUENCIA QUE SE RESUELVE EN EL MISMO CAMBIO, o el defecto vuelve: el HOVER DE
     FILA era `bg-subtle`, o sea el fondo nuevo de esta cabecera. Sin tocarlo, pasar el ratón
     por una fila la pintaba del color EXACTO del encabezado — el defecto que el cambio del
     19-ago había cerrado de paso. El hover baja a `bg-sunken` (ver `.exds-pl-row:hover`), un
     escalón por debajo de aquí. *Así los dos grises nunca significan lo mismo: el claro es
     cromo, el hundido es «esta fila está bajo el puntero».* */
  background: var(--color-bg-subtle);
  border-bottom: 1px solid var(--color-border-subtle);
}

/* SE QUEDA FIJA AL BAJAR · `COMPONENTE_TABLA.md` lo pedía («`thead` sticky») desde antes
   de que existiera este patrón, y no se cumplía en ninguna lista del backoffice.
   ⛔ SU CONTENEDOR ES `.exds-pl-scroll`, y se declara aquí porque **un `sticky` sin su
   contenedor nombrado es la mitad de una regla** (§4-quater). `z-index` sobre las filas, que
   se le meten por debajo.

   ⛔ RETIRADA DECLARADA (2026-08-21) · aquí colgaba también `.exds-pl-scroll > .mc-listhead`.
   Existía por UN solo consumidor: Facturación, que montaba el marco del patrón con la
   cabecera de Mesa de control todavía dentro. **Ese día llegó**: el cuerpo de su tabla se
   homologó y su cabecera es `.exds-pl-head`, así que el selector se quedó sin nadie a quien
   alcanzar (medido: cero `.mc-listhead` dentro de un `.exds-pl-scroll` en las tres apps).
   *El canon cambia el día que se decide; su CSS muere el día que lo suelta su último
   consumidor* (`GOBIERNO.md` §2-ter). Mesa de control conserva la suya intacta — la suya
   cuelga de `.mc-list-scroll`, que es otro contenedor y sigue vivo. */
.exds-pl-scroll > .exds-pl-head {
  position: sticky;
  top: 0;
  z-index: 2;
}
.exds-pl-col {
  display: flex;
  align-items: center;
  gap: 5px;
  padding: 10px var(--space-3);
  font: var(--fw-semibold) var(--fs-eyebrow) var(--font-sans);
  text-transform: uppercase;
  letter-spacing: var(--tracking-eyebrow);
  /* ⛔ `fg3`, Y VA ATADO AL FONDO DE `.exds-pl-head` (Ricardo, 2026-08-20: «legible pero no
     cuasi-negro»). Con el fondo en `bg-sunken` este rótulo TENÍA que ser `fg2`, porque `fg3`
     caía a 4,45:1 y AA exige 4,50 — cinco centésimas, y no se redondea: son versalitas de
     11 px, texto pequeño, que es donde la norma no perdona. Con el fondo de vuelta en
     `bg-subtle` esa atadura desaparece: `fg3` da **4,96:1** y pasa.
     Se elige `fg3` porque es **el más CLARO de la escala que aún pasa AA aquí** — que es
     literalmente lo que Ricardo pidió. `fg1` daría 16,79 (el cuasi-negro que descartó) y `fg2`
     6,85.
     *El color del rótulo nunca fue una preferencia: es la variable dependiente del fondo. Por
     eso cambiar el gris de una cabecera no es «cambiar un color», es un paquete de dos.* */
  color: var(--fg3);
  user-select: none;
  white-space: nowrap;
  min-width: 0;
  /* ⛔ SE RECORTA, NO SE MONTA ENCIMA (2026-08-17). La celda (`.exds-pl-cell`) ya llevaba
     `overflow:hidden` y la CABECERA no, así que en un ancho apretado los rótulos de dos
     palabras se pintaban ENCIMA de la columna vecina — «SALDO INSOLUTO» sobre «PRODUCTO».
     Se cazó MIRANDO una captura a 1000 px, no leyendo: `nowrap` sin `overflow` no desborda
     hacia abajo, desborda hacia el lado, y ahí no hay nada que lo detenga.
     *Recortar dice «no cabe»; solaparse dice «está roto».* */
  overflow: hidden;
}

/* ⛔ Y EL RECORTE SE CORRIGIÓ EL MISMO DÍA, PORQUE CORTABA POR EL LADO EQUIVOCADO.
   Con `overflow:hidden` y la columna alineada a la derecha, «SALDO INSOLUTO» se leía
   **«DO INSOLUTO»**: el sobrante se iba por la IZQUIERDA y ahí lo cortaba la caja. Un
   rótulo recortado por delante no dice «no cabe» — dice otra palabra.

   ⛔ Y LO QUE MÁS IMPORTA DE ESTE DEFECTO ES CÓMO NO SE VIO: el instrumento obvio,
   `scrollWidth > clientWidth`, decía **que no había recorte** en las siete columnas. No
   mentía: `scrollWidth` sólo mide el desbordamiento hacia el FINAL, y aquí el contenido se
   iba hacia el principio. *Lo cazó mirar la captura, no medirla.*

   Se arregla con `text-overflow`, que recorta por el final y **avisa con puntos suspensivos**
   — pero necesita un elemento de texto propio: el `ellipsis` no funciona sobre un contenedor
   flex, sólo sobre el texto. Por eso el rótulo cambia de sitio, no de estilo. */
.exds-pl-col > .exds-pl-col-txt {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}
.exds-pl-col[data-alinea="right"]  { justify-content: flex-end; }
.exds-pl-col[data-alinea="center"] { justify-content: center; }

/* LA CABECERA ORDENABLE ES UN CONTROL (ficha § Accesibilidad): teclado y `aria-sort`. */
.exds-pl-col.is-sortable { cursor: pointer; }
/* ⛔ EL HOVER VUELVE A `fg2` PORQUE EL REPOSO VOLVIÓ A `fg3` (2026-08-20). El hover es UN
   ESCALÓN sobre el reposo, no un valor propio: cuando el reposo subió a `fg2` (19-ago) este
   selector tuvo que subir a `fg1` para seguir significando algo, y al volver el reposo a `fg3`
   vuelve con él. Si se quedara en `fg1` el hover daría un salto de dos peldaños —de 4,96 a
   16,79— y el rótulo pasaría a cuasi-negro justo en el estado que Ricardo acaba de descartar
   para el reposo.
   *Un cambio de token no acaba en el token: acaba cuando se revisan sus VECINOS DE ESTADO. Es
   la segunda vez que este selector se mueve por arrastre, y las dos sin que ningún gate lo
   pidiera — porque «hover y reposo del mismo color» no es un error, es un control que deja de
   contestar.* */
.exds-pl-col.is-sortable:hover { color: var(--fg2); }
.exds-pl-col.is-sortable:focus-visible { outline: 2px solid var(--color-border-focus); outline-offset: -2px; }
/* ⛔ EL ICONO DE «SE PUEDE ORDENAR» SUBE DE fg4 A --color-notice (2026-08-20, Ricardo).
   DECISION-FUENTE: Ricardo 2026-08-20 — «de acuerdo con tus recomendaciones», sobre
   plans/decisiones/_decision_tres_pendientes_2026-08-20.html
   Daba 1,53:1 sobre el fondo del encabezado (bg-sunken) contra el 3:1 que WCAG 1.4.11 pide
   a un control.

   ⛔ CIFRA ACTUALIZADA EL 2026-08-20 (segunda vuelta del mismo dia): con el fondo de vuelta en
   `bg-subtle` este glifo da **4,34:1**, no 3,90. El TOKEN no cambia — cambio el suelo sobre el
   que se mide. Sigue pasando el 3:1 de un control y queda un escalon POR DEBAJO del rotulo
   (`fg3`, 4,96): la jerarquia de la cabecera es rotulo > glifo, porque el nombre es el dato y
   la flecha es el afford.
   *Una cifra de contraste no es del color: es de la PAREJA. Dejarla escrita al lado del token
   que no cambio es como acaban las fichas diciendo numeros que ya no salen.* */
.exds-pl-sic {
  width: 14px; height: 14px;
  display: block; flex-shrink: 0;
  color: var(--color-notice);
  transition: transform var(--motion-base) var(--ease-default);
}
/* Sin ordenar → doble chevrón tenue. Ordenada → un solo chevrón, en tinta más viva,
   que GIRA para cambiar de sentido en vez de sustituirse por otro dibujo.

   ⛔ EL CHEVRÓN **ACTIVO** SE QUEDA EN `fg2` Y ESO INVIERTE UNA RELACIÓN — declarado, no
   olvidado (2026-08-20). Hasta hoy activo (`fg2`, 6,15) y rótulo (`fg2`, 6,15) pesaban IGUAL;
   con el rótulo en `fg3` (4,96) el chevrón activo pasa a **6,85**, o sea POR ENCIMA del nombre
   de su columna. No es lo mismo que el chevrón neutro, al que sí le aplica «rótulo > glifo»:
   el neutro es un AFFORD («esta columna se puede ordenar») y debe pesar menos que el dato; el
   activo es un ESTADO («la lista está ordenada por aquí»), lo lleva UNA sola columna, y desde
   el 2026-08-20 es el ÚNICO sitio donde se ve el criterio de orden — el pie dejó de decirlo
   ese día y su decisión se apoya expresamente en que «la columna ordenada lleva su flecha».
   *Si el único indicador que queda de un dato pesa menos que su etiqueta, el dato desaparece.*
   Queda anotado por si Ricardo quiere bajarlo: es una relación que hoy no existe. */
.exds-pl-col[aria-sort="none"]      .exds-pl-sic[data-ic="activo"],
.exds-pl-col[aria-sort="ascending"] .exds-pl-sic[data-ic="neutro"],
.exds-pl-col[aria-sort="descending"] .exds-pl-sic[data-ic="neutro"] { display: none; }
.exds-pl-col[aria-sort="ascending"]  .exds-pl-sic[data-ic="activo"],
.exds-pl-col[aria-sort="descending"] .exds-pl-sic[data-ic="activo"] { color: var(--fg2); }
.exds-pl-col[aria-sort="descending"] .exds-pl-sic[data-ic="activo"] { transform: rotate(180deg); }

/* ── EL SIGNO DE AYUDA · `AyudaColumna` (COMPONENTE_AYUDA_COLUMNA.md) ─────────────
   ⛔ TODO CONTORNO: sin relleno, sin borde y sin caja. Es el ícono `circle-help` de
   lucide y nada más. Ni un círculo dibujado alrededor, ni un carácter `?` tipográfico.
   ⛔ EN REPOSO VA `--color-notice`, NO `--fg4` (Ricardo, 2026-08-20: «decide tú el color del
   texto, chevron y (?)»). El principio no cambia —va MÁS APAGADO que el nombre de la columna,
   porque el nombre es el dato y la ayuda es un servicio (no-negociable 7-bis)—; lo que cambia
   es que «más apagado» tenía que dejar de significar «invisible»: `--fg4` daba **1,53:1** sobre
   el encabezado, y esto es un `role="button"`, al que WCAG 1.4.11 exige **3:1**. Reprobaba, y
   `COMPONENTE_AYUDA_COLUMNA.md` lo tenía escrito como hallazgo abierto desde esa misma mañana.
   `--color-notice` da **4,34:1**: pasa, y queda por debajo del rótulo (`fg3`, 4,96).

   ⛔ Y VA EN EL MISMO TOKEN QUE SU VECINO EL CHEVRÓN, a propósito. Los dos son glifos de
   servicio en la misma fila y hasta hoy pesaban 3,90 contra 1,53 — un desnivel de más del
   doble entre dos cosas que hacen el mismo trabajo. *Dos elementos con el mismo papel y pesos
   distintos no comunican una jerarquía: comunican que uno de los dos se eligió sin mirar al
   otro.*
   ⛔ Y SU CLIC NO PROPAGA AL DE ORDENAR — el candado vive en el JS (`stopPropagation`
   + orden de ramas). Sin él, pedir ayuda REORDENA LA TABLA debajo del usuario. */
.exds-pl-help {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px; height: 18px;
  margin-left: 1px;            /* 5 del `gap` + 1 = los 6 px que pide la ficha */
  border: 0;                   /* ⛔ sin caja: el ícono ya trae su contenedor dentro */
  background: none;
  border-radius: var(--radius-pill);
  color: var(--color-notice);
  cursor: pointer;
  flex-shrink: 0;              /* no crece el alto de la fila de cabecera */
  transition: color var(--motion-fast) var(--ease-default);
}
.exds-pl-help:hover { color: var(--fg1); }
.exds-pl-help:focus-visible { outline: 2px solid var(--color-border-focus); outline-offset: 1px; }
.exds-pl-help svg { width: 14px; height: 14px; display: block; }

/* ── EL CUERPO · la región que cambia de estado ──────────────────────────────────
   Los cuatro estados y las filas ocupan EL MISMO SITIO (es lo que el patrón garantiza,
   § De quién es cada cosa). La región es `aria-live="polite"`: quien no ve la pantalla
   tiene que enterarse de que la lista dejó de cargar. */
.exds-pl-body { position: relative; }
.exds-pl-filas,
.exds-pl-skel,
.exds-pl-estado { display: none; }
.exds-pl[data-estado="lleno"]    .exds-pl-filas { display: block; }
.exds-pl[data-estado="cargando"] .exds-pl-skel  { display: block; }
.exds-pl[data-estado="vacio"]           .exds-pl-estado[data-cual="vacio"],
.exds-pl[data-estado="error"]           .exds-pl-estado[data-cual="error"],
.exds-pl[data-estado="sin-resultados"]  .exds-pl-estado[data-cual="sin-resultados"] { display: flex; }

/* ⛔ ENTRE ESTADOS: cambio de OPACIDAD corto, sin desplazamiento (ficha § Movimiento).
   *La lista no debe «llegar» desde ningún sitio: ya estaba ahí, sólo faltaba saber qué
   tenía dentro.* Por eso no hay `translate` en esta transición y no debe añadirse. */
.exds-pl-filas,
.exds-pl-skel,
.exds-pl-estado { animation: exds-pl-fundido var(--motion-fast) var(--ease-default) both; }
@keyframes exds-pl-fundido { from { opacity: 0; } to { opacity: 1; } }

/* ── LA FILA · UNA puerta al detalle, no seis celdas pulsables ───────────────────── */
.exds-pl-row {
  display: grid;
  grid-template-columns: var(--exds-pl-cols);
  align-items: center;
  min-height: var(--exds-pl-fila);
  border-bottom: 1px solid var(--color-border-subtle);
  cursor: pointer;
  transition: background var(--motion-fast) var(--ease-default);
}
.exds-pl-row:last-child { border-bottom: 0; }
/* ⛔ EL HOVER BAJA A `bg-sunken` PORQUE LA CABECERA SUBIÓ A `bg-subtle` (2026-08-20). Los dos
   grises no pueden ser el mismo: uno es CROMO (la cabecera, que está siempre) y el otro es un
   ESTADO EFÍMERO («esta fila está bajo el puntero»). Con `bg-subtle` en los dos, pasar el ratón
   pintaba la fila del color exacto del encabezado — un color diciendo dos cosas.
   No es una simulación: es el defecto REAL que había hasta el 19-ago y que aquel cambio cerró
   por accidente al hundir la cabecera. Al revertir la cabecera, el hover tiene que moverse en
   sentido contrario o el defecto vuelve entero.
   **De paso mejora lo que tenía que hacer:** contra el blanco de la fila, `bg-sunken` da
   **1,228:1** y `bg-subtle` daba **1,103** — el hover se nota más, que es su único trabajo.
   *Un token no se elige por su nombre: se elige por con quién va a convivir en la misma
   pantalla.* */
/* ⛔ EL HOVER SE QUEDA EN `bg-subtle`, EL MISMO GRIS QUE EL ENCABEZADO — Y ES DELIBERADO.
   `DECISION-FUENTE: Ricardo, 2026-08-20 — «ignora la colisión, no pasa nada».`
   Al volver el encabezado al gris claro, hover de fila y cromo quedan del mismo color. Se le
   planteó como consecuencia a resolver —el 19-ago se había separado justo por eso— y decidió
   que no importa. Y tiene razón por dónde: **el hover no compite con el encabezado, porque
   nunca están en la misma fila**; lo que el hover tiene que decir es «el puntero está AQUÍ»,
   y eso se lee contra las filas BLANCAS de al lado, no contra un cromo que está arriba.
   *Una colisión de color entre dos cosas que nunca se comparan no es una colisión.*
   Se deja escrito para que nadie lo «arregle» otra vez sin saber que ya se decidió. */
.exds-pl-row:hover { background: var(--color-bg-subtle); }
/* ⛔ Y SOBRE ESE GRIS, LA ALARMA SUBE UN PELDAÑO (2026-08-29). Con el rojo nuevo `--color-error`
   (#DA342B) la alarma mide 4,66 sobre la fila blanca y **4,23 sobre la fila resaltada**, que no
   llega al mínimo de 4,5. Es exactamente el caso que el canon resuelve: base donde alcanza,
   `-deep` donde no — y aquí `--color-error-deep` da 4,84.
   `DECISION-FUENTE: Ricardo, 2026-08-29 — «acercarse lo más posible al color base, si acaso se
   oscurece lo mínimo indispensable para lectura».` La alarma se queda en la base el 100 % del
   tiempo que la fila no está bajo el ratón, que es casi todo.
   *No se oscureció el color: se le puso un escalón al único estado donde el fondo cambia.* */
.exds-pl-row:hover .exds-pl-alarma { color: var(--color-error-deep); }
.exds-pl-row:focus-visible { outline: 2px solid var(--color-border-focus); outline-offset: -2px; }
.exds-pl-cell {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: 13px var(--space-3);
  font: var(--fw-regular) var(--fs-body) var(--font-sans);
  color: var(--fg1);
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.exds-pl-cell[data-alinea="right"]  { justify-content: flex-end; }
.exds-pl-cell[data-alinea="center"] { justify-content: center; }
/* Los identificadores van en MONOESPACIADA, y su ausencia se dice con «—», nunca en
   blanco. *Una celda vacía no distingue «no tiene» de «no cargó».* */
.exds-pl-cell[data-tipo="id"] { font-family: var(--font-mono); font-size: var(--fs-body-sm); color: var(--fg2); }
.exds-pl-vacia { color: var(--fg4); }
/* Cada fila TERMINA EN CHEVRÓN: es la afordancia de que la fila entera abre el detalle. */
.exds-pl-chev {
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--fg4);
  padding-right: var(--space-3);
}
.exds-pl-chev svg { width: 16px; height: 16px; display: block; }
/* ⛔ SIN SANGRADO · el chevrón dentro de un contenedor que YA tiene relleno (2026-08-21).
   Los 12 px de la derecha existen porque en la pantalla de lista la fila va de borde a borde
   del cromo: sin ellos el chevrón se pegaría al filo. Dentro de un cajón de 420 px la fila
   vive en un contenedor con 20 px de relleno propio, así que ese aire se SUMA y el chevrón
   queda a 32 px del borde — desalineado con todo lo demás de la columna.
   Es la MISMA prop de sangrado que ya usan `FieldRows` (`data-bleed="no"`) y `OptionList`
   (`data-sangrado="ninguno"`): se declara la variante en la pieza en vez de tener DOS
   chevrones de fila en el sistema, que es lo que había (`.fxc-rel-chev`, en Facturación). */
.exds-pl-chev[data-sangrado="ninguno"] { padding-right: 0; }

/* ⛔ EL AVATAR DE INICIALES con el que abre cada fila · GRIS, SIEMPRE, SIN EXCEPCIÓN.
   `DECISION-FUENTE: Ricardo 2026-08-16 — «Avatar en listas, gris. Avatar del usuario, este
    soy yo, en color. Esto significa que en la top bar donde está el avatar sí habrá un
    avatar de color, pero en las listas que un analista ve en mesa de control están en
    gris.»`

   No hay `data-tinte` y no debe haberlo: **el color está RESERVADO a «éste eres tú»** — el
   avatar de la barra superior, que es uno y siempre el mismo. Es la regla v0.10 de
   `COMPONENTE_AVATAR.md`, que ya existía antes de este patrón.

   POR QUÉ, en el argumento del canon: en una lista el avatar sale en TODAS las filas, así
   que su color está siempre presente y nunca cambia de significado — y eso entrena al ojo a
   ignorar el color, factura que paga la pastilla de estado del mismo renglón, que sí quería
   decir algo. *En una pantalla el color no es decoración: es un presupuesto.*

   ⚠ Y no se hereda INC-106 (iniciales blancas sobre rosa, 1,29:1): aquí la pareja es
   `--color-bg-subtle` con `--fg2`. */
.exds-pl-av {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px; height: 34px;
  flex-shrink: 0;
  border-radius: var(--radius-pill);
  background: var(--color-bg-subtle);
  color: var(--fg2);
  font: var(--fw-semibold) var(--fs-caption) var(--font-sans);
  /* El contorno del canon del Avatar: sin él la silueta mide 1,10:1 y la forma no llega al
     ojo. Va en el MISMO gris que la etiqueta de contorno — el sistema tiene UN gris de
     contorno, no dos que se lean como un descuido. */
  border: 1px solid var(--color-border-default);
  box-sizing: border-box;
}

/* ⛔ LA ORGANIZACIÓN · LO DICE LA FORMA, y nada más que la forma.
   `DECISION-FUENTE: Ricardo 2026-08-16 — gana el catálogo: persona = círculo ·
    organización = cuadrado-redondeado. El distintivo de edificio se retira: era una segunda
    manera de decir lo mismo.`

   ⛔ EL RADIO ES PROPORCIONAL, ~18 % DEL LADO — y esto NO se decidió hoy: lo decidió Ricardo
   el **2026-08-17** y el KIT lo lleva desde entonces (`Avatar.jsx`: `size * 0.18`).
   **El gemelo vanilla nunca se enteró**, así que aquí seguía el `--radius-lg` (16) fijo.
   Lo vio él en pantalla el 2026-08-21: *«el avatar tiene la forma que se usa para los
   estados vacíos, error, etc. NO CORRESPONDE a avatar, que es cuadrado con esquinas
   redondas o circular»*.

   Por qué el absoluto estaba mal, con los números que lo demuestran: un radio FIJO sobre
   cajas de tamaños distintos **no produce la misma forma**.

     tile de estados   52 px con radio 16 → **31 %**
     avatar            40 px con radio 16 → **40 %**
     avatar            34 px con radio 16 → **47 %**  ← se lee redondo

   *Un radio absoluto sobre una caja variable no es una medida: es una medida distinta en
   cada tamaño.* Y la consecuencia práctica la midió él en Contactos, donde la forma es **la
   única señal que separa una empresa de una persona**.

   ⛔ Y EL VALOR SE COPIA DEL KIT, NO SE ELIGE AQUÍ. La primera versión de este arreglo puso
   un 30 % «para igualar la proporción del tile» — un número razonado y **distinto del que ya
   existía**. *Cuando el kit ya resolvió algo, el gemelo no vuelve a decidirlo: lo traduce.
   Dos valores razonados por separado son dos verdades, y la que gana es la que se mire.* */
.exds-pl-av[data-tipo="organizacion"] { border-radius: 18%; }

/* LA ETIQUETA DE RELACIÓN · Badge variante «etiqueta»: CONTORNO gris, sin color.
   El color queda libre para significar estado.

   ⛔ EN LA FILA SE PINTA **UNA**, Y EL RESTO SE CUENTA (2026-08-25).
   `DECISION-FUENTE: Ricardo, 2026-08-25 — hoja «las cinco recomendaciones aplicadas»,
    decisión 5, opción B: «De acuerdo con todas tus recomendaciones, aplícalas.»`

   Aquí decía *«una fila puede llevar VARIAS»* y **ahí se detenía**: el canon no tenía
   respuesta para cuándo NO caben. Y no caben casi nunca — la columna de relación de
   Contactos mide 137 px con el panel de detalle abierto, que es como se trabaja.

   El defecto no era que se cortara: era que **se cortaba EN SILENCIO**. Esta regla prohíbe
   encogerse (`flex-shrink: 0`) y `.exds-pl-cell` prohíbe desbordar (`overflow: hidden`);
   entre las dos, la segunda etiqueta desaparecía a media palabra, sin puntos suspensivos y
   sin rastro de que existiera. Medido el 2026-08-25: **19 px de recorte con dos relaciones,
   105 px con tres**. *Un dato que falta y lo dice es un dato menos; un dato que falta y
   calla es un dato falso.*

   ⛔ **LO QUE SOBRA SE CUENTA CON `.exds-pl-cnt[data-enfasis="ambiente"]`** — la MISMA
   pastilla que cuenta los avisos de Facturación y las vistas del selector. **Cero pieza
   nueva.** El número va **pelado, sin «+»**: la pastilla ya dice «hay más» por su forma.

   ⛔ Y ES **UNA** FIJA, NO «LAS QUE QUEPAN» — que es la respuesta que parece mejor y no lo
   es. «Las que quepan» no es una cantidad: es una cantidad **distinta en cada momento**, y
   la misma fila enseñaría dos etiquetas con el panel cerrado y una con el panel abierto.
   *Una fila que cambia de contenido al abrir un panel obliga a releerla — y la fila existe
   para encontrar, no para leer.* Las demás se leen enteras en el detalle, que ya las enseña
   todas: **la fila sirve para ENCONTRAR; el detalle, para LEER.**

   La regla entera, con sus cinco líneas duras: `PATRON_PANTALLA_LISTA.md` § «Hay una sola
   manera de decir “hay más de lo que ves”». La vigila `gate_etiquetas_contadas.js`. */
.exds-pl-tag {
  display: inline-flex;
  align-items: center;
  height: 20px;
  padding: 0 10px;
  border: 1px solid var(--color-border-default);
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--fg2);
  font: var(--fw-medium) var(--fs-caption) var(--font-sans);
  line-height: 1;
  white-space: nowrap;
  flex-shrink: 0;
}

/* ═══════════════════════════════════════════════════════════════════════════════
   ▓ 4-ter · EL VOCABULARIO DE CELDA — lo que una fila de negocio necesita decir ▓
   (2026-08-16 · captura de Cartera/contratos)

   ⛔ ESTO NO CONVIERTE AL PATRÓN EN NEGOCIO. Sigue sin saber qué es un contrato: lo que
   se declara aquí son FORMAS DE DECIR —dos líneas, una cifra, una ausencia, un ícono con
   su palabra—, no significados. Quien monta elige cuál usar en cada columna.
   *La alternativa era que cada pantalla se inventara su propia manera de poner un
   subtítulo debajo de un nombre, que es exactamente el desorden de las 13 filas distintas
   que este patrón existe para cerrar.*
   ═══════════════════════════════════════════════════════════════════════════════ */

/* LA CELDA DE DOS LÍNEAS · dato arriba, contexto abajo. El de abajo va un peldaño más
   apagado y más chico: si pesaran igual, la fila tendría dos titulares y ninguno. */
.exds-pl-dos {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 2px;
  min-width: 0;
  overflow: hidden;
}
.exds-pl-dos-a {
  color: var(--fg1);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.exds-pl-dos-b {
  font: var(--fw-regular) var(--fs-body-sm) var(--font-sans);
  color: var(--fg3);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* El identificador va en MONOESPACIADA viva también aquí abajo (el folio bajo el nombre):
   es la misma regla de `data-tipo="id"` de la celda, aplicada a la segunda línea. */
.exds-pl-dos-b[data-tipo="id"] { font-family: var(--font-mono); }
/* AÑADIDO 2026-08-23 (`gate_gemelos_conducta.js`): el kit expresa el MISMO identificador con
   una clase (`.exds-pl-id`) en vez de `[data-tipo="id"]` — misma conducta, otra convención de
   marcado. Se añaden las dos reglas del kit tal cual, como vía alterna: quien monte con la
   clase suelta (fuera de una celda con `data-tipo`) también obtiene la tipografía correcta. */
.exds-pl-id { font-family: var(--font-mono); font-size: var(--fs-body-sm); color: var(--fg2); }
.exds-pl-dos-b .exds-pl-id { font-size: inherit; }
/* Alineada a la derecha, las dos líneas se alinean por la derecha — no sólo la de arriba.
   *Una cifra alineada a la derecha con su fecha centrada debajo se lee como dos columnas.* */
.exds-pl-cell[data-alinea="right"] .exds-pl-dos { align-items: flex-end; text-align: right; }

/* LA CIFRA · tabular SIEMPRE. Sin `tabular-nums` los dígitos tienen anchos distintos y las
   columnas de dinero dejan de poder compararse en vertical, que es para lo que están. */
.exds-pl-cifra {
  font-variant-numeric: tabular-nums;
  color: var(--fg1);
}
/* El ÉNFASIS de columna — 600. **Una sola por tabla**: si todo pesa, nada pesa
   (`COMPONENTE_TABLA.md` › `columns[].fuerte`). */
.exds-pl-cifra[data-fuerte] { font-weight: var(--fw-semibold); }

/* ⛔ EL DATO QUE ALARMA · va en rojo Y con palabra («29 días»), nunca sólo en color.
   El color es el refuerzo, no el portador: quien no distingue el rojo tiene que poder leer
   lo mismo. Es el mismo rojo de texto del canon (`--color-error-deep`, 6.14:1 sobre
   blanco), no uno más vivo elegido a ojo. */
/* ⛔ Y NO LLEVA PESO PROPIO (corregido 2026-08-17, a petición de Ricardo: *«veo el texto de
   las celdas muy inconsistente entre sí… demasiados tamaños, pesos»*).
   Llevaba `--fw-medium` (500), y eso metía un TERCER peso en el texto de las celdas. Medido:
   quedaban 400 (el dato) · 600 (la cifra que se busca) · **500 (sólo esto)**.

   El 500 no era un peso de dato: en esta misma hoja `--fw-medium` es el peso de los BOTONES
   y de los rótulos. La alarma le estaba pidiendo prestado el peso a un control.

   ⛔ Y NO HACÍA FALTA, que es el argumento de fondo: **el rojo ya dice que algo va mal.**
   Sumarle peso no añade significado — añade un EJE. *Cuando dos señales dicen lo mismo, la
   segunda no refuerza: deja de significar, y el ojo empieza a leer peso donde no hay
   jerarquía.* Así que se queda en el peso del dato y la señal la porta el color, que es lo
   que la propia nota de arriba ya decía.

   Con esto el texto de celda queda en **DOS pesos**: 400 lo normal, 600 la cifra que la
   columna busca — que es literalmente lo que manda `COMPONENTE_TABLA.md` (§ «Peso de la
   última columna: 600, es la cifra que se busca»). */
.exds-pl-alarma {
  font-variant-numeric: tabular-nums;
  /* ⛔ LA BASE, NO EL DEEP — y aquí el deep no era «más seguro», era PEOR (2026-08-29).
     Con el rojo NUEVO (#DA342B, Ricardo 2026-08-29) la base mide **4,66 sobre la fila blanca** y
     el deep 5,34.
     ⛔ Y LO QUE SEGUÍA AQUÍ YA NO ES CIERTO, se deja con su fecha: decía que «--color-error-deep es
     MÁS CLARO que --color-error: 5,34 contra 5,96, el rojo es la única familia cuyo profundo pierde
     contraste». Lo era de #B83227; al cambiar la base la inversión desapareció sola y las cuatro
     familias van en el mismo sentido. Lo que NO cambia es por qué esta regla usa la base: es el
     peldaño más cercano al color de familia, que es la regla de Ricardo — antes coincidía además
     con el número mayor, y ahora ya no. *Un argumento que se apoya en dos razones y pierde una no
     deja de ser válido, pero deja de ser el mismo argumento.*
     El texto original decía, para el registro:
     así que usarlo por prudencia empeoraba justo lo que pretendía proteger.
     `DECISION-FUENTE: Ricardo, 2026-08-29 — «Los colores de texto deben acercarse lo más
     posible al color base, si acaso se oscurece lo mínimo indispensable para lectura.»`
     Ver COMPONENTE_SEMANTICOS.md § «Y VALE PARA LAS CUATRO FAMILIAS». */
  color: var(--color-error);
}
/* ⛔ COSECHA 2026-08-21 · LA ALARMA TIENE DOS ESCALONES, NO UNO — y el segundo ya existía
   en el sistema, sólo que no aquí. Un plazo que CORRE («Vence en 41 h») y un plazo que YA
   VENCIÓ («Plazo vencido») no son el mismo dato: el primero pide agenda, el segundo pide
   acción. Con un solo color había que elegir entre pintar los dos de rojo —y entonces el
   rojo deja de significar «tarde»— o dejar el ámbar fuera del catálogo, que es lo que la
   pantalla de Facturación llevaba haciendo con una clase propia (`.mc-wait.warn`).

   ⛔ NO ESTRENA NI UN COLOR: `--color-warning-deep` es el mismo tono que `Badge.jsx` da a
   `warning` y el mismo que la pastilla de aquí arriba, y el nombre `atencion` es el que el
   `Dot` del catálogo ya usa para el ámbar. **La regla de fondo NO cambia** — el color sigue
   siendo refuerzo y no portador: los dos escalones llevan su palabra («Vence en 41 h» ·
   «Plazo vencido»), así que quien no distingue ámbar de rojo lee exactamente lo mismo. */
.exds-pl-alarma[data-tono="atencion"] { color: var(--color-warning-deep); }

/* ÍCONO + PALABRA · el ícono no sustituye a la palabra, la acompaña. Va más apagado que
   ella a propósito: el dato es el texto («Automática»), el dibujo es el atajo visual. */
.exds-pl-icotxt {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1-5);
  min-width: 0;
}
.exds-pl-icotxt svg { width: 15px; height: 15px; display: block; flex-shrink: 0; color: var(--fg3); }

/* ═══════════════════════════════════════════════════════════════════════════════
   ▓ LA PASTILLA DE ESTADO · `Badge variant="tint"` — UNA pieza, y va SÓLO TEXTO ▓

   `DECISION-FUENTE: Ricardo 2026-08-16 — gana el catálogo. La pastilla va tintada, así que
    va sólo texto: el punto se retira.`

   ⛔ Y HAY QUE DECIRLO PORQUE SE PROPAGÓ LO CONTRARIO: **`COMPONENTE_BADGE.md` y
   `COMPONENTE_DOT.md` NO se contradicen.** La regla completa ya existía entera y depende de
   si hay relleno:
       · Badge: **con fondo de color → sólo texto** (el fondo YA es la señal) ·
                **sin fondo → dot + texto** (el punto es la única señal de color que queda).
       · Dot:   «punto dentro de una pastilla o insignia: **no lo gobierna esta ficha**» —
                cede explícitamente al Badge.
   Se leyó el tamaño `sm` del Dot como un permiso para meterlo en una pastilla TINTADA, y no
   lo es: es la medida para cuando el Badge SIN FONDO lo pide. *Dejar escrito un choque que
   no existe hace que alguien lo «resuelva» rompiendo una de las dos fichas.*

   Los tonos NO son libres: fondo y tinta salen del par tint/deep del canon. Aquí no se elige
   ningún color: se elige un SIGNIFICADO y el color viene con él.
   ═══════════════════════════════════════════════════════════════════════════════ */
.exds-pl-est {
  display: inline-flex;
  align-items: center;
  height: 20px;
  padding: 0 10px;
  border-radius: var(--radius-pill);
  font: var(--fw-medium) var(--fs-caption) var(--font-sans);
  line-height: 1;
  white-space: nowrap;
  flex-shrink: 0;
  /* El contorno de los tonos entra en los 20 px, no encima: sin esto la pastilla mide 22 en
     cualquier página que no traiga su propio `border-box`, y el alto de la fila la sigue. */
  box-sizing: border-box;
}
/* ⛔ COSECHA 2026-08-21 · EL TONO ÁMBAR Y EL CONTORNO, QUE EL KIT YA TENÍA Y EL GEMELO NO.
   No es una pieza nueva ni un color nuevo: es la TRADUCCIÓN que faltaba. `Badge.jsx` declara
   seis tonos —`success · warning · error · info · neutral · pause`— y su variante `tint`
   remata cada uno con `border: 1px solid t.borde`. Este gemelo se quedó en CUATRO tonos y
   SIN contorno, así que una pantalla que montara la pastilla del patrón salía distinta de la
   misma pastilla montada con el kit.

   Se midió antes de escribirlo: la falta ya estaba escrita en
   `plans/SOLICITUD_CLAUDE_DESIGN_facturacion_2026-08-20.md` («*Falta el tono ámbar* →
   **existe**: `Badge` lo tiene; lo que no lo tiene es el gemelo `.exds-pl-est`»), y en el
   backoffice la app lo estaba supliendo con un modificador propio de otra pantalla
   (`.mc-state.is-sev-media`, con su contorno). *Cuando una app tiene que inventar un
   modificador para decir algo que el canon ya dice, el defecto está en la traducción.*

   ⛔ EL NOMBRE `atencion` NO SE ELIGE AQUÍ: es el que el catálogo ya usa para el ámbar en
   `componentes/dot/dot.css` («atención · en proceso»). El set de este gemelo está en
   castellano —info · exito · problema · neutro—, así que traducir `warning` como `warning`
   habría metido un quinto idioma en una lista de cuatro.

   El único consumidor de `.exds-pl-est` hasta hoy era el ejemplo del propio patrón, así que
   añadir el contorno no repinta ninguna pantalla existente. */
.exds-pl-est[data-tono="info"]     { background: var(--color-info-tint);    color: var(--color-info-deep);    border: 1px solid var(--color-info-stroke); }
.exds-pl-est[data-tono="exito"]    { background: var(--color-success-tint); color: var(--color-success-deep); border: 1px solid var(--color-success-stroke); }
.exds-pl-est[data-tono="atencion"] { background: var(--color-warning-tint); color: var(--color-warning-deep); border: 1px solid var(--color-warning-stroke); }
.exds-pl-est[data-tono="problema"] { background: var(--color-error-tint);   color: var(--color-error-deep);   border: 1px solid var(--color-error-stroke); }
.exds-pl-est[data-tono="neutro"]   { background: var(--color-bg-subtle);    color: var(--fg2);                border: 1px solid var(--color-notice-stroke); }

/* ── EL ESQUELETO · «cargando» se dice con la FORMA de lo que va a llegar ─────────
   ⛔ NO es un texto «Cargando…». El esqueleto dice QUÉ va a llegar; un texto sólo dice
   que esperes (ficha § Los cuatro estados).

   ⚠ ESTA REGLA TIENE UN CONSUMIDOR FUERA DE `.exds-pl`, Y SE DECLARA (2026-08-21).
   Lee `--exds-pl-cols` y `--exds-pl-fila`, que declara `.exds-pl` y NO `:root`. El cajón de
   detalle de Facturación (`admin/js/backoffice/facturacion-cajon.js` › `esqueleto()`) monta
   `.exds-pl-skel-row` **sin ese ancestro**: vive en `#topbarDrawer`, que cuelga del marco.
   Ahí las dos `var()` quedan sin resolver y la regla cae a su valor inicial —una columna
   implícita, sin alto mínimo—, que resulta ser justo lo que ese esqueleto quiere porque
   tiene UNA celda por fila.
     *Funciona por coincidencia, no por contrato.* Y la coincidencia ya se movió una vez: el
   commit `e599a53` cambió el reparto de columnas de la lista de Facturación y ese esqueleto
   no se enteró. **Quien toque estas dos variables tiene un consumidor fuera de la tabla.**
   ⚠ El remedio de verdad queda ABIERTO —que el patrón declare qué clases suyas se pueden
   montar sueltas y con qué valores por defecto—, porque elegir el alto y el ritmo de un
   esqueleto fuera de una tabla es una decisión de diseño, no una técnica (regla 0-bis).
   Detalle: `design-system/homologacion/facturacion-cajon.md` §3-bis. */
.exds-pl-skel-row {
  display: grid;
  grid-template-columns: var(--exds-pl-cols);
  align-items: center;
  min-height: var(--exds-pl-fila);
  border-bottom: 1px solid var(--color-border-subtle);
}
.exds-pl-skel-row:last-child { border-bottom: 0; }
.exds-pl-skel-c { padding: 0 var(--space-3); gap: var(--space-2, 8px); }
.exds-pl-skel-b {
  display: block;
  height: 10px;
  border-radius: var(--radius-pill);
  background: var(--color-bg-subtle);
  animation: exds-pl-pulso 1.2s ease-in-out infinite;
}
.exds-pl-skel-av {
  display: block;
  width: 34px; height: 34px;
  border-radius: var(--radius-pill);
  background: var(--color-bg-subtle);
  animation: exds-pl-pulso 1.2s ease-in-out infinite;
}
@keyframes exds-pl-pulso { 0%, 100% { opacity: 1; } 50% { opacity: .45; } }

/* ── LOS TRES ESTADOS CON MENSAJE · vacío · error · filtro sin resultados ─────────
   Cáscara única (COMPONENTE_ESTADOS): tile CUADRADO-redondeado de 52 (el círculo es de
   persona) + título + cuerpo + acción. La acción del error es «Reintentar» y va NEUTRA. */
.exds-pl-estado {
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  gap: var(--space-3);
  padding: 48px var(--space-6);
}
.exds-pl-estado-ic {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 52px; height: 52px;
  flex-shrink: 0;
  border-radius: var(--radius-lg);   /* tile cuadrado-redondeado, NUNCA círculo */
  background: var(--color-bg-subtle);
  color: var(--fg3);
}
.exds-pl-estado-ic svg { width: 24px; height: 24px; display: block; }
/* El ERROR va en rojo; «no hay nada» y «el filtro no encontró» van en gris.
   *Nada falló cuando una lista está vacía: el sistema hace exactamente lo que debe.* */
.exds-pl-estado[data-cual="error"] .exds-pl-estado-ic {
  background: var(--color-error-tint);
  color: var(--color-error-deep);
}
.exds-pl-estado-t {
  font: var(--fw-semibold) var(--fs-body-lg) var(--font-sans);
  color: var(--fg1);
}
.exds-pl-estado-b {
  font: var(--fw-regular) var(--fs-body-sm) var(--font-sans);
  color: var(--fg3);
  max-width: 330px;
  line-height: var(--lh-normal);
}
/* ⛔ LA ACCIÓN DE ESTOS TRES ESTADOS **ES UN SECUNDARIO**, y por eso NO tiene declaración
   propia aquí: comparte la de §3-bis (`.exds-pl-btn2, .exds-pl-estado-btn`). Escribirla
   dos veces era lo que la hacía divergir — y de hecho divergía: esta copia pedía prestado
   `--relieve-control` (la sombra del knob del switch), justo el acoplamiento que Ricardo
   cazó el 2026-07-22 y que el canon prohíbe. Corregido el 2026-08-16 al añadir el
   secundario de la barra: **una definición, no una copia.** */

/* ═══════════════════════════════════════════════════════════════════════════════
   ▓ 5 · EL PIE — SIEMPRE, Y CON DOS DATOS ▓ (regla dura 2)

   `DECISION-FUENTE: Ricardo 2026-08-16 — «tabla: pie de página va a la izquierda y
   derecha».`

   Izquierda: «Mostrando N de M {sustantivo}» — N = filas pintadas ahora · M = cuántas
   cumplen EL FILTRO VIGENTE (no el gran total: si M ignorara el filtro, las dos cifras
   contarían universos distintos y el pie mentiría en cuanto alguien filtrara).
   Derecha: el CRITERIO DE ORDEN — «toda lista tiene un orden, aunque nadie lo haya
   elegido», así que el lado derecho nunca se queda vacío.

   ⛔ NUNCA CENTRADO Y NUNCA UN SOLO DATO. *Un slot que se disimula centrando lo que
   queda hace que la misma pieza se vea distinta según lo que le toque dentro — y
   entonces deja de ser la misma pieza.* La izquierda no se mueve; la derecha, tampoco.
   ═══════════════════════════════════════════════════════════════════════════════ */
.exds-pl-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;   /* los DOS extremos · jamás `center` */
  gap: var(--space-4);
  /* ⛔ EL LATERAL NO SE ESCRIBE AQUÍ: lo pone el selector compartido de §2 (regla dura 1).
     Antes esto decía `padding: var(--space-3) var(--space-4)` y metía un SEGUNDO relleno
     lateral (16) en la misma tarjeta que las bandas y las celdas ya tenían a 12. */
  padding-top: var(--space-3);
  padding-bottom: var(--space-3);
  /* ⛔ EL MISMO FONDO QUE LA CABECERA (Ricardo, 2026-08-19: «colorear del mismo color que el
     encabezado, el pie de la tabla»). Cabecera y pie son las dos filas de CROMO: las dos hablan
     SOBRE los datos y ninguna es un dato. Pintarlas igual dice «los datos están entre estas dos».
     Antes el pie flotaba sobre el mismo blanco que las filas y sólo un hairline lo separaba de la
     última — con la tabla llena, el pie se leía como una fila más. */
  /* ⛔ Y VUELVE A `bg-subtle` CON LA CABECERA (2026-08-20). Ricardo decidió el color del
     ENCABEZADO; el pie viene detrás **porque la regla que los ata es suya y es EJECUTABLE**:
     `historias_invariantes.js` («El pie de la tabla va del mismo color que la cabecera: son las
     dos filas de cromo», fuente Ricardo 2026-08-19) compara los dos colores RESUELTOS y se pone
     rojo si difieren. Mover uno solo no era una opción: era romper un invariante.
     *Una regla que ata dos piezas hace la mitad del trabajo sola — el día que una cambia, la
     otra ya sabe qué hacer, y además hay quien lo comprueba.*
     El motivo del hundido del 19-ago («`bg-subtle` contra el lienzo daba 1,028:1 y el pie se
     disolvía en el fondo») decae por lo mismo que el de la cabecera: el pie está DENTRO de la
     tarjeta, y quien lo separa del lienzo es el borde de la tarjeta, no su propio gris. */
  background: var(--color-bg-subtle);
  border-top: 1px solid var(--color-border-subtle);
  font: var(--fw-regular) var(--fs-body-sm) var(--font-sans);
  /* ⛔ Y EL TEXTO VUELVE A `fg3` CON ÉL — HOMOLOGADO A LA FICHA, que nunca dejó de decirlo.
     Esto decía «`fg2` por aritmética: `fg3` sobre `bg-sunken` da 4,45:1 y AA pide 4,50». Era
     cierto y ya no lo es: sobre `bg-subtle`, `fg3` da **4,96:1** y pasa.
     ⛔ Y lo que importa más que el número: `PATRON_PANTALLA_LISTA.md` § *El pie va del mismo
     color que la cabecera* **siempre dijo `--fg3`, con esta misma cifra de 4,96** — el 19-ago se
     cambió el código por la aritmética del fondo nuevo y **la ficha no se tocó**. O sea que el
     catálogo llevaba un día diciendo una cosa y las cuatro copias pintando otra, y nadie lo vio
     porque el defecto se «arregló» solo al revertir el fondo.
     *Una divergencia ficha↔código que nace de un ajuste correcto es la más difícil de cazar: no
     hay error que buscar, sólo un documento que se quedó describiendo el estado anterior.*
     La fuente de la verdad es el catálogo (CLAUDE.md §0-bis), así que gana `fg3`. */
  color: var(--fg3);
}
.exds-pl-foot b {
  font-weight: var(--fw-regular);
  font-variant-numeric: tabular-nums;   /* las cifras no bailan al cambiar el filtro */
}
.exds-pl-foot-r { white-space: nowrap; }

/* ═══════════════════════════════════════════════════════════════════════════════
   ⛔ GUARDA DE MOVIMIENTO REDUCIDO — OBLIGATORIA, sin excepción (ficha § Movimiento ·
   `gate_reduced_motion.js`). Apagarlo aquí NO deja sin información: el knob acaba
   exactamente bajo el mismo segmento, el chevrón acaba exactamente al mismo ángulo y la
   lista acaba con el mismo contenido. Sólo que instantáneo.
   ═══════════════════════════════════════════════════════════════════════════════ */
@media (prefers-reduced-motion: reduce) {
  .exds-pl-sic,
  .exds-pl-row,
  .exds-pl-accion,
  .exds-pl-fila-accion,
  .exds-pl-vpill,
  .exds-pl-btn2,
  .exds-pl-estado-btn,
  .exds-pl .srch,
  .exds-pl .srch button,
  .exds-pl-help { transition: none; }

  .exds-pl-filas,
  .exds-pl-skel,
  .exds-pl-estado,
  .exds-pl-skel-b,
  .exds-pl-skel-av,
  /* El giro de «Actualizar» tampoco: apagarlo no quita información — el acuse de que se
     pidió la recarga lo dan igual el esqueleto y el sello de frescura del pie. */
  .exds-pl-btn2-ic[data-girando] { animation: none; }
}
