/* ============================================================
   app.css — CRM THIS IS BP · Dirección D «Mac nativo»
   Componentes según docs/design/sistema-visual.md §4–§5.
   Mobile (<900px): tab bar inferior. Desktop (≥900px): sidebar.
   Dark: automático por tokens (prefers-color-scheme).
   ============================================================ */

/* ---- Inter self-hosted (fallback no-Apple). Licencia SIL OFL 1.1
       — ver fonts/OFL.txt. En Apple no se descarga (resuelve
       -apple-system primero). ---- */
@font-face {
  font-family: "Inter var";
  src: url("../fonts/InterVariable.woff2") format("woff2-variations");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

/* ============ Base ============ */
* { box-sizing: border-box; }
html, body { height: 100%; }
body {
  margin: 0;
  font-family: var(--font); font-size: var(--fs-body); line-height: 1.45;
  background: var(--bg-app); color: var(--text-1);
  -webkit-font-smoothing: antialiased; font-feature-settings: "cv05" 1;
  padding-bottom: calc(var(--nav-h) + var(--safe-b));
}
body.chromeless { padding-bottom: 0; }
h1, h2, h3 { margin: 0; }
p { margin: 0; }
a { color: inherit; }
button { font: inherit; color: inherit; }
input, select, textarea { font: inherit; color: inherit; }
.num { font-variant-numeric: tabular-nums lining-nums; }
[hidden] { display: none !important; }  /* el atributo hidden gana SIEMPRE (aún con display propio) */
:focus-visible { outline: none; box-shadow: var(--sh-focus); border-radius: var(--r-sm); }
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation: none !important; transition: none !important; }
}
/* Utilidades de formato: .m-only visible solo <900px, .d-only solo ≥900px.
   Media-scoped (no pisan el display propio del elemento al mostrarse). */
@media (max-width: 899.98px) { .d-only { display: none !important; } }

#app { min-height: 100%; }
.view {
  max-width: var(--content-w);
  margin: 0 auto;
  padding: 0 var(--sp-4) var(--sp-8);
  display: flex; flex-direction: column; gap: var(--sp-4);
}
/* El MISMO ritmo de módulos que `.view`, para wrappers internos (paneles que alternan, etc.).
   Un wrapper sin esta clase rompe el sistema en silencio: sus hijos quedan pegados a 0px aunque
   la vista de al lado respire a 16 — medido en Cobranzas el 2026-08-30 (5 costuras en 0px
   exacto). Si un contenedor agrupa módulos, lleva `.stack`; el gap no se redefine por vista. */
.stack { display: flex; flex-direction: column; gap: var(--sp-4); }

/* ============ App bar mobile (material) ============ */
.appbar {
  position: sticky; top: 0; z-index: var(--z-bar);
  margin: 0 calc(-1 * var(--sp-4));
  padding: calc(var(--sp-2) + var(--safe-t)) var(--sp-4) var(--sp-2);
  background: var(--mat-appbar);
  -webkit-backdrop-filter: var(--mat-blur); backdrop-filter: var(--mat-blur);
  box-shadow: 0 .5px 0 var(--hairline);
  display: flex; flex-direction: column; gap: 4px;
}
.appbar-date {
  font-size: var(--fs-caption); font-weight: 700; letter-spacing: .08em;
  text-transform: uppercase; color: var(--text-3);
}
.appbar-row { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); }
.large-title { font-size: var(--fs-large-title); font-weight: 800; letter-spacing: -.035em; line-height: 1.05; }
.view-title { font-size: var(--fs-title-2); font-weight: 800; letter-spacing: -.025em; }
.appbar-sub {
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3);
  font-size: var(--fs-sm); color: var(--text-2);
}
.appbar-sub b { font-weight: 600; color: var(--text-1); }
.avatar {
  display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0;
  width: 38px; height: 38px; border-radius: 50%;
  background: linear-gradient(135deg, #5b5b63, #2e2e33);
  color: #fff; font-size: 14px; font-weight: 700; letter-spacing: .01em;
  text-decoration: none;
  position: relative;
}
/* Target táctil ≥44px sin agrandar el visual (§1.6): hit area extendida e invisible. */
a.avatar::after {
  content: ""; position: absolute; inset: -4px; border-radius: 50%;
}
.avatar.sm { width: 30px; height: 30px; font-size: 11px; }
.avatar.big { width: 48px; height: 48px; font-size: 17px; }

/* Topbar desktop (misma estructura, se re-estiliza ≥900px) */
.topbar-actions { display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap; }

/* ============ Botones ============ */
.btn {
  min-height: var(--touch);
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
  padding: 0 var(--sp-4);
  border-radius: 12px; border: 1px solid transparent;
  font-size: var(--fs-body); font-weight: 600;
  text-decoration: none; cursor: pointer; white-space: nowrap;
  transition: background var(--t-fast) var(--ease), box-shadow var(--t-fast) var(--ease),
              transform var(--t-fast) var(--ease), filter var(--t-fast) var(--ease);
  -webkit-tap-highlight-color: transparent;
}
.btn:active { transform: scale(.97); filter: brightness(.94); }
.btn:disabled { cursor: not-allowed; }
.btn:disabled:active { transform: none; filter: none; }
.btn-block { width: 100%; }
.btn-sm { min-height: 36px; padding: 0 var(--sp-3); font-size: var(--fs-sm); }
.btn-fill, .btn-primary { background: var(--blue-solid); color: #fff; } /* .btn-primary: alias legado (router.js no se toca) */
.btn-wa { background: var(--green-soft); color: var(--green-text); border-color: var(--green-border); }
.btn-wa:hover { background: rgba(52,199,89,.16); }
.btn-ghost { background: var(--surface-2); color: var(--text-1); border-color: var(--border); }
.btn-ghost:hover { background: var(--surface-3); }
.btn-danger { background: var(--surface-2); color: var(--red-text); border-color: var(--border); }
.btn:disabled, .btn-wa:disabled, .btn-ghost:disabled {
  background: var(--surface-2); color: var(--text-4); border-color: var(--border);
}
.btn .ico { display: inline-flex; }

/* ============ Formularios ============ */
.field-label {
  display: block; font-size: var(--fs-caption); font-weight: 600; letter-spacing: .03em;
  text-transform: uppercase; color: var(--text-3); margin: var(--sp-3) 0 6px;
}
input[type="text"], input[type="search"], input[type="password"], input[type="email"], select {
  width: 100%; min-height: var(--touch);
  padding: 0 var(--sp-3);
  border: 1px solid var(--border-strong); border-radius: var(--r-sm);
  background: var(--surface); color: var(--text-1); font-size: var(--fs-body-lg);
}
input::placeholder { color: var(--text-4); }
input:focus-visible, select:focus-visible { border-color: var(--blue); box-shadow: var(--sh-focus); outline: none; }
.form-error {
  color: var(--red-text); background: var(--red-soft); border: 1px solid var(--red-border);
  border-radius: var(--r-sm); padding: var(--sp-2) var(--sp-3); font-size: var(--fs-sm);
}
/* Mensaje neutro/positivo de un form (2026-08-30, recuperación de contraseña) — mismo molde
   que .form-error, tokens de verde en vez de rojo. */
.form-success {
  color: var(--green-text); background: var(--green-soft); border: 1px solid var(--green-border);
  border-radius: var(--r-sm); padding: var(--sp-2) var(--sp-3); font-size: var(--fs-sm);
}
.toggle-chip {
  display: inline-flex; align-items: center; gap: 6px; min-height: var(--touch);
  padding: 0 var(--sp-3); border: 1px solid var(--border-strong); border-radius: var(--r-pill);
  background: var(--surface); font-size: var(--fs-sm); font-weight: 500;
  white-space: nowrap; cursor: pointer;
}
.toggle-chip input { width: 16px; height: 16px; accent-color: var(--blue); }

/* ============ Switch (interruptor iOS) ============
   Componente REUSABLE, nombre genérico a propósito (2026-09-02, pedido de Fernando: "necesito un
   boton arriba como los de apple para prender o apagar algo"): nace para "Mostrar hechas" de la
   Agenda, pero la forma —un booleano que cambia QUÉ se ve— se repite.

   ⚠️ NO reemplaza a `.toggle-chip`, que sigue vivo en 5 vistas (clientes, cobranzas, problemas,
   notas-cuenta, ficha-odoo). La regla para que no se mezclen, escrita acá para que dentro de dos
   meses no haya media app migrada y media no (§4.1 nº6 en cámara lenta): `.toggle-chip` cuando el
   control es UNO de varios tildes que se suman (un facet); `.switch` cuando es un MODO de la
   pantalla.

   El ESTADO vive en `aria-checked` y en ningún otro lado — el selector de pintura es
   `[aria-checked="true"]`, no una clase `.on` paralela como en `.seg`/`.opt`. Así lo que se ve y
   lo que anuncia el lector de pantalla no pueden decir cosas distintas. */
.switch {
  display: inline-flex; align-items: center; gap: var(--sp-2);
  /* 32px = la altura EXACTA de `.seg` (28 del botón + 2+2 del padding del riel): puestos en la
     misma fila de filtros, los dos comparten línea de base sin ajuste manual. */
  min-height: 32px;
  /* Sin padding, sin borde, sin fondo: la caja es el texto + el riel y nada más. El blanco táctil
     se agrega abajo con `::after` — agrandar ESTA caja descoloca la fila (regla del proyecto). */
  padding: 0; border: none; background: none;
  font-size: var(--fs-sm); font-weight: 500; color: var(--text-1);
  white-space: nowrap; cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
/* El foco visible NO se declara acá: el `:focus-visible` global de más arriba ya envuelve al
   botón ENTERO (texto + riel), que es el anillo correcto. Repetirlo sería una segunda definición. */
.switch:disabled { cursor: not-allowed; color: var(--text-4); }
.switch-track {
  position: relative; flex-shrink: 0;
  /* 40×24 con pulgar de 18 y 2px de aire: la proporción del switch de iOS al ritmo de esta app.
     El recorrido queda en 16px exactos = 38 (caja interna) − 18 − 2. */
  width: 40px; height: 24px; border-radius: var(--r-pill);
  /* Apagado: el MISMO fill que el segmented (`--gray-soft` es, textualmente, "fill de segmented
     iOS" en tokens.css). Es translúcido, así que se adapta solo al fondo y el modo oscuro sale
     gratis. El borde da contorno al control apagado, que sobre `--bg-app` claro queda casi del
     mismo tono que el riel. */
  background: var(--gray-soft);
  border: 1px solid var(--border-strong);
  transition: background-color var(--t-med) var(--ease), border-color var(--t-med) var(--ease),
              transform var(--t-fast) var(--ease);
}
.switch-thumb {
  position: absolute; top: 2px; left: 2px; width: 18px; height: 18px; border-radius: 50%;
  /* El ÚNICO hex crudo del componente, y a propósito: el pulgar es blanco en los dos temas, igual
     que en iOS. Sobre el riel apagado claro el blanco casi no contrasta y lo que lo separa es
     `--sh-seg` — el mismo token del pulgar del segmented, exactamente el mismo caso. */
  background: #fff; box-shadow: var(--sh-seg);
  /* `transform` y no `left`: la única propiedad que anima en el compositor. `--t-med` (220ms) y no
     `--t-fast`: éste es el único control de la app cuyo sentido ES verse deslizar; a 120ms el
     gesto se pierde. `prefers-reduced-motion` ya mata toda transición más arriba. */
  transition: transform var(--t-med) var(--ease);
}
/* Encendido: `--blue-solid`, NO verde. Medido con la fórmula WCAG sobre los propios tokens: blanco
   sobre `--blue-solid` (#0071e3) da 4,70:1 en claro Y en oscuro (tokens.css:32), muy por encima
   del 3:1 que pide 1.4.11 para un objeto gráfico. Blanco sobre `--green` (#34c759) da ~1,9:1: el
   pulgar DESAPARECE contra el riel encendido. No existe `--green-solid` e inventarlo es otra
   tarea, con su propia medición en los dos temas. */
.switch[aria-checked="true"] .switch-track { background: var(--blue-solid); border-color: var(--blue-solid); }
.switch[aria-checked="true"] .switch-thumb { transform: translateX(16px); }
.switch:disabled .switch-track { opacity: .5; }
/* Mismo feedback de presión que `.btn`/`.opt` — una sola gramática de "lo estoy tocando". */
.switch:active:not(:disabled) .switch-track { transform: scale(.96); }
/* Blanco táctil ≥44px sin agrandar la caja visual (mismo truco que `.seg button`, `.opt` y
   `.tc-del`): 32 + 6 + 6 = 44 exactos. */
@media (pointer: coarse) {
  .switch { position: relative; }
  .switch::after { content: ""; position: absolute; inset: -6px -4px; }
}

/* Segmented control iOS */
.seg {
  display: inline-flex; align-items: center; gap: 2px;
  background: var(--gray-soft); border-radius: 9px; padding: 2px;
}
.seg button {
  border: none; background: transparent; border-radius: 7px;
  min-height: 28px; padding: 0 12px;
  font-size: var(--fs-caption); font-weight: 600; color: var(--text-2);
  cursor: pointer;
}
.seg button.on { background: var(--surface); color: var(--text-1); box-shadow: var(--sh-seg); }
.seg button:disabled { color: var(--text-4); cursor: not-allowed; }
/* Blanco táctil ≥44px en coarse sin agrandar el chip visual (mismo truco que `.cf-opt`/`.opt`).
   Los chips miden 28px de alto en TODAS las vistas que usan .seg (pipeline, embudo, cobranzas);
   detectado midiendo el embudo en el teléfono, el fix va acá porque la definición es común (§4.1 #13). */
@media (pointer: coarse) {
  .seg button { position: relative; }
  .seg button::after { content: ""; position: absolute; inset: -9px -2px; }
}

/* Buscador estilo macOS (desktop) */
.searchbar {
  display: inline-flex; align-items: center; gap: 7px;
  background: var(--gray-soft); border-radius: 8px;
  min-width: 230px; height: 30px; padding: 0 10px;
  color: var(--text-3);
}
.searchbar input {
  border: none; background: transparent; min-height: 0; height: 100%;
  padding: 0; font-size: var(--fs-sm); color: var(--text-1); width: 100%;
}
.searchbar input:focus-visible { box-shadow: none; border: none; }
.searchbar:focus-within { box-shadow: var(--sh-focus); }

/* ============ Auth (login / cambiar contraseña) ============ */
.auth-screen { min-height: 100dvh; display: flex; align-items: center; justify-content: center; padding: var(--sp-6) var(--sp-4); }
.auth-card { width: 100%; max-width: 380px; display: flex; flex-direction: column; gap: var(--sp-1); }
.auth-brand { display: flex; justify-content: center; margin-bottom: var(--sp-2); }
.brand-logo { display: inline-block; line-height: 0; }
.brand-logo img { display: block; height: auto; }
.auth-sub { color: var(--text-2); font-size: var(--fs-sm); margin-bottom: var(--sp-3); }
/* Bajo el logo centrado (2026-09-03) el subtítulo también va centrado; en Recuperar contraseña no hay logo y queda a la izquierda. */
.auth-brand + .auth-sub { text-align: center; }
.login-form { display: flex; flex-direction: column; }
.login-form .form-error, .login-form .form-success { margin-top: var(--sp-3); }
.login-form > .btn { margin-top: var(--sp-4); }
/* Link "¿Olvidaste tu contraseña?" (2026-08-30): slot vacío hasta que recover-config confirma
   que corresponde mostrarlo — `:empty` evita el hueco/margen mientras tanto. */
.auth-forgot { margin-top: var(--sp-3); text-align: center; }
.auth-forgot:empty { display: none; }
.demo-block { margin-top: var(--sp-6); padding-top: var(--sp-4); border-top: 1px dashed var(--border-strong); }
.demo-title { font-size: var(--fs-chip); font-weight: 700; text-transform: uppercase; letter-spacing: .04em; color: var(--text-3); margin-bottom: var(--sp-2); }
.demo-actions { display: flex; flex-direction: column; gap: 6px; }

/* ============ Chips ============ */
.chip {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 3px 9px; border-radius: var(--r-pill); border: 1px solid transparent;
  font-size: var(--fs-chip); font-weight: 700; letter-spacing: .01em;
  line-height: 1.35; white-space: nowrap;
}
.chip.mini { font-size: 10px; padding: 2px 7px; border-radius: 6px; }
.chip .cdot { width: 5px; height: 5px; border-radius: 50%; background: currentColor; flex-shrink: 0; }
.chip .ico { display: inline-flex; flex-shrink: 0; line-height: 0; margin-right: -1px; } /* ícono de referencia en lugar del punto (chip(text, kind, mini, icon)) */
.chip.red    { background: var(--red-soft);    color: var(--red-text);    border-color: var(--red-border); }
.chip.orange { background: var(--orange-soft); color: var(--orange-text); border-color: var(--orange-border); }
.chip.blue   { background: var(--blue-soft);   color: var(--blue-chip-text); border-color: var(--blue-border); } /* token propio: --blue-text falla AA en oscuro sobre el soft (4,01) */
.chip.green  { background: var(--green-soft);  color: var(--green-text);  border-color: var(--green-border); }
.chip.yellow { background: var(--yellow-soft); color: var(--yellow-text); border-color: var(--yellow-border); }
/* Bordó = chip RELLENO (semáforo BCRA, escalón 4+): el peor nivel pesa más que el rojo soft. */
.chip.bordo  { background: var(--bordo-bg);    color: #fff;               border-color: var(--bordo-bg); }
.chip.gray   { background: var(--gray-soft);   color: var(--text-2);      border-color: var(--border); }
.chips-row { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }

/* ============ Frescura ============ */
.badge-fresh {
  display: inline-flex; align-items: center; gap: 6px;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-pill);
  box-shadow: var(--sh-1); padding: 3px 10px;
  font-size: var(--fs-chip); font-weight: 600; color: var(--text-2); white-space: nowrap;
}
.badge-fresh .dot { width: 6px; height: 6px; border-radius: 50%; background: var(--green); flex-shrink: 0; }
.badge-fresh.is-stale { color: var(--orange-text); border-color: var(--orange-border); }
.badge-fresh.is-stale .dot { background: var(--orange); }
.badge-fresh.is-loading { color: var(--text-3); }
.alert-banner {
  display: flex; align-items: flex-start; gap: var(--sp-2);
  padding: var(--sp-3); border-radius: var(--r); font-size: var(--fs-sm);
}
.alert-banner .ico { flex-shrink: 0; display: inline-flex; margin-top: 1px; }
.alert-banner.warn { background: var(--orange-soft); border: 1px solid var(--orange-border); color: var(--orange-text); }

/* ============ Tarjeta «Hoy» ============ */
.feed { display: flex; flex-direction: column; gap: var(--sp-3); }
.today-card {
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-lg);
  box-shadow: var(--sh-1); padding: var(--sp-4);
  display: flex; flex-direction: column; gap: 0;
  transition: box-shadow var(--t-fast) var(--ease), transform var(--t-fast) var(--ease);
}
.today-card:active { transform: scale(.985); }
.today-card.rise { animation: rise .5s var(--ease-out) both; }
@keyframes rise { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
.tc-top { display: flex; gap: var(--sp-3); align-items: flex-start; }
.disc {
  width: 34px; height: 34px; border-radius: 12px; flex-shrink: 0;
  background: var(--gray-soft); color: var(--text-2);
  display: inline-flex; align-items: center; justify-content: center;
}
.tc-body { min-width: 0; flex: 1; }
/* §4.1 #12 — el nombre va en SU PROPIO RENGLÓN a ancho completo, y el chip debajo.
   Antes esto era una fila con `space-between`: el nombre compartía el ancho con el chip y perdía.
   MEDIDO a 375px el 2026-08-28 en Cobranzas: "Di Martella Orsi, Santiago Manuel" quedaba al 74%
   de su ancho necesario (187px de 252px) contra el chip "Crítico".
   ⚠️ Esa regla ya existía en `pipeline.css`, parcheada SOLO para esa vista con el argumento de
   que "Hoy y Cobranzas no tienen este problema (sus chips son más cortos)". Era cierto cuando se
   escribió y dejó de serlo: el chip de Cobranzas es corto, pero las razones sociales son largas.
   Por eso vive acá una sola vez y no como tercera copia del mismo parche — tres definiciones del
   mismo layout terminan divergiendo (§4.1 #6).
   Sin ellipsis de una línea, a propósito: envuelve si hace falta. El corte se ve prolijo y calla
   justo la parte que identifica a la cuenta. `.tc-hd` solo existe dentro de `.feed.m-only`, que
   app.css oculta a partir de 900px — esto no puede tocar el escritorio. */
.tc-hd {
  display: flex; flex-direction: column; align-items: flex-start; justify-content: flex-start;
  gap: 4px;
}
.tc-name {
  font-size: var(--fs-name); font-weight: 700; letter-spacing: -.01em; color: var(--text-1);
  width: 100%; min-width: 0;
  white-space: normal; overflow: visible; text-overflow: clip; overflow-wrap: anywhere;
}
.tc-why { font-size: var(--fs-body-lg); font-weight: 600; line-height: 1.4; color: var(--text-1); margin-top: 4px; }
.tc-why b { font-weight: 700; }
/* Fila chip de estado + canal + hora de la tarjeta de TAREA en Hoy (2026-09-01, `client-card.js
   ::buildTaskCard`) — debajo del nombre, encima de `.tc-why`. `flex-wrap` porque en 320px con
   canal + hora + chip largo ("Vencida") puede no entrar en una sola línea. */
.tc-task-meta-row { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin-top: 4px; }
.tc-hora { font-size: var(--fs-sm); font-weight: 600; color: var(--text-2); }
/* Ícono de canal accesible (`UI.canalIconBadge`, ui.js) — decorativo en tamaño, pero con nombre
   accesible propio (`role="img"` + `aria-label`, puestos por `UI.canalIconBadge`). */
.canal-badge { display: inline-flex; align-items: center; color: var(--text-3); flex-shrink: 0; }
.tc-fig { font-size: var(--fs-sm); color: var(--text-2); margin-top: 3px; }
/* Cuando el motivo de la API ES la línea de datos, esa única línea
   toma el rol de motivo (15/600) manteniendo el monto estilizado. */
.tc-fig.tc-why-fig { font-size: var(--fs-body-lg); font-weight: 600; color: var(--text-1); margin-top: 4px; }
.money { font-size: 17px; font-weight: 700; letter-spacing: -.02em; }
.money.due { color: var(--red-text); }
.money.ok { color: var(--text-1); }
/* El grupo lleva el margen que antes tenía `.tc-actions`: ahora hay dos filas (las 3 acciones
   principales + Canal/Eliminar) y el margen le corresponde al conjunto, no a la primera fila. */
.tc-actions-group { margin-top: var(--sp-3); }
.tc-actions { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--sp-2); margin-top: 0; }
.tc-actions-group .tc-actions { margin-top: 0; }
/* Shell ESTABLE de `taskActions` (components/timeline.js, 2026-09-01): `data-no-drag` se declara
   UNA vez acá y cubre los cuatro despliegues (Reprogramar/Reasignar/Canal/Eliminar) + el panel
   post-Hecho, para el día en que la tarjeta entera se vuelva clickeable (→ ficha). `display:
   contents` a propósito: el shell no agrega NINGUNA caja al layout — el nivel de DOM nuevo no
   puede introducir un espaciado que haya que salir a medir, es una garantía de CSS. Si esto
   cambia, hay que revisar el espaciado real (medido, no supuesto) de las 3 tarjetas que lo usan. */
.tc-actions-shell { display: contents; }
/* Eliminar (y ahora Canal): acciones poco frecuentes. Van juntas, a la derecha, en texto — no
   compiten con Hecho, que es la que se usa todos los días. Sin fondo ni borde: pesan lo que
   tienen que pesar. `gap` nuevo (2026-09-01): antes era un solo botón, ahora son dos. */
.tc-actions-sec { display: flex; justify-content: flex-end; align-items: center; gap: var(--sp-3); margin-top: var(--sp-2); }
.tc-del {
  background: none; border: 0; padding: 2px 4px; cursor: pointer; position: relative;
  font: 600 var(--fs-caption)/1 var(--font); color: var(--text-3); border-radius: var(--r-sm);
}
.tc-del:hover { color: var(--red-text); }
.tc-del:focus-visible { box-shadow: var(--sh-focus); outline: none; }
/* El blanco táctil crece por pseudo-elemento, NUNCA por padding: agrandar la caja real descoloca
   la fila (regla del proyecto). El botón mide ~20px de alto; el ::after lo lleva a 44. */
@media (pointer: coarse) { .tc-del::after { content: ""; position: absolute; inset: -12px -10px; } }
.tc-del-q { align-self: center; font: 500 var(--fs-sm)/1.3 var(--font); color: var(--text-2); margin-right: var(--sp-1); }
/* "Canal" (2026-09-01): MISMAS propiedades que "Eliminar" — texto chico, sin fondo ni borde, tap
   target de 44px por pseudo-elemento — pero en regla PROPIA, no combinada con `.tc-del`: hay un
   guardián de test que busca el selector `.tc-del {` como substring literal, y una regla
   combinada (`.tc-del, .tc-canal {`) lo rompe. Solo cambia el color de hover (azul, no rojo — no
   es una acción destructiva). */
.tc-canal {
  background: none; border: 0; padding: 2px 4px; cursor: pointer; position: relative;
  font: 600 var(--fs-caption)/1 var(--font); color: var(--text-3); border-radius: var(--r-sm);
}
.tc-canal:hover { color: var(--blue-text); }
.tc-canal:focus-visible { box-shadow: var(--sh-focus); outline: none; }
@media (pointer: coarse) { .tc-canal::after { content: ""; position: absolute; inset: -12px -10px; } }
/* "Editar" (2026-09-02, contrato-cierre-tarea-actividad.md §7.3): MISMAS propiedades que "Canal"/
   "Eliminar" — regla PROPIA, no combinada (mismo guardián de test que ya protege `.tc-del`/
   `.tc-canal`: un selector combinado rompe el grep literal). Color neutro de hover (ni rojo ni
   azul): no es destructiva ni un canal, es "abrir un formulario". */
.tc-edit {
  background: none; border: 0; padding: 2px 4px; cursor: pointer; position: relative;
  font: 600 var(--fs-caption)/1 var(--font); color: var(--text-3); border-radius: var(--r-sm);
}
.tc-edit:hover { color: var(--text-1); }
.tc-edit:focus-visible { box-shadow: var(--sh-focus); outline: none; }
@media (pointer: coarse) { .tc-edit::after { content: ""; position: absolute; inset: -12px -10px; } }
.tc-actions.cols3 { grid-template-columns: repeat(3, 1fr); }
.tc-actions .btn { padding: 0 var(--sp-2); min-width: 0; }
/* En pantalla chica el padding baja a 4px: medido el 2026-09-01 a 390px, "Reprogramar" (el label
   más largo de la fila, y el que reemplazó a "Posponer") necesita 88px de texto y le quedaban 87.
   Se desbordaba por 1px — invisible de un vistazo, y aun así el texto se sale de su caja.
   ⚠️ No lo detecta la sonda de `scrollWidth > clientWidth`: un `.btn` no recorta, así que no hay
   overflow que medir. Sale comparando el ancho REAL del texto (`Range`) contra la caja, que es la
   medición que corresponde cuando el elemento no recorta (§4.1 nº12 y su adenda). */
@media (max-width: 899.98px) { .tc-actions .btn { padding: 0 var(--sp-1); } }
.end-note { text-align: center; font-size: var(--fs-caption); color: var(--text-3); padding: var(--sp-3) 0; }
.end-note b { color: var(--green-text); font-weight: 600; }

/* Rail de contexto Hoy (solo desktop). `.rail` es el wrapper posicionador y SÍ se comparte a
   propósito con el rail de la Ficha 360 (views/ficha.js) — mismo rol en las dos pantallas
   (columna lateral sticky), mismas 3 propiedades acá y el `position:sticky` que le suma el
   media query de abajo le queda bien a cualquiera de las dos. `.rail-card`/`.rail-row`, en
   cambio, NO se comparten: por eso lo de Hoy usa su propio namespace `.hoy-rail-*` desde el fix
   de auditoría 2026-08-25 (3.12) — antes los dos rails usaban `.rail-card`/`.rail-row` a secas y,
   al ser el mismo selector, el rail de Ficha (definido más abajo, en el media query de escritorio)
   le pisaba propiedades a este: `.rail-row` bajaba de 13px (--fs-sm) a 12px hardcodeado y sumaba
   un `border-top` que acá nunca se pidió — "un hairline que nadie decidió". */
.hoy-grid { display: block; }
.rail { display: flex; flex-direction: column; gap: var(--sp-3); }
.hoy-rail-card {
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-lg);
  box-shadow: var(--sh-1); padding: var(--sp-3) var(--sp-4);
}
.rail-title {
  font-size: var(--fs-caption); font-weight: 700; letter-spacing: .08em;
  text-transform: uppercase; color: var(--text-3); margin-bottom: var(--sp-1);
}
.hoy-rail-row { display: flex; align-items: center; gap: 10px; min-height: 34px; font-size: var(--fs-sm); color: var(--text-2); }
.hoy-rail-row .rdot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
.hoy-rail-row .rcount { margin-left: auto; font-weight: 700; color: var(--text-1); }
.hoy-rail-row .rcount.good { color: var(--green-text); }

/* ============ Lista de clientes (mobile, grupo iOS) ============ */
.client-group {
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-lg);
  box-shadow: var(--sh-1); overflow: hidden;
  display: flex; flex-direction: column;
}
/* `.crow` era plano (mono/main/right/chevron como hijos directos, fila única). Desde 2026-08-30
   pasa a columna para poder sumar `.crow-fields` (bloque de campos editables, Fase C) debajo del
   encabezado sin romper el layout horizontal de siempre — ESE layout se mudó tal cual a
   `.crow-head`, cero cambio visual cuando no hay campos que mostrar. */
.crow {
  display: flex; flex-direction: column;
  text-decoration: none; color: inherit; position: relative;
}
.crow:active { background: var(--surface-3); }
.crow + .crow::before {
  content: ""; position: absolute; top: 0; left: 68px; right: 0;
  height: .5px; background: var(--hairline);
}
.crow-head {
  display: flex; align-items: center; gap: var(--sp-3);
  min-height: 64px; padding: 10px var(--sp-4);
}
/* Bloque de campos editables (Fase C, "que la edición también llegue a las tarjetas del
   teléfono"): misma selección de columnas visibles que la tabla — ver clientes.js. Lápiz SIEMPRE
   visible acá (no hay hover en mobile, decisión de Fernando): `.fe-pencil-btn`/`.fe-btn .ico` no
   llevan la regla de opacidad-por-hover que sí lleva `.table-clientes` (ver el media de
   escritorio, más abajo). */
.crow-fields {
  display: flex; flex-direction: column;
  padding: 0 var(--sp-4) 4px; border-top: .5px solid var(--hairline);
}
.crow-field {
  display: flex; align-items: stretch; gap: var(--sp-3);
  min-height: var(--touch);
  border-bottom: .5px solid var(--hairline);
}
.crow-field:last-child { border-bottom: none; }
.crow-field-lbl {
  flex: 0 0 118px; display: flex; align-items: center;
  font-size: 12px; color: var(--text-3); min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.crow-field-val {
  flex: 1; min-width: 0; display: flex; align-items: center;
  font-size: 13px; color: var(--text-1); overflow: hidden;
}
.crow-field-val > span:not(.fe):not(.lc-cell) {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; display: inline-block;
}
/* El botón/valor editable ocupa el renglón ENTERO (blanco táctil real, medido con
   `elementFromPoint` — no un pseudo-elemento invisible que un vecino puede ganar en el hit-test,
   CLAUDE.md §4.1 "Un pseudo-elemento puede estar aplicado y no servir"). */
.crow-field-val .fe,
.crow-field-val .lc-cell { display: flex; align-items: center; width: 100%; height: 100%; }
.crow-field-val .fe-btn,
.crow-field-val .fe-link-row,
.crow-field-val .lc-btn {
  display: flex; align-items: center; width: 100%; min-height: var(--touch);
}
.mono {
  width: 40px; height: 40px; border-radius: 50%; flex-shrink: 0;
  background: var(--surface-2); border: 1px solid var(--border);
  display: inline-flex; align-items: center; justify-content: center;
  font-size: var(--fs-sm); font-weight: 700; color: var(--text-2);
}
.mono.big {
  width: 64px; height: 64px; border: none;
  background: linear-gradient(135deg, #5b5b63, #2e2e33);
  color: #fff; font-size: 20px; font-weight: 700; box-shadow: var(--sh-2);
}
.crow-main { min-width: 0; flex: 1; display: flex; flex-direction: column; gap: 4px; }
.crow-name {
  font-size: var(--fs-body-lg); font-weight: 600; color: var(--text-1);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.crow-right { text-align: right; flex-shrink: 0; }
.crow-amt { font-size: var(--fs-body); font-weight: 700; }
.crow-amt.due { color: var(--red-text); }
/* Saldo (pedido Fernando 2026-08-24): vencido → rojo · a favor → verde, con etiqueta siempre
   al lado (crow-sub) · sin ninguno de los dos → "Al día" en gris, no es un estado "bueno". */
.crow-amt.ok { color: var(--text-3); }
.crow-amt.favor { color: var(--green-text); }
.crow-sub { font-size: var(--fs-chip); color: var(--text-3); margin-top: 2px; }
.chev { color: var(--border-strong); flex-shrink: 0; }

/* Filtros */
.filters-block { display: flex; flex-direction: column; gap: var(--sp-2); }
.filter-row { display: flex; gap: var(--sp-2); flex-wrap: wrap; align-items: center; }
.filter-row select { flex: 1; min-width: 130px; width: auto; }
.clientes-count { color: var(--text-3); font-size: var(--fs-caption); }

/* ----------------------------------------------------------
   Clientes — filtros combinables + selector de columnas (2026-08-30,
   contrato docs/analisis-vista-clientes/contrato-columnas.md §B).
   Reusa el shell `.sheet`/`.sheet-backdrop` (bottom sheet mobile,
   modal centrado escritorio — ver la media query de más abajo,
   línea ~1990) SIN CSS nuevo para eso; acá solo el CONTENIDO:
   secciones de facets (patrón `.mapa-fp-*`/`.mapa-opt` de mapa.css,
   redefinido acá porque esta vista no puede depender de otra hoja).
   ---------------------------------------------------------- */
.table-toolbar { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2); }
.cf-sheet-body { gap: 0; }
.cf-fp-sec { padding: var(--sp-3) var(--sp-4) var(--sp-2); }
.cf-fp-sec + .cf-fp-sec { border-top: .5px solid var(--hairline); }
.cf-fp-hd {
  font-size: var(--fs-chip); font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  color: var(--text-3); margin-bottom: 6px;
}
/* Título de GRUPO del panel "+ Filtros" (2026-09-04): mismo tamaño que el de cada filtro pero en
   el color del texto principal y con más peso — medido en el harness, "ACTIVIDAD" y "ÚLTIMO
   CONTACTO" se veían idénticos y la jerarquía de dos niveles no se leía. */
.cf-fp-grupo { color: var(--text); font-weight: 700; }
.cf-fp-grupo + .cf-fp-hd, .cf-fp-grupo + .toggle-chip { margin-top: 6px; }
.cf-fp-sec-row { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2); margin-bottom: 6px; }
.cf-fp-sec-row .cf-fp-hd { margin: 0; }
.cf-fp-clear { background: none; border: none; color: var(--blue-text); font-size: var(--fs-sm); font-weight: 600; cursor: pointer; padding: 4px 2px; }
.cf-fp-clear:disabled { color: var(--text-4); cursor: not-allowed; }
.cf-col-actions { display: flex; gap: var(--sp-3); }
.cf-opt {
  display: flex; align-items: center; gap: 8px; padding: 8px 4px; border-radius: 8px;
  cursor: pointer; position: relative;
}
.cf-opt:hover { background: var(--surface-2); }
.cf-opt.active { background: var(--blue-soft); }
.cf-opt-label { flex: 1; min-width: 0; font-size: 13px; color: var(--text-1); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cf-opt.active .cf-opt-label { color: var(--blue-text); font-weight: 600; }
.cf-opt-count { font-size: 11.5px; font-weight: 600; color: var(--text-3); flex-shrink: 0; }
.cf-opt.active .cf-opt-count { color: var(--blue-text); }
/* Blanco táctil ≥44px en coarse sin agrandar la fila visual (mismo truco que `.mapa-opt`/`.opt`). */
@media (pointer: coarse) { .cf-opt::after { content: ""; position: absolute; inset: -5px 0; } }
.cf-col-chk { display: flex; align-items: center; gap: 8px; padding: 7px 4px; cursor: pointer; }
.cf-col-chk input { width: 16px; height: 16px; accent-color: var(--blue); flex-shrink: 0; }
.cf-col-chk span { font-size: 13px; color: var(--text-1); }

/* ----------------------------------------------------------
   Chips de filtro con popover (components/facet-chip.js, 2026-09-02).
   Pedido del CEO: "reveé los filtros del panel superior, hay
   redundancia" — Estado/Clasificación/Saldo/Comercial/Operatoria
   SIEMPRE visibles como chip en `.filter-row`, sin abrir "+ Filtros".
   Mockup elegido: docs/design/direcciones/clientes-filtros-a{,-desktop}.html
   (variante A). El CUERPO del popover reusa `.cf-opt*` de arriba —
   una sola definición de "fila de opción con conteo" (§4.1 nº6).
   ---------------------------------------------------------- */
.fchip-wrap { display: inline-flex; }
.fchip {
  display: inline-flex; align-items: center; gap: 5px; min-height: 30px;
  padding: 0 var(--sp-3); border-radius: var(--r-pill);
  border: 1px solid var(--border-strong); background: var(--surface);
  font-size: var(--fs-sm); font-weight: 600; color: var(--text-2);
  white-space: nowrap; cursor: pointer; -webkit-tap-highlight-color: transparent;
}
.fchip:hover { border-color: var(--text-4); }
.fchip.active { background: var(--blue-soft); border-color: var(--blue-border); color: var(--blue-text); }
@media (pointer: coarse) {
  .fchip-wrap { position: relative; }
  .fchip { position: relative; }
  .fchip::after { content: ""; position: absolute; inset: -8px -2px; }
  /* Vistas guardadas: el chip mide 28-30px y el botón ⋯ menos. Medido el 2026-09-02 a 390px:
     blanco táctil de 30px sin pseudo-elemento. Misma técnica (nunca padding real: descoloca la
     fila). El botón ⋯ recibe su propio blanco para que "abrir menú" y "aplicar vista" no se pisen. */
  .saved-chip { position: relative; }
  .saved-chip::after { content: ""; position: absolute; inset: -8px 0 -8px -2px; }
  /* Sin hover no hay forma de "revelar" el ⋯: en táctil se ve siempre. */
  .saved-chip-menu { position: relative; opacity: 1; }
  .saved-chip-menu::after { content: ""; position: absolute; inset: -12px -6px; }  /* 20 + 24 = 44 */
}
/* Popover (puntero fino): montado en `document.body`, `--z-popover` (tokens.css). */
.fchip-pop {
  min-width: 220px; max-width: 300px; max-height: 340px; overflow-y: auto;
  background: var(--surface); border: 1px solid var(--border-strong); border-radius: var(--r);
  box-shadow: var(--sh-2); padding: var(--sp-2) 0; z-index: var(--z-popover);
}
.fchip-pop-hd {
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2);
  padding: 2px var(--sp-3) 6px; margin-bottom: 4px; border-bottom: .5px solid var(--hairline);
}
.fchip-pop-body { padding: 0 var(--sp-2); }
.fchip-empty { padding: var(--sp-2) var(--sp-3); font-size: 13px; color: var(--text-3); }

/* ----------------------------------------------------------
   Vistas guardadas (2026-09-02, app/services/saved_views.py). Fila
   de chips bajo el buscador + barra de "modificada" cuando la vista
   activa difiere de sus filtros guardados.
   ---------------------------------------------------------- */
.saved-row {
  display: flex; align-items: center; gap: 6px; padding: 2px 0 6px;
  overflow-x: auto; scrollbar-width: none;
}
.saved-row::-webkit-scrollbar { display: none; }
.saved-row-label {
  font-size: 10.5px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  color: var(--text-3); white-space: nowrap; flex-shrink: 0;
}
.saved-chip {
  display: inline-flex; align-items: center; gap: 5px; min-height: 28px;
  padding: 0 4px 0 10px; border-radius: var(--r-pill);
  border: 1px solid var(--border-strong); background: var(--surface);
  font-size: 12px; font-weight: 500; color: var(--text-2);
  white-space: nowrap; cursor: pointer; flex-shrink: 0; -webkit-tap-highlight-color: transparent;
}
.saved-chip:hover { border-color: var(--text-4); }
.saved-chip.active { background: var(--surface-3); border-color: var(--border-strong); color: var(--text-1); font-weight: 600; }
.saved-chip-shared { display: inline-flex; opacity: .5; }
.saved-chip-name { max-width: 220px; overflow: hidden; text-overflow: ellipsis; }
.saved-chip-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--blue-solid); flex-shrink: 0; }
.saved-chip-menu {
  display: inline-flex; align-items: center; justify-content: center; position: relative;
  width: 20px; height: 20px; border-radius: 50%; border: none; background: none;
  color: var(--text-3); cursor: pointer; opacity: 0; transition: opacity var(--t-fast);
}
.saved-chip:hover .saved-chip-menu, .saved-chip:focus-within .saved-chip-menu { opacity: 1; }
.saved-chip-menu:hover { background: var(--surface-3); color: var(--text-1); }
@media (pointer: coarse) {
  /* Táctil: sin `:hover`, así que el botón queda siempre visible en vez de invisible-por-siempre. */
  .saved-chip-menu { opacity: 1; }
  .saved-chip { min-height: 30px; }
}
.saved-new-btn {
  display: inline-flex; align-items: center; gap: 3px; min-height: 28px;
  padding: 0 10px; border-radius: var(--r-pill);
  border: 1px dashed var(--blue-border); background: var(--blue-soft); color: var(--blue-text);
  font-size: 12px; font-weight: 500; white-space: nowrap; cursor: pointer; flex-shrink: 0;
}
.view-modified-bar {
  display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap;
  padding: var(--sp-2) var(--sp-3); margin-bottom: var(--sp-2);
  background: var(--blue-soft); border: 1px solid var(--blue-border); border-radius: var(--r-sm);
  font-size: 13px;
}
.view-modified-name { font-weight: 600; color: var(--blue-text); }
.view-modified-msg { color: var(--blue-text); font-size: 12px; }
.view-modified-actions { display: flex; gap: 6px; margin-left: auto; flex-wrap: wrap; }
.view-discard-btn { color: var(--text-3); }

/* Menú "⋯" de una vista propia (popover chico, mismo mecanismo de posicionamiento que
   `.fchip-pop`) — Renombrar / Compartir / Eliminar, con confirmación INLINE (nunca `confirm()`
   del navegador, mismo criterio que `timeline.js::taskActions` "Eliminar"). */
.view-menu {
  min-width: 190px; background: var(--surface); border: 1px solid var(--border-strong);
  border-radius: var(--r); box-shadow: var(--sh-2); padding: 4px 0; z-index: var(--z-popover);
}
.view-menu-item {
  padding: 8px 14px; font-size: 13px; color: var(--text-1); cursor: pointer;
}
.view-menu-item:hover { background: var(--surface-2); }
.view-menu-item.danger { color: var(--red-text); }
.view-menu-divider { height: 1px; background: var(--border); margin: 4px 0; }
.view-menu-rename, .view-menu-confirm { padding: 10px 14px; }
.view-menu-rename input { margin-bottom: 8px; }
.view-menu-confirm p { font-size: 13px; color: var(--text-2); margin-bottom: 8px; }
.view-menu-confirm-actions { display: flex; gap: 6px; }
.view-create-form .char-count { text-align: right; font-size: 11px; color: var(--text-4); margin-top: 3px; }
.view-switch-row { margin-top: var(--sp-3); }

/* ----------------------------------------------------------
   Arrastrar columnas (2026-09-02, mockup elegido: variante A,
   docs/design/direcciones/clientes-columnas-drag-a.html). El th
   origen se atenúa EN SU LUGAR; un th fantasma semitransparente
   sigue al cursor; una línea de 2px marca dónde va a insertarse.
   ---------------------------------------------------------- */
th.col-dragging { opacity: .35; }
body.col-dragging { cursor: grabbing; user-select: none; }
body.col-dragging * { cursor: grabbing !important; }
.col-drag-ghost {
  position: fixed; pointer-events: none; z-index: 50;
  background: var(--surface); border: 1px solid var(--blue-border); border-radius: 6px;
  box-shadow: var(--sh-2), 0 0 0 1px var(--blue-border); padding: 9px 12px;
  font-size: var(--fs-chip); font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  color: var(--blue-text); opacity: .88; white-space: nowrap;
  transform: rotate(-1.5deg) scale(1.02);
}
.col-drop-indicator {
  position: fixed; width: 2px; background: var(--blue-solid); z-index: 45;
  pointer-events: none; display: none;
}

/* "Orden actual" del sheet Columnas — ↑/↓ por fila visible, blanco táctil ≥32px (pedido del CEO). */
.col-order-row { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2); padding: 5px 4px; }
.col-order-label { font-size: 13px; color: var(--text-1); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.col-order-btns { display: flex; gap: 4px; flex-shrink: 0; }
.col-order-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 32px; height: 32px; border-radius: 8px; border: 1px solid var(--border-strong);
  background: var(--surface); color: var(--text-2); font-size: 15px; font-weight: 700; cursor: pointer;
}
.col-order-btn:hover:not(:disabled) { background: var(--surface-3); color: var(--text-1); }
.col-order-btn:disabled { opacity: .35; cursor: not-allowed; }

/* Header sorteable: cualquier columna del catálogo, click o Enter/Espacio (§B). */
table.dense th[role="button"] { cursor: pointer; user-select: none; }
table.dense th[role="button"]:hover { color: var(--text-1); }
table.dense th[role="button"]:focus-visible { outline: 2px solid var(--blue); outline-offset: -2px; }
.sort-ind { color: var(--blue-text); }

/* ============ Card genérica con header (bcard) ============ */
.bcard {
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-lg);
  box-shadow: var(--sh-1); padding: var(--sp-4);
}
.bcard.flush { padding: 0; overflow: hidden; }
/* Una tabla densa dentro de una card flush tiene que poder SCROLLEAR, no recortarse.
   El `overflow: hidden` de arriba existe para que la tabla no se coma las esquinas
   redondeadas -- pero de paso recortaba columnas enteras sin dejar forma de llegar a ellas.
   Medido el 2026-08-26 en el banco de pruebas de Cobranzas: a 1000px de viewport la tabla
   pide 872px dentro de un contenedor de 710 -- 162px de columnas (DIAS y ULTIMO CONTACTO)
   invisibles y ALCANZABLES POR NADIE. No es truncar: es desaparecer. Defecto PREEXISTENTE de
   M3, no lo introdujo el badge de cancelaciones (que suma 28px sobre un layout ya roto).
   Afecta el rango 900-1190px, que es cualquier ventana de laptop sin maximizar.
   `overflow-x: auto` con `overflow-y` heredado en hidden: el eje Y sigue recortando las
   esquinas y el X gana barra cuando hace falta. El body NO scrollea horizontal (verificado).
   Regla §4.1 #7: el hueco se muestra, no se esconde. */
.bcard.flush.table-wrap { overflow-x: auto; }
.bcard.flush .bcard-hd { padding: var(--sp-3) var(--sp-5) var(--sp-2); }
.bcard-hd { display: flex; align-items: baseline; justify-content: space-between; gap: var(--sp-3); margin-bottom: var(--sp-3); }
.bcard-hd h3 {
  font-size: var(--fs-caption); font-weight: 700; letter-spacing: .08em;
  text-transform: uppercase; color: var(--text-3);
}
.bcard-hd .link { font-size: var(--fs-sm); font-weight: 600; color: var(--blue-text); text-decoration: none; }
.chart-note { font-size: var(--fs-chip); color: var(--text-3); margin-top: var(--sp-2); }

/* Tabla densa (desktop) */
.table-wrap { max-width: var(--table-max); }
table.dense { width: 100%; border-collapse: collapse; }
table.dense th {
  font-size: var(--fs-chip); font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  color: var(--text-3); text-align: left; padding: 10px 12px;
  border-bottom: 1px solid var(--border);
}
table.dense td {
  font-size: var(--fs-sm); color: var(--text-1); padding: 10px 12px;
  box-shadow: 0 .5px 0 var(--hairline);
}
table.dense tr:last-child td { box-shadow: none; }
table.dense th:first-child, table.dense td:first-child { padding-left: var(--sp-5); }
table.dense th:last-child, table.dense td:last-child { padding-right: var(--sp-5); }
table.dense tbody tr { cursor: pointer; transition: background var(--t-fast) var(--ease); }
table.dense tbody tr:hover { background: var(--surface-2); }
table.dense .ta-r { text-align: right; }
table.dense .dim { color: var(--text-3); }
/* Excepción a `.prod-name` (sección Ficha, más abajo en este archivo): dentro de una tabla
   densa una fila es un renglón — una sola línea con ellipsis es correcta acá, siempre que la
   columna tenga ancho y el `title=` lleve el nombre completo (ya lo llevan sus dos usos). */
table.dense .prod-name { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; overflow-wrap: normal; }
table.dense .amt-due { font-weight: 700; color: var(--red-text); }
/* "Al día" ya no es un semáforo verde: es neutro (gris). El verde queda para saldo a favor,
   que SÍ es una señal a trabajar (pedido Fernando 2026-08-24, columna Saldo de Clientes). */
table.dense .amt-ok { font-weight: 700; color: var(--text-3); }
table.dense .amt-favor { font-weight: 700; color: var(--green-text); }
.td-name { display: flex; align-items: center; gap: 10px; }
.td-name .mono { width: 30px; height: 30px; font-size: 11px; }
/* ID de Odoo + local, como segunda línea bajo la razón social (pedido Fernando 2026-08-25) —
   ver comentario largo en clientes.js `nameSub()`: no son columnas propias a propósito, la
   Variante A ya venía "apretada" con 9 columnas. */
.td-name-text { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.td-sub { font-size: 11px; color: var(--text-3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ============ Stat tiles ============ */
.tiles { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--sp-2); }
.tile {
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--r);
  box-shadow: var(--sh-1); padding: var(--sp-3);
}
.tile .lb {
  font-size: var(--fs-chip); font-weight: 700; letter-spacing: .05em; text-transform: uppercase;
  color: var(--text-3); min-height: 29px;
}
.tile .v { font-size: 21px; font-weight: 800; letter-spacing: -.03em; color: var(--text-1); }
.tile .v.bad { color: var(--red-text); }
.tile .v.muted { color: var(--text-3); }
.tile .d { font-size: 11.5px; font-weight: 600; margin-top: 2px; }
.tile .d small { font-size: var(--fs-chip); font-weight: 500; color: var(--text-3); }
.tile .d.up-bad, .tile .d.down-bad { color: var(--red-text); }
.tile .d.up-good, .tile .d.down-good { color: var(--green-text); }

/* ============ Ficha ============ */
.navrow { display: flex; align-items: center; min-height: var(--touch); }
.back {
  display: inline-flex; align-items: center; gap: 6px; min-height: var(--touch);
  font-size: var(--fs-body-lg); font-weight: 500; color: var(--blue-text); text-decoration: none;
}
.profile { display: flex; flex-direction: column; align-items: center; text-align: center; gap: var(--sp-2); }
.profile h1 { font-size: 22px; font-weight: 800; letter-spacing: -.025em; }
.profile .fantasia { font-size: var(--fs-sm); color: var(--text-3); }
/* `flex-wrap` + `min-width: 0` — medido el 2026-08-26 con el banco de pruebas: a 375px el botón
   "Prepararme" llegaba a 399px, o sea 24px FUERA de la pantalla, y hacía que la ficha entera
   scrollease de costado en el celular (donde justamente trabaja el comercial). Es el clásico de
   flexbox: `flex: 1` no puede encoger por debajo del ancho del contenido si el ítem conserva
   `min-width: auto`. Defecto PREEXISTENTE -- esta tanda no había tocado esta regla.
   Se deja envolver a un segundo renglón en vez de apretar las etiquetas hasta que no se lean:
   el hueco se muestra, no se esconde (§4.1 regla 7). */
.ficha-actions { display: flex; flex-wrap: wrap; gap: var(--sp-2); width: 100%; }
.ficha-actions .btn { flex: 1 1 auto; min-width: 0; }

/* ============================================================
   CABECERA COMPACTA v2 — perfil horizontal (2026-08-28).
   Contrato: docs/design/direcciones/ficha-ellocal-v2-densidad.html (Variante Densidad, aprobada).
   Reemplaza `.profile` (centrado, apilado, ~394/446px) por una fila: monograma + nombre/chips a
   la izquierda. Las acciones salen de acá — ver `.actions-v2` (mobile) y `.rail-actions` dentro
   del media query de escritorio (rail sticky). `.profile`/`.ficha-actions`/`.doc-*` de arriba
   quedan sin uso desde views/ficha.js pero NO se tocan: `ficha-odoo.js` sigue exportando
   `docActions` (fuera de alcance de esta pasada) y podría volver a necesitarlas.
   ============================================================ */
.profile-cpt { display: flex; align-items: center; gap: var(--sp-3); padding: var(--sp-1) 0; }
/* Monograma de la cabecera: `UI.monogram()` sin modificador da el `.mono` chico gris (para
   listas); acá se pisa con el degradé oscuro que antes daba `.mono.big` (64px, DEMASIADO grande
   para la cabecera compacta) — 40px mobile, 48px escritorio (media query de abajo). */
.profile-cpt .mono {
  flex-shrink: 0; width: 40px; height: 40px; font-size: 14px; border: none; box-shadow: var(--sh-1);
  background: linear-gradient(135deg, #5b5b63, #2e2e33); color: #fff;
}
.profile-cpt-text { flex: 1; min-width: 0; }
/* Nombre de fantasía PROTAGONISTA (contrato B3, 2026-09-04 — reemplaza el h1=razón social +
   `.fantasia` de nota al pie que tenía antes). Es un `<h1>` que envuelve el `.fe` editable de
   `COMPONENTS.fieldEdit.editable()`: el tamaño/peso vive en el `<h1>`, el `.fe`/`.fe-val-text` de
   adentro heredan `font-size`/`font-weight` por no declarar los suyos propios en este contexto —
   mismo mecanismo que ya usan otras cabeceras editables del proyecto. */
.profile-cpt-fantasia {
  font-size: 20px; font-weight: 800; letter-spacing: -.02em; line-height: 1.2;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; min-width: 0;
}
.profile-cpt-fantasia .fe-val-text, .profile-cpt-fantasia .fe-btn { font-size: inherit; font-weight: inherit; }
/* Razón social + CUIT — línea chica SIEMPRE debajo del nombre de fantasía (orden fijo del
   contrato: nombre → razón social+CUIT → chips). Reemplaza a `.profile-cpt .fantasia`, que hacía
   el camino inverso (razón social como h1, fantasía como nota). */
.profile-cpt-razon {
  font-size: 12px; color: var(--text-3); margin-top: 2px; line-height: 1.3;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%;
}
/* T3 (2026-09-08): quién fijó la razón social. `--text-2`, no `--text-3` (4,35:1 en oscuro,
   falla AA — misma lección del 2026-09-01 que ya movió los chips de K1). `--text-2` mide ≥6,88:1
   en los dos temas sobre `--surface`/`--bg-app` (fondo real de la cabecera). */
.razon-social-fuente { font-size: 11px; color: var(--text-2); margin-top: 1px; line-height: 1.3; }
.profile-cpt .chips-row { margin-top: 4px; }
/* Slot de "Prepararme": UNA sola instancia del botón (`brief.btn`) se muda acá adentro en
   escritorio, o a `.actions-v2 .prep-slot` en mobile (ver `placePrepBtn`, views/ficha.js). */
.profile-cpt .prep-slot.d-only { flex-shrink: 0; margin-left: auto; align-self: center; }

/* Acciones compactas — SOLO mobile (`.m-only`): en escritorio viven ÚNICA Y EXCLUSIVAMENTE en el
   rail (`.rail-actions`, dentro del media query de escritorio) — pedido de Fernando 2026-08-28:
   "acciones rápidas es redundante, tiene lo mismo que está arriba de todo". Dos filas: primaria
   (WhatsApp/Actividad/Prepararme) y documentos (Estado de cuenta/One-pager). */
.actions-v2 { display: flex; flex-direction: column; gap: 6px; }
.actions-v2 .actions-row { display: flex; gap: 6px; }
.actions-v2 .actions-row > .btn { flex: 1 1 0; min-width: 0; padding: 0 var(--sp-2); }
.actions-v2 .actions-row > .prep-slot { flex: 1 1 0; min-width: 0; display: flex; }
.actions-v2 .actions-row > .prep-slot .btn { width: 100%; }
.actions-v2 .actions-row > .op-ctl { flex: 1 1 0; min-width: 0; }

/* Grupo iOS genérico */
.group {
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-lg);
  box-shadow: var(--sh-1); overflow: hidden;
  display: flex; flex-direction: column;
}
.group-hd {
  /* `flex-wrap: wrap` — medido el 2026-08-26 en el banco de pruebas: con `nowrap`, a 1000px de
     viewport (columna principal angosta) el bloque de cifras y el badge de frescura SE SALÍAN del
     encabezado 11 y 19px, pisando lo de al lado. Es el "texto que sobresale de un contenedor" que
     marcó Fernando. Envolver a un segundo renglón es la respuesta correcta: el encabezado crece,
     nada se pisa y nada se esconde (§4.1 regla 7). Los hijos llevan `min-width: 0` para poder
     encogerse antes de empujar. */
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2);
  flex-wrap: wrap; row-gap: var(--sp-1);
  padding: var(--sp-3) var(--sp-4) 0;
  font-size: var(--fs-caption); font-weight: 700; letter-spacing: .08em;
  text-transform: uppercase; color: var(--text-3);
}
/* Cuando el lado derecho del header lleva más de un elemento (chip + badge de frescura) */
.group-hd-right { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; min-width: 0; justify-content: flex-end; row-gap: var(--sp-1); }
.grow {
  display: flex; align-items: center; gap: var(--sp-3);
  min-height: 52px; padding: var(--sp-2) var(--sp-4);
  text-decoration: none; color: inherit; position: relative;
}
a.grow:active { background: var(--surface-3); }
.grow + .grow::before, .inv + .inv::before {
  content: ""; position: absolute; top: 0; left: var(--sp-4); right: 0;
  height: .5px; background: var(--hairline);
}
.icon-cell {
  width: 32px; height: 32px; border-radius: 9px; flex-shrink: 0;
  background: var(--blue-soft); color: var(--blue-text);
  display: inline-flex; align-items: center; justify-content: center;
}
.grow .lbl { font-size: var(--fs-chip); color: var(--text-3); }
.grow .val { font-size: var(--fs-body-lg); font-weight: 500; color: var(--blue-text); overflow-wrap: anywhere; }
.grow .val.plain { color: var(--text-1); }
.grow-main { min-width: 0; flex: 1; }

/* field-list (label izquierda / valor derecha) */
.frow {
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3);
  min-height: 44px; padding: var(--sp-2) var(--sp-4); position: relative;
}
.frow + .frow::before {
  content: ""; position: absolute; top: 0; left: var(--sp-4); right: 0;
  height: .5px; background: var(--hairline);
}
.frow .lbl { font-size: var(--fs-sm); color: var(--text-3); flex-shrink: 0; }
.frow .val { font-size: var(--fs-sm); color: var(--text-1); text-align: right; }

/* Variante gris de `.icon-cell` (default es azul) — la usa "El local" para diferenciar un ícono
   de navegación externa (Instagram, azul) de uno neutral (teléfono/persona, gris). */
.icon-cell.gray { background: var(--gray-soft); color: var(--text-2); }

/* ============================================================
   EL LOCAL — perfil comercial del cliente (views/ficha.js, 2026-08-28).
   Contrato de diseño v2: docs/design/direcciones/ficha-ellocal-v2-densidad.html (Variante
   Densidad, aprobada 2026-08-28 — reemplaza la Variante A original). Comparte `.group`/
   `.group-hd`/`.chip` con el resto de la ficha — acá solo lo que es exclusivo de este bloque.
   ============================================================ */
.el-local-ico { display: inline-flex; color: var(--text-3); }
/* "Sin dato" liviano — para los campos del bloque que NO son la grilla de "Datos adicionales"
   (esos usan `.el-local-gap`, el hueco que invita a completarse; ver más abajo). */
.el-local-empty-val { font-size: var(--fs-sm); color: var(--text-4); font-style: italic; }
/* El hueco de Compras en «El local» es un BOTÓN que lleva a Personas (2026-09-14, F12): el texto
   va en `--blue-text` (6,1:1 claro · medido en oscuro sobre surface) porque es una acción, no un
   placeholder; blanco táctil 44 px por pseudo-elemento, como el resto de la ficha. */
.el-local-empty-link { background: none; border: 0; padding: 0; cursor: pointer; color: var(--blue-text); font-style: normal; font-weight: 600; position: relative; font-family: inherit; }
@media (pointer: coarse) { .el-local-empty-link::after { content: ''; position: absolute; inset: -14px 0; } }
/* Lo que ARCA CONTESTÓ sobre el domicilio fiscal (2026-09-08). ⚠️ NO usa `.el-local-empty-val`, y
   la diferencia no es estética: `--text-4` está documentado en tokens.css como "SOLO
   placeholder/decorativo — no texto informativo", y medido da 2,28:1 en tema oscuro (WCAG AA pide
   4,5:1). "ARCA lo revisó y coincide con Odoo" no es un hueco: es una confirmación, y es
   justamente la buena noticia que hasta hoy se veía igual que "nunca se consultó" (§4.1 nº8).
   ⚠️ Se probó `--text-3` primero y NO alcanza: tokens.css lo documenta como "5,1:1", pero esa
   cifra es del tema CLARO — medido en oscuro sobre el fondo de tarjeta (rgb 44,44,46) da **4,35:1**
   y sigue debajo de AA. Es la lección del proyecto sobre `--blue` aplicada a un token de texto: un
   color que pasa en un tema puede fallarlo en el otro, y hay que medir los DOS. `--text-2` (cuerpo)
   es además lo semánticamente correcto: esto informa, no es un caption decorativo.
   Los huecos REALES (`no_consultado`, `sin_partner`) siguen con `.el-local-empty-val`, que ahí sí
   describe lo que son. */
.fiscal-addr-info { font-size: var(--fs-sm); color: var(--text-2); }

/* .fiscal-addr-alert (2026-09-08): ARCA RECHAZA la consulta (18 cuentas con CUIT válido y
   incumplimientos de DDJJ/baja de oficio) — riesgo fiscal, no un hueco ni una confirmación
   neutra. `--red-text` medido en los DOS temas contra `--surface` (fondo de la tarjeta):
   claro #d70015/#ffffff = 5,38:1 · oscuro #ff6961/#2c2c2e = 4,94:1. Los dos ≥4,5:1 AA. */
.fiscal-addr-alert { font-size: var(--fs-sm); font-weight: 600; color: var(--red-text); }

.el-local-loc { display: inline-flex; align-items: center; gap: 5px; }
a.el-local-loc-link { color: var(--blue-text); text-decoration: none; }
a.el-local-loc-link:hover { text-decoration: underline; }

/* ---- Grilla 2 columnas (v2 densidad, 2026-08-28) ----
   Reemplaza las filas `.frow` apiladas (634px) por celdas en 2 columnas (~362px): Instagram/
   Locales · Ciudad/País · Responsable/Teléfono en "El local"; CUIT/CP · Método/AFIP · Web/
   Facebook en "Identidad y contacto" (misma clase — un solo lugar donde vive este layout,
   §4.1 nº6). Separadores por pseudo-elemento/borde, sin `gap`, mismo patrón que ya usaba
   "Datos adicionales" acá abajo. */
.el-local-grid { display: grid; grid-template-columns: 1fr 1fr; }
.el-local-cell { padding: 8px var(--sp-3) 9px; min-width: 0; position: relative; }
.el-local-cell.row-sep::before {
  content: ""; position: absolute; top: 0; left: 0; right: 0; height: .5px; background: var(--hairline);
}
.el-local-cell.col-sep-left { border-right: .5px solid var(--hairline); }
.el-local-cell .k {
  display: block; font-size: 10.5px; font-weight: 700; letter-spacing: .04em;
  text-transform: uppercase; color: var(--text-3); margin-bottom: 2px;
}
.el-local-cell .fe { display: block; width: 100%; }

/* Filas full-width bajo la grilla (Mapa, Nichos) — mismo patrón visual que `.el-local-clf`
   (label arriba, valor debajo, hairline superior), pero como bloque genérico reusable. */
/* Etiqueta y valor en LA MISMA LÍNEA (2026-09-04, reporte de Fernando: "no quedo para nada
   parecido al mockup"). Tenía razón y la causa era esta regla: `.k` estaba en `display: block`
   con `margin-bottom`, así que CADA campo ocupaba dos renglones + su propio `border-top`. Con
   los 5 campos de "Datos comerciales" vacíos —el caso más común— el bloque se leía como una
   lista larga de "ETIQUETA / Completar" en vez de la tabla compacta del mockup aprobado
   (`docs/design/direcciones/ellocal-2026-09-B3.html`, reglas `.td-row`/`.td-label`/`.td-val`).
   Medidas tomadas del mockup, no inventadas: label 130px fijo que no encoge, fila flex de
   28px mínimo, y el separador se mantiene porque ahí sí ayuda a separar campos. */
.el-local-full {
  padding: 4px var(--sp-4) 5px; border-top: .5px solid var(--hairline); position: relative;
  display: flex; align-items: center; gap: 8px; min-height: 30px;
}
.el-local-full .k {
  flex: 0 0 130px; font-size: 12px; font-weight: 400; letter-spacing: 0;
  text-transform: none; color: var(--text-3); margin-bottom: 0;
}
.el-local-full .v { font-size: 13px; color: var(--text-1); display: flex; align-items: center; gap: 5px; flex: 1 1 auto; min-width: 0; }
/* Mobile: a 375px una etiqueta de 130px deja ~180px para el valor y las direcciones no entran.
   Ahí se vuelve a apilar, que es el comportamiento correcto para una columna angosta. */
/* Excepciones al layout de dos columnas: los bloques que tienen ENCABEZADO propio y una lista
   debajo (puntos de venta) siguen siendo de ancho completo — su `.k` es el título de la sección,
   no la etiqueta de un valor. Sin esto, "Puntos de venta" quedaba como columna izquierda y los
   locales apretados a la derecha (visto en pantalla el 2026-09-04). */
.el-local-full.pv-block, .el-local-full.el-local-nichos-row { display: block; padding: 8px var(--sp-4) 9px; }
.el-local-full.pv-block .k {
  display: block; flex: none; font-size: 10.5px; font-weight: 700; letter-spacing: .04em;
  text-transform: uppercase; margin-bottom: 4px;
}

@media (max-width: 520px) {
  .el-local-full { display: block; padding: 8px var(--sp-4) 9px; }
  .el-local-full .k { display: block; font-size: 10.5px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; margin-bottom: 4px; }
}
.el-local-full .v.link { color: var(--blue-text); text-decoration: none; }
.el-local-full .v.link:hover { text-decoration: underline; }

.el-local-nichos-row { align-items: flex-start; }
/* Full-width (antes vivía en una `.frow` con los chips a la derecha): acá van a la izquierda,
   debajo del label "Nichos" — mismo patrón que el resto de `.el-local-full`. */
.el-local-full .el-local-nichos, .el-local-nichos { display: flex; flex-wrap: wrap; gap: 5px; justify-content: flex-start; min-width: 0; }

.el-local-tel-pill {
  font-size: 11px; color: var(--blue-text); background: var(--blue-soft);
  border: 1px solid var(--blue-border); border-radius: 6px; padding: 2px 8px;
  white-space: nowrap; flex-shrink: 0;
}

/* ============================================================
   KPI BAR de "El local" (contrato B3, 2026-09-04): días sin comprar / días sin
   contacto, debajo de la cabecera y arriba de "Contacto". Dos métricas separadas por un
   hairline vertical — mismo patrón visual que ya usa la grilla 2-col (`.col-sep-left`) pero en
   flex, porque acá el ancho de cada mitad es simétrico y no necesita grid.
   ============================================================ */
.el-local-kpi-bar {
  display: flex; border-top: .5px solid var(--hairline); border-bottom: .5px solid var(--hairline);
  padding: 8px var(--sp-4) 9px;
}
.el-local-kpi-metric { flex: 1; display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.el-local-kpi-metric + .el-local-kpi-metric {
  padding-left: 14px; margin-left: 14px; border-left: .5px solid var(--hairline);
}
.el-local-kpi-row { display: flex; align-items: center; gap: 5px; flex-wrap: nowrap; min-width: 0; }
/* Dot de 6px: LLEVA el color del semáforo (`UI.kpiCompraSeveridad`/`UI.kpiContactoSeveridad`,
   ui.js) — el VALOR queda neutro salvo en verde, para que un cliente "Frío" (65% de la cartera
   en la medición del contrato) no grite alarma permanente (§4.1 nº4, mismo criterio que ya usa
   el semáforo BCRA/deuda). */
.el-local-kpi-dot { width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0; }
.el-local-kpi-dot.k-green { background: var(--green-text); }
.el-local-kpi-dot.k-yellow { background: var(--yellow-text); }
.el-local-kpi-dot.k-red { background: var(--red-text); }
.el-local-kpi-val { font-size: 15px; font-weight: 700; color: var(--text-1); line-height: 1.2; white-space: nowrap; }
.el-local-kpi-val.el-local-kpi-green { color: var(--green-text); }
/* Estado "sin registro"/"sin compras" — 88% de las cuentas hoy no tiene ningún contacto (medido
   en producción). NO es alarma ni cero (§4.1 nº2/nº8): itálica muted, sin dot, sin chip — "todavía
   no medimos esto", no "algo está mal". */
.el-local-kpi-val.el-local-kpi-never { font-size: 13px; font-weight: 500; color: var(--text-3); font-style: italic; }
.el-local-kpi-lbl { font-size: 10.5px; color: var(--text-3); line-height: 1.2; white-space: nowrap; padding-left: 11px; }
.el-local-kpi-metric.el-local-kpi-nodot .el-local-kpi-lbl { padding-left: 0; }
.el-local-kpi-row .chip.mini { flex-shrink: 0; }

/* ============================================================
   "Contacto" de "El local" (contrato B3): dos líneas full-width — responsable de compras
   (con su ETIQUETA, ítem 6) + WhatsApp; Instagram + Localidad. Reemplaza a la grilla 2-col vieja
   para estos campos (Instagram/Localidad/Responsable/Teléfono ya no viven en `.el-local-grid`).
   ============================================================ */
.el-local-contact { padding: 10px var(--sp-4) 11px; display: flex; flex-direction: column; gap: 7px; }
.el-local-contact-l1, .el-local-contact-l2 {
  display: flex; align-items: center; flex-wrap: wrap; gap: 6px; row-gap: 6px; min-width: 0;
}
.el-local-contact-role {
  font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em;
  color: var(--text-3); flex-shrink: 0;
}
.el-local-contact-sep { color: var(--text-3); flex-shrink: 0; }
.el-local-contact-l1 .btn-wa { margin-left: auto; }
.el-local-contact-ig { display: inline-flex; align-items: center; gap: 5px; min-width: 0; color: var(--text-2); }
.el-local-contact-ig .el-local-ico, .el-local-contact-ig svg { color: var(--text-3); flex-shrink: 0; }
.el-local-ig-compact { display: inline-flex; align-items: center; gap: 6px; min-width: 0; }
.el-local-ig-compact > a { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 260px; }

/* Fecha de revisión de Clasificación (P3 ronda 2, 2026-09-02): vivía en una fila propia de "El
   local" (`.el-local-clf`, con `margin-left:auto` para pegarse al borde derecho de ESA fila) —
   esa fila se retiró del cuerpo entera, Clasificación/Potencial y su fecha se mudaron a la
   cabecera (`views/ficha.js` `buildHeaderChips`), dentro de `.chips-row` (`gap:6px`, ya se
   encarga del espaciado entre chips). `margin-left:auto` en ESE contenedor empujaría todo lo que
   venga después al borde opuesto — no es lo que se quiere acá, se retira. */
.el-local-clf-date { font-size: 11px; color: var(--text-3); white-space: nowrap; }

/* "Datos comerciales" (contrato B3, 2026-09-04) — filas etiqueta-valor apiladas
   (`.el-local-full`, ya definida arriba), no la grilla `1fr 1fr` que tenía antes. El wrapper
   `.el-local-datos` ya no necesita border/padding propios: cada `.el-local-full` hija aporta su
   propio hairline superior — con los dos, la primera fila quedaba con doble borde. */
.el-local-datos { display: flex; flex-direction: column; }
/* "Marcas" no se corta teniendo lugar (§4.1 nº18/nº8 del contrato B3): dentro de una fila
   full-width el valor deja de truncar con ellipsis y pasa a envolver — con más espacio que la
   vieja grilla de 2/4 columnas, la mayoría de los valores entran en una sola línea igual; el
   `title` con el texto completo lo sigue poniendo `editable()` por default. */
.el-local-full .v .fe { display: flex; width: 100%; }
.el-local-full .v .fe-val-text {
  white-space: normal; overflow: visible; text-overflow: clip; word-break: break-word;
}
/* Punto rojo de "sin verificar" (ítem 7) — SOBRE LA ETIQUETA, nunca sobre el dato; el rojo fuerte
   queda reservado a la deuda (§4.1 nº4). Mismo tamaño/posición que `.td-dot` del mockup. */
.el-local-dot {
  display: inline-block; width: 5px; height: 5px; border-radius: 50%;
  background: var(--red-text); margin-left: 4px; vertical-align: 2px; flex-shrink: 0;
}
/* "Página web" (ítem 9) — fila propia dentro de "Datos comerciales", condicional a vender online. */
.el-local-web-row { display: flex; align-items: center; gap: 6px; padding: 8px var(--sp-4) 9px; border-top: .5px solid var(--hairline); }
.el-local-web-ico { font-size: 12px; flex-shrink: 0; }

/* Banda "Odoo informa otro CUIT" (`cuitDivergenciaBandNode`) DENTRO de "El local", fuera del
   disclosure "Ver más información" (2026-09-08 — el `<details>` nace cerrado y la alerta no se
   veía nunca, medido con `checkVisibility()`). `.group.el-local` es flex column SIN `gap`: las
   filas vecinas se separan con su propio `border-top` + padding, pero `.alta-band` es una caja
   con fondo y borde propios (mismo componente que `.dup-banner`, pensado para un padre con `gap`
   como `.ficha-col`) — sin este margen quedaría pegada a "Datos comerciales" arriba y "Ver más
   información" abajo. `.el-local >` (hijo directo) para no afectar `vinculoCandidatoBandNode`,
   que vive fuera de este grupo. */
.el-local > .alta-band { margin: var(--sp-3) var(--sp-4); }

/* "Ver más información" (contrato B3, ítem 11) — `<details>` INLINE dentro de "El local" (no un
   `.group` aparte: acá el contenedor exterior YA ES la tarjeta). Mismo patrón summary/chevron que
   `.nivel-disclosure`/`.prob-resolved` (prefijo propio para no heredar reglas pensadas para
   envolver un `.group` entero — §4.1 nº13, "sube a definición común" ya se hizo una vez con ese
   layout; acá se REUSA el mismo lenguaje visual, no se copia dos veces más). */
/* `.el-local-vermas` pasó de <details> plegado a bloque `.group` abierto en la pestaña Información
   (F8, 2026-09-13); las reglas de summary/título se retiraron con el disclosure. */
.el-local-vermas-body { border-top: .5px solid var(--hairline); }
/* Contador "N de M sin verificar" (ítem 5) — mismo lenguaje visual que ya usa el resto de la app
   para conteos secundarios (`.chip.mini` sin serlo: acá es texto plano, no un chip semántico). */
.n-unv { font-size: 11px; color: var(--text-4); background: var(--gray-soft); border-radius: 5px; padding: 1px 6px; flex-shrink: 0; }

/* Caso "sin perfil de Notion" (60 de 430 clientes) — el bloque no desaparece, explica el hueco y
   ofrece completarlo (§4.1 nº7). */
.el-local-noprofile {
  padding: 20px var(--sp-4) 18px; display: flex; flex-direction: column; align-items: center;
  text-align: center; gap: 8px;
}
.el-local-noprofile .el-local-ico { color: var(--text-4); }
.el-local-noprofile-title { font-size: 14px; font-weight: 600; color: var(--text-1); }
.el-local-noprofile-msg { font-size: 12.5px; color: var(--text-3); max-width: 320px; line-height: 1.5; }
.el-local-noprofile-cta {
  display: inline-flex; align-items: center; gap: 5px; font-size: 13px; color: var(--blue-text);
  font-weight: 600; text-decoration: none; background: var(--blue-soft); border: 1px solid var(--blue-border);
  border-radius: var(--r-sm); padding: 6px 14px; margin-top: 2px;
}

/* ============================================================
   SITUACIÓN CREDITICIA · BCRA (views/ficha.js `bcraGroup`, 2026-08-29).
   Reusa `.group`/`.group-hd`/`.frow`/`.chip` del resto de la ficha —
   acá solo lo exclusivo de este bloque. El único rojo permitido es el
   `.chip.red` de situación ≥3 (semáforo real); los estados de "no hay
   dato" son ámbar suave o gris neutro, nunca rojo — este bloque no
   compite con `alertaCompuesta`, la única alerta roja de plata NUESTRA
   (§4.1 nº4).
   ============================================================ */
.bcra-msg { margin: 0; padding: var(--sp-2) var(--sp-4); font-size: var(--fs-sm); color: var(--text-2); }
/* Mismo lenguaje que `.el-local-empty-val` (itálica, tono apagado) pero en ámbar — acá el hueco
   SÍ requiere una acción (falta CUIT, o falló la consulta), nunca rojo (§4, pedido explícito). */
.bcra-msg-warn { color: var(--orange-text); font-style: italic; font-weight: 600; }
.bcra-foot {
  margin: 0; padding: 8px var(--sp-4) 10px; font-size: 11px; color: var(--text-3);
  border-top: .5px solid var(--hairline);
}

/* ---- Detalle BCRA ampliado (contrato-bcra-detalle.md, 2026-09-08): marcas de riesgo,
   tendencia + línea de tiempo SVG, tabla por entidad, cheques. Todo dentro de `bcraGroup`,
   debajo de `bcra-foot`. Sin colores nuevos: `.chip`/`--text-*`/`--hairline` ya están medidos
   en los dos temas (tokens.css) — acá solo geometría y el mapeo situación→color de
   `UI.bcraTimelineSvg` reusa las variables de TEXTO de cada chip (`--green-text` etc.), que
   pasan WCAG AA en ambos temas por ser la misma variable que ya pinta el texto del chip. */
.bcra-marcas {
  display: flex; flex-wrap: wrap; gap: 6px; padding: 0 var(--sp-4) var(--sp-2);
}
.bcra-tl {
  padding: var(--sp-2) var(--sp-4) var(--sp-3);
  border-top: .5px solid var(--hairline);
}
.bcra-tl-sentence { margin: 0 0 6px; font-size: var(--fs-sm); color: var(--text-2); }
.bcra-tl-svg { width: 100%; display: block;  }
/* Tope de alto (2026-09-08, medido a 1440: sin tope el SVG rendía 303 px de alto por conservar la
   proporción del viewBox). Con `meet` el dibujo se centra y no se deforma. */
.bcra-tl-svg svg { max-height: 160px; display: block; margin: 0 auto; }
.bcra-tl-svg svg { width: 100%; height: auto; display: block; }
.bcra-tl-svg circle { cursor: default; }

/* Tabla por entidad — desktop = tabla densa; < 700px se apila una tarjeta por entidad (mismo
   patrón que `.crit-table` en criterios.css, sin depender de esa hoja). */
.bcra-ent-wrap { padding: 0 var(--sp-4) var(--sp-2); border-top: .5px solid var(--hairline); }
.bcra-ent-title { margin: var(--sp-2) 0 4px; font-size: 11px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--text-3); }
.bcra-tbl { width: 100%; border-collapse: collapse; }
.bcra-tbl th {
  font-size: 10px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase;
  color: var(--text-3); padding: 5px 8px; text-align: left; white-space: nowrap;
  border-bottom: 1px solid var(--border-strong);
}
.bcra-tbl td {
  padding: 7px 8px; font-size: var(--fs-sm); color: var(--text-1); vertical-align: top;
  border-bottom: .5px solid var(--hairline);
}
.bcra-tbl tr:last-child td { border-bottom: none; }
.bcra-tbl td.num { text-align: right; white-space: nowrap; }
/* Nombre de entidad: puede ser largo ("BANCO DE LA PROVINCIA DE BUENOS AIRES") — una línea con
   ellipsis y SIEMPRE `title` (§4.1 nº12), nunca se apila su propia celda a varias líneas. */
/* ⚠️ Sin ancho fijo (2026-09-08, medido: con `max-width: 220px` la columna medía 302 px y
   "BANCO PROVINCIA DEL NEUQUÉN…" se cortaba con 80 px de sobra al lado — el mismo defecto del
   31/8). La celda del nombre toma TODO lo que las otras columnas no usan: `width: 100%` sobre la
   celda + `max-width: 0` es lo que permite el ellipsis dentro de una tabla auto-layout; las demás
   columnas se ajustan a su contenido con `width: 1%` + nowrap. */
.bcra-tbl td:first-child { width: 100%; max-width: 0; }
.bcra-tbl th:not(:first-child), .bcra-tbl td:not(:first-child) { width: 1%; white-space: nowrap; }
.bcra-ent-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; display: block; }
.bcra-ent-marcas { display: flex; flex-wrap: wrap; gap: 3px; margin-top: 3px; }

@media (max-width: 700px), (pointer: coarse) {
  .bcra-tbl, .bcra-tbl thead, .bcra-tbl tbody, .bcra-tbl tr, .bcra-tbl th, .bcra-tbl td { display: block; }
  .bcra-tbl thead { display: none; }
  .bcra-tbl tr {
    background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--r);
    padding: var(--sp-3); margin-bottom: var(--sp-2);
  }
  .bcra-tbl td {
    display: flex; justify-content: space-between; align-items: flex-start; gap: var(--sp-2);
    padding: 5px 0; border-top: .5px solid var(--hairline); border-bottom: none !important;
    text-align: right;
  }
  .bcra-tbl td:first-child { border-top: none; }
  .bcra-tbl td::before {
    content: attr(data-label); font-size: var(--fs-caption); color: var(--text-3); font-weight: 500;
    min-width: 90px; flex-shrink: 0; text-align: left; padding-top: 1px;
  }
  .bcra-tbl td:first-child, .bcra-tbl td:not(:first-child) { width: auto; max-width: none; }
  /* En la tarjeta apilada el nombre es el título de la fila: a ancho completo y en varias líneas
     (medido a 375 px: en una línea se cortaban 2 de 6 entidades). */
  .bcra-tbl td:first-child { display: block; text-align: left; }
  .bcra-tbl td:first-child::before { display: none; }
  .bcra-ent-name { white-space: normal; overflow: visible; text-overflow: clip; font-weight: 600; text-align: left; }
}

.bcra-cheques-wrap { padding: 0 var(--sp-4) var(--sp-3); border-top: .5px solid var(--hairline); }
.bcra-cheques-list { list-style: none; margin: 4px 0 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.bcra-cheque-row {
  display: flex; flex-wrap: wrap; gap: 4px 8px; align-items: baseline;
  font-size: var(--fs-sm); color: var(--text-1);
}
.bcra-cheque-row .fecha { color: var(--text-3); font-variant-numeric: tabular-nums; }
.bcra-cheque-row .monto { font-weight: 700; }
.bcra-cheque-row .causal { color: var(--red-text); }
.bcra-cheque-row .entidad {
  color: var(--text-3); max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* ============================================================
   FIELD-EDIT — edición in-situ de "El local" e "Identidad y
   contacto" (components/field-edit.js, views/ficha.js, 2026-08-28).
   Pedido textual de Fernando: "ver y agregar los datos que faltan
   necesito poder" + "cuando no hay dato necesito ver que no hay
   dato para cuando lo llamo completarlo". Comparte `.frow`/`.chip`
   con el resto de la ficha — acá solo lo exclusivo del editor.
   ============================================================ */
.fe { display: inline-flex; align-items: center; min-width: 0; max-width: 100%; }
/* T4 (2026-09-08): ayuda "es el mismo número" bajo el editor de CUIT/CUIL, en cualquier
   contexto donde se monte (bandeja, fila densa, disclosure). Nota chica, --text-3 alcanza. */
.fe-cuit-wrap { display: block; min-width: 0; max-width: 100%; }
.fe-cuit-hint { font-size: 11px; color: var(--text-3); margin: 2px 0 0; line-height: 1.3; }
/* En "Datos adicionales" el valor va DEBAJO de la etiqueta (grilla de 2 columnas, no fila
   label/valor) — `.fe` pasa a ocupar el ancho completo de su celda en vez de encogerse. */
.el-local-datos-item .fe { display: block; }
.el-local-datos-item .fe .fe-btn { width: 100%; }
/* El tilde de verificación (2026-08-31, components/verify-check.js) mete un segundo hijo dentro
   de `.fe` cuando el campo es verificable (Marcas/Metros² lo son) — el botón deja de ser hijo
   único y su `width:100%` de arriba pasa a resolver contra `.fe-link-row`, no contra `.fe`. Sin
   esto el blanco de click ancho de la regla anterior se perdía apenas aparecía el tilde. */
.el-local-datos-item .fe .fe-link-row { display: flex; width: 100%; align-items: center; }
.el-local-datos-item .fe .fe-link-row .fe-btn { flex: 1 1 auto; min-width: 0; width: auto; }

/* Hueco editable: mismo lenguaje visual que el hueco de solo lectura (`.el-local-gap`, borde
   punteado ámbar) — la diferencia es que ACÁ es un <button> real. */
.fe-btn {
  display: inline-flex; align-items: center; gap: 5px; min-width: 0; max-width: 100%;
  background: none; border: none; padding: 3px 5px; margin: -3px -5px; border-radius: 6px;
  font: inherit; font-size: var(--fs-sm); color: var(--text-1); cursor: pointer; text-align: left;
}
.fe-btn:hover { background: var(--surface-3); }
.fe-btn .ico { color: var(--text-4); flex-shrink: 0; }
.fe-btn-empty {
  color: var(--orange-text); font-weight: 700; border: 1px dashed var(--orange-border);
  border-radius: 6px; padding: 1px 8px; margin: 0; font-size: 11px; white-space: nowrap;
}
.fe-btn-empty:hover { background: var(--orange-soft); }

/* Texto plano truncado con `title` (§4.1 nº12: nada se corta sin forma de leer el resto). */
.fe-val-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; max-width: 100%; }

/* Valor con acción nativa (llamar/escribir/abrir) + lápiz de edición AL LADO, nunca anidado
   adentro del link — un <button> no puede contener un <a> válido. */
.fe-link-row { display: inline-flex; align-items: center; gap: 6px; min-width: 0; max-width: 100%; }
.fe-val-link { color: var(--blue-text); text-decoration: none; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.fe-val-link:hover { text-decoration: underline; }
.fe-pencil-btn {
  display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0;
  width: 22px; height: 22px; border-radius: 50%; background: none; border: none;
  color: var(--text-4); cursor: pointer;
}
.fe-pencil-btn:hover { background: var(--surface-3); color: var(--text-2); }

/* Editor abierto — la fila que lo contiene se ensancha vía JS (`setRowExpanded`, flex-wrap) para
   que el input entre completo (§4.1 nº12), esto solo define el contenido de la caja. */
.fe-editbox { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; width: 100%; padding: 2px 0; }
.fe-input {
  flex: 1 1 140px; min-width: 0; max-width: 100%; padding: 6px 9px;
  border: 1px solid var(--border-strong); border-radius: var(--r-sm);
  background: var(--surface); color: var(--text-1); font-size: 13px; font: inherit;
}
.fe-icon-btn {
  flex-shrink: 0; width: 26px; height: 26px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--border); background: var(--surface); cursor: pointer; color: var(--text-2);
}
.fe-icon-btn:hover { background: var(--surface-3); }
.fe-icon-btn:disabled { opacity: .5; cursor: not-allowed; }
.fe-icon-btn.fe-save { color: var(--green-text); background: var(--green-soft); border-color: var(--green-border); }
.fe-icon-btn.fe-cancel { color: var(--text-3); }
.fe-error { flex-basis: 100%; font-size: 11px; color: var(--red-text); }
.fe-loading { font-size: 12px; color: var(--text-3); }

/* Preflight informativo antes de guardar (2026-09-07, CUIT editable — docs/contrato-cuit-editable.md
   §8.2). Arriba del `.fe-editbox` en el DOM (field-edit.js lo antepone), fondo neutro para lo que
   solo informa (ej. "esta cuenta ya factura"); `.fe-preflight-warn` sube a tono ámbar para lo que
   pide atención (un duplicado) — nunca rojo: todavía no pasó nada, es una advertencia a leer antes
   de decidir, no un error (§4.1 nº8, un dato que no es la buena noticia no lleva su color). */
.fe-preflight-slot { display: flex; flex-direction: column; gap: 4px; width: 100%; margin-bottom: 6px; }
.fe-preflight-slot:empty { display: none; }
.fe-preflight-item {
  font-size: var(--fs-caption); color: var(--text-2); line-height: 1.4;
  background: var(--surface-2); border-radius: var(--r-sm); padding: 6px var(--sp-2);
}
.fe-preflight-warn { background: var(--orange-soft); color: var(--orange-text); }

/* Área táctil de 44px para TODO lo que se toca al editar (2026-08-28).

   Medido con el banco de pruebas a 375px sobre el bloque "El local": los 14 controles editables
   caían por debajo de 44x44px, el mínimo para el dedo. Los peores eran los lápices, con 22x22 --
   y el contexto de uso lo hace grave: Fernando pidió esto para completar los huecos MIENTRAS
   HABLA POR TELÉFONO con el cliente ("cuando no hay dato necesito ver que no hay dato para cuando
   lo llamo completarlo"). Un blanco de 22px en una mano ocupada se erra.

   El área crece por un pseudo-elemento centrado, NO por padding: agrandar la caja real
   descolocaría las filas de una pantalla que ya está apretada. El control se ve igual y se toca
   el doble. Solo en pantallas táctiles: en escritorio, con mouse, 22px es un blanco holgado y
   estirar las áreas invisibles crearía solapamientos entre controles vecinos. */
@media (pointer: coarse) {
  .fe-pencil-btn, .fe-icon-btn, .fe-btn, .fe-btn-empty, .link-btn { position: relative; }
  .fe-pencil-btn::after, .fe-icon-btn::after, .fe-btn::after, .fe-btn-empty::after, .link-btn::after {
    content: ""; position: absolute; top: 50%; left: 50%;
    transform: translate(-50%, -50%);
    width: 100%; height: 100%; min-width: 44px; min-height: 44px;
  }
}

/* ============================================================
   LOCALIDAD (type-ahead) — components/localidad-input.js,
   2026-08-29. Indicio de normalización (Ficha, campo "Localidad" de
   "El local"): DISCRETO a propósito (§4.1 nº4, nunca una alerta roja
   por un dato simplemente sin verificar).
   ============================================================ */
.loc-badge {
  display: inline-flex; align-items: center; justify-content: center;
  width: 14px; height: 14px; border-radius: 50%; font-size: 10px; font-weight: 700;
  flex-shrink: 0; line-height: 1;
}
.loc-badge-ok { background: var(--green-soft); color: var(--green-text); }
.loc-badge-warn { background: var(--gray-soft); color: var(--text-3); }

/* Lista de sugerencias: montada en `document.body` con `position:fixed` (ver el comentario largo
   en localidad-input.js — un `.table-wrap` con `overflow-x:auto` recorta el eje Y también, así
   que anidar la lista adentro la cortaría en la bandeja "Sin vendedor"). El posicionamiento real
   (left/top/width) lo calcula JS contra `getBoundingClientRect()` del input; acá solo el skin. */
.loc-ac-list {
  position: fixed; z-index: var(--z-popover); margin: 0; padding: 4px; list-style: none;
  background: var(--surface); border: 1px solid var(--border-strong); border-radius: var(--r-sm);
  box-shadow: var(--sh-2); max-height: 260px; overflow-y: auto;
}
.loc-ac-item {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  padding: 7px 9px; border-radius: 8px; cursor: pointer; font-size: 13px; color: var(--text-1);
}
.loc-ac-item:hover, .loc-ac-item.active { background: var(--blue-soft); }
/* Encabezado de grupo (M5, 2026-09-06): NO seleccionable — ↑/↓/Enter lo saltean por construcción
   (navItems, en localidad-input.js, nunca lo incluye). "Clientes" antes de "Localidades". */
.loc-ac-group {
  padding: 6px 12px; font-size: 11px; font-weight: 600; text-transform: uppercase;
  letter-spacing: .03em; color: var(--text-3); cursor: default;
}
/* Ítem "stacked" (M5): nombre arriba, subtítulo (razón social/comercial/localidad) debajo — lo
   usa el grupo "Clientes" del buscador del mapa. Los ítems de localidad NO llevan esta clase y
   siguen lado a lado, sin cambios. */
.loc-ac-item.stacked { flex-direction: column; align-items: flex-start; gap: 1px; }
.loc-ac-item.stacked .loc-ac-name { max-width: 100%; }
.loc-ac-item.stacked .loc-ac-prov {
  flex-shrink: initial; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.loc-ac-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.loc-ac-prov { flex-shrink: 0; font-size: 11px; color: var(--text-3); }
.loc-ac-item.active .loc-ac-prov { color: var(--blue-text); }

@media (pointer: coarse) {
  /* Blanco táctil ≥44px por fila de sugerencia (regla del proyecto) — vía min-height directo:
     a diferencia de los botones circulares de arriba, acá no hay controles vecinos con los que
     solaparse, así que no hace falta el truco del pseudo-elemento. */
  .loc-ac-item { min-height: 44px; padding: 10px 9px; }
}

/* Nichos (multi-selección) — checklist vertical, nunca una fila de chips angosta: el texto de
   cada nicho ("🌆 Urban / Streetwear") necesita su propio renglón para no cortarse (§4.1 nº12). */
.fe-multiselect {
  display: flex; flex-direction: column; gap: 2px; width: 100%; max-height: 240px; overflow-y: auto;
  border: 1px solid var(--border-strong); border-radius: var(--r-sm); padding: 6px; background: var(--surface);
}
.fe-ms-item { display: flex; align-items: center; gap: 8px; font-size: 12.5px; color: var(--text-1); padding: 4px; border-radius: 6px; cursor: pointer; }
.fe-ms-item:hover { background: var(--surface-3); }
.fe-ms-item input { flex-shrink: 0; }

/* Aviso de write-back — UNA vez por bloque (§4.1 nº6), al pie de "El local" e "Identidad y
   contacto"; nunca repetido campo por campo. */
/* Aviso de sincronizacion: informativo, no puede pesar como una fila de datos (medido: 36px
   con el 100% del ancho vacio, dentro de un bloque que compite por la primera pantalla). */

/* ============================================================
   IDENTIDAD Y CONTACTO v2 — direcciones + personas agrupadas
   (views/ficha.js, 2026-08-28). Contrato:
   docs/design/direcciones/ficha-ellocal-v2-densidad.html. La grilla de CUIT/CP/Método/AFIP/Web/
   Facebook reusa `.el-local-grid`/`.el-local-cell` de arriba (§4.1 nº6) — acá solo lo exclusivo
   de direcciones y personas.
   ============================================================ */
.addr-row { padding: 7px var(--sp-4) 8px; border-top: .5px solid var(--hairline); position: relative; }
.addr-row .k {
  display: block; font-size: 10.5px; font-weight: 700; letter-spacing: .04em;
  text-transform: uppercase; color: var(--text-3); margin-bottom: 2px;
}
.addr-row .fe { display: block; width: 100%; }
.addr-sub { font-size: 10px; font-weight: 400; text-transform: none; letter-spacing: 0; color: var(--text-4); }
/* "En el perfil figura diferente" — solo aparece cuando `direccion_contacto` DIFIERE de
   `direccion` (§4.1 nº6: si coinciden, no se repite el dato). Fondo sutil para separarla de las
   dos filas "normales" de arriba, sin ser una alerta. */
.addr-row.addr-perfil { background: var(--surface-2); }

/* Domicilio fiscal (ARCA) — deliberadamente OTRA clase, nunca `.addr-row` (views/ficha.js,
   2026-09-07, migración 0052): no es una 4ª dirección del local, es el domicilio fiscal del
   titular. Rótulo propio (`.subhd` "Domicilio fiscal (ARCA)") arriba de esta fila; acá solo el
   dato + su fecha, sin el bloque de etiqueta `.k` en mayúscula que usan las direcciones del
   local — así la caja se lee distinta a simple vista, no solo por el título. */
.fiscal-addr-row { padding: 7px var(--sp-4) 8px; border-top: .5px solid var(--hairline); }
.fiscal-addr-fecha { display: block; margin-top: 2px; }

/* `.persona-group`/`.persona-hd`/`.persona-fields`/`.persona-field` se retiraron el 2026-09-14 (F12,
   Tanda B): las tres personas fijas ya no se dibujan aparte — viven en «Personas» (contactos.css). */

/* Email con ";"/"," — múltiples links `mailto:` separados, nunca texto plano ni un mailto roto
   (caso real: `email_compras` con dos direcciones). */
.fe-val-multi { display: inline-flex; align-items: center; gap: 5px; flex-wrap: wrap; min-width: 0; }
.fe-val-multi .fe-val-link { overflow: hidden; text-overflow: ellipsis; max-width: 100%; }
.email-sep { color: var(--text-4); font-size: 10px; }

/* Bloque de saldo (protagonista) */
.saldo-hd {
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2);
  padding: var(--sp-3) var(--sp-4) 0;
}
.saldo-hd .t {
  font-size: var(--fs-caption); font-weight: 700; letter-spacing: .08em;
  text-transform: uppercase; color: var(--text-3);
}
.saldo-total { padding: var(--sp-1) var(--sp-4) var(--sp-3); display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.saldo-total .v { font-size: var(--fs-hero); font-weight: 800; letter-spacing: -.035em; color: var(--red-text); }
.saldo-total .v.ok { color: var(--green-text); }
/* Fix auditoría 2026-08-25 (1.2/C1, mobile): el monto pierde el rojo cuando ya hay una alerta
   compuesta arriba avisando lo mismo con más fuerza — dos rojos pegados es "todo grita, nada
   se escucha" (§4.1 nº4). El número sigue siendo el mismo, solo el color deja de competir. */
.saldo-total .v.demoted { color: var(--text-1); }
.saldo-total .s { font-size: var(--fs-caption); color: var(--text-3); }
.saldo-aging { padding: 0 var(--sp-4) var(--sp-3); }

/* ============================================================
   Nivel 1 — "qué hago con esta cuenta hoy" (compartido mobile +
   desktop desde el fix de auditoría 2026-08-25, hallazgo 1.2/C1).
   Antes vivían solo dentro del `@media (min-width:900px)` porque
   solo el rail de escritorio los usaba; ahora `renderMobileBody`
   (ficha.js) reusa las mismas funciones, así que el estilo base
   sale del media query — el media query abajo solo ajusta lo que
   de verdad es específico del rail sticky de escritorio.
   ============================================================ */
/* Semáforo de deuda (Fernando, 2026-09-01: "si el cliente debe mas de 1M … rojo, si debe menos
   … amarillo, y si debe menos de 100 mil … gris, es normal"). Los cinco estados los decide
   `UI.deudaSeveridad` (ui.js) sobre el MISMO neto que se dibuja; acá solo se pintan.
   ⚠️ La base pasó a NEUTRA el 2026-09-01. Antes `.rail-saldo` sin clase era ROJA, o sea que el
   estado por defecto pintaba la alarma: cualquier día que una clase no llegara, una cuenta sana
   se veía como la peor. La severidad ahora se declara siempre y explícitamente (`is-alta`).
   ⚠️ `is-baja` (debe poco, medido) e `is-empty` (no sabemos cuánto debe) comparten la caja gris
   y NO se ven igual: is-baja muestra su número en --text-1, is-empty dice "Sin datos" apagado en
   --text-3. Un dato faltante no puede leerse como "está todo bien" (§4.1 nº8) — el 2026-09-01
   había 282 cuentas sin saldo contra 29 realmente por debajo del piso. */
/* ---- Tarjetas de la Ficha 360 (`.rail-*`) — FUERA del media query de escritorio ----
   Nacieron para el rail sticky, de ahí el prefijo. Desde las pestañas (2026-09-13,
   docs/contrato-ficha-tabs.md) las mismas tarjetas son BLOQUES de Resumen (`railQuienEs`),
   Cliente (`railEstadoRelacion`) y Comunicación (`railUltimosContactos`), y se dibujan en los
   DOS formatos: adentro del media query salían sin ningún estilo en celular. Mismo movimiento
   que ya se le hizo a `.rail-saldo`/`.rail-flag` el 2026-08-25 cuando el Nivel 1 bajó a mobile.
   Siguen siendo EXCLUSIVOS de la Ficha 360 — el rail de Hoy usa `.hoy-rail-card`/`.hoy-rail-row`
   desde el fix de auditoría 2026-08-25 (3.12), así que no pueden volver a pisarse. ---- */
/* `.rail-card`/`.rail-row` de acá para abajo son EXCLUSIVOS de la Ficha 360 (views/ficha.js) —
   el rail de Hoy usa `.hoy-rail-card`/`.hoy-rail-row` (definidos junto a `.rail` más arriba en
   este archivo, fuera de este media query) desde el fix de auditoría 2026-08-25 (3.12), así
   que ya no pueden volver a pisarse por compartir nombre. */
.rail-card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-lg); box-shadow: var(--sh-1); overflow: hidden; }
.rail-hd { font-size: 10.5px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--text-3); padding: 10px var(--sp-3) 6px; }
.rail-row { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2); padding: 6px var(--sp-3); border-top: .5px solid var(--hairline); font-size: 12px; }
.rail-card .rail-hd + .rail-row { border-top: none; }
.rail-row .k { color: var(--text-3); }
.rail-row .v { font-weight: 600; color: var(--text-1); text-align: right; }
.rail-row-date { font-size: 10.5px; color: var(--text-3); }
/* NPS "sin relevar": el hueco se hace VISIBLE a propósito (pedido Fernando 2026-08-25 — "es
   importantísimo, si no lo tenemos hay que ir a buscarlo"). Borde punteado = incompleto,
   invita a completarse; NO es el mismo tratamiento que un dato ausente que se omite. */
.rail-row-gap .v.gap {
  color: var(--orange-text); font-weight: 700; border: 1px dashed var(--orange-border);
  border-radius: 6px; padding: 1px 7px; font-size: 11px;
}

/* "Últimos contactos" (2026-09-01) — atajo de lectura al mismo dato que dibuja el timeline de
   Actividad (§4.1 nº6, no es una segunda fuente). Fila: ícono del canal + "quién · tipo" con
   ellipsis+title (§4.1 nº12, el nombre puede ser largo) + fecha corta a la derecha. */
.rail-contacto-row {
  display: flex; align-items: center; gap: 8px; padding: 6px var(--sp-3);
  border-top: .5px solid var(--hairline); font-size: 12px; color: var(--text-2);
}
.rail-card .rail-hd + .rail-contacto-row { border-top: none; }
.rail-contacto-row .ico { flex-shrink: 0; color: var(--text-3); }
.rc-who {
  flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-weight: 600; color: var(--text-1);
}
.rc-date { flex-shrink: 0; font-size: 10.5px; color: var(--text-3); }
/* Vacío ≠ oculto (§4.1 nº7): "todavía nadie habló con este cliente" es información, no ruido. */
.rail-row-empty { padding: 4px var(--sp-3) var(--sp-2); font-size: 12px; color: var(--text-3); }
.rail-ver-link {
  display: block; padding: 8px var(--sp-3); border-top: .5px solid var(--hairline);
  font-size: 11.5px; font-weight: 600; color: var(--blue-text); text-decoration: none;
}
.rail-ver-link:hover { background: var(--gray-soft); }

/* «Quién es» — confirmación de territorio en menos de un segundo */
.rail-who { display: flex; align-items: center; gap: 10px; padding: var(--sp-3); }
.rail-who .mono { width: 38px; height: 38px; font-size: 13px; flex-shrink: 0; }
.rail-who-text { min-width: 0; }
.rail-who-name { font-size: 14px; font-weight: 700; color: var(--text-1); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rail-who-comercial { font-size: 11.5px; color: var(--text-3); margin-top: 1px; }

.rail-saldo { border-radius: var(--r-lg); box-shadow: var(--sh-1); padding: var(--sp-3); border: 1px solid var(--border); background: var(--surface); }
.rail-saldo.is-alta { border-color: var(--red-border); background: var(--red-soft); }
.rail-saldo.is-media { border-color: var(--yellow-border); background: var(--yellow-soft); }
.rail-saldo.is-favor { border-color: var(--green-border); background: var(--green-soft); }
.rs-lbl { font-size: 10.5px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--text-3); }
.rail-saldo.is-alta .rs-lbl { color: var(--red-text); opacity: .75; }
.rail-saldo.is-media .rs-lbl { color: var(--yellow-text); opacity: .8; }
.rail-saldo.is-favor .rs-lbl { color: var(--green-text); opacity: .75; }
.rs-monto { font-size: 24px; font-weight: 800; letter-spacing: -.03em; color: var(--text-1); margin-top: 2px; }
.rail-saldo.is-alta .rs-monto { color: var(--red-text); }
.rail-saldo.is-media .rs-monto { color: var(--yellow-text); }
/* Debe, pero poco: el número se lee entero y no grita — un renglón menos alto que el rojo. */
.rail-saldo.is-baja .rs-monto { color: var(--text-1); font-size: 20px; }
.rail-saldo.is-favor .rs-monto { color: var(--green-text); }
.rail-saldo.is-ok .rs-monto { color: var(--green-text); font-size: 17px; }
.rail-saldo.is-empty .rs-monto { color: var(--text-3); font-size: 17px; }
.rs-detalle { display: flex; flex-wrap: wrap; gap: 4px 10px; margin-top: 6px; font-size: 11px; }
.rs-item { font-weight: 600; }
/* "Vencido $X" hereda la severidad de la caja: un renglón rojo dentro de una caja amarilla o
   gris le contaría al lector algo distinto de lo que dice el marco (§4.1 nº4). En `is-favor` SÍ
   se queda rojo: ahí el neto es a favor pero el vencido existe, y es la única alerta del bloque. */
.rs-item.due { color: var(--red-text); }
.rail-saldo.is-media .rs-item.due { color: var(--yellow-text); }
.rail-saldo.is-baja .rs-item.due { color: var(--text-2); }
.rs-item.favor { color: var(--green-text); }

/* Bandera de entrega pendiente — Nivel 1 suelto, versión compacta en el rail de escritorio */
.rail-flag {
  display: flex; align-items: center; gap: 7px; padding: 8px var(--sp-3);
  background: var(--orange-soft); border: 1px solid var(--orange-border); border-radius: var(--r-sm);
  color: var(--orange-text); font-size: 12px; font-weight: 600;
}

/* Una sola alerta roja por bloque (regla §4.1 nº4): la compuesta es roja, la de saldo a favor
   es verde — nunca las dos compitiendo por el mismo tono en la misma card. */
.alerta-compuesta { border: 1px solid var(--red-border); background: var(--red-soft); }
.alerta-compuesta-body { display: flex; gap: var(--sp-3); padding: var(--sp-4); color: var(--red-text); }
.alerta-compuesta-title { font-size: 13px; font-weight: 700; margin-bottom: 2px; }
.alerta-compuesta-txt { font-size: 13px; color: var(--text-1); }
.alerta-compuesta-txt b { color: var(--red-text); }
.callout-favor { border: 1px solid var(--green-border); background: var(--green-soft); }
.callout-body { display: flex; align-items: center; gap: var(--sp-3); padding: var(--sp-4); color: var(--green-text); }
.callout-body p { font-size: 13px; color: var(--text-1); }
.callout-body b { color: var(--green-text); }
.inv {
  display: flex; align-items: center; gap: var(--sp-3);
  min-height: 52px; padding: var(--sp-2) var(--sp-4); position: relative;
}
.inv .doc { font-size: var(--fs-sm); font-weight: 600; color: var(--text-1); }
.inv .meta { font-size: var(--fs-chip); color: var(--text-3); margin-top: 1px; }
.inv .inv-main { min-width: 0; flex: 1; }
.inv .amt { font-size: var(--fs-body); font-weight: 700; color: var(--red-text); min-width: 64px; text-align: right; flex-shrink: 0; }
.saldo-foot { border-top: .5px solid var(--hairline); }
.saldo-foot .btn-txt, .invoices-detail summary {
  display: flex; align-items: center; justify-content: center; width: 100%;
  min-height: 48px; border: none; background: none; cursor: pointer;
  font-size: var(--fs-body); font-weight: 500; color: var(--blue-text);
}
.invoices-detail summary { list-style: none; }
.invoices-detail summary::-webkit-details-marker { display: none; }

.coming-soon {
  background: var(--surface-2); border: 1px dashed var(--border-strong); border-radius: var(--r);
  padding: var(--sp-3) var(--sp-4); color: var(--text-3); font-size: var(--fs-sm);
}

/* ============ M1.5 — Profundidad Odoo (ficha) ============ */
.stack { display: flex; flex-direction: column; gap: var(--sp-4); }

/* Botones de documento en la cabecera */
.doc-wrap { display: flex; flex-direction: column; gap: var(--sp-2); width: 100%; align-items: center; }
.doc-actions { display: flex; flex-wrap: wrap; gap: var(--sp-2); width: 100%; }
.doc-actions .btn { flex: 1; min-width: 150px; }
.anon-toggle { font-size: var(--fs-caption); color: var(--text-2); border-style: dashed; }

/* ============================================================
   ONE-PAGER FUSIONADO (v2 densidad, 2026-08-28) — reemplaza el botón + checkbox suelto
   (`.doc-wrap`/`.anon-toggle` de arriba, que sigue existiendo para no romper `ficha-odoo.js`
   `docActions`, fuera de alcance) por UN control con desplegable propio (views/ficha.js
   `buildOnePagerCtl`). Genérico: se usa con clases `.btn.btn-ghost` en la cabecera mobile y
   `.rail-btn.ghost` en el rail de escritorio — acá solo lo que es propio del control, nunca la
   base del botón.
   SIN `position:absolute`, a propósito: este control vive también dentro de una `.rail-card`
   (`overflow:hidden`, ver el media query de escritorio) — un menú flotante se recortaría contra
   el borde de la tarjeta. El menú empuja el contenido hacia abajo en su lugar. */
.op-ctl { display: flex; flex-direction: column; gap: 4px; width: 100%; min-width: 0; }
.op-row { display: flex; }
.op-row .op-main { flex: 1; min-width: 0; border-top-right-radius: 0; border-bottom-right-radius: 0; }
.op-row .op-arrow {
  flex: 0 0 auto; width: 34px; padding: 0; border-left: none;
  border-top-left-radius: 0; border-bottom-left-radius: 0;
}
.op-menu { display: flex; flex-direction: column; gap: 2px; padding: 4px; background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--r-sm); }
.op-item {
  display: flex; flex-direction: column; align-items: flex-start; gap: 1px; min-height: 44px;
  padding: 6px 10px; border: none; border-radius: 6px; background: none; text-align: left;
  cursor: pointer; font-size: 12.5px; font-weight: 600; color: var(--text-1); font-family: inherit;
}
.op-item:hover { background: var(--surface-3); }
.op-item.is-selected { color: var(--blue-text); }
.op-item .op-sub { font-size: 10.5px; font-weight: 400; color: var(--text-3); }
@media (pointer: coarse) {
  .op-arrow { position: relative; }
  .op-arrow::after {
    content: ""; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
    width: 100%; height: 100%; min-width: 44px; min-height: 44px;
  }
}

/* El badge de frescura dentro de un header uppercase no hereda el grito */
.badge-fresh { text-transform: none; letter-spacing: 0; }

/* Pedidos por temporada */
.recencia { padding: var(--sp-3) var(--sp-4) 0; font-size: var(--fs-body-lg); font-weight: 600; color: var(--text-1); }
.recencia b { font-weight: 700; }
.season { position: relative; }
.season + .season::before, .recencia + .season::before {
  content: ""; position: absolute; top: 0; left: var(--sp-4); right: 0;
  height: .5px; background: var(--hairline);
}
.recencia + .season { margin-top: var(--sp-2); }
/* `> summary` — mismo motivo que `.chev-rot` más abajo: sin el `>`, esta regla también le pegaba
   al `<summary>` del `.order-item` anidado (2026-08-28), duplicando el padding encima del que ya
   trae `.order-row`. */
.season > summary {
  list-style: none; cursor: pointer;
  padding: var(--sp-2) var(--sp-4); min-height: var(--touch);
  display: flex; flex-direction: column; justify-content: center; gap: 2px;
  -webkit-tap-highlight-color: transparent;
}
.season > summary::-webkit-details-marker { display: none; }
.season > summary:active { background: var(--surface-3); }
.season-hd { display: flex; align-items: baseline; gap: var(--sp-2); min-width: 0; }
.season-name { font-size: var(--fs-name); font-weight: 700; letter-spacing: -.01em; color: var(--text-1); }
.season-n { font-size: var(--fs-chip); color: var(--text-3); }
.season-total { margin-left: auto; font-size: 17px; font-weight: 700; letter-spacing: -.02em; color: var(--text-1); }
/* `> summary` (no descendiente suelto): 2026-08-28, desde que `orderAccordion` anida OTRO
   `<details class="order-item">` (con su propio `.chev-rot`) adentro de `.season`, un selector
   `.season .chev-rot` pegaba a los dos niveles — con la temporada abierta, el chevron de un
   pedido CERRADO se veía rotado igual, mintiendo sobre su propio estado. */
.season > summary .chev-rot { align-self: center; transition: transform var(--t-fast) var(--ease); }
.season[open] > summary .chev-rot { transform: rotate(90deg); }
.season-cmp { font-size: var(--fs-sm); font-weight: 600; }
.season-cmp.down { color: var(--red-text); }
.season-cmp.up { color: var(--green-text); }
.season-cmp.flat { color: var(--text-3); }
.order-row {
  display: flex; align-items: center; gap: var(--sp-2);
  min-height: 52px; padding: var(--sp-1) var(--sp-3) var(--sp-1) var(--sp-5); position: relative;
}
.order-row::before {
  content: ""; position: absolute; top: 0; left: var(--sp-5); right: 0;
  height: .5px; background: var(--hairline);
}
.order-row .order-main { min-width: 0; flex: 1; }
.order-row .doc { font-size: var(--fs-sm); font-weight: 600; color: var(--text-1); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.order-row .meta { font-size: var(--fs-chip); color: var(--text-3); margin-top: 1px; }
.order-row .amt { font-size: var(--fs-body); font-weight: 700; color: var(--text-1); flex-shrink: 0; }
/* Badge de entrega pendiente (delivery_status): segunda línea del pedido, no compite con el
   chip de estado documental que ya vive a la derecha de la fila. */
.order-delivery { margin-top: 3px; }
.odoo-link {
  display: inline-flex; align-items: center; justify-content: center;
  width: var(--touch); height: var(--touch); margin: -6px 0; flex-shrink: 0;
  border-radius: var(--r-sm); color: var(--blue-text);
  -webkit-tap-highlight-color: transparent;
}
.odoo-link:hover { background: var(--surface-2); }
.odoo-link:active { background: var(--surface-3); }

/* Acordeón de UN pedido (2026-08-28, `orderAccordion`/`loadOrderLines` en ficha-odoo.js): mismo
   look que `.order-row`, pero anidado dentro de un `<details class="order-item">` — abre in-situ
   con sus líneas y el botón de comprobante, nunca navega afuera (pedido de Fernando: "que me
   lleve al pedido y me lo muestre dentro del CRM"). Anida bien: `.season` ya es un `<details>` y
   los `<details>` anidados son válidos en HTML nativo. */
.order-item { position: relative; }
.order-item > summary {
  list-style: none; cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.order-item > summary::-webkit-details-marker { display: none; }
.order-item > summary:active { background: var(--surface-3); }
.order-item > summary .chev-rot { align-self: center; transition: transform var(--t-fast) var(--ease); flex-shrink: 0; }
.order-item[open] > summary .chev-rot { transform: rotate(90deg); }
/* Cuerpo del pedido: fondo levemente distinto para que se lea como "adentro" del pedido, no
   como otra fila más de la temporada — mismo criterio que `.nivel-disclosure-body` (indentación
   visual, no literal, para no perder ancho en 375px). */
.order-lines-wrap { background: var(--surface-2); border-top: .5px solid var(--hairline); }
.order-lines-wrap .prod-row { padding-left: calc(var(--sp-5) + var(--sp-2)); }
.order-lines-wrap .prod-row + .prod-row::before { left: calc(var(--sp-5) + var(--sp-2)); }
.order-lines-wrap .frow { padding-left: calc(var(--sp-5) + var(--sp-2)); } /* "Cargando líneas…" al mismo nivel */
/* Línea DADA DE BAJA (qty=0 en Odoo, §4 CLAUDE.md): NUNCA se ve como una línea normal en cero —
   texto y chip apagados (gris, no naranja/rojo: no es una alerta nueva, es un hecho ya cerrado).
   Si el ledger de cancelaciones tiene la cantidad original, se declara al lado; si no, "Dada de
   baja" solo — nunca se inventa un número que no está (ausencia ≠ cero). */
.line-baja .prod-name { color: var(--text-3); }
.line-baja-val { display: flex; flex-direction: column; align-items: flex-end; gap: 4px; flex-shrink: 0; }
.line-baja-orig { font-size: var(--fs-chip); color: var(--text-3); text-align: right; }
.line-val { display: flex; align-items: center; gap: var(--sp-2); flex-shrink: 0; }
.line-qty { font-size: var(--fs-chip); color: var(--text-3); }
.line-amt { font-size: var(--fs-sm); font-weight: 700; color: var(--text-1); }
.order-item-actions { padding: var(--sp-2) var(--sp-4) var(--sp-3) calc(var(--sp-5) + var(--sp-2)); }
.order-dl-btn { display: inline-flex; align-items: center; gap: 7px; }

/* Productos */
.seg-row { padding: var(--sp-3) var(--sp-4) var(--sp-2); }
.prod-row {
  display: flex; align-items: center; gap: var(--sp-3);
  min-height: 48px; padding: 6px var(--sp-4); position: relative;
}
.prod-row + .prod-row::before {
  content: ""; position: absolute; top: 0; left: var(--sp-4); right: 0;
  height: .5px; background: var(--hairline);
}
.prod-rank { width: 18px; text-align: right; font-size: var(--fs-sm); font-weight: 600; color: var(--text-3); flex-shrink: 0; }
.prod-main { min-width: 0; flex: 1; }
/* Nombre de producto: ENVUELVE a ancho completo por defecto, sin límite de líneas (regla
   §4.1 nº7 — cortar con ellipsis se ve prolijo y no dice nada, que es peor que verse
   apretado). Pulido de layout 2026-08-26: reemplaza el `nowrap` de origen, que en listas
   verticales angostas (acordeón de Cancelaciones, panel de detalle de 320px) dejaba visible
   solo el 25-33% del texto. Única excepción: `table.dense .prod-name` más abajo, donde una
   fila SÍ es un renglón y el `title=` ya lleva el nombre entero. */
.prod-name { font-size: var(--fs-sm); font-weight: 600; color: var(--text-1); overflow-wrap: anywhere; line-height: 1.3; }
.prod-code { font-size: var(--fs-chip); color: var(--text-3); margin-top: 1px; }
.prod-val { font-size: var(--fs-body); font-weight: 700; color: var(--text-1); flex-shrink: 0; }
/* "Compró una vez y no repuso": fecha de única compra + estado de stock, apilados a la derecha. */
.no-rep-val { display: flex; flex-direction: column; align-items: flex-end; gap: 4px; flex-shrink: 0; }
/* "No sabemos" NO es un chip (no es una afirmación) — texto liviano a propósito, para que
   nunca compita visualmente con "Hay stock"/"Sin stock", que sí son señales para actuar. */
.stock-unknown { font-size: var(--fs-chip); color: var(--text-3); }
.subhd {
  margin-top: var(--sp-2); padding: var(--sp-3) var(--sp-4) var(--sp-1);
  font-size: var(--fs-caption); font-weight: 700; letter-spacing: .08em;
  text-transform: uppercase; color: var(--text-3);
  border-top: .5px solid var(--hairline);
}
.joya-note { padding: var(--sp-2) var(--sp-4) var(--sp-3); font-size: var(--fs-caption); color: var(--text-3); }

/* Curva de talles (mini barras CSS; el dato está etiquetado directo) */
/* Curva de talles «arriba/abajo» (breaking change 2026-08-25 — reemplaza la curva por rubro) */
.curva-wrap { padding: var(--sp-2) var(--sp-4) var(--sp-3); }
/* Una sola columna por defecto (mobile-first) y DOS recién en escritorio.

   Medido el 2026-08-28 con harness-ficha.py a 375px: con las dos curvas lado a lado cada una
   quedaba en ~155px y el contenido real necesitaba 215px. El grupo desbordaba hasta los 501px
   dentro de una ventana de 375, y el `.group` que lo contiene tiene `overflow-x: hidden`: se
   recortaba el 25% de la curva SIN dejar forma de llegar a lo recortado -- el mismo defecto que
   la tabla de Cobranzas entre 900 y 1190px (corregido el 2026-08-26), repetido acá.

   Apilar es mejor que darle scroll horizontal: una curva de talles es un gráfico para comparar de
   un vistazo, y un gráfico que hay que arrastrar de costado no se compara. */
.curva-dos { display: grid; grid-template-columns: 1fr; gap: var(--sp-4); }
.curva-hd { display: flex; align-items: baseline; justify-content: space-between; gap: var(--sp-2); margin-bottom: 6px; }
.curva-label { font-size: var(--fs-sm); font-weight: 600; color: var(--text-1); }
/* Volumen de base SIEMPRE visible junto al título (regla de negocio "muestra chica se
   declara" — una curva de 3 unidades no es una curva, es ruido; declarar el volumen deja
   que quien lee lo juzgue, en vez de esconder el dato o fabricar un umbral arbitrario). */
.curva-vol { font-size: 10.5px; color: var(--text-3); }
.curva-bars { display: flex; gap: 6px; align-items: flex-end; }
.curva-otros { display: flex; flex-wrap: wrap; gap: 4px 10px; margin-top: var(--sp-2); font-size: 11px; color: var(--text-3); }
.curva-otros-lbl { font-weight: 600; }
.tbar { display: flex; flex-direction: column; align-items: center; gap: 3px; flex: 1; max-width: 56px; }
.tbar .tqty { font-size: var(--fs-chip); font-weight: 700; color: var(--text-1); }
.tbar .tcol { width: 100%; height: 44px; display: flex; align-items: flex-end; }
.tbar .tfill { width: 100%; background: var(--blue); border-radius: 5px 5px 2px 2px; min-height: 3px; }
.tbar .tlbl { font-size: var(--fs-chip); font-weight: 600; color: var(--text-3); }

/* Facturas (dispensador de documentos) */
.invx {
  display: flex; align-items: center; gap: var(--sp-2);
  min-height: 56px; padding: var(--sp-2) var(--sp-3) var(--sp-2) var(--sp-4); position: relative;
}
.invx + .invx::before {
  content: ""; position: absolute; top: 0; left: var(--sp-4); right: 0;
  height: .5px; background: var(--hairline);
}
.invx-main { min-width: 0; flex: 1; }
/* §4.1 #12 — un número de comprobante NO se corta con ellipsis. Medido a 375px el 2026-08-28:
   "NC-A 00008-00000850" necesitaba 155px en una caja de 144 y perdía los últimos dígitos, que son
   justamente los que lo identifican. Cortar un importe deja algo aproximado; cortar un número de
   factura deja algo FALSO — "NC-A 00008-000008…" es un comprobante que no existe, y se ve tan
   prolijo como el verdadero. Envuelve a dos renglones antes que mentir por un carácter. */
.invx .doc {
  font-size: var(--fs-sm); font-weight: 600; color: var(--text-1);
  white-space: normal; overflow-wrap: anywhere;
}
.invx .meta { font-size: var(--fs-chip); color: var(--text-3); margin-top: 2px; display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.invx .amt { font-size: var(--fs-sm); font-weight: 700; color: var(--text-1); text-align: right; flex-shrink: 0; }
.invx .amt .sub { display: block; font-size: 10px; font-weight: 500; color: var(--text-3); }
/* Nota de crédito: plata A FAVOR del cliente. Verde-texto (capa legible del sistema,
   nunca el tono vivo) para que jamás se confunda con deuda. */
.invx .amt.is-credit { color: var(--green-text); }
.ibtn {
  width: var(--touch); height: var(--touch); flex-shrink: 0;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: var(--r-sm); border: 1px solid var(--border);
  background: var(--surface-2); color: var(--blue-text); cursor: pointer;
  transition: background var(--t-fast) var(--ease);
  -webkit-tap-highlight-color: transparent;
}
.ibtn:hover { background: var(--surface-3); }
.ibtn:active { transform: scale(.95); }
.ibtn:disabled { color: var(--text-4); cursor: not-allowed; transform: none; }

/* Spinner chico para botones (descarga de PDF, sync) */
.spinner-sm {
  width: 15px; height: 15px; border-radius: 50%; flex-shrink: 0;
  border: 2px solid var(--border); border-top-color: var(--blue);
  animation: spin .8s linear infinite;
}
.sync-btn { width: 100%; gap: 8px; }

/* Focos rojos (mobile list) */
.focus-row {
  display: flex; align-items: center; gap: 10px;
  min-height: 48px; padding: 6px var(--sp-1); position: relative;
  text-decoration: none; color: inherit; border-radius: var(--r-sm);
  transition: background var(--t-fast) var(--ease);
}
.focus-row:hover { background: var(--surface-2); }
.focus-row .fdot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
.focus-row .fname {
  font-size: var(--fs-body); font-weight: 600; color: var(--text-1);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; flex: 1;
}
.focus-row .famt { font-size: var(--fs-body); font-weight: 700; color: var(--red-text); flex-shrink: 0; }
.focus-row .fdays { font-size: var(--fs-chip); color: var(--text-3); flex-shrink: 0; }
.sin-asignar-card { border-radius: var(--r); border: 1px solid var(--border); background: var(--surface); padding: var(--sp-3) var(--sp-4); font-size: var(--fs-sm); }
.sin-asignar-card .lb { font-size: var(--fs-chip); font-weight: 700; letter-spacing: .05em; text-transform: uppercase; color: var(--text-3); margin-bottom: 2px; }
.sin-asignar-card.is-bad { background: var(--orange-soft); border-color: var(--orange-border); color: var(--orange-text); }
.sin-asignar-card.is-ok { background: var(--green-soft); border-color: var(--green-border); color: var(--green-text); }

/* «Le quedamos debiendo» (M3, 2026-08-26, ficha-odoo.js `cancelacionesSection`): mercadería
   cancelada sin entregar. NO es rojo a propósito — el único rojo de la ficha es
   `.alerta-compuesta`. `--orange-text` (misma capa legible que `.rail-flag`), nunca `--red-text`. */
.cancel-summary { font-size: 12.5px; font-weight: 700; color: var(--orange-text); }
.cancel-row {
  display: flex; align-items: center; gap: var(--sp-3);
  min-height: 48px; padding: 6px var(--sp-4); position: relative;
}
.cancel-row + .cancel-row::before {
  content: ""; position: absolute; top: 0; left: var(--sp-4); right: 0;
  height: .5px; background: var(--hairline);
}
.cancel-row-meta { display: flex; align-items: center; gap: 6px; margin-top: 1px; flex-wrap: wrap; }
/* "Sin SKU" NO es un "—" (que se leería como un código): texto discreto a propósito, mismo
   tratamiento liviano que `.stock-unknown` en «Compró una vez y no repuso». */
.cancel-nosku { font-size: var(--fs-chip); color: var(--text-4); }
.cancel-qty { font-size: var(--fs-chip); color: var(--text-3); flex-shrink: 0; min-width: 34px; text-align: right; }
.cancel-amt { font-size: var(--fs-body); font-weight: 700; color: var(--orange-text); flex-shrink: 0; min-width: 64px; text-align: right; }
/* Columna derecha compacta (cantidad + importe apilados) — pulido de layout 2026-08-26.
   Reemplaza los DOS ítems sueltos que antes competían por ancho con el nombre en paneles
   angostos: medido en el split-pane de 320px, la fila mezclaba CUATRO columnas (nombre + meta
   + cantidad + importe) donde no entraban y el nombre recibía ~150px de los 320 disponibles.
   Con esto la fila queda en DOS: `.prod-main` (nombre + meta, flex:1) y esta, apilada a la
   derecha. Usan esta clase `productLine` (views/cancelaciones.js), `cancelLineRow`
   (components/ficha-odoo.js) y `clienteEsperaRow` (views/cancelaciones.js). */
.cancel-row-vals { display: flex; flex-direction: column; align-items: flex-end; gap: 2px; flex-shrink: 0; }

/* ============ Dataviz ============ */
.legend { display: grid; grid-template-columns: 1fr 1fr; gap: 6px 16px; margin-top: var(--sp-2); }
/* `flex-wrap` + label con `nowrap`: si la columna se queda corta, lo que baja de renglón es el
   MONTO —que se sigue entendiendo solo— y nunca el rango del bucket partido al medio. */
.lg-row { display: flex; align-items: center; flex-wrap: wrap; gap: 7px; font-size: var(--fs-caption); color: var(--text-2); }
.lg-row .lg-lb { white-space: nowrap; }
.lg-row .sw { width: 9px; height: 9px; border-radius: 3px; flex-shrink: 0; }
.lg-row .lg-val { margin-left: auto; display: inline-flex; align-items: baseline; gap: 7px; white-space: nowrap; }
.lg-row .amt { font-weight: 700; color: var(--text-1); }
.lg-row .pc { font-size: var(--fs-chip); color: var(--text-3); }
.chart-tooltip {
  position: fixed; z-index: var(--z-tooltip); pointer-events: none;
  background: var(--surface); border: 1px solid var(--border); border-radius: 10px;
  box-shadow: var(--sh-2); padding: 6px 10px; font-size: var(--fs-caption);
}
.chart-tooltip .tt-t { font-weight: 600; }
.chart-tooltip .tt-v { font-weight: 700; }

/* ============ Estados genéricos ============ */
.empty-state {
  display: flex; flex-direction: column; align-items: center; text-align: center;
  gap: var(--sp-2); padding: var(--sp-6) var(--sp-4);
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-lg);
  box-shadow: var(--sh-1); color: var(--text-2);
}
.empty-ico { color: var(--text-3); display: inline-flex; }
.empty-title { font-size: var(--fs-body-lg); font-weight: 600; color: var(--text-1); }
.empty-msg { font-size: var(--fs-sm); color: var(--text-2); max-width: 42ch; }
.empty-inline { background: none; border: none; box-shadow: none; padding: var(--sp-4) 0; }
.spinner {
  width: 22px; height: 22px; border-radius: 50%;
  border: 3px solid var(--border); border-top-color: var(--blue);
  animation: spin .8s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* Toasts */
#toast-root {
  position: fixed; left: 0; right: 0; bottom: calc(var(--nav-h) + var(--safe-b) + var(--sp-3));
  display: flex; flex-direction: column; align-items: center; gap: 8px;
  z-index: var(--z-toast); pointer-events: none; padding: 0 var(--sp-4);
}
.toast {
  max-width: var(--content-w); background: var(--text-1); color: var(--bg-app);
  padding: 10px 16px; border-radius: var(--r-pill); font-size: var(--fs-sm); box-shadow: var(--sh-2);
}
/* Tokens propios (tokens.css), no --green-text/--red-text: hallazgo 2.1,
   auditoría 2026-08-25 — esos son variantes de TEXTO, en oscuro son tonos
   claros y como fondo daban 1,8:1 y 2,8:1 (piso 4,5:1). */
.toast.ok { background: var(--toast-ok-bg); color: var(--toast-fg); }
.toast.bad { background: var(--toast-bad-bg); color: var(--toast-fg); }
/* T2 (2026-09-08): rechazo de la escritura a Odoo tras re-consultar ARCA — no es un fallo del
   organismo (eso es .bad) ni un éxito silencioso (.ok): 5,33:1 claro / 9,4:1 oscuro, medido. */
.toast.warn { background: var(--toast-warn-bg); color: var(--toast-fg); }

/* ============ Tab bar mobile ============ */
.bottom-nav {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: var(--z-bar);
  display: flex;
  background: var(--mat-tabbar);
  -webkit-backdrop-filter: var(--mat-blur); backdrop-filter: var(--mat-blur);
  box-shadow: 0 -0.5px 0 var(--hairline);
  padding: 8px 6px calc(6px + var(--safe-b));
}
body.chromeless #navSlot { display: none; }
.bn-item {
  flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px;
  min-height: 48px; text-decoration: none; color: var(--text-3);
  background: none; border: none; cursor: pointer; border-radius: var(--r-sm);
  -webkit-tap-highlight-color: transparent;
}
.bn-item.is-active { color: var(--blue); }
.bn-item.is-disabled { color: var(--text-4); cursor: not-allowed; }
.bn-icon { display: inline-flex; position: relative; }
.bn-label { font-size: var(--fs-micro); font-weight: 600; }
/* Badge de notificación (bandeja "Sin vendedor", 2026-08-29) — NO es un total de cartera como
   los demás contadores: es trabajo pendiente, por eso naranja y no gris. Oculto en 0 por JS
   (bottom-nav.js#refreshBadges); acá solo la forma. */
.bn-badge {
  position: absolute; top: -3px; right: -7px; min-width: 15px; height: 15px;
  padding: 0 3px; border-radius: var(--r-pill);
  background: var(--orange); color: #fff; font-size: 9.5px; font-weight: 700;
  display: flex; align-items: center; justify-content: center;
  box-shadow: 0 0 0 2px var(--mat-tabbar);
}
/* Modificador azul (2026-09-02, badge de Agenda — "tus propias tareas", pedido textual de
   Fernando: "un 5 en un circulito azul"). `--blue-solid`, no `--blue`: es el token que pasa
   contraste con texto blanco encima en LOS DOS temas (`--blue` de oscuro da 3,02:1 y falla). El
   naranja de `.side-badge`/`.bn-badge` sin modificador queda intacto para "Datos". */
.side-badge.blue, .bn-badge.blue { background: var(--blue-solid); }

/* ============ Sidebar desktop ============ */
.sidebar {
  display: none;
  position: fixed; top: 0; bottom: 0; left: 0; width: var(--sidebar-w);
  background: var(--side-bg); box-shadow: .5px 0 0 var(--hairline);
  z-index: var(--z-bar);
  flex-direction: column;
}
.side-head {
  display: flex; align-items: flex-end; gap: 8px;
  padding: 20px 22px 10px;
}
.side-head-sub {
  font-size: var(--fs-chip); font-weight: 700; letter-spacing: .08em;
  text-transform: uppercase; color: var(--text-3); padding-bottom: 2px;
}
.side-nav { display: flex; flex-direction: column; gap: 2px; padding: 0 12px; flex: 1; }
.side-item {
  display: flex; align-items: center; gap: 10px;
  min-height: 34px; padding: 0 10px; border-radius: 8px;
  font-size: 13.5px; font-weight: 500; color: var(--text-1); text-decoration: none;
  border: none; background: none; cursor: pointer; text-align: left;
}
.side-item .ico { display: inline-flex; color: var(--text-2); }
.side-item:hover { background: var(--gray-soft); }
.side-item.on { background: var(--blue-soft); color: var(--blue-text); font-weight: 600; }
.side-item.on .ico { color: var(--blue-text); }
.side-item.is-disabled { color: var(--text-4); cursor: not-allowed; }
.side-item.is-disabled .ico { color: var(--text-4); }
.side-item.is-disabled:hover { background: none; }
.side-count { margin-left: auto; font-size: 11.5px; font-weight: 600; color: var(--text-3); }
.side-item.on .side-count { color: var(--blue-text); }
/* Badge de notificación (bandeja "Sin vendedor") — mismo criterio que `.bn-badge`: trabajo
   pendiente, no un total de cartera, por eso cápsula llena en vez de número gris al costado. */
.side-badge {
  margin-left: auto; min-width: 18px; height: 18px; padding: 0 6px; border-radius: var(--r-pill);
  background: var(--orange); color: #fff; font-size: 11px; font-weight: 700;
  display: flex; align-items: center; justify-content: center;
}
.side-foot {
  display: flex; align-items: center; gap: 10px;
  padding: 12px 18px calc(12px + var(--safe-b));
  box-shadow: 0 -0.5px 0 var(--hairline);
  text-decoration: none; color: inherit;
}
.side-foot .who { font-size: 12.5px; font-weight: 600; color: var(--text-1); }
.side-foot .role { font-size: var(--fs-chip); color: var(--text-3); }

/* ============ Más ============ */
.account-card {
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-lg);
  box-shadow: var(--sh-1); padding: var(--sp-4);
  display: flex; align-items: center; gap: var(--sp-3);
}
.account-name { font-weight: 700; font-size: var(--fs-name); }
.account-rol { color: var(--text-3); font-size: var(--fs-sm); }
.app-version { text-align: center; color: var(--text-4); font-size: var(--fs-chip); margin-top: var(--sp-4); }

/* ============================================================
   M3 — Cobranzas: vista "Nos deben / Les debemos" (docs/design/
   direcciones/cobranzas-a.html, elegida por Fernando 2026-08-25).
   Extiende clases que YA EXISTEN (.tile, .today-card, .chip.mini,
   .bcard, table.dense, .seg, .sin-asignar-card) — acá solo lo que
   es genuinamente nuevo: el toggle de vista, la línea de
   concentración, la fila de "tipo de gestión" y los matices de
   cierre administrativo / saldo a favor.
   ============================================================ */

/* Toggle "Nos deben / Les debemos" — mismo lenguaje que .seg pero
   dos botones anchos con su conteo debajo (elección de vista, no
   filtro rápido). */
.vs-wrap { display: flex; background: var(--gray-soft); border-radius: 10px; padding: 2px; gap: 0; }
.vs-btn {
  flex: 1; border: none; background: none; cursor: pointer; font-family: var(--font);
  font-size: var(--fs-caption); font-weight: 600; color: var(--text-2);
  padding: 6px 10px; border-radius: 8px; min-height: 40px; line-height: 1.3; text-align: center;
  transition: background var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
}
.vs-btn.on { background: var(--surface); color: var(--text-1); box-shadow: var(--sh-seg); font-weight: 700; }
.vs-cnt { display: block; font-size: 10.5px; font-weight: 600; opacity: .75; margin-top: 1px; }

/* Línea de concentración — banner rojo con barra + link ("Ver cuáles").
   .alert-banner.bad es la variante roja del banner genérico (el .warn
   ya existía para el aviso de frescura de datos). */
.alert-banner.bad { background: var(--red-soft); border: 1px solid var(--red-border); color: var(--red-text); }
.conc-banner { align-items: center; }
.conc-bar { flex: 0 0 46px; height: 5px; border-radius: 3px; background: var(--surface-2); overflow: hidden; }
.conc-bar-fill { height: 100%; background: var(--red); border-radius: 3px; }
.conc-txt { flex: 1; min-width: 0; font-size: var(--fs-sm); font-weight: 600; color: var(--text-1); }
.conc-txt b { color: var(--red-text); font-weight: 800; }
.link-btn { flex-shrink: 0; font-size: var(--fs-chip); font-weight: 600; color: var(--blue-text); background: none; border: none; padding: 0; cursor: pointer; font-family: var(--font); }
.link-btn:hover { text-decoration: underline; }

/* Fila "tipo de gestión" bajo las acciones de la tarjeta (mobile: solo
   lectura, D3 — "un vendedor no debería ni ver un tipo que no es suya"
   ya lo filtra el backend; acá solo se muestra, nunca se edita). */
.tipo-row { display: flex; align-items: center; gap: 6px; margin-top: var(--sp-3); padding-top: var(--sp-2); border-top: .5px solid var(--hairline); }
.tipo-src { font-size: var(--fs-chip); color: var(--text-3); flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tipo-src.manual { color: var(--text-2); font-weight: 500; }

/* Cierre administrativo — atenuado, nunca oculto (CLAUDE.md §0:
   "clasificar ≠ filtrar"). */
.today-card.is-adm { opacity: .7; background: var(--surface-2); border-style: dashed; }
.today-card.is-adm .tc-name { color: var(--text-2); }
table.dense tr.row-adm td { opacity: .7; }
table.dense tr.row-adm-hd td {
  padding: 12px; background: var(--surface-2); border-top: 1px solid var(--border);
  color: var(--text-2); font-size: var(--fs-sm); font-weight: 400;
}
.age-dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; margin-right: 6px; vertical-align: middle; flex-shrink: 0; }

/* Saldo a favor: matices propios sobre .tile y table.dense ya existentes. */
.tile.favor .v { color: var(--green-text) !important; }
.tile.warn-orange .v { color: var(--orange-text) !important; }
table.dense .amt-warn { font-weight: 700; color: var(--orange-text); }
table.dense tr.is-warn { background: rgba(255, 149, 0, .04); }
table.dense tr.is-warn:hover { background: rgba(255, 149, 0, .10); }

/* Cancelaciones ("mercadería que pedimos y no entregamos", CLAUDE.md §4 — vive SOLO en el
   CRM) en Cobranzas: el badge pasa de SÍ/NO a CUÁNTO (2026-08-26). SIEMPRE neutro/gris — no es
   una alerta nueva, ya hay una roja arriba (concentración) y la regla #4 pide una sola por
   bloque. Vocabulario del mockup docs/design/direcciones/cancelaciones-*-a.html
   (.cob-sec-badge/.cob-sec-row), renombrado `.cancel-*` porque acá vive en dos pantallas (mora
   Y saldo a favor), no solo en Cancelaciones. */
.cancel-badge {
  display: inline-flex; align-items: center; gap: 4px;
  background: var(--gray-soft); color: var(--text-2); border: 1px solid var(--border);
  font-size: var(--fs-chip); font-weight: 600; border-radius: 6px; padding: 2px 7px; white-space: nowrap;
}
.cancel-badge .ico { display: inline-flex; }
/* "Nos deben" — contraargumento: un pelín más oscuro que la magnitud neutra, porque acá es el
   motivo para frenar el reclamo, no un dato de paso. */
.cancel-badge.cancel-badge-contra { color: var(--text-1); border-color: var(--border-strong); }
.amt-cancel-wrap { display: flex; flex-direction: column; align-items: flex-end; gap: 3px; }
/* Fila de cierre administrativo: más angosta que la principal (opacidad .7, misma columna
   compartida) — el badge se envuelve a dos líneas en vez de desbordar la celda. No se esconde
   nunca (§4.1 regla 7): una fila que oculta $2M por falta de espacio es plata escondida. */
.amt-cancel-wrap .cancel-badge { white-space: normal; text-align: right; }
.cancel-line {
  /* Sin margin propio: la separación la pone el `.stack` del panel — un margen acá se SUMA al
     gap del sistema y esa costura queda distinta de las demás (24 vs 16, medido 2026-08-30). */
  display: flex; align-items: center; gap: 8px;
  background: var(--surface-2); border: 1px solid var(--border); border-radius: 10px;
  padding: 9px 14px; font-size: var(--fs-sm); color: var(--text-2); line-height: 1.4;
}
.cancel-line .ico { color: var(--text-3); flex-shrink: 0; display: inline-flex; }
.cancel-line p { margin: 0; }
.cancel-line b { color: var(--text-1); font-weight: 700; }

/* ============================================================
   M2 — Capa de acción: Anotar (sheet), botón + flotante,
   timeline, tareas, briefing IA, "¿Anotás cómo fue?".
   ============================================================ */

/* Botón + flotante */
.fab {
  position: fixed; right: var(--sp-4); bottom: calc(var(--nav-h) + var(--safe-b) + 14px);
  width: 52px; height: 52px; border-radius: 50%;
  background: var(--blue-solid); color: #fff; border: none; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  box-shadow: var(--sh-3); z-index: var(--z-bar);
  transition: transform var(--t-fast) var(--ease), filter var(--t-fast) var(--ease);
  -webkit-tap-highlight-color: transparent;
}
.fab:active { transform: scale(.93); filter: brightness(.92); }

/* Sheet "Anotar" (mobile: bottom sheet · desktop: modal centrado) */
body.sheet-open { overflow: hidden; }
.sheet-backdrop {
  position: fixed; inset: 0; z-index: var(--z-modal);
  background: rgba(0, 0, 0, .34);
  display: flex; align-items: flex-end; justify-content: center;
  animation: bfade .18s var(--ease) both;
}
.sheet-backdrop.closing { opacity: 0; transition: opacity .18s var(--ease); }
@keyframes bfade { from { opacity: 0; } to { opacity: 1; } }
.sheet {
  width: 100%; max-width: 560px; max-height: calc(100dvh - 40px);
  background: var(--bg-app); border-radius: var(--r-xl) var(--r-xl) 0 0;
  box-shadow: var(--sh-3); overflow-y: auto;
  padding: var(--sp-2) var(--sp-4) calc(var(--sp-3) + var(--safe-b));
  animation: sheetup .28s var(--ease-out) both;
}
@keyframes sheetup { from { transform: translateY(48px); opacity: .4; } to { transform: none; opacity: 1; } }
.sheet-hd { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--sp-3); margin-bottom: 2px; }
.sheet-kicker {
  font-size: var(--fs-chip); font-weight: 700; letter-spacing: .08em;
  text-transform: uppercase; color: var(--text-3);
}
.sheet-client {
  font-size: 20px; font-weight: 800; letter-spacing: -.02em; color: var(--text-1);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.sheet-x {
  width: 32px; height: 32px; border-radius: 50%; flex-shrink: 0;
  border: none; background: var(--gray-soft); color: var(--text-2); cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  -webkit-tap-highlight-color: transparent;
}
.sheet-x:active { background: var(--surface-3); }
.sheet-body { display: flex; flex-direction: column; gap: 6px; }
.flabel {
  display: flex; align-items: baseline; gap: 8px; margin-top: 6px;
  font-size: var(--fs-caption); font-weight: 700; letter-spacing: .05em;
  text-transform: uppercase; color: var(--text-3);
}
.fhint { font-size: var(--fs-chip); font-weight: 500; letter-spacing: 0; text-transform: none; color: var(--text-3); }

/* Chips de opción (canal / resultado / fechas rápidas). Hasta el 2026-08-31 era un carrusel
   horizontal (overflow-x:auto + scrollbar oculta): escondía opciones fuera de pantalla y se veía
   prolijo haciéndolo — exactamente lo que CLAUDE.md §4.1 #12 condena. Ahora envuelve: todas las
   opciones entran sin scroll horizontal. Definición COMÚN — consumidores verificados uno por uno:
   anotar-sheet.js (canalRow 6 chips, pasoRow 4) y timeline.js/.task-row .opt-row (postpone-row, 5
   chips, usado también desde client-card.js en Hoy). Ninguno necesita el scroll: son 3-10 chips
   cortos, entran en 2-3 líneas en 320px de ancho. */
.opt-row {
  display: flex; flex-wrap: wrap; gap: 6px; padding-bottom: 2px;
}
.opt-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 6px; }
.opt {
  min-height: 40px; padding: 0 12px; border-radius: 11px;
  border: 1px solid var(--border); background: var(--surface);
  font-size: var(--fs-sm); font-weight: 600; color: var(--text-1);
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  cursor: pointer; white-space: nowrap; flex-shrink: 0;
  transition: background var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
  -webkit-tap-highlight-color: transparent;
}
.opt:active { transform: scale(.97); }
.opt.on { background: var(--blue-soft); border-color: var(--blue-border); color: var(--blue-text); }
/* Confirmación de borrado: el "Sí" en rojo suave, con TEXTO que dice qué hace — el color solo
   nunca alcanza (§4, un canal un significado). El "Dejar" queda neutro, como en Posponer.
   ⚠️ VA ACÁ, junto a `.opt.on`, y no en el bloque de `.tc-actions`: `.opt-danger` y `.opt` tienen
   la MISMA especificidad (una clase), así que lo único que decide es el orden. Declarado antes de
   `.opt` —como estaba— el color se lo comía `.opt` y el botón "Sí, eliminar" salía BLANCO,
   indistinguible del "Dejar". Se vio midiendo el computed style en pantalla; el test de "existe la
   clase en app.css" pasaba igual. */
.opt-danger { background: var(--red-soft); border-color: var(--red-border); color: var(--red-text); }
/* Target táctil ≥44px sin agrandar el visual (mismo truco que a.avatar) */
.opt, .sheet-x { position: relative; }
.opt::after { content: ""; position: absolute; inset: -3px; }
.sheet-x::after { content: ""; position: absolute; inset: -7px; border-radius: 50%; }
.opt-cal { padding: 0 8px 0 10px; }
/* `input[type="time"]` (2026-09-01, panel de Reprogramar): mismo tratamiento visual que el de
   fecha — es el mismo control `.opt-cal` (ícono + input desnudo), solo cambia qué mide. */
.opt-cal input[type="date"], .opt-cal input.hora-txt {
  border: none; background: transparent; min-height: 0; width: 118px;
  padding: 0; font-size: var(--fs-sm); color: var(--text-2);
}
/* La hora es texto libre normalizado (2026-09-04, ver UI.normalizarHora): más angosta que la
   fecha, y en rojo mientras lo escrito no se entienda. */
.opt-cal input.hora-txt { width: 64px; }
.opt-cal input.hora-txt.is-invalid { color: var(--red); }
.opt-cal input[type="date"]:focus-visible, .opt-cal input.hora-txt:focus-visible { box-shadow: none; }
.opt-cal:focus-within { box-shadow: var(--sh-focus); }

/* Panel de Reprogramar (2026-09-01, ex Posponer, `components/timeline.js`): 3 filas (fecha ·
   hora · Dejar), cada una un `.opt-row` — el `gap` acá separa las FILAS, `.opt-row` ya separa
   los chips DENTRO de cada una. `.postpone-row` (reusada, no una clase nueva de margen) le da al
   panel entero el mismo margen superior que tenía `group` antes de este cambio. */
.reprog-panel { display: flex; flex-direction: column; gap: var(--sp-2); }
/* Panel post-Hecho (2026-09-01, "¿Qué pasó?" + "¿Y ahora?"): mismo criterio — filas en columna,
   cada una con su `.flabel` propio; el `gap` alcanza para separar título+fila+título+fila, no
   hace falta un margen extra por fila (evita doble espaciado). */
.posthecho-panel { display: flex; flex-direction: column; gap: var(--sp-1); }
/* Texto de estado del panel post-Hecho ("Cargando…", "Anotado: Cobranza.") — ni chip ni botón,
   solo confirma qué pasó con el intento. */
.opt-done-note { font-size: var(--fs-sm); color: var(--text-2); padding: 8px 2px; }

/* "Va a pagar" → fecha + monto (la promesa) */
.cond-box {
  background: var(--green-soft); border: 1px dashed var(--green-border); border-radius: var(--r);
  padding: var(--sp-2) var(--sp-3) var(--sp-3); margin-top: 4px;
  display: flex; flex-direction: column; gap: 6px;
}
.cond-box .flabel { margin-top: 0; color: var(--green-text); }
.cond-box .fhint { color: var(--green-text); opacity: .85; }
.cond-row { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }

/* "Va a comprar" (2026-09-01, contrato-motivo-desenlace.md §3) → mismo layout que .cond-box de
   arriba (fecha/promesa), pero en AZUL: acá no se está prometiendo dinero, es una señal
   comercial. El verde de .cond-box queda reservado para lo que sí es plata — mismo criterio que
   ya documenta ui.js sobre RESULTADO_ACT ("verde reservado para plata"). Se agrega una clase
   nueva en vez de reusar .cond-box con un modificador porque el color no es un detalle: es la
   regla de negocio que separa "va a pagar" de "va a comprar" a simple vista
   (components/anotar-sheet.js, compraBox). */
.cond-box-blue {
  background: var(--blue-soft); border: 1px dashed var(--blue-border); border-radius: var(--r);
  padding: var(--sp-2) var(--sp-3) var(--sp-3); margin-top: 4px;
  display: flex; flex-direction: column; gap: 6px;
}
.cond-box-blue .flabel { margin-top: 0; color: var(--blue-text); }
.cond-box-blue .fhint { color: var(--blue-text); opacity: .85; }

textarea.nota {
  width: 100%; min-height: 46px; resize: none;
  border: 1px solid var(--border-strong); border-radius: var(--r-sm);
  background: var(--surface); color: var(--text-1);
  padding: 10px 12px; font-size: var(--fs-body-lg); line-height: 1.4;
}
textarea.nota:focus-visible { border-color: var(--blue); box-shadow: var(--sh-focus); outline: none; }
/* La nota de la hoja Actividad (2026-09-02): mínimo 4 renglones, crece con el texto (JS). */
textarea.nota.nota-grande { min-height: 96px; }

/* ============================================================
   Cierre de tarea / Agendar (2026-09-02, docs/contrato-cierre-tarea-actividad.md)
   `components/anotar-sheet.js` (modos cierre/edición) + `components/agendar-sheet.js` (nuevo).
   Reusa `.sheet*`/`.opt*`/`.chip`/`.toggle-chip`/`.cond-box*`/`.nota` del resto del sistema —
   acá solo lo exclusivo de estas dos hojas.
   ============================================================ */
/* Kicker "Cerrando: «título»" — el nombre de la tarea se destaca del resto del texto gris
   (mismo azul de texto que usa el resto de la app para lo interactivo/relevante). */
.sheet-kicker strong { color: var(--blue-text); font-weight: 600; }

/* "Agendar el siguiente paso" — CTA secundaria, un escalón por debajo del "Guardar" azul sólido
   (mismo criterio que el resto del sistema: sólido = la acción principal, soft = la de al lado).
   `.btn-danger` (ya existe en este archivo) cubre "Solo marcar hecha, sin registrar". */
.btn-agendar {
  display: flex; align-items: center; justify-content: center; gap: 7px;
  background: var(--blue-soft); color: var(--blue-text); border: 1px solid var(--blue-border);
}
.btn-agendar:hover { background: var(--blue-border); }

/* Resumen de una línea del próximo paso ya agendado (contrato §4: "muestra un resumen … con un
   botón para cambiar/quitar"). El texto es un botón (toca = reabre la hoja Agendar precargada,
   "cambiar"); el "×" al lado borra sin reabrir nada. */
.paso-resumen {
  display: flex; align-items: center; gap: var(--sp-2);
  min-height: var(--touch); padding: 0 var(--sp-3);
  border: 1px solid var(--blue-border); border-radius: var(--r); background: var(--blue-soft);
}
.paso-resumen-txt {
  flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  text-align: left; border: none; background: none; color: var(--blue-text);
  font-family: var(--font); font-size: var(--fs-sm); font-weight: 600; cursor: pointer; padding: 10px 0;
}
.paso-resumen-x {
  flex-shrink: 0; width: 28px; height: 28px; border-radius: 50%; border: none;
  background: none; color: var(--text-3); display: flex; align-items: center; justify-content: center;
  cursor: pointer;
}
.paso-resumen-x:hover { background: var(--surface-3); color: var(--text-1); }

/* "Detalle" de la hoja Agendar — extracto recortado con ellipsis, SIEMPRE con `title` con el
   texto completo (CLAUDE.md §4.1 nº12: un texto cortado sin tooltip queda perdido). Toca = reabre
   `components/texto-sheet.js` sobre el mismo valor. */
.agendar-detalle-wrap { min-height: var(--touch); display: flex; align-items: center; }
.agendar-detalle-preview {
  width: 100%; min-height: var(--touch); padding: 10px 12px; text-align: left;
  border: 1px solid var(--border-strong); border-radius: var(--r-sm); background: var(--surface);
  color: var(--text-1); font-family: var(--font); font-size: var(--fs-body); line-height: 1.4;
  cursor: pointer; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* Buscador de cliente (botón +) */
.picker-list {
  margin-top: var(--sp-2); min-height: 60px;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-lg);
  overflow: hidden; display: flex; flex-direction: column;
}
.picker-row {
  display: flex; align-items: center; gap: var(--sp-3);
  min-height: 52px; padding: 8px var(--sp-4); position: relative;
  border: none; background: none; cursor: pointer; text-align: left; color: inherit;
  -webkit-tap-highlight-color: transparent;
}
.picker-row + .picker-row::before {
  content: ""; position: absolute; top: 0; left: 68px; right: 0;
  height: .5px; background: var(--hairline);
}
.picker-row:active { background: var(--surface-3); }
.picker-name {
  flex: 1; min-width: 0; font-size: var(--fs-body-lg); font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.picker-empty { padding: var(--sp-4); font-size: var(--fs-sm); color: var(--text-3); text-align: center; }

/* Mensaje de cobranza (M3 Fase 4) — components/mensaje-sheet.js. Reusa .sheet/.flabel/.nota del
   sistema (mismas clases que "Anotar"); solo lo propio de esta hoja va acá. */
.msg-sug-list { display: flex; flex-direction: column; gap: 6px; }
.msg-sug {
  display: block; width: 100%; text-align: left; padding: 9px 12px;
  border-radius: var(--r-sm); border: 1px solid var(--border); background: var(--surface);
  cursor: pointer; -webkit-tap-highlight-color: transparent;
  transition: background var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease);
}
.msg-sug:active { transform: scale(.99); }
.msg-sug.on { background: var(--blue-soft); border-color: var(--blue-border); }
.msg-sug-label { display: block; font-size: var(--fs-sm); font-weight: 700; color: var(--text-1); }
.msg-sug-preview {
  display: block; margin-top: 2px; font-size: var(--fs-chip); font-weight: 400; color: var(--text-3);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
textarea.msg-textarea { min-height: 108px; }
.msg-ia-row { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; margin-top: 2px; }
.msg-ia-note { font-size: var(--fs-chip); color: var(--text-3); }
.msg-fixed-note {
  font-size: var(--fs-sm); color: var(--text-2); background: var(--gray-soft);
  border-radius: var(--r-sm); padding: 10px 12px; margin-top: 2px;
}
.msg-actions { margin-top: var(--sp-2); }
.btn-wa.is-disabled { opacity: .5; pointer-events: none; }

/* Volver de wa.me → "¿Anotás cómo fue?" */
.wa-return {
  position: fixed; left: var(--sp-3); right: var(--sp-3);
  bottom: calc(var(--nav-h) + var(--safe-b) + var(--sp-3));
  z-index: var(--z-toast);
  display: flex; align-items: center; gap: 10px;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-lg);
  box-shadow: var(--sh-3); padding: 10px 12px;
  animation: rise .4s var(--ease-out) both;
}
.wa-return-ico {
  width: 30px; height: 30px; border-radius: 10px; flex-shrink: 0;
  background: var(--green-soft); color: var(--green-text);
  display: inline-flex; align-items: center; justify-content: center;
}
.wa-return-txt { flex: 1; min-width: 0; font-size: var(--fs-sm); color: var(--text-2); }
.wa-return-txt b { color: var(--text-1); font-weight: 600; }
.wa-return .sheet-x { width: 28px; height: 28px; }

/* Timeline de la ficha */
.tl-group { padding-bottom: var(--sp-2); }
.tl-row { display: flex; gap: var(--sp-3); min-height: 52px; padding: var(--sp-2) var(--sp-4); position: relative; }
.tl-row + .tl-row::before {
  content: ""; position: absolute; top: 0; left: 60px; right: 0;
  height: .5px; background: var(--hairline);
}
.tl-disc {
  width: 28px; height: 28px; border-radius: 9px; flex-shrink: 0; margin-top: 2px;
  background: var(--gray-soft); color: var(--text-2);
  display: inline-flex; align-items: center; justify-content: center;
}
.tl-main { min-width: 0; flex: 1; }
.tl-hd { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.tl-title { font-size: var(--fs-sm); font-weight: 600; color: var(--text-1); }
.tl-body { font-size: var(--fs-sm); color: var(--text-2); margin-top: 2px; overflow-wrap: anywhere; }
.tl-meta { font-size: var(--fs-chip); color: var(--text-3); margin-top: 2px; }
/* Tarea dentro de "Actividad" (2026-09-01): "para X · vence el Y" no puede quedar microscópico
   como el resto de tl-meta (autor+hora de un contacto, monto+fecha de una promesa — esos NO
   cambian). Sube un escalón de tamaño/peso/color, sin llegar al `.task-assignee` dedicado de
   abajo — acá conviven en una lista cronológica con otras filas, no puede gritar más que ellas. */
.tl-meta-task { font-size: var(--fs-sm); font-weight: 600; color: var(--text-2); }
/* Nodos extra al final de una fila del timeline (2026-09-02, `entry()`'s `extraNodes` — el botón
   "Editar" de una actividad, "nació de la actividad del…" de una tarea). Fila propia, chica, para
   no competir con `.tl-meta`/`.tl-body` que ya están arriba. */
.tl-actions { display: flex; gap: var(--sp-3); margin-top: 4px; }
.tl-empty {
  padding: var(--sp-3) var(--sp-4) var(--sp-4);
  display: flex; flex-direction: column; gap: var(--sp-2); align-items: flex-start;
  font-size: var(--fs-sm); color: var(--text-2);
}

/* Tareas (ficha) */
.task-row { padding: var(--sp-2) var(--sp-4) var(--sp-3); position: relative; }
.task-row + .task-row::before {
  content: ""; position: absolute; top: 0; left: var(--sp-4); right: 0;
  height: .5px; background: var(--hairline);
}
.task-title { font-size: var(--fs-body-lg); font-weight: 600; color: var(--text-1); }
/* "A quién y cuándo" es información OPERATIVA de una tarea, no metadato (2026-09-01, pedido de
   Fernando). Línea propia entre el título y el chip de estado: fs-body/600/text-1 — un escalón
   abajo del título (fs-body-lg), bien arriba del chip (fs-chip/text-3). Nunca rojo acá aunque la
   tarea esté vencida: ese aviso ya lo da el chip de `.task-meta` (§4.1 nº4, una sola alerta roja
   por bloque — no se duplica). Ellipsis+title (§4.1 nº12): un nombre largo no puede romper la fila.
   ⚠️ El NOMBRE va adentro de un chip azul desde el 2026-09-01 (segundo pedido de Fernando el
   mismo día: "encapsulado en algo color azul que contraste mas tal vez, mas visible"). Sigue sin
   ser una alerta —azul es identidad, no severidad—, así que la regla de arriba no cambia.
   El `<p>` se queda INLINE a propósito: el ellipsis del padre corta el conjunto (texto + chip)
   como venía haciéndolo. Pasarlo a flex habría convertido los textos sueltos en anonymous flex
   items y el ellipsis dejaba de aplicarles. */
.task-assignee {
  font-size: var(--fs-body); font-weight: 600; color: var(--text-1); margin-top: 3px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* A --fs-chip (11px) el chip quedaba microscópico al lado de un título de 15px — justo lo que
   Fernando pidió corregir horas antes ("debería aparecer mucho más destacado").
   Medido a 375px (el peor ancho) el 2026-09-01: el nombre más largo de los 10 usuarios reales
   —"Gustavo Cornachoni", 18 caracteres— rinde 163px sobre 309 disponibles, o sea sobra el 47% y
   la línea NO se corta. Por eso el chip no lleva max-width propio: haría falta un nombre de ~35
   caracteres para desbordar, y ahí el ellipsis del padre + el `title` ya cubren. ⚠️ Si algún día
   un nombre corta, lo que se pierde es la FECHA de vencimiento (el ellipsis come por el final),
   que es justo lo otro que Fernando pidió destacar: el arreglo entonces es que ceda el chip, no
   la fecha. */
.task-assignee .chip { font-size: var(--fs-sm); vertical-align: baseline; }
.task-meta { display: flex; align-items: center; gap: 6px; font-size: var(--fs-chip); color: var(--text-3); margin-top: 4px; }
/* Descripción de la tarea (2026-09-02, `tasks.descripcion`) — se persiste desde el día 1 y hasta
   acá no tenía dónde mostrarse (CLAUDE.md §4: "una capacidad sin control es una capacidad que no
   existe"). `overflow-wrap: anywhere`: puede ser larga, nunca se corta con ellipsis — el texto
   completo es el dato, no un resumen. */
.task-desc { font-size: var(--fs-sm); color: var(--text-2); margin-top: 3px; overflow-wrap: anywhere; }
/* "Nació de la actividad del…" (2026-09-02, `tasks.origen_activity_id`) — texto chico, discreto:
   es procedencia, no un dato operativo como `.task-assignee`. */
.task-origen { font-size: var(--fs-chip); color: var(--text-3); margin-top: 2px; font-style: italic; }
.task-row .tc-actions, .task-row .opt-row { margin-top: var(--sp-2); }
.postpone-row { margin-top: var(--sp-3); }
.fade-out { opacity: 0; transform: translateY(4px); transition: opacity .25s var(--ease), transform .25s var(--ease); }
.group-action {
  border: none; background: none; cursor: pointer; padding: 4px 0;
  font-size: var(--fs-sm); font-weight: 600; color: var(--blue-text);
  text-transform: none; letter-spacing: 0;
}

/* Notas de la cuenta (components/notas-cuenta.js) — conocimiento durable, distinto del timeline
   de interacciones de arriba. §4.1 nº12: prosa libre, SIN ellipsis ni line-clamp — ancho completo
   y los renglones que necesite (`white-space: pre-wrap` conserva los saltos de línea del autor). */
.note-list { display: flex; flex-direction: column; }
.note-row { padding: var(--sp-3) var(--sp-4); position: relative; display: flex; flex-direction: column; gap: 6px; }
.note-row + .note-row::before {
  content: ""; position: absolute; top: 0; left: var(--sp-4); right: var(--sp-4);
  height: .5px; background: var(--hairline);
}
.note-body { font-size: var(--fs-body-lg); color: var(--text-1); line-height: 1.45; white-space: pre-wrap; overflow-wrap: anywhere; }
.note-meta { display: flex; align-items: center; gap: 5px; font-size: var(--fs-chip); color: var(--text-3); }
.note-meta .note-pin { display: inline-flex; color: var(--blue-text); }
.note-actions { display: flex; align-items: center; gap: var(--sp-3); margin-top: 2px; flex-wrap: wrap; }
.note-act {
  border: none; background: none; cursor: pointer; padding: 4px 0;
  font-size: var(--fs-chip); font-weight: 600; color: var(--blue-text);
  display: inline-flex; align-items: center; gap: 4px;
}
.note-act .ico { display: inline-flex; }
.note-act-danger { color: var(--red-text); }
/* Blanco táctil de las acciones de nota/problema (2026-09-01, medido en la pasada visual de
   #/problemas: 24px reales contra los 44 que pide la regla de la casa). Es un defecto
   PREEXISTENTE —nació con las notas de la ficha el 28/8— y el fix va acá, en la definición
   común, no en `problemas.css`: son los mismos botones en las dos pantallas, y un parche por
   vista lleva escrita su fecha de vencimiento (§4.1 nº13). Por pseudo-elemento y no por padding:
   agrandar la caja real descolocaría la fila. */
@media (pointer: coarse) {
  .note-act { position: relative; }
  .note-act::after { content: ""; position: absolute; inset: -10px -4px; }
}
.note-confirm { display: flex; align-items: center; gap: var(--sp-2) var(--sp-3); flex-wrap: wrap; }
.note-confirm-txt { font-size: var(--fs-sm); color: var(--text-2); flex: 1 1 200px; min-width: 160px; }
.note-empty { padding: var(--sp-3) var(--sp-4) var(--sp-4); display: flex; flex-direction: column; gap: var(--sp-2); align-items: flex-start; }
.note-empty .lbl { font-size: var(--fs-sm); color: var(--text-2); }
.note-compose { padding: 0 var(--sp-4) var(--sp-3); display: flex; flex-direction: column; gap: var(--sp-2); }
.note-compose-actions { display: flex; justify-content: flex-end; gap: var(--sp-2); }

/* Hoja "escribir texto largo" (components/texto-sheet.js, 2026-09-01) — Fernando: "el espacio
   para escribir es más grande y se puede leer mejor lo que uno va escribiendo". Reusa
   .sheet/.sheet-hd/.flabel/.form-error del sistema; acá solo el textarea grande, que en mobile
   ocupa buena parte de la hoja (min-height en vh) y en desktop se acota a un alto cómodo de
   lectura, no a la altura de la ventana entera. */
textarea.txt-sheet-ta {
  min-height: 40vh; max-height: 55vh; font-size: var(--fs-body-lg); line-height: 1.5;
}
@media (min-width: 900px) {
  textarea.txt-sheet-ta { min-height: 220px; max-height: 360px; }
}

/* Problemas de la cuenta (components/problemas-cuenta.js, 2026-09-01) — hermano visual de
   .note-* de arriba: mismo espaciado de fila (.note-row) y las mismas .note-act/.note-actions/
   .note-confirm* para editar/borrar (§4.1 nº6: no se inventa un segundo look de acciones). Lo
   propio de acá es el tilde de resolución y el disclosure de resueltos. */
.prob-list { display: flex; flex-direction: column; }
.prob-row {
  padding: var(--sp-3) var(--sp-4); position: relative;
  display: flex; align-items: flex-start; gap: var(--sp-3);
}
.prob-row + .prob-row::before {
  content: ""; position: absolute; top: 0; left: var(--sp-4); right: var(--sp-4);
  height: .5px; background: var(--hairline);
}
/* Tilde: círculo vacío = abierto, lleno verde = resuelto (§4.1 nº17 — el color de la buena
   noticia se gana, nunca es el default). Visual 22px; el blanco táctil crece a ≥44px SOLO en
   pointer:coarse, con un pseudo-elemento (nunca padding — agrandar la caja real descoloca la fila,
   mismo criterio que .opt/.sheet-x de más arriba). */
.prob-tilde {
  position: relative; flex-shrink: 0; margin-top: 2px; padding: 0;
  width: 22px; height: 22px; border-radius: 50%;
  border: 1.5px solid var(--border-strong); background: var(--surface); color: #fff;
  display: inline-flex; align-items: center; justify-content: center; cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: background var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease);
}
.prob-tilde .ico { display: inline-flex; }
.prob-tilde.is-on { background: var(--green-text); border-color: var(--green-text); }
@media (pointer: coarse) {
  .prob-tilde::after { content: ""; position: absolute; inset: -11px; }
}
.prob-main { min-width: 0; flex: 1; display: flex; flex-direction: column; gap: 6px; }
.prob-body { font-size: var(--fs-body-lg); color: var(--text-1); line-height: 1.45; white-space: pre-wrap; overflow-wrap: anywhere; }
.prob-row.is-resuelto .prob-body { color: var(--text-3); }
.prob-meta { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; font-size: var(--fs-chip); color: var(--text-3); }
/* Oportunidades relacionadas a un problema (2026-09-01, pedido de Fernando: "poder dejar
   registro... [de] posibles oportunidades... y relacionar a esos problemas"). Sub-lista
   SUBORDINADA: letra un escalón menor que `.prob-body` (fs-body en vez de fs-body-lg) + borde
   izquierdo fino — se lee como hija del problema sin competir con él. Se anida DENTRO de
   `.prob-main`, así que hereda el ancho disponible; nunca se dibuja vacía (ver
   problemas-cuenta.js, §4.1 nº7: acá el hueco no es información). */
.prob-op-list {
  display: flex; flex-direction: column; gap: var(--sp-2);
  margin-left: 2px; padding-left: var(--sp-3); border-left: 2px solid var(--border);
}
.prob-op-row { display: flex; flex-direction: column; gap: 4px; }
.prob-op-body { font-size: var(--fs-body); color: var(--text-2); line-height: 1.4; white-space: pre-wrap; overflow-wrap: anywhere; }
.prob-op-meta { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; font-size: var(--fs-chip); color: var(--text-3); }
.prob-empty { padding: var(--sp-3) var(--sp-4) var(--sp-4); display: flex; flex-direction: column; gap: var(--sp-2); align-items: flex-start; }
.prob-empty .lbl { font-size: var(--fs-sm); color: var(--text-2); }
.prob-empty-inline { padding: var(--sp-3) var(--sp-4); font-size: var(--fs-sm); color: var(--text-3); }
/* Disclosure "Resueltos (N)" — clasificar ≠ filtrar (§4.1): los resueltos siguen ahí, solo
   colapsados. Mismo patrón summary/chevron que .nivel-disclosure, prefijo propio para no
   heredar reglas pensadas para envolver un .group entero.
   `.prob-cat-section`/`.prob-cat-section-body` (2026-09-01, views/problemas.js — vista
   transversal #/problemas) se suman a los MISMOS selectores en vez de copiarlos: es el mismo
   disclosure (summary + chevron + borde), solo que ahí nace `open` por defecto en vez de
   colapsado (eso lo decide el atributo `open` del `<details>`, no CSS). Segunda vez que este
   layout se necesita — sube a definición común en vez de copiarse (§4.1 nº13). */
.prob-resolved, .prob-cat-section { border-top: .5px solid var(--hairline); }
.prob-resolved summary, .prob-cat-section summary { display: flex; align-items: center; gap: 8px; padding: var(--sp-3) var(--sp-4); cursor: pointer; list-style: none; }
.prob-resolved summary::-webkit-details-marker, .prob-cat-section summary::-webkit-details-marker { display: none; }
.prob-resolved summary:hover, .prob-cat-section summary:hover { background: var(--surface-2); }
.prob-resolved-title { font-size: var(--fs-sm); font-weight: 700; color: var(--text-2); flex: 1; }
.prob-resolved-body, .prob-cat-section-body { border-top: .5px solid var(--hairline); }
.prob-cat-section-body { display: flex; flex-direction: column; }
.prob-cat-section-body > div:first-child { padding: var(--sp-2) var(--sp-4); }
/* Interruptor "mostrar/ocultar oportunidades" (views/problemas.js): presentación pura, no toca
   `problemas-cuenta.js` — un ancestro con esta clase apaga la sub-lista en TODAS las filas que
   contiene, sin que `problemRow` necesite saber que existe (ver su docstring). */
.probs-hide-op .prob-op-list { display: none; }

/* Briefing IA ("Prepararme") */
.brief-panel {
  background: var(--surface); border: 1px solid var(--blue-border); border-radius: var(--r-lg);
  box-shadow: var(--sh-1); padding: var(--sp-3) var(--sp-4);
}
.brief-hd { display: flex; align-items: baseline; justify-content: space-between; gap: var(--sp-2); }
.brief-title {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: var(--fs-caption); font-weight: 700; letter-spacing: .08em;
  text-transform: uppercase; color: var(--blue-text);
}
.brief-meta { font-size: var(--fs-chip); color: var(--text-3); }
.brief-body { font-size: var(--fs-body); line-height: 1.5; color: var(--text-2); }
.brief-body p { margin: 6px 0; }
.brief-body h4 { margin: 8px 0 2px; font-size: var(--fs-sm); font-weight: 700; color: var(--text-1); }
.brief-body ul { margin: 4px 0; padding-left: 18px; }
.brief-body li { margin: 3px 0; }
.brief-body b { color: var(--text-1); }
.brief-ft {
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3);
  margin-top: var(--sp-2); padding-top: var(--sp-2); border-top: .5px solid var(--hairline);
}
.brief-note { font-size: var(--fs-chip); color: var(--text-3); }
.brief-regen {
  border: none; background: none; cursor: pointer; padding: 4px 0; flex-shrink: 0;
  font-size: var(--fs-sm); font-weight: 600; color: var(--blue-text);
}
.brief-loading { display: flex; align-items: center; gap: 10px; padding: var(--sp-2) 0; font-size: var(--fs-sm); color: var(--text-2); }
.brief-fail { font-size: var(--fs-sm); color: var(--text-3); padding: 2px 0; }

/* ============================================================
   DESKTOP ≥900px — sidebar, topbar, tabla densa, rail.
   Extensión, no rediseño: mismos componentes, otros anchos.
   ============================================================ */

/* ---- Nivel 3/4: disclosures colapsadas ("se consulta, no se exhibe") — COMPARTIDO mobile y
   desktop (2026-08-30: el cuerpo mobile también monta "Identidad y contacto" como disclosure;
   antes estas reglas vivían dentro del @media de escritorio y el celular mostraba el
   triángulo nativo del browser, sin padding). ---- */
.nivel-disclosure summary { display: flex; align-items: center; gap: 8px; padding: var(--sp-3) var(--sp-4); cursor: pointer; list-style: none; }
.nivel-disclosure summary::-webkit-details-marker { display: none; }
.nivel-disclosure summary:hover { background: var(--surface-2); }
.nivel-disclosure-title { font-size: 15px; font-weight: 700; color: var(--text-1); flex: 1; }
.nivel-disclosure-body { border-top: .5px solid var(--hairline); }
.nivel-disclosure-body > .group { border: none; border-radius: 0; box-shadow: none; border-top: .5px solid var(--hairline); }
.nivel-disclosure-body > .group:first-child { border-top: none; }

@media (min-width: 900px) {
  .m-only { display: none !important; }

  body { padding-bottom: 0; padding-left: var(--sidebar-w); }
  body.chromeless { padding-left: 0; }
  .bottom-nav { display: none; }
  .sidebar { display: flex; }
  #toast-root { bottom: var(--sp-5); left: var(--sidebar-w); }

  .view { max-width: none; padding: 0 var(--pad-desk) var(--pad-desk); }

  /* Topbar: el appbar pierde el material y pasa a header de contenido */
  .appbar {
    position: static; margin: 0 calc(-1 * var(--pad-desk));
    padding: 22px var(--pad-desk) 14px;
    background: none; -webkit-backdrop-filter: none; backdrop-filter: none;
    box-shadow: 0 .5px 0 var(--hairline);
    margin-bottom: var(--sp-2);
  }
  .large-title, .view-title { font-size: var(--fs-title-desk); font-weight: 800; letter-spacing: -.03em; line-height: 1.1; }
  .appbar-sub { font-size: 12.5px; color: var(--text-3); }
  .appbar .avatar { display: none; }   /* el pie del sidebar ya lleva a Más */

  /* Hoy: columna de trabajo + rail */
  .hoy-grid {
    display: grid; grid-template-columns: minmax(0, var(--feed-max)) var(--rail-w);
    gap: var(--sp-6); justify-content: center; align-items: start;
  }
  .today-card:hover { box-shadow: var(--sh-2); }
  .tc-actions { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .tc-actions.cols2 { grid-template-columns: repeat(2, 1fr); }

  /* Clientes: tabla densa */
  .table-wrap { max-width: var(--table-max); }

  /* ----------------------------------------------------------
     Clientes — Variante A (elegida por Fernando 2026-08-24, sobre
     docs/design/direcciones/clientes-desktop-a.html): suelta el techo
     fijo (sobraba espacio en los laterales) y usa el aire ganado para
     dos columnas que hoy no entraban (Potencial, Ventas YTD) + filas
     más respiradas. Token propio (--table-max-clientes): el Tablero
     sigue con --table-max tal cual, no se toca en esta pasada.
     ---------------------------------------------------------- */
  .table-wrap.table-clientes { max-width: var(--table-max-clientes); }
  table.dense.table-clientes th,
  table.dense.table-clientes td { padding: 12px 16px; }
  table.dense.table-clientes th:first-child,
  table.dense.table-clientes td:first-child { padding-left: var(--sp-5); }
  table.dense.table-clientes th:last-child,
  table.dense.table-clientes td:last-child { padding-right: var(--sp-5); }

  /* Razón social: min-width para que no se aplaste + elipsis si el nombre es largo.
     El ellipsis se QUEDA — esta es una tabla densa, donde una fila es un renglón, y ahí es el
     patrón correcto (§4.1 #12); envolver acá desalinearía todas las columnas.
     Lo que se va es el `max-width: 320px` fijo: medido el 2026-08-28, la celda daba 435px y el
     nombre se cortaba a los 320 igual, con 115px libres al lado. 25 de 430 razones sociales
     recortadas por un tope hardcodeado, no por falta de lugar. Con `100%` usa lo que su columna
     ya le da, y sigue habiendo elipsis para las que aun así no entren — pero ahora acompañado
     del `title` con el nombre entero (clientes.js), porque un nombre cortado sin forma de leerlo
     completo es un dato perdido, no un dato abreviado. */
  /* Celdas de datos: UNA línea con ellipsis y tope de ancho (§4.1 nº12 — tabla densa). Sin esto,
     una dirección multilínea o un multi largo estiraba TODAS las filas a 138px (medido
     2026-08-30: `direccion_logistica` y `nichos` eran los culpables). El title con el valor
     completo lo pone el render del catálogo — ellipsis sin title es un dato perdido. Quedan
     afuera la columna de nombre (tiene su propio tratamiento) y el chevron. */
  table.dense.table-clientes td { white-space: nowrap; }
  table.dense.table-clientes td:not(.col-name):not(.col-chev) {
    max-width: 280px; overflow: hidden; text-overflow: ellipsis;
  }
  /* La columna del nombre lleva ancho ASIGNADO como todas (2026-08-30, reporte de Fernando con
     la vista en producción: "la primera columna es extremadamente ancha y casi no entra nada
     más"). En layout auto, la única columna sin width absorbe TODO el sobrante del ancho de la
     pantalla — con pocas columnas visibles y un monitor ancho, el nombre se comía media pantalla. */
  .table-clientes .col-name { width: 300px; min-width: 220px; max-width: 420px; }
  .table-clientes .td-name { min-width: 0; }
  .table-clientes .td-name-text { min-width: 0; }
  .table-clientes .razon {
    display: block; min-width: 0; max-width: 100%;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }

  /* Anchos de columnas — claves dinámicas del catálogo (2026-08-30, `col-<key>`, ver
     clientes-columnas.js). Solo se fijan las que vienen visibles por default; una columna
     agregada por el usuario desde el selector usa el ancho automático de `min-width` genérico
     de abajo — no se puede tarifar por anticipado un catálogo de ~90 columnas. */
  .table-clientes th, .table-clientes td { min-width: 90px; }
  .table-clientes .col-lifecycle { width: 110px; min-width: 110px; }
  .table-clientes .col-comercial_nombre { width: 140px; min-width: 140px; }
  .table-clientes .col-clasificacion { width: 74px; min-width: 74px; }
  .table-clientes .col-potencial { width: 84px; min-width: 84px; }
  .table-clientes .col-ventas_ytd { width: 104px; min-width: 104px; }
  .table-clientes .col-saldo { width: 118px; min-width: 118px; }
  .table-clientes .col-ultima_compra { width: 110px; min-width: 110px; }
  .table-clientes .col-bcra_situacion { width: 150px; min-width: 150px; }
  .table-clientes .col-localidad { width: 150px; min-width: 150px; }
  .table-clientes .col-operatoria { width: 130px; min-width: 130px; }
  .table-clientes .col-chev { width: 28px; min-width: 28px; }

  /* ----------------------------------------------------------
     Ancho de columna ajustable (2026-08-30, pedido de Fernando:
     "que se pueda modificar el ancho de cada columna", como en
     Notion). `table-layout:fixed` + un `<colgroup>` real (JS,
     clientes.js) son la fuente de verdad del ancho — con layout
     `fixed`, el `<col>` con `width` explícito MANDA sobre los
     `.col-<key>{width:…}` de arriba (algoritmo de la tabla, no la
     cascada): esas reglas quedan de respaldo inerte para cuando el
     colgroup todavía no montó, no se duplican a mano.
     ---------------------------------------------------------- */
  table.dense.table-clientes { table-layout: fixed; }
  /* Agarre de ~6px en el borde derecho de CADA `<th>` (razón social incluida — el ancho es
     presentación, no edición). Pointer events propios, sin librerías (CSP `script-src 'self'`,
     mismo criterio que el drag del embudo). Sin `position:relative` acá a propósito: TODO
     `<th>` de esta tabla ya es `position:sticky` (ver más abajo), que igual que `relative`
     arma el contenedor para el `absolute` del agarre — declararlo de nuevo con mayor
     especificidad (`.col-resizable` tiene 2 clases contra 1) pisaría el sticky y tiraría abajo
     el header pegajoso. */
  .col-resize-handle {
    position: absolute; top: 0; right: -3px; bottom: 0; width: 6px;
    cursor: col-resize; z-index: 4; background: transparent;
  }
  .table-clientes th.col-resizable:hover .col-resize-handle,
  .table-clientes th.col-resizable:focus-within .col-resize-handle { background: var(--blue-border); }
  /* Línea guía mientras se arrastra — `position:fixed` porque el ancho de columna puede exceder
     el alto de la fila (recorre toda la ventana, igual que un cursor de redimensionado real). */
  .col-resize-guide {
    position: fixed; top: 0; bottom: 0; width: 2px; background: var(--blue);
    z-index: 45; pointer-events: none; /* por encima del toast (40), no compite con un modal (60) */
  }
  body.col-resizing { cursor: col-resize; user-select: none; }
  body.col-resizing * { cursor: col-resize !important; }

  /* Lápiz al hover (Fase A #1, decisión de Fernando: "se edita por lápiz, que aparece al pasar
     el mouse sobre la celda") — SOLO acá: `field-edit.js` lo deja siempre visible por default
     (así lo necesita la Ficha, sin hover posible en su layout de filas). El hueco "Completar"
     queda afuera a propósito (§4.1 nº7: siempre visible, no compite con el hover). */
  .table-clientes .fe-pencil-btn,
  .table-clientes .fe-btn:not(.fe-btn-empty) .ico {
    opacity: 0; transition: opacity var(--t-fast) var(--ease);
  }
  .table-clientes tbody tr:hover .fe-pencil-btn,
  .table-clientes tbody tr:hover .fe-btn:not(.fe-btn-empty) .ico,
  .table-clientes tbody tr:focus-within .fe-pencil-btn,
  .table-clientes tbody tr:focus-within .fe-btn:not(.fe-btn-empty) .ico {
    opacity: 1;
  }
  /* Con el editor abierto, la celda deja de recortar: el `.fe-editbox`/multiselect/type-ahead de
     localidad necesita su ancho real para ser usable, aunque la columna sea angosta. */
  table.dense.table-clientes td:has(.fe-editbox),
  table.dense.table-clientes td:has(.fe-multiselect),
  table.dense.table-clientes td:has(.lc-input) {
    overflow: visible; white-space: normal; max-width: none;
  }

  /* Header sticky + primera columna (nombre) sticky — la tabla scrollea adentro de
     `.table-wrap` (overflow-x:auto), nunca la página (§B). Fondo opaco `var(--surface)`
     (mismo que `.bcard`, el contenedor de la tabla) para que el contenido que pasa por
     detrás no se transparente. */
  .table-clientes thead th {
    position: sticky; top: 0; z-index: 2; background: var(--surface);
  }
  .table-clientes .col-name {
    position: sticky; left: 0; z-index: 1; background: var(--surface);
    box-shadow: 1px 0 0 var(--hairline);
  }
  /* `td.col-name` hereda de `table.dense td` un box-shadow propio (línea separadora inferior,
     ~línea 400) — `box-shadow` no se combina por cascada como `border`, así que acá se
     redeclaran los DOS (inferior + borde derecho del scroll) en una sola declaración. */
  .table-clientes td.col-name { box-shadow: 0 .5px 0 var(--hairline), 1px 0 0 var(--hairline); }
  .table-clientes tr:last-child td.col-name { box-shadow: 1px 0 0 var(--hairline); }
  .table-clientes thead th.col-name { z-index: 3; }
  .table-clientes tbody tr:hover .col-name { background: var(--surface-2); }

  /* Potencial: texto coloreado, la palabra misma dice el estado (Alto/Medio/Bajo) */
  .pot { font-size: 12.5px; font-weight: 600; }
  .pot.alto { color: var(--green-text); }
  .pot.medio { color: var(--text-2); }
  .pot.bajo { color: var(--text-4); }

  /* Saldo: monto + etiqueta SIEMPRE al lado (nunca el color solo) — mismo criterio que
     la fila mobile (crow-amt + crow-sub), en dos líneas para no ensanchar la columna.
     inline-flex en un <span> adentro del <td> (no en el <td> mismo: display:flex ahí
     rompe el layout de tabla). */
  .saldo-cell { display: inline-flex; flex-direction: column; align-items: flex-end; gap: 1px; }
  .saldo-amt { font-size: var(--fs-sm); }
  .saldo-lbl { font-size: 10.5px; font-weight: 600; color: var(--text-3); }

  /* ----------------------------------------------------------
     Ficha 360 — Variante B (elegida por Fernando 2026-08-25, sobre
     docs/design/direcciones/ficha-desktop-b.html): "es angosta, sobra
     espacio en los laterales" — el cap de 640px era el ancho pensado
     para mobile, sobrevivió a desktop sin que nadie lo cuestionara.
     Columna ancha (--feed-max-ficha, token propio) + rail STICKY con
     lo que decide la conversación (saldo neto, bandera de entrega,
     estado de la relación) siempre a la vista, sin scrollear.
     Mobile no se toca: esta sección entera vive atrás de un `if
     (isDesktop)` en ficha.js — el DOM ni se construye por debajo
     de 900px salvo el árbol que ya existía antes de esta pasada.
     ---------------------------------------------------------- */
  .view-ficha .ficha-col { max-width: none; margin: 0; width: 100%; }
  .ficha-body {
    display: grid;
    grid-template-columns: minmax(0, var(--feed-max-ficha)) var(--rail-w);
    gap: var(--sp-5); justify-content: center; align-items: start;
  }
  .col-main { display: flex; flex-direction: column; gap: var(--sp-4); min-width: 0; }

  /* Cabecera compacta v2 — un poco más espaciosa en escritorio que en mobile (20px base, ver la
     definición fuera de este media query). */
  .profile-cpt .mono { width: 48px; height: 48px; font-size: 16px; }
  .profile-cpt-fantasia { font-size: 23px; }

  .curva-dos { grid-template-columns: 1fr 1fr; }

  /* El scrollbar de acá NO se oculta, y es a propósito (2026-08-28).

     Antes iba `scrollbar-width: none` + `::-webkit-scrollbar { display: none }`. Con el rail de la
     Ficha excediendo `calc(100vh - 48px)` en cualquier laptop, eso dejaba la última tarjeta
     recortada SIN NINGUNA SEÑAL de que hubiera más abajo — el mismo defecto que la tabla de
     Cobranzas entre 900 y 1190px (corregido el 2026-08-26): recortar sin dejar forma de llegar a
     lo recortado. Un scrollbar nativo solo se dibuja cuando hay overflow, así que no ensucia el
     rail corto de "Hoy", que comparte esta regla. */
  .rail {
    display: flex; flex-direction: column; gap: var(--sp-3);
    position: sticky; top: 24px;
    max-height: calc(100vh - 48px); overflow-y: auto;
    scrollbar-gutter: stable;
  }
  /* `flex-shrink: 0` es EL arreglo de "el módulo se ve mal, hay información cortada"
     (Fernando, 2026-08-28). Medido con scripts/harness-ficha.py sobre el cliente 100 a 1440x700.

     El rail es un flex column con `max-height`. Sus tarjetas heredaban `flex-shrink: 1` (el
     default), así que cuando el contenido no entraba NO desbordaba: se COMPRIMÍA. Y como
     `.rail-card` lleva `overflow: hidden`, cada tarjeta recortaba lo suyo en silencio --
     "Acciones rápidas" perdía 28px (el botón Anotar cortado al medio), "Ir a sección" 67px
     (dos links que directamente no existían), "Estado de la relación" 33px.

     Lo peor era que NO había señal: `scrollHeight === clientHeight` porque el contenido se había
     achicado para caber, así que ni siquiera aparecía el scrollbar. Un contenido comprimido se ve
     prolijo y miente, que es exactamente el defecto de §4.1 nº12 -- y no se detecta mirando la
     pantalla, solo midiendo caja contra contenido.

     Con `flex-shrink: 0` las tarjetas conservan su alto, el rail desborda de verdad, y el scroll
     (con su barra, ver la regla de arriba) se vuelve la salida real hacia lo que quede abajo. */
  .rail > * { flex-shrink: 0; }

  /* Las tarjetas `.rail-*` de la Ficha 360 se mudaron FUERA de este media query el 2026-09-13
     (se dibujan también en celular, dentro de las pestañas) — buscarlas junto a `.rail-saldo`.
     Acá solo queda lo que de verdad es específico del rail sticky de escritorio. */

  /* Situación de plata NETA (`.rail-saldo`/`.rs-*`) y bandera de entrega (`.rail-flag`): estilo
     base compartido con mobile, definido fuera de este media query desde el fix de auditoría
     2026-08-25 (1.2/C1) — buscarlos cerca de `.saldo-total`. Acá solo queda lo que de verdad es
     específico del rail sticky de escritorio (nav, acciones, disclosures). */

  /* Nav anchors del rail */
  .rail-nav { padding: var(--sp-2) var(--sp-3); }
  .nav-link { display: flex; align-items: center; gap: 8px; min-height: 32px; border-radius: 8px; padding: 0 6px; font-size: 12.5px; font-weight: 500; color: var(--text-2); text-decoration: none; transition: background var(--t-fast) var(--ease); }
  .nav-link:hover { background: var(--gray-soft); color: var(--text-1); }
  .nav-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--text-4); flex-shrink: 0; }

  /* Acciones del rail — desde 2026-08-28 (v2 densidad) ES el único lugar donde viven en
     escritorio (WhatsApp/Actividad/Estado de cuenta/One-pager); la cabecera de escritorio ya no
     las repite ("acciones rápidas es redundante", Fernando 2026-08-28) — la tarjeta pasó de
     "Acciones rápidas" a "Acciones" a secas. */
  .rail-actions { display: flex; flex-direction: column; gap: 6px; padding: var(--sp-2) var(--sp-3); }
  .rail-btn { display: flex; align-items: center; justify-content: center; gap: 6px; min-height: 36px; border-radius: var(--r-sm); font-family: inherit; font-size: 13px; font-weight: 600; cursor: pointer; border: 1px solid; width: 100%; text-decoration: none; }
  .rail-btn.wa { background: var(--green-soft); color: var(--green-text); border-color: var(--green-border); }
  .rail-btn.ghost { background: var(--surface-2); color: var(--text-1); border-color: var(--border); }
  .rail-btn.ghost:hover { background: var(--surface-3); }
  /* Variante "acción principal, sólida" (2026-09-01, pedido textual de Fernando: "necesito que el
     botón de actividad esté destacado en azul y esté en el bloque de acciones arriba de WhatsApp" —
     azul SÓLIDO, ya elegido por él, no el tinte suave que usan `.wa`/`.ghost`).
     Usa `--blue-solid` (tokens.css), NO `var(--blue)` a secas: en modo oscuro `--blue` vale
     `#2997ff` y texto blanco encima da 3,02:1 — falla AA. `--blue-solid` fija #0071e3 en los dos
     temas, 4,70:1 con texto blanco (cálculo y el resto de la familia de tokens en tokens.css). */
  .rail-btn.primary { background: var(--blue-solid); color: #fff; border-color: var(--blue-solid); }
  .rail-btn.primary:hover { background: #0068d1; }
  .rail-btn:disabled { cursor: not-allowed; opacity: .6; }
  /* `<a>` no acepta `disabled` — el control de WhatsApp (`buildWaControl`, views/ficha.js) usa
     esta clase cuando no hay teléfono, mismo criterio visual que `.rail-btn:disabled`. */
  .rail-btn.is-disabled { cursor: not-allowed; opacity: .6; pointer-events: none; }
  /* Separador entre el grupo primario (Actividad/WhatsApp) y el de documentos (Estado de
     cuenta/One-pager) dentro de la misma tarjeta "Acciones". */
  .rail-actions-sep { height: .5px; background: var(--hairline); margin: 2px 0; }

  /* ---- Nivel 1: callouts accionables (`.alerta-compuesta` / `.callout-favor`) ----
     Estilo base compartido con mobile — ver comentario junto a `.saldo-total` más arriba en
     este archivo, fuera del media query. */

  /* ---- Nivel 3/4: disclosures colapsadas — reglas MOVIDAS fuera del media query (2026-08-30):
     el cuerpo mobile ahora también monta "Identidad y contacto" como disclosure, y acá adentro
     estas reglas solo regían en escritorio (triángulo nativo y sin padding en el celular).
     Viven junto a los estilos compartidos, antes de este @media. ---- */
  /* El scroll del índice del rail (scrollIntoView, ver ficha.js) deja un pelín de aire arriba.
     ⚠️ Descendiente, NO hijo directo (2026-09-13): desde las pestañas, cada bloque vive dentro
     de su `.ficha-tabpanel` y con `>` la regla dejaba de aplicar a TODOS — el salto del índice
     quedaba pegado al borde superior, sin ningún error visible. */
  .col-main [id] { scroll-margin-top: 20px; }
  .nivel4-block + .nivel4-block { border-top: .5px solid var(--hairline); }
  /* Nota del detalle de deuda en Nivel 3 cuando no hay nada que listar (Al día) — reemplaza
     al monto grande, que ahora vive solo en el rail. */
  .saldo-empty-note { padding: var(--sp-2) var(--sp-4) var(--sp-3); font-size: var(--fs-sm); color: var(--text-3); }

  /* Ficha / Más: columna de lectura */
  .view-mas .mas-col { max-width: 480px; }

  /* Tablero */
  .tiles4 { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--sp-3); max-width: var(--table-max); }
  .tiles4 .tile { padding: var(--sp-4) 18px; }
  .tiles4 .tile .v { font-size: var(--fs-hero); }
  .tiles4 .tile .d { font-size: var(--fs-caption); }
  .duo { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-3); max-width: var(--table-max); align-items: start; }
  .tablero-col { max-width: var(--table-max); }

  /* M2: la sheet pasa a modal centrado; el + y el aviso de WhatsApp
     se apoyan en la esquina (ya no hay tab bar). */
  .sheet-backdrop { align-items: center; padding-left: var(--sidebar-w); }
  .sheet { max-width: 460px; border-radius: var(--r-xl); max-height: calc(100vh - 80px); }
  .fab { right: 28px; bottom: 28px; }
  .wa-return { left: auto; right: 28px; bottom: 92px; max-width: 400px; }
}

/* ============================================================
   Cancelaciones — "mercadería que pedimos y no entregamos"
   (docs/design/direcciones/cancelaciones-*.html). Dos vistas con
   un toggle: por cliente (default) y por producto.
   Extiende clases que YA EXISTEN (.tile, .bcard, table.dense,
   .seg, .chip.mini, .today-card) — acá solo lo genuinamente
   nuevo: la fila de producto con su chip de stock, el bloque de
   compradores sin cuenta en el CRM, y el par de badges de
   frescura (son DOS fotos: cancelaciones de GitHub, stock de Odoo).
   ============================================================ */

/* Dos badges de frescura lado a lado en el header — nunca colapsados en uno solo (trampa 3). */
.fresh-pair { display: flex; gap: 6px; flex-wrap: wrap; }
.canc-group-foot .badge-fresh { font-size: 10px; padding: 2px 8px; }

/* Chip de stock — TRES estados, no dos (trampa 1: `en_cache_stock` manda, `free_qty` nunca
   se lee solo). "sindato" NUNCA se pinta como "0 u." — sería el hueco con el color de la
   buena/mala noticia, cuando en realidad es "no sabemos" (§4.1 regla 8). */
.stock-chip {
  display: inline-flex; align-items: center; gap: 3px; font-size: var(--fs-chip); font-weight: 700;
  border-radius: 6px; padding: 2px 7px; border: 1px solid; white-space: nowrap; flex-shrink: 0;
}
.stock-chip.ok { background: var(--green-soft); color: var(--green-text); border-color: var(--green-border); }
.stock-chip.zero { background: var(--surface-2); color: var(--text-4); border-color: var(--border); }
.stock-chip.sindato { background: var(--gray-soft); color: var(--text-3); border-color: var(--border); font-style: italic; }

/* Trampa 2: "1 comprador · sin cuenta en el CRM" — nunca "0 clientes". `.lock-note` es
   deliberadamente liviano (no es un chip: no es una afirmación semafórica, es una aclaración). */
.lock-note { display: inline-flex; align-items: center; gap: 4px; font-size: var(--fs-chip); color: var(--text-4); }
.cli-count { font-size: var(--fs-chip); font-weight: 600; color: var(--blue-text); }

/* Tile propio para "sin dato de stock": NO reusa `.tile .v.warn` (que hoy usa views/cobranzas.js
   sin que exista esa regla en ningún lado) — nombre distinto para no heredar ni pisar nada ajeno. */
.tile .v.stock-warn { color: var(--orange-text); }

/* ---- Acordeón por cliente (mobile) — variante B elegida, docs/design/direcciones/
   cancelaciones-b.html. Expande en el lugar donde está (§4.1: "en mobile, acordeón que
   expande donde estás" — pedido explícito de Fernando, no un modal ni una ruta nueva). ---- */
.canc-group { background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-lg); box-shadow: var(--sh-1); overflow: hidden; }
.canc-group-hd { display: flex; align-items: center; gap: 11px; padding: var(--sp-3) var(--sp-4); cursor: pointer; -webkit-tap-highlight-color: transparent; }
.canc-group-hd:active { background: var(--surface-2); }
.canc-group-meta { flex: 1; min-width: 0; }
/* §4.1 #12 — el nombre del cliente envuelve, no se corta. Medido a 375px el 2026-08-28:
   "Di Martella Orsi, Santiago Manuel" perdía el 34% y "Logistica productiva interna" el 25%,
   compitiendo por el ancho con el monto del grupo (`.canc-group-right`, que no se encoge).
   Mismo criterio que `.tc-name` y `.invx .doc`: en una lista vertical hay renglones de sobra y
   el nombre es lo único que identifica la cuenta. */
.canc-group-name { font-size: var(--fs-name); font-weight: 600; color: var(--text-1); white-space: normal; overflow-wrap: anywhere; }
.canc-group-sub { font-size: var(--fs-chip); color: var(--text-3); margin-top: 2px; }
.canc-group-right { text-align: right; flex-shrink: 0; }
.canc-group-amt { font-size: var(--fs-name); font-weight: 700; color: var(--text-1); }
.canc-group-cnt { font-size: var(--fs-chip); color: var(--text-3); margin-top: 1px; }
.canc-chevron { flex-shrink: 0; color: var(--text-4); transition: transform var(--t-fast) var(--ease); }
.canc-group.open .canc-chevron { transform: rotate(90deg); }
.canc-group-body { border-top: .5px solid var(--hairline); }
.canc-empty-stock { padding: var(--sp-3) var(--sp-4); font-size: var(--fs-sm); color: var(--text-3); line-height: 1.4; }
.canc-group-foot { padding: var(--sp-2) var(--sp-4); border-top: .5px solid var(--hairline); background: var(--surface-2); }
.canc-group-actions { padding: var(--sp-2) var(--sp-4) var(--sp-3); }

/* ---- Fila de producto (vista "por producto", mobile) — extiende `.prod-row`/`.prod-main`/
   `.prod-name`/`.prod-val` (ya existen, sección Ficha). Acá solo lo nuevo: la meta-línea que
   junta SKU + chip de stock + "N clientes"/"sin cuenta". ---- */
.prod-row-meta { display: flex; align-items: center; gap: 8px; margin-top: 2px; flex-wrap: wrap; }
.prod-row { cursor: pointer; }
.prod-row.no-account { background: var(--surface-2); }
.prod-row.no-account .prod-name { color: var(--text-2); }

/* Nombre de producto en el acordeón por cliente y el panel de detalle del split-pane: el
   parche de acá (`-webkit-line-clamp: 2`) resultó insuficiente — medido en la pasada de pulido
   2026-08-26, 41 de 42 nombres del panel de 320px necesitaban 3 o 4 líneas y el clamp solo
   daba 2, así que la mitad del texto seguía sin verse (cortar con ellipsis, aunque sea a 2
   líneas, es la misma trampa: prolijo y no dice nada). El límite se sacó: `.prod-name` ahora
   envuelve SIN tope de líneas por definición base (sección Ficha, más arriba en este archivo)
   — acá no queda ninguna regla propia que agregar. */

/* Bloque "compradores sin cuenta en el CRM" (permanente por diseño, CLAUDE.md §4 — nunca una
   fila de la lista de clientes, siempre aparte) reusa `.sin-asignar-card` SIN modificador
   is-bad/is-ok: es información, no una alarma. */

/* ============================================================
   Cancelaciones — DESKTOP ≥900px. Media query PROPIA (no la
   compartida de sidebar/topbar/tabla densa, de arriba en este
   archivo): esta pantalla se construyó con otros agentes editando
   las secciones Ficha y M3 — Cobranzas en simultáneo, así que todo
   lo suyo — mobile y desktop — vive junto, al final del archivo.
   Solo lo genuinamente nuevo: el split-pane por cliente (lista +
   panel de detalle sticky, "no perdés el lugar en la lista" —
   Fernando 2026-08-26). La vista por producto no lo necesita: usa
   `table.dense`/`.bcard` tal cual (clic en la fila navega a
   `#/cancelaciones/producto/:id`, no hay nada que mantener a la vista).
   ============================================================ */
@media (min-width: 900px) {
  .canc-split { display: flex; gap: 0; align-items: flex-start; }
  .canc-list { flex: 1; min-width: 0; max-width: 760px; }
  .canc-detail-panel { width: 320px; flex-shrink: 0; margin-left: var(--sp-4); position: sticky; top: 16px; }
  .canc-detail-card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-lg); box-shadow: var(--sh-2); overflow: hidden; }
  .canc-detail-empty { background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-lg); padding: 32px 20px; text-align: center; color: var(--text-3); font-size: var(--fs-sm); line-height: 1.4; }
  .canc-detail-hd { padding: var(--sp-4) var(--sp-4) var(--sp-2); border-bottom: .5px solid var(--hairline); }
  .canc-detail-client { font-size: var(--fs-body-lg); font-weight: 800; letter-spacing: -.01em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .canc-detail-sub { font-size: var(--fs-chip); color: var(--text-3); margin-top: 2px; }
  .canc-detail-total { font-size: 20px; font-weight: 800; color: var(--text-1); margin-top: 8px; }
  /* Fila de cliente seleccionada en la tabla de la izquierda — mismo azul tenue que `.chip.blue`. */
  .view-cancelaciones table.dense tr.is-selected td { background: var(--blue-soft); }
}

/* ---- Banda "esta cuenta se unificó con otra" (ficha de un duplicado suprimido, 2026-09-01) ----
   Naranja y no roja: no es un error ni una alarma, es un estado de la cuenta — el rojo de esta app
   está reservado para la deuda vencida y para lo que hay que atender hoy (§4.1 nº4, una sola
   alerta roja por bloque). Y es una BANDA, no una pared: la ficha entera se sigue viendo debajo,
   con todos sus campos editables (§4.1 nº21 — la lección del cartel "Sin perfil de Notion", que
   ocultaba los 12 campos editables de 60 cuentas y las volvía incompletables). */
.dup-banner {
  display: flex; flex-wrap: wrap; gap: var(--sp-3); align-items: center;
  justify-content: space-between;
  padding: var(--sp-3) var(--sp-4); border-radius: var(--r);
  background: var(--orange-soft); border: 1px solid var(--orange-border);
}
.dup-banner-txt { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1 1 320px; }
.dup-banner-txt strong { font: 700 var(--fs-body)/1.35 var(--font); color: var(--orange-text); }
.dup-banner-txt span { font: 400 var(--fs-sm)/1.4 var(--font); color: var(--text-2); }
.dup-banner-meta { color: var(--text-3) !important; font-size: var(--fs-caption) !important; }
.dup-banner-acc { flex: 0 0 auto; }

/* Hoja "Alta de clientes" (2026-09-03, components/alta-link.js): el enlace público a mano. */
.alta-link-body { display: flex; flex-direction: column; gap: var(--sp-3); }
.alta-link-body input[readonly] { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 13px; }
.side-nav button.side-item, button.grow { width: 100%; text-align: left; background: none; border: 0; font: inherit; color: inherit; cursor: pointer; }

/* K1 — Criterios de clasificación (docs/contrato-criterios-clasificacion.md §2). Descripción corta
   de la categoría elegida, en su propio renglón bajo el select/hoja de edición — NUNCA con
   ellipsis: son ≤60 caracteres por diseño del backend (catalogos.py), cortarla dejaría un texto
   que ya de por sí es un resumen, sin forma de leer el resto. */
.fe-hint {
  flex-basis: 100%; font-size: 11px; color: var(--text-3);
  white-space: normal; overflow: visible; word-break: break-word;
}

/* K1 — subtítulo de `corta` en las opciones del popover/hoja de la faceta Clasificación
   (components/facet-chip.js). Desktop (puntero fino) usa `title` en el hover, NO este nodo — se
   mantiene oculto para no duplicar el mismo texto dos veces en la misma fila (§4.1 nº6). En
   táctil (`pointer: coarse`, la hoja de abajo) es el segundo renglón visible: `.cf-opt` necesita
   `flex-wrap` para que el `flex-basis: 100%` empuje el subtítulo a su propia línea — se agrega
   acá, sin tocar la regla original de la fila (arriba, línea ~580). */
.cf-opt { flex-wrap: wrap; }
.cf-opt-sub {
  display: none; flex-basis: 100%; font-size: 11px; color: var(--text-3);
  white-space: normal; overflow: visible; word-break: break-word;
}
@media (pointer: coarse) {
  .cf-opt-sub { display: block; }
}

/* K1 — enlace "¿Cómo se clasifica?" (docs/contrato-criterios-clasificacion.md §2.1), al lado del
   `.fe-hint`. Mismo patrón de blanco táctil ≥44px por pseudo-elemento que `.fe-btn`/
   `.fe-pencil-btn`/`.link-btn` (arriba, ~línea 1265) — se declara acá para no editar esa regla
   existente (solo se agrega, no se toca). */
.fe-link {
  background: none; border: none; padding: 0; margin: 0; font: inherit;
  font-size: 11px; font-weight: 600; color: var(--blue-text); cursor: pointer;
  text-decoration: none; flex-shrink: 0;
}
.fe-link:hover { text-decoration: underline; }
@media (pointer: coarse) {
  .fe-link { position: relative; }
  /* Mismo patrón que `.seg button::after` y `.tc-del::after`: `inset` negativo, no un centrado
     con transform — medido el 2026-09-05 en el harness: la versión con `top:50%` + translate
     dejaba el blanco desplazado y un toque 14px bajo el texto no caía en el enlace. */
  .fe-link::after { content: ""; position: absolute; inset: -14px -8px; }
}

/* K1 — chip "Clasificación X" de solo lectura (rol comercial, views/ficha.js) tocable cuando
   `CRM_CRITERIOS` existe: reset de botón para que se vea IGUAL que `UI.chip`, sin borde ni fondo
   propios agregados por el `<button>`. */
.clf-chip-btn {
  background: none; border: none; padding: 0; margin: 0; font: inherit; cursor: pointer;
}

/* K1 — `.fe-hint` pasa a contener DOS nodos (texto de la `corta` + `.fe-link`, cuando el global
   de criterios existe): fila que envuelve, para que el enlace quede al lado del texto en vez de
   forzado a su propia línea por el `flex-basis: 100%` de sus hijos. */
.fe-hint { display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px; }


/* Emails enviados al cliente (2026-09-08, pedido de Fernando) — bloque de la ficha.
   El estado NO usa color de alarma: "no se envió" es información operativa, no el problema de
   nadie (§4.1 nº4, una sola alerta por bloque, y ésta no es la alerta de este bloque).
   ⚠️ `.emails-vacio` usa `--text-2` y no `--text-4`: el texto INFORMA (que todavía no se mandó
   nada), y `--text-4` está documentado en tokens.css como "solo placeholder/decorativo". */
.emails-group .email-row { display: flex; align-items: baseline; gap: 8px; font-size: var(--fs-sm); padding: 3px 0; }
.emails-group .email-label { flex: 1 1 auto; min-width: 0; color: var(--text-1); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.emails-group .email-estado { flex: 0 0 auto; color: var(--text-2); }
.emails-group .email-fecha { flex: 0 0 auto; color: var(--text-3); font-variant-numeric: tabular-nums; }
.emails-group .email-fallido .email-estado { color: var(--orange-text); }
.emails-group .emails-vacio { font-size: var(--fs-sm); color: var(--text-2); margin: 0; }
