/* =====================================================================
   LA TIPOGRAFÍA, SERVIDA DESDE AQUÍ
   =====================================================================
   Antes venía de Google Fonts. Dos razones para traerla:

   1. El sistema se usa desde la red de la oficina. Si se cae internet
      —que pasa— la letra se desmoronaba a media sesión y todo saltaba
      a la fuente del sistema, con otros anchos y otros renglones.
   2. Es una petición menos a un servidor ajeno en cada carga, y una
      cosa menos que Google sepa de quién entra al sistema.

   Es la fuente VARIABLE de Inter: un solo archivo cubre del peso 400 al
   700, así que pesa 136 KB en total en vez de los ~530 KB que serían
   cuatro archivos sueltos. Por eso el `font-weight` del @font-face es un
   rango y no un número.
   ------------------------------------------------------------------ */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('/fonts/inter-latin.woff2') format('woff2');
  /* El español cabe entero aquí; el resto solo se descarga si hace falta. */
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
                 U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
                 U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('/fonts/inter-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
                 U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF,
                 U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* ==========================================================
   style.css — SEISA Eléctrica
   Hoja de estilos única, pensada primero para celular (la
   mayoría va a abrir esto desde el navegador del teléfono en
   la obra) y que también se ve bien en escritorio.
   ========================================================== */

/* ==========================================================
   PALETAS — claro y oscuro
   ==========================================================
   Todo el color del sistema pasa por estas variables. Una regla que
   escriba un #hex directo se rompe al cambiar de tema, así que si hace
   falta un color nuevo, se agrega aquí y se usa con var().

   El tema lo elige cada persona en Sistema > Configuración y se guarda
   en `responsables.tema`. `header.php` lo escribe en el <html> ANTES de
   pintar nada: si se aplicara con JavaScript, la página cargaría en
   blanco un instante y luego se oscurecería — el parpadeo que se ve
   especialmente mal justo en modo oscuro.

   `auto` no fija nada y deja que mande el sistema operativo, vía
   prefers-color-scheme. Es lo que la mayoría espera hoy.
   ========================================================== */
:root {
  --azul-oscuro: #1b2733;   /* header / menú */
  --azul-medio:  #29394a;
  /* EL acento del sistema. Es el rojo de la marca de SEISA. Manda en los
     botones primarios, el activo del menú y el foco del teclado.
     El rojo de ERROR se distingue por TRATAMIENTO, no por tono: los
     avisos de error llevan fondo tenue y una barra a la izquierda, cosa
     que ningún botón primario tiene. Así se resuelve el choque que
     estaba anotado como pendiente desde agosto. */
  --acento:      #df231d;
  --acento-tx:   #ffffff;              /* texto SOBRE el rojo sólido */
  --acento-legible: #a81b14;           /* el acento COMO texto, sobre fondo claro */
  --acento-suave: rgba(223,35,29,.10);
  --amarillo:    #f4b400;   /* segundo acento: la cifra que manda en una pantalla */
  --enlace:      #29394a;   /* color de los enlaces del contenido */
  --fondo:       #f5f6f8;
  --tarjeta:     #ffffff;
  --texto:       #1f2733;
  --texto-suave: #6b7480;
  --borde:       #dde1e6;
  --verde:       #2e7d32;
  --verde-bg:    #e8f5e9;
  --rojo:        #c62828;
  --rojo-solido: #c62828;   /* fondo del botón de peligro: oscuro en AMBOS temas */
  --rojo-bg:     #fdecea;
  --gris-bg:     #eceff1;
  --barra-bg:    #e3e7ea;   /* canal de la barra de porcentaje */
  --grupo-bg:    #dde3e9;   /* fila de grupo de las tablas agrupables */
  --linea-fuerte:#b9c0c7;   /* separadores marcados */
  /* El radio era 2px, "casi cuadrado", buscando verse utilitario. En la
     práctica es lo que más hacía ver el sistema anticuado: las esquinas
     duras leen como panel de administración de hace quince años. 6 px en
     los controles y 10 en las tarjetas se ve actual sin volverse una app
     de celular. (Decisión revisada el 09/09/2026 a petición del dueño.) */
  --radio:       6px;
  --radio-tarjeta: 10px;
  /* Elevación en vez de borde duro: una tarjeta que flota un milímetro
     se distingue del fondo sin necesidad de encerrarla en una línea. */
  --sombra:      0 1px 2px rgba(16,24,40,.05), 0 2px 6px rgba(16,24,40,.07);
  --sombra-alta: 0 2px 4px rgba(16,24,40,.06), 0 8px 20px rgba(16,24,40,.10);
  --borde-suave: rgba(16,24,40,.07);
  --fila-alt:    #f7f8fa; /* zebra de tablas */
  /* Tres tintes, tres significados. El porqué está abajo, en ZONAS
     CLICABLES: "estás en este renglón", "este renglón va a algún lado"
     y "esta celda es la que se va a abrir". */
  --fila-lectura: #f1f3f6;
  --fila-hover:  #e6eefa;
  --zona-bg:     #d0e2f8;
  --zona-acento: var(--acento);        /* la barrita del destino */
  --zona-borde:  rgba(41, 57, 74, .16);
  --th-bg:       #e4e8ec;

  /* Semáforo de constancias y badges de estado. */
  --sem-verde-bg: #d9ead3;  --sem-verde-tx: #274e13;
  --sem-ambar-bg: #fff2cc;  --sem-ambar-tx: #7f6000;
  --sem-rojo-bg:  #f4c7c3;  --sem-rojo-tx:  #8b1a10;
  --sem-gris-bg:  #f1f1f1;

  /* Campos calculados (el importe de caja chica) y avisos. */
  --calculado-bg: #f4f7fb;
  --info-bg:      #eaf2fb;
  --info-borde:   #b9d3ee;
  --info-tx:      #1c3c5e;

  /* Entradas de formulario. */
  --campo-bg:     #ffffff;

  /* Tirador para cambiar el ancho de una columna. */
  --tirador:       #c3cad2;   /* al pasar el mouse por el encabezado */
  --tirador-activo:#f4b400;   /* mientras se arrastra */
}

/* ---------- Tema oscuro ----------
   No es "invertir" el tema claro: los fondos son grises azulados —no
   negro puro, que cansa la vista y hace que los bordes desaparezcan— y
   los colores del semáforo se rehacen con fondos apagados y texto
   claro. Un #d9ead3 sobre gris oscuro se vería como un parche que
   brilla, y el semáforo es justo lo que hay que poder leer de un
   vistazo. */
html[data-tema="oscuro"] {
  --acento:      #ff5a52;
  --acento-tx:   #1a0f0e;              /* texto SOBRE el rojo sólido */
  --acento-legible: #ff8a84;           /* el acento COMO texto, sobre fondo oscuro */
  --acento-suave: rgba(255,90,82,.14);
  --sombra:      0 1px 2px rgba(0,0,0,.35), 0 2px 8px rgba(0,0,0,.28);
  --sombra-alta: 0 2px 6px rgba(0,0,0,.40), 0 10px 26px rgba(0,0,0,.38);
  --borde-suave: rgba(255,255,255,.06);
  --azul-oscuro: #10161d;
  --azul-medio:  #1c2836;
  --amarillo:    #f0b429;
  --enlace:      #7fb3e8;   /* claro: sobre fondo oscuro debe leerse */
  --fondo:       #151a20;
  --tarjeta:     #1c222a;
  --texto:       #e3e7ec;
  --texto-suave: #97a1ad;
  --borde:       #2e3742;
  --verde:       #7fd18a;
  --verde-bg:    #1b3222;
  --rojo:        #ff8a80;
  --rojo-solido: #a52520;
  --rojo-bg:     #3a1f1e;
  --gris-bg:     #272e37;
  --barra-bg:    #2b333d;
  --grupo-bg:    #2d3742;
  --linea-fuerte:#3d4854;
  --fila-alt:    #1f262e;
  --fila-lectura: #222a33;
  --fila-hover:  #2b3949;
  --zona-bg:     #35485d;
  --zona-acento: var(--acento-legible); /* el rojo de marca sobre fondo oscuro da 3.06:1; el legible, 4.12 */
  --zona-borde:  rgba(255, 255, 255, .16);
  --th-bg:       #232b35;

  --sem-verde-bg: #1e3a26;  --sem-verde-tx: #9ad9a5;
  --sem-ambar-bg: #3d3218;  --sem-ambar-tx: #f0c96a;
  --sem-rojo-bg:  #43201e;  --sem-rojo-tx:  #ffa39e;
  --sem-gris-bg:  #262c34;

  --calculado-bg: #1f2831;
  --info-bg:      #172633;
  --info-borde:   #2c4a63;
  --info-tx:      #a8c8e4;

  --campo-bg:     #232b34;

  --tirador:       #495563;
  --tirador-activo:#f0b429;
}

/* Con el tema en "auto" no se escribe data-tema y manda el sistema
   operativo. El :not() evita que esto pise una elección explícita. */
@media (prefers-color-scheme: dark) {
  html[data-tema="auto"] {
  --acento:      #ff5a52;
  --acento-tx:   #1a0f0e;              /* texto SOBRE el rojo sólido */
  --acento-legible: #ff8a84;           /* el acento COMO texto, sobre fondo oscuro */
  --acento-suave: rgba(255,90,82,.14);
  --sombra:      0 1px 2px rgba(0,0,0,.35), 0 2px 8px rgba(0,0,0,.28);
  --sombra-alta: 0 2px 6px rgba(0,0,0,.40), 0 10px 26px rgba(0,0,0,.38);
  --borde-suave: rgba(255,255,255,.06);
    --azul-oscuro: #10161d;
    --azul-medio:  #1c2836;
    --amarillo:    #f0b429;
    --enlace:      #7fb3e8;
    --fondo:       #151a20;
    --tarjeta:     #1c222a;
    --texto:       #e3e7ec;
    --texto-suave: #97a1ad;
    --borde:       #2e3742;
    --verde:       #7fd18a;
    --verde-bg:    #1b3222;
    --rojo:        #ff8a80;
    --rojo-solido: #a52520;
    --rojo-bg:     #3a1f1e;
    --gris-bg:     #272e37;
    --barra-bg:    #2b333d;
    --grupo-bg:    #2d3742;
    --linea-fuerte:#3d4854;
    --fila-alt:    #1f262e;
    --fila-lectura: #222a33;
    --fila-hover:  #2b3949;
    --zona-bg:     #35485d;
    --zona-acento: var(--acento-legible);
    --zona-borde:  rgba(255, 255, 255, .16);
    --th-bg:       #232b35;

    --sem-verde-bg: #1e3a26;  --sem-verde-tx: #9ad9a5;
    --sem-ambar-bg: #3d3218;  --sem-ambar-tx: #f0c96a;
    --sem-rojo-bg:  #43201e;  --sem-rojo-tx:  #ffa39e;
    --sem-gris-bg:  #262c34;

    --calculado-bg: #1f2831;
    --info-bg:      #172633;
    --info-borde:   #2c4a63;
    --info-tx:      #a8c8e4;

    --campo-bg:     #232b34;

    --tirador:       #495563;
    --tirador-activo:#f0b429;
  }
}

* { box-sizing: border-box; }

body {
  margin: 0;
  /* Inter: dibujada para interfaces, legible a tamaño chico y con cifras
     tabulares (los números de una columna quedan alineados aunque
     cambien de dígito). Si no carga —sin internet— cae en la fuente del
     sistema y nada se rompe. */
  font-family: "Inter", -apple-system, "Segoe UI", Roboto, Arial, sans-serif;
  font-feature-settings: "cv05" 1, "tnum" 1;
  -webkit-font-smoothing: antialiased;
  background: var(--fondo);
  color: var(--texto);
  font-size: 14px;
  line-height: 1.35;
}

a { color: var(--enlace); }

/* ---------- Layout general con sidebar ---------- */
.app { display: flex; align-items: stretch; min-height: 100vh; }
.app-cuerpo { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.app-cuerpo .contenido { flex: 1; }

/* ---------- Sidebar lateral ---------- */
.sidebar {
  background: var(--azul-oscuro);
  /* 200px alcanzaban con la fuente del sistema; Inter es un poco más
     ancha y partía "Solicitudes de material" en dos renglones. */
  width: 216px;
  flex: none;
  display: flex;
  flex-direction: column;
  position: sticky;              /* el menú no se va al hacer scroll */
  top: 0;
  height: 100vh;
  overflow-y: auto;
}

.sidebar-nav { padding: 0.5rem 0 1rem; flex: 1; }


/* La regla vieja era `display: none`, que no se puede interpolar: el
   grupo desaparecía de golpe. Ahora colapsa con altura animada — ver
   el bloque "Menú lateral — versión con movimiento" más abajo. */

.sidebar-link {
  display: block;
  color: #dce3ea;
  text-decoration: none;
  font-size: 0.85rem;
  padding: 0.35rem 0.9rem 0.35rem 1.4rem;
  border-left: 3px solid transparent;
}
/* Los links sin grupo (Inicio) no llevan la sangría de los hijos. */
.sidebar-nav > .sidebar-link:first-child { padding-left: 0.9rem; }
.sidebar-link:hover { background: var(--azul-medio); color: #fff; }
.sidebar-link.activo {
  background: var(--azul-medio);
  color: var(--amarillo);
  border-left-color: var(--amarillo);
  font-weight: 600;
}

/* Pestañas compartidas (Facturas / Estimaciones, etc.) — mismo
   contraste fijo en claro y oscuro, no dependen de --azul-oscuro
   (que en oscuro es casi negro y se vuelve ilegible). */
.pestanas { display: flex; gap: 0.3rem; background: var(--gris-bg); border-radius: var(--radio); margin-bottom: 1rem; padding: 0.35rem; }
.pestana { display: inline-block; padding: 0.35rem 0.8rem; border-radius: var(--radio); color: var(--texto-suave); text-decoration: none; font-weight: 400; }
.pestana:hover { background: var(--azul-medio); color: #fff; }
.pestana.activa { background: var(--azul-medio); color: var(--amarillo); font-weight: 700; }

.sidebar-fondo { display: none; }

/* En celular: el sidebar entra desde la izquierda sobre el contenido */
@media (max-width: 900px) {
  .app { display: block; }
  .sidebar {
    position: fixed;
    top: 0;
    left: 0;
    z-index: 40;
    height: 100vh;
    transform: translateX(-100%);
    transition: transform 0.18s ease-out;
  }
  body.sidebar-abierto .sidebar { transform: translateX(0); }
  .sidebar-fondo {
    display: block;
    position: fixed;
    inset: 0;
    z-index: 30;
    background: rgba(0, 0, 0, 0.45);
  }
  .sidebar-fondo[hidden] { display: none; }
}

/* ---------- Contenido general ---------- */
.contenido {
  /* Sin tope de ancho: las tablas densas usan toda la pantalla. */
  max-width: none;
  margin: 0;
  padding: 0.9rem 1rem 2.5rem;
}

h1 { font-size: 1.15rem; margin: 0 0 0.15rem; font-weight: 700; }
h2 { font-size: 0.98rem; margin: 1.1rem 0 0.4rem; font-weight: 700; }
.subtitulo { color: var(--texto-suave); margin: 0 0 0.8rem; font-size: 0.82rem; }

.piepagina {
  text-align: left;
  color: var(--texto-suave);
  font-size: 0.72rem;
  padding: 0.6rem 1rem 1rem;
}

/* ---------- Tarjetas / cajas ---------- */
.tarjeta {
  background: var(--tarjeta);
  border: 1px solid var(--borde-suave);
  border-radius: var(--radio-tarjeta);
  box-shadow: var(--sombra);
  padding: 0.85rem 1rem;
  margin-bottom: 0.8rem;
}

/* ---------- Formularios ---------- */
form.form-simple {
  background: var(--tarjeta);
  border: 1px solid var(--borde);
  border-radius: var(--radio);
  padding: 1.25rem;
  max-width: 480px;
}
label {
  display: block;
  font-weight: 600;
  font-size: 0.78rem;
  margin: 0.6rem 0 0.2rem;
}
label:first-child { margin-top: 0; }
input[type="text"],
input[type="password"],
input[type="number"],
input[type="search"],
input[type="date"],
select,
textarea {
  width: 100%;
  padding: 0.3rem 0.45rem;
  border: 1px solid var(--borde);
  border-radius: var(--radio);
  font-size: 0.82rem;
  /* Los controles de formulario NO heredan la tipografía: el navegador
     les pone la suya —Arial en Windows— y hay que pedírsela. Sin esta
     línea, TODO lo que se teclea en el sistema salía en otra letra que
     el resto de la pantalla. Ya estaba parchado suelto en
     `.tabla-buscar`; aquí vale para todos.

     Que quede claro que esto NO es lo que cortaba el "Buscar por código
     o descripción…" de Insumos: medido, ese texto pide 197.6 px en Arial
     y 213.2 en Inter, o sea que la letra buena es la MÁS ancha. Lo que
     lo cortaba era el ancho de la caja, y eso se arregla abajo. */
  font-family: inherit;
  background: var(--campo-bg);
  color: var(--texto);
}
textarea { resize: vertical; }
.ayuda-campo { color: var(--texto-suave); font-size: 0.8rem; margin-top: 0.2rem; }

/* ---------- Botones ---------- */
.btn {
  display: inline-block;
  padding: 0.38rem 0.8rem;
  border-radius: var(--radio);
  border: 1px solid transparent;
  font-size: 0.82rem;
  font-weight: 600;
  cursor: pointer;
  text-decoration: none;
  text-align: center;
}
.btn-primario { background: var(--azul-oscuro); color: #fff; }
.btn-primario:hover { background: var(--azul-medio); }
.btn-acento { background: var(--amarillo); color: var(--azul-oscuro); }
/* El texto va con --texto y no con --azul-oscuro: en tema oscuro ese
   azul es casi negro y sobre la tarjeta oscura daba 1.14:1 de
   contraste — el botón se volvía invisible. */
.btn-secundario { background: var(--tarjeta); color: var(--texto); border-color: var(--borde); }
.btn-peligro { background: var(--rojo-solido); color: #fff; }
.btn-chico { padding: 0.35rem 0.7rem; font-size: 0.82rem; }

/* ---------- Tablas densas (estilo ERP / Opus) ----------
   Renglones apretados, rayado de zebra, lineas verticales entre
   columnas y encabezado que se queda pegado al hacer scroll. La
   idea es poder leer 30-40 renglones sin scrollear. */
.tabla-wrap {
  overflow: auto;
  max-height: calc(100vh - 190px);   /* el scroll vive en la tabla */
  border: 1px solid var(--borde);
  background: var(--tarjeta);
}
table {
  width: 100%;
  border-collapse: separate;         /* separate: necesario para th pegajoso */
  border-spacing: 0;
  background: var(--tarjeta);
}
th, td {
  text-align: left;
  /* 0.22rem apretaba los renglones al punto de que la tabla se leía como
     un bloque. 0.32 da aire sin perder la densidad que el oficio pide. */
  padding: 0.32rem 0.55rem;
  border-bottom: 1px solid var(--borde);
  /* Sin línea vertical entre columnas. Una tabla con reja completa se lee
     como hoja de cálculo; sin ella, la alineación y el renglón hacen el
     trabajo y se lee como producto. (Además el #eceff1 que había era un
     color fijo: en tema oscuro no correspondía.) */
  font-size: 0.8rem;
  white-space: nowrap;
  vertical-align: top;
}

th {
  position: sticky;
  top: 0;
  z-index: 2;
  background: var(--th-bg);
  font-size: 0.68rem;
  /* Era 700 y leía como una franja gris pesada encima de cada tabla.
     600 con más espaciado entre letras se lee igual de bien y pesa menos. */
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  /* El encabezado de tabla: era #4a545e fijo y sobre el fondo oscuro
     daba 1.85:1. Va con la variable, que ya distingue los dos temas. */
  color: var(--texto-suave);
  border-bottom: 1px solid var(--borde);
  padding: 0.3rem 0.45rem;
}
/* Zebra: en tablas interactivas la pone el JS al repintar (las filas
   se reordenan y se insertan filas de grupo, asi que nth-child no
   sirve). En las demas, nth-child alcanza. */
tr.fila-alt td { background: var(--fila-alt); }
table:not(.tabla-interactiva) tbody tr:nth-child(even) td { background: var(--fila-alt); }
/* El renglón donde está el ratón, en CUALQUIER tabla: un tinte tenue que
   solo sirve para no perder la línea en una tabla de catorce columnas.
   El tinte fuerte (`--fila-hover`) se lo gana el renglón que sí lleva a
   algún lado; ver ZONAS CLICABLES más abajo. Antes los dos eran el
   mismo color, así que iluminarse no significaba nada. */
tbody tr:hover td { background: var(--fila-lectura); }
tr.fila-grupo:hover td { background: inherit; }

/* Columnas de numeros: alineadas a la derecha y monoespaciadas, para
   poder comparar cantidades de un vistazo. */
td.num, th.num { text-align: right; font-variant-numeric: tabular-nums; }
td.num { font-family: "Consolas", "SF Mono", monospace; font-size: 0.78rem; }

/* Celda que puede ser larga (descripciones, notas): se deja cortar. */
td.ancha { white-space: normal; min-width: 200px; }
td.tenue, .tenue { color: var(--texto-suave); }

/* ---------- Etiquetas de estatus ---------- */
.badge {
  display: inline-block;
  padding: 0.02rem 0.35rem;
  border-radius: 2px;
  font-size: 0.68rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.02em;
}
.badge-pendiente        { background: var(--gris-bg); color: var(--texto); }
.badge-pendiente_compra { background: var(--sem-ambar-bg); color: var(--sem-ambar-tx); }
.badge-atendida         { background: var(--verde-bg); color: var(--verde); }
.badge-cancelada        { background: var(--rojo-bg); color: var(--rojo); }
.badge-activo           { background: var(--verde-bg); color: var(--verde); }
.badge-inactivo         { background: var(--rojo-bg); color: var(--rojo); }
.badge-cotizando        { background: var(--gris-bg); color: var(--texto); }
.badge-comparada        { background: var(--sem-ambar-bg); color: var(--sem-ambar-tx); }
.badge-ordenada         { background: var(--sem-ambar-bg); color: var(--sem-ambar-tx); }
.badge-cerrada           { background: var(--verde-bg); color: var(--verde); }
.badge-emitida          { background: var(--sem-ambar-bg); color: var(--sem-ambar-tx); }
.badge-recibida         { background: var(--verde-bg); color: var(--verde); }
.badge-descartada       { background: var(--rojo-bg); color: var(--rojo); }

/* ---------- Mensajes ---------- */
.alerta {
  padding: 0.75rem 1rem;
  border-radius: var(--radio);
  margin-bottom: 1rem;
  font-size: 0.92rem;
}
.alerta-error  { background: var(--rojo-bg); color: var(--rojo); }
.alerta-exito  { background: var(--verde-bg); color: var(--verde); }

/* ---------- Login ---------- */
.login-wrap {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1rem;
}
.login-caja { width: 100%; max-width: 360px; text-align: center; }
/* Recuadro blanco igual que el del sidebar: el logo es navy sobre
   transparente y en tema oscuro, sin el recuadro, solo se veía la
   estrella roja. */
.login-marca {
  display: inline-block;
  background: #fff;
  padding: 0.7rem 1.1rem;
  border-radius: var(--radio);
  margin-bottom: 1.25rem;
}
.login-marca img { height: 34px; width: auto; }

/* ---------- Dashboard de obra ---------- */
.stat-grid {
  display: grid;
  /* 150px y no 115: con contratos de decenas de millones,
     "$32,303,555" necesita 128px y en 115 salía cortado a la
     mitad. Más vale que una tarjeta se baje de renglón a que un
     importe se lea incompleto. */
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 0.4rem;
  margin-bottom: 0.8rem;
}
/* La cifra manda: número grande, etiqueta discreta y una franja de color
   arriba que dice de qué se trata. La sombra la despega del fondo sin
   encerrarla en una línea dura. */
.stat-card {
  background: var(--tarjeta);
  border: 1px solid var(--borde-suave);
  border-radius: var(--radio-tarjeta);
  box-shadow: var(--sombra);
  padding: 0.75rem 0.9rem;
  border-top: 3px solid var(--azul-medio);
  transition: box-shadow .15s ease, transform .15s ease;
}
.stat-card:hover { box-shadow: var(--sombra-alta); transform: translateY(-1px); }
.stat-card.stat-alerta { border-top-color: var(--rojo); }
.stat-num {
  font-size: 1.6rem;
  font-weight: 700;
  line-height: 1.1;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
  color: var(--texto);
}
.stat-card.stat-alerta .stat-num { color: var(--rojo); }

/* Tarjeta destacada: ocupa dos lugares de la rejilla y trae el número
   más grande. Es para LA cifra de la pantalla — si se usa en dos
   tarjetas a la vez deja de destacar ninguna. El amarillo es el color
   de acento del sistema, que existe justo para esto y se usa poco. */
.stat-card.stat-destacada {
  grid-column: span 2;
  border-top-color: var(--amarillo);
  border-top-width: 3px;
}
.stat-card.stat-destacada .stat-num { font-size: 2rem; }
.stat-nota {
  display: block;
  font-size: 0.7rem;
  line-height: 1.25;
  margin-top: 0.15rem;
  color: var(--texto-suave);
}

.stat-etiqueta {
  color: var(--texto-suave);
  font-size: 0.82rem;
  margin-top: 0.3rem;
}
.barra-stock {
  background: var(--gris-bg);
  border-radius: 999px;
  height: 6px;
  width: 100%;
  overflow: hidden;
  margin-top: 0.3rem;
}
.barra-stock-relleno {
  background: var(--rojo);
  height: 100%;
  border-radius: 999px;
}
.fila-insumo {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.6rem 0.2rem;
  border-bottom: 1px solid var(--borde);
}
.fila-insumo:last-child { border-bottom: none; }
.fila-insumo .info { flex: 1; }
.fila-insumo .info small { color: var(--texto-suave); display: block; }
.fila-insumo input[type="number"] { width: 90px; }

/* ---------- Tablas responsivas ----------
   Tabla densa normal en escritorio/iPad (comprador, bodeguero). En
   celular (<768px) se reacomoda sola en tarjetas apiladas, una por
   fila, sin tocar el PHP más que agregar la clase "tabla-responsive"
   a la tabla y data-label="..." a cada <td>. La fila de encabezado
   (siempre la primera <tr>, este sistema no usa <thead>) se oculta
   en celular — el label de cada celda hace su trabajo. */
@media (max-width: 767px) {
  .tabla-responsive thead,
  .tabla-responsive tr:first-child { display: none; }
  .tabla-responsive,
  .tabla-responsive tr,
  .tabla-responsive td { display: block; width: 100%; }
  .tabla-responsive { border: none; background: none; }
  .tabla-responsive tr {
    margin-bottom: 0.75rem;
    border: 1px solid var(--borde);
    border-radius: var(--radio);
    background: var(--tarjeta);
    overflow: hidden;
  }
  .tabla-responsive td {
    border-bottom: 1px solid var(--borde);
    padding: 0.55rem 0.75rem;
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 0.75rem;
    text-align: right;
  }
  .tabla-responsive td:last-child { border-bottom: none; }
  .tabla-responsive td::before {
    content: attr(data-label);
    font-weight: 600;
    color: var(--texto-suave);
    font-size: 0.78rem;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    text-align: left;
    flex-shrink: 0;
  }
  .tabla-responsive td[data-label=""]::before { content: none; }
}

/* ---------- Barra de estado tipo semáforo ---------- */
.semaforo {
  display: flex;
  flex-wrap: wrap;
  gap: 0;
  margin: 0.5rem 0 0.8rem;
  border: 1px solid var(--borde);
  border-radius: var(--radio);
  overflow: hidden;
}
.semaforo-paso {
  flex: 1;
  min-width: 90px;
  padding: 0.3rem 0.4rem;
  text-align: center;
  font-size: 0.7rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  background: var(--gris-bg);
  color: var(--texto-suave);
  border-right: 1px solid var(--borde);
}
.semaforo-paso:last-child { border-right: none; }
.semaforo-paso.semaforo-hecho { background: var(--verde-bg); color: var(--verde); }
.semaforo-paso.semaforo-actual { background: var(--azul-oscuro); color: #fff; }
.semaforo-paso.semaforo-cancelado { background: var(--rojo-bg); color: var(--rojo); }
@media (max-width: 767px) { .semaforo { display: none; } }

/* ---------- Tabla interactiva (filtro, orden, agrupación) ---------- */
.franja-agrupacion {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem;
  border: 1px dashed var(--linea-fuerte);
  border-radius: var(--radio);
  padding: 0.25rem 0.5rem;
  margin-bottom: 0.35rem;
  background: var(--gris-bg);
  min-height: 1.9rem;
}
.franja-agrupacion-sobre { border-color: var(--azul-medio); background: #e3e9ee; }
.franja-agrupacion-texto { color: var(--texto-suave); font-size: 0.74rem; }
.franja-agrupacion-vacia .franja-agrupacion-texto { display: inline; }
.franja-agrupacion:not(.franja-agrupacion-vacia) .franja-agrupacion-texto { display: none; }
.franja-agrupacion-chips { display: flex; flex-wrap: wrap; gap: 0.4rem; flex: 1; }
.chip-agrupacion {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  background: var(--azul-oscuro);
  color: #fff;
  border-radius: 2px;
  padding: 0.1rem 0.25rem 0.1rem 0.45rem;
  font-size: 0.72rem;
  font-weight: 600;
}
.chip-agrupacion button {
  background: rgba(255,255,255,0.2);
  border: none;
  color: #fff;
  border-radius: 50%;
  width: 1.1rem;
  height: 1.1rem;
  line-height: 1;
  cursor: pointer;
  font-size: 0.85rem;
}
.franja-agrupacion-botones { display: flex; gap: 0.4rem; margin-left: auto; }

.th-ordenable { cursor: pointer; user-select: none; }
.th-ordenable:hover { color: var(--amarillo); }
.th-orden-indicador { font-size: 0.7rem; }
.th-arrastrable { cursor: grab; }
.th-arrastrable:active { cursor: grabbing; }

/* ---------- Ancho de columna arrastrable (como Excel) ----------
   Con anchos por <col> hace falta table-layout:fixed: si no, el
   navegador vuelve a repartir el espacio según el contenido y el
   arrastre no se nota. El texto que ya no cabe se corta con "…",
   que es justo la señal de que a esa columna le falta ancho. */
table.tabla-redimensionable { table-layout: fixed; }
table.tabla-redimensionable th,
table.tabla-redimensionable td { overflow: hidden; text-overflow: ellipsis; }

/* Franja de 6px sobre el borde derecho del encabezado. Solo se ve
   al pasar el mouse por esa columna: siempre visible sería una
   reja de líneas encima de una tabla que ya tiene sus bordes. */
.th-redimension {
  position: absolute;
  top: 0;
  bottom: 0;
  right: 0;
  width: 6px;
  cursor: col-resize;
  user-select: none;
  background: transparent;
}
th:hover > .th-redimension { background: var(--tirador); }
th > .th-redimension-activo,
th:hover > .th-redimension-activo { background: var(--tirador-activo); }

/* Mientras se arrastra: el cursor no debe cambiar al pasar sobre
   otros elementos, ni debe seleccionarse texto de la página. */
body.redimensionando-columna { user-select: none; }
body.redimensionando-columna * { cursor: col-resize !important; }

.fila-filtro td {
  background: #eef1f4;
  padding: 0.15rem 0.25rem;
  position: sticky;                 /* el JS le pone el top exacto */
  z-index: 1;
  border-bottom: 1px solid var(--linea-fuerte);
}
.fila-filtro input {
  width: 100%;
  padding: 0.12rem 0.3rem;
  border: 1px solid #c3c9cf;
  border-radius: 2px;
  font-size: 0.72rem;
  background: var(--campo-bg);
}
.fila-filtro input::-webkit-search-cancel-button { cursor: pointer; }

.fila-grupo { background: var(--grupo-bg); cursor: pointer; }
.fila-grupo td { font-size: 0.76rem; padding: 0.2rem 0.5rem; background: var(--grupo-bg); border-right: none; }
.fila-grupo-flecha { display: inline-block; width: 1rem; color: var(--texto-suave); }

.rango-precio { color: var(--texto-suave); font-size: 0.78rem; }
.precio-fuera-rango { color: var(--rojo); font-weight: 700; }
.badge-fuera-rango {
  display: inline-block;
  background: var(--rojo-bg);
  color: var(--rojo);
  border-radius: 999px;
  padding: 0.05rem 0.4rem;
  font-size: 0.7rem;
  font-weight: 700;
  margin-left: 0.3rem;
}

/* En celular la tabla ya se vuelve tarjetas — arrastrar encabezados
   no aplica, así que ocultamos la franja de agrupación (el select de
   respaldo por pantalla lo agrega cada página que lo necesite). El
   filtro por columna se queda oculto también: en tarjetas no hay
   encabezados visibles para saber qué filtra cada input. */
.tabla-interactiva-movil { display: none; }
@media (max-width: 767px) {
  .franja-agrupacion { display: none; }
  .tabla-responsive .fila-filtro { display: none; }
  .th-redimension { display: none; }
  table.tabla-redimensionable { table-layout: auto; width: 100% !important; }
  .tabla-interactiva-movil {
    display: flex;
    gap: 0.5rem;
    margin-bottom: 0.75rem;
  }
  .tabla-interactiva-movil select {
    flex: 1;
    padding: 0.5rem;
    border: 1px solid var(--borde);
    border-radius: var(--radio);
    background: var(--campo-bg);
    font-size: 0.85rem;
  }
}

/* ---------- Encabezado de pantalla y barra de filtros ----------
   Patrón compartido por Inventario, Requisiciones y Bitácora: el
   título y las acciones en una sola línea, y debajo una barra de
   filtros horizontal en vez de un formulario en bloque. Ahorra dos
   renglones de alto en cada pantalla, que es lo que se convierte en
   filas visibles de tabla. */
.barra-titulo {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-wrap: wrap;
  margin-bottom: 0.5rem;
}
.barra-titulo h1 { margin: 0; }
.barra-acciones { margin-left: auto; display: flex; gap: 0.35rem; flex-wrap: wrap; }

.filtros-barra {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.35rem;
  margin-bottom: 0.5rem;
  padding: 0.35rem 0.5rem;
  background: var(--gris-bg);
  border: 1px solid var(--borde);
  border-radius: var(--radio);
}
/* Los que eligen de una lista se quedan del tamaño de lo que muestran. */
.filtros-barra select,
.filtros-barra input[type="date"] {
  width: auto;
  min-width: 130px;
  max-width: 240px;
}
/* La caja de texto libre, en cambio, crece con lo que sobre del renglón.
   Medido en Insumos: la caja daba 184 px —su ancho natural; ni siquiera
   llegaba al máximo de 240 que decía este mismo bloque— el texto pedía
   213 y quedaban 492 px de barra vacía a la derecha. Un campo apretado
   junto a medio renglón en blanco no se sostiene.
   El `flex-basis` es el ancho al que aspira; encoge en pantallas
   angostas hasta el mínimo y, como la barra envuelve, abajo se lleva el
   renglón completo. El tope evita que en un monitor ancho la caja se
   estire sola de lado a lado. Es el mismo trato que ya tenía la búsqueda
   de las tablas interactivas (`.tabla-buscar`, `flex: 0 1 22rem`). */
.filtros-barra input[type="search"],
.filtros-barra input[type="text"] {
  flex: 1 1 17rem;
  width: auto;
  min-width: 11rem;
  max-width: 26rem;
}
.filtros-barra .btn { margin: 0; }
.filtro-check {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  margin: 0;
  font-size: 0.76rem;
  font-weight: 600;
  white-space: nowrap;
}
.filtro-check input { width: auto; margin: 0; }
.filtro-sep {
  color: var(--texto-suave);
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  padding-left: 0.35rem;
}

/* Material que viene en camino: se distingue del resto sin gritar. */
.en-camino { color: var(--enlace); font-weight: 700; }

/* ---------- Barra de porcentaje dentro de una celda ----------
   El número solo se lee renglón por renglón; la barra se lee de un
   escaneo vertical. Se muestran los dos. */
.barra-pct {
  position: relative;
  display: block;
  min-width: 54px;
  height: 13px;
  background: var(--barra-bg);
  border-radius: 2px;
  overflow: hidden;
}
.barra-pct-relleno {
  position: absolute;
  inset: 0 auto 0 0;
  background: var(--azul-medio);
}
.barra-pct.completa .barra-pct-relleno { background: var(--verde); }
.barra-pct.vacia    .barra-pct-relleno { background: transparent; }
.barra-pct-texto {
  position: relative;
  display: block;
  text-align: center;
  font-size: 0.66rem;
  font-weight: 700;
  line-height: 13px;
  color: var(--texto);
  font-variant-numeric: tabular-nums;
}

/* ---------- Semáforo compacto, para meterlo en una fila ---------- */
.semaforo-mini { display: inline-flex; gap: 2px; vertical-align: middle; }
.semaforo-mini span {
  width: 12px;
  height: 8px;
  background: #d5dade;
  border-radius: 1px;
}
.semaforo-mini span.hecho    { background: var(--verde); }
.semaforo-mini span.actual   { background: var(--azul-oscuro); }
.semaforo-mini span.cancelado { background: var(--rojo); }

/* ---------- Fila expandible (ver partidas sin salir de la lista) ---------- */
.fila-expandible td:first-child { cursor: pointer; }
.expandir-flecha {
  display: inline-block;
  width: 0.8rem;
  color: var(--texto-suave);
  font-size: 0.7rem;
}
tr.fila-detalle > td {
  background: #f0f4f8;
  padding: 0.4rem 0.5rem 0.5rem 1.6rem;
  white-space: normal;
}
tr.fila-detalle table {
  width: auto;
  min-width: 380px;
  border: 1px solid var(--borde);
}
tr.fila-detalle th { background: var(--grupo-bg); }
tr.fila-detalle td { background: var(--campo-bg); }

/* ---------- Acciones dentro de una celda ---------- */
td .acciones-fila { display: inline-flex; gap: 0.3rem; white-space: nowrap; }
td .acciones-fila a { text-decoration: none; font-size: 0.74rem; }
/* El subrayado al pasar encima lo reemplaza la pastilla de `.zona-chica`
   (ZONAS CLICABLES): una celda de acciones son varias funciones juntas y
   cada una tiene que iluminarse sola. */

/* ---------- Totales al pie de una tabla ---------- */
tr.fila-totales td {
  background: var(--grupo-bg) !important;
  font-weight: 700;
  border-top: 2px solid var(--linea-fuerte);
  position: sticky;
  bottom: 0;
  z-index: 1;
}

/* ---------- Paginación ---------- */
.paginacion {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  margin-top: 0.5rem;
  font-size: 0.78rem;
}
.paginacion a, .paginacion span {
  padding: 0.15rem 0.45rem;
  border: 1px solid var(--borde);
  border-radius: 2px;
  text-decoration: none;
  background: var(--campo-bg);
}
.paginacion .actual { background: var(--azul-oscuro); color: #fff; border-color: var(--azul-oscuro); }
.paginacion .inerte { color: var(--texto-suave); background: var(--gris-bg); }

/* En celular las barras de filtros se apilan a lo ancho completo. */
@media (max-width: 767px) {
  .filtros-barra select,
  .filtros-barra input[type="search"],
  .filtros-barra input[type="date"],
  .filtros-barra input[type="text"] { max-width: none; width: 100%; }
  .barra-acciones { margin-left: 0; width: 100%; }
  .tabla-wrap { max-height: none; border: none; }
}

/* ---------- Selección múltiple y columnas de control ---------- */
.barra-seleccion {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
  padding: 0.3rem 0.5rem;
  margin-bottom: 0.35rem;
  background: #e3e9ee;
  border: 1px solid var(--azul-medio);
  border-radius: var(--radio);
  font-size: 0.78rem;
}
.barra-seleccion[hidden] { display: none; }

th.col-sel, td.col-sel, th.col-exp, td.col-exp {
  width: 1.4rem;
  padding-left: 0.25rem;
  padding-right: 0.25rem;
  text-align: center;
}
td.col-sel input, th.col-sel input { width: auto; margin: 0; cursor: pointer; }
.btn-expandir {
  background: none;
  border: none;
  cursor: pointer;
  color: var(--texto-suave);
  font-size: 0.7rem;
  padding: 0;
  line-height: 1;
}
.btn-expandir:hover { color: var(--azul-medio); }

/* La fila de detalle no se raya ni se resalta como fila normal. */
tr.fila-detalle td { border-right: none; }
tr.fila-detalle:hover td { background: #f0f4f8; }
tr.fila-detalle[hidden] { display: none; }

/* ---------- Alta rápida de insumo desde la requisición ---------- */
.panel-alta {
  border-left: 3px solid var(--azul-medio);
  background: #f7f9fb;
}
.aviso-parecidos {
  background: #fff6e0;
  border: 1px solid #e6cf9a;
  border-radius: var(--radio);
  padding: 0.5rem 0.7rem;
  margin-top: 0.5rem;
  font-size: 0.8rem;
}
.aviso-parecidos ul { margin: 0.35rem 0; padding-left: 1.1rem; }
.aviso-parecidos li { margin-bottom: 0.15rem; }
.aviso-parecidos code {
  background: var(--campo-bg);
  border: 1px solid var(--borde);
  padding: 0 0.2rem;
  font-size: 0.74rem;
}
.aviso-parecidos p { margin: 0.35rem 0 0; }
.fila-insumo-nuevo { background: #eef7ee; }

/* Recepción incompleta: ámbar, no verde — el material llegó a medias
   y la orden sigue abierta. */
.badge-recibida_parcial { background: var(--sem-ambar-bg); color: var(--sem-ambar-tx); }
tr.fila-completa td { opacity: 0.62; }

/* ---------- Rejilla de campos para fichas con muchos datos ---------- */
.rejilla-campos {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  gap: 0.35rem 0.9rem;
}
.rejilla-campos label:first-child { margin-top: 0; }
.rejilla-campos .ayuda-campo { margin-bottom: 0.3rem; }
@media (max-width: 600px) {
  .rejilla-campos { grid-template-columns: 1fr; }
}

/* ---------- Bitácora del sistema ---------- */
.celda-cambios { font-size: 0.74rem; line-height: 1.5; min-width: 260px; }
.valor-antes   { color: var(--rojo);  text-decoration: line-through; }
.valor-despues { color: var(--verde); font-weight: 600; }

/* ---------- Documento enlazado desde una celda de tabla ---------- */
/* El icono se pinta con ::before y no como texto a propósito: la tabla
   interactiva agrupa y filtra leyendo el textContent de la celda, así
   que un carácter suelto ahí volvería el grupo "4509680882" en
   "4509680882 ⤓". Como pseudo-elemento no entra en textContent. */
a.ico-bajar {
  display: inline-block;
  width: 1.05em;
  text-align: center;
  margin-left: 0.35rem;
  color: var(--texto-suave);
  text-decoration: none;
  vertical-align: baseline;
}
a.ico-bajar::before { content: "\2913"; }   /* ⤓ flecha hacia una línea */
a.ico-bajar:hover   { color: var(--azul-medio); }

/* Superíndice cuando hay más de una versión del mismo documento.
   El número se pinta con ::before desde data-n, por la misma razón que
   el icono: como pseudo-elemento no entra en el textContent que la
   tabla interactiva usa para agrupar. */
span.doc-versiones {
  font-size: 0.62rem;
  font-weight: 700;
  color: var(--sem-ambar-tx);  /* mismo ámbar del badge de recepción parcial */
  vertical-align: super;
  margin-left: 0.1rem;
  cursor: help;
}
span.doc-versiones::before { content: attr(data-n); }

/* ---------- Archivos adjuntos a una factura de cliente ----------
   Van dentro de una celda de tabla, no en una tarjeta: cada factura
   puede traer varios (el PDF, el complemento de pago, el acuse del
   portal) y se listan uno por renglón dentro de su celda. */
.doc-linea {
  display: flex;
  align-items: baseline;
  gap: 0.3rem;
  flex-wrap: wrap;
  margin-bottom: 0.15rem;
}
.doc-linea .tenue { font-size: 0.7rem; }

/* Quitar un adjunto: una × discreta y no un botón rojo de tamaño
   completo — en una celda con tres archivos, tres botones "Quitar"
   pesarían más que los archivos mismos. Se confirma antes de borrar. */
.doc-quitar {
  background: none;
  border: none;
  color: var(--texto-suave);
  cursor: pointer;
  font-size: 0.9rem;
  line-height: 1;
  padding: 0 0.15rem;
}
.doc-quitar:hover { color: var(--rojo); }

details.doc-adjuntar { margin-top: 0.25rem; }
details.doc-adjuntar > summary {
  cursor: pointer;
  color: var(--texto-suave);
  font-size: 0.72rem;
}
details.doc-adjuntar > summary:hover { color: var(--enlace); }
details.doc-adjuntar form {
  margin-top: 0.35rem;
  padding: 0.4rem;
  border: 1px solid var(--borde);
  border-radius: var(--radio);
  background: var(--calculado-bg);
  min-width: 15rem;
}
details.doc-adjuntar label {
  display: block;
  font-size: 0.68rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--texto-suave);
  margin-top: 0.3rem;
}
details.doc-adjuntar select,
details.doc-adjuntar input[type="text"],
details.doc-adjuntar input[type="file"] { width: 100%; font-size: 0.75rem; }
details.doc-adjuntar .ayuda-campo { margin: 0.15rem 0 0; }
details.doc-adjuntar button { margin-top: 0.45rem; }


/* ---------- Semáforo de vigencia de constancias (matriz DC3) ---------- */
/* Cuatro estados, no tres: "no la tiene" (gris) no es lo mismo que "se
   le venció" (rojo). El DC3 de maquinaria pesada no le aplica a un
   electricista; pintarlo de rojo llenaría la pantalla de alarmas que
   nadie debe atender, y el rojo dejaría de significar algo.
   Los tonos son los mismos del Excel que esto reemplaza. */
td.celda-sem {
  text-align: center;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
/* El selector lleva td.celda-sem a propósito: la zebra es
   `tr.fila-alt td` (0,1,1) y le ganaba a un `.sem-x` suelto (0,1,0),
   así que en las filas alternas —la mitad de la tabla— el color del
   semáforo desaparecía. Con td.celda-sem.sem-x (0,2,1) siempre gana. */
td.celda-sem.sem-vigente        { background: var(--sem-verde-bg);  color: var(--sem-verde-tx); }
td.celda-sem.sem-por_vencer     { background: var(--sem-ambar-bg);  color: var(--sem-ambar-tx); }
td.celda-sem.sem-vencido        { background: var(--sem-rojo-bg);   color: var(--sem-rojo-tx); font-weight: 700; }
td.celda-sem.sem-sin_constancia { background: var(--sem-gris-bg);   color: var(--texto-suave); }

/* En la ficha del trabajador el estado se pinta sobre un <strong>. */
strong.celda-sem.sem-vigente    { background: var(--sem-verde-bg); color: var(--sem-verde-tx); }
strong.celda-sem.sem-por_vencer { background: var(--sem-ambar-bg); color: var(--sem-ambar-tx); }
strong.celda-sem.sem-vencido    { background: var(--sem-rojo-bg);  color: var(--sem-rojo-tx); }

/* La leyenda usa cuadros sueltos, no celdas: van sin td. */
.sem-cuadro.sem-vigente        { background: var(--sem-verde-bg); }
.sem-cuadro.sem-por_vencer     { background: var(--sem-ambar-bg); }
.sem-cuadro.sem-vencido        { background: var(--sem-rojo-bg); }
.sem-cuadro.sem-sin_constancia { background: var(--sem-gris-bg); }

/* Leyenda de arriba de la matriz. */
.leyenda-semaforo {
  font-size: 0.76rem;
  color: var(--texto-suave);
  margin: 0.5rem 0 0.3rem;
  display: flex;
  align-items: center;
  gap: 0.35rem;
  flex-wrap: wrap;
}
.leyenda-semaforo .sem-cuadro {
  display: inline-block;
  width: 0.85rem;
  height: 0.85rem;
  border: 1px solid var(--borde);
  margin-left: 0.7rem;
}
.leyenda-semaforo .sem-cuadro:first-child { margin-left: 0; }

/* Botón de subir dentro de una celda: tiene que caber sin engordar el
   renglón, así que va como texto pequeño y no como botón de verdad. */
.btn-subir-constancia {
  background: none;
  border: 1px dashed var(--borde);
  color: var(--texto-suave);
  font-size: 0.7rem;
  padding: 0.05rem 0.3rem;
  cursor: pointer;
  border-radius: var(--radio);
}
.btn-subir-constancia:hover { color: var(--azul-medio); border-color: var(--azul-medio); }
/* Los dos botones de una celda que YA tiene constancia. Ambos se
   pintan por CSS y no como texto, para no meterse en el nombre del
   grupo al agrupar — igual que el icono de descarga.

     .falta-archivo  hay fecha pero nadie subió el papel   -> clip
     .renovar        está completa; se reemplaza al vencer -> flecha

   El de renovar es la respuesta a algo que faltaba: sin él, una
   constancia vencida solo se podía reemplazar entrando a la ficha del
   trabajador, cuando la pantalla donde SE VE que venció es la matriz. */
.btn-subir-constancia.falta-archivo,
.btn-subir-constancia.renovar {
  border: none;
  margin-left: 0.3rem;
  padding: 0;
  opacity: 0.45;
}
.btn-subir-constancia.falta-archivo::before { content: "\1F4CE"; }
.btn-subir-constancia.renovar::before       { content: "\21BB"; font-size: 0.95rem; }
.btn-subir-constancia.falta-archivo:hover,
.btn-subir-constancia.renovar:hover { opacity: 1; }

/* Vencida o por vencer: el botón deja de ser discreto — es justo lo que
   hay que hacer hoy con ese renglón. */
.btn-subir-constancia.urge { opacity: 1; font-weight: 700; }

/* La matriz es ancha: la primera columna se queda fija al desplazarse
   a la derecha, si no se pierde de vista de quién es el renglón. */
.matriz-dc3 td:first-child,
.matriz-dc3 th:first-child {
  position: sticky;
  left: 0;
  background: var(--tarjeta);
  z-index: 2;
}
.matriz-dc3 tr.fila-alt td:first-child { background: var(--fila-alt); }

/* Panel de captura de constancia: en la misma página, no flotante —
   en iPad un modal se pelea con el teclado. */
.panel-constancia { margin-top: 0.8rem; border-left: 3px solid var(--azul-medio); }

/* ---------- Caja chica / viáticos ---------- */
/* La tabla del papel es densa y con muchas columnas de dinero: se
   aprieta más que el resto y los montos van monoespaciados para que
   las cifras se alineen por columna, como en la hoja impresa. */
.tabla-caja td, .tabla-caja th { font-size: 0.76rem; padding: 0.18rem 0.35rem; }
.tabla-caja .num { font-variant-numeric: tabular-nums; }

/* El UUID de un CFDI mide 36 caracteres. Antes se recortaba con "…" y el
   completo quedaba en el `title` — mala idea: el folio fiscal es el dato
   con el que el contador amarra el gasto con la factura del SAT, y uno
   que hay que adivinar con el ratón encima no sirve. Se muestra COMPLETO.
   Cabe sin ensanchar la tabla porque se parte solo: el navegador corta
   después de los guiones del UUID, así que cada tramo queda entero
   (6B279E1B-E06C-4677-82A0- / 7700FA85DC0F) en vez de a media palabra.
   Monoespaciado para poder comparar hexadecimal carácter por carácter. */
.folio-cfdi {
  max-width: 12rem;
  font-family: ui-monospace, Consolas, monospace;
  font-size: 0.7rem;
  line-height: 1.25;
  /* Explícito porque `th, td` de más arriba impone `nowrap` a toda celda.
     Sin esta línea el folio no se parte: se desborda encima de la columna
     de Proveedor. Medido. */
  white-space: normal;
  word-break: normal;
  overflow-wrap: break-word;
}

/* El importe es CALCULADO: se marca visualmente para que se lea distinto
   de los campos que sí se teclean. */
.importe-calc { background: var(--calculado-bg); }

.fila-total td {
  background: var(--th-bg);
  font-weight: 700;
  border-top: 2px solid var(--borde);
}

/* Gasto fiscal sin su factura adjunta: no bloquea el cierre, pero no
   puede pasar desapercibido. */
.falta-factura { color: var(--rojo); font-weight: 700; font-size: 0.72rem; }

/* La marca del XML del CFDI, al lado del enlace de la factura. Va como
   pastilla y no como enlace azul para que se lea como una etiqueta —"este
   gasto tiene su XML"— y no como una segunda factura. */
.etiqueta-xml {
  display: inline-block;
  margin-left: 0.3rem;
  padding: 0 0.3rem;
  border: 1px solid var(--borde);
  border-radius: 4px;
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  color: var(--texto-suave);
  text-decoration: none;
  vertical-align: 1px;
}
.etiqueta-xml:hover {
  color: var(--acento-legible);
  border-color: var(--acento);
}

.btn-editar-gasto, .btn-quitar-gasto {
  background: none;
  border: none;
  color: var(--texto-suave);
  cursor: pointer;
  font-size: 0.72rem;
  padding: 0 0.15rem;
}
.btn-editar-gasto:hover { color: var(--azul-medio); text-decoration: underline; }
.btn-quitar-gasto:hover { color: var(--rojo); }

/* Barra del fondo asignado */
.barra-fondo { padding: 0.5rem 0.7rem; }
.fondo-cifras {
  display: flex;
  gap: 1.4rem;
  flex-wrap: wrap;
  font-size: 0.8rem;
  margin-bottom: 0.35rem;
}
.fondo-cifras .excedido, .excedido { color: var(--rojo); font-weight: 700; }
.barra-cont {
  height: 0.55rem;
  background: var(--gris-bg);
  border: 1px solid var(--borde);
  overflow: hidden;
}
.barra-usado { height: 100%; background: var(--verde); }
.barra-usado.barra-excedida { background: var(--rojo); }

/* Cierre del documento: las tres cifras que se firman */
.cierre-caja { margin-top: 0.8rem; max-width: 30rem; }
.tabla-cierre { width: 100%; }
.tabla-cierre td { border: none; padding: 0.15rem 0.3rem; font-size: 0.82rem; }
.tabla-cierre .num { text-align: right; font-variant-numeric: tabular-nums; }
.tabla-cierre .negativo { color: var(--rojo); font-weight: 700; }
.tabla-cierre .fila-reembolso td {
  border-top: 2px solid var(--texto);
  font-size: 0.9rem;
  padding-top: 0.3rem;
}

/* El importe que se recalcula mientras se teclea en el panel */
.importe-vivo {
  margin: 0.5rem 0 0;
  font-size: 0.85rem;
  padding: 0.3rem 0.5rem;
  background: var(--calculado-bg);
  border-left: 3px solid var(--azul-medio);
}
.importe-vivo .tenue { font-size: 0.74rem; margin-left: 0.5rem; }

.alerta-info {
  background: var(--info-bg);
  border: 1px solid var(--info-borde);
  color: var(--info-tx);
  padding: 0.5rem 0.7rem;
  margin-bottom: 0.7rem;
  border-radius: var(--radio);
  font-size: 0.84rem;
}

/* Campo obligatorio en el panel de captura de gastos. */
.obligatorio {
  font-size: 0.68rem;
  font-weight: 700;
  color: var(--rojo);
  text-transform: uppercase;
  margin-left: 0.3rem;
}

/* ==========================================================
   Configuración
   ========================================================== */

/* --- Selector de tema: tres tarjetas con una miniatura de cómo se ve.
       Una miniatura dice más que la palabra "oscuro", y de paso enseña
       el contraste real antes de aplicarlo. --- */
.temas {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 0.6rem;
}
.tema-opcion {
  display: block;
  border: 2px solid var(--borde);
  border-radius: var(--radio);
  padding: 0.6rem;
  cursor: pointer;
  margin: 0;
  font-weight: 400;
  background: var(--tarjeta);
}
.tema-opcion:hover { border-color: var(--azul-medio); }
.tema-opcion.elegido { border-color: var(--azul-medio); background: var(--fila-hover); }
.tema-opcion input { margin-right: 0.4rem; }
.tema-nombre { font-weight: 700; font-size: 0.85rem; display: inline; }
.tema-ayuda  { display: block; font-size: 0.74rem; color: var(--texto-suave); margin-top: 0.15rem; }

/* La miniatura: una barra de menú y dos renglones de contenido. */
.tema-muestra {
  display: block;
  height: 44px;
  border: 1px solid var(--borde);
  border-radius: 2px;
  overflow: hidden;
  margin-bottom: 0.45rem;
}
.tema-muestra .tm-barra { display: block; height: 11px; }
.tema-muestra .tm-cuerpo { display: block; height: 33px; padding: 5px; }
.tema-muestra .tm-linea { display: block; height: 4px; margin-bottom: 4px; border-radius: 2px; }
.tema-muestra .tm-linea.corta { width: 55%; }

.tema-claro  .tm-barra { background: #1b2733; }
.tema-claro  .tm-cuerpo { background: #f5f6f8; }
.tema-claro  .tm-linea { background: #c9cfd6; }

.tema-oscuro .tm-barra { background: #10161d; }
.tema-oscuro .tm-cuerpo { background: #151a20; }
.tema-oscuro .tm-linea { background: #39434f; }

/* "Automático" se pinta a la mitad de cada uno: es exactamente lo que
   hace — seguir al equipo, que puede estar en cualquiera de los dos. */
.tema-auto .tm-barra { background: linear-gradient(90deg, #1b2733 50%, #10161d 50%); }
.tema-auto .tm-cuerpo { background: linear-gradient(90deg, #f5f6f8 50%, #151a20 50%); }
.tema-auto .tm-linea { background: linear-gradient(90deg, #c9cfd6 50%, #39434f 50%); }

/* --- Los ajustes: rótulo y explicación a la izquierda, campo a la
       derecha. Un ajuste que no explica qué hace obliga a probarlo para
       averiguarlo, que con la configuración de un sistema es caro. --- */
.grupo-ajustes h3 {
  margin: 0 0 0.5rem;
  font-size: 0.9rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--texto-suave);
  border-bottom: 1px solid var(--borde);
  padding-bottom: 0.3rem;
}
.ajuste {
  display: flex;
  gap: 1rem;
  align-items: flex-start;
  padding: 0.6rem 0;
  border-bottom: 1px solid var(--borde);
  flex-wrap: wrap;
}
.ajuste:last-child { border-bottom: none; padding-bottom: 0; }
.ajuste-texto { flex: 1 1 320px; min-width: 0; }
.ajuste-texto label { margin: 0; font-size: 0.86rem; }
.ajuste-desc  { margin: 0.15rem 0 0; font-size: 0.78rem; color: var(--texto-suave); }
.ajuste-ayuda {
  margin: 0.2rem 0 0;
  font-size: 0.74rem;
  color: var(--texto-suave);
  border-left: 2px solid var(--borde);
  padding-left: 0.5rem;
}
.ajuste-campo { flex: 0 0 200px; text-align: right; }
.ajuste-campo input, .ajuste-campo select { text-align: right; }
.ajuste-rango { display: block; font-size: 0.72rem; color: var(--texto-suave); margin-top: 0.15rem; }
/* El nombre técnico queda a la vista pero discreto: sirve para buscarlo
   en el código, no para que sea lo primero que se lee. */
.ajuste-clave {
  display: block;
  font-family: ui-monospace, Consolas, monospace;
  font-size: 0.66rem;
  color: var(--texto-suave);
  opacity: 0.65;
  margin-top: 0.1rem;
  word-break: break-all;
}

@media (max-width: 600px) {
  .ajuste-campo { flex: 1 1 100%; text-align: left; }
  .ajuste-campo input, .ajuste-campo select { text-align: left; }
}

/* ==========================================================
   Aviso ámbar: "hay algo que revisar", sin ser un error.
   Antes se pintaba con colores fijos en línea (#fff6e0) que en tema
   oscuro salían como un parche blanco. Usa las variables del semáforo
   ámbar, que ya están definidas en los dos temas.
   ========================================================== */
.alerta-aviso {
  background: var(--sem-ambar-bg);
  color: var(--sem-ambar-tx);
  border: 1px solid var(--sem-ambar-tx);
}
.alerta-aviso a { color: inherit; font-weight: 600; }

/* ==========================================================
   Estado vacío: una tabla sin renglones debe decir qué hacer,
   no solo "no hay datos". Va dentro de la celda que ocupa toda
   la fila, para no romper la estructura de la tabla.
   ========================================================== */
.estado-vacio {
  text-align: center;
  padding: 1.6rem 1rem;
  color: var(--texto-suave);
}
.estado-vacio strong { display: block; color: var(--texto); font-size: 0.95rem; margin-bottom: 0.25rem; }
.estado-vacio p { margin: 0 0 0.6rem; font-size: 0.85rem; }
.estado-vacio .btn { margin-top: 0.2rem; }

/* ==========================================================
   Utilidades de espaciado. Reemplazan los style="margin-top:…"
   que se repetían en 49 archivos. Llevan !important a propósito:
   un estilo en línea siempre ganaba, y estas clases tienen que
   seguir ganando para que nada cambie de lugar al sustituirlos.
   ========================================================== */
.mt-0   { margin-top: 0 !important; }
.mt-05  { margin-top: 0.5rem !important; }
.mt-06  { margin-top: 0.6rem !important; }
.mt-08  { margin-top: 0.8rem !important; }
.mt-1   { margin-top: 1rem !important; }
.sin-tope { max-height: none !important; }      /* .tabla-wrap sin scroll propio */
.en-linea { display: inline !important; }       /* <form> pegado a otros botones */
.h3-compacto { margin: 0 0 0.5rem !important; font-size: 0.95rem !important; }

/* ==========================================================
   Página de error (403 / 404 / 500) y de sesión expirada.
   ========================================================== */
.error-wrap {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1rem;
}
.error-caja {
  width: 100%;
  max-width: 440px;
  text-align: center;
  background: var(--tarjeta);
  border: 1px solid var(--borde);
  border-radius: var(--radio);
  padding: 1.6rem 1.4rem;
}
.error-codigo {
  font-family: ui-monospace, Consolas, monospace;
  font-size: 0.78rem;
  color: var(--texto-suave);
  letter-spacing: 0.08em;
  margin: 0.6rem 0 0.2rem;
}
.error-caja h1 { font-size: 1.15rem; margin: 0 0 0.5rem; }
.error-caja p  { color: var(--texto-suave); font-size: 0.9rem; margin: 0 0 1rem; }
.error-acciones { display: flex; gap: 0.5rem; justify-content: center; flex-wrap: wrap; }

/* ==========================================================
   Impresión. Se imprime el contenido, no la pantalla: fuera el
   menú, los filtros, los botones y el pie; la tabla ocupa toda la
   hoja y deja de tener su propio scroll.
   ========================================================== */
@media print {
  @page { margin: 12mm; }
  html[data-tema] { color-scheme: light; }
  body { background: #fff !important; color: #000 !important; font-size: 11px; }
  /* .no-imprimir: lo que solo sirve en pantalla (ayudas, barras de
     acciones) y estorba en el papel. */
  .no-imprimir,
  .sidebar, .barra-areas, .sidebar-fondo, .piepagina, .barra-contexto,
  .filtros-barra, .btn, form.filtros, .franja-agrupar,
  .tabla-interactiva .fila-filtro, .acciones-seleccion,
  nav, button { display: none !important; }
  .app { display: block; }
  .contenido { padding: 0 !important; max-width: none !important; }
  .tarjeta, .stat-card, .tabla-wrap {
    background: #fff !important; color: #000 !important;
    border-color: #999 !important; box-shadow: none !important;
  }
  .tabla-wrap { max-height: none !important; overflow: visible !important; }
  table { width: 100% !important; table-layout: auto !important; page-break-inside: auto; }
  th { position: static !important; background: #eee !important; color: #000 !important; }
  tr { page-break-inside: avoid; }
  td, th { border-color: #bbb !important; }
  a { color: #000 !important; text-decoration: none; }
  a[href]::after { content: none !important; }
  .semaforo, .sem-mini, .badge { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
}

/* Las tarjetas del inicio son enlaces: llevan a la pantalla de donde
   sale el número. Se ven igual que las demás y marcan el hover. */
a.stat-card { display: block; text-decoration: none; color: inherit; }
a.stat-card:hover { border-color: var(--amarillo); }

/* =====================================================================
   ETIQUETA DEL TABLERO — el directorio que se pega en la puerta
   =====================================================================
   Se mide en MILÍMETROS y se compone en PUNTOS, no en px, porque el
   destino es papel y no pantalla. Un px de CSS al imprimir vale 1/96 de
   pulgada, así que 9.5 px eran 7.13 pt — y a 7.13 pt, con la regla de
   la ANSI Z535 para condiciones desfavorables (6.67 mm de altura de
   mayúscula por metro de distancia), la hoja se leía a 27 cm. Eso es
   pegar la nariz al tablero.

   La versión anterior además ocupaba 154.9 mm de los 259.4 útiles: el
   60 % de la hoja, con 104.5 mm en blanco al pie. Había encogido la
   letra para caber y luego no llenaba el papel.

   DOS FORMATOS CON PÁGINA PROPIA
   ------------------------------
   `@page` con nombre permite que la misma hoja de estilos tenga la
   carta vertical del directorio grande y la carta apaisada de la
   variante de bolsillo, sin un <style> suelto en la pantalla.
   ===================================================================== */

/* Celdas unidas hacia abajo: un circuito de varios polos ocupa varios
   renglones y su nombre va centrado en el bloque, no pegado arriba. */
.celda-unida,
.etiqueta-tablero td.unida { vertical-align: middle; }

.etiqueta-tablero { color: var(--texto); }

/* --- La rejilla ----------------------------------------------------- */
.et-rejilla {
  border-collapse: collapse; width: 100%; table-layout: fixed;
  font-variant-numeric: tabular-nums;
}
.et-rejilla th,
.et-rejilla td {
  border: 0.25mm solid var(--borde);
  /* 2 mm de aire arriba y abajo: con 42 polos la hoja queda al 91 %
     de su alto útil, que deja margen para una descripción que se
     acomode en dos renglones sin empujar a una segunda hoja. */
  padding: 2mm 1.6mm;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: 13pt; line-height: 1.15;
}
.et-rejilla thead th {
  background: var(--fondo-2); font-weight: 600;
  font-size: 8pt; letter-spacing: .04em; text-transform: uppercase;
  padding: 1mm 1.6mm;
}
.et-rejilla td.num, .et-rejilla th.num { text-align: center; }
.et-rejilla td.et-desc {
  text-align: left; font-weight: 600; font-size: 17pt;
  white-space: normal; overflow-wrap: break-word; hyphens: auto;
}
.et-rejilla td.et-num  { font-size: 12pt; color: var(--texto-tenue); }
.et-rejilla td.et-fase { font-size: 11pt; color: var(--texto-tenue); }
.et-rejilla td.et-dato {
  font-size: 8.5pt; color: var(--texto-tenue);
  white-space: normal; line-height: 1.25;
}
.et-rejilla td.vacia   { background: var(--fondo); }

/* El renglón donde empieza un interruptor lleva línea más marcada: se
   cuentan los bloques sin leer los números. */
.et-rejilla tbody tr.et-abre td { border-top-width: 0.5mm; }

/* Anchos: el nombre de lo que alimenta se lleva el espacio, y las
   columnas de número quedan angostas. Suman 100 en los dos armados. */
/* Suman 100 exactos: 2 × (4.5 + 26 + 9 + 8.5) + 4. El nombre de lo que
   alimenta se lleva la parte del león porque es lo que se busca. */
.et-rejilla col.c-polo { width: 4.5%; }
.et-rejilla col.c-desc { width: 26%; }
.et-rejilla col.c-itm  { width: 9%; }
.et-rejilla col.c-cal  { width: 8.5%; }
.et-rejilla col.c-fase { width: 4%; }
/* Sin la columna central, el 4 % que dejó se reparte entre los dos
   nombres: 2 × (4.5 + 28 + 9 + 8.5) = 100. */
.et-rejilla.et-sin-fase col.c-desc { width: 28%; }
/* Y sin calibres tampoco: 2 × (6 + 38 + 6) = 100. */
/* Los cuatro armados, y los cuatro suman 100. El interruptor nunca
   baja de 12 %: con 6 % "3X150" salía recortado como "3…", que es
   justo el dato por el que alguien abre la puerta del tablero.

     con calibres + con fase   2 × (4.5 + 26 + 9 + 8.5) + 4 = 100
     con calibres + sin fase   2 × (4.5 + 28 + 9 + 8.5)     = 100
     sin calibres + con fase   2 × (6 + 30 + 12) + 4        = 100
     sin calibres + sin fase   2 × (6 + 32 + 12)            = 100  */
.et-rejilla:not(.et-con-calibres) col.c-polo { width: 6%; }
.et-rejilla:not(.et-con-calibres) col.c-desc { width: 30%; }
.et-rejilla:not(.et-con-calibres) col.c-itm  { width: 12%; }
.et-rejilla:not(.et-con-calibres) col.c-fase { width: 4%; }
.et-rejilla.et-sin-fase:not(.et-con-calibres) col.c-desc { width: 32%; }

/* --- El cabezal ----------------------------------------------------- */
.et-cab { margin-bottom: 2.5mm; }

/* En la pieza de bolsillo el cabezal se comprime: el alto que gasta
   arriba sale directo del tamaño de letra de los circuitos. */
.et-pieza .et-cab { margin-bottom: 1.6mm; }
.etiqueta-tablero .et-pieza .et-clave { font-size: 18pt; }
.et-pieza .et-sistema { font-size: 7pt; }
.et-pieza .et-sello { font-size: 6pt; }
.et-pieza .et-sello strong { font-size: 6.5pt; }
.et-pieza .et-banda { font-size: 9pt; padding: 1mm 1.6mm; margin: 1.2mm 0; }
.et-pieza .et-banda em { font-size: 6pt; }
.et-pieza .et-meta { font-size: 6pt; margin-bottom: 1.2mm; }
.et-pieza .et-rejilla td { padding: 1.3mm 1.2mm; }
.et-pieza .et-rejilla td.et-desc { font-size: 13pt; }
.et-pieza .et-rejilla td.et-num { font-size: 9.5pt; }
.et-pieza .et-rejilla td, .et-pieza .et-rejilla th { font-size: 10.5pt; }
.et-pieza .et-rejilla thead th { font-size: 6.5pt; padding: .6mm 1.2mm; }
.et-pieza .et-pie { font-size: 5.5pt; margin-top: 1mm; }
.et-cab-alto {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: 6mm; border-bottom: 0.6mm solid var(--texto); padding-bottom: 1.6mm;
}
/* Va con dos clases a propósito: `.contenido h2` de más abajo tiene
   especificidad (0,1,1) y le ganaba a una sola clase, dejando la clave
   del tablero en 13 pt. */
.etiqueta-tablero .et-clave {
  font-size: 30pt; font-weight: 800; line-height: .95;
  letter-spacing: -.02em; margin: 0;
}
.et-sistema { font-size: 9.5pt; color: var(--texto-tenue); margin: 1mm 0 0; }
.et-sello {
  text-align: right; font-size: 7.5pt; line-height: 1.4;
  color: var(--texto-tenue); white-space: nowrap;
}
.et-sello strong { display: block; color: var(--texto); font-size: 8.5pt; }
.et-sello span { display: block; }

/* "Alimentado de" lleva banda propia porque el artículo 408-4(b) de la
   NOM lo exige por su nombre. Antes era el sexto renglón de una lista
   de nueve, todos del mismo tamaño. */
.et-banda {
  display: flex; align-items: baseline; gap: 3mm;
  background: var(--texto); color: var(--fondo);
  padding: 1.6mm 2.5mm; margin: 2mm 0;
  font-size: 12pt;
}
.et-banda em { font-style: normal; font-size: 7.5pt; letter-spacing: .1em;
               text-transform: uppercase; opacity: .75; }
.et-banda strong { font-weight: 700; }
.et-banda span { margin-left: auto; font-size: 8pt; opacity: .75; }

.et-meta {
  display: flex; flex-wrap: wrap; gap: 0 4mm; margin: 0 0 2mm;
  font-size: 7.5pt; color: var(--texto-tenue);
}
.et-meta span { white-space: nowrap; }

/* --- El pie: leyenda de abreviaturas --------------------------------
   El NEC 2023 la pide de forma expresa cuando el directorio usa
   símbolos, y ésta usa 3X150, T-, CH- y KCMIL. */
.et-pie {
  display: flex; flex-wrap: wrap; justify-content: space-between;
  align-items: baseline; gap: 0 4mm;
  margin-top: 1.8mm; font-size: 7pt; color: var(--texto-tenue);
}
.et-pie > span { min-width: 0; }
.et-pie strong { color: var(--texto); font-weight: 600; }
.et-norma { white-space: nowrap; flex: none; }

/* --- La placa del frente de la puerta -------------------------------
   Nadie identifica un tablero abriéndolo. 152 × 51 mm es la sobremedida
   de fábrica de 2 × 6 pulgadas, por si algún día se manda a grabar en
   micarta; mientras tanto la hoja sirve de plantilla. */
.et-placa {
  width: 152mm; height: 51mm; box-sizing: border-box;
  border: 0.6mm solid var(--texto); padding: 4mm 5mm;
  display: flex; flex-direction: column; justify-content: center;
  margin-bottom: 2mm;
}
.et-placa-clave { font-size: 40pt; font-weight: 800; line-height: .95; letter-spacing: -.02em; }
.et-placa-sis { font-size: 11pt; color: var(--texto-tenue); margin-top: 1.6mm; }
.et-placa-de  { font-size: 11pt; font-weight: 700; margin-top: .8mm; }

/* Línea de corte: sale impresa a propósito, es la guía de la tijera. */
.et-corte {
  position: relative; border-top: 0.25mm dashed var(--borde);
  margin: 0 0 4mm; height: 0;
}
.et-corte span {
  position: absolute; top: -1.6mm; left: 155mm; background: var(--fondo);
  padding: 0 1.5mm; font-size: 7pt; color: var(--texto-tenue);
}
.et-corte span::before { content: "✂ "; }

/* --- Media carta: dos piezas por hoja -------------------------------
   Una se mete al bolsillo del tablero y otra se va a la oficina, que es
   donde se raya a mano el cambio para reimprimir después. Por eso
   tampoco conviene plastificarla. */
/* Dos piezas apiladas con el margen global de 10 mm sólo tenían 259.4 mm
   útiles y la segunda se partía entre dos hojas — que es exactamente lo
   que se reportó. La página propia va con margen 0 y se pide desde el
   BODY, que es donde no fuerza un salto ni deja una hoja en blanco
   antes; el aire lo pone cada pieza con su propio relleno.

   El alto de la pieza es 130 mm y no 139.7 porque MEDIDO, con margen 0,
   el alto que de verdad se aprovecha ronda los 264 mm y no los 279.4 de
   la hoja: el navegador se guarda algo. Dos de 132 entran y dos de 134
   ya no, así que 130 deja holgura sin dejar de caber en el bolsillo de
   fábrica, que mide 137 de alto. */
body.pantalla-etiqueta-doble { page: etiqueta-doble; }
.et-media { display: flex; flex-direction: column; align-items: flex-start; }
/* 152 × 137 mm es el bolsillo de plástico de fábrica de Square D
   (154 × 137, cat. 8003115901) menos 2 mm de holgura. Dos piezas
   apiladas miden 274 mm y caben en el alto de una carta VERTICAL, que
   es la hoja por omisión: así el formato no depende de que el
   navegador respete una orientación pedida por CSS. */
/* `min-height` y no `height`, y sin `overflow: hidden`: si alguien
   fuerza el formato de bolsillo en un tablero que no cabe, la pieza
   crece y se ve. Recortar en silencio media lista de circuitos en la
   hoja que dice qué pastilla bajar sería peor que verse feo. */
.et-pieza {
  width: 152mm; min-height: 130mm; box-sizing: border-box;
  padding: 6mm;
}
.et-corte-horizontal {
  width: 152mm; border-top: 0.25mm dashed var(--borde);
  position: relative; height: 0; margin: 0;
}
.et-corte-horizontal span {
  position: absolute; top: -1.6mm; left: 4mm; background: var(--fondo);
  padding: 0 1.5mm; font-size: 6pt; color: var(--texto-tenue);
}
.et-corte-horizontal span::before { content: "✂ "; }

/* --- Los controles de arriba (no se imprimen) ----------------------- */
.et-controles { margin: 0 0 1.2rem; }
.et-controles fieldset {
  border: 1px solid var(--borde-suave); border-radius: var(--radio-tarjeta);
  padding: .7rem .9rem; margin: 0;
}
.et-controles legend {
  font-size: .78rem; text-transform: uppercase; letter-spacing: .06em;
  color: var(--texto-tenue); padding: 0 .4rem;
}
.et-controles label {
  display: block; font-size: .88rem; padding: .18rem 0; cursor: pointer;
}
.et-controles .et-aparte {
  margin-top: .4rem; padding-top: .5rem; border-top: 1px solid var(--borde-suave);
}

/* --- En pantalla: la hoja se ve como hoja --------------------------- */
@media screen {
  .etiqueta-tablero {
    background: var(--tarjeta); border: 1px solid var(--borde-suave);
    border-radius: var(--radio-tarjeta); box-shadow: var(--sombra);
    padding: 8mm; overflow-x: auto;
  }
  .et-media { padding: 0; }
}

/* =====================================================================
   EN PAPEL
   =====================================================================
   Cada formato con su propia página nombrada. El margen de 8 mm no es
   estético: por debajo de ~9 mm Chrome deja de imprimir su encabezado
   con la URL y la fecha, que en una hoja que se pega en un tablero
   sobran. El aire se recupera con el padding de adentro.
   ===================================================================== */
@page { size: letter portrait; margin: 10mm; }
/* La hoja carta usa la página global y NO una con nombre.

   Pedirla para un elemento de en medio del documento fuerza un salto de
   página ahí: el navegador cierra la hoja anterior y empieza otra. Como
   lo que va antes de la etiqueta está escondido al imprimir, esa hoja
   salía EN BLANCO. Medido: 2 hojas con la regla, 1 sin ella.

   Los 8 mm de margen que compraba no valían una hoja de más, y el
   contenido ya estaba dimensionado contra los 259.4 mm útiles del
   margen global de 10 mm. */
@page etiqueta-doble { size: letter portrait; margin: 0; }

@media print {

  .etiqueta-tablero {
    background: none !important; border: 0 !important; box-shadow: none !important;
    padding: 0 !important; color: #000 !important;
  }
  .etiqueta-tablero *, .et-placa, .et-placa * { color: #000 !important; }

  .et-rejilla th, .et-rejilla td { border-color: #000 !important; }
  .et-rejilla thead th { background: #e0e0e0 !important; }
  .et-rejilla td.vacia { background: #f4f4f4 !important; }
  .et-rejilla td.et-num, .et-rejilla td.et-fase,
  .et-rejilla td.et-dato, .et-meta, .et-pie, .et-sello { color: #333 !important; }

  /* Los fondos sólidos sólo salen si el navegador tiene permitido
     imprimirlos; sin esto la banda negra sale en blanco. */
  .et-banda {
    background: #000 !important; color: #fff !important;
    -webkit-print-color-adjust: exact; print-color-adjust: exact;
  }
  .et-banda * { color: #fff !important; }
  .et-rejilla thead th, .et-rejilla td.vacia {
    -webkit-print-color-adjust: exact; print-color-adjust: exact;
  }

  .et-cab-alto { border-bottom-color: #000 !important; }
  .et-placa { border-color: #000 !important; }
  .et-corte, .et-corte-horizontal { border-color: #666 !important; }
  .et-corte-horizontal span { background: #fff !important; }
  .et-corte span { background: #fff !important; }

  /* Un gabinete de 84 polos no cabe en una hoja: la tabla se parte y el
     <thead> repite los títulos en la siguiente. Lo que no se parte es
     un renglón por la mitad. */
  .et-rejilla tr { page-break-inside: avoid; break-inside: avoid; }

  /* Una PIEZA de bolsillo tampoco se parte, nunca. Se recorta con
     tijera: media pieza en una hoja y media en otra no sirve para nada.
     Si las dos no caben juntas —un tablero grande al que le forzaron el
     formato chico— la segunda se va entera a su propia hoja. Salen dos
     hojas en vez de una, pero las dos se pueden usar. */
  .et-pieza { page-break-inside: avoid; break-inside: avoid; }
  .et-media .et-pieza { padding: 6mm; }
}


/* =====================================================================
   Tablas en papel: nunca en modo celular
   =====================================================================
   `.tabla-responsive` apila cada celda en su propio renglón por debajo
   de 767 px, que en pantalla es lo correcto. El problema es que una
   hoja carta con márgenes mide ~740 px, así que AL IMPRIMIR se disparaba
   ese modo: la etiqueta del tablero salía con una celda por renglón
   ("CIRCUITO 1", "QUÉ ALIMENTA LED", …) y ocupaba tres hojas.
   En papel siempre va como tabla.
   ===================================================================== */
@media print {
  .tabla-responsive { display: table !important; width: 100% !important;
                      border-collapse: collapse !important; }
  .tabla-responsive tr,
  .tabla-responsive tr:first-child { display: table-row !important;
                      margin: 0 !important; border: 0 !important;
                      background: none !important; box-shadow: none !important; }
  .tabla-responsive thead { display: table-header-group !important; }
  .tabla-responsive td,
  .tabla-responsive th { display: table-cell !important; width: auto !important;
                      text-align: inherit; padding: 2px 4px !important; }
  .tabla-responsive td.num,
  .tabla-responsive th.num { text-align: center !important; }
  /* La etiqueta de celular ("QUÉ ALIMENTA", "CIRCUITO"…) sobra: en papel
     el encabezado de la columna ya está arriba. */
  .tabla-responsive td::before { content: none !important; display: none !important; }
}

/* Celdas de captura dentro de una tabla ---------------------------------
   El registro de cableado se captura de corrido, con varias medidas
   anotadas de obra. Los campos van dentro de la tabla, angostos y
   alineados a la derecha como el número que son, para que el renglón
   siga leyéndose como renglón y no como formulario. */
.celda-numero {
  width: 6.5rem; text-align: right; padding: 2px 6px;
  font-variant-numeric: tabular-nums;
}
/* El que ya se midió en obra se distingue del que trae el valor de
   fábrica: es la diferencia entre un metro que se puede comprar y uno
   que todavía no. */
.celda-medida {
  border-color: var(--verde);
  background: var(--verde-bg);
  font-weight: 600;
}

/* Clases de cable: fuerza, control, datos ------------------------------
   Se distinguen por color porque es la división que más se consulta: el
   que compra pregunta "cuánto de fuerza y cuánto de control". El color
   va por variables para que funcione en los dos temas. */
.etiqueta-clase {
  display: inline-block; padding: 1px 8px; border-radius: 999px;
  font-size: .72rem; font-weight: 600; letter-spacing: .01em;
  border: 1px solid transparent; white-space: nowrap;
}
.clase-fuerza  { background: var(--sem-ambar-bg); color: var(--sem-ambar-tx); }
.clase-control { background: var(--info-bg);      color: var(--info-tx);      }
.clase-datos   { background: var(--sem-verde-bg); color: var(--sem-verde-tx); }
.clase-sin     { background: var(--gris-bg);      color: var(--texto-suave);  }

/* La franja superior de la tarjeta de cifra toma el color de su clase,
   para que las cuatro se lean de un vistazo sin buscar la etiqueta. */
.stat-clase-fuerza  { border-top-color: var(--sem-ambar-tx); }
.stat-clase-control { border-top-color: var(--info-tx);      }
.stat-clase-datos   { border-top-color: var(--verde);        }
.stat-clase-sin     { border-top-color: var(--linea-fuerte); }

/* Selector de segmentos -------------------------------------------------
   Para saltar entre las mitades de un conjunto —fuerza, control, datos—
   sin perder de vista cuánto hay de cada una. Va con el conteo y los
   metros dentro del propio botón: la pregunta que se hace quien compra
   es "cuánto de cada cosa", y así se contesta sin entrar. */
.segmentos {
  display: flex; flex-wrap: wrap; gap: 6px;
  margin: 0 0 .7rem;
}
.segmento {
  display: inline-flex; align-items: baseline; gap: .45rem;
  padding: .35rem .75rem;
  border: 1px solid var(--borde);
  border-radius: 999px;
  background: var(--tarjeta);
  color: var(--texto);
  text-decoration: none;
  font-size: .82rem; font-weight: 600;
  transition: background .12s ease, border-color .12s ease, box-shadow .12s ease;
}
.segmento:hover { border-color: var(--linea-fuerte); box-shadow: var(--sombra); text-decoration: none; }
.segmento.activo {
  background: var(--azul-medio);
  border-color: var(--azul-medio);
  color: #fff;
}
.segmento-n {
  font-variant-numeric: tabular-nums; font-weight: 700;
}
.segmento-m {
  font-size: .72rem; font-weight: 500; opacity: .75;
  font-variant-numeric: tabular-nums;
}
/* Cada clase lleva su color en el borde, el mismo de su etiqueta, para
   que el ojo las asocie sin leer. */
.segmento.seg-fuerza  { border-left: 3px solid var(--sem-ambar-tx); }
.segmento.seg-control { border-left: 3px solid var(--info-tx); }
.segmento.seg-datos   { border-left: 3px solid var(--verde); }
.segmento.seg-sin     { border-left: 3px solid var(--linea-fuerte); }
.segmento.activo.seg-fuerza,
.segmento.activo.seg-control,
.segmento.activo.seg-datos,
.segmento.activo.seg-sin { border-left-color: transparent; }

@media (max-width: 767px) {
  .segmento { flex: 1 1 auto; justify-content: center; }
}
@media print { .segmentos { display: none !important; } }

/* =====================================================================
   Menú lateral — versión con movimiento
   =====================================================================
   El menú era una lista de texto plano: sin estado visible al pasar el
   ratón, sin transición al colapsar un grupo, y el link activo marcado
   con una barrita a la izquierda del borde. Se sentía estático.

   Lo que cambia:
   - El activo es una PÍLDORA metida hacia adentro, no una franja pegada
     al borde. Es el patrón que usan hoy las herramientas que se ven
     modernas, y de paso separa visualmente el menú del contenido.
   - Al pasar el ratón, el renglón se desplaza dos píxeles hacia la
     derecha. Es poco, y justo por eso se siente vivo sin marear.
   - El colapso de un grupo se interpola de verdad (reja de 1fr a 0fr),
     en vez de desaparecer de golpe.
   - Todo se apaga si el sistema pide menos movimiento.
   ===================================================================== */

.sidebar-nav { padding: 0.6rem 0.55rem 1rem; }

/* ---- Los links ---- */
.sidebar-link {
  position: relative;
  border-left: none;                 /* la barrita se reemplaza por la píldora */
  border-radius: 8px;
  padding: 0.42rem 0.7rem 0.42rem 1.05rem;
  margin: 1px 0;
  transition: background .16s ease, color .16s ease, transform .16s ease;
}
.sidebar-nav > .sidebar-link:first-child { padding-left: 0.7rem; }

.sidebar-link:hover {
  background: rgba(255, 255, 255, .07);
  color: #fff;
  transform: translateX(2px);
}

/* El activo: píldora sólida y un punto de acento que lo ancla. */
.sidebar-link.activo {
  background: var(--azul-medio);
  color: #fff;
  font-weight: 600;
  border-left-color: transparent;
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .06);
}
.sidebar-link.activo::before {
  content: "";
  position: absolute;
  left: 0.35rem; top: 50%;
  width: 3px; height: 1.05em;
  transform: translateY(-50%);
  border-radius: 2px;
  background: var(--amarillo);
}
.sidebar-link.activo:hover { transform: none; }   /* lo activo no se mueve */

/* ---- Entrada del menú ----
   Un desvanecido corto al cargar. Es breve a propósito: el menú se ve en
   cada página del sistema, y una animación larga que se repite todo el
   día cansa. */
@keyframes menu-entra {
  from { opacity: 0; transform: translateX(-6px); }
  to   { opacity: 1; transform: none; }
}
.sidebar-nav > * { animation: menu-entra .18s ease-out both; }
.sidebar-nav > *:nth-child(2) { animation-delay: .02s; }
.sidebar-nav > *:nth-child(3) { animation-delay: .04s; }
.sidebar-nav > *:nth-child(4) { animation-delay: .06s; }
.sidebar-nav > *:nth-child(5) { animation-delay: .08s; }
.sidebar-nav > *:nth-child(n+6) { animation-delay: .1s; }

/* Quien pidió menos movimiento en su sistema no lo recibe. No es un
   adorno opcional: hay gente a la que el movimiento le provoca mareo. */
@media (prefers-reduced-motion: reduce) {
  .sidebar-link { transition: none; }
  .sidebar-link:hover { transform: none; }
  .sidebar-nav > * { animation: none; }
}

/* =====================================================================
   El acento, aplicado
   =====================================================================
   Antes el sistema no tenía un color que mandara: todo era navy y gris,
   y el amarillo asomaba de vez en cuando. Un acento usado con decisión
   es lo que separa un sistema que se ve armado de uno que se ve viejo.

   Regla: el acento marca LO QUE SE PUEDE HACER (botón primario, activo
   del menú, foco). El rojo de error marca LO QUE SALIÓ MAL, y se
   distingue por su tratamiento —fondo tenue y barra a la izquierda—, no
   por el tono. Un botón nunca lleva ese tratamiento, así que no se
   confunden aunque compartan familia de color.
   ===================================================================== */

.btn-primario {
  background: var(--acento);
  border-color: var(--acento);
  color: var(--acento-tx);
}
.btn-primario:hover {
  filter: brightness(1.08);
  box-shadow: var(--sombra);
}

/* El foco del teclado: visible de verdad. Quien navega con tabulador no
   debería tener que adivinar dónde está parado. */
a:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
.segmento:focus-visible {
  outline: 2px solid var(--acento);
  outline-offset: 2px;
  border-radius: var(--radio);
}

/* El activo del menú toma el acento en su marca lateral. */
.sidebar-link.activo::before { background: var(--acento); }

/* El segmento activo también. */
.segmento.activo { background: var(--acento); border-color: var(--acento); color: var(--acento-tx); }

/* Los errores se reconocen por la barra, no por el tono ---------------- */
.alerta-error {
  border-left: 3px solid var(--rojo);
  padding-left: calc(0.7rem - 3px + 0.7rem);
}
.alerta-exito { border-left: 3px solid var(--verde); padding-left: calc(0.7rem - 3px + 0.7rem); }
.alerta-aviso { border-left: 3px solid var(--sem-ambar-tx); padding-left: calc(0.7rem - 3px + 0.7rem); }

/* Escala tipográfica ---------------------------------------------------
   Antes casi todo medía lo mismo (~13px) y nada guiaba el ojo. */
h1 { font-size: 1.4rem;  font-weight: 650; letter-spacing: -0.02em; }
h2 { font-size: 1.05rem; font-weight: 650; letter-spacing: -0.01em; }
h3 { font-size: 0.92rem; font-weight: 650; }

/* Barra de la tabla ----------------------------------------------------
   Reemplaza a la fila de doce cajitas "Filtrar…" que estaba siempre a la
   vista. Una sola caja busca en todas las columnas —que es lo que se
   quiere el 90% de las veces— y los filtros por columna siguen ahí,
   detrás de un botón, para cuando de verdad hacen falta. */
.tabla-barra {
  display: flex; align-items: center; gap: .5rem;
  margin: 0 0 .5rem;
}
.tabla-buscar {
  flex: 0 1 22rem;
  padding: .4rem .7rem;
  border: 1px solid var(--borde);
  border-radius: 999px;
  background: var(--campo-bg);
  color: var(--texto);
  font-size: .84rem; font-family: inherit;
  transition: border-color .15s ease, box-shadow .15s ease;
}
.tabla-buscar::placeholder { color: var(--texto-suave); }
.tabla-buscar:focus {
  outline: none;
  border-color: var(--acento);
  box-shadow: 0 0 0 3px var(--acento-suave);
}
.tabla-conteo {
  font-size: .78rem; color: var(--texto-suave);
  font-variant-numeric: tabular-nums;
  margin-right: auto;
}
.tabla-barra .btn.activo {
  background: var(--acento); border-color: var(--acento); color: var(--acento-tx);
}

/* La fila de filtros, cuando se abre, se distingue de los datos. */
.fila-filtro td { background: var(--gris-bg); }

/* La franja de agrupar deja de anunciarse: es una función avanzada y no
   tiene por qué ocupar un renglón con instrucciones en cada tabla. */
.franja-agrupacion-texto { font-size: .72rem; opacity: .55; }

@media (max-width: 767px) {
  .tabla-barra { flex-wrap: wrap; }
  .tabla-buscar { flex: 1 1 100%; }
}
@media print { .tabla-barra { display: none !important; } }

/* Iconos del menú -------------------------------------------------------
   Con treinta links, el ojo recorre palabra por palabra hasta dar con el
   que busca — y varios empiezan parecido ("Solicitudes de material",
   "Órdenes de compra"). Con un icono se navega por forma, que el cerebro
   resuelve antes que el texto.

   El icono toma el color del texto (currentColor), así que sigue solo al
   tema y al estado activo sin una regla de color aquí. */
.sidebar-link { display: flex; align-items: center; gap: .55rem; }
.sidebar-icono {
  flex: none;
  opacity: .72;
  transition: opacity .16s ease;
}
.sidebar-link:hover .sidebar-icono,
.sidebar-link.activo .sidebar-icono { opacity: 1; }
/* El activo lleva su marca lateral, así que el icono se recorre un poco
   para no encimarse con ella. */
.sidebar-link.activo { padding-left: 1.05rem; }

/* =====================================================================
   INICIO — portada, dinero por obra, módulos y línea de tiempo
   =====================================================================
   Todo con las variables del tema: hay claro y oscuro, y un #hex suelto
   se rompe en uno de los dos.
   ------------------------------------------------------------------ */

.portada {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 1rem; flex-wrap: wrap; margin-bottom: 1.2rem;
}
.portada-saludo { margin: 0 0 .15rem; }
.portada .btn { display: inline-flex; align-items: center; gap: .45rem; }

/* --- El dinero de cada obra --------------------------------------- */
.obra-dinero { padding: .75rem 0; border-bottom: 1px solid var(--borde-suave); }
.obra-dinero:last-child { border-bottom: 0; padding-bottom: 0; }
.obra-dinero:first-child { padding-top: 0; }
.obra-dinero-enc {
  display: flex; justify-content: space-between; align-items: baseline;
  gap: .8rem; flex-wrap: wrap; margin-bottom: .45rem;
}
.obra-dinero-enc span { font-size: .82rem; }

/* La barra apilada: lo contratado es el fondo, encima va lo estimado y
   encima de eso lo cobrado. Se superponen a propósito — cobrado nunca
   puede pasar de estimado, así que se lee como una barra dentro de otra. */
.barra-apilada {
  position: relative; height: 12px; border-radius: 6px; overflow: hidden;
  background: var(--barra-bg); border: 1px solid var(--borde-suave);
}
.barra-apilada span { position: absolute; top: 0; bottom: 0; left: 0; border-radius: 6px 0 0 6px; }
.barra-apilada-est { background: var(--azul-medio); opacity: .55; }
.barra-apilada-cob { background: var(--verde); }
.obra-dinero-pie {
  display: flex; gap: 1.1rem; flex-wrap: wrap;
  margin-top: .4rem; font-size: .78rem; color: var(--texto-suave);
}
.obra-dinero-falta { color: var(--acento-legible); font-weight: 600; }
.punto {
  display: inline-block; width: 8px; height: 8px; border-radius: 50%;
  margin-right: .3rem; vertical-align: baseline;
}
.punto-est { background: var(--azul-medio); opacity: .55; }
.punto-cob { background: var(--verde); }

/* --- Los módulos: la navegación visual ----------------------------- */
.modulos {
  display: grid; gap: .6rem; margin-bottom: 1.4rem;
  grid-template-columns: repeat(auto-fill, minmax(290px, 1fr));
}
.modulo {
  display: flex; align-items: center; gap: .75rem;
  padding: .8rem .9rem; text-decoration: none; color: inherit;
  background: var(--tarjeta); border: 1px solid var(--borde-suave);
  border-radius: var(--radio-tarjeta); box-shadow: var(--sombra);
  transition: transform .16s ease, box-shadow .16s ease, border-color .16s ease;
}
.modulo:hover, .modulo:focus-visible {
  transform: translateY(-2px); box-shadow: var(--sombra-alta);
  border-color: var(--acento); text-decoration: none;
}
.modulo-icono {
  flex: none; display: grid; place-items: center;
  width: 34px; height: 34px; border-radius: 9px;
  background: var(--acento-suave); color: var(--acento-legible);
}
.modulo-icono svg { width: 18px; height: 18px; }
.modulo-cuerpo { flex: 1 1 auto; min-width: 0; }
.modulo-titulo { display: block; font-weight: 600; font-size: .92rem; }
.modulo-que {
  display: block; font-size: .75rem; color: var(--texto-suave);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.modulo-cuenta { flex: none; text-align: right; line-height: 1.15; }
.modulo-cuenta strong { display: block; font-size: 1.15rem; font-variant-numeric: tabular-nums; }
.modulo-cuenta span { font-size: .68rem; color: var(--texto-suave); }
/* Un módulo en cero no se esconde: se apaga. Sigue siendo la puerta. */
.modulo-vacio .modulo-icono { background: var(--gris-bg); color: var(--texto-suave); }
.modulo-vacio .modulo-cuenta strong { color: var(--texto-suave); font-weight: 500; }

/* --- El arranque del sistema --------------------------------------- */
.pasos { list-style: none; margin: 0; padding: 0; }
.paso {
  display: flex; align-items: flex-start; gap: .6rem;
  padding: .42rem 0; border-bottom: 1px solid var(--borde-suave);
}
.paso:last-child { border-bottom: 0; }
.paso-marca {
  flex: none; margin-top: .18rem; width: 15px; height: 15px; border-radius: 50%;
  border: 2px solid var(--borde); position: relative;
}
.paso-listo .paso-marca { background: var(--verde); border-color: var(--verde); }
.paso-listo .paso-marca::after {
  content: ""; position: absolute; left: 3.5px; top: .5px;
  width: 4px; height: 8px; border: solid #fff;
  border-width: 0 2px 2px 0; transform: rotate(45deg);
}
.paso-texto { font-size: .88rem; }
.paso-texto small { display: block; font-size: .74rem; }
.paso-listo .paso-texto { color: var(--texto-suave); text-decoration: line-through; }

/* --- La línea de tiempo -------------------------------------------- */
.linea-tiempo { list-style: none; margin: 0; padding: 0; }
.linea-dia {
  font-size: .68rem; letter-spacing: .08em; text-transform: uppercase;
  color: var(--texto-suave); margin: .8rem 0 .25rem;
}
.linea-dia:first-child { margin-top: 0; }
.linea-item {
  display: flex; gap: .7rem; padding: .35rem 0 .35rem .7rem;
  border-left: 2px solid var(--borde-suave); position: relative;
}
.linea-item::before {
  content: ""; position: absolute; left: -5px; top: .72rem;
  width: 8px; height: 8px; border-radius: 50%;
  background: var(--borde); border: 2px solid var(--tarjeta);
}
.lt-crear::before   { background: var(--verde); }
.lt-editar::before  { background: var(--azul-medio); }
.lt-borrar::before  { background: var(--acento); }
.linea-hora {
  flex: none; width: 3rem; font-size: .74rem; color: var(--texto-suave);
  font-variant-numeric: tabular-nums;
}
.linea-texto { font-size: .85rem; }
.linea-texto small { display: block; font-size: .72rem; }

/* =====================================================================
   MOVIMIENTO — lo que hace que el sistema se sienta vivo
   ------------------------------------------------------------------ */

/* Aparecer al cargar: quita el parpadeo blanco entre pantallas. */
@keyframes entrar { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }
.contenido > * { animation: entrar .22s ease both; }

/* El botón mientras guarda. No se deshabilita (un botón deshabilitado no
   manda su name/value), solo se apaga y deja de aceptar clics. */
.btn-esperando { opacity: .72; cursor: progress; }
.girando {
  display: inline-block; width: .8em; height: .8em; margin-right: .4em;
  vertical-align: -.08em; border: 2px solid currentColor;
  border-right-color: transparent; border-radius: 50%;
  animation: girar .6s linear infinite;
}
@keyframes girar { to { transform: rotate(360deg); } }

/* El encabezado de tabla que se queda pegado. En 721 renglones de cable
   uno pierde de vista qué columna es cuál a los quince. */
.tabla-wrap table > tr:first-child th,
.tabla-wrap thead th {
  position: sticky; top: 0; z-index: 3;
  background: var(--th-bg);
  box-shadow: inset 0 -1px 0 var(--borde);
}

/* A quien tenga apagadas las animaciones en su sistema, aquí tampoco se
   le mueve nada. */
@media (prefers-reduced-motion: reduce) {
  .contenido > *, .girando, .modulo, .barra-pct-relleno {
    animation: none !important; transition: none !important;
  }
  .modulo:hover { transform: none; }
}

/* Al imprimir no hay hover, ni sombras, ni encabezados pegados. */
@media print {
  .modulos { display: block; }
  .modulo, .tarjeta { box-shadow: none; }
  .tabla-wrap table > tr:first-child th,
  .tabla-wrap thead th { position: static; box-shadow: none; }
  .contenido > * { animation: none; }
}

/* Una fila de filtros que se envía sola al cambiar un selector. */
.filtros-linea {
  display: flex; gap: 1rem; flex-wrap: wrap; align-items: flex-end;
  margin: .6rem 0 .9rem;
}
.filtros-linea label {
  display: flex; flex-direction: column; gap: .2rem;
  font-size: .74rem; color: var(--texto-suave);
}

/* =====================================================================
   MEDIR EN OBRA — un cable a la vez, pensado para el celular
   =====================================================================
   Esta pantalla se usa de pie junto a un tablero, con el flexómetro en
   una mano. Todo es grande a propósito: el nombre del cable, el campo y
   el botón. En escritorio se centra en una columna angosta en vez de
   estirarse, porque estirada dejaría de leerse de un vistazo.
   ------------------------------------------------------------------ */

.medir { max-width: 460px; margin: 0 auto; }
.medir-avance {
  display: flex; align-items: center; gap: .8rem;
  max-width: 460px; margin: 0 auto 1rem;
}
.medir-avance .barra-pct { flex: 1 1 auto; }
.medir-avance .tenue { flex: none; font-size: .76rem; white-space: nowrap; }

.medir-cable {
  background: var(--tarjeta); border: 1px solid var(--borde-suave);
  border-radius: var(--radio-tarjeta); box-shadow: var(--sombra);
  padding: 1.1rem 1.1rem 1rem; text-align: center;
}
.medir-nombre {
  margin: .5rem 0 .3rem; font-size: 2.1rem; line-height: 1.05;
  letter-spacing: -.02em; font-variant-numeric: tabular-nums;
}
.medir-funcion { margin: 0 0 .5rem; font-size: .95rem; }
.medir-ruta {
  display: flex; align-items: center; justify-content: center;
  gap: .5rem; flex-wrap: wrap; margin: 0 0 .5rem;
  font-size: .82rem; color: var(--texto-suave);
}
.medir-flecha { color: var(--acento); font-weight: 700; }
.medir-ficha { margin: 0; font-size: .76rem; color: var(--texto-suave); }

/* El campo. La unidad va dentro para que el número quede pegado a ella
   y no haya duda de qué se está escribiendo. */
.medir-campo {
  display: flex; align-items: center; gap: .5rem; position: relative;
  margin: 1rem 0 .4rem; padding: .7rem .9rem;
  background: var(--campo-bg); border: 2px solid var(--borde);
  border-radius: var(--radio-tarjeta);
  transition: border-color .16s ease, box-shadow .16s ease;
}
.medir-campo:focus-within { border-color: var(--acento); box-shadow: 0 0 0 3px var(--acento-suave); }
.medir-campo > span:first-child {
  position: absolute; top: -.6rem; left: .7rem; padding: 0 .3rem;
  background: var(--fondo); font-size: .72rem; color: var(--texto-suave);
}
.medir-campo input {
  flex: 1 1 auto; min-width: 0; border: 0; background: none; outline: none;
  color: var(--texto); font: inherit; font-size: 2.2rem; font-weight: 600;
  text-align: right; font-variant-numeric: tabular-nums;
  padding: 0; margin: 0; appearance: textfield; -moz-appearance: textfield;
}
.medir-campo input::-webkit-outer-spin-button,
.medir-campo input::-webkit-inner-spin-button { appearance: none; margin: 0; }
.medir-unidad { flex: none; font-size: 1.2rem; color: var(--texto-suave); }

.medir-pista { margin: 0 0 1rem; font-size: .8rem; }
.medir-botones { display: flex; gap: .6rem; align-items: center; flex-wrap: wrap; }
.medir-botones .btn-primario { flex: 1 1 auto; }
.btn-grande { padding: .85rem 1.2rem; font-size: 1rem; }

.tarjeta-centrada { max-width: 460px; margin: 0 auto; text-align: center; }
.tarjeta-centrada .btn { margin: .3rem .2rem 0; }

@media (max-width: 480px) {
  .medir-nombre { font-size: 1.8rem; }
  .medir-campo input { font-size: 1.9rem; }
  .medir-botones { flex-direction: column; align-items: stretch; }
  .medir-botones .btn { width: 100%; text-align: center; }
}

/* =====================================================================
   AVISOS QUE SE DESVANECEN
   =====================================================================
   Un "se guardó" arriba de una tabla de 1,473 renglones no se ve. Este
   se pone encima, se queda seis segundos y se va solo. Si el JavaScript
   no corre, se queda como la alerta normal de siempre: no se pierde.
   ------------------------------------------------------------------ */
.alerta.es-toast {
  position: fixed; z-index: 60; left: 50%; bottom: 1.2rem;
  transform: translateX(-50%);
  max-width: min(30rem, calc(100vw - 2rem));
  box-shadow: var(--sombra-alta);
  animation: toast-entra .28s cubic-bezier(.22,.8,.3,1) both;
}
.alerta.es-toast.se-va { animation: toast-sale .3s ease forwards; }
@keyframes toast-entra { from { opacity: 0; transform: translate(-50%, 14px); } }
@keyframes toast-sale  { to   { opacity: 0; transform: translate(-50%, 14px); } }


/* =====================================================================
   ZONAS CLICABLES
   =====================================================================
   Antes cada destino de una tabla era un enlace azul subrayado dentro de
   la celda, y la fila entera navegaba al destino del PRIMER enlace. Dos
   problemas de fondo:

     · El enlace de texto dentro de una tabla se ve de otra época, y en
       Precios de material había CUATRO en el mismo renglón (material,
       cotización, ficha, historial) sin nada que dijera cuál era cuál
       hasta clicarlo.
     · La regla "la liga de la primera celda" dejaba sin destino a 22 de
       las 30 tablas: en Licitaciones el enlace está en la 3ª columna, y
       en Inventario y Reembolsos no había ninguno.

   Ahora el destino se le asigna a la CELDA que lo significa —esté donde
   esté— y la celda se ilumina al pasar por encima. Tres tintes, tres
   cosas distintas:

     --fila-lectura   "estás en este renglón".      Todas las tablas.
     --fila-hover     "este renglón va a algún lado". Solo las que navegan.
     --zona-bg        "ESTA celda es la que se abre", con la barrita del
                      acento —el mismo rojo del foco del teclado— para
                      que no quede duda de cuál.

   Y se excluyen entre sí: si el ratón está sobre una zona, el renglón se
   apaga y solo queda encendida la zona. Se ve a dónde se va ANTES de
   clicar, que es justo lo que el enlace azul nunca dijo.

   Al pasar por un renglón, TODAS sus zonas se delinean apenas: así se ve
   de un vistazo que hay tres lugares a donde ir, sin pintarlos de azul
   el resto del tiempo.

   El <a> sigue dentro de la celda y no se toca: el Tab, el clic derecho
   y "abrir en pestaña nueva" son del navegador. Lo único que cambia es
   que deja de pintarse como enlace. Quién es zona y quién no lo decide
   `js/interfaz.js`; aquí solo está cómo se ve.
   ------------------------------------------------------------------ */
tr.fila-liga { cursor: pointer; }
td.zona    { cursor: pointer; }

/* El texto de una zona es texto normal. Lo que avisa que se puede clicar
   es que se ilumine, no que sea azul y esté subrayado. */
td.zona a,
td.zona a:visited,
a.zona-chica,
a.zona-chica:visited { color: inherit; text-decoration: none; }

/* `.tenue` es jerarquía que puso la pantalla a propósito —la "ficha" de
   la cotización pesa menos que el archivo— y una zona no tiene por qué
   borrarla: se ilumina igual, pero sigue siendo lo secundario. */
td.zona a.tenue,
a.zona-chica.tenue { color: var(--texto-suave); }

/* Salvo cuando es LA zona encendida. Medido: el gris tenue sobre el
   fondo de la zona cae a 3.59:1 en los dos temas —o sea, el texto se
   volvía MENOS legible justo al apuntarle—. Encendida pasa a texto
   normal: 7.56:1 en oscuro. Lo que estás señalando se lee completo. */
tbody tr td.zona:hover a.tenue,
tbody tr a.zona-chica.tenue:hover,
tbody tr a.zona-chica.tenue:focus-visible { color: var(--texto); }

td.zona,
a.zona-chica { transition: background-color .14s ease, box-shadow .14s ease; }

/* ① El renglón que además va a algún lado. */
tbody tr.fila-liga:hover td { background: var(--fila-hover); }

/* ② Al pasar por el renglón, sus zonas se delinean: "aquí hay destinos". */
tbody tr:hover td.zona      { box-shadow: inset 0 0 0 1px var(--zona-borde); }
tbody tr:hover a.zona-chica { box-shadow: inset 0 0 0 1px var(--zona-borde); }

/* ③ Si el ratón está sobre una zona —de celda o pastilla—, el resto del
   renglón se apaga. `:not(.zona:hover)` deja fuera a la celda encendida:
   sin eso esta regla, que pesa más, le borraría el color a la que sí
   importa. */
tbody tr.fila-liga:hover:has(td.zona:hover, a.zona-chica:hover) td:not(.zona:hover) {
  background: var(--fila-lectura);
}

/* ④ La celda exacta que se va a abrir. `:focus-within` la enciende
   igual cuando se llega con el teclado: el Tab tiene que ver lo mismo
   que el ratón. */
tbody tr:hover td.zona:hover,
tbody tr td.zona:focus-within {
  background: var(--zona-bg);
  box-shadow: inset 2px 0 0 var(--zona-acento);
}

/* ⑤ Sobre una celda que no es zona, se enciende la PRINCIPAL: es a
   donde va el clic en el resto del renglón, y así se ve antes de darlo.
   Si el ratón ya está sobre una pastilla, no: ahí el clic va a la
   pastilla, y anunciar otro destino sería mentir. */
tbody tr.fila-liga:hover:not(:has(td.zona:hover, a.zona-chica:hover)) td.zona-principal {
  background: var(--zona-bg);
  box-shadow: inset 2px 0 0 var(--zona-acento);
}

/* ⑥ Zona chica: cuando en una celda hay varias funciones (la cotización
   y su ficha, el ⤓ de bajar, las acciones), cada una es su propia
   pastilla. Nunca se traga a la vecina — es la regla que ya costó un
   error en Precios, donde clicar el material abría el PDF del proveedor. */
a.zona-chica {
  display: inline-block;
  padding: .05rem .4rem;
  margin: -.05rem 0;
  border-radius: 999px;
}
/* `tbody tr` va al frente para pesar lo mismo que el contorno de ②: si
   no, el contorno tenue le gana a la pastilla encendida y la que está
   bajo el ratón se ve igual que sus vecinas. */
tbody tr a.zona-chica:hover,
tbody tr a.zona-chica:focus-visible {
  background: var(--zona-bg);
  box-shadow: inset 0 0 0 1px var(--zona-acento);
}
/* El ⤓ de descarga es un enlace de ancho fijo (1.05em) con el icono en
   ::before y nada de texto. La pastilla normal lo aplasta, así que lleva
   la suya: redonda y del tamaño del icono. */
a.ico-bajar.zona-chica {
  width: auto;
  min-width: 1.15em;
  padding: .05rem .2rem;
}

/* ⑦ En el celular no hay ratón: nada de esto se descubre pasando por
   encima. Ahí las zonas llevan la barrita puesta desde el principio —en
   modo tarjeta cada celda es un renglón completo, así que se lee como
   una lista de "a dónde puedo ir"— y responden al dedo. */
@media (max-width: 767px) {
  .tabla-responsive td.zona { box-shadow: inset 3px 0 0 var(--zona-borde); }
  .tabla-responsive td.zona:active,
  .tabla-responsive a.zona-chica:active {
    background: var(--zona-bg);
    box-shadow: inset 3px 0 0 var(--zona-acento);
  }
}

@media (prefers-reduced-motion: reduce) {
  td.zona, a.zona-chica { transition: none; }
}

/* Al imprimir no hay ratón ni foco: una zona es una celda y ya. */
@media print {
  td.zona, a.zona-chica { box-shadow: none !important; background: none !important; }
}

/* =====================================================================
   BUSCADOR GLOBAL (Ctrl+K)
   =====================================================================
   Se abre encima de todo, centrado arriba y no al medio: al escribir, la
   lista crece hacia abajo y el ojo no tiene que reencontrarla.
   ------------------------------------------------------------------ */
.buscador-fondo {
  position: fixed; inset: 0; z-index: 90;
  display: flex; justify-content: center; align-items: flex-start;
  padding: 8vh 1rem 1rem;
  background: rgba(10, 14, 20, .45);
  backdrop-filter: blur(2px); -webkit-backdrop-filter: blur(2px);
  animation: entrar .14s ease both;
}
body.sin-scroll { overflow: hidden; }

.buscador {
  width: min(38rem, 100%);
  background: var(--tarjeta);
  border: 1px solid var(--borde);
  border-radius: 14px;
  box-shadow: 0 24px 60px rgba(0, 0, 0, .34);
  overflow: hidden;
  animation: buscador-entra .18s cubic-bezier(.22,.8,.3,1) both;
}
@keyframes buscador-entra { from { opacity: 0; transform: translateY(-10px) scale(.985); } }

.buscador-campo {
  display: flex; align-items: center; gap: .6rem;
  padding: .85rem 1rem; border-bottom: 1px solid var(--borde-suave);
}
.buscador-campo input {
  flex: 1 1 auto; min-width: 0;
  border: 0; background: none; outline: none;
  color: var(--texto); font: inherit; font-size: 1.05rem;
}
.buscador-campo input::-webkit-search-cancel-button { appearance: none; }

.buscador-lista { max-height: min(52vh, 26rem); overflow-y: auto; padding: .35rem; }
.buscador-grupo {
  padding: .55rem .7rem .2rem; font-size: .66rem; letter-spacing: .09em;
  text-transform: uppercase; color: var(--texto-suave);
}
.buscador-item {
  display: flex; align-items: baseline; gap: .6rem;
  padding: .5rem .7rem; border-radius: 8px;
  color: inherit; text-decoration: none;
}
.buscador-item.marcado { background: var(--acento-suave); }
.buscador-item.marcado .buscador-texto { color: var(--acento-legible); }
.buscador-texto { flex: 0 1 auto; font-size: .9rem; font-weight: 500; }
.buscador-nota {
  flex: 1 1 auto; min-width: 0; font-size: .74rem; color: var(--texto-suave);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-align: right;
}
.buscador-vacio { padding: 1.6rem 1rem; text-align: center; color: var(--texto-suave); font-size: .85rem; }

.buscador-pie {
  display: flex; gap: 1.1rem; padding: .5rem 1rem;
  border-top: 1px solid var(--borde-suave); background: var(--barra-bg);
  font-size: .7rem; color: var(--texto-suave);
}
kbd {
  display: inline-block; padding: .05rem .32rem; margin: 0 .1rem;
  border: 1px solid var(--borde); border-bottom-width: 2px; border-radius: 4px;
  background: var(--campo-bg); font-family: inherit; font-size: .95em; line-height: 1.4;
}

/* El botón del encabezado que abre el buscador, para quien no sabe el atajo. */
.btn-buscar {
  display: inline-flex; align-items: center; gap: .45rem;
  padding: .32rem .6rem .32rem .55rem;
  background: var(--campo-bg); border: 1px solid var(--borde-suave);
  border-radius: 999px; color: var(--texto-suave);
  font-size: .78rem; cursor: pointer;
  transition: border-color .16s ease, color .16s ease;
}
.btn-buscar:hover { border-color: var(--acento); color: var(--texto); }
.btn-buscar kbd { border-bottom-width: 1px; font-size: .82em; }
@media (max-width: 640px) { .btn-buscar span.btn-buscar-atajo { display: none; } }

/* El botón de instalar. Toma la forma del de buscar —son vecinos en la
   misma barra y dos píldoras distintas se verían como un descuido— pero
   lleva el acento, porque ofrece algo en vez de sólo abrir algo.

   Sale escondido desde el servidor y lo enseña js/conexion.js sólo
   cuando el navegador de verdad puede instalar. */
.btn-instalar {
  display: inline-flex; align-items: center; gap: .4rem;
  padding: .32rem .68rem .32rem .55rem;
  background: var(--campo-bg);
  border: 1px solid var(--acento-legible);
  border-radius: 999px; color: var(--acento-legible);
  font-size: .78rem; font-weight: 600; cursor: pointer;
  transition: background-color .16s ease, color .16s ease;
}
.btn-instalar:hover { background: var(--acento); color: var(--acento-tx); border-color: var(--acento); }
.btn-instalar:hover .icono { color: inherit; }
.btn-instalar .icono { width: 1em; height: 1em; }
/* En pantalla angosta se queda el puro dibujo: la barra de arriba ya
   pelea por el ancho con las áreas y el usuario. */
@media (max-width: 640px) { .btn-instalar span { display: none; } }
@media print { .btn-instalar { display: none !important; } }

/* =====================================================================
   CONFIRMAR DENTRO DE LA PÁGINA
   =====================================================================
   En vez de la caja gris del sistema operativo, que en el celular tapa
   media pantalla y no se parece en nada al resto del sitio.
   ------------------------------------------------------------------ */
.confirmar-fondo {
  position: fixed; inset: 0; z-index: 95;
  display: grid; place-items: center; padding: 1rem;
  background: rgba(10, 14, 20, .45);
  backdrop-filter: blur(2px); -webkit-backdrop-filter: blur(2px);
  animation: entrar .14s ease both;
}
.confirmar {
  width: min(24rem, 100%);
  background: var(--tarjeta); border: 1px solid var(--borde);
  border-radius: 14px; box-shadow: 0 24px 60px rgba(0, 0, 0, .34);
  padding: 1.2rem;
  animation: buscador-entra .18s cubic-bezier(.22,.8,.3,1) both;
}
.confirmar-texto { margin: 0 0 1.1rem; font-size: .95rem; line-height: 1.45; }
.confirmar-botones { display: flex; gap: .5rem; justify-content: flex-end; flex-wrap: wrap; }
.btn-peligro {
  background: var(--rojo-solido); border-color: var(--rojo-solido); color: #fff;
}
.btn-peligro:hover { filter: brightness(1.08); }

@media (prefers-reduced-motion: reduce) {
  .buscador, .confirmar, .buscador-fondo, .confirmar-fondo, .alerta.es-toast {
    animation: none !important;
  }
}
@media print {
  .buscador-fondo, .confirmar-fondo, .btn-buscar { display: none !important; }
  .alerta.es-toast { position: static; transform: none; }
}


/* =====================================================================
   `hidden` significa oculto, punto
   =====================================================================
   El navegador trae `[hidden] { display: none }`, pero cualquier clase
   propia que fije un `display` le gana por especificidad. Ya había tres
   parches sueltos por eso (.sidebar-fondo, .barra-seleccion,
   tr.fila-detalle) y el buscador global cayó en lo mismo: se abría con
   Ctrl+K y Esc no lo cerraba, porque `hidden = true` no podía contra el
   `display: flex` de .buscador-fondo.

   Con esta regla deja de ser una trampa: poner `hidden` oculta, sin que
   haya que acordarse de escribir la excepción cada vez.
   ------------------------------------------------------------------ */
[hidden] { display: none !important; }

/* =====================================================================
   PULIDO FINAL — el movimiento que se nota sin verse
   =====================================================================
   Regla que se siguió en todo este bloque: si algo llama la atención
   sobre sí mismo, sobra. Lo que va aquí es para que el sistema se sienta
   sólido, no para que se luzca.
   ------------------------------------------------------------------ */

/* --- 1. Cruce entre pantallas ---------------------------------------
   Una sola declaración y el navegador funde una pantalla con la
   siguiente en vez de parpadear en blanco. El menú y la barra de arriba
   se quedan quietos porque llevan nombre propio: el navegador entiende
   que son EL MISMO elemento en las dos páginas y no los redibuja.

   El que no lo soporte (Firefox, Safari viejo) navega como siempre. */
@view-transition { navigation: auto; }

.sidebar { view-transition-name: menu-lateral; }

::view-transition-old(root),
::view-transition-new(root) { animation-duration: .18s; }

/* Con el cruce del navegador encendido, la entrada de cada bloque
   sobra: serían dos animaciones montadas y se ve pastoso. */
@supports (view-transition-name: none) {
  .contenido > * { animation: none; }
}

/* --- 2. Entrada escalonada ------------------------------------------
   Las tarjetas aparecían todas de golpe. Con 45 ms de diferencia se lee
   como algo que se acomoda, no como un volcado. Más de la sexta ya no
   se escalona: a partir de ahí la espera se notaría. */
.stat-grid > *, .modulos > * { animation: entrar .3s cubic-bezier(.22,.8,.3,1) both; }
.stat-grid > :nth-child(1), .modulos > :nth-child(1) { animation-delay: 0ms; }
.stat-grid > :nth-child(2), .modulos > :nth-child(2) { animation-delay: 45ms; }
.stat-grid > :nth-child(3), .modulos > :nth-child(3) { animation-delay: 90ms; }
.stat-grid > :nth-child(4), .modulos > :nth-child(4) { animation-delay: 135ms; }
.stat-grid > :nth-child(5), .modulos > :nth-child(5) { animation-delay: 180ms; }
.stat-grid > :nth-child(n+6), .modulos > :nth-child(n+6) { animation-delay: 225ms; }

/* --- 3. Respuesta al presionar --------------------------------------
   El botón se hunde un poco al tocarlo. En el celular es lo único que
   confirma que sí registró, antes de que el servidor conteste. */
.btn, .segmento, .modulo, .stat-card {
  transition: transform .12s ease, box-shadow .16s ease, border-color .16s ease, background-color .16s ease;
}
.btn:active, .segmento:active { transform: translateY(1px) scale(.985); }
.modulo:active, .stat-card:active { transform: translateY(0) scale(.995); }

/* --- 4. Cruce al cambiar de tema -------------------------------------
   Claro a oscuro era un salto seco. Se cruza solo lo que cambia de
   color, y solo mientras dura el cambio: la clase la pone y la quita el
   JavaScript del selector de tema, así que no hay una transición
   permanente encima de cada elemento de la página. */
html.cambiando-tema,
html.cambiando-tema * {
  transition: background-color .28s ease, color .28s ease,
              border-color .28s ease, fill .28s ease !important;
}

/* --- 5. La fila que se resalta al pasar ------------------------------
   Una barra de acento del lado izquierdo, no un cambio de fondo: en una
   tabla de 1,473 renglones el fondo solo hace ruido, la barra dice
   exactamente en qué renglón está el ojo. */
.tabla-wrap tbody tr, .tabla-wrap table > tr { position: relative; }
.tabla-wrap tr:hover > td:first-child::before {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 2px;
  background: var(--acento); border-radius: 0 2px 2px 0;
}

/* --- 6. Esqueleto mientras busca ------------------------------------
   Tres renglones grises que laten, en vez de un hueco. El hueco hace
   dudar de si se rompió; el esqueleto dice "ya viene". */
.esqueleto { padding: .35rem; }
.esqueleto span {
  display: block; height: 14px; margin: .75rem .7rem; border-radius: 4px;
  background: linear-gradient(90deg, var(--barra-bg) 25%, var(--gris-bg) 37%, var(--barra-bg) 63%);
  background-size: 400% 100%;
  animation: latir 1.4s ease-in-out infinite;
}
.esqueleto span:nth-child(1) { width: 62%; }
.esqueleto span:nth-child(2) { width: 84%; }
.esqueleto span:nth-child(3) { width: 47%; }
@keyframes latir { from { background-position: 100% 0; } to { background-position: 0 0; } }

/* --- 7. Detalles finos ---------------------------------------------- */

/* El texto seleccionado, con el rojo de la marca en vez del azul del
   sistema operativo. */
::selection { background: var(--acento-suave); color: var(--acento-legible); }

/* Barras de desplazamiento delgadas y del color del tema. Las de las
   tablas anchas eran una franja gris del sistema que rompía el diseño. */
* { scrollbar-width: thin; scrollbar-color: var(--borde) transparent; }
.tabla-wrap::-webkit-scrollbar, .buscador-lista::-webkit-scrollbar,
.sidebar::-webkit-scrollbar { width: 8px; height: 8px; }
.tabla-wrap::-webkit-scrollbar-track, .buscador-lista::-webkit-scrollbar-track,
.sidebar::-webkit-scrollbar-track { background: transparent; }
.tabla-wrap::-webkit-scrollbar-thumb, .buscador-lista::-webkit-scrollbar-thumb,
.sidebar::-webkit-scrollbar-thumb { background: var(--borde); border-radius: 4px; }
.tabla-wrap:hover::-webkit-scrollbar-thumb { background: var(--texto-suave); }

/* Números que alinean. Sin esto, "1,473" y "982" no cuadran por la
   derecha y una columna de cifras se lee peor de lo que debería. */
td.num, th.num, .stat-num, .modulo-cuenta strong, .celda-numero,
.segmento-n, .segmento-m, code {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

/* El foco visible, parejo en todo el sistema. Quien navega con el
   teclado tiene que ver dónde está parado. */
:focus-visible {
  outline: 2px solid var(--acento);
  outline-offset: 2px;
  border-radius: 3px;
}

/* Estado vacío: el icono grande y desvanecido detrás del texto, para que
   un módulo sin datos se vea intencional y no roto. */
.estado-vacio { position: relative; }
.estado-vacio svg { width: 40px; height: 40px; opacity: .18; margin-bottom: .4rem; }

@media (prefers-reduced-motion: reduce) {
  .stat-grid > *, .modulos > *, .esqueleto span { animation: none !important; }
  .btn:active, .segmento:active, .modulo:active, .stat-card:active { transform: none; }
  html.cambiando-tema, html.cambiando-tema * { transition: none !important; }
  @view-transition { navigation: none; }
}

@media print {
  .stat-grid > *, .modulos > * { animation: none !important; }
  .tabla-wrap tr:hover > td:first-child::before { display: none; }
}

/* =====================================================================
   BARRA DE CONTEXTO — regresar, dónde estoy, buscar
   =====================================================================
   Antes eran unas migas sueltas encima del título: chiquitas, con
   "Inicio" subrayado y el resto no, y repitiendo abajo lo que ya decía
   el encabezado. Se veía como algo que se olvidó de terminar.

   Ahora es una barra con tres partes claras: el botón de regresar (que
   es lo que de verdad hacía falta), las migas como contexto callado, y
   el buscador a la derecha.
   ------------------------------------------------------------------ */

.barra-contexto {
  display: flex; align-items: center; justify-content: space-between;
  gap: 1rem; margin: 0 0 .9rem; min-height: 30px;
}
.contexto-izq {
  display: flex; align-items: center; gap: .6rem;
  min-width: 0; flex: 1 1 auto;
}

/* --- El botón de regresar ------------------------------------------- */
.btn-volver {
  display: inline-flex; align-items: center; gap: .3rem; flex: none;
  padding: .28rem .6rem .28rem .45rem;
  background: var(--tarjeta); border: 1px solid var(--borde-suave);
  border-radius: 999px; color: var(--texto-suave);
  font-size: .78rem; font-weight: 500; text-decoration: none;
  transition: border-color .16s ease, color .16s ease, background-color .16s ease, transform .12s ease;
}
.btn-volver:hover, .btn-volver:focus-visible {
  border-color: var(--acento); color: var(--acento-legible);
  background: var(--acento-suave); text-decoration: none;
}
.btn-volver:active { transform: translateX(-2px); }
.btn-volver svg { width: 15px; height: 15px; flex: none; }
/* En pantallas chicas se queda solo la flecha: el texto se lo come el
   título, que es lo que importa. */
@media (max-width: 560px) {
  .btn-volver-texto { display: none; }
  .btn-volver { padding: .28rem .45rem; }
}

/* --- Las migas ------------------------------------------------------ */
.migas {
  display: flex; flex-wrap: nowrap; align-items: center; gap: .3rem;
  margin: 0; min-width: 0;
  font-size: .74rem; color: var(--texto-suave);
}
/* Sin subrayado: antes solo "Inicio" salía subrayado y el resto no, y
   esa mezcla es la que se veía descuidada. El color al pasar el ratón
   ya dice que se puede clicar. */
.migas a {
  color: inherit; text-decoration: none; white-space: nowrap;
  padding: .1rem .15rem; border-radius: 4px;
}
.migas a:hover, .migas a:focus-visible {
  color: var(--acento-legible); background: var(--acento-suave); text-decoration: none;
}
.miga-sep { opacity: .38; font-weight: 300; }
.miga-actual {
  color: var(--texto-suave); white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis;
}
/* La miga de la pantalla actual repite lo que dice el título de abajo.
   Se deja para quien navega con lector de pantalla, pero se esconde en
   pantallas angostas, donde el espacio vale más que la redundancia. */
@media (max-width: 720px) {
  .migas { display: none; }
}

/* Con el botón de regresar puesto, el título ya no necesita tanto aire
   arriba. */
.barra-contexto + .barra-titulo,
.barra-contexto + h1 { margin-top: 0; }

/* =====================================================================
   LA BARRA DE FILTROS, RESPIRANDO
   =====================================================================
   Está en 14 pantallas. Era una caja gris con los controles pegados
   entre sí (0.35 rem de separación); en Bitácora, que tiene ocho, se
   leía como un muro. Los cambios son de aire y de material, no de
   estructura: ninguna pantalla cambia una línea.

     - Fondo de tarjeta en vez de gris plano: la barra deja de ser un
       parche y pasa a ser del mismo material que todo lo demás.
     - Más separación, y más entre renglones que entre columnas, para
       que al partirse en dos líneas se vea que son dos.
     - Todos los controles a la misma altura. Antes un select y un campo
       de fecha no medían igual y la fila quedaba dentada.
   ------------------------------------------------------------------ */
.filtros-barra {
  gap: .5rem .45rem;
  padding: .55rem .6rem;
  background: var(--tarjeta);
  border-color: var(--borde-suave);
  border-radius: var(--radio-tarjeta);
  box-shadow: var(--sombra);
}
.filtros-barra select,
.filtros-barra input[type="search"],
.filtros-barra input[type="date"],
.filtros-barra input[type="text"],
.filtros-barra .btn {
  height: 32px;
  line-height: 1.2;
}
/* "DEL" y "AL" entre los campos de fecha: eran mayúsculas en negritas y
   pesaban más que los propios campos. Van en minúsculas y tenues, que
   es lo que son — una preposición, no un encabezado. */
.filtro-sep {
  text-transform: lowercase;
  font-weight: 500;
  font-size: .74rem;
  opacity: .75;
  padding: 0 .1rem;
}

/* La fila de filtros de las pantallas nuevas, igualada a la de siempre. */
.filtros-linea select { height: 32px; }

/* =====================================================================
   ESTADO VACÍO
   =====================================================================
   Seis pantallas lo escribían a mano como un `<td class="tenue">No hay
   datos</td>`: un renglón gris que no dice si falta un permiso, un
   filtro o un paso. Ahora las veintiséis usan el mismo componente.
   ------------------------------------------------------------------ */
.estado-vacio {
  display: flex; flex-direction: column; align-items: center;
  gap: .3rem; padding: 2.2rem 1rem; text-align: center;
}
.estado-vacio svg {
  width: 34px; height: 34px; opacity: .22; margin-bottom: .25rem;
  color: var(--texto-suave);
}
.estado-vacio strong { font-size: .95rem; }
.estado-vacio p {
  margin: 0; max-width: 34rem;
  font-size: .82rem; color: var(--texto-suave); line-height: 1.5;
}
.estado-vacio .btn { margin-top: .6rem; }

/* =====================================================================
   ① LA ACCIÓN PRINCIPAL VA PRIMERO
   =====================================================================
   En Cableado salían "Fuerza / control", "Medir en obra" y "Todos los
   tableros": el botón principal en medio de dos secundarios. En el
   celular se apilan en ese mismo orden, así que lo que hay que hacer
   quedaba en segundo lugar.

   Se resuelve con `order` y no reordenando el HTML de cada pantalla:
   son 26 archivos, y si alguien agrega un botón mañana esta regla lo
   acomoda sola.
   ------------------------------------------------------------------ */
.barra-acciones { display: flex; align-items: center; gap: .4rem; flex-wrap: wrap; }
.barra-acciones > * { order: 2; }
.barra-acciones > .btn-primario { order: 1; }
@media (max-width: 560px) {
  .barra-acciones { width: 100%; }
  .barra-acciones > .btn { flex: 1 1 auto; text-align: center; justify-content: center; }
}

/* =====================================================================
   ③ LAS TRES TIRAS DE LA TABLA, COMO UNA SOLA
   =====================================================================
   Antes de ver el primer dato había cuatro franjas flotando: los
   filtros, el buscador de la tabla, la zona de agrupar y por fin el
   encabezado. Cada una con su propio borde y su propio hueco.

   No se cambia la estructura —eso sería reescribir tabla-interactiva.js
   y sus 700 líneas—: se pegan entre sí para que se lean como una barra
   de herramientas, que es lo que son.
   ------------------------------------------------------------------ */
.tabla-barra {
  margin: 0;
  padding: .5rem .6rem;
  background: var(--barra-bg);
  border-bottom: 1px solid var(--borde-suave);
}
.franja-agrupacion {
  margin: 0;
  border-top: 0;
  border-radius: 0;
  border-left: 0; border-right: 0;
}
.tabla-wrap { border-radius: var(--radio-tarjeta); }

/* =====================================================================
   ④ ANCHO DE LECTURA
   =====================================================================
   En un monitor ancho, un párrafo de explicación cruzaba 1,400 px: al
   llegar al final del renglón el ojo ya no encuentra el principio del
   siguiente. Sesenta y ocho caracteres es el límite cómodo.
   ------------------------------------------------------------------ */
.subtitulo, .alerta p, .estado-vacio p, .contenido > p { max-width: 68ch; }
.alerta { max-width: 96ch; }

/* =====================================================================
   ⑤ LA PRIMERA COLUMNA, FIJA AL DESPLAZAR
   =====================================================================
   En Bitácora hay catorce columnas: al moverse a la derecha se pierde
   de vista de qué renglón se trata, y los números dejan de significar
   algo. La primera columna se queda pegada.

   Va por `data-columnas-fijas]` y no en todas las tablas: una columna
   pegada necesita fondo opaco, y en una tabla que no se desplaza eso es
   una sombra de más sin ninguna ganancia.
   ------------------------------------------------------------------ */
table[data-columnas-fijas] th.fija,
table[data-columnas-fijas] td.fija {
  position: sticky; z-index: 2;
  background: var(--tarjeta);
}
/* La sombra va SOLO en la última fija, no en cada una: con dos
   columnas pegadas, una línea entre ellas las haría ver separadas del
   mismo renglón. */
table[data-columnas-fijas] th.fija-ultima,
table[data-columnas-fijas] td.fija-ultima { box-shadow: 1px 0 0 var(--borde-suave); }
/* Las filas alternas y el hover tienen su propio color: sin esto, la
   columna pegada se vería de otro color que el resto de su fila. */
table[data-columnas-fijas] tr:nth-child(even) td.fija { background: var(--fila-alt); }
table[data-columnas-fijas] tr:hover td.fija { background: var(--fila-lectura); }
table[data-columnas-fijas] tbody tr.fila-liga:hover td.fija { background: var(--fila-hover); }
/* La columna pegada casi siempre ES la zona principal (el material, el
   folio, el nombre). Estas dos repiten lo de ZONAS CLICABLES porque este
   bloque va después en el archivo y, con el mismo peso, gana el último:
   sin esto la columna pegada se quedaba apagada justo cuando el ratón
   estaba encima de ella. */
table[data-columnas-fijas] tbody tr:hover td.fija.zona:hover,
table[data-columnas-fijas] tbody tr.fila-liga:hover:not(:has(td.zona:hover, a.zona-chica:hover)) td.fija.zona-principal {
  background: var(--zona-bg);
  box-shadow: inset 2px 0 0 var(--zona-acento);
}
/* La última fija lleva su propia sombra de separación; al encenderse hay
   que repetirla o la columna pegada pierde el borde contra el resto. */
table[data-columnas-fijas] tbody tr:hover td.fija-ultima.zona:hover,
table[data-columnas-fijas] tbody tr.fila-liga:hover:not(:has(td.zona:hover, a.zona-chica:hover)) td.fija-ultima.zona-principal {
  box-shadow: inset 2px 0 0 var(--zona-acento), 1px 0 0 var(--borde-suave);
}
table[data-columnas-fijas] tbody tr.fila-liga:hover:has(td.zona:hover, a.zona-chica:hover) td.fija:not(.zona:hover) {
  background: var(--fila-lectura);
}
/* El encabezado pegado por arriba y por la izquierda a la vez tiene que
   ganarle a los dos. */
table[data-columnas-fijas] th.fija { z-index: 4; }

/* =====================================================================
   ⑥ QUE SE VEA QUE LA TABLA SIGUE
   =====================================================================
   Una tabla más ancha que la pantalla no avisa que sigue a la derecha.
   Una sombra en el borde sí, y desaparece cuando ya no hay más.
   Las clases las pone y las quita el JavaScript al desplazar.
   ------------------------------------------------------------------ */
.tabla-wrap { position: relative; }
.tabla-wrap::after {
  content: ""; position: absolute; top: 0; bottom: 0; right: 0; width: 22px;
  pointer-events: none; opacity: 0; transition: opacity .2s ease;
  background: linear-gradient(to right, transparent, rgba(0, 0, 0, .13));
}
.tabla-wrap.hay-mas-derecha::after { opacity: 1; }

/* =====================================================================
   ⑦ LAS TABLAS DENSAS, UN TAMAÑO MENOS
   =====================================================================
   Bitácora e Inventario tienen catorce y diecinueve columnas. Con un
   punto menos de letra cabe una columna más sin que nadie tenga que
   entrecerrar los ojos. Solo donde hace falta: bajarle a todas las
   tablas sería empeorar las que ya caben.
   ------------------------------------------------------------------ */
table[data-densa="1"] { font-size: .81rem; }
table[data-densa="1"] th, table[data-densa="1"] td { padding-top: .34rem; padding-bottom: .34rem; }

/* =====================================================================
   ② LOS FILTROS QUE ESTÁN PUESTOS
   =====================================================================
   Una tabla vacía no dice si no hay datos o si hay tres filtros
   encima. Este aviso lo pone el JavaScript leyendo la dirección, así
   que sale en las catorce pantallas sin tocar ninguna.
   ------------------------------------------------------------------ */
.filtros-puestos {
  display: inline-flex; align-items: center; gap: .4rem;
  margin-left: auto; padding: .2rem .3rem .2rem .6rem;
  background: var(--acento-suave); border-radius: 999px;
  font-size: .74rem; color: var(--acento-legible); white-space: nowrap;
}
.filtros-puestos a {
  display: inline-flex; align-items: center; gap: .2rem;
  padding: .12rem .5rem; border-radius: 999px;
  background: var(--tarjeta); color: var(--texto-suave);
  text-decoration: none; font-weight: 500;
}
.filtros-puestos a:hover { color: var(--acento-legible); text-decoration: none; }

@media print {
  .tabla-wrap::after { display: none; }
  table[data-columnas-fijas] th.fija,
  table[data-columnas-fijas] td.fija { position: static; box-shadow: none; }
  .filtros-puestos { display: none; }
}

/* Con las franjas ya fuera del contenedor que se desplaza, se pegan
   por arriba de la tabla para que sigan leyéndose como una sola pieza. */
.tabla-barra {
  border: 1px solid var(--borde-suave); border-bottom: 0;
  border-radius: var(--radio-tarjeta) var(--radio-tarjeta) 0 0;
}
.tabla-barra + .franja-agrupacion,
.tabla-barra + .tabla-wrap { border-top-left-radius: 0; border-top-right-radius: 0; }
.franja-agrupacion {
  border: 1px solid var(--borde-suave); border-top: 0; border-bottom: 0;
}
.franja-agrupacion + .tabla-wrap { border-top-left-radius: 0; border-top-right-radius: 0; }

/* =====================================================================
   ZONA PARA SOLTAR ARCHIVOS
   =====================================================================
   Reemplaza al "Seleccionar archivo · Ningún archivo seleccionado" del
   navegador, que es un control del sistema operativo: distinto en cada
   navegador y sin nada que ver con el resto del sitio.

   El input de verdad va ENCIMA, invisible y del tamaño completo. Por eso
   el clic y el arrastrar-y-soltar funcionan solos, sin JavaScript.
   ------------------------------------------------------------------ */
.soltar {
  position: relative;
  display: block;
  border: 1.5px dashed var(--borde);
  border-radius: var(--radio-tarjeta);
  background: var(--campo-bg);
  transition: border-color .18s ease, background-color .18s ease, transform .14s ease;
}
.soltar:hover { border-color: var(--acento); background: var(--acento-suave); }

/* El input, del tamaño de la zona y transparente. NO con display:none:
   Chrome se niega a enfocar un campo obligatorio invisible y el
   formulario se traba sin decir por qué. */
.soltar input[type="file"] {
  position: absolute; inset: 0;
  width: 100%; height: 100%; margin: 0; padding: 0;
  opacity: 0; cursor: pointer; z-index: 2;
}
.soltar:focus-within {
  border-color: var(--acento);
  box-shadow: 0 0 0 3px var(--acento-suave);
}

.soltar-cara {
  display: flex; align-items: center; gap: .7rem;
  padding: .85rem 1rem; min-height: 62px;
  pointer-events: none;                 /* los clics son del input de arriba */
}
.soltar-icono {
  flex: none; display: grid; place-items: center;
  width: 34px; height: 34px; border-radius: 9px;
  background: var(--gris-bg); color: var(--texto-suave);
  transition: background-color .18s ease, color .18s ease;
}
.soltar-icono svg { width: 18px; height: 18px; }
.soltar:hover .soltar-icono { background: var(--acento-suave); color: var(--acento-legible); }
.soltar-texto {
  flex: 1 1 auto; min-width: 0;
  font-size: .85rem; line-height: 1.35;
}
.soltar-texto b { display: block; font-weight: 600; }
.soltar-nombre { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.soltar-peso, .soltar-tipos { font-size: .74rem; color: var(--texto-suave); }
.soltar-tipos { flex: none; }

/* Mientras se arrastra algo encima. El salto es chico a propósito: es
   para confirmar que sí va a caer ahí, no para llamar la atención. */
.soltar.soltando {
  border-color: var(--acento); border-style: solid;
  background: var(--acento-suave);
  transform: scale(1.006);
}
.soltar.soltando .soltar-icono { background: var(--acento); color: #fff; }

/* Ya con archivo elegido: borde sólido y verde, que es lo que dice
   "esto ya está" sin tener que escribirlo. */
.soltar.con-archivo {
  border-style: solid; border-color: var(--verde);
  background: var(--tarjeta);
}
.soltar.con-archivo .soltar-icono { background: var(--sem-verde-bg); color: var(--sem-verde-tx); }
.soltar-quitar {
  flex: none; pointer-events: auto; position: relative; z-index: 3;
  padding: .25rem .6rem; border-radius: 999px;
  border: 1px solid var(--borde-suave); background: var(--tarjeta);
  color: var(--texto-suave); font: inherit; font-size: .74rem; cursor: pointer;
  transition: color .16s ease, border-color .16s ease;
}
.soltar-quitar:hover { color: var(--acento-legible); border-color: var(--acento); }

/* En el bloque de adjuntar de las cotizaciones el campo iba con letra
   chica; ahora la zona manda y esa regla estorbaba. */
details.doc-adjuntar .soltar input[type="file"] { width: 100%; font-size: inherit; }

@media (prefers-reduced-motion: reduce) {
  .soltar, .soltar.soltando { transition: none; transform: none; }
}
@media print { .soltar { display: none; } }

/* =====================================================================
   LOS DEMÁS CONTROLES DEL NAVEGADOR
   =====================================================================
   Misma idea que el campo de archivo: las casillas, los interruptores y
   los selectores venían con el aspecto del sistema operativo, azul de
   Windows incluido, encima de un sitio que no tiene nada azul.
   ------------------------------------------------------------------ */

/* Una línea y todas las casillas, radios y barras toman el color de la
   marca, en los dos temas. */
input[type="checkbox"], input[type="radio"], input[type="range"], progress {
  accent-color: var(--acento);
}
input[type="checkbox"], input[type="radio"] {
  width: 16px; height: 16px; cursor: pointer;
  transition: transform .12s ease;
}
input[type="checkbox"]:active, input[type="radio"]:active { transform: scale(.9); }

/* La flecha de los selectores: la del sistema es de otro tamaño y otro
   color en cada navegador. Esta es la misma en todos y sigue el tema. */
select:not([multiple]) {
  appearance: none; -webkit-appearance: none; -moz-appearance: none;
  padding-right: 1.9rem;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%237a8899' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9.5 12 15.5 18 9.5'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right .5rem center;
  background-size: 15px 15px;
}
select:not([multiple]):disabled { opacity: .6; }

/* El icono del calendario, del color del texto y no del sistema. */
input[type="date"]::-webkit-calendar-picker-indicator {
  opacity: .55; cursor: pointer; transition: opacity .16s ease;
}
input[type="date"]:hover::-webkit-calendar-picker-indicator { opacity: 1; }

/* =====================================================================
   EL INDICADOR DEL MENÚ, DESLIZÁNDOSE
   =====================================================================
   La barrita roja que marca en qué pantalla estás saltaba de golpe al
   cambiar de página, porque cada página es una carga nueva.

   Dándole nombre propio, el navegador la reconoce como EL MISMO
   elemento en las dos páginas y la desliza de una a otra durante el
   cruce que ya hacía `@view-transition`. Son tres líneas y es lo que
   hace que el menú se sienta una sola cosa y no una imagen que se
   redibuja.
   ------------------------------------------------------------------ */
/* (La regla amplia se quitó: ponía el nombre de transición también en
   la copia fijada, y dos elementos con el mismo nombre cancelan la
   transición entera. La versión acotada a .sidebar-nav está más
   abajo.) */
::view-transition-group(menu-marca) { animation-duration: .32s; }
::view-transition-old(menu-marca), ::view-transition-new(menu-marca) { animation: none; }

/* EL TÍTULO NO LLEVA NOMBRE, Y ES A PROPÓSITO.
   Se probó y se quitó: el mismo h1 tiene cajas muy distintas según la
   pantalla — en Obras va suelto en .contenido y mide 1032 px de ancho;
   en Clientes va dentro de .barra-titulo, que es flex, y mide 86. El
   navegador estiraba "Obras" de 1032 a 86 mientras cruzaba a
   "Clientes", y el texto se veía aplastado.

   Se puede tapar con object-fit en los snapshots, pero sería maquillar
   una incoherencia del marcado. Sin nombre, el título simplemente se
   funde con el resto de la página, que es lo que se quería.

   La barrita del menú sí lleva nombre porque sí es la misma caja en
   todas las pantallas: 3 px de ancho, en el mismo lugar. */

/* =====================================================================
   BARRA DE GUARDADO QUE SE QUEDA A LA VISTA
   =====================================================================
   En los formularios largos —importar precios, editar una estimación—
   el botón de guardar queda al final de tres pantallas de altura: hay
   que bajar hasta el fondo para guardar y volver a subir para seguir
   revisando. Con `position: sticky` el bloque de botones se queda
   pegado abajo mientras el formulario siga arriba.

   Va por `data-guardar-fijo="1"`, no en todos: en un formulario corto
   una barra pegada es una franja de más tapando contenido.
   ------------------------------------------------------------------ */
[data-guardar-fijo="1"] {
  position: sticky; bottom: 0; z-index: 6;
  display: flex; align-items: center; gap: .6rem; flex-wrap: wrap;
  margin: 1rem 0 0; padding: .7rem .9rem;
  background: var(--tarjeta);
  border: 1px solid var(--borde-suave);
  border-radius: var(--radio-tarjeta);
  box-shadow: 0 -4px 16px rgba(0, 0, 0, .07);
}

/* =====================================================================
   TEXTOS QUE CRECEN SOLOS
   =====================================================================
   Un área de texto de tres renglones con quince adentro obliga a
   desplazarse dentro de un cuadrito. Crece con lo que se escribe, hasta
   un tope para no empujar el resto de la página fuera de la vista.
   ------------------------------------------------------------------ */
textarea { transition: height .12s ease; max-height: 60vh; }

/* =====================================================================
   DETALLES QUE SE NOTAN SIN VERSE
   ------------------------------------------------------------------ */

/* El cursor de texto y la selección dentro de los campos, del color de
   la marca. Es de esas cosas que nadie nombra y todos sienten. */
input, textarea, select { caret-color: var(--acento); }

/* Los enlaces del contenido: el subrayado nace del texto en vez de
   estar pegado siempre. */
.contenido p a:not(.btn), .contenido td a:not(.btn) {
  text-decoration-thickness: 1px;
  text-underline-offset: 2px;
  transition: text-decoration-color .16s ease;
}

/* Una tarjeta que se puede clicar lo dice al pasar el ratón. */
.tarjeta { transition: box-shadow .18s ease, border-color .18s ease; }

/* El botón principal, con un poco de profundidad. Nada de degradados:
   una sombra del propio color, que es lo que lo separa del fondo sin
   ensuciarlo. */
.btn-primario { box-shadow: 0 1px 2px rgba(0, 0, 0, .12); }
.btn-primario:hover { box-shadow: 0 2px 8px var(--acento-suave); }

@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(menu-marca) { animation-duration: 1ms; }
  textarea { transition: none; }
}
@media print {
  [data-guardar-fijo="1"] { position: static; box-shadow: none; }
}

/* =====================================================================
   LOS FORMULARIOS DE ALTA, EN SERIO
   =====================================================================
   `form.form-simple` estaba en `max-width: 480px` con campos de 0.82 rem
   y 0.3 rem de relleno. En un monitor de 1280 px eso es una tira angosta
   pegada a la izquierda, con siete campos uno debajo de otro y dos
   tercios de la pantalla vacíos. Se ve chico porque ES chico.

   Son 16 pantallas —dar de alta y editar insumos, obras, clientes,
   plantas, almacenes, proveedores y usuarios— y todas son formularios
   planos: etiqueta, campo, ayuda, etiqueta, campo… Por eso se pueden
   arreglar todas a la vez desde aquí, sin tocar ninguna.

   Los campos se agrupan de tres en tres (etiqueta + control + ayuda) en
   un `.campo` que arma el JavaScript, y esos `.campo` se acomodan en dos
   columnas. Lo que no es un campo —el botón de guardar, un aviso— ocupa
   el ancho completo.
   ------------------------------------------------------------------ */
form.form-simple {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1rem 1.4rem;
  align-items: start;
  max-width: 62rem;
  padding: 1.6rem;
  border-color: var(--borde-suave);
  border-radius: var(--radio-tarjeta);
  box-shadow: var(--sombra);
}
/* Lo que el JavaScript no agrupó ocupa el renglón entero: el token CSRF,
   los avisos, el botón de guardar. */
form.form-simple > *:not(.campo) { grid-column: 1 / -1; }
form.form-simple .campo { display: block; min-width: 0; }
/* Un campo que pide una frase larga necesita el renglón completo. */
form.form-simple .campo-ancho { grid-column: 1 / -1; }

form.form-simple label {
  margin: 0 0 .3rem;
  font-size: .8rem;
  font-weight: 600;
  color: var(--texto);
}
form.form-simple input[type="text"],
form.form-simple input[type="password"],
form.form-simple input[type="number"],
form.form-simple input[type="email"],
form.form-simple input[type="tel"],
form.form-simple input[type="date"],
form.form-simple select,
form.form-simple textarea {
  padding: .55rem .7rem;
  font-size: .9rem;
  border-radius: var(--radio);
  border-color: var(--borde);
  transition: border-color .16s ease, box-shadow .16s ease;
}
form.form-simple input:focus,
form.form-simple select:focus,
form.form-simple textarea:focus {
  outline: none;
  border-color: var(--acento);
  box-shadow: 0 0 0 3px var(--acento-suave);
}
form.form-simple .ayuda-campo {
  margin: .35rem 0 0;
  font-size: .76rem;
  line-height: 1.45;
}

/* El botón de guardar, separado de los campos por una línea que cruza
   el formulario entero: cierra el bloque en vez de quedar colgando
   después del último campo. La línea va en el contenedor y no en el
   botón — puesta en el botón medía lo que el botón y se veía como un
   subrayado suelto. */
form.form-simple > .form-botones {
  margin-top: .4rem;
  padding-top: 1.1rem;
  border-top: 1px solid var(--borde-suave);
}

/* Entrar al sistema son dos campos: en dos columnas se vería absurdo. */
form.form-simple.form-angosta {
  grid-template-columns: 1fr;
  max-width: 22rem;
  box-shadow: none;
}
form.form-simple.form-angosta .campo-ancho { grid-column: auto; }

/* Una columna en cuanto no quepan dos cómodas. El punto no es el celular:
   a menos de 760 px dos columnas ya aprietan en cualquier pantalla. */
@media (max-width: 760px) {
  form.form-simple { grid-template-columns: 1fr; padding: 1.1rem; gap: .9rem; }
  form.form-simple .campo-ancho { grid-column: auto; }
}

.form-botones { display: flex; gap: .6rem; align-items: center; flex-wrap: wrap; }

/* =====================================================================
   RITMO — jerarquía, aire y materiales parejos
   =====================================================================
   Hasta aquí se arreglaron piezas sueltas. Esto es lo que las hace
   verse de la misma familia: una sola escala de títulos, un solo radio,
   una sola sombra, y el mismo aire entre bloques en todas las pantallas.
   Va al final del archivo a propósito: gana por orden de cascada sin
   tener que tocar las reglas viejas.
   ------------------------------------------------------------------ */

/* --- Títulos ---------------------------------------------------------
   Había h1 de 1.5 rem junto a h2 de 1.4: casi el mismo tamaño, así que
   no se leía cuál manda. Ahora hay un salto de verdad, y el espacio de
   ARRIBA de cada título es mayor que el de abajo — es lo que agrupa el
   título con su contenido en vez de dejarlo flotando entre los dos. */
.contenido h1 { font-size: 1.65rem; line-height: 1.15; letter-spacing: -.02em; font-weight: 700; }
.contenido h2 { font-size: 1.08rem; line-height: 1.25; letter-spacing: -.01em; font-weight: 650; margin: 1.9rem 0 .55rem; }
.contenido h3 { font-size: .94rem; font-weight: 600; margin: 1.3rem 0 .4rem; }
.contenido h2:first-child, .contenido h3:first-child { margin-top: 0; }
.contenido > .subtitulo { margin-top: -.1rem; line-height: 1.5; }

/* --- Un solo material para todo lo que es "una caja" ----------------- */
.tarjeta, .stat-card, .tabla-wrap, .modulo, form.form-simple, .filtros-barra {
  border-radius: var(--radio-tarjeta);
}
.tarjeta {
  border: 1px solid var(--borde-suave);
  box-shadow: var(--sombra);
  padding: 1.15rem 1.25rem;
}
.tarjeta + .tarjeta { margin-top: .8rem; }

/* --- Avisos: se distinguen por TRATAMIENTO, no solo por color --------
   El rojo de marca y el rojo de error son el mismo rojo. Lo que separa
   un error de un aviso no puede ser el tono: es la barra de la
   izquierda, que solo llevan los que piden acción. */
.alerta {
  border-radius: var(--radio-tarjeta);
  padding: .8rem 1rem;
  line-height: 1.5;
  border-left-width: 3px;
  border-left-style: solid;
}
.alerta strong { font-weight: 650; }

/* --- Tablas ---------------------------------------------------------- */
.tabla-wrap table th {
  font-size: .68rem;
  letter-spacing: .07em;
  text-transform: uppercase;
  font-weight: 600;
  color: var(--texto-suave);
  padding-top: .55rem; padding-bottom: .55rem;
}
.tabla-wrap table td { padding-top: .48rem; padding-bottom: .48rem; }
/* Sin rayas: la fila se distingue por la línea de abajo, que es más
   callado y no compite con los datos. */
.tabla-wrap table tr:last-child td { border-bottom: 0; }

/* --- Etiquetas de estado --------------------------------------------- */
.badge, .etiqueta-clase {
  border-radius: 999px;
  padding: .12rem .55rem;
  font-size: .7rem;
  font-weight: 600;
  letter-spacing: .01em;
  white-space: nowrap;
}

/* --- Números grandes -------------------------------------------------- */
.stat-num { letter-spacing: -.03em; line-height: 1.05; }
.stat-etiqueta { font-weight: 500; }
.stat-card { padding: .85rem 1rem; }

/* --- Aire entre bloques ---------------------------------------------- */
.stat-grid { gap: .6rem; margin-bottom: 1.3rem; }
.tabla-wrap { margin-top: 0; }
.contenido > .alerta { margin: .9rem 0; }

/* --- Botones ---------------------------------------------------------- */
.btn {
  border-radius: 8px;
  font-weight: 600;
  letter-spacing: .005em;
}
.btn-chico { padding: .32rem .7rem; font-size: .78rem; }
.btn-secundario { background: var(--tarjeta); border: 1px solid var(--borde); }
.btn-secundario:hover { border-color: var(--acento); color: var(--acento-legible); }

/* --- Enlaces del contenido --------------------------------------------
   Esto decía: "sin subrayado se lee mucho más limpio en una tabla, pero
   entonces el COLOR es lo único que dice que se puede clicar; si se
   quitan los dos, un número que lleva a algún lado se ve igual que uno
   que no". Era cierto mientras el enlace estuviera solo.

   Ya no lo está: dentro de una zona lo que dice que se puede clicar es
   que la celda se ilumine al pasar por encima (ver ZONAS CLICABLES), y
   ahí el color de enlace sobra — es justo lo que hacía ver las tablas
   como una página de hace veinte años.

   La regla se queda para los enlaces que NO son zona: los de las tablas
   que viven fuera de `.tabla-wrap`, donde sigue sin haber nada más que
   avise. */
.contenido td a:not(.btn):not(.zona-chica) {
  color: var(--enlace); text-decoration: none; font-weight: 500;
}
.contenido td a:not(.btn):not(.zona-chica):hover { text-decoration: underline; }
.contenido td.zona a:not(.btn) {
  color: inherit; font-weight: inherit; text-decoration: none;
}

@media print {
  .contenido h1 { font-size: 1.3rem; }
  .tarjeta, .stat-card { box-shadow: none; }
}

/* =====================================================================
   EL BOTÓN DE AGREGAR, JUNTO AL TÍTULO
   =====================================================================
   `.barra-acciones` tenía `margin-left: auto`, que lo empujaba al borde
   derecho de la pantalla. Con un título corto como "Clientes" en un
   monitor de 1280 px, el botón "+ Nuevo cliente" acababa a mil píxeles
   del título, contra el filo — y el ojo, que venía leyendo de
   izquierda a derecha, ya lo había pasado de largo.

   Peor: no era parejo. Las pantallas que NO usan `.barra-titulo` (obras,
   por ejemplo) dejaban el botón suelto a la izquierda, debajo del
   subtítulo. Así que la misma acción aparecía en dos lugares distintos
   según la pantalla.

   Ahora va junto al título en las dos. La acción principal se lee
   inmediatamente después de lo que uno vino a hacer.
   ------------------------------------------------------------------ */
.barra-acciones { margin-left: 0; }
.barra-titulo { gap: .55rem 1rem; }

/* Con el botón ya pegado al título, el subtítulo de abajo no debe
   empezar bajo el botón sino bajo el título. */
.barra-titulo + .subtitulo { margin-top: .15rem; }

/* En pantalla angosta la acción principal ocupa el renglón completo:
   pegada al título se apretarían las dos cosas. */
@media (max-width: 560px) {
  .barra-titulo { flex-direction: column; align-items: stretch; }
  .barra-titulo h1 { margin-bottom: .1rem; }
}

/* =====================================================================
   LOS IMPORTES NO SE CORTAN
   =====================================================================
   `.stat-grid` estaba en `minmax(150px)`, un ancho calculado a mano para
   que cupiera "$32,303,555". Después se le subió el relleno de la
   tarjeta de .9rem a 1rem sin volver a medir, y con eso el hueco útil
   bajó de 122 a 118 px: los importes de ocho cifras empezaron a salir
   cortados en Contratos.

   Se deja de adivinar el ancho. La tarjeta se declara contenedor y el
   número se mide contra ELLA: crece hasta 1.55 rem si cabe y se achica
   si no. Así deja de importar cuánto relleno tenga la tarjeta, cuántas
   cifras traiga el importe o en qué pantalla se vea.
   ------------------------------------------------------------------ */
.stat-grid { grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); }
.stat-card { container-type: inline-size; }
.stat-num {
  /* 6.6 = 11 caracteres × 0.6 em de ancho por dígito en Inter tabular.
     Es el importe más largo que puede salir: $33,636,072. */
  font-size: min(1.55rem, calc((100cqw - 2rem) / 6.6));
  white-space: nowrap;
}
.stat-card.stat-destacada .stat-num { font-size: min(1.85rem, calc((100cqw - 2rem) / 6.2)); }

/* Sin soporte de contenedores, se cae a un tamaño fijo que sí cabe. */
@supports not (container-type: inline-size) {
  .stat-num { font-size: 1.3rem; }
  .stat-card.stat-destacada .stat-num { font-size: 1.5rem; }
}

/* =====================================================================
   LAS CIFRAS, SIN CAJA
   =====================================================================
   Las tarjetas con marco, sombra y barra de color arriba hacían que la
   pantalla se leyera como un tablero de widgets: diez cajas iguales
   donde ningún número destaca porque todos gritan lo mismo.

   Ahora las cifras se paran directamente sobre el fondo, separadas por
   una línea fina. Se leen igual de rápido —el tamaño y el peso siguen
   marcando la jerarquía— y la pantalla se calla.

   Se conserva el mismo marcado (`.stat-grid`, `.stat-card`…), así que
   ninguna de las veintitantas pantallas que las usan cambia una línea.
   ------------------------------------------------------------------ */
.stat-grid {
  gap: 0;
  margin-bottom: 1.5rem;
  border-top: 1px solid var(--borde-suave);
  border-bottom: 1px solid var(--borde-suave);
}
.stat-card {
  background: none;
  border: 0;
  border-left: 1px solid var(--borde-suave);
  border-radius: 0;
  box-shadow: none;
  padding: .85rem 1rem;
}
/* La primera de cada renglón no lleva línea a la izquierda. El truco es
   mirar quién empieza pegado al borde del contenedor. */
.stat-card:first-child { border-left: 0; }
.stat-card:hover { background: var(--barra-bg); transform: none; box-shadow: none; }

/* La alerta se marca con la cifra en rojo y una barra corta arriba, no
   con un marco entero: lo que pide atención es el NÚMERO. */
.stat-card.stat-alerta { border-top: 0; position: relative; }
.stat-card.stat-alerta::before {
  content: ""; position: absolute; top: 0; left: 1rem; width: 22px; height: 2px;
  background: var(--acento); border-radius: 0 0 2px 2px;
}
.stat-card.stat-destacada {
  border-top: 0; background: none; position: relative;
}
.stat-card.stat-destacada::before {
  content: ""; position: absolute; top: 0; left: 1rem; width: 22px; height: 2px;
  background: var(--amarillo); border-radius: 0 0 2px 2px;
}
.stat-etiqueta { font-size: .76rem; color: var(--texto-suave); margin-top: .1rem; }
.stat-nota { font-size: .7rem; margin-top: .2rem; }

/* Cuando la cifra es un enlace (la tira del Inicio), sigue siéndolo sin
   parecer un botón. */
a.stat-card { text-decoration: none; color: inherit; }
a.stat-card:hover .stat-num { color: var(--acento-legible); }

@media (max-width: 560px) {
  .stat-grid { border: 0; }
  .stat-card { border-left: 0; border-bottom: 1px solid var(--borde-suave); padding-left: 0; }
  .stat-card.stat-alerta::before, .stat-card.stat-destacada::before { left: 0; }
}
@media print {
  .stat-card:hover { background: none; }
}

/* =====================================================================
   EL FLUJO DEL DINERO
   =====================================================================
   Contratado, estimado, facturado y cobrado no son cuatro cosas: son
   las etapas por las que pasa un mismo peso. Cuatro cajas iguales
   esconden justo eso. Una barra donde cada etapa vive DENTRO de la
   anterior lo dice sin una palabra, y las dos brechas —lo que falta por
   estimar y lo que falta por cobrar— salen solas de los huecos.
   ------------------------------------------------------------------ */
.flujo { max-width: 58rem; margin: 0 0 1.5rem; }
.flujo-barra {
  position: relative; height: 32px; border-radius: 8px; overflow: hidden;
  background: var(--barra-bg); border: 1px solid var(--borde-suave);
}
.flujo-barra i { position: absolute; top: 0; bottom: 0; left: 0; display: block; }
.flujo-est { background: var(--azul-medio); opacity: .30; }
.flujo-fac { background: var(--azul-medio); opacity: .62; }
.flujo-cob { background: var(--verde); }
.flujo-pie {
  display: flex; flex-wrap: wrap; gap: .15rem 1.6rem;
  margin-top: .6rem; font-size: .78rem;
}
.flujo-pie div { display: flex; align-items: baseline; gap: .4rem; }
.flujo-pie i { width: 9px; height: 9px; border-radius: 3px; flex: none; }
.flujo-pie b { font-variant-numeric: tabular-nums; font-weight: 650; }
.flujo-pie span { color: var(--texto-suave); }
.flujo-brechas { display: flex; gap: 2.4rem; flex-wrap: wrap; margin-top: 1rem; }
.flujo-brechas div { font-size: .78rem; color: var(--texto-suave); }
.flujo-brechas b {
  display: block; font-size: 1.4rem; font-weight: 700;
  letter-spacing: -.03em; line-height: 1.1; color: var(--texto);
  font-variant-numeric: tabular-nums;
}
.flujo-brechas .urge b { color: var(--acento-legible); }

/* =====================================================================
   NAVEGACIÓN DE DOS NIVELES
   =====================================================================
   Arriba las áreas, a la izquierda las pantallas del área actual.

   Reemplaza a la columna única de 34 renglones, que no cabía de alto y
   había que desplazar. Ninguna área pasa de 6 pantallas, así que la
   barra lateral ya no necesita desplazarse nunca.
   ------------------------------------------------------------------ */

/* --- La barra de áreas ----------------------------------------------- */
.barra-areas {
  position: sticky; top: 0; z-index: 30;
  display: flex; align-items: center; gap: .9rem;
  height: 52px; padding: 0 .9rem;
  background: var(--azul-oscuro);
  box-shadow: 0 1px 0 rgba(255, 255, 255, .06), 0 2px 12px rgba(0, 0, 0, .16);
  view-transition-name: barra-areas;
}
.areas-marca { flex: none; display: flex; align-items: center; }
.areas-marca img { height: 19px; width: auto; display: block; filter: brightness(0) invert(1); }
.areas-menu-btn {
  display: none; flex: none;
  background: none; border: 0; color: #dce3ea;
  font-size: 1.25rem; line-height: 1; padding: .3rem .45rem;
  border-radius: 7px; cursor: pointer;
}
.areas-menu-btn:hover { background: rgba(255, 255, 255, .12); }

.areas {
  display: flex; align-items: center; gap: .1rem;
  flex: 1 1 auto; min-width: 0;
  overflow-x: auto; overflow-y: hidden;
  scrollbar-width: none;
}
.areas::-webkit-scrollbar { display: none; }

.area {
  position: relative; flex: none;
  display: flex; align-items: center; gap: .42rem;
  padding: .46rem .72rem;
  border-radius: 8px;
  color: #b9c4d0; text-decoration: none;
  font-size: .83rem; font-weight: 500; white-space: nowrap;
  transition: color .16s ease, background-color .16s ease;
}
.area svg { width: 15px; height: 15px; opacity: .68; transition: opacity .16s ease; }
.area:hover { color: #fff; background: rgba(255, 255, 255, .09); text-decoration: none; }
.area:hover svg { opacity: 1; }
.area.activa { color: #fff; font-weight: 650; }
.area.activa svg { opacity: 1; }

/* La barrita del área activa. Lleva nombre propio, así que el navegador
   la reconoce como el MISMO elemento en las dos páginas y la DESLIZA de
   un área a otra durante el cruce — en vez de saltar. Es lo que hace
   que la barra se sienta una sola cosa y no una imagen que se redibuja. */
.area.activa::after {
  content: ""; position: absolute; left: .72rem; right: .72rem; bottom: -1px;
  height: 2.5px; border-radius: 3px 3px 0 0;
  background: var(--acento);
  view-transition-name: area-marca;
}
::view-transition-group(area-marca) { animation-duration: .34s; }
::view-transition-old(area-marca), ::view-transition-new(area-marca) { animation: none; }

/* --- La derecha de la barra: buscar y quién eres --------------------- */
.areas-derecha { flex: none; display: flex; align-items: center; gap: .5rem; }
.barra-areas .btn-buscar {
  background: rgba(255, 255, 255, .09);
  border-color: rgba(255, 255, 255, .14);
  color: #b9c4d0;
  padding: .34rem .6rem;
}
.barra-areas .btn-buscar:hover { border-color: var(--acento); color: #fff; }
.barra-areas .btn-buscar kbd {
  background: rgba(255, 255, 255, .1); border-color: rgba(255, 255, 255, .18); color: #dce3ea;
}

.usuario-chip {
  display: flex; align-items: center; gap: .5rem;
  padding: .22rem .3rem .22rem .28rem;
  border-radius: 999px;
  background: rgba(255, 255, 255, .07);
}
.usuario-inicial {
  flex: none; display: grid; place-items: center;
  width: 26px; height: 26px; border-radius: 50%;
  background: var(--acento); color: #fff;
  font-size: .8rem; font-weight: 700; line-height: 1;
}
.usuario-datos { display: flex; flex-direction: column; line-height: 1.15; }
.usuario-datos strong { color: #eef2f6; font-size: .78rem; font-weight: 600; }
.usuario-datos > span { color: #8b9aa9; font-size: .66rem; }
.usuario-salir {
  flex: none; display: grid; place-items: center;
  width: 26px; height: 26px; border-radius: 50%;
  color: #8b9aa9; text-decoration: none;
  transition: color .16s ease, background-color .16s ease;
}
.usuario-salir svg { width: 15px; height: 15px; }
.usuario-salir:hover { color: #fff; background: rgba(255, 255, 255, .12); }

/* --- La barra lateral, ahora corta ----------------------------------- */
.app { display: flex; align-items: stretch; min-height: calc(100vh - 52px); }
.sidebar {
  width: 202px; flex: none;
  background: var(--tarjeta);
  border-right: 1px solid var(--borde-suave);
  position: sticky; top: 52px;
  height: calc(100vh - 52px);
  display: flex; flex-direction: column;
  padding: .85rem .55rem;
  overflow-y: auto;
  view-transition-name: menu-lateral;
}
.sidebar-tit {
  font-size: .64rem; letter-spacing: .1em; text-transform: uppercase;
  color: var(--texto-suave); font-weight: 600;
  padding: 0 .6rem .4rem;
}
.sidebar-nav { display: flex; flex-direction: column; gap: 1px; }

.sidebar-link {
  position: relative;
  display: flex; align-items: center; gap: .55rem;
  padding: .44rem .6rem;
  border-radius: 8px;
  color: var(--texto); text-decoration: none;
  font-size: .84rem; font-weight: 500;
  transition: background-color .15s ease, color .15s ease, padding-left .18s cubic-bezier(.22,.8,.3,1);
}
.sidebar-link svg { flex: none; width: 16px; height: 16px; opacity: .55; transition: opacity .15s ease; }
.sidebar-link > span:first-of-type { flex: 1 1 auto; min-width: 0; }
.sidebar-link:hover {
  background: var(--barra-bg); color: var(--texto); text-decoration: none;
}
.sidebar-link:hover svg { opacity: .85; }
.sidebar-link.activo {
  background: var(--acento-suave); color: var(--acento-legible); font-weight: 650;
  padding-left: .9rem;
}
.sidebar-link.activo svg { opacity: 1; }
.sidebar-link.activo::before {
  content: ""; position: absolute; left: .3rem; top: 50%;
  width: 3px; height: 1.1em; transform: translateY(-50%);
  border-radius: 2px; background: var(--acento);
}
/* El nombre de transición va SOLO en la lista del área.
   Si la pantalla actual además está fijada, sale dos veces marcada
   como activa — y dos elementos con el mismo `view-transition-name`
   hacen que el navegador **cancele la transición entera**, sin decir
   nada. Se pierde el deslizamiento en todo el sistema por un caso
   que parece de adorno. */
.sidebar-nav .sidebar-link.activo::before { view-transition-name: menu-marca; }
.sidebar-cuenta {
  flex: none; font-size: .68rem; color: var(--texto-suave);
  background: var(--barra-bg); border-radius: 999px; padding: .05rem .4rem;
}

/* --- Frecuentes ------------------------------------------------------ */
/* --- Fijadas ---------------------------------------------------------
   Las pantallas que cada quien decide tener siempre a la mano. El
   alfiler aparece al pasar el ratón sobre cualquier renglón; en las ya
   fijadas se queda visible, para que se vea de un golpe cuáles lo están
   y cómo quitarlas. */
.sidebar-fijadas { margin-bottom: .9rem; }
.sidebar-fijadas-items { display: flex; flex-direction: column; gap: 1px; }
.sidebar-fijadas + .sidebar-nav { padding-top: .85rem; border-top: 1px solid var(--borde-suave); }

/* De qué área viene una fijada. Va tenue y chico: solo hace falta cuando
   dos pantallas de áreas distintas se llaman parecido. */
.sidebar-area-de {
  display: block; width: 100%; order: 3;
  padding-left: 2.15rem; margin-top: -.1rem;
  font-size: .64rem; color: var(--texto-suave); font-weight: 400;
}
.sidebar-fijadas .sidebar-link { flex-wrap: wrap; padding-top: .38rem; padding-bottom: .34rem; }

.sidebar-fijar {
  flex: none; display: grid; place-items: center;
  width: 22px; height: 22px; margin: -.2rem -.25rem -.2rem 0;
  border: 0; border-radius: 6px; background: none; padding: 0;
  color: var(--texto-suave); cursor: pointer;
  opacity: 0; transform: scale(.8);
  transition: opacity .14s ease, transform .14s ease, color .14s ease, background-color .14s ease;
}
.sidebar-fijar svg { width: 13px; height: 13px; }
.sidebar-link:hover .sidebar-fijar,
.sidebar-fijar:focus-visible { opacity: 1; transform: none; }
.sidebar-fijar:hover { background: var(--barra-bg); color: var(--acento-legible); }
.sidebar-fijar.fijada {
  opacity: 1; transform: none; color: var(--acento-legible);
}
.sidebar-fijar.fijada svg { fill: currentColor; }
/* Al llegar al tope, el alfiler se sacude en vez de no hacer nada: un
   botón que no responde se siente roto. */
.sidebar-fijar.tope { animation: sacudir .4s ease; color: var(--acento); }
@keyframes sacudir {
  0%,100% { transform: none; }
  25% { transform: translateX(-2px); }
  75% { transform: translateX(2px); }
}
@media (prefers-reduced-motion: reduce) {
  .sidebar-fijar { transition: none; }
  .sidebar-fijar.tope { animation: none; }
}
/* En pantalla táctil no hay "pasar el ratón": el alfiler se queda. */
@media (hover: none) {
  .sidebar-fijar { opacity: .55; transform: none; }
}
@media print { .sidebar-fijar { display: none; } }

/* --- Celular ---------------------------------------------------------
   La barra de áreas se parte en dos renglones: arriba el menú, el logo
   y buscar; abajo las áreas, que se deslizan de lado. La lateral entra
   desde la izquierda como siempre. */
@media (max-width: 900px) {
  .barra-areas {
    height: auto; flex-wrap: wrap; padding: .5rem .7rem 0; gap: .5rem;
  }
  .areas-menu-btn { display: block; order: 1; }
  .areas-marca { order: 2; }
  .areas-derecha { order: 3; margin-left: auto; }
  .areas {
    order: 4; width: 100%; flex-basis: 100%;
    padding: .1rem 0 .35rem; gap: .15rem;
    -webkit-overflow-scrolling: touch;
  }
  .usuario-datos { display: none; }
  .barra-areas .btn-buscar span:not(.btn-buscar-atajo) { display: none; }

  .app { min-height: 0; }
  .sidebar {
    position: fixed; top: 0; left: 0; bottom: 0; z-index: 60;
    height: 100vh; width: 250px;
    transform: translateX(-100%);
    transition: transform .24s cubic-bezier(.22,.8,.3,1);
    box-shadow: 0 0 40px rgba(0, 0, 0, .3);
    padding-top: 1rem;
  }
  body.sidebar-abierto .sidebar { transform: none; }
  .sidebar-fondo { position: fixed; inset: 0; z-index: 55; background: rgba(10, 14, 20, .5); }
}
@media (min-width: 901px) {
  .areas-menu-btn { display: none; }
  .sidebar-fondo { display: none !important; }
}

@media (prefers-reduced-motion: reduce) {
  .sidebar, .sidebar-link, .area { transition: none !important; }
  ::view-transition-group(area-marca) { animation-duration: 1ms; }
}
@media print {
  .barra-areas, .sidebar { display: none !important; }
  .app { display: block; }
}

/* =====================================================================
   LOS AVISOS, SIN GRITAR
   =====================================================================
   Un aviso llevaba fondo ámbar, borde ámbar y texto ámbar. Con tres
   cosas del mismo color el bloque entero se vuelve una mancha, y en
   pantallas con dos avisos seguidos —Cableado— media pantalla era
   amarilla antes del primer dato.

   El color pasa a MARCAR y no a llenar: una barra de 3 px a la
   izquierda y un icono, sobre el mismo material que las tarjetas. El
   texto va en el color de siempre, que es el que mejor se lee. Se
   distinguen por dónde está el color, no por cuánto hay.
   ------------------------------------------------------------------ */
/* NO se usa `grid` aquí, y costó una prueba: el contenido de un aviso
   es texto suelto mezclado con <strong>, <em> y <a>. En una reja, cada
   fragmento de texto entre etiquetas se vuelve una celda propia y el
   párrafo sale con una palabra por renglón. El icono va posicionado y
   el texto conserva su flujo normal. */
.alerta {
  position: relative;
  max-width: 96ch;
  margin: .9rem 0;
  padding: .7rem .9rem .7rem 2.45rem;
  background: var(--tarjeta);
  color: var(--texto);
  border: 1px solid var(--borde-suave);
  border-left: 3px solid var(--borde);
  border-radius: var(--radio-tarjeta);
  box-shadow: var(--sombra);
  font-size: .84rem;
  line-height: 1.55;
}
.alerta p:first-of-type { margin-top: 0; }
.alerta p:last-child { margin-bottom: 0; }

/* El icono. Va con `mask` y no como imagen de fondo para que tome el
   color de cada tipo: una imagen con el color cocido saldría mal en uno
   de los dos temas. */
.alerta::before {
  content: "";
  position: absolute; left: .85rem; top: .82rem;
  width: 17px; height: 17px;
  background-color: var(--borde);
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-size: contain;     mask-size: contain;
  -webkit-mask-position: center;  mask-position: center;
}

/* --- Aviso: algo que conviene saber, no algo que esté mal ----------- */
.alerta-aviso { border-left-color: var(--sem-ambar-tx); }
.alerta-aviso::before {
  background-color: var(--sem-ambar-tx);
  -webkit-mask-image: var(--icono-aviso); mask-image: var(--icono-aviso);
}
.alerta-aviso a { color: var(--enlace); font-weight: 600; }

/* --- Error: esto sí tiene que verse ---------------------------------
   Es el único que conserva fondo tintado. Un error que se lee igual que
   una nota se pierde, y perder un error cuesta más que un bloque de
   color de más. */
.alerta-error {
  border-left-color: var(--rojo-solido);
  background: var(--rojo-bg);
}
.alerta-error::before {
  background-color: var(--rojo-solido);
  -webkit-mask-image: var(--icono-error); mask-image: var(--icono-error);
}

/* --- Éxito: casi siempre se convierte en un aviso flotante ---------- */
.alerta-exito { border-left-color: var(--verde); }
.alerta-exito::before {
  background-color: var(--verde);
  -webkit-mask-image: var(--icono-exito); mask-image: var(--icono-exito);
}

/* Dos avisos seguidos se pegan: se leen como una nota de dos párrafos y
   no como dos bloques peleando por la atención. */
.alerta + .alerta { margin-top: -.4rem; }

/* Los tres dibujos, en línea. Se declaran como variables para no
   repetir la cadena en cada regla. */
:root {
  --icono-aviso: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='9'/%3E%3Cpath d='M12 8h.01M11 12h1v4h1'/%3E%3C/svg%3E");
  --icono-error: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 3.5 21 19H3z'/%3E%3Cpath d='M12 9.5v4M12 16.5h.01'/%3E%3C/svg%3E");
  --icono-exito: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='9'/%3E%3Cpath d='m8 12.5 2.5 2.5L16 9.5'/%3E%3C/svg%3E");
}

/* El aviso flotante conserva su forma, pero ya sin el fondo de color. */
.alerta.es-toast { background: var(--tarjeta); margin: 0; }

/* Donde no hay `mask` no se dibuja icono: el hueco de 17 px sobraría. */
@supports not ((-webkit-mask-image: none) or (mask-image: none)) {
  .alerta { padding-left: .9rem !important; }
  .alerta::before { display: none; }
}

/* =====================================================================
   CONEXIÓN — la franja de abajo y el rescate de lo capturado
   =====================================================================
   El internet de la oficina se va de rato en rato. Estas dos piezas son
   lo que la persona ve de eso; la lógica está en `js/conexion.js` y en
   `sw.js`.

   La franja va ABAJO y no arriba: arriba ya están las áreas y las migas,
   y una barra más empujaría el contenido justo cuando lo que se necesita
   es seguir leyendo la pantalla.

   Lo importante de verdad es el aviso de COPIA VIEJA. Servir una
   pantalla de inventario de hace dos horas como si fuera de ahorita es
   peor que no servir nada, así que ese estado no es discreto a propósito.
   ------------------------------------------------------------------ */
.franja-conexion {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 100;
  padding: .65rem 1rem;
  font-size: .85rem; line-height: 1.45; text-align: center;
  border-top: 1px solid transparent;
  transform: translateY(100%);
  transition: transform .22s ease;
}
.franja-conexion.visible { transform: translateY(0); }
.franja-conexion strong { font-weight: 700; }

/* Sin conexión: ámbar sólido. Se tiene que notar. */
.franja-mal {
  background: var(--sem-ambar-bg);
  color: var(--sem-ambar-tx);
  border-top-color: var(--sem-ambar-tx);
}
/* Versión nueva: ni alarma ni festejo. Es información, y se queda
   hasta que la persona decida recargar — recargarle la pantalla a
   alguien a media captura es justo lo que el resto de esto evita. */
.franja-nueva {
  background: var(--tarjeta);
  color: var(--texto);
  border-top-color: var(--acento-legible);
  box-shadow: 0 -2px 10px rgba(0,0,0,.10);
}
.franja-boton { margin-left: .8rem; vertical-align: baseline; }

/* Volvió: verde, y se va sola a los seis segundos. */
.franja-bien {
  background: var(--verde-bg);
  color: var(--verde);
  border-top-color: var(--verde);
}

/* La franja tapa el pie de página; se le da aire para que no estorbe al
   último renglón de una tabla larga. */
body:has(.franja-conexion.visible) .piepagina { padding-bottom: 3rem; }

@media (prefers-reduced-motion: reduce) {
  .franja-conexion { transition: none; }
}
@media print { .franja-conexion { display: none !important; } }

/* --- El rescate de lo capturado -------------------------------------
   Se OFRECE, no se aplica solo: rellenar la forma sin preguntar se
   siente bien el día que salvó algo y mal todos los demás. Por eso son
   dos botones y no un aviso pasivo. */
.alerta.rescate {
  display: flex; flex-wrap: wrap; align-items: center; gap: .5rem .9rem;
  padding-left: .9rem;
}
.alerta.rescate::before { display: none; }
.alerta.rescate > span:first-child { flex: 1 1 20rem; min-width: 0; }
.rescate-botones { display: flex; gap: .45rem; flex: none; }
@media print { .alerta.rescate { display: none !important; } }

/* La fecha de la factura, debajo de la del gasto y sólo cuando difieren.
   Va en la misma celda porque la tabla fiscal ya trae once columnas: una
   más que casi siempre repite a su vecina sería cobrar ancho por nada. */
.fecha-factura {
  display: block; font-size: .74rem; line-height: 1.3;
  white-space: nowrap; font-variant-numeric: tabular-nums;
}

/* El resultado de leer una factura. Va debajo del campo de archivo, no
   como aviso flotante: lo que dice es "revisa estos campos", y para eso
   tiene que estar junto a los campos. */
.lectura-factura {
  margin: .45rem 0 0; padding: .5rem .7rem;
  font-size: .82rem; line-height: 1.45;
  border-left: 3px solid var(--borde); border-radius: 0 4px 4px 0;
  background: var(--fondo);
}
.lectura-factura.leyendo { color: var(--texto-tenue); }
.lectura-factura.bien { border-left-color: var(--verde); }
.lectura-factura.mal  { border-left-color: var(--sem-ambar-tx); }
.lectura-factura strong { color: var(--texto); }

/* Un campo que llenó el lector se marca hasta que alguien lo toca: así
   se ve de un vistazo qué se leyó y qué se tecleó. */
.campo-leido {
  background: var(--verde-bg);
  transition: background-color 2.5s ease 1.5s;
}
.campo-leido:focus { background: var(--campo-bg); }
@media (prefers-reduced-motion: reduce) { .campo-leido { transition: none; } }

/* La firma cargada, sobre un fondo claro y con una línea debajo: así se
   ve como se va a ver en el papel, y se nota si el PNG trae un recuadro
   blanco en vez de fondo transparente. */
.firma-muestra {
  background: #fff; border-bottom: 2px solid #333;
  border-radius: 4px 4px 0 0;
  padding: .8rem 1rem .2rem; margin: .6rem 0 0;
  max-width: 22rem; display: flex; justify-content: center;
}
.firma-muestra img { max-height: 70px; max-width: 100%; }

/* =====================================================================
   CABLE QUE NO INSTALAMOS NOSOTROS
   =====================================================================
   El renglón se queda en la tabla —el plano lo trae, y esconderlo haría
   dudar de si se perdió— pero se apaga, para que al recorrer la lista se
   vea de un golpe qué está dentro del alcance y qué no. No se usa color
   de error: no está mal, simplemente no va con nosotros.
   ------------------------------------------------------------------ */
tbody tr.fila-no-va td { color: var(--texto-suave); }
tbody tr.fila-no-va td:first-child { text-decoration: line-through; }
/* La casilla y su rótulo NO se apagan: es el control con el que se
   deshace la marca, y tiene que seguir viéndose. */
tbody tr.fila-no-va td.col-noinst,
tbody tr.fila-no-va td.col-noinst * { color: var(--texto); text-decoration: none; }
