/* =============================================================================
   tarifario-ui.css · Identidad visual del módulo Tarifario
   -----------------------------------------------------------------------------
   Tratamiento: CABECERA LITE (§3 del runbook / árbol de decisión del manual).
   El Tarifario es PANTALLA-TABLA: el padrón son 587 localidades que se recorren
   mucho rato buscando cuáles están vencidas. Las referencias de gestos son
   `remitos-ui.css` y `facturas-ui.css`, no `logistica-ui.css` — aunque el
   módulo viva en la misma carpeta y se llegue desde el mismo grupo del rail.

   POR QUÉ APARTE DE LOGÍSTICA. Decisión T1 de la ronda 2: liquidar un flete y
   fijar un precio son dos trabajos distintos. Logística responde «¿cuánto le
   pago a Perealta por lo que ya llevó?»; el Tarifario, «¿cuánto sale llevar a
   Rosario?». Meterlo como tercera mesa de la banda navy habría puesto una
   pantalla de consulta al lado de dos de liquidación.

   LA FORMA la eligió Hernán sobre dos mockups (ronda 5, variante **B "la
   pregunta primero"** contra A "el padrón, ordenado"):

       DESTINO     — la pregunta y su ficha. Es la entrada.
       PADRÓN      — el gobierno del padrón: tabla, dispersión y mapa.
       COTIZACIÓN  — el precio comercial por grupo de localidades.

   Tres pantallas y UN solo nivel de navegación. No hay banda de mesas: el
   módulo no cambia de negocio adentro, siempre es el mismo padrón mirado desde
   tres lados. Cuatro señales apiladas —como en Logística— acá sería andamio.

   TODO va bajo el scope `.tf-ui` y con prefijo `tf-`: los otros módulos siguen
   en Tailwind con su propia paleta y una clase suelta los rompería.

   CERO TOKENS PROPIOS. La paleta entera sale de `src/core/tokens.css` y este
   módulo no se aparta en ninguno. La única superficie navy rellena es el botón
   primario; la marca la sostienen el subrayado de la pestaña activa y el color
   de los títulos. Se fueron los siete colores de Tailwind que había —indigo
   para lo interactivo, esmeralda para cotización, ámbar para «referencia»,
   púrpura para «viaje», naranja y verde para VENTA y COMPRA—: eso era
   color = categoría de manual (§1.1). Acá el color es del ESTADO y de nada más.

   NO HACE FALTA `.tf-ui.tf-shell.hidden`. El armazón se escribe en un <div>
   HIJO de `#page-tarifario`, igual que el ABM, Configuración y Logística: el
   `.page-content` queda limpio y el `.hidden` del router le pega sin pelear.
   La trampa del portal (§8 del manual) aparece sólo cuando el scope va en el
   propio `.page-content`.
   ========================================================================== */

.tf-ui { background: var(--canvas); }

/* Obligación §5.2: quien navega con Tab tiene que ver dónde está parado. */
.tf-ui :focus-visible { outline: 2px solid var(--navy-500); outline-offset: 2px; border-radius: 5px; }

/* ── Armazón ──────────────────────────────────────────────────────────────
   SIN ESPACIO entre las dos clases: el div es `class="tf-ui tf-shell"`, UN
   elemento con las dos. Con espacio no matchea nada y el bloque se cae en
   silencio — sin `display:flex` ni `height:100%` el módulo crece con su
   contenido adentro de un contenedor `overflow:hidden` y se queda sin scroll.
   Es el bug que le costó seis etapas al ABM.

   Y se va con esto la cadena de alto que el módulo se escribía solo:
   `h-[calc(100vh-80px)]` en el tarifario y `calc(100vh - 240px)` en el mapa.
   Dos números mágicos que había que corregir a mano cada vez que la barra
   superior cambiaba de alto — y que nadie corrigió cuando cambió. */
.tf-ui.tf-shell {
    display: flex; flex-direction: column;
    height: 100%; min-height: 0;
    background: var(--canvas);
}

/* ── Nivel único · las tres pantallas ─────────────────────────────────────
   El subrayado crece desde el centro. Un `border-bottom` no se puede animar
   (manual §7): va un ::after con scaleX. Portado literal de `remitos-ui.css`,
   que es el módulo lite de referencia — no reinventado acá. */
.tf-ui .tf-secnav {
    background: var(--surface); border-bottom: 1px solid var(--hair);
    display: flex; gap: 2px; padding: 0 14px; flex-wrap: wrap; flex-shrink: 0;
}
.tf-ui .tf-secnav button {
    position: relative; background: none; border: 0; font: inherit;
    font-size: 12.8px; font-weight: 600; color: var(--muted);
    padding: 12px 15px 13px; cursor: pointer;
    display: inline-flex; align-items: center; gap: 7px;
    transition: color .15s var(--curva), background .15s var(--curva);
}
.tf-ui .tf-secnav button .material-symbols-outlined {
    font-size: 18px; transition: transform .18s var(--curva);
}
.tf-ui .tf-secnav button::after {
    content: ''; position: absolute; left: 10px; right: 10px; bottom: 0;
    height: 2.5px; background: var(--navy); border-radius: 2px 2px 0 0;
    transform: scaleX(0); transition: transform .18s var(--curva);
}
.tf-ui .tf-secnav button:hover, .tf-ui .tf-secnav button:focus-visible {
    color: var(--navy); background: var(--navy-050); outline: none;
}
.tf-ui .tf-secnav button:hover::after,
.tf-ui .tf-secnav button:focus-visible::after { transform: scaleX(.55); }
.tf-ui .tf-secnav button:hover .material-symbols-outlined { transform: scale(1.18); }
.tf-ui .tf-secnav button[aria-current="true"] { color: var(--navy); font-weight: 700; }
.tf-ui .tf-secnav button[aria-current="true"]::after { transform: scaleX(1); }
/* La cola de la barra: lo que acompaña a la navegación sin ser navegación
   (hoy, el segmento de vista del Padrón). */
.tf-ui .tf-secnav .tf-cola { margin-left: auto; display: flex; align-items: center; gap: 10px; padding: 6px 0; }

/* ── Área de trabajo ──────────────────────────────────────────────────────
   `min-height: 0` no es opcional: un `flex: 1` sin él estira en vez de
   scrollear, y lo que pasa del pliegue queda tapado sin barra en ningún lado.
   El scroll es de la VISTA, no del área: así cada pantalla recuerda dónde
   estaba y ninguna arrastra el scroll de la anterior. */
.tf-ui .tf-work { flex: 1; min-height: 0; min-width: 0; position: relative; }
.tf-ui .tf-view { height: 100%; overflow-y: auto; overscroll-behavior: contain; padding: 14px 0 24px; }
/* El canal lateral, UNA sola medida. Las tarjetas que van de borde a borde no
   lo llevan; se lo aplican a su contenido. */
.tf-ui .tf-pad { padding-left: var(--gutter); padding-right: var(--gutter); }

/* ── Barra de consulta ────────────────────────────────────────────────────
   Alineada abajo, no arriba: los rótulos tienen alturas distintas y lo que
   tiene que quedar en una línea son los CAMPOS. */
.tf-ui .tf-bar {
    background: var(--surface); border-top: 1px solid var(--hair); border-bottom: 1px solid var(--hair);
    padding: 12px var(--gutter); display: flex; gap: 14px; align-items: flex-end;
    flex-wrap: wrap; margin-bottom: 14px;
}
.tf-ui .tf-f { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.tf-ui .tf-lbl {
    font-size: .62rem; font-weight: 700; text-transform: uppercase;
    letter-spacing: .7px; color: var(--faint);
}
.tf-ui .tf-in {
    font: inherit; font-size: .79rem; font-weight: 600; color: var(--ink);
    padding: 7px 11px; border: 1px solid var(--hair-strong); border-radius: 8px; background: #fff;
    transition: border-color .14s var(--curva), box-shadow .14s var(--curva);
}
.tf-ui .tf-in:focus {
    border-color: var(--navy-500); outline: none;
    box-shadow: 0 0 0 3px rgba(43,82,150,.12);
}
.tf-ui .tf-in::placeholder { color: var(--faint); font-weight: 400; }
.tf-ui .tf-empuja { margin-left: auto; }

/* ── Botones ──────────────────────────────────────────────────────────────
   En reposo el secundario es GRIS: si tres botones secundarios llevan texto
   navy, compiten con el primario. Al apuntarlo recupera el color de marca. */
.tf-ui .tf-btn {
    display: inline-flex; align-items: center; gap: 6px; font: inherit;
    font-size: .77rem; font-weight: 700; white-space: nowrap;
    padding: 7px 14px; border-radius: 8px;
    border: 1px solid var(--navy); background: var(--navy); color: #fff; cursor: pointer;
    transition: background .14s var(--curva), border-color .14s var(--curva),
                transform .12s var(--curva), box-shadow .14s var(--curva);
}
.tf-ui .tf-btn .material-symbols-outlined { font-size: 17px; }
.tf-ui .tf-btn:hover:not(:disabled) {
    background: var(--navy-700); border-color: var(--navy-700);
    transform: translateY(-2px); box-shadow: 0 5px 14px rgba(0,43,107,.30);
}
.tf-ui .tf-btn:active:not(:disabled) { transform: translateY(1px); box-shadow: 0 1px 3px rgba(0,43,107,.22); }
.tf-ui .tf-btn:disabled { opacity: .45; cursor: default; }
.tf-ui .tf-btn.sec { background: #fff; color: var(--muted); border-color: var(--hair-strong); }
.tf-ui .tf-btn.sec:hover:not(:disabled) {
    background: var(--navy-050); border-color: var(--navy-500); color: var(--navy);
    box-shadow: 0 5px 14px rgba(0,43,107,.16);
}

/* Los íconos de acción de una fila necesitan aire entre sí: pegados, dos
   objetivos de 30px se leen como un control raro de 60. Lección de la etapa 4b
   de Logística, destapada por la foto. */
.tf-ui .tf-acc { display: inline-flex; align-items: center; gap: 4px; }
.tf-ui .tf-ico {
    display: inline-flex; align-items: center; justify-content: center;
    width: 30px; height: 30px; border: 0; border-radius: 8px;
    background: none; color: var(--faint); cursor: pointer;
    transition: background .14s var(--curva), color .14s var(--curva), transform .12s var(--curva);
}
.tf-ui .tf-ico .material-symbols-outlined { font-size: 19px; }
.tf-ui .tf-ico:hover { background: var(--navy-050); color: var(--navy); transform: scale(1.12); }
.tf-ui .tf-ico:active { transform: scale(.96); }

/* ── Segmento — la MISMA consulta, dibujada distinto ──────────────────────
   No son pantallas y no van en la barra de navegación: el Análisis y el Mapa
   leen los mismos documentos (`cargarTodasLasTarifas` → `computeAnalisisRows`)
   y el Mapa incluso reusa las filas que dejó el Análisis. Eran dos pestañas
   que pedían el mismo filtro dos veces para mostrar lo mismo de dos maneras.
   Es la regla §7 al revés: si dos «pantallas» comparten el dato y sólo cambia
   el dibujo, es un segmento de VISTA. */
.tf-ui .tf-seg {
    display: inline-flex; padding: 3px; gap: 2px;
    background: var(--canvas); border: 1px solid var(--hair-strong); border-radius: 10px;
}
.tf-ui .tf-seg button {
    display: inline-flex; align-items: center; gap: 6px; padding: 5px 12px;
    border: 0; border-radius: 7px; background: none; font: inherit;
    font-size: .77rem; font-weight: 700; color: var(--muted); white-space: nowrap; cursor: pointer;
    transition: background .14s var(--curva), color .14s var(--curva);
}
.tf-ui .tf-seg button .material-symbols-outlined { font-size: 16px; }
.tf-ui .tf-seg button:hover { color: var(--ink); }
.tf-ui .tf-seg button[aria-pressed="true"] {
    background: var(--surface); color: var(--navy);
    box-shadow: 0 1px 2px rgba(16,32,64,.10);
}
.tf-ui .tf-seg .n { font-size: .7rem; font-weight: 800; color: var(--faint); font-variant-numeric: tabular-nums; }
.tf-ui .tf-seg button[aria-pressed="true"] .n { color: var(--navy-500); }

/* ── Tarjeta ──────────────────────────────────────────────────────────────
   Una sola sombra, plana. La escala `--e1/e2/e3` es del tratamiento "Capas"
   del ABM y acá no va: este módulo separa por trazo. */
.tf-ui .tf-card {
    background: var(--surface); border: 1px solid var(--hair);
    border-radius: var(--r-card); box-shadow: var(--shadow);
    margin-bottom: 14px; overflow: hidden;
}
.tf-ui .tf-card.lleno {
    display: flex; flex-direction: column;
    flex: 1 1 auto; min-height: 0; margin-bottom: 0;
}
/* La tarjeta que DEJA SALIR lo que se despliega adentro. Misma historia que
   `.lg-card.abierta` en Logística, y el porqué completo está escrito ahí: el
   desplegable de un smart-selector es un `<ul class="absolute z-50">`, y el
   z-index no salva de un recorte del ancestro. El recorte está para que el
   encabezado teñido y la cabecera sticky de la tabla respeten las esquinas; una
   tarjeta con un buscador adentro no tiene ni una cosa ni la otra. */
.tf-ui .tf-card.abierta { overflow: visible; }
.tf-ui .tf-card-h {
    display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
    padding: 10px 16px; border-bottom: 1px solid var(--hair); background: #fafbfc;
}
.tf-ui .tf-card-h h4 { margin: 0; font-size: .82rem; font-weight: 700; color: var(--navy); }
.tf-ui .tf-card-h .r { margin-left: auto; display: flex; gap: 8px; align-items: center; }
.tf-ui .tf-card-b { padding: 14px 16px; }
.tf-ui .tf-card-f {
    display: flex; align-items: center; justify-content: space-between; gap: 12px;
    flex-wrap: wrap; padding: 10px 16px; border-top: 1px solid var(--hair);
    background: #fafbfd; font-size: .76rem; color: var(--muted);
}

/* ── Tabla ────────────────────────────────────────────────────────────────
   El encabezado se apoya en una regla gruesa, como un informe impreso: una
   franja navy de ancho completo sobre 60 filas es lo que más cansa la vista.
   Sigue sticky y opaco al scrollear. */
.tf-ui .tf-tw { flex: 1 1 auto; min-height: 0; overflow: auto; }
.tf-ui table.tf-g {
    width: 100%; border-collapse: collapse; font-size: .79rem;
    font-variant-numeric: tabular-nums;
}
.tf-ui table.tf-g th {
    position: sticky; top: 0; z-index: 2;
    background: var(--surface); color: var(--muted);
    padding: 9px 11px; text-align: left; white-space: nowrap;
    font-size: .64rem; text-transform: uppercase; letter-spacing: .5px; font-weight: 700;
    border-bottom: 2px solid var(--ink);
}
.tf-ui table.tf-g td { padding: 8px 11px; text-align: left; border-bottom: 1px solid #edeef2; }
.tf-ui table.tf-g th.n, .tf-ui table.tf-g td.n { text-align: right; }
.tf-ui table.tf-g th.c, .tf-ui table.tf-g td.c { text-align: center; }
.tf-ui table.tf-g tbody tr { transition: background .12s var(--curva); }
.tf-ui table.tf-g tbody tr:hover td { background: var(--navy-050); }
.tf-ui table.tf-g .nm { font-weight: 700; }
.tf-ui table.tf-g .dim { color: var(--faint); font-size: .72rem; }

/* La plata. `.big` es la cifra que se lee en columna corriendo el ojo. */
.tf-ui .tf-plata { font-weight: 700; color: var(--ink); font-variant-numeric: tabular-nums; }
.tf-ui .tf-plata.big {
    font-size: 1.06rem; font-weight: 800; color: var(--navy);
    letter-spacing: -.015em;
}
/* «Acá no hay nada» se dibuja como una raya tenue, no como un cero: cero pesos
   y ausencia de dato son cosas distintas, y un `$ 0` invita a operar sobre algo
   que no existe. Portado de `.lg-pl.nada` de Logística. */
.tf-ui .tf-nada { color: var(--hair-strong); font-weight: 600; }

/* ── Estado — la ÚNICA escala de color que existe ─────────────────────────
   Nunca categoría. Una tarifa no es verde por ser de venta; es roja porque
   tiene 74 días y ya no se puede cobrar con ella.

       Vigente          → ok    hasta 30 días
       Pronto a vencer  → wrn   entre 30 y 60
       Vencida          → neg   más de 60: es el único que pide una acción
       Zonal / heredada → nav   informativo: el precio salió de otro lado
       Sin dato         → mut   no es un estado malo, es que no hay */
.tf-ui .tf-b {
    display: inline-flex; align-items: center; gap: 5px;
    font-size: .62rem; font-weight: 700; text-transform: uppercase; letter-spacing: .4px;
    padding: 2.5px 8px; border-radius: 99px; white-space: nowrap;
    background: #eef0f4; color: var(--muted);
}
.tf-ui .tf-b .material-symbols-outlined { font-size: 12px; }
.tf-ui .tf-b.ok  { background: var(--ok-050);   color: var(--ok); }
.tf-ui .tf-b.wrn { background: var(--warn-050); color: var(--warn); }
.tf-ui .tf-b.neg { background: var(--crit-050); color: var(--crit); }
.tf-ui .tf-b.nav { background: var(--navy-050); color: var(--navy-500); }
.tf-ui .tf-b.mut { background: transparent; color: var(--faint); font-style: italic; }

/* ── Avisos y estados vacíos ──────────────────────────────────────────────
   Un aviso es una franja con filete al margen, no un bloque de color: tiene
   que poder convivir con una tabla sin ganarle. */
.tf-ui .tf-aviso {
    display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
    border-left: 4px solid var(--hair-strong); background: #fafbfd;
    border-radius: 0 9px 9px 0; padding: 10px 14px; margin-bottom: 12px;
    font-size: .79rem; color: var(--muted);
}
.tf-ui .tf-aviso .material-symbols-outlined { font-size: 19px; flex-shrink: 0; }
.tf-ui .tf-aviso.info { border-left-color: var(--navy-500); background: var(--navy-050); }
.tf-ui .tf-aviso.wrn  { border-left-color: var(--warn); background: var(--warn-050); color: var(--warn); }
.tf-ui .tf-aviso.neg  { border-left-color: var(--crit); background: var(--crit-050); color: var(--crit); }
/* El cuarto tono. Los tres de arriba nacieron para AVISAR algo, así que ninguno
   podía decir «está todo bien»: la franja de referencia del modal necesita poder
   quedarse en verde con el precio ya tipeado, y dejarla en `info` hacía que un
   precio correcto se leyera igual que uno que todavía no se miró. */
.tf-ui .tf-aviso.ok   { border-left-color: var(--ok); background: var(--ok-050); color: var(--ok); }
.tf-ui .tf-aviso.ok b { color: inherit; }
.tf-ui .tf-aviso b { color: var(--ink); font-weight: 700; }
.tf-ui .tf-aviso.wrn b, .tf-ui .tf-aviso.neg b { color: inherit; }
.tf-ui .tf-aviso .tf-empuja { margin-left: auto; }

/* El error CRUDO de Firestore va en renglón propio y en cuerpo chico: el
   mensaje es largo y técnico, y si se mezcla con la explicación se lee como
   parte de ella. Lo que importa es el link que crea el índice que falta, que no
   está en ningún otro lado —la consola de Firebase NO lista los índices que no
   existen—, así que se separa y se subraya. Copiado de `logistica-ui.css`, que
   lo aprendió en la etapa 4c. */
.tf-ui .tf-err-detalle { display: block; margin-top: 6px; font-size: .72rem; line-height: 1.45; }
.tf-ui .tf-err-detalle a {
    display: inline-block; margin-top: 4px;
    color: inherit; font-weight: 700; text-decoration: underline;
}

.tf-ui .tf-vacio {
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: 6px; padding: 48px 24px; text-align: center; color: var(--faint);
}
.tf-ui .tf-vacio .material-symbols-outlined { font-size: 40px; color: var(--hair-strong); }
.tf-ui .tf-vacio b { font-size: .9rem; font-weight: 700; color: var(--ink); }
.tf-ui .tf-vacio span { font-size: .78rem; line-height: 1.6; max-width: 52ch; }

/* El velo de carga. Deja ver lo que había abajo: si tapara con opaco, cada
   consulta parecería que la pantalla se reinicia. */
.tf-ui .tf-velo {
    display: flex; position: absolute; inset: 0; z-index: 3;
    flex-direction: column; align-items: center; justify-content: center; gap: 8px;
    background: rgba(255,255,255,.82); backdrop-filter: blur(2px);
}
.tf-ui .tf-velo p { font-size: .78rem; font-weight: 700; color: var(--navy); }
/* El ícono gira con la clase del MÓDULO y no con `shell-spin`: esa vive en
   `#app-bar .shell-act …`, o sea que fuera de la barra no anima nada. Lo agarró
   `hub-2-publican.py`, y el equivocado era el código — el velo se habría
   quedado con un ícono quieto sin que ninguna aserción sobre el marcado lo
   notara, porque la clase estaba puesta. */
.tf-ui .tf-gira { animation: tfGirar 1s linear infinite; }
@keyframes tfGirar { to { transform: rotate(360deg); } }

/* ── KPIs del padrón ──────────────────────────────────────────────────────
   Cuatro números que dicen si el padrón está sano. El color va sólo donde el
   número PIDE algo: «sin zona» y «vencidas» se pueden arreglar, «con tarifa» y
   «fuera del modelo» se informan.

   Son BOTONES, no tarjetas: apretar uno es preguntar «¿cuáles?», y pone el
   corte que ya existe en el filtro de abajo. Es el único camino corto que hay
   entre «34 vencidas» y las 34; sin él, el número informa y deja al que mira
   buscándolas a mano en 587 filas. Se marcan con `aria-pressed` y no con una
   clase de color: el estado no se guarda en clases de color. */
.tf-ui .tf-kpis { display: grid; grid-template-columns: repeat(4, minmax(0,1fr)); gap: 12px; margin-bottom: 14px; }
.tf-ui .tf-kpi {
    background: var(--surface); border: 1px solid var(--hair); border-radius: var(--r-card);
    padding: 11px 14px; display: flex; flex-direction: column; gap: 2px; box-shadow: var(--shadow);
    font: inherit; text-align: left; cursor: pointer; position: relative; overflow: hidden;
    transition: border-color .14s var(--curva), transform .12s var(--curva), box-shadow .14s var(--curva);
}
.tf-ui .tf-kpi .k { font-size: .6rem; font-weight: 700; text-transform: uppercase; letter-spacing: .8px; color: var(--faint); }
.tf-ui .tf-kpi .v { font-size: 1.34rem; font-weight: 800; color: var(--navy); letter-spacing: -.02em; font-variant-numeric: tabular-nums; }
.tf-ui .tf-kpi .x { font-size: .7rem; color: var(--muted); line-height: 1.35; }
.tf-ui .tf-kpi.wrn .v { color: var(--warn); }
.tf-ui .tf-kpi.neg .v { color: var(--crit); }
.tf-ui .tf-kpi:hover, .tf-ui .tf-kpi:focus-visible {
    border-color: var(--navy-500); transform: translateY(-2px);
    box-shadow: 0 6px 18px rgba(0,43,107,.13);
}
/* El corte activo se marca con una barra al margen y no con relleno: la tarjeta
   ya tiene un número teñido adentro, y un bloque navy encima se lo comería. */
/* El total no filtra nada, así que tampoco se ofrece: sin cursor y sin subir al
   apuntarlo. Un control que parece apretable y no hace nada es peor que no
   tenerlo. */
.tf-ui .tf-kpi.fijo { cursor: default; }
.tf-ui .tf-kpi.fijo:hover { border-color: var(--hair); transform: none; box-shadow: var(--shadow); }
.tf-ui .tf-kpi[aria-pressed="true"] { border-color: var(--navy); }
.tf-ui .tf-kpi[aria-pressed="true"]::before {
    content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: 3px; background: var(--navy);
}

@media (max-width: 900px) { .tf-ui .tf-kpis { grid-template-columns: 1fr 1fr; } }

/* ── La vista PLENA — sólo el Padrón ──────────────────────────────────────
   Las otras dos pantallas scrollean enteras y está bien: son una barra y una
   tabla. El Padrón tiene cuatro KPIs y un filtro ARRIBA de una tabla de 587
   filas, y si scrolleara entera habría que subir hasta el principio cada vez
   que se quiere cambiar un corte. Acá lo fijo queda fijo y scrollea la zona de
   resultados.

   Las tres reglas que hacen falta, y son las tres formas en que flex se come el
   contenido sin que aparezca una barra (lección del ABM):
     · `min-height: 0` en el que scrollea, o estira en vez de scrollear;
     · `flex-shrink: 0` en los de arriba, o se encogen hasta cero —el mínimo
       automático de un ítem flex es 0 si su `overflow` no es `visible`—;
     · y el scope y la clase en el MISMO elemento, sin espacio. */
.tf-ui .tf-view.pleno {
    display: flex; flex-direction: column; overflow: hidden; padding-bottom: 0;
}
/* El `:not()` NO es cosmético y lo agarró el banco: sin él, esta regla le gana
   por especificidad al `flex: 1 1 auto` de `.tf-zres` —tres clases contra dos—
   y le deja `flex-shrink: 0` a la zona que justamente TIENE que encogerse. El
   resultado es el defecto de siempre con otra cara: el que scrollea pasa a ser
   la pantalla entera, los KPIs y el filtro se van para arriba, y la tabla no
   activa su barra nunca. */
.tf-ui .tf-view.pleno > *:not(.tf-zres):not(.tf-mant-panel) { flex-shrink: 0; }

/* EL PANEL DE MANTENIMIENTO ES LA EXCEPCIÓN, y es la contracara del acierto de
   arriba: los KPIs y la barra de filtro son bajos y de alto conocido, así que
   `flex-shrink: 0` es exactamente lo que quieren. El panel no — es el único de
   los fijos que puede crecer más que la pantalla, y creció cuando pasó de dos
   operaciones a tres.

   Con `flex-shrink: 0` se quedaba con todo el alto que pedía y `.tf-view.pleno`
   —que recorta con `overflow: hidden`— le cortaba los botones de abajo SIN
   BARRA PARA LLEGAR. No es que el scroll estuviera duro: no existía.

   Y dejarlo encoger a secas tampoco alcanza, que es la trampa fina: **el
   faltante se reparte proporcional al TAMAÑO BASE de cada ítem**, y la zona de
   resultados tiene de base la tabla entera. Se lleva casi todo el peso y el
   panel queda aplastado a quince píxeles — apretás «Mantenimiento» y no aparece
   nada. Por eso `tfPadronMantenimiento()` OCULTA la zona de resultados mientras
   el panel está abierto: así queda como único ítem flexible, se lleva el alto
   que haya y scrollea adentro, valga la pantalla 1080 o 500.

   `min-height: 0` va por la misma razón que en `.tf-zres`: sin eso un ítem flex
   estira en vez de scrollear, y volveríamos al mismo lugar por otro camino. */
.tf-ui .tf-mant-panel {
    flex: 1 1 auto; min-height: 0;
    overflow-y: auto; overscroll-behavior: contain;
}
.tf-ui .tf-zres {
    flex: 1 1 auto; min-height: 0; position: relative;
    display: flex; flex-direction: column; padding-bottom: 16px;
}
.tf-ui .tf-vista { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
.tf-ui .tf-vista.hidden { display: none; }

/* Texto de apoyo en la cabecera de una tarjeta: dice de qué habla el número o
   cómo se opera el lienzo. Nunca es un dato. */
.tf-ui .tf-dim { font-size: .72rem; color: var(--faint); }

.tf-ui .tf-check {
    display: inline-flex; align-items: center; gap: 6px;
    font-size: .76rem; font-weight: 600; color: var(--muted); cursor: pointer; white-space: nowrap;
}
.tf-ui .tf-check input { accent-color: var(--navy); width: 14px; height: 14px; cursor: pointer; }

/* ── El lienzo — la dispersión y el mapa ──────────────────────────────────
   Un fondo apenas más oscuro que la tarjeta para que el gráfico tenga borde sin
   dibujarle uno. `.lleno` es el del mapa, que ocupa lo que le queda a la
   tarjeta: se fue con esto la última cadena de alto propia del módulo —el mapa
   tenía `calc(100vh - 240px)`, un número mágico que había que corregir a mano
   cada vez que la barra superior cambiaba de alto, y que nadie corrigió. */
.tf-ui .tf-lienzo { background: var(--canvas); overflow: hidden; }
.tf-ui .tf-lienzo svg { display: block; width: 100%; height: 100%; }
.tf-ui .tf-vista .tf-lienzo { flex: 1 1 auto; min-height: 260px; }
.tf-ui .tf-lienzo.lleno { min-height: 320px; }

/* Acá vivía `.tf-mapa-dest`, el mapa de la ficha del destino. Se fue con él:
   se montaba en CADA consulta y en cada apertura del modal —dos CDN, más una
   tanda de baldosas— para dibujar la polilínea que ya estaba guardada en la
   localidad. El mapa del Padrón sigue vivo y sus reglas están más arriba; ése
   es a pedido, y por eso se queda. */

/* La leyenda de la dispersión. Los puntos son cuadraditos del color del ESTADO;
   el que se aparta del modelo no tiene color propio, tiene un anillo. */
.tf-ui .tf-leyenda { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.tf-ui .tf-pt { display: inline-flex; align-items: center; gap: 5px; font-size: .72rem; color: var(--muted); }
.tf-ui .tf-pt::before {
    content: ''; width: 9px; height: 9px; border-radius: 50%; background: currentColor; flex-shrink: 0;
}
.tf-ui .tf-pt.ok::before  { background: var(--ok); }
.tf-ui .tf-pt.wrn::before { background: var(--warn); }
.tf-ui .tf-pt.neg::before { background: var(--crit); }
.tf-ui .tf-pt.mut::before { background: var(--faint); }
.tf-ui .tf-pt.anillo::before {
    background: transparent; border: 1.8px solid var(--navy); width: 11px; height: 11px;
}
.tf-ui .tf-pt.recta::before {
    width: 16px; height: 0; border-radius: 0; border-top: 2px dashed var(--navy); background: none;
}

/* ── Mantenimiento — las dos masivas ──────────────────────────────────────
   Dos columnas iguales: son dos operaciones del mismo rango y ninguna es la
   principal. Cada una dice qué hace, a cuántos documentos alcanza HOY y cuánto
   tarda, antes del botón — que además lleva el número adentro. */
/* `auto-fit` y no un número fijo de columnas: eran dos operaciones, con el
   trazado de rutas son tres, y una grilla de `1fr 1fr` habría dejado la tercera
   sola en una segunda fila ocupando media pantalla. Con `minmax` las tres entran
   en fila cuando hay lugar y se apilan solas cuando no, sin que el número de
   operaciones esté escrito en el CSS — la próxima no va a tener que acordarse. */
.tf-ui .tf-mant {
    display: grid; gap: 18px;
    grid-template-columns: repeat(auto-fit, minmax(270px, 1fr));
}
/* Los botones van SECUNDARIOS y al pie. Secundarios porque el Padrón
   decidió no tener acción principal —se mira, no se hace— y dos bloques navy
   macizos acá le ganarían a los dos KPIs teñidos, que son el único dato que
   pide algo; el peligro de estas dos operaciones se comunica en el diálogo, con
   `tono:'critico'`, no pintando el botón que lo abre. Y al pie con `margin-top:
   auto` porque las bajadas tienen largos distintos: sin eso los botones quedan
   a dos alturas y las columnas se leen como dos cosas de distinto rango. Las
   dos las mostró la foto. */
.tf-ui .tf-mant > div { display: flex; flex-direction: column; gap: 6px; align-items: flex-start; }
.tf-ui .tf-mant > div > .tf-btn { margin-top: auto; }
.tf-ui .tf-mant b { font-size: .84rem; color: var(--navy); }
.tf-ui .tf-mant p { margin: 0; font-size: .77rem; color: var(--muted); line-height: 1.5; max-width: 56ch; }
.tf-ui .tf-mant p.tf-dim { color: var(--faint); }
.tf-ui .tf-mant .tf-btn { margin-top: 4px; }
@media (max-width: 900px) { .tf-ui .tf-mant { grid-template-columns: 1fr; } }

.tf-ui .tf-prog {
    height: 6px; border-radius: 99px; background: var(--hair); overflow: hidden; margin: 12px 0 6px;
}
.tf-ui .tf-prog-b { height: 100%; background: var(--navy); transition: width .2s var(--curva); }

/* ═══════════════════════════════════════════════════════════════════════════
   LA FICHA DEL DESTINO — etapas 3 y 4 (identidad) · etapa 2 del rediseño
   ---------------------------------------------------------------------------
   Salió del mockup B1 con dos columnas, y desde el rediseño del Destino es UN
   HILO VERTICAL —el mockup B, «un solo hilo»—. Cinco zonas apiladas, cada una
   al ancho completo:

     · la CABECERA, con el nombre del destino en grande y las dos señales;
     · la BANDA DE RESPUESTA, teñida de navy, con la cifra que se cobra;
     · el precio COMERCIAL, que muere en la etapa 4 del rediseño y deja el lugar
       a la franja del precio sugerido;
     · la TIRA de salud, un renglón con todos los «contra qué» del número;
     · el HILO: la cascada y la tabla de transportistas, a todo el ancho.

   Sigue sin haber un solo token propio: la banda usa `--navy-050`, que es el
   mismo fondo teñido del KPI activo y del hover de la tabla.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── Cabecera de la ficha ──────────────────────────────────────────────────
   Alineada abajo: el nombre y su bajada tienen alturas distintas y lo que
   tiene que quedar a la misma línea es la base del texto, no el tope. */
.tf-ui .tf-ficha-h {
    display: flex; align-items: flex-end; gap: 16px; flex-wrap: wrap;
    padding: 14px var(--gutter) 13px;
    background: var(--surface); border-bottom: 1px solid var(--hair);
}
.tf-ui .tf-ficha-h h2 {
    margin: 0; font-size: 1.34rem; font-weight: 800;
    color: var(--navy); letter-spacing: -.015em;
}
.tf-ui .tf-ficha-h .sub { font-size: .78rem; color: var(--muted); }
.tf-ui .tf-ficha-sig { margin-left: auto; display: flex; gap: 8px; flex-wrap: wrap; }

/* Lo que FALTA en la bajada —sin zona, sin km— se dice en ámbar y no en rojo:
   no está roto, está incompleto, y las dos cosas se arreglan desde el Padrón.
   Es la misma distinción que hace `sin-tarifa` en TF_ESTADO. */
.tf-ui .tf-falta { color: var(--warn); font-weight: 700; }

/* ── La banda de respuesta ─────────────────────────────────────────────────
   Teñida, no maciza. Un bloque navy relleno del ancho de la pantalla con una
   cifra blanca adentro se lee como una cabecera de sistema y le gana a todo lo
   que viene abajo — incluido el ámbar del grupo comercial vencido, que es
   justo lo que trae al operador a esta pantalla. */
.tf-ui .tf-resp {
    display: flex; align-items: center; gap: 20px; flex-wrap: wrap;
    padding: 16px var(--gutter);
    background: var(--navy-050); border-bottom: 1px solid var(--hair);
}
.tf-ui .tf-resp .cifra {
    font-size: 2.1rem; font-weight: 800; color: var(--navy);
    letter-spacing: -.03em; font-variant-numeric: tabular-nums; line-height: 1;
}
/* LA RAYA. Cuando no hay tarifa exacta el sistema no completa nada, y eso no se
   dibuja con un cero ni con el número del escalón siguiente: se dibuja con una
   raya tenue. Un número grande arriba de todo se cobra. */
.tf-ui .tf-resp .cifra.nada { color: var(--hair-strong); }
.tf-ui .tf-resp .k {
    font-size: .6rem; font-weight: 700; text-transform: uppercase;
    letter-spacing: .9px; color: var(--navy-500);
}
.tf-ui .tf-resp .u { font-size: .72rem; color: var(--navy-500); font-weight: 700; }
.tf-ui .tf-resp .corte { width: 1px; align-self: stretch; background: var(--hair-strong); }
.tf-ui .tf-resp .dato { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.tf-ui .tf-resp .dato .d { font-size: .84rem; font-weight: 700; color: var(--ink); }
.tf-ui .tf-resp .dato .x { font-size: .74rem; color: var(--muted); }

/* ── La franja del precio sugerido ─────────────────────────────────────────
   Etapa 4. Va PEGADA a la banda de arriba y con la misma anatomía —cifra
   grande, cortes de 1 px, datos de dos renglones, la acción al final— porque es
   la contracara del mismo viaje: navy es lo que PAGAMOS, verde es lo que
   COBRAMOS. Dos componentes distintos ahí romperían el par.

   Teñida y no maciza, por el mismo motivo que la navy: un bloque verde relleno
   del ancho de la pantalla con una cifra blanca adentro se lee como un cartel
   de «todo bien» y le gana a todo lo que viene abajo, incluido el aviso de que
   la tarifa cargada está por debajo de lo que se paga.

   Acá vivía la tarjeta del grupo comercial, adentro del hilo y con su versalita.
   El reemplazo es en el MISMO lugar: lo único que cambió de forma es que una
   tarjeta con marco pasó a ser una franja al ancho. */
.tf-ui .tf-cot {
    display: flex; align-items: center; gap: 18px; flex-wrap: wrap;
    padding: 14px var(--gutter);
    background: var(--ok-050); border-bottom: 1px solid var(--ok-hair);
}
.tf-ui .tf-cot .cifra {
    font-size: 1.8rem; font-weight: 800; color: var(--ok);
    letter-spacing: -.03em; font-variant-numeric: tabular-nums; line-height: 1;
}
/* LA RAYA, igual que en la banda: 13 de los 120 destinos con viajes no tienen
   con qué calcular un precio, y ahí no va un número aproximado. El sistema no
   inventa un precio para cotizarle a un cliente. */
.tf-ui .tf-cot .cifra.nada { color: var(--ok-hair); }
.tf-ui .tf-cot .k {
    font-size: .6rem; font-weight: 700; text-transform: uppercase;
    letter-spacing: .9px; color: var(--ok);
}
.tf-ui .tf-cot .u { font-size: .72rem; color: var(--ok); font-weight: 700; }
.tf-ui .tf-cot .corte { width: 1px; align-self: stretch; background: var(--ok-hair); }
.tf-ui .tf-cot .dato { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.tf-ui .tf-cot .dato .d { font-size: .84rem; font-weight: 700; color: var(--ink); }
.tf-ui .tf-cot .dato .x { font-size: .74rem; color: var(--muted); }
/* La segunda cifra —el precio con cláusula de ajuste— es un PRECIO, no un dato
   más: se cotiza tal cual. Sube de tamaño sin llegar a competir con el firme. */
.tf-ui .tf-cot .dato .d .mas { font-size: 1.05rem; color: var(--ok); }
/* Cuando el sugerido se va muy por encima de lo que pagamos de verdad. No es un
   error —es cobertura— pero en azúcar el flete no lleva margen, así que la
   cifra tiene que poder verse antes de mandar el precio. */
.tf-ui .tf-cot .dato .d .ojo { color: var(--warn); }
.tf-ui .tf-cot-btn { background: var(--ok); border-color: var(--ok); }
.tf-ui .tf-cot-btn:hover { background: #0c6544; border-color: #0c6544; }

/* El pie: sobre qué se paró el precio, y qué quedó afuera. Va sobre papel y no
   sobre el verde para que se lea como la letra chica que es. */
.tf-ui .tf-cot-pie {
    display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
    padding: 8px var(--gutter);
    background: var(--surface); border-bottom: 1px solid var(--hair);
    font-size: .76rem; color: var(--muted); line-height: 1.5;
}
.tf-ui .tf-cot-pie b { color: var(--ink); font-weight: 700; }
/* Los avisos del pie mandan a arreglar algo en otra pantalla: un movimiento mal
   cargado, una tarifa que quedó por debajo de lo que se paga. Ámbar, que es
   «esto hay que mirarlo», y no rojo: la ficha se dibujó entera igual. */
.tf-ui .tf-cot-pie .tf-cot-ojo { color: var(--warn); font-weight: 600; }
.tf-ui .tf-cot-pie .tf-cot-ojo b { color: var(--warn); }

/* El histograma de los viajes. Nueve barras dibujadas con `<i>`: un canvas para
   nueve rectángulos sería traer una dependencia para no usarla. Lo que está a
   la derecha del corte son los viajes que el precio NO cubre. */
.tf-ui .tf-hist { display: inline-flex; align-items: flex-end; gap: 3px; height: 30px; }
/* En el pie es letra chica y no puede empujar el renglón: la mitad de alto y
   pegado al texto que describe la misma muestra. */
.tf-ui .tf-cot-pie .tf-hist { height: 16px; vertical-align: -3px; }
.tf-ui .tf-cot-pie .tf-hist i { width: 8px; }
.tf-ui .tf-hist i { display: block; width: 11px; background: var(--navy-300); border-radius: 1px 1px 0 0; }
.tf-ui .tf-hist i.corte { background: var(--ok); }
.tf-ui .tf-hist i.sobre { background: var(--warn); }

/* ── La cascada — escalones ────────────────────────────────────────────────
   El que contestó lleva la barra navy al margen y recupera el color de tinta;
   los otros se leen en gris. NO son un error y por eso no llevan ni ámbar ni
   rojo: son lo que HABRÍA pasado, que es información y no un problema. */
.tf-ui .tf-casc > div {
    display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap;
    padding: 9px 16px; border-bottom: 1px solid #edeef2; font-size: .79rem;
}
.tf-ui .tf-casc > div:last-child { border-bottom: 0; }
.tf-ui .tf-casc .paso {
    font-size: .6rem; font-weight: 700; text-transform: uppercase;
    letter-spacing: .8px; color: var(--faint); min-width: 92px;
}
.tf-ui .tf-casc .q { color: var(--muted); flex: 1; min-width: 140px; }
.tf-ui .tf-casc .p {
    font-weight: 700; color: var(--muted); font-variant-numeric: tabular-nums;
    margin-left: auto; white-space: nowrap;
}
/* El escalón sin respuesta apaga el precio pero NO el motivo: el renglón existe
   para decir por qué está vacío. */
.tf-ui .tf-casc > div.vacio .q { color: var(--faint); }
.tf-ui .tf-casc > div.usado { background: var(--navy-050); box-shadow: inset 3px 0 0 var(--navy); }
.tf-ui .tf-casc > div.usado .paso { color: var(--navy); }
.tf-ui .tf-casc > div.usado .q { color: var(--ink); }
.tf-ui .tf-casc > div.usado .p { color: var(--navy); }

/* ── EL HILO ───────────────────────────────────────────────────────────────
   Etapa 2. Acá vivían las dos columnas (`.tf-2col`, una grilla de 1,25 / 0,75).
   No se fueron por gusto: `fichaHTML()` es UNA función que dibuja TRES marcos
   —la pantalla, el modal que abren ventas-pendientes y el cotizador, y el
   mobile que todavía no existe—, y dos columnas adentro de un diálogo angosto
   ya venían apretadas. Una sola columna sirve a los tres con una sola maqueta.

   El hilo se lee de arriba abajo en el orden en que se pregunta: cuánto
   pagamos (la banda) → cuánto se cobra (la comercial, que muere en la etapa 4)
   → qué tan sano está el número (la tira) → por qué ése (la cascada) → quién
   más llega (la tabla).

   El `max-width` no es un capricho de composición: un renglón de texto de
   1.400 px no se lee, y las tarjetas de acá adentro son texto —cuatro
   escalones con su explicación al lado—. Lo que necesita el ancho de verdad es
   la tabla, y entra cómoda en 1.000. */
.tf-ui .tf-hilo { padding: 14px var(--gutter); max-width: 1000px; }
.tf-ui .tf-hilo > .tf-card:last-child { margin-bottom: 0; }

/* El título de cada bloque del hilo, AFUERA de la tarjeta. Es una versalita con
   una línea que la acompaña hasta el margen, no un `<h4>` adentro de un
   `.tf-card-h`: sin las columnas, dos tarjetas seguidas con su franja gris
   arriba se leen como dos cajas apiladas, y el hilo tiene que leerse como un
   texto con subtítulos. Lo que iba a la derecha del `.tf-card-h` (un badge con
   el conteo) entra antes de la línea, que es lo que hace el `::after`. */
.tf-ui .tf-hilo-h {
    display: flex; align-items: center; gap: 10px;
    font-size: .64rem; font-weight: 800; letter-spacing: .09em; text-transform: uppercase;
    color: var(--faint); padding: 2px 0 8px;
}
.tf-ui .tf-hilo-h::after { content: ""; flex: 1; height: 1px; background: var(--hair-strong); }

/* ── La tira de salud ──────────────────────────────────────────────────────
   Lo que era la tarjeta «El número, auditado»: siete renglones de rótulo a la
   izquierda y dato a la derecha, apilados en una columna angosta. Acostados en
   un solo renglón al ancho de la pantalla se comparan de un vistazo, que es
   para lo que existen — ninguno se lee solo, todos son «este número contra
   aquél».

   Va SIN fondo teñido y con la misma anatomía que la banda (`.k` arriba, dato
   abajo, corte de 1 px entre celdas) para que se lea como su continuación y no
   como otro componente. Teñirla sería ponerle a un juicio sobre el número el
   mismo peso visual que a la cifra que se cobra. */
.tf-ui .tf-tira {
    display: flex; flex-wrap: wrap; gap: 0;
    padding: 0 var(--gutter);
    background: var(--surface); border-bottom: 1px solid var(--hair);
}
.tf-ui .tf-tira > div {
    display: flex; flex-direction: column; gap: 1px; min-width: 0;
    padding: 9px 18px 9px 0; margin-right: 18px; border-right: 1px solid var(--hair);
}
.tf-ui .tf-tira > div:last-child { border-right: 0; margin-right: 0; padding-right: 0; }
.tf-ui .tf-tira .k {
    font-size: .6rem; font-weight: 700; text-transform: uppercase;
    letter-spacing: .9px; color: var(--faint); white-space: nowrap;
}
.tf-ui .tf-tira .v {
    font-size: .84rem; font-weight: 700; color: var(--ink);
    font-variant-numeric: tabular-nums; white-space: nowrap;
}
/* Los avisos que una celda de dos renglones no puede decir: que faltan las
   coordenadas y cómo se arreglan, que el km cargado no coincide con el punto,
   que este destino sólo tiene tarifas por viaje. Iban como `<p>` adentro de la
   tarjeta y no se pueden perder: casi siempre señalan la acción que destraba
   el dato. */
.tf-ui .tf-tira-pie {
    display: flex; align-items: center; justify-content: space-between; gap: 14px;
    flex-wrap: wrap; padding: 8px var(--gutter);
    background: var(--surface); border-bottom: 1px solid var(--hair);
    font-size: .76rem; color: var(--muted); line-height: 1.5;
}
.tf-ui .tf-tira-pie b { color: var(--ink); font-weight: 700; }

/* Acá estaba `.tf-grupo`, el nombre del grupo comercial. Se fue con la tarjeta
   que lo dibujaba (etapa 4): no lo usa nadie más, y una clase huérfana en la
   hoja hace creer que el componente sigue existiendo. */
.tf-ui .tf-nota { margin: 0; font-size: .76rem; color: var(--muted); line-height: 1.5; }
/* Lo destacado de una nota es TINTA, no color: la nota ya está en gris y un
   segundo color adentro la convierte en un aviso. El único que sí tiñe es
   `.tf-falta`, que es el ámbar de «incompleto». */
.tf-ui .tf-nota b { color: var(--ink); font-weight: 700; }

/* Los renglones de «salud»: rótulo a la izquierda, dato a la derecha, alineados
   a una sola columna invisible para poder correr el ojo de arriba abajo.

   NACIERON EN LA FICHA DEL DESTINO Y HOY SON DE COTIZACIÓN. La ficha los acostó
   en la tira (`.tf-tira`) cuando perdió las dos columnas; el modal de Cotización
   los sigue usando para la referencia de costos, que es exactamente el caso para
   el que se diseñaron: una columna angosta, cuatro renglones, se leen de a uno.
   Con ellos se fue `.tf-sal-sep`, el corte entre «Contra los demás» y «Dónde
   queda», que no tenía ningún otro consumidor. */
.tf-ui .tf-sal {
    display: flex; justify-content: space-between; align-items: center; gap: 10px;
    font-size: .78rem; color: var(--muted); padding: 4px 0;
}
.tf-ui .tf-sal + .tf-sal { border-top: 1px solid #f1f3f6; }
.tf-ui .tf-sal b { color: var(--ink); font-weight: 700; font-variant-numeric: tabular-nums; }

/* Un enlace de pie de tarjeta. Es un <button> porque NAVEGA adentro de la
   aplicación y no tiene URL propia: un <a href="#"> ahí ofrece «abrir en otra
   pestaña» y lo que abre es la pantalla equivocada. */
.tf-ui .tf-enlace {
    background: none; border: 0; padding: 0; font: inherit;
    font-size: .76rem; font-weight: 700; color: var(--navy-500); cursor: pointer;
    transition: color .14s var(--curva);
}
.tf-ui .tf-enlace:hover { color: var(--navy); text-decoration: underline; }

/* ═══════════════════════════════════════════════════════════════════════════
   EL MODAL DEL DESTINO — etapa 3b
   ---------------------------------------------------------------------------
   La MISMA ficha de arriba, adentro de un diálogo. No hay ni una regla que
   redibuje la ficha acá: lo único que este bloque aporta es el marco (velo,
   panel, cabecera, pie) y el botón «usar», que en la pantalla no existe porque
   ahí no hay ningún campo esperando un número.

   EL SCOPE VA EN EL PROPIO ELEMENTO. El diálogo se cuelga de `document.body` y
   no de `#page-tarifario`, así que lleva `class="tf-ui tf-modal"` —las dos
   clases juntas— y todas las reglas van SIN espacio. Es la trampa §7 del
   manual, y acá es la única forma posible: si el scope fuera de un ancestro, un
   modal colgado del body no lo tendría.

   Y por eso el `tarifario-ui.css` se carga en el `index.html` con los demás y no
   con el módulo: lo abren desde Ventas y desde el cotizador, o sea que en el
   caso NORMAL el Tarifario no se abrió nunca en esa sesión.
   ═══════════════════════════════════════════════════════════════════════════ */
/* Z-INDEX 220, Y EL NUMERO IMPORTA. Este dialogo se abre SIEMPRE encima de otra
   cosa: del Tarifario, del cotizador, o —el caso que lo destapo el 24/08/2026—
   del boton «Analizar destino» que vive DENTRO de la ficha de un pedido. Esa
   ficha es `.vt-ui.vt-ficha-velo`, que esta en z-index 200: con 180 el analisis
   se dibujaba por DEBAJO y quedaba invisible, tapado por la ficha que lo habia
   abierto. La escalera del sistema es: 200 los modales de modulo, 240 el
   dialogo de `dialogos.css` (aviso/confirmar), 260 los toasts. 220 lo pone
   arriba de cualquier modal de modulo y DEBAJO del dialogo del sistema, que es
   donde tiene que estar: si el analisis pregunta algo, la pregunta va arriba. */
.tf-ui.tf-modal {
    position: fixed; inset: 0; z-index: 220;
    display: flex; align-items: center; justify-content: center; padding: 22px;
    background: rgba(16, 32, 64, .52); backdrop-filter: blur(2px);
}
/* Se crea y se destruye, no se esconde — pero la regla va igual: si mañana
   alguien lo esconde en vez de removerlo, `.hidden` tiene una clase y
   `.tf-ui.tf-modal` tiene dos, y el diálogo se quedaría tapando la pantalla sin
   que nada avise. Es el bug que le costó el portal entero. */
.tf-ui.tf-modal.hidden { display: none; }

.tf-ui .tf-modal-p {
    display: flex; flex-direction: column;
    width: 100%; max-width: 1180px; max-height: 100%;
    background: var(--canvas); border-radius: var(--r-card);
    box-shadow: 0 18px 50px rgba(10, 25, 60, .34);
    overflow: hidden;
    animation: tfModalEntra .16s var(--curva);
}
@keyframes tfModalEntra { from { opacity: 0; transform: translateY(10px); } }

.tf-ui .tf-modal-h {
    display: flex; align-items: center; gap: 14px; flex-shrink: 0;
    padding: 12px var(--gutter);
    background: var(--surface); border-bottom: 1px solid var(--hair);
}
.tf-ui .tf-modal-h h3 { margin: 0; font-size: .95rem; font-weight: 800; color: var(--navy); }
.tf-ui .tf-modal-h .sub { font-size: .76rem; color: var(--muted); }
.tf-ui .tf-modal-h .tf-ico { margin-left: auto; }

/* EL SCROLL ES DEL CUERPO, no del panel: la cabecera dice a qué destino y a qué
   cliente pertenece lo que se está mirando, y el pie explica qué pasa al elegir
   un precio. Las dos cosas tienen que seguir a la vista cuando la ficha es
   larga — que es siempre, porque la ficha tiene cinco tarjetas. */
.tf-ui .tf-modal-b { flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain; }
.tf-ui .tf-modal-f {
    display: flex; align-items: center; justify-content: space-between; gap: 14px;
    flex-wrap: wrap; flex-shrink: 0;
    padding: 10px var(--gutter);
    background: var(--surface); border-top: 1px solid var(--hair);
}

/* La cabecera de la ficha no necesita su propio borde adentro del modal: ya lo
   pone la cabecera del diálogo, justo arriba. Dos filetes pegados se leen como
   una franja gruesa. */
.tf-ui .tf-modal-b > .tf-ficha-h { border-bottom: 0; padding-bottom: 6px; }

/* ── «Usar este precio» — el verbo del marco modal ─────────────────────────
   Es la acción PRINCIPAL del diálogo, así que va navy: el que abrió esto vino a
   elegir un número. En la pantalla el mismo lugar lo ocupa «Actualizar», que es
   otro trabajo. El apagado dice por qué en su `title` —una tarifa por viaje en
   un campo por tonelada, o una compra en una venta— y no se puede apretar. */
.tf-ui .tf-usar:disabled, .tf-ui .tf-ico:disabled { opacity: .4; cursor: not-allowed; }
.tf-ui .tf-ico:disabled:hover { background: none; color: var(--faint); transform: none; }
.tf-ui .tf-ico.usar { color: var(--navy-500); }
.tf-ui .tf-casc > div > .tf-ico { margin-left: 4px; align-self: center; }

/* ═══════════════════════════════════════════════════════════════════════════
   COTIZACIÓN — etapa 4a
   ---------------------------------------------------------------------------
   Sale del mockup A, «la lista de precios»: la pantalla es una tabla de grupos
   y no estrena ni un componente. Lo único nuevo vive adentro del MODAL, que es
   el primer formulario del módulo — hasta acá el Tarifario sólo mostraba.

   Tres piezas, y ninguna es decorativa:

     · `.tf-campos`  la grilla de 12 columnas del formulario;
     · `.tf-chip`    una localidad elegida, con su cruz para sacarla;
     · `.tf-lista`   filas cortas de lectura: los resultados del buscador y la
                     referencia de lo que pagamos.

   Sigue sin haber un solo token propio.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── La grilla del formulario ──────────────────────────────────────────────
   Doce columnas porque los campos tienen anchos naturales distintos: un precio
   necesita cinco dígitos y un nombre necesita leerse. A 620 px se apila todo —
   un campo de tres columnas en un teléfono es un campo de cuatro caracteres. */
.tf-ui .tf-campos { display: grid; grid-template-columns: repeat(12, minmax(0,1fr)); gap: 12px; }
.tf-ui .tf-campos .tf-f { gap: 5px; }
.tf-ui .tf-campos .tf-in { width: 100%; box-sizing: border-box; }
/* LAS DOCE, aunque hoy se usen cinco. Faltaba `.c6` y el formulario de tarifa lo
   usaba: los dos `<select>` de «Tipo de servicio» y «Modalidad» cayeron a una
   columna de doce —cuarenta píxeles— con el rótulo partido en tres renglones.
   Ninguna aserción se cayó: la clase estaba puesta en el marcado, sólo que no
   existía. Lo mostró la foto. Escribir una clase de grilla que no está definida
   no da error en ningún lado, así que el arreglo no es agregar la que faltó: es
   que no pueda volver a faltar ninguna. */
.tf-ui .c1  { grid-column: span 1; }
.tf-ui .c2  { grid-column: span 2; }
.tf-ui .c3  { grid-column: span 3; }
.tf-ui .c4  { grid-column: span 4; }
.tf-ui .c5  { grid-column: span 5; }
.tf-ui .c6  { grid-column: span 6; }
.tf-ui .c7  { grid-column: span 7; }
.tf-ui .c8  { grid-column: span 8; }
.tf-ui .c9  { grid-column: span 9; }
.tf-ui .c10 { grid-column: span 10; }
.tf-ui .c11 { grid-column: span 11; }
.tf-ui .c12 { grid-column: span 12; }
/* En un teléfono no hay doce columnas: un campo de tres es un campo de cuatro
   caracteres. Va sobre TODOS los hijos directos y no sobre una lista de clases,
   por lo mismo de arriba — una lista se olvida de una. */
@media (max-width: 620px) {
    .tf-ui .tf-campos > * { grid-column: span 12; }
}

/* ── Las pastillas de localidad ────────────────────────────────────────────
   Teñidas y no macizas: son el CONTENIDO del grupo, no una acción. La cruz vive
   adentro de la pastilla y no al lado, o dos pastillas seguidas se leen como
   una sola con dos cruces.

   El nombre no se trunca. Una pastilla que dice «San Miguel de Tuc…» obliga a
   apuntarla para saber cuál es, y son justo los nombres largos los que se
   confunden entre sí. Prefiere ocupar dos renglones. */
.tf-ui .tf-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
.tf-ui .tf-chip {
    display: inline-flex; align-items: center; gap: 4px;
    font-size: .74rem; font-weight: 700; color: var(--navy);
    background: var(--navy-050); border: 1px solid #d7e2f4;
    border-radius: 99px; padding: 3px 4px 3px 11px;
}
.tf-ui .tf-chip .tf-dim { font-weight: 600; }
.tf-ui .tf-chip-x {
    display: inline-flex; align-items: center; justify-content: center;
    width: 20px; height: 20px; border: 0; border-radius: 50%;
    background: none; color: var(--navy-500); cursor: pointer; padding: 0;
    transition: background .14s var(--curva), color .14s var(--curva);
}
.tf-ui .tf-chip-x .material-symbols-outlined { font-size: 15px; }
.tf-ui .tf-chip-x:hover { background: var(--crit-050); color: var(--crit); }

/* ── Filas cortas de lectura ───────────────────────────────────────────────
   No es una `<table>`: son renglones de una sola línea con el dato de plata
   pegado a la derecha. Cuando el renglón es apretable —los resultados del
   buscador— es un <button> de ancho completo, así que el objetivo es la fila
   entera y no el texto. */
.tf-ui .tf-lista {
    background: var(--surface); border: 1px solid var(--hair);
    border-radius: 9px; overflow: hidden; margin-top: 10px;
}
.tf-ui .tf-lista .tf-lista-i {
    display: flex; align-items: center; gap: 9px; width: 100%;
    padding: 7px 11px; border: 0; border-bottom: 1px solid #f1f3f6;
    background: none; font: inherit; font-size: .78rem; color: var(--ink);
    text-align: left; box-sizing: border-box;
}
.tf-ui .tf-lista .tf-lista-i:last-child { border-bottom: 0; }
.tf-ui .tf-lista button.tf-lista-i { cursor: pointer; transition: background .12s var(--curva); }
.tf-ui .tf-lista button.tf-lista-i:hover { background: var(--navy-050); }
.tf-ui .tf-lista .tf-lista-i .nm { font-weight: 700; }
.tf-ui .tf-lista .tf-lista-i .pr {
    margin-left: auto; font-weight: 700; color: var(--muted);
    font-variant-numeric: tabular-nums; white-space: nowrap;
}

/* ── El selector de localidad — botón + desplegable con buscador ───────────
   Reemplaza al campo de texto del smart-selector en la barra del Destino
   (pedido de Hernán, 02/08/2026: el molde es el selector de lote). Cerrado es
   un BOTÓN y no un input, y eso es la mitad del arreglo: un campo de texto
   vacío dice «escribí lo que ya sabés», un botón dice «acá hay una lista».

   El botón vive adentro de `.tf-f`, así que se alinea solo con los otros
   campos de la barra; lo que copia del `.tf-in` es el alto y el borde, para
   que la fila no quede con dos alturas. */
.tf-ui .tf-sel {
    display: flex; align-items: center; gap: 8px; width: 100%;
    padding: 8px 10px; border: 1px solid var(--hair-strong); border-radius: 8px;
    background: #fff; color: var(--faint); font: inherit; font-size: .8rem;
    cursor: pointer; text-align: left; box-sizing: border-box;
    transition: border-color .14s var(--curva), box-shadow .14s var(--curva);
}
.tf-ui .tf-sel:hover { border-color: var(--navy-300); }
.tf-ui .tf-sel[aria-expanded="true"] {
    border-color: var(--navy); box-shadow: 0 0 0 3px var(--navy-050);
}
.tf-ui .tf-sel > .material-symbols-outlined { font-size: 18px; color: var(--faint); }
.tf-ui .tf-sel .txt {
    flex: 1; min-width: 0; color: var(--ink); font-weight: 700;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.tf-ui .tf-sel .txt.vacio { color: var(--faint); font-weight: 400; }
/* La provincia al lado del nombre, en gris: hay «Merlo» en dos provincias y el
   botón cerrado es lo único que se ve. */
.tf-ui .tf-sel .txt .p { color: var(--faint); font-weight: 600; margin-left: 6px; }
.tf-ui .tf-sel .caret { transition: transform .16s var(--curva); }
.tf-ui .tf-sel[aria-expanded="true"] .caret { transform: rotate(180deg); }
/* Elegido: el mismo verde que usaba el smart-selector para decir «esto quedó
   puesto», que es lo único de aquel componente que valía la pena conservar. */
.tf-ui .tf-sel.puesto { border-color: var(--ok); background: var(--ok-050); }
.tf-ui .tf-sel.puesto > .material-symbols-outlined { color: var(--ok); }
.tf-ui .tf-sel-x {
    font-size: 17px; color: var(--faint); border-radius: 50%; padding: 1px;
    transition: background .14s var(--curva), color .14s var(--curva);
}
.tf-ui .tf-sel-x:hover { background: var(--crit-050); color: var(--crit); }

/* EL DESPLEGABLE CUELGA DE `<body>` Y NO DE LA BARRA, y no es una decisión de
   composición: es la lección del recorte. La barra de la pregunta vive adentro
   de contenedores con `overflow` propio, y ahí un hijo se corta contra el borde
   por más `z-index` que lleve —ya pasó en este sistema y estuvo en vivo—. Por
   eso estas reglas NO van bajo `.tf-ui`: el elemento no es descendiente de
   nadie. Los tokens salen de `:root`, así que se ven igual. */
.tf-locpop {
    position: fixed; z-index: 1200; display: flex; flex-direction: column;
    max-height: 340px; min-width: 300px;
    background: var(--surface); border: 1px solid var(--hair-strong);
    border-radius: 10px; box-shadow: 0 12px 34px rgba(10,25,60,.20);
    overflow: hidden; font-size: .8rem; color: var(--ink);
}
.tf-locpop.hidden { display: none; }
.tf-locpop .tf-locpop-b {
    display: flex; align-items: center; gap: 8px;
    padding: 9px 11px; border-bottom: 1px solid var(--hair); background: var(--canvas);
}
.tf-locpop .tf-locpop-b .material-symbols-outlined { font-size: 18px; color: var(--faint); }
.tf-locpop .tf-locpop-b input {
    flex: 1; min-width: 0; border: 0; outline: none; background: none;
    font: inherit; font-size: .8rem; color: var(--ink);
}
.tf-locpop .tf-locpop-l { flex: 1 1 auto; min-height: 0; overflow-y: auto; }
.tf-locpop .tf-locpop-i {
    display: flex; flex-direction: column; gap: 1px;
    padding: 7px 11px; border-bottom: 1px solid #f1f3f6; cursor: pointer;
}
.tf-locpop .tf-locpop-i:last-child { border-bottom: 0; }
.tf-locpop .tf-locpop-i .n { font-weight: 700; color: var(--ink); }
.tf-locpop .tf-locpop-i .s { font-size: .72rem; color: var(--faint); }
/* Lo que le FALTA a una localidad se dice en la lista, antes de elegirla: sin
   km no hay modelo y sin zona no hay cascada zonal, y las dos cosas explican
   media ficha vacía. Ámbar, como en la bajada de la cabecera. */
.tf-locpop .tf-locpop-i .s .falta { color: var(--warn); font-weight: 700; }
/* La MARCADA es la del teclado y la del hover — una sola señal para los dos
   caminos, o la flecha y el mouse dibujarían dos filas distintas a la vez. */
.tf-locpop .tf-locpop-i.marcada { background: var(--navy-050); }
.tf-locpop .tf-locpop-i.elegida .n { color: var(--navy); }
.tf-locpop .tf-locpop-i.elegida .n::after {
    content: "  ✓"; color: var(--ok); font-weight: 800;
}
.tf-locpop .tf-locpop-vacio {
    padding: 22px 14px; text-align: center; color: var(--faint);
    font-size: .76rem; line-height: 1.5;
}
.tf-locpop .tf-locpop-p {
    padding: 6px 11px; border-top: 1px solid var(--hair); background: var(--canvas);
    font-size: .7rem; color: var(--faint);
}

/* ── Los destinos más próximos ─────────────────────────────────────────────
   Reemplazó a la cascada en la PANTALLA (el modal la conserva: ahí se aplica un
   precio y los escalones tienen botón). Es una tabla `.tf-g` más una primera
   columna que dice a qué distancia queda el vecino del destino que se está
   mirando — el dato que decide si la referencia sirve.

   La FILA ENTERA navega a esa ficha, así que el nombre va como enlace y el
   `cursor` es de la fila: media docena de píxeles de botón adentro de una
   tabla de referencia es un objetivo que nadie encuentra. */
.tf-ui table.tf-g tbody tr.tf-clic { cursor: pointer; }
.tf-ui .tf-prox-sep { font-weight: 700; color: var(--navy); font-variant-numeric: tabular-nums; }

/* El nombre de la localidad, en el Padrón, es el camino a su ficha. Hereda el
   tamaño de la celda —el `.tf-enlace` de pie de tarjeta es más chico— y se
   alinea al texto de la fila: un enlace que cambia de cuerpo adentro de una
   tabla desalinea la columna entera. */
.tf-ui table.tf-g .tf-enlace.nm {
    font-size: inherit; color: var(--navy); display: inline-block; text-align: left;
}

/* ═══════════════════════════════════════════════════════════════════════════
   MOVIMIENTO — el vocabulario es el de los módulos YA migrados
   ---------------------------------------------------------------------------
   Portado de `remitos-ui.css` y `logistica-ui.css`, NO del CSS del mockup: el
   mockup fija navegación y jerarquía, y ahí se termina su autoridad.

     · lo que NAVEGA en fila subraya, no se levanta
     · lo que se PRESIONA se levanta 2 px con sombra y se hunde al clic
     · lo que cambia de VISTA no se mueve: se ilumina la pastilla
   ═══════════════════════════════════════════════════════════════════════════ */
@media (prefers-reduced-motion: reduce) {
    .tf-ui *, .tf-ui *::before, .tf-ui *::after {
        animation-duration: .01ms !important;
        transition-duration: .01ms !important;
    }
    /* El destaque se conserva —color y fondo quedan—; lo que se va es el
       desplazamiento. Quien pidió menos movimiento no pidió menos señales. */
    .tf-ui .tf-btn:hover, .tf-ui .tf-btn:active,
    .tf-ui .tf-ico:hover, .tf-ui .tf-ico:active,
    .tf-ui .tf-secnav button:hover .material-symbols-outlined { transform: none !important; }
}
