/* ==========================================================================
   GRADANN · 3. COMPONENTES
   Piezas reutilizables. Si una pantalla necesita algo, se añade AQUÍ y se usa
   por su clase; no se escribe CSS suelto en la vista.
   --------------------------------------------------------------------------
   Índice
     3.1  Iconos
     3.2  Botones
     3.3  Campos de formulario
     3.4  Rejilla
     3.5  Paneles
     3.6  Tarjetas de indicador (KPI)
     3.7  Tablas
     3.8  Etiquetas de estado y pastillas
     3.9  Listas de acciones y de actividad
     3.10 Pares dato/valor
     3.11 Estados vacíos y avisos
     3.12 Pestañas
     3.13 Barra de filtros
     3.14 Paginación
   ========================================================================== */

/* --------------------------------------------------------------------------
   3.1  Iconos
   Van como <svg class="icono"><use href="#i-algo"></use></svg>, contra el
   sprite que el layout imprime una sola vez. Heredan el color del texto, así
   que no hay una variante por color.
   -------------------------------------------------------------------------- */

.icono {
  width: 1.15em;
  height: 1.15em;
  flex: 0 0 auto;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.75;
  stroke-linecap: round;
  stroke-linejoin: round;
  vertical-align: -.2em;
}
.icono--grande { width: 1.5em; height: 1.5em; }

/* --------------------------------------------------------------------------
   3.2  Botones
   -------------------------------------------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--e1);
  min-height: var(--control-alto);
  padding: var(--e1) var(--e2);
  border: 1px solid var(--borde-fuerte);
  border-radius: var(--radio-chico);
  background: var(--superficie);
  color: var(--texto);
  font-weight: 500;
  line-height: 1.2;
  text-decoration: none;
  cursor: pointer;
  white-space: nowrap;
}
/* El hover de la variante básica. Cuidado con esto: `.btn:hover` pesa 0,2,0 y
   `.btn--azul` sólo 0,1,0, así que la regla de abajo GANA al fondo de
   cualquier variante. Por eso **toda variante que ponga fondo tiene que poner
   también su propio fondo en :hover**; si no, al pasar el cursor se queda con
   este gris casi blanco y con el texto blanco de la variante: blanco sobre
   blanco, el botón desaparece. Es lo que le pasaba a `.btn--azul`, que sólo
   declaraba `filter`. Lo vigila tests/reglas.php. */
.btn:hover { background: var(--superficie-2); text-decoration: none; }
.btn:disabled, .btn[aria-disabled="true"] { opacity: .5; cursor: not-allowed; }

.btn--primario {
  background: var(--rojo);
  border-color: var(--rojo);
  color: var(--texto-inv);
}
.btn--primario:hover { background: var(--rojo-oscuro); border-color: var(--rojo-oscuro); }

.btn--azul {
  background: var(--azul);
  border-color: var(--azul);
  color: var(--texto-inv);
}
.btn--azul:hover { background: var(--azul-oscuro); border-color: var(--azul-oscuro); }

.btn--plano {
  border-color: transparent;
  background: transparent;
  color: var(--azul);
}
.btn--plano:hover { background: var(--azul-suave); }

/* Deliberadamente más bajo que --control-alto: vive dentro de las filas de una
   tabla, donde 40 px por botón engordarían la fila un tercio. */
.btn--chico { min-height: 0; padding: var(--e0) var(--e1); font-size: .9rem; }

/* …salvo con el dedo, donde 23 px no se aciertan. La excepción va AQUÍ y no en
   layout.css: componentes.css se enlaza después, así que una regla de igual
   especificidad allí perdía siempre y los botones de las tablas se quedaban en
   23 px en el móvil. Las reglas de un componente viven con el componente. */
@media (pointer: coarse) {
  .btn--chico { min-height: 44px; }
}

/* --------------------------------------------------------------------------
   3.3  Campos de formulario
   -------------------------------------------------------------------------- */

.campo { display: flex; flex-direction: column; gap: var(--e0); }
.campo label { font-size: .85rem; font-weight: 500; color: var(--texto-2); }

.campo input,
.campo select,
.campo textarea,
input[type="text"], input[type="search"], input[type="number"],
input[type="date"], input[type="email"], input[type="password"],
select, textarea {
  height: var(--control-alto);
  padding: var(--e1) var(--e2);
  border: 1px solid var(--borde-fuerte);
  border-radius: var(--radio-chico);
  background: var(--superficie);
}

/* Altura EXACTA y no mínima: el campo de fecha se iba 3 px por encima de sus
   vecinos por el selector de calendario que dibuja dentro. El textarea es la
   excepción evidente, porque crece. */
textarea { height: auto; min-height: calc(var(--control-alto) * 2); }

/* El campo de fecha del navegador trae su propio selector de calendario, que
   ocupa sitio DENTRO del control. Con 12ch la fecha salía como «dd/mm/a» y el
   icono encima del texto. Medido: necesita unos 160 px para mostrar
   dd/mm/aaaa entero y su icono. */
input[type="date"],
input[type="datetime-local"],
input[type="month"] {
  min-width: 11rem;
}
input:focus, select:focus, textarea:focus { border-color: var(--azul); }

.campo input.error, .campo select.error { border-color: var(--rojo); }

.campo__ayuda { display: block; font-size: .82rem; color: var(--texto-3); }

/* Una nota dentro de una celda recortada ENVUELVE en vez de cortarse. La celda
   recorta su texto principal a una línea con puntos suspensivos, y para eso
   lleva su `title`; pero la nota es otro texto, no está en ese `title`, y se
   quedaba cortada sin ninguna forma de leerla entera. */
.recorte .campo__ayuda {
  white-space: normal;
  overflow-wrap: anywhere;
}
.campo__error { display: block; font-size: .82rem; color: var(--rojo); font-weight: 500; }

/* Fila de campos: cada uno mide lo que pide su contenido.
   Se alinea por ARRIBA, no por abajo: con `flex-end`, un campo con texto de
   ayuda debajo empujaba su control hacia arriba y quedaba desalineado de sus
   vecinos. Alineando arriba, las etiquetas coinciden, los controles coinciden
   —todos miden `--control-alto`— y la ayuda cuelga sin mover a nadie. */
.fila { display: flex; flex-wrap: wrap; gap: var(--e3); align-items: flex-start; }
.fila--apretada { gap: var(--e2); }

/* La etiqueta reserva su línea aunque esté vacía, para que el control quede a
   la misma altura que el del campo de al lado. La altura va en un token y no
   en `em`: la etiqueta mide .85rem y el espaciador del botón heredaba 1rem,
   así que «la misma» 1.45em daba 19,7 px en una y 23,2 px en el otro, y los
   botones caían 3 px por debajo de las cajas. Ver DECISIONES.md · D-082. */
.campo label { min-height: var(--etiqueta-alto); }

/* Un botón dentro de una fila de campos no tiene etiqueta: se le reserva esa
   misma línea para que caiga sobre el renglón de los controles. */
.campo--boton { justify-content: flex-start; }
.campo--boton::before { content: ''; display: block; min-height: var(--etiqueta-alto); }

/* --------------------------------------------------------------------------
   3.5  Caja de escaneo
   --------------------------------------------------------------------------
   La usan once pantallas y la barra superior, y hasta ahora no tenía ni una
   regla: era un `div` en flujo normal, así que la caja de texto se apoyaba en
   la línea base tipográfica y caía 3 px por debajo del botón de la cámara. Dos
   vistas se lo habían apañado añadiéndole `fila fila--apretada` a mano, que es
   la señal de que faltaba el componente.

   `scanner.js` marca `.scan--activo` durante 250 ms al leer un código; ese
   destello tampoco estaba definido, así que la lectura correcta no se veía.
   -------------------------------------------------------------------------- */

.scan {
  display: flex;
  align-items: center;
  gap: var(--e1);
}
.scan input { flex: 0 1 auto; }
.scan__camara { flex: 0 0 auto; }

/* Confirmación de lectura: un destello verde y corto. Sin animación de entrada
   —el código ya se leyó— y respetando a quien pide menos movimiento. */
.scan--activo input {
  border-color: var(--verde);
  box-shadow: 0 0 0 2px var(--verde-suave);
}
@media (prefers-reduced-motion: no-preference) {
  .scan--activo input { transition: box-shadow .12s ease-out; }
}

/* --------------------------------------------------------------------------
   3.4  Rejilla
   -------------------------------------------------------------------------- */

/* El `min(…, 100%)` no es adorno: sin él, `minmax(18ch, 1fr)` obliga a la
   columna a medir 18ch aunque el contenedor mida menos, y la rejilla se sale
   por la derecha arrastrando a la página entera. Pasó de verdad en la ficha
   del proveedor a 390 px: un bloque de indicadores, que pide 15rem, dentro de
   un panel de 137 px. Con `min()` la columna baja hasta el ancho disponible en
   vez de desbordar. */
.grid        { display: grid; gap: var(--e3); }
/* Un hijo de rejilla NO encoge por debajo de su contenido a menos que se le
   diga: es el `min-width: auto` de la especificación, y la causa de casi todo
   desborde horizontal que parece venir de la nada. */
.grid > *    { min-width: 0; }
.grid--auto  { grid-template-columns: repeat(auto-fit, minmax(min(18ch, 100%), 1fr)); }
.grid--medio { grid-template-columns: repeat(auto-fit, minmax(min(28ch, 100%), 1fr)); }
.grid--ancho { grid-template-columns: repeat(auto-fit, minmax(min(40ch, 100%), 1fr)); }

/* --------------------------------------------------------------------------
   3.5  Paneles
   La cabecera va en azul profundo, igual que el menú: es la misma capa de la
   interfaz y así el conjunto se lee como una sola estructura.
   -------------------------------------------------------------------------- */

/* SIN `overflow: hidden`.
   Recortaba bien la esquina, pero convertía el panel en el contenedor de
   referencia del `position: sticky` de la cabecera de tabla: el
   desplazamiento de la barra superior pasaba a aplicarse DENTRO del panel y la
   cabecera se pintaba 51 px más abajo, encima de la primera fila. Las esquinas
   las redondea cada parte por su cuenta. Ver DECISIONES.md · D-081. */
.panel {
  background: var(--superficie);
  border: 1px solid var(--borde);
  border-radius: var(--radio);
  box-shadow: var(--sombra);
  margin-bottom: var(--e3);
}

/* Cada extremo del panel redondea el suyo, sin recortar a los hijos. */
.panel > :first-child { border-top-left-radius: var(--radio); border-top-right-radius: var(--radio); }
.panel > :last-child  { border-bottom-left-radius: var(--radio); border-bottom-right-radius: var(--radio); }

/* La cabecera de panel va en MARINO, igual que la de tabla: es lo que muestran
   las tres referencias del cliente y es lo que separa de un vistazo la
   estructura del dato. Antes era al revés —acero por defecto y una variante
   `--marino` que no usaba ni una sola vista, es decir, código muerto— y el
   resultado eran paneles que se confundían con su propio contenido. */
.panel__cabecera {
  display: flex;
  align-items: center;
  gap: var(--e2);
  padding: var(--e2) var(--e3);
  background: var(--marino);
  border-bottom: 1px solid var(--marino);
  color: var(--texto-inv);
  font-weight: 600;
  font-size: .92rem;
  letter-spacing: .02em;
  text-transform: uppercase;
}
/* `:not(.btn)` no es un adorno: `.panel__cabecera a` pesa 0,1,1 y le gana a
   `.btn`, que es 0,1,0. Sin la exclusión, un `<a class="btn">` dentro de una
   cabecera marina se quedaba con el fondo blanco del botón Y el texto claro del
   enlace: blanco sobre blanco, el botón desaparecía. Pasaba en «Ver todos» y
   «Reposición sugerida» del panel. Misma familia que D-087. */
.panel__cabecera a:not(.btn) { color: var(--marino-texto); }
.panel__cabecera a:not(.btn):hover { color: var(--texto-inv); }
.panel__cabecera .icono { color: var(--marino-tenue); }
.panel__cabecera .acciones { margin-left: auto; display: flex; gap: var(--e1); align-items: center; }

/* Dato al final de la cabecera: «5 productos · 8 etiquetas», «Total 440.07». */
.panel__cabecera__dato {
  margin-left: auto;
  color: var(--marino-texto);
  font-weight: 500;
  text-transform: none;
  letter-spacing: 0;
}

/* La variante clara, para un panel secundario que no debe competir con el
   principal: una barra de filtros, un panel dentro de otro. */
.panel__cabecera--acero {
  background: var(--acero);
  border-bottom-color: var(--borde);
  color: var(--texto);
}
.panel__cabecera--acero a:not(.btn) { color: var(--azul); }
.panel__cabecera--acero .icono { color: var(--texto-2); }
.panel__cabecera--acero .panel__cabecera__dato { color: var(--texto-2); }

.panel__cuerpo { padding: var(--e3); }
.panel__cuerpo--pegado { padding: 0; }

.panel__pie {
  padding: var(--e2) var(--e3);
  border-top: 1px solid var(--borde);
  background: var(--superficie-2);
  text-align: center;
}

/* --------------------------------------------------------------------------
   3.6  Tarjetas de indicador (KPI)
   Una cifra grande, su rótulo y un pie. El color dice de qué habla: rojo lo
   que exige actuar hoy, ámbar lo que puede esperar, verde lo que va bien.
   -------------------------------------------------------------------------- */

.indicadores {
  display: grid;
  /* Ver la nota de .grid--auto: sin min() esto desborda dentro de un panel
     estrecho en lugar de encogerse. */
  grid-template-columns: repeat(auto-fit, minmax(min(15rem, 100%), 1fr));
  gap: var(--e2);
  margin-bottom: var(--e3);
}

.kpi {
  display: flex;
  align-items: center;
  gap: var(--e2);
  padding: var(--e2) var(--e3);
  border: 1px solid var(--borde);
  border-radius: var(--radio);
  background: var(--superficie);
  color: inherit;
  text-decoration: none;
}
a.kpi:hover { text-decoration: none; border-color: var(--borde-fuerte); }

.kpi__icono {
  display: grid;
  place-items: center;
  width: 2.6em;
  height: 2.6em;
  flex: 0 0 auto;
  border-radius: 999px;
  background: var(--superficie-2);
  color: var(--texto-2);
}

/* `flex: 1 1 0` y no `auto`: al declarar el cuerpo como contenedor de consulta
   su tamaño deja de depender del contenido, así que con base `auto` el reparto
   flex le daba CERO —y `cqi`, que es el 1 % de ese ancho, valía cero también—.
   Con base 0 y crecimiento 1 se lleva todo el espacio que sobra tras el icono,
   que es lo que hay que medir. */
.kpi__cuerpo { min-width: 0; flex: 1 1 0; }
.kpi__rotulo { font-size: .82rem; color: var(--texto-2); }
.kpi__cifra  { font-family: var(--mono); font-size: 1.5rem; font-weight: 700; line-height: 1.15; }
.kpi__pie    { font-size: .78rem; color: var(--texto-3); }

/* Tonos. Cada uno tiñe fondo, borde, círculo y cifra a la vez. */
.kpi--azul    { background: var(--azul-suave);    border-color: var(--azul-borde); }
.kpi--azul    .kpi__icono { background: #D6E7FB; color: var(--azul); }
.kpi--azul    .kpi__cifra { color: var(--azul); }

.kpi--ambar   { background: var(--ambar-suave);   border-color: var(--ambar-borde); }
.kpi--ambar   .kpi__icono { background: #FBEBCE; color: var(--ambar); }
.kpi--ambar   .kpi__cifra { color: var(--ambar); }

.kpi--rojo    { background: var(--rojo-suave);    border-color: var(--rojo-borde); }
.kpi--rojo    .kpi__icono { background: #F9D6D6; color: var(--rojo); }
.kpi--rojo    .kpi__cifra { color: var(--rojo); }

.kpi--naranja { background: var(--naranja-suave); border-color: var(--naranja-borde); }
.kpi--naranja .kpi__icono { background: #FBDFC3; color: var(--naranja); }
.kpi--naranja .kpi__cifra { color: var(--naranja); }

.kpi--verde   { background: var(--verde-suave);   border-color: var(--verde-borde); }
.kpi--verde   .kpi__icono { background: #CFEBD9; color: var(--verde); }
.kpi--verde   .kpi__cifra { color: var(--verde); }

/* --------------------------------------------------------------------------
   3.7  Tablas
   En móvil siguen siendo tablas: se ocultan columnas por prioridad, no se
   convierten en tarjetas.
   -------------------------------------------------------------------------- */

/* El envoltorio se vuelve contenedor de scroll SÓLO cuando la tabla no cabe.
   Si fuera siempre `overflow-x: auto` pasaría a ser el scrollport del
   `position: sticky` de la cabecera y la empujaría hacia abajo tapando filas.
   app.js pone y quita la clase midiendo el ancho real. */
/* La envoltura de la tabla es CONTENEDOR de consulta: las columnas se esconden
   según lo ancha que sea LA TABLA, no la pantalla.
   Con consultas de medios, una tabla dentro de un panel de 558 px en una
   pantalla de 820 no ocultaba nada —para el navegador la pantalla es ancha— y
   se salía por la derecha: se veía «Por paga…» y «151,25…». Es el mismo error
   que con las cifras: el ancho lo decide el contenedor, no la ventana.
   Ver DECISIONES.md · D-093. */
.tabla-envoltura {
  overflow-x: visible;
  container-type: inline-size;
  container-name: tabla;
}
.tabla-envoltura--desborda { overflow-x: auto; }
.tabla-envoltura--desborda table.tabla thead th { position: static; }

table.tabla {
  width: 100%;
  border-collapse: collapse;
  font-size: .95rem;
}

/* Azul profundo y texto blanco: la cabecera es estructura, no un dato más.
   Es lo que separa de un vistazo los rótulos de las cifras. */
table.tabla thead th {
  position: sticky;
  top: var(--tabla-sticky-top);
  z-index: 5;
  padding: var(--e1) var(--e2);
  background: var(--marino);
  color: var(--texto-inv);
  font-size: .72rem;
  font-weight: 600;
  letter-spacing: .05em;
  text-transform: uppercase;
  text-align: left;
  white-space: nowrap;
}
table.tabla thead th.num { text-align: right; }

table.tabla td {
  padding: var(--e1) var(--e2);
  border-bottom: 1px solid var(--borde);
  vertical-align: middle;
}

table.tabla tbody tr:hover { background: var(--superficie-2); }
table.tabla tbody tr[data-abrir] { cursor: pointer; }

table.tabla tfoot td {
  border-top: 1px solid var(--borde-fuerte);
  border-bottom: 0;
  font-weight: 600;
  background: var(--superficie-2);
}

.apretada { width: 1%; white-space: nowrap; }

/* Celdas que recortan con puntos suspensivos. El mínimo evita que la columna
   principal se lleve todo el ancho y deje a las demás en nada. */
table.tabla td.recorte,
table.tabla th.recorte {
  max-width: 0;
  min-width: 14ch;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
table.tabla td.recorte--principal,
table.tabla th.recorte--principal {
  width: 100%;
  min-width: 18ch;
  max-width: 48ch;
}

/* --------------------------------------------------------------------------
   3.8  Etiquetas de estado y pastillas
   -------------------------------------------------------------------------- */

.etq {
  display: inline-block;
  padding: .1em .5em;
  border: 1px solid transparent;
  border-radius: var(--radio-chico);
  font-size: .78rem;
  font-weight: 600;
  white-space: nowrap;
}
.etq--ok    { background: var(--verde-suave);   border-color: var(--verde-borde);   color: var(--verde); }
.etq--error { background: var(--rojo-suave);    border-color: var(--rojo-borde);    color: var(--rojo); }
.etq--aviso { background: var(--ambar-suave);   border-color: var(--ambar-borde);   color: var(--ambar); }
.etq--info  { background: var(--azul-suave);    border-color: var(--azul-borde);    color: var(--azul); }
.etq--gris  { background: var(--superficie-2);  border-color: var(--borde);         color: var(--texto-2); }

/* Contadores junto a un título. */
.pastilla {
  display: inline-block;
  padding: .1em .5em;
  border: 1px solid var(--borde);
  border-radius: var(--radio-chico);
  background: var(--superficie-2);
  color: var(--texto-2);
  font-size: .8rem;
  font-weight: 500;
  white-space: nowrap;
}
.pastilla--aviso { background: var(--ambar-suave); border-color: var(--ambar-borde); color: var(--ambar); }
.pastilla--error { background: var(--rojo-suave);  border-color: var(--rojo-borde);  color: var(--rojo); }

/* --------------------------------------------------------------------------
   3.9  Listas de acciones y de actividad
   -------------------------------------------------------------------------- */

/* Accesos directos: icono, texto y flecha. */
.acciones-rapidas { display: flex; flex-direction: column; }

.accion-rapida {
  display: flex;
  align-items: center;
  gap: var(--e2);
  padding: var(--e2) var(--e3);
  border-bottom: 1px solid var(--borde);
  color: inherit;
  text-decoration: none;
}
.accion-rapida:last-child { border-bottom: 0; }
.accion-rapida:hover { background: var(--superficie-2); text-decoration: none; }
.accion-rapida__icono { color: var(--azul); }
.accion-rapida__texto { min-width: 0; flex: 1 1 auto; }
.accion-rapida__titulo { font-weight: 600; }
.accion-rapida__pie { font-size: .82rem; color: var(--texto-3); }
.accion-rapida__flecha { color: var(--texto-3); }

/* Actividad reciente: hora en monoespaciada y el hecho al lado. */
.actividad { display: flex; flex-direction: column; gap: var(--e1); }
.actividad__linea { display: flex; gap: var(--e2); font-size: .88rem; }
.actividad__hora { flex: 0 0 auto; font-family: var(--mono); color: var(--texto-3); }
.actividad__texto { min-width: 0; }

/* --------------------------------------------------------------------------
   3.10  Pares dato / valor
   -------------------------------------------------------------------------- */

.dato { display: flex; gap: var(--e2); padding: var(--e0) 0; min-width: 0; }
.dato__etiqueta { flex: 0 0 12ch; color: var(--texto-2); font-size: .88rem; }
/* El valor tiene que poder encogerse. Sin `min-width: 0` un correo largo
   —«ventas@repandina.com.ec» pedía 262 px en una columna de 145— empuja al
   contenedor y arrastra la página entera: en la ficha del proveedor eran 12 px
   de scroll horizontal a 390 px. */
.dato > :last-child { min-width: 0; overflow-wrap: anywhere; }

/* En pantalla estrecha el rótulo va ARRIBA y no al lado: 12ch de rótulo dejan
   87 px para el dato en una columna de 145, y ahí no cabe nada. */
@media (max-width: 600px) {
  .dato { flex-direction: column; gap: 0; }
  .dato__etiqueta { flex: none; }
}

/* --------------------------------------------------------------------------
   3.11  Estados vacíos y avisos
   -------------------------------------------------------------------------- */

.vacio {
  padding: var(--e4) var(--e3);
  text-align: center;
  color: var(--texto-2);
  font-size: .95rem;
}

.aviso {
  display: flex;
  align-items: flex-start;
  gap: var(--e1);
  padding: var(--e2) var(--e3);
  border: 1px solid var(--borde);
  border-left: 3px solid var(--borde-fuerte);
  border-radius: var(--radio-chico);
  background: var(--superficie-2);
  margin-bottom: var(--e2);
}
.aviso--ok    { background: var(--verde-suave); border-color: var(--verde-borde); border-left-color: var(--verde); }
.aviso--error { background: var(--rojo-suave);  border-color: var(--rojo-borde);  border-left-color: var(--rojo); }
.aviso--aviso { background: var(--ambar-suave); border-color: var(--ambar-borde); border-left-color: var(--ambar); }
.aviso--info  { background: var(--azul-suave);  border-color: var(--azul-borde);  border-left-color: var(--azul); }

/* Encabezado de contexto de una entidad: [Kárdex] [Compras] … */
.contexto { display: flex; gap: var(--e0); flex-wrap: wrap; margin: var(--e1) 0 var(--e3); }
.contexto a {
  padding: var(--e0) var(--e2);
  border: 1px solid var(--borde);
  border-radius: var(--radio-chico);
  color: var(--texto-2);
  text-decoration: none;
}
.contexto a:hover { background: var(--superficie-2); text-decoration: none; }
.contexto a.activo { background: var(--azul-suave); border-color: var(--azul-borde); color: var(--azul); font-weight: 600; }

/* --------------------------------------------------------------------------
   3.12  Pestañas
   Filtran una misma lista (Pendientes · Vistas · Aceptadas…). Llevan el
   contador dentro: saber cuántas hay ANTES de tocar evita el viaje en balde.
   -------------------------------------------------------------------------- */

.pestanas {
  display: flex;
  flex-wrap: wrap;
  gap: var(--e1);
  margin-bottom: var(--e3);
}

.pestana {
  display: inline-flex;
  align-items: center;
  gap: var(--e1);
  padding: var(--e1) var(--e2);
  border: 1px solid var(--borde);
  border-radius: var(--radio-chico);
  background: var(--superficie);
  color: var(--texto-2);
  text-decoration: none;
  font-weight: 500;
}
.pestana:hover { background: var(--superficie-2); text-decoration: none; }

.pestana__n { font-family: var(--mono); font-size: .85rem; color: var(--texto-3); }

.pestana.activa {
  background: var(--azul);
  border-color: var(--azul);
  color: var(--texto-inv);
  font-weight: 600;
}
.pestana.activa .pestana__n { color: var(--texto-inv); }

/* Tonos para las pestañas que hablan de un estado concreto. */
.pestana--ok.activa    { background: var(--verde); border-color: var(--verde); }
.pestana--aviso.activa { background: var(--ambar); border-color: var(--ambar); }
.pestana--error.activa { background: var(--rojo);  border-color: var(--rojo); }

/* --------------------------------------------------------------------------
   3.13  Barra de filtros
   Una sola fila sobre fondo de acero, para que se lea como un control y no
   como contenido. Los filtros viven en la URL: se pueden compartir.
   -------------------------------------------------------------------------- */

.filtros {
  display: flex;
  flex-wrap: wrap;
  gap: var(--e2);
  align-items: flex-start;
  padding: var(--e2) var(--e3);
  margin-bottom: var(--e3);
  border: 1px solid var(--borde);
  border-radius: var(--radio);
  background: var(--acero-2);
}
.filtros .campo label { color: var(--texto-2); }
.filtros__acciones { display: flex; gap: var(--e1); align-items: center; margin-left: auto; }

/* Filtros ya aplicados, cada uno con su aspa para quitarlo. */
.filtros-activos { display: flex; flex-wrap: wrap; gap: var(--e1); margin-bottom: var(--e2); }
.filtro-activo {
  display: inline-flex;
  align-items: center;
  gap: var(--e1);
  padding: var(--e0) var(--e1);
  border: 1px solid var(--azul-borde);
  border-radius: var(--radio-chico);
  background: var(--azul-suave);
  color: var(--azul);
  font-size: .85rem;
}
.filtro-activo button {
  border: 0;
  background: transparent;
  color: inherit;
  cursor: pointer;
  padding: 0;
  line-height: 1;
}

/* El aspa del aviso. No tenía ninguna regla: heredaba el botón del navegador y
   se dibujaba como un botón gris de sistema con una letra dentro.
   Es un aspa tipográfica y no un icono del juego a propósito: los avisos salen
   también en el login y en las páginas de error, que no imprimen el sprite. */
.toast__cerrar {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 1.75rem;
  min-height: 1.75rem;
  padding: 0;
  border: 0;
  border-radius: var(--radio-chico);
  background: transparent;
  color: var(--texto-2);
  cursor: pointer;
  font-size: 1.1rem;
  line-height: 1;
}
.toast__cerrar:hover { background: var(--superficie-2); color: var(--texto); }

/* --------------------------------------------------------------------------
   3.14  Paginación
   -------------------------------------------------------------------------- */

.paginacion {
  display: flex;
  align-items: center;
  gap: var(--e2);
  flex-wrap: wrap;
  padding: var(--e2) var(--e3);
  border-top: 1px solid var(--borde);
  background: var(--superficie-2);
}
.paginacion__cuenta { color: var(--texto-2); font-size: .88rem; }
.paginacion__paginas { display: flex; gap: var(--e0); margin-left: auto; align-items: center; }

.paginacion__paginas a,
.paginacion__paginas span {
  display: inline-grid;
  place-items: center;
  min-width: 2.2em;
  padding: var(--e0) var(--e1);
  border: 1px solid var(--borde);
  border-radius: var(--radio-chico);
  background: var(--superficie);
  color: var(--texto-2);
  text-decoration: none;
}
.paginacion__paginas a:hover { background: var(--superficie-2); text-decoration: none; }
.paginacion__paginas .activa {
  background: var(--azul);
  border-color: var(--azul);
  color: var(--texto-inv);
  font-weight: 600;
}
.paginacion__paginas .desactivada { opacity: .45; pointer-events: none; }

/* --------------------------------------------------------------------------
   3.20  Objetivos táctiles de los botones de sólo icono
   --------------------------------------------------------------------------
   Con el ratón un aspa de 14 px se acierta sin pensar; con el dedo, no. Estos
   son los botones que no llevan texto y por eso se quedaban muy por debajo de
   los 44 px: el aspa de un aviso y el de fijar una búsqueda dentro de una
   pastilla. Crecen sólo en pantalla táctil, para no aflojar la densidad que
   pide el diseño en escritorio.
   -------------------------------------------------------------------------- */

@media (pointer: coarse) {
  .toast__cerrar,
  .filtro-activo button {
    min-width: 44px;
    min-height: 44px;
  }
}

/* --------------------------------------------------------------------------
   3.21  Los últimos sueltos
   --------------------------------------------------------------------------
   Piezas que vivían en un `style=` de una vista. Cada una es pequeña, y esa
   era justamente la excusa para dejarla en línea; sumadas eran 366.
   -------------------------------------------------------------------------- */

/* Panel de una sola columna: cambiar la clave, un formulario corto. Un panel a
   todo lo ancho para cuatro campos deja la vista desierta. */
.panel--estrecho { max-width: 34em; }

/* Separa una fila del bloque anterior sin tocar el resto de la rejilla. */
.fila--separada { margin-top: var(--e2); }

/* Nota al pie de un bloque: más pequeña y en gris. */
.nota { font-size: .9rem; color: var(--texto-2); }
.nota--menor { font-size: .8rem; }

/* La traza de un error 500. Sólo se ve con APP_DEBUG. */
.traza {
  font-size: .8rem;
  overflow: auto;
  max-height: 22em;
  background: var(--superficie-2);
  padding: var(--e1);
  border: 1px solid var(--borde);
}

/* Mini plano del establecimiento. El ancho sí es un dato —lo decide quien lo
   incrusta— y por eso sigue calculándose en el servidor. */
.mini-plano { max-width: 100%; }
.mini-plano__vacio { padding: var(--e2); font-size: .85rem; }
.mini-plano__lienzo {
  display: block;
  width: 100%;
  height: auto;
  background: var(--superficie);
  /* Era `var(--linea)`, un token que no existe: la propiedad se descartaba
     en silencio y el plano salía sin borde, flotando sobre el panel. */
  border: 1px solid var(--borde);
  border-radius: var(--radio-chico);
}
.mini-plano__pie { font-size: .8rem; color: var(--texto-2); margin-top: var(--e0); }

/* --------------------------------------------------------------------------
   3.22  Selector — desplegable de listas largas y de dos niveles
   --------------------------------------------------------------------------
   Lo monta selector.js sobre un <select> que sigue estando ahí. El campo que
   se ve es un <button>; el <select> queda transparente encima para que el
   navegador pueda anclar en él su mensaje de validación.

   El panel cuelga de <body> y se posiciona en `fixed` desde JavaScript: si
   fuera `absolute` lo recortaría el panel lateral o el envoltorio con scroll
   de una tabla, que es donde más se usa.
   -------------------------------------------------------------------------- */

.selector { position: relative; display: block; }

/* El <select> de verdad: invisible pero presente y medible. `display:none` o
   `hidden` lo dejarían fuera de la validación nativa del formulario. */
.selector__nativo {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  pointer-events: none;
  -webkit-appearance: none;
  appearance: none;
}

.selector__campo {
  display: flex;
  align-items: center;
  gap: var(--e1);
  width: 100%;
  height: var(--control-alto);
  padding: var(--e1) var(--e2);
  border: 1px solid var(--borde-fuerte);
  border-radius: var(--radio-chico);
  background: var(--superficie);
  color: var(--texto);
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.selector__campo:hover { background: var(--superficie-2); }
.selector__campo:focus-visible { outline: 2px solid var(--azul); outline-offset: 1px; }
.selector__campo:disabled { opacity: .5; cursor: not-allowed; }

.selector__valor {
  flex: 1 1 auto;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* «Todas», «Sin categoría»: es un valor válido, pero no es una elección. */
.selector__valor--vacio { color: var(--texto-3); }

.selector__flecha { flex: 0 0 auto; color: var(--texto-2); transition: transform .12s ease-out; }
.selector--abierto .selector__flecha { transform: rotate(180deg); }
.selector--abierto .selector__campo { border-color: var(--azul); }

/* ------------------------------------------------------------- El panel */

.selector-panel {
  position: fixed;
  /* Por encima del panel lateral (51) y por debajo del modal (60) y de los
     avisos (70): un desplegable dentro del drawer tiene que verse, pero no
     taparle un modal de confirmación. */
  z-index: 55;
  display: flex;
  flex-direction: column;
  min-width: 16rem;
  overflow: hidden;
  border: 1px solid var(--borde-fuerte);
  border-radius: var(--radio);
  background: var(--superficie);
  box-shadow: 0 6px 20px rgba(13, 39, 69, .18);
}

.selector-panel__buscar {
  display: flex;
  align-items: center;
  gap: var(--e1);
  flex: 0 0 auto;
  padding: var(--e1) var(--e2);
  border-bottom: 1px solid var(--borde);
  background: var(--acero-2);
  color: var(--texto-2);
}
.selector-panel__q {
  flex: 1 1 auto;
  height: auto;             /* no es un control de formulario suelto: vive dentro */
  min-width: 0;
  padding: var(--e0) 0;
  border: 0;
  background: transparent;
  font: inherit;
  color: var(--texto);
}
.selector-panel__q:focus { outline: none; }

.selector-panel__lista {
  flex: 1 1 auto;
  margin: 0;
  padding: var(--e0) 0;
  overflow-y: auto;
  list-style: none;
}
.selector-panel__lista:focus { outline: none; }

.selector-opcion__grupo { margin: 0; padding: 0; list-style: none; }

.selector-opcion {
  display: flex;
  align-items: center;
  gap: var(--e1);
  padding: var(--e1) var(--e2);
  cursor: pointer;
}
.selector-opcion:hover,
.selector-opcion--activa { background: var(--azul-suave); }
.selector-opcion[aria-selected="true"] {
  background: var(--azul);
  color: var(--texto-inv);
  font-weight: 600;
}
.selector-opcion[aria-selected="true"] .selector-opcion__cuenta { color: var(--texto-inv); }

/* La sangría de la hija es lo único que las distingue: sin ella hacen falta
   puntos delante del texto, que es justo lo que se venía a quitar. */
.selector-opcion--hija { padding-left: calc(var(--e2) + 1.6em); }

.selector-opcion__flecha {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.4em;
  height: 1.4em;
  border-radius: var(--radio-chico);
  color: var(--texto-2);
}
.selector-opcion__flecha:hover { background: var(--borde); }
.selector-opcion__flecha--vacia { visibility: hidden; }

.selector-opcion__texto {
  flex: 1 1 auto;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.selector-opcion__cuenta {
  flex: 0 0 auto;
  color: var(--texto-3);
  font-size: .85em;
}

.selector-panel__vacio {
  margin: 0;
  padding: var(--e3) var(--e2);
  color: var(--texto-2);
  text-align: center;
}

/* --------------------------------------------- En el móvil, hoja inferior */

.selector-panel--hoja {
  top: auto;
  right: 0;
  bottom: 0;
  left: 0;
  width: auto;
  max-height: 70vh;
  border-width: 1px 0 0;
  border-radius: var(--radio) var(--radio) 0 0;
}

@media (pointer: coarse) {
  .selector-opcion { min-height: 44px; }
  .selector-opcion__flecha { width: 44px; height: 44px; }
  .selector-panel__q { min-height: 44px; }
}

/* --------------------------------------------------------------------------
   3.23  Piezas de las pantallas de referencia
   --------------------------------------------------------------------------
   Salen de las tres capturas que dio el cliente (etiquetas, plano de
   ubicaciones y ajustes de inventario). Son las que faltaban para que las
   pantallas se vean como allí.
   -------------------------------------------------------------------------- */

/* Ficha al lado del título de la página: «Code128», «5 ajustes», «7 bloques».
   Es un dato, no una acción: no se pulsa y no lleva color de estado. */
.ficha-titulo {
  display: inline-flex;
  align-items: center;
  padding: .15em .55em;
  border: 1px solid var(--borde);
  border-radius: var(--radio-chico);
  background: var(--superficie-2);
  color: var(--texto-2);
  font-family: var(--mono);
  font-size: .8rem;
  font-weight: 500;
  white-space: nowrap;
}
.pagina__cabecera h1 + .ficha-titulo { margin-left: var(--e1); }

/* Y la variante en prosa, para «7 bloques · 4 con existencias · 1.8 ms». */
.meta-titulo {
  color: var(--texto-2);
  font-size: .9rem;
}

/* Línea de pie de una pantalla: la explicación que sólo se lee una vez. */
.nota-pie {
  display: flex;
  align-items: flex-start;
  gap: var(--e1);
  padding: var(--e2) var(--e3);
  border: 1px solid var(--borde);
  border-radius: var(--radio);
  background: var(--superficie-2);
  color: var(--texto-2);
  font-size: .9rem;
}
.nota-pie .icono { flex: 0 0 auto; color: var(--azul); margin-top: .1em; }

/* Paso de cantidad: [−][3][+] pegados, como en la referencia de etiquetas.
   El número es un input de verdad para poder teclear 24 sin pulsar 24 veces. */
.paso {
  display: inline-flex;
  align-items: stretch;
  border: 1px solid var(--borde-fuerte);
  border-radius: var(--radio-chico);
  overflow: hidden;
}
.paso__btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.2em;
  border: 0;
  background: var(--superficie-2);
  color: var(--texto);
  font: inherit;
  font-weight: 600;
  cursor: pointer;
}
.paso__btn:hover { background: var(--borde); }
.paso__btn:disabled { opacity: .4; cursor: not-allowed; }
.paso__valor {
  width: 4ch;
  height: auto;            /* manda el alto del grupo, no el token de control */
  padding: var(--e0) var(--e0);
  border: 0;
  border-left: 1px solid var(--borde);
  border-right: 1px solid var(--borde);
  border-radius: 0;
  background: var(--superficie);
  font-family: var(--mono);
  text-align: center;
}
.paso__valor:focus { outline: 2px solid var(--azul); outline-offset: -2px; }

/* Cifras de un panel: «Unidades totales 84 u · Artículos 18 · Valor $4.238». */
.cifras {
  display: flex;
  flex-wrap: wrap;
  gap: var(--e3);
  padding: var(--e2) 0;
}
.cifra { display: flex; flex-direction: column; gap: 2px; }
.cifra__rotulo { color: var(--texto-2); font-size: .82rem; }
.cifra__valor { font-family: var(--mono); font-size: 1.15rem; font-weight: 600; }

/* Estado vacío como tarjeta, no como una línea de texto perdida en el panel:
   dice qué pasó, por qué, y ofrece la salida. Es la de la referencia de
   ajustes de inventario. */
.vacio-tarjeta {
  display: flex;
  align-items: center;
  gap: var(--e3);
  max-width: 46rem;
  margin: var(--e3) auto;
  padding: var(--e3);
  border-radius: var(--radio);
  background: var(--marino);
  color: var(--marino-texto);
}
.vacio-tarjeta__icono {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 3.4rem;
  height: 3.4rem;
  border-radius: var(--radio);
  background: var(--marino-2);
  color: var(--marino-tenue);
}
.vacio-tarjeta__icono .icono { width: 1.8rem; height: 1.8rem; }
.vacio-tarjeta__texto { flex: 1 1 auto; }
.vacio-tarjeta__titulo { color: var(--texto-inv); font-weight: 600; margin-bottom: var(--e0); }
.vacio-tarjeta .btn { flex: 0 0 auto; }

@media (max-width: 600px) {
  .vacio-tarjeta { flex-direction: column; text-align: center; }
}

/* Tamaño de página en la paginación: «20 por página». */
.paginacion__tamano {
  display: flex;
  align-items: center;
  gap: var(--e1);
  color: var(--texto-2);
  font-size: .88rem;
}
.paginacion__tamano select { width: auto; min-width: 5ch; }

/* Existencia en una celda: pastilla con color de estado, no sólo texto de
   color. Con el número suelto en verde o en ámbar hay que fijarse; con la
   pastilla se ve de un vistazo cuál de las veinte filas está en rojo. */
.pastilla-stock {
  display: inline-flex;
  justify-content: center;
  min-width: 4ch;
  padding: .1em .5em;
  border: 1px solid;
  border-radius: var(--radio-chico);
  font-family: var(--mono);
  font-weight: 600;
  font-size: .88rem;
}
.pastilla-stock--ok   { background: var(--verde-suave); border-color: var(--verde-borde); color: var(--verde); }
.pastilla-stock--bajo { background: var(--ambar-suave); border-color: var(--ambar-borde); color: var(--ambar); }
.pastilla-stock--cero { background: var(--rojo-suave);  border-color: var(--rojo-borde);  color: var(--rojo); }

/* Cuerpo de panel en azul muy claro: destaca el bloque por el que se empieza a
   trabajar —el escaneo— sin sacarlo del panel. */
.panel__cuerpo--azul { background: var(--azul-suave); }

/* Botón grande de acción final: el que cierra la pantalla —generar el PDF,
   cobrar—. Lleva su propio renglón pequeño debajo con la consecuencia
   («8 páginas»), que es el dato que evita el susto al pulsar. */
.btn--grande {
  height: auto;
  min-height: 3.2rem;
  padding: var(--e2) var(--e3);
  font-size: 1.05rem;
}
.btn__texto { display: flex; flex-direction: column; align-items: flex-start; line-height: 1.2; }
.btn__texto small { font-size: .78rem; font-weight: 400; opacity: .85; }

/* Rejilla de dos columnas que se apila en cuanto no cabe. */
.rejilla--dos { display: grid; gap: var(--e3); grid-template-columns: repeat(auto-fit, minmax(min(24rem, 100%), 1fr)); }

/* Lista de pares rótulo/valor en columna, con el valor alineado a la derecha. */
.dato-lista { flex: 1 1 14rem; margin: 0; display: flex; flex-direction: column; gap: var(--e1); }
.dato-lista .dato { display: flex; align-items: baseline; justify-content: space-between; gap: var(--e2); }
/* El rótulo no se parte en dos líneas: «Páginas estimadas» en dos renglones
   con el número solo a la derecha se lee peor que una línea que se recorta. */
.dato-lista .dato__etiqueta { white-space: nowrap; }

/* Contenido con columna lateral: lo principal a la izquierda y una ayuda
   permanente a la derecha (la vista previa de la etiqueta, la ficha del bloque
   seleccionado en el plano). Se apila en cuanto no caben las dos. */
.con-aside {
  display: grid;
  gap: var(--e3);
  grid-template-columns: minmax(0, 1fr) minmax(min(20rem, 100%), 22rem);
  align-items: start;
}
@media (max-width: 1100px) {
  .con-aside { grid-template-columns: minmax(0, 1fr); }
}

/* La etiqueta tal como va a salir impresa. */
.etiqueta-previa {
  display: flex;
  flex-direction: column;
  gap: var(--e1);
  padding: var(--e3);
  text-align: center;
}
.etiqueta-previa__marca {
  font-weight: 700;
  font-size: 1.15rem;
  letter-spacing: .06em;
}
.etiqueta-previa__marca em { color: var(--rojo); font-style: normal; }
.etiqueta-previa__campo { text-align: left; }
.etiqueta-previa__rotulo {
  display: block;
  color: var(--texto-3);
  font-size: .72rem;
  letter-spacing: .06em;
  text-transform: uppercase;
}
.etiqueta-previa__valor { font-family: var(--mono); font-size: .95rem; }
.etiqueta-previa__valor--nombre { font-family: var(--fuente); font-weight: 600; text-transform: uppercase; }
.etiqueta-previa__barras {
  width: 100%;
  height: 4.5rem;
  object-fit: contain;
  margin-top: var(--e1);
}
.etiqueta-previa__codigo { font-family: var(--mono); font-size: .9rem; letter-spacing: .08em; }
.etiqueta-previa__pie { display: flex; justify-content: flex-end; }

/* Fila elegida en una tabla de trabajo: la que se está previsualizando. No es
   una selección para actuar en lote, así que no lleva casilla ni color fuerte. */
table.tabla tbody tr.fila--elegida { background: var(--azul-suave); }
table.tabla tbody tr.fila--elegida td:first-child { box-shadow: inset 3px 0 0 var(--azul); }

/* --------------------------------------------------------------------------
   3.24  Reportes
   -------------------------------------------------------------------------- */

/* Ficha del índice de reportes: qué es y qué contesta, no sólo su nombre. */
.ficha-reporte {
  display: flex;
  align-items: center;
  gap: var(--e3);
  padding: var(--e3);
  border: 1px solid var(--borde);
  border-radius: var(--radio);
  background: var(--superficie);
  color: var(--texto);
  text-decoration: none;
}
.ficha-reporte:hover {
  border-color: var(--azul-borde);
  background: var(--azul-suave);
  text-decoration: none;
}
.ficha-reporte__icono {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 2.8rem;
  height: 2.8rem;
  border-radius: var(--radio);
  background: var(--marino);
  color: var(--marino-tenue);
}
.ficha-reporte__icono .icono { width: 1.4rem; height: 1.4rem; }
.ficha-reporte__texto { flex: 1 1 auto; display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.ficha-reporte__titulo { font-weight: 600; }
.ficha-reporte__resumen { color: var(--texto-2); font-size: .9rem; }
.ficha-reporte__flecha { flex: 0 0 auto; color: var(--texto-3); }
.ficha-reporte:hover .ficha-reporte__flecha { color: var(--azul); }

/* El lienzo del gráfico. Alto fijo: sin él, Chart.js con `responsive` crece
   en cada redibujado hasta ocupar la pantalla entera. */
.grafico { position: relative; height: 18rem; }
@media (max-width: 600px) { .grafico { height: 14rem; } }

/* Fila de botones que se envuelve: los rangos rápidos son cinco y en el móvil
   no caben en una línea. */
.acciones--envueltas { flex-wrap: wrap; margin-top: var(--e2); }

/* El pie de una tabla de reporte es un total, y se lee como tal. */
table.tabla tfoot td {
  border-top: 2px solid var(--borde-fuerte);
  background: var(--superficie-2);
  font-weight: 600;
}

.aviso--ambar { border-left-color: var(--ambar); background: var(--ambar-suave); }
.aviso--ambar .icono { color: var(--ambar); }

/* --------------------------------------------------------------------------
   3.25  Tamaños que se adaptan al CONTENEDOR, no a la pantalla
   --------------------------------------------------------------------------
   Ser responsive no es sólo recolocar los bloques cuando la pantalla encoge:
   es que el CONTENIDO quepa en la caja que le toca. Lo primero se resuelve con
   consultas de medios; lo segundo, no, porque el ancho de una tarjeta lo decide
   la rejilla que la contiene y no el ancho de la ventana.

   El caso medido, en una pantalla de 820 px —ancha— con una columna lateral de
   244:
     · «SALDO POR PAGAR» + su ficha ocupaban TRES líneas de cabecera;
     · «151,252.93» pedía exactamente los 134 px disponibles, de modo que un
       dígito más —y las cifras de este negocio los tienen— se recortaba a
       «151,252.9», un número que no es el que dice ser.

   Ninguna consulta de medios ve eso: la pantalla es ancha. Las consultas de
   CONTENEDOR sí, y `cqi` es el 1 % del ancho del contenedor. Por eso la cifra
   se declara con `clamp(mínimo, medida-en-cqi, máximo)`: grande donde hay
   sitio, pequeña donde no, y nunca recortada.

   Cuidado al añadir contenedores nuevos: `container-type: inline-size` aplica
   contención y crea bloque contenedor, así que NO se pone en `.panel` —dentro
   viven las cabeceras de tabla pegajosas, y ya nos costó una vez (D-081)—.
   Va sólo en cajas que no contienen nada pegajoso ni posicionado.
   -------------------------------------------------------------------------- */

/* El contenedor va en el CUERPO, no en la tarjeta: `cqi` es el 1 % del ancho
   del contenedor, y si se mide la tarjeta entera se cuenta también el icono y
   los rellenos —244 px de tarjeta para 134 de hueco real—, así que el cálculo
   sobraba por casi el doble y la cifra nunca encogía. */
.kpi__cuerpo { container-type: inline-size; }

/* De 1,05rem en una tarjeta estrecha a 1,5rem donde hay sitio. El 11cqi sale
   de medir: con diez caracteres de cifra monoespaciada, 11 % del ancho del
   contenedor es el tamaño mayor que sigue cabiendo. */
.kpi__cifra {
  font-size: clamp(.95rem, 16cqi, 1.5rem);
  /* Y una red por si el número crece más de lo previsto: antes de recortarse,
     se encoge la letra; si aun así no cabe, se parte el número, que es feo pero
     honesto —un importe cortado a la mitad es una cifra falsa—. */
  overflow-wrap: anywhere;
}
/* La tarjeta entera también es contenedor, para decidir QUÉ se enseña —no sólo
   de qué tamaño—. Por debajo de 230 px el icono se lleva 44 px más sus huecos,
   más de la mitad del sitio, y la cifra deja de caber por mucho que encoja la
   letra. En una tarjeta así el icono es lo prescindible: el número no. */
.kpi { container-type: inline-size; container-name: tarjeta; }

@container tarjeta (max-width: 230px) {
  .kpi__icono { display: none; }
  .kpi { padding: var(--e2); gap: var(--e1); }
}

.kpi__rotulo { font-size: clamp(.72rem, 6cqi, .82rem); }
.kpi__pie    { font-size: clamp(.68rem, 5.5cqi, .78rem); }

/* La cabecera de panel: el título se adapta y las acciones no lo empujan a una
   tercera línea. */
/* El título de una cabecera es un NODO DE TEXTO suelto, no un elemento: no se
   le puede aplicar una regla propia. Se escala la cabecera entera y el tamaño
   se hereda. En una columna estrecha —«SALDO POR PAGAR» más su ficha en 244 px—
   ocupaba tres líneas. */
.panel__cabecera {
  container-type: inline-size;
  container-name: cabecera;
  font-size: clamp(.72rem, 4.2cqi, .92rem);
}

/* Y en la más estrecha se quitan las mayúsculas y el espaciado entre letras,
   que juntos cuestan cerca del 15 % del ancho y no aportan nada cuando lo que
   falta es sitio. */
@container cabecera (max-width: 260px) {
  .panel__cabecera { text-transform: none; letter-spacing: 0; gap: var(--e1); }
}

/* Los pares dato/valor de una ficha: el valor manda sobre el rótulo cuando el
   sitio escasea. */
.dato { container-type: inline-size; }
.dato > :last-child { font-size: clamp(.85rem, 6cqi, 1rem); }

/* Las cifras de un panel lateral estrecho. */
.cifra__valor { font-size: clamp(.95rem, 9cqi, 1.15rem); }

/* Los mismos cortes que tenían las consultas de medios, pero medidos sobre la
   tabla. `!important` porque compiten con las reglas de medios que quedan. */
@container tabla (max-width: 900px) {
  [data-priority="3"] { display: none !important; }
}
@container tabla (max-width: 600px) {
  [data-priority="2"] { display: none !important; }
}

/* Y cuando ni escondiendo columnas cabe, la tabla ENCOGE. Es la diferencia
   entre recolocar y adaptar: con las columnas de prioridad 1 —las que no se
   pueden ocultar— una tabla de 630 px no entra en un panel de 558, y dejarla
   desplazándose de lado esconde media fila detrás del borde. Bajando el cuerpo
   de letra y los rellenos entra entera y se lee. */
@container tabla (max-width: 620px) {
  table.tabla { font-size: .88rem; }
  table.tabla th, table.tabla td { padding: var(--e1) var(--e1); }
}
@container tabla (max-width: 420px) {
  table.tabla { font-size: .82rem; }
  table.tabla th, table.tabla td { padding: var(--e0) var(--e1); }
}
@container tabla (max-width: 340px) {
  /* El último escalón, para una tabla metida en una columna lateral: cuatro
     tramos de antigüedad en 241 px. Se aprieta todo lo que se puede sin bajar
     de 11 px, que es donde una cifra deja de leerse. Si aun así no entra, la
     envoltura desplaza —y `--desborda` lo hace visible—: preferible a fingir
     que cabe recortando el número. */
  table.tabla { font-size: .74rem; }
  table.tabla th, table.tabla td { padding: 2px 4px; }
  table.tabla th { letter-spacing: 0; }
}


/* --------------------------------------------------------------------------
   3.26  La capa de la cámara
   --------------------------------------------------------------------------
   No existía. El guion creaba `.camara-capa` y le ponía los tamaños con
   `style=` en línea, así que el visor no cubría la pantalla —quedaba como un
   bloque más al final del documento— y la mira, que sí se pintaba en el HTML,
   era invisible por no tener ni una regla.

   La mira es DECORACIÓN, no un recorte: el decodificador lee todo el encuadre,
   también fuera de ella. Su único trabajo es que la gente ponga el código a la
   distancia a la que el teléfono enfoca, unos 15–25 cm, así que tiene que ser
   grande. Si es pequeña la gente acerca demasiado y deja de enfocar.
   Ver RECETA §2.5 y DECISIONES.md · D-096.
   -------------------------------------------------------------------------- */

.camara-capa {
  position: fixed;
  inset: 0;
  z-index: 60;                      /* por encima del panel lateral (50) */
  display: flex;
  flex-direction: column;
  background: #000;
  /* Nada de `height: 100dvh` aquí: `inset: 0` sobre un elemento fijo ya mide
     el hueco visible exacto, y añadir una altura la PISA. Medido en un
     teléfono de 1016 px de alto, el `100dvh` dejaba la capa en 812 y se veía
     la pantalla de detrás por debajo del vídeo. */
}

.camara-capa__barra {
  display: flex;
  align-items: center;
  gap: var(--e1);
  padding: var(--e1) var(--e2);
  background: var(--marino-barra);
  color: #fff;
  flex: 0 0 auto;
}

.camara-capa__barra strong { font-size: .95rem; }

/* El estado se lleva el espacio sobrante para que los botones queden a la
   derecha, y se recorta antes que empujarlos fuera de la pantalla. */
.camara-capa__estado {
  flex: 1 1 auto;
  min-width: 0;
  color: var(--marino-texto);
  font-size: .88rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.camara-capa__linterna.es-activo {
  background: var(--ambar);
  border-color: var(--ambar);
  color: #fff;
}

.camara-capa__visor {
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
}

.camara-capa__video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  background: #000;
}

/* Cuatro esquinas sueltas, no un marco cerrado: se ve dónde apuntar sin tapar
   el código. `pointer-events: none` es imprescindible — si captura los toques,
   el botón de cerrar deja de responder en la mitad de la pantalla. */
.camara-capa__mira {
  position: absolute;
  left: 50%;
  top: 50%;
  width: min(78vw, 340px);
  height: min(52vw, 230px);
  transform: translate(-50%, -55%);
  pointer-events: none;
}

/* Cuatro esquinas de verdad, cada una con dos bordes. Nada de degradados
   —están prohibidos en este proyecto— ni de un marco cerrado que tape el
   código. */
.camara-capa__mira i {
  position: absolute;
  width: 38px;
  height: 38px;
  border: 4px solid #fff;
  opacity: .9;
}

.camara-capa__mira i:nth-child(1) { top: 0;    left: 0;    border-right: 0; border-bottom: 0; }
.camara-capa__mira i:nth-child(2) { top: 0;    right: 0;   border-left: 0;  border-bottom: 0; }
.camara-capa__mira i:nth-child(3) { bottom: 0; left: 0;    border-right: 0; border-top: 0; }
.camara-capa__mira i:nth-child(4) { bottom: 0; right: 0;   border-left: 0;  border-top: 0; }

/* --------------------------------------------------------------------------
   3.27  Fila recién tocada y campo que pide un dato
   --------------------------------------------------------------------------
   La pantalla de ingreso marcaba con `tr.recien` la línea que acababa de
   entrar, y la clase no existía en ninguna hoja: el bodeguero escaneaba y nada
   se movía. Con una lista de treinta líneas, saber cuál acaba de sumar es la
   diferencia entre confiar en el lector y volver a contar a mano.

   `campo--pide` es su pariente: marca un campo cuyo dato falta y hace falta.
   Se usa en el costo del ingreso, porque un artículo que entra sin costo queda
   valorado en cero para siempre. Ver DECISIONES.md · D-097.
   -------------------------------------------------------------------------- */

tr.recien > td {
  background: var(--verde-suave);
  /* Se apaga solo. La transición va en el estado NORMAL, no en `.recien`: así
     el color entra de golpe —que es lo que llama la atención— y se desvanece
     despacio al quitar la clase. */
  transition: none;
}

table.tabla tbody tr > td { transition: background .55s ease-out; }

/* `input.campo--pide` y no `.campo--pide` a secas: el selector genérico de
   arriba es `input[type="text"]`, que pesa 0,1,1, y una clase sola pesa
   0,1,0 — el fondo ámbar perdía siempre y el campo salía blanco. Es la
   misma trampa de especificidad que el hover de los botones. */
input.campo--pide {
  border-color: var(--ambar-borde);
  background: var(--ambar-suave);
}

input.campo--pide::placeholder { color: var(--ambar); opacity: 1; }

input.campo--pide:focus {
  border-color: var(--ambar);
  background: var(--superficie);
}

/* --------------------------------------------------------------------------
   3.28  El lienzo de una gráfica
   --------------------------------------------------------------------------
   Chart.js dibuja sobre un <canvas> y necesita que ALGUIEN le dé alto: con
   `maintainAspectRatio: false` —que es lo que se usa para que la gráfica se
   adapte a su panel— el lienzo no tiene alto propio y la gráfica sale de un
   píxel. El alto va aquí y no en el atributo `height` del canvas, porque el
   atributo es la resolución del mapa de bits, no el tamaño en pantalla.
   -------------------------------------------------------------------------- */

.grafico {
  position: relative;
  height: clamp(180px, 30vh, 280px);
}
.grafico canvas { display: block; }
