/* ═══════════════════════════════════════
   ▓ COMPONENTE · Tabla — gemelo vanilla ▓
   v1.20 · 2026-10-14 (v1.19 · 2026-10-14; v1.18 · 2026-10-25; v1.17 · 2026-10-14; v1.2 · 2026-10-05; v1.1 · 2026-10-03; v1.0 · 2026-08-23). Ficha: `design-system/COMPONENTE_TABLA.md`
   Kit: `catalogo-cd/components/Tabla.jsx`

   v1.20 · 2026-10-14 — Δ8-1: la SEGUNDA línea de la celda (.exds-pl-dos-b) también cede con «…» y globo sólo si se recorta.

   v1.19 · 2026-10-14 — LA COLUMNA DE TEXTO QUE ENCOGE: .exds-td[data-texto=encoge] (elipsis en una línea, piso mínimo, globo de tinta sólo si se recorta, regla dura 18). Ver la regla junto a data-codigo.

   v1.18 · 2026-10-25 — EL ENCABEZADO OCULTO A LA VISTA (`data-encabezado="oculto"`): la fila de
   rótulos sigue en la página para el lector de pantalla y no se ve. Sólo en la lista de una fila
   abierta de cajón cuyo subtítulo ya nombra las columnas (COMPONENTE_TABLA.md › «El encabezado
   oculto a la vista»).

   v1.17 · 2026-10-14 — las reglas PL-HOV cubren también la fila .exds-pl-row, el avatar
   .exds-pl-av y el destino [data-destino] del gemelo de PantallaLista (antes repetidas en la
   hoja del patrón, que sólo dispone: gate_patron_pinta). Sin cambio visible.

   v1.16 · 2026-10-12 — HOVER FILA+AVATAR (PL-HOV-1): toda fila con avatar oscurece el avatar
   al hover de fila; sin avatar, solo el fondo. FILA COMPARTIDA (PL-HOV-2) apaga fondo+avatar
   sobre un destino secundario, que la Tabla conoce como [data-destino] (la clase
   .exds-pl-celda-destino es de PantallaLista, no de la Tabla). Ficha: COMPONENTE_TABLA.md.

   v1.1 · 2026-10-03 — COSECHA DE PAGOS V3: vuelve la SELECCIÓN EN GRUPO (casilla, «Seleccionar
   todos», la barra clara `.exds-tabla-barra`), nace el estado BLOQUEADA (`.exds-tabla-candado`, sin
   apagar el clic de la fila) y la LECTURA A SANGRE con su renglón de dos niveles
   (`[data-sangre]`, `.exds-tabla-ren-*`). Decididas por Ricardo el 2026-10-03; citas en cada bloque.

   El data-grid denso del backoffice: MUCHAS filas homogéneas que se comparan y se ordenan
   por columna. Fila de 67px (44 en modo lectura), cabecera pegajosa de relleno gris, la fila
   entera como superficie de hover, chevrón plano. Si las filas son pocas, heterogéneas o se
   leen de una en una **no es una tabla**: son `Card`s o una lista.

   Es la pieza más redibujada a mano de todo el producto: 20 archivos de las tres apps
   escriben esta anatomía por su cuenta. Este archivo existe para que dejen de hacerlo.

   REQUISITOS DEL ANFITRIÓN:
     1. Los tokens del canon (`colors_and_type.css`).
     2. `box-sizing: border-box`.
     3. **El reparto de columnas, en el marcado** — `style="--exds-cols: 46px 1fr 120px"`.
        Sin él la tabla cae al respaldo (`1fr`) y pinta una sola columna.
     4. **Un `max-height` sobre `.exds-tabla`**, si se quiere que la cabecera pegajosa se pegue
        de verdad. Desde el 2026-08-23 la caja que se desplaza es la tabla misma (su
        `overflow-x: auto` arrastra el eje Y), así que ya no hace falta interponer un
        contenedor: hace falta **acotarle el alto a ésta**. Ver la nota de `.exds-tabla-head`.
     5. `componentes/estados/estados.css` si se monta el estado VACÍO — esta hoja no lo
        redibuja: monta el `EmptyState` del catálogo.
     6. Para la selección y la barra: `checkbox.css`, `tooltip.css` (+ `tooltip.js`), `card.css`,
        `button.css`. Para el renglón de dos niveles: `dot.css`, `boton-copiar.css` e `iconbutton.css`.
   ═══════════════════════════════════════ */

/* ══ EL CONTENEDOR ════════════════════════════════════════════════════════════════════
   ⛔ LAS COLUMNAS VIAJAN EN UNA VARIABLE DESDE EL MARCADO (`--exds-cols`): cuántas hay y de
   qué ancho es un dato de ESA tabla, no una decisión de diseño. **No es un token del canon y
   no debe serlo** — no existe un reparto de columnas «del sistema».
   Pero su valor por defecto se declara **aquí, en la pieza**, y no se deja al respaldo de cada
   `var()`: *una variable que sólo existe por su fallback es una que nadie gobierna*, y así la
   caza `lint_token_fantasma`.
   <!-- FICHA-ALLOW: --exds-cols · parámetro de INSTANCIA, no vocabulario de diseño. -->

   ⛔ LA REGLA DE ANCHOS · LAS COLUMNAS ENCOGEN, EL CONTENIDO NO SE CORTA NUNCA, Y SI AUN ASÍ
   NO CABE **LA TABLA SE NAVEGA DE LADO** (Ricardo, 2026-08-23).
   `DECISION-FUENTE: Ricardo, 2026-08-23 — «el ancho de las columnas se pueda reducir con la`
   `regla fija e inquebrantable de que no se corte ningún contenido, e incluso dejar un pequeño`
   `espacio como margen en caso de que el sistema mida de menos cierto contenido y prevenir`
   `errores de que esta regla se rompa. Si aun así la tabla completa no cabe en este nuevo`
   `ancho, será navegable horizontalmente.»`

   Son **tres piezas y las tres viven en sitios distintos de esta hoja**, así que se enumeran
   aquí para que nadie retire una creyendo que sobra:
     1. **Encoger** — lo hacen las pistas `fr` de `--exds-cols`, como siempre.
     2. **No cortar** — `.exds-td` **no lleva** `min-width: 0`, ni `overflow: hidden`, ni
        `text-overflow: ellipsis`. Ver el bloque de `.exds-td`: son exactamente las tres
        declaraciones que le daban permiso a la rejilla para encoger una pista por DEBAJO de
        su contenido. Sin ellas, el mínimo automático de la celda vuelve a ser su
        `min-content` y **la pista se niega a bajar de ahí**.
     3. **Navegar** — este `overflow-x: auto`. La tabla que no cabe se arrastra.
   Y el **margen de seguridad** que pidió: el `column-gap` de `--space-0-5`. No es aire
   decorativo: es el colchón para que un redondeo a la baja del navegador —medir 119,6 px donde
   hacían falta 120— no acerque dos columnas.

   ⛔ Y POR ESO ESTA CAJA ES AHORA UNA REJILLA, Y LA CABECERA, LA FILA Y EL ESQUELETO SON
   **SUBREJILLAS** SUYAS. No es elegancia: sin esto la regla se cumple y **rompe la tabla**.
   Hasta hoy la cabecera y **cada fila eran rejillas independientes** que casualmente repartían
   igual, porque `min-width: 0` volvía cada pista puramente proporcional — o sea, ciega al
   contenido. Al quitar esa ceguera (que es justo lo que pide la regla), cada fila empieza a
   calcular su mínimo **con SU propio texto**: MEDIDO el 2026-08-23 con la hoja a medio
   arreglar, la fila del nombre largo ponía su segunda columna en 447 px y la del nombre corto
   en 230 — *columnas distintas fingiendo ser una*, el mismo modo de fallo que esta hoja ya
   tenía documentado para las pistas `auto`, y que se ve como «una tabla un poco descuadrada»,
   no como un error. Con `subgrid` hay **un solo juego de pistas** para toda la tabla: se
   calcula una vez con el contenido de TODAS las filas, así que ninguna se corta y todas
   coinciden. Medido a 1200 / 900 / 700 / 480 px: mismos bordes de columna en cabecera, filas y
   esqueleto, **0 celdas recortadas**.
   *La regla de Ricardo no se podía cumplir columna a columna: obligaba a que la tabla dejara de
   ser N rejillas y pasara a ser una.*
   ⚠ Cada rejilla declara `grid-template-columns` **dos veces a propósito**: primero el reparto
   normal y luego `subgrid`. Un navegador sin subrejilla ignora la segunda y se queda en la
   primera — vuelve al comportamiento de antes, con columnas que pueden descuadrarse, pero
   **sigue sin cortar contenido**. Es degradación, no rotura.
   ⚠ Y cualquier hijo directo de `.exds-tabla` que NO sea una de esas tres rejillas tiene que
   declarar `grid-column: 1 / -1` o se meterá en la primera columna. Aquí ya lo lleva
   `.exds-tabla-vacio`; quien añada un pie o una banda, que lo lleve también.

   ⚠ EL EJE Y VIENE EN EL MISMO PAQUETE, Y ESO ES LO QUE ARREGLA LA CABECERA PEGAJOSA. Declarar
   `overflow-x: auto` hace que el eje Y compute a `auto` aunque nadie lo escriba: **esta caja
   pasa a ser el ancestro con desplazamiento de su cabecera `sticky`**. Antes ese papel lo hacía
   el `overflow: hidden` del marco, que NUNCA se desplaza — o sea, la cabecera no se pegaba a
   nada. Ahora basta con que el anfitrión le ponga un **alto acotado a `.exds-tabla`** (un
   `max-height`) y la cabecera se pega de verdad. Ver la nota de `.exds-tabla-head`. */
.exds-tabla {
  --exds-cols: 1fr;
  width: 100%;
  display: grid;
  grid-template-columns: var(--exds-cols, 1fr);
  column-gap: var(--space-0-5, 2px);
  font-family: var(--font-sans, "Inter", -apple-system, system-ui, sans-serif);
  background: transparent;
  overflow-x: auto;
}

/* ── MARCO · quién pinta el borde y el aire (2026-08-12, cosecha de Contactos) ────────
   Las tres piezas que llevan este prop —Tabla, Toolbar y Shell— aparecieron en el triage como
   «síntomas»: la pantalla las sostenía con parches `!important` desde fuera para taparles el
   marco. **Un parche con `!important` desde fuera es una petición de prop que nadie escribió**,
   y tres en una sola pantalla es la señal, no el ruido.

   Por defecto la pieza se pinta su marco, como hasta hoy. `data-marco="no"` = lo pone el
   anfitrión: una card que ya tiene borde y radio, o la ventana. *No se cambia el DEFAULT: eso
   movería pantallas que hoy están bien. Lo que se abre es la puerta para no tener que
   forzarla.*

   ⛔ AQUÍ ESTABA EL `overflow: hidden`, Y SE RETIRA (2026-08-23) — no es una limpieza, es la
   mitad de la regla de anchos. Este archivo lo llevaba declarado como HALLAZGO: convertía al
   contenedor en caja de desplazamiento **que no se desplaza nunca**, así que (a) la tabla que
   no cabía se recortaba en silencio en vez de arrastrarse, y (b) la cabecera `sticky` se
   anclaba a esa caja inmóvil y no se pegaba a nada. Las dos cosas las arregla la MISMA línea:
   el `overflow-x: auto` de `.exds-tabla`, unas reglas más arriba.
   **Las esquinas se siguen recortando** — es lo único que este `hidden` aportaba de visible—:
   cualquier `overflow` distinto de `visible` recorta al radio, y `auto` lo es. Medido, no
   supuesto: ver el informe de la prueba en navegador.
   *Un hallazgo declarado no es un hallazgo resuelto, y éste llevaba un día en el archivo
   diciendo «se conserva tal cual» — hasta que la regla de anchos obligó a tocar la misma línea
   y las dos causas resultaron ser una.* Se comprueba ejecutando con
   `design-system/gate_cabecera_fija.js`. */
.exds-tabla:not([data-marco="no"]) {
  border: 1px solid var(--color-border-subtle, #E5E7EB);
  border-radius: var(--radius-container, 12px);
  background: var(--color-surface, #FFFFFF);
}

/* ══ LA CABECERA ══════════════════════════════════════════════════════════════════════
   ⛔ 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
   `--color-bg-subtle` daba **1,028:1** contra el lienzo DE ENTONCES (`--color-bg-content`
   #F6F7F8, retirado 2026-09-01) y el BLANCO de las filas destacaba MÁS (1,073) que el propio
   cromo, así que se bajó a `--color-bg-sunken` (1,145). Lo que cambió: la barra salió de la
   tarjeta (19 y 20 de agosto), así que **el encabezado ya no comparte superficie con nada con
   lo que compita**, y quien lo separa del lienzo es el borde de la tarjeta, no su propio gris.
   *Un valor elegido por una RELACIÓN deja de estar justificado cuando esa relación se rompe —
   y no avisa: sigue pintando.*

   ⛔ **2026-09-01:** el lienzo pasó de `#F6F7F8` a `#FAFBFC`. Medido hoy, `--color-bg-subtle`
   contra el lienzo NUEVO da **1,064:1** (era 1,028:1) — sigue siendo el mismo gris; el número
   histórico de arriba se conserva porque motivó la decisión del 19/20-ago, no es una
   afirmación del estado de hoy.

   ⛔ `sticky` — Y RESPECTO A QUÉ, QUE ES LA MITAD QUE FALTABA (2026-08-19). La ficha decía
   «sticky» en seis sitios y en ninguno decía cuál era el contenedor, así que la instrucción no
   se podía ejecutar: **`position: sticky` no se pega a la ventana, se pega a su ancestro con
   desplazamiento más cercano**, y un `overflow-x: auto` ya crea uno (el eje Y computa a `auto`
   aunque nadie lo escriba). Medido el 2026-08-19: de las 8 listas del proyecto, 2 declaraban
   la cabecera pegajosa y **sólo 1 era efectiva**.
   **Regla: quien escriba `sticky` en una cabecera declara en el mismo sitio el contenedor con
   alto acotado que la sostiene.** Desde el 2026-08-23 ese contenedor es **`.exds-tabla` misma**
   —su `overflow-x: auto` arrastra el eje Y a `auto`—, así que al anfitrión ya no le toca
   interponer una caja: le toca **acotarle el alto a `.exds-tabla`** (`max-height`). Mecánica
   completa: `COMPONENTE_LISTA_LARGA.md` §1-bis.

   ⛔ LAS TRES REJILLAS DE ESTA HOJA (cabecera, fila y esqueleto) SON **SUBREJILLAS** DE
   `.exds-tabla`, Y LAS TRES LO DECLARAN IGUAL. Si una sola se queda fuera, vuelve a calcular
   sus columnas por su cuenta y deja de coincidir con las otras dos — la tabla «un poco
   descuadrada» del caso `auto`. El porqué entero está arriba, en `.exds-tabla`.
   `grid-column: 1 / -1` es la otra mitad: sin él la fila ocuparía **una sola** columna de la
   rejilla madre. */
.exds-tabla-head {
  display: grid;
  grid-template-columns: var(--exds-cols, 1fr);
  grid-template-columns: subgrid;
  grid-column: 1 / -1;
  position: sticky;
  top: 0;
  z-index: 1;
  background: var(--color-bg-subtle, #F2F4F6);
  border-bottom: 1px solid var(--color-border-subtle, #E5E7EB);
}

/* ── LA CELDA DE CABECERA ─────────────────────────────────────────────────────────────
   ⛔ `exds-tabla-th` LA LLEVAN **TODAS** las celdas de cabecera —incluida la del chevrón, que
   no tiene rótulo— porque es el gancho por el que `gate_ancho_lista.js` mide la columna de la
   lista. Si sólo la llevaran las columnas con texto, el borde derecho medido sería el de la
   última columna CON RÓTULO y no el del contenido de la tabla: **40 px de desvío inventado por
   el instrumento.**

   ⛔ `fg3`, Y SIGUE ATADO AL FONDO DE ARRIBA (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 redondean hacia arriba:
   son 11 px en versalitas, 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 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 nunca es «cambiar un color»: es un paquete de dos, y
   separarlos deja la pantalla en falta — vale igual al bajarlo que al subirlo.* */
.exds-tabla-th {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: var(--space-1-5, 6px);
  padding: 10px 12px;
  /* ⛔ DEJA DE SER EYEBROW EL 2026-09-06 — es la MISMA receta que `.exds-pl-col`, y llega dos
     días tarde: el presupuesto de estilos se aplicó el 05-sep a la tabla del patrón y esta
     pieza —la Tabla del catálogo, la que monta `contactos.html`— se quedó en versalitas. Lo
     cazó Ricardo: «sigue sin corregirse eyebrow». *Una homologación hecha en una pieza y no en
     su hermana no la ve ningún gate* (GOBIERNO §0-ter, forma 6): las dos estaban «al día», cada
     una con su propio canon.
     13 px · 600 · `--fg2`, sin versalitas y sin tracking. */
  font-size: var(--fs-body-sm, 13px);
  font-weight: var(--fw-semibold, 600);
  text-transform: none;
  letter-spacing: normal;
  color: var(--fg2, #4B5563);
  user-select: none;
  white-space: nowrap;
}

/* ⛔ EL ORDEN DENTRO DE LA CABECERA ES FIJO: RÓTULO · (?) · CHEVRONES (Ricardo, 2026-09-06).
   `DECISION-FUENTE: «el contenido del encabezado debe ser título, (?) si aplica y despues
   chevrones de ordenar.»` Se declara con `order` y no confiando en el marcado, porque el
   marcado lo escribe cada consumidor y aquí hay dos (este gemelo y el kit). */
.exds-tabla-th > *                { order: 4; }
.exds-tabla-th > .exds-th-txt     { order: 1; }
/* ⛔ DOS CLASES DE AYUDA, Y HAY QUE NOMBRAR LAS DOS. `AyudaColumna` es una pieza COMPARTIDA del
   kit y emite `.exds-pl-help` —el nombre del patrón de lista— también cuando vive dentro de esta
   Tabla. Medido el 2026-09-06 en `contactos.html`: con sólo `.exds-th-help` en la lista, el (?)
   caía al comodín (order 4) y quedaba DETRÁS del chevrón, justo el defecto que esta regla viene
   a impedir. *Una regla que enumera clases hereda el mapa de nombres de quien la escribió, y ese
   mapa se rompe en cuanto una pieza compartida cruza de familia.* */
.exds-tabla-th > .exds-th-help,
.exds-tabla-th > .exds-pl-help    { order: 2; }
.exds-tabla-th > .exds-th-ic      { order: 3; }

/* La alineación de una columna la comparten cabecera y celda: **una sola fuente**, declarada
   donde se declaran las columnas. En la cabecera es del eje flex; en la celda, de su texto —
   y NUNCA `justify-self`, por la razón que cuenta `.exds-td` más abajo. */
.exds-tabla-th[data-align="center"] { justify-content: center; }
.exds-tabla-th[data-align="right"]  { justify-content: flex-end; }

/* La casilla de seleccion no lleva rotulo, asi que su relleno es el del control, no el del
   texto. VUELVE AL CATALOGO el 2026-10-03 (ver la cabecera del archivo): aqui vive la casilla de
   «Seleccionar todos». Va CENTRADA en su pista de 40 px, la misma linea que las casillas de las
   filas (antes llevaba 14 px a la izquierda y quedaba 4 px corrida), y SIN relleno vertical: el
   alto de la cabecera lo ponen los rotulos, la casilla nunca lo hace crecer. */
.exds-tabla-th[data-col="sel"] { padding: 0; justify-content: center; }

/* ── ORDENAR POR COLUMNA ──────────────────────────────────────────────────────────────
   **Una cabecera ordenable es un CONTROL**, y hasta el 2026-08-20 sólo lo era para el ratón:
   tenía `onClick` y `aria-sort` —o sea, se ANUNCIABA como ordenable— y no se podía alcanzar
   con Tab ni activar con Enter. *Anunciar una capacidad que el teclado no puede ejercer es
   peor que no anunciarla.* El marcado y la conducta: `README.md` y `tabla.js`. */
.exds-th-sort { cursor: pointer; }

/* ⛔ EL HOVER VUELVE A `fg2` PORQUE EL REPOSO VOLVIÓ A `fg3` (2026-08-20). Este hover es UN
   ESCALÓN sobre el reposo, no un valor propio: cuando el reposo subió a `fg2` (19-ago) tuvo
   que subir a `fg1` para seguir significando algo, y al volver el reposo a `fg3` vuelve con
   él. Quedarse en `fg1` sería un salto de dos peldaños —de 4,96 a 16,79— y pondría el rótulo
   en 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 — y
   esto no da error ni gate rojo, sólo deja de contestar.* */
.exds-th-sort:hover { color: var(--fg2, #4B5563); }

/* ── EL CHEVRÓN DE ORDEN ──────────────────────────────────────────────────────────────
   ⛔ SIN ORDENAR VA `--color-notice`, NO `--fg4` (Ricardo, 2026-08-20).
   `DECISION-FUENTE: Ricardo 2026-08-20 — «de acuerdo con tus recomendaciones»`, sobre
   `plans/decisiones/_decision_tres_pendientes_2026-08-20.html`. `--fg4` #B8BDC7 daba **1,53:1**
   sobre el encabezado, contra el **3:1** que WCAG 1.4.11 pide a un control.

   ⛔ CIFRA ACTUALIZADA EL 2026-08-20, segunda vuelta del mismo día: con el encabezado de vuelta
   en `bg-subtle` #F2F4F6, `--color-notice` #6E7378 da **4,34:1**, no 3,90. El TOKEN no cambia —
   cambió el SUELO sobre el que se mide. Sigue pasando el 3:1 y queda un escalón POR DEBAJO del
   rótulo (`fg3`, 4,96): el nombre es el dato, la flecha es el afford.
   *Una cifra de contraste no es del color: es de la PAREJA. Dejarla escrita junto al token que
   no cambió es como acaban las fichas diciendo números que ya no salen.*

   ⛔ Y EL CHEVRÓN **ACTIVO** SE QUEDA EN `fg2`, LO QUE INVIERTE UNA RELACIÓN — declarado, no
   olvidado. Hasta el 20-ago activo (6,15) y rótulo (6,15) pesaban IGUAL; con el rótulo en
   `fg3` (4,96) el activo pasa a **6,85**, o sea POR ENCIMA del nombre de su columna. No es el
   mismo caso que el neutro: 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 apoyándose expresamente en esa flecha.
   *Si el único indicador que queda de un dato pesa menos que su etiqueta, el dato desaparece.*
   Anotado por si Ricardo quiere bajarlo.

   ⛔ **Nunca triángulos de texto** (`▲▼↕`): se ven como «línea + triángulo» y no son del
   sistema (Ricardo, 2026-07-24). Son SVG de lucide, trazo 1.5 — están en el README. */
.exds-th-ic {
  display: inline-flex;
  color: var(--color-notice, #6E7378);
}
.exds-th-ic[data-activo="si"] { color: var(--fg2, #4B5563); }

/* ── LA AYUDA «?» DE LA COLUMNA — sólo en columnas con ESTADO ─────────────────────────
   ⛔ HOMOLOGADO 2026-08-16 a `COMPONENTE_AYUDA_COLUMNA.md`. Antes era el carácter «?» dentro de
   un círculo con borde de 1px — los DOS inventos que esa ficha prohíbe por su nombre: una
   tipografía haciendo de ícono, y una caja que el ícono ya trae dentro. Ricardo, el mismo día:
   *«el ? es icono de lucide, no carácter y no lleva ningún relleno, todo outline»*. Ahora es
   `circle-help` de la fuente única, sin borde y sin fondo, un peldaño por debajo del nombre de
   la columna: el nombre es el dato y la ayuda es un servicio (no-negociable 7-bis).

   ⛔ Y EL REPOSO PASA DE `--fg4` A `--color-notice` (Ricardo, 2026-08-20: «decide tú el color
   del texto, chevron y (?)»). El principio no cambia —sigue por DEBAJO del nombre de la
   columna—; 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 botón**, al que WCAG 1.4.11 exige
   3:1. Reprobaba, y `COMPONENTE_AYUDA_COLUMNA.md` lo tenía escrito como hallazgo ABIERTO —
   escalado a Ricardo por ser decisión de peso visual— desde esa misma mañana.
   ⛔ 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 ese día pesaban 3,90 contra 1,53. *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.* */
.exds-th-help {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* ⛔ CAJA 18×18, DIBUJO 14×14 — HOMOLOGADO A `.ha-qico` Y `.exds-pl-help` (Ricardo,
     2026-09-17: «18x18 pero debemos asegurarnos de que tanto el gemelo como el componente
     deben estar homologados y también producción debe estar homologado en todas las
     instancias»). Hasta hoy esta caja medía 14×14 —el mismo tamaño que su dibujo, sin aire
     alrededor— mientras sus dos hermanas (la de la lista, la de la Mesa) ya usaban 18×18.
     Tres piezas para el mismo servicio con tres cajas distintas no eran una variante: era
     que nadie las había puesto una al lado de la otra. */
  width: 18px;
  height: 18px;
  /* ⛔ EL HUECO VISIBLE BAJA DE 6px A 4px — SE HOMOLOGA A `.exds-signo-info` DEL COTIZADOR
     (Ricardo, 2026-09-21: «se ve mal alineado verticalmente y demasiado pegado… la
     referencia es el cotizador, campo "Nombre sin apellidos"»). RETIRA el target de 6px
     fijado el 2026-09-17 (ver historia debajo) — no por estar mal medido entonces, sino
     porque Ricardo decidió una referencia nueva y más estricta: la MISMA pieza («signo
     de ayuda») debe respirar igual en las dos apps, aunque sean dos componentes
     distintos (`COMPONENTE_AYUDA_COLUMNA.md` § «NO es el signo de ayuda de una
     etiqueta»). En el cotizador (`.exds-signo-info` dentro de `.lzc-name-label`) no hay `gap`
     de por medio: caja = dibujo = 14×14 y `margin-left: 4px` da el hueco completo.
     Aquí la caja es 18×18 (2px de aire interno por lado) y el CONTENEDOR (`.exds-tabla-th`)
     ya pone `gap: var(--space-1-5, 6px)` entre el rótulo y esta caja — así que el margen
     tiene que DESCONTAR ese gap para que el hueco final (visible, al dibujo) sea 4:
     hueco de CAJA necesario = 4 − 2(aire interno) = 2; margin-left = 2 − 6(gap) = **−4**.
     Medido en el navegador (Playwright, headers reales `.exds-tabla-th`/`.exds-pl-col`):
     gap-a-dibujo pasa de 6px a **4px**, centro vertical sin cambio (el flex ya centraba
     por `align-items:center`, ajeno a `line-height`/`vertical-align`).
     La misma resta, con el gap de cada contenedor, vive en `.exds-pl-help`
     (`pantalla-lista.css`, gap 5 → margen −3). `.ha-qico` (`backoffice.css`, Mesa de
     Control) NO se tocó en este cambio — queda declarado en `COMPONENTE_AYUDA_COLUMNA.md`
     como deuda pendiente de homologar al mismo 4px.

     ⛔ HISTORIA — EL 6px DE 2026-09-17 (Ricardo: «18x18 pero (…) deben estar
     homologados»). Con caja 18 y dibujo 14 hay 2px de aire a cada lado dentro de la
     propia caja, así que el hueco de CAJA que hacía falta para que el hueco VISIBLE (al
     dibujo) fuera 6px era 6 − 2 = 4, y margin-left = 4 − 6 = −2. (Antes de ESE cambio
     decía margin-left:0 sobre una caja de 14×14.) Medir el hueco al BORDE de la caja es
     medir el defecto equivocado: el ojo ve dónde empieza el trazo, no dónde empieza su
     caja invisible — ESO sigue vigente; sólo cambió el número objetivo. */
  margin-left: -4px;
  /* ⛔ EL CENTRADO VERTICAL NO DEPENDÍA DE ESTO EN LA CABECERA (flex + align-items:center
     ya centra sin mirar `line-height`), PERO LA PIEZA TAMBIÉN VIVE FUERA DE UN FLEX —
     dentro de texto corrido (p. ej. una etiqueta de cajón). Ahí SÍ importa: sin
     `line-height:1` hereda el 1.5 del párrafo y el ícono flota sobre una caja de línea
     más alta que él, y sin `vertical-align` cae a `baseline` (el pie del texto, no su
     centro). Declarado aquí para que la pieza se centre SOLA, sin depender de que su
     contenedor sea flex (Ricardo, 2026-09-21). */
  line-height: 1;
  vertical-align: middle;
  border: 0;
  background: none;
  padding: 0;
  color: var(--color-notice, #6E7378);
  cursor: pointer;
  flex-shrink: 0;
  transition: color var(--motion-fast, 120ms) var(--ease-default, cubic-bezier(0.32, 0.72, 0, 1));
}
.exds-th-help:hover { color: var(--fg1, #0F1419); }
.exds-th-help:focus-visible { outline: none; box-shadow: inset 0 0 0 1px var(--color-border-focus, #6E7378); }
/* ⛔ LA MEDIDA DEL DIBUJO VIVE EN LA HOJA, NO EN EL ATRIBUTO (2026-09-17).
   El kit React le pasa el tamaño por propiedad (Icono size=14) y por eso aquí no hacía
   falta... hasta que alguien monta la Tabla en vanilla: el consumidor escribe su propio
   svg y, sin esta regla, el dibujo no hereda los 14px de su caja — el contenedor mide
   14 y el svg se sale. Es la forma de propagación 1 de GOBIERNO.md 0-ter, la misma que
   caza lint_icono_sin_medida.js: una medida que vive en una propiedad del kit no
   sobrevive al copiado a mano. Su hermana .exds-pl-help ya la tenía (pantalla-lista.css).
   Detectado al montar el (?) en la tabla de Amortización, primer consumidor vanilla. */
.exds-th-help svg { width: 14px; height: 14px; display: block; }

/* ══ LA FILA ══════════════════════════════════════════════════════════════════════════
   **Densidad, no aire:** 67px. El backoffice trabaja con volumen y el aire del portal aquí
   estorba. El modo lectura baja a 44 — ver más abajo.

   ⛔ LAS COLUMNAS SÍ ENCOGEN — HASTA SU CONTENIDO Y NI UN PÍXEL MÁS, Y DE AHÍ LA TABLA SE
   ARRASTRA (Ricardo, 2026-08-23; enunciado literal arriba, en `.exds-tabla`).
   **Corrige la redacción anterior de este mismo párrafo**, que decía «la tabla NO encoge sus
   columnas» y obligaba a declarar cada columna `minmax(<mínimo en px>, 1fr)`. Aquello resolvía
   el amontonamiento —medido en un teléfono: con `1fr` a secas, `$458,14` y `$75,000` acababan
   pegados e ilegibles— pero fijaba el suelo **a ojo, en píxeles tecleados**, y un suelo tecleado
   se queda corto el día que aparece un importe más largo del que se imaginó. Ahora el suelo lo
   pone **el contenido real**, que es el único que no se equivoca.
   ⛔ **Consecuencia para quien declara `--exds-cols`, y es la trampa que queda viva:** el suelo
   automático sólo protege a las pistas con mínimo INTRÍNSECO (`1fr`, `minmax(min-content, 1fr)`).
   Una pista de **píxeles fijos** (`150px`) o con mínimo en píxeles (`minmax(130px, 1fr)`) hace lo
   que se le mandó: se queda en 130 px, y el contenido más largo se sale de su pista y se monta
   sobre la vecina. **No se corta —la regla se cumple— pero se lee mal.** Por eso el README dice
   ahora que un ancho en píxeles es sólo para contenido de largo CONOCIDO (un ícono, una casilla,
   una fecha corta), nunca para dinero ni para nombres.
   Y se descartó **esconder una columna** cuando hay un cajón abierto: haría que la tabla
   dijera cosas distintas según haya cajón o no, y entonces lo que el analista compara depende
   de lo que tenga abierto.
   ⛔ **NUNCA una pista `auto`** — o fijo, o `fr`. Aquí la cabecera y **cada fila son rejillas
   independientes**, así que una pista `auto` se calcula por separado en cada una y las
   columnas dejan de estar alineadas. Medido el 2026-08-05: con `minmax(52px, auto)` en la
   primera columna, la segunda celda empezaba en **417 px en la cabecera, 437 en una fila
   normal y 507 en la de «Opción de compra»** — tres columnas distintas fingiendo ser una.
   *Es invisible en una captura si las columnas vecinas son anchas: se ve como una tabla «un
   poco descuadrada», no como un error.* */
.exds-tr {
  display: grid;
  grid-template-columns: var(--exds-cols, 1fr);
  grid-template-columns: subgrid;
  grid-column: 1 / -1;
  align-items: center;
  min-height: 67px;
  border-bottom: 1px solid var(--color-border-subtle, #E5E7EB);
  background: transparent;
  cursor: default;
  transition: background var(--motion-fast, 120ms) var(--ease-default, cubic-bezier(0.32, 0.72, 0, 1));
}

/* **La fila entera es la superficie**, no un botón dentro de ella. El cursor de mano lo lleva
   la fila, y sólo cuando de verdad abre algo: en modo lectura no abre nada y se queda en
   `default`. Lo pone quien monta (o `tabla.js`) con `data-clicable="si"`. */
.exds-tr[data-clicable="si"] { cursor: pointer; }

/* El hover se queda en `bg-subtle`, el mismo gris del encabezado.
   `DECISION-FUENTE: Ricardo, 2026-08-20 — «ignora la colisión, no pasa nada»` — hover y cromo
   nunca están en la misma fila, así que el hover se lee contra las filas BLANCAS de al lado. */
.exds-tr:hover { background: var(--color-bg-subtle, #F2F4F6); }

/* LA FILA ACTIVA — mientras su detalle está abierto en el cajón lateral.
   Un escalón más hundida que el hover — `--color-bg-sunken` — por decisión de Ricardo del
   2026-09-02 con el visual delante (la cita, en la propia regla). Contrato completo: `PATRON_PANTALLA_LISTA.md` § La fila activa. La pone y quita quien
   monta (o el motor del patrón que la instancie), nunca el marco del cajón. */
.exds-tr[aria-current="true"] { background: var(--color-bg-sunken, #E6E8EA); } /* un escalón bajo el hover · DECISION-FUENTE: Ricardo, 2026-09-02 — con la hoja `_decision_fila_activa_2026-09-02.html` delante (ANTES · A el mismo gris del hover · B un escalón hundido), eligió «b». */
/* ⛔ LA MARCA DE LA COINCIDENCIA NUNCA COMPARTE FONDO CON SU FILA (v1.17 · 2026-10-04).
   Principio del canon, componentes/search/search.css (§ EL RESALTADO DE LA COINCIDENCIA):
   «dentro de un panel cuyas filas YA se resaltan con --color-bg-subtle, un <mark> del
   mismo tono desaparece; ahí se pasa --color-bg-sunken vía --exds-search-mark-bg».
   Medido: la marca por defecto es bg-subtle (242,244,246) y el hover de la fila TAMBIÉN
   (242,244,246) -> 1,000:1, la coincidencia se esfumaba justo bajo el puntero. Se aplica la
   regla del canon tal cual: hover -> la marca baja a bg-sunken; y por el mismo principio la
   fila activa (ya bg-sunken) -> la marca sube a bg-subtle. La activa va DESPUÉS: gana
   cuando coinciden activa + hover (la fila queda bg-sunken). */
.exds-tr:hover .exds-search-mark { --exds-search-mark-bg: var(--color-bg-sunken, #E6E8EA); }
.exds-tr[aria-current="true"] .exds-search-mark { --exds-search-mark-bg: var(--color-bg-subtle, #F2F4F6); }

/* ══ HOVER FILA + AVATAR · v1.16 · 2026-10-12 ══════════════════════════════════════════
   Regla de lista (PL-HOV-1): si la fila TRAE avatar, el hover de fila lo oscurece a la
   vez (una sola señal). Si no hay avatar, solo el fondo de fila — ningun otro hover.
   DECISION-FUENTE: Ricardo, 2026-10-12 — «el hover de la fila tambien se acompana con
   hover en el avatar (si es que la lista lo contiene, si no, no se activa ningun otro
   hover) esto para homologar lo que hicimos con rh».
   FILA COMPARTIDA (data-fila-compartida=si · PL-HOV-2): ademas, si hay
   `[data-destino]`, al apuntarlo se apagan fondo Y avatar (solo la pastilla).
   ⛔ No filter:brightness — pinta rectangulo detras del radio. Inset sigue la forma.
   ⛔ Nunca un hover de avatar aparte del de fila. */
:is(.exds-tabla, .exds-pl-row) :is(.exds-avatar, .exds-pl-av) {
  transition:
    box-shadow var(--motion-fast, 120ms) var(--ease-default, cubic-bezier(.32, .72, 0, 1)),
    border-color var(--motion-fast, 120ms) var(--ease-default, cubic-bezier(.32, .72, 0, 1));
}
/* GATE-ALLOW: rgba(15,20,25,.18) y rgba(15,20,25,.10) · velos de oscurecido en hover (avatar .18, pastilla .10), no un token: se oscurece lo que haya debajo (gris o color) y los dos hovers van alineados (Ricardo, 2026-10-12 «estos 2 hovers tienen que estar alineados»).
   Fila bajo el puntero y no sobre un destino → fondo + avatar (si existe), gris O color.
   DECISION-FUENTE: Ricardo, 2026-10-12 — «si, solo cuando aparezca» (el de color tambien).
   Contorno: solo quien=otro (el tuyo va sin borde); si solo oscurecemos por dentro, el
   borde default queda mas claro que el relleno y la silueta se rompe (2026-10-12). */
:is(.exds-tr, .exds-pl-row):hover:not(:has([data-destino]:hover)):not(:has([data-destino]:focus-visible)) :is(.exds-avatar, .exds-pl-av) {
  box-shadow: inset 0 0 0 999px rgba(15, 20, 25, 0.18);
}
:is(.exds-tr, .exds-pl-row):hover:not(:has([data-destino]:hover)):not(:has([data-destino]:focus-visible)) :is(.exds-avatar, .exds-pl-av):not([data-quien="tu"]) {
  border-color: var(--color-border-control, #8B92A1);
}
/* Destino secundario: apaga el fondo de fila (el avatar ya no se oscurece por el :not de arriba). */
:is(.exds-tr, .exds-pl-row):hover:has([data-destino]:hover),
:is(.exds-tr, .exds-pl-row):hover:has([data-destino]:focus-visible) {
  background: transparent;
}
:is(.exds-tr, .exds-pl-row)[aria-current="true"]:hover:has([data-destino]:hover),
:is(.exds-tr, .exds-pl-row)[aria-current="true"]:hover:has([data-destino]:focus-visible) {
  background: var(--color-bg-sunken, #E6E8EA);
}
/* El destino llena la celda: toda la columna del día es el segundo destino, no solo la pastilla.
   También en la fila de PantallaLista (`.exds-pl-row`): sin esto el relleno de la celda abría la ficha
   en lugar del día (RH › Asistencia, verificador-canon 2026-10-27; el resto del destino ya cubría las dos). */
.exds-tabla[data-fila-compartida="si"] .exds-td:has(> [data-destino]),
.exds-pl-row .exds-pl-cell:has(> [data-destino]) {
  align-self: stretch;
  display: flex;
  align-items: stretch;
  justify-content: center;
  padding-block: 0;
}
/* Destino: clic en toda la celda; hover VISUAL solo en la pastilla (nunca el rectángulo). */
/* GATE-ALLOW: padding 2px 4px · holgura mínima del destino dentro de una celda que llena entero; no corresponde a un escalón --space-* y no cambia la altura de la fila. */
:is(.exds-tabla, .exds-pl-row) [data-destino] {
  -webkit-appearance: none;
  appearance: none;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  width: 100%;
  height: 100%;
  min-width: 0;
  min-height: 100%;
  margin: 0;
  padding: 2px 4px;
  border: 0;
  border-radius: 0;
  background: transparent;
  box-shadow: none;
  font: inherit;
  color: inherit;
  -webkit-tap-highlight-color: transparent;
}
:is(.exds-tabla, .exds-pl-row) [data-destino]:hover,
:is(.exds-tabla, .exds-pl-row) [data-destino]:active,
:is(.exds-tabla, .exds-pl-row) [data-destino]:focus {
  background: transparent;
  border: 0;
  box-shadow: none;
  outline: none;
  color: inherit;
}
:is(.exds-tabla, .exds-pl-row) [data-destino] .exds-badge {
  transition: box-shadow var(--motion-fast, 120ms) var(--ease-default, cubic-bezier(.32, .72, 0, 1));
}
:is(.exds-tabla, .exds-pl-row) [data-destino]:hover .exds-badge,
:is(.exds-tabla, .exds-pl-row) [data-destino]:focus-visible .exds-badge {
  box-shadow: inset 0 0 0 999px rgba(15, 20, 25, 0.10);
}
:is(.exds-tabla, .exds-pl-row) [data-destino]:focus-visible {
  outline: none;
  box-shadow: inset 0 0 0 2px var(--color-border-focus, #6E7378);
  border-radius: var(--radius-sm, 6px);
}
/* El renglón secundario de un destino de PantallaLista se enciende con él (antes vivía en la hoja
   del patrón: gate_patron_pinta, 2026-10-14). */
:is(.exds-tabla, .exds-pl-row) [data-destino]:hover .exds-pl-dos-b,
:is(.exds-tabla, .exds-pl-row) [data-destino]:focus-visible .exds-pl-dos-b { color: var(--fg1, #0F1419); }

/* ⛔ LA FILA SELECCIONADA VA **DESPUÉS** DEL HOVER A PROPÓSITO, y no es cosmética: los dos
   selectores tienen la misma especificidad, así que gana el último. En el kit este fondo iba
   **en línea** y por tanto ganaba SIEMPRE al hover; con el orden invertido, pasar el ratón por
   una fila ya marcada la despintaría.
   *Al mudar estilo en línea a una hoja, el orden de escritura hereda la prioridad que antes
   daba el atributo — y ése es el defecto silencioso de toda mudanza.* */
.exds-tr[data-sel="si"] { background: var(--color-bg-subtle, #F2F4F6); }

.exds-tr:focus-visible { outline: none; box-shadow: inset 0 0 0 1px var(--color-border-focus, #6E7378); }

/* ══ LA CELDA ═════════════════════════════════════════════════════════════════════════
   ⛔ LA CELDA SE ESTIRA A SU PISTA Y ALINEA CON `text-align` — **NUNCA con `justify-self`**. Y
   esto fue un DEFECTO REAL que llevaba vivo en el kit: ponía `justify-self: start`, que para
   una columna izquierda es lo natural de escribir.

   **Un hijo de rejilla con `justify-self: start` se dimensiona a SU CONTENIDO, no a la pista**
   — así que la celda desborda y las tres declaraciones de abajo (`min-width: 0`,
   `overflow: hidden`, `text-overflow: ellipsis`) **no llegan a actuar nunca**: no hay caja que
   recortar. La elipsis que esta pieza declara en su ficha no ocurría.

   MEDIDO en un navegador el 2026-08-20, no deducido: una celda con el nombre de un proveedor
   en una pista de 120 px salía de **304 px** y sin recortar; con `stretch`, 120 px y recortada.

   ⭐ Y ES EL MISMO DEFECTO QUE `field-row` PAGÓ ESA MISMA MAÑANA: *la mitad reconocible del
   arreglo estaba a la vista y faltaba la mitad que la hace funcionar.* Quien revisara el
   archivo encontraba `text-overflow` y seguía.

   ⛔ AQUÍ VIVÍAN LAS TRES DECLARACIONES QUE RECORTABAN EL IMPORTE, Y NO VUELVEN (2026-08-23).
   `DECISION-FUENTE: Ricardo, 2026-08-23 — «el ancho de las columnas se pueda reducir con la`
   `regla fija e inquebrantable de que no se corte ningún contenido…»`
   Eran `min-width: 0`, `overflow: hidden` y `text-overflow: ellipsis`. Se leían como tres
   cosas y eran una sola: **el mínimo automático de un elemento de rejilla es su `min-content`,
   y queda ANULADO si el elemento lleva `min-width: 0` o un `overflow` distinto de `visible`.**
   Con ellas puestas, la pista podía encoger por debajo del texto y la celda lo tapaba; sin
   ellas, **la propia rejilla se niega a bajar de ahí** — no hay que vigilar nada, no cabe el
   descuido. Por eso las dos que quedan se escriben **explícitas aunque sean el valor inicial**:
   son la regla, no una omisión.
   La factura, vista en pantalla el 2026-08-23: una columna de 110 px pintó `$1,984,203.45` como
   `$1,984,20…`. *Un importe recortado no es un importe mal alineado: es otro número, y se lee
   como si fuera cierto.* Un nombre con elipsis se nota; un importe con elipsis, no.
   ⚠ `white-space: nowrap` SE QUEDA, y ahora carga más peso que antes: es lo que hace que el
   `min-content` de la celda sea **la línea entera** y no la palabra más larga. Quitarlo no
   «permitiría respirar»: haría que el texto se partiera en dos renglones dentro de una fila de
   67 px, que es otra manera de esconder contenido. */
.exds-td {
  padding: 13px 12px;
  font-size: var(--fs-body, 14px);
  color: var(--fg1, #0F1419);
  justify-self: stretch;
  min-width: auto;
  overflow: visible;
  white-space: nowrap;
}
/* v1.21 (llegó como v1.19) · 2026-10-19 · la pastilla no se encoge ni invade el chevrón (GOBIERNO §0-ter forma 8).
   El desborde es del contenedor (`.exds-tabla { overflow-x: auto }`), nunca de `.exds-badge`. */
.exds-td:has(.exds-badge) { min-width: max-content; }
.exds-td .exds-badge { flex-shrink: 0; width: max-content; max-width: none; }
.exds-td[data-align="center"] { text-align: center; }
.exds-td[data-align="right"]  { text-align: right; }
/* v1.10 (2026-10-02) · LA CELDA DE UN CÓDIGO O IDENTIFICADOR (VIN, RFC, CURP, CLABE, folio, placa, UUID, referencia) — la regla GENERAL del catálogo
   (COMPONENTE_BOTONCOPIAR.md § «El identificador que no cabe»; Ricardo, 2026-10-02 — «cuando un código o identificador no cabe en su celda por el
   ancho hay que dejarlo incompleto con tres puntos e integrar un globo tinta y el botón de copiar»). Es la ÚNICA excepción a «ninguna celda recorta» (v1.4):
   la celda opta con data-codigo=si, su contenido es .exds-codigo-copiable (elipsis + globo si se recorta + copiar del valor entero) y su columna declara el
   ancho que un código típico pide: si el código no cabe se recorta él, nunca el botón ni la fila. contain inline-size le quita a la celda el mínimo de
   contenido (el suelo de la v1.4 la ensancharía hasta el código entero y la elipsis nunca ocurriría). */
.exds-td[data-codigo="si"] { min-width: 0; overflow: hidden; contain: inline-size; }

/* v1.19 (2026-10-14) · LA COLUMNA DE TEXTO QUE ENCOGE (regla dura 18 de design-system/README.md; Ricardo, 2026-10-08 — «Primero, siempre se va a buscar acortar el texto.
   Si esto no funciona o no es suficiente, se utiliza la opción del globo.»). Un nombre, una razón social, una descripción: TEXTO, no código. Cuando la tabla no cabe en su
   tarjeta, la columna de texto cede: UNA línea, «…» y globo de tinta SÓLO si se recorta. La celda opta con data-texto=encoge y el texto va en un elemento con data-tip (el
   texto entero) + data-tip-pos + data-tip-si-recorte dentro de un .exds-tip-wrap (el mecanismo de tooltip.js); en la celda de dos líneas (.exds-pl-dos) ese elemento es
   .exds-pl-dos-a. contain inline-size le quita a la celda su mínimo de contenido (si no, el suelo de la v1.4 la ensancharía hasta el texto entero y la tabla se saldría
   de la tarjeta) y min-width es el PISO: la columna nunca desaparece (80 px de caja = 56 de texto; --exds-texto-min lo cambia por tabla). LAS CELDAS DE ESTADO (pastilla),
   LAS ACCIONES Y EL CHEVRÓN NO OPTAN: siguen en su min-content y nunca se recortan. El wrap de tooltip.js es inline-flex sin tope: sin el flex con min-width 0 crece con el
   texto y la elipsis nunca ocurre (mismo porqué que .exds-filacontraparte-txt > .exds-tip-wrap). */
.exds-td[data-texto="encoge"] { min-width: var(--exds-texto-min, 80px); overflow: hidden; contain: inline-size; text-overflow: ellipsis; }
.exds-td[data-texto="encoge"] > .exds-pl-dos { min-width: 0; }
.exds-td[data-texto="encoge"] .exds-tip-wrap { display: flex; min-width: 0; max-width: 100%; }
.exds-td[data-texto="encoge"] [data-tip-si-recorte] { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* v1.20 (2026-10-14 · Δ8-1, verificador-canon): CADA LÍNEA de la celda cede igual, también la de abajo (.exds-pl-dos-b: «Hasta 31 dic 2025», un folio, un contexto). La línea de abajo lleva su propio
   .exds-tip-wrap + data-tip (su texto entero) + data-tip-pos + data-tip-si-recorte, igual que la de arriba: «…» y globo de tinta SÓLO si se recorta (regla dura 18). Esta regla la deja en una
   línea con «…» aunque el consumidor olvide el atributo; el globo, no: el globo sólo sale con data-tip-si-recorte. */
.exds-td[data-texto="encoge"] .exds-pl-dos-b { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }


/* ⛔ EL DINERO VA EN CIFRAS TABULARES Y ALINEADO A LA DERECHA, Y LO TRAE LA TABLA **DE
   FÁBRICA** — no se lo pone la pantalla desde fuera. Sin cifra tabular los dígitos bailan de
   fila en fila y las columnas dejan de compararse en vertical, que es lo ÚNICO que una tabla
   de dinero tiene que hacer bien. Es prop de COLUMNA (`numerica`), no CSS del consumidor:
   antes cada consumidor lo forzaba por su cuenta con CSS externo, y el que se olvidaba no veía
   un error — veía una tabla que se lee peor.
   ⚠ La ALINEACIÓN a la derecha no está aquí dentro a propósito: es `data-align="right"`, y va
   en la misma declaración de columna. Son dos atributos porque hay cifras que no son dinero
   (un folio, un año) y dinero que se centra en papel. Quien declara una columna de dinero pone
   **los dos**, y así lo dice el README. */
.exds-td[data-num="si"] { font-variant-numeric: tabular-nums; }

/* ÉNFASIS DE COLUMNA: el número que el lector busca. **Una sola por tabla** — si todo pesa,
   nada pesa. Se decide donde se declaran las columnas, que es donde se sabe cuál importa. */
.exds-td[data-fuerte="si"] { font-weight: var(--fw-semibold, 600); }

/* FILA DE HITO (Ricardo, 2026-08-05 · COMPONENTE_TABLA.md § El estado fila de hito): la
   Firma de un calendario, la Opción de compra, un total. Se distingue por el PESO del
   texto (600 + fg1), NUNCA con relleno: el gris de fondo ya significa el paso del ratón. */
.exds-tr.es-hito .exds-td { font-weight: var(--fw-semibold, 600); color: var(--fg1, #0F1419); }

/* ══ MODO LECTURA · cuando la tabla no lleva a ningún lado (Ricardo, 2026-08-05) ═══════
   **Sin columna de afordancia** (el chevrón / la palomita de fin de fila), **sin fila clicable**, sin cursor de mano. Para
   tablas que sólo se leen: un calendario de pagos, un extracto, un desglose.

   **Por qué es un modo de la pieza y no CSS del consumidor.** Nació de la pantalla de detalle
   del plan del cotizador: la tabla pintaba una flecha que no lleva a ninguna parte y el
   consumidor la escondía por CSS desde fuera (`visibility: hidden` sobre el último hijo). *Un
   componente no debe necesitar que le tapen partes; si el caso existe, es un prop.* Con la
   afordancia oculta por fuera, además, la columna seguía ocupando su pista de 40 px: aquí se
   quita del reparto de verdad, no se disimula.

   **La densidad, y por qué 44.** El canon sólo declaraba 67px, pensados para un analista que
   trabaja con volumen todo el día. Una tabla que sólo se lee es otra cosa: un calendario de
   hasta 49 renglones que un cliente recorre **una vez**, muchas veces en un teléfono, donde la
   diferencia entre 44 y 67 es ver 12 renglones o ver 7. *No es una excepción del cotizador: es
   la densidad que le faltaba al sistema.*

   ⛔ Y EL TAMAÑO DE LETRA SE ESCRIBE EN LARGO, NUNCA con la abreviatura `font:`. `font: 500
   12px …` parece inofensiva y **reinicia `font-variant-numeric` a `normal`**: apaga las cifras
   tabulares de toda una tabla de dinero, y por especificidad también gana al peso de la
   columna con énfasis. Dos props del catálogo anuladas por una comodidad de sintaxis, y
   ninguna se ve «rota»: se ve como un diseño distinto. Lo cazó MEDIR, no leer. */
.exds-tabla[data-modo="lectura"] .exds-tr { min-height: 44px; }
.exds-tabla[data-modo="lectura"] .exds-td {
  padding: 0 12px;
  font-size: var(--fs-caption, 12px);
  font-weight: 500;
}
/* v1.10 (2026-10-02) · ELEGIR ≠ NAVEGAR. Una tabla de lectura cuyas filas ELIGEN qué muestra el detalle al lado (la lista + visor
   de la pantalla de detalle): cursor de elección y UNA fila aria-current (el fondo ya es el global de la fila actual). Sin columna de
   afordancia —no lleva a ningún lado— y con la densidad 44 del modo lectura intacta. */
.exds-tabla[data-modo="lectura"][data-elige="si"] .exds-tr { cursor: pointer; }

/* ══ LAS DOS COLUMNAS DE SERVICIO ═════════════════════════════════════════════════════
   La casilla de selección (VUELVE al catálogo el 2026-10-03 — ver `.exds-tabla-th[data-col="sel"]`)
   y la afordancia de fin de fila.
   ⛔ El chevrón es **PLANO**: sin círculo, sin fondo y sin cambio en hover — sólo indica
   «abre». Quien lo rodea de un círculo convierte una señal en un botón que no existe.

   ⛔ Y ES UN SVG DE LUCIDE, NUNCA UN CARÁCTER (Ricardo, 2026-08-23).
   `DECISION-FUENTE: Ricardo, 2026-08-23 — «sí homologar lucide — esto no debería ser pregunta,`
   `ya es regla».` Hasta hoy esta celda pintaba los caracteres `›` y `✓`, que es **exactamente**
   el defecto que el chevrón de ORDEN pagó el 2026-07-24 («nunca triángulos de texto: no son del
   sistema»), un nivel más abajo y en la misma pieza. Ahora: `chevron-right` y `check` de la
   fuente única, 17×17, trazo 1.5 — el mismo tamaño que ya declaraba la ficha.
   *Un carácter no es un ícono: lo dibuja la fuente del sistema operativo, así que cambia de
   forma, de grosor y de altura según el equipo — y ninguna de esas tres cosas las decidió
   nadie aquí.* */
.exds-tabla-sel {
  display: flex;
  justify-content: center;
  align-items: center;
  padding: 13px 0;
}
.exds-tabla-afford {
  display: flex;
  justify-content: center;
  align-items: center;
  color: var(--fg4, #B8BDC7);
}
/* ══ LA FILA BLOQUEADA (2026-10-03) ═══════════════════════════════════════════════════
   DECISION-FUENTE: Ricardo, 2026-10-03 — «1 casillas si» (selección en grupo) y la matriz de la
   cosecha, que manda el estado «bloqueada» sin apagar el clic de la fila.
   En el lugar de la casilla va un CANDADO de 16 px con su motivo en un globo (Tooltip del
   catalogo). Es distinto de «no seleccionable sin motivo», que deja la celda VACIA: el candado
   dice POR QUE no se puede, y por eso nunca va sin motivo.
   ⛔ LA FILA SIGUE ABRIENDO SU CAJON. El estado bloqueada es de la CASILLA, no de la fila: quien
   no puede seleccionar un pago sigue pudiendo abrirlo y leerlo. Por eso no hay opacidad, ni
   cursor prohibido, ni apagado de clic en la fila — lo apagaba el prop disabled, y ese sigue
   siendo otra cosa (una fila entera inerte).
   ⛔ NO CAMBIA EL ALTO DE LA FILA: el candado (16 + 2 + 2 = 20 px) cabe en el mismo hueco que la
   casilla (20 px), dentro de la pista de 40 px y del relleno de 13 px de la celda.
   El color es --fg3, el gris de servicio: es un glifo, no el dato. */
.exds-tabla-candado {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-0-5, 2px);
  border-radius: var(--radius-sm, 6px);
  color: var(--fg3, #5C6B7A);
}
.exds-tabla-candado svg { width: 16px; height: 16px; display: block; flex-shrink: 0; }
.exds-tabla-candado:focus-visible { outline: none; box-shadow: inset 0 0 0 1px var(--color-border-focus, #6E7378); }

/* ══ EL VACÍO ═════════════════════════════════════════════════════════════════════════
   La caja que rodea al `EmptyState` canónico. **La tabla NO redibuja el vacío**: monta la pieza
   del catálogo (homologado 2026-08-01, decisión de Ricardo · Gate A; antes era un `<div>`
   plano), así que hereda cualquier cambio futuro de esa pieza. Marcado en el README.
   ⛔ `grid-column: 1 / -1` desde el 2026-08-23: `.exds-tabla` es una rejilla, así que este
   bloque es un hijo suyo y **sin esto se metería en la primera columna** — el vacío saldría
   estrujado en 46 px al lado de las demás pistas. Vale para cualquier hijo directo que no sea
   cabecera, fila o esqueleto. */
.exds-tabla-vacio { padding: 28px 16px; grid-column: 1 / -1; }

/* ══ LA CARGA ═════════════════════════════════════════════════════════════════════════
   **DRAW-ALLOW: Skeleton.** El loader de la Tabla se dibuja a la medida —barras por celda con
   la forma exacta de las columnas— y **NO** monta el `Skeleton` genérico del catálogo, que es
   un bloque avatar+líneas pensado para tarjetas y listas. Son formas distintas a propósito:
   meter el bloque genérico dentro de una tabla se ve mal. Excepción legítima y declarada
   (decisión de diseño de Ricardo, Gate A · 2026-08-01). *Si algún día una tercera superficie
   redibuja estas barras, ahí conviene extraer un átomo «barra fantasma» compartido; por ahora,
   no.* */
@keyframes exds-tbl-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .55; } }

/* El esqueleto es la TERCERA rejilla de esta hoja, así que es subrejilla igual que la cabecera
   y la fila. No es simetría por gusto: si el esqueleto reparte sus columnas de otra manera, la
   lista **da un salto** en el momento exacto en que llegan los datos. */
.exds-tabla-skel {
  display: grid;
  grid-template-columns: var(--exds-cols, 1fr);
  grid-template-columns: subgrid;
  grid-column: 1 / -1;
  align-items: center;
  min-height: 67px;
  border-bottom: 1px solid var(--color-border-subtle, #E5E7EB);
}
.exds-tabla[data-modo="lectura"] .exds-tabla-skel { min-height: 44px; }
/* ESQUELETO = MISMO TAMANO QUE EL CONTENIDO (Ricardo, 2026-10-02). Con cantidad conocida el esqueleto
   trae esas filas y nada mas. Sin ella (primera carga) este relleno lo lleva a llenar el techo: su
   contenido natural excede cualquier ventana y el contenedor lo acota (la lista se desplaza). */
.exds-tabla-skel-techo { grid-column: 1 / -1; min-height: 100vh; }
.exds-tabla-skel-c { padding: 0 12px; }
.exds-tabla-skel-b {
  display: block;
  height: 9px;
  border-radius: 3px;
  background: var(--color-bg-subtle, #F2F4F6);
  animation: exds-tbl-pulse 1.2s ease-in-out infinite;
}

/* ══ LA SELECCION EN GRUPO: LA BARRA (2026-10-03) ════════════════════════════════════
   PIEZA HERMANA de la tabla, no parte de ella: la tabla solo emite que filas estan marcadas; la
   barra dice cuantas son, cuanto suman y ofrece quitarlas o actuar sobre ellas.
   ⛔ ES CLARA, no oscura. DECISION-FUENTE: Ricardo, 2026-10-03 — «1 casillas si»: entra la barra
   del prototipo (una Card con el texto a la izquierda y dos botones), y se CORRIGE la ficha, que
   decia «fondo ink slate». Una barra oscura sobre una pantalla clara pesaba mas que la propia
   tabla; la clara es un renglon mas de la pantalla.
   Es una Card de relleno 12 con una fila dentro (relleno izquierdo 8 para que el texto alinee con
   el aire de la Card). El texto va en 600 con cifras tabulares; el renglon de detalle, en gris
   de servicio. Todo el texto lo pone quien monta — la pieza no sabe que se cuenta ni en que unidad.
   La zona de texto es una region de estado (role status): si una fila SALE de la seleccion porque
   dejo de ser seleccionable, el aviso se anuncia sin que nadie lo busque. */
.exds-tabla-barra { display: flex; align-items: center; gap: var(--space-3, 12px); padding-left: var(--space-2, 8px); }
.exds-tabla-barra-texto { display: flex; flex-direction: column; gap: var(--space-0-5, 2px); min-width: 0; flex: 1; }
.exds-tabla-barra-tit {
  font-size: var(--fs-body, 14px);
  font-weight: var(--fw-semibold, 600);
  color: var(--fg1, #0F1419);
  font-variant-numeric: tabular-nums;
}
.exds-tabla-barra-det { font-size: var(--fs-body-sm, 13px); color: var(--fg3, #5C6B7A); }

/* ══ LA LECTURA A SANGRE (2026-10-03) ═════════════════════════════════════════════════
   La forma de la tabla de LECTURA para listas dentro de un modal o un cajon: sin rotulo, sin
   encabezado visible, sin marco, con las lineas de borde a borde del contenedor.
   DECISION-FUENTE: Ricardo, 2026-10-03 — «Me gusta A, pero no me gusta el rotulo de esa tabla»
   (hoja _decision_lista_en_modal: de borde a borde, sin encabezado ni rotulo, sin marco) y «3»
   (hoja _decision_filas_modal_firma: el renglon de dos niveles).
   ⛔ NO ES OTRA PIEZA: es la tabla de lectura con data-sangre. Por eso la cabecera SIGUE EN EL
   DOM, escondida solo a la vista (recortada, nunca display none): quien lee con un lector de
   pantalla oye las columnas, y la tabla conserva su semantica.
   ⛔ SU ALTO ES PROPIO, NO LOS 44 DEL MODO LECTURA NI LOS 67 DEL DE ACCION. El renglon de dos
   niveles tiene tres alturas posibles segun lleve o no aviso, y un modal no es una pantalla de
   volumen: el alto lo pone el contenido (12 de aire arriba y abajo). La regla de no crecer
   (COMPONENTE_TABLA, NINGUNA CELDA HACE CRECER LA FILA) es de las listas de pantalla; esta
   superficie la declara fuera, con su razon, en la ficha y en gate_alto_fila.
   ⛔ Y EL TEXTO AQUI SE ENVUELVE EN VEZ DE RECORTARSE: en el paso donde alguien firma, un nombre o
   un aviso cortado con puntos suspensivos es un dato tapado. Es la forma que la hoja aprobada
   dibuja; si Ricardo prefiere recorte con globo, es una decision suya.
   El aire lateral lo da --exds-sangre-pad, que vale lo mismo que el relleno lateral del cuerpo del
   modal (28): la tabla se sale de el con margen negativo y su contenido se queda alineado con el
   resto del cuerpo. Es parametro de INSTANCIA, como --exds-cols. */
.exds-tabla[data-sangre="si"] {
  --exds-sangre-pad: 28px;
  position: relative;
  width: auto;
  margin-inline: calc(var(--exds-sangre-pad) * -1);
  border: 0;
  border-top: 1px solid var(--color-border-subtle, #E5E7EB);
  border-radius: 0;
  background: transparent;
}
.exds-tabla[data-sangre="si"] .exds-tabla-head {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}
.exds-tabla[data-sangre="si"] .exds-tr { min-height: 0; }
.exds-tabla[data-sangre="si"] .exds-tr:hover { background: transparent; }
.exds-tabla[data-sangre="si"] .exds-td {
  padding: var(--space-3, 12px) var(--exds-sangre-pad);
  font-size: var(--fs-body, 14px);
  font-weight: 400;
  white-space: normal;
}

/* ══ EL ENCABEZADO OCULTO A LA VISTA (v1.18 · 2026-10-25) ═════════════════════════════
   Hoja plans/decisiones/_decision_cajonfila_sangria_2026-10-25.html, decisión 3, opción B.
   DECISION-FUENTE: Ricardo, 2026-10-25 — «Todo de acuerdo a tus recomendaciones. Estás decisiones salen de 1, del visual anterior. Te respondo lo demás: 2,3,4: como recomiendas.»
   La fila de rótulos de la lista se oculta a la vista cuando el subtítulo de la fila del cajón ya nombra las columnas
   («Conceptos» · «Clave, descripción, cantidad e importe»), y se queda visible cuando dos columnas son del mismo tipo
   («Pagos aplicados»: Pagado · Saldo insoluto). Es la decoración que la regla dura 20 («cuanto más adentro, más simple»)
   sacrifica primero.
   LA TÉCNICA ES LA DE LA LECTURA A SANGRE (arriba): la cabecera sigue en la página, recortada a 1 x 1 y nunca con display
   none, así que el lector de pantalla oye las columnas y la tabla conserva su semántica. Lo nuevo es poder pedirla sin el
   resto de la lectura a sangre (marco, líneas de borde a borde, alto propio): la lista enmarcada del cajón conserva su
   recuadro y sus renglones. Cuándo se usa: sólo dentro de una fila abierta de cajón y sólo si el subtítulo de esa fila
   nombra las columnas (la regla de uso vive en la ficha; esta hoja no la impone). */
.exds-tabla[data-encabezado="oculto"] { position: relative; }
.exds-tabla[data-encabezado="oculto"] .exds-tabla-head {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* EL RENGLON DE DOS NIVELES. Nombre y monto en la misma linea (el nombre ocupa el sobrante y el
   monto se alinea a la derecha con cifras tabulares); debajo, en gris, el dato secundario con su
   identificador en la tipografia de cuenta y el boton de copiar; y al final, si lo hay, el aviso
   con su punto de color. */
.exds-tabla-ren-l1 { display: flex; align-items: baseline; gap: var(--space-4, 16px); }
.exds-tabla-ren-nom {
  flex: 1;
  min-width: 0;
  font-size: var(--fs-body, 14px);
  font-weight: 500;
  line-height: 1.4;
  color: var(--fg1, #0F1419);
}
.exds-tabla-ren-monto {
  font-size: var(--fs-num-data, 14px);
  line-height: 1.4;
  color: var(--fg1, #0F1419);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  text-align: right;
}
.exds-tabla-ren-l2 { display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-3, 12px); margin-top: var(--space-0-5, 2px); }
.exds-tabla-ren-sec { font-size: var(--fs-body-sm, 13px); line-height: 1.4; color: var(--fg3, #5C6B7A); }
.exds-tabla-ren-cuenta { display: inline-flex; align-items: center; gap: var(--space-0-5, 2px); }
.exds-tabla-ren-cuenta .exds-copiar { --exds-copiar-size: 14px; }
/* El identificador (una CLABE, un folio) va en la    tipografia que .exds-pl-id del patron de lista, declarada aqui para que la pieza no dependa de
   la hoja de su vecino. */
.exds-tabla-id { font-family: var(--font-sans, "Inter", -apple-system, "system-ui", sans-serif); font-variant-numeric: tabular-nums; font-size: var(--fs-body-sm, 13px); color: var(--fg2, #4B5563); }
.exds-tabla-ren-aviso { margin-top: var(--space-2, 8px); font-size: var(--fs-body-sm, 13px); line-height: 1.45; color: var(--fg2, #4B5563); }

/* ⛔ APAGAR EL MOVIMIENTO NO ES DEJAR SIN SEÑAL (INC-405), y aquí no se deja: el tinte del
   hover **sigue apareciendo** —lo que se apaga es el desvanecido de 120 ms— y las barras
   grises del esqueleto siguen dibujando la forma de la lista que viene. *El pulso era el
   latido, no el mensaje.*
   ⛔ .exds-tabla-skel-b SE NOMBRA APARTE, A PROPÓSITO (2026-09-10): el comodín
   .exds-tabla * ya lo alcanza en cascada —es descendiente de `.exds-tabla` y `!important`
   gana sin importar especificidad—, pero gate_reduced_motion.js no entiende cascada: mide
   que la clase de CADA regla `animation: … infinite` aparezca LITERAL dentro del bloque de
   guarda, no que la gane. Cartera fue la primera pantalla en producción que monta este
   gemelo con datos reales, y esa primera adopción destapó el hueco (deuda 0→1, trinquete de
   verificar.sh). Se nombra la clase para que el lint la vea; el comodín se queda para
   cualquier otra animación futura del componente. */
@media (prefers-reduced-motion: reduce) {
  .exds-tabla *, .exds-tabla-skel-b { transition: none !important; animation: none !important; }
}
