/* ==========================================================================
   prductize — Design system
   Portado tal cual del de Agrospot (que a su vez viene del de MGA): Linear-style,
   denso, monocromático. Lo ÚNICO que cambia es la marca: donde Agrospot pone el
   teal #13615b de su isotipo, acá va el carbón del wordmark de prductize.

   La regla es la misma y se mantiene: los neutros no se tocan —son los que
   hacen legible una lista densa— y el color de marca entra SÓLO en el botón
   primario, el ítem activo del sidebar, el foco, los links y los badges. Filas,
   texto, iconos y fondos siguen grises. Un token nuevo de marca se agrega acá o
   no se agrega.
   ========================================================================== */

:root {
  /* ── Marca (los dos colores del logo, muestreados del PNG) ────────────────
     El wordmark «prductize» es #181e26 y el anillo punteado es #afafaf. El
     carbón ocupa el lugar que en Agrospot ocupa el teal; el gris del anillo es
     el acento, y es lo único que no es ni neutro ni carbón.

     `--brand` es un punto MÁS CLARO que el wordmark a propósito: el hover tiene
     que poder oscurecer, y con el carbón exacto arriba no le queda a dónde ir.
     Ese carbón exacto es entonces el hover y el `--brand-ink` de los títulos. */
  --brand:        #22272e;   /* primario, ítem activo, foco */
  --brand-hover:  #181e26;   /* su hover: el wordmark exacto */
  --brand-ink:    #181e26;   /* el wordmark: carbón */
  --brand-soft:   #f3f4f5;   /* fondo de badge */
  --brand-line:   #e2e3e5;   /* su borde */
  --brand-anillo: #afafaf;   /* el gris del isotipo, para acentos */

  /* Los dos colores del dinero. Es la ÚNICA excepción a «nada de color fuera de
     la marca», y la paga el dominio: un listado de movimientos donde ingreso y
     egreso se distinguen leyendo el signo obliga a leer cada renglón. Viven
     sólo en el monto y en el punto del tipo — no pintan la fila, que es lo que
     convertiría la lista en un semáforo. */
  --ingreso:      #1a7f52;
  --ingreso-soft: #e9f4ee;
  --egreso:       #a32d2d;
  --egreso-soft:  #fbecec;


  /* Superficies */
  --bg:            #ffffff;   /* contenido principal */
  --bg-sidebar:    #fbfbfa;   /* sidebar, un pelo más cálido */
  --bg-hover:      #f4f4f3;   /* hover de ítems / filas */
  --bg-active:     #ececeb;   /* ítem de sidebar seleccionado */
  --bg-sunken:     #f7f7f6;   /* inputs, badges */

  /* Texto */
  --text:          #08090a;   /* casi negro */
  --text-2:        #62646a;   /* secundario */
  --text-3:        #9a9ca1;   /* terciario / placeholders */
  --text-inv:      #ffffff;

  /* Líneas */
  --border:        #e9e9e7;
  --border-strong: #d6d6d4;

  /* Forma */
  --radius:    6px;
  --radius-lg: 8px;

  /* Sombra (mínima, solo overlays) */
  --shadow-modal: 0 12px 48px rgba(8, 9, 10, .16), 0 0 0 1px var(--border);

  /* Alerta / destructivo. Un solo rojo para toda la app: el de .tag-danger. */
  --danger: #a32d2d;

  --sidebar-w: 240px;
  --font: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;

  /* ── Escala de z-index ─────────────────────────────────────────────────────
     Las capas de la app, declaradas UNA vez y en el orden en que se tapan. Todo
     lo que flota sale de acá: un número suelto en el archivo es un empate
     esperando pasar (la isla y el backdrop del modal eran los dos 50, así que
     ganaba el orden del DOM y la isla quedaba iluminada sobre la penumbra).

     El orden es el de la interacción — lo que abrís último tapa lo anterior:

       fila          (sin z-index: es el flujo normal de la página)
       sticky        lo que se queda quieto mientras la página scrollea por
                     debajo (el panel de propiedades del detalle): tapa al
                     contenido, y nada más
       isla          flota sobre la lista, pero por debajo de todo lo que abras
       popover       los menús anclados a un control (estado, vistas, filtro,
                     acciones del detalle, picker de ícono, datepicker, usuario)
       menú de fila  el popover flotante del kebab y del click derecho: se abre
                     DESDE una fila y tiene que tapar a la isla de la selección
       modal-fondo   la penumbra apaga la pantalla entera: tapa todo lo de arriba
       modal         la caja, sobre su propia penumbra
       toast         se ve incluso con un modal abierto (un rechazo se avisa
                     igual, y el toast no roba el foco)

     Los saltos son de 100 para que quede lugar entre capas sin volver a tocar
     esta escala. Un dropdown que vive DENTRO del modal (p. ej. el TomSelect del
     import) no entra acá: el modal ya es su contexto de apilado, y su z-index
     sólo compite con sus hermanos. */
  --z-sticky:       10;
  --z-isla:        100;
  --z-popover:     200;
  --z-menu-fila:   300;
  --z-modal-fondo: 400;
  --z-modal:       410;
  --z-toast:       500;
}

* { box-sizing: border-box; }

html, body { height: 100%; }

body {
  margin: 0;
  font-family: var(--font);
  font-size: 14px;
  line-height: 1.5;
  color: var(--text);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  font-feature-settings: "cv05" 1, "cv08" 1, "ss01" 1;
}

a { color: inherit; text-decoration: none; }

/* ── App shell (estilo Linear) ──────────────────────────────────────────────
   El sidebar es el FONDO de toda la ventana. El contenido (.main) flota por
   encima como una "hoja" con bordes redondeados y un margen sutil, dejando
   asomar el fondo del sidebar arriba, abajo y a la derecha. */
.app {
  display: grid;
  grid-template-columns: var(--sidebar-w) 1fr;
  height: 100vh;
  overflow: hidden;
  background: var(--bg-sidebar);
}

/* ── Layout limpio (sin sidebar): consent OAuth, etc. ────────────────────── */
.blank { background: var(--bg-sunken); }
.blank-shell {
  min-height: 100vh;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 20px; padding: 24px;
}
.blank-brand { display: flex; align-items: center; gap: 9px; }
.blank-brand .ws-logo {
  width: 26px; height: 26px; border-radius: 6px;
  background: var(--text); color: var(--text-inv);
  display: flex; align-items: center; justify-content: center;
  font-weight: 600; font-size: 14px;
}
.blank-brand .ws-name { font-weight: 600; font-size: 15px; }

.card {
  width: 100%; max-width: 420px;
  background: var(--bg); border: 1px solid var(--border);
  border-radius: var(--radius-lg); padding: 24px;
  box-shadow: 0 1px 2px rgba(8, 9, 10, .04);
}
.card .card-title { font-size: 17px; font-weight: 600; margin: 0 0 12px; }
.card p { margin: 0 0 8px; }
.card .muted { color: var(--text-2); font-size: 13px; }
.card code {
  background: var(--bg-sunken); border: 1px solid var(--border);
  border-radius: 4px; padding: 1px 5px; font-size: 12.5px;
}

/* ── El círculo de una persona (ADR-0011) ──────────────────────────────────
   UNA sola clase para toda la app: donde haya una persona dibujada sale de acá,
   siempre vía el partial `templates/_avatar.html`. La usan el sidebar
   (MGA-185), los tres paneles de detalle y las filas de cesiones y movimientos
   (MGA-187) y las filas de lista de Pedidos y Órdenes (MGA-188, la celda
   `.m-autor` de más abajo): TODAS tal cual, sin una segunda variante ni un
   modificador de tamaño. Están las cinco superficies que ADR-0011 enumera, así
   que no falta ninguna — la lista de Acopios no lleva círculo a propósito.

   Monocromo, sin color por persona. No es una limitación del design system que
   haya que disculpar: se verificó en el DOM de Linear que sus dos avatares de
   iniciales, de personas distintas, comparten el mismo fondo gris. Copiar al
   referente y respetar la línea 1 de este archivo son lo mismo.

   20px es el tamaño que se probó contra las filas reales antes de decidirlo. El
   `inline-flex` (y no `flex`) es para que entre en un renglón de texto sin
   romperlo: en el panel el círculo acompaña al nombre.

   Va ACÁ —antes del Sidebar, con el resto del chrome— y no adentro de ninguna
   sección de pantalla, porque no es de ninguna: este archivo ordena primero lo
   que vale para toda la app y después las pantallas (Perfil, Detalle, Modal,
   Importar). El círculo es un átomo compartido, así que si vive partiendo la
   sección del Sidebar, el próximo que agregue algo al menú de usuario lo escribe
   del otro lado y la sección se parte de nuevo. */
/* El tamaño salió de mirarlo, no de calcularlo: la primera versión (20px, letras
   de 9.5px) se leía barata —el disco quedaba chico y las dos iniciales tocaban
   el borde—, así que el círculo subió a 24px y la letra a 10.5px. Que las dos
   cosas crecieran juntas es la decisión: agrandar SÓLO el círculo deja más
   anillo vacío y el mismo dibujo adentro, y lo que faltaba no era vacío sino
   presencia. El tracking acompaña (.35px) porque dos mayúsculas juntas en Inter
   se empastan al crecer.

   24px es el techo, y lo fija la fila: `.lin-row` mide 44px de alto y la columna
   del autor en las grillas de meta mide 32px (subió de 28 con este cambio), así
   que el círculo entra con 4px de aire de cada lado. Un tamaño más grande no es
   un ajuste de esta regla, es rediseñar la fila. */
.avatar-p {
  width: 24px; height: 24px; border-radius: 50%;
  background: var(--bg-sunken); border: 1px solid var(--border);
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 10.5px; font-weight: 600; color: var(--text-2);
  letter-spacing: .35px; flex-shrink: 0; user-select: none;
}

/* ── Sidebar ─────────────────────────────────────────────────────────────── */
.sidebar {
  background: var(--bg-sidebar);
  display: flex;
  flex-direction: column;
  padding: 0 8px 8px;
  overflow-y: auto;
  user-select: none;
}

/* Header del sidebar: vive sobre el fondo, sin línea (la hoja flotante ya
   marca la separación). Mismo alto que antes para que respire igual. */
.workspace {
  display: flex;
  align-items: center;
  gap: 8px;
  height: 48px;
  flex-shrink: 0;
  margin: 0 -2px;
  padding: 0 8px;
  border-radius: var(--radius);
  cursor: pointer;
}
.workspace:hover { background: var(--bg-hover); }
/* 24px, como TODOS los marcadores de identidad de la app (ADR-0011). El sidebar
   tenía tres —workspace 20, cliente 16, persona 24— y ninguno por una razón. */
.workspace .ws-logo {
  width: 24px; height: 24px; border-radius: 6px;
  background: var(--brand); color: var(--text-inv);
  font-weight: 700; font-size: 13px; letter-spacing: -.02em;
  display: flex; align-items: center; justify-content: center;
  flex-shrink: 0; overflow: hidden;
}
.workspace .ws-logo img { width: 100%; height: 100%; object-fit: cover; display: block; }
.workspace .ws-name { font-weight: 600; font-size: 13.5px; color: var(--brand-ink); }
.workspace .ws-caret { margin-left: auto; color: var(--text-3); display: flex; }

.nav { margin-top: 4px; display: flex; flex-direction: column; gap: 1px; }

.nav-section {
  font-size: 11px;
  font-weight: 500;
  color: var(--text-3);
  padding: 14px 8px 4px;
  letter-spacing: .01em;
}

.nav-item {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 6px 8px;
  border-radius: var(--radius);
  font-size: 13.5px;
  color: var(--text-2);
  cursor: pointer;
  white-space: nowrap;
}
.nav-item:hover { background: var(--bg-hover); color: var(--text); }
.nav-item.active { background: var(--brand-soft); color: var(--brand); font-weight: 500; }
.nav-item.is-stub { cursor: default; }
.nav-item .icon { width: 16px; height: 16px; flex-shrink: 0; display: flex; align-items: center; justify-content: center; }
.nav-item .icon svg { width: 16px; height: 16px; stroke: currentColor; }
.nav-item .count {
  margin-left: auto; font-size: 11px; color: var(--text-2);
  background: var(--bg-sunken); border: 1px solid var(--border);
  border-radius: 10px; padding: 0 7px; line-height: 16px;
}
/* La píldora de «lo que todavía no está». Era una regla anidada bajo `.nav-item`
   —el sidebar era su único usuario— y pasó a ser genérica cuando la barra de
   etapas del negocio la necesitó: adentro de una pestaña heredaba nada y salía
   como texto pelado, que es por qué la barra no entraba en una línea. */
.badge-soon {
  font-size: 10px; font-weight: 500; letter-spacing: .02em;
  color: var(--text-3); background: var(--bg-sunken); border: 1px solid var(--border);
  border-radius: 10px; padding: 0 7px; line-height: 15px; text-transform: none;
}
.nav-item .badge-soon { margin-left: auto; }

/* Cliente (team) colapsable con <details>/<summary> */
.client { }
.client > summary { list-style: none; }
.client > summary::-webkit-details-marker { display: none; }
.client > summary .icon { color: var(--text-3); }
.client > summary .caret { transition: transform .12s; }
.client[open] > summary .caret { transform: rotate(90deg); }
/* La sigla del cliente: 24px, el mismo tamaño que el círculo de la persona y que
   el logo del workspace. El nombre `-sm` quedó de cuando era el chico de los
   tres; hoy no hay grande ni chico, hay UN tamaño.

   Lo que sí cambia con el tamaño es la fila. La altura de un `.nav-item` sale de
   su contenido más 6px de padding arriba y abajo: con texto de 13.5px la línea
   mide ~20px y la fila 32px, que es el ritmo de TODO el nav. Un marcador de 24px
   la empujaría a 36 y la lista de Clientes dejaría de leerse pareja con la de
   Workspace justo arriba — así que esta fila lleva 4px de padding y vuelve a
   medir 32 exacto. El padding compensa al marcador; no es una excepción de
   estilo, es la misma altura sostenida con otro número. */
.client .ws-logo-sm {
  width: 24px; height: 24px; border-radius: 6px;
  background: var(--bg-sunken); border: 1px solid var(--border);
  display: flex; align-items: center; justify-content: center;
  font-size: 10.5px; font-weight: 600; color: var(--text-2);
  letter-spacing: .35px; flex-shrink: 0;
}
.client > summary.nav-item { padding-top: 4px; padding-bottom: 4px; }
.client-children { padding-left: 25px; display: flex; flex-direction: column; gap: 1px; }
.client-children .nav-item { font-size: 13px; color: var(--text-3); }
.client-children a.nav-item:hover { color: var(--text); }

.link { color: var(--text-2); text-decoration: underline; }
.link:hover { color: var(--text); }

.sidebar-footer { margin-top: auto; padding-top: 8px; }

/* Menú de usuario: el nombre es un <summary> clickeable; al abrirlo despliega
   un popover hacia arriba con el email, "Perfil" y "Cerrar sesión". */
.user-menu { position: relative; }
.user-row {
  display: flex; align-items: center; gap: 8px;
  padding: 7px 8px; border-radius: var(--radius);
  font-size: 12.5px; color: var(--text-2); cursor: pointer;
  list-style: none;
}
.user-row::-webkit-details-marker { display: none; }
.user-row:hover { background: var(--bg-hover); }
.user-menu[open] > .user-row { background: var(--bg-hover); }
/* El `.user-row .avatar` de una letra que vivía acá se fue: era el mismo círculo
   dibujado aparte, y ahora es `.avatar-p` como el de todos los demás. */
.user-row .nombre { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.user-row .user-caret { margin-left: auto; color: var(--text-3); display: flex; flex-shrink: 0; }

.user-pop {
  position: absolute; bottom: calc(100% + 6px); left: 0; right: 0; z-index: var(--z-popover);
  padding: 4px;
  background: var(--bg); border: 1px solid var(--border);
  border-radius: var(--radius-lg); box-shadow: var(--shadow-modal);
}
/* El email, ahora que la fila muestra el nombre. Es un dato, no una acción: no
   tiene hover ni cursor, y la línea lo separa de los ítems que sí lo son
   ("Perfil" y "Cerrar sesión"). */
.user-pop-email {
  padding: 6px 8px 8px; margin-bottom: 4px;
  border-bottom: 1px solid var(--border);
  font-size: 12px; color: var(--text-3);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.user-pop-item {
  display: flex; align-items: center; gap: 9px;
  padding: 7px 8px; border-radius: var(--radius);
  font-size: 13px; color: var(--text-2); cursor: pointer; white-space: nowrap;
}
.user-pop-item:hover { background: var(--bg-hover); color: var(--text); }
.user-pop-item svg { width: 16px; height: 16px; stroke: currentColor; flex-shrink: 0; }

/* ── Main: la "hoja" flotante (estilo Linear) ───────────────────────────────
   Tarjeta blanca por encima del fondo del sidebar. El margen deja asomar el
   fondo arriba/abajo/derecha; el borde redondeado + sombra fina la despegan. */
.main {
  display: flex; flex-direction: column; overflow: hidden;
  background: var(--bg);
  margin: 8px 8px 8px 0;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: 0 1px 3px rgba(8, 9, 10, .05);
}

.content { flex: 1; overflow-y: auto; padding: 22px 26px; }
.content-inner { max-width: none; margin: 0; }

/* Cabecera de cada pantalla: título a la izquierda, acciones a la derecha.
   (El comentario que vivía acá decía que Linear no tiene migas de pan y que por
   eso la app tampoco. Es falso —Linear tiene `MGA › Issues`— y el ADR-0004 §6 lo
   derogó: las migas volvieron, ver .migas.) */
.page-head {
  display: flex; align-items: flex-start; gap: 16px;
  margin-bottom: 18px;
}
.page-head h1 { font-size: 18px; font-weight: 600; margin: 0 0 2px; }
/* El subtítulo, en cualquier lado. Era una regla anidada bajo `.page-head` y
   `.section-head`, o sea que un `.sub` fuera de esos dos contenedores no estaba
   estilado: heredaba el color del body y su tamaño, y salía como un párrafo
   negro de 14px que se lee igual que el contenido. Pasaba en las cuatro
   pestañas de Configuración, que usan `.sec-head`.

   Sólo color y tamaño acá: el margen lo sigue poniendo cada contexto, porque no
   es el mismo abajo de un `<h1>` que adentro de un modal. */
.sub { color: var(--text-2); font-size: 13px; }
.page-head .sub { margin: 0; }
.page-head .head-actions { margin-left: auto; flex-shrink: 0; display: flex; gap: 8px; }

/* ── Toolbar ─────────────────────────────────────────────────────────────── */
.toolbar { display: flex; gap: 8px; align-items: center; margin-bottom: 12px; }
.toolbar .grow { flex: 1; }

.search {
  display: flex; align-items: center; gap: 8px;
  border: 1px solid var(--border); border-radius: var(--radius);
  padding: 0 10px; background: var(--bg);
  height: 32px; color: var(--text-3);
}
.search:focus-within { border-color: var(--brand); box-shadow: 0 0 0 3px rgba(24, 30, 38,.08); }
.search svg { width: 15px; height: 15px; stroke: currentColor; flex-shrink: 0; }
.search input {
  border: none; outline: none; background: transparent;
  font: inherit; font-size: 13.5px; color: var(--text);
  width: 100%; padding: 0;
}
.search input::placeholder { color: var(--text-3); }

/* ── Inputs / botones ────────────────────────────────────────────────────── */
input, select, button { font: inherit; }

/* El atributo [hidden] debe ganarle al display de componentes (.btn usa
   inline-flex, que si no lo pisa y el elemento no se oculta al setear .hidden). */
[hidden] { display: none !important; }

.btn {
  display: inline-flex; align-items: center; gap: 6px;
  height: 32px; padding: 0 11px;
  border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--bg); color: var(--text);
  font-size: 13px; font-weight: 500; cursor: pointer;
  white-space: nowrap; transition: background .1s, border-color .1s;
}
.btn:hover { background: var(--bg-hover); }
.btn svg { width: 15px; height: 15px; stroke: currentColor; }

.btn-primary { background: var(--brand); color: var(--text-inv); border-color: var(--brand); }
.btn-primary:hover { background: var(--brand-hover); border-color: var(--brand-hover); }

.btn-ghost { border-color: transparent; background: transparent; color: var(--text-2); height: 28px; padding: 0 8px; }
.btn-ghost:hover { background: var(--bg-hover); color: var(--text); }

.btn-danger { border-color: transparent; background: transparent; color: var(--text-2); height: 28px; padding: 0 8px; }
.btn-danger:hover { background: var(--bg-hover); color: var(--text); }

.btn-sm { height: 28px; font-size: 12.5px; }

/* ── Lista (estilo Linear: plana, full-width) ────────────────────────────── */
.table-wrap { border: none; border-radius: 0; overflow: visible; }
table { width: 100%; border-collapse: collapse; }

/* Header de columnas: transparente, solo una línea fina abajo. */
thead th {
  text-align: left; font-size: 12px; font-weight: 400;
  color: var(--text-3); letter-spacing: 0;
  padding: 8px 12px; background: transparent;
  border-bottom: 1px solid var(--border);
}
thead th.col-meta { text-align: right; }
/* La celda va con su encabezado: la regla del `<th>` estaba sola porque hasta
   Configuración ninguna tabla tenía una columna de meta con contenido. */
tbody td.col-meta { text-align: right; }

/* Filas altas, separador sutil, toda la fila clickeable. */
tbody td {
  padding: 0 12px; height: 44px;
  border-bottom: 1px solid var(--border);
  font-size: 13.5px; vertical-align: middle;
}
tbody tr { transition: background .08s; cursor: pointer; }
tbody tr:hover { background: var(--bg-hover); }
tbody tr.is-empty { cursor: default; }
tbody tr.is-empty:hover { background: transparent; }

/* El checkbox y el kebab no son columnas de datos: existen sólo para colgar los
   dos controles que se revelan en hover (ADR-0004 §2, «sin una columna
   permanente»). Sin un ancho fijo, el layout automático de la tabla les da el
   ancho de una columna de verdad y corre el contenido ~40px a la derecha —
   justo lo que la regla venía a evitar.
   Los números salen de .lin-row, para que una tabla de catálogo y una lista de
   documentos arranquen en la MISMA vertical: 8px de inset, checkbox de 14px,
   10px hasta el contenido. Por eso la celda que sigue al checkbox pierde su
   padding izquierdo: ya lo puso la del checkbox. */
thead th.col-check, tbody td.col-check { width: 14px; padding: 0 10px 0 8px; }
thead th.col-check + th, tbody td.col-check + td { padding-left: 0; }
thead th.col-kebab, tbody td.col-kebab { width: 24px; padding: 0 8px 0 0; }

/* El «seleccionar todo» del header SÍ se revela en hover, al revés de lo que
   fijaba MGA-143 ("una puerta invisible no se encuentra"). Lo que hace que el
   argumento no aplique acá es DÓNDE se engancha el hover: no al `<th>` —que
   sería una puerta de 14px que hay que adivinar— sino a **la tabla entera**. El
   pointer en cualquier parte de la lista lo enciende, así que se encuentra igual
   que antes, y la cabecera queda limpia cuando nadie está por seleccionar nada.

   Sigue visible, obviamente, cuando hay algo tildado: si no, no habría con qué
   destildar. */
.table-wrap:hover thead th.col-check .lin-check input { opacity: 1; }

/* Banner sobre la tabla (variante B del prototipo). Confiesa que «todo» son las
   filas cargadas y no las que matchean el filtro; en la etapa 2 suma acá el link
   que ofrece el resto. Vive entre el toolbar y la tabla, y lo dibuja el JS: sólo
   existe como consecuencia de un click, nunca sale del server. */
.sel-banner {
  display: flex; align-items: center; justify-content: center; gap: 8px;
  padding: 9px 12px; margin-bottom: -1px;
  background: var(--bg-sunken); border: 1px solid var(--border);
  border-radius: var(--radius);
  font-size: 12.5px; color: var(--text-2);
}
.sel-banner b { color: var(--text); font-weight: 500; }

/* Pie que cierra la cuenta (el desglose del acopio por plata, las cesiones):
   la última fila es el total y por eso es la única que pesa. */
tfoot td {
  padding: 0 12px; height: 36px;
  font-size: 13px; color: var(--text-2); vertical-align: middle;
}
tfoot tr:first-child td { border-top: 1px solid var(--border); }
tfoot tr:last-child td { color: var(--text); }
tfoot .muted { font-size: 12.5px; }

/* El mismo pie, pero FUERA de la tabla (el desglose del acopio por plata desde
   MGA-160): salió del <tfoot> para dejarle lugar al renglón de carga entre la
   última fila de material y el cierre de la cuenta (ADR-0010).

   Se ve idéntico al <tfoot> que reemplaza: mismo alto de renglón, mismo cuerpo,
   y el monto contra el borde derecho con el mismo inset de 12px que la columna
   Monto, así la cuenta cierra en la misma vertical que los números que suma. No
   lleva borde arriba: la última fila del <tbody> ya trae el suyo, que es el que
   antes se fusionaba con el del <tfoot> (border-collapse). */
.pie-cuenta .pie-fila {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 0 12px; height: 36px;
  font-size: 13px; color: var(--text-2);
}
.pie-cuenta .pie-total { color: var(--text); }
/* El total es el único renglón que pesa, y pesa ENTERO: adentro de la tabla el
   monto lo tomaba de `tfoot tr:last-child td`, que le ganaba a `.cell-meta` por
   especificidad. Afuera, `.cell-meta` le declara el color al span y una
   declaración propia siempre le gana a una heredada, así que el número hay que
   pintarlo acá o el total queda con la etiqueta negra y el monto gris. */
.pie-cuenta .pie-total .cell-meta { color: var(--text); }
/* Las aclaraciones ("recibido + en vuelo") se leen un tono más claro que su
   etiqueta. Adentro de la tabla eso lo hacía `td .muted`; sin <td>, va acá. */
.pie-cuenta .muted { font-size: 12.5px; color: var(--text-3); }

/* Nombre con ícono a la izquierda. Es un <span> casi siempre, pero en las
   filas que navegan a un detalle propio (plantillas, ADR-0014/MGA-208) es un
   <a>: mismo look, sin el azul ni el subrayado de link por default. */
.cell-name { display: flex; align-items: center; gap: 10px; font-weight: 450; color: var(--text); }
a.cell-name { text-decoration: none; }
a.cell-name:hover { text-decoration: underline; }
.cell-name .row-icon {
  width: 16px; height: 16px; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center; color: var(--text-3);
}
.cell-name .row-icon svg { width: 16px; height: 16px; stroke: currentColor; }
/* Ícono cambiable (estilo Linear) + ID del acopio en la lista. */
.cell-name .icono-cell { position: relative; display: inline-flex; align-items: center; gap: 8px; flex-shrink: 0; }
.icono-btn {
  width: 24px; height: 24px; flex-shrink: 0; padding: 0; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  border: none; background: transparent; border-radius: 6px; color: var(--text-3);
}
.icono-btn:hover { background: var(--bg-hover); color: var(--text); }
.icono-btn svg { width: 16px; height: 16px; stroke: currentColor; }
.cell-name .row-id {
  flex-shrink: 0; font-variant-numeric: tabular-nums;
  font-size: 11px; font-weight: 500; color: var(--text-3); letter-spacing: .02em;
}
/* Los mismos dos, ahora dentro de una fila de lista: Acopios dejó de ser una
   tabla (MGA-110) y se llevó puesto el ícono cambiable y el ID. Las reglas de
   arriba cuelgan de .cell-name, que es la celda de una tabla y ahí ya no está. */
.lin-row .icono-cell { position: relative; display: inline-flex; align-items: center; gap: 8px; flex-shrink: 0; }
.lin-row .row-id {
  flex-shrink: 0; font-variant-numeric: tabular-nums;
  font-size: 11px; font-weight: 500; color: var(--text-3); letter-spacing: .02em;
}

/* Popover del picker. Pre-renderizado y oculto; el botón lo muestra al instante. */
.icono-pop { position: absolute; top: 100%; left: 0; z-index: var(--z-popover); display: none; }
.icono-cell.open .icono-pop { display: block; }
.icono-picker {
  margin-top: 6px; width: 220px; padding: 6px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 10px; box-shadow: var(--shadow-modal);
}
.icono-grid { display: grid; grid-template-columns: repeat(6, 1fr); gap: 2px; }
.icono-opt {
  aspect-ratio: 1; padding: 0; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  border: none; background: transparent; border-radius: 6px; color: var(--text-2);
}
.icono-opt:hover { background: var(--bg-hover); color: var(--text); }
.icono-opt.is-active { background: var(--bg-active); color: var(--text); }
.icono-opt svg { width: 17px; height: 17px; stroke: currentColor; }
/* En el modal de editar el acopio el picker vive dentro de un `.field`, y ahí
   `.field label { display: block }` le gana en especificidad al inline-flex de
   arriba (el ícono se iba a la esquina en vez de centrarse). Las columnas `1fr`,
   además, están calibradas para el popover angosto de la fila: en el modal de
   400px estiraban cada celda a 65px. Se fija el tamaño de la celda. */
.field .icono-grid { grid-template-columns: repeat(6, 34px); }
.field .icono-opt { display: inline-flex; margin-bottom: 0; }

/* Metadata alineada a la derecha (rubro, unidad…). */
.cell-meta { text-align: right; color: var(--text-2); white-space: nowrap; }
.cell-meta .meta { display: inline-flex; align-items: center; gap: 14px; justify-content: flex-end; }
.cell-meta .tag {
  color: var(--text-2); font-size: 12.5px;
  background: var(--bg-sunken); border: 1px solid var(--border);
  border-radius: 5px; padding: 1px 8px;
}
td .muted { color: var(--text-3); }

/* Progreso del acopio: cuánto queda (texto) + cuánto se usó (barra). */
.cell-meta .progress {
  display: inline-flex; align-items: center; gap: 10px;
}
/* La misma barra en la meta de una fila de lista: Acopios dejó de ser una tabla
   (MGA-110) y se la llevó puesta. */
.lin-meta .progress {
  display: inline-flex; align-items: center; gap: 10px;
}
.progress-text { font-size: 12.5px; color: var(--text); font-variant-numeric: tabular-nums; }
.progress-track {
  width: 88px; height: 6px; border-radius: 999px;
  background: var(--bg-active); overflow: hidden; flex-shrink: 0;
}
.progress-fill {
  display: block; height: 100%; border-radius: 999px;
  background: var(--text); transition: width .2s ease;
}
.progress-fill.has-value { min-width: 3px; }
/* Sobregirado (MGA-91): el saldo negativo va en rojo y la barrita se llena de
   rojo entera. Va en la PISTA y no en el tramo porque un acopio se puede
   sobregirar sin nada entregado (todo cedido, o todo en vuelo), y ahí el tramo
   mide 0 y no habría rojo que ver. */
.progress.is-sobregiro .progress-text { color: var(--danger); font-weight: 500; }
.progress.is-sobregiro .progress-track { background: var(--danger); }
.progress.is-sobregiro .progress-fill { display: none; }

.empty { text-align: center; color: var(--text-2); padding: 56px 16px; font-size: 13.5px; }
.empty strong { color: var(--text); font-weight: 500; }

/* Borrar dentro del modal: discreto, abajo a la izquierda. */
.modal-actions .delete-link {
  margin-right: auto; border-color: transparent; background: transparent;
  color: var(--text-2);
}
.modal-actions .delete-link:hover { background: var(--bg-hover); color: var(--text); }

/* Badge de estado (abierto / cerrado). */
.badge {
  display: inline-flex; align-items: center;
  font-size: 12px; font-weight: 500;
  border-radius: 5px; padding: 1px 8px;
  border: 1px solid var(--border);
}
.badge-abierto { color: var(--text); background: var(--bg-sunken); }
.badge-cerrado { color: var(--text-3); background: transparent; }

/* Estados de pedido. Tonos suaves, coherentes con el resto. */
.badge-pendiente      { color: #92560a; background: #fdf2e2; border-color: #f3d9b0; }
.badge-confirmado     { color: #0c447c; background: #e8f1fb; border-color: #bcd6f0; }
.badge-cargar_sistema { color: #4527a0; background: #efeafa; border-color: #d4c8f0; }
.badge-solicitado     { color: #0c447c; background: #e8f1fb; border-color: #bcd6f0; }
.badge-en_camino      { color: #27500a; background: #ecf6e6; border-color: #c5e2b3; }
.badge-recibido       { color: #6a1b9a; background: #f6ecfa; border-color: #e0c8ee; }
.badge-con_problema   { color: #a32d2d; background: #fbebeb; border-color: #f0c4c4; }

/* Estados de OC (creada → enviada → presupuesto → confirmada → recibida;
   presupuesto/negociación solo cuenta corriente). */
.badge-creada      { color: #0c447c; background: #e8f1fb; border-color: #bcd6f0; }
.badge-enviada     { color: #27500a; background: #ecf6e6; border-color: #c5e2b3; }
.badge-presupuesto { color: #8a5300; background: #fbf1e0; border-color: #f0dcba; }
.badge-confirmada  { color: #1b5e51; background: #e4f4ef; border-color: #b7e0d4; }
.badge-recibida    { color: #6a1b9a; background: #f6ecfa; border-color: #e0c8ee; }

/* Dropdown de cambio de estado (badge clickeable → menú de opciones). */
.estado-menu { position: relative; display: inline-block; }
.estado-trigger {
  list-style: none; cursor: pointer;
  display: inline-flex; align-items: center; gap: 5px;
}
.estado-trigger::-webkit-details-marker { display: none; }
.estado-caret { display: inline-flex; opacity: .6; }
.estado-caret svg { width: 12px; height: 12px; stroke: currentColor; }
.estado-pop {
  position: absolute; top: calc(100% + 4px); left: 0; z-index: var(--z-popover);
  min-width: 170px; padding: 4px;
  background: var(--bg); border: 1px solid var(--border);
  border-radius: var(--radius); box-shadow: var(--shadow-modal);
}
.estado-opt {
  display: flex; align-items: center; gap: 8px; width: 100%;
  padding: 6px 8px; border: none; background: transparent;
  border-radius: 5px; cursor: pointer; text-align: left;
  font-size: 13px; color: var(--text);
}
.estado-opt:hover { background: var(--bg-hover); }
.estado-opt.is-active { font-weight: 500; }
.estado-opt .dot {
  width: 9px; height: 9px; border-radius: 50%; flex-shrink: 0;
  border: 1.5px solid currentColor; box-sizing: border-box;
}
/* Colores de punto por estado (mismos que la lista). */
.dot.dot-pendiente      { color: #f9a825; }
.dot.dot-confirmado     { color: #1565c0; }
.dot.dot-cargar_sistema { color: #7b1fa2; }
.dot.dot-solicitado     { color: #1565c0; }
.dot.dot-en_camino      { color: #2e7d32; background: currentColor; }
.dot.dot-recibido       { color: #7b1fa2; background: currentColor; }
.dot.dot-con_problema   { color: #c62828; background: currentColor; }
/* Orden de compra: creada (hueco) → enviada → presupuesto → confirmada →
   recibida (lleno). Presupuesto/negociación es intermedio (hueco ámbar). */
.dot.dot-creada      { color: #1565c0; }
.dot.dot-enviada     { color: #2e7d32; background: currentColor; }
.dot.dot-presupuesto { color: #e08a00; }
.dot.dot-confirmada  { color: #00897b; background: currentColor; }
.dot.dot-recibida    { color: #7b1fa2; background: currentColor; }
/* Acopio: abierto = punto lleno; cerrado = punto hueco tenue. */
.dot.dot-abierto { color: var(--text); background: currentColor; }
.dot.dot-cerrado { color: var(--text-3); }

/* ── Panel de propiedades (compartido acopio + pedido) ───────────────────── */
.props-group { display: flex; flex-direction: column; gap: 4px; }
.props-group + .props-group { margin-top: 16px; }
.props-group-title {
  font-size: 11px; font-weight: 500; color: var(--text-3);
  letter-spacing: .02em; padding: 0 0 4px;
}
.prop {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 5px 0; min-height: 28px;
}
.prop-label { flex: 0 0 70px; font-size: 12.5px; color: var(--text-2); padding-top: 2px; }
.prop-value { flex: 1 1 auto; min-width: 0; font-size: 13px; color: var(--text); overflow-wrap: anywhere; }
/* El tratamiento de un valor vacío del panel (AGR-322): hasta acá los seis
   campos de `.props-box` salían de FKs obligatorias y nunca estaban vacíos,
   así que no había regla que lo cubriera. `--text-3` y no `--text-2` porque es
   el mismo tono que ya usa `.props-group-title` para lo secundario del panel,
   no el de un dato que sí está pero pesa menos. */
.prop-value.is-vacia { color: var(--text-3); }
/* La razón social de cada punta linkea a la ficha de la empresa. En toda la app
   `a` es `color: inherit; text-decoration: none`, así que sin esto el link no se
   distingue del valor de al lado y no lo encuentra nadie. El subrayado en hover
   es la misma señal que traía ese mismo link cuando vivía en la tab Carga
   (`.traspaso-parte-nombre a:hover`, borrado por AGR-323). */
.prop-value a:hover { text-decoration: underline; }
.prop-link { display: inline-flex; align-items: center; gap: 6px; color: var(--text); }
.prop-link:hover { color: var(--text); text-decoration: underline; }
.prop-link svg { width: 14px; height: 14px; stroke: var(--text-3); }
.prop-empty { font-size: 12.5px; }
.props-obras { display: flex; flex-wrap: wrap; gap: 5px; }

/* Variantes de .lin-row para la lista de materiales del detalle. */
.lin-row .lin-icon {
  width: 16px; height: 16px; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center; color: var(--text-3);
}
.lin-row .lin-icon svg { width: 15px; height: 15px; stroke: currentColor; }
.lin-qty { font-size: 12.5px; color: var(--text-2); font-variant-numeric: tabular-nums; }

/* Fila de pedido vencido + tag de peligro. */
.row-vencida { box-shadow: inset 3px 0 0 #c62828; }
.tag-danger { color: #a32d2d; background: #fbebeb; border-color: #f0c4c4; }
/* Chip «Aporte a {cliente}»: OC cuya obra es de otro cliente (MGA-98). Forma de
   pastilla como .tag, pegado a la obra; el tono de alerta lo pone .tag-danger. */
.tag-aporte {
  display: inline-block; font-size: 12px; line-height: 1.4;
  border: 1px solid; border-radius: 5px; padding: 1px 8px; margin-left: 6px;
}

/* ── Lista estilo Linear (pedidos) ───────────────────────────────────────── */
.lin-list { display: flex; flex-direction: column; }
.lin-row {
  display: flex; align-items: center; gap: 10px;
  height: 44px; padding: 0 8px;
  border-bottom: 1px solid var(--border);
  color: var(--text); cursor: pointer;
  transition: background .08s;
}
.lin-row:hover { background: var(--bg-hover); }

/* Checkbox de selección múltiple (borrado en bloque, MGA-67). Zona de click
   generosa para no pifiarle al cuadradito dentro de la fila-link. Estilo Linear:
   aparece on hover de la fila (o si está tildado); si no, queda invisible pero
   ocupando su lugar para que el layout no salte. */
.lin-check { display: flex; align-items: center; flex-shrink: 0; padding: 6px; margin: -6px; cursor: pointer; }
/* El checkbox se dibuja entero acá, con `appearance: none`.
   El nativo lo pinta el sistema operativo: en macOS el borde del no-tildado es
   casi negro, y en una lista de veinte filas eso son veinte manchas oscuras
   compitiendo con el texto. Con `accent-color` sólo se puede cambiar el relleno
   del tildado, no el borde del vacío — que es justo el que se ve siempre. */
.lin-check input {
  appearance: none; -webkit-appearance: none;
  width: 14px; height: 14px; margin: 0; cursor: pointer; flex-shrink: 0;
  border: 1px solid var(--border-strong); border-radius: 4px;
  background: var(--bg) no-repeat center; background-size: 11px;
  opacity: 0; transition: opacity .1s, background-color .1s, border-color .1s;
}
.lin-check input:hover { border-color: var(--text-3); }
.lin-check input:checked, .lin-check input:indeterminate {
  background-color: var(--brand); border-color: var(--brand);
}
.lin-check input:checked { background-image: url("data:image/svg+xml,<svg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2012%2012'><path%20d='M2.6%206.2%204.9%208.5%209.4%203.7'%20fill='none'%20stroke='%23fff'%20stroke-width='1.9'%20stroke-linecap='round'%20stroke-linejoin='round'/></svg>"); }
/* Indeterminado: hay filas tildadas pero no todas. No es un estado que el HTML
   sepa escribir (no es un atributo), lo prende el JS de base.html. */
.lin-check input:indeterminate { background-image: url("data:image/svg+xml,<svg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2012%2012'><path%20d='M3.2%206h5.6'%20fill='none'%20stroke='%23fff'%20stroke-width='1.9'%20stroke-linecap='round'%20stroke-linejoin='round'/></svg>"); }
.lin-check input:focus-visible {
  outline: none; box-shadow: 0 0 0 3px rgba(24, 30, 38, .16);
}
/* El reveal en hover vale también para las tablas de catálogo, que siguen siendo
   tablas (ADR-0004 §1) y ganan selección múltiple sin una columna permanente de
   checkboxes: por eso el hover se escribe contra .lin-row Y contra <tr>. */
.lin-row:hover .lin-check input,
tr:hover .lin-check input,
.lin-check input:checked,
.lin-check input:indeterminate,
.lin-check input:focus-visible { opacity: 1; }

/* Kebab de la fila (ADR-0004 §2): el único hogar de las acciones de una fila.
   Se revela en hover, como el checkbox, y queda prendido mientras su menú está
   abierto. Ocupa lugar siempre (opacity, no display) para que nada salte. */
.lin-kebab {
  display: flex; align-items: center; justify-content: center;
  width: 24px; height: 24px; flex-shrink: 0; margin-left: 2px;
  border: none; background: transparent; border-radius: 5px;
  color: var(--text-3); cursor: pointer;
  opacity: 0; transition: opacity .1s, background .1s;
}
.lin-row:hover .lin-kebab,
tr:hover .lin-kebab,
.lin-kebab.is-open,
.lin-kebab:focus-visible { opacity: 1; }
.lin-kebab:hover, .lin-kebab.is-open { background: var(--bg-active); color: var(--text); }
.lin-kebab svg { width: 15px; height: 15px; stroke: currentColor; }

/* La fila cuyo menú está abierto (por el kebab o por click derecho) queda
   marcada: con el menú flotando al lado, sin esto no se sabe sobre cuál actúa. */
.lin-row.is-ctx, .lin-row.is-ctx:hover, tr.is-ctx { background: var(--bg-active); }

/* ── Isla de acciones flotante (estilo Linear, MGA-67) ─────────────────────── */
/* Fija abajo-centro, aparece con animación al tildar filas. */
.action-island {
  position: fixed; left: 50%; bottom: 12px; z-index: var(--z-isla);
  display: flex; align-items: center; gap: 10px;
  padding: 7px 8px 7px 14px;
  background: var(--bg); border: 1px solid var(--border);
  border-radius: 10px; box-shadow: 0 8px 28px rgba(0, 0, 0, .16);
  font-size: 13px; color: var(--text-2);
  /* Estado oculto: corrido hacia abajo + transparente; .is-visible lo sube. */
  transform: translate(-50%, 12px); opacity: 0;
  transition: transform .18s cubic-bezier(.2, .8, .3, 1), opacity .18s ease;
}
.action-island.is-visible { transform: translate(-50%, 0); opacity: 1; }
.action-island-count { font-weight: 500; color: var(--text); white-space: nowrap; }
.action-island-hint { font-size: 12px; color: var(--text-3); white-space: nowrap; }
.action-island-sep { width: 1px; align-self: stretch; background: var(--border); margin: 2px 2px; }
.action-island .btn-eliminar { display: inline-flex; align-items: center; gap: 5px; }
.action-island .btn-eliminar svg { width: 14px; height: 14px; stroke: currentColor; }
.action-island-close {
  display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; padding: 0;
  border: none; background: none; border-radius: 6px;
  color: var(--text-3); cursor: pointer;
}
.action-island-close:hover { background: var(--bg-hover); color: var(--text); }
.action-island-close svg { width: 15px; height: 15px; stroke: currentColor; }

/* Botón de acción destructiva (tono de .tag-danger). */
.btn-eliminar { color: #a32d2d; border-color: #f0c4c4; }
.btn-eliminar:hover { background: #fbebeb; color: #a32d2d; }

/* Punto de estado a la izquierda (como el ícono de status de Linear).

   `display: inline-block` no es decorativo: sin él, `width` y `height` **no
   aplican**, porque un `<span>` es `inline` y en inline se ignoran. Adentro de
   `.lin-row` se veía bien igual, de rebote —es un contenedor flex y los ítems
   flex se blockifican solos—, pero en el detalle el mismo punto vive dentro de un
   `<p>` y de un `.prop-value` (que es ítem flex, no contenedor) y ahí quedaba
   inline: se veía sólo el borde redondeado colapsado, un arco verde sin tamaño.

   `vertical-align` lo sienta sobre la línea de texto: al lado de una palabra, un
   inline-block se apoya en la baseline y el círculo queda flotando alto. */
.lin-status {
  display: inline-block;
  width: 11px; height: 11px; border-radius: 50%; flex-shrink: 0;
  border: 1.5px solid currentColor; box-sizing: border-box;
  vertical-align: -1px;
}
.lin-status.dot-pendiente      { color: #f9a825; }
.lin-status.dot-confirmado     { color: #1565c0; }
.lin-status.dot-cargar_sistema { color: #7b1fa2; }
.lin-status.dot-solicitado     { color: #1565c0; }
.lin-status.dot-en_camino      { color: #2e7d32; background: currentColor; }
.lin-status.dot-recibido       { color: #7b1fa2; background: currentColor; }
.lin-status.dot-con_problema   { color: #c62828; background: currentColor; }
.lin-status.dot-creada         { color: #1565c0; }
.lin-status.dot-enviada        { color: #2e7d32; background: currentColor; }
.lin-status.dot-presupuesto    { color: #e08a00; }
.lin-status.dot-confirmada     { color: #00897b; background: currentColor; }
.lin-status.dot-recibida       { color: #7b1fa2; background: currentColor; }

/* ID monoespaciado tenue, como "MGA-36" en Linear. */
.lin-id {
  flex-shrink: 0; font-variant-numeric: tabular-nums;
  font-size: 12px; font-weight: 500; color: var(--text-3);
  min-width: 38px;
}

/* Título: ocupa el espacio del medio, recorta con … si no entra. */
.lin-title {
  flex: 1 1 auto; min-width: 0;
  font-size: 13.5px; font-weight: 450;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.lin-title .lin-client { font-weight: 400; }

/* La nota del pedido en la fila (MGA-213). Ocupa EXACTAMENTE el hueco que le
   dejan el título y los labels, ni un pixel más.

   El `width: 0` es lo que lo consigue, y no es decorativo: es lo que hace que la
   nota NO cuente su texto cuando el navegador mide cuánto quiere ocupar la fila.
   Sin él, una nota larga se cobraba el ancho de su prosa y aplastaba lo de al
   lado: medido en el browser a 1040px, los labels pasaban de 82px y 105px a
   22px y 22px —«Al», «T»— para hacerle lugar. Al revés de lo que hay que
   sacrificar. Con `width: 0` la nota no reclama nada, el título y los labels se
   miden a su ancho de siempre, y `flex-grow` le da a la nota el sobrante, donde
   recorta con `…`.

   El orden de sacrificio queda entonces: primero la nota, y recién después
   —cuando la fila ya no entra ni sin ella— labels y título, igual que antes de
   este cambio. Es el orden correcto: el título es identidad (de qué obra es) y
   los labels son una clasificación cerrada; la nota es prosa, y su texto entero
   sigue estando en el `title`. En una fila muy apretada la nota se reduce a
   nada, y está bien.

   Lo que sí cuesta es la separación: una fila CON nota gasta los 8px que la
   despegan del título, y en una ventana angosta eso puede recortar el título
   unos pixeles antes de lo que lo recortaría sin nota. Es el precio de mostrarla
   al lado y no hay forma de no pagarlo; las filas sin nota no pagan nada.

   Tenue y 12.5px a propósito: tiene que poder ignorarse de un vistazo cuando
   escaneás la columna de obras, y aparecer cuando la buscás. */
.lin-nota {
  width: 0; flex: 1 1 auto; min-width: 0;
  font-size: 12.5px; color: var(--text-3);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* Título + nota, en un solo hijo de la fila (MGA-213).

   Existe por dos razones, las dos medidas en el browser:

   1. LA NOTA SE LEE PEGADA AL TÍTULO. Quien se come el espacio libre de la fila
      es el título (`flex: 1 1 auto`): por eso la meta termina contra el borde
      derecho. Con la nota como hermana y el título creciendo igual, la nota
      quedaba escupida al otro extremo del renglón, a un palmo de la obra que
      describe, leyéndose como otra columna en vez de como su aclaración. Acá
      adentro el que crece es `.lin-head`, el título vuelve al ancho de su texto
      y la nota arranca donde termina la obra.

   2. EL PEDIDO SIN NOTA NO PAGA NADA. `.lin-row` tiene `gap: 10px`, y el gap se
      cobra por hijo, no por pixel: una nota de ancho cero le costaba igual 10px
      al renglón y recortaba títulos que antes entraban. Con las dos piezas
      adentro de un hijo, la fila cuenta uno solo, como antes de este cambio.

   El `gap: 8px` de acá adentro sólo aparece cuando hay nota, porque sin nota
   `.lin-head` tiene un solo hijo y no hay entre qué separar. */
.lin-head {
  flex: 1 1 auto; min-width: 0;
  display: flex; align-items: baseline; gap: 8px;
}
.lin-head .lin-title { flex: 0 1 auto; }

/* Chips de label de la fila de pedido (ADR-0007): al lado del título, antes de
   la meta. No es una columna del grid de la derecha —un pedido puede tener cero,
   uno o varios— así que se lleva lo que el título le deje y no empuja nada: con
   muchos labels se recorta la tira, no la fila. El título ya tiene `flex: 1 1
   auto`, así que la tira cede primero. */
.lin-labels {
  flex: 0 1 auto; min-width: 0;
  display: flex; align-items: center; gap: 4px;
  overflow: hidden;
}
.lin-label {
  max-width: 140px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* Metadatos a la derecha: pills + fecha + estado. */
.lin-meta {
  flex-shrink: 0; display: flex; align-items: center; gap: 10px;
  white-space: nowrap;
}
/* El pill es un átomo, no una pieza de la fila: estaba escrito como
   `.lin-meta .pill`, así que los pills de afuera de la meta (los del panel de
   propiedades, los del título de una línea, y los «estimado» del detalle de la
   OC) se veían como texto pelado. Misma pinta, ahora en todos lados. */
.pill {
  font-size: 12px; color: var(--text-2);
  background: var(--bg-sunken); border: 1px solid var(--border);
  border-radius: 20px; padding: 2px 10px;
}
.lin-date { font-size: 12.5px; color: var(--text-2); font-variant-numeric: tabular-nums; }
.lin-date.is-overdue { color: #c62828; font-weight: 500; }
/* Total de la OC en la vista "Por proveedor" (≈ = estimado / incompleto). */
.lin-total {
  font-size: 12.5px; font-weight: 500; color: var(--text-2);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.lin-total.muted { font-weight: 400; color: var(--text-3); }

/* La celda del autor (MGA-188 / ADR-0011). Es la ÚNICA columna que las dos
   metas comparten idéntica —mismo ancho de track (32px), mismo contenido (un
   círculo de 24px y nada más)— así que se escribe una vez: son la misma columna
   en dos listas, y duplicarla es invitar a que se separen sin que nadie lo
   decida. Las demás celdas sí van scopeadas por meta, porque cada lista tiene
   las suyas.

   `display: flex` deja el círculo centrado en su track sin depender de la línea
   base del texto, y —clave— hace que el salto de línea que deja el include NO
   cuente como contenido: sin autor la celda está y no dibuja nada, ni un
   espacio. El fantasma punteado de Linear se rechazó a propósito (es un botón
   para asignarte; el nuestro no va a ser clickeable nunca). */
.lin-meta .m-autor {
  justify-self: center;
  display: flex; align-items: center;
}

/* Meta de la fila de OC: GRID de anchos fijos (ADR-0004 §5, y la V1 del
   prototipo docs/prototypes/0004-ui-fila.html). La meta era un flexbox suelto:
   cada pieza medía lo que medía el dato, así que las fechas y los montos no se
   alineaban entre filas y no se podían comparar de un vistazo.
   Columnas: obra · CC · fecha · total · estado. La del total solo existe donde
   hay total (.con-total, la vista "Por proveedor"): la lista plana no calcula
   ningún precio. Es de Órdenes y no de .lin-meta a secas porque las otras listas
   tienen otras columnas; cuando les toque, cada una define la suya. */
/* obra · CC · autor · fecha · total · estado. La columna del total está SIEMPRE
   (las cuatro superficies de una OC lo muestran), aunque la OC no tenga precio:
   la celda dice "sin precio" y sostiene la alineación de la de al lado.
   El autor (32px, MGA-188) va TERCERO y no en una punta: obra y CC son dos
   pills que se leen como un bloque con el proveedor del título, y fecha/total
   son dos números alineados a la derecha que se comparan de arriba abajo. El
   círculo es el corte natural entre esas dos mitades; meterlo antes de la obra
   despegaría las pills del título, y meterlo al final lo dejaría discutiendo el
   borde derecho con el badge de estado. */
.lin-meta.meta-orden {
  display: grid;
  grid-template-columns: 176px 30px 32px 82px 116px 100px;
  align-items: center; gap: 10px;
}
.meta-orden .m-obra { min-width: 0; justify-self: start; }
/* La OC de varias obras ("QM101, QM102, QM103") no puede ensanchar su columna:
   se recorta con … y el nombre entero queda en el title. */
.meta-orden .m-obra .pill {
  display: block; max-width: 100%;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.meta-orden .m-cc { justify-self: center; }
.meta-orden .m-cc .pill { padding: 2px 7px; font-size: 11px; }
/* Los números pegados al borde derecho de su columna: así se comparan de arriba
   abajo (con .lin-date/.lin-total ya en tabular-nums, las cifras caen en línea). */
.meta-orden .m-fecha, .meta-orden .m-total { justify-self: end; }
/* El badge ocupa toda su columna: los estados forman un bloque parejo, que es lo
   que hace escaneable la última columna. */
.meta-orden .m-estado { justify-self: stretch; }
.meta-orden .m-estado .badge { width: 100%; justify-content: center; }

/* Meta de la fila de PEDIDO: el mismo grid de anchos fijos (ADR-0004 §5), con
   las columnas que tiene esta lista — autor · fecha de necesidad · estado.
   Cada celda se emite siempre, aunque venga vacía (ver pedidos/_row.html): si no,
   un pedido sin fecha correría el estado a la primera columna y la columna
   dejaría de ser una columna.

   El autor (32px, MGA-188 / ADR-0011) DEROGA lo que decía este comentario, que
   era "el autor NO va en la lista: vive en el panel de propiedades (MGA-146)".
   El motivo de aquella decisión era que competía con el label, y era cierto de
   un TEXTO ("Francisco" al lado de "Reposición" son dos pastillas peleando el
   mismo lugar); un círculo de 24px es otro átomo visual y no compite. Va primero
   porque es lo único de la meta que no es un número: fecha y estado se comparan
   de arriba abajo pegados al borde derecho, y el círculo abre la meta del lado
   de los labels, que es de donde viene leyendo el ojo. */
.lin-meta.meta-pedido {
  display: grid;
  grid-template-columns: 32px 132px 100px;
  align-items: center; gap: 10px;
}
/* La fecha, pegada al borde derecho de su columna: así se comparan de arriba
   abajo (el badge de urgencia y la pill de fecha caen en la misma línea). */
.meta-pedido .m-fecha { justify-self: end; }
/* El badge ocupa toda su columna, igual que en Órdenes: los estados forman un
   bloque parejo, que es lo que hace escaneable la última columna. */
.meta-pedido .m-estado { justify-self: stretch; }
.meta-pedido .m-estado .badge { width: 100%; justify-content: center; }

/* Vencida: barrita roja a la izquierda de la fila. */
.lin-row.is-vencida { box-shadow: inset 2px 0 0 #c62828; }

.lin-empty { text-align: center; color: var(--text-2); padding: 56px 16px; font-size: 13.5px; }
.lin-empty strong { color: var(--text); font-weight: 500; }
.lin-load-more { padding: 10px 8px; }

/* Fila de carga continua al pie de la lista de materiales, siempre visible (sin
   modal): material + cantidad + "+" en el pedido, y con la OBRA adelante en la
   orden de compra, donde la obra es un dato de la línea (ADR-0010). */
.lin-add {
  display: flex; align-items: center; gap: 8px;
  padding: 10px 8px;
  border-bottom: 1px solid var(--border);
}
.lin-add-obra { flex: 0 0 190px; min-width: 0; }
.lin-add-material { flex: 1 1 auto; min-width: 0; }
.lin-add-cantidad { flex: 0 0 110px; }
.lin-add-cantidad input { width: 100%; }
.lin-add .btn { flex-shrink: 0; }
/* El mismo renglón al pie de la lista de materiales del acopio (MGA-161), donde
   además de la cantidad va el precio. El precio pide más lugar que una cantidad:
   son pesos con centavos, no bolsas. */
.lin-add-precio { flex: 0 0 130px; }
.lin-add-precio input { width: 100%; }

/* El renglón del acopio crece hacia abajo cuando el alta no entró (MGA-161): el
   motivo aterriza ahí, en el ancho entero, debajo de los campos.

   `row-gap: 0` para que, cuando no hay nada que decir, el bloque vacío no le
   sume aire al renglón: el margen lo pone el propio error cuando existe. */
#item-inline { flex-wrap: wrap; row-gap: 0; }
#item-renglon-error { flex: 0 0 100%; }
.lin-add-error { margin-top: 8px; }

/* El renglón de la OC crece hacia abajo cuando tiene algo que decir sobre la
   línea que se está tecleando (MGA-163): el aviso del aporte cross-cliente —que
   se confirma con el Enter siguiente— o el motivo por el que no entró. Ocupa el
   ancho entero, debajo de los tres campos.

   `row-gap: 0` para que, cuando no hay nada que decir, el bloque vacío no le
   sume 8px de aire al renglón: el margen lo pone el propio aviso cuando existe. */
#orden-linea-inline { flex-wrap: wrap; row-gap: 0; }
#orden-renglon-aviso { flex: 0 0 100%; }
.lin-add-aviso, .lin-add-error { margin-top: 8px; }
.lin-add-confirmar { margin: 6px 0 0; font-size: 12.5px; color: var(--text-2); }

/* Ítems del pedido agrupados por obra. */
.obra-group { margin-bottom: 16px; }
.obra-group:last-child { margin-bottom: 0; }
.obra-group-head {
  display: flex; align-items: center; gap: 10px;
  font-size: 12.5px; font-weight: 600; color: var(--text-2);
  margin-bottom: 6px; padding-left: 2px;
}
.obra-group-obra { flex: 1 1 auto; min-width: 0; }

/* La plata de la OC en su detalle (MGA-122): el subtotal por obra en la cabecera
   del grupo, el precio unitario y el subtotal en cada línea, y el total al pie.
   Los tres son de la misma familia tipográfica (tabular, alineados a la derecha)
   para que la columna se lea de arriba abajo: lo que se ve, sumado, da el total. */
.obra-group-total {
  flex-shrink: 0; display: flex; align-items: center; gap: 6px;
  font-variant-numeric: tabular-nums; white-space: nowrap;
}

.lin-precio {
  flex: 0 0 96px; display: flex; align-items: center; gap: 3px;
  justify-content: flex-end;
  font-size: 12.5px; color: var(--text-2);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.lin-precio-sign { color: var(--text-3); }
.lin-precio-val { overflow: hidden; text-overflow: ellipsis; }
.lin-precio-input {
  width: 76px; padding: 2px 6px; text-align: right;
  font-size: 12.5px; font-variant-numeric: tabular-nums;
  border: 1px solid var(--border); border-radius: 4px;
  background: var(--bg); color: var(--text);
}
.lin-precio-input:focus { outline: none; border-color: var(--border-strong); }

/* El subtotal manda sobre el unitario: es la plata de esa línea. */
.lin-subtotal {
  flex: 0 0 104px; text-align: right;
  font-size: 12.5px; font-weight: 500; color: var(--text);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}

.orden-total-row {
  display: flex; align-items: center; gap: 8px;
  margin-top: 12px; padding: 10px 8px 0;
  border-top: 1px solid var(--border);
  font-size: 13px;
}
/* En la OC el renglón de carga se mete entre las filas y el pie (ADR-0010): el
   borde de abajo del renglón ya es la raya que separa, no van dos. */
.lin-add + #orden-total .orden-total-row { margin-top: 0; border-top: 0; }
.orden-total-label { font-weight: 600; }
.orden-total-val {
  margin-left: auto; font-weight: 600;
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.orden-total-val.muted { font-weight: 400; color: var(--text-3); }

/* ── Migas de pan (ADR-0004 §6) ──────────────────────────────────────────── */
/* El camino por el que se entró: `Acopios › Bendito · CQ › OC #32`. La última
   miga es la pantalla actual (no es link); las de antes son el volver. */
.migas {
  display: flex; align-items: center; flex-wrap: wrap; gap: 6px;
  font-size: 13px; margin-bottom: 10px;
}
.migas .miga { color: var(--text-2); }
.migas .miga:hover { color: var(--text); }
.migas .miga-sep { color: var(--text-3); }
.migas .miga-actual { color: var(--text); }

/* ── La pantalla del perfil (ADR-0011) ─────────────────────────────────────
   Dos inputs y un dato. Todo lo demás lo pone `.field`, que ya existía para los
   modales pero nunca estuvo scopeado a ellos: reusarlo es lo que hace que esta
   pantalla se lea igual que cualquier formulario de la app, y por eso acá abajo
   hay tres reglas y no un formulario nuevo.

   El ancho tope es la decisión visible: un input estirado a lo ancho de la hoja
   dice "escribí un párrafo", y acá se escribe un nombre. 420px es el mismo orden
   de magnitud que un modal (.modal tiene 460), que es el ancho al que el equipo
   ya está acostumbrado a cargar datos cortos. */
.perfil { max-width: 420px; }
.perfil .field { margin-bottom: 22px; }

/* El email NO es un input deshabilitado: un campo gris apagado se lee como
   "esto se podría editar y algo lo impide", y lo cierto es lo contrario — la
   identidad la emite Auth0 y no hay nada que editar. Se dibuja como dato. */
.perfil-fijo { font-size: 13.5px; color: var(--text); padding: 2px 0; }

/* ── Detalle (acopio) ───────────────────────────────────────────────────── */
.detail-head { margin-bottom: 24px; }
.detail-head .back {
  display: inline-flex; align-items: center; gap: 4px;
  color: var(--text-2); font-size: 13px; margin-bottom: 10px;
}
.detail-head .back:hover { color: var(--text); }
.detail-head .back svg { width: 15px; height: 15px; stroke: currentColor; }
.detail-head .detail-title-row { display: flex; align-items: center; gap: 16px; margin: 0 0 8px; }
.detail-head .detail-title-row .btn { margin-left: auto; flex-shrink: 0; }
.detail-head h1 { font-size: 20px; font-weight: 600; margin: 0; }
.detail-meta { display: flex; align-items: center; gap: 10px; margin-bottom: 6px; }

.section { margin-bottom: 28px; }
.section-head {
  display: flex; align-items: center; gap: 12px;
  margin-bottom: 12px;
}
.section-head h2 { font-size: 14px; font-weight: 600; margin: 0; }
.section-head .btn { margin-left: auto; }
.section-head .sub { margin: 0; color: var(--text-3); font-size: 12.5px; }

.monto-big { font-size: 28px; font-weight: 600; letter-spacing: -.02em; }
.monto-big .muted { font-size: 15px; font-weight: 400; }

/* ── Los tres detalles: contenido a la izquierda, propiedades a la derecha ─── */
/* El panel tiene COLUMNA PROPIA. Antes flotaba (`float: right` + `sticky`), y esa
   combinación no se puede arreglar: un float sólo corre el texto que fluye a su
   lado, y una tabla no se acomoda alrededor — le pasa por debajo. Mientras el
   panel estaba quieto no se notaba, pero al pegarse viajaba hacia abajo, sobre la
   lista de materiales. Ponerle fondo y z-index lo hizo opaco: dejó de ser texto
   sobre texto y pasó a TAPAR las filas, que es igual de malo. Dos columnas no
   comparten espacio, así que el bug no puede volver a existir (user story 36). */
.detail-cols {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 260px;
  gap: 28px;
}
/* Las tabs del acopio que no son Resumen no muestran panel: el contenido se queda
   con el ancho entero (decisión 2 de la etapa 3). */
.detail-cols.sin-props { grid-template-columns: minmax(0, 1fr); }
.detail-main { min-width: 0; }
/* La columna se estira a lo alto de la fila; lo que se pega es esto, así el panel
   tiene por dónde viajar sin salirse nunca de su columna. */
.props-sticky { position: sticky; top: 16px; }
/* El panel de la derecha son DOS cajas: arriba los responsables (que colapsan),
   abajo la identidad del negocio. El borde de 1px las separa sin necesidad de un
   título que explique que son dos cosas distintas, y le da al bloque colapsado
   una forma propia en vez de dejarlo como un renglón suelto arriba de una lista.

   Es un borde y NADA más —sin fondo, sin sombra—: el panel es una columna de
   datos para leer, y una caja con relleno se lee como un control. */
.props-box {
  padding: 12px 14px;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
}

/* Título del detalle de acopio (fuera de .detail-head, junto al float). */
.detail-title-row { display: flex; align-items: center; gap: 8px; margin: 0 0 8px; }
.detail-title-row h1 { font-size: 20px; font-weight: 600; margin: 0; margin-right: auto; }
.detail-title-row .btn { flex-shrink: 0; }
.detail-notas { color: var(--text-2); margin: 0 0 16px; }

/* Título editable del detalle de una plantilla (ADR-0014, MGA-208): un
   <input> con la misma tipografía que el <h1> de los otros tres detalles,
   sin borde propio — el fondo al pasar el mouse es lo único que lo delata
   como editable, y al enfocarlo se ve como cualquier campo. */
.plantilla-nombre-input {
  font-size: 20px; font-weight: 600; border: none; background: transparent;
  color: var(--text); padding: 2px 6px; margin: -2px 0 6px -6px;
  border-radius: var(--radius); width: 100%; max-width: 480px;
}
.plantilla-nombre-input:hover { background: var(--bg-hover); }
.plantilla-nombre-input:focus { background: var(--bg-sunken); outline: none; }

/* La cantidad de una línea de plantilla: mismo alto que las filas de la
   tabla, chico y alineado a la derecha con el resto de .col-meta. */
.plantilla-cantidad-input {
  width: 72px; height: 26px; padding: 0 8px; text-align: right;
  border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--bg-sunken); color: var(--text); font-size: 13px;
}

/* Botón cuadrado sólo-ícono (ej: el "…" de más acciones). */
.btn-icon { padding: 0; width: 28px; justify-content: center; }
.btn-icon svg { width: 16px; height: 16px; }

/* Barra de acciones sobre el panel de propiedades: el botón "…" alineado a la
   derecha, arriba de las boxes. */
.props-actions { display: flex; justify-content: flex-end; margin-bottom: 10px; }

/* Menú de acciones (los 3 puntos): dropdown nativo <details> que abre un
   popover hacia abajo/izquierda, alineado al borde derecho del botón. */
.acciones-menu { position: relative; }
.acciones-trigger { list-style: none; }
.acciones-trigger::-webkit-details-marker { display: none; }
.acciones-menu[open] > .acciones-trigger { background: var(--bg-hover); }
.acciones-pop {
  position: absolute; top: calc(100% + 4px); right: 0; z-index: var(--z-popover);
  min-width: 160px; padding: 3px;
  background: var(--bg); border: 1px solid var(--border);
  border-radius: var(--radius); box-shadow: var(--shadow-modal);
}
.acciones-item {
  display: flex; align-items: center; gap: 8px; width: 100%;
  padding: 6px 8px; border: none; background: transparent;
  border-radius: 5px; text-align: left;
  font-size: 12.5px; color: var(--text-2); cursor: pointer;
  text-decoration: none; white-space: nowrap;
}
.acciones-item:hover { background: var(--bg-hover); color: var(--text); }
.acciones-item svg { width: 15px; height: 15px; stroke: currentColor; flex-shrink: 0; }
.acciones-sep { height: 1px; background: var(--border); margin: 4px 2px; }
/* Eliminar, en el menú: el mismo tono que el botón destructivo del modal. */
.acciones-item.is-peligro, .acciones-item.is-peligro:hover { color: #a32d2d; }
.acciones-item.is-peligro:hover { background: #fbebeb; }

/* ── El vínculo a mano de un negocio suelto (AGR-414) ────────────────────
   Las dos listas de candidatas del modal de «Vincular a oportunidad»: la
   oportunidad en el paso 1, la orden en firme en el paso 2. Misma anatomía
   que `.ubi-opt` (nombre + secundario en la misma fila, hover parejo) pero
   sin el posicionamiento flotante de `.ubi-res`: acá la lista vive adentro
   del cuerpo del modal, no de un campo de formulario. */
.vincular-lista {
  display: flex; flex-direction: column; gap: 2px;
  max-height: 320px; overflow-y: auto;
  border: 1px solid var(--border); border-radius: 9px; padding: 4px;
}
.vincular-opt {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  width: 100%; padding: 8px 10px; border: 0; border-radius: 6px;
  background: transparent; text-align: left; cursor: pointer;
  font: inherit; font-size: 13px; color: var(--text);
}
.vincular-opt:hover { background: var(--bg-hover); }
.vincular-opt-nombre { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vincular-opt-sec { color: var(--text-3); font-size: 12px; white-space: nowrap; flex-shrink: 0; }
/* El mismo rojo que `.field .err`, para el rechazo del paso 1 (otra
   oportunidad no es la que el negocio necesita — nunca debería pasar desde
   la lista, que ya filtra, pero un POST directo sí puede mandarla). */
.vincular-error {
  color: var(--danger); background: rgba(163, 45, 45, .06);
  border-radius: 5px; padding: 6px 9px; font-size: 12.5px; margin-bottom: 8px;
}

/* ── La ficha de una empresa ─────────────────────────────────────────────
   Dos bloques —contactos y ubicaciones— con la misma anatomía que el bloque de
   traspaso del cierre: quién a la izquierda, qué hacer a la derecha. Comparten
   la forma a propósito; son la misma pregunta («¿a quién le escribo?») en dos
   pantallas. */
.ficha-titulo { display: flex; align-items: center; gap: 12px; min-width: 0; }
.ficha-titulo .emp-logo { flex: none; }

.ficha-bloque { margin-top: 28px; padding-top: 20px; border-top: 1px solid var(--border); }
.ficha-bloque-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; margin-bottom: 10px;
}
.ficha-bloque-head h2 { font-size: 15px; font-weight: 600; margin: 0; }

.ficha-filas { display: flex; flex-direction: column; }
.ficha-fila {
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px; padding: 10px 8px; margin: 0 -8px; border-radius: var(--radius);
}
/* El separador va entre filas y no debajo de la última: el borde del bloque
   siguiente ya cierra la lista, y dos líneas juntas se leen como un error. */
.ficha-fila + .ficha-fila { border-top: 1px solid var(--border); border-radius: 0; }
.ficha-fila:hover { background: var(--bg-hover); }

.ficha-fila-quien { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.ficha-fila-nombre {
  display: flex; align-items: center; gap: 8px;
  font-size: 13px; color: var(--text); min-width: 0;
}
/* La meta es una fila de datos separados por un punto medio dibujado en CSS: sin
   esto hay que decidir en el template cuál lleva separador y cuál no, y el
   template termina con un `{% if not forloop.last %}` por cada dato opcional. */
.ficha-fila-meta {
  display: flex; align-items: center; flex-wrap: wrap; gap: 4px 8px;
  font-size: 12.5px; color: var(--text-2); min-width: 0;
}
.ficha-fila-meta > * + *::before { content: "·"; margin-right: 8px; color: var(--text-3); }

.ficha-fila-acciones { display: flex; align-items: center; gap: 4px; flex-shrink: 0; }
/* El kebab de estas filas se revela en hover como el de cualquier lista
   (ADR-0004 §2), pero acá la fila no es un `<tr>` ni un `.lin-row`, así que hay
   que repetir la regla sobre este contenedor. */
.ficha-fila .lin-kebab { opacity: 0; }
.ficha-fila:hover .lin-kebab, .ficha-fila .lin-kebab:focus-visible,
.ficha-fila .lin-kebab.is-open { opacity: 1; }

/* La papelera de una fila de archivo (AGR-304): el mismo hover-reveal que el
   kebab de arriba, y por el mismo motivo (ADR-0004 §2) — con el mouse afuera
   de la fila no hay razón para mostrar una acción destructiva al lado de
   "Bajar". Roja recién en su PROPIO hover, no en el de la fila: la fila entera
   se pone roja sería leer como que TODA la fila es la acción de borrar. */
.ficha-fila-borrar { opacity: 0; }
.ficha-fila:hover .ficha-fila-borrar, .ficha-fila-borrar:focus-visible { opacity: 1; }
.ficha-fila-borrar:hover { background: #fbebeb; color: #a32d2d; }

/* La fila se vuelve su propia confirmación (AGR-304) y no el modal genérico:
   mismo `.ficha-fila` — mismo alto, mismo padding, no salta la lista — con el
   texto a la izquierda donde antes iba el nombre del archivo. */
/* Mismo recorte que `.archivo-nombre`, y por el mismo motivo: la fila mide
   428px, los dos botones 145px y el gap 16px, así que a la pregunta le
   quedan 267px. Un nombre de teléfono ("WhatsApp Image 2026-08-28 at
   14.05.32.jpeg") mide 285px él solo -sin la pregunta alrededor-, así que sin
   recorte la frase envuelve, la fila crece de 48 a 68px y empuja a sus
   hermanas. `min-width: 0` es imprescindible: este div es un hijo flex de
   `.ficha-fila`, y sin eso el `overflow: hidden` no recorta nada -un ítem
   flex no baja de su ancho de contenido-. El `?` de cierre se pierde en el
   nombre largo -el `…` lo reemplaza-, igual que hoy se pierde la extensión de
   un nombre de archivo recortado: es el mismo trade-off que ya acepta esta
   pantalla, no uno nuevo. */
.ficha-fila-confirmar-texto {
  font-size: 13px; color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0;
}

/* El vacío explica PARA QUÉ sirve el bloque, no sólo que está vacío: es la
   primera vez que alguien lo ve y no es obvio que de acá sale el botón de
   WhatsApp de la pantalla de carga. */
.ficha-vacio {
  padding: 14px 0; font-size: 12.5px; color: var(--text-3); line-height: 1.6;
}
.ficha-vacio strong { color: var(--text-2); font-weight: 500; }

/* ── El bloque de una etapa, dentro del documento ─────────────────────────
   «Carga» vive abajo del contrato y en la misma columna. Se separa con un borde
   y no con una tarjeta flotante: es otra sección del MISMO documento, y una caja
   con sombra la haría leer como un widget pegado encima. */
.doc-etapa { margin-top: 28px; padding-top: 20px; border-top: 1px solid var(--border); }
/* La primera etapa arranca pegada al subtítulo: su borde superior estaría a un
   renglón del encabezado y se leería como un subrayado del título, no como un
   separador entre secciones. */
.doc-etapa.is-primera { margin-top: 20px; padding-top: 0; border-top: none; }
.doc-etapa-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 12px; flex-wrap: wrap; margin-bottom: 14px;
}
.doc-etapa-head h2 { font-size: 15px; font-weight: 600; margin: 0; }
.doc-etapa-quien {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 12.5px; color: var(--text-2);
}

/* El aviso de «falta tu teléfono»: es el único aviso con una acción adentro, y
   por eso necesita alinear el botón a la derecha. El resto de los `.aviso` son
   sólo texto. */
.aviso-perfil { display: flex; align-items: center; gap: 12px; }
.aviso-perfil .aviso-texto { flex: 1 1 auto; }
.aviso-perfil .btn { flex: none; }

/* ── Las etapas del negocio, como pestañas ────────────────────────────────
   Reusa `.tab-bar` / `.tab` de Configuración tal cual: es el mismo mecanismo
   (links con `?tab=`, el server manda el panel) y tiene que verse igual. Lo único
   que se suma es el dot del estado adentro del link. */
/* La barra de ETAPAS del negocio, más apretada que la de Configuración: allá son
   cinco pestañas de una palabra, acá son seis con dot, avatar y badge. Con el
   padding de 12px la barra medía 864px y se partía en dos líneas en cualquier
   notebook de 1280. Es una clase propia y no un override de `.tab` para no
   angostar las pestañas de Configuración, que no tienen el problema. */
.tab-etapas { gap: 0; flex-wrap: wrap; row-gap: 0; }
.tab-etapas .tab { padding-left: 7px; padding-right: 7px; gap: 5px; }
.tab-etapas .avatar-p { width: 16px; height: 16px; font-size: 8px; }
/* El badge de «todavía no» en una pestaña es un reloj, no texto: es lo que hace
   que la barra entre en una línea (ver `_tabs_items.html`). Sin fondo ni borde —
   a este tamaño la píldora es más ruido que el ícono. */
.badge-icono {
  display: inline-flex; align-items: center;
  padding: 0; background: none; border: none; line-height: 0;
}
.badge-icono svg { width: 13px; height: 13px; stroke: currentColor; }
.tab { display: inline-flex; align-items: center; gap: 6px; }
.tab .lin-status { width: 9px; height: 9px; }
/* El círculo de quién tiene la etapa, más chico que en una fila de lista: acá
   convive con el texto de la pestaña y a 24px la dominaba. */
.tab .avatar-p { width: 18px; height: 18px; font-size: 8.5px; letter-spacing: .2px; }

/* Las etapas que todavía no tienen pantalla. NO son links y se nota: sin cursor
   de mano, sin hover, y con el mismo badge «Próximamente» que el sidebar usa para
   las secciones que faltan. Se ven para que la barra cuente el proceso entero;
   no se abren para no prometer una pantalla que no existe.

   Igual muestran el círculo del responsable, porque a una etapa se le puede
   asignar alguien antes de que exista su panel — y saber quién va a ver la
   liquidación es justamente lo que el cierre bien armado tiene que dejar dicho. */
.tab.is-stub { color: var(--text-3); cursor: default; }
.tab.is-stub:hover { color: var(--text-3); border-bottom-color: transparent; }
/* En una pestaña va más chica que en el sidebar: son cuatro seguidas, y a tamaño
   de sidebar la barra se pasa de una línea. */
.tab .badge-soon { margin-left: 1px; font-size: 9px; padding: 0 5px; line-height: 13px; }

/* Los dos hechos de los que sale el estado, arriba del panel de la etapa. Es una
   grilla y no dos renglones sueltos para que la etiqueta y el valor caigan en la
   misma vertical que el panel de propiedades de la derecha. */
.etapa-datos {
  display: flex; flex-wrap: wrap; gap: 8px 32px;
  padding-bottom: 16px; margin-bottom: 16px;
  border-bottom: 1px solid var(--border);
}
/* En la Carga el bloque de traspaso ya no abre el panel: lo abre la lista de
   equipos, y esto arranca abajo. El aire lo pone la coordinación y no la lista,
   porque `.viajes` es la misma pieza que usa la Descarga y ahí no hay nada
   debajo de lo que separarse. */
.viajes + .etapa-datos { margin-top: 24px; }
/* `flex: none` para que no se encojan: son ítems de un contenedor flex y por
   default se achican hasta el mínimo, y ahí el selector de responsable —que
   trunca con ellipsis— mostraba «Ca...» teniendo lugar de sobra para «Carlota».
   Si no entran los dos, que envuelvan (el contenedor es `wrap`), no que se
   recorten. */
.etapa-dato { display: flex; align-items: center; gap: 10px; min-height: 28px; flex: none; }
.etapa-dato-k { font-size: 12.5px; color: var(--text-3); flex: none; }
/* `min-width: max-content` porque este span es ítem flex de `.etapa-dato` y, como
   tal, se encoge por debajo de su contenido: el selector de responsable trunca
   con ellipsis y mostraba «Ca...» con lugar de sobra para «Carlota». El ellipsis
   sigue existiendo para el caso real que lo justifica —«Santiago García
   Castellanos»— y por eso hay un tope, en vez de sacarlo. */
.etapa-dato-v { font-size: 13px; color: var(--text); flex: none; min-width: max-content; }
.etapa-datos .asignar-trigger { max-width: 240px; }
/* El motivo por el que «Cerrar descarga» está deshabilitado, al lado del
   botón — mismo tono apagado que `.etapa-dato-k`. */
.descarga-cierre-motivo { font-size: 12.5px; color: var(--text-3); }

/* El estado de la etapa, con la fecha que lo produce al lado. Van pegados a
   propósito: el estado no se elige, sale de la fecha. */
.carga-estado { display: inline-flex; align-items: center; gap: 7px; font-size: 12.5px; }
.carga-estado-txt { color: var(--text-2); }
.carga-fecha input {
  border: 1px solid transparent; background: transparent;
  border-radius: 5px; padding: 2px 4px; margin: -2px 0;
  font-family: inherit; font-size: 12.5px; color: var(--text-2);
  font-variant-numeric: tabular-nums; cursor: pointer;
}
.carga-fecha input:hover { border-color: var(--border); background: var(--bg); }
.carga-fecha input:focus { outline: none; border-color: var(--brand); background: var(--bg); }

/* Los estados de la carga. La convención es la de toda la app: HUECO = todavía
   no pasó, LLENO = pasó. Sin asignar es gris porque no está pasando nada;
   asignada es ámbar porque hay alguien esperando hacer algo; coordinada es el
   teal de la marca y va llena — el acuerdo con el vendedor ya ocurrió.

   En curso es el verde que la app ya usa para «en camino» y «enviada», y no un
   color nuevo: es literalmente lo mismo —hay un camión moviéndose— y aprender
   dos verdes para la misma idea no le sirve a nadie.

   Completada es el púrpura de «recibida» y «recibido», y va LLENO. La app tiene
   dos convenciones para lo terminal y se parten por significado: púrpura lleno
   es «la escalera llegó al final» —el cierre de `creada › enviada › presupuesto
   › confirmada › recibida`, acá abajo—, y hueco apagado es «esto ya no está
   vivo» —`cerrada` de Oportunidades, `cerrado` de Acopio—. Ésta es una escalera
   de avance con final, no una de vida y muerte, así que le toca la primera; y la
   regla de arriba manda igual: completada PASÓ, o sea que va llena.

   El gris hueco de Acopio queda descartado por algo concreto: `sin_asignar` ya
   es ese mismo gris hueco, así que los dos extremos de esta escalera se verían
   idénticos en la misma lista. */
.lin-status.dot-sin_asignar { color: var(--text-3); }
.lin-status.dot-asignada    { color: #e08a00; }
.lin-status.dot-coordinada  { color: var(--brand); background: currentColor; }
.lin-status.dot-en_curso    { color: #2e7d32; background: currentColor; }
.lin-status.dot-completada  { color: #7b1fa2; background: currentColor; }

/* Los tres botones de contacto de la etapa, en una sola fila (AGR-324,
   variante E1 del prototipo, CSS literal). El bloque de aviso al responsable
   —`.traspaso-aviso`, con su propia línea de texto— y las dos puntas sueltas
   de AGR-323 vivían separados; ahora las tres acciones —a quién le hablo—
   comparten un único renglón.

   `flex-wrap: wrap` viene de `.traspaso-partes` (AGR-323), que lo puso a
   propósito porque «en pantalla angosta dos botones con texto no siempre entran
   en el mismo renglón», y se perdió al renombrar el contenedor. La fila mientras
   tanto creció a tres botones más la línea divisoria más el ícono de copiar: el
   motivo de tenerlo es más fuerte ahora que cuando se escribió, no más débil. El
   prototipo no lo define, pero su mock tampoco vive en el ancho real de la
   pestaña. */
.carga-acciones { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
/* La línea que separa «Copiar el aviso» —acción secundaria y tenue, sin
   texto visible, sólo el ícono— de los tres botones de contacto, que son la
   acción principal de la barra. */
.carga-acciones-sep { width: 1px; height: 18px; background: var(--border); margin: 0 2px; flex: 0 0 auto; }
/* El «+» de un botón degradado va a 13px y no a los 15px que `.btn svg` le da
   al ícono de WhatsApp: es un signo, no un logo, y así queda del mismo tamaño
   que el «+» del popover de «Asignar responsable» (`.asignar-vacio svg`), que
   es el otro degradado de la misma fila. El prototipo lo baja igual, con un
   `style` inline sobre el `<svg>`. */
.carga-accion-falta svg { width: 13px; height: 13px; }

/* El tercer botón, cuando no hay responsable asignado, es el popover del panel
   de la derecha reusado tal cual (AGR-325), y ahí su disparador es un valor de
   una columna de datos —discreto a propósito, ver `.asignar-trigger`— mientras
   que acá es el tercero de tres botones. Se le da la caja de un `.btn-sm
   .btn-ghost` —los mismos 28px de alto, el mismo padding lateral y el mismo
   radio— para que la fila no tenga dos botones y una tercera cosa, que es lo
   que dibujan los estados 3.3 y 3.5 del prototipo.

   El `margin: -3px -6px` de `.asignar-trigger` allá es correcto —sangra la zona
   de hover para que el nombre quede alineado con los demás valores de la
   columna— y acá se le comía 6px al `gap: 10px` de cada lado, dejando al tercer
   botón visiblemente más pegado que los otros dos. Se va entero y no sólo el
   horizontal: el vertical estaba compensando el padding de allá, y acá la altura
   es explícita. */
.carga-acciones .asignar-trigger {
  margin: 0; height: 28px; padding: 0 8px; border-radius: var(--radius);
}
.carga-acciones .asignar-vacio { color: var(--text-2); }
.carga-acciones .asignar-trigger:hover .asignar-vacio { color: var(--text); }
.traspaso-camion {
  display: flex; align-items: center; gap: 6px;
  margin-top: 12px; font-size: 12.5px; color: var(--text-2);
}
.traspaso-camion svg { width: 15px; height: 15px; stroke: currentColor; }

/* ── Los equipos que salieron ──────────────────────────────────────────────
   El bloque de viajes dentro de la pestaña Carga. En pantalla se dice «equipo»
   —la palabra del negocio— y la tabla se llama `Viaje`; las clases siguen al
   schema, que es lo que hace que el CSS se pueda encontrar desde el modelo.

   La fila NO es una clase nueva: es `.lin-row`, la misma de todas las listas de
   la app, y ahora **sin ninguna variante**. Tenía un `cursor: default` puesto a
   propósito mientras no hubiera nada que abrir; desde que la fila entera abre la
   corrección, ese override decía lo contrario de lo que la fila hace. El
   `cursor: pointer` y el hover salen de `.lin-row`, y el kebab que aparece al
   pasar por encima, de `.lin-row:hover .lin-kebab`. */
.viajes { margin-top: 24px; }
/* Los 24px de arriba son para despegarse de lo que venga antes. En la Carga la
   lista abre el panel —los equipos son lo primero— y no hay de qué despegarse:
   ahí el aire queda de más y el que separa lo pone la coordinación de abajo.

   Va contra `.tab-panel-carga` y no contra `:first-child` a secas para no tocar
   la Descarga, donde la lista también abre el panel pero el aire de arriba es el
   que ya tenía y se deja como estaba. */
.tab-panel-carga > .viajes { margin-top: 0; }
.viajes-head {
  display: flex; align-items: center; gap: 12px;
  padding-bottom: 8px; border-bottom: 1px solid var(--border);
}
.viajes-titulo { font-size: 13px; font-weight: 600; color: var(--text); }
/* El conteo, cuando lo hay. Sin equipos no se dibuja: un cero es una promesa
   incumplida —promete una lista que no existe— repetida en cada pantalla. */
.viajes-conteo { margin-left: 5px; font-weight: 400; color: var(--text-3); }
.viajes-head .btn { margin-left: auto; }

/* `tabular-nums` en los tres números de la fila, y es lo que hace la lista.
   Con cifras de ancho variable, cinco cartas de porte y cinco pesos no se
   comparan de un vistazo: hay que leerlos uno por uno. */
.viaje-cp { font-variant-numeric: tabular-nums; font-size: 13px; }
/* Un equipo sin documento se ve COMPLETO, no roto: «Sin carta de porte» es prosa y
   no un número, así que pierde el tabular y toma el gris del dato ausente. Es
   el caso mayoritario, no el excepcional. */
.viaje-cp.is-vacia { color: var(--text-3); font-variant-numeric: normal; }
.viaje-meta { font-size: 12.5px; color: var(--text-3); font-variant-numeric: tabular-nums; }
.viaje-derecha { margin-left: auto; display: flex; align-items: center; gap: 16px; }
/* La fecha reserva su ancho por el mismo motivo que los kilos: casi ningún equipo
   la trae, y sin el `min-width` los kilos de las filas sin fecha se corren 16px a
   la derecha y las dos columnas dejan de alinearse — que es justo lo que la lista
   viene a permitir, comparar cinco equipos sin leerlos. */
.viaje-fecha { min-width: 42px; text-align: right; }
/* El `min-width` alinea la columna de kilos aunque un equipo diga «sin kilos» y
   el de al lado «30.150 kg»: sin él, la coma flota de fila en fila. */
.viaje-kg { font-size: 13px; font-variant-numeric: tabular-nums; min-width: 78px; text-align: right; }
.viaje-kg.is-vacia { color: var(--text-3); }
.viajes-vacio { padding: 26px 8px; font-size: 13px; color: var(--text-3); }

/* ── El documento del camión ───────────────────────────────────────────────
   El bloque de archivos del modal del equipo y el clip de la fila. Se apoya
   entero en `.ficha-bloque` / `.ficha-fila` / `.ficha-vacio`, que ya son la
   anatomía del bloque de contactos de la ficha de empresa. Ningún token nuevo. */

/* El clip dice que ese camión ya tiene el documento, con el conteo al lado. Sólo se
   dibuja cuando hay algo: un clip en gris para «no hay» sería el cero que esta
   misma lista decidió no dibujar en el encabezado. Toma el tamaño y el color del
   ícono de la fila (`.lin-row .lin-icon`) para que no compita con la carta de
   porte, que es lo que se lee primero. */
.viaje-clip {
  display: inline-flex; align-items: center; gap: 3px;
  font-size: 12.5px; color: var(--text-3); font-variant-numeric: tabular-nums;
}
.viaje-clip svg { width: 14px; height: 14px; stroke: currentColor; }

/* `.ficha-bloque` nace para una ficha, que respira: 28px de margen y 20 de
   padding. Adentro de un modal de 460 eso es medio bloque de aire antes del
   título, y empuja el pie fuera de la pantalla en un notebook. La línea de
   arriba se queda —es lo que separa el documento de los campos del viaje— y el aire
   se achica a la mitad. */
.ficha-bloque-modal { margin-top: 18px; padding-top: 14px; }

/* En el PASO DEL DOCUMENTO (AGR-298) el bloque no separa nada de nada: es lo único
   que hay en la pantalla, ya adentro de su sección y pegado a su título. La
   línea de arriba y el aire existen para despegarlo de los campos del viaje, y
   ahí no hay campos de los que despegarse — dejarlos dibuja un separador entre
   un título y su propio contenido. */
.ficha-bloque-documento { margin-top: 0; padding-top: 0; border-top: 0; }

/* El control de adjuntar es UNO: el botón. El input de archivo nativo —que mide
   ~240px con su «Sin archivos seleccionados» y no entra al lado del título de un
   modal de 460— ya no se dibuja, porque tampoco hay nada que mostrar: elegir el
   archivo lo sube, así que el estado «elegido pero no subido» no existe.

   Se esconde con el mismo `clip-path` que `.grano input` y por el mismo motivo:
   sigue siendo el input que el teclado alcanza y el que los lectores de pantalla
   anuncian. Con `display:none` o con tamaño cero se cae del orden de tabulación
   y del árbol de accesibilidad, y el único control quedaría siendo un botón que
   no es el que sube. */
.archivo-elegir { display: flex; align-items: center; }
.archivo-input {
  position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap;
}
/* Y el foco se ve sobre el botón, que es lo único dibujado: sin esto, quien
   navega con el teclado tabula hasta un elemento invisible y el anillo no
   aparece en ningún lado. Mismo recurso que `.grano input:focus-visible`. */
.archivo-input:focus-visible + .btn { outline: 2px solid var(--brand); outline-offset: 2px; }

/* El ícono del archivo, del mismo tamaño y del mismo gris que el del camión en
   la lista de equipos (`.lin-row .lin-icon`): son la misma clase de dato —qué es
   esta fila— y verlos con dos pesos distintos en la misma pantalla se lee como
   un descuido. */
.archivo-icono {
  width: 16px; height: 16px; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center; color: var(--text-3);
}
.archivo-icono svg { width: 15px; height: 15px; stroke: currentColor; }
/* El nombre viene del teléfono y puede ser `WhatsApp Image 2026-08-30 at
   11.42.07 (1).jpeg`: sin el corte, una fila estira el modal y desalinea el
   resto. Se corta al final, que es donde está la parte que no identifica nada. */
.archivo-nombre { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* El rechazo del archivo es el MISMO rojo del error de un campo (`.field .err`)
   porque es la misma clase de hecho: algo que se mandó y no entró. La regla se
   repite en vez de reusarse porque aquélla cuelga de un `.field`, y en este
   bloque no hay ninguno — no hay label, no hay input con borde que teñir. */
.archivo-err {
  color: var(--danger); background: rgba(163, 45, 45, .06);
  border-radius: 5px; padding: 6px 8px; font-size: 12px; margin-bottom: 8px;
}

/* El estado de la lectura del documento (AGR-299, AGR-301): leyendo, leyó, falló,
   no está configurada, o trae varios camiones juntos. Una sola caja para los
   cinco, porque los cinco contestan la misma pregunta —qué pasó con el
   archivo que acabo de subir— y darles cinco formas distintas obligaría a
   leer la forma antes que el texto.

   Lo que cambia entre uno y otro es sólo el COLOR del ícono, no la caja: el
   fondo hundido es el mismo, y así el bloque no salta de tamaño cuando el
   estado cambia solo debajo del cursor. */
.lectura {
  display: flex; align-items: center; gap: 8px; margin-top: 10px;
  padding: 8px 10px; border-radius: 7px;
  background: var(--bg-sunken); color: var(--text-2); font-size: 12.5px;
}
.lectura svg { width: 15px; height: 15px; flex-shrink: 0; stroke: currentColor; }
.lectura-listo svg { color: var(--brand); }
/* El mismo ámbar del `.campo-aviso`, y por el mismo motivo: los tres dicen
   «algo no cargó y se puede seguir igual». En rojo se leerían como un
   rechazo, y acá no hay nada rechazado — `lectura-falla` guardó el archivo
   igual, y `lectura-varios` (AGR-301) ni siquiera falló: leyó bien y avisa
   que hay que cargar a mano, lo mismo que dice `lectura-off`. */
.lectura-falla svg, .lectura-off svg, .lectura-varios svg { color: #a06a12; }
/* El botón de reintentar se va a la derecha, que es donde vive la acción de una
   fila en toda la app (`.ficha-fila-acciones`). */
.lectura .btn { margin-left: auto; flex-shrink: 0; }

/* El giro mientras el modelo contesta. Reusa el `ubi-giro` del buscador de
   ubicaciones —misma espera, misma respuesta visual— en vez de declarar un
   segundo keyframe que dice lo mismo. A diferencia de aquél éste NO cuelga de
   `.htmx-request`: acá el elemento entero existe sólo mientras se está leyendo,
   así que gira siempre que está en pantalla. */
.lectura-giro {
  width: 13px; height: 13px; flex-shrink: 0; border-radius: 50%;
  border: 1.5px solid var(--border-strong); border-top-color: var(--brand);
  animation: ubi-giro .6s linear infinite;
}

/* ── Lo que agrega la mitad de DESTINO ─────────────────────────────────────
   La pestaña Descarga lista las mismas filas con las columnas del otro lado, así
   que reusa todo lo de arriba y suma cinco reglas. Ningún token nuevo. */

/* El avance en toneladas, a la derecha del encabezado. Ocupa el lugar que en la
   Carga tiene el botón «Nuevo equipo»: la Descarga no crea, completa. */
.viajes-avance {
  margin-left: auto; display: flex; align-items: center; gap: 9px;
  font-size: 12.5px; color: var(--text-2); font-variant-numeric: tabular-nums;
}
.viajes-avance b { color: var(--text); font-weight: 600; }
.viajes-barra { width: 84px; height: 4px; border-radius: 2px; background: var(--bg-sunken); overflow: hidden; }
/* La excepción anotada en el encabezado de este archivo: el único teal que mide
   algo en vez de marcar una acción. El ancho lo pone el server —recortado a 100
   por `_avance_barra()`— porque es un dato, no una decisión de estilo. */
.viajes-barra span { display: block; height: 100%; background: var(--brand); }
/* La mitad de origen adentro de la fila: contra qué se compara el ticket que se
   está cargando. Un punto más chica que el resto de la fila y en el gris del dato
   de fondo, para que se lea cuando se la busca y no compita con los kilos. */
.viaje-origen { font-size: 12px; color: var(--text-3); font-variant-numeric: tabular-nums; }
/* La humedad reserva su ancho por lo mismo que la fecha y los kilos: sin el
   `min-width`, un «sin dato» corre la columna de los kilos de esa fila. */
.viaje-hum { font-size: 12.5px; color: var(--text-2); font-variant-numeric: tabular-nums; min-width: 46px; text-align: right; }
.viaje-hum.is-vacia { color: var(--text-3); }
/* La marca de la cola de trabajo. Es gris y no un badge de color a propósito: un
   equipo sin ticket todavía no es un problema, es lo normal el día que salió. */
.viaje-falta { font-size: 12.5px; color: var(--text-3); }
.viajes-vacio-accion { margin-top: 9px; }

/* El neto a liquidar, al pie de «Calidad» en el modal del ticket. Es lo único que
   la app CALCULA en toda la pantalla de Descarga, así que no se ve como un campo:
   ocupa el ancho entero, no tiene caja de input y el número va en negrita.

   La cuenta —«30.040 − 410»— va en gris y a la izquierda del resultado, que es el
   orden en que se lee: primero de dónde sale, después cuánto da. Que esté a la
   vista es lo que permite controlarla de un vistazo en vez de tener que confiar.

   `tabular-nums` por lo mismo que en las listas: los kilos cambian mientras se
   tipea, y sin ancho fijo de dígito el número salta a cada tecla. */
.neto-liquidar {
  display: flex; align-items: baseline; gap: 8px;
  margin-top: 10px; padding-top: 9px; border-top: 1px solid var(--border);
}
.neto-liquidar-label { font-size: 12.5px; color: var(--text-2); }
.neto-liquidar-cuenta {
  margin-left: auto; font-size: 12.5px; color: var(--text-3);
  font-variant-numeric: tabular-nums;
}
.neto-liquidar-total {
  font-size: 13.5px; font-weight: 600; font-variant-numeric: tabular-nums;
}

/* El desglose de la Cámara, al pie de «Calidad» y arriba del neto a liquidar.
   Es la misma familia que `.neto-liquidar` —ancho entero, sin caja de input,
   `tabular-nums` porque los números cambian a cada tecla— pero son tres renglones
   y no uno, así que en vez de un flex es una grilla de tres columnas: el
   concepto a la izquierda, el porcentaje y los kilos alineados a la derecha.

   Las mismas filas que muestra la calculadora de la Cámara, que es lo que este
   pie reemplaza. Los dos conceptos van separados porque discutir con la planta
   pide poder señalar cuál de los dos no cierra.

   Va ARRIBA del neto a liquidar y no abajo porque se lee en ese orden: de dónde
   salió la merma, y recién después los kilos por los que se paga. El borde
   superior lo trae este pie y el del neto queda como separador entre los dos. */
.merma-camara {
  margin-top: 10px; padding-top: 9px; border-top: 1px solid var(--border);
}
.merma-camara-titulo { font-size: 12.5px; color: var(--text-2); margin-bottom: 3px; }
.merma-camara-fila {
  display: grid; grid-template-columns: 1fr auto auto; gap: 8px;
  align-items: baseline; font-size: 12.5px; color: var(--text-3);
}
.merma-camara-concepto { color: var(--text-2); }
.merma-camara-pct, .merma-camara-kg { font-variant-numeric: tabular-nums; }
.merma-camara-kg { min-width: 58px; text-align: right; }
/* El total no repite el porcentaje —sumarlos no significa nada— así que su celda
   de kilos se salta la columna del medio y queda alineada con las de arriba. */
.merma-camara-total { margin-top: 2px; }
.merma-camara-total .merma-camara-concepto,
.merma-camara-total .merma-camara-kg { color: var(--text); font-weight: 600; }
.merma-camara-total .merma-camara-kg { grid-column: 3; }

/* La frase que reemplaza a los tres ceros cuando la humedad no llegó al umbral
   de recibo. Va en el tono del resto del pie y no en ámbar: no es un aviso, es el
   resultado — la cuenta corrió y no corresponde descontar nada. */
.merma-camara-tolerancia { font-size: 12.5px; color: var(--text-2); }

/* El aviso de un campo, que **no es un error**: `.err` es rojo y significa que no
   se puede guardar; esto es ámbar y significa que se puede guardar igual. Que se
   vean distinto es la mitad del mensaje — el caso que lo estrena es el bruto
   menos la tara que no da el neto, donde un documento mal transcripto tiene que poder
   entrar mientras se averigua cuál de los tres números está mal.

   El ámbar NO es el `#e08a00` del dot de «Asignada», y la diferencia tiene motivo:
   ese valor está elegido para un círculo de 7px sobre fondo claro y como texto de
   12px no llega al contraste mínimo. Éste es el mismo ámbar oscurecido hasta que
   se lee. Son dos usos distintos del mismo concepto —«esto espera algo»— y por eso
   son dos valores; si algún día se declara un token de ámbar, van los dos juntos. */
.campo-aviso {
  margin-top: 5px; font-size: 12px; color: #a06a12;
  display: flex; gap: 5px; align-items: flex-start;
}
.campo-aviso svg {
  width: 13px; height: 13px; stroke: currentColor; flex: none; margin-top: 2px;
}

/* Un teléfono es un valor atómico: se corta entero a la línea siguiente o no se
   corta, pero nunca por la mitad. Sin esto «+54 9 351 513-4266» se parte en
   cuatro renglones apenas la tabla se angosta, y deja de leerse como un número. */
.cell-tel { white-space: nowrap; }

/* El hueco del responsable en la lista de Cargas. `_avatar.html` no dibuja nada
   sin email (ADR-0011 rechazó el fantasma para el AUTOR, que es inmutable), pero
   en Cargas el responsable vacío es justo lo que hay que ver: sin un círculo
   punteado, las filas sin asignar se pierden entre las que tienen una inicial.
   Del mismo tamaño que `.avatar-p` para que la columna no se mueva. */
.avatar-vacio {
  display: inline-block; width: 24px; height: 24px;
  border-radius: 50%; border: 1px dashed var(--border-strong);
}

/* El conteo al lado de cada opción del selector de Cargas: es lo que lo vuelve
   un tablero y no sólo un filtro. `tabular-nums` para que las cifras se alineen
   de arriba abajo en el menú abierto. */
.view-menu-conteo {
  margin-left: auto; padding-left: 10px;
  font-size: 11.5px; color: var(--text-3); font-variant-numeric: tabular-nums;
}
.view-menu-opt.is-active .view-menu-conteo { color: var(--text-2); }

/* ── Asignar responsable ──────────────────────────────────────────────────
   El control de asignación reusa el popover del kebab (`.acciones-*`) y sólo
   aporta lo que no existía: un disparador que se ve como un VALOR y no como un
   botón, y el estado "sin asignar", que acá sí se dibuja.

   Por qué el disparador es discreto: vive adentro de un `.prop-value`, entre
   Vendedor y CUIT, y si tuviera borde y fondo sería el único botón de una
   columna de datos — se leería como la acción principal de la pantalla, cuando
   la acción principal es bajar el PDF. Se revela en hover, que es el mismo
   gesto con el que la app revela el checkbox y el kebab de una fila. */
.asignar-trigger {
  display: inline-flex; align-items: center; gap: 7px;
  margin: -3px -6px; padding: 3px 6px;
  border-radius: 5px; cursor: pointer;
  max-width: 100%; min-width: 0;
}
.asignar-trigger:hover { background: var(--bg-hover); }
/* `min-width: 0` es lo que habilita el ellipsis: sin él un ítem flex se niega
   a encogerse por debajo de su contenido y el nombre desborda en vez de cortarse. */
.asignar-nombre { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* El hueco SÍ se dibuja, al revés que el círculo del autor (ADR-0011 rechazó el
   fantasma ahí porque el autor es inmutable). Acá el hueco es la invitación. */
.asignar-vacio {
  display: inline-flex; align-items: center; gap: 4px;
  color: var(--text-3); font-size: 12.5px;
}
.asignar-vacio svg { width: 13px; height: 13px; stroke: currentColor; }
.asignar-trigger:hover .asignar-vacio { color: var(--text-2); }

/* Hereda el `right: 0` de `.acciones-pop`, o sea que crece hacia la IZQUIERDA
   desde el borde derecho del disparador. Es lo que corresponde acá y no una
   copia por inercia: el panel de propiedades es la última columna de la pantalla,
   así que un popover anclado a la izquierda del disparador crece hacia afuera del
   viewport y se corta. Con `max-width` además, porque `.acciones-item` es
   `nowrap` y un nombre largo («Santiago García Castellanos») estiraba la caja
   hasta salirse igual. */
.asignar-pop { min-width: 210px; max-width: 260px; }
.acciones-item.is-active { color: var(--text); font-weight: 500; }
.asignar-check { margin-left: auto; display: flex; color: var(--brand); }
.asignar-check svg { width: 14px; height: 14px; }
.asignar-nadie {
  padding: 8px; font-size: 12.5px; color: var(--text-3); line-height: 1.5;
  max-width: 220px;
}
.asignar-nadie a { color: var(--brand); }

/* La misma lista, en el modal de la fila. Es más alta y muestra el teléfono:
   acá hay lugar, y saber si esa persona tiene WhatsApp cargado es parte de la
   decisión de a quién asignarle. */
.asignar-sub { margin: 0 0 12px; }
.asignar-lista { display: flex; flex-direction: column; gap: 2px; }
.asignar-opcion {
  display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 8px; border: none; background: transparent;
  border-radius: var(--radius); text-align: left; cursor: pointer;
  font-size: 13px; color: var(--text); font-family: inherit;
}
.asignar-opcion:hover { background: var(--bg-hover); }
.asignar-opcion.is-active { background: var(--brand-soft); }
.asignar-opcion svg { width: 15px; height: 15px; stroke: currentColor; flex-shrink: 0; }
.asignar-opcion-txt { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.asignar-tel { font-size: 11.5px; color: var(--text-3); }
.asignar-opcion.is-quitar { color: var(--text-2); margin-top: 4px; }
.asignar-opcion.is-quitar:hover { color: var(--danger); }

/* ── Configuración › Avisos: el estado de cada canal ─────────────────────────
   Filas y no cajas, como el resto de Configuración. Un canal apagado no se
   esconde ni se pinta de rojo: se apaga y dice qué variable falta — no es un
   error, es que en este entorno no está configurado. */
.canales { display: flex; flex-direction: column; gap: 2px; }
.canal {
  display: flex; align-items: center; gap: 12px; padding: 12px 0;
  font-size: 13px;
}
.canal + .canal { border-top: 1px solid var(--border); }
.canal-icono {
  display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; flex-shrink: 0;
  border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--bg-sunken); color: var(--text-2);
}
.canal-icono svg { width: 15px; height: 15px; stroke: currentColor; }
.canal.is-off .canal-icono { color: var(--text-3); }
.canal-txt { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.canal-nombre { color: var(--text); font-weight: 500; }
.canal-detalle { font-size: 12px; color: var(--text-2); }
.canal-detalle code {
  font-size: 11.5px; padding: 1px 4px;
  background: var(--bg-sunken); border: 1px solid var(--border);
  border-radius: 4px;
}
/* El desvío en ámbar y con triángulo: significa que NADA llega a su
   destinatario real, y es lo que alguien deja prendido sin querer. */
.canal-desvio {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 12px; color: #e08a00;
}
.canal-desvio svg { width: 13px; height: 13px; stroke: currentColor; }
.canal-estado {
  margin-left: auto; flex-shrink: 0;
  font-size: 12px; color: var(--text-2); white-space: nowrap;
}

/* El resumen de envíos, que **es el filtro**: cada caja recorta la lista a su
   estado. Cajas y no píldoras porque el número es el dato —se lee de lejos— y una
   píldora lo dejaría al tamaño de su etiqueta. */
.resumen { display: flex; gap: 8px; margin-bottom: 16px; flex-wrap: wrap; }
.resumen-caja {
  display: flex; flex-direction: column; gap: 1px;
  min-width: 104px; padding: 9px 12px;
  border: 1px solid var(--border); border-radius: var(--radius-lg);
  color: var(--text-2);
}
.resumen-caja:hover { border-color: var(--border-strong); }
/* El elegido se marca con el borde y no con un fondo: son cuatro cajas en fila y
   un relleno de color convierte el resumen en una barra de botones. */
.resumen-caja.is-activo { border-color: var(--text); color: var(--text); }
.resumen-n { font-size: 19px; font-weight: 500; line-height: 1.15; color: var(--text); }
.resumen-k { font-size: 11.5px; }
/* El número toma el tono de lo que cuenta, y sólo el número: la caja entera de
   color haría que «2 fallaron» grite más fuerte que el resto de la pantalla
   incluso cuando es cero. */
.resumen-caja.tono-malo .resumen-n { color: var(--danger); }
.resumen-caja.tono-tibio .resumen-n { color: #e08a00; }
.resumen-caja.tono-bien .resumen-n { color: var(--brand); }

/* La sección de referencia (el catálogo de eventos), colapsada. Contesta «¿este
   evento existe?», que se pregunta una vez cada tanto — abierta y en el medio era
   lo que más lugar ocupaba para lo que menos se mira. */
.ajustes-ref > .sub { margin: 4px 0 12px; }
summary.ajustes-ref-head {
  display: flex; align-items: center; gap: 6px;
  padding: 4px 0; cursor: pointer; list-style: none;
  font-size: 14px; font-weight: 500; color: var(--text);
}
summary.ajustes-ref-head::-webkit-details-marker { display: none; }
summary.ajustes-ref-head:hover { color: var(--text-2); }
details.ajustes-ref[open] > summary .props-grupo-caret { transform: rotate(90deg); }

/* ── El aviso: «¿le avisamos?» y el rastro de lo que salió ───────────────────
   El modal muestra el texto ENTERO y sin maquillar —los asteriscos de la
   negrita de WhatsApp incluidos—, porque el motivo de que este aviso lo dispare
   una persona y no el sistema es justamente que quiere leer lo que se le va a
   mandar a otro. Un preview estilizado sería una versión, no el mensaje. */
.aviso-preview {
  margin: 0 0 14px; padding: 12px 14px;
  background: var(--bg-sunken); border: 1px solid var(--border);
  border-radius: var(--radius);
  font-family: var(--font); font-size: 12.5px; line-height: 1.55;
  color: var(--text-2); white-space: pre-wrap; word-break: break-word;
  max-height: 260px; overflow-y: auto;
}

.aviso-canales { display: flex; flex-direction: column; gap: 2px; }
.aviso-canal {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 8px; border-radius: var(--radius); cursor: pointer; font-size: 13px;
}
.aviso-canal:hover { background: var(--bg-hover); }
.aviso-canal input { margin-top: 2px; accent-color: var(--brand); }
/* Un canal que no se puede usar **se muestra igual**, apagado y con el motivo al
   lado. Esconderlo dejaría al que asigna sin saber que a esa persona no se le
   puede avisar por WhatsApp — que es justo el dato que lo haría ir a cargarle el
   teléfono. */
.aviso-canal.is-off { cursor: default; opacity: .6; }
.aviso-canal.is-off:hover { background: transparent; }
.aviso-canal-txt { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.aviso-canal-nombre { color: var(--text); }
.aviso-canal-dest { font-size: 11.5px; color: var(--text-3); }
.aviso-canal-motivo { font-size: 11.5px; color: #e08a00; }

/* ── La bandeja de avisos ────────────────────────────────────────────────────
   El badge del sidebar. Sólo existe con algo sin leer: un contador en cero es
   una promesa incumplida en cada pantalla. */
.nav-badge {
  margin-left: auto; flex-shrink: 0;
  min-width: 18px; padding: 0 5px;
  border-radius: 9px; background: var(--brand); color: var(--text-inv);
  font-size: 10.5px; font-weight: 600; line-height: 18px; text-align: center;
}

/* La fila de la bandeja. Reusa `.lin-row` —es una lista más de la app— y sólo
   agrega el punto de leído/sin leer y las dos líneas de texto. */
.bandeja-fila { display: flex; align-items: center; gap: 12px; padding: 11px 12px; }
/* Hueco = leído, lleno = sin leer. La misma convención que los dots de estado de
   Órdenes y Acopios, que es lo que hace que una lista se lea de un vistazo sin
   leer las etiquetas. */
.bandeja-dot {
  width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0;
  border: 1.5px solid var(--border-strong);
}
.bandeja-fila.is-nuevo .bandeja-dot { background: var(--brand); border-color: var(--brand); }
.bandeja-txt { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.bandeja-titulo {
  font-size: 13px; color: var(--text-2);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Lo NO leído pesa más. Es la única diferencia visual entre las dos mitades de
   la lista, y alcanza: sin ella hay que leer los puntos uno por uno. */
.bandeja-fila.is-nuevo .bandeja-titulo { color: var(--text); font-weight: 500; }
.bandeja-sub {
  font-size: 12px; color: var(--text-3);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.bandeja-cuando {
  margin-left: auto; flex-shrink: 0;
  font-size: 12px; color: var(--text-3); white-space: nowrap;
}

/* La actividad, al fondo del panel de la etapa y en cuerpo chico: es el registro
   de lo que ya pasó, no algo con lo que se trabaje. Mismo lugar y mismo peso que
   el log de actividad de un issue de Linear.

   Una tira vertical con línea a la izquierda y no una tabla — una tabla promete
   que se va a leer entera. */
.actividad {
  margin-top: 28px; padding-top: 14px;
  border-top: 1px solid var(--border);
}
.actividad-titulo {
  font-size: 11px; font-weight: 500; letter-spacing: .04em; text-transform: uppercase;
  color: var(--text-3); margin-bottom: 8px;
}
.actividad-fila {
  display: flex; align-items: center; gap: 8px;
  padding: 4px 0 4px 10px;
  border-left: 1px solid var(--border);
  font-size: 12px; color: var(--text-3);
}
.actividad-icono { display: flex; flex-shrink: 0; }
.actividad-icono svg { width: 12px; height: 12px; stroke: currentColor; }
.actividad-txt { min-width: 0; }
.actividad-txt strong { color: var(--text-2); font-weight: 500; }
/* La nota es el «por qué» —el motivo, el desvío— y va un tono más apagada que la
   frase: se lee cuando hace falta, no de entrada. */
.actividad-nota { color: var(--text-3); }
.actividad-cuando { margin-left: auto; flex-shrink: 0; white-space: nowrap; }
/* Lo que no salió NO se apaga: es el único renglón de este bloque que pide algo.
   Rojo cuando falló, ámbar cuando fue el modo consola —que no es un error, es que
   no hay proveedor— y en los dos casos el punto es el mismo: esa persona no se
   enteró. */
.actividad-fila.is-fallido { color: var(--danger); }
.actividad-fila.is-fallido strong,
.actividad-fila.is-tibio strong { color: inherit; }
.actividad-fila.is-tibio { color: #e08a00; }
.actividad-reintentar {
  flex-shrink: 0; border: none; background: transparent; cursor: pointer;
  padding: 2px 6px; border-radius: 5px;
  font-family: inherit; font-size: 12px; color: inherit;
  text-decoration: underline; text-underline-offset: 2px;
}
.actividad-reintentar:hover { background: var(--bg-hover); }

/* El bloque de responsables del panel de la derecha. La etiqueta es el nombre de
   la etapa, así que es más larga que las de Propiedades («Procedencia», «CUIT») y
   necesita su propio ancho — con los 70px de `.prop-label`, «Liquidación» se
   partía en dos líneas. */
#negocio-responsables { display: flex; flex-direction: column; gap: 4px; }
#negocio-responsables .prop-label { flex: 0 0 88px; }

/* Colapsable, con el mismo caret que los grupos de la lista (ADR-0004 §4), y
   **cerrado por default**: quién tiene cada etapa ya se ve en la barra de
   pestañas, así que abierto repetía esa información en media columna. El estado
   vive en el `<details>`, que queda afuera del OOB — ver `_responsables.html`.

   Misma caja que `.props-box`: son dos bloques del mismo panel y tienen que
   verse como hermanos, no como un bloque y su encabezado. */
.props-grupo {
  margin-bottom: 10px;
  padding: 8px 14px;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
}
/* Abierto necesita el aire de abajo; cerrado es una sola línea y con el mismo
   padding queda una tira demasiado alta. */
details.props-grupo[open] { padding-bottom: 12px; }
summary.props-grupo-head {
  display: flex; align-items: center; gap: 6px;
  padding: 2px 0;
  cursor: pointer; list-style: none;
}
summary.props-grupo-head::-webkit-details-marker { display: none; }
/* El caret va INLINE y no colgando hacia el margen. Colgaba para alinear el
   título con «Propiedades» cuando los dos vivían en la misma caja; ahora son dos
   cajas con su propio padding, y un caret que se sale del borde se lee como un
   error de maquetado. */
.props-grupo-caret {
  display: flex; color: var(--text-3); flex-shrink: 0;
  transition: transform .12s ease;
}
.props-grupo-caret svg { width: 14px; height: 14px; stroke: currentColor; }
details.props-grupo[open] > summary .props-grupo-caret { transform: rotate(90deg); }
.props-grupo-head .props-group-title { margin-bottom: 0; padding-bottom: 0; }
details.props-grupo[open] #negocio-responsables { margin-top: 8px; }
.props-grupo-head:hover .props-group-title { color: var(--text-2); }
/* El disparador ocupa la fila entera: es el área clickeable de una acción que se
   hace seguido, y media fila muerta al lado del nombre es medio blanco donde el
   click no hace nada. */
#negocio-responsables .asignar-trigger { width: 100%; }

/* El mismo popover, pero flotando: es el menú de la fila (uno solo para toda la
   página, en base.html), que abren el kebab y el click derecho. El JS lo ubica
   con left/top, así que no cuelga de ningún ancestro. */
.acciones-pop.acciones-flotante {
  position: fixed; top: 0; left: 0; right: auto; z-index: var(--z-menu-fila); min-width: 200px;
}

/* ── Tabs (estilo Linear; la tab abierta la decide el server, vive en ?tab=) ── */
.tab-bar {
  display: flex; align-items: center; gap: 4px;
  border-bottom: 1px solid var(--border);
  margin-bottom: 20px;
}
.tab {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 7px 12px; margin-bottom: -1px;
  font-size: 13.5px; font-weight: 500; color: var(--text-2);
  border-bottom: 2px solid transparent;
  cursor: pointer; white-space: nowrap;
}
.tab:hover { color: var(--text); }
.tab-count {
  font-size: 11px; font-weight: 500; color: var(--text-3);
  background: var(--bg-sunken); border: 1px solid var(--border);
  border-radius: 10px; padding: 0 6px; line-height: 16px;
}
/* El server renderiza SOLO el panel de la tab abierta (y sólo en Resumen, el
   panel de propiedades): no hay nada que esconder con CSS. */
.tab.is-active { color: var(--text); border-bottom-color: var(--text); }

/* Aviso en línea (no es un toast: se queda). Hoy lo usa el resumen del acopio
   cuando el saldo en plata no se pudo calcular entero porque hay materiales sin
   precio congelado (MGA-92). Tono ámbar: no es un error, es un dato faltante. */
.aviso {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 10px 12px; margin-bottom: 16px;
  border: 1px solid var(--border); border-radius: var(--radius);
  font-size: 13px; line-height: 1.5;
}
.aviso-icon { display: flex; flex-shrink: 0; margin-top: 1px; }
.aviso-icon svg { width: 16px; height: 16px; stroke: currentColor; }
.aviso-warn { background: #fdf6e7; color: #8a5d10; border-color: #f0dcae; }
.aviso-error { background: #fdecea; color: #8c2f28; border-color: #f0c4c4; }
.aviso-texto strong { font-weight: 600; }

/* Confirmación DENTRO de un aviso (aporte cross-cliente, MGA-85): el checkbox
   que habilita el botón de crear. Va abajo del texto, separado, para que se lea
   como "esto hay que tildarlo" y no como una nota más. */
.aviso-check {
  display: flex; align-items: flex-start; gap: 8px;
  margin-top: 10px; font-weight: 600; cursor: pointer;
}
.aviso-check input { margin-top: 2px; flex-shrink: 0; }

/* El aviso de pedido repetido del alta (MGA-227): un renglón por label con
   antecedentes y, debajo, los pedidos de esa obra que ya lo llevan. Reusa el
   `.aviso .aviso-warn` de arriba —el ámbar de "no es un error, es un dato que
   hay que mirar", que es exactamente el tono de este aviso, que no bloquea— y
   sólo agrega la lista, que es lo único que ese aviso todavía no tenía.

   Sin `flex` en el <li> a propósito: cada renglón es una frase con partes
   heterogéneas (link, estado, fecha, círculo + nombre de la persona) y el flujo
   inline las envuelve solo cuando el modal se angosta; con flex, cada corrida
   de texto se vuelve un ítem y los `·` se despegan de lo que separan. */
.aviso-antecedente { margin-bottom: 8px; }
.aviso-pedidos { list-style: none; margin: 3px 0 0; padding: 0; }
.aviso-pedidos li { padding: 1px 0; line-height: 1.6; }
.aviso-pedidos a { font-weight: 600; text-decoration: underline; }
/* El resto contado ("y 7 más") es el mismo aviso, un tono más bajo: no es un
   pedido más de la lista, es cuántos quedaron afuera del tope. */
.aviso-mas { opacity: .75; }

/* ── Resumen: hero con saldo grande + barra de progreso ───────────────────── */
.resumen-hero {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: 32px; flex-wrap: wrap;
  padding: 20px; margin-bottom: 24px;
  background: var(--bg-sunken); border: 1px solid var(--border);
  border-radius: var(--radius-lg);
}
.resumen-hero-num { display: flex; flex-direction: column; gap: 2px; }
.resumen-hero-label { font-size: 12px; color: var(--text-2); }
.resumen-hero-value {
  font-size: 30px; font-weight: 600; letter-spacing: -.02em;
  font-variant-numeric: tabular-nums; line-height: 1.1;
}
.resumen-hero-sub { font-size: 12.5px; color: var(--text-3); }
.resumen-bar { flex: 1 1 220px; min-width: 200px; }
/* El riel: la pista más, si el acopio está sobregirado, el tramo que se pasó.
   Existe para que el exceso pueda dibujarse FUERA de la pista sin escaparse del
   layout: la pista se encoge, el rojo la sigue de largo. MGA-91. */
.resumen-bar-riel { display: flex; align-items: center; }
.resumen-bar-track {
  position: relative;
  height: 10px; border-radius: 999px;
  background: var(--bg-active); overflow: hidden;
  /* Crece 100 contra el flex-grow del tramo sobregirado (su % de exceso sobre
     el total): así la pista sigue midiendo el acopio entero y el rojo que sale
     de ella está a escala (20% de sobregiro = un quinto de la pista). */
  flex: 100 0 0; min-width: 0;
}
/* Tramo entregado: sólido, anclado a la izquierda. */
.resumen-bar-fill {
  position: absolute; top: 0; left: 0; height: 100%;
  background: var(--text); transition: width .2s ease;
}
/* Si hay algo (>0) pero el % es minúsculo, garantizamos un sliver visible. */
.resumen-bar-fill.has-value { min-width: 3px; }
/* Tramo comprometido (en vuelo): rayado, apilado a continuación del entregado. */
.resumen-bar-fill.is-comprometido {
  background: var(--text-3);
  background-image: repeating-linear-gradient(
    -45deg,
    rgba(255, 255, 255, .45) 0, rgba(255, 255, 255, .45) 3px,
    transparent 3px, transparent 6px
  );
}
/* Tramo cedido (plata que salió sin material): gris claro SÓLIDO, y va último.
   Sólido porque ya ocurrió (la regla: rayado = todavía en vuelo). El gris es un
   punto más oscuro que un gris claro puro porque, al ir último, limita con el
   tramo vacío de la barra y tiene que despegarse de él. MGA-84. */
.resumen-bar-fill.is-cedido { background: #bcbec2; }
.resumen-bar-legend {
  display: flex; flex-wrap: wrap; gap: 14px;
  margin-top: 8px; font-size: 12px; color: var(--text-2);
  font-variant-numeric: tabular-nums;
}
.legend-item { display: inline-flex; align-items: center; gap: 6px; }
.legend-swatch {
  width: 10px; height: 10px; border-radius: 3px; flex-shrink: 0;
  background: var(--text);
}
.legend-swatch.is-comprometido {
  background: var(--text-3);
  background-image: repeating-linear-gradient(
    -45deg,
    rgba(255, 255, 255, .45) 0, rgba(255, 255, 255, .45) 2px,
    transparent 2px, transparent 4px
  );
}
.legend-swatch.is-cedido { background: #bcbec2; }

/* ── Sobregiro: lo único rojo del resumen (MGA-91) ──────────────────────────
   El design system es monocromático, y sigue siéndolo: el rojo acá es
   SEMÁNTICO ("esto está mal"), no decorativo. Por eso lo usa el sobregiro y no
   el Cedido, que es una operación normal. Es el --danger de siempre, el único
   rojo de la app: un segundo rojo sería un acento. */
.resumen-bar-exceso {
  height: 10px;
  /* El flex-grow lo pone el template: el % de exceso sobre el total, contra el
     100 de la pista. Se topa el ancho DIBUJADO, no el número: un acopio
     consumido al 400% no puede empujar el layout, pero la leyenda igual dice
     "400,0% del acopio". */
  max-width: 30%;
  /* Sale de la pista y sigue: pegado a ella (sin redondeo a la izquierda) y
     cerrado a la derecha, para que se lea como desborde y no como otro tramo. */
  border-radius: 0 999px 999px 0;
  background: var(--danger);
}
.legend-item.is-sobregiro { color: var(--danger); font-weight: 500; }
.legend-swatch.is-sobregiro { background: var(--danger); }
.resumen-hero-value.is-sobregiro,
.resumen-hero-sub.is-sobregiro,
.cell-meta.is-sobregiro { color: var(--danger); }

/* ── Modal (rediseñado al estilo Linear) ─────────────────────────────────────
   Tres cambios sobre el modal que venía de MGA, y los tres van en la misma
   dirección: que el marco pese menos que el contenido.

   1. **El encabezado es una cápsula de contexto, no un título.** Linear no
      escribe "Nuevo cierre" en 15px semibold arriba de todo: escribe
      «Cierres › Nuevo cierre» en el mismo cuerpo que el resto, con el ícono de
      la sección adelante. Se sabe dónde estás sin que el marco grite.
   2. **Los campos no son cajas.** El borde permanente de un input multiplica las
      líneas de la pantalla por la cantidad de campos: doce campos son doce cajas
      compitiendo. Acá el campo es una superficie hundida, sin borde, y el borde
      (teal) aparece SOLO al enfocar — que es cuando dice algo.
   3. **El pie enseña el atajo.** «⌘↵ para guardar» a la izquierda: es como se
      carga un formulario sin soltar el teclado, y es lo que separa una pantalla
      de administración de una herramienta.
   ────────────────────────────────────────────────────────────────────────── */
.modal-backdrop {
  position: fixed; inset: 0; z-index: var(--z-modal-fondo);
  background: rgba(8, 9, 10, .38);
  display: flex; align-items: flex-start; justify-content: center;
  padding: 10vh 0 48px;
  /* El overlay scrollea: si el modal es más alto que el viewport, el operador se
     desplaza hasta la barra de acciones. El overflow va acá y NO en .modal ni en
     .modal-body, que quedan en visible para que los popovers (Tom Select,
     datepicker) floten fuera de la caja sin recortarse. */
  overflow-y: auto;
  animation: modal-fondo .12s ease-out;
}
@keyframes modal-fondo { from { opacity: 0 } to { opacity: 1 } }

.modal {
  position: relative; z-index: var(--z-modal);
  background: var(--bg); border-radius: 12px;
  width: 460px; max-width: calc(100vw - 32px);
  box-shadow: 0 16px 60px rgba(8, 9, 10, .18), 0 0 0 1px var(--border);
  /* visible (no hidden) para que los popovers —datepicker, Tom Select— floten
     fuera de la caja del modal en vez de quedar recortados. El redondeo de la
     barra inferior se hace en .modal-actions. */
  overflow: visible;
  animation: modal-entra .14s cubic-bezier(.2, .8, .3, 1);
}
@keyframes modal-entra {
  from { opacity: 0; transform: translateY(-6px) scale(.99) }
  to   { opacity: 1; transform: none }
}

/* El encabezado: la cápsula a la izquierda, la × a la derecha. */
.modal-head {
  display: flex; align-items: center; gap: 8px;
  padding: 12px 12px 12px 16px;
}
.modal-crumb {
  display: flex; align-items: center; gap: 6px;
  font-size: 12.5px; color: var(--text-3); min-width: 0;
}
.modal-crumb-icon { display: flex; color: var(--text-3); }
.modal-crumb-icon svg { width: 14px; height: 14px; stroke: currentColor; }
.modal-crumb-ctx { color: var(--text-3); }
.modal-crumb-sep { display: flex; color: var(--text-3); opacity: .65; }
.modal-crumb-sep svg { width: 12px; height: 12px; stroke: currentColor; }
.modal-crumb-actual {
  color: var(--text); font-weight: 500;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.modal-x {
  margin-left: auto; flex-shrink: 0;
  width: 26px; height: 26px; padding: 0; display: flex;
  align-items: center; justify-content: center;
  border: 0; border-radius: 6px; background: transparent;
  color: var(--text-3); cursor: pointer;
}
.modal-x:hover { background: var(--bg-hover); color: var(--text); }
.modal-x svg { width: 15px; height: 15px; stroke: currentColor; }

.modal-body { padding: 4px 16px 18px; }

/* ── Campos ──────────────────────────────────────────────────────────────── */
.field { margin-bottom: 14px; }
.field:last-child { margin-bottom: 0; }
.field label {
  display: block; font-size: 12px; font-weight: 500;
  color: var(--text-2); margin-bottom: 5px;
}
.field input, .field select, .field textarea {
  width: 100%; padding: 0 10px;
  /* Sin borde: la superficie hundida ya separa el campo del fondo. El borde
     aparece al enfocar (abajo), que es cuando hay algo que señalar. */
  border: 1px solid transparent; border-radius: 7px;
  background: var(--bg-sunken); color: var(--text); font-size: 13.5px;
  transition: background .1s ease, border-color .1s ease, box-shadow .1s ease;
}
.field input, .field select { height: 34px; }
.field textarea {
  padding: 8px 10px; font-family: inherit; resize: vertical; line-height: 1.45;
}
.field input:hover, .field select:hover, .field textarea:hover { background: var(--bg-hover); }

/* El campo que llenó la lectura del documento y no una persona (AGR-299).

   Es un FONDO TENUE y nada más: sin borde, sin ícono, sin badge y sin texto.
   Un borde en teal habría abierto la segunda excepción a la regla de color del
   proyecto —hoy hay una sola, la barrita de avance— y encima los campos de esta
   app no tienen borde por decisión: aparece sólo al enfocar. Y son hasta seis
   campos marcados a la vez en un modal chico: seis íconos o seis badges serían
   más ruido que los seis números.

   **El fondo se pinta con una `box-shadow` `inset` y no con `background`**, que
   es lo que deja las dos capas separadas: el `background` sigue siendo el
   idioma del HOVER, así que un campo marcado se sigue sintiendo al pasarle por
   encima en vez de quedar congelado. El `inset` no mueve un píxel de geometría
   y sigue solo el `border-radius` del campo.

   **El `:focus` la apaga sin una línea de más.** El foco reemplaza la
   `box-shadow` entera por su anillo, así que tocar el campo borra la marca — que
   es exactamente lo que tiene que pasar: desde que alguien lo tocó, el número ya
   no es «lo que dijo el documento». Por eso las dos reglas van ACÁ, arriba de
   `:focus` y con su misma especificidad (una clase y un elemento, sin `.field`
   adelante): así el foco gana por orden, sin `!important` y sin `:not()`. Mover
   estas líneas abajo del `:focus`, o agregarles el `.field`, deja la marca
   pintada sobre un campo enfocado. */
input.leido { box-shadow: inset 0 0 0 100px var(--brand-soft); }
input.leido:hover { box-shadow: inset 0 0 0 100px var(--brand-line); }

.field input::placeholder, .field textarea::placeholder { color: var(--text-3); }
.field input:focus, .field select:focus, .field textarea:focus {
  outline: none; background: var(--bg);
  border-color: var(--brand); box-shadow: 0 0 0 3px rgba(24, 30, 38, .10);
}

/* Campo checkbox: label al lado de un toggle switch, no el input estirado. */
.field-check { display: flex; align-items: center; gap: 10px; flex-direction: row; }
.field-check label { margin: 0; font-size: 13.5px; color: var(--text); order: 2; }
.field-check input[type="checkbox"] {
  appearance: none; -webkit-appearance: none;
  width: 34px; height: 20px; padding: 0; margin: 0; flex-shrink: 0;
  border-radius: 20px; background: var(--border-strong); border: none;
  position: relative; cursor: pointer; transition: background .15s;
}
.field-check input[type="checkbox"]::after {
  content: ""; position: absolute; top: 2px; left: 2px;
  width: 16px; height: 16px; border-radius: 50%;
  background: #fff; box-shadow: 0 1px 2px rgba(0,0,0,.2);
  transition: transform .15s;
}
.field-check input[type="checkbox"]:checked { background: var(--brand); }
.field-check input[type="checkbox"]:checked::after { transform: translateX(14px); }
.field-check input[type="checkbox"]:focus-visible { box-shadow: 0 0 0 3px rgba(24, 30, 38,.16); }

/* El error del campo, en el rojo de la app y no en gris: un rechazo que se ve
   igual que un texto de ayuda no se lee como rechazo. */
.field .err {
  color: var(--danger); background: rgba(163, 45, 45, .06);
  border-radius: 5px; padding: 4px 8px; font-size: 12px; margin-top: 5px;
}
.field .err + input, .field input.is-error { border-color: var(--danger); }

/* ── Secciones del formulario ──────────────────────────────────────────────
   Doce campos seguidos son una pared. Agrupados en «Las partes», «La operación»
   y «Los términos» son tres decisiones, que es como se piensa un cierre. El
   separador es una línea con el título encima, no una caja: sumar bordes es
   exactamente lo que el campo sin borde vino a evitar. */
.form-sec + .form-sec { margin-top: 18px; padding-top: 16px; border-top: 1px solid var(--border); }
.form-sec-title {
  font-size: 11px; font-weight: 600; letter-spacing: .05em; text-transform: uppercase;
  color: var(--text-3); margin-bottom: 12px;
}

/* ── Pie ─────────────────────────────────────────────────────────────────── */
.modal-actions {
  display: flex; align-items: center; gap: 8px;
  padding: 11px 16px; border-top: 1px solid var(--border);
  background: var(--bg-sidebar); border-radius: 0 0 12px 12px;
}
.modal-botones { margin-left: auto; display: flex; gap: 8px; }
.modal-hint { font-size: 11.5px; color: var(--text-3); display: flex; align-items: center; gap: 3px; }
.modal-hint kbd {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 17px; height: 17px; padding: 0 4px;
  border: 1px solid var(--border); border-bottom-width: 2px; border-radius: 4px;
  background: var(--bg); font: inherit; font-size: 10.5px; color: var(--text-2);
}

/* Feedback de "cargando" al enviar: HTMX marca el <form> con .htmx-request
   mientras el POST está en vuelo. Contra una DB remota el guardado puede tardar,
   así que atenuamos la primaria para que el operador no vuelva a clickear. */
.modal form.htmx-request .btn-primary { opacity: .55; pointer-events: none; }
.modal form.htmx-request { cursor: progress; }

/* ── Modal de confirmación (ADR-0005 §2) ─────────────────────────────────── */
/* El cuerpo dice qué se pierde: la frase principal, y abajo el colateral (gris,
   secundario) o el bloqueo (rojo, con el botón de confirmar deshabilitado). */
.confirm-msg { margin: 0; font-size: 13.5px; line-height: 1.5; }
.confirm-aviso {
  margin: 10px 0 0; font-size: 12.5px; line-height: 1.5; color: var(--text-2);
}
.confirm-bloqueo {
  margin: 12px 0 0; padding: 8px 10px; border-radius: var(--radius);
  background: #fbebeb; border: 1px solid #f0c4c4; color: #a32d2d;
  font-size: 12.5px; line-height: 1.5;
}
/* Cuáles bloquean (MGA-130): un renglón por OC, con la misma forma que la lista
   de OC del modal de armado (.mat-confirm-row). El estado va a la derecha, en
   columna: es lo que decide qué hacer con el pedido. El número es un link y
   tiene que verse como tal — el reset global (`a { color: inherit;
   text-decoration: none }`) lo dejaría igual que el texto de al lado. */
.confirm-bloqueo-lista {
  list-style: none; margin: 8px 0 0; padding: 0;
  display: flex; flex-direction: column; gap: 6px;
}
.confirm-bloqueo-item {
  display: flex; align-items: center; gap: 10px;
  padding: 7px 10px; background: var(--bg-sunken);
  border: 1px solid var(--border); border-radius: 8px;
  font-size: 12.5px; line-height: 1.4;
}
.confirm-bloqueo-link {
  color: var(--text-2); text-decoration: underline;
  font-variant-numeric: tabular-nums; flex-shrink: 0;
}
.confirm-bloqueo-link:hover { color: var(--text); }
/* Lo que bloquea pero no tiene pantalla propia (una obra, una plantilla): mismo
   lugar que el link, sin el subrayado que prometería un click. Va en el color
   del texto principal y NO en el del link: son renglones sin detalle ni estado,
   y en gris claro la caja se lee como un input vacío con un placeholder. */
.confirm-bloqueo-link-muerto {
  color: var(--text); font-variant-numeric: tabular-nums; flex-shrink: 0;
}
.confirm-bloqueo-detalle { color: var(--text); }
.confirm-bloqueo-estado { margin-left: auto; color: var(--text-2); white-space: nowrap; }
/* Cuál de los seleccionados encabeza el grupo, en el borrado masivo (MGA-159).
   Va sobre su lista y no adentro: es el objeto que hay que destildar, no uno más
   de los que bloquean. */
.confirm-bloqueo-grupo {
  margin: 14px 0 0; font-size: 12.5px; font-weight: 600;
  color: var(--text); line-height: 1.4;
}
/* El «y N más» del tope: un renglón de la lista, pero sin la caja — no es un
   documento que se pueda abrir, es la confesión de que la lista está cortada. */
.confirm-bloqueo-mas {
  padding: 2px 10px; font-size: 12px; color: var(--text-2);
}
/* Rojo sólo para lo que destruye. Cerrar un acopio también se confirma acá, y
   se confirma con el botón primario: congela precios, no borra nada. */
.btn-peligro { background: #a32d2d; border-color: #a32d2d; color: #fff; }
.btn-peligro:hover { background: #8f2727; }
.btn:disabled { opacity: .45; pointer-events: none; }

/* ── Importar pedido de WhatsApp ─────────────────────────────────────────── */
/* El review necesita más ancho: la grilla muestra texto crudo + cantidad +
   selector de material por fila. */
.modal-md { width: 620px; max-width: calc(100vw - 32px); }
.modal-wide { width: 920px; max-width: calc(100vw - 32px); }
/* OJO: nada de overflow en el modal-body — recortaría el dropdown de Tom Select
   del material y daría la sensación de que "no abre". Si hay muchas filas, deja
   que scrollee la página de fondo. */
.modal-wide .modal-body { overflow: visible; }

.field textarea {
  width: 100%; padding: 8px 10px;
  border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--bg); color: var(--text); font-size: 13.5px;
  font-family: inherit; resize: vertical; line-height: 1.45;
}
.field textarea:focus {
  outline: none; border-color: var(--brand); box-shadow: 0 0 0 3px rgba(24, 30, 38,.10);
}

.field-label { display: block; font-size: 12.5px; color: var(--text-2); margin-bottom: 6px; }

/* Grilla tipo hoja de cálculo: una fila por material, celdas con bordes. */
.imp-grid-wrap {
  border: 1px solid var(--border); border-radius: var(--radius);
  overflow: visible;  /* visible: los dropdowns de Tom Select flotan fuera */
}
/* table-layout: fixed → los anchos de columna mandan y el contenido (incluido
   el Tom Select del material) se ajusta a la celda en vez de estirarla. */
.imp-grid { width: 100%; table-layout: fixed; border-collapse: collapse; font-size: 13px; }
.imp-grid thead th {
  text-align: left; font-size: 11px; font-weight: 600; text-transform: uppercase;
  letter-spacing: .03em; color: var(--text-2);
  padding: 7px 10px; background: var(--bg-sidebar);
  border-bottom: 1px solid var(--border);
}
.imp-grid td {
  padding: 0; border-bottom: 1px solid var(--border); vertical-align: middle;
}
.imp-grid tbody tr:last-child td { border-bottom: none; }
.imp-grid .imp-col-raw  { width: 38%; }
.imp-grid .imp-col-qty  { width: 90px; }
.imp-grid .imp-col-mat  { width: auto; }   /* la más ancha: lo que importa */
.imp-grid .imp-col-x    { width: 40px; text-align: center; }

/* El texto crudo del mensaje, como celda de planilla (truncado, sin wrap). */
.imp-raw {
  display: block; padding: 0 10px; color: var(--text-2);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.imp-raw-manual { font-style: italic; }

/* Inputs embebidos en la celda: sin borde propio, ocupan toda la celda (look
   de hoja de cálculo). El borde lo pone la celda. */
.imp-grid .imp-qty,
.imp-grid .imp-mat {
  border: none; border-radius: 0; background: transparent;
  height: 34px; width: 100%;
}
.imp-grid .imp-qty {
  padding: 0 10px; text-align: right; color: var(--text);
  font-variant-numeric: tabular-nums;
}
/* Sin flechitas: que escriban el número directo (look de planilla). */
.imp-grid .imp-qty::-webkit-outer-spin-button,
.imp-grid .imp-qty::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.imp-grid .imp-qty { -moz-appearance: textfield; appearance: textfield; }
.imp-grid .imp-qty:focus {
  outline: none; box-shadow: inset 0 0 0 1px var(--border-strong); background: var(--bg);
}
/* Tom Select dentro de la celda: sin borde, ocupa el ancho de la celda. OJO: nada
   de overflow:hidden en la celda ni en el control — recorta el dropdown y parece
   que "no abre". El truncado del texto largo se hace solo en el .item. */
.imp-grid .ts-wrapper {
  width: 100%; max-width: 100%; box-sizing: border-box;
}
.imp-grid .ts-wrapper .ts-control {
  border: none; border-radius: 0; background: transparent; min-height: 34px;
  flex-wrap: nowrap; cursor: pointer;
}
/* Material ya elegido: se ve sutil, como texto de la planilla (no como un input
   activo). El affordance de "tocá para buscar otro" aparece en hover/focus. */
.imp-grid .ts-wrapper .ts-control > .item {
  max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  color: var(--text);
}
.imp-grid .ts-wrapper:not(.focus) .ts-control:hover { background: var(--bg-hover); }
/* Selección sutil: un borde fino gris, no el marco negro de 2px. */
.imp-grid .ts-wrapper.focus .ts-control {
  box-shadow: inset 0 0 0 1px var(--border-strong); background: var(--bg);
}
/* El dropdown del material por encima de la barra de acciones del modal. No sale
   de la escala de :root a propósito: vive DENTRO del modal, que ya es su contexto
   de apilado, así que sólo compite con sus hermanos (la barra de acciones). */
.imp-grid .ts-dropdown { z-index: 60; }

/* Fila sin material identificado: marca roja a la izquierda + tinte; bloquea el
   guardado hasta que el usuario elija un material del catálogo. */
.imp-row-bad td { background: var(--bg-sunken); }
.imp-row-bad .imp-col-raw { box-shadow: inset 3px 0 0 var(--danger); }

.imp-row-empty td { padding: 14px 10px; color: var(--text-2); text-align: center; }

.imp-del {
  border: none; background: transparent; cursor: pointer;
  color: var(--text-2); font-size: 18px; line-height: 1;
  width: 28px; height: 28px; border-radius: var(--radius);
}
.imp-del:hover { background: var(--bg-hover); color: var(--text); }

.imp-add { margin-top: 10px; }

/* La importación dejó de ser un modal y pasó a ser una pantalla de dos pasos
   (MGA-116 / ADR-0005 §1). El paso 1 es una columna angosta —hay un solo
   textarea y no gana nada estirándose—; el paso 2 usa el ancho de la página
   completo, que es justo lo que la grilla fila-por-fila pedía y el modal de
   920px nunca le pudo dar. */
/* Columna centrada: es la pantalla entera (miga, título y form), no sólo el
   form. Una columna angosta pegada al borde izquierdo de una página por lo
   demás vacía se lee como un error de maquetado. */
.imp-page { max-width: 720px; margin: 0 auto; }
.imp-head-row { display: flex; gap: 16px; align-items: flex-start; }
.imp-head-row .field { flex: 1; min-width: 0; }
/* La barra de acciones del modal ahora es el pie de la pantalla: misma
   gramática (secundaria a la izquierda, primaria a la derecha), separada del
   cuerpo por una línea en vez de por el borde del modal. */
.imp-page-actions {
  display: flex; justify-content: flex-end; align-items: center; gap: 8px;
  margin-top: 20px; padding-top: 16px; border-top: 1px solid var(--border);
}

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

/* ── Empty-state de páginas stub ─────────────────────────────────────────── */
.stub {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  text-align: center; height: 60vh; color: var(--text-2);
}
.stub .stub-icon {
  width: 40px; height: 40px; border-radius: var(--radius-lg);
  border: 1px solid var(--border); background: var(--bg-sidebar);
  display: flex; align-items: center; justify-content: center; margin-bottom: 14px;
}
.stub .stub-icon svg { width: 20px; height: 20px; stroke: var(--text-3); }
.stub h2 { margin: 0 0 4px; font-size: 15px; font-weight: 600; color: var(--text); }
.stub p { margin: 0; font-size: 13px; }

.htmx-indicator { opacity: 0; transition: opacity .2s; }
.htmx-request .htmx-indicator { opacity: 1; }

/* ── Tom Select (dropdowns con búsqueda, estilo Linear) ──────────────────── */
.ts-wrapper { font-size: 13.5px; }
/* Deshabilitado (ej: acopio mientras no se eligió proyecto). */
.ts-wrapper.disabled .ts-control { background: var(--bg-sunken); cursor: not-allowed; opacity: .6; }
.ts-control {
  min-height: 34px; height: 34px; padding: 0 10px;
  display: flex; align-items: center; flex-wrap: nowrap;
  border: 1px solid var(--border) !important; border-radius: var(--radius);
  background: var(--bg); color: var(--text);
  box-shadow: none;
}
/* Item seleccionado + input de búsqueda en una sola línea, sin crecer. */
.ts-wrapper.single .ts-control .item {
  background: transparent; padding: 0; margin: 0; line-height: 1;
}
.ts-control input { flex: 1 1 auto; min-width: 0; }
.ts-wrapper.single.has-items .ts-control input { width: 0; min-width: 0; flex: 0 0 0; }
/* Picker multi (labels del pedido, ADR-0007). El .ts-control de arriba está
   pensado para el single de la grilla: alto fijo y una sola fila. Un multi mete
   varios chips, así que acá el control crece hacia abajo (los chips fluyen a la
   fila siguiente) y CADA chip se queda en una línea —un label largo se recorta
   con ellipsis, no se parte en dos como venía pasando al salir del campo. */
.ts-wrapper.multi .ts-control {
  height: auto; min-height: 34px;
  flex-wrap: wrap; gap: 4px;
  padding-top: 4px; padding-bottom: 4px;
}
.ts-wrapper.multi .ts-control > .item {
  max-width: 100%;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.ts-wrapper.focus .ts-control {
  border-color: var(--text) !important;
  box-shadow: 0 0 0 3px rgba(8,9,10,.06);
}
.ts-control input, .ts-control input::placeholder { color: var(--text-3); }
.ts-control input { color: var(--text); }
.ts-dropdown {
  margin-top: 4px;
  border: 1px solid var(--border); border-radius: var(--radius);
  box-shadow: var(--shadow-modal);
  font-size: 13.5px; overflow: hidden;
}
.ts-dropdown .option {
  padding: 7px 10px; color: var(--text-2); cursor: pointer;
}
.ts-dropdown .option.active { background: var(--bg-hover); color: var(--text); }
.ts-dropdown .optgroup-header {
  font-size: 11px; font-weight: 500; color: var(--text-3);
  padding: 8px 10px 4px; letter-spacing: .01em;
}
.ts-dropdown .no-results { padding: 8px 10px; color: var(--text-3); }
.ts-wrapper.single .ts-control:after { border-color: var(--text-3) transparent transparent; }
/* Dropdown vacío: no mostrar cajas fantasma. */
.ts-dropdown:empty, .ts-dropdown .ts-dropdown-content:empty { display: none; }
/* Opción "Agregar…": destacada y consistente con el tema. */
.ts-dropdown .create {
  padding: 7px 10px; color: var(--text-2); cursor: pointer;
}
.ts-dropdown .create.active { background: var(--bg-hover); color: var(--text); }
.ts-dropdown .create strong { color: var(--text); font-weight: 600; }
/* Spinner de carga remota: chico, del alto de una línea, sin agrandar nada. */
.ts-wrapper .spinner {
  display: block; width: 14px; height: 14px; margin: 9px auto;
  border: 2px solid var(--border-strong); border-top-color: var(--text-2);
  border-radius: 50%; animation: ts-spin .6s linear infinite;
}
.ts-wrapper .spinner::before, .ts-wrapper .spinner::after { content: none; }
@keyframes ts-spin { to { transform: rotate(360deg); } }

/* --- Datepicker propio ---------------------------------------------------- */
.dp-wrap { position: relative; }
.dp-input { cursor: pointer; }
.dp-pop {
  display: none; position: absolute; top: calc(100% + 6px); left: 0; z-index: var(--z-popover);
  width: 252px; padding: 10px;
  background: var(--bg); border: 1px solid var(--border);
  border-radius: var(--radius-lg); box-shadow: var(--shadow-modal);
}
.dp-wrap.dp-open .dp-pop { display: block; }
/* Flip hacia arriba cuando no hay espacio abajo (ej: campo cerca del footer). */
.dp-wrap.dp-up .dp-pop { top: auto; bottom: calc(100% + 6px); }
.dp-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; }
.dp-title { font-size: 13px; font-weight: 500; color: var(--text); }
.dp-nav {
  width: 26px; height: 26px; padding: 0; cursor: pointer; font-size: 16px; line-height: 1;
  display: inline-flex; align-items: center; justify-content: center;
  border: none; background: transparent; border-radius: var(--radius); color: var(--text-2);
}
.dp-nav:hover { background: var(--bg-hover); color: var(--text); }
.dp-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; }
.dp-dow { font-size: 10.5px; color: var(--text-3); text-align: center; padding: 2px 0 4px; }
.dp-cell { aspect-ratio: 1; }
.dp-day {
  width: 100%; height: 100%; padding: 0; cursor: pointer; font-size: 12.5px;
  display: inline-flex; align-items: center; justify-content: center;
  border: none; background: transparent; border-radius: var(--radius); color: var(--text);
}
.dp-day:hover { background: var(--bg-hover); }
.dp-day.is-today { font-weight: 600; box-shadow: inset 0 0 0 1px var(--border-strong); }
.dp-day.is-sel { background: var(--text); color: var(--text-inv); }
.dp-day.is-sel:hover { background: var(--text); }
.dp-foot { margin-top: 8px; display: flex; justify-content: flex-end; }
.dp-today {
  padding: 4px 10px; cursor: pointer; font-size: 12px;
  border: 1px solid var(--border); background: var(--bg); border-radius: var(--radius); color: var(--text-2);
}
.dp-today:hover { background: var(--bg-hover); color: var(--text); }

/* ── Toggle de vistas en la lista de pedidos ──────────────────────────────── */
/* Reusa el look de .tab pero como links navegables (no radios). */
.tab-bar .tab.is-active { color: var(--text); border-bottom-color: var(--text); }

/* Barra unificada estilo Linear: tabs a la izquierda, controles a la derecha,
   todo sobre una misma línea con un único borde inferior. */
.list-bar {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: 16px; border-bottom: 1px solid var(--border); margin-bottom: 20px;
}
/* Dentro de la barra unificada la tab-bar ya no dibuja su propio borde ni
   margen: los pone .list-bar (así el subrayado activo corre sobre la misma
   línea que los controles de la derecha). */
.list-bar .tab-bar { border-bottom: none; margin-bottom: 0; }
.list-tools {
  display: flex; align-items: center; gap: 8px; flex-shrink: 0;
  padding-bottom: 6px;
}
/* Sin tabs a la izquierda (ej. Órdenes de compra): los controles van a la derecha. */
.list-bar > .list-tools:only-child { margin-left: auto; }
/* Search compacto a la derecha (como los filtros de Linear), no full-width. */
.list-tools .search { height: 30px; width: 200px; }
.list-tools .search input { font-size: 13px; }

/* Selector de layout de la lista de pedidos (Por estado / Lista / Calendario):
   dropdown estilo Linear (<details>/<summary>), no un segmented control. El
   botón muestra el modo activo; el menú despliega las tres opciones con motion. */
.view-menu { position: relative; flex-shrink: 0; }
.view-menu-btn {
  display: inline-flex; align-items: center; gap: 6px;
  height: 30px; padding: 0 8px 0 10px;
  font-size: 13px; font-weight: 500; color: var(--text);
  background: var(--bg); border: 1px solid var(--border);
  border-radius: var(--radius); cursor: pointer; white-space: nowrap;
  list-style: none;
}
.view-menu-btn::-webkit-details-marker { display: none; }
.view-menu-btn:hover { background: var(--bg-hover); }
.view-menu[open] > .view-menu-btn { background: var(--bg-hover); border-color: var(--border-strong); }
.view-menu-btn > svg { width: 15px; height: 15px; stroke: currentColor; color: var(--text-2); flex-shrink: 0; }
.view-menu-caret {
  display: inline-flex; color: var(--text-3); margin-left: 1px;
  transition: transform .15s ease;
}
.view-menu-caret svg { width: 14px; height: 14px; stroke: currentColor; }
.view-menu[open] > .view-menu-btn .view-menu-caret { transform: rotate(180deg); }

/* Popover: aparece con fade + leve slide-down + scale (motion tipo Linear). */
.view-menu-pop {
  position: absolute; top: calc(100% + 5px); right: 0; z-index: var(--z-popover);
  min-width: 184px; padding: 4px;
  background: var(--bg); border: 1px solid var(--border);
  border-radius: var(--radius); box-shadow: var(--shadow-modal);
  transform-origin: top right;
  animation: view-menu-in .14s cubic-bezier(.2, .8, .3, 1);
}
@keyframes view-menu-in {
  from { opacity: 0; transform: translateY(-4px) scale(.97); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}
.view-menu-opt {
  display: flex; align-items: center; gap: 9px; width: 100%;
  padding: 7px 8px; border-radius: 6px;
  font-size: 13px; color: var(--text-2); cursor: pointer;
  text-decoration: none; white-space: nowrap;
}
.view-menu-opt > span:first-of-type { flex: 1; }
.view-menu-opt:hover { background: var(--bg-hover); color: var(--text); }
.view-menu-opt.is-active { color: var(--text); font-weight: 500; }
.view-menu-opt svg { width: 15px; height: 15px; stroke: currentColor; flex-shrink: 0; }
.view-menu-opt > svg:first-child { color: var(--text-2); }
.view-menu-check { display: inline-flex; color: var(--text); }
.view-menu-opt.is-active > svg:first-child { color: var(--text); }

/* ── Filtro de la lista (ADR-0005 §4) ────────────────────────────────────────
   El botón dice "Filtrar" con el ícono al lado, SIEMPRE: un botón que muta es un
   botón que hay que volver a aprender. Es el mismo átomo visual que el .view-menu
   que va al lado en la misma barra.

   El popover trae los DOS menús ya renderizados —el de campos y el de valores de
   cada campo, con sus conteos— y el JS de base.html solo elige cuál se ve
   (.filtro-pane[hidden]): los conteos los calcula el server (core.facetas), así
   que nunca son de mentira. */
.filtro-menu { position: relative; flex-shrink: 0; }
.filtro-btn {
  display: inline-flex; align-items: center; gap: 6px;
  height: 30px; padding: 0 10px;
  border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--bg); color: var(--text-2); font-size: 13px;
  cursor: pointer; list-style: none; white-space: nowrap; user-select: none;
}
.filtro-btn::-webkit-details-marker { display: none; }
.filtro-btn:hover { background: var(--bg-hover); color: var(--text); }
.filtro-menu[open] > .filtro-btn { background: var(--bg-hover); border-color: var(--border-strong); }
.filtro-btn > svg { width: 15px; height: 15px; stroke: currentColor; flex-shrink: 0; }

.filtro-pop {
  position: absolute; top: calc(100% + 5px); left: 0; z-index: var(--z-popover);
  min-width: 220px; padding: 4px;
  background: var(--bg); border: 1px solid var(--border);
  border-radius: var(--radius-lg); box-shadow: var(--shadow-modal);
  animation: view-menu-in .14s cubic-bezier(.2, .8, .3, 1);
}
.filtro-sec {
  font-size: 11px; color: var(--text-3);
  padding: 6px 8px 3px; letter-spacing: .03em;
}
.filtro-opt {
  display: flex; align-items: center; gap: 8px; width: 100%;
  padding: 7px 8px; border: 0; border-radius: 6px; background: transparent;
  font-size: 13px; color: var(--text-2); text-align: left; cursor: pointer;
  white-space: nowrap;
}
.filtro-opt:hover { background: var(--bg-hover); color: var(--text); }
.filtro-opt.is-active { color: var(--text); font-weight: 500; }
.filtro-opt .filtro-label { flex: 1; }
.filtro-opt > svg { width: 14px; height: 14px; stroke: currentColor; color: var(--text-3); flex-shrink: 0; }
/* El tilde ocupa su lugar aunque no esté: si no, la fila salta al marcarla. */
.filtro-check { display: inline-flex; width: 14px; flex-shrink: 0; color: var(--text); }
.filtro-check svg { width: 14px; height: 14px; stroke: currentColor; }
.filtro-n { font-size: 11.5px; color: var(--text-3); font-variant-numeric: tabular-nums; }
/* «Creado por» es el único campo cuyos valores son PERSONAS, así que es el único
   que lleva el círculo (MGA-193). El círculo mide 24px y no se toca (ADR-0011:
   un solo tamaño para todos los marcadores de identidad), así que el costo se
   paga en el padding —igual que en el `.nav-item` del sidebar—: se lo achica
   sólo en esta fila para que el círculo entre sin inflar la altura por encima
   de las demás filas del menú. */
.filtro-pane[data-campo="creado_por"] .filtro-opt { padding-top: 4px; padding-bottom: 4px; }

/* Las píldoras: lo filtrado, arriba de la barra. El cuerpo REABRE el menú de ese
   campo (se cambian, no solo se borran); la × saca el campo entero. */
.filtro-pills { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin-bottom: 12px; }
.filtro-pill {
  display: inline-flex; align-items: stretch; height: 28px;
  border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--bg); overflow: hidden;
}
.filtro-pill-body {
  display: inline-flex; align-items: center; gap: 5px; padding: 0 9px;
  border: 0; background: transparent; cursor: pointer;
  font-size: 12.5px; color: var(--text); white-space: nowrap;
}
.filtro-pill-body:hover { background: var(--bg-hover); }
.filtro-pill-k { color: var(--text-3); }
.filtro-pill-x {
  display: inline-flex; align-items: center; justify-content: center; width: 24px;
  border: 0; border-left: 1px solid var(--border); background: transparent;
  color: var(--text-3); cursor: pointer;
}
.filtro-pill-x:hover { background: var(--bg-hover); color: var(--text); }
.filtro-pill-x svg { width: 13px; height: 13px; stroke: currentColor; }
.filtro-clear {
  border: 0; background: transparent; color: var(--text-3);
  font-size: 12.5px; cursor: pointer; padding: 0 6px;
}
.filtro-clear:hover { color: var(--text); text-decoration: underline; }

/* Vista "Por estado": secciones por estado calculado; Entregados es un
   <details> colapsable (mismo patrón caret que .client en el sidebar). */
.estado-grupo { margin-bottom: 20px; }
.estado-grupo-head {
  display: flex; align-items: center; gap: 8px;
  padding: 7px 8px; font-size: 13px; font-weight: 600; color: var(--text);
  background: var(--bg-sunken); border-bottom: 1px solid var(--border);
  border-radius: var(--radius) var(--radius) 0 0;
}
.estado-grupo-head .lin-status { width: 10px; height: 10px; }
/* Ícono del proveedor en la vista "Por proveedor" de órdenes. */
.estado-grupo-head > svg { width: 15px; height: 15px; stroke: currentColor; color: var(--text-2); flex-shrink: 0; }
summary.estado-grupo-head { cursor: pointer; list-style: none; }
summary.estado-grupo-head::-webkit-details-marker { display: none; }
.estado-grupo-head .caret {
  display: inline-flex; color: var(--text-3); transition: transform .12s;
}
.estado-grupo-head .caret svg { width: 14px; height: 14px; stroke: currentColor; }
details.estado-grupo[open] > summary .caret { transform: rotate(90deg); }

/* ── Materiales pendientes de comprar (armado de OC) ──────────────────────── */
/* Mismo look que la vista "Por estado" de pedidos: sin recuadro ni bordes
   laterales, solo un header con fondo y las filas debajo. */
.mat-pendientes { display: flex; flex-direction: column; gap: 20px; }
.mat-grupo-head {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 7px 8px; background: var(--bg-sunken); border-bottom: 1px solid var(--border);
  border-radius: var(--radius) var(--radius) 0 0;
}
.mat-grupo-prov { display: inline-flex; align-items: center; gap: 8px; font-size: 13px; font-weight: 600; }
.mat-grupo-prov svg { width: 15px; height: 15px; color: var(--text-2); stroke: currentColor; }
.mat-grupo-total {
  margin-left: 6px; font-size: 12.5px; font-weight: 500; color: var(--text-2);
  font-variant-numeric: tabular-nums;
}

/* Fila de material estilo Pedidos: total siempre visible; sin expandible. La
   fila no navega (no es link), pero el checkbox on-hover la selecciona. */
.mat-row { border-bottom: 1px solid var(--border); }
.mat-row:last-child { border-bottom: none; }
.mat-row .lin-title { white-space: normal; }

/* Precio acopio / cuenta corriente como tags chicos en la meta de la fila. */
.mat-precio-tag {
  font-size: 12px; color: var(--text-2);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.mat-precio-tag .mat-saldo { display: inline; margin-left: 4px; }
.mat-saldo { font-size: 11px; color: var(--text-3); font-weight: 400; }
.mat-saldo-corto { color: #b4382f; }
/* "Sin precio": el proveedor lo eligió una persona y dejó de tener precio para
   ese material (MGA-205). Ocupa el lugar del número, así que se lee como una
   falta y no como un estado más de la fila. */
.mat-sin-precio { color: #b4382f; font-style: italic; }

/* La fila cuyo proveedor decidió una persona (MGA-204 / ADR-0013). No lleva
   color ni fondo: lo que la distingue de una sugerida es que TIENE cosas que la
   otra no —el círculo del autor y el botón de volver al sugerido—, y eso ya se
   ve de un vistazo escaneando la columna. Pintarla además sería decir dos veces
   lo mismo en una mesa que ya usa el gris para "sin proveedor". */
.mat-volver {
  display: inline-flex; align-items: center; justify-content: center;
  width: 20px; height: 20px; padding: 0;
  background: none; border: none; border-radius: 4px;
  color: var(--text-3); cursor: pointer;
}
/* `stroke: currentColor` no es opcional: los íconos de `_icons.html` vienen sin
   atributo `stroke`, así que sin esta regla el SVG se dibuja invisible y el
   botón queda como una caja vacía (se vio en el QA de dev, no lo ve ningún
   test). Todas las reglas de ícono de esta hoja lo llevan junto al tamaño. */
.mat-volver svg { width: 13px; height: 13px; stroke: currentColor; }
.mat-volver:hover { background: var(--bg-sunken); color: var(--text); }

/* El tramo de un reparto se ve IGUAL que cualquier fila (.mat-row); no lleva
   estilo propio. El badge de proveedor se usa en el modal de confirmación. */
.mat-badge-prov {
  font-size: 11.5px; font-weight: 500; color: var(--text);
  background: var(--bg); border: 1px solid var(--border);
  border-radius: 20px; padding: 2px 10px; white-space: nowrap;
}

/* Bloque de acciones de selección dentro de la isla (se oculta sin selección). */
.mat-island-sel { display: inline-flex; align-items: center; gap: 8px; }

/* Modal de confirmación de armado: lista de OC por proveedor. */
.mat-confirm-list { display: flex; flex-direction: column; gap: 8px; }
.mat-confirm-row {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 8px 10px; background: var(--bg-sunken);
  border: 1px solid var(--border); border-radius: 8px;
}
.mat-confirm-detalle { font-size: 12.5px; font-variant-numeric: tabular-nums; }

/* Modal de reparto (dividir / reasignar). */
.mat-modal { max-width: 460px; }
.mat-modal-total { margin: 0 0 12px; }
.mat-tramos { display: flex; flex-direction: column; gap: 8px; }
.mat-tramo { display: flex; align-items: center; gap: 8px; }
.mat-tramo-cant {
  width: 90px; padding: 6px 8px; text-align: right;
  font-variant-numeric: tabular-nums; font-size: 13px;
  border: 1px solid var(--border); border-radius: 6px; background: var(--bg);
}
.mat-tramo-prov { flex: 1; }
.mat-tramo-del {
  border: none; background: none; color: var(--text-3);
  font-size: 18px; line-height: 1; cursor: pointer; padding: 0 4px;
}
.mat-tramo-del:hover { color: #a32d2d; }
.mat-tramo-add { margin-top: 10px; }
.mat-tramo-resto { margin: 10px 0 0; font-size: 12.5px; color: var(--text-2); }
.mat-tramo-resto.is-ok { color: #2e7d32; }
.mat-tramo-resto.is-err { color: #a32d2d; }

/* =========================================================================
   Motion — transiciones suaves de navegación (hx-boost) y micro-interacciones.
   Todo puro CSS, enganchado al ciclo de vida de htmx. Se desactiva entero si
   el sistema del usuario pide menos movimiento.
   ========================================================================= */

/* El contenido nuevo entra con un fade + empujoncito hacia arriba. Solo anima
   .content-inner, así el sidebar queda quieto al cambiar de pestaña. */
.content-inner { animation: page-in .18s ease both; }
@keyframes page-in {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* Durante el swap de htmx, desvanecemos lo viejo apenas para que no haya un
   corte seco entre página y página. */
.content-inner.htmx-swapping { opacity: 0; transition: opacity .1s ease; }

/* Micro-interacciones consistentes en los elementos clickeables. */
.nav-item { transition: background .12s ease, color .12s ease; }
.btn, button.btn, .pill { transition: background .12s ease, border-color .12s ease, color .12s ease; }

@media (prefers-reduced-motion: reduce) {
  .content-inner,
  .content-inner.htmx-swapping,
  .nav-item, .btn, button.btn, .pill {
    animation: none !important;
    transition: none !important;
  }
}

/* Toast de error global (red de seguridad HTMX): un 403/500 o una caída de red
   nunca deben ser silencio total. Fijo arriba a la derecha, se apilan y se
   auto-ocultan a los ~6s (ver base.html). Tono de alerta como .tag-danger. */
#toast-host {
  position: fixed; top: 16px; right: 16px; z-index: var(--z-toast);
  display: flex; flex-direction: column; gap: 8px;
  max-width: min(360px, calc(100vw - 32px)); pointer-events: none;
}
.toast-error {
  background: #fbebeb; color: #a32d2d; border: 1px solid #f0c4c4;
  border-radius: var(--radius); padding: 10px 12px; font-size: 13px;
  box-shadow: var(--shadow-modal); pointer-events: auto;
}
/* Toast de éxito (verde): el único caso positivo del ADR-0005 §5 — el resultado
   no está a la vista (las OC armadas se crean en otra pantalla) o la acción tocó
   N filas de un saque y el toast dice cuántas. Llega por el canal `toastOk`
   (HX-Trigger). Mismo formato que el de error, en tono positivo. */
.toast-ok {
  background: #eaf7ee; color: #1e7a3d; border: 1px solid #c6e6cf;
  border-radius: var(--radius); padding: 10px 12px; font-size: 13px;
  box-shadow: var(--shadow-modal); pointer-events: auto;
}

/* Nuevo pedido: fecha + solicitante en una fila de dos columnas. */
.np-head-row { display: flex; gap: 12px; }
.np-head-row .field { flex: 1; min-width: 0; }

/* --- Logos de proveedores -------------------------------------------------
   Imagen redonda con object-fit para no deformar. Tres tamaños: fila de la
   lista de proveedores (base), pill de la fila de OC (-sm) y preview en el
   modal de edición (-lg). Sin logo, cae al ícono de camión de siempre. */
.prov-logo {
  width: 18px; height: 18px; border-radius: 50%;
  object-fit: cover; display: block; flex-shrink: 0;
  background: var(--bg-sunken);
}
.pill-proveedor { display: inline-flex; align-items: center; gap: 6px; }
.pill-proveedor .prov-logo.prov-logo-sm { width: 14px; height: 14px; }
.logo-actual { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }
.logo-actual .prov-logo.prov-logo-lg { width: 48px; height: 48px; border-radius: 10px; }
.logo-actual .muted { color: var(--text-2); font-size: 12.5px; }
.field .hint { color: var(--text-3); font-size: 12px; margin-top: 4px; }

/* Ubicación de la obra (MGA-78): buscador Places + mapa con pin arrastrable. */
.obra-ubic-buscar { width: 100%; margin-bottom: 8px; }
.obra-ubic-map { width: 100%; height: 240px; border-radius: 10px; margin-bottom: 6px; background: var(--surface-2, #f2f2f2); }
.obra-ubic-resumen { font-size: 12.5px; }
.obra-ubic-resumen .muted { color: var(--text-2); }

/* ── Cesiones: la fila expande y muestra sus movimientos (MGA-137) ────────────
   Patrón nuevo para esta app, y se acepta a sabiendas: ADR-0004 avisa que «la
   inconsistencia que duele no es que haya dos patrones: es que el patrón se
   elegía sin regla». Acá la regla es la del prototipo — se probaron las tres
   formas con los datos de Bendito y ésta fue la única que muestra el detalle sin
   tapar el contexto contra el que hay que juzgarlo. */
thead th.col-chevron, tbody td.col-chevron { width: 16px; padding: 0 6px 0 8px; }
.cesion-chevron {
  display: inline-flex; align-items: center; justify-content: center;
  width: 20px; height: 20px; border-radius: 5px; color: var(--text-3);
}
.cesion-chevron:hover { background: var(--bg-hover); color: var(--text); }
.cesion-chevron svg { width: 14px; height: 14px; stroke: currentColor; }
.cesion-ente.is-abierta > td { border-bottom-color: transparent; }
.cesion-ente.is-abierta { background: var(--bg-hover); }

/* Los movimientos, indentados bajo su ente y en un tono más liviano: son el
   detalle de la fila de arriba, no filas hermanas de las otras cesiones. */
.mov-row { cursor: default; }
.mov-row:hover { background: transparent; }
.mov-row > td { height: 36px; font-size: 13px; color: var(--text-2); border-bottom: none; }
/* El desplegado cierra con una línea, y la pone SIEMPRE su última fila —la de
   «Agregar movimiento», que es la que siempre está—. Con `:last-of-type` la
   línea caía en el último <tr> del tbody, que sólo es el correcto cuando la
   cesión abierta es la de más abajo. */
.mov-row.is-agregar > td { border-bottom: 1px solid var(--border); }
.mov-fecha { padding-left: 26px; white-space: nowrap; }
.mov-row.is-agregar > td:not(.col-chevron) { padding-left: 26px; }
.mov-acciones { display: inline-flex; align-items: center; gap: 2px; opacity: 0; }
.mov-row:hover .mov-acciones, .mov-acciones:focus-within { opacity: 1; }
.icono-btn.is-peligro:hover { background: #fbebeb; color: #a32d2d; }


/* ── Agrospot: el badge del producto ─────────────────────────────────────────
   El único lugar de una FILA donde entra el teal. Es el mismo par que ya usaba
   la platform (--brand-soft sobre --brand-line), ahora con nombre. */
.pill-producto {
  background: var(--brand-soft);
  border-color: var(--brand-line);
  color: var(--brand);
  font-weight: 500;
}


/* ── Formulario y modal (portado del mismo design system) ─────────────── */

/* ── Formulario del cierre: doce campos en dos columnas ─────────────────────
   Sigue siendo modal (ADR-0005 §1: no es de más de un paso, no es una grilla de
   revisión, no necesita ver la pantalla de atrás). Lo que necesita es ancho. */
.form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 13px 14px; }
.form-grid .field-ancho { grid-column: 1 / -1; }
.form-grid .field { margin: 0; }

/* Los roles como checkboxes en línea: "Opera como ☑ Vendedor ☑ Comprador". Una
   empresa que hace las dos cosas es UNA ficha — el checkbox es lo que lo dice. */
/* `flex-wrap: wrap` (default nowrap): `roles` y `medios_pago` son listas
   cortas que nunca llenan el ancho ni en mobile, así que en desktop no se
   nota. Pero AGR-407 sumó listas más largas (`cultivos`, `tipos_negocio`,
   `prioridades`) al mismo componente, y a 375px una fila de nowrap se corre
   ~337px afuera del viewport en vez de acomodarse en varias líneas. */
.field-roles > div { display: flex; flex-wrap: wrap; gap: 10px 18px; }
/* Revisión de grupo de AGR-407: `.field-roles label` alcanzaba cualquier
   <label> adentro de `.field-roles`, y eso incluye el rótulo del CAMPO
   entero ("Cultivos que siembra"), no sólo el de cada opción ("Girasol") —
   los dos son <label> ahí adentro. Con la misma especificidad que
   `.field label` pero declarada después, esta regla le ganaba, y el rótulo
   del campo salía negro, 13.5px y en flex: el dibujo de una opción, no el de
   un rótulo. El rótulo del campo es un <label> hijo directo de `.field-roles`;
   las opciones viven dentro del `<div>` del widget (`.field-roles > div`),
   así que acotar el selector a `.field-roles > div label` alcanza sólo a las
   opciones y deja al rótulo del campo con el estilo normal de `.field label`. */
.field-roles > div label { display: flex; align-items: center; gap: 6px; margin: 0; font-size: 13.5px; color: var(--text); cursor: pointer; }
/* Los roles usan el MISMO checkbox que las filas (sin el reveal en hover, que
   acá no tiene sentido): una app con dos dibujos de checkbox tiene dos. */
/* `padding: 0` deshace el `.field input { padding: 0 10px }` general (arriba,
   §Campos): ese padding no se anula en ningún otro lado de esta regla, y con
   `box-sizing: border-box` el navegador no deja que el ancho declarado (15px)
   sea menor que padding + borde (10+10+1+1 = 22px) — el checkbox terminaba
   apaisado (22×15) en vez de cuadrado. Sin altura de por medio (el padding
   vertical del campo ya es 0), el alto sí se respetaba: por eso se notaba
   ancho y no alto. */
.field-roles input[type="checkbox"] {
  appearance: none; -webkit-appearance: none;
  width: 15px; height: 15px; margin: 0; padding: 0; flex-shrink: 0; cursor: pointer;
  border: 1px solid var(--border-strong); border-radius: 4px;
  background: var(--bg) no-repeat center; background-size: 11px;
  transition: background-color .1s, border-color .1s;
}
.field-roles input[type="checkbox"]:hover { border-color: var(--text-3); }
.field-roles input[type="checkbox"]:checked {
  background-color: var(--brand); border-color: var(--brand);
  background-image: url("data:image/svg+xml,<svg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2012%2012'><path%20d='M2.6%206.2%204.9%208.5%209.4%203.7'%20fill='none'%20stroke='%23fff'%20stroke-width='1.9'%20stroke-linecap='round'%20stroke-linejoin='round'/></svg>");
}
.field-roles input[type="checkbox"]:focus-visible {
  outline: none; box-shadow: 0 0 0 3px rgba(24, 30, 38, .16);
}

/* El CUIT en tabular para que las columnas de dígitos se lean de arriba abajo,
   y sin cortar: partido en dos renglones deja de ser un número. */
.col-mono { font-variant-numeric: tabular-nums; color: var(--text-2); white-space: nowrap; }

/* Los roles en una sola línea: el acopio lleva los dos y no puede por eso medir
   el doble de alto que las demás filas. */
.col-roles { white-space: nowrap; }
.col-roles .pill { margin-right: 4px; }

/* ── Configuración ─────────────────────────────────────────────────────────
   Pantalla propia y no modal: son dos formularios que conviven y uno de ellos
   es un texto legal de quince renglones. */
.ajustes { display: flex; flex-direction: column; gap: 28px; max-width: 720px; margin-top: 20px; }
/* Sin caja: la línea de la app es plana y a todo el ancho (ver `.table-wrap`).
   El recuadro venía de cuando Configuración era un formulario solo; con tres
   pestañas, una caja adentro de una pestaña es un borde de más. */
.ajustes-sec { padding: 0; }
.ajustes-sec + .ajustes-sec { margin-top: 34px; }
.ajustes-sec h2 { font-size: 15px; font-weight: 600; margin: 0 0 2px; }
.ajustes-sec .sub { margin: 0 0 16px; }
.ajustes-sec .field { margin-bottom: 14px; }
.ajustes-actions { display: flex; justify-content: flex-end; margin-top: 4px; }
.ajustes-hist { margin-top: 14px; font-size: 12.5px; color: var(--text-3); }

/* El modal ancho (el alta de un cierre: doce campos) no puede empezar a 12vh:
   con esa sangría la barra de acciones queda siempre abajo del pliegue y hay
   que scrollear el overlay para llegar a Guardar. El overlay sigue scrolleando
   —es lo que salva al modal más alto que la pantalla— pero arranca más arriba. */
.modal-backdrop:has(.modal-wide) { padding-top: 5vh; }

/* La aclaración de un campo: el porqué que el label no puede decir sin volverse
   una oración. Va debajo del control, no arriba: primero se ve el control. */
.field-nota { margin-top: 6px; font-size: 11.5px; color: var(--text-3); }

/* La aclaración de TODA una sección ("Sólo interno: no se publica."), no de un
   campo puntual: `.field-nota` sola trae 6px de margen y el mismo tamaño que
   un hint, y el ojo la lee pegada al primer campo en vez de leerla como lo
   que es. Revisión de grupo de AGR-407, sobre «Perfil del vendedor». */
.field-nota.field-nota-seccion { margin: 2px 0 16px; font-size: 12px; }

/* El subtítulo de un bloque adentro de una sección ("Qué hace", "Cómo
   vende"): tiene que pesar MENOS que `.form-sec-title` ("PERFIL DEL
   VENDEDOR") — si pesara igual, competiría con los títulos de sección reales
   por ese lugar, y dejaría de leerse como parte de la misma sección.
   Minúscula, no versalita como `.form-sec-title`, un tono más oscuro que ese
   título pero lejos del negro de un rótulo. Revisión de grupo de AGR-407. */
.form-subsec-title { font-size: 12px; font-weight: 600; color: var(--text-2); margin: 18px 0 10px; }
.form-subsec-title:first-of-type { margin-top: 0; }

/* El modal ancho, en pantallas chicas, deja de ser de dos columnas: dos campos
   de 200px cada uno son peores que uno de 420. */
@media (max-width: 720px) {
  .form-grid { grid-template-columns: 1fr; }
}

/* ── El modal de confirmación ───────────────────────────────────────────────
   La excepción a la cápsula de contexto: acá el título pesa, porque es lo único
   de la app que destruye algo. El ícono lo antecede y se pinta rojo cuando la
   acción es destructiva — que es la mitad de la advertencia, antes de leer. */
.modal-head-confirmar { align-items: flex-start; gap: 10px; padding: 16px 12px 8px 16px; }
.modal-head-confirmar h2 { margin: 0; font-size: 14.5px; font-weight: 600; line-height: 1.45; }
.modal-head-icono {
  display: flex; align-items: center; justify-content: center; flex-shrink: 0;
  width: 22px; height: 22px; border-radius: 6px;
  background: var(--bg-sunken); color: var(--text-2);
}
.modal-head-icono.is-peligro { background: rgba(163, 45, 45, .09); color: var(--danger); }
.modal-head-icono svg { width: 14px; height: 14px; stroke: currentColor; }
.modal-confirmar .modal-body { padding-left: 48px; }

/* ── Tom Select y el date, adentro del modal ────────────────────────────────
   Los dos controles que el navegador (o la librería) dibuja por su cuenta
   tenían caja blanca con borde mientras el resto de los campos son superficies
   hundidas sin borde. En un formulario de doce campos eso se ve como dos clases
   de campo, y no las hay: son todos lo mismo. Se alinean acá, en el modal, para
   no tocar el .ts-control de la grilla de la OC, que sí quiere borde. */
.modal .ts-control {
  border-color: transparent !important;
  background: var(--bg-sunken);
  border-radius: 7px;
  transition: background .1s ease, border-color .1s ease, box-shadow .1s ease;
}
.modal .ts-wrapper:not(.focus) .ts-control:hover { background: var(--bg-hover); }
.modal .ts-wrapper.focus .ts-control {
  background: var(--bg);
  border-color: var(--brand) !important;
  box-shadow: 0 0 0 3px rgba(24, 30, 38, .10);
}

/* El nativo `input[type=date]` trae su propio ícono de calendario y, en Chrome,
   su propio fondo: sin esto queda blanco entre campos grises. */
.modal .field input[type="date"] { background: var(--bg-sunken); }
.modal .field input[type="date"]:hover { background: var(--bg-hover); }
.modal .field input[type="date"]:focus { background: var(--bg); }
.modal .field input[type="date"]::-webkit-calendar-picker-indicator {
  opacity: .45; cursor: pointer;
}
.modal .field input[type="date"]:hover::-webkit-calendar-picker-indicator { opacity: .8; }

/* ── La puerta ──────────────────────────────────────────────────────────────
   La pantalla de login usa `base_blank.html`: sin sidebar. Mostrarle la
   navegación entera a alguien que todavía no entró es enseñarle el mapa de una
   casa cerrada — y de paso le canta qué secciones existen. */
.card-login { padding: 26px 28px 28px; text-align: left; }
.card-login h1 { margin: 0 0 4px; font-size: 17px; font-weight: 600; }
.card-login .sub { margin: 0 0 20px; font-size: 13px; color: var(--text-2); line-height: 1.5; }
.card-login .btn { width: 100%; height: 36px; justify-content: center; font-size: 13.5px; }

/* ── Quién está adentro, en el pie del sidebar ────────────────────────────── */
.sb-usuario {
  display: flex; align-items: center; gap: 8px;
  padding: 7px 8px; margin-bottom: 2px; border-radius: 6px;
  font-size: 13px; color: var(--text-2);
}
.sb-usuario:hover { background: var(--bg-hover); }
.sb-usuario-nombre {
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.sb-salir {
  margin-left: auto; flex-shrink: 0;
  width: 24px; height: 24px; display: flex; align-items: center; justify-content: center;
  border-radius: 5px; color: var(--text-3);
}
.sb-salir:hover { background: var(--bg-active); color: var(--text); }

/* ==========================================================================
   prductize — lo que esta app suma sobre el design system
   Cinco cosas y nada más: el chip de marca del sidebar, las etiquetas, el
   encabezado de totales, las columnas de la tabla de movimientos y los dos
   controles del modal (el segmento y la fila de colores).
   ========================================================================== */

/* El isotipo en el chip del sidebar: el anillo punteado gris sobre el carbón,
   que es exactamente el logo. Reemplaza a la letra del `.ws-logo` de Agrospot,
   y por eso apaga el `font-weight` que ya no dibuja nada. */
.ws-logo-anillo { color: var(--brand-anillo); }
.ws-logo-anillo svg { width: 15px; height: 15px; }

/* ── Etiquetas (los labels de Linear) ──────────────────────────────────────
   Todas la MISMA forma; lo único que cambia entre una y otra es el punto. Es
   deliberado: con ocho fondos de color, una fila con tres etiquetas se lee
   como una alerta y no como un dato. El punto alcanza para distinguirlas de un
   vistazo y no le roba peso a la cifra, que es lo que de verdad hay que leer
   en esta tabla. */
.label {
  display: inline-flex; align-items: center; gap: 5px;
  height: 20px; padding: 0 8px;
  border: 1px solid var(--border); border-radius: 10px;
  background: var(--bg); font-size: 11.5px; color: var(--text-2);
  white-space: nowrap; vertical-align: middle;
}
.label::before {
  content: ""; width: 7px; height: 7px; border-radius: 50%;
  background: var(--label-color, var(--text-3)); flex-shrink: 0;
}
.label + .label { margin-left: 4px; }

/* Los ocho colores, declarados UNA vez: la etiqueta los toma por el punto y el
   selector del modal los toma por la muestra. Cambiar un tono acá los cambia
   en los dos lugares. */
.label-gris,    .color-gris    { --label-color: #9a9ca1; }
.label-carbon,  .color-carbon  { --label-color: #22272e; }
.label-verde,   .color-verde   { --label-color: #1a7f52; }
.label-rojo,    .color-rojo    { --label-color: #a32d2d; }
.label-ambar,   .color-ambar   { --label-color: #c08a1e; }
.label-azul,    .color-azul    { --label-color: #2f6ca8; }
.label-violeta, .color-violeta { --label-color: #6b4ea8; }
.label-rosa,    .color-rosa    { --label-color: #b4477d; }

/* ── Las columnas de la tabla de movimientos ───────────────────────────────
   El monto va a la derecha y en tabular: es la única columna que se lee
   verticalmente, comparando, y desalineada deja de poder leerse así. */
.col-monto {
  text-align: right; white-space: nowrap;
  font-variant-numeric: tabular-nums; font-weight: 500;
}
.col-monto.is-ingreso { color: var(--ingreso); }
.col-monto.is-egreso  { color: var(--egreso); }
/* La moneda al lado de la cifra, en gris: es una unidad, no un valor. Repetirla
   en su propia columna daría 57 renglones que dicen «USD». */
.col-moneda { margin-left: 5px; font-size: 11.5px; color: var(--text-3); font-weight: 400; }
th.col-monto { color: var(--text-3); }
.col-fecha { font-variant-numeric: tabular-nums; color: var(--text-2); white-space: nowrap; }
.col-etiquetas { white-space: nowrap; }
.col-nota { color: var(--text-2); max-width: 280px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.col-n, th.col-n { text-align: right; font-variant-numeric: tabular-nums; width: 110px; }
th.col-n { color: var(--text-3); }
/* Un cliente archivado sigue en la lista —sus movimientos cuentan igual— pero
   atenuado: es un dato de la fila, no una fila de otra clase. */
tr.is-archivado .cell-name { color: var(--text-3); }

/* Los filtros del toolbar. `min-width` y no `width` para que el nombre del
   cliente más largo no quede cortado, y un tope para que tampoco se coma la
   barra entera. */
.filtro-select { min-width: 150px; max-width: 220px; }

/* ── El segmento (tipo y moneda, en el modal) ──────────────────────────────
   Dos o tres opciones fijas: se muestran las tres, no se esconden atrás de un
   desplegable. Es lo primero que se decide al cargar un movimiento, y con un
   <select> hay que abrirlo para saber qué está elegido. */
.segmento {
  display: flex; gap: 3px; padding: 3px; height: 34px;
  background: var(--bg-sunken); border-radius: 7px;
}
.segmento-op { flex: 1; position: relative; cursor: pointer; }
.segmento-op input { position: absolute; opacity: 0; pointer-events: none; }
.segmento-op span {
  display: flex; align-items: center; justify-content: center; height: 100%;
  border-radius: 5px; font-size: 12.5px; color: var(--text-2);
  transition: background .1s ease, color .1s ease, box-shadow .1s ease;
}
.segmento-op:hover span { color: var(--text); }
.segmento-op input:checked + span {
  background: var(--bg); color: var(--text); font-weight: 500;
  box-shadow: 0 1px 2px rgba(8, 9, 10, .08);
}
/* Ingreso y egreso son la ÚNICA excepción a que el segmento sea monocromo: el
   color acá no decora, es el dato — y es el mismo que va a tener el monto en
   la fila, así que elegirlo muestra cómo va a quedar. */
.op-ingreso input:checked + span { color: var(--ingreso); }
.op-egreso  input:checked + span { color: var(--egreso); }
.segmento-op input:focus-visible + span { box-shadow: 0 0 0 2px var(--brand); }

/* ── El selector de color de una etiqueta ────────────────────────────────── */
.colores { display: flex; gap: 8px; flex-wrap: wrap; }
.color-op { position: relative; cursor: pointer; line-height: 0; }
.color-op input { position: absolute; opacity: 0; pointer-events: none; }
.color-muestra {
  display: block; width: 22px; height: 22px; border-radius: 50%;
  background: var(--label-color);
  box-shadow: 0 0 0 2px var(--bg), 0 0 0 2px transparent;
  transition: box-shadow .1s ease;
}
/* El anillo del elegido va por fuera de la muestra y del mismo color, con un
   hueco blanco en el medio: sobre el gris claro, un borde del propio color es
   lo único que se lee como «éste» sin agrandar el círculo. */
.color-op input:checked + .color-muestra,
.color-op input:focus-visible + .color-muestra {
  box-shadow: 0 0 0 2px var(--bg), 0 0 0 4px var(--label-color);
}

/* La lista de lo que se va a borrar, en el modal de confirmación: es lo que
   convierte «¿seguro?» en una pregunta que se puede contestar. */
.confirm-lista {
  margin: 10px 0 0; padding-left: 18px;
  font-size: 13px; color: var(--text-2); line-height: 1.7;
}

/* Un checkbox con su texto al lado, en línea (el «Activo» del cliente). Reusa
   el dibujo del checkbox que ya define `.field-roles`. */
.opcion { display: inline-flex; align-items: center; gap: 8px; font-size: 13.5px; cursor: pointer; }

/* El error que no es de ningún campo en particular, arriba de todo el form. */
.err-form {
  margin-bottom: 14px; padding: 8px 10px;
  background: var(--egreso-soft); border-radius: 6px;
}

/* La tabla scrollea de costado ANTES que la página. Siete columnas de datos no
   entran en una ventana angosta, y sin esto lo que se corre es el layout
   entero: el sidebar se va de pantalla y el encabezado de totales queda a
   mitad de camino. Agrospot puede dejar `.table-wrap` en `visible` porque sus
   popovers cuelgan de la celda; acá el menú de fila vive fuera de
   `.content-inner` y es `position: fixed`, así que nada se recorta. */
.table-wrap { overflow-x: auto; }

/* Salir pasó de link a `<button>` dentro de un `<form>`: el LogoutView de
   Django sólo acepta POST. El botón necesita que le saquen el dibujo de botón
   del navegador, y el `margin-left: auto` que lo empujaba a la derecha tiene
   que vivir en el form, que es ahora el hijo directo del flex. */
.sb-usuario form { margin-left: auto; display: flex; }
.sb-salir { margin-left: 0; border: none; background: transparent; padding: 0; cursor: pointer; }
.sb-salir svg { width: 15px; height: 15px; stroke: currentColor; }


/* ==========================================================================
   Clientes — la lista y la página de cada uno (con lo de Tomi)
   Mismo idioma que el resto: gris, denso, y el carbón sólo donde hay algo
   encendido. El movimiento tiene una regla: sale rápido y se asienta despacio
   (ease-out fuerte), nunca más de 300ms, y sólo transform/opacity.
   ========================================================================== */
:root { --sop-ease: cubic-bezier(.23, 1, .32, 1); }

/* La entrada: cada bloque llega 40ms después del anterior. */
.sop-entra {
  animation: sop-entra 320ms var(--sop-ease) both;
  animation-delay: calc(min(var(--i, 0), 8) * 40ms + 40ms);
}
@keyframes sop-entra { from { opacity: 0; transform: translateY(6px); } }

.sop .btn { transition: background .12s ease, border-color .12s ease, transform 140ms var(--sop-ease); }
.sop .btn:active { transform: scale(.97); }
.sop-vacio { margin: 0; font-size: 12.5px; color: var(--text-3); }
.sop-mono, .cli-mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11.5px; }

/* ── La lista ───────────────────────────────────────────────────────────── */
.cli-fila { cursor: pointer; }
.cli-link { color: inherit; }
.cli-avatar {
  width: 20px; height: 20px; border-radius: 5px; flex-shrink: 0;
  display: inline-grid; place-items: center;
  background: var(--brand); color: var(--text-inv);
  font-size: 10.5px; font-weight: 600;
  transition: transform 160ms var(--sop-ease);
}
.cli-fila:active .cli-avatar { transform: scale(.9); }
.cell-name .cli-avatar { margin-right: 2px; }
.cli-avatar-lg { width: 32px; height: 32px; border-radius: 8px; font-size: 14px; }

.cli-chips { display: inline-flex; flex-wrap: wrap; gap: 5px; }
.cli-chip {
  display: inline-flex; align-items: center; gap: 6px;
  height: 22px; padding: 0 8px; border-radius: 20px;
  font-size: 12px; color: var(--text-2); white-space: nowrap;
  background: var(--bg-sunken); border: 1px solid var(--border);
}
.cli-chip svg { width: 12px; height: 12px; stroke: currentColor; }
.cli-chip-link { transition: border-color .12s ease, color .12s ease; }
.cli-chip-link:hover { border-color: var(--border-strong); color: var(--text); }
.cli-chip-link svg { transition: transform 180ms var(--sop-ease); }
.cli-chip-link:hover svg { transform: translate(1px, -1px); }

/* El punto de los grupos: hueco si Tomi no contesta en ninguno, lleno si
   contesta, y late si lo están llamando ahora. */
.cli-dot {
  position: relative; display: inline-block; flex-shrink: 0;
  width: 7px; height: 7px; border-radius: 50%;
  border: 1.5px solid var(--text-3);
}
.cli-dot.is-on { background: var(--brand); border-color: var(--brand); }
.cli-dot.is-atendiendo::after {
  content: ""; position: absolute; inset: -1.5px; border-radius: 50%;
  background: var(--brand);
  animation: sop-pulso 1.4s var(--sop-ease) infinite;
}
@keyframes sop-pulso { from { transform: scale(1); opacity: .5; } to { transform: scale(2.8); opacity: 0; } }

/* «Está mirando…»: un brillo que recorre el texto mientras atiende. */
.sop-mirando {
  background: linear-gradient(90deg, var(--text-3) 20%, var(--text) 50%, var(--text-3) 80%);
  background-size: 200% 100%;
  -webkit-background-clip: text; background-clip: text; color: transparent;
  animation: sop-brillo 1.6s linear infinite;
}
@keyframes sop-brillo { from { background-position: 100% 0; } to { background-position: -100% 0; } }

/* ── Los grupos sin cliente (al pie de la lista) ─────────────────────────── */
.sop-sueltos { margin-top: 36px; }
.sop-sec-head h2 { display: flex; align-items: center; gap: 8px; margin: 0; font-size: 13.5px; font-weight: 600; }
.sop-sec-head .sub { margin: 2px 0 12px; }
.sop-n {
  font-size: 11px; font-weight: 500; color: var(--text-2);
  background: var(--bg-sunken); border-radius: 10px; padding: 0 7px; line-height: 18px;
}
/* El <select> lo adorna Tom Select (base.html): se le da el ancho al envoltorio. */
.sop-suelto .ts-wrapper { width: 200px; flex-shrink: 0; }
.sop-suelto.htmx-request, .cli-sumar.htmx-request { opacity: .6; transition: opacity .12s ease; }

/* ── La página del cliente ──────────────────────────────────────────────── */
.cli-titulo { gap: 12px; }
.cli-titulo .btn { margin-left: auto; }
.cli-desc {
  margin: 4px 0 28px; max-width: 68ch;
  font-size: 14px; line-height: 1.6; color: var(--text-2);
}
.cli-detalle .section-head .sub { margin: 0; }
.cli-detalle .section-head .btn { margin-left: auto; }
.cli-notas { font-size: 13px; line-height: 1.6; color: var(--text); }
.cli-estado { display: inline-flex; align-items: center; gap: 7px; }

.cli-sumar { display: flex; gap: 8px; align-items: center; margin-top: 10px; }
.cli-sumar .ts-wrapper { flex: 1; max-width: 360px; }

/* ── Un grupo ───────────────────────────────────────────────────────────── */
.sop-grupo, .sop-suelto {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 8px 8px 10px;
  border: 1px solid var(--border); border-radius: var(--radius-lg);
  background: var(--bg);
}
.sop-grupo + .sop-grupo, .sop-suelto + .sop-suelto { margin-top: 6px; }
.sop-grupo-ic {
  width: 28px; height: 28px; border-radius: 7px; flex-shrink: 0;
  display: grid; place-items: center;
  background: var(--bg-sunken); color: var(--text-2);
  transition: opacity .2s ease;
}
.sop-grupo-ic svg { width: 15px; height: 15px; }
.sop-grupo.is-apagado .sop-grupo-ic { opacity: .45; }
.sop-grupo-txt { display: flex; flex-direction: column; flex: 1; min-width: 0; }
.sop-grupo-nombre { font-size: 13px; font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sop-grupo-sub { font-size: 12px; color: var(--text-3); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* El grupo que recién se sumó llega con un anillo que se apaga: dice «acá
   quedó» sin un toast. */
.sop-grupo.is-nuevo { animation: sop-nuevo 1100ms var(--sop-ease) both; }
@keyframes sop-nuevo {
  0%   { opacity: 0; transform: translateY(-4px) scale(.98); box-shadow: 0 0 0 3px rgba(24, 30, 38, .16); }
  30%  { opacity: 1; transform: none; box-shadow: 0 0 0 3px rgba(24, 30, 38, .16); }
  100% { box-shadow: 0 0 0 3px rgba(24, 30, 38, 0); }
}

/* El switch. Se estira al apretarlo, como los de iOS, y el pulgar viaja con
   ease-out: sale apenas lo tocás. */
.sop-switch-form { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }
.sop-switch-label { font-size: 12px; color: var(--text-2); min-width: 54px; text-align: right; }
.sop-switch {
  position: relative; flex-shrink: 0;
  width: 32px; height: 18px; padding: 0; border: 0; border-radius: 20px;
  background: var(--border-strong); cursor: pointer;
  transition: background 200ms ease;
}
.sop-switch span {
  position: absolute; top: 2px; left: 2px;
  width: 14px; height: 14px; border-radius: 50%;
  background: #fff; box-shadow: 0 1px 2px rgba(8, 9, 10, .22);
  transform-origin: left center;
  transition: transform 240ms var(--sop-ease);
}
.sop-switch:active span { transform: scaleX(1.25); }
.sop-switch[aria-checked="true"] { background: var(--brand); }
.sop-switch[aria-checked="true"] span { transform: translateX(14px); transform-origin: right center; }
.sop-switch[aria-checked="true"]:active span { transform: translateX(14px) scaleX(1.25); }
.sop-switch:focus-visible { outline: none; box-shadow: 0 0 0 3px rgba(24, 30, 38, .16); }

/* Sacar el grupo del cliente: aparece al pasar por el renglón. */
.sop-quitar {
  width: 24px; height: 24px; padding: 0; border: 0; border-radius: 6px; flex-shrink: 0;
  display: grid; place-items: center;
  background: transparent; color: var(--text-3); cursor: pointer;
  transition: opacity .12s ease, background .12s ease, color .12s ease;
}
.sop-quitar svg { width: 13px; height: 13px; stroke: currentColor; }
.sop-quitar:hover { background: var(--bg-hover); color: var(--text); }
@media (hover: hover) and (pointer: fine) {
  .sop-quitar { opacity: 0; }
  .sop-grupo:hover .sop-quitar, .sop-quitar:focus-visible { opacity: 1; }
}

/* ── Lo que hizo Tomi: una línea de tiempo ──────────────────────────────── */
.act { list-style: none; margin: 0; padding: 0; }
.act-item { position: relative; display: flex; gap: 12px; padding-bottom: 18px; }
/* La línea que une los puntos, menos en el último. */
.act-item:not(:last-child)::before {
  content: ""; position: absolute; left: 3.5px; top: 14px; bottom: 0;
  width: 1px; background: var(--border);
}
.act-punto {
  width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; margin-top: 6px;
  background: var(--bg); border: 1.5px solid var(--text-3);
}
.act-item:first-child .act-punto { background: var(--brand); border-color: var(--brand); }
.act-item.is-error .act-punto { border-color: var(--danger); background: var(--bg); }
.act-cuerpo { flex: 1; min-width: 0; }
.act-head { font-size: 13px; color: var(--text-2); }
.act-quien { color: var(--text); font-weight: 500; }
.act-cuando { color: var(--text-3); }
.act-pedido {
  margin: 4px 0 0; padding-left: 10px; border-left: 2px solid var(--border);
  font-size: 13px; color: var(--text-2);
}
.act-resp {
  display: flex; gap: 8px; margin-top: 8px; padding: 8px 10px;
  background: var(--bg-sunken); border-radius: var(--radius-lg);
  font-size: 13px; line-height: 1.55;
}
.act-tomi { font-weight: 600; flex-shrink: 0; }
.act-resp-txt {
  display: -webkit-box; -webkit-line-clamp: 5; -webkit-box-orient: vertical; overflow: hidden;
  overflow-wrap: anywhere;
}
.act-resumen { margin: 4px 0 0; font-size: 12.5px; color: var(--text-3); }
.act-issues { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 8px; }
.act-error { margin: 6px 0 0; font-size: 12px; color: var(--danger); }

/* ── Links que salen de la app ──────────────────────────────────────────── */
.sop-link {
  display: inline-flex; align-items: center; gap: 4px;
  color: var(--text); text-decoration: none;
  border-bottom: 1px solid var(--border-strong);
  transition: border-color .12s ease;
}
.sop-link svg { width: 12px; height: 12px; stroke: currentColor; color: var(--text-3); transition: transform 180ms var(--sop-ease), color .12s ease; }
.sop-link:hover { border-color: var(--text); }
.sop-link:hover svg { color: var(--text); transform: translate(1px, -1px); }

/* ── La memoria ─────────────────────────────────────────────────────────── */
.sop-memoria-texto {
  font-size: 13px; line-height: 1.6; color: var(--text);
  background: var(--bg-sunken); border-radius: var(--radius-lg); padding: 10px 12px;
}
.sop-memoria-texto.is-larga {
  display: -webkit-box; -webkit-line-clamp: 6; -webkit-box-orient: vertical; overflow: hidden;
  -webkit-mask-image: linear-gradient(to bottom, #000 65%, transparent);
  mask-image: linear-gradient(to bottom, #000 65%, transparent);
}
.sop-ver {
  margin-top: 6px; padding: 2px 0; border: 0; background: none; cursor: pointer;
  font-size: 12.5px; color: var(--text-2);
}
.sop-ver:hover { color: var(--text); text-decoration: underline; }

.cli-form-sec { margin: 22px 0 12px; padding-top: 16px; border-top: 1px solid var(--border); }

@media (max-width: 760px) {
  .cli-detalle .detail-cols { grid-template-columns: minmax(0, 1fr); }
  .sop-suelto { flex-wrap: wrap; }
  .sop-suelto .ts-wrapper { flex: 1; width: auto; }
}

/* Menos movimiento: se queda lo que ayuda a entender (los fundidos) y se va
   todo lo que se desplaza, escala o late. */
@media (prefers-reduced-motion: reduce) {
  .sop-entra, .sop-grupo.is-nuevo { animation: sop-fundido 200ms ease both; }
  .cli-dot.is-atendiendo::after, .sop-mirando { animation: none; }
  .sop-mirando { color: var(--text); background: none; }
  .sop-switch span, .cli-avatar, .sop .btn, .sop-link svg, .cli-chip-link svg { transition-property: opacity, background, color, border-color; }
}
@keyframes sop-fundido { from { opacity: 0; } }

/* ── Contratos ─────────────────────────────────────────────────────────────
   La tab Contrato de la página del cliente: el PDF embebido a la izquierda y,
   a la derecha, los pasos, el mail tal cual sale y lo que contestó. Afuera, la
   tarjeta de aceptar es la de la puerta, un poco más ancha. */
.cli-tabs { margin: 22px 0 18px; }

.ctr-vacio {
  display: flex; flex-direction: column; align-items: center; text-align: center;
  padding: 56px 24px; border: 1px dashed var(--border); border-radius: var(--radius-lg);
}
.ctr-vacio p { margin: 4px 0 0; max-width: 380px; }
.ctr-vacio-ic {
  width: 38px; height: 38px; border-radius: 10px; margin-bottom: 10px;
  display: flex; align-items: center; justify-content: center;
  background: var(--bg-sunken); color: var(--text-2);
}
.ctr-vacio-ic svg { width: 19px; height: 19px; stroke: currentColor; }

.ctr-otros { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 14px; }
.ctr-chip.is-actual { border-color: var(--text); color: var(--text); }
.ctr-chip-estado { font-size: 11px; color: var(--text-3); }

.ctr-cab { display: flex; align-items: center; justify-content: space-between; gap: 16px; flex-wrap: wrap; }
.ctr-cab-txt { display: flex; align-items: center; gap: 10px; min-width: 0; }
.ctr-titulo { margin: 0; font-size: 16px; font-weight: 600; letter-spacing: -0.01em; }
.ctr-cab-acciones { display: flex; align-items: center; gap: 6px; }
.ctr-cab-acciones .btn svg { width: 14px; height: 14px; stroke: currentColor; }
.ctr-cab-acciones .btn[disabled] { opacity: .45; cursor: not-allowed; }

.ctr-estado { font-size: 11px; padding: 1px 8px; }
.ctr-estado.is-aceptado, .ctr-chip-estado.is-aceptado { color: #1b7a3d; }
.ctr-estado.is-aceptado { background: #e8f5ec; border-color: #c4e5cf; }
.ctr-estado.is-enviado, .ctr-chip-estado.is-enviado { color: #8a5a00; }
.ctr-estado.is-enviado { background: #fdf3e1; border-color: #f2ddb4; }

/* Los cuatro pasos en una línea: el punto se llena cuando el paso pasó. */
.ctr-pasos {
  list-style: none; margin: 16px 0 18px; padding: 14px 16px;
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 12px;
  border: 1px solid var(--border); border-radius: var(--radius-lg); background: var(--bg);
}
.ctr-paso { position: relative; display: grid; grid-template-columns: 14px 1fr; column-gap: 8px; row-gap: 2px; }
.ctr-paso-punto {
  grid-row: span 2; margin-top: 3px; width: 10px; height: 10px; border-radius: 50%;
  border: 1.5px solid var(--border-strong); background: var(--bg);
}
.ctr-paso.is-hecho .ctr-paso-punto { background: var(--text); border-color: var(--text); }
.ctr-paso.is-final .ctr-paso-punto { background: #1f9d55; border-color: #1f9d55; }
.ctr-paso-nombre { font-size: 12.5px; font-weight: 500; color: var(--text-3); }
.ctr-paso.is-hecho .ctr-paso-nombre { color: var(--text); }
.ctr-paso-dato { font-size: 12px; color: var(--text-3); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

.ctr-cuerpo { display: grid; grid-template-columns: minmax(0, 1.35fr) minmax(300px, 1fr); gap: 20px; align-items: start; }
.ctr-doc {
  border: 1px solid var(--border); border-radius: var(--radius-lg); overflow: hidden;
  background: var(--bg-sunken); height: calc(100vh - 150px); min-height: 560px;
}
.ctr-doc iframe { display: block; width: 100%; height: 100%; border: 0; }
.ctr-lado { display: flex; flex-direction: column; gap: 14px; position: sticky; top: 16px; }

.ctr-caja { border: 1px solid var(--border); border-radius: var(--radius-lg); background: var(--bg); padding: 14px 16px; }
.ctr-caja-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 10px; }
.ctr-caja-head h3 { margin: 0; font-size: 13px; font-weight: 600; }
.ctr-caja-head .btn svg { width: 13px; height: 13px; stroke: currentColor; }

.ctr-mail-meta { display: grid; grid-template-columns: 52px 1fr; gap: 5px 10px; margin: 0; font-size: 12.5px; }
.ctr-mail-meta dt { color: var(--text-3); }
.ctr-mail-meta dd { margin: 0; min-width: 0; overflow-wrap: anywhere; }
.ctr-mail-asunto { font-weight: 500; }
.ctr-mail-cuerpo {
  margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--border);
  font-size: 13px; line-height: 1.55; white-space: pre-wrap; overflow-wrap: anywhere;
}
.ctr-mail-adjunto {
  display: inline-flex; align-items: center; gap: 6px; margin-top: 12px;
  padding: 4px 9px; border: 1px solid var(--border); border-radius: 6px;
  font-size: 12px; color: var(--text-2); background: var(--bg-sunken);
}
.ctr-mail-adjunto svg { width: 13px; height: 13px; stroke: currentColor; }
.ctr-falta {
  border: 0; padding: 0; background: none; cursor: pointer;
  color: #b25e00; font: inherit; text-decoration: underline; text-underline-offset: 2px;
}
.ctr-mono { font-family: ui-monospace, monospace; font-size: 11.5px; }

.ctr-resp + .ctr-resp { margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--border); }
.ctr-resp-head { display: flex; justify-content: space-between; gap: 8px; font-size: 12.5px; }
.ctr-resp-asunto { font-weight: 500; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ctr-resp-fecha { color: var(--text-3); flex-shrink: 0; }
.ctr-resp-txt { margin-top: 3px; font-size: 12.5px; color: var(--text-2); }

@media (max-width: 1100px) {
  .ctr-cuerpo { grid-template-columns: minmax(0, 1fr); }
  .ctr-lado { position: static; }
  .ctr-pasos { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

.ctr-card { max-width: 480px; }
.ctr-datos {
  display: grid; grid-template-columns: auto 1fr; gap: 6px 14px;
  margin: 0 0 20px; font-size: 13px;
}
.ctr-datos dt { color: var(--text-3); }
.ctr-datos dd { margin: 0; min-width: 0; }
.ctr-datos a { color: var(--text); text-decoration: underline; text-underline-offset: 2px; }
.ctr-acepto { display: flex; gap: 8px; align-items: flex-start; margin: 4px 0 16px; font-size: 13px; line-height: 1.4; }
.ctr-acepto input { margin-top: 2px; }
.ctr-legal { margin: 12px 0 0 !important; font-size: 11.5px; color: var(--text-3); line-height: 1.5; }
