/* =============================================================================
 * componentes-presentacion.css · Componentes comunes del panel
 *
 * Lo que la plantilla no trae y la guía de presentación pide en toda pantalla:
 *
 *   1. Estados de un contenedor: cargando, vacío y error (§6.5, §7).
 *   2. Indicadores (KPI) del panel (§6.5).
 *   3. Validación de formularios: marca de obligatorio, resumen superior,
 *      campos de solo lectura (§6.4) y ojo para ver la contraseña.
 *   4. Datos de una ficha: etiqueta y valor, y valor ausente (§6.3).
 *   5. Sugerencias bajo un campo de búsqueda, casillas de fecha y de número.
 *   6. Avisos globales (#avisosGlobales).
 *
 * Reglas del fichero:
 *   - Sin nombres funcionales (§13.1): ningún concepto de la clínica. Cada
 *     pantalla pone sus propios identificadores; aquí solo hay clases.
 *   - Sin colores escritos: solo tokens --at-* del tema y variables de Bootstrap,
 *     así que todo vale igual en claro y en oscuro (docs/tema.md).
 *
 * Las pantallas del panel usan estos componentes; el catálogo
 * está en docs/tema.md.
 * ============================================================================= */

/* -----------------------------------------------------------------------------
 * 1. Estados de un contenedor
 * -------------------------------------------------------------------------- */

/* Cargando dentro de un contenedor: tabla o gráfico. infinite-scroll.js lo muestra y
 * lo oculta; la página entera usa #preloader.
 *   <div id="xLoadingIndicator" class="data loading" role="status">
 *     <span class="spinner-border spinner-border-sm" aria-hidden="true"></span> Cargando…
 *   </div> */
.data.loading {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  padding: 1rem;
  color: var(--at-texto-secundario);
  font-size: 0.875rem;
}

/* Vacío y error. Explican qué pasa y, si existe, el siguiente paso (§8.3). El error
 * nunca se confunde con el vacío: otro icono y otro texto (§13.3).
 *   <div class="estado-contenedor estado-vacio">
 *     <i class="fas fa-inbox" aria-hidden="true"></i>
 *     <p class="estado-titulo">No hay resultados</p>
 *     <p class="estado-detalle">Prueba con otros filtros.</p>
 *   </div> */
.estado-contenedor {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.25rem;
  padding: 2rem 1rem;
  text-align: center;
  color: var(--at-texto);
}
.estado-contenedor > .fas,
.estado-contenedor > .far {
  margin-bottom: 0.5rem;
  font-size: 1.75rem;
  color: var(--at-texto-secundario);
}
.estado-contenedor.estado-error > .fas,
.estado-contenedor.estado-error > .far {
  color: var(--at-error-texto);
}
.estado-titulo {
  margin: 0;
  font-weight: 600;
  color: var(--at-titulo);
}
.estado-detalle {
  margin: 0;
  font-size: 0.875rem;
  color: var(--at-texto-secundario);
}
.estado-contenedor .btn {
  margin-top: 0.75rem;
}

/* -----------------------------------------------------------------------------
 * 2. Indicadores (KPI)
 *
 * Empiezan en «-»: el cero se reserva para un dato real recibido (§6.5).
 *   <section class="card kpi">
 *     <div class="card-body">
 *       <span class="kpi-icono"><i class="fas fa-…" aria-hidden="true"></i></span>
 *       <div>
 *         <p class="kpi-etiqueta">Concepto</p>
 *         <p class="kpi-valor" id="kpiConcepto">-</p>
 *         <p class="kpi-detalle">Periodo</p>
 *       </div>
 *     </div>
 *   </section>
 * -------------------------------------------------------------------------- */

.kpi .card-body {
  display: flex;
  align-items: center;
  gap: 1rem;
}
.kpi-icono {
  display: inline-flex;
  flex: 0 0 auto;
  align-items: center;
  justify-content: center;
  width: 3rem;
  height: 3rem;
  border-radius: 50%;
  background-color: rgba(var(--at-primario-rgb), 0.1);
  color: var(--at-primario-texto);
  font-size: 1.25rem;
}
.kpi-etiqueta {
  margin: 0;
  font-size: 0.875rem;
  font-weight: 500;
  color: var(--at-texto-secundario);
}
/* Cabe un importe de seis cifras en la columna de un cuarto de ancho. */
.kpi-valor {
  margin: 0;
  font-size: 1.5rem;
  font-weight: 700;
  line-height: 1.2;
  color: var(--at-titulo);
  font-variant-numeric: tabular-nums;
}
.kpi-detalle {
  margin: 0;
  font-size: 0.75rem;
  color: var(--at-texto-secundario);
}

/* Cuánto ha crecido o decrecido una cifra: flecha y signo, además del color. Favorable o desfavorable dependen
 * de la magnitud, no del signo: lo decide quien la pinta. */
.variacion {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  padding: 0.125rem 0.375rem;
  border-radius: 4px;
  font-size: 0.75rem;
  font-weight: 600;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.variacion-favorable {
  background-color: var(--at-alerta-exito-fondo);
  color: var(--at-alerta-exito-texto);
}
.variacion-desfavorable {
  background-color: var(--at-alerta-peligro-fondo);
  color: var(--at-alerta-peligro-texto);
}
.variacion-neutra {
  background-color: var(--at-deshabilitado-fondo);
  color: var(--at-texto-secundario);
}

/* Tabla que acompaña a un gráfico, con una columna por día. Las dos primeras no se mueven al desplazarla. */
.tabla-diferencias {
  --tabla-diferencias-nombre: 11rem;
  font-size: 0.8125rem;
  white-space: nowrap;
}
.tabla-diferencias th,
.tabla-diferencias td {
  text-align: center;
  vertical-align: middle;
  font-variant-numeric: tabular-nums;
}
.tabla-diferencias thead th {
  font-weight: 600;
}
.tabla-diferencias tr > :first-child {
  position: sticky;
  left: 0;
  z-index: 1;
  min-width: var(--tabla-diferencias-nombre);
  text-align: left;
  background-color: var(--bs-card-bg, var(--bs-body-bg));
}
.tabla-diferencias tr > :nth-child(2) {
  position: sticky;
  left: var(--tabla-diferencias-nombre);
  z-index: 1;
  background-color: var(--bs-card-bg, var(--bs-body-bg));
}
.tabla-diferencias-detalle {
  display: block;
  margin-top: 0.125rem;
  font-size: 0.6875rem;
  color: var(--at-texto-secundario);
}
@media (max-width: 575.98px) {
  /* En móvil las columnas fijas se comerían la pantalla. */
  .tabla-diferencias tr > :first-child,
  .tabla-diferencias tr > :nth-child(2) {
    position: static;
    min-width: 0;
  }
}

/* -----------------------------------------------------------------------------
 * 3. Formularios
 * -------------------------------------------------------------------------- */

/* Obligatorio: el asterisco lo pone la clase y se explica una sola vez con
 * .nota-obligatorios. El control lleva además `required`, que es lo que anuncia el
 * lector de pantalla; por eso el asterisco no se lee («/ ""»). */
.form-label.obligatorio::after {
  content: " *";
  content: " *" / "";
  color: var(--at-error-texto);
}
.nota-obligatorios {
  font-size: 0.8125rem;
  color: var(--at-texto-secundario);
}

/* Resumen de errores encima del formulario cuando falla más de un campo. Recibe el
 * foco para que se anuncie; cada error enlaza con su campo (formulario.js).
 *   <div class="alert alert-danger resumen-errores d-none" role="alert" tabindex="-1">
 *     <p class="resumen-errores-titulo">Revisa estos campos:</p>
 *     <ul></ul>
 *   </div> */
.resumen-errores:focus {
  outline: 2px solid var(--at-foco);
  outline-offset: 2px;
}
.resumen-errores-titulo {
  margin-bottom: 0.25rem;
  font-weight: 600;
}
.resumen-errores ul {
  margin: 0;
  padding-left: 1.25rem;
}
.resumen-errores a {
  color: inherit;
  text-decoration: underline;
}

/* Solo lectura frente a deshabilitado (§6.4). El de solo lectura se lee y se copia: sin
 * fondo y con borde discontinuo. El deshabilitado, con fondo gris y texto atenuado, y su
 * ayuda explica por qué. La plantilla les pone a los dos fondo blanco. */
.form-control[readonly]:not(.form-control-plaintext) {
  background-color: transparent;
  border-style: dashed;
}
.form-control:disabled,
.form-select:disabled {
  background-color: var(--at-deshabilitado-fondo);
  color: var(--at-texto-secundario);
  cursor: not-allowed;
}
/* En oscuro la plantilla fija el fondo de los campos con !important. */
[data-theme-version=dark] .form-control:disabled {
  background-color: var(--at-deshabilitado-fondo) !important;
}

/* Ojo de una contraseña (mostrar-contrasena.js). Ancho fijo: el ojo tachado es más ancho
 * y el campo no debe moverse al pulsarlo.
 *   <button type="button" class="btn btn-outline-primary boton-contrasena"
 *           data-mostrar-contrasena="password" aria-controls="password" aria-pressed="false"
 *           aria-label="Mostrar contraseña" title="Mostrar contraseña">
 *     <i class="fas fa-eye" aria-hidden="true"></i>
 *   </button> */
.boton-contrasena {
  width: 3.25rem;
  padding-left: 0;
  padding-right: 0;
}

/* -----------------------------------------------------------------------------
 * 4. Gráficos
 *
 * El hueco se reserva desde el primer pintado, para que la página no salte al llegar
 * los datos; el estado (cargando, vacío o error) se pinta dentro del propio hueco. Al
 * cambiar de periodo, el gráfico anterior se queda atenuado hasta que llega el nuevo.
 *   <div class="grafico">
 *     <div class="grafico-lienzo" id="chartConcepto" role="img" aria-label="…"></div>
 *     <p class="grafico-estado" id="chartConceptoEstado">Cargando…</p>
 *   </div>
 * Cada gráfico tiene su tabla con los mismos datos (.grafico-tabla), para leerlos sin
 * ratón ni color.
 * -------------------------------------------------------------------------- */

.grafico {
  position: relative;
}
.grafico-lienzo {
  min-height: 300px;
  border-radius: 4px;
}
.grafico.actualizando .grafico-lienzo {
  opacity: 0.45;
  transition: opacity 0.15s;
}
.grafico-estado {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 0;
  padding: 1rem;
  text-align: center;
  color: var(--at-texto-secundario);
}
.grafico-estado[hidden] {
  display: none;
}
.grafico-estado.estado-error {
  color: var(--at-error-texto);
}
.grafico-subtitulo {
  font-size: 0.875rem;
  color: var(--at-texto-secundario);
}
.grafico-tabla summary {
  font-size: 0.875rem;
  color: var(--at-primario-texto);
  cursor: pointer;
}
.grafico-tabla td,
.grafico-tabla th {
  font-variant-numeric: tabular-nums;
}
@media (max-width: 575.98px) {
  .grafico-lienzo {
    min-height: 250px;
  }
}

/* Textos de ApexCharts: ejes, leyenda y tooltip, con la tipografía y los colores del
 * tema en claro y en oscuro (graficos.js pone el resto de opciones). */
.apexcharts-canvas,
.apexcharts-legend-text,
.apexcharts-tooltip,
.apexcharts-xaxistooltip {
  font-family: var(--at-fuente-texto) !important;
}
.apexcharts-legend-text {
  color: var(--at-texto) !important;
}
.apexcharts-tooltip.apexcharts-theme-light {
  border-color: var(--bs-border-color) !important;
}

/* -----------------------------------------------------------------------------
 * 5. Ficha: etiqueta y valor
 *
 *   <dl class="datos-ficha">
 *     <dt>Etiqueta</dt><dd>Valor</dd>
 *     <dt>Otra</dt><dd><span class="valor-ausente">—</span></dd>
 *   </dl>
 * Dos columnas en escritorio y una en móvil, sin cambiar el orden de lectura.
 * -------------------------------------------------------------------------- */

.datos-ficha {
  display: grid;
  grid-template-columns: minmax(8rem, 14rem) 1fr;
  gap: 0.5rem 1.5rem;
  margin: 0;
}
.datos-ficha dt {
  font-weight: 500;
  color: var(--at-titulo);
}
.datos-ficha dd {
  margin: 0;
  overflow-wrap: anywhere;
}
/* Un campo extenso ocupa todo el ancho. */
.datos-ficha .dato-extenso {
  grid-column: 1 / -1;
}
@media (max-width: 575.98px) {
  .datos-ficha {
    grid-template-columns: 1fr;
    gap: 0.125rem;
  }
  .datos-ficha dd {
    margin-bottom: 0.5rem;
  }
}

/* Valor que legítimamente no existe: guion largo atenuado (§6.3). */
.valor-ausente {
  color: var(--at-texto-secundario);
}

/* Un texto largo escrito por alguien (un comentario, unas notas): respeta sus saltos de línea. */
.texto-con-saltos {
  white-space: pre-line;
}

/* Muestra de un color que elige la clínica en la configuración: el color llega como dato y se pone con
   background-color; aquí solo su forma. */
.muestra-de-color {
  display: inline-block;
  width: 1em;
  height: 1em;
  border: 1px solid var(--bs-border-color);
  border-radius: 0.2em;
  vertical-align: -0.125em;
}

/* La plantilla da a .card la altura de su fila: aquí, solo la de su contenido. */
.capa-bloqueo-tarjeta {
  width: 100%;
  max-width: 26rem;
  height: auto;
  margin: 0;
}
.marca-bloqueo {
  height: 2.5rem;
  width: auto;
  max-width: 100%;
}

/* La plantilla quita las viñetas a todos los li. */
.editor-texto-area ul > li {
  list-style: disc;
}
.editor-texto-area ol > li {
  list-style: decimal;
}
.editor-texto-area > :first-child {
  margin-top: 0;
}

/* Sugerencias bajo un campo de búsqueda: un .dropdown-menu de Bootstrap que abre y cierra el script
   de la búsqueda. Ancho para que quepa cada resultado y scroll si son muchos. */
.lista-sugerencias {
  min-width: 22rem;
  max-width: calc(100vw - 2rem);
  max-height: 70vh;
  overflow-y: auto;
}
.lista-sugerencias .dropdown-item {
  white-space: normal;
}
.lista-sugerencias .sugerencia-detalle {
  font-size: 0.8125rem;
  opacity: 0.8;
}

/* -----------------------------------------------------------------------------
 * Fecha que se teclea: la plantilla estira el icono del calendario sobre toda la casilla, así que cualquier clic abre
 * el calendario. Con .fecha-tecleable el icono vuelve a su sitio, a la derecha: al pulsar en el texto se escriben el
 * día, el mes y el año, y el calendario solo se abre desde el icono. Para fechas lejanas, como una de nacimiento.
 * -------------------------------------------------------------------------- */
input[type=date].fecha-tecleable::-webkit-calendar-picker-indicator {
  position: static;
  flex: none;
  width: 1.125rem;
  height: 1.125rem;
  padding: 0;
  margin-left: 0.5rem;
  background: var(--at-texto-secundario);
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M7 2v2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2h-2V2h-2v2H9V2H7zM5 9h14v11H5V9z'/%3E%3C/svg%3E") center / contain no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M7 2v2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2h-2V2h-2v2H9V2H7zM5 9h14v11H5V9z'/%3E%3C/svg%3E") center / contain no-repeat;
  cursor: pointer;
}
input[type=date].fecha-tecleable::-webkit-calendar-picker-indicator:hover {
  background: var(--at-primario);
}

/* -----------------------------------------------------------------------------
 * Casillas numéricas sin las flechas de subir y bajar: un importe, una cantidad o unos minutos se escriben. La
 * plantilla ya las quita en Firefox (appearance: textfield); aquí, en Chrome, Edge y Safari.
 * -------------------------------------------------------------------------- */
input[type=number]::-webkit-inner-spin-button,
input[type=number]::-webkit-outer-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

/* -----------------------------------------------------------------------------
 * Avisos globales (#avisosGlobales, el contenedor de ajax-error-handler.js): flotan arriba a la derecha, bajo la
 * cabecera fija, para que se vean aunque la página se haya desplazado. Por encima de los modales (1055) y por debajo
 * de la capa de bloqueo (3000). En móvil ocupan el ancho, con 1 rem a cada lado.
 * -------------------------------------------------------------------------- */
#avisosGlobales {
  position: fixed;
  top: 8.5rem;
  right: 1.5rem;
  z-index: 2000;
  width: 30rem;
  max-width: calc(100vw - 2rem);
}
#avisosGlobales > .alert {
  box-shadow: var(--bs-box-shadow);
}
@media only screen and (max-width: 1199px) {
  #avisosGlobales {
    top: 6rem;
  }
}
@media only screen and (max-width: 575px) {
  #avisosGlobales {
    left: 1rem;
    right: 1rem;
    width: auto;
    max-width: none;
  }
}

/* -----------------------------------------------------------------------------
 * Membresías (RN-71 a RN-74). El estado de una membresía y la marca de un miembro llevan icono y texto, además del
 * color: como .estado-email, el icono lo pone la clase y el texto, etiquetas.js. La marca va en las citas de los
 * miembros (calendario, pendientes, ficha de la cita) y avisa si la cita pasa del cupo del mes.
 * -------------------------------------------------------------------------- */
.estado-membresia,
.marca-miembro {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  padding: 0.125rem 0.5rem;
  border-radius: 0.375rem;
  font-size: 0.75rem;
  font-weight: 600;
  line-height: 1.4;
  white-space: nowrap;
}
.estado-membresia::before,
.marca-miembro::before {
  font-family: "Font Awesome 6 Free";
  font-weight: 900;
  line-height: 1;
}
.estado-membresia-vigente {
  background-color: var(--at-alerta-exito-fondo);
  color: var(--at-alerta-exito-texto);
}
.estado-membresia-vigente::before {
  content: "\f058";
  content: "\f058" / "";
}
.estado-membresia-programada {
  background-color: var(--at-alerta-info-fondo);
  color: var(--at-alerta-info-texto);
}
.estado-membresia-programada::before {
  content: "\f017";
  content: "\f017" / "";
}
.estado-membresia-terminada {
  background-color: var(--at-cita-cancelada-fondo);
  color: var(--at-cita-cancelada-texto);
}
.estado-membresia-terminada::before {
  content: "\f05e";
  content: "\f05e" / "";
}
.marca-miembro {
  background-color: var(--at-alerta-info-fondo);
  color: var(--at-alerta-info-texto);
}
.marca-miembro::before {
  content: "\f2c2";
  content: "\f2c2" / "";
}
.marca-miembro--fuera-de-cupo {
  background-color: var(--at-alerta-aviso-fondo);
  color: var(--at-alerta-aviso-texto);
}
.marca-miembro--fuera-de-cupo::before {
  content: "\f071";
  content: "\f071" / "";
}
