/* erp.css — la piel del SevenEight ERP (Ola 0 · T5).
   Base OSCURA «Torre HUD» en :root; tema CLARO «Porcelana» bajo
   [data-theme="claro"]. REGLA: ningún color/glow/sombra literal fuera de un
   token — el toggle depende de ello (test de paridad en erp-tema.test.mjs).
   Semántica de marca intocable: ámbar=incidencia · azul=coordinamos · verde=resuelto. */

@font-face{font-family:'Inter';font-weight:400;font-display:swap;src:url('fuentes/inter-400.woff2') format('woff2')}
@font-face{font-family:'Inter';font-weight:500;font-display:swap;src:url('fuentes/inter-500.woff2') format('woff2')}
@font-face{font-family:'Inter';font-weight:600;font-display:swap;src:url('fuentes/inter-600.woff2') format('woff2')}
/* Space Grotesk / JetBrains Mono: si algún día caen sus woff2 en erp/fuentes,
   se añaden aquí sin tocar nada más — mientras, la pila hace de red. */

:root{
  /* — Torre HUD (oscuro, el elegido) — */
  --bg:#0A0F0C; --side:#070B09; --panel:#101713; --panel-2:#16201A;
  --line:rgba(116,182,94,.14); --line-2:rgba(116,182,94,.30);
  --hair:rgba(255,255,255,.06);
  --ink:#E9F2EA; --muted:#9DB3A4; --faint:#5F7566;
  --brand:#74B65E; --brand-deep:#53913F; --brand-dim:rgba(116,182,94,.10);
  --ok:#74B65E; --ok-bg:rgba(116,182,94,.13);
  --warn:#E3A93C; --warn-bg:rgba(227,169,60,.13);
  --danger:#E4685F; --danger-bg:rgba(228,104,95,.13);
  --info:#64A0F2; --info-bg:rgba(100,160,242,.13);
  --display:'Space Grotesk','Inter',sans-serif;
  --body:'Inter','Segoe UI',sans-serif;
  --mono:'JetBrains Mono',ui-monospace,Consolas,monospace;
  --r:12px;
  /* — tokens nuevos: los literales cazados de las demos — */
  --grid-line:rgba(116,182,94,.045);      /* retícula fósforo del body */
  --shadow:0 0 30px rgba(116,182,94,.04); /* «sombra» de tarjeta (glow suave) */
  --kpi-inset:inset 0 1px 0 rgba(116,182,94,.06);
  --glow-logo:drop-shadow(0 0 8px rgba(116,182,94,.5));
  --glow-active:inset 0 0 0 1px var(--line-2),0 0 18px rgba(116,182,94,.12);
  --glow-btn:0 0 22px rgba(116,182,94,.3);
  --glow-text:0 0 12px rgba(116,182,94,.5);
  --glow-bar:drop-shadow(0 0 8px rgba(116,182,94,.6));
  --glow-pill:6px;                        /* radio del halo de pills/estado */
  --glow-danger-soft:0 0 12px rgba(228,104,95,.25);
  --scan:1;                               /* scanline visible (0 en claro) */
  --sobre-brand:#0A1509;                  /* tinta sobre color de marca */
  --chip-bg:rgba(255,255,255,.06);
  --gauge-track:rgba(255,255,255,.07);
  --topbar-bg:rgba(7,11,9,.6);
  --thead-bg:rgba(116,182,94,.03);
  --ops-bg:linear-gradient(180deg,rgba(116,182,94,.05),transparent);
  --active-bg:var(--brand-dim); --active-ink:var(--brand);
  --hover-bg:rgba(116,182,94,.06);
  --btn-primary-bg:var(--brand); --btn-primary-hover:#84C46F;
}
[data-theme="claro"]{
  /* — Salvia (claro · Porcelana v2, 21-ago-2026): página casi blanca y los
     recuadros en verde salvia; estados saturados sobre loza pálida. Elegida
     entre 5 en demos-crm-2026/porcelana-v2-propuestas.html (23/23 pares AA) — */
  --bg:#FAFBF8; --side:#F1F5EE; --panel:#EEF4EA; --panel-2:#E2ECDB;
  --line:#C9DBC0; --line-2:#9FBC90;
  --hair:#DCE7D4;
  --ink:#1A231C; --muted:#4B584A; --faint:#616F60;
  --brand:#386F25; --brand-deep:#326522; --brand-dim:#E2EED8;
  --ok:#2E7020; --ok-bg:#D8EBCA;
  --warn:#915D04; --warn-bg:#F4E9C8;
  --danger:#B03C3C; --danger-bg:#F5E2DE;
  --info:#2158BE; --info-bg:#E0E9F5;
  --grid-line:transparent;
  --shadow:0 1px 2px rgba(38,64,30,.06),0 8px 22px rgba(38,64,30,.08);
  --kpi-inset:0 1px 2px rgba(38,64,30,.05),0 6px 16px rgba(38,64,30,.07);
  --glow-logo:none;
  --glow-active:inset 0 0 0 1px var(--line-2),0 1px 2px rgba(38,64,30,.09);
  --glow-btn:0 1px 2px rgba(38,64,30,.22);
  --glow-text:none;
  --glow-bar:none;
  --glow-pill:0px;
  --glow-danger-soft:none;
  --scan:0;
  --sobre-brand:#FFFFFF;
  --chip-bg:#E7EFE0;
  --gauge-track:#DBE6D2;
  --topbar-bg:rgba(241,245,238,.85);
  --thead-bg:#E8F0E2;
  --ops-bg:linear-gradient(180deg,#EFF4EA,transparent);
  --active-bg:#FFFFFF; --active-ink:var(--brand-deep);
  --hover-bg:#E8F0E2;
  --btn-primary-bg:var(--brand-deep); --btn-primary-hover:#2A5519;
}

*{box-sizing:border-box;margin:0;padding:0}
html,body{height:100%}
/* ☠️ EL ARMAZÓN NO SE MUEVE; SE MUEVE LO DE DENTRO (24-ago-2026).
   Hasta hoy scrolleaba EL DOCUMENTO, y eso traía tres problemas a la vez:
   dos barras de scroll compitiendo (la de la página y la del menú), y —lo
   peor— **el menú se iba con la página**. Su `position:sticky` no funcionaba,
   y la razón es fina: con `height:100%` el grid del body mide lo que la
   ventana, así que su Única fila mide también lo que la ventana, y el
   contenido de `main` la desborda por fuera. Un elemento sticky no tiene
   dónde pegarse dentro de una fila de su mismo alto: se va con ella.
   Medido en la pantalla de Deuda a 1280×720 antes del arreglo: al bajar del
   todo, el `<aside>` quedaba en `top:-650px` — fuera.
   Ahora el body es un marco fijo del alto de la ventana y **el único que
   scrollea es `.content`**. `100dvh` y no `100vh`: en el móvil la barra del
   navegador se esconde y `100vh` deja un trozo cortado por debajo. */
body{
  height:100dvh;overflow:hidden;
  font-family:var(--body);font-size:13.5px;color:var(--ink);line-height:1.5;
  background:var(--bg);
  background-image:linear-gradient(var(--grid-line) 1px,transparent 1px),linear-gradient(90deg,var(--grid-line) 1px,transparent 1px);
  background-size:30px 30px;
  /* minmax(0,…) y no 1fr a secas: con 1fr la columna NUNCA baja del ancho
     mínimo de su contenido, así que una tabla o un nombre de fichero largo
     empujaba la página entera y devolvía el scroll lateral — el .tblwrap
     con overflow-x nunca llegaba a hacer su trabajo. */
  display:grid;grid-template-columns:246px minmax(0,1fr);
  -webkit-font-smoothing:antialiased;
}
a{color:inherit;text-decoration:none}
button{font:inherit;color:inherit;background:none;border:none}
svg.ic{width:17px;height:17px;flex:none;stroke:currentColor;fill:none;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}
:focus-visible{outline:2px solid var(--brand);outline-offset:2px;border-radius:6px}

/* ---------- SIDEBAR ---------- */
/* Sin `position:sticky`: con el body quieto no hay de qué despegarse, y
   dejarlo puesto invitaba a creer que era lo que sujetaba el menú — que es
   justo lo que NO hacía. */
aside{background:var(--side);border-right:1px solid var(--line);display:flex;flex-direction:column;height:100dvh;min-height:0}
.brand{display:flex;align-items:center;gap:11px;padding:20px 18px 14px}
.brand .mark{width:32px;height:32px;color:var(--brand);filter:var(--glow-logo)}
.wm{font-family:var(--mono);font-weight:600;font-size:11.5px;letter-spacing:.2em;line-height:1.2}
.wm small{display:block;font-weight:500;font-size:8.5px;letter-spacing:.24em;color:var(--brand);margin-top:3px}
.estado-sist{margin:0 14px 8px;display:flex;align-items:center;gap:8px;font-family:var(--mono);font-size:9px;letter-spacing:.16em;color:var(--brand);text-transform:uppercase;border:1px solid var(--line);border-radius:8px;padding:7px 10px;background:var(--brand-dim)}
.estado-sist::before{content:"";width:7px;height:7px;border-radius:50%;background:var(--brand);box-shadow:0 0 8px var(--brand);animation:lat 1.8s ease-in-out infinite}
@keyframes lat{0%,100%{opacity:1}50%{opacity:.35}}
/* El menú solo se mueve si NO CABE, y cuando no cabe su barra no tiene por
   qué estar ahí todo el rato: con una barra en el menú y otra en el
   contenido, la pantalla parecía tener tres scrolls. Aparece al acercar el
   ratón. No se OCULTA el scroll — la rueda y el teclado siguen llegando al
   último módulo —, se oculta la BARRA.
   Medido: con una sección abierta el menú pide 789 px, así que por debajo
   de una ventana de ~977 px hay que poder moverlo.
   `overscroll-behavior:contain` para que al llegar al final del menú la
   rueda no siga y se lleve por delante el contenido de al lado. */
nav.menu{flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain;padding:2px 10px 12px;
  scrollbar-width:none}
nav.menu::-webkit-scrollbar{width:0}
aside:hover nav.menu,nav.menu:focus-within{scrollbar-width:thin}
aside:hover nav.menu::-webkit-scrollbar,nav.menu:focus-within::-webkit-scrollbar{width:7px}
nav.menu::-webkit-scrollbar-thumb{background:var(--line-2);border-radius:4px}
.menu details{border:none}
.menu summary{list-style:none;cursor:pointer;display:flex;align-items:center;font-family:var(--mono);font-size:9px;font-weight:600;letter-spacing:.22em;text-transform:uppercase;color:var(--faint);padding:13px 10px 5px;user-select:none}
.menu summary::-webkit-details-marker{display:none}
.menu summary::after{content:"›";margin-left:auto;transform:rotate(90deg);transition:.15s;font-size:11px}
.menu details[open] summary::after{transform:rotate(-90deg)}
.item{display:flex;align-items:center;gap:10px;padding:6.5px 10px;border-radius:8px;color:var(--muted);font-weight:500;position:relative;transition:.12s}
.item:hover{background:var(--hover-bg);color:var(--ink)}
.item.active{background:var(--active-bg);color:var(--active-ink);box-shadow:var(--glow-active);font-weight:600}
.ct{margin-left:auto;font-family:var(--mono);font-size:10px;font-weight:600;color:var(--muted);background:var(--chip-bg);border-radius:6px;padding:1px 6px}
.item.active .ct{background:var(--brand-dim);color:var(--active-ink)}
.ct.hot{background:var(--warn-bg);color:var(--warn)}
.me{display:flex;align-items:center;gap:10px;margin:10px 12px 14px;padding:10px;border:1px solid var(--line);border-radius:10px;background:var(--panel)}
.ava{width:29px;height:29px;border-radius:8px;background:var(--brand-deep);color:var(--sobre-brand);display:grid;place-items:center;font-family:var(--display);font-weight:700;font-size:12px;flex:none;overflow:hidden}
/* La foto del perfil (29-ago): si hay imagen, llena el chip sin deformarse. */
.ava img{width:100%;height:100%;object-fit:cover;border-radius:inherit;display:block}

/* ---------- EL ADHESIVO DE CADA CASA (27-ago-2026) ----------
   El logo del despacho o del proveedor, recortado y sin fondo (erp/logos.js).
   SUELTO, sin recuadro: lo pidió Jordi con esas palabras («no quiero recuadro,
   que quede suelto, sin fondo»). La primera versión los ponía sobre pastilla
   blanca y quedaba pegado y acartonado.

   ── EL PROBLEMA QUE LA PASTILLA RESOLVÍA, RESUELTO DE OTRA FORMA ──
   Diez de los veintidós son de tinta oscura (ELEDEVALL luma 24, DBS 72,
   Althena 89, OTIS 94) y sobre el azul noche del ERP desaparecían. Aclararlos
   sería tocarle el color a una marca ajena.
   La cura es un HALO que sigue la SILUETA del PNG, no una caja: `drop-shadow`
   se calcula sobre el canal alfa, así que abraza las letras y el logo sigue
   estando suelto. Es, literalmente, el borde de una pegatina troquelada.
   Solo lo llevan los que lo necesitan (`.marca-osc`, medido por luminancia al
   procesar cada PNG) y solo en el tema en el que hace falta: en el claro son
   los logos casi blancos (`.marca-clara`) los que se pierden.

   `.ava` y `.marca` comparten alto en cada talla: donde no hay logo cae el
   cuadradito de iniciales y la fila NO da un salto. */
.marca{display:grid;place-items:center;flex:none;overflow:hidden}
.marca img{display:block;max-width:100%;max-height:100%;width:auto;height:auto;object-fit:contain}
.marca-sm{width:46px;height:29px}
.marca-md{width:70px;height:38px}
.marca-lg{width:112px;height:60px}
.ava-sm{width:29px;height:29px}
.ava-md{width:38px;height:38px;font-size:13px;border-radius:9px}
.ava-lg{width:60px;height:60px;font-size:19px;border-radius:11px}

/* El halo, por silueta. Dos capas: una pegada que da el contorno y otra
   abierta que lo separa del fondo. En tema OSCURO lo llevan los logos de
   tinta oscura; en tema CLARO, los casi blancos. */
.marca-osc img{filter:drop-shadow(0 0 1px rgba(255,255,255,.92)) drop-shadow(0 0 3px rgba(255,255,255,.55))}
[data-theme="claro"] .marca-osc img{filter:none}
[data-theme="claro"] .marca-clara img{filter:drop-shadow(0 0 1px rgba(20,34,50,.55)) drop-shadow(0 0 3px rgba(20,34,50,.30))}
/* Al pasar por encima de la tarjeta, el adhesivo se anima un pelo: la tarjeta
   entera responde, no solo el texto. */
a:hover > .marca img, a:hover .marca img{transform:scale(1.04)}
@media (prefers-reduced-motion:no-preference){.marca img{transition:transform .16s ease}}
@media (prefers-reduced-motion:reduce){a:hover > .marca img,a:hover .marca img{transform:none}}
.me b{display:block;font-size:12.5px;font-weight:600}
.me span{font-family:var(--mono);font-size:9.5px;letter-spacing:.08em;color:var(--faint);text-transform:uppercase}

/* ---------- MAIN ---------- */
main{min-width:0;min-height:0;height:100dvh;display:flex;flex-direction:column;overflow:hidden}
/* Las tres franjas de arriba son de alto fijo: sin esto, `flex` las encoge
   para hacerle sitio al contenido y la barra de estado se aplasta. */
.topbar,#avisos,.ops{flex:none}
.topbar{display:flex;align-items:center;gap:14px;padding:13px 26px;border-bottom:1px solid var(--line);background:var(--topbar-bg);backdrop-filter:blur(6px);position:sticky;top:0;z-index:20}
.search{flex:1;max-width:440px;display:flex;align-items:center;gap:9px;background:var(--panel);border:1px solid var(--line);border-radius:9px;padding:7.5px 12px;color:var(--faint);cursor:pointer}
.search .kbd{margin-left:auto;font-family:var(--mono);font-size:10px;color:var(--brand);border:1px solid var(--line-2);border-radius:5px;padding:1px 5px}
.top-actions{margin-left:auto;display:flex;align-items:center;gap:10px}
.btn{display:inline-flex;align-items:center;gap:7px;font-weight:600;font-size:13px;padding:8px 14px;border-radius:9px;border:1px solid transparent;cursor:pointer;transition:.15s}
.btn-primary{background:var(--btn-primary-bg);color:var(--sobre-brand);box-shadow:var(--glow-btn)}
.btn-primary:hover{background:var(--btn-primary-hover)}
.btn-ghost{background:var(--panel);border-color:var(--line-2);color:var(--ink)}
/* El botón ÁMBAR: una acción que no es una etapa ni un guardado — «Generar
   oportunidad» en la incidencia, «Es una oportunidad» en el autopilot. Distinto
   de las etapas a propósito (2-sept-2026). */
.btn-warn{background:var(--panel);border-color:var(--warn);color:var(--warn)}
.iconbtn{width:35px;height:35px;border-radius:9px;border:1px solid var(--line);background:var(--panel);display:grid;place-items:center;color:var(--muted);cursor:pointer;position:relative}
.iconbtn .dot{position:absolute;top:8px;right:8px;width:6px;height:6px;border-radius:50%;background:var(--danger);box-shadow:0 0 6px var(--danger)}
/* la burbuja de la campanita: el número de tareas abiertas (1-sept) */
.iconbtn .ncamp{position:absolute;top:-5px;right:-5px;min-width:16px;height:16px;padding:0 4px;border-radius:9px;background:var(--danger);color:#fff;font:700 10px/16px var(--mono);text-align:center}

/* franja ticker («de vuelo») con scanline tokenizado */
.ops{position:relative;display:flex;align-items:center;border-bottom:1px solid var(--line);padding:0 26px;background:var(--ops-bg);overflow:hidden}
.ops::after{content:"";position:absolute;top:0;bottom:0;width:130px;background:linear-gradient(90deg,transparent,rgba(116,182,94,.08),transparent);animation:scan 6s linear infinite;pointer-events:none;opacity:var(--scan)}
@keyframes scan{from{left:-140px}to{left:110%}}
.ops>div{display:flex;align-items:center;gap:10px;padding:10px 22px 10px 0;margin-right:22px;border-right:1px solid var(--hair)}
.ops>div:last-child{border-right:none}
.ops .k{font-family:var(--mono);font-size:9.5px;letter-spacing:.18em;text-transform:uppercase;color:var(--faint)}
.ops .v{font-family:var(--mono);font-weight:600;font-size:12.5px;color:var(--ink);font-variant-numeric:tabular-nums}
.ops .v.up{color:var(--brand);text-shadow:var(--glow-text)}
.spark{width:88px;height:22px}
.spark polyline{fill:none;stroke:var(--brand);stroke-width:1.6;stroke-linecap:round}
.spark .fill{fill:var(--brand-dim);stroke:none}

/* EL ÚNICO SCROLLER de la aplicación. El centrado a 1240 px se hace con
   `padding` y no con `max-width` + `margin:auto` a propósito: si la caja
   que scrollea midiera 1240 px, su barra de scroll aparecería en mitad de
   la pantalla en un monitor ancho, en vez de pegada al borde. Así la caja
   ocupa todo el ancho —y su barra va al borde— pero el texto sigue
   centrado y con la misma medida de siempre. */
.content{
  flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain;
  padding:24px max(26px,calc((100% - 1240px) / 2)) 34px;width:100%;
  scrollbar-gutter:stable;
}
.page-head{display:flex;align-items:flex-end;justify-content:space-between;gap:20px;margin-bottom:20px;flex-wrap:wrap}
.eyebrow{font-family:var(--mono);font-size:9.5px;font-weight:600;letter-spacing:.2em;text-transform:uppercase;color:var(--brand)}
h1{font-family:var(--display);font-weight:700;font-size:25px;letter-spacing:-.015em;margin-top:7px}
.sub{color:var(--muted);margin-top:3px}
.sub b{color:var(--warn);font-weight:600}
.head-actions{display:flex;gap:10px}

/* KPI con anillo de progreso — strokes por TOKEN, nunca en el markup */
.kpis{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,232px),1fr));gap:12px;margin-bottom:12px}
a.kpi{cursor:pointer}
.kpi{background:var(--panel);border:1px solid var(--line);border-radius:var(--r);padding:15px 17px;display:flex;align-items:center;gap:14px;box-shadow:var(--kpi-inset);transition:.12s}
a.kpi:hover{border-color:var(--line-2);transform:translateY(-1px)}
.gauge{width:52px;height:52px;flex:none}
.gauge .track{fill:none;stroke:var(--gauge-track);stroke-width:5}
.gauge .arc{fill:none;stroke-width:5;stroke-linecap:round;transform:rotate(-90deg);transform-origin:50% 50%}
.gauge .arc.ok{stroke:var(--ok)} .gauge .arc.warn{stroke:var(--warn)} .gauge .arc.danger{stroke:var(--danger)} .gauge .arc.info{stroke:var(--info)}
.gauge text{font:600 13px var(--mono);fill:var(--ink)}
.kpi .lbl{font-family:var(--mono);font-size:9.5px;font-weight:600;letter-spacing:.16em;text-transform:uppercase;color:var(--faint)}
.kpi .val{font-family:var(--display);font-weight:700;font-size:25px;letter-spacing:-.02em;margin-top:4px;font-variant-numeric:tabular-nums}
.chip{display:inline-flex;align-items:center;gap:5px;font-family:var(--mono);font-size:10px;font-weight:600;border-radius:6px;padding:2px 7px;margin-top:4px}
.chip.ok{background:var(--ok-bg);color:var(--ok)}
.chip.warn{background:var(--warn-bg);color:var(--warn)}
.chip.danger{background:var(--danger-bg);color:var(--danger)}

.grid{display:grid;grid-template-columns:2fr 1fr;gap:12px;margin-bottom:12px}
.card{background:var(--panel);border:1px solid var(--line);border-radius:var(--r);min-width:0;box-shadow:var(--shadow)}
.card-h{display:flex;align-items:center;gap:10px;padding:14px 17px;border-bottom:1px solid var(--hair)}
.card-h h2{font-family:var(--display);font-weight:600;font-size:14px}
.card-h .hint{font-family:var(--mono);font-size:9.5px;letter-spacing:.12em;text-transform:uppercase;color:var(--faint)}
.card-h .link{margin-left:auto;font-size:12px;font-weight:600;color:var(--brand);cursor:pointer}
.card-b{padding:15px 17px}

.bars{width:100%;height:auto;display:block}
.bar{fill:var(--brand);opacity:.75}
.bar.cur{opacity:1;filter:var(--glow-bar)}
.grid-l{stroke:var(--grid-line);stroke-width:1}
.base-l{stroke:var(--line-2);stroke-width:1}
.avg-l{stroke:var(--faint);stroke-width:1;stroke-dasharray:3 4}
.ax{font:500 10px var(--mono);fill:var(--faint)}
.vlab{font:600 11.5px var(--mono);fill:var(--brand)}
.avlab{font:500 9px var(--mono);fill:var(--faint);letter-spacing:.06em}

.vrow{display:flex;align-items:center;gap:12px;padding:11px 17px;border-bottom:1px solid var(--hair)}
.vrow:last-child{border-bottom:none}
.vico{width:30px;height:30px;border-radius:8px;background:var(--panel-2);color:var(--brand);display:grid;place-items:center;flex:none;border:1px solid var(--line)}
.vico.hot{color:var(--danger);box-shadow:var(--glow-danger-soft)}
.vrow b{display:block;font-size:12.8px;font-weight:600}
.vrow span{font-size:11.5px;color:var(--muted)}
.days{margin-left:auto;font-family:var(--mono);font-weight:600;font-size:10.5px;border-radius:6px;padding:3px 8px;background:var(--panel-2);color:var(--muted);white-space:nowrap}
.days.warn{background:var(--warn-bg);color:var(--warn)}
.days.danger{background:var(--danger-bg);color:var(--danger);box-shadow:var(--glow-danger-soft)}

table{width:100%;border-collapse:collapse}
.tblwrap{overflow-x:auto}
thead th{font-family:var(--mono);font-size:9.5px;font-weight:600;letter-spacing:.14em;text-transform:uppercase;color:var(--faint);text-align:left;padding:10px 11px;border-bottom:1px solid var(--line);white-space:nowrap;background:var(--thead-bg)}
tbody td{padding:10.5px 11px;border-bottom:1px solid var(--hair);font-size:12.6px;white-space:nowrap}
thead th:first-child,tbody td:first-child{padding-left:17px}
thead th:last-child,tbody td:last-child{padding-right:17px}
tbody tr:last-child td{border-bottom:none}
tbody tr:hover{background:var(--panel-2)}
.ref{font-family:var(--mono);font-size:11px;color:var(--brand)}
td .com{font-weight:600}
td .svc{color:var(--muted)}
.pill{display:inline-flex;align-items:center;gap:6px;font-family:var(--mono);font-size:10px;font-weight:600;border-radius:6px;padding:3px 8px}
.pill::before{content:"";width:5px;height:5px;border-radius:50%;background:currentColor;box-shadow:0 0 var(--glow-pill) currentColor}
.pill.ok{background:var(--ok-bg);color:var(--ok)}
.pill.warn{background:var(--warn-bg);color:var(--warn)}
.pill.danger{background:var(--danger-bg);color:var(--danger)}
.pill.info{background:var(--info-bg);color:var(--info)}
.prio{font-family:var(--mono);font-size:10.5px;font-weight:600}
.prio.alta{color:var(--danger)} .prio.media{color:var(--warn)} .prio.baja,.prio.na{color:var(--faint);font-weight:500}
td.right,th.right{text-align:right}
td .when{color:var(--faint);font-family:var(--mono);font-size:10.5px}

.feed{padding:6px 17px 13px}
.ev{display:flex;gap:12px;padding:10px 0;position:relative}
.ev::before{content:"";position:absolute;left:12.5px;top:33px;bottom:-6px;width:1px;background:var(--line)}
.ev:last-child::before{display:none}
.evico{width:26px;height:26px;border-radius:50%;display:grid;place-items:center;flex:none;position:relative;z-index:1;background:var(--panel-2);border:1px solid var(--line)}
.evico svg.ic{width:12px;height:12px}
.evico.info{color:var(--info)} .evico.ok{color:var(--ok)} .evico.warn{color:var(--warn)} .evico.danger{color:var(--danger)}
.ev .t{font-size:12.5px}
.ev .meta{font-family:var(--mono);font-size:10px;color:var(--faint);margin-top:2px}

/* ---------- carcasa: avisos, demo y en-construcción ---------- */
/* ☠️ ESTO ERA `display:flex; align-items:center; gap:10px` Y PARTÍA EL TEXTO EN
   COLUMNAS (19-ago-2026, lo vio Jordi en el aviso de la migración 0028).
   Un banner es una FRASE, y una frase lleva trozos en <b> y algún <code>. En un
   contenedor flex cada uno de esos trozos —y cada nodo de texto suelto entre
   ellos— se convierte en un ÍTEM, así que la frase se leía en vertical, en
   columnas estrechas, con 10 px de separación entre pedazo y pedazo.
   Medido antes de tocarlo: 117 banners en el ERP, 27 con <b> o <code> dentro.
   O sea que estaban rotos los 27, no solo el que se vio.
   Se cura con `display:block`, que es lo que un párrafo quiere ser: el texto
   fluye, los <b> se quedan en línea y el emoji del principio va pegado a su
   frase (ya lleva su espacio en el marcado). Comprobado antes de cambiarlo que
   NINGÚN banner usa `margin-left:auto` ni ninguna otra cosa que dependa de que
   esto sea una fila flex — si algún día hace falta un botón empujado a la
   derecha, se le pone su propio contenedor y no se vuelve a hacer flex el
   párrafo entero. `overflow-wrap` porque aquí dentro caen rutas de archivo y
   mensajes de error largos, que no pueden empujar la página a lo ancho
   (cicatriz del scroll lateral). */
.banner{padding:9px 26px;font-family:var(--mono);font-size:11px;font-weight:600;letter-spacing:.08em;display:block;line-height:1.6;overflow-wrap:anywhere}
.banner code{font-family:inherit;opacity:.85;overflow-wrap:anywhere}
.banner.demo{background:var(--info-bg);color:var(--info)}
.banner.error{background:var(--danger-bg);color:var(--danger)}
.obra{display:grid;place-items:center;min-height:46vh;text-align:center}
.obra .oico{width:56px;height:56px;border-radius:14px;background:var(--panel);border:1px solid var(--line);display:grid;place-items:center;color:var(--brand);margin:0 auto 14px}
.obra .oico svg.ic{width:26px;height:26px}
.obra h2{font-family:var(--display);font-size:19px}
.obra p{color:var(--muted);max-width:420px;margin:6px auto 0}
.obra .miga{font-family:var(--mono);font-size:9.5px;letter-spacing:.18em;text-transform:uppercase;color:var(--faint);margin-top:12px}

@media(max-width:1120px){.kpis{grid-template-columns:repeat(2,1fr)}.grid{grid-template-columns:1fr}.ops{flex-wrap:wrap}}
@media(prefers-reduced-motion:reduce){*{transition:none!important;animation:none!important}}

/* ══════════ VOLVER ATRÁS, Y QUE EL MÓVIL TENGA MENÚ (19-ago-2026) ═══════
   Hasta hoy `@media(max-width:840px)` hacía `aside{display:none}` — y el
   <aside> contiene el menú ENTERO. En un teléfono la única forma de cambiar
   de módulo era el botón atrás del navegador, que era justo lo roto. Jaime
   (postventa) trabaja en la calle: para él el ERP ERA eso.
   Ahora el mismo <aside>, sin duplicar una sola línea de menú, se convierte en
   un cajón: fuera del flujo, fijo a la izquierda y desplazado. `visibility`
   además de `transform` para que el teclado no pueda tabular a un menú que no
   se ve (el transform solo lo mueve, no lo saca del orden de tabulación). */
#btn-menu{display:none}
#velo{display:none}
.miga-top{
  font-family:var(--mono);font-size:9.5px;font-weight:600;letter-spacing:.16em;
  text-transform:uppercase;color:var(--faint);
  /* minmax no aplica aquí (es flex), pero el suelo es el mismo bicho: sin
     min-width:0 una miga larga estira la barra y empuja el buscador fuera. */
  min-width:0;max-width:30ch;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}
#btn-atras[disabled]{opacity:.38;cursor:default}

/* La declaración del body va PEGADA a la media query, sin salto ni comentario
   en medio: hay un guardia en test/erp-calendario-pantalla.test.mjs que la
   busca literalmente así, y con razón — es la línea que quita el scroll
   lateral. (Las .kpis a dos columnas ya las pone el bloque de 1120px.) */
@media(max-width:840px){body{grid-template-columns:minmax(0,1fr)}
  #btn-menu{display:grid}
  .miga-top{display:none}
  .search .kbd{display:none}
  .topbar{padding:10px 14px;gap:8px}
  .content{padding:18px 14px 30px}
  /* Las barras de filtros (`.card-h`) y las acciones de cabecera son flex SIN
     wrap: en 375 px dos desplegables y un botón ya suman 700 px y empujaban la
     página ENTERA, devolviendo el scroll lateral que tanto costó quitar — el
     `.tblwrap` con su overflow-x nunca llegaba a hacer su trabajo porque el
     desbordamiento no venía de la tabla. Que bajen de línea. */
  .card-h,.head-actions,.page-head{flex-wrap:wrap}
  .card-h>input,.card-h>select{max-width:100%}
  .banner{padding:9px 14px}
  .ops{padding:0 14px}
  aside{
    position:fixed;left:0;top:0;bottom:0;height:100dvh;width:min(84vw,286px);
    z-index:60;display:flex;
    transform:translateX(-100%);visibility:hidden;
    transition:transform .18s ease,visibility .18s ease;
    box-shadow:0 0 40px rgba(0,0,0,.45);
  }
  body.menu-abierto aside{transform:none;visibility:visible}
  /* El velo NO lleva el atributo `hidden`: `[hidden]{display:none!important}`
     le ganaría a esta regla. Se apaga con opacidad y pointer-events. */
  #velo{display:block;position:fixed;inset:0;z-index:55;background:rgba(0,0,0,.5);
        opacity:0;pointer-events:none;transition:opacity .18s ease}
  body.menu-abierto #velo{opacity:1;pointer-events:auto}
}

/* ── Calendario · rejilla mensual/semanal (Ola 6 · T4) ─────────────── */
.calwrap{overflow-x:auto}
.calgrid{display:grid;grid-template-columns:repeat(7,minmax(96px,1fr));gap:1px;background:var(--line);border:1px solid var(--line);border-radius:10px;overflow:hidden;min-width:680px}
.calh{background:var(--panel-2);color:var(--faint);font-family:var(--mono);font-size:9px;letter-spacing:.18em;text-transform:uppercase;padding:6px 8px;text-align:center}
.cald{display:block;background:var(--panel);min-height:86px;padding:6px 7px;text-decoration:none;color:inherit}
.cald:hover{background:var(--panel-2)}
.cald.fuera{opacity:.38}
.cald.hoy{box-shadow:inset 0 0 0 1.5px var(--brand)}
.cald.hoy .caln{color:var(--brand);font-weight:700}
.caln{font-family:var(--mono);font-size:10.5px;color:var(--muted)}
.cald .pas{display:block;margin-top:4px;font-size:10.5px;line-height:1.25;padding:2px 6px;border-radius:6px;background:var(--panel-2);color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.pas.ok{background:var(--ok-bg);color:var(--ok)}
.pas.warn{background:var(--warn-bg);color:var(--warn)}
.pas.danger{background:var(--danger-bg);color:var(--danger)}
.pas.mas{background:transparent;color:var(--faint)}
.calsem .cald{min-height:170px}

/* revisión del lote de actas: fichas que se ENVUELVEN. Una tabla aquí pedía
   8 columnas de controles y obligaba a scroll lateral — con flex-wrap lo que
   no cabe baja de línea, y el móvil sale gratis. */
/* min-width:0 en TODA la cadena: el nombre del fichero va en nowrap y, sin
   esto, su ancho completo se propaga hacia arriba hasta empujar la columna
   1fr del body y devolver el scroll lateral. */
.lote{padding:2px 17px 13px;min-width:0}
.lote-f{padding:12px 0;border-bottom:1px solid var(--hair);min-width:0}
.lote-f:last-child{border-bottom:none}
.lote-cab{display:flex;align-items:center;gap:10px;margin-bottom:8px;min-width:0}
.lote-arch{flex:1 1 0;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.lote-ctrl{display:flex;flex-wrap:wrap;gap:8px 10px;align-items:end;min-width:0}
.lote-ctrl label{display:flex;flex-direction:column;gap:3px;min-width:0;font-family:var(--mono);font-size:9px;font-weight:600;letter-spacing:.14em;text-transform:uppercase;color:var(--faint)}
.lote-ctrl input,.lote-ctrl select{min-width:0}
.lote-ctrl .com{flex:1 1 230px}
.lote-ctrl .com input{width:100%}
.lote-f.mal{opacity:.72}
/* la casilla de publicar al portal: se lee como una frase, no como un control
   suelto — es una decisión sobre lo que ve un cliente, y tiene que verse. */
.pubcheck{display:inline-flex;align-items:center;gap:7px;cursor:pointer;
  font-family:var(--mono);font-size:10px;font-weight:600;letter-spacing:.1em;text-transform:uppercase;
  color:var(--muted);background:var(--panel-2);border:1px solid var(--line);border-radius:8px;padding:7px 11px}
.pubcheck:hover{border-color:var(--line-2);color:var(--ink)}
.pubcheck input{accent-color:var(--brand);cursor:pointer;margin:0}
/* las propuestas de comunidad cuando el casador no llega solo: se ofrecen,
   no se aplican. Ámbar y no verde a propósito — verde diría «ya está bien». */
.lote-sug{display:flex;flex-wrap:wrap;gap:6px;align-items:center;margin-top:8px;min-width:0}
.lote-sug .sub{font-family:var(--mono);font-size:9px;font-weight:600;letter-spacing:.14em;text-transform:uppercase;color:var(--faint)}
.chip-sug{font-family:var(--mono);font-size:10px;font-weight:600;border-radius:6px;padding:4px 9px;cursor:pointer;
  background:var(--warn-bg);color:var(--warn);border:1px solid transparent;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.chip-sug:hover{border-color:var(--warn)}
.chip-sug:focus-visible{outline:2px solid var(--warn);outline-offset:2px}

/* el banner .warn faltaba: hasta ahora solo existían .demo y .error */
.banner.warn{background:var(--warn-bg);color:var(--warn)}
.banner.ok{background:var(--ok-bg);color:var(--ok)}

/* ---------- usuarios y permisos ---------- */
/* [hidden] no ganaba a .modal{display:grid} ni a .banner{display:flex}: el
   atributo nativo pierde contra cualquier regla de clase con la misma
   especificidad, y ambas llegan después en la cascada — sin esto #usr-modal
   pintaba un velo negro permanente sobre toda la pantalla y los banners
   vacíos se veían igual. */
[hidden]{display:none!important}
.usr-lista{display:grid;gap:10px;margin-bottom:14px}
.usr-card{display:flex;align-items:center;gap:12px;padding:12px 14px}
.usr-card.usr-off{opacity:.55}
.usr-ava{width:38px;height:38px;border-radius:50%;display:grid;place-items:center;background:var(--brand-deep);color:var(--sobre-brand);font-weight:700;flex:none}
.usr-main{flex:1;min-width:0}.usr-main .sub{display:block}
.usr-acts{display:flex;gap:6px;flex:none}
.usr-del{color:var(--danger)}
.usr-rejilla{display:grid;gap:2px;margin-top:10px}
.usr-fila{display:grid;grid-template-columns:minmax(0,1fr) 78px 78px 78px;align-items:center;gap:8px;padding:5px 8px;border-radius:6px}
.usr-fila:nth-child(odd){background:var(--panel-2)}
.usr-fila.usr-sub{padding-left:26px}
.usr-fila.usr-huerfana{opacity:.45}
.usr-n em{display:block;font-style:normal;opacity:.6;font-size:.82em}
.usr-chk{white-space:nowrap;font-size:.9em}
.usr-pie{display:flex;gap:8px;margin:12px 17px 17px}
.modal{position:fixed;inset:0;z-index:50;background:rgba(0,0,0,.55);display:grid;place-items:start center;overflow-y:auto;padding:6vh 16px}
.usr-alta-card{max-width:440px;width:100%;display:grid;gap:10px;padding:16px}
.usr-alta-card label{display:flex;flex-direction:column;gap:4px;font-family:var(--mono);font-size:9px;font-weight:600;letter-spacing:.14em;text-transform:uppercase;color:var(--faint)}
.usr-alta-card input,.usr-alta-card select{font:inherit;color:var(--ink);background:var(--panel-2);border:1px solid var(--line-2);border-radius:8px;padding:8px 10px}
#usr-editor label{font-size:12.6px;color:var(--muted)}
#usr-editor select{font:inherit;color:var(--ink);background:var(--panel-2);border:1px solid var(--line-2);border-radius:8px;padding:5px 9px;margin-left:6px}

/* ══════════ QUE QUEPA EN LA PANTALLA (19-ago-2026) ═══════════════════════
   Jordi, sobre la ficha de incidencia: «suerte que he ido a la derecha y
   apareció eso del proveedor». El panel de la derecha estaba FUERA de la
   pantalla y no había forma de saberlo.

   La causa es la misma cicatriz de siempre, y por eso se cura de raíz en vez
   de a base de topes de altura: una columna de grid declarada `1fr` NUNCA baja
   del ancho mínimo de su contenido. Basta con que dentro caiga una cadena sin
   espacios —y en la ficha caía el `data-embedded-props='{"fileData":…}'` que
   el censo dejó pegado en `encargo.detalle`, 400 caracteres de un tirón— para
   que la columna izquierda se estire, empuje a la derecha fuera del viewport y
   el `.tblwrap` con su overflow-x nunca llegue a hacer su trabajo.
   `minmax(0,…)` le quita ese suelo. El body ya se curó así; faltaban las dos
   rejillas de dentro. */
.grid{grid-template-columns:minmax(0,2fr) minmax(0,1fr)}
/* AUTO-FIT y no `repeat(4,…)` desde el 22-ago-2026. Motivo: el Resumen ya no
   pinta cuatro tarjetas siempre — desde que cada perfil ve SU panel y se le
   quitan los indicadores de pantallas que no puede abrir, hay perfiles que se
   quedan con dos o con tres. Con cuatro columnas fijas, esas tarjetas salían
   descuadradas y con un hueco a la derecha que parece una avería. Es además el
   patrón que ya usaban a mano La caja, El libro y Toca facturar: ahora vive en
   un solo sitio. El `min(100%,…)` es la cura de siempre del scroll lateral. */
.kpis{grid-template-columns:repeat(auto-fit,minmax(min(100%,232px),1fr))}
@media(max-width:1120px){
  .kpis{grid-template-columns:repeat(2,minmax(0,1fr))}
  .grid{grid-template-columns:minmax(0,1fr)}
}
/* El suelo no viaja solo por el grid: cualquier hijo en la cadena lo vuelve a
   levantar si no se le dice que puede encoger. */
.card,.card-h,.card-b,.grid>*,.kpis>*{min-width:0}

/* Y el cinturón para el texto que ya está escrito: una cadena sin espacios
   (un JSON pegado, una URL kilométrica, un nombre de fichero) se parte donde
   sea antes que romper la página. Solo en prosa — las tablas y los importes
   siguen mandando su ancho. */
.card-b p,.card-b pre{overflow-wrap:anywhere}

/* ── la tabla que cabe ──────────────────────────────────────────────────
   `tbody td` es `white-space:nowrap` para todas las tablas del ERP, y está
   bien: un importe o una fecha partidos en dos líneas se leen fatal. Pero en
   la bandeja de incidencias hay DOS columnas de texto libre (la dirección de
   la comunidad y el «qué pasa») que entre las dos se llevan media pantalla y
   sacan el estado fuera. `max-width:0` sobre una tabla `width:100%` es el
   truco de toda la vida: la celda deja de reclamar su ancho natural, el
   navegador reparte lo que hay y lo que sobra se corta con puntos suspensivos.
   El texto entero sigue estando: se lee en la ficha, a un clic. */
td.trunc{max-width:0;overflow:hidden;text-overflow:ellipsis}

/* ══════════ EL ANCHO MANDA SOBRE EL DISEÑO (21-ago-2026) ═════════════════
   Jordi, mirando Contactos: «sigue saliendo el scroll lateral… a partir de
   ahora tienes que hacer las pantallas a la medida que no salga el scroll pero
   sin cortar mucha letra. Si la tienes que hacer más pequeña o más grande la
   línea hazlo.»

   La causa de ESTA vuelta del bicho no es la rejilla del body (curada) ni el
   párrafo hecho flex (curado en erp(147)): es que `table{width:100%}` con
   reparto AUTOMÁTICO no es un tope, es una sugerencia. El navegador mira el
   contenido, y un `paulina.carrion@brisasol.com` en la columna del correo
   ensancha la tabla por encima del 100 % de su caja; el `.tblwrap` con su
   `overflow-x` se limita entonces a poner la barra. Medido con
   test-ancho-erp.mjs el 21-ago: 20 pantallas del ERP se salían así, y con un
   correo largo en cada celda la peor llegaba a 3.774 px de más.

   `table-layout:fixed` cambia la pregunta: primero se decide el ancho de la
   tabla (el 100 % de su caja) y DESPUÉS se reparte entre las columnas. El
   contenido deja de mandar; lo que no cabe se corta con puntos suspensivos.
   El texto ENTERO se queda en el DOM —solo se recorta al pintar—, así que un
   lector de pantalla lo lee completo y se puede copiar y pegar; para el ratón,
   erp/ancho.js pone el `title` en las celdas que de verdad se han cortado.

   Y en el móvil NO: aplastar once columnas en 347 px no es «que quepa», es
   dejarlas ilegibles. Por debajo de 840 px la tabla vuelve al reparto natural
   y hace scroll DENTRO de su `.tblwrap`, que es exactamente para lo que está.
   Lo que no se perdona a ningún ancho es que se salga la PÁGINA. */
.tblwrap{overflow-x:auto;max-width:100%;min-width:0}
.tblwrap table{table-layout:fixed}
thead th,tbody td{overflow:hidden;text-overflow:ellipsis}

/* El reparto por defecto es a partes iguales, y eso está mal para dos tipos de
   columna. Un importe cortado es un número EQUIVOCADO —«1.234,5» en vez de
   «1.234,56 €»— y un botón cortado no se puede pulsar. Las dos se piden por
   clase en el `<th>` y se quedan con su ancho pase lo que pase; el resto del
   espacio se lo reparten las de texto, que sí se pueden cortar sin mentir. */
.tblwrap table th.col-eur{width:128px}
.tblwrap table th.col-fecha{width:104px}
.tblwrap table th.col-mini{width:78px}
/* Un trimestre: solo el importe y su estado en corto. Nace porque el cuadro de
   comisiones tiene CUATRO seguidas y con `col-mini` la tabla se salia 24 px de
   su caja (medido por test-ancho-erp.mjs). */
.tblwrap table th.col-tri{width:68px}
.tblwrap table th.col-acc{width:136px}
.tblwrap table th.col-acc2{width:184px}
.tblwrap table th.col-acc3{width:230px}
.tblwrap table th.col-ancha{width:24%}
/* Nada de `overflow:visible` para salvar un botón: lo que se sale de una celda
   sigue contando para el desbordamiento del `.tblwrap`, o sea que devolvería la
   barra por dentro. Estas columnas se arreglan con ANCHO, no dejándolas
   desbordar — y si el ancho no llega, quien lo canta es `test-recorte-erp.mjs`,
   NO el banco del scroll: con `overflow:hidden` un botón recortado no desborda
   ni un píxel, así que el que mide la barra no lo ve. (El 21-ago había 22
   controles recortados con las dos suites en verde y el scroll a cero.) */

/* La CABECERA se parte en dos líneas antes que cortarse. Un dato cortado se
   entiende igual («C.P. RAMBLA DEL CELL…»); una cabecera cortada deja la
   columna sin significado, que es peor: «Contribución/…» y «Estructura/…» se
   leen igual y no son lo mismo. Partirla no mueve la tabla ni un píxel, porque
   el ancho ya está decidido antes de mirar el contenido. */
thead th{white-space:normal;overflow-wrap:break-word}

/* Y el SUBTÍTULO de dentro de una celda también. En estas tablas la celda
   suele llevar el dato arriba y su explicación debajo («MEDIDO · media de 4 ·
   10.600,00 €/año en total»), y esa segunda línea heredaba el `nowrap` pensado
   para los importes: se perdían hasta 200 px de frase por celda. El dato de
   arriba sigue en una sola línea, que es lo que importa. */
tbody td .sub{white-space:normal}

/* Cuando una tabla lleva NUEVE o DIEZ columnas de euros no hay reparto que
   valga: «-223.490,00 €» no cabe en 109 px y un importe cortado es un número
   equivocado. La pantalla marca esa tabla como densa y se gana el sitio por
   donde dijo Jordi: letra un punto más pequeña y menos aire a los lados. */
.tblwrap table.tbl-densa thead th,.tblwrap table.tbl-densa tbody td{padding-left:7px;padding-right:7px}
.tblwrap table.tbl-densa tbody td{font-size:11.8px}
.tblwrap table.tbl-densa thead th{font-size:9px;letter-spacing:.1em}

/* Cuando el sitio aprieta, antes que cortar más letra se gana espacio por los
   lados y con la línea un punto más pequeña. Es literalmente lo que pidió
   Jordi, y son ~60 px por tabla de 8 columnas. */
@media(max-width:1120px){
  thead th,tbody td{padding-left:8px;padding-right:8px}
  tbody td{font-size:12.2px}
  thead th:first-child,tbody td:first-child{padding-left:13px}
  thead th:last-child,tbody td:last-child{padding-right:13px}
}
@media(max-width:840px){
  /* `width:max-content` en vez de `100%`: la tabla pide lo suyo y la barra sale
     DENTRO del `.tblwrap`. `min-width:100%` para que, cuando sobre sitio, siga
     llenando la tarjeta en vez de quedarse encogida a la izquierda. */
  .tblwrap table{table-layout:auto;width:max-content;min-width:100%}
  .tblwrap table th[class*="col-"]{width:auto}
}

/* ── y las filas de botones, que tampoco caben en un teléfono ──────────────
   Tres cosas que a 375 px sacaban la página unos pocos píxeles, medidas con
   test-ancho-erp.mjs: la ficha de usuario (avatar + datos + hasta CUATRO
   botones = 292 px solo de botones), la fila de estado del Resumen y de la
   ficha del administrador (`.vrow`: icono + texto + sello a la derecha) y el
   interior de un KPI estrecho. Pocos píxeles, pero el scroll
   lateral que sale es el mismo, y sale en TODA la pantalla. Que bajen de línea
   antes que empujar: es la misma cura que ya lleva `.card-h`. */
.usr-card,.usr-acts{flex-wrap:wrap}
.kpi>div{min-width:0}
.kpi .lbl,.kpi .val{overflow-wrap:anywhere}
@media(max-width:840px){.vrow{flex-wrap:wrap}}

/* Un botón dentro de una tabla no necesita el mismo aire que uno de cabecera:
   son ~20 px por botón, y en una columna de acciones con tres botones eso es la
   diferencia entre que quepan y que se corte el último. */
tbody td .btn{padding:5.5px 10px;font-size:12.2px}

/* Y la columna que es una FRASE, no un dato: se parte en varias líneas. Un
   «sin fecha límite: no puede vencer, así que no le avisa a nadie» cortado a
   los 40 caracteres no dice nada, y al lado sobra alto de sobra. */
.tblwrap table th.prosa,.tblwrap table td.prosa{white-space:normal;overflow-wrap:anywhere}
/* ⚠️ `prosa` hay que ponerla en el <th> Y en los <td>: en la cabecera solo
   decide el ancho de la columna, el `white-space` lo obedece cada celda. */
/* Y el botón redondo de icono: 35 px de lado es la medida de la barra de
   arriba, no la de una celda con tres botones al lado. */
tbody td .iconbtn{width:27px;height:27px;border-radius:7px}

/* ══════════ LO QUE SE PARTE, PARTIDO DE VERDAD (21-ago-2026, revisión) ═════
   Medido pantalla a pantalla con test-recorte-erp.mjs después del cambio de
   arriba: quitar el scroll dejó 47 columnas perdiendo más del 8 % de su texto
   y 22 controles RECORTADOS dentro de su celda. Estas cuatro reglas son las que
   no se arreglan columna a columna. */

/* Un `chip` o un `pill` es `inline-flex`, y una caja flex pide su ancho de
   contenido: dentro de una columna `prosa` seguía sin partirse y se recortaba
   igual («resuelta 5 días antes del límite» → «res…»). Con `max-width:100%`
   puede encoger, y entonces sí obedece el `white-space` de su celda. */
.tblwrap table td.prosa .chip,.tblwrap table td.prosa .pill{max-width:100%;white-space:normal;text-align:left}

/* La SEGUNDA línea de una celda es un subtítulo, se llame `sub` o se llame
   `svc` (el nº de cliente y la población debajo de la comunidad, el nombre del
   proveedor debajo del chip del servicio). Heredaba el `nowrap` de los importes
   igual que `sub`, y por el mismo motivo tiene que partirse. */
tbody td .svc{white-space:normal}

/* Una columna con un CAMPO EDITABLE dentro no se mide por su texto sino por su
   control: un `<input type="date">` del navegador pide 132 px, y en los 104 de
   `col-fecha` lo que se recortaba era el icono del calendario — o sea que no se
   podía abrir. `overflow:hidden` en la celda no avisa: no hay barra, hay tijera.
   (Cazado en Contratos, 13 campos de fecha, 27 px fuera cada uno.) */
.tblwrap table th.col-ed{width:152px}

/* ══════════════════════════════════════════════════════════════════════════
   LA CAJA (erp/caja.js) — la curva del saldo y la cascada del mes.
   REGLA DE LA CASA: ni un color literal. Todo sale de los tokens que ya
   existen (--ok entra, --danger sale, --brand la línea del saldo), así que
   el tema claro «Salvia» funciona sin una sola regla propia y el test de
   paridad de erp-tema.test.mjs no tiene nada nuevo que vigilar.
   ══════════════════════════════════════════════════════════════════════════ */

/* El lienzo manda sobre el ancho, nunca al revés: `width:100%` + `height:auto`
   sobre un viewBox fijo. Es la cura de la cicatriz del scroll lateral aplicada
   a un SVG — sin esto, un viewBox de 960 px empuja la página en un portátil. */
.cj-lienzo{width:100%;min-width:0;overflow:hidden}
.cj-svg,.cs-svg{width:100%;height:auto;display:block;overflow:visible}

/* — la curva — */
.cj-rej{stroke:var(--hair);stroke-width:1}
.cj-cero{stroke:var(--line-2);stroke-width:1.5}
.cj-eje{fill:var(--faint);font-family:var(--mono);font-size:9.5px}
.cj-area{fill:var(--brand);opacity:.10}
.cj-area.var{opacity:.06}
.cj-linea{fill:none;stroke:var(--brand);stroke-width:2.5;stroke-linejoin:round;stroke-linecap:round;filter:var(--glow-bar)}
.cj-in{fill:var(--ok);opacity:.55}
.cj-out{fill:var(--danger);opacity:.55}
.cj-pt{fill:var(--brand);stroke:var(--panel);stroke-width:1.5}
.cj-pt.neg{fill:var(--danger)}
.cj-pt.sel{stroke-width:2.5}
.cj-hit{fill:transparent;cursor:pointer}
.cj-mes{cursor:pointer}
.cj-mes:hover .cj-in,.cj-mes:focus .cj-in{opacity:.95}
.cj-mes:hover .cj-out,.cj-mes:focus .cj-out{opacity:.95}
.cj-mes:focus{outline:none}
.cj-mes:focus .cj-hit{fill:var(--hover-bg)}
.cj-mes.sel .cj-hit{fill:var(--active-bg)}
.cj-mes.sel .cj-in,.cj-mes.sel .cj-out{opacity:1}
.cj-mesetiq{fill:var(--faint);font-family:var(--mono);font-size:10px;text-transform:uppercase;letter-spacing:.06em}
.cj-mesetiq.sel{fill:var(--brand);font-weight:700}
/* El punto más bajo se anota SIEMPRE: es el número que decide cuánto colchón
   hace falta, y buscarlo a ojo en doce meses es justo lo que nadie hace. */
.cj-bajo{stroke:var(--warn);stroke-width:1;stroke-dasharray:3 3;opacity:.8}
.cj-bajoetiq{fill:var(--warn);font-family:var(--mono);font-size:10.5px;font-weight:700}

/* — la leyenda: cada concepto con su total del periodo — */
.cj-leyenda{display:flex;flex-wrap:wrap;gap:8px 18px;margin-top:12px;padding-top:12px;border-top:1px solid var(--hair)}
.cj-leg{display:flex;align-items:center;gap:7px;font-size:12px;min-width:0}
.cj-leg b{font-weight:600}
.cj-leg span:last-child{font-family:var(--mono);font-size:11.5px;color:var(--muted);font-variant-numeric:tabular-nums}
.cj-punto{width:9px;height:9px;border-radius:3px;flex:none;background:var(--faint)}
.cj-cobros .cj-punto{background:var(--ok)}
.cj-proveedores .cj-punto{background:var(--danger)}
.cj-estructura .cj-punto{background:var(--warn)}
.cj-comision .cj-punto{background:var(--info)}
.cj-iva .cj-punto{background:var(--muted)}

/* — la cascada del mes — */
.cs-cero{stroke:var(--line-2);stroke-width:1}
.cs-con{stroke:var(--faint);stroke-width:1;stroke-dasharray:2 3;opacity:.6}
.cs-b.ancla{fill:var(--brand);opacity:.30}
.cs-b.sube{fill:var(--ok);opacity:.85}
.cs-b.baja{fill:var(--danger);opacity:.85}
.cs-val{font-family:var(--mono);font-size:10.5px;font-weight:700;fill:var(--muted)}
.cs-val.sube{fill:var(--ok)}
.cs-val.baja{fill:var(--danger)}
.cs-val.ancla{fill:var(--ink)}
.cs-et{fill:var(--faint);font-family:var(--mono);font-size:9px;text-transform:uppercase;letter-spacing:.06em}

/* — las palancas — */
.cj-dos{display:grid;grid-template-columns:minmax(0,1.15fr) minmax(0,1fr);gap:12px;margin-top:12px}
.cj-palancas{display:flex;flex-direction:column;gap:11px}
.cj-campo{display:flex;flex-direction:column;gap:4px;min-width:0}
.cj-campo>span{font-family:var(--mono);font-size:9.5px;font-weight:600;letter-spacing:.13em;text-transform:uppercase;color:var(--faint)}
.cj-campo>span b{color:var(--warn);font-weight:600;letter-spacing:.06em;text-transform:none;cursor:help}
.cj-campo input{background:var(--panel-2);border:1px solid var(--line);color:var(--ink);border-radius:8px;padding:7px 10px;font-family:var(--mono);font-size:13px;width:100%;max-width:260px;min-width:0}
.cj-campo input:focus{outline:none;border-color:var(--line-2);box-shadow:var(--glow-active)}
.cj-check{display:flex;align-items:flex-start;gap:8px;font-size:12.5px;color:var(--muted);cursor:pointer}
.cj-check input{margin-top:2px;flex:none;accent-color:var(--brand)}
.cj-acciones{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-top:2px}

/* — los avisos: una lista, no un párrafo (la cicatriz de erp(147)) — */
.cj-avisos{margin:0;padding-left:18px;display:flex;flex-direction:column;gap:9px}
.cj-avisos li{font-size:12.5px;color:var(--muted);line-height:1.6;overflow-wrap:anywhere}
.cj-avisos li b{color:var(--ink);font-weight:600}

/* — la tabla — */
.cj-fila-rojo td{background:var(--danger-bg)}
.cj-cobros-t{color:var(--ok)}
.cj-proveedores-t,.cj-estructura-t,.cj-comision-t,.cj-iva-t{color:var(--muted)}

@media(max-width:900px){
  .cj-dos{grid-template-columns:minmax(0,1fr)}
  /* En móvil doce meses no caben legibles: el lienzo se hace scrollable EN SU
     CAJA (nunca la página) y el SVG recupera un ancho mínimo para no aplastar
     las etiquetas hasta el borrón. */
  .cj-lienzo{overflow-x:auto}
  .cj-svg{min-width:660px}
  .cs-svg{min-width:520px}
}

/* — LA PUERTA DEL SALDO: la primera pregunta de la pantalla, no una palanca
     escondida abajo. Sin saldo cargado se pinta en ámbar (falta un dato); con
     él, en verde y compacta. — */
.cj-puerta{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:14px 22px;
  background:var(--warn-bg);border:1px solid var(--warn);border-radius:var(--r);padding:14px 18px;margin-bottom:14px}
.cj-puerta.puesta{background:var(--ok-bg);border-color:var(--line-2)}
.cj-puerta>div:first-child{display:flex;flex-direction:column;gap:2px;min-width:0;flex:1 1 260px}
.cj-puerta .lbl{font-family:var(--mono);font-size:9.5px;font-weight:600;letter-spacing:.16em;text-transform:uppercase;color:var(--warn)}
.cj-puerta.puesta .lbl{color:var(--ok)}
.cj-puerta>div:first-child>b{font-family:var(--display);font-weight:700;font-size:21px;letter-spacing:-.01em;font-variant-numeric:tabular-nums}
.cj-fecha-saldo{font-size:12px;color:var(--muted);overflow-wrap:anywhere}
.cj-puerta-campos{display:flex;flex-wrap:wrap;align-items:center;gap:8px;min-width:0}
.cj-puerta-campos input{background:var(--panel);border:1px solid var(--line);color:var(--ink);border-radius:8px;
  padding:8px 11px;font-family:var(--mono);font-size:14px;min-width:0;width:150px}
.cj-puerta-campos input[type=date]{width:150px}
.cj-puerta-campos input:focus{outline:none;border-color:var(--line-2);box-shadow:var(--glow-active)}

/* — LA TIRA: doce meses, doce saldos. La curva enseña la forma; esto, el
     número. Nadie lee un eje para saber si llega a fin de mes. — */
.cj-tira{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,116px),1fr));gap:7px;margin-bottom:16px}
.cj-tarj{display:flex;flex-direction:column;align-items:flex-start;gap:2px;min-width:0;cursor:pointer;text-align:left;
  background:var(--panel-2);border:1px solid var(--line);border-radius:9px;padding:9px 10px;color:inherit;font:inherit;transition:.12s}
.cj-tarj:hover{border-color:var(--line-2);background:var(--hover-bg)}
.cj-tarj.sel{border-color:var(--brand);box-shadow:var(--glow-active)}
.cj-tarj.rojo{background:var(--danger-bg);border-color:var(--danger)}
.cj-t-mes{font-family:var(--mono);font-size:9.5px;font-weight:600;letter-spacing:.13em;text-transform:uppercase;color:var(--faint)}
.cj-tarj.sel .cj-t-mes{color:var(--brand)}
.cj-t-saldo{font-family:var(--display);font-weight:700;font-size:14.5px;font-variant-numeric:tabular-nums;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%}
.cj-tarj.rojo .cj-t-saldo{color:var(--danger)}
.cj-t-neto{font-family:var(--mono);font-size:10px;color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%}
.cj-t-neto.sube{color:var(--ok)}
.cj-t-neto.baja{color:var(--danger)}

/* ══════════════════════════════════════════════════════════════════════════
   EL AYUDANTE DE NEGOCIACIÓN de los comparativos (erp/comparativos.js).
   Escribes el precio de venta y debajo aparece hasta cuánto puedes pagarle al
   proveedor manteniendo el margen. Va pegado al campo, no en un lateral: se
   lee mientras se teclea, que es cuando sirve.
   Mono y tabular para que las cifras no bailen al cambiar de dígitos — con
   una fuente proporcional, «675,00» y «1.125,00» mueven el texto de sitio en
   cada tecla y molesta más de lo que parece.
   ══════════════════════════════════════════════════════════════════════════ */
.cmp-max{font-family:var(--mono);font-size:10.5px;line-height:1.5;color:var(--muted);
  padding:1px 2px 3px;margin-top:-2px;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}
.cmp-max b{color:var(--brand);font-weight:700}
.cmp-max[hidden]{display:none}

/* ⚠️ Y LA CICATRIZ DE SIEMPRE, otra vez, en el editor de comparativos.
   Medido el 22-ago-2026: las cuatro tarjetas miden 233 px y los <input> de
   dentro 245 — un input tiene `min-width:auto`, o sea el ancho de unos 20
   caracteres, y NO encoge por debajo aunque su rejilla sea más estrecha. Se
   salían 12 px cada uno y la página arrastraba 4 px a la derecha.
   Ya existía antes del cartel; el banco del ancho no lo veía porque medía el
   LISTADO de comparativos y no el EDITOR, que es otra ruta. Por eso el editor
   entra ahora en el banco. */
#e-bloques input,#e-bloques .cmp-max,#e-bloques label{min-width:0;max-width:100%}
/* Y el `min-width:0` NO bastaba, que es la parte que siempre se olvida: el
   `.card-b` de cada tarjeta es un `grid` SIN columnas declaradas, así que su
   única columna es `auto` y se dimensiona por el contenido más ancho — el
   input de 244 px. Con `minmax(0,1fr)` la columna deja de tener suelo y ya
   manda la tarjeta, no el campo. Es la misma cura de la cicatriz del scroll
   lateral, aplicada donde faltaba. */
#e-bloques .card-b{grid-template-columns:minmax(0,1fr)}

/* ☠️ IMPRIMIR (24-ago-2026). El armazón fijo de arriba es justo lo que NO
   quiere una impresora: con el body a `100dvh` y `overflow:hidden`, un Ctrl+P
   sacaría **una sola página** con lo que se ve, y el resto se pierde sin un
   aviso. Aquí se deshace el marco: el documento vuelve a fluir y se imprime
   entero. Se quitan también el menú y la barra, que en papel no sirven de
   nada y se comen la primera página.
   (El papel de las facturas NO pasa por aquí: `imprimir.js` monta su propio
   iframe y llama a su `print()`. Esto es para cuando alguien imprime la
   pantalla que está mirando.) */
@media print{
  html,body{height:auto;overflow:visible}
  body{display:block;background:none}
  aside,.topbar,.ops,#velo{display:none}
  main{height:auto;overflow:visible;display:block}
  .content{height:auto;overflow:visible;padding:0}
}

/* ── vacaciones y fiestas (spec 30-ago) ─────────────────────────────────────
   La rejilla de 7 días del mini-mes vive AQUÍ y no en un style en línea:
   A-9 prohíbe repeat(N,…) inline (un 1fr sin minmax no baja de su contenido
   y el estilo en línea le gana a cualquier media query — la cicatriz de
   Autopilot a 375px). */
.vac-mes{display:grid;grid-template-columns:repeat(7,minmax(0,1fr));gap:2px}
