/*
 * Cáscara de la aplicación: el molde que comparten los ~23 listados (los 21
 * módulos de Configuración + Usuarios + Productos) y el panel de
 * Configuración.
 *
 * Antes cada módulo se dibujaba como <RadzenCard class="container-xxl mt-8
 * shadow mb-3 p-4 pb-4" style="border-radius:10px">: una tarjeta con sombra
 * de Bootstrap, tope de ancho de 1320px y radio inline de 10px que peleaba
 * con el token de 12px. Adentro, la grilla dibujaba SU propia caja con borde
 * y sombra — caja dentro de caja, sobre un fondo gris. Ahora el lienzo es
 * blanco, el módulo no es una tarjeta, y la única caja de la pantalla es la
 * tabla.
 */

/* --- Ancho y aire de la página --- */

/* Un solo lugar manda el aire de la página, para todas las pantallas
   (listados, Configuración y el tablero por igual): 24px arriba, 32px a los
   lados, 48px abajo para que la última fila de una tabla no quede pegada al
   borde. Sin tope de ancho: los listados anchos (Productos tiene diez
   columnas) tienen que poder usar la pantalla completa, que es justo lo que
   .container-xxl impedía. */
.rz-body {
  --rz-layout-body-padding: var(--space-6) var(--pad-page) var(--space-12);
  background: var(--background);
}

/* Las pantallas de invitado (login) son a sangre: traen su propio lienzo, ya
   dimensionado a 100vh y con su propio color. El aire de página y el fondo que
   .rz-body reparte al resto de la aplicación les dibujaba un marco blanco
   alrededor y, al sumarse a ese 100vh, además empujaban la página a
   desplazarse verticalmente sin motivo. GuestLayout no las distingue con
   ninguna clase, así que se reconocen por el lienzo que montan: el login el
   suyo propio, y el resto (cuenta bloqueada, sesión no válida) el de
   MensajeInvitado. */
.rz-body:has(.login-shell),
.rz-body:has(.invitado-lienzo) {
  --rz-layout-body-padding: 0;
  background: transparent;
}

/* Con campo y hoja del mismo blanco, el menú lateral ya no se despega solo
   por contraste de fondo: lo separa su filete, igual que todo lo demás. */
.rz-sidebar {
  --rz-sidebar-border-inline-end: var(--border-width) solid var(--border);
  background: var(--surface);
}

/* --- La cabecera es una superficie oscura fija ---
 *
 * MainLayout le pone su propio degradado navy→teal con !important, pero los
 * tokens de Radzen asumen que la cabecera va rellena con el color primario:
 * --rz-sidebar-toggle-color sale de --rz-on-primary, que es la tinta OSCURA
 * pensada para ir ENCIMA del relleno teal brillante. Sobre este navy queda
 * prácticamente invisible — por eso el botón de colapsar el menú no se veía.
 *
 * La cabecera no cambia con el tema, así que su tinta tampoco: se declara
 * una vez acá y todo lo que viva adentro la hereda, en claro y en oscuro.
 */
.rz-header {
  --header-ink: #e3f8fb;
  --header-ink-fuerte: #ffffff;
  --rz-sidebar-toggle-color: var(--header-ink);
  --rz-sidebar-toggle-hover-color: var(--header-ink-fuerte);
  --rz-header-color: var(--header-ink);
}

.rz-header .rz-sidebar-toggle {
  color: var(--header-ink);
}

.rz-header .rz-sidebar-toggle:hover,
.rz-header .rz-sidebar-toggle:focus-visible {
  color: var(--header-ink-fuerte);
}

/* Usuario de la sesión, sobre la superficie oscura fija de la cabecera: hereda
   su tinta clara como todo lo demás que vive ahí. */
.usuario-sesion {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  margin-inline-end: var(--space-4);
  color: var(--header-ink);
  font-size: var(--text-small-size);
  line-height: var(--text-small-lh);
  white-space: nowrap;
}

.usuario-sesion .material-symbols-rounded {
  font-size: var(--icon-size);
}

/* En pantallas angostas la cabecera ya lleva logo, versión y el interruptor de
   tema: el nombre se retira antes de que algo se desborde. El menú Mi Perfil
   sigue identificando al usuario. */
@media (max-width: 720px) {
  .usuario-sesion { display: none; }
}

/* Interruptor de tema, sobre esa misma superficie fija. */
.tema-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  margin-inline-start: var(--space-3);
  padding: 0;
  border: 0;
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--header-ink);
  cursor: pointer;
  transition: var(--transition-control);
}

.tema-toggle:hover { background: rgba(255, 255, 255, .14); }
.tema-toggle:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.tema-toggle .material-symbols-rounded { font-size: var(--icon-size); }

/* Se muestra el ícono del tema al que se va a cambiar, no el actual: en
   claro ofrece la luna, en oscuro ofrece el sol. */
.tema-toggle .icon-sun { display: none; }
.tema-toggle .icon-moon { display: inline-flex; }
:root[data-theme="dark"] .tema-toggle .icon-sun { display: inline-flex; }
:root[data-theme="dark"] .tema-toggle .icon-moon { display: none; }

/* Las pantallas que NO son listados (ficha de producto, formularios de
   Identity) siguen usando <RadzenCard class="container-xxl ... shadow">.
   .shadow es utility de Bootstrap con !important: solo otro !important le
   gana, y sin esto vuelve la sombra pesada que contradice la regla de
   elevación del sistema ("sin sombra en la hoja"). */
.rz-card.shadow {
  box-shadow: none !important;
}

.rz-card.container-xxl .rz-text-h5.fw-bold,
.rz-card.container .rz-text-h5.fw-bold {
  font-size: var(--text-h4-size) !important;
  line-height: var(--text-h4-lh) !important;
  letter-spacing: var(--text-h4-ls) !important;
  color: var(--text-primary) !important;
}

/* La ficha de producto se abre como página y como diálogo desde el listado.
   Dentro del modal se disuelve: el diálogo ya es el contenedor, y una tarjeta
   adentro sería una caja dentro de otra caja. Como página conserva su
   .container-xxl y su padding, que los pone el propio .razor. */
.rz-dialog .producto-ficha {
  border: 0;
  background: transparent;
  padding: 0;
}

/* Celda con dos datos apilados: el secundario baja de columna, no desaparece.
   Dos líneas por dato cuando va solo (un nombre largo, una advertencia que no
   se puede cortar a media frase) y UNA por dato cuando van apilados — con dos
   en cada uno el par crecía hasta cuatro y desalineaba la fila. */
.celda-principal,
.celda-secundaria {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  overflow-wrap: anywhere;
}

.celda-secundaria {
  margin-top: 2px;
  font-size: var(--text-caption-size);
  line-height: var(--text-caption-lh);
  color: var(--text-secondary);
}

td:has(.celda-secundaria) .celda-principal,
td .celda-secundaria {
  -webkit-line-clamp: 1;
}

/* --- Molde de módulo (listados) --- */

.modulo {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
}

/* Los 21 catálogos listan dos columnas (nombre + acciones). Estirados al
   ancho del monitor dejaban el nombre pegado a la izquierda y los botones a
   más de mil pixeles de distancia: la fila dejaba de leerse como una sola
   cosa. El ancho lo pone el contenido, no la ventana. Productos, con diez
   columnas, no lleva esta clase: ahí sí hace falta toda la pantalla.
   Clean OS no tiene token de "columna angosta" (su --content-max es 1440px,
   el ancho de página completa), así que toma el del contenedor grande del
   sistema en vez de un número inventado. */
.modulo-angosto {
  max-width: var(--dialog-lg);
  margin-inline: auto;
}

/* Campo de contraseña con ojo para revelar el texto.
   El ojo nativo de los navegadores (::-ms-reveal en Edge) aparece y desaparece
   solo, y en Firefox y Safari directamente no existe; se oculta para no terminar
   con dos, y lo reemplaza el botón de os-clave.js. */
.campo-clave {
  position: relative;
  display: block;
}

.campo-clave input {
  /* Sitio para el ojo: sin esto el texto pasa por debajo del botón. */
  padding-inline-end: 2.75rem;
}

.campo-clave input::-ms-reveal,
.campo-clave input::-ms-clear {
  display: none;
}

.campo-clave-ojo {
  position: absolute;
  inset-block-start: 50%;
  inset-inline-end: var(--space-2);
  transform: translateY(-50%);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  padding: 0;
  border: 0;
  border-radius: var(--radius-inner);
  background: transparent;
  color: var(--text-secondary);
  cursor: pointer;
  transition: var(--transition-control);
}

/* Gana al display:inline-flex de arriba mientras el campo está vacío. */
.campo-clave-ojo[hidden] {
  display: none;
}

.campo-clave-ojo:hover {
  color: var(--text-primary);
  background: var(--surface-hover);
}

.campo-clave-ojo:focus-visible {
  outline: none;
  color: var(--text-primary);
  box-shadow: var(--focus-ring);
}

.campo-clave-ojo .material-symbols-rounded {
  font-size: var(--icon-size-sm);
  line-height: 1;
}

/* Columna de un formulario dentro del molde de módulo. Reemplaza al .row/.col-xl-6
   de Bootstrap que traían las pantallas de cuenta: un campo de texto más ancho que
   esto obliga al ojo a barrer de un borde al otro para leer lo que escribió. */
.modulo-formulario {
  max-width: var(--dialog-sm, 480px);
}

/* Métricas de lista de Clean OS, que hasta acá no se estaban usando: el
   sistema define --row-h 52px con --cell-py 14px / --cell-px 16px, y estas
   listas corrían con Density.Compact (4/8px), que aprieta veinte nombres
   cortos en un bloque denso y los hace ver pesados. 14 + 14 + 24 de
   interlínea da exactamente los 52px de --row-h. Solo aplica a los
   catálogos: Productos y el tablero sí son tablas densas de datos, ahí la
   densidad compacta es la correcta. */
.modulo-angosto .rz-data-grid {
  --rz-grid-cell-padding: var(--cell-py) var(--cell-px);
  --rz-grid-cell-line-height: 1.5rem;
  --rz-grid-header-cell-padding: var(--cell-py) 0;
}

/* Y con la fila ya a 52px, el botón de acción puede tomar el alto de control
   chico del sistema (--btn-h-sm, 36px) en vez del mínimo apretado de Radzen:
   más cerca del --tap-target-min de 44px que pide Clean OS. */
.modulo-angosto .rz-button-xs.rz-button-icon-only {
  min-height: var(--btn-h-sm);
  min-width: var(--btn-h-sm);
  padding: var(--space-2);
}

/* Título a la izquierda, acción primaria a la derecha, en la misma línea:
   el patrón que el usuario ya espera de un listado. Reemplaza al trío
   título + breadcrumb + fila con botón, donde el breadcrumb repetía lo que
   el menú lateral (siempre visible) ya dice. */
.modulo-cab {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: center;
  gap: var(--space-3) var(--space-6);
}

.modulo-titulo {
  margin: 0;
  font-size: var(--text-h3-size);
  line-height: var(--text-h3-lh);
  letter-spacing: var(--text-h3-ls);
  font-weight: var(--weight-semibold);
  color: var(--text-primary);
}

/* Una línea de contexto opcional bajo el título (Productos la usa para el
   conteo). Nunca un subtítulo decorativo: solo dato. */
.modulo-nota {
  /* Ocupa su propio renglón bajo el título en vez de competir con las acciones
     por el espacio del medio: es un dato del listado, no un control. */
  flex-basis: 100%;
  order: 3;
  margin: var(--space-1) 0 0;
  font-size: var(--text-small-size);
  color: var(--text-secondary);
}

/* Acciones de cabecera (nuevo + exportar, en Productos) con un ritmo propio
   en vez de márgenes inline sueltos por archivo. */
.modulo-acciones {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
}

/* Barra de controles de la grilla (agrupar/expandir/bloquear en Productos):
   compacta y sobre la banda hundida, para que se lea como herramienta de la
   tabla y no como otra sección del módulo. */
.modulo-herramientas {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--space-6);
  padding: var(--space-3) var(--space-4);
  border: var(--border-width) solid var(--border);
  border-radius: var(--radius);
  background: var(--surface-secondary);
}

.modulo-herramienta {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.modulo-herramienta > span {
  font-size: var(--text-overline-size);
  line-height: var(--text-overline-lh);
  letter-spacing: var(--text-overline-ls);
  font-weight: var(--text-overline-weight);
  text-transform: uppercase;
  color: var(--text-tertiary);
}

.modulo-herramienta-grupo {
  display: flex;
  gap: var(--space-1);
}

/* Buscador general de un listado. Nació en Productos y ahora lo comparten los 22
   catálogos, así que vive acá y no en el CSS de una página. */
.busqueda {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  height: 34px;
  padding-inline: var(--space-3);
  border: var(--border-width) solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--text-tertiary);
}

.busqueda:focus-within {
  border-color: var(--accent);
  box-shadow: var(--focus-ring);
}

.busqueda input {
  flex: 1;
  min-width: 0;
  border: 0;
  background: none;
  outline: none;
  font: inherit;
  font-size: var(--text-small-size);
  color: var(--text-primary);
}

.busqueda input::placeholder { color: var(--text-tertiary); }

/* Limpiar: aparece solo cuando hay algo que limpiar. Sin esto hay que seleccionar
   el texto y borrarlo a mano para volver a ver la tabla completa. */
.busqueda-limpiar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 22px;
  height: 22px;
  padding: 0;
  border: 0;
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--text-tertiary);
  cursor: pointer;
  transition: var(--transition-control);
}

.busqueda-limpiar:hover { background: var(--surface-active); color: var(--text-primary); }
.busqueda-limpiar:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.busqueda-limpiar .rz-icon { font-size: 16px; }

/* El buscador de un catálogo va solo, sobre la tabla, y no necesita ocupar todo el
   ancho de la barra. (Productos no tiene buscador general: filtra por columna.) */
.busqueda-modulo { max-width: 340px; }

/* --- Panel de Configuración --- */

.config {
  display: flex;
  flex-direction: column;
  gap: var(--space-12);
}

/* Gap corto (4px) para que la bajada quede pegada a su título; la rejilla
   recupera el aire con su propio margen. Así el ritmo hasta las fichas es el
   mismo (16px) tenga bajada la sección o no. */
.config-seccion {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.config-seccion-titulo {
  margin: 0;
  font-size: var(--text-h4-size);
  line-height: var(--text-h4-lh);
  letter-spacing: var(--text-h4-ls);
  font-weight: var(--weight-semibold);
  color: var(--text-primary);
}

/* Bajada de la sección ("Módulos de configuración del sistema"): es la
   bajada del título, no un párrafo suelto, por eso va pegada a él. */
.config-seccion-nota {
  margin: 0;
  font-size: var(--text-small-size);
  line-height: var(--text-small-lh);
  color: var(--text-secondary);
}

/* Rejilla real en vez de flex-wrap con tarjetas de 300px fijos: así todas
   las fichas de una fila miden lo mismo y llenan el ancho disponible, en
   vez de dejar un hueco irregular al final de cada renglón (que es lo que
   se veía como "botones mal distribuidos"). auto-fill + 1fr mantiene el
   mismo ancho de columna aunque la última fila tenga una sola ficha. */
.config-grid {
  margin-top: var(--space-3);
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(232px, 1fr));
  gap: var(--space-3);
}

.config-card {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-width: 0;
  padding: var(--space-4);
  border: var(--border-width) solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--text-primary);
  text-decoration: none;
  transition: var(--transition-control);
}

.config-card:hover {
  border-color: var(--accent);
  background: var(--surface-hover);
  color: var(--text-primary);
}

.config-card:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
}

.config-card .material-symbols-rounded {
  flex-shrink: 0;
  font-size: var(--icon-size-lg);
  color: var(--accent-on-soft);
}

.config-card-texto {
  min-width: 0;
  font-size: var(--text-small-size);
  line-height: var(--text-small-lh);
  font-weight: var(--weight-medium);
}

/* --- Listados (RadzenDataGrid) ---
 *
 * Mismo criterio de alto apalancamiento: los 23 listados repiten el mismo
 * <a> sin href en la primera columna y el mismo trío checkbox + editar +
 * borrar en la última. Se arregla acá una vez.
 */

/* Marca de excepción: un catálogo tiene casi todos sus registros activos, así
   que un control por fila para decir "esto es lo normal" es ruido —
   veinte casillas apagadas compitiendo con las acciones reales. Solo se
   marca lo que se sale de la norma. Tono neutro, no de alarma: inactivo no
   es un error, es un estado. */
.estado-inactivo,
.estado-activo,
.etiqueta-info {
  display: inline-flex;
  align-items: center;
  padding: 2px var(--space-2);
  border-radius: var(--radius-pill);
  font-size: var(--text-caption-size);
  line-height: var(--text-caption-lh);
  font-weight: var(--weight-medium);
  white-space: nowrap;
}

.estado-inactivo {
  background: var(--surface-active);
  color: var(--text-secondary);
}

/* La contraria, para donde la excepción es la de estar activo — hoy el listado
   de Productos, cuyo bActivo casi nunca está puesto. Verde de los tokens
   semánticos (--success-soft / --success-text), que ya tienen su versión para
   modo oscuro; no un verde escrito a mano, que en oscuro se apagaría. */
.estado-activo {
  background: var(--success-soft);
  color: var(--success-text);
}

/* Dato informativo en forma de etiqueta (p. ej. Vida útil en Productos): misma
   geometría que los estados, con el par azul de los tokens semánticos. */
.etiqueta-info {
  background: var(--info-soft);
  color: var(--info-text);
}

/* Borrar en tinta apagada hasta que lo apuntas. Veinte íconos rojos bajando
   por la lista eran lo más fuerte de la pantalla, y gritaban en el estado de
   reposo —cuando nadie va a borrar nada—; el rojo aparece en el momento en
   que sí importa, que es cuando el cursor está encima. No se esconde la
   acción: el ícono sigue ahí, solo deja de gritar. */
.modulo .rz-button.rz-danger.rz-variant-flat {
  color: var(--text-tertiary);
  background: transparent;
}

.modulo .rz-button.rz-danger.rz-variant-flat:hover,
.modulo .rz-button.rz-danger.rz-variant-flat:focus-visible {
  color: var(--danger-text);
  background: var(--danger-soft);
}

/* Última columna (marca de excepción + editar + borrar): un gap parejo como
   única fuente de espacio, alineado a la derecha.

   Solo la ÚLTIMA celda: con `td .rz-cell-data:has(.rz-button)` a secas la
   regla también agarraba la columna de nombre del árbol de archivos, que
   lleva el botón de plegar la carpeta, y mandaba el nombre al borde derecho
   de la celda — con lo que las carpetas se veían más sangradas que su propio
   contenido. */
.rz-datatable-data td:last-child .rz-cell-data:has(.rz-button),
.rz-grid-table td:last-child .rz-cell-data:has(.rz-button) {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-2);
}

/* El flex-end de arriba es el reparto por defecto de una columna de acciones,
   no una decisión que deba pisar lo que la columna pide: si declara
   TextAlign.Center (Productos, con un solo botón en una columna angosta),
   manda esa. Sin esto el ícono quedaba pegado al borde derecho aunque la
   columna pidiera centrado. */
.rz-datatable-data td:last-child.rz-text-align-center .rz-cell-data:has(.rz-button),
.rz-grid-table td:last-child.rz-text-align-center .rz-cell-data:has(.rz-button) {
  justify-content: center;
}

/* ButtonSize.ExtraSmall en Radzen 7.1.3 baja a 1.25rem (20px) de alto/ancho
   mínimo -- por debajo de cualquier objetivo de clic razonable, y acá son
   dos íconos vecinos donde uno de ellos borra. Escalado solo DENTRO de
   listados, a algo compacto pero usable. */
.rz-datatable-data .rz-button-xs.rz-button-icon-only,
.rz-grid-table .rz-button-xs.rz-button-icon-only {
  min-height: 1.75rem;
  min-width: 1.75rem;
  padding: 0.375rem;
}

.rz-datatable-data .rz-button-xs .rzi,
.rz-grid-table .rz-button-xs .rzi {
  font-size: 1.125rem;
  width: 1.125rem;
  height: 1.125rem;
}

/* Enlace dentro de una celda: hereda la tinta del texto y solo se subraya al
   pasar por encima. Una columna entera en color de enlace (Productos tiene
   dos) se lee como ruido, no como jerarquía; por eso el archivo traía un
   Style="color:black" inline que apagaba el token a mano. Esto resuelve lo
   mismo desde el sistema, y deja el subrayado como señal de "esto se puede
   abrir". */
.rz-datatable-data td .rz-link,
.rz-grid-table td .rz-link {
  color: var(--text-primary);
  text-decoration: none;
}

.rz-datatable-data td .rz-link:hover,
.rz-grid-table td .rz-link:hover,
.rz-datatable-data td .rz-link:focus-visible,
.rz-grid-table td .rz-link:focus-visible {
  color: var(--text-link);
  text-decoration: underline;
  text-underline-offset: 2px;
}

/* Fuera de una tabla el enlace sí lleva el color de enlace del sistema. */
.rz-link {
  color: var(--text-link);
}
.rz-link:hover,
.rz-link:focus {
  color: var(--text-link-hover);
}

/* La tabla ES la caja de la pantalla: filete, esquina redondeada del token y sin
   sombra. Se declara sobre las dos raíces porque RadzenDataGrid emite ambas
   clases, y apuntar solo a una deja grillas sin redondear. overflow:hidden
   recorta las esquinas de la banda de cabecera contra el radio. */
.rz-data-grid,
.rz-datatable {
  position: relative;
  border: var(--border-width) solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: none;
  overflow: hidden;
}

/* Productos conserva el filete entre filas igual que el resto de los listados.
 * Se probó quitarlo y dejar solo la banda alterna: con una fila de más de mil
 * pixeles la banda guía bien el recorrido, pero el filete es lo que marca dónde
 * termina un registro, y sin él las filas de un mismo producto se leen como un
 * bloque. Van los dos. El filete sale del token de rz-adapter.css; no hace
 * falta redeclararlo acá. */

/* Columna de acción: el ancho lo pone el botón, no la rejilla. Sin recortar el
   relleno lateral de la celda, la columna no puede bajar de ~56px aunque se le
   declare menos, porque el relleno se suma al botón. El centrado se declara acá y
   no se deja al TextAlign de la columna: la celda va congelada a la derecha y ahí
   Radzen le pone su propio relleno. */
.grid-productos .col-accion {
  padding-inline: var(--space-1);
  text-align: center;
}

.grid-productos .col-accion .rz-cell-data {
  display: flex;
  justify-content: center;
}

/* Agrupado y SOLO en tema oscuro: la jerarquía la marca el plano, no el filete.
 *
 * En claro la banda alterna separa bien las filas de un grupo, pero sobre un
 * plano oscuro esa misma alternancia compite con el escalón entre grupo y
 * detalle y se pierden los dos. Acá el subnivel va todo a un mismo tono (la
 * hoja) y la cabecera del grupo baja al tono del campo: el bloque de datos
 * queda flotando sobre una banda más oscura que lo encabeza, que es como se
 * lee un árbol sin tener que contar filetes.
 *
 * El :not(:hover):not(:focus-within) es lo que deja pasar el resaltado de fila
 * de más abajo: sin él esta regla, que es más específica, lo anularía y se
 * perdería la única pista de en qué fila estás dentro de una de mil pixeles.
 *
 * La fila de grupo se reconoce por .grupo-cabecera — el div del
 * GroupHeaderTemplate de la página — y NO por una clase de Radzen. El DataGrid
 * no marca esa fila con .rz-rowgroup-header (esa clase es de otro componente),
 * así que anclarse ahí dejaba la fila de grupo cayendo en la banda alterna: su
 * color dependía de si le tocaba posición par o impar, y al expandir un grupo
 * se corren todas las de abajo y el primer nivel cambiaba de tono solo.
 *
 * Las dos reglas se excluyen entre sí a propósito (:not(:has(...)) contra
 * :has(...)) en vez de pisarse por orden: así ninguna depende de ganarle a la
 * otra por especificidad, que es justo lo que se rompe al tocar cualquiera. */
:root[data-theme="dark"] .grid-productos.grid-agrupado .rz-grid-table tbody > tr:not(:has(.grupo-cabecera)):not(:hover):not(:focus-within) > td {
  background: var(--surface);
}

:root[data-theme="dark"] .grid-productos.grid-agrupado .rz-grid-table tbody > tr:has(.grupo-cabecera) > td {
  background: var(--background);
}

/* La fila señalada se ve de verdad, y una guía marca dónde empieza: con una fila
   de más de mil pixeles, saber en cuál estás es lo que evita leer el dato de otro
   producto. Responde también al foco, no solo al ratón, para quien tabula. */
.grid-productos .rz-grid-table tbody tr.rz-data-row:hover > td,
.grid-productos .rz-grid-table tbody tr.rz-data-row:focus-within > td {
  background: var(--surface-hover);
}

.grid-productos .rz-grid-table tbody tr.rz-data-row:hover > td:first-child,
.grid-productos .rz-grid-table tbody tr.rz-data-row:focus-within > td:first-child {
  box-shadow: inset 3px 0 0 var(--accent);
}

/* El pie de la grilla (paginador + resumen) se apoya en la hoja, sin banda
   propia: con la tabla ya enmarcada, un fondo extra acá solo agrega peso. */
.rz-pager {
  background: transparent;
  border-top: var(--border-width) solid var(--border);
}

/* Aquí vivía una regla que escondía el paginador cuando solo había una
   página, apoyándose en que no hubiera dos `.rz-pager-page` adyacentes. En
   Radzen 7 los números eran hermanos directos y funcionaba; en Radzen 11
   cada uno va envuelto en un `.rz-pager-item`, así que la condición no se
   cumplía nunca y el pie ENTERO —paginación, tamaño de página y resumen—
   desaparecía en cualquier grilla dentro de .modulo.

   No hace falta CSS para esto: RadzenDataGrid ya trae PagerAlwaysVisible en
   false, que es justamente "no dibujes el paginador si todo cabe en una
   página". Si alguna grilla lo muestra de más, se corrige ahí y no acá. */

/* --- Cabecera fija ---
 *
 * Radzen ya deja el área de datos como contenedor con overflow:auto, pero sin
 * un alto que la limite nunca llega a scrollear: crece y el que scrollea es
 * la página, con lo que los títulos de columna y el paginador se van para
 * arriba. Al acotar el alto, el scroll ocurre DENTRO de la tabla: los títulos
 * quedan fijos arriba y el paginador siempre visible al pie. Es max-height,
 * no height: una tabla corta no se estira ni deja hueco. */
.rz-data-grid-data {
  max-height: calc(100vh - 20rem);
}

/* El filete de la cabecera va como sombra interior y no como border-bottom:
   en una tabla con border-collapse el borde de una celda sticky se pierde al
   scrollear, la sombra no. */
.rz-grid-table thead th,
.rz-datatable-data thead th {
  position: sticky;
  top: 0;
  z-index: 1;
  background: var(--surface-secondary);
  box-shadow: inset 0 -1px 0 var(--border);
}

/* La celda de cabecera de una columna congelada se queda fija en los dos
   ejes (arriba y al costado). Radzen le pone el fondo de celda congelada
   —blanco— y quedaría como un hueco claro en medio de la banda: acá vuelve
   al color de la banda, con más especificidad que esa regla. */
.rz-grid-table thead th.rz-frozen-cell,
.rz-datatable-data thead th.rz-frozen-cell {
  background: var(--surface-secondary);
}