/* VoltPlan — hoja de estilos base.
   Lenguaje visual tomado exactamente de Main.dc.html y Ordenes.dc.html:
   sidebar #12201a, acento #1fa35c, fondo #eef1ee, IBM Plex Sans/Mono.
   Nada de estilos inline en el HTML ni en el JS (CSP: style-src sin 'unsafe-inline'):
   todo vive aquí, como clases. */

:root {
  --sidebar-bg: #12201a;
  --sidebar-text: #e7eee9;
  --sidebar-muted: #9db3a6;
  --sidebar-foot: #748c7e;
  --sidebar-active-bg: rgba(46, 190, 110, .16);
  --sidebar-active-text: #8fe6b1;
  --sidebar-hover-bg: rgba(231, 238, 233, .06);
  --sidebar-divider: rgba(231, 238, 233, .1);

  --accent: #1fa35c;
  --accent-dark: #0f6b3c;
  --accent-ink: #0d1b13;
  /* Verde para superficies con texto blanco encima (botón principal, burbuja
     de Voltio): el --accent vivo da 3.25:1 con blanco y no llega a 4.5:1. El
     --accent se queda para acentos, íconos, bordes y barras. */
  --accent-strong: #168249;
  --accent-strong-hover: #13733f;
  --accent-strong-press: #0f6b3c;
  --on-accent: #ffffff;
  /* Relleno del filtro o del conmutador activo, con --on-accent encima. En
     claro es el mismo verde oscuro de --accent-dark; en oscuro se separan,
     porque ahí --accent-dark es un verde claro para enlaces. */
  --accent-activo: #0f6b3c;

  --bg: #eef1ee;
  --surface: #ffffff;
  --surface-muted: #f5f7f4;
  --surface-strong: #fbfcfa;
  --surface-sunken: #e2e8e2;

  /* Los tres pasan 4.5:1 sobre blanco y sobre --bg; el tenue es el mínimo. */
  --text: #1b2420;
  --text-muted: #505d56;
  --text-faint: #626f6a;
  --text-on-dark: #eef4ef;

  --border: #dee5df;
  --border-soft: #e9ede9;
  /* El borde de un campo es lo que dice "aquí se escribe": ≥ 3:1 sobre blanco.
     Botones y filtros usan --border-control, más suave: a ellos ya los
     identifica su texto. */
  --border-input: #8b978f;
  --border-input-hover: #6f7c74;
  --border-control: #c9d1cb;

  --amber-bg: #fdf3e2;
  --amber-text: #8f5300;
  --amber-border: #ecd3a4;
  --amber-dot: #d98a00;
  --amber-on-dark: #e8b45c;
  --amber-on-dark-bg: rgba(217, 138, 0, .25);

  --red-bg: #fceceb;
  --red-text: #b3261e;
  --red-border: #efc5c1;
  /* Relleno rojo con --on-accent encima (botón de peligro, globos de aviso). */
  --red-strong: #b3261e;
  --red-strong-hover: #921f18;
  --red-strong-press: #7d1a14;
  --red-on-dark: #e0574b;

  --green-bg: #e2f4e8;
  --green-text: #166b3d;
  --green-border: #bcdfc9;

  --blue-bg: #e5edf8;
  --blue-text: #2b5e9e;
  /* Violeta y turquesa (2026-10-01, #116): solo para el ícono de Retirar
     material y de Recibir sin orden en la tablet de Bodega (DISENO §3.17), que
     necesitaban un color propio. No son estados: los chips siguen con los
     cuatro de arriba. El tono es el de --ruta-5 y --ruta-6. Ícono sobre su
     fondo: violeta 6,2:1, turquesa 5,4:1. */
  --violeta-bg: #efe9f8;
  --violeta-text: #6b3fa0;
  --turquesa-bg: #e0f3f4;
  --turquesa-text: #006b75;
  --blue-on-dark: #5f9fe0;

  --scrim: rgba(13, 27, 19, .4);
  /* Los colores de las rutas (Rutas: el punto de cada tarjeta, sus marcadores y
     su línea en el mapa; re-prueba del 29/09, #77). */
  --ruta-1: #1b7f3b;
  --ruta-2: #1e5cb3;
  --ruta-3: #b3261e;
  --ruta-4: #8f5300;
  --ruta-5: #6b3fa0;
  --ruta-6: #00838f;
  --marker-shadow: 0 1px 3px rgba(0, 0, 0, .35);

  --font-sans: 'IBM Plex Sans', 'Segoe UI', system-ui, -apple-system, sans-serif;
  --font-mono: 'IBM Plex Mono', 'SFMono-Regular', Consolas, monospace;

  /* Tipografía por rol (MEJORAS-UI §11): cada rol con su tamaño, peso e
     interlineado. Seis tamaños en toda la app y nada por debajo de 12px. */
  --t-kpi: 600 28px/1.1 var(--font-sans);
  --t-titulo: 600 22px/1.25 var(--font-sans);
  --t-subtitulo: 400 13px/1.45 var(--font-sans);
  --t-seccion: 600 16px/1.35 var(--font-sans);
  --t-modal: 600 18px/1.3 var(--font-sans);
  --t-parrafo: 400 14px/1.5 var(--font-sans);
  --t-tabla: 400 13px/1.4 var(--font-sans);
  --t-tabla-enc: 600 12px/1.3 var(--font-sans);
  --t-etiqueta: 500 13px/1.3 var(--font-sans);
  --t-leyenda: 400 12px/1.4 var(--font-sans);
  --t-boton: 600 14px/1.2 var(--font-sans);
  --t-chip: 600 12px/1.2 var(--font-sans);
  --t-menu: 500 14px/1.3 var(--font-sans);

  /* Espaciado de 4 en 4 (§12). */
  --sp-1: 4px;
  --sp-2: 8px;
  --sp-3: 12px;
  --sp-4: 16px;
  --sp-5: 24px;
  --sp-6: 32px;

  /* Tres radios más la píldora (§13). --radius-xl queda como alias. */
  --radius-sm: 6px;
  --radius: 8px;
  --radius-lg: 12px;
  --radius-xl: var(--radius-lg);
  --radius-full: 999px;

  /* Tres niveles de elevación (§13): tarjeta, menú/aviso, modal. */
  --elev-1: 0 1px 2px rgba(16, 32, 24, .06), 0 1px 1px rgba(16, 32, 24, .03);
  --elev-2: 0 8px 20px rgba(16, 32, 24, .10), 0 2px 4px rgba(16, 32, 24, .05);
  --elev-3: 0 24px 56px rgba(16, 32, 24, .20), 0 4px 10px rgba(16, 32, 24, .08);
  --shadow-modal: var(--elev-3);
  --shadow-toast: var(--elev-2);

  /* Foco de teclado (§8) y movimiento (§16): una curva, dos duraciones. */
  --foco: #0f6b3c;
  --foco-halo: 0 0 0 3px rgba(22, 130, 73, .22);
  --dur-1: 120ms;
  --dur-2: 180ms;
  --ease: cubic-bezier(.2, .8, .2, 1);
  --press: scale(.98);
}

/* ---------- tema oscuro ----------
   Los mismos tokens con otros valores; lo activa tema.js poniendo
   <html data-tema="oscuro"> (por el sistema o por el botón de la cuenta).
   Los neutros son grises casi negros, sin tinte: el verde queda para el
   acento (botón principal, enlaces, lo activo), igual que en claro.
   Contraste medido sobre --surface: --text 14.5:1, --text-muted 8.2:1,
   --text-faint 5.8:1 (el mínimo, también sobre --surface-muted: 5.3:1),
   --border-input 3.3:1 y los textos de estado más de 7:1 sobre su fondo. */
:root[data-tema="oscuro"] {
  /* Las rutas, más claras para que el punto se vea sobre la tarjeta oscura. */
  --ruta-1: #4cc47f;
  --ruta-2: #5f9fe0;
  --ruta-3: #e0574b;
  --ruta-4: #e8b45c;
  --ruta-5: #a98bd6;
  --ruta-6: #3fb8c4;
  color-scheme: dark;

  --sidebar-bg: #121314;
  --sidebar-text: #e8e9ea;
  --sidebar-muted: #a3a7ab;
  --sidebar-foot: #7c8085;
  --sidebar-hover-bg: rgba(232, 233, 234, .06);
  --sidebar-divider: rgba(232, 233, 234, .1);

  --accent-dark: #5cc98a;
  --accent-activo: #168249;

  --bg: #0f1011;
  --surface: #18191b;
  --surface-muted: #202124;
  --surface-strong: #141517;
  --surface-sunken: #0a0a0b;

  --text: #e8e9ea;
  --text-muted: #aeb1b5;
  --text-faint: #909499;

  --border: #2c2e31;
  --border-soft: #242528;
  --border-input: #686c71;
  --border-input-hover: #878b90;
  --border-control: #38393d;

  --amber-bg: #33270f;
  --amber-text: #f0bb62;
  --amber-border: #5c4520;
  --amber-dot: #e69d1f;

  --red-bg: #3a1a19;
  --red-text: #f59a90;
  --red-border: #66302c;
  --red-strong: #c5362b;
  --red-strong-hover: #ad2e24;
  --red-strong-press: #96271f;

  --green-bg: #173323;
  --green-text: #7dd6a2;
  --green-border: #2b5a3e;

  --blue-bg: #182a40;
  --blue-text: #93b9ee;
  /* #116: violeta 7,3:1 y turquesa 8,0:1 sobre su fondo. */
  --violeta-bg: #2a2140;
  --violeta-text: #c3a9ec;
  --turquesa-bg: #0f3036;
  --turquesa-text: #6fd3dc;

  --scrim: rgba(0, 0, 0, .6);
  --marker-shadow: 0 1px 3px rgba(0, 0, 0, .6);

  /* En oscuro la sombra casi no se ve: separan los bordes, y la sombra queda
     para lo que flota (menú, aviso, modal). */
  --elev-1: 0 1px 2px rgba(0, 0, 0, .3);
  --elev-2: 0 8px 24px rgba(0, 0, 0, .45), 0 2px 4px rgba(0, 0, 0, .3);
  --elev-3: 0 24px 56px rgba(0, 0, 0, .6), 0 4px 10px rgba(0, 0, 0, .35);

  --foco: #5cc98a;
  --foco-halo: 0 0 0 3px rgba(92, 201, 138, .28);
}

/* ---------- reset / base ---------- */

*, *::before, *::after { box-sizing: border-box; }

html, body {
  height: 100%;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font: var(--t-parrafo);
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3, h4, p { margin: 0; }
/* La negrita de la app es 600 (DISENO §3.2): sin esto, <strong> sale en 700. */
strong, b { font-weight: 600; }

button {
  font-family: inherit;
  font-size: inherit;
  color: inherit;
}

a { color: var(--accent-dark); text-decoration: none; }
a:hover { color: var(--accent-dark); text-decoration: underline; }

input, select, textarea { font-family: inherit; font-size: inherit; }

/* Foco de teclado: un anillo verde oscuro separado 2px, igual en todos los
   controles. No toca el radio del elemento (antes lo cambiaba a 4px). */
:focus-visible {
  outline: 2px solid var(--foco);
  outline-offset: 2px;
}

/* Barras de desplazamiento (MEJORAS-UI S1–S3 y SB1): en gris medio, finas y
   redondeadas, en toda la app; más oscuras al pasar el mouse. Chrome, Edge y
   Safari toman ::-webkit-scrollbar; Firefox, scrollbar-color. En el teléfono
   las barras flotan y no cambian. */
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track,
::-webkit-scrollbar-corner { background: transparent; }
::-webkit-scrollbar-thumb {
  background-color: var(--border-input);
  background-clip: padding-box;
  border: 2px solid transparent;
  border-radius: var(--radius-full);
}
::-webkit-scrollbar-thumb:hover { background-color: var(--border-input-hover); }
@supports not selector(::-webkit-scrollbar) {
  * { scrollbar-width: thin; scrollbar-color: var(--border-input) transparent; }
}

/* El menú de la izquierda va sin barra: se sigue desplazando con la ruedita y
   el dedo, y el degradé de abajo (.nav-list::after) avisa que hay más. */
.sidebar,
.nav-list { scrollbar-width: none; }
.sidebar::-webkit-scrollbar,
.nav-list::-webkit-scrollbar { display: none; }

[hidden] { display: none !important; }

.text-muted { color: var(--text-muted); }
.text-faint { color: var(--text-faint); }
.mono { font-family: var(--font-mono); }
.flex-row { display: flex; align-items: center; gap: 8px; }
.flex-between { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.flex-wrap { flex-wrap: wrap; }

/* ---------- shell layout ---------- */

/* El shell mide exactamente la ventana: la página nunca se desplaza, lo hacen
   el menú, el cuerpo de cada pantalla y el de cada modal. `dvh` es el alto que
   de verdad se ve en un teléfono (100vh cuenta la barra del navegador
   escondida y dejaba una franja para desplazar la página por detrás). */
#app-root {
  display: flex;
  height: 100vh;
  height: 100dvh;
  width: 100%;
  overflow: hidden;
}

.sidebar {
  /* 240px: con el menú a 14px, en 224px "Órdenes de instalación" se partía en dos. */
  width: 240px;
  flex-shrink: 0;
  background: var(--sidebar-bg);
  color: var(--sidebar-text);
  display: flex;
  flex-direction: column;
  padding: 16px 14px 16px;
  overflow-y: auto;
  overscroll-behavior: contain;
}

/* Logo centrado en la barra. Abajo 18px: con el logo a 22px de alto, la
   cabecera conserva la altura que tenía. */
.brand {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 12px 18px;
}

/* Logo oficial, versión de texto claro para la barra oscura. El PNG está al
   triple de este tamaño para que se vea nítido en pantallas de teléfono. */
.brand-logo {
  display: block;
  width: 121px;
  max-width: 100%;
  height: auto;
}
/* Marca blanca (src/marca.js): un logo propio va a la altura del oficial, con
   su ancho; sin logo, el nombre de la app escrito, en el claro de la barra. */
.brand-logo.brand-logo-propio { width: auto; height: 28px; }
.brand-nombre { font: var(--t-titulo); color: var(--sidebar-text); letter-spacing: -.01em; }

/* El menú es lo que se desplaza si no entra; el logo y la cuenta quedan fijos
   (MEJORAS-UI §25). El margen negativo le deja lugar al anillo de foco.
   overscroll-behavior: al llegar al final, la ruedita no sigue de largo y
   arrastra la página. */
.nav-list {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
  margin: 0 -4px;
  padding: 0 4px;
}

/* Si el menú no entra, un degradé abajo avisa que sigue, en vez de dejar una
   opción cortada a la mitad contra la línea de la cuenta. Es sticky: al llegar
   al final queda en su lugar, debajo de la última opción, sin tapar nada. El
   margen negativo le descuenta el gap para no agregar aire de más. */
.nav-list::after {
  content: '';
  position: sticky;
  bottom: 0;
  flex: none;
  height: 24px;
  margin-top: calc(-1 * var(--sp-1));
  background: linear-gradient(to bottom, transparent, var(--sidebar-bg));
  pointer-events: none;
}

.nav {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  border-radius: var(--radius);
  font: var(--t-menu);
  color: var(--sidebar-muted);
  cursor: pointer;
  border: 0;
  background: transparent;
  width: 100%;
  text-align: left;
  transition: background-color var(--dur-1) var(--ease), color var(--dur-1) var(--ease);
}

/* Las opciones son <a>: sin esto heredan el subrayado de a:hover. */
.nav:hover { color: var(--sidebar-text); background: var(--sidebar-hover-bg); text-decoration: none; }

.nav.active {
  background: var(--sidebar-active-bg);
  color: var(--sidebar-active-text);
  font-weight: 600;
}

.nav.active:hover { color: var(--sidebar-active-text); }

/* Sobre la barra oscura, el verde oscuro del foco casi no se ve (2.5:1):
   ahí el anillo va en el verde claro de la opción activa. */
.sidebar :focus-visible { outline-color: var(--sidebar-active-text); }

.nav svg { flex-shrink: 0; }

.nav-badge {
  margin-left: auto;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: .04em;
  background: var(--amber-on-dark-bg);
  color: var(--amber-on-dark);
  padding: 2px 6px;
  border-radius: var(--radius-sm);
}

.nav-disabled {
  cursor: not-allowed;
  opacity: .72;
}

.nav-disabled:hover { color: var(--sidebar-muted); background: transparent; }

/* Secciones del menú (§25). Una sección sin opciones visibles para el rol se
   oculta entera (app.js, applyNavPermissions). */
.nav-grupo { display: flex; flex-direction: column; gap: var(--sp-1); }

/* Secciones plegables (MEJORAS-UI MP1 y MP2, versión A): Análisis, Operación,
   Materiales y Planeación arrancan cerradas, salvo la de la pantalla actual, y
   cada una se abre y se cierra por su cuenta (app.js). El título es un botón:
   el rótulo en mayúsculas de siempre, con una flecha. Sin el gap de la
   sección: el espacio entre el título y las opciones lo pone el margen de cada
   opción, que queda dentro de la caja y se esconde con ella. */
.nav-plegable { gap: 0; }
.nav-grupo-titulo {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-2);
  width: 100%;
  min-height: 36px;
  margin-top: var(--sp-2);
  padding: 0 12px;
  border: 0;
  border-radius: var(--radius);
  background: transparent;
  font: var(--t-tabla-enc);
  letter-spacing: .06em;
  text-transform: uppercase;
  text-align: left;
  color: var(--sidebar-foot);
  cursor: pointer;
  transition: background-color var(--dur-1) var(--ease), color var(--dur-1) var(--ease);
}
.nav-grupo-titulo:hover { background: var(--sidebar-hover-bg); color: var(--sidebar-text); }
.nav-grupo-flecha { flex: none; }
.nav-grupo.abierto .nav-grupo-flecha { transform: rotate(90deg); }

/* Las opciones se despliegan con una transición de alto. Cerradas no se ven,
   no ocupan lugar y no se alcanzan con Tab. */
.nav-grupo-items { display: grid; grid-template-rows: 0fr; visibility: hidden; }
.nav-grupo.abierto .nav-grupo-items { grid-template-rows: 1fr; visibility: visible; }
.nav-grupo-items-in { min-height: 0; overflow: hidden; display: flex; flex-direction: column; }
.nav-grupo-items-in > * { margin-top: var(--sp-1); }

/* La animación, desde que la app ya se ve: la sección con que se entra aparece
   abierta sin moverse (app.js pone .nav-animada después del primer render). */
.sidebar.nav-animada .nav-grupo-flecha { transition: transform var(--dur-2) var(--ease); }
.sidebar.nav-animada .nav-grupo .nav-grupo-items { transition: grid-template-rows var(--dur-2) var(--ease), visibility 0s linear var(--dur-2); }
.sidebar.nav-animada .nav-grupo.abierto .nav-grupo-items { transition: grid-template-rows var(--dur-2) var(--ease); }

/* Si la sección de la pantalla actual se cierra a mano, su título queda en el
   verde de la opción activa: así se sabe dónde se está. */
.nav-grupo:not(.abierto):has(.nav.active) .nav-grupo-titulo { color: var(--sidebar-active-text); }

.nav-sep { height: 1px; margin: 8px 12px; background: var(--sidebar-divider); }

/* La cuenta, abajo y fija (§25 y MEJORAS-UI P1–P3): una tarjeta con las
   iniciales, el nombre, el rol y "Cerrar sesión", y debajo la firma de 9DC. */
.sidebar-footer {
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
  padding: var(--sp-4) 0 0;
  margin-top: var(--sp-2);
}

.cuenta-tarjeta {
  display: flex;
  flex-direction: column;
  border: 1px solid var(--sidebar-divider);
  border-radius: var(--radius-lg);
  background: var(--sidebar-hover-bg);
}

.cuenta-fila { display: flex; align-items: center; gap: 12px; min-width: 0; padding: 12px; }

.cuenta-avatar {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border-radius: var(--radius-full);
  background: var(--sidebar-active-bg);
  color: var(--sidebar-active-text);
  font: 600 14px/1 var(--font-sans);
  letter-spacing: .02em;
}

.cuenta-texto { min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.cuenta-nombre { font: 600 14px/1.3 var(--font-sans); color: var(--sidebar-text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cuenta-rol { font: var(--t-leyenda); color: var(--sidebar-muted); }

/* "Cerrar sesión", el último renglón de la tarjeta: 42px en escritorio; el
   bloque de ≤ 900 px lo sube a 44px. Las esquinas de abajo siguen las de la
   tarjeta, y el anillo de foco va por dentro para no salirse de ella. */
.cuenta-salir {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: 10px;
  width: 100%;
  min-height: 42px;
  padding: 0 12px 0 22px;
  border: 0;
  border-top: 1px solid var(--sidebar-divider);
  border-radius: 0 0 calc(var(--radius-lg) - 1px) calc(var(--radius-lg) - 1px);
  background: transparent;
  color: var(--sidebar-muted);
  font: var(--t-menu);
  cursor: pointer;
  transition: background-color var(--dur-1) var(--ease), color var(--dur-1) var(--ease),
              transform var(--dur-1) var(--ease);
}

.cuenta-salir:hover { background: var(--sidebar-divider); color: var(--sidebar-text); }
.cuenta-salir:active { transform: var(--press); }
.cuenta-salir:focus-visible { outline-offset: -2px; }
/* "Reportar un problema" (2026-09-25): el mismo renglón que "Cerrar sesión",
   pero en el medio de la tarjeta, así que sin las esquinas de abajo. */
.cuenta-reportar { border-radius: 0; }

/* La firma: "POWERED BY" chico y espaciado, y el logo de 9DC, centrados
   (MEJORAS-UI P3 y F1). El logo es el <symbol id="logo-9dc"> de index.html,
   con relleno currentColor: toma el color de donde se usa (acá, el gris claro
   de la barra; en el login, el gris oscuro). */
.powered { display: flex; align-items: center; justify-content: center; gap: 8px; padding: 0 8px 2px; }
.powered .firma-logo { color: var(--sidebar-muted); }
.firma-rotulo { font: 600 10px/1 var(--font-sans); letter-spacing: .14em; text-transform: uppercase; color: var(--sidebar-foot); }
.firma-logo { flex: none; display: block; width: auto; height: 20px; aspect-ratio: 349 / 410; fill: currentColor; }

/* El sprite con los símbolos SVG (el logo de 9DC): no ocupa lugar. */
.svg-sprite { position: absolute; width: 0; height: 0; overflow: hidden; }

.content {
  flex-grow: 1;
  display: flex;
  flex-direction: column;
  min-width: 0;
  height: 100vh;
  /* En un teléfono, 100vh cuenta el alto CON la barra de direcciones
     escondida, así que el composer del asistente quedaba debajo de ella.
     `dvh` es el alto que de verdad se ve; el 100vh de arriba es el respaldo
     para los navegadores que no lo soportan. */
  height: 100dvh;
}

.topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: var(--sp-4) var(--sp-5);
  border-bottom: 1px solid var(--border);
  background: var(--surface-strong);
  flex-shrink: 0;
}
.topbar-heading { min-width: 0; flex: 1; }

/* El botón del cajón. Solo existe en pantallas chicas (ver el bloque de móvil);
   arriba de 900px la barra lateral está siempre a la vista. */
.nav-toggle {
  display: none;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  margin-left: -8px;
  flex: none;
  border: 0;
  border-radius: var(--radius);
  background: transparent;
  color: var(--text);
  cursor: pointer;
}
.nav-toggle:hover { background: var(--surface-muted); }
/* La marca de la barra solo aparece con el cajón (≤ 900px). */
.topbar-marca { display: none; }

.nav-scrim {
  position: fixed;
  inset: 0;
  z-index: 89;
  background: var(--scrim);
}
/* Explícito a propósito: el `hidden` del HTML lo apaga la hoja del navegador,
   y basta con que alguien le ponga un `display` a .nav-scrim para que el velo
   quede pegado encima de todo sin que nadie entienda por qué. */
.nav-scrim[hidden] { display: none; }

.topbar-title {
  font: var(--t-titulo);
  letter-spacing: -.01em;
}

.topbar-sub {
  font: var(--t-subtitulo);
  color: var(--text-muted);
  margin-top: var(--sp-1);
}

.topbar-sub:empty { display: none; }

.topbar-actions {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-shrink: 0;
}

.screen-body {
  flex-grow: 1;
  overflow-y: auto;
  padding: var(--sp-5) var(--sp-6);
  display: flex;
  flex-direction: column;
  gap: 16px;
}

/* Los hijos no se encogen (MEJORAS-UI §31): en una columna flex con scroll,
   una caja con overflow (.tbl-wrap) se aplastaba por debajo de su contenido
   cuando había otra tabla larga debajo (Inventario). Voltio (.asis) sí se
   estira: su regla va más abajo y la pisa. */
.screen-body > * { flex-shrink: 0; }

/* Vacío (MEJORAS-UI §23): ícono, mensaje y, donde tiene sentido, una acción.
   El ícono sale del CSS, así que todo vacío de la app lo lleva. Los errores de
   carga usan la misma caja con .estado-error y un ícono de alerta. Es un
   bloque y no un flex: un mensaje con texto y etiquetas en línea no se parte. */
.empty-state {
  padding: var(--sp-6) var(--sp-5);
  text-align: center;
  color: var(--text-muted);
  font: var(--t-parrafo);
}

.empty-state::before {
  content: "";
  display: block;
  width: 44px;
  height: 44px;
  margin: 0 auto var(--sp-3);
  border-radius: var(--radius-full);
  background: var(--surface-muted) url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23626f6a' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'><path d='M3 13h4.5l1.5 2.5h6l1.5-2.5H21'/><path d='M5.5 5h13L21 13v5a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-5z'/></svg>") center / 22px no-repeat;
}

.empty-state.estado-error::before {
  background: var(--red-bg) url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23b3261e' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'><circle cx='12' cy='12' r='9'/><path d='M12 7.5v5.5M12 16.5v.01'/></svg>") center / 22px no-repeat;
}

.empty-titulo { display: block; margin-bottom: var(--sp-1); font: var(--t-seccion); color: var(--text); }
.empty-state .btn-secondary { margin-top: var(--sp-3); }

/* Carga (§23): siluetas grises con un brillo que pasa. El texto "Cargando…"
   sigue ahí para los lectores de pantalla, en transparente. */
.loading-state {
  position: relative;
  overflow: hidden;
  min-height: 140px;
  color: transparent;
  background-color: var(--surface);
  background-image:
    linear-gradient(var(--surface-sunken), var(--surface-sunken)),
    linear-gradient(var(--surface-sunken), var(--surface-sunken)),
    linear-gradient(var(--surface-sunken), var(--surface-sunken)),
    linear-gradient(var(--surface-sunken), var(--surface-sunken));
  background-size: 55% 12px, 85% 12px, 70% 12px, 40% 12px;
  background-position: 16px 24px, 16px 56px, 16px 88px, 16px 120px;
  background-repeat: no-repeat;
  border-radius: var(--radius-lg);
}

.loading-state::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, .7), transparent);
  transform: translateX(-100%);
  animation: silueta 1.4s var(--ease) infinite;
}

@keyframes silueta {
  to { transform: translateX(100%); }
}

.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--elev-1);
  padding: var(--sp-4) var(--sp-5);
}

.section-title {
  font: var(--t-seccion);
  color: var(--text);
}

/* Una sección de pantalla con título y conteo (MEJORAS-UI §31). */
.bloque { display: flex; flex-direction: column; gap: var(--sp-3); }
.bloque + .bloque { margin-top: var(--sp-4); }
.bloque-head { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: 4px 12px; }
.bloque-cuenta { font: var(--t-leyenda); color: var(--text-muted); }
.nota-tabla { margin: 0; font-size: 13px; line-height: 1.5; color: var(--text-muted); }
.nota-tabla strong { font-weight: 600; color: var(--text); }

/* El filtro de las órdenes de compra de Inventario (MEJORAS-UI IN1): la
   cantidad de cada estado, y arranca en "En camino". En tableta y teléfono
   toma los 16 y 44 px de todo lo que se escribe (bloque de ≤ 900px). */
.po-filtro {
  min-height: 43px; /* un campo: 43 (DISENO 3.4; iba 40, re-prueba del 29/09, #77) */
  padding: 0 var(--sp-3);
  border: 1px solid var(--border-input);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--text);
  font: var(--t-parrafo);
}
.bloque-head:has(.po-filtro) { align-items: center; row-gap: var(--sp-2); }
/* Filtros de Existencias (2026-09-25): el buscador y los dos filtros en un
   renglón, entre el título y la tabla; en el teléfono, uno por renglón. */
.inv-filtros { display: flex; flex-wrap: wrap; gap: var(--sp-2); align-items: center; }
/* El buscador, como el del Centro de control (.cc-buscar-in): la caja con la lupa.
   .buscador es el mismo en Solicitudes y Órdenes (A.buscador, 2026-09-30). */
.inv-buscar { flex: 1 1 240px; }
.inv-buscar, .buscador { min-width: 0; display: flex; align-items: center; gap: var(--sp-2); min-height: 43px; padding: 0 var(--sp-3); background: var(--surface); border: 1px solid var(--border-input); border-radius: var(--radius); color: var(--text-faint); }
.inv-buscar:hover, .buscador:hover { border-color: var(--border-input-hover); }
.inv-buscar:focus-within, .buscador:focus-within { border-color: var(--accent-strong); box-shadow: var(--foco-halo); }
.inv-buscar svg, .buscador svg { flex: none; }
.inv-buscar input, .buscador input { flex: 1 1 auto; min-width: 0; padding: 0; border: 0; background: none; font: var(--t-parrafo); color: var(--text); }
.inv-buscar input:focus, .buscador input:focus { outline: none; }
/* En Solicitudes y Órdenes va a la derecha de los filtros, alineado con el
   borde derecho de la tabla; si no entra en el renglón, baja y sigue a la
   derecha. En el teléfono, a todo el ancho debajo de la lista de estados. */
.filtros-solicitudes > .buscador, .filtros-ordenes > .buscador { flex: 1 1 240px; max-width: 320px; margin-left: auto; }
/* El filtro, una lista desplegable con la cantidad de cada estado (2026-09-30),
   como los de Existencias: del alto de un campo (43) y del ancho de su texto. */
.filtros-solicitudes > .po-filtro, .filtros-ordenes > .po-filtro { flex: 0 1 auto; }
/* La flecha de las listas desplegables va al final (busca "La flecha, propia"). */
.inv-filtros .po-filtro { flex: 0 1 auto; }
/* Con el filtro, la última orden a la vista no lleva línea abajo. */
.tbl-po tbody tr:not(:has(~ tr:not([hidden]))) td,
.tbl-solicitudes tbody tr:not(:has(~ tr:not([hidden]))) td,
.ce-tabla tbody tr:not(:has(~ tr:not([hidden]))) td { border-bottom: 0; }

/* ---------- botones ---------- */

.btn-primary,
.btn-secondary,
.btn-danger,
.btn-ghost {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  border-radius: var(--radius);
  font: var(--t-boton);
  border: 0;
  cursor: pointer;
  white-space: nowrap;
  transition: background-color var(--dur-1) var(--ease), border-color var(--dur-1) var(--ease),
              color var(--dur-1) var(--ease), transform var(--dur-1) var(--ease);
}

/* Fondo --accent-strong: el --accent vivo con texto blanco da 3.25:1. */
.btn-primary {
  background: var(--accent-strong);
  color: var(--on-accent);
  padding: var(--sp-3) var(--sp-4);
}

.btn-primary:hover { background: var(--accent-strong-hover); }
.btn-primary:active { background: var(--accent-strong-press); transform: var(--press); }
/* Un enlace con forma de botón (<a class="btn-…">, como "Pedir una
   instalación" del portal) no toma el verde oscuro ni el subrayado de los
   enlaces (a:hover) al pasar el mouse o al apretarlo: sobre el verde del botón
   principal, las letras no se leían. */
a.btn-primary:hover { color: var(--on-accent); text-decoration: none; }
a.btn-secondary:hover { color: var(--text); text-decoration: none; }
a.btn-ghost:hover { text-decoration: none; }

/* 11px + 1px de borde: el mismo alto que el botón principal. */
.btn-secondary {
  background: var(--surface);
  border: 1px solid var(--border-control);
  color: var(--text);
  padding: 11px var(--sp-4);
}

.btn-secondary:hover { background: var(--surface-muted); border-color: var(--border-input); }
.btn-secondary:active { background: var(--border-soft); transform: var(--press); }

.btn-danger {
  background: var(--red-strong);
  color: var(--on-accent);
  /* Sin borde, como el principal: 12 arriba y abajo para medir lo mismo que el
     "Cancelar" de al lado, que lleva 11 más el borde (MEJORAS-UI AG4). */
  padding: var(--sp-3) var(--sp-4);
}

.btn-danger:hover { background: var(--red-strong-hover); }
.btn-danger:active { background: var(--red-strong-press); transform: var(--press); }

.btn-ghost {
  background: transparent;
  color: var(--text-muted);
  padding: var(--sp-2) var(--sp-3);
}

.btn-ghost:hover { background: var(--surface-muted); color: var(--text); }
.btn-ghost:active { background: var(--border-soft); transform: var(--press); }

.btn-primary:disabled,
.btn-secondary:disabled,
.btn-danger:disabled,
.btn-ghost:disabled {
  opacity: .45;
  cursor: not-allowed;
  pointer-events: none;
}

.btn-sm { padding: var(--sp-2) var(--sp-3); font-size: 13px; }

/* Botón de ícono (§19): mismo tamaño en toda la app y un tooltip propio que
   lee su data-tip; el nombre para lectores de pantalla va en aria-label. El
   tooltip sale a la izquierda: arriba o abajo lo cortaría la caja de la tabla. */
.btn-icon {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  padding: 0;
  border: 0;
  border-radius: var(--radius);
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
  transition: background-color var(--dur-1) var(--ease), color var(--dur-1) var(--ease),
              transform var(--dur-1) var(--ease);
}

.btn-icon:hover { background: var(--surface-muted); color: var(--text); }
.btn-icon:active { transform: var(--press); }
.btn-icon svg { flex: none; width: 18px; height: 18px; }

.btn-icon[data-tip]::after {
  content: attr(data-tip);
  position: absolute;
  right: calc(100% + 6px);
  top: 50%;
  transform: translateY(-50%);
  z-index: 5;
  padding: var(--sp-1) var(--sp-2);
  border-radius: var(--radius-sm);
  background: var(--sidebar-bg);
  color: var(--text-on-dark);
  font: var(--t-leyenda);
  white-space: nowrap;
  box-shadow: var(--elev-2);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur-1) var(--ease);
}

/* Con dedo no hay "encima": el tooltip se queda para mouse y teclado. */
@media (hover: hover) {
  .btn-icon[data-tip]:hover::after { opacity: 1; }
}
.btn-icon[data-tip]:focus-visible::after { opacity: 1; }

.acciones-fila { display: inline-flex; align-items: center; gap: var(--sp-1); }

/* Menú "⋯" de las acciones de una fila (§17). Lo abre y lo ubica app.js
   (App.menuFila): va fijo sobre la ventana, porque dentro de la tabla lo
   cortaría el overflow. El rojo de lo destructivo vive aquí y en la
   confirmación, no en la fila. */
.menu-fila { display: inline-flex; }
/* QA P7: las acciones de un registro en su modal ("Más acciones"), a la
   izquierda del pie; lo de guardar y cerrar queda a la derecha. */
.modal-footer-mas { margin-right: auto; }
/* Lo que rechaza o anula, solo, a la izquierda del pie (re-prueba del 29/09, #77). */
.modal-footer-izq { margin-right: auto; }

.menu-lista {
  position: fixed;
  z-index: 85;
  min-width: 184px;
  padding: var(--sp-1);
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--elev-2);
}

.menu-item {
  display: flex;
  align-items: center;
  width: 100%;
  padding: var(--sp-2) var(--sp-3);
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  font: var(--t-parrafo);
  color: var(--text);
  text-align: left;
  cursor: pointer;
  transition: background-color var(--dur-1) var(--ease);
}

.menu-item:hover,
.menu-item:focus-visible { background: var(--surface-muted); }
.menu-item:focus-visible { outline-offset: -2px; }
.menu-item-peligro { color: var(--red-text); }
.menu-item-peligro:hover,
.menu-item-peligro:focus-visible { background: var(--red-bg); }
.menu-item:disabled { color: var(--text-faint); background: transparent; cursor: not-allowed; }
/* Un paso que todavía no se puede dar pero se puede tocar para saber por qué (#123): gris como el deshabilitado. */
.menu-item[aria-disabled="true"] { color: var(--text-faint); background: transparent; cursor: not-allowed; }
.menu-sep { height: 1px; margin: var(--sp-1) 0; background: var(--border-soft); }

/* Texto solo para lectores de pantalla. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* ---------- filtros tipo pill ---------- */

.filter {
  display: inline-flex;
  align-items: center;
  padding: 10px var(--sp-4);
  border-radius: var(--radius-full);
  border: 1px solid var(--border-control);
  font: 500 13px/1.2 var(--font-sans);
  color: var(--text-muted);
  background: var(--surface);
  cursor: pointer;
  transition: border-color var(--dur-1) var(--ease), color var(--dur-1) var(--ease),
              transform var(--dur-1) var(--ease);
}

.filter:hover { border-color: var(--border-input); color: var(--text); }
.filter:active { transform: var(--press); }

/* Activo en el verde oscuro de la marca, igual que el conmutador (§21). */
.filter.active {
  background: var(--accent-activo);
  color: var(--on-accent);
  border-color: var(--accent-activo);
  font-weight: 600;
}

/* La lista desplegable del filtro de Viáticos (Órdenes y Solicitudes la usan en
   todos los anchos desde el 2026-09-30, .po-filtro) y los títulos de sección de
   los formularios largos (.modal-secciones: Órdenes y Part Data) solo se ven en
   el teléfono (MEJORAS-UI O1, O4 y PD2). */
.filtro-estado-wrap,
.form-sec { display: none; }

/* ---------- tablas ---------- */

.tbl-wrap {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--elev-1);
  overflow: auto;
}

.tbl {
  width: 100%;
  border-collapse: collapse;
  font: var(--t-tabla);
}

.tbl th {
  text-align: left;
  font: var(--t-tabla-enc);
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--text-muted);
  padding: var(--sp-3);
  background: var(--surface-muted);
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
  position: sticky;
  top: 0;
  z-index: 1;
}

.tbl td {
  padding: var(--sp-3);
  border-bottom: 1px solid var(--border-soft);
  vertical-align: middle;
  transition: background-color var(--dur-1) var(--ease);
}

.tbl tr:last-child td { border-bottom: none; }

.tbl tbody tr:hover td { background: var(--surface-muted); }

/* La primera columna también se queda fija en la computadora (MEJORAS-UI
   AG25): en una laptop de 1024 con el menú a la vista, varias tablas no entran
   y se desplazan dentro de su caja; así el cliente o el SKU siguen a la vista.
   Si la tabla entra, no cambia nada. En tableta y teléfono lo resuelve el
   bloque de ≤ 900 px, con la sombra que avisa que hay más. */
@media (min-width: 901px) {
  .tbl-wrap > .tbl:not(.tbl-barras):not(.tbl-deltas) :is(th, td):first-child {
    position: sticky;
    left: 0;
    z-index: 2;
    background: var(--surface);
  }
  .tbl-wrap > .tbl:not(.tbl-barras):not(.tbl-deltas) th:first-child { z-index: 3; background: var(--surface-muted); }
  .tbl-wrap > .tbl:not(.tbl-barras):not(.tbl-deltas) tbody tr:hover td:first-child { background: var(--surface-muted); }
}

.tbl-compact th,
.tbl-compact td { padding: 8px 8px; }

/* Montos, cantidades y fechas: la letra de siempre con cifras tabulares, que
   se alinean en columna sin el aspecto de terminal de la mono. Los códigos
   (SKU, OI-000123, VIN) sí siguen en mono. */
.num { font-family: var(--font-sans); font-variant-numeric: tabular-nums; text-align: right; }

th.num { text-align: right; }

/* Fechas, cantidades y montos en una sola línea (§20): si no entran, la tabla
   se desplaza dentro de su caja en vez de partirlos en dos renglones. */
.tbl td.num { white-space: nowrap; }

/* Fila que abre el detalle (§18): se toca en cualquier parte; con teclado se
   entra por el nombre, que es un botón. La lógica vive en cada pantalla. */
.tbl tr.fila-link { cursor: pointer; }
.tbl tr.fila-link:hover td { background: var(--surface-muted); }

.fila-titulo {
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  font-weight: 600;
  color: var(--text);
  text-align: left;
  cursor: pointer;
}

.fila-titulo:focus-visible { border-radius: 4px; }
.fila-chevron { width: 32px; text-align: right; color: var(--text-faint); }
.tbl tr.fila-link:hover .fila-chevron { color: var(--text); }

/* Un solo chip de estado; el riesgo de material es un ícono al lado (§20). */
.estado-celda { display: inline-flex; align-items: center; gap: var(--sp-2); white-space: nowrap; }
/* SLA (2026-09-13): en la computadora el chip va en la columna Estado; la celda
   propia solo existe para la tarjeta del teléfono (ver .tbl-ordenes abajo). */
.tbl-ordenes .t-sla { display: none; }
/* El número de serie que encontró el buscador (2026-10-03), debajo del folio:
   el número no se parte en el guion (DISENO §3.7). */
.tbl-ordenes .or-serie .mat-sku { white-space: nowrap; }

.riesgo {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  border-radius: var(--radius-full);
  background: var(--red-bg);
  color: var(--red-text);
}

.cell-low { background: var(--amber-bg); color: var(--amber-text); font-weight: 600; }

/* Faltante por tiempo (MEJORAS-UI §27, opción A): rosado solo donde un pedido
   hecho hoy ya no llega (el tiempo de entrega de la parte pasa el fin de esa
   semana); donde todavía hay tiempo, solo el número en rojo. La clase la
   decide App.claseFaltante. */
.cell-falta-tarde { background: var(--red-bg); color: var(--red-text); font-weight: 600; }
.cell-falta-atiempo { color: var(--red-text); font-weight: 600; }
.leg-num { color: var(--red-text); font-weight: 600; font-variant-numeric: tabular-nums; }

.mat-name { font-weight: 600; font-size: 13px; color: var(--text); }

.mat-sku {
  font-size: 12px;
  color: var(--text-faint);
  font-family: var(--font-mono);
  margin-top: var(--sp-1);
}

.c-main { font-weight: 600; font-size: 13px; color: var(--text); }

/* Catálogo BOM (MEJORAS-UI §32): un resumen por fila que se despliega. */
.bom-mats > summary {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-height: 32px;
  margin-left: -8px;
  padding: 0 var(--sp-2); /* 8 a los dos lados, en la escala (iba 10 a la derecha; re-prueba del 29/09, #77) */
  border-radius: var(--radius);
  list-style: none;
  cursor: pointer;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}
.bom-mats > summary::-webkit-details-marker { display: none; }
.bom-mats > summary:hover { background: var(--surface-muted); }
.bom-mats > summary::after {
  content: "";
  width: 12px;
  height: 12px;
  background: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' fill='none'><path d='M2 4l4 4 4-4' stroke='%23505d56' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/></svg>") center / 12px no-repeat;
  transition: transform var(--dur-1) var(--ease);
}
.bom-mats[open] > summary::after { transform: rotate(180deg); }
.bom-mats-cuenta { font-weight: 600; }
.bom-mats-costo { color: var(--text-muted); }
.bom-mats-lista { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: var(--sp-1) var(--sp-5); max-width: 420px; margin: 8px 0 4px; font: var(--t-tabla); }
.bom-mats-lista dt { color: var(--text); }
.bom-mats-lista dd { margin: 0; color: var(--text-muted); text-align: right; font-variant-numeric: tabular-nums; }

.c-sub { font-size: 12px; color: var(--text-faint); margin-top: var(--sp-1); }

.accion { font-size: 13px; color: var(--text-muted); }

.accion-red { font-size: 13px; color: var(--red-text); font-weight: 600; }

.accion-amber { font-size: 13px; color: var(--amber-text); font-weight: 600; }

.accion-sub { font-size: 12px; color: var(--text-faint); }

/* Planning (MEJORAS-UI PL1): el chip de estado es de la tarjeta del teléfono;
   en la computadora no se ve. El botón de la acción sugerida ("Enviar RFQ",
   "Revisar RFQs") deja 8 px con el texto de arriba (12 px en el teléfono). */
.tbl-planning .p-chip { display: none; }
.tbl-planning .p-accion > .btn-sm { display: flex; width: fit-content; margin-top: var(--sp-2); }

/* Planning en tableta y en laptops chicas (MEJORAS-UI PT1). Por material, la
   tabla mide su caja, no la ventana (como el ranking y el costo del
   Dashboard): si su caja mide menos de 960 px —tableta, o una laptop de 1024
   con el menú a la vista—, la acción sugerida baja a un segundo renglón de la
   fila, a todo el ancho, y el material, Hoy, En camino y las 8 semanas entran
   sin desplazarse de costado. Por BOM pasa lo mismo con la cobertura, pero
   solo en tableta: en una laptop su tabla ya entra. En el teléfono siguen las
   tarjetas; con más lugar, la tabla de siempre. */
.tbl-wrap:has(> .tbl-planning) { container: planning / inline-size; }

@media (min-width: 561px) {
  @container planning (max-width: 959px) {
    .tbl.tbl-planning:has(td.p-hoy),
    .tbl.tbl-planning:has(td.p-hoy) :is(thead, tbody) { display: block; }
    /* Apartado (2026-09-26) va entre Hoy y En camino. */
    .tbl.tbl-planning:has(td.p-hoy) tr { display: grid; grid-template-columns: minmax(0, 2.4fr) minmax(0, 1fr) minmax(0, 1.7fr) minmax(0, 1.4fr) repeat(8, minmax(0, 1fr)); align-items: stretch; border-bottom: 1px solid var(--border-soft); }
    .tbl.tbl-planning:has(td.p-hoy) tbody tr:last-child { border-bottom: 0; }
    /* Celdas más angostas a los lados (4) para que entren cinco cifras; los
       números, centrados en su alto y a la derecha, como en la tabla. Los
       títulos de columna se parten si no entran ("En camino"). */
    .tbl.tbl-planning:has(td.p-hoy) :is(th, td) { padding: var(--sp-2) var(--sp-1); border-bottom: 0; min-width: 0; }
    .tbl.tbl-planning:has(td.p-hoy) th { white-space: normal; }
    .tbl.tbl-planning:has(td.p-hoy) :is(th, td).num { display: flex; align-items: center; justify-content: flex-end; text-align: right; }
    .tbl.tbl-planning:has(td.p-hoy) :is(th, td):first-child { position: static; box-shadow: none; padding-left: var(--sp-3); }
    .tbl.tbl-planning:has(td.p-hoy) th:last-child { display: none; }
    /* La acción, abajo y a todo el ancho: el texto y el botón en un renglón,
       a 12 uno del otro y a 12 de la fila de arriba (sin ese aire el botón
       quedaba pegado a los números). Sin sugerencia no ocupa lugar. */
    .tbl.tbl-planning:has(td.p-hoy) td.p-accion { grid-column: 1 / -1; display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2) var(--sp-3); padding: var(--sp-3); white-space: normal; }
    .tbl.tbl-planning:has(td.p-hoy) td.p-accion > .btn-sm { margin-top: 0; }
    .tbl.tbl-planning:has(td.p-hoy) td.p-accion:has(> .accion:only-child) { display: none; }
  }
}

@media (min-width: 561px) and (max-width: 900px) {
  /* Por BOM, en tableta: el BOM (o el componente) y las 8 semanas; la
     cobertura, abajo y a todo el ancho. */
  .tbl.tbl-planning:not(:has(td.p-hoy)),
  .tbl.tbl-planning:not(:has(td.p-hoy)) :is(thead, tbody) { display: block; }
  .tbl.tbl-planning:not(:has(td.p-hoy)) tr { display: grid; grid-template-columns: minmax(0, 3fr) repeat(8, minmax(0, 1fr)); align-items: stretch; border-bottom: 1px solid var(--border-soft); }
  .tbl.tbl-planning:not(:has(td.p-hoy)) tbody tr:last-child { border-bottom: 0; }
  .tbl.tbl-planning:not(:has(td.p-hoy)) :is(th, td) { padding: var(--sp-2) var(--sp-1); border-bottom: 0; min-width: 0; }
  .tbl.tbl-planning:not(:has(td.p-hoy)) th { white-space: normal; }
  .tbl.tbl-planning:not(:has(td.p-hoy)) :is(th, td).num { display: flex; align-items: center; justify-content: flex-end; text-align: right; }
  .tbl.tbl-planning:not(:has(td.p-hoy)) :is(th, td):first-child { position: static; box-shadow: none; padding-left: var(--sp-3); }
  .tbl.tbl-planning:not(:has(td.p-hoy)) th:last-child { display: none; }
  .tbl.tbl-planning:not(:has(td.p-hoy)) td.p-accion { grid-column: 1 / -1; display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2) var(--sp-3); padding: var(--sp-3); white-space: normal; }
  .tbl.tbl-planning:not(:has(td.p-hoy)) td.p-accion:has(> .accion:only-child) { display: none; }
}

/* ---------- chips / pills / leyenda ---------- */

.chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: var(--sp-1) var(--sp-3);
  border-radius: var(--radius-full);
  font: var(--t-chip);
  white-space: nowrap;
}

.chip-green { background: var(--green-bg); color: var(--green-text); }
.chip-blue { background: var(--blue-bg); color: var(--blue-text); }
.chip-amber { background: var(--amber-bg); color: var(--amber-text); }
.chip-red { background: var(--red-bg); color: var(--red-text); }
.chip-neutral { background: var(--surface-muted); color: var(--text-muted); }

.pill {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 8px 14px;
  border-radius: var(--radius-full);
  font-size: 12px;
  font-weight: 600;
}

.pill-red { background: var(--red-bg); color: var(--red-text); }
.pill-amber { background: var(--amber-bg); color: var(--amber-text); }
.pill-green { background: var(--green-bg); color: var(--green-text); }

/* Estado del buzón (MEJORAS-UI §33): verde si la última revisión salió bien,
   rojo si falló, gris si no corrió. Lo pinta solicitudes.js. */
.estado-buzon {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 8px;
  align-self: flex-start;
  padding: 6px 12px;
  border: 1px solid var(--border);
  border-radius: var(--radius-full);
  background: var(--surface);
  font-size: 13px;
  line-height: 1.4;
  color: var(--text);
}
.estado-buzon .punto { flex: none; width: 8px; height: 8px; border-radius: var(--radius-full); background: var(--text-faint); box-shadow: 0 0 0 3px var(--surface-muted); }
.estado-buzon.estado-ok .punto { background: var(--accent); box-shadow: 0 0 0 3px var(--green-bg); }
.estado-buzon.estado-error .punto { background: var(--red-text); box-shadow: 0 0 0 3px var(--red-bg); }
.estado-buzon .sec { color: var(--text-muted); }

.dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; display: inline-block; }

/* Planning por BOM (AG1): los limitantes, en un renglón de texto debajo de
   las pills (un chip es de una a tres palabras). */
.pl-limitantes { flex-basis: 100%; margin: 0; font: var(--t-leyenda); color: var(--text-muted); }

/* Órdenes de compra (AG8): el atraso, como texto bajo la fecha y no como un
   segundo chip en la fila. */
.po-atraso { display: block; font: var(--t-leyenda); color: var(--red-text); }
.po-adelantada { color: var(--green-text); }

.leg { display: flex; align-items: center; gap: 7px; font: var(--t-leyenda); color: var(--text-muted); }

.leg-swatch { width: 12px; height: 12px; border-radius: 3px; display: inline-block; }

.leg-swatch-red { background: var(--red-bg); border: 1px solid var(--red-border); }
.leg-swatch-amber { background: var(--amber-bg); border: 1px solid var(--amber-border); }

/* ---------- toggle tipo segmented control ---------- */

.segmented {
  display: inline-flex;
  background: var(--surface-sunken);
  border-radius: var(--radius);
  padding: 3px;
}

.segmented-opt {
  padding: 8px 16px;
  border-radius: var(--radius-sm);
  font: 500 13px/1.2 var(--font-sans);
  color: var(--text-muted);
  border: 0;
  background: transparent;
  cursor: pointer;
  transition: background-color var(--dur-1) var(--ease), color var(--dur-1) var(--ease);
}

.segmented-opt.active {
  background: var(--accent-activo);
  font-weight: 600;
  color: var(--on-accent);
}

/* ---------- formularios ---------- */

.field {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  /* Un elemento de grilla o de flex arranca con `min-width: auto`, o sea que no
     se encoge por debajo del ancho de su contenido. Con un <select> adentro ese
     contenido es su opción más larga —"CHG-48 — Cargador Wallbox 48A"— así que
     el campo se salía de su columna y quedaba cortado. Se veía en el Simulador
     y en cualquier modal angosto. En escritorio no cambia nada: ahí los campos
     ya llenaban su celda sin desbordarla. */
  min-width: 0;
}
.field input,
.field select,
.field textarea {
  min-width: 0;
  max-width: 100%;
  box-sizing: border-box;
}

.field label {
  font: var(--t-etiqueta);
  color: var(--text-muted);
}

.field input,
.field select,
.field textarea {
  border: 1px solid var(--border-input);
  border-radius: var(--radius);
  padding: 10px var(--sp-3);
  background: var(--surface);
  color: var(--text);
  font: var(--t-parrafo);
  transition: border-color var(--dur-1) var(--ease), box-shadow var(--dur-1) var(--ease);
}

/* Re-prueba del 29/09 (#77): el control de fecha de Chrome suma 1 px arriba
   y abajo; sin eso mide 43, como los demás campos (DISENO 3.4). */
.field input[type="date"]::-webkit-datetime-edit,
.field input[type="date"]::-webkit-datetime-edit-fields-wrapper { padding: 0; }

.field input:hover,
.field select:hover,
.field textarea:hover { border-color: var(--border-input-hover); }

/* En los campos el foco es el borde verde más un halo (sin el anillo de
   afuera): se ve igual con mouse y con teclado. */
.field input:focus,
.field select:focus,
.field textarea:focus {
  outline: none;
  border-color: var(--accent-strong);
  box-shadow: var(--foco-halo);
}

.field input:disabled,
.field select:disabled,
.field textarea:disabled {
  background: var(--surface-muted);
  border-color: var(--border);
  color: var(--text-faint);
  cursor: not-allowed;
}

.field textarea { resize: vertical; min-height: 64px; }

.field-error {
  font-size: 12px;
  color: var(--red-text);
  min-height: 14px;
}

.field.has-error input,
.field.has-error select,
.field.has-error textarea {
  border-color: var(--red-text);
}

.form-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--sp-4);
}

.form-grid-1 { grid-template-columns: 1fr; }

/* Campo con su unidad adentro, a la derecha ("8 m", "12 ud"), para cuando la
   unidad depende de lo elegido. El número se alinea junto a la unidad y sin
   las flechitas del navegador. El relleno de la derecha lleva .field delante:
   así le gana también al `.field input` del bloque de ≤ 900 px. */
.campo-unidad { position: relative; display: flex; }
.campo-unidad input { width: 100%; text-align: right; appearance: textfield; -moz-appearance: textfield; }
.field .campo-unidad input { padding-right: 44px; }
.campo-unidad input::-webkit-outer-spin-button,
.campo-unidad input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.campo-unidad-texto {
  position: absolute;
  right: 12px;
  top: 50%;
  transform: translateY(-50%);
  font: var(--t-leyenda);
  color: var(--text-muted);
  pointer-events: none;
}

/* Líneas de material del BOM (MEJORAS-UI C1–C4): un renglón por línea con la
   parte, la cantidad con su unidad y la papelera al final; los rótulos una
   sola vez, arriba. La columna de la papelera pasa a 44px con el dedo (bloque
   de ≤ 900 px) y en el teléfono cada línea ocupa dos renglones (≤ 560 px).
   Desde el 2026-10-01 (#93) las mismas reglas valen para cualquier lista de
   renglones que se agregan y se quitan (`.lineas`, `.linea`…, DISENO 3.8): la
   primera son los contactos de una agencia. Cada lista pone sus columnas. */
.bom-lineas,
.lineas { display: flex; flex-direction: column; gap: var(--sp-3); }
.bom-lineas-cabeza,
.lineas-cabeza { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
.bom-lineas-cuenta,
.lineas-cuenta { font: var(--t-leyenda); color: var(--text-muted); }
.bom-lineas-lista,
.lineas-lista { display: flex; flex-direction: column; gap: 8px; }
.bom-lineas-rotulos,
.bom-linea,
.lineas-rotulos,
.linea {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 124px 40px;
  gap: 8px;
  align-items: start;
}
.bom-lineas-rotulos,
.lineas-rotulos { margin-bottom: -4px; font: var(--t-etiqueta); color: var(--text-muted); }
.bom-lineas-rotulos span:nth-child(2) { text-align: right; padding-right: 12px; }
/* Sin error, sin el renglón vacío debajo del campo (vuelve si hay error). */
.bom-linea .field-error:empty,
.linea .field-error:empty { display: none; }
.bom-linea-rotulo,
.linea-rotulo { display: none; }
/* El alto del campo, para que la papelera quede a su altura. */
.bom-linea-quitar,
.linea-quitar { width: 40px; height: 43px; }
.bom-linea-quitar:hover,
.linea-quitar:hover { color: var(--red-text); }
.linea-quitar:disabled,
.linea-quitar:disabled:hover { background: transparent; color: var(--text-faint); cursor: not-allowed; }
.bom-lineas-agregar,
.lineas-agregar { align-self: flex-start; margin-left: -12px; color: var(--accent-strong); font-weight: 600; }
.bom-lineas-agregar:hover,
.lineas-agregar:hover { color: var(--accent-strong-hover); }
/* Contactos de una agencia (#93): nombre, teléfono y correo en un renglón; el
   chip de la cuenta del portal, debajo del nombre, a 4 de su renglón (es un
   dato de ese contacto) y a 12 del siguiente. */
.ag-contactos .lineas-lista { gap: 12px; }
.ag-contactos .lineas-rotulos,
.ag-contactos .linea { grid-template-columns: minmax(0, 1fr) 136px minmax(0, 1.5fr) 40px; }
.ag-contactos .linea { gap: 4px 8px; }
.ag-contacto-cuenta { grid-column: 1 / -1; display: flex; }

/* Obligatorio (§24): asterisco en la etiqueta de un campo con required y una
   nota al pie del modal que lo explica. Si el navegador no soporta :has(), no
   aparecen y el required sigue validando igual. */
.field:has(:required) > label::after { content: " *"; color: var(--red-text); }
.modal-body:has(.field :required)::after { content: "* obligatorio"; font: var(--t-leyenda); color: var(--text-muted); }

/* Cambiar contraseña, en Configuración (MEJORAS-UI CF3): en una tarjeta, como
   las dos listas de la pantalla, con los dos campos de a dos; el botón y la
   nota de la política al pie. Sin el renglón vacío que cada campo reservaba
   para su error: los errores de este formulario llegan en un aviso. */
.cf-clave {
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
  padding: var(--sp-4) var(--sp-5);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--elev-1);
}
.cf-clave .form-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.cf-clave .field-error:empty { display: none; }
.cf-clave-pie { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2) var(--sp-4); }
.cf-clave-nota { font: var(--t-leyenda); color: var(--text-muted); }

/* El tema (claro u oscuro), en la misma tarjeta que la contraseña: el
   conmutador de siempre y la ayuda debajo, a 12. Los dispositivos confiables
   (2026-09-28) van en la misma tarjeta: la ayuda y el botón debajo, a 12. */
.cf-tema, .cf-dispositivos {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--sp-3);
  padding: var(--sp-4) var(--sp-5);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--elev-1);
}

.checkbox-row {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
}
/* La casilla con el verde de la app, como las del equipo en "Reprogramación"
   (Part Data, "Suplidor activo" en Suplidores); en el teléfono, 44 px para
   tocarla (preview aprobado el 2026-09-13). */
.checkbox-row input { flex: none; width: 18px; height: 18px; margin: 0; accent-color: var(--accent-strong); }
@media (max-width: 900px) {
  .checkbox-row { min-height: 44px; }
}

/* ---------- modal ---------- */

.modal-overlay {
  position: fixed;
  inset: 0;
  background: var(--scrim);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 80;
  padding: 20px;
}

.modal {
  background: var(--surface);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-modal);
  width: 100%;
  max-width: 560px;
  max-height: 90vh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.modal-confirm { max-width: 420px; }
/* QA P4: los dos botones de una confirmación, del mismo ancho, como en el
   teléfono: "Volver" no queda chico al lado del rojo. */
.modal-confirm .modal-footer > button { flex: 1 1 0; justify-content: center; }
/* Re-prueba del 29/09 (#77): con flex medían 181 y 179 (el de al lado lleva
   borde); en dos columnas iguales, exactamente lo mismo. */
.modal-confirm .modal-footer { display: grid; grid-template-columns: 1fr 1fr; }
/* Un modal con una tabla ancha (los movimientos de una parte, seis columnas):
   con 640 px la fecha va en un renglón y la tabla entra sin desplazarse. */
.modal.modal-ancho { max-width: 640px; }

/* Los formularios envuelven .modal-body + .modal-footer dentro de un <form>
   (patrón de todas las pantallas). Sin esto, el <form> crece más allá del
   modal y el pie con los botones queda recortado por overflow:hidden cuando
   el contenido es largo (p. ej. un BOM con muchas líneas). */
.modal > form {
  display: flex;
  flex-direction: column;
  min-height: 0;
  overflow: hidden;
}

/* El diálogo de confirmación vive en #confirm-root (al inicio del body); los
   modales de formulario se agregan al FINAL del body con el mismo z-index,
   así que sin esta regla el confirm queda pintado detrás del formulario. */
#confirm-root .modal-overlay { z-index: 110; }

/* Con un modal o el cajón del menú abiertos, la página de atrás no se mueve:
   se desplaza el modal (o el menú) y nada más. */
body:has(.modal-overlay),
body.nav-abierta { overflow: hidden; }

.modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--sp-4) var(--sp-5);
  border-bottom: 1px solid var(--border);
}

.modal-title { font: var(--t-modal); }

/* La "×" de cerrar: un botón chico (DISENO 3.4, .btn-ghost btn-sm: 34 en la
   computadora, 44 hasta 900). Medía 40 (re-prueba del 29/09, #77). */
.modal-header [data-close] {
  width: 34px;
  height: 34px;
  margin: -5px -5px -5px 0;
  padding: 0;
  font-size: 22px;
  line-height: 1;
  color: var(--text-muted);
}

.modal-body {
  padding: var(--sp-5);
  overflow-y: auto;
  overscroll-behavior: contain; /* al llegar al final no arrastra la página de atrás */
  min-height: 0; /* en columna flex, sin esto el body no encoge y no scrollea */
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
  font-size: 14px;
  color: var(--text);
}

/* En un modal nada se encoge (MEJORAS-UI SO2): con mucho contenido, la tabla
   de "Datos recibidos" de una solicitud quedaba de 2 px de alto. Se desplaza
   el cuerpo del modal, como la pantalla (§31). */
.modal-body > * { flex-shrink: 0; }

.modal-footer {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--sp-3);
  padding: var(--sp-4) var(--sp-5);
  border-top: 1px solid var(--border);
  flex-shrink: 0;
}

/* ---------- toasts ---------- */

.toast-container {
  position: fixed;
  bottom: var(--sp-5);
  right: var(--sp-5);
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  z-index: 120;
  max-width: 360px;
}

/* ---------- La alerta de la app ---------- */
/* UNA sola forma para todas las alertas (2026-09-17, elegida en preview entre
   once propuestas): tarjeta blanca, el estado en un círculo de color con su
   ícono, el rótulo en el color del estado y el mensaje en --text. El mensaje
   en negro es lo que no se negocia: es lo que deja leer una alerta larga.
   Antes cada una era una caja del color del estado con el mensaje también en
   ese color, con tres radios y dos rellenos distintos (uno fuera de la
   escala), y solo dos de las siete tenían ícono. */
.toast,
.op-aviso,
.asis-aviso,
.aviso-devuelta,
.devuelta-caja,
.pa-aviso,
.rp-aviso,
.ap-aviso,
.pa-deshacer,
.or-aviso-modal,
.so-pend-a {
  display: flex;
  flex-direction: row;
  align-items: flex-start;
  gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface);
  color: var(--text);
  font: var(--t-tabla);
}

/* El estado, en un círculo con su propio fondo: una mancha de color maciza
   sobre la tarjeta blanca. El ícono de 16 toma el color del círculo. */
.alerta-marca {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border-radius: var(--radius-full);
  background: var(--surface-muted);
  color: var(--text-muted);
}
.alerta-marca svg { flex: none; width: 16px; height: 16px; }
.alerta-cuerpo { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: var(--sp-1); }
/* El rótulo es el token de etiqueta en la negrita de la app, como .c-main. */
.alerta-rotulo { font: var(--t-etiqueta); font-weight: 600; }
/* Un correo o un enlace largo se parte donde haga falta (#95, VP-260929-965F p4):
   sin esto, el correo del reporte enviado empujaba el modal hacia el costado. */
.alerta-texto { font: var(--t-tabla); color: var(--text); overflow-wrap: anywhere; }

/* Los colores del estado, los mismos de los chips. El aviso flotante tiene los
   cuatro; las demás alertas tienen su estado fijo, así que lo llevan en la
   clase. El color nunca es la única señal (3.5): siempre hay ícono y rótulo. */
.toast-success .alerta-marca,
.rp-aviso .alerta-marca,
.pa-deshacer .alerta-marca { background: var(--green-bg); color: var(--green-text); }
.toast-success .alerta-rotulo,
.rp-aviso .alerta-rotulo,
.pa-deshacer .alerta-rotulo { color: var(--green-text); }

.toast-error .alerta-marca,
.aviso-devuelta .alerta-marca,
.devuelta-caja .alerta-marca,
.pa-aviso .alerta-marca,
.or-aviso-modal .alerta-marca { background: var(--red-bg); color: var(--red-text); }
.toast-error .alerta-rotulo,
.aviso-devuelta .alerta-rotulo,
.devuelta-caja .alerta-rotulo,
.pa-aviso .alerta-rotulo,
.or-aviso-modal .alerta-rotulo { color: var(--red-text); }

.toast-warning .alerta-marca,
.op-aviso .alerta-marca,
.asis-aviso .alerta-marca,
.ap-aviso .alerta-marca,
.so-pend-a .alerta-marca,
.or-aviso-modal.or-aviso-ambar .alerta-marca { background: var(--amber-bg); color: var(--amber-text); }
.toast-warning .alerta-rotulo,
.op-aviso .alerta-rotulo,
.asis-aviso .alerta-rotulo,
.ap-aviso .alerta-rotulo,
.so-pend-a .alerta-rotulo,
.or-aviso-modal.or-aviso-ambar .alerta-rotulo { color: var(--amber-text); }

.toast-info .alerta-marca { background: var(--blue-bg); color: var(--blue-text); }
.toast-info .alerta-rotulo { color: var(--blue-text); }

/* Lo propio del aviso flotante: la sombra y la animación de entrada. */
.toast {
  box-shadow: var(--shadow-toast);
  opacity: 0;
  transform: translateY(6px);
  transition: opacity var(--dur-2) var(--ease), transform var(--dur-2) var(--ease);
  cursor: pointer;
}

.toast.show { opacity: 1; transform: translateY(0); }

/* ---------- login ---------- */

/* Estructura de la referencia que pasó Fabiana, en la paleta de la app
   (MEJORAS-UI, sección "Login", versión B): marca centrada, título y
   subtítulo, etiquetas arriba de campos con ícono, el ojo para ver la
   contraseña y una línea de ayuda. */
.login-view {
  position: fixed;
  inset: 0;
  z-index: 100;
  background: var(--bg);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-5);
  padding: 40px 24px;
  overflow-y: auto;
}
/* Tienen display propio, así que el `hidden` del HTML no alcanza solo. */
.login-view[hidden],
/* El login es la excepción de la alerta (2026-09-17, elegido en preview): acá
   la alerta va DENTRO de la tarjeta blanca del login y sin nada más en la
   pantalla, así que una tarjeta blanca sobre blanco casi no se ve. Lleva el
   fondo del estado, el texto en ese color y el ícono suelto, como antes de
   unificar. En el resto de la app la alerta vive sobre el fondo gris de la
   pantalla, donde la tarjeta blanca sí se distingue (DISENO 3.6). */
.login-error,
.login-info {
  display: flex;
  flex-direction: row;
  align-items: flex-start;
  gap: var(--sp-2);
  padding: var(--sp-3);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  font: 500 14px/1.4 var(--font-sans);
}
.login-error { background: var(--red-bg); border-color: var(--red-border); color: var(--red-text); }
.login-info { background: var(--green-bg); border-color: var(--green-border); color: var(--green-text); }
.login-error svg,
.login-info svg { flex: none; width: 18px; height: 18px; margin-top: 1px; }
.login-error[hidden] { display: none; }
/* hidden en un <svg> no lo apaga la hoja del navegador (vale solo para HTML). */
.login-view svg[hidden] { display: none; }

/* Centrada con márgenes automáticos y no con justify-content: si la ventana es
   más baja que la tarjeta (un teléfono acostado), se desplaza en vez de
   cortarle la parte de arriba. */
.login-card {
  width: 100%;
  max-width: 400px;
  margin-top: auto;
  display: flex;
  flex-direction: column;
  gap: var(--sp-5);
  padding: 40px 32px 28px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--elev-2);
}

/* La marca sola, recortada al borde y al triple de este tamaño. */
.login-marca { display: block; width: 46px; height: auto; margin: 0 auto; }
/* La encuesta del consumidor: el símbolo de la empresa con su nombre (#92, DISENO 3.14). */
.encuesta-marca { display: flex; flex-direction: column; align-items: center; gap: var(--sp-2); }
.encuesta-simbolo { display: block; width: 48px; height: 48px; border-radius: var(--radius-full); }
.encuesta-marca-nombre { font: var(--t-leyenda); font-weight: 600; letter-spacing: .02em; color: var(--text-muted); }

.login-encabezado { display: flex; flex-direction: column; gap: var(--sp-1); }
.login-titulo { margin: 0; font: var(--t-titulo); letter-spacing: -.01em; color: var(--text); }
.login-sub { margin: 0; font: var(--t-parrafo); color: var(--text-muted); }

.login-campos { display: flex; flex-direction: column; gap: 18px; }
.login-campo { display: flex; flex-direction: column; gap: var(--sp-2); }
.login-campo label { font: 500 14px/1.3 var(--font-sans); color: var(--text); }

.login-control { position: relative; display: flex; align-items: center; }
.login-icono {
  position: absolute;
  left: 14px;
  width: 18px;
  height: 18px;
  color: var(--text-faint);
  pointer-events: none;
  transition: color var(--dur-1) var(--ease);
}
.login-control:focus-within .login-icono { color: var(--foco); }

/* 48px de alto y letra de 16px: en el iPhone, un campo con menos de 16px hace
   zoom al tocarlo. */
.login-control input {
  width: 100%;
  height: 48px;
  padding: 0 14px 0 42px;
  border: 1px solid var(--border-input);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--text);
  font: 400 16px/1.2 var(--font-sans);
  transition: border-color var(--dur-1) var(--ease), box-shadow var(--dur-1) var(--ease);
}
.login-control-clave input { padding-right: 52px; }
.login-control input:hover { border-color: var(--border-input-hover); }
.login-control input:focus { outline: none; border-color: var(--foco); box-shadow: var(--foco-halo); }
/* Vacíos, o usuario y contraseña incorrectos: en rojo (app.js, showLoginError). */
.login-control input[aria-invalid="true"] { border-color: var(--red-text); }
.login-control input[aria-invalid="true"]:focus { box-shadow: 0 0 0 3px rgba(179, 38, 30, .18); }

/* El ojo: 44×44 dentro del campo de 48, para tocarlo fácil en el teléfono. */
.login-ojo {
  position: absolute;
  right: 2px;
  width: 44px;
  height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--text-faint);
  cursor: pointer;
  transition: background-color var(--dur-1) var(--ease), color var(--dur-1) var(--ease);
}
.login-ojo:hover { background: var(--surface-muted); color: var(--text); }
.login-ojo:focus-visible { outline-offset: -2px; }
.login-ojo svg { width: 20px; height: 20px; }


.login-submit { width: 100%; height: 48px; font-size: 15px; gap: var(--sp-2); }
/* Mientras entra: apagado pero legible, no al 45% de un botón desactivado. */
.login-submit:disabled[aria-busy="true"] { opacity: .8; }
.login-spinner { width: 16px; height: 16px; animation: login-girar .8s linear infinite; }
@keyframes login-girar { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .login-spinner { animation-duration: 2.4s; } }

.login-ayuda { margin: 0; text-align: center; font: 400 13px/1.5 var(--font-sans); color: var(--text-muted); }

/* "Confiar en este dispositivo" (2026-09-28), debajo del código: la casilla de
   siempre con la letra del login (14, como sus etiquetas) y la ayuda a 4,
   alineada con el texto de la casilla (18 de la casilla + 8 de separación). */
.login-confiar { display: flex; flex-direction: column; gap: var(--sp-1); }
.login-confiar .checkbox-row { font: 400 14px/1.3 var(--font-sans); color: var(--text); }
.login-confiar-ayuda { margin: 0; padding-left: 26px; font: 400 13px/1.5 var(--font-sans); color: var(--text-muted); }

/* Identidad por correo (2026-09-16). Un enlace dentro del texto de ayuda del
   login ("¿Olvidaste tu contraseña?", "Reenviar código"): un botón que se ve
   como enlace, con el color de enlace de la app y 44 px de alto en el teléfono
   (lo da el relleno vertical, sin cambiar el renglón). */
.login-enlace {
  display: inline-block;
  padding: var(--sp-2) 0;
  border: 0;
  background: transparent;
  color: var(--accent-dark);
  font: 500 13px/1.5 var(--font-sans);
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 3px;
}
.login-enlace:hover { color: var(--accent-strong); }
.login-enlace:disabled { color: var(--text-faint); cursor: default; text-decoration: none; }
/* Un aviso que no es error (enlace enviado, contraseña guardada): el mismo
   cuadro del error, en los verdes de "salió bien". */
.login-info[hidden] { display: none; }
/* El código de 6 dígitos: en mono, grande y espaciado, para leerlo de un vistazo. */
.login-control input.login-codigo { font: 500 20px/1.2 var(--font-mono); letter-spacing: .3em; }

/* La encuesta del consumidor final (2026-09-18). Quinta tarjeta de #login-view,
   pero la ve alguien que NO es usuario de la app y que llega de un correo: una
   sola pregunta y un comentario. Los cinco puntajes son radios de verdad (se
   recorren con las flechas y los lee un lector de pantalla); lo que se ve es su
   etiqueta, porque un radio nativo no se puede pintar. */
.csat-puntajes { margin: 0; padding: 0; border: 0; }
.csat-puntajes legend { padding: 0 0 var(--sp-3); font: 500 14px/1.3 var(--font-sans); color: var(--text); }
.csat-opciones { display: flex; gap: var(--sp-2); }
.csat-opcion { position: relative; flex: 1 1 0; min-width: 0; }
/* El radio queda debajo de su etiqueta, no escondido con display:none: así
   sigue recibiendo el foco del teclado y el lector de pantalla lo anuncia. */
.csat-opcion input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}
.csat-opcion span {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 48px;
  border: 1px solid var(--border-control);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--text);
  font: 500 16px/1 var(--font-sans);
  transition: border-color var(--dur-1) var(--ease), background-color var(--dur-1) var(--ease), color var(--dur-1) var(--ease);
}
.csat-opcion input:hover + span { border-color: var(--border-input-hover); background: var(--surface-muted); }
.csat-opcion input:focus-visible + span { outline: none; border-color: var(--accent-strong); box-shadow: var(--foco-halo); }
.csat-opcion input:checked + span { background: var(--accent-strong); border-color: var(--accent-strong); color: var(--on-accent); }
/* Qué significan los extremos: sin esto, "3" no quiere decir nada. */
.csat-escala { display: flex; justify-content: space-between; gap: var(--sp-3); margin: var(--sp-3) 0 0; font: var(--t-leyenda); color: var(--text-faint); }
.csat-cuenta { margin: 0; font: var(--t-leyenda); color: var(--text-faint); }
#encuesta-comentario {
  width: 100%;
  box-sizing: border-box;
  resize: vertical;
  border: 1px solid var(--border-input);
  border-radius: var(--radius);
  padding: 10px var(--sp-3);
  background: var(--surface);
  color: var(--text);
  font: var(--t-parrafo);
  transition: border-color var(--dur-1) var(--ease), box-shadow var(--dur-1) var(--ease);
}
#encuesta-comentario:hover { border-color: var(--border-input-hover); }
#encuesta-comentario:focus { outline: none; border-color: var(--accent-strong); box-shadow: var(--foco-halo); }

/* La firma de 9DC del login: la misma del menú, en los grises de la página clara. */
.login-firma { display: flex; align-items: center; justify-content: center; gap: 8px; margin: 0 0 auto; font: var(--t-leyenda); letter-spacing: .02em; color: var(--text-faint); }
.login-firma .firma-rotulo { color: var(--text-faint); }
.login-firma .firma-logo { color: var(--text-muted); }

/* ---------- KPIs / dashboard ---------- */

/* Los paneles del Dashboard van de a dos solo si cada uno tiene 320 px o más
   (MEJORAS-UI DC1). Con 180, un teléfono grande (desde unos 404 px) ponía dos
   paneles de ~190 px: el gráfico no se leía y en el ranking los nombres se
   encimaban con los montos. Los indicadores (.kpi-resumen) tienen su propia
   regla, más abajo. */
.kpi-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 320px), 1fr));
  gap: var(--sp-4);
}

/* Los cuatro KPI del Dashboard (MEJORAS-UI T1): en 4 o en 2 columnas, nunca
   3 + 1. Con lugar para cuatro de 180px (4 × 180 + 3 × 16 de separación =
   768px) van en una fila; si no, dos por fila. (768px − 100%) × 999 es enorme
   si la grilla es más angosta y negativo si es más ancha, así que clamp()
   elige uno de sus dos extremos: 25% (cuatro columnas) o 50% (dos), menos la
   parte de la separación que le toca a cada columna. */
.kpi-resumen {
  grid-template-columns: repeat(auto-fit, minmax(clamp(calc(25% - 12px), (768px - 100%) * 999, calc(50% - 8px)), 1fr));
}

.kpi-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--elev-1);
  padding: var(--sp-4) var(--sp-5);
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}

.kpi-label {
  font: var(--t-leyenda);
  font-weight: 600;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.kpi-value {
  font: var(--t-kpi);
  font-variant-numeric: tabular-nums;
  letter-spacing: -.01em;
  color: var(--text);
}

.kpi-delta { display: flex; align-items: center; flex-wrap: wrap; gap: var(--sp-2); font: var(--t-leyenda); color: var(--text-muted); }
.kpi-delta-up { color: var(--green-text); }
.kpi-delta-down { color: var(--red-text); }

/* CSAT del consumidor final (2026-09-18), en el Dashboard. Reusa .kpi-value y
   .kpi-delta, pero sin la caja de .kpi-card: ya está dentro de un panel y dos
   bordes anidados se ven como un error. */
.csat-resumen { display: flex; flex-direction: column; gap: var(--sp-2); margin: var(--sp-3) 0; } /* 24 con el espacio del panel (iban 28; re-prueba del 29/09, #77) */
.csat-resumen .kpi-value { display: flex; align-items: center; gap: var(--sp-2); }
.csat-icono { flex: none; color: var(--accent); }
/* "de 5": el promedio sin su escala se puede leer como un porcentaje. */
.csat-de { font: var(--t-parrafo); color: var(--text-muted); }
/* Los enlaces que no salieron, arriba de la lista: es lo que hay que mirar
   antes que los puntajes si algo se rompió. */
.csat-envio { display: flex; align-items: center; flex-wrap: wrap; gap: var(--sp-2); margin-bottom: var(--sp-4); }
.csat-envio .c-sub { margin-top: 0; }
/* El comentario es texto libre de un consumidor: envuelve en vez de estirar la
   tabla, y se corta a tres renglones para que una fila no empuje a las demás. */
.tbl.tbl-csat .csat-comentario {
  white-space: normal;
  min-width: 220px;
  max-width: 420px;
  color: var(--text-muted);
}

/* La variación como chip con flecha (§22): la dirección la dice la flecha (y
   un texto oculto para lectores de pantalla), no el signo. */
.kpi-trend {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px var(--sp-2);
  border-radius: var(--radius-full);
  font: var(--t-chip);
  font-variant-numeric: tabular-nums;
}

.kpi-trend svg { flex: none; }
.kpi-trend-up { background: var(--green-bg); color: var(--green-text); }
.kpi-trend-down { background: var(--red-bg); color: var(--red-text); }
.kpi-trend-aviso { background: var(--amber-bg); color: var(--amber-text); }
.kpi-trend-neutro { background: var(--surface-muted); color: var(--text-muted); }

/* Los gráficos se dibujan al ancho real de su tarjeta (dashboard.js, §26). */
.grafico { display: block; }
.grafico text { font-variant-numeric: tabular-nums; }

/* "Otros" junta al resto de los clientes (MEJORAS-UI E2): va en gris y con su
   parte del total, para no leerse como un cliente más en la misma escala. */
.tbl-barras tr.b-otros td { color: var(--text-muted); }
.tbl-barras tr.b-otros .b-valor { font-weight: 600; }
.b-parte { font-weight: 400; white-space: nowrap; }

/* Ranking de clientes y de BOM (MEJORAS-UI D1 y T2): si su caja es angosta
   —teléfono, tableta o una laptop chica, donde a la barra le quedaban unos
   20px—, cada fila va en dos renglones: el nombre y el valor arriba, la barra
   a todo el ancho debajo. En una pantalla grande sigue siendo tabla. Pisa la
   primera columna fija que el bloque de 900px les pone a las tablas. */
.tbl-wrap-barras { container-type: inline-size; }
@container (max-width: 420px) {
  .tbl.tbl-barras,
  .tbl.tbl-barras tbody { display: block; width: 100%; }
  .tbl.tbl-barras tr {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    gap: 6px 12px;
    padding: 10px 14px;
    border-bottom: 1px solid var(--border-soft);
  }
  .tbl.tbl-barras tr:last-child { border-bottom: 0; }
  .tbl.tbl-barras td,
  .tbl.tbl-barras td:first-child {
    position: static;
    display: block;
    min-width: 0;
    padding: 0;
    border: 0;
    background: none;
    box-shadow: none;
    white-space: normal;
  }
  /* Lugares fijos: sin ellos la grilla manda el valor a un tercer renglón. */
  .tbl.tbl-barras .b-nombre { grid-column: 1; grid-row: 1; font-size: 14px; }
  .tbl.tbl-barras .b-valor { grid-column: 2; grid-row: 1; font-size: 14px; font-weight: 600; }
  .tbl.tbl-barras .b-barra { grid-column: 1 / -1; grid-row: 2; }
  .tbl.tbl-barras .b-barra svg { display: block; height: 8px; }
  .tbl.tbl-barras tbody tr:hover td { background: none; }
}

/* "Costo aproximado vs. real" (MEJORAS-UI D2): si su tarjeta es angosta
   —teléfono, tableta o una laptop chica, donde la tabla no entra y había que
   desplazarse de costado—, cada orden va en dos renglones: la orden y el delta
   arriba; el BOM y los dos montos abajo. Mide la caja de la tabla y no la
   ventana: en una pantalla grande, con lugar, sigue siendo tabla. El ::after
   es el salto de línea entre los dos renglones. */
.tbl-wrap-deltas { container-type: inline-size; }
@container (max-width: 480px) {
  .tbl.tbl-deltas thead { display: none; }
  .tbl.tbl-deltas,
  .tbl.tbl-deltas tbody { display: block; width: 100%; }
  .tbl.tbl-deltas tr {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 4px 12px;
    padding: 10px 14px;
    border-bottom: 1px solid var(--border-soft);
  }
  .tbl.tbl-deltas tr:last-child { border-bottom: 0; }
  .tbl.tbl-deltas tr::after { content: ''; order: 2; flex-basis: 100%; height: 0; }
  .tbl.tbl-deltas td,
  .tbl.tbl-deltas td:first-child {
    position: static;
    display: block;
    min-width: 0;
    padding: 0;
    border: 0;
    background: none;
    box-shadow: none;
    text-align: left;
  }
  .tbl.tbl-deltas .d-orden { order: 0; flex: 1 1 auto; font-size: 14px; }
  .tbl.tbl-deltas .d-delta { order: 1; font-size: 13px; font-weight: 600; }
  .tbl.tbl-deltas .d-dato { order: 3; font-size: 13px; color: var(--text-muted); }
  .tbl.tbl-deltas .d-dato[data-label]::before { content: attr(data-label) " "; }
  .tbl.tbl-deltas tbody tr:hover td { background: none; }
}

.panel {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--elev-1);
  padding: var(--sp-4) var(--sp-5);
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
}

.panel-title { font: var(--t-seccion); }

/* Simulador (MEJORAS-UI §30): cada línea es una fila (qué · cuánto · cuándo ·
   quitar) y "Simular" va a la derecha, donde termina la lectura. */
.sim-lineas { display: flex; flex-direction: column; gap: var(--sp-3); }
.sim-linea { display: grid; grid-template-columns: minmax(0, 1fr) 120px 180px 40px; gap: var(--sp-3); align-items: end; }
.sim-linea :is(input, select) { height: 40px; }
/* El error vacío ocupa lugar debajo del campo y descuadra el botón de quitar. */
.sim-linea .field-error:empty { display: none; }
.sim-linea .btn-icon { width: 40px; height: 40px; }
.panel > .btn-agregar { align-self: flex-start; }
.sim-pie { display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: var(--sp-3) var(--sp-4); }
.sim-pie-nota { margin-right: auto; font: var(--t-leyenda); color: var(--text-muted); }

/* ---------- responsive ---------- */

/* ---- Solicitudes por correo ---- */
.row-click { cursor: pointer; }
/* Lo que solo se ve en la tarjeta del teléfono ("3 líneas", "3 partes") y el
   renglón de lo que falta, que en la tabla va debajo del estado. */
.solo-tel { display: none; }
.tbl-solicitudes td.so-extra { display: none; }
.modal-body h4 { margin: var(--sp-4) 0 var(--sp-2); font: var(--t-tabla-enc); text-transform: uppercase; letter-spacing: .04em; color: var(--text-muted); }
.modal-body ul { margin: 0 0 0 var(--sp-4); padding: 0; }
/* Re-prueba del 29/09 (#77): sin el margen de abajo, de una lista al título
   siguiente hay 32, como de una tabla (antes 40). */
.msg { border: 1px solid var(--border); border-radius: var(--radius); padding: var(--sp-2) var(--sp-3); margin-bottom: var(--sp-2); }
.msg summary { cursor: pointer; font-size: 13px; }
.msg-body { white-space: pre-wrap; font: var(--t-leyenda); font-family: var(--font-mono); margin: var(--sp-2) 0 0; max-height: 260px; overflow: auto; }

/* ---- Calendario de instalaciones ---- */
.cal-head { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; }
.cal-title { font-weight: 600; font-size: 16px; text-transform: capitalize; min-width: 180px; text-align: center; }
.cal-grid { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 6px; }
.cal-dow { font-size: 12px; color: var(--text-muted); text-align: center; padding: 4px 0; }
.cal-cell { min-height: 78px; border: 1px solid var(--border); border-radius: 8px; padding: 6px 8px; background: var(--surface); position: relative; }
.cal-empty { border: none; background: transparent; }
.cal-sunday { background: var(--surface-muted); }
.cal-today .cal-num { font-weight: 700; text-decoration: underline; }
.cal-num { font-size: 12px; color: var(--text-muted); }
.cal-count { font-size: 22px; font-weight: 600; margin-top: 4px; }
.cal-cap { font-size: 12px; font-weight: 400; color: var(--text-muted); }
.cal-some { border-color: var(--green-text); }
.cal-full { background: var(--amber-bg); border-color: var(--amber-text); }
.cal-full .cal-count { color: var(--amber-text); }
.cal-over { background: var(--red-bg); border-color: var(--red-text); }
.cal-over .cal-count { color: var(--red-text); }
.cal-flag { position: absolute; right: 6px; bottom: 6px; font-size: 12px; text-transform: uppercase; letter-spacing: .04em; color: var(--red-text); font-weight: 600; }
.input-sm { width: 70px; padding: 4px 8px; }
.cal-rutas { margin-top: 4px; display: flex; flex-direction: column; gap: 2px; }
.cal-ruta { font-size: 12px; line-height: 1.2; border-radius: 4px; padding: 1px 4px; background: var(--surface-muted); color: var(--text-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.cal-ruta.ok { background: var(--green-bg); color: var(--green-text); }
.cal-ruta.bad { background: var(--red-bg); color: var(--red-text); }
/* En la escala de DISENO 3.10 (re-prueba del 29/09, #77: iban 6/14 y 6). */
.ops-list { display: flex; flex-wrap: wrap; gap: var(--sp-2) var(--sp-3); }
.ops-list label { display: flex; align-items: center; gap: var(--sp-2); font-size: 13px; }
.tbl-bad td { background: var(--red-bg); }
.cal-map { width: 100%; max-width: 640px; border-radius: 8px; border: 1px solid var(--border); display: block; }

/* ---- Calendario (resumen por día; el detalle vive en Rutas) ---- */
.cal-chips { margin-top: 4px; display: flex; flex-wrap: wrap; gap: 3px; }
.cal-chip { font-size: 12px; line-height: 1.3; border-radius: 4px; padding: 1px 5px; background: var(--surface-muted); color: var(--text-muted); white-space: nowrap; }
.cal-chip.neg { background: var(--blue-bg); color: var(--blue-text); }
.cal-chip.ok { background: var(--green-bg); color: var(--green-text); }
.cal-chip.bad { background: var(--red-bg); color: var(--red-text); }

/* ---- Calendario de compras (2026-10-19, js/calendario-compras.js) ----
   El mes en una grilla con las compras en camino de cada día. Cada día es una
   caja con 12 de relleno (3.10); cada compra, un renglón que se toca, separado
   del siguiente por la línea --border-soft. En el teléfono, solo los días con
   compras, como una lista de tarjetas. No usa las .cal-* del calendario viejo:
   son de antes de la escala. */
.pcal-head { flex-wrap: wrap; justify-content: flex-start; align-items: center; gap: var(--sp-2); }
.pcal-mes { min-width: 160px; text-align: center; }
.pcal-head .bloque-cuenta { margin-left: auto; }
/* El globo de las flechas va abajo, sobre los días de la semana: a la
   izquierda (el de .btn-icon) tapaba el nombre del mes. Como en el compositor
   de Voltio (3.15), se mueve el globo y no el botón. */
.pcal-head .btn-icon[data-tip]::after { right: auto; left: 50%; top: calc(100% + 6px); transform: translateX(-50%); }
.pcal-grid { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: var(--sp-2); }
.pcal-dow { font: var(--t-tabla-enc); color: var(--text-muted); text-transform: uppercase; letter-spacing: .04em; text-align: center; padding: var(--sp-1) 0; }
.pcal-dia { min-width: 0; min-height: 104px; display: flex; flex-direction: column; gap: var(--sp-2); padding: var(--sp-3); background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); }
.pcal-relleno { background: transparent; border-color: transparent; }
.pcal-num { font: var(--t-leyenda); color: var(--text-muted); }
.pcal-pasado .pcal-num { color: var(--text-faint); }
.pcal-hoy { border-color: var(--accent); }
.pcal-hoy .pcal-num, .pcal-hoy .pcal-fecha { color: var(--text); font-weight: 600; }
.pcal-fecha { display: none; }
.pcal-po { display: flex; flex-direction: column; align-items: flex-start; gap: var(--sp-1); width: 100%; min-width: 0; padding: 0; border: 0; background: none; text-align: left; font: var(--t-leyenda); color: var(--text); cursor: pointer; }
.pcal-po + .pcal-po { border-top: 1px solid var(--border-soft); padding-top: var(--sp-2); }
.pcal-po-que { font-weight: 600; overflow-wrap: anywhere; }
.pcal-po-quien { color: var(--text-muted); overflow-wrap: anywhere; }
.pcal-po:hover .pcal-po-que { color: var(--accent-dark); text-decoration: underline; }
@media (max-width: 560px) {
  .pcal-grid { grid-template-columns: 1fr; gap: var(--sp-3); }
  .pcal-dow, .pcal-relleno, .pcal-dia-vacio, .pcal-num { display: none; }
  .pcal-dia { min-height: 0; padding: var(--sp-4); border-radius: var(--radius-lg); }
  .pcal-fecha { display: block; font: var(--t-tabla); font-weight: 600; color: var(--text); }
  .pcal-po { font: var(--t-tabla); min-height: 44px; justify-content: center; }
}

/* ---- Rutas del día / rango ---- */
.rutas-summary { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-1) var(--sp-2); margin-bottom: var(--sp-4); }
.rutas-layout { display: grid; grid-template-columns: minmax(0, 1fr) minmax(360px, 42%); gap: 16px; align-items: start; }
/* QA P10: 16 entre días (secciones con título, 3.10); 12 del título del día a sus rutas. */
.rutas-list { min-width: 0; display: flex; flex-direction: column; gap: var(--sp-4); }
.rutas-day-head { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2); padding: var(--sp-2) var(--sp-3); border-radius: var(--radius); cursor: pointer; }
.rutas-day-head:hover, .rutas-day-head.selected { background: var(--surface-muted); }
.rutas-day-head.selected { box-shadow: inset 0 0 0 2px var(--accent); }
.rutas-day-title { font: var(--t-seccion); }
/* Los datos del día en texto, después del chip de estado (2026-10-03): antes eran chips. */
.rutas-day-datos { font: var(--t-tabla); color: var(--text-muted); }
.rutas-total { font: var(--t-tabla); color: var(--text); }
/* La capacidad, en su renglón, debajo de los totales: un dato y su subtítulo (4). */
.rutas-capacidad { flex-basis: 100%; }
.rutas-day-motivo { flex-basis: 100%; font: var(--t-tabla); color: var(--text); }
/* 16 entre tarjetas; la tarjeta, como todas: radio 12, 16 de relleno y 12 entre sus bloques. */
.rutas-cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: var(--sp-4); align-items: stretch; margin-top: var(--sp-3); }
.ruta-card { display: flex; flex-direction: column; gap: var(--sp-3); min-width: 0; border: 1px solid var(--border); border-radius: var(--radius-lg); background: var(--surface); padding: var(--sp-4); cursor: pointer; transition: box-shadow var(--dur-1) var(--ease), opacity var(--dur-1) var(--ease); }
.ruta-card:hover { box-shadow: var(--elev-2); }
.ruta-card.selected { box-shadow: 0 0 0 2px var(--accent); }
.ruta-card.dimmed { opacity: .45; }
/* El estado de la ruta lo dicen sus chips (3.5): la tarjeta no cambia de
   borde ni lleva una línea de color al costado. */
.ruta-head { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-1) var(--sp-2); min-height: 32px; min-width: 0; }
.ruta-dot { flex: none; width: 12px; height: 12px; border-radius: var(--radius-full); background: var(--text-muted); }
.ruta-title { flex: none; font: var(--t-tabla); font-weight: 600; color: var(--text); }
/* El equipo entero: si no entra junto a "Cambiar equipo", baja de renglón (se cortaba "Diego R…"; re-prueba del 29/09, #77). */
.ruta-team { flex: 1 1 160px; min-width: 0; overflow-wrap: anywhere; font: var(--t-leyenda); color: var(--text-muted); }
.ruta-km { flex: none; font: var(--t-leyenda); color: var(--text-muted); font-variant-numeric: tabular-nums; }
.ruta-flags { display: flex; flex-wrap: wrap; gap: var(--sp-2); align-items: center; }
.ruta-stops { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--sp-2); }
/* Cada parada es una caja con fondo: 12 de relleno (3.10), 8 entre lo de adentro. */
.stop { display: flex; align-items: center; gap: var(--sp-2); min-height: 44px; padding: var(--sp-2) var(--sp-3); border-radius: var(--radius); background: var(--surface-muted); min-width: 0; }
.stop-n { flex: none; width: 24px; height: 24px; border-radius: var(--radius-full); background: var(--text); color: var(--surface); font: var(--t-chip); display: inline-flex; align-items: center; justify-content: center; }
.stop-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: var(--sp-1); }
.stop-l1, .stop-l2 { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.stop-l1 { font: var(--t-tabla); font-weight: 600; color: var(--text); }
.stop-l2 { font: var(--t-leyenda); color: var(--text-faint); }
.stop .chip { flex: none; }
.stop-empty { background: transparent; border: 1px dashed var(--border); }
.stop-empty .stop-n { background: var(--border); color: var(--text-muted); }
.stop-empty .stop-l1 { color: var(--text-faint); font-weight: 400; }
.ruta-head .btn-sm { flex: none; }
.chip-outline { background: transparent; border: 1px dashed var(--border-input); }
.stop-drag { cursor: grab; }
.stop-drag:active { cursor: grabbing; }
.stop.dragging { opacity: .4; }
.stop-sugerida { border: 1px dashed var(--amber-text); background: var(--amber-bg); }
/* La parada abre el resumen de su orden (2026-10-03): se toca entera y su
   título es un botón (.fila-titulo), como una fila que abre su detalle
   (DISENO §3.7). Al pasar el mouse, el borde de un campo, sin mover nada. */
.stop[data-id] { cursor: pointer; }
.stop[data-id]:hover { box-shadow: inset 0 0 0 1px var(--border-input-hover); }
.stop-l1 { display: flex; align-items: center; gap: var(--sp-2); min-width: 0; }
.stop-titulo { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* "Mover a…": un control chico de 32, como un .btn-sm (medía 24; re-prueba del 29/09, #77). */
.stop-move { flex: none; width: 104px; padding: 6px var(--sp-2); font-size: 13px; border-radius: var(--radius); }
.ruta-card.drop-over { box-shadow: 0 0 0 2px var(--accent); background: var(--surface-strong); }
.ruta-new { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--sp-1); min-height: 96px; padding: var(--sp-4); border: 1px dashed var(--border-input); border-radius: var(--radius-lg); color: var(--text-muted); font: var(--t-tabla); background: transparent; }
.ruta-new small { font: var(--t-leyenda); color: var(--text-faint); }
.ruta-new.drop-over { border-color: var(--accent); color: var(--accent-dark); background: var(--green-bg); }
.rutas-mappane { position: sticky; top: var(--sp-3); display: flex; flex-direction: column; gap: var(--sp-2); }
.rutas-map { height: calc(100vh - 210px); min-height: 420px; border: 1px solid var(--border); border-radius: var(--radius-lg); background: var(--surface-muted); }
.rutas-map-foot { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2); font: var(--t-leyenda); color: var(--text-muted); }
.rmk span { display: inline-flex; align-items: center; justify-content: center; width: 24px; height: 24px; border-radius: 50%; border: 2px solid var(--surface); box-shadow: var(--marker-shadow); background: var(--text-muted); color: var(--surface); font-size: 12px; font-weight: 600; font-family: var(--font-sans); }
.rmk-area span { border-style: dashed; border-width: 2px; }
.rmk-base span { background: var(--text); color: var(--surface); border-radius: var(--radius-sm); }
/* El color de cada ruta, de sus tokens (antes se inyectaba en hex por CSSOM). */
.ruta-c1, .rmk-c1 span { background: var(--ruta-1); }
.ruta-c2, .rmk-c2 span { background: var(--ruta-2); }
.ruta-c3, .rmk-c3 span { background: var(--ruta-3); }
.ruta-c4, .rmk-c4 span { background: var(--ruta-4); }
.ruta-c5, .rmk-c5 span { background: var(--ruta-5); }
.ruta-c6, .rmk-c6 span { background: var(--ruta-6); }
.rmk-c1 span { border-color: var(--ruta-1); } .rmk-c2 span { border-color: var(--ruta-2); } .rmk-c3 span { border-color: var(--ruta-3); }
.rmk-c4 span { border-color: var(--ruta-4); } .rmk-c5 span { border-color: var(--ruta-5); } .rmk-c6 span { border-color: var(--ruta-6); }
/* Por área: el círculo vacío con el número del color de la ruta. */
.rmk-area[class*="rmk-c"] span { background: var(--surface); }
.rmk-area.rmk-c1 span { color: var(--ruta-1); } .rmk-area.rmk-c2 span { color: var(--ruta-2); } .rmk-area.rmk-c3 span { color: var(--ruta-3); }
.rmk-area.rmk-c4 span { color: var(--ruta-4); } .rmk-area.rmk-c5 span { color: var(--ruta-5); } .rmk-area.rmk-c6 span { color: var(--ruta-6); }
.leaflet-container { font-family: inherit; }
.input-date { padding: 4px 8px; }
.input-md { width: 110px; padding: 4px 8px; }

/* Rutas (MEJORAS-UI §29): todos los controles de la barra a la misma altura y
   con el mismo borde; el rango de fechas va en una sola pieza. */
.rutas-controles { display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: var(--sp-2); min-width: 0; }
/* La barra de Rutas (re-prueba del 29/09, #77; medían 34 y 36): las fechas y
   el tipo son campos, 43; "Hoy", "Semana" y "Configurar", botones, 41 (DISENO 3.4). */
.rutas-controles select { height: 43px; }
.rutas-controles select {
  min-width: 120px;
  padding: 0 10px;
  border: 1px solid var(--border-input);
  border-radius: var(--radius);
  background: var(--surface);
  font: var(--t-parrafo);
  color: var(--text);
}
.rutas-rango {
  display: inline-flex;
  align-items: stretch;
  height: 43px;
  border: 1px solid var(--border-input);
  border-radius: var(--radius);
  background: var(--surface);
  overflow: hidden;
}
.rutas-rango button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
  transition: background-color var(--dur-1) var(--ease), color var(--dur-1) var(--ease);
}
.rutas-rango button:hover { background: var(--surface-muted); color: var(--text); }
.rutas-rango button:first-child { border-right: 1px solid var(--border-control); }
.rutas-rango button:last-child { border-left: 1px solid var(--border-control); }
.rutas-rango :is(button, input):focus-visible { outline-offset: -2px; }
.rutas-rango input { height: 100%; padding: 0 6px; border: 0; border-radius: 0; background: transparent; font: var(--t-parrafo); color: var(--text); }
.rutas-rango-a { display: inline-flex; align-items: center; padding: 0 2px; color: var(--text-muted); font: var(--t-parrafo); }
.rutas-atajos { display: inline-flex; }
.rutas-atajos .btn-secondary:first-child { border-top-right-radius: 0; border-bottom-right-radius: 0; }
.rutas-atajos .btn-secondary + .btn-secondary { margin-left: -1px; border-top-left-radius: 0; border-bottom-left-radius: 0; }
.btn-con-icono { display: inline-flex; align-items: center; gap: 6px; }
@media (max-width: 1100px) {
  .rutas-layout { grid-template-columns: 1fr; }
  .rutas-mappane { position: static; }
  .rutas-map { height: 420px; }
}

/* ---- Carrito de inventario ---- */
.cart-count { display: inline-block; min-width: 18px; padding: 0 5px; margin-left: 6px; border-radius: 9px; font-size: 12px; line-height: 18px; text-align: center; background: var(--surface); color: var(--text); }
.cart-add-row { display: flex; align-items: center; gap: 8px; margin-top: 10px; }
.cart-add-row select { flex: 1; min-width: 0; }
.cart-qty { width: 110px; padding: 4px 8px; text-align: right; }
.row-error td { background: var(--red-bg); }
.text-batch { font-family: var(--font-mono); font-size: 12px; color: var(--text-muted); }

/* ---- Asistente ---- */

/* El contenedor llena #screen (.screen-body ya tiene altura definida dentro de
   .content{height:100vh}), y el scroll pasa al hilo: el composer queda fijo
   abajo también cuando el hilo está vacío. */
.asis {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  /* La conversación va centrada (MEJORAS-UI §28). */
  width: 100%;
  max-width: 760px;
  margin: 0 auto;
}

.asis-hilo {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
  padding-bottom: var(--sp-4);
}

/* La barra del hilo, separada de los mensajes (MEJORAS-UI S3): el hilo se
   estira a la derecha lo que mide la barra (10 px) más 16 px de aire. Los
   mensajes quedan alineados con el cuadro de escribir y la barra corre por
   fuera de la columna. Solo con mouse: en el teléfono la barra flota. */
@media (min-width: 901px) {
  .asis-hilo { scrollbar-gutter: stable; padding-right: var(--sp-4); margin-right: -26px; }
}

/* Burbuja de la persona. El texto se pinta con textContent, nunca innerHTML. */
.asis-msg-yo {
  align-self: flex-end;
  max-width: 560px;
  background: var(--accent-strong);
  color: var(--on-accent);
  border-radius: 12px 12px 2px 12px;
  padding: var(--sp-3) var(--sp-4);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.asis-msg-sys { align-self: stretch; max-width: 820px; }

.asis-firma {
  font: var(--t-tabla-enc);
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--text-faint);
  margin-bottom: var(--sp-1);
}

.asis-burbuja {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 2px 12px 12px 12px;
  padding: var(--sp-3) var(--sp-4);
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
}

.asis-burbuja p { margin: 0; }
.asis-nota { font-size: 12px; color: var(--text-muted); }

.asis-traza {
  font-size: 12px;
  color: var(--text-faint);
  font-family: var(--font-mono);
}

/* "Pensando": UNA sola línea que se reemplaza a sí misma, no una lista que
   crece. El texto nombra al agente de dominio que está resolviendo. */
.asis-pensando {
  display: flex;
  align-items: center;
  gap: 8px;
  color: var(--text-muted);
  font-size: 13px;
}

/* Arco girando. Trazo fino como los iconos del nav, para que sea el mismo
   lenguaje visual y no un spinner genérico pegado encima. La pista completa
   queda tenue debajo y solo gira el cuarto de arco. */
.asis-spinner {
  display: inline-flex;
  flex-shrink: 0;
  color: var(--accent);
}

.asis-spinner svg {
  animation: asis-girar .85s linear infinite;
  transform-origin: 50% 50%;
}

.asis-spinner-pista { opacity: .2; }

@keyframes asis-girar {
  to { transform: rotate(360deg); }
}

/* El texto del agente aparece con un desvanecido corto: cada paso reemplaza al
   anterior en la misma línea, y sin esto el cambio se lee como un parpadeo. */
.asis-pensando > span:last-child {
  animation: asis-aparecer .28s ease-out;
}

@keyframes asis-aparecer {
  from { opacity: 0; transform: translateY(2px); }
  to { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  /* Las transiciones duran 1ms y los botones no ceden al presionar. */
  :root { --dur-1: 1ms; --dur-2: 1ms; --press: none; }
  .loading-state::after { animation: none; opacity: 0; }
  .asis-spinner svg { animation-duration: 2.4s; }
  .asis-pensando > span:last-child { animation: none; }
  /* El cajón aparece y desaparece, sin deslizarse. */
  .sidebar { transition: none; }
  .sidebar.nav-animada .nav-grupo-flecha,
  .sidebar.nav-animada .nav-grupo .nav-grupo-items,
  .sidebar.nav-animada .nav-grupo.abierto .nav-grupo-items { transition: none; }
}

/* Chips de sugerencia y de aclaración. Sin handlers inline (CSP): los
   escucha un listener delegado con atributos data-. */
.asis-chips { display: flex; flex-wrap: wrap; gap: 8px; }

.asis-chip {
  display: inline-flex;
  align-items: center;
  padding: 7px 13px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text-muted);
  font-size: 13px;
  font-weight: 500;
  cursor: pointer;
  text-align: left;
  transition: border-color var(--dur-1) var(--ease), color var(--dur-1) var(--ease),
              transform var(--dur-1) var(--ease);
}
.asis-chip:hover { border-color: var(--accent); color: var(--accent-dark); }
.asis-chip:active { transform: var(--press); }

/* Bienvenida: sin LLM, poder ver qué se puede pedir lo es todo. */
.asis-bienvenida { display: flex; flex-direction: column; gap: 16px; padding: 8px 0 4px; }
.asis-grupo { display: flex; flex-direction: column; gap: var(--sp-2); }
.asis-grupo-titulo {
  font: var(--t-tabla-enc);
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--text-faint);
}

/* "Ver todo lo que puedo hacer" (§28): la lista completa, a un toque. */
.asis-todo { border-top: 1px solid var(--border-soft); padding-top: var(--sp-2); }
.asis-todo > summary {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 32px;
  list-style: none;
  cursor: pointer;
  font: 600 13px/1.4 var(--font-sans);
  color: var(--accent-dark);
}
.asis-todo > summary::-webkit-details-marker { display: none; }
.asis-todo > summary::before {
  content: "";
  width: 12px;
  height: 12px;
  background: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' fill='none'><path d='M4 2l4 4-4 4' stroke='%230f6b3c' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/></svg>") center / 12px no-repeat;
  transition: transform var(--dur-1) var(--ease);
}
.asis-todo[open] > summary::before { transform: rotate(90deg); }
.asis-todo .asis-bienvenida { padding-top: var(--sp-3); } /* iba 10 (#77) */

/* Tarjeta de propuesta: toda escritura pasa por aquí (baseline 9DC §6.4).
   Verde muy claro con el borde marcado: se tiene que leer como una decisión
   pendiente y no como algo que ya pasó — los resultados van sobre --surface
   blanco y sin borde de color. Los avisos de verdad (stock negativo) siguen en
   ámbar más abajo: ahí el color sí tiene que alertar. */
.asis-tarjeta {
  border: 1px solid var(--green-border);
  background: var(--green-bg);
  border-radius: var(--radius-lg);
  padding: var(--sp-4);
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
}

.asis-tarjeta-titulo { font-size: 14px; font-weight: 600; color: var(--green-text); }
.asis-tarjeta-entidad { font-family: var(--font-mono); font-size: 13px; color: var(--text); }

.asis-antes {
  display: flex;
  justify-content: space-between;
  gap: var(--sp-3);
  padding: var(--sp-3);
  border-radius: var(--radius-sm);
  background: var(--surface);
  border: 1px solid var(--green-border);
  font-size: 13px;
}
.asis-antes-valor { font-variant-numeric: tabular-nums; font-weight: 600; }

.asis-delta { font-size: 13px; color: var(--text-muted); min-height: 17px; }
.asis-delta-num { font-variant-numeric: tabular-nums; font-weight: 600; color: var(--green-text); }

.asis-fijo {
  font-family: var(--font-mono);
  font-size: 13px;
  padding: 8px 10px;
  border-radius: var(--radius-sm);
  background: var(--surface-muted);
  border: 1px dashed var(--border);
  color: var(--text-muted);
}

.asis-tarjeta-pie { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; }
.asis-caduca { font-size: 12px; color: var(--text-faint); margin-left: auto; }


/* Composer. Sin botón de micrófono a propósito: la Web Speech API solo existe
   en Chrome y manda el audio del empleado a servidores de Google — egreso a un
   tercero sin contrato en un proyecto N2 (baseline 9DC §10). El dictado del
   teclado del sistema funciona en este textarea sin escribir una línea. */
.asis-composer {
  flex-shrink: 0;
  border-top: 1px solid var(--border);
  padding-top: 12px;
  display: flex;
  gap: var(--sp-2); /* en la escala (iba 10; re-prueba del 29/09, #77) */
  align-items: flex-end;
}

.asis-campo textarea {
  flex: 1;
  resize: none;
  min-height: 40px;
  max-height: 130px;
  border: 1px solid var(--border-input);
  border-radius: var(--radius);
  padding: 10px 12px;
  background: var(--surface);
  color: var(--text);
  font-size: 14px;
  line-height: 1.35;
}
/* Sin el anillo de afuera, pero con halo: antes el foco de este campo no se veía (§8). */
.asis-campo textarea:focus { border-color: var(--accent-strong); outline: none; box-shadow: var(--foco-halo); }

/* El compositor no tiene botón "Enviar" (elegido en preview el 2026-09-19,
   DISENO §3.15): la caja de texto es un contenedor y adentro, pegadas a la
   derecha, viven las dos acciones. El relleno del campo se corre para que lo
   que se escribe no pase por debajo. */
.asis-campo { flex: 1; min-width: 0; position: relative; display: flex; }
.asis-campo textarea { padding-right: 76px; }
/* Sin micrófono (sin llave en el servidor) el campo no necesita tanto lugar. */
.asis-campo.sin-mic textarea { padding-right: 44px; }
.asis-acciones { position: absolute; right: 4px; bottom: 4px; display: flex; align-items: center; gap: var(--sp-1); }

/* El micrófono: un botón de ícono sin fondo, que es lo callado (§3.4). */
.asis-composer .asis-mic { width: 32px; height: 32px; border: 0; }
/* Grabando va en el verde del botón principal y NUNCA en rojo: acá el rojo
   significa "salió mal o es urgente" (§3.5), y grabar no es ninguna de las dos. */
.asis-composer .asis-mic.es-grabando,
.asis-composer .asis-mic.es-grabando:hover { background: var(--accent-strong); color: var(--on-accent); }
.asis-composer .asis-mic.es-pasando { color: var(--text-faint); cursor: default; }

/* El círculo es "lo que sigue": enviar si hay texto, conversar si no. Es el
   único botón principal del renglón —por eso se pudo sacar "Enviar" sin romper
   la regla de uno solo por pantalla (§3.4)— y por eso va lleno. Redondo y no
   cuadrado: no es una acción sobre el texto, como sí lo es el micrófono. */
.asis-composer .asis-circulo {
  position: relative;
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  padding: 0;
  border: 0;
  border-radius: var(--radius-full);
  background: var(--accent-strong);
  color: var(--on-accent);
  cursor: pointer;
  transition: background var(--dur-1) var(--ease);
}
.asis-composer .asis-circulo:hover { background: var(--accent-strong-hover); }
.asis-composer .asis-circulo:active { transform: var(--press); }
.asis-composer .asis-circulo svg { flex: none; width: 16px; height: 16px; }
.asis-composer .asis-circulo:disabled { background: var(--border-control); color: var(--text-faint); cursor: default; }
.asis-composer .asis-circulo:disabled:hover { background: var(--border-control); }

/* El latido, mientras escucha. Se apaga con el movimiento reducido. */
@keyframes asis-latido { 0%, 100% { box-shadow: 0 0 0 0 rgba(31, 163, 92, .38); } 50% { box-shadow: 0 0 0 5px rgba(31, 163, 92, 0); } }
.asis-composer .asis-mic.es-grabando { animation: asis-latido 1.6s var(--ease) infinite; }
@media (prefers-reduced-motion: reduce) { .asis-composer .asis-mic.es-grabando { animation: none; } }

/* El globo de un botón de ícono sale a la izquierda, que es lo que sirve en
   una fila de tabla pegada al borde derecho. Acá, a la izquierda está el campo:
   taparía justo lo que la persona escribe. En el compositor va arriba. */
.asis-composer .asis-mic[data-tip]::after,
.asis-composer .asis-circulo[data-tip]::after {
  content: attr(data-tip);
  position: absolute;
  right: auto;
  left: 50%;
  top: auto;
  bottom: calc(100% + 6px);
  transform: translateX(-50%);
  z-index: 5;
  padding: var(--sp-1) var(--sp-2);
  border-radius: var(--radius-sm);
  background: var(--sidebar-bg);
  color: var(--text-on-dark);
  font: var(--t-leyenda);
  white-space: nowrap;
  box-shadow: var(--elev-2);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur-1) var(--ease);
}
@media (hover: hover) { .asis-composer .asis-circulo[data-tip]:hover::after { opacity: 1; } }
.asis-composer .asis-circulo[data-tip]:focus-visible::after { opacity: 1; }

/* El pie mientras graba: el renglón de siempre, con el punto que late a la
   izquierda y el reloj a la derecha. */
.asis-pie-grabando { display: flex; align-items: center; gap: var(--sp-2); color: var(--text-muted); }
.asis-pie-vivo { display: inline-flex; align-items: center; gap: var(--sp-2); min-width: 0; }
.asis-punto { width: 8px; height: 8px; border-radius: var(--radius-full); background: var(--accent); flex: none; animation: asis-punto-late 1.6s var(--ease) infinite; }
@keyframes asis-punto-late { 0%, 100% { opacity: 1; } 50% { opacity: .35; } }
@media (prefers-reduced-motion: reduce) { .asis-punto { animation: none; } }
.asis-pie-reloj { margin-left: auto; padding-left: var(--sp-2); font-variant-numeric: tabular-nums; color: var(--text-faint); }

/* El aviso de "sin micrófono", sobre el compositor, con la forma única (§3.6). */
.asis-mic-aviso { flex-shrink: 0; padding-bottom: var(--sp-2); }

/* Hasta 900 px todo lo que se toca va a 44, salvo estos dos: a 44 harían crecer
   la caja. Van a 36, que sigue siendo cómodo con el dedo. */
@media (max-width: 900px) {
  .asis-composer .asis-mic,
  .asis-composer .asis-circulo { width: 36px; height: 36px; }
  .asis-campo textarea { padding-right: 84px; }
  .asis-campo.sin-mic textarea { padding-right: 48px; }
}

/* Centrado, como la conversación (2026-09-23). El renglón del dictado
   (.asis-pie-grabando) es flex y no cambia: el reloj sigue a la derecha. En
   el teléfono el texto de siempre no va (ocupaba dos renglones); el del
   dictado sí. */
.asis-pie {
  flex-shrink: 0;
  font-size: 12px;
  color: var(--text-faint);
  padding-top: 6px;
  text-align: center;
}

/* ---- Centro de control ---- */

/* Pantalla de demostración (public/js/centro.js): un lienzo con zoom, datos de
   ejemplo y una simulación que arranca en pausa. ADENTRO del lienzo (.cc-mundo)
   los tamaños de letra van a mano y más grandes que la escala, porque el zoom
   los achica (a 33%, una letra de 28px se lee como de 9px), y cambian por nivel
   de zoom (.cc-z-lejos, .cc-z-normal, .cc-z-cerca). Afuera del lienzo, todo con
   los tokens de la guía. */
.cc-wrap { display: flex; flex-direction: column; gap: var(--sp-4); }
.cc-tel { display: none; flex-direction: column; gap: var(--sp-3); }

.cc-mapa { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg); box-shadow: var(--elev-1); overflow: hidden; }
.cc-mapa-h { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-3) var(--sp-4); padding: var(--sp-4) var(--sp-5); border-bottom: 1px solid var(--border-soft); }
.cc-resumen { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2); font: var(--t-tabla); color: var(--text-muted); }
.cc-resumen b { color: var(--text); font-variant-numeric: tabular-nums; }
.cc-ley { margin-left: auto; display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2) var(--sp-4); font: var(--t-leyenda); color: var(--text-muted); }
.cc-ley > span { display: inline-flex; align-items: center; gap: var(--sp-2); }
.cc-ley .cc-av { width: 20px; height: 20px; }
.cc-ley .cc-av svg { width: 12px; height: 12px; }
/* Muestras de la leyenda: las mismas formas que viajan por el lienzo. */
.cc-wkl, .cc-ley-o { width: 20px; height: 20px; border-radius: 50%; background: var(--accent-strong); color: var(--on-accent); display: grid; place-items: center; }
.cc-ley-o { font: var(--t-chip); }
.cc-ley-m { width: 16px; height: 16px; border-radius: var(--radius-sm); background: var(--blue-text); }
.cc-ley-v { width: 16px; height: 16px; border-radius: 50%; border: 3px solid var(--accent-strong); background: var(--surface); }

/* El lienzo y la cámara */
.cc-lienzo { position: relative; height: 640px; overflow: hidden; background-color: var(--surface-muted); background-image: radial-gradient(var(--border) 1.2px, transparent 1.4px); background-size: 24px 24px; cursor: grab; touch-action: none; user-select: none; -webkit-user-select: none; }
.cc-lienzo.cc-arrastrando { cursor: grabbing; }
.cc-mundo { position: absolute; left: 0; top: 0; width: 3368px; height: 1500px; transform-origin: 0 0; }
/* Vuelo de la cámara al tocar un proceso: más largo que --dur-2 para que se
   entienda de dónde a dónde se movió. */
.cc-mundo.cc-anim { transition: transform .6s var(--ease); }
.cc-capa { position: absolute; left: 140px; top: 320px; width: 3208px; height: 1180px; }
svg.cc-fl, svg.cc-sup { position: absolute; left: 0; top: 0; overflow: visible; pointer-events: none; }
.cc-fl .cc-ar { fill: none; stroke: var(--border-input); stroke-width: 3; transition: opacity var(--dur-2) var(--ease); }
.cc-fl .cc-ar-rama { stroke-dasharray: 8 6; }
.cc-fl .cc-ar-hecho { stroke: var(--accent); stroke-width: 4; }
.cc-fl .cc-ar-pend { stroke: var(--accent); stroke-dasharray: 4 8; opacity: .7; }
.cc-fl .cc-ar-dim { opacity: .18; }
.cc-fl .cc-ah { fill: var(--border-input); }
/* Etiquetas de las líneas (no el número de los puntitos, que es .cc-pn). */
.cc-fl .cc-etq { font: 400 14px/1 var(--font-sans); fill: var(--text-muted); paint-order: stroke; stroke: var(--surface-muted); stroke-width: 6px; stroke-linejoin: round; }
/* Líneas de Gerencia: punteadas y fijas. */
.cc-sup path { fill: none; stroke: var(--accent); stroke-width: 2.5; stroke-dasharray: 2 10; stroke-linecap: round; opacity: .75; }
.cc-sup text { font: 600 14px/1 var(--font-sans); fill: var(--green-text); paint-order: stroke; stroke: var(--surface-muted); stroke-width: 6px; stroke-linejoin: round; }
.cc-z-lejos .cc-etq, .cc-z-lejos .cc-sup text { display: none; }
/* Lo que viaja por las líneas: órdenes (círculo verde, con el número si son
   varias), material o compras (cuadradito azul) y las consultas de Voltio
   (anillo verde). */
.cc-pd { fill: var(--accent-strong); }
.cc-ph { fill: var(--green-bg); }
.cc-pd-m { fill: var(--blue-text); }
.cc-ph-m { fill: var(--blue-bg); }
.cc-pd-v { fill: var(--surface); stroke: var(--accent-strong); stroke-width: 3; }
.cc-pn { font: 600 12px/1 var(--font-sans); fill: var(--on-accent); }

/* Agentes: el arco gira solo mientras la simulación los tiene trabajando */
.cc-av { position: relative; width: 28px; height: 28px; border-radius: 50%; background: var(--sidebar-bg); color: var(--sidebar-active-text); display: grid; place-items: center; flex: none; }
.cc-av .cc-arc { position: absolute; left: -4px; top: -4px; width: 36px; height: 36px; color: var(--accent); animation: cc-girar .85s linear infinite; }
.cc-pista { opacity: .2; }
.cc-av.cc-idle .cc-arc { animation: none; }
.cc-av.cc-idle .cc-q { opacity: 0; }
.cc-av-vol { width: 56px; height: 56px; background: var(--accent-strong); color: var(--on-accent); }
.cc-av-vol .cc-arc { left: -6px; top: -6px; width: 68px; height: 68px; color: var(--green-text); }
.cc-av-vol svg:not(.cc-arc) { width: 28px; height: 28px; }

/* Gerencia: Voltio coordina a los agentes */
.cc-ger { position: absolute; left: 1110px; top: 40px; width: 940px; height: 240px; border: 2px solid var(--accent); border-radius: 20px; background: var(--green-bg); transition: opacity var(--dur-2) var(--ease); }
.cc-ger-h { position: absolute; left: 32px; right: 32px; top: 24px; display: flex; align-items: baseline; gap: 16px; }
.cc-ger-t { font: 600 22px/1 var(--font-sans); letter-spacing: .06em; text-transform: uppercase; color: var(--green-text); white-space: nowrap; }
.cc-ger-k { position: absolute; left: 32px; top: 84px; display: grid; grid-template-columns: 380px 224px 224px; gap: 24px; }
.cc-gk { height: 132px; padding: 16px; border-radius: 12px; background: var(--surface); border: 1px solid var(--border); box-shadow: var(--elev-1); display: flex; flex-direction: column; gap: 8px; text-align: left; color: var(--text); cursor: pointer; min-width: 0; }
.cc-gk:hover { border-color: var(--border-input); }
.cc-gk.cc-vol { flex-direction: row; align-items: center; gap: 16px; }
.cc-gk.cc-vol > span:last-child { min-width: 0; }
.cc-gk.cc-vol b { display: block; font: 600 22px/1.1 var(--font-sans); }
.cc-gk-r { display: block; font-size: 13px; color: var(--text-muted); margin-top: 4px; }
.cc-gk-s { display: block; font-size: 14px; color: var(--green-text); font-weight: 600; margin-top: 8px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.cc-gk-d { font-size: 13px; color: var(--text-muted); }
.cc-gk-vos { border-color: var(--red-text); }
.cc-gk-vos .cc-k-c b { color: var(--red-text); }
.cc-z-lejos .cc-ger-t { font-size: 40px; }
.cc-z-lejos .cc-ger-h .cc-dep-s, .cc-z-lejos .cc-gk-d, .cc-z-lejos .cc-gk-r { display: none; }
.cc-z-lejos .cc-gk .cc-k-n { font-size: 24px; line-height: 28px; }
.cc-z-lejos .cc-gk .cc-k-c b { font-size: 44px; line-height: 44px; }
.cc-z-lejos .cc-gk .cc-k-c span { font-size: 22px; }
.cc-z-lejos .cc-gk.cc-vol b { font-size: 36px; }
.cc-z-lejos .cc-gk-s { font-size: 22px; line-height: 26px; white-space: normal; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; }

/* Departamentos: contenedores con sus procesos adentro */
.cc-dep { position: absolute; border: 1px solid var(--border-control); border-radius: 20px; background: var(--surface-strong); transition: opacity var(--dur-2) var(--ease); }
.cc-dep-mat { border-style: dashed; }
.cc-dep-h { position: absolute; left: 32px; right: 32px; top: 28px; display: flex; flex-wrap: wrap; align-items: center; gap: 12px 16px; }
.cc-dep-num { flex: none; width: 32px; height: 32px; border-radius: 50%; background: var(--text); color: var(--surface); display: grid; place-items: center; font: 600 16px/1 var(--font-sans); }
.cc-dep-n { font: 600 22px/1 var(--font-sans); letter-spacing: .06em; text-transform: uppercase; white-space: nowrap; }
.cc-dep-c { font: 400 16px/1 var(--font-sans); color: var(--text-muted); white-space: nowrap; }
.cc-dep-c b { color: var(--text); font-variant-numeric: tabular-nums; }
.cc-dep-s { font-size: 14px; color: var(--text-faint); white-space: nowrap; }
.cc-dep-ags { margin-left: auto; display: flex; gap: 16px; }
.cc-dep-mat .cc-dep-ags { margin-left: 8px; }
.cc-dep-ag { display: inline-flex; align-items: center; gap: 8px; font-size: 14px; color: var(--text-muted); white-space: nowrap; }
.cc-dep-ag.cc-trab { color: var(--green-text); font-weight: 600; }
.cc-z-lejos .cc-dep-num { width: 44px; height: 44px; font-size: 22px; }
.cc-z-lejos .cc-dep-n { font-size: 34px; }
.cc-z-lejos .cc-dep-c { font-size: 26px; }
.cc-z-lejos .cc-dep-s, .cc-z-lejos .cc-agl { display: none; }
.cc-z-lejos .cc-dep-ag { font-size: 22px; gap: 12px; }
.cc-z-lejos .cc-dep-ag .cc-av { width: 36px; height: 36px; }
.cc-z-lejos .cc-dep-ag .cc-av .cc-arc { left: -6px; top: -6px; width: 48px; height: 48px; }
.cc-z-lejos .cc-dep-ag .cc-av svg:not(.cc-arc) { width: 18px; height: 18px; }
.cc-z-lejos .cc-dep-mat .cc-agt, .cc-z-lejos .cc-dep-mat .cc-agx { display: none; }

/* Procesos */
.cc-k { position: absolute; width: 200px; height: 160px; padding: 16px; border-radius: 12px; background: var(--surface); border: 1px solid var(--border); box-shadow: var(--elev-1); display: flex; flex-direction: column; gap: 8px; color: var(--text); cursor: pointer; transition: opacity var(--dur-2) var(--ease), box-shadow var(--dur-2) var(--ease), border-color var(--dur-2) var(--ease); }
.cc-k:hover { border-color: var(--border-input); }
.cc-k:focus-visible, .cc-gk:focus-visible { outline: 3px solid var(--foco); outline-offset: 4px; }
.cc-k.cc-rama { border-style: dashed; background: var(--surface-strong); box-shadow: none; }
.cc-k.cc-ext { border-color: var(--border-input); }
.cc-k.cc-ext .cc-k-n { display: flex; align-items: center; gap: 8px; color: var(--accent-dark); }
.cc-k.cc-rojo { border-color: var(--red-text); }
.cc-k.cc-ambar { border-color: var(--amber-dot); }
.cc-k-n { font: 600 16px/20px var(--font-sans); }
.cc-k-c { display: flex; align-items: baseline; gap: 4px; }
.cc-k-c b { font: 600 28px/32px var(--font-sans); font-variant-numeric: tabular-nums; }
.cc-k-c span { font-size: 14px; color: var(--text-muted); white-space: nowrap; }
.cc-k-e { display: flex; align-items: center; gap: 8px; font-size: 14px; line-height: 18px; color: var(--text-faint); white-space: nowrap; }
.cc-k-e svg { flex: none; }
.cc-k-e.cc-trab { color: var(--green-text); font-weight: 600; }
.cc-k-e.cc-vos { color: var(--red-text); font-weight: 600; }
.cc-k-o { display: none; flex-wrap: wrap; gap: 4px; margin-top: auto; }
.cc-z-cerca .cc-k-o { display: flex; }
.cc-o, .cc-mas { font: 400 12px/1 var(--font-mono); padding: 4px 8px; border-radius: var(--radius-sm); border: 0; background: var(--bg); color: var(--text-muted); }
button.cc-o { cursor: pointer; }
button.cc-o:hover { text-decoration: underline; }
.cc-o.cc-r { background: var(--red-bg); color: var(--red-text); }
.cc-o.cc-a { background: var(--amber-bg); color: var(--amber-text); }
.cc-mas { background: none; padding: 4px 0; color: var(--text-faint); }
.cc-o.cc-yo { outline: 2px solid var(--text); outline-offset: 1px; }
.cc-bdg { display: inline-grid; place-items: center; min-width: 20px; height: 20px; padding: 0 var(--sp-1); border-radius: var(--radius-full); font: var(--t-chip); background: var(--red-strong); color: var(--on-accent); font-variant-numeric: tabular-nums; }
.cc-bdg-a { background: var(--amber-dot); color: var(--accent-ink); }
.cc-k > .cc-bdg { position: absolute; top: -12px; right: -12px; min-width: 24px; height: 24px; font-size: 13px; box-shadow: 0 0 0 3px var(--surface-muted); }
.cc-wk { position: absolute; top: -12px; left: -12px; width: 28px; height: 28px; border-radius: 50%; background: var(--accent-strong); color: var(--on-accent); display: none; place-items: center; box-shadow: 0 0 0 3px var(--surface-muted); }
.cc-k.cc-trabaja .cc-wk { display: grid; }
.cc-wk svg { animation: cc-girar .85s linear infinite; }
.cc-z-lejos .cc-k-n { font-size: 28px; line-height: 32px; }
.cc-z-lejos .cc-k-c b { font-size: 44px; line-height: 44px; }
.cc-z-lejos .cc-k-c span { font-size: 22px; }
.cc-z-lejos .cc-k-e { display: none; }
.cc-z-lejos .cc-k > .cc-bdg { min-width: 40px; height: 40px; font-size: 22px; top: -18px; right: -18px; }
.cc-z-lejos .cc-wk { width: 44px; height: 44px; top: -18px; left: -18px; }
.cc-z-lejos .cc-wk svg { width: 24px; height: 24px; }
.cc-k.cc-sel { outline: 3px solid var(--accent); outline-offset: 4px; }

.cc-fin { position: absolute; left: 3066px; top: 340px; width: 140px; display: flex; flex-direction: column; align-items: center; gap: 12px; text-align: center; }
.cc-fin-c { width: 72px; height: 72px; border-radius: 50%; display: grid; place-items: center; background: var(--accent-strong); color: var(--on-accent); }
.cc-fin b { font: 600 24px/1.1 var(--font-sans); }
.cc-s4 { font-size: 16px; line-height: 1.35; color: var(--text-muted); font-variant-numeric: tabular-nums; }
.cc-z-lejos .cc-fin b { font-size: 32px; }
.cc-z-lejos .cc-s4 { font-size: 22px; }

/* Seguir una orden en el lienzo: lo que no es de su recorrido se atenúa */
.cc-sigue .cc-k { opacity: .25; }
.cc-sigue .cc-dep, .cc-sigue .cc-ger { opacity: .55; }
.cc-sigue .cc-k.cc-ruta { opacity: 1; }
.cc-k.cc-hecho { border-color: var(--accent); box-shadow: 0 0 0 3px var(--green-border); }
.cc-k.cc-pend { border: 2px dashed var(--accent); padding: 15px; }
.cc-k.cc-aca { --cc-anillo: var(--green-border); border-width: 2px; padding: 15px; animation: cc-aca 1.4s ease-in-out infinite; }
.cc-k.cc-aca.cc-rojo { --cc-anillo: var(--red-border); }
.cc-k.cc-aca.cc-ambar { --cc-anillo: var(--amber-border); }
.cc-pin { position: absolute; z-index: 3; transform: translate(-50%, -100%); background: var(--text); color: var(--surface); font: 600 14px/1.2 var(--font-sans); padding: 8px 12px; border-radius: 8px; white-space: nowrap; pointer-events: none; }
.cc-pin::after { content: ""; position: absolute; left: 50%; bottom: -7px; margin-left: -7px; border: 7px solid transparent; border-bottom: 0; border-top-color: var(--text); }

/* Controles sobre el lienzo. El buscador mide lo mismo que un campo: 43 en la
   computadora y 48 con letra de 16 hasta 900 px (DISENO §3.4). */
.cc-buscar { position: absolute; top: var(--sp-4); left: var(--sp-4); z-index: 4; width: 320px; cursor: default; }
.cc-buscar-in { display: flex; align-items: center; gap: var(--sp-2); min-height: 43px; padding: 0 var(--sp-3); background: var(--surface); border: 1px solid var(--border-input); border-radius: var(--radius); box-shadow: var(--elev-1); color: var(--text-faint); }
.cc-buscar-in:hover { border-color: var(--border-input-hover); }
.cc-buscar-in:focus-within { border-color: var(--accent-strong); box-shadow: var(--foco-halo); }
.cc-buscar input, .cc-tbus input { flex: 1; min-width: 0; height: auto; padding: 0; border: 0; outline: 0; background: transparent; box-shadow: none; font: var(--t-parrafo); color: var(--text); }
.cc-buscar ul { list-style: none; margin: var(--sp-2) 0 0; padding: var(--sp-1); background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); box-shadow: var(--elev-2); }
.cc-buscar li button { width: 100%; display: flex; align-items: center; gap: var(--sp-2); min-height: 40px; padding: var(--sp-2); border: 0; background: none; border-radius: var(--radius-sm); font: var(--t-tabla); color: var(--text); text-align: left; cursor: pointer; }
.cc-buscar li button:hover, .cc-buscar li button:focus-visible { background: var(--surface-muted); }
.cc-buscar li .mat-sku { min-width: 72px; margin: 0; }
.cc-bus-vacio { display: block; padding: var(--sp-2); font: var(--t-leyenda); color: var(--text-faint); }
.cc-zoom { position: absolute; left: var(--sp-4); bottom: var(--sp-4); z-index: 4; display: flex; align-items: center; gap: var(--sp-1); padding: var(--sp-1); background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); box-shadow: var(--elev-1); cursor: default; }
.cc-zoom .btn-ghost { color: var(--text); }
.cc-pct { min-width: 48px; text-align: center; font-family: var(--font-mono); font-size: 12px; color: var(--text-muted); font-variant-numeric: tabular-nums; }
.cc-sep { width: 1px; height: 20px; margin: 0 var(--sp-1); background: var(--border); }
.cc-mm { position: absolute; right: var(--sp-4); bottom: var(--sp-4); z-index: 4; padding: var(--sp-2); background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); box-shadow: var(--elev-1); cursor: pointer; transition: right var(--dur-2) var(--ease); }
.cc-con-seg .cc-mm { right: 376px; }
.cc-mm svg { display: block; }
.cc-mm-d { fill: var(--bg); stroke: var(--border-control); }
.cc-mm-v { fill: var(--green-bg); }
.cc-mm-l { fill: none; stroke: var(--accent); stroke-width: 1.5; }
.cc-ayuda { position: absolute; top: var(--sp-4); right: var(--sp-4); z-index: 3; padding: var(--sp-1) var(--sp-3); border-radius: var(--radius-full); background: var(--surface); font: var(--t-leyenda); color: var(--text-muted); white-space: nowrap; pointer-events: none; transition: opacity var(--dur-2) var(--ease); }
.cc-ayuda.cc-oculta, .cc-con-seg .cc-ayuda { opacity: 0; }

/* Renglón de abajo: los últimos 3 movimientos (el más nuevo arriba) o el
   aviso de la pausa. */
.cc-ahora { display: flex; align-items: center; gap: var(--sp-3); min-height: 48px; padding: var(--sp-3) var(--sp-5); border-top: 1px solid var(--border-soft); font: var(--t-tabla); color: var(--text-muted); }
.cc-ahora > svg { flex: none; color: var(--text-faint); }
.cc-feed { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--sp-1); width: 100%; min-width: 0; }
.cc-feed li { display: grid; grid-template-columns: 8px 80px minmax(0, 1fr); gap: var(--sp-3); align-items: baseline; color: var(--text-faint); }
.cc-feed li.cc-feed-nuevo { color: var(--text-muted); }
.cc-feed-i { align-self: center; display: grid; }
.cc-feed time { font-family: var(--font-mono); font-size: 12px; color: var(--text-faint); white-space: nowrap; }
.cc-feed strong { color: var(--text-muted); }
.cc-feed-nuevo strong { color: var(--text); }
.cc-dot { flex: none; width: 8px; height: 8px; border-radius: 50%; background: var(--accent); animation: cc-latido 2s ease-in-out infinite; }

/* Panel de la derecha: los casos de un proceso, lo frenado, Voltio o una orden */
.cc-seg { position: absolute; top: 0; right: 0; bottom: 0; z-index: 5; width: 360px; display: flex; flex-direction: column; background: var(--surface); border-left: 1px solid var(--border); box-shadow: var(--elev-2); cursor: default; transform: translateX(100%); visibility: hidden; transition: transform var(--dur-2) var(--ease), visibility 0s linear var(--dur-2); }
.cc-seg.cc-abierto { transform: none; visibility: visible; transition: transform var(--dur-2) var(--ease); }
.cc-seg-h { display: flex; align-items: flex-start; gap: var(--sp-3); padding: var(--sp-4) var(--sp-5); border-bottom: 1px solid var(--border-soft); }
.cc-seg-h > span { flex: 1; min-width: 0; }
/* La "×" de cerrar como la de los modales (.modal-header [data-close]): el
   mismo botón fantasma, de 40 px y con la "×" de 22. */
.cc-seg-h [data-cerrar] { flex: none; width: 40px; height: 40px; margin: -8px -8px -8px 0; padding: 0; font-size: 22px; line-height: 1; color: var(--text-muted); }
.cc-seg-h .mat-sku { display: block; margin: 0 0 var(--sp-1); }
.cc-seg-arriba { display: block; margin-bottom: var(--sp-1); font: var(--t-leyenda); color: var(--text-faint); }
.cc-seg-t { display: block; font: var(--t-seccion); }
.cc-seg-sub { display: block; margin-top: var(--sp-1); font: var(--t-subtitulo); color: var(--text-muted); }
.cc-seg-back { padding: var(--sp-3) var(--sp-5) 0; }
/* "Volver a…" es un botón fantasma con color de enlace, alineado con el texto. */
.cc-volver { margin-left: calc(var(--sp-3) * -1); color: var(--accent-dark); }
.cc-seg-b { flex: 1; overflow-y: auto; padding: var(--sp-4) var(--sp-5); display: flex; flex-direction: column; gap: var(--sp-4); }
.cc-seg-b > .btn-secondary, .cc-seg-b > .btn-primary { align-self: flex-start; }
.cc-seg-est { display: flex; flex-direction: column; gap: var(--sp-2); padding: var(--sp-3) var(--sp-4); border-radius: var(--radius); background: var(--surface-muted); font: var(--t-tabla); }
.cc-seg-est.cc-rojo { background: var(--red-bg); }
.cc-seg-est.cc-ambar { background: var(--amber-bg); }
.cc-q2 { font: var(--t-leyenda); color: var(--text-muted); }
.cc-tl { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
/* 28 = el punto del recorrido (16) + su separación (12): el rótulo queda alineado con el texto. */
.cc-tl-g { padding: var(--sp-3) 0 var(--sp-2) calc(16px + var(--sp-3)); font: var(--t-tabla-enc); letter-spacing: .04em; text-transform: uppercase; color: var(--text-faint); }
.cc-tl-g:first-child { padding-top: 0; }
.cc-tl-p { position: relative; display: grid; grid-template-columns: 16px minmax(0, 1fr); gap: var(--sp-3); align-items: start; padding: 0 0 var(--sp-3); }
.cc-tl-p::before { content: ""; position: absolute; left: 7px; top: 16px; bottom: 0; width: 2px; background: var(--border); }
.cc-tl-p i { position: relative; z-index: 1; width: 16px; height: 16px; border-radius: 50%; border: 2px solid var(--border-control); background: var(--surface); }
.cc-tl-p.cc-h i { background: var(--accent); border-color: var(--accent); }
.cc-tl-p.cc-a i { background: var(--accent); border-color: var(--accent); box-shadow: 0 0 0 4px var(--green-border); }
.cc-tl-p.cc-a.cc-rojo i { background: var(--red-text); border-color: var(--red-text); box-shadow: 0 0 0 4px var(--red-border); }
.cc-tl-p.cc-a.cc-ambar i { background: var(--amber-dot); border-color: var(--amber-dot); box-shadow: 0 0 0 4px var(--amber-border); }
/* Nombre de un paso, de un caso o de un proceso en una lista: el nivel
   "identificador de una fila" (13 · 600, como .c-main). */
.cc-tl-n, .cc-pr-t, .cc-pr-ag, .cc-pl-n, .cc-tvos b { display: block; font: var(--t-tabla); font-weight: 600; color: var(--text); }
.cc-tl-p.cc-pe .cc-tl-n { font-weight: 400; color: var(--text-muted); }
.cc-tl-d { display: block; margin-top: var(--sp-1); font: var(--t-leyenda); color: var(--text-faint); }
.cc-pr-q { display: flex; flex-direction: column; gap: var(--sp-2); padding: var(--sp-3) var(--sp-4); border-radius: var(--radius); background: var(--surface-muted); font: var(--t-tabla); }
.cc-pr-q p { color: var(--text-muted); }
.cc-pr-q .cc-gk-s, .cc-tvol .cc-gk-s { margin: 0; font: var(--t-tabla); font-weight: 600; color: var(--green-text); white-space: normal; }
.cc-pr-ch { display: flex; flex-wrap: wrap; gap: var(--sp-2); }
.cc-pr-p { font: var(--t-tabla); color: var(--text-muted); }
.cc-pr-b { display: flex; flex-direction: column; gap: var(--sp-2); }
.cc-pr-b > span { font: var(--t-tabla-enc); letter-spacing: .04em; text-transform: uppercase; color: var(--text-faint); }
.cc-pr-b p { font: var(--t-tabla); color: var(--text-muted); }
.cc-pr-l { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
.cc-pr-l li { display: grid; grid-template-columns: 12px minmax(0, 1fr) auto; gap: var(--sp-3); align-items: start; padding: var(--sp-3) 0; border-top: 1px solid var(--border-soft); }
.cc-pr-l li:first-child { border-top: 0; padding-top: var(--sp-1); }
.cc-pr-l li.cc-sigue-f { cursor: pointer; }
.cc-pr-l li.cc-sigue-f:hover .cc-pr-t { text-decoration: underline; }
.cc-pr-l li > svg { margin-top: 2px; color: var(--text-faint); }
.cc-pr-l .cc-sv { margin-top: var(--sp-1); }
.cc-pr-l .btn-primary { margin-top: var(--sp-2); }
.cc-pr-l li.cc-mas-f { display: block; padding: var(--sp-3) 0 0 var(--sp-5); font: var(--t-leyenda); color: var(--text-faint); }
.cc-pr-t .mat-sku { margin: 0 var(--sp-2) 0 0; }
.cc-pr-d { display: block; margin-top: var(--sp-1); font: var(--t-tabla); }
.cc-pr-w { display: block; margin-top: var(--sp-1); font: var(--t-leyenda); color: var(--text-faint); }
.cc-pr-w strong { color: var(--text); }
.cc-sv { flex: none; width: 10px; height: 10px; border-radius: 50%; background: var(--red-text); box-shadow: 0 0 0 3px var(--red-bg); }
.cc-sv-a { background: var(--amber-dot); box-shadow: 0 0 0 3px var(--amber-bg); }
.cc-sv-ok { background: var(--green-border); box-shadow: none; }

/* Pasos apagados (2026-09-25): Outlook, Datos faltantes y Medición en sitio (se
   apagó el formulario por correo) y Costos (falta conectar SAC). En gris y con
   borde punteado, como deshabilitados; siguen ahí para que se vea el recorrido. */
.cc-fl .cc-ar-off { stroke: var(--border-control); stroke-dasharray: 2 8; }
.cc-k.cc-off, .cc-dep-off { background: var(--surface-muted); border-style: dashed; border-color: var(--border-control); box-shadow: none; }
.cc-k.cc-off .cc-k-n, .cc-k.cc-ext.cc-off .cc-k-n, .cc-k.cc-off .cc-k-c b, .cc-dep-off .cc-dep-n { color: var(--text-faint); }
.cc-k-e.cc-apagado { color: var(--text-faint); }
.cc-ley-off { width: 16px; height: 16px; border-radius: var(--radius-sm); border: 1px dashed var(--border-control); background: var(--surface-muted); }
.cc-off-aviso { display: flex; align-items: flex-start; gap: var(--sp-2); padding: var(--sp-3) var(--sp-4); border-radius: var(--radius); background: var(--surface-muted); font: var(--t-tabla); color: var(--text-muted); }
.cc-off-aviso svg { flex: none; margin-top: 2px; color: var(--text-faint); }
.cc-pl button.cc-pl-off .cc-pl-n, .cc-pl button.cc-pl-off .cc-pl-c, .cc-acc-off .cc-acc-t { color: var(--text-faint); }
/* Sin nada que aprobar, el cero no va en rojo. */
.cc-gk-vos.cc-gk-cero .cc-k-c b { color: var(--text-faint); }
.cc-pr-acc { display: flex; flex-wrap: wrap; gap: var(--sp-2); }
.cc-hoja > [data-ir] { margin-top: var(--sp-3); }
.cc-atn li.cc-atn-vacio { display: block; cursor: default; font: var(--t-parrafo); color: var(--text-muted); }
/* Lo que se frenó o quedó pendiente (2026-09-25): el historial de abajo, por día. */
.cc-hist { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
.cc-hist li.cc-hist-f { display: grid; grid-template-columns: 12px 48px minmax(0, 1fr) auto; gap: var(--sp-3); align-items: start; padding: var(--sp-3) 0; border-top: 1px solid var(--border-soft); }
.cc-hist li.cc-hist-dia + li.cc-hist-f { border-top: 0; }
.cc-hist li.cc-hist-dia { padding: var(--sp-4) 0 var(--sp-1); font: var(--t-tabla-enc); letter-spacing: .04em; text-transform: uppercase; color: var(--text-faint); }
.cc-hist li.cc-hist-dia:first-child { padding-top: 0; }
.cc-hist .cc-sv { margin-top: var(--sp-1); }
.cc-hist time { font: 400 12px/18px var(--font-mono); color: var(--text-faint); font-variant-numeric: tabular-nums; }
.cc-hist-t { display: block; font: var(--t-tabla); color: var(--text); }
.cc-hist-t strong { font-weight: 600; }
.cc-hist .c-sub { display: block; margin-top: var(--sp-1); }
.cc-hist-a { display: flex; align-items: center; gap: var(--sp-2); color: var(--text-faint); }
/* Lo que ya se resolvió baja de tono: sigue en la historia, pero no pide nada. */
.cc-hist li.cc-hist-ok .cc-hist-t { color: var(--text-muted); }
.cc-hist li.cc-sigue-f { cursor: pointer; }
.cc-hist li.cc-sigue-f:hover .cc-hist-t { text-decoration: underline; }
@media (max-width: 560px) {
  /* En el teléfono: la hora arriba, el texto y las etiquetas debajo, a todo el ancho. */
  .cc-hist li.cc-hist-f { grid-template-columns: 12px minmax(0, 1fr); gap: var(--sp-1) var(--sp-3); }
  .cc-hist li.cc-hist-f time { grid-column: 2; grid-row: 1; }
  .cc-hist li.cc-hist-f > span:nth-of-type(2) { grid-column: 2; grid-row: 2; }
  .cc-hist li.cc-hist-f .cc-hist-a { grid-column: 2; grid-row: 3; flex-wrap: wrap; margin-top: var(--sp-1); }
  .cc-hist li.cc-hist-f .cc-hist-a > svg { margin-left: auto; }
}

/* Lo que necesita atención */
.cc-atn { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
.cc-atn li { display: grid; grid-template-columns: 12px minmax(0, 1.4fr) minmax(0, 1fr) 170px; gap: var(--sp-4); align-items: center; padding: var(--sp-3) 0; border-top: 1px solid var(--border-soft); cursor: pointer; }
.cc-atn li:first-child { border-top: 0; }
.cc-atn li:hover .cc-pr-t { text-decoration: underline; }
.cc-atn .c-sub { display: block; }
.cc-atn-t { font: var(--t-tabla); }
.cc-atn-a { justify-self: end; display: flex; align-items: center; gap: var(--sp-2); font: var(--t-leyenda); color: var(--text-muted); text-align: right; }

/* Teléfono (se muestra solo hasta 560px; ver el bloque del teléfono al final) */
.cc-tel-est { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2); }
.cc-tel .cc-ahora { align-items: flex-start; padding: var(--sp-3) var(--sp-4); border: 1px solid var(--border); border-radius: var(--radius-lg); background: var(--surface); }
.cc-tel .cc-ahora > svg { margin-top: var(--sp-1); }
.cc-tcard { display: flex; flex-direction: column; gap: var(--sp-3); padding: var(--sp-4); background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg); box-shadow: var(--elev-1); }
.cc-tger { border-color: var(--accent); }
.cc-tvol { display: grid; grid-template-columns: 40px minmax(0, 1fr); gap: var(--sp-3); align-items: center; }
.cc-tvol .cc-av-vol { width: 40px; height: 40px; }
.cc-tvol .cc-av-vol .cc-arc { left: -5px; top: -5px; width: 50px; height: 50px; }
.cc-tvol .cc-av-vol svg:not(.cc-arc) { width: 20px; height: 20px; }
.cc-tvol b { display: block; font: var(--t-seccion); }
.cc-tvol .cc-gk-s { margin-top: var(--sp-1); }
.cc-tvos { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: var(--sp-1) var(--sp-3); align-items: center; padding-top: var(--sp-3); border-top: 1px solid var(--border-soft); }
.cc-tlbl { grid-column: 1 / -1; font: var(--t-tabla-enc); letter-spacing: .04em; text-transform: uppercase; color: var(--red-text); }
.cc-tvos-s { font: var(--t-leyenda); color: var(--text-muted); }
.cc-tvos .btn-primary { grid-row: 2 / 4; grid-column: 2; }
.cc-acc { padding: 0; gap: 0; }
.cc-acc-h { display: flex; align-items: center; gap: var(--sp-2); width: 100%; min-height: 56px; padding: var(--sp-3) var(--sp-4); border: 0; background: none; text-align: left; cursor: pointer; }
.cc-acc-num { flex: none; display: grid; place-items: center; width: 20px; height: 20px; border-radius: 50%; background: var(--text); color: var(--surface); font: var(--t-chip); }
.cc-acc-t { font: var(--t-tabla-enc); letter-spacing: .04em; text-transform: uppercase; white-space: nowrap; }
.cc-acc-c { margin-left: auto; font: var(--t-tabla); color: var(--text-muted); white-space: nowrap; }
.cc-acc-c b { color: var(--text); }
.cc-acc-chev { display: grid; color: var(--text-faint); transition: transform var(--dur-2) var(--ease); }
.cc-acc-h[aria-expanded="true"] .cc-acc-chev { transform: rotate(90deg); }
.cc-acc-h[aria-expanded="true"] > .cc-bdg { display: none; }
.cc-acc-h[aria-expanded="false"] + .cc-acc-b { display: none; }
.cc-acc-ag { display: flex; align-items: center; gap: var(--sp-2); padding: 0 var(--sp-4) var(--sp-3); font: var(--t-leyenda); color: var(--text-muted); }
.cc-acc-ag.cc-trab { color: var(--green-text); font-weight: 600; }
.cc-acc-ag .cc-av { width: 20px; height: 20px; }
.cc-acc-ag .cc-av .cc-arc { left: -4px; top: -4px; width: 28px; height: 28px; }
.cc-acc-ag .cc-av svg:not(.cc-arc) { width: 10px; height: 10px; }
.cc-pl { list-style: none; margin: 0; padding: 0 var(--sp-4) var(--sp-1); display: flex; flex-direction: column; }
.cc-pl li { border-top: 1px solid var(--border-soft); }
.cc-pl button { display: grid; grid-template-columns: 16px minmax(0, 1fr) auto; gap: var(--sp-3); align-items: center; width: 100%; min-height: 52px; padding: var(--sp-2) 0; border: 0; background: none; text-align: left; cursor: pointer; }
.cc-pl-i { display: grid; place-items: center; color: var(--text-faint); }
.cc-pl-i.cc-trab { color: var(--green-text); }
.cc-pl-i.cc-vos { color: var(--red-text); }
.cc-pl-e { display: block; margin-top: var(--sp-1); font: var(--t-leyenda); color: var(--text-faint); }
.cc-pl-e.cc-trab { color: var(--green-text); font-weight: 600; }
.cc-pl-e.cc-vos { color: var(--red-text); font-weight: 600; }
/* La cuenta es el dato principal del renglón: 16 · 600. */
.cc-pl-c { display: flex; align-items: center; gap: var(--sp-2); font: var(--t-seccion); font-variant-numeric: tabular-nums; }
.cc-tbus { display: flex; align-items: center; gap: var(--sp-2); min-height: 48px; padding: 0 var(--sp-3); background: var(--surface); border: 1px solid var(--border-input); border-radius: var(--radius); color: var(--text-faint); }
.cc-tbus:focus-within { border-color: var(--accent-strong); box-shadow: var(--foco-halo); }
.cc-tbus input { font-size: 16px; }
.cc-ej { display: flex; flex-wrap: wrap; gap: var(--sp-2); }
.cc-ej button { min-height: 44px; padding: 0 var(--sp-3); border: 1px solid var(--border-control); border-radius: var(--radius-full); background: var(--surface); font: 500 12px/1 var(--font-mono); color: var(--text); cursor: pointer; }
.cc-ej button[aria-pressed="true"] { background: var(--text); border-color: var(--text); color: var(--surface); }
.cc-tord { display: flex; flex-direction: column; gap: var(--sp-1); }
.cc-tord .mat-sku { margin: 0; }
.cc-tsel { display: flex; flex-direction: column; gap: var(--sp-3); }
.cc-tsel:focus { outline: none; }
.modal-body.cc-hoja { display: flex; flex-direction: column; gap: var(--sp-3); }

@keyframes cc-girar { to { transform: rotate(360deg); } }
@keyframes cc-latido { 0%, 100% { box-shadow: 0 0 0 0 var(--green-border); } 50% { box-shadow: 0 0 0 6px transparent; } }
@keyframes cc-aca { 0%, 100% { box-shadow: 0 0 0 4px var(--cc-anillo); } 50% { box-shadow: 0 0 0 14px var(--cc-anillo); } }

@media (prefers-reduced-motion: reduce) {
  .cc-av .cc-arc, .cc-wk svg, .cc-dot, .cc-k.cc-aca { animation: none; }
  .cc-mundo.cc-anim, .cc-seg, .cc-seg.cc-abierto, .cc-mm { transition: none; }
}

/* Con el panel abierto y poco ancho, el minimapa chocaría con el zoom. */
@media (max-width: 1100px) {
  .cc-con-seg .cc-mm { display: none; }
}
/* Hasta 900 px todo lo que se toca mide 44 y los campos, 48 con letra de 16
   (DISENO §3.4). Los botones de la app ya lo hacen solos. */
@media (max-width: 900px) {
  .cc-lienzo { height: 560px; }
  .cc-buscar-in { min-height: 48px; }
  .cc-buscar input { font-size: 16px; }
  .cc-buscar li button { min-height: 44px; }
  .cc-seg-h [data-cerrar] { width: 44px; height: 44px; margin: -10px -10px -10px 0; }
  /* La "×" del modal de esta pantalla también a 44 × 44 (la de la app queda en 40 de ancho). */
  .cc-modal .modal-header [data-close] { width: 44px; height: 44px; margin: -10px -10px -10px 0; }
}

/* ---- Aviso de ambiente ---- */

/* Franja para distinguir un ambiente de pruebas de producción a simple vista.
   La enciende public/app.js con lo que devuelve /healthz; sin la variable
   ENTORNO no existe. Va fija y por encima de todo (el login es z-index 100 y
   los modales 110/120) porque es un dato que no debe poder taparse. */
.entorno-banner {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 200;
  height: 32px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 0 16px;
  background: var(--amber-bg);
  color: var(--amber-text);
  border-bottom: 1px solid var(--amber-border);
  font-size: 12px;
  font-weight: 600;
  text-align: center;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

/* La versión corta del aviso, para el teléfono (§35); la muestra el bloque de ≤ 680px. */
.entorno-corto { display: none; }

/* El shell y el login se corren 32px para dejarle sitio: #app-root fija su
   alto y .login-view es position:fixed con inset:0, así que ninguno de los dos
   se acomoda solo. Padding en el body y no margen en #app-root: el margen se
   escapaba por arriba del body, lo corría 32px sin achicarlo y la página
   quedaba 32px más alta que la ventana (al terminar de desplazar el menú, o
   con un modal abierto, subía todo y abajo quedaba una franja clara). */
body.con-banner { padding-top: 32px; }
body.con-banner #app-root {
  height: calc(100vh - 32px);
  height: calc(100dvh - 32px);
}
body.con-banner .content { height: calc(100vh - 32px); height: calc(100dvh - 32px); }
body.con-banner .login-view { top: 32px; }

/* Editor de materiales de un BOM: es el único campo de la tarjeta que vale por
   una lista, porque un BOM sin sus líneas no es nada. */
/* Casillas de selección múltiple (el equipo de una instalación). Casillas y no
   un <select multiple>: en un teléfono el select múltiple es casi inusable. */
.asis-casillas { display: flex; flex-wrap: wrap; gap: 6px 14px; padding: 4px 0; }
.asis-casilla {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 32px;
  min-width: 160px;
  font-size: 13px;
  font-weight: 400;
  cursor: pointer;
  margin: 0;
}
.asis-casilla input { width: 16px; height: 16px; margin: 0; flex: none; cursor: pointer; }

.asis-lineas { display: flex; flex-direction: column; gap: 6px; }

.asis-linea {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 96px 30px;
  gap: 6px;
  align-items: center;
}

.asis-linea select,
.asis-linea input {
  border: 1px solid var(--border-input);
  border-radius: var(--radius-sm);
  padding: 7px 9px;
  background: var(--surface);
  color: var(--text);
  font-size: 14px;
  /* `min-width: 0` es lo que impide que un nombre de parte largo estire la
     columna y empuje la cantidad fuera de la tarjeta: por defecto un <select>
     toma como ancho mínimo el de su opción más larga. */
  min-width: 0;
  width: 100%;
  max-width: 100%;
  text-overflow: ellipsis;
}

.asis-linea input { text-align: right; }
.asis-linea select:focus, .asis-linea input:focus { border-color: var(--accent-strong); outline: none; box-shadow: var(--foco-halo); }
.asis-agregar { align-self: flex-start; margin-top: 2px; }

/* Los desplegables de la tarjeta (BOM, tipo de instalación) viven dentro de una
   grilla de dos columnas. Sin esto, el <select> nativo reclama el ancho de su
   opción más larga —"CHG-48 — Cargador Wallbox 48A"— y desborda la tarjeta.
   `min-width: 0` en el .field es lo que deja que la columna de la grilla mande.
.asis-tarjeta .field { min-width: 0; }
   La casilla queda FUERA de esa regla a propósito: un checkbox al 100% del
   ancho empujaba su etiqueta fuera de la tarjeta. */
.asis-tarjeta .field select,
.asis-tarjeta .field input:not([type="checkbox"]),
.asis-tarjeta .field textarea {
  width: 100%;
  min-width: 0;
  max-width: 100%;
  box-sizing: border-box;
  text-overflow: ellipsis;
}

/* Los campos de la tarjeta se acomodan solos: dos columnas cuando entran, una
   cuando no. `orden.crear` tiene nueve campos y en una sola columna la tarjeta
   quedaba más alta que la ventana. El mínimo de 200px es lo que evita que una
   columna quede tan angosta que el desplegable no se pueda leer. */
.asis-campos {
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 12px 14px;
}

/* La clase la pone public/js/asistente.js. Se hace así y no con :has() para no
   depender del soporte del selector. */
.asis-campos > .campo-ancho { grid-column: 1 / -1; }

/* Cuando no hay ancho, la fila de material se parte en dos: el desplegable
   ocupa su propia línea y la cantidad queda debajo. Sin esto, a partir de unos
   650px de ventana el <select> se queda con ~60px y muestra "A…", "B…", "C…" —
   técnicamente no desborda, pero es inservible. El umbral sale de la cuenta:
   la barra lateral son 240px fijos, y el desplegable necesita unos 200px para
   que se lea "CHG-48 — Cargador Wallbox 48A". */
@media (max-width: 680px) {
  /* Simulador (§30): la línea en dos renglones, el BOM con quitar al lado y
     debajo cantidad y fecha. */
  .sim-linea { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) 44px; grid-template-areas: "a a q" "b c c"; }
  .sim-linea > :nth-child(1) { grid-area: a; }
  .sim-linea > :nth-child(2) { grid-area: b; }
  .sim-linea > :nth-child(3) { grid-area: c; }
  .sim-linea > .btn-icon { grid-area: q; }
  /* Franja de ambiente (§35): la versión corta; la larga se cortaba por los dos lados. */
  .entorno-largo { display: none; }
  .entorno-corto { display: inline; }

  /* Una casilla por fila, con área de toque de 44px. */
  .asis-casillas { flex-direction: column; gap: 0; }
  .asis-casilla { min-height: 44px; width: 100%; }
  .asis-linea { grid-template-columns: minmax(0, 1fr) 30px; }
  .asis-linea select { grid-column: 1 / -1; }
  .asis-linea input { text-align: left; }
}

/* ===========================================================================
   Pantallas chicas
   ===========================================================================

   El dueño tiene que poder consultar y operar el MRP desde el teléfono sin
   abrir la laptop, así que esto no es un "que no se rompa": es una vista de
   primera clase.

   Antes había una sola regla a 900px que encogía la barra lateral a un riel de
   76px con puros iconos. En un teléfono de 375px eso se comía uno de cada cinco
   píxeles de ancho y, sin las etiquetas, tampoco decía en qué sección estabas:
   pagaba el precio del menú sin dar el beneficio. Ahora la barra pasa a ser un
   cajón que se abre con el botón de la barra superior (public/app.js), con las
   etiquetas completas.

   Los dos cortes:
     ≤ 900px  el cajón, la grilla de formularios en una columna, tablas densas
     ≤ 560px  teléfono: hoja inferior en los modales, calendario en lista
   =========================================================================== */

@media (max-width: 900px) {
  /* ---- la barra lateral se vuelve cajón ----
     Sale por la derecha, del mismo lado que el botón del menú (MEJORAS-UI N2). */
  .sidebar {
    position: fixed;
    top: 0;
    bottom: 0;
    right: 0;
    width: 268px;
    max-width: 84vw;
    z-index: 90;
    transform: translateX(100%);
    transition: transform var(--dur-2) var(--ease);
    box-shadow: var(--elev-3);
    /* El notch y la barra de gestos de iOS. */
    padding: max(20px, env(safe-area-inset-top)) 14px max(16px, env(safe-area-inset-bottom));
  }
  body.nav-abierta .sidebar { transform: translateX(0); }
  /* La franja de ambiente es fija, de 32px y va encima de todo: sin esto el
     cajón arranca debajo de ella y la franja le tapa el logo. */
  body.con-banner .sidebar { top: 32px; }
  .nav-toggle { display: inline-flex; }

  /* Los items del cajón se tocan con el dedo, no con un puntero. */
  .nav { padding: var(--sp-3); font-size: 16px; }
  .nav-grupo-titulo { min-height: 44px; }

  /* ---- barra superior (MEJORAS-UI N1, versión A) ----
     La marca junto al título, a la izquierda, y el botón del menú (solo el
     ícono; su nombre va en el aria-label) arriba a la derecha. Marca y botón
     se alinean con la primera línea del título, no con el centro de la barra,
     porque el subtítulo puede ocupar dos líneas.
     Las acciones van al lado del título si caben; si no (Rutas, Planning),
     bajan a su propia fila. El título se guarda 240 px, lo que mide "Órdenes
     de instalación": sin eso las acciones lo aplastaban hasta dejar una
     palabra por renglón. El botón va fuera del flujo para quedarse arriba
     aunque las acciones bajen; el relleno derecho le guarda el lugar
     (8 + 44 + 12). En el teléfono vuelve al flujo (bloque de ≤ 560px). */
  .topbar { position: relative; flex-wrap: wrap; padding: var(--sp-3) 64px var(--sp-3) var(--sp-4); gap: var(--sp-3); }
  .topbar-marca { display: block; order: 1; flex: none; width: 30px; height: auto; align-self: flex-start; margin-top: 3px; }
  .topbar-heading { order: 2; flex: 1 1 240px; }
  .topbar-actions { order: 3; flex-shrink: 1; flex-wrap: wrap; min-width: 0; }
  /* 4 = 12 de relleno − 8: así el ícono queda centrado con la primera línea. */
  .nav-toggle { position: absolute; top: 4px; right: 8px; }
  .screen-body { padding: var(--sp-4); gap: var(--sp-4); }

  /* Una sola columna: dos campos en 375px no son dos campos, son dos recortes. */
  .form-grid { grid-template-columns: 1fr; }
  .asis-campos { grid-template-columns: 1fr; }

  /* ---- 16px y 44px en TODO lo que se escribe ----
     Por debajo de 16px, Safari de iOS hace zoom solo al enfocar el campo y deja
     la pantalla corrida y a medio leer. Va por elemento y sin contenedor: la
     lista anterior (.screen-body, .modal, .login-card) dejaba fuera la barra
     superior, y las fechas de Rutas y el periodo del Dashboard hacían zoom. */
  /* :is() les da a los tres la especificidad del más fuerte (0,2,1): así le
     ganan a reglas con clase como `.field select` o `.asis-composer textarea`,
     que si no se quedaban en 14px. */
  :is(input:not([type="checkbox"]):not([type="radio"]), select, textarea) {
    font-size: 16px;
    min-height: 44px;
  }
  /* El buscador de Solicitudes y Órdenes (2026-09-30): los 44 los da la caja,
     como el filtro de al lado; el campo de adentro no suma los suyos (medía 46). */
  .buscador { min-height: 44px; }
  .buscador input[type="search"] { min-height: 0; }
  .field input,
  .field select,
  .field textarea { padding: 11px 12px; }
  /* La cantidad de Comprar equipo no va en un .field: 48 como los campos (medía 44; re-prueba del 29/09, #77). */
  .pa-equipo .pa-equipo-cant input { min-height: 48px; }
  /* El de la fecha del calendario es angosto a propósito; con 16px necesita
     un poco más de caja o el número queda cortado. */
  .input-sm { width: 84px; }

  /* Lo que se despliega también se toca: 44 px, como los botones (AG5). */
  .bom-mats > summary,
  .asis-todo > summary { min-height: 44px; }

  /* ---- tablas: la primera columna se queda fija ----
     Las tablas del MRP tienen hasta diez columnas y en un teléfono se ven tres.
     Sin esto, al desplazarse a la derecha se pierde de vista de qué fila se
     está leyendo, que es justo el dato que importa. */
  .tbl td { padding: var(--sp-2) var(--sp-3); }
  .tbl th:first-child,
  .tbl td:first-child {
    position: sticky;
    left: 0;
    z-index: 2;
    background: var(--surface);
    /* La sombra dice "hay más a la derecha" sin ocupar lugar. */
    box-shadow: 1px 0 0 var(--border-soft);
  }
  .tbl th:first-child {
    z-index: 3;
    background: var(--surface-muted);
  }
  .tbl tbody tr:hover td:first-child { background: var(--surface-muted); }
  .tbl-bad td:first-child { background: var(--red-bg); }
  /* La tabla ya se desplaza a lo ancho, así que partir una celda en tres
     renglones ("vie, / 5 / sept") no gana nada: hace la fila más alta y más
     difícil de leer. La primera columna sí envuelve, porque está fija y una
     columna ancha ahí se come la pantalla. */
  .tbl td { white-space: nowrap; }
  .tbl td:first-child { white-space: normal; min-width: 96px; }
  /* "Datos recibidos" de una solicitud: el dato se parte en renglones en vez
     de estirar la tabla (MEJORAS-UI SO2). */
  .tbl.tbl-datos td { white-space: normal; }

  /* ---- 44px de alto en todo lo que se toca ---- */
  .btn-primary,
  .btn-secondary,
  .btn-danger { padding: 11px 16px; }
  .btn-sm { padding: 9px 13px; }
  .modal-header [data-close] { width: 44px; height: 44px; margin: -10px -10px -10px 0; }
  .btn-primary,
  .btn-secondary,
  .btn-danger,
  .btn-ghost,
  .filter,
  .segmented-opt,
  .asis-chip { min-height: 44px; }
  .nav-toggle { width: 44px; height: 44px; }
  .cuenta-salir { min-height: 44px; }
  .sim-linea .btn-icon { width: 44px; height: 44px; }
  /* Líneas del BOM: la papelera pasa a 44px (.btn-icon, más abajo) y su columna también. */
  .bom-lineas-rotulos,
  .bom-linea { grid-template-columns: minmax(0, 1fr) 124px 44px; }
  .linea-quitar { width: 44px; height: 44px; }
  .ag-contactos .lineas-rotulos,
  .ag-contactos .linea { grid-template-columns: minmax(0, 1fr) 136px minmax(0, 1.5fr) 44px; }
  /* Rutas (§29): la barra de controles a 44px y el rango a todo el ancho. */
  .rutas-rango { height: 44px; flex: 1 1 100%; }
  .rutas-rango button { width: 44px; }
  .rutas-rango input { flex: 1; min-width: 0; min-height: 0; }
  .btn-icon { width: 44px; height: 44px; }
  .menu-item { min-height: 44px; }
  /* Pendiente del bloque de Rutas (MEJORAS-UI §29): el botón y el selector
     compactos de las tarjetas de ruta viven en filas de alto fijo, y subirlos
     a 44px ahora rompe la tarjeta. */
  /* QA P10: en el teléfono también miden 44 (principio 5). */
  /* QA P10: si la parada no entra en un renglón, se reorganiza (3.10): el
     estado y "Mover a…" bajan al segundo, alineados con el texto. */
  .stop { flex-wrap: wrap; row-gap: var(--sp-2); }
  .stop-main { flex: 1 1 calc(100% - 32px); }
  .stop > .chip { margin-left: 32px; }
  .stop > .stop-move { margin-left: auto; }
  /* Hasta 900 la letra del selector sube a 16 (para que el iPhone no haga zoom)
     y en 104 de ancho, con la flecha, se cortaba en "Mover a." (2026-10-03): el
     ancho lo da su opción más larga. Va en el segundo renglón de la parada. */
  .stop-move { width: auto; }

  /* ---- asistente ---- */
  .asis-composer {
    padding-bottom: max(10px, env(safe-area-inset-bottom));
  }
}

@media (max-width: 560px) {
  /* ---- el título primero (MEJORAS-UI §36 y N1) ----
     Arriba la marca, el título y el botón del menú, como en cualquier app, y
     debajo las acciones en su propia fila, llenando el ancho para tocarlas
     fácil. No se esconde ninguna acción. */
  .topbar { padding-right: var(--sp-4); }
  .topbar-heading { flex: 1 1 0; min-width: 0; }
  .nav-toggle { position: static; order: 3; align-self: flex-start; margin: -8px -8px 0 0; }
  .topbar-actions { order: 4; flex: 1 1 100%; margin-left: 0; }
  .topbar-actions:empty { display: none; }
  .topbar-actions > .btn-primary,
  .topbar-actions > .btn-secondary { flex: 1 1 auto; justify-content: center; }
  .topbar-actions > select { flex: 1 1 auto; }
  /* El "Hoy: …" de Planning va en su propio renglón. */
  .topbar-actions > .text-muted { flex: 1 1 100%; text-align: left; }
  /* Rutas (§29): en 375px las dos fechas no entran con la "a" en medio y se
     cortaba el año; una línea las separa en su lugar. */
  .rutas-rango-a { display: none; }
  .rutas-rango-a + input { border-left: 1px solid var(--border-control); }
  .rutas-rango input { padding: 0 4px; }
  .topbar-actions .btn-primary,
  .topbar-actions .btn-secondary { padding: 9px 13px; font-size: 13px; }
  /* Las acciones de la barra no siempre son un botón: el Calendario pone ahí
     un formulario con tres números (paneles, por pareja, radio). Sin envolver,
     se salía por el borde derecho y no se podía ni ver ni tocar. */
  .topbar-actions,
  .topbar-actions .flex-row {
    flex-wrap: wrap;
    justify-content: flex-start;
    row-gap: 8px;
    min-width: 0;
    /* La regla base las deja `flex-shrink: 0`, y sin permitirles encoger nunca
       se estrechan lo suficiente como para que `flex-wrap` haga algo: el
       contenido se salía igual por el borde. */
    flex-shrink: 1;
  }

  .screen-body { padding: var(--sp-4) var(--sp-3); }

  /* ---- Dashboard (MEJORAS-UI D3) ----
     En el teléfono los cuatro KPI van en 2×2, más compactos. El ranking (D1 y
     T2) y "Costo aproximado vs. real" (D2) van con las reglas del Dashboard,
     porque dependen del ancho de su tarjeta; el gráfico por mes (D4) lo
     ajusta dashboard.js. */
  .kpi-resumen { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp-3); }
  .kpi-resumen .kpi-card { padding: var(--sp-3); gap: var(--sp-1); }
  .kpi-resumen .kpi-value { font-size: 22px; }

  /* ---- Órdenes e Inventario: cada fila es una tarjeta (MEJORAS-UI §37) ----
     El encabezado de la tabla se esconde y cada dato lleva su nombre arriba
     (data-label). Las clases t-* las pone el JS de cada pantalla: t-titulo va
     primero y a todo el ancho, t-estado arriba a la derecha, t-acciones al
     final. Pisa lo que el bloque de 900px hace con las tablas (la primera
     columna fija y todo en una sola línea). */
  .tbl.tbl-tarjetas thead { display: none; }
  .tbl.tbl-tarjetas,
  .tbl.tbl-tarjetas tbody { display: block; width: 100%; }
  .tbl.tbl-tarjetas tr {
    position: relative;
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--sp-3);
    padding: var(--sp-4);
    border-bottom: 1px solid var(--border-soft);
  }
  .tbl.tbl-tarjetas tr:last-child { border-bottom: 0; }
  .tbl.tbl-tarjetas td,
  .tbl.tbl-tarjetas td:first-child {
    position: static;
    display: block;
    min-width: 0;
    padding: 0;
    border: 0;
    background: none;
    box-shadow: none;
    white-space: normal;
    text-align: left;
    font-size: 14px;
  }
  .tbl.tbl-tarjetas td.num { text-align: left; }
  .tbl.tbl-tarjetas td[data-label]::before {
    content: attr(data-label);
    display: block;
    margin-bottom: var(--sp-1);
    font: var(--t-leyenda);
    color: var(--text-muted);
  }
  .tbl.tbl-tarjetas .t-titulo { grid-column: 1 / -1; order: -2; padding-right: 112px; font-weight: 600; }
  .tbl.tbl-tarjetas .t-primero { order: -1; }
  .tbl.tbl-tarjetas .t-estado { position: absolute; top: var(--sp-4); right: var(--sp-4); }
  .tbl.tbl-tarjetas .t-acciones { grid-column: 1 / -1; justify-self: end; }
  /* CSAT: el comentario, a todo el ancho de la tarjeta y al final. En la
     grilla de tres columnas quedaría en una columna de ~100 px. */
  .tbl.tbl-tarjetas.tbl-csat .csat-comentario { grid-column: 1 / -1; order: 1; min-width: 0; max-width: none; }
  /* El folio y la fecha, cada uno en un renglón (#95, VP-260930-42B7 p2): en
     tres columnas iguales, a 320 px el folio se partía en "OI-" y "000047" y la
     fecha en "mié, 30" y "sep". Cada uno toma su ancho, a 12 del otro. */
  .tbl.tbl-tarjetas.tbl-csat tr { grid-template-columns: max-content max-content minmax(0, 1fr); }
  .tbl.tbl-tarjetas.tbl-csat :is(.csat-orden, td.num) { white-space: nowrap; }
  /* El aviso de faltante (Órdenes): sin estado arriba a la derecha, el nombre usa todo el ancho. */
  .tbl.tbl-tarjetas.tbl-faltante .t-titulo { padding-right: 0; }
  /* Cuatro datos (#123): en tres columnas el suplidor caía solo, en un tercio
     del ancho y en tres renglones. En dos: las cantidades juntas, debajo la
     fecha y el suplidor. */
  .tbl.tbl-tarjetas.tbl-faltante tr { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* Dos íconos entran en la última columna, al lado de los datos. */
  .tbl.tbl-tarjetas .t-acciones-cortas { grid-column: -2 / -1; align-self: end; }
  .tbl.tbl-tarjetas .t-oculta { display: none; }
  /* Toda la tarjeta responde al toque, no celda por celda. */
  .tbl.tbl-tarjetas tr.fila-link:hover { background: var(--surface-muted); }
  .tbl.tbl-tarjetas tr:hover td,
  .tbl.tbl-tarjetas tbody tr:hover td:first-child { background: none; }
  /* La celda del título es negrita por el nombre; el código y la nota de
     debajo vuelven a su peso normal (MEJORAS-UI T1). */
  .tbl.tbl-tarjetas .t-titulo :is(.mat-sku, .c-sub) { font-weight: 400; }
  /* Si la única acción de la fila es el menú "⋯", va arriba a la derecha y el
     estado se corre a su izquierda, en vez de ocupar un renglón al final
     (MEJORAS-UI T2). grid-column: auto hace que se ubique contra la tarjeta
     entera y no contra su columna. */
  .tbl.tbl-tarjetas .t-acciones:has(> .menu-fila:only-child) {
    position: absolute;
    grid-column: auto;
    top: var(--sp-1);
    right: var(--sp-1);
  }
  .tbl.tbl-tarjetas tr:has(> .t-acciones > .menu-fila:only-child) .t-estado { right: 52px; }
  .tbl.tbl-tarjetas tr:has(> .t-acciones > .menu-fila:only-child) .t-titulo { padding-right: 152px; }

  /* ---- Planning (MEJORAS-UI PL1–PL3) ----
     Cada material es una tarjeta con una grilla propia de 8 columnas: arriba
     el nombre y su estado, debajo Hoy y En camino, las 8 semanas en una tira
     (cada una con su nombre y el saldo proyectado, con los colores de la
     tabla) y la acción sugerida, con 12 px entre su texto y el botón. */
  .tbl.tbl-tarjetas.tbl-planning tr { grid-template-columns: repeat(8, minmax(0, 1fr)); gap: var(--sp-3) var(--sp-1); }
  .tbl.tbl-tarjetas.tbl-planning .t-titulo { padding-right: 96px; }
  .tbl.tbl-tarjetas.tbl-planning .p-chip { display: inline-flex; position: absolute; top: var(--sp-4); right: var(--sp-4); }
  /* Hoy, Apartado (2026-09-26) y En camino, en un renglón. */
  .tbl.tbl-tarjetas.tbl-planning .p-hoy { grid-column: 1 / 3; }
  .tbl.tbl-tarjetas.tbl-planning .p-apartado { grid-column: 3 / 5; }
  .tbl.tbl-tarjetas.tbl-planning .p-camino { grid-column: 5 / -1; }
  .tbl.tbl-tarjetas.tbl-planning .p-semana {
    padding: var(--sp-1) 0;
    border-radius: var(--radius-sm);
    text-align: center;
    font-size: 13px;
    font-variant-numeric: tabular-nums;
  }
  .tbl.tbl-tarjetas.tbl-planning .p-semana[data-label]::before { margin-bottom: 0; font-size: 12px; }
  .tbl.tbl-tarjetas.tbl-planning td.cell-falta-tarde { background: var(--red-bg); }
  .tbl.tbl-tarjetas.tbl-planning td.cell-low { background: var(--amber-bg); }
  .tbl.tbl-tarjetas.tbl-planning .p-accion { grid-column: 1 / -1; }
  .tbl.tbl-tarjetas.tbl-planning .p-accion > .btn-sm { margin-top: var(--sp-3); }
  /* Sin sugerencia, "Sin acción" no suma: el estado ya lo dice. */
  .tbl.tbl-tarjetas.tbl-planning .p-accion:has(> .accion:only-child) { display: none; }
  /* Por BOM: el nombre, las instalaciones de cada semana y la cobertura; sus
     componentes, sobre un fondo apenas más oscuro. "También lo consumen…"
     puede ser una lista larga: va en letra normal. */
  .tbl.tbl-tarjetas.tbl-planning tr.p-bom { cursor: pointer; }
  .tbl.tbl-tarjetas.tbl-planning tr.p-sub { background: var(--surface-muted); }
  .tbl.tbl-tarjetas.tbl-planning tr:is(.p-bom, .p-sub) .t-titulo { padding-right: 0; }
  .tbl.tbl-tarjetas.tbl-planning tr.p-sub .p-accion { font-size: 13px; font-weight: 500; }

  /* ---- Simulador (MEJORAS-UI SI1) ----
     Cada material del resultado, una tarjeta como la de Planning: el nombre y,
     debajo, el cambio; Hoy; las 8 semanas en una tira, con los colores de la
     tabla; y la acción (sin acción, ese renglón no ocupa lugar). */
  .tbl.tbl-tarjetas.tbl-sim tr { grid-template-columns: repeat(8, minmax(0, 1fr)); gap: var(--sp-3) var(--sp-1); }
  .tbl.tbl-tarjetas.tbl-sim .t-titulo { padding-right: 0; }
  .tbl.tbl-tarjetas.tbl-sim .p-hoy { grid-column: 1 / -1; }
  .tbl.tbl-tarjetas.tbl-sim .p-semana {
    padding: var(--sp-1) 0;
    border-radius: var(--radius-sm);
    text-align: center;
    font-size: 13px;
    font-variant-numeric: tabular-nums;
  }
  .tbl.tbl-tarjetas.tbl-sim .p-semana[data-label]::before { margin-bottom: 0; font-size: 12px; }
  .tbl.tbl-tarjetas.tbl-sim td.cell-falta-tarde { background: var(--red-bg); }
  .tbl.tbl-tarjetas.tbl-sim td.cell-low { background: var(--amber-bg); }
  .tbl.tbl-tarjetas.tbl-sim :is(.s-cambio, .p-accion) { grid-column: 1 / -1; }
  .tbl.tbl-tarjetas.tbl-sim .s-cambio { order: -1; }

  /* ---- Órdenes (MEJORAS-UI O1–O4) ----
     El filtro con botones en una lista desplegable: los chips no entran en
     375px. Hoy lo usa Viáticos; Órdenes tiene la lista en todos los anchos. */
  .filtros-ordenes > .filter { display: none; }
  .filtros-ordenes > .filtro-estado-wrap { display: block; width: 100%; }
  .filtro-estado {
    width: 100%;
    min-height: 44px;
    padding: 0 12px;
    border: 1px solid var(--border-control);
    border-radius: var(--radius);
    background: var(--surface);
    color: var(--text);
    font: 500 16px/1.2 var(--font-sans);
  }

  /* La tarjeta compacta, de tres renglones y sin etiquetas: el cliente (y el
     estado a la derecha) · número de orden, BOM y fecha · precio y costo
     aproximado. El vehículo y el operario vacíos, o la cantidad 1, no ocupan
     lugar. Pisa la tarjeta de §37, que sigue igual en Inventario. El ::after
     es el salto de línea antes de los montos. */
  .tbl.tbl-ordenes .t-vacio,
  .tbl.tbl-ordenes .t-uno { display: none; }
  .tbl.tbl-ordenes tr {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--sp-1) 0;
    padding: var(--sp-4);
  }
  .tbl.tbl-ordenes tr::after { content: ''; order: 6; flex-basis: 100%; height: 0; }
  .tbl.tbl-ordenes td[data-label]::before { content: none; }
  /* tr > td: tiene que pesar más que `.tbl.tbl-tarjetas td:first-child` de §37,
     que la deja como bloque. */
  .tbl.tbl-ordenes tr > td.t-titulo { display: contents; }
  .tbl.tbl-ordenes .t-titulo .fila-titulo { order: 1; flex: 1 1 100%; padding-right: 112px; text-align: left; }
  /* Los renglones ya se separan 4 (gap): el código no suma margen propio y
     queda a 4 del nombre (DISENO §3.10). Igual en Part Data y Operarios. */
  .tbl.tbl-ordenes .t-titulo .mat-sku { order: 2; margin-top: 0; font-size: 13px; }
  /* El número de serie que encontró el buscador (2026-10-03): en su renglón,
     debajo del nombre y antes del folio. */
  .tbl.tbl-ordenes .t-titulo .or-serie { order: 1; flex: 1 1 100%; margin-top: 0; }
  /* La agencia (quien paga, 2026-09-13) va con el vehículo y con su nombre
     delante: sin él, "Kia · Kia" (agencia y BOM) no se distinguían. Vacía no
     ocupa lugar (t-vacio). */
  .tbl.tbl-ordenes .t-agencia { order: 5; }
  /* El tipo de trabajo (2026-09-13) va delante del BOM en el segundo renglón. */
  .tbl.tbl-ordenes .t-trabajo { order: 3; }
  .tbl.tbl-ordenes .t-bom { order: 3; }
  .tbl.tbl-ordenes .t-fecha { order: 4; }
  .tbl.tbl-ordenes .t-vehiculo { order: 5; }
  .tbl.tbl-ordenes .t-precio { order: 7; font-size: 14px; font-weight: 600; }
  .tbl.tbl-ordenes .t-costo { order: 8; }
  .tbl.tbl-ordenes .t-operario { order: 9; }
  .tbl.tbl-ordenes .t-cant { order: 10; }
  /* SLA (2026-09-13): arriba a la derecha solo cabe el estado; el chip de SLA
     y su detalle cierran el segundo bloque en su propio renglón. */
  .tbl.tbl-ordenes .t-estado .estado-celda .chip ~ .chip { display: none; }
  .tbl.tbl-ordenes .t-sla { display: flex; align-items: center; gap: var(--sp-2); order: 6; flex-basis: 100%; }
  .tbl.tbl-ordenes .t-sla.t-vacio { display: none; }
  .tbl.tbl-ordenes :is(.t-agencia, .t-trabajo, .t-bom, .t-fecha, .t-vehiculo, .t-costo, .t-operario, .t-cant) { font-size: 13px; color: var(--text-muted); }
  /* Con td y [data-label] para pesar más que la regla de arriba, y en línea:
     la de §37 pone la etiqueta como bloque, arriba del dato. */
  .tbl.tbl-ordenes td:is(.t-agencia, .t-trabajo, .t-bom, .t-fecha, .t-vehiculo, .t-costo, .t-operario, .t-cant)[data-label]::before {
    content: "·";
    display: inline;
    margin: 0 6px;
    font: inherit;
    color: var(--text-faint);
  }
  .tbl.tbl-ordenes td.t-costo[data-label]::before { content: "· Costo aprox. "; }
  .tbl.tbl-ordenes td.t-operario[data-label]::before { content: "· Operario: "; }
  .tbl.tbl-ordenes td.t-agencia[data-label]::before { content: "· Agencia: "; }
  .tbl.tbl-ordenes td.t-cant[data-label]::before { content: "· Cant. "; }
  /* Los montos no se parten: si no entran, el costo baja entero. */
  .tbl.tbl-ordenes :is(.t-precio, .t-costo) { flex: none; white-space: nowrap; }

  /* ---- Part Data (MEJORAS-UI PD1) ----
     Tarjeta compacta de tres renglones, sin nombres de columna, como Órdenes:
     el nombre; código · unidad · suplidor; el costo y los parámetros de
     reorden. El estado y el "⋯" van arriba a la derecha (T2). */
  .tbl.tbl-tarjetas.tbl-partes tr {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--sp-1) var(--sp-2);
    padding: var(--sp-4);
  }
  /* Salto de renglón antes del costo. */
  .tbl.tbl-tarjetas.tbl-partes tr::after { content: ''; order: 6; flex-basis: 100%; height: 0; }
  .tbl.tbl-tarjetas.tbl-partes td[data-label]::before { content: none; }
  .tbl.tbl-tarjetas.tbl-partes tr > td.t-titulo { display: contents; }
  .tbl.tbl-tarjetas.tbl-partes .t-titulo .mat-name { order: 1; flex: 1 1 100%; padding-right: 152px; }
  .tbl.tbl-tarjetas.tbl-partes .t-titulo .mat-sku { order: 2; margin-top: 0; font-size: 13px; }
  .tbl.tbl-tarjetas.tbl-partes .pd-uom { order: 3; }
  .tbl.tbl-tarjetas.tbl-partes .pd-suplidor { order: 4; }
  .tbl.tbl-tarjetas.tbl-partes .pd-costo { order: 7; font-size: 14px; font-weight: 600; }
  .tbl.tbl-tarjetas.tbl-partes .pd-costo .mat-sku { display: none; }
  .tbl.tbl-tarjetas.tbl-partes .pd-disp { order: 8; }
  .tbl.tbl-tarjetas.tbl-partes .pd-reorden { order: 9; }
  .tbl.tbl-tarjetas.tbl-partes .pd-entrega { order: 10; }
  .tbl.tbl-tarjetas.tbl-partes .pd-moq { order: 11; }
  .tbl.tbl-tarjetas.tbl-partes :is(.pd-uom, .pd-suplidor, .pd-disp, .pd-reorden, .pd-entrega, .pd-moq) {
    font-size: 13px;
    color: var(--text-muted);
  }
  .tbl.tbl-tarjetas.tbl-partes :is(.pd-costo, .pd-disp, .pd-reorden, .pd-entrega, .pd-moq) { flex: none; white-space: nowrap; }
  /* Separadores y nombres cortos, en el mismo renglón. */
  .tbl.tbl-tarjetas.tbl-partes td:is(.pd-uom, .pd-suplidor, .pd-disp, .pd-reorden, .pd-entrega, .pd-moq)[data-label]::before {
    display: inline;
    margin: 0;
    font: inherit;
    color: inherit;
  }
  .tbl.tbl-tarjetas.tbl-partes td:is(.pd-uom, .pd-suplidor)[data-label]::before { content: "· "; }
  .tbl.tbl-tarjetas.tbl-partes td.pd-disp[data-label]::before { content: "· Disponible "; }
  .tbl.tbl-tarjetas.tbl-partes td.pd-reorden[data-label]::before { content: "· Reorden "; }
  .tbl.tbl-tarjetas.tbl-partes td.pd-entrega[data-label]::before { content: "· Entrega "; }
  .tbl.tbl-tarjetas.tbl-partes td.pd-moq[data-label]::before { content: "· MOQ "; }

  /* ---- Viáticos (2026-09-15, aprobado en preview por Fabiana) ----
     La tarjeta compacta de Órdenes: quién (y el estado arriba a la derecha) ·
     categoría, comercio y fecha · el total. Sin nombres de columna; 4 entre
     renglones y 8 entre los datos de un renglón (DISENO §3.10, como Part Data). */
  .tbl.tbl-tarjetas.tbl-viaticos tr {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--sp-1) var(--sp-2);
    padding: var(--sp-4);
  }
  /* Salto de renglón antes del total. */
  .tbl.tbl-tarjetas.tbl-viaticos tr::after { content: ''; order: 6; flex-basis: 100%; height: 0; }
  .tbl.tbl-tarjetas.tbl-viaticos td[data-label]::before { content: none; }
  .tbl.tbl-tarjetas.tbl-viaticos tr > td.t-titulo { display: contents; }
  .tbl.tbl-tarjetas.tbl-viaticos .t-titulo .fila-titulo { order: 1; flex: 1 1 100%; padding-right: 112px; text-align: left; }
  .tbl.tbl-tarjetas.tbl-viaticos .t-titulo .c-sub { order: 2; flex: 1 1 100%; margin-top: 0; }
  .tbl.tbl-tarjetas.tbl-viaticos .t-categoria { order: 3; }
  .tbl.tbl-tarjetas.tbl-viaticos .t-comercio { order: 4; }
  .tbl.tbl-tarjetas.tbl-viaticos .t-fecha { order: 5; }
  .tbl.tbl-tarjetas.tbl-viaticos :is(.t-categoria, .t-comercio, .t-fecha) { font: var(--t-tabla); color: var(--text-muted); }
  /* Los separadores, en el mismo renglón. */
  .tbl.tbl-tarjetas.tbl-viaticos td:is(.t-comercio, .t-fecha)[data-label]::before {
    content: "· ";
    display: inline;
    margin: 0;
    font: inherit;
    color: var(--text-faint);
  }
  /* El total no se parte; "cada uno" va al lado, sin negrita. */
  .tbl.tbl-tarjetas.tbl-viaticos .t-total { order: 7; flex: none; white-space: nowrap; font: var(--t-parrafo); font-weight: 600; }
  .tbl.tbl-tarjetas.tbl-viaticos .t-total .c-sub { display: inline; margin: 0 0 0 var(--sp-2); font-weight: 400; }

  /* ---- Operarios (MEJORAS-UI OP1) ----
     Tarjeta compacta: el nombre (toda la tarjeta abre la edición), el teléfono
     y las notas, y los números en un renglón con su nombre corto. El estado va
     arriba a la derecha; el ">" de la fila no se ve. */
  .tbl.tbl-tarjetas.tbl-operarios tr {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--sp-1) var(--sp-2);
    padding: var(--sp-4);
  }
  .tbl.tbl-tarjetas.tbl-operarios tr::after { content: ''; order: 3; flex-basis: 100%; height: 0; }
  .tbl.tbl-tarjetas.tbl-operarios td[data-label]::before { content: none; }
  .tbl.tbl-tarjetas.tbl-operarios tr > td.t-titulo { display: contents; }
  .tbl.tbl-tarjetas.tbl-operarios .t-titulo :is(.fila-titulo, .mat-name) { order: 1; flex: 1 1 100%; padding-right: 112px; text-align: left; }
  .tbl.tbl-tarjetas.tbl-operarios .t-titulo .mat-sku { order: 2; margin-top: 0; font-size: 13px; }
  .tbl.tbl-tarjetas.tbl-operarios .t-titulo .mat-sku:empty { display: none; }
  .tbl.tbl-tarjetas.tbl-operarios .op-comp { order: 4; }
  .tbl.tbl-tarjetas.tbl-operarios .op-30 { order: 5; }
  .tbl.tbl-tarjetas.tbl-operarios .op-prog { order: 6; }
  .tbl.tbl-tarjetas.tbl-operarios .op-ultima { order: 7; }
  /* Fecha de ingreso (2026-09-13): al final del renglón de números; vacía no ocupa lugar. */
  .tbl.tbl-tarjetas.tbl-operarios .op-ingreso { order: 8; }
  .tbl.tbl-tarjetas.tbl-operarios .op-ingreso.t-vacio { display: none; }
  .tbl.tbl-tarjetas.tbl-operarios :is(.op-comp, .op-30, .op-prog, .op-ultima, .op-ingreso) {
    flex: none;
    white-space: nowrap;
    font-size: 13px;
    color: var(--text-muted);
  }
  /* Nombres cortos y separadores, en el mismo renglón. */
  .tbl.tbl-tarjetas.tbl-operarios td:is(.op-comp, .op-30, .op-prog, .op-ultima, .op-ingreso)[data-label]::before {
    display: inline;
    margin: 0;
    font: inherit;
    color: inherit;
  }
  .tbl.tbl-tarjetas.tbl-operarios td.op-comp[data-label]::before { content: "Completadas "; }
  .tbl.tbl-tarjetas.tbl-operarios td.op-30[data-label]::before { content: "· Últimos 30 días "; }
  .tbl.tbl-tarjetas.tbl-operarios td.op-ingreso[data-label]::before { content: "· Ingreso "; }
  .tbl.tbl-tarjetas.tbl-operarios td.op-prog[data-label]::before { content: "· Programadas "; }
  .tbl.tbl-tarjetas.tbl-operarios td.op-ultima[data-label]::before { content: "· Última: "; }

  /* ---- Catálogo BOM (MEJORAS-UI CB1 y CB2) ----
     Cada BOM es una tarjeta de §37, con sus espacios (DISENO §3.10): 16 px
     de margen, 12 entre el nombre y los materiales y 4 entre el nombre y su
     subtítulo. El lugar del estado y del "⋯" lo deja solo el renglón del
     nombre, así los modelos, el tipo y el precio usan todo el ancho.
     "N materiales · costo aprox." es un renglón de 44 px que se despliega
     dentro de la tarjeta, con 8 px entre cada material. */
  .tbl.tbl-tarjetas.tbl-boms tr .t-titulo,
  .tbl.tbl-tarjetas.tbl-boms tr:has(> .t-acciones > .menu-fila:only-child) .t-titulo { padding-right: 0; }
  .tbl.tbl-tarjetas.tbl-boms .t-titulo .mat-name { padding-right: 152px; }
  .tbl.tbl-tarjetas.tbl-boms .bm-mats { grid-column: 1 / -1; }
  .tbl-boms .bom-mats > summary {
    display: flex;
    width: 100%;
    min-height: 44px;
    margin: 0;
    padding: 0 var(--sp-3);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface-muted);
  }
  .tbl-boms .bom-mats > summary::after { margin-left: auto; }
  .tbl-boms .bom-mats[open] > summary { border-radius: var(--radius) var(--radius) 0 0; }
  .tbl-boms .bom-mats[open] .bom-mats-lista {
    max-width: none;
    gap: var(--sp-2) var(--sp-4);
    margin: 0;
    padding: var(--sp-3);
    border: 1px solid var(--border);
    border-top: 0;
    border-radius: 0 0 var(--radius) var(--radius);
  }

  /* ---- Configuración (MEJORAS-UI CF1 y CF3) ----
     Usuarios y API keys en tarjetas compactas, como Operarios y Part Data, con
     el estado y el "⋯" arriba (T2). 8 entre renglones, no 4: el estado (22 px)
     es más alto que el renglón del nombre y con 4 quedaba pegado al segundo;
     en las llaves, el chip del alcance tocaba el nombre y la fecha. */
  .tbl.tbl-tarjetas:is(.tbl-usuarios, .tbl-llaves) tr {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--sp-2);
  }
  .tbl.tbl-tarjetas:is(.tbl-usuarios, .tbl-llaves) td[data-label]::before {
    display: inline;
    margin: 0;
    font: inherit;
    color: inherit;
  }
  /* Usuarios: el nombre; debajo, el rol y cuándo se creó. */
  .tbl.tbl-tarjetas.tbl-usuarios .t-titulo { flex: 1 1 100%; }
  .tbl.tbl-tarjetas.tbl-usuarios :is(.u-rol, .u-creado) { flex: none; font-size: 13px; color: var(--text-muted); }
  .tbl.tbl-tarjetas.tbl-usuarios td.u-rol[data-label]::before { content: none; }
  .tbl.tbl-tarjetas.tbl-usuarios td.u-creado[data-label]::before { content: "· Creado: "; }
  /* API keys: el nombre; el prefijo y el alcance; el último uso; la creación. */
  .tbl.tbl-tarjetas.tbl-llaves tr > td.t-titulo { display: contents; }
  .tbl.tbl-tarjetas.tbl-llaves .t-titulo .mat-name { order: 1; flex: 1 1 100%; padding-right: 152px; }
  .tbl.tbl-tarjetas.tbl-llaves .t-titulo .mat-sku { order: 2; margin-top: 0; font-size: 13px; }
  .tbl.tbl-tarjetas.tbl-llaves .k-alcance { order: 3; }
  .tbl.tbl-tarjetas.tbl-llaves td.k-alcance[data-label]::before { content: none; }
  .tbl.tbl-tarjetas.tbl-llaves .k-uso { order: 4; flex: 1 1 100%; }
  .tbl.tbl-tarjetas.tbl-llaves .k-creada { order: 5; }
  .tbl.tbl-tarjetas.tbl-llaves :is(.k-uso, .k-creada) { white-space: nowrap; font-size: 13px; color: var(--text-muted); }
  .tbl.tbl-tarjetas.tbl-llaves td.k-uso[data-label]::before { content: "Último uso: "; }
  .tbl.tbl-tarjetas.tbl-llaves td.k-creada[data-label]::before { content: "Creada: "; }
  /* Cambiar contraseña: el relleno de una tarjeta del teléfono, un campo
     debajo del otro y el botón a todo el ancho, con la nota a 12. */
  .cf-clave, .cf-tema, .cf-dispositivos { padding: var(--sp-4); }
  .cf-clave .form-grid { grid-template-columns: 1fr; }
  .cf-clave-pie { row-gap: var(--sp-3); }
  .cf-clave-pie .btn-secondary { flex: 1 1 100%; justify-content: center; }

  /* ---- Órdenes de compra de Inventario (MEJORAS-UI IN1 e IN2) ----
     El filtro va debajo del título, a todo el ancho. Cada orden es una tarjeta
     compacta, como en Órdenes: el nombre y el estado arriba; cantidad, fecha y
     proveedor en un renglón; "Recibir" y el "⋯" al pie, a 12 del texto (8 de
     separación + 4). 8 entre renglones: el estado es más alto que el renglón
     del nombre (DISENO §3.10). */
  .bloque-head .po-filtro { flex: 1 1 100%; }
  .inv-filtros > .inv-buscar, .inv-filtros > .po-filtro { flex: 1 1 100%; }
  .tbl.tbl-tarjetas.tbl-po tbody tr:not(:has(~ tr:not([hidden]))),
  .tbl.tbl-tarjetas.tbl-solicitudes tbody tr:not(:has(~ tr:not([hidden]))),
  .tbl.tbl-tarjetas.ce-tabla tbody tr:not(:has(~ tr:not([hidden]))) { border-bottom: 0; }
  .tbl.tbl-tarjetas.tbl-po tr {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--sp-2);
  }
  .tbl.tbl-tarjetas.tbl-po td[data-label]::before {
    display: inline;
    margin: 0;
    font: inherit;
    color: inherit;
  }
  /* El nombre deja lugar al estado de arriba a la derecha (#87-B): `td:first-child { padding: 0 }` le ganaba a los 112 de .t-titulo y el nombre largo se montaba sobre "En camino". */
  .tbl.tbl-tarjetas.tbl-po td.t-titulo { flex: 1 1 100%; padding-right: 112px; }
  .tbl.tbl-tarjetas.tbl-po :is(.po-cant, .po-fecha, .po-prov) { flex: none; font-size: 13px; color: var(--text-muted); }
  .tbl.tbl-tarjetas.tbl-po td.po-cant[data-label]::before { content: "Cant. "; }
  .tbl.tbl-tarjetas.tbl-po td.po-fecha[data-label]::before { content: "· Esperada "; }
  .tbl.tbl-tarjetas.tbl-po td.po-prov[data-label]::before { content: "· "; }
  .tbl.tbl-tarjetas.tbl-po td.po-prov-vacio { display: none; }
  .tbl.tbl-tarjetas.tbl-po .po-atraso { display: inline; font: inherit; }
  .tbl.tbl-tarjetas.tbl-po .po-atraso::before { content: " · "; color: var(--text-muted); }
  .tbl.tbl-tarjetas.tbl-po .po-llegaron { display: inline; font: inherit; color: inherit; }
  .tbl.tbl-tarjetas.tbl-po .po-llegaron::before { content: " · "; }
  .tbl.tbl-tarjetas.tbl-po .t-acciones { flex: 1 1 100%; margin-top: var(--sp-1); }
  .tbl.tbl-tarjetas.tbl-po .t-acciones .acciones-fila { display: flex; justify-content: flex-end; }

  /* Formularios largos con secciones (Órdenes O4, Part Data PD2): los títulos
     de sección, y sin el renglón vacío que cada campo reservaba para su error
     (vuelve si hay error). Cada formulario decide qué campos siguen de a dos:
     en Órdenes, las fechas; en Part Data, todos los pares. El título de
     sección queda a 16 del campo de arriba (4 de margen + 12 de relleno). */
  .modal-secciones { gap: var(--sp-3); }
  .modal-secciones .form-grid { gap: var(--sp-3); }
  .modal-secciones .field-error:empty { display: none; }
  .modal-secciones .form-sec {
    display: block;
    margin-top: var(--sp-1);
    padding-top: var(--sp-3);
    border-top: 1px solid var(--border-soft);
    font: var(--t-tabla-enc);
    letter-spacing: .04em;
    text-transform: uppercase;
    color: var(--text-muted);
  }
  .modal-secciones .form-sec:first-child { margin-top: 0; padding-top: 0; border-top: 0; }
  .modal-orden .form-grid-fechas,
  .modal-parte .form-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }

  /* Líneas del BOM (MEJORAS-UI C1): la parte a todo el ancho y, debajo, la
     cantidad con su rótulo y la papelera; una línea fina separa cada una. */
  .bom-lineas-rotulos { display: none; }
  .bom-lineas-lista { gap: 0; }
  .bom-linea {
    grid-template-columns: minmax(0, 1fr) 44px;
    grid-template-areas: "parte parte" "cant quitar";
    padding: 12px 0;
    border-top: 1px solid var(--border-soft);
  }
  .bom-linea:first-child { padding-top: 0; border-top: 0; }
  .bom-linea-parte { grid-area: parte; }
  .bom-linea-cant { grid-area: cant; flex-direction: row; flex-wrap: wrap; align-items: center; gap: 4px 12px; }
  .bom-linea-rotulo { display: inline; font: var(--t-etiqueta); color: var(--text-muted); }
  .bom-linea-cant .campo-unidad { width: 136px; }
  .bom-linea-cant .field-error { flex-basis: 100%; }
  .bom-linea-quitar { grid-area: quitar; align-self: center; }

  /* Contactos de una agencia (#93): el nombre con la papelera, y debajo el
     teléfono y el correo, cada uno con su rótulo; una línea fina entre uno y
     otro, como las líneas del BOM. */
  .lineas-rotulos { display: none; }
  .lineas-lista,
  .ag-contactos .lineas-lista { gap: 0; }
  .ag-contactos .linea {
    grid-template-columns: minmax(0, 1fr) 44px;
    grid-template-areas: "nombre quitar" "tel tel" "correo correo";
    gap: 12px 8px;
    padding: 12px 0;
    border-top: 1px solid var(--border-soft);
  }
  .ag-contactos .linea:has(.ag-contacto-cuenta) { grid-template-areas: "nombre quitar" "tel tel" "correo correo" "cuenta cuenta"; }
  .ag-contactos .linea:first-child { padding-top: 0; border-top: 0; }
  .linea-rotulo { display: block; font: var(--t-etiqueta); color: var(--text-muted); }
  .ag-c-nombre { grid-area: nombre; }
  .ag-c-tel { grid-area: tel; }
  .ag-c-correo { grid-area: correo; }
  .ag-contactos .linea-quitar { grid-area: quitar; align-self: end; }
  .ag-contacto-cuenta { grid-area: cuenta; }

  /* ---- los modales suben desde abajo ----
     Centrado, un formulario largo deja los botones de guardar a media pantalla
     y lejos del pulgar. Como hoja inferior, el pie queda donde se alcanza. */
  .modal-overlay { padding: 0; align-items: flex-end; }
  .modal {
    max-width: none;
    max-height: 92vh;
    max-height: 92dvh;
    border-radius: var(--radius-xl) var(--radius-xl) 0 0;
  }
  .modal-footer { padding-bottom: max(var(--sp-4), env(safe-area-inset-bottom)); }
  .modal-footer .btn-primary,
  .modal-footer .btn-secondary { flex: 1; justify-content: center; }
  /* Con tres botones o más, el pie va en columna, a todo el ancho, con la
     acción principal primero (MEJORAS-UI CO2): en fila, "Convertir en orden
     de compra" quedaba fuera de la pantalla. */
  .modal-footer:has(> :nth-child(3)) { flex-direction: column; align-items: stretch; gap: var(--sp-2); }
  .modal-footer:has(> :nth-child(3)) > * { flex: none; width: 100%; justify-content: center; }
  .modal-footer .modal-footer-mas { margin-right: 0; }
  .modal-footer .modal-footer-mas > .btn-secondary { width: 100%; justify-content: center; }
  /* 3.8: en columna, la acción principal primero. */
  .modal-footer:has(> :is(.modal-footer-mas, .modal-footer-izq)) > .btn-primary { order: -1; }
  .modal-footer .modal-footer-izq { margin-right: 0; }

  .solo-tel { display: inline; }

  /* ---- Solicitudes (MEJORAS-UI SO1) ----
     El filtro y el buscador, uno por renglón a todo el ancho (también en
     Órdenes); el aviso de arriba, con el botón debajo. Cada solicitud, una
     tarjeta compacta: el folio y el estado arriba; lo que falta (o la orden, o
     la visita); el cliente y el vehículo; la agencia, el tipo, el BOM y cuándo
     llegó. 8 entre renglones: el estado es más alto que el renglón del folio. */
  .filtros-solicitudes > .buscador, .filtros-ordenes > .buscador { flex: 1 1 100%; max-width: none; margin-left: 0; }
  .filtros-solicitudes > .po-filtro, .filtros-ordenes > .po-filtro { flex: 1 1 100%; }
  .tbl.tbl-tarjetas.tbl-solicitudes tr { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--sp-2); }
  .tbl.tbl-tarjetas.tbl-solicitudes td[data-label]::before { display: inline; margin: 0; font: inherit; color: inherit; content: none; }
  .tbl.tbl-tarjetas.tbl-solicitudes tr > td.t-titulo { display: contents; }
  .tbl.tbl-tarjetas.tbl-solicitudes .t-titulo .fila-titulo { order: 1; flex: 1 1 100%; padding-right: 168px; font-size: 14px; }
  .tbl.tbl-tarjetas.tbl-solicitudes td.so-extra { display: block; order: 2; flex: 1 1 100%; font-size: 13px; color: var(--text); }
  .tbl.tbl-tarjetas.tbl-solicitudes td.so-extra:empty { display: none; }
  .tbl.tbl-tarjetas.tbl-solicitudes td.so-cliente { order: 3; flex: 1 1 100%; }
  .tbl.tbl-tarjetas.tbl-solicitudes .so-cliente :is(.mat-name, .mat-sku) { display: inline; margin: 0; }
  .tbl.tbl-tarjetas.tbl-solicitudes .so-cliente .mat-name { font-size: 14px; font-weight: 400; }
  .tbl.tbl-tarjetas.tbl-solicitudes .so-cliente .mat-sku { font-size: 13px; }
  .tbl.tbl-tarjetas.tbl-solicitudes .so-cliente .mat-sku:not(:empty)::before { content: " · "; font-family: var(--font-sans); }
  .tbl.tbl-tarjetas.tbl-solicitudes .t-titulo .mat-sku { order: 4; margin-top: 0; font-family: var(--font-sans); font-size: 13px; font-weight: 400; color: var(--text-muted); }
  .tbl.tbl-tarjetas.tbl-solicitudes td.so-tipo { order: 5; }
  .tbl.tbl-tarjetas.tbl-solicitudes td.so-bom { order: 6; }
  .tbl.tbl-tarjetas.tbl-solicitudes td.so-recibida { order: 7; }
  .tbl.tbl-tarjetas.tbl-solicitudes :is(td.so-tipo, td.so-bom, td.so-recibida) { flex: none; font-size: 13px; color: var(--text-muted); }
  .tbl.tbl-tarjetas.tbl-solicitudes td.so-tipo::before { content: "· "; }
  .tbl.tbl-tarjetas.tbl-solicitudes td.so-bom::before { content: "· BOM "; }
  .tbl.tbl-tarjetas.tbl-solicitudes td.so-recibida::before { content: "· "; }
  .tbl.tbl-tarjetas.tbl-solicitudes td.so-vacio,
  .tbl.tbl-tarjetas.tbl-solicitudes .t-estado .so-extra-tabla { display: none; }

  /* ---- Cotizaciones (MEJORAS-UI CO1) ----
     El número y el estado arriba; el suplidor; líneas, total y envío. */
  .tbl.tbl-tarjetas.tbl-rfqs tr { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--sp-2); }
  .tbl.tbl-tarjetas.tbl-rfqs td[data-label]::before { display: inline; margin: 0; font: inherit; color: inherit; content: none; }
  .tbl.tbl-tarjetas.tbl-rfqs .t-titulo { flex: 1 1 100%; padding-right: 192px; }
  .tbl.tbl-tarjetas.tbl-rfqs .t-titulo .fila-titulo { font-size: 14px; }
  .tbl.tbl-tarjetas.tbl-rfqs td.rf-sup { flex: 1 1 100%; }
  .tbl.tbl-tarjetas.tbl-rfqs :is(td.rf-lineas, td.rf-total, td.rf-enviada) { flex: none; font-size: 13px; color: var(--text-muted); }
  .tbl.tbl-tarjetas.tbl-rfqs td.rf-total::before { content: "· "; }
  .tbl.tbl-tarjetas.tbl-rfqs td.rf-enviada::before { content: "· Enviada "; }
  .tbl.tbl-tarjetas.tbl-rfqs td.rf-vacio { display: none; }

  /* ---- Suplidores (MEJORAS-UI CO3) ----
     El nombre, el estado y el "⋯" arriba (T2); contacto, teléfono y partes;
     el correo, que se parte donde haga falta. */
  .tbl.tbl-tarjetas.tbl-suplidores tr { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--sp-2); }
  .tbl.tbl-tarjetas.tbl-suplidores td[data-label]::before { display: inline; margin: 0; font: inherit; color: inherit; content: none; }
  .tbl.tbl-tarjetas.tbl-suplidores .t-titulo { flex: 1 1 100%; }
  .tbl.tbl-tarjetas.tbl-suplidores td.su-contacto { order: 2; }
  .tbl.tbl-tarjetas.tbl-suplidores td.su-tel { order: 3; }
  .tbl.tbl-tarjetas.tbl-suplidores td.su-partes { order: 4; }
  .tbl.tbl-tarjetas.tbl-suplidores td.su-correo { order: 5; flex: 1 1 100%; overflow-wrap: anywhere; }
  .tbl.tbl-tarjetas.tbl-suplidores :is(td.su-contacto, td.su-tel, td.su-partes, td.su-correo) { font-size: 13px; color: var(--text-muted); }
  .tbl.tbl-tarjetas.tbl-suplidores :is(td.su-contacto, td.su-tel, td.su-partes) { flex: none; }
  .tbl.tbl-tarjetas.tbl-suplidores :is(td.su-tel, td.su-partes)::before { content: "· "; }
  .tbl.tbl-tarjetas.tbl-suplidores td.su-vacio { display: none; }

  /* ---- Detalle de una cotización (MEJORAS-UI CO2) ----
     Cada línea, un renglón: la parte y, debajo, cantidad, precio cotizado,
     contraoferta y entrega. Lo que falta no ocupa lugar. */
  .tbl.tbl-lineas thead { display: none; }
  .tbl.tbl-lineas,
  .tbl.tbl-lineas tbody { display: block; width: 100%; }
  .tbl.tbl-lineas tr { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--sp-1) var(--sp-2); padding: var(--sp-3); border-bottom: 1px solid var(--border-soft); }
  .tbl.tbl-lineas tr:last-child { border-bottom: 0; }
  .tbl.tbl-lineas td,
  .tbl.tbl-lineas td:first-child { display: block; position: static; min-width: 0; padding: 0; border: 0; background: none; box-shadow: none; white-space: normal; text-align: left; }
  .tbl.tbl-lineas tbody tr:hover td { background: none; }
  .tbl.tbl-lineas td.rl-parte { flex: 1 1 100%; }
  .tbl.tbl-lineas .rl-parte :is(.mat-name, .mat-sku) { display: inline; margin: 0; }
  .tbl.tbl-lineas .rl-parte .mat-sku::before { content: " · "; font-family: var(--font-sans); }
  .tbl.tbl-lineas :is(td.rl-cant, td.rl-precio, td.rl-contra, td.rl-lead) { flex: none; font-size: 13px; color: var(--text-muted); }
  .tbl.tbl-lineas td.rl-precio::before { content: "· Cotizado "; }
  .tbl.tbl-lineas td.rl-contra::before { content: "· Contraoferta "; }
  .tbl.tbl-lineas td.rl-lead::before { content: "· Entrega "; }
  .tbl.tbl-lineas td.rl-vacio { display: none; }
  /* En los formularios (capturar la respuesta, la contraoferta), cada línea es
     una tarjeta con el nombre de cada dato y de cada campo: la parte arriba; la
     cantidad (y lo cotizado); los campos lado a lado. */
  .tbl.tbl-lineas-campos tr { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp-3); align-items: start; }
  .tbl.tbl-lineas-campos td.rl-parte,
  .tbl.tbl-lineas-campos td.rl-cant,
  .tbl.tbl-lineas-campos td.rl-campo-ancho { grid-column: 1 / -1; }
  .tbl.tbl-lineas-campos tr:has(> td.rl-precio) > td.rl-cant { grid-column: auto; }
  .tbl.tbl-lineas-campos td[data-label]::before { content: attr(data-label); display: block; margin-bottom: var(--sp-1); font: var(--t-leyenda); color: var(--text-muted); }
  .tbl.tbl-lineas-campos td.rl-campo[data-label]::before { margin-bottom: var(--sp-2); }
  .tbl.tbl-lineas-campos :is(td.rl-cant, td.rl-precio) { font-size: 14px; color: var(--text); }
  .tbl.tbl-lineas-campos .field-error:empty { display: none; }

  /* ---- el calendario, en lista ----
     Siete columnas en 375px dan celdas de 44px: no cabe ni el nombre de una
     ruta. Un día por renglón sí se lee, y el día de la semana lo pone el
     atributo `data-dow` que escribe public/js/calendario.js. */
  .cal-grid { grid-template-columns: 1fr; gap: 4px; }
  .cal-dow { display: none; }
  .cal-empty { display: none; }
  .cal-cell {
    min-height: 0;
    display: grid;
    grid-template-columns: 62px auto 1fr;
    align-items: center;
    gap: 10px;
    padding: 10px 12px;
  }
  .cal-num { font-size: 13px; font-weight: 600; color: var(--text); }
  .cal-num::before { content: attr(data-dow) " "; color: var(--text-muted); font-weight: 400; }
  .cal-count { margin-top: 0; font-size: 16px; }
  .cal-rutas { min-width: 0; }
  .cal-ruta { font-size: 12px; }
  .cal-flag { position: static; justify-self: end; }
  .cal-sunday { opacity: .6; }

  /* ---- los avisos van arriba, no abajo ----
     Abajo a la derecha es una convención de escritorio. En un teléfono el
     borde de abajo lo ocupan el composer del asistente y la barra de gestos
     del sistema: el aviso caía encima de los dos y no se leía. */
  .toast-container {
    top: 12px;
    bottom: auto;
    left: 12px;
    right: 12px;
    max-width: none;
  }
  .toast { transform: translateY(-6px); }
  .toast.show { transform: translateY(0); }

  /* ---- login ---- */
  .login-view { padding: 32px 16px; gap: 20px; }
  .login-card { padding: 32px 20px 24px; gap: 20px; }
}

/* ---- Centro de control en el teléfono ----
   Sin lienzo: los departamentos van en acordeón, los casos de un proceso abren
   el modal que sube desde abajo y "Seguir una orden" muestra el recorrido. */
@media (max-width: 560px) {
  .cc-mapa { display: none; }
  .cc-tel { display: flex; }
  .cc-atn li { grid-template-columns: 12px minmax(0, 1fr); gap: var(--sp-1) var(--sp-3); align-items: start; }
  .cc-atn li > .cc-sv { margin-top: var(--sp-1); }
  .cc-atn-t, .cc-atn-a { grid-column: 2; }
  .cc-atn-a { justify-self: start; text-align: left; }
  /* En el teléfono un botón deja 12 con el texto de arriba (DISENO §3.10). */
  .cc-pr-l .btn-primary { margin-top: var(--sp-3); }
}

/* ---- Bodega en la tablet (public/js/bodega.js, DISENO §3.17) ----
   La usa el bodeguero (2026-09-25, preview aprobado por Fabiana): una pregunta
   por pantalla, el título grande arriba, opciones que se tocan enteras y nada
   escondido en menús. Todo lo que se toca mide 64 o más. */
/* La pantalla vive en .screen-body, con el menú y la barra de la app. Entre sus
   bloques, 24 (más aire que 16: se toca con el dedo). Crece hasta el fondo de
   la pantalla: el pie queda abajo aunque haya poco contenido. */
.bt-raiz { flex: 1 0 auto; display: flex; flex-direction: column; gap: var(--sp-5); }
/* Atrás e Inicio, en la barra de arriba a la derecha: 56 de alto y letra de 16. */
.bt-btn-barra { min-height: 56px; padding: 0 var(--sp-4); font: var(--t-seccion); gap: var(--sp-2); }
.bt-btn-barra svg { width: 22px; height: 22px; }
.topbar-actions:has(.bt-btn-barra) { display: flex; gap: var(--sp-2); }

.bt-pregunta { display: flex; flex-direction: column; gap: var(--sp-1); }
.bt-pregunta h1 { font: var(--t-kpi); letter-spacing: -.01em; }
.bt-pregunta p { font: var(--t-seccion); font-weight: 400; color: var(--text-muted); }

/* Opciones grandes del inicio: el ícono en un círculo de 88 y el nombre en 22. */
.bt-acciones { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--sp-4); }
.bt-accion { position: relative; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--sp-3); min-height: 360px; padding: var(--sp-5) var(--sp-4); text-align: center; background: var(--surface); border: 2px solid var(--border); border-radius: var(--radius-lg); box-shadow: var(--elev-1); color: var(--text); cursor: pointer; transition: border-color var(--dur-1) var(--ease), background-color var(--dur-1) var(--ease), transform var(--dur-1) var(--ease); }
.bt-accion:hover { border-color: var(--border-input); background: var(--surface-muted); }
.bt-accion:active { transform: var(--press); }
.bt-accion:focus-visible, .bt-tarjeta:focus-visible, .bt-mat:focus-visible { outline: 3px solid var(--foco); outline-offset: 3px; }
.bt-accion-icono { flex: none; display: grid; place-items: center; width: 112px; height: 112px; border-radius: var(--radius-full); }
.bt-accion-icono svg { width: 56px; height: 56px; }
.bt-resumen-quien .bt-accion-icono { width: 64px; height: 64px; }
.bt-resumen-quien .bt-accion-icono svg { width: 32px; height: 32px; }
.bt-ic-sale { background: var(--blue-bg); color: var(--blue-text); }
.bt-ic-entra { background: var(--green-bg); color: var(--green-text); }
.bt-ic-compra { background: var(--amber-bg); color: var(--amber-text); }
.bt-ic-retiro { background: var(--violeta-bg); color: var(--violeta-text); }
.bt-ic-sinorden { background: var(--turquesa-bg); color: var(--turquesa-text); }
.bt-accion-texto { display: flex; flex-direction: column; gap: var(--sp-2); }
.bt-accion-titulo { font: var(--t-kpi); letter-spacing: -.01em; }
.bt-accion-sub { font: var(--t-seccion); font-weight: 400; color: var(--text-muted); }
/* El número de lo que espera: la misma píldora roja de los avisos (3.1). */
.bt-globo { position: absolute; top: var(--sp-3); right: var(--sp-3); min-width: 36px; height: 36px; padding: 0 var(--sp-2); display: grid; place-items: center; border-radius: var(--radius-full); background: var(--red-strong); color: var(--on-accent); font: var(--t-seccion); font-variant-numeric: tabular-nums; }

/* Tarjetas que se tocan enteras: operarios, órdenes, avisos, compras. */
.bt-grilla { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--sp-4); }
.bt-lista { display: flex; flex-direction: column; gap: var(--sp-3); }
.bt-tarjeta { display: flex; align-items: center; gap: var(--sp-4); min-height: 96px; padding: var(--sp-4); text-align: left; background: var(--surface); border: 2px solid var(--border); border-radius: var(--radius-lg); box-shadow: var(--elev-1); color: var(--text); cursor: pointer; transition: border-color var(--dur-1) var(--ease), background-color var(--dur-1) var(--ease); }
.bt-tarjeta:hover { border-color: var(--border-input); background: var(--surface-muted); }
.bt-tarjeta-texto { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: var(--sp-1); }
.bt-tarjeta-titulo { font: var(--t-titulo); letter-spacing: -.01em; overflow-wrap: anywhere; }
.bt-tarjeta-sub { font: var(--t-seccion); font-weight: 400; color: var(--text-muted); }
.bt-tarjeta > svg { flex: none; width: 28px; height: 28px; color: var(--text-faint); }
.bt-tarjeta-otra { border-style: dashed; box-shadow: none; background: var(--surface-muted); }
.bt-iniciales { flex: none; display: grid; place-items: center; width: 64px; height: 64px; border-radius: var(--radius-full); background: var(--text); color: var(--surface); font: var(--t-titulo); }
.bt-persona { flex-direction: column; justify-content: center; min-height: 176px; text-align: center; }
.bt-persona .bt-iniciales { width: 80px; height: 80px; }

/* Un aviso de lo que sobró, o una compra que llega: el dato arriba, los botones abajo. */
.bt-caso { display: flex; flex-direction: column; gap: var(--sp-3); padding: var(--sp-4); background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg); box-shadow: var(--elev-1); }
.bt-caso-cabeza { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2) var(--sp-3); font: var(--t-seccion); font-weight: 400; color: var(--text-muted); }
.bt-caso-quien { font: var(--t-seccion); color: var(--text); }
.bt-caso-cabeza .bt-tarjeta-texto { flex: 1 1 240px; }
.bt-caso-que { font: var(--t-titulo); letter-spacing: -.01em; }
.bt-caso-botones { display: flex; flex-wrap: wrap; gap: var(--sp-3); }
.bt-caso-botones > button { flex: 1 1 200px; }
.bt-caso.bt-hecho { background: var(--surface-muted); box-shadow: none; }
/* Lo que vuelve de una instalación que no se pudo completar: la lista entera. */
.bt-caso-lista { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--sp-2); }
.bt-caso-lista li { display: flex; justify-content: space-between; gap: var(--sp-4); font: var(--t-seccion); font-weight: 400; }
.bt-caso-lista b { font-weight: 600; font-variant-numeric: tabular-nums; white-space: nowrap; }

/* Números de serie en Inventario (2026-09-26): el resumen por estado, el modo, los filtros y el recorrido de una serie. */
.serie-resumen { display: flex; flex-wrap: wrap; gap: var(--sp-3) var(--sp-4); align-items: center; margin-bottom: var(--sp-3); font: var(--t-tabla); }
.serie-resumen > span { display: inline-flex; align-items: center; gap: var(--sp-2); }
.serie-modo { display: flex; flex-direction: column; gap: var(--sp-1); margin-bottom: var(--sp-4); padding-bottom: var(--sp-3); border-bottom: 1px solid var(--border-soft); }
.serie-modo label { font: var(--t-seccion); color: var(--text); }
.serie-filtros { display: flex; flex-wrap: wrap; gap: var(--sp-2); margin-bottom: var(--sp-3); }
.serie-filtros input[type="search"] { flex: 1 1 160px; min-width: 0; }
.serie-cabecera { display: flex; align-items: center; gap: var(--sp-3); margin: var(--sp-3) 0 var(--sp-4); }
.serie-numero { font: var(--t-kpi); font-family: var(--font-mono); letter-spacing: .03em; }
.serie-hilo { list-style: none; margin: 0; padding: 0 0 0 var(--sp-4); border-left: 2px solid var(--border); display: flex; flex-direction: column; gap: var(--sp-4); }
.serie-hilo-item { position: relative; font: var(--t-parrafo); }
.serie-hilo-item::before { content: ''; position: absolute; left: calc(-1 * var(--sp-4) - 6px); top: 6px; width: 10px; height: 10px; border-radius: 50%; background: var(--accent-strong); }
.serie-hilo-cabeza { display: flex; justify-content: space-between; gap: var(--sp-3); flex-wrap: wrap; }
.serie-hilo-aviso { color: var(--amber-text); }
.inv-con-numero { font-size: 12px; }
/* Lo que ya llegó de una compra (#87, 5B8E p3): debajo de la cantidad; en la tarjeta del teléfono, en el mismo renglón. */
.po-llegaron { display: block; font-size: 12px; color: var(--text-faint); }
.campo-mono { font-family: var(--font-mono); letter-spacing: .04em; text-transform: uppercase; }
.campo-mono::placeholder { text-transform: none; letter-spacing: 0; font-family: var(--font-sans); }
.conf-serie-aviso { color: var(--amber-text); }

/* Números de serie: un campo por unidad, de 56 y letra de 16, de a dos. */
.bt-series { display: flex; flex-direction: column; gap: var(--sp-3); }
.bt-series-grilla { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp-2) var(--sp-4); }
.bt-serie label { font: var(--t-seccion); color: var(--text); }
.bt-serie input { min-height: 56px; font: var(--t-seccion); font-family: var(--font-mono); letter-spacing: .04em; text-transform: uppercase; }
.bt-serie input::placeholder { text-transform: none; letter-spacing: 0; font-family: var(--font-sans); }
.bt-series-resumen { display: block; margin-top: var(--sp-1); font: var(--t-tabla); font-family: var(--font-mono); color: var(--text-muted); overflow-wrap: anywhere; }
/* Lo que el servidor dijo del número al escribirlo (2026-09-26): si sirve, en verde; si no, .field-error. */
.bt-serie .field-ok { font-size: 12px; color: var(--accent-dark); min-height: 14px; }
.bt-serie .has-error ~ .field-ok, .bt-serie.has-error .field-ok { display: none; }
/* Lo que pasa pocas veces, bajo las tres acciones (#116): Retirar material y
   Recibir sin orden de a dos, con el ícono de su familia en un círculo de 64;
   Anotar números de serie a todo el ancho debajo. 32 de las acciones (con los
   24 de la pantalla; re-prueba del 29/09, #77) y 16 entre ellas, como las acciones. */
.bt-otras { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp-4); margin-top: var(--sp-2); }
.bt-otra .bt-accion-icono { width: 64px; height: 64px; }
.bt-otra .bt-accion-icono svg { width: 32px; height: 32px; }
.bt-anotar { grid-column: 1 / -1; }
.bt-mats-sueltos .bt-mat:disabled { opacity: .55; cursor: default; }
.bt-pie-nota { flex: 1 1 200px; align-self: center; font: var(--t-seccion); font-weight: 400; color: var(--text-muted); }


/* Botones de la tablet: 64 de alto, letra de 16 y el ícono de 24. */
.bt-pantalla .bt-btn { min-height: 64px; padding: 0 var(--sp-5); font: var(--t-seccion); gap: var(--sp-2); justify-content: center; }
.bt-pantalla .bt-btn svg { width: 24px; height: 24px; }
.bt-ancho { width: 100%; }

/* Materiales: los tipos arriba (una fila de botones), las tarjetas debajo. */
/* Los tipos arriba, a todo el ancho, y a 12 las tarjetas y "Se lleva", que
   arrancan a la misma altura (Fabiana, 2026-09-25: la caja de la derecha
   quedaba más arriba que las tarjetas). */
.bt-elegir { display: flex; flex-direction: column; gap: var(--sp-3); }
.bt-armar { display: grid; grid-template-columns: minmax(0, 1fr) 320px; gap: var(--sp-4); align-items: start; }
.bt-tipos { display: flex; flex-wrap: wrap; gap: var(--sp-2); }
.bt-tipos button { min-height: 56px; padding: 0 var(--sp-4); border: 2px solid var(--border-control); border-radius: var(--radius-full); background: var(--surface); font: var(--t-seccion); font-weight: 500; color: var(--text); cursor: pointer; }
.bt-tipos button[aria-pressed="true"] { background: var(--accent-activo); border-color: var(--accent-activo); color: var(--on-accent); font-weight: 600; }
.bt-mats { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--sp-3); }
.bt-mat { position: relative; display: flex; flex-direction: column; align-items: center; gap: var(--sp-2); min-height: 152px; padding: var(--sp-4) var(--sp-3); text-align: center; background: var(--surface); border: 2px solid var(--border); border-radius: var(--radius-lg); box-shadow: var(--elev-1); color: var(--text); cursor: pointer; }
.bt-mat:hover { border-color: var(--border-input); }
.bt-mat.bt-elegido { border-color: var(--accent-dark); background: var(--green-bg); }
.bt-mat-icono { display: grid; place-items: center; width: 56px; height: 56px; border-radius: var(--radius-full); background: var(--surface-muted); color: var(--text-muted); }
.bt-mat-icono svg { width: 30px; height: 30px; }
.bt-mat-nombre { font: var(--t-seccion); }
.bt-mat-hay { font: var(--t-tabla); color: var(--text-muted); }
.bt-mat .bt-globo { top: var(--sp-2); right: var(--sp-2); background: var(--accent-strong); }

/* "Lo que entregás": la lista de la derecha, con − y + de 64. */
.bt-pedido { position: sticky; top: 0; display: flex; flex-direction: column; gap: var(--sp-3); padding: var(--sp-4); background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg); box-shadow: var(--elev-1); }
.bt-pedido h2 { font: var(--t-titulo); }
.bt-pedido-vacio { font: var(--t-seccion); font-weight: 400; color: var(--text-faint); }
.bt-lineas { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--sp-3); }
.bt-linea { display: flex; flex-direction: column; gap: var(--sp-2); padding-top: var(--sp-3); border-top: 1px solid var(--border-soft); }
.bt-linea:first-child { padding-top: 0; border-top: 0; }
.bt-linea-nombre { font: var(--t-seccion); }
.bt-linea-pide { font: var(--t-tabla); color: var(--text-muted); }
.bt-pasos { display: grid; grid-template-columns: 64px minmax(0, 1fr) 64px; gap: var(--sp-2); align-items: stretch; }
.bt-pasos > button { min-height: 64px; padding: 0; justify-content: center; }
.bt-pasos > button svg { width: 24px; height: 24px; }
/* La cantidad se toca con − y + o se escribe: el recuadro es el campo. */
.bt-cant { display: flex; align-items: center; justify-content: center; gap: var(--sp-1); min-height: 64px; padding: 0 var(--sp-2); border: 1px solid var(--border-input); border-radius: var(--radius); background: var(--surface); }
.bt-cant:focus-within { border-color: var(--accent-strong); box-shadow: var(--foco-halo); }
.bt-cant input { width: 100%; min-width: 0; padding: 0; border: 0; background: none; box-shadow: none; text-align: center; font: var(--t-kpi); font-variant-numeric: tabular-nums; color: var(--text); appearance: textfield; -moz-appearance: textfield; }
.bt-cant input:focus { outline: none; box-shadow: none; }
.bt-cant input::-webkit-outer-spin-button, .bt-cant input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.bt-cant small { flex: none; font: var(--t-tabla); color: var(--text-muted); }
/* Una parte con número de serie va en unidades enteras (#87, 965F p2): el error, debajo de la cantidad; sin error no ocupa lugar. */
.bt-cant-error:empty { display: none; }

/* La lista de una orden: el material a todo el ancho, dos por renglón. */
.bt-orden-lineas { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp-3); }
.bt-orden-linea { display: flex; flex-direction: column; gap: var(--sp-2); padding: var(--sp-4); background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg); }
.bt-orden-linea .bt-linea-cabeza { display: flex; align-items: center; gap: var(--sp-3); }

/* Abajo, siempre a la vista: la acción que sigue. */
.bt-pie { position: sticky; bottom: calc(-1 * var(--sp-5)); z-index: 1; margin: auto calc(-1 * var(--sp-6)) calc(-1 * var(--sp-5)); display: flex; flex-wrap: wrap; gap: var(--sp-3); padding: var(--sp-4) var(--sp-6); background: var(--surface); border-top: 1px solid var(--border); }
@media (max-width: 900px) { .bt-pie { bottom: calc(-1 * var(--sp-4)); margin: auto calc(-1 * var(--sp-4)) calc(-1 * var(--sp-4)); padding: var(--sp-3) var(--sp-4); } }
@media (max-width: 560px) { .bt-pie { margin-inline: calc(-1 * var(--sp-3)); padding-inline: var(--sp-3); } }
.bt-pie > .btn-primary { flex: 2 1 280px; }
.bt-pie > .btn-secondary { flex: 1 1 200px; }

/* Escribir un dato (#116): quién, dónde, por qué y el costo. El campo mide 64,
   como los botones, con letra de 16; el porqué, cuatro renglones. La etiqueta
   va oculta: la pregunta de la pantalla la dice (la del costo es el material). */
.bt-campo { max-width: 640px; }
.bt-campo input, .bt-campo textarea { min-height: 64px; padding: var(--sp-3) var(--sp-4); font: var(--t-seccion); font-weight: 400; }
.bt-campo textarea { min-height: 128px; }
.bt-campo label { font: var(--t-seccion); color: var(--text); }
.bt-iniciales-otra { background: var(--surface); color: var(--text-muted); }
.bt-iniciales-otra svg { width: 32px; height: 32px; }
.bt-costos { display: flex; flex-direction: column; gap: var(--sp-4); }
/* Sin error ni "Son …" no ocupan lugar: la etiqueta, a 8 del campo, y el campo siguiente, a 16. */
.bt-campo .field-error:empty, .bt-campo .text-faint:empty { display: none; }
/* ¿Está bien? y Listo */
.bt-resumen { display: flex; flex-direction: column; gap: var(--sp-3); padding: var(--sp-5); background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg); }
.bt-resumen-quien { display: flex; align-items: center; gap: var(--sp-4); padding-bottom: var(--sp-3); border-bottom: 1px solid var(--border-soft); }
.bt-resumen ul { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--sp-2); }
.bt-resumen li { display: flex; justify-content: space-between; gap: var(--sp-4); font: var(--t-titulo); font-weight: 400; }
.bt-resumen li b { font-weight: 600; font-variant-numeric: tabular-nums; white-space: nowrap; }
/* Lo que costó un material (compra sin orden) y el para qué del retiro (#116). */
.bt-resumen-dato { display: block; margin-top: var(--sp-1); font: var(--t-tabla); color: var(--text-muted); }
.bt-resumen-motivo { display: flex; flex-direction: column; gap: var(--sp-1); padding-top: var(--sp-3); border-top: 1px solid var(--border-soft); font: var(--t-seccion); font-weight: 400; overflow-wrap: anywhere; }
.bt-resumen-motivo > span { font: var(--t-etiqueta); color: var(--text-muted); }
.bt-listo { flex: 1 1 auto; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--sp-4); text-align: center; padding: var(--sp-6) var(--sp-5); }
.bt-listo-check { display: grid; place-items: center; width: 120px; height: 120px; border-radius: var(--radius-full); background: var(--accent-strong); color: var(--on-accent); }
.bt-listo-check svg { width: 64px; height: 64px; }
.bt-listo h1 { font: var(--t-kpi); }
.bt-listo p { font: var(--t-seccion); font-weight: 400; color: var(--text-muted); max-width: 520px; }
.bt-cuenta-atras { font: var(--t-tabla); color: var(--text-faint); }


/* Tablet vertical (hasta 900, con el menú en ☰): las acciones en fila y los operarios de a dos. */
@media (max-width: 900px) {
  .bt-acciones { grid-template-columns: 1fr; }
  .bt-accion { flex-direction: row; justify-content: flex-start; gap: var(--sp-5); min-height: 176px; padding: var(--sp-5); text-align: left; }
  .bt-grilla { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .bt-armar { grid-template-columns: 1fr; }
  .bt-pedido { position: static; }
}
/* Teléfono (hasta 560): una columna. */
@media (max-width: 560px) {
  .bt-raiz { gap: var(--sp-4); }
  .bt-pregunta h1 { font: var(--t-titulo); }
  .bt-acciones, .bt-grilla, .bt-orden-lineas { grid-template-columns: 1fr; }
  .bt-accion { min-height: 112px; gap: var(--sp-4); padding: var(--sp-4); }
  .bt-accion-titulo { font: var(--t-titulo); }
  .bt-accion-icono { width: 64px; height: 64px; }
  .bt-accion-icono svg { width: 32px; height: 32px; }
  .bt-mats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .bt-persona { flex-direction: row; min-height: 96px; text-align: left; }
  .bt-persona .bt-iniciales { width: 64px; height: 64px; }
  .bt-resumen { padding: var(--sp-4); }
  .bt-resumen li { font: var(--t-seccion); font-weight: 400; }
  .bt-btn-barra { min-height: 48px; padding: 0 var(--sp-3); }
  .bt-series-grilla { grid-template-columns: 1fr; }
  .bt-otras { grid-template-columns: 1fr; }
}

@media (prefers-reduced-motion: reduce) { .bt-accion, .bt-tarjeta { transition: none; } }


/* ---- Operario: Inicio y Mi ruta (public/js/operario.js) ----
   Las pantallas del rol operario (2026-09-13), aprobadas en preview por
   Fabiana. Pensadas para el teléfono y para personas que lo usan poco: pocas
   opciones, grandes y con su nombre. En la computadora no se estiran a todo el
   ancho (640, como un modal ancho). */

/* Inicio: botones grandes a cada cosa que puede hacer. */
.op-inicio { max-width: 640px; display: flex; flex-direction: column; gap: var(--sp-4); }
.op-saludo { display: flex; flex-direction: column; gap: var(--sp-1); }
.op-saludo-sub { font: var(--t-parrafo); color: var(--text-muted); }
.op-accesos { display: flex; flex-direction: column; gap: var(--sp-4); }

/* Cada opción es una tarjeta que se toca entera: el ícono en un círculo de 64,
   el nombre con la letra del título de pantalla (22) y la flecha a la derecha. */
.op-acceso {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  padding: var(--sp-4);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--elev-1);
  color: var(--text);
  transition: background-color var(--dur-1) var(--ease), border-color var(--dur-1) var(--ease), transform var(--dur-1) var(--ease);
}
a.op-acceso:hover { background: var(--surface-muted); border-color: var(--border-input); color: var(--text); text-decoration: none; }
a.op-acceso:active { transform: var(--press); }
.op-acceso-icono {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 64px;
  height: 64px;
  border-radius: var(--radius-full);
  background: var(--green-bg);
  color: var(--accent-dark);
}
.op-acceso-texto { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: var(--sp-1); }
.op-acceso-cabeza { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2); }
.op-acceso-titulo { font: var(--t-titulo); letter-spacing: -.01em; }
.op-acceso-sub { font: var(--t-parrafo); color: var(--text-muted); }
.op-acceso-flecha { flex: none; display: inline-flex; color: var(--text-faint); }

/* Viáticos: la foto de la factura (js/mis-viaticos.js y js/viaticos.js). Lo
   único nuevo del módulo (2026-09-15, aprobado en preview por Fabiana): la foto
   entera, sin recortarla, sobre el gris de las superficies, con el relleno
   mínimo de una caja (12, DISENO §3.10) y el radio de las tarjetas. En los pasos
   de "Nuevo viático", más baja, para que se vea lo que sigue. */
.foto-factura {
  display: block;
  width: 100%;
  max-height: 320px;
  object-fit: contain;
  padding: var(--sp-3);
  background: var(--surface-muted);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
}
.foto-factura-chica { max-height: 160px; }
/* Las fotos del cargador en el detalle de la orden (2026-09-25): de a cuatro
   en el modal (de a dos en el teléfono), recortadas en 4:3; se tocan para
   verlas en grande. */
.conf-fotos { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--sp-2); }
.conf-foto { display: block; border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; background: var(--surface-muted); }
.conf-foto img { display: block; width: 100%; aspect-ratio: 4 / 3; object-fit: cover; }
@media (max-width: 560px) { .conf-fotos { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

/* Volver al inicio: el "Volver a…" del Centro de control (.cc-volver) sin
   achicar la letra, un botón fantasma con color de enlace alineado con el texto. */
.op-volver { align-self: flex-start; margin-left: calc(var(--sp-3) * -1); color: var(--accent-dark); }
.op-volver:hover { color: var(--accent-dark); text-decoration: none; }

/* Mi ruta: una tarjeta por parada pendiente, en orden de visita, 16 entre ellas. */
.op-ruta { max-width: 640px; }
.op-paradas {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
}
/* La caja de .card con el relleno de una tarjeta del teléfono (16) y 12 entre bloques. */
.op-parada {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  padding: var(--sp-4);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--elev-1);
}
/* Cabeza: el número de la parada y el nombre del cliente (sin número de orden
   ni estado: el operario no los necesita, diseño C). */
.op-parada-cabeza { display: flex; align-items: flex-start; gap: var(--sp-3); }
.op-num {
  flex: none;
  align-self: center;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border-radius: var(--radius-full);
  background: var(--text);
  color: var(--surface);
  font: var(--t-seccion);
  font-variant-numeric: tabular-nums;
}
.op-parada-titulo { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: var(--sp-1); }
.op-cliente { font: var(--t-seccion); color: var(--text); overflow-wrap: anywhere; }
/* Los datos de la parada: un ícono y el texto, 8 entre renglones. */
.op-datos {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}
.op-dato { display: flex; align-items: flex-start; gap: var(--sp-2); min-width: 0; font: var(--t-parrafo); color: var(--text); }
.op-dato > span,
.op-dato > a { min-width: 0; overflow-wrap: anywhere; }
/* Ajuste de 3 px: el ícono de 16 queda centrado con el primer renglón (21). */
.op-dato > svg { flex: none; margin-top: 3px; color: var(--text-faint); }
/* Un contacto de la agencia en Mi agencia (#93): el nombre y, a 4, su teléfono y su correo. */
.pa-contacto { display: flex; flex-direction: column; gap: var(--sp-1); }
.pa-contacto-vos { color: var(--text-muted); }
/* Las acciones de una parada, una debajo de la otra a todo el ancho (8 entre
   ellas): la pareja Waze y Google Maps (.op-nav-par) y "Empezar instalación".
   Son enlaces con forma de botón: sin el subrayado ni el color de a:hover (en
   el teléfono el hover queda pegado). */
.op-acciones { display: flex; flex-direction: column; gap: var(--sp-2); }
.op-acciones > a,
.op-acciones > a:hover { text-decoration: none; }
.op-acciones > a.btn-primary:hover { color: var(--on-accent); }
.op-acciones > a.btn-secondary:hover { color: var(--text); }

@media (max-width: 900px) {
  /* El teléfono del cliente se toca: 44 de alto sin mover el renglón. */
  .op-dato > a { display: inline-flex; align-items: center; min-height: 44px; margin: -11px 0; }
}
@media (prefers-reduced-motion: reduce) {
  .op-acceso { transition: none; }
}

/* Rediseño aprobado por Fabiana el 2026-09-13 (diseño C, verde oscuro): que
   empezar y la instalación en curso no pasen desapercibidos para personas sin
   conocimiento de software. */

/* Los botones que el operario tiene que ver sí o sí: 56 de alto y letra de 16
   (la del título de sección), para acertarles con el pulgar sin mirar. La
   guía pide 44; acá se agranda a propósito. */
.op-btn-grande { min-height: 56px; font: var(--t-seccion); }
.op-btn-grande svg { width: 20px; height: 20px; }

/* Navegar en pareja, lado a lado y más chico: el verde queda para Empezar. */
.op-nav-par { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-2); }
.op-nav-par > a,
.op-nav-par > button { padding-left: var(--sp-3); padding-right: var(--sp-3); }
.op-nav-par > a,
.op-nav-par > a:hover { text-decoration: none; }
.op-nav-par > a:hover { color: var(--text); }
.op-acciones > button { width: 100%; }

/* La instalación en curso, en el inicio (.op-hero) y en Mi ruta
   (.op-parada-activa): el verde oscuro del menú con letra clara. Es lo que más
   resalta en una pantalla clara sin parecer una advertencia (se eligió en lugar
   del ámbar). Es la única caja fuera de la barra lateral con sus tokens. */
.op-hero {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  padding: var(--sp-4);
  border: 1px solid var(--sidebar-bg);
  border-radius: var(--radius-lg);
  box-shadow: var(--elev-1);
}
.op-hero,
.op-parada-activa { background: var(--sidebar-bg); border-color: var(--sidebar-bg); color: var(--sidebar-text); }
.op-hero-rotulo {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  font: var(--t-chip);
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--sidebar-active-text);
}
/* Un punto verde lleno: "está pasando ahora". */
.op-hero-rotulo::before { content: ""; flex: none; width: 10px; height: 10px; border-radius: var(--radius-full); background: var(--accent); }
.op-hero-cliente { font: var(--t-titulo); letter-spacing: -.01em; color: var(--sidebar-text); overflow-wrap: anywhere; }
.op-hero .c-sub { margin-top: 0; color: var(--sidebar-muted); }
.op-hero .op-dato,
.op-parada-activa .op-dato,
.op-parada-activa .op-cliente { color: var(--sidebar-text); }
.op-hero .op-dato > svg,
.op-parada-activa .op-dato > svg { color: var(--sidebar-muted); }
.op-parada-activa .op-dato > a { color: var(--sidebar-active-text); }
.op-parada-activa .op-num { background: var(--accent); color: var(--accent-ink); }
/* El anillo de foco verde no se ve sobre el verde oscuro: el del menú lateral
   (.sidebar :focus-visible). */
.op-hero :focus-visible,
.op-parada-activa :focus-visible { outline-color: var(--sidebar-active-text); }

/* Con una abierta, el aviso de Mi ruta no es una advertencia: va neutro. */
/* "Tenés una instalación en curso" no es una advertencia: es un dato. Apaga
   el color del círculo y del rótulo; la caja es la misma. */
.op-aviso-curso .alerta-marca { background: var(--surface-muted); color: var(--text-muted); }
.op-aviso-curso .alerta-rotulo { color: var(--text-muted); }

/* Con una abierta, Waze, Google Maps y Empezar de las demás (#123): grises
   como un botón apagado (.btn-*:disabled), pero se pueden tocar y abren
   "Primero terminá la instalación en curso". Ni el mouse ni el toque los
   cambian de color. */
.op-acciones [aria-disabled="true"] { opacity: .45; cursor: not-allowed; }
.op-acciones .btn-primary[aria-disabled="true"]:is(:hover, :active) { background: var(--accent-strong); transform: none; }
.op-acciones .btn-secondary[aria-disabled="true"]:is(:hover, :active) { background: var(--surface); border-color: var(--border-control); transform: none; }

/* "Primero terminá la instalación en curso" (#123): ocupa toda la pantalla
   (lo pidió Fabiana, 2026-10-02). El mensaje al medio y el botón abajo, donde
   llega el pulgar; el círculo neutro, como el aviso de Mi ruta: no es un error. */
/* Encima del cajón del menú (90): su sombra, cerrado, asoma por el borde derecho. */
.modal-overlay.op-bloqueo-ov { z-index: 95; padding: 0; align-items: stretch; background: var(--surface); }
.op-bloqueo {
  display: flex;
  flex-direction: column;
  gap: var(--sp-5);
  width: 100%;
  max-width: 640px;
  margin: 0 auto;
  padding: var(--sp-6) var(--sp-4) max(var(--sp-6), env(safe-area-inset-bottom));
  overflow-y: auto;
}
.op-bloqueo-cuerpo {
  flex: 1 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--sp-4);
  text-align: center;
}
.op-bloqueo-icono {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 64px;
  height: 64px;
  border-radius: var(--radius-full);
  background: var(--surface-muted);
  color: var(--text-muted);
}
.op-bloqueo-titulo { margin: 0; font: var(--t-titulo); letter-spacing: -.01em; color: var(--text); text-wrap: balance; }
.op-bloqueo-cuerpo p { margin: 0; max-width: 36ch; font: var(--t-parrafo); color: var(--text); }
.op-bloqueo .op-btn-grande { width: 100%; }

/* "Tu próxima instalación" en el inicio: el título y la tarjeta, 12 entre ellos. */
.op-proxima { display: flex; flex-direction: column; gap: var(--sp-3); }

/* Los formularios de cierre: una columna, campos grandes (48 con letra de 16
   hasta 900 px, como toda la app), secciones con título y ayuda. */
.op-form { max-width: 640px; display: flex; flex-direction: column; gap: var(--sp-4); }
.op-form .field { margin: 0; }
.op-resumen {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  padding: var(--sp-4);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
}
.op-form-sec { display: flex; flex-direction: column; gap: var(--sp-3); }
.op-form-sec-cabeza { display: flex; flex-direction: column; gap: var(--sp-1); }
.op-fieldset { margin: 0; padding: 0; border: 0; min-width: 0; }
.op-fieldset > legend { padding: 0; margin-bottom: var(--sp-3); }
.op-nota { margin: 0; font: var(--t-parrafo); color: var(--text-muted); }
/* "¿Qué pasó?" (2026-09-26): los motivos agrupados por quién lo causó. */
.op-grupo-titulo { margin: var(--sp-3) 0 var(--sp-1); font: var(--t-seccion); color: var(--text-muted); }
.op-grupo-titulo:first-of-type { margin-top: 0; }
/* "* obligatorio", como en los modales (.modal-body), justo antes del botón de
   enviar: con `order`, el pseudoelemento no queda debajo del botón. */
.op-form:has(.field :required)::after { content: "* obligatorio"; order: 1; font: var(--t-leyenda); color: var(--text-muted); }
.op-form > button[type="submit"] { order: 2; }

/* Qué pasó: opciones grandes que se tocan enteras (48 de alto); la elegida con
   el borde y el fondo del verde de la app. */
.op-opciones { display: flex; flex-direction: column; gap: var(--sp-2); }
.op-opcion {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  min-height: 48px;
  padding: var(--sp-2) var(--sp-4);
  border: 1px solid var(--border-control);
  border-radius: var(--radius);
  background: var(--surface);
  font: var(--t-parrafo);
  color: var(--text);
  cursor: pointer;
}
.op-opcion input { flex: none; width: 20px; height: 20px; margin: 0; accent-color: var(--accent-strong); }
/* Número de serie al terminar (2026-09-26): el número en mono y, debajo, de dónde salió. */
.op-opcion-serie > span { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.op-opcion-serie > span > small { font: var(--t-tabla); color: var(--text-muted); font-weight: 400; }
.op-opcion-num > span { font-family: var(--font-mono); letter-spacing: .03em; }
.op-opcion-num > span > small { font-family: var(--font-sans); letter-spacing: 0; }
.op-serie-otra input { font-family: var(--font-mono); letter-spacing: .04em; text-transform: uppercase; }
.op-serie-otra input::placeholder { text-transform: none; letter-spacing: 0; font-family: var(--font-sans); }
.op-opcion:has(input:checked) { border-color: var(--accent-dark); background: var(--green-bg); font-weight: 600; }
.op-fieldset.has-error .op-opcion { border-color: var(--red-text); }

/* Terminar instalación, versión simple (aprobada por Fabiana el 2026-09-13):
   "¿Sobró cable?" y "¿Usaste material extra?" con las opciones grandes, una
   tarjeta por cable o material con − y +, y la lista grande para elegir el
   material. La ayuda de cada pregunta va a 4 del título (DISENO §3.10). */
.op-fieldset > legend:has(+ .op-legend-ayuda) { margin-bottom: var(--sp-1); }
/* Una tarjeta por cable o material: 16 de relleno y 12 entre bloques, como las paradas. */
.op-material-lista { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--sp-3); }
.op-material {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  padding: var(--sp-4);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
}
.op-material .field { margin: 0; }
.op-material .field-error:empty { display: none; }
.op-material-nombre { font: var(--t-seccion); color: var(--text); overflow-wrap: anywhere; }
/* La cantidad: − y + de 48 de ancho y del alto del campo, a los lados del
   número, que se puede escribir igual (sin las flechitas del navegador). */
.op-cantidad { display: grid; grid-template-columns: 48px minmax(0, 1fr) 48px; gap: var(--sp-2); align-items: stretch; }
.op-cantidad > .btn-secondary { width: 48px; padding: 0; justify-content: center; }
.op-cantidad > input { width: 100%; min-width: 0; text-align: center; appearance: textfield; -moz-appearance: textfield; }
.op-cantidad > input::-webkit-outer-spin-button,
.op-cantidad > input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
/* "Quitar" con su texto, alineado con el contenido de la tarjeta; "Agregar" a
   todo el ancho, para acertarle con el pulgar. */
.op-quitar { align-self: flex-start; margin-left: calc(var(--sp-3) * -1); }
.op-agregar { width: 100%; justify-content: center; }
/* Las fotos del cargador (2026-09-25): de a dos en todos los anchos (la
   pantalla llega a 640). Cada una es una tarjeta con la foto recortada en 4:3,
   que en el teléfono se reconoce igual, y "Quitar" debajo. La foto va a ras
   del borde de la tarjeta: es el contenido, no algo metido en una caja. */
.op-fotos-lista { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp-3); }
.op-foto {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  padding-bottom: var(--sp-2);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  overflow: hidden;
}
.op-foto-fallo { border-color: var(--red-text); }
.op-foto-img { display: block; width: 100%; aspect-ratio: 4 / 3; object-fit: cover; background: var(--surface-muted); }
.op-foto-estado { padding: 0 var(--sp-3); }
.op-foto .op-quitar { margin-left: 0; align-self: flex-start; }
.op-fotos > .field-error { margin: 0; }
/* La lista grande para elegir el material, en el modal. .modal-body ul (las
   listas de texto de los modales) le pondría 16 a la izquierda. */
.op-elegir-lista { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
.modal-body .op-elegir-lista { margin: 0; }
.op-elegir-lista > li + li { border-top: 1px solid var(--border-soft); }
.op-elegir-lista button {
  width: 100%;
  min-height: 56px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  padding: var(--sp-3) 0;
  background: transparent;
  border: 0;
  font: var(--t-parrafo);
  color: var(--text);
  text-align: left;
  cursor: pointer;
}
.op-elegir-lista button:hover { color: var(--accent-dark); }
.op-elegir-unidad { flex: none; font: var(--t-leyenda); color: var(--text-muted); }
.op-elegir-vacio { padding: var(--sp-3) 0; font: var(--t-parrafo); color: var(--text-muted); }

/* ---- Órdenes devueltas: la oficina (public/js/orders.js, app.js, rutas.js) ----
   Cuando la cuadrilla avisa "no se pudo completar" (2026-09-13), la oficina lo
   ve en rojo hasta que alguien la reprograma: el número del menú, el aviso de
   arriba de Órdenes, el chip "No completada", la caja del detalle y la
   reprogramación. Aprobado en preview por Fabiana el 2026-09-13. */

/* El número rojo del menú: cuántas volvieron de la calle sin completarse. */
.nav-badge.nav-badge-alerta {
  min-width: 20px;
  height: 20px;
  padding: 0 var(--sp-1);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  letter-spacing: 0;
  background: var(--red-strong);
  color: var(--on-accent);
  border-radius: var(--radius-full);
}

/* En el teléfono el menú está guardado: el número va en el botón que lo abre. */
.nav-toggle-aviso {
  position: absolute;
  top: 2px;
  right: 0;
  min-width: 18px;
  height: 18px;
  padding: 0 var(--sp-1);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-full);
  background: var(--red-strong);
  color: var(--on-accent);
  font: var(--t-chip);
}
@media (max-width: 560px) {
  /* El ☰ va static en el teléfono: relative, sin moverlo, para que el número
     se ubique sobre él (hasta 900 ya va absolute). */
  .nav-toggle { position: relative; top: auto; right: auto; }
}

/* El aviso de arriba de Órdenes: rojo como un error de carga, con el motivo,
   quién avisó y qué hacer. */
.aviso-devuelta .btn-secondary { flex: none; }
/* Se toca entero (2026-10-03): como una fila que abre su detalle (DISENO §3.7). */
.aviso-devuelta { cursor: pointer; }
.aviso-devuelta:hover { background: var(--surface-muted); }
@media (max-width: 560px) {
  /* En el teléfono el botón baja debajo del texto, a todo el ancho y a 12 de
     él (DISENO §3.10). El 12 es el gap de la caja. */
  .aviso-devuelta { flex-wrap: wrap; }
  .aviso-devuelta .alerta-cuerpo { flex-basis: calc(100% - 28px - var(--sp-3)); }
  .aviso-devuelta .btn-secondary { flex: 1 1 100%; }
}

/* Lo que espera a la oficina, arriba de Solicitudes (2026-09-30): la alerta de
   las no completadas de Órdenes (.aviso-devuelta), en ámbar, con los botones
   arriba a la derecha, a 8 entre ellos. Va después de la lista de la alerta
   única, que si no le pisa estas reglas. */
.so-pend-a-acc { flex: none; display: flex; flex-wrap: wrap; gap: var(--sp-2); }
/* Un folio va entero: no se parte en el guion. */
.so-folio { white-space: nowrap; }
@media (max-width: 900px) {
  /* En la tableta los botones bajan debajo del texto, a 12 de él (el gap de la
     caja) y alineados con el texto (el círculo de 28 y sus 12): a la derecha
     dejaban el texto en una columna angosta. */
  .so-pend-a { flex-wrap: wrap; }
  .so-pend-a .alerta-cuerpo { flex-basis: calc(100% - 28px - var(--sp-3)); }
  .so-pend-a-acc { flex: 1 1 100%; margin-left: calc(28px + var(--sp-3)); }
}
@media (max-width: 560px) {
  /* En el teléfono, a todo el ancho y uno debajo del otro (como .aviso-devuelta). */
  .so-pend-a-acc { margin-left: 0; flex-direction: column; }
  .so-pend-a-acc .btn-secondary { width: 100%; }
}

/* La caja de la orden devuelta en el detalle: lo que declaró la cuadrilla,
   tal cual, y qué hacer para resolverlo. */
/* La devuelta lleva la cita, el quién y el qué hacer: 8 entre esos bloques,
   como cuando la caja los apilaba ella misma. */
.devuelta-caja .alerta-cuerpo,
.aviso-devuelta .alerta-cuerpo { gap: var(--sp-2); }
.devuelta-meta { font: var(--t-leyenda); color: var(--text-muted); }

/* Reprogramar la devuelta: la fecha y el equipo, como en el resto de los
   formularios. El grupo de casillas lleva su título como una etiqueta. */
.reprog-fieldset { margin: 0; padding: 0; border: 0; min-width: 0; }
.reprog-fieldset > legend { padding: 0; margin-bottom: var(--sp-2); font: var(--t-etiqueta); color: var(--text-muted); }
.reprog-equipo { display: flex; flex-direction: column; gap: var(--sp-2); margin-bottom: var(--sp-2); }
.reprog-equipo label { display: flex; align-items: center; gap: var(--sp-2); font: var(--t-parrafo); color: var(--text); }
.reprog-equipo input { flex: none; width: 18px; height: 18px; margin: 0; accent-color: var(--accent-strong); }
@media (max-width: 900px) {
  .reprog-equipo { gap: 0; }
  .reprog-equipo label { min-height: 44px; }
}

/* El historial de la orden: cada devolución y cada reprogramación, como la
   lista de confirmaciones de la cuadrilla. */
.devuelta-historial { display: flex; flex-direction: column; gap: var(--sp-2); }
/* Reprogramaciones (2026-09-26): lo que la cola necesita ver, los intentos y las fechas sugeridas. */
.devuelta-sub { margin-top: var(--sp-1); font: var(--t-tabla); color: var(--text-muted); }
.visitas-lista { display: flex; flex-direction: column; gap: var(--sp-2); }
.visita { display: flex; align-items: flex-start; gap: var(--sp-3); font: var(--t-parrafo); color: var(--text); }
.visita-n { flex: none; min-width: 32px; font: var(--t-seccion); color: var(--text-muted); font-variant-numeric: tabular-nums; }
.visita-que { flex: 1 1 auto; min-width: 0; }
.visita .chip { flex: none; }
.sug-prioridad { margin-bottom: var(--sp-2); }
.sug-dias { display: flex; flex-wrap: wrap; gap: var(--sp-2); margin-bottom: var(--sp-2); }
.sug-dia { display: flex; flex-direction: column; align-items: center; gap: 2px; min-width: 64px; min-height: 48px; padding: var(--sp-1) var(--sp-2); border: 1px solid var(--border-control); border-radius: var(--radius); background: var(--surface); color: var(--text); font: var(--t-tabla); cursor: pointer; }
.sug-dia .sug-fecha { font-weight: 600; text-transform: capitalize; }
.sug-dia .sug-cupo { color: var(--text-muted); }
.sug-dia.sug-sugerido { border-color: var(--accent-dark); }
.sug-dia.sug-sugerido .sug-cupo { color: var(--accent-dark); }
.sug-dia.sug-elegido { background: var(--green-bg); border-color: var(--accent-dark); }
.sug-dia:disabled { opacity: .55; cursor: default; }
.sug-dia:hover:not(:disabled) { border-color: var(--accent-dark); }


/* ---------- Solicitudes: pestañas y Compras de equipo (2026-09-26) ----------
   Instalaciones | Compras de equipo con el conmutador de la app, arriba de los
   filtros; en el teléfono, a todo el ancho. En la compra: cuánto hay en bodega
   de cada línea (verde si ya está todo) y el precio editable al aprobar. */
.so-pestanas { align-self: flex-start; }
/* QA P2: las pestañas, los filtros y la tabla de cada pestaña, a 16 (3.10:
   "una caja y la de al lado"); antes la tabla quedaba pegada a los filtros
   (0 px) y las pestañas, a 32 de ellos. */
.so-cuerpo { display: flex; flex-direction: column; gap: var(--sp-4); min-width: 0; }
.so-cuerpo > [data-tabla]:empty { display: none; }
/* Aprobar una solicitud (QA P6, 2026-09-29): la alerta "No hay un BOM de la
   marca" va antes del campo del BOM, a 12 de él (3.10). */
.ap-aviso { margin-bottom: var(--sp-3); }
.ce-lista { color: var(--green-text); font-weight: 600; }
.ce-completo { color: var(--green-text); font-weight: 600; }
.ce-falta { color: var(--amber-text); font-weight: 600; }
.ce-ligar { margin-top: var(--sp-2); }
.ce-ligar select { width: 100%; max-width: 360px; }
.ce-precio input { width: 120px; text-align: right; font-variant-numeric: tabular-nums; }
.ce-lineas tfoot td { border-top: 1px solid var(--border); }
/* QA P9 (2026-09-29): la venta recibida es una tabla de líneas con campos
   (3.8, .tbl-lineas .tbl-lineas-campos) en el modal ancho (640, 3.10). Las
   columnas tienen su ancho y Equipo usa el resto: así entra en los 640 aunque
   la lista de un "Otro" tenga opciones largas (#55: el precio se cortaba). */
/* Capturar respuesta y contraoferta de una RFQ (re-prueba del 29/09, #75):
   la tabla entra en los 512 del modal sin desplazarse de costado. Anchos
   fijos para los números y los campos; la parte usa lo que queda. Los dos
   campos de una fila quedan a la misma altura: el error vacío no ocupa lugar. */
.tbl.rfq-campos { table-layout: fixed; width: 100%; }
.tbl.rfq-campos th { white-space: normal; }
.tbl.rfq-campos th:nth-child(2) { width: 80px; }
.tbl.rfq-campos th:nth-child(3), .tbl.rfq-campos th:nth-child(4) { width: 136px; }
.tbl.rfq-campos td.rl-campo input { width: 100%; min-width: 0; }
.tbl.rfq-campos .field-error:empty { display: none; }

.tbl.ce-lineas { table-layout: fixed; width: 100%; }
.ce-lineas th:nth-child(2) { width: 104px; }
.ce-lineas th:nth-child(3) { width: 88px; }
.ce-lineas th:nth-child(4) { width: 152px; }
.ce-lineas th:nth-child(5) { width: 56px; }
/* La venta ya aprobada o entregada, sin campos (re-prueba del 29/09, #77): el
   subtotal va entero dentro de los 640 (antes tomaba los 56 de la papelera). */
.ce-lineas.ce-lectura th:nth-child(2) { width: 80px; }
.ce-lineas.ce-lectura th:nth-child(4) { width: 120px; }
.ce-lineas.ce-lectura th:nth-child(5) { width: 128px; }
.ce-lineas td.rl-parte select,
.ce-lineas td.rl-campo input { width: 100%; min-width: 0; }
.ce-lineas [data-qty] { text-align: right; font-variant-numeric: tabular-nums; }
.ce-lineas td.rl-quitar { text-align: right; vertical-align: middle; }
.ce-lineas .fila-avisos { margin-top: var(--sp-1); }
.ce-cambios { margin-top: var(--sp-3); display: flex; flex-direction: column; gap: var(--sp-2); }
.ce-cambios h4 { margin: 0; }
.ce-cambios ul { margin: 0; padding-left: var(--sp-4); }
/* P9b: la parte nueva, una tarjeta en su renglón (16 de relleno y radio 12, 3.10). */
.ce-nueva { display: flex; flex-direction: column; gap: var(--sp-3); padding: var(--sp-4); border: 1px solid var(--border); border-radius: var(--radius-lg); background: var(--surface); }
.ce-nueva h4 { margin: 0 0 var(--sp-1); }
/* Regla nueva "avisos en tablas" (P7/P9): lo que le pasa a una fila, debajo
   del dato, uno por renglón, con ícono de 16 y texto en el color del estado. */
.fila-avisos { list-style: none; margin: var(--sp-1) 0 0; padding: 0; display: flex; flex-direction: column; gap: var(--sp-1); }
.fila-aviso { display: flex; align-items: center; gap: var(--sp-1); font: var(--t-leyenda); }
.fila-aviso svg { flex: none; width: 16px; height: 16px; }
.fila-aviso-rojo { color: var(--red-text); }
.fila-aviso-ambar { color: var(--amber-text); }
/* Debajo de un campo (el precio de venta bajo el costo en Part Data, #87-B): a 8 del control, el espacio del .field. */
.field > .fila-avisos { margin-top: 0; }
@media (max-width: 560px) {
  .so-pestanas { display: grid; grid-template-columns: 1fr 1fr; width: 100%; }
  .ce-precio input { width: 100%; }
  /* P9 en el teléfono: la cantidad a todo su lugar; la papelera abajo a la
     derecha; la parte nueva a todo el ancho. */
  .ce-lineas [data-qty] { width: 100%; }
  .tbl.tbl-lineas-campos.ce-lineas td.rl-quitar { width: auto; justify-self: end; align-self: end; }
  .tbl.tbl-lineas-campos.ce-lineas .ce-nueva-fila > td { grid-column: 1 / -1; }
}

/* ---------- Hilo de una solicitud en la oficina (public/js/solicitudes.js) ----------
   Cada mensaje es una tarjeta chica; los de la agencia en el fondo hundido, los
   del sistema en línea punteada y sin negrita. */
.hilo { display: flex; flex-direction: column; gap: var(--sp-2); }
.hilo-msg { padding: var(--sp-3) var(--sp-4); border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface); }
.hilo-msg.de-agencia { background: var(--surface-muted); }
.hilo-msg.sistema { border-style: dashed; color: var(--text-muted); }
/* QA P14 (#51): el mensaje recién mandado se tiñe un momento (no es un foco). */
.hilo-msg.hilo-msg-nuevo { background: var(--green-bg); }
.hilo-meta { font: var(--t-leyenda); color: var(--text-muted); margin-bottom: var(--sp-1); }
.hilo-texto { white-space: pre-wrap; overflow-wrap: anywhere; }
.hilo-form { margin-top: var(--sp-2); } /* 24 del hilo, con el espacio del modal (antes 28; #77) */
.hilo-form-pie { display: flex; justify-content: flex-end; }

/* ---------- Portal de agencias (public/js/portal.js) ----------
   Rediseño aprobado en preview por Fabiana el 2026-09-18 (DISENO §3.13): el
   enfoque de las pantallas del operario (§3.11), para personas sin
   conocimiento técnico, pero pensado primero para la computadora, que es
   donde lo usan las agencias. Reusa del operario la
   tarjeta que se toca entera (.op-acceso), los datos con ícono (.op-dato), las
   opciones grandes (.op-opcion) y los botones grandes (.op-btn-grande); acá va
   solo lo propio. Lo que cambia con el ancho se decide por el ancho de la
   página (consulta de contenedor), no de la ventana: así también sirve con el
   menú abierto en una laptop chica. */

.pa-pagina { container: pa / inline-size; display: flex; flex-direction: column; gap: var(--sp-4); min-width: 0; }
.pa-col,
.pa-columnas { display: flex; flex-direction: column; gap: var(--sp-4); min-width: 0; }

/* La caja de las tarjetas del portal: 16 de relleno en el teléfono y, con
   lugar, 16 × 24 como los paneles (§3.10); 12 entre bloques. */
.pa-caja,
.pa-estado,
.pa-sec {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  min-width: 0;
  padding: var(--sp-4);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--elev-1);
}
.pa-caja-titulo { font: var(--t-seccion); color: var(--text); overflow-wrap: anywhere; }

/* ---- Mis solicitudes ---- */

/* Pedir una instalación, la lista y el resumen. En la computadora: pedir
   arriba a todo el ancho, la lista y el resumen al costado (un tercio, 360 como mínimo). */
.pa-portal { display: flex; flex-direction: column; gap: var(--sp-4); min-width: 0; }
/* El resumen va después de la lista en el código: sin esto, .bloque + .bloque
   le suma 16 arriba y en la computadora queda más bajo que la lista. El
   espacio entre las dos ya lo da el gap. */
.pa-portal > .bloque + .bloque { margin-top: 0; }
.pa-pedir .op-btn-grande { width: 100%; }

/* Las dos cosas que se piden (2026-09-26): instalación y equipo. Una debajo
   de la otra en el teléfono; con lugar, una al lado de la otra. Una sola
   primaria: la instalación; comprar equipo va como secundaria (§3.4). */
.pa-pedir-grupo { display: grid; gap: var(--sp-4); min-width: 0; }

/* Tres cifras en una sola tarjeta, separadas por una línea. */
.pa-cifras {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  padding: var(--sp-4) 0;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--elev-1);
}
.pa-cifra { display: flex; flex-direction: column; gap: var(--sp-1); min-width: 0; padding: 0 var(--sp-4); }
.pa-cifra + .pa-cifra { border-left: 1px solid var(--border-soft); }
.pa-cifra-valor { font: var(--t-titulo); letter-spacing: -.01em; color: var(--text); font-variant-numeric: tabular-nums; }
.pa-cifra-texto { font: var(--t-tabla); color: var(--text-muted); }

/* En curso / Terminadas: el conmutador de la app; en el teléfono, a todo el
   ancho y en dos mitades, debajo del título. */
.pa-lista-cabeza { align-items: center; row-gap: var(--sp-3); }
.pa-filtro { display: grid; grid-template-columns: 1fr 1fr; width: 100%; }

/* Las solicitudes: de a una en el teléfono; con lugar, de a dos. */
.pa-sols {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 340px), 1fr));
  gap: var(--sp-4);
}
/* Cada una, la tarjeta que se toca entera (.op-acceso): el cliente en la
   letra del título de sección y el estado al lado; debajo, en qué va y el
   carro. 8 entre los renglones: son cosas de un mismo grupo (§3.10). */
.pa-sol { height: 100%; }
.pa-sol .op-acceso-texto { gap: var(--sp-2); }
.pa-sol-cliente { font: var(--t-seccion); color: var(--text); overflow-wrap: anywhere; }
/* Un motivo de rechazo largo no estira la tarjeta: dos renglones y el resto en el detalle. */
.pa-sol-sigue > span { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }

/* El resumen: las cifras y los dos gráficos, 16 entre tarjetas. */
.pa-resumen { min-width: 0; }
.pa-resumen-cajas { display: flex; flex-direction: column; gap: var(--sp-4); }

/* "En qué van": una barra por etapa, el nombre y la cantidad arriba y la
   barra debajo, a todo el ancho (como el ranking del Dashboard en una caja
   angosta). La barra es SVG: su largo es un dato (§7). */
.pa-barras { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--sp-3); }
.pa-barras > li { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: baseline; gap: var(--sp-1) var(--sp-3); }
.pa-barra-nombre { font: var(--t-tabla); color: var(--text); }
.pa-barra-valor { font: var(--t-tabla); font-weight: 600; color: var(--text); font-variant-numeric: tabular-nums; }
.pa-barra { grid-column: 1 / -1; display: block; width: 100%; height: 8px; }

/* ---- Solicitud (detalle) ---- */

.pa-estado > .chip { align-self: flex-start; }
.pa-estado-cabeza { display: flex; flex-direction: column; gap: var(--sp-1); }
.pa-estado-titulo { font: var(--t-titulo); letter-spacing: -.01em; color: var(--text); overflow-wrap: anywhere; }
.pa-estado-texto { margin: 0; font: var(--t-parrafo); color: var(--text-muted); }

/* El seguimiento, como el de un paquete: un círculo de 28 por paso (el de la
   alerta, §3.6) y una línea de un círculo al otro que empieza y termina en su
   borde, sin meterse adentro. Hecho: el verde del estado con el tilde; la
   etapa en la que está: el anillo del verde oscuro con su punto; lo que falta:
   el borde de un campo. El nombre de cada paso lo dice también para el lector
   de pantalla. */
.pa-pasos {
  list-style: none;
  margin: 0;
  padding: var(--sp-3) 0 0;
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  border-top: 1px solid var(--border-soft);
}
.pa-paso { position: relative; display: flex; flex-direction: column; align-items: center; gap: var(--sp-1); min-width: 0; text-align: center; }
/* Del borde derecho del círculo anterior (su centro + 14) al borde izquierdo
   de este (su centro − 14): el tramo mide una columna menos un círculo. */
.pa-paso + .pa-paso::before {
  content: "";
  position: absolute;
  top: 13px;
  right: calc(50% + 14px);
  width: calc(100% - 28px);
  height: 2px;
  background: var(--border);
}
.pa-paso + .pa-paso-hecho::before,
.pa-paso + .pa-paso-actual::before { background: var(--accent); }
.pa-paso-marca {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  margin-bottom: var(--sp-1);
  border: 2px solid var(--border-input);
  border-radius: var(--radius-full);
  background: var(--surface);
  color: var(--green-text);
}
.pa-paso-hecho .pa-paso-marca { border-color: var(--accent); background: var(--green-bg); }
.pa-paso-actual .pa-paso-marca { border-color: var(--accent-dark); }
.pa-paso-actual .pa-paso-marca::after { content: ""; width: 10px; height: 10px; border-radius: var(--radius-full); background: var(--accent-dark); }
.pa-paso-nombre { font: var(--t-leyenda); color: var(--text-muted); overflow-wrap: anywhere; }
.pa-paso-hecho .pa-paso-nombre,
.pa-paso-actual .pa-paso-nombre { font-weight: 600; color: var(--text); }
.pa-paso-fecha { font: var(--t-leyenda); color: var(--text-muted); }

/* Los mensajes, como un chat: los de VoltPlan a la izquierda en gris, los
   propios a la derecha en el verde claro de la app, y los avisos del sistema
   al centro, sin globo. */
.pa-chat { gap: var(--sp-4); }
.pa-mensajes { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--sp-3); }
.pa-msg { display: flex; flex-direction: column; gap: var(--sp-1); max-width: 85%; }
.pa-msg-voltplan { align-self: flex-start; }
.pa-msg-mio { align-self: flex-end; align-items: flex-end; }
.pa-msg-texto {
  padding: var(--sp-3);
  border-radius: var(--radius-lg);
  font: var(--t-parrafo);
  color: var(--text);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
.pa-msg-voltplan .pa-msg-texto { background: var(--surface-muted); border: 1px solid var(--border); }
.pa-msg-mio .pa-msg-texto { background: var(--green-bg); }
.pa-msg-meta { font: var(--t-leyenda); color: var(--text-muted); }
.pa-msg-sistema { align-self: center; align-items: center; max-width: 100%; text-align: center; }
.pa-msg-sistema .pa-msg-texto { padding: 0; font: var(--t-tabla); color: var(--text-muted); }
.pa-escribir { display: flex; flex-direction: column; gap: var(--sp-3); padding-top: var(--sp-4); border-top: 1px solid var(--border-soft); }
.pa-escribir .field { margin: 0; }
.pa-escribir .field-error:empty { display: none; }
.pa-escribir > .btn-primary { width: 100%; justify-content: center; }

/* Lo que se pidió: los datos con ícono del operario; el VIN debajo del carro. */
.pa-dato-lineas { display: flex; flex-direction: column; gap: var(--sp-1); }
.pa-dato-sub { font: var(--t-tabla); color: var(--text-muted); overflow-wrap: anywhere; }

.pa-cancelar { display: flex; flex-direction: column; align-items: flex-start; gap: var(--sp-2); }

/* ---- Pedir una instalación ---- */

/* Una sección numerada por pregunta, cada una en su tarjeta, con el número en
   el círculo de las paradas del operario (.op-num). */
.pa-form { display: flex; flex-direction: column; gap: var(--sp-4); min-width: 0; }
.pa-sec-titulo { display: flex; align-items: center; gap: var(--sp-3); margin: 0; font: var(--t-seccion); color: var(--text); }
.pa-sec-titulo .op-num { align-self: auto; }
.pa-sec .field { margin: 0; }
.pa-sec .field-error:empty { display: none; }
.pa-sec .form-grid { margin: 0; }
.pa-sec .op-fieldset > legend.pa-grupo-rotulo { margin-bottom: var(--sp-2); font: var(--t-etiqueta); color: var(--text-muted); }
.pa-sec .op-fieldset .field-error:not(:empty) { margin-top: var(--sp-2); }
/* La marca del cargador: cinco opciones cortas, de a dos en el teléfono. */
.pa-opciones-2 { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp-2); }
/* Una opción con su aclaración debajo ("Negocio o sitio comercial"). */
.pa-opcion-texto { display: flex; flex-direction: column; gap: var(--sp-1); min-width: 0; padding: var(--sp-1) 0; }
.pa-opcion-sub { font: var(--t-tabla); color: var(--text-muted); }
/* Los campos cortos (el VIN, la fecha) no se estiran a todo el ancho. */
.pa-campo-corto { max-width: 360px; }
/* Con datos pendientes: el número de la pregunta en rojo (el mismo rojo del
   error de un campo) y, arriba del botón, el aviso con cuántos faltan y en qué
   preguntas (.pa-aviso, la alerta de la app). "Ir a la pregunta N" queda a 8
   del texto (12 en el teléfono), como todo botón (§3.10). */
.pa-sec-pendiente .op-num { background: var(--red-strong); color: var(--on-accent); }
.pa-aviso .btn-sm { align-self: flex-start; margin-top: var(--sp-1); }

/* El pie: "* obligatorio" y el botón; en el teléfono, el botón a todo el ancho. */
.pa-form-pie { display: flex; flex-direction: column; gap: var(--sp-3); }
.pa-obligatorio { font: var(--t-leyenda); color: var(--text-muted); }
.pa-form-pie .op-btn-grande { width: 100%; }

/* ---- Comprar equipo (2026-09-26, public/js/portal-compras.js) ---- */

.pa-compras { display: flex; flex-direction: column; gap: var(--sp-4); min-width: 0; }
.pa-compras > .bloque + .bloque { margin-top: 0; }
/* El seguimiento de un pedido tiene tres pasos (Enviado → Aprobado → Entregado). */
.pa-pasos.pa-pasos-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
/* El carrito: un renglón por cargador, el nombre a la izquierda y la cantidad
   a la derecha (un campo corto, del alto de un toque, §3.11). */
.pa-equipos { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
.pa-equipo { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); min-height: 56px; padding: var(--sp-2) 0; }
.pa-equipo + .pa-equipo { border-top: 1px solid var(--border-soft); }
.pa-equipo-texto { display: flex; flex-direction: column; gap: var(--sp-1); min-width: 0; }
.pa-equipo-nombre { font: var(--t-parrafo); font-weight: 600; color: var(--text); overflow-wrap: anywhere; }
.pa-equipo-cant { flex: none; width: 112px; }
.pa-equipo-cant input { width: 100%; min-height: 43px; text-align: right; font-variant-numeric: tabular-nums; }
.pa-equipo.has-error .pa-equipo-cant input { border-color: var(--red-strong); }
/* "Otro equipo": qué es, cuántos y quitar. En el teléfono, uno debajo del otro. */
.pa-otro { display: grid; gap: var(--sp-2) var(--sp-3); padding-bottom: var(--sp-3); border-bottom: 1px solid var(--border-soft); }
.pa-otro-quitar { justify-self: start; color: var(--accent-dark); }
/* Lo que se pidió: el equipo y la cantidad a la derecha. */
.pa-linea-cant { flex: none; font: var(--t-parrafo); font-weight: 600; color: var(--text); font-variant-numeric: tabular-nums; }

/* ---- Mi agencia ---- */

/* Los carros en una sola tarjeta, un renglón por carro. */
.pa-lista {
  list-style: none;
  margin: 0;
  padding: var(--sp-1) var(--sp-4);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--elev-1);
}
.pa-lista > li { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); min-height: 56px; padding: var(--sp-2) 0; }
.pa-lista > li + li { border-top: 1px solid var(--border-soft); }
.pa-carro { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2); min-width: 0; }
.pa-carro-nombre { font: var(--t-parrafo); font-weight: 600; color: var(--text); overflow-wrap: anywhere; }
.pa-lista .btn-ghost { flex: none; color: var(--accent-dark); }
/* QA P12: los botones de cada carro, juntos a la derecha (8 entre botones, 3.10). */
.pa-carro-acc { flex: none; display: flex; flex-wrap: wrap; gap: var(--sp-2); }

/* ---- Con lugar (tableta, laptop y computadora) ---- */

@container pa (min-width: 640px) {
  .pa-caja,
  .pa-estado,
  .pa-sec { padding: var(--sp-4) var(--sp-5); }
  .pa-lista { padding-left: var(--sp-5); padding-right: var(--sp-5); }
  /* Pedir: el texto a la izquierda y el botón a la derecha, en un renglón. */
  .pa-pedir { flex-direction: row; align-items: center; justify-content: space-between; gap: var(--sp-5); }
  .pa-pedir .op-btn-grande { flex: none; width: auto; }
  .pa-filtro { display: inline-grid; width: auto; }
  .pa-pedir-grupo { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* De a dos, cada tarjeta es angosta: el texto arriba y el botón debajo. */
  .pa-pedir-grupo .pa-pedir { flex-direction: column; align-items: flex-start; justify-content: space-between; }
  .pa-otro { grid-template-columns: minmax(0, 1fr) 140px auto; align-items: start; }
  .pa-otro-quitar { margin-top: 26px; }
  /* Las opciones, una al lado de la otra. */
  .pa-opciones-3 { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); }
  /* Retiro o entrega (#80): una al lado de la otra desde 640; en el teléfono, una debajo de la otra. */
  .pa-opciones-lado { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .pa-opciones-auto { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); }
  .pa-opciones-2.pa-opciones-marca { grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); }
  .pa-form-pie { flex-direction: row; align-items: center; justify-content: space-between; }
  .pa-form-pie .op-btn-grande { width: auto; min-width: 280px; }
  .pa-cancelar .btn-secondary { width: auto; }
}
@container pa (min-width: 880px) {
  .pa-portal {
    display: grid;
    grid-template-columns: minmax(0, 2fr) minmax(360px, 1fr);
    grid-template-areas: "pedir pedir" "lista resumen";
    align-items: start;
    gap: var(--sp-4) var(--sp-5);
  }
  /* QA P1 (#14): solo el hijo directo va a la celda "pedir"; con ".pa-pedir"
     suelto, la tarjeta de adentro del grupo también iba ahí y quedaba tapada. */
  .pa-portal > .pa-pedir,
  .pa-portal > .pa-pedir-grupo { grid-area: pedir; }
  .pa-lista-sec { grid-area: lista; }
  .pa-resumen { grid-area: resumen; }
  /* Los dos títulos a la misma altura: el de la lista lleva el conmutador al lado. */
  .pa-lista-sec > .bloque-head,
  .pa-resumen > .bloque-head { min-height: 40px; align-items: center; }
  /* Detalle y Mi agencia: dos columnas. */
  .pa-columnas { display: grid; align-items: start; gap: var(--sp-5); }
  .pa-detalle { grid-template-columns: minmax(0, 3fr) minmax(0, 2fr); }
  .pa-agencia { grid-template-columns: minmax(0, 2fr) minmax(0, 3fr); }
}

/* Pantallas muy anchas: el portal usa todo el ancho, como el resto de la app,
   y el formulario pone sus preguntas de a dos (1 | 2, 3 | 4, 5 | 6) para que
   los campos no se estiren de lado a lado. */
@container pa (min-width: 1200px) {
  .pa-form { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .pa-form-pie,
  .pa-form > .pa-aviso { grid-column: 1 / -1; }
}

@media (max-width: 560px) {
  .pa-cifra { padding: 0 var(--sp-3); }
  .pa-aviso .btn-sm { margin-top: var(--sp-2); }
  .pa-cancelar .btn-secondary { width: 100%; }
}

/* ---------- La marca de la empresa en el Dashboard (2026-09-19) ----------
   La empresa que usa la app (src/marca.js). Va su símbolo, si lo tiene,
   y el nombre escrito con la letra de la app: el logo completo trae su propio
   azul y su propia tipografía, que no conviven con el verde ni con IBM Plex.
   El símbolo va redondeado, como los demás círculos de la app (la cuenta del
   pie del menú, el estado de una alerta). Elegido en preview entre tres
   opciones (DISENO §3.14). */
.marca-enc {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2) var(--sp-3);
  padding-bottom: var(--sp-4);
  border-bottom: 1px solid var(--border-soft);
}
.marca-simbolo { display: block; width: 40px; height: 40px; border-radius: var(--radius-full); object-fit: contain; }
.marca-enc-t { display: flex; flex-direction: column; gap: var(--sp-1); min-width: 0; }
.marca-enc-n { margin: 0; font: var(--t-titulo); letter-spacing: -.01em; color: var(--text); }
.marca-enc-s { margin: 0; font: var(--t-subtitulo); color: var(--text-muted); }
/* La fecha equilibra la fila: del otro lado, en el gris de lo secundario. */
.marca-enc-f { margin: 0 0 0 auto; font: var(--t-subtitulo); color: var(--text-muted); }

@media (max-width: 560px) {
  .marca-enc { padding-bottom: var(--sp-3); }
  .marca-simbolo { width: 36px; height: 36px; }
  /* En el teléfono la fecha quedaba sola en un renglón y descolgada: ahí no va. */
  .marca-enc-f { display: none; }
}

/* ---------- La conversación por voz (DISENO §3.15) ----------
   Una tarjeta centrada, elegida en preview el 2026-09-23. Ocupa el lugar del
   compositor, en el flujo, y deja ver el hilo: cuando Voltio dice "para
   mañana hay una instalación, la de Rosa Quim", la tabla con esa orden sigue
   arriba (por eso no es pantalla completa, 2026-09-19). En la computadora va
   en un renglón: con el orbe arriba medía 303 px y en una laptop tapaba medio
   chat. Arriba el orbe, el estado y el botón para terminar; abajo la nota y el
   reloj. En el teléfono va vertical, con el orbe arriba (ver el @media). */
/* El ancho de la columna de Voltio, igual que el hilo y el compositor: los
   mismos márgenes a los costados que el resto de la pantalla. */
.vz {
  flex-shrink: 0;
  width: 100%;
  margin: 0 0 var(--sp-2);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--elev-2);
  animation: vz-entrar var(--dur-2) var(--ease);
}
@keyframes vz-entrar { from { opacity: 0; transform: translateY(var(--sp-3)); } }

.vz-caja {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  grid-template-areas: "orbe texto fin" "nota nota reloj";
  align-items: center;
  column-gap: var(--sp-4);
  row-gap: var(--sp-3);
  padding: var(--sp-4) var(--sp-5);
}

/* El orbe (public/js/orbe.js) a 72, con un halo del acento detrás que se
   apaga hacia los bordes; sin círculo ni fondo propio. */
.vz-orbe-marco { grid-area: orbe; position: relative; display: grid; place-items: center; width: 72px; height: 72px; }
.vz-orbe-marco::before {
  content: "";
  position: absolute;
  inset: calc(-1 * var(--sp-2));
  border-radius: var(--radius-full);
  background: radial-gradient(closest-side, color-mix(in srgb, var(--accent) 14%, transparent), transparent);
}
.vz-orbe { position: relative; display: block; width: 72px; height: 72px; }

.vz-texto { grid-area: texto; display: flex; flex-direction: column; gap: var(--sp-1); min-width: 0; }
.vz-titulo { margin: 0; font: var(--t-seccion); color: var(--text); }
.vz-detalle { margin: 0; font: var(--t-tabla); color: var(--text-muted); }

/* Terminar, como una llamada: un botón redondo de 36 con el cuadrado de
   "detener", en el color del texto (invertido), y "Terminar" debajo. De 48
   se veía demasiado grande al lado del orbe. Nunca
   rojo: acá el rojo es "salió mal o es urgente" (§3.5), y cortar no es
   ninguna de las dos. */
.vz-fin {
  grid-area: fin;
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-1);
  padding: 0;
  border: 0;
  background: none;
  color: var(--text-muted);
  font: var(--t-leyenda);
  cursor: pointer;
}
.vz-fin svg {
  box-sizing: content-box;
  width: 14px;
  height: 14px;
  padding: 11px;
  border-radius: var(--radius-full);
  background: var(--text);
  color: var(--surface);
  transition: transform var(--dur-1) var(--ease), background-color var(--dur-1) var(--ease);
}
.vz-fin:hover { color: var(--text); }
.vz-fin:hover svg { background: var(--text-muted); }
.vz-fin:active svg { transform: var(--press); }
/* El globo de ayuda sobra: el texto ya está a la vista. */
.vz-fin[data-tip]::after { content: none; }

/* La nota, separada del renglón de arriba por una línea; el reloj, chico, a
   su derecha y debajo del botón: es un dato, no lo que se mira. */
.vz-nota { grid-area: nota; margin: 0; padding-top: var(--sp-3); border-top: 1px solid var(--border-soft); font: var(--t-leyenda); color: var(--text-faint); }
.vz-reloj { grid-area: reloj; justify-self: center; align-self: end; margin: 0; font: var(--t-leyenda); font-variant-numeric: tabular-nums; color: var(--text-faint); }

/* Mientras está abierta, la tarjeta reemplaza al compositor, en el flujo y no
   flotando: una tarjeta flotante a distancia fija pisaba el pie, que mide 24
   en la computadora y 42 en el teléfono. asistente.js la agrega al final de
   .asis; el orden la deja antes del pie (por si muestra el dictado). */
.asis:has(> .vz) .asis-composer { display: none; }
.asis:has(> .vz) .asis-pie { order: 1; }
/* El texto de siempre del pie dice casi lo mismo que la nota de la tarjeta:
   mientras dura la conversación queda solo la nota, que es la regla de la voz. */
.asis:has(> .vz) .asis-pie-normal { display: none; }

@media (max-width: 560px) {
  .asis-pie-normal { display: none; }
}

/* En el teléfono, vertical: el orbe de 96 arriba y todo centrado debajo, con
   el reloj en la esquina. Elegido en preview el 2026-09-23 (en la computadora,
   horizontal). asistente.js crea el orbe a 96 o a 72 según el ancho. */
@media (max-width: 560px) {
  .vz-caja { position: relative; display: flex; flex-direction: column; align-items: center; gap: var(--sp-3); padding: var(--sp-5) var(--sp-4) var(--sp-4); text-align: center; }
  .vz-orbe-marco { width: 96px; height: 96px; }
  .vz-orbe-marco::before { inset: calc(-1 * var(--sp-4)); }
  .vz-orbe { width: 96px; height: 96px; }
  .vz-texto { align-items: center; }
  .vz-reloj { position: absolute; top: var(--sp-3); right: var(--sp-4); }
  .vz-nota { padding-top: 0; border-top: 0; }
}
/* Hasta 900 px lo que se toca mide 44: el círculo se ve de 36, pero el botón
   (círculo y "Terminar") suma 56 de alto y 44 de ancho como mínimo. */
@media (max-width: 900px) {
  .vz-fin { min-width: 44px; }
}
@media (prefers-reduced-motion: reduce) {
  .vz { animation: none; }
}

/* La flecha, propia, en toda lista desplegable de la app: la del navegador
   quedaba casi pegada al borde derecho (Fabiana, 2026-09-30 en los filtros de
   Solicitudes y Órdenes; 2026-10-01 en todas). La misma flecha del Catálogo
   BOM (.bom-mats), de 12, a 12 del borde como el texto a la izquierda; el
   texto deja 32 a la derecha (12 + 12 + 8). Va acá, después de las reglas de
   cada campo, y con :not() le gana a `.field select` y a las que ponen
   `background` o `padding` (también a la deshabilitada). La lista de
   selección múltiple no la lleva. Su versión oscura, abajo con las demás. */
select:not([multiple]):not([size]) {
  appearance: none;
  -webkit-appearance: none;
  padding-right: var(--sp-6);
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' fill='none'><path d='M2 4l4 4 4-4' stroke='%23505d56' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/></svg>");
  background-repeat: no-repeat;
  background-position: right var(--sp-3) center;
  background-size: 12px;
  cursor: pointer;
}
select:not([multiple]):not([size]):disabled { cursor: not-allowed; }

/* ---------- tema oscuro: lo que no sale solo de los tokens ---------- */

/* La barra lateral, más oscura que la página en claro, en oscuro queda casi
   igual: la separa una línea. */
:root[data-tema="oscuro"] .sidebar { border-right: 1px solid var(--border-soft); }

/* Íconos dibujados dentro del CSS (data:), que no aceptan variables: el mismo
   dibujo con el color del token en oscuro. */
:root[data-tema="oscuro"] .empty-state::before {
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23909499' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'><path d='M3 13h4.5l1.5 2.5h6l1.5-2.5H21'/><path d='M5.5 5h13L21 13v5a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-5z'/></svg>");
}
:root[data-tema="oscuro"] .empty-state.estado-error::before {
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23f59a90' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'><circle cx='12' cy='12' r='9'/><path d='M12 7.5v5.5M12 16.5v.01'/></svg>");
}
:root[data-tema="oscuro"] select:not([multiple]):not([size]) {
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' fill='none'><path d='M2 4l4 4 4-4' stroke='%23aeb1b5' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/></svg>");
}
:root[data-tema="oscuro"] .bom-mats > summary::after {
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' fill='none'><path d='M2 4l4 4 4-4' stroke='%23aeb1b5' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/></svg>");
}
:root[data-tema="oscuro"] .asis-todo > summary::before {
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' fill='none'><path d='M4 2l4 4-4 4' stroke='%235cc98a' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/></svg>");
}

/* El brillo que recorre la silueta de "Cargando…": blanco al 70 % era un
   destello sobre el fondo oscuro. */
:root[data-tema="oscuro"] .loading-state::after {
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, .05), transparent);
}

/* Globos de ayuda: en claro, el verde oscuro de la barra sobre la página
   clara; en oscuro ese verde se pierde contra la página, así que suben un
   tono y llevan borde. */
:root[data-tema="oscuro"] .btn-icon[data-tip]::after,
:root[data-tema="oscuro"] .asis-composer .asis-circulo[data-tip]::after {
  background: #2a2c2f;
  color: var(--text);
  box-shadow: 0 0 0 1px var(--border-control), var(--elev-2);
}

/* La instalación en curso del operario va en el verde de la barra: en oscuro
   la separa de la página su borde verde. */
:root[data-tema="oscuro"] :is(.op-hero, .op-parada-activa) { border-color: var(--green-border); }

/* Gráficos en SVG (Dashboard y portal): sus colores son atributos con los
   valores de la guía (DISENO 3.1), y el CSS les gana a los atributos. */
:root[data-tema="oscuro"] svg [fill="#626f6a"] { fill: var(--text-faint); }
:root[data-tema="oscuro"] svg [fill="#43524a"] { fill: var(--text-muted); }
:root[data-tema="oscuro"] svg [fill="#e9ede9"] { fill: var(--border-soft); }
:root[data-tema="oscuro"] svg [stroke="#e9ede9"] { stroke: var(--border-soft); }
:root[data-tema="oscuro"] svg [stroke="#ffffff"] { stroke: var(--surface); }
:root[data-tema="oscuro"] svg [fill="#b9c4bb"] { fill: #53575c; }

/* Mapas (Rutas y Centro de control): los mosaicos de OpenStreetMap vienen
   claros; se invierten y se apagan para que no encandilen. Los marcadores
   y las rutas no se tocan: conservan sus colores. */
:root[data-tema="oscuro"] .leaflet-tile-pane { filter: invert(1) hue-rotate(180deg) brightness(.9) contrast(.85) saturate(.5); }
:root[data-tema="oscuro"] .leaflet-container { background: var(--surface-muted); }
:root[data-tema="oscuro"] .leaflet-bar a { background: var(--surface); color: var(--text); border-bottom-color: var(--border); }
:root[data-tema="oscuro"] .leaflet-control-attribution { background: rgba(24, 25, 27, .8); color: var(--text-faint); }
:root[data-tema="oscuro"] .leaflet-control-attribution a { color: var(--text-muted); }

/* ---------- Reportar un problema (2026-09-25, js/reportar.js) ----------
   Un modal de la app con las opciones grandes del operario (.op-opcion) para
   "¿Podés seguir trabajando?": lo usan todos los roles, también el operario en
   la calle, y dos opciones que se tocan enteras se entienden sin leer. La
   pregunta se ve como la etiqueta de un campo (3.2), con su asterisco. */
.rp-pregunta > legend { margin-bottom: var(--sp-2); font: var(--t-etiqueta); color: var(--text-muted); }
.rp-pregunta > legend::after { content: " *"; color: var(--red-text); }
.rp-pregunta .field-error { margin-top: var(--sp-2); }
/* El número entero, en un renglón (#95, 2026-10-01): se partía en el guion
   ("VP-261001-" y "F1BD"), y es lo que la persona tiene que guardar. */
.rp-ticket { font-family: var(--font-mono); font-weight: 600; white-space: nowrap; }

/* ===================================================================
   QA crsandbox, bloque 7 (P2, P7, P11 del 2026-09-29).
   =================================================================== */
/* P2 (#12): la novedad de una tarjeta del portal: el punto de los contadores
   (--red-strong) y el texto que dice qué cambió. Solo portal (3.13). */
.pa-novedad { display: inline-flex; align-items: center; gap: var(--sp-2); font: var(--t-tabla); font-weight: 600; color: var(--text); }
.pa-novedad-punto { flex: none; width: 8px; height: 8px; border-radius: var(--radius-full); background: var(--red-strong); }
/* P2: con la sección del menú cerrada, el título lleva la suma de sus contadores. */
.nav-grupo-titulo .nav-badge { margin-left: auto; }
.nav-grupo-titulo[aria-expanded="true"] :is(.nav-badge, [data-avisos-sr]) { display: none; }
/* P7: el bloque de avisos arriba de la orden (la alerta única, 3.6). */
.or-aviso-modal { margin-block: var(--sp-3); }
.or-aviso-lista { margin: 0; padding-left: var(--sp-4); display: flex; flex-direction: column; gap: var(--sp-1); }
.or-aviso-acciones { display: flex; flex-wrap: wrap; gap: var(--sp-2); margin-top: var(--sp-2); }
.or-aviso-acciones:empty { display: none; }
/* P11: la gravedad también en palabras (3.5), en el color del estado. */
.cc-sv-txt { color: var(--red-text); }
.cc-sv-txt-a { color: var(--amber-text); }
@media (max-width: 560px) {
  /* P2: en la tarjeta de Solicitudes el aviso va en el renglón de lo que sigue
     (td.so-extra), no junto al estado de arriba a la derecha (3.10). */
  .tbl.tbl-solicitudes .t-estado .fila-avisos { display: none; }
  .tbl.tbl-solicitudes .so-extra .fila-avisos { margin-top: var(--sp-1); }
}

/* ===================================================================
   QA crsandbox, bloque 8 (P13 y P14 del 2026-09-29).
   =================================================================== */
/* P13 (#66): "¿No está en la lista?" y su botón (8 en la computadora, 12 en el teléfono). */
.pa-no-esta { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2); }
/* P14 (#51): el mensaje recién mandado se tiñe un momento y se apaga (no es un foco). */
@media (prefers-reduced-motion: no-preference) {
  .hilo-msg { transition: background-color var(--dur-2) var(--ease); }
}
/* #42: "Ya está en la lista: …" debajo del "otro" que coincide con el catálogo,
   como la ayuda de un campo con su acción (3.8). */
.pa-otro-sugiere { grid-column: 1 / -1; display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2); }
.pa-otro-sugiere:empty { display: none; }
/* #30: "Cambiar el pedido" y "Cancelar pedido", a 8 (3.10). */
.pa-cancelar-acc { display: flex; flex-wrap: wrap; gap: var(--sp-2); }
@media (max-width: 560px) {
  .pa-cancelar-acc { flex-direction: column; width: 100%; }
  /* P13 (#56): el estado comparte el primer renglón con el nombre, sin taparlo;
     un nombre largo se parte en su espacio. */
  .tbl.tbl-ordenes .t-titulo .fila-titulo { order: 0; flex: 1 1 0; min-width: 0; padding-right: 0; }
  .tbl.tbl-ordenes .t-estado { position: static; order: 0; flex: none; max-width: 50%; margin-left: var(--sp-2); }
  .tbl.tbl-ordenes tr::before { content: ''; order: 1; flex-basis: 100%; height: 0; }
  .pa-no-esta { gap: var(--sp-3); }
}

/* Empezaron sin entrega registrada (2026-10-03, tablet de bodega): el aviso del
   inicio, un renglón por instalación con su botón a la derecha; en el teléfono,
   el botón debajo. */
.bt-sin-entrega { margin-bottom: 0; }
.bt-sin-entrega-lista { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--sp-3); }
.bt-sin-entrega-lista li { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-4); flex-wrap: wrap; }
.bt-sin-entrega-lista .alerta-texto { flex: 1 1 320px; min-width: 0; }

/* Descuadres de bodega (2026-10-03, Centro de control): la lista del modal, un
   descuadre por renglón con "Marcar como revisado" a la derecha; en el
   teléfono, el botón debajo. */
.modal-body .cc-des-lista { list-style: none; margin: var(--sp-3) 0 0; padding: 0; display: flex; flex-direction: column; }
.cc-des-lista li { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-4); padding: var(--sp-3) 0; border-top: 1px solid var(--border-soft); flex-wrap: wrap; }
.cc-des-lista li:first-child { border-top: 0; padding-top: 0; }
.cc-des-texto { display: flex; flex-direction: column; gap: var(--sp-1); flex: 1 1 280px; min-width: 0; }
