/* docs/pwa/styles.css — phone-first, no framework, no build step. */

:root {
  /* Cerrado 2026-09-02 (C5): estos valores estaban sueltos por la hoja como
     hex literales, fuera del sistema de tokens. Cada uno era un punto que no
     respondia a un cambio de tema. */
  --blanco-fijo: #fff;             /* texto sobre fondos de color saturado */
  --grafico-1: #7fa0d6;
  --grafico-2: #e3823f;
  --grafico-3: #8ec27c;
  --grafico-4: #e28468;
  --grafico-5: #b6ae97;

  /* ---- Dirección Holded (saaci360-direccion-holded, 2026-08-29) —
     blanco/gris claro, azul, Inter, radios 8-10px, sombras suaves.
     Mismos NOMBRES de variable que la paleta "cuaderno de obra" anterior
     (deliberado: casi todo el CSS/JS de la app referencia estos nombres,
     así que cambiar solo los VALORES revierte el look en cascada sin
     tocar cada regla una a una). ---- */
  --papel: #f7f4ef;
  --papel-alto: #ffffff;
  --tinta: #2a2320;
  --tinta-suave: #6f635b;
  --linea: #e8e0d6;
  --linea-fuerte: #d9cfc2;
  --acento: #c05731;
  --acento-suave: #f8e8e0;
  /* Fijo en ambos temas (a diferencia de --acento, que aclara en oscuro para
     seguir siendo legible como texto/borde SOBRE fondo oscuro) — para
     relleno solido + texto blanco (cabecera, botones primarios, pestana/
     pastilla activa). Un --acento aclarado ahi dejaria texto blanco casi sin
     contraste; ver saaci360-rediseno-visual unidad 3, revision del
     orquestador. */
  --acento-solido: #c05731;
  --atencion: #e0663a;
  --atencion-suave: #fdece4;
  --exito: #1a9d6c;
  --exito-suave: #e5f7ee;
  --alerta: #d92d20;
  --alerta-suave: #fdeceb;
  /* Tonos estado "fuertes" (saaci360-assessment): texto de sello/nota con
     contraste AA en tema claro; en oscuro se resuelven a los tokens claros
     (--exito/--atencion/...) para seguir legibles sobre fondos oscuros. */
  --exito-fuerte: #2a704b;
  --aviso-fuerte: #8a5c0f;
  --error-fuerte: #a33d2e;
  --info-fuerte: #a34421;
  --neutro-fuerte: #5c534c;
  --sombra: 0 1px 2px rgba(18, 23, 43, 0.05), 0 12px 32px -14px rgba(18, 23, 43, 0.16);
  --radio: 12px;
  /* 68 y no 64: la barra mide 65 px reales en tactil (52 de alto minimo
     del dedo + su relleno), asi que con 64 el ultimo renglon de cada
     pagina se quedaba DEBAJO de la barra. Medido, no estimado. */
  --nav-alto: 68px;

  --mono: 'Inter', ui-sans-serif, system-ui, sans-serif;
  --sans: 'Inter', ui-sans-serif, system-ui, sans-serif;
  --display: 'Sora', 'Inter', ui-sans-serif, system-ui, sans-serif;

  /* ---- Design tokens (pwa-design-system, unidad 1) ---- */
  --txt-xs: 0.68rem;
  --txt-sm: 0.8rem;
  --txt-base: 0.88rem;
  --txt-lg: 1.02rem;
  --txt-xl: 1.15rem;

  --esp-1: 0.25rem;
  --esp-2: 0.4rem;
  --esp-3: 0.6rem;
  --esp-4: 0.9rem;
  --esp-5: 1.4rem;
  --esp-6: 2rem;

  --radio-pastilla: 999px;

  --elev-1: var(--sombra);
  --elev-2: var(--sombra);

  --foco: 0 0 0 3px rgba(31, 58, 102, 0.35);
  --foco-borde: var(--acento);

  --nav-ancho: 232px;
  --cabecera-alto: 56px;
}

/* ---- Cuaderno de obra — paleta aprobada, tema oscuro (saaci360-rediseno-visual, unidad 1) ---- */
@media (prefers-color-scheme: dark) {
  :root {
    --papel: #17160f;
    --papel-alto: #201e16;
    --tinta: #f1efe4;
    --tinta-suave: var(--grafico-5);
    --linea: #3a3626;
    --linea-fuerte: #524c37;
    --acento: var(--grafico-1);
    --acento-suave: #1c2634;
    --atencion: var(--grafico-2);
    --atencion-suave: #33231a;
    --exito: var(--grafico-3);
    --exito-suave: #1f2b1b;
    --alerta: var(--grafico-4);
    --alerta-suave: #33201a;
    --exito-fuerte: var(--grafico-3);
    --aviso-fuerte: var(--grafico-2);
    --error-fuerte: var(--grafico-4);
    --info-fuerte: var(--grafico-1);
    --neutro-fuerte: var(--grafico-5);
    --sombra: 0 1px 0 rgba(0, 0, 0, 0.4), 0 8px 24px -12px rgba(0, 0, 0, 0.6);
  }
}

* { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  font-family: var(--sans);
  background: var(--papel);
  color: var(--tinta);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  padding-bottom: calc(var(--nav-alto) + env(safe-area-inset-bottom));
}

.mono {
  font-family: var(--mono);
}

/* ---------- Cabecera ---------- */

.cabecera {
  position: sticky;
  top: 0;
  z-index: 10;
  background: var(--tinta);
  border-bottom: 3px solid var(--acento-solido);
  color: var(--blanco-fijo);
  padding: calc(0.85rem + env(safe-area-inset-top)) 1.15rem 0.85rem;
  box-shadow: 0 10px 28px -14px rgba(16, 24, 46, 0.65);
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}

.cabecera-fila {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  min-height: 32px;
}

.marca {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  font-family: var(--display);
  font-size: 1.24rem;
  font-weight: 800;
  letter-spacing: 0.01em;
  text-transform: uppercase;
}

.marca-360 {
  color: var(--atencion);
}

.marca-punto {
  display: inline-block;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--atencion);
  box-shadow: 0 0 0 4px rgba(200, 90, 40, 0.3);
}

/* ---------- Barra de frescura (dentro de la cabecera) ---------- */

.frescura {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-size: 0.72rem;
  color: rgba(255, 255, 255, 0.78);
  font-weight: 500;
}

.frescura button {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  padding: 0;
  border: none;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.14);
  color: var(--blanco-fijo);
  cursor: pointer;
  transition: background 0.15s ease, transform 0.2s ease;
}

/* El atributo `hidden` gana SIEMPRE. Sin esta regla no oculta nada en
   cuanto el elemento tiene una clase que fija `display`: la del navegador
   es una hoja de USUARIO-AGENTE y cualquier regla de autor la pisa, por
   poca especificidad que tenga.

   No es teórico, se comió tres cosas de esta app:
     · #portada  (.portada display:flex)  — la puerta de sesión no se podía
       cerrar: entrabas con Google, recargaba, y seguías mirándola.
     · #forzar-actualizacion (.btn display:inline-flex) — el botón que el
       usuario reportó el 2026-08-27 como "visible siempre". Se arregló la
       lógica en JS y siguió saliendo, porque el `hidden` que ponía esa
       lógica no hacía nada.
     · #frescura (.frescura display:flex) — chip vacío en la cabecera.
   Tres síntomas distintos, una sola causa. */
[hidden] { display: none !important; }

/* Puerta de sesión: tapa la app entera hasta saber quién eres. */
.portada {
  position: fixed;
  inset: 0;
  z-index: 90;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--esp-4, 20px);
  background: var(--papel);
}

.portada-caja {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  max-width: 22rem;
  text-align: center;
}

.marca--grande { font-size: 1.5rem; color: var(--tinta); }

.portada-texto {
  margin: 0;
  color: var(--tinta-suave);
  font-size: var(--txt-sm);
  line-height: 1.5;
}

.portada-error {
  margin: 0;
  color: var(--peligro, #c0392b);
  font-size: var(--txt-sm);
}

/* Sesión: vive en la cabecera oscura, así que se apoya en blancos con alfa
   igual que .frescura — nunca en --tinta, que ahí no contrasta. */
.sesion {
  flex: 0 0 auto;
  margin-left: 0.5rem;
  padding: 0.3rem 0.7rem;
  border: 1px solid rgba(255, 255, 255, 0.28);
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.1);
  color: var(--blanco-fijo);
  font: inherit;
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.01em;
  max-width: 9rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease;
}

.sesion:hover { background: rgba(255, 255, 255, 0.2); }
.sesion:disabled { opacity: 0.55; cursor: progress; }

/* Dentro: un punto verde discreto. Estar dentro es el estado bueno y
   silencioso — no necesita gritar, solo dejarse ver de un vistazo. */
.sesion--dentro {
  border-color: rgba(126, 200, 143, 0.55);
  background: rgba(126, 200, 143, 0.16);
}

.sesion--dentro::before {
  content: '';
  display: inline-block;
  width: 6px;
  height: 6px;
  margin-right: 0.4rem;
  border-radius: 999px;
  background: #7ec88f;
  vertical-align: middle;
}

.frescura button:hover { background: rgba(255, 255, 255, 0.26); }
.frescura button:active svg { transform: rotate(150deg); }
.frescura button svg { transition: transform 0.3s ease; }

/* ---------- Contenido ---------- */

main {
  padding: 1.1rem 1rem 1.5rem;
  max-width: 720px;
  margin: 0 auto;
}

/* La transición de entrada es propia del router de app.js (#vista), no de
   cualquier <main> — sin este scope, docs/pwa/componentes.html (que no
   carga app.js y por tanto nunca gana la clase .entrando) quedaba con
   opacity:0 permanente (bug encontrado en la QA visual de la unidad 3). */
#vista {
  opacity: 0;
  transform: translateY(6px);
}

#vista.entrando {
  opacity: 1;
  transform: translateY(0);
  transition: opacity 0.25s ease, transform 0.25s ease;
}

h2 {
  font-family: var(--display);
  font-size: 1.45rem;
  font-weight: 800;
  letter-spacing: -0.01em;
  padding-bottom: var(--esp-2);
  border-bottom: 2px solid var(--linea-fuerte);
  margin: 0.1rem 0 0.9rem;
  color: var(--tinta);
}

/* ---------- Banner flotante de estado global (auth/red) ---------- */

#estado-global.banner {
  position: sticky;
  top: calc(var(--cabecera-alto) + env(safe-area-inset-top));
  z-index: 9;
  margin: 0.6rem 1rem 0;
  border-radius: var(--radio);
}

/* ---------- Banners de estado ---------- */

.banner {
  margin: 0.75rem 0;
  padding: 0.8rem 1rem;
  border-radius: var(--radio);
  font-size: 0.88rem;
  border: 1px solid transparent;
  border-left-width: 4px;
  box-shadow: var(--sombra);
}

.banner.error {
  background: var(--alerta-suave);
  color: var(--alerta);
  border-color: var(--alerta);
  border-left-color: var(--alerta);
}

.banner.info {
  background: var(--acento-suave);
  color: var(--acento);
  border-color: var(--acento);
  border-left-color: var(--acento);
}

/* Version historica CONGELADA (neutro-arena): una version antigua no es un
   error ni una accion — es un estado pasivo. El banner 'info' (acento
   terracota) se leia como alerta; este neutro comunica 'historia/estado'.
   Se reserva el acento para accion (Añadir partida) y el verde para la
   version ACCEPTADA vigente. */
.banner.neutro {
  background: var(--linea);
  color: var(--tinta-suave);
  border-color: var(--linea-fuerte);
  border-left-color: var(--tinta-suave);
}

.banner.ok {
  background: var(--exito-suave);
  color: var(--exito);
  border-color: var(--exito);
  border-left-color: var(--exito);
}

/* 'aviso': ni error ni información pasiva — algo está esperando a que
   actúes. Nace del avance bloqueado por una certificación en borrador:
   nada ha fallado, pero no se sigue hasta que la mandes. */
.banner.aviso {
  background: var(--atencion-suave);
  color: var(--atencion);
  border-color: var(--atencion);
  border-left-color: var(--atencion);
}

.acciones-login {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-top: 0.6rem;
}

.banner code {
  font-family: var(--mono);
  font-size: 0.82em;
  background: rgba(0, 0, 0, 0.06);
  padding: 0.1em 0.4em;
  border-radius: 4px;
}

/* ---------- Tablas ---------- */

.tabla-envoltorio {
  overflow-x: auto;
  border-radius: var(--radio);
  box-shadow: var(--sombra);
  background: var(--papel-alto);
  margin-top: 0.6rem;
  border: 1px solid var(--linea);
}

table {
  width: 100%;
  min-width: 460px;
  border-collapse: collapse;
  font-size: 0.86rem;
  background: var(--papel-alto);
}

th, td {
  text-align: left;
  padding: 0.5rem 0.7rem;
  border-bottom: 1px solid var(--linea);
  vertical-align: top;
  white-space: nowrap;
}

td { color: var(--tinta); font-variant-numeric: tabular-nums; }

th {
  background: var(--papel);
  font-family: var(--mono);
  font-weight: 600;
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--tinta-suave);
  position: sticky;
  top: 0;
}

tbody tr:last-child td { border-bottom: none; }
tbody tr:hover td { background: var(--papel); }

/* ---------- Tabla — hooks numérico/total/pendiente (unidad 2,
   design decision 4). `.tabla-num` is author-declared on the column
   descriptor (`numerico: true` or `alinear: 'derecha'`), never sniffed
   per-cell — JSONP delivers every value as a string. ---------- */

.tabla-num {
  text-align: right;
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
}

th.tabla-cab.tabla-num { text-align: right; }

tr.tabla-total td {
  border-top: 2px solid var(--tinta);
  border-bottom: none;
  font-weight: 700;
  background: var(--papel);
}

tbody tr:hover td.tabla-pendiente,
td.tabla-pendiente {
  color: var(--aviso-fuerte);
  font-style: normal;
}

/* Columnas de texto descriptivo (p.ej. Partida, trabajador, obra): envolver
   en vez de forzar nowrap + scroll horizontal, para que el contenido se vea
   completo sin ensanchar la tabla ni cortar texto. Author-declared por
   columna (`envolver: true` en el descriptor) — nunca sniffed por celda. */
.tabla-envuelve {
  white-space: normal;
  /* break-word (no anywhere): mantiene el ancho mínimo de la columna en la
     palabra más larga, así la descripción se ve en pocas líneas en vez de
     romperse por mitad de palabra y aplastar la columna. */
  overflow-wrap: break-word;
}

/* Celda de una sola linea con elipsis (saaci360-editar-ppto): la columna
   fusion Capítulo+Partida se queda a una linea y recorta con '…' en vez de
   ensanchar la tabla o partir la palabra. El valor completo vive en el
   editor (Editar / Editar todo el ppto). */
.tabla-recortar {
  max-width: 30ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Versión más ancha para el Catálogo (2026-08-31): la columna fusionada
   añade ·Partida y debe absorber el hueco sobrante en vez de dejárselo a
   Unidad (que se veía con mucho espacio vacío). Hasta 48ch con elipsis. */
.tabla-recortar--ancha {
  max-width: 48ch;
}

/* Celda sin dato (2026-09-02): el "—" de una descripción vacía no debe
   pesar lo mismo que una descripción real — se lee de un vistazo cuáles
   están por rellenar. */
.tabla-tenue {
  color: var(--tinta-suave);
}

/* Editor de presupuesto (saaci360-editar-ppto, 2026-08-31) — modal de
   edición de todo el presupuesto de una obra: lista agrupada por capítulo,
   filas con jerarquía (partida + meta) y pie de acciones claro. Diseñado
   con los tokens, no HTML pelado. */
.ppto-editor { display: flex; flex-direction: column; }

/* El editor de presupuesto NO es un diálogo de confirmación: es la
   superficie de trabajo donde se arma la obra entera. Con los 440px que
   sirven para un "¿seguro?" quedaba una rendija en medio de un monitor
   vacío, con los nombres de partida cortados. Aquí el ancho ES la función.
   :has() acota el ensanche a este modal — el resto siguen estrechos, que es
   lo correcto para un diálogo. */
.modal:has(.ppto-editor) { max-width: min(960px, 94vw); }
.ppto-editor { gap: var(--esp-2); max-height: min(74vh, 760px); }
/* Que scrolle la LISTA, no el modal: buscador, contador y Guardar quedan
   siempre a la vista mientras hojeas capítulos. */
.ppto-editor .ppto-cesta-grid {
  flex: 1; min-height: 0; overflow-y: auto;
  margin-bottom: 0; padding-right: var(--esp-1);
}
.ppto-editor .ppto-cesta-contador { margin: 0 0 var(--esp-2); }
.ppto-editor-body {
  display: flex; flex-direction: column; gap: var(--esp-5);
  overflow-y: auto; max-height: 60vh;
  padding: var(--esp-2) 0 var(--esp-3);
}
.ppto-editor-grupo { display: flex; flex-direction: column; }
.ppto-editor-capitulo {
  font-family: var(--mono); font-size: var(--txt-xs); text-transform: uppercase;
  letter-spacing: 0.08em; color: var(--tinta-suave);
  padding-bottom: var(--esp-2); margin-bottom: var(--esp-2);
  border-bottom: 1px solid var(--linea);
}
.ppto-editor-fila {
  display: flex; align-items: center; justify-content: space-between; gap: var(--esp-3);
  padding: var(--esp-3) var(--esp-3); border-radius: var(--radio);
}
.ppto-editor-fila:hover { background: var(--papel); }
.ppto-editor-fila + .ppto-editor-fila { margin-top: 2px; }
.ppto-editor-info { min-width: 0; flex: 1; }
.ppto-editor-partida { font-weight: 600; color: var(--tinta); line-height: 1.25; }
.ppto-editor-meta { font-size: var(--txt-sm); color: var(--tinta-suave); margin-top: 2px; }
.ppto-editor-pie {
  display: flex; gap: var(--esp-3); flex-wrap: wrap; align-items: center;
  padding-top: var(--esp-4); margin-top: var(--esp-4);
  border-top: 1px solid var(--linea);
}

/* ---------- Acción inline por fila (anular-ledgers-pwa, Fase 8) —
   `SaaciUI.tabla({accion:{titulo,render}})`'s last column. `.btn--
   compacto` shrinks a normal `.btn` (only variant/color CSS classes
   already existed; this is the first size modifier) so a table cell can
   hold one or two side-by-side without dwarfing the row; `.tabla-
   accion-grupo` lets a cell hold more than one (Presupuesto vivo:
   "Anular partida" + "Anular precio", two distinct domains/Sheets). ---------- */

.tabla-accion-grupo {
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  gap: var(--esp-2);
  white-space: normal;
}

.btn.btn--compacto {
  padding: var(--esp-2);
  font-size: var(--txt-xs);
  line-height: 1.1;
  white-space: nowrap;
}

/* Botón a ancho completo (acción destacada, p.ej. '+ Añadir partida') */
.btn--bloque {
  width: 100%;
  padding: var(--esp-3) var(--esp-4);
  margin-bottom: var(--esp-3);
  font-size: var(--txt-base);
}

/* ---------- Listas tipo tarjeta (Obras) ---------- */

ul.lista-simple {
  list-style: none;
  margin: 0.6rem 0 0;
  padding: 0;
  display: grid;
  gap: 0.7rem;
}

ul.lista-simple li {
  background: var(--papel-alto);
  border: 1px solid var(--linea);
  border-radius: var(--radio);
  padding: 1rem 1.1rem;
  box-shadow: var(--sombra);
  transition: box-shadow 0.15s ease, transform 0.15s ease;
}

ul.lista-simple li:has(a:hover) {
  box-shadow: 0 10px 28px -10px rgba(31, 58, 102, 0.28);
  transform: translateY(-1px);
}

/* ---------- Empujón visual (saaci360-empujon-visual, 2026-08-29) ----------
   Aplica de verdad `.tablero`/`.kpi-*` (definidos desde pwa-rediseno-visual
   pero sin usar en ninguna vista todavía) + añade el "sello" de estado
   (variante con más carácter que `.insignia` para el dato más importante
   de una tarjeta de obra) y una fila de metadatos con iconos separada por
   una línea discontinua — motivo "cuaderno de obra", mismo lenguaje visual
   que ya definía el resto del sistema, nunca una paleta/tipografía nueva. */

ul.lista-simple li.tarjeta-obra {
  padding: 0.9rem 1rem;
}

/* Sello (2026-08-29, dirección Holded): pastilla rellena con punto, sin
   rotación ni borde — mismo lenguaje que `.insignia`, pero con más peso
   visual para el dato más importante de una tarjeta (estado de obra). */
.sello {
  display: inline-flex;
  align-items: center;
  gap: 0.35em;
  font-size: var(--txt-sm);
  font-weight: 700;
  padding: 0.3em 0.75em;
  border-radius: var(--radio-pastilla);
  white-space: nowrap;
  flex-shrink: 0;
}
.sello::before {
  content: '';
  width: 5px; height: 5px;
  border-radius: 50%;
  background: currentColor;
  flex-shrink: 0;
}

.sello--ok { background: var(--exito-suave); color: var(--exito-fuerte); }
.sello--aviso { background: var(--atencion-suave); color: var(--aviso-fuerte); }
.sello--error { background: var(--alerta-suave); color: var(--error-fuerte); }
.sello--neutro { background: var(--linea); color: var(--neutro-fuerte); }
.sello--info { background: var(--acento-suave); color: var(--info-fuerte); }

.tarjeta-obra-fila-top {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 0.6rem;
}

/* Avatar-chip de obra (2026-08-29, dirección Holded) — inicial del
   nombre en un cuadrado redondeado con tinte de acento, mismo patrón que
   Holded usa para empresas/contactos en sus listados. Puramente
   decorativo/derivado (primera letra de `nombre_corto`), nunca un campo
   nuevo del índice. */
.avatar-obra {
  width: 38px; height: 38px;
  border-radius: 9px;
  background: var(--acento-suave);
  color: var(--acento-solido);
  display: flex; align-items: center; justify-content: center;
  font-weight: 800; font-size: 0.9rem;
  flex-shrink: 0;
}

.tarjeta-obra-titulo-grupo {
  display: flex;
  align-items: center;
  gap: 0.65rem;
  min-width: 0;
}
.tarjeta-obra-titulo-grupo > div:last-child { min-width: 0; }

.tarjeta-obra-titulo-grupo .tarjeta-enlace {
  display: inline-block;
  margin-bottom: 0;
  font-size: var(--txt-base);
}
.tarjeta-obra-titulo-grupo .tarjeta-enlace::after { content: none; }

.tarjeta-obra-sub {
  font-size: var(--txt-xs);
  color: var(--tinta-suave);
  margin-top: 1px;
}

.tarjeta-obra-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1rem;
  margin-top: 0.6rem;
  padding-top: 0.55rem;
  padding-left: calc(38px + 0.65rem);
  border-top: 1px solid var(--linea);
}

.meta-item {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  font-size: var(--txt-sm);
  color: var(--tinta-suave);
}

.meta-item svg { flex-shrink: 0; opacity: 0.75; }
.meta-item b { color: var(--tinta); font-weight: 600; }

/* ---------- Pestañas ---------- */

.tabs {
  display: flex;
  gap: 0.3rem;
  margin-bottom: 0.9rem;
  background: var(--papel-alto);
  padding: 0.3rem;
  border-radius: 999px;
  border: 1px solid var(--linea);
  width: fit-content;
  box-shadow: var(--sombra);
}

.tabs button {
  padding: 0.42rem 0.95rem;
  border: none;
  border-radius: 999px;
  background: transparent;
  color: var(--tinta-suave);
  cursor: pointer;
  font-size: 0.84rem;
  font-weight: 700;
  transition: background 0.15s ease, color 0.15s ease;
}

.tabs button[aria-selected="true"] {
  background: var(--acento-solido);
  color: var(--blanco-fijo);
}

/* ---------- Formulario de Horas ---------- */

.formulario-horas {
  display: flex;
  flex-wrap: wrap;
  gap: 0.7rem;
  align-items: flex-end;
  margin-bottom: 1rem;
  background: var(--papel-alto);
  border: 1px solid var(--linea);
  border-radius: var(--radio);
  padding: 1rem;
  box-shadow: var(--sombra);
}

.formulario-horas label {
  display: flex;
  flex-direction: column;
  font-family: var(--mono);
  font-size: 0.68rem;
  font-weight: 600;
  color: var(--tinta-suave);
  gap: 0.3rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

.formulario-horas input {
  padding: 0.5rem 0.6rem;
  border: 1px solid var(--linea);
  border-radius: var(--radio);
  font-size: 0.9rem;
  font-family: inherit;
  color: var(--tinta);
  background: var(--papel);
}

.formulario-horas input:focus-visible {
  outline: 2px solid var(--acento);
  outline-offset: 1px;
  background: var(--papel-alto);
}

.formulario-horas button {
  padding: 0.55rem 1.2rem;
  border: none;
  border-radius: var(--radio);
  background: var(--acento-solido);
  color: var(--blanco-fijo);
  cursor: pointer;
  font-size: 0.88rem;
  font-weight: 700;
  transition: background 0.15s ease, transform 0.1s ease;
}

.formulario-horas button:hover:not(:disabled) { background: color-mix(in srgb, var(--acento-solido) 85%, black); }
.formulario-horas button:active:not(:disabled) { transform: scale(0.97); }

.formulario-horas button:disabled {
  background: var(--linea);
  cursor: not-allowed;
}

/* ---------- Pie ---------- */

.pie {
  padding: 1.5rem 1rem 1.25rem;
  font-size: 0.7rem;
  color: var(--tinta-suave);
  max-width: 720px;
  margin: 0 auto;
  text-align: center;
}

/* ---------- Nav inferior (estilo app nativa) ---------- */

.nav {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: 20;
  display: flex;
  /* RESPALDO SOLIDO PRIMERO, y no es cosmetica: si `color-mix` no se
     entiende, el navegador TIRA esa declaracion entera y la barra se
     queda SIN FONDO — translucida sobre el contenido, que es como se ve
     una barra «perdida». Con el respaldo delante, lo peor que pasa es
     que se vea opaca. `color-mix` es de Safari 16.2 en adelante, y esta
     app se mira desde moviles que no elegimos nosotros. */
  background: var(--papel-alto);
  background: color-mix(in srgb, var(--papel-alto) 92%, transparent);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  border-top: 1px solid var(--linea);
  padding: 0.4rem 0.5rem calc(0.4rem + env(safe-area-inset-bottom));
  box-shadow: 0 -6px 20px -10px rgba(28, 27, 23, 0.18);
}

.nav a {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.2rem;
  padding: 0.35rem 0.2rem;
  color: var(--tinta-suave);
  text-decoration: none;
  font-size: 0.68rem;
  font-weight: 600;
  border-radius: var(--radio);
  border-left: 3px solid transparent;
  transition: color 0.15s ease, background 0.15s ease, border-color 0.15s ease;
}

.nav a svg { stroke: currentColor; }

/* 6 items at very narrow widths (design decision 7): trims horizontal
   padding so the extra 2 entries never squeeze existing labels below the
   ellipsis guard's usual comfort margin. Below 380px only. */
@media (max-width: 380px) {
  .nav a {
    padding-left: 0.1rem;
    padding-right: 0.1rem;
  }
}

/* Structural guard (saaci360-listados-clientes-trabajadores, design
   decision 7): no future nav label can ever grow the bar and wrap/clip —
   ellipsis is the fallback, not the expected outcome for today's 6 items. */
.nav a span {
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.nav a:hover { background: var(--papel); }

.nav a.activo {
  color: var(--acento);
  background: var(--acento-suave);
  border-left-color: var(--acento);
}

.nav a.activo svg {
  stroke: var(--acento);
}

/* =================================================================
   SaaciUI (pwa-design-system, unidad 1) — componentes de ui.js.
   Toda regla de aquí abajo lee tokens únicamente (spec: "Design Tokens
   Replace Ad-Hoc Literals").
   ================================================================= */

/* ---------- Botón (.btn) ---------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--esp-2);
  padding: var(--esp-3) var(--esp-4);
  border: 1px solid transparent;
  border-radius: var(--radio);
  font-family: inherit;
  font-size: var(--txt-base);
  font-weight: 700;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease, transform 0.1s ease;
}

.btn:active:not(:disabled) { transform: scale(0.97); }

.btn:focus-visible {
  outline: none;
  box-shadow: var(--foco);
}

.btn:disabled { cursor: not-allowed; }

.btn--primario {
  background: var(--acento-solido);
  border-color: var(--acento-solido);
  color: var(--blanco-fijo);
  box-shadow: 0 2px 8px -2px rgba(192, 87, 49, 0.4);
}
.btn--primario:hover:not(:disabled) { background: color-mix(in srgb, var(--acento-solido) 85%, black); border-color: color-mix(in srgb, var(--acento-solido) 85%, black); box-shadow: 0 8px 18px -8px rgba(192, 87, 49, 0.55); transform: translateY(-1px); }
.btn--primario:disabled { background: var(--linea); border-color: var(--linea); }

.btn--secundario {
  background: var(--papel-alto);
  border-color: var(--linea);
  color: var(--tinta);
}
.btn--secundario:hover:not(:disabled) { background: var(--papel); }
.btn--secundario:disabled { color: var(--tinta-suave); }

.btn--peligro {
  background: var(--alerta);
  border-color: var(--alerta);
  color: var(--blanco-fijo);
}
.btn--peligro:hover:not(:disabled) { background: color-mix(in srgb, var(--alerta) 82%, black); border-color: color-mix(in srgb, var(--alerta) 82%, black); }
.btn--peligro:disabled { background: var(--alerta-suave); border-color: var(--alerta-suave); }

.btn--fantasma {
  background: transparent;
  border-color: transparent;
  color: var(--tinta-suave);
}
.btn--fantasma:hover:not(:disabled) { background: var(--papel); color: var(--tinta); }

/* Acción destructiva silenciosa (diseño presupuesto 2026-08-30): comunica
   la destructividad por jerarquía, no por gritar rojo permanente — muted
   en reposo, rojo solo al hover. */
.btn--peligro-fantasma {
  background: transparent;
  border-color: transparent;
  color: var(--tinta-suave);
  box-shadow: none;
}
.btn--peligro-fantasma:hover:not(:disabled) { background: var(--alerta-suave); color: var(--error-fuerte); }

.btn-giro {
  display: none;
  flex-shrink: 0;
}

.btn--pendiente .btn-giro {
  display: block;
  animation: girar 0.9s linear infinite;
}

@keyframes girar {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}

@media (prefers-reduced-motion: reduce) {
  .btn--pendiente .btn-giro { animation: none; }
}

/* Botón-icono de fila (saaci360-iconos-fila): cuando está pendiente el
   spinner sustituye al icono, no lo acompaña. */
.btn--pendiente .btn-icono { display: none; }

/* ---------- Campo (.campo) ---------- */

.campo {
  display: flex;
  flex-direction: column;
  gap: var(--esp-1);
}

.campo-etiqueta {
  font-family: var(--mono);
  font-size: var(--txt-xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--tinta-suave);
}

.campo-control {
  padding: var(--esp-3) var(--esp-3);
  border: 1px solid var(--linea);
  border-radius: var(--radio);
  font-size: var(--txt-lg);
  font-family: inherit;
  color: var(--tinta);
  background: var(--papel);
}

/* Prosa larga (2026-09-02, descripción de partida de catálogo). Hereda todo
   lo demás de .campo-control, así que foco/inválido/tema funcionan igual
   sin duplicar una regla. */
.campo-control--area {
  min-height: 5.5rem;
  line-height: 1.5;
  resize: vertical;
}

.campo-control:focus-visible {
  outline: none;
  box-shadow: var(--foco);
  border-color: var(--foco-borde);
  background: var(--papel-alto);
}

.campo--invalido .campo-control {
  border-color: var(--alerta);
}

/* comboBuscar (ui.js) — dropdown de sugerencias, posicionado sobre el
   flujo normal para no empujar el resto del formulario al abrirse. */
.campo-combo {
  position: relative;
}

.campo-combo .campo-control {
  padding-right: calc(var(--esp-3) + 18px);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%236b7185' stroke-width='2'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 10px center;
  background-size: 15px;
}

.combo-lista {
  position: absolute;
  z-index: 20;
  top: calc(100% + var(--esp-1));
  left: 0;
  right: 0;
  margin: 0;
  padding: var(--esp-1);
  list-style: none;
  background: var(--papel-alto);
  border: 1px solid var(--linea);
  border-radius: var(--radio);
  box-shadow: var(--elev-2);
  max-height: 240px;
  overflow-y: auto;
}

.combo-opcion {
  padding: var(--esp-2) var(--esp-3);
  border-radius: var(--radio);
  font-size: var(--txt-lg);
  cursor: pointer;
}

.combo-opcion:hover,
.combo-opcion:focus-visible {
  background: var(--linea);
  outline: none;
}

.combo-sin-coincidencia {
  margin-top: var(--esp-1);
  font-size: var(--txt-sm);
  color: var(--tinta-suave);
}

.combo-sin-coincidencia a {
  color: var(--acento);
  font-weight: 700;
  text-decoration: none;
}

.combo-sin-coincidencia a:hover {
  text-decoration: underline;
}

/* graficoBarrasApiladas (ui.js) — Horas por obra por día. */
.grafico-barras {
  overflow-x: auto;
}

.grafico-vacio {
  margin: 0;
  font-size: var(--txt-sm);
  color: var(--tinta-suave);
}

.grafico-leyenda {
  display: flex;
  flex-wrap: wrap;
  gap: var(--esp-2) var(--esp-4);
  margin-top: var(--esp-2);
}

.grafico-leyenda-item {
  display: inline-flex;
  align-items: center;
  gap: var(--esp-1);
  font-size: var(--txt-xs);
  color: var(--tinta-suave);
}

.grafico-leyenda-swatch {
  width: 10px;
  height: 10px;
  border-radius: 2px;
  display: inline-block;
}

.campo-ayuda {
  margin: 0;
  font-size: var(--txt-xs);
  color: var(--tinta-suave);
}

.campo-mensaje {
  margin: 0;
  font-size: var(--txt-xs);
  color: var(--alerta);
}

/* ---------- Formulario (.formulario) ---------- */

.formulario {
  background: var(--papel-alto);
  border: 1px solid var(--linea);
  border-radius: var(--radio);
  padding: var(--esp-4);
  box-shadow: var(--elev-1);
  margin-bottom: var(--esp-4);
}

.formulario-campos {
  display: flex;
  flex-wrap: wrap;
  gap: var(--esp-4);
}

.formulario-acciones {
  display: flex;
  justify-content: flex-end;
  gap: var(--esp-2);
  margin-top: var(--esp-4);
}

/* Cabecera de grupo dentro de un formulario (saaci360-assessment):
   seccion de campos con titulo propio, separada por linea discontinua —
   misma jerarquia de bloques que .hoja, sin anadir un contenedor nuevo. */
.form-grupo {
  flex: 1 1 100%;
  margin: 0;
  padding-top: var(--esp-4);
  border-top: 1px solid var(--linea);
  font-family: var(--display);
  font-size: var(--txt-sm);
  font-weight: 700;
  letter-spacing: 0.02em;
  color: var(--tinta);
}
.form-grupo:first-child {
  border-top: none;
  padding-top: 0;
}

/* ---------- Insignia (.insignia) ---------- */

.insignia {
  display: inline-flex;
  align-items: center;
  gap: var(--esp-1);
  padding: var(--esp-1) var(--esp-3);
  border-radius: var(--radio-pastilla);
  font-size: var(--txt-sm);
  font-weight: 700;
  text-transform: capitalize;
  background: var(--linea);
  color: var(--tinta-suave);
}

/* Dot inherits currentColor from .insignia--{tono} below — always the
   same semantic tone driving the badge (exito/alerta/neutro/…), never
   the brand accent (unidad 2). */
.insignia-punto {
  display: inline-block;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
  flex-shrink: 0;
}

.insignia--neutro { background: var(--linea); color: var(--tinta-suave); }
.insignia--ok { background: var(--exito-suave); color: var(--exito); }
.insignia--aviso { background: var(--atencion-suave); color: var(--atencion); }
.insignia--error { background: var(--alerta-suave); color: var(--alerta); }
.insignia--info { background: var(--acento-suave); color: var(--acento); }

/* ---------- Tarjeta (.tarjeta) ---------- */

.tarjeta {
  background: var(--papel-alto);
  border: 1px solid var(--linea);
  border-radius: var(--radio);
  padding: var(--esp-4);
  box-shadow: var(--elev-1);
}

.tarjeta-enlace {
  color: var(--tinta);
  font-weight: 700;
  font-size: var(--txt-lg);
  text-decoration: none;
  letter-spacing: -0.01em;
  display: block;
  margin-bottom: var(--esp-3);
}

.tarjeta-enlace::after {
  content: '→';
  margin-left: var(--esp-1);
  color: var(--acento);
  font-weight: 400;
}

.tarjeta-enlace:hover { color: var(--acento); }

/* saaci360-detalle-edicion: link into a row's own #/dominio/:fila detail
   view, rendered via SaaciUI.tabla's opciones.accion.render() — a plain
   <a>, styled to match .tarjeta-enlace's color language at table scale. */
.tabla-enlace {
  color: var(--acento);
  font-weight: 600;
  font-size: var(--txt-sm);
  text-decoration: none;
  white-space: nowrap;
}

.tabla-enlace:hover { text-decoration: underline; }

.enlace-volver {
  display: inline-flex;
  align-items: center;
  gap: var(--esp-1);
  color: var(--tinta-suave);
  font-size: var(--txt-sm);
  font-weight: 600;
  text-decoration: none;
  margin-bottom: var(--esp-3);
}

.enlace-volver::before { content: '←'; }

.enlace-volver:hover { color: var(--acento); }

.tarjeta-campos {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
  gap: var(--esp-3) var(--esp-4);
  margin: 0;
}

.tarjeta-campos dt {
  font-family: var(--mono);
  font-size: var(--txt-xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--tinta-suave);
  margin: 0 0 var(--esp-1);
}

.tarjeta-valor {
  margin: 0;
  font-size: var(--txt-base);
  font-weight: 600;
  color: var(--tinta);
  overflow-wrap: anywhere;
}

.tarjeta-valor-tenue {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--txt-xs);
  font-weight: 400;
  color: var(--tinta-suave);
}

/* ---------- Cabecera de bloque-título (.tarjeta-cabecera / .hoja-cabecera)
   — saaci360-rediseno-visual unidad 2, design decision 5. One rule serves
   both `tarjeta()`'s optional header and `hoja()`. ---------- */

.tarjeta-cabecera,
.hoja-cabecera {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--esp-2) var(--esp-3);
  margin-bottom: var(--esp-4);
}

.tarjeta-titulo,
.hoja-titulo {
  margin: 0;
  font-family: var(--display);
  font-size: 1.4rem;
  font-weight: 800;
  letter-spacing: 0.01em;
  color: var(--tinta);
}

.tarjeta-recuento,
.hoja-recuento {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 1.6em;
  padding: 0.05em 0.5em;
  border-radius: var(--radio-pastilla);
  background: var(--linea);
  color: var(--tinta-suave);
  font-family: var(--mono);
  font-size: var(--txt-xs);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.tarjeta-accion,
.hoja-accion {
  margin-left: auto;
  border: none;
  background: transparent;
  padding: 0;
  color: var(--acento);
  font-family: inherit;
  font-size: var(--txt-sm);
  font-weight: 700;
  text-decoration: none;
  cursor: pointer;
}

.tarjeta-accion:hover,
.hoja-accion:hover {
  text-decoration: underline;
}

/* Segundo enlace opcional en la misma cabecera (C3, roadmap 2026-08-28)
   — el primer .hoja-accion ya absorbe el espacio sobrante con
   margin-left:auto; este solo se ancla a su lado, sin volver a
   reclamarlo, para que ambos queden juntos a la derecha. */
.hoja-accion--secundaria {
  margin-left: 0;
  color: var(--tinta-suave);
}

/* ---------- Hoja (.hoja) — title-block section, wraps a body area
   (saaci360-rediseno-visual unidad 2, design decisions 5/6). Not yet
   adopted by app.js's crearSeccion() — Unit 3. ---------- */

.hoja {
  background: var(--papel-alto);
  border: 1px solid var(--linea);
  border-radius: var(--radio);
  padding: var(--esp-4);
  box-shadow: var(--elev-1);
  margin-bottom: var(--esp-4);
}

.hoja-cuerpo > :first-child { margin-top: 0; }
.hoja-cuerpo > :last-child { margin-bottom: 0; }

/* ---------- Navegación de secciones (Obra 360) — sticky tab strip ---------- */
.obra-cabecera-fija {
  position: sticky;
  top: calc(var(--cabecera-alto) + env(safe-area-inset-top));
  z-index: 9;
  margin: 0 -1rem var(--esp-4);
  padding: var(--esp-2) var(--esp-3) var(--esp-3);
  background: var(--papel);
  border-bottom: 1px solid var(--linea);
}

.secciones-nav {
  display: flex;
  gap: var(--esp-1);
  overflow-x: auto;
  padding: var(--esp-2) 0 0;
  margin: 0;
  background: transparent;
  scrollbar-width: none;
}
.secciones-nav::-webkit-scrollbar { display: none; }
.secciones-nav-item {
  flex: 0 0 auto;
  padding: var(--esp-2) var(--esp-4);
  border: 1px solid var(--linea);
  border-radius: var(--radio-pastilla);
  background: var(--papel-alto);
  color: var(--tinta-suave);
  font-size: var(--txt-sm);
  font-weight: 700;
  cursor: pointer;
  white-space: nowrap;
  transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}
.secciones-nav-item:hover { background: var(--papel); }
.secciones-nav-item--activa {
  background: var(--acento-solido);
  border-color: var(--acento-solido);
  color: var(--blanco-fijo);
}
/* margen de anclaje para que el scroll no tape el título bajo la sticky */
.seccion-ancla { scroll-margin-top: calc(var(--cabecera-alto) + 52px + env(safe-area-inset-top)); }

/* ---------- Filas clicables (usabilidad 2026-08-29) ---------- */
.tarjeta-clicable { cursor: pointer; }
.tarjeta-clicable:hover { border-color: var(--linea-fuerte); }
table tbody tr.tabla-fila-clicable { cursor: pointer; }
table tbody tr.tabla-fila-clicable:hover td { background: var(--papel); }
/* La fila se comporta como un botón, así que tiene que enfocarse como uno
   (auditoría 2026-09-02). El foco va en el <tr>, no en la celda: un anillo
   por celda partiría la fila en cinco cajas. */
table tbody tr.tabla-fila-clicable:focus-visible {
  outline: none;
  box-shadow: inset 0 0 0 2px var(--foco-borde);
}
table tbody tr.tabla-fila-clicable:focus-visible td { background: var(--papel); }

/* Contenedor de las alertas del panel: se emitía en app.js y no tenía ni
   una regla, así que el bloque de alertas iba pegado al listado. */
.alerta-contenedor { margin: var(--esp-3) 0; }

/* ---------- Checklist legal (ítem 17) ---------- */
.checklist-legal {
  display: flex;
  flex-wrap: wrap;
  gap: var(--esp-2);
}

/* ---------- Partidas en la visita (borrador, saaci360-nueva-visita-v2 Fase 2b) ---------- */
.lista-partidas {
  list-style: none;
  margin: 0 0 var(--esp-3);
  padding: 0;
  display: grid;
  gap: var(--esp-2);
}
.partida-borrador {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--esp-3);
  padding: var(--esp-2) var(--esp-3);
  border: 1px solid var(--linea);
  border-radius: var(--radio);
  background: var(--papel);
  font-size: var(--txt-base);
}
.partida-borrador-quitar {
  border: none;
  background: transparent;
  color: var(--tinta-suave);
  cursor: pointer;
  font-size: 0.95rem;
  padding: 0 var(--esp-1);
}
.partida-borrador-quitar:hover { color: var(--alerta); }

/* ---------- Tablero / KPI (.tablero, .kpi-*) — KPI strip
   (saaci360-rediseno-visual unidad 2). Ruled-grid look via `gap: 1px`
   over a --linea background, not per-card borders (design decision on
   the factory-by-factory map). Not yet adopted by any view — Unit 3. ---------- */

/* Las lineas del tablero las pinta CADA celda con `box-shadow` (que no
   ocupa espacio y lo recorta el `overflow: hidden` del contenedor), no el
   fondo del grid con `gap: 1px`.

   Por que: con el fondo-gris + gap, una ultima fila incompleta (5 KPIs en
   3 columnas) dejaba un bloque gris colgando donde no habia celda. La
   solucion de entonces fue fijar el nº de columnas inline en cada vista
   (`repeat(N, 1fr)`) — y eso derribo el diseño en movil: un estilo inline
   gana a cualquier media query, asi que 4 columnas seguian siendo 4 en una
   pantalla de 430px y el tablero se salia empujando la pagina entera.
   Quitando el motivo, `auto-fit` vuelve a mandar: 4 columnas en escritorio,
   2 en un movil, y ninguna celda fantasma. */
.tablero {
  /* FLEX Y NO GRID (2026-09-13). Con `auto-fit` la ultima fila incompleta
     —3 KPIs en 2 columnas, que es lo que pasa en un movil— dejaba una
     CELDA VACIA con su borde, y una caja vacia al lado de una cifra se lee
     como un dato que falta. Con flex, el ultimo elemento estira y ocupa lo
     que queda: ningun hueco, a cualquier numero de columnas. */
  display: flex;
  flex-wrap: wrap;
  gap: 0;
  background: var(--papel-alto);
  border: 1px solid var(--linea);
  border-radius: var(--radio);
  overflow: hidden;
  box-shadow: var(--elev-1);
  margin-bottom: var(--esp-4);
}

.tablero-kpi {
  display: flex;
  flex: 1 1 140px;
  min-width: 0;
  flex-direction: column;
  gap: var(--esp-1);
  background: var(--papel-alto);
  padding: var(--esp-4);
  box-shadow: 1px 0 0 var(--linea), 0 1px 0 var(--linea);
}

.kpi-etiqueta {
  font-family: var(--mono);
  font-size: var(--txt-xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--tinta-suave);
}

.kpi-valor {
  margin: 0;
  font-family: var(--display);
  font-size: 1.3rem;
  font-weight: 800;
  line-height: 1.1;
  color: var(--tinta);
  font-variant-numeric: tabular-nums;
}

.kpi-unidad {
  margin-left: var(--esp-1);
  font-family: var(--sans);
  font-size: var(--txt-sm);
  font-weight: 600;
  color: var(--tinta-suave);
}

.kpi-nota {
  margin: 0;
  font-size: var(--txt-xs);
  font-weight: 600;
  color: var(--tinta-suave);
}

.kpi-nota--exito { color: var(--exito-fuerte); }
.kpi-nota--alerta { color: var(--error-fuerte); }
.kpi-nota--neutro { color: var(--tinta-suave); }

.kpi-barra {
  height: 3px;
  border-radius: var(--radio-pastilla);
  background: var(--linea);
  overflow: hidden;
  margin-top: var(--esp-1);
}

.kpi-barra-relleno {
  height: 100%;
  background: var(--acento);
  border-radius: inherit;
}

/* ---------- Barra de filtros (.barra-filtros) — search + status pills
   (saaci360-rediseno-visual unidad 2, design decision 7). Built and
   available; not wired to any view in this unit — future list screens. ---------- */

.barra-filtros {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--esp-3);
  margin-bottom: var(--esp-4);

  /* PEGAJOSA BAJO LA CABECERA (2026-09-14). Con veintisiete obras, bajar
     tres pantallas y querer filtrar por origen obligaba a subir otras
     tres. Peor: desde abajo la barra no se ve, asi que no parece que
     este arriba — parece que NO ESTA. Isaac lo reporto tal cual: «el
     combo de origenes en el menu de obra se ha perdido». No se habia
     perdido; se habia ido con el scroll.

     Los margenes negativos y el relleno son para que el fondo tape la
     lista al pasar por debajo: una barra pegajosa translucida sobre
     tarjetas es peor que ninguna. */
  position: sticky;
  top: calc(var(--cabecera-alto) + env(safe-area-inset-top));
  z-index: 9;
  margin-left: calc(-1 * var(--esp-4));
  margin-right: calc(-1 * var(--esp-4));
  padding: var(--esp-3) var(--esp-4);
  background: var(--papel);
  border-bottom: 1px solid var(--linea);
}

.barra-buscar {
  display: flex;
  align-items: center;
  gap: var(--esp-2);
  flex: 1 1 220px;
  padding: var(--esp-2) var(--esp-3);
  border: 1px solid var(--linea);
  border-radius: var(--radio);
  background: var(--papel-alto);
  color: var(--tinta-suave);
}

.barra-buscar-icono { flex-shrink: 0; stroke: currentColor; }

.barra-buscar-control {
  flex: 1;
  border: none;
  background: transparent;
  color: var(--tinta);
  font-size: var(--txt-base);
  font-family: inherit;
}

.barra-buscar-control:focus-visible { outline: none; }
.barra-buscar:focus-within {
  border-color: var(--foco-borde);
  box-shadow: var(--foco);
}

/* Economia de la obra (2026-09-11) — presupuesto, descuento, neto y
   anticipo en la cabecera del Presupuesto. Es una CUENTA, asi que se lee
   como tal: importes alineados a la derecha, cifras tabulares para que las
   columnas de digitos caigan a plomo, y una regla sobre el total. */
.ppto-economia { display: flex; flex-direction: column; }

.ppto-economia-fila {
  display: flex;
  align-items: center;
  gap: var(--esp-3);
  padding: var(--esp-2) 0;
  border-bottom: 1px solid var(--linea);
}
.ppto-economia-fila:last-child { border-bottom: 0; }

.ppto-economia-fila > div { flex: 1; min-width: 0; display: flex; flex-direction: column; }

.ppto-economia-et { font-size: var(--txt-base); color: var(--tinta); font-weight: 600; }
.ppto-economia-nota { font-size: var(--txt-sm); color: var(--tinta-suave); }

.ppto-economia-val {
  font-variant-numeric: tabular-nums;
  font-size: var(--txt-lg);
  white-space: nowrap;
  text-align: right;
}

/* El descuento es lo unico que resta: se pinta como tal y en ningun otro
   sitio se usa este color, para que restar siempre se lea igual. */
.ppto-economia-fila--resta .ppto-economia-val { color: var(--acento); }

.ppto-economia-fila--total { border-top: 2px solid var(--tinta); }
.ppto-economia-fila--total .ppto-economia-val { font-weight: 700; }
.ppto-economia-fila--total .ppto-economia-et { font-weight: 700; }

/* Combo de dimension (2026-09-11) — para el filtro que NO es un enum
   corto. Mismo alto y mismo borde que la caja de busqueda para que lean
   como una sola fila de controles, no como un intruso. */
.barra-combo { display: flex; align-items: center; min-width: 0; }

.barra-combo-control {
  max-width: 16rem;
  padding: var(--esp-2) var(--esp-3);
  border: 1px solid var(--linea);
  border-radius: var(--radio);
  background: var(--papel-alto);
  color: var(--tinta);
  font-family: inherit;
  font-size: var(--txt-base);
}

.barra-combo-control:focus-visible {
  outline: 2px solid var(--acento);
  outline-offset: 1px;
}

.barra-estados {
  display: flex;
  flex-wrap: wrap;
  gap: var(--esp-2);
}

.pastilla {
  padding: var(--esp-1) var(--esp-3);
  border: 1px solid var(--linea-fuerte);
  border-radius: var(--radio-pastilla);
  background: var(--papel-alto);
  color: var(--tinta-suave);
  font-size: var(--txt-sm);
  font-weight: 700;
  cursor: pointer;
  box-shadow: 0 1px 2px rgba(42, 35, 32, 0.06);
  transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}

.pastilla:hover { background: var(--papel); }

.pastilla--activa {
  background: var(--acento-solido);
  border-color: var(--acento-solido);
  color: var(--blanco-fijo);
}

/* Fix (saaci360-listados-lectura): `.pastilla:hover` has higher specificity
   (element+pseudo-class) than `.pastilla--activa` (single class), so
   hovering a pressed pill silently reverted it to the unpressed
   background — latent since Fase 1, only now visibly exercised because
   pills are actually wired to a live filter. Reassert the pressed
   background on hover explicitly. */
.pastilla--activa:hover { background: var(--acento-solido); }

/* Tono tints (border+text token only, no new token) — only when NOT the
   pressed pill, so --acento-solido always wins regardless of rule order
   (design decision 4). */
.pastilla--ok:not(.pastilla--activa) { border-color: var(--exito); color: var(--exito-fuerte); }
.pastilla--aviso:not(.pastilla--activa) { border-color: var(--atencion); color: var(--aviso-fuerte); }
.pastilla--error:not(.pastilla--activa) { border-color: var(--alerta); color: var(--error-fuerte); }

/* ---------- Vacío (.vacio) — muted block, NOT a banner ---------- */

.vacio {
  margin: var(--esp-4) 0;
  padding: var(--esp-6) var(--esp-4);
  border: 1px dashed var(--linea-fuerte);
  border-radius: var(--radio);
  text-align: center;
  color: var(--tinta-suave);
  background: color-mix(in srgb, var(--papel-alto) 60%, transparent);
}

.vacio::before {
  content: '';
  display: block;
  width: 46px;
  height: 46px;
  margin: 0 auto var(--esp-3);
  border-radius: 50%;
  background: radial-gradient(circle at 50% 38%, var(--papel-alto) 0 26%, var(--acento-suave) 27% 100%);
  box-shadow: inset 0 0 0 1.5px var(--linea-fuerte);
}

.vacio-texto {
  margin: 0 0 var(--esp-2);
  font-size: var(--txt-base);
}

/* ---------- Cargando (.cargando) — skeleton ---------- */

.cargando-texto {
  margin: 0.75rem 0;
  color: var(--tinta-suave);
  font-size: var(--txt-base);
}

.cargando-linea,
.cargando-tarjeta {
  background: linear-gradient(90deg, var(--linea) 25%, var(--papel-alto) 37%, var(--linea) 63%);
  background-size: 400% 100%;
  animation: pulso-cargando 1.4s ease infinite;
  border-radius: var(--radio);
}

.cargando-linea {
  height: 2.2rem;
  margin: var(--esp-2) var(--esp-3);
}

.cargando-tarjeta {
  height: 4.5rem;
  margin-bottom: var(--esp-3);
}

@keyframes pulso-cargando {
  0% { background-position: 100% 50%; }
  100% { background-position: 0 50%; }
}

@media (prefers-reduced-motion: reduce) {
  .cargando-linea, .cargando-tarjeta { animation: none; }
}

/* ---------- Avisos (#avisos, .aviso) — toast ---------- */

#avisos {
  position: fixed;
  z-index: 40;
  top: calc(var(--esp-4) + env(safe-area-inset-top));
  right: var(--esp-4);
  left: var(--esp-4);
  display: flex;
  flex-direction: column;
  gap: var(--esp-2);
  pointer-events: none;
}

.aviso {
  pointer-events: auto;
  display: flex;
  align-items: flex-start;
  gap: var(--esp-2);
  max-width: 420px;
  margin-left: auto;
  padding: var(--esp-3) var(--esp-4);
  border-radius: var(--radio);
  border: 1px solid transparent;
  box-shadow: var(--elev-2);
  font-size: var(--txt-base);
}

.aviso-texto { margin: 0; flex: 1; }

.aviso-cerrar {
  border: none;
  background: transparent;
  color: inherit;
  font-size: var(--txt-lg);
  line-height: 1;
  cursor: pointer;
  padding: 0 var(--esp-1);
}

.aviso--info { background: var(--acento-suave); color: var(--acento); border-color: var(--acento); }
.aviso--ok { background: var(--exito-suave); color: var(--exito); border-color: var(--exito); }
.aviso--aviso { background: var(--atencion-suave); color: var(--atencion); border-color: var(--atencion); }
.aviso--error { background: var(--alerta-suave); color: var(--alerta); border-color: var(--alerta); }

/* ---------- Modal (.modal-fondo, .modal) ---------- */

.modal-fondo {
  position: fixed;
  inset: 0;
  z-index: 50;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--esp-4);
  background: rgba(0, 0, 0, 0.5);
}

.modal {
  width: 100%;
  max-width: 440px;
  max-height: calc(100vh - var(--esp-6) * 2);
  overflow-y: auto;
  background: var(--papel-alto);
  border-radius: var(--radio);
  box-shadow: var(--elev-2);
  padding: var(--esp-4);
}

.modal-titulo {
  margin: 0 0 var(--esp-3);
  font-family: var(--display);
  font-size: var(--txt-xl);
  font-weight: 800;
  letter-spacing: 0.01em;
  color: var(--tinta);
}

.modal-cuerpo { margin-bottom: var(--esp-4); }
.modal-cuerpo p { margin: 0; color: var(--tinta); font-size: var(--txt-base); }

.modal-ciclo-cuerpo {
  display: flex;
  flex-direction: column;
  gap: var(--esp-3);
  margin-bottom: var(--esp-4);
}

/* Formulario largo dentro de un modal (2026-09-11). Editar una obra son
   diez campos, y diez campos apilados a lo largo sin una sola division son
   una lista de la compra: todo pesa igual y no hay donde descansar la
   vista. Se agrupan por la PREGUNTA que contestan (quien / donde / cuando)
   y los cortos se emparejan, que ademas acorta el scroll a la mitad. */
.modal--ancho {
  max-width: 560px;
  /* El modal deja de hacer scroll EL MISMO y se lo cede al cuerpo, para que
     Cancelar/Guardar queden siempre visibles al pie. Un formulario de diez
     campos es mas alto que la pantalla, y un boton de guardar al que hay que
     hacer scroll para llegar es un boton que la mitad de la gente no
     encuentra. Solo aqui: los otros diez modales caben de sobra y no hay por
     que reestructurarles la caja. */
  display: flex;
  flex-direction: column;
  overflow: hidden;
  padding-bottom: 0;
}

.modal--ancho .modal-cuerpo {
  overflow-y: auto;
  min-height: 0;
  margin-bottom: 0;
  /* Sitio para la sombra del foco, que si no la recorta el overflow. */
  padding: 2px;
  margin-inline: -2px;
}

.modal--ancho .modal-pie {
  padding: var(--esp-3) 0 var(--esp-4);
  border-top: 1px solid var(--linea);
  background: var(--papel-alto);
}


.modal-grupo {
  display: flex;
  flex-direction: column;
  gap: var(--esp-3);
}

.modal-grupo + .modal-grupo {
  margin-top: var(--esp-4);
  padding-top: var(--esp-4);
  border-top: 1px solid var(--linea);
}

.modal-grupo-titulo {
  margin: 0;
  font-size: var(--txt-sm);
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--tinta-suave);
}

.modal-pareja {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--esp-3);
  align-items: start;
}

/* Un <select> se ancha hasta su opcion MAS LARGA, y un hijo de grid/flex
   trae `min-width: auto`, asi que se niega a encogerse por debajo de eso:
   el combo de Cliente reclamaba 380 de los 530 px y echaba el de Origen
   fuera del modal. `min-width: 0` le devuelve al carril el mando sobre el
   ancho; el `width: 100%` hace que el control ocupe su carril entero en vez
   de quedarse corto. Acotado a los grupos del modal — `.campo-control` sin
   ancho es lo correcto en el resto de la app. */
.modal-grupo > *,
.modal-pareja > * { min-width: 0; }

.modal-grupo .campo-control {
  width: 100%;
  max-width: 100%;
}

/* Por debajo de esto dos columnas dejan de ser dos columnas y pasan a ser
   dos campos estrujados. */
@media (max-width: 480px) {
  .modal-pareja { grid-template-columns: 1fr; }
}

.modal-ciclo-cuerpo p {
  margin: 0;
  color: var(--tinta);
  font-size: var(--txt-base);
}

.campo-check {
  display: flex;
  align-items: flex-start;
  gap: var(--esp-2);
  font-size: var(--txt-base);
  color: var(--tinta);
  cursor: pointer;
}

.campo-check input[type="checkbox"] {
  margin-top: 3px;
  width: 18px;
  height: 18px;
  accent-color: var(--acento);
  flex: none;
}

/* Barra de acción del ciclo de vida de obra (Consolidar/Descartar) —
   rediseñada 2026-08-28 tras feedback del usuario: prominente, arriba de
   la ficha, insignia de estado + botones, no una hoja() genérica con
   controles sueltos. */
/* Cabecera de obra (2026-08-29): datos identificativos fijos arriba */
.cabecera-obra {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--esp-2) var(--esp-4);
  padding: var(--esp-3) var(--esp-4);
  margin-bottom: var(--esp-2);
  background: var(--papel-alto);
  border: 1px solid var(--linea);
  border-radius: var(--radio);
  box-shadow: var(--elev-1);
}
.cabecera-obra-dato {
  display: inline-flex;
  align-items: baseline;
  gap: var(--esp-1);
}
.cabecera-obra-etiqueta {
  color: var(--tinta-suave);
  font-size: var(--txt-xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.cabecera-obra-valor {
  color: var(--tinta);
  font-size: var(--txt-sm);
  font-weight: 600;
}

/* Pantalla de partidas en lote (2026-08-29) */
.barra-alta-partida { display: flex; flex-wrap: wrap; gap: var(--esp-2); align-items: center; justify-content: flex-start; }
.barra-alta-partida .btn { flex: 0 0 auto; }
.barra-alta-partida .btn--bloque { width: auto; padding: var(--esp-3) var(--esp-4); margin-bottom: 0; }

/* ---------- Versionado de presupuesto (Fase 3) — tira de versiones. Ver
   .design/direction-ppto-versionado.md: chips pill, seleccionado = relleno
   azul solido, aceptada = verde, borrador = borde discontinuo. ---------- */
.ppto-tira {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--esp-2);
  margin-bottom: var(--esp-4);
}
.ppto-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--esp-2);
  padding: var(--esp-2) var(--esp-3);
  border: 1px solid var(--linea-fuerte);
  border-radius: var(--radio-pastilla);
  background: var(--papel-alto);
  color: var(--tinta-suave);
  font-family: var(--mono);
  font-size: var(--txt-xs);
  font-weight: 700;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}
.ppto-chip:hover { border-color: var(--acento); color: var(--tinta); }
.ppto-chip--sel {
  background: var(--acento-solido);
  border-color: var(--acento-solido);
  color: var(--blanco-fijo);
}
.ppto-chip--sel:hover { border-color: var(--acento-solido); color: var(--blanco-fijo); }
.ppto-chip--aceptada {
  background: var(--exito-suave);
  border-color: var(--exito);
  color: var(--exito-fuerte);
}
.ppto-chip--borrador {
  border-style: dashed;
  border-color: var(--linea-fuerte);
}
.ppto-chip-texto { white-space: nowrap; }
.ppto-chip-marca { font-size: var(--txt-xs); line-height: 1; }
.ppto-chip-punto {
  width: 8px; height: 8px;
  border-radius: 50%;
  background: var(--atencion);
}
/* Punto de "en curso": sobre el chip seleccionado (relleno azul solido) el
   naranja original quedaba ~1.6:1 de contraste — se resuelve a blanco. */
.ppto-chip--sel .ppto-chip-punto { background: var(--blanco-fijo); }
.ppto-pdf {
  margin-bottom: var(--esp-4);
  padding-bottom: var(--esp-3);
  /* Separa el grupo de estado/PDF del grupo de trabajo (tabla): antes el
     PDF quedaba fundido con la tabla, sin jerarquia. */
  border-bottom: 1px solid var(--linea);
}
.ppto-pdf-grupo {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--esp-2) var(--esp-3);
}

/* Nota bajo el boton de previsualizar (2026-09-11): dice que el borrador
   NO se archiva. Sin ella, «Previsualizar» y «Ver presupuesto» parecen dos
   formas de lo mismo, y la diferencia -uno deja fichero en Drive y el otro
   no- es justo la que importa. */
.ppto-pdf-nota {
  margin: var(--esp-1) 0 0;
  font-size: var(--txt-sm);
  color: var(--tinta-suave);
}
.ppto-pdf-enlace {
  display: inline-flex;
  align-items: center;
  gap: var(--esp-2);
  color: var(--acento);
  font-weight: 700;
  text-decoration: none;
}
.ppto-pdf-enlace::before { content: '↗'; }
.ppto-pdf-enlace:hover { text-decoration: underline; }
.ppto-cesta-config { display: flex; flex-wrap: wrap; gap: var(--esp-2) var(--esp-3); }
.ppto-cesta-config .campo { flex: 1 1 100%; }
.ppto-cesta-contador { color: var(--tinta-suave); font-size: var(--txt-sm); margin: var(--esp-2) 0; }
.ppto-cesta-grid { display: flex; flex-direction: column; gap: var(--esp-1); margin-bottom: var(--esp-4); }

/* Catálogo navegable por capítulos (2026-09-02). Antes el catálogo solo se
   pintaba al escribir en el buscador: para añadir una partida había que
   saberse su nombre de memoria. Un catálogo se hojea, no se recuerda.
   <details>/<summary> nativos: teclado y lector de pantalla gratis. */
.ppto-cesta-grupo-titulo {
  margin: var(--esp-3) 0 var(--esp-1); font-size: var(--txt-xs); font-weight: 700;
  letter-spacing: .6px; text-transform: uppercase; color: var(--tinta-suave);
}
.ppto-cesta-capitulo { border: 1px solid var(--linea); border-radius: var(--radio); background: var(--papel); }
.ppto-cesta-capitulo-titulo {
  display: flex; align-items: center; justify-content: space-between; gap: var(--esp-2);
  min-height: 44px; padding: var(--esp-2) var(--esp-3); cursor: pointer;
  font-size: var(--txt-sm); font-weight: 600; color: var(--tinta);
  list-style: none; border-radius: var(--radio);
}
.ppto-cesta-capitulo-titulo::-webkit-details-marker { display: none; }
.ppto-cesta-capitulo-titulo::after {
  content: '›'; margin-left: auto; padding-left: var(--esp-2);
  color: var(--tinta-suave); transform: rotate(90deg); transition: transform .15s ease;
}
.ppto-cesta-capitulo[open] > .ppto-cesta-capitulo-titulo::after { transform: rotate(270deg); }
.ppto-cesta-capitulo[open] > .ppto-cesta-capitulo-titulo { border-bottom: 1px solid var(--linea); border-radius: var(--radio) var(--radio) 0 0; }
.ppto-cesta-capitulo > .ppto-cesta-fila { border: 0; border-radius: 0; background: transparent; }
.ppto-cesta-capitulo > .ppto-cesta-fila + .ppto-cesta-fila { border-top: 1px solid var(--linea); }
@media (prefers-reduced-motion: reduce) {
  .ppto-cesta-capitulo-titulo::after { transition: none; }
}
.ppto-cesta-fila {
  display: flex; align-items: center; gap: var(--esp-3);
  padding: var(--esp-2) var(--esp-3);
  background: var(--papel-alto); border: 1px solid var(--linea); border-radius: var(--radio);
}

/* Fila de partida del catálogo pendiente de añadir (editor de presupuesto):
   borde discontinuo + tinte azulado para distinguirla de las ya existentes. */
.ppto-cesta-fila--nueva { border-style: dashed; border-color: var(--acento); background: var(--acento-suave); }
.ppto-cesta-fila-info { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; }
.ppto-cesta-partida { color: var(--tinta); font-size: var(--txt-sm); font-weight: 600; }
.ppto-cesta-meta { color: var(--tinta-suave); font-size: var(--txt-xs); }
.ppto-cesta-cantidad {
  width: 76px; flex: 0 0 76px;
  padding: var(--esp-2) var(--esp-3);
  border: 1px solid var(--linea-fuerte); border-radius: var(--radio);
  font-size: var(--txt-base); text-align: right;
}
.ppto-cesta-cantidad:focus { outline: none; border-color: var(--acento); box-shadow: var(--foco); }

/* Ventanas de funcionalidad (Obra 360 refactor 2026-08-30): rejilla de botones */
.obra-panel {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: var(--esp-2);
}
/* Ficha de obra limpia (2026-08-30) */
.ficha-cabecera {
  background: var(--papel-alto);
  border: 1px solid var(--linea);
  border-radius: var(--radio);
  padding: var(--esp-3) var(--esp-4);
  margin-bottom: var(--esp-3);
  box-shadow: var(--elev-1);
}
.ficha-cabecera-estado { display: flex; align-items: center; gap: var(--esp-2); margin-bottom: var(--esp-2); }
.ficha-cabecera-nombre { font-size: var(--txt-xl); font-weight: 700; color: var(--tinta); }
.ficha-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: var(--esp-2) var(--esp-3); }
.ficha-item { display: flex; flex-direction: column; gap: 2px; }
.ficha-etiqueta { font-size: var(--txt-xs); color: var(--tinta-suave); text-transform: uppercase; letter-spacing: .04em; }
.ficha-valor { font-size: var(--txt-sm); color: var(--tinta); font-weight: 600; }
.ficha-detalles { margin-bottom: var(--esp-2); }

/* Obra 360 rediseñada (mockup aprobado 2026-08-30) */
.obra-hero {
  position: relative;
  background: var(--tarjeta);
  border-radius: var(--radio);
  box-shadow: var(--elev-1);
  padding: var(--esp-4) var(--esp-5);
  display: block;
}
.obra-hero-acciones {
  position: absolute; top: var(--esp-3); right: var(--esp-3);
  display: flex; gap: var(--esp-1); flex-wrap: wrap; max-width: 100%;
}
.hero-icono {
  width: 36px; height: 36px; border-radius: 10px;
  border: 1px solid var(--linea); background: var(--papel);
  font-size: 16px; line-height: 1; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  transition: background .15s ease, border-color .15s ease, transform .15s ease;
}
.hero-icono:hover { background: var(--acento-suave); border-color: var(--azul2); transform: translateY(-1px); }
.obra-hero-badge { display: inline-flex; align-items: center; }
.obra-hero-nombre { font-size: var(--txt-xl); font-weight: 800; margin-top: var(--esp-2); padding-right: 150px; }
.obra-hero-meta {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: var(--esp-3) var(--esp-5);
  margin-top: var(--esp-3);
}
/* etiqueta arriba, valor debajo — los spans iban inline pegados ("CLIENTEFley") */
.obra-hero-meta > div {
  display: flex;
  flex-direction: column;
  gap: 3px;
}
.obra-hero-meta .ficha-hero-dato { min-width: 0; }
.obra-hero-meta .lbl { font-size: var(--txt-xs); color: var(--tinta-suave); text-transform: uppercase; letter-spacing: .05em; font-weight: 600; }
.obra-hero-meta .val { font-size: var(--txt-sm); font-weight: 600; }

.acciones-ciclo { display: flex; gap: var(--esp-2); margin-top: var(--esp-3); flex-wrap: wrap; }
.acciones-ciclo .btn { flex: 0 0 auto; }

.obra-panel { display: grid; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); gap: var(--esp-3); }
.obra-panel-btn {
  display: flex; flex-direction: column; gap: var(--esp-2);
  padding: var(--esp-4);
  background: var(--tarjeta);
  border: 1px solid var(--linea);
  border-radius: var(--radio);
  text-decoration: none;
  box-shadow: var(--elev-1);
  transition: transform .15s ease, box-shadow .15s ease, border-color .15s ease;
}
.obra-panel-btn:hover { transform: translateY(-3px); border-color: var(--azul2); box-shadow: var(--elev-2); }
.obra-panel-ico { width: 42px; height: 42px; border-radius: 12px; display: flex; align-items: center; justify-content: center; color: var(--acento-solido); background: var(--acento-suave); }
.obra-panel-nombre { font-weight: 700; font-size: var(--txt-base); color: var(--tinta); }
.obra-panel-sub { font-size: var(--txt-xs); color: var(--tinta-suave); line-height: 1.35; }

/* Funcionalidades en movil: mosaico de iconos, no tarjetas apiladas
   (2026-09-04, reportado con captura).

   El suelo de 210px de `auto-fill` esta pensado para un monitor: en los
   ~398px utiles de un movil no caben dos, asi que daba UNA columna y los
   cinco destinos se apilaban en ~650px. Un menu que no se ve entero no es
   un menu, es una lista — y obliga a hacer scroll para elegir.

   En escritorio la tarjeta descriptiva se gana su sitio: el subtitulo se
   lee y orienta. En movil se paga su altura sin cobrar el beneficio, y es
   justo el subtitulo lo que la infla ("Avance y certificacion" envuelve a
   tres lineas en una celda de 126px) mientras el nombre ya se explica
   solo. Se cambia la FORMA, no el contenido: mismo marcado, mismos
   enlaces, mismos iconos — tres columnas, icono arriba y etiqueta debajo.
   Cinco destinos entran en dos filas y caben de un vistazo. */
@media (max-width: 640px) {
  .obra-panel { grid-template-columns: repeat(3, 1fr); gap: var(--esp-2); }
  .obra-panel-btn {
    align-items: center;
    text-align: center;
    gap: var(--esp-2);
    padding: var(--esp-3) var(--esp-2);
  }
  .obra-panel-sub { display: none; }
  .obra-panel-nombre { font-size: var(--txt-sm); line-height: 1.2; }
  /* Sin `translateY` en tactil: no hay hover que lo dispare y en un tap
     se queda a medias. */
  .obra-panel-btn:hover { transform: none; }
}

/* Pantallas muy estrechas (iPhone SE y similares): tres columnas dejan la
   etiqueta mas larga partida en tres lineas. Dos columnas respiran. */
@media (max-width: 360px) {
  .obra-panel { grid-template-columns: repeat(2, 1fr); }
}

.seccion-obra-cab { display: flex; align-items: center; gap: var(--esp-2); margin-bottom: var(--esp-3); flex-wrap: wrap; }
.seccion-obra-cab h2 { font-size: var(--txt-xl); font-weight: 800; }

.barra-ciclo-obra {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--esp-3);
  padding: var(--esp-3) var(--esp-4);
  margin-bottom: var(--esp-4);
  background: var(--papel-alto);
  border: 1px solid var(--linea);
  border-radius: var(--radio);
  box-shadow: var(--elev-1);
}

.barra-ciclo-obra-botones {
  display: flex;
  gap: var(--esp-2);
  margin-left: auto;
  flex-wrap: wrap;
}

.modal-pie {
  display: flex;
  justify-content: flex-end;
  gap: var(--esp-2);
}

[inert] { pointer-events: none; }

/* ---------- Utilidades ---------- */

@media (max-width: 380px) {
  main { padding: 0.9rem 0.75rem 1.25rem; }
  .marca { font-size: 1.02rem; }
}

/* =================================================================
   Escritorio/portátil (pwa-design-system, unidad 3) — progressive
   enhancement puro CSS sobre el MISMO DOM: el <nav> inferior (bottom
   tab bar) pasa a barra lateral izquierda solo por media query, sin
   JS ni marcado duplicado (design decision 8). 1024px, no 768px —
   768–834px CSS es iPad en vertical, contexto táctil que debe seguir
   en la barra inferior (design decision 7).
   ================================================================= */

@media (min-width: 1024px) {
  body {
    padding-bottom: 0;
  }

  .nav {
    /* Empieza DEBAJO de la cabecera (no top:0) para que el degradado de
       la cabecera quede a todo ancho, ininterrumpido (design decision 8). */
    top: calc(var(--cabecera-alto) + env(safe-area-inset-top));
    bottom: 0;
    right: auto;
    left: 0;
    width: var(--nav-ancho);
    flex-direction: column;
    align-items: stretch;
    justify-content: flex-start;
    gap: 0.2rem;
    padding: var(--esp-5) var(--esp-3) var(--esp-4);
    border-top: none;
    border-right: 1px solid var(--linea);
    box-shadow: 6px 0 20px -14px rgba(28, 27, 23, 0.18);
  }

  .nav a {
    flex: none;
    flex-direction: row;
    justify-content: flex-start;
    gap: var(--esp-3);
    padding: var(--esp-3) var(--esp-3);
    font-size: var(--txt-base);
  }

  /* La cabecera sigue a todo ancho (design decision 8) — solo el
     contenido bajo ella se indenta el ancho de la barra lateral. */
  main,
  .pie,
  #estado-global.banner {
    margin-left: var(--nav-ancho);
    /* respiro entre el nav lateral y el contenido (feedback 2026-08-30:
       el titulo rozaba el borde del nav) */
    padding-left: var(--esp-4);
  }

  main,
  .pie {
    max-width: 1080px;
  }

  /* Los formularios de SaaciUI usan el ancho extra: campos en fila en
     vez de siempre apilados (design.md's componentes.html structure). */
  .formulario-campos > .campo {
    flex: 1 1 220px;
  }
}

/* ---------- Gantt por fases (Planificación y Avance, 2026-08-30) ---------- */
.gantt { display: flex; flex-direction: column; gap: var(--esp-2); font-size: 0.86rem; }
.gantt-cab { display: grid; grid-template-columns: minmax(120px, 200px) 1fr; align-items: center; gap: var(--esp-3); }
.gantt-fila { display: grid; grid-template-columns: minmax(120px, 200px) 1fr; align-items: center; gap: var(--esp-3); }
.gantt-etiqueta { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.gantt-fase { color: var(--tinta); font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.gantt-fechas { color: var(--tinta-suave); font-size: var(--txt-xs); font-variant-numeric: tabular-nums; white-space: nowrap; }
.gantt-linea { position: relative; height: 14px; }
.gantt-marca { position: absolute; top: 0; transform: translateX(-50%); color: var(--tinta-suave); font-size: var(--txt-xs); font-family: var(--mono); }
.gantt-pista { position: relative; height: 28px; background: var(--papel); border: 1px solid var(--linea); border-radius: var(--radio); }
.gantt-barra { position: absolute; top: 3px; bottom: 3px; border-radius: var(--radio-pastilla); display: flex; align-items: center; padding: 0 var(--esp-3); overflow: hidden; cursor: default; }
.gantt-barra-texto { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: var(--blanco-fijo); font-weight: 600; font-size: var(--txt-xs); }
.gantt-barra--ok { background: var(--exito); }
.gantt-barra--aviso { background: var(--atencion); }
.gantt-barra--neutro { background: var(--tinta-suave); }
.gantt-tooltip { display: none; position: absolute; bottom: calc(100% + 6px); left: 0; background: var(--tinta); color: var(--papel-alto); padding: var(--esp-2) var(--esp-3); border-radius: var(--radio); font-size: var(--txt-xs); white-space: nowrap; z-index: 20; pointer-events: none; }
.gantt-barra:hover .gantt-tooltip { display: block; }
@media (max-width: 640px) {
  .gantt-cab, .gantt-fila { grid-template-columns: 1fr; gap: var(--esp-1); }
  .gantt-etiqueta { flex-direction: row; justify-content: space-between; align-items: baseline; }
  .gantt-fila { padding-bottom: var(--esp-2); border-bottom: 1px solid var(--linea); }
}

/* ---- Cesta del presupuesto ----
   Aquí vivían 27 reglas de la cesta como pantalla aparte (.ppto-cesta-zona-*,
   -seleccionadas, -item-*, -tabla*, -subtotal, .lk-anulado). C2 metió la
   cesta DENTRO de la pestaña Presupuesto y ninguna de ellas tenía ya un solo
   usuario en app.js; varias apuntaban además a --texto-suave y --regla, dos
   tokens que no existen en la hoja. Retiradas en la auditoría de 2026-09-02.
   Lo vivo de la cesta actual está más arriba (.ppto-cesta-capitulo/-fila/
   -cantidad/-grid…), junto al editor. */
.ppto-cesta-badge{display:inline-flex;align-items:center;justify-content:center;min-width:22px;height:22px;
  padding:0 7px;border-radius:999px;background:var(--linea);color:var(--tinta);font-size:12px;font-weight:700;}

/* ---- Previsualización a pantalla completa ---- */
.ppto-preview-overlay{position:fixed;inset:0;background:var(--linea);z-index:1000;display:flex;flex-direction:column;}
.ppto-preview-barra{display:flex;align-items:center;gap:12px;padding:12px 20px;background:var(--papel);border-bottom:1px solid var(--regla);}
.ppto-preview-titulo{font-weight:700;font-size:16px;margin-right:auto;}
.ppto-preview-iva{font-size:14px;color:var(--texto-suave);}
.ppto-preview-iva select{padding:6px 8px;border:1px solid var(--linea);border-radius:6px;}
.ppto-preview-lienzo{flex:1;width:100%;border:0;background:var(--blanco-fijo);}

/* ---------- Visor de documentos, modo blob (galeria-documentos-obra) ----
   `.ppto-preview-lienzo` ya da flex:1 + ancho completo a estos tres — solo
   se afina lo que cambia por tipo de contenido. */
.docs-visor-imagen { display: flex; align-items: center; justify-content: center; overflow: auto; padding: var(--esp-4); }
.docs-visor-img { max-width: 100%; max-height: 100%; object-fit: contain; }
.docs-visor-objeto { display: block; }
.docs-visor-fallback {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: var(--esp-3); height: 100%; padding: var(--esp-6) var(--esp-4);
  text-align: center; color: var(--tinta-suave);
}
.docs-visor-fallback-nombre { font-size: var(--txt-lg); font-weight: 700; color: var(--tinta); margin: 0; word-break: break-word; }
.docs-visor-fallback-tam { font-size: var(--txt-sm); margin: 0; }
.docs-visor-fallback-acciones { display: flex; gap: var(--esp-3); flex-wrap: wrap; justify-content: center; }

/* PDF vía pdf.js (2026-09-17) — páginas apiladas y con scroll, como
   cualquier visor de PDF real; ver docstring de abrirDocumento_. */
.docs-visor-pdf { overflow-y: auto; display: flex; flex-direction: column; align-items: center; gap: var(--esp-3); padding: var(--esp-4); background: var(--regla); }
.docs-visor-pdf-cargando { color: var(--tinta-suave); }
.docs-visor-pdf-pagina { max-width: 100%; background: var(--blanco-fijo); box-shadow: 0 1px 4px rgba(0,0,0,.2); }


/* ---------------------------------------------------------------
   Ciclo del dinero: Presupuesto · Avance · Certificaciones
   Una sola barra compartida por las tres secciones (2026-09-02).
   --------------------------------------------------------------- */
.pestanas-dinero { display: flex; border: 1px solid var(--linea); background: var(--papel-alto); margin-bottom: 16px; }
.pestanas-dinero .pestana { flex: 1; padding: 10px 8px; text-align: center; text-decoration: none; color: var(--tinta-suave); border-right: 1px solid var(--linea); }
.pestanas-dinero .pestana:last-child { border-right: 0; }
.pestanas-dinero .pestana--activa { background: var(--papel); color: var(--tinta); box-shadow: inset 0 -3px 0 var(--exito); }
.pestana-nombre { display: block; font-weight: 700; font-size: .95rem; }
.pestana-sub { display: block; font-size: .72rem; opacity: .75; margin-top: 1px; }

/* Avance: barra de tres estados + lista marcable */
.avance-cabecera { margin-bottom: 14px; }
/* Tres estados, no dos: certificado (cobrable), medido sin certificar (el
   dinero trabajando gratis) y pendiente. El porcentaje va DENTRO del tramo
   cuando cabe — un número flotando fuera obliga a emparejarlo a ojo. */
.avance-barra { display: flex; height: 26px; border-radius: 6px;
  border: 1px solid var(--linea-fuerte); background: var(--linea); overflow: hidden; }
.avance-barra > span { display: flex; align-items: center; justify-content: center;
  font-size: var(--txt-xs); font-weight: 700; color: var(--blanco-fijo);
  font-variant-numeric: tabular-nums; overflow: hidden; white-space: nowrap;
  transition: width .25s ease; }
.avance-barra-cert { background: var(--exito); }
.avance-barra-ejec { background: var(--atencion); }
.avance-leyenda { display: flex; flex-wrap: wrap; gap: 6px 22px; margin: 10px 0 0;
  font-size: var(--txt-sm); color: var(--tinta-suave); }
.avance-leyenda-item i { display: inline-block; width: 10px; height: 10px;
  border-radius: 3px; margin-right: 7px; }
.avance-leyenda-item b { color: var(--tinta); font-weight: 700;
  font-variant-numeric: tabular-nums; }
.avance-capitulo { padding: 6px 12px; background: var(--papel-alto); border: 1px solid var(--linea); border-bottom: 0; font-size: .72rem; letter-spacing: .08em; text-transform: uppercase; color: var(--tinta-suave); }
/* Cabecera de capítulo con su propio avance (2026-09-02): el total de la
   obra dice si vas bien; el del capítulo dice DÓNDE estás atascado, que es
   lo accionable. Misma codificación de color que la barra global. */
.avance-capitulo-cab { display: flex; align-items: center; gap: 10px; }
.avance-capitulo-nombre { flex: 1; min-width: 0; }
.avance-capitulo-barra { flex: 0 0 96px; display: flex; height: 8px; border-radius: 999px; background: var(--linea); overflow: hidden; }
.avance-capitulo-barra > span { display: block; background: var(--atencion); transition: width .25s ease; }
.avance-capitulo-pc { flex: 0 0 auto; min-width: 4ch; text-align: right; font-variant-numeric: tabular-nums; color: var(--tinta); font-weight: 700; }
.avance-item { display: grid; gap: 6px; padding: 10px 12px; border: 1px solid var(--linea); border-bottom: 0; background: var(--papel); }
/* En escritorio la caja de cantidad va A LA DERECHA del nombre, no debajo:
   apilada duplicaba la altura de cada fila y una obra de 60 partidas se
   volvía un muro. En móvil se mantiene apilada, que ahí sí hace falta.

   REJILLA, no flex (2026-09-02): con `justify-content: flex-end` cada fila
   se alineaba a su propio borde, y como el texto de la unidad mide distinto
   en cada partida («de 6 m2» vs «de 120 ml»), las cajas quedaban bailando
   en zig-zag. Columnas fijas y compartidas: input, unidad y botón caen
   siempre en la misma vertical. Y con ratón no hacen falta 44px de alto —
   ese es el mínimo del DEDO, no del cursor. */
@media (min-width: 760px) {
  .avance-item {
    grid-template-columns: 1fr max-content;
    align-items: center; gap: 6px 16px; padding: 4px 12px;
  }
  .avance-control { display: grid; grid-template-columns: 84px 8ch auto; gap: 10px; align-items: center; }
  .avance-control .avance-cantidad { width: 100%; min-height: 34px; padding: 4px 8px; font-size: .95rem; }
  .avance-control .avance-meta { white-space: nowrap; }
  .avance-control .btn { min-height: 34px; padding-top: 4px; padding-bottom: 4px; }
  .avance-capitulo { padding: 5px 12px; }
}
.avance-lista > .avance-item:last-child { border-bottom: 1px solid var(--linea); }
.avance-item--exceso { background: var(--alerta-suave); }
.avance-nombre { font-weight: 600; font-size: .92rem; line-height: 1.3; }
/* Las tres cifras que gobiernan la fila: contratado, ya certificado y lo
   que queda. Visibles siempre — sin ellas el tope del input es una regla
   invisible que solo se descubre al fallar. */
.avance-info { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
/* REJILLA de anchos fijos, no flex: las cuatro magnitudes caen en la misma
   vertical entre filas, así el ojo compara columnas en vez de leer frases.
   (Mockup 2026-09-02, regla 02.) */
.avance-cifras { display: grid; grid-template-columns: repeat(4, minmax(58px, auto));
  gap: 2px 18px; justify-content: start; }
.avance-cifra { display: flex; flex-direction: column; line-height: 1.25; }
.avance-cifra b { font-weight: 700; color: var(--tinta); font-size: .8rem;
  font-variant-numeric: tabular-nums; }
.avance-cifra i { font-style: normal; text-transform: uppercase; letter-spacing: .06em;
  font-size: .62rem; color: var(--tinta-suave); }
.avance-cifra--cert b { color: var(--exito-fuerte); }
.avance-cifra--queda b { color: var(--acento); }
@media (max-width: 700px) { .avance-cifras { grid-template-columns: repeat(2, 1fr); } }
/* Estado de cada certificación en la tira: el color dice en qué punto del
   ciclo está sin tener que leerlo. */
/* El estado va en una pastilla DENTRO del chip, no pegado al número con un
   punto: "Cert. 4 · borrador" se lee como una sola frase larga; separado,
   el número identifica y la pastilla dice en qué punto del ciclo está. */
.ppto-chip--cargando { opacity: .6; cursor: progress; }
.cert-chip-estado { margin-left: 8px; padding: 1px 7px; border-radius: 999px;
  font-size: .64rem; font-weight: 700; text-transform: uppercase;
  letter-spacing: .06em; background: var(--linea); color: var(--tinta-suave); }
.cert-chip--borrador { border-style: dashed; border-color: var(--acento); }
.cert-chip--borrador .cert-chip-estado { background: var(--acento-suave); color: var(--acento); }
.cert-chip--enviada { border-color: var(--atencion); }
.cert-chip--enviada .cert-chip-estado { background: var(--atencion-suave); color: var(--atencion); }
.cert-chip--aprobada { border-color: var(--exito); }
.cert-chip--aprobada .cert-chip-estado { background: var(--exito-suave); color: var(--exito-fuerte); }
.avance-control { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
/* 44px de alto: por debajo de eso no se acierta con guantes puestos. */
.avance-cantidad { width: 96px; min-height: 44px; padding: 8px 10px; font-size: 1rem; text-align: right; font-variant-numeric: tabular-nums; border: 1px solid var(--linea-fuerte); border-radius: 6px; background: var(--papel); color: var(--tinta); }
.avance-meta { font-size: .82rem; color: var(--tinta-suave); }
.avance-cantidad--lleno { border-color: var(--atencion); background: var(--atencion-suave); }
.avance-cantidad:focus-visible { outline: 2px solid var(--acento); outline-offset: 1px;
  border-color: var(--acento); }
.avance-acciones { position: sticky; bottom: 0; display: flex; flex-wrap: wrap; gap: 10px; padding: 12px 0; background: var(--papel); border-top: 1px solid var(--linea); margin-top: 14px; }
.avance-acciones .btn { flex: 1 1 220px; }

/* Por qué el botón de cerrar está apagado. Va debajo de la barra pegajosa,
   no dentro: dentro empujaría los botones y la barra bailaría de alto cada
   vez que cambia el motivo. */
/* Aviso de avance bloqueado: UNA linea. Sustituye a un banner de dos que
   repetia el chip de arriba y los botones de abajo — tres sitios diciendo
   lo mismo, y en el movil medio pantallazo. Aqui va solo lo que no dice
   nadie mas: que el avance esta bloqueado, y por cual. */
.avance-bloqueo {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0 0 var(--esp-3, 12px);
  padding: 8px 12px;
  border-radius: 10px;
  border: 1px solid var(--linea);
  background: var(--papel-alto);
  color: var(--tinta-suave);
  font-size: var(--txt-sm);
  line-height: 1.35;
}

.avance-bloqueo-icono { flex: 0 0 auto; font-size: 0.95em; }

.avance-motivo {
  margin: 0;
  padding: 2px 0 10px;
  font-size: var(--txt-sm);
  color: var(--tinta-suave);
  text-align: center;
}

/* Desglose de la certificación en el diálogo de confirmación: emitir mueve
   dinero, así que se confirma sobre los números, no sobre una frase. */
.confirmar-cuerpo { display: flex; flex-direction: column; gap: var(--esp-3); }
.cert-previa { display: flex; flex-direction: column; gap: 2px; }
.cert-previa-linea { display: flex; justify-content: space-between; gap: var(--esp-3); padding: 6px 0; font-size: var(--txt-sm); color: var(--tinta-suave); border-bottom: 1px solid var(--linea); }
.cert-previa-linea--total { color: var(--tinta); font-weight: 700; }
.cert-previa-linea:last-child { border-bottom: 0; }
.cert-previa-valor { font-variant-numeric: tabular-nums; white-space: nowrap; }

/* =================================================================
   Barra inferior: 5 destinos, no 9 (2026-09-03)
   -----------------------------------------------------------------
   Esta barra se diseñó para 6 ítems y su comentario ya avisaba de que
   el ellipsis era "el respaldo, no el resultado esperado". Llegó a 8 al
   añadir Facturas, y en un iPhone de 375px cada uno se queda en ~43px:
   "Trabajadores" y "Proveedores" se cortan a "Trab…"/"Prov…". Una
   etiqueta cortada no es una etiqueta, es un icono con ruido debajo.

   Se sigue la convención de las barras de pestañas nativas: 4 destinos
   del día a día (Obras · Cuadro · Horas · Facturas) + "Más" para los
   cuatro registros de mantenimiento, que se visitan de uvas a peras.
   En escritorio la barra es una columna lateral y CABEN LOS 8, así que
   ahí no se esconde nada ni aparece el botón.
   ================================================================= */

.nav-mas {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.2rem;
  padding: 0.35rem 0.2rem;
  background: none;
  border: 0;
  border-left: 3px solid transparent;
  border-radius: var(--radio);
  color: var(--tinta-suave);
  font-family: inherit;
  font-size: 0.68rem;
  font-weight: 600;
  cursor: pointer;
}
.nav-mas span { min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.nav-mas svg { stroke: currentColor; }
.nav-mas.activo { color: var(--acento); background: var(--acento-suave); }
.nav-mas.activo svg { stroke: var(--acento); }

@media (max-width: 1023px) {
  .nav .nav-secundario { display: none; }
}

@media (min-width: 1024px) {
  .nav-mas { display: none; }
}

/* Rejilla del panel "Más": destinos grandes, no una lista de enlaces
   finos. Se abre con el pulgar en la parte baja de la pantalla. */
.mas-rejilla { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--esp-3); }
.mas-destino {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: var(--esp-2); padding: var(--esp-5) var(--esp-3);
  min-height: 88px;
  background: var(--papel-alto); border: 1px solid var(--linea);
  border-radius: var(--radio); color: var(--tinta);
  text-decoration: none; font-weight: 600; font-size: var(--txt-sm);
  text-align: center;
}
.mas-destino.activo { border-color: var(--acento); background: var(--acento-suave); color: var(--acento); }
.mas-destino svg { stroke: currentColor; }

/* =================================================================
   Objetivos táctiles (2026-09-03)
   -----------------------------------------------------------------
   `.btn` quedaba en ~38px de alto y `.btn--compacto` en ~26px. El
   mínimo cómodo con el dedo son 44px; con ratón no hacen falta, y esa
   distinción ya estaba escrita en la hoja para las cajas de cantidad
   del avance ("ese es el mínimo del DEDO, no del cursor"). Aquí se
   aplica la misma regla al resto de botones, acotada a punteros
   gruesos para no engordar el escritorio.
   ================================================================= */

@media (pointer: coarse) {
  .btn { min-height: 44px; }
  .btn.btn--compacto { min-height: 38px; padding: var(--esp-2) var(--esp-3); }
  .nav a, .nav-mas { min-height: 52px; justify-content: center; }
}

/* =================================================================
   Aviso de instalacion (2026-09-03)
   -----------------------------------------------------------------
   Un amigo del usuario no supo instalarla en su iPhone. No era un fallo
   del manifiesto ni de las etiquetas apple-*: es que NO HABIA NADA que
   lo ofreciera. En Android el navegador da su propia pista; en iOS no
   hay ninguna, y "Añadir a pantalla de inicio" vive escondido dentro
   del menu Compartir. Una PWA que no dice como se instala no se
   instala.
   ================================================================= */

.instalar {
  display: flex; align-items: center; gap: var(--esp-3);
  margin: 0 0 var(--esp-4); padding: var(--esp-4);
  background: var(--acento-suave); border: 1px solid var(--acento);
  border-radius: var(--radio); font-size: var(--txt-sm);
}
.instalar-icono { flex: 0 0 auto; color: var(--acento); display: flex; }
.instalar-texto { flex: 1; min-width: 0; }
.instalar-texto strong { display: block; color: var(--tinta); }
.instalar-texto span { color: var(--tinta-suave); }
.instalar-cerrar {
  flex: 0 0 auto; background: none; border: 0; cursor: pointer;
  color: var(--tinta-suave); font-size: 1.3rem; line-height: 1;
  padding: var(--esp-2);
}
@media (pointer: coarse) { .instalar-cerrar { min-width: 44px; min-height: 44px; } }
@media (min-width: 1024px) { #instalar.instalar { margin-left: var(--nav-ancho); } }
/* Dentro de la portada el aviso va apilado en la caja, sin margen lateral
   y a todo su ancho: ahi no hay barra de navegacion que esquivar. */
.portada-caja .instalar { margin: var(--esp-3) 0 0; width: 100%; text-align: left; }

/* =================================================================
   Tablas en movil: tarjetas, no scroll lateral (2026-09-03)
   -----------------------------------------------------------------
   Reportado por el usuario: "las tablas con tanto scroll horizontal".
   El caso peor es el presupuesto — seis columnas mas la accion, y la
   UNICA que hay que poder leer, "Capitulo · Partida", es justo la que
   se recorta con elipsis para que quepan las demas. Se lee el importe
   de una partida cuyo nombre no se ve.

   La salida NO es esconder columnas: cantidad, unidad y precio son
   datos reales que se necesitan. Es cambiar la forma. Debajo de 640px
   cada fila pasa a ser una TARJETA: el identificador arriba, a todo el
   ancho y sin recortar, y el resto de valores debajo como
   `etiqueta: valor`. Cero scroll lateral y el nombre entero legible.

   Sin JavaScript: `<thead>` desaparece y cada celda se presenta sola
   con `content: attr(data-etiqueta)`, que `tabla()` ya escribe. Una
   media query, sin escuchadores de resize ni segundo arbol de DOM que
   se desincronice del primero.

   `columna.movil: 'oculta'` sigue disponible para la columna que de
   verdad no aporte nada en un movil — pero es la excepcion, no el
   mecanismo.
   ================================================================= */

@media (max-width: 640px) {
  .tabla-envoltorio { overflow-x: visible; border: 0; box-shadow: none; background: none; }
  .tabla-envoltorio table,
  .tabla-envoltorio tbody,
  .tabla-envoltorio tfoot,
  .tabla-envoltorio tr,
  .tabla-envoltorio td { display: block; width: auto; }
  .tabla-envoltorio thead { display: none; }

  /* `table { min-width: 460px }` existe para que en ESCRITORIO una tabla
     estrecha no se aplaste. En tarjeta no hay tabla que defender, y ese
     minimo es mas ancho que un movil de 430px: como aqui el envoltorio ya
     no hace scroll (`overflow-x: visible`), esos 30px sobrantes no los
     absorbe nadie y empujan al <body>, desplazando la PAGINA ENTERA en
     horizontal (cabecera incluida). Bug real reportado con capturas. */
  .tabla-envoltorio table { min-width: 0; }

  .tabla-envoltorio tr {
    background: var(--papel-alto);
    border: 1px solid var(--linea);
    border-radius: var(--radio);
    box-shadow: var(--sombra);
    padding: var(--esp-3) var(--esp-4);
    margin-bottom: var(--esp-3);
  }

  .tabla-envoltorio td {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--esp-4);
    padding: var(--esp-2) 0;
    border: 0;
    border-top: 1px solid var(--linea);
    text-align: right;
    white-space: normal;
  }
  .tabla-envoltorio td::before {
    content: attr(data-etiqueta);
    flex: 0 0 auto;
    text-align: left;
    font-size: var(--txt-xs);
    text-transform: uppercase;
    letter-spacing: .04em;
    color: var(--tinta-tenue);
    font-weight: 600;
  }

  /* El identificador: sin etiqueta, sin recorte, a todo el ancho y
     arriba del todo. Es el titulo de la tarjeta. */
  .tabla-envoltorio td.tabla-td-titulo {
    display: block;
    border-top: 0;
    padding-top: 0;
    text-align: left;
    font-weight: 700;
    font-size: var(--txt-base);
    color: var(--tinta);
    max-width: none;
    overflow: visible;
    text-overflow: clip;
    white-space: normal;
  }
  .tabla-envoltorio td.tabla-td-titulo::before { content: none; }

  /* `.tabla-recortar` existe para que una tabla ancha no crezca. En
     tarjeta no hay ancho que defender y recortar solo esconde. */
  .tabla-envoltorio td.tabla-recortar { max-width: none; overflow: visible; text-overflow: clip; white-space: normal; }

  /* Celda vacia: en tabla es un hueco mudo; en tarjeta pintaria su
     etiqueta con nada al lado. */
  .tabla-envoltorio td:empty { display: none; }

  .tabla-col-movil-oculta { display: none !important; }

  /* La fila de total es una tarjeta con acento propio: cierra la lista,
     no es una fila mas. */
  .tabla-envoltorio tfoot tr.tabla-total {
    background: var(--acento-suave);
    border-color: var(--acento);
  }

  /* La celda de accion no lleva etiqueta y va a la derecha, sola. */
  .tabla-envoltorio td.tabla-td-accion { justify-content: flex-end; padding-top: var(--esp-3); }
  .tabla-envoltorio td.tabla-td-accion::before { content: none; }
}

/* =================================================================
   Enlace al documento en Drive (2026-09-04)
   -----------------------------------------------------------------
   Las dos caras de la facturacion guardaban el `Drive File ID` del PDF
   y nadie lo abria: el papel estaba archivado y era inalcanzable desde
   la app. Un icono + "PDF", no un boton: lleva fuera, no ejecuta nada,
   y tiene que verse distinto de las acciones que si escriben.
   ================================================================= */

.enlace-doc {
  display: inline-flex; align-items: center; gap: var(--esp-2);
  padding: var(--esp-2) var(--esp-3);
  border: 1px solid var(--linea-fuerte); border-radius: var(--radio);
  color: var(--tinta-suave); text-decoration: none;
  font-size: var(--txt-xs); font-weight: 600; white-space: nowrap;
}
.enlace-doc:hover { color: var(--acento); border-color: var(--acento); background: var(--acento-suave); }
.enlace-doc svg { stroke: currentColor; width: 14px; height: 14px; }

.acciones-fila { display: flex; align-items: center; gap: var(--esp-2); justify-content: flex-end; }

@media (pointer: coarse) { .enlace-doc { min-height: 38px; } }
/* En tarjeta las acciones respiran a lo ancho, no apretadas a la derecha. */
@media (max-width: 640px) { .acciones-fila { justify-content: flex-start; flex-wrap: wrap; } }

/* =================================================================
   Segunda linea tenue en una celda (`columna.sub`) — 2026-09-04
   -----------------------------------------------------------------
   Para el dato que se repite en cada fila y no puede robarle ancho al
   que si hay que leer: el capitulo bajo el nombre de la partida.
   ================================================================= */
.tabla-sub {
  display: block;
  margin-top: 2px;
  font-size: var(--txt-xs);
  color: var(--tinta-tenue);
  font-weight: 500;
  letter-spacing: .02em;
}

/* Precio ausente en el catalogo: se lee como un HUECO, no como un dato.
   Una partida sin precio no se puede cotizar, asi que tiene que cantar
   al recorrer la lista con la vista. */
.tabla-falta {
  color: var(--aviso-fuerte);
  font-weight: 700;
}

/* =================================================================
   CUADRO DE MANDO (cuadro.js) — 2026-09-04
   -----------------------------------------------------------------
   Seis bloques, sin libreria de graficos: todo lo que se pinta son
   barras y proporciones, y una dependencia de 300 KB para dibujar
   rectangulos se paga en cada carga de movil.
   ================================================================= */

.cm-vista { display: flex; flex-direction: column; gap: var(--esp-5); }

.cm-cabecera { display: flex; align-items: baseline; gap: var(--esp-4);
  flex-wrap: wrap; }
.cm-cabecera h2 { font-family: var(--display); font-size: var(--txt-xl);
  font-weight: 800; letter-spacing: -.02em; }
.cm-sello-hora { font-family: var(--mono); font-size: var(--txt-xs);
  color: var(--tinta-tenue); letter-spacing: .03em; }

/* ---- KPI ---- */
.cm-kpis { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  border: 1px solid var(--linea); border-radius: var(--radio); overflow: hidden;
  background: var(--papel-alto); box-shadow: var(--sombra); }
.cm-kpi { padding: var(--esp-4) var(--esp-5);
  box-shadow: 1px 0 0 var(--linea), 0 1px 0 var(--linea); }
.cm-kpi-et { font-family: var(--mono); font-size: 9.5px; letter-spacing: .1em;
  text-transform: uppercase; color: var(--tinta-tenue); font-weight: 600; }
.cm-kpi-vl { font-family: var(--display); font-size: 1.55rem; font-weight: 800;
  letter-spacing: -.02em; margin-top: 5px; font-variant-numeric: tabular-nums;
  color: var(--tinta); line-height: 1.1; }
.cm-kpi-nt { font-size: var(--txt-xs); margin-top: 3px; color: var(--tinta-suave); }
.cm-ok { color: var(--exito-fuerte); } .cm-mal { color: var(--error-fuerte); }
.cm-warn { color: var(--aviso-fuerte); }

/* ---- bloque ---- */
.cm-bloque { background: var(--papel-alto); border: 1px solid var(--linea);
  border-radius: var(--radio); padding: var(--esp-5); box-shadow: var(--sombra); }
.cm-cab { margin-bottom: var(--esp-4); }
.cm-cab h3 { font-family: var(--display); font-size: var(--txt-lg); font-weight: 700;
  letter-spacing: -.01em; margin: 0; }
.cm-cab p { margin: 3px 0 0; font-size: var(--txt-xs); color: var(--tinta-tenue); }

.cm-par { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: var(--esp-5); }

/* ---- alertas: lo accionable, arriba del todo ---- */
.cm-alertas { display: flex; flex-direction: column; gap: 1px;
  background: var(--linea); border: 1px solid var(--linea);
  border-radius: var(--radio-s, 8px); overflow: hidden; }
.cm-al { display: grid; grid-template-columns: 4px 30px 1fr auto;
  align-items: center; background: var(--papel-alto); text-decoration: none;
  color: inherit; transition: background .12s ease; }
a.cm-al:hover { background: var(--papel); }
.cm-al-tira { align-self: stretch; background: var(--tinta-tenue); }
.cm-al--alerta .cm-al-tira { background: var(--alerta); }
.cm-al--aviso .cm-al-tira { background: var(--atencion); }
.cm-al--info .cm-al-tira { background: var(--azul2, #3c6e8f); }
.cm-al-ico { display: flex; align-items: center; justify-content: center;
  color: var(--tinta-tenue); }
.cm-al--alerta .cm-al-ico { color: var(--alerta); }
.cm-al--aviso .cm-al-ico { color: var(--atencion); }
.cm-al-ico svg { width: 15px; height: 15px; }
.cm-al-cont { padding: 12px 4px 12px 0; min-width: 0; }
.cm-al-tit { display: block; font-size: var(--txt-base); font-weight: 650;
  color: var(--tinta); }
.cm-al-sub { display: block; font-size: var(--txt-xs); color: var(--tinta-suave);
  margin-top: 2px; }
.cm-al-der { padding: 12px var(--esp-4); text-align: right; white-space: nowrap; }
.cm-al-imp { display: block; font-family: var(--mono); font-size: var(--txt-base);
  font-weight: 700; font-variant-numeric: tabular-nums; }
.cm-al--alerta .cm-al-imp { color: var(--error-fuerte); }
.cm-al--aviso .cm-al-imp { color: var(--aviso-fuerte); }
.cm-al-dias { display: block; font-family: var(--mono); font-size: 10px;
  color: var(--tinta-tenue); margin-top: 1px; }
.cm-vacio-ok { display: flex; align-items: center; gap: var(--esp-3);
  padding: var(--esp-4); background: var(--exito-suave); border-radius: var(--radio);
  color: var(--exito-fuerte); font-size: var(--txt-sm); font-weight: 600; }
.cm-vacio-ok svg { width: 16px; height: 16px; flex: 0 0 auto; }

/* ---- barras compartidas ---- */
.cm-b-exito { background: var(--exito); } .cm-b-alerta { background: var(--alerta); }
.cm-b-aviso { background: var(--atencion); } .cm-b-atencion { background: #d98324; }
.cm-b-azul { background: var(--azul2, #3c6e8f); }
.cm-b-acento { background: var(--acento-solido); }

/* ---- embudo ---- */
.cm-embudo { display: flex; flex-direction: column; }
.cm-emb-fila { display: grid; grid-template-columns: 108px 1fr 104px;
  align-items: center; gap: var(--esp-3); }
.cm-emb-et { font-size: var(--txt-sm); font-weight: 600; }
.cm-emb-pista { height: 26px; background: var(--papel); border-radius: 5px;
  overflow: hidden; }
.cm-emb-barra { height: 100%; border-radius: 5px; transition: width .4s ease; }
.cm-emb-val { font-family: var(--mono); font-size: var(--txt-sm); font-weight: 600;
  text-align: right; font-variant-numeric: tabular-nums; }
.cm-fuga { padding: 5px 0 5px 116px; }
.cm-fuga-t { font-family: var(--mono); font-size: var(--txt-xs);
  color: var(--error-fuerte); letter-spacing: .01em; }

/* ---- tabla de obras ---- */
.cm-tabla-env { overflow-x: auto; }
.cm-tabla { width: 100%; border-collapse: collapse; font-size: var(--txt-sm); }
.cm-tabla th { font-family: var(--mono); font-size: 9.5px; letter-spacing: .09em;
  text-transform: uppercase; color: var(--tinta-tenue); font-weight: 600;
  text-align: right; padding: 0 10px 8px; border-bottom: 1px solid var(--linea-fuerte); }
.cm-tabla th:first-child { text-align: left; }
.cm-tabla td { padding: 11px 10px; border-bottom: 1px solid var(--linea);
  text-align: right; white-space: nowrap; font-variant-numeric: tabular-nums; }
.cm-tabla tbody tr:last-child td { border-bottom: none; }
.cm-td-obra { text-align: left !important; white-space: normal !important; }
.cm-obra-nom { font-weight: 650; color: var(--tinta); text-decoration: none; }
.cm-obra-nom:hover { color: var(--acento); }
.cm-obra-sub { display: block; font-family: var(--mono); font-size: 10px;
  color: var(--tinta-tenue); margin-top: 2px; }
.cm-n { font-family: var(--mono); font-size: var(--txt-sm); }
.cm-barrita { display: inline-block; width: 44px; height: 6px; border-radius: 3px;
  background: var(--papel); overflow: hidden; vertical-align: middle;
  margin-right: 7px; }
.cm-barrita i { display: block; height: 100%; border-radius: 3px; }
.cm-sello { display: inline-block; padding: 2px 9px; border-radius: 99px;
  font-family: var(--mono); font-size: 10px; font-weight: 700; letter-spacing: .03em; }
.cm-sello--ok { background: var(--exito-suave); color: var(--exito-fuerte); }
.cm-sello--warn { background: var(--atencion-suave); color: var(--aviso-fuerte); }
.cm-sello--mal { background: var(--alerta-suave); color: var(--error-fuerte); }
.cm-sello--neutro { background: var(--papel); color: var(--tinta-tenue); }
.cm-pie-nota { margin: var(--esp-4) 0 0; font-size: var(--txt-xs);
  color: var(--tinta-tenue); }

/* ---- antiguedad ---- */
.cm-aging { display: flex; flex-direction: column; gap: var(--esp-2); }
.cm-aging-fila { display: grid; grid-template-columns: 64px 1fr 96px;
  align-items: center; gap: var(--esp-3); }
.cm-aging-et { font-family: var(--mono); font-size: var(--txt-xs);
  color: var(--tinta-suave); }

/* ---- tesoreria ---- */
.cm-tes { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--esp-3);
  align-items: end; }
.cm-tes-col { text-align: center; }
.cm-tes-caja { height: 104px; display: flex; flex-direction: column;
  justify-content: flex-end; gap: 3px; }
.cm-tes-in { background: var(--exito); border-radius: 4px 4px 0 0; min-height: 2px; }
.cm-tes-out { background: var(--alerta); opacity: .78; border-radius: 0 0 4px 4px;
  min-height: 2px; }
.cm-tes-mes { font-family: var(--mono); font-size: 9.5px; letter-spacing: .06em;
  text-transform: uppercase; color: var(--tinta-tenue); margin-top: 7px; }
.cm-tes-net { font-family: var(--mono); font-size: var(--txt-sm); font-weight: 700;
  margin-top: 3px; font-variant-numeric: tabular-nums; }
.cm-leyenda { display: flex; gap: var(--esp-5); margin-top: var(--esp-4);
  font-size: var(--txt-xs); color: var(--tinta-suave); flex-wrap: wrap; }
.cm-leyenda span { display: flex; align-items: center; gap: 6px; }
.cm-leyenda i { width: 10px; height: 10px; border-radius: 3px; display: block; }

/* ---- pipeline ---- */
.cm-pipe { display: flex; flex-direction: column; gap: 1px;
  background: var(--linea); border: 1px solid var(--linea);
  border-radius: var(--radio-s, 8px); overflow: hidden; }
.cm-pipe-f { display: grid; grid-template-columns: 1fr 110px 108px;
  align-items: center; gap: var(--esp-3); padding: 11px var(--esp-4);
  background: var(--papel-alto); text-decoration: none; color: inherit; }
.cm-pipe-f:hover { background: var(--papel); }
.cm-pipe-izq { min-width: 0; }
.cm-pipe-nom { display: block; font-size: var(--txt-base); font-weight: 650;
  color: var(--tinta); }
.cm-pipe-sub { display: block; font-size: var(--txt-xs); color: var(--tinta-tenue);
  margin-top: 2px; }
.cm-pipe-pista { height: 6px; background: var(--papel); border-radius: 3px;
  overflow: hidden; display: block; }
.cm-pipe-pista span { display: block; height: 100%; border-radius: 3px; }
.cm-pipe-val { font-family: var(--mono); font-size: var(--txt-sm); font-weight: 600;
  text-align: right; font-variant-numeric: tabular-nums; }

/* ---- movil: las tablas y rejillas se estrechan, nada hace scroll lateral ---- */
@media (max-width: 640px) {
  .cm-bloque { padding: var(--esp-4); }
  .cm-emb-fila { grid-template-columns: 84px 1fr 84px; }
  .cm-fuga { padding-left: 90px; }
  .cm-aging-fila { grid-template-columns: 54px 1fr 84px; }
  .cm-tes { grid-template-columns: repeat(4, 1fr); gap: 6px; }
  .cm-al { grid-template-columns: 4px 26px 1fr auto; }
  .cm-pipe-f { grid-template-columns: 1fr 92px; }
  .cm-pipe-pista { display: none; }
  .cm-kpi-vl { font-size: 1.3rem; }
}

/* =====================================================================
   Ofertas y adjudicación (Fase 04 del circuito de presupuesto)
   ---------------------------------------------------------------------
   La pantalla donde alguien decide, partida a partida, quién ejecuta y a
   cuánto. El criterio de diseño no es que quede bonita: es que confirmar
   veintiuna partidas no sea un suplicio.

   De ahí las tres reglas de esta hoja:

   1. Una partida = una fila con TODO lo que hace falta para decidir a la
      vista — su nombre, el código del arquitecto, su medición, y el
      candidato con su precio. Nada de abrir un panel por partida: veintiún
      panales son veintiún clics antes del primer sí.
   2. La confianza se lee de un vistazo (insignia), pero NADA viene
      preseleccionado. Ninguna regla de esta hoja resalta un candidato más
      que otro: resaltar es preseleccionar por la puerta de atrás.
   3. El dinero en tabular-nums y alineado, como en el cuadro de mando.
      Comparar cifras que bailan de ancho es leerlas dos veces.
   ===================================================================== */

.oferta-cab {
  display: flex; align-items: center; gap: var(--esp-3);
  flex-wrap: wrap; margin-bottom: var(--esp-3);
}
.oferta-ref { color: var(--tinta-suave); font-size: var(--txt-sm); }
.oferta-total {
  margin-left: auto; font-family: var(--mono);
  font-variant-numeric: tabular-nums; font-size: var(--txt-base);
}
.oferta-notas { color: var(--tinta-suave); font-size: var(--txt-sm); margin: 0 0 var(--esp-3); }
.oferta-panel:not(:empty) { margin-top: var(--esp-4); }

/* Las líneas de la oferta se leen SIN pulsar nada — son el documento que
   te han mandado, no un detalle avanzado. La tabla va pegada a la cabecera
   de la tarjeta, con una regla fina que las separa: se ve que pertenecen a
   ese proveedor y no a la pantalla. */
.oferta-lineas {
  margin: var(--esp-3) 0 var(--esp-4);
  padding-top: var(--esp-3);
  border-top: 1px solid var(--linea);
}
.oferta-lineas .tabla-envoltorio { margin: 0; }

/* Las acciones al final de la tarjeta y alineadas a la izquierda, donde
   acaba de leer el ojo. Decidir viene DESPUÉS de leer. */
.oferta-acciones {
  display: flex; flex-wrap: wrap; gap: var(--esp-2);
}

/* ---- Margen previsto ------------------------------------------------
   Bloque propio con su caja. Antes los avisos se soltaban directamente en
   el cuerpo de la sección y quedaban flotando, sin pertenecer a nada: la
   caja los ancla a la cifra que explican y les fija el ancho, que es la
   otra mitad del problema. */
.margen-bloque {
  margin-top: var(--esp-6);
  padding-top: var(--esp-4);
  border-top: 1px solid var(--linea);
}
.margen-bloque .tablero { margin-bottom: var(--esp-3); }
.margen-bloque .bloque-titulo { margin-top: 0; }

/* Los avisos del margen NO son banners. Un banner naranja es una alarma, y
   tres alarmas seguidas dejan de leerse — que es exactamente lo que pasaba
   al abrir una obra sin precios. Esto es una nota al pie de una cifra:
   se lee si el número sorprende, y no grita mientras tanto. */
.margen-avisos {
  list-style: none;
  margin: 0; padding: 0;
  display: flex; flex-direction: column; gap: var(--esp-2);
  width: 100%;
}
.margen-avisos li {
  position: relative;
  padding-left: var(--esp-4);
  font-size: var(--txt-sm);
  color: var(--tinta-suave);
  line-height: 1.45;
}
.margen-avisos li::before {
  content: ''; position: absolute; left: 0; top: 0.5em;
  width: 5px; height: 5px; border-radius: 50%;
  background: var(--atencion);
}

/* Enlace hacia delante. `.enlace-volver` no vale: lleva una flecha «←»
   pegada, y apuntar hacia atrás en un enlace que te lleva al siguiente
   paso es decir lo contrario de lo que hace. */
.enlace-adelante {
  display: inline-flex; align-items: center; gap: var(--esp-1);
  color: var(--acento); font-size: var(--txt-sm); font-weight: 600;
  text-decoration: none; margin-top: var(--esp-3);
}
.enlace-adelante::after { content: '→'; }
.enlace-adelante:hover { text-decoration: underline; }

.bloque-titulo { margin: var(--esp-5) 0 var(--esp-2); font-size: var(--txt-lg); }
.bloque-nota {
  margin: 0 0 var(--esp-3); color: var(--tinta-suave);
  font-size: var(--txt-sm); max-width: 62ch;
}

.emparejar-lista { display: flex; flex-direction: column; gap: var(--esp-2); }

/* Rejilla de dos columnas: la partida a la izquierda ocupa lo que necesita,
   la decisión a la derecha se mantiene SIEMPRE en el mismo sitio. Que el
   botón de adjudicar baile de posición entre filas es como se adjudica la
   partida de debajo. */
.emparejar-fila {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.1fr);
  gap: var(--esp-4); align-items: start;
  padding: var(--esp-3) var(--esp-4);
  border: 1px solid var(--linea); border-radius: var(--radio);
  background: var(--papel-alto);
}
.emparejar-partida { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.emparejar-capitulo {
  font-size: var(--txt-xs); text-transform: uppercase;
  letter-spacing: 0.04em; color: var(--tinta-suave);
}
.emparejar-meta {
  font-size: var(--txt-xs); color: var(--tinta-suave);
  font-variant-numeric: tabular-nums;
}
.emparejar-accion { display: flex; flex-direction: column; gap: var(--esp-2); min-width: 0; }
.emparejar-candidato {
  display: flex; flex-direction: column; gap: var(--esp-1);
  padding: var(--esp-2) var(--esp-3);
  border: 1px solid var(--linea); border-radius: var(--radio);
  background: var(--papel);
}
/* Deliberadamente SIN variante destacada para el candidato de confianza
   alta: la insignia ya lo dice. Un borde de color aquí lo convertiría en
   "el elegido" antes de que nadie lo haya elegido. */

.confirmar-numeros { display: flex; flex-direction: column; gap: 2px; }
.confirmar-numeros p { margin: 0; font-variant-numeric: tabular-nums; }

@media (max-width: 640px) {
  /* En móvil la rejilla se apila. La partida va primero y la decisión
     debajo: se lee qué se decide antes de poder decidirlo. */
  .emparejar-fila { grid-template-columns: 1fr; gap: var(--esp-3); }
  .oferta-total { margin-left: 0; width: 100%; }
}

/* Cuadro · por origen. Reutiliza la rejilla de `cm-pipe-f`: es la misma
   forma —nombre, barra, cifra— y duplicarla solo abriria la puerta a que
   las dos se separen con el tiempo. */
.cm-b-gris { background: var(--tinta-tenue, #8a8a8a); opacity: .45; }
.cm-orig-conv { display: block; font-family: var(--sans); font-size: var(--txt-xs);
  font-weight: 500; color: var(--tinta-tenue); }


/* ---------- Ofertas: pedir precio por capitulos (2026-09-13) ----------
   La barra vive ARRIBA del listado y esta tambien cuando no hay ninguna
   oferta: es entonces cuando mas falta hace. En movil apila —el boton a
   ancho completo— porque un boton de 150px alineado a la derecha en una
   pantalla de 430px es un boton que no se ve. ---------- */

.ofertas-barra {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--esp-3);
  padding: var(--esp-4);
  margin-bottom: var(--esp-4);
  background: var(--papel-alto);
  border: 1px solid var(--linea);
  border-radius: var(--radio);
  box-shadow: var(--elev-1);
}

.ofertas-barra-texto { flex: 1 1 220px; min-width: 0; }
.ofertas-barra-texto .bloque-titulo { margin: 0 0 2px; }
.ofertas-barra-texto .bloque-nota { margin: 0; }

@media (max-width: 560px) {
  .ofertas-barra { flex-direction: column; align-items: stretch; }
  /* EL `flex-basis` SE RESETEA AL APILAR. En una fila, `flex: 1 1 220px`
     es un ancho minimo razonable; al girar a columna esos 220 px pasan a
     ser una ALTURA, y el bloque de texto —que ocupa 62 px— se estiraba a
     220 dejando un agujero enorme entre el titulo y el boton. El mismo
     valor significa dos cosas distintas segun la direccion del flex. */
  .ofertas-barra-texto { flex: 0 0 auto; }
  .ofertas-barra .btn { width: 100%; justify-content: center; }
}

/* El alcance de una oferta pedida: lo que le mandamos, sin precios. */
.oferta-alcance-titulo {
  margin: 0 0 var(--esp-2);
  font-family: var(--mono);
  font-size: var(--txt-xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--tinta-suave);
}

/* ---------- El dialogo de pedir precio ---------- */

.pedir-oferta { display: flex; flex-direction: column; gap: var(--esp-3); }

.familias-cab {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--esp-2);
}

.familias {
  display: flex;
  flex-direction: column;
  border: 1px solid var(--linea);
  border-radius: var(--radio);
  overflow: hidden;
  /* Un presupuesto de diecisiete capitulos no puede empujar el pie del
     modal fuera de la pantalla: la lista scrollea, los botones no. */
  max-height: 42vh;
  overflow-y: auto;
}

.familia {
  display: flex;
  align-items: center;
  gap: var(--esp-3);
  padding: var(--esp-3) var(--esp-4);
  cursor: pointer;
  background: var(--papel-alto);
}

.familia + .familia { border-top: 1px solid var(--linea); }
.familia:hover { background: var(--papel); }

.familia-caja { flex: 0 0 auto; width: 18px; height: 18px; accent-color: var(--acento); }

.familia-texto { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 1px; }

.familia-nombre {
  font-weight: 650;
  color: var(--tinta);
  /* El nombre de un capitulo del arquitecto es largo y se lee entero o no
     se lee: envuelve, no se corta con puntos suspensivos. */
  overflow-wrap: anywhere;
}

.familia-detalle { font-size: var(--txt-xs); color: var(--tinta-suave); }

.familia-importe {
  flex: 0 0 auto;
  font-family: var(--display);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--tinta);
}

.pedir-resumen {
  margin: 0;
  padding: var(--esp-3) var(--esp-4);
  background: var(--papel);
  border: 1px dashed var(--linea);
  border-radius: var(--radio);
  font-size: var(--txt-sm);
  font-weight: 600;
  color: var(--tinta-suave);
  font-variant-numeric: tabular-nums;
}

/* =====================================================================
   CONTROL DE OBRA (2026-09-13)
   ---------------------------------------------------------------------
   La pantalla se lee de arriba abajo una sola vez: veredicto, avisos,
   cascada, coste, caja, detalle. Por eso los bloques son bandas a ancho
   completo separadas por aire, y no tarjetas en rejilla — una rejilla
   invita a saltar, y aqui el orden ES el argumento.

   Los colores de estado salen de los tokens que ya existen (--exito,
   --atencion, --alerta). Ni un hex suelto: el dia que el tema cambie,
   esta pantalla cambia con el.
   ===================================================================== */

.bloque-obra {
  padding: var(--esp-5) var(--esp-4);
  border-top: 1px solid var(--linea);
}
.bloque-obra:first-of-type { border-top: none; }
.bloque-obra-cab { margin-bottom: var(--esp-4); }
.bloque-obra-cab .bloque-titulo { margin: 0 0 var(--esp-1); }
.bloque-obra-cab .bloque-nota { margin: 0; max-width: 62ch; }
.bloque-nota--alerta { color: var(--error-fuerte); font-weight: 600; }

/* ---------- El veredicto ---------- */

.veredicto {
  display: flex;
  flex-wrap: wrap;
  align-items: stretch;
  gap: var(--esp-5);
  padding: var(--esp-5) var(--esp-4);
  border-radius: var(--radio);
  border: 1px solid var(--linea);
  background: var(--papel-alto);
  box-shadow: var(--elev-1);
  /* La franja de estado es un borde IZQUIERDO grueso y no un fondo de
     color: un fondo saturado detras de una cifra de 3rem la vuelve
     ilegible en el tema oscuro, y ademas grita mas que el numero. */
  border-left: 5px solid var(--linea-fuerte);
  margin-bottom: var(--esp-5);
}
.veredicto--ok { border-left-color: var(--exito); }
.veredicto--aviso { border-left-color: var(--atencion); }
.veredicto--alerta { border-left-color: var(--alerta); }
.veredicto--neutro { border-left-color: var(--linea-fuerte); }

.veredicto-izq { flex: 1 1 260px; min-width: 0; }
.veredicto-der {
  flex: 1 1 240px;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--esp-4);
  justify-content: center;
  padding-left: var(--esp-5);
  border-left: 1px solid var(--linea);
}

.veredicto-etiqueta {
  font-family: var(--mono);
  font-size: var(--txt-xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--tinta-suave);
}
.veredicto--ok .veredicto-etiqueta { color: var(--exito-fuerte); }
.veredicto--aviso .veredicto-etiqueta { color: var(--aviso-fuerte); }
.veredicto--alerta .veredicto-etiqueta { color: var(--error-fuerte); }

.veredicto-cifra {
  margin: var(--esp-2) 0 var(--esp-1);
  font-family: var(--display);
  font-size: 3.2rem;
  font-weight: 800;
  line-height: 1;
  letter-spacing: -0.03em;
  color: var(--tinta);
  font-variant-numeric: tabular-nums;
}
.veredicto-cifra small {
  font-size: 1.4rem;
  margin-left: 2px;
  color: var(--tinta-suave);
}
.veredicto-detalle { margin: 0; font-size: var(--txt-sm); color: var(--tinta-suave); }

.veredicto-sin-cifra {
  margin: var(--esp-2) 0 var(--esp-2);
  font-family: var(--display);
  font-size: 1.35rem;
  font-weight: 700;
  line-height: 1.15;
  color: var(--tinta);
}
.veredicto-motivos {
  margin: 0;
  padding-left: 1.1em;
  font-size: var(--txt-sm);
  color: var(--tinta-suave);
  max-width: 54ch;
}
.veredicto-motivos li + li { margin-top: var(--esp-1); }
.veredicto-cifrilla .kpi-valor { font-size: 1.25rem; }

@media (max-width: 700px) {
  .veredicto-der {
    border-left: none;
    border-top: 1px solid var(--linea);
    padding-left: 0;
    padding-top: var(--esp-4);
  }
  .veredicto-cifra { font-size: 2.6rem; }
}

/* ---------- Avisos de obra ---------- */

.aviso-obra {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--esp-3);
  padding: var(--esp-3) var(--esp-4);
  border-radius: var(--radio);
  border: 1px solid var(--linea);
  border-left: 4px solid var(--linea-fuerte);
  background: var(--papel-alto);
  margin-bottom: var(--esp-3);
}
.aviso-obra--alerta { border-left-color: var(--alerta); background: var(--alerta-suave); }
.aviso-obra--aviso { border-left-color: var(--atencion); background: var(--atencion-suave); }
.aviso-obra--info { border-left-color: var(--linea-fuerte); }
.aviso-obra-texto { margin: 0; font-size: var(--txt-sm); flex: 1 1 240px; }
.aviso-obra-enlace {
  font-size: var(--txt-sm);
  font-weight: 700;
  color: var(--acento);
  white-space: nowrap;
}

/* ---------- La cascada ---------- */

.cascada { display: flex; flex-direction: column; }

.cascada-paso { padding: var(--esp-2) 0; }
.cascada-cab {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--esp-3);
}
.cascada-nombre { font-weight: 650; font-size: var(--txt-base); }
.cascada-valor {
  font-family: var(--display);
  font-size: var(--txt-lg);
  font-variant-numeric: tabular-nums;
}
.cascada-canal {
  height: 10px;
  border-radius: var(--radio-pastilla);
  background: var(--linea);
  overflow: hidden;
  margin: var(--esp-2) 0 var(--esp-1);
}
.cascada-barra { height: 100%; border-radius: inherit; background: var(--acento); }
/* Cada momento del dinero, un paso mas cerca del banco: la barra se va
   oscureciendo para que la secuencia se vea sin leer las etiquetas. */
.cascada-paso--contratado .cascada-barra { background: var(--grafico-5); }
.cascada-paso--certificado .cascada-barra { background: var(--grafico-2); }
.cascada-paso--facturado .cascada-barra { background: var(--acento); }
.cascada-paso--cobrado .cascada-barra { background: var(--exito); }
.cascada-nota { font-size: var(--txt-xs); color: var(--tinta-suave); }

/* La caida entre dos momentos. Va SANGRADA y con linea de puntos: no es
   un dato del mismo rango que los cuatro momentos, es la distancia entre
   dos de ellos. */
.cascada-caida {
  display: flex;
  justify-content: space-between;
  gap: var(--esp-3);
  margin: var(--esp-1) 0 var(--esp-1) var(--esp-5);
  padding: var(--esp-2) var(--esp-3);
  border-left: 2px dotted var(--linea-fuerte);
  font-size: var(--txt-sm);
  color: var(--tinta-suave);
}
.cascada-caida-valor {
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--tinta);
}

/* De donde sale lo contratado, un paso antes de la barra de "Contratado":
   bruto -> descuento -> neto. Filas pequenas y muted, para que no compitan
   con las cuatro barras principales de abajo. */
.cascada-previo {
  display: flex;
  flex-direction: column;
  gap: var(--esp-1);
  padding-bottom: var(--esp-3);
  margin-bottom: var(--esp-3);
  border-bottom: 1px dashed var(--linea);
}
.cascada-previo-fila {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--esp-2);
  font-size: var(--txt-sm);
  color: var(--tinta-suave);
}
.cascada-previo-etiqueta { flex: 1 1 auto; }
.cascada-previo-valor {
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.cascada-previo-fila--neto {
  font-size: var(--txt-base);
  font-weight: 650;
  color: var(--tinta);
}

/* El ancla del neto: mismo par visual que la pastilla aceptada/borrador
   de Presupuesto (`.ppto-chip--aceptada`/`--borrador`) — verde y solido es
   dato FIRME, discontinuo es dato PROVISIONAL. No es un color nuevo: es
   la misma distincion "fiable / no fiable" que ya usa el resto de la app. */
.ancla-badge {
  display: inline-block;
  padding: 2px var(--esp-2);
  border-radius: var(--radio-pastilla);
  font-size: var(--txt-xs);
  font-weight: 700;
  white-space: nowrap;
  border: 1px solid var(--linea-fuerte);
}
.ancla-badge--aceptado {
  background: var(--exito-suave);
  border-color: var(--exito);
  color: var(--exito-fuerte);
}
.ancla-badge--vivo {
  border-style: dashed;
  color: var(--tinta-suave);
}

/* Pendiente de reconocer: un HECHO de contrato firmado sin ejecutar
   todavia, NUNCA un margen. Caja propia con acento neutro-informativo
   (no verde/rojo del veredicto de margen), para que a nadie se le ocurra
   leerlo como beneficio. */
.cascada-pendiente {
  margin-top: var(--esp-2);
  padding: var(--esp-3);
  border-radius: var(--radio);
  background: var(--acento-suave);
}
.cascada-pendiente-etiqueta {
  display: block;
  font-size: var(--txt-xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .02em;
  color: var(--acento);
}
.cascada-pendiente-valor {
  display: block;
  font-family: var(--display);
  font-size: var(--txt-lg);
  font-variant-numeric: tabular-nums;
}
.cascada-pendiente-nota {
  margin: var(--esp-1) 0 0;
  font-size: var(--txt-xs);
  color: var(--tinta-suave);
}

/* El ciclo del contrato: misma tabla `.pyg` que la Cuenta de resultados,
   sin fila de Resultado (a proposito, ver docstring de
   contratoInformativoControl_). */
.pyg--informativo .pyg-fila:last-child td { border-bottom: none; }

/* ---------- Reparto del coste ---------- */

.apilada {
  display: flex;
  height: 14px;
  border-radius: var(--radio-pastilla);
  overflow: hidden;
  background: var(--linea);
  margin-bottom: var(--esp-4);
}
.apilada-seg { height: 100%; min-width: 2px; }
.apilada-seg--0, .reparto-punto--0 { background: var(--acento); }
.apilada-seg--1, .reparto-punto--1 { background: var(--grafico-2); }
.apilada-seg--2, .reparto-punto--2 { background: var(--grafico-5); }
.apilada-seg--3, .reparto-punto--3 { background: var(--grafico-3); }
.apilada-seg--4, .reparto-punto--4 { background: var(--grafico-4); }
.apilada-seg--5, .reparto-punto--5 { background: var(--grafico-1); }

.reparto { display: flex; flex-direction: column; }
.reparto-fila {
  display: flex;
  align-items: center;
  gap: var(--esp-3);
  padding: var(--esp-2) 0;
  border-bottom: 1px solid var(--linea);
}
.reparto-fila--total {
  border-bottom: none;
  border-top: 2px solid var(--tinta);
  margin-top: var(--esp-2);
  padding-top: var(--esp-3);
  font-weight: 700;
}
.reparto-punto {
  flex: 0 0 auto;
  width: 10px; height: 10px;
  border-radius: 3px;
  background: transparent;
}
.reparto-nombre { flex: 1 1 auto; min-width: 0; overflow-wrap: anywhere; }
.reparto-pct {
  flex: 0 0 3.5em;
  text-align: right;
  color: var(--tinta-suave);
  font-variant-numeric: tabular-nums;
  font-size: var(--txt-sm);
}
.reparto-valor {
  flex: 0 0 auto;
  min-width: 6.5em;
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.compromiso {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--esp-4);
  margin-top: var(--esp-4);
  padding: var(--esp-4);
  border: 1px dashed var(--linea-fuerte);
  border-radius: var(--radio);
}
.compromiso-texto { flex: 1 1 auto; min-width: 0; }
.compromiso-texto .bloque-nota { margin: var(--esp-1) 0 0; }
.compromiso-valor {
  font-family: var(--display);
  font-size: 1.3rem;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* ---------- Linea de certificaciones ---------- */

.cert-linea { list-style: none; margin: 0; padding: 0 0 0 var(--esp-4); }
.cert-hito {
  position: relative;
  padding: 0 0 var(--esp-4) var(--esp-4);
  border-left: 2px solid var(--linea);
}
.cert-hito:last-child { border-left-color: transparent; padding-bottom: 0; }
.cert-hito::before {
  content: '';
  position: absolute;
  left: -6px; top: 4px;
  width: 10px; height: 10px;
  border-radius: 50%;
  background: var(--papel-alto);
  border: 2px solid var(--acento);
}
.cert-hito-cab {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--esp-2);
}
.cert-hito-num {
  font-family: var(--mono);
  font-size: var(--txt-xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--tinta-suave);
}
.cert-hito-importe {
  margin-left: auto;
  font-family: var(--display);
  font-variant-numeric: tabular-nums;
}
.cert-hito-pie { margin: 2px 0 0; font-size: var(--txt-xs); color: var(--tinta-suave); }

/* =====================================================================
   GALERÍA DE DOCUMENTOS (galeria-documentos-obra, Fase 4 — 2026-09-16)
   ---------------------------------------------------------------------
   Sucesora de "Avance en fotos" (2026-09-13): mismo lenguaje visual —
   rejilla `auto-fill` de tarjetas cuadradas, agrupada por día — pero
   ahora para las 8 secciones de `documentos/listar`, no solo fotos.
   `.fotos-dia`/`.fotos-total`/`.docs-rejilla` (antes `.fotos-rejilla`) se
   REUTILIZAN sin cambiar una línea: agrupar por día sigue siendo la
   organización correcta cuando se mira un solo tipo (una pastilla
   pulsada) — lo nuevo es agrupar por TIPO en la vista "Todos".
   ===================================================================== */

.fotos-dia {
  margin: var(--esp-5) 0 var(--esp-3);
  font-family: var(--mono);
  font-size: var(--txt-xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--tinta-suave);
  padding-bottom: var(--esp-2);
  border-bottom: 1px solid var(--linea);
}

.docs-rejilla {
  display: grid;
  /* 180 px y no 150: un documento se mira, y a 150 px un icono de PDF no
     se distingue de uno de imagen. En un movil de 390 px siguen entrando
     dos por fila. */
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: var(--esp-3);
}

.fotos-total {
  margin: var(--esp-5) 0 0;
  padding-top: var(--esp-3);
  border-top: 1px solid var(--linea);
  font-size: var(--txt-xs);
  color: var(--tinta-suave);
}

.docs-tarjeta { margin: 0; }

.docs-marco {
  display: flex;
  align-items: center;
  justify-content: center;
  /* Cuadrado SIEMPRE, con la misma tarjeta para las 4 clases (imagen,
     plano, pdf, otro): la rejilla no se fragmenta segun de que tipo sea
     cada documento — es lo que la hace leerse como un album y no como
     una lista con iconos sueltos por medio (design.md, "por que esto no
     es una tabla"). */
  aspect-ratio: 1 / 1;
  overflow: hidden;
  border-radius: var(--radio);
  border: 1px solid var(--linea);
  background: var(--papel-alto);
  box-shadow: var(--elev-1);
  padding: 0;
  cursor: pointer;
  width: 100%;
}

.docs-marco:hover, .docs-marco:focus-visible { border-color: var(--acento); }
.docs-marco:focus-visible { outline: 2px solid var(--acento); outline-offset: 2px; }

.docs-marco-img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* El bloque de icono: PDF u "otro" lo llevan SIEMPRE (no hay rasterizado
   de PDF en el servidor — ver design.md decision 5, la misma razon que
   ya tumbo el contenedor una vez con WeasyPrint); una imagen/plano lo
   lleva solo si su miniatura no cargo. */
.docs-icono-bloque {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--acento-suave);
  color: var(--acento);
}

.docs-tarjeta-pie {
  margin-top: var(--esp-1);
  font-size: var(--txt-xs);
  color: var(--tinta-suave);
  /* El nombre de un fichero es largo y no se lee entero nunca: una linea
     y elipsis. El dato que importa es la imagen. */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.docs-tarjeta-meta { opacity: .7; font-size: 0.62rem; margin-top: 1px; }

/* ---------- Chips de tipo (galeria-documentos-obra) --------------------
   Reutiliza la MISMA familia `.pastilla`/`.pastilla--activa`/
   `.pastilla--error` de `barraFiltros` (ui.js): cero literales nuevos de
   color/espaciado/radio para el chip en si. */
.docs-chips { display: flex; flex-wrap: wrap; gap: var(--esp-2); margin-bottom: var(--esp-4); }
.docs-chip--tenue { opacity: .5; cursor: default; }

.docs-tipo-bloque { margin-bottom: var(--esp-6); }
.docs-tipo-titulo {
  font-family: var(--display);
  font-size: var(--txt-lg);
  margin: 0 0 var(--esp-3);
}

.docs-subir-fila { display: flex; align-items: center; gap: var(--esp-3); flex-wrap: wrap; }

/* ---------- Menú de la cabecera (2026-09-14) ----------
   La barra inferior sigue siendo el camino normal. Esto es el que no
   depende de llegar al borde de abajo de la pantalla: con el gesto de
   inicio de un móvil, con la barra del navegador encima, o con un
   dispositivo emulado más alto que la ventana, ese borde no siempre se
   alcanza — e Isaac reportó exactamente eso dos veces.

   La cabecera es sticky, así que este botón está SIEMPRE, se haya
   scrolleado lo que se haya scrolleado. */
.cabecera-menu {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 34px;
  height: 34px;
  padding: 0;
  border: none;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.14);
  color: var(--blanco-fijo);
  cursor: pointer;
}
.cabecera-menu:hover { background: rgba(255, 255, 255, 0.24); }
.cabecera-menu svg { stroke: currentColor; }

/* En escritorio la barra lateral está entera a la vista y no se va con el
   scroll: un menú que repite lo mismo sobra. */
@media (min-width: 1024px) {
  .cabecera-menu { display: none; }
}

/* La cabecera, más baja en un móvil. La fecha completa de «Última
   actualización» partía en DOS líneas en un teléfono de 412 px y se
   comía 97 px de alto en todas las pantallas para decir una hora.
   Se queda la hora, que es lo que se mira. */
@media (max-width: 520px) {
  .frescura { font-size: 0.66rem; }
  .frescura-fecha { display: none; }
}

/* ---------- Cuenta de resultados (.pyg) — 2026-09-14 ----------
   Se lee como una CUENTA y por eso se compone como una: importes a la
   derecha, cifras tabulares para que las columnas de dígitos caigan a
   plomo, y una regla gruesa antes del resultado. Nada de tarjetas: una
   cuenta se lee de arriba abajo. ---------- */

.pyg-cab {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--esp-3);
}
.pyg-cab > div { flex: 1 1 260px; min-width: 0; }

.pyg {
  width: 100%;
  border-collapse: collapse;
  font-variant-numeric: tabular-nums;
}

.pyg td { padding: var(--esp-2) 0; vertical-align: top; }
.pyg-n { text-align: right; white-space: nowrap; padding-left: var(--esp-4); }

.pyg-seccion td {
  padding-top: var(--esp-4);
  font-family: var(--mono);
  font-size: var(--txt-xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--tinta-suave);
  border-bottom: 1px solid var(--linea-fuerte);
}

.pyg-fila td { border-bottom: 1px solid var(--linea); }
.pyg-concepto { display: block; }
.pyg-detalle {
  display: block;
  font-size: var(--txt-xs);
  color: var(--tinta-suave);
  /* El «por qué» de cada línea cabe entero: es lo que impide que alguien
     sume dos veces un anticipo por no saber de dónde sale la cifra. */
  overflow-wrap: anywhere;
}
.pyg-vacio { color: var(--tinta-suave); font-size: var(--txt-sm); }

.pyg-total td {
  font-weight: 700;
  border-bottom: 1px solid var(--linea-fuerte);
}

.pyg-resultado td {
  padding-top: var(--esp-3);
  border-top: 2px solid var(--tinta);
  font-family: var(--display);
  font-size: 1.15rem;
  font-weight: 800;
}
.pyg-resultado--negativo td { color: var(--error-fuerte); }

.pyg-libro { margin-top: var(--esp-4); }
.pyg-libro > summary {
  cursor: pointer;
  font-size: var(--txt-sm);
  font-weight: 650;
  color: var(--acento);
  padding: var(--esp-2) 0;
}
.pyg-libro > summary::marker { color: var(--tinta-suave); }
.pyg-libro[open] > summary { margin-bottom: var(--esp-3); }
