/* =========================================================================
   admin2 — estilos propios sobre Bootstrap 5.
   Solo lo que Bootstrap no cubre: el armazon de dos columnas y la barra.
   ========================================================================= */

:root {
  /*
   * El panel se ve igual con el sistema en claro y en oscuro.
   *
   * Declarar color-scheme: only light no es cosmetico: sin el, Safari y Chrome
   * pintan en oscuro por su cuenta los controles de formulario, las barras de
   * desplazamiento y los desplegables nativos cuando el sistema esta en oscuro,
   * aunque el resto de la pagina siga clara. El resultado era un panel a medio
   * teñir a partir de cierta hora.
   *
   * Antes habia ademas cuatro bloques @media (prefers-color-scheme: dark) con
   * su version oscura. Se han retirado: dos de sus guardias estaban mal
   * escritos —comprobaban data-bs-theme en <body>, donde no esta, y un
   * data-tema que no existe en ninguna plantilla—, asi que se aplicaban
   * SIEMPRE que el sistema estuviera en oscuro y oscurecian el fondo del panel
   * y la pantalla de acceso. Manteniendo un solo tema no hay dos versiones que
   * puedan descuadrarse.
   */
  color-scheme: only light;

  /* Barra superior blanca: deja que el logotipo, que es negro con azul y rojo
     corporativos, se vea tal cual sin fondos ni inversiones de color. */
  --app-barra: #ffffff;
  --app-barra-texto: #1f2937;
  /* Alto de la barra superior = alto del logotipo. Se guarda en una variable
     porque de el dependen tambien el alto del armazon y la posicion de los
     avisos; con numeros sueltos acabarian desincronizandose. */
  --app-barra-alto: 47px;
  /* 25% mas estrecha que antes (232px) para dejar sitio al area de trabajo. */
  --app-menu-ancho: 174px;
}

body {
  background-color: #f4f6fa;
}

/* ---------------------------------------------------------------- barra top */

.app-topbar {
  background-color: var(--app-barra);
  color: var(--app-barra-texto);
  /* La barra mide exactamente lo que el logotipo: se anula el relleno vertical
     que .navbar trae de serie (0.5rem arriba y abajo), que si no sumaria 16px
     de aire sobrante. */
  height: var(--app-barra-alto);
  min-height: var(--app-barra-alto);
  padding-top: 0;
  padding-bottom: 0;
  /* La barra lateral tambien es blanca: sin este filete no se distinguirian. */
  border-bottom: 1px solid var(--bs-border-color);
}

.app-topbar .navbar-brand {
  color: var(--app-barra-texto);
}

/* Logotipo de la barra superior. Con la barra en blanco va limpio, sin fondo
   ni contenedor: es el motivo de haberla aclarado. */
.app-marca {
  display: inline-flex;
  align-items: center;
  line-height: 0;
}

.app-marca img {
  /* El archivo esta recortado a su contenido real (270x65), asi que estos 37px
     son 37px de logotipo: no hay transparencia de relleno como en el PNG
     anterior, que traia 43px de vacio debajo del dibujo. */
  height: 37px;
  width: auto;
  display: block;
}

/* ------------------------------------------------------------------ armazon */

.app-shell {
  display: flex;
  align-items: stretch;
  min-height: calc(100vh - var(--app-barra-alto));
}

.app-contenido {
  flex: 1 1 auto;
  min-width: 0;            /* deja que las tablas se encojan en lugar de desbordar */
  display: flex;
  flex-direction: column;
}

.app-contenido > .container-fluid {
  flex: 1 0 auto;
}

/* -------------------------------------------------------------------- menu */

.app-menu {
  flex: 0 0 var(--app-menu-ancho);
  width: var(--app-menu-ancho);
  background-color: #fff;
  border-right: 1px solid var(--bs-border-color);
}

.app-menu .nav-link {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 0.75rem;
  color: var(--bs-body-color);
  border-left: 3px solid transparent;
  font-size: 0.85rem;
}

.app-menu .nav-link:hover {
  background-color: var(--bs-tertiary-bg);
}

.app-menu .nav-link.active {
  background-color: var(--bs-primary-bg-subtle);
  border-left-color: var(--bs-primary);
  font-weight: 600;
}

.app-menu-icono {
  width: 1.25rem;
  text-align: center;
  opacity: 0.7;
}

.app-menu .offcanvas-body {
  display: flex;
  flex-direction: column;
}

/* ------------------------------------------------ estadisticas del lateral */

.app-menu-stats {
  border-top: 1px solid var(--bs-border-color);
  padding: 0.75rem 0.75rem 0.5rem;
}

.app-menu-stats-titulo {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--bs-secondary-color);
  margin-bottom: 0.5rem;
}

.app-menu-stats-titulo img {
  border-radius: 2px;
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.1);
}

/* Cada dato: numero grande alineado a la derecha y etiqueta pequena al lado.
   Es la misma lectura del panel antiguo, pero sin tipografias sueltas. */
.app-stat {
  display: flex;
  align-items: baseline;
  justify-content: flex-end;
  gap: 0.35rem;
  padding: 0.15rem 0;
}

.app-stat-valor {
  font-size: 1.15rem;
  font-weight: 700;
  line-height: 1.2;
  font-variant-numeric: tabular-nums;
}

.app-stat-etiqueta {
  font-size: 0.7rem;
  color: var(--bs-secondary-color);
}

/* Los programados son informativos: se atenuan para que no compitan. */
.app-stat-suave .app-stat-valor {
  font-weight: 600;
  color: var(--bs-secondary-color);
}

/* Cuenta atras del refresco de las cifras: mitad de tamano y gris al 50%,
   para que se lea como una nota al pie y no compita con los numeros. */
.app-stats-cuenta {
  margin: .4rem 0 0;
  text-align: right;
  font-size: 50%;
  color: var(--bs-secondary-color);
  opacity: .5;
  font-variant-numeric: tabular-nums;
}

/* ─────────────────────────────────────────────── movil y tableta */

@media (max-width: 991.98px) {
  /* El menu es un offcanvas: no debe ocupar sitio en el flujo. */
  .app-menu {
    flex: 0 0 auto;
    width: auto;
    border-right: 0;
  }

  /* La barra ya mide 47px en todos los tamanos: no hace falta bajarla mas en
     movil, y el logotipo a 37px deja sitio al boton de menu y al de cuenta. */
}

/* La barra nunca debe partirse en dos lineas: antes de eso, que el contenido
   se ajuste. .navbar solo pone nowrap a partir del breakpoint de expansion. */
.app-topbar > .container-fluid {
  flex-wrap: nowrap;
}

/* En pantallas muy estrechas el nombre del administrador puede recortarse; el
   boton sigue siendo pulsable y el menu desplegable muestra todo. */
.app-topbar .dropdown-toggle {
  max-width: 8rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* -------------------------------------------------------------------- pie */

.app-pie {
  font-size: 0.8125rem;
  color: var(--bs-secondary-color);
  border-top: 1px solid var(--bs-border-color);
}

/* ------------------------------------------------------------------ avisos */

.app-avisos {
  position: fixed;
  /* Justo debajo de la barra, sea cual sea su alto. */
  top: calc(var(--app-barra-alto) + 1rem);
  right: 1rem;
  z-index: 1080;
  width: min(24rem, calc(100vw - 2rem));
}

.app-avisos .alert {
  box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
}

/* ------------------------------------------------- acceso / recuperacion */

.app-acceso {
  display: grid;
  place-items: center;
  min-height: 100vh;
  padding: 1.5rem 1rem;
  /* Fondo sobrio con un punto de luz detras de la tarjeta, para que el logo
     en PNG con transparencia se recorte limpio sobre claro. */
  background:
    radial-gradient(1200px 600px at 50% -10%, #ffffff 0%, #eef1f6 55%, #e6eaf1 100%);
}

.app-acceso-caja {
  width: min(25rem, 100%);
}

/* El logo mide 270x65 px y se muestra a tamano real. El max-width evita que
   se salga de la tarjeta en pantallas estrechas. */
.app-acceso-logo {
  width: 270px;
  height: auto;
  max-width: 100%;
}

.app-acceso-tarjeta {
  border: 1px solid rgba(20, 30, 55, 0.08);
  border-radius: 0.75rem;
  box-shadow:
    0 1px 2px rgba(16, 24, 40, 0.04),
    0 12px 32px -12px rgba(16, 24, 40, 0.18);
}

/* Boton "Ver" superpuesto dentro del campo de contrasena. */
.app-acceso-clave {
  position: relative;
}

.app-acceso-clave .form-control {
  padding-right: 3.25rem;
}

.app-acceso-ver {
  position: absolute;
  top: 50%;
  right: 0.25rem;
  transform: translateY(-50%);
  padding: 0.25rem 0.6rem;
  font-size: 0.8125rem;
  text-decoration: none;
  color: var(--bs-secondary-color);
}

.app-acceso-ver:hover,
.app-acceso-ver:focus-visible {
  color: var(--bs-primary);
}

/* En modo oscuro el logo es negro sobre fondo oscuro: se aclara el lienzo
   detras en vez de invertir el logo, que tiene rojo y azul corporativos. */
/* --------------------------------------------------------- tabla de modems */

/* El color de fondo indica el estado del modem, igual que en el panel antiguo.
   Se usa --bs-table-bg para que Bootstrap no lo pise con el rayado de la tabla,
   y se acompana SIEMPRE de una etiqueta de texto: el color por si solo no vale
   como unica senal para quien no distingue verde de rojo.

   Los cuatro tonos viven en variables porque los usan DOS sitios: las filas de
   la tabla y las muestras de la leyenda del pie. Escritos dos veces acabarian
   separandose en el primer retoque, y una leyenda que no coincide con la tabla
   es peor que no tener leyenda. */
:root {
  --app-modem-activo: #d9f5df;
  --app-modem-error: #ffe0b8;
  --app-modem-apagado: #ffd6d6;
  --app-modem-hibernado: #e7e9ee;
}

.app-tabla-modems > tbody > tr.app-modem-activo    { --bs-table-bg: var(--app-modem-activo); }
.app-tabla-modems > tbody > tr.app-modem-error     { --bs-table-bg: var(--app-modem-error); }
.app-tabla-modems > tbody > tr.app-modem-apagado   { --bs-table-bg: var(--app-modem-apagado); }
.app-tabla-modems > tbody > tr.app-modem-hibernado { --bs-table-bg: var(--app-modem-hibernado); }

/* Leyenda de colores al pie de la tabla. */
.app-leyenda {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem 1.1rem;
  font-size: 0.7rem;
  color: var(--bs-secondary-color);
}

.app-leyenda-item {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
}

/* La muestra lleva borde propio: los tonos claros sobre fondo blanco casi no se
   distinguen del papel, y el gris de "inactivo" desapareceria del todo. */
.app-leyenda-muestra {
  width: 0.9rem;
  height: 0.9rem;
  border-radius: 3px;
  border: 1px solid rgba(0, 0, 0, .18);
  flex: 0 0 auto;
}

/* Los botones con borde (btn-outline-*) son transparentes por definicion, asi
   que sobre las filas de color de la tabla de modems o sobre el fondo gris de
   la pagina se ven deslavados. En esta pantalla se les da fondo solido: siguen
   distinguiendose por el color del borde y del texto, pero se leen como
   botones. Los de accion sobre un modem concreto ya van en version solida. */
.app-tabla-modems .btn-outline-primary,
.app-tabla-modems .btn-outline-secondary,
.app-tabla-modems .btn-outline-success,
.app-tabla-modems .btn-outline-danger,
.app-tabla-modems .btn-outline-warning,
.app-tabla-modems .btn-outline-dark,
.app-saldo-controles .btn-outline-primary,
.app-saldo-controles .btn-outline-secondary,
.app-saldo-controles .btn-outline-success,
.app-saldo-controles .btn-outline-danger {
  background-color: #fff;
}

/* Al pulsarlos o pasar por encima recuperan el relleno de color de Bootstrap. */
.app-saldo-controles .btn-outline-primary:hover,
.app-saldo-controles .btn-outline-primary.active,
.app-saldo-controles .btn-outline-secondary:hover,
.app-saldo-controles .btn-outline-success:hover,
.app-saldo-controles .btn-outline-danger:hover {
  background-color: var(--bs-btn-hover-bg);
}

/* Contenedor de grafica con altura fija: Chart.js se adapta al padre cuando
   maintainAspectRatio es false, asi que la altura se controla aqui y no en el
   atributo height del canvas, que el propio Chart.js sobrescribe. */
.app-grafica {
  position: relative;
  /* 185px: las etiquetas del eje X ocupan dos lineas (fecha y dia). */
  height: 185px;
}

/* Tiempo restante dentro del boton de refrescar: la mitad de tamano, para que
   se lea como dato secundario y no como parte de la etiqueta. */
.app-cuenta-atras {
  font-size: 50%;
  opacity: .7;
  font-variant-numeric: tabular-nums;
  margin-left: .15rem;
}

/* Los botones de la columna Acciones, centrados y con separacion uniforme.
   Se usa un contenedor flex en la propia celda para que no dependan de los
   espacios en blanco del HTML. */
.app-modem-acciones {
  white-space: nowrap;
}

.app-modem-acciones .btn + .btn {
  margin-left: .25rem;
}

/* Rotulo de las tarjetas del resumen de Saldo. */
.app-tarjeta-titulo {
  font-size: 1.1rem;
  line-height: 1.35;
  color: var(--bs-secondary-color);
}

/* Tarjetas con un tono suave de fondo.

   Se usan las variables *-bg-subtle y *-border-subtle de Bootstrap en vez de
   colores escritos a mano porque ya vienen resueltas para el modo claro y el
   oscuro: en oscuro un #e7f1ff fijo deslumbraria. El filete de la izquierda es
   lo que de verdad las separa; el fondo solo insinua.

   Aun asi, usados tal cual tinen mas de lo que pide un fondo de tarjeta, de
   modo que se rebajan al 45% contra el blanco de la propia tarjeta con
   color-mix. La primera declaracion de background-color es la reserva para
   navegadores sin color-mix: deja el tono sin rebajar, mas subido de lo
   deseado pero legible, nunca en blanco.

   Los tintes van por color y no por concepto (.app-tinte-verde y no
   .app-tarjeta-saldo) porque el mismo tono lo usan ya tres pantallas: con una
   clase por tarjeta habia que duplicar el bloque entero cada vez. */
.app-tarjeta {
  border-left: 3px solid transparent;
}

.app-tinte-azul {
  background-color: var(--bs-primary-bg-subtle);
  background-color: color-mix(in srgb, var(--bs-primary-bg-subtle) 45%, var(--bs-card-bg, #fff));
  border-left-color: var(--bs-primary-border-subtle);
}

.app-tinte-verde {
  background-color: var(--bs-success-bg-subtle);
  background-color: color-mix(in srgb, var(--bs-success-bg-subtle) 45%, var(--bs-card-bg, #fff));
  border-left-color: var(--bs-success-border-subtle);
}

.app-tinte-celeste {
  background-color: var(--bs-info-bg-subtle);
  background-color: color-mix(in srgb, var(--bs-info-bg-subtle) 45%, var(--bs-card-bg, #fff));
  border-left-color: var(--bs-info-border-subtle);
}

.app-tinte-ambar {
  background-color: var(--bs-warning-bg-subtle);
  background-color: color-mix(in srgb, var(--bs-warning-bg-subtle) 45%, var(--bs-card-bg, #fff));
  border-left-color: var(--bs-warning-border-subtle);
}

/* Rotulo a la izquierda y cifra a la derecha, en una sola linea.

   Se alinean por la BASE y no por el centro: son dos tipografias de tamano
   distinto y centradas se ve que no se apoyan en la misma raya. El gap y el
   flex-wrap son la precaucion de tamano: cuando el ancho no da, la cifra baja
   a su propia linea entera en vez de encogerse o partirse. */
.app-tarjeta-cuerpo {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.25rem 1rem;
  padding-top: 0.6rem;
  padding-bottom: 0.6rem;
}

.app-tarjeta-cifra {
  font-size: 1.25rem;
  font-weight: 500;
  line-height: 1.2;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
  margin-left: auto;   /* pegada a la derecha tambien cuando cae sola */
}

/* En las tarjetas de una linea el rotulo compite con la cifra, asi que baja
   del 1.1rem que usa /saldo, donde va en su propio renglon. */
.app-tarjeta-cuerpo .app-tarjeta-titulo {
  font-size: 0.9rem;
}

/* Sobre fondo de color, el gris de .text-body-secondary del titulo pierde
   contraste; se oscurece un punto para que siga leyendose. */
.app-tarjeta .app-tarjeta-titulo {
  color: var(--bs-emphasis-color);
  opacity: 0.72;
}

/* Tabla de recargas: todo centrado. Se hace aqui y no con clases en cada celda
   porque estas filas se repintan desde JavaScript al cambiar de dia, y asi el
   alineado no depende de que las dos plantillas —la de PHP y la de JS— se
   mantengan sincronizadas. Se centra tambien la cabecera: con los titulos a un
   lado y los datos al otro parece un fallo de maquetacion. */
.app-tabla-recargas th,
.app-tabla-recargas td {
  text-align: center;
}

/* El importe conserva la tabulacion fija para que las cifras se alineen entre
   si aunque tengan distinto numero de digitos. */
.app-tabla-recargas td:last-child,
.app-tabla-recargas tfoot td {
  font-variant-numeric: tabular-nums;
}

/* Saldo con formato moneda y tabulacion fija, para que las cifras se alineen
   por columnas aunque tengan distinto numero de digitos. */
.app-saldo {
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* El bono va pegado al saldo (5.000/200): 75% de tamano y en gris, porque es
   informacion secundaria. Antes ocupaba una columna entera. */
.app-saldo-bono {
  font-size: 75%;
  color: var(--bs-secondary-color);
  opacity: .75;
}

/* Fila oculta por el filtro de servidor. */
.app-tabla-modems tr[hidden] {
  display: none;
}

/* Bloque de autorecarga: se resalta mientras se guarda. Es una fila de una
   lista, no una tarjeta, asi que el realce va por fondo y no por contorno. */
.app-autorecarga.app-guardando {
  background-color: var(--bs-primary-bg-subtle);
  transition: background-color .15s ease;
}

/* Rejilla de la autorecarga: tantas columnas como quepan de 165px. En pantalla
   ancha salen dos servidores por fila; en un portatil, uno. Se decide por el
   ancho REAL del contenedor, no por el del viewport, que es lo que fallaria
   aqui: esta columna mide un tercio de la pantalla, no la pantalla entera. */
.app-autorecarga-lista {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(165px, 1fr));
  gap: .5rem 1rem;

  /* Se declara contenedor para poder consultar SU ancho mas abajo. */
  container-type: inline-size;
}

/* Cada fila en tres columnas: etiqueta a la izquierda, interruptor CENTRADO y
   campo a la derecha. Con flex el interruptor quedaba pegado a la etiqueta,
   porque se centraba en el hueco sobrante y no en la fila. Las columnas
   laterales son minmax(0, 1fr): iguales entre si —lo que deja el interruptor
   en el centro real— y capaces de encogerse sin desbordar. */
.app-autorecarga-lista > .app-autorecarga {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  align-items: center;
  gap: .5rem;
  padding: .35rem 0;
}

.app-autorecarga-lista > .app-autorecarga > :last-child {
  justify-self: end;
}

/* El campo llena su columna hasta un tope, para que no se estire en pantalla
   ancha ni desaparezca en la estrecha. */
.app-autorecarga input[type="number"] {
  width: 100%;
  max-width: 4.75rem;
  min-width: 3.25rem;
}

/* Separador vertical entre las dos columnas.
   La condicion es la misma que hace saltar la rejilla a dos columnas:
   2 x 165px + 1rem de hueco = 346px. Se consulta el ancho del CONTENEDOR, no el
   del viewport, porque esta columna mide un tercio de la pantalla; con una
   media query normal la linea aparecereria tambien cuando hay una sola columna.
   El filete va en los elementos pares, que en dos columnas son siempre los de
   la derecha; el tercero cae en la fila siguiente y no lo lleva. */
@container (min-width: 346px) {
  .app-autorecarga-lista > .app-autorecarga:nth-child(even) {
    border-left: 1px solid var(--bs-border-color);
    padding-left: 1rem;
  }
}

/* Interruptor mas grande.
   Bootstrap dimensiona .form-check-input en "em" (1em de alto, 2em de ancho en
   los form-switch), asi que subir el font-size del propio input lo escala
   entero sin tocar proporciones. Se anulan ademas el padding y el margen
   negativo que .form-check reserva para una etiqueta que aqui no existe. */
.app-autorecarga .form-switch {
  padding-left: 0;
  margin-bottom: 0;
  min-height: 0;
}

.app-autorecarga .form-switch .form-check-input {
  font-size: 1.35rem;
  margin-left: 0;
  margin-top: 0;
  cursor: pointer;
}

/* Campos numericos sin las flechas de incremento: el valor se escribe, no se
   sube de uno en uno, y las flechas se comen parte del ancho util. Hacen falta
   las dos reglas porque cada motor las dibuja de forma distinta. */
.app-sin-flechas,
.app-autorecarga input[type="number"] {
  -moz-appearance: textfield;
  appearance: textfield;
}

.app-sin-flechas::-webkit-outer-spin-button,
.app-sin-flechas::-webkit-inner-spin-button,
.app-autorecarga input[type="number"]::-webkit-outer-spin-button,
.app-autorecarga input[type="number"]::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

/* Campo deshabilitado: se atenua para que se vea que la autorecarga esta
   apagada, sin llegar a ocultarlo. */
.app-autorecarga input[type="number"]:disabled {
  background-color: var(--bs-secondary-bg);
  opacity: .55;
  cursor: not-allowed;
}

/* --------------------------------------------- campo de contrasena con "Ver" */

/* Version generica del patron de la pantalla de acceso, para usarlo tambien
   dentro del panel (perfil). */
.app-clave {
  position: relative;
}

.app-clave .form-control {
  padding-right: 3rem;
}

.app-clave-ver {
  position: absolute;
  top: 50%;
  right: 0.15rem;
  transform: translateY(-50%);
  padding: 0.15rem 0.5rem;
  font-size: 0.75rem;
  text-decoration: none;
  color: var(--bs-secondary-color);
}

.app-clave-ver:hover,
.app-clave-ver:focus-visible {
  color: var(--bs-primary);
}

/* Texto largo (User-Agent) que no debe romper la anchura de la tarjeta. */
.app-truncar {
  max-width: 14rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ------------------------------------------------------------------ tablas */

/* Cola de envio: lo programado para otro dia, sobre fondo gris.

   Se pintan las variables de Bootstrap y no `background-color` a secas porque
   la tabla es .table-hover, y el resaltado del raton no cambia el fondo: mete
   su color en --bs-table-bg-state, que tiene prioridad sobre --bs-table-bg. Si
   solo se fijara el fondo normal, al pasar por encima la fila volveria al color
   de las demas y se perderia la marca justo cuando se esta mirando. */
#tablaCola tbody tr.app-fila-otro-dia {
  --bs-table-bg: var(--bs-secondary-bg);
  --bs-table-hover-bg: var(--bs-secondary-bg);
  color: var(--bs-secondary-color);
}

.app-nota-cola,
.app-nota {
  font-size: 0.7rem;
  color: var(--bs-secondary-color);
}

/* ------------------------------------------------------------ estadisticas */

/* Tablas de dos columnas —concepto a la izquierda, cifra a la derecha— de los
   bloques "Datos SMS" y "Facturacion". */
.app-tabla-cifras th {
  font-weight: 400;
  color: var(--bs-secondary-color);
}

/* El `white-space: nowrap` que habia aqui estaba pensado para que un importe
   no se partiera por el separador de miles, pero estas tablas tambien listan
   nombres de compania, y hay algunos larguisimos ("COOPERATIVA DE AHORRO Y
   CREDITO REFACCIONARIO DE LA COMUNIDAD DE SA -SINPE-"). Al no poder partir,
   la celda crecia y se salia del dialogo por el lado derecho.

   Ahora se permite partir: los importes no se ven afectados porque caben de
   sobra en una linea, y los nombres largos ocupan dos en vez de desbordar. */
.app-tabla-cifras td {
  text-align: right;
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}

/* Y el reparto se fija, en vez de dejar que lo calcule el navegador.
   
   Con la maquetacion automatica, el ancho de la tabla lo decide su contenido:
   un nombre de compania muy largo puede empujar la tabla mas alla del dialogo
   antes de que el navegador se plantee partirlo. Con table-layout fijo el
   reparto lo mandan estos porcentajes y el contenido se acomoda dentro,
   pase lo que pase. */
.app-tabla-cifras {
  table-layout: fixed;
  width: 100%;
}

.app-tabla-cifras th {
  width: 45%;
}

.app-tabla-cifras .app-fila-fuerte th,
.app-tabla-cifras .app-fila-fuerte td {
  font-weight: 600;
  color: var(--bs-body-color);
}

/* SMS por compania: el nombre ocupa lo que sobre y se corta con puntos
   suspensivos en vez de partirse en dos lineas. El texto ya llega recortado
   desde JavaScript, pero un nombre largo sin recortar todavia cabe de sobra
   para desbordar la columna en una pantalla estrecha.

   El max-width:0 es el truco de siempre para que text-overflow funcione dentro
   de una tabla: sin una anchura declarada la celda crece con su contenido y
   nunca llega a haber desbordamiento que recortar. Al ser una tabla de
   maquetacion automatica, el 0 actua como "lo minimo posible" y el reparto
   real lo deciden las otras dos columnas, que no se parten. */
#tablaCompaniasMes td:first-child {
  max-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Resumen anual: 13 columnas de cifras no caben con la tipografia normal, asi
   que se reduce y se tabula para que los digitos queden en columna. */
.app-tabla-anual {
  font-size: 0.7rem;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

/* .table-sm deja las celdas en 0.25rem, y con trece columnas de cifras las
   tres filas quedaban apretadas una contra otra. Se sube el relleno vertical y
   se deja el horizontal corto, que es el que decide si la tabla cabe a lo
   ancho o hay que arrastrarla. */
.app-tabla-anual > :not(caption) > * > * {
  padding-top: 0.55rem;
  padding-bottom: 0.55rem;
  padding-left: 0.4rem;
  padding-right: 0.4rem;
}

.app-tabla-anual th[scope="row"] {
  font-weight: 700;
  cursor: help;
}

/* El mejor mes de cada fila, como en el panel antiguo. */
.app-tabla-anual .app-max {
  color: var(--bs-success);
  font-weight: 600;
}

/* Concepto con explicacion en el title. El subrayado de puntos es lo unico que
   avisa de que ahi hay algo que consultar: sin el, nadie pasaria el raton por
   encima. */
.app-con-ayuda {
  text-decoration: underline dotted var(--bs-border-color);
  text-underline-offset: 0.2em;
  cursor: help;
}

/* Mensajes recibidos del panel: el reparto de anchos lo fija panel.js en
   porcentajes (10 · 10 · 39 · 26 · 15). Con la maquetacion automatica el
   navegador los reajusta segun el contenido y un solo mensaje largo se lleva
   media tabla; con `fixed` manda la cabecera y las columnas no se mueven de
   una pagina a otra. */
#tablaRecibidos {
  table-layout: fixed;
}

/* El texto ya llega recortado desde el render, pero una palabra sin espacios
   —una URL larga— seguiria desbordando la celda. */
#tablaRecibidos td {
  overflow-wrap: anywhere;
}

/* "Lista negra" + "Borrar" ocupan 148px, y el 15% solo llega a esa cifra si la
   tabla mide al menos ~990px; por debajo, los botones se salen de su celda.
   El suelo se pone en la tabla y no en la celda a proposito: con
   `table-layout: fixed` el navegador reparte los porcentajes y NO respeta el
   min-width de un td, asi que ahi no serviria de nada.
   En pantallas menores manda el scroll lateral de .table-responsive, que ya
   estaba en la plantilla. */
#tablaRecibidos {
  min-width: 64rem;
}

/* Y aun con ese suelo los dos botones se pasaban por 6px del 15%. En vez de
   ensanchar la columna —que obligaria a hacer scroll lateral en un portatil de
   1280— se aprietan los botones: el relleno lateral de .btn-sm es de 8px y
   aqui sobra, porque la columna ya esta alineada a la derecha. */
#tablaRecibidos td:last-child .btn {
  padding-inline: 0.3rem;
}

/* SMS recibidos (/smsIn): mismo criterio que la tabla del panel. Los
   porcentajes de ancho solo se sostienen con table-layout fijo; si no, el
   navegador los reajusta segun el contenido y un mensaje largo se lleva media
   tabla. El suelo de 60rem es el que hace que el 12% de la columna de acciones
   siga dando para los dos botones. */
#tablaSmsIn {
  table-layout: fixed;
  /* 64rem y no menos: los dos botones necesitan 107px, y el 13% de la columna
     de acciones solo llega a esa cifra a partir de ~1000px de tabla. */
  min-width: 64rem;
}

#tablaSmsIn td {
  overflow-wrap: anywhere;
}

#tablaSmsIn td:last-child .btn {
  padding-inline: 0.3rem;
}

/* SMS enviados y lista negra: mismo criterio de anchos en porcentaje. La lista
   negra necesita menos suelo porque solo tiene un boton en acciones. */
#tablaSmsOut {
  table-layout: fixed;
  min-width: 62rem;
}

#tablaListaNegra {
  table-layout: fixed;
  min-width: 44rem;
}

#tablaSmsOut td,
#tablaListaNegra td {
  overflow-wrap: anywhere;
}

#tablaListaNegra td:last-child .btn {
  padding-inline: 0.4rem;
}

/* Numero de factura en las tablas de pendientes. Puede venir vacio o ser una
   referencia larga; se corta en una linea y el concepto completo queda en el
   title de la celda. */
.app-col-factura {
  max-width: 10rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Columna del Nº de factura: cabecera centrada y contenido a la derecha. En
   "por cobrar" es la 4ª (lleva delante Fecha y Fecha-factura); en las otras dos
   es la 3ª. */
#tablaFacturacion thead th:nth-child(3),
#tablaPorAutorizar thead th:nth-child(3),
#tablaPorCobrar thead th:nth-child(4) {
  text-align: center;
}

#tablaFacturacion tbody td:nth-child(3),
#tablaPorAutorizar tbody td:nth-child(3),
#tablaPorCobrar tbody td:nth-child(4) {
  text-align: right;
}

/* Colores de fila en Movimientos autorizados:
     rojo   -> la compania no tiene datos de facturacion (nombre y correo)
     naranja -> el movimiento no tiene numero de factura
   El rojo manda sobre el naranja (lo decide el JS/PHP: solo una clase por fila).
   Se pintan con --bs-table-bg para que el rayado de la tabla no los tape. */
/* Se pinta el fondo directo de las celdas (no via --bs-table-bg) y se anula el
   rayado de DataTables en esas filas, para que el color se vea siempre. */
#tablaFacturacion tbody tr.app-fila-sin-fact > * {
  background-color: var(--bs-danger-bg-subtle) !important;
  color: var(--bs-danger-text-emphasis);
  box-shadow: none !important;
}

#tablaFacturacion tbody tr.app-fila-sin-factura > * {
  background-color: var(--bs-warning-bg-subtle) !important;
  color: var(--bs-warning-text-emphasis);
  box-shadow: none !important;
}

/* Muestras de la leyenda, a juego con el fondo de cada fila. */
.app-muestra-sin-fact {
  background-color: var(--bs-danger-bg-subtle);
  border-color: var(--bs-danger-border-subtle);
}

.app-muestra-sin-factura {
  background-color: var(--bs-warning-bg-subtle);
  border-color: var(--bs-warning-border-subtle);
}

/* "Sin factura en sistema" en Pendientes de cobro: amarillo muy leve, a juego
   con el fondo que reciben esas filas. */
.app-muestra-sin-num {
  background-color: rgba(255, 199, 0, 0.05);
  border-color: var(--bs-warning-border-subtle);
}

/* Leyenda del gradiente de antiguedad de "Pendientes de cobro": una barra que
   va de transparente al mismo rojo tope (50 %) que la fila mas atrasada. */
.app-leyenda-antiguedad {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
}

.app-barra-antiguedad {
  display: inline-block;
  width: 8rem;
  height: 0.85rem;
  border: 1px solid var(--bs-border-color);
  border-radius: 0.2rem;
  background: linear-gradient(to right,
              rgba(220, 53, 69, 0), rgba(220, 53, 69, 0.5));
}

/* Zona de arrastre de facturas: caja punteada que reacciona al arrastre. El
   <input file> va oculto; esta caja es la que se ve y se pulsa. */
.app-soltar {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
  width: 100%;
  min-height: 6.5rem;
  padding: 1rem;
  text-align: center;
  color: var(--bs-body-color);
  background-color: var(--bs-tertiary-bg);
  border: 2px dashed var(--bs-border-color);
  border-radius: 0.5rem;
  cursor: pointer;
  transition: border-color 0.15s ease, background-color 0.15s ease;
}

.app-soltar:hover {
  border-color: var(--bs-primary);
}

.app-soltar-activa {
  border-color: var(--bs-primary);
  background-color: var(--bs-primary-bg-subtle);
}

/* Los hijos no interceptan el arrastre: sin esto, dragleave parpadea al pasar
   el cursor por encima del icono o del texto. */
.app-soltar > * {
  pointer-events: none;
}

.app-soltar-icono {
  font-size: 1.5rem;
  line-height: 1;
  color: var(--bs-primary);
}

/* Enlace de descarga del ZIP con los 3 ficheros de la factura. La flecha
   avisa de que baja un archivo, no de que navega a otra pagina. */
.app-enlace-descarga {
  text-decoration: none;
  font-weight: 600;
  color: inherit;            /* no parece un enlace: hereda el color del texto */
}

.app-enlace-descarga::after {
  content: " \2913";   /* ⤓ */
  font-weight: 700;
}

.app-enlace-descarga:hover {
  text-decoration: none;
  opacity: 0.7;
}

/* Numero (descarga) + lupa (ver PDF) juntos en la celda de factura. */
.app-col-factura-enlaces {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
}

.app-enlace-ver {
  text-decoration: none;
}

.app-enlace-ver:hover {
  text-decoration: none;
  opacity: 0.7;
}

/* Campo de solo lectura: sombreado y cursor bloqueado para que se note que su
   valor es calculado y no se teclea (p. ej. los SMS en "Modificar"). */
.app-campo-bloqueado,
.app-campo-bloqueado:focus {
  background-color: var(--bs-secondary-bg);
  color: var(--bs-secondary-color);
  cursor: not-allowed;
  box-shadow: none;
}

/* Resumen de "Enviar factura": etiquetas tenues, cifras alineadas y el
   redondeo separado por un filete para que se lea como el cierre del calculo. */
.app-resumen-factura dt {
  font-weight: 500;
  color: var(--bs-secondary-color);
}

.app-resumen-factura dd {
  font-variant-numeric: tabular-nums;
}

.app-resumen-factura .app-resumen-redondeo {
  font-weight: 600;
  border-top: 1px solid var(--bs-border-color);
  padding-top: 0.35rem;
  margin-top: 0.15rem;
}

/* Fila de totales al pie de las tablas de facturacion. Un filete arriba y algo
   mas de peso: tiene que leerse como cierre, no como una fila mas. */
.app-totales th,
.app-totales td {
  border-top: 2px solid var(--bs-border-color);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

#tablaFacturacion {
  table-layout: fixed;
}

#tablaFacturacion td {
  overflow-wrap: anywhere;
}

/* DataTables 2 adivina el tipo de cada columna y le pone dt-type-date o
   dt-type-numeric; su hoja de estilos alinea ambas a la derecha. Para los
   importes es lo que queremos, pero una fecha pegada al borde derecho no casa
   con el resto del panel, donde las fechas van a la izquierda. */
#tablaPorCobrar th.dt-type-date,
#tablaPorCobrar td.dt-type-date {
  text-align: left;
}

/* Al meter el filtro de meses en la fila de controles de la tabla, las columnas
   de Bootstrap lo estrechan hasta cortar el texto ("Julio 2("). Estos selects
   no pueden encogerse por debajo de lo que mide "Septiembre 2026". */
.dt-container [data-filtros] select {
  min-width: 9.5rem;
}

/* Lo mismo para los campos de fecha: el control nativo lleva ademas el icono
   del calendario, asi que necesita mas sitio del que aparenta el texto. Sin
   esto se leia "22/07/2". */
.dt-container [data-filtros] input[type="date"] {
  min-width: 8.5rem;
}

/* Dentro de la tabla, cada control ocupa lo que necesita y no la fraccion de
   la rejilla de 12 columnas que le tocaria.

   Los porcentajes de Bootstrap tienen sentido cuando el formulario ocupa el
   ancho de la pagina, pero aqui comparte fila con "Ver" y "Buscar" y no hay
   holgura: con ellos, el selector de compania se llevaba un tercio del hueco y
   empujaba el boton "Hoy" a una segunda linea. */
.dt-container [data-filtros] > [class*="col-"] {
  flex: 0 0 auto;
  width: auto;
}

/* La compania es el unico control de ancho variable: se le pone un techo para
   que un nombre largo no vuelva a desplazar a los botones. */
.dt-container [data-filtros] select[name="c"] {
  max-width: 13rem;
}

/* Y el formulario no envuelve mientras quepa a lo ancho.
   
   Sin esto se partia en dos lineas aun sobrando sitio: la celda que lo contiene
   se dimensiona con el contenido YA ENVUELTO, asi que se quedaba en 623px
   cuando habia 982 libres, y el boton "Hoy" caia debajo. Con nowrap la celda
   mide lo que miden los controles seguidos y todo entra en una linea.

   Por debajo de 768px no aplica: ahi la propia rejilla de DataTables baja el
   formulario a su linea completa y envolver es lo correcto. */
@media (min-width: 768px) {
  .dt-container [data-filtros] {
    flex-wrap: nowrap;
  }
}

/* Tipografia de las tablas de datos: algo menor que el texto corrido, para que
   quepan mas filas sin recurrir al scroll. */
.table {
  font-size: 0.80rem;
}

/* El pie de la tabla —recuento y paginacion— al 75% del tamano de la tabla
   (0.80 x 0.75 = 0.60rem). Es informacion de apoyo y no debe competir con los
   datos.
   "Ver" y "Buscar" NO bajan tanto: son controles con los que se interactua, y a
   0.60rem el rotulo se vuelve dificil de leer. */
.dt-container .dt-info,
.dt-container .dt-paging {
  font-size: 0.60rem;
}

.dt-container .dt-length,
.dt-container .dt-search {
  font-size: 0.75rem;
}

.dt-container .dt-paging .page-link {
  font-size: 0.60rem;
  padding: 0.2rem 0.5rem;
}

/* Los campos siguen siendo pulsables con comodidad aunque el rotulo encoja.
   OJO: solo se ajusta el relleno VERTICAL. Con la abreviatura `padding` se
   anulaba el de la derecha, y .form-select reserva ahi 2.25rem para dibujar la
   flecha: el valor "250" acababa impreso debajo del icono. */
.dt-container .dt-length select,
.dt-container .dt-search input {
  font-size: 0.75rem;
  padding-top: 0.15rem;
  padding-bottom: 0.15rem;
}

/* Ancho minimo para que el valor mas largo del menu (250) no quede pegado a la
   flecha: con el ancho automatico sobraban menos de 3px. */
.dt-container .dt-length select {
  min-width: 5rem;
}

/* Las tablas anchas se desplazan dentro de su contenedor, nunca la pagina. */
.table-responsive {
  overflow-x: auto;
}

table.dataTable td {
  word-break: break-word;
}

.dt-search input,
.dt-length select {
  font-size: 0.875rem;
}

/* ------------------------------------------------------- modo oscuro basico */


/* ------------------------------------------------------------------ modales */

/**
 * Modal desplazable cuando el formulario envuelve todo el contenido.
 *
 * Bootstrap consigue que .modal-body se desplace y el pie quede fijo con una
 * cadena flex: .modal-content es una columna flex y .modal-body es el hijo que
 * absorbe el sobrante. Si entre medias hay un <form> —como aqui, porque la
 * cabecera, el cuerpo y el pie tienen que ir dentro del mismo formulario— la
 * cadena se corta: el form no es flex, el cuerpo crece sin limite y el pie con
 * el boton de guardar queda fuera de la pantalla, sin forma de llegar a el.
 *
 * Basta con que el <form> herede el mismo comportamiento de columna flex.
 * min-height:0 es imprescindible: sin el, un hijo flex nunca encoge por debajo
 * de su contenido y el desplazamiento no llega a activarse.
 */
.modal-content > form {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
  overflow: hidden;
}

.modal-content > form > .modal-body {
  min-height: 0;
  overflow-y: auto;
}


/* --------------------------------------------------------- ayudas de formulario */

/**
 * Las notas bajo los campos, a la mitad del tamano del texto que las rodea.
 *
 * Se usa un porcentaje y no un valor fijo para que sigan siendo la mitad
 * aunque el contenedor cambie de tamano. La clase .small de Bootstrap se
 * retira del marcado: sumaba su propia reduccion y el resultado dependia de
 * cuantas capas llevara encima.
 */
.form-text {
  font-size: 50%;
  line-height: 1.4;
}

/* "Pendientes de cobro": la columna Compañía (3ª, tras Fecha y Fecha-factura)
   ocupa menos; los nombres largos se recortan con puntos suspensivos (el nombre
   completo queda en el title). */
#tablaPorCobrar th:nth-child(3),
#tablaPorCobrar td:nth-child(3) {
  max-width: 11rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Cola de envio: filas de campaña que cuelgan de una compañia al desplegar el
   [+]. Fondo apenas tintado y un filete a la izquierda para que se lean como
   hijas de la fila de arriba y no como filas sueltas. */
#tablaCola tbody tr.app-fila-campanya > * {
  background-color: rgba(13, 110, 253, 0.035);
}

#tablaCola tbody tr.app-fila-campanya > *:first-child {
  border-left: 3px solid rgba(13, 110, 253, 0.35);
}

/* El modal de confirmacion pinta el mensaje con textContent (nunca HTML, para
   que no pueda inyectarse nada). Con esto los saltos de linea del texto se
   respetan y se pueden escribir avisos de varios parrafos. */
#modalConfirmacionMensaje {
  white-space: pre-line;
}
