/* ============================================================
   ERP LATNOVVA — capa base de diseño
   Regla dura: ningún color literal fuera del bloque de tokens.
   Los SVG usan currentColor.

   Semántica del color:
     teal   = acción / navegación (token `--azul`, ver abajo)
     verde  = al día
     ámbar  = revisar, pero el ciclo sigue
     rojo   = bloqueante o dato que el reporte está leyendo mal
     gris   = informativo, sin acción posible
   ============================================================ */

:root {
  --bg:            #f6f7f9;
  --surface:       #ffffff;
  --surface-2:     #eef0f4;
  --border:        #d4d8e0;
  --border-fuerte: #b3bac7;
  /* LA RAYA ENTRE DOS COLUMNAS, y es MAS FLOJA que `--border` a proposito: la de fila separa
     registros —el corte que de verdad se lee— y esta solo guia el ojo al bajar por una columna.
     Con el mismo valor, la rejilla grita mas que el dato que lleva dentro. */
  --border-suave:  #e6e9ef;

  --texto:         #16181d;
  --texto-suave:   #5b6472;
  /* #838c9b daba 3,39:1 sobre `--surface` y 2,97:1 sobre `--surface-2`, o sea POR DEBAJO del
     4,5:1 de AA — y es el color de `.nota` (12px) y del texto secundario de toda la app. En
     oscuro el equivalente sí pasaba (4,54:1), así que el que estaba mal era el claro. Ahora
     son 5,45:1 sobre blanco y 4,77:1 sobre la superficie gris. */
  --texto-tenue:   #626b7a;

  /* EL COLOR DE ACCION ES EL TEAL DE LATNOVVA DESDE EL 2026-09-27, y el token se sigue llamando
     `--azul` a proposito: son 61 usos en este archivo, que tocan a la vez varias sesiones, y el
     nombre dice el PAPEL —accion, navegacion— desde que se escribio la cabecera. Era #1f5fd0.

     NO ES EL TEAL DEL LOGO (#009199, `--marca`): ese da 3,81:1 sobre blanco, por debajo del 4,5
     de AA, y aqui el color es TEXTO —enlaces, item activo— y fondo de un boton con texto blanco.
     #006f76 es el mismo tono oscurecido: 5,94:1 sobre blanco, 5,21:1 sobre `--surface-2` y
     4,93:1 sobre `--azul-bg`, lo que daba el azul viejo sobre su fondo (4,99).

     EL FONDO TIRA A AZUL A PROPOSITO. Un #e3f4f4, teal puro, quedaba primo de `--verde-bg`
     (#e0f3e8) y en `/bandeja/` el item activo y el aviso de «al dia» se leian del mismo color:
     acción y estado confundidos, justo lo que la cabecera prohíbe. Visto en pantalla. */
  --azul:          #006f76;
  --azul-bg:       #d8eef1;
  /* EL TEAL EXACTO DEL LOGO, y SOLO para adorno de marca. Nunca texto: ver arriba. */
  --marca:         #009199;
  /* El texto QUE VA ENCIMA de `--azul` cuando el azul es fondo (`.boton.primario`). Es un
     token y no un `#fff` fijo porque `--azul` se aclara en oscuro: ver `.boton.primario`. */
  --sobre-azul:    #ffffff;
  --verde:         #127a45;
  --verde-bg:      #e0f3e8;
  /* #9a6206 daba 4,43:1 sobre `--ambar-bg` —el `.chip.AMBAR` y todo texto ambar sobre su fondo—,
     por debajo del 4,5 de AA. #946005 da 4,64:1 (5,34 sobre blanco). El oscuro pasaba: 6,68:1. */
  --ambar:         #946005;
  --ambar-bg:      #fbeed3;
  --rojo:          #b3251e;
  --rojo-bg:       #fbe4e2;
  --gris:          #5b6472;
  --gris-bg:       #e9ebef;

  /* JERARQUIA: la única hue que NO significa un estado. Dice DÓNDE estás en una matriz que se
     expande —bloque, código, partida—, nunca cómo va. Las cuatro de arriba están ocupadas por
     la semántica que declara la cabecera de este archivo, así que un nivel pintado de ámbar o
     de rojo apagaría la señal de la fila que se pasó de su presupuesto. Ver `[data-nivel]`. */
  --jerarquia-1:   #6a3fa8;
  --jerarquia-2:   #9b74cd;
  --jerarquia-3:   #c9b4e4;
  /* El escalón NEUTRO del fondo: entre `--surface-2` (nivel 1) y `--surface` (nivel 3). */
  --nivel-2-bg:    #f5f6fa;

  /* LA BARRA DE SCROLL (2026-09-28). Hasta ese dia no habia ni una regla y el navegador pintaba
     la de Windows: gruesa, con flechas y GRIS CLARO TAMBIEN EN OSCURO, porque nadie le decia que
     la pagina era oscura (`color-scheme`, abajo). Queda entre `--border` y `--border-fuerte`: la
     barra dice que hay mas, no es dato, y no puede gritar mas que la rejilla de la tabla. */
  --scroll-pulgar:       #c3c9d3;
  --scroll-pulgar-hover: #9aa2b0;
  --scroll-tabla-pulgar:       #8b93a1;
  --scroll-tabla-pulgar-hover: #5f6776;
  --scroll-tabla-canal:        #e4e7ec;
  color-scheme: light;

  --radio:   10px;
  --radio-s: 6px;

  /* Espaciado. Antes iba en px literales repetidos por las 15 plantillas, así que
     "un poco más de aire" significaba tocar 120 atributos `style=` a mano. */
  --e0:  4px;
  --e1:  6px;
  --e2:  9px;
  --e3: 12px;
  --e4: 16px;
  --e5: 24px;
  --e6: 32px;

  /* LA CELDA. Va aparte de la escala `--e*` porque no es espaciado general: es LA medida de la
     densidad de la app, la que decide si una tabla parece una hoja de calculo o un formulario.
     5 + 17,5 (13px x 1,35 de `line-height`) + 5 + 1 de borde = **28px de fila**, que es lo que
     `design-system/MASTER.md` §3 declara desde el 2026-08-25. Tocar esto los mueve TODOS. */
  --celda-y: 5px;
  /* EL AIRE DE LOS LADOS SUBIO DE 8 A 12 EL 2026-09-11, y el alto NO SE TOCO. Alex: *«las
     tablas van muy apretadas»* — pero la fila de 28px la fijo el mismo el 2026-08-25 pidiendo
     *«filas de Excel angostas»*, y es lo que compro poder tener 12 columnas en /pcc/. Se le
     puso la contradiccion delante y eligio: **el aire de los lados, no el alto.**

     CUESTA MENOS DE LO QUE PARECE EN LAS TABLAS DE DINERO: `td.num` lleva `padding-left: 6px`
     escrito a mano —el numero va pegado a la derecha, asi que su aire izquierdo es recortable—
     de modo que una columna numerica sube 4px y una de texto 8. El ancho lo absorbe
     `.tabla-scroll`; lo que no puede pasar es que scrollee el CUERPO. */
  --celda-x: 12px;

  /* TIPOGRAFIA. La escala la declara `design-system/MASTER.md` §2 desde el 2026-08-12 y hasta
     hoy NO EXISTIA EN CSS: eran diez tamaños en px sueltos, con un 8px y un 9px por debajo de
     lo legible. `--t0` es el SUELO y nada baja de ahí.

     VA SOLO EN `:root`, Y ESO NO CONTRADICE LA REGLA DE LOS TRES BLOQUES: aquélla es de los
     tokens de COLOR, que cambian con el tema. Un tamaño no cambia, así que repetirlo en los
     tres sería tres sitios que pueden divergir sin que ninguno tenga razón.

     Los literales que quedan en el archivo se pagan al tocar cada regla, no en una pasada
     mecánica — el mismo trato que los `style=` de las plantillas. */
  --t0: 11px;   /* EL SUELO: chips, marcas de eje, glifos de cabecera */
  --t1: 12px;   /* .nota, texto secundario, celda densa */
  --t2: 13px;   /* cuerpo de tabla */
  --t3: 15px;   /* cuerpo, etiquetas de formulario */
  --t4: 20px;   /* h2, título de tarjeta */
  --t5: 24px;   /* .kpi, el número grande */

  --sombra:  0 1px 2px rgba(16, 20, 28, .06), 0 4px 12px rgba(16, 20, 28, .05);
  --nav-w:   248px;
  /* El ancho de la barra ABIERTA aunque esté plegada: `--nav-w` pasa a 56px con
     `.nav-plegada`, y al desplegarse encima del contenido vuelve a este. */
  --nav-w-abierta: 248px;

  /* LA CURVA DE ENTRADA (MASTER.md §6). El `ease-out` del navegador es flojo: se pasa la mitad
     del tiempo acelerando y lo que entra parece que duda. Esta arranca rapida y se posa. Va solo
     en `:root` por lo mismo que `--t*`: no es un color y no cambia con el tema. */
  --ease-out: cubic-bezier(0.23, 1, 0.32, 1);

  --fuente: "Fira Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  --mono:   "Fira Code", ui-monospace, "Cascadia Code", Consolas, monospace;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-tema="claro"]) {
    --bg:            #14161a;
    --surface:       #1c1f25;
    --surface-2:     #23272f;
    /* #2e2e2e daba ~1.4:1 sobre la superficie: el borde era invisible. */
    --border:        #363b45;
    --border-fuerte: #4a515e;
    --border-suave:  #2a2f38;

    --texto:         #e8eaed;
    --texto-suave:   #a9b1bd;
    --texto-tenue:   #7e8794;

    /* El teal ACLARADO, como el azul lo estaba: 8,06:1 sobre `--surface` y 6,63:1 sobre su fondo. */
    --azul:          #4cc6cc;
    --azul-bg:       #0f3336;
    /* El logo no cambia de tema: su teal da 4,33:1 sobre la superficie oscura, de sobra para un
       grafico (3:1). Se repite aqui por la regla de los tres bloques, no porque varie. */
    --marca:         #009199;
    /* INVERTIDO: aquí `--azul` es claro, así que lo que va ENCIMA tiene que ser oscuro. */
    --sobre-azul:    #14161a;
    --verde:         #5bc98a;
    --verde-bg:      #10301f;
    --ambar:         #e0ad44;
    --ambar-bg:      #3a2b0c;
    --rojo:          #f08a82;
    --rojo-bg:       #3d1714;
    --gris:          #a9b1bd;
    --gris-bg:       #262a31;

    /* En oscuro la escala se INVIERTE: el nivel de arriba es el más brillante y el de abajo el
       más apagado. Con los valores del claro, el rail de la partida (#c9b4e4) sería lo que más
       gritara de la tabla, justo en la fila más numerosa. */
    --jerarquia-1:   #b78ce8;
    --jerarquia-2:   #8a6ab5;
    --jerarquia-3:   #574670;
    --nivel-2-bg:    #1f232b;
    --scroll-pulgar:       #3e4450;
    --scroll-pulgar-hover: #5a6270;
    --scroll-tabla-pulgar:       #6c7484;
    --scroll-tabla-pulgar-hover: #939cab;
    --scroll-tabla-canal:        #1b1f26;
    /* Sin esto el navegador cree que la pagina es clara: barras, checkboxes, la lista de un
       `<select>` y el calendario de una fecha salian en blanco sobre el fondo oscuro. */
    color-scheme: dark;

    --sombra: 0 1px 2px rgba(0, 0, 0, .35), 0 4px 14px rgba(0, 0, 0, .28);
  }
}

:root[data-tema="oscuro"] {
  --bg:            #14161a;
  --surface:       #1c1f25;
  --surface-2:     #23272f;
  --border:        #363b45;
  --border-fuerte: #4a515e;
  --border-suave:  #2a2f38;
  --texto:         #e8eaed;
  --texto-suave:   #a9b1bd;
  --texto-tenue:   #7e8794;
  --azul:          #4cc6cc;
  --azul-bg:       #0f3336;
  --marca:         #009199;
  --sobre-azul:    #14161a;
  --verde:         #5bc98a;
  --verde-bg:      #10301f;
  --ambar:         #e0ad44;
  --ambar-bg:      #3a2b0c;
  --rojo:          #f08a82;
  --rojo-bg:       #3d1714;
  --gris:          #a9b1bd;
  --gris-bg:       #262a31;
  --jerarquia-1:   #b78ce8;
  --jerarquia-2:   #8a6ab5;
  --jerarquia-3:   #574670;
  --nivel-2-bg:    #1f232b;
  --scroll-pulgar:       #3e4450;
  --scroll-pulgar-hover: #5a6270;
  --scroll-tabla-pulgar:       #6c7484;
  --scroll-tabla-pulgar-hover: #939cab;
  --scroll-tabla-canal:        #1b1f26;
  color-scheme: dark;
  --sombra: 0 1px 2px rgba(0, 0, 0, .35), 0 4px 14px rgba(0, 0, 0, .28);
}

* { box-sizing: border-box; }

/* FINA Y SIN CANAL: pista transparente, sin flechas. Sin `::-webkit-scrollbar` a proposito: en
   Chrome/Edge 121+ las propiedades estandar la anulan, y es donde se usa la app. El `:hover`
   oscurece la barra del contenedor bajo el raton: asi se ve cual se puede arrastrar. */
* {
  scrollbar-width: thin;
  scrollbar-color: var(--scroll-pulgar) transparent;
}
*:hover { scrollbar-color: var(--scroll-pulgar-hover) transparent; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--texto);
  font-family: var(--fuente);
  font-size: 14px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

a { color: var(--azul); text-decoration: none; }
a:hover { text-decoration: underline; }

/* ---------------- layout ---------------- */

.nav {
  position: fixed; inset: 0 auto 0 0;
  width: var(--nav-w);
  background: var(--surface);
  border-right: 1px solid var(--border);
  padding: 18px 0;
  overflow-y: auto;
  display: flex; flex-direction: column;
}
.nav .marca {
  padding: 0 18px 16px;
  font-weight: 600; font-size: var(--t3); letter-spacing: -.01em;
}
.nav .marca small { display: block; color: var(--texto-tenue); font-weight: 400; font-size: var(--t1); }
/* EL LOGO EN PANTALLA DESDE EL 2026-09-27: hasta ese dia solo salia en los PDF y la barra decia
   «Control de Costos» en texto. A 150px el PNG (1110x270) mide 36 de alto, y el «ERP» va debajo
   en caja alta —la que llevaban los `<summary>` hasta el 2026-11-04— para que se lea como
   etiqueta y no como titulo. */
.nav .marca img, .login-marca img { display: block; height: auto; }
.nav .marca img { width: 150px; }
.nav .marca small, .login-marca small {
  margin-top: var(--e1);
  font-weight: 600; letter-spacing: .12em; text-transform: uppercase;
}
.login-marca { text-align: center; margin-bottom: var(--e5); }
.login-marca img { width: 210px; margin: 0 auto; }
.login-marca small { display: block; color: var(--texto-tenue); font-size: var(--t1); }

.nav details { border-top: 1px solid var(--border); }
/* EL MENU EN CAJA NORMAL DESDE EL 2026-11-04 (Alex, con la bandeja nueva): «Presupuesto» y no
   «PRESUPUESTO». La caja alta de 12 px se leia como rotulo de tabla y no como sitio al que ir, y
   a ese cuerpo cuesta mas leerla. Sube a 13 px y a 500 para que el grupo siga pesando mas que
   sus enlaces (que van a 13 px y 400). */
.nav summary {
  padding: 10px 18px; cursor: pointer;
  font-size: var(--t2); font-weight: 500;
  color: var(--texto-suave); list-style: none;
  display: flex; align-items: center; gap: 8px;
}
.nav summary::-webkit-details-marker { display: none; }
.nav summary::after {
  content: ""; margin-left: auto;
  width: 6px; height: 6px; border-right: 1.5px solid currentColor; border-bottom: 1.5px solid currentColor;
  transform: rotate(-45deg); transition: transform .15s;
}
.nav details[open] summary::after { transform: rotate(45deg); }
.nav a.item {
  display: block; padding: 7px 18px 7px 30px;
  color: var(--texto-suave); font-size: var(--t2);
}
.nav a.item:hover { background: var(--surface-2); color: var(--texto); text-decoration: none; }
.nav a.item.activo {
  color: var(--azul); background: var(--azul-bg);
  box-shadow: inset 3px 0 0 var(--azul); font-weight: 500;
}
/* UN GRUPO DE UN SOLO ITEM NO ES UN GRUPO. Compras y Notificaciones son un módulo con una
   sola pantalla, y meterlos en un <details> son dos clics para llegar a una cosa. Van al
   primer nivel del nav y se pintan con la caja de un <summary> —misma altura, mismo aire,
   mismo cuerpo— para que los cinco bloques se lean como cinco. */
.nav > a.item {
  border-top: 1px solid var(--border);
  padding: 10px 18px;
  font-size: var(--t2); font-weight: 500;
}
.nav > a.item.activo { box-shadow: inset 3px 0 0 var(--azul); font-weight: 600; }

/* LOS ICONOS DE LA BARRA (2026-09-26): uno por entrada de primer nivel, delante de la palabra.
   A 14px y no a los 13 de `.icono`: la caja alta de 12px los acompaña, y a 13 el casco y la
   calculadora pierden el detalle. El color lo hereda de la entrada, así que el activo sale en
   azul como su texto. Los enlaces de dentro de un grupo se mueven a 40px para quedar debajo
   de la palabra del grupo y no debajo de su icono. */
.nav > a.item { display: flex; align-items: center; gap: 8px; }
.nav > a.item .icono,
.nav summary .icono { width: 14px; height: 14px; }
.nav details a.item { padding-left: 40px; }
/* «Remesas de Pago» lleva `class="grupo"`, y `details.grupo > summary` es el plegable de TABLA
   (relleno de 4px, `baseline`, `wrap`): su fila salía pegada al borde y con la flecha partida
   a otra línea. Con el icono delante se notaba; aquí vuelve a la caja de sus hermanos. */
.nav details.grupo > summary { padding: 10px 18px; gap: 8px; align-items: center; flex-wrap: nowrap; }

.nav .pie {
  margin-top: auto; padding: 14px 18px 0;
  border-top: 1px solid var(--border); font-size: var(--t1); color: var(--texto-suave);
}

/* EL PIE DEL MENÚ: LA CAMPANITA, EL TEMA Y «SALIR» (2026-10-27). Alex pidió la campanita al lado
   del tema y el tema como icono igual que ella. Los dos son `.pie-icono`: 30 px, sin borde ni
   fondo, color de texto suave, y el mismo hover y foco. Van PRIMERO y «Salir» al final: plegada
   la barra el pie solo deja ver la campanita (más abajo), y al desplegarse con el ratón ella
   tiene que seguir bajo el puntero; con «Salir» delante se correría lo que mide la palabra y el
   clic caería en el vacío. */
.nav #pie-acciones { margin-top: 8px; display: flex; gap: 10px; align-items: center; }
.pie-icono {
  flex: none; display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; margin: 0; padding: 0;
  border: 0; border-radius: var(--radio-s); background: transparent;
  color: var(--texto-suave); cursor: pointer; font: inherit;
}
.pie-icono:hover { background: var(--surface-2); color: var(--texto); }
.pie-icono:focus-visible { outline: 2px solid var(--azul); outline-offset: 1px; }
.pie-icono .icono { width: 20px; height: 20px; }
/* Iguales en el espacio que ocupan: el pie va pegado a la izquierda con 18 px, y el primer icono
   queda alineado con el texto de encima (el nombre, el rol) y no con su borde. */
.nav #pie-acciones { margin-left: -6px; }

/* EL TEMA DEL PIE: SOL EN OSCURO, LUNA EN CLARO (el icono es a donde vas; el porqué, en
   `_iconos.html`). Lo decide el CSS con las MISMAS dos condiciones que dan los colores oscuros
   —`data-tema="oscuro"`, o el sistema en oscuro sin que nadie haya elegido claro— y así no hay
   parpadeo ni script de por medio. */
#tema-toggle .tema-sol { display: none; }
:root[data-tema="oscuro"] #tema-toggle .tema-sol { display: inline; }
:root[data-tema="oscuro"] #tema-toggle .tema-luna { display: none; }
@media (prefers-color-scheme: dark) {
  :root:not([data-tema="claro"]) #tema-toggle .tema-sol { display: inline; }
  :root:not([data-tema="claro"]) #tema-toggle .tema-luna { display: none; }
}

/* LA BARRA PLEGADA (2026-09-27). Un botón de `nav.js` la deja en una tira de iconos para que
   las tablas anchas —PCC, plan, comparativos— ganen 190px. Solo en escritorio: bajo 900px
   manda «☰ Menú» y la clase no significa nada.

   SE DESPLIEGA ENCIMA, NO EMPUJANDO. Con el ratón o con el foco dentro, la barra vuelve a su
   ancho POR ENCIMA del contenido, y `.contenido` sigue con su margen de 56px: la hoja no salta
   cada vez que el ratón cruza el borde. Así se llega a los enlaces de dentro de los grupos sin
   inventar menús flotantes. SIN RETRASO al abrir: con uno, el texto aparecía antes que el
   ancho y se partía en la tira de 56px durante ese rato.

   EL FOCO QUE LA ABRE ES EL DE TECLADO (`:focus-visible`), no cualquiera (`:focus-within`):
   pulsar «Plegar» con el ratón deja el foco en el botón, y con `:focus-within` la barra se
   quedaba abierta encima de la hoja justo después de pedir que se plegara.

   El texto se esconde con `font-size: 0` y no con `display`: el enlace sigue siendo el mismo,
   con su nombre para un lector de pantalla, y el icono conserva sus 14px porque los lleva en
   píxeles. */
.nav-plegar {
  /* El mismo sitio plegada y abierta: a 18px del borde cae centrado en la tira de 56px, y un
     botón que se mueve al acercarle el ratón no se deja pulsar. */
  align-self: flex-start; margin: 0 18px 14px;
  padding: 2px 8px; border: 1px solid var(--border); border-radius: var(--radio-s);
  background: transparent; color: var(--texto-suave); font: inherit; font-size: var(--t1);
  cursor: pointer;
}
.nav-plegar:hover { background: var(--surface-2); color: var(--texto); }
/* «Fijar» es lo que se busca con la barra abierta por el ratón, así que va en el color de
   acción y no en el gris de «Plegar». Fijar quita `.nav-plegada`: `--nav-w` vuelve a 248px y
   `.contenido` se corre con él. Sin transición en el margen: la regla de movimiento del repo
   (MASTER.md §6) anima `transform` y `opacity`, nunca medidas. */
.nav-plegar.fijar {
  display: inline-flex; align-items: center; gap: 6px;
  color: var(--azul); border-color: var(--azul); font-weight: 600;
}
.nav-plegar.fijar:hover { background: var(--azul-bg); color: var(--azul); }
.nav-plegar .icono { width: 14px; height: 14px; }

@media (min-width: 901px) {
  html.nav-plegada { --nav-w: 56px; }
  html.nav-plegada .nav { z-index: 30; overflow-x: hidden; }
  /* `.recien-plegada` CUENTA COMO SI EL RATÓN NO ESTUVIERA. Al pulsar «Plegar» el ratón sigue
     encima de la barra, y con `:hover` a secas la barra no se movía: se quedaba abierta y
     encima de la hoja, que ya se había corrido a la izquierda, hasta sacar el ratón. Parecía
     que el botón no hacía nada (Alex, 2026-09-28). `nav.js` quita la clase al salir el ratón,
     y a partir de ahí acercarlo vuelve a desplegarla como siempre. */
  html.nav-plegada .nav:hover:not(.recien-plegada),
  html.nav-plegada .nav:has(:focus-visible) {
    width: var(--nav-w-abierta);
    box-shadow: var(--sombra);
  }
  html.nav-plegada .nav:is(:not(:hover), .recien-plegada):not(:has(:focus-visible)) .marca,
  html.nav-plegada .nav:is(:not(:hover), .recien-plegada):not(:has(:focus-visible)) details a.item { display: none; }
  html.nav-plegada .nav:is(:not(:hover), .recien-plegada):not(:has(:focus-visible)) > a.item,
  html.nav-plegada .nav:is(:not(:hover), .recien-plegada):not(:has(:focus-visible)) summary { font-size: 0; gap: 0; }
  html.nav-plegada .nav:is(:not(:hover), .recien-plegada):not(:has(:focus-visible)) summary::after { display: none; }
  /* EL PIE, PLEGADA, SOLO DEJA LA CAMPANITA: el nombre, el rol, «Salir» y el tema se esconden como
     siempre, pero la campanita se ve en la tira —la barra no puede esconder un aviso—, centrada
     en los 56 px (13 + 30 + 13). Al desplegarse con el ratón queda a 12 px de donde estaba, y su
     caja de 30 la sigue teniendo debajo. */
  html.nav-plegada .nav:is(:not(:hover), .recien-plegada):not(:has(:focus-visible)) .pie { padding: 14px 13px 0; }
  html.nav-plegada .nav:is(:not(:hover), .recien-plegada):not(:has(:focus-visible)) .pie > :not(#pie-acciones),
  html.nav-plegada .nav:is(:not(:hover), .recien-plegada):not(:has(:focus-visible)) #pie-acciones > :not(.campanita) { display: none; }
  html.nav-plegada .nav:is(:not(:hover), .recien-plegada):not(:has(:focus-visible)) #pie-acciones { margin-left: 0; }
  /* En la tira de 56px no cabe la palabra: queda la chincheta, centrada donde estaba la «›». */
  html.nav-plegada .nav:is(:not(:hover), .recien-plegada):not(:has(:focus-visible)) .nav-plegar-texto { display: none; }
  html.nav-plegada .nav:is(:not(:hover), .recien-plegada):not(:has(:focus-visible)) .nav-plegar { padding: 3px 4px; }
  /* Plegada, el grupo de la página se ve cerrado: su icono es el que dice «aquí». */
  html.nav-plegada .nav:is(:not(:hover), .recien-plegada):not(:has(:focus-visible)) details:has(a.activo) > summary {
    color: var(--azul); background: var(--azul-bg);
  }
}

/* SALTAR EL NAV. Se esconde MOVIÉNDOLO, nunca con `display:none` ni `visibility:hidden`: las
   dos lo sacan del orden de tabulación, y entonces el único elemento que existe para poder
   enfocarse no se podría enfocar nunca. Es la lección de `.marca-borrar`.
   Y se mueve con `transform` y no con `top`, que es la regla de movimiento del repo. */
.skip {
  position: absolute; left: 10px; top: 10px; z-index: 100;
  padding: 8px 14px; border-radius: var(--radio-s);
  background: var(--azul); color: var(--sobre-azul);
  font-size: var(--t2); font-weight: 600; text-decoration: none;
  transform: translateY(-250%); transition: transform 150ms ease-out;
}
.skip:focus { transform: translateY(0); }
/* El destino del salto. `tabindex="-1"` lo hace enfocable POR PROGRAMA y no por tabulación:
   sin él, varios navegadores mueven el scroll y dejan el foco atrás, en el propio enlace. */
#principal:focus { outline: none; }

/* EL ANCHO DE LA HOJA Y LOS MONITORES ULTRA WIDE (2026-10-29). Hasta ese día la hoja medía 1.500 px
   y se quedaba pegada al menú: en 3.440 px sobraban ~1.700 px en blanco a la derecha y una tabla
   ancha (Remesas de Pago, el plan) seguía scrolleando de lado dentro de su tarjeta. Son dos cosas
   y van separadas:

   1. CENTRAR. Cuando sobra sitio, la hoja se centra en lo que queda a la derecha del menú, no en
      la ventana: el menú no se mueve. El margen sale de `--nav-w`, así que con la barra plegada
      (56 px) se recoloca solo; `100%` es el ancho del `<body>`, el mismo sitio donde el margen lo
      mide. `max()` lo deja en `--nav-w` cuando no sobra nada, que es el caso de siempre.
   2. ENSANCHAR, SOLO LAS TABLAS. `.contenido.ancho` (la marca la pone `_base.html` cuando la
      plantilla declara `pantalla_ancha`) sube el tope a 2.200 px. Una línea de texto de 3.000 px no
      se lee, así que fichas, formularios y textos conservan los 1.500 de siempre.

   TODO VA DENTRO DE `min-width: 1921px`: hasta 1.920 px —portátiles y monitores normales— la hoja
   mide y se coloca como antes, pixel por pixel. A cambio hay un salto de 86 px al cruzar 1.920
   (la hoja pasa de estar pegada al menú a estar centrada): nadie redimensiona una ventana por ahí
   y la alternativa era tocar los 1.920. */
:root { --ancho-trabajo: 1500px; }
.contenido { margin-left: var(--nav-w); padding: 22px 26px 60px; max-width: var(--ancho-trabajo); }
@media screen and (min-width: 1921px) {
  .contenido.ancho { --ancho-trabajo: 2200px; }
  .contenido { margin-left: max(var(--nav-w), calc((100% + var(--nav-w) - var(--ancho-trabajo)) / 2)); }
  /* Los avisos flotantes van a la esquina de la VENTANA y en 3.440 px quedaban a 800 px de lo que
     se está mirando: se alinean al borde derecho de la hoja. `100%` en un `position: fixed` es el
     ancho de la ventana; `max(0px, ...)` es el hueco que sobra a cada lado. Lleva `.contenido`
     delante para ganarle a la regla de `.avisos-flotantes` de más abajo, que es del mismo peso. */
  /* LA CURVA DEL PLAN NO CRECE CON LA HOJA. Es un SVG de proporción fija (1000x300) que se estira
     al ancho de su tarjeta, y estirada a 2.100 px mide 630 de alto con los rótulos del eje al doble
     de tamaño: se comía media pantalla en lugar de dar sitio a la tabla de debajo. Se queda en los
     1.410 px que mide hoy en un monitor normal, centrada en la tarjeta. */
  .contenido.ancho .curva svg { max-width: 1410px; margin-inline: auto; }
  .contenido .avisos-flotantes {
    right: calc(var(--e3) + max(0px, (100% - var(--nav-w) - var(--ancho-trabajo)) / 2));
  }
}

/* EL SUELO DEL `<h2>`, Y POR QUE VA A PELO (2026-09-16). Hasta hoy el archivo estilaba
   `.tarjeta > h2`, `h2.seccion-notificaciones` y `.prosa-manual h2`, y NINGUN `h2` a secas. El
   que cae fuera de esos tres sitios —el de `.encabezado` en la portada y en `/catalogos`, los
   tres de `.cabecera-bloque` en `/compras/<obra>`, el del `<dialog>` de borrado— se quedaba con
   el estilo por defecto del NAVEGADOR: 21 px y peso 700. O sea MAS GRANDE que el `<h1>` de
   20 px que tiene encima, con la jerarquia visual diciendo lo contrario que la del documento.

   Va sin clase A PROPOSITO: una regla por sitio es una lista que se queda corta en cuanto
   alguien escriba el sexto `<h2>`. Los tres selectores de arriba son (0,1,1) y este (0,0,1),
   asi que ganan ellos y no se mueve ni una tarjeta. */
h2 { margin: 0; font-size: var(--t3); font-weight: 600; letter-spacing: -.01em; }

.encabezado { display: flex; align-items: flex-start; gap: 14px; margin-bottom: 18px; flex-wrap: wrap; }
.encabezado h1 { margin: 0; font-size: var(--t4); font-weight: 600; letter-spacing: -.01em; }
.encabezado .sub { color: var(--texto-suave); font-size: var(--t2); margin-top: 2px; }
/* El estatus de una obra junto a su código (`obras_ficha.html`, 2026-09-24). */
.encabezado h1 .chip { vertical-align: middle; margin-left: 8px; }
/* EL BLOQUE DERECHO DE UNA CABECERA, Y POR QUE TIENE NOMBRE DESDE EL 2026-09-16. Hasta hoy lo
   empujaban 23 plantillas con `style="margin-left:auto"` a mano, y el breakpoint de 600px tenia
   que deshacerlo seleccionando POR EL ATRIBUTO DE ESTILO —`[style*="margin-left:auto"]`—, que es
   la confesion de que faltaba un nombre al que agarrarse. `.barra-tabla-fin` ya lo tenia; esto
   es lo mismo una fila mas arriba. La variante `.derecha` son las 7 que alineaban sus cifras. */
.encabezado-fin { margin-left: auto; }
.encabezado-fin.derecha { text-align: right; }
/* LOS BOTONES DE UNA CABECERA VAN EN UNA FILA FIJA (2026-09-25). `/plan/<obra>` los metia en
   `.en-juego`, que es una caja de CIFRAS sin flex, junto a la barra de plegar: la barra ocupaba
   su linea y «Programar» se quedaba solo arriba. Aqui no entra nada que no sea un boton, y el
   orden lo fija la plantilla: un boton que depende del ESTADO se pinta apagado, no se quita,
   asi que los demas no cambian de sitio. */
.acciones-cabecera { display: flex; align-items: center; gap: var(--e1, 6px); flex-wrap: nowrap; }

/* ---------------- tarjetas ---------------- */

.tarjeta {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radio);
  box-shadow: var(--sombra);
  padding: 16px 18px;
  margin-bottom: 16px;
}
/* EL TÍTULO DE TARJETA EN CAJA NORMAL DESDE EL 2026-11-05 (Alex: «cabeceras y títulos en letra
   normal», después del menú y la bandeja el 2026-11-04). Era 13 px en MAYÚSCULAS, espaciadas y en
   gris suave: la caja alta hacía de jerarquía. Sin ella la jerarquía la marcan EL PESO Y EL
   COLOR, así que sube a `--t3` y a `--texto` —el «Title» de DESIGN.md, el mismo que el paso de
   la bandeja—: a 13 px y en gris, en minúsculas, se leía como una nota. */
.tarjeta > h2 {
  margin: 0 0 12px; font-size: var(--t3); font-weight: 600; color: var(--texto);
}
/* El título de una SECCIÓN de `/notificaciones`, que agrupa varias tarjetas y por eso vive
   fuera de ellas. Reusa el tamaño y el color de `.tarjeta > h2` —dos tipografías para el
   mismo rango de encabezado es cómo se acaba con tres tamaños para el mismo componente— y
   solo añade el aire de arriba y una línea que marca dónde empieza la sección. */
h2.seccion-notificaciones {
  margin: 26px 0 12px; font-size: var(--t3); font-weight: 600; color: var(--texto);
  border-top: 1px solid var(--border); padding-top: 16px;
}
/* MAYOR QUE EL TEXTO QUE ACOMPANA: este `<h2>` es `--t3` (15px), asi que un icono a su
   tamano exacto se lee como parte de la palabra; 16 px lo despega lo justo para verse como
   un icono. El `vertical-align` lo baja a la linea base del texto, que es lo que un glifo
   hacia solo.

   (Hasta el 2026-09-16 este comentario decia «este `<h2>` es `--t4` (20px)». La regla de
   aqui arriba era `font-size: 13px` literal desde que se escribio: el razonamiento del
   tamano del icono estaba montado sobre un tamano que la regla nunca tuvo. Es lo que pasa
   cuando el token no se usa — no hay contra que contrastar lo que dice el comentario.) */
h2.seccion-notificaciones .icono { width: 16px; height: 16px; vertical-align: -2px; }
h2.seccion-notificaciones:first-of-type { margin-top: 0; border-top: none; padding-top: 0; }

.rejilla { display: grid; gap: 14px; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); }

/* Cifras de CONTEXTO, no la pantalla: cuatro celdas estrechas encima de los filtros. Lo que
   cambia es el ancho de la celda y NO el tamaño del número —el número grande de una tarjeta
   tiene un solo tamaño, `.kpi`, y esa regla existe porque se escribió con tres distintos. */
/* El mínimo son 200px y NO 170, que es lo que era hasta el 2026-08-07: con 170 la celda salía
   de 175px en /pcc/ y `US$22,032,403.88` mide 190 a `.kpi`, así que TRES cifras se montaban
   encima de la vecina y se leía `US$22,032,403.88$5,826,327.02`. El número no se encoge —el
   número grande de una tarjeta tiene UN tamaño—: se ensancha la celda, que es de lo que va
   esta clase. */
.rejilla.compacta { gap: 10px; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); }

/* El número grande de una tarjeta. Se escribía a mano con tres tamaños distintos
   (28/24/22px) para el mismo componente, y la pantalla se veía desalineada sin que
   se supiera por qué. */
.kpi { font-size: var(--t5); font-weight: 600; letter-spacing: -.01em; font-variant-numeric: tabular-nums; }

/* La nota al pie de una tarjeta: una por tarjeta, dos frases como mucho. */
.nota { margin-top: var(--e2); color: var(--texto-tenue); font-size: var(--t1); }

/* ---------------- la cabecera de `/pcc/<obra>`: tarjetas KPI y la regla ----------------
   CADA CIFRA EN SU TARJETA, con la etiqueta en el color del texto y no tenue: el dueño no
   quiere cifras leídas en gris (2026-09-27). `.tk` y no `.tarjeta` porque van en rejilla y
   sin sombra: una sombra por cifra son nueve sombras en una cabecera. La señal es el COLOR
   DEL TEXTO —rojo lo que se pasa, ámbar lo que falta imputar—, nunca una pastilla. */
.cabeza-pcc { margin-bottom: var(--e4); }
.tk-fila { display: grid; gap: var(--e3); }
.tk-cuatro { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.tk { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radio-s);
  padding: var(--e3) 14px; min-width: 0; }
.tk-et { font-size: var(--t1); font-weight: 600; color: var(--texto); }
.tk-cifra { font-size: var(--t5); font-weight: 600; letter-spacing: -.01em; margin-top: var(--e0);
  font-variant-numeric: tabular-nums; white-space: nowrap; }
.tk-dato { font-size: var(--t2); font-weight: 600; color: var(--texto); margin-top: 2px;
  font-variant-numeric: tabular-nums; }
.tk.malo { border-color: var(--rojo); }
.tk-cifra.malo, .tk-dato.malo, .tk.malo .tk-cifra { color: var(--rojo); }
.tk-cifra.bueno, .tk-dato.bueno { color: var(--verde); }
.tk.pendiente .tk-cifra, .tk-dato.pendiente { color: var(--ambar); }
/* La cuenta del actualizado: dos grupos que se parten por el signo `=` cuando no caben en una
   fila, para que ningún sumando quede separado de su `+`. */
.tk-cuenta { display: flex; flex-wrap: wrap; gap: var(--e3); }
.tk-grupo { display: flex; gap: var(--e2); align-items: stretch; flex: 1 1 auto; }
.tk-grupo .tk { flex: 1 1 0; }
.tk-cuenta .signo { align-self: center; font-size: 18px; font-weight: 600; color: var(--texto); padding: 0 2px; }
.punto { display: inline-block; width: 8px; height: 8px; margin-right: var(--e1); border-radius: 1px; }
/* LA REGLA DIBUJA LA FILA DE ABAJO: imputado, comprometido y por gastar sobre la escala del
   mayor de actualizado y objetivo, la marca del objetivo encima y en rojo lo que se pasa. */
.regla { position: relative; margin: 18px 2px 26px; }
.regla-barra { position: relative; display: flex; height: 8px; border-radius: 2px; background: var(--surface-2); }
.regla .seg { display: block; height: 100%; }
.seg.imputado, .punto.imputado { background: var(--texto); }
.seg.comprometido, .punto.comprometido { background: var(--texto-tenue); }
.seg.por-gastar, .punto.por-gastar { background: var(--border-fuerte); }
.regla-objetivo { position: absolute; top: -5px; bottom: -5px; width: 2px; margin-left: -1px; background: var(--texto); }
.regla-exceso { position: absolute; top: -5px; height: 3px; background: var(--rojo); }
.regla-marca { position: absolute; top: 14px; transform: translateX(-50%); font-size: var(--t0);
  font-weight: 600; color: var(--texto); white-space: nowrap; }
@media (max-width: 1200px) { .tk-cuatro { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

.aviso-filtro { margin: var(--e2) 0 0; font-size: var(--t2); font-weight: 600; color: var(--texto); }

/* LÍNEA ID Y DESCRIPCIÓN SE QUEDAN QUIETAS al scrollear en horizontal (2026-09-27): con diez
   columnas de dinero, a partir de la cuarta no se sabía de qué partida era la cifra. La
   segunda necesita saber cuánto mide la primera, y aquí `anchos.js` la deja cambiar, así que
   el `left` lo pone `arbol.js` en `--pcc-col1` y no un px fijo como `.rejilla-plan`. El fondo
   sale del NIVEL de la fila, y el rosa de `tr.falta` tiene que llegar también a la parte fija. */
table[data-tabla-id="pcc-partidas"] tr > :nth-child(-n+2) {
  position: sticky; z-index: 2; background: var(--nivel-bg, var(--surface)); }
table[data-tabla-id="pcc-partidas"] tr > :first-child { left: 0; }
table[data-tabla-id="pcc-partidas"] tr > :nth-child(2) {
  left: var(--pcc-col1, 0px); box-shadow: inset -1.5px 0 0 var(--border-fuerte); }
table[data-tabla-id="pcc-partidas"] tr[data-nivel]:hover > :nth-child(-n+2) { background: var(--surface-2); }
table[data-tabla-id="pcc-partidas"] tr.falta > :nth-child(-n+2) { background: var(--rojo-bg); }
table[data-tabla-id="pcc-partidas"] tfoot tr > :nth-child(-n+2) { background: var(--surface); }
table[data-tabla-id="pcc-partidas"] thead tr > :nth-child(-n+2) { z-index: 4; background: var(--surface-2); }
table[data-tabla-id="pcc-partidas"] thead tr > :nth-child(2) {
  box-shadow: inset 0 -1.5px 0 var(--border-fuerte), inset -1.5px 0 0 var(--border-fuerte); }
@media (max-width: 640px) {
  .tk-cuatro { grid-template-columns: minmax(0, 1fr); }
  .tk-grupo { flex-wrap: wrap; }
  .tk-grupo .tk { flex: 1 1 100%; }
}

/* ---------------- el avance de certificación ---------------- */

/* CERTIFICAR DE MAS NO ES UN ERROR DE CALCULO: es que falta la adenda que ya se firmó, o que
   sobra una estimación. Va en rojo porque es lo que hay que mirar, y el % NO se recorta al
   100 — hoy 10 contratos lo pasan, con `085-A` al 162,4 %. Por debajo del 100 no se colorea
   nada: que quede obra por certificar no es bueno ni malo. */
.sobre-estimado { color: var(--rojo); }

/* La barra es la MISMA cifra en otra unidad, no un dato nuevo, así que es decorativa
   (`aria-hidden`) y el número manda. El relleno se corta al 100 aunque el % se pase: una
   barra que se sale de su caja rompe la fila, y el desbordamiento ya lo dice el color. */
.barra-avance {
  display: inline-block; vertical-align: middle; margin-left: 8px;
  width: 64px; height: 6px; border-radius: 999px;
  /* `--borde` no existe —el token es `--border`— y un `var()` sin definir cae al valor
     inicial de la propiedad, que en `background` es TRANSPARENTE: la pista de la barra no
     se pintaba desde que se escribió, y solo se veía el relleno flotando sobre la tarjeta. */
  background: var(--border); overflow: hidden;
}
.barra-avance .relleno { display: block; height: 100%; background: var(--texto-tenue); }
.barra-avance .relleno.sobre { background: var(--rojo); }

/* Una tarjeta plegada es un camino que existe pero no es el normal (importar de un Excel,
   sembrar al arrancar). Se conserva el triángulo nativo: sin él no se ve que se abre. */
details.tarjeta > summary { cursor: pointer; }
/* SALVO QUE LA TARJETA TRAIGA SU PROPIO ICONO, y entonces el nativo sobra. `estructura_grupo`
   pintaba DOS marcadores —el del navegador y su `.flecha-plegar`— desde que se escribió, y no
   se veía porque el triángulo de bordes pasaba por decoración. Con el icono de verdad se ve.
   La regla sale del DATO (`:has`) y no de una lista de plantillas: la siguiente que se ponga
   su icono lo hereda, y la que no, conserva el nativo que la regla de arriba le promete. */
details.tarjeta:has(> summary > .flecha-plegar) > summary { list-style: none; }
details.tarjeta:has(> summary > .flecha-plegar) > summary::-webkit-details-marker { display: none; }

/* ---------------- LA BARRA DE UNA TABLA ----------------
   El hueco unico que sustituye a los cinco `<div id="plegar-*" style="display:flex; gap:6px">`
   escritos a mano en cinco plantillas, al boton `Anchos` que `anchos.js` colgaba en el `<nav>`
   y al `Borrar filtros de columna` que `tablas.js` insertaba por su cuenta. Va DENTRO de la
   tarjeta y FUERA de `.tabla-scroll`: dentro se iria con el scroll horizontal de una tabla
   ancha, que es donde mas falta hace — la misma regla que `tablas.js` ya llevaba escrita.

   `flex-wrap` porque a 900px la barra de `/imputaciones/<obra>` no cabe en una linea, y ahi
   envolver es lo correcto: son botones sueltos, no una tabla. */
.barra-tabla { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin-bottom: 10px; }
/* Los menus de la pantalla (`Columnas`, `Filas`) a la derecha: no son acciones de un clic. */
.barra-tabla-fin { margin-left: auto; display: flex; align-items: center; gap: 6px; }
/* EL DESPLEGABLE DE LA BARRA MIDE LO QUE SUS BOTONES (2026-10-08). Los de la barra son
   `.boton.mini` (20 px) y el «Semana de pago» de las remesas salía con el alto de un campo de
   ficha, 33: el doble que los botones a los que da destino. Mismo renglón y mismos bordes que
   `.boton.mini`; el alto va escrito porque un `<select>` no baja de su renglón con el padding. */
.barra-tabla-fin select {
  height: calc(var(--t1) * 1.2 + 6px); padding: 0 7px; font-size: var(--t1);
}
/* EN EL MOVIL LA PARTE DERECHA BAJA ENTERA Y SUS BOTONES NO SE PARTEN (2026-09-27). Sin esto,
   en /remesas-pago/ a 375 px los botones se estrujaban en columnas de cuatro renglones y
   «Guardar la captura» se cortaba contra el borde de la tarjeta. */
@media (max-width: 600px) {
  .barra-tabla-fin { flex-wrap: wrap; margin-left: 0; flex-basis: 100%; }
  .barra-tabla-fin .boton { white-space: nowrap; }
}
/* Una barra que se quedo sin un solo boton no puede cobrar sus 10px de margen. `:empty` no
   sirve — el marcado deja saltos de linea dentro— y `:not(:has(...))` si: mira si queda algo
   PINTADO, o sea un control que el script no haya dejado `hidden`. */
.barra-tabla:not(:has(> :not([hidden]))) { display: none; }
/* El boton que tiene abierto lo suyo se marca, como el embudo de una columna filtrada. Sin
   esto, con su menu abierto nada dice desde donde se abrio. */
.barra-btn.activo { border-color: var(--azul); color: var(--azul); }


/* ---------------- semáforos ---------------- */

/* EN CAJA NORMAL DESDE EL 2026-11-05, como las cabeceras: sube a 12 px porque una minúscula de
   11 px es medio punto más baja que la mayúscula que había. El texto es el del vocabulario de
   estados de `MASTER.md` §5 («sin PCC», «a medias»), que ya iba en minúscula y no se toca. */
.chip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 2px 9px; border-radius: 999px;
  font-size: var(--t1); font-weight: 600;
  white-space: nowrap;
}
.chip::before { content: ""; width: 7px; height: 7px; border-radius: 50%; background: currentColor; }
.chip.VERDE { color: var(--verde); background: var(--verde-bg); }
.chip.AMBAR { color: var(--ambar); background: var(--ambar-bg); }
.chip.ROJO  { color: var(--rojo);  background: var(--rojo-bg); }
.chip.GRIS,
.chip.DESCONOCIDO { color: var(--gris); background: var(--gris-bg); }
/* AZUL en un chip es la excepción declarada, y encaja con la semántica de arriba —«acción /
   navegación»— porque el único que lo usa ES UN ENLACE: el estatus de una certificación en la
   cabecera de su columna, que lleva a donde se cambia. Hacía falta un tercer color porque los
   tres estados son EN TRAMITE (ámbar: el ciclo sigue), FACTURADA y COBRADA (verde: al día), y
   con dos, dos de ellos se pintaban igual. */
.chip.AZUL { color: var(--azul); background: var(--azul-bg); }
a.chip:hover { text-decoration: none; filter: brightness(.96); }
/* La trampa de `.boton[hidden]`, en el chip: su `display:inline-flex` pisa el `[hidden]` del
   navegador, y `/plan/<obra>/programar` enseñaba «0 sin repartir del todo» (2026-09-24). */
.chip[hidden] { display: none; }

.aviso { border-left: 3px solid var(--border-fuerte); padding: 12px 14px; border-radius: var(--radio-s); background: var(--surface-2); }
.aviso.ROJO  { border-left-color: var(--rojo);  background: var(--rojo-bg); }
.aviso.AMBAR { border-left-color: var(--ambar); background: var(--ambar-bg); }
.aviso.VERDE { border-left-color: var(--verde); background: var(--verde-bg); }
.aviso .titulo { font-weight: 600; margin-bottom: 3px; }

/* EL VISOR DE UN PDF DENTRO DE UNA FICHA (2026-09-24, el del pedido). Alto de pantalla y no
   de hoja: una hoja A4 entera a ese ancho obliga a desplazar la pagina para llegar a las firmas. */
.visor-pdf { display: block; width: 100%; height: 80vh; border: 1px solid var(--border);
             border-radius: var(--radio-s); background: var(--surface-2); }
.aviso .accion { margin-top: 8px; font-size: 12.5px; color: var(--texto-suave); }
.aviso .accion b { color: var(--texto); font-weight: 600; }

/* ---------------- tablas ---------------- */

.tabla-scroll { overflow-x: auto; }
/* LA BARRA HORIZONTAL DE UNA TABLA ANCHA NO ES LA FINA DEL RESTO (2026-10-06). Alex: *«la barra
   de scroll horizontal debería ser distinta»*. La fina y sin canal de `*` sirve para un panel que
   se desplaza con la rueda; una tabla ancha se recorre ARRASTRANDO la barra, y en tema claro su
   pulgar gris claro sobre blanco no se encontraba. Aquí va gruesa (`auto`), con canal visible y
   un pulgar con contraste en los dos temas. Sigue sin `::-webkit-scrollbar`, por lo mismo que
   la de `*`: en Chrome/Edge 121+ las propiedades estándar mandan. */
.tabla-scroll, .rejilla-scroll {
  scrollbar-width: auto;
  scrollbar-color: var(--scroll-tabla-pulgar) var(--scroll-tabla-canal);
}
.tabla-scroll:hover, .rejilla-scroll:hover {
  scrollbar-color: var(--scroll-tabla-pulgar-hover) var(--scroll-tabla-canal);
}
/* LA FILA DE 28px, Y POR QUE UNA HOJA DE CALCULO. Hasta el 2026-08-25 la celda medía `8px 10px`
   con el `line-height: 1.5` heredado de `body`, o sea ~36-40px de fila —y 41+ donde la celda
   lleva un `<select>`—. La queja fue literal: *«las tablas están muy gordas, que parezcan filas
   de Excel angostas»*. El barrido de compactación llevaba trece pasos quitando COSAS (columnas,
   tiles, prosa) y no había tocado ni un píxel de la fila.

   `line-height` SE DECLARA AQUI Y NO SE HEREDA: el 1.5 de `body` es la mitad de la grasa de una
   celda, y sin esta línea bajar el padding solo aprieta por arriba y por abajo de un renglón
   que sigue siendo alto. */
table { width: 100%; border-collapse: collapse; font-size: var(--t2); line-height: 1.35; }

/* LA CABECERA LLEVA FONDO desde el 2026-08-25, y no es adorno: con la raya de columna puesta,
   una cabecera transparente deja la rejilla corriendo por encima del `<thead>` como si fuera una
   fila más. El fondo es lo que la separa de los datos, que es lo que hace una hoja de cálculo.

   Y EL TEXTO SUBE A `--texto`: `--texto-suave` sobre `--surface` daba 5,45:1, pero la cabecera
   ya no va sobre `--surface` sino sobre `--surface-2`, que es más oscuro.

   EN CAJA NORMAL DESDE EL 2026-11-05 (Alex, después del menú y la bandeja): eran 11 px en
   MAYÚSCULAS espaciadas. Sube a 12 px (`--t1`) porque la minúscula de 11 se queda medio punto por
   debajo de la mayúscula que había, y el texto escrito en la plantilla —«Costo Objetivo»,
   «Semana»— se lee tal cual. La fila crece un píxel; los anchos medidos (`anchos.js`) miden el
   texto, que en minúscula es más estrecho, así que ninguna columna se ensancha.

   `position: relative` NO SE TOCA —aloja el `.popover` del embudo, que es `top: 100%`— y por lo
   mismo aquí no entra ningún `overflow: hidden`. */
thead th {
  text-align: left; padding: var(--celda-y) var(--celda-x);
  background: var(--surface-2);
  border-bottom: 1.5px solid var(--border-fuerte);
  font-size: var(--t1); font-weight: 600;
  color: var(--texto); white-space: nowrap; position: relative;
}
tbody td { padding: var(--celda-y) var(--celda-x); border-bottom: 1px solid var(--border); }

/* LA RAYA DE COLUMNA. La de fila (`--border`) separa registros y la de columna (`--border-suave`)
   solo guía el ojo al bajar por una columna: dos pesos y no uno, o la rejilla tapa el dato.

   AL ULTIMO NO SE LE PONE: `border-collapse: collapse` funde el borde de la última celda con el
   de la tabla, y ahí la raya se ve el doble de gruesa justo en el filo. */
thead th, tbody td { border-right: 1px solid var(--border-suave); }
thead th:last-child, tbody td:last-child { border-right: none; }

/* CENTRADO Y NO ARRIBA. Con 8px de padding un texto de dos líneas al lado de uno de una se
   notaba poco; con 5px, `top` deja el renglón corto pegado al borde de arriba y la fila se lee
   torcida. */
tbody td { vertical-align: middle; }

/* NINGUNA CELDA ENVUELVE — 2026-08-25, y es la mitad que faltaba para que 28px sea la fila de
   verdad y no la fila mínima. La celda medía 28 solo cuando su contenido cabía en un renglón:
   una descripción larga de `/imputaciones/<obra>` la subía a 43-76px, y el ancho de esa columna
   NO es una decisión de diseño sino lo que trajo el origen. Palabras del dueño mirando
   producción: *«no quiero que se anchen las filas, que se corte el texto y si pasas el mouse se
   vea el completo»*.

   **EL PATRON YA ESTABA EN EL REPO Y SE COPIA DE AHI**: `.rejilla-plan .col-nombre` lleva estas
   tres propiedades desde que la rejilla existe, por la misma razón —260px fijos y un nombre de
   partida que no cabe—. Lo que cambia es que deja de ser de una columna y pasa a ser la regla.

   LO CORTADO LO DICE UN `title` QUE PONE `anchos.js`, y solo cuando de verdad no cabe
   (`scrollWidth > clientWidth`): cortar sin decir qué se cortó sería esconder un dato. */
tbody td { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* Y LOS RENGLONES SECUNDARIOS SUBEN A LA MISMA LINEA. Sin esto el `nowrap` no sirve de nada en
   la mitad de las tablas: un `<div>` es un bloque y sigue bajando de línea aunque su texto no
   envuelva. Son la desviación, el «de X a Y» de un movimiento, el «% de la venta» de un margen y
   —el que más cuesta— **el importe POR DIVISA del macro `importe()`**, que apila uno por moneda:
   todos nacieron como bloque porque la celda podía crecer, y ya no puede.

   ES DESCENDIENTE Y NO HIJO DIRECTO (`td div` y no `td > div`): los importes por divisa van
   DENTRO del `<a>` que enlaza la celda, así que un `>` los dejaría fuera — que es exactamente el
   defecto que se midió el 2026-08-25 en la columna «Sin clasificar» de `/pcc/`, con el `+` en un
   renglón y su importe en el siguiente.

   EL HUECO VA ENTRE HERMANOS Y NO EN EL PRIMERO. El primer `<div>` de una celda suele venir
   pegado al texto que lo precede —el `+` de «sin clasificar»— y separarlo lo partiría en dos
   cosas; el segundo SI necesita aire, o dos divisas se leerían como un solo número. */
tbody td div { display: inline-block; vertical-align: baseline; margin-left: var(--e1); }
tbody td div:first-child { margin-left: 0; }

/* LAS REJILLAS DE CAPTURA SE QUEDAN FUERA, y no es una excepción escondida: `overflow: hidden`
   recorta el ANILLO DE FOCO de un `<input>` que llena su celda —2px con 1 de `outline-offset`,
   fuera de la caja— y ahí se teclea fila a fila. Es el mismo corte que ya separa el control de
   28px del de 44 en `MASTER.md` §5: una tabla que se LEE y una rejilla donde se ESCRIBE. */
.rejilla-captura tbody td, .matriz-fija tbody td,
tbody td.celda-captura, tbody td.celda-tasa, tbody td.celda-acciones { overflow: visible; }
/* `celda-acciones` ES PARA UNA CELDA CON BOTONES DE ACTO, no de texto (2026-10-04, nacida en
   «Cancelaciones por revisar», retirada el 2026-10-26). `anchos.js` reparte el ancho EN PIXELES
   Y REDONDEA cada columna
   por su lado —el error de redondeo entre varias columnas en `table-layout: fixed` puede
   dejar una un par de px corta de su contenido real— y ahi `text-overflow: ellipsis` no deja
   un boton a medio cortar: lo esconde ENTERO. Un boton escondido no se pulsa; un texto no
   cabe y se lee en el tooltip. Son dos cosas distintas y cada una lleva su escape. */
/* Y el cerrojo general, para el `<select>` de imputar y cualquier control que caiga en una celda
   de lectura: mientras tenga el foco, su celda enseña el anillo entero. */
tbody td:focus-within { overflow: visible; }
/* TEXTO LIBRE EN DOS RENGLONES (`.dos-lineas`, 2026-10-05). El `nowrap` de arriba corta una frase
   en un solo renglon, y la que Costos lee para decidir —el «por que» de una cancelacion— quedaba
   en «el proveedor ya no en…» a 1.440 px. Un `<span>` dentro de la celda, no la celda: una `<td>`
   con `display: -webkit-box` deja de ser celda y descuadra la fila. Lo que ni en dos renglones
   cabe lo sigue diciendo el tooltip (`anchos.js` mira tambien el alto). */
tbody td .dos-lineas { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;
  line-clamp: 2; overflow: hidden; white-space: normal; line-height: 1.3; }
tbody tr:hover { background: var(--surface-2); }
/* El padding IZQUIERDO de una columna numérica es aire: el número va pegado a la derecha.
   Bajarlo devuelve ancho por columna, y en el índice de /pcc/ —siete columnas de dinero— fue lo
   que le faltaba a `Comprometido` para caber sin scroll. Se escribe en px y no con `--celda-x`
   porque NO es la medida de la celda: es la mitad que se puede recortar de ella. */
td.num, th.num { text-align: right; font-variant-numeric: tabular-nums; padding-left: 6px; }
td.mono, .mono { font-family: var(--mono); font-size: 12.5px; }
tfoot td { padding: var(--e0) var(--celda-x); border-top: 1.5px solid var(--border-fuerte); font-weight: 600; }

/* TRES RENGLONES POR OBRA (`/gestion/`, 2026-10-26: «el scroll horizontal me mata»). Cada obra es
   un `<tbody class="obra">` de tres filas con las mismas columnas, y lo que es de la obra entera
   —el código con su nombre, la venta— va en una celda que abarca las tres (`rowspan`).

   LA RAYA ENTRE RENGLONES ES LA SUAVE y la de la obra es la de fila de siempre: sin dos pesos
   las nueve filas de tres obras se leerían como nueve registros. Y el `border-bottom` de la
   celda que abarca cae al pie del `<tbody>`, que es donde termina la obra.

   EL NOMBRE SE CORTA CON «…» Y NO ENSANCHA LA COLUMNA (`max-width: 0` + un `width`: la celda
   toma ese reparto y su texto, que ya es `nowrap` + `overflow: hidden`, se recorta). Es la única
   columna que cede: las de dinero nunca se cortan —un número cortado es un número falso—. El
   `title` de la celda dice el nombre entero.

   EL RENGLÓN QUE MÁS SE MIRA, el Actualizado, va en seminegrita: sin chips ni colores nuevos.
   `.obra:hover` pinta la obra entera, y no un renglón suelto con la mitad de su obra en blanco. */
table.tres-renglones tbody.obra > tr > td { border-bottom: 1px solid var(--border-suave); }
table.tres-renglones tbody.obra > tr:last-child > td,
table.tres-renglones tbody.obra > tr > td.dato-obra { border-bottom: 1px solid var(--border); }
table.tres-renglones tbody.obra > tr:last-child > td { border-bottom-color: var(--border-fuerte); }
table.tres-renglones tbody.obra:hover > tr { background: var(--surface-2); }
table.tres-renglones tbody.obra > tr.renglon-clave > td:not(.dato-obra) { font-weight: 600; }
table.tres-renglones td.renglon { font-size: var(--t1); color: var(--texto-suave); }
table.tres-renglones tr.renglon-clave > td.renglon { color: var(--texto); }
table.tres-renglones td.dato-obra { width: 25%; max-width: 0; }
table.tres-renglones td.num.dato-obra { width: auto; max-width: none; }
table.tres-renglones .obra-codigo,
table.tres-renglones .obra-nombre { display: block; overflow: hidden; text-overflow: ellipsis; }

/* RENGLONES DENTRO DE LA CELDA (`/pcc/` y `/plan/`, 2026-10-26: «arregla también el PCC y la
   planificación, sin scroll»). Es el otro camino de «Una lista de obras se lee sin desplazarla de
   lado», para las tablas que SI ordenan y eligen columnas —`data-tabla-id`— y por tanto no pueden
   partir cada obra en varias filas: cada celda lleva su cifra principal (`.r1`) y, debajo, lo que
   va con ella (`.r2`, `.r3`: un margen, una desviación, el nombre de la obra). La fila es una, así
   que `tablas.js` ordena y filtra como siempre, por el `data-v` de la cifra principal.

   `.rN` SON `<span>` EN BLOQUE Y NO `<div>`: `tbody td div` los sube a la misma línea (ninguna celda
   envuelve) y aquí lo que se quiere es justo lo contrario. Un importe NUNCA se corta —un número
   cortado es un número falso—; lo único que cede es el texto (`.r2.corta`: el cliente, el nombre),
   con «…» y su `title`. El ancho lo decide la cifra más larga de la columna, no la cabecera:
   la cabecera parte en dos renglones (`data-encoge` en la primera columna, ver `anchos.js`).

   EL ÁMBAR DE LO PENDIENTE ES EL DE SIEMPRE (`td.num.revisar`), en el enlace y no en un chip: es
   un renglón de texto, y Alex no quiere chips en tablas. */
table.renglones td > .r1,
table.renglones td > .r2,
table.renglones td > .r3 { display: block; overflow: hidden; text-overflow: ellipsis; }
table.renglones td > .r2,
table.renglones td > .r3 { font-size: var(--t1); color: var(--texto-tenue); }
/* `.dato`: un renglón de segundo nivel que es una CIFRA y no una nota (el final de un ejercicio):
   con el tamaño y el color del cuerpo, y no con los de lo que va debajo de una cifra. */
table.renglones td > .r2.dato { font-size: var(--t2); color: var(--texto); }
table.renglones td > .r2 a.revisar,
table.renglones td > .r3 a.revisar { color: var(--ambar); }
table.renglones td > .r2.malo,
table.renglones td > .r3.malo { color: var(--rojo); }
table.renglones td > .r2.bueno,
table.renglones td > .r3.bueno { color: var(--verde); }
/* LA CABECERA PARTE EN RENGLONES, y es lo que hace caber la tabla. Con `nowrap` («COSTE INCURRIDO Y
   PENDIENTE», 200 px) la cabecera, y no la cifra, decidía el ancho de su columna; partida, el ancho
   es el del renglón más largo del cuerpo. Tiene que ser así ANTES de que monte `anchos.js`, que
   mide cada columna tal como el navegador la deja: un `<span>` de bloque mide lo que su celda. */
table.renglones thead th { white-space: normal; }
/* LA CELDA DE UN ICONO (la curva): el botón mini de siempre, centrado y sin más. */
table.renglones td.celda-icono { text-align: center; overflow: visible; }
table.renglones th.celda-icono { width: 56px; }

/* ---- anchos.js: SOLO cuando el script monta ----
   La clase la pone el JS, nunca la plantilla. Sin JavaScript esta regla no casa con nada y la
   tabla se pinta con el algoritmo automático de siempre: poner `table-layout: fixed` desde la
   plantilla dejaría columnas cortadas con anchos que nadie ha calculado. */
table.anchos { table-layout: fixed; }
/* Bajo `fixed` el `nowrap` de la cabecera ya no reserva ancho: una etiqueta larga en una
   columna estrecha se saldría por encima de la vecina. Que se parta en dos líneas.
   EL <th> NO se recorta con `overflow: hidden`: aloja el agarre de `anchos.js`, a caballo de su
   borde. Lo que se recorta es la caja de dentro (abajo). */
table.anchos thead th { white-space: normal; }
/* Y LA PALABRA QUE NO CABE NI SOLA SE CORTA CON «…» — 2026-10-06. Partir en renglones no basta:
   «PENDIENTE» en una columna de 60 px se montaba encima de la vecina. Recorta `.th-caja`, que
   `anchos.js` mete en cada cabecera con todo menos el agarre (el <th> no se recorta, por el
   agarre a caballo del borde). SIGUE PARTIENDO EN RENGLONES ENTRE PALABRAS, como hasta hoy: las
   cabeceras que la pasada 1 de `caber()` deja en dos a proposito («Se necesita» en `/bandeja/`)
   se leen igual, con el embudo tras la ultima palabra. El «…» sale solo en el renglon que se
   desborda, y lo que se corto lo dice el tooltip. No se limita a dos renglones con
   `line-clamp`: esconderia el embudo cuando cae en el tercero.
   EL MARGEN NEGATIVO Y SU RELLENO son 4 px de aire para el anillo de foco del embudo y del
   enlace de ordenar, que con el recorte justo en el borde del texto saldria partido. */
table.anchos thead th > .th-caja {
  display: block; overflow: hidden; text-overflow: ellipsis; margin: -4px; padding: 4px; }
/* EL ENLACE DE ORDENAR VA EN LINEA aquí, y no `inline-flex`: una caja entera el «…» de arriba la
   escondería COMPLETA, nombre incluido. En línea se corta letra a letra, y su flecha es una
   palabra más, que baja de renglón cuando no cabe.
   NO SE PEGA LA FLECHA AL NOMBRE, y se probó: `caber()` aprieta la columna hasta la palabra más
   ancha sin contar la flecha, así que pegada no cabía y el «…» se llevaba letras del nombre
   («CONCEPT…» en `/pedidos/<obra>` con el ancho por defecto). Contarla ensanchaba esas columnas y
   devolvía el scroll a `/imputaciones/<obra>` a 1.440. Suelta, la flecha baja de renglón y se ve
   entera, cuando hasta hoy la tapaba la columna de al lado. El precio, medido: en
   `/pedidos/<obra>` «% INCURRIDO» pasa a tres renglones y su cabecera crece una línea (40 → 55 px);
   en `/imputaciones/<obra>` no cambia nada, porque esos segundos renglones ya existían. */
table.anchos thead th a.ordenar { display: inline; }
/* Un `pedido` o un `ID Único` no llevan espacios: sin esto desbordan su celda. */
table.anchos td { overflow-wrap: anywhere; }
/* UN IMPORTE NUNCA SE LEE COMO OTRA CIFRA — 2026-10-06. Los renglones secundarios del cuerpo
   suben a la misma línea como `inline-block` (arriba, «Y LOS RENGLONES SECUNDARIOS…»), y una caja
   entera NO se corta letra a letra: si la primera no cabía, el navegador la recortaba en seco y
   sin «…» —en `/pcc/<obra>` estrechado, «$680,00» por «$680,000.00»—, que se lee como otra cifra.
   En línea el «…» entra dentro del número y el tooltip lo dice entero.
   SOLO BAJO `.anchos`, que es donde se corta: sin el script la columna crece y no hay nada que
   cortar. Y con `:where()`, para no pasar por encima de un `<div>` con clase que declare su propio
   `display`: pesa lo mismo que la regla de arriba y gana solo por ir después. Medido con el ancho
   por defecto en las ocho pantallas con estos `<div>`: ni un ancho ni un alto de celda se mueve —
   para un texto sin hijos de bloque, en línea y `inline-block` ocupan lo mismo. */
:where(table.anchos) tbody td div { display: inline; }
/* LOS TOTALES NO SE PARTEN: CADA DATO EN SU RENGLON, CON «…» Y EL TOOLTIP — 2026-10-06. El pie
   envolvía, y con el `anywhere` de arriba partía un importe por la mitad («US$1,192,» /
   «611.19») en cuanto se estrechaba la columna. Ahora se corta como el cuerpo: sin envolver,
   con «…», y lo cortado lo dice el tooltip (`anchos.js`).
   LO QUE NO SE COPIA DEL CUERPO ES SUBIR LOS RENGLONES SECUNDARIOS A LA MISMA LINEA. Allí lo
   exige la fila de 28 px; el pie son cuatro filas y lo que lleva debajo del total es su nota.
   Se probó en una línea y, con el ancho por defecto, el TOTAL de «Por gastar» de `/pcc/<obra>`
   salía «$750,750.…»: la nota de detrás («el de las partidas: sin descontar…») empujaba el «…»
   dentro de la cifra. Cada `<div>` —un importe por divisa, la nota, «1 pedido»— es su propio
   renglón y se corta él solo, así que un importe se ve entero o con «…», nunca partido.
   SOLO BAJO `.anchos`: el ancho por defecto se mide ANTES, con el pie como siempre. */
table.anchos tfoot td { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
table.anchos tfoot td div { overflow: hidden; text-overflow: ellipsis; }
table.anchos tfoot td:focus-within { overflow: visible; }

/* LAS COLUMNAS QUIETAS DE UNA TABLA CON `anchos.js` (`data-fija`, 2026-09-23). El `left` lo
   escribe el script —es la suma de lo que tienen delante, y eso cambia al arrastrar—; aqui va
   lo que no cambia. Fondo OPACO, o lo que corre por debajo se ve a traves; y el de la fila
   sobrevolada, o al pasar el raton la fila se parte en dos colores. El borde marca donde acaba
   lo quieto, lo mismo que en `.matriz-fija` — pero como SOMBRA y no como `border`: bajo
   `table-layout: fixed` un borde de 1,5px se come el ancho de la columna, y la ultima fija
   (la obra) salia cortada justo por eso. */
table.anchos .fija { position: sticky; z-index: 1; background: var(--surface); }
/* EL TÍTULO DE UNA COLUMNA QUIETA VA POR ENCIMA DE LOS DEMÁS TÍTULOS (2026-10-24, D4 de la prueba
   de usabilidad). Con la cabecera pegada (`data-sticky`), TODOS los `<th>` son `sticky` con
   `z-index: 3`; a igualdad de nivel pinta encima el que va después en el HTML, así que al
   desplazar a lo ancho «Concepto», «Importe»… pasaban por ENCIMA de «Pedido» y «Obra»: los
   títulos de las columnas quietas desaparecían mientras sus celdas seguían en su sitio. */
table.anchos thead .fija { z-index: 5; background: var(--surface-2); }
table.anchos tbody tr:hover .fija { background: var(--surface-2); }
table.anchos .fija-ultima { box-shadow: inset -1.5px 0 0 var(--border-fuerte); }

/* LA TABLA DE REMESAS, A 28px DE VERDAD (2026-09-23). Medida servida: 32px por fila, y 35 las
   que llevan el desplegable de semana. No era el padding —es el de todas, 5 + 17 + 5 + 1— sino lo
   que va DENTRO del renglon: el chip mide 19, el `.boton.mini` de los actos 20, el `<select>`
   24, y **la casilla de marcar trae 3px de margen del navegador arriba y abajo** — era ella sola
   la que dejaba en 32 las filas sin desplegable. Aqui se les quita el aire vertical, para que
   ninguno pase de los 17 del renglon. Medido despues: 28,5 en todas, que es 5 + 17,5 + 5 + 1.
   El `<select>` va `top` y no `middle`: centrado todavia asomaba medio pixel por debajo.

   SOLO EN ESTA TABLA, y es a proposito: el chip y el boton mini viven en toda la app, y bajarlos
   en todas es otra decision con otro barrido. Esta es la que Alex pidio.

   EL DESPLEGABLE SIN BORDE HASTA QUE SE TOCA, el trato de `.rejilla-captura`: en reposo la
   semana se LEE como un dato mas de la fila; al pasar por encima o con el foco se ve que se puede
   cambiar. El foco conserva su anillo (`tbody td:focus-within` deja de recortar). */
table[data-tabla-id="remesas-pago"] tbody td .chip,
table[data-tabla-id="remesas-pago"] tbody td .boton.mini {
  padding-top: 0; padding-bottom: 0; line-height: 15px; vertical-align: middle;
}
table[data-tabla-id="remesas-pago"] tbody td input[type=checkbox] {
  margin: 0; vertical-align: middle;
}
table[data-tabla-id="remesas-pago"] tbody td select {
  height: 17px; padding: 0 2px; line-height: 15px; vertical-align: top;
  border-color: transparent; background: transparent;
}
table[data-tabla-id="remesas-pago"] tbody tr:hover select,
table[data-tabla-id="remesas-pago"] tbody td select:focus { border-color: var(--border); }
/* «A PAGAR» (2026-10-23): el único campo que se teclea EN la fila del pool, sin doble clic. Mismo
   trato que el desplegable: en reposo se lee como la cifra de al lado —a la derecha, sin borde—,
   y al pasar o con el foco se ve que se cambia. 17px de alto para no mover la fila de 28. */
table[data-tabla-id="remesas-pago"] tbody td.celda-a-pagar input {
  /* `min-width` EN `ch`: `anchos.js` mide lo que la celda necesita, y un campo con `width: 100%`
     no pide nada; sin él la columna salía a la medida de su cabecera y «278,500.00» se cortaba. */
  width: 100%; min-width: 13ch; height: 17px; padding: 0 2px; line-height: 15px; vertical-align: top;
  text-align: right; font-variant-numeric: tabular-nums;
  border: 1px solid transparent; background: transparent;
}
table[data-tabla-id="remesas-pago"] tbody tr:hover td.celda-a-pagar input,
table[data-tabla-id="remesas-pago"] tbody td.celda-a-pagar input:focus {
  border-color: var(--border); background: var(--surface);
}

/* EL PLAN DE PAGOS DEL PEDIDO, UN PAGO POR RENGLON (2026-10-01). Alex: «en el plan de pagos,
   botones encimados». La regla de arriba sube los `<div>` de una celda a la misma linea, y en
   la columna «Sus pagos» cada `<div>` es UNA remesa con su enlace: un tramo con tres pagos los
   ponia en fila, la celda se estiraba al triple y empujaba la tabla fuera de su tarjeta. Aqui
   cada remesa baja a su renglon y la fila crece, que es lo que esta tabla necesita: pocos
   tramos y cada pago se tiene que poder pulsar. La clase la pone `compras_incurrido.html` en
   su `<table>`; el resto de la fila se alinea arriba para leerse junto al primer pago. */
table.plan-pagos tbody td { vertical-align: top; }
table.plan-pagos tbody td div { display: block; margin-left: 0; }

/* LA CELDA QUE SE ABRE CON DOBLE CLIC SE NOTA AL PASAR (2026-09-29). Alex: «estoy dando doble
   clic a las celdas y no me deja modificar datos»: nada decia cuales se abren y cuales no. El
   mismo trato que el desplegable de arriba —en reposo se LEE como un dato— y al pasar el cursor
   de celda y un filo de marca dentro de la propia celda. Borde por `box-shadow` y no `border`
   para no mover ni un pixel la fila de 28. Las cerradas ya dicen su porque en el `title`. */
table[data-tabla-id="remesas-pago"] tbody td[data-editable] { cursor: cell; }
table[data-tabla-id="remesas-pago"] tbody td[data-editable]:hover {
  background: var(--azul-bg);
  box-shadow: inset 0 0 0 1px var(--azul);
}

/* ---- LA MATRIZ HITOS x CERTIFICACIONES ----
   Una columna por certificación —`GR-LNV-078` tiene 39— así que la tabla scrollea en horizontal
   y la primera columna se queda quieta, igual que la de nombres de la rejilla del plan.

   `left: 0` NO basta sin `position: sticky` en el propio `<th>`/`<td>`: el sticky horizontal es
   de la CELDA, no de la columna. Y necesita fondo opaco, o las celdas de la derecha se le ven
   por debajo al scrollear. */
.matriz-fija { width: max-content; min-width: 100%; }
.matriz-fija .col-hito {
  position: sticky; left: 0; z-index: 2;
  background: var(--nivel-bg, var(--surface));
  min-width: 260px; max-width: 260px;
  /* El borde derecho marca dónde acaba el área fija: sin él, al scrollear parece que las
     columnas se meten por debajo del texto del hito. */
  border-right: 1.5px solid var(--border-fuerte);
}
.matriz-fija thead .col-hito { background: var(--surface-2); z-index: 4; }
.matriz-fija tfoot .col-hito { background: var(--surface-2); z-index: 4; }
/* La fila en rosa de un hito sobrecertificado tiene que llegar TAMBIEN a la columna fija, o el
   aviso se corta justo en la celda que dice de qué hito se habla. */
.matriz-fija tr.falta .col-hito { background: var(--rojo-bg); }
/* Sin salto de línea y con tipografía reducida: esta tabla está para escanear en horizontal. */
.matriz-fija td, .matriz-fija th { white-space: nowrap; font-size: var(--t1); }
.matriz-fija .col-hito { white-space: normal; }

/* SE LLAMABA `.matriz-cert` HASTA EL 2026-08-16, y el rename es el de `pcc.js` -> `arbol.js`:
   una clase que ahora montan DOS pantallas —la rejilla de un contrato y la producción semanal de
   una obra— no puede llamarse por el módulo de la primera. Lo que describe es la FORMA: un
   bloque de columnas quieto a la izquierda y un eje que corre a la derecha. */

/* ---- EL BLOQUE FIJO DE LA REJILLA DE UN CONTRATO (2026-08-11) ----
   La ficha de un contrato es ahora la rejilla entera —unidades de obra × certificaciones— y su
   parte izquierda es un formulario: código, concepto, unidad, cantidad, P.U. e importe. Seis
   columnas quietas, no una.

   POR ESO HACEN FALTA ANCHOS FIJOS EN PX. Un `left` acumulado necesita saber cuánto mide lo que
   tiene delante, y con anchos automáticos ese número no existe hasta después del layout: las
   columnas se solaparían al scrollear. Es la diferencia con `.col-hito`, que al ser la primera
   se apaña con `left: 0`.

   NO SE PUEDE USAR `table-layout: fixed` NI `anchos.js` sobre esta tabla: aquél reescribe el
   `<colgroup>` y pelearía con estos mismos anchos. Por eso la tabla no lleva `data-tabla-id`. */
.matriz-fija .fijo {
  position: sticky; z-index: 2;
  background: var(--nivel-bg, var(--surface));
}
.matriz-fija .f1 { left: 0;     width: 58px;  min-width: 58px; }
.matriz-fija .f2 { left: 58px;  width: 250px; min-width: 250px; white-space: normal; }
.matriz-fija .f3 { left: 308px; width: 66px;  min-width: 66px; }
.matriz-fija .f4 { left: 374px; width: 92px;  min-width: 92px; }
.matriz-fija .f5 { left: 466px; width: 104px; min-width: 104px; }
.matriz-fija .f6 {
  left: 570px; width: 118px; min-width: 118px;
  /* Igual que en `.col-hito`: sin este borde, al scrollear parece que las columnas de la
     derecha se meten por debajo del bloque fijo. */
  border-right: 1.5px solid var(--border-fuerte);
}
/* EL COMPARATIVO CIERRA SU BLOQUE EN LA CUARTA (2026-09-24): perdió P. objetivo y Total
   objetivo, y el borde de `.f6` no se puede mover porque contrato y producción siguen con seis. */
.matriz-fija .cierre-fijo { border-right: 1.5px solid var(--border-fuerte); }
.matriz-fija thead .fijo { background: var(--surface-2); z-index: 4; }
.matriz-fija tfoot .fijo { background: var(--surface-2); z-index: 4; }
/* El rosa del sobrecertificado llega a LAS SEIS, no solo a la primera: es la misma regla que
   `.col-hito`, aplicada a un bloque en vez de a una columna. */
.matriz-fija tr.falta .fijo { background: var(--rojo-bg); }
/* Los inputs del bloque fijo llenan su celda: el ancho lo manda la columna, no el control. */
.matriz-fija .fijo input { width: 100%; box-sizing: border-box; }

/* EN EL MÓVIL EL BLOQUE QUIETO SE SUELTA (2026-10-05, prueba de rotura 2). Mide 466 px el
   comparativo y 688 el contrato y la producción: con una pantalla de 375 el bloque fijo es MÁS
   ANCHO que la pantalla, y por mucho que se deslice la tabla no deja ver ni un precio —las
   columnas de datos pasan por debajo de él—. Por debajo del punto en que el menú se pliega la
   tabla es una sola hoja que desliza de lado, con sus columnas en su sitio: se pierde que la
   descripción acompañe, y se gana poder leer los precios, que es para lo que se abre. */
@media (max-width: 900px) {
  .matriz-fija .fijo, .matriz-fija .col-hito { position: static; }
}

/* EL RAIL DE NIVEL 2 CAIA ENCIMA DEL CODIGO. Mismo defecto que ya se arregló en
   `.tabla-produccion` y en `.rejilla-plan`, y por lo mismo: la primera columna de esta matriz
   mide 58 px y su texto arranca en el padding de la celda, así que el escalón de 13 px del
   componente de nivel dibujaba la barra morada dentro de los dígitos del código. Salió mirando
   `/produccion/<obra>/semanas`.

   AQUI EL RAIL SE PEGA AL BORDE Y NO SE TOCA LA SANGRIA, que es la diferencia con
   `.tabla-produccion`: con 58 px de columna, apartar el código 6 px lo cortaría por debajo de
   `.f2`, que es `sticky` y opaca —o sea que ni se vería que está cortado. El escalón lo siguen
   diciendo el grosor del rail (3 px el nivel 1, 2 px el 2) y el fondo de la fila. */
.matriz-fija tr[data-nivel="2"] { --rail-x: 3px; }

/* Donde acaban las certificaciones y empiezan los totales de la fila. */
.matriz-fija .sep { border-left: 1.5px solid var(--border-fuerte); }

/* La ✕ de quitar una fila. Va la última y fuera del bloque fijo: se usa una vez por fila al
   capturar, y robarle 40 px a los 688 del bloque quieto no lo vale. */
.matriz-fija .quitar { text-align: center; }
.matriz-fija .quitar .boton { padding: 0 7px; line-height: 1.6; }

/* UNA OFERTA PLEGADA (`comparativo.js`, 2026-10-04). El `nowrap` de arriba («esta tabla esta
   para escanear en horizontal») es justo lo que impedia que plegar una oferta bajara su ancho
   de verdad: colspan 1 pero el nombre del proveedor entero seguia pidiendo su renglon completo,
   y con tres ofertas la `propuesto` seguia fuera de 1.440 px aunque las otras dos se ocultaran.
   Plegada ya no hay precio que leer en esa columna, solo QUIEN es, asi que puede envolver. */
table.matriz-fija th.oferta-plegada { max-width: 108px; white-space: normal; vertical-align: top; }

/* ---- LA COLUMNA DE CAPTURA de un formulario de certificación ----
   Azul es el color del campo que se rellena (ver la cabecera de este archivo): separa
   visualmente lo que se introduce de lo que es referencia. */
td.celda-captura, th.celda-captura { background: var(--azul-bg); }
td.celda-captura input { width: 100%; }

/* ---- LA CABECERA SE QUEDA QUIETA en las tablas largas (`data-sticky`) ----
   Opt-in por atributo y no para todas: la mayoría de las tablas de la app caben en pantalla, y
   ahí una cabecera flotante es un adorno.

   ESTO ESTUVO ROTO DESDE QUE SE ESCRIBIO Y HASTA EL 2026-08-15, en las tres tablas que lo
   declaraban. El comentario que había aquí afirmaba que `top: 0` se resuelve «contra el VIEWPORT
   y no contra `.tabla-scroll`, que solo scrollea en horizontal», y es al revés: **`overflow-x:
   auto` NO deja el otro eje en `visible`** —el CSS lo computa a `auto`— así que el contenedor SI
   es el ancestro de scroll, y el `<th>` se pegaba a él, que nunca scrollea en vertical. Medido
   con el navegador: `position: sticky` y `top: -722px` con la página bajada 1.200.

   EL ARREGLO ES DARLE A ESA CAJA SU PROPIO SCROLL VERTICAL, y por eso la regla de abajo va
   emparejada con ésta: con `max-height` el contenedor scrollea de verdad y el `top: 0` se pega
   donde tiene que pegarse. Lo que se paga es que la tabla deja de scrollear con la página; en
   las 83 filas de `/pcc/<obra>` es justo lo que se quiere, y en una de cinco no se activa
   porque el contenido no llega a la altura.

   NO SE ARREGLA EN `.tabla-scroll` A SECAS: la comparten las 20+ tablas de la app, rejillas de
   captura y matriz de certificaciones incluidas, y darles a todas una caja con scroll propio
   cambiaría el comportamiento de la app entera de una vez. El `:has()` lo acota a las que lo
   piden — y el repo ya lo usa en `tr:has(> td .marca-borrar:checked)`.

   FONDO OPACO OBLIGATORIO: el `<th>` hereda el de la tarjeta, que es transparente, y las filas
   se le verían por debajo al pasar. Y `z-index` por encima de las celdas, que no lo declaran. */
.tabla-scroll:has(> table[data-sticky]) {
  max-height: 70vh;
  overflow-y: auto;
}
/* Y LA CABECERA PEGADA TAPA LA FILA A LA QUE SE SALTA, que es la objeción que este bloque ya
   tenía escrita antes de funcionar: `/pcc/<obra>` vuelve del detalle de una partida con
   `#p-<linea_id>`, y medido —`AT-LNV-109`, `CD-BESS.4.8-014`— la fila quedaba VISIBLE pero por
   debajo del `<th>`. `scroll-margin-top` es la respuesta del CSS a eso y vale para los dos
   caminos: el salto nativo del `#hash` y el `scrollIntoView` de `plegar.js`. 40px sobre una
   cabecera de 33 dejan la fila despegada y no pegada al borde. */
.tabla-scroll:has(> table[data-sticky]) tbody tr { scroll-margin-top: 40px; }
table[data-sticky] thead th {
  position: sticky; top: 0; z-index: 3;
  /* `--surface-2` Y NO `--surface` DESDE EL 2026-08-25: la cabecera lleva fondo propio en TODAS
     las tablas, así que la pegada tiene que ser la misma o al pegarse cambiaría de color. Sigue
     siendo OPACO, que es la razón original de esta línea: el `<th>` hereda el de la tarjeta,
     que es transparente, y las filas se le verían por debajo al pasar. */
  background: var(--surface-2);
}
/* Las líneas de la cabecera son BORDES DE CELDA, y bajo `border-collapse: collapse` los pinta la
   TABLA y no la celda: no viajan con el elemento pegado, se quedan dibujadas donde estaba la
   tabla. Se repintan con sombras, que sí viajan. Son DOS desde el 2026-08-25 —la de abajo y la
   de columna— y olvidar la segunda deja la cabecera pegada sin rejilla justo cuando se scrollea,
   que es cuando más falta hace para no perder la columna. */
table[data-sticky] thead th {
  box-shadow: inset 0 -1.5px 0 var(--border-fuerte), inset -1px 0 0 var(--border-suave);
  border-bottom: none; border-right: none;
}
table[data-sticky] thead th:last-child { box-shadow: inset 0 -1.5px 0 var(--border-fuerte); }

/* Un código de obra son tres tokens unidos por guiones, y el guion ES un salto de línea
   válido: en una columna estrecha el navegador apila `AC-` / `LNV-` / `126`, que es como se
   veía el índice de /pcc/ hasta el 2026-08-07. Es una identidad, no un texto que se pueda
   romper. El `overflow-wrap: normal` anula el `anywhere` de arriba, que existe para los
   `pedido` y los `ID Único` de /explorar —esos sí deben poder partirse—. */
td.codigo { white-space: nowrap; overflow-wrap: normal; }

/* Una marca de tiempo es lo mismo que un código: `11/08 14:48` lleva un espacio, y el espacio ES
   un salto de línea válido, así que en una columna estrecha el navegador apila `11/08` sobre
   `14:48` y la fila mide el doble de alto. Salió mirando `/auditoria` con las dos columnas de
   JSON encendidas, que es cuando la primera columna se comprime. */
td.fechahora { white-space: nowrap; }

/* ---------------- EL NIVEL DE UNA MATRIZ QUE SE EXPANDE ----------------
   `data-nivel="1|2|3"` en la fila, o en el contenedor plegable, y ya está. Lo llevan las
   matrices con jerarquía de verdad —el PCC de una obra y el detalle de su partida, la rejilla
   del plan y su programación, los pedidos, el árbol de la oferta y las cuatro pantallas de
   producción (catálogo, obra, reportar y semanas)— y lo hereda cualquiera que venga después.
   Es UN componente y no una copia por pantalla, por la misma razón que `_tabla_obras.html` es
   un macro: varias escalas de color separadas divergen en la primera semana que alguien toque
   una.

   UNA LISTA PLANA NO LO LLEVA. Salió el 2026-08-24 en `/estimaciones/contrato/<id>` y en
   `/estimaciones/<id>/certificar`: sus unidades de obra iban TODAS marcadas `data-nivel="3"`
   sin que hubiera un nivel 1 ni un 2 encima, o sea sin jerarquía ninguna, y el escalón de
   28 px del nivel 3 acabó dibujando la barra morada encima del `H-001`. El atributo no
   significa «fila de tabla»: significa DÓNDE ESTÁS dentro de algo que se expande. Sin eso,
   fuera —y quitarlo no cambia el fondo, porque el nivel 3 ya resuelve `var(--surface)`.

   POR QUE UNA HUE PROPIA Y NO UNA DE LAS CUATRO. Azul, verde, ámbar y rojo ya significan algo
   —lo declara la cabecera de este archivo— y la fila de una partida que se pasó de su Coste
   Objetivo YA se pinta con `.falta`. Un nivel pintado de rojo o de ámbar apagaría justo esa
   señal. `--jerarquia-*` son tres intensidades de UNA hue sin estado detrás.

   EL RAIL VA EN UN `::before` Y EL FONDO EN EL `<tr>`, y están separados a propósito: el fondo
   lo pisan `.falta` (rojo) y `.destacada` (el azul de «volver al PCC»), que son las filas que
   más se miran, y con el nivel metido ahí desaparecería exactamente ahí. Así conviven: el
   fondo dice el ESTADO y la barra izquierda dice el NIVEL.

   El fondo va en el `<tr>` y NUNCA en el `<td>`: un fondo de celda gana siempre al de su fila,
   así que con él puesto ni `.falta` ni el `:hover` podrían pintar nada.

   `--rail-x` es el escalón, y cada matriz lo ajusta a SU sangría: el PCC sangra 10/22/40 px y
   la rejilla del plan 10/12/16, así que ahí abajo se reescribe. */
[data-nivel="1"] { --rail: var(--jerarquia-1); --rail-w: 3px; --rail-x: 2px;  --nivel-bg: var(--surface-2); }
[data-nivel="2"] { --rail: var(--jerarquia-2); --rail-w: 2px; --rail-x: 13px; --nivel-bg: var(--nivel-2-bg); }
/* El nivel 3 vuelve a `--surface`, que es el fondo de la tarjeta que lo contiene: la escala
   TERMINA en limpio. No es `transparent` porque la primera columna de la rejilla del plan es
   `sticky` y hereda este fondo — transparente, las 40 semanas le pasarían por debajo. */
[data-nivel="3"] { --rail: var(--jerarquia-3); --rail-w: 2px; --rail-x: 28px; --nivel-bg: var(--surface); }

tr[data-nivel] { background: var(--nivel-bg); }
/* El `background` de arriba le gana a `tbody tr:hover` —misma especificidad y va después— así
   que el hover hay que devolverlo, o las filas de nivel dejarían de responder al ratón. */
tr[data-nivel]:hover { background: var(--surface-2); }
/* La especificidad de estas dos se deja BAJA a propósito (0,2,0): el `.col-nombre` de la
   rejilla del plan es `position: sticky` y tiene que poder ganar. Sticky ya es contenedor de
   lo absoluto, así que ahí el rail se dibuja igual sin el `relative`. */
[data-nivel] > :first-child { position: relative; }
[data-nivel] > :first-child::before {
  content: ''; position: absolute; top: 0; bottom: 0;
  left: var(--rail-x); width: var(--rail-w); background: var(--rail);
}
/* Un contenedor plegable no es una fila: ahí el rail es el borde, y así llega hasta el pie del
   contenido abierto en vez de morir en la cabecera. Se anula el de su `<summary>` —con la
   especificidad justa para ganarle a la regla de arriba— o habría dos. */
details[data-nivel] { border-left: var(--rail-w) solid var(--rail); padding-left: var(--rail-x); }
details[data-nivel] > summary:first-child::before { content: none; }

/* ---- los tres niveles del PCC de una obra ----
   Bloque -> código -> partida en UNA tabla. La jerarquía la dicen el rail, la sangría y el
   peso, no un plegable: la pantalla existe para leer las partidas. */
tr.fila-bloque td {
  font-weight: 600;
  border-top: 1.5px solid var(--border-fuerte); border-bottom: none;
}
tr.fila-codigo td { font-weight: 600; border-bottom: none; padding-top: 9px; }
tr.fila-codigo td:first-child { padding-left: 22px; }
/* La partida es lo más hondo y lo más numeroso: va tenue y sin línea, para que las de un
   mismo código se lean como un bloque y no como 92 filas sueltas. */
tr.fila-partida td { border-bottom: none; padding-top: 3px; padding-bottom: 3px; }
tr.fila-partida td:first-child { padding-left: 40px; }
tr.fila-partida:hover td { background: var(--surface-2); }

/* Plegar los dos niveles de arriba (`pcc.js`). La clase `.plegable` la pone el SCRIPT, así
   que sin JavaScript la flecha ni se ve y las 118 filas siguen todas visibles: lo que se
   pierde es el atajo, no la pantalla. `visibility` y no `display` para que activarlo no mueva
   ni un píxel. */
tr.fila-bloque .flecha-plegar, tr.fila-codigo .flecha-plegar {
  visibility: hidden; margin-right: 5px;
}
tr.plegable .flecha-plegar { visibility: visible; }
tr.plegable { cursor: pointer; }
tr.plegable:hover td { background: var(--surface-2); }
tr.plegada { display: none; }
/* El pie: los bloques otra vez y el TOTAL. El TOTAL cierra con la línea gruesa. */
tfoot tr.fila-bloque-total td { font-weight: 600; border-top: none; }
tfoot tr.fila-bloque-total:first-child td { border-top: 1.5px solid var(--border-fuerte); }
tfoot tr.fila-total td {
  border-top: 1.5px solid var(--border-fuerte); font-size: 14px;
}
/* LOS TRAMOS DEL INCURRIDO entre los bloques y el TOTAL de /pcc/<obra> (2026-09-27): lo imputado,
   lo que está fuera del PCC y lo que no tiene partida, para que el TOTAL cuadre a la vista. */
tfoot tr.fila-tramo td { font-weight: 600; border-top: none; }
tfoot tr.fila-bloque-total + tr.fila-tramo td { border-top: 1.5px solid var(--border-fuerte); }
/* Lo que todavía no tiene partida: es trabajo pendiente, y va en ámbar como texto plano. */
.por-imputar { color: var(--ambar); }

/* ---- las secciones y los totales del cierre semanal ----
   `cierre_obra.html` pinta las cuatro secciones de `EJEMPLO CIERRE.xlsx` en UNA tabla, con un
   <tbody> por seccion. Estas dos clases son las filas que NO son arbol: no llevan `plegable`,
   asi que `arbol.js` no las toca y no se pueden esconder — un total que se pliega no es un
   total.

   VAN SOBRE `th` Y NO SOBRE `td` porque son cabeceras de fila de verdad: un lector de pantalla
   tiene que poder decir «TOTAL CERTIFICACION» al leer su importe. Por eso tampoco hace falta
   `font-weight`: un <th> ya viene en negrita.

   NO SE REUSA `tfoot tr.fila-total`, que esta a cuatro lineas de aqui: aquella vive en el pie
   de la tabla del PCC y aqui hay CINCO totales en mitad del <tbody>. Comparten nombre porque
   son la misma idea; lo que no comparten es el sitio, y `tfoot` acota aquella regla. */
tbody tr.fila-seccion th {
  border-top: 2px solid var(--border-fuerte);
  padding-top: 16px; font-size: 14px;
}
/* La primera seccion no lleva raya arriba: la cabecera de la tabla ya cierra por ese lado. */
tbody:first-of-type tr.fila-seccion:first-child th { border-top: none; padding-top: 9px; }
tbody tr.fila-total th {
  border-top: 1.5px solid var(--border-fuerte);
  background: var(--surface-2);
}
/* EL ROTULO VA A LA IZQUIERDA Y CON EL HUECO DE UNA CELDA (2026-09-29). Un <th> dentro de
   <tbody> no casaba con `thead th` ni con `tbody td`, así que tomaba el centrado y el padding
   del navegador: en la hoja de `/cierre/` «1. Certificación» y «Total producción» salían
   centrados y sin aire, desalineados de las filas que resumen. `:not(.num)` deja los importes
   a la derecha, que es lo que ya dice `th.num`. */
tbody tr:is(.fila-seccion, .fila-total) th { padding-left: var(--celda-x); padding-right: var(--celda-x); }
tbody tr:is(.fila-seccion, .fila-total) th:not(.num) { text-align: left; }
/* EL NEGATIVO DE UN TOTAL, en rojo como el de una celda: solo existía `td.num.malo`, así que el
   «Resultado final» negativo de la hoja salía del color del texto — con signo, pero sin la señal. */
th.num.malo { color: var(--rojo); }
/* LA HOJA DE `/cierre/`, SOLA (`table.hoja-cierre`, 2026-10-12). `.fila-total` vive también en el
   `<tbody>` de `/pcc/<obra>/<linea>`, así que lo de aquí va acotado y no la toca.
   - EL TOTAL CON EL ALTO DE UNA FILA: un <th> no casa con `tbody td`, así que llevaba el 1px del
     navegador arriba y abajo, y los tres totales de «4. Resultado» salían apilados sin aire.
   - EL RESULTADO FINAL, como el renglón de abajo de una hoja contable: más grande y doble raya.
     Se imprime en blanco y negro, así que la señal es la raya y no un color.
   - EL ESTATUS DE UN CONTRATO no es un subtotal: va sin la negrita de `.fila-codigo`. */
.hoja-cierre tbody tr.fila-total th { padding-top: var(--celda-y); padding-bottom: var(--celda-y); }
.hoja-cierre tbody tr.fila-resultado th {
  padding-top: var(--e2); padding-bottom: var(--e2); font-size: var(--t3);
  border-bottom: 3px double var(--border-fuerte);
}
.hoja-cierre tr.fila-estatus td { font-weight: 400; }
/* EN EL MOVIL CABEN LAS TRES COLUMNAS: el concepto, sin cortar, ocupaba más de media pantalla y
   «A origen» quedaba fuera, detrás del scroll. Aquí el concepto SÍ baja de línea —cortarlo
   escondería el dato, y esta tabla no monta `anchos.js` para decirlo en un `title`—; los
   importes no, que se leen por la coma. */
@media (max-width: 640px) {
  .hoja-cierre { font-size: var(--t1); }
  .hoja-cierre tbody td:first-child { white-space: normal; }
  .hoja-cierre tbody td a { white-space: nowrap; }
  .hoja-cierre :is(thead th, tbody td, tbody th) { padding-left: var(--e1); padding-right: var(--e1); }
  .hoja-cierre tr.fila-codigo td:first-child { padding-left: 14px; }
}
/* EL ACTO DEBAJO DE SU LISTA O DE SU NOTA lleva aire: la tarjeta de estado de `/cierre/` pegaba
   el botón de cerrar a la última línea de texto. */
:is(.nota, .checklist) + .fila-form { margin-top: var(--e3); }

/* ---- el pedido y sus partidas (compras.js) ----
   Un <tbody> por pedido, y sus partidas colgando. No es un <details>: uno no puede envolver
   un <tr> —el parser lo expulsa fuera de la tabla— y esta tabla ya trae orden del servidor,
   paginación y selector de columnas. */
tbody.pedido + tbody.pedido .fila-pedido td { border-top: 1px solid var(--border); }
.fila-pedido td { vertical-align: middle; }
tbody.pedido.plegable .fila-pedido { cursor: pointer; }
tbody.pedido.plegable .fila-pedido:hover td { background: var(--surface-2); }
/* LA FLECHA ERA UN GLIFO `▸` (U+25B8) HASTA EL 2026-08-25, y era el que la cabecera del
   set de iconos prohibe con nombre y apellido: no esta en Fira Sans, asi que caia a la fuente
   del sistema y se leia como un guion. Ahora es el mismo `plegar()` que el arbol del PCC, y
   esta clase se queda solo como envoltorio. */
.flecha-pedido {
  display: inline-flex; align-items: center; margin-right: 5px; color: var(--texto-tenue);
}
tbody.pedido.plegado .fila-partida,
tbody.pedido.plegado .fila-pago { display: none; }
/* LOS PAGOS DE UN PEDIDO EN /remesas-pago/ Y EN LA HOJA DE LA SEMANA (C5, 2026-10-14): debajo de
   la fila del pedido, sangrados y más bajos. Los pliega `compras.js` (`hidden`). */
tr.fila-dentro td { padding-top: 3px; padding-bottom: 3px; border-bottom: none; }
tr.fila-dentro td:nth-child(2) { padding-left: 22px; }
tr.fila-ped .plegar-pedido { margin-right: 4px; }
.plegar-pedido .icono-contraer { display: none; }
.plegar-pedido[aria-expanded="true"] .icono-desplegar { display: none; }
.plegar-pedido[aria-expanded="true"] .icono-contraer { display: inline-block; }
.fila-partida td { border-bottom: none; padding-top: 3px; padding-bottom: 3px; }
tbody.pedido .fila-partida:last-child td { padding-bottom: 9px; }

/* ---- el pago dentro de su pedido, en /pcc/<obra>/<linea_id> ----
   NO REUSA `.fila-partida`, y no es por gusto: ahi «partida» es el renglon de un pedido y aqui
   es la unidad de obra del PCC —el sujeto de la pantalla entera—, asi que la misma clase diria
   dos cosas contrarias a dos pantallas de distancia. Ademas aquella trae su sangria de 40 px,
   que aqui sobra porque la primera celda ya va vacia. */
.fila-pago td { border-bottom: none; padding-top: 3px; padding-bottom: 3px; }
tbody.pedido .fila-pago:last-child td { padding-bottom: 9px; }
/* Las cinco piezas de un pago en una linea. `minmax(0,…)` en las dos de texto o una descripcion
   larga empuja el importe fuera de la celda en vez de partirse. */
.renglon-pago {
  display: grid; gap: 10px; align-items: baseline;
  grid-template-columns: 44px minmax(0, 1fr) minmax(0, 1.4fr) 130px 120px;
}
.renglon-pago .proveedor, .renglon-pago .descripcion { overflow-wrap: anywhere; }
.renglon-pago .num { text-align: right; font-variant-numeric: tabular-nums; }
@media (max-width: 900px) { .renglon-pago { grid-template-columns: 1fr; gap: 2px; } }
/* Las seis piezas de un renglón en una línea. `minmax(0,…)` en la descripción o un texto
   largo empuja a los importes fuera de la celda en vez de partirse. */
.renglon-partida {
  display: grid; gap: 10px; align-items: baseline;
  grid-template-columns: minmax(0, 1fr) 110px 90px 90px 110px 120px;
}
.renglon-partida .descripcion { overflow-wrap: anywhere; }
.renglon-partida .num { text-align: right; font-variant-numeric: tabular-nums; }
@media (max-width: 900px) { .renglon-partida { grid-template-columns: 1fr; gap: 2px; } }

.tenue { color: var(--texto-tenue); }
.suave { color: var(--texto-suave); }
.pequeno { font-size: var(--t1); }
/* EL CAMPO QUE EL SERVIDOR GUARDA EN MAYÚSCULAS se ve ya en mayúsculas al teclear (2026-09-25).
   Sustituye a la pista «Se guarda en mayúsculas»: la regla la cumple el campo, no un texto. */
.mayus { text-transform: uppercase; }

/* ---------------- formularios de ficha ----------------
   Un formulario de verdad: la etiqueta ENCIMA del campo. `.fila-form` sirve para una accion
   de una linea (buscar, un nombre y un boton); para seis campos con el `placeholder` por
   toda pista, no — y eso era la mitad de lo que hacia ilegible la ficha de obra.
   Existe por la misma razon que `.nota` y `.fila-form`: el mismo `style=` copiado. */
.campos {
  display: grid; gap: var(--e2);
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
}
/* Y `compacta` PARA UNA FICHA DE DIEZ CAMPOS, hermana de `.rejilla.compacta`: la misma
   rejilla con la columna mas estrecha y el hueco vertical del tamano del horizontal. Se
   estreno en «Datos de la Oferta» —diez campos que ocupaban tres filas— y no cambia ni un
   campo de sitio: solo caben mas por fila. */
.campos.compacta { gap: var(--e1) var(--e2); grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); }
.campo label {
  display: block; font-size: var(--t1); color: var(--texto-suave);
  margin-bottom: 4px; font-weight: 600;
}
/* EL ASTERISCO DE LO OBLIGATORIO (2026-10-13). UNA sola regla para toda la app, y NO se escribe
   en ninguna plantilla: el rótulo se marca solo en cuanto su control lleva `required`. Cuelga del
   control y no de una clase que alguien tenga que acordarse de poner, así que un campo nuevo
   con `required` sale marcado sin tocar nada, y uno al que un script le pone o le quita el
   `required` (`campo.required = …`) cambia al instante.
   - `content: " *" / ""`: el texto alternativo VACÍO deja el asterisco fuera del lector de
     pantalla, que ya dice «obligatorio» por el `required`. La línea de antes es para el
     navegador que no entiende la barra.
   - `:required:not(:disabled)`: un control apagado no se rellena, así que no se marca.
   - Las seis formas de rótulo que hay: (1) `.campo > label`, que es casi todo; (2) la casilla o
     el radio que lleva su texto DENTRO del `<label>`; (3) un `<label>` pegado ANTES del control,
     sin `.campo` (el acceso, los diálogos); (4) la zona de soltar que está sola, sin rótulo al
     lado; (5) el botón «Elegir archivo» que sustituye al input nativo; (6) la `<th>` de una
     columna cuyas celdas se rellenan, que se marca a mano con `class="obligatorio"` porque CSS
     no sabe subir de una celda a su columna. UN `<label>` QUE ENVUELVE A UNA CAJA DE TEXTO NO
     ESTÁ: el asterisco caería detrás de la caja. Se escribe como `.campo`.
     Lo que no encaje lo canta `navegacion.py` (clave `sinmarca`).
   - El rojo es `--rojo`, el de los errores y los botones de peligro: aquí avisa de lo que falta. */
:is(
  .campo:has(:required:not(:disabled)) > label:not(.zona-soltar),
  label:has(> :is([type=checkbox], [type=radio]):required:not(:disabled)),
  label:has(+ :required:not(:disabled)),
  .zona-soltar:not(.con-archivo):has(:required:not(:disabled)):not(.campo > .zona-soltar) .elegido,
  :required:not(:disabled) + .archivo-propio > label,
  th.obligatorio
)::after {
  content: " *"; content: " *" / "";
  color: var(--rojo); font-weight: 700;
}
/* LA LEYENDA «* obligatorio» (`static/obligatorio.js`): una por formulario, tenue y pequeña, y
   NO es un chip. Ocupa toda la fila allí donde cae: en la rejilla de `.campos`, en una fila de
   `.fila-form` y encima de una tabla. */
.leyenda-obligatorio {
  grid-column: 1 / -1; flex: 0 0 100%; order: -1;
  margin: 0 0 var(--e1); font-size: 11.5px; line-height: 1.35; color: var(--texto-tenue);
}
.leyenda-obligatorio b { color: var(--rojo); font-weight: 700; }
@media print { .leyenda-obligatorio { display: none; } }

/* Sin `type` escrito un `<input>` es de texto, así que el ancho completo es lo que se
   quiere. Casilla y radio se excluyen: uno a 100% de ancho es una raya. */
.campo input:where(:not([type=checkbox]):not([type=radio])),
.campo select, .campo textarea { width: 100%; }
/* La pista dice QUE hacer o de donde sale el dato. El porque largo va al codigo. */
.campo .pista { font-size: 11.5px; color: var(--texto-tenue); margin-top: 4px; line-height: 1.35; }
.campo .pista code { font-size: var(--t0); }
/* EL ERROR DEBAJO DE SU CAMPO (MASTER.md §5), también dentro de una celda de rejilla: la fila
   va marcada `.falta` y el campo con `aria-invalid`, y la frase dice causa y arreglo. */
.error-campo { font-size: 11.5px; color: var(--rojo); margin-top: 4px; line-height: 1.35;
  white-space: normal; }
[aria-invalid="true"] { border-color: var(--rojo); }

/* EL MENU DE COLUMNAS (`columnas.js`). Era tambien el del embudo de `tablas.js` hasta el
   2026-09-28; el embudo usa ya `.menu-filtro`, el mismo que el del servidor. */
.popover {
  position: absolute; z-index: 40; top: 100%; left: 0;
  background: var(--surface); border: 1px solid var(--border-fuerte);
  border-radius: var(--radio-s); box-shadow: var(--sombra);
  padding: 8px; min-width: 190px; max-height: 300px; overflow-y: auto;
  text-transform: none; letter-spacing: 0; font-weight: 400;
}
.popover label { display: block; padding: 3px 4px; cursor: pointer; font-size: 12.5px; color: var(--texto); }
.popover label:hover { background: var(--surface-2); }

/* agarre de anchos.js. Va a caballo del borde entre dos columnas —como en cualquier hoja de
   cálculo— y por eso `right:-4px`. Al último <th> el script le pone `right:0`, o el agarre
   asomaría fuera de `.tabla-scroll` y crearía 4px de scroll de la nada. */
th .agarre {
  position: absolute; top: 0; bottom: 0; right: -4px; width: 9px; z-index: 5;
  cursor: col-resize; touch-action: none; user-select: none;
}
th .agarre:hover, th .agarre:focus-visible { background: var(--azul); opacity: .35; }
th .agarre:focus-visible { outline: 2px solid var(--azul); outline-offset: 0; }
/* Con el dedo, 9px no se acierta. */
@media (pointer: coarse) { th .agarre { width: 24px; right: -12px; } }
/* Mientras se arrastra, el cursor es de redimensionar en TODA la página y no se selecciona
   texto: sin esto, arrastrar pinta de azul media cabecera. */
body.redimensionando { cursor: col-resize; user-select: none; }

/* ---------------- barra de progreso ---------------- */

.barra { height: 7px; background: var(--surface-2); border-radius: 999px; overflow: hidden; }
.barra > span { display: block; height: 100%; background: var(--azul); border-radius: 999px; }

/* ---------------- formularios y botones ---------------- */

/* `input[type=text]` NO casa con un `<input>` sin atributo `type`: un selector de atributo
   mira el atributo escrito, no el default del IDL. La mitad de los campos de la app se
   escriben `<input name="descripcion">` a secas, así que se pintaban con el control nativo
   del navegador —blanco, cuadrado, con otra tipografía— al lado de `<select>` sí estilizados.
   Eso era lo que se veía roto, y no era el HTML de una pantalla concreta.

   El `:where()` es obligatorio: no suma especificidad, así que esto vale (0,0,1) y
   `td.celda-tasa input` (0,1,2) le sigue ganando el ancho y el padding. Con `:not()` a pelo
   serían (0,4,1) y este bloque pisaría todos los ajustes por sitio. */
input:where(:not([type=file]):not([type=checkbox]):not([type=radio]):not([type=submit]):not([type=button])),
select, textarea {
  padding: 7px 10px; border: 1px solid var(--border-fuerte);
  border-radius: var(--radio-s); background: var(--surface); color: var(--texto);
  font-family: inherit; font-size: var(--t2);
}
input:focus, select:focus, button:focus-visible, a:focus-visible {
  outline: 2px solid var(--azul); outline-offset: 1px;
}
.boton {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 7px 13px; border-radius: var(--radio-s);
  border: 1px solid var(--border-fuerte); background: var(--surface); color: var(--texto);
  font-family: inherit; font-size: var(--t2); font-weight: 500; cursor: pointer;
}
/* Un formulario que vive dentro de una celda o al pie de una tarjeta. */
.fila-form { display: flex; gap: var(--e1); align-items: center; flex-wrap: wrap; }
/* EL PIE QUE CIERRA UN GRUPO: la condicion a la izquierda, la accion a la derecha. `.fila-form`
   ya da el flex y el hueco —el mismo razonamiento que `dialog .acciones` escribio mas abajo—,
   asi que esto solo los separa. Existe porque no lo hacia nadie: `.linea-total` es un pie de
   tarjeta de verdad pero solo lleva cifras, y `dialog .acciones` esta calificado a `<dialog>`.
   LA CONDICION VA DELANTE PORQUE BLOQUEA: se lee antes de decidir. La consecuencia que solo
   AVISA va detras del boton, que es el orden que ya tenian `remesa_ficha` y `comparativo`. */
.fila-form.cierre { justify-content: space-between; }
/* LA ETIQUETA ENCIMA Y EL BOTÓN AL LADO: EL BOTÓN VA A RAS DEL CAMPO (2026-10-08). Alex, con el
   plan del mes: «están desalineados (octubre) (ver)». `.fila-form` centra en vertical, y con un
   `.campo` dentro lo que se centra es la CAJA ENTERA —etiqueta más campo—, así que el botón
   quedaba a media altura de la etiqueta, unos 10 px por encima del campo. Con un `.campo` en la
   fila se alinea al borde de abajo, que es donde está el campo.
   CON UNA PISTA DEBAJO DEL CAMPO el borde de abajo es el de la pista y no sirve: la fila se alinea
   arriba y lo que no es campo baja lo que mide la etiqueta (su renglón, `--t1` a 1.5, más los
   4 px de `.campo label`). Así los campos quedan a una altura y el botón con ellos. */
.fila-form:has(> .campo) { align-items: flex-end; }
.fila-form:has(> .campo > .pista) { align-items: flex-start; }
.fila-form:has(> .campo > .pista) > :not(.campo) { margin-top: calc(var(--t1) * 1.5 + 4px); }
/* En el móvil la fila se parte y ese margen dejaría un hueco encima del botón que baja solo a
   su renglón: ahí manda el borde de abajo, como sin pista. */
@media (max-width: 600px) {
  .fila-form:has(> .campo > .pista) { align-items: flex-end; }
  .fila-form:has(> .campo > .pista) > :not(.campo) { margin-top: 0; }
}
/* Y CAMPO Y BOTÓN MIDEN LO MISMO. Con el mismo padding, el `<select>` sale 2 px más alto que el
   `<input>` y que el botón (el navegador le reserva más renglón), y al lado se nota: 33 contra
   31. Se igualan con un MÍNIMO y no con un alto fijo: un campo con letra mayor —los de 16 px de
   la recepción en el móvil— tiene que poder crecer. Fuera de las celdas: ahí `tbody td select`
   los baja a propósito (más abajo) y un alto mínimo los volvería a engordar. */
:root { --alto-control: 33px; }
:is(.campo, .fila-form:has(> .campo)):not(td *)
  :is(input:where(:not([type=file]):not([type=checkbox]):not([type=radio]):not([type=submit]):not([type=button]):not([type=hidden])),
      select:not([multiple]):not([size])),
.fila-form:has(> .campo) > :is(.boton, button):not(.mini),
.campos > :is(.boton, button) { min-height: var(--alto-control); }
/* El mínimo no basta con un `<select class="mono">`: su renglón `normal` en la monoespaciada es
   más alto que en la Fira y lo pasaba, 34,5 contra 33. El navegador no deja fijar el renglón de un
   `<select>` (ignora `line-height`), así que cede 1 px de relleno arriba y abajo y el mínimo manda. */
.campo select.mono:not([multiple]):not([size]):not(td *) { padding-block: 6px; }
/* Un ENLACE con cara de botón hereda el renglón del cuerpo (21 px) y un `<button>` no (el
   navegador le pone `normal`): «Volver» salía 5 px más alto que «Guardar» a su lado. En estas
   filas, el del botón para los dos. */
.fila-form:has(> .campo) > a.boton:not(.mini),
.campos > a.boton { line-height: normal; }
/* Y TODO BOTÓN MIDE LO QUE UN CAMPO, EN CUALQUIER FILA (2026-10-09). Las dos reglas de arriba lo
   arreglaban fila por fila, y una auditoría de todas las pantallas encontró 13 donde no llegaban:
   un `<a class="boton">` a 35,5 junto a un `<button>` a 31 en las cabeceras y los pies de
   formulario, un botón a 31 junto a un campo a 33 cuando va dentro de su `.campo`, el «+» que
   solo lleva icono a 29, y la cabecera de la oferta escalonada 2 px porque el candado mueve la
   línea de apoyo de un `inline-flex`. Renglón del botón para todos, el alto de un campo como
   mínimo y centrado con sus vecinos.
   FUERA DE LAS CELDAS, por lo mismo que los campos: una fila de tabla no crece por su botón.
   `:not(td *)` lleva un selector COMPLEJO dentro, que es de Selectors 4 (Chromium 88, Firefox 84,
   Safari 9); el bloque de los campos de arriba ya lo usa, y se comprobó en el navegador sin
   ventana que deja los botones de `td` como estaban. (El «Tema» del menú se excluía por su `id`, porque era pequeño a propósito; desde el
   2026-10-27 es un icono y ya no es un `.boton`.)
   `.mini` sigue fuera, y en `th`, `tfoot` y `.barra-tabla-fin` no hay hoy ningún `.boton` que
   no sea `.mini`: medido abriendo cada `<dialog>` y cada `<details>`. */
.boton:not(.mini):not(td *) {
  line-height: normal; min-height: var(--alto-control); vertical-align: middle;
}
/* EL BOTÓN SUELTO DENTRO DE LA REJILLA DE CAMPOS no se estira: es una celda más de `.campos` y el
   `stretch` de la rejilla lo hacía tan alto como la celda —etiqueta y campo, 53 px— y tan ancho
   como la columna. Va abajo, a ras de los campos de su fila, y de su tamaño. */
.campos > :is(.boton, button) { align-self: end; justify-self: start; }
/* UN FILTRO ENCIMA DE SU TARJETA lleva aire: el «Mes» del plan tocaba el borde de la tarjeta. */
.fila-form + .tarjeta { margin-top: var(--e4); }
/* LA TARJETA DE ACCIONES DE UNA FICHA, cuando lleva varios actos (2026-10-01). Cada acto es su
   propio `<form>` —uno anidado no existe— y apilados a pelo salian PEGADOS: en la solicitud, los
   «Firmar como…» del admin se pisaban un pixel y el formulario de costos tocaba la fila de
   salidas. Alex: «botones de acción encimados». El hueco va en el CONTENEDOR y no en cada form,
   asi que un acto que no se pinta no deja aire de mas; un `<dialog>` cerrado o modal no ocupa
   sitio en el flujo y tampoco suma. */
.pila-actos { display: flex; flex-direction: column; gap: var(--e3); }
.pila-actos > .nota { margin: 0; }

/* ================= LOS PAPELES DE UNA CERTIFICACIÓN (2026-10-09), `estimaciones_certificar.html`

   Tres columnas —el papel, cómo está y su botón— y no una `.fila-form` por papel: el nombre más
   largo, «Estimación de cobro firmada por el cliente», no cabía en el `min-width` que medía cada
   fila, y su botón caía a la derecha del de la factura. Con la rejilla la primera columna mide
   el más largo y los botones arrancan en la misma vertical. Cada fila es `display: contents`:
   sus tres hijos son celdas de la rejilla. */
.papeles-cert { display: grid; grid-template-columns: max-content max-content auto;
  gap: var(--e2) var(--e4); align-items: center; margin-top: var(--e2); }
.papel-cert { display: contents; }
.papel-cert > :last-child { justify-self: start; }
/* La ventana sin JavaScript se ve en la hoja (`open`): a lo ancho, debajo de su papel. */
.papeles-cert > dialog { grid-column: 1 / -1; }
/* En el móvil el nombre ocupa su renglón, y debajo el estado —entero, sin partirse— y el botón,
   que es el que se parte si no cabe. */
@media (max-width: 600px) {
  .papeles-cert { grid-template-columns: max-content minmax(0, 1fr); gap: 4px var(--e2); }
  .papel-cert > b { grid-column: 1 / -1; margin-top: var(--e2); }
  .papel-cert > :nth-child(2) { white-space: nowrap; }
}

.boton:hover { background: var(--surface-2); text-decoration: none; }
/* EL BOTON SE HUNDE AL PULSARLO: la señal de que el clic llego, antes de que conteste el
   servidor. 0,98 y no menos porque se pulsa decenas de veces al dia —se tiene que notar sin
   verse—, y 100ms para que el dedo no espere al boton. El desactivado no se mueve: no hace nada. */
.boton { transition: transform 100ms var(--ease-out); }
.boton:active:not(:disabled):not([aria-disabled="true"]) { transform: scale(.98); }
/* EL TEXTO DEL BOTON PRIMARIO ES UN TOKEN Y NO `#fff`, y no es estética: `--azul` se ACLARA
   en oscuro (hoy el teal #4cc6cc; era el azul #7aa9f7) porque ahí sirve de texto sobre fondo
   oscuro, y ese mismo color es aquí el FONDO. Blanco encima daba 2,37:1 —AA pide 4,5— así que
   el botón más usado de la app era ilegible en oscuro. Con el token invertido son 8,85:1. Era
   además el único hex fuera del bloque de tokens, que la cabecera de este archivo prohíbe. */
.boton.primario { background: var(--azul); border-color: var(--azul); color: var(--sobre-azul); }
.boton.primario:hover { filter: brightness(1.08); }

/* NAVEGAR NO ES ACTUAR. Un enlace que lleva a otra pantalla no cambia nada, y pintarlo con el
   mismo cromo que el boton que escribe es lo que amontona una cabecera: la de `/oferta/<obra>`
   parecia tener seis acciones y tenia dos, las otras cuatro eran enlaces disfrazados. Se queda
   la geometria —mismo alto y misma linea base, para que la fila no salte— y se va el relieve.
   Es lo que `.obra-nav` ya hacia para el carril de las seis caras, aplicado a un enlace suelto. */
.boton.enlace { background: none; border-color: transparent; color: var(--texto-suave); }
.boton.enlace:hover { background: none; color: var(--texto); border-color: var(--border); }

/* «ESTAS AQUI» NO ES «PULSA AQUI», Y HASTA EL 2026-08-13 SE PINTABAN IGUAL. Las pestañas de
   `/catalogos` marcaban la sección abierta con `.primario`, o sea que la pantalla ofrecía dos
   acciones obvias —la pestaña donde ya estás y el Guardar de la sección— y la de arriba no
   hacía nada. Es el mismo error de categoría que el `.boton.primario` por fila que se retiró
   el día antes: `primario` significa LA ACCION DE LA PANTALLA, y un estado no es una acción.

   VA POR `aria-current` Y NO POR UNA CLASE NUEVA porque el atributo ya tenía que estar: sin
   él un lector de pantalla tampoco puede decir qué sección está abierta.

   Y EL ASPECTO ES EL DEL ITEM ACTIVO DEL NAV (`--azul-bg` de fondo, `--azul` de texto), que es
   el idioma que esta app ya tiene para «estás aquí». `--azul` a secas es el FONDO del primario:
   usarlo aquí volvería a poner dos cosas del mismo color diciendo cosas distintas. */
.boton[aria-current="page"] {
  background: var(--azul-bg); border-color: var(--azul); color: var(--azul); font-weight: 600;
  cursor: default;
}
.boton[aria-current="page"]:hover { background: var(--azul-bg); }

/* DESACTIVADO ES DEL COMPONENTE, no de una variante. Estaba escrito solo para `.boton.mini`, así
   que los 11 `disabled` de las plantillas —los botones de consolidar mientras hay una corrida en
   curso, los controles de una ficha bloqueada— se pintaban EXACTAMENTE IGUAL que los activos y
   solo se descubría al pulsarlos. */
.boton:disabled,
.boton[aria-disabled="true"] { opacity: .45; cursor: default; }
.boton:disabled:hover,
.boton[aria-disabled="true"]:hover { background: var(--surface); }
.boton.primario:disabled:hover { background: var(--azul); }

/* EL CONTROL DENTRO DE UNA FILA DE TABLA — 2026-08-25.
   Un `<select>` de celda mide 31px con el padding general (`7px 10px`), así que en
   `/imputaciones/<obra>` la fila no bajaba de ahí por mucho que se le quitara padding a la celda:
   el que engorda esa tabla no es la celda, es el control que lleva dentro.

   LA ESPECIFICIDAD ES EL MECANISMO, Y ES A PROPOSITO. Esto vale (0,0,3) y (0,1,2), así que las
   rejillas de CAPTURA le ganan solas y no hay que excluirlas a mano: `.rejilla-captura input`
   (0,1,1), `.celda-captura input` (0,1,1), `td.celda-tasa input` (0,1,2) y
   `.matriz-fija .fijo input` (0,2,1). Ahí el mínimo de 44px de `MASTER.md` §5 sigue mandando,
   porque son las tablas que se rellenan a mano fila a fila. Aquí se LEE. */
tbody td select,
tbody td input:where(:not([type=checkbox]):not([type=radio])),
tbody td .boton { padding: 2px var(--e1); font-size: var(--t1); }
/* Y UN BOTON DE FILA NO ENVUELVE. Medido el 2026-08-25 en `/pcc/<obra>`: bajo `table-layout:
   fixed` el «ver detalle» de la última columna partía en dos líneas, y **la fila entera la medía
   ese botón** —41px con la celda ya en 3px de padding—. Es el mismo modo de fallo que el `<th>`
   con `white-space: nowrap`: en una tabla el ancho lo decide la columna, y lo que no cabe scrollea
   con ella, no crece hacia abajo. */
tbody td .boton { white-space: nowrap; }

/* Las flechas del reparto: van tres por celda, así que ocupan lo mínimo. */
.boton.mini { padding: 2px 7px; font-size: var(--t1); line-height: 1.2; margin-right: 3px; }
.boton.mini:disabled { opacity: .35; }

/* ---- PELIGRO: LO QUE NO SE DESHACE ----
   Hasta el 2026-08-24 esta clase NO EXISTIA, y por eso «Borrar cliente» se pintaba EXACTAMENTE
   igual que «Guardar» y estaba a su lado en la misma fila (`obras_cliente.html`). Un boton que
   borra y uno que guarda no pueden ser el mismo boton.

   NO SE REUSA `.falta`, y no es preferencia: esa clase solo tiene reglas para `tr` y `td` —dice
   el ESTADO de una fila, `MASTER.md` §4— asi que el `class="boton mini falta"` del «confirmar
   borrado» de `_catalogos_codigos.html` llevaba desde que se escribio SIN PINTAR UN PIXEL de
   rojo. Se descubrio al escribir esto, no al mirarlo: un `.falta` de boton no casa con ninguna
   regla y eso no da ningun error.

   SIN TOKEN NUEVO: es el par `--rojo` / `--rojo-bg` que ya usan `.chip.ROJO` y `.aviso.ROJO`, o
   sea que su contraste ya esta medido en los tres bloques de tema. Un token nuevo tendria que ir
   en los tres (§1) y aqui no hace falta ninguno. */
.boton.peligro { color: var(--rojo); border-color: var(--rojo); }
.boton.peligro:hover { background: var(--rojo-bg); }
/* EL SEGUNDO PASO VA RELLENO: el primero dice «esto se puede borrar» y este «esto borra AHORA».
   Si los dos se pintaran igual, el segundo POST seria un tramite y no una pregunta. */
.boton.peligro.confirmar { background: var(--rojo-bg); font-weight: 600; }
.boton.peligro.confirmar:hover { background: var(--rojo-bg); filter: brightness(.96); }

/* ---- EL BOTÓN DE SOLO ICONO (auditoría de botones, 2026-10-12) ----
   Qué botón va solo con icono y cuál con texto lo dice `docs/UI.md` → «Icono o texto». Aquí va lo
   que lo hace convivir con los de texto.

   EL DESNIVEL. Un `inline-flex` que solo lleva un `<svg>` no tiene línea de texto, así que el
   navegador le inventa la línea base en su borde de abajo y lo apoya ahí: medido en la fila de un
   pedido, el ojo salía 2 px más arriba y 1,4 px más bajo que «PDF» a su lado, unos 3 px de
   desnivel entre centros. Un espacio de ancho cero delante le da el MISMO renglón que a un botón
   de texto —su línea base y su alto—, y por eso vale en una celda, en la barra de una tabla y en
   una cabecera sin una regla por sitio. `gap: 0` para que ese carácter invisible no lo descentre.

   EN UNA PANTALLA TÁCTIL no hay ratón y el tooltip no sale nunca (la pulsación larga tampoco: en
   iPhone abre la vista previa del enlace). Ahí el botón crece a 44 px, el mínimo de un dedo
   (`MASTER.md` §5), y enseña su palabra corta debajo del icono: la de `data-palabra`, que solo se
   pinta en ese caso. En el ordenador no cambia nada. Un plegable lleva además
   `data-palabra-abierto` para decir «Plegar» cuando está abierto. */
.boton.solo-icono { justify-content: center; gap: 0; }
.boton.solo-icono::before { content: "\200b"; }
@media (hover: none) and (pointer: coarse) {
  .boton.solo-icono {
    flex-direction: column; gap: 2px; min-width: 44px; min-height: 44px; padding: 4px 6px;
  }
  .boton.solo-icono::before { content: none; }
  .boton.solo-icono .icono { width: 16px; height: 16px; }
  .boton.solo-icono[data-palabra]::after {
    content: attr(data-palabra); font-size: 10px; line-height: 1.1; font-weight: 500;
  }
  .boton.solo-icono[aria-expanded="true"][data-palabra-abierto]::after {
    content: attr(data-palabra-abierto);
  }
}

/* ---- LA ACCIÓN DE UNA FILA (2026-11-05, `templates/_accion_fila.html`) ----
   UN SOLO FORMATO para todo lo que se pulsa al final de una fila: «Repartir», «Ver», «Sacar de la
   semana», el ojo, el lápiz. Es el lenguaje del botón de acto de la bandeja (`.bj-acto`):
   contorno teal, el verbo, y el chevron cuando lleva a otra pantalla. Más bajo que aquél —20 px,
   el alto que ya tenía el `.boton.mini` de las filas— para no engordar una fila de 28.
   Va SOBRE `.boton.mini` y no en su lugar: así hereda el apagado, el hundirse al pulsar, el
   `enviando.js` y la forma táctil de `.solo-icono`, que ya estaban medidos. */
.boton.accion-fila {
  display: inline-flex; align-items: center; justify-content: center; gap: 2px;
  min-height: 20px; margin: 0; padding: 1px 4px 1px 7px; vertical-align: middle;
  border: 1px solid var(--azul); border-radius: var(--radio-s);
  background: var(--surface); color: var(--azul);
  font-size: var(--t1); font-weight: 500; line-height: 1.2; white-space: nowrap;
}
.boton.accion-fila:not(:has(.icono)) { padding-right: 7px; }
.boton.accion-fila:hover { background: var(--azul-bg); text-decoration: none; }
.boton.accion-fila .icono { width: 11px; height: 11px; flex: none; }
.boton.accion-fila.solo-icono { min-width: 26px; padding: 1px 6px; gap: 0; }
.boton.accion-fila.solo-icono .icono { width: 13px; height: 13px; }
.boton.accion-fila.peligro { color: var(--rojo); border-color: var(--rojo); }
.boton.accion-fila.peligro:hover { background: var(--rojo-bg); }
.boton.accion-fila[aria-disabled="true"], .boton.accion-fila:disabled {
  border-color: var(--border-fuerte); color: var(--texto-suave);
}
.boton.accion-fila[aria-disabled="true"]:hover, .boton.accion-fila:disabled:hover {
  background: var(--surface);
}
/* VARIAS EN UNA CELDA: separadas por aire y nunca partidas en dos renglones. */
.boton.accion-fila + .boton.accion-fila,
.boton.accion-fila + form, form + .boton.accion-fila { margin-left: 4px; }
/* LA COLUMNA: a la derecha, como el botón de la bandeja, y sin recortar el botón (la regla de
   `celda-acciones`: un `…` escondería el botón entero y su área de clic). */
th.col-accion, td.col-accion { text-align: right; }
tbody td.col-accion { overflow: visible; }
/* CON EL DEDO, 44 px, como cualquier control táctil (`MASTER.md` §5); el de icono enseña su
   palabra debajo, la regla de `.solo-icono` de arriba, que este bloque pisaría si no se repite. */
@media (hover: none) and (pointer: coarse) {
  .boton.accion-fila { min-height: 44px; padding: 4px 10px; }
  .boton.accion-fila.solo-icono { min-width: 44px; flex-direction: column; gap: 2px; padding: 4px 6px; }
  .boton.accion-fila .icono { width: 16px; height: 16px; }
}

/* ---- LAS CINCO CARAS DE UNA OBRA (`_obra_nav.html`) ----
   Una fila de palabras y no de botones: son NAVEGACION, no acciones, y cinco `.boton` seguidos
   compiten con el que sí hace algo en esa pantalla —«Cerrar e ir a PCC», «Programar»—. El
   negocio lo pidió con esas palabras: «solo la palabra».

   LA LINEA DE ABAJO ES EL SEPARADOR, y hace de riel: sin ella los cinco enlaces flotan sobre el
   fondo de la tarjeta y no se leen como un grupo. */
.obra-nav {
  display: flex; gap: 18px; flex-wrap: wrap; align-items: center;
  margin-top: 10px; padding-bottom: 6px;
  border-bottom: 1px solid var(--border);
  font-size: var(--t2);
}
.obra-nav a { color: var(--texto-suave); text-decoration: none; padding: 2px 0; }
.obra-nav a:hover { color: var(--azul); text-decoration: none; }
/* «ESTAS AQUI» SE MARCA CON PESO Y UN SUBRAYADO PROPIO, nunca con `.primario`: ése es el azul de
   la ACCION, y una pantalla que se marcara a sí misma con él ofrecería dos cosas obvias a la
   vez. Es la misma distinción que `app.css` ya hace con `.boton[aria-current="page"]`.
   El `box-shadow` y no un `border-bottom`: el borde de la barra ya ocupa esa línea, y dos
   bordes en el mismo píxel se suman en un trazo de 2px que se ve más grueso en el activo. */
.obra-nav-aqui {
  color: var(--texto); font-weight: 600; padding: 2px 0;
  box-shadow: inset 0 -2px 0 var(--azul);
}

/* LAS PESTAÑAS DE UNA PANTALLA (2026-09-28): el mismo carril que las caras de una obra. Eligen
   QUÉ se ve —una sección del catálogo, por recibir o recibidos— y NO son filtros: hasta hoy
   vivían en la fila de chips de los filtros, y se leían como uno más. */
.pestanas {
  display: flex; gap: 18px; flex-wrap: wrap; align-items: center;
  margin: 0 0 var(--e3); padding-bottom: 6px;
  border-bottom: 1px solid var(--border);
  font-size: var(--t2);
}
.pestanas a { color: var(--texto-suave); text-decoration: none; padding: 2px 0; }
.pestanas a:hover { color: var(--azul); text-decoration: none; }
.pestanas a[aria-current="page"] {
  color: var(--texto); font-weight: 600;
  box-shadow: inset 0 -2px 0 var(--azul);
}
.pestanas .n { color: var(--texto-suave); font-weight: 400; }

/* ---------------- producción (2026-09-29) ---------------- */

/* EL ESTADO EN TEXTO PLANO: la palabra con el color en el texto, sin fondo, sin borde y sin
   pastilla —la regla del texto plano de DESIGN.md—. Lleva los MISMOS sufijos que `.chip`, así
   que retirar un chip heredado es cambiar `chip` por `senal` y nada más. */
.senal.VERDE { color: var(--verde); }
.senal.AMBAR { color: var(--ambar); }
.senal.ROJO  { color: var(--rojo); }
.senal.GRIS  { color: var(--gris); }

/* LO QUE SOLO LEE UN LECTOR DE PANTALLA: el nombre de una columna de acciones, el motivo de un
   botón apagado. Fuera de la vista pero dentro del árbol de accesibilidad —un `display:none`
   lo sacaría de los dos—. */
.solo-lector {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* EL KPI EN MONO SIGUE SIENDO UN KPI. `td.mono, .mono` fija 12.5 px más abajo y, con la misma
   especificidad y escrito después, le ganaba a `.kpi`: el «W40» de la semana en curso salía en
   letra de celda al lado de tres cifras de 24 px. Mismo caso en las dos fechas de
   `plan_periodo.html`. (0,2,0) gana sin depender del orden. */
.kpi.mono { font-size: var(--t5); }

/* Separaciones y anchos que antes vivían en un `style=` de la plantilla. */
.separado { margin-top: var(--e3); }
.campo-corto { width: 90px; }
.campo-motivo { max-width: 18em; }

/* EL SELECTOR DE MODO DEL CATALOGO: qué se teclea en la cuarta columna del lote. */
.modo-captura { border: 0; padding: 0; margin: 0 0 var(--e3); min-width: 0; }
.modo-captura legend {
  float: left; padding: 0; margin-right: var(--e3);
  font-size: var(--t2); font-weight: 600; line-height: 36px;
}
.modo-captura label { display: inline-flex; align-items: center; gap: var(--e0); min-height: 36px; }

/* CADA MODO ENSEÑA SU COLUMNA, y lo hace el CSS con `:has()` para que la pantalla sirva sin
   JavaScript: cambiar de radio cambia la rejilla sin enviar nada. `data-modos` dice en qué modos
   se ve una celda; la que no lo lleva se ve siempre. `display:none` saca también el campo del
   orden de tabulación, que es lo que se quiere: no se teclea lo que no se ve. */
form:has([name="modo"][value="medicion"]:checked) [data-modos]:not([data-modos~="medicion"]),
form:has([name="modo"][value="venta"]:checked) [data-modos]:not([data-modos~="venta"]),
form:has([name="modo"][value="ponderado"]:checked) [data-modos]:not([data-modos~="ponderado"]) {
  display: none;
}
/* LA SEMANA SE TECLEA EN UN CONTROL DE 44 px (MASTER.md §5): es la rejilla que un jefe de obra
   rellena fila a fila. `celda-captura` sola solo daba el ancho, y el campo se quedaba en el alto
   de las tablas de lectura. Acotado a producción: certificar usa la misma clase y es de otra
   pantalla. */
.tabla-produccion td.celda-captura input { min-height: 44px; min-width: 90px; }
/* LA TIRA DE DÍAS DE `/reportar` (2026-10-13): la producción se reporta POR DÍA, uno cada vez.
   Cada botón es un día de la semana con cuántas partidas tiene debajo, para ver qué día falta;
   44 px de alto y ancho porque se pulsa en el móvil de obra. Los futuros no son enlace: se ven
   apagados. Siete de 44 px caben en 390 px; si no, bajan de línea. */
.tira-dias {
  display: flex; flex-wrap: wrap; gap: 6px; margin: 0 0 var(--e3);
}
.tira-dias .dia {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  min-width: 44px; min-height: 44px; padding: 2px 8px;
  border: 1px solid var(--border); border-radius: var(--radio-s);
  color: var(--texto); text-decoration: none; font-size: var(--t2); line-height: 1.2;
}
.tira-dias a.dia:hover { border-color: var(--azul); text-decoration: none; }
.tira-dias .dia[aria-current="page"] {
  border-color: var(--azul); box-shadow: inset 0 0 0 1px var(--azul); font-weight: 600;
}
.tira-dias .dia .n { font-size: var(--t1); color: var(--texto-suave); font-weight: 400; }
.tira-dias .dia.futuro { color: var(--texto-suave); opacity: .55; }
.rejilla-produccion .c-concepto { width: 38%; }
.rejilla-produccion .c-ud { width: 10%; }

/* EL CONCEPTO VA EN UN RENGLÓN EN TODA LA PRODUCCIÓN (2026-10-12). Alex: *«que recortes, y con
   tooltip se lea el texto completo»*. El concepto de una partida es una descripción de obra
   entera: en la matriz por semanas se partía en 20 renglones, y en reportar, sin partir, empujaba
   las columnas de cantidad fuera de la vista.

   `.una-linea` ES UN RENGLÓN QUE NO PESA EN EL ANCHO DE SU COLUMNA: `width: 0` lo saca de la
   medida del navegador y `min-width: 100%` lo estira a lo que la columna mida. Va en la columna
   de ancho fijo de las matrices (`.f2`, 250 px) y en la fila de capítulo, cuyo `colspan` sin esto
   repartía su texto entero entre las columnas de números y las ensanchaba a todas. En las tablas
   ajustables el concepto de la partida NO lo lleva: ahí lo corta `anchos.js` en su techo.
   El tooltip lo pone `anchos.js` (mira esta caja, como `.dos-lineas`), y `.entera` es la
   pulsación que abre la celda en el móvil (`data-recorte`). */
:is(.tabla-produccion, .matriz-produccion) .una-linea {
  display: block; width: 0; min-width: 100%;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.matriz-produccion .f2 { white-space: nowrap; }
:is(.tabla-produccion, .matriz-produccion) td[data-titulo-auto],
:is(.tabla-produccion, .matriz-produccion) td.entera { cursor: pointer; }
:is(.tabla-produccion, .matriz-produccion) td.entera,
:is(.tabla-produccion, .matriz-produccion) td.entera .una-linea { white-space: normal; }

/* EL ICONO DE UN BOTON: un `<svg>` inline, y por eso aquí solo se le da tamaño. Hereda
   `currentColor` —así sigue al texto en claro, en oscuro y con el botón desactivado— y el
   `gap: 6px` de `.boton` ya lo separa, que es por lo que no lleva margen.
   `flex: none` porque `.boton` es `inline-flex`: sin él, un texto largo lo aplastaría.
   NO SE ESCRIBE CON UN GLIFO NI CON UN EMOJI. La flecha de plegar nació dibujada con bordes
   por esto mismo: `▸` no existe en Fira Sans y el fallback lo pintaba como un guion. Un emoji
   sale en color y a otro tamaño en cada sistema, y el repo no tiene ni uno como icono.
   El 2026-08-25 esa flecha pasó a ser un icono de verdad, y `.flecha-plegar` se quedó como su
   envoltorio — ver el bloque de PLEGADO justo debajo. */
/* EL TAMAÑO ES DEL ICONO, NO DEL SITIO DONDE CAE. Hasta el 2026-08-24 la unica regla era
   `.boton .icono`, y valia mientras los dos unicos iconos vivieran dentro de un boton. Con
   el set dirigido caen ademas en un `<label class=tacha>`, en la pildora de un filtro y en
   un `<h2>`, y un `<svg>` inline SIN tamaño no se pinta a su viewBox: se estira a su
   contenedor y se come la celda. Una regla por sitio es una lista que se queda corta al
   cuarto sitio; con el defecto aqui, lo que hace falta es solo el AJUSTE de los que se
   salen de 13px. */
.icono { width: 13px; height: 13px; flex: none; }

/* ---------------- PLEGADO: EL ICONO DICE LA ACCION, NO EL ESTADO ----------------
   Encargo del dueno el 2026-08-25: «que salga el icono contrario al status de la tabla». Asi
   que contraida ensena la flecha ABAJO —«abreme»— y desplegada la de ARRIBA. Es LO CONTRARIO
   de lo que hacia `.marca-plegar`, que pintaba el estado, y por eso esta escrito aqui: sin esta
   nota, la proxima pasada lo «arregla» devolviendolo.

   LOS DOS `<svg>` ESTAN SIEMPRE EN EL DOM Y AQUI SE ENSENA UNO. No se intercambia el nodo desde
   JavaScript porque `estructura.html` pliega sus semanas con `<details>` NATIVO y sin una linea
   de script: un intercambio en JS dejaria esa pantalla sin icono. Y los cuatro scripts que
   pliegan (`plegar.js`, `arbol.js`, `compras.js`, `plan.js`) no tuvieron que tocarse.

   LAS CUATRO REGLAS VAN JUNTAS A PROPOSITO, y la cuarta esta INVERTIDA: en la rejilla del plan
   la clase marca lo CONTRAIDO (`tr.contraida`) y en las otras tres lo ABIERTO. Separarlas es
   como se olvida que una de las cuatro va al reves. */
/* LA CONMUTACION ES DEL TIRADOR Y NUNCA GLOBAL, y esto se vio MIRANDO la pantalla: con
   `.icono-contraer { display: none }` a secas, el boton «Plegar todo» de la barra salia SIN
   ICONO —su flecha es esa misma clase y ahi no hay ningun estado que la encienda— mientras
   «Desplegar todo» si lo tenia. Un boton de accion fija no conmuta: ensena el suyo y ya.
   Por eso las reglas cuelgan de los envoltorios y no del documento.
   DESDE EL 2026-09-25 LA BARRA YA NO TIENE BOTON DE ACCION FIJA: el par se fusiono en un
   `.tirador-todo` que alterna, y el de cada bloque de `/oferta/<obra>` tambien lo es. Su estado
   es su propio `aria-expanded`, que pone `Barra.alterna`. */
:is(.flecha-plegar, .flecha-pedido, .marca-plegar, .tirador-todo) .icono-contraer { display: none; }
.tirador-todo[aria-expanded="true"] .icono-desplegar { display: none; }
.tirador-todo[aria-expanded="true"] .icono-contraer  { display: inline-block; }
details[open] > summary .flecha-plegar .icono-desplegar,
tr.plegable.abierto     .flecha-plegar .icono-desplegar,
tbody.pedido.abierto    .flecha-pedido .icono-desplegar { display: none; }
details[open] > summary .flecha-plegar .icono-contraer,
tr.plegable.abierto     .flecha-plegar .icono-contraer,
tbody.pedido.abierto    .flecha-pedido .icono-contraer  { display: inline-block; }
/* La rejilla del plan, al reves: aqui la clase dice CONTRAIDA, asi que el defecto es abierto. */
.marca-plegar .icono-contraer  { display: inline-block; }
.marca-plegar .icono-desplegar { display: none; }
tr.contraida .marca-plegar .icono-contraer  { display: none; }
tr.contraida .marca-plegar .icono-desplegar { display: inline-block; }

/* ---------------- EL RECUADRO DE CONFIRMAR (`<dialog>`) ----------------
   EL PRIMER Y UNICO MODAL DEL REPO, desde el 2026-08-17, y entra con su porque escrito porque
   hasta ese dia tres sitios decian que no habia ninguno (`compras.py`, `pcc.py` y la regla de
   §275 de MASTER.md). Lo pidio el negocio con esas palabras —«cuando le piques te puede abrir un
   pequeno recuadro que te diga seguro que quieres eliminar este pedido si o no»— y el patron
   canonico no servia aqui: el segundo POST repinta la pantalla ENTERA —filtro, pagina y hasta 100
   filas, 410 KB en `/pedidos/GE-LNV-093`— para preguntar una cosa de UNA fila.

   LA REGLA DEL SEGUNDO POST NO SE DEROGA, se acota: sigue siendo el patron cuando el token tiene
   que ser EL DATO (el importe pendiente, la divisa, el codigo), porque ahi la pregunta cambia si
   algo se movio entre el aviso y el clic. Un descarte de pedido es soft y reversible desde
   `/pedidos/basurero`, asi que no hay token que vigilar.

   SE ESTILA EL ELEMENTO Y NO UNA CLASE. `<dialog>` trae su propio `display:none` de origen UA
   mientras esta cerrado, y el navegador lo centra, atrapa el foco, cierra con Esc y devuelve el
   foco al boton que lo abrio. Nada de eso se reimplementa.

   NI UN TOKEN NUEVO: `--surface`, `--border`, `--radio` y `--sombra` ya existen en los TRES
   bloques de tema, asi que esto no puede divergir entre el automatico y el manual — que es la
   trampa de la que avisa la cabecera de este archivo. */
dialog {
  border: 1px solid var(--border); border-radius: var(--radio);
  background: var(--surface); color: var(--texto);
  box-shadow: var(--sombra); padding: var(--e3);
  /* `max-width` en `min()` y no en `vw` pelados: a 900px el recuadro tiene que caber con sus
     margenes, y `<dialog>` no hereda el ancho de nadie. */
  width: min(420px, calc(100vw - 2 * var(--e3)));
}
dialog::backdrop { background: rgba(16, 20, 28, .45); }
/* La fila de botones va a la derecha y el destructivo el ULTIMO: se lee «no / si», que es el
   orden en el que se decide. `.fila-form` ya da el flex y el hueco. */
dialog .acciones { display: flex; justify-content: flex-end; gap: var(--e1); margin-top: var(--e3); }

/* LAS VENTANAS (`static/ventana.js`, 2026-09-24): el formulario que antes se pintaba sobre la
   hoja. Mas anchas que la pregunta de si/no porque llevan campos —motivo, contrasena, semanas—.
   `:not(:modal)` es la misma ventana SIN JavaScript: el servidor la manda con `open` y se ve en
   su sitio como una tarjeta, que es como se veia antes. Con JS nunca llega a pintarse asi. */
dialog.ventana { width: min(520px, calc(100vw - 2 * var(--e3))); }
dialog.ventana h2 { margin: 0 0 var(--e2); font-size: 1.05rem; text-wrap: balance; }
dialog.ventana p { margin: 0 0 var(--e2); }
dialog.ventana form > :last-child { margin-bottom: 0; }
dialog.ventana .campo { display: grid; gap: 4px; margin-bottom: var(--e2); }
dialog.ventana .campo :is(input, textarea, select) { width: 100%; }
dialog.ventana[open]:not(:modal) {
  position: static; display: block; width: auto; max-width: none; margin: 0 0 var(--e3);
  box-shadow: none;
}
/* LAS DOS MANERAS DE CREAR UN PRESUPUESTO (`oferta_obra.html`, ventana `v-crear`, 2026-10-12):
   dos enlaces grandes, uno al lado del otro, con la línea que explica cada uno. En el teléfono
   van uno encima del otro. Son ENLACES con forma de tarjeta, no botones: elegir no escribe. */
dialog.ventana.ventana-ancha { width: min(680px, calc(100vw - 2 * var(--e3))); }
.opciones-crear { display: grid; grid-template-columns: 1fr 1fr; gap: var(--e2); }
@media (max-width: 600px) { .opciones-crear { grid-template-columns: 1fr; } }
.opcion-crear {
  display: grid; gap: 6px; align-content: start; padding: var(--e3);
  border: 1px solid var(--border); border-radius: var(--radio); color: var(--texto);
  text-decoration: none; background: var(--surface);
}
.opcion-crear b { font-size: 1rem; color: var(--azul); }
.opcion-crear:hover { border-color: var(--azul); background: var(--surface-2); }
.opcion-crear:focus-visible { outline: 2px solid var(--azul); outline-offset: 2px; }
/* La cascada del bloque «Indirectos, estructura y margen»: tres renglones, sin cabecera. */
.tabla-cascada { width: auto; min-width: 320px; margin-top: var(--e2); }
/* LA VENTANA ENTRA, NO APARECE (2026-09-24, encargo de Alex: «que no se vea tan plana»). Es
   movimiento con significado —§6 de MASTER.md—: dice que lo de delante es otra capa y que la
   hoja sigue detrás. 150ms, `opacity` y `transform`, nada de medidas. `prefers-reduced-motion`
   la apaga con la regla global. Solo `:modal`: sin JS la ventana es una tarjeta más y no entra. */
dialog:modal { animation: ventana-entra 150ms var(--ease-out); }
/* EL FONDO ENTRA CON LA VENTANA, misma curva y misma duracion (2026-09-26). Oscurecer la hoja al
   45 % de golpe era lo que MAS cambiaba en pantalla, y era justo lo que no se movia: la ventana
   entraba suave sobre un fondo que ya habia saltado. Al cerrar, las dos se van a la vez y sin
   animar, que es lo que se espera de un «No». */
dialog:modal::backdrop { backdrop-filter: blur(2px); animation: fondo-entra 150ms var(--ease-out); }
@keyframes ventana-entra {
  from { opacity: 0; transform: translateY(4px) scale(.985); }
  to   { opacity: 1; transform: none; }
}
@keyframes fondo-entra { from { opacity: 0; } }

/* LA TARJETA QUE ES UN ENLACE se levanta al pasar el ratón: es la única que se puede pinchar
   entera, y dicho así no hace falta pintarla de azul (§5: el azul es para salir de la pantalla).
   Las demás tarjetas NO: una sombra que se mueve en cada una competiría con la tabla.
   SOLO CON RATON: en una pantalla tactil el `:hover` se queda pegado tras el toque, y la tarjeta
   seguia levantada al volver atras. */
.tarjeta-enlace { transition: box-shadow .15s ease-out, transform .15s ease-out; }
@media (hover: hover) and (pointer: fine) {
  .tarjeta-enlace:hover { box-shadow: var(--sombra), 0 6px 18px rgba(16, 20, 28, .08);
    transform: translateY(-1px); }
}

/* LA FICHA BLOQUEADA SE LEE COMO TEXTO. Sus controles siguen ahí y `disabled` —ver la cabecera
   de `obras_ficha.html`: enseñan el valor y no viajan—, pero con borde y fondo parecían un
   formulario vacío esperando que alguien tecleara. */
.ficha-bloqueada :is(input, select, textarea):is(:disabled, [readonly]) {
  border-color: transparent; background: transparent; color: var(--texto); opacity: 1;
  padding-left: 0; cursor: default; -webkit-appearance: none; appearance: none;
}

/* EL MEDIDOR DE CUADRE: cuánto de la venta llevan capturado las filas. La barra NUNCA va sola:
   al lado va la cifra y lo que falta (§1, el color no es la única señal). Ámbar mientras falta,
   verde al cuadrar, rojo si se pasa. Lo pinta el servidor y `medidor.js` lo recalcula. */
.medidor { display: flex; align-items: center; gap: var(--e2); flex-wrap: wrap; }
.medidor .barra-medidor {
  flex: 1 1 160px; max-width: 280px; height: 8px; border-radius: 4px; background: var(--gris-bg);
  overflow: hidden;
}
/* LA BARRA CRECE CON `scaleX` Y NO CON `width`: MASTER.md §6 solo anima transform y opacity. */
.medidor .barra-medidor > span {
  display: block; height: 100%; width: 100%; background: var(--ambar);
  transform: scaleX(0); transform-origin: left center; transition: transform .2s ease-out;
}
.medidor.cuadra .barra-medidor > span { background: var(--verde); }
.medidor.pasa   .barra-medidor > span { background: var(--rojo); }
/* LA CIFRA ES EL ÚLTIMO HIJO y se colorea por el estado del medidor, no por su clase: las
   rejillas le reescriben `className` en cada tecla y se llevarían una clase propia. */
.medidor > :last-child { font-variant-numeric: tabular-nums; }
.medidor.cuadra > :last-child { color: var(--verde); }
.medidor.pasa   > :last-child { color: var(--rojo); }

/* LA ZONA DE SOLTAR (`soltar.js`). Es un `<label>` que envuelve el `<input type=file>`: sin JS,
   pulsarla abre el selector del sistema, que es lo que hacía el input a pelo. El input se
   esconde de la vista y no del formulario —`display:none` lo sacaría del orden de foco y de
   la validación `required`—. */
.zona-soltar {
  display: flex; align-items: center; gap: var(--e2); padding: var(--e3);
  border: 1.5px dashed var(--border-fuerte); border-radius: var(--radio);
  background: var(--surface-2); cursor: pointer;
}
.zona-soltar:hover, .zona-soltar.encima { border-color: var(--azul); background: var(--azul-bg); }
.zona-soltar:focus-within { outline: 2px solid var(--azul); outline-offset: 2px; }
.zona-soltar > .icono { width: 26px; height: 26px; color: var(--texto-tenue); }
.zona-soltar input[type=file] {
  position: absolute; width: 1px; height: 1px; opacity: 0; overflow: hidden;
}
.zona-soltar .elegido { font-weight: 600; }
/* LA PISTA DE LA ZONA VA EN `--texto-suave` Y NO EN `--texto-tenue` (2026-09-27, auditoría de
   Oferta). El tenue es el suelo de contraste SOBRE `--surface`; la zona es `--surface-2`, y en
   oscuro daba 4,12:1 —y 3,99 con el `--azul-bg` del hover—, por debajo de AA. El suave pasa de
   5:1 en los cuatro casos, claro y oscuro, reposo y encima. */
.zona-soltar .tenue { color: var(--texto-suave); }
/* DENTRO DE UN `.campo` la zona es un `<label>` y le caería `.campo label` —bloque, 12 px, gris,
   seminegrita—, que es el rótulo del campo y no la zona. Se deshace aquí, no en la plantilla. */
.campo label.zona-soltar {
  display: flex; font-size: var(--t2); color: var(--texto); font-weight: 400; margin: 0;
}
/* Y EN UNA FILA DE CAMPOS MIDE LO QUE UN CAMPO (2026-10-08). Con su `--e3` de relleno y el icono
   de 26 px salía de 56 px entre un `<select>` de 33 y dos botones de 30: tres alturas en una
   fila. Aquí la zona es una celda más y la tarjeta ya dice qué se suelta; suelta, sin `.campo`
   —la de documentos del cliente—, conserva el tamaño de diana. */
.campo label.zona-soltar {
  min-height: var(--alto-control); padding: 0 var(--e2); gap: var(--e1);
}
.campo .zona-soltar > .icono { width: 16px; height: 16px; flex: none; }
/* Los botones de una celda de acciones van por un `<span class="acciones-cabecera">`, y la regla
   del alto mínimo solo alcanza a los hijos directos de `.campos`: se quedaban en 30 px. */
.campos > .campo > .acciones-cabecera > :is(.boton, button):not(.mini) { min-height: var(--alto-control); }

/* LAS INICIALES de una persona: el equipo de obra y los contactos de un cliente. Un círculo con
   dos letras, no una foto —no hay fotos que enseñar— y del color neutro, porque una persona no
   es un estado. */
.iniciales {
  display: inline-grid; place-items: center; width: 24px; height: 24px; flex: none;
  border-radius: 50%; background: var(--azul-bg); color: var(--azul);
  font-size: 10.5px; font-weight: 600; letter-spacing: .02em; vertical-align: middle;
  margin-right: 8px;
}
.iniciales.vacia { background: var(--gris-bg); color: var(--gris); }

/* EL CHECKLIST de lo que le falta a una obra para activarse. Cada paso es una fila: la marca
   `hecho()` en verde, o un círculo hueco. La palabra dice el paso, el icono si está. */
.checklist { list-style: none; margin: 0; padding: 0; display: grid; gap: 6px; }
.checklist li { display: flex; align-items: center; gap: 8px; }
.checklist .marca {
  display: inline-grid; place-items: center; width: 18px; height: 18px; flex: none;
  border-radius: 50%; border: 1.5px solid var(--border-fuerte);
}
.checklist li.hecho .marca { border-color: var(--verde); background: var(--verde-bg); color: var(--verde); }
.checklist li.hecho .marca .icono { width: 12px; height: 12px; }
.checklist li:not(.hecho) .paso { font-weight: 600; }

/* LOS PASOS de una pantalla de carga: «1 Obra · 2 Archivo · 3 Cargar». Texto con su número en
   círculo, no un asistente: todo está en la misma hoja. */
.pasos { display: flex; gap: var(--e3); flex-wrap: wrap; margin: 0 0 var(--e3); padding: 0;
  list-style: none; }
.pasos li { display: flex; align-items: center; gap: 6px; color: var(--texto-suave); }
.pasos li b {
  display: inline-grid; place-items: center; width: 20px; height: 20px; border-radius: 50%;
  background: var(--azul-bg); color: var(--azul); font-size: 11px;
}

/* Los mensajes de despues de una accion flotan y no empujan la hoja.
   `pointer-events` solo en los mensajes: la franja vacia no puede tapar clics de la pagina.

   ABAJO A LA DERECHA desde el 2026-09-25. Arriba caian justo encima de `.encabezado-fin`, que es
   donde vive la fila de botones de cada pantalla, y los tapaban 6 s —un rojo, hasta cerrarlo—.
   Abajo no hay nada fijo: las barras pegadas de las rejillas se pegan ARRIBA. */
.avisos-flotantes {
  position: fixed; bottom: calc(var(--e3) + env(safe-area-inset-bottom, 0px)); right: var(--e3);
  z-index: 50; display: grid; gap: var(--e1); width: min(420px, calc(100vw - 2 * var(--e3)));
  pointer-events: none;
}
.avisos-flotantes .flash {
  pointer-events: auto; margin: 0; box-shadow: var(--sombra);
  display: flex; gap: var(--e2); align-items: flex-start; justify-content: space-between;
  animation: aviso-entra 200ms var(--ease-out);
  transition: opacity 150ms ease-in, transform 150ms ease-in;
}
/* ENTRA Y SALE POR EL MISMO BORDE (2026-09-26): sube 6px al llegar y se hunde 6px al irse, y asi
   se lee como una sola cosa que viene y se va. Hasta ese dia aparecia de golpe y solo la salida
   se movia. La salida es mas corta que la entrada —§6: `ease-in` al salir— porque lo que se va
   ya no hay que leerlo. Keyframes y no `@starting-style`, como `ventana-entra`: los avisos
   llegan con la carga de la pagina y ahi un keyframe corre seguro.
   `pointer-events: none` al salir: un aviso que se esta yendo ya no puede tapar un clic. */
.avisos-flotantes .flash.saliendo { opacity: 0; transform: translateY(6px); pointer-events: none; }
@keyframes aviso-entra { from { opacity: 0; transform: translateY(6px); } }
.flash-cerrar {
  background: none; border: 0; color: inherit; cursor: pointer; font-size: 18px; line-height: 1;
  padding: 0 2px; opacity: .7;
}
.flash-cerrar:hover { opacity: 1; }

/* ---------------- bloques por semana (reparto de ESTRUCTURA) ---------------- */

.cabecera-bloque {
  display: flex; justify-content: space-between; align-items: flex-start;
  gap: 16px; flex-wrap: wrap; margin-bottom: 14px;
}
.en-juego { text-align: right; }
.en-juego .mono { font-size: var(--t3); font-weight: 600; font-variant-numeric: tabular-nums; }

/* Un <details> por semana (reparto) o por código de coste (presupuesto de una oferta). El
   resumen es la fila que se ve plegada, así que lleva el importe: si hay que abrirlo para
   saber cuánta plata hay, no sirve de índice.

   `.grupo` va en el MISMO selector y no en un componente propio: dos plegables con el mismo
   aspecto y CSS separado divergen en la primera semana que alguien toque uno. */
details.semana, details.grupo { border-top: 1px solid var(--border); }
details.semana > summary, details.grupo > summary {
  display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap;
  padding: 9px 4px; cursor: pointer; list-style: none;
}
details.semana > summary::-webkit-details-marker,
details.grupo > summary::-webkit-details-marker { display: none; }
details.semana > summary:hover, details.grupo > summary:hover { background: var(--surface-2); }

/* DEJO DE SER UN TRIANGULO Y PASO A SER EL ENVOLTORIO DEL ICONO, el 2026-08-25. Se dibujaba
   con bordes CSS porque `▸` (U+25B8) no esta en Fira Sans y el fallback lo pintaba como un
   guion —ese argumento sigue en pie y es el que mato de paso a `.flecha-pedido` y a
   `.marca-plegar`, que si usaban el glifo—. Lo que cambio es que ahora hay un `<svg>` de
   verdad: `_iconos.html` -> `plegar()`.

   LA CLASE SE CONSERVA AUNQUE YA NO DIBUJE NADA, y no es residuo: es lo que hace que
   `tr.plegable .flecha-plegar { visibility: visible }` siga valiendo sin tocar `arbol.js`.
   Sin ella habria que reescribir esa regla contra las dos clases del icono. */
.flecha-plegar {
  display: inline-flex; align-items: center; flex: none;
  color: var(--texto-suave);
}
.etiqueta-semana { font-weight: 600; font-size: var(--t2); }
.importes-semana {
  margin-left: auto; display: flex; gap: 14px; flex-wrap: wrap;
  font-variant-numeric: tabular-nums; font-size: 12.5px;
}
details.semana > .tabla-scroll, details.grupo > .tabla-scroll { padding-bottom: 8px; }
/* Sin <thead>: la cabecera se repetiría en cada semana diciendo siempre lo mismo. */
details.semana table tbody td,
details.grupo table tbody td { border-bottom: none; padding: 5px 10px; }
/* Dentro de un `<details>` que YA está sangrado por su propio rail, el escalón de 28 px del
   nivel 3 caería en mitad del `Línea ID`. La escalera la hace aquí el anidamiento. */
details.grupo tr[data-nivel="3"] { --rail-x: 2px; }

/* ---------------- el indice de `/bandeja/` (2026-09-26; una linea desde el 2026-11-05) ----------------
   Lo primero que se lee en la bandeja del administrador: una linea con cada puesto y su cuenta
   («Obra 7 · Direccion 3 · …»), que salta a su seccion. Hasta el 2026-11-05 era una fila por
   puesto con todos sus pasos, el indice de cuando los pasos nacian plegados. ENLACES DE TEXTO,
   SIN PASTILLA: Alex no quiere chips, ni siquiera como boton de indice. El separador `·` lo pone
   el CSS para que el lector de pantalla no lo lea entre puesto y puesto. */
.indice-bandeja { padding: var(--e3) var(--e4); }
.pasos-indice { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; }
/* El separador va DETRAS de cada paso y no delante: al partirse la fila, un `·` al principio de
   la linea nueva parece una vineta. */
.pasos-indice li:not(:last-child)::after { content: "·"; color: var(--texto-tenue); padding: 0 var(--e2); }
.pasos-indice a { color: var(--azul); text-decoration: none; }
.pasos-indice a:hover { text-decoration: underline; }
.pasos-indice b { color: var(--texto); font-variant-numeric: tabular-nums; }

/* `.importes-semana` alinea por la base el dinero de una cabecera con su numero grande. Nacio
   para las tarjetas de la bandeja (2026-09-26) y la usan tambien otras cabeceras; la bandeja ya
   no, desde que es una lista (2026-11-04, abajo). */
.importes-semana { align-items: baseline; }

/* ---------------- /bandeja/ como lista de pendientes (2026-11-04) ----------------
   Alex aprobo la maqueta: cada paso es un titulo con su cuenta y una LISTA, y cada fila dice
   que es, cuanto, desde cuando y lleva UN boton con el verbo del paso. Sin filo de color, sin
   sombra, sin pastillas: los estados van en el texto. Todo color sale de los tokens, asi que
   el tema oscuro no necesita una regla propia. */
.bj-cabeza { margin-bottom: var(--e5); }
.bj-resumen { font-variant-numeric: tabular-nums; }
.bj-resumen b { color: var(--texto); font-weight: 600; }
.bj-resumen .sep { margin: 0 var(--e1); color: var(--border-fuerte); }
.bj-viejo-txt { font-weight: 600; }
/* EL PUESTO (la bandeja del administrador) abre con una raya, como las secciones de
   `/notificaciones`: con los pasos siempre abiertos, la raya es lo que dice donde empieza otro. */
h2.bj-seccion {
  margin: var(--e6) 0 var(--e3); padding-top: var(--e4); border-top: 1px solid var(--border);
}

/* El paso: una `<section>` con su titulo, SIEMPRE ABIERTA desde el 2026-11-05 —hasta ese dia
   un `<details>` con la flecha de plegar—. El titulo es un `<h2>` o un `<h3>` segun haya puesto
   encima (`bandeja.html`), y lleva la cuenta dentro: «Documentacion por subir 3». */
.bj-paso { margin: 0 0 var(--e5); }
.bj-paso-cabeza {
  display: flex; flex-wrap: wrap; align-items: baseline; column-gap: var(--e2); row-gap: var(--e0);
  margin: 0 0 var(--e2); padding: 0 2px;
}
.bj-paso-titulo { margin: 0; font-size: var(--t3); font-weight: 600; color: var(--texto); letter-spacing: normal; }
.bj-cuenta {
  font-size: var(--t3); font-weight: 500; color: var(--texto-tenue);
  font-variant-numeric: tabular-nums;
}
.bj-de { font-size: var(--t1); color: var(--texto-tenue); }
.bj-fin { margin-left: auto; display: flex; flex-wrap: wrap; align-items: baseline; column-gap: var(--e4); }
.bj-total { font-size: var(--t1); color: var(--texto-suave); font-variant-numeric: tabular-nums; }
.bj-total b { color: var(--texto); font-weight: 500; }
.bj-listado { font-size: var(--t1); white-space: nowrap; }

/* La lista: una superficie con raya. El contenedor decide la forma de la fila por el ancho que
   TIENE —con el menu abierto a 1.024 px tiene poco—, no por el de la ventana. */
.bj-lista {
  list-style: none; margin: 0; padding: 0;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radio);
  overflow: hidden; container-type: inline-size;
}
.bj-mas { margin: var(--e1) 2px 0; font-size: var(--t1); color: var(--texto-tenue); }

/* La fila: que es · cifra · antiguedad · acto. */
.bj-fila {
  position: relative;
  display: grid; grid-template-columns: minmax(0, 1fr) 170px 118px 152px;
  align-items: center; column-gap: var(--e5);
  padding: var(--e2) var(--e4) var(--e2) 18px; min-height: 58px;
}
.bj-fila + .bj-fila { border-top: 1px solid var(--border); }
.bj-fila:hover { background: var(--surface-2); }
.bj-que { min-width: 0; }
.bj-titulo {
  display: block; color: var(--texto); font-size: var(--t3); font-weight: 500; line-height: 1.35;
  overflow-wrap: anywhere;
}
a.bj-titulo { position: relative; z-index: 1; }
.bj-contexto {
  margin-top: 2px; color: var(--texto-suave); font-size: var(--t1); line-height: 1.45;
  overflow-wrap: anywhere;
}
/* Nada se esconde: el contexto baja de renglon, pero cada dato CORTO va entero. */
.bj-contexto .i { white-space: nowrap; }
.bj-contexto .mono { font-size: var(--t1); color: var(--texto-suave); }
.bj-contexto .p { margin: 0 2px; color: var(--border-fuerte); }

.bj-cifra, .bj-edad { text-align: right; font-variant-numeric: tabular-nums; line-height: 1.3; }
.bj-cifra .etq, .bj-edad .etq { display: block; font-size: var(--t0); color: var(--texto-tenue); }
.bj-cifra .v, .bj-edad .v { font-size: var(--t2); font-weight: 500; white-space: nowrap; }
.bj-cifra .v { color: var(--texto); }
.bj-paso .div { font-size: var(--t0); color: var(--texto-tenue); margin-left: 3px; font-weight: 400; }
/* La antiguedad: el numero SIEMPRE, el color solo como refuerzo. Gris hasta 6 dias, ambar desde
   7 y rojo desde 14 (`bandeja.DIAS_ESPERA`, `DIAS_ROJO`). */
.bj-edad .v { color: var(--texto-suave); }
.bj-edad.ambar .v, .bj-viejo-txt.ambar { color: var(--ambar); }
.bj-edad.rojo .v, .bj-viejo-txt.rojo { color: var(--rojo); }

/* El acto: con su verbo y su contorno. ABRE el documento donde se hace el acto —`MASTER.md` lo
   declara como excepcion a «lo que navega no lleva cromo»—, y su `::after` se estira sobre la
   fila: la fila entera se pulsa con UN solo enlace. */
.bj-acto {
  justify-self: end;
  display: inline-flex; align-items: center; justify-content: center; gap: var(--e1);
  min-width: 120px; min-height: 36px; padding: 0 var(--e3);
  border: 1px solid var(--azul); border-radius: var(--radio-s);
  background: var(--surface); color: var(--azul);
  font: inherit; font-size: var(--t2); font-weight: 500; white-space: nowrap; cursor: pointer;
}
a.bj-acto::after { content: ""; position: absolute; inset: 0; }
.bj-acto:hover { background: var(--azul-bg); text-decoration: none; }
.bj-acto-form { justify-self: end; margin: 0; }
.bj-acto .icono { width: 13px; height: 13px; }

/* Por debajo de ~620 px DE LISTA cada fila es una mini ficha: el que arriba, cifra y antiguedad
   en una linea y el boton a todo lo ancho, de 44 px para el dedo. Sin scroll lateral. */
@container (max-width: 620px) {
  .bj-fila {
    grid-template-columns: 1fr auto;
    grid-template-areas: "que que" "cifra edad" "acto acto";
    row-gap: var(--e2); padding: var(--e3);
  }
  .bj-que { grid-area: que; }
  .bj-cifra { grid-area: cifra; text-align: left; }
  .bj-edad { grid-area: edad; }
  .bj-acto, .bj-acto-form { grid-area: acto; justify-self: stretch; min-height: 44px; }
  .bj-acto-form .bj-acto { width: 100%; }
}
@media (max-width: 600px) {
  .bj-paso-cabeza .bj-fin { margin-left: 0; flex-basis: 100%; }
}

/* El estado vacio: la pantalla que se ve al terminar el trabajo. */
.bj-vacio {
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radio);
  padding: 56px var(--e5) 52px; text-align: center;
}
.bj-vacio .sello {
  width: 48px; height: 48px; margin: 0 auto 14px; border-radius: 50%;
  display: grid; place-items: center; background: var(--verde-bg); color: var(--verde);
}
.bj-vacio .sello .icono { width: 24px; height: 24px; }
.bj-vacio h2 { font-size: var(--t4); }
.bj-vacio p { margin: var(--e1) auto 0; max-width: 46ch; color: var(--texto-suave); font-size: var(--t2); }
.bj-vacio .atajos { margin-top: 22px; font-size: var(--t2); color: var(--texto-tenue); }
.bj-vacio .atajos a { white-space: nowrap; }
.bj-vacio .atajos .p { margin: 0 var(--e2); color: var(--border-fuerte); }
@media (max-width: 600px) { .bj-vacio { padding: 40px 18px; } }

/* La barra de vuelta a la bandeja, arriba del documento abierto desde ella. Una linea de texto
   con dos enlaces: sin fondo de color ni pastilla, y el siguiente empujado a la derecha. */
.vuelta-bandeja {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--e3);
  padding: var(--e2) 0; margin-bottom: var(--e3);
  border-bottom: 1px solid var(--border-suave);
}
.vuelta-bandeja a { color: var(--azul); text-decoration: none; }
.vuelta-bandeja a:hover { text-decoration: underline; }
.vuelta-bandeja > a:last-child:not(:first-child) { margin-left: auto; }
.flash a { color: inherit; font-weight: 600; }

/* ---------------- el presupuesto de una oferta ----------------
   Bloque -> código de coste -> partidas. El código lleva su detalle al lado porque `CI.3` no
   le dice nada a nadie, y el conteo de partidas porque es la relación que la pantalla vieja
   escondía: un código son VARIAS partidas. */
.etiqueta-codigo { font-weight: 600; font-size: var(--t2); font-family: var(--mono); }
.detalle-codigo { color: var(--texto-suave); font-size: 12.5px; }

/* La suma de todas las partidas: el presupuesto original del proyecto. Va al pie de la
   tarjeta y con `.kpi`, que es el tamaño que ya tiene el número grande de una tarjeta. */
.linea-total {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 16px; flex-wrap: wrap;
  margin-top: var(--e3); padding-top: var(--e3);
  border-top: 1.5px solid var(--border-fuerte);
}
/* En caja normal desde el 2026-11-05, como las cabeceras de tabla y a su tamaño. */
.linea-total .etiqueta {
  font-size: var(--t1); font-weight: 600; color: var(--texto-suave);
}
/* Una línea por divisa. Nunca se apilan en un solo número. */
.linea-total .cifras { margin-left: auto; text-align: right; }

/* ---------------- rejilla de captura ----------------
   Una tabla donde cada celda es un campo. El borde solo aparece al pasar o al enfocar: con
   borde permanente, cinco filas vacías son treinta cajas blancas y la pantalla se lee como
   un formulario a medio rellenar en vez de como una hoja. Los anchos van en <col>. */
.rejilla-captura td, .rejilla-captura th { padding: 3px 5px; }
.rejilla-captura tbody td { border-bottom: 1px solid var(--border); vertical-align: middle; }
/* LA COLUMNA DE QUITAR, AL ANCHO DE SU BOTON Y CON AIRE (2026-10-01). Alex, viendo el vídeo del
   plan de pagos de la oferta: «botones encimados». La columna iba en porcentaje y sin regla
   propia, a diferencia de `.matriz-fija .quitar`, y la «×» salia pegada al importe de su
   izquierda, que va alineado a la derecha. Ancho minimo, centrada y con un hueco a la izquierda
   del tamano del que separa dos campos. */
.rejilla-captura :is(td, th).quitar { width: 1%; text-align: center; padding-left: var(--e4); }
.rejilla-captura td.quitar .boton.mini { margin-right: 0; }
/* EL PLAN DE PAGOS NO SE ESTRUJA (2026-10-01): sus columnas van en px, y cuando no caben la
   tabla automatica las encoge hasta el contenido minimo —un `<input>` al 100 % no tiene—, con
   la caja del % en 22 px en un movil. Con un minimo, `.tabla-scroll` la desliza. */
.rejilla-captura.pagos { min-width: 560px; }
/* LOS PAPELES DE CADA PAGO, DEBAJO DE SU NOMBRE (2026-10-13; solo lectura desde el 2026-10-17).
   El número de pago decide qué es y qué pide: el nombre en su línea y, debajo, en pequeño y
   tenue, la lista de papeles que pedirá, que parte donde haga falta. Sin casillas. */
/* EN BLOQUE Y PARTIENDO LÍNEA, contra dos reglas de tabla que aquí estorban: `tbody td div` las
   pone en línea, y la celda no parte. Sin esto la lista del finiquito empujaba el % y el importe
   fuera de la tabla. */
.rejilla-captura.pagos td:has(> .pide-pago) { white-space: normal; }
.rejilla-captura.pagos td :is(.nombre-pago, .pide-pago) { display: block; margin-left: 0; }
.nombre-pago { font-weight: 600; }
.pide-pago { margin-top: 2px; font-size: var(--t1); color: var(--texto-tenue);
  line-height: 1.35; white-space: normal; }
/* En reposo la celda es un rectángulo tenue sin borde: cinco filas vacías se leen como una
   rejilla y no como treinta cajas blancas de formulario. Pero SÍ se ve —transparente del
   todo no se distingue dónde se escribe— y al enfocar sube a superficie con borde. */
/* LA CASILLA SE EXCLUYE DEL `width: 100%`, y es la MISMA exclusión que este archivo ya lleva
   escrita en `.campo input:where(...)` con su porqué: «uno a 100% de ancho es una raya». Aquí
   no era una raya, era 795 px de scroll horizontal en el cuerpo. `.marca-borrar` es (0,1,0) y
   este selector (0,1,1), así que le ganaba: la casilla de borrar heredaba el ancho del
   viewport, y al ser `position:absolute` SIN ancestro posicionado su bloque contenedor es el
   inicial —o sea que `.tabla-scroll` NO puede recortarla, un `overflow` solo recorta a los
   absolutos cuyo bloque contenedor está dentro— y empujaba la página entera. Medido en
   `/oferta/AC-LNV-126/editar`: el cuerpo daba 2.741 px sobre un viewport de 1.440, y 1.695
   sobre 900. Con la exclusión, 1.440 y 900 clavados.
   EL `:where()` NO ES COSMÉTICO: no suma especificidad, así que este bloque sigue valiendo
   (0,1,1) y no se mueve ni un campo de las tres rejillas. Es el mismo argumento que el
   `input:where(:not([type=file])…)` de más abajo. */
.rejilla-captura input:where(:not([type=checkbox])), .rejilla-captura select {
  width: 100%; padding: 5px 7px;
  border-color: transparent; background: var(--surface-2);
}
.rejilla-captura input.num { text-align: right; font-variant-numeric: tabular-nums; }
.rejilla-captura tr:hover input, .rejilla-captura tr:hover select { border-color: var(--border); }
.rejilla-captura input:focus, .rejilla-captura select:focus {
  border-color: var(--border-fuerte); background: var(--surface);
}
/* ---------------- el símbolo de divisa, y la tacha de quitar una fila ---------------- */

/* EL `$` VA FUERA DEL CAMPO, SIEMPRE. Dentro, `oferta._num` no lo sabe leer y el importe
   entraría como `None`: un renglón sin coste, sin un solo error. Así que la celda es un `flex`
   con el símbolo delante y el campo ocupando el resto — el `width: 100%` de arriba tendría que
   ceder, y por eso el `<input>` pasa a `flex: 1 1 auto` dentro de este contenedor. */
.celda-importe { display: flex; align-items: center; gap: 3px; }
.celda-importe > input { flex: 1 1 auto; width: auto; min-width: 0; }
.prefijo { color: var(--texto-tenue); font-size: var(--t1); flex: 0 0 auto; }

/* LA CASILLA SE ESCONDE PERO NO SE APAGA. Nada de `display:none` ni `visibility:hidden`: las
   dos la sacan del orden de tabulación, y entonces quitar una partida deja de poder hacerse con
   el teclado en la única pantalla que se recorre entera corrigiendo. Es la lección de
   `.marca-plegar`, que era un `<span>` con un `click` por el mismo descuido. */
.marca-borrar { position: absolute; opacity: 0; width: 1px; height: 1px; margin: 0; }
/* La ✕ es el objetivo pulsable, y mide 24 px por eso. Es el MISMO glifo con el que se quita una
   unidad de obra en la ficha de un contrato: un emoji de papelera se pinta distinto en cada
   sistema y en algunos ni siquiera es cuadrado. */
.tacha {
  display: inline-flex; align-items: center; justify-content: center;
  width: 24px; height: 24px; border-radius: var(--radio-s); cursor: pointer;
  color: var(--texto-tenue); font-size: var(--t2); line-height: 1; user-select: none;
}
.tacha:hover { background: var(--surface-2); color: var(--rojo); }
.marca-borrar:focus-visible + .tacha { outline: 2px solid var(--azul); outline-offset: 1px; }
.marca-borrar:checked + .tacha { color: var(--rojo); background: var(--surface-2); }
/* LA FILA MARCADA SE TACHA ENTERA, y hace falta `:has()` porque la casilla vive DENTRO del
   `<tr>` y desde un descendiente no se puede pintar el ancestro de ninguna otra forma. Sin esta
   señal, marcar 4 de 83 filas y bajar a guardar es confiar en la memoria.
   No toca `background`: ahí ya viven `.falta` y `.destacada`, que dicen el ESTADO. */
tr:has(> td .marca-borrar:checked) { opacity: .5; }
tr:has(> td .marca-borrar:checked) input,
tr:has(> td .marca-borrar:checked) select { text-decoration: line-through; }
/* APAGADA: la celda existe, se ve dónde iría y no se teclea. Es la rejilla del PCC, donde el
   Coste de Estudio es siempre 0 porque nadie ofertó esa partida. Sin esta regla un `disabled`
   se pinta EXACTAMENTE igual que un campo vivo —el bloque de arriba no distingue— y la
   pantalla parece rota en vez de decidida. El hermano de `.boton:disabled`, con su misma
   opacidad. */
.rejilla-captura input:disabled, .rejilla-captura select:disabled {
  opacity: .45; cursor: default; background: var(--surface-2);
}
.rejilla-captura tr:hover input:disabled { border-color: transparent; }
/* Lo que calcula el navegador, no lo que se teclea: se ve que es derivado. */
.rejilla-captura .calculado {
  text-align: right; font-variant-numeric: tabular-nums; color: var(--texto-tenue);
  white-space: nowrap;
}
.rejilla-captura tfoot td { border-top: 1.5px solid var(--border-fuerte); }
/* EL `<colgroup>` MANDA SOBRE SUS ANCHOS, y no al revés. Con el layout automático el ancho que
   pide un `<select>` es el de su opción más larga —«BESS.4.12 · TUBERÍA PEAD, PVC, REGISTROS,
   CHAROLA, ACCESORIOS, LUMINARIAS», 73 caracteres desde que la opción dice el `detalle`— así
   que los `<col>` en % pasan a ser una sugerencia y la columna del código se come la de la
   descripción. Fijo, el `<col>` manda y el control recorta su texto él solo, que es lo que hace
   un desplegable nativo.

   ES OPT-IN Y NO VA EN `.rejilla-captura` ENTERA: la matriz de certificaciones lleva esa misma
   clase con ~170 columnas y seis fijas en px, y ahí `fixed` repartiría los anchos de otra
   forma. Y no se pone `data-tabla-id` —que también activaría `fixed`— porque `anchos.js`
   reescribe el `<colgroup>` y pelearía con el de la plantilla. */
.anchos-fijos { table-layout: fixed; }

.bloque-cerrado > summary {
  cursor: pointer; list-style: none; display: flex; align-items: baseline; gap: 8px;
}
.bloque-cerrado > summary::-webkit-details-marker { display: none; }
/* Con el trato de `.tarjeta > h2`, en caja normal desde el 2026-11-05. */
.bloque-cerrado > summary h2 {
  font-size: var(--t3); font-weight: 600; color: var(--texto);
}

/* ---------------- explorador: filtros y orden ---------------- */

/* EL BORRADOR. Quitar los filtros de golpe se busca en un solo momento —cuando la pantalla
   se ha quedado vacía y no se sabe cuál de los nueve la vació— y hasta el 2026-08-07 era un
   «limpiar» gris de 12px al final de una fila de píldoras. Ahora es un botón, y se pinta SOLO
   si hay algo que borrar: un botón permanente que casi siempre no hace nada es ruido. */
.borrar-filtros { font-size: var(--t1); padding: 4px 10px; }

/* «SOLO si hay algo que borrar» dependía de un `hidden` que no escondía nada: el
   `[hidden] { display:none }` es una regla del NAVEGADOR (origen UA) y pierde ante cualquier
   regla de autor, así que el `display:inline-flex` de `.boton` la pisaba y el botón salía
   encima de las 20+ tablas de la app. Misma trampa que `.menu-filtro-borrar[hidden]`.
   Va sobre `.boton` y no sobre `.borrar-filtros`: el defecto es del componente. */
.boton[hidden] { display: none; }

/* ---------------- LOS FILTROS EN LA CABECERA, COMO EN EXCEL (2026-09-28) ----------------
   Un solo patrón para las diecisiete pantallas con filtros: el embudo en el `<th>` de su
   columna, el menú en la capa superior (`[popover]`) y la barra de la tabla con Buscar, los
   filtros sin columna y «Borrar filtros (N)». Las piezas: `templates/_filtros.html`,
   `tabla.filtros()` y `static/embudo.js`. Todo en texto plano: ni una pastilla (`DESIGN.md`).

   EL EMBUDO NO CRECE LA FILA DE 28 px. Sin padding ni borde, y el blanco para el dedo lo da un
   `::after` de 6 px por lado que no ocupa sitio. Hueco en reposo y RELLENO cuando filtra: la
   señal que no es solo color. */
button.embudo {
  position: relative; display: inline-flex; align-items: center; justify-content: center;
  margin-left: 4px; padding: 0; border: 0; background: none; line-height: 0;
  vertical-align: middle; color: var(--texto-suave); opacity: .45; cursor: pointer;
}
button.embudo .icono { width: 11px; height: 11px; }
button.embudo::after { content: ""; position: absolute; inset: -6px; }
button.embudo:hover, button.embudo:focus-visible, button.embudo.puesto,
button.embudo.ordenado { opacity: 1; color: var(--azul); }
th[data-filtrada], th[data-filtrada] a.ordenar { color: var(--azul); }

/* EL MENÚ. Sin JavaScript el navegador lo centra (su `inset:0; margin:auto` de siempre); con
   JavaScript `embudo.js` le pone `.colocado` y lo cuelga bajo su embudo. Por eso aquí no se toca
   ni `inset` ni `margin` fuera de `.colocado`: con `margin:0` sin colocar saldría pegado arriba
   a la izquierda. */
.menu-filtro {
  padding: 6px 0; width: max-content; min-width: 240px; max-width: min(340px, calc(100vw - 16px));
  background: var(--surface); color: var(--texto); font-size: var(--t2);
  border: 1px solid var(--border-fuerte); border-radius: var(--radio-s); box-shadow: var(--sombra);
}
.menu-filtro.colocado { inset: auto; margin: 0; }
/* EL MENÚ DE EXCEL (2026-11-05, las capturas de Alex): ordenar arriba, una raya, «Buscar», la
   lista con casillas y su cuenta a la derecha, y Cancelar / Aceptar abajo. LO QUE SE DESPLAZA ES
   LA LISTA, NO EL MENÚ: con 122 proveedores el pie se quedaría fuera y no habría cómo aceptar. */
.menu-filtro-orden {
  display: flex; flex-direction: column; padding: 0 4px 6px; margin-bottom: 6px;
  border-bottom: 1px solid var(--border);
}
.menu-filtro-orden[hidden] { display: none; }
.menu-filtro .opcion.orden {
  width: 100%; background: none; border: 0; font: inherit; text-align: left; cursor: pointer;
  gap: 8px;
}
.menu-filtro .opcion.orden .icono { width: 13px; height: 13px; color: var(--texto-suave); flex: none; }
.menu-filtro .opcion.orden[aria-current] { color: var(--azul); font-weight: 600; }
.menu-filtro .opcion.orden[aria-current] .icono { color: var(--azul); }
.menu-filtro-borrar {
  display: flex; align-items: center; gap: 6px; padding: 5px 12px; margin: -2px 0 6px;
  color: var(--azul); border-bottom: 1px solid var(--border);
}
.menu-filtro .buscador {
  display: block; width: calc(100% - 16px); margin: 0 8px 6px; padding: 5px 8px;
  font-size: var(--t2);
}
.menu-filtro .buscador[hidden] { display: none; }
.menu-filtro-lista { padding: 0 4px; }
.menu-filtro.multi .menu-filtro-lista {
  margin: 0 8px; padding: 4px 0; max-height: 240px; overflow-y: auto;
  border: 1px solid var(--border); border-radius: var(--radio-s);
}
.menu-filtro .opcion {
  display: flex; align-items: center; gap: 6px; padding: 4px 8px; border-radius: var(--radio-s);
  color: var(--texto); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.menu-filtro .opcion:hover, .menu-filtro .opcion:focus-visible {
  background: var(--surface-2); text-decoration: none;
}
.menu-filtro .opcion[aria-current] { color: var(--azul); font-weight: 600; }
/* LAS CASILLAS: la casilla, el valor —cortado con «…» si no cabe— y, pegado a la derecha y en
   gris, cuántas filas lo tienen. Una rejilla y no un `flex`: así los números se alinean en una
   columna aunque los valores midan distinto. */
.menu-filtro label.opcion {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto; column-gap: 8px;
  border-radius: 0; cursor: pointer;
}
.menu-filtro label.opcion input { margin: 0; flex: none; accent-color: var(--azul); }
.menu-filtro label.opcion .txt { overflow: hidden; text-overflow: ellipsis; }
.menu-filtro label.opcion .n {
  padding-left: 12px; color: var(--texto-tenue); font-size: var(--t1);
  font-variant-numeric: tabular-nums; text-align: right;
}
.menu-filtro label.opcion.todo .txt { font-weight: 600; }
.menu-filtro label.opcion.vacia .txt { color: var(--texto-suave); }
.menu-filtro label.opcion[hidden], .menu-filtro .opcion[hidden] { display: none; }
.menu-filtro-borrar[hidden] { display: none; }
button.menu-filtro-borrar {
  width: 100%; background: none; border: 0; border-bottom: 1px solid var(--border);
  font: inherit; text-align: left; cursor: pointer;
}
/* La marca va en una columna fija, puesta o no: así los valores no bailan al elegir. */
.menu-filtro .marca { flex: none; display: inline-flex; width: 13px; }
.menu-filtro-rango { display: flex; flex-wrap: wrap; gap: 6px; padding: 2px 8px 4px; }
.menu-filtro-rango input { width: 96px; }
.menu-filtro-rango input[type="date"] { width: 140px; }
/* EL PIE: «Cancelar» con contorno y «Aceptar» relleno, a la derecha, como en Excel. No son
   `.boton` —ver `_filtros.html`, `_pie`— pero miden lo mismo que un `.boton.mini`. */
.menu-filtro-pie {
  display: flex; justify-content: flex-end; gap: 8px; padding: 8px 8px 2px;
}
.menu-filtro-cancelar, .menu-filtro-aceptar {
  min-height: 28px; min-width: 84px; padding: 0 14px; border-radius: var(--radio-s);
  font: inherit; font-size: var(--t2); font-weight: 500; cursor: pointer;
}
.menu-filtro-cancelar {
  background: var(--surface); color: var(--texto); border: 1px solid var(--border-fuerte);
}
.menu-filtro-cancelar:hover { background: var(--surface-2); }
.menu-filtro-aceptar {
  background: var(--azul); color: var(--sobre-azul); border: 1px solid var(--azul);
}
.menu-filtro-aceptar:hover { filter: brightness(1.08); }
.menu-filtro-aceptar:disabled { opacity: .5; cursor: not-allowed; filter: none; }
/* LA COLUMNA ORDENADA EN EL NAVEGADOR: la flecha junto al rótulo (`tablas.js`). La de una tabla
   que ordena el servidor la pinta su `a.ordenar`. */
th .orden-flecha { margin-left: 4px; color: var(--azul); font-size: var(--t0); }
th[aria-sort] { color: var(--azul); }

/* LA BARRA: a la izquierda de los iconos de la tabla, que se quedan a la derecha. */
.filtros-barra {
  display: flex; flex: 1 1 auto; flex-wrap: wrap; align-items: center; gap: 4px 12px;
  min-width: 0; font-size: var(--t2);
}
.filtros-barra .buscar { width: 220px; padding: 4px 9px; font-size: var(--t2); }
.filtro-barra {
  display: inline-flex; align-items: center; gap: 2px; white-space: nowrap; color: var(--texto-suave);
}
.filtro-barra > button {
  display: inline-flex; align-items: center; gap: 6px; padding: 3px 2px; border: 0;
  background: none; color: inherit; font: inherit; cursor: pointer;
}
.filtro-barra > button:hover { color: var(--texto); text-decoration: underline; }
.filtro-barra.puesto, .filtro-barra.puesto > button:hover { color: var(--azul); }
.filtro-barra b { font-weight: 600; }
.filtro-barra .flechita {
  width: 5px; height: 5px; opacity: .6;
  border-right: 1.5px solid currentColor; border-bottom: 1.5px solid currentColor;
  transform: translateY(-2px) rotate(45deg);
}
.filtro-barra .quitar {
  display: inline-flex; padding: 3px; border-radius: var(--radio-s); color: inherit; opacity: .7;
}
.filtro-barra .quitar:hover { opacity: 1; background: var(--surface-2); text-decoration: none; }
.filtro-barra .quitar .icono { width: 10px; height: 10px; }
.filtros-barra .borrar-filtros {
  display: inline-flex; align-items: center; gap: 5px; padding: 3px 4px; color: var(--azul);
  font-size: var(--t2);
}
.filtros-barra .borrar-filtros[hidden] { display: none; }
/* El de `tablas.js` es un `<button>` (no recarga la pagina): el mismo aspecto que el enlace. */
.filtros-barra button.borrar-filtros { background: none; border: 0; font: inherit; cursor: pointer; }
.filtros-barra .borrar-filtros .n { color: var(--texto-suave); }

/* EN PAPEL, LO FILTRADO SE DICE Y LO DEMÁS SE VA. Sin esto la hoja impresa no diría con qué
   filtro salió, y sus totales parecerían los de la obra entera. */
@media print {
  button.embudo, .menu-filtro, .filtro-barra:not(.puesto), .filtro-barra .quitar,
  .filtros-barra .borrar-filtros, .filtros-barra .buscar:placeholder-shown { display: none !important; }
  .filtros-barra .buscar { border: 0; padding: 0; }
}


/* La cabecera ordenable es un <a>: sin JS también ordena, porque el orden lo hace el
   servidor sobre el filtro entero y no sobre las 200 filas que se ven. */
th a.ordenar { color: inherit; display: inline-flex; align-items: center; gap: 4px; }
th a.ordenar:hover { color: var(--azul); text-decoration: none; }
th a.ordenar .flecha { opacity: .25; font-size: var(--t0); }
th a.ordenar.activo { color: var(--azul); }
th a.ordenar.activo .flecha { opacity: 1; }

.columnas-menu { position: relative; }
.columnas-menu > summary {
  list-style: none; cursor: pointer; display: inline-flex; align-items: center; gap: 6px;
}
.columnas-menu > summary::-webkit-details-marker { display: none; }
/* EL MENU SALE DE SU BOTON (2026-09-26): crece desde la esquina de arriba a la derecha, que es
   donde esta «Columnas». `columnas.js` lo crea en cada apertura, asi que el keyframe corre solo.
   SOLO ESTE MENU: el de un filtro (`.menu-filtro`) se abre cientos de veces al dia y ahi
   cualquier animacion es espera. Cerrar no se anima. */
.columnas-menu .popover {
  right: 0; left: auto; top: calc(100% + 6px); min-width: 210px; position: absolute;
  transform-origin: top right; animation: menu-entra 150ms var(--ease-out);
}
@keyframes menu-entra { from { opacity: 0; transform: scale(.97); } }
.columnas-menu .popover .pie { border-top: 1px solid var(--border); margin-top: 6px; padding-top: 6px; }

.flash { padding: 10px 14px; border-radius: var(--radio-s); margin-bottom: 14px; }
.flash.error { background: var(--rojo-bg); color: var(--rojo); }
.flash.ok { background: var(--verde-bg); color: var(--verde); }
.flash.aviso { background: var(--ambar-bg); color: var(--ambar); }

/* La fila a la que apunta el ancla al volver del coste de una partida. En un PCC de 87
   partidas, llegar al sitio y no saber cuál era es llegar a medias. Se apaga sola: pasados
   unos segundos ya no dice nada y se queda estorbando la lectura. */
tbody tr.destacada, tbody tr.destacada:hover { background: var(--azul-bg); }
tbody tr.destacada { transition: background 1.2s ease 1.8s; }
tbody tr.destacada.apagada { background: transparent; }

/* Fila con un hueco que bloquea. Sobre el hover de tabla, que si no la tapa. */
tbody tr.falta, tbody tr.falta:hover { background: var(--rojo-bg); }
tbody tr.falta td { border-bottom-color: var(--border-fuerte); }

/* Rejilla de tasas: una celda vacía ES la respuesta a "qué falta", así que se ve.
   Roja = falta y hace falta. Tenue = falta y esa semana todavía no la pide nadie. */
td.celda-tasa { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
td.celda-tasa.falta { background: var(--rojo-bg); }
td.celda-tasa input { width: 88px; padding: 4px 6px; font-size: var(--t1); }

/* ---------------- login ---------------- */

.login-wrap { display: flex; min-height: 100vh; align-items: center; justify-content: center; padding: 20px; }
.login-caja { width: 100%; max-width: 340px; }
.login-caja .tarjeta { padding: 24px; }
.login-caja label { display: block; font-size: var(--t1); color: var(--texto-suave); margin: 10px 0 4px; }
.login-caja input { width: 100%; }
.login-caja .boton { width: 100%; justify-content: center; margin-top: 16px; }

pre.log {
  background: var(--surface-2); border: 1px solid var(--border);
  border-radius: var(--radio-s); padding: 12px;
  font-family: var(--mono); font-size: var(--t1); line-height: 1.45;
  overflow-x: auto; white-space: pre; margin: 0;
}

/* ---------------- responsive ---------------- */

@media (max-width: 900px) {
  .nav { position: static; width: auto; border-right: none; border-bottom: 1px solid var(--border); }
  .contenido { margin-left: 0; padding: 16px; }

  /* EL NAV SE PLIEGA. Hasta el 2026-08-12 los 7 grupos se pintaban enteros ENCIMA del
     contenido, así que para llegar a cualquier pantalla había que scrollear el menú entero.
     El botón lo crea `nav.js` y solo existe aquí abajo; sin JavaScript no hay `.nav-toggle`,
     esta regla no encuentra a quién esconder y la barra se ve entera — la pantalla de hoy. */
  .nav-toggle ~ .nav { display: none; }
  .nav-toggle ~ .nav.abierto { display: block; }
  .nav-toggle { margin: 12px 0 0 12px; }
}

/* SEGUNDO BREAKPOINT: en un teléfono la rejilla de tarjetas no puede seguir siendo
   `auto-fit minmax(280px)` —dos columnas de 160px parten cada cifra en tres líneas— y los
   botones del encabezado no caben al lado del título. */
@media (max-width: 600px) {
  .rejilla, .rejilla.compacta { grid-template-columns: 1fr; }
  .encabezado { flex-direction: column; align-items: stretch; gap: 10px; }
  .encabezado > .encabezado-fin { margin-left: 0; text-align: left; }
  .acciones-cabecera { flex-wrap: wrap; }
}

@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; }
}

/* ============================================================
   LA CURVA DE OBRA (/plan)
   El SVG lo genera el servidor y NO trae un solo color: las series se pintan desde
   aqui con los tokens, asi el modo oscuro funciona sin tocar la plantilla.
   ============================================================ */

/* Una cifra que es buena o mala noticia. Sustituye al `style="color:var(--rojo)"`
   suelto de _margen.html: el token es el mismo, el sitio donde vive es uno. */
.kpi.malo  { color: var(--rojo); }
.kpi.bueno { color: var(--verde); }
td.num.malo  { color: var(--rojo); }
td.num.bueno { color: var(--verde); }
/* La cifra de una ficha (`.campo > .num`) cuando es mala noticia: «Pendiente tras el propuesto»
   del comparativo (2026-10-17). */
.campo > .num.malo { color: var(--rojo); }
/* Y LA GLOSA DE DEBAJO DE UN NUMERO, que es donde vive la desviacion desde que se puso bajo
   su Coste Actualizado —el patron de `linea_margen`—. Sin estas dos, `class="pequeno malo"`
   se pintaba del color del texto normal y la desviacion de /plan no salia en rojo: no falla,
   simplemente no avisa, que es el peor modo de fallo de una senal. */
.pequeno.malo  { color: var(--rojo); }
.pequeno.bueno { color: var(--verde); }

.curva { margin: var(--e3) 0 0; }
.curva svg { width: 100%; height: auto; display: block; overflow: visible; }

/* `vector-effect` mantiene el grosor del trazo aunque el SVG escale: sin el, en una
   pantalla ancha las lineas se engordan y en el movil desaparecen. */
/* LA CURVA PEQUEÑA de la cartera. Comparte TODOS los selectores de color con la grande, y no
   por ahorrar lineas: dos bloques de color divergen, y entonces la misma serie sale de un
   color en `/plan` y de otro en `/plan/<obra>` sin que nadie lo vea. Lo unico suyo es el
   tamano, que es fijo —240x48, lo que mide la celda— porque una curva de 22 semanas estirada
   al ancho de la columna diria una pendiente distinta en cada fila. */
.curva-mini { width: 240px; height: 48px; display: block; overflow: visible; }

.curva polyline, .curva-mini polyline {
  fill: none; stroke-width: 2; stroke-linejoin: round; stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}
.curva .rejilla line { stroke: var(--border); stroke-width: 1; vector-effect: non-scaling-stroke; }
.curva .rejilla text,
.curva .ejes text {
  fill: var(--texto-tenue); font-size: var(--t0); font-family: var(--mono);
  font-variant-numeric: tabular-nums;
}

/* LA BASE VA PUNTEADA Y EN GRIS: es la referencia, no reclama nada. Que sea discontinua
   la distingue del pronostico incluso impresa en blanco y negro, que es donde el color
   deja de servir. */
.curva .s-base, .curva-mini .s-base { stroke: var(--gris); stroke-dasharray: 5 4; }
/* EL TRAZO, NO SOLO EL COLOR. `--ambar` y `--verde` estan a 1,06:1 de contraste: no es que se
   parezcan, es que tienen la MISMA luminancia. Asi que «el pronostico se pasa» y «cabe» —que
   es la unica senal por la que existe esta grafica— no se distinguian ni para un deuterano,
   ni en escala de grises, NI IMPRESAS. Y esto se imprime: es la razon declarada de que la
   curva sea un SVG del servidor y no una libreria. Punteada = se pasa. */
.curva .s-pronostico.se-pasa, .curva-mini .s-pronostico.se-pasa {
  stroke: var(--ambar); stroke-dasharray: 2 3;
}
.curva .s-pronostico.cabe,    .curva-mini .s-pronostico.cabe    { stroke: var(--verde); }
.curva .s-incurrido, .curva-mini .s-incurrido { stroke: var(--azul); stroke-width: 2.5; }

.leyenda {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--e1) var(--e3);
  margin-top: var(--e2); font-size: var(--t1); color: var(--texto-suave);
}
.leyenda .marca { width: 18px; height: 0; border-top: 2px solid var(--gris); }
.leyenda .marca.base { border-top-style: dashed; }
/* La leyenda repite el TRAZO de su serie, no solo su color: si dice «pronostico» con una
   linea continua y la grafica la pinta punteada, la leyenda deja de servir para leerla. */
.leyenda .marca.pronostico.se-pasa { border-top-color: var(--ambar); border-top-style: dotted; }
.leyenda .marca.pronostico.cabe    { border-top-color: var(--verde); }
.leyenda .marca.incurrido { border-top-color: var(--azul); border-top-width: 3px; }

/* Se imprime, y por eso no se metio una libreria de graficas: esta pantalla acaba
   proyectada o en papel en una reunion de obra. */
@media print {
  .curva svg { break-inside: avoid; }
  .curva .rejilla line { stroke: var(--border-fuerte); }
}

/* ============================================================
   EL CORTE SEMANAL (/plan/<obra>/reporte)
   Programado contra real, semana a semana. Acaba proyectado o en papel.
   ============================================================ */

/* La semana en curso, con el MISMO azul que la marca en la rejilla de programar: en las dos
   pantallas contesta lo mismo —donde estamos— y con dos colores serian dos cosas. `.chip.AZUL`
   NO se redeclara aquí: estaba copiada literal de la línea del bloque de semáforos, o sea dos
   sitios que tocar el día que ese azul cambie. Vive con sus cuatro hermanas y punto. */
tr.fila-hoy > td { background: var(--azul-bg); }
tr.fila-hoy > td:first-child { box-shadow: inset 2px 0 0 var(--azul); }

/* Al imprimir se va lo que no es el reporte. La tabla NO se parte por una fila: un corte
   semanal partido a la mitad de una semana se lee mal en papel. */
@media print {
  table[data-tabla-id="plan-reporte"] tr { break-inside: avoid; }
  tr.fila-hoy > td { background: transparent; }
  tr.fila-hoy > td:first-child { box-shadow: none; }
}

/* ============================================================
   LA REJILLA DE PROGRAMACION (/plan/<obra>/programar)
   Se pinta arrastrando el raton. Una celda = una semana de una unidad de obra.
   ============================================================ */

/* PEGADA ARRIBA (2026-09-25): con 90 filas «Guardar el plan» se quedaba fuera de la vista al
   bajar a pintar la ultima. Lleva fondo porque la rejilla pasa por debajo. `z-index` por
   encima de las columnas `sticky` de la rejilla, que tambien se apilan. Eran dos reglas con el
   mismo selector hasta el 2026-09-27; ahora una. */
.barra-plan {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--e3); margin-bottom: var(--e3);
  position: sticky; top: env(safe-area-inset-top, 0px); z-index: 6;
  background: var(--surface); padding: var(--e2) 0;
}
.barra-plan > .barra-tabla { margin-bottom: 0; }
.barra-plan-cuenta { flex: 1; display: flex; flex-wrap: wrap; align-items: baseline;
                     column-gap: var(--e1); }
/* LO QUE EL SERVIDOR NO CONTESTO (2026-09-27). Rojo porque bloquea: con una fila sin respuesta
   el plan no se guarda. Texto plano, sin caja, y el «vuelve a intentarlo» es un enlace que
   actua — por eso es un `<button>` sin cromo. */
.aviso-reparto { flex-basis: 100%; font-size: var(--t1); color: var(--rojo); }
.aviso-reparto[hidden] { display: none; }
.aviso-reparto .reintentar {
  background: none; border: 0; padding: 0; font: inherit; color: var(--azul);
  text-decoration: underline; cursor: pointer;
}

/* LA MISMA BARRA PEGADA, PARA CUALQUIER REJILLA LARGA (2026-09-25). La del plan resolvio lo de
   «Guardar» fuera de la vista; ocho rejillas mas tenian el mismo defecto (oferta, produccion,
   contrato, comparativo, imputaciones, tasas, permisos). Una clase con nombre y no ocho copias
   de `.barra-plan`: la de la rejilla del plan lleva ademas la cuenta y el pintado. */
.barra-pegada {
  position: sticky; top: env(safe-area-inset-top, 0px); z-index: 6;
  display: flex; align-items: center; gap: var(--e2, 8px); flex-wrap: wrap;
  background: var(--surface); padding: var(--e2, 8px) 0; margin-bottom: var(--e2, 8px);
}
.barra-pegada > .barra-tabla { margin-bottom: 0; }
.barra-pegada-fin { margin-left: auto; display: flex; align-items: center; gap: var(--e1, 6px); }
@media print { .barra-pegada { position: static; } }

/* La rejilla scrollea EN HORIZONTAL dentro de su caja y nunca la pagina: 40 columnas de
   semana no caben, y una pagina que se mueve entera al arrastrar hace imposible pintar.
   El `position: relative` es el ancla del editor de celda: dentro del contenedor que
   scrollea, el editor se mueve CON la rejilla; colgado del `<body>` se quedaria quieto
   sobre la celda equivocada en cuanto alguien tocara la barra.

   `overflow-y: visible` ES LO QUE SE PIDE Y `auto` LO QUE SE OBTIENE, y aqui eso NO es un
   detalle: el otro eje scrollea, asi que el CSS lo computa a `auto` —el mismo hecho medido
   el 2026-08-15 y escrito arriba, en el bloque de `.tabla-scroll`—. Consecuencia: un hijo
   absoluto que asome por abajo vuelve scrolleable este contenedor en vertical. Por eso el
   editor de celda mide el hueco y se ancla por arriba cuando no cabe, en
   `plan.js:colocarEditor`, y no hay ningun `padding` aqui abajo fingiendo sitio. */
/* `container-type` (2026-09-27): la rejilla decide su forma por el ancho QUE TIENE, no por el
   de la ventana. El nav se pliega a 900px y la tarjeta cambia de ancho sin que la ventana lo
   haga; ver la `@container` de abajo. */
.rejilla-scroll { overflow-x: auto; overflow-y: visible; position: relative;
                  container-type: inline-size; }

.rejilla-plan { border-collapse: collapse; width: max-content; min-width: 100%; }
.rejilla-plan th, .rejilla-plan td {
  border: 1px solid var(--border); padding: 0; font-size: var(--t1);
}
.rejilla-plan .col-nombre { min-width: 260px; max-width: 260px; padding: 3px var(--e2);
                            white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* ANCHO CERRADO Y NO SOLO `min-width`: la tabla es `width: max-content`, asi que sin tope la
   columna crece con su contenido y los `left` acumulados de aqui abajo dejan de ser ciertos.
   Es la misma nota que `.matriz-fija .fN` escribe para el bloque fijo de un contrato. */
.rejilla-plan .col-imp    { width: 108px; min-width: 108px; max-width: 108px;
                            padding: 3px var(--e2);
                            font-variant-numeric: tabular-nums; }
.rejilla-plan .col-ajuste { width: 26px; min-width: 26px; max-width: 26px; text-align: center; }

/* LAS CUATRO PRIMERAS COLUMNAS SE QUEDAN QUIETAS al scrollear —el nombre, el Coste Objetivo,
   lo Repartido y el boton de ajustar—: con 40 semanas la rejilla mide ~3.400px, y sin esto no
   se sabe que fila se esta pintando NI contra que presupuesto en cuanto te mueves a la derecha.
   Repartir sin ver el objetivo ni lo que llevas colocado es trabajar a ciegas, y el boton que
   coloca el resto se iba con ellos.

   HASTA EL 2026-09-16 ESTO LO DECIA EL COMENTARIO Y LO HACIA UNA SOLA COLUMNA: la prosa
   afirmaba tres y solo `.col-nombre` llevaba `position: sticky`. Lo ancla ahora un grep del
   paso 10 de `plan._self_test`, porque un comentario no sujeta nada.

   POR ESO HACEN FALTA ANCHOS FIJOS EN PX, que es la misma nota de `.matriz-fija .fN`: un `left`
   acumulado necesita saber cuanto mide lo que tiene delante, y con anchos automaticos ese
   numero no existe hasta despues del layout. Todo es `border-box`, asi que el ancho declarado
   es el que ocupa: 260 + 108 + 108 + 26 = 502px quietos. PRECIO DECLARADO: en una pantalla de
   1.440px se ven ~11 semanas en vez de ~15.

   El fondo sale del NIVEL de la fila (`--nivel-bg`), no de un literal: si no, la columna que
   se queda quieta se pintaría de otro color que el resto de su propia fila en cuanto empieza
   el scroll horizontal. El fallback es para una fila sin `data-nivel`. */
.rejilla-plan .col-nombre,
.rejilla-plan .col-obj,
.rejilla-plan .col-rep,
.rejilla-plan .col-ajuste { position: sticky; z-index: 2;
                            background: var(--nivel-bg, var(--surface)); }
.rejilla-plan .col-nombre { left: 0; }
.rejilla-plan .col-obj    { left: 260px; }
.rejilla-plan .col-rep    { left: 368px; }
/* El borde marca donde acaba el area fija: sin el, al scrollear parece que las semanas se
   meten por debajo del importe. Es el mismo remate que `.matriz-fija .col-hito`. */
.rejilla-plan .col-ajuste { left: 476px; border-right: 1.5px solid var(--border-fuerte); }
/* La rejilla es la matriz con las columnas más estrechas de la app: el escalón de 13/28 px del
   componente de nivel caería en mitad del nombre. Aquí la sangría es 10/12/16, así que el rail
   la acompaña. */
.rejilla-plan tr[data-nivel="2"] { --rail-x: 5px; }
.rejilla-plan tr[data-nivel="3"] { --rail-x: 9px; }

/* LA HOJA DE PRODUCCION: mismo ajuste y por el mismo motivo. Su primera columna es el código de
   la partida —un correlativo de una o dos cifras— y arranca en el `padding` de 10 px de la
   celda, así que el escalón de 13 px del componente de nivel dibujaba el rail JUSTO ENCIMA del
   dígito. Salió mirando la pantalla: los números del 1 al 9 se leían tachados.
   El rail se pega al borde y el código se aparta lo justo para dejarlo pasar. */
.tabla-produccion tr[data-nivel="2"] { --rail-x: 3px; }
.tabla-produccion tr[data-nivel="2"] td:first-child { padding-left: 16px; }
.rejilla-plan thead th { position: sticky; top: 0; z-index: 3; background: var(--surface-2); }
/* La cabecera de las cuatro quietas cruza los dos ejes, asi que va por encima de todo. Y
   necesita SU fondo: las reglas de arriba le ganan por especificidad a `thead th` y le meterian
   el `--nivel-bg`, que en `<thead>` no existe. */
.rejilla-plan thead .col-nombre,
.rejilla-plan thead .col-obj,
.rejilla-plan thead .col-rep,
.rejilla-plan thead .col-ajuste { background: var(--surface-2); z-index: 4; }

/* LA COLUMNA PASO DE 22px A 72px EL 2026-08-24, y es lo que cuesta que el importe se lea
   DENTRO de la celda. Hasta ese día vivía solo en el `title`, o sea que leer un calendario
   pedía pasar el ratón celda a celda. 72px es `1,234,567.89` en tabulares de 11px con su
   padding; por debajo, los importes de siete cifras de `TR-LNV-102` se cortan.

   PRECIO DECLARADO: con 44 semanas la rejilla mide ~3.400px en vez de ~1.200. Scrollea dentro
   de `.rejilla-scroll` y las cuatro primeras columnas siguen `sticky`, así que se sigue sabiendo
   qué fila se está pintando y contra qué presupuesto. Es el mismo trato que la matriz de `/estimaciones/contrato/<id>`.

   LOS DOS NIVELES SE MANTIENEN, pero por ENCIMA del suelo: la semana en `--t1` y su año en
   `--t0`. Igualar los dos a `--t0` habría respetado el suelo y perdido la jerarquía; bajar el
   año a 8px es lo que hacía que no se leyera. */
.rejilla-plan .col-sem { width: 72px; min-width: 72px; text-align: center;
                         font-family: var(--mono); font-size: var(--t1); line-height: 1.1;
                         color: var(--texto-tenue); padding: 2px 0; }
/* SIN `opacity` DESDE EL 2026-09-27: el año quedaba en 2,5:1 de contraste en los dos temas,
   por debajo del suelo de la app. La jerarquía la marca el tamaño. */
.rejilla-plan .col-sem .sem-anio { display: block; font-size: var(--t0); }
/* La semana en curso: sin ella, en 40 columnas no hay forma de saber que es pasado y que
   es plan. */
.rejilla-plan .col-sem.es-hoy { color: var(--azul); font-weight: 600; }
.rejilla-plan .col-sem.es-hoy::after {
  content: ''; display: block; height: 2px; background: var(--azul); margin-top: 2px;
}

.rejilla-plan .celda, .rejilla-plan .celda-resumen { height: 20px; cursor: crosshair; }
/* LA CELDA DE UNA PARTIDA LLEVA SU IMPORTE DENTRO desde el 2026-08-24, así que necesita
   padding, tabulares y alineación a la derecha —el dinero se lee por la coma—. Las de bloque
   y código no llevan texto: ahí siguen siendo bloques de color y sus reglas de abajo ganan. */
.rejilla-plan .fila-partida .celda {
  padding: 2px var(--e1); text-align: right; font-size: var(--t0); line-height: 1.4;
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
/* EL PASO DEL RATON ES UN FILETE Y NO UN FONDO (2026-09-27). Con `azul-bg` una celda vacía
   bajo el ratón se veía igual que una pintada, así que no se sabía qué iba a hacer el clic.
   El foco del teclado va por la misma señal, con el grosor del foco del resto de la app. */
.rejilla-plan .celda:hover { box-shadow: inset 0 0 0 1px var(--azul); }
.rejilla-plan .celda:focus { outline: none; }
.rejilla-plan .celda:focus-visible { outline: 2px solid var(--azul); outline-offset: -2px; }
/* EL AZUL SOLIDO SE FUE CON EL NUMERO DENTRO. Un `--azul` saturado con la cifra encima no se
   lee en ninguno de los dos temas, y con el importe visible el bloque de color ya no es lo que
   dice que la celda está pintada: lo dice el número. Queda el fondo suave, que sigue dibujando
   la barra del calendario de un vistazo sin pelearse con el texto. */
.rejilla-plan .celda.pintada { background: var(--azul-bg); }
/* LA QUE TECLEO UNA PERSONA. Se marca con un filete a la IZQUIERDA y nunca con otro color:
   azul, verde, ámbar y rojo ya significan estado en esta app, y una celda fijada no es un
   estado mejor ni peor — es una que el reparto no puede pisar. */
.rejilla-plan .celda.fijada { box-shadow: inset 3px 0 0 var(--azul); font-weight: 600; }
.rejilla-plan .celda.fijada:hover { box-shadow: inset 3px 0 0 var(--azul), inset 0 0 0 1px var(--azul); }
/* PINTADA PERO SIN IMPORTE: son las 48 partidas sin `coste_objetivo` capturado. Se distingue
   con rayas y no con otro color, porque no es un estado peor: es un dato que falta. */
.rejilla-plan .celda.pintada.sin-importe {
  background: repeating-linear-gradient(45deg, var(--ambar) 0 3px, transparent 3px 6px);
}
/* PINTADA Y SIN RESPUESTA DEL SERVIDOR (2026-09-27). No son las rayas de «sin objetivo»: es un
   importe que no llegó. Filete rojo discontinuo, que es lo que señala el aviso de la barra. */
.rejilla-plan .celda.pintada.pendiente { outline: 1px dashed var(--rojo); outline-offset: -3px; }
/* La fila del codigo: llena si TODAS sus partidas tienen esa semana, a medias si solo
   algunas. Un solo color diria que estan todas cuando no lo estan. */
.rejilla-plan .fila-codigo .celda.pintada,
.rejilla-plan .fila-bloque .celda-resumen.pintada { background: var(--azul); opacity: .55; }
.rejilla-plan .fila-codigo .celda.parcial,
.rejilla-plan .fila-bloque .celda-resumen.parcial {
  background: repeating-linear-gradient(45deg, var(--azul) 0 2px, transparent 2px 5px);
  opacity: 1;
}

/* El fondo de las tres lo pone `[data-nivel]`; aquí solo queda lo que es de esta rejilla. */
.rejilla-plan .fila-bloque { font-weight: 600; }
.rejilla-plan .fila-codigo .col-nombre { padding-left: var(--e3); }
.rejilla-plan .fila-partida .col-nombre { padding-left: var(--e4); }

/* ES UN <button> Y NO UN <span> desde el 2026-08-12: como `<span>` con un `click` colgado,
   plegar un bloque o un código era INALCANZABLE POR TECLADO —no recibe foco, así que no hay
   forma de llegar a él con Tab ni de activarlo con Enter— en la única pantalla de la app que se
   recorre entera capturando. El `.candado` de esta misma rejilla ya estaba bien hecho
   (`<button type="button" aria-pressed>`), así que aquí se copió y no se inventó nada. Aquel
   botón se retiró el 2026-08-24 con el modo que representaba; su heredero, `.ajuste`, sigue
   siendo el mismo patrón.

   Las cuatro primeras declaraciones son para que un botón no se vea como un botón: sin ellas
   trae borde, fondo y la fuente del sistema, y la columna del nombre cambiaría de ancho. */
.marca-plegar {
  display: inline-flex; align-items: center; cursor: pointer; user-select: none;
  color: var(--texto-tenue);
  background: none; border: 0; padding: 0; font: inherit; line-height: inherit;
  text-align: left;
}

/* EL IMPORTE DE UNA CELDA SE ESCRIBE FLOTANDO SOBRE ELLA, no dentro del `<td>`. La columna
   de una semana mide 22 px: un `<input>` ahi dentro o no se lee o ensancha la columna, y una
   columna mas ancha que las demas deja de ser un calendario. Sustituye al `window.prompt` que
   habia aqui, que era el unico dialogo del navegador que quedaba en toda la app —el argumento
   es el mismo que `catalogos.py` escribia para el `confirm()` de quitar un mapeo, retirado el
   2026-08-24: hoy no queda ni un dialogo del navegador en toda la app—. */
/* 176 px es lo que mide `CD-FV.5.12-007 · 2026-W15` en la mono de 10 px. Con menos el titulo
   se corta justo por la semana, que es la mitad de lo que la caja tiene que decir. */
.editor-celda {
  position: absolute; z-index: 6; width: 176px; padding: var(--e1);
  background: var(--surface); border: 1px solid var(--azul);
  border-radius: var(--radio-s); box-shadow: var(--sombra);
}
.editor-celda .titulo {
  display: block; font-size: var(--t0); line-height: 1.3; color: var(--texto-tenue);
  font-family: var(--mono); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  margin-bottom: 3px;
}
.editor-celda input {
  width: 100%; padding: 3px 5px; font-size: var(--t1); text-align: right;
  font-variant-numeric: tabular-nums;
}
.editor-celda .pista { display: block; font-size: var(--t0); color: var(--texto-tenue); margin-top: 3px; }

/* AJUSTAR EN LA ULTIMA SEMANA. Sustituye al `.candado`, que se retiró el 2026-08-24 con el
   modo que representaba. Solo se pinta cuando la fila no cuadra —lo esconde `plan.js` con
   `hidden`— así que aquí no hay estado que dibujar: o está o no está. */
.ajuste {
  border: 0; background: none; cursor: pointer; line-height: 1;
  padding: 2px; color: var(--ambar);
}
/* El icono `al_final()` sustituye al glifo `⇥` desde el 2026-09-27. */
.ajuste .icono { display: block; width: 14px; height: 14px; }
.ajuste:hover { color: var(--texto); }
/* Misma trampa que `.boton[hidden]`: el `[hidden] { display:none }` es una regla del
   NAVEGADOR y pierde ante cualquier regla de autor. Aquí no hay ninguna que lo pise hoy, y se
   declara igual para que el día que la haya el botón no se pinte siempre. */
.ajuste[hidden] { display: none; }

/* LO QUE FALTA POR REPARTIR VA DEBAJO DE SU NUMERO, que es el patrón de `linea_margen` y el de
   la desviación del PCC: pegado al número del que es la resta, y no en una columna propia.
   `--e4` de `.col-imp` no da para dos líneas, así que la celda se alinea arriba. */
.rejilla-plan .celda-programado { text-align: right; vertical-align: top; }
/* AMBAR DESDE EL 2026-09-27, como el botón de ajustar y el aviso de la barra: «sin repartir»
   es revisar, no bloqueo. Hasta ese día la cifra salía roja y su botón ámbar. */
.rejilla-plan .celda-programado.descuadra .cifra { color: var(--ambar); }
.rejilla-plan .falta-repartir { display: block; font-size: var(--t0); line-height: 1.2;
                                color: var(--ambar); }
.rejilla-plan .falta-repartir[hidden] { display: none; }

/* Nada de esto se imprime: es una pantalla de captura, no un informe. */
/* LA REJILLA ESTRECHA (2026-09-27, decisión de Alex). Las cuatro columnas quietas miden
   502px: en un portátil de 13" la rejilla enseñaba dos o tres semanas. Por debajo de 1.000px
   de rejilla, Coste Objetivo y Repartido bajan a un segundo renglón del nombre y solo el nombre
   y el ajuste se quedan quietos (286px). Es una rejilla de CAPTURA, así que la fila puede
   crecer: la excepción a la fila de 28px que ya tienen las rejillas de captura. */
.rejilla-plan .bajo-nombre { display: none; }
@container (max-width: 1000px) {
  .rejilla-plan .col-obj, .rejilla-plan .col-rep { display: none; }
  .rejilla-plan .col-ajuste { left: 260px; }
  /* En la sans y no en la mono: con Fira Code «$1,234,567.89 · repartido $1,234,567.89» no cabe
     en los 260px y la fila se iba a tres renglones. Tabulares igual, que es lo que alinea. */
  .rejilla-plan .bajo-nombre { display: block; white-space: normal;
                               font-variant-numeric: tabular-nums; color: var(--texto-suave); }
}

/* LO QUE ERA `style=` EN LAS PANTALLAS DEL PLAN, en clase desde el 2026-09-27 (la Regla del
   Literal Cero). */
form.en-linea { display: inline; }
.lista-faltan { max-height: 40vh; overflow: auto; margin: 0 0 var(--e2); padding-left: var(--e4); }
.tras-cifras { margin-top: var(--e3); }
/* «Sin repartir» en la cartera del plan: el ámbar de revisar en una celda de cifra. */
td.num.revisar { color: var(--ambar); }

@media print { .rejilla-scroll, .barra-plan { display: none; } }

/* ---------------------------------------------------------------- la prosa del manual
   LO DE DENTRO LO ESCRIBE MARKDOWN Y NO LLEVA NI UNA CLASE DEL SISTEMA (2026-09-10), asi que
   hay que estilizar etiquetas a secas — `h2`, `table`, `blockquote`, `img`—. Va TODO acotado a
   `.prosa-manual` por eso: sin el prefijo, estas reglas alcanzarian a media app.

   LA CAPTURA LLEVA `max-width:100%`, que es lo unico que impide que una imagen de 1100 px
   desborde el cuerpo a 900 px de ancho. Es la regla que ya paga `_base.html` con el logo. */
.prosa-manual > :first-child { margin-top: 0; }
/* LOS TAMANOS SALEN DE `--t*` Y NO DE `--e*`. Hasta el 2026-09-16 estas dos reglas pedian
   `var(--e3)` y `var(--e2)`, que son la escala del ESPACIADO: dejaban el `h1` en 12 px y el
   `h2` en 9 px, o sea los dos encabezados MAS PEQUENOS que el parrafo que presentan. El 9 px
   tipografico es ademas el hueco que §8 de MASTER.md da por cerrado.

   Y el `h2` va sin versalitas A PROPOSITO: hasta el 2026-11-05 `.tarjeta > h2` le ponia
   mayusculas, gris suave y letter-spacing porque ahi rotula una tarjeta, pero esto es un
   documento y su `h2` abre una seccion. Desde ese dia la tarjeta va en caja normal y el
   `text-transform: none` de abajo es solo el cinturon. */
.prosa-manual h1 { font-size: var(--t4); margin: 18px 0 8px; }
.prosa-manual h2 {
  font-size: var(--t3); margin: 18px 0 6px;
  text-transform: none; letter-spacing: normal; color: var(--texto);
}
.prosa-manual p, .prosa-manual li { line-height: 1.55; }
.prosa-manual ul, .prosa-manual ol { padding-left: 20px; }
.prosa-manual img {
  max-width: 100%; height: auto; display: block;
  margin: 12px 0; border: 1px solid var(--border); border-radius: 6px;
}
/* EL PIE DE LA CAPTURA ES SU `alt`, y se pinta debajo con CSS en vez de escribirlo dos veces en
   el Markdown: asi la leyenda es la misma que lee quien no ve la imagen. */
.prosa-manual img + em, .prosa-manual figcaption { color: var(--texto-tenue); font-size: var(--t1); }
/* UNA CONSIDERACION ES UN `>` DE MARKDOWN, y se pinta como el `.aviso` del sistema sin serlo:
   dentro de la prosa no hay forma de ponerle una clase. */
.prosa-manual blockquote {
  margin: 12px 0; padding: 8px 12px;
  border-left: 3px solid var(--azul); background: var(--surface-2); border-radius: 0 6px 6px 0;
}
.prosa-manual blockquote > :first-child { margin-top: 0; }
.prosa-manual blockquote > :last-child { margin-bottom: 0; }
/* UNA TABLA DE UNA GUIA SCROLLEA SOLA, Y EL CUERPO NUNCA (2026-09-11). `markdown` emite un
   `<table>` pelado —no hay `.tabla-scroll` que ponerle, porque el marcado lo escribe la
   libreria— asi que el contenedor es la tabla misma: `display: block` le da el `overflow` que
   un `<table>` no tiene. Salio MIRANDOLO: la tabla de tres columnas de `obra_equipo` hacia
   scrollear el CUERPO a 900 px, que es lo que la regla de este repo prohibe. */
.prosa-manual table {
  border-collapse: collapse; margin: 12px 0;
  display: block; overflow-x: auto; max-width: 100%;
}
.prosa-manual th, .prosa-manual td {
  border: 1px solid var(--border); padding: 5px 9px; text-align: left; vertical-align: top;
}
.prosa-manual th { background: var(--surface-2); }
.prosa-manual code { background: var(--surface-2); padding: 1px 5px; border-radius: 4px; }

/* ================= LAS TRES VISTAS DE UNA TABLA, Y LA CELDA DE PAPELES (2026-09-21) =========

   Nacen del diagnostico del dueno sobre `/remesas-pago/`: «las tablas y los modulos como los
   tienes ahora estan complicadisimos». Aquella pantalla servia 23 columnas —la que mas de las
   72— y tres `<input type="file">` por fila.

   LOS DOS COMPONENTES SON GENERICOS A PROPOSITO y no llevan el nombre de esa pantalla: la
   primera tabla grande que los pida despues los reusa sin copiar CSS, que es la regla de
   `data-nivel` y la de `.barra-tabla`. */

/* LA TIRA DE VISTAS se fue el 2026-09-28: las vistas de `/remesas-pago/` van en `.pestanas`,
   el mismo carril que las caras de una obra y las secciones del catálogo. */

/* ---------------- la celda de papeles ----------------
   TRES LETRAS EN UNA CELDA EN VEZ DE TRES COLUMNAS DE ARCHIVO. La decision del dueno era «tres
   celdas fijas, para que se vea de un vistazo a que filas les falta cual» (2026-09-15) y se
   cumple entera: las tres iniciales estan SIEMPRE, viva la que esta y apagada la que falta.

   LO QUE COMPRA: un selector de archivo nativo mide 44px de alto y la fila tiene techo de 28
   (`MASTER.md` §3), asi que tres de ellos por fila rompian la densidad de la tabla entera. Aqui
   el `<input>` vive DENTRO de su `<label>` y se esconde; el que abre el dialogo es la letra. */
/* LAS LETRAS MIDEN 24 px DESDE EL 2026-09-27, el mínimo de WCAG para algo que se pulsa: a 17 px
   y 2 px de distancia era fácil subir la factura en la casilla del comprobante. La celda cede
   su aire de arriba y abajo para que la fila siga en 28 px. */
.papeles { white-space: nowrap; }
td.papeles { padding-top: 2px; padding-bottom: 2px; }
/* A LA IZQUIERDA AUNQUE LLEVE `num` (2026-10-09). `num` se queda: es lo que hace que `tablas.js`
   ordene por `data-v` como número. Pero en `remesas_pago` detrás de las letras va a veces el
   «falta N», de ancho variable, y alineadas a la derecha ese texto empujaba F C P a una x
   distinta en cada fila: la columna dejaba de leerse de un vistazo. Solo esa tabla, que es la
   única que lo lleva: se reconoce por su `data-col` y la cabecera va con sus letras. */
th[data-col="papeles"], table:has(th[data-col="papeles"]) td.papeles { text-align: left; }
.papel {
  display: inline-flex; align-items: center; justify-content: center;
  width: 24px; height: 24px; margin-right: 5px;
  border-radius: var(--radio-s); font-size: var(--t1); font-weight: 600; line-height: 1;
  font-family: var(--mono); border: 1px solid transparent;
}
.papel.puesto { background: var(--verde-bg); color: var(--verde); }
/* EL QUE FALTA NO DESAPARECE: se apaga. Una letra ausente obligaria a contar cuales quedan, y
   la pregunta de esta celda es «cual falta», no «cuantos hay». */
/* `--texto-suave` Y NO `--texto-tenue` (2026-09-27): en oscuro, el tenue sobre `--surface-2`
   daba 4,12:1, por debajo del 4,5:1 de AA. */
.papel.falta {
  background: var(--surface-2); color: var(--texto-suave); border-color: var(--border);
}
/* SOLO EN `capturar` LA LETRA ES UN `<label>`, y entonces se comporta como un boton. El cursor
   es la unica senal de que ahi se puede pulsar, asi que no puede faltar. */
/* `position: relative` NO ES ADORNO: el `<input>` de abajo es `position: absolute`, y sin un
   contexto aqui se posiciona contra el primer ancestro posicionado —o el viewport— y **asoma
   fuera de `.tabla-scroll` creando scroll de la nada**. Ya mordio una vez en este archivo, en
   el anillo de foco de las rejillas. */
label.papel { cursor: pointer; position: relative; }
label.papel:hover { border-color: var(--border-fuerte); }
/* EL ANILLO DE FOCO VIAJA AL `<label>`: el `<input>` esta escondido y no se puede dibujar, asi
   que sin esto la celda es invisible para quien navega con el teclado. `MASTER.md` §8. */
label.papel:focus-within { outline: 2px solid var(--azul); outline-offset: 1px; }
/* `clip-path` Y NO `display:none`: un `<input type=file>` con `display:none` no recibe foco de
   teclado en ningun navegador, asi que la celda dejaria de ser alcanzable con TAB. Es el patron
   de «visualmente escondido» de siempre. */
label.papel > input[type=file] {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
}

/* ---------------- remesas: lo que pidió la auditoría del 2026-09-27 ---------------- */
/* LAS DOS DIVISAS DE UNA SEMANA, UNA ENCIMA DE OTRA. Es la única celda que se exime del techo de
   28 px (`MASTER.md` §3): Alex prefiere que la fila crezca a que las cifras vayan en línea. */
.tabla-lotes td.importes > div { display: block; }
/* EL RAÍL DE NIVEL 2 CAE A 13 px Y LA CELDA EMPIEZA A 12: tapaba la primera letra de la
   sociedad («atnovva México»). */
.tabla-lotes tr[data-nivel="2"] > td:first-child { padding-left: 22px; }
/* LO QUE ANTES ERA UN CHIP SUELTO, EN TEXTO DE COLOR: sin fondo, sin borde y sin punto. Un chip
   solo vive en la columna de estado. */
.texto-senal.AMBAR { color: var(--ambar); font-weight: 600; }
/* EL RÓTULO QUE SEPARA EL POOL DE LAS SEMANAS, DENTRO DE LA MISMA TABLA. Es una fila y no un
   `<h2>` para que las columnas de abajo caigan bajo las de arriba. */
table[data-tabla-id="remesas-pago"] tbody.titulo-semanas th {
  background: var(--surface); color: var(--texto); text-align: left; text-transform: none;
  letter-spacing: 0; font-size: var(--t3); font-weight: 700;
  padding: var(--e4) var(--celda-x) var(--celda-y); border-bottom: 1px solid var(--border);
}

/* LA SOCIEDAD, CABECERA DE SU BLOQUE en el pool y en las semanas (2026-10-06). Fondo gris
   y una raya arriba: separa bloques sin competir con el rótulo «Semanas programadas», que va
   en el fondo de la tarjeta y más grande. */
table[data-tabla-id="remesas-pago"] .fila-sociedad td {
  background: var(--surface-2); border-top: 1px solid var(--border);
  padding-top: var(--e2); padding-bottom: var(--e2); vertical-align: middle;
}
/* SU TIRADOR VA DELANTE DEL NOMBRE (2026-10-06), en la columna de la flecha de cada semana: los
   dos niveles se pliegan desde el mismo borde. */
table[data-tabla-id="remesas-pago"] .fila-sociedad .tirador-todo { margin-right: var(--e1); }

/* LAS FILAS QUE CRUZAN LA TABLA ENTERA —la sociedad, «Semanas programadas» y la cabecera de cada
   semana con su «Mandar a aprobación»— SE QUEDAN A LA VISTA AL DESPLAZAR A LO ANCHO (2026-10-24,
   D4). Su celda mide lo que la tabla, así que al desplazar su texto se iba por la izquierda y la
   fila quedaba como una franja en blanco. Lo que se pega es lo de dentro (`.fila-quieta`), no la
   celda: una celda tan ancha como la tabla no tiene adónde moverse. El ancho máximo es el de la
   pantalla, para que en el móvil envuelva en vez de cortarse por la derecha. */
table[data-tabla-id="remesas-pago"] .fila-quieta {
  position: sticky; left: var(--celda-x, 8px); display: inline-block;
  max-width: calc(100vw - 96px); white-space: normal;
}
/* Y SU CELDA SIN `overflow: hidden`: la de todas las celdas de `anchos.js` la convertiría en la
   caja contra la que se pega, y entonces no se pega a nada. */
table[data-tabla-id="remesas-pago"] :is(td, th):has(> .fila-quieta) { overflow: visible; }

/* EN EL MÓVIL SE QUEDA QUIETO SOLO EL PEDIDO (2026-10-24, D4). La casilla, el pedido y la obra
   quietos ocupaban 290 de los 320 px de la tabla: al desplazar quedaban 30 px para todo lo
   demás y las cifras salían cortadas. La obra se desplaza con el resto: su `left` lo escribe
   `anchos.js` en línea, por eso el `!important`. Sigue `sticky` arriba por la cabecera pegada;
   baja de nivel para pasar por debajo del pedido, que sigue quieto. */
@media (max-width: 600px) {
  table[data-tabla-id="remesas-pago"].anchos .fija-ultima { left: auto !important; box-shadow: none; }
  table[data-tabla-id="remesas-pago"].anchos tbody .fija-ultima { z-index: 0; }
  table[data-tabla-id="remesas-pago"].anchos thead .fija-ultima { z-index: 3; }
  table[data-tabla-id="remesas-pago"].anchos tr > .fija:nth-child(2) { box-shadow: inset -1.5px 0 0 var(--border-fuerte); }
}

/* LO QUE CUELGA DE UN GRUPO PLEGADO (`tablas.js`, `montarGrupos`): sus filas en el pool y sus
   semanas debajo. Una CLASE y no `style.display`, que es del filtro de columna: así plegar y
   filtrar no se pisan. */
tr.grupo-plegado, tbody.grupo-plegado { display: none; }

/* LA CELDA NUMÉRICA VACÍA DE UNA REJILLA, en discontinuo (2026-09-24): se ve de un vistazo qué
   falta por teclear sin pintar nada de rojo, que es para lo que ya se rechazó. */
.rejilla-captura input:not(:focus):placeholder-shown { border-style: dashed; }

/* UNA CASILLA CON SU NOMBRE, en línea (el equipo de obra, donde caben varios compradores). El
   `<label>` envuelve al input: pulsar el nombre marca, y no hace falta un `for`. */
.casilla { display: inline-flex; align-items: center; gap: 4px; margin-right: var(--e2);
  font-size: var(--t2); cursor: pointer; }

/* LA RECEPCION DE UN PEDIDO (2026-09-25), `recepcion_pedido.html`. Se llena en el MOVIL del jefe
   de obra con el material delante: cada renglon es un bloque con su campo, no una columna de una
   tabla ancha. En escritorio el renglón y su «Llegó hoy» van en una fila y JUNTOS: el campo a
   continuación de las cuentas, no al otro lado de la tarjeta (2026-10-09). Por debajo de 600px
   se apilan. */
.renglon-recepcion { display: grid; grid-template-columns: minmax(0, 440px) 180px; justify-content: start;
  gap: var(--e2) var(--e4); align-items: end; padding: var(--e3) 0;
  border-top: 1px solid var(--border-suave); }
.renglon-recepcion:first-of-type { border-top: 0; }
.renglon-recepcion .campo { margin: 0; }
.renglon-recepcion-que b { font-size: var(--t3); }
/* LAS CUENTAS EN VEZ DEL ESTADO (2026-10-09): pedido, llegado y lo que falta, y una barra de lo
   llegado. Sustituyen a los chips «a medias / completo / excedido», que había que aprender. */
.renglon-recepcion-cuenta { display: flex; flex-wrap: wrap; gap: 2px 14px; margin-top: 4px;
  font-size: var(--t2); color: var(--texto-suave); font-variant-numeric: tabular-nums; }
.renglon-recepcion-cuenta .falta { color: var(--texto); font-weight: 600; }
.barra-recepcion { height: 6px; max-width: 420px; margin-top: 8px; border-radius: 3px;
  background: var(--surface-2); overflow: hidden; }
.barra-recepcion > span { display: block; height: 100%; border-radius: 3px; background: var(--azul); }
.recepcion-resto { display: grid; gap: var(--e3); margin-top: var(--e3); padding-top: var(--e3);
  border-top: 1px solid var(--border-suave); }
.recepcion-resto .campo { margin: 0; }
/* «SOLO SI PASÓ ALGO»: las dos excepciones de una entrega, juntas y al final. Lo de cada renglón
   va en un `<details>` —cerrado, cuenta un control en el medidor— con CARA DE CASILLA: su
   `<summary>` pinta la caja de la de al lado, y marcada al abrirse. */
.recepcion-si-paso { display: grid; gap: var(--e2); justify-items: start; }
.recepcion-si-paso > .titulo { font-size: var(--t1); font-weight: 600; color: var(--texto-suave); }
.recepcion-si-paso .casilla { margin: 0; }
.recepcion-mal-pliegue { justify-self: stretch; display: grid; gap: var(--e2); }
.recepcion-mal-pliegue > summary { list-style: none; display: inline-flex; align-items: center; gap: 4px;
  width: fit-content; cursor: pointer; font-size: var(--t2); }
.recepcion-mal-pliegue > summary::-webkit-details-marker { display: none; }
/* El margen es el de la casilla nativa de al lado (3px 3px 3px 4px) con su hueco al texto, para que cajas y textos caigan
   en la misma vertical. */
.recepcion-mal-pliegue > summary::before { content: ""; flex: none; width: 13px; height: 13px; margin: 3px 1px 3px 4px;
  border: 1px solid var(--border-fuerte); border-radius: 3px; background: var(--surface); }
.recepcion-mal-pliegue[open] > summary::before { border-color: var(--azul); background: var(--azul)
  url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M2.5 6.2l2.3 2.3 4.7-5' fill='none' stroke='white' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / 11px no-repeat; }
.recepcion-mal-pliegue > summary:focus-visible { outline: 2px solid var(--azul); outline-offset: 2px; }
.recepcion-mal { display: grid; gap: var(--e2); margin-top: var(--e2); }
.recepcion-mal-renglon { display: grid; grid-template-columns: minmax(0, 1fr) 120px minmax(0, 1.4fr);
  gap: var(--e2); align-items: end; padding: var(--e2) var(--e3); border-radius: var(--radio);
  background: var(--surface-2); }
.recepcion-mal-renglon .campo { margin: 0; }
.recepcion-mal-renglon fieldset { border: 0; padding: 0; min-width: 0; }
.recepcion-mal-renglon legend { padding: 0; margin-bottom: 4px; font-size: var(--t1); font-weight: 600;
  color: var(--texto-suave); }
.recepcion-mal-renglon .opciones { display: flex; flex-wrap: wrap; gap: var(--e3);
  min-height: var(--alto-control); align-items: center; }
/* EL BOTON PROPIO DE UN `<input type=file>` (2026-09-27): el nativo sale en el idioma del navegador.
   El input se esconde a la vista pero sigue siendo el que tiene el foco y el que se envia, asi
   que el teclado y el lector de pantalla lo siguen encontrando; el anillo se lo pinta al boton.
   Es el mismo «visualmente escondido» de `label.papel`, y con `input[type=file]` delante porque
   las reglas de formulario le ponen ancho y ganarian a una clase sola. */
input[type=file].archivo-oculto {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
}
.archivo-propio { display: inline-flex; align-items: center; gap: var(--e1); }
.archivo-propio[hidden] { display: none; }
.archivo-oculto:focus-visible + .archivo-propio .boton { outline: 2px solid var(--azul); outline-offset: 1px; }
.fotos-previa, .fotos-entrega { display: flex; flex-wrap: wrap; gap: var(--e1); margin-top: var(--e1); }
.fotos-previa img, .fotos-entrega img { width: 72px; height: 72px; object-fit: cover;
  border-radius: 6px; border: 1px solid var(--border); display: block; }
.entrega { padding: var(--e3) 0; border-top: 1px solid var(--border-suave); }
.entrega:first-of-type { border-top: none; }
.entrega.anulada .entrega-cabecera b, .entrega.anulada .entrega-renglones { text-decoration: line-through;
  color: var(--texto-tenue); }
.entrega-cabecera { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px var(--e2); }
.entrega-cabecera .pequeno { color: var(--texto-suave); }
.entrega-renglones { margin: var(--e1) 0; padding: 0; list-style: none; display: grid; gap: 2px; }
/* «Pedir cancelar» va como enlace rojo y no como botón: es la acción rara de la tarjeta, y un
   botón con borde rojo bajo cada entrega pesaba más que lo que se vino a leer (2026-10-09). */
.entrega .boton.enlace.peligro { border-color: transparent; padding-inline: 0; }
@media (max-width: 600px) {
  .renglon-recepcion, .recepcion-mal-renglon { grid-template-columns: 1fr; }
  /* 16px en el campo y no los 13 de la hoja: por debajo de 16 el navegador del iPhone hace
     zoom al enfocarlo, y la pantalla se descoloca a cada renglon. */
  .renglon-recepcion input, .renglon-recepcion select,
  .recepcion-resto input:not([type=checkbox]):not([type=radio]) { font-size: 16px; }
}

/* ================= LA DOCUMENTACIÓN DE UN PEDIDO (2026-10-07; en recuadros, 2026-10-26) ========
   `_papeles_pedido.html` y `_documentos.html`.

   Alex sobre la lista de cuatro líneas por documento: «en vez de esto, recuadros con un icono y
   abajo el documento que es; le das clic y un pop up», y «todos los iconos enfilados en el ancho
   de una fila». Cada grupo —lo del pedido, cada pago— es UNA fila de recuadros pequeños que, si
   no caben, bajan a la línea siguiente (`flex-wrap`, nunca scroll de lado). Título, estado e
   importe del grupo, en una línea encima.

   SIN CHIPS (`alex-no-quiere-chips`): el estado es el icono del recuadro (✓ / ⚠) y una palabra
   de color. Ningún color va solo: el recuadro dice «Subido» o «Falta» con su marca y el borde de
   lo que falta va a trazos. Los colores son los de siempre (`--verde`, `--ambar`). */
.papeles-bloque { display: grid; gap: var(--e4); }
.papeles-grupo { border-top: 1px solid var(--border); padding-top: var(--e3); }
/* EL TÍTULO DEL GRUPO, SU ESTADO Y SU IMPORTE EN UNA LÍNEA: «Pago 3 · Estimación 2 — Le falta 1
   para programarse · $478,500.00». En el teléfono baja a la línea siguiente lo que no cabe. */
.papeles-cabecera {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px var(--e2);
}
.papeles-cabecera h3 { margin: 0; font-size: var(--t3); font-weight: 600; }
.papeles-sub { color: var(--texto-suave); font-size: var(--t1); }
/* EL ESTADO DEL PAGO, LO PRIMERO QUE SE LEE: en negrita y con su color, pegado al título. */
.papeles-estado {
  font-weight: 600; font-size: var(--t2); display: inline-flex; align-items: baseline; gap: 4px;
}
.papeles-estado::before { content: "—"; font-weight: 400; color: var(--texto-tenue); margin-right: 2px; }
.papeles-estado .icono { width: 14px; height: 14px; align-self: center; }
.papeles-estado.VERDE { color: var(--verde); }
.papeles-estado.AMBAR { color: var(--ambar); }
.papeles-estado.GRIS  { color: var(--texto-suave); }

/* LA FILA DE RECUADROS. Cada uno es un `<button>` pequeño: icono, nombre (hasta cuatro líneas) y
   estado, a la altura del más alto de la fila. Sin JavaScript el botón no se pinta (`hidden`) y las
   ventanas se ven abiertas en la hoja, que es como se hace el resto de ventanas de la app. */
.doc-fila { display: flex; flex-wrap: wrap; gap: var(--e2); margin-top: var(--e2); }
.doc-fila > dialog.ventana[open]:not(:modal) { flex: 1 1 100%; margin: 0; }
.doc-recuadro {
  flex: 0 0 auto; width: 116px; padding: var(--e2) var(--e1); box-sizing: border-box;
  display: flex; flex-direction: column; align-items: center; gap: 4px; text-align: center;
  font: inherit; color: var(--texto); background: var(--surface); cursor: pointer;
  border: 1px solid var(--border); border-radius: var(--radio-s);
}
.doc-recuadro[hidden] { display: none; }
.doc-recuadro:hover { border-color: var(--border-fuerte); background: var(--surface-2); }
.doc-recuadro:focus-visible { outline: 2px solid var(--azul); outline-offset: 2px; }
.doc-icono .icono { width: 26px; height: 26px; color: var(--texto-tenue); display: block; }
.doc-nombre {
  font-size: var(--t1); line-height: 1.25; font-weight: 600;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 4; overflow: hidden;
  overflow-wrap: break-word;
}
.doc-estado {
  display: inline-flex; align-items: flex-start; justify-content: center; gap: 3px; margin-top: auto;
  font-size: var(--t1); font-weight: 600; line-height: 1.2; color: var(--texto-tenue);
}
.doc-estado .icono { width: 12px; height: 12px; flex: none; margin-top: 1px; }
.doc-recuadro.hecho .doc-icono .icono, .doc-recuadro.hecho .doc-estado { color: var(--verde); }
.doc-recuadro.falta { border-style: dashed; border-color: var(--ambar); }
.doc-recuadro.falta .doc-icono .icono, .doc-recuadro.falta .doc-estado { color: var(--ambar); }
.doc-recuadro.sin .doc-estado { font-weight: 400; }

/* LA VENTANA DE UN DOCUMENTO (`dialog.ventana.ventana-doc`): quién, cuándo y la descarga; debajo,
   reemplazar plegado y las versiones anteriores en pequeño. */
.doc-donde { margin: calc(-1 * var(--e1)) 0 var(--e2); }
.doc-requisito { color: var(--texto-suave); }
.doc-datos {
  display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: 2px var(--e3);
  margin: 0 0 var(--e3); font-size: var(--t2);
}
.doc-datos dt { color: var(--texto-suave); }
.doc-datos dd { margin: 0; font-weight: 600; }
.doc-reemplazo[open] > form { max-width: none; }
.doc-historial { margin-top: var(--e3); }
.doc-historial ul { margin: var(--e1) 0 0; padding-left: 1.1em; }
dialog.ventana-doc .fila-form { margin-bottom: 0; }

.texto-senal.VERDE { color: var(--verde); font-weight: 600; }
/* UN ENLACE QUE NO SE PARTE: «Ir a la recepción →» salía montado y cortado a final de línea. */
.sin-corte { white-space: nowrap; }
.archivo-nombre { max-width: 16em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.archivo-nombre.elegido { color: var(--texto); }

/* LO RARO, PLEGADO: reemplazar un papel, cambiar el tipo de un pago, corregir los «si aplica».
   Un `<details>` con un resumen que se lee como enlace discreto; abierto, el formulario. */
.papel-reemplazar { margin-top: var(--e1); }
.papel-reemplazar > summary {
  display: inline-block; cursor: pointer; font-size: var(--t1); color: var(--azul); list-style: none;
}
.papel-reemplazar > summary::-webkit-details-marker { display: none; }
.papel-reemplazar > summary::after { content: " ▾"; font-size: 10px; }
.papel-reemplazar[open] > summary::after { content: " ▴"; }
.papel-reemplazar > summary:hover { text-decoration: underline; }
.papel-reemplazar > summary:focus-visible { outline: 2px solid var(--azul); outline-offset: 2px; border-radius: 2px; }
.papel-reemplazar[open] > form,
.acto-plegado[open] > form {
  margin-top: var(--e2); padding: var(--e3); border-radius: var(--radio-s);
  background: var(--surface-2); display: grid; gap: var(--e2); max-width: 34em;
}
.papel-reemplazar .campo, .acto-plegado .campo { margin: 0; }
/* EL MISMO PLIEGUE CON UN BOTÓN POR RESUMEN (2026-10-08), para el acto que no es raro pero sí
   pide algo antes —corregir el plan del mes pide el motivo—. Abierto siempre, «Motivo de la
   corrección» y su botón se leían como un formulario suelto debajo de la lista; Alex: «y ese
   botón, ¿para qué es?». Plegado, el botón dice el acto y, al abrirlo, una frase dice lo que
   hace antes de pedir el motivo. El botón de dentro es el que confirma. */
.acto-plegado { margin-top: var(--e3); }
.acto-plegado > summary { list-style: none; }
.acto-plegado > summary::-webkit-details-marker { display: none; }
.acto-plegado[open] > summary { background: var(--surface-2); }
.acto-plegado > form > p { margin: 0; font-size: var(--t2); color: var(--texto-suave); }

/* UNA LISTA DE POCOS RENGLONES, no una tabla (2026-10-08): las versiones del plan del mes, que
   son una a tres. Una tabla con embudos y orden para eso sobra —lo que Alex ya dijo de los
   papeles— y además no llenaba su tarjeta y dejaba una columna vacía para el botón. Cada renglón:
   lo que es y su estado debajo, en pequeño; a la derecha, su acto. */
.renglones { list-style: none; margin: 0; padding: 0; }
.renglones > li {
  display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: var(--e1) var(--e3);
  align-items: center; padding: var(--e2) 0; border-top: 1px solid var(--border-suave);
}
.renglones > li:first-child { border-top: none; padding-top: 0; }
.renglones .renglon-titulo { font-weight: 600; font-size: var(--t2); }
.renglones .renglon-titulo .tenue { font-weight: 400; }
.renglones .renglon-detalle { font-size: var(--t1); color: var(--texto-tenue); margin-top: 1px; }
.papeles-tipo { margin-top: 2px; }

/* LOS «SI APLICA» DEL PEDIDO, COMO DATOS: cuatro líneas cortas, el sí en negrita. */
.papeles-lleva-titulo { font-size: var(--t2); font-weight: 600; }
.papeles-lleva-titulo .tenue { font-weight: 400; font-size: var(--t1); }
.papeles-lleva-lista {
  list-style: none; margin: var(--e1) 0 0; padding: 0; font-size: var(--t2);
  display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 24em), 1fr)); gap: 2px var(--e4);
}
/* LAS CASILLAS DE «CORREGIR LO QUE LLEVA» (pulido del 2026-10-07): la ayuda va DEBAJO de su
   etiqueta. En el `.casilla` de la hoja, que es flex en fila, se volvía una segunda columna y
   partía cada etiqueta en dos o tres renglones; a 390 px no se leía. */
.papeles-casillas { display: grid; gap: var(--e2); }
/* LAS NUEVE CASILLAS DE UN PAGO (2026-10-13) van en columnas: en una sola, la ventana de corregir
   medía más que la pantalla. */
.papeles-casillas-pago { grid-template-columns: repeat(auto-fill, minmax(min(100%, 240px), 1fr)); }
.papeles-tipo[open] > form { max-width: 52em; }
.papeles-casilla { display: flex; align-items: flex-start; gap: var(--e1); font-weight: 400; cursor: pointer; }
.papeles-casilla input { margin: 3px 0 0; flex: none; }
.papeles-casilla-ayuda { display: block; font-size: var(--t1); color: var(--texto-tenue); }

/* LA EXCEPCIÓN A DOS MANOS, AL PIE DEL PAGO Y EN PEQUEÑO: es la salida rara, no el camino. Una
   raya discontinua la separa de la lista y el icono de aviso dice qué es antes de leerla. */
.papeles-excepcion {
  display: flex; align-items: flex-start; gap: var(--e1);
  margin-top: var(--e2); padding-top: var(--e2); border-top: 1px dashed var(--border);
  font-size: var(--t1); color: var(--texto-suave);
}
.papeles-excepcion > .icono { width: 14px; height: 14px; margin-top: 1px; color: var(--ambar); flex: none; }
.papeles-excepcion > span { flex: 1 1 auto; min-width: 0; }
/* EL ACTO DE LA EXCEPCIÓN, PEGADO A SU FRASE Y EN EL AZUL de «Reemplazar» y «Cambiar el tipo»:
   en el gris de `.boton.enlace` y al otro extremo de la línea no se leía como algo que se pulsa. */
.papeles-excepcion .boton.enlace.papeles-excepcion-acto {
  color: var(--azul); font-weight: 600; font-size: inherit; padding: 0 2px; margin: 0; border: 0;
  vertical-align: baseline;   /* sin `display`: pisaría el `.boton[hidden]` hasta que ventana.js lo enciende */
}
.papeles-excepcion .boton.enlace.papeles-excepcion-acto:hover { text-decoration: underline; border: 0; }

/* LOS ESTADOS EN TEXTO QUE LE FALTABAN A `.texto-senal`: la recepción de la ficha puede ir en
   gris (cerrada con faltante) o llevar «con exceso» en rojo. */
.texto-senal.ROJO { color: var(--rojo); font-weight: 600; }
.texto-senal.GRIS { color: var(--texto-suave); font-weight: 600; }
/* LA FILA DE LA DESCARGA, ENCIMA DEL VISOR DEL PEDIDO. */
.ficha-pdf { margin-bottom: var(--e3); }

.papeles-historial > summary { cursor: pointer; font-size: var(--t2); color: var(--texto-suave); }
.papeles-historial ul { font-size: var(--t2); margin: var(--e2) 0 0; }
/* CANCELAR, AL PIE DE «DATOS Y DOCUMENTACIÓN DEL PEDIDO» y tras una raya: después de los papeles
   no puede leerse como un acto más de la lista. */
.ficha-cancelar { margin-top: var(--e4); padding-top: var(--e3); border-top: 1px solid var(--border); }

@media (max-width: 600px) {
  /* TRES POR LÍNEA A 390 px: el ancho de la fila partido en tres, con su hueco. */
  .doc-recuadro { width: calc((100% - 2 * var(--e2)) / 3); padding-inline: var(--e0); }
  /* UN PUNTO MENOS: con 12 px «responsabilidad» no cabe en un tercio de 390 px y se partía a mitad
     de palabra. */
  .doc-nombre { font-size: var(--t0); }
  .papel-reemplazar select { max-width: 100%; }
}

/* ---------------- certificaciones: los tres documentos en su sitio (2026-10-27) ---------------- */
/* FACTURA · ESTIMACIÓN · COBRO, UN HUECO DE ANCHO FIJO CADA UNO. Antes eran las letras F E C
   pegadas al ojo y al PDF, y el PDF caía en un sitio distinto en cada fila. Cada nombre ocupa
   siempre su hueco, lo tenga o no, así que la columna se lee de arriba abajo igual que se lee
   de izquierda a derecha. Los anchos son en `em` porque el nombre es texto, no un icono: el
   ✓ cabe en el hueco y no lo ensancha. `position: relative` es el contexto de `.solo-lector`
   (sin él, el texto escondido asoma fuera de `.tabla-scroll` y crea scroll lateral). */
.docs-cert { display: flex; align-items: center; gap: var(--e2); white-space: nowrap; font-size: var(--t1); }
.doc-cert { position: relative; display: inline-flex; align-items: center; box-sizing: border-box; gap: 3px; }
.doc-cert.doc-f { width: 5.1em; }
.doc-cert.doc-e { width: 6.5em; }
.doc-cert.doc-c { width: 4.1em; }
.doc-cert.hecho { color: var(--verde); font-weight: 600; text-decoration: none; }
a.doc-cert.hecho:hover { text-decoration: underline; }
.doc-cert .icono { width: 11px; height: 11px; }
/* `--texto-suave` Y NO `--texto-tenue`: en oscuro el tenue no llega a AA (ver `.papel.falta`). */
.doc-cert.falta, .doc-cert.no-aplica { color: var(--texto-suave); }
/* EL «—» DE LO QUE NO APLICA: en su hueco, a la izquierda como el nombre que sustituye. */
.doc-cert.no-aplica { font-weight: 400; }
/* LA COLUMNA DE ACCIONES: el ojo y el PDF en dos huecos fijos, siempre en las mismas posiciones. */
td.acciones-cert { white-space: nowrap; }
td.acciones-cert > .boton { vertical-align: middle; }
td.acciones-cert > .hueco-boton { display: inline-block; width: 28px; }
