/* ===========================================================================
   public/css/components.css
   Badges, tarjetas, grilla de stats, toasts, estado vacio, spinner, tabs, paginacion y barra de filtros.

   Parte 7 de 13 de la hoja de estilos. El orden de carga IMPORTA:
   se enlazan en este mismo orden desde index.html y dashboard.html.
   =========================================================================== */
/* ===== 12. BADGES / STATUS PILLS ===== */
.badge {
  display: inline-flex;
  align-items: center;
  padding: .2rem .6rem;
  border-radius: 100px;
  font-size: .72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .05em;
  white-space: nowrap;
}

.badge-borrador         { background: var(--badge-bg-gray);  color: var(--clr-gray-soft); }
.badge-pendiente        { background: var(--badge-bg-amber);   color: var(--clr-amber-soft); }
.badge-en-revision      { background: var(--badge-bg-orange);   color: var(--clr-orange-soft); }
.badge-aprobada         { background: var(--badge-bg-green);   color: var(--clr-green-soft); }
.badge-enviada          { background: var(--badge-bg-blue);   color: var(--clr-blue-soft); }
/* CONFIRMADA PASA DE VIOLETA A VERDE, Y ES EL UNICO CAMBIO DE COLOR DE ESTADO
   La paleta usaba SEIS matices para los estados (ambar, naranja, azul, violeta,
   verde, rojo). Ese arcoiris es lo que mas queda de estetica generica, y el
   violeta era el mas arbitrario de todos: no significaba nada.

   'Confirmada' es la venta cerrada — exactamente lo que el verde ya significa
   en 'Aprobada'. Darle su propio matiz obligaba a aprender un color de mas para
   decir algo que la paleta ya sabia decir.

   Se eligio ESTE cambio y no reducir los seis a tres porque es el unico que no
   pisa un significado que la gente ya tenga incorporado: nadie asocia el
   violeta con nada en particular, mientras que ambar-pendiente y rojo-rechazada
   estan aprendidos despues de meses de uso.

   Se distinguen de 'Aprobada' por el peso: la confirmada usa el verde pleno,
   la aprobada el tono suave. Misma familia, distinta intensidad — que es lo
   que son: aprobar es un paso hacia cerrar. */
/* El estado 'Aceptada' se renombró a 'Confirmada'; las filas viejas que
   todavía dicen 'Aceptada' se pintan con esta misma clase, porque el mapa
   STATE_BADGE de helpers.js las manda acá. Por eso no hace falta una
   `.badge-aceptada` aparte — existía, era idéntica a esta, y no la usaba
   nadie. Se borró en la auditoría del 2026-08-28. */
.badge-confirmada       { background: var(--badge-bg-green);    color: var(--clr-green); }
.badge-rechazada        { background: var(--badge-bg-red);    color: var(--clr-red-soft); }
.badge-archivada        { background: var(--badge-bg-slate);  color: var(--text-secondary); }

.badge-role-jefe        { background: var(--badge-bg-green);   color: var(--clr-green); }
.badge-role-ejecutivo   { background: var(--badge-bg-blue);   color: var(--clr-blue);  }
.badge-role-admin       { background: var(--badge-bg-amber);   color: var(--clr-amber); }
.badge-role-sysadmin    { background: var(--badge-bg-violet);   color: var(--clr-violet-soft); }
.badge-role-proyectos   { background: var(--badge-bg-teal);   color: var(--clr-teal-soft); }
.badge-active           { background: var(--badge-bg-green);   color: var(--clr-green); }
.badge-inactive         { background: var(--badge-bg-red);    color: var(--clr-red);   }

/* Licitación lifecycle state badges */
.badge-lic-preparacion  { background: var(--badge-bg-gray);  color: var(--clr-slate-soft); }
.badge-lic-cotizando    { background: var(--badge-bg-amber);   color: var(--clr-amber-soft); }
.badge-lic-evaluacion   { background: var(--badge-bg-indigo);   color: var(--clr-indigo-soft); }
.badge-lic-presentada   { background: var(--badge-bg-blue);   color: var(--clr-blue-soft); }
.badge-lic-adjudicada   { background: var(--badge-bg-green);   color: var(--clr-green-soft); }
.badge-lic-noadjudicada { background: var(--badge-bg-red);    color: var(--clr-red-soft); }
.badge-lic-archivada    { background: var(--badge-bg-slate);  color: var(--text-secondary); }

/* Seguimiento comercial (estado_venta) — pipeline de venta con el cliente,
   independiente del estado de aprobación interno de arriba. */
.badge-seg-interesado   { background: var(--badge-bg-blue);   color: var(--clr-blue-soft); }
.badge-seg-negociacion  { background: var(--badge-bg-amber);  color: var(--clr-amber-soft); }
.badge-seg-confirmado   { background: var(--badge-bg-indigo); color: var(--clr-indigo-soft); }
.badge-seg-no-interesa  { background: var(--badge-bg-red);    color: var(--clr-red-soft); }
.badge-seg-concretada   { background: var(--badge-bg-green);  color: var(--clr-green); }
.badge-seg-otro         { background: var(--badge-bg-gray);   color: var(--clr-gray-soft); }

/* El detalle libre de "Otro" (lo que escribe el vendedor a mano) no tiene
   límite de largo real — sin esto, una frase larga ensanchaba TODA la
   columna "Seguim." de la tabla (no sólo esa fila: una celda ancha estira
   la columna entera en table-layout:auto), y filas de al lado quedaban con
   muchísimo espacio vacío para no perder la alineación.
   inline-block (no el inline-flex de .badge) porque text-overflow:ellipsis
   necesita un box de línea, no un contenedor flex, para recortar de forma
   consistente entre navegadores. El texto completo sigue disponible con el
   title= que arma seguimientoVentaBadgeHtml() — se ve al pasar el mouse. */
.badge-truncado {
  display: inline-block;
  max-width: 160px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  vertical-align: bottom;
}

/* Audit log — acción category badges (semantic color by action family) */
.badge-audit-auth       { background: var(--badge-bg-violet);   color: var(--clr-violet-soft); }  /* LOGIN/LOGOUT — violet */
.badge-audit-auth-fail  { background: var(--badge-bg-red);    color: var(--clr-red-soft); }  /* LOGIN_FAILED — red */
.badge-audit-create     { background: var(--badge-bg-blue);   color: var(--clr-blue-soft); }  /* CREAR_* — blue */
.badge-audit-edit       { background: var(--badge-bg-amber);   color: var(--clr-amber-soft); }  /* EDITAR_*, CAMBIAR_ESTADO — amber */
.badge-audit-approve    { background: var(--badge-bg-green);   color: var(--clr-green-soft); }  /* APROBAR — green */
.badge-audit-reject     { background: var(--badge-bg-red);    color: var(--clr-red-soft); }  /* RECHAZAR, DESACTIVAR_USUARIO — red */
.badge-audit-file       { background: var(--badge-bg-teal);   color: var(--clr-teal-soft); }  /* SUBIR_PDF/DESCARGAR_PDF — teal */

/* Audit log — resultado badges */
.badge-resultado-exito  { background: var(--badge-bg-green);   color: var(--clr-green); }
.badge-resultado-fallo  { background: var(--badge-bg-red);    color: var(--clr-red);   }

/* ===== 26. EN ESPERA BADGE ===== */
.badge-en-espera {
  background: var(--badge-bg-indigo);
  color: var(--clr-indigo-soft);
}

/* ===== 13. CARDS ===== */
.card {
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  overflow: hidden;
  box-shadow: var(--shadow-sm);
  transition: var(--transition-smooth);
}

.card:hover {
  box-shadow: var(--shadow-md);
  border-color: var(--bg-hover);
}

.card-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 1rem 1.25rem;
  border-bottom: 1px solid var(--border);
}

/* ── El titulo de una tarjeta ────────────────────────────────────────────────
   El filete naranja reemplaza al emoji que encabezaba cada seccion (habia 129
   en la interfaz, y un emoji en el titulo es el tic visual mas reconocible del
   software generado).

   No es un adorno elegido al azar: es LA MISMA linea que el PDF ya dibuja
   antes de cada seccion — ver sectionTitle() en reportePdfService.js, que hace
   doc.rect(MARGIN, y, 3.5, 12).fill(C.ORANGE). Traerla a la pantalla es lo que
   hace que la aplicacion y el documento que recibe el cliente se lean como la
   misma empresa.

   Va con ::before sobre el selector que ya existia, asi que ninguna pantalla
   tuvo que agregar una clase: alcanzo con borrar el emoji del texto. */
.card-header h3, .card-header h4 {
  font-size: .95rem;
  font-weight: 600;
  display: flex;
  align-items: center;
  gap: .55rem;
  min-width: 0;              /* deja que el texto se recorte antes de desbordar */
}

.card-header h3::before, .card-header h4::before {
  content: '';
  flex: none;
  width: 3px;
  height: 1em;
  border-radius: 1px;
  background: var(--clr-orange);
}

.card-body { padding: 1.25rem; }

/* Acá vivía `.card-footer`. La reemplazó `.card-toolbar` —la misma barra pero
   arriba de la tabla, con el borde abajo— y quedó sin usar en ningún HTML ni
   JS. Se borró en la auditoría del 2026-08-28; el porqué de la barra arriba
   está explicado en el comentario de `.card-toolbar`. */

/* ===== 14. STATS GRID ===== */
.stats-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 1rem;
  margin-bottom: 1.5rem;
}

.stat-card {
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: 1.1rem 1.25rem;
  position: relative;
  overflow: hidden;
  box-shadow: var(--shadow-sm);
  transition: var(--transition-smooth);
}

.stat-card:hover {
  transform: translateY(-3px);
  box-shadow: var(--shadow-hover);
}

.stat-card::before {
  content: '';
  position: absolute;
  top: 0; left: 0;
  width: 4px; height: 100%;
  background: var(--stat-accent, var(--clr-blue));
}

.stat-card-value {
  font-size: 1.75rem;
  font-weight: 700;
  line-height: 1;
  color: var(--text-primary);
}

.stat-card-label {
  font-size: .75rem;
  font-weight: 600;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: .07em;
  margin-top: .35rem;
}

/* Acentos de tarjeta reutilizables: evitan un style="--stat-accent:...;" por
   cada tarjeta nueva. Las tarjetas ya existentes lo siguen fijando inline
   (no se tocaron), pero cualquier tarjeta nueva puede usar una clase. */
.stat-accent-blue   { --stat-accent: var(--clr-blue); }
.stat-accent-amber  { --stat-accent: var(--clr-amber); }
.stat-accent-green  { --stat-accent: var(--clr-green); }
.stat-accent-red    { --stat-accent: var(--clr-red); }
.stat-accent-violet { --stat-accent: var(--clr-violet); }
.stat-accent-gray   { --stat-accent: var(--clr-gray); }

/* ===== 17. TOAST NOTIFICATIONS ===== */
.toast-stack {
  position: fixed;
  bottom: 1.5rem;
  right: 1.5rem;
  z-index: 300;
  display: flex;
  flex-direction: column;
  gap: .5rem;
  max-width: 340px;
}

.toast {
  background: var(--bg-raised);
  border: 1px solid var(--border);
  border-left: 4px solid var(--clr-blue);
  border-radius: var(--radius-sm);
  padding: .75rem 1rem;
  font-size: .875rem;
  box-shadow: var(--shadow-md);
  transform: translateX(120%);
  opacity: 0;
  transition: transform .25s ease, opacity .25s ease;
}

.toast.toast-visible  { transform: translateX(0); opacity: 1; }
.toast.toast-success  { border-left-color: var(--clr-green); }
.toast.toast-error    { border-left-color: var(--clr-red);   }
.toast.toast-warning  { border-left-color: var(--clr-amber); }

/* ===== 21. EMPTY STATE ===== */
.empty-state {
  text-align: center;
  padding: 3rem 1.5rem;
  color: var(--text-muted);
}

/* Tipo de archivo, al lado del nombre en una lista de adjuntos. Se alinea con
   la línea de texto y hereda su color, como el resto de los íconos de trazo. */
.file-icon {
  width: 17px; height: 17px;
  vertical-align: -3px;
  color: var(--text-muted);
  flex-shrink: 0;
}

/* El ícono es de trazo y hereda el color del texto, así que se apaga con el
   resto del estado vacío. Antes era un emoji a font-size:2.5rem — a todo color
   sobre un panel gris, y dibujado distinto en cada sistema operativo. */
.empty-state-icon { margin-bottom: .85rem; color: var(--text-muted); opacity: .55; }
.empty-state-icon .empty-icon { width: 40px; height: 40px; }
.empty-state h4   { font-size: .95rem; color: var(--text-secondary); margin-bottom: .35rem; }
.empty-state p    { font-size: .82rem; }

/* ===== 22. LOADING / SPINNER ===== */
.spinner {
  display: inline-block;
  width: 1em; height: 1em;
  border: 2px solid currentColor;
  border-top-color: transparent;
  border-radius: 50%;
  animation: spin .7s linear infinite;
  vertical-align: middle;
}

.spinner-lg {
  width: 2.5rem; height: 2.5rem;
  border-width: 3px;
}

.page-loading {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1rem;
  padding: 4rem;
  color: var(--text-muted);
  font-size: .875rem;
}

@keyframes spin { to { transform: rotate(360deg); } }

/* ===== 23. TABS ===== */
.tab-bar {
  display: flex;
  border-bottom: 1px solid var(--border);
  margin-bottom: 1.5rem;
  gap: .1rem;
  overflow-x: auto;
}

.tab-btn {
  background: none;
  border: none;
  padding: .65rem 1.1rem;
  font-size: .875rem;
  font-weight: 600;
  color: var(--text-muted);
  cursor: pointer;
  border-bottom: 2px solid transparent;
  transition: color var(--transition), border-color var(--transition);
  white-space: nowrap;
  font-family: inherit;
}

.tab-btn:hover  { color: var(--text-secondary); }
.tab-btn.active { color: var(--clr-green); border-bottom-color: var(--clr-green); }

/* Acá vivían `.tab-panel` y `.tab-panel.active`. Los paneles del dashboard no
   se muestran ni se esconden con una clase: cada strategy limpia el contenedor
   y escribe el panel activo con innerHTML, así que las dos reglas nunca se
   aplicaban a nada. Se borraron en la auditoría del 2026-08-28. */

/* ===== 24. PAGINATION ===== */
/* La regla .pagination está más abajo, junto al control estilo Gmail que la
   define de verdad. Acá había una PRIMERA declaración —la paginación centrada
   original— que la de abajo pisaba desde que se rediseñó el control: de sus
   seis propiedades sólo sobrevivían tres. Se fusionaron en una sola para que
   lo que se lee sea lo que se aplica. Auditoría del 2026-08-28. */

.pagination-info { color: var(--text-muted); font-size: .8rem; }

/* Barra de herramientas ARRIBA de la tabla. Es card-footer dado vuelta: el
   borde va abajo en vez de arriba, porque separa del contenido que sigue.

   La paginacion vive aca y no al pie: con 50 filas en pantalla, cambiar de
   pagina u ordenar obligaba a recorrer toda la tabla hacia abajo, hacer el
   clic, y volver a subir para leer el resultado. Arriba queda a la vista junto
   con los filtros, que es donde uno ya esta mirando cuando decide moverse por
   la lista. Es la misma razon por la que Gmail lo tiene arriba. */
.card-toolbar {
  padding: .75rem 1.25rem;
  border-bottom: 1px solid var(--border);
  background: rgba(255,255,255,.02);
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: .5rem;
}

/* Vacia (sin resultados) la barra no debe dejar una franja fantasma. */
.card-toolbar:empty { display: none; }

/* ── Control estilo Gmail: «1–50 de 6.058» con salto al principio y al final ──
   El rango va al centro y las flechas a la derecha, como en cualquier bandeja
   de correo: es una disposición que la gente ya tiene aprendida, y copiarla
   ahorra tener que explicarla. */
.pagination {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-4);
  padding: var(--space-3) var(--space-5);
  flex-wrap: wrap;
  font-size: .875rem;
}

.pagination-size {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin: 0;
  margin-right: auto;   /* empuja el resto del control hacia la derecha */
}

.pagination-size-label {
  color: var(--text-muted);
  font-size: var(--text-xs);
  white-space: nowrap;
}

.pagination-size-select {
  width: auto;
  min-width: 4.5rem;
  padding: .25rem 1.5rem .25rem .5rem;
  font-size: var(--text-sm);
}

.pagination-range { position: relative; }

.pagination-range-btn {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: .35rem .6rem;
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  color: var(--text-secondary);
  font-size: var(--text-sm);
  font-family: inherit;
  cursor: pointer;
  white-space: nowrap;
  transition: background-color var(--transition), border-color var(--transition);
}

.pagination-range-btn:hover:not(:disabled) {
  background: var(--bg-hover);
  border-color: var(--border);
}

.pagination-range-btn:disabled {
  cursor: default;
  color: var(--text-muted);
}

/* El caret desaparece cuando no hay a dónde saltar: un menú desplegable que se
   abre vacío es peor que no ofrecerlo. */
.pagination-range-btn:disabled .pagination-caret { visibility: hidden; }

.pagination-caret { font-size: .7em; opacity: .7; }

.pagination-menu {
  position: absolute;
  top: calc(100% + 4px);
  right: 0;
  z-index: 20;
  min-width: 11rem;
  padding: var(--space-1);
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
}

.pagination-menu[hidden] { display: none; }

.pagination-menu button {
  display: block;
  width: 100%;
  padding: .5rem .75rem;
  background: none;
  border: none;
  border-radius: var(--radius-sm);
  color: var(--text-primary);
  font-family: inherit;
  font-size: var(--text-sm);
  text-align: left;
  cursor: pointer;
}

.pagination-menu button:hover:not(:disabled) { background: var(--bg-hover); }

/* Deshabilitado, no escondido: que la opción siga a la vista le dice al usuario
   que ya está en ese extremo de la lista. Si desapareciera, el menú cambiaría
   de tamaño entre páginas y costaría entender por qué. */
.pagination-menu button:disabled {
  color: var(--text-muted);
  cursor: default;
}

.pagination-arrows { display: flex; gap: var(--space-1); }

.pagination-arrows .btn {
  min-width: 2rem;
  font-size: 1.1rem;
  line-height: 1;
}

@media (max-width: 600px) {
  .pagination { justify-content: center; }
  .pagination-size { margin-right: 0; }
}

/* ===== 25. FILTER BAR ===== */
.filter-bar {
  display: flex;
  gap: .65rem;
  flex-wrap: wrap;
  align-items: flex-end;
  padding: .75rem 1.25rem;
  background: var(--bg-raised);
  border-bottom: 1px solid var(--border);
}

.filter-bar .form-group { margin-bottom: 0; }
.filter-bar .form-label { font-size: .68rem; }
.filter-bar .form-control { font-size: .82rem; padding: .4rem .6rem; }

/* ===== CAMPANA DE NOTIFICACIONES (barra superior) =====
   Estaba embebido con style="" en dashboard.html. La visibilidad se controla
   con la clase .hidden (base.css), NO tocando element.style desde el JS:
   mezclar ambos hace que el estado dependa de quién escribió último. */

.notif-btn {
  position: relative;
  display: inline-flex;
}

.notif-badge {
  position: absolute;
  top: 2px;
  right: 2px;
  background: var(--clr-red);
  color: var(--clr-white);
  border-radius: 50%;
  font-size: .65rem;
  min-width: 16px;
  height: 16px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  line-height: 1;
  padding: 0 3px;
}

/* ===== SELECTOR DE TEMA (barra superior) =====
   El botón rota automático → claro → oscuro; el icono lo escribe theme.js. */

.theme-toggle {
  font-size: 1rem;
  line-height: 1;
  padding: .35rem .55rem;
}

/* Transición al cambiar de tema. Se aplica sólo a las propiedades de color:
   incluir `all` haría que cada layout shift de la app se anime también.
   No se toca el texto para que la lectura no "flashee" durante el cambio. */
@media (prefers-reduced-motion: no-preference) {
  body,
  .card, .sidebar, .topbar, .modal-dialog, .data-table,
  .form-control, .btn {
    transition: background-color .25s ease, border-color .25s ease, color .25s ease;
  }
}

/* ===== 25. ESQUELETOS DE CARGA =====
   Reemplazan al spinner en los paneles que listan datos. La idea no es
   entretener durante la espera sino RESERVAR EL ESPACIO: cuando llega la
   respuesta, la tabla ocupa el lugar que el esqueleto ya tenía y la página no
   pega el salto que daba al desaparecer un spinner centrado.

   El brillo se hace con una capa translúcida ENCIMA del color base
   (background-image sobre background-color) en vez de un degradado opaco: así
   el mismo gradiente sirve en tema claro y oscuro, y sólo cambian los dos
   tokens. La animación se apaga sola con prefers-reduced-motion — la regla
   global de base.css la reduce a .01ms, y un esqueleto quieto sigue
   comunicando perfectamente que algo está cargando. */

.skeleton {
  padding: var(--space-2) 0;
}

.skeleton-table {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.skeleton-row {
  display: flex;
  gap: var(--space-4);
  align-items: center;
  padding: var(--space-3) var(--space-4);
  border-bottom: 1px solid var(--border);
}

/* La fila de cabecera imita el <thead>: barras más cortas y algo más marcadas. */
.skeleton-row-head {
  border-bottom-width: 2px;
}

.skeleton-row-head .skeleton-bar {
  width: 45%;
  opacity: .75;
}

.skeleton-cell {
  flex: 1;
  min-width: 0;
}

.skeleton-bar {
  display: block;
  height: .8rem;
  width: 70%;
  border-radius: var(--radius-sm);
  background-color: var(--skeleton-base);
  background-image: linear-gradient(
    90deg,
    transparent 0%,
    var(--skeleton-shine) 50%,
    transparent 100%
  );
  background-size: 200% 100%;
  background-repeat: no-repeat;
  animation: skeleton-shine 1.4s ease-in-out infinite;
}

@keyframes skeleton-shine {
  from { background-position: 150% 0; }
  to   { background-position: -50% 0; }
}

/* Variante en tarjetas, para las grillas de indicadores del dashboard. */
.skeleton-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: var(--space-4);
}

.skeleton-card {
  padding: var(--space-5);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--bg-surface);
}


/* ── Pie de acciones de un modal ──────────────────────────────────────────────
   Cancelar a la izquierda, la accion principal a la derecha. Estaba escrito
   inline doce veces, con dos margenes distintos (1rem y 1.25rem) por puro
   descuido de copiar y pegar. Se unifica en el valor de la escala: la
   diferencia son 4 px que nadie va a extranar, y es exactamente la clase de
   inconsistencia que la escala de espaciado existe para eliminar. */
.modal-actions {
  display: flex;
  justify-content: flex-end;
  gap: var(--space-2);
  margin-top: var(--space-4);
}

/* ═══════════════════════════════════════════════════════════════════════════
   PANEL DE PROFORMAS DEL DÍA (executiveStrategy.js)

   Una tarjeta con filete azul a la izquierda que el ejecutivo ve primero al
   entrar. Estaba escrita dos veces con el mismo style —una para el caso con
   datos y otra para el vacío— y las dos repetían el color, el borde y el
   margen. Un solo dueño: la clase.
   ═══════════════════════════════════════════════════════════════════════════ */
.panel-del-dia {
  border-left: 4px solid var(--clr-blue);
  margin-bottom: 1rem;
}

.panel-del-dia .card-header { padding-bottom: .5rem; }
.panel-del-dia .card-header h4 { margin: 0; color: var(--clr-blue); }

/* El texto de «no hay nada hoy». Lleva el padding de la tarjeta porque va
   suelto, sin la caja que envuelve a la tabla. */
.panel-del-dia-vacio { padding: .5rem 1rem 1rem; }

/* Insignia de conteo: cuántas van hoy. A diferencia de las de estado, no
   codifica una situación — es un número —, así que va en el azul de la
   sección y no en un color de la máquina de estados. */
.badge-conteo {
  background: var(--clr-blue);
  color: var(--clr-white);
}

/* ═══════════════════════════════════════════════════════════════════════════
   LO QUE QUEDABA COMO ESTILO INLINE EN LOS PANELES

   Antes de escribir cada regla revisé si la clase ya existía. Varias sí:
   .filter-bar ya hacía flex/gap/wrap/align-end, .truncate ya recortaba con
   puntos suspensivos, .sub-modal ya traía max-width:560px y .item-input ya
   daba el ancho de los campos de la tabla de ítems. Estaban siendo
   reimplementadas inline encima de sí mismas.
   ═══════════════════════════════════════════════════════════════════════════ */

/* Mensaje de error de un formulario. Reserva su alto SIEMPRE, aunque esté
   vacío: sin el min-height, al aparecer el error empuja todo lo de abajo y el
   botón que la persona estaba por apretar se corre de lugar. */
.form-error {
  color: var(--clr-red);
  min-height: 1.2em;
  font-size: .8rem;
}

/* Subtítulo dentro de un modal de detalle: separa bloques (Gastos, Documentos,
   Historial) sin la jerarquía de un encabezado de tarjeta. */
.sub-seccion-title { margin: 1rem 0 .35rem; }

/* Lista sin viñetas ni sangría — la base de toda lista de documentos, gastos
   o eventos. Se repetía como style="list-style:none;padding:0;margin:0". */
.lista-limpia { list-style: none; padding: 0; margin: 0; }

/* Un renglón de esas listas: ícono, nombre que se recorta, acciones. */
.lista-item {
  display: flex;
  align-items: center;
  gap: .5rem;
  padding: .4rem 0;
  border-bottom: 1px solid var(--border);
}

/* El nombre, que es lo único que puede crecer sin límite. */
.lista-item-nombre {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Fila de acciones al pie de un bloque. */
.acciones-fila {
  display: flex;
  flex-wrap: wrap;
  gap: .5rem;
  margin-top: .5rem;
}

.acciones-centro { display: flex; justify-content: center; gap: .75rem; }

/* Detalle desplegable de la bitácora: pares dato/valor en dos columnas. */
.audit-dl {
  margin: 0;
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: .4rem 1rem;
}
.audit-dl dt {
  color: var(--text-secondary);
  font-size: .78rem;
  font-weight: 600;
  white-space: nowrap;
}
.audit-dl dd { margin: 0; font-size: .82rem; word-break: break-word; }

.audit-detail-cell { background: var(--bg-raised); padding: .85rem 1rem; }

/* La dirección IP va en monoespaciada: se compara de un vistazo entre filas,
   y con tipografía proporcional los dígitos no quedan alineados. */
.mono { font-family: ui-monospace, 'Cascadia Code', Consolas, monospace; }

/* Encabezado de columna que ordena la tabla al hacer clic. */
.th-ordenable {
  cursor: pointer;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
}

/* ── BARRA COMPARATIVA (misMetricas) ────────────────────────────────────────
   Emitidas contra cerradas, mes a mes. Dos barras superpuestas: la de atrás
   es el total emitido y la de adelante, en verde, lo que se cerró. La lectura
   es la proporción, no el número. */
.barra-mes {
  display: flex;
  align-items: center;
  gap: .75rem;
}
.barra-mes-etiqueta { min-width: 5rem; }
.barra-mes-cifra    { min-width: 8.5rem; text-align: right; }

.barra-pista {
  flex: 1;
  height: 1.15rem;
  background: var(--bg-hover);
  border-radius: var(--radius-sm);
  overflow: hidden;
  position: relative;
}
.barra-emitidas { height: 100%; background: var(--badge-bg-blue); }
.barra-cerradas { height: 100%; background: var(--clr-green); position: absolute; top: 0; left: 0; }

.panel-barras {
  padding: 1rem 1.25rem;
  display: flex;
  flex-direction: column;
  gap: .6rem;
}

/* ── LÍNEA DE TIEMPO (timelineView) ─────────────────────────────────────── */
.timeline { list-style: none; padding: 0; margin: 0; position: relative; }

.timeline-item {
  display: flex;
  gap: .75rem;
  margin-bottom: .75rem;
  align-items: flex-start;
}

.timeline-punto {
  flex-shrink: 0;
  width: 10px; height: 10px;
  border-radius: 50%;
  margin-top: 4px;
  background: var(--clr-indigo);
}
/* El evento más reciente, arriba de todo. */
.timeline-punto-actual { background: var(--clr-blue); }

.timeline-texto { font-size: .85rem; line-height: 1.4; }

.timeline-seccion {
  margin-top: 1.5rem;
  padding-top: 1rem;
  border-top: 1px solid var(--border);
}
.timeline-seccion h4 {
  margin-bottom: .75rem;
  font-size: .95rem;
  color: var(--text-secondary);
}

/* Anchos de filtro que faltaban en la escala. */
.fc-xnarrow { min-width: 120px; }

/* Renglón compacto de una lista de archivos elegidos, antes de subirlos. */
.lista-item-compacto {
  display: flex;
  align-items: center;
  gap: .4rem;
  padding: .15rem 0;
  font-size: .82rem;
}

/* Pares dato/valor de la cabecera de un detalle. */
.datos-dl {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: .25rem .75rem;
  margin: .75rem 0;
}

/* Texto de ayuda al pie del encabezado de un panel. */
.panel-ayuda {
  margin: 0;
  padding: .6rem 1.25rem;
  color: var(--text-muted);
  border-bottom: 1px solid var(--border);
}

/* La cifra de resultado (ganancia o pérdida) de una licitación. */
.resultado-cifra { font-size: 1.02rem; }

/* Detalle al pie de una tarjeta de indicador. */
.stat-detalle { margin-top: .35rem; line-height: 1.35; }

/* La grilla de indicadores cuando va DENTRO de una tarjeta y no suelta en la
   página: no necesita el margen inferior, que lo pone la tarjeta. */
.stats-grid-en-tarjeta { padding: 1rem 1rem 0; margin-bottom: 0; }

/* Esqueleto de una tarjeta de indicador: un renglón de rótulo y una cifra. */
.skeleton-bar-titulo { width: 55%; height: .65rem; }
.skeleton-bar-cifra  { width: 75%; height: 1.4rem; margin-top: .6rem; }

/* ===== CALENDARIO DE SEGUIMIENTOS (public/js/shared/calendarPicker.js) ===== */

.cal-picker { min-width: 260px; }

.cal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: .5rem;
}

.cal-titulo-mes {
  font-weight: 600;
  color: var(--text-primary);
  text-transform: capitalize;
}

.cal-dias-semana,
.cal-grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: .25rem;
}

.cal-celda {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 2.1rem;
}

.cal-dia-nombre {
  font-size: .7rem;
  font-weight: 600;
  color: var(--text-muted);
  text-transform: uppercase;
}

.cal-dia {
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--text-primary);
  cursor: pointer;
  font-size: .85rem;
  position: relative;
}

.cal-dia:hover { background: var(--bg-hover); }

.cal-dia.cal-hoy { border-color: var(--clr-blue); }

.cal-dia.cal-seleccionado {
  background: var(--clr-violet);
  color: var(--clr-white);
}

.cal-dia.cal-ocupado::after {
  content: '';
  position: absolute;
  bottom: 3px;
  left: 50%;
  transform: translateX(-50%);
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--clr-amber);
}

.cal-dia.cal-seleccionado.cal-ocupado::after { background: var(--clr-white); }

.cal-leyenda { display: flex; align-items: center; gap: .4rem; }

.cal-leyenda-punto {
  display: inline-block;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--clr-amber);
}

/* ── Lienzos de gráfico ──────────────────────────────────────────────────────
   La ALTURA se fija acá, no la decide el JavaScript. Asignar canvas.height
   reescribe el atributo height del elemento; si el dibujado lo volviera a leer
   de ahí en el cuadro siguiente, se realimentaría y el lienzo crecería solo
   hasta estirar la página. Con la altura puesta en CSS, ese fallo no puede
   pasar del código al maquetado. Ver public/js/shared/graficos.js. */
.lienzo-grafico { padding: .75rem 1.25rem 0; }
.lienzo-grafico canvas { display: block; width: 100%; height: 170px; max-height: 170px; }

.lienzo-anillo { display: flex; justify-content: center; padding: 1rem 1rem .25rem; }
.lienzo-anillo canvas { display: block; width: 150px; height: 150px; max-height: 150px; }

/* Dos gráficos lado a lado encabezando un reporte. En pantalla angosta se
   apilan: dos anillos de 150px no entran en un teléfono. */
.fila-graficos { display: flex; flex-wrap: wrap; justify-content: center; gap: 1rem; }
