/* ==========================================================================
   Vinculación con la Sociedad · Facultad de la Salud Humana · UNL
   Estructura, cabecera, navegación y páginas.
   ==========================================================================

   Arquitectura de la hoja de estilos (se cargan en este orden):

     1. `fonts.css`                    Montserrat, servida desde este servidor.
     2. `design-tokens.css`            color, tipografía, densidad, estructura.
     3. Bootstrap 5.3 (CDN)            base de componentes.
     4. `components.css`               tarjetas, tablas, formularios, botones…
     5. `app.css`  ← este archivo      shell, cabecera, barra lateral, páginas.

   **Un solo sistema visual para toda la plataforma.** La estructura, la
   cabecera y la barra lateral son las del Sistema de Gestión de Laboratorios
   (`/var/www/fsh-laboratorios-django`): misma gramática, mismos tokens, mismo
   comportamiento móvil. Vinculación y Laboratorios son la misma plataforma
   institucional y no pueden verse como dos productos distintos. La hoja se
   **replica** aquí; ningún repositorio enlaza los estáticos del otro.

   Diferencia funcional documentada frente a Laboratorios: aquí no existen el
   panel con fotografía institucional (`welcome-panel`) ni la portada pública
   (`public-hero`), porque Vinculación no tiene esas pantallas. No se
   reinterpreta nada: simplemente no se copia lo que no tiene página que vestir,
   y con ello tampoco se arrastran las fotografías que esos bloques necesitan.
   Las páginas de error (sección 8) sí se replican —403, 404 y 500 existen en
   cualquier aplicación—, pero con el fondo plano del acceso de esta aplicación
   en lugar de la fotografía del campus, que aquí no está.

   La sección 12 recoge las clases de las pantallas ya existentes de
   Vinculación (`.page-heading`, `.data-table`, `.form-field`…). No son un
   segundo sistema: se redefinen **con estos mismos tokens** para que las
   pantallas anteriores hablen el idioma nuevo sin reescribir su marcado.
   ========================================================================== */

/* ==========================================================================
   1. Base tipográfica
   ========================================================================== */
/* El salto a un ancla no debe dejar el destino debajo de la cabecera fija: se
   reserva su altura. Es el mismo token que usa el desplazamiento de la barra
   lateral, de modo que las tres piezas —cabecera, lateral y contenido— se
   alinean siempre con la misma medida. */
html { scroll-padding-top: var(--fsh-navbar-height); }

body {
    font-family: var(--fsh-font);
    color: var(--fsh-text);
    font-size: var(--font-size-base);
    font-weight: var(--font-weight-regular);
    line-height: 1.55;
    min-height: 100vh;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    /* Montserrat es geométrica y de caja ancha: sin un poco de tracking
       negativo en cuerpo, los párrafos largos se leen sueltos. */
    letter-spacing: -.003em;
}

button, input, select, textarea, .btn, .badge, .alert, .table,
.breadcrumb, .modal, .dropdown-menu {
    font-family: var(--fsh-font);
}

/* Títulos: semibold, interlineado corto y tracking negativo creciente con el
   tamaño. En Montserrat, un título grande sin tracking negativo se ve disperso.
   El color pasa al azul institucional oscuro: da jerarquía sin más peso. */
h1, .h1, h2, .h2, h3, .h3, h4, .h4, h5, .h5, h6, .h6 {
    color: var(--fsh-blue-700);
    font-weight: var(--font-weight-bold);
    line-height: 1.22;
    letter-spacing: -.015em;
}
h1, .h1 { letter-spacing: -.022em; }

/* Título de página y subtítulo: una sola jerarquía en toda la aplicación. */
.page-title {
    font-size: var(--font-size-2xl);
    font-weight: var(--font-weight-bold);
    color: var(--fsh-blue-700);
    margin: 0;
    letter-spacing: -.022em;
}
.page-subtitle {
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-medium);
    color: var(--fsh-text-muted);
    margin: .2rem 0 0;
}
/* Rótulo de sección: pequeño, en versalitas y con tracking abierto. El tracking
   es lo que lo convierte en rótulo y no en un texto pequeño cualquiera. */
.section-title {
    font-size: var(--font-size-xs);
    font-weight: var(--font-weight-bold);
    text-transform: uppercase;
    letter-spacing: .13em;
    color: var(--fsh-blue-600);
    margin-bottom: var(--space-3);
}
.text-secondary { color: var(--fsh-text-muted) !important; }

/* Metadatos y textos de apoyo. */
.small, small { font-size: var(--font-size-sm); }
.fsh-meta { font-size: var(--font-size-xs); color: var(--fsh-text-muted); font-weight: var(--font-weight-medium); }

/* Números destacados de métricas. Tabulares para que no bailen al refrescar. */
.metric-value {
    font-size: 1.55rem;
    font-weight: var(--font-weight-bold);
    line-height: 1.1;
    letter-spacing: -.03em;
    font-variant-numeric: tabular-nums;
    color: var(--fsh-blue-700);
}
.metric-label {
    font-size: var(--font-size-xs);
    font-weight: var(--font-weight-semibold);
    line-height: 1.35;
    color: var(--fsh-text-muted);
    text-transform: uppercase;
    letter-spacing: .06em;
}

/* ==========================================================================
   2. Foco visible — accesibilidad transversal
   ========================================================================== */
:focus-visible {
    outline: 3px solid rgba(0, 76, 123, .28);
    outline-offset: 2px;
    border-radius: var(--fsh-radius-sm);
}
/* Sobre azul oscuro el foco se marca en blanco: el rojo corporativo solo llega
   a 1.85:1 contra #003A5D y un indicador de foco necesita al menos 3:1. */
.app-sidebar :focus-visible {
    outline: 2px solid #fff;
    outline-offset: 1px;
}
.btn:focus-visible,
.form-control:focus-visible,
.form-select:focus-visible { outline-offset: 1px; }
/* Bootstrap anula el contorno de `.btn` al enfocarlo y lo sustituye por una
   sombra que solo existe si el botón declara una variante (`btn-primary` y
   compañía). Los dos controles de la cabecera son `.btn` sin variante: se
   quedaban sin contorno y sin sombra, es decir, sin ningún indicador de foco.
   Son los únicos `.btn` sin variante de la aplicación y llegaron con la
   homologación del shell, así que se nombran en lugar de escribir una regla
   general que también tocaría a los botones que ya se marcan bien. En reposo no
   cambia nada: solo al llegar con el teclado. */
.app-navbar .navbar-toggle:focus-visible,
.app-navbar .navbar-account__toggle:focus-visible {
    outline: 3px solid rgba(0, 76, 123, .28);
    outline-offset: 2px;
}

/* La flecha de `.form-select` es una imagen de fondo anclada a 12 px del borde
   derecho y ocupa 16 px, así que necesita 28 px reservados. `components.css`
   iguala el relleno de todos los campos en .75rem (12 px) y con eso el texto de
   la opción entra por debajo de la flecha —medido: 26 px de solape en «Todos los
   estados»—. Bootstrap reserva 2.25rem justamente para esto.

   La corrección se escribe AQUÍ y no en `components.css` a propósito:
   `design-tokens.css` y `components.css` son réplica byte a byte de las de
   Laboratorios y no deben divergir. El defecto es de la plataforma —Laboratorios
   lo tiene igual— y su arreglo definitivo pertenece a la hoja compartida; hasta
   que se haga allí, esta aplicación no lo muestra. `app.css` se carga después,
   que es lo que hace que esta regla gane. */
.form-select { padding-right: 2.25rem; }

/* Salto al contenido para navegación por teclado. */
.skip-link {
    position: absolute;
    left: -9999px;
    top: 0;
    z-index: 1090;
    background: var(--fsh-navy);
    color: #fff;
    padding: .65rem 1rem;
    border-radius: 0 0 var(--fsh-radius) 0;
    font-weight: var(--font-weight-semibold);
    text-decoration: none;
}
.skip-link:focus {
    left: 0;
    color: #fff;
}

/* ==========================================================================
   3. Cabecera institucional
   ==========================================================================
   Fondo blanco, logotipo oficial de la UNL a la izquierda, denominación
   institucional en texto, datos de la cuenta a la derecha con avatar de
   iniciales y rol, y un filete inferior rojo–azul de 3 px que es el sello de
   marca de toda la plataforma.

   Tres decisiones estructurales sostienen el desplazamiento de la página:

     · **Altura fija, no mínima.** El desplazamiento de la barra lateral, el
       alto del shell y el salto a anclas se calculan con
       `--fsh-navbar-height`. Con `min-height`, la altura real podía separarse
       del token y esa diferencia se veía como una franja vacía al desplazar.
     · **Fondo opaco.** El blanco translúcido con desenfoque obligaba a
       recomponer la cabecera en cada fotograma del desplazamiento.
     · **El filete es el borde.** El filete rojo–azul cierra la cabecera a ras,
       sin un borde gris debajo que haya que tapar. */
.app-navbar {
    position: sticky;
    top: 0;
    z-index: 1030;
    height: var(--fsh-navbar-height);
    padding: 0;
    background: rgba(255, 255, 255, 1);
    border-bottom: 0;
    box-shadow: 0 1px 2px rgba(33, 25, 21, .05);
}
/* La fila interior ocupa la cabecera completa: el centrado vertical es suyo y
   no depende de rellenos que haya que mantener a mano. */
.app-navbar > .app-container { height: 100%; }
.app-navbar::after {
    content: "";
    position: absolute;
    inset: auto 0 0 0;
    height: 3px;
    background: var(--rule-brand);
}

/* Marca: el logotipo oficial horizontal, sin caja, sin radio y sin sombra. El
   área de reserva del manual (≈1/7 de su alto) se respeta como margen propio,
   para que nunca quede pegado a un icono o a un texto. */
.app-navbar .navbar-brand {
    display: inline-flex;
    align-items: center;
    gap: .9rem;
    min-width: 0;
    padding: 0;
    margin: 0;
    color: var(--fsh-text);
    text-decoration: none;
}
.app-navbar .navbar-brand:hover { color: var(--fsh-text); }
.navbar-logo {
    height: 40px;
    width: auto;
    display: block;
    object-fit: contain;
    object-position: left center;
    margin-right: calc(40px / 7);
}
.navbar-brand-copy { min-width: 0; line-height: 1.2; }
.navbar-brand-title {
    display: block;
    font-size: .78rem;
    font-weight: var(--font-weight-bold);
    letter-spacing: .01em;
    text-transform: uppercase;
    color: var(--fsh-blue-700);
}
.navbar-brand-institution {
    display: block;
    margin-top: .12rem;
    color: var(--fsh-text-muted);
    font-size: var(--font-size-xs);
    font-weight: var(--font-weight-semibold);
    letter-spacing: .01em;
}
/* Contexto de página: separa la marca del título de la pantalla con un filete
   vertical tenue, en lugar de dejarlo flotando. */
.navbar-context {
    color: var(--fsh-text-muted);
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-semibold);
    letter-spacing: .01em;
    padding-left: .95rem;
    margin-left: .3rem;
    border-left: 1px solid var(--fsh-border);
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Botón de menú en móvil: control neutro sobre fondo claro. */
.app-navbar .navbar-toggle {
    width: 38px;
    height: 38px;
    flex: 0 0 38px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    border: 1px solid var(--fsh-border);
    border-radius: 9px;
    background: var(--fsh-surface-muted);
    color: var(--fsh-blue-700);
}
.app-navbar .navbar-toggle:hover {
    background: var(--fsh-blue-050);
    border-color: var(--fsh-blue-400);
    color: var(--fsh-blue-700);
}

/* Zona de cuenta.
   ==========================================================================
   Composición fina, no cápsula: primero quién (avatar de iniciales), después
   el nombre con el rol debajo y, al final, un galón discreto que anuncia el
   menú. La caja desaparece en reposo; el fondo azul muy claro solo aparece al
   pasar el puntero o con el menú desplegado. */
.navbar-account { flex: 0 0 auto; }
.app-navbar .navbar-account__toggle {
    display: flex;
    align-items: center;
    gap: .6rem;
    /* Sin altura de control: este no es un botón de trabajo. El objetivo táctil
       lo garantiza el propio avatar (34 px) más el relleno. */
    min-height: 0;
    padding: .3rem .45rem;
    border: 0;
    border-radius: var(--radius-control);
    color: var(--fsh-text);
    background: transparent;
    text-align: left;
    transition: background-color var(--fsh-transition);
}
/* Los botones de la aplicación se levantan 1 px al pasar el puntero; el de la
   cuenta no, porque no es una acción: es la identidad de la sesión. */
.app-navbar .navbar-account__toggle:hover,
.app-navbar .navbar-account__toggle:active { transform: none; }
.app-navbar .navbar-account__toggle:hover,
.app-navbar .navbar-account__toggle[aria-expanded="true"] {
    background: var(--fsh-blue-050);
    color: var(--fsh-text);
}
/* Separador de la misma familia que el del contexto de página: filete tenue,
   no contorno. Solo donde hay sitio para el nombre. */
@media (min-width: 768px) {
    .navbar-account::before {
        content: "";
        position: absolute;
        inset: .9rem auto .9rem 0;
        width: 1px;
        background: var(--fsh-border);
    }
    .navbar-account { position: relative; padding-left: .85rem; }
}
/* Indicador de despliegue: galón fino que gira al abrir. */
.navbar-account__caret {
    flex: 0 0 auto;
    font-size: .7rem;
    line-height: 1;
    color: var(--fsh-text-muted);
    transition: transform var(--fsh-transition);
}
.navbar-account__toggle[aria-expanded="true"] .navbar-account__caret {
    transform: rotate(180deg);
}

/* Jerarquía del texto: el nombre manda, el rol acompaña. */
.navbar-user-copy { min-width: 0; line-height: 1.25; }
.navbar-user-name {
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-bold);
    letter-spacing: -.005em;
    color: var(--fsh-blue-700);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    max-width: 14rem;
}
.navbar-user-role {
    margin-top: .1rem;
    font-size: .625rem;
    font-weight: var(--font-weight-semibold);
    letter-spacing: .07em;
    text-transform: uppercase;
    color: var(--fsh-text-muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    max-width: 14rem;
}
.navbar-avatar {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 34px;
    height: 34px;
    flex: 0 0 34px;
    border-radius: 50%;
    background: var(--fsh-blue-700);
    color: #fff;
    font-size: .7rem;
    font-weight: var(--font-weight-bold);
    letter-spacing: .04em;
}

/* ==========================================================================
   4. Estructura general (shell)
   ========================================================================== */
.app-body {
    background-color: var(--fsh-content-bg);
    min-height: 100vh;
    min-height: 100dvh;
}
/* La altura útil se mide con `dvh` —el alto *visible*— y no con `vh`. En móvil,
   `100vh` cuenta la pantalla como si la barra del navegador no existiera: al
   desplazar, esa barra se retrae, la ventana crece y el shell queda calculado
   sobre una medida que ya no es la buena. `vh` queda como respaldo. */
.app-shell {
    display: flex;
    align-items: stretch;
    min-height: calc(100vh - var(--fsh-navbar-height));
    min-height: calc(100dvh - var(--fsh-navbar-height));
}
.app-content {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    background: var(--fsh-content-bg);
}
/* Limita el ancho en monitores muy amplios sin estrechar el trabajo diario. */
.app-container {
    width: 100%;
    max-width: var(--fsh-content-max);
    margin-inline: auto;
}
/* Ancho cómodo de lectura para formularios y fichas de detalle. */
.content-narrow { max-width: 780px; }
.content-medium { max-width: 1100px; }

.app-footer {
    margin-top: auto;
    border-top: 1px solid var(--fsh-border);
    background: var(--fsh-surface);
    color: var(--fsh-text-muted);
    font-size: var(--font-size-xs);
}
.app-footer .footer-logo {
    height: 26px;
    width: auto;
    opacity: .85;
}

/* ==========================================================================
   5. Barra lateral
   ==========================================================================
   Azul petróleo institucional: el degradado recorre el azul de la UNL entre
   sus profundidades —más claro arriba, junto a la cabecera; profundo abajo—
   sin introducir ningún tono ajeno.

   La identidad va arriba, con la **barra roja lateral**: un filete vertical de
   4 px a la izquierda del bloque de texto. El nombre del sistema se escribe
   como texto institucional; no se crea ninguna marca gráfica nueva. */
.app-sidebar {
    position: relative;
    display: flex;
    flex-direction: column;
    overflow: hidden;
    background: linear-gradient(180deg,
        var(--unl-blue) 0%, var(--unl-blue-dark) 56%, var(--unl-blue-deep) 100%);
    color: #fff;
    width: var(--fsh-sidebar-width);
    border-right: 0;
}
@media (min-width: 992px) {
    .app-sidebar {
        flex: 0 0 var(--fsh-sidebar-width);
        /* La navegación acompaña al usuario en listados largos. Se ancla justo
           bajo la cabecera —misma medida que su altura fija— y ocupa el resto
           de la ventana visible: no queda franja entre una y otra al desplazar. */
        position: sticky;
        top: var(--fsh-navbar-height);
        height: calc(100vh - var(--fsh-navbar-height));
        height: calc(100dvh - var(--fsh-navbar-height));
    }
    /* Por encima del punto de ruptura, Bootstrap deja el cuerpo del offcanvas
       sin crecer (`flex-grow: 0`), lo que dejaría la identidad, la lista y el
       pie apilados arriba. Aquí el cuerpo ocupa la barra completa: identidad y
       pie fijos en sus extremos y solo corre la lista de opciones. */
    .app-sidebar .offcanvas-body {
        display: flex;
        flex: 1 1 auto;
        min-height: 0;
        overflow: hidden;
    }
}
.app-sidebar .offcanvas-header {
    background: var(--unl-blue-dark);
    color: #fff;
    border-bottom: 1px solid rgba(255, 255, 255, .13);
}
.app-sidebar .offcanvas-header .btn-close {
    filter: invert(1) grayscale(100%) brightness(200%);
}

/* Identidad del sistema: dos líneas, el rótulo «UNL · FSH» y el nombre del
   sistema. La denominación completa de la dependencia se lee en la cabecera;
   repetirla aquí no reforzaría la identidad, la convertiría en ruido. */
.sidebar-identity {
    flex: 0 0 auto;
    padding: 1.05rem 1.05rem .95rem;
    border-bottom: 1px solid rgba(255, 255, 255, .13);
}
/* Barra roja lateral: el acento de marca en el punto de más jerarquía de la
   navegación. */
.sidebar-identity > span {
    display: block;
    min-width: 0;
    line-height: 1.3;
    padding-left: .85rem;
    border-left: 4px solid var(--unl-red);
}
.sidebar-identity__kicker {
    display: block;
    color: rgba(255, 255, 255, .66);
    font-size: .5875rem;
    font-style: normal;
    font-weight: var(--font-weight-bold);
    letter-spacing: .19em;
    text-transform: uppercase;
}
.sidebar-identity strong {
    display: block;
    margin-top: .35rem;
    color: #fff;
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-bold);
    letter-spacing: -.01em;
}

/* Zona desplazable: la identidad y el pie quedan fijos; solo corre la lista de
   opciones, que es lo que crece con el rol del usuario. */
.app-sidebar .offcanvas-body { flex-direction: column; padding: 0; }
.app-sidebar-nav {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    padding: .55rem .7rem 1rem;
    width: 100%;
    scrollbar-width: thin;
    scrollbar-color: rgba(255, 255, 255, .22) transparent;
}
.app-sidebar-nav::-webkit-scrollbar { width: 8px; }
.app-sidebar-nav::-webkit-scrollbar-thumb {
    background: rgba(255, 255, 255, .2);
    border-radius: 4px;
}
.app-sidebar-nav > .sidebar-section-title:first-child { padding-top: .35rem; }

/* Rótulo de grupo: separa módulos sin recargar. */
.sidebar-section-title {
    padding: .95rem .55rem .35rem;
    color: rgba(255, 255, 255, .55);
    font-size: .5875rem;
    font-weight: var(--font-weight-bold);
    letter-spacing: .16em;
    text-transform: uppercase;
}

.sidebar-link {
    display: flex;
    align-items: center;
    gap: .65rem;
    min-height: 40px;
    margin-bottom: 2px;
    padding: .5rem .65rem;
    border: 0;
    border-radius: 9px;
    color: rgba(255, 255, 255, .84);
    text-decoration: none;
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-semibold);
    letter-spacing: -.005em;
    line-height: 1.3;
    transition: background-color var(--fsh-transition), color var(--fsh-transition);
}
.sidebar-link > span:not(.badge) {
    /* La etiqueta ocupa el espacio disponible y se ajusta por PALABRAS, nunca
       letra por letra: `anywhere` rompería «Mis participaciones» en vertical
       cuando la columna se estrecha. */
    flex: 1 1 auto;
    min-width: 0;
    white-space: normal;
    word-break: normal;
    overflow-wrap: break-word;
}
.sidebar-link i {
    font-size: 1rem;
    flex: 0 0 auto;
    width: 1.2rem;
    text-align: center;
    color: rgba(255, 255, 255, .72);
    transition: color var(--fsh-transition);
}
.sidebar-link:hover {
    background-color: rgba(255, 255, 255, .11);
    color: #fff;
}
.sidebar-link:hover i { color: #fff; }
.sidebar-link:focus-visible {
    background-color: rgba(255, 255, 255, .12);
    color: #fff;
}
/* Estado activo: rojo institucional macizo. Es el único lugar de la navegación
   donde el rojo ocupa superficie, y por eso funciona: marca una sola opción
   entre muchas. Blanco sobre #BF0811 alcanza 6.45:1, por encima del mínimo AA. */
.sidebar-link.active {
    background: var(--unl-red);
    color: #fff;
    font-weight: var(--font-weight-bold);
    box-shadow: 0 8px 20px rgba(0, 0, 0, .18);
}
.sidebar-link.active i { color: #fff; }
.sidebar-link.disabled {
    color: rgba(255, 255, 255, .45);
    cursor: default;
}
.sidebar-link.disabled:hover { background-color: transparent; }
.sidebar-link .badge { flex: 0 0 auto; font-size: .625rem; margin-left: auto; }

/* Contadores del menú: sólidos, porque compiten con el fondo azul profundo. */
.sidebar-link .badge.text-bg-danger {
    background-color: #fff !important;
    color: var(--unl-red) !important;
    box-shadow: none;
}
.sidebar-link.active .badge.text-bg-danger {
    background-color: rgba(255, 255, 255, .95) !important;
    color: var(--unl-red-dark) !important;
}
.sidebar-link .badge.text-bg-warning {
    background-color: #F2C14E !important;
    color: var(--unl-black) !important;
    box-shadow: none;
}
.sidebar-link .badge.text-bg-light {
    background-color: rgba(255, 255, 255, .16) !important;
    color: rgba(255, 255, 255, .85) !important;
    box-shadow: none;
}

/* Pie de la barra lateral: estable, discreto, sin competir con la navegación. */
.sidebar-footer {
    flex: 0 0 auto;
    border-top: 1px solid rgba(255, 255, 255, .13);
    color: rgba(255, 255, 255, .62);
    font-size: .625rem;
    font-weight: var(--font-weight-semibold);
    letter-spacing: .06em;
    text-transform: uppercase;
}

/* ==========================================================================
   6. Encabezado de página y migas
   ========================================================================== */
.page-header {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--space-3);
    padding: .15rem 0 var(--space-md);
    margin-bottom: var(--space-lg);
    border-bottom: 1px solid var(--fsh-border);
}
.page-header__text { min-width: 0; }
/* Filete rojo corto: marca institucional discreta, sin ocupar espacio. */
.page-header__text .page-title::before {
    content: "";
    display: block;
    width: 34px;
    height: 3px;
    background: var(--unl-red);
    border-radius: 2px;
    margin-bottom: .5rem;
}
.page-header__actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-sm);
}

/* Migas de pan discretas: contexto, no protagonismo. */
.breadcrumb {
    font-size: var(--font-size-sm);
    margin-bottom: var(--space-md);
    padding: .38rem .75rem;
    width: fit-content;
    border: 1px solid var(--fsh-border);
    border-radius: 999px;
    background: var(--fsh-surface);
}
.breadcrumb-item a {
    color: var(--fsh-text-muted);
    text-decoration: none;
}
.breadcrumb-item a:hover { color: var(--fsh-blue-600); text-decoration: underline; }
.breadcrumb-item.active { color: var(--fsh-blue-700); font-weight: var(--font-weight-semibold); }

/* ==========================================================================
   7. Acceso institucional
   ==========================================================================
   Vinculación no tiene portada pública ni panel fotográfico; su pantalla de
   acceso es una tarjeta blanca centrada sobre el fondo neutro de la
   aplicación, con el mismo filete superior rojo–azul que encabeza cada
   tarjeta del sistema. */
.auth-body {
    min-height: 100vh;
    background-color: var(--fsh-content-bg);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: 1.5rem;
}
.auth-container { width: 100%; max-width: 440px; }
.auth-card,
.login-card {
    position: relative;
    overflow: hidden;
    background: var(--fsh-surface);
    border: 1px solid var(--fsh-border);
    border-radius: var(--fsh-radius-lg);
    box-shadow: var(--fsh-shadow-lg);
    padding: 1.9rem 1.85rem 1.75rem;
}
.auth-card::before,
.login-card::before {
    content: "";
    position: absolute;
    inset: 0 0 auto 0;
    height: 5px;
    background: var(--rule-brand-card);
}
.auth-brand { text-align: center; margin-bottom: 1.4rem; }
.auth-brand img { height: 46px; width: auto; }
.auth-title,
.login-heading h1 {
    font-size: 1.4rem;
    font-weight: var(--font-weight-bold);
    color: var(--fsh-blue-700);
    letter-spacing: -.025em;
    margin: .2rem 0 .3rem;
}
.auth-subtitle { color: var(--fsh-text-muted); font-size: var(--font-size-sm); }
/* Los campos del acceso son más altos que los del resto del sistema: es una
   pantalla de un solo propósito y el objetivo táctil manda sobre la densidad. */
.auth-card .form-control,
.login-card .form-control { min-height: 46px; }
.auth-card .btn-lg { min-height: 48px; }

/* ==========================================================================
   8. Páginas de error
   ==========================================================================
   Mismo patrón que el Sistema de Gestión de Laboratorios: tarjeta centrada,
   código grande, filete institucional y una única salida. Una página de error
   sigue siendo una página institucional, y hasta ahora 403 y 404 se servían con
   la página desnuda de Django —Times New Roman sobre blanco—, que no dice a qué
   sistema pertenece ni ofrece por dónde salir.

   El fondo va plano, sin la fotografía del campus que usa Laboratorios: esa
   imagen no existe en este repositorio y enlazarla dejaría una petición rota
   justo en la pantalla que se muestra cuando algo ya ha fallado. Se usa el mismo
   fondo que el acceso de esta aplicación, que también es plano. */
.error-body {
    min-height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
    background-color: var(--fsh-content-bg);
    padding: 1.5rem;
}
.error-wrapper {
    max-width: 520px;
    width: 100%;
}
.error-wrapper .card { box-shadow: var(--fsh-shadow-lg); }
.error-code {
    font-size: clamp(3.5rem, 12vw, 5.5rem);
    font-weight: var(--font-weight-bold);
    line-height: 1;
    letter-spacing: -.06em;
    color: var(--fsh-blue-600);
    font-variant-numeric: tabular-nums;
}
/* El tipo de error se distingue con un modificador propio, no con las
   utilidades de color de Bootstrap: aquellas no alcanzan AA sobre blanco y, al
   llevar `!important`, obligarían a responder con otro `!important`. El
   significado no depende del color: el encabezado lo dice con palabras. */
.error-code--warning { color: var(--fsh-warning); }
.error-code--danger  { color: var(--fsh-danger); }
.error-code--info    { color: var(--fsh-blue-600); }

/* ==========================================================================
   9. Utilidades
   ========================================================================== */
.text-institutional { color: var(--fsh-blue-600); }
.bg-surface { background-color: var(--fsh-surface); }
/* Filete institucional bajo el código de error, del mismo ancho y color que en
   Laboratorios. */
.gold-rule {
    width: 40px; height: 3px;
    background: var(--fsh-gold);
    border-radius: 2px;
    border: 0;
    opacity: 1;
    margin: .5rem 0;
}
/* No hay `.border-institutional`: el color de borde institucional ya viaja en
   `--bs-border-color`, así que la utilidad `border` de Bootstrap lo aplica sola. */

/* ==========================================================================
   10. Responsive
   ==========================================================================
   Tablet y móvil: la cabecera recupera la marca compacta y el menú se abre
   como panel lateral. La altura de la cabecera baja en el token (ver
   `design-tokens.css`), de modo que el shell y el desplazamiento siguen
   midiéndose con la altura real. */
@media (max-width: 991.98px) {
    .navbar-logo { height: 34px; }

    /* Cabecera: respeta el notch/isla (área segura superior) creciendo hacia
       arriba; su fondo cubre esa franja y el contenido queda debajo. */
    .app-navbar {
        height: auto;
        min-height: var(--fsh-navbar-height);
        padding-top: var(--safe-top);
        padding-left: var(--safe-left);
        padding-right: var(--safe-right);
    }

    /* Contenido a ancho completo del teléfono (la barra lateral es off-canvas,
       no ocupa columna). El área segura lateral se aplica al `main`, por FUERA
       del relleno del contenedor: en vertical vale 0 y no molesta; en
       horizontal evita que el contenido quede bajo el notch. */
    .app-content { width: 100%; max-width: 100%; }
    .app-content, .app-footer {
        padding-left: var(--safe-left);
        padding-right: var(--safe-right);
    }
    .app-footer { padding-bottom: calc(.75rem + var(--safe-bottom)); }

    /* ── Drawer lateral móvil ──────────────────────────────────────────────
       Panel real: ancho cómodo, alto de viewport visible, y el scroll vive SOLO
       en la lista de opciones. El bloqueo del fondo lo aporta el off-canvas de
       Bootstrap (backdrop).

       `position: fixed` es imprescindible: la regla base
       `.app-sidebar{position:relative}` —necesaria en escritorio— anularía el
       posicionamiento del off-canvas y dejaría la barra DENTRO del flujo,
       empujando el contenido y dejando su hueco al cerrarse. */
    .app-sidebar {
        position: fixed;
        top: 0;
        left: 0;
        z-index: 1045;
        width: var(--sidebar-mobile-width);
        max-width: 92vw;
        /* `bottom:auto` para que mande `height:100dvh` (Bootstrap fija top:0 y
           bottom:0, que daría el viewport de layout, no el visible). */
        bottom: auto;
        height: 100dvh;
        overflow: hidden;
    }
    .app-sidebar .offcanvas-body {
        display: flex;
        flex-direction: column;
        min-height: 0;
        overflow: hidden;
        padding: 0;
    }
    .app-sidebar .offcanvas-header { padding-top: calc(.6rem + var(--safe-top)); }
    .sidebar-identity { padding-top: calc(1.05rem + var(--safe-top)); }
    .app-sidebar-nav {
        flex: 1 1 auto;
        min-height: 0;
        overflow-y: auto;
        overscroll-behavior: contain;
        -webkit-overflow-scrolling: touch;
        padding-bottom: calc(1rem + var(--safe-bottom));
    }
    /* Área táctil cómoda por opción y sin partir etiquetas. */
    .sidebar-link { min-height: 44px; }
}

@media (max-width: 767.98px) {
    .navbar-brand-title { font-size: .7rem; }
    .navbar-logo { height: 30px; }
    .app-navbar .navbar-brand { gap: .6rem; }
    /* Sin nombre ni rol a la vista, el bloque de cuenta se reduce al avatar y
       al galón: no necesita separador ni relleno lateral. */
    .app-navbar .navbar-account__toggle { gap: .35rem; padding-inline: .3rem; }
}

/* Ajustes de pantallas pequeñas (320–575 px). */
@media (max-width: 575.98px) {
    .page-title { font-size: 1.25rem; }
    .metric-value { font-size: 1.5rem; }
    .card-body, .data-panel__body { padding: .9rem; }
    .page-header { gap: var(--space-sm); }
    .page-header__actions { width: 100%; }
    .page-header__actions > .btn { flex: 1 1 auto; }
    .form-actions > .btn { width: 100%; }
    .login-card, .auth-card { padding: 1.5rem 1.25rem 1.4rem; }
    /* Los títulos largos se ajustan en lugar de desbordar. */
    h1, .h1, h2, .h2, .page-title { overflow-wrap: anywhere; }
}

/* ==========================================================================
   11. Impresión
   ==========================================================================
   Sin navegación ni acciones; el contenido ocupa la página. */
@media print {
    .app-sidebar, .app-navbar, .app-footer, .page-header__actions,
    .filter-bar, .action-group, .ajax-loader { display: none !important; }
    .app-shell { display: block; }
    .card, .data-panel, .table-panel { border: 1px solid #999; box-shadow: none; }
    body { background: #fff; }
}

/* ==========================================================================
   12. Pantallas anteriores de Vinculación
   ==========================================================================
   Las pantallas institucionales de las fases anteriores —catálogos, proyectos,
   participaciones, documentos, auditoría— se escribieron con un vocabulario de
   clases propio (`.page-heading`, `.data-table`, `.form-field`…) antes de que
   la plataforma tuviera un sistema visual común.

   Aquí ese vocabulario se redefine **con los tokens de arriba**. No es un
   segundo sistema conviviendo con el primero: es el mismo sistema hablando el
   nombre antiguo, para que ninguna pantalla se quede sin homologar mientras se
   migra su marcado. Toda pantalla nueva —la importación de pacientes de
   HEARTS, la primera— usa directamente el vocabulario de `components.css`. */

/* Encabezado de página heredado → misma jerarquía que `.page-header`. */
.page-heading {
    max-width: none;
    padding: .15rem 0 var(--space-md);
    margin-bottom: var(--space-lg);
    border-bottom: 1px solid var(--fsh-border);
}
.page-heading h1 {
    font-size: var(--font-size-2xl);
    color: var(--fsh-blue-700);
    margin: 0 0 .3rem;
    letter-spacing: -.022em;
}
.page-heading h1::before {
    content: "";
    display: block;
    width: 34px;
    height: 3px;
    background: var(--unl-red);
    border-radius: 2px;
    margin-bottom: .5rem;
}
.page-heading p {
    color: var(--fsh-text-muted);
    font-size: var(--font-size-sm);
    line-height: 1.6;
    margin: 0;
    max-width: 80ch;
}
.eyebrow, .institution-label {
    display: block;
    color: var(--fsh-blue-600);
    font-size: var(--font-size-xs);
    font-weight: var(--font-weight-bold);
    letter-spacing: .13em;
    text-transform: uppercase;
}

/* Cuadrícula de módulos del panel → tarjeta institucional con filete. */
.module-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
    gap: var(--space-3);
}
.module-card {
    position: relative;
    overflow: hidden;
    padding: 1.15rem;
    background: var(--fsh-surface);
    border: 1px solid var(--fsh-border);
    border-radius: var(--fsh-radius);
    box-shadow: var(--fsh-shadow-sm);
    transition: border-color var(--fsh-transition), box-shadow var(--fsh-transition),
                transform var(--fsh-transition);
}
.module-card::before {
    content: "";
    position: absolute;
    inset: 0 0 auto 0;
    height: 3px;
    background: var(--rule-brand-card);
}
.module-card h2 {
    font-size: var(--font-size-lg);
    margin: .35rem 0 .3rem;
    color: var(--fsh-blue-700);
}
.module-card p { color: var(--fsh-text-muted); font-size: var(--font-size-sm); line-height: 1.6; }
.module-card:not(.module-card-disabled):hover {
    border-color: var(--fsh-blue-200);
    box-shadow: var(--fsh-shadow);
    transform: translateY(-2px);
}
.module-card-disabled { background: var(--fsh-surface-muted); opacity: 1; }
.module-card-disabled::before { background: var(--fsh-border); }
.module-status {
    display: inline-flex;
    align-items: center;
    padding: .26em .62em;
    border-radius: 999px;
    background: var(--fsh-success-bg);
    color: var(--fsh-success);
    box-shadow: inset 0 0 0 1px rgba(65, 142, 58, .3);
    font-size: var(--font-size-xs);
    font-weight: var(--font-weight-bold);
}

/* Tablas heredadas → mismo contenedor blanco y misma cabecera clara. */
.table-scroll, .table-wrapper {
    overflow-x: auto;
    width: 100%;
    background: var(--fsh-surface);
    border: 1px solid var(--fsh-border);
    border-radius: var(--fsh-radius);
    box-shadow: var(--fsh-shadow-sm);
    scrollbar-width: thin;
    scrollbar-color: var(--fsh-border-strong) transparent;
}
.data-table, .table-wrapper > table {
    width: 100%;
    border-collapse: collapse;
    font-size: var(--font-size-sm);
    margin: 0;
}
.data-table caption, .table-wrapper > table caption {
    caption-side: top;
    padding: .8rem 1rem .2rem;
    text-align: left;
    color: var(--fsh-blue-700);
    font-weight: var(--font-weight-bold);
}
.data-table th, .table-wrapper > table th { text-align: left; }
.data-table > thead > tr > th,
.table-wrapper > table > thead > tr > th {
    background: var(--fsh-blue-050);
    color: var(--fsh-blue-700);
    font-size: var(--font-size-xs);
    font-weight: var(--font-weight-bold);
    text-transform: uppercase;
    letter-spacing: .055em;
    padding: .7rem .8rem;
    white-space: nowrap;
    border-bottom: 1px solid var(--fsh-border);
}
.data-table > tbody > tr > td,
.data-table > tbody > tr > th,
.table-wrapper > table > tbody > tr > td,
.table-wrapper > table > tbody > tr > th {
    padding: .65rem .8rem;
    vertical-align: middle;
    border-bottom: 1px solid var(--fsh-border);
}
.data-table > tbody > tr:last-child > td,
.table-wrapper > table > tbody > tr:last-child > td { border-bottom: 0; }
.data-table > tbody > tr:hover,
.table-wrapper > table > tbody > tr:hover { background: var(--fsh-blue-050); }
.row-inactive { color: var(--fsh-text-muted); }
.row-actions { display: flex; gap: var(--space-sm); align-items: center; flex-wrap: wrap; }
.row-actions form { display: inline; margin: 0; }

/* Distintivos heredados → misma pastilla que `components.css`. */
.badge-ok, .badge-success {
    background-color: var(--fsh-success-bg);
    color: var(--fsh-success);
    box-shadow: inset 0 0 0 1px rgba(65, 142, 58, .3);
}
.badge-muted {
    background-color: var(--fsh-neutral-bg);
    color: var(--fsh-neutral);
    box-shadow: inset 0 0 0 1px rgba(91, 84, 80, .22);
    opacity: 1;
}
.muted { color: var(--fsh-text-muted); font-size: var(--font-size-sm); }

/* Formularios heredados → mismo campo, misma etiqueta, mismo foco.

   El segundo selector es el puente de migración: los formularios de las fases
   anteriores no declaran `form-control` en sus widgets, así que sus campos
   llegarían al navegador sin ninguna clase y se verían con el aspecto por
   omisión del sistema operativo —lo único de toda la pantalla que no seguiría
   el sistema visual—. Se les da el mismo campo que a los demás mientras se
   migran a `apps.core.forms.BootstrapFormMixin`, que es lo que usa ya el
   importador de pacientes de HEARTS y todo lo que venga después. */
.institutional-form { max-width: 780px; }
.form-field, .form-group { margin-bottom: var(--space-3); display: flex; flex-direction: column; gap: .3rem; }
.form-field label, .form-group label {
    font-weight: var(--font-weight-semibold);
    font-size: var(--font-size-sm);
    color: var(--fsh-blue-700);
}
.form-field input, .form-field select, .form-field textarea,
.form-group input, .form-group select, .form-group textarea,
.filter-bar input, .filter-bar select,
.app-content form input:not([type="hidden"]):not([type="checkbox"]):not([type="radio"]):not([type="submit"]):not([type="button"]):not(.form-control):not(.form-check-input),
.app-content form select:not(.form-select):not(.form-control),
.app-content form textarea:not(.form-control) {
    width: 100%;
    max-width: 100%;
    padding: .48rem .75rem;
    min-height: var(--control-height);
    border: 1px solid var(--fsh-border-strong);
    border-radius: var(--radius-control);
    background-color: var(--fsh-surface-muted);
    color: var(--fsh-text);
    font: inherit;
    font-size: var(--font-size-base);
    transition: border-color var(--fsh-transition), background-color var(--fsh-transition),
                box-shadow var(--fsh-transition);
}
.form-field input:focus, .form-field select:focus, .form-field textarea:focus,
.form-group input:focus, .form-group select:focus, .form-group textarea:focus,
.filter-bar input:focus, .filter-bar select:focus,
.app-content form input:not(.form-control):not(.form-check-input):focus,
.app-content form select:not(.form-select):not(.form-control):focus,
.app-content form textarea:not(.form-control):focus {
    border-color: var(--fsh-blue-600);
    background-color: #fff;
    box-shadow: 0 0 0 3px rgba(0, 76, 123, .15);
    outline: none;
}
.form-help, .field-help { font-size: var(--font-size-sm); color: var(--fsh-text-muted); }
.form-error, .field-errors {
    font-size: var(--font-size-sm);
    color: var(--fsh-danger);
    font-weight: var(--font-weight-medium);
}
.form-field-error input, .form-field-error select, .form-field-error textarea {
    border-color: var(--fsh-danger);
    border-width: 2px;
}

/* Botones heredados → misma escala y mismo color que `.btn` de Bootstrap. */
.primary-button, .secondary-button, .link-button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: .4rem;
    min-height: var(--control-height);
    padding: .4rem .9rem;
    border: 1px solid transparent;
    border-radius: var(--radius-control);
    font: inherit;
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-semibold);
    cursor: pointer;
    transition: background-color var(--fsh-transition), border-color var(--fsh-transition),
                box-shadow var(--fsh-transition);
}
.primary-button {
    background: var(--fsh-blue-600);
    border-color: var(--fsh-blue-600);
    color: #fff;
    box-shadow: 0 1px 2px rgba(0, 76, 123, .24), 0 4px 12px rgba(0, 76, 123, .16);
}
.primary-button:hover { background: var(--fsh-blue-700); border-color: var(--fsh-blue-700); }
.secondary-button {
    margin-top: .5rem;
    background: var(--fsh-surface);
    border-color: var(--fsh-border-strong);
    color: var(--fsh-text);
}
.secondary-button:hover { background: var(--fsh-surface-muted); border-color: var(--fsh-blue-400); color: var(--fsh-blue-700); }
.link-button {
    min-height: 0;
    padding: 0;
    background: transparent;
    border: 0;
    color: var(--fsh-blue-600);
    text-decoration: underline;
}
.link-button:hover { color: var(--fsh-blue-700); }

/* Barra de filtros heredada. `components.css` ya le da el panel blanco con
   borde y sombra; aquí solo se coloca en fila lo que el marcado antiguo pone
   suelto dentro, sin rejilla de Bootstrap. */
.filter-bar {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: var(--space-sm);
}
.filter-bar > .row { flex: 1 1 100%; }
/* Solo los controles que el marcado antiguo deja SUELTOS dentro de la barra:
   esos sí deben ocupar lo que necesitan y no la fila entera. Escrito como
   descendiente alcanzaba también a los que van dentro de una rejilla de
   Bootstrap —la barra de filtros de las importaciones—, y les quitaba la anchura
   de su columna para dejarlos del tamaño de su texto. */
.filter-bar > input, .filter-bar > select { width: auto; }

/* Fichas de detalle y listas heredadas. */
.detail-block { margin: var(--space-lg) 0; }
.detail-block h2 { font-size: var(--font-size-lg); margin-bottom: var(--space-3); }
.entity-list { list-style: none; padding: 0; margin: var(--space-2) 0; display: flex; flex-direction: column; }
.entity-list li {
    padding: .55rem 0;
    border-bottom: 1px solid var(--fsh-border);
    font-size: var(--font-size-sm);
}
.notice {
    background: var(--fsh-surface-muted);
    border: 1px solid var(--fsh-border);
    border-left: 3px solid var(--fsh-blue-400);
    border-radius: var(--fsh-radius-sm);
    padding: .65rem .9rem;
    font-size: var(--font-size-sm);
    color: var(--fsh-text-muted);
}
.notice code { font-size: var(--font-size-sm); }

/* Migas y paginación heredadas. */
.breadcrumbs {
    display: flex;
    flex-wrap: wrap;
    gap: .4rem;
    width: fit-content;
    font-size: var(--font-size-sm);
    margin-bottom: var(--space-md);
    padding: .38rem .75rem;
    border: 1px solid var(--fsh-border);
    border-radius: 999px;
    background: var(--fsh-surface);
    color: var(--fsh-text-muted);
}
.breadcrumbs a { color: var(--fsh-text-muted); text-decoration: none; }
.breadcrumbs a:hover { color: var(--fsh-blue-600); text-decoration: underline; }
/* `.detail-list` no se redefine aquí: `components.css` ya la apila por debajo
   de 576 px, y añadir un segundo punto de corte propio sería introducir un
   comportamiento móvil distinto del de la plataforma. */

/* Aviso heredado con la etiqueta de Django (`error` en lugar de `danger`). */
.alert-error {
    background: var(--fsh-danger-bg);
    border-color: rgba(191, 8, 17, .3);
    border-left-color: var(--fsh-danger);
    color: #8c1e18;
}

/* Barra lateral heredada: la navegación antigua deja de dibujarse como columna
   propia; el shell la sustituye por la barra institucional. */
.sidebar { display: none; }
