/* GENERADO — no editar a mano. Origen: software/web/design-system/app/app.css
   Se rehace con: node app/scripts/tomar-app.js (vigila pruebas/app-css.js). */
/* Componentes de aplicación del design system de Musho (1.1, 26/09/2026; la 1.2 añade § 22-23; la 1.3, § 25-28;
   la 1.4, 27/09: anatomía de la fila, grupo de botones, vacío a la izquierda y § 33 página;
   la 1.5, 27/09: primario, foco, interruptor y pestaña neutros; el coral, solo peligro;
   la 1.6, 27/09: § 59-67 panel, menú, tooltip, árbol, barra de vista, desplegable,
   cabecera segmentada, detalle y propiedades editables; sidebar plegable; el coral
   también dice «aquí estás» — SPEC-DS-1-6.md;
   la 1.7, 28/09: ventana de unificación (Karri Saarinen y Brad Frost): la
   sidebar un escalón por debajo (--ds-app-texto-lateral) y un modificador por
   significado (reglas.md § 32 bis): --atencion → --aviso, --alerta → --error,
   --gris → --neutro; las viejas quedan UNA versión con ficha de obsoleta y se
   borran en la 1.8 (§ 0.12 c) — SPEC-DS-1-7.md;
   la 1.8.5, 28/09: caja táctil 40 en < 1056 para el chevron del árbol y la X
   del chip, dibujo 24/16 — SPEC-CRM3-ROJOS.md).
   Los usa el CRM desde la fase 0b de CRM 2 (software/crm/SPEC-CRM2-0B.md).
   Reglas de la casa:
   · Prefijo .ds- en todo: el CRM ya tiene .tarjeta, .fila, .etiqueta con
     otro estilo y las pantallas se migran una a una; sin prefijo se pisan.
     Modificadores con --, partes con __, activo con .es-activo o el ARIA.
   · Solo tokens de app/tokens.css. Lo que no tiene token se declara UNA vez
     aquí abajo como --ds-app-*, con su porqué. Lo vigila app/sonda-app.js.
   · Sin JS se ve bien: el activo lo pinta el servidor con la clase o el
     ARIA; app.js solo anima y abre/cierra.
   · Móvil primero: base < 720 · ≥ 720 tablet · ≥ 1056 PC; tope 1584
     (reglas.md § 33: SOLO esos breakpoints; lo mide sonda-app.js).
   Lo copia el CRM: node app/scripts/tomar-app.js (vigila pruebas/app-css.js). */

:root {
  /* Fuente: el token trae solo «Geist»; si la página no la carga, cae a
     la del sistema en vez de a Times. */
  --ds-app-fuente: var(--ds-typography-family-sans), system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  /* Medio paso (4 px): la escala de espacio empieza en 8 y los huecos
     entre icono y texto pequeño piden la mitad. */
  --ds-app-paso-medio: calc(var(--ds-spacing-2xs) / 2);
  /* 1.19.0 (§ 0.16, Yaz 02/10): aire por proximidad. Cinco escalones con
     ROL, alias de los que ya existen (no son valores nuevos): lo que va
     junto, cerca; el hueco ENTRE grupos ≥ 2× el de DENTRO. Toda regla de
     aire vertical usa el token del rol, no --ds-spacing-*. */
  --ds-aire-pieza: var(--ds-app-paso-medio);   /* 4 · dentro de una pieza pequeña */
  --ds-aire-parte: var(--ds-spacing-2xs);      /* 8 · partes de UNA cosa */
  --ds-aire-bloque: var(--ds-spacing-xs);      /* 16 · cosas distintas dentro de un bloque */
  --ds-aire-campo: var(--ds-spacing-sm);       /* 24 · campo → campo, último campo → pie */
  --ds-aire-seccion: var(--ds-spacing-md);     /* 32 · sección → sección */
  /* Tope de una línea de texto secundario (subtítulo, meta de tarjeta, sub de
     fila): ~62 caracteres. En una página ancha (1584) una frase suelta llegaba
     a 219 caracteres (banco del CRM, 27/09); Carbon pide 60-80. (1.5.1) */
  --ds-app-texto-max: 62ch;
  /* Tope de un PÁRRAFO de lectura dentro de una pieza (descripción de tarjeta,
     texto de un vacío): ~52 caracteres de ancho ≈ 70 reales. 62ch daba ~85
     (largo) y el 40ch a pelo del vacío ~55 (corto), y los dos párrafos de la
     misma tarjeta cortaban a 575 y 371 px sobre 944 (/kpis, Yaz 03/10). (1.20.1) */
  --ds-app-parrafo-max: 52ch;
  /* Paso y medio (12 px): relleno lateral de chips y etiquetas densas. */
  --ds-app-paso-12: calc(var(--ds-spacing-2xs) * 1.5);
  /* Línea fina de bordes y separadores. */
  --ds-app-linea: 1px;
  /* Grosor del contorno de foco y del subrayado activo de pestañas. */
  --ds-app-foco: 2px;
  /* Color del foco (1.5, reglas.md § 0.7): neutro, como el primario. El
     coral en una app es peligro o marca quieta; un anillo coral en cada
     tabulación decía «peligro» en todas partes. */
  --ds-app-foco-color: var(--ds-color-bg-inverse);
  /* Acción primaria neutra (1.5, § 0.7 y § 30.8, Carbon: rojo = error, el
     primario es neutro; Untitled en oscuro y Apple hacen lo mismo): el
     inverso del fondo. En oscuro, claro sobre oscuro; en el tema claro del
     CRM los inversos ya son oscuros. El hover mezcla hacia el fondo. */
  --ds-app-primario-fondo: var(--ds-color-bg-inverse);
  --ds-app-primario-texto: var(--ds-color-text-inverse);
  --ds-app-primario-hover: color-mix(in srgb, var(--ds-color-bg-inverse) 88%, var(--ds-color-bg));
  /* Alto del chip de filtro (App Store / MLS): más bajo que un control. */
  --ds-app-chip: 32px;
  /* Relleno interior del segmentado: el indicador «flota» 3 px dentro. */
  --ds-app-segmentado-relleno: 3px;
  /* 1.14 (§ 3, H6): marco del segmentado de página, el p-1 (4) del
     button-border de Untitled (untitled/medidas.md § Pestañas). La cabecera
     segmentada (§ 65) sigue con el relleno de 3. */
  --ds-app-segmentado-marco: 4px;
  /* Punto de color delante de la etiqueta de estado. */
  --ds-app-punto: 6px;
  /* Barra inferior del móvil: 56 px, la medida de Material/iOS para la zona
     del pulgar. Botón flotante: 48 (1.9, P47, el «máx. 48» de Asier). */
  --ds-app-barra: 56px;
  --ds-app-fab: 48px;
  /* Anchos máximos de hoja (en tablet/PC) y diálogo. */
  --ds-app-hoja-max: 480px;
  --ds-app-dialogo-max: 400px;
  /* Alto máximo de la hoja en móvil: deja ver que debajo sigue la página.
     1.15.1 (P87): en dvh, el alto VISIBLE; con vh, Safari de iPhone medía
     con las barras del navegador escondidas y la hoja quedaba cortada. */
  --ds-app-hoja-alto: 85dvh;
  /* Mínimo de cada métrica en la rejilla auto-fit. */
  --ds-app-metrica-min: 160px;
  /* Avatar normal (32); el grande usa --ds-spacing-control (40). */
  --ds-app-avatar: 32px;
  /* Icono del estado vacío: 40, más grande que cualquier icono de UI. */
  --ds-app-icono-vacio: 40px;
  /* Capas: barra y FAB sobre el contenido; los <dialog> van en la capa
     superior del navegador y no necesitan z-index. */
  --ds-app-capa-barra: 30;
  --ds-app-capa-fab: 31;
  /* Saltar al contenido (§ 27): por encima de la barra y del FAB al recibir foco. */
  --ds-app-capa-saltar: 40;

  /* 1.2 · piezas de documento y de vista de datos (§ 22-23). Medidas de
     notion/huecos.md, tablas A y B. Son geometría de UNA pieza, no tokens:
     en @theme serían 37 variables de ruido para Yaz y para Figma (Ari,
     26/09). La línea de 1 px es la --ds-app-linea de arriba. */
  /* Cita: raya de 3 px en tinta (tabla A). */
  --ds-app-raya: 3px;
  /* Chip: 20 px de alto; 18 dentro de tarjeta (regla 6). */
  --ds-app-chip-bd: 20px;
  --ds-app-chip-bd-tarjeta: 18px;
  /* Relleno lateral del chip: 6 px (regla 6, medido en medidas.md §8). */
  --ds-app-chip-bd-x: 6px;
  /* Título de página: icono de caja 78 px (tabla A). */
  --ds-app-icono-pagina: 78px;
  /* Migas: 44 px de alto, enlace máx. 160 px (tabla A). */
  --ds-app-migas: 44px;
  --ds-app-miga-max: 160px;
  /* Columnas: hueco de 46 px (tabla A). */
  --ds-app-columnas-hueco: 46px;
  /* Separador: bloque de 13 px con la línea en medio (tabla A). */
  --ds-app-separador: 13px;
  /* Interruptor (§ 24): hueco entre bola y pista = (24 - 20) / 2 menos el borde. */
  --ds-app-interruptor-hueco: calc((var(--ds-icon-size-lg) - var(--ds-icon-size)) / 2 - var(--ds-app-linea));
  /* Tabla simple: celda de 35 px, relleno 7/9 (tabla A). */
  --ds-app-celda-simple: 35px;
  --ds-app-celda-y: 7px;
  --ds-app-celda-x: 9px;
  /* Caja de código: relleno 24/22 (tabla A). */
  --ds-app-codigo-y: 24px;
  --ds-app-codigo-x: 22px;
  /* Pestañas de vista: barra 40 (tabla B). La píldora 32 y su relleno 6/12
     se retiraron en 1.8 (P41: subrayadas como la base). */
  --ds-app-vista-barra: 40px;
  /* Tabla de base: cabecera 36, fila 37 (45 a 390), grupo 42 (tabla B). */
  --ds-app-tabla-cabecera: 36px;
  --ds-app-tabla-fila: 37px;
  --ds-app-tabla-fila-movil: 45px;
  --ds-app-tabla-grupo: 42px;
  /* 1.14 (§ 16, § 70, H4-H5): el bg-secondary de Untitled (nuestro
     terciario): cabecera de tabla (el mismo gris que el hover de fila, como
     en Untitled) y chip de filtro aplicado. */
  --ds-app-fondo-secundario: var(--ds-color-bg-tertiary);
  /* 1.14 (§ 16, A3): tope de una celda que se recorta con «…» (un correo
     largo); el mismo ancho que el campo de búsqueda abierto. */
  --ds-app-tabla-recorte: 240px;
  /* Tablero: columna 276, tarjeta 260 (tabla B). */
  --ds-app-tablero-columna: 276px;
  --ds-app-tablero-tarjeta: 260px;
  /* Relleno de tarjeta 12/12 (1.19.0, § 0.16: antes 10/12, tabla B). */
  --ds-app-tarjeta-y: var(--ds-app-paso-12);
  --ds-app-tarjeta-x: var(--ds-app-paso-12);
  /* Galería: tarjeta de 296 px (tabla B). */
  --ds-app-galeria: 296px;
  /* Calendario: celda de 124 px de alto (tabla B). */
  --ds-app-calendario-celda: 124px;
  /* Cronograma: fila 36, barra 34 (tabla B). */
  --ds-app-cronograma-fila: 36px;
  --ds-app-cronograma-barra: 34px;
  /* Lista: fila de 30 px (tabla B). */
  --ds-app-lista-fila: 30px;
  /* Gráfico: 300 px de alto (tabla B). */
  --ds-app-grafico: 300px;
  /* Ficha: fila 34, etiqueta 160 (120 a 390), 37 hasta el cuerpo (tabla B). */
  --ds-app-prop-fila: 34px;
  /* Tarjeta de radio (§ 5, 1.8.4): ancho mínimo; así caben 2-3 por fila. */
  --ds-app-tarjeta-radio-min: 200px;
  --ds-app-prop-etiqueta: 160px;
  --ds-app-prop-etiqueta-movil: 120px;
  --ds-app-prop-cuerpo: 37px;
  /* Estado vacío: 144 px sin borde (tabla B). */
  --ds-app-vacio: 144px;
  /* Página ancha (§ 33, 1.4): el tope de Carbon «Max»; no hay token de
     rejilla con ese valor (--ds-grid-grid-max es 1440, el de la web). */
  --ds-app-pagina-max: 1584px;

  /* 1.6 (27/09/2026, SPEC-DS-1-6.md; piezas § 59-67). */
  /* D1 · el coral dice «aquí estás»: pestaña activa, ítem activo de la barra
     lateral y de la inferior, nodo activo del árbol. Posición no es acción
     (Stripe, Notion); el primario sigue neutro. Veto de Yaz = cambiar esta
     línea por var(--ds-app-foco-color). */
  --ds-app-activo-color: var(--ds-color-accent); /* marca */
  /* Panel lateral (§ 59): 480, el ancho del peek del CRM que Asier llamó
     «precioso» (30rem); 720 para fichas con dos columnas dentro. */
  --ds-app-panel-ancho: 480px;
  --ds-app-panel-ancho-grande: 720px;
  /* Tooltip (§ 61): una frase corta; más ancho ya no es una pista. */
  --ds-app-tooltip-max: 240px;
  /* Árbol (§ 62): 12 por nivel (3C del CRM: con 16 el tercer nivel no
     dejaba sitio al título en una barra de 256); fila 40, 32 en compacto. */
  --ds-app-arbol-sangria: 12px;
  --ds-app-arbol-fila: 40px;
  --ds-app-arbol-fila-compacta: 32px;
  /* 1.18.3 (Yaz 02/10, «como Google Docs»): el árbol que cuelga de un destino
     de la barra (Documentos) entra un paso más que el padre: sin él, el icono
     del hijo quedaba 4 px A LA IZQUIERDA del del padre y no se leía como hijo.
     1.19.1: 22 = el icono del hijo empieza 16 a la derecha del icono del
     padre (medido en prod: con 20 quedaba a 14; 42 frente a 28). */
  --ds-app-arbol-colgado: 22px;
  /* Página de detalle (§ 66): la columna lateral de propiedades (Stripe). */
  --ds-app-detalle-lateral: 320px;

  /* 1.7 (28/09/2026, SPEC-DS-1-7.md). */
  /* K1 · la navegación va un escalón por debajo del cuerpo: texto e iconos
     de la barra lateral en el secundario; el activo lleva el peso (§ 0.7).
     Lo mide medidas-ds.js g) lateral-tenue (reglas.md § 12). */
  --ds-app-texto-lateral: var(--ds-color-text-secondary);
  /* Menú (§ 60): ancho mínimo para que «Borrar lead» no baile. */
  --ds-app-menu-min: 200px;
  /* Búsqueda expandible (§ 63): el campo abierto (Notion). */
  --ds-app-buscar-ancho: 240px;
  /* Esquema de color del <select> nativo (P22: la lista salía blanca en
     oscuro). Cada app lo fija a dark o light en su bloque de tema. */
  --ds-app-esquema: light dark;
  /* Capas: menú y tooltip por encima de barra, FAB y saltar. */
  --ds-app-capa-menu: 60;
  --ds-app-capa-tooltip: 70;
  /* Botón sm (Untitled sm, 36): solo barras de vista y cabeceras de sección. */
  --ds-app-boton-sm: var(--ds-spacing-control-sm);

  /* 1.12 (29/09/2026, SPEC-CRM3-KPIS-STRIPE; reglas.md § 70-72): Stripe
     compone la página, Untitled viste la pieza (canon 29/09). */
  /* Tope de contenido de una página de app (§ 72, decisión 4): Stripe mide
     880-900 a 1440 con barra de 240; la nuestra es de 280. */
  --ds-app-pagina-app: 960px;
  /* 1.13: h1 de página de app a 28/700 (Stripe «Tu resumen», stripe/medidas.md
     § Dashboard). La escala h3 (32) sigue para documentos. */
  --ds-app-titulo-app: 28px;
  /* Radios de Untitled (untitled/medidas.md § Resolución): rounded-lg 8 en
     controles (chip de filtro), rounded-md 6 en la etiqueta, rounded-xl 12
     en tarjeta y panel. No hay token con esos valores (card y chip son 4). */
  --ds-app-radio-control: 8px;
  --ds-app-radio-etiqueta: 6px;
  --ds-app-radio-tarjeta: 12px;
  /* 1.17 (§ 71, R9): el panel de métricas lleva 16, un paso por encima de la
     tarjeta (12) que contiene: radios iguales anidados hacen «gordo» el de
     dentro (Untitled rounded-2xl en contenedor). */
  --ds-app-radio-panel: 16px;
  /* 1.16 (§ 70, P2): medida de lo «no elegido» en una fila de chips con uno
     pulsado. Es la que ya apagaba el texto: text-secondary sobre el fondo
     del chip equivale al texto al 66 % (claro) / 69 % (oscuro). */
  --ds-app-atenuado: 0.66;
  /* Sombra xs de Untitled (0 1px 2px al 5 %). Sale del velo del tema para
     valer en los dos sin literal: claro ≈ 5 %, oscuro ≈ 9 %. La
     --ds-shadow-xs del token (40 % de negro) es la del tema oscuro de la web
     y en claro pesa como un borde. */
  --ds-app-sombra-xs: 0 1px 2px color-mix(in srgb, var(--ds-color-bg-overlay) 15%, transparent);
  /* Anillo de 1 px de la tarjeta: el border-secondary de Untitled
     (neutral-200 / 800) es nuestro --ds-color-border (#e2e2e2 / #2E2E31).
     Anillo y no borde: no suma al alto (Untitled ring-1). */
  --ds-app-anillo: 0 0 0 var(--ds-app-linea) var(--ds-color-border);
  /* Panel de métricas (§ 71, decisión 6): gris bajo la página en claro
     (bg-secondary de Untitled), el elevado en oscuro; la tarjeta encima, el
     fondo «primario» (blanco en claro, el de la página en oscuro).
     light-dark() lee el color-scheme que cada app fija en su tema. */
  /* 1.13: en oscuro, página #111 < panel (mezcla 50 % de bg y elevated,
     #181819; no hay token intermedio) < tarjeta elevated #1E1E20. Con la
     tarjeta en tertiary (1.12.3) el texto terciario daba 4,27 (axe pide 4,5). */
  --ds-app-panel-fondo: light-dark(var(--ds-color-bg-tertiary), color-mix(in srgb, var(--ds-color-bg) 50%, var(--ds-color-bg-elevated)));
  --ds-app-panel-tarjeta: var(--ds-color-bg-elevated);

  /* 1.15 (30/09/2026, SPEC-CRM4-DS-1; PLAN-CRM-4 D11). */
  /* Caja de texto (§ 5, P8): sin asa, crece con lo escrito de 3 a 12 líneas
     y luego se desplaza. Líneas, no píxeles: valen para cualquier letra. */
  --ds-app-caja-lineas-min: 3;
  --ds-app-caja-lineas-max: 12;
  /* Avatar dentro de un chip de filtro o una opción de menú (§ 70, P49): el
     tamaño del icono (20), para que el chip siga midiendo 36. */
  --ds-app-avatar-sm: var(--ds-icon-size);

  /* 1.15.1 (30/09/2026, SPEC-CRM4-DS-2; PLAN-CRM-4 D5-D9). */
  /* D5 · el ítem activo lleva caja; su ICONO va en el coral de marca en los
     DOS temas (un icono pide 3:1 y da 3,6:1 sobre blanco). El texto activo de
     la barra inferior sigue en --ds-app-activo-color (4,5:1 en claro). */
  --ds-app-activo-icono: var(--ds-color-marca); /* marca */
  /* 1.18 (§ 8, Yaz y Asier 01/10): el coral de marca para el icono de
     identidad de una fila (Proyectos). 1.18.1: el coral aclarado (oklch) se
     leía como un color ajeno a Musho («desconozco el color», Yaz); vale el de
     marca porque vive en el contenido, no en la barra, donde el activo lleva
     además caja y 600. 1.18.2 (Yaz 02/10, lab de cinco variantes): el coral
     suelto se leía igual que el activo de la barra; va dentro de una CAJA
     con fondo coral al 12 % (Apple Recordatorios/Ajustes): la forma, no el
     color, lo separa del activo. Icono blanco sobre ese fondo, no (1,15:1). */
  /* 1.19.7 (Yaz 02/10, «tanto rojo me rompe»): caja neutra; icono en el
     color del texto (blanco en oscuro) sobre el gris de «seleccionado»
     (quaternary, no tertiary: tertiary es el hover de la fila y la caja
     desaparecería al pasar el ratón). Antes: coral y coral al 12 %. */
  /* 1.19.9 (Yaz 03/10, Asier «en blanco le cuesta detectarlos»): icono
     pervinca (--ds-color-apoyo: #a99ef0 en oscuro, #6a5bc4 en claro) sobre
     su caja al 16 % (5,3:1 y 4,3:1). Translúcida, así que encima del hover
     de la fila sigue viéndose. 1.19.8 (caja sólida con icono blanco) era
     «muy sólido» (Yaz). El coral sigue solo para lo activo. */
  --ds-app-icono-tinte: var(--ds-color-apoyo);
  --ds-app-icono-caja-fondo: color-mix(in srgb, var(--ds-color-apoyo) 16%, transparent);
  --ds-app-icono-caja: var(--ds-app-avatar);        /* 32 en PC, como el avatar */
  --ds-app-icono-caja-movil: var(--ds-spacing-control-sm); /* 36 bajo 720 */
  --ds-app-icono-caja-radio: 8px;
  /* Plegar la barra lateral (P19): 200 ms, la curva del panel derecho. */
  --ds-app-barra-plegar: 200ms;
  /* Contador (P58): número en círculo, 20 de alto (el icono). */
  --ds-app-contador: var(--ds-icon-size);
  /* Recorrido (P20, P74): columna fija de la etiqueta de cada parada. */
  --ds-app-recorrido-etiqueta: 120px;
  /* Árbol en móvil (P65): filas de 44, la medida táctil de Apple. */
  --ds-app-arbol-fila-movil: var(--ds-spacing-touch);
  /* Rejilla de huecos (§ 76, 1.23.0): columna mínima de una celda. Cabe
     «Hoy 17» y «3 huecos» sin partir; a 393 salen 4 columnas. Sustituye a
     --crm-agenda-dia (72) y --crm-agenda-hora (92) del CRM. */
  --ds-app-hueco-ancho: 80px;
  /* Lista con tope (§ 8, 1.23.0): alto máximo de una lista de resultados
     que crece con la red (Visitas, Nuevo lead). ~5 filas de dos líneas;
     viene de la receta .lista-lugares del CRM (D10, Yaz 01/10). */
  --ds-app-lista-tope: 272px;
  /* Mando del kit (§ 77, 1.24.0, CRM 6 · KIT-2 K3): siempre con los valores
     del tema OSCURO (Yaz 07/10: discreto sobre la demo, que es oscura). En
     oscuro son los tokens tal cual (elevated #1E1E20, text, border,
     tertiary, secondary). app.css no lleva hex (sonda-app 1), así que en
     una página clara se toma el par inverso, que es oscuro (bg-inverse
     #1e1e1e, text-inverse #fff) y el borde, el hover y el secundario como
     mezcla del texto inverso sobre ese fondo. La isla (kit-mando.css, la
     que se carga de verdad en la demo, la web y el informe) lleva los hex
     oscuros fijos. light-dark() lee el color-scheme de la página. */
  --ds-app-mando-fondo: light-dark(var(--ds-color-bg-inverse), var(--ds-color-bg-elevated));
  --ds-app-mando-texto: light-dark(var(--ds-color-text-inverse), var(--ds-color-text));
  --ds-app-mando-borde: light-dark(color-mix(in srgb, var(--ds-color-text-inverse) 12%, var(--ds-color-bg-inverse)), var(--ds-color-border));
  --ds-app-mando-hover: light-dark(color-mix(in srgb, var(--ds-color-text-inverse) 8%, var(--ds-color-bg-inverse)), var(--ds-color-bg-tertiary));
  --ds-app-mando-secundario: light-dark(color-mix(in srgb, var(--ds-color-text-inverse) 70%, var(--ds-color-bg-inverse)), var(--ds-color-text-secondary));
  /* En reposo deja ver un poco lo de debajo; al tocar o abrir, entero.
     No hay token de transparencia para un control (solo la cortina). */
  --ds-app-mando-reposo: .92;

  /* El tono de un chip de base: lo fija cada .ds-chip--<color> (§ 23, B4)
     y lo leen el chip, la columna de tablero, la portada de galería, la
     barra de cronograma y el gráfico. Es un mecanismo, no un token: sin
     prefijo --ds- porque su valor cambia por clase y app.css solo declara
     --ds-* en este :root. Por defecto, gris. */
  --tono: var(--ds-color-chip-gris);
  --tono-soft: var(--ds-color-chip-gris-soft);
}

/* ═══ 1. Botón ═══════════════════════════════════════════════════════ */
.ds-boton {
  display: inline-flex; align-items: center; justify-content: center;
  gap: var(--ds-spacing-2xs);
  min-height: var(--ds-spacing-control); padding: 0 var(--ds-spacing-xs);
  /* 1.17 (§ 1, T2/R1, Yaz 01/10): botón = Untitled md, 14/20/600 y radio 8
     (--ds-app-radio-control, el de todo control: botón, campo, selector,
     buscador, chip de filtro). Antes 16/24/500 y radio 4 (el de tarjeta de
     la web): en la misma barra convivía con el chip de filtro a 8. */
  border: 0; border-radius: var(--ds-app-radio-control);
  font-family: inherit; font-size: var(--ds-typography-size-ui-sm);
  line-height: var(--ds-typography-line-height-ui-sm); font-weight: 600;
  white-space: nowrap; text-decoration: none; cursor: pointer;
  background: var(--ds-color-bg-tertiary); color: var(--ds-color-text);
  transition: background-color var(--ds-motion-duration-instant) var(--ds-motion-easing-musho),
              color var(--ds-motion-duration-instant) var(--ds-motion-easing-musho);
}
.ds-boton--primario { background: var(--ds-app-primario-fondo); color: var(--ds-app-primario-texto); }
.ds-boton--primario:hover { background: var(--ds-app-primario-hover); }
.ds-boton--secundario { background: var(--ds-color-bg-tertiary); color: var(--ds-color-text); }
.ds-boton--secundario:hover { background: var(--ds-color-bg-quaternary); }
/* 1.15 (§ 1, P69 «los botones se distinguen del texto»; Untitled secundario
   ring-1 neutral-300/700): el secundario lleva borde EN REPOSO; en móvil no
   hay hover que lo revele. Borde real (no anillo) con border-box: el alto no
   cambia (40/44). */
.ds-boton--secundario { box-sizing: border-box; border: var(--ds-app-linea) solid var(--ds-color-border-strong); }
.ds-boton--terciario { background: transparent; color: var(--ds-color-text-secondary); }
.ds-boton--terciario:hover { background: var(--ds-color-bg-tertiary); color: var(--ds-color-text); }
/* Destructivo = sólido carmín con texto blanco (1.9, P10, § 0.7), como el
   «Delete» de Stripe: familia y tono lejos del coral de marca; el hover
   oscurece. El «Borrar» de un menú sigue en texto error-fg (§ 24). */
.ds-boton--destructivo { background: var(--ds-color-error); color: var(--ds-color-text-on-error); }
.ds-boton--destructivo:hover { background: var(--ds-color-error-hover); color: var(--ds-color-text-on-error); }
.ds-boton--enlace { background: transparent; color: var(--ds-color-text); padding: 0; white-space: normal; text-align: left; /* dentro de texto: parte línea (§ 1, 1.5.1) */ text-decoration: underline; text-underline-offset: var(--ds-app-paso-medio); }
.ds-boton--enlace:hover { text-decoration: underline; text-underline-offset: var(--ds-app-paso-medio); }
/* 1.14 (§ 1, C9): el enlace no lleva alto de control en PC (≤ 32 a 1440, el
   de su línea de texto); el alto de dedo (44) solo bajo 1056. «Avanzar a…»
   medía 44 en el Board a 1440. */
.ds-boton--enlace { min-height: 0; }
@media (max-width: 1055px) { .ds-boton--enlace { min-height: var(--ds-spacing-touch); } }
/* 1.17 (§ 1, U11): el botón de icono no encoge (35 × 40 en la Biblioteca a 393). */
.ds-boton--icono { padding: 0; width: var(--ds-spacing-control); flex-shrink: 0; }
/* 1.17 (§ 1 y § 19, I1): el icono de un botón CON texto mide 20 (Untitled md);
   un ds-icono--sm dentro se fuerza a 20 (los lotes quitan la clase; esto es la
   red). El de solo icono y el control de persona (avatar + chevron) no. */
.ds-boton:not(.ds-boton--icono, :has(> .ds-avatar)) > .ds-icono--sm { width: var(--ds-icon-size); height: var(--ds-icon-size); }
.ds-boton--fila { min-height: var(--ds-spacing-control-sm); }
.ds-boton--icono.ds-boton--fila { width: var(--ds-spacing-control-sm); }
/* sm (1.6, Untitled sm): barras de vista y cabeceras de sección; nunca el
   primario de una página (§ 1, P30). */
.ds-boton--sm { min-height: var(--ds-app-boton-sm); padding: 0 var(--ds-app-paso-12); font-size: var(--ds-typography-size-ui-sm); line-height: var(--ds-typography-line-height-ui-sm); }
.ds-boton--icono.ds-boton--sm { width: var(--ds-app-boton-sm); padding: 0; }
/* El display de la pieza gana al [hidden] del navegador: se devuelve. */
.ds-boton[hidden] { display: none; }
.ds-boton:disabled, .ds-boton[aria-disabled="true"] {
  color: var(--ds-color-text-disabled); cursor: not-allowed;
}
.ds-boton--primario:disabled, .ds-boton--secundario:disabled, .ds-boton--destructivo:disabled,
.ds-boton--primario[aria-disabled="true"], .ds-boton--destructivo[aria-disabled="true"] {
  background: var(--ds-color-bg-tertiary);
}
.ds-boton--terciario:disabled, .ds-boton--enlace:disabled { background: transparent; text-decoration: none; }
/* Grupo de botones (1.4, reglas.md § 1 y § 30.4, Carbon): el par o trío de un
   diálogo, hoja o pie de formulario iguala el ancho del más largo. */
.ds-botones--iguales { display: inline-grid; grid-auto-flow: column; grid-auto-columns: 1fr; gap: var(--ds-spacing-2xs); }

/* Foco visible, común a todo lo que se pulsa. */
.ds-boton:focus-visible, .ds-chip:focus-visible, .ds-segmentado__item:focus-visible,
.ds-pestanas__item:focus-visible, a.ds-tarjeta:focus-visible, .ds-fila:focus-visible,
.ds-fab:focus-visible, .ds-barra-inferior__item:focus-visible, .ds-sidebar__item:focus-visible,
.ds-migas a:focus-visible, .ds-chip__quitar:focus-visible,
.ds-sidebar__logo:focus-visible, .ds-arbol__flecha:focus-visible,
.ds-arbol__enlace:focus-visible, .ds-propiedades__ctrl:focus-visible {
  outline: var(--ds-app-foco) solid var(--ds-app-foco-color);
  outline-offset: var(--ds-app-foco);
}

/* ═══ 2. Chip de filtro ══════════════════════════════════════════════ */
/* Fila con scroll horizontal, sin salto de línea y sin barra visible. */
.ds-chips {
  display: flex; gap: var(--ds-spacing-2xs); overflow-x: auto;
  scrollbar-width: none; -webkit-overflow-scrolling: touch;
}
.ds-chips::-webkit-scrollbar { display: none; }
/* 1.14.2 (§ 23, C16): el desplazable es para móvil; desde 720 la fila parte
   línea y nada queda cortado en el borde sin señal («Registrado hoy»). */
@media (min-width: 720px) { .ds-chips { flex-wrap: wrap; overflow-x: visible; } }
.ds-chip {
  flex: none; display: inline-flex; align-items: center; gap: var(--ds-app-paso-medio);
  height: var(--ds-app-chip); padding: 0 var(--ds-app-paso-12);
  border: 0; border-radius: var(--ds-radius-full);
  background: var(--ds-color-bg-tertiary); color: var(--ds-color-text-secondary);
  font-family: inherit; font-size: var(--ds-typography-size-ui-sm);
  line-height: var(--ds-typography-line-height-ui-sm); font-weight: 500;
  white-space: nowrap; text-decoration: none; cursor: pointer;
  transition: background-color var(--ds-motion-duration-instant) var(--ds-motion-easing-musho),
              color var(--ds-motion-duration-instant) var(--ds-motion-easing-musho);
}
.ds-chip:hover { color: var(--ds-color-text); }
/* Activo = inverso (fondo claro sobre oscuro), como la MLS. */
.ds-chip[aria-pressed="true"], .ds-chip.es-activo {
  background: var(--ds-color-bg-inverse); color: var(--ds-color-text-inverse);
}
.ds-chip__cuenta { font-variant-numeric: tabular-nums; opacity: 0.7; }
.ds-chip__quitar {
  display: inline-flex; align-items: center; justify-content: center;
  margin-right: calc(var(--ds-app-paso-medio) * -1);
  border: 0; padding: 0; background: none; color: inherit; cursor: pointer;
  font: inherit; line-height: 1;
}
/* 1.10 (P26, § 2 y § 23): los filtros aplicados en móvil van en UNA línea
   que se desplaza (Notion móvil), no en tres. Bajo 720: sin salto, sin barra
   visible; el valor del chip (el hijo que no es __quitar, __cuenta ni icono)
   se trunca a ~20 caracteres y el completo va en title/data-tooltip. El
   relleno vertical deja sitio a la caja de 40 de la X (bajo 1056) dentro del
   scroll, y el margen negativo lo devuelve: la fila no crece. */
@media (max-width: 719px) {
  .ds-chips--desplazable {
    flex-wrap: nowrap; overflow-x: auto; overflow-y: hidden;
    scrollbar-width: none; -webkit-overflow-scrolling: touch;
    padding-block: calc((var(--ds-spacing-control) - var(--ds-app-chip)) / 2);
    margin-block: calc((var(--ds-app-chip) - var(--ds-spacing-control)) / 2);
  }
  .ds-chips--desplazable::-webkit-scrollbar { display: none; }
  /* En una fila que se desplaza nada se encoge (un .ds-selector con min-width 0
     se aplastaba y su botón pisaba al vecino: responsive.js, /tareas a 360). */
  .ds-chips--desplazable > * { flex: none; }
  .ds-chips--desplazable .ds-chip > :not(.ds-chip__quitar, .ds-chip__cuenta, .ds-icono) {
    display: inline-block; min-width: 0; max-width: 20ch;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; vertical-align: bottom;
  }
}

/* ═══ 3. Control segmentado ══════════════════════════════════════════ */
/* Sin JS, el ítem activo lleva el fondo él mismo. Con JS (app.js pone
   data-listo), el fondo lo lleva __indicador y se desliza. */
/* 1.14 (§ 3, H6; Untitled button-border): mide lo que sus ítems, no el
   ancho de la página. inline-grid con columnas iguales al ítem más largo
   (1fr en una caja de ancho max-content: ni la columna flex ni la rejilla
   que lo contiene lo estiran) y marco de 4. Alto 40 en
   PC; 44 bajo 1056 (abajo). En Hoy medía 960 y en Ajustes 720. */
.ds-segmentado {
  position: relative; display: inline-grid; grid-auto-flow: column; grid-auto-columns: 1fr;
  width: max-content; max-width: 100%; box-sizing: border-box; isolation: isolate; vertical-align: top;
  padding: var(--ds-app-segmentado-marco);
  background: var(--ds-color-bg-tertiary); border-radius: var(--ds-radius-full);
}
.ds-segmentado[hidden] { display: none; }
/* 1.15 (§ 3, P77 «sale cortado a la mitad»): bajo 720 el segmentado ocupa
   todo el ancho de su contenedor con ítems iguales (el dedo necesita la
   caja entera; en PC sigue midiendo lo que sus ítems). La cabecera
   segmentada (§ 65) y la barra lateral ya lo hacían. */
@media (max-width: 719px) { .ds-segmentado:not(.ds-cabecera *, .ds-sidebar *) { width: 100%; } }
/* 1.14: la barra lateral es armazón (§ 0.10: cambia en versión mayor) y su
   segmentado de área (Ventas | Estudio) sigue como estaba: a todo el ancho
   de la barra, relleno de 3 e ítems de 34. */
.ds-sidebar .ds-segmentado { display: grid; width: auto; padding: var(--ds-app-segmentado-relleno); }
.ds-sidebar .ds-segmentado__item { min-height: calc(var(--ds-spacing-control) - 2 * var(--ds-app-segmentado-relleno)); }
.ds-sidebar .ds-segmentado__indicador { top: var(--ds-app-segmentado-relleno); bottom: var(--ds-app-segmentado-relleno); }
.ds-segmentado__item {
  min-width: 0; position: relative; z-index: 1;
  display: flex; align-items: center; justify-content: center;
  min-height: calc(var(--ds-spacing-control) - 2 * var(--ds-app-segmentado-marco));
  padding: 0 var(--ds-app-paso-12);
  border: 0; border-radius: var(--ds-radius-full); background: transparent;
  color: var(--ds-color-text-secondary); font-family: inherit;
  font-size: var(--ds-typography-size-ui-sm); line-height: var(--ds-typography-line-height-ui-sm);
  font-weight: 500; text-decoration: none; cursor: pointer;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  transition: color var(--ds-motion-duration-move) var(--ds-motion-easing-musho);
}
.ds-segmentado__item[aria-selected="true"], .ds-segmentado__item.es-activo {
  background: var(--ds-color-bg-quaternary); color: var(--ds-color-text);
}
.ds-segmentado__indicador {
  display: none; position: absolute; z-index: 0;
  top: var(--ds-app-segmentado-marco); bottom: var(--ds-app-segmentado-marco); left: 0;
  background: var(--ds-color-bg-quaternary); border-radius: var(--ds-radius-full);
  pointer-events: none;
}
.ds-segmentado[data-listo] .ds-segmentado__indicador { display: block; }
.ds-segmentado[data-listo] .ds-segmentado__item[aria-selected="true"],
.ds-segmentado[data-listo] .ds-segmentado__item.es-activo { background: transparent; }
.ds-segmentado[data-animado] .ds-segmentado__indicador {
  transition: transform var(--ds-motion-duration-move) var(--ds-motion-easing-musho),
              width var(--ds-motion-duration-move) var(--ds-motion-easing-musho);
}

/* 1.15.3 (P58): el número de cada ítem es .ds-contador (§ 75); los
   .ds-segmentado__num y __num--error se borran. */

/* ═══ 4. Pestañas ════════════════════════════════════════════════════ */
/* 1.8 (P41, Stripe «Transacciones», stripe/medidas.md): fila de 36 con UNA
   línea de 1 px bajo toda la fila; ítem 14/600 con 12 a cada lado; la activa,
   texto principal + subrayado de 2 px pegado a la línea; sin fondo ni radio.
   Bajo 1056 (táctil, § 0.8) la fila mide 40. */
.ds-pestanas {
  display: flex; gap: 0; overflow-x: auto; min-height: var(--ds-spacing-control-sm);
  scrollbar-width: none; border-bottom: var(--ds-app-linea) solid var(--ds-color-border);
}
.ds-pestanas::-webkit-scrollbar { display: none; }
.ds-pestanas__item {
  flex: none; display: flex; align-items: center;
  min-height: var(--ds-spacing-control-sm); padding: 0 var(--ds-app-paso-12);
  color: var(--ds-color-text-secondary); text-decoration: none; white-space: nowrap;
  font-size: var(--ds-typography-size-ui-sm); line-height: var(--ds-typography-line-height-ui-sm);
  font-weight: 600; background: none; border: 0; border-radius: 0; font-family: inherit; cursor: pointer;
  transition: color var(--ds-motion-duration-move) var(--ds-motion-easing-musho),
              box-shadow var(--ds-motion-duration-move) var(--ds-motion-easing-musho);
}
.ds-pestanas__item:hover { color: var(--ds-color-text); }
/* Subrayado activo en --ds-app-activo-color (1.6, D1: el coral dice «aquí estás»). */
.ds-pestanas__item[aria-current], .ds-pestanas__item.es-activo {
  color: var(--ds-color-text);
  box-shadow: inset 0 calc(var(--ds-app-foco) * -1) 0 var(--ds-app-activo-color);
}
@media (max-width: 1055px) {
  .ds-pestanas, .ds-pestanas__item { min-height: var(--ds-spacing-control); }
}

/* ═══ 5. Campo ═══════════════════════════════════════════════════════ */
/* 1.19.0 (§ 0.16): etiqueta → control y control → ayuda/error, 8 (parte);
   antes 4. */
.ds-campo { display: flex; flex-direction: column; gap: var(--ds-aire-parte); min-width: 0; }
.ds-campo__etiqueta {
  font-size: var(--ds-typography-size-ui-sm); line-height: var(--ds-typography-line-height-ui-sm);
  color: var(--ds-color-text-secondary); font-weight: 500;
}
.ds-campo__obligatorio { color: var(--ds-color-error-fg); margin-left: var(--ds-app-paso-medio); }
.ds-campo__control {
  width: 100%; min-height: var(--ds-spacing-control); padding: 0 var(--ds-app-paso-12);
  background: var(--ds-color-bg-elevated); color: var(--ds-color-text);
  border: var(--ds-app-linea) solid var(--ds-color-border); border-radius: var(--ds-app-radio-control); /* 1.17 (R1): radio de control */
  font-family: inherit; font-size: var(--ds-typography-size-ui-md);
  line-height: var(--ds-typography-line-height-ui-md);
  transition: border-color var(--ds-motion-duration-instant) var(--ds-motion-easing-musho);
}
/* 1.15 (§ 5, P8 «si hay información, muéstramela; si no, no»): caja de
   texto SIN asa. Alto por contenido entre 3 y 12 líneas (field-sizing; donde
   no existe, app.js ajusta el alto al escribir) y luego barra de
   desplazamiento. Relleno de input de Untitled (12×8); untitled/medidas.md no
   trae textarea: las líneas son decisión de Asier (PLAN-CRM-4 D11). */
textarea.ds-campo__control {
  box-sizing: border-box; padding: var(--ds-spacing-2xs) var(--ds-app-paso-12);
  resize: none; field-sizing: content; overflow-y: auto;
  min-height: calc(var(--ds-app-caja-lineas-min) * var(--ds-typography-line-height-ui-md) + 2 * var(--ds-spacing-2xs) + 2 * var(--ds-app-linea));
  max-height: calc(var(--ds-app-caja-lineas-max) * var(--ds-typography-line-height-ui-md) + 2 * var(--ds-spacing-2xs) + 2 * var(--ds-app-linea));
}
/* <select> nativo (1.6, P22): la lista abierta sigue el tema de la app. */
select.ds-campo__control { color-scheme: var(--ds-app-esquema); background: var(--ds-color-bg-elevated); color: var(--ds-color-text); height: var(--ds-spacing-control); }
.ds-campo__control::placeholder { color: var(--ds-color-text-placeholder); }
/* 1.17 (§ 5, R3): la sombra del campo es la de app (≈ 5 % en claro); la
   --ds-shadow-xs (40 % de negro) pesaba como un borde. */
.ds-campo__control:focus { outline: none; border-color: var(--ds-color-border-strong); box-shadow: var(--ds-app-sombra-xs); }
.ds-campo__control:disabled { color: var(--ds-color-text-disabled); cursor: not-allowed; }
.ds-campo__ayuda {
  font-size: var(--ds-typography-size-ui-xs); line-height: var(--ds-typography-line-height-ui-xs);
  color: var(--ds-color-text-tertiary);
}
.ds-campo__error {
  font-size: var(--ds-typography-size-ui-xs); line-height: var(--ds-typography-line-height-ui-xs);
  color: var(--ds-color-error-fg);
}
.ds-campo--error .ds-campo__control { border-color: var(--ds-color-error); }
/* 1.8.4 · Tarjetas de radio (Stripe, formulario largo): elegir UNA entre 2-4
   con descripción. Rejilla de label.ds-tarjeta-radio; 2-3 por fila, 1 en
   móvil. La marcada, borde de 2 px en el color de foco (neutro, nunca coral). */
.ds-campo--tarjetas { display: grid; grid-template-columns: repeat(auto-fit, minmax(var(--ds-app-tarjeta-radio-min), 1fr)); gap: var(--ds-spacing-2xs); border: 0; padding: 0; margin: 0; min-width: 0; }
.ds-campo--tarjetas > .ds-campo__etiqueta, .ds-campo--tarjetas > .ds-campo__ayuda, .ds-campo--tarjetas > .ds-campo__error { grid-column: 1 / -1; padding: 0; }
@media (max-width: 719px) { .ds-campo--tarjetas { grid-template-columns: minmax(0, 1fr); } }
/* 1.19.0 (§ 0.16): legend → primera tarjeta, 8 (antes 0). El legend queda
   fuera de la rejilla y el gap no le llega: su margen inferior pone los 8
   (igual en Chrome y Safari; flotarlo lo metía en la rejilla solo en Chrome). */
.ds-campo--tarjetas > legend { margin-bottom: var(--ds-aire-parte); }
/* 1.15.3 (§ 5, V-2A): pie de un formulario que vive en la página, una
   sección o un panel (no en un diálogo ni una hoja, que tienen el suyo).
   Fila al final, 24 por encima (1.19.0, § 0.16: aire-campo; antes 16), primario a la DERECHA y los secundarios a su
   izquierda; bajo 720, a todo el ancho con la regla de pie móvil de 1.15.
   Un enviar apagado (data-requiere) no cambia de sitio. */
.ds-form__pie {
  display: flex; flex-wrap: wrap; justify-content: flex-end; align-items: center;
  gap: var(--ds-spacing-2xs); margin: var(--ds-aire-campo) 0 0;
}
.ds-tarjeta-radio {
  display: grid; grid-template-columns: auto minmax(0, 1fr); column-gap: var(--ds-app-paso-12); row-gap: var(--ds-app-paso-medio);
  padding: var(--ds-aire-bloque); cursor: pointer; background: var(--ds-color-bg-elevated);
  /* 1.17 (§ 5, R5): anillo de 1 como .ds-tarjeta, sin borde real (no suma al
     alto); la elegida, anillo de 2 en el color de foco. Antes borde de 1 +
     anillo interior de 1. */
  border: 0; border-radius: var(--ds-app-radio-tarjeta); box-shadow: var(--ds-app-anillo);
}
/* 1.15 (§ 5, P12 «el radio va arriba, alineado con el título»): el radio
   vive en la fila del título y se centra con su PRIMERA línea; no abarca dos
   filas (estirado, el navegador lo pintaba centrado en la tarjeta). */
.ds-tarjeta-radio > input {
  grid-row: 1; grid-column: 1; align-self: start; box-sizing: border-box;
  width: var(--ds-icon-size-sm); height: var(--ds-icon-size-sm);
  margin: calc((var(--ds-typography-line-height-ui-sm) - var(--ds-icon-size-sm)) / 2) 0 0;
  accent-color: var(--ds-app-foco-color);
}
.ds-tarjeta-radio > :not(input) { grid-column: 2; }
.ds-tarjeta-radio__titulo { font-size: var(--ds-typography-size-ui-sm); line-height: var(--ds-typography-line-height-ui-sm); font-weight: 600; color: var(--ds-color-text); }
.ds-tarjeta-radio__texto { font-size: var(--ds-typography-size-ui-xs); line-height: var(--ds-typography-line-height-ui-xs); color: var(--ds-color-text-secondary); }
.ds-tarjeta-radio:hover { box-shadow: 0 0 0 var(--ds-app-linea) var(--ds-color-border-strong); }
.ds-tarjeta-radio:has(> input:checked) { box-shadow: 0 0 0 var(--ds-app-foco) var(--ds-app-foco-color); }
.ds-tarjeta-radio:has(> input:focus-visible) { outline: var(--ds-app-foco) solid var(--ds-app-foco-color); outline-offset: var(--ds-app-foco); }
.ds-tarjeta-radio:has(> input:disabled) { cursor: default; color: var(--ds-color-text-disabled); }
.ds-tarjeta-radio:has(> input:disabled) :is(.ds-tarjeta-radio__titulo, .ds-tarjeta-radio__texto) { color: var(--ds-color-text-disabled); }

/* ═══ 6. Etiqueta de estado ══════════════════════════════════════════ */
/* 1.12 (decisión 8, Untitled badge-color md, bdg:84/133): alto 24 (relleno
   8×2 + línea de 20), radio 6, anillo de 1 px por dentro del tono al 30 %
   (el X-200 de Untitled en claro, el X-800 en oscuro) sobre el *-soft (X-50 /
   X-950) con el texto *-fg (X-700 / X-300). El punto, solo en --punto. Antes:
   radio 2, alto 18, sin anillo y punto siempre. */
.ds-etiqueta {
  display: inline-flex; align-items: center; gap: var(--ds-app-paso-medio);
  box-sizing: border-box; padding: calc(var(--ds-app-paso-medio) / 2) var(--ds-spacing-2xs);
  border-radius: var(--ds-app-radio-etiqueta);
  /* 1.17 (§ 6, T1): 12/18 (ui-xs, Untitled text-xs); antes 12/20. */
  font-size: var(--ds-typography-size-ui-xs); line-height: var(--ds-typography-line-height-ui-xs);
  font-weight: 500; white-space: nowrap;
  background: var(--ds-color-bg-tertiary); color: var(--ds-color-text-secondary);
  box-shadow: inset 0 0 0 var(--ds-app-linea) color-mix(in srgb, currentColor 30%, transparent);
}
.ds-etiqueta--punto::before {
  content: ""; flex: none; width: var(--ds-app-punto); height: var(--ds-app-punto);
  border-radius: var(--ds-radius-full); background: currentColor;
}
.ds-etiqueta--ok { background: var(--ds-color-success-soft); color: var(--ds-color-success-fg); }
.ds-etiqueta--aviso { background: var(--ds-color-warning-soft); color: var(--ds-color-warning-fg); }
.ds-etiqueta--error { background: var(--ds-color-error-soft); color: var(--ds-color-error-fg); }
.ds-etiqueta--info { background: var(--ds-color-info-soft); color: var(--ds-color-info-fg); }
.ds-etiqueta--neutro { background: var(--ds-color-bg-tertiary); color: var(--ds-color-text-secondary); }
/* 1.15 (§ 6, P70 «Archivado» y P45 «Recursos ventas | Activo»): una etiqueta
   junto a un texto o un título deja ≥ 8. En los contenedores de TEXTO (no
   flex: el hueco sería solo el espacio, ~4) lleva margen lógico de 8, como
   ya hacía la de la fila (§ 8); los contenedores flex con gap no cambian. */
/* 1.15.3: + .ds-panel__caption («Nuevo · Centro» + «Sin próxima acción» a 0 en el panel del lead). */
:is(.ds-titulo__h1, .ds-tarjeta__titulo, .ds-panel__titulo, .ds-seccion__titulo, .ds-dialogo__titulo, .ds-hoja__titulo, .ds-caption, .ds-panel__caption, .ds-fila__sub) .ds-etiqueta {
  margin-inline-start: var(--ds-spacing-2xs);
}

/* ═══ 7. Tarjeta ═════════════════════════════════════════════════════ */
/* El estado va DENTRO como etiqueta, nunca como borde.
   1.12 (decisión 7, Untitled tbl:55): toda tarjeta va vestida: radio 12,
   anillo de 1 px (border-secondary) + sombra xs. Era la razón del «se ve
   pobre»: radio 4 y ni anillo ni sombra. 1.16 (P11, D5): la de tablero
   (--bd) y toda superficie de contenido (lista, tabla, columna de tablero,
   métrica, diálogo, hoja, tarjeta de radio) van también a 12.
   1.19.0 (§ 0.16): líneas de la tarjeta a 8 (parte; antes 4) y relleno 16
   (bloque; antes 24). */
.ds-tarjeta {
  display: flex; flex-direction: column; gap: var(--ds-aire-parte);
  background: var(--ds-color-bg-elevated); border-radius: var(--ds-app-radio-tarjeta);
  box-shadow: var(--ds-app-anillo), var(--ds-app-sombra-xs);
  padding: var(--ds-aire-bloque); color: var(--ds-color-text); text-decoration: none; min-width: 0;
}
a.ds-tarjeta { transition: background-color var(--ds-motion-duration-instant) var(--ds-motion-easing-musho); }
a.ds-tarjeta:hover { background: var(--ds-color-bg-tertiary); }
/* 1.13.2 (§ 0.3, remate de Asier 29/09): los captions van en frase, sin
   mayúsculas ni tracking; los de título y panel calcan este. */
.ds-tarjeta__caption {
  font-size: var(--ds-typography-size-ui-xs); line-height: var(--ds-typography-line-height-ui-xs);
  color: var(--ds-color-text-tertiary);
  font-weight: 500;
}
/* 1.17.1 (§ 7): caption con icono. El icono (--sm) va DELANTE del texto, en
   la misma línea, centrado y a 8; sin la variante, el svg (block) caía
   encima del texto (Campos de la base, 34 de alto en vez de 18). */
.ds-tarjeta__caption--icono { display: flex; align-items: center; gap: var(--ds-spacing-2xs); margin: 0; }
.ds-tarjeta__titulo {
  margin: 0; font-size: var(--ds-typography-size-ui-lg); line-height: var(--ds-typography-line-height-ui-lg);
  font-weight: 600; color: var(--ds-color-text);
}
/* 1.15 (P53, § 7 y § 8): un texto nunca se sale de su caja. Un título de 80
   caracteres sin espacios pasaba por detrás de las tarjetas vecinas; ahora
   parte dentro. Donde el título se recorta con «…» (nowrap) no cambia nada. */
.ds-tarjeta__titulo, .ds-fila__titulo, .ds-tarjeta-radio__titulo, .ds-seccion__titulo, .ds-dialogo__titulo, .ds-hoja__titulo { overflow-wrap: anywhere; }
/* 1.7.2: el título puede ser un enlace (tarjeta de tablero): sin subrayado ni color de enlace. */
.ds-tarjeta__titulo:is(a) { color: inherit; text-decoration: none; }
.ds-tarjeta__meta {
  margin: 0; font-size: var(--ds-typography-size-ui-sm); line-height: var(--ds-typography-line-height-ui-sm);
  color: var(--ds-color-text-secondary); max-width: var(--ds-app-parrafo-max); /* 1.20.1 (§ 0.17, p. 6) */
}
/* 1.17 (§ 7, E10): cuando __meta reparte piezas (flex o rejilla), el hueco
   es 8 en línea y 4 entre líneas en TODA tarjeta; antes 12 en Ruta, 6 en el
   tablero y 4 en Agendar. Pesa (0,2,0) para ganar a la receta que lo hace flex. */
.ds-tarjeta .ds-tarjeta__meta { column-gap: var(--ds-spacing-2xs); row-gap: var(--ds-app-paso-medio); }
/* 1.19.0 (§ 0.16): líneas → acciones 16 (bloque) = gap de la tarjeta 8 + este 8. */
.ds-tarjeta__acciones { display: flex; flex-wrap: wrap; gap: var(--ds-spacing-2xs); margin-top: var(--ds-aire-parte); }
/* 1.15.2 (P27, § 7): en el tablero, el botón de etapa de la tarjeta solo
   bajo 1056; en PC la tarjeta se arrastra. Lo decide el DS, no la vista. */
@media (min-width: 1056px) { .ds-tablero .ds-tarjeta__acciones { display: none; } }
/* 1.10 (P29, § 7) → 1.12 (decisión 6): --borde ya no dibuja un borde de 1
   que suma al alto: desde 1.12 TODA tarjeta lleva el anillo fino + sombra xs
   (arriba). La clase queda para las tarjetas de Panel y Hoy hasta que migren
   a .ds-panel-metricas (§ 71); no añade nada salvo quitar un borde heredado. */
.ds-tarjeta--borde { border: 0; }
/* 1.11 (§ 7, panel de resumen): el «Ver todo (n)» de un bloque mide lo que su
   texto, no el ancho de la tarjeta (como en .ds-detalle__principal). */
.ds-tarjeta--borde > .ds-boton--enlace { align-self: flex-start; }

/* ═══ 8. Lista de filas ══════════════════════════════════════════════ */
.ds-lista { list-style: none; margin: 0; padding: 0; background: var(--ds-color-bg-elevated); border-radius: var(--ds-app-radio-tarjeta); box-shadow: var(--ds-app-anillo), var(--ds-app-sombra-xs); }
/* 1.18 (§ 8, Yaz 01/10): la lista suelta va vestida como la tarjeta (anillo de
   1 + sombra xs, decisión 7 de 1.12): en Rutas se veía «sin borde» al lado de
   las tarjetas de Hoy. Dentro de otra superficie (tarjeta, panel, hoja,
   diálogo) ya la viste su caja: sin anillo, o saldría un borde doble. */
:is(.ds-tarjeta, .ds-panel, .ds-hoja, .ds-dialogo) .ds-lista { box-shadow: none; }
/* 1.16 (§ 8, P11/D5): con radio 12 el fondo de hover de la primera y la última
   fila asomaba por la esquina; las esquinas de la fila heredan las de la lista. */
.ds-lista > :first-child, .ds-lista > :first-child > .ds-fila { border-start-start-radius: inherit; border-start-end-radius: inherit; }
.ds-lista > :last-child, .ds-lista > :last-child > .ds-fila { border-end-start-radius: inherit; border-end-end-radius: inherit; }
.ds-lista > li + li, .ds-lista > .ds-fila + .ds-fila { border-top: var(--ds-app-linea) solid var(--ds-color-border-subtle); }
/* § 8 · 1.23.0: lista de resultados que crece con la red; scroll dentro. */
.ds-lista--tope { max-height: var(--ds-app-lista-tope); overflow-y: auto; overscroll-behavior: contain; }
.ds-lista--tope[hidden] { display: none; }
.ds-fila {
  display: flex; align-items: center; gap: var(--ds-app-paso-12);
  /* 1.19.2 (§ 0.16, Yaz 02/10 «poco padding vertical en /rutas»): 12 arriba y
     abajo (antes 8): la lista suelta lleva anillo como una tarjeta y a 8 se
     leía apretada; 12 = el relleno compacto del tablero (Apple, lista agrupada). */
  min-height: var(--ds-spacing-touch); padding: var(--ds-app-paso-12) var(--ds-spacing-xs);
  color: var(--ds-color-text); text-decoration: none;
  transition: background-color var(--ds-motion-duration-instant) var(--ds-motion-easing-musho);
}
a.ds-fila:hover, button.ds-fila:hover { background: var(--ds-color-bg-tertiary); }
button.ds-fila { width: 100%; border: 0; background: none; font: inherit; text-align: left; cursor: pointer; }
/* 1.16 (P1, D1): el icono de la fila va en el color del texto (antes
   secundario; los de proyecto y documento, en coral con --marca). */
.ds-fila__icono { color: var(--ds-color-text); }
/* 1.18 (§ 8): icono de identidad con color, en el coral de marca
   (--ds-app-icono-tinte); 1.18.2: dentro de una caja de coral suave. Solo en listas de contenido, nunca en la barra ni
   el árbol (el motivo de retirar D5 en 1.16). Pesa
   (0,2,0): la base `.ds-icono { color: inherit }` (§ 19) va después. */
/* 1.19.4 (Yaz 02/10, «unificar los iconos como en /proyectos»): la caja
   vale también en el árbol vestido de lista (§ 62) y en el nombre de una
   tabla de base (§ 23): contenido de página, nunca la barra. */
:is(.ds-fila, .ds-arbol--lista, .ds-tabla__nombre) .ds-fila__icono--tinte {
  box-sizing: border-box; flex: none;
  width: var(--ds-app-icono-caja); height: var(--ds-app-icono-caja);
  padding: calc((var(--ds-app-icono-caja) - var(--ds-icon-size)) / 2);
  border-radius: var(--ds-app-icono-caja-radio);
  color: var(--ds-app-icono-tinte); background: var(--ds-app-icono-caja-fondo);
}
/* 1.17.1 (§ 8): el icono de fila (el de delante; el chevron no) mide 20 en
   PC y 24 bajo 720, como el icono que se toca con el dedo (§ 19). Vale para
   toda fila: listas, Más, Hoy y la lista de una tarjeta de panel. */
@media (max-width: 719px) {
  /* 1.19.5: sin la caja de identidad: este selector pesa (0,4,0) y dejaba la
     caja de 36 en 24 (Proyectos y Recientes en móvil, medido en prod 02/10). */
  .ds-fila__icono, .ds-fila > .ds-icono:first-child:not(.ds-fila__chevron, .ds-fila__icono--tinte) { width: var(--ds-icon-size-lg); height: var(--ds-icon-size-lg); }
  /* 1.18.2: la caja del icono de identidad crece con él (24 dentro de 36). */
  :is(.ds-fila, .ds-arbol--lista, .ds-tabla__nombre) .ds-fila__icono--tinte {
    width: var(--ds-app-icono-caja-movil); height: var(--ds-app-icono-caja-movil);
    padding: calc((var(--ds-app-icono-caja-movil) - var(--ds-icon-size-lg)) / 2);
  }
}
.ds-fila__texto { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.ds-fila__titulo {
  font-size: var(--ds-typography-size-ui-md); line-height: var(--ds-typography-line-height-ui-md);
  max-width: var(--ds-app-texto-max); /* 1.14.2: en listas de 960 un título pasaba de 110 caracteres */
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ds-fila__sub {
  font-size: var(--ds-typography-size-ui-sm); line-height: var(--ds-typography-line-height-ui-sm);
  color: var(--ds-color-text-secondary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  max-width: var(--ds-app-texto-max);
}
.ds-fila__chevron { color: var(--ds-color-text-tertiary); }
/* Anatomía de la fila (1.4, reglas.md § 8): icono · texto (la etiqueta DENTRO
   del título, tras el texto) · meta (un dato corto a la derecha) · hueco de
   acción (chevron, ≤ 2 iconos o .ds-fila__hueco del ancho del chevron), para
   que el borde derecho de la lista sea el mismo en todas las filas. */
.ds-fila__titulo .ds-etiqueta { vertical-align: middle; margin-inline-start: var(--ds-spacing-2xs); }
/* Con etiqueta dentro, el título parte línea en vez de recortarse: si el
   nombre es largo, el recorte se comía la etiqueta entera (el Panel del CRM a
   360 px perdía «Atrasado», que es lo único que importa de esa fila). El
   texto no se trunca cuando lleva estado (1.5.1). */
.ds-fila__titulo:has(.ds-etiqueta) { white-space: normal; }
/* D4 (1.6, revierte la 1.5.1 cuando hay __nombre): el nombre que escribe el
   usuario se recorta con «…» y la etiqueta queda fija a su lado (Carbon:
   lo generado por el usuario se trunca). Sin __nombre, sigue partiendo línea. */
.ds-fila__titulo:has(.ds-fila__nombre) { display: flex; align-items: center; min-width: 0; white-space: nowrap; }
.ds-fila__nombre { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ds-fila__titulo:has(.ds-fila__nombre) .ds-etiqueta { flex: none; }
.ds-fila__meta {
  flex: none; text-align: right; font-variant-numeric: tabular-nums;
  font-size: var(--ds-typography-size-ui-sm); line-height: var(--ds-typography-line-height-ui-sm);
  color: var(--ds-color-text-secondary);
}
.ds-fila__hueco { flex: none; width: var(--ds-icon-size); }
/* 1.22.0 (O3/U6, § 8): FILA QUE ES ENLACE. Cuando la fila es un <div>/<li>
   (no un <a>) porque lleva otro control dentro, .ds-fila--enlace la hace
   pulsable entera: su enlace principal, .ds-fila__destino, se estira a la fila
   con un ::after (inset 0); cualquier otro botón o enlace queda encima y
   funciona. Mismo contrato que la tabla (1.15.2 y 1.21.0): sin input, select
   ni textarea dentro (con uno, el enlace deja de estirarse). Alto: el de la
   .ds-fila, ≥ 44 (--ds-spacing-touch) en todos los anchos, también bajo 720.
   Foco: en la fila entera, nunca en el texto del enlace. */
.ds-fila--enlace { position: relative; cursor: pointer; }
.ds-fila--enlace:hover { background: var(--ds-color-bg-tertiary); }
.ds-fila__destino { color: inherit; text-decoration: none; }
.ds-fila--enlace .ds-fila__destino::after { content: ""; position: absolute; inset: 0; border-radius: inherit; }
.ds-fila--enlace :is(button, a:not(.ds-fila__destino), summary, label) { position: relative; z-index: 1; }
.ds-fila--enlace:has(:is(input, select, textarea)) { cursor: default; }
.ds-fila--enlace:has(:is(input, select, textarea)):hover { background: none; }
.ds-fila--enlace:has(:is(input, select, textarea)) .ds-fila__destino::after { content: none; }
.ds-fila__destino:focus-visible { outline: none; }
.ds-fila--enlace:has(.ds-fila__destino:focus-visible) { outline: var(--ds-app-foco) solid var(--ds-app-foco-color); outline-offset: calc(-1 * var(--ds-app-foco)); }
@media (max-width: 719px) { .ds-fila--enlace { min-height: var(--ds-spacing-touch); } }
/* 1.22.2 (§ 8, Sparring asalto 36): FILA QUE SE LEE. La fila que se ESCANEA
   (una lista de cosas) recorta a una línea; la que se LEE (una cita, una
   explicación) lleva .ds-fila--lectura y no recorta nunca: título y sub parten
   línea, con el mismo tope de ancho de lectura. La meta no cambia. */
.ds-fila--lectura :is(.ds-fila__titulo, .ds-fila__sub) {
  white-space: normal; overflow: visible; text-overflow: clip; overflow-wrap: anywhere;
}

/* ═══ 9. Hoja inferior ═══════════════════════════════════════════════ */
/* <dialog>: en móvil sube desde abajo; en ≥ 720 se centra. La entrada
   la anima @starting-style; app.js solo llama a showModal()/close(). */
.ds-hoja {
  position: fixed; inset: auto 0 0 0; margin: 0; width: 100%; max-width: none;
  max-height: var(--ds-app-hoja-alto); padding: 0; border: 0;
  display: none; flex-direction: column;
  background: var(--ds-color-bg-elevated); color: var(--ds-color-text);
  border-radius: var(--ds-app-radio-tarjeta) var(--ds-app-radio-tarjeta) 0 0;
  transform: translateY(100%);
  transition: transform var(--ds-motion-duration-reveal) var(--ds-motion-easing-musho),
              overlay var(--ds-motion-duration-reveal) allow-discrete,
              display var(--ds-motion-duration-reveal) allow-discrete;
}
.ds-hoja[open] { display: flex; transform: translateY(0); }
@starting-style { .ds-hoja[open] { transform: translateY(100%); } }
.ds-hoja::backdrop, .ds-dialogo::backdrop, .ds-panel::backdrop {
  background: var(--ds-color-bg-overlay); opacity: 0;
  transition: opacity var(--ds-motion-duration-reveal) var(--ds-motion-easing-musho),
              overlay var(--ds-motion-duration-reveal) allow-discrete,
              display var(--ds-motion-duration-reveal) allow-discrete;
}
.ds-hoja[open]::backdrop, .ds-dialogo[open]::backdrop, .ds-panel[open]::backdrop { opacity: 1; }
@starting-style { .ds-hoja[open]::backdrop, .ds-dialogo[open]::backdrop, .ds-panel[open]::backdrop { opacity: 0; } }
.ds-hoja__cabecera {
  display: flex; align-items: center; justify-content: space-between; gap: var(--ds-spacing-2xs);
  padding: var(--ds-spacing-2xs) var(--ds-spacing-2xs) var(--ds-spacing-2xs) var(--ds-spacing-xs);
  border-bottom: var(--ds-app-linea) solid var(--ds-color-border-subtle);
}
.ds-hoja__titulo {
  margin: 0; font-size: var(--ds-typography-size-ui-lg); line-height: var(--ds-typography-line-height-ui-lg); font-weight: 600;
}
.ds-hoja__cuerpo { flex: 1; overflow-y: auto; padding: var(--ds-spacing-xs); display: flex; flex-direction: column; gap: var(--ds-spacing-xs); }
.ds-hoja__pie {
  display: flex; gap: var(--ds-spacing-2xs); justify-content: space-between;
  padding: var(--ds-app-paso-12) var(--ds-spacing-xs) calc(var(--ds-app-paso-12) + env(safe-area-inset-bottom));
  border-top: var(--ds-app-linea) solid var(--ds-color-border-subtle);
}
.ds-hoja__pie .ds-boton--primario { flex: 1; }
@media (min-width: 720px) {
  .ds-hoja {
    inset: 0; margin: auto; width: calc(100% - 2 * var(--ds-spacing-sm)); max-width: var(--ds-app-hoja-max);
    height: fit-content; border-radius: var(--ds-app-radio-tarjeta);
    transform: translateY(var(--ds-spacing-sm)); opacity: 0;
    transition: transform var(--ds-motion-duration-reveal) var(--ds-motion-easing-musho),
                opacity var(--ds-motion-duration-reveal) var(--ds-motion-easing-musho),
                overlay var(--ds-motion-duration-reveal) allow-discrete,
                display var(--ds-motion-duration-reveal) allow-discrete;
  }
  .ds-hoja[open] { transform: translateY(0); opacity: 1; }
  @starting-style { .ds-hoja[open] { transform: translateY(var(--ds-spacing-sm)); opacity: 0; } }
  .ds-hoja__pie { padding-bottom: var(--ds-app-paso-12); }
}

/* ═══ 10. Botón flotante ═════════════════════════════════════════════ */
.ds-fab {
  position: fixed; z-index: var(--ds-app-capa-fab);
  right: var(--ds-spacing-xs);
  bottom: calc(var(--ds-app-barra) + env(safe-area-inset-bottom) + var(--ds-spacing-xs));
  display: flex; align-items: center; justify-content: center;
  width: var(--ds-app-fab); height: var(--ds-app-fab); padding: 0; border: 0;
  border-radius: var(--ds-radius-full); background: var(--ds-app-primario-fondo);
  color: var(--ds-app-primario-texto); box-shadow: var(--ds-shadow-float);
  text-decoration: none; cursor: pointer;
  transition: background-color var(--ds-motion-duration-instant) var(--ds-motion-easing-musho);
}
.ds-fab:hover { background: var(--ds-app-primario-hover); }
@media (min-width: 1056px) { .ds-fab { display: none; } }

/* ═══ 11. Barra inferior ═════════════════════════════════════════════ */
.ds-barra-inferior {
  position: fixed; z-index: var(--ds-app-capa-barra); left: 0; right: 0; bottom: 0;
  display: flex; align-items: stretch;
  height: calc(var(--ds-app-barra) + env(safe-area-inset-bottom));
  padding-bottom: env(safe-area-inset-bottom);
  background: var(--ds-color-bg-elevated);
  border-top: var(--ds-app-linea) solid var(--ds-color-border-subtle);
}
.ds-barra-inferior__item {
  flex: 1 1 0; min-width: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--ds-app-paso-medio);
  color: var(--ds-color-text-tertiary); text-decoration: none;
  transition: color var(--ds-motion-duration-instant) var(--ds-motion-easing-musho);
}
/* Activo = texto en --ds-app-activo-color y en 600, sin fondo: color Y peso
   (§ 11; 1.6, D1). 1.22.0 (O4/P57): el ICONO va en --ds-app-activo-icono
   (marca) en los dos temas, como la barra lateral; un icono pide 3:1 y el
   coral lo da, un texto pide 4,5:1 y no. */
.ds-barra-inferior__item[aria-current], .ds-barra-inferior__item.es-activo { color: var(--ds-app-activo-color); background: none; }
.ds-barra-inferior__item[aria-current] > .ds-icono, .ds-barra-inferior__item.es-activo > .ds-icono { color: var(--ds-app-activo-icono); }
.ds-barra-inferior__item[aria-current] .ds-barra-inferior__texto, .ds-barra-inferior__item.es-activo .ds-barra-inferior__texto { font-weight: 600; }
.ds-barra-inferior__texto {
  max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: var(--ds-typography-size-ui-xs); line-height: var(--ds-typography-line-height-ui-xs); font-weight: 500;
}
@media (min-width: 1056px) { .ds-barra-inferior { display: none; } }

/* ═══ 12. Barra lateral ══════════════════════════════════════════════ */
.ds-sidebar {
  display: none; flex-direction: column; gap: var(--ds-spacing-2xs);
  width: var(--ds-spacing-sidebar); flex: none;
  padding: var(--ds-spacing-xs); background: var(--ds-color-bg-elevated);
}
@media (min-width: 1056px) { .ds-sidebar { display: flex; } }
.ds-sidebar__logo {
  display: flex; align-items: center; gap: var(--ds-spacing-2xs);
  min-height: var(--ds-spacing-control); padding: 0 var(--ds-app-paso-12);
  font-size: var(--ds-typography-size-ui-lg); line-height: var(--ds-typography-line-height-ui-lg);
  font-weight: 600; color: var(--ds-color-text); text-decoration: none;
}
/* 1.6 (P1-P5): orden __logo → __buscar → segmentado → __nav → __pie. El logo
   es un <button aria-expanded> que pliega la barra ENTERA (sin modo iconos);
   su marca mide 28 (--ds-spacing-icon). */
button.ds-sidebar__logo {
  width: 100%; border: 0; border-radius: var(--ds-radius-card); background: none;
  font-family: inherit; text-align: left; cursor: pointer;
  transition: background-color var(--ds-motion-duration-instant) var(--ds-motion-easing-musho);
}
button.ds-sidebar__logo:hover { background: var(--ds-color-bg-tertiary); }
.ds-sidebar__logo > svg, .ds-sidebar__logo > img { flex: none; width: var(--ds-spacing-icon); height: var(--ds-spacing-icon); }
/* 1.18 (§ 12, Yaz 01/10): «Buscar + Ventas/Estudio + pestañas se siente
   pegado». La lupa sube a la derecha del logo (`__cabecera`, un
   .ds-boton--icono terciario con el atajo en el tooltip); el conmutador
   respira 16 por arriba y la navegación arranca tras una línea con 16 a cada
   lado: tres escalones (marca · área · destinos). Retira `__buscar` de
   ancho entero (1.6; lupa a 20 en 1.17, I10). */
.ds-sidebar__cabecera { display: flex; align-items: center; justify-content: space-between; gap: var(--ds-spacing-2xs); }
.ds-sidebar__cabecera > button.ds-sidebar__logo { width: auto; }
.ds-sidebar__cabecera + .ds-segmentado { margin-top: var(--ds-spacing-2xs); }
.ds-sidebar > :is(.ds-sidebar__cabecera, .ds-segmentado) + .ds-sidebar__nav {
  margin-top: var(--ds-spacing-2xs); padding-top: var(--ds-spacing-xs);
  border-top: var(--ds-app-linea) solid var(--ds-color-border-subtle);
}
.ds-sidebar__nav { display: flex; flex-direction: column; gap: var(--ds-app-paso-medio); flex: 1; }
.ds-sidebar__seccion {
  margin: var(--ds-spacing-xs) 0 var(--ds-app-paso-medio); padding: 0 var(--ds-app-paso-12);
  font-size: var(--ds-typography-size-ui-xs); line-height: var(--ds-typography-line-height-ui-xs);
  color: var(--ds-color-text-tertiary); text-transform: uppercase;
  letter-spacing: var(--ds-typography-letter-spacing-label); font-weight: 500;
}
.ds-sidebar__item {
  display: flex; align-items: center; gap: var(--ds-app-paso-12);
  min-height: var(--ds-spacing-control); padding: 0 var(--ds-app-paso-12);
  border-radius: var(--ds-radius-card); color: var(--ds-app-texto-lateral); text-decoration: none;
  font-size: var(--ds-typography-size-ui-sm); line-height: var(--ds-typography-line-height-ui-sm); font-weight: 500;
  transition: background-color var(--ds-motion-duration-instant) var(--ds-motion-easing-musho),
              color var(--ds-motion-duration-instant) var(--ds-motion-easing-musho);
}
.ds-sidebar__item:hover { background: var(--ds-color-bg-tertiary); color: var(--ds-color-text); }
/* 1.15.1 (P48, D5; Notion): activo = la caja del hover, texto en su color
   normal a 600 e icono en el coral de marca. Anula el «sin fondo» de 1.6. */
.ds-sidebar__item[aria-current], .ds-sidebar__item.es-activo { background: var(--ds-color-bg-tertiary); color: var(--ds-color-text); font-weight: 600; }
.ds-sidebar__item[aria-current] > .ds-icono, .ds-sidebar__item.es-activo > .ds-icono { color: var(--ds-app-activo-icono); }
/* Pie (1.6, P7): «Nuevo» primario a todo el ancho y, debajo, la persona. */
.ds-sidebar__pie {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--ds-app-paso-12);
  padding-top: var(--ds-app-paso-12); border-top: var(--ds-app-linea) solid var(--ds-color-border-subtle);
}
.ds-sidebar__pie > .ds-boton--primario { flex: 1 1 100%; }
.ds-sidebar__persona { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.ds-sidebar__nombre {
  font-size: var(--ds-typography-size-ui-sm); line-height: var(--ds-typography-line-height-ui-sm);
  font-weight: 500; color: var(--ds-color-text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ds-sidebar__rol {
  font-size: var(--ds-typography-size-ui-xs); line-height: var(--ds-typography-line-height-ui-xs);
  color: var(--ds-color-text-tertiary);
}
/* Plegada (1.6, P2): html[data-sidebar="plegada"] la quita entera y el
   contenido ocupa el ancho; .ds-sidebar__abrir (botón de icono fijo arriba a
   la izquierda) la devuelve. El estado lo pone ANTES de pintar un <script>
   de tres líneas en <head> (catálogo 12); app.js lo guarda en localStorage. */
.ds-sidebar__abrir { display: none; position: fixed; top: var(--ds-spacing-2xs); left: var(--ds-spacing-2xs); z-index: var(--ds-app-capa-barra); }
/* 1.15.1 (P19): la barra hija del body no se quita de golpe: se desliza (abajo, § maqueta). */
html[data-sidebar="plegada"] .ds-sidebar:not(body > .ds-sidebar) { display: none; }
@media (min-width: 1056px) { html[data-sidebar="plegada"] .ds-sidebar__abrir { display: inline-flex; } }
/* 1.13.1: mientras el botón fijo se ve, la página baja lo que él ocupa
   (2xs + 40 = 48; lg + 2xs = 56 deja aire también en Board y Tareas, que
   no llevan el relleno de #contenido; medidas-ds m) (Asier, 29/09). */
@media (min-width: 1056px) { html[data-sidebar="plegada"] .ds-pagina { padding-block-start: calc(var(--ds-spacing-lg) + var(--ds-spacing-2xs)); } }

/* ═══ 13. Migas ══════════════════════════════════════════════════════ */
.ds-migas ol {
  display: flex; align-items: center; min-width: 0; list-style: none; margin: 0; padding: 0;
  font-size: var(--ds-typography-size-ui-sm); line-height: var(--ds-typography-line-height-ui-sm);
  color: var(--ds-color-text-tertiary);
}
.ds-migas li { display: flex; align-items: center; min-width: 0; white-space: nowrap; }
/* Se recorta PRIMERO la última miga (la página en la que estás, que el
   título de la página repite entera) y las anteriores conservan su texto
   hasta un 40 % del ancho: con todas encogiendo por igual, en un móvil
   salía «Bo… › Clínica…» y no se leía ni de dónde vienes ni dónde estás
   (CRM 2 fase 1, 26/09). */
.ds-migas li:not(:last-child) { flex: 0 0 auto; max-width: 40%; }
.ds-migas li:last-child { flex: 0 1 auto; }
.ds-migas li + li::before { content: "›"; flex: none; padding: 0 var(--ds-spacing-2xs); }
.ds-migas a, .ds-migas span { overflow: hidden; text-overflow: ellipsis; color: inherit; text-decoration: none; }
.ds-migas a:hover { color: var(--ds-color-text-secondary); }
/* 1.17 (§ 13, U4): bajo 720 el enlace de una miga es blanco táctil de 44
   (antes 20): relleno arriba y abajo que el margen negativo devuelve, así
   la línea de migas no crece ni mueve nada. */
@media (max-width: 719px) {
  .ds-migas a {
    padding-block: calc((var(--ds-spacing-touch) - var(--ds-typography-line-height-ui-sm)) / 2);
    margin-block: calc((var(--ds-typography-line-height-ui-sm) - var(--ds-spacing-touch)) / 2);
  }
}
.ds-migas li:last-child, .ds-migas [aria-current] { color: var(--ds-color-text-secondary); }

/* ═══ 14. Estado vacío ═══════════════════════════════════════════════ */
/* 1.4 (reglas.md § 14, § 30.7 Carbon): a la izquierda, como el resto de la
   página; el centrado de la 1.3 queda en .ds-vacio--centrado. */
.ds-vacio {
  display: flex; flex-direction: column; align-items: flex-start; text-align: left;
  gap: var(--ds-spacing-2xs); padding: var(--ds-spacing-lg) var(--ds-spacing-sm);
}
.ds-vacio--centrado { align-items: center; text-align: center; }
.ds-vacio__icono { width: var(--ds-app-icono-vacio); height: var(--ds-app-icono-vacio); color: var(--ds-color-text-tertiary); }
.ds-vacio__titulo {
  margin: var(--ds-spacing-2xs) 0 0; font-size: var(--ds-typography-size-ui-lg);
  line-height: var(--ds-typography-line-height-ui-lg); font-weight: 600; color: var(--ds-color-text);
}
.ds-vacio__texto {
  /* 1.20.1 (§ 0.17, p. 6): el 40ch a pelo pasa al token; mismo ancho que la
     descripción de la pieza que lo contiene. */
  margin: 0 0 var(--ds-spacing-2xs); max-width: var(--ds-app-parrafo-max);
  font-size: var(--ds-typography-size-ui-sm); line-height: var(--ds-typography-line-height-ui-sm);
  color: var(--ds-color-text-secondary);
}

/* ═══ 15. Métrica ════════════════════════════════════════════════════ */
/* 1.12 (decisión 11 b): fila que se llena. Con la rejilla auto-fit, 3 o 5
   cifras dejaban una celda vacía al final de la fila (KPIs y Panel a 393);
   en flex cada fila reparte su ancho y la última ocupa lo que queda. */
.ds-metricas { display: flex; flex-wrap: wrap; gap: var(--ds-spacing-2xs); }
.ds-metricas > * { flex: 1 1 var(--ds-app-metrica-min); min-width: 0; }
.ds-metrica {
  display: flex; flex-direction: column; align-items: flex-start; gap: var(--ds-app-paso-medio);
  padding: var(--ds-spacing-xs); background: var(--ds-color-bg-elevated); border-radius: var(--ds-app-radio-tarjeta);
}
/* 1.8.1 · --texto (Stripe «Hoy»: los KPIs son texto, no tarjetas): cifra +
   etiqueta sin fondo ni radio; se separan por aire, no por borde. 3-4 por
   fila en PC, 2 en móvil. Para la fila de un panel de resumen. */
.ds-metricas--texto { gap: var(--ds-spacing-xs) var(--ds-spacing-sm); }
.ds-metricas--texto > .ds-metrica { padding: 0; background: none; border-radius: 0; }
/* 1.12 (decisión 5, Stripe «Tu resumen»): la métrica a escala de Stripe.
   Etiqueta 14/600 en frase (sin mayúsculas ni tracking), cifra 20/700,
   caption 14/400 secundaria; todas iguales. Antes: etiqueta 12 en
   mayúsculas, cifra 30/600 y la dominante a 48 y dos columnas, que dejaba
   una celda vacía en la fila (medidas-ds.js i y k). */
.ds-metrica__caption {
  margin: 0; font-size: var(--ds-typography-size-ui-sm); line-height: var(--ds-typography-line-height-ui-sm);
  color: var(--ds-color-text); font-weight: 600;
}
.ds-metrica__numero {
  margin: 0; font-size: var(--ds-typography-size-lead); line-height: var(--ds-typography-line-height-ui-lg);
  font-weight: 700; font-variant-numeric: tabular-nums; color: var(--ds-color-text);
}
.ds-metrica__sub {
  margin: 0; font-size: var(--ds-typography-size-ui-sm); line-height: var(--ds-typography-line-height-ui-sm);
  color: var(--ds-color-text-secondary);
}
/* Dominante (CRM 2 · fase 3 → 1.12): UNA por fila, la que pide acción, va
   PRIMERO; desde 1.12 no ensancha ni sube de tamaño (decisión 5: todas
   iguales): solo la colorea su modificador. El color del número dice qué
   hacer: error (rojo) = atrasado, aviso (ámbar) = falta algo, ok (verde) = bien. */
.ds-metrica--dominante { grid-column: auto; }
.ds-metrica--error .ds-metrica__numero { color: var(--ds-color-error-fg); }
/* obsoleta desde 1.7 → usar .ds-metrica--error */
.ds-metrica--alerta .ds-metrica__numero { color: var(--ds-color-error-fg); }
.ds-metrica--aviso .ds-metrica__numero { color: var(--ds-color-warning-fg); }
.ds-metrica--ok .ds-metrica__numero { color: var(--ds-color-success-fg); }

/* ═══ 16. Tabla ══════════════════════════════════════════════════════ */
/* .ds-tabla es el envoltorio con scroll horizontal; dentro, un <table>. */
.ds-tabla { overflow-x: auto; min-width: 0; background: var(--ds-color-bg-elevated); border-radius: var(--ds-app-radio-tarjeta); }
.ds-tabla table { width: 100%; border-collapse: collapse; }
/* 1.14 (§ 16, H4; Untitled § Tabla y lista final n.º 10; Stripe § Lista):
   cabecera EN FRASE, 12/600, sin mayúsculas ni tracking, sobre el fondo
   secundario, 36 de alto (Untitled sm). El texto va en secundario y no en
   terciario: el terciario sobre el gris da < 4,5 en oscuro. Antes: 12/500
   en MAYÚSCULAS con tracking y sin fondo («NOMBRE · CORREO · ROL»). */
.ds-tabla th {
  height: var(--ds-app-tabla-cabecera); padding: 0 var(--ds-spacing-xs); text-align: left;
  font-size: var(--ds-typography-size-ui-xs); line-height: var(--ds-typography-line-height-ui-xs);
  color: var(--ds-color-text-secondary); text-transform: none;
  letter-spacing: normal; font-weight: 600; white-space: nowrap;
  background: var(--ds-app-fondo-secundario);
  border-bottom: var(--ds-app-linea) solid var(--ds-color-border-subtle);
}
/* 1.14 (§ 16, A3): celda que se recorta con «…» (el correo de 60
   caracteres); el texto entero va en title. En la celda o en un span dentro. */
.ds-tabla .ds-tabla__recorte {
  max-width: var(--ds-app-tabla-recorte); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
span.ds-tabla__recorte { display: inline-block; vertical-align: bottom; }
/* 1.14 (§ 16, C3): marco sin piel de una tabla ancha. La tabla hace scroll
   DENTRO de su tarjeta o columna en vez de salirse de ella: min-width 0 para
   que la rejilla o la fila que la contiene no se ensanche hasta la tabla. */
.ds-tabla-marco { display: block; min-width: 0; max-width: 100%; overflow-x: auto; }
.ds-tabla td {
  height: var(--ds-spacing-touch); padding: 0 var(--ds-spacing-xs);
  font-size: var(--ds-typography-size-ui-sm); line-height: var(--ds-typography-line-height-ui-sm);
  color: var(--ds-color-text); white-space: nowrap;
  border-bottom: var(--ds-app-linea) solid var(--ds-color-border-subtle);
}
.ds-tabla tbody tr:last-child td { border-bottom: 0; }
.ds-tabla tbody tr { transition: background-color var(--ds-motion-duration-instant) var(--ds-motion-easing-musho); }
.ds-tabla tbody tr:hover { background: var(--ds-color-bg-tertiary); }
.ds-tabla .ds-tabla__num { text-align: right; font-variant-numeric: tabular-nums; }
/* 1.10 (§ 16, devuelto 1 del 0e): la celda del nombre de una base lleva una
   .ds-fila__titulo (título + etiqueta, como la fila de § 8) con la letra de la
   celda. En una línea: con __nombre el nombre trunca (D4) y la etiqueta queda
   fija a su lado, dentro de la fila también a 393. La sonda celda-mixta
   salta esta celda y ninguna otra. */
.ds-tabla__nombre .ds-fila__titulo { display: flex; align-items: center; gap: var(--ds-spacing-2xs); min-width: 0; font: inherit; white-space: nowrap; }
.ds-tabla__nombre .ds-fila__titulo .ds-etiqueta { flex: none; margin-inline-start: 0; }
/* 1.15.2 (P84, § 16; Biblioteca): fila-enlace. Toda la fila abre su destino:
   el a.ds-tabla__destino de la primera celda se estira a la fila con un
   ::after (inset 0), sin JS; cualquier otro control queda encima y funciona.
   El hover es el de la fila; el foco se pinta en la fila entera. */
.ds-tabla tr.ds-tabla__fila--enlace { position: relative; cursor: pointer; }
.ds-tabla__destino { color: inherit; text-decoration: none; }
.ds-tabla__fila--enlace .ds-tabla__destino::after { content: ""; position: absolute; inset: 0; }
.ds-tabla__fila--enlace :is(button, a:not(.ds-tabla__destino), input, select, .ds-tabla__desplegar) { position: relative; z-index: 1; }
/* 1.21.0 (§ 16): una fila-enlace NO lleva dentro un control que cambie un
   dato. El supuesto de 1.15.2 ("el control queda encima y funciona") solo
   vale si el control CUBRE su celda, y un chip mide lo que su texto: en la
   Biblioteca, el hueco a la derecha de «Activo» navegaba y el chip editaba,
   dentro de la misma celda (medido el 04/10: 3 de 7 columnas cambiaban de
   gesto a mitad de celda). Con un control dentro, el enlace deja de
   estirarse y el cuerpo de la fila no navega: abre el nombre y su
   .ds-tabla__abrir. No es un apaño de la Biblioteca — impide que la
   combinación vuelva por una vista nueva. */
.ds-tabla__fila--enlace:has(:is(input, select, textarea)) { cursor: default; }
.ds-tabla__fila--enlace:has(:is(input, select, textarea)) .ds-tabla__destino::after { content: none; }
/* Abrir la página desde una tabla que se edita en celda: diana de 40 de alto
   al extremo derecho de la celda del nombre, invisible en reposo. FLOTA
   sobre esa celda (absoluta, con el fondo de la fila detrás) en vez de ir en
   el flujo: puesto en el flujo se comía 93 px de nombre SIEMPRE, y con una
   etiqueta al lado dejaba el título en una letra («C… [Desfasado]»). Solo
   tapa el final del nombre mientras se apunta esa fila, que es cuando ya
   decidiste abrirla. Sin ratón no se pinta: no hay con qué apuntar, la celda
   del nombre ocupa casi toda la fila y el nombre ya es la diana. */
.ds-tabla__abrir {
  position: absolute; right: 0; top: 50%; transform: translateY(-50%); z-index: 2;
  background: var(--ds-color-bg); box-shadow: -8px 0 8px -4px var(--ds-color-bg);
  opacity: 0; pointer-events: none;
  transition: opacity var(--ds-motion-duration-instant) var(--ds-motion-easing-musho);
}
.ds-tabla tr:hover .ds-tabla__abrir, .ds-tabla tr:focus-within .ds-tabla__abrir { opacity: 1; pointer-events: auto; }
@media (hover: none) { .ds-tabla__abrir { display: none; } }
@media (prefers-reduced-motion: reduce) { .ds-tabla__abrir { transition: none; } }
.ds-tabla__destino:focus-visible { outline: none; }
.ds-tabla__fila--enlace:has(.ds-tabla__destino:focus-visible) { outline: var(--ds-app-foco) solid var(--ds-app-foco-color); outline-offset: calc(-1 * var(--ds-app-foco)); }
/* Bajo 720 la fila se despliega: button.ds-tabla__desplegar (chevron, gira
   abierto) controla la tr.ds-tabla__detalle siguiente (hidden = plegada),
   que ocupa todas las columnas y enseña los campos como chips (app.js). */
.ds-tabla__desplegar { display: none; }
.ds-tabla__desplegar .ds-icono { transition: transform var(--ds-motion-duration-instant) var(--ds-motion-easing-musho); }
.ds-tabla__desplegar[aria-expanded="true"] .ds-icono { transform: rotate(180deg); }
.ds-tabla .ds-tabla__detalle td { height: auto; padding-block: var(--ds-spacing-2xs) var(--ds-spacing-xs); white-space: normal; }
.ds-tabla__detalle .ds-chips { flex-wrap: wrap; overflow: visible; }
/* 1.21.1 · Un campo de TEXTO LIBRE no va en chip (§ 2: un chip categoriza,
   filtra o se quita). En el detalle va en `.ds-propiedades`, que envuelve y se
   queda dentro de la pantalla. Medido el 05/10 a 393: como chip, una frase de
   310 caracteres daba 2.181 px y arrastraba la tabla en horizontal. */
/* Y la fila entera sale del reparto de columnas (`display: block`). Su
   `colspan` abarca también las columnas que en móvil están ocultas: al
   desplegarse, el navegador las materializa y les reparte el ancho a partes
   iguales, y el nombre caía de 320 a 64 px (320/5) — el título del documento
   desaparecía justo al abrir uno. Fuera del reparto, la celda ya no hereda el
   ancho de la tabla, así que pide el de la pantalla menos el margen de la
   página (16 a cada lado); va en `min-width`, porque el `width` de una celda
   lo sigue imponiendo su fila. Probado y descartado: `min-width` en la
   cabecera del nombre y `table-layout: fixed` — con el `colspan` de por
   medio, ninguno sujeta la columna. Acotado a bajo 720, que es donde esta
   fila existe. */
@media (max-width: 719px) {
  .ds-tabla__detalle { display: block; }
  .ds-tabla__detalle > td { display: block; box-sizing: border-box; min-width: calc(100vw - 32px); }
}
/* La caja de dentro solo agrupa lo que haya (chips y propiedades) y no empuja. */
.ds-tabla__detalle-caja { min-width: 0; }
.ds-tabla__detalle .ds-propiedades { margin-top: var(--ds-aire-parte); }
.ds-tabla__detalle .ds-propiedades__valor { white-space: normal; overflow-wrap: anywhere; text-align: left; }
.ds-tabla__detalle td { max-width: 100vw; }
/* 1.16 (§ 16, P6, D8; Notion): ordenar al clicar el título de columna. La app
   envuelve el texto del th en button.ds-tabla__ordenar (a lo ancho, sin
   borde ni fondo, la letra del th) con su .ds-tabla__flecha (chevron arriba
   = ascendente, abajo = descendente; oculta sin orden) detrás del texto; el
   th ordenado lleva aria-sort y su texto va en 600. Bajo 1056 se toca con el
   dedo: 40 de alto (regla 40/44). */
.ds-tabla__ordenar {
  display: inline-flex; align-items: center; gap: var(--ds-radius-chip);
  width: 100%; min-height: var(--ds-app-tabla-cabecera); margin: 0; padding: 0;
  border: 0; background: none; font: inherit; color: inherit; text-align: inherit;
  cursor: pointer;
}
.ds-tabla__ordenar:focus-visible { outline: var(--ds-app-foco) solid var(--ds-app-foco-color); outline-offset: calc(-1 * var(--ds-app-foco)); }
.ds-tabla__num .ds-tabla__ordenar { justify-content: flex-end; }
.ds-tabla__flecha { flex: none; width: var(--ds-icon-size-sm); height: var(--ds-icon-size-sm); }
/* El icono de tipo de una base (B2) no encoge dentro del botón y guarda sus 6
   hasta el texto (4 del hueco + 2). */
.ds-tabla__ordenar > .ds-tabla__tipo { flex: none; margin-right: calc(var(--ds-radius-bar) - var(--ds-radius-chip)); }
.ds-tabla__flecha[hidden] { display: none; }
:is(.ds-tabla, .ds-tabla--bd) th[aria-sort] { font-weight: 600; color: var(--ds-color-text); }
@media (max-width: 1055px) { .ds-tabla__ordenar { min-height: var(--ds-spacing-control); } }
/* 1.16 (§ 16, P6, D8): ancho de columna arrastrando. span.ds-tabla__asa
   (role=separator, la pone la app en cada th) ocupa los 6 px del borde
   derecho a toda la altura; al pasar, enfocar o arrastrar (:active, o
   [data-arrastrando] si la app lo pone)
   pinta una línea de 2 en el borde fuerte. El th se vuelve contenedor sin
   especificidad (:where) para no pisar una primera columna sticky (sticky ya
   es contenedor del asa). Con anchos puestos por la app (style.width en un
   th de una tabla con data-columnas), la rejilla es fija y la celda recorta
   con «…»: así una columna puede estrecharse por debajo de su contenido
   (mínimo 120, lo aplica la app). */
:where(.ds-tabla th:has(> .ds-tabla__asa)) { position: relative; }
.ds-tabla__asa {
  position: absolute; top: 0; bottom: 0; right: 0; z-index: 2;
  width: calc(var(--ds-app-paso-medio) * 1.5); cursor: col-resize; touch-action: none;
}
.ds-tabla__asa::after {
  content: ""; position: absolute; top: 0; bottom: 0; right: 0; width: calc(var(--ds-app-linea) * 2);
  background: transparent;
}
.ds-tabla__asa:is(:hover, :active, :focus-visible, [data-arrastrando])::after { background: var(--ds-color-border-strong); }
.ds-tabla__asa:focus-visible { outline: none; }
table[data-columnas]:has(> thead th[style*="width"]) { table-layout: fixed; }
table[data-columnas]:has(> thead th[style*="width"]) :is(th, td) { overflow: hidden; text-overflow: ellipsis; }
@media (max-width: 719px) { .ds-tabla__desplegar { display: inline-flex; vertical-align: middle; } }
@media (min-width: 720px) { .ds-tabla__detalle { display: none; } }

/* ═══ 17. Avatar ═════════════════════════════════════════════════════ */
.ds-avatar {
  flex: none; display: inline-flex; align-items: center; justify-content: center; overflow: hidden;
  width: var(--ds-app-avatar); height: var(--ds-app-avatar); border-radius: var(--ds-radius-full);
  background: var(--ds-color-bg-quaternary); color: var(--ds-color-text);
  font-size: var(--ds-typography-size-ui-sm); line-height: 1; font-weight: 600; text-transform: uppercase;
}
.ds-avatar--grande { width: var(--ds-spacing-control); height: var(--ds-spacing-control); }
.ds-avatar img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* ═══ 18. Diálogo ════════════════════════════════════════════════════ */
.ds-dialogo {
  width: calc(100% - 2 * var(--ds-spacing-xs)); max-width: var(--ds-app-dialogo-max);
  margin: auto; padding: 0; border: 0; border-radius: var(--ds-app-radio-tarjeta);
  background: var(--ds-color-bg-elevated); color: var(--ds-color-text);
  box-shadow: var(--ds-shadow-float); opacity: 0; transform: scale(0.96);
  transition: opacity var(--ds-motion-duration-reveal) var(--ds-motion-easing-musho),
              transform var(--ds-motion-duration-reveal) var(--ds-motion-easing-musho),
              overlay var(--ds-motion-duration-reveal) allow-discrete,
              display var(--ds-motion-duration-reveal) allow-discrete;
}
.ds-dialogo[open] { opacity: 1; transform: scale(1); }
@starting-style { .ds-dialogo[open] { opacity: 0; transform: scale(0.96); } }
.ds-dialogo__titulo {
  margin: 0; padding: var(--ds-spacing-sm) var(--ds-spacing-sm) 0;
  font-size: var(--ds-typography-size-ui-lg); line-height: var(--ds-typography-line-height-ui-lg); font-weight: 600;
}
.ds-dialogo__texto {
  margin: 0; padding: var(--ds-spacing-2xs) var(--ds-spacing-sm) 0;
  font-size: var(--ds-typography-size-ui-sm); line-height: var(--ds-typography-line-height-ui-sm);
  color: var(--ds-color-text-secondary);
}
.ds-dialogo__pie {
  display: flex; justify-content: flex-end; gap: var(--ds-spacing-2xs);
  padding: var(--ds-spacing-sm);
}
/* 1.19.0 (§ 0.16): bajo 720 el diálogo lleva 16 de relleno (bloque), como
   el panel; antes 24 siempre. */
@media (max-width: 719px) {
  .ds-dialogo__titulo { padding: var(--ds-aire-bloque) var(--ds-aire-bloque) 0; }
  .ds-dialogo__texto { padding: var(--ds-aire-parte) var(--ds-aire-bloque) 0; }
  .ds-dialogo__pie { padding: var(--ds-aire-bloque); }
}
/* 1.8 (P13, P63): X de cerrar arriba a la derecha, 40×40, centrada con la
   primera línea del título; el título le deja su hueco. Anatomía: título ·
   X · cuerpo · pie (Cancelar + acción). */
.ds-dialogo > [data-cierra] {
  position: absolute;
  top: calc(var(--ds-spacing-sm) + (var(--ds-typography-line-height-ui-lg) - var(--ds-spacing-control)) / 2);
  right: var(--ds-app-paso-12);
}
.ds-dialogo:has(> [data-cierra]) .ds-dialogo__titulo { padding-right: calc(var(--ds-app-paso-12) + var(--ds-spacing-control) + var(--ds-spacing-2xs)); }

/* ═══ 19. Icono ══════════════════════════════════════════════════════ */
.ds-icono {
  flex: none; display: block;
  width: var(--ds-icon-size); height: var(--ds-icon-size);
  fill: none; stroke: currentColor; stroke-width: var(--ds-icon-stroke);
  stroke-linecap: round; stroke-linejoin: round; color: inherit;
}
.ds-icono--sm { width: var(--ds-icon-size-sm); height: var(--ds-icon-size-sm); }
.ds-icono--lg { width: var(--ds-icon-size-lg); height: var(--ds-icon-size-lg); }
/* Móvil (1.6, P45, Apple): el icono que se pulsa con el dedo mide 24; el 16
   (--sm) queda para dentro de texto, chips y etiquetas. */
@media (max-width: 719px) {
  .ds-boton--icono .ds-icono, .ds-barra-inferior .ds-icono, .ds-cabecera .ds-icono, .ds-arbol .ds-icono {
    width: var(--ds-icon-size-lg); height: var(--ds-icon-size-lg);
  }
}

/* ═══ 20. Cabecera móvil ═════════════════════════════════════════════ */
/* Arriba en móvil y tablet: (avatar) Título (acciones). El título es el
   de la SECCIÓN (de dónde vienes, como Apple); el <h1> de la página dice
   dónde estás. Pegada arriba con sticky y no fixed: así no pide un hueco
   de relleno en cada página. Oculta desde 1056 (allí manda la barra
   lateral). Los botones de las zonas son .ds-boton--terciario --icono. */
.ds-cabecera {
  position: sticky; top: 0; z-index: var(--ds-app-capa-barra);
  display: flex; align-items: center; gap: var(--ds-spacing-2xs);
  min-height: calc(var(--ds-spacing-control) + 2 * var(--ds-spacing-2xs));
  padding: calc(var(--ds-spacing-2xs) + env(safe-area-inset-top)) var(--ds-spacing-xs) var(--ds-spacing-2xs);
  background: var(--ds-color-bg-elevated); color: var(--ds-color-text);
  border-bottom: var(--ds-app-linea) solid var(--ds-color-border-subtle);
}
.ds-cabecera__inicio, .ds-cabecera__acciones {
  flex: none; display: flex; align-items: center; gap: var(--ds-app-paso-medio);
  min-width: var(--ds-spacing-control);
}
/* 1.8.2: a la derecha aunque no haya título (margin-left: auto en la pieza). */
.ds-cabecera__acciones { justify-content: flex-end; margin-left: auto; }
.ds-cabecera__titulo {
  flex: 1; min-width: 0; margin: 0; text-align: center;
  font-size: var(--ds-typography-size-ui-md); line-height: var(--ds-typography-line-height-ui-md);
  font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* 1.10.1 (j, § 20 y § 33): la cabecera comparte la key line de la página.
   Relleno lateral 16 (antes 8, y el avatar caía en x 8-12); el avatar de
   __inicio mide 40 (la medida de --grande) y llena el .ds-boton--icono que
   lo envuelve, 40×40 exactos, sin relleno: el círculo cae en 16 y el botón
   redondo de la derecha a 16 del borde. Apple HIG: la barra va al margen. */
.ds-cabecera__inicio .ds-avatar { width: var(--ds-spacing-control); height: var(--ds-spacing-control); }
.ds-cabecera__inicio .ds-boton--icono:has(> .ds-avatar) {
  width: var(--ds-spacing-control); height: var(--ds-spacing-control); min-height: 0; padding: 0;
  border-radius: var(--ds-radius-full);
}
@media (min-width: 1056px) { .ds-cabecera { display: none; } }

/* ═══ 21. Título de página ════════════════════════════════════════════ */
/* Cabecera de un detalle (la ficha de un lead): caption (qué es) → h1
   (cuál) → acciones (lo que se hace ya) → meta (el resto). El caption
   calca el de la tarjeta; una etiqueta de estado puede ir a su lado. */
.ds-titulo { display: flex; flex-direction: column; gap: var(--ds-spacing-2xs); min-width: 0; }
.ds-titulo__caption {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--ds-spacing-2xs); margin: 0;
  font-size: var(--ds-typography-size-ui-xs); line-height: var(--ds-typography-line-height-ui-xs);
  color: var(--ds-color-text-tertiary);
  font-weight: 500;
}
.ds-titulo__h1 {
  margin: 0; font-size: var(--ds-typography-size-h3); line-height: var(--ds-typography-line-height-h3);
  letter-spacing: var(--ds-typography-letter-spacing-h3); font-weight: 600;
  color: var(--ds-color-text); overflow-wrap: anywhere;
}
.ds-titulo__acciones { display: flex; flex-wrap: wrap; gap: var(--ds-spacing-2xs); }
/* 1.14.1 (§ 21): los datos del título caben en el ancho de texto (62ch)
   como todo texto secundario; a 1920 un cliente largo pasaba de 95. */
.ds-titulo__meta {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--ds-app-paso-medio) var(--ds-app-paso-12);
  max-width: var(--ds-app-texto-max);
  margin: 0; font-size: var(--ds-typography-size-ui-sm); line-height: var(--ds-typography-line-height-ui-sm);
  color: var(--ds-color-text-secondary);
}
/* Subtítulo: UNA frase de prosa bajo el h1 (qué es esta página). Es bloque,
   no flex (el __meta es para datos sueltos: quién, fecha, etiqueta) y cabe en
   una columna de lectura. Lo pidieron las tres pantallas del CRM que lo
   usaban como intro (1.5.1, 27/09). */
.ds-titulo__sub {
  margin: 0; max-width: var(--ds-app-texto-max);
  font-size: var(--ds-typography-size-ui-sm); line-height: var(--ds-typography-line-height-ui-sm);
  color: var(--ds-color-text-secondary);
}
/* PC = dos columnas (1.6, § 21, P34 Untitled): caption, h1, sub y meta a la
   izquierda; las acciones a la derecha, arriba, a la altura del h1. En < 720
   siguen debajo, como antes. */
@media (min-width: 720px) {
  /* 1.14 (§ 21, B1): el h1 no se aplasta contra sus acciones. Conserva el
     60 % del ancho; si las acciones no caben en el resto, parten línea dentro
     de su columna (a 1090 el h1 del lead partía en 4 líneas y el de la tarea
     en 6). */
  .ds-titulo { display: grid; grid-template-columns: minmax(min(60%, 100%), 1fr) auto; column-gap: var(--ds-spacing-sm); row-gap: 0; align-items: start; }
  .ds-titulo > * { grid-column: 1; }
  .ds-titulo > * + *:not(.ds-titulo__acciones) { margin-top: var(--ds-spacing-2xs); }
  .ds-titulo > .ds-titulo__acciones { grid-column: 2; grid-row: 1 / span 5; justify-content: flex-end; }
  .ds-titulo:has(> .ds-titulo__caption:first-child) > .ds-titulo__acciones { grid-row: 2 / span 4; margin-top: var(--ds-spacing-2xs); }
}
/* 1.8 (contrato a de CRM 3): en < 720 crear vive en la cabecera («+»
   redondo, § 20), no bajo el h1. El primario de crear lleva
   data-movil="cabecera" y aquí se oculta; si era lo único, se va la fila. */
@media (max-width: 719px) {
  /* 1.9 (P34): en móvil las acciones van DEBAJO de la descripción: caption ·
     h1 · sub · acciones · meta, sea cual sea el orden del markup. */
  .ds-titulo > .ds-titulo__acciones { order: 1; }
  .ds-titulo > .ds-titulo__meta { order: 2; }
  .ds-titulo__acciones [data-movil="cabecera"] { display: none; }
  /* 1.9 (P31, § 23): el crear de una vista de datos vive en la barra; en
     móvil también es el «+» de la cabecera. */
  .ds-vista-barra__acciones [data-movil="cabecera"] { display: none; }
  .ds-titulo__acciones:not(:has(> :not([data-movil="cabecera"]))) { display: none; }
}
/* 1.10 (defecto 8): dentro de un .ds-doc (inicio de Documentos) el caption,
   el sub y el h1 del título son p y h1, y cogían los márgenes del documento
   (.ds-doc p 12 abajo, .ds-doc h1 30 arriba): 50 px entre caption y h1 en
   móvil, 20 en PC. El título es una pieza: su ritmo lo pone ella (gap 8). */
.ds-doc .ds-titulo > * { margin-bottom: 0; }
.ds-doc .ds-titulo > :first-child { margin-top: 0; }
@media (max-width: 719px) { .ds-doc .ds-titulo > * { margin-top: 0; } }
/* 1.10.1 (d, § 21): y su TAMAÑO también. .ds-doc h1 (0,1,1) pisaba
   .ds-titulo__h1 (0,1,0) y el título de /documentos medía el h1 del
   documento, no 32. El título conserva su medida donde viva; bajo 720,
   igual que fuera del documento (esta regla gana a la escala móvil A17). */
.ds-doc .ds-titulo__h1 {
  font-size: var(--ds-typography-size-h3); line-height: var(--ds-typography-line-height-h3);
  letter-spacing: var(--ds-typography-letter-spacing-h3); font-weight: 600;
}

/* Movimiento reducido: los tokens de duración no lo apagan solos, así
   que aquí se lleva a 0 todo lo de .ds-. */
@media (prefers-reduced-motion: reduce) {
  [class*="ds-"], [class*="ds-"]::backdrop {
    transition-duration: 0s !important; animation-duration: 0s !important;
  }
  /* 1.15.1: la rejilla de la maqueta vive en el body, que no lleva clase ds-. */
  html body:has(> .ds-sidebar) { transition: none; }
}

/* ═══ 22. Documento ══════════════════════════════════════════════════ */
/* Piezas de Notion, familia A (1.2, 26/09/2026): Documentos y Biblioteca.
   Mergeadas desde design-system/propuesta/piezas-notion.css (spec
   SPEC-DS-NOTION-PIEZAS.md; valores de notion/huecos.md). Notion da
   geometría, ritmo y relaciones; el color, los roles del DS. Lo que ya
   existía arriba (migas) lleva modificador (--doc) y la base no cambia;
   lo nuevo, nombre nuevo. Sin JS: el desplegable es <details>, el to-do un
   checkbox real. Base ≥ 720, ajustes < 720. */

/* ═══ A1. Marco de lectura (regla 2) ═══ */
.ds-doc {
  max-width: var(--ds-spacing-lectura); margin-inline: auto;
  font-family: var(--ds-typography-family-sans), system-ui, sans-serif;
  font-size: var(--ds-doc-cuerpo); line-height: var(--ds-doc-linea);
  color: var(--ds-color-text);
}
@media (max-width: 719px) { .ds-doc { max-width: var(--ds-spacing-lectura-movil); } }

/* ═══ A2. Título de página (40/700, icono de caja 78) ═══ */
.ds-doc__icono {
  display: grid; place-items: center;
  width: var(--ds-app-icono-pagina); height: var(--ds-app-icono-pagina);
  margin-bottom: var(--ds-spacing-2xs); border-radius: var(--ds-radius-block);
  background: var(--ds-color-bg-tertiary); font-size: var(--ds-doc-titulo); line-height: 1;
}
.ds-doc__titulo {
  margin: 0 0 var(--ds-spacing-2xs);
  font-size: var(--ds-doc-titulo); line-height: 1.2;
  font-weight: var(--ds-doc-peso-titulo); color: var(--ds-color-text);
}

/* ═══ A3. Encabezados y párrafo (reglas 3 y 4) ═══ */
.ds-doc h1, .ds-doc h2, .ds-doc h3, .ds-doc h4 {
  font-weight: var(--ds-doc-peso-encabezado); line-height: 1.3; color: var(--ds-color-text);
}
.ds-doc h1 { font-size: var(--ds-doc-h1); margin: var(--ds-doc-sobre-h1) 0 var(--ds-doc-bajo-h1); }
.ds-doc h2 { font-size: var(--ds-doc-h2); margin: var(--ds-doc-sobre-h2) 0 var(--ds-doc-bajo-h2); }
.ds-doc h3 { font-size: var(--ds-doc-h3); margin: var(--ds-doc-sobre-h3) 0 var(--ds-doc-bajo-h3); }
.ds-doc h4 { font-size: var(--ds-doc-h4); margin: var(--ds-doc-sobre-h4) 0 var(--ds-doc-bajo-h4); }
.ds-doc p { margin: 0 0 var(--ds-doc-parrafo); }

/* ═══ A4. Enlace en tinta (regla 5: la marca no entra en el párrafo) ═══ */
.ds-doc a {
  color: inherit;
  text-decoration: underline;
  text-decoration-thickness: .8px; text-underline-offset: .1em;
  text-decoration-color: color-mix(in srgb, currentColor 40%, transparent);
}
.ds-doc a:hover { text-decoration-color: currentColor; }

/* ═══ A5. Listas (26 px por nivel, 2 px entre puntos) ═══ */
.ds-doc ul, .ds-doc ol { margin: 0 0 var(--ds-doc-parrafo); padding-left: var(--ds-doc-lista-sangria); }
.ds-doc li + li { margin-top: var(--ds-doc-lista-hueco); }
.ds-doc li > ul, .ds-doc li > ol { margin: var(--ds-doc-lista-hueco) 0 0; }
.ds-doc li::marker { color: var(--ds-color-text); }
/* La prosa no alcanza a las migas (1.22.0, P66): dentro de un .ds-doc la
   sangría de lista y el subrayado de enlace movían «Documentos» 26 px fuera
   de la key line del contenido. */
.ds-doc .ds-migas ol { margin: 0; padding-left: 0; }
.ds-doc .ds-migas li + li { margin-top: 0; }
.ds-doc .ds-migas a { text-decoration: none; }

/* ═══ A6. Código en línea (tinta sobre bg-tertiary, mono, nunca en rojo) ═══ */
.ds-codigo {
  font-family: var(--ds-typography-family-mono), ui-monospace, monospace;
  font-size: .85em; padding: .2em .4em; border-radius: var(--ds-radius-chip);
  background: var(--ds-color-bg-tertiary); color: var(--ds-color-text);
}

/* ═══ A7. To-do (casilla 16, marcada = texto tachado) ═══ */
.ds-todo { list-style: none; padding-left: 0; margin: 0 0 var(--ds-doc-parrafo); }
.ds-todo li { display: flex; align-items: flex-start; gap: var(--ds-spacing-2xs); }
.ds-todo input {
  flex: none; margin: calc((var(--ds-doc-linea) - var(--ds-icon-size-sm)) / 2) 0 0;
  width: var(--ds-icon-size-sm); height: var(--ds-icon-size-sm);
  accent-color: var(--ds-color-info); cursor: pointer;
}
.ds-todo input:checked + span { text-decoration: line-through; color: var(--ds-color-text-tertiary); }

/* ═══ A8. Desplegable (<details>, triángulo en tinta) ═══ */
/* 1.17 (T9, T3): lo que contiene va a 20 de interlínea (ui-sm) y no hereda
   el 1,5 del body; el summary, 16/24/600 en todo sitio. */
.ds-desplegable { margin: 0 0 var(--ds-doc-parrafo); line-height: var(--ds-typography-line-height-ui-sm); }
/* 1.14.2 (§ 33): dentro de una sección el aire lo pone su gap (Ruta: 28 entre dos desplegables). */
/* 1.19.0 (§ 0.16): en una tarjeta tampoco: el aire lo pone su gap. */
.ds-seccion > .ds-desplegable, .ds-tarjeta > .ds-desplegable { margin: 0; }
.ds-desplegable > summary {
  display: flex; align-items: flex-start; gap: var(--ds-radius-chip);
  list-style: none; cursor: pointer;
  font-size: var(--ds-typography-size-ui-md); line-height: var(--ds-typography-line-height-ui-md); font-weight: 600;
}
.ds-desplegable > summary::-webkit-details-marker { display: none; }
.ds-desplegable > summary::before {
  content: "▸"; flex: none; width: var(--ds-icon-size-lg); text-align: center;
  color: var(--ds-color-text);
  transition: transform var(--ds-motion-duration-move) var(--ds-motion-easing-musho);
}
.ds-desplegable[open] > summary::before { transform: rotate(90deg); }
.ds-desplegable > :not(summary) { margin: var(--ds-doc-lista-hueco) 0 0 calc(var(--ds-icon-size-lg) + var(--ds-radius-chip)); }
/* 1.19.6 (Yaz 02/10, alineacion.js): con icono en el summary, el contenido
   entra lo de la flecha Y lo del icono, a ras del texto del summary (como un
   desplegable de Notion). Antes 28 frente al texto a 56 («Campos de la base»). */
.ds-desplegable:has(> summary > .ds-icono) > :not(summary) { margin-inline-start: calc(2 * (var(--ds-icon-size-lg) + var(--ds-radius-chip))); }

/* ═══ A9. Aviso / callout (relleno 12, radio 10, icono 24 + 6) ═══ */
.ds-aviso {
  display: flex; align-items: flex-start; gap: calc(var(--ds-spacing-2xs) * .75);
  margin: 0 0 var(--ds-doc-parrafo); padding: calc(var(--ds-spacing-2xs) * 1.5);
  border-radius: var(--ds-radius-block); background: var(--ds-color-bg-tertiary); color: var(--ds-color-text);
}
.ds-aviso__icono { flex: none; width: var(--ds-icon-size-lg); text-align: center; font-size: var(--ds-doc-cuerpo); }
.ds-aviso > p, .ds-aviso > div { margin: 0; min-width: 0; }
.ds-aviso--info { background: var(--ds-color-info-soft); }
.ds-aviso--neutro { background: var(--ds-color-bg-tertiary); }
/* obsoleta desde 1.7 → usar .ds-aviso--neutro */
.ds-aviso--gris { background: var(--ds-color-bg-tertiary); }
.ds-aviso--aviso { background: var(--ds-color-warning-soft); }
.ds-aviso--ok { background: var(--ds-color-success-soft); }

/* ═══ A10. Cita (raya de 3 en tinta, texto en tinta) ═══ */
.ds-cita {
  margin: 0 0 var(--ds-doc-parrafo); padding: 0 calc(var(--ds-spacing-2xs) * 1.75);
  border-left: var(--ds-app-raya) solid var(--ds-color-text); color: var(--ds-color-text);
}
.ds-cita p { margin: 0; }

/* ═══ A11. Separador (bloque de 13 con línea de 1 al 11 %) ═══ */
.ds-separador {
  height: var(--ds-app-separador); margin: 0 0 var(--ds-doc-parrafo); border: 0;
  background: linear-gradient(var(--ds-color-border), var(--ds-color-border)) center / 100% var(--ds-app-linea) no-repeat;
}

/* ═══ A12. Caja de código (radio 8, relleno 24/22, mono 14/20; 1.17) ═══ */
.ds-bloque-codigo {
  margin: 0 0 var(--ds-doc-parrafo); padding: var(--ds-app-codigo-y) var(--ds-app-codigo-x);
  /* 1.17 (§ 57, T12/R6): escala ui (14/20, antes 0,85 em = 13,6/20,4) y radio de control (8, antes 10). */
  border-radius: var(--ds-app-radio-control); background: var(--ds-color-bg-tertiary); color: var(--ds-color-text);
  font-family: var(--ds-typography-family-mono), ui-monospace, monospace;
  font-size: var(--ds-typography-size-ui-sm); line-height: var(--ds-typography-line-height-ui-sm); overflow-x: auto; white-space: pre;
}

/* ═══ A13. Tabla simple (celda 35, relleno 7/9, bordes al 11 %) ═══ */
.ds-tabla-simple { width: 100%; margin: 0 0 var(--ds-doc-parrafo); border-collapse: collapse; }
.ds-tabla-simple th, .ds-tabla-simple td {
  height: var(--ds-app-celda-simple); padding: var(--ds-app-celda-y) var(--ds-app-celda-x);
  border: var(--ds-app-linea) solid var(--ds-color-border); text-align: left; vertical-align: top;
}
.ds-tabla-simple th { background: var(--ds-color-bg-elevated); font-weight: 500; }

/* ═══ A14. Columnas (hueco 46; a < 720 se apilan) ═══ */
.ds-columnas {
  display: grid; grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr);
  gap: var(--ds-app-columnas-hueco); margin: 0 0 var(--ds-doc-parrafo);
}
.ds-columnas > * > :last-child { margin-bottom: 0; }
@media (max-width: 719px) { .ds-columnas { grid-auto-flow: row; gap: var(--ds-doc-parrafo); } }

/* ═══ A15. Migas de documento (44, separador «/» tenue, enlace máx. 160) ═══ */
.ds-migas--doc { display: flex; align-items: center; min-height: var(--ds-app-migas); }
.ds-migas--doc ol { color: var(--ds-color-text-secondary); }
.ds-migas--doc li + li::before { content: "/"; color: var(--ds-color-border-strong); }
.ds-migas--doc li:not(:last-child) { max-width: var(--ds-app-miga-max); }
.ds-migas--doc li:last-child, .ds-migas--doc [aria-current] { color: var(--ds-color-text); }

/* ═══ A16. Enlace a página hija (línea, icono 24, sin subrayado) ═══ */
.ds-doc a.ds-pagina-hija, .ds-pagina-hija {
  display: flex; align-items: center; gap: var(--ds-radius-chip);
  margin: 0 0 var(--ds-doc-lista-hueco); padding: var(--ds-doc-lista-hueco) 0;
  color: var(--ds-color-text); font-weight: 500; text-decoration: none;
  border-radius: var(--ds-radius-chip);
}
.ds-pagina-hija:hover { background: var(--ds-color-bg-tertiary); }
.ds-pagina-hija__icono { flex: none; width: var(--ds-icon-size-lg); text-align: center; }

/* ═══ A17. Escala de documento a < 720 (regla 3, medida a 390) ═══ */
/* tokens.css se resuelve a 1440: la escala de móvil viaja como tokens
   --ds-doc-*-movil y se aplica aquí, pieza a pieza. */
@media (max-width: 719px) {
  .ds-doc { line-height: var(--ds-doc-linea-movil); }
  .ds-doc__icono, .ds-doc__titulo { font-size: var(--ds-doc-titulo-movil); }
  .ds-doc h1 { font-size: var(--ds-doc-h1-movil); }
  .ds-doc h2 { font-size: var(--ds-doc-h2-movil); }
  .ds-doc h3 { font-size: var(--ds-doc-h3-movil); }
  .ds-doc h4 { font-size: var(--ds-doc-h4-movil); }
  .ds-todo input { margin-top: calc((var(--ds-doc-linea-movil) - var(--ds-icon-size-sm)) / 2); }
}

/* ═══ 23. Vistas de datos ════════════════════════════════════════════ */
/* Piezas de Notion, familia B (1.2, 26/09/2026): Pipeline, Tareas, Hoy,
   fichas. Densidad de interfaz: se escanea a 14 px y la meta a 12. Lo que
   ya existía arriba (pestañas, tabla, chip, tarjeta, lista, vacío) lleva
   modificador (--vista, --bd) y su base NO cambia: las pantallas 0-2a la
   usan. El color de estado es el tono de chip (--tono, ver :root). */

/* ═══ B1. Pestañas de vista (1.8, P41: subrayadas como la base) ═══
   La píldora se retira: la vista activa lleva el mismo subrayado que la
   pestaña (Stripe). Solo cambia el alto: 40, el de la barra de vista, para
   alinearse con sus herramientas. */
.ds-pestanas--vista, .ds-pestanas--vista .ds-pestanas__item { min-height: var(--ds-app-vista-barra); }

/* ═══ B2. Tabla de base (cabecera 36 con icono de tipo, fila 37, 14 px) ═══ */
.ds-tabla--bd { background: transparent; border-radius: 0; }
.ds-tabla--bd th {
  height: var(--ds-app-tabla-cabecera); padding: 0 var(--ds-spacing-2xs); background: transparent;
  font-size: var(--ds-typography-size-ui-sm); line-height: var(--ds-typography-line-height-ui-sm);
  font-weight: 400; color: var(--ds-color-text-secondary);
  text-transform: none; letter-spacing: normal;
}
.ds-tabla--bd td {
  height: var(--ds-app-tabla-fila); padding: 0 var(--ds-spacing-2xs);
  font-size: var(--ds-typography-size-ui-sm); line-height: var(--ds-typography-line-height-ui-sm);
}
.ds-tabla--bd th + th, .ds-tabla--bd td + td { border-left: var(--ds-app-linea) solid var(--ds-color-border-subtle); }
.ds-tabla--bd th:first-child, .ds-tabla--bd td:first-child { width: var(--ds-spacing-sidebar); }
.ds-tabla--bd td:first-child { font-weight: 500; }
.ds-tabla--bd tbody tr:last-child td { border-bottom: var(--ds-app-linea) solid var(--ds-color-border-subtle); }
.ds-tabla--bd .ds-tabla__num { text-align: right; font-variant-numeric: tabular-nums; }
/* El icono de tipo se dibuja como todo icono (§ 19): trazo, sin relleno.
   1.9: sin esto el <svg> del sprite salía relleno (cuadros negros). */
.ds-tabla__tipo {
  width: var(--ds-icon-size-sm); height: var(--ds-icon-size-sm);
  margin-right: var(--ds-radius-bar); vertical-align: text-bottom; color: var(--ds-color-text-tertiary);
  fill: none; stroke: currentColor; stroke-width: var(--ds-icon-stroke);
  stroke-linecap: round; stroke-linejoin: round;
}
/* Agrupada: cabecera de grupo 42, 12 de aire debajo. */
.ds-tabla--bd .ds-tabla__grupo td {
  height: var(--ds-app-tabla-grupo); padding-top: calc(var(--ds-spacing-2xs) * 1.5);
  vertical-align: bottom; border-left: 0; font-weight: 500;
}
.ds-tabla--bd .ds-tabla__grupo:hover { background: transparent; }
@media (max-width: 719px) {
  /* 1.14.3: la cabecera conserva los 14 px del PC (el CRM la fijaba así); baja solo el interlineado. */
  .ds-tabla--bd th, .ds-tabla--bd td { line-height: var(--ds-typography-line-height-ui-xs); }
  .ds-tabla--bd td { font-size: var(--ds-typography-size-ui-xs); height: var(--ds-app-tabla-fila-movil); }
}

/* ═══ B3. Formatos: fecha larga y euros a la derecha ═══ */
.ds-fecha { white-space: nowrap; }
.ds-euro { display: block; text-align: right; white-space: nowrap; font-variant-numeric: tabular-nums; }

/* ═══ B4. Chip de base (20 px, 14 px, radio 4, tinta POR COLOR) ═══ */
.ds-chip--bd, .ds-chip--bd:hover {
  height: var(--ds-app-chip-bd); padding: 0 var(--ds-app-chip-bd-x);
  border-radius: var(--ds-radius-chip); line-height: var(--ds-app-chip-bd);
  font-size: var(--ds-typography-size-ui-sm); font-weight: 400;
  background: var(--tono-soft); color: var(--tono); cursor: default;
}
/* 1.17 (§ 2, T5/R7): el chip de selección es UNA receta en todo sitio:
   14/20/400, radio 4, sin borde. Con el doble de clase gana a la receta
   «.chip» de un CSS de app (Biblioteca y Documento: radio 3 y borde de 1).
   La medida sm (12/18) la pone la tarjeta de tablero, y solo ella (B6). */
.ds-chip.ds-chip--bd { border: 0; border-radius: var(--ds-radius-chip); }
/* El tono: la clase solo fija --tono; lo leen chip, columna, portada, barra y gráfico. */
.ds-chip--gris { --tono: var(--ds-color-chip-gris); --tono-soft: var(--ds-color-chip-gris-soft); }
.ds-chip--marron { --tono: var(--ds-color-chip-marron); --tono-soft: var(--ds-color-chip-marron-soft); }
.ds-chip--naranja { --tono: var(--ds-color-chip-naranja); --tono-soft: var(--ds-color-chip-naranja-soft); }
.ds-chip--amarillo { --tono: var(--ds-color-chip-amarillo); --tono-soft: var(--ds-color-chip-amarillo-soft); }
.ds-chip--verde { --tono: var(--ds-color-chip-verde); --tono-soft: var(--ds-color-chip-verde-soft); }
.ds-chip--azul { --tono: var(--ds-color-chip-azul); --tono-soft: var(--ds-color-chip-azul-soft); }
.ds-chip--morado { --tono: var(--ds-color-chip-morado); --tono-soft: var(--ds-color-chip-morado-soft); }
.ds-chip--rosa { --tono: var(--ds-color-chip-rosa); --tono-soft: var(--ds-color-chip-rosa-soft); }
.ds-chip--rojo { --tono: var(--ds-color-chip-rojo); --tono-soft: var(--ds-color-chip-rojo-soft); }

/* ═══ B5. Tablero (columna 276 tintada al 4 %, hueco 12; < 720 APILA) ═══ */
.ds-tablero {
  display: flex; align-items: flex-start; gap: calc(var(--ds-spacing-2xs) * 1.5);
  overflow-x: auto; padding-bottom: var(--ds-spacing-2xs);
}
.ds-tablero__columna {
  flex: none; display: flex; flex-direction: column; gap: var(--ds-aire-parte);
  width: var(--ds-app-tablero-columna); padding: 0 var(--ds-spacing-2xs) var(--ds-spacing-2xs);
  border-radius: var(--ds-app-radio-tarjeta);
  background: color-mix(in srgb, var(--tono) 4%, transparent);
}
.ds-tablero__cabecera {
  display: flex; align-items: center; gap: var(--ds-spacing-2xs);
  min-height: var(--ds-app-vista-barra);
}
/* 1.7.2: contador y pie en text-secondary: text-tertiary da 4,3 sobre el tinte de columna. */
/* 1.15.3 (P58): el número de la columna es .ds-contador (§ 75); se borra .ds-tablero__contador. */
.ds-tablero__pie {
  display: flex; align-items: center; gap: var(--ds-radius-chip);
  min-height: var(--ds-spacing-control); padding: 0 var(--ds-spacing-2xs);
  border: 0; border-radius: var(--ds-app-radio-tarjeta); /* 1.17 (R6): 12, es parte de la columna */ background: transparent;
  font: inherit; font-size: var(--ds-typography-size-ui-sm); color: var(--ds-color-text-secondary);
  cursor: pointer; text-align: left;
}
.ds-tablero__pie:hover { background: var(--ds-color-bg-tertiary); color: var(--ds-color-text); }
@media (max-width: 719px) {
  .ds-tablero { flex-direction: column; align-items: stretch; overflow-x: visible; }
  .ds-tablero__columna { width: auto; }
}
/* 1.7.2: dedo, no ratón. Hasta 1056 la app es táctil (cabecera móvil, § 0.8):
   controles de la tarjeta de tablero, pie de columna, título-enlace y
   botones --sm de la barra de vista a 40 (--ds-spacing-control); también el
   segmentado que navega, el botón de fila y el chip que es enlace (a.ds-chip):
   el chip estático sigue a 32, no se pulsa (28/09, /hoy en el CRM). */
@media (max-width: 1055px) {
  .ds-boton--fila, a.ds-chip { min-height: var(--ds-spacing-control); }
  /* 1.14 (§ 3): el segmentado de página a alto de dedo. Cada ítem mide el
     alto de control (40, el mínimo táctil del banco); con el marco, 48.
     1.14.0 lo dejaba en 44 CON el marco y el ítem en 36 (banco rojo). */
  .ds-segmentado__item { min-height: var(--ds-spacing-control); }
  a.ds-chip { height: auto; }
  .ds-tablero__pie { min-height: var(--ds-spacing-control); }
  .ds-tarjeta--bd .ds-boton--sm, .ds-vista-barra .ds-boton--sm { min-height: var(--ds-spacing-control); }
  .ds-tarjeta--bd .ds-boton--icono.ds-boton--sm { width: var(--ds-spacing-control); }
  .ds-tarjeta--bd .ds-tarjeta__titulo { display: flex; align-items: center; min-height: var(--ds-spacing-control); }
}

/* ═══ B6. Tarjeta de base (radio 12 desde 1.16, P11/D5; antes 10) ═══ */
/* 1.19.0 (§ 0.16): líneas a 8 (parte; antes 6) y relleno 12/12 (antes 10/12). */
.ds-tarjeta--bd {
  gap: var(--ds-aire-parte);
  padding: var(--ds-app-tarjeta-y) var(--ds-app-tarjeta-x);
  border-radius: var(--ds-app-radio-tarjeta);
  /* 1.14.2 (§ 7, C12): anillo + sombra xs como toda tarjeta; la sombra de
     capas hacía del Board la única página con tarjetas que flotan. */
  box-shadow: var(--ds-app-anillo), var(--ds-app-sombra-xs);
}
.ds-tablero .ds-tarjeta--bd { width: var(--ds-app-tablero-tarjeta); }
@media (max-width: 719px) { .ds-tablero .ds-tarjeta--bd { width: auto; } }
/* 1.17 (§ 7, T4) → 1.20.0 (§ 0.17, Yaz 03/10): TRES medidas de título de
   tarjeta, con nombre. «De página» (la base, 18/28/600: Hoy), «compacta»
   (tablero, 14/20/600) y «de panel» (panel de métricas, 16/24/600). El 500 que
   llevaba la de tablero sale. La de panel sube de 14 a 16 porque el nombre de
   su fila (16) era MAYOR que el título de su tarjeta y el título de su vacío se
   leía a la misma talla y color (§ 0.17, reglas 2 y 3). */
.ds-tarjeta--bd .ds-tarjeta__titulo {
  font-size: var(--ds-typography-size-ui-sm); line-height: var(--ds-typography-line-height-ui-sm); font-weight: 600;
}
.ds-tarjeta--metrica > .ds-tarjeta__titulo {
  font-size: var(--ds-typography-size-ui-md); line-height: var(--ds-typography-line-height-ui-md);
  font-weight: 600; color: var(--ds-color-text);
}
.ds-tarjeta--bd .ds-tarjeta__meta {
  font-size: var(--ds-typography-size-ui-xs); line-height: var(--ds-typography-line-height-ui-xs);
  /* 1.11 (§ 7): una fila; la persona la cierra pegada a la derecha. Si lo
     de la izquierda no cabe (Blocker + #id largo + notas, o «Hecha 12/09»),
     salta la persona sola a la fila de abajo, a la derecha: la esquina es
     la misma en todas las tarjetas. */
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--ds-app-paso-medio) var(--ds-spacing-2xs); max-width: none; /* 1.17 (E10): 8 en línea (antes 6), 4 entre líneas */
}
.ds-tarjeta--bd .ds-tarjeta__meta > span:has(> .ds-icono) { display: inline-flex; align-items: center; gap: var(--ds-radius-chip); }
.ds-tarjeta--bd .ds-tarjeta__meta > .ds-avatar:last-child,
.ds-tarjeta--bd .ds-tarjeta__meta > :last-child:has(> .ds-avatar) { margin-inline-start: auto; flex: none; }
/* El control de la persona es el avatar redondo + su chevron, sin caja: el
   relleno del --sm (12) lo hacía de 84 y empujaba la fila a dos. */
.ds-tarjeta--bd .ds-tarjeta__meta > .ds-boton:last-child:has(> .ds-avatar) { padding-inline: var(--ds-app-paso-medio); gap: var(--ds-app-paso-medio); }
/* La medida sm del chip de selección (12/18): solo aquí, en la tarjeta de tablero (T5). */
.ds-tarjeta--bd .ds-chip--bd {
  height: var(--ds-app-chip-bd-tarjeta); line-height: var(--ds-app-chip-bd-tarjeta);
  font-size: var(--ds-typography-size-ui-xs);
}
/* 1.17 (§ 2, E9): el hueco de .ds-chips es 8 en todo sitio (antes 4 aquí). */
.ds-tarjeta--bd .ds-chips { flex-wrap: wrap; }

/* ═══ B7. Lista de base (fila 30, propiedades a la DERECHA) ═══ */
.ds-lista--bd { background: transparent; border-radius: 0; box-shadow: none; }
.ds-lista--bd > li {
  display: flex; align-items: center; gap: calc(var(--ds-spacing-2xs) * 1.5);
  min-height: var(--ds-app-lista-fila); padding: var(--ds-radius-chip) var(--ds-spacing-2xs);
  font-size: var(--ds-typography-size-ui-sm); line-height: var(--ds-typography-line-height-ui-sm);
}
.ds-lista--bd > li > :first-child { flex: 1; min-width: 0; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ds-lista--bd > li > :not(:first-child) { flex: none; font-size: var(--ds-typography-size-ui-xs); color: var(--ds-color-text-secondary); }
.ds-lista--bd .ds-lista__relacion { color: var(--ds-color-chip-azul); }
@media (max-width: 719px) {
  .ds-lista--bd > li { flex-wrap: wrap; padding-block: var(--ds-spacing-2xs); gap: var(--ds-radius-chip) var(--ds-spacing-2xs); }
  .ds-lista--bd > li > :first-child { flex-basis: 100%; white-space: normal; }
}

/* ═══ B8. Galería (tarjeta 296, sombra de la regla 8) ═══ */
.ds-galeria {
  display: grid; gap: var(--ds-spacing-xs);
  grid-template-columns: repeat(auto-fill, minmax(min(var(--ds-app-galeria), 100%), 1fr));
}
.ds-galeria__portada {
  height: calc(var(--ds-app-galeria) / 2.5); margin: calc(var(--ds-app-tarjeta-y) * -1) calc(var(--ds-app-tarjeta-x) * -1) var(--ds-radius-chip);
  border-radius: var(--ds-app-radio-tarjeta) var(--ds-app-radio-tarjeta) 0 0;
  background: color-mix(in srgb, var(--tono) 14%, var(--ds-color-bg-tertiary));
}

/* ═══ B9. Calendario (celda ×124, número 14 terciario, píldora radio 6) ═══ */
.ds-calendario {
  display: grid; grid-template-columns: repeat(7, minmax(0, 1fr));
  border-left: var(--ds-app-linea) solid var(--ds-color-border-subtle);
  border-top: var(--ds-app-linea) solid var(--ds-color-border-subtle);
}
.ds-calendario__cabecera {
  padding: var(--ds-radius-chip) var(--ds-spacing-2xs);
  font-size: var(--ds-typography-size-ui-xs); line-height: var(--ds-typography-line-height-ui-xs);
  color: var(--ds-color-text-tertiary);
  border-right: var(--ds-app-linea) solid var(--ds-color-border-subtle);
  border-bottom: var(--ds-app-linea) solid var(--ds-color-border-subtle);
}
.ds-calendario__dia {
  display: flex; flex-direction: column; gap: var(--ds-radius-chip); min-width: 0;
  min-height: var(--ds-app-calendario-celda); padding: var(--ds-radius-bar);
  border-right: var(--ds-app-linea) solid var(--ds-color-border-subtle);
  border-bottom: var(--ds-app-linea) solid var(--ds-color-border-subtle);
}
.ds-calendario__dia--fuera { background: var(--ds-color-bg-elevated); }
.ds-calendario__numero {
  align-self: flex-end; font-size: var(--ds-typography-size-ui-sm); line-height: var(--ds-typography-line-height-ui-sm);
  color: var(--ds-color-text-tertiary); font-variant-numeric: tabular-nums;
}
.ds-calendario__evento {
  padding: 0 var(--ds-radius-bar);
  border-radius: var(--ds-radius-bar); background: var(--ds-color-bg-elevated);
  /* 1.17 (§ 71, R4): anillo + sombra de app; la --ds-shadow-card (0,28) es la de la web. */
  box-shadow: var(--ds-app-anillo), var(--ds-app-sombra-xs);
  font-size: var(--ds-typography-size-ui-xs); line-height: var(--ds-typography-line-height-ui-xs);
  color: var(--ds-color-text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
@media (max-width: 719px) {
  .ds-calendario__dia { min-height: calc(var(--ds-app-calendario-celda) / 2); padding: var(--ds-radius-chip) 0; }
  .ds-calendario__evento { font-size: var(--ds-typography-size-ui-2xs); padding: 0 0; }
}

/* ═══ B10. Cronograma (fila 36, barra 34 radio 6, cabecera 32/14) ═══ */
.ds-cronograma { overflow-x: auto; }
.ds-cronograma__cabecera, .ds-cronograma__fila {
  display: grid; grid-template-columns: repeat(14, minmax(var(--ds-spacing-md), 1fr));
  min-width: calc(var(--ds-spacing-lectura) - var(--ds-spacing-lg));
}
.ds-cronograma__cabecera {
  min-height: var(--ds-spacing-md); align-items: center;
  font-size: var(--ds-typography-size-ui-sm); color: var(--ds-color-text-tertiary);
  font-variant-numeric: tabular-nums; border-bottom: var(--ds-app-linea) solid var(--ds-color-border-subtle);
}
.ds-cronograma__cabecera > * { text-align: center; }
.ds-cronograma__fila {
  min-height: var(--ds-app-cronograma-fila); align-items: center;
  border-bottom: var(--ds-app-linea) solid var(--ds-color-border-subtle);
}
.ds-cronograma__barra {
  height: calc(var(--ds-app-cronograma-barra) - var(--ds-radius-chip)); padding: 0 var(--ds-spacing-2xs);
  border-radius: var(--ds-radius-bar); background: var(--tono-soft); color: var(--ds-color-text);
  box-shadow: 0 0 0 var(--ds-app-linea) color-mix(in srgb, var(--tono) 30%, transparent);
  display: block; min-width: 0; line-height: calc(var(--ds-app-cronograma-barra) - var(--ds-radius-chip));
  font-size: var(--ds-typography-size-ui-sm); overflow: hidden; white-space: nowrap; text-overflow: ellipsis;
}

/* ═══ B11. Gráfico (svg de barras, 300 de alto, etiquetas 14) ═══ */
.ds-grafico { height: var(--ds-app-grafico); }
.ds-grafico svg { display: block; width: 100%; height: 100%; overflow: visible; }
.ds-grafico__barra { fill: var(--tono); }
.ds-grafico__etiqueta { fill: var(--ds-color-text-secondary); font-size: var(--ds-typography-size-ui-sm); font-family: var(--ds-typography-family-sans), system-ui, sans-serif; }
.ds-grafico__eje { stroke: var(--ds-color-border-subtle); stroke-width: var(--ds-app-linea); }

/* ═══ B12. Propiedades de ficha (fila 34, etiqueta 160, valor SIN caja) ═══ */
.ds-propiedades { display: flex; flex-direction: column; margin: 0 0 var(--ds-app-prop-cuerpo); }
/* 1.15.2 (SPEC-CRM4-DS-2B, visto por V-2A en la ficha de lead): dentro de una
   .ds-seccion, sin margen propio: el aire hasta el separador lo pone la
   sección (§ 54, § 72); con los 37 del cuerpo sumaba ~90. */
.ds-seccion .ds-propiedades, .ds-seccion .ds-propiedades > .ds-propiedades__fila:last-child { margin-bottom: 0; }
.ds-propiedades__fila {
  display: grid; grid-template-columns: var(--ds-app-prop-etiqueta) minmax(0, 1fr);
  gap: calc(var(--ds-spacing-2xs) * 2.5); align-items: start;
  min-height: var(--ds-app-prop-fila); margin-bottom: var(--ds-radius-chip);
  font-size: var(--ds-typography-size-ui-sm); line-height: var(--ds-typography-line-height-ui-sm);
}
/* 1.10.2 (l): la fila alinea ARRIBA; la etiqueta mide lo que una fila de una
   línea (34), así en una línea sigue centrada con el valor y en una fila alta
   («Qué es») queda arriba, a la altura de la primera línea (Notion). */
.ds-propiedades__etiqueta { display: flex; align-items: center; gap: var(--ds-radius-bar); min-height: var(--ds-app-prop-fila); color: var(--ds-color-text-secondary); }
.ds-propiedades__etiqueta svg { flex: none; width: var(--ds-icon-size-sm); height: var(--ds-icon-size-sm); color: var(--ds-color-text-tertiary); }
.ds-propiedades__valor {
  display: flex; align-items: center; gap: var(--ds-radius-chip); flex-wrap: wrap;
  min-height: var(--ds-app-prop-fila); padding: 0 var(--ds-radius-bar);
  border-radius: var(--ds-radius-chip); color: var(--ds-color-text); cursor: default;
}
/* 1.16 (§ 67, P9/D6): mano solo en lo que se pulsa (__ctrl, enlace, botón),
   texto en lo que se escribe (__input) y flecha en lo que se lee; el fondo al
   pasar, solo si el valor se edita (antes todo valor prometía edición). */
.ds-propiedades__valor:is(a, button, input, textarea, select, :has(a, button, input, textarea, select)):hover { background: var(--ds-color-bg-tertiary); }
.ds-propiedades__valor--vacio { color: var(--ds-color-text-tertiary); }
/* 1.10.2 (n): un control (casilla, interruptor) trae su caja: sin el relleno
   de 6 que el valor de texto usa para su fondo al pasar, y sin ese fondo. Así
   el borde del control cae en la key line de los valores. */
.ds-propiedades__valor--control { padding-inline: 0; cursor: auto; }
.ds-propiedades__valor--control:hover { background: transparent; }
/* 1.10.2 (a): un valor de texto largo se recorta a 3 líneas (D4) con «Ver
   más» / «Ver menos» en línea, a la derecha de la última, sin salir del panel.
   El relleno de arriba pone la primera línea a la altura de la etiqueta (l).
   La app alterna --abierto y aria-expanded del botón (app.js pinta y abre;
   la lógica es de la app). */
.ds-propiedades__valor--largo {
  align-items: flex-start; flex-wrap: nowrap;
  padding-top: calc((var(--ds-app-prop-fila) - var(--ds-typography-line-height-ui-sm)) / 2);
}
.ds-propiedades__texto {
  flex: 1; min-width: 0; overflow: hidden;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3; line-clamp: 3;
}
.ds-propiedades__valor--abierto .ds-propiedades__texto { display: block; -webkit-line-clamp: none; line-clamp: none; overflow: visible; }
.ds-propiedades__mas { flex: none; align-self: flex-end; }
@media (max-width: 1055px) { .ds-propiedades__mas { min-height: var(--ds-spacing-control); } }
/* 1.17 (§ 67, U5): bajo 720 la fila de propiedad se toca con el dedo: 44,
   la medida de --ds-app-prop-fila en móvil (el campo editable del lead y del
   documento medía 34 a 393). */
@media (max-width: 719px) {
  .ds-propiedades__fila, .ds-propiedades__etiqueta, .ds-propiedades__valor,
  .ds-propiedades__ctrl, .ds-propiedades__input, .ds-propiedades__valor input:not([type="checkbox"]) { min-height: var(--ds-spacing-touch); }
}
/* Bajo 720 la columna del valor es estrecha (≈ 200): el botón baja a su
   línea, con su texto en la línea del valor, para no robarle ancho al texto. */
@media (max-width: 719px) {
  .ds-propiedades__valor--largo { flex-wrap: wrap; }
  .ds-propiedades__texto { flex-basis: 100%; }
  .ds-propiedades__mas { align-self: flex-start; margin-inline-start: calc(var(--ds-app-paso-12) * -1); }
}
@media (max-width: 719px) {
  .ds-propiedades__fila { grid-template-columns: var(--ds-app-prop-etiqueta-movil) minmax(0, 1fr); gap: var(--ds-spacing-2xs); }
}

/* ═══ B13. Estado vacío de base (144 sin borde, «Sin resultados») ═══ */
.ds-vacio--bd {
  align-items: center; text-align: center; justify-content: center; gap: calc(var(--ds-spacing-2xs) * 1.5);
  min-height: var(--ds-app-vacio); padding: 0;
}
.ds-vacio--bd .ds-vacio__texto {
  margin: 0; font-size: var(--ds-typography-size-ui-sm); color: var(--ds-color-text-tertiary);
}
/* 1.14 (§ 14, C8): el vacío de una columna de tablero es una nota, no un
   error: la regla «dentro de una tarjeta» ampliada a la columna. Título
   14/500 en secundario, sin icono, sin el relleno de página (48 × 24) ni el
   alto de 144 del vacío de base. Tareas pintaba icono + 18/600 en cada
   columna; Board, «Vacío» centrado en 144. */
.ds-tablero__columna .ds-vacio {
  min-height: 0; align-items: flex-start; justify-content: flex-start; text-align: left;
  gap: var(--ds-app-paso-medio);
  /* 1.19.6 (alineacion.js): a los lados, el relleno de la tarjeta del tablero
     (12): «Sin leads» empieza donde el texto de las tarjetas, no 4 antes. */
  padding: var(--ds-spacing-2xs) var(--ds-app-tarjeta-x);
}
.ds-tablero__columna .ds-vacio__icono { display: none; }
/* 1.17 (§ 14, U8): tampoco en una tarjeta (KPIs y Panel vacíos): el vacío de
   tarjeta es título 14/500 + acción, sin icono. */
.ds-tarjeta .ds-vacio__icono { display: none; }
/* 1.20.1 (§ 0.17, p. 4): el vacío de una SECCIÓN conserva icono, título y
   botón (es una invitación), pero sus dos papeles no compiten con los de la
   sección: el título baja un escalón (de ui-lg 18/28/600, idéntico al
   .ds-seccion__titulo, a ui-md 16/24/600) y el texto pasa a terciario (era
   secundario, idéntico al .ds-seccion__texto «0 abiertas»). Terciario sobre
   el fondo de la sección: 4,56:1 en claro y 5,57:1 en oscuro. Lo mide
   pruebas/jerarquia.js (solo-peso). [M, Yaz 03/10] */
.ds-seccion .ds-vacio__titulo {
  font-size: var(--ds-typography-size-ui-md); line-height: var(--ds-typography-line-height-ui-md);
}
.ds-seccion .ds-vacio__texto { color: var(--ds-color-text-tertiary); }
.ds-tablero__columna .ds-vacio__titulo {
  margin: 0; font-size: var(--ds-typography-size-ui-sm); line-height: var(--ds-typography-line-height-ui-sm);
  font-weight: 500; color: var(--ds-color-text-secondary);
}
/* El texto, en secundario: el terciario da 4,3 sobre el tinte de la columna
   (devuelto del CRM, estilo.css 1785). */
.ds-tablero__columna .ds-vacio__texto { margin: 0; color: var(--ds-color-text-secondary); }
/* 1.14 (§ 1, C9): el enlace dentro de una columna va a la escala de la
   tarjeta (ui-xs); devuelto del CRM («Avanzar a…», estilo.css 1779). */
.ds-tablero__columna .ds-boton--enlace { font-size: var(--ds-typography-size-ui-xs); }

/* ═══ 24. Interruptor ════════════════════════════════════════════════ */
/* Toggle a la medida md de Untitled UI (pista 44 × 24, bola 20; regla de
   Asier 26/09: los controles se miden contra Untitled). Un <button
   role="switch" aria-checked>; encendido = acento, como el botón primario.
   Nació en el Sparring (rueditas, 3b) y sube al DS el 26/09 noche: Asier y
   David vieron que «no era del DS». Va siempre con su texto al lado. */
.ds-interruptor {
  flex: none; position: relative; cursor: pointer; padding: 0; box-sizing: border-box; color: inherit; /* button: sin esto, buttontext del navegador (colores-tema, 30/09) */
  width: var(--ds-spacing-touch); height: var(--ds-icon-size-lg);
  border: var(--ds-app-linea) solid var(--ds-color-border); border-radius: var(--ds-radius-full);
  background: var(--ds-color-bg-tertiary);
  transition: background-color var(--ds-motion-duration-instant) var(--ds-motion-easing-musho);
}
.ds-interruptor__bola {
  position: absolute; top: 50%; left: var(--ds-app-interruptor-hueco);
  width: var(--ds-icon-size); height: var(--ds-icon-size); border-radius: var(--ds-radius-full);
  background: var(--ds-color-text-tertiary); transform: translateY(-50%);
  transition: left var(--ds-motion-duration-instant) var(--ds-motion-easing-musho);
}
.ds-interruptor[aria-checked="true"] { background: var(--ds-app-primario-fondo); border-color: var(--ds-app-primario-fondo); }
.ds-interruptor[aria-checked="true"] .ds-interruptor__bola { left: calc(100% - var(--ds-icon-size) - var(--ds-app-interruptor-hueco)); background: var(--ds-app-primario-texto); }
.ds-interruptor:focus-visible { outline: var(--ds-app-foco) solid var(--ds-app-foco-color); outline-offset: var(--ds-app-foco); }
.ds-interruptor:disabled { cursor: not-allowed; background: var(--ds-color-bg-tertiary); border-color: var(--ds-color-border-subtle); }
.ds-interruptor:disabled .ds-interruptor__bola { background: var(--ds-color-text-disabled); }

/* ═══ 25. Título de sección ══════════════════════════════════════════ */
/* 1.3 (26/09/2026, auditoría del Sparring, documentos/p/46): el título de un
   bloque DENTRO de una página. El DS solo tenía títulos amarrados a una pieza
   (tarjeta, hoja, documento) y cada app inventaba el suyo. Título + una frase
   opcional; sin acciones dentro: las de la página viven en .ds-titulo (§ 21).
   Uso: reglas.md § 54. */
.ds-seccion { display: flex; flex-direction: column; gap: 0; min-width: 0; margin: 0; }
/* 1.19.0 (§ 0.16): título → su frase 8 (parte); título o frase → contenido
   16 (bloque), también en la sección anidada (antes 4). Sin gap y sin el
   margen negativo de antes: cada hijo tras otro visible lleva su hueco
   arriba. Flex en columna: los márgenes no colapsan. */
.ds-seccion > :not([hidden]) ~ * { margin-block-start: var(--ds-aire-bloque); }
.ds-seccion > .ds-seccion__titulo + .ds-seccion__texto { margin-block-start: var(--ds-aire-parte); }
.ds-seccion__titulo {
  margin: 0; font-size: var(--ds-typography-size-ui-lg); line-height: var(--ds-typography-line-height-ui-lg);
  font-weight: 600; color: var(--ds-color-text);
}
.ds-seccion__texto {
  margin: 0; font-size: var(--ds-typography-size-ui-sm); line-height: var(--ds-typography-line-height-ui-sm);
  color: var(--ds-color-text-secondary);
}
/* 1.15 (§ 54, P10 «mi cerebro no lo lee como dos cosas… eso no debería poder
   pasar en el DS»): dentro de un panel y de la columna principal de un
   detalle, dos secciones seguidas llevan separador (1 px de borde) con 32
   arriba y 32 abajo; así el título queda más cerca de lo suyo que de la
   sección anterior. En la página ya lo hacía la pila (§ 72).
   1.19.0 (§ 0.16): el aire de arriba lo pone UNA fuente, el gap de la
   columna (.ds-panel__paso y .ds-detalle__principal, 32 = aire-seccion);
   antes gap 16 + margen 32 = 48 en el panel. En un panel sin paso
   (.ds-panel__cuerpo, gap 16 de bloque) la sección suma 16 más. */
:is(.ds-panel, .ds-detalle__principal) .ds-seccion + .ds-seccion {
  margin-block-start: 0; padding-block-start: var(--ds-aire-seccion);
  border-block-start: var(--ds-app-linea) solid var(--ds-color-border);
}
.ds-panel__cuerpo > .ds-seccion + .ds-seccion { margin-block-start: var(--ds-aire-bloque); }
/* El título admite un icono delante (16, secundario): Actividad, Notas… */
.ds-seccion__titulo:has(> .ds-icono) { display: flex; align-items: center; gap: var(--ds-spacing-2xs); }
.ds-seccion__titulo > .ds-icono { width: var(--ds-icon-size-sm); height: var(--ds-icon-size-sm); color: var(--ds-color-text-secondary); }
/* 1.15.3 (§ 54, devuelto por V-5): las acciones DEL bloque (Añadir asset,
   Añadir documento) van en una fila al pie de la sección, 16 por encima de
   lo último (aire-bloque; 1.19.0, § 0.16: en toda sección, de una fuente) y
   alineadas a la izquierda con el texto. No son acciones de página: esas
   siguen en .ds-titulo. */
.ds-seccion__acciones {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-start;
  gap: var(--ds-spacing-2xs); margin: var(--ds-aire-bloque) 0 0;
}
/* 1.16 (§ 54, P8, D9): --fin = la misma fila alineada a la derecha, también
   en móvil. Para lo que cierra una lista sin ser su acción principal
   («Papelera (N)» bajo Recientes en Documentos). */
.ds-seccion__acciones--fin { justify-content: flex-end; }

/* ═══ 26. Alerta en línea ════════════════════════════════════════════ */
/* Feedback de algo que acaba de pasar, pegado a lo que lo causó (reglas.md
   § 22 bis y § 55): título = qué pasó (sin punto), texto = qué hacer. No es el
   callout de documento (.ds-aviso, 31), que da contexto ANTES de actuar. El
   color va con texto (§ 0.5): raya + título en el tono, fondo suave. Con
   `hidden` no ocupa sitio. El toast sigue sin pieza. */
.ds-alerta {
  display: flex; flex-direction: column; gap: var(--ds-app-paso-medio); min-width: 0;
  /* 1.17 (§ 55, R8): la alerta es superficie (radio 12, D5) y la raya de 3
     va por dentro con box-shadow, así sigue la curva; el relleno izquierdo
     suma la raya y el texto no se mueve. Antes radio 4 y border-left. */
  padding: var(--ds-app-paso-12); padding-inline-start: calc(var(--ds-app-paso-12) + var(--ds-app-raya));
  border-radius: var(--ds-app-radio-tarjeta);
  box-shadow: inset var(--ds-app-raya) 0 0 var(--ds-color-info-fg);
  background: var(--ds-color-info-soft); color: var(--ds-color-text);
  font-size: var(--ds-typography-size-ui-sm); line-height: var(--ds-typography-line-height-ui-sm);
}
.ds-alerta[hidden] { display: none; }
.ds-alerta__titulo { margin: 0; font-weight: 600; color: var(--ds-color-info-fg); }
.ds-alerta__texto { margin: 0; color: var(--ds-color-text); }
.ds-alerta--info { box-shadow: inset var(--ds-app-raya) 0 0 var(--ds-color-info-fg); background: var(--ds-color-info-soft); }
.ds-alerta--info .ds-alerta__titulo { color: var(--ds-color-info-fg); }
.ds-alerta--ok { box-shadow: inset var(--ds-app-raya) 0 0 var(--ds-color-success-fg); background: var(--ds-color-success-soft); }
.ds-alerta--ok .ds-alerta__titulo { color: var(--ds-color-success-fg); }
.ds-alerta--aviso { box-shadow: inset var(--ds-app-raya) 0 0 var(--ds-color-warning-fg); background: var(--ds-color-warning-soft); }
.ds-alerta--aviso .ds-alerta__titulo { color: var(--ds-color-warning-fg); }
/* obsoleta desde 1.7 → usar .ds-alerta--aviso */
.ds-alerta--atencion { box-shadow: inset var(--ds-app-raya) 0 0 var(--ds-color-warning-fg); background: var(--ds-color-warning-soft); }
/* obsoleta desde 1.7 → usar .ds-alerta--aviso */
.ds-alerta--atencion .ds-alerta__titulo { color: var(--ds-color-warning-fg); }
.ds-alerta--error { box-shadow: inset var(--ds-app-raya) 0 0 var(--ds-color-error-fg); background: var(--ds-color-error-soft); }
.ds-alerta--error .ds-alerta__titulo { color: var(--ds-color-error-fg); }

/* ═══ 27. Saltar al contenido ════════════════════════════════════════ */
/* Primer hijo de <body> (o primer foco de la página) en toda app con
   cabecera (reglas.md § 0.5 y § 56): invisible hasta que recibe foco con el
   teclado; entonces, fijo arriba a la izquierda con el anillo del DS. */
.ds-saltar {
  position: fixed; top: var(--ds-spacing-2xs); left: var(--ds-spacing-2xs); z-index: var(--ds-app-capa-saltar);
  display: inline-flex; align-items: center; min-height: var(--ds-spacing-control);
  padding: 0 var(--ds-app-paso-12); border-radius: var(--ds-radius-card);
  background: var(--ds-color-bg-elevated); color: var(--ds-color-text); box-shadow: var(--ds-shadow-float);
  font-size: var(--ds-typography-size-ui-sm); font-weight: 500; text-decoration: none;
}
.ds-saltar:not(:focus) {
  width: var(--ds-app-linea); height: var(--ds-app-linea); min-height: 0; padding: 0;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap; box-shadow: none;
}
.ds-saltar:focus-visible { outline: var(--ds-app-foco) solid var(--ds-app-foco-color); outline-offset: var(--ds-app-foco); }

/* ═══ 28. Texto suelto ═══════════════════════════════════════════════ */
/* Dos utilidades, nada más (reglas.md § 57): .ds-mono para códigos y cifras
   que se comparan en columna; .ds-caption para un pie o una procedencia
   FUERA de una tarjeta (el tamaño y el color de .ds-tarjeta__caption, sin
   versalitas: la mayúscula inicial la pone el texto, § 0.3). */
.ds-mono { font-family: var(--ds-typography-family-mono), ui-monospace, 'Cascadia Mono', Consolas, monospace; font-variant-numeric: tabular-nums; }
.ds-caption {
  font-size: var(--ds-typography-size-ui-xs); line-height: var(--ds-typography-line-height-ui-xs);
  color: var(--ds-color-text-tertiary); font-weight: 500;
  /* 1.19.0 (§ 0.16): sin los 12 del navegador en un <p>; el aire lo pone el padre. */
  margin: 0;
}

/* ═══ 33. Página ═════════════════════════════════════════════════════ */
/* 1.4 (27/09/2026, reglas.md § 33, Carbon 2x-grid): el contenedor de toda
   página de una app. Relleno 16 en móvil y 24 desde 720 (Carbon «Max»); UN
   modo por página: lectura (720 de texto + el relleno, la columna de Notion)
   o ancha (tope 1584, Carbon «Max»). Breakpoints: 720 y 1056, nada más. */
/* 1.13.1: el aire de arriba (16) es de la página; vivía en #contenido del
   CRM y el id ganaba a la regla de la barra plegada (§ 12). */
.ds-pagina { box-sizing: border-box; width: 100%; margin-inline: auto; padding-inline: var(--ds-spacing-xs); padding-block-start: var(--ds-spacing-xs); }
@media (min-width: 720px) { .ds-pagina { padding-inline: var(--ds-spacing-sm); } }
.ds-pagina--lectura { max-width: calc(var(--ds-spacing-lectura) + 2 * var(--ds-spacing-sm)); }
.ds-pagina--ancha { max-width: var(--ds-app-pagina-max); }
/* 1.12 (§ 72, decisión 4): tercer modo, la página de app. Tope de contenido
   960 más el relleno (Stripe 880-900 a 1440). KPIs, Hoy, Panel y listas;
   --ancha queda para tableros. La página de detalle también topa en 960
   (hasta re-medir la factura de Stripe, Opus H14). */
/* 1.13.1: centrada en el hueco que deja la barra (Asier, 29/09: pegada a
   la izquierda, a 2560 quedaba media pantalla vacía a la derecha; la 1.12.3
   la pegó a la barra copiando a Stripe a 1440, y a su monitor no vale). */
.ds-pagina--app { max-width: calc(var(--ds-app-pagina-app) + 2 * var(--ds-spacing-sm)); }
/* 1.19.0 (§ 0.16): un <p> suelto en la pila no trae los 16 del navegador
   (colapsaban con el de la pila); el de arriba lo pone la pila. */
.ds-pagina--app > p { margin: 0; }
/* 1.14 (§ 21, B1 y C2): el título de app (28/700) en TODA página que no
   sea documento, no solo en --app: listas, detalles, tableros, ajustes y
   formularios. El documento (y lo que va dentro de un .ds-doc, § 21)
   conserva la escala h3. Fuera: el título de un panel o diálogo. */
.ds-pagina:not([data-vista="documento"]) .ds-titulo__h1:not(.ds-doc *, dialog *) { font-size: var(--ds-app-titulo-app); font-weight: 700; }
/* 1.14 (§ 33, decisión 2): formulario y ajustes viven en --app (960) para
   compartir la key line; lo que no es título ni migas topa en 720 y se
   alinea a la IZQUIERDA (el h1 en la key line; el formulario debajo). */
/* 1.14.2: lo que lleva una tabla no topa en 720 (Usuarios en Ajustes: Estado y ⋯ quedaban tras un scroll). */
.ds-pagina--app:is([data-vista="formulario"], [data-vista="ajustes"]) > :not(.ds-titulo, .ds-migas, :has(.ds-tabla)) { max-width: var(--ds-spacing-lectura); }
@media (max-width: 719px) { .ds-pagina--app { max-width: calc(var(--ds-app-pagina-app) + 2 * var(--ds-spacing-xs)); } }
/* 1.12 (§ 72, decisión 3): la pila. El aire lo pone la página, no la pieza:
   32 entre hijos de .ds-pagina; 16 dentro de una sección de primer nivel
   (título → filtros → panel). Stripe: h1 → 20 → filtros → 28 → panel.
   Lo mide medidas-ds.js h) pila-hueco (16-40). */
/* :not(:first-child) y no «* + *»: pesa (0,2,0) y gana al margin: 0 de una
   pieza (.ds-chips, .ds-seccion) sin !important. Fuera de la pila lo que
   no es un bloque de la columna: el dialog (panel lateral, § 59) se bajaba
   32 px y el SOP del panel empezaba en y 506 (documentos-ds, 29/09). */
.ds-pagina > :not(:first-child, dialog, [hidden], script, template) { margin-block-start: var(--ds-spacing-md); }
/* Lo que va pegado a otra pieza es pieza de la misma sección (§ 33: entre
   piezas 16): las migas con su título; la barra de vista con sus filtros,
   sus pestañas y la vista que controla. */
.ds-pagina > :is(.ds-migas, .ds-vista-barra, .ds-filtros, .ds-pestanas) + :not(dialog, [hidden], script, template) { margin-block-start: var(--ds-spacing-xs); }
/* 1.17 (§ 23, § 72, E3): barra de vista + filtros + lo que filtran son UNA
   pieza (16 entre ellas) aunque entre medias haya nodos ocultos (el menú de
   un filtro, el recuento, el vacío o la alerta con hidden): con «+» el 16 se
   perdía y la pila metía 32 (Proyectos, Usuarios, Tareas a 393). Hasta tres
   ocultos seguidos; sin envoltorio nuevo. */
.ds-pagina > :is(.ds-migas, .ds-vista-barra, .ds-filtros, .ds-pestanas) + :is(dialog, [hidden], script, template) + :not(dialog, [hidden], script, template),
.ds-pagina > :is(.ds-migas, .ds-vista-barra, .ds-filtros, .ds-pestanas) + :is(dialog, [hidden], script, template) + :is(dialog, [hidden], script, template) + :not(dialog, [hidden], script, template),
.ds-pagina > :is(.ds-migas, .ds-vista-barra, .ds-filtros, .ds-pestanas) + :is(dialog, [hidden], script, template) + :is(dialog, [hidden], script, template) + :is(dialog, [hidden], script, template) + :not(dialog, [hidden], script, template) { margin-block-start: var(--ds-spacing-xs); }
/* 1.17.1 (§ 23, § 72, E3): la tabla que filtran la barra de vista o la
   fila de filtros va a 16 de ellas aunque entre medias haya CUALQUIER número
   de nodos ocultos (la Biblioteca mete un menú por filtro: cinco, y la
   cadena de arriba llega a tres; medía 32). No aplica si entre la barra y
   la tabla hay algo visible que no sea barra o filtros. */
.ds-pagina > :is(.ds-vista-barra, .ds-filtros):not([hidden]) ~ :is(.ds-tabla, .ds-tabla-marco):not(.ds-pagina > :is(.ds-vista-barra, .ds-filtros) ~ :not(dialog, [hidden], script, template, .ds-vista-barra, .ds-filtros) ~ *) { margin-block-start: var(--ds-spacing-xs); }

/* ═══ 71. Panel de métricas ══════════════════════════════════════════ */
/* 1.12 (§ 71, decisión 6; Stripe «Tu resumen»): contenedor gris, radio 12,
   relleno 8, hueco 8, rejilla de 6 columnas. Delimita con fondo, no con
   borde. Cada hija dice cuánto ocupa (data-ocupa 2 | 3 | 6; 2 por defecto);
   cada fila suma 6. Bajo 720, todas a 6. */
.ds-panel-metricas {
  display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); gap: var(--ds-spacing-2xs);
  padding: var(--ds-spacing-2xs); border-radius: var(--ds-app-radio-panel);
  background: var(--ds-app-panel-fondo);
}
.ds-panel-metricas > .ds-tarjeta--metrica { grid-column: span 2; }
/* 1.14 (§ 16, C3): sin min-width 0 la tarjeta se ensanchaba hasta su tabla
   (+122 y +326 px en KPIs a 1090); ahora la tabla hace scroll dentro. */
.ds-panel-metricas > * { min-width: 0; }
.ds-panel-metricas > .ds-tarjeta--metrica[data-ocupa="3"] { grid-column: span 3; }
.ds-panel-metricas > .ds-tarjeta--metrica[data-ocupa="6"] { grid-column: 1 / -1; }
/* [data-ocupa] en el móvil: sin él pesa menos que la de 3 y las tarjetas de
   3 se quedaban en 145 px (lote B, 29/09). */
@media (max-width: 719px) { .ds-panel-metricas > .ds-tarjeta--metrica, .ds-panel-metricas > .ds-tarjeta--metrica[data-ocupa] { grid-column: 1 / -1; } }
/* La tarjeta del panel: la .ds-tarjeta vestida (§ 7) con el fondo
   «primario» sobre el gris. 1.17 (§ 71, E7): relleno 16 en TODA tarjeta del
   panel (antes 16 la de cifra y 20 la de contenido: dos rellenos en el mismo
   panel). Título 14/600 (la medida compacta, § 7). */
.ds-tarjeta--metrica { background: var(--ds-app-panel-tarjeta); padding: var(--ds-aire-bloque); gap: var(--ds-aire-parte); }
.ds-tarjeta--metrica > .ds-metrica { padding: 0; background: none; border-radius: 0; }
/* 1.13: el vacío dentro de una tarjeta del panel no suma su relleno de
   página (48 × 24): el aire ya lo pone la tarjeta (Panel, 29/09). */
.ds-tarjeta--metrica > .ds-vacio { padding: 0; }
/* 1.13.2 (remate de Asier al Panel, 29/09: «título grande, botón metido»):
   dentro de una tarjeta el vacío va a su escala. Título 14/500, por debajo
   del de la tarjeta (14/600), y la acción es un --enlace a ras del texto:
   el terciario lleva relleno y se lee sangrado (medidas-ds.js n). */
.ds-tarjeta .ds-vacio__titulo {
  margin: 0; font-size: var(--ds-typography-size-ui-sm); line-height: var(--ds-typography-line-height-ui-sm); font-weight: 500;
}
/* 1.20.0 (§ 0.17, Yaz 03/10): en la tarjeta de PANEL el vacío es UNA línea
   terciaria + su acción, sin `__titulo` (las vistas ya no lo pintan). Así el
   título de la tarjeta (16/24/600 text) se lee antes que cualquier otro texto
   suyo y la nota no comparte firma con la descripción (14/20/400 secundaria),
   que era el fallo que vio Yaz en «¿Por qué perdemos?». Terciario sobre el
   fondo de la tarjeta: 4,91:1 en oscuro y 4,88:1 en claro. */
.ds-tarjeta--metrica .ds-vacio__texto { color: var(--ds-color-text-tertiary); }
.ds-tarjeta--metrica > .ds-boton--enlace { align-self: flex-start; }
/* 1.15.3 (P76, P82): bajo 720 el relleno se sumaba dos veces (tarjeta 20 +
   fila 16) y la pila de Trabajo diario o el feed de Equipo y actividad
   dejaban el texto a ~160 de 393. Ahora la tarjeta lleva 16, como la de una
   cifra, y la fila de su lista va a ras del título de la tarjeta; el icono
   de fila sigue la medida de § 8 (1.17.1: 24 bajo 720, la de toda fila). */
/* 1.19.3b (Yaz 02/10, medido en /panel a 1440): en PC el texto de la fila
   quedaba a 32 del borde y el título de la tarjeta a 16. Ahora a ras en
   todos los anchos, no solo bajo 720. */
.ds-tarjeta--metrica .ds-lista > li > .ds-fila, .ds-tarjeta--metrica .ds-lista > .ds-fila { padding-inline: 0; }
/* 1.11 (SPEC-CRM3-HUECOS, decisión 2; reglas.md § 33): la rejilla de bloques
   de un panel de resumen. Una columna en móvil, dos iguales desde 720;
   __ancha ocupa la fila entera. Hueco entre piezas (16). min-width: 0 NO es
   decorativo: sin él un hijo de grid se ensancha hasta su tabla más ancha y
   el móvil aleja el zoom. .ds-columnas es otra cosa (columnas de documento). */
.ds-rejilla { display: grid; gap: var(--ds-spacing-xs); align-items: start; }
.ds-rejilla > * { min-width: 0; }
@media (min-width: 720px) {
  .ds-rejilla { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .ds-rejilla__ancha { grid-column: 1 / -1; }
}

/* ═══ 59. Panel lateral ══════════════════════════════════════════════ */
/* 1.6 (D2, P11-P17; reglas.md § 59): la ficha se abre en un <dialog> fijo a
   la derecha que FLOTA sobre la página (Carbon empuja 256; Musho flota a 480
   porque la ficha es contexto, no herramienta: § 30.15). Es el peek del CRM
   promovido. Entra deslizando y se esconde por el mismo camino (P15): la
   salida usa la curva inversa y la duración de salida de página. Dentro se
   navega por pasos (__paso), nunca abriendo otro dialog (P16). Cabecera y
   pie fijos; el cuerpo hace scroll. En < 720, pantalla completa con X. */
.ds-panel {
  position: fixed; inset: 0 0 0 auto; margin: 0; padding: 0; overflow: visible;
  width: min(var(--ds-app-panel-ancho), 100vw); max-width: none; height: 100dvh; max-height: none;
  border: 0; border-left: var(--ds-app-linea) solid var(--ds-color-border); border-radius: 0;
  background: var(--ds-color-bg-elevated); color: var(--ds-color-text); box-shadow: var(--ds-shadow-float);
  display: none; flex-direction: column; transform: translateX(100%);
  transition: transform var(--ds-motion-duration-page-exit) var(--ds-motion-easing-musho-inverse),
              overlay var(--ds-motion-duration-page-exit) allow-discrete,
              display var(--ds-motion-duration-page-exit) allow-discrete;
}
.ds-panel[open] {
  display: flex; transform: translateX(0);
  transition: transform var(--ds-motion-duration-reveal) var(--ds-motion-easing-musho),
              overlay var(--ds-motion-duration-reveal) allow-discrete,
              display var(--ds-motion-duration-reveal) allow-discrete;
}
@starting-style { .ds-panel[open] { transform: translateX(100%); } }
/* 1.8 (P15): la salida recorre la trayectoria entera. app.js pone
   --saliendo, espera el transitionend del transform (240, el mismo easing
   de la entrada) y ENTONCES cierra; antes se cortaba a 150 ms. */
.ds-panel[open].ds-panel--saliendo {
  transform: translateX(100%);
  transition: transform var(--ds-motion-duration-move) var(--ds-motion-easing-musho);
}
.ds-panel[open].ds-panel--saliendo::backdrop { opacity: 0; }
.ds-panel--ancho { width: min(var(--ds-app-panel-ancho-grande), 100vw); }
/* Cabecera: [atrás] caption / título [X]. Sin envoltorio: rejilla con áreas. */
.ds-panel__cabecera {
  flex: none; position: sticky; top: 0; z-index: 1;
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto auto;
  grid-template-areas: "atras caption acciones cerrar" "atras titulo acciones cerrar"; align-items: center;
  min-height: calc(var(--ds-spacing-control) + 2 * var(--ds-spacing-2xs));
  padding: var(--ds-spacing-2xs) var(--ds-spacing-2xs) var(--ds-spacing-2xs) var(--ds-spacing-sm);
  background: var(--ds-color-bg-elevated); border-bottom: var(--ds-app-linea) solid var(--ds-color-border-subtle);
}
.ds-panel__caption {
  grid-area: caption; margin: 0;
  font-size: var(--ds-typography-size-ui-xs); line-height: var(--ds-typography-line-height-ui-xs);
  color: var(--ds-color-text-tertiary);
  font-weight: 500;
}
.ds-panel__titulo {
  grid-area: titulo; margin: 0; min-width: 0; overflow-wrap: anywhere;
  font-size: var(--ds-typography-size-ui-lg); line-height: var(--ds-typography-line-height-ui-lg); font-weight: 600;
}
/* 1.19.0 (§ 0.16): caption → título del panel, 8 (parte; antes 0). En el
   padre y solo si hay caption: sin él la fila vacía no suma hueco. */
.ds-panel__cabecera:has(> .ds-panel__caption) { row-gap: var(--ds-aire-parte); }
.ds-panel__cabecera > [data-cierra] { grid-area: cerrar; margin-left: var(--ds-spacing-2xs); }
/* 1.7.2: hueco para el «Más» (⋯) de la ficha, entre el título y la X. */
.ds-panel__acciones { grid-area: acciones; display: flex; align-items: center; gap: var(--ds-spacing-2xs); }
.ds-panel__atras { grid-area: atras; margin: 0 var(--ds-spacing-2xs) 0 calc(var(--ds-spacing-2xs) - var(--ds-spacing-sm)); }
/* «Atrás» solo cuando app.js ha llevado el panel a un paso que no es el 0. */
.ds-panel:not([data-paso]) .ds-panel__atras, .ds-panel[data-paso="0"] .ds-panel__atras { display: none; }
.ds-panel__cuerpo {
  flex: 1; min-height: 0; overflow-y: auto; overscroll-behavior: contain;
  display: flex; flex-direction: column; gap: var(--ds-spacing-xs); padding: var(--ds-spacing-sm);
}
/* 1.19.0 (§ 0.16): sección → sección 32 (aire-seccion), de una sola fuente;
   antes gap 16 + margen 32 del separador = 48. */
.ds-panel__paso { display: flex; flex-direction: column; gap: var(--ds-aire-seccion); }
/* 1.10 (defecto 4, § 59): un panel que ENSEÑA un documento es de lectura,
   no de edición: UNA columna, propiedades arriba y el contenido debajo a
   todo el ancho del cuerpo (≈ 656 en --ancho). Nunca .ds-columnas: eso es
   para tarea y lead. 1.10.1 (a): propiedades arriba en .ds-propiedades
   (etiqueta a la izquierda, § 8 regla 51), no apiladas: 8 filas ≈ 272 dejan
   el documento a la vista (apiladas eran 606). Es lectura: el valor no
   promete edición (sin cursor de mano ni fondo al pasar). */
/* 1.10.2 (a): propiedades → caption → documento a 16 (--ds-spacing-xs), la
   caption sin margen propio; bajo 720 las propiedades van en un
   .ds-desplegable «Propiedades (n)» plegado (la app lo cierra al abrir el
   panel en móvil); en PC va abierto y sin resumen, como si no estuviera. */
.ds-panel__lectura { display: flex; flex-direction: column; gap: var(--ds-spacing-xs); min-width: 0; }
.ds-panel__lectura > .ds-propiedades, .ds-panel__lectura > .ds-caption, .ds-panel__lectura > .ds-desplegable { margin: 0; }
.ds-panel__lectura > .ds-desplegable > .ds-propiedades { margin: var(--ds-spacing-2xs) 0 0; }
.ds-panel__lectura > .ds-propiedades .ds-propiedades__valor, .ds-panel__lectura > .ds-desplegable > .ds-propiedades .ds-propiedades__valor { cursor: default; }
.ds-panel__lectura > .ds-propiedades .ds-propiedades__valor:hover, .ds-panel__lectura > .ds-desplegable > .ds-propiedades .ds-propiedades__valor:hover { background: transparent; }
.ds-panel__lectura > .ds-desplegable > summary { align-items: center; min-height: var(--ds-spacing-control); }
@media (min-width: 720px) { .ds-panel__lectura > .ds-desplegable[open] > summary { display: none; } .ds-panel__lectura > .ds-desplegable[open] > .ds-propiedades { margin: 0; } }
.ds-panel__lectura > :is(iframe, .ds-doc) { width: 100%; max-width: none; border: 0; }
.ds-panel__paso[hidden] { display: none; }
.ds-panel__pie {
  flex: none; position: sticky; bottom: 0; display: flex; justify-content: flex-end;
  padding: var(--ds-app-paso-12) var(--ds-spacing-sm) calc(var(--ds-app-paso-12) + env(safe-area-inset-bottom));
  background: var(--ds-color-bg-elevated); border-top: var(--ds-app-linea) solid var(--ds-color-border-subtle);
}
@media (max-width: 719px) {
  .ds-panel, .ds-panel--ancho { inset: 0; width: 100%; border-left: 0; }
  .ds-panel__cabecera { padding-left: var(--ds-spacing-xs); padding-top: calc(var(--ds-spacing-2xs) + env(safe-area-inset-top)); }
  .ds-panel__atras { margin-left: calc(var(--ds-spacing-2xs) - var(--ds-spacing-xs)); }
  .ds-panel__cuerpo { padding: var(--ds-spacing-xs); }
  .ds-panel__pie { padding-inline: var(--ds-spacing-xs); }
}
/* 1.15 (§ 0.2, § 18, § 59; P75, D11): bajo 720 los botones del pie de panel,
   diálogo y hoja ocupan TODO el ancho, repartidos a partes iguales en una
   fila (el primario a la derecha); uno solo, a todo el ancho. Antes el panel
   los apilaba y un pie con dos enlaces sueltos los dejaba a la derecha
   (Chrome de Android, «Cómo llegar» y «Registrar visita»).
   1.15.3 (SPEC-CRM4-DS-3, 3 quater): también el pie de formulario y las
   acciones del pie de una tarjeta («Guardar» y «Quitar campo» de Campos de
   la base no cabían en una línea a 393). En PC esas acciones parten línea. */
@media (max-width: 719px) {
  :is(.ds-panel__pie, .ds-dialogo__pie, .ds-hoja__pie, .ds-form__pie, .ds-tarjeta__acciones) { gap: var(--ds-spacing-2xs); }
  :is(.ds-panel__pie, .ds-dialogo__pie, .ds-hoja__pie, .ds-form__pie, .ds-tarjeta__acciones) > :not([hidden], input[type="hidden"]) { flex: 1 1 0; min-width: 0; }
  :is(.ds-panel__pie, .ds-dialogo__pie, .ds-hoja__pie, .ds-form__pie, .ds-tarjeta__acciones) > :is(form, .ds-botones--iguales) { display: grid; grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr); gap: var(--ds-spacing-2xs); width: 100%; }
  :is(.ds-panel__pie, .ds-dialogo__pie, .ds-hoja__pie, .ds-form__pie, .ds-tarjeta__acciones) .ds-boton:not(.ds-boton--icono) { width: 100%; }
}
/* 1.15.3 (3 quater, remate de R-2 con WebKit): en el pie de formulario y
   en las acciones de tarjeta los botones van a partes iguales SOLO si caben;
   si no, cada uno parte a su línea a todo el ancho. Nunca se encogen por
   debajo de su texto (con flex 1 1 0, «Volver a la ruta sin registrar» y
   «Quitar campo» sacaban el icono de la caja). */
@media (max-width: 719px) {
  :is(.ds-form__pie, .ds-tarjeta__acciones) { flex-wrap: wrap; }
  :is(.ds-form__pie, .ds-tarjeta__acciones) > :not([hidden], input[type="hidden"]) { flex: 1 1 auto; min-width: auto; }
  :is(.ds-form__pie, .ds-tarjeta__acciones) > form { grid-auto-columns: auto; width: auto; }
  :is(.ds-form__pie, .ds-tarjeta__acciones) > .ds-boton:not(.ds-boton--icono) { width: auto; }
}

/* ═══ 60. Menú ═══════════════════════════════════════════════════════ */
/* 1.6 (reglas.md § 24 y § 60): el «⋯» de una fila o tarjeta y el «Acciones»
   de una cabecera. Un button[aria-haspopup="menu"][data-menu="#id"] abre un
   .ds-menu[role="menu"][hidden]; app.js lo coloca fijo bajo el botón (encima
   si no cabe), lo muda al <dialog> abierto si el botón vive en uno, lo cierra
   con clic fuera, Esc, scroll o al elegir, y lo recorre con ↑ ↓ Inicio Fin.
   Lo destructivo, al final tras un separador y con el MISMO modificador que
   el botón (--destructivo; B18: un nombre por significado). Aparece con un
   fundido corto; sin JS de animación. */
.ds-menu {
  position: fixed; z-index: var(--ds-app-capa-menu);
  display: flex; flex-direction: column; margin: 0; box-sizing: border-box;
  min-width: var(--ds-app-menu-min); max-width: calc(100vw - 2 * var(--ds-spacing-xs));
  max-height: calc(100dvh - 2 * var(--ds-spacing-xs)); overflow-y: auto;
  padding: var(--ds-app-paso-medio);
  background: var(--ds-color-bg-elevated); color: var(--ds-color-text);
  border: var(--ds-app-linea) solid var(--ds-color-border); border-radius: var(--ds-radius-card);
  box-shadow: var(--ds-shadow-float); opacity: 1;
  transition: opacity var(--ds-motion-duration-instant) var(--ds-motion-easing-musho),
              display var(--ds-motion-duration-instant) allow-discrete;
}
.ds-menu[hidden] { display: none; opacity: 0; }
@starting-style { .ds-menu:not([hidden]) { opacity: 0; } }
.ds-menu__item {
  display: flex; align-items: center; gap: var(--ds-spacing-2xs); width: 100%;
  min-height: var(--ds-spacing-control); padding: 0 var(--ds-app-paso-12);
  border: 0; border-radius: var(--ds-radius-card); background: none; color: var(--ds-color-text);
  font-family: inherit; font-size: var(--ds-typography-size-ui-sm); line-height: var(--ds-typography-line-height-ui-sm);
  text-align: left; text-decoration: none; white-space: nowrap; cursor: pointer;
}
.ds-menu__item:hover, .ds-menu__item:focus-visible { background: var(--ds-color-bg-tertiary); }
.ds-menu__item:focus-visible { outline: var(--ds-app-foco) solid var(--ds-app-foco-color); outline-offset: calc(var(--ds-app-foco) * -1); }
.ds-menu__item .ds-icono { color: var(--ds-color-text-secondary); }
/* Texto de peligro en -fg (1.8): el sólido da 4,1 sobre el menú en oscuro. */
.ds-menu__item--destructivo, .ds-menu__item--destructivo .ds-icono { color: var(--ds-color-error-fg); }
.ds-menu__item--destructivo:hover, .ds-menu__item--destructivo:focus-visible { background: var(--ds-color-error-soft); }
.ds-menu__item[disabled] { color: var(--ds-color-text-disabled); background: none; cursor: not-allowed; }
.ds-menu__item[disabled] .ds-icono { color: var(--ds-color-text-disabled); }
.ds-menu__separador { height: 0; margin: var(--ds-app-paso-medio) 0; border: 0; border-top: var(--ds-app-linea) solid var(--ds-color-border-subtle); }
/* Selección (P65): radio, casilla u opción; el check (i-check) va a la
   derecha en todos los ítems y solo se ve en el marcado. */
.ds-menu--seleccion .ds-menu__item > svg:has(use[href$="#i-check"]) { margin-left: auto; visibility: hidden; color: var(--ds-color-text); }
.ds-menu--seleccion [aria-checked="true"] > svg:has(use[href$="#i-check"]),
.ds-menu--seleccion [aria-selected="true"] > svg:has(use[href$="#i-check"]) { visibility: visible; }
.ds-menu--seleccion [aria-checked="true"], .ds-menu--seleccion [aria-selected="true"] { font-weight: 500; }
/* 1.8.4 · --buscable (> 12 opciones): campo de filtro arriba; app.js oculta
   las opciones que no casan y Enter elige la primera visible. */
.ds-menu--buscable .ds-menu__filtro { position: sticky; top: 0; z-index: 1; flex: none; width: 100%; margin-bottom: var(--ds-app-paso-medio); }
.ds-menu__item[hidden] { display: none; }

/* ═══ 61. Tooltip ════════════════════════════════════════════════════ */
/* 1.6 (reglas.md § 25 y § 61): data-tooltip="texto" en cualquier cosa que
   reciba foco (botón de icono, cabecera de tabla). app.js crea UN
   #ds-tooltip y lo enseña a los 400 ms de ratón o al foco de teclado, arriba
   y centrado (abajo si no cabe); Esc lo quita. Nunca interactivo. */
.ds-tooltip {
  position: fixed; z-index: var(--ds-app-capa-tooltip); box-sizing: border-box;
  max-width: var(--ds-app-tooltip-max); padding: var(--ds-app-paso-medio) var(--ds-spacing-2xs);
  border-radius: var(--ds-radius-card); background: var(--ds-color-bg-inverse); color: var(--ds-color-text-inverse);
  font-size: var(--ds-typography-size-ui-sm); line-height: var(--ds-typography-line-height-ui-sm); font-weight: 500;
  pointer-events: none; opacity: 1;
  transition: opacity var(--ds-motion-duration-instant) var(--ds-motion-easing-musho),
              display var(--ds-motion-duration-instant) allow-discrete;
}
.ds-tooltip[hidden] { display: none; opacity: 0; }
@starting-style { .ds-tooltip:not([hidden]) { opacity: 0; } }

/* ═══ 62. Árbol ══════════════════════════════════════════════════════ */
/* 1.6 (P33, Confluence; reglas.md § 62): el árbol de Documentos promovido.
   Fila = [flecha | hueco] · icono · enlace · acciones. La flecha solo en los
   nodos con hijos; en los demás, __hueco del mismo ancho para que el texto
   alinee. Con icono, flecha e icono comparten hueco: se ve el icono y, al
   pasar el ratón o con foco, la flecha lo sustituye (--siempre-flecha los
   separa y deja la flecha a la vista). En < 720 no hay ratón: los nodos con
   hijos enseñan la flecha y las acciones se ven siempre. */
.ds-arbol, .ds-arbol__hijos { list-style: none; margin: 0; padding: 0; }
.ds-arbol__nodo { margin: 0; }
.ds-arbol__nodo[aria-expanded="false"] > .ds-arbol__hijos { display: none; }
.ds-arbol__fila {
  display: grid; grid-template-columns: var(--ds-icon-size-lg) minmax(0, 1fr) auto; align-items: center;
  column-gap: var(--ds-app-paso-medio); min-height: var(--ds-app-arbol-fila);
  padding: 0 var(--ds-app-paso-medio) 0 calc(var(--nivel, 0) * var(--ds-app-arbol-sangria) + var(--ds-app-paso-medio));
  border-radius: var(--ds-radius-card); color: var(--ds-color-text-secondary);
  transition: background-color var(--ds-motion-duration-instant) var(--ds-motion-easing-musho);
}
.ds-arbol--compacto .ds-arbol__fila { min-height: var(--ds-app-arbol-fila-compacta); }
.ds-sidebar .ds-arbol .ds-arbol__fila {
  padding-inline-start: calc(var(--ds-app-arbol-colgado) + var(--nivel, 0) * var(--ds-app-arbol-sangria) + var(--ds-app-paso-medio));
}
.ds-arbol__fila:hover { background: var(--ds-color-bg-tertiary); color: var(--ds-color-text); }
.ds-arbol__flecha, .ds-arbol__hueco, .ds-arbol__icono { grid-column: 1; grid-row: 1; }
.ds-arbol__flecha {
  display: inline-grid; place-items: center; width: var(--ds-icon-size-lg); height: var(--ds-icon-size-lg);
  padding: 0; border: 0; border-radius: var(--ds-radius-card); background: none;
  color: var(--ds-color-text-tertiary); cursor: pointer;
  transition: transform var(--ds-motion-duration-move) var(--ds-motion-easing-musho);
}
.ds-arbol__flecha:hover { background: var(--ds-color-bg-quaternary); color: var(--ds-color-text); }
.ds-arbol__nodo[aria-expanded="true"] > .ds-arbol__fila .ds-arbol__flecha { transform: rotate(90deg); }
.ds-arbol__hueco { width: var(--ds-icon-size-lg); }
/* 1.8.3: el icono no se come el clic del chevron que lo sustituye al pasar el ratón. */
.ds-arbol__icono { justify-self: center; color: var(--ds-color-text-tertiary); pointer-events: none; }
.ds-arbol__fila:has(.ds-arbol__icono) .ds-arbol__flecha { opacity: 0; }
.ds-arbol__fila:has(.ds-arbol__icono):is(:hover, :focus-within) .ds-arbol__flecha { opacity: 1; }
.ds-arbol__fila:has(.ds-arbol__flecha):is(:hover, :focus-within) .ds-arbol__icono { opacity: 0; }
.ds-arbol--siempre-flecha .ds-arbol__fila { grid-template-columns: var(--ds-icon-size-lg) auto minmax(0, 1fr) auto; }
.ds-arbol--siempre-flecha .ds-arbol__icono { grid-column: 2; opacity: 1; }
.ds-arbol--siempre-flecha .ds-arbol__flecha { opacity: 1; }
.ds-arbol__enlace {
  grid-column: -3; display: flex; align-items: center; min-width: 0; min-height: inherit;
  color: inherit; text-decoration: none; border-radius: var(--ds-radius-card);
  font-size: var(--ds-typography-size-ui-sm); line-height: var(--ds-typography-line-height-ui-sm); font-weight: 500;
}
.ds-arbol__titulo { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ds-arbol__acciones { grid-column: -2; display: flex; align-items: center; opacity: 0; }
.ds-arbol__fila:is(:hover, :focus-within) .ds-arbol__acciones { opacity: 1; }
/* 1.17 (§ 62, E12): con ratón, las acciones (+, ⋯) solo ocupan su hueco al
   pasar o con foco dentro de la fila; el nombre ya no se corta 68 px antes
   en reposo. En táctil (hover: none) lo reservan siempre, como antes. */
@media (hover: hover) {
  .ds-arbol__fila:not(:hover, :focus-within) > .ds-arbol__acciones { display: none; }
}
.ds-arbol--compacto .ds-arbol__acciones .ds-boton--icono { width: var(--ds-app-arbol-fila-compacta); min-height: var(--ds-app-arbol-fila-compacta); }
/* Activo: el padre plegado hereda «aquí estás» si el activo está dentro
   (Carbon 03:L188). 1.17 (§ 62, I6): se marca con FONDO, la caja del ítem
   activo de la barra (bg-tertiary, texto en su color), no con coral: el
   coral es solo el ítem activo de la navegación (§ 0.7). En la página de un
   documento había dos corales a la vez (Documentos y la página abierta). */
.ds-arbol__fila[aria-current], .ds-arbol__fila:has(> .ds-arbol__enlace[aria-current]),
.ds-arbol__nodo[aria-expanded="false"]:has(.ds-arbol__hijos [aria-current]) > .ds-arbol__fila {
  background: var(--ds-color-bg-tertiary); color: var(--ds-color-text);
}
.ds-arbol__fila[aria-current] .ds-arbol__icono, .ds-arbol__fila:has(> .ds-arbol__enlace[aria-current]) .ds-arbol__icono { color: inherit; }
/* 1.19.3 (§ 62, Yaz 02/10 «hazlo igual que en proyectos»): árbol vestido
   como la lista de § 8, para el árbol que ES el contenido de una página
   (/documentos), nunca en la barra. Anillo + sombra xs de la .ds-lista,
   una línea entre filas (también entre padre e hijos: se lee como una
   lista), fila de 44 con el texto a 16 del borde y en el color del texto.
   Sin chevron a la derecha: el árbol ya lleva la flecha a la izquierda.
   El icono sigue en gris (D1). La caja recorta el fondo del hover en las
   esquinas (overflow: clip; la .ds-lista lo hace heredando radios, pero
   aquí la última fila visible puede estar a cualquier nivel). */
.ds-arbol--lista {
  overflow: clip; background: var(--ds-color-bg-elevated);
  border-radius: var(--ds-app-radio-tarjeta); box-shadow: var(--ds-app-anillo), var(--ds-app-sombra-xs);
}
.ds-arbol--lista .ds-arbol__fila {
  min-height: var(--ds-spacing-touch); border-radius: 0; color: var(--ds-color-text);
  padding-inline: calc(var(--nivel, 0) * var(--ds-app-arbol-sangria) + var(--ds-spacing-xs)) var(--ds-spacing-xs);
  border-top: var(--ds-app-linea) solid var(--ds-color-border-subtle);
}
.ds-arbol--lista > .ds-arbol__nodo:first-child > .ds-arbol__fila { border-top: 0; }
/* 1.19.4: con la caja de identidad (32; 36 bajo 720) la primera columna mide
   la caja y la flecha va centrada en ella; el texto a 12 de la caja y 12
   arriba y abajo, como la .ds-fila; cada nivel entra 24 (con 12 la caja del hijo casi tocaba la del
   padre). */
.ds-arbol--lista .ds-arbol__fila:has(.ds-fila__icono--tinte) {
  padding-inline-start: calc(var(--nivel, 0) * var(--ds-aire-campo) + var(--ds-spacing-xs));
  padding-block: var(--ds-app-paso-12); /* 12 arriba y abajo, como la .ds-fila (1.19.2): la fila mide 56, la de Proyectos */
  grid-template-columns: var(--ds-app-icono-caja) minmax(0, 1fr) auto; column-gap: var(--ds-app-paso-12);
}
.ds-arbol--lista .ds-arbol__fila:has(.ds-fila__icono--tinte) .ds-arbol__flecha { justify-self: center; }
/* El enlace mide la caja (no hereda los 44 de la fila, que con el relleno de
   12 daban 68): 12 + 32 + 12 = 56 en PC y 12 + 36 + 12 = 60 bajo 720. */
.ds-arbol--lista .ds-arbol__fila:has(.ds-fila__icono--tinte) .ds-arbol__enlace { min-height: var(--ds-app-icono-caja); }
@media (max-width: 719px) {
  .ds-arbol--lista .ds-arbol__fila:has(.ds-fila__icono--tinte) { grid-template-columns: var(--ds-app-icono-caja-movil) minmax(0, 1fr) auto; }
  .ds-arbol--lista .ds-arbol__fila:has(.ds-fila__icono--tinte) .ds-arbol__enlace { min-height: var(--ds-app-icono-caja-movil); }
  /* La caja crece a 36 como en la fila: «.ds-arbol .ds-icono» (§ 19, 24 al
     dedo) va antes en el fichero que este bloque y la dejaba en 24. */
  .ds-arbol--lista .ds-arbol__icono.ds-fila__icono--tinte {
    width: var(--ds-app-icono-caja-movil); height: var(--ds-app-icono-caja-movil);
    padding: calc((var(--ds-app-icono-caja-movil) - var(--ds-icon-size-lg)) / 2);
  }
}
.ds-arbol--lista .ds-arbol__enlace { font-size: var(--ds-typography-size-ui-md); line-height: var(--ds-typography-line-height-ui-md); font-weight: 400; }
@media (max-width: 719px) {
  .ds-arbol__fila:has(.ds-arbol__icono) .ds-arbol__flecha { opacity: 1; }
  .ds-arbol__fila:has(.ds-arbol__flecha) .ds-arbol__icono { opacity: 0; }
  .ds-arbol--siempre-flecha .ds-arbol__fila .ds-arbol__icono { opacity: 1; }
  .ds-arbol__acciones { opacity: 1; }
}
/* 1.8.5 · caja táctil 40 bajo 1056 (§ 62, § 2; P47 «controles táctiles a 40»):
   la CAJA del chevron (24) y de la X del chip (16) crece a 40×40 con relleno y
   la anula con margen negativo, así el sitio que ocupan en la fila no se mueve;
   el dibujo no cambia (la 1.8.3 lo hacía con un ::after y la caja seguía en
   24/16: el banco mide la caja). El fondo del hover del chevron va en ::before
   a 24, para que siga siendo el mismo cuadrado. */
@media (max-width: 1055px) {
  .ds-arbol__flecha, .ds-chip__quitar {
    position: relative; isolation: isolate; box-sizing: border-box;
    width: var(--ds-spacing-control); height: var(--ds-spacing-control);
  }
  .ds-arbol__flecha {
    padding: calc((var(--ds-spacing-control) - var(--ds-icon-size-lg)) / 2);
    margin: calc((var(--ds-icon-size-lg) - var(--ds-spacing-control)) / 2);
  }
  .ds-arbol__flecha, .ds-arbol__flecha:hover { background: none; }
  .ds-arbol__flecha::before {
    content: ""; position: absolute; z-index: -1; border-radius: var(--ds-radius-card);
    inset: calc((var(--ds-spacing-control) - var(--ds-icon-size-lg)) / 2);
  }
  .ds-arbol__flecha:hover::before { background: var(--ds-color-bg-quaternary); }
  .ds-chip__quitar {
    padding: calc((var(--ds-spacing-control) - var(--ds-icon-size-sm)) / 2);
    margin: calc((var(--ds-icon-size-sm) - var(--ds-spacing-control)) / 2);
    margin-right: calc((var(--ds-icon-size-sm) - var(--ds-spacing-control)) / 2 - var(--ds-app-paso-medio));
  }
}

/* ═══ 63. Barra de vista ═════════════════════════════════════════════ */
/* 1.6 (reglas.md § 23 y § 63; Carbon 02:L389-390, Notion): encima de una
   lista o tabla. Izquierda, filtros y orden (iconos); derecha, la búsqueda
   que se expande y el «Crear» en sm. Los filtros aplicados van en una fila
   plegable debajo; plegada, el botón de filtros lleva el contador. */
.ds-vista-barra {
  display: flex; align-items: center; justify-content: space-between; gap: var(--ds-spacing-2xs);
  min-height: var(--ds-app-vista-barra);
}
.ds-vista-barra__filtros, .ds-vista-barra__acciones { display: flex; align-items: center; gap: var(--ds-app-paso-medio); min-width: 0; }
/* 1.15.3 (V-4): las acciones no encogen; a 1090 con la barra abierta «Nueva
   tarea» se salía 32 px. Cede la zona de filtros. */
.ds-vista-barra__acciones { flex: none; }
/* 1.17.1 (§ 23, R2): en PC todo control de la barra mide 36, lo que el chip
   de filtro y el crear --sm: la lupa, el filtro y el orden (botones de
   icono de 40) desentonaban (alto-control-barra, Proyectos y Biblioteca).
   El enlace («Quitar filtros») sigue a su línea (§ 1). Bajo 1056, al dedo,
   como estaba. La barra conserva su alto mínimo (40): nada se mueve. */
@media (min-width: 1056px) {
  .ds-vista-barra :is(.ds-boton, .ds-campo__control):not(.ds-boton--enlace) { min-height: var(--ds-app-boton-sm); height: var(--ds-app-boton-sm); }
  .ds-vista-barra .ds-boton--icono { width: var(--ds-app-boton-sm); }
}
.ds-vista-barra__filtros .ds-boton--icono { position: relative; }
/* 1.15.3 (P58): el número del botón de filtros es .ds-contador (§ 75),
   sobre la esquina del botón; se borra .ds-vista-barra__num. Abierta la
   fila de filtros, el número sobra: los filtros se ven. */
.ds-vista-barra__filtros .ds-boton--icono > .ds-contador { position: absolute; top: 0; right: 0; margin: 0; }
[aria-expanded="true"] > .ds-contador { display: none; }
/* Búsqueda expandible: la lupa; al pulsarla, el campo de 240 con su X. Vacía
   y sin foco, se pliega. */
.ds-buscar--expandible { position: relative; display: flex; align-items: center; }
.ds-buscar--expandible .ds-campo__control {
  width: 0; min-width: 0; padding: 0; border-width: 0; opacity: 0;
  transition: width var(--ds-motion-duration-move) var(--ds-motion-easing-musho),
              opacity var(--ds-motion-duration-move) var(--ds-motion-easing-musho);
}
.ds-buscar--expandible[data-abierto] .ds-campo__control {
  width: var(--ds-app-buscar-ancho); padding: 0 var(--ds-spacing-control) 0 var(--ds-app-paso-12);
  border-width: var(--ds-app-linea); opacity: 1;
}
.ds-buscar--expandible[data-abierto] > .ds-boton--icono:first-child { display: none; }
.ds-buscar--expandible .ds-campo__control + .ds-boton { position: absolute; right: 0; }
/* 1.14 (§ 23, A7): búsqueda FIJA, la variante no expandible (la página de
   buscar, un buscador que es el cuerpo de la página): campo de 40, radio 8,
   la lupa dentro a la izquierda y la X de pieza a la derecha (oculta con el
   campo vacío; app.js la enseña al escribir y la pulsa). */
.ds-buscar { position: relative; display: flex; align-items: center; min-width: 0; }
.ds-buscar > .ds-icono {
  position: absolute; left: var(--ds-app-paso-12); pointer-events: none; color: var(--ds-color-text-tertiary);
}
.ds-buscar > .ds-campo__control {
  min-height: var(--ds-spacing-control); border-radius: var(--ds-app-radio-control);
  padding: 0 var(--ds-spacing-control) 0 calc(var(--ds-app-paso-12) * 2 + var(--ds-icon-size));
}
.ds-buscar > .ds-campo__control + .ds-boton { position: absolute; right: calc((var(--ds-spacing-control) - var(--ds-app-boton-sm)) / 2); }
/* La X azul del navegador no es la de la pieza (§ 0.7: el azul no es color
   de la app): fuera en TODA búsqueda del DS, fija o expandible. */
:is(.ds-buscar, .ds-buscar--expandible) input[type="search"]::-webkit-search-cancel-button { -webkit-appearance: none; appearance: none; }
.ds-filtros { display: flex; flex-wrap: wrap; align-items: center; gap: var(--ds-spacing-2xs); padding-top: var(--ds-spacing-2xs); }
.ds-filtros[hidden] { display: none; }
.ds-filtros:not(:has(.ds-chip--filtro, [data-aplicado], .ds-chip[aria-pressed="true"])) .ds-boton--enlace { display: none; }
/* 1.15 (§ 70, D4 d; P51 «Quitar filtros» y «29 tareas» pegados): «Quitar
   filtros» (x + texto) cierra la fila de filtros; el recuento va a la
   derecha de la barra, separado. */
.ds-filtros__recuento {
  margin: 0 0 0 auto; padding-inline-start: var(--ds-spacing-xs);
  font-size: var(--ds-typography-size-ui-xs); line-height: var(--ds-typography-line-height-ui-xs);
  color: var(--ds-color-text-secondary); font-variant-numeric: tabular-nums; white-space: nowrap;
}
/* 1.15 (§ 70, D4 c; P49 «avatar además del nombre, en todos sitios»): la
   persona en un chip de filtro o en una opción de menú lleva su avatar a 20
   delante del nombre; el chip sigue midiendo 36 (44 al dedo). */
:is(.ds-chip, .ds-filtros__chip, .ds-menu__item) > .ds-avatar {
  width: var(--ds-app-avatar-sm); height: var(--ds-app-avatar-sm);
  font-size: var(--ds-typography-size-ui-xs);
}
.ds-chip > .ds-avatar:first-child { margin-inline-start: calc(var(--ds-app-paso-medio) * -1); }
/* 1.16 (§ 70, P2): con un chip pulsado en la fila, los no pulsados se ven
   apagados ENTEROS: el texto ya va en secundario y el avatar (letra, emoji o
   foto) baja a la misma medida; al pasar o enfocar, vuelve entero. Antes una
   foto o un avatar de color (Asier) se veía encendido y el emoji de Ari no. */
:is(.ds-filtros, .ds-vista-barra, .ds-chips):has(.ds-chip[aria-pressed="true"]) .ds-chip[aria-pressed="false"]:not(:hover, :focus-visible) > .ds-avatar { opacity: var(--ds-app-atenuado); }
/* ═══ 70. Barra de filtros: el chip de filtro de una sección ═════════ */
/* 1.12 (§ 70, decisión 9): «Intervalo · Últimos 30 días ▾». Es un
   .ds-selector con ropa de Untitled (btn sm + badge-modern): alto 36, radio
   8, anillo de 1 px border-primary (--ds-color-border-strong) y sombra xs.
   Dentro, la etiqueta 12/600 terciaria, una raya de 1 px y el valor 14/600.
   El valor es el PRIMER span (app.js lo reescribe al elegir); la etiqueta es
   un small y se pinta delante con order. Bajo 1056, táctil: 40 (§ 2). Los
   28 de Stripe no se copian (§ 30.19). */
.ds-seccion > .ds-filtros, .ds-vista-barra .ds-filtros { padding-top: 0; }
.ds-filtros__chip {
  display: inline-flex; align-items: center; gap: var(--ds-spacing-2xs);
  box-sizing: border-box; min-height: var(--ds-app-boton-sm); margin: 0;
  padding: 0 var(--ds-app-paso-12); border: 0; border-radius: var(--ds-app-radio-control);
  background: var(--ds-color-bg-elevated); color: var(--ds-color-text);
  box-shadow: inset 0 0 0 var(--ds-app-linea) var(--ds-color-border-strong), var(--ds-app-sombra-xs);
  font-family: inherit; font-size: var(--ds-typography-size-ui-sm); line-height: var(--ds-typography-line-height-ui-sm);
  font-weight: 600; white-space: nowrap; cursor: pointer;
  transition: background-color var(--ds-motion-duration-instant) var(--ds-motion-easing-musho);
}
.ds-filtros__chip:hover, .ds-filtros__chip[aria-expanded="true"] { background: var(--ds-color-bg-tertiary); }
.ds-filtros__chip:focus-visible { outline: var(--ds-app-foco) solid var(--ds-app-foco-color); outline-offset: var(--ds-app-foco); }
.ds-filtros__etiqueta {
  order: -1; padding-inline-end: var(--ds-spacing-2xs);
  border-inline-end: var(--ds-app-linea) solid var(--ds-color-border);
  font-size: var(--ds-typography-size-ui-xs); line-height: var(--ds-typography-line-height-ui-xs);
  font-weight: 600; color: var(--ds-color-text-tertiary);
}
.ds-filtros__valor { color: var(--ds-color-text); }
/* 1.14.2: sobre el fondo del chip aplicado el terciario daba 4,24 (axe). */
.ds-filtros__chip[data-aplicado] .ds-filtros__etiqueta { color: var(--ds-color-text-secondary); }
/* 1.14 (§ 23, § 70, H5): UN solo filtro. Todo chip dentro de una barra de
   vista o de filtros mide lo que el chip de filtro (36, radio 8): el Board
   pintaba píldoras de 32 y Tareas cajas de campo de 40/4. Aplicado = fondo
   secundario + anillo, NUNCA el negro del primario: se distingue sin
   parecer el botón principal. El chip de filtro aplicado lleva
   data-aplicado. Bajo 1056, el que se pulsa a alto de dedo (44). */
.ds-vista-barra .ds-chip, .ds-filtros .ds-chip { height: var(--ds-app-boton-sm); border-radius: var(--ds-app-radio-control); }
.ds-vista-barra .ds-chip:is([aria-pressed="true"], .es-activo),
.ds-filtros .ds-chip:is([aria-pressed="true"], .es-activo, .ds-chip--filtro),
.ds-filtros__chip[data-aplicado] {
  background: var(--ds-app-fondo-secundario); color: var(--ds-color-text);
  box-shadow: inset 0 0 0 var(--ds-app-linea) var(--ds-color-border-strong);
}
/* 1.17 (§ 70, R2): UNA medida de control en la fila de filtros. Persona
   (chip) y selector (también el que lleva ropa de campo) miden lo que el
   chip de filtro: 36 de alto, radio 8. «Quitar filtros» (enlace) queda a
   su línea: chocaba con § 1 «el enlace no lleva alto de control en PC»
   (enlace-alto-pc ≤ 32); devuelto a plan. */
.ds-filtros .ds-selector__boton.ds-campo__control { min-height: var(--ds-app-boton-sm); height: var(--ds-app-boton-sm); }
@media (max-width: 1055px) {
  .ds-filtros__chip { min-height: var(--ds-spacing-touch); }
  .ds-vista-barra :is(a, button).ds-chip, .ds-filtros :is(a, button).ds-chip { height: auto; min-height: var(--ds-spacing-touch); }
  .ds-filtros .ds-selector__boton.ds-campo__control { min-height: var(--ds-spacing-touch); height: auto; }
}
/* Chip de filtro aplicado: «Campo · contiene · X» y su X. No es un botón. */
.ds-chip--filtro { cursor: default; color: var(--ds-color-text); padding-right: var(--ds-spacing-2xs); }

/* ═══ 64. Desplegable ════════════════════════════════════════════════ */
/* 1.6 (reglas.md § 64; Carbon 02:L156): para filtros, propiedades y
   acciones. En formularios y en < 720 se usa el <select> nativo (el sistema
   lo hace mejor en el móvil). Abre un .ds-menu--seleccion[role="listbox"] con
   el mismo JS del menú; al elegir, el texto del botón cambia y salta change. */
/* .ds-selector (cat. 64): el desplegable de filtros y propiedades. Se llama selector y no
   desplegable porque .ds-desplegable ya es el <details> de documento (A8): un nombre por pieza (B18). */
.ds-selector { position: relative; display: inline-flex; min-width: 0; max-width: 100%; margin: 0; }
.ds-selector__boton {
  display: flex; align-items: center; justify-content: space-between; gap: var(--ds-spacing-2xs);
  width: auto; text-align: left; cursor: pointer;
}
.ds-selector__boton > span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ds-selector__boton > svg { color: var(--ds-color-text-tertiary); }
.ds-selector__boton:hover { border-color: var(--ds-color-border-strong); }
.ds-selector__boton[aria-expanded="true"] { border-color: var(--ds-color-border-strong); box-shadow: var(--ds-app-sombra-xs); }

/* ═══ 65. Cabecera móvil segmentada ══════════════════════════════════ */
/* 1.6 (P38, Revolut/Apple): avatar · segmentado en píldora al centro · UN
   botón redondo. Sin título cuando la página lleva migas (P39). --transparente
   quita fondo y raya (sobre una portada). */
/* 1.12 (decisión 10, Opus H5): rejilla 1fr auto 1fr (avatar | segmentado |
   acción): el segmentado cae en el centro de la pantalla aunque los lados
   midan distinto. Antes, flex + margin-left:auto en __acciones lo
   desplazaba 19 px (medidas-ds.js l). */
.ds-cabecera--segmentada { display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); }
.ds-cabecera--segmentada .ds-cabecera__inicio { justify-self: start; }
.ds-cabecera--segmentada .ds-cabecera__acciones { justify-self: end; margin-left: 0; }
@media (min-width: 1056px) { .ds-cabecera--segmentada { display: none; } }
.ds-cabecera--segmentada .ds-segmentado {
  display: inline-grid; grid-auto-flow: column; grid-auto-columns: 1fr; margin-inline: 0;
  min-height: var(--ds-app-chip); box-sizing: border-box;
  padding: var(--ds-app-segmentado-relleno);
}
/* 1.14: la excepción del segmentado intrínseco (§ 3) conserva su relleno de 3. */
.ds-cabecera--segmentada .ds-segmentado__indicador { top: var(--ds-app-segmentado-relleno); bottom: var(--ds-app-segmentado-relleno); }
.ds-cabecera--segmentada .ds-segmentado__item { min-height: calc(var(--ds-app-chip) - 2 * var(--ds-app-segmentado-relleno)); }
.ds-cabecera--segmentada .ds-cabecera__acciones .ds-boton--icono { border-radius: var(--ds-radius-full); }
/* 1.8.2: bajo 1056 el segmentado se toca con el dedo: 40 (regla 40/44); el 26 solo vale en PC. */
@media (max-width: 1055px) {
  /* 1.17 (§ 20, § 33, E1): la cabecera segmentada mide lo que la normal (56):
     su segmentado con marco (40 + 2 × 3 = 46) la subía a 62 y el h1 (o las
     migas) arrancaba 6 px más abajo que en Hoy o en una ficha. El marco del
     segmentado sale del relleno vertical de la barra. Así el h1 de toda
     página sin migas arranca en y 72 y las migas en 72 (h1 en 108). */
  .ds-cabecera--segmentada {
    padding-block: calc(var(--ds-spacing-2xs) - var(--ds-app-segmentado-relleno) + env(safe-area-inset-top)) calc(var(--ds-spacing-2xs) - var(--ds-app-segmentado-relleno));
  }
  .ds-cabecera--segmentada .ds-segmentado { min-height: var(--ds-spacing-control); }
  .ds-cabecera--segmentada .ds-segmentado__item { min-height: var(--ds-spacing-control); } /* 1.14.3: el ítem, a 40 como el CRM lo medía */
}
/* 1.8.2: «transparente» = sin relleno propio, no sin fondo. La cabecera es
   sticky: con fondo transparente el texto pasaba por debajo al hacer scroll.
   Lleva el fondo de la página, sin borde ni sombra. */
.ds-cabecera--transparente { background: var(--ds-color-bg); border-bottom: 0; box-shadow: none; }

/* ═══ 66. Página de detalle ══════════════════════════════════════════ */
/* 1.6 (P18-P21, Stripe): dentro de .ds-pagina--ancha, bajo un .ds-titulo.
   Desde 1056, principal + columna lateral de 320 (propiedades apiladas y
   actividad); por debajo, una columna y el lateral DESPUÉS del principal. */
.ds-detalle { display: flex; flex-direction: column; gap: var(--ds-spacing-lg); min-width: 0; max-width: var(--ds-app-pagina-app); }
@media (min-width: 1056px) {
  .ds-detalle { display: grid; grid-template-columns: minmax(0, 1fr) var(--ds-app-detalle-lateral); align-items: start; }
}
/* 1.19.0 (§ 0.16): sección → sección 32 (aire-seccion; antes 48). */
.ds-detalle__principal { display: flex; flex-direction: column; gap: var(--ds-aire-seccion); min-width: 0; }
.ds-detalle__lateral { display: flex; flex-direction: column; gap: var(--ds-spacing-md); min-width: 0; }
.ds-detalle__principal > .ds-seccion > .ds-boton--enlace { align-self: flex-start; }
/* 1.17 (§ 54, E2): en la columna principal de un detalle, lo que sigue al
   título de una sección y no es su __texto (formulario, .ds-notas, .ds-campo,
   lista) queda a 16 del título (hueco 4 + 12), como en las secciones de
   primer nivel; antes a 4 (Actividad, Notas y Detalle del lead y la tarea). */

/* ═══ 67. Propiedades editables ══════════════════════════════════════ */
/* 1.6 (D3, P23-P27; Notion, el peek del CRM): cada valor se edita en su
   sitio, sin caja en reposo. Un menú (button __ctrl, con su chevron al pasar
   el ratón) o un campo (input/textarea __input, caja solo con foco). Guardar
   es de la app (evento change); el DS pinta y abre. --apiladas pone la
   etiqueta encima (columna estrecha, < 320: el lateral del detalle). */
.ds-propiedades__ctrl, .ds-propiedades__input {
  width: 100%; min-width: 0; box-sizing: border-box; margin: 0;
  min-height: var(--ds-app-prop-fila); padding: 0 var(--ds-radius-bar);
  border: var(--ds-app-linea) solid transparent; border-radius: var(--ds-radius-chip);
  background: transparent; color: var(--ds-color-text); font: inherit; text-align: left;
}
.ds-propiedades__ctrl { cursor: pointer; }
/* 1.16 (§ 67, devuelto por LEAD): un valor largo en un __ctrl («Próxima
   acción» escrita a mano) se recorta con «…» en UNA línea y el chevron se
   queda a la derecha; antes la fila partía y el chevron bajaba a otra línea. */
.ds-propiedades__ctrl { flex-wrap: nowrap; }
.ds-propiedades__ctrl > span:not(.ds-avatar, .ds-etiqueta, .ds-chip) { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ds-propiedades__ctrl > svg:has(use[href*="chevron"]) { margin-left: auto; color: var(--ds-color-text-tertiary); opacity: 0; }
.ds-propiedades__ctrl:is(:hover, :focus-visible, [aria-expanded="true"]) > svg:has(use[href*="chevron"]) { opacity: 1; }
.ds-propiedades__ctrl[aria-expanded="true"] { background: var(--ds-color-bg-tertiary); }
/* 1.17.1 (§ 67): la prioridad (receta .prio del CRM, 12/700 en su tarjeta)
   dentro de una propiedad se lee como el resto de valores: 14/20/400 y en
   el color del texto. Sigue siendo icono + texto, no una etiqueta (§ 6). */
.ds-propiedades .prio {
  font-size: var(--ds-typography-size-ui-sm); line-height: var(--ds-typography-line-height-ui-sm); font-weight: 400;
}
.ds-propiedades .prio .prio-texto { color: var(--ds-color-text); letter-spacing: normal; }
.ds-propiedades__input { cursor: text; }
.ds-propiedades__input::placeholder { color: var(--ds-color-text-tertiary); }
.ds-propiedades__input:focus { outline: none; border-color: var(--ds-color-border-strong); background: var(--ds-color-bg-elevated); }
/* 1.15 (§ 5, P8): sin asa, como la caja de § 5; alto por contenido entre 3 y
   12 líneas de SU letra (1lh) y luego desplazamiento. */
textarea.ds-propiedades__input {
  padding-block: var(--ds-app-paso-medio); resize: none; field-sizing: content; overflow-y: auto;
  min-height: max(calc(var(--ds-app-prop-fila) * 2), calc(var(--ds-app-caja-lineas-min) * 1lh + 2 * var(--ds-app-paso-medio) + 2 * var(--ds-app-linea)));
  max-height: calc(var(--ds-app-caja-lineas-max) * 1lh + 2 * var(--ds-app-paso-medio) + 2 * var(--ds-app-linea));
}
/* 1.8.4: bajo 1056 se toca con el dedo: 40 (regla 40/44); en PC, 34. */
@media (max-width: 1055px) { .ds-propiedades__ctrl, .ds-propiedades__input { min-height: var(--ds-spacing-control); } }
.ds-propiedades--apiladas .ds-propiedades__fila {
  grid-template-columns: minmax(0, 1fr); gap: var(--ds-app-paso-medio); align-items: start;
  min-height: 0; margin-bottom: var(--ds-app-paso-12);
}
.ds-propiedades--apiladas .ds-propiedades__valor { padding-inline: 0; }
.ds-propiedades--apiladas .ds-propiedades__etiqueta { min-height: 0; }
.ds-propiedades--apiladas.ds-propiedades--editable .ds-propiedades__valor { padding-inline: var(--ds-radius-bar); margin-inline: calc(var(--ds-radius-bar) * -1); }
/* 1.10.2 (n): el control no tiene fondo al pasar, así que tampoco el margen
   de −6 que lo compensa: sin él, su caja caía 6 px antes de la key line. */
.ds-propiedades--apiladas.ds-propiedades--editable .ds-propiedades__valor--control { padding-inline: 0; margin-inline: 0; }

/* ═══ 71. Lo que el CRM corregía desde fuera (1.14.3) ════════════════
   Reglas que vivían en estilo.css con la marca «devuelto-al-ds» y eran de la
   pieza, no de una pantalla (software/crm/DEVUELTO-AL-DS-2026-09-30.md).
   Van al final para ganar a la regla base de su pieza, como ganaban
   desde estilo.css. Sin cambio visible (huella-estilos.js a 0). */

/* Pila: dos .ds-alerta seguidas se separan con xs. */
.ds-alerta + .ds-alerta { margin-top: var(--ds-spacing-xs); }
/* Una lista larga dentro de un diálogo: tope de alto, scroll y borde sutil. */
.ds-dialogo .ds-lista {
  max-height: min(16rem, 45vh); overflow-y: auto;
  border: var(--ds-app-linea) solid var(--ds-color-border-subtle);
}
/* .ds-pestanas: aire arriba en la pila de la página. */
.ds-pestanas { margin-top: var(--ds-spacing-xs); }
/* Enlace dentro de .ds-titulo__meta: color heredado y corte de palabra. */
.ds-titulo__meta a { color: inherit; overflow-wrap: anywhere; }
/* El icono delante del h1 no encoge y va en el color del título (1.16, P1/D1;
   antes gris terciario y, con --marca, coral). 1.17 (§ 21, I3/I4): mide 24 en
   TODA página (con un h1 de 28; antes 20 en Proyecto y 24 en Documentos) y
   flex-shrink 0 explícito (a 393 el del proyecto de vendedor medía 1 × 20). */
.ds-titulo__h1 > .ds-icono { flex: none; flex-shrink: 0; width: var(--ds-icon-size-lg); height: var(--ds-icon-size-lg); color: var(--ds-color-text); }
/* 1.16 (§ 21, P7): un h1 con icono delante lo lleva EN LÍNEA con el nombre,
   centrado en vertical y a 8, mida lo que mida el nombre (el .ds-icono es
   block y caía encima). Como el título de la Biblioteca. La etiqueta de
   estado del h1 se separa por el hueco, no por su margen (sumaban 16). */
.ds-titulo__h1:has(> .ds-icono, > .ds-boton--icono) { display: flex; align-items: center; gap: var(--ds-spacing-2xs); }
.ds-titulo__h1:has(> .ds-icono, > .ds-boton--icono) .ds-etiqueta { margin-inline-start: 0; }
/* 1.17 (§ 21, U3): el h1 de una página de detalle (el .ds-titulo que va
   delante de un .ds-detalle) se recorta a 2 líneas; el nombre entero va en
   title= (lo ponen las vistas) y en el lateral. Con icono el h1 es flex: se
   recorta su primer texto, no la fila. Antes 5 líneas en una tarea a 1090. */
.ds-titulo:has(~ .ds-detalle) .ds-titulo__h1:not(:has(> .ds-icono, > .ds-boton--icono)),
.ds-titulo:has(~ .ds-detalle) .ds-titulo__h1:has(> .ds-icono, > .ds-boton--icono) > span:not(.ds-etiqueta, :has(> .ds-etiqueta)) {
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2; overflow: hidden; min-width: 0;
}
/* Una etiqueta con tooltip dice que hay algo más: cursor de ayuda. */
.ds-etiqueta[data-tooltip] { cursor: help; }
/* La X de un chip, cuando es enlace: sin subrayado. */
a.ds-chip__quitar { text-decoration: none; }

/* Barra lateral: la persona del pie ocupa el hueco; su rol, legible sobre el activo. */
.ds-sidebar__perfil { flex: 1; min-width: 0; min-height: auto; padding-block: var(--ds-app-paso-medio); }
.ds-sidebar__perfil[aria-current] .ds-sidebar__rol { color: var(--ds-color-text-secondary); }
@media (min-width: 1056px) {
  /* Maqueta de app con .ds-sidebar HIJA DEL BODY: rejilla barra + contenido;
     la columna de la barra se pinta también en el body (sin costura en páginas
     altas). Con `>` (1.14.5): una barra de muestra dentro de la página (el
     catálogo) no convierte la página entera en maqueta. */
  body:has(> .ds-sidebar) {
    display: grid; grid-template-columns: var(--ds-spacing-sidebar) minmax(0, 1fr);
    align-items: start; min-height: 100vh;
    background: linear-gradient(90deg, var(--ds-color-bg-elevated) var(--ds-spacing-sidebar), var(--ds-color-bg) var(--ds-spacing-sidebar)) var(--ds-color-bg);
  }
  body > .ds-sidebar { grid-column: 1; grid-row: 1 / span 3; position: sticky; top: 0; height: 100vh; overflow-y: auto; }
  /* 1.15.1 (P19, YA PEDIDO; medidas-ds barra-animada): plegar anima 200 ms
     con la curva del panel derecho: la columna de la barra va de 280 a 0 y
     la barra se desliza a la izquierda; al terminar se oculta (visibility,
     fuera del tabulador). La rejilla se queda: el contenido no salta. */
  body:has(> .ds-sidebar) { transition: grid-template-columns var(--ds-app-barra-plegar) var(--ds-motion-easing-musho); }
  body > .ds-sidebar { transition: transform var(--ds-app-barra-plegar) var(--ds-motion-easing-musho), visibility 0s linear 0s; }
  html[data-sidebar="plegada"] body:has(> .ds-sidebar) { grid-template-columns: 0px minmax(0, 1fr); background: var(--ds-color-bg); }
  html[data-sidebar="plegada"] body > .ds-sidebar { transform: translateX(-100%); visibility: hidden; transition: transform var(--ds-app-barra-plegar) var(--ds-motion-easing-musho), visibility 0s linear var(--ds-app-barra-plegar); }
  /* El texto suelto de la página y el de una alerta, a ancho de lectura. */
  .ds-pagina > p, .ds-pagina > .ds-alerta > .ds-alerta__texto { max-width: 40rem; }
}

/* Propiedades: en lectura el valor encoge y corta palabras largas; editable = campo plano. */
.ds-panel__lectura .ds-propiedades__valor { min-width: 0; overflow-wrap: anywhere; }
.ds-propiedades__valor input:not([type="checkbox"]) {
  width: 100%; max-width: 100%; min-height: var(--ds-app-prop-fila); padding: 0;
  border: 0; background: none; font: inherit; color: var(--ds-color-text); text-overflow: ellipsis;
}
.ds-propiedades__valor input::placeholder { color: var(--ds-color-text-tertiary); }
.ds-propiedades__valor input:focus-visible,
.ds-propiedades__valor select:focus-visible { outline: var(--ds-app-foco) solid var(--ds-app-foco-color); }
.ds-propiedades__valor input[type="checkbox"] {
  width: var(--ds-icon-size-sm); height: var(--ds-icon-size-sm); accent-color: var(--ds-color-info);
}
.ds-propiedades__etiqueta[title] { cursor: help; }
/* Un __ctrl que es enlace: en línea con su icono y sin subrayado. */
a.ds-propiedades__ctrl { display: inline-flex; align-items: center; gap: var(--ds-radius-chip); text-decoration: none; }

/* Tabla de base: la fila bajo el ratón, la celda en secundario; chips con margen;
   el título de fila hereda la medida de la celda. */
.ds-tabla--bd td { color: var(--ds-color-text-secondary); }
.ds-tabla--bd tbody tr:hover td { background: var(--ds-color-bg-tertiary); }
.ds-tabla--bd td .ds-chip--bd { margin: 0 var(--ds-radius-chip) var(--ds-app-linea) 0; }
:is(.ds-tabla, .ds-tabla--bd) .ds-fila__titulo { font-size: inherit; line-height: inherit; gap: 0; }
/* 1.15 (§ 6, P70): la celda de nombre (título + etiqueta) recupera su hueco
   de 8; el gap 0 de arriba la dejaba pegada («Archivado» en la Biblioteca). */
:is(.ds-tabla, .ds-tabla--bd) .ds-tabla__nombre .ds-fila__titulo { gap: var(--ds-spacing-2xs); }

/* Documento: migas sin subrayado ni negrita en la actual; título e icono editables. */
.ds-migas--doc a { text-decoration: none; }
.ds-migas--doc [aria-current] { font-weight: 400; }
.ds-doc__icono input {
  width: 100%; height: 100%; padding: 0; border: 0; border-radius: inherit;
  background: none; text-align: center; font-size: inherit; line-height: 1;
}
.ds-doc__icono input:focus-visible { outline: var(--ds-app-foco) solid var(--ds-app-foco-color); }
textarea.ds-doc__titulo {
  display: block; width: 100%; resize: none; overflow: hidden; field-sizing: content;
  padding: 0; margin: 0 0 var(--ds-spacing-2xs);
  font-family: inherit; font-size: var(--ds-doc-titulo); font-weight: var(--ds-doc-peso-titulo); line-height: 1.2;
  color: var(--ds-color-text); white-space: pre-wrap; overflow-wrap: anywhere;
}
/* El h1 (y su subtítulo) editables: el campo sin caja, con la tipografía del título. */
.ds-titulo__h1 input {
  min-width: 0; margin: 0; padding: 0; border: 0; border-radius: var(--ds-radius-card);
  background: none; font: inherit; letter-spacing: inherit; color: inherit;
}
.ds-titulo__sub input { width: 100%; font: inherit; color: inherit; }
/* Árbol: el icono de emoji mide lo que el de svg (caja lg, centrado). */
.ds-arbol__fila span.ds-arbol__icono {
  display: inline-grid; place-items: center; width: var(--ds-icon-size-lg); height: var(--ds-icon-size-lg);
  font-size: var(--ds-icon-size); line-height: 1;
}
.ds-pagina-hija__icono svg { width: var(--ds-icon-size-sm); height: var(--ds-icon-size-sm); color: var(--ds-color-text-tertiary); }
/* Con el dedo, la subpágina pide su diana de 44. */
@media (hover: none) { .ds-pagina-hija { min-height: var(--ds-spacing-touch); } }

/* ═══ 1.15.1 · Armazón y piezas nuevas (SPEC-CRM4-DS-2, PLAN-CRM-4 D5-D9) ═══ */
/* D5 de 1.15.1 (icono de identidad en coral) RETIRADO en 1.16 (CRM 5, P1, D1):
   con iconos de proyecto y documento en coral, el activo de la barra dejaba de
   distinguirse. El coral queda SOLO en el icono del ítem activo de la
   navegación (--ds-app-activo-icono); cualquier otro icono hereda su contexto
   (gris en la barra y el árbol, color de texto en listas y títulos). La clase
   no colorea nada y se borra en 1.17 (§ 0.12 c; regla 27 la cuenta en el HTML). */
/* obsoleta desde 1.16 → usar .ds-icono */
.ds-icono--marca { }

/* D9 (P39, P62; § 66): página de detalle con lateral. La columna principal
   nunca baja de 720; el lateral mide 320, va separado por una línea a toda
   la altura y se queda quieto al desplazar. Si 720 + 48 + 320 no caben (con
   la barra abierta, por debajo de ~1416), el lateral sube ENCIMA del
   principal (1.15.2, SPEC-CRM4-DS-2B: debajo, el teléfono y la etapa de un
   lead quedaban tras toda la actividad), a todo el ancho y sin línea: lo
   decide el ancho, no un breakpoint más (§ 33 solo admite 720/1056/1584).
   wrap-reverse apila las líneas de abajo arriba: la segunda (el lateral)
   queda encima; en una sola línea, flex-end = arriba. En un panel, como antes. */
@media (min-width: 1056px) {
  .ds-pagina--app:has(> .ds-detalle) { max-width: calc(var(--ds-spacing-lectura) + 4 * var(--ds-spacing-sm) + var(--ds-app-detalle-lateral)); }
  .ds-detalle:not(dialog *) {
    display: flex; flex-flow: row wrap-reverse; align-items: flex-end;
    gap: var(--ds-spacing-lg) calc(2 * var(--ds-spacing-sm)); max-width: none; overflow-x: clip;
  }
  .ds-detalle:not(dialog *) > .ds-detalle__principal { position: relative; flex: 1000 1 var(--ds-spacing-lectura); align-self: stretch; }
  /* La línea vive en el hueco (a 24 del principal y del lateral); cuando el
     lateral sube, queda fuera del bloque y overflow-x: clip la corta. */
  .ds-detalle:not(dialog *) > .ds-detalle__principal::after {
    content: ""; position: absolute; top: 0; bottom: 0;
    right: calc(-1 * var(--ds-spacing-sm) - var(--ds-app-linea));
    border-left: var(--ds-app-linea) solid var(--ds-color-border);
  }
  .ds-detalle:not(dialog *) > .ds-detalle__lateral { flex: 1 0 var(--ds-app-detalle-lateral); }
  /* Quieto SOLO al lado: encima, un sticky se quedaría pegado arriba y el
     principal pasaría por debajo. Sin consulta de contenedor (su contención
     haría del bloque la referencia de menús y tooltips fijos), el top sale
     del ancho de ventana: 16 si caben barra + relleno (2 × 24) + 1088 + 24
     de margen (la barra de desplazamiento cuenta en 100vw); si no, -100000
     (nunca se pega). Entre ~1416 y 1440 con la barra abierta, al lado y sin
     pegarse: se pierde el sticky, nunca se solapa. */
  .ds-pagina--app > .ds-detalle:not(dialog *) > .ds-detalle__lateral {
    position: sticky;
    top: calc(var(--ds-spacing-xs) + clamp(-100000px, (100vw - var(--ds-spacing-lectura) - 5 * var(--ds-spacing-sm) - var(--ds-app-detalle-lateral)) * 1000, 0px));
  }
  html:not([data-sidebar="plegada"]) body:has(> .ds-sidebar) .ds-pagina--app > .ds-detalle:not(dialog *) > .ds-detalle__lateral {
    top: calc(var(--ds-spacing-xs) + clamp(-100000px, (100vw - var(--ds-spacing-sidebar) - var(--ds-spacing-lectura) - 5 * var(--ds-spacing-sm) - var(--ds-app-detalle-lateral)) * 1000, 0px));
  }
}
/* Bajo 1056, una columna y el lateral debajo del título. */
@media (max-width: 1055px) { .ds-detalle:not(dialog *) > .ds-detalle__lateral { order: -1; } }
/* 1.22.0 (O2/P61): detalle INTERCALADO. Bajo 1056, con .ds-detalle--intercalado
   el lateral no sube arriba: se mete entre las secciones del principal. Se lee
   principal → lateral → lo marcado .ds-detalle__cola (Detalle → propiedades →
   Notas en una tarea). El principal se deshace (display: contents) y sus
   secciones pasan a ser hijas de la pila; el aire entre bloques sigue siendo
   el de la columna (32 = aire-seccion, § 0.16) y el separador de § 54 sigue
   saliendo (depende del DOM, no de la caja). A ≥ 1056, nada cambia; sin el
   modificador, nada cambia. */
@media (max-width: 1055px) {
  .ds-detalle--intercalado:not(dialog *) { gap: var(--ds-aire-seccion); }
  .ds-detalle--intercalado:not(dialog *) > .ds-detalle__principal { display: contents; }
  .ds-detalle--intercalado:not(dialog *) > .ds-detalle__lateral { order: 1; }
  /* El margen de cuerpo de unas propiedades (--ds-app-prop-cuerpo) se sumaba a los 32. */
  .ds-detalle--intercalado:not(dialog *) > .ds-detalle__lateral > :last-child { margin-bottom: 0; }
  .ds-detalle--intercalado:not(dialog *) > .ds-detalle__principal > .ds-detalle__cola { order: 2; }
}

/* D9 (P64; § 21): en móvil, si las acciones del título son solo botones de
   icono (Opciones, ⋯), van en la fila del h1, a la derecha. */
@media (max-width: 719px) {
  .ds-titulo:has(> .ds-titulo__acciones):not(:has(> .ds-titulo__acciones > :not(.ds-boton--icono, .ds-menu, [data-movil="cabecera"], [hidden]))) {
    display: grid; grid-template-columns: minmax(0, 1fr) auto; column-gap: var(--ds-spacing-2xs); row-gap: var(--ds-spacing-2xs); align-items: start;
  }
  .ds-titulo:has(> .ds-titulo__acciones):not(:has(> .ds-titulo__acciones > :not(.ds-boton--icono, .ds-menu, [data-movil="cabecera"], [hidden]))) > * { grid-column: 1 / -1; }
  .ds-titulo:has(> .ds-titulo__acciones):not(:has(> .ds-titulo__acciones > :not(.ds-boton--icono, .ds-menu, [data-movil="cabecera"], [hidden]))) > .ds-titulo__h1 { grid-column: 1; grid-row: 1; }
  .ds-titulo:has(> .ds-titulo__acciones):not(:has(> .ds-titulo__acciones > :not(.ds-boton--icono, .ds-menu, [data-movil="cabecera"], [hidden]))) > .ds-titulo__acciones { grid-column: 2; grid-row: 1; }
  .ds-titulo:has(> .ds-titulo__caption:first-child):has(> .ds-titulo__acciones):not(:has(> .ds-titulo__acciones > :not(.ds-boton--icono, .ds-menu, [data-movil="cabecera"], [hidden]))) > :is(.ds-titulo__h1, .ds-titulo__acciones) { grid-row: 2; }
}

/* D8 (P87; § 9-10): la hoja que abre el «+» se ve ENTERA en Safari de
   iPhone: el alto sale del viewport dinámico (--ds-app-hoja-alto en dvh) y,
   sin pie, el cuerpo deja la zona segura de abajo. */
.ds-hoja__cuerpo:last-child { padding-bottom: calc(var(--ds-spacing-xs) + env(safe-area-inset-bottom)); }

/* D7 (P65; § 62): bajo 720 el árbol de una página se toca con el dedo:
   filas de 44 con una línea entre ellas, sin caja redondeada. */
@media (max-width: 719px) {
  .ds-arbol .ds-arbol__fila { min-height: var(--ds-app-arbol-fila-movil); border-radius: 0; border-top: var(--ds-app-linea) solid var(--ds-color-border-subtle); }
  .ds-arbol > .ds-arbol__nodo:first-child > .ds-arbol__fila { border-top: 0; }
}

/* ═══ 73. Nota (.ds-nota, P60) ═══════════════════════════════════════ */
/* Nota con autor: avatar ARRIBA, en la fila del nombre y la fecha; el texto
   debajo, alineado con el nombre. 16 entre notas. La nota nueva: campo y,
   debajo a la derecha y a 8, su botón. */
.ds-notas { display: flex; flex-direction: column; gap: var(--ds-spacing-xs); margin: 0; padding: 0; list-style: none; }
.ds-nota {
  display: grid; grid-template-columns: auto minmax(0, 1fr); column-gap: var(--ds-app-paso-12); row-gap: var(--ds-app-paso-medio);
  align-items: start; margin: 0;
}
.ds-nota > .ds-avatar { grid-row: 1 / span 2; }
.ds-nota__cabecera { display: flex; flex-wrap: wrap; align-items: center; column-gap: var(--ds-spacing-2xs); min-height: var(--ds-app-avatar); min-width: 0; margin: 0; } /* 1.15.3: sin el margen del <p> (V-4) */
.ds-nota__autor { font-size: var(--ds-typography-size-ui-sm); line-height: var(--ds-typography-line-height-ui-sm); font-weight: 600; color: var(--ds-color-text); }
.ds-nota__fecha { font-size: var(--ds-typography-size-ui-xs); line-height: var(--ds-typography-line-height-ui-xs); color: var(--ds-color-text-tertiary); }
.ds-nota__texto {
  grid-column: 2; margin: 0; white-space: pre-wrap; overflow-wrap: anywhere;
  max-width: var(--ds-app-texto-max); /* 1.15.3 (V-4): a 720 daba ~97 caracteres por línea */
  font-size: var(--ds-typography-size-ui-sm); line-height: var(--ds-typography-line-height-ui-sm); color: var(--ds-color-text);
}
/* 1.19.0 (§ 0.16): campo → «Guardar nota» se queda en 8 (parte): el
   compositor es UNA pieza, como el comentario de Linear; no es un formulario
   con pie (24). */
.ds-notas__nueva { display: flex; flex-direction: column; gap: var(--ds-aire-parte); margin: 0; }
.ds-notas__nueva > .ds-boton { align-self: flex-end; }

/* ═══ 74. Recorrido (.ds-recorrido, P20, P74) ═════════════════════════ */
/* Paradas en orden unidas por una línea de puntos vertical (Maps). Cada
   parada: marcador · nombre · su etiqueta en una columna de ancho fijo (la
   etiqueta no baila con el largo del nombre). Pendiente = aro gris; hecha =
   punto gris lleno; la actual (aria-current="step") = punto en el coral de
   «aquí estás» (§ 0.7) y el nombre en 600. */
.ds-recorrido { display: flex; flex-direction: column; margin: 0; padding: 0; list-style: none; }
.ds-recorrido__parada {
  position: relative; display: grid; grid-template-columns: var(--ds-icon-size-lg) minmax(0, 1fr) var(--ds-app-recorrido-etiqueta);
  align-items: center; column-gap: var(--ds-app-paso-12); min-height: var(--ds-spacing-touch); margin: 0;
}
/* La línea: media por arriba (salvo la primera) y media por abajo (salvo la última). */
.ds-recorrido__parada::before, .ds-recorrido__parada::after {
  content: ""; position: absolute; left: calc(var(--ds-icon-size-lg) / 2 - var(--ds-app-linea));
  border-left: calc(2 * var(--ds-app-linea)) dotted var(--ds-color-border-strong);
}
.ds-recorrido__parada::before { top: 0; bottom: 50%; }
.ds-recorrido__parada::after { top: 50%; bottom: 0; }
.ds-recorrido__parada:first-child::before, .ds-recorrido__parada:last-child::after { content: none; }
.ds-recorrido__marcador {
  position: relative; z-index: 1; justify-self: center; box-sizing: border-box;
  width: var(--ds-app-paso-12); height: var(--ds-app-paso-12); border-radius: var(--ds-radius-full);
  border: calc(2 * var(--ds-app-linea)) solid var(--ds-color-text-tertiary); background: var(--ds-color-bg);
}
.ds-recorrido__parada--hecha .ds-recorrido__marcador { background: var(--ds-color-text-tertiary); }
.ds-recorrido__parada[aria-current] .ds-recorrido__marcador { border-color: var(--ds-app-activo-color); background: var(--ds-app-activo-color); }
.ds-recorrido__nombre {
  min-width: 0; overflow-wrap: anywhere; color: var(--ds-color-text); text-decoration: none;
  font-size: var(--ds-typography-size-ui-sm); line-height: var(--ds-typography-line-height-ui-sm); font-weight: 500;
}
.ds-recorrido__parada[aria-current] .ds-recorrido__nombre { font-weight: 600; }
.ds-recorrido__parada--hecha .ds-recorrido__nombre { color: var(--ds-color-text-secondary); }
.ds-recorrido__estado { justify-self: start; min-width: 0; }

/* ═══ 75. Contador (.ds-contador, P58) ════════════════════════════════ */
/* Número en círculo dentro de una pestaña, un segmentado o un ítem de la
   barra lateral: 20 de alto, 12/600, cifras tabulares. --error con algo
   dentro dice PRIORIDAD (el de Atrasados). Desde 1.15.3 es el ÚNICO número
   de un ítem: sustituye a .ds-segmentado__num, .ds-vista-barra__num y
   .ds-tablero__contador. */
.ds-contador {
  display: inline-flex; align-items: center; justify-content: center; flex: none; box-sizing: border-box;
  min-width: var(--ds-app-contador); height: var(--ds-app-contador); padding: 0 var(--ds-app-paso-medio);
  margin-inline-start: var(--ds-app-paso-medio); border-radius: var(--ds-radius-full);
  background: var(--ds-color-bg-quaternary); color: var(--ds-color-text);
  font-size: var(--ds-typography-size-ui-xs); line-height: 1; font-weight: 600; font-variant-numeric: tabular-nums;
}
.ds-contador--error { background: var(--ds-color-error-soft); }
.ds-contador[hidden] { display: none; }
.ds-sidebar__item > .ds-contador { margin-inline-start: auto; }

/* ═══ 76. Rejilla de huecos (.ds-huecos, CRM 5 · 05/10) ═══════════════ */
/* Elegir UN día o UNA hora entre los libres, viéndolos todos a la vez.
   Rejilla auto-fill de columna mínima 80 y 8 entre celdas (§ 0.16, parte).
   Celda de 44 de alto mínimo; valor 14/20/600 y cuenta 12/16 apilados a 4.
   Elegido = aria-current (sin clase): el anillo de 2 de .ds-tarjeta-radio
   elegida, igual en día y hora; el relleno sólido queda para el primario
   (§ 0.1). Lleno = span sin borde ni fondo, todo en terciario. Hoy no lleva
   modificador: el ancla es la palabra «Hoy». */
.ds-huecos {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(var(--ds-app-hueco-ancho), 1fr));
  gap: var(--ds-aire-parte); margin: 0; padding: 0;
}
.ds-hueco {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--ds-aire-pieza);
  box-sizing: border-box; min-width: 0; min-height: var(--ds-spacing-touch); padding: var(--ds-aire-parte);
  border: var(--ds-app-linea) solid var(--ds-color-border-subtle); border-radius: var(--ds-app-radio-control);
  background: var(--ds-color-bg-elevated); color: var(--ds-color-text); text-decoration: none; text-align: center;
  font-variant-numeric: tabular-nums;
}
.ds-hueco__valor {
  font-size: var(--ds-typography-size-ui-sm); line-height: var(--ds-typography-line-height-ui-sm);
  font-weight: 600; color: var(--ds-color-text); white-space: nowrap;
}
.ds-hueco__cuenta {
  font-size: var(--ds-typography-size-ui-xs); line-height: var(--ds-typography-line-height-ui-2xs);
  font-weight: 400; color: var(--ds-color-text-secondary); white-space: nowrap;
}
a.ds-hueco:hover { background: var(--ds-color-bg-tertiary); }
a.ds-hueco:focus-visible { outline: var(--ds-app-foco) solid var(--ds-app-foco-color); outline-offset: var(--ds-app-foco); }
/* Elegido: borde de 1 + sombra de 1 hacia fuera = anillo de 2 en el color de
   foco, como .ds-tarjeta-radio elegida. Si Yaz prefiere el relleno, es ESTA regla. */
.ds-hueco[aria-current="true"] {
  border-color: var(--ds-app-foco-color);
  box-shadow: 0 0 0 calc(var(--ds-app-foco) - var(--ds-app-linea)) var(--ds-app-foco-color);
}
.ds-hueco[aria-current="true"] .ds-hueco__cuenta { color: var(--ds-color-text); }
.ds-hueco--lleno { border-color: transparent; background: transparent; }
.ds-hueco--lleno :is(.ds-hueco__valor, .ds-hueco__cuenta) { color: var(--ds-color-text-tertiary); }

/* ═══ 77. Mando del kit (.ds-mando-kit, CRM 6 · KIT-2 · 07/10) ═════════
   Un botón redondo de 40 que flota arriba a la derecha, a 16 del borde y
   bajo la zona segura, sobre una pieza a pantalla completa (la demo, la
   cuenta, el informe), y abre el § 24 en modo selección con las piezas
   para saltar de una a otra. Siempre oscuro (--ds-app-mando-*). No es el
   .ds-fab (§ 10: crear, coral, 48, abajo). El menú no lo coloca app.js
   (el botón no lleva data-menu): cuelga del mando, a 8 bajo el botón y
   alineado a su derecha. */
.ds-mando-kit {
  position: fixed; z-index: var(--ds-app-capa-menu);
  top: calc(var(--ds-spacing-xs) + env(safe-area-inset-top));
  right: calc(var(--ds-spacing-xs) + env(safe-area-inset-right));
}
.ds-mando-kit__boton {
  display: flex; align-items: center; justify-content: center; box-sizing: border-box;
  width: var(--ds-spacing-control); height: var(--ds-spacing-control); margin: 0; padding: 0;
  border: var(--ds-app-linea) solid var(--ds-app-mando-borde); border-radius: var(--ds-radius-full);
  background: var(--ds-app-mando-fondo); color: var(--ds-app-mando-texto);
  box-shadow: var(--ds-shadow-float); opacity: var(--ds-app-mando-reposo); cursor: pointer;
  transition: opacity var(--ds-motion-duration-instant) var(--ds-motion-easing-musho);
}
.ds-mando-kit__boton:hover, .ds-mando-kit__boton:focus-visible, .ds-mando-kit__boton[aria-expanded="true"] { opacity: 1; }
.ds-mando-kit__boton:focus-visible { outline: var(--ds-app-foco) solid var(--ds-app-foco-color); outline-offset: var(--ds-app-foco); }
.ds-mando-kit__boton .ds-icono { width: var(--ds-icon-size); height: var(--ds-icon-size); }
.ds-mando-kit .ds-menu {
  position: absolute; top: calc(100% + var(--ds-spacing-2xs)); right: 0;
  background: var(--ds-app-mando-fondo); color: var(--ds-app-mando-texto); border-color: var(--ds-app-mando-borde);
}
.ds-mando-kit .ds-menu__item { color: var(--ds-app-mando-texto); }
.ds-mando-kit .ds-menu__item:hover, .ds-mando-kit .ds-menu__item:focus-visible { background: var(--ds-app-mando-hover); }
.ds-mando-kit .ds-menu--seleccion .ds-menu__item > svg:has(use[href$="#i-check"]) { color: var(--ds-app-mando-texto); }

/* Impresión: el cromo del DS no se imprime y la página ocupa el papel. */
@media print {
  .ds-sidebar, .ds-barra-inferior, .ds-fab, .ds-cabecera { display: none; }
  .ds-pagina { max-width: 100%; }
}
