/* ═══════════════════════════════════════════════════════════════════════════
   Aplicación de vencimientos — lo que el sistema de marca no tiene
   ═══════════════════════════════════════════════════════════════════════════

   REGLA DE ORO, la de siempre: aquí no se define ni un color, ni una fuente,
   ni una sombra. Todo sale de los tokens de `sistema.css`, que es el sistema
   compartido con la web. Los tokens propios llevan prefijo `--app-` para que
   nunca choquen y para que el sincronizador pueda distinguirlos.

   LO QUE ESTO ES, Y LO QUE NO. La web se mira una vez y tiene que impresionar;
   esto se usa ocho horas al día y tiene que desaparecer. Comparten la familia
   —Plus Jakarta Sans, morado y teal de marca, lienzo con sesgo lila, esquinas
   redondeadas, filo de 1 px, sombra tintada— y no comparten NADA de su
   comportamiento: aquí no hay revelado al desplazarse, ni entradas
   escalonadas, ni héroes, ni scroll cinematográfico. En una herramienta la
   elegancia es que se lea rápido y no canse.

   LAS TRES COSAS QUE MANDAN EN ESTE ARCHIVO:

   1. DENSIDAD. Claudia recorre cincuenta renglones buscando uno. Todo lo que
      quita renglones de la pantalla —un radio grande, un margen generoso, una
      tipografía de display— le cuesta desplazamientos a ella.
   2. EL ESTADO SE VE SIN LEER, Y NUNCA SOLO POR EL COLOR. Cada pastilla lleva
      color, FORMA y texto. Uno de cada doce hombres no distingue el rojo del
      verde y esta pantalla es exactamente la que separa «vencido» de «al día».
   3. NADA SE MUEVE SOLO. Solo responde a lo que la persona toca.
   ═══════════════════════════════════════════════════════════════════════════ */


/* ── 0 · Compatibilidad ─────────────────────────────────────────────────────
   `src/index.js` escribe en línea `var(--tinta-2)`, `var(--tinta-3)`,
   `var(--bien)` y `var(--atencion)` en una veintena de sitios —subtítulos de
   celda, párrafos de ayuda, el estado del interruptor maestro—. Esos cuatro
   tokens NO EXISTEN en ningún sistema: son restos de una versión anterior. Un
   `color:var(--nombre-que-no-existe)` no falla ni avisa, simplemente no se
   aplica, así que todo ese texto secundario se estaba pintando con el color
   heredado y se veía igual de negro que el principal. Es exactamente el tipo
   de fallo silencioso del que vive avisando la documentación de este proyecto.

   Se resuelven aquí, como alias, y no borrando los treinta usos del marcado:
   una pasada de interfaz no tiene por qué tocar treinta rutas para arreglar
   cuatro nombres. Quedan marcados para que quien los limpie sepa qué eran. */
:root{
  --tinta-2:var(--suave);
  --tinta-3:var(--tenue);
  --bien:var(--ok);
  --atencion:var(--aviso);

  /* Medidas propias de la herramienta. */
  --app-cab-alto:58px;
  --app-tabla:14.5px;           /* cuerpo de tabla: un paso bajo el del sistema */
  --app-mini:11.5px;
  --app-vig-alto:6px;           /* el medidor. Medía 3 px y se leía como un rayón */
  --app-fila-y:10px;            /* aire vertical de celda */
}

/* ── 1 · El cascarón ────────────────────────────────────────────────────────
   La cabecera es la de la web: cristal, filo de 1 px, esquinas suaves. Lo
   único que la distingue es el rótulo «Vencimientos», para que se sepa que ya
   no se está en el sitio público.

   El filete inferior de dos píxeles va del morado al teal y es el ÚNICO
   degradado de cromo de toda la interfaz. Un solo lugar, y es el que cose el
   sitio con la aplicación. */
.app-cuerpo{ min-height:100vh; display:flex; flex-direction:column }

.app-cab{
  position:sticky; top:0; z-index:50;
  background:var(--cristal);
  -webkit-backdrop-filter:saturate(180%) blur(16px);
  backdrop-filter:saturate(180%) blur(16px);
  box-shadow:inset 0 -1px 0 var(--filete2);
  transition:box-shadow var(--t-entra) var(--sal), background-color var(--t-entra) var(--sal);
}
.app-cab::after{
  content:""; position:absolute; inset:auto 0 -2px 0; height:2px;
  background:linear-gradient(90deg,var(--acento) 0%,var(--acento) 38%,var(--apoyo) 100%);
}
/* Al desplazarse, la cabecera gana cuerpo. No es un adorno: es lo que le dice
   a quien va por la fila cuarenta que ese bloque de arriba flota y que hay
   contenido por encima. */
.app-cab[data-pegada]{ background:var(--cristal-bd); box-shadow:inset 0 -1px 0 var(--filete2), var(--s2) }

.app-cab__fila{
  max-width:var(--ancho); margin-inline:auto; min-height:var(--app-cab-alto);
  padding:8px clamp(16px,3vw,30px);
  display:flex; align-items:center; gap:clamp(12px,2vw,24px);
}
.app-cab__marca{ display:flex; align-items:center; gap:10px; flex-shrink:0; min-height:44px }
.app-cab__marca img{ height:28px; width:auto }
.app-cab__marca span{
  font-size:var(--app-mini); font-weight:700; text-transform:uppercase;
  letter-spacing:.13em; color:var(--tenue);
  padding-left:11px; border-left:1px solid var(--filete);
}

/* La navegación va en píldoras, no en subrayados. Con subrayado, la sección
   activa se distingue por dos píxeles al pie de una palabra; con píldora, se
   distingue por una mancha de color que se ve sin enfocar la vista. En una
   barra que se mira de reojo cincuenta veces al día eso es la diferencia. */
.app-nav{ display:flex; gap:2px; margin-left:auto; align-items:center; flex-wrap:wrap }
.app-nav a{
  display:inline-flex; align-items:center; min-height:36px; padding:0 13px;
  border-radius:999px; font-size:var(--t-s); font-weight:500; color:var(--suave);
  transition:color var(--t-sale) var(--sal), background-color var(--t-sale) var(--sal);
}
.app-nav a:hover{ color:var(--tinta); background:rgba(119,21,113,.07) }
.app-nav a[aria-current]{ color:var(--acento); font-weight:700; background:rgba(119,21,113,.11) }

.app-quien{
  font-size:var(--app-mini); text-transform:uppercase; letter-spacing:.1em;
  color:var(--tenue); white-space:nowrap; display:flex; align-items:center; gap:4px;
}
.app-quien b{ color:var(--tinta); font-weight:700 }
/* «Salir» medía 29×14 px. Un enlace incrustado en una línea de texto no es un
   objetivo táctil: se le da área propia. */
.app-quien a{
  display:inline-flex; align-items:center; min-height:44px; padding:0 10px;
  border-radius:999px; color:var(--acento); font-weight:700;
  transition:background-color var(--t-sale) var(--sal);
}
.app-quien a:hover{ background:rgba(119,21,113,.09) }

.app-lienzo{ flex:1; width:100%; max-width:var(--ancho); margin-inline:auto;
  padding:clamp(18px,2.6vw,28px) clamp(16px,3vw,30px) 64px }

.app-pie{ background:var(--papel); box-shadow:inset 0 1px 0 var(--filete2) }
.app-pie div{ max-width:var(--ancho); margin-inline:auto;
  padding:14px clamp(16px,3vw,30px); font-size:var(--t-xs); color:var(--tenue);
  line-height:2.4 }
.app-pie a{
  display:inline-flex; align-items:center; min-height:44px; padding:0 10px;
  border-radius:999px; font-weight:600;
  transition:background-color var(--t-sale) var(--sal);
}
.app-pie a:hover{ background:rgba(119,21,113,.08) }

/* ── 2 · Encabezado de pantalla ─────────────────────────────────────────── */
.app-titulo{
  display:flex; align-items:flex-end; justify-content:space-between;
  gap:16px; flex-wrap:wrap; margin-bottom:clamp(14px,2vw,20px);
}
.app-titulo h1{ font-size:var(--t-xl); font-weight:700; letter-spacing:-.02em;
  line-height:1.12; margin:0 }
.app-titulo h1 em{ font-style:normal; color:var(--acento) }
.app-titulo p{ margin:6px 0 0; color:var(--suave); font-size:var(--t-s);
  max-width:72ch; line-height:1.5 }
/* El rótulo con el punto teal, igual que en la web. Quieto: allá late porque
   encabeza una sección que se ve una vez; aquí sería un parpadeo permanente. */
.app-rotulo{
  display:inline-flex; align-items:center; gap:7px; margin:0 0 6px;
  font-size:var(--app-mini); font-weight:700; letter-spacing:.14em;
  text-transform:uppercase; color:var(--apoyo-txt);
}
.app-rotulo::before{ content:""; width:6px; height:6px; border-radius:50%;
  background:var(--apoyo); flex:none }

/* ── 3 · Las cifras ─────────────────────────────────────────────────────────
   Seis rectángulos blancos no son un tablero: son seis números. Lo que hace
   que cada uno se lea de reojo ANTES de leer su cifra es el color de su
   propio estado, y por eso cada tarjeta lleva un filete superior grueso del
   color que le toca. El filete es horizontal y de tres píxeles porque a esa
   altura se ve desde lejos sin teñir la tarjeta entera: una tarjeta con el
   fondo rojo entero grita, y si tres de seis gritan, no grita ninguna.

   El número hereda el mismo color; el rótulo, no. Si los dos fueran de color
   la tarjeta se vuelve una mancha y el rótulo deja de leerse. */
.app-cifras{
  display:grid; gap:clamp(9px,1.2vw,12px); margin-bottom:clamp(16px,2.2vw,22px);
  grid-template-columns:repeat(auto-fit,minmax(142px,1fr));
}
.app-cifra{
  position:relative; overflow:hidden; border-radius:var(--r);
  background-image:var(--cristal-tarjeta);
  box-shadow:var(--filo-doble), var(--s1);
  padding:15px 16px 14px;
  transition:transform var(--t-sale) var(--sal), box-shadow var(--t-sale) var(--sal);
}
/* La cifra sin estado —«vencen este mes», «debieron salir hoy»— lleva filete
   gris lila y no «ninguno». Una tarjeta sin filete en una fila de tarjetas con
   filete no se lee como neutra: se lee como que algo no cargó. */
.app-cifra::before{
  content:""; position:absolute; inset:0 0 auto 0; height:3px;
  background:var(--app-tinte,var(--quieto-filo)); border-radius:var(--r) var(--r) 0 0;
}
.app-cifra:hover{ transform:translateY(-2px); box-shadow:var(--filo-doble), var(--s2) }
.app-cifra__v{ font-size:32px; font-weight:700; line-height:1.05; letter-spacing:-.03em;
  font-variant-numeric:tabular-nums; color:var(--tinta) }
.app-cifra__e{ font-size:var(--app-mini); font-weight:600; text-transform:uppercase;
  letter-spacing:.1em; color:var(--tenue); margin-top:7px; line-height:1.4 }

.app-cifra--mal { --app-tinte:var(--alerta) } .app-cifra--mal  .app-cifra__v{ color:var(--alerta) }
.app-cifra--ojo { --app-tinte:var(--aviso)  } .app-cifra--ojo  .app-cifra__v{ color:var(--aviso) }
.app-cifra--bien{ --app-tinte:var(--ok)     } .app-cifra--bien .app-cifra__v{ color:var(--ok) }
.app-cifra--acc { --app-tinte:var(--acento) } .app-cifra--acc  .app-cifra__v{ color:var(--acento) }

/* Una cifra que no es un número —una fecha, un valor en pesos— no puede ir a
   32 px o parte la tarjeta en dos líneas. */
.app-cifra__v{ overflow-wrap:anywhere }

/* ── 4 · Paneles y avisos ───────────────────────────────────────────────── */
.app-tarjeta{
  background-image:var(--cristal-tarjeta); border-radius:var(--r);
  box-shadow:var(--filo-doble), var(--s1);
  padding:clamp(15px,1.9vw,20px); margin-bottom:clamp(12px,1.6vw,16px);
}
.app-tarjeta > h2{ font-size:var(--t-m); font-weight:700; letter-spacing:-.015em;
  margin:0 0 3px }
.app-tarjeta > h2 + p{ margin:0 0 13px; color:var(--suave); font-size:var(--t-xs) }
/* Un título pegado al encabezado de su tabla se lee como una fila más. Los
   rótulos de columna van en versalitas de 11,5 px y el título en 17 px: sin
   aire entre los dos, el ojo los agrupa. */
.app-tarjeta > h2 + .app-tabla-marco,
.app-tarjeta > h2 + table,
.app-tarjeta > h2 + form{ margin-top:13px }
.app-tarjeta--plana{ background-image:none; background:transparent;
  box-shadow:inset 0 0 0 1px var(--filete2) }
/* MEDIDA DE LECTURA. Los paneles de Operación ocupan 1.180 px y sus párrafos
   explican por qué el interruptor arranca apagado o qué significa una corrida:
   son textos que hay que leer enteros, no datos que se barren. A ancho
   completo el ojo pierde el renglón al volver a la izquierda. 80 caracteres es
   el techo; el panel sigue siendo ancho porque lo que va debajo son tablas. */
.app-tarjeta > p,
.app-tarjeta > form > p,
.app-tarjeta > ul{ max-width:80ch }

/* El aviso permanente. Lleva barra de color a la izquierda Y fondo lavado Y
   un título en versalitas: tres señales para el mismo hecho, porque estos son
   los que dicen que el motor está apagado o que faltan secretos, y que uno de
   esos pase inadvertido es el fallo que la aplicación entera existe para
   evitar. */
.app-nota{
  position:relative; padding:12px 16px 12px 17px; border-radius:var(--r-s);
  margin-bottom:12px; font-size:var(--t-s); line-height:1.5;
  background:var(--apoyo-lav); box-shadow:inset 0 0 0 1px var(--apoyo-filo);
  color:var(--suave);
}
/* La nota SIN estado va en teal, que es el color de apoyo del sistema: no es
   una alarma, es una explicación —«Sobre el documento», «Ojo con esto»—. El
   gris lila se reserva para lo que está inerte (pausado, cumplido, anulado), y
   una nota que explica algo no está inerte. */
.app-nota::before{ content:""; position:absolute; inset:0 auto 0 0; width:3px;
  border-radius:var(--r-s) 0 0 var(--r-s); background:var(--apoyo) }
/* SOLO EL PRIMER <strong> ES EL TÍTULO. La regla apuntaba a todos y convertía
   en rótulo en versalitas cada énfasis del cuerpo: en la ficha de un
   vencimiento, «arranca de nuevo» y «renovar» —que van en negrita dentro de la
   frase— se partían a renglón aparte y la advertencia quedaba ilegible. El
   patrón del marcado es siempre <strong>título</strong> seguido del cuerpo. */
.app-nota > strong:first-child{ display:block; font-size:var(--app-mini); font-weight:700;
  text-transform:uppercase; letter-spacing:.11em; margin-bottom:4px; color:var(--tinta) }
.app-nota strong{ color:var(--tinta) }
.app-nota--ojo { background:var(--aviso-lav);  box-shadow:inset 0 0 0 1px var(--aviso-filo) }
.app-nota--ojo::before { background:var(--aviso) }
.app-nota--mal { background:var(--alerta-lav); box-shadow:inset 0 0 0 1px var(--alerta-filo) }
.app-nota--mal::before { background:var(--alerta) }
.app-nota--bien{ background:var(--ok-lav);     box-shadow:inset 0 0 0 1px var(--ok-filo) }
.app-nota--bien::before{ background:var(--ok) }
.app-nota ul{ margin:7px 0 0; padding-left:18px }
.app-nota li{ margin-bottom:3px }
.app-nota code{ background:var(--fondo2); padding:1px 6px; border-radius:5px;
  font-family:ui-monospace,SFMono-Regular,Menlo,monospace; font-size:.92em }
/* El enlace del aviso —«se enciende en Operacion»— es lo único que queda por
   debajo de 44 px en toda la aplicación, y es el caso que WCAG 2.5.8 exime
   expresamente: un enlace DENTRO de una frase no puede crecer sin separar los
   renglones. Lo que sí se puede es engordar su área sensible con relleno
   vertical, que en un elemento en línea no empuja la maquetación: pasa de 18 a
   26 px de alto y con eso cumple el mínimo de 24 px de la norma. */
.app-nota a:not(.btn){ color:var(--acento); text-decoration:underline;
  text-underline-offset:3px; font-weight:600; padding-block:4px }

/* ── 5 · La tabla densa ─────────────────────────────────────────────────────
   Una herramienta se lee en filas, no en tarjetas. La fila se marca al pasar
   el cursor con un lavado de morado muy bajo y no con una sombra: con sombra,
   recorrer la lista hace vibrar toda la tabla. */
.app-tabla-marco{
  overflow-x:auto; -webkit-overflow-scrolling:touch;
  margin-inline:calc(-1 * clamp(15px,1.9vw,20px));
  padding-inline:clamp(15px,1.9vw,20px);
}
.app-tabla{ width:100%; border-collapse:collapse; font-size:var(--app-tabla) }
/* EL ENCABEZADO NO VA PEGADO, y esto merece una explicación porque estaba y
   parecía que funcionaba. Llevaba `position:sticky; top:0`, pero la tabla vive
   dentro de `.app-tabla-marco`, que tiene `overflow-x:auto` — y eso convierte
   al marco en contenedor de desplazamiento, así que el `sticky` se ata a un
   contenedor que no se desplaza en vertical y no se pega a nada. Al darle una
   separación desde arriba para librar la cabecera de la aplicación, lo que
   hizo fue bajarse 58 px y meterse DENTRO de la primera fila, tapando el
   nombre del primer cliente. Un encabezado pegajoso de verdad exige sacar la
   tabla del marco con desplazamiento lateral, y eso se lleva por delante el
   comportamiento en pantalla angosta. Se queda quieto y con el filo marcado. */
.app-tabla th{
  text-align:left; white-space:nowrap;
  font-size:var(--app-mini); font-weight:700; text-transform:uppercase;
  letter-spacing:.11em; color:var(--tenue);
  padding:0 12px 9px;
  box-shadow:inset 0 -1px 0 var(--filete);
}
.app-tabla td{ padding:var(--app-fila-y) 12px; vertical-align:top; line-height:1.4;
  box-shadow:inset 0 -1px 0 var(--filete2) }
.app-tabla tbody tr{ transition:background-color var(--t-sale) var(--sal) }
.app-tabla tbody tr:hover{ background:rgba(119,21,113,.045) }
.app-tabla tbody tr:last-child td{ box-shadow:none }
/* El nombre del cliente va en tinta, no en morado, aunque sea un enlace. Una
   columna de cincuenta nombres en el color de acento convierte el acento en el
   color de fondo de la pantalla y deja de señalar nada — que es justo lo que
   el sistema de marca quiere evitar racionándolo. Se comporta como enlace al
   tocarlo, que es cuando hace falta saberlo. */
.app-tabla .n{ font-weight:700; color:var(--tinta) }
.app-tabla td > a.n:hover,
.app-tabla td > a.n:focus-visible{ color:var(--acento);
  text-decoration:underline; text-underline-offset:3px }
.app-tabla .sub{ display:block; color:var(--tenue); font-size:var(--t-xs); margin-top:2px }
.app-dato{ white-space:nowrap; font-variant-numeric:tabular-nums }
.app-acc{ white-space:nowrap; text-align:right }

/* REPARTO DE ANCHO. Las columnas de dato fijo —fecha, medidor, insignia,
   acción— se encogen a su contenido con el truco de `width:1%`, y todo el
   sobrante queda para las dos columnas de texto. Sin esto los nombres se
   partían en dos líneas: «Ana Maria Restrepo / Cardona». Apareció el día que
   cambió la tipografía del sitio, y es exactamente el tipo de regresión que un
   cambio de fuente produce sin que nada falle. */
.app-tabla th.app-estrecha,
.app-tabla td.app-dato,
.app-tabla td.app-acc,
.app-tabla td.app-medidor,
.app-tabla td.app-insignia{ width:1%; white-space:nowrap }
.app-tabla td.app-medidor{ min-width:120px }
@media (min-width:900px){ .app-tabla td > a.n{ white-space:nowrap } }

/* La acción se insinúa y se afirma al pasar el cursor: leyendo, la lista queda
   limpia. Nunca baja de 0,55 de opacidad —por debajo deja de cumplir el
   contraste— y con el foco del teclado se afirma igual que con el ratón. */
.app-tabla tbody tr .app-acc .btn{ opacity:.62; transition:opacity var(--t-sale) var(--sal) }
.app-tabla tbody tr:hover .app-acc .btn,
.app-tabla tbody tr:focus-within .app-acc .btn{ opacity:1 }

/* LA FILA MUDA: la obligación que NO va a avisar. Es el hallazgo central del
   premortem hecho visible, y es un estado propio —«pausado»— que no cabía en
   la pastilla de situación: una póliza puede estar vigente Y silenciada a la
   vez. Se dice con tres cosas: lavado ámbar, barra lateral y el motivo escrito
   con su marca de silencio. Nota al margen, no error: el dato está bien, lo
   que falla es el aviso. */
/* El lavado de la fila es MÁS FLOJO que el de la nota de arriba, a propósito.
   Un tablero normal trae seis de cada quince filas silenciadas; con el ámbar
   de la nota, media tabla se tiñe y las pastillas de estado —que son lo que
   hay que leer primero— se pierden dentro de la mancha. La barra lateral de
   3 px es la que sostiene la señal; el fondo solo agrupa. */
.app-tabla tr[data-muda]{ background:rgba(138,90,30,.055) }
.app-tabla tr[data-muda]:hover{ background:rgba(138,90,30,.09) }
.app-tabla tr[data-muda] td:first-child{ box-shadow:inset 3px 0 0 var(--aviso), inset 0 -1px 0 var(--filete2) }
/* El motivo va en su propio renglón, con sangría francesa, para que un motivo
   de dos líneas no se meta debajo de su marca. En bloque y no en línea: puesto
   al lado del nombre de la obligación se lee como parte del nombre. */
.app-muda{
  display:block; margin-top:5px; padding-left:17px; text-indent:-17px;
  font-size:var(--t-xs); line-height:1.4; color:var(--aviso); font-weight:600;
}
.app-muda::before{ content:"\2298"; font-weight:400; margin-right:6px }
/* En la lista de clientes el motivo va seguido de un <br> que antes hacía
   falta —era una línea más— y ahora deja un renglón en blanco dentro de la
   celda. Se anula aquí y no en el marcado: el mismo <br> es correcto cuando lo
   que lo precede no es un motivo. */
.app-muda + br{ display:none }

/* ── 6 · LA FIRMA: el medidor de la cadena de avisos ────────────────────────
   Dónde está esta obligación dentro de la ventana que el motor vigila —30
   días, o 5 en la planilla mensual—. El relleno avanza hacia el vencimiento y
   las muescas son los hitos que faltan, de modo que la fila dice de un golpe
   dos cosas que la pastilla no dice: cuánto falta DENTRO de la ventana de
   aviso, y cuántos avisos quedan por delante. Fuera de la ventana no se pinta
   nada, y esa ausencia también informa.

   Medía 3 px y se leía como un rayón en el papel. A 6 px con la vía marcada se
   lee como un medidor, que es lo que es. */
.vig{
  position:relative; width:100%; min-width:100px; height:var(--app-vig-alto);
  background:var(--quieto-lav); box-shadow:inset 0 0 0 1px var(--filete2);
  border-radius:99px; margin-top:7px; overflow:hidden;
}
.vig__c{ position:absolute; inset:0 auto 0 0; border-radius:99px;
  background:var(--app-vig-color) }
.vig[data-estado="vigente"]{ --app-vig-color:var(--ok) }
.vig[data-estado="proxima"]{ --app-vig-color:var(--aviso) }
.vig[data-estado="critica"]{ --app-vig-color:var(--alerta) }
/* Lo vencido lleva trama, no solo rojo: es la única barra llena de punta a
   punta, y sin textura un rojo lleno y un ámbar lleno se distinguen sólo por
   el tono — que es justo lo que no puede pasar aquí.
   La trama es FINA y de dos rojos. La primera versión alternaba el rojo con su
   lavado y, repetida en las seis filas vencidas de un tablero, la columna
   entera se veía como un bastón de caramelo: la textura llamaba más la
   atención que el estado. Con dos valores del mismo rojo se lee como una
   superficie rayada, que es lo que se buscaba. */
.vig[data-estado="vencida"]{ --app-vig-color:var(--alerta) }
.vig[data-estado="vencida"] .vig__c{
  background:repeating-linear-gradient(-45deg,
    var(--alerta) 0 3px, rgba(94,17,112,.55) 3px 6px);
}
/* Las muescas de los hitos, pintadas por posición para no meter un elemento
   por hito en cada una de cincuenta filas. */
.vig__h{ position:absolute; inset:0; pointer-events:none }
.vig__h i{ position:absolute; top:0; bottom:0; width:1.5px;
  background:var(--papel2); opacity:.85 }
.vig-fuera{ display:inline-block; margin-top:6px; color:var(--tenue);
  font-size:15px; line-height:1; opacity:.6 }

/* ── 7 · Las pastillas de estado ────────────────────────────────────────────
   NUNCA SOLO COLOR. Cada estado lleva las tres cosas a la vez:

     · color     — para reconocerlo de un vistazo a metro y medio
     · FORMA     — un dibujo distinto por estado, para quien no distingue el
                   rojo del verde, que es uno de cada doce hombres
     · texto     — que es lo que se lee cuando hay duda

   Las formas se dibujan con CSS y no con caracteres. Un «▲» o un «⏸» depende
   de que la fuente lo tenga; si no, el navegador lo saca de otra familia y la
   pastilla queda con un glifo de otro tamaño y otro color. Un rombo hecho con
   un cuadrado girado siempre mide lo mismo.

   El escalón de intensidad también es forma: VENCIDA es la única pastilla
   rellena, así que es la única que se ve desde el otro lado del escritorio. */
.app-ins{
  display:inline-flex; align-items:center; gap:6px; white-space:nowrap;
  font-size:var(--app-mini); font-weight:700; text-transform:uppercase;
  letter-spacing:.06em; padding:4px 11px 4px 9px; border-radius:99px;
  background:var(--quieto-lav); color:var(--quieto);
  box-shadow:inset 0 0 0 1px var(--quieto-filo);
}
.app-ins::before{ content:""; width:8px; height:8px; flex:none }

/* Vencida · rombo relleno, pastilla sólida. El grado máximo. */
.app-ins--vencida,.app-ins--fecha_invalida{
  background:var(--alerta); color:#fff; box-shadow:none;
}
/* EN OSCURO SE INVIERTE LA PASTILLA SÓLIDA. El rojo de señal en oscuro es un
   salmón claro —tiene que serlo para leerse sobre el fondo—, y blanco sobre
   salmón da 1,9:1. Lo que se conserva es la IDEA (la única pastilla rellena,
   la que se ve desde lejos), no el color de su letra. */
@media (prefers-color-scheme:dark){
  :root:not([data-tema="claro"]) .app-ins--vencida,
  :root:not([data-tema="claro"]) .app-ins--fecha_invalida{ color:#2A1013 }
}
.app-ins--vencida::before,.app-ins--fecha_invalida::before{
  background:currentColor; transform:rotate(45deg);
}
/* Crítica · triángulo. Mismo rojo que vencida —es la misma familia de alarma,
   faltan siete días— pero hueca: se ve que es un paso antes. */
.app-ins--critica{
  background:var(--alerta-lav); color:var(--alerta);
  box-shadow:inset 0 0 0 1.5px var(--alerta-filo);
}
.app-ins--critica::before{
  width:0; height:0; background:none;
  border-left:5px solid transparent; border-right:5px solid transparent;
  border-bottom:9px solid currentColor;
}
/* Próxima · círculo hueco. Vence este mes: hay que verlo, no hay que correr. */
.app-ins--proxima{
  background:var(--aviso-lav); color:var(--aviso);
  box-shadow:inset 0 0 0 1px var(--aviso-filo);
}
.app-ins--proxima::before{ border-radius:50%; box-shadow:inset 0 0 0 2px currentColor }
/* Al día · círculo relleno. */
.app-ins--vigente,.app-ins--activa{
  background:var(--ok-lav); color:var(--ok); box-shadow:inset 0 0 0 1px var(--ok-filo);
}
.app-ins--vigente::before,.app-ins--activa::before{ border-radius:50%; background:currentColor }
/* Cumplida y renovada · un visto. Cerrado por las buenas: sin urgencia, así
   que van en el gris lila que no compite con nada. */
.app-ins--cumplida::before,.app-ins--renovada::before{
  width:5px; height:9px; margin-top:-2px;
  border-right:2px solid currentColor; border-bottom:2px solid currentColor;
  transform:rotate(42deg);
}
/* Anulada · aspa, y el texto tachado como ya estaba. */
.app-ins--anulada{ text-decoration:line-through }
.app-ins--anulada::before{
  background:
    linear-gradient(45deg,transparent 42%,currentColor 42% 58%,transparent 58%),
    linear-gradient(-45deg,transparent 42%,currentColor 42% 58%,transparent 58%);
}
/* Pausada · las dos barras de la pausa. No la emite hoy `insignia()` —el
   motivo de pausa viaja como texto en la fila muda— pero el estado existe en
   la base y la pastilla está lista para el día que se pinte. */
.app-ins--pausada::before{
  background:linear-gradient(90deg,currentColor 0 3px,transparent 3px 5px,currentColor 5px 8px);
}

/* ── 8 · Formularios ────────────────────────────────────────────────────────
   Quien digita treinta vencimientos al día pasa aquí la mitad de su jornada.
   Los campos son altos —44 px— porque a esa altura el cursor cae dentro sin
   apuntar, y el foco se ve desde lejos: filo morado más halo. */
.app-campos{ display:grid; gap:14px; margin-bottom:16px;
  grid-template-columns:repeat(auto-fit,minmax(214px,1fr)) }
.app-ancho{ grid-column:1/-1 }
.app-campo label{
  display:block; font-size:var(--app-mini); font-weight:700; text-transform:uppercase;
  letter-spacing:.1em; color:var(--tenue); margin-bottom:5px;
}
/* Los campos sueltos —el buscador de clientes, el motivo de la pausa— no van
   envueltos en `.app-campo`, así que se estilan también por etiqueta. Sin
   esto quedaban con el cuadro gris del navegador en medio de una interfaz de
   filos morados, que es la clase de detalle que delata que algo se terminó a
   medias. */
.app-campo input,.app-campo select,.app-campo textarea,
.app-filtros input,.app-acciones input[type="text"],.app-acciones input:not([type]){
  width:100%; min-height:44px; padding:10px 13px;
  background:var(--papel2); color:var(--tinta);
  border:1px solid var(--filete); border-radius:var(--r-s); font-size:var(--t-b);
  transition:border-color var(--t-sale) var(--sal), box-shadow var(--t-sale) var(--sal);
}
.app-campo textarea{ min-height:66px; line-height:1.5 }
.app-campo input:hover,.app-campo select:hover,.app-campo textarea:hover,
.app-filtros input:hover{ border-color:rgba(119,21,113,.38) }
.app-campo input:focus,.app-campo select:focus,.app-campo textarea:focus,
.app-filtros input:focus,.app-acciones input:focus{
  outline:none; border-color:var(--acento); box-shadow:0 0 0 3px rgba(119,21,113,.15);
}
.app-campo .pista{ display:block; margin-top:5px; font-size:var(--t-xs);
  color:var(--tenue); line-height:1.45 }
/* El error va PEGADO al campo y en tinta de alerta, y el campo se marca. Con
   el mensaje solo arriba hay que buscar cuál de doce campos falló. */
.app-error{ color:var(--alerta); font-weight:600 }
.app-campo input[aria-invalid],.app-campo select[aria-invalid],.app-campo textarea[aria-invalid]{
  border-color:var(--alerta); box-shadow:0 0 0 3px rgba(179,38,30,.14);
}
.app-acciones{ display:flex; gap:10px; align-items:center; flex-wrap:wrap }

/* ── 9 · Filtros, paginación y vacíos ───────────────────────────────────── */
.app-filtros{ display:flex; gap:7px; flex-wrap:wrap; align-items:center; margin-bottom:14px }
.app-filtros a{
  display:inline-flex; align-items:center; min-height:36px; padding:0 15px;
  border-radius:999px; font-size:var(--t-xs); font-weight:600;
  text-transform:uppercase; letter-spacing:.08em;
  color:var(--suave); background:var(--papel2);
  box-shadow:var(--filo-doble);
  transition:color var(--t-sale) var(--sal), box-shadow var(--t-sale) var(--sal),
             background-color var(--t-sale) var(--sal);
}
.app-filtros a:hover{ color:var(--acento); box-shadow:inset 0 0 0 1px rgba(119,21,113,.4) }
/* El filtro activo lleva el degradado de marca: es el mismo gesto que el botón
   primario, y así «dónde estoy» y «qué hace esto» hablan el mismo idioma. */
.app-filtros a[aria-current]{
  background-image:var(--deg-morado); color:#fff;
  box-shadow:0 8px 18px -12px rgba(106,18,112,.8);
}
.app-filtros input{ max-width:340px }
/* El botón de buscar mide lo mismo que su campo. Un control de 40 px al lado
   de uno de 44 se lee como una pieza mal encajada, y es de lo primero que ve
   quien juzga por la captura. */
.app-filtros .btn{ min-height:44px }

.app-paginas{ display:flex; gap:14px; justify-content:center; align-items:center;
  margin-top:18px; font-size:var(--t-s); color:var(--tenue) }
.app-paginas a{ display:inline-flex; align-items:center; min-height:40px; padding:0 14px;
  border-radius:999px; font-weight:600; color:var(--acento);
  box-shadow:var(--filo-doble) }
.app-paginas a:hover{ box-shadow:inset 0 0 0 1px rgba(119,21,113,.4) }

.app-vacio{ text-align:center; padding:clamp(28px,4vw,46px) 20px; color:var(--suave) }
.app-vacio h2{ font-size:var(--t-l); font-weight:700; color:var(--tinta); margin:0 0 7px }
.app-vacio p{ margin:0 auto 14px; max-width:48ch; font-size:var(--t-s) }

/* ── 10 · La pantalla de ingreso ────────────────────────────────────────────
   El único sitio donde la aplicación se permite atmósfera. Es lo primero que
   se ve al llegar desde el sitio y tiene que sentirse continua con él: las dos
   luces difuminadas son los dos colores de marca, exactamente el recurso que
   usa la portada detrás de su héroe. Dentro, en cambio, no hay ni una.

   Sin animación de entrada: la caja aparece puesta. Una caja que sube al
   cargar se ve bien la primera vez y estorba las cuatrocientas siguientes. */
.app-entrada{ min-height:100vh; display:grid; place-items:center; padding:24px;
  position:relative; overflow:hidden; background:var(--fondo) }
.app-entrada::before,.app-entrada::after{
  content:""; position:absolute; border-radius:50%; pointer-events:none;
  filter:blur(80px); opacity:.24;
}
.app-entrada::before{ width:min(58vw,540px); aspect-ratio:1; left:-14vw; top:-18vh;
  background:var(--acento) }
.app-entrada::after{ width:min(46vw,430px); aspect-ratio:1; right:-12vw; bottom:-18vh;
  background:var(--apoyo) }
.app-caja{
  position:relative; width:100%; max-width:410px;
  background-image:var(--cristal-tarjeta); border-radius:var(--r-g);
  box-shadow:var(--filo-doble), var(--s4); padding:clamp(24px,4vw,34px);
}
.app-caja h1{ font-size:var(--t-l); font-weight:700; letter-spacing:-.02em; margin:0 0 6px }
.app-caja > p{ margin:0 0 20px; color:var(--suave); font-size:var(--t-s); line-height:1.5 }
.app-caja .app-marca{ display:flex; align-items:center; gap:11px; margin-bottom:18px }
.app-caja .app-marca img{ height:32px }
.app-caja .app-campo{ margin-bottom:14px }
/* UN ENLACE QUE PARECE TEXTO NO ES UN ENLACE. «entre con un codigo al correo»
   es la salida de quien olvidó la contraseña, y estaba pintado del mismo gris
   que la frase que lo rodea: el párrafo lleva `color` en línea y el enlace lo
   heredaba, porque en la base `a{color:inherit}`. Va en morado y subrayado —
   subrayado además del color, que es lo que lo hace visible para quien no
   distingue el morado del gris. */
.app-caja p a:not(.btn){
  color:var(--acento); font-weight:700;
  text-decoration:underline; text-underline-offset:3px;
  padding-block:4px;   /* área sensible, sin separar los renglones */
}
/* Un enlace que es TODO el párrafo no está «dentro de una frase» y no le
   corresponde la excepción de WCAG 2.5.8: es un botón escrito como enlace —el
   «Volver al ingreso con contraseña» de la pantalla del código— y medía
   204×16 px. Ahí sí se pide el objetivo completo. */
.app-caja p > a:only-child{
  display:inline-flex; align-items:center; min-height:44px; padding-block:0;
}
.app-volver{
  display:inline-flex; align-items:center; gap:7px; margin-top:18px; min-height:44px;
  font-size:var(--t-xs); font-weight:600; color:var(--tenue);
  transition:color var(--t-sale) var(--sal);
}
.app-volver:hover{ color:var(--acento) }
/* El código de un solo uso. Espaciado como se dicta por teléfono, que es como
   llega: de seis en seis. */
.app-codigo{ font-size:28px; font-weight:700; letter-spacing:.3em; text-align:center }
.app-codigo-visible{ font-size:21px; font-weight:700; letter-spacing:.2em; color:var(--acento) }

/* ── 11 · Movimiento ────────────────────────────────────────────────────────
   `.app-rev` la escribe `src/index.js` en una treintena de bloques y ERA una
   entrada escalonada. Se deja escrita en el marcado y se deja SIN animación a
   propósito, en vez de borrarla de treinta rutas: un tablero que se recompone
   cada vez que se guarda un registro no es elegante, es lento. La persona ya
   sabe que pulsó Guardar; lo que quiere es ver la fila, no verla llegar.

   Aquí NADA se mueve solo. Lo único que se mueve responde al ratón o al
   teclado, dura menos de 250 ms y se apaga entero con movimiento reducido. */
.app-rev{ animation:none }

/* ── 11b · Usuarios y permisos ──────────────────────────────────────────────
   Un usuario de CONSULTA no ve los formularios que modifican ni los enlaces de
   «nuevo». Esto es solo la cara: la regla de verdad la aplica el servidor, que
   rechaza la petición aunque alguien la envíe a mano. Lo que se oculta aquí es
   para no ofrecerle un botón que le va a responder «sin permiso». Su propia
   contraseña sí la puede cambiar. */
[data-rol="consulta"] form[method="post"]:not([action="/clave"]),
[data-rol="consulta"] a[href$="/nuevo"],
[data-rol="consulta"] a[href$="/nueva"]{ display:none }

/* La tarjeta de Usuarios en Operación: una fila, texto y botón. */
.app-usuarios-resumen{ display:flex; align-items:center; justify-content:space-between;
  gap:16px; flex-wrap:wrap }
.app-usuarios-resumen p{ margin:4px 0 0 }

/* La columna de la contraseña dice su ESTADO, nunca la contraseña: el sistema
   no la tiene, guarda una huella de ella. */
.app-clave{ font-size:var(--t-xs); font-weight:600 }
.app-clave--bien{ color:var(--ok) }
.app-clave--ojo { color:var(--aviso) }
.app-clave--mal { color:var(--alerta) }
.app-tu{ display:inline-block; margin-left:6px; padding:1px 8px; border-radius:99px;
  font-size:var(--app-mini); font-weight:700; letter-spacing:.06em; text-transform:uppercase;
  background:var(--apoyo-lav); color:var(--apoyo-os); box-shadow:inset 0 0 0 1px var(--apoyo-filo) }
/* Inactivo: la fila entera en segundo plano. Sigue en la lista porque un
   usuario no se borra; se desactiva. */
.app-tabla tr.app-inactivo td{ color:var(--tenue) }
.app-tabla tr.app-inactivo .n{ color:var(--tenue) }

/* Parámetros de la calculadora de la web. Los grupos del formulario son
   fieldset —el lector de pantalla anuncia «Independientes, Salud»—, sin el
   marco gris del navegador. En la revisión, lo que cambia se ve de un golpe:
   la misma marca lateral que las filas mudas del tablero. */
.app-grupo{ border:0; margin:0 0 6px; padding:0; min-width:0 }
.app-grupo legend{ padding:0; margin:0 0 4px; font-size:var(--t-s); font-weight:700; color:var(--tinta) }
.app-grupo > .pista{ margin:0 0 10px; font-size:var(--t-xs); color:var(--tenue); line-height:1.45 }
.app-tabla tr[data-cambio]{ background:rgba(138,90,30,.055) }
.app-tabla tr[data-cambio] td:first-child{ box-shadow:inset 3px 0 0 var(--aviso), inset 0 -1px 0 var(--filete2) }

/* ── 12 · Objetivos táctiles ────────────────────────────────────────────────
   44 px CUANDO HAY DEDO, no cuando la pantalla es angosta. El criterio es el
   del puntero y no el del ancho: una tableta de 1024 px se toca con el pulgar
   igual que un teléfono, y un portátil con la ventana a 700 px se apunta con
   ratón. Con ratón, un botón de fila de 34 px ya es un blanco cómodo y cada
   píxel de más son renglones menos en pantalla; con dedo no se negocia.

   La excepción de WCAG 2.5.8 para enlaces DENTRO de una frase se respeta: los
   sueltos crecen, los embebidos en un párrafo no. */
@media (pointer:coarse){
  .app-nav a,
  .app-filtros a,
  .app-paginas a,
  .btn,
  .app-tabla .app-acc .btn{ min-height:44px }
  .btn--sm{ padding-inline:16px }
  .app-tabla .app-acc .btn{ opacity:1 }   /* sin hover, la acción se ve siempre */
  /* El nombre del cliente dentro de la celda medía 82×37 px. Con el dedo,
     tocar el nombre tiene que abrir la ficha: el enlace ocupa la celda. */
  .app-tabla td > a.n{ display:block; padding:4px 0; min-height:44px }
  .app-quien a,.app-pie a,.app-volver{ min-height:44px }
}

/* ── 13 · Pantalla angosta ──────────────────────────────────────────────── */
@media (max-width:760px){
  .app-cab__fila{ flex-wrap:wrap; gap:8px; padding-block:6px }
  .app-quien{ order:3; width:100%; justify-content:flex-start }
  .app-nav{ order:2; margin-left:0; width:100%; flex-wrap:nowrap;
    overflow-x:auto; -webkit-overflow-scrolling:touch; padding-bottom:2px }
  .app-nav a{ flex-shrink:0 }
  .app-tabla{ font-size:14px }
  .app-tabla th,.app-tabla td{ padding-inline:9px }
  .app-tabla th{ position:static }   /* pegada bajo una cabecera que crece: estorba */
  .app-cifra__v{ font-size:27px }
  .app-titulo{ align-items:flex-start }
}

/* ── 14 · Impresión de una pantalla ─────────────────────────────────────────
   ESTO NO TOCA EL COMPROBANTE. El comprobante térmico es un documento aparte,
   con su propio `@page` de 48 y 72 mm, y no carga ni esta hoja ni el sistema:
   lo que hay aquí no puede alcanzarlo. Esto es para cuando alguien imprime el
   tablero o una ficha para llevársela a una reunión, que hasta hoy salía con
   la navegación, el pie y los botones en el papel. */
@media print{
  .app-cab,.app-pie,.app-filtros,.app-acc,.app-paginas,.salto-contenido,
  .app-acciones,.btn{ display:none!important }
  .app-cuerpo{ display:block }
  .app-lienzo{ max-width:none; padding:0 }
  .app-tarjeta,.app-cifra{ background-image:none; background:#fff;
    box-shadow:none; border:1px solid #BBB; break-inside:avoid }
  .app-tabla-marco{ overflow:visible; margin:0; padding:0 }
  .app-tabla tr{ break-inside:avoid }
  .app-ins{ box-shadow:none; border:1px solid #666 }
}
