/* ============================================================
   SISTEMA VISUAL DE LA PLATAFORMA
   ============================================================
   Identidad: educativa, confiable, moderna. Azul institucional
   sobre superficies blancas y un fondo gris muy claro.

   Reglas del sistema (respetarlas al agregar pantallas):
   - Fondo gris claro, tarjetas blancas, bordes suaves de 1px.
   - Sombras casi imperceptibles. Nada de gradientes decorativos.
   - El color saturado se reserva para la acción principal y para
     los estados con significado (ok / aviso / error / pendiente).
   - Tipografía: Inter para la interfaz, Lora para los títulos de
     documento, IBM Plex Mono para cifras y notas (precisión de
     libreta).

   Los nombres de variable vienen de la identidad anterior
   ("tinta y papel") y se conservan a propósito: hay ~600 líneas
   de CSS de las pantallas de consolidación que los usan, y
   renombrarlos habría sido un refactor sin ningún beneficio
   visible. Lo que cambió son los VALORES.
   ============================================================ */

:root {
    /* --- Azul institucional: el color de la marca --- */
    --ink-900: #14315c;   /* Navy: barra lateral, títulos, texto fuerte */
    --ink-700: #1f4478;
    --ink-500: #64748b;   /* Gris azulado: texto secundario */
    --ink-100: #e8edf6;   /* Relleno suave */

    /* --- Acción principal --- */
    --brand: #2563eb;
    --brand-dark: #1d4ed8;
    --brand-soft: #eff4ff;

    /* --- Superficies --- */
    --paper: #f4f6fa;        /* Fondo general */
    --paper-card: #ffffff;   /* Tarjetas y tablas */
    --paper-line: #e4e9f2;   /* Bordes */

    /* --- Acentos de las tarjetas de resumen --- */
    --acento-verde: #16a34a;
    --acento-verde-soft: #e7f6ed;
    --acento-naranja: #ea8c2b;
    --acento-naranja-soft: #fdf0e0;
    --acento-morado: #7c3aed;
    --acento-morado-soft: #f2ecfe;

    /* Se llamaba "sello dorado" y ahora es ámbar, para que el estado
       "pendiente / requiere atención" use el mismo color en toda la
       plataforma en vez de tener un dorado aparte. */
    --gold: #b45309;
    --gold-soft: #fef3c7;

    /* --- Estados con significado --- */
    --ok: #15803d;
    --ok-soft: #e7f6ed;
    --warn: #b45309;
    --warn-soft: #fdf0e0;
    --danger: #b91c1c;
    --danger-soft: #fdeaea;

    --font-display: "Lora", "Source Serif 4", Georgia, serif;
    --font-body: "Inter", system-ui, -apple-system, sans-serif;
    --font-mono: "IBM Plex Mono", "SFMono-Regular", Menlo, monospace;

    --radius: 12px;
    --radius-sm: 8px;
    --shadow-sm: 0 1px 2px rgba(20, 49, 92, 0.05), 0 1px 3px rgba(20, 49, 92, 0.04);
    --shadow-md: 0 4px 14px rgba(20, 49, 92, 0.07);

    /* Medidas del armazón. Se leen desde el CSS del layout y desde las
       reglas de móvil, así que cambiarlas aquí mueve las dos. */
    --ancho-lateral: 15rem;
    --alto-superior: 4rem;
}
/* Lora e IBM Plex Mono se cargan en el <link> de base.html, junto con Inter.
   No usar @import aquí: al medio de la hoja los navegadores lo ignoran por
   especificación, y así fue como la tipografía de identidad pasó meses sin
   cargar y nadie lo notó. */

* { -webkit-font-smoothing: antialiased; }

html, body {
    background: var(--paper);
    color: var(--ink-900);
    font-family: var(--font-body);
}

/* Las pantallas de flujo no necesitan 1440px de ancho; topar el contenedor
   hace las líneas legibles en monitores grandes. Las tablas anchas (vista
   previa, tutor) scrollean dentro de su propio contenedor, no del body. */
.contenedor-pagina { max-width: 1320px; margin-inline: auto; }

/* Enlace de salto para teclado: invisible hasta recibir foco. */
.salto-contenido {
    position: absolute;
    left: -999px;
    top: 0;
    z-index: 2000;
    background: var(--ink-900);
    color: #fff;
    padding: .5rem 1rem;
    border-radius: 0 0 var(--radius) 0;
}
.salto-contenido:focus { left: 0; color: #fff; }

/* ============================================================
   ARMAZÓN DE LA APLICACIÓN
   ============================================================
   Escritorio: barra lateral fija a la izquierda + barra superior.
   Móvil (<992px): la lateral se convierte en un panel deslizante
   y la superior gana el botón de menú.

   El desplazamiento vive en <main>, no en <body>: así la barra
   lateral y la superior no se mueven, y —lo importante— las
   tablas anchas de la vista previa scrollean dentro de su propio
   contenedor sin arrastrar la página entera.
   ============================================================ */

.app-shell { min-height: 100vh; }

/* ---------- Barra lateral ---------- */
.app-lateral {
    position: fixed;
    inset-block: 0;
    inset-inline-start: 0;
    width: var(--ancho-lateral);
    background: var(--paper-card);
    border-inline-end: 1px solid var(--paper-line);
    display: flex;
    flex-direction: column;
    z-index: 1045;
}

.lateral-marca {
    display: flex; align-items: center; gap: .625rem;
    padding: .875rem 1rem;
    min-height: var(--alto-superior);
    border-bottom: 1px solid var(--paper-line);
    text-decoration: none;
}

.marca-escudo {
    width: 2.25rem; height: 2.25rem;
    flex: 0 0 auto;
    display: inline-flex; align-items: center; justify-content: center;
    background: var(--ink-900);
    color: #fff;
    border-radius: 10px;
    font-size: 1.1rem;
}
.marca-escudo img { width: 100%; height: 100%; object-fit: contain; border-radius: 10px; }

.marca-texto { min-width: 0; line-height: 1.15; }
.marca-nombre {
    display: block;
    font-family: var(--font-display);
    font-weight: 600;
    font-size: .95rem;
    color: var(--ink-900);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.marca-sub {
    display: block;
    font-size: .68rem;
    letter-spacing: .09em;
    text-transform: uppercase;
    color: var(--ink-500);
}

.lateral-nav {
    flex: 1 1 auto;
    overflow-y: auto;
    padding: .75rem .625rem;
    list-style: none;
    margin: 0;
}

.lateral-nav .nav-titulo {
    padding: .75rem .625rem .3rem;
    font-size: .7rem;
    font-weight: 600;
    letter-spacing: .08em;
    text-transform: uppercase;
    /* --ink-500 y no #94a3b8: sobre la lateral blanca aquel daba ≈2,6:1 de contraste
       en un texto de 0,7 rem (10/10/2026). */
    color: var(--ink-500);
}

/* El primer encabezado no lleva aire arriba: ya lo pone la lista. Solo pasa
   en el menú del Creator sin colegio, que empieza directamente por un grupo
   con título (los demás abren con "Inicio", que va sin encabezado). */
.lateral-nav .nav-titulo:first-child { padding-top: 0; }

/* Aviso de que un Creator está trabajando dentro de un colegio ajeno. Va en
   ámbar, que en toda la plataforma significa "requiere atención", y no en
   rojo: no es un error, es un modo de trabajo que conviene no olvidar. */
.aviso-suplantacion {
    display: flex; align-items: center; justify-content: space-between; gap: .5rem;
    margin: 0 .625rem .25rem;
    padding: .45rem .55rem;
    border-radius: var(--radius-sm);
    background: var(--gold-soft);
    color: var(--ink-900);
    font-size: .78rem;
    line-height: 1.3;
}
.aviso-suplantacion i { margin-inline-end: .3rem; }
.aviso-suplantacion form { margin: 0; }
.aviso-suplantacion button {
    border: 0; background: transparent; padding: 0;
    font-size: .78rem; font-weight: 600;
    color: var(--ink-700); text-decoration: underline; cursor: pointer;
}
.aviso-suplantacion button:hover { color: var(--ink-900); }

/* El pie comparte los estilos de la navegación: es la misma lista, solo
   que anclada abajo. Sin esto sus enlaces salían como enlaces sueltos,
   subrayados y sin alinear con el resto del menú. */
.lateral-nav a,
.lateral-pie a {
    display: flex; align-items: center; gap: .7rem;
    padding: .55rem .625rem;
    margin-bottom: .125rem;
    border-radius: var(--radius-sm);
    color: var(--ink-700);
    text-decoration: none;
    font-size: .925rem;
    font-weight: 500;
    transition: background .12s ease, color .12s ease;
}
.lateral-nav a i,
.lateral-pie a i { font-size: 1.05rem; width: 1.25rem; text-align: center; color: var(--ink-500); }
.lateral-nav a:hover,
.lateral-pie a:hover { background: var(--paper); color: var(--ink-900); }
.lateral-nav a:hover i,
.lateral-pie a:hover i { color: var(--ink-700); }

/* El activo va relleno, no subrayado: en una lista vertical de
   ocho elementos el subrayado se pierde. */
.lateral-nav a.es-activo,
.lateral-pie a.es-activo { background: var(--brand); color: #fff; }
.lateral-nav a.es-activo i,
.lateral-pie a.es-activo i { color: #fff; }
.lateral-nav a.es-activo:hover,
.lateral-pie a.es-activo:hover { background: var(--brand-dark); color: #fff; }

/* Contador de pendientes al final del elemento (ej. comunicados sin leer). */
.lateral-nav .nav-contador {
    margin-inline-start: auto;
    background: var(--danger-soft);
    color: var(--danger);
    border-radius: 999px;
    padding: .05rem .45rem;
    font-size: .72rem;
    font-weight: 600;
}
.lateral-nav a.es-activo .nav-contador { background: rgba(255,255,255,.22); color: #fff; }

.lateral-pie {
    padding: .625rem;
    border-top: 1px solid var(--paper-line);
    list-style: none;
    margin: 0;
}

/* ---------- Barra superior ---------- */
.app-superior {
    position: sticky; top: 0;
    z-index: 1030;
    min-height: var(--alto-superior);
    display: flex; align-items: center; gap: .75rem;
    padding: .625rem 1rem;
    background: var(--paper-card);
    border-bottom: 1px solid var(--paper-line);
}

.superior-titulo { min-width: 0; }
.superior-titulo strong {
    display: block;
    font-family: var(--font-display);
    font-size: 1.05rem;
    color: var(--ink-900);
    line-height: 1.2;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.superior-titulo span { font-size: .8rem; color: var(--ink-500); }

.superior-acciones { margin-inline-start: auto; display: flex; align-items: center; gap: .5rem; }

.chip-anio {
    display: inline-flex; align-items: center; gap: .4rem;
    padding: .35rem .7rem;
    background: var(--paper);
    border: 1px solid var(--paper-line);
    border-radius: 999px;
    font-size: .82rem;
    color: var(--ink-700);
    white-space: nowrap;
}
.chip-anio strong { font-weight: 600; color: var(--ink-900); }

.btn-usuario {
    display: inline-flex; align-items: center; gap: .55rem;
    padding: .25rem .5rem .25rem .25rem;
    background: transparent;
    border: 1px solid transparent;
    border-radius: 999px;
}
.btn-usuario:hover, .btn-usuario:focus { background: var(--paper); border-color: var(--paper-line); }

.avatar-iniciales {
    width: 2rem; height: 2rem;
    flex: 0 0 auto;
    display: inline-flex; align-items: center; justify-content: center;
    background: var(--ink-900);
    color: #fff;
    border-radius: 50%;
    font-size: .78rem;
    font-weight: 600;
    letter-spacing: .02em;
}

.usuario-datos { text-align: start; line-height: 1.15; }
.usuario-datos strong { display: block; font-size: .85rem; font-weight: 600; color: var(--ink-900); }
.usuario-datos span { font-size: .74rem; color: var(--ink-500); }

.dropdown-menu {
    border-color: var(--paper-line);
    border-radius: var(--radius);
    box-shadow: var(--shadow-md);
    font-size: .9rem;
    padding: .35rem;
}
.dropdown-item { border-radius: var(--radius-sm); padding: .5rem .65rem; }
.dropdown-item:active { background: var(--brand); }

/* ---------- Zona de contenido ---------- */
.app-contenido { margin-inline-start: var(--ancho-lateral); min-height: 100vh; }
.app-main { padding: 1.5rem 1.25rem 3rem; }

/* ---------- Menú lateral en móvil ----------
   Por debajo de 992px la lateral sale de flujo y se desliza. El
   fondo oscuro (.lateral-fondo) es lo que se pulsa para cerrar,
   y existe además del botón porque en móvil es el gesto que la
   gente intenta primero. */
.btn-menu { display: none; border: 0; background: transparent; font-size: 1.4rem; color: var(--ink-700); padding: .25rem .5rem; }
.lateral-fondo { display: none; }

@media (max-width: 991.98px) {
    .btn-menu { display: inline-flex; }
    .app-contenido { margin-inline-start: 0; }
    .app-lateral {
        transform: translateX(-100%);
        transition: transform .22s ease;
        box-shadow: var(--shadow-md);
        width: min(17rem, 84vw);
    }
    body.lateral-abierta .app-lateral { transform: translateX(0); }
    body.lateral-abierta { overflow: hidden; }
    body.lateral-abierta .lateral-fondo {
        display: block;
        position: fixed; inset: 0;
        background: rgba(20, 49, 92, .45);
        z-index: 1040;
        border: 0;
    }
    .app-main { padding: 1rem .875rem 3rem; }
}

@media (prefers-reduced-motion: reduce) {
    .app-lateral { transition: none; }
}

/* ---------- Breadcrumb ---------- */
.app-breadcrumb {
    font-size: .875rem;
    background: transparent;
}
.app-breadcrumb .breadcrumb-item a { color: var(--ink-500); text-decoration: none; }
.app-breadcrumb .breadcrumb-item.active { color: var(--ink-900); font-weight: 500; }

/* ---------- Headings ---------- */
h1, h2, h3, .page-title {
    font-family: var(--font-display);
    color: var(--ink-900);
    letter-spacing: -.01em;
}

.page-title {
    font-weight: 600;
    border-bottom: 2px solid var(--ink-900);
    display: inline-block;
    padding-bottom: .35rem;
}

.text-mono { font-family: var(--font-mono); }

/* ---------- Cards ---------- */
.card {
    background: var(--paper-card);
    border: 1px solid var(--paper-line);
    border-radius: var(--radius);
    box-shadow: var(--shadow-sm);
}

.card-ruled {
    background-image: repeating-linear-gradient(
        to bottom,
        transparent, transparent 2.15rem,
        var(--paper-line) 2.15rem, var(--paper-line) calc(2.15rem + 1px)
    );
}

/* ---------- Botones ----------
   Un solo estilo por jerarquía, para que no convivan tres azules
   distintos: primario = acción de la pantalla, outline = acción
   secundaria, link = terciaria. */
.btn {
    --bs-btn-font-weight: 500;
    border-radius: var(--radius-sm);
    padding: .5rem 1rem;
}
.btn-primary {
    background: var(--brand);
    border-color: var(--brand);
}
.btn-primary:hover, .btn-primary:focus, .btn-primary:active {
    background: var(--brand-dark);
    border-color: var(--brand-dark);
}
.btn-primary:disabled, .btn-primary.disabled {
    background: var(--brand);
    border-color: var(--brand);
    opacity: .45;
}
.btn-outline-primary {
    color: var(--brand);
    border-color: #c7d7fb;
    background: var(--paper-card);
}
.btn-outline-primary:hover, .btn-outline-primary:focus {
    background: var(--brand);
    border-color: var(--brand);
    color: #fff;
}
.btn-outline-secondary {
    color: var(--ink-700);
    border-color: var(--paper-line);
    background: var(--paper-card);
}
.btn-outline-secondary:hover, .btn-outline-secondary:focus {
    background: var(--ink-100);
    border-color: var(--ink-100);
    color: var(--ink-900);
}
/* Se llamaba "dorado" y ahora es ámbar: la acción que resuelve algo
   pendiente (aplicar a todos, generar lo que falta). */
.btn-gold {
    background: var(--gold-soft);
    border-color: #f6dfa0;
    color: var(--gold);
    font-weight: 600;
}
.btn-gold:hover, .btn-gold:focus { background: #fde9ad; color: #7c3a06; }

/* El foco tiene que verse SIEMPRE: hay usuarios que navegan estas
   tablas con teclado y sin esto no sabrían dónde están. */
.btn:focus-visible, a:focus-visible, .form-control:focus-visible,
.form-select:focus-visible, summary:focus-visible, [tabindex]:focus-visible {
    outline: 3px solid rgba(37, 99, 235, .45);
    outline-offset: 2px;
}

/* ---------- Formularios ---------- */
.form-control, .form-select {
    border-color: var(--paper-line);
    border-radius: var(--radius-sm);
    padding: .55rem .75rem;
    color: var(--ink-900);
}
.form-control:focus, .form-select:focus {
    border-color: var(--brand);
    box-shadow: 0 0 0 .2rem rgba(37, 99, 235, .12);
}
.form-control::placeholder { color: #9aa5b8; }
.form-label { font-weight: 500; font-size: .9rem; color: var(--ink-700); margin-bottom: .3rem; }
.form-text { font-size: .82rem; color: var(--ink-500); }
.form-check-input:checked { background-color: var(--brand); border-color: var(--brand); }

/* ---------- Badges / sellos de estado ---------- */
.seal {
    display: inline-flex; align-items: center; gap: .35rem;
    padding: .3rem .7rem;
    border-radius: 999px;
    font-size: .78rem;
    font-weight: 600;
    letter-spacing: .02em;
    text-transform: uppercase;
}
.seal-pendiente { background: var(--ink-100); color: var(--ink-700); }
.seal-procesando { background: var(--warn-soft); color: var(--warn); }
.seal-completado { background: var(--ok-soft); color: var(--ok); }
.seal-error { background: var(--danger-soft); color: var(--danger); }

/* ---------- Dropzone de subida ---------- */
.dropzone {
    border: 2px dashed var(--paper-line);
    border-radius: var(--radius);
    background: var(--paper);
    padding: 2rem 1rem;
    text-align: center;
    transition: border-color .15s ease, background .15s ease;
}
.dropzone.is-dragover {
    border-color: var(--brand);
    background: var(--brand-soft);
}

/* El <input type=file> nativo se oculta a la vista pero NO se quita del
   documento: sigue recibiendo el foco con teclado y lo anuncia el lector
   de pantalla. `display:none` o `visibility:hidden` lo sacarían de la
   navegación, y entonces solo se podría subir un archivo con ratón.
   El clic lo dispara el <label> que envuelve la zona. */
.dropzone input[type="file"] {
    position: absolute;
    width: 1px; height: 1px;
    padding: 0; margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}
/* Al enfocarlo con teclado, la zona entera se marca: si no, el foco
   estaría en un elemento invisible y nadie sabría dónde está. */
.dropzone:has(input[type="file"]:focus-visible) {
    border-color: var(--brand);
    background: var(--brand-soft);
    outline: 3px solid rgba(37, 99, 235, .45);
    outline-offset: 2px;
}

/* ---------- Lista de archivos por curso ---------- */
.course-row {
    border: 1px solid var(--paper-line);
    border-radius: var(--radius);
    background: var(--paper-card);
    transition: box-shadow .15s ease, transform .15s ease;
}
.course-row:hover { box-shadow: var(--shadow-md); }

.course-icon {
    width: 40px; height: 40px;
    border-radius: 8px;
    background: var(--ink-100);
    color: var(--ink-700);
    display: inline-flex; align-items: center; justify-content: center;
}

/* Archivo repetido: misma asignatura y bimestre que otro del lote. */
.course-row.is-duplicado {
    border-color: var(--danger);
    background: var(--danger-soft);
}
.course-row.is-duplicado .course-icon { background: #fff; color: var(--danger); }

.badge-duplicado {
    font-size: .7rem;
    font-weight: 600;
    letter-spacing: .03em;
    text-transform: uppercase;
    padding: .15rem .45rem;
    border-radius: 5px;
    background: var(--danger);
    color: #fff;
}

/* Asignatura leída pero que no existe en el mapeo curricular del grado: el
   archivo se lee bien, pero sus notas no tendrían a dónde ir. */
.badge-sin-mapeo {
    font-size: .7rem;
    font-weight: 600;
    letter-spacing: .03em;
    text-transform: uppercase;
    padding: .15rem .45rem;
    border-radius: 5px;
    background: var(--warn);
    color: #fff;
}

/* ---------- Asignación manual de asignatura ---------- */
.asignar-manual > summary {
    cursor: pointer;
    color: var(--ink-500);
    width: fit-content;
}
.asignar-manual > summary:hover { color: var(--ink-900); }
.asignar-manual[open] > summary { margin-bottom: .25rem; font-weight: 600; }

.btn-eliminar {
    border: 1px solid var(--paper-line);
    background: var(--paper-card);
    color: var(--ink-500);
    line-height: 1;
}
.btn-eliminar:hover:not(:disabled) {
    border-color: var(--danger);
    background: var(--danger);
    color: #fff;
}

/* ---------- Empty state ---------- */
.empty-state {
    text-align: center;
    padding: 3.5rem 1.5rem;
    border: 1px dashed var(--paper-line);
    border-radius: var(--radius);
    background: var(--paper-card);
}
.empty-state i { font-size: 2.25rem; color: var(--ink-500); }

/* ---------- Progreso de emparejamiento ---------- */
.match-bar { height: 6px; border-radius: 4px; background: var(--ink-100); overflow: hidden; }
.match-bar > span { display: block; height: 100%; background: var(--ok); }

/* ---------- Vista previa de la libreta ----------
   Reproduce la hoja Bim_N: son ~45 columnas, así que la tabla scrollea en
   horizontal y el nombre del alumno queda fijo a la izquierda (si no, se
   pierde de vista a las 3 columnas y no se sabe de quién es cada nota). */
.tabla-previa-scroll {
    overflow: auto;
    max-height: 75vh;
    border: 1px solid var(--paper-line);
    border-radius: var(--radius);
    background: var(--paper-card);
}

.tabla-previa {
    border-collapse: separate;
    border-spacing: 0;
    font-size: .8125rem;
    margin: 0;
}

.tabla-previa th,
.tabla-previa td {
    border-right: 1px solid var(--paper-line);
    border-bottom: 1px solid var(--paper-line);
    padding: .35rem .5rem;
    white-space: nowrap;
}

.tabla-previa thead th {
    position: sticky;
    background: var(--ink-100);
    color: var(--ink-900);
    font-weight: 600;
    vertical-align: bottom;
    z-index: 2;
}
.tabla-previa thead tr:first-child th { top: 0; }
.tabla-previa thead tr:last-child th { top: 2rem; }

.tabla-previa .th-area {
    text-align: center;
    font-family: var(--font-display);
    background: var(--ink-700);
    color: #fff;
    border-right-color: rgba(255, 255, 255, .25);
}

/* Los nombres de competencia son largos; se giran para no hacer la tabla
   inmanejable. El texto completo va en el title del <th>. */
.tabla-previa .th-curso {
    height: 11rem;
    padding: .35rem .15rem;
    vertical-align: bottom;
}
.tabla-previa .th-curso > span {
    display: block;
    writing-mode: vertical-rl;
    transform: rotate(180deg);
    max-height: 10rem;
    overflow: hidden;
    text-overflow: ellipsis;
    font-weight: 500;
}
.tabla-previa .th-curso.es-valoracion {
    background: var(--gold-soft);
    color: var(--gold);
}

.tabla-previa .col-orden,
.tabla-previa .col-alumno {
    position: sticky;
    z-index: 3;
    background: var(--paper-card);
    text-align: left;
}
.tabla-previa .col-orden { left: 0; width: 2.5rem; text-align: right; font-family: var(--font-mono); color: var(--ink-500); }
.tabla-previa .col-alumno { left: 2.5rem; min-width: 16rem; border-right: 2px solid var(--paper-line); }
.tabla-previa thead .col-orden,
.tabla-previa thead .col-alumno { background: var(--ink-100); z-index: 4; }

.tabla-previa tbody tr:hover td { background: var(--paper); }
.tabla-previa tbody tr:hover .col-orden,
.tabla-previa tbody tr:hover .col-alumno { background: var(--ink-100); }

.tabla-previa .celda-nota {
    text-align: center;
    font-family: var(--font-mono);
    font-weight: 500;
    min-width: 2.5rem;
}
/* Vacía = el docente no puso nota, o es una columna que calcula Excel. */
.tabla-previa .celda-nota.esta-vacia { background: repeating-linear-gradient(
    45deg, transparent, transparent 4px, var(--paper) 4px, var(--paper) 8px); }

/* Corregible a mano: las notas de asignatura y las de entornos virtuales. Las
   columnas de valoración y las del tutor no se marcan, y por eso se distinguen
   a simple vista de las que sí se pueden tocar. */
.tabla-previa .celda-editable { cursor: pointer; }
.tabla-previa .celda-editable:hover,
.tabla-previa .celda-editable:focus-visible {
    outline: 2px solid var(--gold);
    outline-offset: -2px;
}

/* Nota cambiada a mano: tiene que verse distinta de la que vino del Registro
   Auxiliar, porque la libreta ya no coincide con el archivo del docente. */
.tabla-previa .esta-corregida {
    background: var(--gold-soft);
    font-weight: 700;
}
/* Y con un punto mientras siga sin escribirse en el .xlsm: lo que se ve en
   pantalla todavía no es lo que se descarga. */
.tabla-previa .esta-pendiente { position: relative; }
.tabla-previa .esta-pendiente::after {
    content: "";
    position: absolute;
    top: 2px;
    right: 2px;
    width: .4rem;
    height: .4rem;
    border-radius: 50%;
    background: var(--gold);
}

/* Alumno retirado a mano: la fila sigue en el archivo hasta regenerar la
   libreta, así que se muestra apagada y con el nombre tachado, no oculta —
   ocultarla haría creer que el archivo descargado ya no la trae. */
.tabla-previa tr.esta-excluida td { opacity: .55; }
.tabla-previa tr.esta-excluida .nombre-alumno { text-decoration: line-through; }

/* Fila unida a mano con otra: también desaparece al regenerar, pero sus notas
   pasan a la fila destino en vez de perderse — apagada, sin tachar. */
.tabla-previa tr.esta-unida td { opacity: .55; }
.tabla-previa tr.esta-unida .nombre-alumno { font-style: italic; }

/* El botón de retirar solo aparece al pasar por la fila: estar siempre a la
   vista en 60 filas convertiría la lista en una pared de botones rojos. En
   táctil no hay hover, así que ahí queda siempre visible. */
.tabla-previa .btn-excluir-alumno,
.tabla-previa .btn-unir-alumno { opacity: 0; }
.tabla-previa tbody tr:hover .btn-excluir-alumno,
.tabla-previa tbody tr:hover .btn-unir-alumno,
.tabla-previa .btn-excluir-alumno:focus-visible,
.tabla-previa .btn-unir-alumno:focus-visible { opacity: 1; }
@media (pointer: coarse) {
    .tabla-previa .btn-excluir-alumno,
    .tabla-previa .btn-unir-alumno { opacity: 1; }
    /* Las celdas que se corrigen con el dedo, del tamaño mínimo táctil
       (10/10/2026): eran ~48×30 px, por debajo de los botones AD/A/B/C. */
    .tabla-previa .celda-editable { min-width: 2.75rem; height: 2.75rem; }
}

/* ---------- Tabla de datos del tutor ---------- */
/* El nombre del alumno queda fijo al desplazarse a lo ancho: el bloque de
   enfoques transversales son 8 columnas y sin esto se pierde de vista a quién
   se le está poniendo la nota. */
.tabla-tutor .col-orden,
.tabla-tutor .col-alumno {
    position: sticky;
    z-index: 3;
    background: var(--paper-card);
}
.tabla-tutor .col-orden { left: 0; width: 2.5rem; text-align: right; font-family: var(--font-mono); }
.tabla-tutor .col-alumno { left: 2.5rem; min-width: 15rem; border-right: 2px solid var(--paper-line); }
.tabla-tutor thead .col-orden,
.tabla-tutor thead .col-alumno { background: var(--ink-100); z-index: 4; }
.tabla-tutor tbody tr:hover .col-orden,
.tabla-tutor tbody tr:hover .col-alumno { background: var(--ink-100); }

/* Fila de "aplicar a todos": se distingue del encabezado real para que no se
   confunda con una fila de alumno. */
.tabla-tutor .fila-aplicar td { background: var(--gold-soft); border-bottom: 2px solid var(--paper-line); }
.tabla-tutor .fila-aplicar .col-orden,
.tabla-tutor .fila-aplicar .col-alumno { background: var(--gold-soft); }

/* Valoración marcable (AD/A/B/C) en vez de un desplegable por celda. */
.valoracion-grupo { display: inline-flex; gap: 2px; white-space: nowrap; }

.valoracion-btn {
    --bs-btn-padding-x: .35rem;
    --bs-btn-padding-y: .05rem;
    --bs-btn-font-size: .75rem;
    min-width: 1.9rem;
    border: 1px solid var(--paper-line);
    background: var(--paper-card);
    color: var(--ink-500);
    font-family: var(--font-mono);
    font-weight: 500;
}
.valoracion-btn:hover { border-color: var(--ink-500); color: var(--ink-900); background: var(--paper); }
.valoracion-btn-vacio { color: var(--ink-500); } /* #9aa3b5 daba ≈2,7:1 (10/10/2026) */

.btn-check:checked + .valoracion-btn {
    background: var(--ink-900);
    border-color: var(--ink-900);
    color: #fff;
}
.btn-check:checked + .valoracion-btn-vacio {
    background: var(--paper-line);
    border-color: var(--paper-line);
    color: var(--ink-700);
}
.btn-check:focus-visible + .valoracion-btn { box-shadow: 0 0 0 .2rem rgba(61, 84, 134, .3); }

/* Borrador propuesto de conclusión descriptiva: se distingue del cuadro real
   para que no se confunda con lo que el tutor ya escribió. */
.borrador-propuesto {
    border-left: 3px solid var(--gold);
    background: var(--gold-soft);
    border-radius: 4px;
    padding: .5rem .6rem;
    max-width: 46rem;
}
.borrador-propuesto .borrador-texto { line-height: 1.45; }

/* ---------- Encabezado de página (componente compartido) ----------
   Título + contexto + acciones. Antes cada plantilla lo armaba a mano con
   pequeñas variaciones; este es el único patrón. */
.encabezado-pagina {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    gap: .75rem 1.5rem;
    flex-wrap: wrap;
    margin-bottom: 1.5rem;
}
.encabezado-pagina .contexto-pagina {
    display: flex;
    align-items: center;
    gap: .5rem;
    flex-wrap: wrap;
    color: var(--ink-500);
    font-size: .875rem;
}

/* Grupo de acciones: la primaria (btn-gold) se distingue sola; las demás son
   outline. En pantallas chicas se apilan a lo ancho en vez de desbordar. */
.acciones-pagina {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: .5rem;
    flex-wrap: wrap;
}
@media (max-width: 575.98px) {
    .acciones-pagina { justify-content: stretch; }
    .acciones-pagina > .btn,
    .acciones-pagina > form,
    .acciones-pagina > form > .btn { flex: 1 1 100%; }
    .acciones-pagina > form > .btn { width: 100%; }
}

/* Por qué una acción está bloqueada: visible siempre, no solo en el title
   (el title no existe en táctil ni con teclado). */
.motivo-bloqueo {
    font-size: .8125rem;
    color: var(--warn);
    display: flex;
    align-items: baseline;
    gap: .35rem;
    max-width: 26rem;
}

/* ---------- Guía de pasos del flujo ----------
   Los siete pasos de una consolidación: datos → subir → revisar →
   generar → vista previa → datos del tutor → descargar.

   Es lo que responde "¿en qué voy y qué falta?" sin leer nada. El
   número va arriba y el rótulo debajo, unidos por una línea, porque
   con siete pasos la versión en fila (número + texto al lado) no
   entra en un portátil sin desplazarse.

   En móvil se convierte en una sola línea de puntos con el rótulo
   del paso actual: siete rótulos apilados ocuparían la pantalla
   entera antes de llegar al contenido. */
.guia-pasos {
    display: flex;
    list-style: none;
    padding: 1.125rem .75rem .875rem;
    margin: 0 0 1.25rem;
    background: var(--paper-card);
    border: 1px solid var(--paper-line);
    border-radius: var(--radius);
    box-shadow: var(--shadow-sm);
    counter-reset: paso;
}
.guia-pasos li {
    position: relative;
    flex: 1 1 0;
    min-width: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: .45rem;
    font-size: .75rem;
    line-height: 1.25;
    text-align: center;
    color: var(--ink-500);
}
/* La línea que une los pasos pasa POR DETRÁS de los círculos
   (z-index) y no se dibuja antes del primero. */
.guia-pasos li::before {
    content: "";
    position: absolute;
    top: .875rem;
    right: 50%;
    left: -50%;
    height: 2px;
    background: var(--paper-line);
}
.guia-pasos li:first-child::before { display: none; }
.guia-pasos li.es-hecho::before,
.guia-pasos li.es-actual::before { background: var(--brand); }

.guia-pasos .paso-num {
    position: relative;
    z-index: 1;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.75rem;
    height: 1.75rem;
    border-radius: 50%;
    background: var(--paper-card);
    border: 2px solid var(--paper-line);
    font-family: var(--font-mono);
    font-size: .75rem;
    flex-shrink: 0;
}
.guia-pasos .paso-texto { display: block; }

.guia-pasos .es-hecho { color: var(--brand); }
.guia-pasos .es-hecho .paso-num {
    border-color: var(--brand);
    background: var(--brand);
    color: #fff;
}
.guia-pasos .es-actual { color: var(--ink-900); font-weight: 600; }
.guia-pasos .es-actual .paso-num {
    border-color: var(--brand);
    background: var(--brand);
    color: #fff;
    box-shadow: 0 0 0 4px var(--brand-soft);
}

@media (max-width: 767.98px) {
    .guia-pasos { padding: .875rem .5rem; }
    .guia-pasos li { font-size: 0; gap: 0; }
    .guia-pasos .paso-num { width: 1.4rem; height: 1.4rem; font-size: .68rem; }
    .guia-pasos li::before { top: .7rem; }
    /* Solo el paso actual conserva su rótulo, debajo. */
    .guia-pasos .es-actual { font-size: .72rem; gap: .4rem; }
}

/* ---------- Panel de advertencias ----------
   log_errores son decisiones del sistema que hay que poder revisar, no un
   volcado: cabecera con conteo y lista con aire. Sigue siendo texto plano
   por dentro (pendiente conocido: separarlas por tipo en el backend). */
.panel-advertencias {
    border: 1px solid var(--warn);
    border-left-width: 4px;
    border-radius: var(--radius);
    background: var(--warn-soft);
    margin-bottom: 1.5rem;
}
.panel-advertencias > summary {
    cursor: pointer;
    padding: .75rem 1rem;
    font-weight: 600;
    color: var(--warn);
    list-style: none;
    display: flex;
    align-items: center;
    gap: .5rem;
}
.panel-advertencias > summary::-webkit-details-marker { display: none; }
.panel-advertencias > summary .bi-chevron-down { margin-left: auto; transition: transform .15s ease; }
.panel-advertencias[open] > summary .bi-chevron-down { transform: rotate(180deg); }
.panel-advertencias .lista-advertencias {
    margin: 0;
    padding: 0 1rem 1rem 2.4rem;
    font-size: .875rem;
}
.panel-advertencias .lista-advertencias li { margin-bottom: .35rem; }
.panel-advertencias .lista-advertencias li:last-child { margin-bottom: 0; }

/* ---------- Contraste ----------
   El dorado puro sobre fondo dorado suave no llega a 3:1; para texto se usa
   esta tinta dorada oscura (≈5.8:1 sobre --gold-soft). */
.tabla-previa .th-curso.es-valoracion {
    background: var(--gold-soft);
    color: #6b5310;
}

/* ---------- Tamaños táctiles ----------
   Los botones AD/A/B/C median ~27×20 px; con dedo son un sorteo. En punteros
   gruesos se llevan al mínimo táctil sin tocar el diseño de escritorio. */
@media (pointer: coarse) {
    .valoracion-btn {
        min-width: 2.5rem;
        min-height: 2.5rem;
        --bs-btn-font-size: .875rem;
    }
    .btn-eliminar { min-width: 2.75rem; min-height: 2.75rem; }
}

/* En móvil, la columna fija del alumno no puede comerse 2/3 de la pantalla:
   se acorta y el nombre se trunca con elipsis (el título completo sigue en
   la celda, solo deja de empujar la tabla). */
@media (max-width: 575.98px) {
    .tabla-previa .col-alumno,
    .tabla-tutor .col-alumno {
        min-width: 9.5rem;
        max-width: 9.5rem;
        overflow: hidden;
        text-overflow: ellipsis;
    }
}

/* Marcador de campo obligatorio: el color solo no basta (accesibilidad),
   por eso es un asterisco con texto de apoyo en la cabecera del formulario. */
.campo-obligatorio { color: var(--danger); font-weight: 700; }

/* ---------- Utilidades ---------- */
.text-ink-500 { color: var(--ink-500); }
.bg-paper { background: var(--paper); }

@media (prefers-reduced-motion: reduce) {
    * { transition: none !important; animation: none !important; }
}


/* ============================================================
   COMPONENTES DE LA PLATAFORMA
   ============================================================
   Todo lo de aquí abajo se agregó al convertir el consolidador en
   plataforma. Antes de inventar una variante en una pantalla
   nueva, buscar aquí: el objetivo del sistema es que dos
   pantallas distintas no tengan dos tarjetas distintas.
   ============================================================ */

/* ---------- Pantallas de acceso (login, recuperación) ----------
   Dos columnas en escritorio: la marca a la izquierda sobre azul
   institucional, el formulario a la derecha sobre blanco. En
   móvil la marca se reduce a una cabecera para que el formulario
   quede a la vista sin desplazarse. */
.acceso-pantalla { min-height: 100vh; display: grid; grid-template-columns: 1fr; }

.acceso-marca {
    background: var(--ink-900);
    color: #fff;
    padding: 2rem 1.5rem;
    display: flex; flex-direction: column; justify-content: center;
}
.acceso-marca .marca-nombre, .acceso-marca .marca-sub { color: #fff; }
.acceso-marca .marca-sub { color: rgba(255,255,255,.6); }
.acceso-marca .marca-escudo { background: rgba(255,255,255,.12); }

.acceso-lema {
    font-family: var(--font-display);
    font-size: 1.6rem;
    font-weight: 600;
    line-height: 1.3;
    margin: 1.75rem 0 1.25rem;
    max-width: 20ch;
}

.acceso-roles { list-style: none; padding: 0; margin: 0; display: grid; gap: .625rem; }
.acceso-roles li {
    display: flex; align-items: center; gap: .75rem;
    padding: .75rem .875rem;
    background: rgba(255,255,255,.07);
    border: 1px solid rgba(255,255,255,.1);
    border-radius: var(--radius);
}
.acceso-roles i { font-size: 1.15rem; color: #fff; opacity: .85; }
.acceso-roles strong { display: block; font-size: .9rem; }
.acceso-roles span { font-size: .8rem; color: rgba(255,255,255,.65); }

.acceso-formulario {
    display: flex; align-items: center; justify-content: center;
    padding: 2rem 1.25rem 3rem;
    background: var(--paper-card);
}
.acceso-caja { width: 100%; max-width: 23rem; }

/* El ojo que muestra la contraseña va DENTRO del campo, y es un
   <button> de verdad: con un icono suelto no se puede llegar con
   teclado ni anunciarlo a un lector de pantalla. */
.campo-contrasena { position: relative; }
.campo-contrasena .form-control { padding-inline-end: 2.75rem; }
.btn-ver-contrasena {
    position: absolute; inset-inline-end: .25rem; top: 50%;
    transform: translateY(-50%);
    border: 0; background: transparent;
    color: var(--ink-500);
    padding: .35rem .5rem;
    border-radius: var(--radius-sm);
    line-height: 1;
}
.btn-ver-contrasena:hover { color: var(--ink-900); }

@media (min-width: 992px) {
    .acceso-pantalla { grid-template-columns: 22rem 1fr; }
    .acceso-marca { padding: 3rem 2.25rem; }
    .acceso-formulario { padding: 3rem; }
}

/* ---------- Tarjetas de resumen (fila de indicadores) ----------
   Cifra grande, rótulo arriba, icono en cuadro de color suave y
   un enlace abajo. Son datos accionables, no adorno: cada una
   lleva a la pantalla donde se actúa sobre ese número. */
.rejilla-resumen {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
    gap: 1rem;
}

.tarjeta-resumen {
    background: var(--paper-card);
    border: 1px solid var(--paper-line);
    border-radius: var(--radius);
    padding: 1.125rem;
    box-shadow: var(--shadow-sm);
    display: flex; flex-direction: column; gap: .5rem;
}
.tarjeta-resumen .resumen-fila { display: flex; align-items: flex-start; gap: .75rem; }
.tarjeta-resumen .resumen-rotulo {
    font-size: .82rem; color: var(--ink-500); font-weight: 500;
    display: block; margin-bottom: .15rem;
}
.tarjeta-resumen .resumen-cifra {
    font-family: var(--font-mono);
    font-size: 1.85rem; font-weight: 500;
    color: var(--ink-900); line-height: 1.05;
}
.tarjeta-resumen .resumen-nota { font-size: .8rem; color: var(--ink-500); }
.tarjeta-resumen .resumen-enlace {
    font-size: .82rem; font-weight: 500; color: var(--brand);
    text-decoration: none; margin-top: auto;
}
.tarjeta-resumen .resumen-enlace:hover { text-decoration: underline; }

.resumen-icono {
    margin-inline-start: auto;
    width: 2.5rem; height: 2.5rem;
    flex: 0 0 auto;
    display: inline-flex; align-items: center; justify-content: center;
    border-radius: 10px;
    font-size: 1.15rem;
}
.icono-azul    { background: var(--brand-soft);          color: var(--brand); }
.icono-verde   { background: var(--acento-verde-soft);   color: var(--acento-verde); }
.icono-naranja { background: var(--acento-naranja-soft); color: var(--acento-naranja); }
.icono-morado  { background: var(--acento-morado-soft);  color: var(--acento-morado); }

/* ---------- Panel: la caja estándar de contenido ----------
   Sustituye a las tarjetas anidadas. Un panel tiene cabecera con
   título y (opcional) un enlace a la derecha, y un cuerpo. */
.panel {
    background: var(--paper-card);
    border: 1px solid var(--paper-line);
    border-radius: var(--radius);
    box-shadow: var(--shadow-sm);
    overflow: hidden;
}
.panel + .panel { margin-top: 1rem; }

.panel-cabecera {
    display: flex; align-items: center; gap: .75rem;
    padding: .875rem 1.125rem;
    border-bottom: 1px solid var(--paper-line);
}
.panel-cabecera h2, .panel-cabecera h3 {
    font-size: .975rem; font-weight: 600; margin: 0;
    font-family: var(--font-body); color: var(--ink-900);
}
.panel-cabecera .panel-enlace {
    margin-inline-start: auto;
    font-size: .82rem; color: var(--brand); text-decoration: none;
    white-space: nowrap;
}
.panel-cabecera .panel-enlace:hover { text-decoration: underline; }

.panel-cuerpo { padding: 1.125rem; }
.panel-cuerpo-liso { padding: 0; }

/* ---------- Lista de elementos (cursos, comunicados, lotes) ----------
   Filas separadas por línea, no tarjetas dentro de tarjetas. En
   móvil se apilan: el bloque derecho baja debajo del texto. */
.lista-filas { list-style: none; margin: 0; padding: 0; }
.lista-filas > li { border-bottom: 1px solid var(--paper-line); }
.lista-filas > li:last-child { border-bottom: 0; }

.fila-elemento {
    display: flex; align-items: center; gap: .875rem;
    padding: .8rem 1.125rem;
    text-decoration: none;
    color: inherit;
}
a.fila-elemento:hover { background: var(--paper); }

.fila-icono {
    width: 2.25rem; height: 2.25rem;
    flex: 0 0 auto;
    display: inline-flex; align-items: center; justify-content: center;
    border-radius: var(--radius-sm);
    background: var(--brand-soft); color: var(--brand);
    font-size: 1rem;
}
.fila-texto { min-width: 0; flex: 1 1 auto; }
.fila-texto strong { display: block; font-size: .9rem; font-weight: 600; color: var(--ink-900); }
.fila-texto span { display: block; font-size: .8rem; color: var(--ink-500); }
.fila-lado { margin-inline-start: auto; text-align: end; flex: 0 0 auto; }

@media (max-width: 575.98px) {
    .fila-elemento { flex-wrap: wrap; }
    .fila-lado { margin-inline-start: 3.125rem; text-align: start; width: 100%; }
}

/* ---------- Etiquetas de estado ----------
   `.seal` (más arriba) sigue sirviendo para el estado del lote.
   `.etiqueta` es la versión menor, para dentro de las filas. */
.etiqueta {
    display: inline-flex; align-items: center; gap: .3rem;
    padding: .18rem .5rem;
    border-radius: 6px;
    font-size: .75rem;
    font-weight: 600;
    white-space: nowrap;
}
.etiqueta-ok      { background: var(--ok-soft);      color: var(--ok); }
.etiqueta-aviso   { background: var(--warn-soft);    color: var(--warn); }
.etiqueta-error   { background: var(--danger-soft);  color: var(--danger); }
.etiqueta-neutra  { background: var(--ink-100);      color: var(--ink-700); }
.etiqueta-info    { background: var(--brand-soft);   color: var(--brand); }

/* ---------- Tablas ---------- */
.tabla-datos { width: 100%; margin: 0; font-size: .9rem; }
.tabla-datos thead th {
    background: var(--paper);
    border-bottom: 1px solid var(--paper-line);
    font-size: .76rem;
    font-weight: 600;
    letter-spacing: .04em;
    text-transform: uppercase;
    color: var(--ink-500);
    padding: .625rem .875rem;
    white-space: nowrap;
}
.tabla-datos tbody td {
    padding: .7rem .875rem;
    border-bottom: 1px solid var(--paper-line);
    vertical-align: middle;
    color: var(--ink-900);
}
.tabla-datos tbody tr:last-child td { border-bottom: 0; }
.tabla-datos tbody tr:hover td { background: var(--paper); }

/* Una tabla ancha scrollea DENTRO de su contenedor. Que el <body>
   no se desplace en horizontal es una regla del sistema: si lo
   hiciera, la barra lateral y la superior se irían con él. */
.tabla-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }

/* ---------- Estado vacío ----------
   Nunca una tabla vacía a secas: se dice qué falta y cuál es el
   siguiente paso, porque quien llega aquí suele estar perdido. */
.estado-vacio {
    text-align: center;
    padding: 2.5rem 1.25rem;
    color: var(--ink-500);
}
.estado-vacio .vacio-icono {
    width: 3.25rem; height: 3.25rem;
    margin: 0 auto .875rem;
    display: flex; align-items: center; justify-content: center;
    background: var(--paper);
    border: 1px solid var(--paper-line);
    border-radius: 50%;
    font-size: 1.4rem;
    color: var(--ink-500);
}
.estado-vacio h3 {
    font-family: var(--font-body);
    font-size: 1rem; font-weight: 600;
    color: var(--ink-900);
    margin-bottom: .35rem;
}
.estado-vacio p { font-size: .875rem; max-width: 38ch; margin: 0 auto 1rem; }

/* ---------- Alertas ---------- */
.alert {
    border-radius: var(--radius);
    border-width: 1px;
    font-size: .9rem;
    padding: .875rem 1rem;
}
.alert-info    { background: var(--brand-soft);   border-color: #cfdefb; color: #1e40af; }
.alert-success { background: var(--ok-soft);      border-color: #b7e2c7; color: #14532d; }
.alert-warning { background: var(--warn-soft);    border-color: #f6dcb8; color: #78350f; }
.alert-danger  { background: var(--danger-soft);  border-color: #f5c2c2; color: #7f1d1d; }
.alert strong { font-weight: 600; }

/* ---------- Estado de carga ----------
   Se usa junto a `data-texto-cargando` (app.js) en los botones que
   disparan algo lento: subir archivos, generar la libreta. */
.cargando-punto {
    display: inline-block;
    width: .5rem; height: .5rem;
    border-radius: 50%;
    background: currentColor;
    animation: latido 1s ease-in-out infinite;
}
@keyframes latido { 0%, 100% { opacity: .25; } 50% { opacity: 1; } }

/* ---------- Paginación ---------- */
.pagination { --bs-pagination-border-radius: var(--radius-sm); font-size: .875rem; }
.page-link { color: var(--ink-700); border-color: var(--paper-line); }
.page-item.active .page-link { background: var(--brand); border-color: var(--brand); }

/* ---------- Rejillas ---------- */
.rejilla-dos {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(19rem, 1fr));
    gap: 1rem;
    align-items: start;
}
.rejilla-tres {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
    gap: 1rem;
    align-items: start;
}

/* ---------- Utilidades de texto ----------
   Existen para que las plantillas no lleven `style="..."` inline. Son
   pocas y con nombre por INTENCIÓN, no por medida: `texto-menor` sirve
   igual si mañana la escala cambia, mientras que una clase llamada
   `fs-83` habría que renombrarla. */
.texto-menor    { font-size: .9rem; }
.texto-chico    { font-size: .85rem; }
.texto-mini     { font-size: .78rem; }
.texto-ok       { color: var(--ok); }
.texto-marca    { color: var(--brand); }

/* Anchos de las pantallas de un solo formulario. Una línea de texto de
   más de ~70 caracteres cansa de leer, y un formulario a 1320 px deja
   los campos separados de sus etiquetas. */
.ancho-formulario { max-width: 38rem; }
.ancho-estrecho   { max-width: 32rem; }

/* Icono grande de la zona de arrastre. */
.dropzone-icono { font-size: 1.75rem; color: var(--ink-500); }

/* Variante del icono de estado vacío para un resultado correcto. */
.vacio-icono-ok {
    color: var(--ok);
    background: var(--ok-soft);
    border-color: #b7e2c7;
}

/* Caja de solo lectura para un dato que se muestra pero no se edita
   (el correo en el perfil). No es un input deshabilitado a propósito:
   un campo apagado invita a intentar escribir en él. */
.dato-fijo {
    display: flex; align-items: center; gap: .5rem;
    padding: .55rem .75rem;
    background: var(--paper);
    border: 1px solid var(--paper-line);
    border-radius: var(--radius-sm);
}

/* El texto del panel de "Generar libreta" no baja de este ancho: por
   debajo, el motivo del bloqueo se parte en cuatro líneas y el botón
   se le sube encima. */
.texto-panel-accion { min-width: 14rem; }

/* ============================================================
   PORTAL DEL ADMINISTRADOR
   ============================================================ */

/* ---------- Barra de filtros de un listado ----------
   Buscador + filtros + acción, en una sola línea que se apila
   en móvil. Se repite en profesores, padres y alumnos, así que
   vive aquí y no en cada plantilla. */
.barra-filtros {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .5rem;
    margin-bottom: 1rem;
}
.barra-filtros .campo-buscar { flex: 1 1 16rem; min-width: 0; position: relative; }
.barra-filtros .campo-buscar .form-control { padding-inline-start: 2.25rem; }
.barra-filtros .campo-buscar .bi {
    position: absolute;
    inset-inline-start: .75rem;
    top: 50%;
    transform: translateY(-50%);
    color: var(--ink-500);
    pointer-events: none;
}
.barra-filtros .form-select { flex: 0 1 12rem; }
.barra-filtros .acciones { margin-inline-start: auto; display: flex; gap: .5rem; }

@media (max-width: 575.98px) {
    .barra-filtros .acciones { margin-inline-start: 0; width: 100%; }
    .barra-filtros .acciones .btn { flex: 1 1 auto; }
}

/* ---------- Persona en una fila de listado ----------
   Avatar de iniciales + nombre + correo. Igual en profesores y
   en padres. */
.persona { display: flex; align-items: center; gap: .625rem; min-width: 0; }
.persona .avatar-iniciales { width: 2.25rem; height: 2.25rem; font-size: .8rem; }
.persona .persona-datos { min-width: 0; }
.persona strong { display: block; font-size: .9rem; color: var(--ink-900); }
.persona span {
    display: block;
    font-size: .8rem;
    color: var(--ink-500);
    overflow-wrap: anywhere;
}

/* ---------- Grupo de casillas de permiso ----------
   Cada permiso lleva su explicación debajo: sin ella, "Puede
   corregir notas a mano" no dice qué implica conceder eso. */
.grupo-permisos {
    border: 1px solid var(--paper-line);
    border-radius: var(--radius);
    padding: .875rem 1rem;
    margin-bottom: .875rem;
}
.grupo-permisos > legend {
    font-size: .75rem;
    font-weight: 600;
    letter-spacing: .06em;
    text-transform: uppercase;
    color: var(--ink-500);
    float: none;
    width: auto;
    margin-bottom: .625rem;
}
.grupo-permisos .form-check { margin-bottom: .625rem; }
.grupo-permisos .form-check:last-child { margin-bottom: 0; }
.grupo-permisos .form-check-label { font-size: .9rem; font-weight: 500; }
.grupo-permisos .form-text { margin-top: .1rem; }

/* ---------- Lista de casillas larga (asignar cursos) ----------
   Con 24 cursos por sección, una columna obliga a desplazarse
   muchísimo; en pantallas anchas se reparte en columnas. */
.casillas-columnas { column-width: 17rem; column-gap: 1.5rem; }
.casillas-columnas .form-check {
    break-inside: avoid;
    margin-bottom: .4rem;
}

/* ---------- Desplegable de casillas ----------
   Un <details> por grupo (asignar cursos: uno por sección). La cabecera
   resume lo marcado para que se lea el reparto sin abrir nada; app.js la
   mantiene al día. Sin JS funciona igual, solo que el resumen se queda con
   el valor con que cargó la página. */
.desplegable-casillas {
    border: 1px solid var(--paper-line);
    border-radius: var(--radius-sm);
    background: var(--paper-card);
}
.desplegable-casillas + .desplegable-casillas { margin-top: .5rem; }
.desplegable-casillas > summary {
    cursor: pointer;
    list-style: none;
    display: flex;
    align-items: center;
    gap: .625rem;
    padding: .65rem .875rem;
    font-weight: 600;
    color: var(--ink-900);
}
.desplegable-casillas > summary::-webkit-details-marker { display: none; }
.desplegable-casillas > summary:hover { background: var(--paper); border-radius: var(--radius-sm); }
.desplegable-casillas > summary .etiqueta { white-space: normal; }
.desplegable-casillas > summary .bi-chevron-down {
    margin-left: auto;
    color: var(--ink-500);
    transition: transform .15s ease;
}
.desplegable-casillas[open] > summary .bi-chevron-down { transform: rotate(180deg); }
.desplegable-cuerpo {
    padding: .75rem .875rem .5rem;
    border-top: 1px solid var(--paper-line);
}

/* ---------- Aviso en línea dentro de un panel ---------- */
.nota-panel {
    display: flex;
    gap: .625rem;
    padding: .75rem .875rem;
    background: var(--brand-soft);
    border-radius: var(--radius-sm);
    font-size: .85rem;
    color: #1e40af;
}
.nota-panel i { flex: 0 0 auto; margin-top: .1rem; }

/* ============================================================
   ASISTENCIA
   ============================================================
   La pantalla de pasar lista tiene que resolverse en un minuto
   y con una mano desde el celular. Todos empiezan presentes y
   el profesor solo toca las excepciones. */

.lista-asistencia { list-style: none; margin: 0; padding: 0; }
.lista-asistencia > li { border-bottom: 1px solid var(--paper-line); }
.lista-asistencia > li:last-child { border-bottom: 0; }

.fila-asistencia {
    display: flex;
    align-items: center;
    gap: .875rem;
    padding: .7rem 1.125rem;
}
/* La fila entera cambia de tono según el estado, no solo el botón:
   al repasar 30 alumnos hay que poder ver las excepciones de un
   vistazo sin leer cada control. */
.fila-asistencia.es-tardanza { background: var(--warn-soft); }
.fila-asistencia.es-falta { background: var(--danger-soft); }
.fila-asistencia.es-justificada,
.fila-asistencia.es-tardanza_justificada { background: var(--brand-soft); }

.fila-asistencia .orden {
    flex: 0 0 1.5rem;
    font-family: var(--font-mono);
    font-size: .78rem;
    color: var(--ink-500);
    text-align: end;
}
.fila-asistencia .nombre {
    flex: 1 1 auto;
    min-width: 0;
    font-size: .9rem;
    font-weight: 500;
    color: var(--ink-900);
}
.fila-asistencia .nombre small {
    display: block;
    font-weight: 400;
    color: var(--ink-500);
}

/* Los cuatro estados como botones grandes. Un <select> obligaría a
   abrir el menú 30 veces; aquí es un toque. */
.estados-grupo { display: inline-flex; gap: 3px; flex: 0 0 auto; }

.estado-btn {
    width: 2.4rem;
    height: 2.4rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--paper-line);
    border-radius: var(--radius-sm);
    background: var(--paper-card);
    color: var(--ink-500);
    font-size: 1rem;
    cursor: pointer;
    transition: background .1s ease, color .1s ease, border-color .1s ease;
}
.estado-btn:hover { border-color: var(--ink-500); color: var(--ink-900); }

.btn-check:checked + .estado-btn { color: #fff; }
.btn-check:checked + .estado-btn.estado-presente { background: var(--ok); border-color: var(--ok); }
.btn-check:checked + .estado-btn.estado-tardanza { background: var(--warn); border-color: var(--warn); }
.btn-check:checked + .estado-btn.estado-falta { background: var(--danger); border-color: var(--danger); }
.btn-check:checked + .estado-btn.estado-justificada,
.btn-check:checked + .estado-btn.estado-tardanza_justificada { background: var(--brand); border-color: var(--brand); }
.btn-check:focus-visible + .estado-btn {
    outline: 3px solid rgba(37, 99, 235, .45);
    outline-offset: 2px;
}

/* En móvil la fila se aprieta para que los cuatro botones y el
   nombre entren sin desplazamiento horizontal. */
@media (max-width: 575.98px) {
    .fila-asistencia { padding: .6rem .75rem; gap: .5rem; }
    .fila-asistencia .orden { display: none; }
    .estado-btn { width: 2.6rem; height: 2.6rem; }
}
@media (pointer: coarse) {
    .estado-btn { min-width: 2.75rem; min-height: 2.75rem; }
}

/* ---------- Barra de acción fija al pie ----------
   Guardar tiene que estar siempre a la vista: con 30 alumnos, un
   botón al final de la lista obliga a desplazarse hasta abajo
   para algo que se hace en cada visita. */
.barra-guardar {
    position: sticky;
    bottom: 0;
    z-index: 10;
    display: flex;
    align-items: center;
    gap: .75rem;
    padding: .75rem 1.125rem;
    background: var(--paper-card);
    border-top: 1px solid var(--paper-line);
    box-shadow: 0 -4px 12px rgba(20, 49, 92, .06);
}
.barra-guardar .btn { flex: 0 0 auto; }
.barra-guardar .resumen-vivo {
    font-size: .83rem;
    color: var(--ink-500);
    margin-inline-end: auto;
}

/* ---------- Selector de día ---------- */
.selector-dia { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; }
.selector-dia .form-control, .selector-dia .form-select { flex: 0 1 auto; width: auto; }

/* ============================================================
   COMUNICADOS
   ============================================================
   Lista cronológica limpia. NO imita un chat a propósito: es
   comunicación formal del colegio, no una conversación. */

.comunicado-fila {
    display: flex;
    gap: .875rem;
    padding: .95rem 1.125rem;
    text-decoration: none;
    color: inherit;
}
a.comunicado-fila:hover { background: var(--paper); }

/* Punto de no leído. Va a la izquierda y ocupa sitio siempre
   (visibility, no display) para que las filas no se desalineen
   al ir marcándolas. */
.comunicado-punto {
    flex: 0 0 auto;
    width: .5rem; height: .5rem;
    margin-top: .45rem;
    border-radius: 50%;
    background: var(--brand);
    visibility: hidden;
}
.comunicado-fila.no-leido .comunicado-punto { visibility: visible; }
.comunicado-fila.no-leido .comunicado-titulo { font-weight: 600; }

.comunicado-cuerpo { min-width: 0; flex: 1 1 auto; }
.comunicado-titulo {
    display: block;
    font-size: .95rem;
    color: var(--ink-900);
    margin-bottom: .15rem;
}
.comunicado-extracto {
    display: block;
    font-size: .85rem;
    color: var(--ink-500);
    line-height: 1.4;
    margin-bottom: .35rem;
}
.comunicado-meta {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .5rem;
    font-size: .78rem;
    color: var(--ink-500);
}

/* El cuerpo del comunicado ya abierto. Ancho de lectura acotado:
   una línea de 140 caracteres cansa. */
.comunicado-texto {
    font-size: .95rem;
    line-height: 1.65;
    max-width: 62ch;
    white-space: pre-line;
}

/* Barra de lectura para el autor: cuántos lo abrieron. */
.barra-lectura {
    height: 8px;
    border-radius: 4px;
    background: var(--ink-100);
    overflow: hidden;
}
.barra-lectura > span { display: block; height: 100%; background: var(--brand); }

/* ============================================================
   PORTAL DE LA FAMILIA
   ============================================================
   Pensado primero para celular: es donde lo van a abrir. Todo
   se lee sin desplazamiento horizontal y con una mano. */

/* ---------- Selector de hijo ----------
   Con dos o más hijos, una fila de tarjetas. Solo aparece si de
   verdad hay varios: con uno sería un control que no hace nada. */
.selector-hijos {
    display: flex;
    gap: .625rem;
    overflow-x: auto;
    padding-bottom: .25rem;
    margin-bottom: 1rem;
    -webkit-overflow-scrolling: touch;
}
.tarjeta-hijo {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    gap: .625rem;
    padding: .625rem .875rem;
    background: var(--paper-card);
    border: 1px solid var(--paper-line);
    border-radius: var(--radius);
    text-decoration: none;
    min-width: 11rem;
}
.tarjeta-hijo:hover { border-color: #c7d7fb; }
/* El hijo activo se marca con borde Y con fondo, no solo con
   color: tiene que distinguirse sin depender de la vista. */
.tarjeta-hijo.es-activo {
    border-color: var(--brand);
    background: var(--brand-soft);
    box-shadow: 0 0 0 1px var(--brand);
}
.tarjeta-hijo strong { display: block; font-size: .875rem; color: var(--ink-900); }
.tarjeta-hijo span { display: block; font-size: .78rem; color: var(--ink-500); }

/* ---------- Estado de hoy ----------
   Lo primero que mira un padre al abrir la aplicación. */
.estado-hoy {
    display: flex;
    align-items: center;
    gap: .875rem;
    padding: 1rem 1.125rem;
}
.estado-hoy .icono-estado {
    width: 2.75rem; height: 2.75rem;
    flex: 0 0 auto;
    display: inline-flex; align-items: center; justify-content: center;
    border-radius: 50%;
    font-size: 1.25rem;
}

/* ---------- Notas por área ----------
   La libreta agrupada, NO una tabla de 44 columnas. */
.area-notas { border-bottom: 1px solid var(--paper-line); }
.area-notas:last-child { border-bottom: 0; }
.area-titulo {
    padding: .75rem 1.125rem .35rem;
    font-size: .72rem;
    font-weight: 600;
    letter-spacing: .07em;
    text-transform: uppercase;
    color: var(--ink-500);
}
.curso-nota {
    display: flex;
    align-items: center;
    gap: .75rem;
    padding: .55rem 1.125rem;
}
.curso-nota .curso-nombre { flex: 1 1 auto; min-width: 0; font-size: .9rem; }
/* Lo que escribe el tutor va como texto, no como ficha (10/10/2026). */
.texto-conclusion,
.texto-ayuda-panel {
    margin: 0;
    padding: .35rem 1.125rem .85rem;
    font-size: .9rem;
    line-height: 1.55;
    overflow-wrap: anywhere;
}
.texto-ayuda-panel { font-size: .8rem; color: var(--ink-500); padding-top: .75rem; }
.curso-nota .curso-nombre small {
    display: block;
    font-size: .76rem;
    color: var(--ink-500);
    line-height: 1.3;
}

/* La valoración como ficha cuadrada. Lleva el texto largo al lado
   en la leyenda, porque AD/A/B/C no significa nada para quien no
   conoce la escala. */
.ficha-nota {
    flex: 0 0 auto;
    width: 2.5rem; height: 2.5rem;
    display: inline-flex; align-items: center; justify-content: center;
    border-radius: var(--radius-sm);
    font-family: var(--font-mono);
    font-weight: 500;
    font-size: .9rem;
}
.nota-ad { background: var(--ok-soft); color: var(--ok); }
.nota-a  { background: var(--brand-soft); color: var(--brand); }
.nota-b  { background: var(--warn-soft); color: var(--warn); }
.nota-c  { background: var(--danger-soft); color: var(--danger); }
.nota-otra { background: var(--ink-100); color: var(--ink-700); }

/* ---------- Historial de asistencia ---------- */
.dia-asistencia {
    display: flex;
    align-items: center;
    gap: .75rem;
    padding: .6rem 1.125rem;
    border-bottom: 1px solid var(--paper-line);
}
.dia-asistencia:last-child { border-bottom: 0; }
.dia-asistencia .fecha {
    flex: 0 0 5.5rem;
    font-size: .83rem;
    color: var(--ink-500);
}

/* ============================================================
   PÁGINAS DE ERROR (400, 403, 404, 500)
   ============================================================
   Centradas y sobrias. El número es grande pero DECORATIVO
   (`aria-hidden`): lo que la persona necesita leer es la frase
   de abajo, no el código. Un lector de pantalla que anuncie
   "cuatrocientos cuatro" no ayuda a nadie.

   Todo esto también lo usa `templates/500.html`, que es una
   página suelta sin `base.html`; por eso el bloque no depende
   de nada del armazón. */

.pagina-error {
    max-width: 34rem;
    margin: 3rem auto;
    padding: 2.5rem 1.5rem;
    text-align: center;
    background: var(--paper-card);
    border: 1px solid var(--paper-line);
    border-radius: var(--radius);
    box-shadow: var(--shadow-sm);
}

.error-codigo {
    font-family: var(--font-mono);
    font-size: 3.5rem;
    font-weight: 500;
    line-height: 1;
    color: var(--ink-100);
    margin-bottom: .5rem;
    /* Sin `user-select: none` a propósito: alguien que reporta el
       problema por WhatsApp querrá copiar el número. */
}

.pagina-error .alert { text-align: start; }

@media (max-width: 575.98px) {
    .pagina-error { margin: 1.5rem auto; padding: 2rem 1.25rem; }
    .error-codigo { font-size: 2.75rem; }
}

/* Datos del tutor: un número propuesto desde la asistencia y todavía sin
   guardar. Ver `LoteTutorView.proponer_asistencia`. */
.celda-propuesta { background: var(--brand-soft); }
