/* ===========================================================================
   SOLID ERP — tema claro "premium" (estilo Tailwind/Shadcn) + sidebar + grid
   =========================================================================== */
:root {
  --background: 50 33% 98%;
  --foreground: 240 21% 15%;
  --card: 0 0% 100%;
  --card-foreground: 240 21% 15%;
  --popover: 50 33% 98%;
  --sidebar: 0 0% 100%;
  --sidebar-border: 240 5% 90%;
  --sidebar-primary: 208 88% 22%;
  --primary: 208 88% 22%;
  --primary-foreground: 0 0% 100%;
  --muted: 240 5% 90%;
  --muted-foreground: 240 6% 50%;
  --border: 240 5% 90%;
  --input: 240 5% 90%;
  --radius: .75rem;

  /* Acento por modo del chat (se sobreescribe con data-mode) */
  --accent: 208 88% 22%;
  --danger: 0 72% 51%;
  --mono: "JetBrains Mono", ui-monospace, monospace;

  /* Hoja de detalle en móvil (≤640px). El fondo va en este valor y la hoja en +1.
     Por encima del contenido normal y por debajo de los modales (500) y del sidebar
     móvil (1000): la hoja nunca tapa a un diálogo. */
  --z-hoja-detalle: 300;

  /* Fase 29 — Altura de los desplegables anclados. Uno solo para los tres (antes
     convivían 100, 100 y 200 sin motivo). Debe quedar por DEBAJO de los modales (500)
     y del sidebar móvil (1000): un desplegable nunca tapa a un diálogo.
     POR ENCIMA de la hoja de detalle (300/301) desde la corrección móvil de Ventas: el
     menú «Acciones» opera sobre el documento SELECCIONADO, y a ≤640px seleccionar es
     justo lo que abre la hoja. Con el valor antiguo (200) el menú se desplegaba DETRÁS
     de ella, es decir, en el único momento en que se usa. Que la hoja no tape a un
     desplegable abierto ya no depende del z-index: abrirPanelDetalles() llama a
     Anclaje.cerrarTodos() (app.js), que además suelta sus listeners. */
  --z-panel-anclado: 400;
}

/* ===========================================================================
   FASE 25 — TEMA OSCURO ("slate azulado"). Sobrescribe SOLO las variables HSL;
   como el resto del CSS usa hsl(var(--…)), todas las superficies se adaptan.
   Mayor especificidad que :root, así que gana sin !important.
   =========================================================================== */
:root[data-theme="dark"] {
  --background: 222 18% 11%;
  --foreground: 210 20% 92%;
  --card: 222 16% 15%;
  --card-foreground: 210 20% 92%;
  --popover: 222 16% 15%;
  --sidebar: 222 17% 13%;
  --sidebar-border: 222 12% 24%;
  --sidebar-primary: 208 90% 58%;
  --primary: 208 90% 58%;            /* azul aclarado: resalta sobre fondo oscuro */
  --primary-foreground: 0 0% 100%;
  --muted: 222 14% 22%;
  --muted-foreground: 215 15% 65%;
  --border: 222 12% 24%;
  --input: 222 12% 24%;
  --accent: 208 90% 58%;
  --danger: 0 75% 62%;
}
/* Superficies con color CLARO hardcodeado (no derivan de variables) que sí conviene
   oscurecer por ser muy visibles: el fondo global y los campos de texto (login/chat). */
:root[data-theme="dark"] body {
  background: linear-gradient(135deg, hsl(222 22% 8%), hsl(222 18% 12%));
}
:root[data-theme="dark"] .field input,
:root[data-theme="dark"] .field select,
:root[data-theme="dark"] :is(#input, [id^="input_"]) {
  background: hsl(var(--card));
}

* { box-sizing: border-box; margin: 0; padding: 0; }

html, body { overflow-x: hidden; }

body {
  font-family: "Montserrat", system-ui, sans-serif;
  color: hsl(var(--foreground));
  background: linear-gradient(135deg, #f8fafc, #e0e7ef);
  height: 100vh; height: 100dvh; overflow: hidden;
}

.screen { height: 100vh; height: 100dvh; }
/* `align-items: safe center` va DESPUÉS de place-items a propósito: cuando la
   tarjeta no cabe (móvil apaisado, teclado abierto, título envuelto a dos
   líneas), el centrado normal recorta por ARRIBA y con `body{overflow:hidden}`
   esa parte queda inalcanzable. Con `safe` el centrado degrada a "desde el
   principio" y el overflow-y permite desplazarse. Los navegadores que no
   entiendan `safe` descartan solo esa declaración y se quedan como antes. */
.centered {
  display: grid; place-items: center; padding: 24px;
  align-items: safe center; overflow-y: auto;
}

.logo-badge {
  width: 34px; height: 34px; border-radius: 10px; flex-shrink: 0;
  background: hsl(var(--primary)); color: hsl(var(--primary-foreground));
  display: grid; place-items: center; font-weight: 700; font-size: 1.1rem;
  box-shadow: 0 4px 14px hsl(var(--primary) / 0.35);
}

/* ----- Tarjetas login / empresa ------------------------------------------ */
.card {
  width: 100%; max-width: 390px;
  background: hsl(var(--card)); border: 1px solid hsl(var(--border));
  border-radius: 18px; padding: 34px 30px;
  box-shadow: 0 20px 50px rgba(30, 41, 59, 0.12);
  animation: rise .3s ease;
}
/* Marca y subtítulo CENTRADOS; el formulario NO. El centrado se pone en estos
   dos hermanos y nunca en .card, porque .field label, .field input/select y
   .form-error no declaran text-align y heredarían: el texto tecleado dentro de
   los campos saldría centrado. */
.brand-block {
  display: flex; align-items: center; justify-content: center;
  gap: 12px; margin-bottom: 6px;
}
.brand-block h1 {
  font-size: 1.35rem;
  font-weight: 700;
  letter-spacing: -0.02em;
  text-align: center;
  color: hsl(var(--muted-foreground));   /* ← NUEVA LÍNEA */
}
.brand-mayus {
  color: hsl(var(--primary));
}
.accent { color: hsl(var(--primary)); }
.card-sub {
  color: hsl(var(--muted-foreground)); font-size: .9rem; margin-bottom: 22px;
  text-align: center;
}

.field { margin-bottom: 16px; }
.field label {
  display: block; font-size: .76rem; color: hsl(var(--muted-foreground));
  margin-bottom: 6px; text-transform: uppercase; letter-spacing: .05em; font-weight: 600;
}
.field input, .field select {
  width: 100%; background: #fff; border: 1px solid hsl(var(--input));
  border-radius: 10px; color: hsl(var(--foreground));
  font-family: inherit; font-size: .95rem; padding: 12px 14px; outline: none;
  transition: border-color .2s, box-shadow .2s;
}
.field input:focus, .field select:focus {
  border-color: hsl(var(--primary)); box-shadow: 0 0 0 3px hsl(var(--primary) / 0.12);
}
.field select { cursor: pointer; }

.btn-primary {
  width: 100%; margin-top: 6px; background: hsl(var(--primary));
  color: hsl(var(--primary-foreground)); border: none; font-family: inherit;
  font-weight: 600; font-size: .95rem; padding: 13px; border-radius: 10px;
  cursor: pointer; transition: transform .1s, opacity .2s, box-shadow .2s;
  box-shadow: 0 6px 18px hsl(var(--primary) / 0.25);
}
.btn-primary:hover { transform: translateY(-1px); }
.btn-primary:disabled { opacity: .5; cursor: not-allowed; transform: none; }
.btn-link {
  width: 100%; margin-top: 12px; background: none; border: none;
  color: hsl(var(--muted-foreground)); font-size: .82rem; cursor: pointer; text-decoration: underline;
}
.btn-link:hover { color: hsl(var(--primary)); }
.form-error { color: hsl(var(--danger)); font-size: .84rem; min-height: 18px; margin-bottom: 4px; }
/* Las barras de error de acceso arrancan ocultas; app.js las muestra solo ante un error. */
#login-error, #empresa-error { display: none; }

@keyframes rise { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; } }

/* ----- Shell: sidebar + main --------------------------------------------- */
.shell { display: flex; height: 100vh; height: 100dvh; }

.sidebar {
  width: 240px; flex-shrink: 0; background: hsl(var(--sidebar));
  border-right: 1px solid hsl(var(--sidebar-border));
  display: flex; flex-direction: column; padding: 18px 14px; gap: 8px;
  z-index: 40; box-shadow: 1px 0 8px rgba(30,41,59,.04);
  height: 100vh; height: 100dvh;   /* altura acotada explícita para el scroll interno */
  min-height: 0; overflow-y: auto; overflow-x: hidden; overscroll-behavior: contain;
}
.sidebar-brand {
  display: flex; align-items: center; gap: 10px; font-size: 1.05rem;
  padding: 6px 8px 16px; border-bottom: 1px solid hsl(var(--sidebar-border)); margin-bottom: 8px;
}
.nav-group { display: flex; flex-direction: column; gap: 2px; }
.nav-label {
  font-size: .68rem; text-transform: uppercase; letter-spacing: .08em;
  color: hsl(var(--muted-foreground)); font-weight: 600; padding: 10px 10px 4px;
}
.nav-item {
  display: flex; align-items: center; gap: 11px; width: 100%; text-align: left;
  background: transparent; border: none; cursor: pointer; font-family: inherit;
  font-size: .92rem; color: hsl(var(--foreground)); padding: 10px 12px;
  border-radius: 10px; transition: background .15s, color .15s;
  min-height: 44px;
}
.nav-item span { font-size: 1.05rem; }
.nav-item:hover { background: hsl(var(--muted) / 0.6); }
.nav-item.is-active { background: hsl(var(--primary)); color: hsl(var(--primary-foreground)); font-weight: 600; }
.nav-ai { border: 1px dashed hsl(var(--sidebar-border)); }
.nav-ai.is-active { border-color: transparent; }

/* Carpetas desplegables (acordeón) */
.nav-folder { display: flex; flex-direction: column; }
.nav-folder-toggle .chev {
  margin-left: auto; font-size: .8rem; color: hsl(var(--muted-foreground));
  transition: transform .2s;
}
/* Con «>» y no con descendiente: Impuestos lleva una carpeta anidada (Auxiliares)
   y, si no, abrir la de fuera abriría y giraría también la de dentro. */
.nav-folder.open > .nav-folder-toggle .chev { transform: rotate(90deg); }
.nav-sub { display: none; flex-direction: column; gap: 2px; margin: 2px 0 4px; }
.nav-folder.open > .nav-sub { display: flex; }
.nav-subitem { padding-left: 40px; font-size: .88rem; color: hsl(var(--muted-foreground)); }
/* Tercer nivel (Impuestos → Auxiliares → …): un escalón más de sangría. */
.nav-sub .nav-sub .nav-subitem { padding-left: 56px; }
.nav-subitem:hover { color: hsl(var(--foreground)); }
.nav-subitem.is-active { color: hsl(var(--primary-foreground)); }
.sidebar-foot { margin-top: auto; display: flex; flex-direction: column; gap: 2px;
  padding-top: 12px; border-top: 1px solid hsl(var(--sidebar-border)); }
.link-btn {
  background: none; border: none; text-align: left; cursor: pointer; font-family: inherit;
  font-size: .82rem; color: hsl(var(--muted-foreground)); padding: 8px 12px; border-radius: 8px;
  transition: color .15s, background .15s;
  min-height: 44px; display: flex; align-items: center;
}
.link-btn:hover { color: hsl(var(--primary)); background: hsl(var(--muted) / 0.5); }
.sidebar-backdrop { display: none; }

.main-area { flex: 1; display: flex; flex-direction: column; min-width: 0; }

/* ----- Topbar ------------------------------------------------------------ */
.topbar {
  display: flex; align-items: center; gap: 14px;
  padding: 14px 22px; border-bottom: 1px solid hsl(var(--border)); background: hsl(var(--card));
  position: relative; z-index: 50; flex-shrink: 0;
  box-shadow: 0 1px 8px rgba(30,41,59,.08);
}
.topbar-title { flex: 1; min-width: 0; }
.topbar-title h2, .topbar-title .sub {
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.topbar-title h2 { font-size: 1.05rem; font-weight: 700; letter-spacing: -0.01em; }
.sub { color: hsl(var(--muted-foreground)); font-size: .78rem; }
/* Fase 22 — indicador discreto de la sesión nativa ("Sesión: <n> - <fecha>"). */
.session-badge {
  display: block; font-family: var(--mono); font-size: .68rem;
  color: hsl(var(--muted-foreground)); opacity: .6; letter-spacing: .02em;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.session-badge:empty { display: none; }   /* no ocupa espacio si aún no hay sesión */
.status {
  font-family: var(--mono); font-size: .72rem; color: hsl(var(--primary));
  border: 1px solid hsl(var(--border)); padding: 5px 12px; border-radius: 999px;
  background: hsl(var(--background)); white-space: nowrap;
}
/* [hidden] SIEMPRE oculta, pase lo que pase.
   El display:none de [hidden] lo pone la hoja del USER-AGENT, así que cualquier
   regla de autor con `display` propio le gana —da igual la especificidad— y el
   elemento se queda a la vista con el atributo puesto. Ya ha pasado tres veces:
   .status, .fac-medidas y el <label> .art-campo de los múltiplos, que trae
   display:flex y por eso «Múltiplo 4» seguía viéndose con 3 unidades extendidas
   aunque el JS lo marcara. Cada una se había parcheado por su cuenta; esta regla
   lo corta de raíz, y `el.hidden = true` oculta y punto.
   Las dos puntuales (.status aquí abajo y .fac-medidas) quedan redundantes: se
   dejan estar, porque documentan el porqué en su sitio. */
[hidden] { display: none !important; }

/* En reposo el aviso va vacío y con [hidden]: sin esta regla, cualquier `display`
   futuro sobre .status ganaría al display:none del user-agent. */
.status[hidden] { display: none; }

/* Marca de la EMPRESA ACTIVA en la topbar: ocupa el sitio donde antes ponía
   "Listo". El logo llega ya reducido desde /api/empresas/{empresa}/logo. */
/* flex-shrink: 0 — sin él la marca se encoge cuando la barra va justa (tiene
   min-width: 0) y el logo daría saltos al estrechar la ventana. Quien debe ceder
   el ancho es .topbar-title, que ya tiene flex:1 y recorte con puntos suspensivos. */
.topbar-empresa { display: flex; align-items: center; gap: 8px; min-width: 0; flex-shrink: 0; }
.topbar-empresa:empty { display: none; }
/* 40px es el techo seguro: es justo el alto del menú de usuario, que está
   siempre presente, así que la barra NO crece ni cuando el distintivo de sesión
   va vacío. width:auto + object-fit:contain conservan la proporción original
   (los logos reales van de 48x48 a 118x48) y no deforman ninguno. */
.topbar-logo {
  height: 70px; width: auto; max-width: 270px;
  object-fit: contain; display: block;
}
/* Rótulo del fallback cuando la empresa no tiene imagen: se acompaña del
   .logo-badge, la misma insignia que usan el login y el sidebar. */
.topbar-empresa-nombre {
  font-size: .8rem; font-weight: 600; color: hsl(var(--foreground));
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 150px;
}
.hamburger {
  display: none; flex-direction: column; align-items: center; justify-content: center; gap: 4px;
  background: hsl(var(--muted) / 0.5); border: 1px solid hsl(var(--border));
  border-radius: 8px; cursor: pointer; padding: 11px 10px; flex-shrink: 0;
  min-width: 44px; min-height: 44px;
}
.hamburger span { width: 20px; height: 2px; background: hsl(var(--foreground)); border-radius: 2px; }

/* ===========================================================================
   FASE 25 — Botón de tema + menú de usuario de la topbar
   =========================================================================== */
.icon-btn {
  flex-shrink: 0; display: inline-flex; align-items: center; justify-content: center;
  width: 38px; height: 38px; font-size: 1.05rem; line-height: 1; cursor: pointer;
  background: hsl(var(--muted) / 0.5); border: 1px solid hsl(var(--border));
  border-radius: 10px; color: hsl(var(--foreground)); transition: background .15s, border-color .15s;
}
.icon-btn:hover { background: hsl(var(--muted)); border-color: hsl(var(--primary) / 0.4); }

.user-menu { flex-shrink: 0; }
.user-menu-toggle {
  display: inline-flex; align-items: center; gap: 8px; cursor: pointer;
  background: hsl(var(--muted) / 0.5); border: 1px solid hsl(var(--border));
  border-radius: 999px; padding: 4px 12px 4px 4px; color: hsl(var(--foreground));
  font-family: inherit; font-size: .85rem; transition: background .15s, border-color .15s;
}
.user-menu-toggle:hover { background: hsl(var(--muted)); border-color: hsl(var(--primary) / 0.4); }
.user-avatar {
  display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; border-radius: 50%; flex-shrink: 0;
  background: hsl(var(--primary)); color: hsl(var(--primary-foreground));
  font-size: .74rem; font-weight: 700; letter-spacing: .02em; text-transform: uppercase;
}
.user-name {
  max-width: 150px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-weight: 600;
}
.user-caret { font-size: .7rem; color: hsl(var(--muted-foreground)); }

/* La posición la pone anclaje.js (.panel-anclado): aquí solo el aspecto. */
.user-dropdown-menu {
  min-width: 210px; padding: 6px; display: flex; flex-direction: column; gap: 2px;
  background: hsl(var(--card)); border: 1px solid hsl(var(--border));
  border-radius: 12px; box-shadow: 0 12px 32px rgba(0, 0, 0, 0.22);
}
.user-dropdown-item {
  display: flex; align-items: center; gap: 10px; width: 100%; text-align: left;
  padding: 10px 12px; border: none; border-radius: 8px; cursor: pointer;
  background: transparent; color: hsl(var(--foreground)); font-family: inherit; font-size: .9rem;
}
.user-dropdown-item:hover { background: hsl(var(--muted) / 0.7); }

/* Vista de perfil (Fase 25) */
.perfil-wrap { padding: 28px 32px; overflow-y: auto; }
.perfil-wrap h2 { font-size: 1.3rem; font-weight: 700; margin-bottom: 6px; color: hsl(var(--foreground)); }
.perfil-intro { color: hsl(var(--muted-foreground)); margin-bottom: 20px; }
.perfil-datos {
  display: grid; grid-template-columns: max-content 1fr; gap: 10px 24px;
  max-width: 640px; background: hsl(var(--card)); border: 1px solid hsl(var(--border));
  border-radius: var(--radius); padding: 20px 24px;
}
.perfil-datos dt { font-weight: 600; color: hsl(var(--muted-foreground)); }
.perfil-datos dd { color: hsl(var(--foreground)); font-family: var(--mono); font-size: .88rem; }

.view { flex: 1; display: flex; flex-direction: column; min-height: 0; min-width: 0; }

/* ----- Dashboard de Dirección --------------------------------------------- */
/* Aviso del Dashboard (error de carga o «sin movimientos»). Mismo padding lateral
   que .dashboard-grid para que quede alineado con las tarjetas. */
.dashboard-aviso {
  margin: 22px 22px 0;
  padding: 12px 14px;
  border-radius: 8px;
  font-size: .9rem;
  border: 1px solid hsl(var(--border));
  background: hsl(var(--muted) / 0.5);
  color: hsl(var(--muted-foreground));
}
.dashboard-aviso--error {
  border-color: hsl(var(--danger) / 0.4);
  background: hsl(var(--danger) / 0.08);
  color: hsl(var(--danger));
}

.dashboard-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 18px;
  padding: 22px;
  overflow-y: auto;
}

.dashboard-card {
  background: hsl(var(--card));
  border: 1px solid hsl(var(--border));
  border-radius: var(--radius);
  box-shadow: 0 2px 12px rgba(30, 41, 59, 0.06);
  padding: 16px 18px;
  display: flex;
  flex-direction: column;
  /* 280 → 300: el rótulo de totales ocupa una línea y no debe pagarla el canvas. */
  min-height: 300px;
}

.dashboard-card h3 {
  margin: 0 0 6px;
  font-size: .95rem;
  font-weight: 600;
  color: hsl(var(--foreground));
}

.dashboard-card canvas {
  flex: 1;
  min-height: 0;
}

/* ----- Totales anuales de cada gráfico -----------------------------------
   Van en el FLUJO (una línea entre el título y el canvas), no superpuestos: así
   no tapan el gráfico ni dependen del tamaño de la tarjeta. `flex-wrap` deja que
   «Anterior» baje sola cuando la tarjeta se estrecha, en vez de comprimirse.
   Los valores los escribe pintarTotales() ya abreviados (180k, 1,25M). ------- */
.dashboard-totales {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 14px;
  margin: 0 0 10px;
  font-size: .8rem;
  line-height: 1.3;
}
.dash-total { display: inline-flex; align-items: baseline; gap: 5px; cursor: default; }
/* El punto repite el color de la serie en el gráfico: es lo que identifica qué
   cifra es cuál sin necesidad de más texto. El color llega en --dash-color desde
   JS (COLOR_ACTUAL / COLOR_ANTERIOR), fuente única para gráfico y rótulo. */
.dash-punto {
  width: 8px; height: 8px; border-radius: 50%;
  background: hsl(var(--dash-color));
  flex-shrink: 0;
  align-self: center;
}
/* Sin relleno donde esa serie NO está dibujada (Beneficio, que solo pinta el año
   actual): el dato se ofrece igual, pero no se finge una línea que no existe. */
.dash-punto.hueco {
  background: transparent;
  box-shadow: inset 0 0 0 1.5px hsl(var(--dash-color));
}
.dash-total-etq { color: hsl(var(--muted-foreground)); }
.dash-total-val {
  font-weight: 700;
  color: hsl(var(--foreground));
  font-variant-numeric: tabular-nums;
}

@media (max-width: 1100px) {
  .dashboard-grid { grid-template-columns: repeat(2, 1fr); }
}

@media (max-width: 640px) {
  .dashboard-grid { grid-template-columns: 1fr; padding: 14px; }
  .dashboard-aviso { margin: 14px 14px 0; }   /* alineado con el padding del grid */
  .dashboard-totales { font-size: .75rem; gap: 4px 10px; }
}

/* ----- Modo dual --------------------------------------------------------- */
#app-root[data-mode="asesor"]      { --accent: 208 88% 22%; }
#app-root[data-mode="operaciones"] { --accent: 160 70% 32%; }
#app-root[data-mode="consultas"]   { --accent: 262 60% 40%; }

.modebar {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  padding: 10px 22px; border-bottom: 1px solid hsl(var(--border)); background: hsl(var(--card));
}
.mode-switch { display: inline-flex; background: hsl(var(--muted) / 0.5);
  border: 1px solid hsl(var(--border)); border-radius: 999px; padding: 3px; }
.mode-tab {
  background: transparent; border: none; color: hsl(var(--muted-foreground));
  font-family: inherit; font-size: .84rem; font-weight: 500; padding: 9px 16px;
  border-radius: 999px; cursor: pointer; transition: all .18s;
}
.mode-tab:hover { color: hsl(var(--foreground)); }
.mode-tab.is-active { background: hsl(var(--accent)); color: #fff; font-weight: 600; }
.mode-hint { color: hsl(var(--muted-foreground)); font-size: .8rem; }

/* ----- Chat -------------------------------------------------------------- */
.chat { flex: 1; overflow-y: auto; padding: 24px 22px; display: flex; flex-direction: column; gap: 18px; }
.welcome { margin: auto; text-align: center; max-width: 540px; }
.welcome h2 { font-size: 1.7rem; margin-bottom: 10px; letter-spacing: -0.02em; }
.welcome p { color: hsl(var(--muted-foreground)); margin-bottom: 22px; }
.chips { display: flex; flex-wrap: wrap; gap: 10px; justify-content: center; }
.chip {
  background: hsl(var(--card)); border: 1px solid hsl(var(--border)); color: hsl(var(--foreground));
  padding: 9px 15px; border-radius: 999px; font-size: .85rem; cursor: pointer;
  transition: border-color .2s, transform .1s; font-family: inherit;
}
.chip:hover { border-color: hsl(var(--accent)); transform: translateY(-1px); }

.msg { display: flex; max-width: 100%; animation: rise .25s ease; }
.msg.user { justify-content: flex-end; }
.msg.user .bubble {
  background: hsl(var(--accent)); color: #fff;
  border-radius: var(--radius) var(--radius) 4px var(--radius);
  max-width: 75%; padding: 12px 16px; white-space: pre-wrap;
}
.msg.ai { justify-content: flex-start; }
.msg.ai .bubble {
  background: hsl(var(--card)); border: 1px solid hsl(var(--border));
  border-radius: var(--radius) var(--radius) var(--radius) 4px;
  max-width: 100%; width: 100%; padding: 16px 18px;
  box-shadow: 0 2px 10px rgba(30, 41, 59, 0.05);
}
.bubble { line-height: 1.5; word-break: break-word; overflow-wrap: anywhere; }
.answer { font-size: 1.02rem; line-height: 1.55; margin-bottom: 14px; white-space: pre-wrap;
  overflow-wrap: anywhere; }

.files-tag { font-family: var(--mono); font-size: .72rem; color: hsl(var(--muted-foreground));
  margin-bottom: 10px; display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.files-tag .pill { background: hsl(var(--muted) / 0.6); border: 1px solid hsl(var(--border));
  padding: 2px 8px; border-radius: 6px; color: hsl(var(--primary)); }

.sql-block { position: relative; margin: 4px 0 14px; }
.sql-block > summary { cursor: pointer; list-style: none; font-family: var(--mono);
  font-size: .72rem; text-transform: uppercase; letter-spacing: .06em;
  color: hsl(var(--muted-foreground)); padding: 6px 0; }
.sql-block > summary::-webkit-details-marker { display: none; }
.sql-block > summary::before { content: "▸ "; color: hsl(var(--accent)); }
.sql-block[open] > summary::before { content: "▾ "; }
.sql-block pre {
  background: #0f172a; border-radius: 10px; padding: 14px 16px; overflow-x: auto;
  font-family: var(--mono); font-size: .82rem; color: #c7e6e2; border-left: 3px solid hsl(var(--accent));
}
.copy-btn { position: absolute; top: 30px; right: 10px; background: #1e293b; border: 1px solid #334155;
  color: #94a3b8; font-size: .7rem; padding: 4px 9px; border-radius: 6px; cursor: pointer; z-index: 2; }
.copy-btn:hover { color: #fff; }

.table-wrap { border: 1px solid hsl(var(--border)); border-radius: 10px; overflow: auto; max-height: 420px; }
table { border-collapse: collapse; width: 100%; font-size: .82rem; background: hsl(var(--card)); }
thead th { position: sticky; top: 0; background: hsl(var(--primary)); color: #fff;
  font-family: var(--mono); font-weight: 500; text-align: left; padding: 10px 14px;
  white-space: nowrap; }
tbody td { padding: 9px 14px; border-bottom: 1px solid hsl(var(--border)); white-space: nowrap; }
tbody tr:nth-child(even) { background: hsl(var(--muted) / 0.3); }
tbody tr:hover { background: hsl(var(--primary) / 0.07); }
td.null { color: hsl(var(--muted-foreground)); font-style: italic; }

.meta { font-size: .76rem; color: hsl(var(--muted-foreground)); margin-top: 10px; }
.error { border-left: 3px solid hsl(var(--danger)); background: hsl(var(--danger) / 0.08);
  padding: 12px 14px; border-radius: 8px; color: hsl(var(--danger)); font-size: .88rem; white-space: pre-wrap; }

.thinking { display: flex; align-items: center; gap: 8px; color: hsl(var(--muted-foreground)); }
.thinking .ball { width: 7px; height: 7px; border-radius: 50%; background: hsl(var(--accent)); animation: blink 1.2s infinite ease-in-out; }
.thinking .ball:nth-child(2) { animation-delay: .2s; }
.thinking .ball:nth-child(3) { animation-delay: .4s; }
@keyframes blink { 0%, 80%, 100% { opacity: .25; } 40% { opacity: 1; } }

.composer { display: flex; gap: 10px; align-items: flex-end; padding: 16px 22px 20px;
  border-top: 1px solid hsl(var(--border)); background: hsl(var(--card)); }
:is(#input, [id^="input_"]) { flex: 1; resize: none; background: #fff; border: 1px solid hsl(var(--input));
  border-radius: 12px; color: hsl(var(--foreground)); font-family: inherit; font-size: .95rem;
  padding: 13px 16px; max-height: 160px; line-height: 1.4; outline: none; transition: border-color .2s; }
:is(#input, [id^="input_"]):focus { border-color: hsl(var(--accent)); }
:is(#input, [id^="input_"]):disabled { opacity: .6; cursor: not-allowed; background: hsl(var(--muted) / 0.4); }
:is(#send, [id^="send_"]) { background: hsl(var(--accent)); color: #fff; border: none; width: 46px; height: 46px;
  border-radius: 12px; cursor: pointer; display: grid; place-items: center; flex-shrink: 0;
  transition: transform .1s, opacity .2s; }
:is(#send, [id^="send_"]):hover { transform: translateY(-1px); }
:is(#send, [id^="send_"]):disabled { opacity: .4; cursor: not-allowed; transform: none; }
/* Dictado por voz: mismo tamaño que «Enviar», fondo neutro; acento mientras escucha. */
:is(#mic, [id^="mic_"]) { background: hsl(var(--muted)); color: hsl(var(--foreground));
  border: 1px solid hsl(var(--input)); width: 46px; height: 46px; border-radius: 12px;
  cursor: pointer; display: grid; place-items: center; flex-shrink: 0; font-size: 1.15rem;
  transition: transform .1s, opacity .2s, background .2s; }
:is(#mic, [id^="mic_"])[hidden] { display: none; }
:is(#mic, [id^="mic_"]):hover { transform: translateY(-1px); }
:is(#mic, [id^="mic_"]):disabled { opacity: .4; cursor: not-allowed; transform: none; }
:is(#mic, [id^="mic_"]).is-listening { background: hsl(var(--accent)); color: #fff;
  border-color: transparent; animation: mic-pulso 1.2s ease-in-out infinite; }
@keyframes mic-pulso { 0%, 100% { box-shadow: 0 0 0 0 hsl(var(--accent) / .5); }
  50% { box-shadow: 0 0 0 6px hsl(var(--accent) / 0); } }

/* ----- Vista de datos directos (grid) ------------------------------------ */
.data-head { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; padding: 14px 22px 10px; }
.data-head h3 { font-size: 1.15rem; font-weight: 700; }
.data-count { font-size: .8rem; color: hsl(var(--muted-foreground)); }
.data-scroll {
  flex: 1; min-height: 0; min-width: 0;
  display: flex; flex-direction: column;
  padding: 0 22px 22px;
}
.data-placeholder, .data-loading { color: hsl(var(--muted-foreground)); padding: 40px; text-align: center; }
.data-table-wrap {
  flex: 1; min-height: 0; max-width: 100%;
  overflow: auto; -webkit-overflow-scrolling: touch;
  border: 1px solid hsl(var(--border)); border-radius: var(--radius);
  background: hsl(var(--card)); box-shadow: 0 2px 12px rgba(30, 41, 59, 0.06);
}
.data-table { border-collapse: collapse; width: 100%; font-size: .85rem; }
.data-table thead th { position: sticky; top: 0; background: hsl(var(--primary)); color: #fff;
  text-align: left; padding: 11px 16px; font-weight: 600; white-space: nowrap; }
.data-table tbody td { padding: 10px 16px; border-top: 1px solid hsl(var(--border)); white-space: nowrap; }
.data-table tbody tr:nth-child(even) { background: hsl(var(--muted) / 0.3); }
/* El :hover va dentro de @media (hover: hover) por el mismo motivo que en la ficha
   (ver el comentario largo junto a .fac-btn): en táctil el navegador DEJA el :hover
   aplicado tras soltar el dedo, hasta que se toca en otro sitio. Aquí no era solo
   estético: quedaba una fila «encendida» compitiendo con la fila realmente
   seleccionada, y el usuario no sabía sobre cuál iba a actuar «Acciones». */
@media (hover: hover) {
  .data-table tbody tr:hover { background: hsl(var(--primary) / 0.07); }
}

/* ----- Maestro-Detalle: dos paneles apilados ----------------------------- */
.data-panel { flex: 1; min-height: 0; display: flex; flex-direction: column; }
/* openTab() sufija los id al clonar la plantilla (grid-detalles_data_facturas_venta), así
   que el selector por id pelado NUNCA llegaba a aplicarse y el panel se quedaba sin su
   separador. Misma convención :is(#x, [id^="x_"]) que ya se usa más arriba en el fichero. */
:is(#grid-detalles, [id^="grid-detalles_"]) { border-top: 2px solid hsl(var(--border)); }
.data-table tbody tr.fila-doc { cursor: pointer; }
.data-table tbody tr.fila-sel,
.data-table tbody tr.fila-sel:hover {
  background: hsl(var(--primary) / 0.16);
  box-shadow: inset 3px 0 0 hsl(var(--primary));
}
/* El empuje hacia la derecha lo da el contador, no el botón de cerrar. Antes lo hacía
   `margin-left:auto` en .detalle-close, y funcionaba mientras era el único elemento
   flexible; con «Acciones» ahora SIEMPRE en esta cabecera habría dos `auto` y el espacio
   libre se repartiría entre los dos, dejando el menú flotando en mitad de la fila. Con
   el `margin-right:auto` en el contador, todo lo que venga después queda agrupado a la
   derecha en el orden del HTML: [título] [nº líneas] ······ [Acciones ▾] [✕ Cerrar]. */
:is(#grid-detalles, [id^="grid-detalles_"]) .data-count { margin-right: auto; }
.detalle-close { width: auto; margin: 0 0 0 8px; text-decoration: none; }
@media (hover: hover) {
  .detalle-close:hover { background: hsl(var(--muted) / 0.5); }
}
.detalle-close:active { background: hsl(var(--muted) / 0.8); }
/* El ✕ y la palabra «Cerrar» son dos spans para poder dejar solo el ✕ en móvil. En
   escritorio se leen juntos, exactamente como antes. */
.detalle-close-ico { margin-right: 4px; }
/* Fondo de la hoja de detalle: inerte en escritorio, solo se activa en el bloque de
   ≤640px (espeja a .sidebar-backdrop, que sigue el mismo esquema). */
.detalle-backdrop { display: none; }

/* ----- Ficha de cabecera del documento activo ----------------------------
   Datos generales (fecha, cliente/proveedor, NIF, bases, IVA, total) del MISMO
   documento cuyas líneas se ven debajo: los dos salen de una sola respuesta de
   /api/documentos/{tipo}/{rig}/ficha. `flex: 0 0 auto` para que no le robe alto al
   scroll de las líneas, ni aquí ni en la hoja de móvil. Los campos que la vista no
   expone se omiten en JS, así que la rejilla se adapta sola a ventas y a compras. */
.doc-cabecera {
  flex: 0 0 auto;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
  gap: 10px 20px;
  padding: 12px 16px;
  border-bottom: 1px solid hsl(var(--border));
  background: hsl(var(--muted) / 0.3);
}
.doc-cab-campo { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
/* Título y dirección son textos largos: ocupan toda la fila cuando hay sitio. */
.doc-cab-campo.ancho { grid-column: 1 / -1; }
.doc-cab-campo.num { text-align: right; }
.doc-cab-etq {
  font-size: 0.72rem; font-weight: 600; letter-spacing: .03em;
  text-transform: uppercase; color: hsl(var(--muted-foreground));
}
/* Los valores SÍ se parten (a diferencia de las celdas de tabla): un nombre comercial
   o una dirección larga no deben desbordar la ficha ni forzar scroll horizontal. */
.doc-cab-val {
  font-size: 0.92rem; color: hsl(var(--foreground));
  overflow-wrap: anywhere;
}
.doc-cab-campo.num .doc-cab-val { font-variant-numeric: tabular-nums; }
.doc-cab-campo.destacado .doc-cab-val {
  font-size: 1.05rem; font-weight: 700; color: hsl(var(--primary));
}

::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-thumb { background: hsl(var(--muted)); border-radius: 6px; }
::-webkit-scrollbar-thumb:hover { background: hsl(var(--muted-foreground) / 0.5); }

/* ----- Responsive / Mobile-first -----------------------------------------
   BREAKPOINTS CANÓNICOS del proyecto (usar solo estos, de mayor a menor):
     1100px = desktop pequeño   ·   820px = tablet
      640px = móvil grande      ·   480px = móvil
   El visor de documentos (.docs-view) usa el mismo canon (640) desde M1.
   -------------------------------------------------------------------------- */

/* Hamburger span — base para animación */
.hamburger span { transition: transform .25s ease, opacity .2s; }

@media (max-width: 820px) {
  .hamburger { display: flex; }
  .sidebar {
    position: fixed; top: 0; left: 0; height: 100vh; height: 100dvh;
    width: min(240px, 84vw);
    overflow-y: auto; overflow-x: hidden; overscroll-behavior: contain;
    transform: translateX(-100%);
    transition: transform .25s ease; box-shadow: 0 0 40px rgba(0, 0, 0, 0.35);
    z-index: 1000;
  }
  .shell.sidebar-open .sidebar { transform: translateX(0); }
  .sidebar-backdrop { display: none; }
  .shell.sidebar-open .sidebar-backdrop {
    display: block; position: fixed; inset: 0;
    background: rgba(15, 23, 42, 0.45); z-index: 999;
  }

  /* Hamburger → X cuando el sidebar está abierto */
  .shell.sidebar-open .hamburger span:nth-child(1) { transform: translateY(6px) rotate(45deg); }
  .shell.sidebar-open .hamburger span:nth-child(2) { opacity: 0; transform: scaleX(0); }
  .shell.sidebar-open .hamburger span:nth-child(3) { transform: translateY(-6px) rotate(-45deg); }

  .msg.user .bubble { max-width: 88%; }
  .chat, .composer, .data-scroll { padding-left: 14px; padding-right: 14px; }
  .composer { padding-bottom: calc(20px + env(safe-area-inset-bottom)); }
  .topbar { padding: 12px 16px; }
  .modebar { padding: 10px 16px; }
  /* En móvil la topbar va justa: el logo se encoge y el nombre del fallback
     desaparece, dejando solo la insignia con la inicial de la empresa.
     30px queda holgadamente por debajo del techo de aquí (la hamburguesa mide
     44px), así que tampoco cambia la altura de la barra. */
  .topbar-logo { height: 30px; width: auto; max-width: 110px; }
  .topbar-empresa-nombre { display: none; }
}

@media (max-width: 640px) {
  .tab { min-width: 120px; max-width: 160px; padding: 8px 10px; font-size: 0.8rem; }
  .data-head { padding: 12px 14px 8px; gap: 8px; }
  .data-head h3 { font-size: 1rem; }
  .table-controls { width: 100%; }
  /* El reparto de ancho de la barra va en el bloque del final: `.table-search` tiene
     `max-width: 320px` en una regla posterior a ésta, que ganaría por orden. */
  .table-search { min-width: 0; flex: 1; }
}

/* M3 — Evita el zoom automático de iOS al enfocar: los controles de formulario
   deben tener font-size >= 16px. Afecta SOLO a input/select/textarea (no toca
   botones, pestañas, etiquetas ni elementos visuales). La especificidad de estos
   selectores gana a las reglas por clase/elemento sin necesidad de !important. */
@media (max-width: 640px) {
  input:not([type="checkbox"]):not([type="radio"]),
  select:not([multiple]),
  textarea { font-size: 16px; }
  /* El input del chat usa selector por id (mayor especificidad) → override puntual. */
  :is(#input, [id^="input_"]) { font-size: 16px; }
}

/* ----- M4 — Rejillas en pantallas estrechas: la tabla pasa a TARJETAS ----------
   Una tarjeta por fila, con la etiqueta de la columna (data-col) a la izquierda y el
   valor a la derecha. El corte SUBE de 480 a 640px, el "móvil grande" del canon de
   arriba y el mismo BP_MOVIL que usa anclaje.js: entre 481 y 640px la tabla seguía
   siendo tabla con white-space:nowrap y se apelotonaba en móviles apaisados y tablets
   pequeñas. Por encima de 640px NO cambia absolutamente nada (escritorio intacto).

   Alcanza a la vez cabeceras, líneas de detalle, maestro-detalle y Extractos, porque
   todo el ERP pinta sus tablas con el mismo buildDataTable(). ------------------- */
@media (max-width: 640px) {
  .data-table thead { display: none; }
  .data-table tbody tr {
    display: block;
    border: 1px solid hsl(var(--border));
    border-radius: var(--radius);
    margin-bottom: 10px;
    padding: 10px 12px;
    background: hsl(var(--card));
    box-shadow: 0 1px 6px rgba(30,41,59,.06);
  }
  /* El zebrado de la vista tabla no tiene sentido con tarjetas ya separadas por borde y
     sombra: sin esto, la regla de :nth-child(even) (más específica) las pintaba a dos
     tonos y parecía un fallo.

     El :not(.fila-sel) NO es decorativo. Sin él, esta regla y `.data-table tbody
     tr.fila-sel` pesaban EXACTAMENTE lo mismo (0,2,2) y ésta va más abajo en el
     archivo, así que ganaba: la tarjeta seleccionada perdía su tinte cuando le tocaba
     posición par. Resultado medido: la selección se veía en la mitad de las filas
     (solo quedaba la barrita del box-shadow) y parecía aleatorio. Excluir la fila
     seleccionada dice lo que de verdad se quería: el zebrado no le aplica. */
  .data-table tbody tr:nth-child(even):not(.fila-sel) { background: hsl(var(--card)); }

  /* En tarjeta, un tinte de fondo es una señal débil: las tarjetas ya tienen fondo
     propio y borde. La selección se refuerza con el borde y una sombra de color, que
     se leen aunque el tinte quede por debajo del contraste en pantallas al sol. */
  .data-table tbody tr.fila-sel {
    border-color: hsl(var(--primary));
    box-shadow: inset 3px 0 0 hsl(var(--primary)), 0 0 0 1px hsl(var(--primary) / .35);
  }
  .data-table tbody td {
    display: flex; justify-content: space-between; align-items: flex-start;
    padding: 6px 0; border-top: none;
    border-bottom: 1px solid hsl(var(--border) / 0.5);
    font-size: 0.875rem; white-space: normal;
    gap: 10px;
  }
  .data-table tbody td::before {
    content: attr(data-col);
    font-weight: 600; font-size: 0.75rem; color: hsl(var(--muted-foreground));
    flex-shrink: 0; max-width: 45%; line-height: 1.5;
    text-transform: uppercase; letter-spacing: .04em;
  }
  /* LA regla anti-solapamiento. El valor iba suelto como texto y era un ítem flex
     anónimo con min-width:auto → no podía encogerse por debajo de su min-content y se
     salía por encima de la etiqueta. Envuelto en .cell-val (lo hace buildDataTable) sí
     puede encoger y partir palabras largas. */
  .data-table tbody td .cell-val {
    flex: 1; min-width: 0; text-align: right;
    overflow-wrap: anywhere; word-break: break-word;
  }
  /* Textos largos (TITULO de una línea es VARCHAR(60)): etiqueta arriba y valor debajo a
     todo el ancho, en vez de disputarse el hueco en la misma línea. */
  .data-table tbody td[data-col="TITULO"],
  .data-table tbody td[data-col="TEXTO"],
  .data-table tbody td[data-col="NOMBRE_COMERCIAL"],
  .data-table tbody td[data-col="TITULO_CORTO"],
  .data-table tbody td[data-col="NOMBRE_R_SOCIAL"] {
    flex-direction: column; align-items: stretch; gap: 2px;
  }
  .data-table tbody td[data-col="TITULO"] .cell-val,
  .data-table tbody td[data-col="TEXTO"] .cell-val,
  .data-table tbody td[data-col="NOMBRE_COMERCIAL"] .cell-val,
  .data-table tbody td[data-col="TITULO_CORTO"] .cell-val,
  .data-table tbody td[data-col="NOMBRE_R_SOCIAL"] .cell-val { text-align: left; }
  .data-table tbody td:last-child { border-bottom: none; }
  .data-table tbody td.col-acciones {
    justify-content: flex-end; gap: 8px; padding-top: 8px;
  }
  .data-table tbody td.col-acciones::before { display: none; }
  /* Mantener sticky header en modo tabla (> 640px) */
  .data-table { min-width: unset; }
}

/* ----- M5 — El detalle, en móvil, es una HOJA SUPERPUESTA ---------------------
   En escritorio el maestro-detalle son dos paneles apilados y funciona bien. En un
   móvil los dos layouts se peleaban por la misma pantalla: textos pisados, la tabla de
   fondo asomando y dos scrolls anidados. No es un problema de ancho sino de interacción,
   así que a ≤640px el detalle deja de ser "un panel al lado" y pasa a ser "una pantalla
   encima": hoja inferior a ancho completo, fondo bloqueado y scroll propio.

   NO se crea un panel nuevo: es el MISMO #grid-detalles, con su mismo contenido y su
   misma lógica (cargarFichaDocumento / cargarDetalleMaestro), solo re-presentado. Por eso vale a
   la vez para los 7 documentos de compra/venta y para el maestro-detalle genérico
   (Terceros→Direcciones, Diario→Apuntes), sin una sola bifurcación en JS.

   Sacar la hoja del flujo con position:fixed tiene un efecto secundario deseado: como
   el reparto 50/50 lo producía `.data-panel { flex: 1 }`, el listado recupera el 100%
   del alto automáticamente, sin necesidad de ninguna regla para él.

   Reutiliza los tokens de hoja que ya existen en el proyecto: @keyframes slideUp, el asa
   ::before del modal CRUD y env(safe-area-inset-bottom).  -------------------------- */
@keyframes detalleFondoEntra { from { opacity: 0; } to { opacity: 1; } }

@media (max-width: 640px) {
  /* Fondo bloqueado: impide tocar el listado mientras la hoja está abierta. */
  .view.detalle-abierto .detalle-backdrop {
    display: block;
    position: fixed; inset: 0;
    background: rgba(15, 23, 42, 0.5);
    z-index: var(--z-hoja-detalle);
    animation: detalleFondoEntra .2s ease;
  }

  /* La hoja. `position: fixed` escapa del overflow:hidden de .views-container — es la
     misma técnica que .panel-anclado y depende de que ningún ancestro cree bloque
     contenedor (ver el aviso junto a .views-container). */
  :is(#grid-detalles, [id^="grid-detalles_"]) {
    position: fixed; inset: auto 0 0 0;
    z-index: calc(var(--z-hoja-detalle) + 1);
    height: 80vh; height: 80dvh;          /* dvh por la barra de direcciones, como .shell */
    background: hsl(var(--card));
    border-top: none;                      /* el separador es cosa del panel de escritorio */
    border-radius: 20px 20px 0 0;
    box-shadow: 0 -8px 30px rgba(30, 41, 59, 0.18);
    padding-bottom: env(safe-area-inset-bottom, 0px);
    overflow: hidden;                      /* recorta el contenido a las esquinas curvas */
    animation: slideUp .28s cubic-bezier(.34, 1.56, .64, 1);
  }

  /* Cabecera fija tipo app: asa de arrastre, título que puede fluir y ✕ de 44px. */
  :is(#grid-detalles, [id^="grid-detalles_"]) .data-head {
    position: relative; flex-shrink: 0;
    padding: 24px 14px 10px;
    border-bottom: 1px solid hsl(var(--border));
  }
  :is(#grid-detalles, [id^="grid-detalles_"]) .data-head::before {
    content: ''; display: block; position: absolute;
    top: 10px; left: 50%; transform: translateX(-50%);
    width: 36px; height: 4px;
    background: hsl(var(--muted)); border-radius: 2px;
  }
  :is(#grid-detalles, [id^="grid-detalles_"]) .data-head h3 {
    flex: 1 1 auto; min-width: 0;
    white-space: normal; line-height: 1.3; font-size: 1rem;
  }
  /* El contador baja a una segunda línea (basis 100%) y deja la primera al título +
     «Acciones» + ✕. */
  :is(#grid-detalles, [id^="grid-detalles_"]) .data-count { flex: 1 1 100%; order: 3; }

  /* «Acciones», entre el título y el ✕. `flex: 0 0 auto` para que no se comprima: el que
     cede ancho es el título, que puede fluir a dos líneas (regla de arriba). A 360px
     entran los tres en la primera fila. */
  /* `margin-left: auto` empuja a la derecha al PAR «Acciones» + ✕ (la ✕ va detrás y no
     lleva margen propio). Sin esto, con un título largo —que es lo normal: «Factura ·
     A/1042 — NOMBRE DEL CLIENTE»— el título llena la primera línea y los dos controles
     caían pegados a la izquierda de la segunda. Cuando el hueco está vacío se colapsa y
     la ✕ se comporta exactamente como antes de esta fase. */
  .doc-actions-slot { order: 1; flex: 0 0 auto; margin-left: auto; }
  .doc-actions-slot .doc-actions-container { display: inline-flex; }
  /* El botón, dentro de la hoja, es un destino táctil principal: 44px como el ✕. */
  .doc-actions-slot .btn-outline { min-height: 44px; }

  :is(#grid-detalles, [id^="grid-detalles_"]) .detalle-close {
    order: 2; flex: 0 0 auto; margin: 0;
    min-width: 44px; min-height: 44px;
    display: inline-flex; align-items: center; justify-content: center;
    border-radius: 50%; font-size: 1.05rem;
  }
  .detalle-close-ico { margin-right: 0; }
  .detalle-close-txt { display: none; }

  /* La ficha de cabecera no se estira ni scrollea: en 80dvh el alto es para las líneas.
     Dos columnas fijas (en un móvil `auto-fit` con minmax 170px daría una sola y la
     ficha se comería media hoja); los campos anchos siguen ocupando la fila entera. */
  :is(#grid-detalles, [id^="grid-detalles_"]) .doc-cabecera {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 8px 12px;
    padding: 10px 14px;
  }

  /* Scroll propio. `overscroll-behavior: contain` evita que al llegar al final de las
     líneas el gesto siga arrastrando el listado que hay detrás. */
  :is(#grid-detalles, [id^="grid-detalles_"]) .data-scroll {
    padding: 12px 14px 14px;
    overscroll-behavior: contain;
  }
}

/* ----- M6 — El detalle, en ESCRITORIO, con altura utilizable -----------------
   Complemento exacto del bloque M5 de arriba (641 = 640 + 1): las dos presentaciones
   del MISMO panel quedan juntas y no pueden pisarse. Todo lo de aquí es inalcanzable
   para el móvil, así que la hoja superpuesta sigue intacta.

   El problema que resuelve: `.data-panel { flex: 1 }` repartía la vista 50/50 sin
   contar con que el panel de abajo arrastra mucho más cromo fijo que el de arriba
   (cabecera del panel + ficha del documento). Con la ficha de cabecera, en un portátil
   de 768px de alto a las líneas les quedaban ~29px: existían, estaban cargadas y no
   se veían. La causa NO era un overflow ni un z-index, sino altura repartida a ciegas.

   Dos correcciones que se complementan: (a) el reparto pasa a 60/40 a favor del
   detalle cuando hay un documento abierto, y (b) el cromo de dentro del detalle
   adelgaza y deja de poder crecer sin límite. Ningún `height` fijo: todo proporcional,
   de modo que redimensionar la ventana readapta las dos zonas sin solaparlas.
   -------------------------------------------------------------------------- */
@media (min-width: 641px) {
  /* `detalle-abierto` la pone abrirPanelDetalles() y la quita ocultarDetalles(): ya
     estaba cableada para el fondo de la hoja móvil, así que aquí no hace falta ni una
     línea de JS nueva. Al cerrar, la clase desaparece y el listado recupera el 100%. */
  .view.detalle-abierto :is(#grid-cabeceras, [id^="grid-cabeceras_"]) { flex: 3 1 0; }
  .view.detalle-abierto :is(#grid-detalles, [id^="grid-detalles_"]) {
    flex: 2 1 0;
    /* Suelo para ventanas bajas, donde el 40% no da ni para la ficha. Acotado al 50%
       de la vista: así el suelo NUNCA puede desbordar el overflow:hidden del contenedor
       ni comerse el listado, que es lo que pasaría con un min-height en píxeles pelado. */
    min-height: min(260px, 50%);
  }

  /* Cromo del panel de detalle: cada píxel que se ahorra aquí es un píxel de líneas.
     Solo dentro de #grid-detalles — el listado de arriba no se toca. */
  :is(#grid-detalles, [id^="grid-detalles_"]) .data-head { padding: 10px 22px 8px; }
  /* Los 44px de .link-btn son la regla de área táctil, que en escritorio no aplica.
     En móvil el bloque M5 los vuelve a subir a 44px explícitamente. */
  :is(#grid-detalles, [id^="grid-detalles_"]) .detalle-close { min-height: 32px; }
  /* «Acciones» se rige por el mismo criterio: en esta cabecera es cromo, y su altura
     natural de botón de barra de herramientas la haría descollar sobre el ✕ de al lado. */
  .doc-actions-slot .btn-outline { min-height: 32px; padding-top: 4px; padding-bottom: 4px; }
  :is(#grid-detalles, [id^="grid-detalles_"]) .data-scroll { padding-bottom: 12px; }

  /* LA corrección de fondo. La ficha estaba en `flex: 0 0 auto` con el comentario de
     «que no le robe alto al scroll de las líneas», pero shrink:0 hace justo lo
     contrario: fija su alto de contenido y obliga a recortar SIEMPRE por las líneas.
     Lo que se quería era que no CREZCA (grow:0), no que no pueda encoger.
     El max-height es el tope duro: pase lo que pase con el número de campos, las
     líneas conservan como mínimo el 60% del panel. */
  :is(#grid-detalles, [id^="grid-detalles_"]) .doc-cabecera {
    flex: 0 1 auto;
    max-height: 40%;
    overflow-y: auto;
    overscroll-behavior: contain;
    gap: 6px 16px;
    padding: 8px 16px;
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  }
  /* Dirección y Título ocupaban una fila ENTERA cada uno: en una pantalla ancha eso
     son dos filas casi vacías. Con `span 3` siguen teniendo sitio de sobra para un
     texto largo y la ficha baja de ~4 filas a 2-3. */
  :is(#grid-detalles, [id^="grid-detalles_"]) .doc-cab-campo.ancho { grid-column: span 3; }
}

/* ----- Formularios CRUD (vista de alta) ---------------------------------- */
.form-wrap { padding: 24px; max-width: 560px; overflow-y: auto; flex: 1; min-height: 0; }
.crud-form { display: flex; flex-direction: column; gap: 16px; }
.campo { display: flex; flex-direction: column; gap: 6px; }
.campo > span { font-size: 0.9rem; font-weight: 600; color: hsl(var(--foreground)); }
.campo > span em { color: #c0392b; font-style: normal; }
.campo input[type="text"], .campo input[type="number"], .campo select {
  padding: 11px 12px; border: 1px solid hsl(var(--border)); border-radius: 10px;
  font-size: 1rem; background: hsl(var(--card)); color: hsl(var(--foreground));
}
.campo input:focus, .campo select:focus {
  outline: none; border-color: hsl(var(--primary)); box-shadow: 0 0 0 3px hsl(var(--primary) / 0.15);
}
.campo small { color: hsl(var(--muted-foreground)); font-size: 0.8rem; }
.campo-check { flex-direction: row; align-items: center; gap: 10px; }
.campo-check input { width: 18px; height: 18px; }
.campo-check span { font-weight: 600; font-size: 0.9rem; }
.form-actions { margin-top: 4px; }
.form-msg { padding: 12px 14px; border-radius: 10px; font-size: 0.92rem; margin: 0; }
.form-ok { background: hsl(140 50% 95%); color: hsl(140 60% 25%); border: 1px solid hsl(140 40% 75%); }
.form-error { background: hsl(0 70% 96%); color: hsl(0 60% 38%); border: 1px solid hsl(0 60% 82%); }
/* Variantes que app.js ya venía poniendo (modal de familia, stock del artículo)
   pero que no tenían regla: el mensaje salía sin color ninguno. Solo se añade el
   color; el relleno de .form-msg se queda como estaba para no cambiar el aspecto
   de los formularios que ya la usaban. */
.form-msg--ok    { background: hsl(var(--success) / .1);     color: hsl(var(--success)); }
.form-msg--error { background: hsl(var(--destructive) / .1); color: hsl(var(--destructive)); }
/* Sin texto no debe ocupar ni reservar sitio (el hueco junto a un botón). */
.form-msg:empty  { padding: 0; }


/* ----- Buscador y paginación de tablas ----------------------------------- */
.table-search {
  margin-left: auto; min-width: 200px; max-width: 320px;
  padding: 8px 12px; border: 1px solid hsl(var(--border)); border-radius: 10px;
  font-size: 0.9rem; background: hsl(var(--card)); color: hsl(var(--foreground));
}
.table-search:focus { outline: none; border-color: hsl(var(--primary));
  box-shadow: 0 0 0 3px hsl(var(--primary) / 0.15); }
.page-size-select {
  padding: 8px 10px; border: 1px solid hsl(var(--border)); border-radius: 10px;
  font-size: 0.85rem; background: hsl(var(--card)); color: hsl(var(--foreground));
  cursor: pointer; min-height: 36px;
}
.page-size-select:focus { outline: none; border-color: hsl(var(--primary));
  box-shadow: 0 0 0 3px hsl(var(--primary) / 0.15); }

/* Fase 23: desplegables de la barra de herramientas (Ordenar por…, filtro de serie). */
.form-select {
  padding: 8px 10px; border: 1px solid hsl(var(--border)); border-radius: 10px;
  font-size: .85rem; background: hsl(var(--card)); color: hsl(var(--foreground));
  cursor: pointer; min-height: 36px;
}
.form-select:focus { outline: none; border-color: hsl(var(--primary));
  box-shadow: 0 0 0 3px hsl(var(--primary) / 0.15); }
.pagination {
  display: flex; flex-wrap: wrap; gap: 4px; align-items: center;
  padding: 10px 16px; border-top: 1px solid hsl(var(--border));
}
.pagination:empty { display: none; }

/* Pie de tabla: botón "Exportar CSV" a la izquierda + paginación a la derecha.
   El borde superior y el padding van en el pie; la paginación (que va dentro)
   los cede para no duplicarlos. */
.data-footer {
  display: flex; align-items: center; justify-content: space-between;
  flex-wrap: wrap; gap: 12px;
  padding: 10px 16px; border-top: 1px solid hsl(var(--border));
}
.data-footer .pagination { border-top: none; padding: 0; }
.data-footer .btn-outline { white-space: nowrap; }
.pg-btn {
  min-width: 36px; min-height: 36px; padding: 6px 10px; border: 1px solid hsl(var(--border));
  background: hsl(var(--card)); color: hsl(var(--foreground));
  border-radius: 8px; cursor: pointer; font-size: 0.88rem;
  transition: background .15s, transform .1s;
}
@media (hover: hover) {
  .pg-btn:hover:not(:disabled):not(.pg-activa) { background: hsl(var(--muted) / 0.5); transform: translateY(-1px); }
}
/* En táctil el :hover se queda pegado tras soltar el dedo y NO da feedback de pulsación:
   la página tarda en repintarse y parecía que el botón no había respondido. */
.pg-btn:active:not(:disabled):not(.pg-activa) { background: hsl(var(--muted) / 0.7); }
.pg-btn:disabled { opacity: 0.4; cursor: not-allowed; }
.pg-activa { background: hsl(var(--primary)); color: hsl(var(--primary-foreground));
  border-color: hsl(var(--primary)); font-weight: 600; }
.pg-sep { padding: 0 4px; color: hsl(var(--muted-foreground)); }


  /* Todas las clases van scopeadas bajo .docs-view para no pisar el ERP.
   Hereda las variables del ERP; aquí solo se añaden las que el escáner usa y
   el ERP no tiene, en valores de tema CLARO.
   ========================================================================== */
.docs-view {
  --secondary: 240 5% 96%;
  --secondary-foreground: 240 21% 15%;
  --accent-foreground: 0 0% 100%;
  --destructive: 0 72% 51%;
  --destructive-foreground: 0 0% 100%;
  --ring: 208 88% 22%;
  --success: 142 71% 38%;
  --success-foreground: 0 0% 100%;
  --warning: 38 92% 50%;
  --warning-foreground: 0 0% 12%;
  --info: 208 88% 40%;
  --popover: 0 0% 100%;
  --popover-foreground: 240 21% 15%;
}
/* Dark mode del módulo: el dark del ERP (:root[data-theme="dark"]) solo redefine sus
   propios tokens y no toca .docs-view, así que aquí reasignamos los tokens que el módulo
   añade y aclaramos los textos hardcodeados de badges/avisos/NIF para que sean legibles
   sobre fondo oscuro. Mismo patrón que styles.css:33-50: solo tokens/colores, sin layout. */
:root[data-theme="dark"] .docs-view {
  --secondary: 222 14% 22%;   --secondary-foreground: 210 20% 92%;
  --popover: 222 16% 15%;     --popover-foreground: 210 20% 92%;
  --success: 142 60% 45%;     --warning: 38 92% 55%;
  --info: 208 90% 62%;        --destructive: 0 70% 55%;   --ring: 208 90% 58%;
}
:root[data-theme="dark"] .docs-view .nif-ok    { color: hsl(142 60% 65%); }
:root[data-theme="dark"] .docs-view .nif-error { color: hsl(0 72% 68%); }
:root[data-theme="dark"] .docs-view .tipo-albaran,
:root[data-theme="dark"] .docs-view .tipo-doc-albaran,
:root[data-theme="dark"] .docs-view .albaran-warning-text strong { color: hsl(38 92% 62%); }
.docs-view, .docs-view * , .docs-view *::before, .docs-view *::after { box-sizing: border-box; }
.docs-view .docs-scroll { flex: 1; min-height: 0; overflow-y: auto; padding: 20px 24px; }
/* Los modales van por encima de todo */
.docs-view .modal-overlay { z-index: 2000; }

.docs-view /* ── Modal Overlay ──────────────────────────────────────────────────────── */
.modal-overlay {
  display: none;
  position: fixed;
  inset: 0;
  z-index: 50;
  align-items: center;
  justify-content: center;
  padding: 16px;
  background: rgba(0, 0, 0, 0.8);
  backdrop-filter: blur(4px);
}
.docs-view .modal-overlay.active { display: flex; }
.docs-view /* ── Card ───────────────────────────────────────────────────────────────── */
.card {
  background: hsl(var(--card));
  border: 1px solid hsl(var(--border));
  border-radius: var(--radius);
  overflow: hidden;
  animation: fadeIn 0.2s ease-out;
}
/* Tarjetas dentro de un modal (Tipo factura / SOLID / Albarán): limitar alto y
   permitir scroll interno para que no se corten en viewports bajos. */
.docs-view .modal-overlay .card {
  max-height: 90vh;
  display: flex;
  flex-direction: column;
}
.docs-view .modal-overlay .card-content {
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  flex: 1;
  min-height: 0;
}
@keyframes fadeIn {
  from { opacity: 0; transform: translateY(4px); }
  to   { opacity: 1; transform: translateY(0); }
}
.docs-view .card-header {
  padding: 24px 24px 0;
}
.docs-view .card-title {
  font-size: 1.25rem;
  font-weight: 600;
  letter-spacing: -0.025em;
  color: hsl(var(--foreground));
  display: flex;
  align-items: center;
  gap: 10px;
}
.docs-view .card-description {
  font-size: 0.875rem;
  color: hsl(var(--muted-foreground));
  margin-top: 4px;
}
.docs-view .card-content {
  padding: 24px;
}
.docs-view .card-footer {
  padding: 0 24px 24px;
  display: flex;
  align-items: center;
  gap: 8px;
}
.docs-view /* ── Login Card ─────────────────────────────────────────────────────────── */
.login-card {
  width: 100%;
  max-width: 400px;
}
.docs-view .login-card .card-header {
  text-align: center;
  padding: 32px 24px 0;
}
.docs-view .login-card .card-title {
  font-size: 1.5rem;
  justify-content: center;
}
.docs-view .login-card .card-description {
  margin-top: 8px;
}
.docs-view .login-icon {
  width: 48px;
  height: 48px;
  margin: 0 auto 16px;
  background: hsl(var(--secondary));
  border-radius: var(--radius);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.25rem;
}
.docs-view /* ══════════════════════════════════════════════════════════════════════════
   NUEVO: Casilla "Recuérdame"
   ══════════════════════════════════════════════════════════════════════════ */
.remember-me {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 12px;
}
.docs-view .remember-me input[type="checkbox"] {
  width: 16px;
  height: 16px;
  accent-color: hsl(var(--primary));
  cursor: pointer;
}
.docs-view .remember-me label {
  font-size: 0.8rem;
  color: hsl(var(--muted-foreground));
  cursor: pointer;
  font-weight: 400;
}
.docs-view /* ══════════════════════════════════════════════════════════════════════════
   NUEVO: Desplegable de cuentas guardadas
   ══════════════════════════════════════════════════════════════════════════
   Aparece debajo del campo "Usuario" en el login.
   Muestra una lista de cuentas que el usuario guardó con "Recuérdame".
*/

/* Wrapper del campo usuario para que el desplegable se posicione relativo a él */
.form-group-username-wrap {
  position: relative;
}
/* CÓDIGO MUERTO: no hay HTML ni JS que use .saved-accounts-dropdown (resto del login
   del antiguo Pfactura). Se conserva tal cual, pero OJO si algún día se revive: usa el
   viejo patrón de anclar con CSS, que es justo el que se salía de la pantalla en móvil
   (Fase 29). Engánchalo a Anclaje.abrir/cerrar como los otros tres desplegables en vez
   de copiar este `position:absolute`. */
.docs-view .saved-accounts-dropdown {
  display: none;
  position: absolute;
  top: 100%;           /* Justo debajo del campo de texto */
  left: 0;
  right: 0;
  z-index: 100;
  background: hsl(240 6% 8%);
  border: 1px solid hsl(var(--border));
  border-radius: var(--radius);
  margin-top: 4px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
  overflow: hidden;
  max-height: 280px;
  overflow-y: auto;
}
.docs-view .saved-accounts-dropdown.visible {
  display: block;
  animation: dropdownFadeIn 0.15s ease-out;
}
@keyframes dropdownFadeIn {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: translateY(0); }
}
.docs-view .saved-accounts-header {
  padding: 8px 12px;
  font-size: 0.7rem;
  font-weight: 600;
  color: hsl(var(--muted-foreground));
  text-transform: uppercase;
  letter-spacing: 0.05em;
  border-bottom: 1px solid hsl(var(--border));
}
.docs-view .saved-account-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  cursor: pointer;
  transition: background 0.1s;
  border-bottom: 1px solid hsl(var(--border) / 0.5);
}
.docs-view .saved-account-item:last-child {
  border-bottom: none;
}
.docs-view .saved-account-item:hover {
  background: hsl(var(--secondary));
}
.docs-view /* Avatar circular con la inicial del nombre */
.saved-account-avatar {
  width: 34px;
  height: 34px;
  min-width: 34px;
  border-radius: 50%;
  background: hsl(var(--info) / 0.2);
  color: hsl(var(--info));
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.85rem;
  font-weight: 600;
}
.docs-view .saved-account-info {
  flex: 1;
  min-width: 0;   /* Para que el text-overflow funcione */
}
.docs-view .saved-account-name {
  font-size: 0.85rem;
  font-weight: 500;
  color: hsl(var(--foreground));
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.docs-view .saved-account-user {
  font-size: 0.72rem;
  color: hsl(var(--muted-foreground));
}
.docs-view /* Botón × para eliminar una cuenta del historial */
.saved-account-remove {
  width: 24px;
  height: 24px;
  min-width: 24px;
  border-radius: 50%;
  border: none;
  background: transparent;
  color: hsl(var(--muted-foreground));
  font-size: 1rem;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: all 0.15s;
  font-family: inherit;
}
.docs-view .saved-account-remove:hover {
  background: hsl(var(--destructive) / 0.2);
  color: hsl(0 72% 42%);
}
.docs-view /* ══════════════════════════════════════════════════════════════════════════
   NUEVO: Modal de selección de tipo (Acreedor / Proveedor)
   ══════════════════════════════════════════════════════════════════════════ */
.tipo-card {
  width: 100%;
  max-width: 520px;
}
.docs-view .tipo-info {
  font-size: 0.9rem;
  color: hsl(var(--muted-foreground));
  line-height: 1.6;
  margin-bottom: 20px;
  text-align: center;
}
.docs-view .tipo-options {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
}
.docs-view .tipo-option {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  padding: 24px 16px;
  background: hsl(var(--secondary) / 0.5);
  border: 2px solid hsl(var(--border));
  border-radius: var(--radius);
  cursor: pointer;
  font-family: inherit;
  color: hsl(var(--foreground));
  transition: all 0.2s;
  text-align: center;
}
.docs-view .tipo-option:hover {
  border-color: hsl(var(--ring));
  background: hsl(var(--secondary));
  transform: translateY(-2px);
}
.docs-view .tipo-option-icon {
  font-size: 2rem;
  margin-bottom: 4px;
}
.docs-view .tipo-option-title {
  font-size: 1rem;
  font-weight: 600;
}
.docs-view .tipo-option-desc {
  font-size: 0.75rem;
  color: hsl(var(--muted-foreground));
  line-height: 1.4;
}
.docs-view /* ══════════════════════════════════════════════════════════════════════════
   NUEVO: Badge de tipo en el modal SOLID
   ══════════════════════════════════════════════════════════════════════════ */
.tipo-badge {
  display: inline-flex;
  align-items: center;
  padding: 2px 10px;
  font-size: 0.7rem;
  font-weight: 600;
  border-radius: 9999px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}
.docs-view .tipo-proveedor {
  background: hsl(var(--info) / 0.15);
  border: 1px solid hsl(var(--info) / 0.3);
  color: hsl(217 91% 70%);
}
.docs-view .tipo-acreedor {
  background: hsl(280 60% 40% / 0.15);
  border: 1px solid hsl(280 60% 40% / 0.3);
  color: hsl(280 60% 70%);
}
.docs-view .tipo-albaran {
  background: hsl(var(--warning) / 0.15);
  border: 1px solid hsl(var(--warning) / 0.3);
  color: hsl(38 92% 32%);
}
.docs-view /* ══════════════════════════════════════════════════════════════════════════
   NUEVO: Badge de tipo de documento en el editor (Factura / Albarán)
   ══════════════════════════════════════════════════════════════════════════ */
.tipo-doc-badge {
  display: inline-flex;
  align-items: center;
  padding: 3px 12px;
  font-size: 0.7rem;
  font-weight: 700;
  border-radius: 9999px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  vertical-align: middle;
  margin-left: 10px;
}
.docs-view .tipo-doc-factura {
  background: hsl(var(--info) / 0.15);
  border: 1px solid hsl(var(--info) / 0.3);
  color: hsl(217 91% 70%);
}
.docs-view .tipo-doc-albaran {
  background: hsl(var(--warning) / 0.15);
  border: 1px solid hsl(var(--warning) / 0.3);
  color: hsl(38 92% 32%);
  animation: albaran-pulse 2s ease-in-out 3;
}
@keyframes albaran-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.6; }
}
.docs-view /* ══════════════════════════════════════════════════════════════════════════
   NUEVO: Modal de confirmación de albarán
   ══════════════════════════════════════════════════════════════════════════ */
.albaran-card {
  width: 100%;
  max-width: 520px;
}
.docs-view .albaran-warning-box {
  display: flex;
  gap: 14px;
  padding: 16px;
  background: hsl(var(--warning) / 0.08);
  border: 1px solid hsl(var(--warning) / 0.25);
  border-radius: var(--radius);
  margin-bottom: 20px;
  border-left: 4px solid hsl(var(--warning));
}
.docs-view .albaran-warning-icon {
  font-size: 1.6rem;
  flex-shrink: 0;
  line-height: 1;
}
.docs-view .albaran-warning-text {
  font-size: 0.85rem;
  color: hsl(var(--foreground));
  line-height: 1.6;
}
.docs-view .albaran-warning-text strong {
  color: hsl(38 92% 32%);
}
.docs-view .albaran-note {
  font-size: 0.8rem;
  color: hsl(var(--muted-foreground));
  line-height: 1.5;
  margin: 16px 0;
  font-style: italic;
}
.docs-view .btn-albaran {
  background: hsl(var(--warning));
  color: hsl(var(--warning-foreground));
  font-weight: 600;
}
.docs-view .btn-albaran:hover {
  opacity: 0.9;
  background: hsl(38 92% 45%);
}
.docs-view /* ── SOLID Modal ────────────────────────────────────────────────────────── */
.solid-card {
  width: 100%;
  max-width: 480px;
}
.docs-view .solid-info {
  font-size: 0.875rem;
  color: hsl(var(--muted-foreground));
  line-height: 1.6;
  margin-bottom: 20px;
}
.docs-view .solid-config-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
  margin-bottom: 16px;
}
.docs-view .solid-warning {
  font-size: 0.8rem;
  color: hsl(var(--warning));
  background: hsl(var(--warning) / 0.1);
  border: 1px solid hsl(var(--warning) / 0.2);
  border-radius: var(--radius);
  padding: 12px;
  margin-bottom: 20px;
  line-height: 1.5;
}
.docs-view .solid-buttons {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.docs-view /* ══════════════════════════════════════════════════════════════════════════
   NUEVO: Mensajes de estado del NIF
   ══════════════════════════════════════════════════════════════════════════
   Estos estilos dan formato al mensaje que aparece debajo del campo
   CIF/NIF del emisor. Hay tres estados:
   - .nif-ok       → Verde: el NIF existe en la BD
   - .nif-error    → Rojo: el NIF NO existe en la BD
   - .nif-checking → Gris: verificando...
*/
.nif-status {
  font-size: 0.78rem;
  line-height: 1.4;
  min-height: 0;
  transition: all 0.2s;
  margin-top: 4px;
}
.docs-view .nif-status:empty {
  display: none;
}
.docs-view .nif-ok {
  color: hsl(142 60% 28%);
  padding: 6px 10px;
  background: hsl(var(--success) / 0.08);
  border: 1px solid hsl(var(--success) / 0.2);
  border-radius: calc(var(--radius) - 2px);
}
.docs-view .nif-error {
  color: hsl(0 72% 42%);
  padding: 6px 10px;
  background: hsl(var(--destructive) / 0.08);
  border: 1px solid hsl(var(--destructive) / 0.2);
  border-radius: calc(var(--radius) - 2px);
}
.docs-view .nif-checking {
  color: hsl(var(--muted-foreground));
  padding: 6px 10px;
  background: hsl(var(--secondary) / 0.5);
  border: 1px solid hsl(var(--border));
  border-radius: calc(var(--radius) - 2px);
}
.docs-view /* ── Form Controls ──────────────────────────────────────────────────────── */
.form-group {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.docs-view .form-group + .form-group {
  margin-top: 16px;
}
.docs-view label {
  font-size: 0.875rem;
  font-weight: 500;
  color: hsl(var(--foreground));
}
.docs-view input,
.docs-view textarea,
.docs-view select {
  width: 100%;
  padding: 8px 12px;
  font-size: 0.875rem;
  font-family: inherit;
  color: hsl(var(--foreground));
  background: transparent;
  border: 1px solid hsl(var(--input));
  border-radius: calc(var(--radius) - 2px);
  outline: none;
  transition: border-color 0.15s, box-shadow 0.15s;
}
.docs-view input::placeholder,
.docs-view textarea::placeholder {
  color: hsl(var(--muted-foreground));
}
.docs-view input:focus,
.docs-view textarea:focus,
.docs-view select:focus {
  border-color: hsl(var(--ring));
  box-shadow: 0 0 0 2px hsl(var(--ring) / 0.2);
}
.docs-view input:read-only {
  opacity: 0.7;
  cursor: default;
}
.docs-view textarea {
  resize: vertical;
  min-height: 64px;
  line-height: 1.5;
}
.docs-view input[type="file"] { display: none; }
.docs-view /* ── Buttons ────────────────────────────────────────────────────────────── */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 8px 16px;
  font-size: 0.875rem;
  font-weight: 500;
  font-family: inherit;
  border-radius: calc(var(--radius) - 2px);
  border: none;
  cursor: pointer;
  transition: background 0.15s, opacity 0.15s;
  white-space: nowrap;
  line-height: 1.25rem;
}
.docs-view .btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}
.docs-view .btn-primary {
  background: hsl(var(--primary));
  color: hsl(var(--primary-foreground));
}
.docs-view .btn-primary:hover:not(:disabled) { opacity: 0.9; }
.docs-view .btn-secondary {
  background: hsl(var(--secondary));
  color: hsl(var(--secondary-foreground));
}
.docs-view .btn-secondary:hover:not(:disabled) { background: hsl(var(--secondary) / 0.8); }
.docs-view .btn-destructive {
  background: hsl(var(--destructive));
  color: hsl(var(--destructive-foreground));
}
.docs-view .btn-destructive:hover:not(:disabled) { opacity: 0.9; }
.docs-view .btn-outline {
  background: transparent;
  color: hsl(var(--foreground));
  border: 1px solid hsl(var(--input));
}
.docs-view .btn-outline:hover:not(:disabled) {
  background: hsl(var(--accent));
  color: hsl(var(--accent-foreground));
}
.docs-view .btn-ghost {
  background: transparent;
  color: hsl(var(--foreground));
}
.docs-view .btn-ghost:hover:not(:disabled) {
  background: hsl(var(--accent));
  color: hsl(var(--accent-foreground));
}
.docs-view .btn-success {
  background: hsl(var(--success));
  color: hsl(var(--success-foreground));
}
.docs-view .btn-success:hover:not(:disabled) { opacity: 0.9; }
.docs-view .btn-warning {
  background: hsl(var(--warning));
  color: hsl(var(--warning-foreground));
  font-weight: 600;
}
.docs-view .btn-warning:hover:not(:disabled) { opacity: 0.9; }
.docs-view .btn-full { width: 100%; }
.docs-view .btn-lg {
  padding: 10px 20px;
  font-size: 0.925rem;
}
.docs-view .btn-icon {
  padding: 8px;
  width: 36px;
  height: 36px;
}
.docs-view /* ── Alerts ─────────────────────────────────────────────────────────────── */
.alert {
  padding: 12px 16px;
  border-radius: var(--radius);
  font-size: 0.875rem;
  margin-bottom: 16px;
  border: 1px solid;
}
.docs-view .alert-error {
  background: hsl(var(--destructive) / 0.15);
  border-color: hsl(var(--destructive) / 0.3);
  color: hsl(0 72% 42%);
}
.docs-view .alert-warning {
  background: hsl(var(--warning) / 0.1);
  border-color: hsl(var(--warning) / 0.25);
  color: hsl(38 92% 32%);
}
.docs-view .alert-success {
  background: hsl(var(--success) / 0.15);
  border-color: hsl(var(--success) / 0.3);
  color: hsl(142 60% 28%);
}
.docs-view /* ── Spinner ────────────────────────────────────────────────────────────── */
.spinner-wrap {
  display: none;
  justify-content: center;
  padding: 12px 0;
}
.docs-view .spinner-wrap.active { display: flex; }
.docs-view .spinner {
  width: 20px;
  height: 20px;
  border: 2px solid hsl(var(--border));
  border-top-color: hsl(var(--foreground));
  border-radius: 50%;
  animation: spin 0.6s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
.docs-view .spinner-lg {
  width: 32px;
  height: 32px;
  border-width: 3px;
}
.docs-view .progress {
  height: 2px;
  background: hsl(var(--secondary));
  border-radius: 1px;
  margin-top: 16px;
  overflow: hidden;
}
.docs-view .progress-fill {
  height: 100%;
  background: hsl(var(--destructive));
  border-radius: 1px;
  transition: width 0.3s;
}
.docs-view .text-muted {
  font-size: 0.75rem;
  color: hsl(var(--muted-foreground));
  text-align: center;
  margin-top: 16px;
}
.docs-view /* ── Main Layout ────────────────────────────────────────────────────────── */
.app-container {
  display: none;
  max-width: 1000px;
  margin: 0 auto;
  padding: 24px 16px;
}
.docs-view .app-container.visible { display: block; }
.docs-view /* ── App Header ─────────────────────────────────────────────────────────── */
.app-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 16px 0;
  margin-bottom: 24px;
  border-bottom: 1px solid hsl(var(--border));
}
.docs-view .app-header-left {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.docs-view .app-header-title {
  font-size: 1.125rem;
  font-weight: 600;
  letter-spacing: -0.025em;
}
.docs-view .app-header-subtitle {
  font-size: 0.8rem;
  color: hsl(var(--muted-foreground));
}
.docs-view .app-header-right {
  display: flex;
  align-items: center;
  gap: 12px;
}
.docs-view .user-badge {
  font-size: 0.8rem;
  color: hsl(var(--muted-foreground));
  padding: 4px 10px;
  background: hsl(var(--secondary));
  border-radius: calc(var(--radius) - 2px);
}
.docs-view .btn-logout {
  font-size: 0.8rem;
  padding: 4px 12px;
  background: transparent;
  color: hsl(var(--muted-foreground));
  border: 1px solid hsl(var(--border));
  border-radius: calc(var(--radius) - 2px);
  cursor: pointer;
  font-family: inherit;
  transition: all 0.15s;
}
.docs-view .btn-logout:hover {
  background: hsl(var(--destructive) / 0.15);
  border-color: hsl(var(--destructive) / 0.3);
  color: hsl(0 72% 42%);
}
.docs-view /* ── Badge ──────────────────────────────────────────────────────────────── */
.badge {
  display: inline-flex;
  align-items: center;
  padding: 2px 8px;
  font-size: 0.75rem;
  font-weight: 500;
  border-radius: 9999px;
  border: 1px solid transparent;
}
.docs-view .badge-outline {
  border-color: hsl(var(--border));
  color: hsl(var(--foreground));
}
.docs-view .badge-success {
  background: hsl(var(--success) / 0.15);
  border-color: hsl(var(--success) / 0.3);
  color: hsl(142 60% 28%);
}
.docs-view /* ── Features Section ───────────────────────────────────────────────────── */
.features-card {
  margin-bottom: 24px;
}
.docs-view .features-list {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 8px;
}
.docs-view .features-list li {
  font-size: 0.85rem;
  color: hsl(var(--muted-foreground));
  padding: 6px 0 6px 24px;
  position: relative;
  line-height: 1.4;
}
.docs-view .features-list li::before {
  content: "✓";
  position: absolute;
  left: 0;
  color: hsl(var(--success));
  font-weight: 600;
}
.docs-view /* ── Upload Area ────────────────────────────────────────────────────────── */
.upload-area {
  border: 2px dashed hsl(var(--border));
  border-radius: var(--radius);
  padding: 48px 24px;
  text-align: center;
  cursor: pointer;
  transition: all 0.2s;
}
.docs-view .upload-area:hover {
  border-color: hsl(var(--muted-foreground));
  background: hsl(var(--secondary) / 0.5);
}
.docs-view .upload-area.dragover {
  border-color: hsl(var(--ring));
  background: hsl(var(--ring) / 0.05);
}
.docs-view .upload-icon {
  width: 48px;
  height: 48px;
  margin: 0 auto 16px;
  background: hsl(var(--secondary));
  border-radius: var(--radius);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.25rem;
}
.docs-view .upload-area h3 {
  font-size: 1rem;
  font-weight: 600;
  margin-bottom: 4px;
}
.docs-view .upload-area p {
  font-size: 0.85rem;
  color: hsl(var(--muted-foreground));
  margin-bottom: 16px;
}
.docs-view /* ── File Info ──────────────────────────────────────────────────────────── */
.file-info {
  display: none;
  border: 1px solid hsl(var(--border));
  border-radius: var(--radius);
  padding: 16px;
  margin-top: 16px;
}
.docs-view .file-info.active { display: block; }
.docs-view .file-info p {
  font-size: 0.85rem;
  color: hsl(var(--muted-foreground));
  margin-bottom: 4px;
}
.docs-view .file-info strong {
  color: hsl(var(--foreground));
}
.docs-view .file-info-actions {
  display: flex;
  gap: 8px;
  margin-top: 12px;
}
.docs-view /* ── Loading ────────────────────────────────────────────────────────────── */
.loading {
  display: none;
  text-align: center;
  padding: 48px 0;
}
.docs-view .loading.active { display: block; }
.docs-view .loading p {
  font-size: 0.875rem;
  color: hsl(var(--muted-foreground));
  margin-top: 12px;
}
.docs-view /* ── Data Editor ────────────────────────────────────────────────────────── */
.data-editor {
  display: none;
  margin-top: 24px;
}
.docs-view .data-editor.active { display: block; }
.docs-view .editor-header {
  margin-bottom: 20px;
}
.docs-view .editor-header h3 {
  font-size: 1.125rem;
  font-weight: 600;
  letter-spacing: -0.025em;
}
.docs-view .editor-header p {
  font-size: 0.85rem;
  color: hsl(var(--muted-foreground));
  margin-top: 4px;
}
.docs-view .form-section {
  border: 1px solid hsl(var(--border));
  border-radius: var(--radius);
  padding: 20px;
  margin-bottom: 16px;
}
.docs-view .form-section-title {
  font-size: 0.9rem;
  font-weight: 600;
  margin-bottom: 16px;
  padding-bottom: 12px;
  border-bottom: 1px solid hsl(var(--border));
  display: flex;
  align-items: center;
  gap: 8px;
}
.docs-view .form-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 12px;
}
.docs-view .form-grid .form-group {
  margin-top: 0;
}
.docs-view .form-group-full {
  grid-column: 1 / -1;
}
.docs-view /* NUEVO: Clase para que la descripción ocupe más espacio en las líneas */
.form-group-desc {
  grid-column: span 2;
}
.docs-view /* ── Invoice Lines ──────────────────────────────────────────────────────── */
.line-item {
  border: 1px solid hsl(var(--border));
  border-radius: var(--radius);
  padding: 16px;
  margin-bottom: 8px;
  background: hsl(var(--secondary) / 0.3);
}
.docs-view .line-item-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 12px;
}
.docs-view .line-item-header h5 {
  font-size: 0.85rem;
  font-weight: 600;
  color: hsl(var(--muted-foreground));
}
.docs-view .btn-remove-line {
  font-size: 0.75rem;
  padding: 4px 10px;
  background: hsl(var(--destructive) / 0.15);
  color: hsl(0 72% 42%);
  border: 1px solid hsl(var(--destructive) / 0.2);
  border-radius: calc(var(--radius) - 2px);
  cursor: pointer;
  font-family: inherit;
  transition: all 0.15s;
}
.docs-view .btn-remove-line:hover {
  background: hsl(var(--destructive) / 0.25);
}
.docs-view .btn-add-line {
  font-size: 0.85rem;
  padding: 8px 16px;
  background: transparent;
  color: hsl(var(--success));
  border: 1px dashed hsl(var(--success) / 0.4);
  border-radius: calc(var(--radius) - 2px);
  cursor: pointer;
  font-family: inherit;
  font-weight: 500;
  margin-top: 8px;
  transition: all 0.15s;
}
.docs-view .btn-add-line:hover {
  background: hsl(var(--success) / 0.1);
  border-style: solid;
}
.docs-view /* ── Action Buttons ─────────────────────────────────────────────────────── */
.action-buttons {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  margin-top: 20px;
  padding-top: 20px;
  border-top: 1px solid hsl(var(--border));
}
.docs-view /* ── Results / Preview ──────────────────────────────────────────────────── */
.results {
  display: none;
  margin-top: 24px;
}
.docs-view .results.active { display: block; }
.docs-view .result-tabs {
  display: flex;
  gap: 0;
  margin-bottom: 0;
  border-bottom: 1px solid hsl(var(--border));
}
.docs-view .tab {
  padding: 10px 20px;
  background: transparent;
  border: none;
  cursor: pointer;
  font-size: 0.85rem;
  font-weight: 500;
  color: hsl(var(--muted-foreground));
  font-family: inherit;
  border-bottom: 2px solid transparent;
  transition: all 0.15s;
  margin-bottom: -1px;
}
.docs-view .tab.active {
  color: hsl(var(--foreground));
  border-bottom-color: hsl(var(--foreground));
}
.docs-view .tab:hover {
  color: hsl(var(--foreground));
}
.docs-view .tab-content {
  display: none;
  border: 1px solid hsl(var(--border));
  border-top: none;
  border-radius: 0 0 var(--radius) var(--radius);
  padding: 16px;
  max-height: 400px;
  overflow-y: auto;
}
.docs-view .tab-content.active { display: block; }
.docs-view pre {
  background: hsl(240 6% 6%);
  color: hsl(var(--foreground));
  padding: 16px;
  border-radius: calc(var(--radius) - 2px);
  overflow-x: auto;
  font-size: 0.8rem;
  line-height: 1.6;
  font-family: 'JetBrains Mono', 'SF Mono', 'Fira Code', 'Consolas', monospace;
  tab-size: 2;
}
.docs-view /* ── Error & Success Messages ───────────────────────────────────────────── */
.message-error {
  background: hsl(var(--destructive) / 0.1);
  color: hsl(0 72% 42%);
  padding: 12px 16px;
  border-radius: var(--radius);
  margin-top: 16px;
  border: 1px solid hsl(var(--destructive) / 0.2);
  font-size: 0.875rem;
}
.docs-view .message-success {
  background: hsl(var(--success) / 0.1);
  color: hsl(142 60% 28%);
  padding: 12px 16px;
  border-radius: var(--radius);
  margin-top: 16px;
  border: 1px solid hsl(var(--success) / 0.2);
  font-size: 0.875rem;
}
.docs-view .preview-banner {
  background: hsl(var(--secondary));
  padding: 12px 16px;
  border-radius: var(--radius);
  margin-bottom: 16px;
  font-size: 0.875rem;
  color: hsl(var(--muted-foreground));
}
.docs-view /* ── Separator ──────────────────────────────────────────────────────────── */
.separator {
  height: 1px;
  background: hsl(var(--border));
  margin: 16px 0;
}
.docs-view /* ── Scrollbar ──────────────────────────────────────────────────────────── */
::-webkit-scrollbar { width: 6px; height: 6px; }
.docs-view ::-webkit-scrollbar-track { background: transparent; }
.docs-view ::-webkit-scrollbar-thumb { background: hsl(var(--border)); border-radius: 3px; }
.docs-view ::-webkit-scrollbar-thumb:hover { background: hsl(var(--muted-foreground)); }
/* ── Responsive del visor (M1: el @media va SIN prefijo; sus reglas se scopean
   con .docs-view, coherente con el resto del bloque). Breakpoint canónico 640. ── */
@media (max-width: 640px) {
  .docs-view .app-header {
    flex-direction: column;
    align-items: flex-start;
    gap: 12px;
  }
  .docs-view .app-header-right {
    width: 100%;
    justify-content: space-between;
  }
  .docs-view .form-grid {
    grid-template-columns: 1fr;
  }
  /* En móvil, la descripción no ocupa 2 columnas (solo hay 1) */
  .docs-view .form-group-desc {
    grid-column: span 1;
  }
  .docs-view .action-buttons {
    flex-direction: column;
  }
  .docs-view .action-buttons .btn {
    width: 100%;
  }
  .docs-view .solid-config-grid {
    grid-template-columns: 1fr;
  }
  .docs-view .features-list {
    grid-template-columns: 1fr;
  }
  /* Modal de tipo: una columna en móvil */
  .docs-view .tipo-options {
    grid-template-columns: 1fr;
  }
}

/* ==========================================================================
   FASE 4: SISTEMA DE PESTAÑAS (BROWSER-LIKE)
   ========================================================================== */
.tab-bar {
  display: flex;
  background: hsl(var(--muted) / 0.5);
  border-bottom: 1px solid hsl(var(--border));
  padding: 8px 8px 0 8px;
  overflow-x: auto;
  scrollbar-width: none; /* Oculta scrollbar en Firefox */
  -webkit-overflow-scrolling: touch;
  touch-action: pan-x;
  gap: 4px;
  min-height: 42px;
}
.tab-bar::-webkit-scrollbar { display: none; } /* Oculta scrollbar en Chrome */

.tab {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 9px 14px;
  background: hsl(var(--muted));
  border: 1px solid hsl(var(--border));
  border-bottom: none;
  border-radius: 8px 8px 0 0;
  cursor: pointer;
  font-size: 0.85rem;
  font-weight: 500;
  color: hsl(var(--muted-foreground));
  transition: background-color 0.2s;
  min-width: 140px;
  max-width: 220px;
  user-select: none;
  position: relative;
}

@media (hover: hover) {
  .tab:hover { background: hsl(var(--border)); }
}

.tab.active {
  background: hsl(var(--card));
  color: hsl(var(--foreground));
  border-color: hsl(var(--border));
  z-index: 10;
  box-shadow: 0 -2px 8px rgba(30,41,59,.06);
}

.tab.active::after {
  content: '';
  position: absolute;
  bottom: -1px;
  left: 0;
  right: 0;
  height: 2px;
  background: hsl(var(--card)); /* Solapa el borde inferior para efecto de fusión */
}

.tab-title {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  flex: 1;
}

.tab-close {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  border: none;
  background: transparent;
  color: hsl(var(--muted-foreground));
  cursor: pointer;
  font-size: 1.1rem;
  line-height: 1;
  margin: -8px -10px -8px 0;
  flex-shrink: 0;
  transition: background .15s, color .1s;
}
/* Cerrar pestaña en rojo al pasar por encima es un aviso útil con ratón; pegado en
   táctil, una pestaña con la ✕ en rojo parece estar en un estado de error. */
@media (hover: hover) {
  .tab-close:hover {
    background: hsl(var(--danger));
    color: #fff;
  }
}
.tab-close:active { background: hsl(var(--danger)); color: #fff; }

/* OJO (Fase 29): NO añadas transform/filter/perspective/contain/will-change a
   .views-container ni a .view-instance. Crearían un bloque contenedor y los paneles
   desplegables (.panel-anclado), que son position:fixed para escapar del overflow:hidden
   de aquí abajo, dejarían de posicionarse contra el viewport y aparecerían descolocados.
   anclaje.js avisa por consola si detecta un ancestro así.
   M5: la hoja de detalle de móvil (#grid-detalles a ≤640px) depende de lo MISMO, y esa
   sí no tiene aviso automático: se quedaría recortada dentro del panel en vez de cubrir
   la pantalla. Son dos motivos independientes para no tocar esto. */
.views-container {
  flex: 1;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  position: relative;
  background: hsl(var(--card));
}

/* La vista clonada ocupa todo el espacio absoluto */
.view-instance {
  position: absolute;
  top: 0; left: 0; right: 0; bottom: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

/* ============================================================================
   FASE 29 — PANELES ANCLADOS A UN BOTÓN (patrón común)
   Lo comparten el menú "Acciones", el selector de columnas y el menú de usuario.
   Antes cada uno se anclaba por su cuenta con `position:absolute; right:0`, que da
   por hecho que el botón está pegado al borde derecho; cuando dejaba de ser cierto
   (móvil), el panel se salía de la pantalla y el overflow:hidden de la vista lo
   amputaba. Ahora la posición la MIDE anclaje.js.

   `fixed` no es un capricho: es lo que saca al panel del overflow:hidden de
   .views-container/.view-instance. top/left/max-* los escribe el JS.
   ============================================================================ */
.panel-anclado {
  position: fixed;
  top: 0; left: 0; right: auto; bottom: auto;
  margin: 0;
  z-index: var(--z-panel-anclado);
  overflow-y: auto;
  overscroll-behavior: contain;  /* al llegar al final no arrastra la página de detrás */
}

/* Presentación en móvil (≤640px) de los paneles de la barra de herramientas: hoja
   inferior a ancho completo. No es un capricho estético: al ancho de un teléfono, un
   panel anclado a un botón cabe pero queda diminuto y lejos del pulgar. Hereda los
   valores que .column-popover ya usaba, y el mismo @keyframes slideUp que los modales. */
.panel-anclado--hoja {
  inset: auto 0 0 0 !important;
  width: 100%;
  max-width: none !important;
  /* El !important es lo que hace que anclaje.js tenga que escribir su tope con
     setProperty(..., 'important') cuando hay teclado virtual (ver colocar()). */
  max-height: 70vh !important;
  max-height: 70dvh !important;
  border-radius: 20px 20px 0 0;
  box-shadow: 0 -8px 30px rgba(30, 41, 59, .15);
  padding-bottom: env(safe-area-inset-bottom, 0px);
  animation: slideUp .25s ease;
}

/* ==========================================================================
   FASE 4: COLUMN CHOOSER (SELECTOR DINÁMICO)
   ========================================================================== */
.table-controls {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 12px;
  margin-left: auto; /* Empuja los controles a la derecha */
}

/* Sin `position: relative`: existía solo para anclar el popover cuando era `absolute`.
   Ahora es `fixed` y lo coloca anclaje.js contra el viewport. */
.column-chooser-container {
  display: inline-flex;
}

.btn-outline {
  background: white;
  border: 1px solid #cbd5e1;
  padding: 6px 12px;
  border-radius: 6px;
  font-size: 0.85rem;
  font-weight: 500;
  color: #334155;
  cursor: pointer;
  display: flex;
  align-items: center;
  min-height: 36px;
  transition: background .15s, box-shadow .15s;
}
@media (hover: hover) {
  .btn-outline:hover:not(:disabled) { background: #f1f5f9; box-shadow: 0 2px 8px rgba(30,41,59,.08); }
}
.btn-outline:active:not(:disabled) { background: #e2e8f0; }
/* Faltaba: un .btn-outline deshabilitado (p. ej. "Convirtiendo…") no se
   distinguía de uno pulsable más allá del estilo por defecto del navegador. */
.btn-outline:disabled { opacity: .5; cursor: not-allowed; }

/* La posición la pone anclaje.js (.panel-anclado): aquí solo el aspecto. */
.column-popover {
  background: white;
  border: 1px solid #e2e8f0;
  border-radius: 8px;
  box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
  width: 240px;
  display: flex;
  flex-direction: column;
}

.column-popover-header {
  padding: 10px 14px;
  font-weight: 600;
  border-bottom: 1px solid #e2e8f0;
  background: #f8fafc;
  border-radius: 8px 8px 0 0;
  font-size: 0.85rem;
  color: #0f172a;
}

.column-list {
  max-height: 250px;
  overflow-y: auto;
  padding: 8px 0;
}

.column-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 6px 14px;
  cursor: pointer;
  font-size: 0.85rem;
  color: #334155;
}
@media (hover: hover) {
  .column-item:hover { background: #f1f5f9; }
}
.column-item:active { background: #e2e8f0; }
.column-item input[type="checkbox"] { cursor: pointer; }
.column-item input[type="checkbox"]:disabled { opacity: 0.4; cursor: not-allowed; }
.column-item:has(input:disabled) { opacity: 0.5; pointer-events: none; }

/* La hoja inferior de móvil que este popover tenía en exclusiva es ahora el modo
   compartido .panel-anclado--hoja (Fase 29): la usan también el menú "Acciones" y
   cualquier desplegable futuro. Aquí solo queda lo específico: sin el tope de 250px
   de la lista, es la hoja la que manda la altura y hace el scroll. */
.panel-anclado--hoja .column-list { max-height: none; }

.column-master-row {
  padding: 8px 14px 6px;
  border-bottom: 1px solid #f1f5f9;
}
.column-master-label {
  display: flex; align-items: center; gap: 10px;
  font-size: 0.85rem; font-weight: 600; color: #0f172a; cursor: pointer;
}
.col-master-toggle { cursor: pointer; width: 15px; height: 15px; accent-color: hsl(var(--primary)); }
.column-popover-divider { height: 1px; background: #e2e8f0; margin: 2px 0; }

/* ==========================================================================
   FASE 5: BOTÓN NUEVO Y ACCIONES DE FILA
   ========================================================================== */
.btn-new-row {
  background: hsl(var(--primary)); color: hsl(var(--primary-foreground));
  border: none; border-radius: 8px; padding: 10px 16px;
  font-size: 0.85rem; font-weight: 600; font-family: inherit;
  cursor: pointer; white-space: nowrap; flex-shrink: 0;
  transition: opacity .15s, transform .1s;
}
@media (hover: hover) {
  .btn-new-row:hover { opacity: 0.88; transform: translateY(-1px); }
}
.btn-new-row:active { opacity: 0.8; transform: translateY(1px); }

.col-acciones { white-space: nowrap; width: 80px; text-align: center; }
th.col-acciones { width: 80px; text-align: center; }

/* La columna de acciones queda ANCLADA al borde derecho -------------------- */
/* Con `white-space: nowrap` en las celdas, una sola columna de texto larga ya
   desborda la tabla y empuja ✏️/🗑️ fuera del scroll de .data-table-wrap. Medido
   con las métricas de Segoe UI a .85rem sobre los datos reales: la rejilla de
   Clientes ocupa 1023px (TITULO sola gasta 511px), así que por debajo de una
   ventana de ~1366px los botones dejaban de verse. Anclarla los mantiene siempre
   accesibles y hace la rejilla independiente de lo anchos que sean los datos.
   El contenedor del scroll es .data-table-wrap (overflow: auto), que es el
   ancestro correcto para `position: sticky`. */
.data-table tbody td.col-acciones,
.data-table thead th.col-acciones {
  position: sticky; right: 0;
  border-left: 1px solid hsl(var(--border));
}
.data-table thead th.col-acciones { z-index: 3; }   /* sobre el resto del thead */

/* Las celdas ancladas necesitan fondo OPACO: si no, las columnas que pasan por
   debajo al hacer scroll se ven a través. Se reconstruye el color exacto de cada
   estado de la fila superponiendo su tinte translúcido sobre hsl(var(--card)),
   que es el fondo de .data-table-wrap. Así la columna anclada no rompe el
   zebrado, el hover ni la fila seleccionada. */
.data-table tbody td.col-acciones { z-index: 1; background: hsl(var(--card)); }
.data-table tbody tr:nth-child(even) td.col-acciones {
  background: linear-gradient(hsl(var(--muted) / 0.3), hsl(var(--muted) / 0.3)),
              hsl(var(--card));
}
/* El hover de la celda anclada acompaña al de la fila: si se guarda uno y el otro no,
   en táctil se quedaría iluminada SOLO la columna de acciones. */
@media (hover: hover) {
  .data-table tbody tr:hover td.col-acciones {
    background: linear-gradient(hsl(var(--primary) / 0.07), hsl(var(--primary) / 0.07)),
                hsl(var(--card));
  }
}
.data-table tbody tr.fila-sel td.col-acciones,
.data-table tbody tr.fila-sel:hover td.col-acciones {
  background: linear-gradient(hsl(var(--primary) / 0.16), hsl(var(--primary) / 0.16)),
              hsl(var(--card));
}

/* En móvil (≤640px) cada fila es una TARJETA y no hay scroll horizontal, así que
   anclar no aporta nada y el fondo opaco taparía el de la tarjeta. Se cancela.
   Este bloque va DESPUÉS de las reglas de arriba a propósito: el @media de las
   tarjetas está más arriba en el fichero y, a igual especificidad, perdería. */
@media (max-width: 640px) {
  .data-table tbody td.col-acciones,
  .data-table thead th.col-acciones {
    position: static; border-left: none; background: none;
  }
}

/* Columnas numéricas del detalle (Debe/Haber, importes): alineadas a la derecha. */
.data-table td.num, .data-table th.num { text-align: right; font-variant-numeric: tabular-nums; }

.btn-row-edit, .btn-row-del {
  background: transparent; border: none; cursor: pointer;
  font-size: 1rem; padding: 10px 12px; border-radius: 5px; line-height: 1;
  min-width: 40px; min-height: 40px;
  display: inline-flex; align-items: center; justify-content: center;
  transition: background .15s;
}
@media (hover: hover) {
  .btn-row-edit:hover { background: hsl(var(--primary) / 0.1); }
  .btn-row-del:hover  { background: hsl(var(--danger) / 0.12); }
}
/* Sin esto, en un teléfono pulsar ✏️ no producía ningún cambio visible mientras se
   abría la ficha, y se acababa pulsando dos veces. */
.btn-row-edit:active { background: hsl(var(--primary) / 0.2); }
.btn-row-del:active  { background: hsl(var(--danger) / 0.22); }

/* ==========================================================================
   FASE 5: MODAL GENÉRICO CRUD
   ========================================================================== */
.modal-crud-overlay {
  position: fixed; inset: 0; z-index: 500;
  background: rgba(15, 23, 42, 0.55); backdrop-filter: blur(3px);
  display: flex; align-items: center; justify-content: center; padding: 20px;
}
/* Modal que se abre ENCIMA de otro modal (Fase 34): el mantenimiento de catálogo
   que abre el botón «Configuración» desde la ficha de almacén o de familia. Antes
   ningún modal se apilaba, así que todos compartían el mismo z-index; sin esto, el
   de arriba quedaba por debajo del que lo había abierto. Quién recibe el Escape y
   el clic en el fondo lo decide la pila de app.js (modalEsTope), no el z-index. */
.modal-crud-overlay--sobre { z-index: 520; }
.modal-crud-box {
  background: hsl(var(--card)); border: 1px solid hsl(var(--border));
  border-radius: var(--radius); box-shadow: 0 20px 50px rgba(30,41,59,.18);
  /* El par vh/dvh es la convención del proyecto (.shell, .sidebar, la hoja de detalle):
     con la barra de direcciones desplegada, 85vh mide contra la ventana IDEAL y el
     modal se salía por abajo — en el de e-mail eso dejaba fuera «Enviar». */
  width: 100%; max-width: 520px; max-height: 85vh; max-height: 85dvh;
  display: flex; flex-direction: column; animation: rise .25s ease;
}
.modal-crud-header {
  display: flex; align-items: center; justify-content: space-between;
  padding: 18px 22px; border-bottom: 1px solid hsl(var(--border));
  flex-shrink: 0;
}
.modal-crud-header h3 { font-size: 1rem; font-weight: 700; }
.modal-crud-close {
  background: none; border: none; font-size: 1.1rem; cursor: pointer;
  color: hsl(var(--muted-foreground)); border-radius: 6px; padding: 10px;
  min-width: 36px; min-height: 36px;
  display: inline-flex; align-items: center; justify-content: center;
  transition: background .15s, color .15s;
}
@media (hover: hover) {
  .modal-crud-close:hover { background: hsl(var(--muted) / .5); }
}
.modal-crud-close:active { background: hsl(var(--muted) / .8); }
.modal-crud-body { padding: 20px 22px; overflow-y: auto; flex: 1; }
.crud-dyn-form { display: flex; flex-direction: column; gap: 14px; }
.crud-dyn-field { display: flex; flex-direction: column; gap: 5px; }
.crud-dyn-field label {
  font-size: 0.76rem; font-weight: 600; text-transform: uppercase;
  letter-spacing: .05em; color: hsl(var(--muted-foreground));
}
.crud-dyn-field input {
  padding: 10px 12px; border: 1px solid hsl(var(--border)); border-radius: 9px;
  font-size: .93rem; background: hsl(var(--card)); color: hsl(var(--foreground));
  font-family: inherit; outline: none; transition: border-color .2s;
}
.crud-dyn-field input:focus {
  border-color: hsl(var(--primary)); box-shadow: 0 0 0 3px hsl(var(--primary) / .12);
}
.modal-crud-actions { display: flex; gap: 10px; margin-top: 6px; align-items: center; }
.modal-crud-actions .btn-primary { width: auto; min-width: 120px; padding: 11px 20px; font-size: 0.9rem; }
.modal-crud-actions .btn-link { width: auto; margin-top: 0; font-size: 0.88rem; }

@keyframes slideUp {
  from { transform: translateY(100%); opacity: 0; }
  to   { transform: translateY(0);    opacity: 1; }
}

@media (max-width: 640px) {
  .modal-crud-overlay { align-items: flex-end; padding: 0; }
  .modal-crud-box {
    max-width: 100%;
    border-radius: 20px 20px 0 0;
    max-height: 92vh; max-height: 92dvh;
    animation: slideUp .3s cubic-bezier(.34,1.56,.64,1);
    padding-bottom: env(safe-area-inset-bottom, 0px);
  }
  .modal-crud-header { position: relative; padding-top: 24px; }
  .modal-crud-header::before {
    content: ''; display: block; position: absolute;
    top: 10px; left: 50%; transform: translateX(-50%);
    width: 36px; height: 4px;
    background: hsl(var(--muted)); border-radius: 2px;
  }
}

/* ============================================================================
   MENÚ DE ACCIONES SOBRE DOCUMENTOS (Fase 26 — dropdown escalable)
   Deriva del patrón .user-dropdown-menu / .user-dropdown-item, con las variables
   de tema del ERP para encajar visualmente. Dentro de la vista clonada view-data.
   ============================================================================ */
.doc-actions-container { display: inline-flex; }

/* Hueco de la cabecera del panel de detalle donde ubicarDocActions() coloca el menú
   mientras hay un documento abierto — en cualquier ancho, no solo en móvil. Se queda
   vacío al cerrarlo, y un div vacío en un contenedor flex con `gap` deja un hueco
   fantasma de 8px junto al título: por eso se colapsa. */
.doc-actions-slot { display: inline-flex; }
.doc-actions-slot:empty { display: none; }
/* La posición la pone anclaje.js (.panel-anclado): aquí solo el aspecto. El antiguo
   `right: 0` era la causa del menú que se salía por la izquierda en móvil, y el
   `max-width: calc(100vw - 24px)` que lo acompañaba no servía de nada: limitaba el
   ancho pero nunca movía la caja. Ahora el ancho lo acota el helper. */
.doc-actions-menu {
  min-width: 220px; padding: 6px;
  display: flex; flex-direction: column; gap: 2px;
  background: hsl(var(--card)); border: 1px solid hsl(var(--border));
  border-radius: 12px; box-shadow: 0 12px 32px rgba(0, 0, 0, .22);
}
.doc-actions-item {
  display: flex; align-items: center; gap: 10px; width: 100%;
  text-align: left; padding: 10px 12px; border: none; border-radius: 8px;
  cursor: pointer; background: transparent; color: hsl(var(--foreground));
  font-size: .9rem; font-family: inherit;
}
@media (hover: hover) {
  .doc-actions-item:hover { background: hsl(var(--muted) / .7); }
}
/* Imprimir y Enviar por e-mail tardan (viaje al ERP + generación del PDF). Sin señal de
   pulsación el usuario cree que no ha pasado nada y vuelve a tocar. */
.doc-actions-item:active { background: hsl(var(--muted)); }
.doc-actions-icon { width: 1.2em; text-align: center; flex-shrink: 0; }

/* ---- Modal "Enviar por e-mail": reutiliza .modal-crud-* y .crud-dyn-form ---- */
.email-doc-ref { margin: 0 0 14px; font-size: .85rem; color: hsl(var(--muted-foreground)); }
.email-field { display: flex; flex-direction: column; gap: 5px; }
.email-field > label {
  font-size: .76rem; font-weight: 600; text-transform: uppercase;
  letter-spacing: .05em; color: hsl(var(--muted-foreground));
}
.email-field input, .email-field textarea {
  padding: 10px 12px; border: 1px solid hsl(var(--border)); border-radius: 9px;
  font-size: .93rem; background: hsl(var(--card)); color: hsl(var(--foreground));
  font-family: inherit; outline: none; transition: border-color .2s; resize: vertical;
}
.email-field input:focus, .email-field textarea:focus {
  border-color: hsl(var(--primary)); box-shadow: 0 0 0 3px hsl(var(--primary) / .12);
}
.email-recipients { display: flex; flex-direction: column; gap: 4px; max-height: 160px; overflow-y: auto; }
.email-recipient {
  display: flex; align-items: center; gap: 10px; padding: 9px 11px;
  border: 1px solid hsl(var(--border)); border-radius: 9px; cursor: pointer;
  font-size: .9rem; background: hsl(var(--card));
}
@media (hover: hover) {
  .email-recipient:hover { background: hsl(var(--muted) / .5); }
}
.email-recipient:active { background: hsl(var(--muted) / .7); }
.email-recipient input { margin: 0; flex-shrink: 0; }
.email-empty { font-size: .85rem; color: hsl(var(--muted-foreground)); padding: 6px 2px; margin: 0; }

/* ============================================================================
   TPV NATIVO (Fase 14 — Tabula Rasa). Reconstruido desde cero con CSS Grid.
   Clases con prefijo tpv-/articulo- aisladas del resto de la app. Reutiliza las
   variables de tema (--primary, --card, --muted, --border, --foreground, --radius,
   --danger) ya definidas en el :root al inicio de este archivo.
   ============================================================================ */
.tpv-view { height: 100%; }
.tpv-grid {
  flex: 1; min-height: 0; height: 100%;
  display: grid; grid-template-columns: 1fr 340px; gap: 16px; padding: 16px;
}

/* ---- Panel izquierdo: catálogo ---- */
.tpv-catalogo { display: flex; flex-direction: column; min-width: 0; min-height: 0; gap: 12px; }
/* Buscador + selector Familias/Agrupaciones en una sola fila: el selector no
   añade altura (el buscador cede anchura, que sobra). */
.tpv-catalogo-head { flex-shrink: 0; display: flex; align-items: stretch; gap: 8px; }
.tpv-buscar {
  flex: 1 1 auto; min-width: 0;
  width: 100%; padding: 10px 16px; border-radius: 999px;
  border: 1px solid hsl(var(--border)); background: hsl(var(--card));
  color: hsl(var(--foreground)); font-size: .95rem;
}
.tpv-buscar:focus {
  outline: none; border-color: hsl(var(--primary));
  box-shadow: 0 0 0 3px hsl(var(--primary) / 0.12);
}

/* Selector de modo: control SEGMENTADO (dos botones pegados con borde común).
   Deriva del .tpv-chip pero no se disfraza de chip: es el interruptor que decide
   qué chips hay debajo, no una categoría más. */
.tpv-modo {
  flex-shrink: 0; display: inline-flex; overflow: hidden;
  border: 1px solid hsl(var(--border)); border-radius: 999px; background: hsl(var(--card));
}
.tpv-modo-btn {
  padding: 8px 14px; border: none; cursor: pointer; font-size: .85rem; white-space: nowrap;
  background: transparent; color: hsl(var(--muted-foreground));
}
.tpv-modo-btn + .tpv-modo-btn { border-left: 1px solid hsl(var(--border)); }
@media (hover: hover) {
  .tpv-modo-btn:hover { background: hsl(var(--muted) / 0.5); color: hsl(var(--foreground)); }
}
.tpv-modo-btn:active { background: hsl(var(--muted) / 0.8); }
.tpv-modo-btn.active { background: hsl(var(--primary)); color: hsl(var(--primary-foreground)); }
.tpv-modo-btn:focus-visible { outline: 2px solid hsl(var(--primary)); outline-offset: -2px; }

/* UNA fila de chips con scroll horizontal, en TODOS los anchos. Era la regla del
   bloque móvil (≤820) y en PC los chips se envolvían en dos o tres filas, comiéndose
   la altura de la rejilla de artículos. `flex-wrap: nowrap` + `flex-shrink: 0` en
   cada chip (+ el `white-space: nowrap` que ya tenía) = un chip no puede ni saltar
   de línea ni encogerse; `overflow-x: auto` se queda el desbordamiento dentro de la
   fila (el .tpv-catalogo ya lleva min-width: 0, así que no empuja la rejilla).
   `scrollbar-width: thin`: Windows pinta la barra siempre, y así cuesta ~8px y no 17. */
.tpv-categorias {
  flex-shrink: 0; display: flex; flex-wrap: nowrap; gap: 8px;
  overflow-x: auto; overflow-y: hidden; -webkit-overflow-scrolling: touch;
  scrollbar-width: thin; padding-bottom: 2px;
}
.tpv-categorias .tpv-chip { flex-shrink: 0; }
.tpv-chip {
  padding: 8px 16px; border-radius: 999px; cursor: pointer; font-size: .85rem;
  white-space: nowrap;
  border: 1px solid hsl(var(--border)); background: hsl(var(--card)); color: hsl(var(--foreground));
}
@media (hover: hover) {
  .tpv-chip:hover { background: hsl(var(--muted) / 0.5); }
}
.tpv-chip:active { background: hsl(var(--muted) / 0.8); }
.tpv-chip.active { background: hsl(var(--primary)); color: hsl(var(--primary-foreground)); border-color: hsl(var(--primary)); }

.tpv-articulos {
  flex: 1; min-height: 0; overflow-y: auto; padding-right: 4px;
  display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 12px;
  align-content: start;
}
.tpv-empty { grid-column: 1 / -1; color: hsl(var(--muted-foreground)); padding: 24px; text-align: center; }

.articulo-btn {
  aspect-ratio: 1 / 1; display: flex; flex-direction: column; justify-content: space-between;
  gap: 8px; padding: 12px; text-align: left; cursor: pointer;
  border: 1px solid hsl(var(--border)); border-radius: var(--radius);
  background: hsl(var(--card)); color: hsl(var(--foreground));
  transition: transform .1s ease, box-shadow .1s ease, border-color .1s ease;
}
/* El :hover pegado era especialmente confuso aquí: una rejilla de artículos donde uno
   queda con borde azul y elevado se lee como «seleccionado», y en el TPV no existe tal
   cosa — al tocarlo se añade al ticket y punto. */
@media (hover: hover) {
  .articulo-btn:hover { transform: translateY(-2px); box-shadow: 0 6px 16px rgba(0,0,0,.08); border-color: hsl(var(--primary)); }
}
.articulo-btn:active { transform: translateY(0); background: hsl(var(--primary) / .1); }
.articulo-btn .art-nombre {
  font-size: .9rem; font-weight: 600; line-height: 1.2;
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical;
  overflow: hidden; overflow-wrap: break-word; word-break: break-word;
}
.articulo-btn .art-precio { font-size: 1.05rem; font-weight: 700; color: hsl(var(--primary)); }
/* Artículo sin tipo de IVA resuelto: su cobro será rechazado, así que se avisa
   ANTES de añadirlo al ticket en vez de dejar que falle al pulsar COBRAR. */
.articulo-btn--sin-iva { border-color: hsl(var(--danger) / 0.5); }
.articulo-btn--sin-iva .art-precio { color: hsl(var(--danger)); }

/* ---- Panel derecho: ticket ---- */
.tpv-ticket {
  display: flex; flex-direction: column; min-height: 0; overflow: hidden;
  border: 1px solid hsl(var(--border)); border-radius: var(--radius); background: hsl(var(--card));
}
.tpv-ticket-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 14px 16px; border-bottom: 1px solid hsl(var(--border)); font-weight: 600;
}
.tpv-vaciar {
  padding: 6px 12px; border-radius: 8px; cursor: pointer; font-size: .8rem;
  border: 1px solid hsl(var(--border)); background: hsl(var(--card)); color: hsl(var(--danger));
}
@media (hover: hover) {
  .tpv-vaciar:hover { background: hsl(var(--danger) / 0.08); }
}
.tpv-vaciar:active { background: hsl(var(--danger) / 0.16); }

.tpv-cliente-row { flex-shrink: 0; padding: 10px 12px; border-bottom: 1px solid hsl(var(--border)); }
.tpv-cliente-select {
  width: 100%; padding: 8px 10px; border-radius: 8px; font-size: .88rem; cursor: pointer;
  border: 1px solid hsl(var(--border)); background: hsl(var(--card)); color: hsl(var(--foreground));
}
.tpv-cliente-select:focus { outline: none; border-color: hsl(var(--primary)); box-shadow: 0 0 0 3px hsl(var(--primary) / .12); }

.tpv-ticket-items { flex: 1; min-height: 0; overflow-y: auto; list-style: none; margin: 0; padding: 8px 12px; }
.tpv-linea {
  display: grid; grid-template-columns: 1fr auto; gap: 4px 8px; align-items: center;
  padding: 10px 4px; border-bottom: 1px solid hsl(var(--border) / 0.6);
}
.tpv-linea-nombre { grid-column: 1 / -1; font-size: .85rem; }
.tpv-linea-controls { display: flex; align-items: center; gap: 6px; }
.tpv-linea-btn {
  width: 30px; height: 30px; border-radius: 6px; cursor: pointer;
  font-size: 1rem; line-height: 1;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid hsl(var(--border)); background: hsl(var(--card)); color: hsl(var(--foreground));
}
@media (hover: hover) {
  .tpv-linea-btn:hover { background: hsl(var(--muted) / 0.5); }
}
/* Es el control que más se repite en el TPV (subir/bajar unidades): sin feedback de
   pulsación no se sabe si el toque ha contado. */
.tpv-linea-btn:active { background: hsl(var(--muted) / 0.9); }
.tpv-linea-uds { min-width: 28px; text-align: center; font-variant-numeric: tabular-nums; }
.tpv-linea-subtotal { justify-self: end; font-weight: 600; font-variant-numeric: tabular-nums; }
/* Notas de la línea del ticket (GES_DETALLES_S.NOTAS, 9.º parámetro de
   G_INSERTA_DETALLE_FAC, máx. 200). Nace PLEGADA ([hidden]) y solo ocupa altura cuando
   se abre con el 📝: en móvil el alto del ticket es lo que escasea. */
.tpv-linea-btn--notas.active { background: hsl(var(--primary) / .15); border-color: hsl(var(--primary)); }
.tpv-linea-notas {
  grid-column: 1 / -1; width: 100%; min-height: 2.6rem; resize: vertical;
  padding: 6px 8px; border-radius: 6px; font: inherit; font-size: .85rem;
  border: 1px solid hsl(var(--border)); background: hsl(var(--card)); color: hsl(var(--foreground));
}
.tpv-linea-notas[hidden] { display: none; }
.tpv-linea-notas:focus { outline: none; border-color: hsl(var(--primary)); box-shadow: 0 0 0 3px hsl(var(--primary) / .12); }

.tpv-ticket-foot { flex-shrink: 0; padding: 16px; border-top: 1px solid hsl(var(--border)); }
/* Desglose base + IVA sobre el TOTAL. Deliberadamente discreto: el importe que
   manda es el TOTAL, y este desglose solo justifica de dónde sale. */
.tpv-desglose-row {
  display: flex; align-items: baseline; justify-content: space-between;
  font-size: .85rem; color: hsl(var(--muted-foreground)); margin-bottom: 4px;
}
.tpv-desglose-row span:last-child { font-variant-numeric: tabular-nums; }

.tpv-total-row {
  display: flex; align-items: baseline; justify-content: space-between;
  font-size: 1.4rem; font-weight: 700; margin-bottom: 14px;
  padding-top: 8px; border-top: 1px solid hsl(var(--border));
}
.tpv-total-row span:last-child { color: hsl(var(--primary)); font-variant-numeric: tabular-nums; }
.tpv-cobrar {
  width: 100%; padding: 20px; border: none; border-radius: var(--radius); cursor: pointer;
  background: hsl(var(--primary)); color: hsl(var(--primary-foreground));
  font-size: 1.4rem; font-weight: 700; letter-spacing: .04em;
  transition: filter .1s ease;
}
@media (hover: hover) {
  .tpv-cobrar:hover:not(:disabled) { filter: brightness(1.08); }
}
.tpv-cobrar:active:not(:disabled) { filter: brightness(.92); }
.tpv-cobrar:disabled { opacity: .45; cursor: not-allowed; }

/* Aviso flotante del cobro (éxito/error), anclado a body fuera de la pestaña. */
.tpv-aviso {
  position: fixed; top: 20px; left: 50%; transform: translateX(-50%); z-index: 2000;
  padding: 14px 22px; border-radius: 10px; max-width: 90vw; text-align: center;
  font-size: .95rem; font-weight: 600; color: #fff; box-shadow: 0 8px 24px rgba(0,0,0,.18);
}
.tpv-aviso-ok    { background: hsl(142 71% 40%); }
.tpv-aviso-error { background: hsl(var(--danger)); }

/* Responsive TPV: pantalla partida en vertical, catálogo arriba y ticket abajo, cada
   mitad con scroll interno propio; nunca se solapan.

   EL REPARTO YA NO ES FIJO (era 55 % / 45 %). Medido en un móvil de 360×640: la vista
   dispone de ~530 px (100dvh menos topbar y barra de pestañas); el 45 % del ticket eran
   238 px, pero solo su «cromo» —cabecera con Vaciar a 44 px, fila de cliente y pie con
   desglose, TOTAL y COBRAR— medía ~278 px. Como todo eso es flex-shrink:0 y el ticket
   recorta con overflow:hidden, las líneas se quedaban a 0 px y COBRAR salía por abajo.
   Ahora el ticket se dimensiona por su contenido con un TOPE del 55 %: vacío, cede casi
   toda la altura al catálogo; lleno, crece hasta el tope y las líneas hacen scroll
   dentro, con un mínimo de 7,5rem (≈1,5 líneas) que garantiza que se ven. El pie sigue
   siendo flex-shrink:0, así que COBRAR no puede salirse del tope.

   El catálogo va con flex-basis 0 y NO auto: con auto su base sería el alto de la
   rejilla entera (60 tarjetas) y, al repartir el encogimiento, aplastaría al ticket
   por debajo de su contenido y COBRAR saldría por abajo (medido en el arnés). Con
   base 0 el catálogo solo se queda con el hueco que el ticket no usa. El ticket no
   encoge (flex-shrink 0): su tope lo pone max-height. */
@media (max-width: 820px) {
  .tpv-grid {
    display: flex; flex-direction: column;
    height: 100%; overflow: hidden; padding: 0; gap: 0;
  }
  .tpv-catalogo {
    flex: 1 1 0%; min-height: 0; height: auto;
    display: flex; flex-direction: column; overflow: hidden;
    padding: 6px 8px; gap: 8px;
  }
  .tpv-ticket {
    flex: 0 0 auto; min-height: 0; height: auto; max-height: 55%; width: 100%;
    display: flex; flex-direction: column; overflow: hidden;
    border-left: none; border-top: 2px solid hsl(var(--border));
  }
  /* La fila única de chips con scroll horizontal ya es la regla base
     (.tpv-categorias, arriba). Aquí solo se retira la barra: en táctil se desplaza
     con el dedo y la barra «thin» costaba 10 px de alto en Windows. Con ratón sigue
     funcionando la rueda (initTpvTab la convierte en scroll horizontal). */
  .tpv-categorias { scrollbar-width: none; padding-bottom: 0; }
  .tpv-categorias::-webkit-scrollbar { display: none; }
  /* Tarjetas: se abandona el cuadrado (aspect-ratio 1/1 → ~106 px de alto por 106 de
     ancho) por una tarjeta de 72 px mínimo con el nombre a dos líneas. Sigue muy por
     encima de los 44×44 táctiles y caben dos filas donde antes cabía una y media. */
  .tpv-articulos {
    grid-template-columns: repeat(auto-fill, minmax(104px, 1fr)); gap: 8px;
    overflow-y: auto; flex: 1; min-height: 0;
  }
  .articulo-btn { aspect-ratio: auto; min-height: 72px; padding: 8px 10px; gap: 4px; }
  .articulo-btn .art-nombre { font-size: .85rem; -webkit-line-clamp: 2; }
  .tpv-ticket-items {
    overflow-y: auto; flex: 1 1 auto; min-height: 7.5rem;
  }
  .tpv-linea { padding: 6px 4px; }
  /* Compactar el "cromo" del ticket para dejar sitio a las líneas en pantallas pequeñas.
     El pie pasa a una rejilla de dos columnas: [Base][IVA] arriba y [TOTAL][COBRAR]
     abajo. Los cuatro son hijos directos del footer, así que no hace falta tocar el
     HTML; ahorra ~70 px respecto a las cuatro filas apiladas. */
  .tpv-ticket-head, .tpv-cliente-row { padding: 4px 12px; }
  .tpv-ticket-foot {
    display: grid; grid-template-columns: 1fr 1fr; align-items: center;
    gap: 4px 10px; padding: 8px 12px;
  }
  .tpv-desglose-row { margin-bottom: 0; }
  .tpv-total-row { font-size: 1.15rem; margin: 0; padding-top: 0; border-top: none; }
  .tpv-cobrar { padding: 10px 16px; font-size: 1.1rem; min-height: 48px; }

  /* Áreas táctiles. Van en el breakpoint del TPV (820) y no en el general (640) porque
     es aquí donde el TPV cambia a vertical: una tablet de 800px es táctil igual que un
     teléfono, y los +/− del ticket son el control que más se pulsa de toda la pantalla
     —medían 30px—. Se compensa el alto extra afinando el hueco entre ellos, para no
     robarle sitio al subtotal en la misma fila. */
  .tpv-linea-btn { width: 44px; height: 44px; }
  .tpv-linea-controls { gap: 4px; }
  .tpv-chip, .tpv-vaciar, .tpv-modo-btn { min-height: 44px; display: inline-flex; align-items: center; }
}

/* ---- Vista placeholder (módulos aún en construcción, p. ej. Cierre-Apertura) ---- */
.placeholder-view {
  flex: 1; min-height: 0; display: flex; flex-direction: column;
  align-items: center; justify-content: center; text-align: center;
  gap: 12px; padding: 32px; color: hsl(var(--muted-foreground));
}
.placeholder-view .placeholder-icon { font-size: 3rem; }
.placeholder-view h2 { font-size: 1.3rem; color: hsl(var(--foreground)); }
.placeholder-view p { max-width: 420px; line-height: 1.5; }

/* ===========================================================================
   FASE 30 — FICHA DE TERCERO (.ter-view)
   ---------------------------------------------------------------------------
   Bloque AUTÓNOMO, a propósito. Todo el CSS del módulo de documentos
   (styles.css:795-~1850) está scopeado bajo `.docs-view` mediante un truco de
   comentarios, así que `.form-section`, `.form-grid`, `.line-item`, `.badge` o
   `.nif-status` NO aplican fuera de esa vista. En lugar de des-scoparlos —lo que
   arriesgaría romper Documentos— aquí se duplican con prefijo `.ter-`.

   Las clases NO pueden contener la subcadena `.tab`: el patch de
   querySelectorAll (app.js:60) fuerza ámbito global a esos selectores y
   rompería la barra de pestañas del workspace.
   =========================================================================== */

/* Tokens propios: --success, --warning, --destructive y --popover solo existen
   dentro de .docs-view (styles.css:765-778). Sin declararlos aquí,
   hsl(var(--success)) sería un color inválido. */
.ter-view {
  --success: 142 71% 38%;
  --warning: 38 92% 50%;
  --destructive: 0 72% 51%;
  --info: 208 88% 40%;
  --secondary: 240 5% 96%;
  --popover: 0 0% 100%;
}
:root[data-theme="dark"] .ter-view {
  --success: 142 60% 45%;
  --warning: 38 92% 55%;
  --destructive: 0 70% 55%;
  --info: 208 90% 62%;
  --secondary: 222 14% 22%;
  --popover: 222 16% 15%;
}
.ter-view, .ter-view *, .ter-view *::before, .ter-view *::after { box-sizing: border-box; }

.ter-view {
  display: flex; flex-direction: column; gap: 14px;
  padding: 18px; overflow-y: auto; height: 100%;
}

/* ---------------------------------------------------------------- cabecera */
.ter-cabecera {
  display: flex; align-items: center; gap: 16px; flex-wrap: wrap;
  padding: 14px 18px; border-radius: var(--radius);
  background: hsl(var(--card)); border: 1px solid hsl(var(--border));
}
.ter-identidad { min-width: 220px; flex: 1 1 260px; }
.ter-identidad h2 { font-size: 1.15rem; margin: 0; color: hsl(var(--foreground)); }
.ter-subtitulo { display: flex; gap: 12px; margin-top: 4px; flex-wrap: wrap; }
.ter-mono { font-family: var(--mono); font-size: .82rem; color: hsl(var(--muted-foreground)); }
.ter-acciones { display: flex; gap: 8px; margin-left: auto; }

/* Chips de rol: qué ES este tercero (requisito 8 del encargo) */
.ter-chips { display: flex; gap: 6px; flex-wrap: wrap; }
.ter-chip {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 3px 10px; border-radius: 999px; font-size: .76rem; font-weight: 600;
  background: hsl(var(--primary) / .1); color: hsl(var(--primary));
  border: 1px solid hsl(var(--primary) / .25);
}
.ter-chip--cliente   { background: hsl(var(--success) / .12); color: hsl(var(--success));
                       border-color: hsl(var(--success) / .3); }
.ter-chip--proveedor { background: hsl(var(--info) / .12); color: hsl(var(--info));
                       border-color: hsl(var(--info) / .3); }
.ter-chip--acreedor  { background: hsl(var(--warning) / .15); color: hsl(38 92% 32%);
                       border-color: hsl(var(--warning) / .35); }
.ter-chip--agente    { background: hsl(280 60% 50% / .12); color: hsl(280 60% 45%);
                       border-color: hsl(280 60% 50% / .3); }
.ter-chip--neutro    { background: hsl(var(--muted)); color: hsl(var(--muted-foreground));
                       border-color: hsl(var(--border)); }
.ter-chip small { font-weight: 400; opacity: .8; }
:root[data-theme="dark"] .ter-chip--acreedor { color: hsl(38 92% 65%); }

/* ------------------------------------------------- conversión a rol --------
   Botones "Hacer cliente / proveedor / acreedor / agente" de la cabecera. Son
   el negativo de los chips de arriba: se ofrece lo que el tercero AÚN no es.

   Dos presentaciones, las dos pintadas siempre por terPintarConversiones(): los
   botones sueltos mandan en escritorio y el desplegable a ≤820px. La conmutación
   es SOLO CSS (abajo, en el bloque responsive) para no atarla a un listener de
   resize. El menú deriva de .doc-actions-menu / .doc-actions-item.

   Nada de selectores por id: openTab() sufija todos los ids con _{tabId}, así
   que #ter-conv-toggle no casaría con ter-conv-toggle_3.                     */
.ter-conv { display: inline-flex; gap: 6px; }
.ter-conv-botones { display: flex; gap: 6px; flex-wrap: wrap; }
.ter-conv-toggle { display: none; }            /* escritorio: mandan los botones */
.ter-conv-menu {
  min-width: 220px; padding: 6px;
  display: flex; flex-direction: column; gap: 2px;
  background: hsl(var(--card)); border: 1px solid hsl(var(--border));
  border-radius: 12px; box-shadow: 0 12px 32px rgba(0, 0, 0, .22);
}
.ter-conv-item {
  display: flex; align-items: center; gap: 10px; width: 100%;
  text-align: left; padding: 10px 12px; border: none; border-radius: 8px;
  cursor: pointer; background: transparent; color: hsl(var(--foreground));
  font-size: .9rem; font-family: inherit;
}
.ter-conv-item:hover:not([disabled]) { background: hsl(var(--muted) / .7); }
.ter-conv-item[disabled] { opacity: .55; cursor: default; }
.ter-conv-icono { width: 1.2em; text-align: center; flex-shrink: 0; }
/* Marca "ya lo es" del menú: empujada a la derecha, en mono como los códigos. */
.ter-conv-ya {
  margin-left: auto; font-family: var(--mono); font-size: .78rem;
  color: hsl(var(--success));
}

/* ------------------------------------------------------------------ avisos */
.ter-avisos:empty { display: none; }
.ter-aviso {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 10px 14px; border-radius: var(--radius); font-size: .87rem;
  border: 1px solid; margin-bottom: 8px;
}
.ter-aviso--ojo   { background: hsl(var(--warning) / .1); border-color: hsl(var(--warning) / .4);
                    color: hsl(38 92% 28%); }
.ter-aviso--error { background: hsl(var(--destructive) / .1); border-color: hsl(var(--destructive) / .4);
                    color: hsl(var(--destructive)); }
.ter-aviso--ok    { background: hsl(var(--success) / .1); border-color: hsl(var(--success) / .4);
                    color: hsl(var(--success)); }
.ter-aviso--info  { background: hsl(var(--info) / .1); border-color: hsl(var(--info) / .35);
                    color: hsl(var(--info)); }
.ter-aviso button { margin-left: auto; flex-shrink: 0; }
:root[data-theme="dark"] .ter-aviso--ojo { color: hsl(38 92% 68%); }

/* -------------------------------------------------------------- sub-pestañas */
/* BARRA DE PESTAÑAS DE UNA FICHA — definición ÚNICA para las tres fichas de
   entidad: Tercero (.ter-*), Artículo (.art-*) y las cuatro de rol (.rol-*).
   Antes estaba escrita dos veces, regla por regla, y ya habían empezado a
   separarse; la tercera copia habría sido la definitiva.

   Los tres prefijos existen —y no una clase `.tab` única— porque el parche de
   querySelectorAll (app.js:60) fuerza ámbito global a cualquier selector que
   contenga `.tab`, y estas vistas se clonan por pestaña de trabajo. `-tab` se
   libra; `.tab` no.

   `flex: 0 0 auto` NO es redundante: es la corrección del bug por el que la barra
   desaparecía entera estando en «General». .ter-view es un flex column de altura
   definida (height:100% + overflow-y:auto), así que cuando el panel activo no cabe
   hay espacio NEGATIVO que repartir. Por la regla del automatic minimum size, un
   ítem flex cuyo overflow no es `visible` tiene mínimo automático 0: esta barra es
   `overflow-x:auto`, o sea el ÚNICO hijo que puede encogerse —.ter-cabecera y
   .ter-panels son overflow:visible y su mínimo es su altura de contenido—, así que
   se comía ella sola todo el recorte y se aplastaba a 0px. Ocurría solo en General
   porque es el panel más alto con diferencia (~23 campos), y peor a ≤640px, donde
   .ter-grid pasa a una columna. Sin esta línea el bug vuelve. */
.ter-barra, .art-barra, .rol-barra, .pry-barra, .prd-barra {
  display: flex; gap: 2px; overflow-x: auto; scrollbar-width: thin;
  flex: 0 0 auto;
  border-bottom: 1px solid hsl(var(--border));
}
.ter-tab, .art-tab, .rol-tab, .pry-tab, .prd-tab {
  padding: 9px 16px; border: none; background: transparent; cursor: pointer;
  font-size: .9rem; font-weight: 500; white-space: nowrap;
  color: hsl(var(--muted-foreground));
  border-bottom: 2px solid transparent; transition: color .15s, border-color .15s;
}
.ter-tab:hover, .art-tab:hover, .rol-tab:hover, .pry-tab:hover, .prd-tab:hover { color: hsl(var(--foreground)); }
.ter-tab--activa, .art-tab--activa, .rol-tab--activa, .pry-tab--activa, .prd-tab--activa
  { color: hsl(var(--primary)); border-bottom-color: hsl(var(--primary)); }
.ter-num {
  display: inline-block; min-width: 18px; padding: 0 5px; margin-left: 4px;
  border-radius: 999px; background: hsl(var(--muted)); font-size: .72rem;
  color: hsl(var(--muted-foreground));
}
.ter-num:empty { display: none; }

.ter-panel, .art-panel, .rol-panel, .pry-panel, .prd-panel { display: none; padding-top: 14px; }
.ter-panel--activo, .art-panel--activo, .rol-panel--activo, .pry-panel--activo, .prd-panel--activo { display: block; }
/* En la ficha de ROL el panel es ADEMÁS la tarjeta (`.rol-seccion`), y su
   `padding: 16px` gana al `padding-top` de arriba por ir después en el archivo:
   el aire bajo la barra de pestañas se da aquí, en el contenedor. */
.rol-panels { padding-top: 14px; }

/* ---------------------------------------------------------------- secciones */
.ter-seccion {
  background: hsl(var(--card)); border: 1px solid hsl(var(--border));
  border-radius: var(--radius); padding: 16px; margin-bottom: 14px;
}
.ter-seccion-cab { display: flex; align-items: center; gap: 12px; margin-bottom: 12px; }
.ter-seccion-cab .ter-seccion-titulo { margin: 0; border: none; padding: 0; }
.ter-seccion-cab button { margin-left: auto; }
.ter-seccion-titulo {
  font-size: .95rem; font-weight: 600; margin: 0 0 12px;
  padding-bottom: 8px; border-bottom: 1px solid hsl(var(--border));
  color: hsl(var(--foreground));
}
.ter-ayuda { font-size: .82rem; color: hsl(var(--muted-foreground)); margin: 0 0 10px; }

/* ------------------------------------------------------------------- campos */
.ter-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); gap: 12px; }
.ter-campo { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.ter-campo > span { font-size: .8rem; font-weight: 500; color: hsl(var(--muted-foreground)); }
.ter-campo > span em { color: hsl(var(--destructive)); font-style: normal; }
.ter-campo input, .ter-campo select, .ter-campo textarea {
  padding: 8px 10px; border: 1px solid hsl(var(--input)); border-radius: 8px;
  background: hsl(var(--background)); color: hsl(var(--foreground));
  font-size: .9rem; font-family: inherit; width: 100%; min-width: 0;
}
.ter-campo input:focus, .ter-campo select:focus, .ter-campo textarea:focus {
  outline: none; border-color: hsl(var(--primary)); box-shadow: 0 0 0 3px hsl(var(--primary) / .12);
}
.ter-campo input[readonly] { background: hsl(var(--muted)); color: hsl(var(--muted-foreground)); }
.ter-campo small { font-size: .74rem; color: hsl(var(--muted-foreground)); }
.ter-campo--ancho { grid-column: span 2; }
.ter-campo--total { grid-column: 1 / -1; }
.ter-campo--corto { max-width: 150px; }
.ter-campo--error input, .ter-campo--error select, .ter-campo--error textarea {
  border-color: hsl(var(--destructive)); box-shadow: 0 0 0 3px hsl(var(--destructive) / .12);
}
.ter-campo--error > span { color: hsl(var(--destructive)); }
.ter-campo textarea { resize: vertical; }

/* Semáforo del NIF, alimentado por SP_VALIDA_NIF */
.ter-nif-estado { min-height: 1.1em; font-size: .76rem; }
.ter-nif-estado.es-ok       { color: hsl(var(--success)); }
.ter-nif-estado.es-error    { color: hsl(var(--destructive)); }
.ter-nif-estado.es-cargando { color: hsl(var(--muted-foreground)); }
.ter-nif-estado.es-aviso    { color: hsl(38 92% 32%); }
:root[data-theme="dark"] .ter-nif-estado.es-aviso { color: hsl(38 92% 65%); }

/* ---------------------------------------- CAMPO + BOTÓN DE CATÁLOGO AUXILIAR */
/* Patrón ÚNICO de la APP para «este campo depende de un catálogo y aquí se
   gestiona». Lo usan la ficha de Tercero, la de Artículo y los modales de
   Familia y Almacén, así que las clases no llevan prefijo: antes existían tres
   versiones (.ter-btn-mas con «+», .art-btn-mas con «＋» y .art-btn-config con
   la palabra «Configuración»), dos de ellas idénticas carácter por carácter.

   Es un botón de SOLO ICONO (⚙): el rótulo viaja en title y aria-label. El
   borde discontinuo lo distingue de un botón de acción normal —no guarda ni
   envía nada, abre una gestión— y `align-items: stretch` le da exactamente la
   altura del <select> que acompaña. */
.campo-fila { display: flex; gap: 6px; align-items: stretch; }
.campo-fila > input, .campo-fila > select { flex: 1 1 auto; min-width: 0; }
.btn-catalogo {
  flex: 0 0 auto; width: 34px; min-height: 38px; border-radius: 8px; cursor: pointer;
  border: 1px dashed hsl(var(--primary) / .5); background: hsl(var(--primary) / .06);
  color: hsl(var(--primary)); font-size: 1.05rem; line-height: 1;
  display: inline-flex; align-items: center; justify-content: center;
}
.btn-catalogo:hover { background: hsl(var(--primary) / .14); border-style: solid; }
.btn-catalogo:focus-visible { outline: 2px solid hsl(var(--info)); outline-offset: 2px; }
/* En móvil NO se va a ancho completo (eso hacía el antiguo botón de texto, que
   empujaba el campo siguiente): se queda al lado del campo con el ancho mínimo
   táctil, el mismo criterio que .agr-btn. */
@media (max-width: 640px) { .btn-catalogo { width: 44px; min-height: 44px; } }

/* ------------------------------------------ sub-rejillas (dir./cont./bancos) */
.ter-lista { display: flex; flex-direction: column; gap: 10px; }
.ter-lista:empty::after {
  content: "No hay registros todavía."; display: block; padding: 18px;
  text-align: center; color: hsl(var(--muted-foreground)); font-size: .87rem;
  border: 1px dashed hsl(var(--border)); border-radius: var(--radius);
}
.ter-fila {
  border: 1px solid hsl(var(--border)); border-radius: var(--radius);
  padding: 12px; background: hsl(var(--background));
}
.ter-fila--defecto { border-color: hsl(var(--primary) / .45); background: hsl(var(--primary) / .04); }
.ter-fila-cab { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; flex-wrap: wrap; }
.ter-fila-titulo { font-weight: 600; font-size: .9rem; color: hsl(var(--foreground)); }
.ter-fila-resumen { font-size: .8rem; color: hsl(var(--muted-foreground)); }
.ter-fila-acciones { display: flex; gap: 6px; margin-left: auto; }
.ter-btn-icono {
  border: 1px solid hsl(var(--border)); background: hsl(var(--card));
  border-radius: 7px; padding: 4px 9px; cursor: pointer; font-size: .8rem;
  color: hsl(var(--muted-foreground));
}
.ter-btn-icono:hover { border-color: hsl(var(--primary)); color: hsl(var(--primary)); }
.ter-btn-icono--peligro:hover { border-color: hsl(var(--destructive)); color: hsl(var(--destructive)); }
.ter-btn-icono[disabled] { opacity: .45; cursor: not-allowed; }
/* Oficinas FACe de la dirección en su fila: se lee como dato, no como texto gris. */
.ter-fila-face { color: hsl(var(--foreground)); margin-top: 4px; }

/* ------------------------------------------------ modal de DIRECCIÓN (#modal-direccion)
   Hereda el esqueleto de .modal-crud-box; más ancho (como .fac-traspaso-box) porque
   lleva la dirección postal entera más las cuatro parejas de oficinas FACe. Cada
   pareja es una fila «Oficina N · rol · código» para que la relación entre el rol y
   su código quede a la vista; a ≤640px se apila (etiqueta, rol, código). */
.modal-direccion-box { max-width: 680px; }
.modal-direccion-box .art-seccion-titulo { margin-top: 4px; }
.modal-direccion-box .art-grid + .art-seccion-titulo { margin-top: 10px; }
.dir-oficinas { display: flex; flex-direction: column; gap: 8px; }
.dir-oficina {
  display: grid; grid-template-columns: 84px minmax(0, 1.4fr) minmax(0, 1fr);
  gap: 8px; align-items: center;
}
.dir-oficina-titulo { font-size: .8rem; font-weight: 500; color: hsl(var(--muted-foreground)); }
.dir-oficina select, .dir-oficina input {
  padding: 8px 10px; border: 1px solid hsl(var(--input)); border-radius: 8px;
  background: hsl(var(--background)); color: hsl(var(--foreground));
  font-size: .9rem; font-family: inherit; width: 100%; min-width: 0;
}
.dir-oficina select:focus, .dir-oficina input:focus {
  outline: none; border-color: hsl(var(--primary)); box-shadow: 0 0 0 3px hsl(var(--primary) / .12);
}
/* Los códigos DIR3 son mayúsculas (LA0016544): se ve así mientras se escribe; el
   valor real lo pone en mayúsculas guardarDireccion() y lo revalida el backend. */
.dir-oficina input { text-transform: uppercase; }
.dir-oficina input::placeholder { text-transform: none; }
.dir-oficina.art-campo--error select, .dir-oficina.art-campo--error input {
  border-color: hsl(var(--destructive)); box-shadow: 0 0 0 3px hsl(var(--destructive) / .12);
}
.dir-oficina.art-campo--error .dir-oficina-titulo { color: hsl(var(--destructive)); }
@media (max-width: 640px) {
  .dir-oficina { grid-template-columns: 1fr; gap: 6px; }
  .dir-oficina + .dir-oficina { padding-top: 8px; border-top: 1px dashed hsl(var(--border)); }
}

/* ------------------------------------------------ modal de CUENTA BANCARIA (#modal-banco)
   Misma caja que el de dirección. Los cuatro tramos del CCC (entidad, sucursal,
   control, cuenta) van en una sola fila para que se lean como la cuenta que son;
   a ≤640px pasan a dos filas de dos. El contenedor lleva también .art-campo (para
   ocupar la fila entera del .art-grid), cuya regla display:flex está MÁS ABAJO en
   este fichero: se necesita la doble clase para ganarle. */
.art-campo.ban-ccc {
  display: grid; grid-template-columns: 1fr 1fr 64px 1.6fr; gap: 8px; align-items: end;
}
.ban-ccc .art-campo input { font-family: 'JetBrains Mono', monospace; letter-spacing: .04em; }
@media (max-width: 640px) {
  .art-campo.ban-ccc { grid-template-columns: 1fr 1fr; }
}

/* -------------------------------------------------------------------- roles */
.ter-roles-detalle { display: flex; flex-direction: column; gap: 8px; margin-bottom: 14px; }
.ter-rol-linea {
  display: flex; align-items: center; gap: 10px; padding: 9px 12px;
  border: 1px solid hsl(var(--border)); border-radius: 9px; font-size: .87rem;
  background: hsl(var(--background));
}
.ter-rol-linea .ter-mono { margin-left: auto; }
.ter-roles-alta { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.ter-roles-alta select {
  padding: 8px 10px; border: 1px solid hsl(var(--input)); border-radius: 8px;
  background: hsl(var(--background)); color: hsl(var(--foreground)); font-size: .9rem;
}

/* --------------------------------------------- combo con búsqueda (combo.js) */
/* Anclaje.abrir() fuerza display:flex al mostrar el panel, así que la dirección
   de columna es obligatoria: sin ella las opciones se colocarían en fila. */
.ter-combo-panel {
  flex-direction: column;
  max-height: 300px; overflow-y: auto; min-width: 240px; padding: 4px;
  background: hsl(var(--popover)); border: 1px solid hsl(var(--border));
  border-radius: var(--radius); box-shadow: 0 10px 30px hsl(240 21% 15% / .16);
}
.ter-combo-opcion {
  padding: 7px 10px; border-radius: 7px; cursor: pointer; font-size: .87rem;
  color: hsl(var(--foreground));
}
.ter-combo-opcion:hover, .ter-combo-opcion.es-activa { background: hsl(var(--primary) / .1); }
.ter-combo-opcion small { display: block; font-size: .75rem; color: hsl(var(--muted-foreground)); }
.ter-combo-vacio { padding: 12px; text-align: center; font-size: .83rem;
                   color: hsl(var(--muted-foreground)); }
.ter-combo-crear {
  padding: 8px 10px; border-radius: 7px; cursor: pointer; font-size: .85rem;
  color: hsl(var(--primary)); font-weight: 600;
  border-top: 1px solid hsl(var(--border)); margin-top: 4px;
}
.ter-combo-crear:hover { background: hsl(var(--primary) / .1); }

/* El panel del combo se cuelga del clon de la pestaña, pero Anclaje lo posiciona
   con position:fixed, así que necesita su propia capa. Misma que .panel-anclado. */
.ter-combo-panel { z-index: var(--z-panel-anclado); }

/* --------------------------------------------------------------- responsive */
@media (max-width: 1100px) {
  .ter-campo--ancho { grid-column: span 1; }
}
@media (max-width: 820px) {
  .ter-view { padding: 12px; }
  .ter-grid { grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); }
  .ter-acciones { width: 100%; margin-left: 0; }
  /* Solo los HIJOS DIRECTOS se reparten el ancho. Con `.ter-acciones button` a
     secas se estiraban también los ítems del menú de conversión, que cuelgan de
     .ter-conv-menu y deben conservar su altura de lista. */
  .ter-acciones > button, .ter-acciones > .ter-conv { flex: 1; }
  /* Cuatro botones más Cancelar y Guardar no caben: se colapsan en el menú. */
  .ter-conv-botones { display: none; }
  .ter-conv-toggle { display: inline-flex; justify-content: center; width: 100%; }
}
@media (max-width: 640px) {
  .ter-grid { grid-template-columns: 1fr; }
  .ter-campo--corto { max-width: none; }
  .ter-cabecera { padding: 12px; }
  .ter-seccion { padding: 12px; }
  .ter-fila-acciones { width: 100%; margin-left: 0; }
  /* Las cinco pestañas miden ~590px y no caben en un teléfono: la barra se
     arrastra (overflow-x:auto, arriba). Se recorta el acolchado para que entren
     más por pantalla y se añade inercia en iOS. A la pestaña activa la acerca
     terActivarPanel() con scrollLeft, que es lo que evita que quede escondida
     cuando el panel se activa por código. */
  .ter-tab { padding: 9px 12px; }
  .ter-barra { -webkit-overflow-scrolling: touch; }
}
/* ===========================================================================
   FASE 32 — FICHA DE ARTÍCULO Y MODAL DE FAMILIA (.art-view / #modal-familia)
   ---------------------------------------------------------------------------
   Deriva del bloque .ter-* de la Fase 30 (styles.css:2549+) con el mismo
   criterio: bloque AUTÓNOMO, porque el CSS de Documentos está scopeado bajo
   `.docs-view` y sus utilidades no aplican aquí.

   Las clases NO pueden contener la subcadena `.tab`: el patch de
   querySelectorAll (app.js:60) fuerza ámbito global a esos selectores y
   rompería la barra de pestañas del workspace. De ahí `.art-tab`/`.art-panel`.

   Los selectores .art-* NO se limitan a `.art-view` porque el modal de familia
   los reutiliza y vive fuera de la vista, colgando de <body>.
   =========================================================================== */

/* Tokens propios: --success, --warning, --destructive y --info solo existen
   dentro de .docs-view y .ter-view. Se declaran también en el modal para que
   hsl(var(--destructive)) del asterisco de obligatorio sea un color válido. */
.art-view, #modal-familia, #modal-maestro {
  --success: 142 71% 38%;
  --warning: 38 92% 50%;
  --destructive: 0 72% 51%;
  --info: 208 88% 40%;
}
:root[data-theme="dark"] .art-view,
:root[data-theme="dark"] #modal-familia,
:root[data-theme="dark"] #modal-maestro {
  --success: 142 60% 45%;
  --warning: 38 92% 55%;
  --destructive: 0 70% 55%;
  --info: 208 90% 62%;
}
.art-view, .art-view *, .art-view *::before, .art-view *::after { box-sizing: border-box; }

.art-view {
  display: flex; flex-direction: column; gap: 14px;
  padding: 18px; overflow-y: auto; height: 100%;
}

/* ---------------------------------------------------------------- cabecera */
.art-cabecera {
  display: flex; align-items: center; gap: 16px; flex-wrap: wrap;
  padding: 14px 18px; border-radius: var(--radius);
  background: hsl(var(--card)); border: 1px solid hsl(var(--border));
}
.art-identidad { min-width: 220px; flex: 1 1 260px; }
.art-identidad h2 { font-size: 1.15rem; margin: 0; color: hsl(var(--foreground)); }
.art-subtitulo { display: flex; gap: 12px; margin-top: 4px; flex-wrap: wrap;
                 font-size: .82rem; color: hsl(var(--muted-foreground)); }
.art-mono { font-family: var(--mono); font-size: .82rem; color: hsl(var(--muted-foreground)); }
.art-acciones { display: flex; gap: 8px; margin-left: auto; }

/* Chips de estado: disponibilidad, control de stock, PVP bloqueado... */
.art-chips { display: flex; gap: 6px; flex-wrap: wrap; }
.art-chip {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 3px 10px; border-radius: 999px; font-size: .76rem; font-weight: 600;
  background: hsl(var(--primary) / .1); color: hsl(var(--primary));
  border: 1px solid hsl(var(--primary) / .25);
}
.art-chip--ok     { background: hsl(var(--success) / .12); color: hsl(var(--success));
                    border-color: hsl(var(--success) / .3); }
.art-chip--aviso  { background: hsl(var(--warning) / .15); color: hsl(38 92% 32%);
                    border-color: hsl(var(--warning) / .35); }
.art-chip--baja   { background: hsl(var(--destructive) / .12); color: hsl(var(--destructive));
                    border-color: hsl(var(--destructive) / .3); }
.art-chip--neutro { background: hsl(var(--muted)); color: hsl(var(--muted-foreground));
                    border-color: hsl(var(--border)); }
/* «Abierto» (ESTADO 0) en las rejillas de documento: el chip BLANCO que pide el
   diseño. No se usa --muted como el neutro porque ahí se vería gris, y no se pone
   #fff literal porque en tema oscuro dejaría texto negro sobre blanco en medio de
   la tabla: --card es el blanco del tema claro y el gris oscuro del oscuro, y
   --foreground lo acompaña. Por eso no necesita regla aparte para el tema oscuro. */
.art-chip--abierto { background: hsl(var(--card)); color: hsl(var(--foreground));
                     border-color: hsl(var(--border)); }
/* Los dos tonos del chip de TIPO que faltaban. El violeta no es nuevo en el
   proyecto: es el mismo de .ter-chip--agente (styles.css:2701). No se reutiliza
   --baja (rojo) para «serializado» porque ese rojo ya significa «De baja» en el
   chip de disponibilidad, que va justo al lado. */
.art-chip--info   { background: hsl(var(--info) / .12); color: hsl(var(--info));
                    border-color: hsl(var(--info) / .3); }
.art-chip--serie  { background: hsl(280 60% 50% / .12); color: hsl(280 60% 45%);
                    border-color: hsl(280 60% 50% / .3); }
:root[data-theme="dark"] .art-chip--aviso { color: hsl(38 92% 65%); }
:root[data-theme="dark"] .art-chip--serie { color: hsl(280 70% 72%); }

/* .art-chip se usa TAMBIÉN en la rejilla (columna TIPO, buildDataTable), y estos
   cuatro tokens NO son globales: se declaran por bloque en .art-view (styles.css:2946),
   .docs-view y .ter-view. Sin esto el chip saldría sin color dentro de la tabla.
   Si se cambian los valores allí, cambiarlos aquí. */
.data-table { --success: 142 71% 38%; --warning: 38 92% 50%;
              --destructive: 0 72% 51%; --info: 208 88% 40%; }
:root[data-theme="dark"] .data-table {
              --success: 142 60% 45%; --warning: 38 92% 55%;
              --destructive: 0 70% 55%; --info: 208 90% 62%; }

/* ------------------------------------------------------------------ avisos */
.art-avisos:empty { display: none; }
.art-aviso {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 10px 14px; border-radius: var(--radius); font-size: .87rem;
  border: 1px solid; margin-bottom: 8px;
}
.art-aviso--ojo   { background: hsl(var(--warning) / .1); border-color: hsl(var(--warning) / .4);
                    color: hsl(38 92% 28%); }
.art-aviso--error { background: hsl(var(--destructive) / .1); border-color: hsl(var(--destructive) / .4);
                    color: hsl(var(--destructive)); }
.art-aviso--ok    { background: hsl(var(--success) / .1); border-color: hsl(var(--success) / .4);
                    color: hsl(var(--success)); }
.art-aviso--info  { background: hsl(var(--info) / .1); border-color: hsl(var(--info) / .35);
                    color: hsl(var(--info)); }
.art-aviso button { margin-left: auto; flex-shrink: 0; }
:root[data-theme="dark"] .art-aviso--ojo { color: hsl(38 92% 68%); }

/* ------------------------------------------------------------ sub-pestañas */
/* .art-barra / .art-tab / .art-panel se definen ARRIBA, junto a .ter-* y .rol-*
   (busca «BARRA DE PESTAÑAS DE UNA FICHA»): son la misma barra y ahora se
   escriben una sola vez. Aquí no queda nada propio del artículo. */

/* ---------------------------------------------------------------- secciones */
.art-seccion {
  background: hsl(var(--card)); border: 1px solid hsl(var(--border));
  border-radius: var(--radius); padding: 16px; margin-bottom: 14px;
}
.art-seccion-titulo {
  font-size: .95rem; font-weight: 600; margin: 0 0 12px;
  padding-bottom: 8px; border-bottom: 1px solid hsl(var(--border));
  color: hsl(var(--foreground));
}
.art-ayuda { font-size: .82rem; color: hsl(var(--muted-foreground)); margin: 0 0 10px; }

/* Secciones plegables (cuentas de devolución/abono, tarifas del modal) */
.art-detalles { margin-top: 12px; }
.art-detalles > summary {
  cursor: pointer; font-size: .85rem; font-weight: 600; padding: 8px 0;
  color: hsl(var(--primary)); list-style-position: inside;
}
.art-detalles > summary:hover { text-decoration: underline; }
.art-detalles[open] > summary { margin-bottom: 10px; }

/* ------------------------------------------------------------------- campos */
.art-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); gap: 12px; }
.art-campo { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.art-campo > span { font-size: .8rem; font-weight: 500; color: hsl(var(--muted-foreground)); }
.art-campo > span em { color: hsl(var(--destructive)); font-style: normal; }
.art-campo input, .art-campo select, .art-campo textarea {
  padding: 8px 10px; border: 1px solid hsl(var(--input)); border-radius: 8px;
  background: hsl(var(--background)); color: hsl(var(--foreground));
  font-size: .9rem; font-family: inherit; width: 100%; min-width: 0;
}
.art-campo input:focus, .art-campo select:focus, .art-campo textarea:focus {
  outline: none; border-color: hsl(var(--primary)); box-shadow: 0 0 0 3px hsl(var(--primary) / .12);
}
.art-campo input[readonly] { background: hsl(var(--muted)); color: hsl(var(--muted-foreground)); }
.art-campo small { font-size: .74rem; color: hsl(var(--muted-foreground)); }
.art-campo--ancho { grid-column: span 2; }
.art-campo--total { grid-column: 1 / -1; }
.art-campo--corto { max-width: 170px; }
.art-campo--error input, .art-campo--error select, .art-campo--error textarea {
  border-color: hsl(var(--destructive)); box-shadow: 0 0 0 3px hsl(var(--destructive) / .12);
}
.art-campo--error > span { color: hsl(var(--destructive)); }
.art-campo textarea { resize: vertical; }

/* Casillas: la etiqueta va a la derecha, no encima. El input recupera su
   tamaño natural (arriba se le ha puesto width:100% a todos los inputs). */
.art-campo--check { flex-direction: row; align-items: center; gap: 8px; }
.art-campo--check input { width: auto; min-width: 0; flex: 0 0 auto; }
.art-campo--check > span { font-size: .85rem; color: hsl(var(--foreground)); }

/* Casilla deshabilitada: en un SERVICIO se apagan lotes, números de serie y ubicable.
   Se atenúa la fila entera (no solo el input) para que se lea que no es editable, con
   el mismo tratamiento que .column-item del selector de columnas. */
/* La opacidad del contenedor ya atenúa lo que lleva dentro. NO añadir otra al input:
   las opacidades se multiplican (.55 x .4 = .22) y la casilla quedaba casi invisible
   en vez de gris. Una sola regla, y el cursor en ambos. */
.art-campo--check:has(input:disabled) { opacity: .55; }
.art-campo--check:has(input:disabled),
.art-campo--check input:disabled { cursor: not-allowed; }

/* El patrón «campo + botón de catálogo» (.campo-fila / .btn-catalogo) está
   definido ARRIBA, en la zona de terceros, y lo comparten las dos fichas y los
   modales de familia y almacén. Aquí ya no hay copia propia del artículo.
   Con él se fue `.art-campo--con-boton`, que solo existía para mandar el botón
   «Configuración» a una línea propia en móvil: el ⚙ ya no lo necesita. */

/* Valores de solo lectura (PVP calculado, códigos de barras) */
.art-output {
  display: block; padding: 8px 10px; border-radius: 8px;
  background: hsl(var(--muted)); color: hsl(var(--muted-foreground));
  font-family: var(--mono); font-size: .86rem; min-height: 36px;
}

/* Campo de solo lectura dentro de un formulario dinámico: la clave primaria al
   editar (los triggers _BU0 la congelan) y los valores que recalcula un trigger,
   como la descripción de un plazo de garantía. */
.art-campo--ro > input { background: hsl(var(--muted) / .6); color: hsl(var(--muted-foreground)); }

/* ---------- Mantenimiento de catálogo auxiliar (botón «Configuración») -------- */
/* Reutiliza .data-table para que la lista se vea igual que las rejillas del ERP.
   El alto está acotado y el scroll es interno: algunos catálogos traen 16 filas y
   el modal no debe crecer hasta salirse de la pantalla. */
.cat-lista { margin-bottom: 16px; }
.cat-tabla-wrap {
  max-height: 40vh; overflow: auto;
  border: 1px solid hsl(var(--border)); border-radius: var(--radius);
  background: hsl(var(--card));
}
.cat-tabla-wrap .data-table { font-size: .82rem; }
.cat-tabla-wrap .data-table tbody td { padding: 7px 12px; }
.cat-tabla-wrap .data-table thead th { padding: 8px 12px; }
/* Los ✏️/🗑️ deshabilitados llevan el motivo en el title: se atenúan pero se dejan
   visibles y enfocables, para que se pueda leer por qué no están disponibles. */
.cat-tabla-wrap .btn-row-edit[disabled],
.cat-tabla-wrap .btn-row-del[disabled] { opacity: .35; cursor: not-allowed; }
.cat-subtitulo {
  margin: 4px 0 10px; font-size: .9rem; font-weight: 600;
  color: hsl(var(--foreground));
}
/* Móvil: la lista del catálogo se ve en tarjetas (.data-table ≤640) y el modal es una
   hoja inferior con scroll propio en .modal-crud-body. El tope de 40vh con overflow
   metía un SEGUNDO scroll dentro de la hoja, y el padding lateral de 12 px de arriba
   (más específico que el de tarjeta, 6px 0) descuadraba el valor de cada celda. */
@media (max-width: 640px) {
  .cat-tabla-wrap { max-height: none; overflow: visible; border: none; background: transparent; }
  .cat-tabla-wrap .data-table { font-size: .875rem; }
  .cat-tabla-wrap .data-table tbody td { padding: 6px 0; }
}

/* Listas de solo lectura (fichas de almacén) */
.art-lista { display: flex; flex-direction: column; gap: 6px; }
.art-lista-item {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 8px 12px; border: 1px solid hsl(var(--border));
  border-radius: 8px; background: hsl(var(--background)); font-size: .86rem;
}
.art-lista-item small { margin-left: auto; color: hsl(var(--muted-foreground)); }
.art-lista-vacia { font-size: .82rem; color: hsl(var(--muted-foreground)); font-style: italic; }

.art-fila-acciones { display: flex; gap: 8px; margin-top: 12px; flex-wrap: wrap; }

/* Tabla de stock por almacén (bloque «Stock y almacén» de la ficha de artículo).
   El scroll es del contenedor, no de la página: con varios almacenes la tabla se
   sale de ancho en móvil y no debe arrastrar toda la ficha. */
.art-tabla-scroll { overflow-x: auto; }
.art-tabla-stock {
  width: 100%; border-collapse: collapse; font-size: .86rem; min-width: 420px;
}
.art-tabla-stock th, .art-tabla-stock td {
  padding: 8px 10px; border-bottom: 1px solid hsl(var(--border)); text-align: left;
}
.art-tabla-stock thead th {
  font-size: .74rem; text-transform: uppercase; letter-spacing: .04em;
  color: hsl(var(--muted-foreground)); font-weight: 600;
}
.art-tabla-stock .art-col-num { text-align: right; }
.art-tabla-stock tfoot td {
  border-bottom: 0; border-top: 2px solid hsl(var(--border)); font-weight: 600;
}
.art-tabla-stock .art-input-num {
  width: 100px; text-align: right; padding: 5px 8px;
  border: 1px solid hsl(var(--border)); border-radius: 6px;
  background: hsl(var(--background)); color: hsl(var(--foreground));
}
.art-tabla-stock .art-input-num:disabled {
  background: hsl(var(--muted)); color: hsl(var(--muted-foreground));
}
.art-acciones-bloque {
  display: flex; align-items: center; gap: 12px; margin-top: 12px; flex-wrap: wrap;
}

/* Pestaña «Agrupaciones»: las dos listas (pertenece / disponibles) lado a lado en
   PC y apiladas en móvil (el corte de 820 es el mismo del resto de la ficha, abajo).
   La tabla reutiliza .art-tabla-stock pero SIN su min-width de 420px: son tres
   columnas cortas y en media columna ese mínimo forzaría un scroll horizontal
   inútil. La fila entera es clicable (doble clic) y lo dice el cursor; el botón
   de la fila es el mismo gesto para el dedo (.btn-row-edit/.btn-row-del ya suben
   a 44px en ≤640px, styles.css al final). */
.art-dos-columnas { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
.art-dos-columnas > .art-seccion { margin-bottom: 0; min-width: 0; }
.art-tabla-stock--agr { min-width: 0; }
.art-tabla-stock--agr tbody tr { cursor: pointer; }
/* Solo con ratón: en un teléfono el :hover se queda pegado a la última fila tocada. */
@media (hover: hover) {
  .art-tabla-stock--agr tbody tr:hover td { background: hsl(var(--muted) / .5); }
}
.art-tabla-stock--agr td.art-col-accion,
.art-tabla-stock--agr th.art-col-accion { width: 1%; text-align: center; padding: 2px 4px; }
/* El título envuelve (el `tbody td { white-space: nowrap }` global lo impediría y en
   un móvil «REPARACION EQUIPOS Y PERIFERICOS» forzaría scroll); el código, no. */
.art-tabla-stock--agr td { white-space: normal; }
.art-tabla-stock--agr td.art-col-codigo { white-space: nowrap; width: 1%; }
@media (max-width: 820px) {
  .art-dos-columnas { grid-template-columns: 1fr; }
}

/* --------------------------------------------------------------- responsive
   Mismos cortes que la ficha de tercero (styles.css:2822+): 1100 / 820 / 640.
   El modal de familia no necesita reglas propias: hereda el bottom-sheet de
   .modal-crud-box (styles.css:2313) y aquí solo colapsa su rejilla. */
@media (max-width: 1100px) {
  .art-campo--ancho { grid-column: span 1; }
}
@media (max-width: 820px) {
  .art-view { padding: 12px; }
  .art-grid { grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); }
  .art-acciones { width: 100%; margin-left: 0; }
  .art-acciones > button { flex: 1; }
}
@media (max-width: 640px) {
  .art-grid { grid-template-columns: 1fr; }
  .art-campo--corto { max-width: none; }
  .art-cabecera { padding: 12px; }
  .art-seccion { padding: 12px; }
  .art-fila-acciones { width: 100%; }
  .art-fila-acciones > button { flex: 1; }
}

/* ===========================================================================
   FASE 33 — FICHA DE ROL: CLIENTE / PROVEEDOR / ACREEDOR / AGENTE (.rol-view)
   ---------------------------------------------------------------------------
   Deriva del bloque de la ficha de tercero (.ter-view, arriba) y repite sus dos
   restricciones:

   · Los tokens --success/--warning/--destructive/--info solo existen dentro de
     .docs-view, así que hay que declararlos también aquí o hsl(var(--success))
     sería un color inválido.
   · Ninguna clase puede contener la subcadena `.tab`: el patch de
     querySelectorAll (app.js:60) fuerza ámbito global a esos selectores.

   El formulario lo genera rolPintarGrupos() desde la definición del backend,
   pero el markup que emite usa EXACTAMENTE estas clases, con la misma rejilla
   auto-fit que .ter-grid, así que el responsive se comporta igual que el resto
   de fichas sin necesidad de JS.
   =========================================================================== */
.rol-view {
  --success: 142 71% 38%;
  --warning: 38 92% 50%;
  --destructive: 0 72% 51%;
  --info: 208 88% 40%;
}
:root[data-theme="dark"] .rol-view {
  --success: 142 60% 45%;
  --warning: 38 92% 55%;
  --destructive: 0 70% 55%;
  --info: 208 90% 62%;
}
.rol-view, .rol-view *, .rol-view *::before, .rol-view *::after { box-sizing: border-box; }

.rol-view {
  display: flex; flex-direction: column; gap: 14px;
  padding: 18px; overflow-y: auto; height: 100%;
}

/* ---------------------------------------------------------------- cabecera */
.rol-cabecera {
  display: flex; align-items: center; gap: 16px; flex-wrap: wrap;
  padding: 14px 18px; border-radius: var(--radius);
  background: hsl(var(--card)); border: 1px solid hsl(var(--border));
}
.rol-identidad { min-width: 220px; flex: 1 1 260px; }
.rol-identidad h2 { font-size: 1.15rem; margin: 0; color: hsl(var(--foreground)); }
/* Empresa · ejercicio · canal SIEMPRE a la vista: los campos contables se
   guardan por ejercicio y canal, y hay que poder ver cuál se está editando. */
.rol-contexto {
  margin-top: 4px; font-size: .8rem; color: hsl(var(--muted-foreground));
  font-family: var(--mono);
}
.rol-acciones { display: flex; gap: 8px; margin-left: auto; }

/* -------------------------------------------------------- tercero asociado */
/* Se muestra y se ENLAZA, pero no se edita: los triggers EMP_<ROL>_BU0 hacen
   «new.tercero = old.tercero», así que un campo editable aceptaría el cambio y
   la BD lo descartaría en silencio (ver roles.py). */
.rol-tercero {
  display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
  padding: 12px 18px; border-radius: var(--radius);
  background: hsl(var(--primary) / .05);
  border: 1px solid hsl(var(--primary) / .22);
}
.rol-tercero-etq {
  font-size: .78rem; font-weight: 600; text-transform: uppercase;
  letter-spacing: .04em; color: hsl(var(--muted-foreground));
}
/* Aspecto de enlace, no de botón: es la convención del SOLID de escritorio y
   lo que hace evidente que lleva a otra ficha. */
.rol-tercero-link {
  padding: 0; border: none; background: none; cursor: pointer;
  font: inherit; font-weight: 600; font-size: .95rem;
  color: hsl(var(--info)); text-decoration: none;
}
.rol-tercero-link::after { content: " ↗"; font-size: .85em; }
.rol-tercero-link:hover { text-decoration: underline; }
.rol-tercero-link:focus-visible {
  outline: 2px solid hsl(var(--info)); outline-offset: 2px; border-radius: 4px;
}
.rol-tercero-datos { font-size: .84rem; color: hsl(var(--muted-foreground)); }

/* ------------------------------------------------------------------ avisos */
.rol-avisos:empty { display: none; }
.rol-aviso {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 10px 14px; border-radius: var(--radius); font-size: .87rem;
  border: 1px solid; margin-bottom: 8px;
}
.rol-aviso--aviso { background: hsl(var(--warning) / .1); border-color: hsl(var(--warning) / .4);
                    color: hsl(38 92% 28%); }
.rol-aviso--error { background: hsl(var(--destructive) / .1); border-color: hsl(var(--destructive) / .4);
                    color: hsl(var(--destructive)); }
.rol-aviso--info  { background: hsl(var(--info) / .1); border-color: hsl(var(--info) / .35);
                    color: hsl(var(--info)); }
:root[data-theme="dark"] .rol-aviso--aviso { color: hsl(38 92% 68%); }

/* ---------------------------------------------------------------- secciones */
.rol-seccion {
  background: hsl(var(--card)); border: 1px solid hsl(var(--border));
  border-radius: var(--radius); padding: 16px; margin-bottom: 14px;
}
.rol-seccion-titulo {
  display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
  font-size: .95rem; font-weight: 600; margin: 0 0 12px;
  padding-bottom: 8px; border-bottom: 1px solid hsl(var(--border));
  color: hsl(var(--foreground));
}
/* «solo ejercicio 2026 · canal 1» en los grupos que viven en
   CON_CUENTAS_GES_*, para que nadie crea que edita todos los ejercicios. */
.rol-seccion-nota {
  font-size: .74rem; font-weight: 500; color: hsl(var(--muted-foreground));
  background: hsl(var(--muted)); padding: 2px 8px; border-radius: 999px;
}

/* -------------------------------------------------------------- agrupaciones */
/* Bloque «Agrupaciones», COMPARTIDO por la ficha de rol (cliente / proveedor /
   agente) y la pestaña de la ficha de artículo: los dos llevan `.agr-bloque` y
   se comportan igual. Por eso las clases son `agr-` y no `rol-` ni `art-`.
   La maqueta de las dos listas se reutiliza de la pestaña de artículos
   (.art-dos-columnas y .art-tabla-stock--agr, arriba en este mismo archivo);
   lo propio de aquí son dos cosas: la cabecera con el ⚙ y el modo gestión.
   Con «Disponibles» plegado, la rejilla de dos columnas dejaría media ficha en
   blanco, así que fuera de gestión pasa a una sola columna y el bloque se
   comporta como lo que es: una lista de consulta. */
.agr-cabecera {
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px; flex-wrap: wrap;
  margin-bottom: 12px; padding-bottom: 8px;
  border-bottom: 1px solid hsl(var(--border));
}
/* El título cede su línea a la cabecera entera: como elemento flex, su propio
   border-bottom subrayaría solo el ancho del texto. */
.agr-cabecera .rol-seccion-titulo,
.agr-cabecera .art-seccion-titulo { margin: 0; padding: 0; border-bottom: 0; }
/* Botón de SOLO ICONO: cuadrado y centrado, no un botón de texto al que se le
   ha quitado el texto (el padding de .btn-outline lo dejaría alto y estrecho).
   El rótulo vive en title/aria-label, que agrToggleGestion cambia con el
   estado, igual que los ➕/🗑️ de las filas. */
.agr-btn {
  padding: 0; font-size: 1rem; line-height: 1;
  min-width: 38px; min-height: 38px;
  display: inline-flex; align-items: center; justify-content: center;
}
/* Pulsado se nota además de por el ✓: sin texto, el icono solo es poca señal.
   El gris va a pelo, NO con hsl(var(--muted)): .btn-outline fija `background:
   white; color:#334155` en los dos temas, así que un token de tema pintaría el
   fondo oscuro dejando el ✓ oscuro encima e invisible en modo noche. Es el mismo
   #e2e8f0 que ya usa .btn-outline:active. */
.agr-btn[aria-expanded="true"] { background: #e2e8f0; border-color: #94a3b8; }
/* El bloque táctil del final del archivo ya sube .btn-outline a 44px de ALTO;
   a un botón de icono le falta el ancho, que allí solo se les da a .btn-row-*. */
@media (max-width: 640px) { .agr-btn { min-width: 44px; } }
/* Dentro de la ficha de ROL las dos columnas NO son tarjetas: la sección que las
   envuelve ya lo es, y anidar dos dejaría un marco dentro de otro. En la pestaña
   del artículo sí lo son, porque allí el panel contenedor no tiene fondo. */
.rol-seccion.agr-bloque .art-seccion
  { background: none; border: 0; border-radius: 0; padding: 0; }
.agr-bloque:not(.agr--gestion) .art-dos-columnas { grid-template-columns: 1fr; }
.agr-bloque:not(.agr--gestion) .art-col-accion { display: none; }
.agr-bloque:not(.agr--gestion) .agr-solo-gestion { display: none; }
/* Sin gestión no hay gesto: ni cursor de mano ni realce al pasar por encima,
   que prometerían un doble clic que no va a hacer nada. */
.agr-bloque:not(.agr--gestion) .art-tabla-stock--agr tbody tr { cursor: default; }
@media (hover: hover) {
  .agr-bloque:not(.agr--gestion) .art-tabla-stock--agr tbody tr:hover td
    { background: none; }
}

/* ------------------------------------------------------------------- campos */
.rol-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); gap: 12px; }
.rol-campo { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.rol-campo > span { font-size: .8rem; font-weight: 500; color: hsl(var(--muted-foreground)); }
.rol-campo > span em { color: hsl(var(--destructive)); font-style: normal; }
.rol-campo input, .rol-campo select, .rol-campo textarea {
  padding: 8px 10px; border: 1px solid hsl(var(--input)); border-radius: 8px;
  background: hsl(var(--background)); color: hsl(var(--foreground));
  font-size: .9rem; font-family: inherit; width: 100%; min-width: 0;
}
.rol-campo input:focus, .rol-campo select:focus, .rol-campo textarea:focus {
  outline: none; border-color: hsl(var(--primary)); box-shadow: 0 0 0 3px hsl(var(--primary) / .12);
}
.rol-campo small { font-size: .74rem; color: hsl(var(--muted-foreground)); }
.rol-campo textarea { resize: vertical; }
.rol-campo--ancho { grid-column: span 2; }
.rol-campo--total { grid-column: 1 / -1; }
.rol-campo--corto { max-width: 150px; }

/* Solo lectura: los mantiene SOLID (cuenta contable, riesgo calculado…). Se
   muestran porque son datos útiles, atenuados para que se vea que no se tocan. */
.rol-campo--ro input, .rol-campo--ro select, .rol-campo--ro textarea {
  background: hsl(var(--muted)); color: hsl(var(--muted-foreground)); cursor: default;
}
.rol-campo--ro select:disabled, .rol-campo--ro input:disabled { opacity: 1; }

/* Casillas: el control va DELANTE del texto (igual que .art-campo--check). */
.rol-campo--check { flex-direction: row; align-items: center; gap: 8px; }
.rol-campo--check input { width: auto; flex: 0 0 auto; }
.rol-campo--check > span { font-size: .85rem; color: hsl(var(--foreground)); }
.rol-campo--check small { flex-basis: 100%; }

.rol-campo--error input, .rol-campo--error select, .rol-campo--error textarea {
  border-color: hsl(var(--destructive)); box-shadow: 0 0 0 3px hsl(var(--destructive) / .12);
}
.rol-campo--error > span { color: hsl(var(--destructive)); }

/* ------------------------------------------------------------------ responsive */
@media (max-width: 1100px) {
  .rol-campo--ancho { grid-column: span 1; }
}
@media (max-width: 820px) {
  .rol-view { padding: 12px; }
  .rol-grid { grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); }
  .rol-acciones { width: 100%; margin-left: 0; }
  .rol-acciones > button { flex: 1; }
}
@media (max-width: 640px) {
  .rol-grid { grid-template-columns: 1fr; }
  .rol-campo--corto { max-width: none; }
  .rol-cabecera { padding: 12px; }
  .rol-seccion { padding: 12px; }
  .rol-tercero { padding: 12px; }
}

/* ===========================================================================
   FICHA DE FACTURA DE VENTA (Fase 35) — prefijo .fac-

   Bloque autónomo, como .ter-*, .art-* y .rol-*. Dos reglas del proyecto que
   NO se pueden saltar:

   1) Tokens propios. --success/--warning/--destructive/--info solo existen
      dentro de .docs-view (styles.css:765): sin redeclararlos aquí,
      hsl(var(--destructive)) sería un color inválido.
   2) Ninguna clase puede contener la subcadena `.tab`: el patch de
      querySelectorAll (app.js:60) fuerza ámbito global a esos selectores.

   Responsive con los breakpoints canónicos del proyecto (1100/820/640/480).
   La tabla de líneas se convierte en TARJETAS a ≤640px con el mismo mecanismo
   que arregló el solapamiento en las rejillas: td[data-col] + ::before para la
   etiqueta, y .cell-val con min-width:0 para que el valor pueda encoger en vez
   de desbordar sobre ella (ver styles.css:630-696).
   =========================================================================== */
.fac-view {
  --success: 142 71% 38%;
  --warning: 38 92% 50%;
  --destructive: 0 72% 51%;
  --info: 208 88% 40%;
  /* Texto del botón primario, que va SOBRE --info. Es un token propio porque
     --info cambia mucho de luminosidad entre temas: en claro es un azul oscuro
     (blanco encima ≈ 5,4:1) y en oscuro un azul claro (blanco encima ≈ 2,7:1,
     por debajo del 4,5:1 de WCAG AA). Con el color fijo en #fff, Guardar, Crear
     factura, Añadir y Aplicar quedaban ilegibles en tema oscuro. */
  --fac-primario-texto: 0 0% 100%;
}
:root[data-theme="dark"] .fac-view {
  --success: 142 60% 45%;
  --warning: 38 92% 55%;
  --destructive: 0 70% 55%;
  --info: 208 90% 62%;
  --fac-primario-texto: 208 90% 12%;   /* ≈ 6,2:1 sobre el --info oscuro */
}
.fac-view, .fac-view *, .fac-view *::before, .fac-view *::after { box-sizing: border-box; }

.fac-view {
  display: flex; flex-direction: column; gap: 14px;
  padding: 18px; overflow-y: auto; height: 100%;
}

/* ---------------------------------------------------------------- cabecera */
.fac-cabecera {
  display: flex; align-items: center; gap: 16px; flex-wrap: wrap;
  padding: 14px 18px; border-radius: var(--radius);
  background: hsl(var(--card)); border: 1px solid hsl(var(--border));
}
.fac-identidad { min-width: 200px; flex: 1 1 240px; }
.fac-identidad h2 { font-size: 1.15rem; margin: 0; color: hsl(var(--foreground)); }
.fac-contexto {
  margin-top: 4px; font-size: .8rem; color: hsl(var(--muted-foreground));
  font-family: var(--mono);
}
.fac-acciones { display: flex; gap: 8px; flex-wrap: wrap; margin-left: auto; }
/* Sin botones (documento de solo lectura) no debe quedar un hijo vacío: se
   llevaría su `gap` de la cabecera, 16px de aire muerto junto al chip de estado. */
.fac-acciones:empty { display: none; }

/* ------------------------------------- menú «Acciones ▾» de la ficha --------
   Aquí viven las acciones de EMISIÓN del documento —imprimir, enviar por e-mail,
   descargar XML—, que hasta ahora solo existían en la rejilla: con la ficha
   delante no había forma de imprimirla sin volver al listado. Y, a ≤820px,
   también las SECUNDARIAS (eliminar, cerrar/reabrir, convertir), cuyos botones se
   ocultan ahí: en una oferta eran siete rótulos partidos que empujaban los datos
   del documento fuera de la primera pantalla del teléfono.

   Las DOS presentaciones las pinta facPintarAcciones() a la vez y la conmutación
   es SOLO CSS, así que no hay listener de `resize` que mantener. Es el mismo
   criterio de .ter-conv (styles.css:2891), de donde se copian los valores del
   desplegable para que los dos menús se vean idénticos. La capa y el
   posicionamiento los pone Anclaje con .panel-anclado (styles.css:2248).      */
.fac-acc { display: inline-flex; }
.fac-acc-menu {
  min-width: 230px; padding: 6px;
  display: flex; flex-direction: column; gap: 2px;
  background: hsl(var(--card)); border: 1px solid hsl(var(--border));
  border-radius: 12px; box-shadow: 0 12px 32px rgba(0, 0, 0, .22);
}
.fac-acc-item {
  display: flex; align-items: center; gap: 10px; width: 100%;
  text-align: left; padding: 10px 12px; border: none; border-radius: 8px;
  cursor: pointer; background: transparent; color: hsl(var(--foreground));
  font-size: .9rem; font-family: inherit;
}
.fac-acc-item:hover { background: hsl(var(--muted) / .7); }
.fac-acc-icono { width: 1.2em; text-align: center; flex-shrink: 0; }

/* VARIANTES DE LOS ÍTEMS. Antes el color lo llevaba el botón de la barra
   (.fac-btn--peligro, --cerrar, --convertir); desde que las secundarias viven
   solo en el menú, el aviso visual tiene que estar aquí o «Eliminar» se lee
   igual que «Imprimir». Se tiñe el TEXTO y no el fondo: son ítems de una lista,
   no botones, y un fondo rojo dentro del desplegable pesa demasiado. */
.fac-acc-item--peligro { color: hsl(var(--destructive)); }
.fac-acc-item--peligro:hover { background: hsl(var(--destructive) / .12); }
.fac-acc-item--convertir { color: hsl(var(--primary)); }
.fac-acc-item--convertir:hover { background: hsl(var(--primary) / .12); }
/* «Cerrar» no se tiñe: es una acción normal del ciclo del documento, y pintarla
   de rojo la confundiría con «Eliminar». Su icono (🔒) ya la distingue. */

/* Chip de estado: mismo vocabulario visual que .art-chip (styles.css:2982). */
.fac-chip {
  display: inline-block; padding: 3px 12px; border-radius: 999px;
  font-size: .78rem; font-weight: 600; white-space: nowrap;
}
.fac-chip--ok     { background: hsl(var(--success) / .14); color: hsl(var(--success)); }
.fac-chip--aviso  { background: hsl(var(--warning) / .16); color: hsl(38 92% 30%); }
.fac-chip--baja   { background: hsl(var(--destructive) / .12); color: hsl(var(--destructive)); }
:root[data-theme="dark"] .fac-chip--aviso { color: hsl(38 92% 68%); }

/* --------------------------------------------------- banda de bloqueo (candado) */
.fac-bloqueo {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 12px 16px; border-radius: var(--radius); font-size: .88rem;
  background: hsl(var(--destructive) / .09);
  border: 1px solid hsl(var(--destructive) / .35);
  color: hsl(var(--destructive));
}
.fac-bloqueo-icono { font-size: 1.1rem; line-height: 1.2; }

/* ------------------------------------------------------------------ avisos */
.fac-avisos:empty { display: none; }
.fac-aviso {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 10px 14px; border-radius: var(--radius); font-size: .87rem;
  border: 1px solid; margin-bottom: 8px;
}
.fac-aviso--aviso { background: hsl(var(--warning) / .1); border-color: hsl(var(--warning) / .4);
                    color: hsl(38 92% 28%); }
.fac-aviso--error { background: hsl(var(--destructive) / .1); border-color: hsl(var(--destructive) / .4);
                    color: hsl(var(--destructive)); }
.fac-aviso--info  { background: hsl(var(--info) / .1); border-color: hsl(var(--info) / .35);
                    color: hsl(var(--info)); }
:root[data-theme="dark"] .fac-aviso--aviso { color: hsl(38 92% 68%); }

/* -------------------------------------------- cabecera del documento (.fac-doc) */
.fac-doc {
  background: hsl(var(--card)); border: 1px solid hsl(var(--border));
  border-radius: var(--radius); padding: 16px;
}
.fac-doc-fieldset { border: 0; padding: 0; margin: 0; min-width: 0; }
.fac-doc-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 12px;
}
.fac-doc-cliente {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  margin-top: 12px; padding-top: 12px; border-top: 1px solid hsl(var(--border));
  font-size: .85rem; color: hsl(var(--muted-foreground));
}
.fac-doc-nif { font-family: var(--mono); }
.fac-doc-dir { flex: 1 1 240px; min-width: 0; overflow-wrap: anywhere; }
.fac-enlace {
  background: none; border: 0; padding: 0; cursor: pointer;
  font: inherit; color: hsl(var(--info)); text-decoration: none;
}
.fac-enlace::after { content: " \2197"; font-size: .85em; }
@media (hover: hover) {
  .fac-enlace:hover { text-decoration: underline; }
}
.fac-enlace:active { text-decoration: underline; opacity: .7; }
.fac-enlace:focus-visible {
  outline: 2px solid hsl(var(--info)); outline-offset: 2px; border-radius: 4px;
}

/* ------------------------------------------------------------------- campos */
.fac-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); gap: 12px; }
.fac-campo { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.fac-campo > span { font-size: .8rem; font-weight: 500; color: hsl(var(--muted-foreground)); }
.fac-campo input, .fac-campo select, .fac-campo textarea {
  width: 100%; padding: 8px 10px; border-radius: 8px;
  border: 1px solid hsl(var(--border)); background: hsl(var(--background));
  color: hsl(var(--foreground)); font: inherit; font-size: .9rem; min-width: 0;
}
/* Notas de la línea (frame): dos líneas por defecto y estirable; la marca 📝 de la
   rejilla solo señala que la línea las tiene (el texto va en su title). */
.fac-campo textarea { resize: vertical; min-height: 2.6rem; line-height: 1.35; }
.fac-linea-notas { opacity: .75; margin-left: 4px; cursor: help; }
.fac-campo input:disabled, .fac-campo select:disabled, .fac-campo textarea:disabled,
.fac-campo input[readonly] {
  background: hsl(var(--muted)); color: hsl(var(--muted-foreground)); cursor: default;
}
.fac-campo input:focus-visible, .fac-campo select:focus-visible,
.fac-campo textarea:focus-visible {
  outline: 2px solid hsl(var(--info)); outline-offset: 1px; border-color: transparent;
}
.fac-campo--corto { max-width: 190px; }
.fac-campo--ancho { grid-column: span 2; }
.fac-campo--total { grid-column: 1 / -1; }
/* UNIDADES cuando lo PROPONEN las unidades extendidas. Ya no va en readonly (el
   usuario puede corregir el total), así que aquí no hay gris que heredar: lo
   único que dice el borde discontinuo es que ese número lo ha puesto el cálculo
   y que se puede cambiar. La negrita evita que parezca un campo desactivado y
   olvidado, y NO se toca el cursor: el campo se teclea y necesita su I-beam. */
.fac-campo input.fac-campo--calculado {
  font-weight: 600; color: hsl(var(--foreground));
  border-style: dashed;
}

/* ------------------------------- panel plegable «Datos adicionales del cliente» */
.fac-cliente {
  background: hsl(var(--card)); border: 1px solid hsl(var(--border));
  border-radius: var(--radius);
}
.fac-cliente-resumen {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 12px 16px; cursor: pointer; font-weight: 600; font-size: .92rem;
  color: hsl(var(--foreground)); list-style: none; border-radius: var(--radius);
}
.fac-cliente-resumen::-webkit-details-marker { display: none; }
.fac-cliente-resumen:hover { background: hsl(var(--muted) / .5); }
.fac-cliente-resumen:focus-visible { outline: 2px solid hsl(var(--info)); outline-offset: -2px; }
.fac-cliente-flecha { transition: transform .15s ease; color: hsl(var(--muted-foreground)); }
.fac-cliente[open] .fac-cliente-flecha { transform: rotate(90deg); }
.fac-cliente-pista {
  margin-left: auto; font-weight: 500; font-size: .8rem;
  color: hsl(var(--muted-foreground)); font-family: var(--mono);
}
.fac-cliente-cuerpo { padding: 0 16px 16px; }
.fac-cliente-fieldset { border: 0; padding: 0; margin: 0; min-width: 0; }
.fac-cliente-nota {
  margin: 0 0 12px; font-size: .82rem; color: hsl(var(--muted-foreground));
  padding: 8px 12px; background: hsl(var(--muted) / .6); border-radius: 8px;
}

/* Bloque informativo: lo que es del cliente y aquí solo se consulta. */
.fac-info { margin-top: 16px; padding-top: 14px; border-top: 1px solid hsl(var(--border)); }
.fac-info h4 {
  margin: 0 0 10px; font-size: .85rem; font-weight: 600; color: hsl(var(--foreground));
}
.fac-info h4 small { font-weight: 400; color: hsl(var(--muted-foreground)); }
.fac-info-fila {
  display: flex; justify-content: space-between; gap: 14px;
  padding: 6px 0; font-size: .85rem; border-bottom: 1px solid hsl(var(--border) / .5);
}
.fac-info-fila > span:first-child {
  color: hsl(var(--muted-foreground)); flex-shrink: 0;
}
.fac-info-fila > span:last-child {
  text-align: right; min-width: 0; overflow-wrap: anywhere;
}
.fac-info-fila > span:last-child small {
  display: block; font-size: .75rem; color: hsl(var(--muted-foreground));
}
.fac-info-fila--alerta { color: hsl(var(--destructive)); font-weight: 600; }

/* ------------------------------------------- Carta de Portes (albarán de venta)
   El panel es .fac-cliente (mismo plegado y estética que «Datos adicionales del
   cliente»); aquí solo lo que es propio: las tarjetas de rol, las matrículas como
   fichas y la fila de botones. Una columna en móvil por el auto-fit. */
.fac-cporte .fac-aviso { margin-bottom: 8px; }
.cp-bloque {
  margin: 14px 0 8px; font-size: .85rem; font-weight: 600; color: hsl(var(--foreground));
}
.cp-bloque small { font-weight: 400; color: hsl(var(--muted-foreground)); }
.cp-roles {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 8px;
}
.cp-rol {
  border: 1px solid hsl(var(--border)); border-radius: 8px; padding: 8px 10px;
  font-size: .84rem; min-width: 0; overflow-wrap: anywhere;
  background: hsl(var(--muted) / .35);
}
.cp-rol-titulo {
  font-size: .72rem; text-transform: uppercase; letter-spacing: .05em;
  color: hsl(var(--muted-foreground)); margin-bottom: 2px;
}
.cp-rol-nombre { font-weight: 600; }
.cp-rol-dir { color: hsl(var(--muted-foreground)); }
.cp-vacio { color: hsl(var(--muted-foreground)); }
.cp-falta { color: hsl(var(--warning)); }
.cp-datos { margin-top: 8px; }
.cp-chips { display: flex; flex-wrap: wrap; gap: 6px; margin: 2px 0 6px; }
.cp-chip {
  display: inline-flex; align-items: center; gap: 4px; max-width: 100%;
  padding: 3px 4px 3px 10px; border-radius: 999px; font-size: .82rem;
  border: 1px solid hsl(var(--border)); background: hsl(var(--card));
  overflow-wrap: anywhere;
}
.cp-chip-quitar {
  border: 0; background: none; cursor: pointer; font-size: .75rem; line-height: 1;
  padding: 4px 6px; border-radius: 999px; color: hsl(var(--muted-foreground));
}
.cp-chip-quitar:hover { background: hsl(var(--destructive) / .12); }
.cp-chip-quitar:focus-visible { outline: 2px solid hsl(var(--info)); outline-offset: 1px; }
/* Columnas más anchas que el .fac-grid general: aquí los desplegables llevan
   textos largos («Mercancías en paletas», «1234ABC · Tractora»). */
.fac-cporte .fac-grid { grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); }
.cp-campo-notas { grid-column: 1 / -1; }
.cp-campo-notas textarea { width: 100%; box-sizing: border-box; resize: vertical; font: inherit; }
.cp-botones { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }

/* ------------------------------------------------------------ panel de líneas */
.fac-lineas-panel {
  background: hsl(var(--card)); border: 1px solid hsl(var(--border));
  border-radius: var(--radius); padding: 16px;
}
.fac-lineas-barra {
  display: flex; align-items: baseline; gap: 12px;
  margin-bottom: 12px; padding-bottom: 8px; border-bottom: 1px solid hsl(var(--border));
}
.fac-lineas-barra h3 { margin: 0; font-size: .95rem; font-weight: 600; }
.fac-lineas-num { font-size: .8rem; color: hsl(var(--muted-foreground)); }
.fac-lineas-vacio {
  padding: 24px; text-align: center; font-size: .88rem;
  color: hsl(var(--muted-foreground));
}
.fac-lineas-pie:empty { display: none; }
.fac-lineas-pie { margin-top: 12px; }
.fac-btn-linea {
  padding: 8px 14px; border-radius: 8px; cursor: pointer; font: inherit; font-size: .88rem;
  border: 1px dashed hsl(var(--border)); background: none; color: hsl(var(--info));
  transition: background-color .12s ease, transform .06s ease;
}
@media (hover: hover) {
  .fac-btn-linea:hover { background: hsl(var(--info) / .08); border-style: solid; }
}
.fac-btn-linea:active:not(:disabled) {
  background: hsl(var(--info) / .16); border-style: solid; transform: translateY(1px);
}
/* Era el único botón de la ficha sin anillo de foco propio, y es justamente el
   que da acceso a añadir la primera línea. */
.fac-btn-linea:focus-visible { outline: 2px solid hsl(var(--info)); outline-offset: 2px; }
.fac-btn-linea:disabled { opacity: .55; cursor: default; transform: none; }

.fac-rejilla-wrap { max-width: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch; }
.fac-rejilla { width: 100%; border-collapse: collapse; font-size: .88rem; }
.fac-rejilla thead th {
  text-align: left; padding: 8px 10px; font-size: .75rem; font-weight: 600;
  text-transform: uppercase; letter-spacing: .04em;
  color: hsl(var(--muted-foreground)); border-bottom: 1px solid hsl(var(--border));
  white-space: nowrap;
}
.fac-rejilla tbody td {
  padding: 9px 10px; border-bottom: 1px solid hsl(var(--border) / .6);
  vertical-align: top;
}
.fac-rejilla tbody tr:hover { background: hsl(var(--muted) / .4); }
.fac-rejilla .num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.fac-col-num { width: 44px; color: hsl(var(--muted-foreground)); }
.fac-col-desc { min-width: 180px; }
/* Tarifa de la línea: cabe un código de 3 letras y su desplegable, sin robarle
   sitio a la descripción. No es una columna numérica. */
.fac-col-tarifa { width: 110px; text-align: left; }
.fac-rejilla .cell-val { display: inline; overflow-wrap: anywhere; }
.fac-unidad { color: hsl(var(--muted-foreground)); font-size: .78rem; }
/* Cifra presente pero que todavía no dice nada: el «pendiente de facturar» de un
   albarán de compra sin facturar es igual a las unidades, y repetir el mismo
   número a plena tinta en dos columnas contiguas compite con el que sí importa.
   En cuanto difiere —hay algo facturado— la celda pasa a <strong>. */
.fac-apagado { color: hsl(var(--muted-foreground)); }

/* Distintivo de unidades extendidas. Se pinta SIEMPRE que la línea tenga factor:
   es parte del importe, no un detalle opcional. */
.fac-ue {
  display: inline-block; margin-left: 6px; padding: 1px 7px; border-radius: 999px;
  font-size: .74rem; font-weight: 600; white-space: nowrap;
  background: hsl(var(--info) / .12); color: hsl(var(--info));
}

/* ----------------------------------------------------------------- totales */
.fac-totales {
  background: hsl(var(--card)); border: 1px solid hsl(var(--border));
  border-radius: var(--radius); padding: 14px 18px;
  display: flex; flex-direction: column; gap: 2px; margin-left: auto;
  min-width: 280px; max-width: 380px;
}
.fac-total {
  display: flex; justify-content: space-between; gap: 20px;
  padding: 4px 0; font-size: .88rem; font-variant-numeric: tabular-nums;
}
.fac-total > span:first-child { color: hsl(var(--muted-foreground)); }
.fac-total--fuerte {
  font-size: 1.02rem; font-weight: 700; color: hsl(var(--foreground));
  border-top: 1px solid hsl(var(--border)); margin-top: 6px; padding-top: 8px;
}
.fac-total--fuerte > span:first-child { color: hsl(var(--foreground)); }

/* ================================================================ responsive */
@media (max-width: 1100px) {
  .fac-campo--ancho { grid-column: span 1; }
  .fac-col-desc { min-width: 140px; }
}

@media (max-width: 820px) {
  .fac-view { padding: 12px; gap: 12px; }
  .fac-grid { grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); }
  .fac-doc-grid { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); }
  .fac-acciones { width: 100%; margin-left: 0; }
  .fac-acciones > button { flex: 1; }
  .fac-totales { max-width: none; margin-left: 0; }
  /* En móvil «Guardar» y «Acciones ▾» ocupan una fila entera cada uno: son los
     dos destinos táctiles principales de la ficha. (Ya no hace falta ocultar
     nada aquí: desde que las secundarias viven solo en el menú, la barra trae
     únicamente «Guardar» en cualquier ancho.) */
  .fac-acc { width: 100%; }
  .fac-acc-toggle { justify-content: center; width: 100%; }
}

@media (max-width: 640px) {
  .fac-cabecera { padding: 12px; }
  .fac-doc, .fac-lineas-panel, .fac-cliente-cuerpo { padding: 12px; }
  .fac-grid, .fac-doc-grid { grid-template-columns: 1fr; }
  .fac-campo--corto { max-width: none; }
  .fac-cliente-pista { margin-left: 0; width: 100%; }

  /* --- La tabla de líneas pasa a TARJETAS -------------------------------
     Mismo mecanismo que las rejillas del ERP (styles.css:630-696): se oculta
     el thead y cada <td> se pinta con su etiqueta desde data-col. Sin esto, 8
     columnas en 390px producen justo el solapamiento que ya se corrigió una vez.
     El scroll horizontal se desactiva: en tarjeta no hay nada que desplazar. */
  .fac-rejilla-wrap { overflow-x: visible; }
  .fac-rejilla thead { display: none; }
  .fac-rejilla, .fac-rejilla tbody, .fac-rejilla tr, .fac-rejilla td { display: block; width: 100%; }
  .fac-rejilla tbody tr {
    border: 1px solid hsl(var(--border)); border-radius: var(--radius);
    margin-bottom: 10px; padding: 10px 12px; background: hsl(var(--card));
    box-shadow: 0 1px 6px rgba(30, 41, 59, .06);
  }
  .fac-rejilla tbody tr:hover { background: hsl(var(--card)); }
  /* white-space: normal NO es opcional. El global `tbody td { white-space: nowrap }`
     llegaba también aquí, y con nowrap el `overflow-wrap: anywhere` de .cell-val no
     parte nada: una descripción larga («APP EN LA NUBE CC RELOJ (CONFIGURACION Y
     FORMACION) 📝») salía de la tarjeta por la derecha. Es lo mismo que ya hace la
     tarjeta de .data-table. Las cifras siguen sin partirse: `.fac-rejilla .num`
     pesa más y conserva su nowrap. */
  .fac-rejilla tbody td {
    display: flex; justify-content: space-between; align-items: flex-start;
    gap: 10px; padding: 6px 0; border-bottom: 1px solid hsl(var(--border) / .5);
    white-space: normal;
  }
  .fac-rejilla tbody td:last-child { border-bottom: 0; }
  .fac-rejilla tbody td::before {
    content: attr(data-col); flex-shrink: 0; max-width: 45%;
    font-weight: 600; font-size: .75rem; letter-spacing: .04em;
    text-transform: uppercase; color: hsl(var(--muted-foreground));
  }
  /* LA regla anti-solapamiento: el valor tiene que poder encoger por debajo de
     su min-content, cosa que un ítem flex anónimo (texto suelto) no puede. */
  .fac-rejilla tbody td .cell-val {
    flex: 1; min-width: 0; text-align: right;
    overflow-wrap: anywhere; word-break: break-word;
  }
  /* La descripción es la única que gana más leyendo en dos líneas. */
  .fac-rejilla tbody td[data-col="Descripción"] { flex-direction: column; }
  .fac-rejilla tbody td[data-col="Descripción"] .cell-val { text-align: left; width: 100%; }
  /* El nº de línea encabeza la tarjeta, no merece una fila propia. */
  .fac-rejilla tbody td[data-col="Nº"] {
    border-bottom: 1px solid hsl(var(--border));
    padding-bottom: 8px; margin-bottom: 4px;
  }

  /* Con muchas líneas el total se pierde al final del scroll: se ancla abajo. */
  .fac-totales {
    position: sticky; bottom: 0; z-index: 1;
    box-shadow: 0 -4px 14px rgba(30, 41, 59, .10);
    padding-bottom: calc(14px + env(safe-area-inset-bottom));
  }
  /* Área táctil mínima en los controles de línea. */
  .fac-btn-linea { width: 100%; min-height: 44px; }
}

/* --------------------------------- botones de acción de la ficha (Fase 35) */
/* POR QUÉ EL :hover VA DENTRO DE @media (hover: hover)
   En táctil el navegador deja el :hover APLICADO tras soltar el dedo, hasta que
   se toca en otro sitio: el botón se quedaba "encendido" después de pulsarlo.
   La media query lo reserva a los dispositivos con puntero real; el feedback de
   pulsación en táctil lo da :active, que sí se suelta.

   Y POR QUÉ HACE FALTA :active
   Sin él, pulsar no producía ningún cambio visible, así que durante el viaje al
   ERP (que en una factura recalcula toda la cascada de importes) el botón
   parecía no haber respondido. */
.fac-btn {
  padding: 8px 16px; border-radius: 8px; cursor: pointer; font: inherit;
  font-size: .88rem; font-weight: 600; min-height: 38px;
  border: 1px solid hsl(var(--border)); background: hsl(var(--background));
  color: hsl(var(--foreground));
  transition: background-color .12s ease, transform .06s ease;
}
@media (hover: hover) {
  .fac-btn:hover:not(:disabled) { background: hsl(var(--muted)); }
}
.fac-btn:active:not(:disabled) { transform: translateY(1px); background: hsl(var(--muted)); }
.fac-btn:disabled { opacity: .55; cursor: default; transform: none; }
.fac-btn:focus-visible { outline: 2px solid hsl(var(--info)); outline-offset: 2px; }
.fac-btn--primario {
  background: hsl(var(--info)); border-color: hsl(var(--info));
  color: hsl(var(--fac-primario-texto));
}
@media (hover: hover) {
  .fac-btn--primario:hover:not(:disabled) { filter: brightness(1.08); }
}
.fac-btn--primario:active:not(:disabled) {
  filter: brightness(.94); background: hsl(var(--info));
}
/* .fac-btn--peligro, --cerrar y --convertir se retiraron al mover TODAS las
   acciones secundarias al menú «Acciones ▾»: ya no existe ningún botón de esas
   variantes en la barra de la ficha. Su papel —distinguir de un vistazo lo que
   borra o lo que crea otro documento— lo hacen ahora .fac-acc-item--peligro y
   .fac-acc-item--convertir, arriba. */

/* Campo rechazado por el backend. Mismo lenguaje visual que .ter-campo--error. */
.fac-campo--error > input, .fac-campo--error > select, .fac-campo--error > textarea {
  border-color: hsl(var(--destructive));
  box-shadow: 0 0 0 3px hsl(var(--destructive) / .12);
}
.fac-campo--error > span { color: hsl(var(--destructive)); }

/* El panel del combo (combo.js) se ancla al input y debe salir por encima del
   resto de la ficha; los paneles de la ficha no crean contexto de apilamiento
   propio, así que basta con un z-index alto sobre el panel del combo. */
.fac-view .ter-combo-panel { z-index: var(--z-panel-anclado, 200); }

@media (max-width: 640px) {
  .fac-btn { flex: 1; min-height: 44px; }
}

/* ------------------------------------- edición en línea de las líneas (Fase 5)
   SIN USO desde que la tabla de líneas es de CONSULTA: ya no hay controles dentro
   de las filas, así que .fac-celda y sus variantes solo las viste el desplegable
   de tarifa de rejilla, que también quedó huérfano. Los campos de la línea se
   estilan ahora con .fac-campo, dentro de .fac-borrador. Se conservan por si
   alguna vista antigua las usara. */
.fac-celda {
  width: 100%; max-width: 110px; padding: 5px 7px; border-radius: 6px;
  border: 1px solid hsl(var(--border)); background: hsl(var(--background));
  color: hsl(var(--foreground)); font: inherit; font-size: .86rem;
  text-align: right; font-variant-numeric: tabular-nums; min-width: 0;
}
.fac-celda--texto { max-width: none; text-align: left; }
/* Tarifa de la línea. Alineada a la IZQUIERDA porque es un código (NOR, ESP), no
   una cifra: con tabular-nums y a la derecha se lee como si fuera un importe. */
.fac-celda--tarifa {
  max-width: 100px; text-align: left; font-variant-numeric: normal;
}
.fac-celda:focus-visible {
  outline: 2px solid hsl(var(--info)); outline-offset: 1px; border-color: transparent;
}
.fac-celda:hover { border-color: hsl(var(--info) / .5); }

.fac-col-acc { width: 44px; text-align: center; }
.fac-borrar-linea {
  background: none; border: 0; cursor: pointer; font-size: 1rem;
  width: 32px; height: 32px; border-radius: 6px; line-height: 1;
  transition: background-color .12s ease, transform .06s ease;
}
@media (hover: hover) {
  .fac-borrar-linea:hover { background: hsl(var(--destructive) / .12); }
}
.fac-borrar-linea:active { background: hsl(var(--destructive) / .22); transform: translateY(1px); }
.fac-borrar-linea:focus-visible {
  outline: 2px solid hsl(var(--destructive)); outline-offset: 2px;
}

/* --------------------------------------------- borrador de línea nueva */
.fac-borrador {
  border: 1px dashed hsl(var(--info) / .5); border-radius: var(--radius);
  padding: 14px; margin-bottom: 12px; background: hsl(var(--info) / .04);
  /* El panel del combo de artículo se ancla aquí dentro (panelPadre). */
  position: relative;
}
.fac-borrador-campos {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px;
}
.fac-borrador-pie {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-top: 12px;
}
.fac-borrador-info {
  flex: 1 1 200px; min-width: 0; font-size: .82rem;
  color: hsl(var(--muted-foreground)); overflow-wrap: anywhere;
}

@media (max-width: 640px) {
  /* En tarjeta el input ocupa el lado derecho de su fila, como el valor al que
     sustituye: sigue valiendo el contrato data-col + .cell-val. */
  .fac-celda { max-width: none; }
  .fac-col-acc { width: auto; text-align: right; }
  .fac-rejilla tbody td.fac-col-acc { justify-content: flex-end; }
  .fac-rejilla tbody td.fac-col-acc::before { content: none; }
  .fac-borrar-linea { width: 44px; height: 44px; }
  /* Los otros dos botones de esta celda (📊 stock y ⧉ duplicar) también suben a 44px,
     pero sus reglas base están MÁS ABAJO en el archivo y ganarían por orden: se corrigen
     en el bloque de áreas táctiles del final. */
  .fac-borrador-campos { grid-template-columns: 1fr; }
  .fac-borrador-pie > .fac-btn { flex: 1; }
  /* M3 en el editor de línea. La regla global de 16 px (select:not([multiple]),
     textarea) EMPATA en especificidad con `.fac-campo select` y pierde contra
     `.fac-campo textarea`, que van después a .9rem: los desplegables (IVA,
     Ajustes Especiales) y las notas quedaban a 14,4 px y iOS ampliaba la página
     al tocarlos, sin devolverla. Los input ya ganaban (su selector pesa más). */
  .fac-borrador .fac-campo select,
  .fac-borrador .fac-campo textarea { font-size: 16px; }
}

/* ------------------------------- frame de UNIDADES EXTENDIDAS (Fase 6)
   .fac-ue--boton está SIN USO: en la tabla el 📐 es ya solo un distintivo (.fac-ue,
   más abajo), y las medidas se teclean en el frame de la línea. El resto del
   bloque (.fac-medidas*) SÍ sigue vivo: es el que viste ese frame. */
.fac-ue--boton {
  border: 1px solid hsl(var(--info) / .35); cursor: pointer; font: inherit;
  font-size: .74rem; font-weight: 600;
  transition: background-color .12s ease;
}
@media (hover: hover) {
  .fac-ue--boton:hover { background: hsl(var(--info) / .22); }
}
.fac-ue--boton:active { background: hsl(var(--info) / .3); }
.fac-ue--boton:focus-visible { outline: 2px solid hsl(var(--info)); outline-offset: 1px; }

.fac-medidas {
  margin-top: 10px; padding: 10px 12px; border-radius: 8px;
  background: hsl(var(--info) / .07); border: 1px solid hsl(var(--info) / .3);
  /* En la celda de una tabla el panel no debe estirar la columna. */
  min-width: 210px; max-width: 340px; white-space: normal; text-align: left;
}
/* Plegado. El panel se OCULTA, nunca se desmonta: las medidas que viajan al
   guardar se leen de él. Explícito porque .fac-medidas trae `display` propio en
   el bloque de móvil de más abajo y el `hidden` del navegador no se pelea. */
.fac-medidas[hidden] { display: none; }
.fac-medidas-cab {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  margin-bottom: 8px; font-size: .72rem; font-weight: 600;
  text-transform: uppercase; letter-spacing: .03em;
  color: hsl(var(--muted-foreground));
}
/* Misma forma que el 📊 de la rejilla (.fac-stock-linea): botón sin caja que
   solo se tiñe al pasar por encima. */
.fac-medidas-cerrar {
  background: none; border: none; cursor: pointer; font-size: .95rem;
  padding: 4px 6px; border-radius: 6px; line-height: 1;
  color: hsl(var(--muted-foreground));
  transition: background .15s, color .15s;
}
@media (hover: hover) {
  .fac-medidas-cerrar:hover { background: hsl(var(--info) / .18); color: hsl(var(--info)); }
}
.fac-medidas-cerrar:active { background: hsl(var(--info) / .26); }
.fac-medidas-cerrar:focus-visible { outline: 2px solid hsl(var(--info)); outline-offset: 1px; }

.fac-medidas-campos {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(88px, 1fr)); gap: 8px;
}
.fac-medida { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.fac-medida > span {
  font-size: .72rem; font-weight: 600; text-transform: uppercase; letter-spacing: .03em;
  color: hsl(var(--muted-foreground));
}
.fac-medida input {
  width: 100%; padding: 5px 7px; border-radius: 6px; min-width: 0;
  border: 1px solid hsl(var(--border)); background: hsl(var(--background));
  color: hsl(var(--foreground)); font: inherit; font-size: .85rem; text-align: right;
}
.fac-medidas-calculo {
  margin-top: 8px; font-size: .8rem; color: hsl(var(--foreground));
  overflow-wrap: anywhere;
}
.fac-medidas-calculo small {
  display: block; margin-top: 3px; font-size: .72rem; color: hsl(var(--muted-foreground));
}
.fac-medidas-pie { display: flex; gap: 8px; justify-content: flex-end; margin-top: 10px; }
.fac-medidas-pie .fac-btn { padding: 5px 12px; min-height: 32px; font-size: .82rem; }

@media (max-width: 640px) {
  .fac-medidas { max-width: none; width: 100%; }
  .fac-medidas-pie .fac-btn { flex: 1; min-height: 44px; }
  /* Área táctil, la misma que .fac-borrar-linea. El botón 📐 que lo reabre no
     necesita nada: es un .fac-btn del pie del borrador y ya lo estira la regla
     `.fac-borrador-pie > .fac-btn { flex: 1 }`. */
  .fac-medidas-cerrar { width: 44px; height: 44px; }

  /* facAbrirFrameMedidas inyecta .fac-medidas DENTRO del <td> de «Artículo»
     (boton.closest('td')). A este ancho ese <td> ya es una fila flex con la
     etiqueta en ::before, así que el panel entraba como un TERCER ítem de esa
     misma fila: con min-width:210px no cabía junto a la etiqueta (hasta 45 %)
     y el valor, y desbordaba la tarjeta por la derecha. Dejando envolver al
     <td> y forzando base 100 % en el panel, ocupa una fila propia debajo.
     No hace falta ningún breakpoint nuevo: se reutiliza el de 640. */
  .fac-rejilla tbody td { flex-wrap: wrap; }
  .fac-rejilla tbody td .fac-medidas { flex: 1 0 100%; min-width: 0; }
}

/* ------------------------------------- botón de CIERRE contable (Fase 7) */
.fac-bloqueo em { font-style: normal; opacity: .8; font-family: var(--mono); font-size: .92em; }


/* ===========================================================================
   FICHA DE ALBARÁN DE VENTA — añadidos sobre el vocabulario `fac-*`

   El albarán reutiliza TODA la hoja de la ficha de factura: mismas clases,
   mismos breakpoints (1100 / 820 / 640 px), mismo comportamiento de tarjeta en
   móvil. Aquí solo van los tres elementos que la factura no tiene: los
   distintivos de trazabilidad de la línea, el botón de duplicar y el panel de
   importación de CSV/TXT.
   =========================================================================== */

/* --- Distintivos de trazabilidad de la línea -------------------------------
   Comparten forma con .fac-ue (píldora pequeña junto al código de artículo),
   pero NO su color: el azul de .fac-ue significa «esto forma parte del cálculo
   del importe», y esto otro significa «esto le falta a la entrega». */
.fac-trz {
  display: inline-block; margin-left: 6px; padding: 1px 7px; border-radius: 999px;
  font-size: .74rem; font-weight: 600; white-space: nowrap;
}
/* Mismo par claro/oscuro que .fac-chip--aviso y .fac-aviso--aviso: el ámbar de
   var(--warning) no tiene contraste suficiente como texto en tema claro, así que
   se oscurece aquí y se aclara en el oscuro. Es la convención ya establecida. */
.fac-trz--pendiente { background: hsl(var(--warning) / .16); color: hsl(38 92% 28%); }
.fac-trz--servicio  { background: hsl(var(--muted)); color: hsl(var(--muted-foreground)); }
:root[data-theme="dark"] .fac-trz--pendiente { color: hsl(38 92% 68%); }
/* Línea con Ajustes Especiales (proyecto/agente/almacén propios). Tono info,
   discreto: es un dato, no un problema. */
.fac-trz--ajuste { background: hsl(var(--info) / .12); color: hsl(var(--info)); }

/* --- Ajustes Especiales de la línea ----------------------------------------
   Es el mismo plegable que «Datos adicionales del cliente» (.fac-cliente*);
   aquí solo su encaje dentro del frame de línea y el contador del resumen. */
.fac-ajustes-linea { margin-top: 12px; }
.fac-ajustes-linea > .fac-cliente-resumen { min-height: 44px; padding: 8px 14px; }
.fac-ajustes-linea > .fac-cliente-cuerpo { padding: 0 14px 14px; }
.fac-ajustes-cuenta {
  margin-left: auto; padding: 1px 8px; border-radius: 999px;
  font-size: .74rem; font-weight: 600;
  background: hsl(var(--info) / .12); color: hsl(var(--info));
}
.fac-ajustes-cuenta[hidden] { display: none; }
@media (max-width: 640px) {
  /* En móvil el editor ya es más alto que el área visible: menos aire. */
  .fac-ajustes-linea { margin-top: 8px; }
}

/* --- Duplicar línea --------------------------------------------------------
   Mismo tamaño y comportamiento que la papelera para que la columna de acciones
   siga alineada; en tono neutro porque duplicar no es destructivo. */
.fac-dup-linea {
  background: none; border: 0; cursor: pointer; font-size: 1rem;
  width: 32px; height: 32px; border-radius: 6px; line-height: 1;
  color: hsl(var(--info));
  transition: background-color .12s ease, transform .06s ease;
}
@media (hover: hover) {
  .fac-dup-linea:hover { background: hsl(var(--info) / .12); }
}
.fac-dup-linea:active { background: hsl(var(--info) / .22); transform: translateY(1px); }
.fac-dup-linea:focus-visible { outline: 2px solid hsl(var(--info)); outline-offset: 2px; }

/* --- Editar línea ----------------------------------------------------------
   Abre el frame de la línea, lo mismo que el doble clic sobre la fila. Mismo
   tamaño que sus vecinos para que la columna no baile, y en tono neutro: editar
   no destruye nada. En móvil es el ÚNICO camino a la edición, porque el doble
   toque no es un gesto fiable en táctil. */
.fac-edit-linea {
  background: none; border: 0; cursor: pointer; font-size: 1rem;
  width: 32px; height: 32px; border-radius: 6px; line-height: 1;
  transition: background-color .12s ease, transform .06s ease;
}
@media (hover: hover) {
  .fac-edit-linea:hover { background: hsl(var(--info) / .12); }
}
.fac-edit-linea:active { background: hsl(var(--info) / .22); transform: translateY(1px); }
.fac-edit-linea:focus-visible { outline: 2px solid hsl(var(--info)); outline-offset: 2px; }

/* Con dos botones la columna necesita el doble de sitio; sin esto el segundo
   empuja la tabla y reaparece el scroll horizontal que se arregló en su día. */
.fac-col-acc--doble { width: 84px; }
.fac-rejilla td.fac-col-acc { white-space: nowrap; }

/* --- La fila como gesto ----------------------------------------------------
   La tabla de líneas es de CONSULTA: no hay controles dentro de la fila y la
   edición se abre con doble clic (o con el ✏️, que hace lo mismo). El cursor lo
   anuncia, igual que en la rejilla de documentos (.data-table tr.fila-doc).
   La fila que se está editando se marca para que no haya duda de cuál es. */
.fac-rejilla tbody tr.fac-fila--editable { cursor: pointer; }
.fac-rejilla tbody tr.fac-fila--edit,
.fac-rejilla tbody tr.fac-fila--edit:hover {
  background: hsl(var(--info) / .1);
  box-shadow: inset 3px 0 0 hsl(var(--info));
}

/* --- Pie de líneas con varios botones --------------------------------------
   «+ Nueva línea» sigue siendo el primario (borde discontinuo, azul); importar
   y exportar son secundarios y no deben competir con él.

   OJO: el pie pasa a ser FLEX, y dentro de él se insertan también el borrador de
   nueva línea (.fac-borrador) y el panel de importación, que antes eran bloques
   apilados. Los dos necesitan declarar que ocupan la fila entera o el flex los
   comprimiría junto a los botones — y eso afectaría también a la factura, que
   comparte este pie. */
.fac-lineas-pie { display: flex; flex-wrap: wrap; gap: 8px; align-items: flex-start; }
.fac-lineas-pie > .fac-borrador { flex: 1 0 100%; }
.fac-btn-linea--sec { color: hsl(var(--muted-foreground)); }
@media (hover: hover) {
  .fac-btn-linea--sec:hover { background: hsl(var(--muted) / .8); color: hsl(var(--foreground)); }
}

/* --- Panel de importación --------------------------------------------------
   Ocupa la fila entera del pie (flex-basis:100%) para no quedar comprimido
   junto a los botones, y hereda el aire del borrador de nueva línea. */
.fac-importar {
  flex: 1 0 100%;
  border: 1px dashed hsl(var(--info) / .5); border-radius: var(--radius);
  padding: 14px; margin-bottom: 12px; background: hsl(var(--info) / .04);
}
.fac-importar-cab {
  display: flex; justify-content: space-between; align-items: center;
  gap: 12px; margin-bottom: 8px;
}
.fac-importar-ayuda {
  margin: 0 0 12px; font-size: .8rem; line-height: 1.5;
  color: hsl(var(--muted-foreground));
}
.fac-importar-ayuda code {
  font-family: var(--mono); font-size: .95em; padding: 0 3px; border-radius: 4px;
  background: hsl(var(--muted)); color: hsl(var(--foreground));
}
.fac-importar-campos {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 12px;
}
.fac-importar-informe:not(:empty) { margin-top: 12px; }
/* El informe de errores puede ser largo: se le pone tope y scroll propio para
   que no empuje los botones de confirmar fuera de la pantalla. */
.fac-importar-informe { max-height: 260px; overflow-y: auto; }
.fac-rejilla--informe { font-size: .82rem; }
.fac-importar-pie { display: flex; gap: 8px; justify-content: flex-end; margin-top: 12px; }

/* --- Notas de campo y dato no monetario ----------------------------------- */
.fac-campo-nota {
  display: block; margin-top: 4px; font-size: .74rem; line-height: 1.35;
  color: hsl(var(--muted-foreground));
}
/* Aviso de que el documento mueve stock: mismo tono que .fac-trz--pendiente. */
.fac-nota--stock {
  background: hsl(var(--warning) / .12);
  color: hsl(38 92% 28%);
}
:root[data-theme="dark"] .fac-nota--stock { color: hsl(38 92% 68%); }

/* Faltaba el tono «correcto» en la familia .fac-aviso--*: lo usa el informe de
   validación de la importación para decir «N líneas listas». */
.fac-aviso--ok { background: hsl(var(--success) / .1); border-color: hsl(var(--success) / .4);
                 color: hsl(var(--success)); }
/* Bultos: es un recuento, no un importe. Se separa del bloque de dinero. */
.fac-total--dato {
  border-top: 1px dashed hsl(var(--border)); margin-top: 6px; padding-top: 8px;
  color: hsl(var(--muted-foreground));
}

/* En móvil el panel de importación va a una columna y los botones a lo ancho,
   igual que hacen .fac-borrador-campos y .fac-medidas-campos. */
@media (max-width: 640px) {
  .fac-importar-campos { grid-template-columns: 1fr; }
  .fac-importar-pie { flex-direction: column-reverse; }
  .fac-importar-pie .fac-btn { width: 100%; }
  .fac-lineas-pie .fac-btn-linea { flex: 1 1 100%; text-align: center; }
  .fac-importar-informe { max-height: 50vh; }
}

/* ===========================================================================
   INFORMACIÓN DE STOCKS  (ficha de PEDIDO y de ALBARÁN)

   Botón por línea y modal de consulta. El modal reutiliza .modal-crud-* para
   heredar el fondo, la caja y el encabezado del resto del ERP; aquí solo va lo
   propio de la rejilla de cifras.

   El modal se crea sobre document.body, no dentro de la pestaña, así que NO
   necesita ninguna precaución con los ids sufijados. Sí necesita —como todo lo
   demás de esta ficha— que ninguna clase contenga la subcadena «tab».
   =========================================================================== */
/* TOKENS DE COLOR DE LOS MODALES DE LA FICHA.

   --success, --warning, --destructive e --info NO existen en :root: se declaran
   dentro de .docs-view (styles.css:930-937) y de .ter-view (2652-2659), que son
   los divs de cada vista. Estos dos modales se cuelgan de document.body —tienen
   que hacerlo, porque dentro de la pestaña heredarían el sufijo _${tabId} de
   todos los ids—, así que quedan FUERA de ese ámbito y sin ellos
   `hsl(var(--destructive))` es un color inválido: el aviso de error se pintaba
   sin fondo ni borde rojo, igual que uno normal.

   Mismo remedio y mismo motivo que el bloque de .ter-view. */
.fac-stock-box, .fac-traspaso-box {
  --success: 142 71% 38%;
  --warning: 38 92% 50%;
  --destructive: 0 72% 51%;
  --info: 208 88% 40%;
}
:root[data-theme="dark"] .fac-stock-box,
:root[data-theme="dark"] .fac-traspaso-box {
  --success: 142 60% 45%;
  --warning: 38 92% 55%;
  --destructive: 0 70% 55%;
  --info: 208 90% 62%;
}

.fac-stock-linea {
  background: none; border: none; cursor: pointer; font-size: 1rem;
  padding: 6px; border-radius: 6px; line-height: 1;
  color: hsl(var(--muted-foreground));
  transition: background .15s, color .15s;
}
@media (hover: hover) {
  .fac-stock-linea:hover:not(:disabled) {
    background: hsl(var(--primary) / .12); color: hsl(var(--primary));
  }
}
.fac-stock-linea:active:not(:disabled) { background: hsl(var(--primary) / .18); }
.fac-stock-linea:disabled { opacity: .3; cursor: not-allowed; }

/* Tres botones por fila (stock + editar + borrar) no caben en los 44 px de
   .fac-col-acc ni en los 84 de --doble. Y el albarán de venta llega a CUATRO
   (stock + editar + duplicar + borrar). */
.fac-col-acc--triple { width: 124px; }
.fac-col-acc--cuadruple { width: 164px; }

.fac-stock-box { max-width: 720px; }
.fac-stock-cargando { color: hsl(var(--muted-foreground)); font-size: .9rem; }
.fac-stock-titulo {
  font-size: .82rem; font-weight: 700; text-transform: uppercase;
  letter-spacing: .05em; color: hsl(var(--muted-foreground));
  margin: 4px 0 10px;
}
.fac-stock-titulo + .fac-rejilla-wrap { margin-bottom: 4px; }

/* Las diez cifras del almacén del documento. auto-fit para que pase sola de 3 a
   2 y a 1 columna sin más reglas. */
.fac-stock-rejilla {
  display: grid; gap: 8px; margin-bottom: 14px;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
}
.fac-stock-dato {
  display: flex; align-items: baseline; justify-content: space-between; gap: 8px;
  padding: 8px 10px; border: 1px solid hsl(var(--border)); border-radius: 9px;
  background: hsl(var(--muted) / .25);
}
.fac-stock-etq {
  font-size: .74rem; text-transform: uppercase; letter-spacing: .04em;
  color: hsl(var(--muted-foreground)); min-width: 0; overflow-wrap: anywhere;
}
.fac-stock-val { font-weight: 700; font-variant-numeric: tabular-nums; white-space: nowrap; }
.fac-stock-dato--alerta {
  border-color: hsl(var(--destructive) / .45); background: hsl(var(--destructive) / .08);
}
.fac-stock-dato--alerta .fac-stock-val { color: hsl(var(--destructive)); }

.fac-stock-nota {
  font-size: .78rem; color: hsl(var(--muted-foreground));
  margin: -6px 0 16px; line-height: 1.45;
}
.fac-stock-tabla { font-size: .86rem; }
.fac-stock-fila--doc { background: hsl(var(--primary) / .07); }
.fac-stock-fila--doc td { font-weight: 600; }

/* A ≤640 px la tabla del desglose se convierte en tarjetas con el MISMO
   mecanismo que la rejilla de líneas (thead oculto + data-col en ::before), que
   es lo que evita el solapamiento. Las reglas de .fac-rejilla ya lo hacen: aquí
   solo hay que dejar que la caja del modal ocupe el ancho útil. */
@media (max-width: 640px) {
  .fac-stock-box { max-width: 96vw; }
  .fac-stock-rejilla { grid-template-columns: 1fr 1fr; }
}
@media (max-width: 420px) {
  .fac-stock-rejilla { grid-template-columns: 1fr; }
}


/* ===========================================================================
   CONVERSIÓN ENTRE DOCUMENTOS — el modal de «Convertir en…»

   Reutiliza la caja de modal del ERP (.modal-crud-*) y el vocabulario `fac-*`
   de la ficha; aquí solo van las piezas que no existían: la cabecera de flujo
   origen → destino, los campos del formulario y la barra de acciones inferior.

   Como el de stocks, el modal vive sobre document.body y ninguna clase contiene
   la subcadena «tab».
   =========================================================================== */

/* Los distintivos de la cadena de documentos, cuando llevan a otra ficha. Es un
   <button> con aspecto de .fac-chip: hereda todo salvo lo que el navegador
   impone a los botones. */
.fac-chip--enlace {
  border: none; cursor: pointer; font-family: inherit; line-height: inherit;
  transition: filter .15s;
}
@media (hover: hover) {
  .fac-chip--enlace:hover { filter: brightness(.92); text-decoration: underline; }
}
.fac-chip--enlace:active { filter: brightness(.85); }

.fac-traspaso-box { max-width: 680px; }

/* Origen → destino. Lo primero que se lee, y por eso ocupa el ancho entero. */
.fac-traspaso-flujo {
  display: flex; align-items: stretch; gap: 12px; margin-bottom: 16px;
}
.fac-traspaso-doc {
  flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px;
  padding: 10px 12px; border: 1px solid hsl(var(--border)); border-radius: 9px;
  background: hsl(var(--muted) / .25);
}
.fac-traspaso-doc--destino {
  border-color: hsl(var(--primary) / .45); background: hsl(var(--primary) / .07);
}
.fac-traspaso-doc strong { font-size: 1rem; overflow-wrap: anywhere; }
.fac-traspaso-doc small {
  font-size: .78rem; color: hsl(var(--muted-foreground)); overflow-wrap: anywhere;
}
.fac-traspaso-tipo {
  font-size: .72rem; text-transform: uppercase; letter-spacing: .05em;
  font-weight: 700; color: hsl(var(--muted-foreground));
}
.fac-traspaso-flecha {
  align-self: center; font-size: 1.4rem; color: hsl(var(--muted-foreground));
  flex: 0 0 auto;
}

/* Cada sección del formulario. `details` y `div` comparten estilo para que
   plegar «Qué se hereda» no cambie el ritmo vertical de la ventana. */
.fac-traspaso-bloque {
  border-top: 1px solid hsl(var(--border)); padding-top: 14px; margin-bottom: 16px;
}
.fac-traspaso-bloque > h4,
.fac-traspaso-bloque > summary {
  font-size: .82rem; font-weight: 700; text-transform: uppercase;
  letter-spacing: .05em; color: hsl(var(--muted-foreground)); margin: 0 0 10px;
}
.fac-traspaso-bloque > summary { cursor: pointer; }
.fac-traspaso-bloque > summary::marker { color: hsl(var(--muted-foreground)); }

.fac-traspaso-campos {
  display: grid; gap: 12px; margin-bottom: 8px;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
}
.fac-traspaso-campo { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.fac-traspaso-campo label { font-size: .82rem; font-weight: 600; }
.fac-traspaso-campo input,
.fac-traspaso-campo select,
.fac-traspaso-campo textarea {
  padding: 8px 10px; border: 1px solid hsl(var(--border)); border-radius: 7px;
  background: hsl(var(--background)); color: hsl(var(--foreground));
  font-size: .9rem; font-family: inherit; width: 100%; min-width: 0;
}
.fac-traspaso-campo textarea { resize: vertical; min-height: 4.2rem; }
.fac-traspaso-campo input:focus-visible,
.fac-traspaso-campo select:focus-visible,
.fac-traspaso-campo textarea:focus-visible {
  outline: 2px solid hsl(var(--primary) / .5); outline-offset: 1px;
}
.fac-traspaso-req { color: hsl(var(--destructive)); }
.fac-traspaso-ayuda {
  font-size: .78rem; color: hsl(var(--muted-foreground)); line-height: 1.45;
}

/* Barra de acciones. Fuera del cuerpo con scroll: el botón de convertir tiene
   que seguir a la vista por larga que sea la lista de artículos. */
.fac-traspaso-pie {
  display: flex; justify-content: flex-end; gap: 10px; flex-wrap: wrap;
  padding: 14px 22px; border-top: 1px solid hsl(var(--border));
  background: hsl(var(--muted) / .3);
}

@media (max-width: 640px) {
  .fac-traspaso-box { max-width: 96vw; }
  /* En vertical la flecha «→» miente: el flujo pasa a ser de arriba abajo. */
  .fac-traspaso-flujo { flex-direction: column; gap: 8px; }
  .fac-traspaso-flecha { transform: rotate(90deg); }
  .fac-traspaso-campos { grid-template-columns: 1fr; }
  .fac-traspaso-pie > .fac-btn { flex: 1; justify-content: center; }
}


/* --- SELECCIÓN DE ALBARANES A FACTURAR -------------------------------------
   Reutiliza la caja, los campos y el pie del modal de conversión: es la misma
   operación vista de otra manera y no merece un modal distinto. Lo único propio
   es la LISTA con casillas, que allí no hace falta.

   La lista tiene scroll con tope en 40vh: un cliente con veinte albaranes
   abiertos es normal, y sin el tope el botón «Facturar» se iría fuera de la
   pantalla. El pie ya está fuera del cuerpo con scroll, así que se queda fijo. */
.fac-fab-intro {
  margin: 0 0 10px; font-size: .86rem; color: hsl(var(--muted-foreground));
  line-height: 1.5;
}
.fac-fab-lista {
  display: flex; flex-direction: column; gap: 2px;
  max-height: 40vh; overflow-y: auto;
  border: 1px solid hsl(var(--border)); border-radius: 8px; padding: 6px;
}
.fac-fab-fila {
  display: grid; grid-template-columns: auto 1fr auto auto;
  align-items: center; gap: 10px;
  padding: 7px 8px; border-radius: 6px; cursor: pointer;
  font-size: .86rem;
  /* 40px de alto mínimo: es una casilla que se marca con el dedo. */
  min-height: 40px;
}
.fac-fab-fila:hover { background: hsl(var(--muted) / .5); }
.fac-fab-fila input { width: 17px; height: 17px; cursor: pointer; }
.fac-fab-ref { font-weight: 600; }
.fac-fab-fecha { color: hsl(var(--muted-foreground)); font-size: .8rem; }
.fac-fab-total { font-variant-numeric: tabular-nums; text-align: right; }
/* La previsión de cuántas facturas saldrán. Cambia al marcar y desmarcar, así
   que va destacada: es la respuesta a la pregunta que trae al usuario aquí. */
.fac-fab-prevision {
  margin: 10px 0 4px; font-size: .86rem; font-weight: 600;
}

@media (max-width: 640px) {
  /* La fecha cabe debajo de la referencia; el importe se queda a la derecha. */
  .fac-fab-fila { grid-template-columns: auto 1fr auto; }
  .fac-fab-fecha { grid-column: 2; grid-row: 2; }
}


/* ===========================================================================
   M7 — ÁREAS TÁCTILES Y BARRA DE HERRAMIENTAS EN MÓVIL

   POR QUÉ ESTE BLOQUE ESTÁ AL FINAL DEL ARCHIVO
   No es desorden: es la única posición en la que funciona. Todas estas reglas
   pesan lo MISMO que las que sobrescriben (una clase), así que a igual
   especificidad decide el orden de aparición. Y las reglas base viven repartidas
   por todo el archivo, muchas de ellas por debajo de los bloques @media de móvil:
   `.pg-btn`  (min-height: 36px)      está bajo la paginación,
   `.btn-outline` y `.btn-row-edit`   bajo los botones de la rejilla,
   `.fac-dup-linea` (32px)            bajo la ficha de albarán,
   `.table-search` (max-width: 320px) bajo el buscador.
   Puesto arriba —donde se intentó primero— este bloque perdía contra todas ellas y
   no cambiaba nada. Es la misma trampa que ya documenta la columna de acciones
   ancladas, y por el mismo motivo se resuelve igual: yendo DESPUÉS.

   QUÉ CORRIGE
   La ficha (.fac-*) subió sus controles a 44px en la Fase 35. El chrome que rodea a
   la rejilla —paginación, barra de herramientas, botones de fila, listas de los
   desplegables— se quedó con los 30-40px pensados para el ratón, y son justo los
   controles del recorrido «seleccionar → Acciones → editar → paginar».

   Se tocan SOLO tamaños de caja (min-height/min-width/flex), nunca color ni tipografía,
   y todo dentro de @media (max-width: 640px): por encima de ese ancho no cambia
   absolutamente nada.
   =========================================================================== */
@media (max-width: 640px) {
  /* --- Controles de la barra y de la paginación --- */
  .pg-btn,
  .btn-outline,
  .btn-new-row,
  .page-size-select,
  .form-select,
  .table-search { min-height: 44px; }

  /* Botones de icono: además de alto, ancho — un ✏️ de 44x28 se sigue fallando. */
  .btn-row-edit,
  .btn-row-del,
  .modal-crud-close { min-width: 44px; min-height: 44px; }
  /* Los botones de acción de los modales CRUD (Añadir/Guardar) medían 40 px en
     móvil; el resto de controles del modal ya iban a 44. */
  .modal-crud-actions .btn-primary, .modal-crud-actions .btn-outline { min-height: 44px; }

  /* Filas de los desplegables (Columnas, Acciones, destinatarios del e-mail): se
     pulsan en vertical, así que basta con el alto. `align-items: center` mantiene el
     texto centrado en la caja ya más alta. */
  .column-item,
  .doc-actions-item,
  .email-recipient { min-height: 44px; align-items: center; }

  /* --- Botones de línea de la ficha ---
     📊 (información de stocks, en pedido y albarán) medía ~28px y ⧉ (duplicar línea,
     albarán) 32px, al lado de una papelera que ya estaba en 44: además de fallarse al
     pulsar, la celda de acciones quedaba descuadrada.
     ✏️ (editar línea) entra aquí con más motivo que ninguno: en móvil es el único
     camino a la edición, porque el doble toque sobre la fila no es fiable. */
  .fac-stock-linea,
  .fac-edit-linea,
  .fac-dup-linea { width: 44px; height: 44px; padding: 0; }

  /* --- Reparto de la barra de herramientas ---
     Con `flex: 1` a secas, los 6 controles (Acciones, Buscar, Mostrar-N, Serie,
     Ordenar, Columnas) se repartían a capricho del contenido: a 360px caían en 3-4
     filas, ~180px de alto, y con la hoja de detalle ocupando 80dvh esa barra ya no
     cabía en el hueco de arriba. Ahora el buscador ocupa su propia fila y los tres
     desplegables van de dos en dos.
     El `max-width` hay que anularlo explícitamente: son los 320px del buscador de
     escritorio los que impedían que llenara la fila. */
  .table-search { flex: 1 1 100%; max-width: none; margin-left: 0; }
  .page-size-select,
  :is(#filtro-serie, [id^="filtro-serie_"]),
  :is(#table-sort, [id^="table-sort_"]) { flex: 1 1 calc(50% - 6px); min-width: 0; }
  /* Los ids de la plantilla se sufijan al clonar la pestaña (filtro-serie_data_…), de
     ahí el :is(#x, [id^="x_"]) — misma convención que el resto del archivo. */
}

/* ============================================================================
   FACTURACIÓN AUTOMÁTICA DE ALBARANES (venta) / FACTURACIÓN DE ALBARANES DE
   PROVEEDOR (compra) — #view-facturacion, facturacion.js.
   La tabla es una .data-table normal, así que hereda el zebrado, la cabecera
   pegajosa y las TARJETAS de móvil (≤640px) sin una regla más. Aquí solo va lo
   propio: la barra de controles, la casilla, la fila marcada/bloqueada y el
   cuadro de resultado.
   ============================================================================ */
/* Tokens --success/--warning/--destructive/--info: solo existen dentro de
   .docs-view/.ter-view/.art-view, y .fac-aviso los necesita. Mismo remedio que
   .art-view. */
.fal-view {
  --success: 142 71% 38%;
  --warning: 38 92% 50%;
  --destructive: 0 72% 51%;
  --info: 208 88% 40%;
}
:root[data-theme="dark"] .fal-view {
  --success: 142 60% 45%;
  --warning: 38 92% 55%;
  --destructive: 0 70% 55%;
  --info: 208 90% 62%;
}

.fal-head { row-gap: 8px; }
.fal-head h3 { flex: 1 1 100%; }
/* La barra de controles ocupa su propia fila, alineada a la izquierda: son
   varios controles y el «margin-left: auto» de .table-controls los apretaría
   contra el borde derecho. */
.fal-controles { margin-left: 0; flex: 1 1 auto; align-items: flex-end; }
.fal-campo {
  display: flex; flex-direction: column; gap: 3px;
  font-size: .75rem; font-weight: 600; color: hsl(var(--muted-foreground));
  text-transform: uppercase; letter-spacing: .04em;
}
.fal-select, .fal-fecha {
  padding: 7px 10px; border: 1px solid hsl(var(--border)); border-radius: 10px;
  font-size: .9rem; background: hsl(var(--card)); color: hsl(var(--foreground));
  min-height: 36px; text-transform: none; letter-spacing: 0; font-weight: 400;
}
.fal-select { min-width: 180px; }
.fal-select:focus, .fal-fecha:focus { outline: none; border-color: hsl(var(--primary));
  box-shadow: 0 0 0 3px hsl(var(--primary) / 0.15); }
.fal-btn-facturar { min-height: 36px; white-space: nowrap; }
.fal-info { flex: 1 1 100%; }

.fal-resultado:empty { display: none; }
.fal-resultado { padding: 0 22px 10px; }
.fal-resultado-box { position: relative; padding-right: 40px; }
.fal-resultado-cerrar {
  position: absolute; top: 6px; right: 8px;
  border: 0; background: transparent; cursor: pointer; font-size: 1rem;
  color: inherit; opacity: .7; min-width: 32px; min-height: 32px;
}
.fal-resultado-cerrar:hover { opacity: 1; }
.fal-facturas { list-style: none; padding: 0; margin: 8px 0 0; display: flex; flex-wrap: wrap; gap: 8px 14px; }
.fal-facturas li { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.fal-abrir { min-height: 32px; padding: 4px 10px; }
.fal-origen { font-size: .8rem; color: hsl(var(--muted-foreground)); }
.fal-resultado-avisos { margin: 8px 0 0; padding-left: 18px; font-size: .85rem; }

.fal-avisos { margin: 0; padding: 0 22px 14px 40px; font-size: .8rem; color: hsl(var(--muted-foreground)); }
.fal-avisos:empty { display: none; }
.fal-avisos li { margin: 2px 0; }

.fal-error { margin: 0 0 12px; }

/* Casilla grande y centrada; la columna no necesita más ancho que ella. */
.fal-tabla th.fal-col-check, .fal-tabla td.fal-col-check { width: 44px; text-align: center; }
.fal-check { width: 20px; height: 20px; cursor: pointer; accent-color: hsl(var(--primary)); }
.fal-check:disabled { cursor: not-allowed; opacity: .45; }
.fal-tabla tbody tr.fal-fila--marcada { background: hsl(var(--primary) / .08); }
.fal-tabla tbody tr.fal-fila--marcada:nth-child(even) { background: hsl(var(--primary) / .11); }
.fal-tabla tbody tr.fal-fila--bloqueada:not(.fal-fila--marcada) td { color: hsl(var(--muted-foreground)); }
.fal-motivo, .fal-suref { display: block; font-size: .75rem; font-weight: 400; color: hsl(var(--muted-foreground)); }
.fal-motivo { color: hsl(38 92% 32%); }

@media (max-width: 820px) {
  .fal-select { min-width: 140px; }
}
@media (max-width: 640px) {
  /* Los controles llenan la fila de dos en dos; Facturar, a todo el ancho. */
  .fal-controles { width: 100%; }
  .fal-campo { flex: 1 1 calc(50% - 6px); min-width: 0; }
  .fal-select, .fal-fecha { width: 100%; min-width: 0; }
  .fal-controles .btn-outline { flex: 1 1 calc(33% - 8px); justify-content: center; min-height: 44px; }
  .fal-btn-facturar { flex: 1 1 100%; min-height: 48px; }
  /* En tarjeta la casilla va a la derecha de su etiqueta «FACTURAR», con zona
     táctil de 44px. */
  .fal-tabla tbody td.fal-col-check { width: auto; text-align: right; align-items: center; }
  .fal-check { width: 24px; height: 24px; margin: 8px 0; }
  .fal-tabla tbody tr.fal-fila--marcada { border-color: hsl(var(--primary)); box-shadow: inset 3px 0 0 hsl(var(--primary)); }
  .fal-resultado, .fal-avisos { padding-left: 16px; padding-right: 16px; }
  .fal-avisos { padding-left: 30px; }
}

/* ==========================================================================
   CONSULTAS GUARDADAS (Inteligencia)
   ========================================================================== */
/* Panel del modo «consultas» dentro de la pestaña del asistente (vive en la plantilla
   view-chat y se clona con ella). PC: lista a la izquierda + editor + resultados.
   Móvil: todo en columna, la lista plegable y el editor/resultados con alturas
   mínimas en vez de porcentajes (con 30 %/50 % fijos el editor se quedaba en ~60 px). */
.consultas-layout { display: flex; height: 100%; flex: 1; min-height: 0; overflow: hidden; background: hsl(var(--background)); }
.consultas-sidebar { width: 280px; flex-shrink: 0; border-right: 1px solid hsl(var(--border)); display: flex; flex-direction: column; min-height: 0; background: hsl(var(--card)); }
.consultas-list-toggle {
  display: flex; align-items: center; gap: 8px; width: 100%; padding: 16px; margin: 0;
  border: none; border-bottom: 1px solid hsl(var(--border)); background: transparent;
  color: hsl(var(--foreground)); font-family: inherit; font-size: 0.95rem; font-weight: 600;
  text-align: left; cursor: default;
}
.consultas-list-count { color: hsl(var(--muted-foreground)); font-weight: 500; }
.consultas-list-caret { display: none; margin-left: auto; color: hsl(var(--muted-foreground)); transition: transform .18s; }
.consultas-list { list-style: none; padding: 0; margin: 0; overflow-y: auto; flex: 1; min-height: 0; }
.consultas-list li { padding: 12px 16px; border-bottom: 1px solid hsl(var(--border)); cursor: pointer; transition: background 0.2s; }
.consultas-list li:hover { background: hsl(var(--muted) / 0.5); }
.consultas-list li.active { background: hsl(var(--primary) / 0.1); border-left: 3px solid hsl(var(--primary)); padding-left: 13px; }
.consultas-list li .titulo { display: block; font-weight: 500; font-size: 0.9rem; color: hsl(var(--foreground)); margin-bottom: 4px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.consultas-list li .fecha { display: block; font-size: 0.75rem; color: hsl(var(--muted-foreground)); }
.consultas-list li.consultas-aviso { cursor: default; color: hsl(var(--muted-foreground)); font-size: 0.85rem; }
.consultas-list li.consultas-aviso-error { color: hsl(var(--danger)); }

.consultas-main { flex: 1; display: flex; flex-direction: column; min-width: 0; min-height: 0; }
.consultas-editor-header { display: flex; padding: 12px 16px; gap: 12px 16px; border-bottom: 1px solid hsl(var(--border)); background: hsl(var(--card)); align-items: center; flex-wrap: wrap; }
.consultas-editor-meta { flex-basis: 100%; display: flex; flex-wrap: wrap; gap: 4px 14px; font-size: 0.78rem; color: hsl(var(--muted-foreground)); }
.consultas-estado { font-weight: 600; color: hsl(var(--foreground)); }
.consultas-editor-header input { flex: 1; min-width: 200px; padding: 8px 12px; border: 1px solid hsl(var(--border)); border-radius: 6px; font-size: 0.95rem; background: hsl(var(--background)); color: hsl(var(--foreground)); }
.consultas-actions { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
/* .btn-primary es el botón de los formularios de acceso (100 % de ancho, 13px de
   relleno): aquí va en fila con los .btn-outline, con su mismo alto. */
.consultas-actions .btn-primary { width: auto; margin-top: 0; padding: 6px 16px; min-height: 36px; border-radius: 6px; font-size: 0.85rem; box-shadow: none; }
.consultas-actions button:disabled { opacity: .5; cursor: not-allowed; }
.consultas-sql-editor { flex: 1; min-height: 120px; resize: none; padding: 16px; border: none; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; font-size: 0.9rem; background: hsl(var(--background)); color: hsl(var(--foreground)); outline: none; line-height: 1.5; }
.consultas-resultados { height: 40%; min-height: 200px; border-top: 1px solid hsl(var(--border)); display: flex; flex-direction: column; background: hsl(var(--card)); }
.consultas-resultados-header { display: flex; align-items: center; gap: 12px; padding: 8px 16px; border-bottom: 1px solid hsl(var(--border)); background: hsl(var(--muted) / 0.3); }
.consultas-resultados-header h4 { margin: 0; font-size: 0.85rem; font-weight: 600; text-transform: uppercase; color: hsl(var(--muted-foreground)); }
.consultas-resultados-header .meta { margin: 0; flex: 1; }
.consultas-resultados-header .btn-outline { min-height: 28px; padding: 4px 10px; font-size: 0.8rem; margin-left: auto; }
.consultas-tabla-container { flex: 1; min-height: 0; overflow: auto; padding: 12px 16px; }
/* La tabla es la misma que la del chat (.table-wrap); aquí el scroll lo pone el contenedor. */
.consultas-tabla-container .table-wrap { max-height: none; }
.consultas-vacio { margin: 0; color: hsl(var(--muted-foreground)); font-size: 0.88rem; }

@media (max-width: 768px) {
  .consultas-layout { flex-direction: column; }
  .consultas-sidebar { width: 100%; flex: 0 0 auto; max-height: 40vh; border-right: none; border-bottom: 1px solid hsl(var(--border)); }
  .consultas-list-toggle { cursor: pointer; min-height: 44px; padding: 10px 16px; }
  .consultas-list-caret { display: inline; }
  .consultas-sidebar.plegada .consultas-list { display: none; }
  .consultas-sidebar.plegada .consultas-list-caret { transform: rotate(-90deg); }
  /* El resto desplaza en bloque: editor y resultados con alturas mínimas tocables. */
  .consultas-main { overflow-y: auto; }
  .consultas-editor-header { padding: 10px 14px; }
  .consultas-actions { width: 100%; }
  .consultas-actions button, .consultas-actions .btn-primary { flex: 1 1 calc(50% - 8px); min-height: 44px; justify-content: center; }
  .consultas-sql-editor { flex: 0 0 auto; min-height: 160px; padding: 12px 14px; }
  .consultas-resultados { height: auto; flex: 0 0 auto; min-height: 220px; }
  .consultas-resultados-header .btn-outline { min-height: 36px; }
  .consultas-tabla-container { padding: 10px 14px; max-height: 60vh; }
}

/* ===========================================================================
   CLIENTE MAIL (sept. 2026) — pestaña ✉️ (plantilla #view-correo, correo.js).
   Escritorio: lista | detalle. Móvil (≤640px): un panel cada vez; la clase
   .correo-en-detalle del contenedor esconde la lista mientras hay algo abierto.
   Botones propios (.correo-btn): .btn-primary/.btn-link ocupan el 100 % del ancho.
   =========================================================================== */
.correo-wrap { flex: 1; min-height: 0; display: flex; }
.correo-lista-panel {
  width: 380px; flex-shrink: 0; display: flex; flex-direction: column; min-height: 0;
  border-right: 1px solid hsl(var(--border));
}
.correo-detalle-panel { flex: 1; min-width: 0; min-height: 0; display: flex; flex-direction: column; overflow-y: auto; }
.correo-barra {
  display: flex; align-items: center; justify-content: space-between; gap: 8px; flex-wrap: wrap;
  padding: 12px 14px; border-bottom: 1px solid hsl(var(--border));
}
.correo-titulo { font-size: 1rem; font-weight: 700; margin: 0; display: flex; align-items: center; gap: 8px; }
.correo-badge {
  font-size: .72rem; font-weight: 600; padding: 2px 8px; border-radius: 999px;
  background: hsl(var(--primary)); color: hsl(var(--primary-foreground));
}
.correo-barra-acciones { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
.correo-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  min-height: 36px; padding: 6px 12px; border-radius: 10px; cursor: pointer;
  border: 1px solid hsl(var(--border)); background: hsl(var(--muted) / 0.5);
  color: hsl(var(--foreground)); font-family: inherit; font-size: .88rem;
}
.correo-btn:hover { border-color: hsl(var(--primary) / 0.5); background: hsl(var(--muted)); }
.correo-btn:disabled { opacity: .5; cursor: not-allowed; }
.correo-btn--primario {
  background: hsl(var(--primary)); color: hsl(var(--primary-foreground)); border-color: transparent; font-weight: 600;
}
.correo-btn--primario:hover { background: hsl(var(--primary)); opacity: .92; }
.correo-aviso {
  display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap;
  margin: 10px 12px 0; padding: 10px 12px; border-radius: 10px; font-size: .88rem;
  background: hsl(var(--muted) / 0.7); color: hsl(var(--foreground));
}
.correo-aviso[hidden] { display: none; }
.correo-aviso--error { background: hsl(var(--destructive) / .1); color: hsl(var(--destructive)); }
.correo-aviso--ok { background: hsl(var(--success) / .1); color: hsl(var(--success)); }
.correo-lista { list-style: none; margin: 0; padding: 0; overflow-y: auto; flex: 1; min-height: 0; }
.correo-cargando { padding: 18px 14px; color: hsl(var(--muted-foreground)); font-size: .9rem; }
.correo-item {
  width: 100%; display: grid; grid-template-columns: 1fr auto; gap: 2px 10px; text-align: left;
  padding: 10px 14px; border: none; border-bottom: 1px solid hsl(var(--border));
  background: transparent; color: hsl(var(--foreground)); font-family: inherit; cursor: pointer;
  border-left: 3px solid transparent;
}
.correo-item:hover { background: hsl(var(--muted) / 0.5); }
.correo-item--activo { background: hsl(var(--primary) / 0.1); border-left-color: hsl(var(--primary)); }
.correo-item--nuevo { border-left-color: hsl(var(--success)); }
.correo-item-de, .correo-item-asunto { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }
.correo-item-de { font-size: .9rem; }
.correo-item-fecha { font-size: .76rem; color: hsl(var(--muted-foreground)); white-space: nowrap; }
.correo-item-asunto { grid-column: 1 / -1; font-size: .84rem; color: hsl(var(--muted-foreground)); }
.correo-item--no-leido .correo-item-de, .correo-item--no-leido .correo-item-asunto { font-weight: 700; color: hsl(var(--foreground)); }
.correo-paginacion {
  display: flex; align-items: center; justify-content: center; gap: 10px; padding: 8px;
  border-top: 1px solid hsl(var(--border)); font-size: .82rem; color: hsl(var(--muted-foreground));
}
.correo-paginacion[hidden] { display: none; }
.correo-vacio { margin: auto; padding: 24px; color: hsl(var(--muted-foreground)); font-size: .92rem; }
.correo-vacio[hidden], .correo-lectura[hidden], .correo-redaccion[hidden], .correo-r-reenvio[hidden] { display: none; }
.correo-lectura { display: flex; flex-direction: column; flex: 1; min-height: 0; padding: 12px 18px 18px; gap: 10px; }
.correo-lectura-barra { display: flex; align-items: center; justify-content: space-between; gap: 8px; flex-wrap: wrap; }
.correo-volver { display: none; }      /* solo en móvil: en escritorio la lista sigue a la vista */
.correo-l-asunto { font-size: 1.15rem; font-weight: 700; margin: 4px 0 0; overflow-wrap: anywhere; }
.correo-cabeceras { display: grid; grid-template-columns: auto 1fr; gap: 4px 12px; margin: 0; font-size: .86rem; }
.correo-cabeceras dt { font-weight: 600; color: hsl(var(--muted-foreground)); }
.correo-cabeceras dd { margin: 0; overflow-wrap: anywhere; }
.correo-adjuntos { display: flex; flex-wrap: wrap; gap: 8px; }
.correo-adjuntos:empty { display: none; }
.correo-adjunto {
  display: inline-flex; align-items: center; gap: 6px; max-width: 100%; min-height: 36px;
  padding: 6px 10px; border-radius: 999px; cursor: pointer; font-family: inherit; font-size: .84rem;
  border: 1px solid hsl(var(--border)); background: hsl(var(--card)); color: hsl(var(--foreground));
}
.correo-adjunto span { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.correo-adjunto small { color: hsl(var(--muted-foreground)); white-space: nowrap; }
.correo-adjunto:hover { border-color: hsl(var(--primary) / 0.5); }
/* Chip + su acción de «documento de compra» (Lectura Activa / Importar XML). */
.correo-adjunto-grupo { display: inline-flex; align-items: center; gap: 4px; max-width: 100%; flex-wrap: wrap; }
.correo-adjunto-accion {
  display: inline-flex; align-items: center; min-height: 36px; padding: 6px 10px; border-radius: 999px;
  cursor: pointer; font-family: inherit; font-size: .8rem; font-weight: 600; white-space: nowrap;
  border: 1px solid hsl(var(--primary) / 0.45); background: hsl(var(--primary) / 0.08); color: hsl(var(--primary));
}
.correo-adjunto-accion:hover { background: hsl(var(--primary) / 0.16); }
.correo-adjunto-accion:disabled { opacity: .5; cursor: progress; }
.correo-cuerpo { flex: 1; min-height: 320px; display: flex; flex-direction: column; gap: 8px; }
.correo-texto {
  margin: 0; white-space: pre-wrap; overflow-wrap: anywhere; font-family: inherit; font-size: .92rem;
  line-height: 1.5; color: hsl(var(--foreground));
}
.correo-ver-html { align-self: flex-start; }
/* El iframe no puede medir su contenido (sandbox sin same-origin): ocupa el hueco. */
.correo-html { flex: 1; width: 100%; min-height: 420px; border: 1px solid hsl(var(--border)); border-radius: 10px; background: #fff; }
.correo-redaccion { display: flex; flex-direction: column; gap: 12px; padding: 12px 18px 18px; max-width: 820px; }
.correo-redaccion .campo input[type="text"], .correo-redaccion textarea {
  padding: 10px 12px; border: 1px solid hsl(var(--border)); border-radius: 10px; font-family: inherit;
  font-size: .95rem; background: hsl(var(--card)); color: hsl(var(--foreground)); width: 100%; box-sizing: border-box;
}
.correo-redaccion textarea { resize: vertical; min-height: 200px; line-height: 1.45; }
.correo-redaccion .campo input:focus, .correo-redaccion textarea:focus {
  outline: none; border-color: hsl(var(--primary)); box-shadow: 0 0 0 3px hsl(var(--primary) / 0.15);
}
.correo-r-de { margin: 0; font-size: .86rem; color: hsl(var(--muted-foreground)); }
.correo-r-de--aviso { color: hsl(var(--destructive)); }
.correo-r-reenvio { margin: 0; font-size: .84rem; color: hsl(var(--muted-foreground)); overflow-wrap: anywhere; }

@media (max-width: 640px) {
  .correo-lista-panel { width: 100%; border-right: none; }
  .correo-detalle-panel { display: none; }
  .correo-wrap.correo-en-detalle .correo-lista-panel { display: none; }
  .correo-wrap.correo-en-detalle .correo-detalle-panel { display: flex; }
  .correo-volver { display: inline-flex; }
  .correo-lectura, .correo-redaccion { padding: 10px 12px 14px; }
  .correo-btn, .correo-adjunto, .correo-adjunto-accion { min-height: 44px; }
  .correo-html { min-height: 60vh; }
}

/* ============================================================================
   CONTABILIDAD → ASIENTOS: borrador de asiento (view-asiento-borrador, asientos.js)
   ----------------------------------------------------------------------------
   Reutiliza .data-panel/.data-head/.data-table/.doc-cabecera/.art-campo; aquí solo
   va lo propio: inputs dentro de las celdas, la fila enlazada (línea con cargo y
   abono), la barra de totales y la cabecera del asiento en el panel de apuntes.
   ============================================================================ */
/* Mismos tokens que .fal-view/.art-view: .ter-aviso y .art-chip los necesitan. */
.asi-view, .asi-modal-box {
  --success: 142 71% 38%;
  --warning: 38 92% 50%;
  --destructive: 0 72% 51%;
  --info: 208 88% 40%;
}
:root[data-theme="dark"] .asi-view, :root[data-theme="dark"] .asi-modal-box {
  --success: 142 60% 45%;
  --warning: 38 92% 55%;
  --destructive: 0 70% 55%;
  --info: 208 90% 62%;
}
.asi-head { row-gap: 8px; }
.asi-btn-traspasar { white-space: nowrap; }
/* .btn-primary es width:100% por defecto (la hereda de los formularios de login y
   modales): en la barra de la vista va a su tamaño, junto a los demás botones. */
.asi-controles .asi-btn-traspasar { width: auto; margin-top: 0; padding: 9px 16px; font-size: .9rem; }
.asi-cabecera { align-items: end; }
.asi-campo-titulo { grid-column: span 2; }
.asi-tipo, .asi-origen { min-height: 36px; display: flex; align-items: center; font-size: .9rem; }
.asi-avisos:empty { display: none; }
.asi-avisos { padding: 10px 16px 0; display: flex; flex-direction: column; gap: 6px; }
.asi-avisos ul { margin: 4px 0 0; padding-left: 18px; }

.asi-tabla tbody td { padding: 6px 8px; vertical-align: middle; }
.asi-tabla input {
  width: 100%; min-width: 0; padding: 6px 8px; font: inherit; font-size: .85rem;
  border: 1px solid hsl(var(--border)); border-radius: 6px;
  background: hsl(var(--card)); color: hsl(var(--foreground));
}
.asi-tabla input:focus { outline: none; border-color: hsl(var(--primary));
  box-shadow: 0 0 0 2px hsl(var(--primary) / .15); }
.asi-tabla input:disabled { background: hsl(var(--muted) / .5); color: hsl(var(--muted-foreground)); }
.asi-tabla input.num { text-align: right; font-variant-numeric: tabular-nums; }
.asi-tabla input.asi-error { border-color: hsl(var(--destructive)); }
.asi-tabla td[data-col="Cuenta"] { min-width: 130px; }
.asi-tabla td[data-col="Título de la cuenta"] { min-width: 150px; max-width: 220px; white-space: normal;
  font-size: .8rem; color: hsl(var(--muted-foreground)); }
.asi-tabla td[data-col="Texto"] { min-width: 150px; }
.asi-tabla td[data-col="Debe"], .asi-tabla td[data-col="Haber"] { min-width: 100px; }
.asi-tabla td[data-col="Doc. serie"] { width: 70px; }
.asi-tabla td[data-col="Doc. nº"] { width: 90px; }
.asi-tabla tr.asi-par td:first-child { box-shadow: inset 3px 0 0 hsl(var(--info)); }
.asi-par-ico { color: hsl(var(--info)); margin-left: 4px; cursor: help; }
.asi-quitar { min-width: 30px; min-height: 30px; padding: 2px 6px; margin-right: 6px; }
.asi-tabla td[data-col="Nº"] { white-space: nowrap; }
.asi-tabla td[data-col="Nº"] .cell-val { display: inline-flex; align-items: center; gap: 2px; }
.asi-tabla td[data-col="Doc. fecha"] { min-width: 140px; }

.asi-totales {
  flex: 0 0 auto; display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 10px 28px;
  padding: 12px 16px; border-top: 1px solid hsl(var(--border)); background: hsl(var(--muted) / .3);
}
.asi-total { display: flex; flex-direction: column; align-items: flex-end; gap: 2px; }
.asi-total-etq { font-size: .72rem; font-weight: 600; letter-spacing: .03em; text-transform: uppercase;
  color: hsl(var(--muted-foreground)); }
.asi-total-val { font-size: 1rem; font-weight: 700; font-variant-numeric: tabular-nums; }
.asi-total--ok .asi-total-val { color: hsl(var(--success)); }
.asi-total--mal .asi-total-val { color: hsl(var(--destructive)); }

.asi-modal-box { max-width: 760px; }
.asi-nota { font-size: .85rem; color: hsl(var(--muted-foreground)); margin: 0 0 12px; }
.asi-modal-box tbody tr { cursor: pointer; }
/* Ficha de un asiento real (view-asiento): la barra lleva «Modificar» o, si el tipo no
   es NOR, la etiqueta «Solo consulta» (su explicación va en el title). */
.asi-consulta { font-size: .85rem; color: hsl(var(--muted-foreground)); cursor: help;
  border: 1px dashed hsl(var(--border)); border-radius: 999px; padding: 4px 12px; }
/* Diez columnas: con los textos en una sola línea (.data-table es nowrap) el Haber y el
   documento quedaban fuera por la derecha. Solo en escritorio: en móvil son tarjetas. */
@media (min-width: 641px) {
  .asf-tabla thead th, .asf-tabla tbody td { padding-left: 12px; padding-right: 12px; }
  .asf-tabla td[data-col="Título de la cuenta"], .asf-tabla td[data-col="Texto"] {
    white-space: normal; min-width: 160px; max-width: 280px;
  }
}

@media (max-width: 640px) {
  .asi-controles { width: 100%; }
  .asi-controles .btn-outline { flex: 1 1 calc(33% - 8px); justify-content: center; min-height: 44px; }
  .asi-controles .asi-btn-traspasar { flex: 1 1 100%; width: 100%; min-height: 48px; }
  .asi-campo-titulo { grid-column: span 1; }
  .asi-tabla td[data-col] { min-width: 0; width: auto; }
  .asi-tabla input { min-height: 40px; }
  .asi-quitar { min-width: 44px; min-height: 44px; }
  .asi-totales { justify-content: space-between; }
}

/* ===========================================================================
   TESORERÍA (30-09-2026) — Diario IVA, Listados IVA, Retenciones, Inmueble y
   Hacienda (tesoreria.js). Reutilizan la barra de filtros de Facturación
   (.fal-head/.fal-controles/.fal-campo) y las tablas .data-table (tarjetas en
   móvil). Aquí solo va lo que no existía: varias tablas en la misma pantalla
   (resultado + rectificativas + totales) y las tarjetas de modelos de Hacienda.
   =========================================================================== */
.tes-scroll { overflow: auto; gap: 14px; }
/* La tabla principal ocupa lo que quede y hace scroll dentro (cabecera fija);
   los bloques secundarios (rectificativas, totales) no se encogen. */
.tes-scroll > .tes-bloque { display: flex; flex-direction: column; min-height: 0; flex: none; }
.tes-scroll > .tes-bloque--principal { flex: 1 1 auto; min-height: 220px; }
.tes-scroll > .tes-bloque--principal > .data-table-wrap { flex: 1; min-height: 0; }
.tes-scroll > .tes-bloque:not(.tes-bloque--principal) > .data-table-wrap { flex: none; max-height: 40vh; }
.tes-bloque-titulo {
  font-size: .9rem; font-weight: 600; margin: 0 0 8px; color: hsl(var(--foreground));
  display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
}
.tes-bloque-titulo small { font-weight: 400; font-size: .78rem; color: hsl(var(--muted-foreground)); }
.data-table tbody tr.tes-fila-total td { font-weight: 700; }

/* Hacienda: una tarjeta por modelo (reutiliza .art-seccion como tarjeta). */
.tes-modelos { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 14px; }
.tes-modelos .art-seccion { margin-bottom: 0; display: flex; flex-direction: column; gap: 8px; }
.tes-modelo-cab { display: flex; align-items: center; justify-content: space-between; gap: 8px; flex-wrap: wrap; }
.tes-modelo-cab .art-seccion-titulo { margin: 0; padding: 0; border: 0; }
.tes-modelo-desc { font-size: .85rem; margin: 0; }
.tes-modelo-meta { font-size: .76rem; color: hsl(var(--muted-foreground)); margin: 0; }
.tes-modelo-meta code { font-size: .74rem; }

/* Retenciones: los cinco importes, de dos en dos o de tres en tres. */
.tes-grid-importes { margin-top: 12px; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); }

/* .btn-primary es de ancho completo por defecto (el del login); en la barra de
   filtros va a su tamaño, a la altura de los campos, como el resto de botones. */
.tes-view .fal-controles .btn-primary {
  width: auto; margin-top: 0; padding: 7px 16px; min-height: 36px; white-space: nowrap;
}

/* Impuestos → Auxiliares: maestro con filas que se eligen (fila-sel es la clase de
   selección de la rejilla) y, debajo, el detalle con su propia barra de acciones. */
.aux-maestro tbody tr { cursor: pointer; }
/* Maestro y detalle comparten la pantalla: el maestro no se encoge a su mínimo
   (220 px, 2 filas) en cuanto aparece el detalle; cada uno lleva su scroll y la
   pantalla entera hace scroll si no caben. */
.tes-scroll > .aux-maestro.tes-bloque--principal { flex: none; min-height: 0; }
.tes-scroll > .aux-maestro > .data-table-wrap { flex: none; max-height: 42vh; }
/* «cuenta · título» es largo: se parte en líneas para que se vean las cuatro
   cuentas sin scroll lateral. */
.aux-detalle .data-table td { white-space: normal; }
.aux-detalle .data-table td.col-acciones { white-space: nowrap; }
.aux-acciones { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin: 0 0 8px; }
.aux-acciones .btn-primary, .aux-acciones .btn-outline {
  width: auto; margin-top: 0; padding: 7px 14px; min-height: 36px; white-space: nowrap;
}
.aux-nota { font-size: .8rem; color: hsl(var(--muted-foreground)); margin: 0; }
.aux-cuenta-tit { display: block; font-size: .76rem; color: hsl(var(--muted-foreground)); }
#modal-impaux .art-campo--ancho { grid-column: 1 / -1; }
/* Parámetros Hacienda: los seis grupos de consulta en tarjetas, dos columnas si
   caben y una en móvil (min(100%, …) evita el desborde a 375 px). Los grupos
   que siguen al primero en el modal se separan un poco del anterior. */
.tes-scroll > .hpa-grupos {
  flex: none; display: grid; gap: 14px; align-items: start;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 440px), 1fr));
}
.hpa-grupos .art-seccion { margin-bottom: 0; }
#modal-impaux .art-grid > .art-seccion-titulo { margin: 6px 0 0; }
#modal-impaux .art-grid > .art-seccion-titulo:first-child { margin-top: 0; }
.aux-titulo-mayus { text-transform: uppercase; }

@media (max-width: 640px) {
  /* En móvil las filas son tarjetas y la pantalla entera hace scroll: ningún
     bloque se encoge (si no, el principal quedaba a 0 y los totales encima). */
  .tes-scroll > .tes-bloque,
  .tes-scroll > .tes-bloque--principal { flex: none; min-height: auto; }
  .tes-scroll > .tes-bloque > .data-table-wrap { flex: none; max-height: none; overflow: visible; }
  .tes-modelos { grid-template-columns: 1fr; }
  .tes-view .fal-controles .btn-primary { flex: 1 1 100%; min-height: 48px; justify-content: center; }
  #modal-local .art-campo--ancho { grid-column: auto; }
  .aux-acciones .btn-primary, .aux-acciones .btn-outline { flex: 1 1 100%; min-height: 44px; justify-content: center; white-space: normal; }
}

/* ===========================================================================
   DESPLEGABLE EMPRESAS (oct. 2026) — proyectos.js
   Nota fija de las cinco rejillas, modal de maestro y ficha de proyecto. La
   ficha reutiliza .ter-view y sus piezas; aquí solo lo que es suyo.
   =========================================================================== */
.emp-nota {
  margin: 10px 12px 4px; padding: 10px 12px; border-radius: 8px; font-size: .84rem;
  line-height: 1.6; background: hsl(var(--info) / .08); border: 1px solid hsl(var(--info) / .3);
  color: hsl(var(--foreground));
}
.emp-nota .art-chip { margin: 2px 2px 2px 0; }
.emp-estado { display: flex; gap: 6px; flex-wrap: wrap; margin: 0 0 12px; }
#modal-maestro .art-campo--total { grid-column: 1 / -1; }

/* Ficha de proyecto en CONSULTA (doble clic / toque en la rejilla): un <fieldset>
   envuelve General y Máquina/Vehículo y se pone disabled. Sin marco ni márgenes,
   como .fac-doc-fieldset; min-width: 0 evita el min-content del fieldset, que en
   móvil empujaría la ficha fuera de la pantalla. */
.pry-fieldset { border: 0; padding: 0; margin: 0; min-width: 0; }

/* Fichas de TERCERO y de ROL en CONSULTA (doble clic / toque en la rejilla): el
   mismo mecanismo. El fieldset ya deshabilita todo lo de dentro; además se OCULTAN
   los botones que escriben al momento —los de cada fila de direcciones, contactos
   y bancos, los «+ Añadir», el alta de rol y el ⚙ de gestión de agrupaciones—
   para que no parezcan pulsables. Ver terAplicarModo / rolAplicarModo (app.js). */
.ter-fieldset, .rol-fieldset { border: 0; padding: 0; margin: 0; min-width: 0; }
.ter-fieldset:disabled .ter-fila-acciones,
.ter-fieldset:disabled [data-nuevo],
.ter-fieldset:disabled .ter-roles-alta,
.ter-fieldset:disabled .btn-catalogo,
.rol-fieldset:disabled .agr-btn,
.rol-fieldset:disabled .btn-catalogo { display: none; }

/* Resumen económico: cuatro tarjetas de cifra. */
.pry-resumen {
  flex: 0 0 auto; margin: 0 0 12px; padding: 14px 16px; border-radius: 10px;
  background: hsl(var(--card)); border: 1px solid hsl(var(--border));
}
.pry-resumen-titulo { margin: 0 0 10px; font-size: .95rem; }
.pry-tarjetas { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 10px; }
.pry-tarjeta {
  display: flex; flex-direction: column; gap: 4px; padding: 10px 12px; border-radius: 8px;
  background: hsl(var(--muted) / .45); min-width: 0;
}
.pry-tarjeta > span { font-size: .78rem; color: hsl(var(--muted-foreground)); font-weight: 500; }
.pry-tarjeta > strong {
  font-size: 1.15rem; font-variant-numeric: tabular-nums; overflow-wrap: anywhere;
}
.pry-tarjeta--beneficio { background: hsl(var(--primary) / .07); }
.pry-pos { color: hsl(var(--success)); }
.pry-neg { color: hsl(var(--destructive)); }
.pry-nota { margin: 8px 0 0; font-size: .78rem; color: hsl(var(--muted-foreground)); }
.pry-referencia { margin-top: 8px; font-size: .82rem; }
.pry-referencia summary { cursor: pointer; color: hsl(var(--muted-foreground)); }
.pry-referencia-valores { display: flex; flex-wrap: wrap; gap: 6px 18px; margin-top: 6px; }
.pry-referencia-valores small { color: hsl(var(--muted-foreground)); margin-right: 4px; }

.pry-check-fila { display: flex; align-items: center; gap: 8px; font-size: .88rem; min-height: 38px; }
.pry-enlace {
  border: none; background: none; padding: 0 0 0 6px; cursor: pointer; font-size: .74rem;
  color: hsl(var(--primary)); text-decoration: underline;
}

/* Sub-pestañas de PROYECTOS: un escalón por debajo de las de la ficha. */
.prd-barra { margin-top: 2px; }
.prd-tab { font-size: .85rem; padding: 7px 13px; }
.prd-panel { padding-top: 6px; }
.pry-docs .data-table tbody tr.fila-doc { cursor: pointer; }
.pry-docs .data-table-wrap { max-height: none; }

/* --- Fase 2 (05-10-2026): ajustes visuales de la ficha de proyecto ---------
   Todo con ámbito .pry-view: no cambia ninguna otra ficha. */
/* Cabecera: Cancelar y Guardar con la misma altura (.btn-primary es 100 % y más
   alto por defecto; .btn-outline, 6×12 px). */
.pry-view .ter-acciones > button {
  width: auto; margin-top: 0; min-height: 38px; padding: 8px 18px;
  font-size: .9rem; line-height: 1.2; border-radius: 8px;
}
/* Resumen: Beneficio con más peso que las otras tres cifras. */
.pry-view .pry-tarjeta--beneficio {
  background: hsl(var(--primary) / .08); box-shadow: inset 3px 0 0 hsl(var(--primary));
}
.pry-view .pry-tarjeta--beneficio > strong { font-size: 1.35rem; }
.pry-resumen-titulo small {
  font-size: .75rem; font-weight: 400; color: hsl(var(--muted-foreground));
}
.pry-view .ter-subtitulo { align-items: center; gap: 6px 10px; }
.pry-resumen-pie {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 18px; margin-top: 8px;
}
.pry-resumen-pie > .pry-nota { margin: 0; }
.pry-resumen-pie > .pry-referencia { margin-top: 0; }
.pry-resumen-pie > .pry-referencia[open] { flex-basis: 100%; }
/* Sub-pestañas de PROYECTOS como control segmentado: un escalón por debajo de la
   barra subrayada de la ficha, para que la jerarquía se lea de un vistazo. */
.pry-view .prd-barra {
  display: inline-flex; max-width: 100%; gap: 4px; padding: 4px; border: 0;
  border-radius: 10px; background: hsl(var(--muted) / .7);
}
.pry-view .prd-tab {
  border: 0; border-radius: 7px; padding: 6px 14px; font-size: .84rem;
}
.pry-view .prd-tab--activa {
  background: hsl(var(--card)); color: hsl(var(--primary));
  box-shadow: 0 1px 3px rgba(15, 23, 42, .15);
}
.pry-docs-caja > .pry-nota { margin: 10px 0 6px; }
.pry-docs-caja .prd-panel { padding-top: 0; }

@media (max-width: 640px) {
  .pry-tarjetas { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; }
  .pry-resumen { padding: 12px; }
  .pry-resumen-titulo { margin-bottom: 8px; }
  /* El subtítulo partía el título en dos líneas: su texto está también en
     «¿Cómo se calcula?». */
  .pry-resumen-titulo small { display: none; }
  .pry-resumen-pie { gap: 4px 14px; margin-top: 6px; }
  .pry-resumen-pie summary { font-size: .78rem; }
  .pry-tarjeta { padding: 7px 10px; gap: 2px; }
  .pry-tarjeta > strong, .pry-view .pry-tarjeta--beneficio > strong { font-size: 1.05rem; }
  .pry-tab, .prd-tab { padding: 9px 12px; }
  .pry-view .prd-tab { padding: 8px 10px; }
  .pry-barra, .prd-barra { -webkit-overflow-scrolling: touch; }
  .emp-nota { margin: 8px 0 4px; }
  /* Cabecera compacta: título con su código y chips; los botones, en la fila de abajo. */
  .pry-view .ter-cabecera { gap: 8px 10px; }
  .pry-view .ter-identidad { flex: 1 1 100%; min-width: 0; }
  .pry-view .ter-acciones { width: auto; margin-left: auto; }
  .pry-view .ter-acciones > button { flex: 0 0 auto; min-height: 44px; padding: 8px 14px; }
}

/* --- Modal de maestro por grupos (fase 2) ------------------------------------ */
#modal-maestro .modal-crud-box { max-width: 760px; }
#modal-maestro .emp-grupo + .emp-grupo,
#modal-maestro .emp-estado-bloque + .emp-grupo { margin-top: 14px; }
#modal-maestro .emp-grupo .art-seccion-titulo { margin-bottom: 10px; }
.emp-grupo-aviso {
  margin: -4px 0 10px; font-size: .78rem; color: hsl(var(--muted-foreground));
}
.emp-estado-bloque {
  padding: 10px 12px; border-radius: 10px; border: 1px solid hsl(var(--border));
  background: hsl(var(--muted) / .35);
}
.emp-estado-fila { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; }
.emp-estado-chip { font-size: .85rem; padding: 4px 12px; }
.emp-estado-check {
  display: inline-flex; align-items: center; gap: 8px; margin-left: auto;
  font-size: .9rem; font-weight: 500; cursor: pointer; min-height: 36px;
}
.emp-estado-check input { width: 18px; height: 18px; margin: 0; }
.emp-estado-nota { margin: 8px 0 0; font-size: .8rem; color: hsl(var(--muted-foreground)); }
.emp-imagen-fila { display: flex; align-items: center; gap: 10px; }
.emp-imagen-fila select { flex: 1 1 auto; min-width: 0; }
.emp-logo {
  display: inline-flex; align-items: center; justify-content: center; flex: 0 0 auto;
  width: 64px; height: 40px; border-radius: 6px; border: 1px solid hsl(var(--border));
  background: #fff; overflow: hidden;
}
.emp-logo img { max-width: 100%; max-height: 100%; object-fit: contain; }
.emp-logo small { font-size: .68rem; color: #64748b; text-align: center; padding: 0 2px; }
.emp-envio {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 10px 12px; border-radius: 8px; border: 1px dashed hsl(var(--border));
}
.emp-envio-texto { flex: 1 1 220px; font-size: .88rem; }
.emp-envio-texto--vacia { color: hsl(var(--muted-foreground)); font-style: italic; }
#modal-maestro .art-campo input[readonly],
#modal-maestro .art-campo textarea[readonly] {
  background: hsl(var(--muted) / .6); color: hsl(var(--muted-foreground));
}
@media (max-width: 640px) {
  #modal-maestro .modal-crud-actions {
    position: sticky; bottom: -20px; z-index: 1; margin: 0 -22px -20px; padding: 10px 22px 14px;
    background: hsl(var(--card)); border-top: 1px solid hsl(var(--border));
  }
  #modal-maestro .modal-crud-actions > button { flex: 1 1 0; min-height: 44px; }
  .emp-estado-check { margin-left: 0; min-height: 44px; }
  .emp-estado-check input { width: 22px; height: 22px; }
}
