/*!
 * @constructora-garcia/ui — hoja única de estilos
 * Generada por scripts/build-css.mjs a partir del orden declarado en
 * styles.css. No editar a mano: los cambios van en tokens/*.css o en el orden
 * de styles.css, y se recompilan con `npm run build:css`.
 *
 * Importar UNA vez, en el layout raíz de la app:
 *   import '@constructora-garcia/ui/styles.css';
 */

/* ═══ tokens/fonts.css ══════════════════════════════════════════════ */
/* ==========================================================================
   GARCÍA CONSTRUCTORA — Tipografía
   --------------------------------------------------------------------------
   LA UI SIGUE SIN WEBFONTS. Cuerpo y datos usan el stack del sistema
   (`--font-sans`, `--font-mono`): render instantáneo, cero red, y una app que
   se siente parte del sistema operativo. Eso no cambia, y no conviene que
   cambie — es lo que sostiene la densidad que la gente en obra necesita.

   LO QUE SÍ CAMBIA: LA DISPLAY VIENE EMPAQUETADA.

   `--font-display` declaraba `'Century Gothic', 'Jost', 'Futura'` y en la
   práctica caía a `system-ui` en casi todas las máquinas, porque los equipos
   Windows de la empresa no tienen ninguna de las tres. O sea: los títulos ya
   decían querer una voz propia y terminaban con la fuente del sistema, igual
   que cualquier otro producto.

   Ahora Jost —el sustituto libre de Century Gothic, y el que la marca ya
   nombraba— viaja DENTRO de esta hoja, como data URI.

   POR QUÉ INCRUSTADA Y NO COMO ARCHIVO SUELTO. La decisión anterior sacó las
   webfonts por tres motivos, y el más serio era que fallaban en terreno sin
   señal, justo donde la app tiene que funcionar. Un archivo aparte —aunque sea
   del propio dominio— es una petición más que puede no llegar. Incrustada no:
   si esta hoja cargó, la tipografía está. Cero peticiones, cero dependencia de
   red, imposible que se caiga por su cuenta.

   Los otros dos motivos también quedan resueltos: no hay ida a Google Fonts, y
   Jost no es la familia que usa cualquiera — es la que la identidad de esta
   empresa ya declaraba.

   QUÉ PESA. 17,5 kB en woff2, subseteado a latín + acentos del español +
   la puntuación y las cifras que usa la marca, con el eje de peso 100-900
   completo. Menos que el logo en PNG.

   LICENCIA. Jost, SIL Open Font License 1.1 — Copyright 2020 The Jost Project
   Authors. El texto completo está en `assets/fonts/OFL.txt`, y el .woff2
   subseteado del que sale este data URI en `assets/fonts/`.
   ========================================================================== */

@font-face {
  font-family: 'Jost CG';
  font-style: normal;
  /* Variable: un solo archivo cubre de 100 a 900 y evita las cuatro peticiones
     que costaría una familia estática. */
  font-weight: 100 900;
  font-display: swap;
  src: url(data:font/woff2;base64,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) format('woff2-variations');
}

/* ==========================================================================
   LAS CIFRAS — IBM Plex Mono, y por qué esta va como ARCHIVO y no incrustada.

   `--font-mono` se usa en 45 lugares de 30 componentes: DataTable, TreeTable,
   StatCard, ProgressBar, Pagination, el shell. Resolvía al monoespaciado del
   sistema operativo, o sea SF Mono en Mac, Cascadia en Windows 11, Segoe UI
   Mono en Windows 10 y Roboto Mono en el teléfono de terreno. La misma tabla
   de presupuesto se dibujaba con una tipografía distinta según quién la
   abriera, y ninguna era de la marca. La identidad original ya lo tenía
   resuelto: «Datos — IBM Plex Mono», por las cifras tabulares.

   LA DIFERENCIA CON LA DISPLAY, QUE ES DELIBERADA.

   Jost va incrustada en esta hoja porque si falla se pierde la marca, y cuesta
   17,5 kB. Las tres pesos de Plex Mono cuestan 34 kB —el doble— y el base64 no
   comprime: incrustarlas duplicaría el peso de una hoja que BLOQUEA el primer
   pintado. Eso es exactamente el costo que la decisión de sacar las webfonts
   quiso evitar «en terreno sin señal».

   Y acá el respaldo es distinto: si la display no carga, se pierde la voz de
   la marca; si la mono no carga, se ven las cifras en el monoespaciado del
   sistema — que es lo que se ve HOY. El peor caso es el presente, no una
   regresión.

   Por eso: archivo aparte. Y `font-display: OPTIONAL`, no `swap`, que es la
   diferencia que se ve.

   Medido: Plex Mono es 22% MÁS ANGOSTA que el monoespaciado del sistema (24
   muestras de cifras reales —montos, RUT, fechas, porcentajes— a tres tamaños;
   entre -14,6% y -27,3%). Con `swap`, la fuente llega tarde y TODAS las
   columnas de números se encogen de golpe delante del usuario. En una tabla de
   presupuesto ese salto no es un detalle estético: hace desconfiar de la cifra.

   Con `optional` el navegador da una ventana muy corta y, si la fuente no
   llegó, usa el respaldo PARA TODA ESA VISTA y no la cambia. La fuente queda
   en caché y entra desde la navegación siguiente. Resultado: cero salto,
   nunca. En una herramienta que se usa todo el día, la primera carga es la
   única que podría verse con la del sistema.

   TRES PESOS PORQUE EL SISTEMA USA TRES: regular en 2 lugares, medium en 7 y
   semibold en 9. Sintetizar los que faltan deforma una monoespaciada.

   NO SE RECORTARON LAS MINÚSCULAS aunque tentaba: de los 45 bloques en mono
   solo 1 va en versalita, así que sin minúsculas una palabra cambiaría de
   fuente a mitad. Con el juego completo son 34 kB; sin ellas, 26,6.

   LICENCIA. IBM Plex Mono, SIL Open Font License 1.1 — Copyright 2017 IBM Corp.
   con Reserved Font Name "Plex". Texto completo en assets/fonts/OFL-IBMPlexMono.txt.
   ========================================================================== */

@font-face {
  font-family: 'Plex Mono CG';
  font-style: normal;
  font-weight: 400;
  font-display: optional;
  src: url("./assets/fonts/plex-mono-400-latin.woff2") format("woff2");
}
@font-face {
  font-family: 'Plex Mono CG';
  font-style: normal;
  font-weight: 500;
  font-display: optional;
  src: url("./assets/fonts/plex-mono-500-latin.woff2") format("woff2");
}
@font-face {
  font-family: 'Plex Mono CG';
  font-style: normal;
  font-weight: 600;
  font-display: optional;
  src: url("./assets/fonts/plex-mono-600-latin.woff2") format("woff2");
}

/* ═══ tokens/colors.css ═════════════════════════════════════════════ */
/* ==========================================================================
   GARCÍA CONSTRUCTORA — Color tokens
   --------------------------------------------------------------------------
   Paleta base del Manual de Identidad Corporativa:
     Azul corporativo  PANTONE 660 C  #2871B8
     Café corporativo  PANTONE 411 C  #6F6462
     Café oscuro       PANTONE 411 C  #4F3F3F
     Gris 80% negro                   #333333
     Gris 40% negro                   #999999

   REGLA DE JERARQUÍA DE MARCA (v2):
   El Manual de Identidad es la fuente de verdad. El azul #2871B8 encabeza
   marca, interfaces y producto. El navy #1F3864 NO es un segundo azul de
   marca: es la tinta institucional heredada de la papelería y plantillas
   Office, y queda acotada a documentos ejecutivos (ver tokens/office.css).
   Nunca usar #1F3864 y #2871B8 como acentos alternativos en la misma pieza.

   ACCESIBILIDAD (v2): los estados semánticos se recalibraron para cumplir
   WCAG 2.1 AA (>=4,5:1) sobre las dos superficies del sistema: papel cálido
   #F6F4F3 y tarjeta blanca. Los valores anteriores (ámbar #D4922A 2,64:1 y
   verde #2F8F5B 4,04:1) fallaban y se retiraron del uso textual.
   ========================================================================== */

:root {
  /* ---- Azul corporativo (primary) ---------------------------------- */
  --blue-50:  #eef4fb;
  --blue-100: #d6e6f5;
  --blue-200: #aecced;
  --blue-300: #7fb0e2;
  --blue-400: #5191d2;
  --blue-500: #2871b8;   /* PANTONE 660 C — brand azul */
  --blue-600: #21609f;
  --blue-700: #1c4f83;
  --blue-800: #1a4269;
  --blue-900: #15375a;

  /* ---- Café corporativo (warm neutral / secondary) ----------------- */
  --cafe-50:  #f6f4f3;
  --cafe-100: #ebe7e6;
  --cafe-200: #d8d1cf;
  --cafe-300: #beb4b1;
  --cafe-400: #8e827f;   /* contorno de control: 3,72:1 sobre tarjeta, 3,39:1 sobre
                            página y 3,03:1 sobre fondo hundido. Era #968a87 y sobre
                            hundido quedaba en 2,72:1 — un campo dentro de una barra
                            de filtros no se distinguía del fondo. */
  --cafe-500: #6f6462;   /* PANTONE 411 C — café corporativo */
  --cafe-600: #5b5250;
  --cafe-700: #4f3f3f;   /* café oscuro */
  --cafe-800: #3c3130;
  --cafe-900: #2a2322;

  /* ---- Neutral grays ----------------------------------------------- */
  --gray-0:   #ffffff;
  --gray-50:  #f7f8f9;
  --gray-100: #eef0f2;
  --gray-200: #e1e4e7;
  --gray-300: #cbd0d4;
  --gray-400: #8d8d8d;   /* 3,32:1 sobre tarjeta y 3,03:1 sobre página. Era #999999 y no
                            llegaba a 3:1: se usa como cuadrado de estado en tablas
                            —«sin plazo», «borrador», «no aplica»— y eso no es
                            decoración, es información. NO alcanza para texto. */
  --gray-500: #6e7378;
  --gray-600: #4f5458;
  --gray-700: #3a3e41;
  --gray-800: #333333;   /* gris 80% negro */
  --gray-900: #1d1f21;

  /* ---- Estados semánticos — recalibrados AA ------------------------ */
  /* -500 = relleno sólido (texto blanco encima). -700 = texto sobre -50. */
  --success-50:  #eaf5ee;
  --success-100: #cce8d6;
  --success-500: #26754a;   /* conforme / aprobado — 5,64:1 con blanco */
  --success-700: #17532f;   /* 8,12:1 sobre success-50 */

  --warning-50:  #fbf3e3;
  --warning-100: #f6e4c2;
  --warning-500: #9c6814;   /* por vencer / atención — 4,77:1 con blanco */
  --warning-700: #5e3d07;   /* 8,87:1 sobre warning-50 */

  --danger-50:   #fbecea;
  --danger-100:  #f6d3ce;
  --danger-500:  #b3271a;
  --danger-600:  #9f2216;   /* vencido / crítico — 6,52:1 con blanco */
  --danger-700:  #8f1e14;   /* 7,74:1 sobre danger-50 */

  --info-50:  var(--blue-50);
  --info-500: var(--blue-500);
  --info-700: var(--blue-700);

  /* Ámbar y verde originales — conservados SOLO para gráficos y fills
     decorativos donde no hay texto encima. Prohibidos en texto y en
     indicadores de estado operacional. */
  --chart-amber-legacy: #d4922a;
  --chart-green-legacy: #2f8f5b;

  /* ================================================================== */
  /* SEMANTIC ALIASES — referenciar estos en producto, no las ramps      */
  /* ================================================================== */

  /* ── EL AZUL TIENE UN TRABAJO POR TONO ─────────────────────────────────
     MEDIDO en las 21 pantallas de arranque: `--blue-500` —el mismo píxel,
     rgb(40,113,184)— hacía SIETE trabajos distintos. Fondo de botón (26 veces),
     tinta de botón (75), texto e ícono (18), superficie e indicador (16), regla
     de acento (4), navegación (4) y serie de gráfico (37). Y hasta SEIS papeles
     distintos en una misma pantalla —la ficha y el checklist—.
     Con eso, la misma tinta dice «apretame», «estás acá», «soy un dato», «soy
     información» y «acá empieza otra sección». Cuando un color significa cinco
     cosas, no significa ninguna: la jerarquía se pierde, y es la razón por la que
     una pantalla llena de azul se siente plana aunque cada pieza esté bien.

     LOS CANALES, y cada uno con su lugar:

       ACCIÓN      azul sólido. Lo que se aprieta y lo que está elegido. Nada más.
       DATO        el azul del gráfico y del indicador, dentro del gráfico.
       INFORMACIÓN superficie suave y un ícono, nunca otro botón azul.
       PAPEL       la regla que estructura la hoja NO es azul: es café. Ya estaba
                   escrito —«el azul es interacción, el café es papel y tinta»— y
                   había seis acentos de 3px en azul contradiciéndolo.

     Los valores no cambian al declarar los canales: cambia que ahora se puedan
     nombrar, verificar y decidir de una vez. `npm run canales` cuenta los papeles
     por pantalla y falla si el azul sólido pinta algo que no se aprieta.        */
  --accion-fondo:         var(--blue-500);
  --accion-fondo-hover:   var(--blue-600);
  --accion-fondo-activo:  var(--blue-700);
  --accion-tinta:         var(--text-on-brand);
  /* EL AZUL CUANDO LA ACCIÓN ES TEXTO, y no un fondo sólido. Medido en navegador
     el 28-08-2026, con el botón «?» de ayuda montado en el marco real: el azul de
     marca como TEXTO da 4,13:1 sobre `--topbar-surface` y sobre `--surface-sunken`
     —los dos son `--cafe-100`, #ebe7e6—, y el mínimo de WCAG AA para texto normal
     es 4,5:1. Pasa raspando sobre la hoja (4,62) y bien sobre tarjeta (5,07); el
     que falla es justo el fondo hundido.

     Y ahí es donde el sistema lo pinta dos veces: el rótulo de un `Button ghost`,
     que es lo que va en la ranura `acciones` de la barra superior, y la flecha de
     orden en la cabecera de `DataTable`/`DataGrid` —`_tablabase.jsx` pinta el
     `<thead>` con `--surface-sunken`—.

     `--blue-600` da 5,28 sobre el hundido, 5,91 sobre la hoja y 6,48 sobre la
     tarjeta: pasa en las tres. No es un azul nuevo: es el mismo que ya usan
     `--accion-fondo-hover` y `--brand-primary-hover`, así que la familia no se
     ensancha. El fondo sólido NO cambia — el azul de marca sigue siendo
     `--blue-500` donde lleva tinta blanca encima, que es donde el contraste nunca
     estuvo en duda.

     ⚠️ NO usar `--brand-primary` como texto chico sobre fondo hundido. Para eso
     está esta línea. */
  --accion-texto:         var(--blue-600);
  /* La marca de 3px que dice «estás acá»: navegación, fila elegida, paso en
     curso. Es estado de interacción, así que sí es azul. */
  --accion-marca:         var(--blue-500);
  /* Selección y contexto: superficie suave, no un bloque sólido. */
  --seleccion-fondo:      var(--blue-50);
  --seleccion-tinta:      var(--blue-700);
  /* El acento que estructura una hoja de papel: cabecera de reporte, borde
     superior de una tarjeta, franja de una cola de trabajo. Café, no azul. */
  --acento-papel:         var(--cafe-700);
  /* La marca de un hito o de un dato que NO trae estado propio. Un punto de línea
     de tiempo es historia —lo que ya pasó—, no algo que se apriete: iba en el azul
     de acción y pasa a tinta. Los estados de dominio siguen usando su propia marca
     (`--est-*-mark`), que es la que sí significa algo. */
  --dato-marca:           var(--cafe-500);

  /* Brand */
  --brand-primary:        var(--blue-500);
  --brand-primary-hover:  var(--blue-600);
  --brand-primary-active: var(--blue-700);
  --brand-secondary:      var(--cafe-500);
  --brand-ink:            var(--cafe-700);
  /* El ámbar de la marca. No estaba declarado y dos apps lo pedían igual:
     `var(--brand-amber, #d98b1f)` en cuatro lugares de `lecciones-aprendidas` y
     `cgarcia-paneles`, siempre con la misma reserva. Cuatro escrituras idénticas
     del mismo valor son un token que falta, no cuatro decisiones sueltas.
     Se declara con el valor que ya usaban: ninguna app cambia de aspecto y todas
     pasan a seguir al sistema. Es marca decorativa —una viñeta, la barra bajo un
     título—; para TEXTO de aviso está `--fg-warning`, que sí está medido. */
  --brand-amber:          #d98b1f;

  /* Superficies — papel cálido, no gris frío */
  --surface-page:    var(--cafe-50);
  --surface-card:    var(--gray-0);
  --surface-sunken:  var(--cafe-100);
  --surface-raised:  var(--gray-0);
  --surface-inverse: var(--cafe-700);
  --surface-brand:   var(--blue-500);
  --surface-hover:   var(--cafe-100);
  --surface-selected:var(--blue-50);

  /* ── EL PLANO QUE FLOTA ───────────────────────────────────────────────
     Modal, drawer, popover y la barra inferior de teléfono. Vale lo mismo que
     `--surface-card` y NO es un duplicado: lo que separa a los dos es el ROL, y
     el rol decide con qué sombra se pinta. Una tarjeta va con `--shadow-card`;
     esto va con `--shadow-floating`, que se ve. Sin este nombre la elección de
     sombra la hacía cada componente por gusto —`--shadow-lg` porque «se ve
     mejor»— y por eso había tarjetas ordinarias con la sombra de un modal.
     @kind color */
  --surface-floating:var(--gray-0);

  /* Texto — todos AA sobre papel y tarjeta */
  --text-strong:   var(--gray-900);   /* 15,4:1 */
  --text-body:     var(--gray-800);   /* 11,5:1 */
  --text-muted:    var(--cafe-600);   /*  6,9:1 — reemplaza gray-500 */
  --text-subtle:   #6b6664;           /*  5,2:1 — reemplaza gray-400 */
  --text-on-brand: var(--gray-0);
  --text-on-dark:  var(--gray-0);
  --text-link:     var(--blue-600);

  /* Bordes y divisores — hairlines cálidos */
  --border-subtle:  #e8e2df;
  --border-default: #d8d0cd;
  --border-strong:  var(--cafe-400);
  --border-brand:   var(--blue-500);
  --border-focus:   var(--blue-500);

  /* Escala categórica de gráficos — MÁXIMO 6 SERIES.
     Derivada de la paleta, ordenada por luminosidad, no por matiz. En la v4
     el café y el gris se distinguían solo por temperatura: en proyector de
     sala de reuniones y en impresión blanco y negro quedaban idénticos.
     Acá cada par de series está a 29 puntos de luma o más, así que el gráfico
     se sigue leyendo en escala de grises.

     Luma sRGB de cada tono:  1→103  2→66  3→140  4→30  5→169  6→201

     El orden importa: los cuatro primeros superan 3:1 contra la tarjeta
     blanca, así que un gráfico de hasta 4 series no necesita nada más. Del
     quinto en adelante el tono es demasiado claro para sostenerse solo y
     lleva --chart-outline como hairline de separación.

     Regla de asignación (ver components/charts/*.prompt.md):
     la serie principal SIEMPRE va en --chart-1 (azul de marca); las
     secundarias siguen el orden; los colores semánticos no entran acá. */
  --chart-1: var(--blue-500);   /* azul de marca — serie principal */
  --chart-2: var(--cafe-700);
  --chart-3: var(--cafe-400);
  --chart-4: var(--gray-900);
  --chart-5: var(--blue-400);   /* era blue-300: 2,22:1 contra la superficie del
                                   gráfico. Una barra que no se distingue del fondo
                                   no es una serie, es un hueco. */
  --chart-6: var(--cafe-500);   /* era cafe-200: 1,47:1, el peor de la escala. Es el
                                   café de marca, que además le devuelve croma a la
                                   sexta serie. */
  --chart-outline: var(--border-default);

  /* Cromo del gráfico — rejilla, ejes y series de referencia */
  --chart-grid:       var(--border-subtle);
  --chart-grid-soft:  var(--cafe-100);
  --chart-axis:       var(--text-subtle);
  --chart-plan:       var(--cafe-400);  /* programado / referencia */
  --chart-proyeccion: var(--blue-400);  /* proyección — siempre discontinua (2 3), que
                                           es lo que la distingue de --chart-5 cuando
                                           comparten tono */
  --chart-area:       rgba(111, 100, 98, .07);

  /* Superposición de visor — galería de fotos y lightbox.
     El fondo del visor es café 900 con alfa, no negro: la foto de obra se
     mide contra el mismo neutro cálido que el resto del sistema. Los controles
     son blanco al 14% porque van sobre la foto, que puede ser clara u oscura,
     y ese es el único valor que funciona en las dos. */
  --overlay-fondo:     rgba(42, 35, 34, .92);
  --overlay-control:   rgba(255, 255, 255, .14);
  --overlay-pie:       rgba(42, 35, 34, .82);

  /* Chrome — sidebar y topbar.
     El chrome es MARCO, no contenido: envuelve el dato, no compite con él.
     Un chrome oscuro sobre contenido claro se lee siempre como pesado y le
     roba jerarquía a la cifra, que es lo único que la persona vino a mirar.
     Por eso la navegación va clara y el azul solo marca dónde estás.
     Para volver a un chrome oscuro se cambian ESTAS 12 LÍNEAS y nada más:
     ningún componente escribe un hex de chrome por su cuenta. */
  /* EL MENÚ VA UN PLANO ATRÁS DEL CONTENIDO, NO ADELANTE.
     `--nav-surface` era `--surface-card`, o sea blanco puro, y las tarjetas
     también: el menú quedaba siendo la cosa más brillante de la pantalla, un
     paso ADELANTE del contenido que acompaña. Es cromo, no contenido. Se midió
     en postventa con el menú claro puesto: riel en rgb(255,255,255) contra una
     página en #f6f4f3 — el riel más claro que el papel, y separados sólo por
     una línea de 1 px. Se veía plano, y no era impresión: era la jerarquía al
     revés.

     Con el papel cálido el menú se apoya y las tarjetas blancas saltan encima,
     que es el orden correcto. Sigue en tonos claros: es el mismo papel de la
     página, no un gris nuevo.

     El borde sube de `--border-subtle` a `--border-default` porque ahora separa
     dos planos parecidos, y una línea apenas visible no alcanza para eso.

     ── 18-08-2026: EL MENÚ DEJA DE SER EXACTAMENTE EL MISMO PAPEL ──────────
     El párrafo de arriba dice «es el mismo papel de la página», y ahí estaba el
     problema: siendo el MISMO valor, la separación entre menú y lienzo quedaba
     entera a cargo del borde, y ese borde medía **1,39:1** contra el papel.
     Medido, no impresión: dos planos que no se distinguen y una línea que casi
     no está. Es lo que se lee como «le falta contraste».

     La respuesta se mantiene en claro —es el pedido explícito del dueño— y no
     vuelve al blanco puro, que es justamente lo que este bloque descartó:

       · superficie `--cafe-100` #ebe7e6, un punto más profunda que el lienzo
         #f6f4f3, que es `--cafe-50`. 1,12:1 entre planos. Sigue siendo papel y sigue estando DEBAJO de las
         tarjetas blancas, así que la jerarquía que este bloque defiende no se
         toca. El texto del menú encima queda en 10,49:1.
       · borde `--cafe-300` #beb4b1, de 1,39:1 a 1,85:1. Ahora la línea acompaña a una
         diferencia de plano en vez de ser la única separación.
       · tinta tenue `--cafe-600` #5b5250 en vez de `--text-subtle`: los números 01–05 y el
         rótulo de grupo son mono de 10,5 px con letra espaciada, y a ese tamaño
         5,16:1 se lee gris. Sube a 6,18:1 sobre la superficie nueva, y sigue
         siendo claramente secundaria contra el 10,29:1 de la etiqueta.

     Y LOS TRES SALEN DE LA ESCALA DE MARCA, no de valores nuevos. La primera
     versión de este ajuste inventó #eee9e6 / #c4b8b3 / #5f5a58, que resultaron
     estar a un pelo de `--cafe-100` / `--cafe-300` / `--cafe-600` —los pasos
     reales de PANTONE 411 C— y además medían un poco PEOR. Inventar un valor
     que ya existe es la copia local naciendo adentro del propio sistema. Ahora
     el menú está hecho del café de la empresa, que es exactamente el trabajo
     que este archivo le asigna: «el azul es interacción, el café es papel y
     tinta». El menú es papel.

     Siguen siendo alias —lo correcto— pero de la ESCALA DE MARCA y no de la
     superficie de página: el menú es su propio plano. Aliasarlo al de la página
     es lo que lo hizo invisible. */
  --nav-surface:         var(--cafe-100);      --nav-border:          var(--cafe-300);
  --nav-text:            var(--text-body);
  --nav-text-muted:      var(--cafe-600);
  /* BLANCO, NO PAPEL. Esto valía `--surface-page`, que es EL MISMO VALOR que
     `--nav-surface` desde que el menú se mudó al papel cálido: el hover pasó a
     pintar el fondo del mismo color que ya tenía, o sea a no hacer nada. Quedó
     así al mover uno de los dos y no el otro, y no se vio porque un hover que
     no pasa nada no se distingue de un hover que todavía no llegó.
     Blanco es la respuesta coherente con el modelo que el bloque de arriba
     declara —el papel se apoya y lo blanco salta encima—: el item bajo el
     cursor se levanta del papel igual que una tarjeta. */
  --nav-item-hover:      var(--surface-card);
  --nav-item-active-bg:  var(--blue-50);
  --nav-item-active-fg:  var(--blue-700);
  --nav-item-active-bar: var(--blue-500);
  /* EL BADGE ERA `--gray-100` Y DEJÓ DE VERSE. #eef0f2 sobre el papel cálido del
     menú da 1,1:1 — la pastilla desaparecía y la cifra quedaba flotando como un
     exponente al costado del ícono—, y además es un gris FRÍO sobre una superficie
     cálida, así que aun viéndose leía de otra familia. Es el mismo defecto que
     tuvo `--nav-item-hover`, que valía lo mismo que la superficie del menú y por
     lo tanto el hover no hacía nada: cuando el menú se mudó al café, los tokens
     que se apoyaban en la escala gris se quedaron atrás en silencio.
     `--cafe-200` da 1,23:1 y es de la misma familia. @kind color */
  --nav-badge-bg:        var(--cafe-200);
  --nav-badge-fg:        var(--gray-600);
  /* ── LA BARRA SUPERIOR, EN CAFÉ OSCURO ────────────────────────────────
     El café es el color de la marca —PANTONE 411 C— y no existía en pantalla:
     todo era azul, blanco y gris. Vivía en los tokens y no en la vista.

     La regla que lo ordena: EL AZUL ES INTERACCIÓN, EL CAFÉ ES PAPEL Y TINTA.
     Si algo se aprieta, se selecciona o está activo, va azul. Si es la materia
     sobre la que se trabaja —el documento, la cifra, la estructura— va café.
     Nunca compiten porque nunca hacen lo mismo.

     La barra es lo primero que se ve y era blanca, indistinguible de cualquier
     producto. Una banda café oscuro cruzando el ancho completo ancla la
     aplicación entera. Y funciona AHORA porque el riel quedó claro: banda
     oscura arriba, superficies de papel abajo. Con el menú café que había
     antes habrían sido dos oscuros peleándose. */
  /* EL PLANO OSCURO DEL SHELL DE MARCA, que hasta ahora era un literal.
     `components/marca/AppShell.tsx` tenía `const SIDE = "#1e1917"` y trece
     literales más alrededor: la superficie del menú de las nueve apps del
     ecosistema estaba escrita a mano en un componente, o sea fuera del alcance de
     cualquier token, de cualquier medición de contraste y de cualquier ajuste
     futuro.
     NO se cambia el valor —cambiarlo alteraría lo que hoy ven nueve apps en
     producción—, se DECLARA. Y queda dicho que es más oscuro que `--cafe-900`
     (#2a2322) a propósito: el menú de marca es el plano más profundo de la
     interfaz, por debajo de la barra superior. @kind color */
  --nav-marca-surface:   #1e1917;

  /* ── LA BARRA SUPERIOR SE ACLARA ──────────────────────────────────────
     Era `--cafe-900` (#2a2322): una franja oscura permanente en cada pantalla
     de cada app del sistema. El menú lateral se había mudado al papel claro y
     la barra no entró en esa mudanza, así que quedaron dos cromos que no se
     parecen —el de marca claro, el del sistema oscuro— para la misma empresa.
     Estaba medido y escrito en `docs/MARCOS-ACTUALES.md` como hallazgo antes de
     tocarse.

     Ahora la barra es DEL MISMO PLANO QUE EL MENÚ, `--cafe-100`. No es una
     tercera superficie: es que el cromo sea uno solo. La L que forman el menú y
     la barra queda del papel cálido, el lienzo va más claro por debajo
     (`--surface-page`, --cafe-50) y las tarjetas blancas quedan encima. Tres
     planos que suben en claridad hacia el contenido, que es lo que hace que la
     profundidad se lea sin una sola sombra.

     El café oscuro NO se retira: sigue siendo tinta de marca —`--text-data`,
     `--cafe-700` para un momento puntual—. Lo que se retira es que ocupe una
     franja fija en toda pantalla.

     Medido sobre #ebe7e6: tinta 10,29:1 · tinta tenue 6,18:1. La norma pide
     4,5:1. @kind color */
  --topbar-surface:      var(--cafe-100);
  --topbar-border:       var(--cafe-300);
  --topbar-fg:           var(--nav-text);
  --topbar-fg-muted:     var(--nav-text-muted);

  /* ── LA TINTA DE LOS DATOS ────────────────────────────────────────────
     Folios, códigos de vivienda, cifras en tabular: eran negro casi puro. En
     café oscuro se leen como un documento técnico impreso y no como texto de
     pantalla. Es sutil y es de los que se sienten sin poder explicarlos.
     11,2:1 sobre tarjeta: por encima de lo que pide la norma. */
  --text-data:           var(--cafe-800);

  /* Rótulos en versales y la regla bajo un título de sección: eran grises
     genéricos. En café son marca sin gritar. */
  --rotulo-fg:           var(--cafe-600);
  --regla-seccion:       var(--cafe-400);

  /* Fondos de estado */
  --bg-success: var(--success-50);
  --bg-warning: var(--warning-50);
  --bg-danger:  var(--danger-50);
  --bg-info:    var(--info-50);

  --fg-success: var(--success-700);
  --fg-warning: var(--warning-700);
  --fg-danger:  var(--danger-700);
  --fg-info:    var(--info-700);
}

/* ═══ tokens/typography.css ═════════════════════════════════════════ */
/* ==========================================================================
   GARCÍA CONSTRUCTORA — Typography tokens
   Display: Jost (Century Gothic substitute) · UI: IBM Plex Sans · Data: IBM Plex Mono
   ========================================================================== */

:root {
  /* ---- Families ---------------------------------------------------- */
  /* Display / marca — títulos, portadas, wordmark.
     `Jost CG` viaja dentro de `tokens/fonts.css` como data URI, así que está
     siempre: no depende de qué tenga instalado la máquina ni de que llegue una
     petición de red. Century Gothic queda PRIMERO porque es la familia de la
     identidad y quien tenga licencia debe verla; Jost es su sustituto libre y
     el que va a resolver en casi todos los equipos.
     Antes esta lista terminaba cayendo a `system-ui` en la práctica —ninguna
     de las tres existe en los Windows de la empresa— y los títulos se veían
     como los de cualquier otro producto. */
  --font-display: 'Century Gothic', 'Jost CG', 'Jost', 'Futura', system-ui, sans-serif;

  /* UI de aplicación — stack de sistema. Resuelve a Segoe UI Variable en Windows 11,
     Segoe UI en Windows 10, SF Pro en iOS/macOS y Roboto en Android. Es la única
     forma de ser nativo en los cinco dispositivos donde corre el sistema: pedir
     Segoe UI a un iPhone en terreno da fallback, no coherencia Microsoft. */
  --font-sans: system-ui, -apple-system, 'Segoe UI Variable Text', 'Segoe UI',
               Roboto, 'Helvetica Neue', Arial, sans-serif;

  /* Alternativa técnica cuando se requiera render idéntico entre plataformas
     (ej. capturas para documentación o comparación de maquetas). */
  --font-sans-fija: 'Inter', 'IBM Plex Sans', var(--font-sans);

  /* Datos — solo cifras, fechas, folios y códigos. Ver «Regla de tipografía». */
  /* `Plex Mono CG` va PRIMERO: es la que hace que una tabla de presupuesto se
     vea igual en el Mac de oficina, el Windows de obra y el teléfono de
     terreno. Se carga como archivo con `font-display: optional` (ver
     fonts.css): si no llega a tiempo, esa vista entera usa el monoespaciado del
     sistema y NO se cambia a mitad. Es a propósito — Plex Mono es 22% más
     angosta, y cambiarla en caliente encogería todas las columnas de números
     delante del usuario. El respaldo no es una degradación: es lo que se veía
     antes. */
  --font-mono: 'Plex Mono CG', ui-monospace, 'SFMono-Regular', 'Cascadia Mono', 'Segoe UI Mono',
               'IBM Plex Mono', Menlo, Consolas, monospace;

  /* ---- Weights ----------------------------------------------------- */
  --fw-light:     300;  /* @kind font */
  --fw-regular:   400;  /* @kind font */
  --fw-medium:    500;  /* @kind font */
  --fw-semibold:  600;  /* @kind font */
  --fw-bold:      700;  /* @kind font */

  /* ---- Type scale (1.250 major-third on a 16px base) --------------- */
  /* Debajo de la escala: solo ejes de gráfico y marcadores dentro de una
     fila de tabla. Nunca texto de lectura — 9px no se lee en una pantalla de
     obra. Existe como token para que esos casos sean tres y declarados, y no
     un 8.5 suelto en cada gráfico. */
  --fs-micro: 9px;
  --fs-2xs:  11px;
  --fs-xs:   12px;
  --fs-sm:   13px;
  --fs-md:   14px;   /* base UI text in dense software */
  --fs-base: 16px;
  --fs-lg:   18px;
  --fs-xl:   20px;
  --fs-2xl:  24px;
  --fs-3xl:  30px;
  --fs-4xl:  38px;
  --fs-5xl:  48px;
  --fs-6xl:  64px;

  /* ---- Line heights ------------------------------------------------ */
  --lh-tight:   1.1;   /* @kind other */
  --lh-snug:    1.25;  /* @kind other */
  --lh-normal:  1.5;   /* @kind other */
  --lh-relaxed: 1.65;  /* @kind other */

  /* ---- Letter spacing ---------------------------------------------- */
  --ls-tight:  -0.02em;  /* @kind other */
  --ls-normal: 0;

  /* ── TÍTULOS DE PANTALLA ─────────────────────────────────────────────────
     Century Gothic es la tipografía de la marca y hasta ahora no hacía casi
     nada: los títulos eran la sans un poco más grande, y por eso las pantallas
     se veían correctas y anónimas. Es la observación que abrió `MODERNIZAR.md`
     —«se ve como una página hecha en un día»— y este es el primero de los tres
     pasos que ahí se proponen.

     Se declaran como CONJUNTO y no como tamaños sueltos, a propósito: un título
     no es «30 px», es una familia, un peso, un interletrado y una altura de
     línea que funcionan juntos. Suelto, cada app vuelve a decidir y volvemos al
     punto de partida.

     El interletrado va NEGATIVO y crece con el tamaño. Century Gothic es una
     geométrica de ojo grande: en cuerpos grandes se abre y las palabras se
     desarman. Cerrarla un punto es lo que la hace ver dibujada en vez de
     estirada — y es exactamente lo que ya se hace con las versales de los
     rótulos, sólo que al revés. */
  --titulo-font:   var(--font-display);
  --titulo-fw:     var(--fw-semibold);
  --titulo-lh:     var(--lh-tight);
  --titulo-ls:     -0.02em;    /* pantalla — 30 px */
  --titulo-ls-lg:  -0.03em;    /* portada o cifra grande — 38 px y más */

  /* La bajada que acompaña al título. Va en la sans y en un tono secundario:
     dos tipografías distintas separan mejor que dos tamaños de la misma. */
  --bajada-font:   var(--font-sans);
  --bajada-fw:     var(--fw-regular);
  --bajada-lh:     var(--lh-normal);        /* @kind other */
  --ls-wide:   0.04em;   /* @kind other */
  --ls-wider:  0.12em;   /* @kind other */

  /* ---- Semantic roles ---------------------------------------------- */
  --text-display-font: var(--font-display);  /* @kind font */
  /* EL ENCABEZADO OPERATIVO VA EN SANS, y este token es donde se decide. Era
     `--font-display` y con eso el rol «heading» arrastraba la voz de marca a
     cada título de sección y de tarjeta. Display es para el H1, los momentos de
     marca y las cifras grandes; ahí está `--text-display-font`. */
  --text-heading-font: var(--font-sans);     /* @kind font */
  --text-body-font:    var(--font-sans);     /* @kind font */
  --text-data-font:    var(--font-mono);     /* @kind font */

  /* ---- Mapeo a Office ---------------------------------------------- */
  /* Jost e IBM Plex no existen en los equipos Windows de la empresa.
     Todo entregable PPTX/DOCX/XLSX usa Calibri. Ver tokens/office.css.
     La sustitución es obligatoria, no opcional: un deck con Jost se abre
     con fallback y rompe la consistencia con las plantillas de gerencia. */
  /* Aptos es la tipografía por defecto de Microsoft 365 desde 2024; Calibri
     queda como alternativa para equipos con Office anterior. */
  --font-display-office: Aptos, Calibri, 'Segoe UI', sans-serif;    /* @kind font */
  --font-sans-office:    Aptos, Calibri, 'Segoe UI', sans-serif;    /* @kind font */
  --font-mono-office:    'Aptos Mono', Consolas, 'Courier New', monospace; /* @kind font */
}

/* ═══ tokens/spacing.css ════════════════════════════════════════════ */
/* ==========================================================================
   GARCÍA CONSTRUCTORA — Spacing, radius, borders, shadow, motion, layout
   --------------------------------------------------------------------------
   The brand language is structural and rectilinear (the logo is built from
   metal profiles + a single rotated square). Radii stay TIGHT and crisp;
   shadows stay restrained and corporate.
   ========================================================================== */

:root {
  /* ---- Spacing (4px base grid) ------------------------------------- */
  --space-0:  0;
  --space-1:  4px;
  /* Único escalón bajo 4px. NO es un paso de la escala: existe solo para el
     ajuste óptico de una línea contra otra —el detalle bajo un título, el
     relleno de un resaltado— donde 4px ya se lee como separación. Si aparece
     definiendo el espacio entre dos bloques, está mal usado. */
  --space-05: 2px;
  --space-2:  8px;
  --space-3:  12px;
  --space-4:  16px;
  --space-5:  20px;
  --space-6:  24px;
  --space-8:  32px;
  --space-10: 40px;
  --space-12: 48px;
  /* Sin --space-16/20/24: 64, 80 y 96 px no los usaba ningún componente y una
     escala con tramos muertos invita a elegir el número en vez del token. El
     aire de página lo da --gutter, que sí cambia por perfil de densidad. */

  /* ---- Ancho máximo de contenido ------------------------------------
     Tres medidas, no una. Una tabla de partidas necesita todo el ancho que
     haya; un formulario a 1600px obliga a barrer la pantalla con la cabeza
     entre la etiqueta y el campo. Las plantillas eligen según lo que llevan
     dentro — ninguna escribe un ancho en px. */
  --content-max:          1600px;  /* bandeja, tablero — tabla ancha */
  --content-max-lectura:   980px;  /* detalle, formulario — medida de lectura */
  --content-max-estrecho:  680px;  /* ajustes, confirmaciones */

  /* ---- Radius: DOS FAMILIAS, PORQUE HAY DOS TRABAJOS ----------------
     La escala valía 0 / 1 / 2 / 2 / 3 / 4 / 999: siete nombres para tres
     valores visibles, con `sm` y `md` IDÉNTICOS. Y medido el 17-08-2026, el
     problema de fondo no era la calibración sino la división:

         Input, Button, Select    ->  --radius-md
         DataGrid, TreeTable      ->  --radius-md

     El mismo token servía a un campo de formulario y a una grilla de datos, que
     quieren cosas opuestas. Por eso estaba trabado en 2 px: subirlo redondeaba
     las tablas, bajarlo endurecía los botones. No se podía mover sin romper algo,
     así que no se movió nunca — y de ahí sale parte de que el conjunto se sienta
     «tipo planilla con CSS».

     Se separa por ROL:

       TÉCNICO — datos, planos, documentos. Borde duro; se queda donde estaba.
       Una tabla con esquinas blandas miente sobre lo que es.

       INTERACTIVO — lo que se aprieta, se escribe y se abre. Ahí el radio es
       lenguaje: dice «esto responde».

     Los nombres numéricos SE CONSERVAN con su valor: hay 131 usos internos.
     Migrar un componente es cambiarle el nombre del token, no editar un valor. */

  /* Técnico */
  --radius-none: 0;
  --radius-xs:   1px;
  --radius-sm:   2px;
  --radius-md:   2px;
  --radius-lg:   3px;
  --radius-xl:   4px;
  --radius-full: 999px;  /* pills, avatars */

  /* Por rol — lo que un componente debería nombrar */
  --radius-dato:    var(--radius-sm);   /* celda, sello, chip de estado */
  --radius-tabla:   var(--radius-lg);   /* el marco de una grilla */
  --radius-control: 6px;                /* campo, botón, selector */
  /* 14px, y el valor NO lo eligió el sistema: lo eligió postventa mirando sus
     pantallas —`--radius-card: 14px`, con el comentario «tarjetas premium»— y el
     sistema declaraba 8px elegidos frente al token y no frente a una app. Entre
     una decisión tomada mirando la pantalla y una tomada mirando el CSS, gana la
     primera. Con esto la línea local de esa app desaparece sin que se mueva un
     píxel allá, que es la mejor forma de converger: la app no cambia de aspecto y
     deja de estar fuera del sistema. */
  --radius-panel:   14px;               /* tarjeta, aviso, panel */
  --radius-capa:    12px;               /* modal, drawer: lo que flota */

  /* ---- Border widths ----------------------------------------------- */
  --border-w-hairline: 1px;
  --border-w-thick:    2px;
  --border-accent:     3px;  /* blue accent rule — echoes the brand's square block */

  /* ---- Shadows (corporate, low-spread) ----------------------------- */
  --shadow-xs:  0 1px 2px rgba(29, 31, 33, 0.06);
  --shadow-sm:  0 1px 3px rgba(29, 31, 33, 0.08), 0 1px 2px rgba(29, 31, 33, 0.06);
  --shadow-md:  0 2px 6px rgba(29, 31, 33, 0.08), 0 4px 12px rgba(29, 31, 33, 0.06);
  --shadow-lg:  0 6px 16px rgba(29, 31, 33, 0.10), 0 12px 32px rgba(29, 31, 33, 0.08);
  --shadow-focus: 0 0 0 3px rgba(40, 113, 184, 0.30);

  /* ---- Sombras POR ROL, sobre la escala de arriba ------------------- */
  /* La escala se llama por TAMAÑO, y un nombre de tamaño no dice cuándo usarlo:
     quien elige entre `--shadow-md` y `--shadow-lg` lo hace por gusto, y así
     terminaron tarjetas ordinarias con la sombra de un modal. Estos tres se
     llaman por el rol, que sí se puede decidir sin mirar la pantalla:
     ¿es una tarjeta, algo que flota, o una capa con velo detrás?
     No son valores nuevos —son la escala existente con el nombre que se elige.
     @kind other */
  --shadow-card:     var(--shadow-xs);   /* casi nada: la superficie ya separa */
  --shadow-floating: var(--shadow-md);   /* popover, barra inferior, menú asomado */
  --shadow-modal:    var(--shadow-lg);   /* modal y drawer, con velo detrás */

  /* ---- Motion ------------------------------------------------------ */
  /* Dos curvas y ninguna de entrada lenta. Acá vivía `--ease-in`, sin un solo
     uso en el sistema: no era una violación, era una violación disponible,
     servida con nombre de token y esperando que alguien la eligiera de buena fe
     porque «está en el sistema». `ease-in` arranca lento y retrasa exactamente
     el instante que la persona está mirando.

     Si algún día hace falta una curva de aceleración para una salida pura, se
     declara con nombre propio y con el comentario de para qué sirve y para qué
     no. Un token sin uso no se guarda «por si acaso». */
  --ease-standard: cubic-bezier(0.4, 0.0, 0.2, 1);  /* @kind other */
  --ease-out:      cubic-bezier(0.0, 0.0, 0.2, 1);  /* @kind other */
  --dur-fast:   120ms;  /* @kind other */
  /* Bajó de 200ms a 180ms. La revisión externa fijó 120-180ms para una
     microinteraccion normal y 200 quedaba justo afuera. La proporción ~0,7 del
     perfil terreno se mantiene sola: 140ms ya estaba dentro del rango. */
  --dur-base:   180ms;  /* @kind other */
  --dur-slow:   320ms;  /* @kind other */

  /* ---- Z-index ----------------------------------------------------- */
  --z-base:      0;    /* @kind other */
  --z-sticky:    100;  /* @kind other */
  --z-dropdown:  200;  /* @kind other */
  --z-overlay:   300;  /* @kind other */
  --z-modal:     400;  /* @kind other */
  --z-toast:     500;  /* @kind other */

  /* ---- Layout ------------------------------------------------------ */
  --sidebar-w:    248px;
  /* El ancho del menú expandido como token propio: `AppShell` lo publica inline
     según el tramo que ya resolvió midiendo SU contenedor, y para eso necesita un
     nombre que no sea el que está pisando —`--sidebar-w: var(--sidebar-w)` es un
     ciclo—. Ver la nota de ANCHO ESTRUCTURAL en components/nav/AppShell.jsx. */
  --sidebar-w-exp: 248px;
  /* Ancho mínimo para dibujar el LOCKUP completo —isotipo sobre wordmark de
     dos líneas—. Medido en navegador: a su ancho nativo de 285px la segunda
     línea mide 8px, y necesita 320px para alcanzar los 9px de --fs-micro, el
     tipo más chico que el sistema se permite. Bajo este ancho va el isotipo,
     no el lockup encogido. El menú expandido mide 248: por eso ahí nunca va
     el lockup. @kind dimension */
  --logo-lockup-min: 320px;
  /* El rail es el mismo menú colapsado, no otro componente: por eso su ancho
     es un token del sistema y no un número por app. Cabe el ícono de 22px con
     su badge y el área táctil completa. */
  --sidebar-w-rail: 72px;

  /* Alto de la barra inferior de teléfono. Estaba implícito en el alto de sus
     botones, o sea que no se podía reservar espacio al pie del contenido sin
     adivinarlo, y la última fila de una tabla quedaba debajo de la barra.
     @kind other */
  --nav-bottom-h:  64px;

  /* DÓNDE SE APOYA LO QUE FLOTA SOBRE LA PANTALLA, y por qué es un token y no
     un número en cada componente.

     Medido el 27-08-2026 sobre `cgarcia-control-gestion`: su píldora «? Ayuda»
     estaba en `bottom: 20` escrito a mano. Bajo 640px el shell dibuja la barra
     inferior, que ocupa de 0 a 64px desde el borde — así que la píldora, que
     ocupa de 20 a 64, quedaba ENTERA encima de la navegación primaria. Y sin
     `env(safe-area-inset-bottom)`, en iPhone la barra sube y empeora. La misma
     app en `cg-postventa-app` usaba 18, o sea el mismo defecto con otro número:
     dos apps adivinando la misma distancia por separado.

     El valor es EL MISMO que ya reserva el contenido en `responsive.css`
     (`.gc-appshell-main`), y no por prolijidad: así lo que flota se apoya
     exactamente donde el contenido termina, en vez de negociar aparte.

     `-x` es el margen lateral, que NO cambia con la barra: la barra ocupa alto,
     no ancho. Se declara igual para que un componente flotante no tenga que
     mezclar un token con un literal.

     `@kind other` y no `dimension`: el manifest clasifica por prefijo del nombre
     y por valor, no por esta anotación, y un `calc()` con `env()` no matchea
     ninguna de las dos reglas. Se escribe lo que el manifest registra de
     verdad — una anotación que dice otra cosa es una que alguien va a creer.
     @kind other */
  --flotante-inset-b: calc(var(--space-6) + env(safe-area-inset-bottom, 0px));
  --flotante-inset-x: var(--space-6);
  /* EL RÓTULO DE GRUPO OCUPA ESTE ALTO EN LOS DOS ESTADOS, y por eso es un
     token y no un margen suelto. En rail el título del grupo no cabe y se dibuja
     una hairline; si esa hairline ocupa otro alto, al asomar el panel TODOS los
     ítems se corren hacia abajo y el destino se mueve debajo del cursor. Medido:
     con la hairline de antes el primer ítem quedaba 24px más arriba en rail que
     asomado, así que un clic en el rail aterrizaba en el rótulo del grupo y no
     navegaba a nada. @kind dimension */
  --nav-rotulo-h: 20px;
  /* 54px y no 60. Los dos marcos declaraban su propio alto —60 el del sistema,
     54 escrito a mano en el de marca— y la barra que ven nueve apps en
     producción es la de 54. Unificar hacia arriba habría movido nueve apps para
     que coincidieran con un solo shell; unificar hacia abajo no mueve ninguna y
     además la deja más delgada, que es lo que se pidió. @kind dimension */
  --topbar-h:     54px;
  --container-max: 1280px;
}

/* ═══ tokens/densidad.css ═══════════════════════════════════════════ */
/* ==========================================================================
   GARCÍA CONSTRUCTORA — Densidad y contexto de uso
   --------------------------------------------------------------------------
   El sistema original está calibrado a una sola densidad: escritorio denso
   (cuerpo 14px, radios 2px, controles de 32–40px). Sirve para control de
   gestión en oficina. NO sirve para postventa.

   Postventa se opera en terreno: teléfono en mano, a veces con guantes,
   con sol directo sobre la pantalla y con una sola mano libre. Los targets
   de 32px y el texto de 13px son inoperables ahí.

   Tres contextos, un solo set de componentes:
     [data-densidad="oficina"]  → default, escritorio denso
     [data-densidad="terreno"]  → móvil/tablet, targets 48px, texto +2px
     [data-contraste="alto"]    → sol directo, bordes y texto reforzados
   ========================================================================== */

:root {
  /* Densidad oficina (default) */
  --control-h-sm:   32px;
  /* 44 px y no 40. La norma pide 44×44 para un objetivo táctil (WCAG 2.1,
     criterio 2.5.5) y el perfil por omisión daba 40: cada app quedaba cuatro
     píxeles corta sin que nadie lo viera, porque el número está acá y no en la
     app. Se midió en postventa —34 de 39 controles bajo el mínimo en el
     tablero— y el diagnóstico fue el mismo en todas: el defecto no era de la
     app, era de este archivo.

     Cuatro píxeles no descuadran una pantalla: `--row-h` ya vale 44 y los
     controles conviven con las filas de tabla, así que la altura queda pareja
     en vez de desalineada por cuatro. `--control-h-sm` se queda en 32 a
     propósito: es la variante compacta que se elige a mano para una barra de
     herramientas, no el tamaño por omisión de nada. */
  --control-h-md:   44px;
  --control-h-lg:   48px;
  --touch-min:      32px;

  /* ── EL PISO TÁCTIL, EN UN TOKEN QUE NO SE PUEDE PERDER ──────────────────
     Más abajo hay una regla que le pone `min-height: var(--touch-min)` a todo
     input, select y textarea en perfil terreno. No sirve, y el motivo es de
     cascada: un componente que escribe `minHeight: 'var(--control-h-sm)'` en su
     `style` inline le gana a cualquier hoja. Diecisiete lugares del paquete lo
     hacen, así que en terreno esos controles medían 40px con el piso en 48 —lo
     encontró `npm run layout` al poner las pantallas piloto bajo contrato, y
     ninguna prueba de tokens podía verlo porque los VALORES estaban bien: lo que
     estaba mal era quién ganaba—.

     Es el mismo mecanismo que dejaba el rail visible en teléfono: la hoja decía
     `display: none` y el componente decía `display: flex` inline.

     `--control-h-sm-tactil` resuelve el max() en el token, así que la invariante
     viaja con el valor y no depende de que ninguna hoja gane. Un componente que
     use este token no puede quedar bajo el mínimo, y no hace falta que se acuerde.
     @kind dimension */
  --control-h-sm-tactil: max(var(--control-h-sm), var(--touch-min));
  --fs-ui:          var(--fs-md);      /* 14px */
  --fs-ui-sm:       var(--fs-sm);      /* 13px */
  --row-h:          44px;              /* fila de tabla */
  --gutter:         var(--space-6);    /* 24px */
  --panel-pad:      var(--space-6);
}

/* --------------------------------------------------------------------------
   TERRENO — móvil / tablet en obra
   Targets 48px (mínimo WCAG 2.2 AA para target size es 24px; 48px es el
   mínimo operativo real con guante de trabajo). Texto un escalón arriba.
   -------------------------------------------------------------------------- */
/* --------------------------------------------------------------------------
   ESPECIFICIDAD DE LOS PERFILES — por qué el selector va duplicado

   Los perfiles se declaran como `[data-x="y"][data-x]` a propósito: 0-2-0 en
   vez de 0-1-0. responsive.css declara sus propios `--touch-min` y `--gutter`
   sobre `:root` dentro de media queries, tiene la misma especificidad y viene
   DESPUÉS en la hoja concatenada, así que ganaba siempre.

   Consecuencia real: en cualquier dispositivo táctil el modo terreno quedaba
   en 44px en vez de 48, y el modo gerencial perdía su gutter entre 1024 y
   1365 px, que es justo el proyector de comité. La capa completa era
   decorativa bajo 1366 px — el perfil bandera del sistema, el capataz con
   guantes, nunca se aplicaba.

   Un media query no aporta especificidad, así que subirla acá alcanza y no
   obliga a reordenar los archivos.
   -------------------------------------------------------------------------- */
[data-densidad="terreno"][data-densidad] {
  --control-h-sm:   40px;
  --control-h-md:   48px;
  --control-h-lg:   56px;
  --touch-min:      48px;
  --fs-ui:          var(--fs-base);    /* 16px — evita zoom automático en iOS */
  --fs-ui-sm:       var(--fs-md);      /* 14px */
  --row-h:          64px;
  --gutter:         var(--space-4);    /* 16px — pantalla angosta */
  --panel-pad:      var(--space-4);

  /* Motion más corto, con el movimiento intacto.

     Los tres perfiles compartían los mismos seis tokens y no debieran: en
     oficina una transición de 200ms es un detalle, y en terreno son 200ms en
     que la interfaz no acepta el toque mientras alguien espera de pie, con
     guantes y con sol sobre la pantalla, para marcar un avance.

     Se acortan y NO se apagan. Apagar el desplazamiento es lo que hace
     `prefers-reduced-motion`, y ahí lo pide la persona; acá lo decidiríamos
     nosotros por ella. En una pantalla chica el recorrido del panel es lo que
     dice de dónde salió, y esa ayuda se pierde entera. Si alguien usa la app
     en obra y el desplazamiento estorba, la vuelta es cambiar estas tres
     líneas por `transition-property: opacity`.

     Proporción ~0,7 sobre el perfil de oficina: se sigue notando que hubo
     transición, deja de haber espera. */
  --dur-fast:       90ms;
  --dur-base:      140ms;
  --dur-slow:      220ms;
}

/* Todo control interactivo garantiza el target mínimo en terreno.

   LA ESPECIFICIDAD (0,1,1) VA A PROPÓSITO, Y SE MIDIÓ ANTES DE BAJARLA (23-09-2026).
   Le gana a cualquier clase suelta, y por eso ya hubo que esquivarla cuatro
   veces: la clase doble de `nav.css` (pie del menú e identidad) y de
   `responsive.css` (barra inferior), y en terrenos-cjmg una cruz que quedaba
   ovalada y un «+» que se achicaba. La salida obvia es bajarle el peso con
   `:where()`. Se midió con `npm run terreno` sobre las 21 pantallas de arranque
   y sobre las pantallas de inicio de las 14 apps levantadas en local, a 390 y
   834 px con dedo.

     · Pantallas de arranque del paquete: 0 cambios. Los componentes llevan su
       tamaño inline, y un estilo inline ya le gana a cualquier hoja.
     · Apps: a 390 px cambiarían 109 de 505 controles, en 7 de las 14 (a 834,
       107 de 532 en 6).
       - 103 bajan de letra, de 16 px a entre 9 y 14: presupuesto-bim 29 de 40
         (hasta 9 px), tablero de proyectos 67 de 299 (a 14), loteo, gerencial,
         terrenos y capacitaciones.
       - 2 quedan bajo el piso táctil: en control-gestion, «Cambiar contraseña»
         y «Cerrar sesión» bajan de 48 a 36 px, porque la app declara
         `button { min-height }` suelto después de esta hoja.
       - El formulario de terreno nuevo, en terrenos, pasa sus 21 campos de 16 a
         13 px. En un iPhone, un campo con letra bajo 16 px hace zoom al tocarlo,
         y evitar ese zoom es justamente lo que hace esta regla.
       - asistencia, calculo-tratos, acceso, paneles, ecosistema, lecciones y
         postventa (en su bandeja: el tablero no carga en el banco local): 0.
       - Pesando (0,0,1) en vez de (0,0,0) da exactamente lo mismo en las 14:
         no hay una versión intermedia que se salve.

   O sea: la regla hace el trabajo para el que está. Las clases que hoy pierden
   contra ella son tamaños de oficina que en terreno no sirven, y bajarle el peso
   los devolvería a todos de una vez. Se queda así.

   Cuando un control de verdad necesita su propio tamaño en terreno, el camino no
   es pelearle la especificidad:
     1. un ícono en vez de una letra, porque mide lo que dice su `size`;
     2. el lado de un botón redondo sale de `--touch-min`, no de un número fijo;
     3. y si hace falta otra letra, la clase doble, con el motivo escrito al lado.

   OJO: `:where([data-densidad="terreno"]) :is(button, a[role="button"], …)` no
   baja nada, aunque lo parezca. `:is()` toma la especificidad de su argumento
   más específico, y `a[role="button"]` pesa (0,1,1). Las dos formas que sí
   bajan el peso son las que mide `scripts/medir-terreno.mjs`. */
[data-densidad="terreno"] button,
[data-densidad="terreno"] a[role="button"],
[data-densidad="terreno"] input,
[data-densidad="terreno"] select,
[data-densidad="terreno"] textarea,
[data-densidad="terreno"] [role="tab"],
[data-densidad="terreno"] [role="option"] {
  min-height: var(--touch-min);
  font-size: var(--fs-ui);
}

/* En terreno el pulgar llega abajo: las acciones primarias van fijas al pie */
[data-densidad="terreno"] .acciones-fijas {
  position: sticky;
  bottom: 0;
  z-index: var(--z-sticky);
  display: flex;
  gap: var(--space-2);
  padding: var(--space-3) var(--gutter);
  padding-bottom: calc(var(--space-3) + env(safe-area-inset-bottom, 0px));
  background: var(--surface-card);
  border-top: 1px solid var(--border-default);
}

/* --------------------------------------------------------------------------
   ALTO CONTRASTE — sol directo
   No cambia la marca: refuerza bordes, sube el peso del texto secundario y
   apaga el papel cálido en favor de blanco puro (más luminancia disponible).
   -------------------------------------------------------------------------- */
[data-contraste="alto"][data-contraste] {
  --surface-page:   var(--gray-0);
  --text-muted:     var(--gray-800);
  --text-subtle:    var(--cafe-600);
  --border-subtle:  var(--cafe-300);
  --border-default: var(--cafe-400);
  --border-strong:  var(--cafe-600);
}

[data-contraste="alto"] [data-estado],
[data-contraste="alto"] .badge {
  border: 1px solid currentColor;
  font-weight: var(--fw-bold);
}

/* --------------------------------------------------------------------------
   Respeto a preferencias del sistema operativo
   -------------------------------------------------------------------------- */
/* Movimiento reducido apaga el DESPLAZAMIENTO, no la retroalimentación.

   Acá había un interruptor global que llevaba toda animación y toda
   transición a 0.01ms. Apagaba también lo que no es movimiento: el esqueleto
   de carga y el pulso de «sincronizando» quedaban congelados, y los dos
   comunican estado. En terreno el segundo es la diferencia entre reintentar y
   perder el registro.

   Quien activa esta preferencia lo hace por mareo o por batería, no para
   quedarse sin señales de que el sistema está haciendo algo. Así que la lista
   blanca deja pasar lo que cambia sin moverse —opacidad y color— y lo que se
   desplaza deja de desplazarse. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    transition-property:
      opacity, color, background-color, background, border-color,
      box-shadow, fill, stroke, outline-color !important;
    transition-duration: var(--dur-fast) !important;
  }

  /* Los overlays son lo único del sistema que se desplaza. Como `transform`
     salió de la lista blanca, sin esto cambiarían de posición de un salto:
     en vez de eso aparecen y desaparecen con opacidad. */
  .gc-drawer, .gc-modal, .gc-toast { transform: none !important; }
  .gc-drawer[data-abierto="false"] { opacity: 0; pointer-events: none; }
}

@media (pointer: coarse) {
  :root {
    --touch-min: 44px;
  }
}

/* --------------------------------------------------------------------------
   MODO GERENCIAL — comité, directorio, Power BI
   Tercer contexto de lectura, no un tercer design system. Pocas cifras,
   grandes, con la desviación y la decisión requerida al frente. La densidad
   operativa aquí estorba: quien lee esto no ejecuta, decide.
   -------------------------------------------------------------------------- */
[data-modo="gerencial"][data-modo] {
  --fs-ui:       var(--fs-base);
  --fs-ui-sm:    var(--fs-md);
  --row-h:       52px;
  --gutter:      var(--space-8);
  --panel-pad:   var(--space-8);
}

/* La cifra manda: KPI a escala de titular */
[data-modo="gerencial"] .kpi-valor {
  font-family: var(--font-mono);
  font-size: var(--fs-5xl);
  font-weight: var(--fw-light);
  line-height: 1;
  letter-spacing: var(--ls-tight);
  font-variant-numeric: tabular-nums;
  color: var(--text-strong);
}

[data-modo="gerencial"] .kpi-label {
  font-family: var(--font-sans);
  font-size: var(--fs-md);
  font-weight: var(--fw-medium);
  color: var(--text-muted);
}

/* Una sola conclusión por bloque, en prosa, no en jerga de sistema */
[data-modo="gerencial"] .lectura {
  font-family: var(--font-sans);
  font-size: var(--fs-lg);
  line-height: var(--lh-snug);
  color: var(--text-body);
  max-width: 62ch;
}

/* Decisión requerida: lo único que justifica llevar algo a comité */
[data-modo="gerencial"] .decision {
  border-left: var(--border-accent) solid var(--brand-primary);
  padding: var(--space-4) var(--space-5);
  background: var(--surface-card);
}

/* ═══ tokens/responsive.css ═════════════════════════════════════════ */
/* ==========================================================================
   GARCÍA CONSTRUCTORA — Breakpoints y comportamiento responsive
   --------------------------------------------------------------------------
   Los breakpoints se definen por COMPORTAMIENTO DEL CONTENIDO, no por modelos
   de dispositivo. El nombre describe qué deja de funcionar, no qué teléfono es.

     compacto   < 640    una columna, sin tablas anchas, navegación inferior
     medio      640–1023 dos columnas, tablas de 4–5 columnas, drawer 80%
     amplio     1024–1365 sidebar visible, tabla completa, panel lateral
     extenso    ≥ 1366   análisis: tabla + panel simultáneos, filtros abiertos

   Validado en 360 · 390 · 430 · 768 · 1024 · 1366 · 1440 px.

   REGLA: una vista de escritorio reducida no es una vista móvil. Cada
   breakpoint reordena jerarquía, no solo achica. Lo que en escritorio es una
   columna de tabla, en celular es una línea de la tarjeta o no aparece.
   ========================================================================== */

:root {
  --bp-compacto: 640px;
  --bp-medio:    1024px;
  --bp-amplio:   1366px;

  /* Ancho de contenido por contexto */
  --ancho-lectura:  68ch;   /* prosa: informes, lecturas gerenciales */
  --ancho-form:     560px;  /* formularios de una columna */
  --ancho-app:      1440px; /* contenedor máximo de aplicación */
}

/* --------------------------------------------------------------------------
   COMPACTO — celular (< 640px)
   Densidad terreno automática, navegación inferior, sin hover.
   -------------------------------------------------------------------------- */
@media (max-width: 639px) {
  :root {
    --control-h-sm: 40px;
    --control-h-md: 48px;
    --control-h-lg: 56px;
    --touch-min:    44px;      /* mínimo WCAG 2.5.5 */
    /* ACÁ ABAJO HAY UNA BARRA DE NAVEGACIÓN, así que lo que flota se apoya
       sobre ella y no encima. Es el MISMO cálculo que `.gc-appshell-main` usa
       más abajo para reservar el pie del contenido: lo flotante y lo que se lee
       terminan en la misma línea. Sin esto, una píldora en `bottom: 24` queda
       entera dentro de los 64px de la barra — medido en dos apps del ecosistema,
       cada una con su propio número inventado. @kind other */
    --flotante-inset-b: calc(var(--nav-bottom-h) + var(--space-6) + env(safe-area-inset-bottom, 0px));
    --fs-ui:        var(--fs-base);   /* 16px — evita el zoom automático de iOS */
    --fs-ui-sm:     var(--fs-md);
    --row-h:        64px;
    --gutter:       var(--space-4);
    --panel-pad:    var(--space-4);
    --sidebar-w:    100%;
  }

  /* El sidebar permanente no cabe: pasa a navegación inferior */
  .gc-sidebar { display: none; }
  /* LA BARRA INFERIOR FLOTA, y no es decoración. Pegada al borde con un
     `border-top` de lado a lado, la barra leía como el final de la pantalla: la
     última fila de la tabla parecía cortada por ella y no se sabía si abajo
     había más. Separada del borde, con el papel del lienzo asomando por los
     cuatro lados, se lee como lo que es —una capa por encima del contenido— y
     el contenido se entiende que sigue por debajo.

     El alto se reserva con `--nav-bottom-h` y NO se adivina: antes el alto salía
     del alto de sus propios botones, así que ninguna pantalla podía dejar aire
     al pie y la última fila quedaba tapada. */
  .gc-navbar-inferior {
    position: fixed;
    left: var(--space-3); right: var(--space-3);
    bottom: calc(env(safe-area-inset-bottom, 0px) + var(--space-3));
    z-index: var(--z-sticky);
    display: flex;
    min-height: var(--nav-bottom-h);
    background: var(--surface-floating);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-panel);
    box-shadow: var(--shadow-floating);
  }
  /* DOS CLASES, y no una. Un destino con hijos se dibuja como `<button>` —apretarlo
     despliega, no navega— y `densidad.css` tiene `[data-densidad="terreno"] button`
     con especificidad (0,1,1), que le gana a `.gc-navbar-inferior > *` (0,1,0): en
     terreno esos rótulos salían a 16px y los enlaces a 11. Medido el 23-09-2026 en
     postventa a 390px: «Casos» y «Entregas» en 16px junto a «Tablero» en 11, y
     «Entregas» cortado (71px de texto en 69 de pista). Y en los menús del registro,
     donde TODOS los destinos son botones, la barra entera salía a 16px. Es el mismo
     caso medido que el pie del menú y los botones de la identidad (`tokens/nav.css`). */
  .gc-navbar-inferior.gc-navbar-inferior > * {
    flex: 1;
    min-height: var(--touch-min);
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: 2px;
    font-size: var(--fs-2xs);
     padding: 0 2px;   
  }

  /* EL PIE DE ACCIONES SE APILA, Y LA PRIMARIA VA A ANCHO COMPLETO Y ARRIBA.
     Arriba y no abajo: el pulgar llega antes al borde inferior, pero ahí está la
     barra de gestos del sistema, y la acción de salir o cancelar no puede quedar
     pegada a ella. `column-reverse` sobre el grupo consigue las dos cosas —la
     primaria queda primera en el orden visual sin cambiar el orden del DOM, que es
     el que recorre el teclado— y el `order` explícito no hace falta.
     Lo pidió la revisión textualmente: ningún botón cortado, primaria a ancho
     completo, secundaria debajo o con menos jerarquía. */
  .gc-pie-acciones-fila {
    flex-direction: column;
    align-items: stretch;
  }
  .gc-pie-acciones-grupo {
    margin-left: 0;
    flex-direction: column-reverse;
    width: 100%;
  }
  .gc-pie-acciones-grupo > * {
    width: 100%;
    justify-content: center;
  }

  /* El contenido reserva el alto de la barra MÁS su separación del borde, o la
     barra flotante tapa lo último que se lee. Va sobre la caja de contenido del
     shell y no sobre una clase nueva: la primera versión de esto declaró
     `.gc-contenido-movil`, que no existía en ningún componente, así que la regla
     no se aplicaba nunca y la barra tapaba la última fila igual. Lo encontró
     `validar.js` con la regla de clase huérfana. */
  .gc-appshell-main {
    padding-bottom: calc(var(--nav-bottom-h) + var(--space-6) + env(safe-area-inset-bottom, 0px));
  }
  /* SIN BARRA INFERIOR —una app de un solo destino, 2.31.0— no hay qué
     reservarle al pie, y lo flotante se apoya en el borde de la pantalla. */
  .gc-appshell[data-sin-barra-inferior] {
    --flotante-inset-b: calc(var(--space-6) + env(safe-area-inset-bottom, 0px));
  }
  .gc-appshell[data-sin-barra-inferior] .gc-appshell-main {
    padding-bottom: env(safe-area-inset-bottom, 0px);
  }

  /* Las tablas anchas se leen como lista de detalle, no comprimidas.

     ESTO SE QUEDA EN MEDIA QUERY A PROPÓSITO, y no es un olvido de la tanda de
     container queries. Es la presentación de TELÉFONO —cabecera oculta, cada
     celda con su rótulo encima, columnas de prioridad baja escondidas—, y eso
     depende del dispositivo, no del ancho del panel: una tabla de cuatro
     columnas en un panel de 500px en escritorio se lee perfecto como tabla y
     desarmarla sería peor.
     El caso del panel angosto ya está resuelto donde corresponde: `DataTable`
     compara el ancho de su contenedor contra la suma de los pisos de sus propias
     columnas y colapsa a tarjetas cuando no cabe, midiendo con ResizeObserver.
     Ver `anchoMinimoTabla` en components/data/_tablabase.jsx. */
  .gc-tabla-responsive table { display: block; }
  .gc-tabla-responsive thead { display: none; }
  .gc-tabla-responsive tbody tr {
    display: grid;
    grid-template-columns: 1fr auto;
    gap: var(--space-1) var(--space-3);
    padding: var(--space-3) var(--gutter);
    border-bottom: 1px solid var(--border-subtle);
  }
  .gc-tabla-responsive td { display: block; padding: 0; border: 0; }
  /* Cada celda antepone su rótulo, tomado de data-col */
  .gc-tabla-responsive td[data-col]::before {
    content: attr(data-col);
    display: block;
    font-size: var(--fs-2xs);
    color: var(--text-subtle);
  }
  /* Columnas marcadas como secundarias no aparecen en celular */
  .gc-tabla-responsive td[data-prioridad="baja"] { display: none; }

  /* Modales a pantalla completa */
  .gc-modal {
    inset: 0 !important;
    max-width: none !important;
    max-height: none !important;
    /* Dividido por el factor de letra: el dvh crece con el zoom del marco. */
    height: calc(100dvh / var(--gc-letra, 1));
    border-radius: 0 !important;
  }

  /* Los paneles laterales se vuelven hoja inferior */
  .gc-drawer {
    inset: auto 0 0 0 !important;
    max-width: none !important;
    height: calc(88dvh / var(--gc-letra, 1));
    transform: translateY(var(--drawer-y, 100%)) !important;
    border-radius: var(--radius-xl) var(--radius-xl) 0 0;
    border-left: 0 !important;
    border-top: 1px solid var(--border-default);
  }
  .gc-drawer[data-abierto="true"] { --drawer-y: 0; /* @kind spacing */ }

  /* Los filtros no ocupan pantalla: colapsan a un disparador */
  .gc-filtros-avanzados { display: none; }

  /* Una sola columna en formularios */
  .gc-form-grid { grid-template-columns: 1fr !important; }
}

/* --------------------------------------------------------------------------
   MEDIO — tablet vertical (640–1023px)
   Supervisión y reuniones: táctil, formularios, evidencia, aprobaciones.
   -------------------------------------------------------------------------- */
@media (min-width: 640px) and (max-width: 1023px) {
  :root {
    --control-h-md: 44px;
    --touch-min:    44px;
    --fs-ui:        var(--fs-base);
    --row-h:        56px;
    --gutter:       var(--space-5);
    --sidebar-w:    var(--sidebar-w-rail);    /* solo íconos: el rail del SideNav */
  }
  .gc-sidebar-rotulo { display: none; }
  /* EL 80 % ES UN TOPE, NO UN ANCHO. Esta regla era `max-width: 80vw !important`
     a secas: pisaba el `maxWidth` que cada panel pide, y como el `Drawer` va con
     `width: 100%`, el tope pasaba a ser el ancho. Medido el 23-09-2026 a 834px: el
     manual, que pide 340, medía 667 —el 80 % de la tablet—, y lo mismo cualquier
     panel lateral. Ahora gana el menor de los dos: lo que el panel pide, o el 80 %. */
  .gc-drawer { max-width: min(var(--drawer-ancho, 100vw), calc(80vw / var(--gc-letra, 1))) !important; }
  .gc-tabla-responsive td[data-prioridad="baja"] { display: none; }
}

/* --------------------------------------------------------------------------
   EL RÓTULO SIGUE AL MODO RESUELTO, NO A LA VENTANA
   --------------------------------------------------------------------------
   La regla de arriba oculta los rótulos por ancho de ventana, y el modo del
   menú lo resuelve `AppShell` midiendo su propio contenedor. Una página que
   monta un marco de 1440 dentro de una ventana de 924 quedaba con el menú
   expandido y los nueve rótulos en 0px de ancho.

   Estas dos reglas van FUERA de toda media query y con mayor especificidad, así
   que ganan en los dos sentidos: marco expandido en ventana angosta, y marco en
   rail dentro de ventana ancha. El `display` de un rótulo es estructura, y la
   estructura la decide el contenedor.
   -------------------------------------------------------------------------- */
.gc-sidebar[data-modo="expandido"] .gc-sidebar-rotulo { display: revert; }
.gc-sidebar[data-modo="rail"] .gc-sidebar-rotulo { display: none; }

/* --------------------------------------------------------------------------
   AMPLIO — notebook y tablet horizontal (1024–1365px)
   -------------------------------------------------------------------------- */
@media (min-width: 1024px) and (max-width: 1365px) {
  :root {
    --sidebar-w: 216px;
    --gutter:    var(--space-6);
  }
  .gc-drawer { max-width: 480px !important; }
}

/* --------------------------------------------------------------------------
   EXTENSO — escritorio de análisis (≥ 1366px)
   Tabla y panel de detalle conviven; los filtros quedan abiertos.
   -------------------------------------------------------------------------- */
@media (min-width: 1366px) {
  :root {
    --sidebar-w: 248px;
    --gutter:    var(--space-6);
  }
  /* El detalle deja de tapar la tabla: se acopla al costado */
  .gc-layout-analisis {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 420px;
    gap: var(--space-5);
  }
  .gc-filtros-avanzados { display: block; }
}

/* --------------------------------------------------------------------------
   ENTRADA TÁCTIL — independiente del ancho
   Un notebook con pantalla táctil necesita targets grandes aunque sea ancho.
   -------------------------------------------------------------------------- */
@media (pointer: coarse) {
  :root { --touch-min: 44px; }
  /* Nada crítico puede depender de hover */
  .gc-solo-hover { display: none; }

  /* EL RÓTULO DE GRUPO ES UN OBJETIVO CUANDO LAS SECCIONES SE PLIEGAN.
     Con puntero grueso pasa de rótulo a botón, y 20px no se aciertan con el
     pulgar. Crece al piso táctil.

     SE CAMBIA EL TOKEN, no el estilo del botón, y esa es la parte que importa:
     `SideNav` dibuja el rótulo Y su hairline de rail con esta misma variable,
     justamente para que ningún destino se mueva al asomar el menú. Tocar solo
     el botón devolvería ese defecto —el ítem se corre bajo el cursor y el clic
     aterriza en otra cosa—, y es un defecto que este componente ya pagó. */
  :root { --nav-rotulo-h: var(--touch-min); }
}

@media (hover: hover) and (pointer: fine) {
  .gc-solo-hover { display: initial; }
  /* IndiceApps: el destino bajo el mouse se hunde, como una fila de tabla.
     Solo con mouse: un toque no deja nada realzado. */
  [data-gc="indice-apps"] [data-destino]:hover { background: var(--surface-sunken); }
}

/* AppsRecientes en tarjeta: bajo 220 px de tarjeta, ícono arriba y texto debajo
   —la maqueta eco-prop en el teléfono—, en vez de un nombre partido en dos. */
[data-rec-cuerpo] { align-items: center; }
@container (max-width: 220px) {
  [data-rec-cuerpo] { flex-direction: column; align-items: flex-start; gap: var(--space-2); }
}

/* IndiceApps destacado: en el PC el destino ya está a un clic en el índice y en
   el buscador; el acceso grande es para el teléfono, donde se usa. Mide el
   ancho del índice, no de la ventana: el menú lateral le quita espacio. */
@container (min-width: 641px) {
  /* `!important` porque el componente pinta `display: flex` en línea, y en
     línea le gana a cualquier hoja: sin él el acceso no se ocultaba nunca. */
  [data-gc="indice-destacado"][data-solo-angosto] { display: none !important; }
}

/* IndiceApps POR ETAPA (2.43.0): tarjetas con espacio, acordeón en el teléfono.
   Las dos vistas están en el DOM; `display: none` saca la que no corresponde del
   árbol de accesibilidad, así que el lector no oye cada app dos veces. Mide el
   ancho del índice, no de la ventana: el menú lateral le quita espacio.
   `!important` porque el componente pinta `display` en línea. */
@container (max-width: 640px) {
  [data-gc="indice-apps"] [data-vista="tarjetas"] { display: none !important; }
  /* En el teléfono, cuatro frecuentes y el ícono al lado: seis tarjetas con el
     ícono arriba ocupaban la primera pantalla entera (medido a 390 px). */
  [data-gc="indice-frecuentes"] li:nth-child(n+5) { display: none !important; }
  [data-gc="indice-apps"] [data-frecuente] { flex-direction: row !important; align-items: center; gap: var(--space-3) !important; padding: var(--space-3) !important; }
  [data-gc="indice-apps"] [data-frecuente] > [aria-hidden] { width: 32px !important; height: 32px !important; }
  [data-gc="indice-apps"] [data-frecuente] > span:last-child { overflow-wrap: anywhere; }
}
@container (min-width: 641px) {
  [data-gc="indice-apps"] [data-vista="acordeon"] { display: none !important; }
}
/* El acordeón dibuja su propia flecha: la del navegador se esconde, y la de la
   fila gira al abrir. */
[data-gc="indice-apps"] [data-vista="acordeon"] summary::-webkit-details-marker { display: none; }
[data-gc="indice-apps"] [data-vista="acordeon"] details:first-child { border-top: 0 !important; }
[data-gc="indice-apps"] [data-vista="acordeon"] details[open] [data-flecha] { transform: rotate(180deg); }
@media (prefers-reduced-motion: no-preference) {
  [data-gc="indice-apps"] [data-flecha] { transition: transform var(--dur-fast) var(--ease-standard); }
}
@media (hover: hover) and (pointer: fine) {
  [data-gc="indice-apps"] [data-frecuente]:hover { box-shadow: var(--shadow-md); }
}

/* IndiceApps POR ETAPA · 2.44.0 — tarjetas parejas, descripción al apuntar y
   movimiento. Las razones, en `components/nav/_indiceEtapas.jsx`. Desde 2.45.0
   el largo se resuelve con «Ver más» en el componente, no con scroll aquí. */


@media (hover: hover) and (pointer: fine) {
  /* La descripción de la app NO ocupa lugar en reposo y aparece al apuntar o al
     llegar con el teclado. En 2.44.0 quedaba reservada y transparente, y eso
     dejaba un hueco bajo cada nombre y el título del área desalineado con su
     conteo (captura del dueño, 05-10-2026). La del área no se dibuja en las
     tarjetas: el nombre y el conteo bastan, y el texto entero sigue en `title`. */
  /* `!important` porque el componente pinta `display: block` en línea. */
  [data-gc="indice-apps"] [data-vista="tarjetas"] [data-desc] { display: none !important; }
  [data-gc="indice-apps"] [data-vista="tarjetas"] [data-destino]:hover [data-desc],
  [data-gc="indice-apps"] [data-vista="tarjetas"] [data-destino]:focus-visible [data-desc] { display: block !important; }

  /* Se levantan al pasar el mouse; la flecha de la app avanza. */
  [data-gc="indice-apps"] [data-tarjeta]:hover { box-shadow: var(--shadow-md); transform: translateY(-2px); }
  [data-gc="indice-apps"] [data-frecuente]:hover { transform: translateY(-2px); }
  [data-gc="indice-apps"] [data-destino]:hover [data-chev] { transform: translateX(3px); }
}

/* Orden de entrada de cada tarjeta y frecuente; el componente lo fija en línea. */
[data-gc="indice-apps"] { --gc-i: 0; }

@media (prefers-reduced-motion: no-preference) {
  [data-gc="indice-apps"] [data-tarjeta],
  [data-gc="indice-apps"] [data-frecuente] {
    transition: box-shadow var(--dur-base) var(--ease-standard), transform var(--dur-base) var(--ease-standard);
    /* `backwards` y no `both`: terminada la entrada, el transform vuelve a ser
       del hover. Con `both` la animación lo retendría y la tarjeta no subiría. */
    animation: gc-indice-entra var(--dur-slow) var(--ease-out) backwards;
    animation-delay: calc(var(--gc-i) * var(--dur-fast) / 3);
  }
  [data-gc="indice-apps"] [data-chev] { transition: transform var(--dur-fast) var(--ease-standard); }
}
/* Movimiento reducido: sin desplazamiento, pero las tarjetas igual aparecen
   (sólo opacidad) y el realce de sombra se mantiene. */
@media (prefers-reduced-motion: reduce) {
  [data-gc="indice-apps"] [data-tarjeta]:hover,
  [data-gc="indice-apps"] [data-frecuente]:hover,
  [data-gc="indice-apps"] [data-destino]:hover [data-chev] { transform: none; }
  [data-gc="indice-apps"] [data-tarjeta],
  [data-gc="indice-apps"] [data-frecuente] {
    animation: gc-indice-aparece var(--dur-base) var(--ease-standard) backwards;
    animation-delay: calc(var(--gc-i) * var(--dur-fast) / 3);
  }
}
@keyframes gc-indice-entra { from { opacity: 0; transform: translateY(8px); } }
@keyframes gc-indice-aparece { from { opacity: 0; } }

/* El anillo de foco de una fila del índice va pegado a ella: por fuera
   chocaba con la fila vecina, que está a 2 px. */
[data-gc="indice-apps"] [data-destino]:focus-visible { outline-offset: 0; }

/* --------------------------------------------------------------------------
   FOCO VISIBLE — obligatorio, en todos los contextos
   -------------------------------------------------------------------------- */
:where(a, button, input, select, textarea, [tabindex]):focus-visible {
  outline: 2px solid var(--border-focus);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

/* --------------------------------------------------------------------------
   ANTIDESBORDE — un scroll horizontal accidental rompe la lectura en móvil
   -------------------------------------------------------------------------- */
/* --------------------------------------------------------------------------
   ANTIDESBORDE — un scroll horizontal accidental rompe la lectura en móvil

   NO se declara `html, body { overflow-x: hidden }`: esta hoja viaja dentro de
   una librería instalada por 12 apps, y una regla global sobre html/body les
   rompería el scroll horizontal legítimo (tablas anchas, carruseles, Gantt).
   Cada app decide eso en su propio layout. Acá solo contenemos los elementos
   que típicamente desbordan.
   -------------------------------------------------------------------------- */
img, svg, video, canvas, table { max-width: 100%; }

/* --------------------------------------------------------------------------
   LO FLOTANTE NO TAPA LA ÚLTIMA FILA

   La reserva del pie (`.gc-appshell-main`, bajo 640px) cubría la barra inferior
   y su separación, pero no el botón de `AccionFlotante`, que se apoya justo
   encima: al terminar de bajar, la última fila quedaba debajo del botón. Medido
   el 01-10-2026 con 40 filas: 48px tapados en 390, 72 en 834 y 68 en 1440.

   Se reserva lo que el botón ocupa —su borde inferior (`--flotante-inset-b`),
   su alto (`--control-h-md`, que crece en terreno) y un respiro— y SOLO cuando el
   botón está montado: una pantalla sin acción flotante no pierde ese alto. Es
   `:has()` y no un prop del marco a propósito: la pantalla que monta el botón es
   la que lo sabe, y no tiene que acordarse de avisarle a nadie.

   Con el contenido desplazándose en su propia caja —lo que hacen las plantillas—
   la reserva achica esa caja y el botón ya no pasa por encima de nada. Si es el
   DOCUMENTO el que se desplaza —una app que no le da alto al `<body>`—, durante
   el recorrido el botón sí pasa sobre las filas: es inherente a cualquier botón
   flotante, y por eso el sistema no lo usa por omisión y la acción principal va
   en la barra superior. Para el foco de teclado, ver el bloque siguiente. */
.gc-appshell:has([data-gc="accion-flotante"]) .gc-appshell-main {
  padding-bottom: calc(var(--flotante-inset-b) + var(--control-h-md) + var(--space-3));
}

/* --------------------------------------------------------------------------
   EL FOCO NO QUEDA DEBAJO DE LO FIJO (WCAG 2.4.11)

   Cuando el que se desplaza es el documento, al llegar con Tab a algo que está
   fuera de la vista el navegador lo deja justo en el borde de la ventana, que es
   donde están la barra inferior y la acción flotante. Medido el 01-10-2026 con 40
   filas enfocables: 46px tapados en 390, 45 en 834 y 42 en 1440. `scroll-padding`
   le dice al navegador cuánto del borde no cuenta como visible. Va en `<html>`
   porque es el contenedor que se desplaza en ese caso; con el contenido en su
   propia caja la reserva de arriba ya lo resuelve y esto no hace nada.
   Lo mide `npm run foco`, con gemelo negativo.
   -------------------------------------------------------------------------- */
@media (max-width: 639px) {
  html:has(.gc-appshell:not([data-sin-barra-inferior])) {
    scroll-padding-bottom: calc(var(--nav-bottom-h) + var(--space-6) + env(safe-area-inset-bottom, 0px));
  }
}
html:has(.gc-appshell [data-gc="accion-flotante"]) {
  scroll-padding-bottom: calc(var(--flotante-inset-b) + var(--control-h-md) + var(--space-3));
}
html:has(.gc-appshell[data-sin-barra-inferior] [data-gc="accion-flotante"]) {
  scroll-padding-bottom: calc(var(--space-6) + env(safe-area-inset-bottom, 0px) + var(--control-h-md) + var(--space-3));
}

/* ═══ tokens/estados-postventa.css ══════════════════════════════════ */
/* ==========================================================================
   GARCÍA CONSTRUCTORA — Estados del dominio: Postventa
   --------------------------------------------------------------------------
   Los estados genéricos (success / warning / danger) no bastan para operar
   postventa: el negocio distingue el ESTADO DEL CASO (dónde va en el flujo)
   del SEMÁFORO DE PLAZO (si está dentro o fuera de SLA). Son dos ejes
   independientes: un caso "en ejecución" puede estar vencido, y uno
   "cerrado conforme" puede haberse cerrado fuera de plazo.

   No colapsar ambos ejes en un solo color. La tabla muestra los dos.

   Flujo canónico:
     solicitud → observaciones → OT → ejecución → validación → cierre
   ========================================================================== */

:root {
  /* ---- EJE 1 · Estado del caso ------------------------------------- */
  /* Cada estado: -bg (fondo suave) / -fg (texto sobre ese fondo) / -mark
     (cuadrado sólido para tablas). Todos AA. */

  /* Recibida — ingresó, nadie la tomó todavía */
  --est-recibida-bg:   var(--blue-50);
  --est-recibida-fg:   var(--blue-700);
  --est-recibida-mark: var(--blue-500);

  /* En evaluación — visita técnica / calificación de procedencia */
  --est-evaluacion-bg:   var(--cafe-100);
  --est-evaluacion-fg:   var(--cafe-800);
  --est-evaluacion-mark: var(--cafe-500);

  /* Programada — con OT emitida y fecha comprometida */
  --est-programada-bg:   #e7edf7;
  --est-programada-fg:   var(--blue-800);
  --est-programada-mark: var(--blue-400);

  /* En ejecución — cuadrilla trabajando */
  --est-ejecucion-bg:   var(--warning-50);
  --est-ejecucion-fg:   var(--warning-700);
  --est-ejecucion-mark: var(--warning-500);

  /* Ejecutada · pendiente de validación del propietario */
  --est-validacion-bg:   #efe9f2;
  --est-validacion-fg:   #4a3357;
  --est-validacion-mark: #6d4c81;

  /* Cerrada conforme — con acta firmada */
  --est-cerrada-bg:   var(--success-50);
  --est-cerrada-fg:   var(--success-700);
  --est-cerrada-mark: var(--success-500);

  /* Improcedente — no cubierta por garantía (mal uso, terceros, desgaste) */
  --est-improcedente-bg:   var(--gray-100);
  --est-improcedente-fg:   var(--gray-800);
  --est-improcedente-mark: var(--gray-600);

  /* Reabierta — el propietario rechazó la ejecución o reincidió la falla */
  --est-reabierta-bg:   var(--danger-50);
  --est-reabierta-fg:   var(--danger-700);
  --est-reabierta-mark: var(--danger-500);

  /* ---- EJE 2 · Semáforo de plazo (SLA) ----------------------------- */
  /* Se calcula contra el compromiso: garantía legal, plazo Serviu o
     compromiso interno. Umbrales por defecto en --sla-umbral-*. */

  --sla-en-plazo-bg:    var(--success-50);
  --sla-en-plazo-fg:    var(--success-700);
  --sla-en-plazo-mark:  var(--success-500);

  --sla-por-vencer-bg:   var(--warning-50);
  --sla-por-vencer-fg:   var(--warning-700);
  --sla-por-vencer-mark: var(--warning-500);

  --sla-vencido-bg:   var(--danger-50);
  --sla-vencido-fg:   var(--danger-700);
  --sla-vencido-mark: var(--danger-500);

  --sla-sin-plazo-bg:   var(--gray-100);
  --sla-sin-plazo-fg:   var(--gray-700);
  --sla-sin-plazo-mark: var(--gray-400);

  /* Plazo detenido a propósito (pospuesto, esperando al propietario). Gris como
     «sin plazo»: no exige acción. 2.37.0, bandeja v4 del plan de mejora. */
  --sla-en-pausa-bg:   var(--gray-100);
  --sla-en-pausa-fg:   var(--gray-700);
  --sla-en-pausa-mark: var(--gray-400);

  /* Umbral en días para pasar de "en plazo" a "por vencer". Ajustable por
     tipo de compromiso; el valor por defecto asume plazo interno de obra. */
  --sla-umbral-por-vencer: 3;

  /* ---- Severidad de la observación --------------------------------- */
  /* Independiente del estado y del plazo. Ordena la bandeja. */
  --sev-critica-mark: var(--danger-500);   /* habitabilidad comprometida */
  --sev-mayor-mark:   var(--warning-500);  /* afecta uso, no habitabilidad */
  --sev-menor-mark:   var(--blue-400);     /* terminaciones, estético */

  /* ---- Canal de origen --------------------------------------------- */
  --canal-terreno-mark:  var(--cafe-500);
  --canal-telefono-mark: var(--blue-400);
  --canal-serviu-mark:   var(--cafe-700);
  --canal-app-mark:      var(--blue-500);
}

/* --------------------------------------------------------------------------
   Clases utilitarias — para HTML estático y prototipos rápidos.
   En React, usar <EstadoCaso> y <SlaChip> del design system.
   -------------------------------------------------------------------------- */

[data-estado] {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  height: 22px;
  padding: 0 var(--space-2);
  font-family: var(--font-sans);
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  line-height: 1;
  border-radius: var(--radius-sm);
  white-space: nowrap;
}

[data-estado="recibida"]     { background: var(--est-recibida-bg);     color: var(--est-recibida-fg); }
[data-estado="evaluacion"]   { background: var(--est-evaluacion-bg);   color: var(--est-evaluacion-fg); }
[data-estado="programada"]   { background: var(--est-programada-bg);   color: var(--est-programada-fg); }
[data-estado="ejecucion"]    { background: var(--est-ejecucion-bg);    color: var(--est-ejecucion-fg); }
[data-estado="validacion"]   { background: var(--est-validacion-bg);   color: var(--est-validacion-fg); }
[data-estado="cerrada"]      { background: var(--est-cerrada-bg);      color: var(--est-cerrada-fg); }
[data-estado="improcedente"] { background: var(--est-improcedente-bg); color: var(--est-improcedente-fg); }
[data-estado="reabierta"]    { background: var(--est-reabierta-bg);    color: var(--est-reabierta-fg); }

[data-sla] {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  font-weight: var(--fw-medium);
  font-variant-numeric: tabular-nums;
  letter-spacing: var(--ls-wide);
}
[data-sla="en-plazo"]    { color: var(--sla-en-plazo-fg); }
[data-sla="por-vencer"]  { color: var(--sla-por-vencer-fg); }
[data-sla="vencido"]     { color: var(--sla-vencido-fg); }
[data-sla="sin-plazo"]   { color: var(--sla-sin-plazo-fg); }
[data-sla="en-pausa"]    { color: var(--sla-en-pausa-fg); }

/* ═══ tokens/estados-gestion.css ════════════════════════════════════ */
/* ==========================================================================
   GARCÍA CONSTRUCTORA — Estados del dominio: Control de Gestión
   --------------------------------------------------------------------------
   Control de Gestión son 24 de las 96 vistas del ecosistema —la cuarta parte,
   y la app que mira un gerente— y hasta ahora el único vocabulario de estados
   declarado en el sistema era el de postventa. Un dominio sin vocabulario no
   se queda sin estados: cada app los inventa, y catorce inventos distintos
   para «esta OC ya llegó» es exactamente la divergencia que este sistema vino
   a cerrar.

   TRES EJES INDEPENDIENTES, y separarlos importa igual que en postventa:

   1 · CICLO DEL DOCUMENTO — dónde va una orden de compra o un estado de pago.
       Comparten forma a propósito: los dos se emiten, alguien los revisa, y
       terminan pagados o rechazados. Un solo eje para los dos evita que
       adquisiciones y subcontratos pinten «visado» de distinto color.

   2 · VIGENCIA DEL PRESUPUESTO — un itemizado en borrador y uno vigente se
       leen igual y valen distinto: sobre el vigente se compra.

   3 · CUMPLIMIENTO DEL PROGRAMA — el eje del PPC de Last Planner. Es binario
       —la tarea comprometida se cumplió o no— y la CAUSA es un dato aparte,
       no un color: por eso el Pareto la rotula con texto.

   Un documento «visado» puede estar sobre un presupuesto «cerrado», y una
   tarea «cumplida» puede pertenecer a una partida con sobrecosto. No colapsar
   ejes en un solo color.

   PREFIJOS PROPIOS, Y NO ES ESTÉTICA. La capa de postventa se quedó con el
   prefijo genérico `--est-*`, así que `--est-recibida-bg` YA significa «caso
   de postventa recibido». Reusarlo acá para una OC recibida haría que cambiar
   el color de un caso cambiara el de una orden de compra. Acá: `--doc-*`,
   `--pres-*`, `--ppc-*`.
   ========================================================================== */

:root {
  /* ---- EJE 1 · Ciclo del documento (OC y EP) ------------------------ */
  /* Cada estado: -bg (fondo suave) / -fg (texto sobre ese fondo) / -mark
     (cuadrado sólido para tablas). Todos contra su propio fondo, AA. */

  /* Emitido — existe y salió, nadie lo revisó todavía */
  --doc-emitido-bg:   var(--blue-50);
  --doc-emitido-fg:   var(--blue-700);
  --doc-emitido-mark: var(--blue-500);

  /* En revisión — oficina técnica o ITO lo está visando */
  --doc-revision-bg:   var(--cafe-100);
  --doc-revision-fg:   var(--cafe-800);
  --doc-revision-mark: var(--cafe-500);

  /* Recepción parcial — llegó parte de la OC. Es estado propio y no un
     «pendiente»: sobre una recepción parcial se paga proporcional. */
  --doc-parcial-bg:   var(--warning-50);
  --doc-parcial-fg:   var(--warning-700);
  --doc-parcial-mark: var(--warning-500);

  /* Visado — aprobado para pago, todavía sin pagar. La distancia entre
     visado y pagado es la que explica un flujo de caja apretado. */
  --doc-visado-bg:   #e7edf7;
  --doc-visado-fg:   var(--blue-800);
  --doc-visado-mark: var(--blue-400);

  /* Pagado — cerrado y con egreso registrado */
  --doc-pagado-bg:   var(--success-50);
  --doc-pagado-fg:   var(--success-700);
  --doc-pagado-mark: var(--success-500);

  /* Observado — devuelto con reparos, la pelota está en el proveedor */
  --doc-observado-bg:   var(--danger-50);
  --doc-observado-fg:   var(--danger-700);
  --doc-observado-mark: var(--danger-500);

  /* Anulado — no se ejecuta y no se borra: la trazabilidad importa */
  --doc-anulado-bg:   var(--gray-100);
  --doc-anulado-fg:   var(--gray-800);
  --doc-anulado-mark: var(--gray-600);

  /* ---- EJE 2 · Vigencia del presupuesto ---------------------------- */

  /* Borrador — se está armando, no se compra contra esto */
  --pres-borrador-bg:   var(--gray-100);
  --pres-borrador-fg:   var(--gray-800);
  --pres-borrador-mark: var(--gray-400);

  /* Vigente — el itemizado contra el que se controla y se compra */
  --pres-vigente-bg:   var(--success-50);
  --pres-vigente-fg:   var(--success-700);
  --pres-vigente-mark: var(--success-500);

  /* Con adicionales — vigente más obras extra aprobadas. Estado propio
     porque comparar el real contra el original, cuando hay adicionales
     aprobados, muestra un sobrecosto que no existe. */
  --pres-adicionales-bg:   var(--warning-50);
  --pres-adicionales-fg:   var(--warning-700);
  --pres-adicionales-mark: var(--warning-500);

  /* Cerrado — no admite movimientos; queda como base de comparación */
  --pres-cerrado-bg:   #e7edf7;
  --pres-cerrado-fg:   var(--blue-800);
  --pres-cerrado-mark: var(--blue-400);

  /* ---- EJE 3 · Cumplimiento del programa (PPC) --------------------- */
  /* Binario a propósito. El PPC es el porcentaje de tareas comprometidas
     que se cumplieron: media tarea cumplida no existe. */
  --ppc-cumplida-mark:    var(--success-500);
  --ppc-no-cumplida-mark: var(--danger-500);

  /* Umbral bajo el cual el PPC de la semana se lee como problema. 80% es
     el valor con el que trabaja Last Planner; se ajusta por obra. */
  --ppc-umbral: 80;
}

/* --------------------------------------------------------------------------
   Clases utilitarias — para HTML estático y prototipos rápidos.
   En React, usar <StatusMark> con estos tokens, igual que en postventa.
   -------------------------------------------------------------------------- */

[data-doc] {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  height: 22px;
  padding: 0 var(--space-2);
  font-family: var(--font-sans);
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  line-height: 1;
  border-radius: var(--radius-sm);
  white-space: nowrap;
}

[data-doc="emitido"]   { background: var(--doc-emitido-bg);   color: var(--doc-emitido-fg); }
[data-doc="revision"]  { background: var(--doc-revision-bg);  color: var(--doc-revision-fg); }
[data-doc="parcial"]   { background: var(--doc-parcial-bg);   color: var(--doc-parcial-fg); }
[data-doc="visado"]    { background: var(--doc-visado-bg);    color: var(--doc-visado-fg); }
[data-doc="pagado"]    { background: var(--doc-pagado-bg);    color: var(--doc-pagado-fg); }
[data-doc="observado"] { background: var(--doc-observado-bg); color: var(--doc-observado-fg); }
[data-doc="anulado"]   { background: var(--doc-anulado-bg);   color: var(--doc-anulado-fg); }

[data-presupuesto] {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  height: 22px;
  padding: 0 var(--space-2);
  font-family: var(--font-sans);
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  line-height: 1;
  border-radius: var(--radius-sm);
  white-space: nowrap;
}

[data-presupuesto="borrador"]    { background: var(--pres-borrador-bg);    color: var(--pres-borrador-fg); }
[data-presupuesto="vigente"]     { background: var(--pres-vigente-bg);     color: var(--pres-vigente-fg); }
[data-presupuesto="adicionales"] { background: var(--pres-adicionales-bg); color: var(--pres-adicionales-fg); }
[data-presupuesto="cerrado"]     { background: var(--pres-cerrado-bg);     color: var(--pres-cerrado-fg); }

/* ═══ tokens/estados-dominios.css ═══════════════════════════════════ */
/* ==========================================================================
   GARCÍA CONSTRUCTORA — Estados del dominio: los que faltaban
   --------------------------------------------------------------------------
   Postventa tenía su vocabulario, Control de Gestión ya tiene el suyo, y
   quedaban doce apps sin ninguno. Un dominio sin vocabulario no se queda sin
   estados: cada app los inventa, y catorce inventos distintos para «esta
   observación ya se resolvió» es la divergencia que este sistema vino a cerrar.

   ── PRIMERO: LO QUE NO SE DECLARA ACÁ, PORQUE YA EXISTE ──────────────────
   Este archivo agrega CINCO ejes. Antes de agregar un sexto, revisar si el
   estado que hace falta es uno de estos con otro nombre:

   | Lo que se necesita                        | Usar                        |
   |-------------------------------------------|-----------------------------|
   | OC, estado de pago, factura, cualquier    | `--doc-*`                   |
   | documento que se emite, se visa y se paga | (estados-gestion.css)       |
   | Presupuesto, itemizado, APU, línea base   | `--pres-*` (idem)           |
   | Escenario de planificación aprobado o no  | `--doc-*`: emitido /        |
   |                                           | revisión / visado           |
   | Cumplimiento de una tarea del programa    | `--ppc-*` (idem)            |
   | Caso de postventa                         | `--est-*`                   |
   |                                           | (estados-postventa.css)     |
   | Plazo contra un compromiso                | `--sla-*` (idem)            |
   | Gravedad de un hallazgo                   | `--sev-*` (idem): crítica / |
   |                                           | mayor / menor               |

   Motor de Presupuestos y Presupuesto BIM no aparecen abajo por eso: sus
   estados son de presupuesto y de documento, y ya están declarados. Reusar no
   es ahorro de trabajo, es que el mismo concepto se vea igual en dos apps.

   ── LOS COLORES NO SON NUEVOS ────────────────────────────────────────────
   Cada estado usa un par fondo/texto que el validador ya verifica en AA. No se
   inventó ningún valor: un vocabulario nuevo que además estrena colores es un
   vocabulario que hay que volver a verificar entero.
   ========================================================================== */

:root {
  /* ---- EJE 1 · Ciclo de una observación ---------------------------------
     Calidad, revisión de loteo, informes de visita y revisión de modelos IFC
     tienen todos el mismo ciclo: alguien anota un hallazgo, otro responde, y
     un tercero cierra o lo desestima. Cuatro apps, un solo vocabulario.

     No confundir con `--est-*` de postventa: un caso de postventa es un
     compromiso con el propietario y tiene garantía y SLA detrás; una
     observación es interna y se cierra entre áreas. */

  /* Abierta — anotada, sin respuesta todavía */
  --obs-abierta-bg:   var(--danger-50);
  --obs-abierta-fg:   var(--danger-700);
  --obs-abierta-mark: var(--danger-500);

  /* Respondida — el responsable contestó; falta que el que anotó verifique */
  --obs-respondida-bg:   var(--warning-50);
  --obs-respondida-fg:   var(--warning-700);
  --obs-respondida-mark: var(--warning-500);

  /* Subsanada — corregida y verificada en terreno */
  --obs-subsanada-bg:   var(--success-50);
  --obs-subsanada-fg:   var(--success-700);
  --obs-subsanada-mark: var(--success-500);

  /* Desestimada — no procedía. Estado propio y no un borrado: la próxima vez
     que alguien anote lo mismo, la respuesta ya está escrita. */
  --obs-desestimada-bg:   var(--gray-100);
  --obs-desestimada-fg:   var(--gray-800);
  --obs-desestimada-mark: var(--gray-600);

  /* ---- EJE 2 · Resultado de una inspección -----------------------------
     Checklist de calidad por recinto y protocolo de informe de visita. Es el
     resultado de un ÍTEM, no el ciclo de su observación: un ítem «no
     conforme» genera una observación, que después recorre el eje 1.

     «No aplica» es un estado y no un vacío: un recinto sin ese ítem —un baño
     sin ventana— aprobado y sin revisar se ven igual si no se distingue, y el
     porcentaje de cumplimiento sale mal. */

  --insp-conforme-bg:   var(--success-50);
  --insp-conforme-fg:   var(--success-700);
  --insp-conforme-mark: var(--success-500);

  --insp-observado-bg:   var(--warning-50);
  --insp-observado-fg:   var(--warning-700);
  --insp-observado-mark: var(--warning-500);

  --insp-no-conforme-bg:   var(--danger-50);
  --insp-no-conforme-fg:   var(--danger-700);
  --insp-no-conforme-mark: var(--danger-500);

  --insp-no-aplica-bg:   var(--gray-100);
  --insp-no-aplica-fg:   var(--gray-800);
  --insp-no-aplica-mark: var(--gray-400);

  /* Pendiente — el ítem existe y nadie lo revisó. Distinto de «no aplica». */
  --insp-pendiente-bg:   var(--blue-50);
  --insp-pendiente-fg:   var(--blue-700);
  --insp-pendiente-mark: var(--blue-500);

  /* ---- EJE 3 · Gestión de un terreno -----------------------------------
     El ciclo comercial de un paño, del primer dato al cierre. Es el único eje
     de este archivo que puede durar años, y por eso «descartado» no borra:
     un terreno descartado por precio en 2024 se reevalúa en 2027, y el
     historial es el dato que evita repetir la visita. */

  --terr-prospecto-bg:   var(--gray-100);
  --terr-prospecto-fg:   var(--gray-800);
  --terr-prospecto-mark: var(--gray-400);

  --terr-estudio-bg:   var(--blue-50);
  --terr-estudio-fg:   var(--blue-700);
  --terr-estudio-mark: var(--blue-500);

  --terr-negociacion-bg:   var(--warning-50);
  --terr-negociacion-fg:   var(--warning-700);
  --terr-negociacion-mark: var(--warning-500);

  /* Promesa firmada — comprometido y no transferido: el paño ya no se ofrece
     y todavía no es de la empresa. Los dos hechos importan. */
  --terr-promesa-bg:   #e7edf7;
  --terr-promesa-fg:   var(--blue-800);
  --terr-promesa-mark: var(--blue-400);

  --terr-comprado-bg:   var(--success-50);
  --terr-comprado-fg:   var(--success-700);
  --terr-comprado-mark: var(--success-500);

  --terr-descartado-bg:   var(--cafe-100);
  --terr-descartado-fg:   var(--cafe-800);
  --terr-descartado-mark: var(--cafe-500);

  /* ---- EJE 4 · Asistencia ----------------------------------------------
     Marcación del día. Va a remuneraciones, así que la diferencia entre
     ausente y con licencia no es de matiz: una descuenta y la otra no.

     Verde para «presente» y rojo para «ausente» sería tentador y está mal: un
     ausente con permiso aprobado no es una falla. El rojo queda solo para la
     ausencia injustificada, que es la única que alguien tiene que perseguir. */

  --asis-presente-bg:   var(--success-50);
  --asis-presente-fg:   var(--success-700);
  --asis-presente-mark: var(--success-500);

  /* Atraso — presente, fuera de hora. Es dato propio: un atraso repetido se
     gestiona distinto de una ausencia. */
  --asis-atraso-bg:   var(--warning-50);
  --asis-atraso-fg:   var(--warning-700);
  --asis-atraso-mark: var(--warning-500);

  --asis-ausente-bg:   var(--danger-50);
  --asis-ausente-fg:   var(--danger-700);
  --asis-ausente-mark: var(--danger-500);

  /* Licencia médica — justificada y con respaldo */
  --asis-licencia-bg:   var(--blue-50);
  --asis-licencia-fg:   var(--blue-700);
  --asis-licencia-mark: var(--blue-500);

  /* Permiso — autorizado por jefatura, con o sin goce */
  --asis-permiso-bg:   #e7edf7;
  --asis-permiso-fg:   var(--blue-800);
  --asis-permiso-mark: var(--blue-400);

  /* No laborable — feriado o día no pactado. No es ausencia de nadie. */
  --asis-no-laborable-bg:   var(--gray-100);
  --asis-no-laborable-fg:   var(--gray-800);
  --asis-no-laborable-mark: var(--gray-400);

  /* ---- EJE 5 · Producción de elementos ---------------------------------
     Paneles prefabricados: el ciclo de una pieza desde el despiece hasta el
     muro. Sirve para cualquier elemento que se fabrica fuera y se instala en
     obra, no solo para paneles.

     Fabricado y despachado son estados distintos a propósito: una pieza
     fabricada que no salió de planta no sirve en obra, y confundirlos hace
     ver un avance que en terreno no existe. */

  --prod-planificado-bg:   var(--gray-100);
  --prod-planificado-fg:   var(--gray-800);
  --prod-planificado-mark: var(--gray-400);

  --prod-fabricacion-bg:   var(--warning-50);
  --prod-fabricacion-fg:   var(--warning-700);
  --prod-fabricacion-mark: var(--warning-500);

  --prod-fabricado-bg:   var(--blue-50);
  --prod-fabricado-fg:   var(--blue-700);
  --prod-fabricado-mark: var(--blue-500);

  --prod-despachado-bg:   #e7edf7;
  --prod-despachado-fg:   var(--blue-800);
  --prod-despachado-mark: var(--blue-400);

  --prod-instalado-bg:   var(--success-50);
  --prod-instalado-fg:   var(--success-700);
  --prod-instalado-mark: var(--success-500);

  /* Rechazado en control — la pieza no pasó y se refabrica */
  --prod-rechazado-bg:   var(--danger-50);
  --prod-rechazado-fg:   var(--danger-700);
  --prod-rechazado-mark: var(--danger-500);
}

/* --------------------------------------------------------------------------
   Clases utilitarias — para HTML estático y prototipos rápidos.
   En React, usar <StatusMark> con estos tokens, igual que en los otros dos
   archivos de dominio.
   -------------------------------------------------------------------------- */

[data-obs], [data-inspeccion], [data-terreno], [data-asistencia], [data-produccion] {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  height: 22px;
  padding: 0 var(--space-2);
  font-family: var(--font-sans);
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  line-height: 1;
  border-radius: var(--radius-sm);
  white-space: nowrap;
}

[data-obs="abierta"]     { background: var(--obs-abierta-bg);     color: var(--obs-abierta-fg); }
[data-obs="respondida"]  { background: var(--obs-respondida-bg);  color: var(--obs-respondida-fg); }
[data-obs="subsanada"]   { background: var(--obs-subsanada-bg);   color: var(--obs-subsanada-fg); }
[data-obs="desestimada"] { background: var(--obs-desestimada-bg); color: var(--obs-desestimada-fg); }

[data-inspeccion="conforme"]    { background: var(--insp-conforme-bg);    color: var(--insp-conforme-fg); }
[data-inspeccion="observado"]   { background: var(--insp-observado-bg);   color: var(--insp-observado-fg); }
[data-inspeccion="no-conforme"] { background: var(--insp-no-conforme-bg); color: var(--insp-no-conforme-fg); }
[data-inspeccion="no-aplica"]   { background: var(--insp-no-aplica-bg);   color: var(--insp-no-aplica-fg); }
[data-inspeccion="pendiente"]   { background: var(--insp-pendiente-bg);   color: var(--insp-pendiente-fg); }

[data-terreno="prospecto"]   { background: var(--terr-prospecto-bg);   color: var(--terr-prospecto-fg); }
[data-terreno="estudio"]     { background: var(--terr-estudio-bg);     color: var(--terr-estudio-fg); }
[data-terreno="negociacion"] { background: var(--terr-negociacion-bg); color: var(--terr-negociacion-fg); }
[data-terreno="promesa"]     { background: var(--terr-promesa-bg);     color: var(--terr-promesa-fg); }
[data-terreno="comprado"]    { background: var(--terr-comprado-bg);    color: var(--terr-comprado-fg); }
[data-terreno="descartado"]  { background: var(--terr-descartado-bg);  color: var(--terr-descartado-fg); }

[data-asistencia="presente"]      { background: var(--asis-presente-bg);      color: var(--asis-presente-fg); }
[data-asistencia="atraso"]        { background: var(--asis-atraso-bg);        color: var(--asis-atraso-fg); }
[data-asistencia="ausente"]       { background: var(--asis-ausente-bg);       color: var(--asis-ausente-fg); }
[data-asistencia="licencia"]      { background: var(--asis-licencia-bg);      color: var(--asis-licencia-fg); }
[data-asistencia="permiso"]       { background: var(--asis-permiso-bg);       color: var(--asis-permiso-fg); }
[data-asistencia="no-laborable"]  { background: var(--asis-no-laborable-bg);  color: var(--asis-no-laborable-fg); }

[data-produccion="planificado"] { background: var(--prod-planificado-bg); color: var(--prod-planificado-fg); }
[data-produccion="fabricacion"] { background: var(--prod-fabricacion-bg); color: var(--prod-fabricacion-fg); }
[data-produccion="fabricado"]   { background: var(--prod-fabricado-bg);   color: var(--prod-fabricado-fg); }
[data-produccion="despachado"]  { background: var(--prod-despachado-bg);  color: var(--prod-despachado-fg); }
[data-produccion="instalado"]   { background: var(--prod-instalado-bg);   color: var(--prod-instalado-fg); }
[data-produccion="rechazado"]   { background: var(--prod-rechazado-bg);   color: var(--prod-rechazado-fg); }

/* ═══ tokens/office.css ═════════════════════════════════════════════ */
/* ==========================================================================
   GARCÍA CONSTRUCTORA — Capa Office (documentos ejecutivos)
   --------------------------------------------------------------------------
   PROBLEMA QUE RESUELVE
   Jost e IBM Plex son webfonts. No están instaladas en los equipos Windows
   de la empresa. Un PPTX o DOCX generado con esas familias se abre con
   fallback y se ve distinto en cada máquina — y peor, distinto de las
   plantillas que gerencia ya usa.

   REGLA DE MAPEO (obligatoria)
   ┌────────────────────┬──────────────────────────┬───────────────────────┐
   │ Destino            │ Tipografía               │ Color de cabecera     │
   ├────────────────────┼──────────────────────────┼───────────────────────┤
   │ Web / app / mock   │ Jost + IBM Plex          │ Azul #2871B8          │
   │ PPTX / DOCX / XLSX │ Calibri                  │ Navy #1F3864          │
   │ Impresión de marca │ Century Gothic (si hay   │ Azul #2871B8          │
   │ (folletos, papel.) │ licencia) → si no, Jost  │                       │
   └────────────────────┴──────────────────────────┴───────────────────────┘

   El navy #1F3864 NO reemplaza al azul de marca. Es la tinta institucional
   de documento: cabeceras, títulos de lámina, bordes de tabla. El azul
   #2871B8 sigue siendo el acento de marca dentro de esos documentos
   (barras de gráfico, destacados, línea de acento). Nunca al revés.
   ========================================================================== */

:root {
  /* ---- Familias Office --------------------------------------------- */
  --office-font-display: Calibri, 'Segoe UI', system-ui, sans-serif;
  --office-font-body:    Calibri, 'Segoe UI', system-ui, sans-serif;
  --office-font-data:    'Consolas', 'Courier New', monospace;

  /* ---- Tinta institucional (papelería / plantillas) ---------------- */
  --office-ink:        #1f3864;  /* navy — cabeceras y títulos. 11,6:1 con blanco */
  --office-ink-soft:   #2f4a7a;
  --office-accent:     #4472c4;  /* azul de plantilla heredado — datos secundarios */
  --office-surface:    #f2f2f2;  /* gris de fondo de lámina */
  --office-rule:       #d9d9d9;

  /* El azul de marca sigue disponible dentro del documento */
  --office-brand:      var(--blue-500);

  /* ---- Escala de tamaños para lámina 16:9 -------------------------- */
  --office-fs-titulo:    28pt;
  --office-fs-subtitulo: 18pt;
  --office-fs-cuerpo:    14pt;
  --office-fs-nota:      10pt;
  --office-fs-tabla:     11pt;
}

/* --------------------------------------------------------------------------
   Modo documento. Envolver la pieza en [data-modo="office"] para que herede
   Calibri y navy sin tocar el resto del sistema.
   -------------------------------------------------------------------------- */
[data-modo="office"] {
  font-family: var(--office-font-body);
  color: var(--gray-800);
  background: var(--gray-0);
}

[data-modo="office"] h1,
[data-modo="office"] h2,
[data-modo="office"] h3,
[data-modo="office"] h4 {
  font-family: var(--office-font-display);
  color: var(--office-ink);
  letter-spacing: 0;
}

[data-modo="office"] .num,
[data-modo="office"] [data-num] {
  font-family: var(--office-font-data);
  font-variant-numeric: tabular-nums;
}

[data-modo="office"] .eyebrow {
  font-family: var(--office-font-display);
  color: var(--office-ink);
}

[data-modo="office"] table { border-color: var(--office-rule); }
[data-modo="office"] thead  { background: var(--office-ink); color: #fff; }
[data-modo="office"] tbody tr:nth-child(even) { background: var(--office-surface); }

/* --------------------------------------------------------------------------
   FORMATO NUMÉRICO CHILENO — obligatorio en todo entregable
   Miles con punto, decimales con coma:  $1.842.000.000 · 12,4% · 1.250,50 m²
   En JS:  new Intl.NumberFormat('es-CL').format(n)
   En Excel: formato "#.##0" con configuración regional es-CL.
   -------------------------------------------------------------------------- */

/* ═══ tokens/nav.css ════════════════════════════════════════════════ */
/* ==========================================================================
   GARCÍA CONSTRUCTORA — EL PIE DEL MENÚ LATERAL
   --------------------------------------------------------------------------
   POR QUÉ ESTAS CLASES EXISTEN, Y NO SON ESTILOS EN LÍNEA COMO EL RESTO DEL NAV.

   Dos motivos, y el segundo es el que decide:

   1 · HOVER Y FOCO NO SE PUEDEN ESCRIBIR EN LÍNEA. Estas filas dejaron de tener
       caja —ver abajo—, así que la única señal de que son accionables aparece al
       pasar el mouse o al llegar con el teclado. Sin `:hover` y `:focus-visible`
       serían texto que no parece un control.

   2 · LAS DOS FILAS TIENEN QUE COINCIDIR POR CONSTRUCCIÓN. En el pie hay un botón
       —fijar el menú— y un enlace —volver al ecosistema—, que son elementos
       distintos y viven en archivos distintos: el botón en `SideNav`, el enlace en
       `EnlaceEcosistema`. Con estilos en línea, coincidir dependía de que alguien
       copiara bien los diez valores, y de que los siguiera copiando. Una clase
       compartida hace que no se puedan desalinear.

   EL CAMBIO QUE ORIGINÓ ESTO (20-08-2026). Lo pidió Martín mirando la pantalla:
   «el fijar menú abierto y volver al ecosistema son botones muy grandes y feos».
   Y tenía razón — el enlace al ecosistema llevaba borde, negrita y
   `--control-h-md`, o sea que se leía como el control más importante del menú
   cuando es el que menos se aprieta. Además había DOS separadores apilados, uno
   por bloque, que partían el pie en dos franjas.

   Medido antes y después, con la hoja real: el pie pasó de 138px a 107px, de dos
   separadores a uno, y de un botón con caja a dos filas del mismo registro.

   LO QUE NO SE TOCÓ, Y ES DELIBERADO:

   · `min-height: var(--touch-min)`. La maqueta original tenía 32px fijos y eso
     viola el piso táctil: el token vale 32px en oficina con mouse, pero 48px con
     puntero grueso o en perfil terreno, y `npm run modos` exige 44px como mínimo
     de WCAG 2.5.5 en esos casos. Con el token, la fila se agranda sola donde hace
     falta. Medido: 44px en oficina, 48px en terreno.

   · `font-size: var(--fs-ui-sm)`, y no un tamaño fijo más chico. Se dibujaron las
     dos: 11px se veía más sutil, pero `--fs-ui-sm` crece de 13 a 14px en terreno y
     un valor fijo no — dejar el pie en 11px lo habría encogido, en relación al
     resto, justo en la pantalla de obra. La subordinación la dan el color tenue, la
     falta de caja y el separador único; no hace falta comprarla con tamaño.
     Medido después: el pie queda del mismo tamaño que los ítems del menú en los
     tres perfiles —13/13, 14/14, 13/13— que es lo que corresponde a un pie.
   ========================================================================== */

/* El pie completo: un solo bloque y un solo separador, aunque tenga dos filas. */
.gc-nav-pie {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: var(--space-2);
  border-top: 1px solid var(--nav-border);
}

/* La fila, igual para el botón y para el enlace.

   EL SELECTOR LLEVA DOS CLASES, Y ES POR UN CASO MEDIDO EN TERRENO. `densidad.css`
   tiene una regla —«todo control interactivo garantiza el target mínimo en
   terreno»— que a `button` le pone `min-height: var(--touch-min)` y
   `font-size: var(--fs-ui)`, o sea 16px. Su especificidad (0,1,1) le ganaba a una
   sola clase (0,1,0), así que en el perfil terreno el pie quedaba con el BOTÓN en
   16px y el ENLACE en 14px: dos filas del mismo bloque con distinto tamaño.
   Medido, no supuesto — se vio renderizando los tres perfiles.

   Y el número que decide cuál gana: los ítems del menú son `--fs-ui-sm`, o sea
   14px en terreno. Con la regla del sistema, el pie se dibujaba MÁS GRANDE que el
   menú que tiene encima, que es justo al revés de lo que un pie tiene que ser.

   El `min-height` de esa regla sí se respeta y no se toca: es el piso táctil, y
   esta fila ya lo declara igual. Lo que se ajusta es sólo el tamaño de letra, para
   un par de filas de navegación que no son un campo de formulario — el 16px de esa
   regla está ahí para evitar el zoom automático de iOS, que ocurre en entradas de
   texto, no en un enlace del pie del menú. */
.gc-nav-pie .gc-nav-pie-fila,
.gc-nav-pie-fila {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  width: 100%;
  min-height: var(--touch-min);
  padding: 0 var(--space-2);
  box-sizing: border-box;
  background: transparent;
  border: 0;
  border-radius: var(--radius-control);
  color: var(--nav-text-muted);
  font-family: var(--font-sans);
  font-size: var(--fs-ui-sm);
  text-align: left;
  text-decoration: none;
  cursor: pointer;
}

/* La única señal de que es accionable, ahora que no hay caja. */
.gc-nav-pie-fila:hover {
  background: color-mix(in srgb, var(--nav-text) 6%, transparent);
  color: var(--nav-text);
}

/* SÓLO EL COLOR: EL ANILLO YA LO PONE EL SISTEMA. `responsive.css` tiene una regla
   global —`:where(a, button, …):focus-visible`— con el anillo de 2px en
   `--border-focus`, y estas filas son `a` y `button`, así que lo reciben solas.
   La primera versión de este archivo lo volvió a declarar acá, con un
   `var(--focus-ring)` que NO EXISTE como token: se lo inventó. Duplicar una
   decisión del sistema con un nombre nuevo es cómo terminan habiendo dos estilos
   de foco y ninguno canónico. */
.gc-nav-pie-fila:focus-visible {
  color: var(--nav-text);
}

/* El hueco del ícono, del mismo ancho que el de los ítems del menú, para que los
   rótulos del pie queden alineados con los de arriba y no medio píxel corridos. */
.gc-nav-pie-icono {
  width: 22px;
  height: 22px;
  display: grid;
  place-items: center;
  flex: none;
}

.gc-nav-pie-icono > svg {
  width: 15px;
  height: 15px;
}

/* ==========================================================================
   EL BLOQUE DE IDENTIDAD DE LA BARRA SUPERIOR
   --------------------------------------------------------------------------
   Estilos de `IdentidadSesion`. Son clases y no estilos en línea por los dos
   mismos motivos que el pie del menú, más uno propio:

   1 · HOVER Y FOCO NO SE ESCRIBEN EN LÍNEA. Los dos controles son botones de
       ícono sin caja: sin `:hover` no se ve que son accionables.

   2 · EL CANDADO Y LA SALIDA TIENEN QUE COINCIDIR POR CONSTRUCCIÓN. Uno es un
       `<button>` suelto y el otro va dentro de un `<form>`; con estilos en línea,
       coincidir dependía de copiar bien los ocho valores dos veces.

   3 · Y EL PISO TÁCTIL TIENE QUE SALIR DEL TOKEN. El patrón copiado en las doce
       apps usaba `padding: 4` con un ícono de 16px, o sea 24px de alto. Medido en
       el navegador: eso queda BAJO el `--touch-min` del propio perfil en las
       combinaciones de escritorio —24 contra 32 en oficina con mouse, 24 contra
       44 con puntero grueso a 1440—. En pantalla angosta no se veía porque
       `densidad.css` ya empuja todo `button` a 48px, así que el defecto vivía
       justo en la configuración más común de la oficina.
   ========================================================================== */

.gc-identidad {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-width: 0;
}

/* La chapa con la inicial. La usa una app —`cgarcia-paneles`— y va acá para que
   no sea la única con una circunferencia dibujada a mano. */
.gc-identidad-chapa {
  width: 26px;
  height: 26px;
  flex: none;
  border-radius: 50%;
  background: var(--cafe-600);
  color: var(--gray-0);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: var(--fs-xs);
  font-weight: 700;
}

.gc-identidad-texto {
  display: flex;
  flex-direction: column;
  line-height: 1.2;
  min-width: 0;
}

/* `nowrap` NO ES POR LA ELIPSIS —se midió: un correo no tiene espacios, así que
   no tiene dónde envolver y la elipsis funciona sin él—. Es por el ancho: sin
   `nowrap` + `min-width: 0`, un correo largo no encoge y empuja el botón fuera de
   la pantalla en un teléfono. Está medido en `cgarcia-paneles`, que lo dejó
   escrito: la barra pedía 427px en una de 390. */
.gc-identidad-correo {
  font-size: var(--fs-ui-sm);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}

.gc-identidad-rotulo {
  font-size: var(--fs-2xs);
  color: var(--topbar-fg-muted);
}

/* EL SELECTOR LLEVA DOS CLASES por el mismo caso medido que el pie del menú:
   `densidad.css` tiene `[data-densidad="terreno"] button { … }` con especificidad
   (0,1,1), que le gana a una sola clase (0,1,0). Acá el `min-height` de esa regla
   coincide con el de esta —los dos son `--touch-min`—, así que no hay conflicto
   hoy; el selector doblado es para que siga sin haberlo si mañana esa regla
   cambia de valor. El `font-size` de esa regla no molesta: estos botones no
   llevan texto. */
.gc-identidad .gc-identidad-boton,
.gc-identidad-boton {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: var(--touch-min);
  min-height: var(--touch-min);
  padding: 0;
  background: transparent;
  border: 0;
  border-radius: var(--radius-control);
  color: var(--topbar-fg-muted);
  cursor: pointer;
}

/* La única señal de que es accionable, igual que en el pie del menú. */
.gc-identidad-boton:hover {
  background: color-mix(in srgb, var(--topbar-fg) 8%, transparent);
  color: var(--topbar-fg);
}

/* SÓLO EL COLOR: el anillo de foco lo pone `responsive.css` con su regla global
   `:where(a, button, …):focus-visible` en `--border-focus`, y esto es un
   `<button>`. Redeclararlo acá con otro nombre es cómo terminan habiendo dos
   estilos de foco y ninguno canónico. */
.gc-identidad-boton:focus-visible {
  color: var(--topbar-fg);
}

/* ==========================================================================
   EL ALFILER DEL MENÚ, Y LA ENTRADA DEL PANEL ASOMADO
   --------------------------------------------------------------------------
   Los dos salieron del mismo pedido de Martín, mirando la pantalla el
   20-08-2026: el pie se leía «tosco y sobredimensionado», y el asomo «es un
   salto, uno pasa por arriba sin querer y se te abre y te tapa».
   ========================================================================== */

/* Sólo ícono, sin caja, y con el piso táctil desde el token — el mismo criterio
   que los botones de la identidad. Antes era una fila con rótulo en el pie. */
.gc-nav-alfiler {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: var(--touch-min);
  min-height: var(--touch-min);
  padding: 0;
  background: transparent;
  border: 0;
  border-radius: var(--radius-control);
  color: var(--nav-text-muted);
  cursor: pointer;
}

.gc-nav-alfiler > svg {
  width: 17px;
  height: 17px;
}

.gc-nav-alfiler:hover {
  background: color-mix(in srgb, var(--nav-text) 8%, transparent);
  color: var(--nav-text);
}

/* Fijado es un estado, no un hover: se queda marcado para que se lea sin pasar
   el mouse. `aria-pressed` ya lo dice a quien escucha; esto lo dice a quien ve. */
.gc-nav-alfiler[aria-pressed="true"] {
  color: var(--nav-item-active-fg);
  background: var(--nav-item-active-bg);
}

/* SÓLO EL COLOR: el anillo de foco lo pone la regla global de `responsive.css`. */
.gc-nav-alfiler:focus-visible {
  color: var(--nav-text);
}

/* ── LA ENTRADA DEL PANEL ─────────────────────────────────────────────────────
   POR QUÉ ES SEGURO ANIMAR ESTO Y NO EL FIJADO. Más arriba en `SideNav.jsx` está
   escrito que animar el ancho del menú rompe la medición: el shell mide su
   contenedor con un ResizeObserver, la transición cambia el content box, el
   observer dispara, y la transición se recrea en t=0 para siempre. Eso vale para
   el FIJADO, que sí está en el flujo.

   El panel asomado NO está en el flujo: va en `position: absolute` sobre un
   hueco de 72px que se queda quieto. Así que su entrada no cambia el content box
   de nadie y se puede animar sin tocar ese bucle. Es la misma propiedad que hace
   que el contenido no se mueva la que permite animarlo.

   DOS ANIMACIONES Y NO UNA, por la regla 6 de `npm run motion`: el bloque de
   `prefers-reduced-motion` tiene que quitar el DESPLAZAMIENTO y no la
   retroalimentación. Separadas, se puede apagar el deslizamiento y dejar la
   opacidad, que es señal de estado y no movimiento. Juntas en un solo keyframe
   habría que elegir entre apagar las dos o ninguna.

   Y la opacidad arranca en 0,6 y no en 0: el panel se dibuja sobre el rail, y
   desde 0 se ve el rail entero por debajo durante 120ms, que parpadea. Desde 0,6
   se lee como que aparece, no como que titila. */
@keyframes gc-asomar-opacidad {
  from { opacity: 0.6; }
  to   { opacity: 1; }
}

@keyframes gc-asomar-desliz {
  from { transform: translateX(-6px); }
  to   { transform: none; }
}

.gc-sidebar[data-asomado] {
  animation:
    gc-asomar-opacidad var(--dur-fast) var(--ease-out),
    gc-asomar-desliz var(--dur-fast) var(--ease-out);
}

@media (prefers-reduced-motion: reduce) {
  /* Se queda la opacidad —es la señal de que algo apareció— y se va el
     desplazamiento, que es lo único que aquí es movimiento. */
  .gc-sidebar[data-asomado] {
    animation-name: gc-asomar-opacidad;
  }
}

/* ==========================================================================
   LA SALIDA AL ECOSISTEMA, EN LA BARRA SUPERIOR
   --------------------------------------------------------------------------
   Vivía en el pie del menú, escrita a mano en las once apps. Dos problemas
   medidos con ese lugar: obligaba a que existiera un pie en todas las pantallas
   de todas las apps, y el pie no se dibuja en rail —el estado por omisión—, así
   que la salida faltaba justamente cuando más se necesita.

   En la barra no cuesta nada, y está medido: a 1920 y a 1440 la barra mide 54px
   con el botón y sin él, en las once y también en postventa, que es la única que
   la tiene llena. Se midieron 32 combinaciones de app × forma × ancho: ninguna
   desborda y en ninguna el control baja a un segundo renglón.
   ========================================================================== */

.gc-appshell-portal {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-height: var(--touch-min);
  padding: 0 var(--space-2);
  border-radius: var(--radius-control);
  color: var(--topbar-fg-muted);
  font-family: var(--font-sans);
  font-size: var(--fs-ui-sm);
  text-decoration: none;
  white-space: nowrap;
}

.gc-appshell-portal > svg {
  width: 15px;
  height: 15px;
  flex: none;
}

.gc-appshell-portal:hover {
  background: color-mix(in srgb, var(--topbar-fg) 8%, transparent);
  color: var(--topbar-fg);
}

/* SÓLO EL COLOR: el anillo lo pone la regla global de `responsive.css`. */
.gc-appshell-portal:focus-visible {
  color: var(--topbar-fg);
}

/* EL TAMAÑO DE LA LETRA — tres botones con la misma presencia que el portal:
   mismo alto de toque, misma tinta apagada, mismo hover. No es una acción de la
   pantalla, así que no compite con ellas: se lee como parte de la barra. */
/* EL FACTOR DEL CONTROL DE LETRA. El marco lo escribe EN LÍNEA sobre
   .gc-appshell-main cuando la letra no está al 100 %, junto con el `zoom`. Acá
   vale 1 para que un var() fuera del marco no dependa de su respaldo. Un largo
   en vh/vw dentro del contenido se divide por él y mide lo mismo a cualquier
   escala: calc(86vh / var(--gc-letra, 1)). Ver «EL TAMAÑO DE LA LETRA» en
   components/nav/AppShell.jsx. */
:root {
  --gc-letra: 1; /* @kind other */
}

.gc-appshell-letra {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: 1px;
}

.gc-appshell-letra > button {
  min-height: var(--touch-min);
  min-width: var(--touch-min);
  padding: 0 var(--space-1);
  border: 0;
  border-radius: var(--radius-control);
  background: transparent;
  color: var(--topbar-fg-muted);
  font-family: var(--font-sans);
  font-size: var(--fs-ui-sm);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  cursor: pointer;
}

.gc-appshell-letra > .gc-appshell-letra-boton {
  font-weight: 600;
}

.gc-appshell-letra > button:hover:not(:disabled) {
  background: color-mix(in srgb, var(--topbar-fg) 8%, transparent);
  color: var(--topbar-fg);
}

.gc-appshell-letra > button:focus-visible {
  color: var(--topbar-fg);
}

/* Deshabilitado = en el tope, o ya en el tamaño normal. Se atenúa pero no
   desaparece: que el botón se esfume al llegar al 140 % haría saltar la barra. */
.gc-appshell-letra > button:disabled {
  cursor: default;
  opacity: 0.45;
}

/* Bajo 900 el porcentaje cede, igual que el rótulo del portal: quedan A− y A+,
   que es lo que hace falta para leer mejor en un teléfono. */
@media (max-width: 900px) {
  .gc-appshell-letra > .gc-appshell-letra-valor {
    display: none;
  }
}

/* EL RÓTULO CEDE ANTES QUE EL CONTROL. Medido a 390px: la ranura del contexto
   envuelve a su propio renglón y cuesta 60px, y cuesta lo MISMO con el texto
   largo que con sólo el ícono —lo que envuelve es la ranura, no su contenido—.
   Por eso el marco no lo dibuja bajo 640. Entre 640 y 900, donde sí se dibuja y
   el ancho ya aprieta, se queda el chevron: la dirección sola dice «salir». */
@media (max-width: 900px) {
  .gc-appshell-portal-rotulo {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
}

/* LA IDENTIDAD CEDE ANTES QUE LA BARRA. Cuando la barra no entra en una línea,
   el marco pone `data-identidad="compacta"` y el texto de la identidad —el correo
   y su rótulo— sale de la vista: se quedan la inicial y los botones de la sesión.
   Sale de la VISTA y no del documento, igual que el rótulo del portal: un lector
   de pantalla lo sigue leyendo. Lo decide la medida y no un breakpoint, porque
   cuánto aprieta depende de qué puso cada app en su barra. Ver «LA IDENTIDAD
   CEDE ANTES QUE LA BARRA» en components/nav/AppShell.jsx.
   `!important` porque la identidad la escribe la app, casi siempre con estilos en
   línea, y un estilo en línea le gana a cualquier regla sin él. */
.gc-appshell-topbar[data-identidad="compacta"] .gc-appshell-identidad .gc-identidad-texto,
.gc-appshell-topbar[data-identidad="compacta"] .gc-appshell-identidad [data-gc-identidad-texto] {
  position: absolute !important;
  width: 1px !important;
  height: 1px !important;
  margin: 0 !important;
  padding: 0 !important;
  overflow: hidden !important;
  clip-path: inset(50%) !important;
  white-space: nowrap !important;
}

/* LA INICIAL QUE PONE EL MARCO al compactar, cuando la app no dibuja una: es la
   `.gc-identidad-chapa` de siempre. Va como hija de la zona derecha, que separa
   con `--space-4`; dentro de una identidad la chapa queda a `--space-3` de lo que
   sigue. El margen negativo la acerca a esa distancia, para que se lea junto a los
   botones de la sesión y no suelta entre el control de letra y el candado. */
.gc-appshell-chapa {
  margin-inline-end: calc(var(--space-3) - var(--space-4));
}

/* EL INICIO EN CELULAR, que el portal no tiene.

   Bajo 640 el marco no dibuja la salida al ecosistema porque la ranura envuelve
   y cuesta un renglón entero. El inicio de la app SÍ se dibuja ahí —lo pidió el
   dueño, que usa el ecosistema desde el teléfono— y para que no cueste lo mismo
   se le saca todo lo que ocupa ancho sin decir nada: el rótulo ya se fue a los
   900px, y acá cede también el relleno lateral. Queda un objetivo de 44px, que
   es el mínimo de toque, y nada más.

   Va DESPUÉS de la media query de 900 a propósito: la de arriba esconde el
   rótulo de los dos controles y ésta sólo ajusta el que sobrevive. */
@media (max-width: 639px) {
  .gc-appshell-inicio {
    padding: 0;
    min-width: var(--touch-min);
  }
}

/* ==========================================================================
   LA BARRA DE DESPLAZAMIENTO DEL MENÚ NO SE COME EL RAIL
   --------------------------------------------------------------------------
   El rail mide `--sidebar-w-rail` (72px). La barra de desplazamiento que dibuja
   el sistema operativo mide unos 15px en Windows y en Linux, así que **un quinto
   del ancho** se lo llevaba la barra: los íconos quedaban descentrados y el rail
   se veía a medio terminar. Reportado con captura desde postventa el 28-08-2026.

   No se pudo arreglar antes por una razón mecánica: la caja de la lista eran
   estilos en línea puros, y `::-webkit-scrollbar` es un pseudo-elemento — no
   existe forma de escribirlo en línea. Por eso el cambio viene en dos partes: la
   clase `gc-sidebar-lista` en `SideNav.jsx` y esta regla.

   POR QUÉ FINA Y NO ESCONDIDA. Esconderla del todo (`scrollbar-width: none`)
   dejaría el rail más limpio, y es justo lo que no se puede hacer: cuando los
   destinos no caben —que es el caso que motivó el reporte— la barra es **el único
   rastro** de que hay más abajo. Un menú que esconde destinos es exactamente lo
   que la cabecera de `SideNav.jsx` dice que este componente no puede ser. Fina se
   ve poco y sigue diciendo dónde estás.

   El color sale de `--nav-border`, que es la misma hairline que separa los grupos:
   la barra pertenece al menú, no al navegador.
   ========================================================================== */
.gc-sidebar-lista {
  scrollbar-width: thin;
  scrollbar-color: var(--nav-border) transparent;
}

.gc-sidebar-lista::-webkit-scrollbar {
  width: 6px;
}

.gc-sidebar-lista::-webkit-scrollbar-track {
  background: transparent;
}

.gc-sidebar-lista::-webkit-scrollbar-thumb {
  background: var(--nav-border);
  /* `--radius-full`, que es el token que existe. La primera versión de esta
     regla pedía `--radius-pill`, que el sistema NO declara: el respaldo de 999px
     la salvaba, así que se dibujaba bien y nadie lo veía. Un nombre inventado con
     respaldo es una deuda silenciosa — el día que alguien le saque el respaldo,
     la declaración entera se descarta. Es el mismo defecto que dejó dos campos
     con padding 0 el 28-08, sin el respaldo que lo tapara. */
  border-radius: var(--radius-full);
}

/* El pulgar sólo se refuerza cuando la persona está en el menú: en reposo es una
   hairline más, y al recorrerlo se vuelve agarrable. */
.gc-sidebar-lista:hover::-webkit-scrollbar-thumb {
  background: var(--nav-text-muted);
}

/* EN EL RAIL LA BARRA NO SE DIBUJA: EL RASTRO PASA A SER UNA SOMBRA (2.30.0).
   Fina y todo, en Windows la barra mide 10px, y el rail mide 72. Medido el
   23-09-2026 en postventa con los tres grupos abiertos: a 1280×720 la lista pide
   690px y tiene 640, la barra aparece, y a la abreviatura de cada grupo le quedan
   15px de los 21 a 24 que pide —«OPE» se leía «OP», «ADM» «AD»—, y la barra, con
   sus flechas, ocupaba el costado derecho del rail. Reportado con captura por el dueño:
   «colapsado se ve un poco raro». Pasa en cualquier pantalla de menos de ~820px
   de alto con los grupos abiertos, no sólo en un notebook.

   El motivo de arriba sigue en pie —cuando los destinos no caben, algo tiene que
   decir que hay más—, así que lo que cambia es QUÉ lo dice: una sombra en el
   borde que tiene más contenido. Abajo al principio, arriba al final, las dos en
   el medio. Son cuatro fondos, sin JavaScript: dos sombras pegadas a la caja
   (`scroll`) y dos tapas del color del menú pegadas al contenido (`local`), que
   las cubren cuando no hay nada más de ese lado. La lista se sigue desplazando
   con la rueda, el dedo o el teclado. Al asomar el menú vuelve la barra fina:
   ahí hay ancho de sobra y la barra dice además dónde estás. */
.gc-sidebar[data-modo="rail"]:not([data-asomado]) .gc-sidebar-lista {
  scrollbar-width: none;
}
.gc-sidebar[data-modo="rail"]:not([data-asomado]) .gc-sidebar-lista::-webkit-scrollbar {
  display: none;
}
.gc-sidebar-lista {
  background:
    linear-gradient(var(--nav-surface) 30%, transparent) top / 100% var(--space-8) no-repeat local,
    linear-gradient(transparent, var(--nav-surface) 70%) bottom / 100% var(--space-8) no-repeat local,
    radial-gradient(farthest-side at 50% 0, color-mix(in srgb, var(--nav-text) 22%, transparent), transparent) top / 100% var(--space-3) no-repeat scroll,
    radial-gradient(farthest-side at 50% 100%, color-mix(in srgb, var(--nav-text) 22%, transparent), transparent) bottom / 100% var(--space-3) no-repeat scroll;
}

/* --------------------------------------------------------------------------
   SALTAR AL CONTENIDO (WCAG 2.4.1)

   El primer enlace del marco. Fuera de la vista hasta que recibe el foco, y
   entonces aparece arriba a la izquierda, sobre todo lo demás: quien navega con
   teclado lo ve en el primer Tab y quien usa el mouse no lo ve nunca. Se corre
   con `transform` y no con `display: none`, que lo sacaría del orden de Tab.
   El anillo es el global de `:focus-visible`; acá sólo va la caja.
   -------------------------------------------------------------------------- */
.gc-saltar {
  position: absolute;
  top: var(--space-2);
  left: var(--space-2);
  z-index: var(--z-toast);
  padding: var(--space-2) var(--space-4);
  min-height: var(--touch-min);
  display: inline-flex;
  align-items: center;
  background: var(--surface-card);
  color: var(--text-body);
  border: 1px solid var(--border-default);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
  font: inherit;
  font-weight: var(--fw-semibold);
  text-decoration: none;
  transform: translateY(calc(-100% - var(--space-4)));
}
.gc-saltar:focus { transform: none; }
/* El destino recibe el foco para que el Tab siguiente parta desde ahí; no es un
   control, así que no lleva anillo. */
.gc-appshell-main:focus { outline: none; }

/* Una zona de scroll enfocable (`TableroTemplate`, 2.33.0) muestra el anillo
   POR DENTRO: por fuera lo corta el `overflow` del marco. */
.gc-zona-scroll:focus-visible {
  outline: 2px solid var(--border-focus);
  outline-offset: -2px;
}

/* ═══ tokens/base.css ═══════════════════════════════════════════════ */
/* ==========================================================================
   GARCÍA CONSTRUCTORA — Base element defaults
   Applied on top of the tokens. Keep light: tokens do the heavy lifting.
   ========================================================================== */

/* ── LOS DEFAULTS DE ELEMENTO VAN EN `@layer base`, Y NO SUELTOS ──────────
   No es cosmético: es la diferencia entre que una app pueda cambiarle el color a
   un enlace y que no pueda.

   EL DEFECTO, MEDIDO EL 19-08-2026. `a { color: var(--text-link) }` estaba fuera
   de toda capa. Las apps usan Tailwind v4, que pone TODO en capas —`theme`,
   `base`, `components`, `utilities`—, y en el cascade de CSS **lo que está fuera
   de capa le gana a cualquier cosa que esté dentro, sin importar la
   especificidad**. O sea que `.text-white` (0,1,0) perdía contra `a` (0,0,1).

   Consecuencia: en las once apps, NINGUNA utilidad `text-*` podía cambiar el
   color de un `<a>`. Lo encontró Martín en la barra de submódulos de
   `cgarcia-control-gestion`: la pestaña activa pinta `bg-blue-600` y declara
   `text-white`, y el rótulo salía del MISMO color que su fondo —los dos
   `rgb(33,96,159)`, medido— o sea invisible. «Se come los nombres de las
   pestañas al ser del mismo color», y era exactamente eso.

   Con el bloque en `@layer base`, las utilidades —que Tailwind pone en
   `utilities`, declarada después— vuelven a ganar. El orden de las capas lo fija
   la primera aparición: esta hoja se importa antes de `globals.css`, así que
   declara `base` primero y `utilities` queda después. Y para una app SIN Tailwind
   no cambia nada malo: su CSS propio, que está fuera de capa, sigue ganándole a
   estos defaults, que es lo que corresponde.

   Las clases de abajo —`.num`, `.eyebrow` y las demás— quedan fuera de la capa a
   propósito: son piezas del sistema, no defaults del navegador, y su precedencia
   frente al CSS de la app no es lo que este arreglo discute. */
@layer base {
  *, *::before, *::after { box-sizing: border-box; }

  body {
    margin: 0;
    font-family: var(--font-sans);
    font-size: var(--fs-md);
    line-height: var(--lh-normal);
    color: var(--text-body);
    background: var(--surface-page);
    -webkit-font-smoothing: antialiased;
    text-rendering: optimizeLegibility;
  }

  /* ── LAS TRES VOCES, Y DÓNDE ENTRA CADA UNA ───────────────────────────────
     Acá vivía `h1, h2, h3, h4, h5, h6 { font-family: var(--font-display) }`, o
     sea que TODO encabezado hablaba con la voz de marca. Una revisión externa lo
     marcó y tiene razón: la geométrica de la marca le da carácter al título de una
     pantalla, a una cifra grande o a una portada, pero puesta también en el título
     de cada tarjeta y de cada sección hace que todo suene igual — y cuando todo
     enfatiza, nada enfatiza.

     MEDIDO ANTES DE CAMBIAR, en los 21 arquetipos: 21 H1 con la cara de marca y 27
     H2, de los cuales 26 YA se dibujaban en sans porque el componente lo declara
     inline. O sea que el sistema ya hacía casi lo correcto y esta regla decía otra
     cosa. A quien afecta de verdad es a las 17 apps: un `<h2>` escrito a mano en
     una app recibía la voz de marca sin que nadie lo hubiera decidido.

       · DISPLAY (--font-display) → H1, momentos de marca, cifras grandes. Las
         plantillas ya lo declaran en su título de página, que es lo correcto.
       · SANS (--font-sans) → H2 a H6: títulos de sección, de tarjeta, de filtro,
         de formulario, de tabla. Es la voz operativa, la que se lee todo el día.
       · MONO (--font-mono) → sólo dato. Ver la tarjeta «Regla de tipografía».

     Para un encabezado que SÍ es un momento de marca en cualquier nivel está
     `.titulo-marca`: explícito, y por eso no se pone por accidente.            */
  h1, h2, h3, h4, h5, h6 {
    font-family: var(--font-sans);
    font-weight: var(--fw-semibold);
    line-height: var(--lh-tight);
    color: var(--text-strong);
    margin: 0;
  }

  h1, .titulo-marca {
    font-family: var(--titulo-font);
    font-weight: var(--titulo-fw);
    line-height: var(--titulo-lh);
    letter-spacing: var(--titulo-ls);
  }

  p { margin: 0; text-wrap: pretty; }

  a { color: var(--text-link); text-decoration: none; }
  a:hover { text-decoration: underline; }

  /* ── UN BOTÓN NO HEREDA LA TIPOGRAFÍA, Y ESO NO ERA UNA OPINIÓN ────────────
     Medido el 17-09-2026 en un navegador de verdad, cargando SÓLO esta hoja y
     nada más, sobre elementos desnudos:

       <p>         system-ui   14px      ← la voz operativa del sistema
       <button>    Arial       13,33px   ← el valor por omisión del navegador
       <input>     Arial       13,33px
       <select>    Arial       13,33px
       <textarea>  monospace   13,33px

     Los controles de formulario no heredan `font` de su contenedor: es una
     regla vieja del navegador, anterior a que existiera la herencia que hoy uno
     da por hecha. Así que cada `<button>` y cada `<input>` escritos a mano en
     una app —que son muchos: el paquete cubre las piezas, no cada formulario—
     salían en Arial mientras el texto de al lado salía en la del sistema.

     APARECIÓ ADOPTANDO, que es el único lugar donde podía aparecer. La copia
     congelada de esta misma hoja que arrastraba `capacitaciones` traía la
     línea; al borrar la copia para que la app dejara de ignorar al sistema, los
     botones se fueron a Arial y quedó a la vista que la línea le faltaba AL
     SISTEMA. Ninguna de las trece la declara por su cuenta.

     VA EN `@layer base` Y NO SUELTA, como todo lo de acá: una app que quiera
     otra cosa en su propio CSS le gana sin pelear con la especificidad, que es
     justo el contrato de esta capa.                                          */
  button, input, select, textarea, optgroup { font: inherit; color: inherit; }
}

/* Tabular figures everywhere numbers appear in data */
.num, [data-num] {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}

/* Brand eyebrow / overline — wide tracking like the logo's CONSTRUCTORA */
.eyebrow {
  font-family: var(--font-display);
  font-weight: var(--fw-semibold);
  font-size: var(--fs-xs);
  letter-spacing: var(--ls-wider);
  text-transform: uppercase;
  color: var(--brand-primary);
}

::selection { background: var(--blue-200); color: var(--gray-900); }

/* ── Dos cosas que Tailwind 4 dejó de hacer ───────────────────────────────
   No son estética: son comportamiento que la gente ya tenía y perdió.

   1 · LA MANITO SOBRE LOS BOTONES. Tailwind 4 dejó de poner `cursor: pointer`
       a propósito, alineándose con el botón nativo del navegador. En una
       herramienta de trabajo con mucho clic, perder la manito es un retroceso
       de uso: lo que se puede apretar tiene que parecer que se puede apretar.

   2 · EL COLOR DEL TEXTO DE AYUDA. Tailwind 3 pintaba `::placeholder` con un
       gris propio; la 4 usa «el color del texto al 50 %», que en un formulario
       sobre papel cálido queda más oscuro y compite con el valor escrito.
       Acá va `--text-subtle` —5,66:1 sobre tarjeta—, que es texto legible y no
       se confunde con un dato ya ingresado. El `opacity: 1` no sobra: Firefox
       le aplica opacidad propia al placeholder y sin esto se aclara de nuevo.

   ESTO NO SE DESCUBRIÓ ACÁ. `cg-postventa-app` —una app que todavía no adoptó
   el sistema— las tenía repuestas a mano en su `globals.css`, con una prueba
   que las custodia. Es la regla de la casa funcionando al revés de lo
   habitual: la app tenía algo mejor y sube al sistema para que lo hereden las
   diez. Sin esto, las diez apps que migraron hoy perdieron la manito. */

button, [role="button"], summary { cursor: pointer; }
:disabled, [aria-disabled="true"] { cursor: default; }

input::placeholder,
textarea::placeholder { color: var(--text-subtle); opacity: 1; }

/* ── Carga ────────────────────────────────────────────────────────────────
   Mecanismo ÚNICO de carga del sistema. Dos primitivas y nada más:
   .gc-spinner       para acciones puntuales (un botón que ya fue pulsado)
   .gc-skeleton-line para contenido que va a llegar con forma conocida

   Vacío y cargando NO son lo mismo: vacío es "no hay nada", cargando es
   "todavía no sé". Un componente que solo distingue vacío le miente a la
   persona durante toda la espera.

   Nunca usar un spinner solo al centro de una tabla: el esqueleto conserva
   la forma del dato y evita el salto de layout cuando llega. */
@keyframes gc-spin { to { transform: rotate(360deg); } }

.gc-spinner { animation: gc-spin 700ms linear infinite; }

@keyframes gc-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .45; } }

.gc-skeleton-line {
  background: var(--surface-sunken);
  border-radius: var(--radius-sm);
  animation: gc-pulse 1.4s ease-in-out infinite;
}

/* Con movimiento reducido se detiene lo que gira y se conserva lo que late.
   El spinner es rotación —desplazamiento puro— y se detiene; su arco quieto
   sigue diciendo que hay una acción en curso. El esqueleto late en opacidad,
   que no es movimiento, y quedarse quieto lo vuelve indistinguible de un
   bloque vacío: ahí «no sé todavía» pasa a leerse como «no hay nada». */
@media (prefers-reduced-motion: reduce) {
  .gc-spinner { animation: none; }
  /* El barrido del esqueleto sí es desplazamiento —un gradiente cruzando la
     pieza— así que cambia a latido de opacidad. Va con !important porque el
     componente declara la animación en el atributo style. */
  .gc-skeleton-brillo {
    animation-name: gc-pulse !important;
    background-position: 0 50% !important;
  }
}

/* ═══ templates/templates.css ═══════════════════════════════════════ */
/* --------------------------------------------------------------------------
   PLANTILLAS — colapso a una columna
   Las siete plantillas usan dos columnas en escritorio. En terreno el ancho no
   alcanza y la columna lateral pasa arriba o abajo según lo que se necesite
   primero: en una ficha, saber DE QUÉ es la ficha; en un formulario, el índice
   estorba y se oculta; en un buscador, los filtros van sobre los resultados.
   -------------------------------------------------------------------------- */
/* Detalle y tablero NO están acá: sus plantillas declaran contenedor de
   consulta y colapsan por su propio ancho, más abajo. Una regla por ventana los
   apilaba dentro de un marco de 1440 solo porque la ventana medía 900 —el mismo
   error que el ancho del menú, en otra capa—. Las cinco que quedan todavía no
   tienen contenedor: cuando lo tengan, se mudan también. */
@media (max-width: 900px) {
  .gc-formulario-cols,
  .gc-buscador-cols,
  .gc-config-cols,
  .gc-aprobaciones-cols {
    grid-template-columns: minmax(0, 1fr) !important;
  }

  /* El índice del formulario no aporta en pantalla angosta: el scroll es corto
     y las secciones ya se ven. Ocuparía una pantalla completa antes del
     primer campo. */
  .gc-formulario-indice {
    display: none !important;
  }

  .gc-buscador-filtros,
  .gc-config-cols > nav,
  .gc-aprobaciones-cols > aside {
    position: static !important;
  }

  /* Las pestañas de configuración pasan a fila desplazable horizontal. */
  .gc-config-cols > nav {
    flex-direction: row !important;
    overflow-x: auto;
    gap: var(--space-2) !important;
    padding-bottom: var(--space-2);
  }

  .gc-config-cols > nav > button {
    flex: 0 0 auto;
    border-left: 0 !important;
    border-bottom: var(--border-accent) solid transparent;
  }

  .gc-config-cols > nav > button[aria-selected="true"] {
    border-bottom-color: var(--brand-primary);
  }
}

/* --------------------------------------------------------------------------
   RANGO DE FECHAS EN TERRENO
   Con guantes y una mano no se usa un calendario doble ni dos campos lado a
   lado. Los campos se apilan a ancho completo y los atajos pasan a fila
   desplazable: el pulgar llega a todo sin apuntar.
   -------------------------------------------------------------------------- */
[data-densidad="terreno"] .gc-rango-campos {
  flex-direction: column;
  align-items: stretch;
}

[data-densidad="terreno"] .gc-rango-guion {
  display: none;
}

[data-densidad="terreno"] .gc-rango-atajos {
  flex-wrap: nowrap;
  overflow-x: auto;
  scroll-snap-type: x proximity;
  padding-bottom: var(--space-1);
}

[data-densidad="terreno"] .gc-rango-atajos > button {
  flex: 0 0 auto;
  scroll-snap-align: start;
}

/* --------------------------------------------------------------------------
   REESTRUCTURA POR ANCHO DEL CONTENEDOR
   Los tokens escalan —altura de control, tipo, gutter— y el AppShell decide el
   modo, pero lo que reestructura la página es de la plantilla: la grilla del
   tablero y las dos columnas del detalle.
   Container queries y no media queries: la plantilla mide SU ancho, no el de la
   ventana. Una vista montada dentro de un marco de 390 px tiene que apilarse
   aunque la ventana mida 1440.
   El contenedor es la columna de contenido, no la raíz de la plantilla: en la
   raíz, `container-type` implica `contain: layout`, que convierte al elemento en
   bloque contenedor de sus descendientes posicionados —un Modal `fixed` dentro
   de la plantilla dejaría de cubrir la ventana para cubrir la plantilla.
   -------------------------------------------------------------------------- */
/* La columna de contenido de Tablero, Detalle y Bandeja —las tres que hospedan
   las regiones que se reestructuran—. Las otras siete plantillas siguen con el
   colapso por media query de arriba: no contienen ninguna de estas clases. */
.gc-plantilla-cq,
/* La cabecera mide aparte: vive fuera del `main` y el contenedor de la columna
   no la alcanza. */
.gc-cabecera-cq {
  container-type: inline-size;
}

/* Acciones de cabecera y de grilla: en columna angosta pasan a ancho completo y
   apiladas. Con `nowrap` y `min-width: auto` no pueden encogerse bajo su
   contenido, y el `wrap` del padre las baja de línea pero no las estrecha. */
@container (max-width: 640px) {
  .gc-cab-acciones { width: 100%; flex-direction: column; align-items: stretch; }
  .gc-cab-acciones > * { width: 100%; }
  .gc-cab-acciones button,
  .gc-cab-acciones [role="button"] { width: 100%; justify-content: center; }

  .gc-grid-acciones {
    width: 100%; margin-left: 0 !important; min-width: 0;
    flex-direction: column; align-items: stretch;
  }
  .gc-grid-acciones > * { width: 100%; justify-content: center; }
}

@container (max-width: 1180px) {
  .gc-tablero-grid > * { grid-column: span 12 !important; }
  /* Bajo 1180 todo ocupa las 12 columnas, así que la separación entre columnas
     no se ve —pero sí se cuenta: con el gap de 48px del tratamiento abierto,
     los 11 huecos suman 528px y desbordan un marco de 390. La separación entre
     bloques la sigue dando el gap de fila. */
  .gc-tablero-grid { column-gap: var(--space-5) !important; }
}

/* El detalle apila más tarde: dos columnas caben desde 860px —lateral de 320,
   gap de 24 y una medida de lectura utilizable— y por debajo el principal
   quedaría más angosto que su tabla. */
@container (max-width: 860px) {
  .gc-detalle-cols { grid-template-columns: minmax(0, 1fr) !important; }
  /* En la ficha el lateral son los metadatos de identificación: van primero. */
  .gc-detalle-cols > aside { order: -1; position: static !important; }
}

/* Respaldo para navegadores sin container queries: ahí sí manda la ventana. */
@supports not (container-type: inline-size) {
  @media (max-width: 1180px) {
    .gc-tablero-grid > * { grid-column: span 12 !important; }
    .gc-tablero-grid { column-gap: var(--space-5) !important; }
  }
  @media (max-width: 860px) {
    .gc-detalle-cols { grid-template-columns: minmax(0, 1fr) !important; }
    .gc-detalle-cols > aside { order: -1; position: static !important; }
  }
  @media (max-width: 640px) {
    .gc-cab-acciones { width: 100%; flex-direction: column; align-items: stretch; }
    .gc-cab-acciones > * { width: 100%; }
    .gc-cab-acciones button,
    .gc-cab-acciones [role="button"] { width: 100%; justify-content: center; }
    .gc-grid-acciones {
      width: 100%; margin-left: 0 !important; min-width: 0;
      flex-direction: column; align-items: stretch;
    }
    .gc-grid-acciones > * { width: 100%; justify-content: center; }
  }
}

/* ==========================================================================
   LA BARRA SUPERIOR DEL SHELL SE ENCOGE ANTES DE EMPUJAR

   El nodo del buscador lo entrega la app —un `<input>`, un combo, lo que sea— y
   un `<input>` trae `min-width: auto` con su ancho intrínseco. Un hijo flex con
   ese mínimo no se encoge, así que a 390px la barra pedía 409 y toda la zona de
   contenido quedaba con scroll horizontal: la navegación inferior salía 19px más
   ancha que el teléfono. No es un caso raro, es un teléfono con buscador.

   Va en la hoja y no inline porque el nodo es de la app: el shell no puede
   escribirle estilos a un elemento que no crea. Así lo heredan las 17.
   ========================================================================== */
.gc-appshell-topbar > * { min-width: 0; }
.gc-appshell-buscador > * {
  width: 100%;
  min-width: 0;
  box-sizing: border-box;
}
