/* public/assets/css/app.css */

/**
 * Color de marca: púrpura corporativo, elegido por el usuario entre tres
 * propuestas con muestra de contraste verificado.
 *
 * `--tblr-primary` nunca se sobreescribía en el proyecto: todo el panel
 * —botones, insignias, chips, foco de teclado— heredaba #0054a6, el azul
 * exacto de la demo pública de Tabler (tabler.io/admin-template/preview).
 * Es la señal más reconocible de "plantilla sin personalizar": cualquiera
 * que haya visto esa demo identifica el origen al instante.
 *
 * #5b21b6 se eligió, entre otras razones, porque NO coincide con ninguno de
 * los siete colores que ya identifican los estados de ticket en los KPIs
 * (blue, indigo, azure, teal, green, gray, red) — si coincidiera, cada botón
 * "Filtrar" parecería asociado a un estado concreto por accidente.
 *
 * Contraste verificado (fórmula WCAG 2.1, relativa luminance):
 *   #5b21b6 vs texto blanco  → 8.98:1  (excede el mínimo AA de 4.5:1)
 *   #7c4fe0 (variante oscura) vs blanco → 5.18:1
 *   #7c4fe0 vs fondo oscuro típico      → 3.26:1 (suficiente para texto/enlaces)
 *
 * El tema no depende de prefers-color-scheme: Auth::user()['theme'] fija
 * data-bs-theme directamente en <html> (lo pone el layout), así
 * que basta con el selector de atributo para el ajuste de modo oscuro.
 *
 * OJO AL CAMBIAR ESTOS DOS VALORES: hay una copia obligada en
 * el layout de la pagina (variable `$marca`). El favicon va incrustado como
 * SVG dentro de un atributo y `theme-color` es un <meta>; ninguno de los dos
 * puede leer una variable CSS, así que el hex tiene que repetirse allí. Si
 * aquí cambia la marca y allí no, el icono de la pestaña y el tinte del
 * navegador en móvil se quedan con el color viejo — que es exactamente lo que
 * pasaba con el azul de fábrica de Tabler.
 *
 * Este aviso ya no depende de que alguien lo lea: una prueba automatica
 * compara los dos hex de aquí con los del layout y falla si divergen. La
 * vista no puede llevar su mitad del aviso porque en `views/` solo caben
 * rótulos de sección.
 */
:root{--tblr-primary:#5b21b6;--tblr-primary-rgb:91,33,182}
[data-bs-theme="dark"]{--tblr-primary:#7c4fe0;--tblr-primary-rgb:124,79,224}

/* ==========================================================================
   El gris apagado, que en oscuro no llegaba
   --------------------------------------------------------------------------
   TABLER DECLARA ESTE TOKEN UNA SOLA VEZ, PARA LOS DOS TEMAS:

       --tblr-secondary:     #667382
       --tblr-secondary-rgb: 102,115,130
       --tblr-muted-rgb:     102,115,130

   De ahi salen `.text-secondary`, `.text-muted` y todo lo que este proyecto
   pinta con `color:var(--tblr-secondary)`. Medido con la formula de luminancia
   relativa contra la superficie de cada tema:

       #667382 sobre tarjeta clara  #ffffff ... 4.84:1  pasa, por poco
       #667382 sobre tarjeta oscura #182433 ... 3.24:1  NO llega a 4.5
       #667382 sobre fondo oscuro   #151f2c ... 3.43:1  NO llega a 4.5

   Ese es el motivo de que «en oscuro hay zonas donde las letras no se ven»: el
   mismo tono esta justo por encima del minimo en blanco y claramente por debajo
   en oscuro. No es una pantalla concreta -- son 307 sitios entre vistas, JS y
   este mismo archivo, porque es el gris por defecto de todo el panel.

   POR QUE SE REDEFINE EL TOKEN Y NO SE CAMBIAN LAS 307 CLASES
   -----------------------------------------------------------
   Porque las reglas de Tabler ya LEEN el token: cambiarlo aqui corrige los 307
   sitios sin tocar una sola vista, sin `!important` y sin un diff de cuarenta
   archivos donde es facil colar un cambio no deseado. El tema claro no se
   toca, asi que lo que hoy se ve bien sigue igual.

   #9aa4b2 da 6.22:1 sobre la tarjeta y 6.59:1 sobre el fondo. Se eligio ese y
   no uno mas claro para que siga leyendose como APAGADO: subir hasta 9:1 lo
   acerca tanto al texto normal que deja de distinguirse la jerarquia, que es
   justo para lo que sirve este gris.

   EFECTO EN EL RESTO DE COMPONENTES, comprobado uno a uno.
   El token lo leen tambien `.btn-outline-secondary` (25 usos), `.form-hint`
   (26), `.bg-secondary-lt` (15), `.card-subtitle`, `.empty-icon` y
   `.btn-ghost-secondary`. Todos son texto, borde o tinte apagado sobre fondo
   oscuro, asi que todos mejoran por el mismo motivo. El unico que pinta un
   bloque solido de ese color es `.bg-secondary`, con UN uso en todo el panel.
   ========================================================================== */
[data-bs-theme="dark"]{
  --tblr-secondary:     #9aa4b2;
  --tblr-secondary-rgb: 154,164,178;
  --tblr-muted-rgb:     154,164,178;
}

:root{scroll-behavior:smooth}
/* Sólo la barra superior de la página, no los .navbar anidados que Tabler usa
   dentro del menú desplegable. */
body > .navbar,
header.navbar{position:sticky;top:0;z-index:1030}

/* ==========================================================================
   Encabezado de página fijo
   --------------------------------------------------------------------------
   Se queda pegado justo DEBAJO de la barra de navegación, que ya es fija. El
   desplazamiento vertical se toma de --altura-navbar, que calcula un script
   del layout midiendo la barra real: su altura cambia entre escritorio y
   móvil, así que un número fijo se desajustaría en cuanto cambia el ancho.

   z-index por debajo de la navbar (1030) y por encima del contenido, para que
   pase por debajo del menú al desplegarlo y por encima de las tarjetas.
   ========================================================================== */
.page-header{
  position:sticky;
  top:var(--altura-navbar,3.5rem);
  z-index:1020;
  background:var(--tblr-body-bg,#f6f8fb);
  /* NO SE ENCOGE. Tabler declara `body{display:flex;flex-direction:column}` para
     dejar el pie abajo, así que este encabezado es un HIJO FLEX y, por defecto,
     `flex-shrink:1`: cuando el contenido pasa del alto de la ventana, el navegador
     lo comprime. Con el relleno de arriba y `box-sizing:border-box`, la caja de
     contenido llegaba a quedarse en UN PIXEL y la fila entera se salía del
     encabezado.

     El síntoma no parecía de altura: en una pantalla de tres líneas --migajero,
     título y subtítulo-- la tercera caía FUERA del fondo que pinta la cabecera, y
     al desplazar, el contenido le pasaba por encima y la partía por la mitad. Se
     vio en el detalle de una respuesta, cuyo subtítulo lleva la versión y el
     client_entry_id. */
  flex-shrink:0;
  /* Tabler aplica `margin: var(--tblr-page-padding-y) 0 0`. Ese margen
     superior dejaría una franja transparente por la que se vería pasar el
     contenido al desplazar, así que se sustituye por relleno interno.

     Y el relleno de arriba tiene que ser HOLGADO: con .75rem el título del
     módulo nacía pegado a la barra de navegación, sin aire que los separara.
     Como la cabecera es fija, ese aire no puede ser margen —dejaría una
     rendija por la que se ve pasar el contenido—, así que va aquí dentro. */
  margin:0;
  padding:1.5rem 0 1.125rem;
  border-bottom:1px solid var(--tblr-border-color,rgba(0,0,0,.08));
}

/* Los destinos de anclaje deben quedar por debajo de ambas barras fijas: la
   de navegacion y el encabezado de pagina, que tambien es fijo. Se subio a 6rem
   al dar aire al encabezado; con 5rem el enlace «Saltar al contenido» aterrizaba
   justo debajo de el y la primera linea quedaba tapada. */
:target{scroll-margin-top:calc(var(--altura-navbar,3.5rem) + 6rem)}
.table-scroll-wrapper{overflow-x:auto}

/**
 * Bloqueo temporal de una zona mientras hay una petición en curso.
 *
 * El árbol de categorías seguía aceptando clics mientras se aplicaba un
 * filtro, de modo que el usuario podía cambiar la selección y recibir después
 * unos resultados que ya no correspondían a lo que veía marcado. Se retira al
 * terminar la petición.
 *
 * No fija ningún color: sólo baja opacidad y saturación, así que funciona
 * igual en tema claro y oscuro.
 */
.is-cargando{pointer-events:none;opacity:.55;filter:saturate(.5);transition:opacity .15s ease-out}

/**
 * Encabezado fijo de la tabla de tickets.
 *
 * No se usa la clase `sticky-top` de Bootstrap: lleva z-index 1020, por encima
 * del 1000 de `.dropdown-menu`, así que el encabezado se dibujaba ENCIMA del
 * desplegable de columnas y parecía cortarlo. Aquí basta con un z-index de 2:
 * lo justo para quedar sobre las filas de la tabla y por debajo de cualquier
 * menú flotante.
 *
 * El fondo es obligatorio: sin él las filas se transparentarían al desplazar.
 * Se toma de las variables de Tabler para que acompañe al tema claro/oscuro.
 */
/* ==========================================================================
   Esqueletos de carga
   --------------------------------------------------------------------------
   Un bloque gris con brillo en el sitio exacto donde va a aparecer el dato.
   Frente a un spinner suelto tiene dos ventajas: el contenido no da un salto
   al llegar, y el usuario ve de antemano la forma de lo que está cargando.

   El patrón existía sólo dentro de cradlepoint.css (.kpi-skeleton); aquí se
   generaliza para que lo use cualquier módulo. Los colores salen de las
   variables de Tabler, así que acompañan al tema claro/oscuro sin duplicar
   reglas.
   ========================================================================== */
.skeleton{
  position:relative;
  overflow:hidden;
  border-radius:var(--tblr-border-radius,4px);
  background:var(--tblr-border-color,rgba(0,0,0,.08));
  color:transparent!important;
  user-select:none;
  min-height:1em;
}
.skeleton::after{
  content:"";
  position:absolute;
  inset:0;
  transform:translateX(-100%);
  background:linear-gradient(90deg,transparent,var(--tblr-bg-surface,rgba(255,255,255,.35)),transparent);
  animation:skeleton-brillo 1.4s infinite;
}
@keyframes skeleton-brillo{100%{transform:translateX(100%)}}

.skeleton-text{height:.8em;margin:.25em 0}
.skeleton-block{min-height:100%;height:100%;width:100%}

/* ==========================================================================
   Iconos de estado vacío
   --------------------------------------------------------------------------
   Un único tamaño y una única opacidad. Antes cada estado vacío llevaba su
   propio `style="font-size: …"` con valores distintos (4rem/2.5rem/2rem) y
   opacidades distintas (.25/.2/.3), de modo que el mismo recurso visual se
   veía diferente en cada pantalla.
   ========================================================================== */
.icono-vacio{
  font-size:3rem;
  line-height:1;
  opacity:.25;
}

/* ==========================================================================
   Movimiento
   --------------------------------------------------------------------------
   Transiciones cortas y funcionales: sirven para explicar un cambio de estado,
   no para decorar. Todas quedan anuladas por el bloque de
   `prefers-reduced-motion` del final del archivo.
   ========================================================================== */

/* Tarjeta que CONTIENE algo interactivo (las gráficas filtran al pulsarlas).
   -------------------------------------------------------------------------
   Antes esto levantaba la tarjeta entera —sombra más `translateY(-2px)`— al
   pasar el ratón. El problema es que la tarjeta entera NO es pulsable: sólo
   lo son los segmentos dentro del lienzo. Entrar con el ratón en la cabecera,
   en el relleno o en el espacio vacío levantaba 460 px y el clic no hacía
   nada.

   Prometer una acción donde no la hay hace que el usuario deje de creerse la
   señal, y entonces cualquier efecto que se añada encima se lee con la misma
   desconfianza. Además, levantar seis tarjetas grandes mientras se escanea la
   pantalla es movimiento que no informa de nada.

   Queda un cambio de borde muy leve: dice «esta tarjeta está viva» sin
   prometer que se pueda pulsar en cualquier punto. La señal fuerte va donde
   corresponde: en la marca, con el cursor de mano y el resaltado del segmento
   (ver graficas.js). */
.card-interactiva{
  transition:border-color .18s ease-out;
}
.card-interactiva:hover{
  border-color:rgba(var(--tblr-primary-rgb),.45);
}

/* El chevron del plegable gira para indicar el estado. Sin esto el botón
   «Mostrar / ocultar» no dice en cuál de los dos estados se encuentra. */
[data-bs-toggle="collapse"] .ti-chevron-down{
  transition:transform .2s ease-out;
}
[data-bs-toggle="collapse"][aria-expanded="true"] .ti-chevron-down{
  transform:rotate(180deg);
}

/* ==========================================================================
   Foco visible por teclado
   --------------------------------------------------------------------------
   `:focus-visible` sólo se activa al navegar con teclado, nunca al hacer clic
   con el ratón, así que mejora la accesibilidad sin ensuciar el uso normal.
   ========================================================================== */
:focus-visible{
  outline:2px solid var(--tblr-primary,#5b21b6);
  outline-offset:2px;
  border-radius:2px;
}

/* Enlace «saltar al contenido»: invisible hasta que recibe el foco. Evita
   tener que tabular el menú entero en cada página. */
.skip-link{
  position:absolute;
  left:-9999px;
  top:0;
  z-index:2000;
  padding:.5rem 1rem;
  background:var(--tblr-primary,#5b21b6);
  color:#fff;
  border-radius:0 0 .25rem 0;
}
.skip-link:focus{left:0}

/* ==========================================================================
   Movimiento reducido
   --------------------------------------------------------------------------
   Quien lo pide en su sistema suele hacerlo por mareo o migraña. Se anulan
   animaciones y transiciones en toda la aplicación, incluidas las de Tabler.
   ========================================================================== */
@media (prefers-reduced-motion: reduce){
  *,*::before,*::after{
    animation-duration:.001ms!important;
    animation-iteration-count:1!important;
    transition-duration:.001ms!important;
    scroll-behavior:auto!important;
  }
}

/* ==========================================================================
   Campos de fecha dentro de un input-group
   --------------------------------------------------------------------------
   Dos garantías para que el selector de fecha siga siendo accesible:

   1. El indicador nativo del navegador (el iconito del calendario) se fuerza a
      visible. Dentro de un `.input-group`, Bootstrap aplica `width:1%` al
      control y Chrome OCULTA ese indicador cuando calcula que el campo es
      demasiado estrecho para mostrarlo; en pantallas pequeñas el usuario se
      quedaba sin forma de abrir el calendario.

   2. El recuadro del icono se marca como clicable; el JavaScript de la vista
      le asocia showPicker(). Antes era puramente decorativo, aunque es el
      sitio donde cualquiera intenta pulsar.
   ========================================================================== */
.input-group > input[type="date"].form-control::-webkit-calendar-picker-indicator{
  display:block!important;
  opacity:.65;
  cursor:pointer;
}
.input-group > input[type="date"].form-control::-webkit-calendar-picker-indicator:hover{
  opacity:1;
}
.input-group-text.js-abrir-calendario{
  cursor:pointer;
}
.input-group-text.js-abrir-calendario:hover{
  color:var(--tblr-primary,#5b21b6);
}

/* ==========================================================================
   Tamaño de los iconos de navegación
   --------------------------------------------------------------------------
   `.nav-link-icon` y `.dropdown-item-icon` fijan WIDTH y HEIGHT (1.25rem),
   una regla pensada para los SVG antiguos de Tabler, que se dibujaban a un
   tamaño de caja fijo sin importar el contexto. La fuente de iconos `ti` no
   funciona así: es texto, y su tamaño lo da `font-size`, que ninguna de las
   dos clases define. Al migrar el navbar de SVG a `ti`, los iconos heredaron
   el font-size del menú (más pequeño) y quedaron visiblemente chicos aunque
   la caja que los contiene siguiera midiendo lo mismo que antes.
   ========================================================================== */
.nav-link-icon.ti,
.dropdown-item-icon.ti{
  font-size:1.25rem;
  line-height:1;
}

.table-sticky-head thead th{
  position:sticky;
  top:0;
  z-index:2;
  background:var(--tblr-bg-surface,#fff);
  box-shadow:inset 0 -1px 0 var(--tblr-border-color,rgba(0,0,0,.1));
}

/* ==========================================================================
   Árbol de categorías: subcategorías incluidas por herencia
   --------------------------------------------------------------------------
   Al seleccionar una rama, TODOS sus descendientes entran en el filtro: el
   árbol envía la ruta hasta el nodo elegido y el servidor sólo fija las
   condiciones de esos niveles, de modo que lo que cuelga por debajo pasa
   entero. Sus casillas, en cambio, quedan en blanco, y eso se lee justo al
   revés: como si no estuvieran incluidos.

   La marca es una clase (`.rama-incluida`), NUNCA la casilla. `checked` es lo
   que lee el envío del árbol, y marcar los hijos haría que el filtro se
   estrechara a la última hoja marcada. Ver `_marcarRamaIncluida()` en
   categoryTree.js.

   El tinte usa --tblr-primary-rgb, así que sigue al color de marca y funciona
   en tema claro y oscuro sin declararlo dos veces.
   ========================================================================== */
.category-tree li.rama-incluida > .tree-item{
  background:rgba(var(--tblr-primary-rgb),.07);
  border-radius:var(--tblr-border-radius,4px);
}

/* ==========================================================================
   Tipografía del panel
   --------------------------------------------------------------------------
   Tabler resuelve su fuente a través de --tblr-font-sans-serif, así que basta
   con redefinir esa variable: no hay que tocar ninguna regla suya.

   `InterVariable` es el nombre que usa la hoja actual de rsms.me; `Inter var`
   e `Inter` son los de versiones anteriores. Se listan los tres para que la
   fuente siga aplicándose si el host actualiza su empaquetado, y detrás va la
   pila del sistema completa como último recurso.
   ========================================================================== */
:root{
  --tblr-font-sans-serif:'InterVariable','Inter var','Inter',-apple-system,
    BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',Arial,
    'Noto Sans',sans-serif,'Apple Color Emoji','Segoe UI Emoji';
}

/* --------------------------------------------------------------------------
   Cifras tabulares
   --------------------------------------------------------------------------
   Por defecto los dígitos son proporcionales: el «1» es más estrecho que el
   «8», de modo que en una columna de números las unidades no caen en la misma
   vertical y la cifra «baila» al actualizarse. `tabular-nums` les da a todos
   el mismo ancho.

   Se aplica SÓLO donde hay cantidades —tablas, KPI, tarjetas de métrica—, no
   al texto corrido: en una frase las cifras tabulares dejan huecos feos.
   -------------------------------------------------------------------------- */
.cifras,
.table td,
.table th,
.datagrid-content,
.card .h1,
.card .h2,
.card .h3,
.card .display-6{
  font-variant-numeric:tabular-nums;
  font-feature-settings:"tnum" 1;
}

/* ==========================================================================
   Comparativo: barras enfrentadas período anterior / período actual
   --------------------------------------------------------------------------
   Dos cifras juntas obligan a restar mentalmente; dos barras a la misma escala
   hacen visible la diferencia sin leer. La superior es el período anterior en
   gris y la inferior el actual en color de marca, de modo que el orden de
   lectura (antes arriba, ahora abajo) coincide con el de la línea temporal.

   Las dos se escalan contra el MISMO máximo —el mayor valor de la tabla, mire
   al período que mire—, porque escalar cada fila por su cuenta haría que
   barras de longitud parecida representaran cantidades muy distintas.
   ========================================================================== */
.cmp-barras{
  display:flex;
  flex-direction:column;
  gap:3px;
  min-width:5rem;
}
.cmp-barra{
  height:6px;
  border-radius:3px;                    /* extremos redondeados, anclados al 0 */
  background:var(--tblr-border-color,rgba(0,0,0,.12));
}
.cmp-barra > span{
  display:block;
  height:100%;
  border-radius:3px;
  min-width:3px;                        /* un valor > 0 nunca se ve como cero */
}
.cmp-barra-antes > span{ background:var(--tblr-gray-400,#adb5bd); }
.cmp-barra-ahora > span{ background:var(--cmp-ahora); }

/* ==========================================================================
   Identidad de color de «Antes» y «Ahora»
   --------------------------------------------------------------------------
   Las dos barras estaban apiladas SIN etiquetar: gris arriba, morada abajo, y
   ninguna leyenda que dijera cuál era cuál. Había que deducirlo. Y el
   emparejamiento estaba a medias: el número de «Antes» ya era gris como su
   barra, pero el de «Ahora» iba en tinta normal mientras su barra era morada.

   Se arregla en dos sitios:
     - un chip del color de cada barra junto a su cabecera, que es lo que por
       fin las etiqueta —una vez arriba, no repitiendo color en cada fila—;
     - el número de «Ahora» en el mismo morado, que cierra la pareja.

   UN SOLO token para los tres usos (barra, chip, cifra). Si el chip tuviera su
   propio valor podría desincronizarse de la barra que dice etiquetar, que es
   justo lo contrario de lo que se busca.

   El morado del tema oscuro NO es el de marca. `--tblr-primary` en oscuro es
   #7c4fe0, que da 3.26:1 sobre la superficie: suficiente para una barra —un
   gráfico pide 3:1— pero por debajo del 4.5:1 que exige AA para TEXTO normal,
   y estas cifras son pequeñas. #a78bfa es el mismo morado un paso más claro y
   llega a 5.8:1, sin dejar de destacar sobre el gris de «Antes» (#6c757d),
   que es la jerarquía que la comparación necesita.
   ========================================================================== */
/* --------------------------------------------------------------------------
   `--marca-trazo`: el morado de marca en la versión que SÍ se puede usar para
   texto pequeño y trazos finos en los dos temas.
   --------------------------------------------------------------------------
   Existe porque `--tblr-primary` no sirve para eso en tema oscuro: su #7c4fe0
   da 3.06:1 sobre la superficie, que basta para un relleno grande pero se
   queda corto para una cifra o un borde de 2px. #a78bfa es el mismo morado un
   paso más claro y llega a 5.84:1.

   Lo usan la tarjeta comparativa (cifra y barra de «Ahora») y el borde de las
   casillas del árbol de categorías. Un solo sitio: si mañana cambia el morado
   de marca, no hay que acordarse de dos.
   -------------------------------------------------------------------------- */
:root{ --marca-trazo:var(--tblr-primary); }          /* #5b21b6 → 9.01:1 sobre blanco */
[data-bs-theme="dark"]{ --marca-trazo:#a78bfa; }     /* 5.84:1 sobre superficie oscura */

:root,[data-bs-theme="dark"]{ --cmp-ahora:var(--marca-trazo); }

/* --------------------------------------------------------------------------
   Chip de filtro activo
   --------------------------------------------------------------------------
   Iban en `bg-blue-lt text-blue`, y azul es el color con el que los KPI
   identifican el estado «Abiertos». Un chip azul junto a esas tarjetas se
   podía leer como relacionado con ese estado, cuando lo que dice es «hay un
   filtro puesto» — que es una acción del usuario y por tanto territorio del
   color de marca.

   No se usa `bg-primary-lt` de Tabler, aunque parezca lo obvio: en
   1.0.0-beta20 esa clase toma el TEXTO de `--tblr-primary-rgb` (que aquí sí se
   sobrescribe) pero el FONDO de `--tblr-primary-lt-rgb` (que no). El resultado
   sería texto morado sobre un fondo azul #e6eef6. Aquí los dos salen del mismo
   sitio y no pueden separarse.

   Contraste verificado (WCAG 2.1):
     claro   #5b21b6 sobre 10 % de morado en blanco  → 7.58:1
     oscuro  #a78bfa sobre 20 % de morado en oscuro  → 4.84:1
   Los dos superan el 4.5:1 de texto normal.
   -------------------------------------------------------------------------- */
/* --------------------------------------------------------------------------
   `.badge-marca` y `.encabezado-marca`: lo mismo, para el resto del panel
   --------------------------------------------------------------------------
   Cuando se rehizo Administracion, sus vistas volvieron a `bg-primary-lt
   text-primary` -- que es justo lo que el parrafo de arriba explica que NO se
   puede usar-- en ocho sitios: los avatares y las insignias de rol del listado
   de usuarios, la insignia «todo» de roles, la de «Proyecto» del catalogo de
   permisos, la del perfil y el avatar de la barra de navegacion.

   El resultado medido, con los tokens reales de Tabler 1.0.0-beta20
   (`--tblr-primary-lt` vale #e6eef6 en claro y #16293f en oscuro, los dos
   derivados de su azul de fabrica, y ninguno se sobreescribe aqui):

     claro   morado #5b21b6 sobre azul palido #e6eef6 ... 7.67:1  legible, pero
                                                          AZUL en un panel morado
     oscuro  morado #7c4fe0 sobre azul marino #16293f ... 2.85:1  POR DEBAJO DEL
                                                          MINIMO AA de 4.5:1

   En claro se lee como «plantilla sin personalizar», que es la impresion que
   dio pie a esta revision; en oscuro pasa desapercibido pero es peor, porque
   incumple contraste. Una sola declaracion sirve a los dos casos y ya estaba
   escrita: el chip de filtro.

   `.encabezado-marca` es la variante para tinta de fondo sin tenir el texto:
   la cabecera del grupo «Administracion» en el editor de roles usaba
   `bg-primary-lt`, que ademas de traer el azul forzaba a morado el titulo
   entero. Aqui solo se tine el fondo y el titulo conserva su tinta normal.
   -------------------------------------------------------------------------- */
.chip-filtro,
.badge-marca{
  background:rgba(var(--tblr-primary-rgb),.10);
  color:var(--marca-trazo);
  border-color:transparent;
}

.encabezado-marca{ background:rgba(var(--tblr-primary-rgb),.07); }
[data-bs-theme="dark"] .encabezado-marca{ background:rgba(var(--tblr-primary-rgb),.16); }
/* Sobre superficie oscura el 10 % es casi invisible: hace falta más cuerpo
   para que el chip se lea como una pieza y no como texto suelto. */
[data-bs-theme="dark"] .chip-filtro,
[data-bs-theme="dark"] .badge-marca{
  background:rgba(var(--tblr-primary-rgb),.20);
}

.cmp-chip{
  display:inline-block;
  width:.5rem;
  height:.5rem;
  border-radius:2px;
  margin-right:.35rem;
  vertical-align:baseline;
}
.cmp-chip-antes{ background:var(--cmp-barra-antes,#adb5bd); }
.cmp-chip-ahora{ background:var(--cmp-ahora); }

.cmp-cifra-ahora{ color:var(--cmp-ahora); }

/* Etiqueta del padre encima de la hoja. Va en dos renglones a propósito: el
   prefijo se repetía en la mayoría de las filas y se comía la columna, pero
   quitarlo del todo dejaba hojas ambiguas («Otro», «Error»). */
.cmp-padre{
  font-size:.75rem;
  line-height:1.1;
  color:var(--tblr-secondary,#6c757d);
}

/* ==========================================================================
   Tokens de las piezas nuevas, con su variante oscura
   --------------------------------------------------------------------------
   Se declaran aparte en lugar de usar directamente los grises de Tabler porque
   esos están pensados para fondo claro. Ya pasó una vez en este proyecto: nueve
   de doce tonos de la paleta de gráficas eran invisibles en tema oscuro porque
   se oscurecían en AMBOS temas. Aquí el valor oscuro es una decisión propia, no
   una consecuencia.
   ========================================================================== */
:root{
  --cmp-barra-fondo:rgba(0,0,0,.08);
  /* El `gray-400` real de Tabler beta20 es #bbc3cd, no el #adb5bd de Bootstrap
     que había aquí de respaldo: la barra salía MÁS CLARA de lo previsto y daba
     1.78:1 sobre la tarjeta —1.49:1 sobre su propia pista—, muy por debajo del
     3:1 que necesita un elemento gráfico. Afecta a la barra «Antes», a su chip
     de leyenda y a las barritas no actuales de la tendencia, que son justo las
     que responden a «¿venía subiendo?». Con gray-600 la jerarquía se conserva:
     el morado de «Ahora» da 8.98:1 y sigue pesando el doble. */
  --cmp-barra-antes:var(--tblr-gray-600,#667382);
  --arbol-rama-incluida:rgba(var(--tblr-primary-rgb),.07);
}
[data-bs-theme="dark"]{
  /* Sobre superficie oscura el mismo 7 % es invisible, y un gris claro pesaría
     MÁS que la barra de marca, invirtiendo la jerarquía de la comparación. */
  --cmp-barra-fondo:rgba(255,255,255,.10);
  --cmp-barra-antes:var(--tblr-gray-600,#6c757d);
  --arbol-rama-incluida:rgba(var(--tblr-primary-rgb),.18);
}

.cmp-barra{ background:var(--cmp-barra-fondo); }
.cmp-barra-antes > span{ background:var(--cmp-barra-antes); }
.category-tree li.rama-incluida > .tree-item{ background:var(--arbol-rama-incluida); }

/* ==========================================================================
   Zonas con desplazamiento interno
   --------------------------------------------------------------------------
   El árbol, la tabla y el selector de columnas tienen alto máximo. Cuando el
   contenido crece y aparece la barra de desplazamiento, el contenido se
   desplaza unos píxeles hacia la izquierda. `scrollbar-gutter` reserva ese
   hueco siempre, así que el salto desaparece.
   ========================================================================== */
#tabla-envoltorio,
#selectorColumnas,
.category-tree{
  scrollbar-gutter:stable;
}

/* ==========================================================================
   Tabla de tickets: primera columna fija
   --------------------------------------------------------------------------
   El CSV trae muchas columnas —por eso existe el selector— y al desplazarse en
   horizontal se perdía el identificador de la fila que se está leyendo.

   `:first-child` es seguro aquí porque las columnas ocultas NO se renderizan:
   `columnasVisibles()` las filtra antes de construir la fila, así que la
   primera celda del DOM es siempre la primera columna visible.
   ========================================================================== */
#dataTable thead th:first-child,
#dataTable tbody td:first-child{
  position:sticky;
  left:0;
  background-color:var(--tblr-bg-surface,#fff);
  /* Sombra de 1px en lugar de borde: no ocupa espacio y marca el corte. */
  box-shadow:1px 0 0 var(--tblr-border-color,rgba(0,0,0,.1));
}
#dataTable tbody td:first-child{ z-index:1; }
/* La esquina superior izquierda cruza con la cabecera fija (z-index 2 en
   .table-sticky-head), así que necesita quedar por encima de las dos. */
#dataTable thead th:first-child{ z-index:3; }
/* Sin esto, al pasar el ratón la fila se tiñe salvo su primera celda, que
   quedaría con el fondo opaco y rompería la continuidad visual. */
#dataTable tbody tr:hover td:first-child{
  background-color:var(--tblr-bg-surface-secondary,var(--tblr-bg-surface,#fff));
}

/* ==========================================================================
   Avisos flotantes
   --------------------------------------------------------------------------
   Aquí había una regla para bajar `#chart-toast-container` por debajo de la
   cabecera fija, porque los avisos de las gráficas caían sobre los chips de
   filtro — justo lo que se mira cuando aparece el aviso.
   Ese contenedor ya no existe: graficas.js pasó a usar `notificar()`, el toast
   común de la aplicación, que se ancla abajo a la derecha (`#app-toasts`) y no
   estorba a nada. La regla se retiró para no dejar CSS apuntando a un elemento
   que nunca se crea.
   ========================================================================== */

/* ==========================================================================
   Detalles de acabado
   ========================================================================== */
::selection{
  background:rgba(var(--tblr-primary-rgb),.22);
}

/* Para quien pide más contraste en el sistema operativo: el texto secundario
   de un panel de datos es información, no decoración, y al 60 % de opacidad
   deja de leerse. */
@media (prefers-contrast: more){
  .text-secondary{ color:var(--tblr-body-color) !important; opacity:.85; }
  .card{ border-color:var(--tblr-body-color); }
}

/* ==========================================================================
   Panel de detalle del comparativo
   --------------------------------------------------------------------------
   Todo se define en tokens sobre `:root` y se redefine bajo
   `[data-bs-theme="dark"]`, que es como este proyecto conmuta el tema: sale de
   la preferencia guardada del usuario (Auth::user()['theme']), NO de
   `prefers-color-scheme`. Un color cuya única definición viviera dentro de una
   consulta de medios se aplicaría según el sistema operativo y no según lo que
   el usuario eligió en su perfil.
   ========================================================================== */
:root{
  --det-ancho:44rem;
  --det-superficie:var(--tblr-bg-surface-secondary,#f8fafc);
  --det-borde:var(--tblr-border-color,#e6e7e9);
  --det-via:rgba(0,0,0,.06);          /* canal de las barras de la historia */
  --det-sube:var(--tblr-red,#d63939);
  --det-baja:var(--tblr-green,#2fb344);
}
[data-bs-theme="dark"]{
  /* El mismo 6 % es invisible sobre superficie oscura. Y los dos tonos de
     dirección se aclaran: el rojo y el verde de Tabler están calculados para
     fondo claro y sobre oscuro caen por debajo del contraste de texto. */
  --det-superficie:rgba(255,255,255,.04);
  --det-via:rgba(255,255,255,.10);
  --det-sube:#ff8787;
  --det-baja:#8ce99a;
}

/* Ancho: cómodo para una tabla de comparación, pero nunca más que la pantalla.
   En móvil ocupa casi todo, que es lo único legible a ese tamaño.
   --------------------------------------------------------------------------
   Se fija por VARIABLE y no sólo con `width`. Tabler declara
   `.offcanvas-end{ width:var(--tblr-offcanvas-width) }`, así que una regla
   propia con `width` compite en la misma especificidad y depende del orden de
   los archivos — frágil, y de hecho no se aplicaba: el cajón salía a los 400 px
   de fábrica. Poniendo la variable, gana siempre.
   El `width` se conserva para navegadores donde la variable no exista. */
.offcanvas.detalle-cajon{
  --tblr-offcanvas-width:min(44rem,94vw);
  --bs-offcanvas-width:min(44rem,94vw);
  width:min(44rem,94vw);
}

.detalle-cajon .offcanvas-footer{ background:var(--tblr-bg-surface); }

/* --- Bloques del contenido --- */
.det-bloque{ margin-bottom:1.5rem; }
.det-bloque-tit{
  font-size:.75rem; font-weight:600; text-transform:uppercase;
  letter-spacing:.04em; color:var(--tblr-secondary);
  margin-bottom:.5rem;
}

/* --- La historia: una fila por período, pulsable --- */
/* Cada salto se puede examinar. El más antiguo NO es pulsable porque no tiene
   contra qué compararse, y se dice en vez de dejar un control muerto: un botón
   que no responde se lee como una avería. */
.det-historia{ display:flex; flex-direction:column; gap:.125rem; }

.det-fila{
  display:flex; align-items:center; gap:.625rem;
  width:100%; padding:.3125rem .5rem;
  font:inherit; color:inherit; text-align:left;
  background:transparent;
  border:1px solid transparent;
  border-radius:var(--tblr-border-radius,4px);
  cursor:pointer;
}
.det-fila:hover:not([disabled]){ border-color:var(--det-borde); }
.det-fila:focus-visible{
  outline:2px solid var(--marca-trazo); outline-offset:1px;
}
.det-fila[disabled]{ cursor:default; opacity:.65; }
.det-fila.elegido{
  border-color:var(--marca-trazo);
  background:var(--det-superficie);
}

.det-fila-etq{
  flex:none; width:7.5rem;
  font-size:.8125rem; white-space:nowrap;
  overflow:hidden; text-overflow:ellipsis;
}
.det-via{
  flex:1 1 auto; height:.5rem; min-width:2rem;
  background:var(--det-via);
  border-radius:.25rem; overflow:hidden;
}
.det-via > i{ display:block; height:100%; background:var(--marca-trazo); }
.det-fila-cifra{
  flex:none; width:3.5rem; text-align:right;
  font-variant-numeric:tabular-nums; font-size:.8125rem;
}
.det-marca{
  flex:none; width:5.5rem; text-align:right;
  font-size:.625rem; color:var(--tblr-secondary);
}
.det-marca.pista{ color:var(--marca-trazo); }

/* --- Tabla del desglose --- */
.det-tabla{ width:100%; font-size:.75rem; }
.det-tabla th{
  font-size:.625rem; font-weight:600; text-transform:uppercase;
  letter-spacing:.03em; color:var(--tblr-secondary);
  padding:.25rem .375rem; border-bottom:1px solid var(--det-borde);
  /* Las cabeceras de período llevan fecha y no deben partirse en tres
     renglones: con el cajón estrecho salía «1–29 / AGO / 2025». */
  white-space:nowrap;
}
.det-tabla td{ padding:.3125rem .375rem; border-bottom:1px solid var(--det-borde); }
/* La columna del nombre se queda con todo el ancho sobrante y las cifras
   ocupan lo justo: así el nombre se recorta antes que descolocar los números. */
.det-tabla th:first-child, .det-tabla td:first-child{ width:100%; }
.det-tabla td.num{ text-align:right; font-variant-numeric:tabular-nums; white-space:nowrap; }
.det-sube{ color:var(--det-sube); }
.det-baja{ color:var(--det-baja); }

/* El nombre largo de una categoría no puede empujar las cifras fuera del
   panel: se recorta y la ruta completa queda en el `title`. */
.det-nombre{
  max-width:16rem;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}

/* Una tabla ancha se desplaza DENTRO de su caja; el cajón nunca en horizontal. */
.det-scroll-x{ overflow-x:auto; }

/* --------------------------------------------------------------------------
   Los seis estados del SLA
   --------------------------------------------------------------------------
   Colores SEMÁNTICOS, no de marca: aquí el color sí codifica bien y mal,
   porque un SLA es un compromiso con un objetivo. «Incumplió» y «Vencido» son
   los dos malos pero distintos —uno ya pasó, el otro sigue abierto y pide
   atención ahora—, así que se separan en rojo y granate en vez de compartir
   tono, que haría imposible distinguirlos en la barra apilada.

   En tema oscuro TODOS se aclaran. Los rojos y verdes de Tabler están
   calculados para fondo claro y sobre superficie oscura caen por debajo del
   contraste que necesita una franja fina.
   -------------------------------------------------------------------------- */
:root{
  --sla-cumplio:#2fb344;
  --sla-incumplio:#d63939;
  --sla-enPlazo:#4299e1;
  --sla-porVencer:#f76707;
  --sla-vencido:#8f2323;
  --sla-sinSla:#adb5bd;
  --sla-sinFecha:#ced4da;
}
[data-bs-theme="dark"]{
  --sla-cumplio:#8ce99a;
  --sla-incumplio:#ff8787;
  --sla-enPlazo:#74c0fc;
  --sla-porVencer:#ffa94d;
  --sla-vencido:#e03131;
  --sla-sinSla:#868e96;
  --sla-sinFecha:#5c636a;
}

/* --------------------------------------------------------------------------
   Semáforo de cumplimiento
   --------------------------------------------------------------------------
   Aquí el color SÍ codifica bien y mal, al contrario que en el comparativo: un
   SLA es un compromiso con un objetivo, así que quedarse corto es objetivamente
   malo. No es una opinión sobre el dato, es su definición.

   POR QUÉ NO SE USAN `text-green` / `text-yellow` / `text-red` DE TABLER
   ---------------------------------------------------------------------
   Porque NO cambian entre temas —valen lo mismo en claro y en oscuro— y en
   claro se quedan muy cortos justo donde más importa:

       text-yellow  #f59f00 sobre la tarjeta blanca ...... 2.13:1
       text-green   #2fb344 ............................... 2.74:1
       bg-yellow    en la barra de progreso ............... 1.80:1

   El porcentaje de cumplimiento es EL número de esa tarjeta, y con esos tonos
   deja de leerse precisamente cuando el dato es bueno. Los valores de abajo
   están medidos con la fórmula de luminancia relativa contra la superficie de
   cada tema.
   -------------------------------------------------------------------------- */
:root{
  --sem-bien:#217a30;      /* 5.40:1 sobre blanco */
  --sem-atencion:#9a6200;  /* 5.10:1 */
  --sem-mal:#d63939;       /* 4.66:1 */
}
[data-bs-theme="dark"]{
  /* Los mismos tonos aclarados: sobre superficie oscura los de arriba caen
     por debajo del mínimo, igual que les pasa a los de Tabler en claro. */
  --sem-bien:#8ce99a;      /* 10.63:1 sobre #182433 */
  --sem-atencion:#f7b233;  /*  8.49:1 */
  --sem-mal:#ff8787;       /*  6.77:1 */
}

.sem-bien{ color:var(--sem-bien); }
.sem-atencion{ color:var(--sem-atencion); }
.sem-mal{ color:var(--sem-mal); }

.sem-fondo-bien{ background-color:var(--sem-bien); }
.sem-fondo-atencion{ background-color:var(--sem-atencion); }
.sem-fondo-mal{ background-color:var(--sem-mal); }

/* --------------------------------------------------------------------------
   Series de consumo de NetCloud (pestaña «Consumo»)
   --------------------------------------------------------------------------
   SIM, WiFi y cable son TRES SERIES, no juicios: ninguna es mejor que otra, así
   que no usan los semánticos ni el morado de marca. Elegidas para distinguirse
   también con daltonismo; además WiFi lleva rayas y cable puntos, para no
   depender sólo del color. Son barras, no texto: el mínimo es 3:1.
   -------------------------------------------------------------------------- */
:root{
  --serie-sim:#2560b8;     /* 6.10:1 sobre blanco */
  --serie-wifi:#b8336a;    /* 5.65:1 */
  --serie-cable:#9a7b2f;   /* 4.00:1 */
}
[data-bs-theme="dark"]{
  --serie-sim:#3d8fe0;     /* 4.63:1 sobre #182433 */
  --serie-wifi:#d9507f;    /* 4.04:1 */
  --serie-cable:#b3862a;   /* 4.75:1 */
}

/* Botones del desglose de SLA.
   --------------------------------------------------------------------------
   Sustituyen a `btn-ghost-green/red/orange`, que a 12 px se quedaban en
   2.74:1, 4.66:1 y 3.04:1 sobre la tarjeta clara.

   Y el naranja se va por un segundo motivo: en el panel de detalle naranja es
   «Por vencer» y granate es «Vencido». Tener «Vencidos» en naranja aquí hacía
   que el mismo tono significara dos cosas distintas en dos piezas que el mismo
   usuario ve en la misma sesión — y son justo los dos estados que más importa
   no confundir, porque uno pide acción ahora y el otro ya pasó.

   Con los tres niveles del semáforo la lectura queda inequívoca: lo terminado
   mal es ámbar (es historia) y lo que sigue vencido es rojo (es deuda viva). */
.btn-sem{ background-color:transparent; border-color:transparent; }
.btn-sem:hover:not(:disabled){ background-color:var(--det-superficie); }
.btn-sem-bien{ color:var(--sem-bien); }
.btn-sem-atencion{ color:var(--sem-atencion); }
.btn-sem-mal{ color:var(--sem-mal); }
.btn-sem:hover:not(:disabled).btn-sem-bien{ color:var(--sem-bien); }
.btn-sem:hover:not(:disabled).btn-sem-atencion{ color:var(--sem-atencion); }
.btn-sem:hover:not(:disabled).btn-sem-mal{ color:var(--sem-mal); }

/* La barra apilada: las seis partes de un período, a escala. */
.det-sla-barra{
  display:flex; height:.75rem; width:100%;
  border-radius:.375rem; overflow:hidden;
  background:var(--det-via);
}
.det-sla-barra > span{ display:block; height:100%; }

/* La leyenda: un cuadrito con su color, el nombre y la cifra. */
.det-sla-leyenda{
  display:grid; grid-template-columns:repeat(2,minmax(0,1fr));
  gap:.125rem .75rem; margin-top:.5rem;
}
.det-sla-item{
  display:flex; align-items:center; gap:.375rem;
  font-size:.75rem; line-height:1.5;
}
.det-sla-punto{
  flex:none; width:.625rem; height:.625rem; border-radius:.1875rem;
}
.det-sla-nom{ flex:1 1 auto; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.det-sla-cif{ flex:none; font-variant-numeric:tabular-nums; font-weight:600; }
.det-sla-dif{ flex:none; width:2.75rem; text-align:right; font-variant-numeric:tabular-nums; }

.det-sla-pct{ font-size:1.5rem; font-weight:600; font-variant-numeric:tabular-nums; }
.det-porvencer{ color:var(--sla-porVencer); }

/* --------------------------------------------------------------------------
   Contexto en una línea
   --------------------------------------------------------------------------
   No lleva caja a propósito. Es una frase de apoyo —cuánto pesa esta categoría
   y en qué puesto está—, no una métrica que compita con las tarjetas del
   dashboard que están justo encima.
   -------------------------------------------------------------------------- */
.det-contexto{
  font-size:.8125rem; color:var(--tblr-secondary);
  padding-bottom:.75rem; margin-bottom:1rem;
  border-bottom:1px solid var(--det-borde);
}
.det-contexto strong{ color:var(--tblr-body-color); font-variant-numeric:tabular-nums; }

/* --------------------------------------------------------------------------
   Bloques plegados
   --------------------------------------------------------------------------
   El cumplimiento de SLA y la lista de tickets llegan plegados, y su petición
   sale al abrirlos. No es cosmética: esos datos necesitan la API 2 en lotes
   secuenciales, y pedirlos siempre hacía que el panel tardara eso en pintar la
   primera línea aunque nadie fuera a mirarlos.

   El titular resume el contenido para poder decidir si merece la pena abrirlo
   sin tener que abrirlo.
   -------------------------------------------------------------------------- */
.det-plegable{
  border:1px solid var(--det-borde);
  border-radius:var(--tblr-border-radius,4px);
  margin-bottom:.5rem;
}
.det-plegable-cab{
  display:flex; align-items:center; gap:.5rem;
  width:100%; padding:.625rem .75rem;
  font:inherit;
  /* `color` explícito: sin él, un <button> con font:inherit toma el color de
     texto del navegador y en tema oscuro sale casi negro sobre fondo oscuro. */
  color:var(--tblr-body-color);
  text-align:left; background:transparent; border:0;
  border-radius:var(--tblr-border-radius,4px);
  cursor:pointer;
}
.det-plegable-cab:hover{ background:var(--det-superficie); }
.det-plegable-cab:focus-visible{ outline:2px solid var(--marca-trazo); outline-offset:-2px; }
.det-plegable-nom{ font-weight:500; }
.det-plegable-tit{
  margin-left:auto; font-size:.75rem;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.det-flecha{
  flex:none; color:var(--tblr-secondary);
  transition:transform .15s ease;
}
.det-plegable-cab.abierto .det-flecha{ transform:rotate(180deg); }
.det-plegable-cuerpo{ padding:0 .75rem .75rem; }

/* --------------------------------------------------------------------------
   Ayuda emergente
   --------------------------------------------------------------------------
   El usuario de este panel es experto en tickets pero no en dashboards. Los
   términos propios de aquí tienen que estar a mano, y plegados para que quien
   ya los conoce no los relea cada vez.
   -------------------------------------------------------------------------- */
.det-ayuda{
  color:var(--tblr-secondary); vertical-align:baseline; line-height:1;
  /* El icono mide 14 px, y con `p-0` la caja pulsable medía eso: la mitad del
     mínimo cómodo. Se agranda el área SIN agrandar el icono, y el margen
     negativo evita que ese relleno separe el botón del texto al que acompaña. */
  min-width:1.5rem; min-height:1.5rem;
  display:inline-flex; align-items:center; justify-content:center;
  margin:-.3125rem 0 -.3125rem .25rem;
}
.det-ayuda:hover{ color:var(--marca-trazo); }
.det-ayuda-caja{
  background:var(--det-superficie);
  border:1px solid var(--det-borde);
  border-radius:var(--tblr-border-radius,4px);
  padding:.625rem .75rem; margin-bottom:.75rem;
  font-size:.75rem; color:var(--tblr-secondary);
}
.det-ayuda-caja p:last-child{ margin-bottom:0; }

.det-glosario{ margin:0; }
.det-glosario dt{
  font-weight:600; color:var(--tblr-body-color);
  font-size:.75rem; margin-top:.375rem;
}
.det-glosario dd{ margin:0 0 0 0; }

/* ==========================================================================
   `hidden` tiene que ocultar, siempre
   --------------------------------------------------------------------------
   Bootstrap declara las utilidades de display con `!important`, así que un
   elemento con `class="d-flex" hidden` SE SIGUE VIENDO: `.d-flex{display:flex
   !important}` gana al `[hidden]{display:none}` del navegador.

   Pasó con la franja de «Comparar con», que salía visible y vacía antes de
   marcar la casilla. Es una trampa que se repite en cuanto alguien añade una
   utilidad de display a algo que se oculta, así que se cierra de una vez para
   todo el panel en lugar de caso por caso.
   ========================================================================== */
[hidden]{ display:none !important; }

/* ==========================================================================
   Tabla de «Categorías con más tickets»
   --------------------------------------------------------------------------
   La fila ENTERA abre el detalle, no sólo el nombre: el objetivo de pulsación
   es toda la línea, que es mucho más fácil de acertar, y con tabIndex + rol de
   botón funciona igual con teclado. La pista «Ver detalle ›» se apaga hasta
   que el ratón entra, para que ocho filas no griten a la vez.
   ========================================================================== */
.cmp-fila{ cursor:pointer; }
.cmp-fila:hover{ background:var(--tblr-bg-surface-secondary,#f8fafc); }
[data-bs-theme="dark"] .cmp-fila:hover{ background:rgba(255,255,255,.04); }
.cmp-fila:focus-visible{
  outline:2px solid var(--marca-trazo); outline-offset:-2px;
}

.cmp-padre{ font-size:.75rem; color:var(--tblr-secondary); }
.cmp-hoja{ font-weight:500; }

/* --------------------------------------------------------------------------
   Las cifras del período
   --------------------------------------------------------------------------
   Cuatro tarjetas compactas, no una línea corrida: en una sola línea las
   cuatro cifras con su variación se leían como un párrafo y había que
   descifrar dónde acababa un dato y empezaba el siguiente.

   EL VALOR VA EN MORADO DE MARCA, y no es decoración: en esta tarjeta el
   morado ya significa «el período actual» —es el color de la columna «Ahora»
   y de su chip en la cabecera de la tabla, justo debajo—. Usarlo aquí hace
   que las cifras de arriba y las columnas de abajo hablen el mismo idioma.

   La VARIACIÓN se queda en tinta neutra a propósito. Que suban los tickets
   creados no es bueno ni malo por sí solo, y pintarlo de verde o rojo
   afirmaría lo contrario. El único indicador con dirección objetiva es el
   cumplimiento de SLA, y ése sí lleva su semáforo.
   -------------------------------------------------------------------------- */
.cmp-cifras{
  display:grid;
  grid-template-columns:repeat(2,minmax(0,1fr));
  gap:.75rem;
}
@media (min-width:48rem){
  .cmp-cifras{ grid-template-columns:repeat(4,minmax(0,1fr)); }
}

.cmp-cifra{
  background:var(--det-superficie);
  border:1px solid var(--det-borde);
  border-radius:var(--tblr-border-radius,4px);
  padding:.625rem .75rem;
  display:flex; flex-direction:column; gap:.125rem;
  min-width:0;   /* sin esto una cifra larga desborda la rejilla */
}
.cmp-cifra-rot{
  font-size:.6875rem; font-weight:600;
  text-transform:uppercase; letter-spacing:.04em;
  color:var(--tblr-secondary);
}
.cmp-cifra-val{
  font-size:1.375rem; font-weight:600; line-height:1.2;
  font-variant-numeric:tabular-nums;
  color:var(--marca-trazo);
}
.cmp-cifra-pie{
  font-size:.75rem; color:var(--tblr-secondary);
  min-height:1.125rem;   /* reserva el hueco: sin comparar las cajas no encogen */
}

/* La pista de que la fila se abre.
   --------------------------------------------------------------------------
   SIEMPRE visible, en gris apagado, y de marca al pasar por encima. La primera
   versión la escondía y sólo la enseñaba en la fila señalada: la columna
   quedaba vacía en las otras siete y el texto aparecía suelto en mitad de la
   nada, sin caja ni alineación con nada. Constante y discreta se lee como una
   columna, que es lo que es.

   El morado es el color de marca porque abrir el detalle es una ACCIÓN del
   usuario; el azul de enlace de Tabler es además el color con el que los KPI
   identifican «Abiertos», y se leería como un estado. */
.cmp-abrir-pista{
  display:inline-flex; align-items:center; gap:.25rem;
  color:var(--tblr-secondary); font-size:.6875rem; white-space:nowrap;
  transition:color .12s ease;

  /* Es un <button> de verdad —el camino de teclado de la fila— así que hay que
     quitarle el aspecto que le pone el navegador: sin esto salía como un
     rectángulo gris con relleno y borde en cada fila, ocho bloques opacos en
     la última columna. Lo que se quiere es texto que se comporte como botón. */
  padding:0; border:0; background:transparent;
  font-family:inherit; font-weight:inherit; text-align:left;
  cursor:pointer;
}
.cmp-abrir-pista:focus-visible{
  outline:2px solid var(--marca-trazo); outline-offset:2px;
  border-radius:2px;
}
.cmp-fila:hover .cmp-abrir-pista,
.cmp-fila:focus-visible .cmp-abrir-pista{ color:var(--marca-trazo); }

/* --------------------------------------------------------------------------
   Los desplegables de la tarjeta, con la marca
   --------------------------------------------------------------------------
   El azul de foco de fábrica es el mismo con el que los KPI identifican
   «Abiertos», y estos controles son acciones del usuario.
   -------------------------------------------------------------------------- */
#cmpDimension:focus, #cmpContra:focus{
  border-color:var(--marca-trazo);
  box-shadow:0 0 0 .25rem rgba(var(--tblr-primary-rgb),.2);
}
#cmpDimension:hover, #cmpContra:hover{ border-color:var(--marca-trazo); }

/* --- Tendencia: la serie entera en barritas verticales --- */
/* Es lo que permite ver si una categoría venía subiendo o se disparó de golpe
   sin abrir el detalle. Con seis períodos, seis columnas de cifras harían
   ilegible la tabla. */
.cmp-tendencia{
  display:flex; align-items:flex-end; gap:2px;
  height:1.75rem; min-width:4rem;
}
.cmp-tendencia > i{
  flex:1 1 0; min-width:3px;
  background:var(--cmp-barra-antes);
  border-radius:1px;
}
.cmp-tendencia > i.actual{ background:var(--marca-trazo); }

/* --- SLA por fila: dos mini barras apiladas y su lectura --- */
.cmp-sla{ display:flex; flex-direction:column; gap:2px; min-width:9rem; }
.cmp-mini{ display:flex; align-items:center; gap:.375rem; }
.cmp-mini-rot{
  flex:none; width:2.25rem;
  font-size:.5625rem; text-transform:uppercase; letter-spacing:.03em;
  color:var(--tblr-secondary);
}
.cmp-mini-barra{
  flex:1 1 auto; display:flex; height:.375rem;
  border-radius:.1875rem; overflow:hidden;
  background:var(--det-via);
}
.cmp-mini-barra > span{ display:block; height:100%; }
.cmp-sla-txt{ font-size:.6875rem; margin-left:2.625rem; }

/* El botón «Ver detalle» usa `btn-outline-primary` de catálogo.
   --------------------------------------------------------------------------
   Aquí hubo un bloque propio justificado con una trampa de Tabler que NO
   existe: decía que `btn-outline-primary` toma el borde de `--tblr-primary`
   pero el hover de `--tblr-primary-rgb` y que el proyecto sólo sobrescribe uno.
   Es falso en beta20 —esa clase saca de `--tblr-primary` el borde, el texto y
   el fondo de hover— y el proyecto sobrescribe las dos variables. La prueba
   está en la misma página: «Recargar reporte» y «Expandir» la usan y salen
   moradas en los dos temas. Se quitaron esas veinte líneas. */

@media (prefers-reduced-motion:reduce){
  .offcanvas{ transition:none !important; }
}

/* ==========================================================================
   Impresión
   --------------------------------------------------------------------------
   Los reportes se imprimen. Sin estas reglas salía la navegación, los botones
   y las barras de desplazamiento, los fondos de color se perdían —el navegador
   los omite por defecto para ahorrar tinta, y aquí esos fondos SON el dato— y
   los filtros plegados no se imprimían, de modo que el papel no decía sobre
   qué datos se había generado.
   ========================================================================== */
@media print{
  *{
    print-color-adjust:exact;
    -webkit-print-color-adjust:exact;
  }
  body{ background:#fff; }

  .navbar,
  .card-actions,
  .pagination,
  .btn,
  .skip-link,
  #filtro-loading,
  .card-progress{
    display:none !important;
  }

  /* La cabecera fija se imprimiría en cada página o taparía contenido. */
  .page-header{ position:static !important; }
  .table-sticky-head thead th{ position:static !important; }
  #dataTable thead th:first-child,
  #dataTable tbody td:first-child{ position:static !important; box-shadow:none; }

  /* Los filtros van plegados en pantalla; en papel hay que ver con qué se
     generó el reporte. */
  .collapse{ display:block !important; height:auto !important; visibility:visible !important; }

  /* Nada de scroll en papel: se imprime el contenido completo. */
  #tabla-envoltorio,
  .table-responsive,
  .category-tree{
    max-height:none !important;
    overflow:visible !important;
  }

  .card{
    box-shadow:none !important;
    border:1px solid #d0d0d0;
    break-inside:avoid;
  }
  .row-cards > *{ break-inside:avoid; }
}

/* ---------------------------------------------------------------------------
   MULTI-PROYECTO
   ---------------------------------------------------------------------------
   Todo lo de aqui abajo se apoya en las variables de Tabler y en la marca ya
   definida arriba (--tblr-primary). Ni un color literal nuevo: cambiar la marca
   en un sitio sigue cambiandola en todas partes.
   -------------------------------------------------------------------------- */

/* --- Selector de ciudad en la barra de navegacion ------------------------- */
/* El relleno sale del morado de marca y NO de `--tblr-bg-surface-secondary`,
   que era lo que habia. Ese token vale `gray-100` (#f6f8fb) en claro, y la
   barra de navegacion de Tabler es BLANCA: el selector quedaba plano, sin mas
   pista que un borde de 1 px, mientras que en oscuro el mismo token (#1b293a
   sobre una barra #182433) sí lo dibujaba como una pastilla. La pieza central
   del multi-proyecto se veia como un control solo en uno de los dos temas.
   Con un lavado de marca se comporta igual en ambos, y de paso dice a que
   pertenece. */
.selector-proyecto .dropdown-toggle{
  display:inline-flex; align-items:center; gap:.5rem;
  border:1px solid rgba(var(--tblr-primary-rgb),.20); border-radius:.5rem;
  padding:.3rem .6rem; font-size:.875rem; font-weight:500;
  background:rgba(var(--tblr-primary-rgb),.06); color:inherit;
}
[data-bs-theme="dark"] .selector-proyecto .dropdown-toggle{
  border-color:rgba(var(--tblr-primary-rgb),.32);
  background:rgba(var(--tblr-primary-rgb),.14);
}
.selector-proyecto .dropdown-toggle:hover{ border-color:var(--tblr-primary); }
.selector-proyecto .dropdown-menu{ min-width:15rem; }

/* La etiqueta de la ciudad: MID, CUN. Aparece junto al nombre, junto a cada
   hora y delante del nombre de los Excel. Es lo que hace que un dato se sepa
   de que ciudad es sin tener que preguntar. */
.cinta-proyecto{
  font-family:var(--tblr-font-monospace,ui-monospace,monospace);
  font-size:.625rem; font-weight:500; letter-spacing:.06em;
  padding:.125rem .375rem; border-radius:.25rem;
  background:rgba(var(--tblr-primary-rgb),.10); color:var(--tblr-primary);
  white-space:nowrap;
}
[data-bs-theme="dark"] .cinta-proyecto{ background:rgba(var(--tblr-primary-rgb),.20); }

/* Modulo en obras dentro del menu: atenuado y con distintivo, nunca ausente.
   Desaparecer se lee como "me han quitado el permiso"; el distintivo dice la
   verdad, que es cuestion de tiempo. */
.nav-link.modulo-obra{ opacity:.62; }
.nav-link.modulo-obra .marca-obra{
  display:inline-block; width:.45rem; height:.45rem; border-radius:.125rem;
  background:var(--tblr-warning,#f59f00); margin-left:.4rem; vertical-align:middle;
}

/* Hora con etiqueta de ciudad: "14:22 CUN". */
.reloj-proyecto{
  display:inline-flex; align-items:center; gap:.35rem;
  font-variant-numeric:tabular-nums; font-size:.8125rem; color:var(--tblr-secondary);
}

/* ==========================================================================
   Cartel de modulo en construccion
   --------------------------------------------------------------------------
   Es la pantalla que mas se ve de un modulo que todavia no existe, asi que no
   puede parecer una pagina de error. Tiene que decir tres cosas en este orden:
   QUE modulo falta, EN QUE ciudad, y CUANDO se espera -- y no cargar nada.

   Todo el dibujo es CSS: no hay imagen, ni icono suelto, ni una sola peticion.
   ========================================================================== */
.obra-card{
  max-width:38rem;
  overflow:hidden;          /* la valla se recorta con las esquinas de la tarjeta */
}

/* --- La valla de obra -----------------------------------------------------
   El patron es un mosaico de 18px, no un `repeating-linear-gradient`: asi el
   desplazamiento de la animacion coincide EXACTAMENTE con el ancho del mosaico
   y el bucle no da el saltito que deja un periodo mal medido. */
.obra-valla{
  height:8px;
  background-image:linear-gradient(135deg,
    var(--obra-valla-color) 25%, transparent 25% 50%,
    var(--obra-valla-color) 50% 75%, transparent 75%);
  background-size:18px 18px;
  animation:obra-desfile 3s linear infinite;
}
:root{ --obra-valla-color:rgba(var(--tblr-warning-rgb,245,159,0),.85); }
.obra-card-plan{ --obra-valla-color:rgba(var(--tblr-secondary-rgb,102,115,130),.5); }

@keyframes obra-desfile{ to{ background-position:18px 0; } }

/* La valla es decoracion: quien pide menos movimiento la ve quieta, no ausente. */
@media (prefers-reduced-motion:reduce){
  .obra-valla{ animation:none; }
}

/* --- Cabecera de la tarjeta ----------------------------------------------- */
.obra-card .card-body{ padding:2.5rem 1.5rem 2rem; }

/*
 * LA MEDALLA LLEVA EL ICONO DEL PROPIO MODULO, no un cono de obra generico.
 * Quien aterriza aqui viene de pulsar una entrada del menu, y la primera
 * pregunta es «cual era». El estado ya lo cuentan la valla, el titular y la
 * escalera; repetirlo aqui gastaria el unico sitio que puede responder otra
 * cosa.
 */
.obra-medalla{
  width:3.5rem; height:3.5rem; margin:0 auto 1.25rem;
  display:flex; align-items:center; justify-content:center;
  border-radius:50%;
  background:rgba(var(--tblr-warning-rgb,245,159,0),.14);
  color:var(--obra-tinta);
  font-size:1.75rem; line-height:1;
}
.obra-card-plan .obra-medalla{
  background:rgba(var(--tblr-secondary-rgb,102,115,130),.14);
  color:var(--tblr-secondary);
}

.obra-eyebrow{
  display:flex; align-items:center; justify-content:center; gap:.5rem;
  margin:0 0 .625rem;
  font-size:.6875rem; font-weight:600; letter-spacing:.13em;
  text-transform:uppercase; color:var(--tblr-secondary);
}

.obra-titular{
  margin:0 0 .75rem;
  font-size:1.5rem; font-weight:600; line-height:1.25;
  text-wrap:balance;
}

.obra-parrafo{
  max-width:32rem; margin:0 auto 1.75rem;
  color:var(--tblr-secondary); line-height:1.6;
  text-wrap:pretty;
}

/* --------------------------------------------------------------------------
   `--obra-tinta`: el ambar con el que se escribe sobre el aviso de obras
   --------------------------------------------------------------------------
   Aqui habia un error que solo se veia en TEMA CLARO, y era grave: el color se
   pedia como `var(--tblr-warning-fg, #a86a00)` dando por supuesto que ese
   token no existia y que se aplicaria el respaldo ambar. Existe. Tabler lo
   declara en su bloque de variables como `--tblr-warning-fg: var(--tblr-light)`
   -- es decir, #fcfdfe, BLANCO --, porque su proposito es el texto que va
   ENCIMA de un relleno naranja solido, no sobre un lavado del 12 %.

   El respaldo, por tanto, nunca llegaba a usarse, y la nota quedaba en blanco
   sobre un fondo casi blanco (#feede1): 1.1:1. El texto que explica por que un
   modulo esta en obras era literalmente invisible en claro. En oscuro no se
   notaba porque habia un override explicito debajo, y esa asimetria es la que
   hacia parecer el tema claro «a medio terminar».

   Contraste verificado (WCAG 2.1) con los valores nuevos:
     claro   #8a5600 sobre la nota #feede1 ......... 5.40:1
     claro   #8a5600 sobre la tarjeta blanca ....... 6.15:1  (paso «estas aqui»)
     oscuro  #f0b429 sobre la nota #2c2c2e ......... 7.48:1
     oscuro  #f0b429 sobre la tarjeta #182433 ...... 8.42:1

   El valor oscuro tambien se fija a mano en vez de heredar `--tblr-warning`:
   ese token vale #f76707, que sobre la nota se queda en 4.58:1 -- pasa por los
   pelos y sin margen para el ojo cansado.
   -------------------------------------------------------------------------- */
:root{ --obra-tinta:#8a5600; }
[data-bs-theme="dark"]{ --obra-tinta:#f0b429; }

/* --- La escalera de estados ----------------------------------------------- */
.obra-escalera{
  display:flex; align-items:flex-start; gap:0;
  max-width:24rem; margin:0 auto 1.75rem;
  list-style:none; padding:0;
}
.obra-paso{
  flex:1; display:flex; flex-direction:column; align-items:center; gap:.5rem;
  position:relative;
}
.obra-paso::before{
  content:""; position:absolute; top:7px; left:-50%; width:100%; height:2px;
  background:var(--tblr-border-color);
}
.obra-paso:first-child::before{ display:none; }
.obra-punto{
  width:16px; height:16px; border-radius:50%; position:relative; z-index:1;
  border:2px solid var(--tblr-border-color); background:var(--tblr-bg-surface);
}
.obra-texto{ font-size:.78rem; font-weight:500; color:var(--tblr-secondary); }

.obra-hecho .obra-punto{ background:var(--tblr-green,#2fb344); border-color:var(--tblr-green,#2fb344); }

/*
 * EL TRAMO RECORRIDO SE PINTA DELANTE DEL PASO HECHO, NO DETRAS.
 *
 * Cada `::before` dibuja la linea que ENTRA en su paso, asi que la de un paso
 * completado es la que queda a su izquierda -- y la del primero esta oculta.
 * Coloreando `.obra-hecho::before` el tramo entre «Planeacion» y
 * «Construccion» se quedaba gris: la escalera enseñaba un punto verde suelto,
 * sin camino. Se pinta el `::before` del paso SIGUIENTE.
 */
.obra-hecho + .obra-paso::before{ background:var(--tblr-green,#2fb344); }

.obra-aqui .obra-punto{
  background:var(--tblr-warning,#f59f00); border-color:var(--tblr-warning,#f59f00);
  box-shadow:0 0 0 4px rgba(var(--tblr-warning-rgb,245,159,0),.18);
}
/* Mismo caso que la nota: se pedia `--tblr-warning-fg`, que es blanco, y el
   paso «estas aqui» de la escalera se borraba en tema claro. */
.obra-aqui .obra-texto{ color:var(--obra-tinta); font-weight:600; }

/* El latido dice «es aqui donde esta» sin depender del color, que es lo unico
   que distinguia este paso de los otros dos. */
.obra-aqui .obra-punto::after{
  content:""; position:absolute; inset:-2px; border-radius:50%;
  border:2px solid var(--tblr-warning,#f59f00);
  animation:obra-latido 2.4s ease-out infinite;
}
@keyframes obra-latido{
  0%   { transform:scale(1);   opacity:.7; }
  70%  { transform:scale(2.1); opacity:0;  }
  100% { transform:scale(2.1); opacity:0;  }
}
@media (prefers-reduced-motion:reduce){
  .obra-aqui .obra-punto::after{ animation:none; opacity:0; }
}

/* En planeacion el paso actual no es ambar: nada esta en marcha todavia. */
.obra-card-plan .obra-aqui .obra-punto{
  background:var(--tblr-secondary); border-color:var(--tblr-secondary);
  box-shadow:0 0 0 4px rgba(var(--tblr-secondary-rgb,102,115,130),.16);
}
.obra-card-plan .obra-aqui .obra-punto::after{ display:none; }
.obra-card-plan .obra-aqui .obra-texto{ color:var(--tblr-secondary); }

/* --- La nota configurada en /projects ------------------------------------- */
.obra-nota{
  display:inline-flex; align-items:flex-start; gap:.5rem;
  max-width:32rem; margin:0 auto 1.75rem; text-align:left;
  font-size:.875rem; font-weight:500; border-radius:.5rem; padding:.625rem .875rem;
  background:rgba(var(--tblr-warning-rgb,245,159,0),.12); color:var(--obra-tinta);
}
.obra-nota .ti{ flex:none; margin-top:.125rem; }

/* --- Los dos botones ------------------------------------------------------
   A partir de 480px caben en fila; por debajo se apilan a lo ancho, porque dos
   botones a media anchura en un movil dejan el texto en tres lineas. */
.obra-acciones{ display:flex; flex-direction:column; gap:.5rem; }
@media (min-width:480px){
  .obra-acciones{ flex-direction:row; flex-wrap:wrap; justify-content:center; }
}

/* --- La franja de administrador ------------------------------------------- */
.obra-admin{
  font-size:.8125rem; color:var(--tblr-secondary);
  background:rgba(var(--tblr-primary-rgb),.05);
  border-top:1px dashed var(--tblr-border-color);
}
/* El mismo defecto de la nota, en la direccion contraria: un 5 % de morado
   sobre superficie oscura no se distingue del fondo y la franja de
   administrador se leia como parte de la tarjeta. */
[data-bs-theme="dark"] .obra-admin{ background:rgba(var(--tblr-primary-rgb),.12); }

.obra-admin-titulo{
  display:flex; align-items:center; gap:.375rem; margin:0 0 .375rem;
  font-size:.625rem; font-weight:600; letter-spacing:.12em; text-transform:uppercase;
  color:var(--tblr-primary);
}
.obra-admin-texto{ margin:0 0 .625rem; }

/*
 * LAS VARIABLES, EN LISTA Y NO EN UNA FRASE.
 *
 * Iban separadas por comas dentro del parrafo, y cuatro nombres largos en
 * mayusculas cortados a mitad de linea son ilegibles justo para quien tiene que
 * copiarlos. En fichas separadas se leen de un vistazo y se ve cuantos son.
 */
.obra-claves{
  display:flex; flex-wrap:wrap; gap:.375rem;
  list-style:none; padding:0; margin:0 0 .75rem;
}
.obra-claves code{
  display:inline-block; padding:.1875rem .4375rem; border-radius:.25rem;
  background:var(--tblr-bg-surface); border:1px solid var(--tblr-border-color);
  color:var(--tblr-body-color); font-size:.75rem;
}

.obra-copiar.obra-copiado{
  border-color:var(--tblr-green,#2fb344);
  color:var(--tblr-green,#2fb344);
}
/* --- Formularios: autocompletado del navegador ---------------------------- */
/* Sin esta regla Chrome pinta los campos rellenados con SU color y se salta el
   tema de la aplicacion. Es lo que hacia que el login se viera con recuadros
   grises, y en tema oscuro seria peor: texto oscuro sobre fondo claro dentro de
   una tarjeta negra. */
.form-control:-webkit-autofill,
.form-control:-webkit-autofill:hover,
.form-control:-webkit-autofill:focus,
.form-select:-webkit-autofill{
  -webkit-text-fill-color:var(--tblr-body-color);
  -webkit-box-shadow:0 0 0 1000px var(--tblr-bg-forms,var(--tblr-bg-surface)) inset;
          box-shadow:0 0 0 1000px var(--tblr-bg-forms,var(--tblr-bg-surface)) inset;
  caret-color:var(--tblr-body-color);
  /* La transicion larguisima es el truco habitual para que el navegador no
     vuelva a pintar su fondo al recuperar el foco. */
  transition:background-color 9999s ease-out;
}

/* --- Matriz de accesos por ciudad ----------------------------------------- */
.matriz-accesos th, .matriz-accesos td{ text-align:center; vertical-align:middle; }
.matriz-accesos th:first-child, .matriz-accesos td:first-child{ text-align:left; }
.matriz-accesos .form-check{ display:inline-block; margin:0; padding:0; }
.matriz-accesos .form-check-input{ margin:0; float:none; }

.resumen-claro{
  border-left:3px solid var(--tblr-primary);
  background:rgba(var(--tblr-primary-rgb),.06);
  border-radius:.5rem; padding:.9rem 1.1rem;
}
.resumen-claro dt{ font-weight:600; }
.resumen-claro dd{ color:var(--tblr-secondary); margin-bottom:.35rem; }

/* --- Grupos de permisos en el editor de roles ----------------------------- */
.grupo-permisos + .grupo-permisos{ margin-top:.75rem; }
.grupo-permisos .card-header{ padding:.6rem 1rem; }
.grupo-permisos .lista-permisos{
  display:grid; grid-template-columns:repeat(auto-fit,minmax(14rem,1fr)); gap:.5rem 1rem;
}
.permiso-vetado{ opacity:.55; }
.permiso-slug{
  font-family:var(--tblr-font-monospace,ui-monospace,monospace);
  font-size:.6875rem; color:var(--tblr-secondary); display:block; line-height:1.2;
}

/* --- Pie de pagina --------------------------------------------------------
   Ninguna pantalla del panel tenia pie: todas terminaban en el borde del
   ultimo elemento, pegado al final del documento. Se lee como si faltara
   contenido por cargar, y en las que cargan por partes esa duda es peor: no
   se distingue «esto es todo» de «esto aun no ha llegado».

   Es discreto a proposito -- una linea fina y texto apagado --: su trabajo es
   cerrar la pagina, no competir con ella.

   EL RELLENO VA EN LA FORMA CORTA, no como `padding-top`/`padding-bottom`.
   Tabler ya le da a `.footer` el suyo, generoso, porque su pie de referencia
   lleva columnas de enlaces; sumarle mas dejaba una banda de mas de cien
   pixeles para una sola linea de texto. La forma corta lo sustituye entero en
   vez de añadirse. */
/* EL PIE SE ANCLA AL FONDO DE LA VENTANA.
   Con un pie de altura normal pero contenido corto -- el Dashboard antes de
   cargar un reporte, una pestaña sin resultados --, el pie quedaba a media
   pagina y debajo se abria un hueco muerto de varios cientos de pixeles. Eso
   no es «la pagina termina aqui»: es «la pagina se corto aqui».

   El cuerpo se dispone en columna y el pie se empuja con `margin-top:auto`,
   que es lo que ocupa el espacio sobrante en lugar de dejarlo al final. Los
   hijos de <body> son todos bloques -- barra, cabecera, cuerpo, pie --, asi que
   la columna se comporta igual que el flujo normal cuando el contenido si
   llena la pantalla. Los modales y menus son de posicion fija o absoluta y
   quedan fuera del flujo, de modo que no se convierten en columnas. */
body{
  min-height:100vh;
  display:flex;
  flex-direction:column;
}

.footer{
  margin-top:auto;             /* empuja el pie hasta el fondo */
  padding:.625rem 0;
  border-top:1px solid var(--tblr-border-color);
  line-height:1.4;
}

/* Separacion minima cuando el contenido SI llega abajo: sin esto, el pie se
   pegaria al ultimo elemento en las pantallas largas. */
.page-body{ margin-bottom:1.5rem; }

.footer.footer-transparent{ background:transparent; }
