/**
 * FAS System CSS — Flow Automation Suite
 * CSS centralizado: variables, reset, componentes, utilidades
 * NO eliminar: este archivo complementa el CSS inline existente
 */

/* ============================================
   MOBILE NAVIGATION — HAMBURGER
   ============================================ */

.mobile-menu-toggle {
  display: none;
  position: fixed;
  top: 12px;
  left: 12px;
  z-index: 1001;
  width: 2.5rem;
  height: 2.5rem;
  padding: 0;
  background: var(--fas-bg-primary, #fff);
  border: 1px solid var(--fas-border-default, #D1D5DB);
  border-radius: var(--fas-radius-lg, 0.5rem);
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: 4px;
  cursor: pointer;
  box-shadow: var(--fas-shadow-md);
  transition: background var(--fas-transition-fast), box-shadow var(--fas-transition-fast);
}
.mobile-menu-toggle:hover { background: var(--fas-bg-secondary, #F9FAFB); }

.hamburger-line {
  width: 1.125rem;
  height: 2px;
  background: var(--fas-text-primary, #111827);
  border-radius: 2px;
  transition: all var(--fas-transition-normal);
  display: block;
}
.mobile-menu-toggle.is-active .hamburger-line:nth-child(1) { transform: translateY(6px) rotate(45deg); }
.mobile-menu-toggle.is-active .hamburger-line:nth-child(2) { opacity: 0; transform: translateX(-8px); }
.mobile-menu-toggle.is-active .hamburger-line:nth-child(3) { transform: translateY(-6px) rotate(-45deg); }

/* Mobile Overlay */
.mobile-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.5);
  -webkit-backdrop-filter: blur(2px);   /* Safari lo necesita prefijado */
  backdrop-filter: blur(2px);
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--fas-transition-normal), visibility var(--fas-transition-normal);
  z-index: 999;
}
.mobile-overlay.is-visible { opacity: 1; visibility: visible; }

/* Mobile Breakpoints */
@media (max-width: 768px) {
  .mobile-menu-toggle { display: flex; }

  /* Sidebar como drawer */
  .sidebar {
    position: fixed !important;
    left: 0 !important;
    top: 0 !important;
    height: 100vh !important;
    height: 100dvh !important;
    width: 280px !important;
    max-width: 85vw !important;
    transform: translateX(-100%) !important;
    transition: transform var(--fas-transition-normal) !important;
    z-index: 1000 !important;
    overflow-y: auto !important;
  }
  .sidebar.is-open { transform: translateX(0) !important; }

  /* Layout: contenido ocupa todo el ancho */
  .layout {
    grid-template-columns: 1fr !important;
    display: block !important;
  }
  .content-area {
    margin-left: 0 !important;
    padding-left: 1rem !important;
    padding-right: 1rem !important;
    padding-top: 4rem !important;
    width: 100% !important;
  }

  /* Topbar: dar espacio al hamburger */
  .topbar {
    padding-left: 3.5rem !important;
  }

  /* Ocultar el collapse button del topbar (sólo para desktop) */
  .topbar > button:first-child {
    display: none !important;
  }
}

/* ============================================
   BOTTOM NAVIGATION
   ============================================ */

.fas-bottom-nav {
  display: none;
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  background: var(--fas-bg-primary, #fff);
  border-top: 1px solid var(--fas-border-light, #E5E7EB);
  padding: 0.375rem 0.5rem;
  padding-bottom: calc(0.375rem + env(safe-area-inset-bottom, 0px));
  z-index: 998;
  box-shadow: 0 -4px 6px -1px rgba(0,0,0,0.08);
}
@media (max-width: 768px) {
  .fas-bottom-nav {
    display: flex;
    justify-content: space-around;
    align-items: center;
  }
  .content-area { padding-bottom: 5rem !important; }
}

.fas-bottom-nav__item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding: 0.375rem 0.5rem;
  color: var(--fas-text-secondary, #4B5563);
  text-decoration: none;
  font-size: 0.625rem;
  font-weight: 600;
  border-radius: var(--fas-radius-lg, 0.5rem);
  transition: all var(--fas-transition-fast);
  min-width: 3.5rem;
  text-align: center;
  background: none;
  border: none;
  cursor: pointer;
  font-family: inherit;
}
.fas-bottom-nav__item:hover,
.fas-bottom-nav__item.active {
  color: var(--accent-texto, #a98ee0);
  background: var(--fas-accent-light, rgba(99,102,241,0.1));
}
.fas-bottom-nav__item svg {
  width: 1.375rem;
  height: 1.375rem;
  flex-shrink: 0;
}
.fas-bottom-nav__item span {
  text-transform: uppercase;
  letter-spacing: 0.02em;
  line-height: 1;
}

/* Dark mode bottom nav */
[data-theme="dark"] .fas-bottom-nav {
  background: var(--bg2, #1E293B);
  border-color: var(--border, #334155);
}

/* ============================================
   RESPONSIVE TABLES
   ============================================ */

@media (max-width: 640px) {
  .fas-table--responsive {
    display: block;
  }
  .fas-table--responsive thead { display: none; }
  .fas-table--responsive tbody {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
  }
  .fas-table--responsive tbody tr {
    display: flex;
    flex-direction: column;
    padding: 1rem;
    background: var(--fas-bg-secondary, #F9FAFB);
    border-radius: var(--fas-radius-lg, 0.5rem);
    border: 1px solid var(--fas-border-light, #E5E7EB);
  }
  .fas-table--responsive td {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 0.375rem 0;
    border: none;
    border-bottom: 1px solid var(--fas-border-light, #E5E7EB);
    font-size: 0.8125rem;
  }
  .fas-table--responsive td:last-child {
    border-bottom: none;
    padding-top: 0.75rem;
    justify-content: flex-end;
    gap: 0.5rem;
  }
  .fas-table--responsive td::before {
    content: attr(data-label);
    font-weight: 600;
    font-size: 0.6875rem;
    color: var(--fas-text-secondary, #4B5563);
    text-transform: uppercase;
    letter-spacing: 0.05em;
    flex-shrink: 0;
    margin-right: 0.5rem;
  }
  .fas-table--responsive td:last-child::before { content: none; }
}

/* ============================================
   CSS VARIABLES
   ============================================ */
/* Estos tokens NO llevan valores propios: apuntan a la paleta de FAS que se
   define mas abajo por tema. Antes duplicaban una paleta distinta —acento
   indigo #6366F1 y superficies pizarra— y eso salia a la vista en cuatro
   lugares que SI se usan (13/08/2026):

     · el anillo de foco de TODA la aplicacion (`:focus-visible`)
     · el foco de los 219 botones al tabular
     · el menu inferior del celular, en 5 pantallas
     · los toasts y los skeletons

   O sea que el resaltado al tabular salia indigo dentro de una aplicacion
   violeta. Apuntando a los tokens reales, la identidad queda en un solo lugar
   y no puede volver a separarse. El valor de respaldo es por si algun dia se
   sirve el CSS sin que el tema este marcado en el <html>. */
:root {
  /* Colores Primarios */
  --fas-accent:        var(--accent, #4b2c80);
  --fas-accent-hover:  var(--accent-lt, #6d45b0);
  --fas-accent-light:  var(--active-bg, rgba(75, 44, 128, 0.12));
  --fas-gold:          var(--gold, #f6bb54);
  --fas-gold-hover:    #e0a63c;

  /* Colores Semánticos — los mismos que usan los badges de estado, para que
     un exito en un toast sea el mismo verde que un badge "Aprobado". */
  --fas-success:       var(--tag-ok-c, #7fd3a9);
  --fas-success-light: var(--tag-ok-bg, rgba(74, 222, 128, 0.1));
  --fas-warning:       var(--tag-warn-c, #f4b06b);
  --fas-warning-light: var(--tag-warn-bg, rgba(251, 191, 36, 0.1));
  --fas-error:         var(--tag-err-c, #f29b91);
  --fas-error-light:   var(--tag-err-bg, rgba(248, 113, 113, 0.1));
  --fas-info:          var(--tag-blue-c, #9fb9ec);
  --fas-info-light:    var(--tag-blue-bg, rgba(96, 165, 250, 0.1));

  /* Superficies */
  --fas-bg-primary:    var(--bg2, #23233a);
  --fas-bg-secondary:  var(--bg3, #2d2d45);
  --fas-bg-tertiary:   var(--bg4, #353555);
  --fas-bg-hover:      var(--bg3, #2d2d45);

  /* Texto */
  --fas-text-primary:   var(--text, #e2e2f0);
  --fas-text-secondary: var(--text2, #9898b8);
  --fas-text-tertiary:  var(--text3, #9494b4);
  --fas-text-inverse:   #FFFFFF;

  /* Bordes */
  --fas-border-light:   var(--border, #3a3a55);
  --fas-border-default: var(--border, #3a3a55);
  --fas-border-dark:    var(--text3, #8888a8);

  /* Sombras */
  --fas-shadow-sm: 0 1px 2px 0 rgba(0,0,0,0.05);
  --fas-shadow-md: 0 4px 6px -1px rgba(0,0,0,0.1), 0 2px 4px -2px rgba(0,0,0,0.1);
  --fas-shadow-lg: 0 10px 15px -3px rgba(0,0,0,0.1), 0 4px 6px -4px rgba(0,0,0,0.1);
  --fas-shadow-xl: 0 20px 25px -5px rgba(0,0,0,0.1), 0 8px 10px -6px rgba(0,0,0,0.1);

  /* Spacing */
  --fas-space-1:  0.25rem;
  --fas-space-2:  0.5rem;
  --fas-space-3:  0.75rem;
  --fas-space-4:  1rem;
  --fas-space-5:  1.25rem;
  --fas-space-6:  1.5rem;
  --fas-space-8:  2rem;
  --fas-space-10: 2.5rem;
  --fas-space-12: 3rem;

  /* Border Radius */
  --fas-radius-sm:   0.25rem;
  --fas-radius-md:   0.375rem;
  --fas-radius-lg:   0.5rem;
  --fas-radius-xl:   0.75rem;
  --fas-radius-full: 9999px;

  /* Transiciones: UN solo juego de tiempos, `--t-fast/--t-normal/--t-slow`
     (definidos más abajo, con la curva `--ease-smooth`). Estos tres nombres
     quedan como alias por los componentes que ya los usaban; había dos
     juegos con tiempos distintos (150/200/300 vs 150/220/360) y 30 duraciones
     sueltas (.1 .12 .14 .15 .18 .2 .22 .25 .3 .35 .4). Normalizado 16/09/2026. */
  --fas-transition-fast:   var(--t-fast);
  --fas-transition-normal: var(--t-normal);
  --fas-transition-slow:   var(--t-slow);

  /* Z-Index */
  --fas-z-dropdown:       100;
  --fas-z-sticky:         200;
  --fas-z-fixed:          300;
  --fas-z-modal-backdrop: 400;
  --fas-z-modal:          500;
  --fas-z-popover:        600;
  --fas-z-tooltip:        700;
  /* Los avisos van por encima de cualquier ventana (29/10/2026, Ana: el
     aviso de «No se puede anular» quedaba detrás del velo de la ventana de
     Anular, que va en 1100). Solo el aviso de sesión por vencer
     (fas-session.js, 99999) queda más arriba. Lo controla
     tests/test_aviso_encima_de_las_ventanas.py. */
  --fas-z-toast:          9000;
}

/* Dark Mode */
/* Aca ya NO se redefinen superficies, textos ni bordes: los de arriba apuntan
   a la paleta de FAS, que cambia sola con el tema. Redefinirlos aca los volvia
   a clavar en pizarra y era lo que hacia que un toast en modo oscuro tuviera
   un fondo distinto al de los paneles que lo rodean. Solo quedan las sombras,
   que si necesitan mas opacidad sobre fondo oscuro. */
[data-theme="dark"] {
  --fas-shadow-sm: 0 1px 2px 0 rgba(0,0,0,0.3);
  --fas-shadow-md: 0 4px 6px -1px rgba(0,0,0,0.4), 0 2px 4px -2px rgba(0,0,0,0.3);
  --fas-shadow-lg: 0 10px 15px -3px rgba(0,0,0,0.4), 0 4px 6px -4px rgba(0,0,0,0.3);
}

/* ============================================
   FOCUS / ACCESIBILIDAD
   ============================================ */
/* UN solo anillo de foco para todo FAS (16/09/2026). Era var(--fas-accent),
   el violeta #4b2c80: 1,4:1 sobre el fondo oscuro, o sea invisible con
   teclado en el tema que usa todo el mundo. --accent-texto es el acento
   legible en cada tema (5,6:1 oscuro, 9,5:1 claro). Con !important en los
   controles porque varias plantillas traen `outline: none` en su propio
   <style>, que carga después y ganaba. El mouse no lo ve
   (:focus:not(:focus-visible)); el teclado y el lector, siempre. */
:focus-visible {
  outline: 2px solid var(--accent-texto, #a98ee0);
  outline-offset: 2px;
}
input:focus-visible, select:focus-visible, textarea:focus-visible,
button:focus-visible, a:focus-visible, [tabindex]:focus-visible, [role="button"]:focus-visible {
  outline: 2px solid var(--accent-texto, #a98ee0) !important;
  outline-offset: 2px;
}
:focus:not(:focus-visible) {
  outline: none;
}
.btn:focus-visible {
  box-shadow: 0 0 0 3px var(--bg, #1b1b2a);
}

/* Skip Link */
.fas-skip-link {
  position: absolute;
  top: -100%;
  left: var(--fas-space-4);
  padding: var(--fas-space-3) var(--fas-space-4);
  background: var(--fas-accent);
  color: var(--fas-text-inverse);
  font-weight: 500;
  border-radius: var(--fas-radius-md);
  z-index: 9999;
  transition: top var(--fas-transition-fast);
  text-decoration: none;
  font-size: 0.875rem;
}
.fas-skip-link:focus {
  top: var(--fas-space-4);
}

/* ============================================
   BOTONES
   ============================================ */
.fas-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--fas-space-2);
  padding: var(--fas-space-2) var(--fas-space-4);
  font-size: 0.875rem;
  font-weight: 500;
  line-height: 1.5;
  border-radius: var(--fas-radius-md);
  border: 1px solid transparent;
  cursor: pointer;
  transition: all var(--fas-transition-fast);
  text-decoration: none;
  font-family: inherit;
}
.fas-btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}
.fas-btn:active:not(:disabled) {
  transform: scale(0.98);
}

.fas-btn--primary {
  background: var(--fas-accent);
  color: var(--fas-text-inverse);
  border-color: var(--fas-accent);
}
.fas-btn--primary:hover:not(:disabled) {
  background: var(--fas-accent-hover);
  border-color: var(--fas-accent-hover);
}
.fas-btn--secondary {
  background: var(--fas-bg-secondary);
  color: var(--fas-text-primary);
  border-color: var(--fas-border-default);
}
.fas-btn--secondary:hover:not(:disabled) {
  background: var(--fas-bg-tertiary);
}
.fas-btn--success {
  background: var(--fas-success);
  color: var(--fas-text-inverse);
}
.fas-btn--success:hover:not(:disabled) {
  background: var(--est-ok-fuerte);
  filter: brightness(1.08);
}
.fas-btn--danger {
  background: var(--fas-error);
  color: var(--fas-text-inverse);
}
.fas-btn--danger:hover:not(:disabled) {
  background: var(--est-err);
}
.fas-btn--ghost {
  background: transparent;
  color: var(--fas-text-secondary);
  border-color: transparent;
}
.fas-btn--ghost:hover:not(:disabled) {
  background: var(--fas-bg-hover);
  color: var(--fas-text-primary);
}
.fas-btn--sm {
  padding: var(--fas-space-1) var(--fas-space-3);
  font-size: 0.75rem;
}
.fas-btn--lg {
  padding: var(--fas-space-3) var(--fas-space-6);
  font-size: 1rem;
}
.fas-btn--icon {
  padding: var(--fas-space-2);
  width: 2.5rem;
  height: 2.5rem;
}
/* La rueda del boton que se acaba de apretar.
   ESTABA ROTA Y NADIE LO HABIA NOTADO porque ninguna pantalla usaba la clase:
   ponia `color: transparent !important` para tapar el texto y dibujaba la
   rueda con `border: 2px solid currentColor` — o sea, transparente tambien.
   El resultado era un boton vacio, sin texto y sin nada girando.
   Se vio el 09/09, al usarla por primera vez de verdad.
   Ahora el texto SE QUEDA —«Cargando…» es informacion, no decoracion— y la
   rueda va a su izquierda, con el color del boton. */
/* `button.` y no `.fas-btn--loading` a secas: `.ent-btn` se define 4.000 lineas
   mas abajo con la misma especificidad, asi que su `padding: 0 16px` ganaba por
   orden y la rueda quedaba montada sobre la primera letra. */
button.fas-btn--loading {
  position: relative;
  padding-left: 2.15rem;
  pointer-events: none;
}
/* Mientras carga no es un boton apagado: es un boton trabajando, y tiene que
   leerse. El `opacity: .5` de `:disabled` lo dejaba casi borrado. */
.ent-btn.fas-btn--loading:disabled, .fas-btn--loading:disabled { opacity: 0.85; }
/* `button.` también acá: `.btn::after` (la onda al apretar) y el `::after` de
   los botones de las pantallas públicas usan el mismo pseudoelemento, con la
   misma especificidad y más abajo en la cascada. Con el `button.` la rueda
   gana, y se anulan a propósito las propiedades de la onda (inset, fondo,
   escala) para que no quede nada de ella debajo. */
button.fas-btn--loading::after {
  content: '';
  position: absolute;
  inset: auto;
  width: 0.85rem;
  height: 0.85rem;
  top: 50%;
  left: 0.85rem;
  margin-top: -0.425rem;
  border: 2px solid currentColor;
  border-right-color: transparent;
  border-radius: 50%;
  background: none;
  transform: none;
  transition: none;
  opacity: 0.9;
  animation: fas-spin 0.6s linear infinite;
}

/* ============================================
   INPUTS
   ============================================ */
.fas-input {
  width: 100%;
  padding: var(--fas-space-3) var(--fas-space-4);
  font-size: 0.875rem;
  line-height: 1.5;
  color: var(--fas-text-primary);
  background: var(--fas-bg-primary);
  border: 1px solid var(--fas-border-default);
  border-radius: var(--fas-radius-md);
  transition: border-color var(--fas-transition-fast), box-shadow var(--fas-transition-fast);
  font-family: inherit;
}
.fas-input:focus {
  border-color: var(--fas-accent);
  box-shadow: 0 0 0 3px var(--fas-accent-light);
  outline: none;
}
.fas-input::placeholder { color: var(--fas-text-tertiary); }
.fas-input:disabled {
  background: var(--fas-bg-secondary);
  cursor: not-allowed;
}

/* ───────────────────────────────────────────────────────────────────────────
   READONLY/DISABLED uniforme global (pedido Mauricio 09/06/2026):
   en TODA la app, los campos en modo "solo visualizar" (factura aprobada,
   pre-aprobada, en cola SAP, error, etc.) deben verse iguales: fondo gris
   suave, sin focus ring, texto un poco mas tenue, cursor por defecto.
   Aplica a todos los inputs/textareas/selects con readonly o disabled,
   sin importar la clase. No afecta editables.

   El buscador de la cabecera queda afuera (QA-F03, 19/10/2026): lleva
   `readonly` como truco contra el autocompletado del navegador, no porque sea
   de solo lectura. Esta regla le ponía fondo y borde y le pisaba la opacidad
   0 del buscador cerrado: quedaba una rayita clara junto a la lupa, en todos
   los módulos.
   ─────────────────────────────────────────────────────────────────────────── */
input[readonly]:not([type="checkbox"]):not([type="radio"]):not(.tb-search-input),
textarea[readonly],
select[disabled],
input[disabled]:not([type="checkbox"]):not([type="radio"]) {
  background: var(--bg2, var(--fas-bg-secondary)) !important;
  border-color: var(--border, #d1d5db) !important;
  color: var(--text2, #475569) !important;
  cursor: default !important;
  box-shadow: none !important;
  opacity: .95;
}
input[readonly]:not(.tb-search-input):focus,
textarea[readonly]:focus,
select[disabled]:focus,
input[disabled]:focus {
  border-color: var(--border, #d1d5db) !important;
  box-shadow: none !important;
  outline: none !important;
}
.fas-input.is-valid   { border-color: var(--fas-success); }
.fas-input.is-invalid { border-color: var(--fas-error); }
.fas-input.is-valid:focus   { box-shadow: 0 0 0 3px var(--fas-success-light); }
.fas-input.is-invalid:focus { box-shadow: 0 0 0 3px var(--fas-error-light); }

.fas-label {
  display: block;
  margin-bottom: var(--fas-space-2);
  font-size: 0.875rem;
  font-weight: 500;
  color: var(--fas-text-primary);
}
.fas-field-error {
  display: block;
  margin-top: var(--fas-space-1);
  font-size: 0.75rem;
  color: var(--fas-error);
}
.fas-field-hint {
  display: block;
  margin-top: var(--fas-space-1);
  font-size: 0.75rem;
  color: var(--fas-text-tertiary);
}

/* ============================================
   CARDS
   ============================================ */
.fas-card {
  background: var(--fas-bg-primary);
  border: 1px solid var(--fas-border-light);
  border-radius: var(--fas-radius-lg);
  box-shadow: var(--fas-shadow-sm);
}
.fas-card--interactive {
  cursor: pointer;
  transition: transform var(--fas-transition-fast), box-shadow var(--fas-transition-fast);
}
.fas-card--interactive:hover {
  transform: translateY(-2px);
  box-shadow: var(--fas-shadow-lg);
}
.fas-card__header {
  padding: var(--fas-space-4) var(--fas-space-6);
  border-bottom: 1px solid var(--fas-border-light);
}
.fas-card__body {
  padding: var(--fas-space-6);
}
.fas-card__footer {
  padding: var(--fas-space-4) var(--fas-space-6);
  border-top: 1px solid var(--fas-border-light);
  background: var(--fas-bg-secondary);
  border-radius: 0 0 var(--fas-radius-lg) var(--fas-radius-lg);
}

/* ============================================
   BADGES
   ============================================ */
.fas-badge {
  display: inline-flex;
  align-items: center;
  padding: var(--fas-space-1) var(--fas-space-2);
  font-size: 0.75rem;
  font-weight: 500;
  line-height: 1;
  border-radius: var(--fas-radius-full);
  white-space: nowrap;
}
.fas-badge--success { background: var(--fas-success-light); color: var(--fas-success); }
.fas-badge--warning { background: var(--fas-warning-light); color: var(--fas-warning); }
.fas-badge--error   { background: var(--fas-error-light);   color: var(--fas-error); }
.fas-badge--info    { background: var(--fas-info-light);    color: var(--fas-info); }
.fas-badge--neutral { background: var(--fas-bg-tertiary);   color: var(--fas-text-secondary); }

/* ============================================
   TABLAS
   ============================================ */
.fas-table {
  width: 100%;
  border-collapse: collapse;
}
.fas-table th,
.fas-table td {
  padding: var(--fas-space-3) var(--fas-space-4);
  text-align: left;
  border-bottom: 1px solid var(--fas-border-light);
}
.fas-table th {
  font-weight: 600;
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--fas-text-secondary);
  background: var(--fas-bg-secondary);
}
.fas-table tbody tr {
  transition: background var(--fas-transition-fast), box-shadow var(--fas-transition-fast);
}
.fas-table tbody tr:hover {
  background: var(--fas-bg-hover);
  box-shadow: inset 3px 0 0 var(--fas-accent);
}
.fas-table .td-empty {
  text-align: center;
  padding: var(--fas-space-8);
  color: var(--fas-text-tertiary);
}

/* ============================================
   ALERTAS
   ============================================ */
.fas-alert {
  display: flex;
  align-items: flex-start;
  gap: var(--fas-space-3);
  padding: var(--fas-space-4);
  border-radius: var(--fas-radius-md);
  border: 1px solid;
  font-size: 0.875rem;
}
.fas-alert--success { background: var(--fas-success-light); border-color: var(--fas-success); color: var(--fas-success); }
.fas-alert--warning { background: var(--fas-warning-light); border-color: var(--fas-warning); color: var(--fas-warning); }
.fas-alert--error   { background: var(--fas-error-light);   border-color: var(--fas-error);   color: var(--fas-error); }
.fas-alert--info    { background: var(--fas-info-light);    border-color: var(--fas-info);    color: var(--fas-info); }
.fas-alert__icon { flex-shrink: 0; width: 1.25rem; height: 1.25rem; }
.fas-alert__content { flex: 1; }
.fas-alert__close {
  flex-shrink: 0;
  padding: var(--fas-space-1);
  background: none;
  border: none;
  cursor: pointer;
  opacity: 0.7;
  transition: opacity var(--fas-transition-fast);
}
.fas-alert__close:hover { opacity: 1; }

/* ============================================
   TOAST NOTIFICATIONS
   ============================================ */
.fas-toast-container {
  position: fixed;
  top: var(--fas-space-4);
  right: var(--fas-space-4);
  z-index: var(--fas-z-toast);
  display: flex;
  flex-direction: column;
  gap: var(--fas-space-2);
  max-width: 24rem;
  pointer-events: none;
}
.fas-toast {
  display: flex;
  align-items: center;
  gap: var(--fas-space-3);
  padding: var(--fas-space-3) var(--fas-space-4);
  background: var(--fas-bg-primary);
  border-radius: var(--fas-radius-lg);
  box-shadow: var(--fas-shadow-lg);
  border-left: 4px solid var(--fas-success);
  transform: translateX(120%);
  opacity: 0;
  transition: transform var(--fas-transition-slow), opacity var(--fas-transition-slow);
  pointer-events: auto;
}
.fas-toast--visible  { transform: translateX(0); opacity: 1; }
.fas-toast--success  { border-left-color: var(--fas-success); }
.fas-toast--error    { border-left-color: var(--fas-error); }
.fas-toast--warning  { border-left-color: var(--fas-warning); }
.fas-toast--info     { border-left-color: var(--fas-info); }
.fas-toast__icon { flex-shrink: 0; width: 1.25rem; height: 1.25rem; }
.fas-toast--success .fas-toast__icon { color: var(--fas-success); }
.fas-toast--error   .fas-toast__icon { color: var(--fas-error); }
.fas-toast--warning .fas-toast__icon { color: var(--fas-warning); }
.fas-toast--info    .fas-toast__icon { color: var(--fas-info); }
.fas-toast__message {
  flex: 1;
  font-size: 0.875rem;
  color: var(--fas-text-primary);
  line-height: 1.4;
}
.fas-toast__close {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 1.5rem;
  height: 1.5rem;
  padding: 0;
  background: none;
  border: none;
  border-radius: var(--fas-radius-sm);
  color: var(--fas-text-tertiary);
  cursor: pointer;
  transition: color var(--fas-transition-fast), background var(--fas-transition-fast);
}
.fas-toast__close:hover { color: var(--fas-text-primary); background: var(--fas-bg-hover); }
.fas-toast__close svg  { width: 1rem; height: 1rem; }

/* Toast enriquecido: mensaje humano + detalle técnico colapsable + acción.
   Patrón de error unificado (P1-02). Se activa con FasToast.errorDetallado(). */
.fas-toast--rich { align-items: flex-start; }
.fas-toast--rich .fas-toast__icon { margin-top: 0.1rem; }
.fas-toast__body { flex: 1; display: flex; flex-direction: column; gap: var(--fas-space-2); }
.fas-toast__detail-toggle {
  align-self: flex-start;
  background: none;
  border: none;
  padding: 0;
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--fas-text-tertiary);
  cursor: pointer;
}
.fas-toast__detail-toggle:hover { color: var(--fas-text-secondary); }
.fas-toast__detail {
  display: none;
  background: var(--fas-bg-hover);
  border-radius: var(--fas-radius-sm);
  padding: var(--fas-space-2) var(--fas-space-3);
  font-family: monospace;
  font-size: 0.72rem;
  color: var(--fas-text-secondary);
  line-height: 1.5;
  white-space: pre-wrap;
  word-break: break-word;
  max-height: 9rem;
  overflow-y: auto;
}
.fas-toast__detail.open { display: block; }
.fas-toast__action {
  align-self: flex-start;
  display: inline-flex;
  align-items: center;
  gap: var(--fas-space-2);
  background: var(--fas-error);
  color: #fff;
  border: none;
  border-radius: var(--fas-radius-sm);
  padding: 0.4rem 0.85rem;
  font-size: 0.8rem;
  font-weight: 600;
  cursor: pointer;
  transition: opacity var(--fas-transition-fast);
}
.fas-toast__action:hover { opacity: 0.88; }

@media (max-width: 480px) {
  .fas-toast-container {
    top: auto;
    bottom: var(--fas-space-4);
    left: var(--fas-space-4);
    right: var(--fas-space-4);
    max-width: none;
  }
  .fas-toast             { transform: translateY(120%); }
  .fas-toast--visible    { transform: translateY(0); }
}

/* ============================================
   CARGA — UN SOLO CONTRATO (16/09/2026)
   ============================================
   Cuatro formas de decir «estoy trabajando», cada una para un caso, y todas
   con el mismo color (desde el 09/10/2026, la barra de luz del login):
     · un BOTÓN que envía   → `fas-btn--loading` (más arriba, con `fasEnviando`)
     · una CAJA que consulta → `fas-cargando` (barra sobre el borde, más abajo)
     · un PROCESO largo con texto (el OCR leyendo el PDF) → `.fas-carga` con
       una `.fas-spinner` adentro; se muestra con `.visible`
     · un CONTENIDO que todavía no llegó → `.fas-skeleton` (sección 4)
   Antes cada pantalla dibujaba la suya: seis `@keyframes spin`, cinco
   `.btn-….loading` con el texto escondido, dos `.upload-spinner` iguales y un
   `.fas-skeleton` muerto con variables que no existían. */
.fas-carga {
  display: none;
  align-items: center;
  justify-content: center;
  gap: 12px;
  padding: 18px;
  background: var(--bg3);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  flex-shrink: 0;
}
.fas-carga.visible { display: flex; }
.fas-carga p { color: var(--text); font-weight: 600; font-size: 13px; }
/* La rueda pasó a ser la barra de luz del login (09/10/2026): un trazo que
   corre de violeta a dorado. La rueda dentro del botón que envía
   (`fas-btn--loading`) se queda: en un botón, una barra no entra. */
.fas-spinner {
  width: 72px;
  height: 4px;
  border: 0;
  border-radius: 4px;
  background: var(--bg4);
  position: relative;
  overflow: hidden;
  flex-shrink: 0;
}
.fas-spinner::after {
  content: '';
  position: absolute;
  top: 0; bottom: 0; left: 0;
  width: 45%;
  border-radius: inherit;
  background: var(--marca-luz, linear-gradient(90deg, transparent, #a78bfa, #f6bb54, transparent));
  animation: fas-luz-viaja 1.2s ease-in-out infinite;
}
@keyframes fas-luz-viaja {
  from { transform: translateX(-110%); }
  to   { transform: translateX(230%); }
}
/* Con movimiento reducido, quieta y visible, como la barra de las tarjetas. */
@media (prefers-reduced-motion: reduce) {
  .fas-spinner::after { animation: none; transform: none; width: 100%; opacity: .6; }
}
/* Texto de espera dentro de una lista desplegable («Buscando OC … en SAP»). */
.fas-carga-texto { padding: 12px 14px; font-size: 12px; color: var(--text3); text-align: center; }

/* ============================================
   EMPTY STATES
   ============================================ */
.fas-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: var(--fas-space-12);
  text-align: center;
}
.fas-empty__icon        { width: 4rem; height: 4rem; margin-bottom: var(--fas-space-4); color: var(--fas-text-tertiary); }
.fas-empty__title       { margin: 0 0 var(--fas-space-2); font-size: 1.125rem; font-weight: 600; color: var(--fas-text-primary); }
.fas-empty__description { margin: 0 0 var(--fas-space-6); font-size: 0.875rem; color: var(--fas-text-secondary); max-width: 24rem; }

/* ============================================
   ANIMACIONES
   ============================================ */
@keyframes fas-spin             { to { transform: rotate(360deg); } }

/* ── BARRA DE CARGA ─────────────────────────────────────────────────────────
   Pedido de Mauricio el 09/09/2026: «quiero un spinner así como en SAP o una
   barra, algo más llamativo». Y con el motivo puesto: hoy las consultas
   vuelven rápido, pero el día que haya varias a la vez la persona tiene que
   ver que el sistema está trabajando y no que se colgó.

   Va sobre el contenedor que carga (la tarjeta de la tabla): se pone la clase
   `fas-cargando` y aparece una barra que recorre el borde de arriba. Sirve
   para cualquier pantalla, no solo para Trazabilidad.

   `::before` y no un elemento nuevo: así no hay que tocar el HTML de cada
   tabla ni acordarse de sacarlo. */
.fas-cargando { position: relative; }
.fas-cargando::before {
    content: '';
    position: absolute;
    top: 0; left: 0; right: 0;
    height: 3px;
    border-radius: 3px 3px 0 0;
    /* La barra de luz del login (09/10/2026): violeta que pasa a dorado. */
    background: linear-gradient(90deg,
                transparent 0%, var(--marca-luz-1, #8b6fd6) 40%,
                var(--gold, #f6bb54) 62%, transparent 100%);
    background-size: 45% 100%;
    background-repeat: no-repeat;
    animation: fas-barra-carga 1.15s ease-in-out infinite;
    z-index: 3;
}
@keyframes fas-barra-carga {
    0%   { background-position: -50% 0; }
    100% { background-position: 150% 0; }
}

/* Con movimiento reducido la barra NO desaparece: se queda quieta y visible.
   La regla global de `prefers-reduced-motion` deja la animación en 0.01ms, y
   sin esto quedaría en su último fotograma —fuera de la vista— y la persona
   perdería el único aviso de que algo está cargando. */
@media (prefers-reduced-motion: reduce) {
    .fas-cargando::before {
        animation: none;
        background: var(--accent, #4b2c80);
        background-size: 100% 100%;
        opacity: .55;
    }
}
@keyframes fas-fadeIn          { from { opacity: 0; } to { opacity: 1; } }
@keyframes fas-fadeInUp         { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
@keyframes fas-fadeInDown       { from { opacity: 0; transform: translateY(-10px); } to { opacity: 1; transform: translateY(0); } }
@keyframes fas-scaleIn          { from { opacity: 0; transform: scale(0.95); } to { opacity: 1; transform: scale(1); } }

.fas-animate-fade-in    { animation: fas-fadeIn    var(--fas-transition-normal) ease-out; }
.fas-animate-fade-in-up { animation: fas-fadeInUp  var(--fas-transition-normal) ease-out; }
.fas-animate-scale-in   { animation: fas-scaleIn   var(--fas-transition-normal) ease-out; }

/* Hover effects */
.fas-card--interactive:hover  { transform: translateY(-2px); box-shadow: var(--fas-shadow-lg); }
.fas-link-animated            { position: relative; text-decoration: none; }
.fas-link-animated::after     { content: ''; position: absolute; bottom: -2px; left: 0; width: 0; height: 2px; background: currentColor; transition: width var(--fas-transition-normal); }
.fas-link-animated:hover::after { width: 100%; }

/* ============================================
   UTILIDADES
   ============================================ */
.fas-flex          { display: flex; }
.fas-flex-col      { flex-direction: column; }
.fas-items-center  { align-items: center; }
.fas-items-start   { align-items: flex-start; }
.fas-justify-center  { justify-content: center; }
.fas-justify-between { justify-content: space-between; }
.fas-justify-end     { justify-content: flex-end; }
.fas-gap-1 { gap: var(--fas-space-1); }
.fas-gap-2 { gap: var(--fas-space-2); }
.fas-gap-3 { gap: var(--fas-space-3); }
.fas-gap-4 { gap: var(--fas-space-4); }
.fas-gap-6 { gap: var(--fas-space-6); }

.fas-text-center  { text-align: center; }
.fas-text-right   { text-align: right; }
.fas-text-sm      { font-size: 0.875rem; }
.fas-text-xs      { font-size: 0.75rem; }
.fas-text-lg      { font-size: 1.125rem; }
.fas-font-medium  { font-weight: 500; }
.fas-font-bold    { font-weight: 700; }
.fas-text-muted   { color: var(--fas-text-tertiary); }

.fas-sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0,0,0,0);
  white-space: nowrap;
  border: 0;
}

/* ============================================
   PRINT
   ============================================ */
@media print {
  .sidebar, .header, .mobile-menu-toggle, .mobile-overlay,
  .fas-toast-container, .fas-skip-link {
    display: none !important;
  }
  .main-content { margin: 0 !important; padding: 0 !important; width: 100% !important; }
  * { color: #000 !important; background: #fff !important; box-shadow: none !important; }
  .fas-table    { border: 1px solid #000; }
  .fas-table th, .fas-table td { border: 1px solid #ccc; }
  .fas-badge    { border: 1px solid currentColor !important; background: transparent !important; }
}

/* ============================================
   REDUCED MOTION
   ============================================ */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

/* ============================================================
   SHARED TEMPLATE CSS — Design tokens y componentes comunes
   Extraído de compras / admin / proveedor / revisar
   ============================================================ */

/* ── Design Tokens ── */
:root {
    --accent:        #4b2c80;
    --accent-lt:     #6d45b0;
    /* El acento como TEXTO sobre el fondo del tema. --accent (#4b2c80) es
       1,4:1 sobre el fondo oscuro: sirve de fondo de botón, no de color de
       letra. Este da 5,6 sobre --bg2 (16/09/2026, axe-core). */
    --accent-texto:  #a98ee0;
    --gold:          #f6bb54;
    --radius:        8px;
    --sidebar-w:     220px;
    /* Botones llenos de estado, con letra blanca: iguales en los dos temas
       (blanco sobre cada uno: 5,3 · 4,5 · 5,8 · 7,0). Hasta el 28/10/2026
       eran los de Tailwind (#10b981, #f59e0b, #ef4444): 2,5 a 3,8 con blanco. */
    --est-ok-fuerte:   #23795a;
    --est-warn-fuerte: #b4600f;
    --est-err-fuerte:  #b53a33;
    --est-info-fuerte: #35589a;
}
[data-theme="dark"] {
    /* La barra de scroll es un CONTROL, no un fondo: token propio. Con --bg4
       quedaba del color del panel y desaparecia.
       Va con el violeta de FAS, no gris: es lo que la hace encontrar de un
       vistazo. Medido 4,32:1 contra el fondo y 3,89:1 contra el panel —
       WCAG pide 3:1 para un control que no es texto. */
    --scroll-thumb:  #8b6fd6;
    --scroll-hover:  #a894e8;
    --scroll-track:  #2c2c46;
    --bg:            #1b1b2a;
    --bg2:           #23233a;
    --bg3:           #2d2d45;
    --bg4:           #353555;
    --sidebar-bg:    #161625;
    --topbar-bg:     #111120;
    --border:        #3a3a55;
    --text:          #e2e2f0;
    --text2:         #9898b8;
    /* text3 era #8888a8: 3,9:1 sobre --bg3, por debajo del 4,5 de WCAG AA.
       axe-core lo marcó en todas las pantallas (16/09/2026). #9494b4 da 4,55
       sobre --bg3 y 5,8 sobre --bg, y sigue más apagado que text2. */
    --text3:         #9494b4;
    --hover-bg:      rgba(255,255,255,.05);
    --active-bg:     rgba(75,44,128,.35);
    --active-border: #4b2c80;
    --panel-bg:      #1e1e30;
    --input-bg:      #2a2a40;
    --input-border:  #44446a;
    --shadow:        0 4px 20px rgba(0,0,0,.4);
    --zebra-bg:      rgba(255,255,255,.025);
    /* La paleta de estados, de la marca (28/10/2026): era la de Tailwind tal
       cual y se veía genérica (Mauri). Verde, ámbar y ladrillo templados, con
       el violeta y el naranja de FAS. Cada texto da 7:1 o más sobre su
       etiqueta y sobre la tarjeta; `test_contraste_temas.py` lo mide. */
    --tag-ok-bg:     #173a2d; --tag-ok-c:    #7fd3a9;
    --tag-warn-bg:   #3d2a14; --tag-warn-c:  #f4b06b;
    --tag-err-bg:    #3e1e1f; --tag-err-c:   #f29b91;
    --tag-blue-bg:   #1d2a44; --tag-blue-c:  #9fb9ec;
    --tag-pur-bg:    #2c2244; --tag-pur-c:   #c4b0f0;
    --tag-login-bg:  #2a2a40; --tag-login-c: #b0b0d0;
    /* El mismo estado como raya, punto o barra (3:1 o más sobre la tarjeta). */
    --est-ok:        #52b487;
    --est-warn:      #e8964a;
    --est-err:       #e06a5f;
    --est-info:      #6f93d6;
    --est-neutro:    #7d7f9c;
}
[data-theme="light"] {
    /* El violeta de FAS. 7,21:1 contra el fondo y 8,08:1 contra el panel. */
    --scroll-thumb:  #5f3aa0;
    --scroll-hover:  #4b2c80;
    --scroll-track:  #e4e4f2;
    --bg:            #f0f2f7;
    --bg2:           #ffffff;
    --bg3:           #f8f8fc;
    --bg4:           #ededf8;
    --sidebar-bg:    #1e1e2e;
    --topbar-bg:     #1a1a2a;
    --border:        #e2e2ee;
    --text:          #1a1a2e;
    --text2:         #555570;
    /* text3 era #9090a8: 2,8:1 sobre --bg claro. #66667f da 4,97 sobre el
       fondo y 5,57 sobre blanco (16/09/2026). */
    --text3:         #66667f;
    --hover-bg:      rgba(75,44,128,.06);
    --active-bg:     rgba(75,44,128,.12);
    --active-border: #4b2c80;
    --panel-bg:      #ffffff;
    --input-bg:      #ffffff;
    --input-border:  #d1d5db;
    --shadow:        0 4px 20px rgba(0,0,0,.1);
    --zebra-bg:      rgba(0,0,0,.025);
    /* La paleta de estados, de la marca (28/10/2026; ver el tema oscuro). */
    --tag-ok-bg:     #e1f1e8; --tag-ok-c:    #1d6a4c;
    --tag-warn-bg:   #fcecd8; --tag-warn-c:  #8a4b0f;
    --tag-err-bg:    #f9e2df; --tag-err-c:   #9c2a26;
    --tag-blue-bg:   #e2e8f5; --tag-blue-c:  #2c4b85;
    --tag-pur-bg:    #ece6f6; --tag-pur-c:   #4b2c80;
    --tag-login-bg:  #eeeff3; --tag-login-c: #44465a;
    --est-ok:        #2f8a63;
    --est-warn:      #c26a17;
    --est-err:       #c8423b;
    --est-info:      #4a6fb5;
    --est-neutro:    #8a8ca3;
    --accent-texto:  #4b2c80;   /* 9,5:1 sobre el fondo claro */
}

/* ── Reset base ── */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html { height: 100%; }
body {
    font-family: 'Inter', 'Segoe UI', system-ui, sans-serif;
    background: var(--bg);
    color: var(--text);
    display: flex;
    flex-direction: column;
    font-size: 14px;
    transition: background var(--t-normal), color var(--t-normal);
    height: 100vh;
    /* En Safari de iPhone, 100vh es MAS alto que la pantalla visible (no
       descuenta la barra del navegador). Con overflow:hidden el fondo de la
       app quedaba tapado por esa barra y no habia forma de scrollear para
       recuperarlo — se comia la navegacion inferior. 100dvh mide el alto real;
       los navegadores que no lo entienden ignoran la linea y usan el 100vh. */
    height: 100dvh;
    overflow: hidden;
    margin: 0;
}

/* ── Topbar ── */
.topbar {
    height: 48px;
    background: var(--topbar-bg);
    display: flex; align-items: center;
    padding: 0 16px; gap: 12px;
    flex-shrink: 0; z-index: 200; position: relative;
    box-shadow: 0 1px 0 rgba(255,255,255,.06);
}
.topbar-title { font-size: 14px; font-weight: 600; color: #fff; letter-spacing: .2px; flex: 1; }
.topbar-right { display: flex; align-items: center; gap: 6px; }
.topbar-role-chip {
    font-size: 10px; font-weight: 700;
    background: rgba(246,187,84,.15);
    border: 1px solid rgba(246,187,84,.3);
    color: var(--gold);
    border-radius: 20px; padding: 2px 9px;
    text-transform: uppercase; letter-spacing: .6px;
    flex-shrink: 0; white-space: nowrap;
}
.tb-icon-btn {
    width: 34px; height: 34px;
    background: none; border: none;
    border-radius: 6px; color: rgba(255,255,255,.7);
    cursor: pointer;
    display: flex; align-items: center; justify-content: center;
    transition: background var(--t-fast), color var(--t-fast);
    position: relative;
}
.tb-icon-btn:hover { background: rgba(255,255,255,.1); color: #fff; }
.tb-search-wrap { display: flex; align-items: center; }
.tb-search-input {
    width: 0; max-width: 220px; overflow: hidden; opacity: 0;
    background: rgba(255,255,255,.1); border: 1px solid transparent;
    border-radius: 20px; color: #fff; font-size: 13px; outline: none;
    padding: 0; margin: 0;
    transition: width var(--t-normal), opacity var(--t-normal), padding var(--t-normal), border-color var(--t-normal);
}
.tb-search-wrap.open .tb-search-input {
    width: 200px; opacity: 1;
    padding: 5px 14px; border-color: rgba(255,255,255,.2);
    margin-right: 4px;
}
.tb-search-wrap.open .tb-search-input:focus { border-color: var(--gold); }
.tb-search-input::placeholder { color: rgba(255,255,255,.45); }
.avatar-btn {
    width: 32px; height: 32px; border-radius: 50%;
    background: var(--accent); color: white;
    font-size: 12px; font-weight: 700;
    border: 2px solid var(--gold); cursor: pointer;
    display: flex; align-items: center; justify-content: center;
    transition: opacity var(--t-fast);
}
.avatar-btn:hover { opacity: .85; }

/* ── Profile menu ── */
.profile-menu {
    position: fixed; top: 48px; right: 12px;
    width: 240px; background: var(--bg2);
    border: 1px solid var(--border); border-radius: var(--radius);
    box-shadow: var(--shadow); z-index: 500;
    overflow: hidden; display: none; animation: dropIn .18s ease;
}
.profile-menu.open { display: block; }
@keyframes dropIn { from { opacity:0; transform:translateY(-8px); } to { opacity:1; transform:none; } }
.pm-header { padding: 14px 16px; border-bottom: 1px solid var(--border); display: flex; align-items: center; gap: 10px; }
.pm-avatar { width: 38px; height: 38px; border-radius: 50%; background: var(--accent); color: white; font-size: 14px; font-weight: 700; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
.pm-name { font-size: 13px; font-weight: 700; color: var(--text); }
.pm-role { font-size: 11px; color: var(--text2); }
.pm-item { display: flex; align-items: center; gap: 10px; padding: 10px 16px; cursor: pointer; font-size: 13px; color: var(--text); transition: background var(--t-fast); border: none; background: none; width: 100%; text-align: left; text-decoration: none; }

/* Cerrar sesión es un formulario POST (15/09/2026): el <form> no ocupa lugar
   y el botón se ve igual que el enlace que era. */
.fas-logout { display: contents; }
button.pm-item, button.nav-item { font: inherit; }

/* La página de confirmación de cierre de sesión (GET /logout) usa el diseño
   del acceso desde el 09/10/2026: static/css/fas-acceso.css. */
.pm-item:hover { background: var(--hover-bg); }
/* El rojo de texto del tema: #f87171 daba 2,8:1 sobre el menú claro (14/10/2026). */
.pm-item.danger { color: var(--tag-err-c, #f87171); }
.pm-item-icon { width: 18px; height: 18px; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
.pm-item-icon svg { width: 15px; height: 15px; }
.pm-divider { border: none; border-top: 1px solid var(--border); margin: 4px 0; }
.pm-toggle-row { display: flex; align-items: center; justify-content: space-between; padding: 9px 16px; font-size: 13px; color: var(--text); }
.toggle-switch { width: 38px; height: 20px; background: var(--bg4); border-radius: 10px; position: relative; cursor: pointer; transition: background var(--t-normal); flex-shrink: 0; border: none; }
.toggle-switch.on { background: var(--accent); }
.toggle-switch::after { content: ''; position: absolute; width: 14px; height: 14px; background: white; border-radius: 50%; top: 3px; left: 3px; transition: transform var(--t-normal); }
.toggle-switch.on::after { transform: translateX(18px); }

/* ── Layout principal ── */
.layout {
    flex: 1;
    display: grid;
    grid-template-columns: var(--sidebar-w) 1fr;
    overflow: hidden;
    min-height: 0;
    /* SIN transicion en `grid-template-columns`, a proposito.
       Se probo el 31/08/2026 y se saco el mismo dia: Mauricio lo miro en
       testing y dijo *"el de transicion no funciona muy bien"*. Tenia razon.
       Animar una columna del grid obliga al navegador a recalcular el ancho
       de TODA la tabla en cada cuadro, y con la grilla de ingreso abierta se
       entrecorta. Se ve peor animado que saltando.
       La barra lateral si conserva su propia transicion de ancho (0,25s), que
       es la que se nota y no cuesta nada. */
}

/* ── Sidebar ── */

.sidebar {
    background: var(--sidebar-bg);
    display: flex; flex-direction: column;
    overflow-y: auto;
    border-right: 1px solid rgba(255,255,255,.06);
    z-index: 100;
    transition: width 0.25s cubic-bezier(.25,.8,.25,1);
    width: var(--sidebar-w);
    /* `overflow: hidden` a secas anulaba el `overflow-y: auto` de arriba y
       dejaba el menú SIN scroll: con los ~10 ítems de Admin, los últimos
       quedaban fuera de alcance. Lo que se quería ocultar es el desborde
       HORIZONTAL al colapsar la barra, no el vertical.
       Lo encontró stylelint en su primera corrida (07/08/2026). */
    overflow-x: hidden;
    flex-shrink: 0;
}

/* El menu de modulos NO lleva barra de scroll a la vista. La barra violeta
   funciona en una tabla o en un documento largo; pegada a la lista de modulos,
   sobre el fondo oscuro de la barra lateral, queda como un tajo al costado del
   menu — reportado el 25/08/2026 con captura.
   Igual sigue scrolleando: con los ~10 items de Admin, en una pantalla baja los
   ultimos no entran. Lo que avisa que hay mas es la sombra de abajo, que se
   apaga sola al llegar al final — la misma tecnica que las tablas anchas.
   VA DESPUES de la regla de arriba a proposito: su `background` pisaria este. */
.sidebar {
    scrollbar-width: none;
    --scroll-fade: var(--sidebar-bg);
    background:
        linear-gradient(var(--scroll-fade) 40%, transparent)
            center top / 100% 20px no-repeat local,
        linear-gradient(transparent, var(--scroll-fade) 60%)
            center bottom / 100% 20px no-repeat local,
        radial-gradient(farthest-side at 50% 100%,
            rgb(0 0 0 / 40%), transparent) center bottom / 100% 10px no-repeat scroll,
        var(--sidebar-bg);
}
.sidebar::-webkit-scrollbar { display: none; }
.sidebar-logo-wrap {
    padding: 14px 12px 10px;
    border-bottom: 1px solid rgba(255,255,255,.06);
    display: flex; align-items: center; justify-content: center;
}
/* El logo de FAS ya trae su propio cuadro redondeado (mismo icono que la
   pestaña), así que no lleva borde ni fondo extra: se veía como caja doble. */
.sidebar-logo { width: 46px; height: 46px; object-fit: contain; border-radius: 10px;
                transition: transform var(--t-fast); }
.sidebar-logo-wrap:hover .sidebar-logo { transform: scale(1.06); }
.sidebar-section-title { font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: .8px; color: rgba(255,255,255,.3); padding: 16px 16px 6px; }
.nav-item {
    display: flex; align-items: center; gap: 10px; padding: 9px 16px; cursor: pointer;
    font-size: 13px; font-weight: 500; color: rgba(255,255,255,.65);
    border: none; border-left: 3px solid transparent;
    transition: background var(--t-fast), color var(--t-fast), border-color var(--t-fast);
    text-decoration: none; background: none; width: 100%; text-align: left;
}
.nav-item:hover { background: rgba(255,255,255,.06); color: #fff; }
.nav-item.active { background: rgba(75,44,128,.3); color: #fff; border-left-color: var(--gold); }
.nav-item .nav-icon { width: 20px; height: 20px; display: flex; align-items: center; justify-content: center; flex-shrink: 0; opacity: .8; }
.nav-item.active .nav-icon { opacity: 1; }
.nav-item .nav-icon svg { width: 16px; height: 16px; flex-shrink: 0; }
.nav-dot-alert { margin-left: auto; width: 7px; height: 7px; background: var(--est-err); border-radius: 50%; flex-shrink: 0; }
.sidebar-footer { margin-top: auto; padding: 12px 0; border-top: 1px solid rgba(255,255,255,.06); }

/* Bloque de versión y empresas en sidebar-footer */
.sidebar-version-block {
    padding: 8px 14px 4px;
    display: flex;
    flex-direction: column;
    gap: 4px;
}
.sidebar-version-row {
    display: flex;
    align-items: center;
    gap: 6px;
}
.sidebar-version-badge {
    font-size: 9px;
    font-weight: 800;
    letter-spacing: .06em;
    color: var(--accent-texto);
    background: rgba(75,44,128,.18);
    border: 1px solid rgba(75,44,128,.35);
    border-radius: 4px;
    padding: 1px 5px;
    line-height: 1.5;
    text-transform: uppercase;
    flex-shrink: 0;
}
.sidebar-version-badge.env-test {
    /* El naranja de la marca, el mismo de la franja TESTING (28/10/2026). */
    color: #f0943e;
    background: rgba(240,148,62,.12);
    border-color: rgba(240,148,62,.4);
}
body.sidebar-collapsed .sidebar-version-block { display: none; }

/* Sidebar colapsable */
body.sidebar-collapsed { --sidebar-w: 52px; }
body.sidebar-collapsed .layout { grid-template-columns: 52px 1fr; }
body.sidebar-collapsed .sidebar-logo-wrap { padding: 10px 8px; justify-content: center; }
body.sidebar-collapsed .sidebar-logo-wrap img { width: 34px; height: 34px; max-height: none; max-width: none; }
body.sidebar-collapsed .sidebar-section-title { font-size: 0; padding: 4px 0; min-height: 0; overflow: hidden; }
body.sidebar-collapsed .nav-item { font-size: 0; padding: 10px 0; justify-content: center; gap: 0; }
body.sidebar-collapsed .nav-item .nav-icon { flex-shrink: 0; }
body.sidebar-collapsed .nav-dot-alert { display: none; }
body.sidebar-collapsed .sidebar-footer .nav-item { font-size: 0; padding: 10px 0; justify-content: center; gap: 0; }
body.sidebar-collapsed .sidebar-footer .nav-item .nav-icon { flex-shrink: 0; }

/* ── Content area ── */
.content-area {
    overflow: hidden; display: flex; flex-direction: column;
    min-width: 0; width: 100%;
}
.page {
    display: none; flex-direction: column;
    overflow: hidden; min-height: 0; width: 100%;
}
.page.active { display: flex; flex: 1; }
.page > * { width: 100%; }
.dash-scroll { flex: 1; overflow-y: auto; padding: 20px 24px; width: 100%; }
.page-topbar {
    padding: 14px 24px; flex-shrink: 0;
    display: flex; align-items: center; justify-content: space-between;
    flex-wrap: wrap; gap: 10px;
    border-bottom: 1px solid var(--border); background: var(--bg);
}
.page-breadcrumb { font-size: 12px; color: var(--text2); margin-bottom: 4px; }
.page-breadcrumb span { color: var(--text3, var(--text2)); }
.bc-link { background: none; border: none; color: var(--text2); font-size: 12px; font-family: inherit; cursor: pointer; padding: 0; transition: color var(--t-fast); }
.bc-link:hover { color: var(--accent-texto); }
/* Boton Volver + separador del topbar (compartidos: Revisar / Rotulos / Entradas) */
.btn-back-top {
    display: inline-flex; align-items: center; gap: 7px;
    background: rgba(255,255,255,.1); border: 1px solid rgba(255,255,255,.15);
    color: rgba(255,255,255,.85); padding: 5px 12px; border-radius: 6px;
    font-size: 12px; font-weight: 600; text-decoration: none; cursor: pointer;
    transition: background var(--t-fast); white-space: nowrap; flex-shrink: 0;
}
.btn-back-top:hover { background: rgba(255,255,255,.18); color: #fff; }
.topbar-sep { width: 1px; height: 20px; background: rgba(255,255,255,.15); flex-shrink: 0; }
.page-title { font-size: 20px; font-weight: 700; color: var(--text); margin: 0; }   /* es <h1> desde el 16/09/2026: sin el margen del navegador */
.page-subtitle { font-size: 12px; color: var(--text2); margin-top: 2px; }
.page-actions { display: flex; gap: 8px; align-items: center; }

/* ── Flash ── */
.flash-area { position: fixed; top: 64px; left: 50%; transform: translateX(-50%); z-index: 1000; display: flex; flex-direction: column; gap: 6px; min-width: 320px; max-width: 560px; pointer-events: none; }
.flash { padding: 10px 14px; border-radius: 6px; font-size: 13px; font-weight: 600; display: flex; align-items: center; gap: 8px; pointer-events: auto; box-shadow: 0 4px 12px rgba(0,0,0,.15); }
.flash-success { background: var(--tag-ok-bg);   color: var(--tag-ok-c);   border: 1px solid var(--tag-ok-c); }
.flash-error   { background: var(--tag-err-bg);  color: var(--tag-err-c);  border: 1px solid var(--tag-err-c); }
.flash-warning { background: var(--tag-warn-bg); color: var(--tag-warn-c); border: 1px solid var(--tag-warn-c); }

/* ── Botones (.btn system) ── */
.btn { display: inline-flex; align-items: center; gap: 6px; padding: 7px 16px; border: none; border-radius: 6px; font-size: 13px; font-weight: 600; cursor: pointer; transition: opacity var(--t-fast), transform var(--t-fast); white-space: nowrap; text-decoration: none; font-family: inherit; }
.btn:hover { opacity: .85; }
.btn:active { transform: scale(.97); }
.btn-primary { background: var(--accent); color: white; }
.btn-success  { background: var(--est-ok-fuerte); color: white; }
.btn-warning  { background: var(--est-warn-fuerte); color: white; }
.btn-danger   { background: var(--est-err-fuerte); color: white; }
.btn-ghost    { background: var(--bg3); color: var(--text); border: 1px solid var(--border); }
.btn-sm  { padding: 5px 12px; font-size: 12px; }
.btn-xs  { padding: 3px 8px;  font-size: 11px; }

/* ── Cards (.card system) ── */
.card { background: var(--bg2); border-radius: var(--radius); border: 1px solid var(--border); margin-bottom: 20px; overflow: hidden; }
.card-header { display: flex; justify-content: space-between; align-items: center; padding: 14px 18px; border-bottom: 1px solid var(--border); }
.card-header h2 { margin: 0; font-size: 15px; font-weight: 700; color: var(--text); display: flex; align-items: center; gap: 8px; }
.card-header h2 svg { opacity: .6; }

/* ── Action bar ── */

/* ── Search bar ── */
.search-bar { display: flex; align-items: center; gap: 10px; padding: 10px 18px; border-bottom: 1px solid var(--border); }
.search-input-wrap { position: relative; flex: 1; max-width: 340px; }
.search-input-wrap svg { position: absolute; left: 10px; top: 50%; transform: translateY(-50%); color: var(--text3); pointer-events: none; }
.search-input { width: 100%; padding: 7px 12px 7px 32px; background: var(--input-bg); border: 1px solid var(--input-border); border-radius: 20px; font-size: 13px; font-family: inherit; color: var(--text); outline: none; transition: border-color var(--t-normal); }
.search-input:focus { border-color: var(--accent); }
.search-input::placeholder { color: var(--text3); }
.search-count { font-size: 12px; color: var(--text2); white-space: nowrap; }

/* ── Data table ── */
.data-table { width: 100%; border-collapse: separate; border-spacing: 0; font-size: 13px; }
.data-table thead th {
    background: linear-gradient(180deg, var(--bg3) 0%, var(--bg2) 100%);
    color: var(--text2);
    padding: 12px 16px;
    font-size: 11px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .6px;
    white-space: nowrap;
    border-bottom: 2px solid var(--border);
    text-align: left;
    vertical-align: middle;
    position: sticky;
    top: 0;
    z-index: 2;
    box-shadow: 0 1px 0 var(--border);
}
/* Pegado al borde del scroll (14/10/2026). En Depósito el contenedor que
   scrollea tiene margen arriba, y un sticky con top:0 se traba a esa
   distancia del borde: por la franja de arriba se veían pasar las filas y la
   primera quedaba medio tapada (Entradas registradas). Se compensa con el
   mismo margen, en negativo. */
.ent-scroll .data-table thead th,
.rot-scroll .data-table thead th { top: calc(-1 * var(--scroll-pad-arriba, 0px)); }
/* Helpers para alinear headers con su columna */
.data-table thead th.th-right  { text-align: right; }
.data-table thead th.th-center { text-align: center; }
.data-table tbody tr {
    border-bottom: 1px solid var(--border);
    transition: background var(--t-fast), box-shadow var(--t-fast);
}
.data-table tbody tr:nth-child(even) { background: var(--zebra-bg); }
.data-table tbody tr:hover {
    background: var(--hover-bg);
    box-shadow: inset 4px 0 0 var(--accent), 0 2px 8px rgba(0,0,0,.08);
}
.data-table tbody tr:last-child { border-bottom: none; }
.data-table td {
    padding: 14px 16px;
    color: var(--text);
    vertical-align: middle;
    text-align: left;          /* Default — todos los <td> alineados a la izquierda */
}

/* Fecha — principal grande, subtitulo "Cargada:" mas chico */
.td-date {
    color: var(--text);
    font-family: 'SF Mono', 'Consolas', 'Courier New', monospace;
    font-size: 13px;
    font-weight: 600;
    line-height: 1.45;
    white-space: nowrap;
}
.td-date div {
    font-weight: 400;
    font-size: 11px;
    color: var(--text3);
    margin-top: 2px;
}

/* Proveedor + RUC — bloque dos lineas alineadas */
.td-ruc { font-size: 12px; color: var(--text2); }

/* N Factura — destacado con color accent */
.td-factura {
    font-weight: 700;
    color: var(--accent-texto);
    font-size: 13.5px;
    letter-spacing: .2px;
    line-height: 1.45;
    /* El numero de comprobante NO se parte. Con la columna apretada, Chrome
       cortaba «001-002-0001537» por el guion y lo dejaba en dos renglones:
       cada fila pasaba a doble alto y el numero —que es lo que se busca al
       escanear la lista— quedaba partido a la mitad (26/08/2026). */
    white-space: nowrap;
}
.td-factura div {
    font-weight: 500;
    font-size: 11px;
    color: var(--text3);
    margin-top: 2px;
    letter-spacing: normal;
}
[data-theme="light"] .td-factura { color: var(--accent-texto); }

/* OC — numero verde con un dot sutil al frente para que se identifique */
.td-oc {
    color: var(--tag-ok-c);
    font-weight: 700;
    font-size: 13px;
    font-variant-numeric: tabular-nums;
}

/* Monto — estilo extracto bancario. Moneda y monto del mismo peso y
   tamano similar, pegados con gap chico, ambos al borde derecho. */
.td-monto {
    text-align: right;
    font-variant-numeric: tabular-nums;
    font-size: 13.5px;
    color: var(--text);
    font-weight: 600;
    white-space: nowrap;
    padding-right: 20px !important;  /* un poco mas de aire al borde */
}
.td-monto strong {
    color: var(--text2);
    font-weight: 600;
    font-size: 12.5px;
    margin-right: 4px;
    text-transform: uppercase;
}
/* Header MONTO con mismo padding-right para alinear exactamente con la celda */
.data-table thead th.th-right {
    text-align: right;
    padding-right: 20px;
}

/* Empty state polished */
.td-empty  { text-align: center; padding: 48px !important; color: var(--text3); font-size: 13px; }
.empty-state { display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; padding: 40px 24px; color: var(--text3); }
.empty-state svg { opacity: .25; margin-bottom: 14px; }
.empty-state h4 { font-size: 14px; font-weight: 700; color: var(--text); margin: 0 0 6px; }
.empty-state p { font-size: 13px; color: var(--text3); max-width: 320px; line-height: 1.6; margin: 0; }

/* Fila clickable */
.tr-clickable { cursor: pointer; }
.tr-clickable td:first-child { cursor: default; }

/* ── Badges estado ──
   Del tamaño y el peso del chip de Depósito (22/10/2026). Mauri: «no me gusta
   que tengamos tantos colores, se ve poco profesional; el semáforo está bien».
   Eran más grandes, en negrita y con un punto que brillaba: en una lista de
   facturas, cada fila gritaba. El punto queda, sin brillo: es el semáforo. */
.badge-estado {
    padding: 3px 10px;
    border-radius: 20px;
    font-size: 11px;
    font-weight: 600;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    white-space: nowrap;
    border: 1px solid transparent;
    transition: filter var(--t-fast);
}
.badge-estado::before {
    content: '';
    display: inline-block;
    width: 6px;
    height: 6px;
    border-radius: 50%;
    flex-shrink: 0;
}
.est-pendiente   { background: var(--tag-warn-bg); color: var(--tag-warn-c); }
.est-pendiente::before   { background: var(--tag-warn-c); }
.est-preaprobado { background: var(--tag-blue-bg); color: var(--tag-blue-c); }
.est-preaprobado::before { background: var(--tag-blue-c); }
.est-aprobado    { background: var(--tag-ok-bg);   color: var(--tag-ok-c); }
.est-aprobado::before    { background: var(--tag-ok-c); }
.est-rechazado   { background: var(--tag-err-bg);  color: var(--tag-err-c); }
.est-rechazado::before   { background: var(--tag-err-c); }
.est-anulado     { background: var(--tag-login-bg); color: var(--tag-login-c); }
.est-anulado::before     { background: var(--tag-login-c); }

/* Líneas Contado sin OC (bug #004/#008) */
.cl-section { margin: 16px 0; padding: 0; border: 1px solid var(--border); border-radius: 10px; background: var(--bg2); overflow: hidden; box-shadow: 0 1px 3px rgba(0,0,0,.06); }
.cl-titulo { font-size: 13px; font-weight: 700; color: var(--text); padding: 12px 14px 2px; display: flex; align-items: center; gap: 6px; }
.cl-titulo::before { content: ''; width: 3px; height: 14px; background: var(--accent); border-radius: 2px; }
.cl-hint { font-size: 11.5px; color: var(--text2); padding: 0 14px 12px 23px; }
.iva-estimado { margin-top: 4px; font-size: 12px; font-weight: 600; color: var(--accent-texto); }
.iva-estimado[hidden] { display: none; }
/* Cabecera: almacén + cuenta contable aplican a toda la factura */
.cl-cab { display: grid; grid-template-columns: 1.6fr 1fr; gap: 10px; padding: 0 14px 12px; }
.cl-cab-field label { display: block; font-size: 10.5px; font-weight: 700; color: var(--text2); text-transform: uppercase; letter-spacing: .3px; margin-bottom: 4px; }
.cl-cab-field select, .cl-cab-field input { width: 100%; height: 36px; padding: 0 10px; background: var(--input-bg); border: 1px solid var(--input-border); border-radius: 8px; font-size: 12.5px; color: var(--text); font-family: inherit; outline: none; transition: border-color var(--t-fast), box-shadow var(--t-fast); }
.cl-cab-field select:focus, .cl-cab-field input:focus { border-color: var(--accent); box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 18%, transparent); }
.cl-head, .cl-row { display: grid; grid-template-columns: 3fr 0.9fr 1.1fr 1.1fr 30px; gap: 8px; align-items: center; padding: 0 14px; }
.cl-head { font-size: 10px; font-weight: 700; color: var(--text2); text-transform: uppercase; letter-spacing: .4px; padding-top: 8px; padding-bottom: 8px; background: var(--bg3, var(--input-bg)); border-top: 1px solid var(--border); border-bottom: 1px solid var(--border); }
.cl-row { padding-top: 7px; padding-bottom: 7px; border-bottom: 1px solid var(--border); transition: background var(--t-fast); }
.cl-row:hover { background: var(--input-bg); }
.cl-cell { position: relative; min-width: 0; }
.cl-cell input, .cl-cell select { width: 100%; height: 34px; padding: 0 9px; background: var(--input-bg); border: 1px solid var(--input-border); border-radius: 7px; font-size: 12px; color: var(--text); font-family: inherit; outline: none; transition: border-color var(--t-fast), box-shadow var(--t-fast); }
.cl-cell input:focus, .cl-cell select:focus { border-color: var(--accent); box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 18%, transparent); }
.cl-cant-input, .cl-precio-input { text-align: right; }
.cl-total { justify-content: flex-end; font-size: 12px; color: var(--text); font-weight: 700; font-variant-numeric: tabular-nums; }
.cl-del-btn { width: 28px; height: 28px; border: none; border-radius: 7px; background: var(--tag-err-bg); color: var(--tag-err-c); cursor: pointer; font-size: 12px; display: flex; align-items: center; justify-content: center; transition: filter var(--t-fast); }
.cl-del-btn:hover { filter: brightness(1.15); }
.cl-add-btn { margin: 12px 14px; padding: 8px 16px; border: 1px dashed var(--accent); border-radius: 8px; background: transparent; color: var(--accent-texto); font-size: 12px; font-weight: 600; cursor: pointer; font-family: inherit; transition: background var(--t-fast); }
.cl-add-btn:hover { background: color-mix(in srgb, var(--accent) 10%, transparent); }
.cl-total-row { padding: 10px 14px; text-align: right; font-size: 13px; color: var(--text2); border-top: 1px solid var(--border); background: var(--bg3, var(--input-bg)); }
.cl-total-row strong { color: var(--text); font-size: 14px; font-variant-numeric: tabular-nums; }
/* Autocomplete dropdown */
/* min-width: el dropdown puede ser mas ancho que la celda del articulo para
   que los nombres largos se lean completos (pedido Mauricio 10/06/2026). */
.cl-ac { display: none; position: absolute; top: 100%; left: 0; right: 0; z-index: 50; min-width: 340px; max-height: 300px; overflow-y: auto; background: var(--bg2); border: 1px solid var(--border); border-radius: 8px; box-shadow: var(--shadow); margin-top: 3px; }
/* white-space: normal + word-break: el texto hace wrap en vez de truncarse con
   "..." — asi se distingue entre articulos/cuentas de nombre parecido. */
.cl-ac-item { padding: 8px 10px; font-size: 12px; line-height: 1.4; color: var(--text); cursor: pointer; border-bottom: 1px solid var(--border); white-space: normal; word-break: break-word; }
.cl-ac-item:last-child { border-bottom: none; }
.cl-ac-item:hover { background: var(--tag-blue-bg); color: var(--tag-blue-c); }
/* Texto escrito pero sin ítem válido seleccionado de la lista */
.cl-sin-sel { border-color: var(--fas-error) !important; box-shadow: 0 0 0 2px var(--fas-error-light) !important; }

/* Preview de confirmación para Contado sin OC */
.cl-modal-intro { margin-bottom: 10px; }
.cl-modal-summary { background: var(--bg2); border: 1px solid var(--border); border-radius: 6px; padding: 10px; margin-top: 8px; overflow-x: auto; }
.cl-modal-title { font-size: 11px; color: var(--text3); text-transform: uppercase; letter-spacing: .5px; margin-bottom: 6px; font-weight: 700; }
.cl-modal-kv { display: grid; grid-template-columns: 1fr 1fr; gap: 4px 12px; font-size: 12px; margin-bottom: 8px; }
.cl-modal-kv span { color: var(--text3); }
.cl-modal-table { width: 100%; border-collapse: collapse; font-size: 11.5px; min-width: 560px; }
.cl-modal-table thead tr { color: var(--text3); font-size: 10.5px; text-transform: uppercase; }
.cl-modal-table th { padding: 4px 6px; text-align: left; }
.cl-modal-cell { padding: 4px 6px; border-bottom: 1px solid var(--border); font-size: 11.5px; vertical-align: top; }
.cl-modal-num { text-align: right !important; font-variant-numeric: tabular-nums; white-space: nowrap; }
.cl-modal-free { color: var(--tag-warn-c); font-weight: 700; }
.cl-modal-total-label { padding: 6px; text-align: right; font-weight: 600; border-top: 2px solid var(--border); }
.cl-modal-total { padding: 6px; text-align: right; font-weight: 700; border-top: 2px solid var(--border); font-variant-numeric: tabular-nums; white-space: nowrap; }
.cl-modal-note { font-size: 10.5px; color: var(--text3); margin-top: 8px; border-top: 1px dashed var(--border); padding-top: 6px; }

/* Tag variants (admin) */
.tag { display: inline-flex; align-items: center; padding: 2px 9px; border-radius: 12px; font-size: 11px; font-weight: 700; white-space: nowrap; }
.tag-activo    { background: var(--tag-ok-bg);    color: var(--tag-ok-c); }
.tag-bloqueado { background: var(--tag-err-bg);   color: var(--tag-err-c); }
.tag-admin     { background: var(--tag-pur-bg);   color: var(--tag-pur-c); }
/* Los roles, neutros salvo Administrador (28/10/2026; ver admin.html). */
.tag-asistente { background: var(--tag-login-bg); color: var(--tag-login-c); }
.tag-aprobador { background: var(--tag-login-bg); color: var(--tag-login-c); }
.tag-inactivo  { background: var(--tag-login-bg); color: var(--tag-login-c); }
/* Usuario interno al que le faltan datos del RF1.2 (29/09/2026). */
.tag-incompleto { background: var(--tag-warn-bg); color: var(--tag-warn-c); }
.td-sub .tag-incompleto { margin-left: 4px; }

/* Admin → Usuarios → «Datos del usuario» (RF1.2, 29/09/2026). */
.usr-datos__grilla { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; margin: 6px 0 8px; }
.usr-datos__aviso { margin: 4px 0; padding: 5px 9px; border-radius: 6px; font-size: 12px; background: var(--tag-warn-bg); color: var(--tag-warn-c); }
@media (max-width: 480px) {
    .usr-datos__grilla { grid-template-columns: 1fr; }
}
.tag-proveedor { background: var(--tag-login-bg); color: var(--tag-login-c); }
.tag-login     { background: var(--tag-login-bg); color: var(--tag-login-c); }

/* Audit chip */
.audit-chip { background: var(--bg3); padding: 3px 9px; border-radius: 5px; font-weight: 600; font-size: 11px; color: var(--text2); display: inline-block; border: 1px solid var(--border); }

/* ── Modal de confirmación ── */
.modal-overlay { display: none; position: fixed; inset: 0; background: rgba(0,0,0,.5); z-index: 700; align-items: center; justify-content: center; }
.modal-overlay.open { display: flex; animation: mFadeIn .15s ease; }
@keyframes mFadeIn { from { opacity:0; } to { opacity:1; } }
/* ⚠️ EL `max-height` Y EL SCROLL NO SON DECORATIVOS — LEER ANTES DE SACARLOS.

   Hasta el 07/09/2026 este modal crecía sin límite. Con una factura de 17
   líneas —la 001-002-0000940, OC 18054, en PRODUCCIÓN— el resumen quedaba más
   alto que la ventana, y como el overlay centra verticalmente, el modal se
   desbordaba por arriba Y por abajo. Los botones «Cancelar» y «Aprobar y
   enviar a SAP» quedaban fuera de la pantalla, sin forma de llegar a ellos:
   no había scroll ni en el modal ni en la página.

   La persona que lo reportó tuvo que bajar el zoom del navegador al 80% para
   poder apretar Aprobar. Eso no es una molestia estética: es una factura que
   no se puede migrar, y la única salida era saber que existe el zoom.

   Con esto, el modal nunca pasa del 90% de la altura: lo que scrollea es el
   CUERPO, y la fila de botones queda siempre a la vista. Un modal chico no
   cambia en nada —su altura natural es menor que el tope—. */
.modal-box { background: var(--bg2); border: 1px solid var(--border); border-radius: 12px; padding: 28px 28px 22px; max-width: 420px; width: 90%; box-shadow: var(--shadow); animation: mSlideUp .2s ease;
    max-height: 90vh; display: flex; flex-direction: column; }
/* El cuerpo es lo único que scrollea. `min-height:0` es lo que se lo permite:
   sin eso, un hijo de flex no baja de su tamaño de contenido y el modal
   volvería a crecer. */
.modal-box > .modal-body { overflow-y: auto; min-height: 0; }
/* Y estos tres nunca se encogen: el título y los botones tienen que quedar
   visibles con cualquier cantidad de líneas. */
.modal-box > .modal-icon,
.modal-box > .modal-title,
.modal-box > .modal-actions { flex-shrink: 0; }
@keyframes mSlideUp { from { transform:translateY(12px); opacity:0; } to { transform:none; opacity:1; } }
.modal-icon { width: 44px; height: 44px; border-radius: 50%; display: flex; align-items: center; justify-content: center; margin-bottom: 14px; }
.modal-icon-warn   { background: var(--tag-warn-bg); color: var(--tag-warn-c); }
.modal-icon-danger { background: var(--tag-err-bg);  color: var(--tag-err-c); }
.modal-box.modal-box-wide { max-width: 760px; }
.modal-title { font-size: 16px; font-weight: 700; color: var(--text); margin-bottom: 6px; }
.modal-body  { font-size: 13px; color: var(--text2); margin-bottom: 20px; line-height: 1.5; }
.modal-motivo { margin-bottom: 16px; }
.modal-motivo label { display: block; font-size: 12px; font-weight: 600; color: var(--text2); margin-bottom: 6px; }
.modal-motivo textarea { width: 100%; padding: 9px 11px; background: var(--input-bg); border: 1px solid var(--input-border); border-radius: 6px; font-family: inherit; font-size: 13px; color: var(--text); resize: none; outline: none; transition: border-color var(--t-normal); }
.modal-motivo textarea:focus { border-color: var(--accent); }
/* `flex-wrap` para que en un ancho justo el botón de confirmar baje de línea en
   vez de comprimirse. En móvil esto ya estaba resuelto desde antes —ver el
   bloque `@media` más abajo, que además le da `flex: 1 1 auto` a los botones—;
   acá cubre el tramo de escritorio angosto, donde no había nada. */
.modal-actions { display: flex; flex-wrap: wrap; gap: 10px; justify-content: flex-end; }
.modal-btn { padding: 8px 20px; border-radius: 6px; font-size: 13px; font-weight: 600; cursor: pointer; border: none; font-family: inherit; transition: opacity var(--t-fast); }
.modal-btn:hover { opacity: .85; }
.modal-btn-cancel  { background: var(--bg3); color: var(--text); border: 1px solid var(--border); }
.modal-btn-confirm { background: var(--accent); color: white; }
.modal-btn-ok      { background: var(--est-ok-fuerte); color: white; }
.modal-btn-danger  { background: var(--est-err-fuerte); color: white; }

/* ── Modal centrado (Ajustes / Soporte) ── */
.center-modal-overlay { display: none; position: fixed; inset: 0; background: rgba(0,0,0,.5); z-index: 700; align-items: center; justify-content: center; }
.center-modal-overlay.open { display: flex; }
.center-modal { background: var(--bg2); border: 1px solid var(--border); border-radius: 12px; box-shadow: var(--shadow); width: 480px; max-width: 94vw; animation: scaleIn .18s ease; overflow: hidden; }
@keyframes scaleIn { from { opacity:0; transform:scale(.95); } to { opacity:1; transform:none; } }
.cm-header { background: var(--accent); padding: 16px 20px; display: flex; align-items: center; justify-content: space-between; }
.cm-title { font-size: 16px; font-weight: 700; color: #fff; display: flex; align-items: center; gap: 10px; }
.cm-title svg { width: 18px; height: 18px; opacity: .9; }
.cm-close { background: none; border: none; color: rgba(255,255,255,.7); cursor: pointer; padding: 5px; border-radius: 4px; transition: color var(--t-fast); display: flex; align-items: center; justify-content: center; }
.cm-close:hover { color: #fff; }
.cm-body { padding: 24px 20px; }
.cm-section { margin-bottom: 18px; }
.cm-section:last-child { margin-bottom: 0; }
/* El acento de TEXTO, no el de fondo: en el oscuro el violeta #4b2c80 daba 1,4:1
   y los títulos de Ajustes, Ayuda y Soporte no se leían (medido el 14/10/2026). */
.cm-section-title { font-size: 11px; font-weight: 700; color: var(--accent-texto, #a98ee0); text-transform: uppercase; letter-spacing: .5px; margin-bottom: 10px; }
.cm-row { display: flex; align-items: center; gap: 12px; padding: 10px 14px; background: var(--bg3); border-radius: 8px; margin-bottom: 8px; }
.cm-row svg { width: 16px; height: 16px; stroke: var(--accent); flex-shrink: 0; }
.cm-row-text { font-size: 13px; color: var(--text); }
.cm-row-sub  { font-size: 11px; color: var(--text2); margin-top: 1px; }
.dp-form-label { font-size: 12px; font-weight: 700; color: var(--text2); display: block; margin-bottom: 5px; }
.dp-input { width: 100%; background: var(--input-bg); border: 1px solid var(--input-border); border-radius: 6px; padding: 8px 11px; font-size: 13px; color: var(--text); outline: none; transition: border-color var(--t-normal); font-family: inherit; }
.dp-input:focus { border-color: var(--accent); box-shadow: 0 0 0 2px rgba(75,44,128,.2); }
.dp-input::placeholder { color: var(--text3); }

/* ── La cabecera de FAS, igual en todos los módulos (14/10/2026) ──
   templates/_cabecera.html + js/fas-cabecera.js: búsqueda rápida, avisos y
   ayuda. Lo de la barra (tb-icon-btn, tb-search-*, notif-*) es lo mismo que
   ya usaba Facturación. */
.cab-buscar { position: relative; }
.cab-sugerencias { position: absolute; top: calc(100% + 8px); right: 0; width: 290px; margin: 0; padding: 4px; list-style: none; background: var(--bg2); border: 1px solid var(--border); border-radius: 8px; box-shadow: var(--shadow); z-index: 600; }
.cab-sugerencia { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; padding: 8px 10px; border-radius: 6px; font-size: 13px; color: var(--text); cursor: pointer; }
.cab-sugerencia--activa, .cab-sugerencia:hover { background: var(--hover-bg); }
.cab-sugerencia__forma { flex-shrink: 0; font-size: 12px; color: var(--text2); }
.cab-sugerencia__valor { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 600; }
.cab-aviso { color: inherit; text-decoration: none; }
.cab-aviso .notif-item-body > span { display: block; }
.cab-aviso--error .notif-item-icon { background: var(--tag-err-bg); color: var(--tag-err-c); }
.cab-aviso--espera .notif-item-icon { background: var(--tag-warn-bg); color: var(--tag-warn-c); }
.cab-aviso__accion { margin-top: 4px; font-size: 12px; font-weight: 600; color: var(--accent-texto, #a98ee0); }
.cab-ayuda { width: 520px; }
.cab-ayuda__cuerpo { max-height: calc(100vh - 140px); overflow-y: auto; }
.cab-ayuda__ir { color: inherit; text-decoration: none; transition: background var(--t-fast); }
.cab-ayuda__ir:hover, .cab-ayuda__ir:focus-visible { background: var(--hover-bg); }
.cab-ayuda__atajos { display: flex; flex-wrap: wrap; gap: 8px 18px; font-size: 12px; color: var(--text2); }
.cab-ayuda__atajos kbd { display: inline-block; min-width: 20px; margin-right: 6px; padding: 1px 6px; border: 1px solid var(--border); border-bottom-width: 2px; border-radius: 4px; background: var(--bg3); color: var(--text); font: 600 11px/1.4 inherit; text-align: center; }
@media (max-width: 768px) {
  .cab-ayuda__cuerpo { max-height: none; overflow: visible; }
  .cab-sugerencias { position: fixed; top: 54px; right: 12px; left: 12px; width: auto; }
}

/* ── Perfil de usuario, igual en todos los módulos (14/10/2026) ──
   templates/_perfil.html + js/fas-perfil.js: Ajustes y Soporte. */
.perfil-modal { width: 520px; }
.perfil-modal__cuerpo { max-height: calc(100vh - 140px); overflow-y: auto; }
.perfil-cuenta { display: flex; align-items: center; gap: 14px; padding: 14px; background: var(--bg3); border-radius: 10px; }
.perfil-cuenta__avatar { width: 44px; height: 44px; border-radius: 50%; background: var(--accent); color: #fff; font-size: 15px; font-weight: 700; display: flex; align-items: center; justify-content: center; flex-shrink: 0; letter-spacing: .5px; }
.perfil-cuenta__nombre { font-size: 15px; font-weight: 700; color: var(--text); }
.perfil-cuenta__sub { font-size: 12px; color: var(--text2); margin-top: 2px; }
.perfil-datos { display: grid; grid-template-columns: 1fr 1fr; gap: 10px 16px; margin: 12px 0 0; }
.perfil-dato dt { font-size: 11px; font-weight: 700; color: var(--text3); text-transform: uppercase; letter-spacing: .4px; }
.perfil-dato dd { margin: 2px 0 0; font-size: 13px; color: var(--text); overflow-wrap: anywhere; }
.perfil-nota { font-size: 12px; color: var(--text2); margin: 10px 0 0; }
.perfil-link { background: none; border: none; padding: 0; font: inherit; font-weight: 600; color: var(--accent-texto, #a98ee0); text-decoration: underline; cursor: pointer; }
.perfil-campo { position: relative; margin-bottom: 10px; }
.perfil-campo .dp-input { padding-right: 40px; }
.perfil-ojo { position: absolute; right: 5px; bottom: 4px; width: 28px; height: 28px; border: none; border-radius: 6px; background: none; color: var(--text3); display: flex; align-items: center; justify-content: center; cursor: pointer; }
.perfil-ojo:hover, .perfil-ojo[aria-pressed="true"] { color: var(--accent-texto, #a98ee0); }
.perfil-ojo svg { width: 16px; height: 16px; }
.perfil-reglas { list-style: none; margin: 0 0 12px; padding: 0; display: grid; grid-template-columns: 1fr 1fr; gap: 4px 12px; }
.perfil-regla { font-size: 12px; color: var(--text3); display: flex; align-items: center; gap: 7px; transition: color var(--t-fast); }
.perfil-regla::before { content: ''; width: 8px; height: 8px; border: 1.5px solid currentColor; border-radius: 50%; flex-shrink: 0; }
.perfil-regla--ok { color: var(--tag-ok-c); }
.perfil-regla--ok::before { background: currentColor; }
.perfil-coincide { min-height: 16px; margin: -4px 0 10px; font-size: 12px; }
.perfil-coincide--ok { color: var(--tag-ok-c); }
.perfil-coincide--no { color: var(--tag-err-c); }
.perfil-msg { margin-bottom: 10px; padding: 8px 10px; border-radius: 6px; font-size: 12px; }
.perfil-msg--ok { background: var(--tag-ok-bg); color: var(--tag-ok-c); }
.perfil-msg--err { background: var(--tag-err-bg); color: var(--tag-err-c); }
.perfil-acciones { display: flex; align-items: center; justify-content: flex-end; gap: 12px; margin-top: 12px; }
.perfil-acciones .perfil-nota { margin: 0 auto 0 0; }
.perfil-acciones .btn:disabled { opacity: .5; cursor: not-allowed; }
/* Un botón deshabilitado se tiene que ver deshabilitado en todo FAS: hasta el
   20/10/2026 solo el perfil lo hacía, y el catálogo de componentes mostró el
   dorado «Deshabilitado» igual que uno que se puede apretar. El «enviando»
   (fas-btn--loading) tiene su propia opacidad, más arriba, y no se toca. */
.btn:disabled:not(.fas-btn--loading) { opacity: .5; cursor: not-allowed; box-shadow: none; transform: none; }
.perfil-copiar { margin-left: auto; padding: 5px 12px; font-size: 12px; }
/* En el celular la ventana es la hoja que sube desde abajo y ya se desplaza
   entera: con el cuerpo desplazable adentro quedaban dos barras. */
@media (max-width: 768px) {
  .perfil-modal__cuerpo { max-height: none; overflow: visible; }
}
@media (max-width: 560px) {
  .perfil-datos, .perfil-reglas { grid-template-columns: 1fr; }
  .perfil-acciones { flex-wrap: wrap; }
}

/* ── Notificaciones ── */
.notif-badge { position: absolute; top: 5px; right: 5px; width: 8px; height: 8px; background: var(--est-err); border-radius: 50%; pointer-events: none; }
.notif-dropdown { position: fixed; top: 48px; right: 50px; width: 320px; background: var(--bg2); border: 1px solid var(--border); border-radius: var(--radius); box-shadow: var(--shadow); z-index: 500; display: none; animation: dropIn .18s ease; }
.notif-dropdown.open { display: block; }
.notif-header { padding: 12px 16px; border-bottom: 1px solid var(--border); display: flex; align-items: center; justify-content: space-between; }
.notif-header-title { font-size: 13px; font-weight: 700; color: var(--text); }
.notif-list { max-height: 320px; overflow-y: auto; }
.notif-item { padding: 12px 16px; border-bottom: 1px solid var(--border); display: flex; gap: 12px; align-items: flex-start; cursor: pointer; transition: background var(--t-fast); }
.notif-item:hover { background: var(--hover-bg); }
.notif-item-icon { width: 34px; height: 34px; border-radius: 50%; background: var(--tag-warn-bg); color: var(--tag-warn-c); display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
.notif-item-icon--urgente { background: var(--tag-err-bg); color: var(--tag-err-c); }
/* El ícono del aviso de perfil incompleto: `var()` en el atributo `stroke`
   no lo toma Chrome; con currentColor, el color sale de acá (28/10/2026). */
.aviso-perfil__icono { color: var(--tag-warn-c); }
.notif-item-icon svg { width: 16px; height: 16px; }
.notif-item-body { flex: 1; min-width: 0; }
.notif-item-title { font-size: 13px; font-weight: 600; color: var(--text); }
.notif-item-sub   { font-size: 11px; color: var(--text2); margin-top: 2px; }
.notif-empty      { padding: 32px 16px; text-align: center; color: var(--text3); font-size: 13px; }
.notif-dismiss { background: none; border: none; color: var(--text3); cursor: pointer; padding: 5px; border-radius: 4px; flex-shrink: 0; opacity: 0; transition: opacity var(--t-fast), color var(--t-fast); margin-left: auto; display: flex; align-items: center; justify-content: center; }
.notif-item:hover .notif-dismiss { opacity: 1; }
.notif-dismiss:hover { color: var(--text); background: var(--hover-bg); }

/* ── KPI cards ── */
/* auto-fit, NO auto-fill. La diferencia es la que dejaba media pantalla
   vacia a la derecha: auto-fill crea todas las columnas que entran —en 1366px
   son 8— y las que no se usan quedan vacias ocupando lugar. auto-fit colapsa
   las vacias y reparte el ancho entre las tarjetas que hay.
   Asi, si manana se agrega un estado nuevo, las tarjetas se acomodan solas y
   siguen llenando la fila (pedido de Mauricio, 14/08/2026). Deposito ya lo
   usaba; Facturacion y Admin habian quedado con el otro.

   El minimo es 200px y no 160px porque la division tiene que caer pareja: con
   160 el tablero de Admin —que tiene 10 tarjetas— quedaba en 6+4 y la segunda
   fila dejaba el mismo hueco a la derecha. Medido: entre 190 y 218 da 5+5
   lleno; 230 lo vuelve a romper. 200 queda en el medio del rango que funciona,
   asi un cambio chico de padding no lo tumba. */
.kpi-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 14px; margin-bottom: 20px; }
.kpi-card { background: var(--bg2); border: 1px solid var(--border); border-radius: var(--radius); padding: 16px 18px; display: flex; flex-direction: column; gap: 6px; }
.kpi-label { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .5px; color: var(--text2); }
.kpi-value { font-size: 26px; font-weight: 800; color: var(--text); line-height: 1; font-variant-numeric: tabular-nums; }
/* Los colores de estado del tema, no fijos (09/10/2026): eran los del oscuro
   en los dos temas, y en el claro el monto aprobado en verde o azul pastel
   sobre blanco no llegaba al contraste (axe, primera medición en claro). En
   el oscuro son los mismos valores de siempre. */
.kpi-value.warn  { color: var(--tag-warn-c); }
.kpi-value.ok    { color: var(--tag-ok-c); }
.kpi-value.err   { color: var(--tag-err-c); }
.kpi-value.blue  { color: var(--tag-blue-c); }
.kpi-monto { font-size: 12px; color: var(--text2); margin-top: 2px; }
.kpi-trend { display: flex; align-items: center; gap: 4px; font-size: 11px; font-weight: 600; margin-top: 4px; }
.kpi-trend.up   { color: var(--tag-ok-c); }
.kpi-trend.down { color: var(--tag-err-c); }
.kpi-trend svg  { width: 12px; height: 12px; }
.kpi-card.kpi-link { cursor: pointer; transition: border-color var(--t-fast), transform var(--t-fast), box-shadow var(--t-fast); }
.kpi-card.kpi-link:hover { border-color: var(--accent-lt); transform: translateY(-2px); box-shadow: 0 6px 20px rgba(75,44,128,.15); }
.kpi-card.kpi-link:active { transform: translateY(0); }

.chart-wrap { background: var(--bg2); border: 1px solid var(--border); border-radius: var(--radius); padding: 18px; margin-bottom: 20px; }
.chart-title { font-size: 13px; font-weight: 700; color: var(--text); margin-bottom: 14px; }

/* ── Date filter ── */
.date-filter { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.date-filter-label { font-size: 11px; color: var(--text3); font-weight: 600; }
.btn-fecha { padding: 4px 12px; border-radius: 20px; font-size: 11px; font-weight: 600; cursor: pointer; border: 1px solid var(--border); background: var(--bg3); color: var(--text2); font-family: inherit; transition: background var(--t-fast), color var(--t-fast), border-color var(--t-fast); }
.btn-fecha:hover  { background: var(--bg4); color: var(--text); }
.btn-fecha.active { background: var(--accent); color: #fff; border-color: var(--accent); }
.date-range-hint  { font-size: 11px; color: var(--text3); padding: 4px 0 0; display: block; }

/* ── Paginación ── */
.list-footer { padding: 9px 16px; border-top: 1px solid var(--border); display: flex; align-items: center; justify-content: space-between; flex-shrink: 0; background: var(--bg); flex-wrap: wrap; gap: 8px; }
.pg-info { font-size: 12px; color: var(--text2); }
/* Búsqueda sin resultados (fas-paginador.js, `opciones.vacio`): la tabla
   conserva encabezados y filtros, y dice qué pasó en vez de quedar en blanco. */
/* Con `.data-table td` delante: esa regla (texto a la izquierda) es más
   específica y el mensaje salía pegado al borde (visto en testing, 02/10). */
.data-table td.fas-vacio__texto,
.fas-vacio__texto { text-align: center; justify-content: center; padding: 28px 16px; color: var(--text2); font-size: 13px; }
.pg-btns { display: flex; gap: 3px; }
.pg-btn { min-width: 28px; height: 28px; background: var(--bg3); border: 1px solid var(--border); border-radius: 5px; color: var(--text); font-size: 12px; font-weight: 600; cursor: pointer; display: inline-flex; align-items: center; justify-content: center; padding: 0 6px; transition: all var(--t-fast); }
.pg-btn:hover  { border-color: var(--accent); color: var(--accent-texto); }
.pg-btn.active { background: var(--accent); border-color: var(--accent); color: white; }
.pg-btn:disabled { opacity: .35; pointer-events: none; }
.pg-puntos { padding: 0 4px; color: var(--text3); align-self: center; font-size: 12px; }

/* ── Botones acción tabla ── */
/* «Revisar» en cada fila de la Bandeja: secundario, con borde (UX-01,
   20/10/2026). Eran ocho botones dorados en pantalla compitiendo entre sí; el
   dorado es la acción principal de la pantalla. La fila entera se puede tocar. */
.btn-rev {
    background: var(--bg3);
    color: var(--text);
    border: 1px solid var(--border);
    padding: 6px 13px;
    border-radius: 6px;
    text-decoration: none;
    font-size: 11.5px;
    font-weight: 700;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    white-space: nowrap;
    transition: border-color var(--t-fast), color var(--t-fast), background var(--t-fast);
}
.btn-rev:hover {
    border-color: var(--accent-lt);
    color: var(--accent-texto);
    background: var(--bg4);
}
.btn-view {
    background: var(--bg3);
    color: var(--text2);
    padding: 7px 14px;
    border-radius: 6px;
    text-decoration: none;
    font-size: 11.5px;
    font-weight: 600;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    border: 1px solid var(--border);
    transition: background var(--t-fast), color var(--t-fast), transform var(--t-fast), border-color var(--t-fast);
}
.btn-view:hover {
    background: var(--bg4);
    color: var(--text);
    border-color: var(--accent);
    transform: translateY(-1px);
}
/* La acción que destruye no grita en la lista (22/10/2026): se ve como «Ver»
   y se pone roja al pasar el mouse. El rojo fuerte queda para el cuadro que
   confirma, que es donde se decide. */
.btn-view--peligro { margin-left: 4px; }
.btn-view--peligro:hover { border-color: var(--tag-err-c); color: var(--tag-err-c); }

/* ── Bulk actions ── */

/* ── CTA box ── */
.cta-box { text-align: center; padding: 48px 24px; }
.cta-box p { color: var(--text2); font-size: 14px; margin-bottom: 20px; line-height: 1.6; }

/* ── Devuelto alert ── */
/* El aviso de SIFEN al cargar una factura (28/10/2026): estaba armado con
   estilos en el JS, copiado cuatro veces y con colores del tema claro (en
   oscuro salía un recuadro casi blanco). El ícono toma el color del texto. */
.sifen-aviso { display: flex; align-items: center; gap: 10px; border: 1px solid; border-left-width: 4px; border-radius: 8px; padding: 10px 14px; margin-bottom: 14px; }
.sifen-aviso svg { flex: none; }
.sifen-aviso__cuerpo { flex: 1; min-width: 0; }
.sifen-aviso__titulo { font-size: 12.5px; font-weight: 700; }
.sifen-aviso__detalle { font-size: 11px; margin-top: 2px; }
.sifen-aviso--ok  { background: var(--tag-ok-bg);  color: var(--tag-ok-c);  border-color: color-mix(in srgb, var(--est-ok) 40%, transparent);  border-left-color: var(--est-ok); }
.sifen-aviso--err { background: var(--tag-err-bg); color: var(--tag-err-c); border-color: color-mix(in srgb, var(--est-err) 40%, transparent); border-left-color: var(--est-err); }
.alerta-devuelto { display: flex; align-items: center; gap: 5px; font-size: 11px; color: var(--tag-warn-c); background: var(--tag-warn-bg); border: 1px solid color-mix(in srgb, var(--est-warn) 30%, transparent); border-radius: 4px; padding: 2px 7px; margin-top: 3px; font-weight: 500; }

/* ── Scrollbar ─────────────────────────────────────────────────────────────
   Tiene que VERSE y poder agarrarse. Venia de 6px pintada con `--bg4`, que es
   un color de FONDO: en tema claro el pulgar era #ededf8 sobre un panel casi
   igual, o sea invisible. En una pantalla corta no se notaba; en la historia
   de un lote —nueve hojas de movimientos— no se sabia ni donde estaba uno
   parado ni habia de donde arrastrar (reportado el 25/08/2026).

   10px es un objetivo que se agarra con el mouse sin comerse ancho util, y el
   borde transparente con `background-clip` deja aire a los costados para que
   no toque el contenido. Firefox no entiende `::-webkit-`: va con las
   propiedades estandar, que ademas no estaban puestas. */
/* ESTE ES EL UNICO LUGAR donde se define la barra de scroll. Habia TRES
   bloques repartidos por el archivo —"SCROLLBAR premium", otro mas abajo y
   este—, y ganaba el ultimo, que la pintaba con `--bg4`: un color de FONDO.
   Por eso los dos arreglos anteriores no cambiaron nada en pantalla. Si hace
   falta tocar la barra, se toca ACA. Hay una prueba que falla si aparece una
   segunda copia.

   OJO CON EL ORDEN DE PRECEDENCIA, que cuesta una tarde entenderlo:
   Chrome, desde la 121, cuando encuentra las propiedades ESTANDAR
   (`scrollbar-width` / `scrollbar-color`) IGNORA por completo las reglas
   `::-webkit-scrollbar` de ese elemento. La primera version de este arreglo
   ponia `scrollbar-width: thin` y por eso salio MAS fina que antes: las reglas
   de 10px de abajo no se estaban aplicando.
   Por eso ahora el ancho queda en `auto` —la barra normal del sistema, la mas
   facil de agarrar— y el color va por la propiedad estandar. El bloque
   `::-webkit-` queda para los navegadores que no entienden las estandar. */
* { scrollbar-color: var(--scroll-thumb) var(--scroll-track); }
::-webkit-scrollbar { width: 12px; height: 12px; }
::-webkit-scrollbar-track { background: var(--scroll-track); }
::-webkit-scrollbar-thumb {
    background: var(--scroll-thumb); border-radius: 6px;
    border: 2px solid transparent; background-clip: padding-box;
}
::-webkit-scrollbar-thumb:hover {
    background: var(--scroll-hover); background-clip: padding-box;
}
/* Donde se cruzan la barra horizontal y la vertical. Sin esto queda un
   cuadradito blanco que en tema oscuro canta. */
::-webkit-scrollbar-corner { background: var(--scroll-track); }

/* ── Scroll horizontal, con aviso de que hay mas ─────────────────────────────
   Una tabla mas ancha que la pantalla no avisa que sigue hacia el costado. La
   barra de abajo ayuda, pero en una tabla de 25 filas esta al final de todo:
   no se ve hasta bajar, que es tarde.

   La SOMBRA en el borde si se ve, y desaparece sola al llegar a la punta. Es
   la tecnica de los fondos con `background-attachment`: los dos primeros
   degradados son del color de la tarjeta y viajan CON el contenido (`local`),
   asi que tapan la sombra cuando no hay nada mas de ese lado; los dos
   radiales se quedan quietos (`scroll`). Cero JavaScript y cero listeners de
   scroll — la pantalla no se pone mas pesada por esto.

   El color de tapado es `--bg2` porque estos contenedores viven adentro de una
   tarjeta. Sobre otro fondo hay que pasarle `--scroll-fade` distinto. */
.scroll-x {
    overflow-x: auto;
    --scroll-fade: var(--bg2);
    background:
        linear-gradient(to right, var(--scroll-fade) 40%, transparent)
            left center / 28px 100% no-repeat local,
        linear-gradient(to left, var(--scroll-fade) 40%, transparent)
            right center / 28px 100% no-repeat local,
        radial-gradient(farthest-side at 0 50%,
            rgb(0 0 0 / 20%), transparent) left center / 14px 100% no-repeat scroll,
        radial-gradient(farthest-side at 100% 50%,
            rgb(0 0 0 / 20%), transparent) right center / 14px 100% no-repeat scroll;
}

/* El anillo de foco vive en un solo lugar: FOCUS / ACCESIBILIDAD, arriba.
   Acá había un segundo :focus-visible en dorado que lo pisaba (y en el tema
   claro el dorado es 1,8:1). */

/* ── Tipografía refinada ── */
body { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; }
h1, h2, h3, h4 { letter-spacing: -0.02em; }
.tabular-nums { font-variant-numeric: tabular-nums; }

/* ============================================================
   FAS PREMIUM v2 — Dark Glassmorphism Enhancement Layer
   Puramente aditivo — no sobreescribe reglas existentes
   ============================================================ */

/* ── Tokens de elevación y glow ── */
:root {
    --surface-0: var(--bg);
    --surface-1: var(--bg2);
    --surface-2: var(--bg3);
    --surface-3: var(--bg4);

    --glow-xs:   0 0  8px rgba(75,44,128,.22);
    --glow-sm:   0 0 16px rgba(75,44,128,.30);
    --glow-md:   0 0 28px rgba(75,44,128,.40);
    --glow-gold: 0 0 12px rgba(246,187,84,.22);

    --shadow-premium-sm: 0 2px 8px  rgba(0,0,0,.32), 0 0 0 1px rgba(255,255,255,.04);
    --shadow-premium-md: 0 4px 20px rgba(0,0,0,.42), 0 0 0 1px rgba(255,255,255,.05), var(--glow-xs);
    --shadow-premium-lg: 0 8px 40px rgba(0,0,0,.50), 0 0 0 1px rgba(255,255,255,.06), var(--glow-sm);
    --shadow-premium-xl: 0 16px 60px rgba(0,0,0,.60), 0 0 0 1px rgba(255,255,255,.07), var(--glow-md);

    --glass-blur:        blur(16px) saturate(180%);
    --glass-bg-dark:     rgba(27,27,42,.75);
    --glass-bg-light:    rgba(255,255,255,.85);
    --glass-border:      rgba(255,255,255,.07);
    --glass-border-hover:rgba(255,255,255,.14);

    --ease-spring: cubic-bezier(0.34,1.56,0.64,1);
    --ease-smooth: cubic-bezier(0.25,0.46,0.45,0.94);
    --t-fast:   150ms var(--ease-smooth);
    --t-normal: 220ms var(--ease-smooth);
    --t-slow:   360ms var(--ease-smooth);
    --t-spring: 400ms var(--ease-spring);
}

/* ── Keyframes premium ── */
@keyframes fas-bounce-in {
    0%   { opacity: 0; transform: scale(.92) translateY(-6px); }
    60%  { transform: scale(1.02) translateY(2px); }
    100% { opacity: 1; transform: scale(1) translateY(0); }
}
@keyframes fas-slide-up {
    from { opacity: 0; transform: translateY(14px); }
    to   { opacity: 1; transform: translateY(0); }
}
@keyframes fas-shimmer {
    0%   { background-position: -200% center; }
    100% { background-position:  200% center; }
}
@keyframes fas-ripple {
    to { transform: scale(3.5); opacity: 0; }
}
@keyframes fas-dot-pulse {
    0%, 100% { transform: scale(1);   opacity: 1; }
    50%       { transform: scale(1.5); opacity: .65; }
}
@keyframes fas-glow-pulse {
    0%, 100% { box-shadow: 0 0 8px  rgba(75,44,128,.25); }
    50%       { box-shadow: 0 0 22px rgba(75,44,128,.55); }
}
@keyframes fas-avatar-ring {
    0%, 100% { box-shadow: 0 0 0 2px var(--bg), 0 0 0 4px rgba(246,187,84,.30); }
    50%       { box-shadow: 0 0 0 2px var(--bg), 0 0 0 4px rgba(246,187,84,.60); }
}

/* ── Glassmorphism utility ── */
.glass-panel {
    background: var(--glass-bg-dark);
    backdrop-filter: var(--glass-blur);
    -webkit-backdrop-filter: var(--glass-blur);
    border: 1px solid var(--glass-border);
    transition: border-color var(--t-normal);
}
.glass-panel:hover { border-color: var(--glass-border-hover); }
[data-theme="light"] .glass-panel { background: var(--glass-bg-light); }

/* ── Stagger animation utility ── */
.fas-stagger > *:nth-child(1)   { animation: fas-slide-up .28s var(--ease-smooth) both; animation-delay: .04s; }
.fas-stagger > *:nth-child(2)   { animation: fas-slide-up .28s var(--ease-smooth) both; animation-delay: .09s; }
.fas-stagger > *:nth-child(3)   { animation: fas-slide-up .28s var(--ease-smooth) both; animation-delay: .14s; }
.fas-stagger > *:nth-child(4)   { animation: fas-slide-up .28s var(--ease-smooth) both; animation-delay: .19s; }
.fas-stagger > *:nth-child(5)   { animation: fas-slide-up .28s var(--ease-smooth) both; animation-delay: .24s; }
.fas-stagger > *:nth-child(n+6) { animation: fas-slide-up .28s var(--ease-smooth) both; animation-delay: .28s; }

/* ── Shimmer utility ── */
.fas-shimmer {
    background: linear-gradient(90deg, var(--bg2) 25%, var(--bg3) 50%, var(--bg2) 75%);
    background-size: 200% 100%;
    animation: fas-shimmer 1.6s infinite;
}

/* ── ENHANCED buttons — ripple + lift ── */
.btn {
    position: relative;
    overflow: hidden;
    transition: opacity var(--t-fast), transform var(--t-fast), box-shadow var(--t-fast);
}
.btn::after {
    content: '';
    position: absolute;
    inset: 0;
    background: radial-gradient(circle at center, rgba(255,255,255,.22) 0%, transparent 70%);
    opacity: 0;
    transform: scale(0);
    transition: transform .5s var(--ease-smooth), opacity .5s;
    border-radius: inherit;
    pointer-events: none;
}
.btn:active::after { transform: scale(2.8); opacity: 1; transition: 0s; }

.btn-primary:hover:not(:disabled) {
    transform: translateY(-1px);
    box-shadow: 0 4px 16px rgba(75,44,128,.45), var(--glow-xs);
}
.btn-success:hover:not(:disabled) {
    transform: translateY(-1px);
    box-shadow: 0 4px 16px color-mix(in srgb, var(--est-ok) 35%, transparent);
}
.btn-danger:hover:not(:disabled) {
    transform: translateY(-1px);
    box-shadow: 0 4px 16px color-mix(in srgb, var(--est-err) 35%, transparent);
}
.btn-ghost:hover:not(:disabled) {
    transform: translateY(-1px);
}

/* ── ENHANCED nav items — slide indicator ── */
.nav-item {
    transition: background var(--t-fast), color var(--t-fast), border-color var(--t-fast), transform var(--t-fast);
}
.nav-item:hover {
    transform: translateX(2px);
}
.nav-item.active {
    box-shadow: inset 0 0 20px rgba(75,44,128,.12);
}

/* ── ENHANCED KPI cards ── */
.kpi-card {
    transition: transform var(--t-normal), box-shadow var(--t-normal), border-color var(--t-normal);
    position: relative;
    overflow: hidden;
}
.kpi-card::before {
    content: '';
    position: absolute;
    top: 0; left: 0; right: 0;
    height: 1px;
    background: linear-gradient(90deg, transparent, rgba(75,44,128,.5), transparent);
    opacity: 0;
    transition: opacity var(--t-normal);
}
.kpi-card.kpi-link:hover {
    transform: translateY(-3px) !important;
    box-shadow: 0 8px 30px rgba(0,0,0,.32), var(--glow-xs) !important;
    border-color: rgba(75,44,128,.4) !important;
}
.kpi-card.kpi-link:hover::before { opacity: 1; }

/* ── ENHANCED cards ── */
.card {
    border: 1px solid rgba(255,255,255,.055);
    transition: border-color var(--t-normal), box-shadow var(--t-normal);
}
.card:hover {
    border-color: rgba(255,255,255,.10);
    box-shadow: 0 4px 24px rgba(0,0,0,.25);
}

/* El punto que late es solo «el sistema está trabajando» (`.est-encola`).
   Pendiente espera a una persona: no late (22/10/2026). */

/* ── ENHANCED modals ── */
.modal-overlay.open .modal-box {
    animation: fas-bounce-in .28s var(--ease-spring) forwards;
}
.modal-box {
    border: 1px solid rgba(255,255,255,.08);
    box-shadow: var(--shadow-premium-xl);
}
.center-modal {
    border: 1px solid rgba(255,255,255,.08);
    box-shadow: var(--shadow-premium-xl);
}

/* ── ENHANCED profile/notif dropdowns ── */
.profile-menu,
.notif-dropdown {
    border: 1px solid rgba(255,255,255,.08);
    box-shadow: var(--shadow-premium-lg);
}

/* ── ENHANCED data table row glow lateral ── */
.data-table tbody tr:hover {
    box-shadow: inset 3px 0 0 var(--gold);
}

/* ── ENHANCED input focus — gold glow ── */
.dp-input:focus,
.search-input:focus {
    box-shadow: 0 0 0 2px rgba(246,187,84,.14), 0 0 10px rgba(246,187,84,.07) !important;
}
input[type="text"]:focus:not([class]),
input[type="email"]:focus:not([class]),
input[type="password"]:focus:not([class]) {
    box-shadow: 0 0 0 2px rgba(246,187,84,.14), 0 0 10px rgba(246,187,84,.07);
}

/* ── TOPBAR glass ── */
.topbar {
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    border-bottom: 1px solid rgba(255,255,255,.06);
}

/* ── SIDEBAR refined border + shadow ── */
.sidebar {
    box-shadow: 2px 0 24px rgba(0,0,0,.18);
}

/* ── AVATAR ring premium ── */
.avatar-btn {
    box-shadow: 0 0 0 2px var(--bg), 0 0 0 4px rgba(246,187,84,.28);
    transition: box-shadow var(--t-normal), opacity var(--t-fast);
}
.avatar-btn:hover {
    box-shadow: 0 0 0 2px var(--bg), 0 0 0 4px rgba(246,187,84,.65);
    opacity: 1 !important;
}


/* ── PAGE section transition ── */
.page.active {
    animation: fas-slide-up .2s var(--ease-smooth) both;
}

/* ── Entre pantallas (22/10/2026) ──
   Pasar de Cargar factura a Mis facturas, o de Ingreso a Rótulos, carga otra
   página, y el navegador la pintaba de golpe («en seco», Mauri). Con la
   transición entre documentos, el contenido se funde y el menú queda quieto.
   El navegador que no la conoce la ignora y navega como siempre. */
@media (prefers-reduced-motion: no-preference) {
    @view-transition { navigation: auto; }
    .sidebar { view-transition-name: fas-menu; }
}

/* ── LIGHT mode adaptations ── */
[data-theme="light"] .modal-box,
[data-theme="light"] .center-modal {
    border-color: rgba(0,0,0,.07);
    box-shadow: 0 16px 60px rgba(0,0,0,.14), 0 0 0 1px rgba(0,0,0,.04);
}
[data-theme="light"] .card:hover {
    border-color: rgba(75,44,128,.18);
    box-shadow: 0 4px 20px rgba(75,44,128,.07);
}
[data-theme="light"] .kpi-card.kpi-link:hover {
    box-shadow: 0 8px 30px rgba(75,44,128,.12) !important;
}
[data-theme="light"] .btn-primary:hover:not(:disabled) {
    box-shadow: 0 4px 16px rgba(75,44,128,.28);
}
[data-theme="light"] .data-table tbody tr:hover {
    box-shadow: inset 3px 0 0 var(--accent);
}
[data-theme="light"] .sidebar {
    box-shadow: 2px 0 16px rgba(0,0,0,.08);
}

/* ============================================
   MEJORAS VISUALES 21/07/2026
   Barra de entorno · estados semánticos · filas con error ·
   skeleton de carga · contador del menú
   ============================================ */

/* ── 1. Barra de entorno ─────────────────────────────────────────────────────
   Franja fija arriba de todo SOLO en testing. El favicon se ve únicamente en la
   pestaña; esta franja se ve mientras se trabaja, que es cuando importa no
   confundirse de entorno. En producción no existe (no molesta al usuario final). */
.env-bar {
    position: fixed;
    top: 0; left: 0; right: 0;
    height: 3px;
    background: linear-gradient(90deg, #F0943E, #f7b267, #F0943E);
    z-index: 3000;
    pointer-events: none;
}
/* Al centro, donde la barra no tiene controles (UX-18, 19/10/2026): pegada a
   la derecha tapaba el borde del avatar y el botón «Registro completo» de la
   ficha del lote. En el celular queda solo la franja naranja. */
.env-bar::after {
    content: 'TESTING';
    position: absolute;
    top: 3px; left: 50%; transform: translateX(-50%);
    background: #F0943E;
    color: #fff;
    font-size: 9px;
    font-weight: 800;
    letter-spacing: .12em;
    padding: 2px 8px;
    border-radius: 0 0 6px 6px;
    box-shadow: 0 2px 6px rgba(0,0,0,.18);
}
@media (max-width: 600px) {
    .env-bar::after { display: none; }
}

/* ── 2. Estados semánticos ───────────────────────────────────────────────────
   Antes "En Cola SAP" usaba el mismo azul que "Pre-Aprobado" y se confundían.
   Criterio: gris = esperando al sistema · ámbar = requiere acción de alguien ·
   verde = cerrado OK · rojo = error. */
.est-encola {
    background: var(--tag-login-bg, rgba(148,163,184,.16));
    color: var(--tag-login-c, #64748b);
}
.est-encola::before {
    background: currentColor;
    box-shadow: none;
    animation: estPulso 1.6s ease-in-out infinite;   /* late: algo está pasando */
}
@keyframes estPulso { 0%,100% { opacity:1; } 50% { opacity:.35; } }
/* Error de migración: se diferencia de "Rechazado" (que es decisión humana) */
.est-error {
    background: var(--tag-err-bg);
    color: var(--tag-err-c);
    border-color: color-mix(in srgb, var(--tag-err-c) 45%, transparent);
}
.est-error::before { background: var(--tag-err-c); }

/* ── 3. Filas que requieren atención ─────────────────────────────────────────
   Un chip de color se pierde en una lista larga; el borde izquierdo se ve al
   escanear la tabla de un vistazo. Solo lo que alguien tiene que resolver:
   una Rechazada ya está decidida y no lleva franja (22/10/2026). */
tr.row-error   > td:first-child { box-shadow: inset 3px 0 0 var(--tag-err-c); }
tr.row-atencion > td:first-child { box-shadow: inset 3px 0 0 var(--tag-warn-c); }
tr.row-error:hover, tr.row-atencion:hover { background: var(--input-bg); }

/* ── 4. Skeleton de carga ────────────────────────────────────────────────────
   Mientras el OCR analiza el PDF (2-3 s). Se percibe más rápido que un texto
   fijo porque hay movimiento. */
.fas-skeleton {
    background: linear-gradient(90deg,
        var(--input-bg) 25%,
        color-mix(in srgb, var(--text3) 12%, var(--input-bg)) 37%,
        var(--input-bg) 63%);
    background-size: 400% 100%;
    animation: skelShimmer 1.3s ease-in-out infinite;
    border-radius: 8px;
}
@keyframes skelShimmer { 0% { background-position: 100% 50%; } 100% { background-position: 0 50%; } }
.fas-skeleton-linea { height: 34px; margin-bottom: 10px; }
.fas-skeleton-linea.corta { width: 55%; }
.fas-skeleton-caja { height: 190px; }

/* ============================================================================
   MOBILE — CAPA DE OVERRIDE (23/07/2026)

   VA AL FINAL A PROPÓSITO: es un layer de override. Las media queries NO suman
   especificidad, así que si estas reglas estuvieran arriba, cualquier regla de
   escritorio posterior (.data-table en 1307, body en 1029, etc.) las pisaría.

   Todo vive dentro de @media (max-width: 768px) → en desktop no aplica NADA.
   ============================================================================ */
@media (max-width: 768px) {

  /* ── SCROLL: lo más importante ──────────────────────────────────────────
     En escritorio el shell es de altura fija (body 100vh + overflow hidden) y
     el que scrollea es un contenedor interno (.dash-scroll con flex:1). En celu
     esa cadena se rompe y el contenido queda clipeado, sin poder desplazarse.
     Acá devolvemos el scroll natural del documento.
     OJO: `overflow` de body va SIN !important a propósito — fas-mobile.js le
     pone overflow:hidden inline para bloquear el fondo con el drawer abierto,
     y un !important se lo comería. */
  /* `overflow-x` va en HTML y no en BODY, y esto es LA razon de que no se
     pudiera scrollear con el dedo (27/08/2026).
     Por especificacion, si un eje es `hidden` el otro deja de ser `visible` y
     pasa a `auto`. Con `overflow-x: hidden` en el body, su `overflow-y` se
     volvia `auto`: el body pasaba a ser un contenedor de scroll que NO
     scrollea —su contenido le entra justo— y el gesto tactil moria ahi, sin
     llegar al documento, que es el que si tiene para desplazarse.
     Medido: `window.scrollTo` funcionaba y el arrastre del dedo no. Por eso
     parecia que la pantalla estaba "trabada". */
  /* En celular el que scrollea es el documento, así que la reserva va acá.
     ────────────────────────────────────────────────────────────────────────
     Abajo conviven dos barras fijas: la de acciones del ingreso (`bottom:
     68px`) y la navegación inferior (68px de alto). Cualquier
     `scrollIntoView` —el que enfoca una fila con error, por ejemplo— dejaba
     el elemento justo debajo de las dos: se veía que "algo pasó" pero no qué.
     `scroll-padding-bottom` le dice al navegador que ese espacio no cuenta
     como visible. */
  html { height: auto; overflow-x: hidden; scroll-padding-bottom: 140px; }
  body { height: auto; min-height: 100%; overflow: visible; }
  .layout        { overflow: visible !important; height: auto !important; min-height: 0 !important; }
  .content-area  { overflow: visible !important; height: auto !important; }
  .page          { overflow: visible !important; height: auto !important; padding: 0; }
  .page.active   { display: flex; }
  /* Los contenedores que scrollean en escritorio pasan a fluir con la página.
     ────────────────────────────────────────────────────────────────────────
     Estaba SOLO `.dash-scroll`, y ese lo usan Facturación y Admin. Depósito
     usa `.ent-scroll` y Rótulos `.rot-scroll`: los dos seguían con
     `overflow-y: auto` en el celular.

     Un contenedor con `overflow-y: auto` cuyo contenido le entra justo NO
     scrollea, pero SÍ se queda con el gesto: el dedo lo arrastra a él, él no
     tiene adónde ir, y el documento —que sí tenía 8.700 px para desplazarse—
     nunca se entera. Por eso Trazabilidad «no dejaba moverse» y Facturación
     sí funcionaba: no era el contenido, era de qué clase colgaba.

     Medido con touch real (`Input.dispatchTouchEvent`, 27/08/2026): con
     `page.mouse` el arrastre da 0 hasta en una página que scrollea perfecto,
     así que esa prueba no vale. */
  .dash-scroll,
  .ent-scroll,
  .rot-scroll,
  .list-scroll   { overflow: visible !important; height: auto !important; flex: none !important; padding: 16px 14px !important; }

  /* Los paneles de Admin, que arman el layout de dos columnas tipo Microsoft
     365. En escritorio cada uno scrollea por su cuenta; en el celular van uno
     abajo del otro y su `overflow` solo servía para tragarse el gesto: los
     cuatro miden exactamente lo que su contenido, así que no scrollean pero
     igual se lo quedan. */
  .m365-layout, .list-pane, .detail-panel, .dp-body {
    overflow: visible !important; height: auto !important;
    max-height: none !important;
  }
  /* Inercia de scroll en iOS */
  body, .content-area { -webkit-overflow-scrolling: touch; }

  /* ── KPIs del dashboard: de 6 columnas a 2 ── */
  .kpi-grid { grid-template-columns: 1fr 1fr !important; gap: 10px !important; }
  .kpi-card { padding: 14px !important; }
  .kpi-value { font-size: 22px !important; }

  /* ── Tablas .data-table → tarjetas (nunca desbordan el viewport) ──
     thead se oculta y cada fila se apila como tarjeta con su etiqueta
     (data-label en el <td>). Las tablas sin data-label degradan a valores
     apilados legibles, sin romperse. */
  /* min-width !important: varias tablas traen `min-width:900/980px` en el style
     inline del template (para que en escritorio no se aplasten). En celular eso
     forzaba la tarjeta a ~980px de ancho → las etiquetas quedaban a un lado y
     los valores se iban fuera de la pantalla, con scroll horizontal adentro. */
  .data-table { display: block; width: 100% !important; min-width: 0 !important; border: none; }
  .data-table thead { display: none; }

  /* Las tablas de Admin usan .list-tbl y no tenían NINGUNA regla móvil: en
     celular desbordaban a lo ancho. Reciben el mismo tratamiento de tarjetas.
     Las etiquetas las completa fas-table-mobile.js leyendo el <th>. */
  .list-tbl { display: block; width: 100% !important; min-width: 0 !important; border: none; }
  .list-tbl thead { display: none; }
  .list-tbl tbody { display: flex; flex-direction: column; gap: 10px; }
  .list-tbl tbody tr {
    display: flex; flex-direction: column; padding: 12px 14px;
    background: var(--bg2); border: 1px solid var(--border); border-radius: 10px;
    box-shadow: 0 1px 3px rgba(0,0,0,.16);
  }
  .list-tbl tbody td {
    display: flex; align-items: baseline; gap: 10px;
    padding: 5px 0; border: none; border-bottom: 1px solid var(--border);
    font-size: 13px; white-space: normal; word-break: break-word; text-align: left;
  }
  .list-tbl tbody td:last-child { border-bottom: none; padding-top: 10px; }
  .list-tbl tbody td[data-label]::before {
    content: attr(data-label);
    flex: 0 0 38%; font-weight: 700; font-size: 10.5px; color: var(--text2);
    text-transform: uppercase; letter-spacing: .04em;
  }
  .list-tbl tbody td[colspan] { display: block !important; text-align: center; }
  .list-tbl tbody td[colspan]::before { content: none; }
  /* El contenedor con overflow-x deja de necesitar scroll: ya nada desborda.
     Iba enganchado al ATRIBUTO style —`div[style*="overflow-x"]`—, que se
     rompia el dia que alguien sacara el estilo inline. Ahora va por clase. */
  .data-table-wrap, .scroll-x { overflow-x: visible !important; background: none !important; }
  .data-table tbody { display: flex; flex-direction: column; gap: 10px; }
  .data-table tbody tr {
    display: flex; flex-direction: column; padding: 12px 14px;
    background: var(--bg2); border: 1px solid var(--border); border-radius: 10px;
  }
  .data-table tbody tr:nth-child(even) { background: var(--bg2); }
  .data-table tbody tr:hover { background: var(--bg2); }
  .data-table tbody td {
    display: flex; align-items: baseline; gap: 10px;
    padding: 6px 0; border: none; border-bottom: 1px solid var(--border);
    font-size: 13px; white-space: normal; word-break: break-word;
  }
  .data-table tbody td:last-child { border-bottom: none; padding-top: 10px; justify-content: center; }
  /* Lista de definición: etiqueta izquierda (ancho fijo) + valor. Las celdas SIN
     data-label muestran el valor a lo ancho (proveedor/estado/acción se explican solos). */
  .data-table tbody td[data-label]::before {
    content: attr(data-label);
    flex: 0 0 38%; font-weight: 700; font-size: 11px; color: var(--text2);
    text-transform: uppercase; letter-spacing: .04em;
  }
  /* La celda es flex, así que los <div> secundarios que trae adentro
     ("Cargada: …", "Timb. …", "Devuelta: …") se ponían AL LADO del valor y lo
     aplastaban hasta partir la fecha por la mitad: "15/07/202" + "6".
     Con esto bajan a su propia línea, debajo del dato principal. 06/08/2026. */
  .data-table tbody td[data-label] { flex-wrap: wrap; }
  /* 62 y no 100: la etiqueta de la izquierda se lleva el 38 %, así que un
     `flex-basis: 100%` MAS `margin-left: 38%` da 138 % y se sale de la
     tarjeta. Medido el 27/08/2026 en Facturación: los «Carg. 26/08/2026» y
     «Timb. 18045107» llegaban a 428 px en una pantalla de 360, y el desborde
     horizontal es lo que traba el gesto de scroll en el celular. */
  .data-table tbody td[data-label] > div {
    flex: 0 0 62%; margin-left: 38%; min-width: 0;
  }
  /* El nombre del proveedor trae max-width:280px + recorte con puntos suspensivos
     para la tabla de escritorio. En celular hay ancho de sobra: se deja entero. */
  .data-table tbody td > div[title] {
    max-width: none !important; white-space: normal !important; overflow: visible !important;
  }
  /* Celdas con colspan (mensajes vacíos / "sin resultados"): display BLOCK, no
     flex. Con flex, cada trocito de texto y cada <b> se vuelve un ítem flexible
     y el mensaje se renderiza en columnas verticales (una letra por línea). */
  .data-table tbody td[colspan] {
    display: block !important; text-align: center;
    white-space: normal; line-height: 1.55;
  }
  .data-table tbody td[colspan]::before { content: none; }
  /* La fila del mensaje no necesita borde ni fondo de tarjeta */
  .data-table tbody tr:has(td[colspan]) { background: none; border: none; box-shadow: none; padding: 4px; }

  /* ── TRAZABILIDAD en celular: tarjeta de producto, no lista de etiquetas ──
     Se reordena la fila para que se lea como una ficha: descripción como
     título, código debajo, badges de origen/estado arriba a la derecha y el
     resto como pares etiqueta/valor. Los campos sin dato se ocultan. */
  #tFilas tr {
    position: relative; padding: 14px; gap: 0;
    box-shadow: 0 1px 3px rgba(0,0,0,.18);
  }
  #tFilas td { padding: 5px 0; font-size: 13px; }
  #tFilas td.celda-vacia { display: none; }          /* sin dato → fuera */

  /* Título: descripción del artículo */
  #tFilas td.tz-desc {
    order: -3; display: block; padding: 0 68px 2px 0; border-bottom: none;
    font-size: 15px; font-weight: 700; line-height: 1.3; color: var(--text);
  }
  #tFilas td.tz-desc::before { content: none; }

  /* Subtítulo: código del artículo */
  #tFilas td.tz-codigo {
    order: -2; display: block; padding: 0 0 10px; border-bottom: 1px solid var(--border);
    font-family: ui-monospace, "Cascadia Code", Consolas, monospace;
    font-size: 12px; color: var(--text2); letter-spacing: .02em;
  }
  #tFilas td.tz-codigo::before { content: none; }

  /* El estado, arriba a la derecha. Hasta el 29/10/2026 iba debajo del origen
     («#36» o «Inventario»); desde entonces Registro e Ingreso FAS son pares
     etiqueta/valor, porque «Recibo de producción #4521» no entra de costado. */
  #tFilas td.tz-estado {
    position: absolute; right: 14px; top: 13px; padding: 0; border-bottom: none;
    display: block; width: auto;
  }
  #tFilas td.tz-estado::before { content: none; }

  /* Pares etiqueta/valor: etiqueta a la izquierda (ancho fijo), valor a la
     derecha ocupando el resto. `min-width:0` + `word-break` para que un texto
     largo baje de línea en vez de estirar la tarjeta. */
  #tFilas td[data-label]:not(.tz-desc):not(.tz-codigo):not(.tz-estado) {
    display: flex; align-items: baseline; gap: 10px;
    border-bottom: none; padding: 4px 0; text-align: left !important;
  }
  #tFilas td[data-label]:not(.tz-desc):not(.tz-codigo):not(.tz-estado)::before {
    flex: 0 0 40%; font-size: 10.5px; line-height: 1.4;
  }
  #tFilas td.th-right { text-align: left !important; }
  #tFilas tr { overflow: hidden; }              /* nada se escapa de la tarjeta */
  #tFilas td { min-width: 0; word-break: break-word; }
  #tFilas td.tz-desc { white-space: normal; }   /* el título baja de línea */

  /* ── REVISAR: split form + PDF → apilado (el PDF no entra al lado en celu) ── */
  .workspace   { flex-direction: column !important; overflow: visible !important; height: auto !important; }
  .panel-form  { width: 100% !important; max-width: none !important; flex: none !important; overflow: visible !important; }
  .panel-resizer { display: none !important; }
  .panel-pdf   { width: 100% !important; height: auto !important; min-height: 0 !important; flex: none !important; }

  /* ── VISOR DE PDF EN CELULAR ─────────────────────────────────────────────
     Los navegadores móviles NO renderizan un PDF embebido en un <iframe>:
     Chrome de Android muestra un fallback con texto crudo (base64) y un botón
     "Abrir". Por eso ocultamos el visor incrustado y ofrecemos abrirlo en el
     visor nativo del teléfono, que es la experiencia correcta en móvil.
     Lleva !important porque los enlaces traen estilos inline del template. */
  .pdf-viewer iframe, #pdfIframe { display: none !important; }
  .pdf-open-link {
    position: static !important; bottom: auto !important; right: auto !important;
    display: flex !important; align-items: center; justify-content: center; gap: 8px;
    margin: 16px !important; padding: 14px 18px !important;
    background: var(--accent) !important; color: #fff !important;
    font-size: 14px !important; font-weight: 700;
    border-radius: 8px !important; text-decoration: none;
  }
  /* En Cargar Factura el botón solo aparece cuando ya hay un PDF cargado */
  #pdfAbrirMovil { display: none !important; }
  #pdfAbrirMovil.tiene-pdf { display: flex !important; }
  /* Sin visor incrustado, los paneles no necesitan alto fijo */
  .col-pdf   { min-height: 0 !important; height: auto !important; }
  .pdf-empty { padding: 26px 16px; }

  /* ── Barras de búsqueda / acciones / topbar de página ── */
  .search-bar   { flex-wrap: wrap; padding: 10px 12px; }
  .page-topbar  { flex-direction: column; align-items: flex-start; gap: 10px; }
  .page-actions { flex-wrap: wrap; width: 100%; }
  .card         { margin-bottom: 14px; }

  /* ── Modales: casi full-width y scrollables ── */
  .modal-box { max-width: 95vw !important; width: 95vw !important; max-height: 88vh; overflow-y: auto; padding: 22px 18px; }
  .modal-box.modal-box-wide { max-width: 95vw !important; }
  .modal-actions { flex-wrap: wrap; }
  .modal-actions .modal-btn { flex: 1 1 auto; }

  /* ── Formularios ──────────────────────────────────────────────────────────
     OJO con `align-items`: al pasar .form-row a column, el eje transversal pasa
     a ser el horizontal. Como el desktop usa `align-items:flex-start`, los
     campos se ENCOGÍAN al contenido en vez de ocupar el ancho (por eso el RUC
     salía angosto y Razón Social —que no está en un form-row— salía ancho).
     Con `stretch` cada campo toma el ancho completo. */
  .form-row, .form-grid, .field-row {
    grid-template-columns: 1fr !important;
    flex-direction: column !important;
    align-items: stretch !important;
  }
  .form-row > div, .form-grid > div, .field-row > div { width: 100% !important; min-width: 0 !important; }
  input, select, textarea { font-size: 16px; }  /* 16px evita el zoom automático de iOS */

  /* ── TOPBAR pegado arriba ───────────────────────────────────────────────
     La hamburguesa es position:fixed; si el topbar se va con el scroll, se
     solapan. Sticky los mantiene alineados. Título en una línea con elipsis
     para que la barra no crezca a dos renglones. */
  .topbar { position: sticky; top: 0; z-index: 200; min-height: 52px; flex-wrap: nowrap; }
  .topbar-title { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; font-size: 13px; }
  .topbar-role-chip { flex-shrink: 0; font-size: 10px; }

  /* ── CARGAR FACTURA: form (340px fijo) + PDF (flex:1) → apilados ─────────
     En celu el formulario quedaba clavado en 340px y el panel del PDF se veía
     como una franja oscura al costado. Lleva !important porque el <style> del
     template se carga DESPUÉS de este archivo. */
  .upload-fill { flex-direction: column !important; overflow: visible !important; padding: 14px !important; min-height: 0 !important; }
  .col-form    { width: 100% !important; min-width: 0 !important; flex: none !important; overflow: visible !important; }
  .col-pdf     { width: 100% !important; flex: none !important; min-height: 55vh; }

  /* ── ADMIN: el panel lateral de 440px pasa a ancho completo ── */
  .modal-drawer { width: 100% !important; }

  /* ── RÓTULOS: la etiqueta (360px) no debe desbordar la pantalla ── */
  .rot-etq { max-width: 100%; }

  /* ── Botones más tocables ── */
  .btn-xs, .btn-sm { padding: 7px 12px; font-size: 12px; }

  /* El bottom-nav tapa el final del contenido si no se reserva espacio */
  .content-area { padding-bottom: 5.5rem !important; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   REFINAMIENTO MÓVIL — que se sienta APP, no una web achicada
   ---------------------------------------------------------------------------
   El bloque anterior resuelve que TODO ENTRE y se pueda usar. Este resuelve
   que se vea PROFESIONAL: jerarquía tipográfica, ritmo vertical, filtros que
   no coman media pantalla, feedback al tocar, y modales tipo hoja inferior
   (el patrón nativo de iOS/Android).
   Va al final a propósito: en empates de especificidad, gana lo de acá.
   ═══════════════════════════════════════════════════════════════════════════ */
@media (max-width: 768px) {

  /* ── 1. Tipografía y ritmo ──────────────────────────────────────────────
     En celu los títulos gigantes del escritorio roban pantalla. Se achican
     pero ganan peso, que es lo que da jerarquía en una pantalla chica. */
  .page-title     { font-size: 19px !important; line-height: 1.25; margin-bottom: 2px; }
  .page-subtitle  { font-size: 12.5px !important; line-height: 1.45; color: var(--text3); }
  /* Sin opacity: text3 ya es el gris terciario, y con .75 encima bajaba de 4,5:1. */
  .page-breadcrumb{ font-size: 11px; margin-bottom: 4px; }
  .section-title, .card-title { font-size: 14px !important; }
  .dash-scroll    { padding: 14px 12px !important; }

  /* ── 2. Filtros: de una columna gigante a una grilla compacta ───────────
     Antes cada filtro ocupaba una fila entera y había que scrollear para
     llegar al botón Buscar. Ahora entran de a dos y las acciones van juntas. */
  .ent-filtros, .log-filters, .rot-filtros {
    display: grid !important; grid-template-columns: 1fr 1fr; gap: 10px;
    padding: 12px; background: var(--bg2); border: 1px solid var(--border);
    border-radius: 12px; margin-bottom: 14px;
  }
  .ent-filtros .field-group, .log-filters .lf-group,
  .rot-filtros .field-group { max-width: none !important; width: auto !important;
                              min-width: 0 !important; }
  .ent-filtros input, .ent-filtros select,
  .rot-filtros input,
  .log-filters .lf-input { width: 100% !important; }
  /* Rótulos quedaba fuera de estas dos reglas: sus inputs conservaban el ancho
     por defecto del navegador, no entraban de a dos y la grilla se desarmaba en
     una sola columna. El formulario ocupaba 700 de los 800px del celular y
     había que scrollearlo entero para ver un resultado (05/08/2026).
     El lote es el valor más largo (MEB219100119) → fila completa. */
  .rot-filtros .field-group:nth-of-type(3) { grid-column: 1 / -1; }
  /* Los botones del bloque de filtros ocupan la fila completa y se reparten */
  .ent-filtros button, .log-filters button, .rot-filtros button {
    grid-column: span 1; width: 100%; min-height: 42px;
  }
  /* Las acciones de Trazabilidad viven en su propio contenedor (22/09/2026),
     así que acá es UNA celda del grid: se le da el ancho completo y adentro
     vuelve a ser una grilla de dos, que es como se veían antes.

     Se escribe con el padre —`.ent-filtros .ent-filtros-acciones`— a
     propósito: la regla de escritorio (`display:flex`) está MÁS ABAJO en el
     archivo, y con la misma especificidad ganaría ella aunque estemos en el
     media query del celular. Sin esto, los cuatro botones caían uno por
     renglón y el bloque de filtros se llevaba media pantalla. */
  .ent-filtros .ent-filtros-acciones {
    grid-column: 1 / -1;
    display: grid; grid-template-columns: 1fr 1fr; gap: 10px;
  }
  /* Los cuatro atajos de vencimiento entran en UNA fila: son chicos y van
     juntos. Como celda del grid quedaban de a dos, en dos renglones. El
     `margin-left:auto` los empuja a la derecha en escritorio; acá estorba. */
  .ent-filtros .ent-filtros-acciones .venc-rapido {
    grid-column: 1 / -1; margin-left: 0;
    /* Acá ya tienen renglón propio: la línea que los separa en escritorio
       queda colgando a la izquierda sin separar nada. */
    padding-left: 0; border-left: 0;
  }
  /* La × de un producto elegido es un `button` y estaba cayendo en la regla
     de arriba —`width: 100%`, 42 px de alto—: cada cápsula se inflaba hasta
     ocupar media fila. Es un botón de quitar, no una acción del formulario;
     24 px alcanzan y el área táctil la da la cápsula entera. */
  .ent-filtros .ent-elegido-x {
    width: auto; min-height: 24px;
  }
  .ent-filtros .ent-elegidos { grid-column: 1 / -1; }
  .field-group label, .lf-label {
    font-size: 10px !important; text-transform: uppercase; letter-spacing: .05em;
    color: var(--text3); margin-bottom: 3px; display: block;
  }

  /* ── 3. Tarjetas: feedback al tocar y jerarquía ─────────────────────────
     Sin :active la app se siente "muerta" al tocar. */
  .data-table tbody tr {
    box-shadow: 0 1px 3px rgba(0,0,0,.16);
    transition: transform var(--t-fast), background var(--t-fast);
  }
  .data-table tbody tr:active { transform: scale(.988); }
  .kpi-card, .card { border-radius: 12px; }
  .kpi-card:active { transform: scale(.98); }
  .kpi-label { font-size: 10.5px !important; letter-spacing: .04em; }
  .kpi-monto { font-size: 10.5px !important; line-height: 1.35; }

  /* ── 4. Botones: tamaño táctil real (44px es el mínimo recomendado) ─────*/
  .btn, .ent-btn, .rot-btn, .modal-btn { min-height: 42px; border-radius: 9px; }
  .btn-primary, .ent-btn:not(.sec), .rot-btn:not(.sec) { font-weight: 700; }
  /* La acción principal de una barra ocupa todo el ancho: es lo que se toca */
  .page-actions .btn-primary { width: 100%; justify-content: center; }
  button:active, .btn:active, .ent-btn:active, .rot-btn:active { opacity: .82; }

  /* ── 5. Modales como HOJA INFERIOR (bottom sheet) ───────────────────────
     Es el patrón nativo en móvil: sube desde abajo, ocupa el ancho completo
     y se cierra hacia abajo. Mucho más "app" que un cuadro centrado. */
  .ent-modal-bg, .rot-modal-bg, .modal-bg {
    align-items: flex-end !important; padding: 0 !important;
  }
  .ent-modal, .rot-modal, .modal-box {
    width: 100% !important; max-width: 100% !important;
    max-height: 92vh !important; overflow-y: auto;
    border-radius: 16px 16px 0 0 !important;
    padding: 16px 14px calc(16px + env(safe-area-inset-bottom)) !important;
    animation: fasSheetUp .22s ease-out;
  }
  @keyframes fasSheetUp { from { transform: translateY(24px); opacity: .6; } to { transform: none; opacity: 1; } }
  /* Barrita superior tipo "agarradera", como en las apps nativas */
  .ent-modal::before, .rot-modal::before {
    content: ""; display: block; width: 38px; height: 4px; border-radius: 2px;
    background: var(--border); margin: 0 auto 12px;
  }

  /* ── 6. Formularios: campos más altos y legibles ────────────────────────*/
  input, select, textarea {
    min-height: 44px; border-radius: 9px; padding: 10px 12px;
  }
  textarea { min-height: 88px; }
  input[type="checkbox"] { min-height: 0; width: 20px; height: 20px; }

  /* ── 7. Estados vacíos y mensajes: que no parezcan un error ─────────────*/
  .empty-state, .empty-text { padding: 22px 16px; font-size: 13px; line-height: 1.5; }

  /* ── 8. Chips / badges consistentes ─────────────────────────────────────*/
  .badge-estado, .chip, .tag { font-size: 10.5px; padding: 3px 9px; border-radius: 10px; }

  /* ── 9. Grilla del ingreso: scroll horizontal contenido ─────────────────
     Esta tabla NO se convierte en tarjetas (es una grilla de carga, se edita
     celda por celda). Se deja scrollear en su propio contenedor. */
  #nGrid, #nGridCard table { min-width: 780px; }
  #nGridCard { overflow-x: auto; -webkit-overflow-scrolling: touch; }
}

/* ═══ ESTRUCTURA APP: navegación inferior + acciones fijas ═══════════════════ */
@media (max-width: 768px) {

  /* ── Navegación inferior: look nativo ──────────────────────────────────── */
  .fas-bottom-nav {
    background: var(--bg2); border-top: 1px solid var(--border);
    -webkit-backdrop-filter: saturate(180%) blur(12px);   /* Safari */
    backdrop-filter: saturate(180%) blur(12px);
    box-shadow: 0 -2px 14px rgba(0,0,0,.22);
  }
  .fas-bottom-nav__item {
    flex: 1; gap: 3px; padding: 7px 4px; border-radius: 10px;
    color: var(--text3); background: none; border: none;
    transition: color var(--t-fast), background var(--t-fast);
  }
  .fas-bottom-nav__item svg  { width: 21px; height: 21px; }
  .fas-bottom-nav__item span { font-size: 10px; font-weight: 600; letter-spacing: .01em; }
  .fas-bottom-nav__item.active { color: var(--accent-texto, #a98ee0); background: color-mix(in srgb, var(--accent,#4B2C80) 12%, transparent); }
  .fas-bottom-nav__item:active { transform: scale(.94); }

  /* Ocultar la navegación inferior cuando hay una hoja/modal abierto:
     compiten visualmente y el modal ya ocupa la pantalla. */
  body:has(.ent-modal-bg.open) .fas-bottom-nav,
  body:has(.rot-modal-bg.open) .fas-bottom-nav,
  body:has(.modal-bg.open)     .fas-bottom-nav { display: none; }

  /* ── Barra de acción fija: la acción principal siempre al alcance ──────── */
  .mobile-action-bar {
    position: sticky; bottom: 0; z-index: 90;
    display: flex; gap: 8px; margin: 14px -12px 0;
    padding: 10px 12px calc(10px + env(safe-area-inset-bottom));
    background: var(--bg2); border-top: 1px solid var(--border);
    box-shadow: 0 -2px 10px rgba(0,0,0,.16);
  }
  .mobile-action-bar > * { flex: 1; min-height: 46px; }

  /* En el ingreso, la fila de confirmar se vuelve barra fija.
     OJO con el selector: `#nGridCard + div` apuntaba al bloque siguiente
     ("Entradas registradas"), no al botón — quedaba el título flotando y
     tapando la lista. El botón vive DENTRO de la tarjeta, como último hijo. */
  #nGridCard > div:last-child, .ent-confirm-row {
    position: sticky; bottom: 68px; z-index: 80;
    background: var(--bg2); padding: 10px 12px;
    border-top: 1px solid var(--border); border-radius: 0;
    margin: 12px -14px -13px; box-shadow: 0 -2px 10px rgba(0,0,0,.16);
    flex-direction: column; gap: 8px !important;
  }
  #nBtnConfirmar { width: 100%; min-height: 48px; font-size: 15px; }

  /* Los botones de acción dentro de modales/hojas: full width y apilados */
  .ent-modal .ent-btn, .rot-modal .rot-btn { flex: 1; }

  /* ── Encabezado de página más compacto ─────────────────────────────────── */
  .page-topbar { gap: 6px; margin-bottom: 10px; }

  /* ── Gráficos del panel Admin ───────────────────────────────────────────
     `.charts-grid` es de 2 columnas fijas: en celular la segunda tarjeta se
     salía de la pantalla. Pasa a una sola columna y con alto contenido, porque
     los lienzos venían con altura de escritorio y dejaban huecos enormes. */
  .charts-grid { grid-template-columns: 1fr !important; gap: 12px !important; margin-bottom: 14px !important; }
  .chart-card { padding: 12px !important; }
  .chart-card canvas { max-height: 210px !important; }
  .chart-title { font-size: 13px !important; }

  /* ── Historial / listados: que la fila se lea de un vistazo ────────────── */
  .data-table tbody td:first-child { font-weight: 600; }

  /* ── ÁREA TÁCTIL 44px — SOLO Depósito ───────────────────────────────────
     El personal opera de pie, con guantes y apurado: 44x44 es el mínimo
     recomendado para no errar el toque. Se limita a los selectores .ent-* y
     .rot-* a propósito: subirlo globalmente rompería la densidad de las
     tablas y paneles de Compras y Admin en escritorio. */
  .ent-btn, .rot-btn,
  .ent-bultos-toggle,
  .ent-modal button, .rot-modal button,
  .rot-card .col-menu-wrap > .btn {
    min-width: 44px; min-height: 44px;
  }
  /* Los checkbox son chicos por naturaleza: se agranda su zona de toque
     sin agrandar el cuadrito, usando padding en la celda que los contiene. */
  #rotBultos td:first-child, #drBody td:first-child {
    padding: 10px 8px; min-height: 44px;
  }
  #rotBultos input[type="checkbox"], .rot-chk {
    width: 22px; height: 22px; cursor: pointer;
  }
  /* Los campos de la grilla de ingreso ya son 42px por la regla de tarjetas;
     se llevan a 44 para cumplir el mínimo. */
  #nGrid .ent-grid-input { min-height: 44px; }
  .ent-bulto-input { height: 40px !important; }
  .ent-bulto-tag { line-height: 40px; }

  /* ── HAMBURGUESA integrada al topbar ───────────────────────────────────
     Era un recuadro BLANCO flotante (position:fixed, fondo claro, borde y
     sombra) sobre una barra oscura: se veía despegada, y al ocultarse la barra
     de direcciones del navegador parecía "moverse sola".
     Ahora se funde con el topbar: sin caja, líneas claras y alineada a su alto
     (52px), de modo que scrollee junto con él. */
  .mobile-menu-toggle {
    top: 0; left: 0; height: 52px; width: 50px;
    background: transparent; border: none; box-shadow: none;
    border-radius: 0; gap: 5px;
  }
  .mobile-menu-toggle:hover,
  .mobile-menu-toggle:focus { background: transparent; }
  .mobile-menu-toggle:active { background: rgba(255,255,255,.10); }
  .hamburger-line { background: #fff; width: 20px; height: 2px; }
  /* Con el menú abierto el fondo es oscuro: las líneas siguen claras */
  .mobile-menu-toggle.is-active .hamburger-line { background: #fff; }
  /* El título del topbar deja lugar al botón */
  .topbar { padding-left: 50px; padding-right: 10px; gap: 8px; }

  /* El botón "Volver a…" comía tanto ancho que el título quedaba reducido a un
     sliver ilegible. En celular la navegación ya está en la barra inferior, así
     que se deja solo la flecha (sin texto) y el título recupera su lugar. */
  .btn-back-top { flex-shrink: 0; padding: 6px 8px !important; font-size: 0 !important; gap: 0 !important; }
  /* Área táctil: la flecha sola medía 28 px de alto (medido el 21/09/2026). */
  .btn-back-top { min-width: 44px; min-height: 44px; justify-content: center; }
  .btn-back-top svg { width: 16px; height: 16px; }
  .topbar-sep { display: none; }            /* el separador no aporta en celular */
  .topbar-title { flex: 1; font-size: 14px !important; font-weight: 700; }
  /* El botón del menú lateral duplica a la hamburguesa: sobra en celular */
  .topbar > .tb-icon-btn:first-of-type { display: none; }

  /* ══ GRILLA DE INGRESO → TARJETA EDITABLE ═══════════════════════════════
     Cargar datos en una tabla con scroll horizontal es lo peor en un celular.
     Cada línea de la OC pasa a ser una ficha con sus campos apilados, que se
     completa con el pulgar sin mover la pantalla de lado.
     (Anula el `min-width:780px` + overflow-x del bloque anterior.) */
  #nGridCard { overflow-x: visible !important; }
  #nGrid, #nGridCard table { min-width: 0 !important; }
  #nGridCard table { display: block; }
  #nGridCard thead { display: none; }
  #nGrid { display: flex; flex-direction: column; gap: 12px; }

  #nGrid tr[data-idx] {
    display: grid; grid-template-columns: 1fr 1fr; gap: 8px 10px;
    padding: 13px 14px; background: var(--bg2);
    border: 1px solid var(--border); border-radius: 12px;
    box-shadow: 0 1px 3px rgba(0,0,0,.16);
  }
  #nGrid tr[data-idx] td { display: flex; flex-direction: column; gap: 4px; padding: 0; border: none;
                           min-width: 0; }
  /* min-width:0 es lo que permite que la celda se achique. Sin eso, una celda
     de grid nunca baja del tamaño natural de su contenido: el input de fecha
     trae un ancho intrínseco grande, empujaba la columna y se salían de la
     pantalla el Vencimiento, el Almacén y el botón "⋯" de bultos. La
     descripción quedaba cortada por lo mismo.
     Reproducido el 06/08/2026 a 360, 390 y 412px con la fila real. */
  #nGrid .ent-grid-input { min-width: 0; max-width: 100%; }
  #nGrid td[data-label="Bultos"] > div { min-width: 0; }
  #nGrid td.g-desc { overflow-wrap: anywhere; }

  /* Esta grilla comparte la clase `data-table` con la bandeja, así que le
     caían encima las reglas de tarjeta de allá: `flex-wrap: wrap` en la celda,
     el rótulo con `flex: 0 0 38%` y los <div> hijos con `margin-left: 38%`.
     Allá la celda va en dirección FILA y eso ordena bien; acá va en COLUMNA, y
     con `wrap` el rótulo se quedaba con el 38% de la ALTURA y el campo, al no
     entrar, se envolvía a una SEGUNDA COLUMNA a la derecha — fuera de la
     pantalla. Se salían Vencimiento, Almacén y el botón de bultos, y la
     descripción quedaba cortada.
     Medido el 06/08/2026 a 412px: cada campo arrancaba justo después de su
     rótulo y se pasaba exactamente el ancho de ese texto. */
  #nGrid tr[data-idx] td { flex-wrap: nowrap; }
  #nGrid td::before { flex: 0 0 auto; }
  #nGrid tr[data-idx] td > div { flex: 0 1 auto; margin-left: 0; }
  #nGrid td::before {
    content: attr(data-label); font-size: 10px; font-weight: 700;
    color: var(--text3); text-transform: uppercase; letter-spacing: .04em;
  }
  #nGrid td:not([data-label])::before { content: none; }

  /* Cabecera de la ficha: n° + código a la izquierda, descripción abajo. El
     almacén va DENTRO de la celda del artículo desde el 31/08/2026 —es el
     único que no necesita columna propia: sale de la orden, no se edita y
     nadie lo busca. */
  #nGrid td.g-num  { grid-column: 1; order: -3; flex-direction: row; align-items: center;
                     font-weight: 700; font-size: 13px; color: var(--text2); }
  #nGrid td.g-cod  { grid-column: 2; order: -3; align-items: flex-end; justify-content: center;
                     font-family: ui-monospace, Consolas, monospace; font-size: 12px; color: var(--text2); }
  #nGrid td.g-desc { grid-column: 1 / -1; order: -2; font-size: 14px; font-weight: 700;
                     line-height: 1.3; padding-bottom: 8px; border-bottom: 1px solid var(--border); }
  #nGrid td.g-ped  { grid-column: 1 / -1; order: -1; flex-direction: row; align-items: baseline;
                     justify-content: space-between; }

  /* Campos: alto cómodo y ancho completo dentro de su celda */
  #nGrid .ent-grid-input { width: 100% !important; min-height: 42px; font-size: 16px; }
  #nGrid td[data-label="Bultos"] > div { justify-content: flex-start !important; }
  #nGrid td[data-label="Bultos"] .ent-grid-input { width: 72px !important; }
  .ent-bultos-toggle { min-width: 40px; min-height: 42px; }

  /* Filas cerradas/completadas: ficha compacta de SOLO LECTURA.
     Ya no van todas las celdas en "—": ahora traen qué se recibió, con qué
     lote y en qué almacén, así que necesitan sus etiquetas como cualquier
     otra ficha. Se atenúan apenas (no .55, que dejaba el dato ilegible) y no
     tienen un solo campo adentro, que es lo que dice "esto no se edita". */
  #nGrid tr.ent-cerrada { display: block; opacity: .8; padding: 10px 14px; }
  #nGrid tr.ent-cerrada td { display: flex; gap: 8px; padding: 2px 0; border: 0; }
  #nGrid tr.ent-cerrada td::before {
    content: attr(data-label);
    flex: 0 0 40%;
    color: var(--text3);
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: .3px;
  }
  /* Código y artículo encabezan la ficha, sin etiqueta */
  #nGrid tr.ent-cerrada td[data-label="Línea"] { display: none; }
  #nGrid tr.ent-cerrada td[data-label="Código"],
  #nGrid tr.ent-cerrada td[data-label="Artículo"] { display: block; }
  #nGrid tr.ent-cerrada td[data-label="Código"]::before,
  #nGrid tr.ent-cerrada td[data-label="Artículo"]::before { content: none; }
  #nGrid tr.ent-cerrada td[data-label="Código"] { font-weight: 600; color: var(--text); }
  /* Bultos no se guarda por línea cerrada: no ocupa lugar en pantalla chica */
  #nGrid tr.ent-cerrada td[data-label="Bultos"] { display: none; }
  /* Las filas editables neutralizan el `margin-left: 38%` que .data-table le
     pone a los <div> de sus tarjetas, pero lo hacen con `tr[data-idx]` y esta
     fila no lo tiene: los chips de origen se iban al 38% de la pantalla. */
  #nGrid tr.ent-cerrada td > div { margin-left: 0; }

  /* Sub-fila del desglose por bulto: dentro de la ficha, no como fila aparte */
  .ent-bultos-subrow { display: block !important; }
  .ent-bultos-subrow td { display: block !important; padding: 0 !important; }

  /* ── TOASTS: en celular bajan desde arriba, a ancho completo ─────────────
     El patrón de escritorio (deslizar desde la derecha, ancho fijo) en un
     teléfono queda cortado y tapa contenido. */
  .fas-toast-container {
    top: 0; left: 0; right: 0; max-width: none;
    padding: calc(8px + env(safe-area-inset-top)) 10px 0;
  }
  .fas-toast {
    width: 100%; transform: translateY(-130%); border-radius: 12px;
    border-left-width: 5px; font-size: 13.5px;
    box-shadow: 0 6px 22px rgba(0,0,0,.32);
  }
  .fas-toast--visible { transform: translateY(0); }

  /* ── ESQUELETO DE CARGA: en vez de un texto "Buscando…" pelado ──────────
     Da sensación de app: el contenido "aparece" en lugar de parpadear. */
  .fas-skeleton {
    display: block; height: 62px; border-radius: 12px; margin-bottom: 10px;
    background: linear-gradient(100deg, var(--bg2) 30%, var(--bg3) 50%, var(--bg2) 70%);
    background-size: 220% 100%; animation: fasShimmer 1.2s infinite linear;
  }
  @keyframes fasShimmer { from { background-position: 180% 0; } to { background-position: -20% 0; } }

  /* ── SCROLL más natural en listas largas ───────────────────────────────── */
  .dash-scroll, .content-area { scroll-behavior: smooth; overscroll-behavior-y: contain; }

  /* ══ DENSIDAD: aprovechar la pantalla sin amontonar ═════════════════════
     El problema no era "poco espacio" sino espacio MAL REPARTIDO: márgenes
     grandes heredados del escritorio conviviendo con textos pegados entre sí.
     Se recorta el relleno exterior y se agranda el aire ENTRE datos, que es
     lo que hace legible una tarjeta en un teléfono. */

  /* El hueco grande arriba: .content-area traía 64px de padding superior
     (pensado para un encabezado fijo de escritorio). En celular el topbar ya es
     sticky, así que ese espacio quedaba vacío. Además su padding lateral se
     sumaba al del scroll interno (16+10=26px), comiendo ancho útil. */
  .content-area { padding: 0 0 5rem !important; }
  .dash-scroll, .ent-scroll, .rot-scroll { padding: 12px 12px !important; }
  /* El encabezado fijo compensa el margen de arriba: si cambia uno, el otro. */
  .ent-scroll, .rot-scroll { --scroll-pad-arriba: 12px; }

  /* Tarjetas: menos relleno, más separación entre ellas */
  .data-table tbody, .list-tbl tbody { gap: 8px; }
  .data-table tbody tr, .list-tbl tbody tr { padding: 11px 13px; }
  /* Aire real entre cada par etiqueta/valor (antes quedaban pegados) */
  .data-table tbody td, .list-tbl tbody td { padding: 6px 0; line-height: 1.45; }
  .data-table tbody td:last-child, .list-tbl tbody td:last-child { padding-bottom: 0; }

  /* Encabezados de sección: ocupaban tres renglones de aire */
  .page-topbar { margin-bottom: 8px !important; }
  .page-subtitle { margin-bottom: 10px !important; }
  h2, .section-title { margin-top: 16px !important; margin-bottom: 6px !important; }

  /* Tarjetas contenedoras: el relleno de escritorio desperdicia ancho */
  .ent-card, .rot-card, .card { padding: 12px !important; margin-bottom: 12px; }
  .kpi-grid { gap: 8px !important; margin-bottom: 12px;
              grid-template-columns: 1fr 1fr !important; }
  .kpi-card { padding: 10px 12px !important; gap: 2px; }
  /* Las tarjetas de monto y los accesos rápidos traen su tamaño a propósito
     (20px y 16px): antes como style inline, desde el 16/09/2026 como clase
     `.kpi-value--largo` (la pone tools/extraer_estilos_inline.py). Sin esta
     exclusión, el !important los pisaba y el importe partía en dos líneas. */
  .kpi-value:not([style*="font-size"]):not(.kpi-value--largo) { font-size: 26px !important; }
  .kpi-value { line-height: 1.15; margin: 2px 0; }
  .kpi-label { font-size: 10px !important; }
  .kpi-monto { font-size: 10.5px !important; line-height: 1.3; }
  /* `.kpi-value--largo` (o un `font-size` inline, si quedara alguno) marca las
     tarjetas de texto largo: accesos rápidos (16px), montos de Admin (18px) y
     montos de Compras (20px).
     Todas van a ancho completo. En media tarjeta, un importe real de Fusquim
     ("Gs. 1.231.032.455") partía "Gs." a una línea y CORTABA el último dígito
     contra el borde — pasaba en Compras y en Admin (05/08/2026).
     Regla general a propósito: si mañana se agrega otra tarjeta de monto, ya
     queda cubierta sin tener que tocar el CSS de nuevo. */
  .kpi-card:has(.kpi-value[style*="font-size"]), .kpi-card:has(.kpi-value--largo) { grid-column: 1 / -1; }
  .kpi-card:has(.kpi-value[style*="font-size"]) .kpi-value, .kpi-card:has(.kpi-value--largo) .kpi-value { white-space: nowrap; }

  /* ── Revisión de Documento: barra de acciones fija al pie ────────────────
     En celular esa pantalla mide ~3.700px y los botones de Aprobar/Rechazar
     quedaban hasta abajo de todo: había que recorrerla entera para aprobar una
     factura, que es LA acción del módulo (06/08/2026).
     `body .action-area` y no `.action-area` a secas: el <style> del template
     define el fondo con !important y, a igual especificidad, gana el que va
     después — el del template. Con el `body` delante, gana esta regla. */
  body .action-area {
    position: sticky; bottom: 0; z-index: 20;
    background: var(--bg2) !important;   /* opaco: el contenido pasa por debajo */
    padding: 10px 12px calc(10px + env(safe-area-inset-bottom));
    box-shadow: 0 -6px 18px rgba(0,0,0,.28);
    flex-direction: row; flex-wrap: wrap; gap: 8px;
  }
  /* La acción principal ocupa toda la fila; las secundarias comparten la de
     abajo. Así la barra mide ~2 filas en vez de 3 y no come media pantalla. */
  body .action-area .btn-action { width: auto; flex: 1 1 calc(50% - 4px); margin: 0; }
  body .action-area .btn-action.btn-approve,
  body .action-area .btn-action.btn-preapprove { flex-basis: 100%; }

  /* Filtros: más compactos, sin perder el área táctil */
  .ent-filtros, .log-filters, .rot-filtros { padding: 10px; gap: 8px; margin-bottom: 12px; }
  .field-group label, .lf-label { margin-bottom: 2px; }

  /* Legibilidad: nada operativo por debajo de 12px (se lee de pie, con
     iluminación variable). Solo lo accesorio queda chico. */
  .ent-chip, .badge-estado, .chip, .tag { font-size: 11.5px; }
  .data-table tbody td, .list-tbl tbody td { font-size: 13.5px; }
  .kpi-monto, .empty-text { font-size: 12px; }

  /* ── INTERACCIÓN: que la app "responda" al dedo ────────────────────────── */
  .data-table tbody tr, .list-tbl tbody tr,
  .kpi-card, .ent-card, .rot-card {
    -webkit-tap-highlight-color: transparent;   /* sin el recuadro azul feo */
  }
  /* Las filas que llevan a algún lado se sienten tocables */
  .list-tbl tbody tr[data-fas-click], .data-table tbody tr[data-fas-click],
  .row-depto, .kpi-link { cursor: pointer; }
  .list-tbl tbody tr[data-fas-click]:active, .data-table tbody tr[data-fas-click]:active,
  .row-depto:active { background: color-mix(in srgb, var(--accent,#4B2C80) 10%, var(--bg2)); }
  /* La fila seleccionada se distingue de un vistazo */
  .list-tbl tbody tr.selected { border-left: 3px solid var(--accent,#4B2C80); }

  /* ── ESCÁNER QR A PANTALLA COMPLETA ─────────────────────────────────────
     Escanear es una tarea de pantalla completa (así lo hace cualquier app).
     En una hoja chica el QR queda lejos y no engancha. */
  #entScanModal .ent-modal {
    width: 100% !important; height: 100% !important; max-height: 100% !important;
    border-radius: 0 !important; padding: 0 !important;
    background: #000 !important; display: flex; flex-direction: column;
  }
  #entScanModal .ent-modal::before { content: none; }   /* sin agarradera */
  #entScanModal .ent-modal > div:first-child {
    padding: calc(12px + env(safe-area-inset-top)) 14px 12px;
    color: #fff; position: relative; z-index: 2;
  }
  #entScanModal .ent-modal > div:first-child strong { color: #fff; }
  #entScanModal .scan-viewport { flex: 1; min-height: 0; }
}

/* ── Escáner: estilos comunes (aplican también en escritorio) ───────────────*/
.scan-viewport {
  position: relative; background: #000; overflow: hidden;
  border-radius: 10px; display: flex; align-items: center; justify-content: center;
}
.scan-viewport video { width: 100%; height: 100%; object-fit: cover; display: block; }
/* Marco de enfoque con esquinas, como las apps de escaneo */
.scan-frame {
  position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
  width: min(62vw, 260px); aspect-ratio: 1; pointer-events: none;
}
.scan-frame span {
  position: absolute; width: 30px; height: 30px;
  border: 3px solid var(--accent, #4B2C80); border-radius: 3px;
}
.scan-frame span:nth-child(1) { top: 0; left: 0; border-right: none; border-bottom: none; }
.scan-frame span:nth-child(2) { top: 0; right: 0; border-left: none; border-bottom: none; }
.scan-frame span:nth-child(3) { bottom: 0; left: 0; border-right: none; border-top: none; }
.scan-frame span:nth-child(4) { bottom: 0; right: 0; border-left: none; border-top: none; }
/* Línea que barre el marco: indica que está buscando */
.scan-frame::after {
  content: ""; position: absolute; left: 6px; right: 6px; height: 2px;
  background: linear-gradient(90deg, transparent, var(--accent, #4B2C80), transparent);
  animation: fasScanLine 2s ease-in-out infinite;
}
@keyframes fasScanLine { 0%,100% { top: 8%; opacity: .35; } 50% { top: 92%; opacity: 1; } }
.scan-msg {
  text-align: center; font-size: 13px; color: #fff; font-weight: 600;
  padding: 14px 16px 6px; background: #000;
}
/* Alternativa al escaneo en vivo: abre la camara DEL TELEFONO y lee el QR de
   la foto. La camara del navegador en Android da una imagen bastante peor que
   la app nativa (05/08/2026, Samsung A55). */
.scan-foto {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 10px 14px 0; background: #000;
}
.scan-foto .ent-btn { min-height: 44px; }
.scan-foto-hint { font-size: 11.5px; color: rgba(255,255,255,.6); flex: 1; min-width: 120px; }

.scan-manual {
  display: flex; gap: 8px; padding: 10px 14px calc(14px + env(safe-area-inset-bottom));
  background: #000;
}
.scan-manual input {
  flex: 1; min-height: 44px; border-radius: 9px; padding: 0 12px;
  background: rgba(255,255,255,.10); border: 1px solid rgba(255,255,255,.22); color: #fff;
}
.scan-manual input::placeholder { color: rgba(255,255,255,.55); }

/* En celular el chip de rol no cede espacio (flex-shrink:0) y el título queda
   cortado: "Revisió…", "Bandeja de Admi…". Saber en qué pantalla estás importa
   más que ver tu propio rol, que además sigue a un toque en el menú de perfil.
   Desde 481px (tablets en adelante) el chip vuelve. 06/08/2026. */
/* La Ayuda en el menú del perfil, solo en el celular (UX-11, 19/10/2026). */
.pm-item.pm-item--celular { display: none; }
@media (max-width: 480px) {
  .topbar-role-chip { display: none; }
  .tb-icon-btn.tb-ayuda--al-perfil { display: none; }
  .pm-item.pm-item--celular { display: flex; }
  /* Utilidad: texto que solo tiene sentido cuando hay ancho de sobra. Se usa
     para acortar títulos largos sin cambiar lo que se ve en la computadora
     (ej: "Bandeja de Administración" → "Administración"). */
  .solo-ancho { display: none; }
}

/* Pantallas muy chicas: solo se achica el texto, los filtros siguen de a dos */
@media (max-width: 360px) {
  .page-title { font-size: 17.5px !important; }
  .fas-bottom-nav__item span { font-size: 9px; }
}

/* Una sola columna recién acá. Antes esto estaba en el bloque de 360px, que es
   uno de los anchos de celular MÁS comunes: los 3 filtros y los 2 botones de
   Rótulos caían apilados y el formulario ocupaba 700 de los 800px de pantalla —
   había que scrollearlo entero para ver un resultado (05/08/2026).
   A 360px entran dos columnas de ~150px, de sobra para "Nro de OC" o "Código". */
@media (max-width: 339px) {
  .ent-filtros, .log-filters, .rot-filtros { grid-template-columns: 1fr !important; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   COMPORTAMIENTO NATIVO EN CELULAR
   ═══════════════════════════════════════════════════════════════════════════
   Lo que separa "una web abierta en el teléfono" de "una app": no es el
   diseño, son los gestos. Un botón que no se selecciona al mantenerlo
   apretado, una lista que no arrastra la página entera al llegar al final, un
   campo que no hace zoom al tocarlo. Esta capa unifica eso en TODO FAS —
   Facturación, Depósito y Admin— en vez de resolverlo pantalla por pantalla.
   06/08/2026. */

/* El teléfono agranda el texto solo al girar a horizontal y descuadra todo.
   Va fuera del @media porque aplica al documento entero. */
html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; }

@media (max-width: 768px) {

  /* ── 1. Gestos ──────────────────────────────────────────────────────────
     `manipulation` saca el retardo de ~300ms que el navegador espera por si
     viene un doble toque, y de paso evita el zoom accidental al tocar dos
     veces seguidas un botón. Es el cambio que más "responde como app" hace
     sentir a la interfaz. */
  button, a, .nav-item, .kpi-card, .tr-clickable, .oc-item, .rot-sug-item,
  label, summary, [role="button"], [data-fas-click] {
    touch-action: manipulation;
  }

  /* Mantener apretado un botón no debe seleccionar su texto ni abrir el menú
     de copiar: eso delata que es una página web. Los DATOS quedan
     seleccionables (más abajo) porque el usuario necesita copiar un número de
     factura o un lote. */
  .topbar, .fas-bottom-nav, .sidebar, .nav-item, button, .btn-action,
  .ent-btn, .rot-btn, .kpi-label, .page-breadcrumb, .tag, .badge-estado,
  .ent-chip, th, .sort-icon {
    -webkit-user-select: none; user-select: none;
    -webkit-touch-callout: none;
  }
  /* …pero todo lo que es dato, sí se puede seleccionar y copiar */
  td, .kpi-value, .detail-value, input, textarea, .rot-etq, .oc-item-num {
    -webkit-user-select: text; user-select: text; -webkit-touch-callout: default;
  }

  /* ── 2. Scroll ──────────────────────────────────────────────────────────
     `contain` evita el "scroll encadenado": al llegar al final de una lista o
     de un modal, el gesto dejaba de mover la lista y empezaba a arrastrar la
     página de atrás. En una app eso no pasa: la lista simplemente frena. */
  .ent-scroll, .rot-scroll, .content-area, .ent-modal, .rot-modal,
  .oc-dropdown, .rot-sug, .detail-panel, .modal-body, .panel-form {
    overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;    /* inercia al soltar el dedo */
  }
  /* El fondo de un modal abierto no se mueve detrás */
  .ent-modal-bg.open, .rot-modal-bg.open { overscroll-behavior: none; }

  /* Las barras de scroll no se muestran en móvil: ocupan lugar y ninguna app
     nativa las dibuja. El contenido sigue desplazándose igual. */
  .ent-scroll::-webkit-scrollbar, .rot-scroll::-webkit-scrollbar,
  .content-area::-webkit-scrollbar, .oc-dropdown::-webkit-scrollbar,
  .rot-sug::-webkit-scrollbar { width: 0; height: 0; }

  /* Con el encabezado pegado arriba, al saltar a un campo con error o a un
     ancla el navegador lo dejaba escondido DEBAJO de la barra. */
  html { scroll-padding-top: 64px; scroll-behavior: smooth; }

  /* ── 3. Tipografía ──────────────────────────────────────────────────────
     Escala pareja en los tres módulos. Nada operativo por debajo de 12.5px:
     esto se lee de pie, con guantes y con luz variable. */
  body { font-size: 14.5px; line-height: 1.45; }
  .page-title { line-height: 1.25; }
  .page-subtitle, .kpi-monto { line-height: 1.4; }
  /* El rótulo de cada dato en las tarjetas: chico pero legible y parejo */
  .data-table tbody td[data-label]::before,
  .list-tbl tbody td[data-label]::before,
  #nGrid td::before { font-size: 10.5px; }
  /* Los valores mandan sobre los rótulos */
  .data-table tbody td, .list-tbl tbody td { font-size: 14px; }

  /* Campos a 16px SIEMPRE. Si un campo mide menos, iOS hace zoom solo al
     tocarlo y el usuario queda con la pantalla agrandada y tiene que volver a
     achicarla con los dedos. Ya existía la regla general, pero los estilos
     propios de cada módulo (filtros a 13px, grilla a 12.5px) la pisaban por
     ser más específicos: por eso va con !important, que acá es la forma
     correcta y no un atajo. Medido: fallaba en Facturación, Depósito y Admin. */
  input:not([type="checkbox"]):not([type="radio"]):not([type="hidden"]),
  select, textarea { font-size: 16px !important; }

  /* ── 4. Respuesta al toque ──────────────────────────────────────────────
     Sin una reacción visible al dedo, la interfaz se siente "muerta". */
  button:active, .btn-action:active, .ent-btn:active, .rot-btn:active,
  .nav-item:active, .oc-item:active, .rot-sug-item:active {
    transform: scale(.97);
    transition: transform var(--t-fast);
  }

  /* ── 5. Área segura del teléfono ────────────────────────────────────────
     El indicador de inicio del iPhone y las barras de gestos de Android tapan
     la última franja: lo fijo abajo tiene que respetarla. */
  .fas-bottom-nav { padding-bottom: calc(6px + env(safe-area-inset-bottom)); }
  body .action-area { padding-bottom: calc(10px + env(safe-area-inset-bottom)); }
}

@media (max-width: 768px) {
  /* ── 6. Área táctil en TODO FAS ─────────────────────────────────────────
     44px es el mínimo que recomiendan Apple y Google: es aproximadamente el
     ancho de la yema de un dedo. Por debajo se falla el toque — y en depósito
     se opera de pie, con guantes y a veces con una sola mano.
     Estaba resuelto únicamente en Depósito, en sus clases propias: Facturación,
     Admin y el login quedaban con controles de 13 a 38px. Medido el 06/08/2026:
     entre 3 y 22 controles chicos por pantalla.
     OJO al comentar: escribir las clases de Depósito con comodín y barra cierra
     el comentario acá mismo y descarta la regla siguiente. Pasó con esta. */
  .nav-item, .btn-action, .ent-btn, .rot-btn, .btn-modal-cancel,
  .btn-modal-ok, .dp-tab, .btn-primary, .btn-secondary,
  button[type="submit"] { min-height: 44px; }

  /* Botones que son solo un ícono: cuadrados, para que el área coincida con
     lo que se ve. */
  .tb-icon-btn, .avatar-btn, .modal-close, .dp-close, .eye-btn,
  .ent-bultos-toggle, .mobile-menu-toggle {
    min-width: 44px; min-height: 44px;
    display: inline-flex; align-items: center; justify-content: center;
  }

  /* Paginación: son chicos por naturaleza, pero tienen que poder tocarse. */
  .pg-btn { min-width: 40px; min-height: 40px; }
  /* Y si igual no entran en el ancho, se parten en dos renglones: esconder
     el desborde dejaria paginas inalcanzables (auditoria del 15/09/2026). */
  .pg-btns { flex-wrap: wrap; justify-content: flex-end; row-gap: 6px; }

  /* Enlaces con forma de texto (miga de pan, "política de privacidad"): se
     agranda el ÁREA de toque con relleno vertical, sin tocar el tamaño de la
     letra — agrandar el texto ahí descuadraría la línea. */
  .bc-link, .btn-privacy, .link-btn, .oc-label {
    display: inline-flex; align-items: center; min-height: 44px;
  }

  /* ── 7. Legibilidad mínima ──────────────────────────────────────────────
     Nada operativo por debajo de 12.5px. Se lee de pie, con luz variable y
     con la pantalla a medio metro. */
  td, th, p, li, label, .detail-value, .detail-label, .pm-role, .pm-name,
  .feature-text, .feature-title, .login-sub, .kpi-monto {
    font-size: max(12.5px, 1em);
  }
}

@media (max-width: 768px) {
  /* ── 8. Los que quedaban fuera de la norma ──────────────────────────────
     Cierre de la pasada del 06/08/2026: controles y textos que no entraban en
     los selectores generales porque tienen clase propia o ninguna.
     Los `td` de la bandeja quedaban en 12px por una regla anterior más
     específica; se suben a 12.5px, que es el mínimo operativo del sistema. */
  .btn-fecha, .pass-toggle, .eye-toggle {
    min-width: 44px; min-height: 44px;
    display: inline-flex; align-items: center; justify-content: center;
  }
  /* Cualquier botón, tenga o no clase propia, tiene que poder tocarse. Va a 44
     y no a 40: al ponerlo en 40 este selector le GANABA en especificidad a las
     reglas de arriba (.modal-close, .btn-modal-cancel…) y les bajaba el mínimo
     en vez de subirlo. Un mínimo general nunca puede quedar por debajo de los
     particulares. */
  button:not(.pg-btn), a.btn { min-height: 44px; }
  .data-table tbody td, .list-tbl tbody td, p, .detail-value { font-size: 12.5px; }
  /* …salvo los valores de las tarjetas, que mandan y van más grandes */
  .data-table tbody td, .list-tbl tbody td { font-size: 14px; }
}

@media (max-width: 768px) {
  /* Textos con el tamaño escrito en el propio HTML: como estilo en línea (los
     que quedan) o como `.u-texto-chico`, que tools/extraer_estilos_inline.py
     le pone a todo lo que tenía font-size de 10 a 12px. Eso gana sobre
     cualquier hoja, así que acá el !important es la única vía y no un
     atajo. Se suben SOLO los valores de 10 a 12px —los que no se leen en un
     teléfono— y se deja intacto todo lo demás: los tamaños grandes puestos a
     propósito (montos, títulos) no se tocan. */
  [style*="font-size:10px"], [style*="font-size: 10px"],
  [style*="font-size:11px"], [style*="font-size: 11px"],
  [style*="font-size:12px"], [style*="font-size: 12px"],
  .u-texto-chico { font-size: 12.5px !important; }
}

/* ── Pantallas táctiles de más de 768 px (01/10/2026) ─────────────────────
   Los bloques de arriba dan 44 px de área táctil (URS de Depósito, RNF1)
   desde el 06/08, pero solo hasta 768 px de ancho. Una tablet acostada o un
   teléfono grande de costado pasan ese ancho y quedaban con los tamaños de
   escritorio (25 a 38 px, medido por la QA del 01/10). `pointer: coarse` es
   «se usa con el dedo»: en una PC con mouse no aplica y la densidad de
   escritorio no cambia. Mismos selectores que arriba, más los enlaces con
   forma de botón. */
@media (pointer: coarse) and (min-width: 769px) {
  .nav-item, .btn-action, .ent-btn, .rot-btn, .btn-modal-cancel,
  .btn-modal-ok, .dp-tab, .btn-primary, .btn-secondary,
  button:not(.pg-btn), a.btn { min-height: 44px; }
  .tb-icon-btn, .avatar-btn, .modal-close, .dp-close, .eye-btn,
  .ent-bultos-toggle, .mobile-menu-toggle, .btn-fecha, .pass-toggle, .eye-toggle {
    min-width: 44px; min-height: 44px;
    display: inline-flex; align-items: center; justify-content: center;
  }
  .pg-btn { min-width: 44px; min-height: 44px; }
  .bc-link, .btn-privacy, .link-btn, .oc-label {
    display: inline-flex; align-items: center; min-height: 44px;
  }
}

@media (max-width: 768px) {
  /* El código de artículo es dato operativo: se lee para verificar contra la
     etiqueta física, así que no puede quedar por debajo del mínimo. */
  #nGrid td.g-cod { font-size: 12.5px; }
  /* Con `.card-header-text p` a secas no alcanzaba: el <style> propio de la
     plantilla va DESPUÉS de esta hoja y gana a igual especificidad. */
  .card-header .card-header-text p { font-size: 12.5px; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   SEMÁFORO DE CONFIANZA DE LA LECTURA AUTOMÁTICA   ·   COMPUTADORA Y CELULAR
   ═══════════════════════════════════════════════════════════════════════════
   ORDEN DEL ARCHIVO — este bloque es de los COMUNES: define el componente y se
   ve igual en las dos pantallas. Todo lo que sea SOLO de celular (área táctil,
   gestos, tamaños de letra) vive más arriba, dentro de
   `@media (max-width: 768px)`, y no se mezcla acá. Si mañana el semáforo
   necesita algo distinto en el teléfono, va allá, no en este bloque.
   ───────────────────────────────────────────────────────────────────────────
   El OCR ya sabe de dónde sacó cada dato: del QR (oficial), de una etiqueta
   clara, o adivinado porque no la encontró. Ese cálculo existía y se
   descartaba: los 9 campos se mostraban exactamente iguales y había que
   revisarlos todos.
   Caso que lo motivó (04/08/2026, factura manuscrita 001-001-0003681): la
   fecha se tomó del timbrado y el monto quedó en 0, sin ninguna marca.
   Ahora el que carga ve DOS campos señalados en vez de doce iguales.
   07/08/2026. */

/* ── El semáforo: un punto al lado de cada etiqueta ──────────────────────────
   Verde = leído con certeza · Ámbar = deducido · Rojo = no lo pudo leer.
   Un texto ("✓ VERIFICADO") se pisaba con la etiqueta de la columna de al
   lado; el punto ocupa 8px y se lee de un vistazo. La explicación va en el
   `title`, al pasar el mouse. */
.punto-confianza {
  display: inline-block; width: 8px; height: 8px; border-radius: 50%;
  margin-left: 6px; flex-shrink: 0; vertical-align: middle; cursor: help;
  position: relative;
}
.punto-confianza.alta  { background: var(--tag-ok-c); }
.punto-confianza.media { background: var(--tag-warn-c); }
.punto-confianza.baja  { background: var(--tag-err-c); }

/* El rojo late: es lo único que exige atención, y el movimiento lleva el ojo
   ahí sin que haya que leer nada. El anillo crece y se desvanece; el punto
   queda quieto para que no moleste al leer. */
.punto-confianza.baja::after {
  content: ''; position: absolute; inset: 0; border-radius: 50%;
  background: var(--tag-err-c);
  animation: fas-latido 1.8s ease-out infinite;
}
@keyframes fas-latido {
  0%   { transform: scale(1);   opacity: .55; }
  70%  { transform: scale(2.6); opacity: 0; }
  100% { transform: scale(2.6); opacity: 0; }
}
/* Quien pidió menos movimiento en el sistema operativo no ve el latido */
@media (prefers-reduced-motion: reduce) {
  .punto-confianza.baja::after { animation: none; }
}

/* Campo que el sistema no pudo leer: se resalta en el mismo rojo del punto */
.campo-dudoso {
  border-color: var(--tag-err-c) !important;
  background: var(--tag-err-bg) !important;
}
.campo-dudoso:focus { box-shadow: 0 0 0 3px var(--tag-err-bg); }

/* En celular no hay cursor: el punto se puede tocar y el aviso aparece igual */
.punto-confianza:focus { outline: 2px solid var(--accent); outline-offset: 2px; }

/* El globo del aviso vive PEGADO AL BODY, no dentro del campo.
   Estando dentro, el panel del formulario —que tiene su propio scroll— lo
   recortaba y el texto salía cortado contra el borde (07/08/2026). Al colgarlo
   del body con posición fija, ningún contenedor puede recortarlo, y el JS lo
   ubica junto al punto cuidando que no toque los bordes de la pantalla. */
.globo-confianza {
  position: fixed; z-index: 3000; max-width: 240px;
  padding: 9px 12px; border-radius: 9px;
  background: var(--bg2); color: var(--text);
  border: 1px solid var(--border);
  box-shadow: 0 10px 28px rgba(0,0,0,.4);
  font-size: 12.5px; font-weight: 500; line-height: 1.4;
  text-align: left; pointer-events: none;
  opacity: 0; transform: translateY(4px);
  transition: opacity var(--t-fast), transform var(--t-fast);
}
.globo-confianza.visible { opacity: 1; transform: translateY(0); }

/* Panel de avisos de la lectura, arriba del formulario */
.avisos-ocr {
  display: flex; flex-direction: column; gap: 6px;
  padding: 11px 13px; margin-bottom: 14px;
  background: var(--tag-warn-bg); color: var(--tag-warn-c);
  border: 1px solid currentColor; border-radius: 9px;
  font-size: 12.5px; line-height: 1.4;
}
.avisos-ocr-titulo {
  font-weight: 700; display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
}
/* Cuántos campos hay que mirar. El detalle de cada uno está en su punto rojo:
   listarlo acá repetía el mismo texto dos veces en pantalla (07/08/2026). */
.avisos-ocr-cuenta {
  margin-left: auto; font-weight: 600; font-size: 11.5px; opacity: .85;
}

/* Panel con el texto crudo del OCR — SOLO aparece en testing (el backend no lo
   manda en producción). Diagnostica en el momento si un campo salió mal porque
   no se leyó o porque se leyó y se interpretó mal. 07/08/2026. */
.texto-ocr {
  margin-top: 14px; border: 1px dashed var(--border); border-radius: 9px;
  background: var(--bg3, var(--input-bg)); font-size: 12px;
}
.texto-ocr > summary {
  padding: 9px 12px; cursor: pointer; font-weight: 600; color: var(--text2);
  list-style: none; user-select: none;
}
.texto-ocr > summary::before { content: '▸ '; }
.texto-ocr[open] > summary::before { content: '▾ '; }
.texto-ocr pre {
  margin: 0; padding: 0 12px 12px; max-height: 320px; overflow: auto;
  white-space: pre-wrap; word-break: break-word;
  font-family: ui-monospace, Consolas, monospace; font-size: 11.5px;
  line-height: 1.45; color: var(--text2);
}

/* Texto que solo leen los lectores de pantalla. Se usa para el motivo por el
   que un campo quedó marcado: el punto del semáforo ya no recibe el tabulador
   —para no frenar la carga— así que la explicación se cuelga del campo con
   aria-describedby y se escucha al entrar en él. 07/08/2026. */
.solo-lectores {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}

/* ═══════════════════════════════════════════════════════════════════════════
   DEPÓSITO EN COMPUTADORA   ·   solo pantallas grandes
   ═══════════════════════════════════════════════════════════════════════════
   ORDEN DEL ARCHIVO: este bloque es EXCLUSIVO de escritorio. Lo de celular
   vive en `@media (max-width: 768px)` y no se toca desde acá. Si algo tiene
   que valer en las dos, va en los comunes, no duplicado en ambos lados.
   07/08/2026. */
@media (min-width: 769px) {

  /* La grilla de ingreso es una tabla ancha de verdad: código, descripción,
     cantidades, lote, vencimiento y almacén. Con el tope de 1240px del resto
     del módulo, en un monitor de 1920 quedaban 680px sin usar mientras el
     código se partía en tres renglones ("AF-" / "MAQ-" / "FAB-04") y la
     descripción en cinco. Se le deja tomar el ancho disponible. */
  /* Red de seguridad: si aun así no entra, se desplaza. Antes se RECORTABA —
     a 1920px la columna Almacén quedaba cortada ("ALMA" / "CUA") y no había
     forma de verla ni de editarla. Medido el 07/08/2026. */
  #nGridCard { max-width: none; overflow-x: auto; }
  #nGridCard table { width: 100%; min-width: 1080px; }

  /* En monitores anchos se levanta el tope del módulo: con 1240px fijos, en un
     1920 quedaban 680px sin usar mientras la última columna se recortaba.
     `body` delante porque el <style> del template va después de esta hoja.
     El resumen no se estira: sus tarjetas ya se acomodan solas. */
  @media (min-width: 1500px) {
    body .ent-wrap { max-width: 1560px; }
  }
  /* Con el menú apartado (Trazabilidad lo aparta si la tabla no entra) una
     notebook de 1366 tiene 1278 px, y el tope de 1240 se comía 38 que a la
     tabla le faltaban (02/10/2026). Solo se estira si hay lugar: con el menú
     abierto el espacio es menor que el tope y nada cambia. */
  @media (min-width: 1300px) and (max-width: 1499px) {
    body.sidebar-collapsed .ent-wrap { max-width: 1300px; }
  }

  /* El código de artículo no se parte: es un dato que se compara de un vistazo
     contra la etiqueta física. */
  #nGrid td.g-cod, #nGrid th:nth-child(4) { white-space: nowrap; }

  /* La descripción se queda con el espacio sobrante, que es la columna que
     realmente lo necesita. */
  #nGrid td.g-desc { min-width: 260px; }

  /* Los campos numéricos, cómodos para tres cifras sin quedar enormes */
  #nGrid .ent-grid-input { min-width: 64px; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   NOTEBOOK DE OFICINA   ·   1366×768, que es lo que tiene la mayoría
   ═══════════════════════════════════════════════════════════════════════════
   Medido el 11/08/2026: en 1366 la grilla de ingreso pide 1281px y tiene 1056,
   y la bandeja de facturas pide 1114 y tiene 1096. Las dos obligan a scroll
   horizontal. En 1920 no pasa nada, por eso no se veía.

   De dónde sale el ancho que sobra: `.data-table` usa `padding: 14px 16px`, o
   sea 32px por celda. Con 9 columnas son 288px SOLO de relleno — más que los
   225 que faltan. Un espaciado pensado para una tabla de lectura queda enorme
   en una grilla de carga, donde lo que importa es ver todas las columnas de
   una y no tener que llevar el mouse de costado con el proveedor esperando.

   Se toca SOLO en este tramo de anchos y SOLO en estas dos tablas. Un monitor
   grande sigue con el espaciado cómodo de siempre.
   ─────────────────────────────────────────────────────────────────────────── */
@media (min-width: 769px) and (max-width: 1599px) {
  #nGridCard .data-table th,
  #nGridCard .data-table td,
  #tablaBandeja th, #tablaBandeja td,
  .tabla-bandeja th, .tabla-bandeja td {
    padding-left: 8px;
    padding-right: 8px;
  }

  /* Estuvo en 6px peleando por el ancho, subio a 10 cuando Mauricio dijo que
     las columnas se juntaban, y queda en 8.

     Volvio el scroll: ensanchar el campo de cantidad a 96 y el del bulto a 88
     -para que el numero no se cortara- sumo unos 144px, y el relleno de 10 era
     44 de esos.

     Y 8 es MEJOR que 10 para lo que se buscaba: el aire de zona son 26px, asi
     que contra 8 el contraste es mas fuerte que contra 10. Las tres zonas se
     distinguen mas, no menos. */
  #nGridCard .data-table th,
  #nGridCard .data-table td { padding-left: 8px; padding-right: 8px; }

  /* Acá vivían las reglas del campo de almacén. Se fueron con la columna el
     31/08/2026: el almacén ya no es un input de sólo lectura, es texto dentro
     de la celda del artículo. */

  /* La descripción cede primero: es la única que puede seguir leyéndose
     recortada, y el texto completo queda en el title del campo. */
  #nGrid td.g-desc { min-width: 0; }

  /* Los últimos 24px, de los dos campos que iban más holgados. El lote de
     proveedor sigue mostrando 12 caracteres y la cantidad, cinco cifras —
     de sobra para lo que se carga acá. La fecha NO se toca: por debajo de
     130px Chrome le come el calendario. */
  /* El lote del proveedor a 96px. Los que se cargan de verdad son cortos
     -"MEB326050091" son doce caracteres y es de los mas largos que hay en el
     sistema viejo-, asi que los 8px que sobraban valen mas en el ancho total.
     El texto completo queda igual: el campo scrollea al escribir. */
  #nGrid .ent-grid-input.txt[data-f="lote_prov"] { width: 96px; }
  /* La cantidad NO se achica. Estaba en 72px para ganar ancho y cortaba un
     digito de "400,000000" — lo vio Mauricio en una captura el 31/08/2026.
     Una cantidad son siempre seis decimales; el numero a medias es peor que
     los 24px que se ahorraban, y esos 24px ya volvieron cuando el menu paso a
     apartarse al traer la orden. */

  /* CARGAR FACTURA — el botón de confirmar quedaba FUERA DE PANTALLA.
     Medido el 13/08/2026: en 1366x768 el botón cae en y=807 con una ventana de
     768, y la columna del formulario pide 714px de los que se ven 599. O sea
     que cada factura que se carga obliga a scrollear para encontrar la acción
     principal. En 1920 entra, por eso no se veía.

     Se ancla al pie de la columna, que ya tiene scroll propio. En celular la
     página entera scrollea y el botón aparece al final, que es lo normal: por
     eso esta regla arranca en 769px y no lo toca.

     Se ancla el CONTENEDOR y no el botón: deshabilitado usa `opacity`, así que
     pegado directo se transparentaba y se leía el texto que pasaba por detrás. */
  .col-form-pie {
    position: sticky;
    bottom: 0;
    z-index: 5;
    background: var(--bg2);
    padding-top: 10px;
    margin-top: auto;
    box-shadow: 0 -8px 12px -8px rgba(0, 0, 0, .35);
  }
}

/* Estado vacío de las listas de Depósito. En un monitor de 1920 una sola línea
   flotando en 900px de nada no dice qué hacer; ahora explica las opciones. Vale
   igual en celular, por eso va en los comunes. 07/08/2026. */
.estado-vacio { padding: 26px 18px; text-align: center; }
.estado-vacio-titulo { font-size: 14px; font-weight: 700; color: var(--text2); margin-bottom: 5px; }
.estado-vacio-texto { font-size: 12.5px; color: var(--text3); line-height: 1.5; max-width: 460px; margin: 0 auto; }
.estado-vacio-texto b { color: var(--text2); font-weight: 600; }

/* ══════════════════════════════════════════════════════════════════════════
   ACABADO DE PRODUCTO · 13/08/2026
   ══════════════════════════════════════════════════════════════════════════
   Todo lo de aca es SOLO color, sombra, transicion y separacion vertical.
   Ni un ancho, ni un alto, ni un display: eso es lo que causa desbordes y es
   justo lo que no hay que tocar despues de haber medido las 19 pantallas.

   Se apoya en el marcado que YA existe y es igual en los 6 templates
   (.nav-item, .sidebar-section-title, .data-table, .card), asi que aplica a
   Facturacion, Deposito y Admin de una sola vez sin editar un solo HTML.
   ══════════════════════════════════════════════════════════════════════════ */

/* ── Sidebar: jerarquia y respuesta al mouse ─────────────────────────────── */

/* Las secciones se leian como texto suelto. Una linea corta arriba las separa
   en grupos sin ocupar mas alto del que ya ocupaban. */
.sidebar-section-title {
    position: relative;
    padding-top: 18px;
    margin-top: 2px;
    color: rgba(255, 255, 255, .38);
}
.sidebar-section-title::before {
    content: "";
    position: absolute;
    top: 8px;
    left: 16px;
    right: 16px;
    height: 1px;
    background: rgba(255, 255, 255, .07);
}
.sidebar-logo-wrap + .sidebar-section-title { padding-top: 6px; }
.sidebar-logo-wrap + .sidebar-section-title::before { display: none; }

/* El item respondia solo con un cambio de fondo. Ahora acompana con un
   desplazamiento minimo, que es lo que hace que se sienta vivo sin animar
   nada llamativo. */
.nav-item {
    transition:
        background var(--fas-transition-fast), 
        color var(--fas-transition-fast),
        border-color var(--fas-transition-fast),
        padding-left var(--fas-transition-fast);
}
.nav-item:hover { padding-left: 20px; }
.nav-item.active {
    /* El dorado del borde izquierdo se refuerza con un lavado del mismo tono,
       para que el item activo se ubique de un vistazo en una lista larga
       —el sidebar de Admin tiene 19—. */
    background: linear-gradient(90deg, rgba(75, 44, 128, .45) 0%, rgba(75, 44, 128, .22) 100%);
    font-weight: 600;
}
.nav-item:focus-visible {
    outline: 2px solid var(--gold);
    outline-offset: -2px;
}

/* ── Tablas: que se sepa donde esta parado el ojo ────────────────────────── */

.data-table tbody tr {
    transition: background var(--fas-transition-fast);
}
.data-table tbody tr:hover {
    background: var(--hover-bg);
}
/* Llegando por teclado tambien tiene que verse. Sin esto, tabular por una
   bandeja de 50 filas es adivinar. */
.data-table tbody tr:focus-visible {
    outline: 2px solid var(--accent-lt);
    outline-offset: -2px;
}
/* ── Cards: profundidad, no decoracion ───────────────────────────────────── */

.card {
    box-shadow: var(--fas-shadow-sm);
    transition: box-shadow var(--fas-transition-normal), border-color var(--fas-transition-normal);
}
.kpi-card {
    transition:
        box-shadow var(--fas-transition-normal), 
        border-color var(--fas-transition-normal),
        transform var(--fas-transition-normal);
}
/* Solo las tarjetas que llevan a algun lado se levantan. Si se levantaran
   todas, el gesto dejaria de significar "esto se puede apretar". */
.kpi-card.kpi-link:hover {
    box-shadow: var(--fas-shadow-md);
    border-color: var(--accent-lt);
    transform: translateY(-2px);
}
.kpi-card.kpi-link { cursor: pointer; }

/* ── Estado vacio: que no parezca que se rompio ──────────────────────────── */

.empty-state svg { opacity: .3; }
.empty-state h4 { font-size: 15px; }
.empty-state p { margin-bottom: 4px; }

/* ── Respeto por quien pidio menos movimiento ────────────────────────────── */

@media (prefers-reduced-motion: reduce) {
    .nav-item:hover { padding-left: 16px; }
    .kpi-card.kpi-link:hover { transform: none; }
}

/* ── Tabs de estado sobre una tabla ──────────────────────────────────────────
   Reemplazan al select para los filtros que se usan todo el tiempo: el estado
   se ve y se cambia de un clic, sin desplegar nada. El numero al lado no viene
   del backend, se cuenta de las filas visibles en la tabla. */
.estado-tabs {
    display: flex;
    align-items: center;
    gap: 4px;
    flex-wrap: wrap;
    padding: 12px 18px 0;
    border-bottom: 1px solid var(--border);
}
.estado-tab {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    padding: 8px 13px;
    margin-bottom: -1px;
    border: none;
    border-bottom: 2px solid transparent;
    background: none;
    color: var(--text2);
    font-family: inherit;
    font-size: 12.5px;
    font-weight: 600;
    cursor: pointer;
    white-space: nowrap;
    transition: color var(--fas-transition-fast), border-color var(--fas-transition-fast);
}
.estado-tab:hover { color: var(--text); }
.estado-tab.active {
    color: var(--accent-texto);
    border-bottom-color: var(--accent-lt);
}
.estado-tab:focus-visible {
    outline: 2px solid var(--accent-lt);
    outline-offset: -2px;
    border-radius: 4px;
}
.estado-tab-n {
    min-width: 20px;
    padding: 1px 6px;
    border-radius: 10px;
    background: var(--bg4);
    color: var(--text2);
    font-size: 11px;
    font-weight: 700;
    text-align: center;
    font-variant-numeric: tabular-nums;
}
.estado-tab.active .estado-tab-n { background: var(--accent); color: #fff; }
/* En cero se apaga, no se esconde: esconderlo mueve los demas de lugar y se
   pierde la referencia de donde estaba cada uno. */
.estado-tab--vacio { opacity: .45; }
/* "Con error" es lo unico de esta pantalla que exige que alguien haga algo hoy,
   asi que se distingue —salvo cuando esta en cero, que es lo normal—. */
.estado-tab--alerta:not(.estado-tab--vacio) .estado-tab-n {
    background: var(--tag-err-bg);
    color: var(--tag-err-c);
}
.estado-tab--alerta:not(.estado-tab--vacio) { color: var(--tag-err-c); }

@media (max-width: 768px) {
    .estado-tabs {
        padding: 10px 12px 0;
        overflow-x: auto;
        flex-wrap: nowrap;
        /* La barra se esconde a proposito: debajo de una fila de pestañas, en
           un telefono, ocupa mas de lo que aporta. Pero entonces NADA avisaba
           que habia mas pestañas a la derecha, y eso es peor. El aviso lo da
           la sombra del borde (.scroll-x), que ademas se apaga sola al llegar
           al final. */
        scrollbar-width: none;
        --scroll-fade: var(--bg2);
        background:
            linear-gradient(to right, var(--scroll-fade) 40%, transparent)
                left center / 24px 100% no-repeat local,
            linear-gradient(to left, var(--scroll-fade) 40%, transparent)
                right center / 24px 100% no-repeat local,
            radial-gradient(farthest-side at 0 50%,
                rgb(0 0 0 / 18%), transparent) left center / 12px 100% no-repeat scroll,
            radial-gradient(farthest-side at 100% 50%,
                rgb(0 0 0 / 18%), transparent) right center / 12px 100% no-repeat scroll;
    }
    .estado-tabs::-webkit-scrollbar { display: none; }
}

/* ── Requiere tu atención ────────────────────────────────────────────────────
   Bloque de alertas del dashboard. Aparece solo cuando hay algo que atender:
   un panel siempre visible y siempre vacio se vuelve invisible a las dos
   semanas y deja de cumplir su funcion. */
.atencion {
    background: var(--bg2);
    border: 1px solid var(--border);
    border-left: 3px solid var(--tag-warn-c);
    border-radius: var(--radius);
    padding: 14px 16px;
    margin-bottom: 20px;
}
.atencion-head {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 11px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .7px;
    color: var(--tag-warn-c);
    margin-bottom: 12px;
}
.atencion-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
    gap: 10px;
}
.atencion-card {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 12px 14px;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--bg3);
    font-family: inherit;
    text-align: left;
    cursor: pointer;
    transition: border-color var(--fas-transition-fast), box-shadow var(--fas-transition-fast);
}
.atencion-card:hover { border-color: var(--accent-lt); box-shadow: var(--fas-shadow-sm); }
.atencion-card:focus-visible { outline: 2px solid var(--accent-lt); outline-offset: 2px; }
.atencion-n {
    flex: none;
    min-width: 34px;
    padding: 4px 8px;
    border-radius: var(--radius);
    font-size: 17px;
    font-weight: 800;
    text-align: center;
    font-variant-numeric: tabular-nums;
}
.atencion-txt { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; }
.atencion-txt strong { font-size: 13px; font-weight: 700; color: var(--text); line-height: 1.25; }
.atencion-txt small { font-size: 11.5px; color: var(--text2); line-height: 1.35; }
.atencion-ir {
    flex: none;
    font-size: 11.5px;
    font-weight: 700;
    color: var(--accent-texto);
    white-space: nowrap;
}
.atencion-card--err  .atencion-n { background: var(--tag-err-bg);  color: var(--tag-err-c); }
.atencion-card--warn .atencion-n { background: var(--tag-warn-bg); color: var(--tag-warn-c); }
.atencion-card--info .atencion-n { background: var(--tag-blue-bg); color: var(--tag-blue-c); }

/* Todo al día — el contrapunto del bloque de arriba. */
.atencion-ok {
    display: flex;
    align-items: center;
    gap: 9px;
    padding: 11px 15px;
    margin-bottom: 20px;
    border: 1px solid var(--border);
    border-left: 3px solid var(--tag-ok-c);
    border-radius: var(--radius);
    background: var(--bg2);
    color: var(--text2);
    font-size: 13px;
}
.atencion-ok svg { color: var(--tag-ok-c); flex: none; }
.atencion-ok strong { color: var(--text); }

/* ── Selector de columnas de una tabla ───────────────────────────────────────
   La preferencia vive en el navegador de quien la elige. */
.col-menu-wrap { position: relative; margin-left: auto; }
.col-menu {
    position: absolute;
    right: 0;
    top: calc(100% + 6px);
    z-index: var(--fas-z-dropdown);
    min-width: 190px;
    padding: 6px;
    background: var(--bg2);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--fas-shadow-lg);

    /* En Trazabilidad la tabla tiene 13 columnas, así que este menú tiene 13
       casillas más el título y el botón del pie. Sin tope de altura, en una
       pantalla de 320 px medía más que la ventana: «Línea de tiempo» quedaba
       tapada y «Mostrar todas» directamente fuera, sin forma de llegar —el
       menú es `position:absolute`, así que la página tampoco scrollea hasta
       él—. Con el tope, el propio menú scrollea. */
    max-height: min(70vh, 420px);
    max-width: calc(100vw - 24px);
    overflow-y: auto;
    overscroll-behavior: contain;   /* que el scroll no se contagie a la página */
}
.col-menu-tit {
    padding: 6px 9px 8px;
    font-size: 10px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .7px;
    color: var(--text3);
}
.col-menu-item {
    display: flex;
    align-items: center;
    gap: 9px;
    padding: 7px 9px;
    border-radius: 6px;
    font-size: 12.5px;
    color: var(--text);
    cursor: pointer;
}
.col-menu-item:hover { background: var(--hover-bg); }
.col-menu-item input { accent-color: var(--accent); cursor: pointer; }
.col-menu-reset {
    width: 100%;
    margin-top: 4px;
    padding: 7px 9px;
    border: none;
    border-top: 1px solid var(--border);
    /* Pegado al pie del menú aunque la lista scrollee: es la salida cuando
       alguien escondió columnas y no se acuerda cuáles. Tener que scrollear
       hasta el final para encontrarla es justo lo que pasaba en 320 px.
       Fondo opaco a propósito: sin él las casillas se leen por debajo. */
    position: sticky;
    bottom: -6px;                    /* compensa el padding del contenedor */
    background: var(--bg2);
    color: var(--accent-texto);
    font-family: inherit;
    font-size: 12px;
    font-weight: 600;
    text-align: left;
    cursor: pointer;
    border-radius: 0 0 6px 6px;
}
.col-menu-reset:hover { background: var(--hover-bg); }


/* ══════════════════════════════════════════════════════════════════════════
   ELEVACIÓN, MICROINTERACCIONES Y MOVIMIENTO · 14/08/2026
   ══════════════════════════════════════════════════════════════════════════
   El sistema se veia plano por una razon concreta: casi todo usaba el mismo
   --bg2 con un borde de 1px, y las sombras que habia no se ven sobre fondo
   oscuro. La escala --bg → --bg4 ya existia y no se estaba usando.

   La profundidad se resuelve DISTINTO en cada tema, y esto es lo que suele
   hacerse mal:

     · En CLARO  la profundidad es sombra: lo que esta arriba proyecta.
     · En OSCURO la sombra casi no se percibe. Ahi lo que esta arriba es mas
       CLARO, y lleva una linea de luz de 1px en el borde superior —como si le
       diera una luz cenital—. Es lo que hace que una tarjeta se despegue del
       fondo sin inventar un gris raro.

   Por eso hay dos definiciones y no una sola con opacidades distintas.
   ══════════════════════════════════════════════════════════════════════════ */

:root {
    /* Sombras reales. En claro hacen el trabajo; en oscuro se refuerzan abajo. */
    --elev-1: 0 1px 2px rgba(16, 18, 32, .06), 0 1px 3px rgba(16, 18, 32, .04);
    --elev-2: 0 2px 6px rgba(16, 18, 32, .08), 0 4px 14px rgba(16, 18, 32, .06);
    --elev-3: 0 8px 20px rgba(16, 18, 32, .12), 0 18px 44px rgba(16, 18, 32, .10);
    /* Linea de luz superior. En claro no hace falta. */
    --luz-alta: none;
}

[data-theme="dark"] {
    /* Mas profundas y opacas: sobre fondo oscuro una sombra suave no existe. */
    --elev-1: 0 1px 2px rgba(0, 0, 0, .35);
    --elev-2: 0 4px 12px rgba(0, 0, 0, .45);
    --elev-3: 0 14px 40px rgba(0, 0, 0, .6);
    /* El truco que reemplaza a la sombra: el borde de arriba recibe luz. */
    --luz-alta: inset 0 1px 0 rgba(255, 255, 255, .05);
}

/* ── Nivel 1 · lo que descansa sobre la pagina ───────────────────────────── */
.card,
.chart-wrap,
.kpi-card,
.atencion,
.atencion-ok {
    box-shadow: var(--elev-1), var(--luz-alta);
}

/* ── Nivel 2 · lo que se levanta al interactuar ──────────────────────────── */
.kpi-card.kpi-link:hover,
.atencion-card:hover,
.card:hover { box-shadow: var(--elev-2), var(--luz-alta); }

/* ── Nivel 2 · lo que flota sobre la pagina ──────────────────────────────── */
.col-menu,
.notif-dropdown,
.rot-sug { box-shadow: var(--elev-2), var(--luz-alta); }

/* ── Nivel 3 · lo que interrumpe ─────────────────────────────────────────── */
.modal-box,
.rot-modal,
.center-modal { box-shadow: var(--elev-3), var(--luz-alta); }

/* ── Jerarquia de superficie ─────────────────────────────────────────────── */

/* La cabecera de una tabla no es la tabla: se hunde un escalon para que las
   filas queden por encima y el ojo entienda cual es el contenido. */
.data-table thead th { box-shadow: inset 0 -1px 0 var(--border); }

/* La barra de herramientas se hunde respecto de la tarjeta: asi se lee como
   controles DE la tabla y no como parte del contenido. */
.search-bar,

/* ── Microinteracciones ──────────────────────────────────────────────────── */

/* Un boton que se hunde al apretarlo confirma el clic antes de que llegue la
   respuesta del servidor. Es la diferencia entre "aprete" y "creo que aprete". */
.btn, .rot-btn, .pg-btn, .btn-fecha, .modal-btn {
    transition:
        background var(--fas-transition-fast), 
        border-color var(--fas-transition-fast),
        color var(--fas-transition-fast),
        box-shadow var(--fas-transition-fast),
        transform var(--t-fast);
}
.btn:active:not(:disabled),
.rot-btn:active:not(:disabled),
.modal-btn:active:not(:disabled) { transform: scale(.975); }

/* Los campos avisan que estan activos con el color de la marca, no con el
   azul del navegador. */
input:not([type="checkbox"]):not([type="radio"]):focus,
select:focus,
textarea:focus,
.search-input:focus,
.dp-input:focus {
    border-color: var(--accent-lt);
    box-shadow: 0 0 0 3px var(--active-bg);
}

/* ── El periodo deja de competir con los tabs ────────────────────────────── */
/* Los dos filtran, pero el estado es la pregunta principal y el periodo es un
   ajuste. Se veian igual de fuertes y por eso parecian redundantes. */
.btn-fecha {
    padding: 3px 10px;
    font-size: 10.5px;
    background: transparent;
    border-color: transparent;
}
.btn-fecha:hover { background: var(--bg4); border-color: var(--border); }
.btn-fecha.active {
    background: var(--active-bg);
    border-color: var(--accent-lt);
    color: var(--accent-texto);
}
.date-filter-label { font-size: 10.5px; text-transform: uppercase; letter-spacing: .5px; color: var(--text3); }

/* ── Movimiento ──────────────────────────────────────────────────────────── */

/* Al cambiar de pantalla el contenido entra: sin eso el cambio es un salto y
   cuesta ubicar que cambio. Son 180ms, no se percibe como espera. */
.page.active { animation: fas-fadeInUp 180ms var(--ease-smooth, ease-out) both; }

/* Las filas aparecen escalonadas, pero SOLO las primeras: encadenar 50 filas
   deja la tabla temblando y molesta mas de lo que ayuda. */
.data-table tbody tr:nth-child(1) { animation: fas-fadeIn 160ms ease-out both; }
.data-table tbody tr:nth-child(2) { animation: fas-fadeIn 160ms ease-out both; animation-delay: 25ms; }
.data-table tbody tr:nth-child(3) { animation: fas-fadeIn 160ms ease-out both; animation-delay: 50ms; }
.data-table tbody tr:nth-child(4) { animation: fas-fadeIn 160ms ease-out both; animation-delay: 75ms; }
.data-table tbody tr:nth-child(5) { animation: fas-fadeIn 160ms ease-out both; animation-delay: 100ms; }

/* Los modales entran con escala desde el centro: dice de donde salio la cosa. */
.modal-box, .rot-modal { animation: fas-scaleIn 160ms var(--ease-smooth, ease-out) both; }
.col-menu { animation: fas-fadeInUp 130ms ease-out both; }

/* Scroll: al saltar a una seccion, que acompañe en vez de teletransportar. */
html { scroll-behavior: smooth; }

/* ── Quien pidio menos movimiento no recibe ninguno ──────────────────────── */
@media (prefers-reduced-motion: reduce) {
    html { scroll-behavior: auto; }
    .page.active,
    .modal-box, .rot-modal, .col-menu,
    .data-table tbody tr { animation: none !important; }
    .btn:active:not(:disabled),
    .rot-btn:active:not(:disabled),
    .modal-btn:active:not(:disabled) { transform: none; }
}

/* `hidden` es un atributo del HTML y el navegador lo aplica con display:none
   desde SU hoja de estilos, que pierde contra cualquier `display` que pongamos
   nosotros. Resultado: .atencion-ok tenia display:flex y se veia SIEMPRE, aun
   con hidden puesto — el bloque de alertas y el "Todo al dia" salian juntos,
   diciendo cosas opuestas (14/08/2026).
   Vale para todo el sistema, no solo para ese caso. */
[hidden] { display: none !important; }

/* ══════════════════════════════════════════════════════════════════════════
   ACABADO · SEGUNDA PASADA
   Lo anterior le dio profundidad a las tarjetas, pero el resto seguia plano:
   la barra superior, la cabecera de las tablas, los campos y los botones.
   Nada de esto cambia una medida — solo color, borde y sombra.
   ══════════════════════════════════════════════════════════════════════════ */

/* ── La barra superior se despega del contenido ──────────────────────────── */
/* Sin esto, en claro la barra oscura y el contenido se tocan sin transicion y
   el corte se ve duro. */
.topbar {
    box-shadow: 0 1px 0 rgba(0, 0, 0, .18), 0 2px 10px rgba(0, 0, 0, .10);
    position: relative;
    z-index: var(--fas-z-sticky);
}

/* ── El sidebar tambien es una superficie, no un fondo ───────────────────── */
.sidebar {
    box-shadow: 1px 0 0 rgba(0, 0, 0, .16);
    position: relative;
    z-index: 2;
}

/* ── La cabecera de la tabla se lee como cabecera ────────────────────────── */
/* Era un degradado casi invisible: en claro quedaba blanco sobre blanco y no
   se distinguia de la primera fila. */
.data-table thead th {
    background: var(--bg3);
    border-bottom: 1px solid var(--rule-fuerte, var(--border));
    box-shadow: inset 0 -1px 0 var(--border), 0 1px 0 rgba(0, 0, 0, .03);
}
[data-theme="light"] .data-table thead th { background: #eef0f6; color: #4a4a68; }
[data-theme="dark"]  .data-table thead th { background: #262640; }

/* La barra de herramientas se hunde de verdad, no un tono imperceptible. */
[data-theme="light"] .search-bar,
[data-theme="light"] .estado-tabs { background: #f4f5fa; }
[data-theme="dark"]  .search-bar,
[data-theme="dark"]  .estado-tabs { background: rgba(0, 0, 0, .14); }

/* ── Los campos se ven hundidos, no dibujados ────────────────────────────── */
/* Un campo tiene que parecer un hueco donde se escribe. Plano compite con el
   texto que ya esta en la pantalla. */
.search-input,
.rot-filtros input,
input[type="text"], input[type="search"], input[type="number"],
input[type="password"], input[type="email"], input[type="date"],
select, textarea {
    box-shadow: inset 0 1px 2px rgba(0, 0, 0, .06);
}
[data-theme="dark"] .search-input,
[data-theme="dark"] .rot-filtros input,
[data-theme="dark"] input[type="text"], [data-theme="dark"] input[type="search"],
[data-theme="dark"] input[type="number"], [data-theme="dark"] input[type="password"],
[data-theme="dark"] input[type="email"], [data-theme="dark"] input[type="date"],
[data-theme="dark"] select, [data-theme="dark"] textarea {
    box-shadow: inset 0 1px 3px rgba(0, 0, 0, .3);
}

/* ── Los botones principales pesan mas que los secundarios ───────────────── */
/* Si todos pesan igual, ninguno guia. El que ejecuta la accion principal
   levanta; los demas quedan planos a proposito. */
.btn-primary, .btn-success, .btn-danger, .btn-warning,
.rot-btn:not(.sec), .modal-btn-confirm, .modal-btn-danger {
    box-shadow: var(--elev-1);
}
.btn-primary:hover, .btn-success:hover, .btn-danger:hover, .btn-warning:hover,
.rot-btn:not(.sec):hover, .modal-btn-confirm:hover, .modal-btn-danger:hover {
    box-shadow: var(--elev-2);
}

/* ── Historial de impresión de Rótulos (30/09/2026) ──────────────────────────
   Antes eran colores escritos a mano en el JS (#2e7d32 / #e08600), que la
   regla del front prohíbe y que no cambiaban con el tema oscuro. */
.rot-hist__vacio { text-align: center; color: var(--text3); padding: 18px; }
.rot-hist__evento { font-weight: 700; }
.rot-hist__evento--impresion { color: var(--tag-ok-c); }
.rot-hist__evento--reimpresion { color: var(--tag-warn-c); }
/* El saldo rotulado (15/10/2026). */
.rot-hist__evento--saldo { color: var(--accent-texto, #a98ee0); }
/* Ancho para las cinco columnas con seis decimales y la unidad: a 680 px la
   del saldo nuevo quedaba cortada, con scroll de costado (15/10/2026). */
.rot-saldo { width: min(900px, 96vw); }
.rot-saldo__tabla { max-height: 50vh; margin: 10px 0 12px; overflow: auto; }
.rot-saldo__tabla .data-table td { vertical-align: middle; }
.rot-saldo__tabla .th-right { white-space: nowrap; }
.rot-saldo__input { width: 140px; text-align: right; font-variant-numeric: tabular-nums; }
.rot-saldo__input--mal { border-color: var(--tag-err-c); }
/* El control contra el stock del lote (15/10/2026): un renglón por lote. */
.rot-saldo__controles { display: flex; flex-direction: column; gap: 6px; margin: 0 0 12px; }
.rot-saldo__control { font-size: 12.5px; color: var(--text2); padding: 8px 10px; border: 1px solid var(--border);
                      border-left: 3px solid var(--border); border-radius: var(--radius); background: var(--bg3); }
.rot-saldo__control--ok { border-left-color: var(--tag-ok-c); }
.rot-saldo__control--dif { border-left-color: var(--tag-warn-c); }
.rot-saldo__control--mal { border-left-color: var(--tag-err-c); color: var(--text); }
.rot-saldo__supera { color: var(--tag-err-c); }
.rot-hist__fecha { white-space: nowrap; }
.rot-hist__codigo { font-family: monospace; font-size: 12px; }
.rot-hist__motivo { font-size: 12px; color: var(--text2); }


/* ── El fondo de la pagina se separa de las tarjetas ─────────────────────── */
/* En claro el fondo era casi blanco y las tarjetas blancas: no habia contraste
   y por eso todo se leia como una sola lamina. */
[data-theme="light"] body { background: #eaecf3; }

/* ── Las tarjetas de KPI: el numero manda ────────────────────────────────── */
.kpi-value { letter-spacing: -.5px; }
.kpi-card { border-radius: 10px; }


/* ── Notificaciones como tarjetas ────────────────────────────────────────────
   Eran filas de tabla: cinco columnas para decir una sola cosa —esta factura
   lleva N dias parada—. Una tabla sirve para comparar registros entre si; acá
   cada aviso se lee solo y lo que importa es cuanto hace que no se mueve. */
.notif-lista { display: flex; flex-direction: column; gap: 8px; }

.notif-card {
    display: flex;
    align-items: center;
    gap: 13px;
    padding: 13px 15px;
    background: var(--bg2);
    border: 1px solid var(--border);
    border-left: 3px solid var(--tag-warn-c);
    border-radius: var(--radius);
    box-shadow: var(--elev-1), var(--luz-alta);
    transition: border-color var(--fas-transition-fast), box-shadow var(--fas-transition-fast);
}
.notif-card:hover { box-shadow: var(--elev-2), var(--luz-alta); }
.notif-card--urgente { border-left-color: var(--tag-err-c); }

/* Una notificacion leida no se esconde —puede hacer falta volver a mirarla—
   pero deja de competir por la atencion con las que siguen abiertas. */
.notif-card--leida { opacity: .6; border-left-color: var(--border); }

.notif-card-ico {
    flex: none;
    width: 34px;
    height: 34px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    background: var(--tag-warn-bg);
    color: var(--tag-warn-c);
}
.notif-card--urgente .notif-card-ico { background: var(--tag-err-bg); color: var(--tag-err-c); }
.notif-card--leida   .notif-card-ico { background: var(--bg3); color: var(--text3); }

.notif-card-cuerpo { flex: 1; min-width: 0; }
.notif-card-tit {
    margin: 0;
    font-size: 13.5px;
    font-weight: 700;
    color: var(--text);
    line-height: 1.25;
}
.notif-card--urgente .notif-card-tit { color: var(--tag-err-c); }
.notif-card-sub { margin: 3px 0 0; font-size: 12px; color: var(--text2); }
.notif-card-sub strong { color: var(--text); font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }

.notif-card-acciones { flex: none; display: flex; align-items: center; gap: 9px; }
.notif-card-leida-txt { font-size: 11.5px; color: var(--text3); }
/* El punto dice "sin leer" de un vistazo, sin tener que leer una etiqueta. */
.notif-punto {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--accent-lt);
    flex: none;
}
.notif-vacio {
    padding: 26px 0;
    text-align: center;
    color: var(--text3);
    font-size: 13px;
}

@media (max-width: 640px) {
    .notif-card { flex-wrap: wrap; }
    .notif-card-acciones { width: 100%; justify-content: flex-end; }
}

/* ── Línea de tiempo de la factura ───────────────────────────────────────────
   La linea vertical es un ::before del <li> y no un borde del <ol>: asi el
   ultimo punto la corta y la linea no sigue de largo hacia la nada. */
.tl { list-style: none; margin: 0 0 18px; padding: 0; }

.tl-item {
    position: relative;
    padding: 0 0 16px 26px;
}
.tl-item::before {
    content: "";
    position: absolute;
    left: 5px;
    top: 14px;
    bottom: 0;
    width: 2px;
    background: var(--border);
}
.tl-item--ultimo { padding-bottom: 2px; }
.tl-item--ultimo::before { display: none; }

.tl-punto {
    position: absolute;
    left: 0;
    top: 4px;
    width: 12px;
    height: 12px;
    border-radius: 50%;
    background: var(--bg2);
    border: 2px solid var(--accent-lt);
}
/* El ultimo es donde esta parada la factura hoy: va lleno para que el ojo
   caiga ahi primero, que es lo que se quiere saber. */
.tl-item--ultimo .tl-punto { background: var(--accent); border-color: var(--accent); }

/* ── El punto con FLECHA, en la historia del lote ────────────────────────────
   No es decoracion: en una lista de quince movimientos el ojo agarra la forma
   antes que el texto, y una flecha para abajo (entro) contra una para arriba
   (salio) deja ver de un vistazo que un lote entro una vez y salio ocho.
   Los eventos de FAS —cargado, confirmado, rotulo— llevan un tilde. */
.lote-pagina .tl-punto,
.lote-modal .tl-punto {
    width: 18px; height: 18px; top: 1px; left: -3px;
    display: flex; align-items: center; justify-content: center;
    border-width: 1.5px;
}
.tl-punto svg { width: 10px; height: 10px; }
.tl-item--entrada .tl-punto { border-color: var(--tag-ok-c); color: var(--tag-ok-c); }
.tl-item--salida  .tl-punto { border-color: var(--tag-warn-c); color: var(--tag-warn-c); }
.lote-pagina .tl-item--ultimo .tl-punto,
.lote-modal .tl-item--ultimo .tl-punto {
    /* El relleno del ultimo taparia la flecha. Se marca con un halo. */
    background: var(--bg2);
    box-shadow: 0 0 0 3px color-mix(in srgb, currentcolor 18%, transparent);
}

.tl-cuerpo { min-width: 0; }
.tl-cab {
    display: flex;
    align-items: baseline;
    gap: 10px;
    flex-wrap: wrap;
}
.tl-accion { font-size: 13px; font-weight: 700; color: var(--text); }
.tl-fecha {
    font-size: 11.5px;
    color: var(--text3);
    font-variant-numeric: tabular-nums;
}
.tl-quien { font-size: 12px; color: var(--text2); margin-top: 1px; }
/* El mismo lote en otro artículo (v3.60): el paso a la otra ficha. */
.tl-enlace { display: inline-block; margin-top: 3px; font-size: 12.5px; font-weight: 600; color: var(--accent-texto); }
.tl-enlace:hover { text-decoration: underline; }
@media print { .tl-enlace { display: none !important; } }

/* La frase corta del evento: "Salieron 129 de DISP PRO". Va con el color del
   texto normal —no el apagado de `.tl-quien`— porque NO es un dato secundario:
   es la mitad de lo que el evento cuenta. */
.tl-resumen {
    font-size: 13px; color: var(--text); margin-top: 2px;
    font-variant-numeric: tabular-nums;
}
.tl-detalle {
    margin-top: 5px;
    padding: 6px 9px;
    background: var(--bg3);
    border-radius: 6px;
    font-size: 11.5px;
    color: var(--text2);
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    word-break: break-word;
    /* Un detalle largo no puede empujar el ancho de la columna. */
    max-height: 88px;
    overflow-y: auto;
}

/* ── Detalle de la línea de tiempo ───────────────────────────────────────────
   Va cerrado: abierto, tres eventos llenaban la pantalla de JSON y tapaban lo
   que se viene a ver, que es la secuencia. */
.tl-det { margin-top: 5px; }

.tl-det-abrir {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    font-size: 11.5px;
    font-weight: 600;
    font-family: inherit;
    color: var(--accent-texto);
    cursor: pointer;
    user-select: none;
    padding: 2px 0;
    /* Era un <summary>; ahora es un <button>, que trae borde y fondo propios
       del navegador. Sin esto "Ver detalle" se ve como un botón con recuadro
       en vez del enlace discreto que era. */
    background: none;
    border: 0;
    text-align: left;
}
/* Flecha propia: la del navegador cambia de forma en cada uno y descoloca la
   linea de la izquierda. */
.tl-det-abrir::before {
    content: "";
    width: 0;
    height: 0;
    border-left: 4px solid currentColor;
    border-top: 3.5px solid transparent;
    border-bottom: 3.5px solid transparent;
    transition: transform var(--fas-transition-fast);
}
.tl-det-abrir[aria-expanded="true"]::before { transform: rotate(90deg); }
.tl-det-abrir:hover { text-decoration: underline; }
.tl-det-abrir:focus-visible { outline: 2px solid var(--accent-lt); outline-offset: 2px; border-radius: 3px; }
.tl-det-abrir[aria-expanded="true"] { margin-bottom: 5px; }

/* Pares etiqueta/valor: reemplazan al JSON crudo, que es salida de programador. */
.tl-detalle--pares {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: 3px 12px;
    font-family: inherit;
    max-height: none;
}
.tl-par { display: contents; }
.tl-par-et {
    font-size: 11px;
    color: var(--text3);
    text-transform: uppercase;
    letter-spacing: .3px;
    white-space: nowrap;
}
.tl-par-val {
    font-size: 12px;
    color: var(--text);
    word-break: break-word;
    font-variant-numeric: tabular-nums;
}

/* ── Salud del sistema ───────────────────────────────────────────────────────
   Eran filas de seis columnas: había que leerlas para saber si algo andaba
   mal. Una tarjeta con su semáforo se ve de un vistazo, que es para lo que se
   entra a esta pantalla. */
.salud-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
    gap: 10px;
    margin-top: 12px;
}

.salud-card {
    padding: 11px 13px;
    background: var(--bg3);
    border: 1px solid var(--border);
    border-left: 3px solid var(--text3);
    border-radius: var(--radius);
}
.salud-card--ok      { border-left-color: var(--tag-ok-c); }
.salud-card--warning { border-left-color: var(--tag-warn-c); }
.salud-card--error   { border-left-color: var(--tag-err-c); }

.salud-cab { display: flex; align-items: center; gap: 7px; }
.salud-punto {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--text3);
    flex: none;
}
.salud-card--ok      .salud-punto { background: var(--tag-ok-c); }
.salud-card--warning .salud-punto { background: var(--tag-warn-c); }
/* Solo lo caído late. Si latieran los tres colores, el movimiento dejaría de
   significar "esto necesita que alguien haga algo". */
.salud-card--error   .salud-punto { background: var(--tag-err-c); animation: fas-dot-pulse 1.6s ease-in-out infinite; }

.salud-nombre {
    font-size: 11px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .5px;
    color: var(--text2);
}
.salud-lat {
    margin-left: auto;
    font-size: 10px;
    font-weight: 600;
    color: var(--text3);
    font-variant-numeric: tabular-nums;
}
.salud-estado { margin-top: 5px; font-size: 15px; font-weight: 700; color: var(--text); }
.salud-card--ok      .salud-estado { color: var(--tag-ok-c); }
.salud-card--warning .salud-estado { color: var(--tag-warn-c); }
.salud-card--error   .salud-estado { color: var(--tag-err-c); }
.salud-detalle {
    margin-top: 2px;
    font-size: 11.5px;
    color: var(--text3);
    line-height: 1.35;
    word-break: break-word;
}

@media (prefers-reduced-motion: reduce) {
    .salud-card--error .salud-punto { animation: none; }
}

/* ── Dashboard de Admin (auditoría 21/09/2026) ──────────────────────────────
   Salud como LISTA: una línea por servicio con su semáforo y su texto. Las
   nueve tarjetas de antes decían «Operativo» nueve veces y empujaban los
   números de facturas debajo del pliegue. El detalle largo va en el title y,
   como segunda línea, solo cuando el servicio no está bien. */
.salud-lista {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
    gap: 6px 14px;
    margin-top: 10px;
}
.salud-lista .salud-card {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px 8px;
    padding: 6px 10px;
    border-left-width: 3px;
}
.salud-lista .salud-nombre { flex: 1 1 auto; text-transform: none; letter-spacing: 0; font-size: 12.5px; color: var(--text); }
.salud-lista .salud-estado { margin-top: 0; font-size: 12.5px; white-space: nowrap; }
.salud-lista .salud-lat { margin-left: 0; min-width: 44px; text-align: right; white-space: nowrap; }
.salud-lista .salud-detalle { flex-basis: 100%; margin-top: 0; padding-left: 16px; }
.salud-lista .salud-card--ok .salud-detalle { display: none; }

.dash-cab-seccion { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; margin: 4px 0 10px; }
.dash-seccion { margin: 0; font-size: 11px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--text2); }
/* Admin → Errores de integración: una sección por módulo, cada una del alto de
   lo que tiene, en un solo contenedor que se desplaza (08/10/2026). */
.err-scroll { flex: 1; overflow-y: auto; padding: 16px 0 28px; }
.err-bloque { margin: 0 16px 18px; background: var(--bg2); border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; }
.err-seccion { display: flex; align-items: center; justify-content: space-between; gap: 12px; min-height: 44px; padding: 8px 16px; border-bottom: 1px solid var(--border); }
.err-tabla { overflow-x: auto; }
.err-msg { font-size: 12px; line-height: 1.45; max-width: 460px; }
.err-msg-sub { color: var(--text3); font-size: 11px; margin-top: 3px; }
.err-vacio { padding: 18px 16px; color: var(--text3); font-size: 13px; text-align: center; }
.dash-hora { font-variant-numeric: tabular-nums; }
/* Los números de facturas son de la carga de la página: a los 10 minutos se
   avisa que envejecieron y aparece «Actualizar datos». */
.dash-hora--vieja { color: var(--tag-warn-c); font-weight: 600; }
.dash-recargar[hidden] { display: none; }
/* Fichas compactas SOLO en el dashboard de Admin (21/09/2026, «se siente
   bastante feo»): la tarjeta grande con un número adentro y aire alrededor,
   repetida veinte veces en tres filas, cansa. Acá cada ficha mide ~70 px:
   etiqueta arriba, número y detalle en la misma línea. Una fila por sección.
   Las tarjetas de Compras y Depósito siguen con su tamaño. */
.dash-scroll .kpi-grid { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px; margin-bottom: 18px; }
.dash-scroll .kpi-card { display: grid; grid-template-columns: 1fr auto; align-items: baseline; column-gap: 8px; row-gap: 3px; padding: 9px 13px 8px; border-top-width: 2px; animation: none; }
.dash-scroll .kpi-card:hover { transform: none; box-shadow: none; }
.dash-scroll .kpi-clickeable:hover { border-color: var(--accent); box-shadow: none; transform: none; }
.dash-scroll .kpi-label { grid-column: 1 / -1; font-size: 10.5px; letter-spacing: .04em; margin: 0; }
.dash-scroll .kpi-value { font-size: 22px; line-height: 1.05; font-variant-numeric: tabular-nums; white-space: nowrap; }
.dash-scroll .kpi-value.kpi-value--largo { font-size: 17px; }
.dash-scroll .kpi-sub { margin: 0; font-size: 11px; color: var(--text3); text-align: right; white-space: nowrap; }
.dash-scroll .kpi-card:has(.kpi-value--largo) { grid-column: auto; }
.kpi-grid--admin { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); }
@media (max-width: 768px) {
    .dash-scroll .kpi-grid { grid-template-columns: 1fr 1fr !important; gap: 8px !important; }
    .dash-scroll .kpi-card:has(.kpi-value--largo) { grid-column: auto; }
    .dash-scroll .kpi-sub { white-space: normal; }
}
/* «3 / 5»: el segundo número más chico, es el total de referencia. */
.kpi-de { font-size: .55em; font-weight: 600; color: var(--text3); }

/* Acción de riesgo entre botones informativos: se ve distinta antes de tocarla.
   Con dos clases: el `.btn-ghost` del bloque de estilos de admin.html se carga
   después de esta hoja y, con una sola, le ganaba: el botón que borra se veía
   igual que «Refrescar» (visto en la captura, 19/10/2026). */
.btn-ghost.btn-ghost--peligro { color: var(--tag-err-c); border-color: var(--tag-err-c); }
.btn-ghost.btn-ghost--peligro:hover:not(:disabled) { background: var(--tag-err-bg); color: var(--tag-err-c); }

/* Estado de error de un bloque del dashboard, con reintento. */
.dash-error { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; color: var(--tag-err-c); font-size: 12.5px; }

/* ── Admin, relevo de front del 14/10 (19/10/2026) ──────────────────────────
   Lo que borra va separado de lo que solo consulta (UX-15): en la barra de
   Salud del sistema, una raya; en Acciones operativas, su propio renglón. */
.acciones-sep { align-self: stretch; width: 1px; margin: 0 4px; background: var(--border); }
.acciones-peligro { margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--border); }
/* Los dos montos aprobados en una tarjeta (UX-14). */
.kpi-montos { display: flex; flex-direction: column; gap: 5px; }
.kpi-montos__fila { display: flex; align-items: baseline; gap: 8px; white-space: nowrap; }
.kpi-montos__moneda { min-width: 30px; font-size: 11px; font-weight: 700; color: var(--text2); }
.kpi-value.kpi-montos__valor { font-size: 17px; }
/* Estilos que estaban escritos en la plantilla y en el JS de Admin (DT-01). */
.notif-item-icon--grave { background: var(--tag-err-bg); color: var(--tag-err-c); }
.notif-item-icon--aviso { background: var(--tag-warn-bg); color: var(--tag-warn-c); }
.notif-footer { padding: 10px 16px; border-top: 1px solid var(--border); text-align: center; }
.sistema-logs {
    margin: 0; padding: 12px; max-height: 480px; overflow: auto;
    background: var(--bg3, var(--input-bg)); color: var(--text);
    border: 1px solid var(--border); border-radius: 8px;
    font-family: 'Cascadia Mono', Consolas, monospace; font-size: 10.5px; line-height: 1.5;
    white-space: pre-wrap; word-break: break-all;
}
.fas-modal-confirma {
    margin-top: 14px; padding: 10px 12px; border-radius: 8px;
    border: 1px solid var(--tag-warn-c); background: var(--tag-warn-bg);
}
.fas-modal-confirma__input {
    width: 100%; height: 34px; padding: 0 10px; border: 1px solid var(--border);
    border-radius: 6px; background: var(--input-bg); color: var(--text); font-size: 13px;
}
/* Certificados y PDF por expirar: el color por los días que quedan. */
.venc-estado--ok { color: var(--tag-ok-c); }
.venc-estado--aviso { color: var(--tag-warn-c); }
.venc-estado--grave { color: var(--tag-err-c); }
.venc-estado--neutro { color: var(--text3); }
.venc-dias { font-size: 11px; }
.venc-dias--ancho { min-width: 60px; }
.venc-punto { width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0; background: currentColor; }

/* Cada advertencia del OCR sobre el documento entero —que no parezca una
   factura, que el timbrado este vencido—. Un punto rojo dice QUE campo mirar;
   esto dice POR QUE. */
.avisos-ocr-item {
    margin-top: 6px;
    padding-left: 22px;
    font-size: 12.5px;
    line-height: 1.45;
    color: var(--text2);
}
.avisos-ocr-item::before {
    content: "•";
    margin-left: -12px;
    margin-right: 6px;
    color: var(--tag-warn-c);
    font-weight: 700;
}

/* Aviso de que el PDF esta escaneado y por eso tarda. Aparece recien a los 2
   segundos: decirlo antes, en un PDF que se lee en medio segundo, seria ruido. */
.upload-lento {
    margin-top: 10px;
    font-size: 12px;
    line-height: 1.45;
    color: var(--text3);
    text-align: center;
}

/* ── Modal base (el envoltorio, no el contenido) ────────────────────────────
   Vivia dentro de entradas.html. Se subio aca cuando el modal del FILTRO paso
   a ser compartido con Facturacion: los estilos de un componente compartido no
   pueden quedar dentro de una sola pantalla, o el componente se ve bien en una
   y crudo en la otra. El prefijo `ent-` queda por compatibilidad — lo usan
   todos los modales de Deposito y ahora tambien el filtro. */
.ent-modal-bg {
    position: fixed; inset: 0; background: rgb(0 0 0 / 55%);
    display: none; align-items: center; justify-content: center; z-index: 1000;
}
.ent-modal-bg.open { display: flex; }
.ent-modal {
    background: var(--bg2); border: 1px solid var(--border); border-radius: 12px;
    padding: 20px; max-width: 94vw; max-height: 90vh; overflow: auto;
}
.ent-btn {
    height: 34px; padding: 0 16px; border: none; border-radius: 8px;
    background: var(--accent); color: #fff; font-weight: 600; font-size: 13px;
    cursor: pointer;
}
.ent-btn.sec { background: var(--bg3, var(--input-bg)); color: var(--text); border: 1px solid var(--border); }
/* Detalle del ingreso: anulación pedida y todavía sin respuesta (29/09/2026). */
.ent-det-aviso { align-self: center; padding: 6px 10px; border-radius: 6px; font-size: 13px; background: var(--tag-warn-bg); color: var(--tag-warn-c); }
.ent-btn:disabled { opacity: .5; cursor: not-allowed; }

/* ── La historia del lote como PAGINA propia ────────────────────────────────
   /entradas/lote/<lote>, que abre el boton "Ver mas" de la tabla en una
   pestaña nueva. Es una hoja centrada y sin barra lateral a proposito: se
   entra para leer UN lote, y de ahi se imprime. */
/* El fondo y el color NO se tocan: `body` ya los pone con var(--bg) y
   var(--text). Pisarlos con otra variable fue justo lo que dejo la pagina con
   fondo blanco y texto claro en modo oscuro. Lo unico que hay que soltar es el
   `height: 100vh` del body, que aca corta la hoja si el lote tiene muchos
   movimientos. */
.lote-pagina { height: auto; min-height: 100vh; overflow-y: auto; }

/* La barra queda FIJA al bajar. Con 85 productos y 32 días se scrollea
   bastante, y ahí arriba están las dos impresiones y el botón de volver: sin
   esto hay que subir hasta el principio para imprimir. */
.lote-pagina .topbar { position: sticky; top: 0; }

/* El número de lote en la barra. Con varias pestañas de lotes abiertas es lo
   que las distingue de un vistazo. En pantalla angosta se corta antes que
   empujar los botones fuera de la barra. */
.topbar-lote {
    font-weight: 700; color: rgba(255, 255, 255, .72);
    font-variant-numeric: tabular-nums;
}
.lote-pagina .topbar-title {
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0;
}

/* ── Volver arriba ───────────────────────────────────────────────────────────
   Aparece recién después de 600 px de scroll: en una página corta sería un
   adorno tapando contenido. */
.lote-arriba {
    position: fixed; right: 20px; bottom: 20px; z-index: 150;
    width: 38px; height: 38px; display: flex;
    align-items: center; justify-content: center;
    background: var(--bg2); color: var(--text2);
    border: 1px solid var(--border); border-radius: 50%;
    cursor: pointer; box-shadow: var(--shadow-md);
    transition: background var(--fas-transition-fast), 
                color var(--fas-transition-fast),
                border-color var(--fas-transition-fast);
}
.lote-arriba:hover {
    background: var(--accent); color: #fff; border-color: var(--accent);
}
.lote-arriba:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.lote-arriba[hidden] { display: none; }

/* ── En el celular ───────────────────────────────────────────────────────────
   El flujo de planta es: se escanea el QR del envase, sale la traza, se aprieta
   «Ver más» y se lee la ficha ahí mismo. Así que el celular no es un caso
   secundario de esta pantalla: es EL caso.

   Medido en 360 px —el ancho de un Samsung A55, que es lo que se usa en
   planta— la barra desbordaba 28 px y toda la página quedaba con scroll
   horizontal: los dos botones de imprimir se llevaban 282 px de los 360.

   Se quedan, pero en icono. Imprimir desde el celular no es lo que se va a
   hacer, y esconderlos del todo obligaría a volver a la computadora para algo
   que igual entra. */
@media (max-width: 640px) {
    .lote-pagina .topbar { padding: 0 8px; gap: 6px; }
    .lote-pagina .topbar-right .btn-txt { display: none; }
    .lote-pagina .topbar-right .btn { padding: 6px 9px; }
    .lote-pagina .btn-back-top { flex: 0 0 auto; }
    /* El título se corta antes que empujar nada fuera de la barra. */
    .lote-pagina .topbar-title { flex: 1 1 auto; min-width: 0; font-size: 13px; }

    /* La hoja, sin el margen de escritorio: en 360 px cada píxel cuenta. */
    .lote-hoja { padding: 16px 12px 40px; }

    /* Volver arriba, más adentro: en el celular el borde es zona de gestos. */
    .lote-arriba { right: 14px; bottom: 14px; }
}

/* ── Las listas en el celular: tarjeta con jerarquía ─────────────────────────
   Medido el 27/08/2026: cada fila ocupaba una PANTALLA ENTERA. La conversión
   de tabla a tarjeta apilaba las diez columnas como diez renglones de
   «ETIQUETA … valor», así que para ver cinco facturas había que scrollear
   cinco pantallas.

   La jerarquía sale de un dato que ya existe en el HTML y no de contar
   columnas —que se rompe el día que alguien agrega una—:

     · celdas SIN `data-label` → proveedor, estado y acción. Se explican solas
       y van a lo ancho: el proveedor arriba de todo, el resto al pie.
     · celdas CON `data-label` → datos puntuales (fecha, factura, OC, monto).
       Van de a dos por fila, con la etiqueta chica ARRIBA del valor en vez de
       al lado: la mitad de alto y sin la franja vacía del medio. */
@media (max-width: 768px) {
    /* Se intentó un GRID de dos columnas el 27/08/2026 y NO salió: alineaba
       bien los datos, pero «#1» y el chip de estado —que en el HTML son
       celdas como cualquier otra— se montaban sobre el nombre del producto.
       Con `flex-wrap` la cabecera queda bien y los datos aceptables. */
    .data-table tbody tr,
    .list-tbl tbody tr { flex-direction: row; flex-wrap: wrap; gap: 0; }

    /* Lo que identifica la fila, a lo ancho. */
    .data-table tbody td:not([data-label]):not([colspan]),
    .list-tbl tbody td:not([data-label]):not([colspan]) {
        flex: 1 1 100%; padding: 3px 0; border-bottom: 0;
    }
    .data-table tbody td:not([data-label]):first-child,
    .data-table tbody td:not([data-label]):nth-child(2) {
        font-weight: 700; font-size: 14px; line-height: 1.3;
    }

    /* Los datos puntuales, de a dos y con la etiqueta arriba. */
    .data-table tbody td[data-label],
    .list-tbl tbody td[data-label] {
        /* `!important` porque acá compiten dos cosas: la regla vieja de
           tarjetas —que pone `display: flex` a TODA celda— y los anchos de
           columna que la tabla trae para escritorio. Sin esto, una celda
           quedaba en 14 px y otra en 280, en el mismo grid. */
        display: block; flex: 1 1 42%; min-width: 0;
        padding: 6px 10px 6px 0; border-bottom: 0;
    }
    .data-table tbody td[data-label]::before,
    .list-tbl tbody td[data-label]::before {
        display: block; flex: none; margin-bottom: 1px;
        font-size: 9.5px; letter-spacing: .04em; opacity: .8;
        /* La etiqueta no se parte AL MEDIO DE LA PALABRA. En 360 px
           «VENCIMIENTO» salía como «VENCIMI / ENTO», que se lee como un error
           de pintado.
           La tabla hereda `word-break: break-word` para que los códigos
           largos no desborden; acá se revierte. Si una etiqueta no entra pasa
           entera al renglón siguiente, que es lo que hace cualquier texto. */
        overflow-wrap: normal; word-break: normal; hyphens: none;
    }

    /* El valor arranca pegado a su etiqueta, no flotando abajo. */
    .data-table tbody td[data-label],
    .list-tbl tbody td[data-label] { align-self: start; }

    /* El chip de estado, en su propio renglón. Se superponía al nombre del
       producto cuando el nombre ocupaba dos líneas. */
    .data-table tbody td:not([data-label]) .ent-chip,
    .data-table tbody td:not([data-label]) .badge-estado { margin-top: 2px; }

    /* Una celda vacía no ocupa lugar: en las filas que vienen de SAP, media
       tarjeta eran guiones sueltos sin etiqueta que los explicara. */
    .data-table tbody td:not([data-label]):empty { display: none; }
    /* Los datos secundarios que la celda trae adentro («Carg. …», «Timb. …»)
       vuelven a fluir: ya no hace falta correrlos bajo la etiqueta. */
    .data-table tbody td[data-label] > div {
        flex: none; margin-left: 0; font-size: 11px;
    }
    /* El PROVEEDOR es lo que identifica la fila: va arriba de todo y a lo
       ancho, no como una celda más al lado de la fecha. `order: -1` lo sube
       sin tocar el HTML, y el selector va por el texto de la etiqueta —con
       `i` para no depender de mayúsculas— y no por posición, que se rompe el
       día que alguien agregue una columna. */
    .data-table tbody td[data-label*="rovee" i],
    .list-tbl tbody td[data-label*="rovee" i],
    .data-table tbody td[data-label*="escrip" i],
    .data-table tbody td[data-label*="rtícul" i] {
        order: -1; flex: 1 1 100%; padding-top: 0;
    }
    .data-table tbody td[data-label*="rovee" i]::before,
    .list-tbl tbody td[data-label*="rovee" i]::before,
    .data-table tbody td[data-label*="escrip" i]::before,
    .data-table tbody td[data-label*="rtícul" i]::before { display: none; }
    .data-table tbody td[data-label*="rovee" i],
    .list-tbl tbody td[data-label*="rovee" i],
    .data-table tbody td[data-label*="escrip" i] {
        font-size: 14.5px; font-weight: 700; line-height: 1.25;
    }

    /* El monto, que es el número que se busca. */
    .data-table tbody td[data-label*="onto" i] { font-size: 14px; font-weight: 700; }


    /* El pie: estado y acción comparten renglón en vez de uno cada uno. */
    .data-table tbody td:last-child,
    .list-tbl tbody td:last-child {
        flex: 1 1 auto; border-top: 1px solid var(--border);
        margin-top: 6px; padding-top: 8px; text-align: right;
    }
    .data-table tbody td:last-child::before { display: none; }
}

/* ── Trazabilidad en el celular ──────────────────────────────────────────────
   El flujo de planta es: se escanea el QR del envase acá y de acá se pasa a la
   ficha del lote. O sea que esta pantalla es la PRIMERA que se ve en el
   teléfono, y estaba peor que la ficha.

   Medido el 27/08/2026 en cuatro pantallas: los chips de vencimiento
   —«30 d · 60 d · 90 d · Vencidos»— desbordaban 54 px en un Galaxy A55, y los
   campos tienen ancho fijo en el HTML, así que ninguno se adaptaba.

   Y el desborde horizontal no es solo feo: con `overflow-x: hidden` en el
   body, el navegador intenta desplazar hacia un costado donde no puede y el
   gesto se pierde. Es por lo que «no dejaba scrollear». */
@media (max-width: 768px) {
    /* Los campos, a lo ancho. Los `style="width:130px"` del HTML son de
       escritorio y acá hay que pisarlos. `.ent-filtros` es un GRID de dos
       columnas (regla de más arriba), así que acá se trabaja con el grid y
       no con flex. */
    .ent-filtros .field-group input,
    .ent-filtros .field-group select { width: 100% !important; }

    /* El campo de días se lleva la fila entera: lleva un input MÁS cuatro
       atajos, y en media fila no entran. */
    .ent-filtros .field-group:has(.venc-rapido) { grid-column: 1 / -1; }

    /* Y los atajos vuelven a ser chips.
       La regla del grid pone `width: 100%` a TODOS los botones de
       `.ent-filtros` —para que Buscar y Limpiar llenen su celda—, y eso
       alcanzaba también a estos cuatro: con `flex-wrap`, cada uno se comía
       una fila entera y el bloque de filtros ocupaba media pantalla. */
    .venc-fila { flex-wrap: wrap; gap: 8px; }
    .venc-fila > input { flex: 1 1 100%; }
    .venc-rapido { flex-wrap: wrap; width: 100%; gap: 6px; }
    .ent-filtros .venc-rapido button {
        width: auto !important; flex: 1 1 0; min-width: 62px; min-height: 40px;
    }

    /* «Escanear QR» se lleva la fila completa: en el celular es el flujo
       principal —se llega con un envase en la mano, no tipeando un lote— y
       era el más chico de los tres botones.
       Sin `>`: desde el 22/09/2026 los botones viven dentro de
       `.ent-filtros-acciones`, no sueltos en `.ent-filtros`. */
    .ent-filtros .ent-btn[data-fas-click*="entScanQR"] { grid-column: 1 / -1; }

    /* La fila de arriba de la tabla —«N resultados · Filtro · Exportar ·
       Columnas»— se llevaba 285 px de los 360 y desbordaba. */
    .col-menu-wrap { flex-wrap: wrap; gap: 8px; }
    .col-menu-wrap > * { min-width: 0; }
    .col-menu-wrap .btn { min-height: 40px; }
}

/* ── Admin en el celular ─────────────────────────────────────────────────────
   Medido el 27/08/2026 en un Galaxy A55: cuatro cosas desbordaban. Admin se
   usa casi siempre desde la computadora, pero «casi siempre» no es nunca —y
   un desborde horizontal traba el scroll de toda la página, no solo del
   elemento que lo causa—. */
@media (max-width: 768px) {
    /* Las solapas de estado son muchas y no entran. Scroll propio y no wrap:
       en dos filas se pierde la noción de que son una sola barra de filtros.
       El scroll es DENTRO de la barra, así que no afecta al de la página. */
    .estado-tabs {
        overflow-x: auto; flex-wrap: nowrap;
        -webkit-overflow-scrolling: touch;
        scrollbar-width: none;
    }
    .estado-tabs::-webkit-scrollbar { display: none; }
    .estado-tab { flex: 0 0 auto; min-height: 40px; }

    /* Los gráficos, uno por fila y sin ancho mínimo. */
    .charts-grid { grid-template-columns: 1fr !important; }
    .chart-card { max-width: 100%; min-width: 0; overflow: hidden; }
    .chart-title { min-width: 0; flex-wrap: wrap; }
    .chart-wrap { max-width: 100%; overflow-x: auto; }

    /* El panel de detalle, a lo ancho de la pantalla, y solo abierto: sin
       `.open` se veía vacío y el botón de cerrar no lo escondía (QA 12/10/2026). */
    .detail-panel:not(.open) { display: none !important; }
    .detail-panel.open { width: 100% !important; max-width: 100% !important; }
    .dp-header { flex-wrap: wrap; min-width: 0; }
    .dp-avatar { flex: 0 0 auto; }

    /* Los botones de cada tarjeta de rol, repartidos. */
    .roles-card-header { flex-wrap: wrap; gap: 8px; }
    .roles-card-header .btn { min-height: 40px; }

    /* Las casillas de permisos: 18 px es menos de la mitad de lo que pide
       cualquier guía táctil.
       `input.perm-chk` y no `.perm-chk` a secas: el tamaño está en el <style>
       del propio admin.html, que carga DESPUÉS de esta hoja, así que con la
       misma especificidad ganaría el del template. */
    input.perm-chk, .list-tbl input.chk {
        width: 24px; height: 24px; min-width: 24px; min-height: 24px;
    }
}

/* ── Lo que se toca con el dedo ──────────────────────────────────────────────
   `pointer: coarse` es «esta pantalla se toca», y eso incluye el celular
   acostado y la tablet — que por ancho caerían en las reglas de escritorio y
   quedarían con botones de 27 px.

   Los mínimos son los de las guías: 44 px (Apple) y 48 px (Material). Medido
   el 27/08/2026, el botón de volver de la barra daba 34 × 30 en las nueve
   pantallas probadas: el control principal de navegación era el más difícil
   de acertar. */
@media (pointer: coarse) {
    .lote-pagina .topbar {
        height: auto; min-height: 56px;
        padding-top: 6px; padding-bottom: 6px;
    }
    .btn-back-top,
    .lote-pagina .topbar-right .btn {
        min-height: 44px; min-width: 44px;
        display: inline-flex; align-items: center; justify-content: center;
    }
    /* El día entero es el área de toque, no solo el texto de la fecha. */
    .tl-dia { min-height: 44px; }
    .tl-todos,
    .lote-uso-mas { min-height: 44px; padding: 10px 12px; }
    .tl-det-abrir { min-height: 40px; }
    .lote-arriba { width: 46px; height: 46px; }
}

/* ── Pantallas de 320 px ─────────────────────────────────────────────────────
   Un iPhone SE de primera generación sigue existiendo en planta. Ahí no entra
   todo: el número de lote de la barra y el nombre del producto compiten por
   el mismo ancho. Gana el nombre —es el dato— y el lote sale de la barra,
   donde ya está el título de la pantalla y abajo el número en grande. */
@media (max-width: 480px) {
    /* La cabecera de cada bloque, apilada. En 360 px el título y el dato de
       la derecha —«Dónde está hoy» y «STOCK TOTAL 0,025 LTS.»— compiten por
       el mismo renglón, y el que perdía era el título: se partía en dos
       líneas y quedaba desalineado del subrayado. */
    .lote-bloque-cab { flex-wrap: wrap; gap: 2px 10px; }
    .lote-bloque-tit { flex: 1 1 100%; }
    .lote-bloque-n { flex: 1 1 100%; text-align: left; }
    .lote-total { margin-left: 0; }
}

@media (max-width: 380px) {
    .topbar-lote { display: none; }
    /* Nombre arriba, cantidad abajo: en una línea no entran los dos. */
    .lote-uso-item { flex-wrap: wrap; gap: 2px 10px; }
    .lote-uso-prod { flex: 1 1 100%; }
    .lote-uso-datos { flex: 1 1 100%; text-align: right; }
}

/* ── Las zonas seguras del teléfono ──────────────────────────────────────────
   En un iPhone con notch, la barra fija se mete debajo de la cámara y el botón
   flotante queda tapado por la barra de gestos. En un Android con pantalla
   curva pasa lo mismo con los bordes laterales.

   `env(safe-area-inset-*)` es lo que el navegador informa de esas zonas. Vale
   0 en cualquier pantalla que no las tenga, así que la regla es una sola para
   los dos mundos y no hace falta detectar el teléfono. */
@supports (padding: env(safe-area-inset-left)) {
    .lote-pagina .topbar {
        padding-left: max(8px, env(safe-area-inset-left));
        padding-right: max(8px, env(safe-area-inset-right));
    }
    .lote-hoja {
        padding-left: max(12px, env(safe-area-inset-left));
        padding-right: max(12px, env(safe-area-inset-right));
    }
    .lote-arriba {
        right: max(14px, env(safe-area-inset-right));
        bottom: max(14px, calc(env(safe-area-inset-bottom) + 6px));
    }
}

/* La HOJA. Es un documento: en una auditoria GMP esto se imprime y se entrega,
   asi que se lee mejor como una hoja acotada que como texto suelto sobre todo
   el ancho de la pantalla. */
.lote-hoja {
    width: 100%; max-width: 860px; margin: 0 auto; padding: 24px 20px 56px;
}

/* Cabecera: el numero de lote es lo que la persona vino a buscar, asi que es
   lo mas grande de la pagina. */
.lote-cab-txt { min-width: 0; }
.lote-etiqueta {
    font-size: 10px; font-weight: 700; color: var(--text3);
    text-transform: uppercase; letter-spacing: .8px;
}
.lote-pagina .lote-titulo {
    font-size: 26px; font-weight: 800; margin: 2px 0 0;
    letter-spacing: -0.02em; word-break: break-all;
}
.lote-pagina .lote-cab {
    border-bottom: 0; margin-bottom: 18px; padding-bottom: 0;
}

/* Cada bloque en su tarjeta, con las mismas variables que `.card`. Antes era
   todo una lista corrida y no se veia donde terminaba una cosa y empezaba la
   otra. */
.lote-bloque {
    background: var(--bg2); border: 1px solid var(--border);
    border-radius: var(--radius); padding: 16px 18px; margin-bottom: 14px;
}
.lote-bloque-cab {
    display: flex; align-items: baseline; justify-content: space-between;
    gap: 12px; margin-bottom: 12px;
}
.lote-pagina .lote-bloque-tit,
.lote-bloque .lote-bloque-tit {
    font-size: 11px; font-weight: 700; color: var(--text3);
    text-transform: uppercase; letter-spacing: .8px; margin: 0;
}
.lote-bloque-n { font-size: 12px; color: var(--text3); }

/* El stock TOTAL. Pedido de Deposito: con el lote repartido en tres depositos
   habia que sumar de cabeza para saber cuanto queda. */
.lote-total { display: flex; align-items: baseline; gap: 8px; }
.lote-total-et { font-size: 11px; color: var(--text3); text-transform: uppercase; letter-spacing: .6px; }
.lote-total-val {
    font-size: 17px; font-weight: 800; color: var(--text);
    font-variant-numeric: tabular-nums;
}

/* ── Lo que falta en este registro ───────────────────────────────────────────
   Un dato que no se pudo leer se ve, en la hoja, igual que un dato que no
   existe. Este recuadro es lo que los diferencia, y por eso va antes que
   cualquier otra cosa — incluso antes del vencimiento. */
.lote-aviso {
    display: flex; flex-direction: column; gap: 4px;
    padding: 12px 16px; border-radius: var(--radius);
    border: 2px solid var(--tag-err-c); background: var(--tag-err-bg);
    color: var(--tag-err-c); margin-bottom: 14px; font-size: 13px;
}
.lote-aviso strong {
    font-size: 12px; font-weight: 800; letter-spacing: .4px;
    text-transform: uppercase;
}
.lote-aviso-lista {
    margin: 2px 0 0; padding-left: 20px; line-height: 1.5;
}
.lote-aviso-pie { font-size: 12px; opacity: .9; }
.lote-aviso-btn { align-self: flex-start; margin-top: 6px; }
/* Lista de ingresos: con varios lotes, el primero y el chip «N lotes», en un
   renglón (todos en el globito y el filtro). La fecha no se parte: con cifras
   proporcionales unas entraban y otras no, y cada fila salía de otra altura. */
.ent-lote__varios, .ent-fecha { white-space: nowrap; }
.ent-lote__varios .ent-chip--cant { margin-left: 6px; }
@media print { .lote-aviso-btn { display: none !important; } }

/* ── El registro de auditoría avisa cuando está incompleto ───────────────────
   Mismo lenguaje que `.lote-aviso` —borde rojo grueso sobre fondo tenue— y por
   la misma razón: las dos dicen que a un registro le falta algo, y bajo GMP eso
   no es una advertencia más de la pantalla. Se lee antes que los filtros. */
.audit-hueco {
    display: flex; gap: 12px; align-items: flex-start;
    padding: 13px 16px; margin-bottom: 14px;
    border: 2px solid var(--tag-err-c); border-radius: var(--radius);
    background: var(--tag-err-bg); color: var(--tag-err-c);
    font-size: 13px; line-height: 1.5;
}
.audit-hueco svg { flex-shrink: 0; margin-top: 1px; }
.audit-hueco strong {
    font-weight: 800; letter-spacing: .2px;
}
.audit-hueco-que-hacer {
    margin-top: 5px; font-size: 12.5px; opacity: .92;
}
.audit-hueco code {
    font-family: ui-monospace, Menlo, Consolas, monospace;
    font-size: 11.5px; padding: 1px 5px; border-radius: 4px;
    background: color-mix(in srgb, var(--tag-err-c) 14%, transparent);
}

/* ── El estado del lote, arriba de todo ──────────────────────────────────────
   Un lote que vence en siete dias no puede estar escrito igual que el resto de
   los datos: en una planta farmaceutica es lo primero que hay que ver. */
.lote-estado {
    display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
    padding: 11px 16px; border-radius: var(--radius);
    border: 1px solid; margin-bottom: 14px; font-size: 13px;
}
.lote-estado strong {
    font-size: 12px; font-weight: 800; letter-spacing: .4px;
    text-transform: uppercase; white-space: nowrap;
}
.lote-estado--vencido {
    background: var(--tag-err-bg); border-color: var(--tag-err-c); color: var(--tag-err-c);
}
.lote-estado--proximo {
    background: var(--tag-warn-bg); border-color: var(--tag-warn-c); color: var(--tag-warn-c);
}
/* Vencido y ya consumido es historia, no un problema: se dice sin alarma. */
.lote-estado--historico {
    background: var(--bg3, var(--input-bg)); border-color: var(--border); color: var(--text2);
}
/* El ingreso anulado (V3, 30/10/2026): se ve antes que cualquier dato. */
.lote-estado--anulado {
    background: var(--tag-err-bg); border-color: var(--tag-err-c); color: var(--tag-err-c);
}
/* Dónde quedó el lote al cierre del día (L5): un renglón, sin punto ni número. */
.tl-cierre {
    list-style: none; margin: 0 0 6px; padding-left: 26px;
    font-size: 12px; color: var(--text2); font-variant-numeric: tabular-nums;
}
.tl-cierre::marker { content: none; }
.tl-cierre strong { font-weight: 700; color: var(--text); }

/* ── El balance del lote ─────────────────────────────────────────────────────
   Es LA pregunta de una inspeccion de DINAVISA: entro tanto, salio tanto,
   queda tanto, y que cierre. Por eso se lee como una CUENTA y no como tres
   datos sueltos: los signos - y = hacen el trabajo. */
.lote-balance-cuenta {
    display: flex; align-items: flex-end; gap: 14px; flex-wrap: wrap;
}
.lote-bal { display: flex; flex-direction: column; gap: 1px; }
.lote-bal-et {
    font-size: 10px; font-weight: 700; color: var(--text3);
    text-transform: uppercase; letter-spacing: .6px;
}
.lote-bal-val {
    font-size: 19px; font-weight: 800; color: var(--text);
    font-variant-numeric: tabular-nums; line-height: 1.1;
}
.lote-bal-val--entro { color: var(--tag-ok-c); }
.lote-bal-val--salio { color: var(--tag-warn-c); }
.lote-bal-op {
    font-size: 17px; color: var(--text3); font-weight: 400; padding-bottom: 1px;
}
.lote-balance-ok { font-size: 12px; font-weight: 600; color: var(--tag-ok-c); }
.lote-balance-mal { font-size: 12px; font-weight: 700; color: var(--tag-err-c); }

/* ── En qué se usó el lote ───────────────────────────────────────────────────
   Una lista de dos columnas: el producto a la izquierda y cuánto/en cuántas
   órdenes a la derecha, alineado. El nombre puede ser larguísimo —los de SAP
   traen presentación y gramaje— así que la columna de la derecha no se mueve
   y los nombres largos bajan de renglón sin desarmar la lista. */
.lote-uso-lista { list-style: none; margin: 0; padding: 0; }
.lote-uso-item {
    display: flex; justify-content: space-between; align-items: baseline;
    gap: 16px; padding: 7px 0;
    border-bottom: 1px solid var(--border-soft, rgba(0, 0, 0, .06));
}

/* ── La proporción de cada producto ──────────────────────────────────────────
   Eran 85 renglones idénticos, con cantidades que van de 16 a 0,0133 y todas
   pesando lo mismo en la página. La barra los vuelve comparables de un vistazo:
   se ve dónde terminó de verdad el lote sin leer los 85.

   Va de FONDO del renglón y no como una fila aparte: con 85 productos, dos
   renglones por producto son 170. El ancho lo pone `--uso-barra`, que la
   pantalla calcula desde el dato —no es una decisión de diseño, es cuánto se
   llevó ese producto—. */
.lote-uso-item { position: relative; isolation: isolate; }
.lote-uso-item::before {
    content: "";
    position: absolute; left: 0; top: 2px; bottom: 2px;
    width: var(--uso-barra, 0%);
    /* Degradado y no color plano: con un corte duro, el borde derecho de la
       barra parte las palabras del nombre y se lee como un error de pintado.
       Así se desvanece y el nombre queda entero. */
    background: linear-gradient(90deg,
        color-mix(in srgb, var(--accent) 16%, transparent) 0%,
        color-mix(in srgb, var(--accent) 9%, transparent) 82%,
        transparent 100%);
    border-radius: 3px;
    z-index: -1;
}

/* El porcentaje del total consumido. Va entre la cantidad y las órdenes, en
   una pastilla tenue: es un dato de apoyo, no compite con la cantidad. */
.lote-uso-pct {
    display: inline-block; margin-left: 7px; padding: 1px 5px;
    font-size: 10.5px; font-weight: 600; color: var(--text2);
    background: var(--bg3); border-radius: 3px;
    font-variant-numeric: tabular-nums;
}
.lote-uso-ord { margin-left: 8px; color: var(--text3); }
/* Emitido y devuelto, cuando hubo devolución: el neto solo no dice que el lote
   estuvo en la orden (13/10/2026). */
.lote-uso-dev { display: block; margin-top: 2px; color: var(--text3); }

/* «Ver los 85 productos». Mismo criterio que «Expandir todo»: la vista normal
   muestra los que importan y el resto se pide. */
.lote-uso-mas {
    display: block; width: 100%; margin-top: 8px; padding: 7px;
    background: none; border: 1px dashed var(--border); border-radius: var(--radius-sm);
    color: var(--accent-texto); font: inherit; font-size: 11.5px; font-weight: 600;
    cursor: pointer; transition: background var(--fas-transition-fast), 
                                 border-color var(--fas-transition-fast);
}
.lote-uso-mas:hover { background: var(--bg3); border-color: var(--accent); }
.lote-uso-mas:focus-visible { outline: 2px solid var(--accent-lt); outline-offset: 1px; }
.lote-uso-item:last-child { border-bottom: 0; padding-bottom: 0; }
.lote-uso-prod {
    font-size: 13px; font-weight: 600; color: var(--text); line-height: 1.4;
}
.lote-uso-datos {
    font-size: 12px; color: var(--text2); white-space: nowrap;
    font-variant-numeric: tabular-nums;
}
.lote-uso-datos strong { color: var(--text); font-weight: 700; }

/* Si no cierra, se ve. Es un hallazgo, no un detalle. */
.lote-balance--difiere { border-color: var(--tag-err-c); }
.lote-balance-nota {
    font-size: 12px; color: var(--text2); margin: 10px 0 0; line-height: 1.5;
}

/* ── La fecha como encabezado de la linea de tiempo ──────────────────────────
   Con cuatro movimientos el mismo dia, repetir la fecha cuatro veces es ruido.
   Asi la historia se lee como lo que es: una sucesion de dias. */
.tl-dia {
    list-style: none; position: relative; padding-left: 26px;
    margin: 14px 0 6px;
}
.tl-dia:first-child { margin-top: 0; }

/* ── El día como desplegable ─────────────────────────────────────────────────
   En pantalla arranca CERRADO: el lote MP226060082 tiene 230 movimientos y
   casi todos son el mismo consumo repetido, así que la página era scroll sin
   fin. Cerrados, la historia entera se lee en veinte renglones.

   Al imprimir se abren todos (`beforeprint`): la hoja de una inspección trae
   los 230, uno por uno. */
/* ── El día en la línea de tiempo ────────────────────────────────────────────
   Toda la geometría cuelga de UN eje vertical, en x = 20px: por ahí pasa el
   hilo, ahí está el centro del punto de cada día y ahí cae también el punto de
   cada movimiento cuando el día se abre. Un solo hilo, sin quiebres.

       20px
        │
        ●  05/06/2026   1 compra          ← día con hito
        │
        ·  17/06/2026   14 consumos       ← día de puro consumo
        │
   Los días con compra, control de calidad, traslado o salida se ven distinto
   de los de puro consumo. Sin eso, 31 renglones iguales y el día que importa
   perdido entre los que no. */
.tl-grupo { list-style: none; position: relative; }

/* El hilo, CONTINUO de punta a punta.
   Cada grupo dibuja su tramo de borde a borde, así los tramos se tocan y no
   queda una línea punteada; el primero arranca en su punto y el último
   termina en el suyo. */
.tl-grupo::before {
    content: ""; position: absolute; left: 20px; top: 0; bottom: 0;
    width: 2px; background: var(--border); transform: translateX(-1px);
    border-radius: 1px;
}
.tl-grupo:first-child::before { top: 17px; }
.tl-grupo:last-child::before { bottom: auto; height: 17px; }
/* Un solo día no necesita hilo: no conecta con nada. */
.tl-grupo:only-child::before { display: none; }

.tl-dia {
    position: relative; display: flex; align-items: baseline; gap: 9px;
    flex-wrap: wrap; width: 100%; text-align: left; cursor: pointer;
    padding: 7px 8px 7px 34px; margin: 0;
    background: none; border: 0; color: inherit; font: inherit;
    border-radius: var(--radius-sm);
    transition: background var(--fas-transition-fast);
}
.tl-dia:hover { background: var(--bg3); }
.tl-dia:focus-visible { outline: 2px solid var(--accent); outline-offset: -1px; }

/* El triangulito: dice que se abre, sin robarle protagonismo al punto. */
.tl-dia::before {
    content: ""; position: absolute; left: 6px; top: 50%;
    width: 0; height: 0; margin-top: -3.5px;
    border-left: 4px solid var(--text3);
    border-top: 3.5px solid transparent; border-bottom: 3.5px solid transparent;
    transition: transform var(--fas-transition-fast);
    transform-origin: 1.5px 50%;
}
.tl-dia[aria-expanded="true"]::before { transform: rotate(90deg); }

/* El punto del hilo, centrado en el eje. */
.tl-dia-punto {
    position: absolute; left: 15px; top: 50%; margin-top: -5px;
    width: 10px; height: 10px; border-radius: 50%;
    background: var(--bg2); border: 2px solid var(--border);
    transition: background var(--fas-transition-fast), 
                border-color var(--fas-transition-fast);
}
/* Día de puro consumo: apenas una marca, pero una marca.
   El halo del color del fondo lo recorta del hilo — sin eso el punto y la
   línea son del mismo gris y del mismo ancho, y en vez de «un punto sobre un
   hilo» se ve un hilo con engrosamientos. */
.tl-grupo:not(.tl-grupo--hito) .tl-dia-punto {
    left: 16px; margin-top: -4px; width: 8px; height: 8px;
    border: 2px solid var(--bg2); background: var(--text3);
}
/* Día con hito: punto lleno, con halo. Es lo que el ojo tiene que encontrar. */
.tl-grupo--hito .tl-dia-punto {
    background: var(--accent); border-color: var(--bg2);
    box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 22%, transparent);
}
.tl-dia:hover .tl-dia-punto { border-color: var(--accent); }

/* La jerarquía también en el texto: el día que importa se lee primero. */
.tl-dia-fecha {
    font-size: 12px; font-weight: 600; color: var(--text2);
    letter-spacing: .1px; font-variant-numeric: tabular-nums;
}
.tl-dia--hito .tl-dia-fecha { font-weight: 700; color: var(--text1); }

/* Lo que pasó ese día, para leerlo SIN abrirlo: «12 consumos · 4 productos». */
.tl-dia-resumen { font-size: 11.5px; color: var(--text3); font-weight: 400; }
.tl-dia--hito .tl-dia-resumen { color: var(--text2); }

/* «Expandir todo». Con 32 días, quien quiere leer la historia entera no va a
   abrirlos de a uno. Va en la cabecera del bloque, discreto: la vista normal
   es la de los días cerrados. */
.tl-todos {
    background: none; border: 0; padding: 2px 6px; cursor: pointer;
    font: inherit; font-size: 11.5px; font-weight: 600;
    color: var(--accent-texto); border-radius: var(--radius-sm);
    transition: background var(--fas-transition-fast);
}
.tl-todos:hover { background: var(--bg3); text-decoration: underline; }
.tl-todos:focus-visible { outline: 2px solid var(--accent-lt); outline-offset: 1px; }

/* Los movimientos del día abierto. El padding deja el punto de cada uno
   exactamente sobre el eje: 14 + 6 = 20. */
.tl--dia { margin: 2px 0 6px; padding-left: 14px; }
.tl--dia .tl-item:last-child { padding-bottom: 6px; }

/* ── LA HOJA IMPRESA TRAE TODO, Y NO DEPENDE DE JAVASCRIPT ──────────────────
   Los desplegables arrancan cerrados en pantalla con el atributo `hidden`.
   Estas dos líneas lo revierten en el papel: es CSS puro, así que la hoja sale
   completa aunque el JS no llegue a correr.

   No es teórico. Hasta el 27/08/2026 esto se hacía con <details> y el único
   que los abría era `beforeprint`: medido ese día, imprimiendo sin ese evento
   cada día medía 20 px y cada detalle 0 — la hoja salía con las fechas y
   ningún movimiento, en silencio. Un registro de trazabilidad vacío que no
   avisa de que está vacío es lo peor que puede pasar en una inspección.

   Impreso, además, el desplegable desaparece como tal: la fecha vuelve a ser
   un encabezado y el resumen del día sobra —abajo están los movimientos uno
   por uno—. */
@media print {
    .tl--dia[hidden] { display: block !important; }
    .tl-detalle[hidden] { display: grid !important; }

    .tl-dia {
        cursor: auto; padding: 0 0 0 26px; margin: 14px 0 6px;
        display: block; width: auto;
    }
    /* En papel el día es un encabezado, no un control: sin triangulito, sin
       punto y sin el hilo que los une —abajo van los movimientos, cada uno
       con el suyo—. Y sin el resumen, que repite lo que ya está listado. */
    .tl-dia::before,
    .tl-dia-punto,
    .tl-grupo::before,
    .tl-dia-resumen,
    .tl-todos,
    .lote-arriba,
    .lote-uso-mas,
    .tl-det-abrir { display: none !important; }

    /* Los 85 productos van completos en la hoja, y sin la barra: en papel
       gris sobre gris no se lee y el porcentaje ya dice lo mismo. */
    .lote-uso-resto[hidden] { display: block !important; }
    .lote-uso-item::before { display: none !important; }
    .tl--dia { padding-left: 0; }
}
.tl-dia time {
    font-size: 11px; font-weight: 700; color: var(--text2);
    letter-spacing: .3px;
}

/* El boton de la columna "Linea de tiempo". No se parte en dos renglones: en
   una tabla de trece columnas, "Ver" arriba y "mas" abajo queda pesimo. */
.tz-vermas { white-space: nowrap; }
.tz-vermas-td { text-align: center; }

/* ── Historia del lote (Trazabilidad) ───────────────────────────────────────
   El lote se abre desde la tabla y muestra su vida entera: lo que hizo FAS
   (quién lo cargó, qué rótulos se imprimieron y por qué se reimprimieron) más
   los movimientos que SAP registró después. */
.lote-modal { width: 640px; max-width: 94vw; max-height: 86vh; overflow-y: auto; }
.lote-cab {
    display: flex; justify-content: space-between; align-items: flex-start;
    gap: 14px; margin-bottom: 14px; padding-bottom: 12px;
    border-bottom: 1px solid var(--border);
}
.lote-titulo { font-size: 16px; font-weight: 700; color: var(--text); word-break: break-all; }
.lote-sub { font-size: 12px; color: var(--text2); margin-top: 3px; }

.lote-datos {
    display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
    gap: 10px 18px; margin-bottom: 16px;
}
.lote-dato-et {
    font-size: 10px; font-weight: 700; color: var(--text3);
    text-transform: uppercase; letter-spacing: .6px;
}
.lote-dato-val { font-size: 13px; color: var(--text); margin-top: 2px; word-break: break-word; }

/* Dónde está hoy: es la pregunta que se hace en planta, va arriba de todo. */
.lote-bloque-tit {
    font-size: 10px; font-weight: 700; color: var(--text3); text-transform: uppercase;
    letter-spacing: 1px; margin: 0 0 8px; padding-bottom: 6px;
    border-bottom: 1px solid var(--border);
}
.lote-stock { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 18px; }
/* Dentro de una tarjeta el margen de abajo sobra: la tarjeta ya tiene su
   relleno, y sumados dejaban un hueco muerto bajo los depositos. */
.lote-bloque > .lote-stock:last-child,
.lote-bloque > .lote-sin-stock:last-child { margin-bottom: 0; }
.lote-bodega {
    display: flex; flex-direction: column; gap: 1px; padding: 8px 14px;
    background: var(--bg3); border: 1px solid var(--border);
    border-left: 3px solid var(--accent); border-radius: 6px;
}
/* El NOMBRE ubica y el NUMERO es el dato: antes el nombre estaba en negrita y
   la cantidad en gris chiquito, o sea al reves de lo que se viene a leer. */
.lote-bodega-nom {
    font-size: 10px; font-weight: 700; color: var(--text3);
    text-transform: uppercase; letter-spacing: .5px;
}
.lote-bodega-cant {
    font-size: 15px; font-weight: 700; color: var(--text);
    font-variant-numeric: tabular-nums;
}
.lote-sin-stock { font-size: 12px; color: var(--text3); margin-bottom: 18px; }

/* Un dato que NO esta, dicho. No es una alarma —puede ser un lote viejo,
   anterior al sistema— pero callarlo hace que la hoja no distinga "no hay
   inspeccion" de "nadie pregunto". El trazo al costado lo separa de la lista
   de movimientos: es una afirmacion sobre el registro, no un movimiento mas. */
.lote-nota-falta {
    margin: 14px 0 0; padding: 8px 0 8px 12px;
    border-left: 2px solid var(--border);
    font-size: 12.5px; color: var(--text2);
}

/* De dónde salió cada punto: lo hizo FAS o lo registró SAP. */
.tl-origen {
    font-size: 9px; font-weight: 700; letter-spacing: .5px; padding: 1px 6px;
    border-radius: 8px; margin-left: 7px; vertical-align: 1px;
}
.tl-origen--fas { background: var(--tag-ok-bg); color: var(--tag-ok-c); }
.tl-origen--sap { background: var(--tag-blue-bg); color: var(--tag-blue-c); }
/* El punto de una salida iba RELLENO. Con la flecha adentro (24/08/2026) el
   relleno la tapaba: quedaba un circulo marron y se perdia justo lo que hace
   que la salida se distinga de un vistazo. Ahora solo se colorea el borde y
   el trazo; el relleno del bloque de arriba se encarga del resto. */
.tl-item--salida .tl-punto { border-color: var(--tag-warn-c); color: var(--tag-warn-c); }


/* ── Ficha del lote: imprimible ──────────────────────────────────────────────
   Un auditor no pide "mostrame la pantalla", pide "dame la trazabilidad del
   lote X". Con esto el navegador imprime SOLO la ficha, en A4, y "Guardar como
   PDF" produce el documento que se entrega. */
.lote-acciones { display: flex; gap: 8px; flex-shrink: 0; }

/* El membrete y el pie solo existen en el papel: en pantalla ya se sabe donde
   se esta parado y quien esta logueado.

   La nota sobre el orden dentro de un mismo dia va con ellos (22/09/2026).
   Es informacion que un inspector necesita —SAP guarda la fecha y no la hora,
   asi que el orden intradia es el del proceso y no una cronologia medida—,
   pero en pantalla eran dos renglones de explicacion arriba de los
   movimientos, y la regla del 18/09 es un rotulo, no una explicacion. En la
   hoja impresa, que es el documento que va a la inspeccion, sigue estando. */
.lote-membrete,
.lote-fin,
.tl-nota { display: none; }

@media print {
    /* Todo lo que no es la ficha desaparece: el fondo del modal, la tabla de
       atras, el menu, los botones.

       ⚠️ ESTA LISTA ES UNA TRAMPA, Y YA MORDIO DOS VECES.
       La regla vive en el CSS que carga TODA la aplicacion, pero la escribio
       Trazabilidad para su propia pantalla. Cualquier modulo que imprima
       desde un contenedor que no este nombrado aca IMPRIME EN BLANCO, sin
       ningun aviso: `display:none` en el padre gana sobre el
       `visibility:visible` que el modulo le ponga a sus hijos.

         1a vez — la pagina /entradas/lote/<lote> no tiene modal; su contenido
                  es `.lote-hoja`. Se agrego.
         2a vez — ROTULOS (03/09/2026). Su modal es `.rot-modal-bg`. La
                  pantalla se veia perfecta y la hoja salia vacia con
                  cualquier impresora: Datamax, Brother y "Guardar como PDF".
                  Se aislo renderizando con Chrome headless: sin esta regla
                  imprime, con ella no.
         Sin morder — el informe de produccion del Inicio de Deposito
                  (15/10/2026) imprime desde `.ent-prod-hoja`. Se agrego
                  antes de que saliera en blanco.

       SI AGREGAS UN MODULO QUE IMPRIMA, agregalo aca o no va a salir nada.
       El test tests/test_impresion_no_sale_en_blanco.py recorre los
       contenedores de impresion de los templates y falla si alguno falta. */
    body > *:not(.ent-modal-bg):not(.lote-hoja):not(.rot-modal-bg):not(.ent-prod-hoja) { display: none !important; }

    /* La hoja suelta ocupa la pagina entera y sin tarjetas: en papel los
       recuadros y las sombras solo gastan tinta. */
    .lote-pagina { height: auto !important; }
    .lote-hoja { max-width: none !important; padding: 0 !important; }
    .lote-bloque {
        background: none !important; border: 0 !important;
        border-top: 1px solid #999 !important;
        border-radius: 0 !important; padding: 8px 0 0 !important;
        margin-bottom: 8px !important;
    }
    .lote-bloque:first-of-type { border-top: 0 !important; }

    /* La HISTORIA se parte entre hojas; los movimientos de a uno, no.
       Con `break-inside: avoid` en el bloque entero, "Qué le pasó" saltaba
       completo a la hoja siguiente y dejaba dos tercios de la primera en
       blanco. Eran 3 hojas para lo que entra en 2 — reportado el 24/08/2026
       con el PDF del lote MP125120573. */
    .lote-bloque:has(.tl) { break-inside: auto; }
    .tl-item, .lote-balance, .lote-estado, .lote-aviso { break-inside: avoid; }

    /* ── En papel, cada movimiento entra en UN renglón ───────────────────────
       El título y la frase decían lo mismo en dos líneas: «Se consumió en
       producción» arriba de «Se consumieron 0,5 para fabricar…». Y entre
       evento y evento había 16 px de aire, que con 230 movimientos son cuatro
       hojas de blanco.

       En pantalla el título separado ayuda a escanear la lista; en papel, con
       cientos de eventos, es lo que la vuelve ilegible (27/08/2026). */
    .tl-item { padding-bottom: 5px; }

/* La nota sobre el orden del dia. Chica y en gris: no compite con los
   movimientos. Solo aca: en pantalla esta oculta (ver arriba). */
.tl-nota {
    display: block;
    font-size: 11px; line-height: 1.5; color: var(--text3);
    margin: 0 0 14px; padding-bottom: 10px;
    border-bottom: 1px solid var(--border);
}
    .tl-cab, .tl-accion, .tl-resumen { display: inline; }
    .tl-resumen::before { content: " · "; }
    .tl-fecha { margin-left: 6px; }
    /* El origen (FAS / SAP) queda, pero sin ocupar lugar propio. */
    .tl-origen { margin-left: 5px; }
    /* «En qué se usó» se PARTE entre hojas, igual que la historia. Se puso
       `avoid` pensando que eran pocas líneas: el lote MP226060082 tiene 85
       productos —tres hojas— y el bloque saltaba entero, dejando la anterior
       casi en blanco. Es el mismo error de arriba, repetido el 27/08/2026.
       Lo que no se parte es cada línea. */
    .lote-uso { break-inside: auto; }
    .lote-uso-item { break-inside: avoid; }

    /* El aviso de registro incompleto conserva SU marco en papel: sin el, la
       hoja no se distingue de una completa, que es justo el problema que
       viene a resolver. Y va con tinta, aunque el navegador tenga los fondos
       apagados: por eso el borde grueso y el texto en negro. */
    .lote-aviso {
        background: none !important; color: #000 !important;
        border: 2px solid #000 !important; padding: 8px 10px !important;
    }
    /* Una fecha sola al pie de la hoja, con sus movimientos en la siguiente,
       se lee como si ese dia no hubiera pasado nada. */
    .tl-dia { break-after: avoid; }

    /* Menos aire: en papel, el espacio en blanco entre bloques es una hoja
       mas, no respiro. */
    .lote-hoja .lote-datos { gap: 6px 18px !important; }
    .lote-hoja .tl-item { margin-bottom: 8px !important; }

    /* En papel el detalle va ABIERTO. "Ver detalle" en una hoja impresa es
       una instruccion que no se puede seguir, y encima esconde justo lo que
       una auditoria viene a mirar: la bodega, la cantidad y el tercero de
       cada movimiento. */
    .tl-det-abrir { display: none !important; }
    .tl-det > .tl-detalle { display: grid !important; }
    .tl-item { break-inside: avoid; }
    .ent-modal-bg { position: static !important; display: block !important;
                    background: none !important; padding: 0 !important; }
    .ent-modal-bg:not(.open) { display: none !important; }

    .lote-modal {
        width: 100% !important; max-width: none !important;
        max-height: none !important; overflow: visible !important;
        box-shadow: none !important; border: 0 !important;
        border-radius: 0 !important; padding: 0 !important;
        background: #fff !important; color: #000 !important;
    }
    .lote-acciones { display: none !important; }

    /* Los margenes no se aplican a un grupo de tabla: el aire y el trazo van
       en los renglones de adentro. */
    /* ── El membrete de la primera hoja (15/09/2026) ─────────────────────────
       Logo sobre blanco, empresa a la izquierda, el documento a la derecha:
       título, lote y artículo, sistema y usuario. Debajo, la regla gruesa. En
       las hojas siguientes lo que se repite es la cabecera de página (@page,
       en lote.html), que el navegador sí sabe repetir. */
    .lote-membrete {
        display: flex; align-items: flex-end; gap: 5mm;
        color: #000 !important; padding-bottom: 3mm; margin-bottom: 4mm;
        border-bottom: 1.5pt solid #000;
    }
    .lote-membrete-marca { width: 46mm; height: auto; display: block; }
    .lote-membrete-emp {
        flex: 0 0 auto; display: flex; flex-direction: column; gap: 1mm;
        white-space: nowrap;
    }
    .lote-membrete-emp-sub { font-size: 8pt; color: #333 !important; }
    .lote-membrete-docs {
        flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 1mm;
        text-align: right;
    }
    .lote-membrete-tit {
        font-size: 9pt; font-weight: 700; text-transform: uppercase; letter-spacing: .8px;
    }
    .lote-membrete-meta { font-size: 7pt; color: #444 !important; }
    .lote-titulo { font-size: 16pt !important; }
    /* Con `padding-bottom: 0` (regla de pantalla) el subtítulo apoyaba sobre
       la regla gruesa: en la impresión del 15/09/2026 el texto la tocaba. */
    .lote-cab { border-bottom: 2px solid #000 !important; padding-bottom: 2mm !important; }

    /* El cierre del registro. Si la última hoja se traspapela, la falta se
       nota: el documento termina en el aire. Y el total de movimientos deja
       contar contra lo que se tiene en la mano. */
    .lote-nota-falta {
        color: #000 !important; border-left: 1pt solid #000 !important;
        break-inside: avoid;
    }

    .lote-fin {
        display: block; margin-top: 14px; text-align: center;
        font-size: 8.5pt; letter-spacing: .5px; color: #000 !important;
        text-transform: uppercase; break-inside: avoid;
    }

    /* ── La ficha de dos hojas ("Imprimir ficha") ────────────────────────────
       Se saca la línea de tiempo, que es lo que hace crecer el documento: el
       registro completo del lote MP226060082 daba 62 hojas y así nadie lo
       abría (27/08/2026). Todo lo demás —el estado, dónde está, el balance y
       en qué se usó— se queda, porque es lo que se va a leer.

       Los dos cierres se turnan: la ficha dice que el registro completo se
       imprime aparte, para que dos hojas no pasen por el registro entero. */
    body.lote-solo-ficha .lote-bloque:has(.tl) { display: none; }
    body.lote-solo-ficha .lote-fin { display: none; }
    body.lote-solo-ficha .lote-fin--ficha { display: block; }
    .lote-fin--ficha { display: none; }

    /* Pie CORRIDO: se repite en TODAS las hojas. Va DENTRO del margen inferior
       —de ahí el `bottom` negativo contra el margen de 20mm de `@page`— para
       no pisar el contenido de la hoja. */
    /* El MEMBRETE se repite en TODAS las hojas, con el lote y el articulo.
       Una hoja suelta de un registro de trazabilidad tiene que decir sola de
       que lote es; sin esto, la hoja 4 de un legajo es un papel sin
       identificar.
       Se hace volviendo la hoja una tabla y el membrete su encabezado: es el
       UNICO mecanismo que el navegador repite de verdad. `position: fixed` se
       imprime una sola vez, el pie de tabla tampoco se repite en Chrome, y los
       contadores de pagina de CSS no estan implementados. Probado con el PDF
       del 25/08/2026. */
    /* Los MOVIMIENTOS van numerados, y el cierre dice cuántos son.
       Es lo que permite darse cuenta de que falta una hoja: si el registro
       dice 8 movimientos y en la mano están el 1, 2 y el 7, falta papel.
       Se hace así porque el navegador NO sabe imprimir "hoja X de Y" —no
       implementa los contadores de página de CSS— y ni el pie de tabla ni un
       elemento fijo se repiten en cada hoja al imprimir desde Chrome (probado
       las tres formas con el PDF del 25/08/2026).
       Desde Chrome 131 SÍ hay «Página N de M»: lo pone lote.html con las
       cajas de margen de @page (15/09/2026). La numeración de movimientos
       queda igual: es la que permite contar contra el cierre.

       El contador se reinicia UNA vez, en la lista de días (`.tl--dias`), no
       en cada día: con `.tl` a secas se reiniciaba por fecha y el registro
       impreso decía «1, 2» en cada día — visto en el PDF del 15/09/2026 —, y
       así no se puede contar contra «41 movimientos registrados». */
    .tl--dias { counter-reset: mov; }
    .tl-item { counter-increment: mov; }
    .tl-item .tl-accion::before {
        content: counter(mov) ". ";
        font-weight: 700; color: #000 !important;
    }

    /* En papel no hay "Ver detalle" que apretar: todo abierto o el documento
       llega incompleto, que es justo lo que no puede pasar en una auditoria.
       Lo abre `.tl-detalle[hidden]` mas arriba, para las dos pantallas. */
    .lote-modal .tl-detalle { display: grid !important; }

    /* Que un evento no se corte entre dos hojas. */
    .lote-modal .tl-item { page-break-inside: avoid; }
    .lote-modal .lote-bodega { border: 1px solid #000 !important; }

    @page { margin: 14mm; }
}


/* Atajos de vencimiento y contador de resultados de Trazabilidad. */
/* Los atajos van AL LADO del campo, no debajo: apilados hacen que este grupo
   sea mas alto que los demas y desalinean toda la fila de filtros. */
/* Una columna vacía en TODAS las filas no ocupa lugar. Lo marca el JS con
   `data-vacia` —ver `_ocultarColumnasVacias()`—: «Bultos» es un dato que
   carga FAS al recibir, SAP no lo tiene por lote, y en Trazabilidad salía
   «—» en pantallas enteras. */
.data-table [data-vacia] { display: none; }

/* ── Las filas de Trazabilidad, más bajas ────────────────────────────────────
   Medido el 27/08/2026 en 1440 px: los nombres largos —«ALUMINIO IMPRESO
   CARBONATO DE CALCIO»— se partían en TRES líneas y dejaban filas de 76 px.
   Con trece columnas, cada renglón de más se multiplica por toda la pantalla.

   Se probó repartir el ancho entre columnas y no sirve: con `table-layout:
   auto` manda el contenido, y forzar un `min-width` en la descripción empujaba
   la tabla 224 px fuera del contenedor —cambiaba filas altas por scroll
   horizontal—.

   SOLO EN ESCRITORIO. En el celular la tabla se convierte en tarjetas y el
   nombre del producto es el TÍTULO de cada una: recortarlo ahí sería cortar
   justo lo que identifica la fila. La primera versión de esta regla no tenía
   el `@media` y en 360 px el nombre salía con puntos suspensivos. */
@media (min-width: 769px) {
    #tablaTraza tbody td.tz-desc {
        /* UNA línea con puntos suspensivos, que es lo que funciona en una
           celda de tabla.
           Se intentó `-webkit-line-clamp: 2`: dentro de un `<td>` hay que
           cambiar el `display` a `-webkit-box`, y eso pelea con el layout de
           la tabla — ponía los puntos en la segunda línea pero dejaba asomar
           la tercera.

           El nombre completo no se pierde: está en el `title` de la celda y
           entero en la ficha del lote. Y al lado está el código, que es lo
           que identifica el producto sin ambigüedad. */
        max-width: 190px;
        white-space: normal;
    }
    /* DOS renglones (UX-10, 19/10/2026, Mauri reabrió Trazabilidad para esto):
       en 1366 la descripción cortada en uno no se leía. El corte va en el
       texto de ADENTRO, no en la celda: el `-webkit-box` en el `<td>` peleaba
       con la tabla y dejaba asomar el tercer renglón (lo de arriba). En un
       `<span>` la celda sigue siendo celda. */
    #tablaTraza tbody td.tz-desc .tz-desc__txt {
        display: -webkit-box;
        -webkit-box-orient: vertical;
        -webkit-line-clamp: 2;
        line-clamp: 2;
        overflow: hidden;
        overflow-wrap: anywhere;
        line-height: 1.3;
    }

    /* El vencimiento, en un renglón. «05/01/2026 · VENCIDO» partido en dos
       hacía crecer la fila entera por una palabra. */
    #tablaTraza tbody td[data-label="Vencimiento"] { white-space: nowrap; }

    /* Once columnas con 16 px de margen a cada lado eran 352 px de margen: la
       tabla no entraba ni en 1903 px. Con 10 px se recuperan 132 px. */
    #tablaTraza th, #tablaTraza td { padding-left: 10px; padding-right: 10px; }

    /* El proveedor, igual que la descripción: un renglón con «…» y el nombre
       completo en el `title`. Con razones sociales largas ocupaba hasta cinco
       renglones y cada fila medía otra cosa (02/10/2026). */
    #tablaTraza tbody td.tz-prov {
        max-width: 130px;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
    }
}

.venc-fila { display: flex; gap: 6px; align-items: center; }
.venc-rapido { display: flex; gap: 5px; }
.venc-rapido button {
    padding: 7px 10px; font-size: 11.5px; font-weight: 600;
    font-family: inherit; color: var(--text2); cursor: pointer;
    background: var(--bg3); border: 1px solid var(--border);
    border-radius: 6px; transition: all var(--t-fast);
}
.venc-rapido button:hover {
    color: var(--text); border-color: var(--accent-lt);
    background: var(--bg4);
}
/* El chip aplicado (UX-10, 19/10/2026): es lo único que dice que el filtro de
   vencimiento está puesto. */
.venc-rapido button[aria-pressed="true"] {
    color: #fff; background: var(--accent); border-color: var(--accent);
}
.traza-conteo { font-size: 12px; font-weight: 600; color: var(--text3); }


/* Modal de exportacion de Trazabilidad. */
.exp-campos {
    display: grid; grid-template-columns: 1fr 1fr; gap: 12px;
}
.exp-campos .field-group { display: flex; flex-direction: column; gap: 5px; }
/* Las etiquetas del sistema se definen dentro de `.ent-filtros`, y el modal
   vive fuera de ese bloque: sin esto salen con el tamaño por defecto y no se
   parecen a ninguna otra etiqueta de FAS. */
.exp-campos label {
    font-size: 10.5px; font-weight: 700; color: var(--text2);
    text-transform: uppercase; letter-spacing: .3px; border: 0;
}
.exp-campos input, .exp-campos select {
    width: 100%; padding: 8px 10px; font-size: 13px; font-family: inherit;
    color: var(--text); background: var(--input-bg);
    border: 1px solid var(--input-border); border-radius: 6px;
}
.exp-heredado {
    margin: 14px 0 0; padding: 9px 11px; border-radius: 6px;
    background: var(--bg3); border: 1px solid var(--border);
    font-size: 12px; line-height: 1.5; color: var(--text2);
}
.exp-heredado:empty { display: none; }

@media (max-width: 520px) {
    .exp-campos { grid-template-columns: 1fr; }
}

/* Encabezados que ordenan. La flecha aparece solo en la columna activa: una
   flecha en cada una es ruido y no dice cual manda. */
.th-ordenable { cursor: pointer; user-select: none; position: relative; }
.th-ordenable:hover { color: var(--accent-texto); }
.th-ordenable:focus-visible { outline: 2px solid var(--accent-lt); outline-offset: -2px; }
.th-asc::after, .th-desc::after {
    content: ''; display: inline-block; margin-left: 5px;
    border-left: 4px solid transparent; border-right: 4px solid transparent;
    vertical-align: middle;
}
.th-asc::after  { border-bottom: 5px solid var(--accent-lt); }
.th-desc::after { border-top: 5px solid var(--accent-lt); }
.th-asc, .th-desc { color: var(--accent-texto); }

/* "Vencidos" no es un plazo mas: es lo que hay que sacar del deposito hoy. */
.venc-rapido .venc-ya {
    color: var(--tag-err-c);
    border-color: color-mix(in srgb, var(--tag-err-c) 45%, transparent);
    background: var(--tag-err-bg);
}
.venc-rapido .venc-ya:hover {
    color: var(--tag-err-c);
    border-color: var(--tag-err-c);
    background: var(--tag-err-bg);
}

/* ── Filtros aplicados ───────────────────────────────────────────────────────
   Se muestran como fichas quitables de a una. Sin esto, una tabla filtrada se
   ve igual que una tabla vacía y nadie sabe por qué faltan filas. */
.filtro-chips {
    display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
    margin-bottom: 10px;
}
.filtro-chips[hidden] { display: none; }
.filtro-chips-tit {
    font-size: 11px; font-weight: 700; color: var(--text3);
    text-transform: uppercase; letter-spacing: .6px; margin-right: 2px;
}
.filtro-chip {
    display: inline-flex; align-items: center; gap: 7px;
    padding: 5px 10px; font-size: 12px; font-family: inherit;
    color: var(--text2); cursor: pointer;
    background: var(--bg3); border: 1px solid var(--border);
    border-left: 3px solid var(--accent-lt);
    border-radius: 6px; transition: all var(--t-fast);
}
.filtro-chip strong { color: var(--text); font-weight: 700; }
.filtro-chip span { font-size: 14px; line-height: 1; color: var(--text3); }
.filtro-chip:hover { border-color: var(--tag-err-c); color: var(--text); }
.filtro-chip:hover span { color: var(--tag-err-c); }
.filtro-chip--todos {
    border-left-color: var(--border); color: var(--text3); font-weight: 600;
}

/* Cuántos valores ofrece la autoayuda del filtro. */
.filtro-pista {
    margin: 8px 0 0; font-size: 11.5px; color: var(--text3); line-height: 1.4;
}
.filtro-pista:empty { display: none; }

/* ── Desplegable de valores del filtro ───────────────────────────────────────
   Propio y no <datalist>: el nativo lo dibuja el navegador —fondo negro,
   tipografía del sistema— y no hay forma de estilarlo. */
.filtro-val-wrap { position: relative; }
.filtro-val-wrap input { width: 100%; padding-right: 28px; }
.filtro-val-flecha {
    position: absolute; right: 6px; top: 50%; transform: translateY(-50%);
    display: flex; align-items: center; justify-content: center;
    width: 20px; height: 20px; padding: 0; cursor: pointer;
    color: var(--text3); background: none; border: 0;
}
.filtro-val-flecha:hover { color: var(--accent-texto); }
/* ── La lista de la autoayuda ────────────────────────────────────────────────
   Flota sobre el resto del modal, así que tiene que TAPAR de verdad: con una
   sombra floja se leía el texto de atrás asomando por el costado y parecía un
   error de pintado. */
.filtro-lista {
    position: absolute; z-index: 20; top: calc(100% + 4px); left: 0; right: 0;
    /* Un poco más ancha que el campo cuando el campo es angosto: los códigos
       de almacén entran justos y cortarlos no ayuda a elegir. */
    min-width: 220px;
    max-height: 260px; overflow-y: auto; overscroll-behavior: contain;
    background: var(--bg2); border: 1px solid var(--border);
    border-radius: 10px; padding: 5px;
    box-shadow: 0 10px 28px rgba(0, 0, 0, .18), 0 2px 6px rgba(0, 0, 0, .10);
    /* Sin `scrollbar-width: thin`: en Chrome ≥121, si encuentra las
       propiedades estándar ignora las reglas `::-webkit-scrollbar` del
       elemento y la barra sale MÁS angosta. Está cuidado por
       `test_NO_se_pide_barra_fina`, que me lo acaba de frenar. */
}
.filtro-lista[hidden] { display: none; }

/* Cada opción. Más compacta que antes —eran 60 px por renglón y cuatro
   almacenes ocupaban media pantalla—: 34 px deja ver siete de un vistazo sin
   dejar de ser cómoda para el dedo. */
.filtro-op {
    display: block; width: 100%; padding: 8px 10px; text-align: left;
    font-size: 12.5px; line-height: 1.25; font-family: inherit;
    color: var(--text); background: none; border: 0;
    border-radius: 6px; cursor: pointer;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
    transition: background var(--fas-transition-fast), 
                color var(--fas-transition-fast);
}
.filtro-op:hover,
.filtro-op:focus-visible {
    background: color-mix(in srgb, var(--accent) 12%, transparent);
    color: var(--text); outline: none;
}

/* Lo que coincide con lo escrito. Con veinte valores parecidos —CUART MP,
   CUART PT, CUARENTENA…— sin esto hay que leer cada renglón entero. */
.filtro-op-hit {
    background: none; color: var(--accent-texto); font-weight: 700;
    padding: 0; border-radius: 2px;
}

.filtro-op-mas {
    padding: 8px 10px; font-size: 11px; color: var(--text3);
    border-top: 1px solid var(--border); margin-top: 4px;
    line-height: 1.4;
}

/* Con la lista abierta, la pista se va.
   La lista flota encima y dejaba asomar el texto de atrás por el costado
   —«…ara verlos»—, que se lee como un error de pintado. Y además la pista
   dice «tocá el campo para verlos»: si ya se están viendo, sobra. */
#fasFiltroModal:has(.filtro-lista:not([hidden])) .filtro-pista {
    opacity: 0;
    transition: opacity var(--fas-transition-fast);
}
.filtro-pista { transition: opacity var(--fas-transition-fast); }

/* ── Ingreso: quitar una línea del ingreso (31/08/2026) ──────────────────────
   Antes la regla era implícita —«las líneas con cantidad 0 no se incluyen»—.
   Como todas arrancan en 0, el estado por defecto significaba «no va» sin que
   nadie lo dijera. Ahora sacar una línea es un acto explícito y reversible. */
.data-table th.th-quitar,
.data-table td.g-quitar   { width: 34px; padding-left: 6px; padding-right: 0; }

.ent-quitar {
    background: none; border: 0; cursor: pointer; padding: 5px 7px;
    font-size: 15px; line-height: 1; opacity: .72; border-radius: 6px;
    min-width: 30px; min-height: 30px;
    transition: opacity var(--t-fast), background var(--t-fast);
}
.ent-quitar:hover  { opacity: 1; background: var(--bg3); }
.ent-quitar:focus-visible { outline: 2px solid var(--primary); opacity: 1; }

.ent-quitadas {
    display: flex; align-items: center; gap: 10px; margin-bottom: 10px;
    font-size: 12.5px; color: var(--text3);
}

/* La unidad de medida al lado de la cantidad. Apagada a proposito: acompania
   al numero, no compite con el. Son cinco valores en todo el maestro y el mas
   largo tiene seis caracteres (medido el 31/08/2026), asi que no desarma la
   columna. */
.ent-unidad {
    font-size: 10.5px; color: var(--text3); font-weight: 600;
    margin-left: 4px; white-space: nowrap;
}

/* Aviso de que la OC ya tiene ingresos cargados en FAS. No bloquea: solo marca
   que hay que contrastar antes de registrar otro.

   Estaba escrito como `style="color:#ffb74d"` dentro del JS — prohibido por
   CLAUDE.md, y encima un naranja claro FIJO: sobre el tema claro —el que usa el
   celular, que es como se carga un ingreso en el deposito— quedaba casi
   invisible. `--tag-warn-c` esta definida en los dos temas. */
.ent-aviso-previos {
    color: var(--tag-warn-c); font-weight: 600;
}

/* El aviso de cantidad. Salta a la MITAD de diferencia, no a la primera:
   una recepcion casi nunca cierra exacta y el aviso anterior estaba encendido
   siempre. No bloquea nada — la cantidad se registra igual. */
.ent-aviso {
    font-size: 10px; font-weight: 700; letter-spacing: .2px;
    color: var(--tag-warn-c); margin-top: 3px; white-space: nowrap;
}

/* Los avisos de la grilla con los colores del tema (19/10/2026). Estaban
   fijos en el JS (#e08600, #e5484d, #37b24d): en tema claro —el del celular,
   con el que se carga un ingreso— el ámbar daba 2,9:1. El `!important` del
   borde ocupa el lugar del estilo en línea que reemplaza: tiene que ganarle
   al borde del campo y al de :focus. */
.ent-aviso--fuerte { color: var(--tag-err-c); }
input.ent-campo--aviso, select.ent-campo--aviso { border-color: var(--tag-warn-c) !important; }
input.ent-campo--error, select.ent-campo--error { border-color: var(--tag-err-c) !important; }
input.ent-campo--aviso-texto { color: var(--tag-warn-c); }
.ent-tercero {
    font-size: 9px; background: var(--accent); color: #fff; padding: 1px 6px;
    border-radius: 8px; vertical-align: middle; white-space: nowrap;
}
/* Los íconos de las tarjetas de vencimientos del Inicio de Depósito. Eran
   emojis (⛔ ⚠): cada sistema los dibuja distinto y el resto de FAS usa íconos
   SVG (TX-14, 19/10/2026). Toman el color de la tarjeta. */
.ent-ind-ico { vertical-align: -2px; margin-right: 5px; flex-shrink: 0; }
/* Los accesos rápidos repiten la barra de abajo del celular (UX-12). */
@media (max-width: 768px) {
    .ent-accesos { display: none; }
}
/* Rótulos: los filtros en su tarjeta, como en Entradas (UX-13). */
.rot-card--filtros .rot-filtros { margin-bottom: 0; }
/* El estado del ingreso en la bandeja de Rótulos. */
.rot-estado { font-weight: 700; }
.rot-estado--ok { color: var(--tag-ok-c); }
.rot-estado--espera { color: var(--tag-warn-c); }

/* El ALMACEN, debajo de la descripcion. Es el unico dato de identidad que no
   necesita columna propia: sale de la orden, no se edita y nadie lo va a
   BUSCAR con la vista — se mira una vez, para confirmar que la mercaderia va a
   cuarentena. Antes era una columna con un input de solo lectura.

   Va rotulado ("Almacen CUART MP"): sin el rotulo era un codigo suelto y no se
   entendia que era. Un `title` no alcanza, hay que pasar el mouse.

   El numero de linea y el codigo de articulo NO viven aca. Estuvieron un rato
   y fue un error: esos SI se buscan con la vista, y en una linea de metadatos
   no se buscan, se adivinan. Volvieron a ser columnas el 31/08/2026.

   Apagada a proposito: acompana a la descripcion, no compite con ella. */
.ent-meta {
    font-size: 10.5px; color: var(--text3); margin-top: 3px; white-space: nowrap;
    font-weight: 400;
}

/* El puntito de estado de la linea: gris sin cargar, ambar incompleta, verde
   completa. Va con el numero de linea, en su columna. */
.ent-dot {
    display: inline-block; width: 8px; height: 8px; border-radius: 50%;
    background: var(--text3); margin-right: 6px; vertical-align: middle;
    flex: 0 0 auto;
}
.ent-dot--incompleta { background: var(--tag-warn-c); }
.ent-dot--completa { background: var(--tag-ok-c); }
/* El codigo, en monoespaciada: se compara caracter por caracter contra el
   remito y la etiqueta fisica. */
.ent-meta .ent-cod {
    font-family: ui-monospace, Consolas, monospace; letter-spacing: -.2px;
}

/* Registro sanitario: el numero y su vencimiento, apilados. Son un solo
   concepto y tenian dos columnas. Pegados -3px- para que se lean como UN
   control y no como dos campos sueltos que cayeron ahi. */
.data-table td.g-regsan .ent-grid-input { display: block; width: 100%; }
.data-table td.g-regsan .ent-grid-input + .ent-grid-input { margin-top: 3px; }

/* ── LA FILA COMO UNIDAD (31/08/2026) ───────────────────────────────────────
   Mauricio: *"mejorame esta visual, necesito que sea lo mas pulcro y
   profesional"*.

   El problema no era ningun elemento suelto: era que la fila no se leia como
   una fila. Once columnas anchas, sin una linea que las separe y sin nada que
   acompane a la vista, y el ojo se pierde a mitad de camino entre el articulo
   y el campo donde tiene que escribir.

   Solo en escritorio: en el celular cada linea ya es una tarjeta con su propio
   borde, y esto la ensuciaria. */
@media (min-width: 769px) {
  /* Una linea fina entre filas. Es lo que convierte dos bloques de campos en
     dos renglones. */
  #nGrid tr[data-idx] + tr[data-idx] > td,
  #nGrid tr.ent-cerrada + tr[data-idx] > td,
  #nGrid tr[data-idx] + tr.ent-cerrada > td {
    border-top: 1px solid var(--border);
  }

  /* Y que se ilumine la que se esta tocando: con once columnas, seguir la
     fila con la vista de punta a punta es justo lo que cuesta. */
  #nGrid tr[data-idx]:hover > td,
  #nGrid tr[data-idx]:focus-within > td {
    background: var(--bg3, rgba(255, 255, 255, .03));
  }

  /* El boton del desglose media 24px contra los 28 de los campos: quedaba
     hundido respecto del numero de bultos que acompana. */
  .ent-bultos-toggle { width: 28px; height: 28px; }

  /* El tachito estaba en .45 y casi no se veia — y es la unica accion
     destructiva de la fila: tiene que encontrarse sin buscarla. */
  .ent-quitar { opacity: .8; }

  /* ── LAS TRES ZONAS DE LA FILA ─────────────────────────────────────────
     Mauricio: *"siente que se juntan las columnas entre si"*.

     Once columnas seguidas no se leen como once cosas: se leen como una masa.
     Se agrupa donde CAMBIA EL TIPO de dato. La fila tiene tres zonas y se leen
     en ese orden:

        QUE ES              Entrega · Linea · Codigo · Articulo
        CUANTO              Pedido · Recibido · Bultos
        QUE TRAE EL ENVIO   Lote · Vencimiento · Registro sanitario

     ── CON ESPACIO, NO CON LINEAS ────────────────────────────────────────
     La primera version puso dos lineas verticales. Mauricio las vio y las
     saco: *"ahora si esta separado, pero sacale esas lineas no me gusta,
     mantene nomas el orden"*.

     Y queda mejor. Una linea es una afirmacion —dice "aca hay un limite"— y en
     una fila que se lee de corrido no hay ningun limite: hay tres grupos. El
     espacio agrupa igual y no dibuja nada. Es lo que fue haciendo Fluent al
     dejar atras las tablas con reglas: separar con aire y reservar la tinta
     para los datos.

     El doble de aire donde arranca cada zona. Nada mas. */
  #nGrid td:nth-child(6),  #nGridCard thead th:nth-child(6),
  #nGrid td:nth-child(9),  #nGridCard thead th:nth-child(9) {
    padding-left: 26px;
  }
}

/* ── QUE TODA LA FILA ARRANQUE EN LA MISMA LINEA (31/08/2026) ───────────────
   Mauricio: *"kg no esta a la misma altura que el otro kg... es como que no
   estan todos en la misma linea, arriba abajo arriba abajo"*.

   Eran dos problemas encimados:

   1. La unidad ENVOLVIA. "250,000000 KG." no entraba en la columna, asi que
      "KG." caia a una segunda linea — y del lado de Recibido caia debajo del
      campo, o sea a otra altura. Con `nowrap` la columna se ensancha lo justo
      y los dos quedan en una sola linea.

   2. Las celdas se centraban verticalmente. La fila la hace alta la celda de
      Registro sanitario (dos campos apilados), asi que al centrarse cada una
      contra esa altura, su PRIMER campo quedaba mas arriba que todos los
      demas. Con `top` la primera linea de cada celda coincide.

   Los inputs traen borde y relleno propios; el texto plano no. Por eso las
   celdas de solo texto llevan unos pixeles mas arriba, para caer en la misma
   linea optica que los campos de al lado. */
@media (min-width: 769px) {
  #nGrid td { vertical-align: top; }
  #nGrid td.g-ped, #nGrid td.g-rec { white-space: nowrap; }

  /* ── Lo que sobraba a lo ancho, medido el 15/09/2026 ────────────────────
     Con el JS real y la OC 15097, a 1233 px de ventana —una pantalla de 1920
     al 125 % de escala, que es la de Mauricio— la tabla necesitaba 1209 px y
     la tarjeta daba 1090: desbordaba 119 px, la columna Artículo caía a 117
     y los nombres se partían. La grasa estaba en «UNIDAD» al lado del numero:
     Pedido medía 143 px y Recibido 161. La unidad se queda —fue un pedido—,
     pero DEBAJO del número. Medido después: entra con margen. */
  #nGrid .ent-unidad { display: block; margin-left: 0; margin-top: 2px; }

  /* Los últimos 14 px a 1233: no están en las celdas —la grilla ya lleva 8 px
     por lado desde el 31/08— sino en el margen de la página, 28 px por lado.
     Por debajo de 1400 se baja a 18: el contenido gana 20 px y el margen sigue
     siendo margen. En pantallas anchas no cambia nada. */
  @media (max-width: 1399px) {
    /* `body` delante: el <style> de la plantilla va después de esta hoja y
       con la misma especificidad la pisaba. Misma trampa que `.ent-wrap`. */
    body .ent-scroll { padding-left: 18px; padding-right: 18px; }
  }

  /* La etiqueta «Cerrada en SAP» iba al lado del nombre: con un nombre corto
     quedaba en la misma linea y con uno largo saltaba abajo. Dos filas
     seguidas, dos posiciones distintas. Siempre debajo. */
  #nGrid td.g-desc .ent-cerrada-tag { display: block; width: max-content; margin-top: 3px; }

  /* `.th-right` alineaba solo el ENCABEZADO: para `td` no había regla y las
     celdas quedaban a la izquierda bajo un título a la derecha. En Pedido no
     se notaba porque la columna mide justo lo que el número; en Recibido la
     unidad quedaba a la izquierda, y en las filas cerradas «Sin ingreso» y
     el «—» de Bultos también. Mauricio, 15/09: «no los veo alineados fila
     columna». Solo Depósito usa la clase en celdas. */
  .data-table td.th-right { text-align: right; }

  /* El nombre del artículo en UNA línea, como en Trazabilidad (27/08): a 1233
     px la columna queda en 117 y «CINTILLO BLANCO -ENZIMIX VIT» se partía en
     tres. Filas de alturas distintas en una grilla de captura. El nombre
     entero queda en el title, y al lado está el código, que es lo que
     identifica sin ambigüedad. `max-width: 0` es lo que permite recortar sin
     empujar la tabla: con `table-layout: auto` un nowrap sin tope ensancha
     la columna en vez de cortarla. */
  /* `width: 100%` con `max-width: 0`: el nombre absorbe todo lo que sobra y
     cede primero cuando falta. Sin el width, a 1920 el sobrante se iba a Reg.
     sanitario (236 px) y el nombre quedaba en 143. Medido. */
  #nGrid td.g-desc { max-width: 0; min-width: 120px; width: 100%; }
  #nGrid td.g-desc .ent-nombre { display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  #nGrid tr:not(.ent-cerrada) > td.g-fec,
  #nGrid tr:not(.ent-cerrada) > td.g-num,
  #nGrid tr:not(.ent-cerrada) > td.g-cod,
  #nGrid tr:not(.ent-cerrada) > td.g-ped,
  #nGrid tr:not(.ent-cerrada) > td.g-desc {
    padding-top: 21px;
  }
  /* El tachito es un boton sin borde: se alinea con los campos, no con el
     texto. */
  #nGrid tr:not(.ent-cerrada) > td.g-quitar { padding-top: 19px; }
  /* La fila CERRADA no lleva ningun campo -es toda texto-, asi que no hay nada
     que compensar: todas sus celdas arrancan parejas por si solas. */
}
.ent-link {
    background: none; border: 0; padding: 0; cursor: pointer;
    color: var(--primary); font-size: 12.5px; font-family: inherit;
    text-decoration: underline; text-underline-offset: 2px;
}

/* En celular la fila es una tarjeta: el tachito va arriba a la derecha, no
   como una "columna" perdida entre los datos. */
@media (max-width: 768px) {
    .data-table td.g-quitar { position: absolute; top: 6px; right: 6px;
                              width: auto; padding: 0; border: 0; }
    .ent-quitar { opacity: .7; }
}


/* ═══════════════════════════════════════════════════════════════════════════
   MODAL — UN SOLO CONTRATO VISUAL (16/09/2026)
   ═══════════════════════════════════════════════════════════════════════════
   FAS tenía cuatro modales distintos —.modal-overlay/.modal-box (Facturación
   y login), .ent-modal-bg/.ent-modal (Ingreso), .rot-modal-bg/.rot-modal
   (Rótulos) y .center-modal-overlay/.center-modal (Admin)— cada uno con su
   fondo, su radio, su sombra y su animación. La auditoría del 15/09/2026 lo
   marcó como «componentes no reutilizables». Cambiar el HTML de 40 modales
   es riesgo sin valor: lo que importa es que se VEAN y se COMPORTEN igual.
   Este bloque va al final del archivo a propósito: pisa a los cuatro con el
   mismo contrato. Los z-index no se tocan (el de Rótulos apila la vista
   previa sobre la lista de bultos). fas-modal-a11y.js cubre a los cuatro:
   role=dialog, aria-modal, Escape y devolución del foco. */
/* Solo en pantalla: al imprimir, Rótulos vuelve el fondo del modal al flujo
   y un padding acá correría la etiqueta en el papel. */
@media screen {
    .modal-overlay, .ent-modal-bg, .rot-modal-bg, .center-modal-overlay, .modal-bg {
        background: rgb(10 10 20 / 60%);
        backdrop-filter: blur(2px);
        padding: 16px;
    }
}
.modal-box, .ent-modal, .rot-modal, .center-modal {
    background: var(--bg2);
    color: var(--text);
    border: 1px solid var(--border);
    border-radius: 14px;
    box-shadow: 0 24px 60px rgb(0 0 0 / 35%), 0 2px 8px rgb(0 0 0 / 20%);
    animation: fasModalEntra .18s ease-out;
}
@keyframes fasModalEntra {
    from { opacity: 0; transform: translateY(10px) scale(.985); }
    to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
    .modal-box, .ent-modal, .rot-modal, .center-modal { animation: none; }
}
/* En el celular los cuatro son la misma hoja que sube desde abajo: la regla
   de móvil de arriba ya lo hace para tres; el de Admin entra acá. */
@media (max-width: 768px) {
    .center-modal-overlay { align-items: flex-end !important; padding: 0 !important; }
    .center-modal {
        width: 100% !important; max-width: 100% !important;
        max-height: 92vh !important; overflow-y: auto;
        border-radius: 16px 16px 0 0 !important;
        padding-bottom: calc(16px + env(safe-area-inset-bottom)) !important;
    }
    body:has(.center-modal-overlay.open) .fas-bottom-nav { display: none; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   MONITOR DE PLANTA (16/09/2026) — una TV por sector, leída a 4 metros.
   Tipografía grande, contraste alto, sin barra lateral. Verde = en el
   sector (A), rojo = en pausa (P). Mismos tokens que el resto de FAS.
   ═══════════════════════════════════════════════════════════════════════════ */
/* Tokens del monitor por tema. Claro por defecto: las TVs de 32" en la
   pared se leen mejor con fondo claro, como el monitor anterior. Contraste
   calculado: texto verde 1b5e20 sobre dff3e4 = 6,8:1; rojo 9f1b17 sobre
   fde2e1 = 6,5:1; en oscuro, 4ade80 y f87171 sobre 1b1b2a > 8:1. */
:root, :root[data-theme="light"] {
    --mon-ok-c: #1b5e20; --mon-ok-bg: #dff3e4; --mon-ok-borde: #2e7d32;
    --mon-pausa-c: #9f1b17; --mon-pausa-bg: #fde2e1; --mon-pausa-borde: #c62828;
    --mon-th-bg: #eef0f5;
}
:root[data-theme="dark"] {
    --mon-ok-c: #4ade80; --mon-ok-bg: rgb(74 222 128 / 8%); --mon-ok-borde: #4ade80;
    --mon-pausa-c: #f87171; --mon-pausa-bg: rgb(248 113 113 / 8%); --mon-pausa-borde: #f87171;
    --mon-th-bg: transparent;
}
.mon-body { margin: 0; background: var(--bg); color: var(--text); font-family: 'Inter', system-ui, sans-serif; min-height: 100vh; }
.mon { display: flex; flex-direction: column; min-height: 100vh; padding: clamp(16px, 2.5vw, 40px); gap: clamp(12px, 1.5vw, 24px); }
.mon-cabecera { display: flex; align-items: center; justify-content: space-between; gap: 24px; flex-wrap: wrap; }
.mon-cabecera-izq { display: flex; align-items: center; gap: 18px; min-width: 0; }
.mon-logo { width: clamp(40px, 4vw, 64px); height: auto; flex-shrink: 0; }
.mon-sobre { font-size: clamp(13px, 1.2vw, 20px); font-weight: 700; letter-spacing: .12em; text-transform: uppercase; color: var(--text2); }
.mon-titulo { margin: 0; font-size: clamp(26px, 3.4vw, 56px); font-weight: 800; letter-spacing: -0.02em; line-height: 1.1; }
.mon-cabecera-der { display: flex; align-items: center; gap: 24px; }
.mon-total { font-size: clamp(44px, 6vw, 104px); font-weight: 800; line-height: 1; font-variant-numeric: tabular-nums; color: var(--accent-texto); }
.mon-selector { display: flex; flex-direction: column; gap: 4px; }
.mon-selector-rotulo { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .1em; color: var(--text3); }
.mon-selector select { min-width: 220px; height: 38px; padding: 0 12px; border: 1px solid var(--border); border-radius: 8px; background: var(--input-bg); color: var(--text); font-size: 15px; }
.mon-aviso { padding: 12px 18px; border-radius: 10px; background: var(--tag-warn-bg); color: var(--tag-warn-c); font-size: clamp(15px, 1.4vw, 24px); font-weight: 600; }
.mon-tabla { width: 100%; border-collapse: collapse; font-size: clamp(16px, 1.75vw, 30px); }
.mon-tabla th { text-align: left; padding: 8px 14px; font-size: clamp(12px, 1vw, 17px); font-weight: 700; letter-spacing: .1em; text-transform: uppercase; color: var(--text2); background: var(--mon-th-bg); border-bottom: 2px solid var(--border); }
.mon-tabla td { padding: clamp(8px, .9vw, 16px) 14px; border-bottom: 1px solid var(--border); vertical-align: middle; }
.mon-fila--activo td { color: var(--mon-ok-c); background: var(--mon-ok-bg); }
.mon-fila--pausa td { color: var(--mon-pausa-c); background: var(--mon-pausa-bg); }
.mon-fila--activo td:first-child { box-shadow: inset 6px 0 0 var(--mon-ok-borde); }
.mon-fila--pausa td:first-child { box-shadow: inset 6px 0 0 var(--mon-pausa-borde); }
.mon-fila--activo .mon-orden, .mon-fila--pausa .mon-orden { color: inherit; opacity: .75; }
.mon-col-emp { width: 30%; font-weight: 700; }
.mon-col-art { width: 34%; }
.mon-col-pro { width: 26%; }
.mon-col-hora { width: 10%; text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.mon-orden { font-size: .7em; font-weight: 600; color: var(--text3); margin-left: 8px; white-space: nowrap; }
.mon-emp-art { display: none; }
.mon-vacio { text-align: center; color: var(--text2); padding: 48px 14px; font-size: clamp(18px, 2vw, 34px); }
.mon-pie { margin-top: auto; display: flex; align-items: center; gap: 22px; flex-wrap: wrap; color: var(--text2); font-size: clamp(13px, 1.1vw, 19px); }
.mon-pie-sep { flex: 1; }
.mon-pie-tv { color: var(--text3); }
.mon-tema { border: 1px solid var(--border); background: var(--bg2); color: var(--text2); border-radius: 8px; padding: 6px 12px; font: inherit; font-size: clamp(12px, 1vw, 17px); cursor: pointer; }
.mon-tema:hover { color: var(--text); border-color: var(--text3); }
.mon-chips { margin: 0 0 10px; font-size: clamp(12px, 1vw, 17px); }

/* Prototipo de Ana (17/09/2026): referencias, columna
   PRESENCIA (píldora) y TIEMPO ESTÁNDAR (icono + estado + detalle). */
.mon-refs { display: flex; gap: 12px; flex-wrap: wrap; margin: 0 0 12px; }
.mon-ref { flex: 1; min-width: 280px; display: flex; align-items: center; flex-wrap: wrap; gap: 6px 12px; padding: 8px 14px; border: 1px solid var(--border); border-radius: 10px; background: var(--mon-th-bg); color: var(--text2); font-size: clamp(12px, 1vw, 17px); }
.mon-ref-tit { flex-basis: 100%; font-size: .78em; font-weight: 700; letter-spacing: .1em; text-transform: uppercase; color: var(--text3); }
.mon-col-pres { width: 1%; white-space: nowrap; }
.mon-pres { display: inline-block; padding: .18em .7em; border-radius: 999px; font-size: .78em; font-weight: 700; letter-spacing: .06em; color: #fff; }
.mon-pres--abierta { background: var(--mon-ok-borde); }
.mon-pres--cerrada { background: var(--mon-pausa-borde); }
.mon-col-te { width: 1%; white-space: nowrap; text-align: center; }
.mon-te { display: inline-flex; align-items: center; gap: .5em; }
.mon-te-ico { display: inline-flex; align-items: center; justify-content: center; width: 1.7em; height: 1.7em; border-radius: 50%; color: #fff; background: var(--text3); flex-shrink: 0; }
.mon-te-ico svg { width: 65%; height: 65%; fill: none; stroke: currentColor; stroke-width: 3; stroke-linecap: round; stroke-linejoin: round; }
.mon-te--verde .mon-te-ico { background: var(--mon-ok-borde); }
.mon-te--amarillo .mon-te-ico { background: #f9a825; }
.mon-te--rojo .mon-te-ico { background: var(--mon-pausa-borde); }
/* Parpadean solo mientras la marcación sigue abierta: amarillo desde 10 min
   antes, rojo desde que excedió. Una cerrada queda quieta. */
.mon-fila--activo .mon-te--amarillo .mon-te-ico { animation: mon-parpadeo 1s steps(2, jump-none) infinite; }
.mon-fila--activo .mon-te--rojo .mon-te-ico { animation: mon-latido .8s ease-in-out infinite; }
.mon-te--sin .mon-te-ico { background: var(--text3); }
@keyframes mon-latido { 50% { opacity: .3; transform: scale(.85); } }
@keyframes mon-parpadeo { 50% { opacity: .2; } }
/* La fila por la que suena: tres destellos y un borde, 4 s. */
.mon-fila--activo.mon-fila--suena-amarillo td { background: #ffe39a; color: #6b4300; animation: mon-destello 1.3s ease-in-out 3; }
.mon-fila--activo.mon-fila--suena-rojo td { background: #ffb8b5; color: #7a100c; animation: mon-destello 1.3s ease-in-out 3; }
.mon-fila--suena-amarillo td:first-child { box-shadow: inset 10px 0 0 #f9a825; }
.mon-fila--suena-rojo td:first-child { box-shadow: inset 10px 0 0 var(--mon-pausa-borde); }
.mon-fila--suena-amarillo .mon-te-ico, .mon-fila--suena-rojo .mon-te-ico { transform: scale(1.35); }
@keyframes mon-destello { 50% { filter: brightness(.8) saturate(1.5); } }
@media (prefers-reduced-motion: reduce) { .mon-fila--suena-amarillo td, .mon-fila--suena-rojo td { animation: none; } }
@media (prefers-reduced-motion: reduce) { .mon-fila--activo .mon-te--rojo .mon-te-ico, .mon-fila--activo .mon-te--amarillo .mon-te-ico { animation: none; opacity: 1; } }
.mon-tema--activo { border-color: var(--accent); color: var(--accent-texto); }
.mon-sonido-aviso { color: var(--warning, #b26a00); font-size: clamp(12px, 1vw, 17px); }
.mon-sonido-aviso[hidden] { display: none; }
.mon-sin-acceso { min-height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; padding: 24px; gap: 12px; }
.mon-texto { color: var(--text2); font-size: 18px; max-width: 520px; }
@media (max-width: 768px) {
    .mon-cabecera-der { width: 100%; justify-content: space-between; }
    .mon-selector { flex: 1; min-width: 0; }
    .mon-selector select { width: 100%; min-width: 0; }
    .mon-tabla { table-layout: fixed; }
    .mon-tabla th, .mon-tabla td { padding-left: 8px; padding-right: 8px; }
    /* Con table-layout fixed la última columna recibe lo que queda: un título
       más ancho que la celda salía del th y empujaba la pantalla (22 px a
       320, medido el 21/09/2026). El th no puede desbordar, y el título de
       Tiempo estándar es «Est.». */
    .mon-tabla th { letter-spacing: .04em; overflow: hidden; text-overflow: ellipsis; }
    .mon-col-art, .mon-tabla th.mon-col-art { display: none; }
    .mon-emp-art { display: block; font-size: .8em; font-weight: 400; opacity: .8; margin-top: 2px; }
    .mon-col-emp { width: 38%; }
    .mon-col-pro { width: 34%; }
    .mon-col-hora { width: 16%; }
    /* En el teléfono la columna Presencia no entra, pero es dato operativo
       (EN SECTOR / CERRADO): va como etiqueta bajo el nombre, no se oculta
       (auditoría 21/09/2026). El tiempo estándar queda solo con el icono. */
    .mon-col-pres, .mon-tabla th.mon-col-pres { display: none; }
    .mon-emp-pres { display: inline-block; margin-top: 3px; padding: 1px 6px; border-radius: 10px; font-size: .68em; font-weight: 700; letter-spacing: .04em; }
    .mon-refs { display: none; }
    .mon-col-te, .mon-tabla th.mon-col-te { width: 12%; padding-left: 4px; padding-right: 4px; text-align: center; }
    .mon-orden { display: block; margin-left: 0; }
    .mon-th-largo { display: none; }
}
.mon-th-corto { display: none; }
@media (max-width: 768px) { .mon-th-corto { display: inline; } }
@media (min-width: 769px) { .mon-emp-pres { display: none; } }
/* Teléfonos angostos (320): los títulos «Hora» y «Est.» no entraban ni con
   elipsis; menos espaciado y dos puntos más de columna para ellos. */
@media (max-width: 400px) {
    .mon-tabla th { font-size: 11px; letter-spacing: 0; }
    .mon-col-emp { width: 35%; }
    .mon-col-pro { width: 31%; }
    .mon-col-hora { width: 18%; }
    .mon-col-te, .mon-tabla th.mon-col-te { width: 16%; }
}
.mon-pasos { margin: 8px 0 0; padding-left: 22px; color: var(--text2); font-size: 13px; line-height: 1.7; }
.mon-acciones { white-space: nowrap; }
.mon-enlace-acciones { display: flex; gap: 8px; align-items: center; }
@media (max-width: 768px) { .mon-acciones { white-space: normal; } }
/* Ajuste automático a la pantalla: en una TV nadie scrollea. monitor.js
   mide si la tabla se pasa del alto de la ventana y sube la densidad de a
   un paso hasta que entre (tipografía y relleno más chicos). */
.mon--denso-1 .mon-tabla { font-size: clamp(14px, 1.35vw, 24px); }
.mon--denso-1 .mon-tabla td { padding-top: clamp(5px, .55vw, 10px); padding-bottom: clamp(5px, .55vw, 10px); }
.mon--denso-2 .mon-tabla { font-size: clamp(13px, 1.1vw, 20px); }
.mon--denso-2 .mon-tabla td { padding-top: clamp(4px, .4vw, 7px); padding-bottom: clamp(4px, .4vw, 7px); }
.mon--denso-2 .mon-titulo { font-size: clamp(22px, 2.6vw, 40px); }
.mon--denso-2 .mon-total { font-size: clamp(36px, 4.5vw, 72px); }
.mon--denso-3 .mon-tabla, .mon--denso-4 .mon-tabla, .mon--denso-5 .mon-tabla { font-size: clamp(12px, .9vw, 16px); }
.mon--denso-3 .mon-tabla td, .mon--denso-4 .mon-tabla td { padding-top: 3px; padding-bottom: 3px; }
.mon--denso-3 .mon-titulo, .mon--denso-4 .mon-titulo { font-size: clamp(20px, 2.2vw, 32px); }
.mon--denso-3 .mon-total, .mon--denso-4 .mon-total { font-size: clamp(30px, 3.6vw, 56px); }
.mon--denso-3 .mon, .mon--denso-4 .mon { gap: 8px; padding-top: 12px; padding-bottom: 12px; }
/* Notebooks (24/09/2026). Con el escalado de Windows al 125-150 % la ventana
   útil queda en 1280×590 o 1536×730, y desde 12-15 personas el nivel 3 no
   entraba: los nombres largos de artículo y proceso ocupaban dos o tres
   renglones por fila. Una TV a pantalla completa no llega a estos niveles
   con menos de ~20 personas, así que se ve igual que antes.
   Solo de 769 px para arriba: en el teléfono la tabla es otra y se desplaza. */
@media (min-width: 769px) {
    /* 4 · Un renglón por fila; lo largo se corta con «…» y el title lo
       muestra entero. max-width: 0 deja que la celda sea más angosta que su
       texto sin pasar la tabla a table-layout: fixed. */
    .mon--denso-4 .mon-col-emp, .mon--denso-4 .mon-col-art, .mon--denso-4 .mon-col-pro,
    .mon--denso-5 .mon-col-emp, .mon--denso-5 .mon-col-art, .mon--denso-5 .mon-col-pro {
        max-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    }
    /* 5 · Además, lo que rodea a la tabla ocupa menos alto. */
    .mon--denso-5 .mon { gap: 6px; padding-top: 8px; padding-bottom: 8px; }
    .mon--denso-5 .mon-tabla th { padding-top: 4px; padding-bottom: 4px; }
    .mon--denso-5 .mon-tabla td { padding-top: 2px; padding-bottom: 2px; }
    .mon--denso-5 .mon-te-ico { width: 1.35em; height: 1.35em; }
    .mon--denso-5 .mon-logo { width: clamp(28px, 2.6vw, 44px); }
    .mon--denso-5 .mon-titulo { font-size: clamp(18px, 1.8vw, 26px); }
    .mon--denso-5 .mon-total { font-size: clamp(26px, 2.8vw, 44px); }
    .mon--denso-5 .mon-selector select { height: 30px; }
    .mon--denso-5 .mon-refs { margin-bottom: 0; }
    .mon--denso-5 .mon-ref { padding: 3px 10px; }
    .mon--denso-5 .mon-ref-tit { display: none; }
    .mon--denso-5 .mon-tema { padding-top: 3px; padding-bottom: 3px; }
}
/* Ni en el último nivel entra: la página se desplaza. El body del sistema
   tiene overflow: hidden, y en el Monitor eso escondía personas. */
.mon--desborda .mon-body { overflow-y: auto; }

/* ── Buscar el producto por NOMBRE (Trazabilidad, 21/09/2026) ───────────────
   Pedido de Compras: «si escribo Aro, que el autobuscador ya le ayude y le
   traiga todo lo que empieza con Aro, Aroma o demás». Compras no se sabe los
   códigos de artículo; se sabe cómo se llama el producto.

   Desplegable propio y no datalist, por lo mismo que en Rótulos y en el
   filtro de columnas: el nativo lo pinta el sistema operativo y no se puede
   llevar al estilo de FAS. */
.ent-ac { position: relative; }
.ent-sug {
    display: none; position: absolute; z-index: 60; top: 100%; left: 0;
    min-width: 100%; max-width: 420px; margin-top: 5px;
    background: var(--bg2); border: 1px solid var(--border);
    border-radius: 10px; box-shadow: var(--elev-2, 0 10px 28px rgba(0,0,0,.16));
    max-height: 300px; overflow-y: auto; padding: 4px;
    /* Que el scroll de la lista no arrastre la página cuando se llega al
       final: en el celular era lo que pasaba y se perdía el campo de vista. */
    overscroll-behavior: contain;
}
.ent-sug.open { display: block; }
.ent-sug-item {
    padding: 7px 11px; border-radius: 7px; cursor: pointer;
    font-size: 12.5px; color: var(--text); line-height: 1.35;
}
.ent-sug-item:hover, .ent-sug-item.sel { background: var(--accent); color: #fff; }
.ent-sug-item mark {
    background: none; color: inherit; font-weight: 700;
    text-decoration: underline; text-underline-offset: 2px;
}
/* El código, en monoespaciada y debajo del nombre: el nombre es lo que se
   busca, el código es lo que después aparece en la tabla. */
.ent-sug-cod {
    display: block; font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 11px; color: var(--text3); letter-spacing: .02em;
}
.ent-sug-item:hover .ent-sug-cod, .ent-sug-item.sel .ent-sug-cod { color: rgba(255,255,255,.75); }
/* En el Historial (30/10/2026), el nombre debajo del código o del lote: en
   letra común, porque es para leer, no para copiar. */
.ent-sug-det { display: block; font-size: 11px; color: var(--text3); }
.ent-sug-item:hover .ent-sug-det, .ent-sug-item.sel .ent-sug-det { color: rgba(255,255,255,.75); }
.ent-sug-vacio { padding: 9px 11px; font-size: 12px; color: var(--text3); }

/* Los productos ya elegidos. Se pueden acumular varios —«todo lo que empieza
   con Aro» son varios artículos— y cada uno se saca por separado.

   Van en su PROPIA fila, debajo de los campos (22/09/2026): dentro del campo
   Producto se dibujaban encima de los de abajo y con dos elegidos tapaban
   «Lote / Lote proveedor», etiqueta incluida. */
.ent-elegidos {
    display: flex; flex-wrap: wrap; gap: 6px; align-items: center;
    padding-top: 10px; border-top: 1px solid var(--border);
}
.ent-elegidos-tit {
    font-size: 10.5px; font-weight: 700; color: var(--text2);
    text-transform: uppercase; letter-spacing: .3px; margin-right: 2px;
}
.ent-elegido {
    display: inline-flex; align-items: center; gap: 6px;
    background: color-mix(in srgb, var(--accent) 14%, transparent);
    border: 1px solid var(--border); border-radius: 999px;
    padding: 3px 4px 3px 10px; font-size: 12px; color: var(--text);
    max-width: 260px;
}
.ent-elegido-txt { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ent-elegido-x {
    border: none; background: none; color: var(--text2); cursor: pointer;
    font-size: 15px; line-height: 1; padding: 2px 6px; border-radius: 999px;
    min-width: 24px; min-height: 24px;
}
.ent-elegido-x:hover { background: var(--input-bg); color: var(--text); }

/* ── Las acciones de los filtros, agrupadas (22/09/2026) ────────────────────
   Buscar, Limpiar, Ficha del artículo y Escanear QR estaban sueltos entre los
   campos de `.ent-filtros`: se acomodaban donde cayeran, y al entrar el campo
   «Producto» quedaban dos arriba junto a los filtros y dos abajo.

   Agrupados se mueven como uno solo. NO se les fuerza renglón propio: bajan
   cuando no entran y se quedan al lado cuando sí. Con un campo —«Buscar OC»—
   todo queda en una línea; con cinco, los botones abajo. */
.ent-filtros-acciones {
    display: flex; flex-wrap: wrap; gap: 10px; align-items: center;
}

/* ══════════════════════════════════════════════════════════════════════════
   MARCA · 09/10/2026
   ══════════════════════════════════════════════════════════════════════════
   El login nuevo es el rebranding que pidió Ana. Adentro tiene que sentirse la
   misma marca sin perder lo que una pantalla de trabajo necesita: leer, cargar
   y revisar durante horas. Por eso acá NO hay fotos, vidrio ni transparencias
   sobre datos —fondos sólidos— y la marca aparece en cinco lugares:

     · el menú y la barra de arriba, en el violeta profundo del acceso, con
       Grupo Heisecke · FUSQUIM al pie del menú (templates/_marca_menu.html);
     · la barra de luz (violeta → dorado) como indicador de carga;
     · la acción principal en dorado, como «Ingresar», en todos los módulos;
     · los títulos con el peso del login;
     · tarjetas con un solo radio y sombras suaves.

   Los colores de estado (éxito, advertencia, error) significan lo mismo que
   antes. No cambia ninguna medida de tablas ni formularios, no se mueve ningún
   botón ni cambia un nombre. Va todo junto y al final a propósito: si algo no
   convence, se saca este bloque y FAS vuelve a como estaba.
   ══════════════════════════════════════════════════════════════════════════ */

:root {
    --radius:            10px;
    --marca-luz-1:       #8b6fd6;
    --marca-luz:         linear-gradient(90deg, transparent, #a78bfa, #f6bb54, transparent);
    --marca-oro:         linear-gradient(180deg, #f8c66a, #f2ac3a);
    --marca-oro-tinta:   #24123f;   /* 8,9:1 sobre el dorado */
    --marca-oro-sombra:  0 2px 8px rgba(242, 172, 58, .22);
    --marca-oro-sombra-2: 0 6px 18px rgba(242, 172, 58, .34);
}
/* El menú y la barra de arriba son oscuros en los dos temas. Del gris azulado
   pasan al violeta del acceso, con la misma luminancia: el contraste del texto
   del menú queda igual. */
[data-theme="dark"],
[data-theme="light"] {
    --sidebar-bg: #1a1130;
    --topbar-bg:  #130b25;
}
/* En claro, la sombra es la que separa la tarjeta del fondo: un poco más
   presente y con el tono de la marca en vez de gris. */
[data-theme="light"] {
    --elev-1: 0 1px 2px rgba(31, 18, 64, .06), 0 2px 8px rgba(31, 18, 64, .05);
    --elev-2: 0 4px 12px rgba(31, 18, 64, .09), 0 10px 26px rgba(31, 18, 64, .07);
}

/* ── Barra de arriba: una línea de luz al pie, quieta ───────────────────── */
.topbar::after {
    content: '';
    position: absolute;
    left: 0; right: 0; bottom: 0;
    height: 1px;
    background: linear-gradient(90deg, transparent 0%, rgba(167, 139, 250, .45) 30%,
                                rgba(246, 187, 84, .45) 70%, transparent 100%);
    pointer-events: none;
}
.topbar-title { font-weight: 700; letter-spacing: .2px; }
/* El título se acorta con «…» antes que empujar la barra: con el selector de
   módulos, en Admin a 320 px la barra se pasaba 5 px (axe, 09/10/2026). */
.topbar-title { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ── Menú lateral ───────────────────────────────────────────────────────── */
.sidebar { border-right-color: rgba(167, 139, 250, .10); }
.nav-item.active {
    background: linear-gradient(90deg, rgba(167, 139, 250, .20) 0%, rgba(167, 139, 250, .07) 100%);
    box-shadow: none;
}
/* .56 y no .42 (14/10/2026): con .42 los títulos daban 4,05:1 sobre el menú,
   debajo del 4,5:1 que pide un texto de 10 px. Medido en las 18 pantallas. */
.sidebar-section-title { color: rgba(255, 255, 255, .56); }

/* ── Sublista del menú (26/10/2026) ─────────────────────────────────────────
   Pedido de Calidad: «Anulación de ingresos» aparte, debajo de «Ingreso de
   mercadería», como el menú de Compras. El padre abre y cierra
   (fasNavGrupo, fas-utils.js); los hijos van con sangría y un ícono más
   chico. Con el menú colapsado la sublista no se ve y el padre lleva al
   primer hijo. */
/* La flecha va sobre el margen derecho y no en el renglón: en los 220 px del
   menú, «Ingreso de mercadería» no entraba en una línea con ella al lado. El
   padre no se corre al pasar el mouse: abre y cierra, no navega. */
/* Medido en Chrome (26/10/2026): el texto pide 149 px; con 17 px a la derecha
   tiene 151, dos de holgura para otro equipo. */
.nav-item.nav-grupo__padre { position: relative; padding-right: 17px; }
.nav-item.nav-grupo__padre:hover { padding-left: 16px; }
.nav-grupo__texto { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }
.nav-grupo__flecha { position: absolute; right: 4px; top: 50%; margin-top: -6px; display: flex; opacity: .7; transition: transform var(--fas-transition-fast); }
.nav-grupo__flecha svg { width: 12px; height: 12px; }
.nav-grupo__padre[aria-expanded="false"] .nav-grupo__flecha { transform: rotate(-90deg); }
.nav-grupo__padre--activo { color: #fff; }
.nav-grupo__lista[hidden] { display: none; }
.nav-item.nav-grupo__hijo { padding-left: 32px; font-size: 12.5px; }
.nav-item.nav-grupo__hijo:hover { padding-left: 36px; }
.nav-item.nav-grupo__hijo .nav-icon svg { width: 14px; height: 14px; }
body.sidebar-collapsed .nav-grupo__lista,
body.sidebar-collapsed .nav-grupo__flecha { display: none; }
.sidebar-marca { display: flex; align-items: center; gap: 10px; padding: 2px 0 9px; }
/* Un solo logo desde el 03/11/2026: el oficial de Grupo Heisecke, en vector. */
.sidebar-marca img { display: block; width: auto; height: 24px; opacity: .85; }
/* La versión iba en el violeta de fondo de botón (#4b2c80) sobre el menú
   oscuro: no se leía. La leyenda «FAS · Grupo Heisecke» salió del pie: los
   logos ya dicen de quién es. */
.sidebar-version-badge {
    color: #cdbdf7;
    background: rgba(167, 139, 250, .12);
    border-color: rgba(167, 139, 250, .32);
}
.sidebar-version-badge.env-test {
    color: #f7b267;
    background: color-mix(in srgb, var(--est-warn) 12%, transparent);
    border-color: color-mix(in srgb, var(--est-warn) 35%, transparent);
}

/* ── Títulos con el peso del login ──────────────────────────────────────── */
.page-title { font-weight: 800; letter-spacing: -.4px; }
.card-header h2 { letter-spacing: -.1px; }

/* ── La acción principal, en dorado y en todos los módulos ──────────────────
   El mismo botón que «Ingresar» del login. Antes cada módulo tenía su violeta
   (.btn-primary, .ent-btn, .rot-btn, el confirmar del modal) y en el tema
   oscuro el violeta de fondo de botón casi no se separaba del panel. Los
   secundarios quedan como estaban: así la principal se distingue sola.
   `.btn.btn-primary`, `.ent-btn:not(.sec)` y `.modal-btn.modal-btn-confirm`
   (dos clases): Admin, Rótulos y Revisar redefinen el botón en su propia
   hoja, que va después de esta. */
.btn-primary,
.btn.btn-primary,
.ent-btn:not(.sec),
.rot-btn:not(.sec),
.modal-btn.modal-btn-confirm {
    background: var(--marca-oro);
    color: var(--marca-oro-tinta);
    font-weight: 700;
    box-shadow: var(--marca-oro-sombra);
}
.btn-primary.btn-primary:hover:not(:disabled),
.ent-btn:not(.sec):hover:not(:disabled),
.rot-btn:not(.sec):hover:not(:disabled),
.modal-btn.modal-btn-confirm:hover {
    opacity: 1;
    filter: brightness(1.04);
    box-shadow: var(--marca-oro-sombra-2);
}
/* Advertencia ≠ acción principal. El naranja liso (#f59e0b) quedaba casi igual
   al dorado y además no llegaba al contraste con letra blanca (2,2:1). Pasa a
   la forma de las etiquetas de advertencia: se lee y no compite. */
.btn.btn-warning {
    background: var(--tag-warn-bg);
    color: var(--tag-warn-c);
    border: 1px solid var(--tag-warn-c);
}

/* ── Monitor de planta ──────────────────────────────────────────────────────
   Lo ve toda la planta. Marca discreta y sin sumar altura (el monitor ajusta
   la densidad a lo que entra): la barrita de luz en línea con «Personas en
   sector» y los logos del grupo al pie, en blanco sobre el oscuro y en gris
   oscuro sobre el claro. Los colores del semáforo no se tocan. */
.mon-sobre::before {
    content: '';
    display: inline-block;
    vertical-align: middle;
    width: 1.6em;
    height: .22em;
    margin: -.12em .6em 0 0;
    border-radius: 999px;
    background: linear-gradient(90deg, #a78bfa, #f6bb54);
}
.mon-marca { display: inline-flex; align-items: center; gap: clamp(10px, 1vw, 18px); }
/* El logo oficial de Grupo Heisecke (03/11/2026): con texto blanco en el tema
   oscuro y a color en el claro, en lugar de oscurecer el blanco con un filtro. */
.mon-marca img { display: block; width: auto; height: clamp(18px, 1.6vw, 30px); opacity: .8; }
.mon-marca-claro { display: none; }
[data-theme="light"] .mon-marca-oscuro { display: none; }
[data-theme="light"] .mon-marca-claro { display: block; }

/* ── La barra de luz como indicador de carga ────────────────────────────────
   `.fas-spinner` (la espera del OCR y la hora del Inicio) es ahora la barra
   del login. Se definió en su lugar, «CARGA — UN SOLO CONTRATO», y no acá:
   la regla es una sola definición por indicador. */

/* ══════════════════════════════════════════════════════════════════════════
   PLATAFORMA · 09/10/2026
   ══════════════════════════════════════════════════════════════════════════
   FAS es la plataforma del grupo: un núcleo y módulos, una instalación por
   sociedad. Desde cualquier pantalla se ve en qué sociedad y en qué módulo se
   está, y se salta a otro módulo (templates/_modulos.html y
   _menu_cabecera.html; el catálogo, en app/utils/modulos.py). */

/* ── La cabecera del menú: FAS y el módulo ──────────────────────────────── */
.sidebar-logo-wrap.sidebar-cabecera { justify-content: flex-start; gap: 11px; padding: 14px 16px 12px; }
.sidebar-cabecera .sidebar-logo { width: 36px; height: 36px; border-radius: 9px; flex-shrink: 0; }
.sidebar-cabecera-txt { display: flex; flex-direction: column; min-width: 0; line-height: 1.1; }
.sidebar-cabecera-txt strong { color: #fff; font-size: 15px; font-weight: 800; letter-spacing: .08em; }
.sidebar-cabecera-txt small {
    margin-top: 3px; color: rgba(255, 255, 255, .64); font-size: 11.5px; font-weight: 600;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
body.sidebar-collapsed .sidebar-logo-wrap.sidebar-cabecera { justify-content: center; padding: 10px 8px; }
body.sidebar-collapsed .sidebar-cabecera-txt { display: none; }

/* ── La sociedad, en la barra de arriba ─────────────────────────────────── */
.tb-sociedad {
    font-size: 10.5px; font-weight: 700; letter-spacing: .08em; white-space: nowrap;
    color: rgba(255, 255, 255, .8); border: 1px solid rgba(255, 255, 255, .2);
    border-radius: 999px; padding: 3px 10px; margin-right: 2px;
}
@media (max-width: 820px) { .tb-sociedad { display: none; } }

/* ── El selector de módulos ─────────────────────────────────────────────── */
.fas-mod { position: relative; }
.fas-mod-btn[aria-expanded="true"] { background: rgba(255, 255, 255, .14); color: #fff; }
.fas-mod-panel {
    position: absolute; right: -6px; top: calc(100% + 10px); z-index: 600;
    width: min(344px, calc(100vw - 24px)); padding: 8px;
    background: var(--bg2); border: 1px solid var(--border); border-radius: 14px;
    box-shadow: var(--elev-3), var(--luz-alta);
    animation: fas-fadeInUp 140ms var(--ease-smooth, ease-out) both;
}
/* La cabecera del panel lleva el violeta del acceso y la barra de luz: es la
   plataforma, no un módulo. */
.fas-mod-cab {
    position: relative; display: flex; flex-direction: column; gap: 3px;
    margin-bottom: 6px; padding: 12px 14px 16px; border-radius: 10px; overflow: hidden;
    background: linear-gradient(135deg, #1d1238 0%, #2c1a58 100%); color: #fff;
}
.fas-mod-cab::after {
    content: ''; position: absolute; left: 14px; bottom: 9px; width: 44px; height: 3px; border-radius: 3px;
    background: linear-gradient(90deg, #a78bfa, #f6bb54);
}
.fas-mod-sociedad { font-size: 13px; font-weight: 800; letter-spacing: .06em; }
.fas-mod-grupo { font-size: 11px; color: rgba(255, 255, 255, .72); }
.fas-mod-lista { display: flex; flex-direction: column; gap: 2px; }
.fas-mod-item {
    --mod-c: var(--accent-texto); --mod-bg: var(--active-bg);
    display: flex; align-items: center; gap: 12px; padding: 9px 10px; border-radius: 10px;
    color: var(--text); text-decoration: none; transition: background var(--t-fast);
}
.fas-mod-item:hover, .fas-mod-item:focus-visible { background: var(--hover-bg); }
.fas-mod-item.activo { background: var(--active-bg); }
.fas-mod-item.activo::after {
    content: ''; margin-left: auto; width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0; background: var(--gold);
}
.fas-mod-item--deposito { --mod-c: var(--tag-warn-c); --mod-bg: var(--tag-warn-bg); }
.fas-mod-item--monitor  { --mod-c: var(--tag-ok-c);   --mod-bg: var(--tag-ok-bg); }
.fas-mod-item--admin    { --mod-c: var(--tag-blue-c); --mod-bg: var(--tag-blue-bg); }
.fas-mod-ico {
    width: 36px; height: 36px; border-radius: 10px; flex-shrink: 0;
    display: flex; align-items: center; justify-content: center;
    color: var(--mod-c); background: var(--mod-bg);
}
.fas-mod-ico svg { width: 18px; height: 18px; }
.fas-mod-txt { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.fas-mod-txt strong { font-size: 13.5px; font-weight: 700; color: var(--text); }
.fas-mod-txt small { font-size: 11.5px; color: var(--text2); }
@media (prefers-reduced-motion: reduce) { .fas-mod-panel { animation: none; } }
/* En un teléfono angosto la barra de Admin ya va llena (búsqueda, avisos,
   ayuda y perfil): con el selector se pasaba 5 px a 320 (axe, 09/10/2026).
   Ahí se navega con la barra de abajo. */
@media (max-width: 380px) { .fas-mod { display: none; } }
/* Zonas de carga de PDF con teclado: el foco se ve (QA 12/10/2026). */
#uploadZone:focus-visible, #masivoZone:focus-visible { outline: 2px solid var(--gold); outline-offset: 3px; }
/* La alternativa: los mismos módulos al principio del menú lateral, que en el
   teléfono se abre con el botón de arriba (12/10/2026). */
.sidebar-mods { display: none; }
@media (max-width: 380px) {
    .sidebar-mods { display: flex; flex-wrap: wrap; gap: 6px; padding: 4px 16px 12px; }
    .sidebar-mods-item {
        font-size: 12px; font-weight: 600; color: rgba(255, 255, 255, .82); text-decoration: none;
        border: 1px solid rgba(255, 255, 255, .22); border-radius: 999px; padding: 8px 12px; min-height: 44px;
        display: inline-flex; align-items: center;
    }
    .sidebar-mods-item.activo { background: rgba(255, 255, 255, .14); color: #fff; border-color: rgba(255, 255, 255, .4); }
    .sidebar-mods-item:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
}

/* ── La portada de un módulo (templates/_portada.html) ──────────────────────
   El Inicio de cada módulo abre con la identidad del acceso: el violeta, la
   barra de luz y la marca de FAS de fondo. Es oscura en los dos temas —como
   el menú—, y el contenido de abajo sigue con fondo sólido. */
.portada {
    position: relative; overflow: hidden; isolation: isolate;
    display: flex; align-items: flex-end; justify-content: space-between; gap: 18px; flex-wrap: wrap;
    margin: 0 0 18px; padding: 20px 26px 22px; border-radius: 14px;
    color: #fff;
    background:
        radial-gradient(60% 120% at 92% 0%, rgba(246, 187, 84, .16), transparent 60%),
        radial-gradient(70% 140% at 0% 100%, rgba(167, 139, 250, .22), transparent 62%),
        linear-gradient(120deg, #170e2e 0%, #24164a 55%, #301d61 100%);
    box-shadow: var(--elev-2);
}
.portada-txt { position: relative; z-index: 1; min-width: 0; }
.portada-saludo { min-height: 1.3em; font-size: 12.5px; font-weight: 600; color: rgba(255, 255, 255, .72); margin-bottom: 4px; }
.portada .page-title { color: #fff; font-size: 24px; }
.portada .page-subtitle { color: rgba(255, 255, 255, .74); font-size: 12.5px; margin-top: 3px; }
.portada-luz { width: 64px; height: 3px; border-radius: 3px; margin-top: 12px;
               background: linear-gradient(90deg, #a78bfa, #f6bb54); }
.portada-lado { position: relative; z-index: 1; display: flex; flex-direction: column; align-items: flex-end; gap: 3px; }
.portada-modulo { font-size: 10.5px; font-weight: 700; letter-spacing: .1em; text-transform: uppercase; color: #f6bb54; }
.portada-fecha { font-size: 13px; font-weight: 600; color: rgba(255, 255, 255, .86); }
/* La marca de FAS, grande y apenas visible, del lado derecho. */
.portada-marca {
    position: absolute; z-index: 0; right: -18px; bottom: -42px; width: 190px; height: 190px;
    color: rgba(255, 255, 255, .05); pointer-events: none;
}
@media (max-width: 640px) {
    .portada { padding: 16px 18px 18px; border-radius: 12px; }
    .portada .page-title { font-size: 20px; }
    .portada-lado { align-items: flex-start; }
    .portada-marca { width: 130px; height: 130px; bottom: -30px; }
}

/* ── El catálogo de componentes (UX-19, 20/10/2026, templates/componentes.html) ──
   Solo el armado de la página: los componentes que muestra son los de siempre. */
.cat-cuerpo { background: var(--bg); color: var(--text); min-height: 100vh; margin: 0; }
.cat { max-width: 1100px; margin: 0 auto; padding: 24px 20px 48px; display: flex; flex-direction: column; gap: 22px; }
.cat-cabecera { display: flex; justify-content: space-between; align-items: flex-end; gap: 16px; flex-wrap: wrap; }
.cat-temas { display: flex; gap: 8px; }
.cat-seccion { display: flex; flex-direction: column; gap: 10px; }
.cat-titulo { font-size: 13px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--accent-texto); margin: 0; }
.cat-fila { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; }
.cat-nota { font-size: 12.5px; color: var(--text3); margin: 0; }
.cat-form { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 12px; }
.cat-tag { display: inline-block; padding: 3px 10px; border-radius: 20px; font-size: 11.5px; font-weight: 600; white-space: nowrap; }
.cat-tag--ok    { background: var(--tag-ok-bg);    color: var(--tag-ok-c); }
.cat-tag--warn  { background: var(--tag-warn-bg);  color: var(--tag-warn-c); }
.cat-tag--err   { background: var(--tag-err-bg);   color: var(--tag-err-c); }
.cat-tag--blue  { background: var(--tag-blue-bg);  color: var(--tag-blue-c); }
.cat-tag--login { background: var(--tag-login-bg); color: var(--tag-login-c); }

/* ── Indicadores del Inicio: componente común (23/10/2026) ──────────────────
   Nació en Depósito (de ahí el «ent-») y desde esta fecha lo usa también
   Facturación: Mauri pidió sus indicadores «así como Depósito». Un solo
   componente, no una copia por pantalla. Se pasó sin cambiar ninguna regla. */
:root { --tab-barra: #9573d6; --tab-fondo: rgba(255,255,255,.06); }
[data-theme="light"] { --tab-barra: #6d45b0; --tab-fondo: #eceaf5; }
.ent-ind { margin-bottom:26px; }
.ent-ind-cab { display:flex; align-items:baseline; justify-content:space-between; gap:12px; margin:0 0 10px; }
.ent-ind-cab h2 { margin:0; font-size:13px; font-weight:800; letter-spacing:.06em; text-transform:uppercase; color:var(--text2); }
/* La barra de luz del login, quieta, delante de cada sección (09/10/2026).
   En línea y no con flex: el mes del título va en un <span> y con flex se
   separaba del resto en el celular. */
.ent-ind-cab h2::before { content:''; display:inline-block; vertical-align:middle; width:18px; height:3px;
                          border-radius:3px; margin:-2px 9px 0 0;
                          background:linear-gradient(90deg, #a78bfa, #f6bb54); }
.ent-ind-hora { font-size:12px; color:var(--text3); }
.ent-ind-grid { display:grid; grid-template-columns:repeat(auto-fit, minmax(210px, 1fr)); gap:14px; margin-bottom:14px; }
.ent-ind-tile { display:flex; flex-direction:column; gap:4px; text-align:left; font:inherit; color:inherit;
                background:var(--bg2); border:1px solid var(--border); border-left:4px solid transparent;
                border-radius:var(--radius); padding:14px 16px; cursor:pointer;
                transition:border-color var(--t-fast), transform var(--t-fast), box-shadow var(--t-fast); }
.ent-ind-tile:hover { transform:translateY(-2px); box-shadow:0 6px 20px rgba(75,44,128,.15); }
.ent-ind-tile--fijo { cursor:default; }
.ent-ind-tile--fijo:hover { transform:none; box-shadow:none; }
/* Color solo si hay algo que mirar (28/10/2026): la raya y el número se
   pintan cuando el valor pasa de 0 (lo pone el JS). Sin señal, la tarjeta
   no lleva raya de color: antes todas tenían la violeta y Vencidos iba en
   rojo aunque diera 0. */
.ent-ind-tile--crit { border-left-color:var(--est-err); }
.ent-ind-tile--crit .ent-ind-valor { color:var(--tag-err-c); }
.ent-ind-tile--warn { border-left-color:var(--est-warn); }
.ent-ind-tile--warn .ent-ind-valor { color:var(--tag-warn-c); }
.ent-ind-label { font-size:11.5px; font-weight:700; letter-spacing:.05em; text-transform:uppercase; color:var(--text2); }
.ent-ind-valor { font-size:30px; font-weight:800; line-height:1.1; color:var(--text); font-variant-numeric:tabular-nums; }
.ent-ind-sub { font-size:12px; color:var(--text3); }
/* Mientras llegan los datos: el número pulsa y la hora muestra la barra de luz. */
.ent-ind-hora { display:inline-flex; align-items:center; gap:8px; }
.ent-ind-spin { width:36px; height:3px; }
.ent-ind--cargando .ent-ind-valor { color:transparent; min-width:72px; border-radius:6px;
                                    background:var(--bg4); animation:ent-ind-pulso 1.2s ease-in-out infinite; }
@keyframes ent-ind-pulso { 0%, 100% { opacity:.45; } 50% { opacity:1; } }
@media (prefers-reduced-motion: reduce) { .ent-ind--cargando .ent-ind-valor { animation:none; } }
.ent-tab-grid { display:grid; grid-template-columns:1fr 1fr; gap:14px; margin-bottom:14px; }
.ent-tab-grid--par { grid-template-columns:1fr 1fr; }
@media (max-width: 900px) { .ent-tab-grid, .ent-tab-grid--par { grid-template-columns:1fr; } }
.ent-tab-grid .card { margin-bottom:0; }
.ent-tab-total { font-size:12px; color:var(--text3); }
.ent-tab-cuerpo { padding:14px 18px 16px; position:relative; }
.ent-tab-svg { display:block; width:100%; height:170px; }
.ent-tab-barra { fill:var(--tab-barra); }
.ent-tab-barra-hit { fill:transparent; }
.ent-tab-eje { stroke:var(--border); stroke-width:1; }
.ent-tab-eje-txt { fill:var(--text3); font-size:11px; }
.ent-tab-valor { fill:var(--text); font-size:11px; font-weight:700; }
.ent-tab-vacio { font-size:12.5px; color:var(--text3); padding:6px 0; }
.ent-tab-ok { color:var(--tag-ok-c); font-weight:600; }
.ent-tab-lista { list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:6px; }
.ent-tab-item { display:flex; align-items:center; gap:10px; padding:8px 10px; border:1px solid var(--border);
                border-radius:8px; cursor:pointer; background:none; color:inherit; font:inherit; text-align:left; width:100%; }
.ent-tab-item:hover { border-color:var(--accent-lt); }
.ent-tab-item-txt { flex:1; min-width:0; font-size:12.5px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.ent-tab-item-accion { font-size:11.5px; color:var(--text3); white-space:nowrap; }
.ent-tab-item-sub { color:var(--text3); }
.ent-tab-prov { display:grid; grid-template-columns:18px minmax(0,1.3fr) minmax(0,1fr) auto; align-items:center; gap:10px;
                font-size:12.5px; padding:6px 0; border-bottom:1px solid var(--border); }
.ent-tab-prov:last-child { border-bottom:none; }
.ent-tab-prov-pos { color:var(--text3); font-weight:700; text-align:right; }
.ent-tab-prov-nom { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.ent-tab-prov-svg { display:block; width:100%; height:10px; }
.ent-tab-prov-fondo { fill:var(--tab-fondo); }
.ent-tab-prov-n { font-weight:700; min-width:22px; text-align:right; font-variant-numeric:tabular-nums; }

/* El interruptor de tema es un botón: el mínimo de 44 px para el dedo lo volvía
   un cuadrado (celular real, 05/11/2026). Sigue midiendo 20 de alto y el área de
   toque la da `::before`. */
button.toggle-switch { min-height: 20px; }
@media (pointer: coarse) {
    .toggle-switch::before { content: ''; position: absolute; inset: -12px -3px; }
}

/* ══ CELULAR · revisión del 03/11/2026 ══════════════════════════════════════
   Los 12 puntos medidos a 320, 360 y 390 px. Solo pantallas chicas: la
   computadora no cambia. Lo mide `tests/test_celular_revision.py`. */
@media (max-width: 768px) {
    /* La cabecera va pegada arriba desde el 24/07. La sombra del 14/08 la dejó en
       `relative`: al bajar se iba, y la hamburguesa, que es fija, quedaba sola
       sobre el contenido. */
    .topbar { position: sticky; top: 0; }
    /* Con el menú abierto la X va afuera: adentro tapa la marca. */
    .mobile-menu-toggle.is-active { left: min(280px, 85vw); }

    /* Ingreso. La tarjeta es la referencia del tacho: sin esto los tres quedaban
       apilados debajo del avatar, y el toque lo recibía el avatar. */
    #nGrid tr[data-idx], #nGrid tr.ent-cerrada { position: relative; }
    /* Con la fila en el selector: `#nGrid tr[data-idx] td` deja el padding en 0, y
       el tacho tapaba «Código». */
    #nGrid tr[data-idx] td.g-cod { padding-right: 46px; }
    /* Encima de la celda del código: si no, el toque lo recibía ella. */
    #nGrid td.g-quitar { z-index: 2; }
    .ent-quitar { min-width: 44px; min-height: 44px; }
    /* Línea y Pendiente van en renglón: la regla de los campos los apilaba. */
    #nGrid tr[data-idx] td.g-num { flex-direction: row; gap: 6px; }
    #nGrid tr[data-idx] td.g-ped {
        flex-direction: row; flex-wrap: wrap; align-items: baseline; justify-content: space-between;
    }
    #nGrid tr[data-idx] td.g-ped .ent-saldo { flex: 0 0 100%; text-align: right; }

    /* Las tarjetas de lista suben al proveedor y dejan la última celda al pie.
       Acá son campos: «Lote Proveedor» y «Reg. sanitario» perdían su rótulo. */
    #nGrid tr td[data-label*="rovee" i] {
        order: 0; padding-top: 0; font-size: inherit; font-weight: inherit; line-height: inherit;
    }
    #nGrid tr td[data-label*="rovee" i]::before,
    #nGrid tr td:last-child::before { display: block; }
    #nGrid tr td:last-child {
        margin-top: 0; padding-top: 0; border-top: 0; text-align: left; justify-content: flex-start;
    }

    /* Rótulos: lo mismo con el vencimiento, el botón y las casillas. */
    #rotBultos td:last-child::before { display: block; }
    #rotBultos td:last-child { margin-top: 0; padding-top: 6px; border-top: 0; text-align: left; }
    #rotBultosModal .rot-modal > div:first-child .rot-btn { flex: 0 0 auto; }
    #rotBultos input[type="checkbox"], .rot-chk { width: 28px; height: 28px; }

    /* Bandeja y Mis facturas: a 390-430 px el buscador quedaba en 52 px. */
    .search-input-wrap { flex: 1 1 100%; max-width: none; }

    /* Inicio: dos tarjetas por renglón. Una debajo de otra medía 4.400 px. */
    .ent-ind-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
    .ent-ind-valor { font-size: 24px; }
    .ent-ind-cab { flex-wrap: wrap; }
    .ent-ind-cab h2 { flex: 1 1 100%; }

    /* Proveedores y productos: el nombre entero, con su barra abajo. */
    .ent-tab-prov { grid-template-columns: 18px minmax(0, 1fr) auto; row-gap: 4px; }
    .ent-tab-prov-nom { grid-column: 2; }
    .ent-tab-prov-n { grid-column: 3; grid-row: 1; }
    .ent-tab-prov-svg { grid-column: 2 / -1; grid-row: 2; }
    .ent-tab-prov-nom, .ent-tab-prov-nom .ent-prod-nom {
        overflow: visible; white-space: normal; overflow-wrap: anywhere;
    }

    /* Generar ticket: el botón, en su propio renglón. */
    .perfil-modal .cm-row { flex-wrap: wrap; }
    .perfil-modal .cm-row .btn { flex: 1 1 100%; justify-content: center; }

    /* El panel de módulos se salía 8 px por la izquierda. */
    .fas-mod-panel { position: fixed; top: 58px; left: 12px; right: 12px; width: auto; }

    /* Historial: sin renglones vacíos en las tarjetas. */
    #entTabHistorial td.celda-vacia { display: none; }

    /* Trazabilidad: todos los datos con el rótulo arriba, de a dos. Unos lo
       llevaban al costado y otros arriba, y quedaban desalineados. */
    #tFilas td[data-label]:not(.tz-desc):not(.tz-codigo):not(.tz-estado) { display: block; flex: 1 1 42%; }
    #tFilas td[data-label]:not(.tz-desc):not(.tz-codigo):not(.tz-estado)::before {
        display: block; margin-bottom: 1px;
    }
    #tFilas td.tz-desc { padding-right: 104px; }
}
