/* ============================================
   SIGA Design System
   Registro Académico Digital
   ============================================ */

/* ---- Las dos fuentes de la PORTADA (auto-hospedadas) --------------------
   ⚠️ Están en `public/fonts`, no en un CDN, y eso no es purismo: la nota de
   `--font-serif` de más abajo explica por qué esta app no descarga letras de
   un tercero —en un colegio con conexión mala, un servidor ajeno es la página
   entera esperando—. Sirviéndolas desde el mismo dominio, la razón desaparece
   y la maqueta del diseñador se puede calcar tal cual: son 80 KB, con
   `swap` para que el texto se lea desde el primer instante.

   Se usan ÚNICAMENTE dentro de `.portada`. El resto de la app sigue con el
   stack del sistema, que no descarga nada. */
@font-face {
  font-family: 'DM Sans';
  font-style: normal;
  font-weight: 100 1000;
  font-display: swap;
  src: url(/fonts/dm-sans-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'DM Sans';
  font-style: normal;
  font-weight: 100 1000;
  font-display: swap;
  src: url(/fonts/dm-sans-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'DM Serif Display';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(/fonts/dm-serif-display-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* ⚠️ La itálica va como ARCHIVO y no sintetizada. Sin ella el navegador
   inclina la redonda por su cuenta, y en una serif de display eso se ve
   exactamente como lo que es: letras torcidas. La segunda línea de cada
   título de la portada va en itálica, así que se nota en cada sección. */
@font-face {
  font-family: 'DM Serif Display';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url(/fonts/dm-serif-display-italic-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'DM Serif Display';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url(/fonts/dm-serif-display-italic-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'DM Serif Display';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(/fonts/dm-serif-display-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

:root {
  /* ---- Foreground (Text Hierarchy) ---- */
  --text-primary: #2c2c2c;           /* Tinta carbón - texto principal */
  --text-secondary: #5a5a5a;         /* Texto secundario */
  --text-tertiary: #878787;          /* Metadatos */
  --text-muted: #a8a8a8;            /* Deshabilitado/placeholder */

  /* ---- Background (Surface Elevation) ---- */
  --surface-canvas: #faf8f5;         /* Base - papel crema */
  --surface-elevated: #ffffff;       /* Elevado - papel blanco */
  --surface-input: #f5f3f0;          /* Inputs - área de escritura */
  --surface-hover: #f0ede8;          /* Hover state */

  /* ---- Border (Separation Hierarchy) ---- */
  --border-subtle: rgba(44, 44, 44, 0.08);    /* Separación suave */
  --border-standard: rgba(44, 44, 44, 0.12);  /* Separación estándar */
  --border-emphasis: rgba(44, 44, 44, 0.18);  /* Énfasis */
  --border-strong: rgba(44, 44, 44, 0.24);    /* Máximo énfasis */

  /* ---- Brand (Institutional Blue) ---- */
  --brand-primary: #1e4a7a;          /* Azul institucional */
  --brand-hover: #163854;            /* Azul hover */
  --brand-active: #0f2538;           /* Azul active */
  --brand-subtle: #e8eef5;           /* Fondo azul muy suave */

  /* ---- Tonos de acción (MOR-172) ----
     ⚠️ TODOS en la familia fría, y no es capricho: verde, rojo y ámbar están
     RESERVADOS para el dato (ver la regla de color, MOR-135). Un número en rojo
     es un niño aplazado, y si un botón también fuera rojo el ojo dejaría de
     saber cuál de los dos importa.

     Lo que la regla sí deja libre es el azul institucional —«es la afordancia
     del control, no un significado»— y de ahí salen estos tres. Se distinguen
     por MATIZ dentro de esa familia, no por semántica:

        #1e4a7a  azul      210°   crear, guardar, confirmar
        #4a3f7a  índigo    255°   lo que hace mucho de una vez
        #12596e  petróleo  193°   lo que SALE del sistema

     Los tres llevan texto blanco encima con más de 7:1 de contraste. */
  --accion-primary: #4a3f7a;         /* Índigo: copiar, promover, importar */
  --accion-hover: #3a3160;
  --documento-primary: #12596e;      /* Petróleo: descargar, emitir */
  --documento-hover: #0d4353;

  /* ---- Semantic Colors ---- */
  --success-text: #2d5a3d;           /* Verde academia */
  --success-bg: #e8f4ec;
  --success-border: #c3e6d0;

  --warning-text: #8b6914;
  --warning-bg: #fef9e7;
  --warning-border: #f9e79f;

  --danger-text: #7a2828;            /* Rojo discreto */
  --danger-bg: #fdeaea;
  --danger-border: #f5c6c6;

  /* ---- Control Tokens (Form Elements) ---- */
  --control-bg: var(--surface-input);
  --control-border: var(--border-standard);
  --control-focus-border: var(--brand-primary);
  --control-focus-ring: rgba(30, 74, 122, 0.15);

  /* ---- Spacing Scale ---- */
  --space-micro: 4px;      /* Icon gaps */
  --space-xs: 8px;         /* Component internals */
  --space-sm: 12px;
  --space-md: 16px;        /* Standard spacing */
  --space-lg: 24px;        /* Section spacing */
  --space-xl: 32px;        /* Major separation */
  --space-2xl: 48px;

  /* ---- Border Radius ---- */
  --radius-sm: 3px;        /* Inputs, buttons */
  --radius-md: 6px;        /* Cards */
  --radius-lg: 8px;        /* Modals */

  /* ---- Typography ---- */
  /* ⚠️ Fuentes del SISTEMA, sin Google Fonts, y es una decisión (MOR-149).
     
     Cargar dos familias de Google son ~120 KB y dos peticiones a un dominio
     ajeno ANTES de que se pinte una letra. En Caracas con una conexión de
     colegio eso es medio segundo de pantalla en blanco, todos los días, para
     que un título se vea distinto.
     
     El stack cubre lo mismo: cada sistema aporta su mejor cara y ninguna se
     descarga. */
  --font-serif: 'Iowan Old Style', 'Palatino Linotype', Palatino, Georgia,
                'Times New Roman', serif;
  --font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
  /* Para lo que se lee como dato y no como prosa: cédulas, carnets, códigos. */
  --font-mono: ui-monospace, 'SF Mono', SFMono-Regular, Menlo, Consolas,
               'Liberation Mono', monospace;

  /* ---- Animation ---- */
  --transition-fast: 120ms cubic-bezier(0.2, 0, 0, 1);
  --transition-base: 180ms cubic-bezier(0.2, 0, 0, 1);
}

/* ---- Base Styles ---- */

* {
  box-sizing: border-box;
}

/* El atributo `hidden` gana siempre.
   ─────────────────────────────────
   `[hidden] { display: none }` vive en la hoja del NAVEGADOR, y cualquier
   `display` que escriba el autor le gana — sin importar la especificidad,
   porque una hoja de autor siempre pisa a la del navegador. El resultado es un
   elemento marcado como oculto que se dibuja igual, sin error de ningún tipo.

   Pasaba en dos lugares y ninguno era menor:

     · los DOS ojos del campo de contraseña, superpuestos, en la pantalla que
       un profesor abre cada mañana;
     · una caja de aviso VACÍA con un botón "Reintentar", permanente, en la
       planilla de notas — la pantalla más importante del profesor.

   El `!important` es correcto acá y no es pereza: `hidden` es una afirmación
   sobre el DOM ("esto no es relevante ahora"), no una sugerencia de estilo.
   Ninguna regla de presentación debería poder contradecirla. */
[hidden] { display: none !important; }

body {
  margin: 0;
  padding: 0;
  background: var(--surface-canvas);
  color: var(--text-primary);
  font-family: var(--font-sans);
  font-size: 15px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* ---- Typography Scale ---- */

h1, h2, h3, h4, h5, h6 {
  font-family: var(--font-serif);
  font-weight: 600;
  line-height: 1.3;
  margin: 0 0 var(--space-md) 0;
  color: var(--text-primary);
  letter-spacing: -0.01em;
}

h1 {
  font-size: 28px;
  margin-bottom: var(--space-lg);
}

h2 {
  font-size: 22px;
}

h3 {
  font-size: 18px;
}

p {
  margin: 0 0 var(--space-md) 0;
}

/* ---- Links ---- */

a {
  color: var(--brand-primary);
  text-decoration: none;
  transition: color var(--transition-fast);
}

a:hover {
  color: var(--brand-hover);
}

/* ---- Navigation ---- */

/* UNA barra, fina y FIJA (MOR-184).
   ────────────────────────────────
   Daniel: «esta barra la podemos hacer más fina o fusionarla en una, no veo
   para qué 2. Y también convendría dejarla fija. En realidad el content debe
   ser el que se mueva».

   Eran dos franjas apiladas —la del producto y la del colegio— que juntas se
   comían 120 px en cada pantalla, y al bajar por una lista de doscientos
   alumnos se perdían las dos: con ellas se iban el selector de año escolar y el
   buscador, que es justo lo que hace falta a mitad de esa lista.

   ⚠️ El alto vive en UNA variable. La usa también el `sidebar`, que se posiciona
   debajo; con el número escrito en los dos lados, cambiar el `padding` de la
   barra descuadra el menú y nadie relaciona las dos cosas. Antes era un `120px`
   literal en tres reglas distintas. */
:root { --alto-barra: 52px; }

.barra-superior {
  position: sticky;
  top: 0;
  z-index: 50;
  height: var(--alto-barra);
  background: var(--surface-elevated);
  border-bottom: 1px solid var(--border-standard);
}

.barra-superior-inner {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  height: 100%;
  padding: 0 var(--space-lg);
}

.barra-superior-espacio { flex: 1; }

/* ⚠️ `.nav-institutional` ya no existe en el marcado (MOR-184): la barra del
   producto y la del colegio se fusionaron en `.barra-superior`. Todo lo que
   colgaba de aquella clase se movió acá — el logo, la sesión, el nombre — y
   olvidarse de mover una sola regla es lo que hizo que el logo saliera a tamaño
   natural, ocupando media pantalla. */

.barra-superior .container-fluid {
  display: flex;
  align-items: center;
  padding: var(--space-md) var(--space-lg);
}

/* ⚠️ Sin `margin-left: auto`: el hueco lo empuja `.barra-superior-espacio`. Con
   los dos, el buscador quedaba pegado al nombre del colegio en vez de repartido
   (MOR-184). */
.barra-superior .session-menu {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  flex-shrink: 0;
  font-family: var(--font-sans);
}

.barra-superior .navbar-brand {
  display: inline-flex;
  align-items: center;
  gap: var(--space-sm);
  font-family: var(--font-sans);
  font-size: 20px;
  font-weight: 800;
  color: var(--brand-primary);
  letter-spacing: 0.02em;
  padding: 0;
  margin: 0;
}

.barra-superior .navbar-brand:hover {
  color: var(--brand-hover);
}

.barra-superior .navbar-brand-logo {
  width: 24px;
  height: 24px;
  display: block;
}

.barra-superior .navbar-brand-text {
  line-height: 1;
}

.barra-superior .nav-link {
  color: var(--text-secondary);
  font-size: 14px;
  padding: var(--space-xs) var(--space-md);
  border-radius: var(--radius-sm);
  transition: all var(--transition-fast);
}

.barra-superior .nav-link:hover {
  color: var(--text-primary);
  background: var(--surface-hover);
}

.barra-superior .dropdown-menu {
  border: 1px solid var(--border-standard);
  border-radius: var(--radius-md);
  box-shadow: none;
  padding: var(--space-xs);
  margin-top: var(--space-micro);
  background: var(--surface-elevated);
}

.barra-superior .dropdown-item {
  color: var(--text-secondary);
  font-size: 14px;
  padding: var(--space-xs) var(--space-md);
  border-radius: var(--radius-sm);
  transition: all var(--transition-fast);
}

.barra-superior .dropdown-item:hover {
  background: var(--surface-hover);
  color: var(--text-primary);
}

.barra-superior .user-info {
  color: var(--text-secondary);
  font-size: 14px;
  margin-right: var(--space-md);
  font-family: var(--font-sans);
}

/* ---- Buttons ---- */

.btn {
  font-family: var(--font-sans);
  font-size: 14px;
  font-weight: 500;
  padding: var(--space-xs) var(--space-md);
  border-radius: var(--radius-sm);
  border: 1px solid transparent;
  transition: all var(--transition-fast);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  /* Sin esto, un `.btn` al que se le da ancho —los de ancho completo en el
     teléfono— deja su etiqueta pegada al borde izquierdo: `inline-flex` alinea
     a `flex-start` por defecto. No se notaba porque casi todos los botones se
     miden por su contenido. Un `input[type=submit]` centra solo; un `<a class="btn">`
     no, y por eso convivían dos botones distintos en la misma columna. */
  justify-content: center;
  gap: var(--space-xs);

  /* ⚠️ Alto FIJO, o un `<input type=submit>` y un `<a class="btn">` no miden
     igual y quedan desparejos en la misma fila (QA de Daniel).

     El `<a>` lleva un SVG de 16 px adentro, que estira su caja más allá de la
     altura del texto; el `input` no puede contener nada, así que su alto sale
     solo de la tipografía. Con el mismo `padding` daban dos alturas distintas.

     `line-height: 1` los iguala en la base y `min-height` fija el resultado.
     Y el `input` necesita además que se le quiten los estilos del sistema. */
  line-height: 1;
  min-height: 36px;
  -webkit-appearance: none;
  appearance: none;

  /* ⚠️ Y por eso mismo el texto NO se parte.
     Con `line-height: 1` y un alto fijo de 36 px, un botón de dos palabras
     —«Copiar de otro año», «Emitir y descargar»— partía en dos renglones y la
     segunda línea se salía por debajo del borde. Se veía en el catálogo, en
     cuanto los seis tonos quedaron uno encima del otro.
     ⚠️ En el teléfono se revierte a `normal` más abajo, y ahí es correcto: el
     botón mide 56 px de alto y sí puede envolver. */
  white-space: nowrap;
}

/* ⚠️ `.btn-primary` SE RETIRÓ, y no es un renombre.
   ─────────────────────────────────────────────────
   Al pasar el alta al verde —«el verde significa: esto queda guardado»—, el
   azul se quedó sin trabajo. Y «primario» nunca fue un trabajo: es jerarquía
   visual, justo lo que MOR-172 reemplazó por QUÉ HACE el botón.

   Los seis que quedan, y cada uno con su verbo:

       guardar     verde       queda guardado    [+ Agregar] [Crear] [Guardar]
       accion      índigo      mucho de una vez  [Copiar de otro año]
       documento   petróleo    algo sale         [Descargar] [Emitir]
       ver         azul suave  mirar sin tocar   [Ver]
       secondary   neutro      volver atrás      [Volver] [Cancelar]
       danger      sin tono    destruye          [Eliminar]

   Si estás por escribir `btn-primary`, la pregunta no es de qué color: es cuál
   de esos seis verbos hace el botón. El catálogo vivo está en /siga/estilo. */

.btn-secondary {
  background: var(--surface-elevated);
  color: var(--text-primary);
  border-color: var(--border-standard);
}

.btn-secondary:hover {
  background: var(--surface-hover);
  border-color: var(--border-emphasis);
  color: var(--text-primary);
}

/* SEIS tonos, uno por lo que el botón HACE (MOR-172, revisado el 8 sep 2026).
   ─────────────────────────────────────────────────────────────────────────
   Daniel: «veo todo del mismo color, más inductivo sería un tono de azul para
   los create, otro para los show, otro para las descargas. Eso sería mejor
   cognitivamente». Tenía razón: con 59 botones azules y 57 grises, el color
   había dejado de decir nada.

      btn-guardar     verde            QUEDA GUARDADO: alta, crear, guardar
      btn-accion      índigo sólido    lo que hace MUCHO de una vez
      btn-documento   petróleo sólido  lo que SALE: descargar, emitir
      btn-ver         azul suave       ir al detalle de algo
      btn-secondary   neutro           volver, cancelar
      btn-danger      sin tono         destructivo, por peso y posición

   ⚠️ La distinción que importa no es «primario vs secundario»: es **qué pasa
   al tocarlo**. «Copiar de otro año» crea ochenta filas y «Guardar» actualiza
   una; los dos eran el mismo azul.

   ⚠️ Y el verde NO significa «crea»: significa **queda guardado**. Por eso lo
   llevan por igual el `[+ Agregar]` que va al formulario y el `[Crear]` que lo
   envía — y también `[Guardar cambios]` en una edición, que no crea nada. El
   `+` es lo único que dice «y además nace algo nuevo». */
.btn-accion {
  background: var(--accion-primary);
  color: white;
  border-color: var(--accion-primary);
}

.btn-accion:hover {
  background: var(--accion-hover);
  border-color: var(--accion-hover);
  color: white;
}

.btn-documento {
  background: var(--documento-primary);
  color: white;
  border-color: var(--documento-primary);
}

.btn-documento:hover {
  background: var(--documento-hover);
  border-color: var(--documento-hover);
  color: white;
}

/* Ir al detalle. Suave a propósito: aparece muchas veces por pantalla —una por
   fila— y en sólido convertiría un listado en una pared de botones. */
.btn-ver {
  background: var(--brand-subtle);
  color: var(--brand-primary);
  border-color: transparent;
}

.btn-ver:hover {
  background: var(--brand-subtle);
  border-color: var(--brand-primary);
  color: var(--brand-primary);
}

/* El botón destructivo NO lleva rojo (MOR-135 · la regla de color).
   ────────────────────────────────────────────────────────────────
   En un sistema escolar el rojo tiene un significado que no se puede gastar:
   **un número en rojo es un niño aplazado**. Es la señal de más carga
   emocional del producto y aparece en la libreta que mira una madre, en las
   notas del alumno y en el conteo de inasistencias. Si el rojo también quiere
   decir "este botón borra cosas", deja de decir lo primero.

   Lo destructivo se señala igual, pero por PESO y POSICIÓN:

       btn-guardar     verde sólido     la acción a la que vino
       btn-secondary   borde suave      todo lo demás
       btn-danger      borde fuerte     destructivo — y en hover se vuelve
                                        tinta sólida, que se lee como "en serio"

   Además ya no aparece en ninguna fila de listado: vive en la pantalla de
   edición, dentro de un bloque rotulado, con su confirmación y su explicación
   de qué se pierde. El contexto carga el peso que antes cargaba el color. */
.btn-danger {
  background: transparent;
  color: var(--text-primary);
  border-color: var(--border-strong);
  font-weight: 600;
}

.btn-danger:hover {
  background: var(--text-primary);
  border-color: var(--text-primary);
  color: var(--surface-elevated);
}

.btn-sm {
  font-size: 13px;
  padding: 6px var(--space-sm);
}

.btn-outline-light {
  background: transparent;
  color: var(--text-secondary);
  border-color: var(--border-standard);
}

.btn-outline-light:hover {
  background: var(--surface-hover);
  color: var(--text-primary);
  border-color: var(--border-emphasis);
}

/* ---- Cards & Panels ---- */

.card-academic {
  background: var(--surface-elevated);
  border: 1px solid var(--border-standard);
  border-radius: var(--radius-md);
  padding: var(--space-lg);
  margin-bottom: var(--space-lg);
}

.card-academic-header {
  display: flex;
  /* `flex-wrap` y `gap`, no decorativos: sin ellos, en un teléfono el título y
     el botón de la derecha se aprietan hasta encimarse. Se veía en la toma de
     asistencia —"Asistencia" quedaba debajo del chip de la fecha— y es la misma
     cabecera de TODAS las pantallas. */
  flex-wrap: wrap;
  gap: var(--space-sm) var(--space-md);
  align-items: center;
  justify-content: space-between;
  margin-bottom: var(--space-lg);
  padding-bottom: var(--space-md);
  border-bottom: 1px solid var(--border-subtle);
}

.card-academic-header h1,
.card-academic-header h2 {
  margin: 0;
}

/* Dos botones en la cabecera de una tarjeta —«Subir matrícula» y «Subir
   materias y profesores»—. Se agrupan para que envuelvan juntos: sin esto, en
   un teléfono el segundo cae solo debajo del título y parece de otra sección. */
.acciones-de-cabecera {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm);
}

/* ---- Tables ---- */

.table-academic {
  width: 100%;
  border-collapse: separate;
  border-spacing: 0;
  background: var(--surface-elevated);
  border: 1px solid var(--border-standard);
  border-radius: var(--radius-md);
  overflow: hidden;
}

.table-academic thead {
  background: var(--surface-canvas);
}

.table-academic thead th {
  font-family: var(--font-sans);
  font-size: 13px;
  font-weight: 600;
  color: var(--text-secondary);
  text-transform: uppercase;
  letter-spacing: 0.03em;
  padding: var(--space-sm) var(--space-md);
  text-align: left;
  border-bottom: 1px solid var(--border-standard);
}

.table-academic tbody tr {
  border-bottom: 1px solid var(--border-subtle);
  transition: background var(--transition-fast);
}

.table-academic tbody tr:last-child {
  border-bottom: none;
}

.table-academic tbody tr:hover {
  background: var(--surface-hover);
}

.table-academic tbody td {
  padding: var(--space-md);
  color: var(--text-primary);
  font-size: 14px;
}

.table-academic tbody td:first-child {
  font-weight: 500;
}

/* ---- Forms ---- */

.form-group {
  margin-bottom: var(--space-lg);
}

.form-label {
  display: block;
  font-size: 14px;
  font-weight: 500;
  color: var(--text-primary);
  margin-bottom: var(--space-xs);
}

.form-control {
  width: 100%;
  padding: var(--space-sm) var(--space-md);
  font-family: var(--font-sans);
  font-size: 14px;
  color: var(--text-primary);
  background: var(--control-bg);
  border: 1px solid var(--control-border);
  border-radius: var(--radius-sm);
  transition: all var(--transition-base);
}

.form-control:focus {
  outline: none;
  border-color: var(--control-focus-border);
  box-shadow: 0 0 0 3px var(--control-focus-ring);
  background: var(--surface-elevated);
}

.form-control::placeholder {
  color: var(--text-muted);
}

select.form-control {
  cursor: pointer;
}

textarea.form-control {
  resize: vertical;
  min-height: 100px;
}

.password-toggle-group {
  position: relative;
}

.password-toggle-group .form-control {
  padding-right: 52px;
}

/* Los dos SVG del ojo, que reemplazaron a Font Awesome entero (MOR-134). */
.password-toggle-icon {
  width: 18px;
  height: 18px;
  display: block;
}

/* El caso puntual se fue: lo cubre la regla global `[hidden]` de arriba. */

.password-toggle-btn {
  position: absolute;
  right: var(--space-xs);
  top: 50%;
  transform: translateY(-50%);
  /* ⚠️ FLEX, y por eso el ojo se veía corrido (MOR-163).
     El botón mide 34×30 y el SVG 18×18 con `display: block`: sin centrarlo, el
     icono se pegaba arriba a la izquierda de su propia caja. Se veía desplazado
     y nadie sabía por qué — el botón estaba en su lugar, el dibujo no. */
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--border-standard);
  background: var(--surface-elevated);
  color: var(--text-secondary);
  border-radius: var(--radius-sm);
  line-height: 1;
  width: 34px;
  height: 30px;
  padding: 0;
  cursor: pointer;
  transition: all var(--transition-fast);
}

.password-toggle-btn:hover {
  background: var(--surface-hover);
  color: var(--text-primary);
}

/* ---- Alerts ---- */

.alert {
  padding: var(--space-md) var(--space-lg);
  border-radius: var(--radius-md);
  border: 1px solid;
  margin-bottom: var(--space-lg);
  font-size: 14px;
  font-family: var(--font-sans);
}

.alert-success {
  background: var(--success-bg);
  border-color: var(--success-border);
  color: var(--success-text);
}

.alert-danger {
  background: var(--danger-bg);
  border-color: var(--danger-border);
  color: var(--danger-text);
}

.alert-warning {
  background: var(--warning-bg);
  border-color: var(--warning-border);
  color: var(--warning-text);
}

/* ---- Layout Structure ---- */

.app-layout {
  display: flex;
  min-height: calc(100vh - var(--alto-barra));
}

.sidebar-index {
  width: 260px;
  background: var(--surface-elevated);
  border-right: 1px solid var(--border-standard);
  padding: var(--space-lg) 0;
  position: sticky;
  /* ⚠️ Debajo de la barra fija, no en el borde de la ventana: con `top: 0` el
     menú se metería DETRÁS de la barra al bajar. */
  top: var(--alto-barra);
  height: calc(100vh - var(--alto-barra));
  overflow-y: auto;
  /* El ancho se anima porque el `main` de al lado se ensancha al mismo tiempo:
     sin transición el contenido salta 204 px de golpe y se pierde el hilo de
     dónde estaba uno leyendo. */
  transition: width var(--transition-base);
}

.sidebar-section {
  margin-bottom: var(--space-lg);
}

.sidebar-section-title {
  font-family: var(--font-serif);
  font-size: 13px;
  font-weight: 600;
  color: var(--text-tertiary);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  padding: 0 var(--space-lg);
  margin-bottom: var(--space-sm);
}

.sidebar-nav {
  list-style: none;
  margin: 0;
  padding: 0;
}

.sidebar-nav-item {
  margin: 0;
}

.sidebar-nav-link {
  display: block;
  padding: var(--space-sm) var(--space-lg);
  color: var(--text-secondary);
  font-size: 14px;
  text-decoration: none;
  transition: all var(--transition-fast);
  border-left: 2px solid transparent;
}

.sidebar-nav-link:hover {
  color: var(--text-primary);
  background: var(--surface-hover);
  border-left-color: var(--border-emphasis);
}

.sidebar-nav-link.active {
  color: var(--brand-primary);
  background: var(--brand-subtle);
  border-left-color: var(--brand-primary);
  font-weight: 500;
}

/* ---- Container ---- */

.container-academic {
  flex: 1;
  max-width: 100%;
  /* ⚠️ El relleno de ARRIBA no puede ser cero, y lo fue hasta MOR-192.
     Daniel: «el padding del content está pegado con navbar». Y estaba: la
     primera tarjeta nacía contra el borde inferior de la barra, sin nada que
     las separara, así que el título de la pantalla se leía como parte de la
     barra en vez de como el comienzo del contenido. */
  padding: var(--space-lg) var(--space-2xl) var(--space-xl);
}

/* En un teléfono de 390 px, 48 px de relleno a cada lado son 96 px —casi un
   TERCIO de la pantalla— gastados en aire antes de la primera letra. Y no es
   un problema de estética: la tabla de notas que el profesor abre desde el
   teléfono (MOR-96) pierde una columna entera por esto.

       390 px    │◄─48─►│ contenido 294 px │◄─48─►│     antes
       390 px  │◄16►│  contenido 358 px  │◄16►│       ahora   (+64 px, +22 %)  */
@media (max-width: 640px) {
  .container-academic { padding: var(--space-md) var(--space-md) var(--space-lg); }
}

/*
   El menú en un teléfono (MOR-134).

   Antes esto era una sola regla —`flex-direction: column`— y bastaba para que
   el sidebar ENTERO, con sus cinco secciones y hasta trece enlaces, se apilara
   encima del contenido. En un teléfono eso significa deslizar el menú completo
   antes de ver la primera fila de datos.

   Importa más de lo que parece: según la conversación de MOR-99, el profesor
   carga notas DESDE EL TELÉFONO. La pantalla de carga ya estaba resuelta
   (MOR-96); llegar a ella no.

   Ahora el menú es un NAVBAR: los cinco grupos como chips, y el que se toca
   despliega sus pantallas en cajas debajo.

   ## Por qué dejó de ser una tira que se desliza (MOR-149)

   La primera versión ponía los dieciocho enlaces en una fila con scroll
   horizontal. Con cinco enlaces se leía bien; con dieciocho, encontrar
   «Departamentos» es deslizar a ciegas por una tira más ancha que tres
   pantallas — y el scroll horizontal no avisa cuánto falta.

   Cinco chips caben sin deslizar nada. Y eso destraba lo demás: sin scroll no
   hace falta `overflow`, y sin `overflow` el desplegable puede salirse de la
   barra en vez de quedar recortado, que era el problema que antes obligaba a
   dibujar el menú dos veces.
*/
@media (max-width: 768px) {
  .app-layout {
    flex-direction: column;
  }

  /* ⚠️ `overflow: visible` es lo que hace posible todo lo de abajo.
     Con `overflow-y: hidden` —lo que había— el desplegable de un grupo queda
     cortado al ras de la barra, porque crece justo hacia donde está el recorte.
     Y no se puede pedir `overflow-x: auto` con `overflow-y: visible`: el CSS
     convierte el `visible` en `auto` en cuanto el otro eje no lo es. O sea que
     el scroll horizontal y el desplegable son incompatibles, y el desplegable
     es el que resuelve el problema de verdad. */
  .sidebar-index {
    width: 100%;
    height: auto;
    position: sticky;
    top: 0;
    z-index: 20;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-xs);
    padding: var(--space-xs) var(--space-sm);
    border-right: none;
    border-bottom: 1px solid var(--border-standard);
    overflow: visible;
  }

  /* El Panel: no tiene título de grupo, así que se dibuja como un chip más. */
  .sidebar-section {
    display: flex;
    align-items: center;
    margin-bottom: 0;
    flex: 0 0 auto;
  }

  .sidebar-section .sidebar-nav {
    display: flex;
    gap: var(--space-micro);
  }

  .sidebar-nav-item {
    flex: 0 0 auto;
  }


  /* ── el grupo, como botón del navbar ────────────────────────────────── */

  .sidebar-grupo {
    margin-bottom: 0;
    flex: 0 0 auto;
    /* `static` a propósito: así el desplegable se posiciona contra la BARRA
       —que es `sticky`, o sea el bloque contenedor— y sale de lado a lado en
       vez de quedar colgando bajo un chip de setenta píxeles. */
    position: static;
  }

  .sidebar-grupo > summary {
    /* `margin-bottom` y el `padding` ancho son de la columna de escritorio: acá
       el título dejó de ser un encabezado y pasó a ser un chip. */
    margin-bottom: 0;
    padding: var(--space-xs) var(--space-sm);
    /* 36px: es un objetivo del dedo, no un renglón de texto. Los chips van en
       una barra que se toca, no se apunta. */
    min-height: 36px;
    font-size: 11px;
    border-radius: var(--radius-sm);
    border: 1px solid var(--border-subtle);
    cursor: pointer;
  }

  /* El chip abierto se marca: sin eso, con el desplegable de lado a lado no se
     sabe cuál de los cinco lo abrió. */
  .sidebar-grupo[open] > summary {
    color: var(--brand-primary);
    border-color: var(--brand-primary);
    background: var(--surface-hover);
  }

  /* El triángulo apunta hacia ABAJO, no hacia la derecha: en una columna «a la
     derecha» significa «se abre hacia abajo», pero en una barra horizontal
     significaría «hay más a la derecha», que es justo lo que ya no hay. */
  .sidebar-grupo > summary::before { transform: rotate(90deg); }
  .sidebar-grupo[open] > summary::before { transform: rotate(-90deg); }

  /* ── el desplegable, en cajas ───────────────────────────────────────── */

  /* Cajas y no una lista: en una lista de una columna, cuatro pantallas ocupan
     cuatro renglones y hay que leer para elegir. En cajas de dos columnas se
     ven las cuatro de un vistazo, con el icono haciendo de ancla. */
  .sidebar-grupo[open] > .sidebar-nav {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    z-index: 30;
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
    gap: var(--space-xs);
    padding: var(--space-sm);
    background: var(--surface-elevated);
    border-bottom: 1px solid var(--border-standard);
    /* Más suave que la del modal de búsqueda: esto es un desplegable pegado a
       la barra, no una capa que tapa la página. */
    box-shadow: 0 12px 28px rgba(10, 16, 26, 0.18);
  }

  .sidebar-grupo[open] > .sidebar-nav .sidebar-nav-link {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-micro);
    /* 56px de alto: la caja se toca con el pulgar, y sobre una lista de datos
       un objetivo chico se falla y navega a otro lado. */
    min-height: 56px;
    justify-content: center;
    padding: var(--space-sm);
    white-space: normal;
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-sm);
  }

  /* ⚠️ El desplegable CERRADO se estaba maquetando igual.
     `.sidebar-section .sidebar-nav { display: flex }` alcanza también al `<ul>`
     de un `<details>` cerrado, y un `display` explícito **anula el ocultamiento
     que hace el navegador**. O sea que los enlaces de los cinco grupos estaban
     ahí, con ancho, aunque no se vieran.
     Nunca se notó porque ninguna etiqueta había sido lo bastante larga: el día
     que apareció «Planes de evaluación» desbordó 9 px las TRECE pantallas del
     director a la vez. */
  .sidebar-grupo:not([open]) > .sidebar-nav { display: none; }

  /* ⚠️ El NOMBRE tiene que poder encogerse, y un `<span>` dentro de un flex NO
     lo hace solo: `min-width: auto` es el valor inicial de un ítem flex, o sea
     «nunca más angosto que mi contenido». Con `flex-direction: column` de
     arriba, el nombre es un ítem — y «Planes de evaluación», veinte caracteres,
     desbordaba 9 px **las trece pantallas del director a la vez**.
     Se arregla la causa y no el nombre: el próximo enlace largo no rompe nada.
     Es la misma trampa de `.cabecera-de-lista` en MOR-194, un nivel más adentro. */
  .sidebar-grupo[open] > .sidebar-nav .sidebar-nav-link > span {
    min-width: 0;
    overflow-wrap: anywhere;
  }

  .sidebar-grupo[open] > .sidebar-nav .sidebar-nav-link.active {
    border-color: var(--brand-primary);
    border-bottom-width: 1px;
  }

  /* ── los enlaces sueltos de la barra (el Panel) ─────────────────────── */

  /* La marca de "estás acá" pasa del borde izquierdo al de abajo: en una barra
     horizontal, un borde a la izquierda de cada enlace parece un separador. */
  .sidebar-nav-link {
    padding: var(--space-xs) var(--space-sm);
    min-height: 36px;
    white-space: nowrap;
    border-left: none;
    border-bottom: 2px solid transparent;
    border-radius: var(--radius-sm);
  }

  .sidebar-nav-link:hover {
    border-left-color: transparent;
  }

  .sidebar-nav-link.active {
    border-left-color: transparent;
    border-bottom-color: var(--brand-primary);
  }
}

/* ---- Escala de un listado: cuántos son ---- */
/*
   El número que le faltaba a las dieciséis pantallas iguales (MOR-134).

   Va grande a propósito: es lo único de la pantalla que dice de qué tamaño es
   lo que sigue, y es lo que hace que un listado de 200 alumnos NO se vea igual
   que uno de 3 departamentos. Sin esto, ninguna tabla pesa más que otra y el
   ojo no encuentra dónde mirar.
*/

.listado-escala {
  display: flex;
  align-items: baseline;
  gap: var(--space-xs);
  margin-bottom: var(--space-md);
  color: var(--text-secondary);
  font-size: 14px;
}

.listado-escala-numero {
  font-family: var(--font-serif);
  font-size: 28px;
  font-weight: 600;
  line-height: 1;
  color: var(--text-primary);
  font-variant-numeric: tabular-nums;
}

.listado-escala-sustantivo {
  font-size: 15px;
  color: var(--text-secondary);
}

/* Lo que explica el número cuando no se explica solo: una búsqueda filtrando,
   una tabla topada. Va apagado — es la nota al pie del número, no el número. */
.listado-escala-detalle {
  color: var(--text-tertiary);
  font-size: 13px;
}

/* «142 alumnos DE PRIMARIA»: el nivel sobre el que se está contando.
   En índigo, el mismo tono del selector de la barra — es la misma información
   dicha dos veces en la pantalla, y que se reconozcan es el punto. */
.listado-escala-nivel {
  color: var(--accion-primary);
  font-weight: 600;
}

/* ---- Action Buttons Group ---- */

.actions-group {
  display: flex;
  gap: var(--space-xs);
  align-items: center;
}

/* ---- Empty State ---- */

.empty-state {
  text-align: center;
  padding: var(--space-2xl) var(--space-lg);
  color: var(--text-tertiary);
}

.empty-state h3 {
  color: var(--text-secondary);
  font-size: 16px;
}

/* ⚠️ Los botones de un estado vacío no tenían NINGÚN estilo: el `div` existía en
   las vistas y en la hoja no había regla, así que salían pegados uno contra
   otro. `.acciones-de-cabecera` sí lo tenía — es el mismo problema resuelto en
   un solo lado. */
.empty-state-acciones {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm);
  justify-content: center;
  margin-top: var(--space-lg);
}

/* ---- Barra de contexto: en qué colegio y en qué año escolar estás ---- */
/*
   Antes esta franja decía "Período Académico Activo" —una etiqueta anunciando
   un dato que nunca traía— y repetía el nombre del producto debajo del logo.
   Cero información en el lugar más visible de la app.

   Ahora carga la identidad del inquilino: el colegio manda visualmente, el año
   escolar acompaña. Es lo que reemplaza a la columna "Escuela" que se quitó de
   los listados en MOR-90 — el dato iba una vez arriba, no en cada fila.
*/

/* ⚠️ `.academic-context` y `.academic-context-inner` SE BORRARON (MOR-194).
   Eran de la franja que MOR-184 fusionó dentro de la barra: su parcial dejó de
   rendirse y nadie borró ni el parcial ni sus reglas. La de teléfono seguía
   ahí, apuntando a una clase que ya no existía, así que la barra nueva se
   quedó sin reglas de teléfono — ver el `@media` de más abajo. Las clases de
   adentro —`.contexto-colegio`, `.contexto-periodo`— sí siguen vivas. */

.contexto-colegio {
  /* ⚠️ NO cede el ancho, y hubo que decírselo. Es la identidad de lo que se
     está mirando: con el aviso de «acá el nivel no aplica» puesto, «COLEGIO A
     7328013» se recortaba a «C».
     Era el único elemento encogible de la barra —`.contexto-periodo` y
     `.contexto-nivel` traen `flex-shrink: 0`— así que cualquier cosa nueva
     salía de su ancho. El `max-width: 34ch` de abajo ya lo acota; no hace falta
     además que se achique. */
  flex: 0 0 auto;
  display: flex;
  /* ⚠️ `center` y no `baseline`: con un `<img>` adentro, `baseline` alinea el
     borde inferior de la imagen con la línea del texto y empuja la fila hacia
     abajo. En la franja propia que había antes no se notaba; en una barra de
     52 px, descuadra todo. */
  align-items: center;
  gap: var(--space-sm);
  min-width: 0;
  /* El nombre de un colegio largo se recorta en vez de empujar al año escolar
     fuera de la pantalla. */
  max-width: 34ch;
}

/* El nombre del colegio es la identidad de la pantalla: va en la serif del
   sistema y con más peso que todo lo demás de la franja. */
.contexto-colegio-nombre {
  font-family: var(--font-serif);
  font-size: 15px;
  font-weight: 600;
  color: var(--text-primary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.contexto-colegio-lugar {
  color: var(--text-tertiary);
  white-space: nowrap;
}

/* ⚠️ `.contexto-periodo*` SE BORRÓ con MOR-223: era el contenedor del año
   cuando vivía suelto en la barra, y ahora el año vive dentro de la pastilla de
   contexto (`.contexto-valor-anio`). Se borra en vez de dejarse «por si acaso»:
   una regla huérfana no falla, simplemente deja de aplicar — y es exactamente
   así como esta barra se quedó sin reglas de teléfono en MOR-194. */

/* La barra en un teléfono (MOR-194).
   ─────────────────────────────────
   Daniel, abriendo /secciones a 493 px: «mira cómo se ve desde el teléfono:
   BOOM, EXPLOTÓ TODO». Y explotaba: la página entera se corría 189 px de lado,
   IDÉNTICOS en todas las pantallas y a cualquier ancho — la pista de que el
   culpable no era el contenido sino la barra, que es lo único común.

   ⚠️ Es la MISMA falla de `.nav-institutional` que está documentada treinta
   líneas más arriba, y por eso duele: al fusionar las dos barras (MOR-184), la
   única regla de teléfono que existía quedó apuntando a
   `.academic-context-inner` —clase del parcial que dejó de rendirse—, así que
   la barra nueva se quedó SIN reglas de teléfono y nadie se enteró. Una regla
   huérfana no falla: simplemente deja de aplicar.

   Lo que se cae a este ancho, en orden de qué se puede perder primero:

       el lugar del colegio      Caracas, Distrito Capital  ← ya estaba
       la palabra «SIGA»         el logo alcanza
       la etiqueta «Año escolar» el selector se explica solo
       el nombre del colegio     se recorta, no se envuelve
       «Cerrar sesión»           queda el nombre, que enlaza al perfil

   El nombre del colegio se RECORTA en vez de desaparecer: es lo que dice en
   cuál de los dos colegios está parada la secretaria que tiene los dos. */
@media (max-width: 640px) {
  .barra-superior-inner {
    /* ⚠️ El aire de la barra, al mínimo. Cinco elementos separados por 12 px
       son 48 px; a 4 px son 16. En una pantalla de 390 px esa diferencia es la
       que decide si «Cerrar sesión» cabe o hay que esconderlo — y esconderlo
       fue lo que Daniel encontró mal. */
    gap: var(--space-micro);
    /* 16 px de cada lado son 32 px de una pantalla de 390: el 8 % gastado en
       margen antes de dibujar nada. */
    padding: 0 var(--space-sm);
    /* ⚠️ `min-width: 0` en el contenedor Y en el hijo que se encoge: un ítem
       flex no baja de su tamaño de contenido si no se le dice, y ES esa
       negativa la que empuja la fila entera más allá del viewport. */
    min-width: 0;
  }

  .contexto-colegio {
    min-width: 0;
    overflow: hidden;
  }

  .contexto-colegio-nombre {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .contexto-colegio-lugar,
  .navbar-brand-text {
    display: none;
  }

  /* ⚠️ «Cerrar sesión» se queda, como ICONO.

     Estuvo escondido, con el argumento de que el nombre enlaza al perfil y ahí
     adentro está el botón. Daniel lo probó en un iPhone XR: *«en el mobile
     display no se visualiza el cerrar sesión»*. Y tenía razón — nadie va a
     buscar la salida dentro de su propia ficha.

     Lo que lo hace posible ahora es que la barra adelgazó: la pastilla de
     contexto reemplazó a cinco controles (MOR-223), así que hay lugar para un
     objetivo táctil de 36 px.

     ⚠️ Se esconde el TEXTO, no el botón, y con `.sr-only` en vez de
     `display: none`: el nombre sigue existiendo para el lector de pantalla, que
     es justo quien lo necesita cuando lo único visible es un dibujo. */
  .barra-superior .session-menu .btn {
    padding: 0;
    width: 36px;
    min-height: 36px;
    justify-content: center;
  }

  /* El nombre entero se va y queda la inicial: son ~50 px para decirle a
     alguien quién es él mismo, y ese lugar lo necesita la salida. */
  .barra-superior .session-menu .user-info-nombre { display: none; }
  .barra-superior .session-menu .user-info-inicial { display: inline; }

  .barra-superior .session-menu .btn .salir-texto {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
  }

  /* ⚠️ El buscador global se encoge a su icono, y su `min-width: 220px` TIENE
     que levantarse explícitamente: es un mínimo, así que ningún `flex-shrink`
     lo baja. Eran 220 px de los 390 que hay — más de la mitad de la pantalla
     para un control que en el teléfono se toca, no se lee. */
  .buscador-disparador {
    min-width: 0;
    margin-right: var(--space-xs);
  }

  /* El texto de adentro («Buscar…», el atajo ⌘K) se va: en un teléfono no hay
     teclado que muestre un atajo, y el icono de lupa ya dice qué hace. */
  .buscador-disparador > :not(svg) { display: none; }

  /* ⚠️ EL culpable de los 90 px, y el que costó encontrar: `.contexto-periodo`
     medía 283 de los 390 px de la pantalla y tiene `flex-shrink: 0`, así que no
     cedía ni uno. Son el selector de año MÁS la frase de situación
     —«finalizado el 15 de julio»— una al lado de la otra.

     La situación se va: en el teléfono importa CUÁL año se está mirando, y si
     está cerrado se ve al entrar a cualquier listado. El selector se queda,
     porque es lo que decide todo lo demás. */
  .contexto-periodo {
    flex-shrink: 1;
    min-width: 0;
  }

  /* ⚠️ El gap de la barra se achica: cinco elementos × 12 px son 48 px, que a
     390 px es el 12 % de la pantalla gastado en aire. */
  .barra-superior-inner { gap: var(--space-xs); }

  /* Las reglas de teléfono del control de contexto NO viven acá: están junto a
     `.contexto`, cinco mil líneas más abajo, porque a igual especificidad gana
     la última regla del archivo. Ver la nota de ahí. */
}

/* ---- Pagination ---- */

/* Wrapper div que usamos en las vistas */
.pagy-nav {
  margin-top: var(--space-lg);
  padding-top: var(--space-lg);
  border-top: 1px solid var(--border-standard);
}

/* nav generado por pagy_nav: nav.pagy.nav */
nav.pagy {
  display: flex;
  gap: var(--space-xs);
  justify-content: center;
  list-style: none;
  flex-wrap: wrap;
}

/* Todos los enlaces de paginación */
nav.pagy a,
nav.pagy a[role="link"] {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 32px;
  height: 32px;
  padding: 0 var(--space-sm);
  font-size: 14px;
  color: var(--text-secondary);
  background: var(--surface-elevated);
  border: 1px solid var(--border-standard);
  border-radius: var(--radius-sm);
  text-decoration: none;
  transition: all var(--transition-fast);
}

/* Hover en enlaces activos */
nav.pagy a:hover:not([aria-disabled="true"]) {
  background: var(--surface-hover);
  border-color: var(--border-emphasis);
  color: var(--text-primary);
}

/* Página actual */
nav.pagy a.current,
nav.pagy a[aria-current="page"] {
  background: var(--brand-primary);
  border-color: var(--brand-primary);
  color: white;
  font-weight: 500;
  cursor: default;
}

/* Prev/Next deshabilitados y gap */
nav.pagy a[aria-disabled="true"]:not(.current) {
  opacity: 0.4;
  cursor: not-allowed;
}

nav.pagy a.gap {
  background: transparent;
  border-color: transparent;
  cursor: default;
  opacity: 0.5;
}

/* ---- Utility Classes ---- */

.d-flex {
  display: flex;
}

.align-items-center {
  align-items: center;
}

.justify-content-center {
  justify-content: center;
}

.justify-content-between {
  justify-content: space-between;
}

.gap-1 {
  gap: var(--space-xs);
}

.gap-2 {
  gap: var(--space-md);
}

.gap-3 {
  gap: var(--space-lg);
}

.text-center {
  text-align: center;
}

/* ---- Form Select (for consistency with form-control) ---- */

.form-select {
  width: 100%;
  padding: var(--space-sm) var(--space-md);
  font-family: var(--font-sans);
  font-size: 14px;
  color: var(--text-primary);
  background: var(--control-bg);
  border: 1px solid var(--control-border);
  border-radius: var(--radius-sm);
  transition: all var(--transition-base);
  cursor: pointer;
}

.form-select:focus {
  outline: none;
  border-color: var(--control-focus-border);
  box-shadow: 0 0 0 3px var(--control-focus-ring);
  background: var(--surface-elevated);
}

/* ---- Dashboard Styles ---- */

.dashboard-administrador {
  /* Container ya definido */
}

/* Stats Grid */
/* `auto-fit` con 200 px deja tres columnas en un contenedor de ~900 px, y con
   cuatro tarjetas eso da 3 + 1, con la última sola en una fila vacía. Con
   `min(160px, 100%)` entran las cuatro en una línea en escritorio y caen a dos
   en el teléfono — nunca una huérfana.
   El `min(…, 100%)` es lo que impide que la tarjeta imponga 160 px en una
   pantalla más angosta que eso. */
.stats-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(160px, 100%), 1fr));
  gap: var(--space-md);
  margin-top: var(--space-lg);
}

.stat-card {
  background: var(--brand-subtle);
  border: 1px solid var(--border-standard);
  border-radius: var(--radius-md);
  padding: var(--space-lg);
  text-align: center;
  transition: all var(--transition-fast);
}

.stat-card:hover {
  border-color: var(--brand-primary);
  box-shadow: 0 0 0 3px var(--control-focus-ring);
}

.stat-card h3 {
  font-family: var(--font-serif);
  font-size: 36px;
  color: var(--brand-primary);
  margin: 0 0 var(--space-xs) 0;
  font-weight: 600;
}

.stat-card p {
  font-size: 14px;
  color: var(--text-secondary);
  margin: 0;
}

/* Quick Access Grid */
.quick-access-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: var(--space-md);
  margin-top: var(--space-lg);
}

.quick-access-card {
  display: block;
  background: var(--surface-elevated);
  border: 1px solid var(--border-standard);
  border-radius: var(--radius-md);
  padding: var(--space-lg);
  text-decoration: none;
  transition: all var(--transition-fast);
}

.quick-access-card:hover {
  border-color: var(--brand-primary);
  background: var(--brand-subtle);
  transform: translateY(-2px);
}

.quick-access-icon {
  font-size: 32px;
  margin-bottom: var(--space-sm);
}

.quick-access-card h3 {
  font-family: var(--font-serif);
  font-size: 18px;
  color: var(--brand-primary);
  margin: 0 0 var(--space-xs) 0;
}

.quick-access-card p {
  font-size: 14px;
  color: var(--text-secondary);
  margin: 0;
}

/* Admin Info Badge */
.admin-info-badge {
  display: inline-flex;
  align-items: center;
  gap: var(--space-sm);
  padding: var(--space-sm) var(--space-md);
  background: var(--surface-canvas);
  border: 1px solid var(--border-standard);
  border-radius: var(--radius-sm);
  font-size: 14px;
}

.role-badge {
  background: var(--brand-primary);
  color: white;
  padding: 4px var(--space-sm);
  border-radius: var(--radius-sm);
  font-size: 12px;
  font-weight: 500;
  /* Sin `text-transform: capitalize`: capitaliza CADA palabra, y en español eso
     produce Title Case inglés. Acá lo hacía sobre una fecha —"15 De Agosto De
     2026", en la pantalla de asistencia— y sobre "3 Registros". El único caso
     que quería arreglar era `admin.role.humanize`, y `humanize` ya pone la
     mayúscula inicial él solo. */
}


.section-title {
  font-family: var(--font-serif);
  font-size: 40px;
  text-align: center;
  color: var(--brand-primary);
  margin: 0 0 var(--space-2xl) 0;
  font-weight: 600;
}

.features-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
  gap: var(--space-xl);
}

.feature-card {
  background: var(--surface-canvas);
  padding: var(--space-xl);
  border-radius: var(--radius-lg);
  border: 1px solid var(--border-standard);
  transition: all var(--transition-base);
}

.feature-card:hover {
  transform: translateY(-5px);
  box-shadow: 0 10px 30px rgba(30, 74, 122, 0.1);
  border-color: var(--brand-primary);
}

.feature-icon {
  font-size: 48px;
  margin-bottom: var(--space-md);
  display: block;
}

.feature-card h3 {
  font-family: var(--font-serif);
  font-size: 22px;
  color: var(--brand-primary);
  margin: 0 0 var(--space-sm) 0;
  font-weight: 600;
}

.feature-card p {
  color: var(--text-secondary);
  line-height: 1.6;
  margin: 0;
  font-size: 15px;
}

/* ---- Animations ---- */

@keyframes fadeInDown {
  from {
    opacity: 0;
    transform: translateY(-30px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

@keyframes fadeInUp {
  from {
    opacity: 0;
    transform: translateY(30px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

@keyframes fadeIn {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

/* ---- Responsive ---- */

@media (max-width: 768px) {
  .hero-section-bg-logo {
    background-position: center 12%;
    background-size: min(500px, 95vw);
  }

  .hero-section-bg-logo .hero-content {
    padding-top: clamp(110px, 24vw, 170px);
  }

  .hero-section-fullwidth {
    width: calc(100% + (var(--space-lg) * 2));
    margin-left: calc(var(--space-lg) * -1);
    margin-right: calc(var(--space-lg) * -1);
  }

  .features-section,
  .benefits-section,
  .stats-section,
  .cta-section,
  .landing-footer {
    width: calc(100% + (var(--space-lg) * 2));
    margin-left: calc(var(--space-lg) * -1);
    margin-right: calc(var(--space-lg) * -1);
  }

  .hero-title {
    font-size: clamp(38px, 10vw, 60px);
  }

  .hero-subtitle {
    font-size: clamp(24px, 7vw, 34px);
  }

  .hero-logo {
    width: 200px;
  }

  .section-title {
    font-size: 32px;
  }

  .benefit-content {
    flex-direction: column;
    text-align: center;
  }

  .benefit-image-placeholder {
    width: 150px;
    height: 150px;
  }

  .placeholder-icon {
    font-size: 60px;
  }

  .cta-title {
    font-size: 32px;
  }

  .stat-number {
    font-size: 42px;
  }

  .footer-content {
    grid-template-columns: 1fr;
    gap: var(--space-xl);
  }
}

/* ---- No Role State ---- */

.no-role-container {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 60vh;
}

.no-role-card {
  text-align: center;
  max-width: 420px;
  padding: var(--space-2xl);
  background: var(--surface-elevated);
  border: 1px solid var(--border-standard);
  border-radius: var(--radius-lg);
}

.no-role-icon {
  font-size: 56px;
  margin-bottom: var(--space-lg);
}

.no-role-card h2 {
  font-family: var(--font-serif);
  color: var(--text-primary);
  margin: 0 0 var(--space-md) 0;
}

.no-role-card p {
  color: var(--text-secondary);
  line-height: 1.6;
  margin: 0 0 var(--space-xl) 0;
}

/* ---- Section Block (Profesor & Estudiante Dashboards) ---- */

.section-block {
  border: 1px solid var(--border-standard);
  border-radius: var(--radius-md);
  padding: var(--space-lg);
  margin-bottom: var(--space-md);
  transition: border-color var(--transition-fast);
}

.section-block:hover {
  border-color: var(--brand-primary);
}

.section-block-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-md);
  margin-bottom: var(--space-md);
}

.section-block-title h3 {
  font-family: var(--font-serif);
  font-size: 18px;
  color: var(--brand-primary);
  margin: 0 0 var(--space-xs) 0;
  font-weight: 600;
}

.section-period {
  font-size: 12px;
  color: var(--text-tertiary);
  background: var(--surface-hover);
  padding: 2px 8px;
  border-radius: var(--radius-sm);
}

.section-block-meta {
  flex-shrink: 0;
  text-align: right;
}

.section-student-count {
  font-size: 13px;
  color: var(--text-secondary);
  white-space: nowrap;
}

.section-subjects {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs);
  margin-bottom: var(--space-md);
}

.subject-tag {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  background: var(--brand-subtle);
  border: 1px solid rgba(30, 74, 122, 0.15);
  border-radius: var(--radius-sm);
  padding: 4px 10px;
  font-size: 13px;
}

.subject-name {
  color: var(--brand-primary);
  font-weight: 500;
}

.subject-teacher {
  color: var(--text-tertiary);
}

.section-actions {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-top: var(--space-sm);
  border-top: 1px solid var(--border-subtle);
}

/* ---- Detail Row (ficha de detalles) ---- */

.detail-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xl);
  margin-top: var(--space-lg);
}

.detail-item {
  flex: 1;
  min-width: 160px;
}

.detail-label {
  display: block;
  font-size: 12px;
  font-weight: 600;
  color: var(--text-tertiary);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  margin-bottom: var(--space-xs);
}

.detail-value {
  font-size: 16px;
  color: var(--text-primary);
  margin: 0;
  font-weight: 500;
}

/* ---- Form Row (formulario en línea) ---- */

/* ⚠️ `flex-start`, no `flex-end`.
   Con `flex-end` las dos columnas se alinean por ABAJO, y basta que una lleve
   una nota de ayuda debajo del campo para que su rótulo suba y el de al lado se
   quede: los dos «Grado» e «Identificador» quedaban a distinta altura. Alineados
   arriba se alinean los RÓTULOS, que es lo que el ojo usa para leer una fila.

   El botón «Limpiar» del buscador de listas —que sí necesita ir abajo, porque no
   tiene rótulo— se alinea solo, con su propio estilo en el parcial. */
.form-row {
  display: flex;
  gap: var(--space-md);
  flex-wrap: wrap;
  align-items: flex-start;
  /* Sin esto, el campo que sigue a la fila se le pega encima: los
     `.form-group` de adentro tienen el margen en cero. El mismo valor que un
     `.form-group` normal, o la fila rompe el ritmo vertical del formulario. */
  margin-bottom: var(--space-lg);
}

.form-row .form-group {
  flex: 1;
  min-width: 200px;
  margin-bottom: 0;
}

/* ---- Activity Log Badges ---- */

.activity-badge {
  display: inline-block;
  padding: 3px 10px;
  border-radius: var(--radius-sm);
  font-size: 12px;
  font-weight: 500;
  white-space: nowrap;
  background: var(--brand-subtle);
  color: var(--brand-primary);
  border: 1px solid var(--border-standard);
}

.activity-badge--login,
.activity-badge--logout {
  background: #e8f5e9;
  color: #2e7d32;
  border-color: #a5d6a7;
}

.activity-badge--created {
  background: #e3f2fd;
  color: #1565c0;
  border-color: #90caf9;
}

.activity-badge--updated {
  background: #fff8e1;
  color: #f57f17;
  border-color: #ffe082;
}

.activity-badge--destroyed {
  background: #fce4ec;
  color: #b71c1c;
  border-color: #f48fb1;
}

.activity-badge--assigned,
.activity-badge--removed {
  background: #f3e5f5;
  color: #6a1b9a;
  border-color: #ce93d8;
}

/* ---- Search Input with spinner ---- */

.search-input-wrap {
  position: relative;
}

.search-input-wrap .form-select {
  width: 100%;
  padding-right: 36px;
}

.search-spinner {
  position: absolute;
  right: 10px;
  top: 50%;
  transform: translateY(-50%);
  width: 16px;
  height: 16px;
  border: 2px solid var(--border-standard);
  border-top-color: var(--brand-primary);
  border-radius: 50%;
  animation: spin 0.6s linear infinite;
}

.search-spinner.hidden {
  display: none;
}

@keyframes spin {
  to { transform: translateY(-50%) rotate(360deg); }
}

/* ---- Filter Pills ---- */

.filter-pills {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  flex-wrap: wrap;
  margin-top: var(--space-sm);
}

.filter-pill {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  background: var(--brand-subtle);
  border: 1px solid var(--brand-primary);
  color: var(--brand-primary);
  padding: 3px 10px;
  border-radius: 20px;
  font-size: 13px;
  font-weight: 500;
}

.filter-pill:hover {
  background: var(--brand-primary);
  color: #fff;
}

.filter-pill.active {
  background: var(--brand-primary);
  color: #fff;
}

.filter-pill small {
  opacity: 0.75;
  font-weight: 400;
}

/* ============================================
   Medidor de ponderación
   --------------------------------------------
   Cuánto del 100 % de un lapso está comprometido. Se usa en el listado (una
   barra por grupo sección–materia–lapso) y dentro del formulario, donde además
   muestra en otro tono lo que sumaría la evaluación que se está escribiendo.
   ============================================ */

.weight-meter-intro {
  color: var(--text-secondary);
  font-size: 14px;
  margin: 0 0 var(--space-md);
}

.weight-meter {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-micro);
  margin-bottom: var(--space-md);
  /* ⚠️ Separado del deslizador, que ahora está justo encima. Son dos barras
     horizontales y a dos píxeles se leen como una sola cosa partida: el
     deslizador es el CONTROL —cuánto vale esta— y el medidor es el ESTADO del
     lapso entero. */
  margin-top: var(--space-sm);
}

.weight-meter-label {
  font-size: 14px;
  color: var(--text-secondary);
}

.weight-meter-label strong {
  color: var(--text-primary);
}

.weight-meter-bar {
  display: flex;
  height: 10px;
  border-radius: 999px;
  background: var(--surface-input);
  border: 1px solid var(--border-subtle);
  overflow: hidden;
}

.weight-meter-fill {
  background: var(--brand-primary);
  transition: width 120ms ease-out;
}

.weight-meter-fill--completo {
  background: var(--success-text);
}

.weight-meter-fill--nuevo {
  background: var(--success-text);
  opacity: 0.55;
}

.weight-meter-fill--excedido {
  background: var(--danger-text);
  opacity: 1;
}

.weight-meter-value {
  font-size: 13px;
  color: var(--text-tertiary);
}

.weight-meter-value--excedido {
  color: var(--danger-text);
  font-weight: 600;
}

.weight-meter-falta {
  color: var(--warning-text);
}

/* ============================================
   Planilla de carga de notas
   --------------------------------------------
   Móvil primero: una fila por alumno, en vertical. Una grilla de 30 × N en un
   teléfono no se arregla con overflow-x — se arregla no siendo una grilla.
   ============================================ */

.carga-contexto {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-xs);
  color: var(--text-secondary);
  font-size: 14px;
  margin: 0 0 var(--space-md);
}

/* El peso de la evaluación dentro del lapso.
   ⚠️ Es un CHIP, no negrita: cambia lo que estas notas significan —el mismo 15
   vale distinto en una evaluación del 30 % que en una del 70 %— y en negrita se
   perdía dentro de la misma frase que el grado, la sección y el lapso.
   Sin color semántico: es un dato de la evaluación, no una alerta (MOR-135). */
.carga-peso {
  display: inline-flex;
  align-items: center;
  padding: 2px var(--space-sm);
  border-radius: 999px;
  background: color-mix(in srgb, var(--brand-primary) 10%, transparent);
  color: var(--brand-primary);
  font-size: 13px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.carga-progreso {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  margin-bottom: var(--space-md);
}

.carga-progreso-barra {
  flex: 1;
  height: 8px;
  border-radius: 999px;
  background: var(--surface-input);
  border: 1px solid var(--border-subtle);
  overflow: hidden;
}

.carga-progreso-fill {
  height: 100%;
  background: var(--success-text);
  transition: width 160ms ease-out;
}

.carga-progreso-texto {
  font-size: 13px;
  color: var(--text-tertiary);
  white-space: nowrap;
}

.carga-aviso {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-sm);
  flex-wrap: wrap;
  background: var(--warning-bg);
  border: 1px solid var(--warning-border);
  color: var(--warning-text);
  border-radius: var(--radius-sm);
  padding: var(--space-sm) var(--space-md);
  margin-bottom: var(--space-md);
  font-size: 14px;
}

.carga-lista {
  list-style: none;
  margin: 0;
  padding: 0;
  border-top: 1px solid var(--border-subtle);
}

.carga-fila {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md);
  padding: var(--space-sm) 0;
  border-bottom: 1px solid var(--border-subtle);
}

/* ⚠️ `flex: 1` para que el nombre se coma TODO el espacio sobrante.
   Sin esto, `space-between` lo reparte entre los cinco hijos de la fila y el
   campo de la nota queda en una x distinta en cada renglón — se nota apenas la
   marca de estado sale del mismo grupo que el campo. La columna de notas tiene
   que caer a plomo: es la que se recorre de arriba abajo. */
.carga-alumno {
  min-width: 0;
  flex: 1;
}

.carga-alumno-nombre {
  font-size: 15px;
  color: var(--text-primary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.carga-alumno-carnet {
  font-size: 12px;
  color: var(--text-tertiary);
}

.carga-campo {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  flex-shrink: 0;
}

/* 64 px de ancho y 44 de alto: el mínimo cómodo para un pulgar. */
.carga-input {
  width: 64px;
  height: 44px;
  text-align: center;
  font-size: 18px;
  font-variant-numeric: tabular-nums;
  background: var(--control-bg);
  border: 1px solid var(--control-border);
  border-radius: var(--radius-sm);
  color: var(--text-primary);
}

.carga-input:focus {
  outline: none;
  border-color: var(--control-focus-border);
  box-shadow: 0 0 0 3px var(--control-focus-ring);
}

/* Sin nota se ve DISTINTO de una nota baja: el guion está donde iría el número. */
.carga-input::placeholder {
  color: var(--text-muted);
}

/* ── la marca de qué pasó con una fila ─────────────────────────────────────

   UNA para las cuatro planillas —notas, reparaciones, informes descriptivos y
   Educación Inicial—. Antes eran tres: un símbolo en notas, una palabra en
   informes y un destello verde en Inicial, para la misma pregunta —¿quedó
   guardado?— que el mismo profesor se hace en la misma tarde.

   ⚠️ El estado va en el ELEMENTO, no en la fila. Lo pinta el servidor por
   Turbo Stream reemplazando este `<span>`, así que la fila de arriba ni se
   entera — y es justo eso lo que conserva el foco del campo siguiente. */

.planilla-estado {
  flex: 0 0 auto;
  width: 18px;
  text-align: center;
  font-size: 15px;
  color: var(--text-muted);
  user-select: none;
}

.planilla-estado[data-estado="guardando"] { color: var(--text-tertiary); }
.planilla-estado[data-estado="guardado"]  { color: var(--success-text); }
.planilla-estado[data-estado="vacio"]     { color: var(--text-muted); }
.planilla-estado[data-estado="sin_red"]   { color: var(--warning-text); }
.planilla-estado[data-estado="rechazado"] { color: var(--danger-text); font-weight: 700; }

/* Y el campo se tiñe con él: la marca sola, de 18 px, se pierde en una lista de
   treinta. Es el DATO el que está en problemas, no un control.

   ⚠️ Desde la FILA y no desde el formulario: la marca vive fuera del `<form>`
   —adentro, reemplazarla por stream le devuelve el foco al campo que envió— así
   que son hermanas, no ancestro y descendiente. */
.carga-fila:has(.planilla-estado[data-estado="sin_red"]) .carga-input {
  border-color: var(--warning-border);
  background: var(--warning-bg);
}

.carga-fila:has(.planilla-estado[data-estado="rechazado"]) .carga-input {
  border-color: var(--danger-border);
  background: var(--danger-bg);
}

.carga-ayuda {
  margin-top: var(--space-md);
  font-size: 13px;
  color: var(--text-tertiary);
}

.carga-ayuda kbd {
  background: var(--surface-input);
  border: 1px solid var(--border-standard);
  border-radius: 3px;
  padding: 1px 5px;
  font-size: 12px;
}

@media (max-width: 480px) {
  .carga-alumno-nombre { font-size: 16px; }
  .carga-input { width: 72px; height: 48px; font-size: 20px; }
}

/* ============================================
   Notas del estudiante
   --------------------------------------------
   Lo que NUNCA puede pasar acá es que una evaluación sin corregir se lea como
   un cero: el alumno llama al colegio y deja de confiar en el sistema. Por eso
   "Pendiente" es texto en gris y nunca un número.
   ============================================ */

.nota-cabecera {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  flex-wrap: wrap;
}

.nota-valor {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 34px;
  padding: 2px 8px;
  border-radius: var(--radius-sm);
  font-size: 16px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.nota-valor--aprobada {
  background: var(--success-bg);
  color: var(--success-text);
  border: 1px solid var(--success-border);
}

.nota-valor--aplazada {
  background: var(--danger-bg);
  color: var(--danger-text);
  border: 1px solid var(--danger-border);
}

/* Gris y en palabras: no se puede confundir con una calificación. */
.nota-pendiente {
  font-size: 13px;
  font-style: italic;
  color: var(--text-muted);
}

.nota-parcial {
  font-size: 12px;
  color: var(--warning-text);
  background: var(--warning-bg);
  border: 1px solid var(--warning-border);
  border-radius: 20px;
  padding: 2px 10px;
}

/* La nota que SÍ se queda visible: la que dice qué pasa si no se hace (MOR-161).
   Se afina para que no compita con el campo, pero se sigue leyendo.

   ⚠️ NO lleva `max-width`, y antes sí: tenía `62ch`, la medida clásica de
   lectura. Daniel lo señaló DOS veces —«no entiendo por qué los textos de ayuda
   están recortados y no ocupan el ancho»— y tenía razón las dos.

   La medida de lectura se gana su lugar en PROSA LARGA. Acá el texto más largo
   de los 74 usos son 90 caracteres, o sea línea y media: cortar a 62 no evita
   ninguna fatiga y sí deja la nota terminando en el aire, sin alinearse con el
   campo del que cuelga. En un formulario, la nota pertenece al control que
   tiene encima, y esa relación pesa más que un ideal tipográfico.

   ⚠️ Y estaba definida DOS VECES, a cuatro mil líneas de distancia. Eso es lo
   que hacía difícil de encontrar el recorte: quien leía la primera no veía el
   `max-width` de la segunda. Ahora es una sola.

   `pretty` se queda: evita la línea huérfana de dos palabras al final, y no
   cuesta ancho. */
.nota-ayuda {
  margin: var(--space-sm) 0 0;
  font-size: 13px;
  color: var(--text-tertiary);
  text-wrap: pretty;
}

@media (max-width: 600px) {
  /* ── Tablas en un teléfono ─────────────────────────────────────────────
     Dos tratamientos, y elegir mal el tratamiento es peor que no tener ninguno.

     Por defecto: la tabla SE DESLIZA de costado dentro de su propia caja. Es
     seguro para cualquier cantidad de columnas y nunca hace que la página
     entera se mueva en horizontal, que es lo que se siente roto.

     Con `--tarjetas`: cada fila se convierte en una tarjeta. Se ve mucho mejor,
     pero asume una forma concreta —el dato importante a la derecha, el resto
     apagado debajo del nombre—, así que va SOLO donde se diseñó para eso.
     Aplicarlo a una tabla de cinco columnas produce un revoltijo.

     Antes esta regla era global y se llevaba puestas las tablas nuevas. */

  .table-academic {
    display: block;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    white-space: nowrap;
  }

  .table-academic--tarjetas {
    display: table;
    overflow-x: visible;
    white-space: normal;
  }

  .table-academic--tarjetas tbody tr {
    display: grid;
    grid-template-columns: 1fr auto;
    gap: 2px var(--space-sm);
    padding: var(--space-sm) 0;
    border-bottom: 1px solid var(--border-subtle);
  }

  .table-academic--tarjetas tbody td {
    border: none;
    padding: 0;
  }

  .table-academic--tarjetas tbody td:first-child {
    font-weight: 500;
  }

  .table-academic--tarjetas tbody td:last-child {
    grid-row: 1 / span 3;
    grid-column: 2;
    align-self: center;
  }

  .table-academic--tarjetas tbody td:not(:first-child):not(:last-child) {
    font-size: 12px;
    color: var(--text-tertiary);
  }

  .table-academic--tarjetas thead {
    display: none;
  }
}

/* ============================================
   Boletines y su verificación pública
   ============================================ */

/* ---- Revisar boletines, uno por uno -------------------------------------
   El vistazo final antes de liberarlos. Las flechas a los lados y el nombre en
   el medio: es como se revisa un taco de papeles, y con treinta alumnos lo que
   importa es no perder de vista en cuál se va. */
.revisar-navegacion {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md);
  padding-bottom: var(--space-md);
  border-bottom: 1px solid var(--border-subtle);
}
.revisar-quien { text-align: center; }
/* Quiénes faltan para poder cerrar el lapso. Se listan con nombre porque quien
   revisa tiene que saber a quién perseguir: con un «faltan 3 alumnos» le toca
   buscarlos uno por uno en la lista de arriba. */
.revisar-faltantes {
  margin: var(--space-xs) 0 0;
  padding: 0;
  list-style: none;
  font-size: 13px;
  color: var(--text-secondary);
}

.revisar-faltantes li {
  display: flex;
  align-items: baseline;
  gap: var(--space-sm);
  padding: 3px 0;
  border-bottom: 1px solid var(--border-subtle);
}

.revisar-faltantes li:last-child { border-bottom: 0; }
.revisar-faltantes li span:first-child { flex: 1 1 auto; }

.revisar-estado {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: var(--space-md) 0;
}

/* ---- Lo que el colegio cobra -------------------------------------------
   Se edita en la MISMA fila donde se lee: son trece cuotas y corregir un monto
   no vale una pantalla aparte. Cada fila es su propio formulario. */
.fila-de-cobro { display: contents; }
.fila-de-cobro .form-control { min-width: 0; }
.acciones-de-cobro { display: flex; gap: 6px; white-space: nowrap; }
.acciones-de-cobro form { margin: 0; }

/* ---- Los pagos de la familia --------------------------------------------
   spec: docs/specs/pagos-de-la-familia.md

   ⚠️ El COLOR lo lleva el dato, nunca el control (regla de color, MOR-135).
   Acá el dato es «este mes está pagado / vencido / esperando», así que son
   pastillas de texto —no botones de colores— y el verde, el ámbar y el rojo
   se usan justamente donde sí les toca: sobre un hecho. */
.pastilla-de-cobro {
  display: inline-block;
  padding: 2px 9px;
  border-radius: 999px;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.01em;
  white-space: nowrap;
}
.estado-pagado, .estado-validado {
  background: var(--success-bg);
  color: var(--success-text);
}
.estado-en-revision {
  background: var(--warning-bg);
  color: var(--warning-text);
}
.estado-en-mora, .estado-rechazado {
  background: var(--danger-bg);
  color: var(--danger-text);
}
.estado-por-vencer {
  background: var(--surface-hover);
  color: var(--text-secondary);
}

/* El número vencido, en el tono del dato que hay que mirar. */
.dato-en-mora { color: var(--danger-text); font-weight: 600; }

/* Las dos cifras del pago, una al lado de la otra: lo que cubre (en la unidad
   de cuenta del colegio) y lo que se transfirió (lo que dice el voucher). No se
   comparan — son monedas distintas, y el puente lo hace quien valida. */
.equivalencia { background: var(--brand-subtle); border-radius: var(--radius-md); padding: var(--space-md); }

/* ---- La tarjeta de pago -------------------------------------------------
   Daniel: «eso se acostumbraba mucho hace años… una tarjeta de pago donde la
   persona veía que había pagado y qué le faltaba». La barra es lo que se lee
   de un vistazo; la tabla, lo que se consulta. */
.tarjeta-de-pago { margin-bottom: var(--space-lg); }

/* La pastilla de deuda en la lista de representados: va debajo del grado, no
   al lado del nombre. Al lado compite con el nombre del hijo, que es lo que la
   madre viene a buscar. */
.portal-tarjeta-hijo .pastilla-de-cobro { align-self: flex-start; margin-top: 6px; }

.barra-de-pago {
  height: 10px;
  margin: var(--space-md) 0 6px;
  border-radius: 999px;
  background: var(--surface-hover);
  overflow: hidden;
}
.barra-de-pago-lleno {
  height: 100%;
  background: var(--success-text);
  border-radius: 999px;
  transition: width var(--transition-base);
}

/* ---- Marcar qué cuotas se pagan -----------------------------------------
   ⚠️ La fila entera es el control, no solo la casilla: en un teléfono una
   casilla de 16 px es el objetivo más difícil de la pantalla. */
/* El nombre del hijo, sobre sus cuotas. Solo cuando hay más de uno: con uno
   solo sería un rótulo que no distingue nada. */
.cuotas-de-quien {
  margin: var(--space-md) 0 0;
  font-family: var(--font-serif);
  font-size: 17px;
  font-weight: 600;
}

/* El punto de «tiene cuotas vencidas» en su pestaña: el color lo lleva el DATO
   (regla de color, MOR-135), y la pestaña sigue siendo navegación. */
.pestana-en-mora { color: var(--danger-text); margin-left: 6px; font-size: 11px; }

.cuotas-a-marcar { display: grid; gap: 8px; margin-top: var(--space-sm); }
.cuota-a-marcar {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  padding: 11px 14px;
  border: 1px solid var(--border-standard);
  border-radius: var(--radius-md);
  cursor: pointer;
  min-height: 44px;
}
.cuota-a-marcar:has(input:checked) {
  border-color: var(--brand-primary);
  background: var(--brand-subtle);
}
.cuota-a-marcar:has(input:focus-visible) { outline: 2px solid var(--brand-primary); outline-offset: 2px; }
.cuota-nombre { flex: 1; font-weight: 600; }
.cuota-monto { font-variant-numeric: tabular-nums; }

/* ---- El comprobante -----------------------------------------------------
   Se ve junto a los datos, no en una lupa aparte: el trabajo de la oficina es
   comparar el número de la pantalla con el de la foto. */
.voucher {
  display: block;
  max-width: 460px;
  width: 100%;
  border: 1px solid var(--border-standard);
  border-radius: var(--radius-md);
}

/* Las dos salidas de una fila de la bandeja. «Validar» primero: es la que se
   usa 390 de cada 400 veces, y en una tabla la primera es la que el pulgar
   alcanza. */
.decidir-en-la-fila { display: flex; gap: 8px; justify-content: flex-end; }

/* «Corregir», al lado de la pastilla del estado en la tarjeta de la familia.
   Es un enlace y no un botón: compite con «Reportar un pago», que es la acción
   de esa pantalla. */
.corregir-el-pago { margin-left: 10px; font-size: 13px; text-decoration: underline; }
.decidir-en-la-fila form { margin: 0; }
.columna-de-decidir { width: 1%; white-space: nowrap; }

.decidir-el-pago {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-lg);
  align-items: flex-start;
}
.decidir-el-pago .rechazo {
  display: grid;
  gap: 6px;
  min-width: 280px;
  flex: 1;
}

/* El aviso de «falta ponerse al día»: ámbar, no rojo. Quien lo lee no hizo
   nada malo — casi siempre es alguien a quien se le pasó la fecha. */
.al-dia { border-left: 3px solid var(--warning-text); }
.al-dia-detalle { font-size: 15px; }
.al-dia-acciones { display: flex; flex-wrap: wrap; gap: var(--space-sm); margin-top: var(--space-md); }

/* ---- El directorio público de planteles ---------------------------------
   Cuatro pantallas que vive cualquiera sin sesión, con el MISMO sistema de
   diseño del panel: `card-academic`, `shared/cabecera` y `table-academic`. No
   son la portada —que es otro mundo visual, oscuro y de venta— sino un
   documento que se consulta, y ahí lo que hace falta es que se lea. */
.lista-directorio {
  list-style: none;
  margin: var(--space-md) 0 0;
  padding: 0;
  /* Veinticuatro estados en una columna son un scroll tonto en un monitor y la
     lista correcta en un teléfono: se reparte por ANCHO DISPONIBLE, no por un
     número de columnas fijo. */
  columns: 260px 3;
  column-gap: var(--space-lg);
}
.lista-directorio li {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-sm);
  padding: 7px 0;
  border-bottom: 1px solid var(--border-subtle);
  break-inside: avoid;
}
.lista-directorio a { text-decoration: none; }
.lista-directorio a:hover { text-decoration: underline; }

/* El remate: es la razón por la que el directorio existe. Va destacado sin
   gritar — el fondo del acento propio y nada más. */
.directorio-cierre { background: var(--brand-subtle); }
.directorio-cierre-titulo {
  font-family: var(--font-serif);
  font-size: 22px;
  font-weight: 600;
  margin: 0 0 var(--space-sm);
  color: var(--brand-primary);
}

/* El informe descriptivo dentro del boletín verificado: es texto corrido y en
   primaria es la mitad del documento, así que se lee como un párrafo y no como
   un dato más de la tabla. */
.verificacion-informe {
  margin-top: var(--space-lg);
  padding: var(--space-md);
  border-left: 3px solid var(--brand-primary);
  background: var(--surface-hover);
}
.verificacion-informe h3 {
  margin: 0 0 6px;
  font-family: var(--font-serif);
  font-size: 17px;
}
.verificacion-informe p { margin: 0; max-width: 68ch; }

.verificacion-sello {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: var(--success-bg);
  color: var(--success-text);
  border: 1px solid var(--success-border);
  border-radius: 20px;
  padding: 4px 14px;
  font-size: 13px;
  font-weight: 600;
}

.codigo-verificacion {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 13px;
  letter-spacing: 0.04em;
  color: var(--brand-primary);
}

/* ═══════════════════════════════════════════════════════════════════════════
   ASISTENCIA — la pantalla que tiene un minuto (MOR-111)
   ═══════════════════════════════════════════════════════════════════════════

   Un profesor la usa de pie, con treinta alumnos entrando y el teléfono en una
   mano. Todo lo de acá sale de esa restricción:

     · La fila ENTERA es el objetivo del toque, no una casilla de 20 px.
     · 52 px de alto: por encima del mínimo de 44 px que se puede tocar con el
       pulgar sin mirar, que es como se toca de verdad.
     · El estado se lee por color Y por palabra. Solo color deja afuera a quien
       no distingue rojo de verde, y en una lista de treinta nombres eso es no
       poder usar la pantalla.
*/

.asistencia-dias {
  display: flex;
  gap: var(--space-xs);
  flex-wrap: wrap;
  margin-bottom: var(--space-md);
}

.asistencia-lista {
  list-style: none;
  margin: 0 0 var(--space-lg);
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.asistencia-alumno { margin: 0; }

.asistencia-boton {
  width: 100%;
  min-height: 52px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md);
  padding: var(--space-sm) var(--space-md);
  background: var(--surface-elevated);
  border: 1px solid var(--border-standard);
  border-left: 4px solid var(--border-standard);
  border-radius: var(--radius-sm);
  font-family: inherit;
  font-size: 15px;
  color: var(--text-primary);
  text-align: left;
  cursor: pointer;
  transition: background var(--transition-fast), border-color var(--transition-fast);
  -webkit-tap-highlight-color: transparent;
}

.asistencia-boton:hover { background: var(--surface-hover); }

.asistencia-boton:focus-visible {
  outline: 2px solid var(--brand-primary);
  outline-offset: 2px;
}

.asistencia-nombre { font-weight: 500; }

.asistencia-estado {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: .04em;
  text-transform: uppercase;
  padding: 4px 10px;
  border-radius: 999px;
  white-space: nowrap;
  flex: 0 0 auto;
}

/* Presente es el estado por defecto y va callado: si los treinta gritaran, las
   dos ausencias no se verían. */
.asistencia-alumno[data-estado="presente"] .asistencia-estado {
  background: var(--surface-hover);
  color: var(--text-tertiary);
}

.asistencia-alumno[data-estado="ausente"] .asistencia-boton {
  border-left-color: var(--danger-text);
  background: var(--danger-bg);
}

.asistencia-alumno[data-estado="ausente"] .asistencia-estado {
  background: var(--danger-text);
  color: #fff;
}

.asistencia-alumno[data-estado="tarde"] .asistencia-boton {
  border-left-color: var(--warning-text);
  background: var(--warning-bg);
}

.asistencia-alumno[data-estado="tarde"] .asistencia-estado {
  background: var(--warning-text);
  color: #fff;
}

/* El pie queda pegado abajo: con treinta alumnos el botón de guardar cae fuera
   de la pantalla, y un botón que hay que buscar es un día sin registrar. */
.asistencia-pie {
  position: sticky;
  bottom: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md);
  flex-wrap: wrap;
  padding: var(--space-md) 0;
  background: var(--surface-elevated);
  border-top: 1px solid var(--border-standard);
}

.asistencia-resumen {
  font-size: 15px;
  font-weight: 600;
  color: var(--text-secondary);
  font-variant-numeric: tabular-nums;
}

.asistencia-guardar { min-height: 48px; padding-inline: var(--space-xl); }

/* ── Elegir sección ──────────────────────────────────────────────────────── */

.asistencia-secciones {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: var(--space-sm);
}

.asistencia-seccion {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-sm);
  min-height: 60px;
  padding: var(--space-md);
  background: var(--surface-elevated);
  border: 1px solid var(--border-standard);
  border-radius: var(--radius-sm);
  text-decoration: none;
  color: var(--text-primary);
  transition: border-color var(--transition-fast), background var(--transition-fast);
}

.asistencia-seccion:hover {
  border-color: var(--brand-primary);
  background: var(--surface-hover);
}

.asistencia-seccion-nombre { font-weight: 600; }
.asistencia-seccion-dato { font-size: 13px; color: var(--text-tertiary); }

.asistencia-marca-tomada {
  font-size: 12px;
  font-weight: 600;
  color: var(--success-text);
  background: var(--success-bg);
  padding: 3px 9px;
  border-radius: 999px;
}

@media (max-width: 600px) {
  .asistencia-boton { min-height: 56px; font-size: 16px; }
  .asistencia-pie { padding-bottom: var(--space-lg); }
  .asistencia-guardar { flex: 1; }
}

/* Marcas de estado en el historial y el reporte de inasistencias (MOR-111). */

.asistencia-marca {
  font-size: 12px;
  font-weight: 600;
  padding: 3px 10px;
  border-radius: 999px;
  white-space: nowrap;
}

.asistencia-marca--presente { background: var(--success-bg); color: var(--success-text); }
.asistencia-marca--ausente  { background: var(--danger-bg);  color: var(--danger-text); }
.asistencia-marca--tarde    { background: var(--warning-bg); color: var(--warning-text); }

/* El conteo de faltas es el dato que decide si hay que llamar a la casa, así que
   pesa más que el resto de la fila. */
.falta-conteo {
  font-family: var(--font-serif);
  font-size: 18px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.falta-conteo--alta { color: var(--danger-text); }

/* ═══════════════════════════════════════════════════════════════════════════
   PORTAL DEL REPRESENTANTE (MOR-110)
   ═══════════════════════════════════════════════════════════════════════════

   Es la superficie más visible del producto: dos representantes por alumno, ~700
   en un colegio de 350, contra quince profesores.

   Entran TRES VECES AL AÑO, desde un teléfono, a saber UNA cosa. No es un
   tablero: es una respuesta. De ahí que la nota sea el elemento más grande de la
   pantalla y que no haya un solo control que no lleve a un dato.
*/

/* `.portal-cabecera` se retiró en la etapa ②: el portal del representante usa
   la misma cabecera que el resto de la app. Daniel: «un único tipo de títulos
   tanto para administradores, profesores, estudiantes y representantes». */

.portal-seccion {
  margin: 0;
  color: var(--text-secondary);
  font-size: 15px;
}

.portal-punto { color: var(--text-muted); margin: 0 6px; }

.portal-lapsos { margin-bottom: var(--space-lg); }

/* ── Las materias y sus notas ────────────────────────────────────────────── */

.portal-materias {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
}

.portal-materia {
  display: flex;
  /* ⚠️ COLUMNA desde MOR-136: debajo del nombre y la nota va el plan que se
     trabajó. En fila, las competencias quedaban al lado de la nota en vez de
     abajo. Con una sola hija se ve igual que antes — `.portal-materia-fila`
     hace el `space-between` que hacía este bloque. */
  flex-direction: column;
  align-items: stretch;
  gap: var(--space-xs);
  padding: var(--space-md) 0;
  border-bottom: 1px solid var(--border-subtle);
  min-height: 56px;
}

.portal-materia:last-child { border-bottom: none; }

.portal-materia-nombre { font-size: 16px; }

/* La nota es lo que la persona vino a ver, así que es lo más grande. */
.portal-nota {
  font-family: var(--font-serif);
  font-size: 26px;
  font-weight: 600;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.portal-nota--aprobada { color: var(--success-text); }
.portal-nota--aplazada { color: var(--danger-text); }

/* "Pendiente" en letras, NUNCA un cero: el profesor no corrigió todavía, y un
   cero hace que una familia crea que su hijo está reprobando. */
.portal-pendiente {
  font-size: 13px;
  color: var(--text-tertiary);
  font-style: italic;
  white-space: nowrap;
}

.portal-justificada {
  font-size: 11px;
  color: var(--text-tertiary);
  margin-left: var(--space-xs);
}

/* ── Elegir hijo ─────────────────────────────────────────────────────────── */

.portal-lista-hijos { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-sm); }

.portal-tarjeta-hijo {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-height: 68px;
  justify-content: center;
  padding: var(--space-md);
  background: var(--surface-elevated);
  border: 1px solid var(--border-standard);
  border-radius: var(--radius-md);
  text-decoration: none;
  color: var(--text-primary);
  transition: border-color var(--transition-fast);
}

.portal-tarjeta-hijo:hover { border-color: var(--brand-primary); color: var(--text-primary); }

.portal-tarjeta-nombre { font-family: var(--font-serif); font-size: 19px; font-weight: 600; }
.portal-tarjeta-seccion { font-size: 14px; color: var(--text-secondary); }

/* ── Hijos en colegios distintos ─────────────────────────────────────────── */

.portal-colegios { display: flex; flex-wrap: wrap; gap: var(--space-xs); }

.portal-colegio {
  padding: var(--space-xs) var(--space-md);
  min-height: 44px;
  background: var(--surface-elevated);
  border: 1px solid var(--border-standard);
  border-radius: var(--radius-sm);
  font-family: inherit;
  font-size: 14px;
  color: var(--text-primary);
  cursor: pointer;
}

.portal-colegio--activo {
  border-color: var(--brand-primary);
  background: var(--brand-subtle);
  color: var(--brand-primary);
  font-weight: 600;
}

.portal-boletines { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-xs); }

.portal-boletines li {
  display: flex;
  justify-content: space-between;
  gap: var(--space-md);
  padding: var(--space-sm) 0;
  border-bottom: 1px solid var(--border-subtle);
}

.portal-boletin-codigo {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 13px;
  color: var(--text-tertiary);
  letter-spacing: .06em;
}

@media (max-width: 600px) {
  .portal-materia { min-height: 60px; }
  .portal-nota { font-size: 30px; }
  .portal-tarjeta-hijo { min-height: 72px; }
}

/* Selección de alumnos al dar de alta un representante (MOR-110). */
.representante-alumnos {
  max-height: 260px;
  overflow-y: auto;
  border: 1px solid var(--border-standard);
  border-radius: var(--radius-sm);
  padding: var(--space-xs);
  display: flex;
  flex-direction: column;
}

.representante-alumno {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  min-height: 40px;
  padding: 0 var(--space-xs);
  cursor: pointer;
  border-radius: var(--radius-sm);
}

.representante-alumno:hover { background: var(--surface-hover); }
.representante-alumno input { margin: 0; }

.detalle { display: grid; grid-template-columns: auto 1fr; gap: var(--space-xs) var(--space-lg); margin: 0; }
.detalle dt { font-weight: 600; color: var(--text-secondary); }
.detalle dd { margin: 0; }

/* ═══════════════════════════════════════════════════════════════════════════
   ADMINISTRACIÓN DE LA PLATAFORMA (MOR-133)
   ═══════════════════════════════════════════════════════════════════════════

   Deliberadamente distinta al panel de un colegio, y no por gusto: quien entra
   acá tiene que saber en tres segundos que NO está mirando el panel de un
   colegio. Cuando las dos se veían iguales, confundirlas era cuestión de tiempo
   — y confundirlas fue exactamente lo que produjo cuatro fugas.

   El panel del colegio es papel crema y azul institucional. Este es oscuro,
   denso, y con la tipografía de datos por delante: es una consola de operación,
   no un documento académico.
*/

.siga-admin {
  --siga-fondo:  #10151c;
  --siga-panel:  #171e27;
  --siga-borde:  #26303c;
  --siga-texto:  #e6ecf3;
  --siga-tenue:  #8fa0b3;
  --siga-acento: #5eead4;
  --siga-alerta: #fbbf24;

  /* Los tres estados que faltaban por nombrar. Estaban escritos como hex crudo
     en `.siga-estado--*`, que es la forma de que dentro de seis meses nadie
     sepa si ese `#f87171` es "vencida" o simplemente un rojo que quedó ahí.
     El panel de SIGA no comparte la paleta del colegio a propósito —tiene que
     verse distinto en tres segundos, ver `MOR-133`— pero sus tokens se nombran
     igual: por lo que significan. */
  --siga-ok:       #34d399;   /* suscripción activa */
  --siga-mal:      #f87171;   /* vencida */
  --siga-apagado:  #6b7280;   /* cancelada — un hecho, no una alarma */
  --siga-apagado-texto: #9ca3af;

  background: var(--siga-fondo);
  color: var(--siga-texto);
  min-height: 100vh;
  font-family: var(--font-sans);
}

.siga-admin h1 { font-size: 26px; color: var(--siga-texto); margin-bottom: var(--space-xs); }
.siga-admin h2 { font-size: 18px; color: var(--siga-texto); margin: 0; }
.siga-admin h3 { font-size: 15px; color: var(--siga-tenue); margin: var(--space-lg) 0 var(--space-xs); }
.siga-admin a { color: var(--siga-acento); }

.siga-nav { background: var(--siga-panel); border-bottom: 1px solid var(--siga-borde); }

.siga-nav-inner {
  max-width: 1180px; margin: 0 auto; padding: var(--space-sm) var(--space-lg);
  display: flex; align-items: center; gap: var(--space-lg); flex-wrap: wrap;
}

.siga-marca { display: flex; align-items: baseline; gap: 8px; text-decoration: none; }
.siga-marca-nombre { font-weight: 800; font-size: 19px; color: var(--siga-texto); letter-spacing: .02em; }
.siga-marca-etiqueta {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 10px; text-transform: uppercase; letter-spacing: .12em;
  color: var(--siga-acento); border: 1px solid var(--siga-acento);
  padding: 1px 6px; border-radius: 2px;
}

.siga-menu { display: flex; gap: var(--space-xs); list-style: none; margin: 0; padding: 0; }
.siga-menu-link {
  display: block; padding: 6px 12px; border-radius: var(--radius-sm);
  color: var(--siga-tenue); text-decoration: none; font-size: 14px;
}
.siga-menu-link:hover { background: var(--siga-borde); color: var(--siga-texto); }
.siga-menu-link.active { background: var(--siga-borde); color: var(--siga-acento); font-weight: 600; }

.siga-sesion { margin-left: auto; display: flex; align-items: center; gap: var(--space-sm); }
.siga-quien { font-size: 13px; color: var(--siga-tenue); }
.siga-rol {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 10px; text-transform: uppercase; letter-spacing: .08em;
  margin-left: 6px; color: var(--siga-acento);
}

/* La franja es el reverso exacto de la barra de contexto del colegio: allá dice
   EN QUÉ colegio estás, acá dice que estás fuera de todos ellos. */
.siga-aviso { background: var(--siga-borde); border-bottom: 1px solid var(--siga-borde); }
.siga-aviso-inner {
  max-width: 1180px; margin: 0 auto; padding: 8px var(--space-lg);
  font-size: 12.5px; color: var(--siga-tenue);
}
.siga-aviso strong { color: var(--siga-acento); }

.siga-contenido { max-width: 1180px; margin: 0 auto; padding: var(--space-xl) var(--space-lg) 80px; }
.siga-subtitulo { color: var(--siga-tenue); margin-bottom: var(--space-lg); }

.siga-cifras {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: var(--space-sm); margin: var(--space-lg) 0 var(--space-xl);
}
.siga-cifra {
  background: var(--siga-panel); border: 1px solid var(--siga-borde);
  border-radius: var(--radius-md); padding: var(--space-md);
  display: flex; flex-direction: column; gap: 2px;
}
.siga-cifra--atencion { border-color: var(--siga-alerta); }
.siga-cifra--atencion .siga-cifra-numero { color: var(--siga-alerta); }
.siga-cifra-numero {
  font-size: 30px; font-weight: 700; line-height: 1;
  font-variant-numeric: tabular-nums; color: var(--siga-acento);
}
.siga-cifra-que { font-size: 13px; color: var(--siga-tenue); }

.siga-bloque {
  background: var(--siga-panel); border: 1px solid var(--siga-borde);
  border-radius: var(--radius-md); padding: var(--space-lg); margin-bottom: var(--space-lg);
}
.siga-bloque-cab {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-md); flex-wrap: wrap; margin-bottom: var(--space-md);
}
.siga-vacio { color: var(--siga-tenue); margin: 0; }
.siga-nota { color: var(--siga-tenue); font-size: 13.5px; max-width: 74ch; }
.siga-nota code { background: var(--siga-borde); color: var(--siga-acento); }

.siga-tabla-envoltura { overflow-x: auto; }
.siga-tabla { width: 100%; border-collapse: collapse; font-size: 14px; min-width: 520px; }
.siga-tabla th {
  text-align: left; padding: 8px 12px 8px 0; font-size: 10.5px; font-weight: 600;
  text-transform: uppercase; letter-spacing: .08em; color: var(--siga-tenue);
  border-bottom: 1px solid var(--siga-borde);
}
.siga-tabla td { padding: 10px 12px 10px 0; border-bottom: 1px solid var(--siga-borde); }
.siga-tabla tr:last-child td { border-bottom: none; }
.siga-tabla .num { text-align: right; font-variant-numeric: tabular-nums; }
.siga-tabla .mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12.5px; }
.siga-tabla .apagado { color: var(--siga-tenue); }

.siga-estado {
  font-size: 11px; padding: 2px 8px; border-radius: 999px;
  border: 1px solid var(--siga-borde); color: var(--siga-tenue);
}
.siga-estado--pending { border-color: var(--siga-alerta); color: var(--siga-alerta); }

.siga-lista-sistemas { list-style: none; margin: 0; padding: 0; }
.siga-lista-sistemas li {
  display: flex; justify-content: space-between; gap: var(--space-md);
  padding: 6px 0; border-bottom: 1px solid var(--siga-borde); font-size: 14px;
}

.siga-detalle { display: grid; grid-template-columns: auto 1fr; gap: 6px var(--space-lg); margin: 0; }
.siga-detalle dt { color: var(--siga-tenue); font-size: 13px; }
.siga-detalle dd { margin: 0; }

.siga-cita {
  margin: 0; padding: var(--space-md); background: var(--siga-fondo);
  border-left: 3px solid var(--siga-acento); border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  color: var(--siga-texto); font-size: 14px;
}

.siga-fieldset {
  border: 1px solid var(--siga-borde); border-radius: var(--radius-md);
  padding: var(--space-lg); margin: 0 0 var(--space-lg);
}
.siga-fieldset legend { padding: 0 var(--space-xs); color: var(--siga-acento); font-size: 13px; font-weight: 600; }
.siga-admin .form-label { color: var(--siga-tenue); }
.siga-admin .form-control, .siga-admin .form-select {
  background: var(--siga-fondo); border-color: var(--siga-borde); color: var(--siga-texto);
}
.siga-admin .form-text { color: var(--siga-tenue); }
.siga-form-inline { display: flex; gap: var(--space-sm); align-items: center; flex-wrap: wrap; }

@media (max-width: 640px) {
  .siga-nav-inner { gap: var(--space-sm); }
  .siga-sesion { margin-left: 0; width: 100%; justify-content: space-between; }
}

/* Cuando la matrícula se pasó del plan facturado (MOR-108). No es un error: el
   plan se observa siempre y se cobra al renovar. */
.siga-desfase { font-size: 11px; color: var(--siga-alerta); }
.siga-estado--activa    { border-color: var(--siga-ok); color: var(--siga-ok); }
.siga-estado--en_gracia { border-color: var(--siga-alerta); color: var(--siga-alerta); }
.siga-estado--vencida   { border-color: var(--siga-mal); color: var(--siga-mal); }
.siga-estado--cancelada { border-color: var(--siga-apagado); color: var(--siga-apagado-texto); }

/* Aviso de suscripción (MOR-108). Solo aparece cuando hay algo que decir: un
   cartel permanente de "estás al día" se deja de ver, y el día que dice otra
   cosa tampoco se ve. */
.aviso-suscripcion {
  background: var(--warning-bg);
  border-bottom: 1px solid var(--warning-border);
  color: var(--warning-text);
}

.aviso-suscripcion--vencida {
  background: var(--danger-bg);
  border-bottom-color: var(--danger-border);
  color: var(--danger-text);
}

.aviso-suscripcion-inner {
  max-width: 1200px;
  margin: 0 auto;
  padding: var(--space-xs) var(--space-lg);
  font-size: 13px;
}

/* Alguien de SIGA adentro de un colegio. Imposible de no ver: si quien da
   soporte olvida que está en un sistema ajeno, el aviso no sirve — y el colegio
   lo ve igual, que es lo que lo hace defendible frente al cliente. */
.aviso-visita {
  background: #4c1d95;
  color: #ede9fe;
  border-bottom: 2px solid #7c3aed;
}

.aviso-visita-inner {
  max-width: 1200px;
  margin: 0 auto;
  padding: var(--space-xs) var(--space-lg);
  font-size: 13px;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm);
  align-items: center;
}

.aviso-visita-salir {
  margin-left: auto;
  background: transparent;
  border: 1px solid #a78bfa;
  color: #ede9fe;
  border-radius: var(--radius-sm);
  padding: 2px 10px;
  font-size: 12px;
  font-family: inherit;
  cursor: pointer;
}

.aviso-visita-salir:hover { background: #6d28d9; }

/* El escudo del colegio (identidad). Chico y al lado del nombre: acompaña, no
   compite — el nombre es el que se lee. */
/* ⚠️ Más chico desde que el colegio vive DENTRO de la barra fina (MOR-184):
   antes tenía una franja propia de 44 px de alto y ahora comparte 52 px con
   todo lo demás. */
.contexto-escudo {
  width: 26px;
  height: 26px;
  border-radius: 50%;
  object-fit: cover;
  flex-shrink: 0;
}

/* ── Identidad del colegio ───────────────────────────────────────────────── */

.identidad {
  display: grid;
  grid-template-columns: 180px 1fr;
  gap: var(--space-xl);
  align-items: start;
}

.identidad-escudo { display: flex; flex-direction: column; gap: var(--space-xs); align-items: center; }

.identidad-escudo-imagen {
  width: 160px;
  height: 160px;
  object-fit: contain;
  border: 1px solid var(--border-standard);
  border-radius: var(--radius-md);
  padding: var(--space-sm);
  background: var(--surface-elevated);
}

.identidad-escudo-vacio {
  width: 160px;
  height: 160px;
  display: grid;
  place-items: center;
  border: 2px dashed var(--border-emphasis);
  border-radius: var(--radius-md);
  color: var(--text-tertiary);
  font-size: 14px;
}

/* Neutro: quitar el escudo es reversible —se vuelve a subir— y no destruye
   ningún dato académico. El rojo acá era puro énfasis sobre un control. */
.identidad-quitar { font-size: 13px; color: var(--text-secondary); text-decoration: underline; }
.identidad-vacio-nota { font-size: 12px; text-align: center; max-width: 160px; margin: 0; }

@media (max-width: 640px) {
  .identidad { grid-template-columns: 1fr; }
  .identidad-escudo { align-items: flex-start; }
}

/* ==========================================================================
   SOLICITUD DE DEMOSTRACIÓN — la página que llega por WhatsApp (MOR-135)
   ==========================================================================
   Se diseña para UN caso: una directora, de pie, en un teléfono, con datos
   móviles. De ahí sale todo lo de acá abajo.

     · Una sola columna hasta 900 px. El formulario primero, SIEMPRE: lo que
       está arriba del pliegue es el primer campo, no un titular decorativo.
     · Campos de 52 px de alto. El dedo no es un puntero de ratón.
     · 16 px de tipografía en los inputs, ni uno menos: por debajo de eso
       Safari de iOS hace zoom solo al enfocar, la página salta y la persona
       pierde de vista dónde estaba escribiendo.
     · Nada de `autofocus` — abre el teclado al cargar y tapa la pantalla.
   ========================================================================== */

.solicitud {
  max-width: 1040px;
  margin: 0 auto;
  padding: var(--space-xl) var(--space-lg) var(--space-2xl);
}

.solicitud-cabecera {
  text-align: center;
  margin-bottom: var(--space-xl);
}

.solicitud-marca { width: 76px; height: 76px; margin-bottom: var(--space-md); }

.solicitud-titulo {
  font-family: var(--font-serif);
  font-size: clamp(28px, 5vw, 40px);
  line-height: 1.15;
  text-wrap: balance;
  margin: 0 0 var(--space-sm);
  color: var(--text-primary);
}

.solicitud-entrada {
  max-width: 46ch;
  margin: 0 auto;
  color: var(--text-secondary);
  font-size: 17px;
  line-height: 1.6;
  text-wrap: pretty;
}

/* El formulario primero en el DOM y en la pantalla. En escritorio se pone al
   lado el "qué pasa después"; en teléfono queda debajo, que es donde se lee
   DESPUÉS de haber decidido escribir. */
.solicitud-cuerpo {
  display: grid;
  grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr);
  gap: var(--space-xl);
  align-items: start;
}

.solicitud-formulario {
  background: var(--surface-elevated);
  border: 1px solid var(--border-standard);
  border-radius: var(--radius-lg);
  padding: var(--space-xl);
  box-shadow: 0 1px 2px rgba(44, 44, 44, 0.04);
}

.solicitud-form { display: flex; flex-direction: column; gap: var(--space-lg); }
.solicitud-form .form-group { margin: 0; display: flex; flex-direction: column; gap: 6px; }

.solicitud-form .form-control,
.solicitud-form .form-select {
  min-height: 52px;
  font-size: 16px;
}

/* "opcional" en la etiqueta, no en el placeholder: el placeholder desaparece
   al escribir, justo cuando la persona duda de si ese campo hacía falta. */
.solicitud-opcional {
  font-size: 12px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-tertiary);
  margin-top: -4px;
}

.solicitud-form .form-label { margin-bottom: 0; }
.solicitud-form .nota-ayuda { margin: 0; font-size: 13px; color: var(--text-tertiary); }

.solicitud-enviar {
  min-height: 54px;
  font-size: 17px;
  width: 100%;
  margin-top: var(--space-xs);
}

.solicitud-letra-chica {
  margin: 0;
  text-align: center;
  font-size: 13px;
  color: var(--text-tertiary);
}

.solicitud-error-titulo { margin: 0 0 var(--space-xs); font-weight: 600; }
.solicitud-errores { margin: 0; padding-left: 1.2em; }

.solicitud-aparte { display: flex; flex-direction: column; gap: var(--space-lg); }

.solicitud-aparte-titulo {
  font-family: var(--font-serif);
  font-size: 20px;
  margin: 0 0 var(--space-md);
  color: var(--text-primary);
}

.solicitud-pasos {
  background: var(--brand-subtle);
  border-radius: var(--radius-lg);
  padding: var(--space-lg);
}

/* Numerados porque SON una secuencia en el tiempo: primero le escriben, luego
   la llamada, luego decide. El número lleva información, no es adorno. */
.solicitud-lista-pasos {
  margin: 0;
  padding: 0;
  list-style: none;
  counter-reset: paso;
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
}

.solicitud-lista-pasos li {
  counter-increment: paso;
  display: grid;
  grid-template-columns: 28px 1fr;
  gap: var(--space-sm);
  align-items: start;
  line-height: 1.5;
}

.solicitud-lista-pasos li::before {
  content: counter(paso);
  grid-row: span 2;
  width: 28px;
  height: 28px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--brand-primary);
  color: #fff;
  font-size: 14px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.solicitud-lista-pasos strong { display: block; color: var(--text-primary); }
.solicitud-lista-pasos span { color: var(--text-secondary); font-size: 15px; }

.solicitud-directo { text-align: center; }
.solicitud-directo-texto { margin: 0 0 var(--space-sm); color: var(--text-secondary); font-size: 15px; }
.solicitud-whatsapp { width: 100%; min-height: 48px; }

.solicitud-confianza {
  border-top: 1px solid var(--border-subtle);
  padding-top: var(--space-lg);
  color: var(--text-tertiary);
  font-size: 14px;
  line-height: 1.6;
}
.solicitud-confianza p { margin: 0; }

.solicitud-volver { text-align: center; margin: var(--space-xl) 0 0; }

@media (max-width: 900px) {
  .solicitud { padding: var(--space-lg) var(--space-md) var(--space-xl); }

  /* `minmax(0, 1fr)` y no `1fr`. Son distintos: `1fr` es `minmax(auto, 1fr)`,
     y ese `auto` NO baja del ancho intrínseco del contenido — un `<input>`
     trae un `size` implícito de 20 caracteres. La columna se niega a encogerse,
     la tarjeta se sale por la derecha y el teléfono queda con scroll
     horizontal. El `0` es lo único que le da permiso de achicarse. */
  .solicitud-cuerpo { grid-template-columns: minmax(0, 1fr); }
  .solicitud-formulario { padding: var(--space-lg); }
}

/* Los inputs no deben imponer su ancho intrínseco a ninguna grilla. */
.solicitud-form .form-control,
.solicitud-form .form-select { width: 100%; min-width: 0; }

.solicitud-recibida {
  width: 72px;
  height: 72px;
  margin: 0 auto var(--space-md);
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--success-bg);
  border: 2px solid var(--success-border);
  color: var(--success-text);
  font-size: 34px;
  line-height: 1;
}

.solicitud-gracias {
  max-width: 52ch;
  margin: 0 auto;
  background: var(--surface-elevated);
  border: 1px solid var(--border-standard);
  border-radius: var(--radius-lg);
  padding: var(--space-xl);
  text-align: center;
  color: var(--text-secondary);
  line-height: 1.65;
}
.solicitud-gracias p { margin: 0 0 var(--space-md); }
.solicitud-gracias p:last-of-type { margin-bottom: var(--space-sm); }

/* ==========================================================================
   PORTADA PÚBLICA (MOR-135 · rediseño sobre la maqueta del diseñador)
   ==========================================================================
   La referencia es la maqueta aprobada (siga-pearl.vercel.app), y lo que se
   calcó de ahí es concreto y verificable:

     · La PALETA ya era la del sistema —#faf8f5 papel, #1e4a7a azul, #e8eef5
       azul suave, #2c2c2c tinta—. Faltaba nombrar `--ink` (#07142f), que ya
       vivía en el layout como `theme-color`, y el cielo #7cc0ff.
     · La TIPOGRAFÍA sí cambia: DM Serif Display (con su itálica de verdad,
       no sintetizada) para titulares y DM Sans para el resto. Auto-hospedadas
       en `public/fonts`; se usan ÚNICAMENTE dentro de `.portada`.
     · El RECORRIDO son las cinco secciones numeradas de la maqueta.
     · La sección de las cuatro funciones se calcó tal cual —Daniel: «esta
       vista tal cual me encanta, sus colores, formas, todo»—: encabezado a
       dos columnas, cuatro tarjetas de colores distintos con filo superior y
       esquinas RECTAS. Los iconos son propios; los de la maqueta eran signos
       tipográficos sueltos (∿ ▤ ◇ ◷).

   ⚠️ Dos piezas NO salen de la maqueta y las pidió Daniel: el fondo del hero
   —rejilla en fuga + emblema fundido— y que la maqueta del panel vaya
   LADEADA, «que tenga movimiento».

   ⚠️ Los colores cálidos de las tarjetas (ámbar, durazno) son de la PORTADA y
   no contradicen la regla de color del sistema (MOR-135): adentro, el ámbar y
   el rojo significan un dato —un alumno aplazado, una nota pendiente— y por eso
   no se gastan en cromo. Acá no hay datos de nadie: es una tapa.
   ========================================================================== */

.portada {
  --ink: #07142f;
  --cielo: #7cc0ff;
  --portada-serif: 'DM Serif Display', Georgia, 'Times New Roman', serif;
  --portada-sans: 'DM Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;

  background: var(--surface-canvas);
  font-family: var(--portada-sans);
  font-size: 16px;
}

/* La portada es lo único de la app que va de borde a borde: el contenedor
   general le mete 48 px a cada lado y el hero quedaba flotando en una isla. */
.container-academic:has(> .portada) { padding: 0; }

/* Y por lo mismo se queda con su propio encabezado: la barra blanca del
   layout, encima de un hero oscuro, partía el banner en dos y repetía el logo. */
body:has(.portada) .barra-superior { display: none; }

.portada-inner {
  max-width: 1140px;
  margin: 0 auto;
  padding: 0 clamp(20px, 5vw, 44px);
}

/* ---- Encabezado y acceso ------------------------------------------------
   ⚠️ «Entrar» es un BOTÓN y no un enlace más de la lista. Daniel: «el ingreso
   al sistema debe ser evidente». Quien llega a la portada de un sistema que ya
   usa no viene a leer: viene a entrar. Aparece tres veces —acá, en el hero y
   en el cierre— y esa repetición es deliberada. */
.portada-nav {
  position: relative;
  z-index: 3;
  display: flex;
  align-items: center;
  gap: clamp(16px, 3vw, 32px);
  max-width: 1140px;
  margin: 0 auto;
  padding: 20px clamp(20px, 5vw, 44px);
}

.portada-nav-marca {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-size: 19px;
  font-weight: 700;
  color: #fff;
  text-decoration: none;
}
.portada-nav-marca:hover { color: #fff; }
.portada-nav-logo { width: 30px; height: 30px; display: block; }

.portada-nav-enlaces {
  display: flex;
  align-items: center;
  gap: clamp(14px, 2.4vw, 30px);
  margin-left: auto;
  margin-block: 0;
  padding: 0;
  list-style: none;
}
.portada-nav-enlaces a {
  font-size: 15px;
  color: rgba(255, 255, 255, 0.72);
  text-decoration: none;
  transition: color var(--transition-fast);
}
.portada-nav-enlaces a:hover { color: #fff; }

.portada-nav-entrar {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 9px 18px;
  border: 1px solid rgba(124, 192, 255, 0.55);
  border-radius: 999px;
  background: rgba(124, 192, 255, 0.12);
  font-size: 15px;
  font-weight: 500;
  color: #fff;
  text-decoration: none;
  white-space: nowrap;
  transition: background var(--transition-fast), border-color var(--transition-fast);
}
.portada-nav-entrar:hover { background: var(--cielo); border-color: var(--cielo); color: var(--ink); }

@media (max-width: 860px) {
  .portada-nav-enlaces li:not(.portada-nav-entrar-item) { display: none; }
}

/* ---- Hero ----------------------------------------------------------------
   Cuatro capas, y el orden es lo que hace que funcione:
     0 · REJILLA — hoja cuadriculada acostada en perspectiva, desvanecida.
     1 · EMBLEMA — ver la nota de la máscara más abajo.
     2 · TEXTO
     3 · MAQUETA del panel, ladeada. */
.portada-hero {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  background: var(--ink);
  padding-bottom: clamp(40px, 6vw, 72px);
}

/* ⚠️ La ALTURA es propia y no un porcentaje del hero, y esa es la diferencia
   entre que la rejilla se vea y que desaparezca.

   Estuvo escrita como `inset: -30% -60% -12%`, y funcionó hasta que el hero
   creció ~700 px al recibir la maqueta del panel. Los porcentajes crecieron con
   él: el `transform-origin: 50% 100%` —que es el HORIZONTE de la fuga— se fue
   muy por debajo del borde inferior de la pantalla, y las líneas quedaron
   apretadas contra el infinito hasta borrarse. Con una altura fija, el horizonte
   cae siempre bajo el bloque de texto, mida lo que mida el resto del hero. */
.portada-hero-rejilla {
  position: absolute;
  top: -16%;
  left: -60%;
  right: -60%;
  height: clamp(820px, 106vh, 1180px);
  z-index: 0;
  background-image:
    linear-gradient(to right, rgba(124, 192, 255, 0.30) 1px, transparent 1px),
    linear-gradient(to bottom, rgba(124, 192, 255, 0.30) 1px, transparent 1px);
  background-size: 54px 54px;
  transform: perspective(700px) rotateX(56deg);
  transform-origin: 50% 100%;
  -webkit-mask-image: radial-gradient(ellipse 62% 58% at 50% 42%, #000 20%, transparent 76%);
  mask-image: radial-gradient(ellipse 62% 58% at 50% 42%, #000 20%, transparent 76%);
  pointer-events: none;
}

/* ⚠️ La MÁSCARA es lo que borra el rectángulo, no el `screen` ni la opacidad.
   Se intentó primero solo con `mix-blend-mode: screen` y se veía un cuadrado
   claro alrededor del engranaje: `screen` ACLARA, y el azul de fondo del JPG
   (#0b2545) es más claro que el `--ink` del hero (#07142f), así que el recorte
   quedaba dibujado con MÁS nitidez, no con menos. `screen` solo desaparece
   sobre negro puro. Con la máscara radial el archivo se desvanece hacia sus
   bordes y el blend hace lo único que tiene que hacer: fundir el brillo. */
.portada-emblema {
  position: absolute;
  left: 50%;
  top: clamp(190px, 31vw, 320px);
  z-index: 1;
  width: clamp(340px, 52vw, 640px);
  height: auto;
  transform: translate(-50%, -50%);
  opacity: 0.5;
  mix-blend-mode: screen;
  -webkit-mask-image: radial-gradient(circle at 50% 47%, #000 24%, rgba(0, 0, 0, 0.45) 44%, transparent 66%);
  mask-image: radial-gradient(circle at 50% 47%, #000 24%, rgba(0, 0, 0, 0.45) 44%, transparent 66%);
  pointer-events: none;
  user-select: none;
}

.portada-hero-inner {
  position: relative;
  z-index: 2;
  max-width: 820px;
  margin: 0 auto;
  padding: clamp(40px, 7vw, 88px) clamp(20px, 5vw, 44px) 0;
  text-align: center;
}

.portada-antetitulo {
  display: inline-block;
  margin: 0 0 var(--space-lg);
  padding: 6px 15px;
  border: 1px solid rgba(124, 192, 255, 0.35);
  border-radius: 999px;
  font-size: 13px;
  letter-spacing: 0.06em;
  color: var(--cielo);
}

/* El eslogan lo fijó Daniel y es lo único que tiene que quedar en la cabeza. */
.portada-tesis {
  font-family: var(--portada-serif);
  font-weight: 400;
  font-size: clamp(38px, 6.6vw, 74px);
  line-height: 1.06;
  letter-spacing: -0.02em;
  text-wrap: balance;
  margin: 0 0 var(--space-lg);
  color: #fff;
}
.portada-tesis em { font-style: italic; color: var(--cielo); }

.portada-bajada {
  max-width: 58ch;
  margin: 0 auto var(--space-xl);
  font-size: clamp(16px, 1.6vw, 19px);
  line-height: 1.6;
  color: rgba(255, 255, 255, 0.74);
}

.portada-acciones {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-sm) var(--space-md);
  margin-bottom: var(--space-lg);
}

.portada-cta {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-height: 50px;
  padding: 0 26px;
  border: 1px solid transparent;
  border-radius: 999px;
  font-size: 16px;
  font-weight: 500;
  text-decoration: none;
  transition: background var(--transition-fast), color var(--transition-fast);
}
.portada-cta--principal { background: var(--cielo); color: var(--ink); }
.portada-cta--principal:hover { background: #a5d4ff; color: var(--ink); }
.portada-cta--fantasma { background: transparent; border-color: rgba(255, 255, 255, 0.28); color: #fff; }
.portada-cta--fantasma:hover { background: rgba(255, 255, 255, 0.10); color: #fff; }

/* ---- La puerta para registrarse ------------------------------------------
   Reemplaza al sello del precio, que Daniel sacó del hero: «se ve como
   antipático, como que si le estoy cobrando antes de sentarse».

   ⚠️ Es texto, no un botón, y esa es toda la decisión de diseño. Un tercer
   botón le compite a «Agende una demostración», que es la conversión que se
   busca; un enlace subrayado se ve, se entiende y no pelea. Daniel: «evidente
   pero discreto».

   Vive sobre los DOS fondos oscuros de la portada —el hero y el cierre— así
   que el color sale de la misma familia translúcida que el resto. */
.portada-alta {
  margin: 0;
  font-size: 15px;
  color: rgba(255, 255, 255, 0.62);
}
.portada-alta-enlace {
  color: #fff;
  font-weight: 600;
  text-decoration: underline;
  text-underline-offset: 4px;
  text-decoration-color: rgba(255, 255, 255, 0.32);
  transition: text-decoration-color var(--transition-fast), color var(--transition-fast);
}
.portada-alta-enlace:hover,
.portada-alta-enlace:focus-visible {
  color: var(--cielo);
  text-decoration-color: var(--cielo);
}

/* ---- La maqueta del panel, ladeada --------------------------------------
   Daniel: «eso me encanta, incluso ladeado, que tenga movimiento». La
   inclinación es 3D de verdad —`rotateY` con perspectiva— y no un `rotate`
   plano: lo que se busca es que la pantalla parezca un objeto puesto sobre la
   mesa, no una imagen torcida.

   ⚠️ Todo lo que se ve dentro es un EJEMPLO. Va `aria-hidden` y con su propio
   texto para quien no la ve: son cifras inventadas para mostrar la forma de la
   pantalla, y presentarlas como datos de un colegio real sería mentir. */
/* Texto que solo leen los lectores de pantalla. Hace falta porque la maqueta
   del panel va `aria-hidden`: son cifras de ejemplo, y sin este párrafo quien
   navega con lector se encuentra un hueco mudo en mitad del hero.

   ⚠️ `clip-path` y no `display:none`: lo oculto de verdad tampoco se lee. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.portada-maqueta {
  position: relative;
  z-index: 2;
  max-width: 900px;
  margin: clamp(36px, 6vw, 64px) auto 0;
  padding: 0 clamp(20px, 5vw, 44px);
  perspective: 1600px;
}

.portada-maqueta-marco {
  position: relative;
  background: #fff;
  border-radius: 18px;
  padding: clamp(16px, 2.4vw, 26px);
  box-shadow: 0 40px 80px rgba(0, 0, 0, 0.45), 0 4px 12px rgba(0, 0, 0, 0.3);
  transform: rotateX(4deg) rotateY(-9deg) rotate(-1.4deg);
  transform-origin: 50% 100%;
  font-size: 13px;
  color: var(--text-primary);
  text-align: left;
}

.portada-maqueta-barra {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: clamp(14px, 2vw, 22px);
}
.portada-maqueta-logo {
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  border-radius: 8px;
  background: var(--brand-primary);
  color: #fff;
  font-family: var(--portada-serif);
  font-size: 17px;
  line-height: 1;
}
.portada-maqueta-marca { font-weight: 700; font-size: 15px; letter-spacing: 0.01em; }
.portada-maqueta-avatar {
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  margin-left: auto;
  border-radius: 50%;
  background: #8db4e8;
  color: var(--ink);
  font-size: 11px;
  font-weight: 600;
}

.portada-maqueta-cab {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--space-sm);
  margin-bottom: clamp(12px, 2vw, 18px);
}
.portada-maqueta-eyebrow { margin: 0 0 2px; font-size: 12.5px; color: var(--text-tertiary); }
.portada-maqueta-saludo {
  margin: 0;
  font-family: var(--portada-serif);
  font-weight: 400;
  font-size: clamp(21px, 2.6vw, 28px);
  line-height: 1.1;
  color: var(--ink);
}
.portada-maqueta-anio {
  margin-left: auto;
  padding: 7px 13px;
  border: 1px solid var(--border-standard);
  border-radius: 8px;
  font-size: 12.5px;
  color: var(--text-secondary);
  white-space: nowrap;
}

.portada-maqueta-tarjetas {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(10px, 1.6vw, 16px);
  margin-bottom: clamp(10px, 1.6vw, 16px);
}
.portada-maqueta-tarjeta {
  background: #f1f2f0;
  border-radius: 10px;
  padding: clamp(12px, 1.8vw, 18px);
}
.portada-maqueta-tarjeta dt { font-size: 12.5px; color: var(--text-secondary); margin-bottom: 4px; }
.portada-maqueta-tarjeta dd { margin: 0; }
.portada-maqueta-dato {
  display: block;
  font-size: clamp(26px, 3.4vw, 38px);
  font-weight: 700;
  line-height: 1;
  letter-spacing: -0.02em;
  color: var(--ink);
}
.portada-maqueta-pie { display: block; margin-top: 8px; font-size: 12px; color: #3f7a52; }

.portada-maqueta-grafico {
  border: 1px solid var(--border-subtle);
  border-radius: 10px;
  padding: clamp(12px, 1.8vw, 18px);
}
.portada-maqueta-grafico-cab {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  margin-bottom: clamp(10px, 1.6vw, 16px);
}
.portada-maqueta-grafico-titulo { margin: 0; font-size: 14px; font-weight: 700; color: var(--ink); }
.portada-maqueta-leyenda {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  color: var(--text-secondary);
}
.portada-maqueta-leyenda::before {
  content: "";
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--ink);
}

/* Las barras son `div` con altura en porcentaje: un gráfico de siete valores
   no justifica una librería, y una imagen no se adapta al ancho. */
.portada-maqueta-barras {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  align-items: end;
  gap: clamp(6px, 1.4vw, 14px);
  height: clamp(90px, 13vw, 130px);
  border-bottom: 1px solid var(--border-subtle);
  padding-bottom: 2px;
}
.portada-maqueta-barra {
  border-radius: 5px 5px 0 0;
  background: #3f5f8a;
}
.portada-maqueta-barra--claro { background: #8db4e8; }
.portada-maqueta-dias {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: clamp(6px, 1.4vw, 14px);
  margin: 8px 0 0;
  padding: 0;
  list-style: none;
  font-size: 11.5px;
  color: var(--text-tertiary);
  text-align: center;
}

.portada-maqueta-cierre {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-sm);
  margin-top: clamp(10px, 1.6vw, 16px);
  font-size: 12.5px;
  color: var(--text-secondary);
}
.portada-maqueta-cierre .portada-maqueta-ver {
  margin-left: auto;
  font-weight: 600;
  color: var(--brand-primary);
}

/* Las dos notas que se salen del marco: son las que dan la sensación de que la
   pantalla está viva. Se ocultan en pantallas chicas, donde solo estorban. */
.portada-maqueta-nota {
  position: absolute;
  z-index: 2;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 11px 15px;
  border-radius: 12px;
  background: #fff;
  box-shadow: 0 14px 34px rgba(0, 0, 0, 0.28);
  font-size: 12.5px;
  line-height: 1.35;
  text-align: left;
}
.portada-maqueta-nota strong { display: block; font-size: 13.5px; color: var(--ink); }
.portada-maqueta-nota span { color: var(--text-tertiary); }
.portada-maqueta-nota-marca {
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  flex-shrink: 0;
  border-radius: 50%;
  font-size: 14px;
}
.portada-maqueta-nota--arriba { top: 6%; right: -2%; transform: rotate(-1deg); }
.portada-maqueta-nota--arriba .portada-maqueta-nota-marca { background: #e4efe7; color: #3f7a52; }
.portada-maqueta-nota--abajo { bottom: 14%; left: -3%; transform: rotate(-1deg); }
.portada-maqueta-nota--abajo .portada-maqueta-nota-marca { background: #fbf0d4; color: #a8791d; }

@media (max-width: 860px) {
  .portada-maqueta-marco { transform: none; }
  .portada-maqueta-nota { display: none; }
  .portada-maqueta-tarjetas { grid-template-columns: 1fr; }
}

/* ---- Ritmo de las secciones --------------------------------------------- */
.portada-seccion { padding: clamp(56px, 8vw, 108px) 0; }
.portada-seccion--papel { background: var(--surface-elevated); }

/* ⚠️ El encabezado va a DOS COLUMNAS, y es la respuesta a por qué los párrafos
   «se acortan siempre»: un párrafo se lee bien hasta unos 65 caracteres por
   línea, así que un `max-width` es obligatorio. Puesto debajo de un título que
   sí ocupa el ancho completo, ese límite deja media página vacía y parece un
   error. Al lado del título, el mismo párrafo llena la fila. */
.portada-encabezado {
  display: grid;
  grid-template-columns: minmax(0, 1.1fr) minmax(0, 0.9fr);
  gap: clamp(20px, 4vw, 64px);
  align-items: end;
  margin-bottom: clamp(32px, 5vw, 56px);
}
.portada-encabezado--solo { grid-template-columns: minmax(0, 1fr); }
@media (max-width: 860px) {
  .portada-encabezado { grid-template-columns: 1fr; align-items: start; }
}

.portada-rotulo {
  display: block;
  margin: 0 0 var(--space-md);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--brand-primary);
}

.portada-titulo {
  font-family: var(--portada-serif);
  font-weight: 400;
  font-size: clamp(30px, 4.4vw, 52px);
  line-height: 1.08;
  letter-spacing: -0.015em;
  text-wrap: balance;
  margin: 0;
  color: var(--ink);
}
/* La segunda línea en itálica azul es la firma tipográfica de la maqueta. */
.portada-titulo em {
  display: block;
  font-style: italic;
  color: var(--brand-primary);
}

.portada-parrafo {
  max-width: 62ch;
  margin: 0 0 var(--space-md);
  font-size: 16.5px;
  line-height: 1.72;
  color: var(--text-secondary);
}
.portada-parrafo:last-child { margin-bottom: 0; }


/* ---- Las tres cifras ----------------------------------------------------
   ⚠️ Antes acá vivían las del MERCADO —3.900 colegios, 9 % con portal—, y
   Daniel las sacó con la razón exacta: «ese dato es relevante para mí, que
   quiero vender SIGA; para mi cliente, el director, no». Un director ya sabe
   cómo está su colegio; lo que no sabe es cuánto le cuesta esto. Las tres de
   ahora son del PRODUCTO y se calculan solas desde `Subscription::PLANES`. */
.portada-cifras {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 1px;
  background: var(--border-standard);
  border: 1px solid var(--border-standard);
  overflow: hidden;
}
.portada-cifra { background: var(--surface-elevated); padding: var(--space-xl) var(--space-lg); }
.portada-seccion--papel .portada-cifra { background: var(--surface-canvas); }
.portada-cifra-numero {
  display: block;
  font-family: var(--portada-serif);
  font-size: clamp(40px, 5vw, 56px);
  line-height: 1;
  color: var(--brand-primary);
  margin-bottom: 12px;
}
.portada-cifra-que { display: block; font-size: 15px; line-height: 1.55; color: var(--text-secondary); }
.portada-cifra-fuente {
  display: block;
  margin-top: 8px;
  font-size: 12px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--text-muted);
}

/* ---- Las cuatro funciones -----------------------------------------------
   Calcado de la maqueta: cuatro colores, filo superior, esquinas RECTAS. El
   radio cero es parte del gesto —son fichas de papel, no tarjetas de app—. */
.portada-piezas {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(238px, 1fr));
  gap: clamp(14px, 2vw, 24px);
}
.portada-pieza {
  display: flex;
  flex-direction: column;
  border-top: 3px solid var(--pieza-filo);
  border-radius: 0;
  background: var(--pieza-fondo);
  padding: clamp(16px, 2vw, 22px) clamp(16px, 2vw, 22px) clamp(18px, 2.2vw, 24px);
}
.portada-pieza--1 { --pieza-fondo: #ffffff; --pieza-filo: #0f1b33; }
.portada-pieza--2 { --pieza-fondo: #e5eaf0; --pieza-filo: #7f9d8e; }
.portada-pieza--3 { --pieza-fondo: #fcf1d6; --pieza-filo: #e0a93a; }
.portada-pieza--4 { --pieza-fondo: #f4e5df; --pieza-filo: #2d5a8a; }

.portada-pieza-cab {
  display: flex;
  align-items: center;
  margin-bottom: clamp(22px, 3vw, 38px);
}
.portada-pieza-orden { font-size: 13px; color: var(--text-tertiary); font-variant-numeric: tabular-nums; }
.portada-pieza-icono { margin-left: auto; color: var(--pieza-filo); display: block; }

.portada-pieza-tipo {
  display: block;
  margin-bottom: 6px;
  font-size: 11.5px;
  font-weight: 500;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: var(--text-tertiary);
}
.portada-pieza h3 {
  font-family: var(--portada-serif);
  font-weight: 400;
  font-size: 24px;
  line-height: 1.18;
  margin: 0 0 var(--space-sm);
  color: var(--ink);
}
.portada-pieza p {
  margin: 0 0 var(--space-sm);
  font-size: 14.5px;
  line-height: 1.62;
  color: var(--text-secondary);
}
.portada-pieza-detalle {
  margin: 0;
  padding-top: var(--space-sm);
  border-top: 1px solid rgba(15, 27, 51, 0.12);
  font-size: 13px;
  line-height: 1.55;
  color: var(--text-tertiary);
}
.portada-pieza-mas {
  margin-top: auto;
  padding-top: clamp(18px, 2.4vw, 28px);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.04em;
  color: var(--ink);
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.portada-pieza-mas:hover { color: var(--brand-primary); }

/* ---- La diferencia ------------------------------------------------------ */
.portada-principios {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(232px, 1fr));
  gap: clamp(18px, 3vw, 32px);
  list-style: none;
  padding: 0;
  margin: 0;
}
.portada-principio { border-top: 2px solid var(--brand-primary); padding-top: var(--space-md); }
.portada-principio h3 { font-size: 16px; font-weight: 600; margin: 0 0 6px; color: var(--ink); }
.portada-principio p { margin: 0; font-size: 14.5px; line-height: 1.62; color: var(--text-secondary); }

/* ---- Planes ------------------------------------------------------------- */
.portada-lista-planes {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  gap: clamp(14px, 2vw, 22px);
}
.portada-plan {
  background: var(--surface-elevated);
  border: 1px solid var(--border-standard);
  padding: var(--space-lg);
  text-align: center;
}
.portada-seccion--papel .portada-plan { background: var(--surface-canvas); }
.portada-plan--convenir { background: var(--brand-subtle); border-color: transparent; }
.portada-seccion--papel .portada-plan--convenir { background: var(--brand-subtle); }
.portada-plan-nombre {
  margin: 0 0 var(--space-sm);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-tertiary);
}
.portada-plan-precio { margin: 0 0 6px; display: flex; align-items: baseline; justify-content: center; gap: 6px; }
.portada-plan-monto { font-family: var(--portada-serif); font-size: 48px; line-height: 1; color: var(--brand-primary); }
.portada-plan--convenir .portada-plan-monto { font-size: 30px; }
.portada-plan-moneda { font-size: 14px; color: var(--text-secondary); }
.portada-plan-cupo { margin: 0; font-size: 14px; color: var(--text-secondary); }


/* ---- Cierre ------------------------------------------------------------- */
.portada-cierre {
  background: var(--ink);
  color: #fff;
  padding: clamp(56px, 8vw, 104px) 0;
  text-align: center;
}
.portada-cierre-titulo {
  font-family: var(--portada-serif);
  font-weight: 400;
  font-size: clamp(30px, 4.4vw, 50px);
  line-height: 1.1;
  text-wrap: balance;
  margin: 0 0 var(--space-md);
  color: #fff;
}
.portada-cierre-titulo em { font-style: italic; color: var(--cielo); }
.portada-cierre-texto {
  max-width: 56ch;
  margin: 0 auto var(--space-xl);
  font-size: 17px;
  line-height: 1.65;
  color: rgba(255, 255, 255, 0.72);
}

/* ---- Pie ---------------------------------------------------------------- */
.portada-pie {
  background: var(--surface-elevated);
  border-top: 1px solid var(--border-standard);
  padding: clamp(40px, 6vw, 64px) 0 var(--space-xl);
}
.portada-pie-columnas { display: flex; flex-wrap: wrap; justify-content: space-between; gap: var(--space-xl); }
.portada-pie-logo { width: 40px; height: 40px; margin-bottom: 10px; display: block; }
.portada-pie-nombre { margin: 0; font-family: var(--portada-serif); font-size: 22px; color: var(--ink); }
.portada-pie-que { margin: 4px 0 0; font-size: 14px; color: var(--text-tertiary); max-width: 36ch; line-height: 1.6; }
.portada-pie-enlaces ul { margin: 0; padding: 0; list-style: none; display: flex; flex-direction: column; gap: var(--space-sm); }
.portada-pie-enlaces a { font-size: 15px; color: var(--text-secondary); text-decoration: none; }
.portada-pie-enlaces a:hover { color: var(--brand-primary); }
.portada-pie-legal {
  margin: var(--space-xl) 0 0;
  padding-top: var(--space-lg);
  border-top: 1px solid var(--border-subtle);
  font-size: 13px;
  color: var(--text-muted);
}
/* La versión, al lado del año y más apagada: está para leerla cuando hace
   falta, no para que compita con nada. */
.portada-pie-version {
  margin-left: 10px;
  opacity: 0.65;
  font-variant-numeric: tabular-nums;
}

@media (max-width: 640px) {
  .portada-acciones { flex-direction: column; align-items: stretch; }
  .portada-cta { justify-content: center; width: 100%; }
}

@media (prefers-reduced-motion: reduce) {
  .portada-emblema { opacity: 0.4; }
}

/* ==========================================================================
   FICHA DEL ALUMNO (MOR-135)
   ==========================================================================
   Reemplaza un `dl` del generador maquetado con clases de Bootstrap que esta
   app no carga — salía literalmente sin estilo. El orden de la pantalla es el
   de la secretaría: a quién llamar, cómo va, y recién después los datos.
   ========================================================================== */

.ficha { display: flex; flex-direction: column; gap: var(--space-lg); }

.ficha-cabecera {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-md);
  align-items: flex-start;
  justify-content: space-between;
  padding-bottom: var(--space-md);
  border-bottom: 1px solid var(--border-standard);
}

/* La cabecera de una ficha que es toda la tarjeta: sin nada debajo, la raya
   inferior no separa nada y se lee como un subrayado suelto. */
.ficha-cabecera--sola {
  padding-bottom: 0;
  border-bottom: none;
}

/* El avatar al lado del nombre, en la ficha de una persona.

   ⚠️ `min-width: 0` en el bloque del texto: sin eso, un nombre largo estira el
   flex y empuja los botones de la derecha fuera de la tarjeta. */
.ficha-identidad {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  min-width: 0;
}

.ficha-identidad > div {
  min-width: 0;
}

.ficha-nombre {
  font-family: var(--font-serif);
  font-size: clamp(24px, 3vw, 32px);
  margin: 0 0 4px;
  color: var(--text-primary);
}

.ficha-situacion { margin: 0; color: var(--text-secondary); font-size: 15px; }
.ficha-punto { margin: 0 6px; color: var(--text-muted); }
.ficha-sin-cupo { color: var(--warning-text); }

.ficha-acciones { display: flex; gap: var(--space-sm); flex-wrap: wrap; }

/* ---- A quién llamar ---- */

.ficha-responsables { list-style: none; margin: 0; padding: 0; }

.ficha-responsable {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm) var(--space-lg);
  justify-content: space-between;
  align-items: baseline;
  padding: var(--space-md) 0;
  border-bottom: 1px solid var(--border-subtle);
}
.ficha-responsable:last-child { border-bottom: none; }

.ficha-responsable-nombre { font-weight: 600; color: var(--text-primary); }

.ficha-responsable-vinculo {
  margin-left: var(--space-sm);
  font-size: 13px;
  color: var(--text-tertiary);
}

.ficha-principal {
  margin-left: var(--space-sm);
  padding: 2px 8px;
  border-radius: 999px;
  background: var(--brand-subtle);
  color: var(--brand-primary);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.05em;
  text-transform: uppercase;
}

.ficha-responsable-contacto { display: flex; flex-direction: column; align-items: flex-end; gap: 2px; }

/* El teléfono es el dato operativo de esta pantalla: se destaca y es
   pulsable. Desde el teléfono de la secretaría, `tel:` es un toque. */
.ficha-telefono {
  font-size: 17px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--brand-primary);
}

.ficha-falta { color: var(--warning-text); font-size: 14px; }
.ficha-correo { font-size: 13px; color: var(--text-tertiary); }

/* ---- Cómo va ---- */

.ficha-materias { list-style: none; margin: var(--space-md) 0 0; padding: 0; }

.ficha-materia {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--space-md);
  padding: var(--space-sm) 0;
  border-bottom: 1px solid var(--border-subtle);
}
.ficha-materia:last-child { border-bottom: none; }

.ficha-materia-nombre { color: var(--text-primary); }

.ficha-nota {
  font-size: 17px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.ficha-nota--aprobada { color: var(--success-text); }
.ficha-nota--aplazada { color: var(--danger-text); }

/* "Pendiente" en letras y en gris. Nunca un cero: sin nota quiere decir que el
   profesor no ha corregido, y confundirlos reprueba a un alumno por teléfono. */
.ficha-pendiente { font-size: 14px; color: var(--text-muted); font-style: italic; }

.ficha-promedio {
  margin: var(--space-lg) 0 0;
  padding-top: var(--space-md);
  border-top: 1px solid var(--border-standard);
  color: var(--text-secondary);
}
.ficha-promedio strong { font-size: 19px; color: var(--text-primary); font-variant-numeric: tabular-nums; }

.ficha-volver { margin: var(--space-md) 0 0; }

@media (max-width: 640px) {
  .ficha-responsable-contacto { align-items: flex-start; }
  .ficha-acciones .btn { flex: 1; }
}

/* Bloques compartidos por las fichas de grado, materia y profesor. */

.ficha-agregar { margin-top: var(--space-lg); }
.ficha-agregar-fila { display: flex; gap: var(--space-sm); align-items: stretch; margin-top: 6px; }
.ficha-agregar-fila .form-select { flex: 1; min-width: 0; }

.ficha-secciones {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm);
}

.ficha-seccion {
  display: flex;
  align-items: baseline;
  gap: var(--space-sm);
  padding: var(--space-sm) var(--space-md);
  background: var(--surface-input);
  border-radius: var(--radius-sm);
}

.ficha-seccion-nombre { font-weight: 600; }
.ficha-seccion-periodo { font-size: 13px; color: var(--text-tertiary); }

@media (max-width: 640px) {
  .ficha-agregar-fila { flex-direction: column; }
}

/* La fila de acciones de un formulario: guardar y volver, juntos y al mismo
   nivel. Antes el "Volver" vivía FUERA del formulario, en un `div.mt-3` —clase
   de Bootstrap que esta app no carga—, así que caía debajo y sin separación,
   como si fuera otra cosa. */
.form-acciones {
  display: flex;
  gap: var(--space-sm);
  align-items: center;
  flex-wrap: wrap;
  margin-top: var(--space-lg);
  padding-top: var(--space-lg);
  border-top: 1px solid var(--border-subtle);
}

@media (max-width: 640px) {
  .form-acciones { flex-direction: column-reverse; align-items: stretch; }
}

/* El bloque de borrado: separado del formulario y con su propio encabezado,
   porque no es "un botón más" de la pantalla de edición.
   Neutro, como el botón — ver la nota en `.btn-danger`. */
.borrar {
  margin-top: var(--space-xl);
  padding: var(--space-lg);
  border: 1px solid var(--border-emphasis);
  border-radius: var(--radius-md);
  background: var(--surface-input);
}

.borrar--imposible { border-color: var(--border-standard); }

.borrar-titulo {
  margin: 0 0 var(--space-xs);
  font-size: 16px;
  color: var(--text-primary);
}
.borrar--imposible .borrar-titulo { color: var(--text-secondary); }

.borrar-aviso { margin: 0 0 var(--space-md); font-size: 14px; color: var(--text-secondary); line-height: 1.6; }
.borrar-aviso:last-child { margin-bottom: 0; }

/* La ÚNICA parte roja del bloque, y la que la regla sí permite: la frase que
   nombra lo que se perdería. Ahí el rojo no adorna un control — describe un
   dato que va a dejar de existir. */
.borrar-consecuencia {
  color: var(--danger-text);
  font-weight: 500;
}

/* Los grupos del menú, todos plegables (MOR-149).
   `<details>` nativo: anda sin JavaScript, responde al teclado solo, y no hay
   un estado paralelo que se pueda desincronizar con lo que se ve.
   El JavaScript solo RECUERDA cuál quedó plegado; abrir y cerrar es del
   navegador. */
.sidebar-grupo { margin-bottom: var(--space-lg); }

.sidebar-grupo > summary {
  cursor: pointer;
  list-style: none;
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  user-select: none;
}
.sidebar-grupo > summary::-webkit-details-marker { display: none; }

/* El triángulo lo dibujamos nosotros para que gire: el nativo no se puede
   animar ni recolorear de forma consistente entre navegadores. */
.sidebar-grupo > summary::before {
  content: "";
  width: 0;
  height: 0;
  border-left: 5px solid currentColor;
  border-top: 4px solid transparent;
  border-bottom: 4px solid transparent;
  transition: transform var(--transition-fast);
}
.sidebar-grupo[open] > summary::before { transform: rotate(90deg); }

.sidebar-grupo > summary:hover { color: var(--brand-primary); }
.sidebar-grupo > summary:focus-visible {
  outline: 2px solid var(--brand-primary);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

/* La fila del listado es el enlace (MOR-135).
   Antes cada fila terminaba en una columna "Acciones" con botones de 40 px.
   Ahora el destino es el NOMBRE, que es un objetivo mucho más grande y el
   lugar donde el ojo ya estaba. */
.table-academic tbody td:first-child a {
  font-weight: 500;
  color: var(--text-primary);
  text-decoration: none;
}

.table-academic tbody td:first-child a:hover {
  color: var(--brand-primary);
  text-decoration: underline;
}

/* La fila entera reacciona, para que se note que es tocable antes de tocarla. */
.table-academic tbody tr:hover { background: var(--surface-hover); }

/* Un encabezado que es lo ÚNICO de su tarjeta no necesita separarse de nada:
   su margen y su filete dibujaban un hueco vacío debajo del título, en toda
   pantalla que arranca con "título + botón" y nada más. */
.card-academic-header:last-child {
  margin-bottom: 0;
  padding-bottom: 0;
  border-bottom: none;
}

/* ==========================================================================
   CHIP DE ESTADO (MOR-135 · la regla de color)
   ==========================================================================
   El único lugar del panel del colegio donde un color semántico envuelve algo
   que no es un número. Existe porque había dos parches sueltos:

     · `enrollments/index` pintaba el ámbar **a mano** (`#f57f17`), fuera del
       sistema de tokens.
     · `dashboards/estudiante` usaba `.status-badge.status-pendiente`, dos
       clases que **no existen en ninguna hoja**, con un `style` inline que
       las pintaba de VERDE. El nombre decía "pendiente" y el color decía
       "aprobado".

   Los modificadores se llaman por lo que SIGNIFICAN, no por su color: el día
   que el verde cambie de tono, `--al-dia` sigue queriendo decir lo mismo.

   Y "retirado" es `--inactivo`, gris, no ámbar: un alumno que se fue del
   colegio es un HECHO, no una alerta. El ámbar dice "haz algo con esto".
   ========================================================================== */

.estado {
  display: inline-block;
  padding: 2px 10px;
  border-radius: 999px;
  border: 1px solid transparent;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.02em;
  white-space: nowrap;
}

.estado--al-dia   { background: var(--success-bg); color: var(--success-text); border-color: var(--success-border); }
.estado--atencion { background: var(--warning-bg); color: var(--warning-text); border-color: var(--warning-border); }
.estado--grave    { background: var(--danger-bg);  color: var(--danger-text);  border-color: var(--danger-border); }
.estado--inactivo { background: var(--surface-input); color: var(--text-tertiary); border-color: var(--border-standard); }

/* Los avisos, en el tema oscuro del panel de SIGA (MOR-135).
   ─────────────────────────────────────────────────────────
   `.alert-success` y `.alert-danger` están escritas para el papel crema del
   colegio: fondo verde claro, texto verde oscuro. Sobre el panel oscuro
   aparecían como un rectángulo blanco pegado encima — la única parte clara de
   la pantalla, y encima la más grande.

   La regla de color se sostiene igual: el aviso sigue diciendo qué pasó con un
   color semántico. Lo que cambia es el sustrato, no el significado. */
.siga-admin .alert {
  background: var(--siga-panel);
  border: 1px solid var(--siga-borde);
  color: var(--siga-texto);
  border-left-width: 3px;
}

.siga-admin .alert-success { border-left-color: var(--siga-ok); }
.siga-admin .alert-danger  { border-left-color: var(--siga-mal); }
.siga-admin .alert-warning { border-left-color: var(--siga-alerta); }

/* ==========================================================================
   PANTALLAS DE ACCESO (MOR-135)
   ==========================================================================
   Login, "olvidé mi contraseña" y "elegir contraseña nueva" compartían seis
   estilos IDÉNTICOS escritos en línea, uno por vista. Tres copias de lo mismo
   es el momento exacto en que se vuelven tres cosas distintas: alguien toca
   una y las otras dos quedan atrás sin que nadie lo note.

   Es la segunda superficie más vista del sistema después de la portada, y la
   primera que ve un profesor cada mañana.
   ========================================================================== */

.acceso {
  max-width: 480px;
  margin: var(--space-2xl) auto;
}

.acceso-titulo {
  text-align: center;
  margin: 0 0 var(--space-md);
}

.acceso-entrada {
  text-align: center;
  color: var(--text-secondary);
  margin: 0 0 var(--space-xl);
}

/* Botón a lo ancho: en el teléfono es el objetivo más grande de la pantalla, y
   acá no compite con nada. */
.acceso-enviar { width: 100%; }

.acceso-pie {
  margin-top: var(--space-lg);
  padding-top: var(--space-lg);
  border-top: 1px solid var(--border-subtle);
  text-align: center;
  font-size: 14px;
}

.acceso-enlaces {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

/* "Recordarme": la etiqueta ENTERA es el objetivo, no la casilla de 16 px. */
.acceso-recordarme {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  font-size: 14px;
  color: var(--text-secondary);
  cursor: pointer;
}
.acceso-recordarme input { margin: 0; }

/* La entradilla bajo el título de una pantalla: dice para qué sirve, en el
   único renglón donde alguien la va a leer. */
.entrada-pagina {
  color: var(--text-secondary);
  margin: 0 0 var(--space-xl);
}

/* Un `.form-group` cuyo contenido es un botón que debe alinearse con los
   campos de al lado, no con sus etiquetas. */
/* ⚠️ `align-self`, no solo `align-items`.
   El botón «Limpiar» no tiene rótulo, así que su columna es más baja que las
   demás y tiene que caer al PIE de la fila para quedar a la altura de los
   campos. `align-items` alinea a sus HIJOS; para bajar el grupo mismo hace falta
   `align-self`.

   Antes funcionaba de rebote: `.form-row` alineaba todo por abajo. Al pasar la
   fila a `flex-start` —para que los RÓTULOS se alineen, que es lo que el ojo usa
   para leerla— este botón se subía con los demás. */
.form-group--boton {
  display: flex;
  align-items: flex-end;
  align-self: flex-end;
}

/* Lo que hay que atender, en el panel del director (MOR-135).
   ─────────────────────────────────────────────────────────
   Cada renglón enlaza a donde se resuelve: un número sin destino es una
   preocupación, no una tarea. El número va grande porque es lo que se escanea;
   la frase, al lado, porque es lo que se lee al detenerse. */
.pendientes { list-style: none; margin: 0; padding: 0; }

.pendiente { border-bottom: 1px solid var(--border-subtle); }
.pendiente:last-child { border-bottom: none; }

.pendiente-enlace {
  display: flex;
  align-items: baseline;
  gap: var(--space-md);
  padding: var(--space-md) 0;
  color: var(--text-primary);
  text-decoration: none;
}
.pendiente-enlace:hover { color: var(--brand-primary); }
.pendiente-enlace:hover .pendiente-que { text-decoration: underline; }

/* Ámbar, no rojo: son cosas por hacer, no un colegio en llamas. El rojo de
   este sistema significa "un niño aplazado" — ver la regla de color. */
.pendiente-numero {
  font-family: var(--font-serif);
  font-size: 30px;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  color: var(--warning-text);
  min-width: 2.5ch;
  text-align: right;
}

.pendiente-que { color: var(--text-secondary); }

/* Lo que al profesor le falta corregir (MOR-135). Es lo primero de su panel
   porque es a lo que entra: semanal, con apuro, desde el teléfono. */
.por-corregir { list-style: none; margin: 0; padding: 0; }

.por-corregir-fila {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm) var(--space-lg);
  align-items: center;
  justify-content: space-between;
  padding: var(--space-md) 0;
  border-bottom: 1px solid var(--border-subtle);
}
.por-corregir-fila:last-child { border-bottom: none; }

.por-corregir-que { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.por-corregir-nombre { font-weight: 600; color: var(--text-primary); }
.por-corregir-donde { font-size: 13px; color: var(--text-tertiary); }

.por-corregir-avance { display: flex; align-items: center; gap: var(--space-md); }

/* En letras y no una barra: "12 de 20" contesta cuántas faltan, que es la
   pregunta. Una barra contesta "60 %", que no lo es. */
.por-corregir-cuenta {
  font-size: 14px;
  color: var(--text-secondary);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

@media (max-width: 640px) {
  .por-corregir-avance { width: 100%; justify-content: space-between; }
}

/* Las dos formas del grupo de configuración (MOR-135).
   ───────────────────────────────────────────────────
   En escritorio va plegado en un `<details>`; en la tira del teléfono va
   plano, porque un `<details>` que crece hacia abajo dentro de un deslizador
   horizontal con `overflow-y: hidden` deja sus enlaces recortados e
   inalcanzables. Ver la nota del sidebar.

   Se apagan con `display: none` y no con `hidden`/`visibility`, para que el
   apagado no ocupe lugar en la maqueta. */
.solo-telefono { display: none; }

@media (max-width: 768px) {
  .solo-escritorio { display: none; }
  .solo-telefono { display: block; }
}

/* La firma escaneada, en la pantalla de identidad del colegio. */
.identidad-firma {
  margin-top: var(--space-sm);
  padding: var(--space-sm);
  background: var(--surface-input);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-sm);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
}

.identidad-firma-imagen { max-width: 220px; max-height: 70px; object-fit: contain; }

.identidad-sello-imagen { max-width: 110px; max-height: 110px; object-fit: contain; }

/* El enlace de activación, que se ve una sola vez (MOR-143).
   ─────────────────────────────────────────────────────────
   Tratado como un dato frágil y no como un aviso: quien lo tiene delante lo va
   a copiar y pegar en WhatsApp, y perder tres caracteres de la URL rompe el
   enlace sin decir por qué. De ahí el monoespaciado y el `word-break`.

   Sin color semántico: no es un éxito ni un peligro, es algo que hay que
   copiar ahora. Lo que lo destaca es el marco y la tipografía, no el tono. */
/* ⚠️ Fondo VERDE pastel, no gris. Daniel: «el fondo puede ser verde o azul
   pastel para hacerlo notar un poco».

   Y el verde es el correcto de los dos: es el único momento en que esta
   pantalla tiene algo que entregar —un enlace que se ve una sola vez y no se
   puede recuperar— y en SIGA el verde es «esto queda guardado». El azul del
   sistema es de los controles.

   El borde punteado se queda: dice que el bloque es temporal, que se va en
   cuanto se descarte. */
.codigo-de-acceso {
  margin-bottom: var(--space-lg);
  padding: var(--space-md);
  border: 1px dashed var(--success-border, var(--border-strong));
  border-radius: var(--radius-md);
  background: var(--success-bg);
}

/* Los dos botones en FILA y a la misma altura: uno debajo del otro se leían
   como dos pasos, y son alternativas — copio, o digo que ya lo copié. */
.codigo-de-acceso-acciones {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-sm);
  margin-top: var(--space-sm);
}

/* ⚠️ `button_to` envuelve su botón en un `<form>`, que es `block`: sin esto el
   segundo caía debajo del primero aunque el contenedor fuera flex. */
.codigo-de-acceso-acciones form { display: contents; }

.codigo-de-acceso-aviso {
  margin: 0 0 var(--space-sm);
  font-size: 13px;
  color: var(--text-tertiary);
}

.codigo-de-acceso-enlace {
  margin: 0 0 var(--space-md);
  font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
  font-size: 13px;
  line-height: 1.5;
  word-break: break-all;
  user-select: all;
}

/* El enlace de activación, sobre el panel oscuro de SIGA (MOR-143).
   Mismo significado que `.codigo-de-acceso` del colegio; lo que cambia es el
   sustrato, no la idea: algo frágil que hay que copiar ahora. */
.siga-codigo {
  padding: var(--space-md);
  border: 1px dashed var(--siga-borde);
  border-radius: 6px;
}

.siga-codigo .mono {
  margin: var(--space-sm) 0 var(--space-md);
  color: var(--siga-acento);
  word-break: break-all;
  user-select: all;
}

/* Grupos de campos dentro de un formulario.
   ─────────────────────────────────────────
   Reemplaza a `%fieldset` + `%legend`, que en el tema del colegio **no tenían
   ninguna regla**: el navegador los dibujaba con su borde gris hundido y la
   etiqueta flotando encima del trazo. Adentro de una `.card-academic` eso se
   veía como una caja mal pegada dentro de otra caja.

   Acá el grupo no dibuja marco: separa. Es la misma idea que
   `.card-academic-header` —una línea y un título— un escalón más abajo. */
.form-seccion + .form-seccion {
  margin-top: var(--space-lg);
  padding-top: var(--space-lg);
  border-top: 1px solid var(--border-subtle);
}

.form-seccion-titulo {
  margin: 0 0 var(--space-md);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-tertiary);
}

/* Buscador local de una lista larga de casillas.
   No va al servidor: filtra lo que ya está en la página. Con 200 alumnos en el
   colegio, encontrar a uno scrolleando es el trabajo que nadie quiere hacer. */
.lista-filtrable-buscar {
  margin-bottom: var(--space-sm);
}

.lista-filtrable-vacio {
  padding: var(--space-md);
  color: var(--text-tertiary);
  font-size: 13px;
  font-style: italic;
}

/* De quién es la cuenta que se está activando o recuperando (MOR-143).
   Va arriba del formulario y no como ayuda al pie: es lo primero que hay que
   poder confirmar antes de escribir una contraseña. */
.acceso-quien {
  margin: 0 0 var(--space-sm);
  font-weight: 600;
  color: var(--text-primary);
}

.acceso-quien-correo {
  display: block;
  font-weight: 400;
  font-size: 13px;
  color: var(--text-tertiary);
}

/* El importador de matrícula (MOR-126).
   ─────────────────────────────────────
   Un valor de ejemplo al lado de cada encabezado. Es lo que hace usable la
   pantalla del mapeo: «AÑO/SECC» no dice nada, «1er Año A» lo dice todo. Va en
   monoespaciado porque es un dato tal como vino, no texto de la interfaz. */
.muestra-de-celda {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 13px;
  color: var(--text-secondary);
}

.lista-faltantes {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

.lista-faltantes li {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs) var(--space-sm);
  align-items: baseline;
  padding: var(--space-sm) var(--space-md);
  border-left: 3px solid var(--border-strong);
  background: var(--surface-input);
}

.lista-faltantes .nota-ayuda { margin: 0; }

/* El resumen de una importación, antes de confirmarla (MOR-126).
   ──────────────────────────────────────────────────────────────
   Los números primero y grandes: es lo único que alguien mira de verdad antes
   de apretar el botón. El rojo va SOLO en las que no se van a cargar —un dato
   que se pierde— y no en el marco ni en el botón, que es la regla de color. */
.cifras-de-importacion {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-md);
  margin-top: var(--space-md);
}

.cifra-de-importacion {
  flex: 1 1 150px;
  padding: var(--space-md);
  border: 1px solid var(--border-standard);
  border-radius: var(--radius-sm);
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.cifra-numero {
  font-size: 30px;
  font-weight: 600;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}

.cifra-que {
  font-size: 13px;
  color: var(--text-tertiary);
  line-height: 1.35;
}

.cifra-de-importacion.--rechazadas .cifra-numero { color: var(--danger-text); }

.motivos {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 3px;
  font-size: 13px;
  color: var(--text-secondary);
}

/* El selector de año escolar de una importación (MOR-126). */
.periodo-de-importacion {
  margin-top: var(--space-md);
  max-width: 340px;
}

/* ---- Carga rápida desde la previsualización de una importación ---- */

/* La planilla nombra un grado que no existe y SIGA no sabe dónde cortarlo, así
   que propone y la persona confirma. Va dentro de una celda de tabla: los
   campos se apilan en el teléfono y se alinean en el escritorio. */
.carga-rapida-campos {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm);
  align-items: flex-end;
  margin-bottom: var(--space-sm);
}

.carga-rapida-campos .form-group {
  margin-bottom: 0;
  min-width: 7rem;
  flex: 1 1 7rem;
}

/* ═══════════════════════════════════════════════════════════════════════════
   ICONOS Y AVATARES (MOR-149)

   La app llegó a agosto de 2026 con CERO iconos: diecinueve enlaces de menú,
   todos texto, y ningún botón con nada al lado. Una pantalla así no distingue
   «cargar asistencia» de «emitir una certificación del MPPE», y las dos viven
   en el mismo menú.

   Los SVG los pone `IconosHelper` en el HTML, no un archivo aparte, para que
   hereden `currentColor` — ver el comentario del helper.
   ═══════════════════════════════════════════════════════════════════════════ */

.icono {
  /* `flex: none` porque el caso normal es un icono dentro de un flex con texto,
     y sin esto el icono se aplasta cuando el texto es largo. Se veía en
     «Participación en Grupos de Creación, Recreación y Producción». */
  flex: none;
  display: inline-block;
  vertical-align: -0.15em;
}

/* El icono acompaña al texto: nunca compite con él. */
.btn .icono,
.sidebar-nav-link .icono,
.persona-linea .icono {
  opacity: 0.85;
}
.btn:hover .icono,
.sidebar-nav-link:hover .icono,
.sidebar-nav-link.active .icono {
  opacity: 1;
}

/* ── el menú, ahora con iconos ─────────────────────────────────────────── */

/* ⚠️ De `display: block` a flex. El enlace era una línea de texto y ahora es
   icono + palabra, así que el `gap` es lo que los mantiene alineados cuando la
   palabra envuelve —«Inasistencias» en un teléfono angosto—. */
.sidebar-nav-link {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
}

/* ── el número de orden de una fila ────────────────────────────────────────

   Una planilla se recorre alumno por alumno, y sin numerar no hay forma de
   contestar «¿por dónde iba?» al volver de una interrupción — que en un aula es
   cada tres minutos. Tampoco de decir «el doce todavía no tiene nota» por
   teléfono, ni de cotejar contra la lista en papel, que es lo que el colegio ya
   tiene.

   ⚠️ Es un ÍNDICE, no un dato del alumno: gris, chico y alineado a la derecha,
   para que se lea de un barrido vertical sin competir con el nombre.

   ⚠️ `tabular-nums` no es refinamiento: sin él, el «9» y el «10» no alinean y
   la columna se ve chueca justo donde tiene que servir de guía. */

.columna-orden,
.fila-orden {
  color: var(--text-tertiary);
  font-size: 13px;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  text-align: right;
  user-select: none;
}

/* En una tabla es una columna angosta. */
th.columna-orden,
td.columna-orden { width: 2.5rem; }

/* En una lista es un marcador de ancho fijo antes del avatar. `inline-block`
   para que el ancho valga también donde la fila NO es flex —el nombre del niño
   en la planilla de Inicial es un `<h4>`. */
.fila-orden {
  flex: 0 0 auto;
  display: inline-block;
  min-width: 1.75rem;
}

/* Una columna cuyo CONTENIDO es un número —una nota, un total— y no el orden
   de la fila. Comparte el `tabular-nums` y nada más: no es gris ni angosta,
   porque ahí el número SÍ es el dato. */
.columna-numero {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* ── inscribir: un buscador y una fila por alumno ──────────────────────────

   Reemplaza a un `<select>` de trescientos nombres. Cada fila dice quién es
   —cara, nombre, cédula— y EN QUÉ SITUACIÓN está, que es lo que evita
   inscribir dos veces al mismo niño en septiembre. */

.inscribir-lista {
  list-style: none;
  margin: var(--space-md) 0 0;
  padding: 0;
  border-top: 1px solid var(--border-subtle);
}

.inscribir-alumno {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  padding: var(--space-sm) 0;
  border-bottom: 1px solid var(--border-subtle);
}

/* Quien ya está inscrito se ve APAGADO, no escondido: esconderlo dejaría a
   quien lo busca pensando que el alumno no existe. */
.inscribir-alumno--ya { opacity: 0.55; }

.inscribir-quien {
  display: flex;
  flex-direction: column;
  min-width: 0;
  flex: 1;
}

.inscribir-nombre {
  font-size: 15px;
  color: var(--text-primary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.inscribir-detalle {
  font-size: 12px;
  color: var(--text-tertiary);
}

.inscribir-punto { margin: 0 var(--space-micro); }

/* ── el identificador fusionado y las pastillas del formulario ───────────── */

/* Las pastillas de un identificador son cortas —«A», «B», «U1»— así que no
   tienen por qué ocupar 110 px como las de «Femenino / Masculino». */
.opciones-compactas .opcion-boton {
  flex: 0 0 auto;
  min-width: 3.5rem;
}

/* «Otra…» no es una opción más: es la puerta al campo libre. Se distingue
   porque elegirla no elige nada — abre dónde escribir. */
.opcion-boton--otra {
  font-style: italic;
  color: var(--text-tertiary);
}

/* El área donde se pega la lista: ancha, monoespaciada y sin ajuste de línea,
   para que las columnas se vean como columnas y se note si algo vino pegado. */
.campo-pegado {
  font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
  font-size: 13px;
  white-space: pre;
  overflow-x: auto;
}

/* ⚠️ Los DOS nombres, y no es descuido: `elegida` la pone el controlador de los
   identificadores y `activa` la pone `paneles`, que es el de las pestañas. Son
   la misma idea —«esta es la que está»— y tienen que verse igual; unificar los
   nombres es tocar dos controladores que ya usan otras pantallas. */
.opcion-boton.elegida,
.opcion-boton.activa {
  border-color: var(--control-focus-border);
  background: var(--control-focus-ring);
  font-weight: 600;
}

/* ── «Guía, cupo y aula»: lo que no hace falta para crear ────────────────── */

.form-avanzado {
  margin: var(--space-md) 0;
  border-top: 1px solid var(--border-subtle);
  padding-top: var(--space-md);
}

.form-avanzado-titulo {
  cursor: pointer;
  font-weight: 600;
  color: var(--text-secondary);
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  list-style: none;
}

/* La flechita de `<details>`, dibujada por nosotros: la nativa cambia de forma
   entre navegadores y en Safari ni se puede ocultar con `list-style`. */
.form-avanzado-titulo::-webkit-details-marker { display: none; }

.form-avanzado-titulo::before {
  content: "▸";
  font-size: 11px;
  color: var(--text-tertiary);
  transition: transform var(--transition-fast);
}

.form-avanzado[open] > .form-avanzado-titulo::before { transform: rotate(90deg); }

/* ── el formulario de a un paso por vez ────────────────────────────────────

   Diecisiete campos en una pantalla eran tres decisiones distintas —qué
   colegio, dónde queda, quién responde— presentadas como una sola lista. */

.pasos-barra {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  margin-bottom: var(--space-lg);
  flex-wrap: wrap;
}

.pasos-punto {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  color: var(--text-tertiary);
  font-size: 13px;
}

/* El número en un círculo, que es cómo se lee un paso de un asistente. */
.pasos-punto-numero {
  display: grid;
  place-items: center;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  border: 1px solid var(--border-standard);
  font-size: 12px;
  font-variant-numeric: tabular-nums;
}

/* En el teléfono se esconde el nombre del paso y quedan los tres números: tres
   palabras más tres círculos no caben en 360 px, y lo que hace falta saber ahí
   es por cuál se va. El nombre del paso actual lo dice el `<legend>`. */
.pasos-punto-nombre { white-space: nowrap; }

@media (max-width: 600px) {
  .pasos-punto-nombre { display: none; }
}

.pasos-punto--actual { color: var(--text-primary); font-weight: 600; }

.pasos-punto--actual .pasos-punto-numero {
  border-color: var(--brand-primary);
  background: var(--brand-primary);
  color: #fff;
}

/* ⚠️ El paso HECHO se marca con un fondo suave y no con un ✓ dibujado: el
   check dentro de un círculo de 22 px se lee como un borrón. */
.pasos-punto--hecho .pasos-punto-numero {
  border-color: var(--brand-primary);
  color: var(--brand-primary);
  background: var(--control-focus-ring);
}

.pasos-acciones {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  margin-top: var(--space-lg);
}

/* La cuenta empuja a los botones a la derecha, que es donde se avanza. */
.pasos-cuenta {
  margin-right: auto;
  font-size: 13px;
  color: var(--text-tertiary);
}

/* ── las sugerencias del catálogo de planteles ─────────────────────────────

   Aparecen debajo del nombre mientras se escribe. No son un desplegable del
   navegador: cada una muestra el código DEA y de dónde es, que es lo que
   permite distinguir dos colegios que se llaman parecido. */

.sugerencias {
  list-style: none;
  margin: var(--space-xs) 0 0;
  padding: 0;
  border: 1px solid var(--border-standard);
  border-radius: var(--radius-md);
  overflow: hidden;
  background: var(--surface-elevated);
}

.sugerencia {
  display: flex;
  flex-direction: column;
  gap: 2px;
  width: 100%;
  padding: var(--space-sm) var(--space-md);
  border: 0;
  border-bottom: 1px solid var(--border-subtle);
  background: transparent;
  text-align: left;
  cursor: pointer;
  font: inherit;
}

.sugerencias li:last-child .sugerencia { border-bottom: 0; }

.sugerencia:hover,
.sugerencia:focus-visible {
  background: var(--surface-hover);
  outline: none;
}

.sugerencia-nombre { color: var(--text-primary); }

.sugerencia-detalle {
  font-size: 12px;
  color: var(--text-tertiary);
  display: flex;
  gap: var(--space-xs);
}

/* El código, en monoespaciada: es un identificador y se compara carácter a
   carácter con el que el colegio tenga en su papel. */
.sugerencia-dea {
  font-family: var(--font-mono);
  color: var(--text-secondary);
}

.sugerencias-vacio,
.sugerencias-pie { margin-top: var(--space-xs); }

/* ⚠️ La marca de «esto vino del catálogo» dice ADEMÁS que se puede cambiar.
   Sin esa mitad, un campo que se rellenó solo se lee como un dato cerrado. */
.catalogo-marca {
  display: flex;
  align-items: center;
  gap: var(--space-micro);
  color: var(--success-text);
  margin-top: var(--space-xs);
}

/* ── el avatar ─────────────────────────────────────────────────────────────

   `--avatar-lado` viene del helper y no de una clase por tamaño: son cinco
   tamaños y cada uno necesitaría su propia regla de `width`, `height`,
   `font-size` y `border-radius`. Con la variable, una regla sirve para todos.  */

.avatar {
  width: var(--avatar-lado, 36px);
  height: var(--avatar-lado, 36px);
  flex: none;
  display: inline-grid;
  place-items: center;
  /* Un radio proporcional al lado: un cuadrado de 24 px con 10 px de radio es
     casi un círculo, y uno de 96 px con 10 px es casi un cuadrado. */
  border-radius: calc(var(--avatar-lado, 36px) * 0.28);
  background: var(--avatar-color, var(--brand-primary));
  color: #fff;
  font-family: var(--font-sans);
  /* Las iniciales ocupan el 38 % del lado: probado en los cinco tamaños con
     «CJ», que es el par más ancho que produce el helper. */
  font-size: calc(var(--avatar-lado, 36px) * 0.38);
  font-weight: 600;
  line-height: 1;
  letter-spacing: -0.02em;
  user-select: none;
  overflow: hidden;
}

/* Cuando hay foto, `object-fit` evita que una foto vertical de carnet salga
   estirada — que es exactamente cómo llega la mayoría. */
img.avatar {
  object-fit: cover;
  background: var(--surface-hover);
}

/* ── la foto de perfil en el formulario ────────────────────────────────────

   El avatar a la izquierda y los controles a la derecha, para que el resultado
   se vea al lado de lo que lo cambia. Apilarlos dejaría el botón lejos de la
   foto en un teléfono, que es donde se sube. */

.foto-de-perfil {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  margin-bottom: var(--space-lg);
}

.foto-de-perfil-controles {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  min-width: 0;
}

.foto-de-perfil-botones {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs);
}

/* ⚠️ El `<label>` que envuelve al campo de archivo tiene que verse y
   comportarse como el botón que aparenta: sin esto queda con el cursor de
   texto, y el `<input>` escondido con `.sr-only` deja al usuario sin señal de
   que ahí se hace clic. */
.foto-de-perfil-botones label.btn {
  cursor: pointer;
  margin: 0;
}

/* El foco vive en el `<input>`, que está fuera de la pantalla: sin esto, quien
   navega con el teclado no ve NADA moverse al llegar acá. */
.foto-de-perfil-botones label.btn:focus-within {
  outline: 2px solid var(--brand-primary);
  outline-offset: 2px;
}

.foto-de-perfil .nota-ayuda {
  margin: 0;
}

/* ── la ficha del alumno vista por su profesor ─────────────────────────────

   Dos tarjetas al lado, porque «cómo va» y «cuánto ha faltado» son la misma
   pregunta partida en dos —por qué este niño anda así— y leerlas juntas es lo
   que la contesta. En el teléfono se apilan, que es donde se lee de una en una. */

.ficha-dos-columnas {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: var(--space-md);
  align-items: start;
}

.ficha-areas { list-style: none; margin: 0; padding: 0; }

.ficha-area {
  display: flex;
  flex-direction: column;
  gap: var(--space-micro);
  padding: var(--space-sm) 0;
  border-bottom: 1px solid var(--border-standard);
}

.ficha-area:last-child { border-bottom: none; }

.ficha-area-nombre {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-tertiary);
}

/* La valoración de Educación Inicial es un párrafo, y se lee como tal. */
.ficha-area-texto {
  margin: 0;
  color: var(--text-primary);
  line-height: 1.5;
}

/* ⚠️ Sin color: un número es el DATO, y la regla de color (MOR-135) reserva el
   rojo para cuando SIGA marca un aplazado — no para cada nota de una lista. */
.ficha-area-nota {
  font-size: 20px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--text-primary);
}

.ficha-asistencia-cuenta {
  margin: 0;
  font-size: 20px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--text-primary);
}

.ficha-asistencia-faltas {
  margin: var(--space-micro) 0 0;
  color: var(--text-secondary);
}

.ficha-sin-faltas { color: var(--text-secondary); }

/* ── avatar + nombre, que es como se usa casi siempre ──────────────────── */

.persona-linea {
  display: inline-flex;
  align-items: center;
  gap: var(--space-sm);
  min-width: 0;
}

.persona-linea-texto {
  display: flex;
  flex-direction: column;
  min-width: 0;
  line-height: 1.25;
}

.persona-linea-nombre {
  font-weight: 500;
  color: inherit;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.persona-linea-detalle {
  font-size: 12px;
  color: var(--text-tertiary);
  font-variant-numeric: tabular-nums;
}

/* En un teléfono el detalle es lo primero que sobra: el nombre no se puede
   perder, la cédula sí se puede consultar entrando. */
@media (max-width: 480px) {
  .persona-linea-detalle { display: none; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   LOS NÚMEROS SE LEEN COMO REGISTRO (MOR-149)

   Es la mitad de la «piel de Expediente» que faltaba. Los títulos ya iban en
   serif; lo que no iba era lo que de verdad se lee en esta app — columnas de
   cédulas, carnets y notas.

   Sin `tabular-nums`, el 1 es más angosto que el 8 y una columna de doscientas
   cédulas queda con el borde derecho dentado. Con él, los dígitos ocupan lo
   mismo y la columna se alinea sola: es la diferencia entre una lista y un
   registro, y no cuesta un byte de descarga.
   ═══════════════════════════════════════════════════════════════════════════ */

.table-academic td,
.table-academic th,
.stat-card-value,
.cifra-numero,
.persona-linea-detalle,
.detail-value {
  font-variant-numeric: tabular-nums;
}

/* Un identificador NO es prosa: es una cadena que alguien va a cotejar carácter
   por carácter contra una cédula de cartón. La monoespaciada hace que un 0 y
   una O no se confundan, que es exactamente el error que arruina un trámite. */
.dato,
.identificador {
  font-family: var(--font-mono);
  font-size: 0.92em;
  letter-spacing: -0.01em;
}

/* ── la jerarquía de la pantalla ─────────────────────────────────────────

   `h1` es el nombre del documento que se está mirando, no un rótulo de sección.
   Se le da aire y una línea abajo, como el encabezado de una planilla del MPPE:
   lo que sigue pertenece a lo que dice arriba.                                */

.card-academic-header h1 {
  font-size: 26px;
  letter-spacing: -0.015em;
}

/* Una tabla que se lee como registro necesita que el encabezado se distinga por
   PESO y espaciado, no por color de fondo: el fondo gris compite con las filas
   que sí llevan color por su estado. */
.table-academic thead th {
  font-family: var(--font-sans);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--text-tertiary);
}

/* ═══════════════════════════════════════════════════════════════════════════
   EL MENÚ QUE SE ENCOGE (MOR-149)

   La estructura de la propuesta «Consola»: el menú pasa de 260 px a un riel de
   56 px con solo iconos, y el contenido gana ese ancho. En una laptop de 13"
   —que es con lo que trabaja media secretaría— son 204 px más para la tabla.

   ⚠️ La clase la pone el controlador en <html>, no en el <aside>: el `main`
   también tiene que reaccionar, y desde el aside no se alcanza a un hermano
   anterior sin `:has()`.
   ═══════════════════════════════════════════════════════════════════════════ */

.menu-encogido .sidebar-index {
  width: 56px;
  padding-left: 0;
  padding-right: 0;
}

/* ⚠️ El texto se oculta A LA VISTA, no del árbol de accesibilidad.
   
   Con `display: none` un lector de pantalla leería «enlace, imagen» y nada más:
   el icono va `aria-hidden` a propósito, así que la palabra es lo ÚNICO que
   nombra al enlace. Recortarlo a un píxel lo saca de la pantalla y lo deja
   donde tiene que estar para quien no la mira. */
.menu-encogido .sidebar-nav-link span,
.menu-encogido .sidebar-section-title,
.menu-encogido .sidebar-grupo summary {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.menu-encogido .sidebar-nav-link {
  justify-content: center;
  padding-left: 0;
  padding-right: 0;
  /* El objetivo táctil no se encoge con el menú: 44 px es el mínimo que se
     puede tocar sin apuntar, y un riel de iconos se usa más con el dedo que el
     menú ancho. */
  min-height: 44px;
}

/* ⚠️ En el riel NINGÚN grupo puede quedar plegado (MOR-149).

   El título del grupo es lo que se pulsa para abrirlo, y en el riel el título
   está oculto. Un grupo plegado ahí esconde sus iconos SIN NINGUNA MANERA DE
   RECUPERARLOS: «Configuración» nace plegado, así que encoger el menú se
   llevaría cuatro pantallas y no habría cómo traerlas de vuelta.

   Es el mismo agujero que el del teléfono, en otro lado. */
.menu-encogido .sidebar-grupo::details-content {
  content-visibility: visible;
  display: contents;
}

/* Y si el navegador no conoce `::details-content`, entonces el título NO se
   esconde: mejor un riel con rótulos de más que cuatro pantallas perdidas. */
@supports not selector(::details-content) {
  .menu-encogido .sidebar-grupo > summary {
    position: static;
    width: auto;
    height: auto;
    margin: 0;
    clip-path: none;
    font-size: 9px;
    justify-content: center;
  }
}

/* Un separador que sustituye al título de sección cuando el título se esconde:
   sin él los dieciocho iconos quedan en una tira continua y se pierde el
   agrupamiento entero. */
.menu-encogido .sidebar-section + .sidebar-section::before,
.menu-encogido .sidebar-section + .sidebar-grupo::before,
.menu-encogido .sidebar-grupo + .sidebar-grupo::before {
  content: "";
  display: block;
  height: 1px;
  background: var(--border-subtle);
  margin: 0 var(--space-sm) var(--space-sm);
}

/* ── el nombre al pasar el ratón ─────────────────────────────────────────
   
   Un riel de iconos sin nombres obliga a memorizar; el tooltip es lo que hace
   que se pueda aprender usándolo. Va en CSS y no como `title` nativo porque el
   nativo tarda un segundo largo en aparecer y se ve como una etiqueta del
   sistema operativo, no del producto.                                        */

.menu-encogido .sidebar-nav-item {
  position: relative;
}

.menu-encogido .sidebar-nav-link::after {
  content: attr(data-nombre);
  position: absolute;
  left: calc(100% + 6px);
  top: 50%;
  transform: translateY(-50%);
  background: var(--text-primary);
  color: var(--surface-elevated);
  font-size: 12px;
  font-weight: 500;
  white-space: nowrap;
  padding: 4px 8px;
  border-radius: var(--radius-sm);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--transition-fast);
  z-index: 30;
}

.menu-encogido .sidebar-nav-link:hover::after,
.menu-encogido .sidebar-nav-link:focus-visible::after {
  opacity: 1;
}

/* ── la cabecera del menú, con el botón de encoger ──────────────────────────

   ⚠️ El botón estaba al PIE y no se encontraba. Acá arriba está en su propia
   franja, alineado a la derecha: no compite con el primer enlace por el lugar
   donde el ojo cae, y se ve apenas se abre la pantalla.                       */

.sidebar-cabecera {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-sm);
  padding: 0 var(--space-lg) var(--space-sm);
  margin-bottom: var(--space-sm);
  border-bottom: 1px solid var(--border-subtle);
}

.sidebar-cabecera-titulo {
  font-family: var(--font-serif);
  font-size: 13px;
  font-weight: 600;
  color: var(--text-tertiary);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

.sidebar-encoger {
  display: grid;
  place-items: center;
  /* 32 px de caja visible dentro de un objetivo táctil de 44: el botón se ve
     discreto y se toca sin apuntar. */
  width: 32px;
  height: 32px;
  padding: 0;
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  color: var(--text-tertiary);
  cursor: pointer;
  transition: all var(--transition-fast);
  position: relative;
}

.sidebar-encoger::before {
  content: "";
  position: absolute;
  inset: -6px;
}

.sidebar-encoger:hover {
  background: var(--surface-hover);
  color: var(--text-primary);
  border-color: var(--border-subtle);
}

.sidebar-encoger .icono {
  transition: transform var(--transition-base);
}

/* Encogido: el rótulo desaparece y el botón queda centrado, con la flecha al
   revés — que es lo que dice hacia dónde va a moverse el menú. */
.menu-encogido .sidebar-cabecera {
  justify-content: center;
  padding-left: 0;
  padding-right: 0;
}

.menu-encogido .sidebar-cabecera-titulo {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
}

.menu-encogido .sidebar-encoger .icono {
  transform: rotate(180deg);
}

/* En el teléfono el menú ya es una tira horizontal que se desliza: encogerlo no
   significa nada, y el botón solo ocuparía lugar. */
@media (max-width: 768px) {
  /* En el teléfono el menú ya es una tira horizontal que se desliza: encogerlo
     no significa nada, y la cabecera solo ocuparía lugar. */
  .sidebar-cabecera { display: none; }

  .menu-encogido .sidebar-index { width: 100%; }
  .menu-encogido .sidebar-nav-link span { position: static; width: auto; height: auto; clip-path: none; margin: 0; }
  .menu-encogido .sidebar-nav-link::after { content: none; }
}

@media (prefers-reduced-motion: reduce) {
  .sidebar-index,
  .sidebar-encoger,
  .sidebar-encoger .icono { transition: none; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   EL BUSCADOR GLOBAL · ⌘K (MOR-149)

   La secretaria busca a un alumno cuarenta veces al día con la mamá esperando
   del otro lado del mostrador. Hoy son cuatro pasos y dos cargas de página.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── el disparador en la barra ─────────────────────────────────────────────
   
   ⚠️ Existe porque un atajo invisible solo sirve a quien ya sabe que existe —
   y quien más lo necesita es justamente quien no lo sabe. El botón enseña el
   atajo mostrándolo escrito al lado.                                         */

.buscador-disparador {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  margin-left: auto;
  margin-right: var(--space-md);
  padding: 6px var(--space-sm);
  min-width: 220px;
  background: rgba(255, 255, 255, 0.1);
  border: 1px solid rgba(255, 255, 255, 0.18);
  border-radius: var(--radius-sm);
  color: rgba(255, 255, 255, 0.75);
  font-family: var(--font-sans);
  font-size: 13px;
  cursor: pointer;
  transition: all var(--transition-fast);
}

.buscador-disparador:hover {
  background: rgba(255, 255, 255, 0.16);
  color: #fff;
}

.buscador-disparador-texto { flex: 1; text-align: left; }

.buscador-tecla {
  font-family: var(--font-mono);
  font-size: 10px;
  padding: 2px 5px;
  border: 1px solid currentColor;
  border-radius: 3px;
  opacity: 0.65;
  line-height: 1.4;
  white-space: nowrap;
}

/* En el teléfono no hay teclado físico: el atajo no existe y el botón se
   reduce a la lupa, que es lo único que hace falta. */
@media (max-width: 768px) {
  .buscador-disparador { min-width: 0; }
  .buscador-disparador-texto,
  .buscador-disparador .buscador-tecla { display: none; }
}

/* ── el modal ─────────────────────────────────────────────────────────────── */

.buscador-fondo {
  position: fixed;
  inset: 0;
  background: rgba(10, 16, 26, 0.55);
  /* Arriba y no centrado: el ojo ya está arriba —viene de la barra— y centrar
     obliga a bajar la vista para volver a subirla al leer los resultados. */
  display: flex;
  justify-content: center;
  align-items: flex-start;
  padding: 10vh var(--space-md) var(--space-md);
  z-index: 100;
}

.buscador-fondo[hidden] { display: none; }

.buscador-panel {
  width: 100%;
  max-width: 560px;
  background: var(--surface-elevated);
  border: 1px solid var(--border-standard);
  border-radius: var(--radius-lg);
  box-shadow: 0 20px 60px rgba(10, 16, 26, 0.28);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  max-height: 70vh;
}

.buscador-campo {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  padding: var(--space-md);
  border-bottom: 1px solid var(--border-subtle);
  color: var(--text-tertiary);
}

.buscador-campo input {
  flex: 1;
  border: 0;
  background: transparent;
  font-family: var(--font-sans);
  font-size: 16px;
  color: var(--text-primary);
  outline: none;
  min-width: 0;
}

/* ⚠️ 16 px y no menos: por debajo de eso Safari en iOS hace zoom al enfocar un
   campo, y el modal se sale de la pantalla. */
.buscador-campo input::placeholder { color: var(--text-muted); }

/* La cruz nativa de `type=search` en WebKit no hereda color y se ve como un
   elemento del sistema operativo pegado al diseño. */
.buscador-campo input::-webkit-search-cancel-button { display: none; }

.busqueda-resultados { overflow-y: auto; }

.busqueda-lista { list-style: none; margin: 0; padding: var(--space-xs); }

.busqueda-resultado {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  padding: var(--space-xs) var(--space-sm);
  border-radius: var(--radius-sm);
  text-decoration: none;
  color: var(--text-primary);
  min-height: 44px;
}

/* ⚠️ `.activo` y no `:hover`: la selección la mueve el TECLADO, y el ratón no
   tiene por qué robársela mientras alguien navega con las flechas. El hover se
   agrega aparte para quien sí usa el ratón. */
.busqueda-resultado.activo,
.busqueda-resultado:hover {
  background: var(--brand-subtle);
}

.busqueda-icono {
  width: 28px;
  height: 28px;
  flex: none;
  display: grid;
  place-items: center;
  border-radius: 8px;
  background: var(--surface-hover);
  color: var(--text-secondary);
}

.busqueda-texto { display: flex; flex-direction: column; min-width: 0; line-height: 1.3; }
.busqueda-titulo { font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.busqueda-detalle {
  font-size: 12px;
  color: var(--text-tertiary);
  font-variant-numeric: tabular-nums;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.busqueda-tipo {
  margin-left: auto;
  flex: none;
  font-size: 11px;
  color: var(--text-tertiary);
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

.busqueda-vacia {
  padding: var(--space-xl) var(--space-lg);
  text-align: center;
  color: var(--text-tertiary);
}
.busqueda-vacia .icono { opacity: 0.4; margin-bottom: var(--space-sm); }
.busqueda-vacia p { margin: 0 0 var(--space-xs); }

.buscador-pie {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-md);
  padding: var(--space-xs) var(--space-md);
  border-top: 1px solid var(--border-subtle);
  background: var(--surface-canvas);
  font-size: 11px;
  color: var(--text-tertiary);
}
.buscador-pie span { display: flex; align-items: center; gap: 4px; }

/* En el teléfono el pie explica atajos que no se pueden usar. */
@media (max-width: 768px) {
  .buscador-pie { display: none; }
  .buscador-fondo { padding-top: 4vh; }
}

/* Con el modal abierto la página de atrás no se desplaza: sin esto, mover las
   flechas hace scroll de las dos cosas a la vez. */
body.con-modal { overflow: hidden; }

/* ═══════════════════════════════════════════════════════════════════════════
   EL PANEL DEL DIRECTOR (MOR-149)

   La estructura ya era correcta desde MOR-135: lo accionable arriba, las cifras
   después. Lo que faltaba era que se VIERA la diferencia — «3 secciones sin
   profesor» y «212 alumnos» se dibujaban exactamente igual, y una de las dos
   pide una decisión hoy.
   ═══════════════════════════════════════════════════════════════════════════ */

.pendiente-enlace {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  min-height: 44px;
}

/* ⚠️ Ámbar sobre el DATO, no sobre el control. La regla de color prohíbe teñir
   el botón, no señalar lo que está pendiente: acá el icono describe un hecho
   —hay tres secciones sin profesor— igual que un número en rojo describe a un
   niño aplazado. */
.pendiente-alerta {
  display: grid;
  place-items: center;
  color: var(--warning-text);
  flex: none;
}

/* La flecha aparece al pasar: en reposo sería una tercera cosa compitiendo con
   el número y el texto, y lo que importa leer primero es cuántos son. */
.pendiente-ir {
  margin-left: auto;
  flex: none;
  opacity: 0;
  transform: translateX(-4px);
  transition: all var(--transition-fast);
  color: var(--brand-primary);
}

.pendiente-enlace:hover .pendiente-ir,
.pendiente-enlace:focus-visible .pendiente-ir {
  opacity: 1;
  transform: translateX(0);
}

/* En pantalla táctil no hay hover: la flecha se queda puesta, tenue. */
@media (hover: none) {
  .pendiente-ir { opacity: 0.45; transform: none; }
}

.titulo-ok { color: var(--success-text); vertical-align: -0.2em; margin-right: 4px; }

/* ── las cifras, ahora navegables ──────────────────────────────────────────
   
   Eran cuatro números muertos. Ahora cada uno lleva a donde se administra eso
   mismo, con el icono de su sección del menú: es lo que enseña dónde buscarlo
   la próxima vez sin tener que explicarlo.                                   */

.stat-card {
  position: relative;
  display: block;
  text-decoration: none;
  color: inherit;
  transition: all var(--transition-fast);
}

a.stat-card:hover {
  border-color: var(--brand-primary);
  transform: translateY(-1px);
}

.stat-card-icono {
  position: absolute;
  top: var(--space-sm);
  right: var(--space-sm);
  color: var(--text-muted);
  transition: color var(--transition-fast);
}

a.stat-card:hover .stat-card-icono { color: var(--brand-primary); }

@media (prefers-reduced-motion: reduce) {
  a.stat-card:hover { transform: none; }
  .pendiente-ir { transition: none; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   LA PLANILLA DE NOTAS Y LA ASISTENCIA (MOR-149)

   Las dos pantallas donde el profesor pasa el tiempo, y las dos que más ganan
   con el trato de la propuesta «Aula»: donde hay una persona del otro lado,
   se le ve la cara.
   ═══════════════════════════════════════════════════════════════════════════ */

.carga-fila { align-items: center; }

/* El promedio del lapso al lado del carnet: es lo que distingue un 8 accidental
   de una tendencia, y el profesor solo lo tiene en la cabeza para los cinco
   alumnos que conoce bien. */
.carga-promedio {
  font-variant-numeric: tabular-nums;
  color: var(--text-tertiary);
}

/* ⚠️ Rojo porque es un DATO. La regla de color de SIGA no prohíbe el rojo: lo
   reserva para lo que significa un niño aplazado, que es exactamente esto. */
.carga-promedio.bajo {
  color: var(--danger-text);
  font-weight: 600;
}

/* La nota con la que queda el alumno, al lado del campo de reparación
   (MOR-168). Va en la misma escala que el número que se escribe, porque es la
   consecuencia directa de ese número — no una nota al pie. */
.carga-definitiva {
  min-width: 30px;
  text-align: center;
  font-variant-numeric: tabular-nums;
  font-size: 17px;
  font-weight: 700;
  color: var(--text-tertiary);
}

/* ⚠️ Las dos son DATOS —con qué nota cierra un alumno— así que el color aplica
   tal cual: verde el que aprobó, rojo el que sigue aplazado. */
.carga-definitiva.aprobada { color: var(--success-text); }

/* El resumen de la promoción (MOR-169): cuántos pasan, repiten y egresan.
   ⚠️ Los números son DATOS —cuántos niños hay en cada situación— así que la
   regla de color aplica: rojo el que repite, neutro el resto. «Egresa» no es
   una alerta: es un muchacho que terminó. */
.promocion-resumen {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-lg);
  margin: var(--space-lg) 0;
}

.promocion-cifra {
  color: var(--text-secondary);
  font-size: 15px;
}

.promocion-cifra strong {
  font-size: 22px;
  font-variant-numeric: tabular-nums;
  color: var(--text-primary);
  margin-right: var(--space-micro);
}

/* Un campo que se guarda en mayúscula tiene que VERSE en mayúscula mientras se
   escribe (MOR-170). Si no, quien escribe «c» cree que quedó «c» y se entera
   —o no— al ver la fila guardada. */
.campo-mayuscula { text-transform: uppercase; }

/* El acceso al sistema, en la cabecera (MOR-187). Era una tarjeta entera para
   decir dos cosas: si la persona puede entrar y un botón. */
.acceso-en-linea {
  display: inline-flex;
  align-items: center;
  gap: var(--space-sm);
  margin-left: auto;
  margin-right: var(--space-sm);
}

.acceso-en-linea form { margin: 0; }

/* Los correos que se le escribieron (MOR-186). Del más nuevo al más viejo, que
   es como se lee: la pregunta suele ser «¿ya le avisamos?». */
.mensajes {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-lg);
}

.mensaje {
  padding-bottom: var(--space-lg);
  border-bottom: 1px solid var(--border-subtle);
}

.mensaje:last-child { border-bottom: 0; padding-bottom: 0; }

.mensaje-cabecera {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-md);
  flex-wrap: wrap;
}

.mensaje-asunto { color: var(--text-primary); }

/* `pre-wrap` conserva los saltos de línea que la persona escribió: es el texto
   tal como salió, no un párrafo reflotado. */
.mensaje-cuerpo {
  margin: var(--space-xs) 0;
  white-space: pre-wrap;
  color: var(--text-secondary);
  max-width: 70ch;
}

/* Escribirle a una familia (MOR-181 · MOR-184).
   ────────────────────────────────────────────
   Cada acción AL LADO de su dato: el botón de correo junto al correo, el de
   WhatsApp junto al teléfono. Una sección «Escribirle» aparte obligaba a leer
   el dato arriba, bajar, y volver a decidir a cuál de los dos canales
   corresponde.

   ⚠️ El compositor queda CERRADO. Esta pantalla se abre muchas veces para mirar
   un teléfono y pocas para escribir; abierto, ocupaba media pantalla siempre. */
.dato-con-accion {
  display: flex;
  align-items: baseline;
  gap: var(--space-sm);
  flex-wrap: wrap;
}

/* El `<details>` ocupa el ancho de la celda para que el formulario, al abrirse,
   no quede estrangulado al lado del correo. */
.compositor { flex-basis: 100%; }

.dato-con-accion > .compositor { flex-basis: auto; }

.compositor[open] { flex-basis: 100%; }

/* ⚠️ `<details>` nativo, sin JavaScript: se abre solo, el teclado lo alcanza y
   el navegador ya sabe anunciarlo. Solo hay que quitarle el triangulito y dejar
   que el `summary` se pinte como botón. */
.compositor > summary {
  list-style: none;
  width: fit-content;
}

.compositor > summary::-webkit-details-marker { display: none; }

.compositor[open] > summary {
  background: var(--surface-elevated);
  color: var(--documento-primary);
  border-color: var(--documento-primary);
}

.compositor-cuerpo {
  margin-top: var(--space-md);
  padding: var(--space-lg) 0 0;
  border-top: 1px solid var(--border-subtle);
}

/* El atajo de «sin representante» en el listado de alumnos (MOR-180).
   ─────────────────────────────────────────────────────────────────
   No es un filtro más del buscador: es UNA pregunta que el colegio se hace en
   septiembre —¿a quién no puedo llamar?— y que el panel enlaza directo. Metida
   como un desplegable más quedaría escondida entre los otros controles. */
.filtro-atajo {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md);
  flex-wrap: wrap;
}

.filtro-atajo-texto,
.filtro-atajo-activo {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  color: var(--text-secondary);
  font-size: 14px;
}

/* ⚠️ Dentro de un `.alert` el texto hereda el color del aviso, no el gris de
   siempre: si no, el renglón se pinta de ámbar por fuera y se lee en gris
   apagado por dentro — que es peor que no teñirlo. */
.alert .filtro-atajo-texto { color: inherit; }

/* Cuando el filtro está PUESTO, el renglón dice qué se está mirando. Va en
   negrita y no en color: es contexto, no una alerta. */
.filtro-atajo-activo {
  color: var(--text-primary);
  font-weight: 600;
}

/* Dos controles que son UN campo (MOR-178).
   ───────────────────────────────────────
   Una cédula venezolana es «V-12345678»: una sola cosa que se dice de un tirón.
   Partirla en dos cajas separadas la presentaba como dos decisiones.

   El select se pega al input —sin borde entre ellos, esquinas redondeadas solo
   por fuera— y el conjunto se ilumina junto al recibir el foco. */
.campo-compuesto {
  display: flex;
  align-items: stretch;
}

.campo-compuesto-sigla {
  flex: 0 0 auto;
  width: 4.5rem;
  padding: var(--space-xs) var(--space-xs);
  font-family: var(--font-sans);
  font-size: 14px;
  font-weight: 600;
  color: var(--text-primary);
  background: var(--surface-hover);
  border: 1px solid var(--control-border);
  border-right: 0;
  border-radius: var(--radius-sm) 0 0 var(--radius-sm);
  cursor: pointer;
}

.campo-compuesto .form-control {
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  min-width: 0;
}

/* ⚠️ El foco se marca en TODO el grupo, no en la mitad que se tocó: si solo se
   iluminara el input, el conjunto se leería otra vez como dos campos. */
.campo-compuesto:focus-within {
  outline: 2px solid var(--control-focus-border);
  outline-offset: 1px;
  border-radius: var(--radius-sm);
}

.campo-compuesto:focus-within .campo-compuesto-sigla,
.campo-compuesto:focus-within .form-control {
  outline: none;
}

/* Dos o tres opciones, como botones (MOR-177).
   ──────────────────────────────────────────
   Daniel: «sexo puede ser un radio con label tipo botones». Un `select` de dos
   opciones obliga a abrir, leer y elegir —tres gestos— donde dos botones se ven
   y se tocan de una. En un teléfono la diferencia es mayor todavía.

   ⚠️ El `input[type=radio]` sigue EXISTIENDO, solo que no se ve: es lo que hace
   que funcione el teclado, que el formulario se envíe solo y que un lector de
   pantalla anuncie «opción 1 de 2». Reemplazarlo por `div`s con JavaScript sería
   más corto de escribir y peor para quien no usa el ratón. */
.opciones-en-botones {
  display: flex;
  gap: var(--space-xs);
  flex-wrap: wrap;
}

.opcion-boton {
  flex: 1;
  min-width: 110px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-xs);
  min-height: 36px;
  padding: var(--space-xs) var(--space-md);
  border: 1px solid var(--control-border);
  border-radius: var(--radius-sm);
  background: var(--surface-input);
  color: var(--text-secondary);
  font-size: 14px;
  cursor: pointer;
  transition: all var(--transition-fast);
}

.opcion-boton:hover { border-color: var(--border-emphasis); }

/* El radio queda accesible al teclado pero fuera de la vista: el botón entero
   ES el control. */
.opcion-boton input[type="radio"] {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}

.opcion-boton:has(input:checked) {
  background: var(--brand-subtle);
  border-color: var(--brand-primary);
  color: var(--brand-primary);
  font-weight: 600;
}

/* ⚠️ El foco se ve en el BOTÓN, no en el radio escondido. Sin esto, quien
   navega con teclado no sabe dónde está parado. */
.opcion-boton:has(input:focus-visible) {
  outline: 2px solid var(--control-focus-border);
  outline-offset: 2px;
}

/* ── Lo que ya viene decidido, mostrado y no preguntado ───────────────────── */
/* QA de Daniel, sobre los dos primeros desplegables del formulario de
   evaluación: «vienen del enlace». No es un campo: es el encabezado que dice a
   qué plan pertenece lo que se está por crear. Y su «Cambiar», porque mostrar
   el contexto no puede volverlo una jaula.

   ⚠️ NO se llama `.contexto-fijo`: ese nombre ya es del año escolar en la barra
   superior, y dos definiciones del mismo nombre en la misma hoja se pisan en
   silencio —gana la de más abajo, y la otra se aplica a medias—. */
.contexto-del-enlace {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-xs);
  margin-bottom: var(--space-lg);
  padding: var(--space-sm) var(--space-md);
  border: 1px solid var(--border-subtle);
  border-left: 3px solid var(--brand-primary);
  border-radius: var(--radius-sm);
  background: var(--surface-hover);
  font-size: 14px;
  color: var(--text-primary);
}

.contexto-del-enlace-etiqueta {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-tertiary);
  margin-right: var(--space-micro);
}

.contexto-del-enlace-separador { color: var(--text-tertiary); }

.contexto-del-enlace-cambiar {
  margin-left: auto;
  font-size: 13px;
  color: var(--brand-primary);
  text-decoration: none;
}

.contexto-del-enlace-cambiar:hover { text-decoration: underline; }

/* ── El peso, arrastrado y escrito ────────────────────────────────────────── */
/* Daniel pidió un «range value». Un porcentaje es una porción de algo, y
   arrastrarla es la manera natural de repartir; escribir «30» en una caja
   obliga a imaginarse la proporción.

   ⚠️ Van los DOS controles: el deslizador REPARTE, el número PRECISA — 33,33
   no se arrastra jamás. El que viaja al servidor es el número. */
.campo-rango {
  display: flex;
  align-items: center;
  gap: var(--space-md);
}

.campo-rango-barra {
  flex: 1 1 auto;
  min-width: 0;
  height: 20px;
  margin: 0;
  padding: 0;
  background: transparent;
  cursor: pointer;
  /* `appearance: none` es lo que permite estilar la pista y el pulgar; sin él
     el control queda con la piel del sistema operativo, distinta en cada uno. */
  appearance: none;
  -webkit-appearance: none;
}

.campo-rango-barra::-webkit-slider-runnable-track {
  height: 6px;
  border-radius: 999px;
  background: var(--border-standard);
}

.campo-rango-barra::-moz-range-track {
  height: 6px;
  border-radius: 999px;
  background: var(--border-standard);
}

.campo-rango-barra::-webkit-slider-thumb {
  appearance: none;
  -webkit-appearance: none;
  width: 18px;
  height: 18px;
  /* El pulgar mide 18 y la pista 6: hay que subirlo 6 px para centrarlo. */
  margin-top: -6px;
  border-radius: 50%;
  border: 2px solid var(--surface-elevated);
  background: var(--brand-primary);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);
}

.campo-rango-barra::-moz-range-thumb {
  width: 18px;
  height: 18px;
  border-radius: 50%;
  border: 2px solid var(--surface-elevated);
  background: var(--brand-primary);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);
}

.campo-rango-barra:focus-visible { outline: none; }

.campo-rango-barra:focus-visible::-webkit-slider-thumb {
  outline: 2px solid var(--brand-primary);
  outline-offset: 2px;
}

.campo-rango-barra:focus-visible::-moz-range-thumb {
  outline: 2px solid var(--brand-primary);
  outline-offset: 2px;
}

/* El número, angosto: son tres dígitos y una coma, no un campo de texto. */
.campo-rango-valor {
  flex: 0 0 auto;
  width: 5.5rem;
  padding: var(--space-xs) var(--space-sm);
  border: 1px solid var(--border-standard);
  border-radius: var(--radius-sm);
  background: var(--surface-elevated);
  font-family: var(--font-sans);
  font-size: 15px;
  font-variant-numeric: tabular-nums;
  text-align: right;
  color: var(--text-primary);
}

/* Los mismos tokens de foco que `.form-control`: es un campo del formulario y
   tiene que responder igual que los de al lado. */
.campo-rango-valor:focus {
  outline: none;
  border-color: var(--control-focus-border);
  box-shadow: 0 0 0 3px var(--control-focus-ring);
}

.campo-rango-signo {
  flex: 0 0 auto;
  font-size: 14px;
  color: var(--text-tertiary);
}

/* ── La cabecera de «Qué evalúa», con su [+] ──────────────────────────────── */
.que-evalua-cabecera {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
}

.que-evalua-cabecera .btn { margin-left: auto; }

/* ⚠️ Sin sección elegida no hay plan al que agregar, así que el [+] queda
   apagado. Un `<a>` no tiene `disabled`: se le quita el `href` y esto lo
   muestra. `pointer-events: none` impide además el clic en el hueco. */
.que-evalua-cabecera .btn[aria-disabled="true"] {
  opacity: 0.4;
  pointer-events: none;
}

/* ── Datos de apoyo ───────────────────────────────────────────────────────── */
/* Lo que acompaña al dato principal sin competir con él: la fecha de una fila,
   una IP, «inscrito el 12/09». Existían siete veces escritas a mano dentro de
   la vista —`style="font-size: 13px; color: var(--text-secondary)"`— cada una
   con su propio tamaño, y ninguna encontrable con un grep.

   ⚠️ Nombradas por lo que SON, no por cómo se ven. `.dato-menor` sobrevive a
   un cambio de tamaño; `.texto-13` obliga a renombrarlo o a mentir. */
/* ⚠️ `.dato-secundario` NO se define acá: ya existe (MOR-161) y significa esto
   mismo. Dos definiciones del mismo nombre en la misma hoja se pisan en
   silencio — es la colisión de `.contexto-fijo`, y volvió a aparecer al
   escribir estas clases. */
.dato-menor {
  font-size: 12px;
  color: var(--text-tertiary);
}

/* Una fecha en una celda: nunca se parte en dos renglones. */
.celda-fecha {
  white-space: nowrap;
  font-size: 13px;
  color: var(--text-secondary);
  font-variant-numeric: tabular-nums;
}

/* Un código corto dentro de una tabla —«MAT», «LEN»—. En la sans del sistema y
   no en monoespaciada: es un identificador que un director lee, no código. */
.codigo-corto {
  font-family: var(--font-sans);
  font-size: 13px;
  color: var(--text-primary);
  background: var(--surface-canvas);
  padding: 2px 6px;
  border-radius: 3px;
}


/* ── El catálogo del sistema de diseño ────────────────────────────────────── */
/* ⚠️ Una MUESTRA, no una tarjeta.
   El panel de SIGA es oscuro a propósito —para que confundirlo con el panel de
   un colegio sea imposible (MOR-133)— y los componentes del colegio viven sobre
   claro. Meter un `.card-academic` blanco ahí adentro dejaba los títulos, que
   son claros por el layout de SIGA, ilegibles sobre el blanco.

   La salida no es pelearle a la cascada: es separar las dos cosas. El chrome
   del catálogo —títulos, explicaciones— habla el idioma de SIGA; lo de adentro
   de este marco es «así se ve en el colegio», y por eso lleva su fondo. */
.catalogo-muestra {
  background: var(--surface-canvas);
  border: 1px solid var(--siga-borde);
  border-radius: var(--radius-md);
  padding: var(--space-lg);
  margin-bottom: var(--space-xl);
  /* Una tabla ancha desborda antes de romper la página. */
  overflow-x: auto;
}

/* Y adentro del marco manda el colegio, no SIGA: sin esto, `.siga-admin h2` y
   `.siga-admin a` pintan el texto en el claro del panel oscuro. */
.catalogo-muestra,
.catalogo-muestra h1,
.catalogo-muestra h2,
.catalogo-muestra h3 { color: var(--text-primary); }

.catalogo-muestra a:not(.btn) { color: var(--brand-primary); }

.catalogo-nota {
  color: var(--siga-tenue);
  font-size: 14px;
  margin-bottom: var(--space-md);
  max-width: 68ch;
}


/* ── El árbol del menú del profesor (etapa ③) ─────────────────────────────── */
/* Área → nivel → grado → sección, con los escalones que no dividen nada ya
   podados al construir. La profundidad REAL varía por profesor, así que el
   sangrado se acumula solo: cada rama corre a sus hijos un poco más. */
.sidebar-rama > .sidebar-nav,
.sidebar-rama > .sidebar-rama { padding-left: var(--space-sm); }

.sidebar-rama-titulo {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  padding: 6px var(--space-lg);
  font-size: 13px;
  font-weight: 500;
  color: var(--text-secondary);
  cursor: pointer;
  list-style: none;
  /* El nombre de un área es largo —«Grupo de Creación, Recreación y
     Producción»— y el menú tiene 260 px: se parte en dos renglones antes que
     desbordar, porque el sidebar recorta. */
  line-height: 1.3;
}

.sidebar-rama-titulo::-webkit-details-marker { display: none; }
.sidebar-rama-titulo::marker { content: ""; }

/* La flecha: la dibuja el CSS y gira al abrir. Un icono más sería un SVG por
   rama, y un profesor puede tener veinte. */
.sidebar-rama-titulo::before {
  content: "";
  flex: 0 0 auto;
  width: 0;
  height: 0;
  border-left: 4px solid currentColor;
  border-top: 3.5px solid transparent;
  border-bottom: 3.5px solid transparent;
  transition: transform var(--transition-fast);
}

.sidebar-rama[open] > .sidebar-rama-titulo::before { transform: rotate(90deg); }
.sidebar-rama-titulo:hover { color: var(--text-primary); background: var(--surface-hover); }

.sidebar-rama-titulo:focus-visible {
  outline: 2px solid var(--brand-primary);
  outline-offset: -2px;
}


/* Tooltip propio (MOR-173).
   ───────────────────────
   Daniel: «mira el tooltip, es de HTML puro. Revisalos todos». El `title` del
   navegador no se puede estilar, tarda un segundo largo en aparecer, sale con
   la tipografía del sistema y en el teléfono no existe.

   Sin librería: la app no tiene Node ni Bootstrap, y esto son treinta líneas de
   CSS. Se usa poniendo `data-tooltip` en cualquier elemento.

   ⚠️ Aparece también con `:focus-visible`, no solo al pasar el ratón. Un
   tooltip solo-hover deja sin la explicación a quien navega con teclado — y en
   un control que es SOLO un icono, esa explicación es su nombre.

   ⚠️ Va DEBAJO por defecto. Arriba se recorta cuando el elemento está pegado al
   borde superior, que es justo donde viven los controles de la barra de
   contexto. `--arriba` existe para las filas del final de una lista. */
[data-tooltip] {
  position: relative;
}

[data-tooltip]::after {
  content: attr(data-tooltip);
  position: absolute;
  top: calc(100% + 8px);
  left: 50%;
  transform: translateX(-50%);
  z-index: 60;

  background: var(--text-primary);
  color: var(--surface-elevated);
  font-family: var(--font-sans);
  font-size: 12.5px;
  font-weight: 400;
  line-height: 1.35;
  letter-spacing: 0;
  text-transform: none;
  white-space: normal;
  width: max-content;
  max-width: 240px;
  padding: var(--space-xs) var(--space-sm);
  border-radius: var(--radius-sm);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.18);

  opacity: 0;
  visibility: hidden;
  transition: opacity var(--transition-fast), visibility var(--transition-fast);
  pointer-events: none;
}

/* La flechita, del mismo color que el globo. */
[data-tooltip]::before {
  content: "";
  position: absolute;
  top: calc(100% + 2px);
  left: 50%;
  transform: translateX(-50%);
  z-index: 61;
  border: 4px solid transparent;
  border-bottom-color: var(--text-primary);
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--transition-fast), visibility var(--transition-fast);
  pointer-events: none;
}

[data-tooltip]:hover::after,
[data-tooltip]:hover::before,
[data-tooltip]:focus-visible::after,
[data-tooltip]:focus-visible::before {
  opacity: 1;
  visibility: visible;
}

/* ⚠️ Anclado a la DERECHA, creciendo hacia adentro.
   Para un control pegado al borde de un contenedor que recorta. El caso que lo
   trajo: el estado de cada sección en el menú lateral, que tiene
   `overflow-y: auto` — y el CSS convierte el eje X en `auto` en cuanto el otro
   no es `visible`, así que un globo centrado se corta al ras del sidebar. */
[data-tooltip][data-tooltip-izquierda]::after {
  left: auto;
  right: 0;
  transform: none;
}

[data-tooltip][data-tooltip-izquierda]::before {
  left: auto;
  right: 4px;
  transform: none;
}

[data-tooltip][data-tooltip-arriba]::after {
  top: auto;
  bottom: calc(100% + 8px);
}

[data-tooltip][data-tooltip-arriba]::before {
  top: auto;
  bottom: calc(100% + 2px);
  border-bottom-color: transparent;
  border-top-color: var(--text-primary);
}

@media (prefers-reduced-motion: reduce) {
  [data-tooltip]::after,
  [data-tooltip]::before { transition: none; }
}

/* La celda de acciones de un año escolar: el conteo y el botón en una línea,
   alineados, sin que el botón estire la fila. */
.anio-acciones {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  flex-wrap: wrap;
}

.promocion-cifra--repite strong    { color: var(--danger-text); }
.promocion-cifra--sin_datos strong { color: var(--warning-text); }
.carga-definitiva.bajo     { color: var(--danger-text); }

/* ── la asistencia ────────────────────────────────────────────────────────

   ⚠️ `.asistencia-boton` es UNA DE LAS DOS EXCEPCIONES declaradas a la regla de
   color: tocarlo cicla presente → ausente → tarde, así que el control y el dato
   son el mismo objeto. El icono sigue esa misma lógica.                       */

.asistencia-alumno .avatar { flex: none; }

.asistencia-boton {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
}

/* El icono de estado se elige por CSS y no por Ruby: el estado cambia con un
   toque, sin ir al servidor, así que el marcador tiene que poder cambiar sin
   volver a dibujar la fila. */
.asistencia-icono { flex: none; display: grid; place-items: center; }

.asistencia-alumno[data-estado="presente"] .asistencia-icono { color: var(--success-text); }
.asistencia-alumno[data-estado="ausente"]  .asistencia-icono { color: var(--danger-text); }
.asistencia-alumno[data-estado="tarde"]    .asistencia-icono { color: var(--warning-text); }

/* Solo se ve el icono del estado en el que está la fila. Los tres viven en el
   HTML para que alternar no tenga que pedirle nada al servidor. */
.asistencia-icono[data-para] { display: none; }
.asistencia-alumno[data-estado="presente"] .asistencia-icono[data-para="presente"],
.asistencia-alumno[data-estado="ausente"]  .asistencia-icono[data-para="ausente"],
.asistencia-alumno[data-estado="tarde"]    .asistencia-icono[data-para="tarde"] {
  display: grid;
}


/* ---- Pestañas de una misma pantalla (MOR-149) ---- */
/* Cada una es un enlace con su propia URL: el botón de atrás y el "mira esto"
   por WhatsApp tienen que funcionar. Ver `shared/_pestanas`. */
.pestanas {
  display: flex;
  gap: var(--space-xs);
  border-bottom: 1px solid var(--border-standard);
  margin-bottom: var(--space-lg);
  overflow-x: auto;
  scrollbar-width: none;
}
.pestanas::-webkit-scrollbar { display: none; }

.pestana {
  padding: var(--space-sm) var(--space-md);
  color: var(--text-secondary);
  text-decoration: none;
  font-weight: 500;
  font-size: 14px;
  white-space: nowrap;
  border-bottom: 2px solid transparent;
  /* El borde inferior se mueve 1px hacia abajo para tapar el de `.pestanas`:
     si no, la pestaña activa queda con dos líneas de distinto color. */
  margin-bottom: -1px;
  transition: color var(--transition-fast), border-color var(--transition-fast);
  /* 44px de alto mínimo: es un objetivo táctil, no un renglón de texto. */
  display: inline-flex;
  align-items: center;
  min-height: 44px;
}

.pestana:hover {
  color: var(--text-primary);
  border-bottom-color: var(--border-standard);
}

/* ⚠️ La activa lleva además un fondo pastel, no solo la línea de abajo.
   Daniel: «la pestaña activa un poco más resaltada, pudiera ser agregarle un
   sutil bg pastel». Con la línea sola, en una fila de cuatro hay que buscar
   cuál está subrayada; con el fondo se ve sin leer.
   Es el mismo `--brand-subtle` de la franja del título y del ítem activo del
   sidebar: las tres marcas de «estás acá» son la misma. */
.pestana.activa {
  color: var(--brand-primary);
  border-bottom-color: var(--brand-primary);
  background: var(--brand-subtle);
  border-radius: var(--radius-sm) var(--radius-sm) 0 0;
}

.pestana:focus-visible {
  outline: 2px solid var(--brand-primary);
  outline-offset: -2px;
  border-radius: var(--radius-sm) var(--radius-sm) 0 0;
}


/* El avance de cada área en el panel del profesor (MOR-223).
   Daniel: «si ya las tengo calificadas me aparece 20 de 20 y un check en verde;
   si me falta me aparece 20 de 30 y en amarillo; si no tengo nada, en rojo».

   ⚠️ Es un ENLACE: lleva a la primera evaluación incompleta, no al listado.
   «Al hacer clic me voy directamente a la calificación que falta». */
.subject-avance {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  margin-left: var(--space-xs);
  padding: 1px var(--space-xs);
  border-radius: 999px;
  font-size: 11px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  text-decoration: none;
  white-space: nowrap;
}

.subject-avance--completo {
  background: color-mix(in srgb, var(--success-text) 12%, transparent);
  color: var(--success-text);
}

.subject-avance--a_medias {
  background: color-mix(in srgb, var(--warning-text) 14%, transparent);
  color: var(--warning-text);
}

.subject-avance--vacio {
  background: color-mix(in srgb, var(--danger-text) 12%, transparent);
  color: var(--danger-text);
}

.subject-avance--sin_evaluaciones {
  color: var(--text-tertiary);
  font-weight: 400;
}

a.subject-avance:hover { filter: brightness(0.9); }

/* El estado de carga de una sección, en el menú (MOR-223).
   Daniel: «hay un indicativo de si les falta por calificar alguna nota».

   ⚠️ Acá el color SÍ es del dato —cuántas notas faltan es un hecho de la
   sección, no la afordancia de un control— y por eso puede llevarlo. Pero
   nunca SOLO color: el nombre va en el tooltip y en el `aria-label`. */
/* ⚠️ Un ICONO, no un punto de color (QA del 7 de septiembre).
   Los tres estados eran el MISMO círculo de 8 px y solo cambiaba el color, así
   que el color cargaba con todo el significado y había que leer el tooltip
   para saber cuál era. Un check, un reloj y un «!» se distinguen sin color. */
.seccion-estado {
  flex: 0 0 auto;
  display: inline-flex;
  margin-left: auto;
}

.seccion-estado--completo { color: var(--success-text); }
.seccion-estado--a_medias { color: var(--warning-text); }
.seccion-estado--vacio    { color: var(--danger-text); }

/* Sin evaluaciones todavía: es un HECHO, no un problema. Va en el gris de lo
   secundario — un ámbar acá le pondría urgencia a septiembre. */
.seccion-estado--sin_evaluaciones { color: var(--text-tertiary); }

/* ---- La libreta de la sección (MOR-223) ---- */
/* Todas las notas del lapso en una tabla: alumno × evaluación. Daniel: «ten
   presente que lo más importante para los profesores son sus estudiantes». */

/* ⚠️ El scroll horizontal es de la TABLA, no de la página. Una sección con
   ocho evaluaciones no cabe en un teléfono, y sin este marco el `body` entero
   se corre de lado — que es el bug que `desborde_en_el_telefono_test` persigue. */
.libreta-marco {
  overflow-x: auto;
  margin-bottom: var(--space-md);
}

.libreta { min-width: max-content; }

/* La columna del alumno se queda quieta al desplazar: sin ella, a la tercera
   evaluación no se sabe de quién es la fila que se está mirando. */
.libreta .libreta-alumno {
  position: sticky;
  left: 0;
  z-index: 1;
  background: var(--surface-elevated);
  white-space: nowrap;
  font-weight: 500;
}

.libreta thead .libreta-alumno { background: var(--surface-hover); }

.libreta-columna { min-width: 8rem; }

.libreta-columna-enlace {
  display: flex;
  flex-direction: column;
  gap: 2px;
  text-decoration: none;
  color: inherit;
}

.libreta-columna-enlace:hover .libreta-columna-nombre { color: var(--brand-primary); }

.libreta-columna-nombre {
  font-weight: 600;
  text-transform: none;
  letter-spacing: 0;
  font-size: 13px;
  /* Un nombre largo se recorta: la columna la define el conteo de abajo, no
     cuánto escribió el profesor. */
  max-width: 12rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.libreta-columna-peso {
  font-size: 11px;
  color: var(--text-tertiary);
  font-variant-numeric: tabular-nums;
}

/* ⚠️ El avance en TRES estados, y con el texto siempre visible.
   
   Daniel: «si ya las tengo calificadas me aparece 20 de 20 y un check en verde;
   si me falta me aparece 20 de 30 y en amarillo; si no tengo nada, en rojo».
   
   Acá el color SÍ es del dato —cuántas notas faltan es un hecho de la
   evaluación, no una afordancia de un control— y por eso puede llevarlo. Pero
   nunca solo color: el «20 de 30» se lee igual en blanco y negro y por quien no
   distingue el rojo del verde. */
.libreta-columna-avance {
  font-size: 11px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  text-transform: none;
  letter-spacing: 0;
}

/* Cuántas competencias mide esta evaluación. Un número con su icono, y el
   texto completo en el tooltip: en un encabezado de tabla no hay lugar para
   «Resuelve problemas de adición y sustracción con números naturales». */
.libreta-columna-mide {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  font-size: 10px;
  font-weight: 600;
  color: var(--text-tertiary);
  text-transform: none;
  letter-spacing: 0;
}

/* ── El MOMENTO de cada columna (etapa ③) ─────────────────────────────────── */
/* ⚠️ La fecha ORDENA y da FOCO. No cierra nada.
   Daniel lo había planteado como «se activa conforme a la fecha y se
   desactivan las otras», y suena razonable hasta que se piensa quién queda
   afuera: el profesor que carga tarde, que es el caso NORMAL y lo descubre un
   domingo a las nueve. Quedó en que la fecha jerarquiza, y él estuvo de
   acuerdo.

   Las tres columnas se escriben igual; lo que cambia es cuánto pesan. */

/* ATRASADA: ya pasó y le faltan notas. Es lo que hay que hacer HOY, así que
   es la única con fondo — el resto de la fila de encabezados es plana. */
.libreta-columna--atrasada {
  background: var(--warning-bg);
  box-shadow: inset 0 -2px 0 var(--warning-text);
}

.libreta-columna--atrasada .libreta-cuando-marca {
  color: var(--warning-text);
  font-weight: 600;
}

/* HOY: se aplica hoy. Se marca, sin alarmar: todavía no falta nada. */
.libreta-columna--hoy { box-shadow: inset 0 -2px 0 var(--brand-primary); }
.libreta-columna--hoy .libreta-cuando-marca { color: var(--brand-primary); font-weight: 600; }

/* FUTURA: atenuada, pero ESCRIBIBLE. Una evaluación se puede adelantar, y una
   columna apagada que igual acepta notas es más honesta que una cerrada. */
.libreta-columna--futura .libreta-columna-nombre,
.libreta-columna--futura .libreta-columna-peso,
.libreta-columna--futura .libreta-columna-cuando { opacity: 0.55; }

/* ⚠️ Y su «0 de 22» pierde el rojo.
   El rojo del avance significa «no has cargado ninguna», y en una evaluación
   que todavía no se aplicó eso no es un problema: es lo esperado. Dejarlo
   rojo pone una alarma sobre algo que no pasó, y una alarma que suena sin
   motivo enseña a ignorarlas todas. */
.libreta-columna--futura .libreta-columna-avance {
  color: var(--text-tertiary);
  opacity: 0.55;
}

.libreta-columna-cuando {
  display: flex;
  align-items: baseline;
  gap: var(--space-micro);
  font-size: 11px;
  font-weight: 400;
  color: var(--text-tertiary);
  text-transform: none;
  letter-spacing: 0;
  white-space: nowrap;
}

.libreta-avance--completa { color: var(--success-text); }
.libreta-avance--a_medias { color: var(--warning-text); }
.libreta-avance--vacia    { color: var(--danger-text); }

.libreta-nota,
.libreta-promedio {
  text-align: center;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.libreta thead .libreta-promedio { min-width: 6rem; }

.libreta-sin-nota { color: var(--text-tertiary); }

/* ---- Paneles de una misma pantalla (MOR-223) ---- */
/* Daniel: «un clic vale más que muchos scroll hacia abajo… si no lo hago me
   pierdo de lo que está allí que no me aparece al cargar la vista».

   El problema NO era el scroll: era no saber que había algo más abajo. Una fila
   de pestañas dice de una qué tiene esta pantalla. */
.paneles-grupo { display: block; }

.paneles {
  display: flex;
  gap: var(--space-xs);
  border-bottom: 1px solid var(--border-standard);
  margin-bottom: var(--space-lg);
  overflow-x: auto;
  scrollbar-width: none;
}

.paneles::-webkit-scrollbar { display: none; }

.panel-pestana {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  /* 44 px de objetivo táctil: esto se toca desde un teléfono. */
  min-height: 44px;
  padding: 0 var(--space-md);
  border: 0;
  border-bottom: 2px solid transparent;
  /* Tapa el borde de `.paneles`: sin esto la activa queda con dos líneas de
     distinto color. */
  margin-bottom: -1px;
  background: transparent;
  font-family: var(--font-sans);
  font-size: 14px;
  font-weight: 500;
  color: var(--text-secondary);
  white-space: nowrap;
  cursor: pointer;
  transition: color var(--transition-fast), border-color var(--transition-fast);
}

.panel-pestana:hover {
  color: var(--text-primary);
  border-bottom-color: var(--border-standard);
}

.panel-pestana.activa {
  color: var(--brand-primary);
  border-bottom-color: var(--brand-primary);
  font-weight: 600;
}

.panel-pestana:focus-visible {
  outline: 2px solid var(--brand-primary);
  outline-offset: -2px;
  border-radius: var(--radius-sm) var(--radius-sm) 0 0;
}

/* El número al lado del nombre —«Alumnos 22»— para no tener que entrar a
   contar. Sin color semántico: es un conteo, no una alerta (MOR-135). */
.panel-insignia {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 1.5rem;
  height: 1.25rem;
  padding: 0 5px;
  border-radius: 999px;
  background: var(--surface-hover);
  color: var(--text-tertiary);
  font-size: 11px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.panel-pestana.activa .panel-insignia {
  background: color-mix(in srgb, var(--brand-primary) 12%, transparent);
  color: var(--brand-primary);
}

/* ---- Pastillas: elegir UNO entre pocos (MOR-136) ---- */
/* Primas de las pestañas y por eso se parecen: enlaces, no botones de
   JavaScript. Lo que cambia es qué preguntan — una pestaña cambia el ÁNGULO
   sobre lo mismo, una pastilla cambia el SUJETO. Ver `shared/_pastillas`.

   ⚠️ Miden y se redondean como un `.btn`: 36 px de alto y `--radius-sm`.
   Estuvieron con `border-radius: 999px` y 40 px, y desentonaban con todo lo
   demás —«los botones no cuadran con el resto del estilo del proyecto»—: en
   una app donde cada control tiene 3 px de radio, una cápsula se lee como
   pegada de otro sitio. */

/* La escalera: las tres filas juntas. El `gap` va acá y no en cada fila —
   márgenes de hermanos se colapsan entre sí y se duplican contra el padding
   de la tarjeta; un `gap` de grid no hace ninguna de las dos cosas. */
.plan-escalera {
  display: grid;
  gap: var(--space-sm);
}

/* ⚠️ El margen de abajo, y su excepción en la última.
   En la libreta la fila de lapsos quedaba PEGADA a la tabla —«sigue sin
   espacio entre los filtros de lapsos y la lista»—. Donde las pastillas son lo
   último de su tarjeta (la escalera del plan) el margen sobraría, así que se
   quita con `:last-child` en vez de escribirlo a mano en cada pantalla. */
.pastillas {
  margin-bottom: var(--space-md);
  display: flex;
  /* ⚠️ `center`, NUNCA `baseline`.
     Con `baseline`, el navegador alinea la línea de texto de la etiqueta de
     11 px con la de una pastilla de 36 px, y para lograrlo empuja la pastilla
     HACIA ARRIBA fuera de su renglón: las tres filas se montaban una sobre
     otra y la activa aparecía como un borrón azul encima de la de al lado. */
  align-items: center;
  gap: var(--space-md);
  flex-wrap: wrap;
}

/* La etiqueta ancla la fila, así que necesita ancho FIJO: sin él, «Lapso» y
   «Área de aprendizaje» empiezan sus pastillas en columnas distintas y las
   tres filas se leen como tres controles sueltos en vez de una escalera. */
.pastillas:last-child { margin-bottom: 0; }

.pastillas-etiqueta {
  flex: 0 0 auto;
  min-width: 9.5rem;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-tertiary);
}

.pastillas-fila {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs);
  /* Que quede algo por envolver: sin esto, un `flex` mide por el contenido y
     una fila de nueve grados empuja la tarjeta hacia afuera. */
  min-width: 0;
  flex: 1 1 auto;
}

.pastilla {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 36px;
  padding: 0 var(--space-md);
  border: 1px solid var(--border-standard);
  border-radius: var(--radius-sm);
  background: var(--surface-elevated);
  color: var(--text-secondary);
  font-family: var(--font-sans);
  font-size: 13px;
  font-weight: 500;
  line-height: 1;
  text-decoration: none;
  white-space: nowrap;
  transition: all var(--transition-fast);
}

.pastilla:hover {
  border-color: var(--brand-primary);
  color: var(--brand-primary);
  background: var(--surface-hover);
}

/* ⚠️ La activa se pinta con el azul de marca y no con un tono semántico: es
   afordancia de un control, no significado de un dato (regla de color,
   MOR-135). Y va SÓLIDA, no con un borde: entre nueve pastillas, un borde de
   1px no se encuentra de un vistazo. */
.pastilla.activa,
.pastilla.activa:hover {
  background: var(--brand-primary);
  border-color: var(--brand-primary);
  color: #fff;
}

.pastilla:focus-visible {
  outline: 2px solid var(--brand-primary);
  outline-offset: 2px;
}

/* ---- La planilla de informes descriptivos (MOR-216) ---- */
/* La maestra escribe veinticinco seguidos: la pantalla tiene que sentirse como
   una lista y no como veinticinco formularios. */
.informes-lista {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--space-lg);
}

.informe-cabecera {
  display: flex;
  align-items: baseline;
  gap: var(--space-sm);
  margin-bottom: var(--space-xs);
}

.informe-alumno {
  font-weight: 600;
  flex: 1 1 auto;
  min-width: 0;
}

/* El promedio al lado del nombre: escribiendo veinticinco informes seguidos hay
   que poder recordar de quién se habla sin salir de la pantalla.
   ⚠️ SIN color semántico — es una referencia para el docente, no una alarma, y
   en SIGA el rojo quiere decir «este niño está aplazado» (MOR-135). */
.informe-promedio {
  flex: 0 0 auto;
  font-size: 13px;
  font-variant-numeric: tabular-nums;
  color: var(--text-secondary);
  white-space: nowrap;
}

/* El estado del guardado, por fila. Un guardado silencioso que falla es peor
   que no guardar: se cierra el teléfono creyendo que se terminó. */
.informe-campo {
  /* Crece con lo que se escribe: tres líneas fijas esconden lo ya escrito justo
     cuando se relee para corregir. El alto lo pone el controlador. */
  resize: vertical;
  min-height: 4.5rem;
  overflow-y: hidden;
  line-height: 1.5;
}

.informe-fila[data-estado="error"] .informe-campo { border-color: var(--eliminar-primary); }

/* El informe en la boleta y en el portal de la familia. */
.informe-descriptivo {
  margin-top: var(--space-md);
  padding: var(--space-md);
  border-left: 3px solid var(--border-standard);
  background: var(--surface-hover);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
}

.informe-descriptivo-titulo {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-tertiary);
  margin-bottom: var(--space-xs);
}

.informe-descriptivo-cuerpo {
  margin: 0;
  white-space: pre-line;
  line-height: 1.6;
}

/* ---- Las competencias en el portal de la familia (MOR-136) ---- */
/* Es para lo ÚNICO que existen las competencias: que la madre lea qué se
   trabajó, no solo con qué nota salió su hijo. */
.portal-materia-fila {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-sm);
}

.portal-competencias {
  list-style: none;
  margin: var(--space-xs) 0 0;
  padding: 0 0 0 var(--space-sm);
  border-left: 2px solid var(--border-standard);
  display: grid;
  gap: var(--space-xs);
}

.portal-competencia-fila {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-sm);
  font-size: 14px;
  color: var(--text-secondary);
}

/* El nivel de la competencia es DATO, así que puede llevar tono — pero nunca
   rojo, que en SIGA quiere decir «este niño está aplazado» y ese significado no
   se gasta (regla de color, MOR-135). Neutro y con la sigla siempre legible. */
.portal-competencia-nivel {
  flex: 0 0 auto;
  font-weight: 600;
  font-size: 13px;
  font-variant-numeric: tabular-nums;
  color: var(--text-primary);
  white-space: nowrap;
}

.portal-indicadores {
  margin: 2px 0 0;
  padding-left: var(--space-md);
  font-size: 13px;
  color: var(--text-tertiary);
}

/* Cada indicador con SU nivel (MOR-136, segunda mitad). Un indicador lo mide
   una sola evaluación, así que el nivel es el de esa nota — una lectura, no un
   promedio inventado. Salen solo los que se midieron: el renglón sin nivel
   alarga una boleta que ya tiene ocho materias. */
.portal-indicador {
  display: flex;
  align-items: baseline;
  gap: var(--space-xs);
}

/* Se apoya en la línea de puntos para que el ojo llegue del texto a su nivel
   sin que la columna tenga que estar alineada — los indicadores son de largos
   muy distintos. */
.portal-indicador::after {
  content: "";
  flex: 1 1 auto;
  min-width: var(--space-sm);
  border-bottom: 1px dotted var(--border-subtle);
  transform: translateY(-3px);
}

.portal-indicador-nivel {
  order: 3;
  flex: 0 0 auto;
  font-weight: 600;
  color: var(--text-secondary);
  white-space: nowrap;
}

.portal-leyenda {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 4px;
}

.portal-leyenda strong { margin-right: var(--space-xs); }
.portal-leyenda .dato-secundario { margin-left: var(--space-xs); }

/* ---- El plan ANIDADO: la evaluación cuelga de su competencia ---- */
/* Decisión de Daniel, 8 de septiembre: «¿será que colocamos las competencias y
   evaluaciones juntas?». Eran dos pantallas, y qué evalúa qué era una lista de
   chips que había que ir a buscar. */

/* ⚠️ `.plan-evaluaciones` se retiró con el `<ul>` (21 de septiembre).
   Era una grilla con `gap: 1px` sobre fondo gris, que es lo correcto para
   FILAS de una línea. Desde que cada evaluación lleva sus indicadores dentro,
   ya no es una fila: es una tarjeta, y se separan con su propio margen. */

/* ⚠️ `flex-wrap: wrap` es obligatorio, no una comodidad para el teléfono.
   El bloque de indicadores pide `flex: 1 0 100%` para caer en su propio
   renglón; sin envoltura ese 100 % no puede bajar, se queda en la misma línea
   y comprime al nombre —que lleva `min-width: 0`— hasta dejarlo en un
   rectángulo de diez píxeles. Se vio en pantalla el 9 de septiembre. */
.plan-evaluacion {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-sm);
  margin-top: var(--space-sm);
  padding: var(--space-sm);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-sm);
  /* ⚠️ Crema sobre el blanco de su componente, no blanco sobre blanco: tres
     niveles anidados necesitan que cada uno se distinga del de afuera, y
     alternar el fondo lo hace sin sumar un tercer borde. */
  background: var(--surface-canvas);
}

/* ── Cuántas notas tiene ya esta evaluación ──────────────────────────────── */
/* Daniel: «colocar un elemento que indique si ya tiene notas cargadas, o
   cuántas de cuántos». Hasta ahora solo se sabía intentando borrarla.

   ⚠️ PASTILLA con icono, y no texto al margen. Salió como un número pequeño al
   lado del peso y Daniel no lo encontró: «no lo conseguía en la vista, me costó
   de hecho». El renglón ya lleva «40 %» y «5 de nov», así que un tercer par de
   números en el mismo gris se pierde entre ellos.

   ⚠️ Y es un DATO, no un control: el color lo lleva el número (`MOR-135`).
   Quien va a cargar notas usa la flecha de la izquierda. */
.plan-evaluacion-avance {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  margin-right: var(--space-xs);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.plan-evaluacion-avance svg { flex: 0 0 auto; opacity: 0.7; }

/* Sin alumnos inscritos no hay «de cuántos»: sin pastilla y en gris, porque no
   es un estado del profesor — es que la sección está vacía. */
.plan-evaluacion-avance--sin-alumnos {
  padding: 0;
  background: none;
  font-size: 11.5px;
  font-style: italic;
  color: var(--text-tertiary);
}

/* ⚠️ `aria-disabled` y no `disabled`, así que el apagado se pinta acá.
   SIN `pointer-events: none`: el tooltip es lo único que explica por qué el
   botón no se puede tocar, y quitándole los eventos el tooltip no aparece —
   quedaría un botón muerto sin razón. */
.btn--apagado {
  opacity: 0.4;
  cursor: not-allowed;
}

.btn--apagado:hover { opacity: 0.55; }

/* ⚠️ El botón DESAPARECE mientras la fila nueva está abierta, y sin una línea
   de JavaScript. Daniel: «al hacer clic en "Agregar evaluación" desaparece el
   botón y aparece un nuevo form igual al de la edición».

   Lo hace el orden del marcado —el frame va ANTES del botón— más `:not(:empty)`:
   el `<turbo-frame>` está vacío hasta que llega la fila. Con el botón visible
   al lado, la pantalla ofrece empezar una segunda antes de terminar la primera. */
.plan-alta-de-evaluacion:not(:empty) + .plan-competencia-acciones { display: none; }

/* ── La fila de una evaluación que todavía NO existe ─────────────────────── */
/* Daniel: «al hacer clic en Agregar evaluación desaparece el botón y aparece un
   nuevo form igual al de la edición, y una X al final parecida al basurero, de
   modo que se entienda que aún no está creado».

   ⚠️ Es la MISMA fila —las mismas clases, el mismo alto, los mismos campos— y
   lo único que la distingue es el borde punteado: lo que está por existir se
   dibuja con línea discontinua, que es como se dibuja lo provisional en
   cualquier sitio. Un color distinto la habría sacado de la familia. */
.plan-evaluacion--naciendo {
  border-style: dashed;
  border-color: var(--brand-primary);
  background: var(--surface-elevated);
}

/* El hueco de la flecha de «cargar notas»: todavía no hay planilla a donde ir,
   pero sin el hueco la fila se corre respecto de las de arriba. */
.plan-evaluacion-ir--vacio {
  color: var(--text-tertiary);
  opacity: 0.5;
  cursor: default;
}

/* ⚠️ En la fila nueva los campos se VEN, no esperan al hover: en las otras el
   borde aparece al apuntar porque se leen mucho más de lo que se editan, y acá
   es al revés — la fila existe para escribirla. */
.plan-evaluacion--naciendo .plan-evaluacion-nombre-campo,
.plan-evaluacion--naciendo .plan-evaluacion-peso-campo {
  border-color: var(--border-standard);
  background: var(--surface-canvas);
}

/* Y sus dos botones se ven siempre: son la única manera de terminar. */
.plan-evaluacion--naciendo .plan-evaluacion-acciones { opacity: 1; }

/* ── Los indicadores que mide, dentro de su evaluación ───────────────────── */
/* Daniel, 21 de septiembre: «cada componente está en un contenedor que contiene
   la evaluación y los indicadores». El bloque vuelve al blanco: alternado con
   el crema de la evaluación, los tres niveles se leen sin bordes apilados. */
.plan-evaluacion-mide {
  flex: 1 0 100%;
  margin-top: var(--space-xs);
  padding: var(--space-xs) var(--space-sm) var(--space-sm);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-sm);
  background: var(--surface-elevated);
}

/* ⚠️ AVISO y no error: la evaluación cuenta para el promedio igual. Lo que no
   puede hacer la boleta es decir qué se midió. Alcanza a lo escrito antes de
   la regla; lo que nace ahora nace con su indicador. */
.plan-sin-indicador {
  display: flex;
  align-items: center;
  gap: 4px;
  margin: 0 0 var(--space-xs);
  font-size: 11.5px;
  color: var(--warning-text);
}

.plan-sin-indicador svg { flex: 0 0 auto; }

/* Y el nombre no baja de un ancho legible: comprimido, un `<input>` no se ve
   vacío — se ve roto, que es lo que parecía.

   ⚠️ Pero TAMPOCO pasa de la mitad de la fila. Daniel, 24 de septiembre, en un
   monitor de escritorio: «el input del título es bastante laaargo, creo que
   demasiado; podría funcionar con el 50 % del total del largo y así estiramos
   un poco más el range para darle precisión».

   Creciendo sin tope, el nombre se quedaba con todo el monitor para un texto
   de cuatro palabras —«Examen — 1er Lapso»— mientras el deslizador, que es lo
   único que NECESITA largo, se quedaba en sus 76 px. El 50 % es el reparto:
   lo que el nombre ya no toma se lo lleva el peso, que es quien lo aprovecha.

   ⚠️ `max-width` en porcentaje y no un `flex-basis` fijo: así las filas quedan
   alineadas entre sí —todas cortan en el mismo punto— y en una ventana chica
   el nombre sigue estirándose hasta su mitad en vez de quedarse en 11rem. */
.plan-evaluacion-campo--nombre { flex: 1 1 11rem; max-width: 50%; }

/* ── Editar donde se lee (QA del 9 de septiembre) ────────────────────────── */
/* Daniel: «al hacer clic en el elemento que deseo editar, como lo hace Linear».
   El input está SIEMPRE en el DOM; lo único que cambia es que se le ve el
   borde. Cambiar un `<span>` por un `<input>` al hacer clic obliga a recrear el
   elemento, se lleva el foco a mitad de camino y hace saltar el texto cuando la
   caja no calca la tipografía. */
.plan-evaluacion-campo { display: flex; align-items: center; min-width: 0; }

/* ⚠️ El peso CRECE, y es la otra mitad del tope del nombre: el espacio que el
   título dejó de tomar tiene que irse a alguna parte, y el único control de la
   fila que mejora con largo es el deslizador. Lo absorbe él —`flex` en el
   rango, abajo—, así que el número y el `%` no se mueven de su sitio. */
.plan-evaluacion-campo--peso { flex: 1 1 auto; gap: 2px; }

.plan-evaluacion-nombre-campo,
.plan-evaluacion-peso-campo {
  padding: 2px 5px;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: transparent;
  font: inherit;
  color: var(--text-primary);
  transition: border-color var(--transition-fast), background var(--transition-fast);
}

.plan-evaluacion-nombre-campo {
  width: 100%;
  text-overflow: ellipsis;
  font-size: 13px;
}

.plan-evaluacion-peso-campo {
  width: 4.5ch;
  font-size: 12px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  text-align: right;
  color: var(--text-secondary);
}

/* ⚠️ La afordancia aparece al apuntar el CAMPO, no la fila entera. Encendiendo
   los tres a la vez la fila se convierte en un formulario y deja de leerse como
   una lista — y esto se lee mucho más de lo que se edita. */
.plan-evaluacion-nombre-campo:hover,
.plan-evaluacion-peso-campo:hover {
  border-color: var(--border-subtle);
  background: var(--surface-elevated);
}

.plan-evaluacion-nombre-campo:focus,
.plan-evaluacion-peso-campo:focus {
  border-color: var(--brand-primary);
  background: var(--surface-elevated);
  color: var(--text-primary);
  outline: none;
}

/* El enlace a la planilla se muda al margen: el nombre ya no puede ser enlace
   —ahora se escribe encima— y acá no compite con nada. */
.plan-evaluacion-ir {
  flex: 0 0 auto;
  display: inline-flex;
  padding: 2px;
  border-radius: var(--radius-sm);
  color: var(--text-tertiary);
}

.plan-evaluacion-ir:hover { color: var(--brand-primary); background: var(--surface-hover); }

/* ── Qué mide esta evaluación, en su fila ────────────────────────────────── */
/* ⚠️ El chip dice las OTRAS competencias, no todas: en el plan anidado la
   evaluación ya está debajo de la que mide, y repetirlo es decir dos veces lo
   mismo en el mismo renglón. Lo que la posición no puede decir es que además
   mide otra — ahí la evaluación sale dos veces, y sin el chip las dos
   apariciones se ven idénticas y sin relación. */
.plan-evaluacion-chips {
  flex: 1 0 100%;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 4px;
  margin-top: 3px;
  padding-left: 22px;
}

.plan-evaluacion-chips-etiqueta,
.plan-evaluacion-chips-vacio {
  font-size: 11px;
  color: var(--text-tertiary);
}

.plan-evaluacion-chips-vacio {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  color: var(--warning-text);
}

.plan-evaluacion-chip-form { display: contents; }

/* Un chip es un botón que QUITA ese enganche: se ve lo que hay y se deshace
   donde se ve, sin abrir nada. La × aparece al apuntarlo, para que el chip se
   lea como dato y no como un control a medio apretar. */
.plan-evaluacion-chip {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  max-width: 100%;
  padding: 1px 6px;
  border: 1px solid var(--border-subtle);
  border-radius: 999px;
  background: var(--surface-elevated);
  font: inherit;
  font-size: 11px;
  color: var(--text-secondary);
  cursor: pointer;
}

.plan-evaluacion-chip:hover {
  border-color: var(--danger-text);
  color: var(--danger-text);
}

.plan-evaluacion-chip-quitar { opacity: 0; font-weight: 600; }
.plan-evaluacion-chip:hover .plan-evaluacion-chip-quitar,
.plan-evaluacion-chip:focus-visible .plan-evaluacion-chip-quitar { opacity: 1; }

/* En el táctil no hay «apuntar», así que la × se queda puesta. */
@media (hover: none) {
  .plan-evaluacion-chip-quitar { opacity: 1; }
}

.plan-evaluacion-mas { position: relative; }

.plan-evaluacion-mas-abrir {
  display: inline-flex;
  align-items: center;
  padding: 2px 5px;
  border: 1px dashed var(--border-subtle);
  border-radius: 999px;
  color: var(--text-tertiary);
  cursor: pointer;
  list-style: none;
}

.plan-evaluacion-mas-abrir::-webkit-details-marker { display: none; }

/* ⚠️ Con el menú ABIERTO, su tooltip sobra y estorba: sale hacia abajo, justo
   encima de la primera opción, y hay que apartar el ratón para poder elegir.
   Daniel: «el tooltip tapa la opción a seleccionar». Abierto, el panel ya dice
   lo que el tooltip explicaba. */
.plan-evaluacion-mas[open] > .plan-evaluacion-mas-abrir::after,
.plan-evaluacion-mas[open] > .plan-evaluacion-mas-abrir::before { display: none; }
.plan-evaluacion-mas-abrir:hover { border-color: var(--brand-primary); color: var(--brand-primary); }

.plan-evaluacion-mas-panel {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  z-index: 40;
  width: 260px;
  max-height: 220px;
  overflow-y: auto;
  padding: 4px;
  border: 1px solid var(--border-standard);
  border-radius: var(--radius-md);
  background: var(--surface-elevated);
  box-shadow: 0 8px 24px rgb(0 0 0 / 0.12);
}

.plan-evaluacion-mas-opcion {
  display: block;
  width: 100%;
  padding: 5px 7px;
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  font: inherit;
  font-size: 12px;
  text-align: left;
  color: var(--text-primary);
  cursor: pointer;
}

.plan-evaluacion-mas-opcion:hover { background: var(--surface-hover); }

.plan-evaluacion-error {
  flex: 1 0 100%;
  display: flex;
  align-items: center;
  gap: 4px;
  margin: 4px 0 0;
  font-size: 12px;
  color: var(--danger-text);
}

.plan-evaluacion--con-error { background: var(--danger-bg); }

/* ⚠️ El destello ES la confirmación: en la edición en línea no hay botón que se
   quede apretado ni flash que avise, y sin ninguna señal el profesor no sabe si
   su cambio llegó. Un aviso por campo tocado —tres por evaluación, treinta por
   plan— sería una alarma. */
@keyframes plan-guardada {
  from { background: var(--success-bg); }
  to   { background: transparent; }
}

.plan-evaluacion--guardada { animation: plan-guardada 1.2s ease-out; }

@media (prefers-reduced-motion: reduce) {
  .plan-evaluacion--guardada { animation: none; background: var(--success-bg); }
}

/* ── Entrar y salir suave (23 de septiembre) ─────────────────────────────── */
/* Daniel: «puedes suavizar la entrada y salida de los elementos al agregar o
   quitar (fade-in fade-out)».

   ⚠️ Son DOS mecanismos, y no por gusto: el navegador solo regala uno. Lo que
   APARECE se anima solo, porque una `animation` corre cuando el elemento entra
   al DOM. Lo que SE VA no tiene ese momento — Turbo reemplaza el bloque y el
   nodo desaparece de golpe, sin pasar por ningún estado intermedio. Por eso el
   desvanecido lo EMPIEZA el controlador `se-desvanece` mientras la petición
   viaja, y el reemplazo llega encima de algo que ya se apagó.

   ⚠️ Y entra solo lo que de verdad es nuevo. El stream reemplaza la competencia
   entera, así que animar `.plan-indicador` a secas haría parpadear la lista
   completa cada vez que se corrige una letra de cualquier otro: `--recien` lo
   pone el servidor, y solo en el alta. */
@keyframes plan-aparece {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: none; }
}

.plan-evaluacion--naciendo,
.alta-en-linea,
.plan-indicador--recien { animation: plan-aparece 180ms ease-out; }

.se-esta-yendo {
  opacity: 0;
  transform: scale(0.985);
  pointer-events: none;
  transition: opacity 160ms ease-out, transform 160ms ease-out;
}

@media (prefers-reduced-motion: reduce) {
  .plan-evaluacion--naciendo,
  .alta-en-linea,
  .plan-indicador--recien { animation: none; }
  .se-esta-yendo { transition: none; }
}

.plan-evaluacion-descripcion {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 13px;
}

/* El peso es el dato que se compara entre filas, así que va tabular y en su
   propio ancho: alineado, la suma se lee de arriba abajo sin sumar. */
.plan-evaluacion-peso {
  flex: 0 0 auto;
  font-size: 12px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--text-secondary);
}

/* ── El calendario del año escolar ───────────────────────────────────────── */
/* Daniel: «el año es un elemento distractor — no se va a hacer una evaluación
   fuera del año y meses escolares». Once meses en fila, sin año en ninguna
   parte: un colegio venezolano cruza dos años calendario, y mostrarlos obliga a
   leer cuál toca en cada mes. */
.plan-evaluacion-fecha { position: relative; flex: 0 0 auto; }

.plan-evaluacion-fecha-abrir {
  padding: 2px 5px;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: transparent;
  font: inherit;
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  color: var(--text-tertiary);
  cursor: pointer;
  white-space: nowrap;
}

.plan-evaluacion-fecha-abrir:hover,
.plan-evaluacion-fecha-abrir:focus-visible {
  border-color: var(--border-subtle);
  background: var(--surface-elevated);
  color: var(--text-primary);
}

/* «sin fecha» no es un dato menor: es un dato que NO está, y se escribe en
   letras por lo mismo que la libreta escribe «Pendiente» en vez de un cero. */
.plan-evaluacion-fecha .dato-vacio { font-style: italic; }

.plan-calendario {
  position: absolute;
  top: calc(100% + 4px);
  right: 0;
  z-index: 40;
  width: 244px;
  padding: var(--space-sm);
  border: 1px solid var(--border-standard);
  border-radius: var(--radius-md);
  background: var(--surface-elevated);
  box-shadow: 0 8px 24px rgb(0 0 0 / 0.12);
}

.plan-calendario-meses {
  display: flex;
  gap: 1px;
  margin-bottom: var(--space-xs);
  padding-bottom: var(--space-xs);
  border-bottom: 1px solid var(--border-subtle);
  overflow-x: auto;
}

/* ⚠️ La versalita la pone el CSS y no el texto: escrito «SEP», un lector de
   pantalla lo deletrea «S-E-P». */
.plan-calendario-mes {
  flex: 0 0 auto;
  padding: 3px 5px;
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  font: inherit;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--text-tertiary);
  cursor: pointer;
}

.plan-calendario-mes:hover { background: var(--surface-hover); color: var(--text-primary); }
.plan-calendario-mes--activo { background: var(--brand-primary); color: #fff; font-weight: 600; }

.plan-calendario-semana,
.plan-calendario-dias {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 1px;
}

.plan-calendario-dia-nombre {
  padding: 2px 0;
  font-size: 10px;
  text-align: center;
  color: var(--text-tertiary);
}

.plan-calendario-dia {
  aspect-ratio: 1;
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  font: inherit;
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  color: var(--text-primary);
  cursor: pointer;
}

.plan-calendario-dia:hover:not(:disabled) { background: var(--surface-hover); }

/* Fuera del año escolar: se ve, pero no se toca. Esconderlo dejaría un mes con
   un agujero, que se lee como un error de la pantalla. */
.plan-calendario-dia--fuera { color: var(--text-tertiary); opacity: 0.35; cursor: not-allowed; }
.plan-calendario-dia--hoy { box-shadow: inset 0 0 0 1px var(--border-strong); }
.plan-calendario-dia--elegido { background: var(--brand-primary); color: #fff; font-weight: 600; }

.plan-calendario-pie {
  display: flex;
  justify-content: space-between;
  gap: var(--space-xs);
  margin-top: var(--space-xs);
  padding-top: var(--space-xs);
  border-top: 1px solid var(--border-subtle);
}

.plan-calendario-atajo {
  padding: 2px 5px;
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  font: inherit;
  font-size: 11px;
  color: var(--brand-primary);
  cursor: pointer;
}

.plan-calendario-atajo:hover { background: var(--surface-hover); }

.plan-evaluacion-acciones {
  flex: 0 0 auto;
  display: flex;
  gap: 2px;
}

/* Los botones de la fila aparecen al pasar por encima —son tres por fila y
   siempre visibles compiten con el nombre— pero NUNCA se esconden del teclado
   ni del táctil, donde no hay «pasar por encima». */
.plan-evaluacion-acciones { opacity: 0.45; transition: opacity var(--transition-fast); }
.plan-evaluacion:hover .plan-evaluacion-acciones,
.plan-evaluacion:focus-within .plan-evaluacion-acciones { opacity: 1; }

@media (hover: none) {
  .plan-evaluacion-acciones { opacity: 1; }
}

@media (max-width: 700px) {
  .plan-evaluacion { flex-wrap: wrap; }
  .plan-evaluacion-nombre { flex: 1 0 100%; }
}

/* ⚠️ El aviso va en ÁMBAR, no en gris: una competencia que ninguna evaluación
   mide sale en la boleta SIN nivel, y eso hay que poder verlo antes de
   emitirla. Acá el color es del dato —es un hecho del plan, no una afordancia—
   así que puede llevarlo (regla de color, MOR-135).

   Y dice la CONSECUENCIA, no el síntoma: «sin evaluaciones» no significa nada
   para quien está armando su plan; «saldrá sin nivel en la boleta» sí. */
.plan-sin-evaluar {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  margin: var(--space-sm) 0 0;
  padding: 6px var(--space-sm);
  border-radius: var(--radius-sm);
  background: var(--warning-bg);
  color: var(--warning-text);
  font-size: 12.5px;
}

.plan-sin-evaluar svg { flex: 0 0 auto; }

.plan-competencia-acciones {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-sm);
  margin-top: var(--space-sm);
}

/* ── Las evaluaciones que no miden ninguna competencia ──────────────────── */
/* No es un error —el profesor puede querer una suelta— pero cuentan para el
   promedio sin describir nada, así que la boleta las suma sin explicarlas. */
.plan-sueltas {
  margin-top: var(--space-lg);
  padding: var(--space-md);
  border: 1px solid var(--warning-border);
  border-radius: var(--radius-md);
  background: var(--warning-bg);
}

.plan-sueltas-titulo {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  margin: 0;
  font-size: 13px;
  font-weight: 600;
  color: var(--warning-text);
}

.plan-sueltas .nota-ayuda { margin: var(--space-xs) 0 0; }

/* ⚠️ En Educación Media todas las evaluaciones están «sueltas» por definición
   —no hay competencias— así que el bloque es LA lista, no una advertencia: sin
   marco ámbar y sin sangría, como cualquier otra sección del plan. */
.plan-sueltas--normal {
  margin-top: 0;
  padding: 0;
  border: 0;
  background: transparent;
}

/* Las altas: crear una competencia o una evaluación. Separadas de
   `.plan-acciones`, que es replicar a las secciones hermanas. */
.plan-altas {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm);
  margin-top: var(--space-lg);
  padding-top: var(--space-md);
  border-top: 1px solid var(--border-subtle);
}

/* ── El reparto del lapso, como un TODO ──────────────────────────────────── */
/* Daniel: «no ver el porcentaje aislado sino como un todo». La barra tiene un
   tramo por evaluación, no una franja llena: «40 %» no dice si es mucho, y al
   lado de las otras tres sí. El hueco del final es lo que queda por repartir. */
.plan-ponderacion {
  margin-bottom: var(--space-lg);
  display: grid;
  gap: 6px;
}

.plan-ponderacion-cabecera {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-sm);
  flex-wrap: wrap;
}

.plan-ponderacion-titulo {
  font-size: 12px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-secondary);
}

.plan-ponderacion-cuenta {
  font-size: 13px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--text-primary);
}

.plan-ponderacion-cuenta--excedida { color: var(--danger-text); }

.plan-ponderacion-resto {
  margin-left: 4px;
  font-weight: 400;
  color: var(--text-tertiary);
}

.plan-ponderacion-resto--ok     { color: var(--success-text); }
.plan-ponderacion-resto--exceso { color: var(--danger-text); font-weight: 600; }

.plan-ponderacion-barra {
  display: flex;
  height: 12px;
  border-radius: 999px;
  overflow: hidden;
  background: var(--surface-hover);
}

/* Un tono por tramo, rotando: los tramos vecinos tienen que distinguirse, y
   los colores NO significan nada —no hay una evaluación «buena» y otra «mala»—
   así que son variaciones del mismo azul y no una escala de semáforo. */
.plan-ponderacion-tramo {
  min-width: 2px;
  transition: width var(--transition-fast);
  border-right: 1px solid var(--surface-elevated);
}

.plan-ponderacion-tramo--0 { background: var(--brand-primary); }
.plan-ponderacion-tramo--1 { background: color-mix(in srgb, var(--brand-primary) 72%, white); }
.plan-ponderacion-tramo--2 { background: color-mix(in srgb, var(--brand-primary) 52%, white); }
.plan-ponderacion-tramo--3 { background: color-mix(in srgb, var(--brand-primary) 86%, black); }
.plan-ponderacion-tramo--4 { background: color-mix(in srgb, var(--brand-primary) 36%, white); }

/* Pasado el 100 % los tramos se dibujan proporcionales a la SUMA, así que el
   color es lo único que queda para decir que algo está mal. */
.plan-ponderacion-tramo--excedido { background: var(--danger-text); }

.plan-ponderacion-hueco {
  transition: width var(--transition-fast);
  background: repeating-linear-gradient(
    45deg, transparent, transparent 4px,
    var(--border-subtle) 4px, var(--border-subtle) 8px
  );
}

.plan-ponderacion-guardar {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  flex-wrap: wrap;
}

.plan-ponderacion-exceso {
  font-size: 12px;
  font-weight: 600;
  color: var(--danger-text);
}

/* Las flechitas del `number` roban la mitad del ancho en un campo de 4ch. */
.plan-evaluacion-peso-campo::-webkit-outer-spin-button,
.plan-evaluacion-peso-campo::-webkit-inner-spin-button { appearance: none; margin: 0; }
.plan-evaluacion-peso-campo { appearance: textfield; }

.plan-evaluacion-peso-signo {
  font-size: 11px;
  color: var(--text-tertiary);
}

/* ── El peso también se arrastra (23 de septiembre) ──────────────────────── */
/* Daniel: «en el porcentaje podemos agregar un rango en la misma línea,
   pequeño; podemos robarle un poco de espacio al texto y que la persona
   deslice en lugar de escribir».

   ⚠️ El NÚMERO sigue siendo el campo —es el que se envía, el que lee un lector
   de pantalla y el que permite poner un 37 exacto—; el rango es una segunda
   manera de moverlo. Los mantiene juntos `peso-deslizante`, que además copia el
   tope: el máximo de una evaluación es lo que QUEDA contando a las otras, no
   100, y si el rango no lo respetara arrastrando se volvería a pasar del 100 %.

   ⚠️ Un `<input type="range">` no se estila por fuera: cada motor pinta la
   CARRETERA y el PULGAR con su propio pseudoelemento. Y no se pueden juntar en
   un selector — un navegador descarta la regla ENTERA si no conoce uno de sus
   pseudos, así que webkit y firefox van separados aunque digan lo mismo. */
/* ⚠️ El pulgar es GRANDE, y salió chico. Daniel, 24 de septiembre: «me gustaba
   más el primer deslizador, el original; el punto nuevo es demasiado pequeño e
   incómodo para precisar». Tenía razón y el error fue de escala: la fila es
   compacta, así que encogí el control para que no pesara — pero un deslizador
   no se mira, se AGARRA, y un punto de 12 px es más chico que la yema con la
   que se toma. El de Bootstrap, que es el que él tenía delante, mide 16.

   ⚠️ Y `box-sizing: border-box` en los dos pulgares, escrito a mano: webkit
   suma el borde POR FUERA del ancho y firefox lo mete adentro, así que con el
   mismo `width` el pulgar mide distinto en cada navegador. */
.plan-evaluacion-peso-rango {
  /* ⚠️ Crece con la fila, entre 76 y 220 px. Es el único control que gana algo
     con el largo: 100 puntos repartidos en 76 px son 1,3 por píxel —y hay que
     clavar el 40—; en 220 son casi tres píxeles por punto. El tope existe para
     que en un monitor ancho el deslizador no se vuelva una regla de medio
     metro, que es tan incómodo como el otro extremo. */
  flex: 1 1 76px;
  min-width: 76px;
  max-width: 220px;
  height: 18px;
  margin: 0;
  padding: 0;
  appearance: none;
  background: transparent;
  cursor: ew-resize;
}

.plan-evaluacion-peso-rango::-webkit-slider-runnable-track {
  height: 6px;
  border-radius: 999px;
  background: var(--border-standard);
}

.plan-evaluacion-peso-rango::-moz-range-track {
  height: 6px;
  border-radius: 999px;
  background: var(--border-standard);
}

/* Firefox es el único que rellena lo recorrido; en los demás la carretera va
   pareja y el pulgar dice dónde está. */
.plan-evaluacion-peso-rango::-moz-range-progress {
  height: 6px;
  border-radius: 999px;
  background: var(--brand-primary);
}

.plan-evaluacion-peso-rango::-webkit-slider-thumb {
  box-sizing: border-box;
  width: 16px;
  height: 16px;
  /* Centrarlo en la carretera: la mitad de lo que sobresale, hacia arriba. */
  margin-top: -5px;
  border: 2px solid var(--surface-elevated);
  border-radius: 50%;
  background: var(--brand-primary);
  box-shadow: 0 1px 2px rgb(0 0 0 / 0.2);
  appearance: none;
}

.plan-evaluacion-peso-rango::-moz-range-thumb {
  box-sizing: border-box;
  width: 16px;
  height: 16px;
  border: 2px solid var(--surface-elevated);
  border-radius: 50%;
  background: var(--brand-primary);
  box-shadow: 0 1px 2px rgb(0 0 0 / 0.2);
}

.plan-evaluacion-peso-rango:focus-visible {
  outline: 2px solid var(--brand-primary);
  outline-offset: 2px;
}

/* ⚠️ En el teléfono NO va, y el número se queda. Sesenta píxeles para repartir
   de 1 a 100 son casi dos puntos por píxel: con el dedo encima, poner un 40
   exacto es imposible — y el campo, que sí puede, está justo al lado. */
@media (max-width: 640px) {
  .plan-evaluacion-peso-rango { display: none; }

  /* ⚠️ Y sin deslizador que lo absorba, el peso deja de crecer: creciendo, el
     número quedaría pegado a la izquierda de una celda vacía y ancha, con la
     fecha y la pastilla empujadas al borde. */
  .plan-evaluacion-campo--peso { flex: 0 0 auto; }
}

/* La dirección lee el plan pero no lo escribe: ve los mismos números, sin las
   cajas. */
.plan-evaluacion-peso-fijo,
.plan-evaluacion-fecha-fija {
  flex: 0 0 auto;
  font-size: 12px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--text-secondary);
}

.plan-medida-peso {
  flex: 0 0 auto;
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  color: var(--text-tertiary);
}

/* ⚠️ El `<details>` de «Cambiar cuáles la miden» se retiró el 9 de septiembre.
   El enganche vive ahora en la FILA de cada evaluación, con sus chips: aquel
   preguntaba al revés —de la competencia hacia las evaluaciones— cuando lo
   que uno mira en el plan es la evaluación. Ver `.plan-evaluacion-chips`. */

/* ---- Qué evalúa una evaluación (MOR-136) ---- */
/* El enganche entre el plan y la nota. Se marcan COMPETENCIAS; los indicadores
   van debajo como contexto —ayudan a reconocer cuál es cuál— y no se guardan. */
.que-evalua-lista {
  list-style: none;
  margin: var(--space-xs) 0 0;
  padding: 0;
  display: grid;
  gap: var(--space-xs);
}

.que-evalua-item {
  border: 1px solid var(--border-standard);
  border-radius: var(--radius-sm);
  padding: var(--space-sm) var(--space-md);
  background: var(--surface-elevated);
  transition: border-color var(--transition-fast), background var(--transition-fast);
}

/* Marcada se NOTA, y no solo por la casilla: en una lista de seis competencias
   de dos renglones cada una, un tilde de 13px no se encuentra de un vistazo. */
.que-evalua-item:has(input:checked) {
  border-color: var(--brand-primary);
  background: var(--surface-hover);
}

.que-evalua-competencia {
  display: flex;
  align-items: flex-start;
  gap: var(--space-xs);
  cursor: pointer;
  /* 44px de objetivo táctil: esto se marca desde un teléfono. */
  min-height: 44px;
  align-content: center;
  flex-wrap: wrap;
}

.que-evalua-competencia input {
  flex: 0 0 auto;
  margin-top: 3px;
  width: 18px;
  height: 18px;
  accent-color: var(--brand-primary);
}

.que-evalua-competencia span { flex: 1 1 0; min-width: 0; }

/* ⚠️ Los indicadores SÍ se marcan desde MOR-136 (segunda mitad): se elige cuál
   mide esta evaluación. Lo que sigue siendo cierto es que no se CALIFICAN
   aparte — su nivel en la boleta es el de su competencia. Van sangrados bajo
   ella porque describen; la casilla dice cuál se mide, no cuánto vale. */
.que-evalua-indicadores {
  margin: var(--space-micro) 0 0 calc(18px + var(--space-xs));
  padding-left: var(--space-md);
  color: var(--text-secondary);
  font-size: 13px;
}

.que-evalua-indicadores li { margin-top: 2px; }

/* Lo que la evaluación ya evalúa, en su ficha. */
.evalua-chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs);
}

.evalua-chip {
  display: inline-flex;
  align-items: center;
  padding: 4px var(--space-sm);
  border: 1px solid var(--border-standard);
  border-radius: var(--radius-sm);
  background: var(--surface-hover);
  font-size: 13px;
  color: var(--text-secondary);
}

/* ---- La rúbrica del colegio (MOR-136) ---- */
/* Una tabla de cinco filas que se editan juntas: es una ESCALERA, y sus
   peldaños solo tienen sentido unos respecto de otros. */
.tabla-rubrica .columna-codigo { width: 7rem; }
.tabla-rubrica .columna-desde  { width: 6rem; }
.tabla-rubrica .columna-cubre  { width: 8rem; }

.tabla-rubrica td { vertical-align: middle; }

/* El código es corto y se lee como una sigla: centrado y en mayúscula. */
.campo-rubrica-codigo {
  text-transform: uppercase;
  text-align: center;
  letter-spacing: 0.04em;
  font-weight: 600;
}

.campo-rubrica-valor { text-align: center; }

/* «Cubre» es DATO derivado, no un campo: se pinta como tal — sin caja, en el
   tono secundario y con cifras tabulares para que las bandas se lean como una
   columna de números y no como cinco frases. */
.rubrica-cubre {
  color: var(--text-secondary);
  font-size: 13px;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* Quitar es una CASILLA disfrazada de botón, y a propósito: el guardado es uno
   solo, así que una fila que desapareciera en el acto no se podría recuperar
   con el botón de atrás. Marcada, la fila se atenúa y se tacha — se ve que va
   a irse, y se puede desmarcar. */
.rubrica-quitar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 36px;
  min-height: 36px;
  border-radius: var(--radius-sm);
  color: var(--text-tertiary);
  cursor: pointer;
  transition: all var(--transition-fast);
}

.rubrica-quitar:hover { color: var(--eliminar-primary); background: var(--surface-hover); }

.rubrica-quitar-check { position: absolute; opacity: 0; width: 0; height: 0; }

.rubrica-quitar:has(.rubrica-quitar-check:checked) {
  color: var(--eliminar-primary);
}

.rubrica-fila:has(.rubrica-quitar-check:checked) td:not(.columna-acciones) {
  opacity: 0.4;
  text-decoration: line-through;
}

.rubrica-quitar:focus-within {
  outline: 2px solid var(--brand-primary);
  outline-offset: 2px;
}

/* El aviso de que la rúbrica es una propuesta y no está guardada. */
.rubrica-propuesta {
  display: flex;
  align-items: flex-start;
  gap: var(--space-xs);
  padding: var(--space-sm) var(--space-md);
  border-left: 3px solid var(--brand-primary);
  background: var(--surface-hover);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  margin-bottom: var(--space-md);
}

.rubrica-propuesta .icono { flex: 0 0 auto; margin-top: 2px; color: var(--brand-primary); }

@media (max-width: 700px) {
  /* En el teléfono la etiqueta pasa a ser un renglón propio: reservarle 9.5rem
     al lado dejaba a las pastillas en una columna de dos dedos de ancho. */
  .pastillas { gap: var(--space-micro); }
  .pastillas-etiqueta { min-width: 100%; }
}
}

/* ═══════════════════════════════════════════════════════════════════════════
   QUÉ NIVELES DICTA EL COLEGIO (MOR-153)

   Tres casillas que reemplazan catorce formularios. Se dibujan grandes a
   propósito: es la decisión más importante de la primera hora, y de ella salen
   los nombres de los grados, su orden y la escala de sus boletines.
   ═══════════════════════════════════════════════════════════════════════════ */

.niveles-opciones {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: var(--space-md);
  margin-bottom: var(--space-xl);
}

.nivel-opcion {
  display: flex;
  align-items: flex-start;
  gap: var(--space-sm);
  padding: var(--space-md);
  border: 1px solid var(--border-standard);
  border-radius: var(--radius-md);
  cursor: pointer;
  transition: border-color var(--transition-fast), background var(--transition-fast);
}

.nivel-opcion:hover { border-color: var(--brand-primary); }

/* ⚠️ `:has()` y no una clase que ponga JavaScript: la casilla marcada ES el
   estado, y duplicarlo en una clase deja dos verdades que se desincronizan. */
.nivel-opcion:has(input:checked) {
  border-color: var(--brand-primary);
  background: var(--brand-subtle);
}

.nivel-opcion:has(input:focus-visible) {
  outline: 2px solid var(--brand-primary);
  outline-offset: 2px;
}

.nivel-opcion input { margin-top: 3px; flex: 0 0 auto; }

.nivel-cuerpo { display: flex; flex-direction: column; gap: var(--space-micro); min-width: 0; }

.nivel-nombre { font-weight: 600; font-size: 15px; color: var(--text-primary); }
.nivel-detalle { font-size: 13px; color: var(--text-secondary); }

/* Los nombres de los grados se muestran ANTES de crear nada: un director tiene
   que poder ver qué va a aparecer, no descubrirlo después. */
.nivel-grados {
  font-size: 12px;
  color: var(--text-tertiary);
  line-height: 1.5;
}

.nivel-puesto {
  display: inline-flex;
  align-items: center;
  gap: var(--space-micro);
  font-size: 12px;
  color: var(--success-text);
}

/* ---- La escala de primaria ---- */

.escala-de-primaria {
  border-top: 1px solid var(--border-subtle);
  padding-top: var(--space-lg);
  margin-bottom: var(--space-lg);
}

.escala-opciones { display: flex; flex-wrap: wrap; gap: var(--space-md); }

.escala-opcion {
  display: flex;
  align-items: flex-start;
  gap: var(--space-xs);
  cursor: pointer;
}

.escala-opcion > span { display: flex; flex-direction: column; }
.escala-opcion .nota-ayuda { margin: 0; }

/* ---- Los grados que ya tiene ---- */

.niveles-titulo-grados {
  font-size: 15px;
  margin: var(--space-xl) 0 var(--space-sm);
  color: var(--text-secondary);
}

.niveles-lista { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: var(--space-xs); }

.niveles-item {
  display: flex;
  align-items: baseline;
  gap: var(--space-xs);
  padding: var(--space-xs) var(--space-sm);
  background: var(--surface-input);
  border-radius: var(--radius-sm);
  font-size: 14px;
}

.niveles-item-nombre { font-weight: 500; }
.niveles-item-nivel { font-size: 12px; color: var(--text-tertiary); }

/* Un grado con nombre fuera del catálogo no se rechaza —hay colegios con datos
   cargados antes de esto— pero sí se señala: es lo que el importador no va a
   poder emparejar contra una planilla. */
.niveles-item-aviso {
  display: inline-flex;
  align-items: center;
  gap: var(--space-micro);
  font-size: 12px;
  color: var(--danger-text);
}

/* ═══════════════════════════════════════════════════════════════════════════
   EL MAPA DE LA PUESTA EN MARCHA (MOR-154)

   No es un wizard: es una lista de pasos en el panel, donde cada uno enlaza a
   la pantalla que ya existe. Ver `_puesta_en_marcha.html.haml`.

   ⚠️ El color acá lo lleva el ESTADO, que es un dato —«esto ya está»— y nunca
   el botón. Un paso listo se tiñe; su enlace «Cambiar» no.
   ═══════════════════════════════════════════════════════════════════════════ */

.puesta-en-marcha { margin-bottom: var(--space-xl); }

.pasos {
  list-style: none;
  margin: var(--space-lg) 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
}

.paso {
  display: grid;
  grid-template-columns: 2rem 1fr auto;
  gap: var(--space-sm) var(--space-md);
  padding: var(--space-md) 0;
  border-top: 1px solid var(--border-subtle);
  align-items: start;
}

.paso:first-child { border-top: none; }

/* La marca: número mientras falta, visto cuando está. */
.paso-marca {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 1.6rem;
  color: var(--text-tertiary);
}

.paso-numero {
  font-family: var(--font-serif);
  font-size: 15px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.paso--listo .paso-marca { color: var(--success-text); }

.paso-cuerpo { display: flex; flex-direction: column; gap: var(--space-xs); min-width: 0; }

.paso-titulo {
  font-size: 15px;
  font-weight: 600;
  margin: 0;
  color: var(--text-primary);
}

/* Un paso terminado se colapsa a una línea con su resultado. NO se atenúa hasta
   volverse ilegible: sigue siendo información que alguien va a querer leer para
   verificar qué quedó configurado. */
.paso--listo .paso-titulo { font-weight: 500; color: var(--text-secondary); }

.paso-resultado {
  margin: 0;
  font-size: 14px;
  color: var(--text-primary);
}

/* La explicación del paso, como un aviso (MOR-203).
   ────────────────────────────────────────────────
   Daniel, en dos vueltas: «esos textos deberían ser más llamativos» y «aún las
   descripciones están más cortas que el ancho total; deberían ser mensajes tipo
   warning o info».

   ⚠️ El tono es AZUL y no ámbar, y eso no contradice el pedido: la regla de
   color (MOR-135) reserva el ámbar, el verde y el rojo para el DATO —un ámbar
   acá compite con «196 alumnos sin representante», que sí es una anomalía—. La
   familia fría queda libre para el cromo, y un aviso informativo es cromo: no
   dice que algo esté mal, dice qué es este paso.

   ⚠️ Y sin `max-width`. Lo tuvo en 62ch y después en 92ch, y las dos veces
   Daniel vio el texto cortado antes del borde. La medida cómoda de lectura está
   pensada para un párrafo largo; acá son dos renglones dentro de una caja que
   YA los acota con su propio relleno. */
.paso-explicacion {
  margin: 0;
  padding: var(--space-sm) var(--space-md);
  border-left: 3px solid var(--brand-primary);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  background: color-mix(in srgb, var(--brand-primary) 6%, transparent);
  font-size: 15px;
  line-height: 1.6;
  color: var(--text-primary);
  text-wrap: pretty;
}

/* El bloqueo se DICE, con el nombre de lo que falta, y el botón de al lado
   lleva ahí. Nunca un control apagado. */
.paso-bloqueo {
  margin: 0;
  font-size: 13px;
  color: var(--text-tertiary);
}

.paso-acciones {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs);
  margin-top: var(--space-micro);
}

.paso-acciones form { margin: 0; }

.paso-rehacer { display: flex; align-items: center; }

.paso-enlace-tenue {
  font-size: 13px;
  color: var(--text-tertiary);
  text-decoration: none;
  padding: var(--space-micro) var(--space-xs);
  border-radius: var(--radius-sm);
}

.paso-enlace-tenue:hover { color: var(--brand-primary); text-decoration: underline; }

.paso--omitido .paso-titulo,
.paso--omitido .paso-resultado { color: var(--text-tertiary); }

@media (max-width: 640px) {
  /* En el teléfono el enlace de «Cambiar» pasa abajo: tres columnas en 375px
     dejan el título en dos letras por línea. */
  .paso { grid-template-columns: 2rem 1fr; }
  .paso-rehacer { grid-column: 2; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   QUIÉN NO HA CARGADO SUS NOTAS (MOR-155)

   El único pendiente del panel con fecha de vencimiento: el lapso cierra y los
   boletines salen incompletos. Por eso va primero de los tres.
   ═══════════════════════════════════════════════════════════════════════════ */

.atrasados { display: flex; flex-direction: column; gap: var(--space-md); }

.atrasado {
  border: 1px solid var(--border-standard);
  border-radius: var(--radius-md);
  overflow: hidden;
}

.atrasado-cabecera {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-sm);
  padding: var(--space-md);
  background: var(--surface-input);
}

.atrasado-nombre { font-weight: 600; text-decoration: none; color: var(--text-primary); }
.atrasado-nombre:hover { color: var(--brand-primary); }

.atrasado-resumen {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-xs);
  font-size: 13px;
  color: var(--text-secondary);
}

.atrasado-punto { color: var(--border-emphasis); }

/* ⚠️ El rojo acá describe un DATO —cuántos niños no tienen nota—, no un
   control. La regla del proyecto prohíbe teñir el botón, no señalar la falta. */
.atrasado-dato--alerta { color: var(--danger-text); font-weight: 500; }

.atrasado-correo {
  display: inline-flex;
  align-items: center;
  gap: var(--space-micro);
  color: var(--brand-primary);
  text-decoration: none;
}
.atrasado-correo:hover { text-decoration: underline; }

.atrasado-lista { list-style: none; margin: 0; padding: 0; }

/* Una fila por evaluación, no una tabla: son dos o tres por profesor y una
   tabla de tres filas con cinco encabezados pesa más de lo que informa. */
.atrasado-fila {
  display: grid;
  grid-template-columns: 5rem minmax(9rem, 1fr) 2fr auto;
  gap: var(--space-sm);
  align-items: baseline;
  padding: var(--space-sm) var(--space-md);
  border-top: 1px solid var(--border-subtle);
  font-size: 14px;
}

.atrasado-fecha { color: var(--text-tertiary); font-size: 13px; white-space: nowrap; }

.atrasado-que { display: flex; flex-direction: column; }
.atrasado-materia { font-size: 12px; color: var(--text-tertiary); }

.atrasado-descripcion { color: var(--text-secondary); min-width: 0; }

.atrasado-avance {
  font-variant-numeric: tabular-nums;
  font-size: 13px;
  color: var(--text-secondary);
  white-space: nowrap;
  text-align: right;
}

@media (max-width: 640px) {
  /* En el teléfono la descripción pasa a su propia línea: cuatro columnas en
     375px dejan «Examen del primer corte» en una palabra por renglón. */
  .atrasado-fila { grid-template-columns: 5rem 1fr auto; }
  .atrasado-descripcion { grid-column: 1 / -1; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   LA FRANJA DEL RECORRIDO (MOR-154)

   Una línea que dice dónde está y cómo vuelve al plan. Ver
   `shared/_franja_de_recorrido`.

   ⚠️ NO es `position: fixed`, a propósito. En una laptop de 13" ya hay barra de
   contexto y a veces avisos de suscripción; una cuarta banda fija son doscientos
   píxeles de cromo antes del primer dato del colegio.
   ═══════════════════════════════════════════════════════════════════════════ */

.franja {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-sm);
  /* 44px de alto mínimo: los dos enlaces son objetivos táctiles. */
  min-height: 44px;
  padding: var(--space-xs) var(--space-md);
  margin-bottom: var(--space-lg);
  background: var(--brand-subtle);
  border: 1px solid var(--brand-primary);
  border-radius: var(--radius-md);
  font-size: 13px;
}

.franja-volver,
.franja-seguir {
  display: inline-flex;
  align-items: center;
  gap: var(--space-micro);
  color: var(--brand-primary);
  text-decoration: none;
  font-weight: 500;
  padding: var(--space-micro) var(--space-xs);
  border-radius: var(--radius-sm);
  white-space: nowrap;
}

.franja-volver:hover,
.franja-seguir:hover { text-decoration: underline; }

.franja-volver:focus-visible,
.franja-seguir:focus-visible,
.franja-cerrar:focus-visible {
  outline: 2px solid var(--brand-primary);
  outline-offset: 2px;
}

.franja-centro {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-micro);
  flex: 1 1 auto;
  min-width: 0;
  color: var(--text-secondary);
}

.franja-titulo { font-weight: 600; color: var(--text-primary); }
.franja-avance { font-variant-numeric: tabular-nums; }
.franja-punto { color: var(--border-emphasis); }
.franja-paso { color: var(--text-primary); }

/* Cuando se desvió a otra pantalla, la franja lo dice en vez de fingir. */
.franja-paso--lejos { color: var(--text-secondary); font-style: italic; }

.franja-acciones {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  margin-left: auto;
}

.franja-cerrar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* 32px porque va pegado a «seguir»: más chico se falla y se sale del
     recorrido queriendo avanzar. */
  width: 32px;
  height: 32px;
  color: var(--text-tertiary);
  border-radius: var(--radius-sm);
}

.franja-cerrar:hover { color: var(--text-primary); background: var(--surface-hover); }

@media (max-width: 640px) {
  /* En el teléfono el centro pasa a su propia línea y las acciones quedan
     abajo: los tres bloques en 375px dejan «va por materias» ilegible. */
  .franja { gap: var(--space-xs); }
  .franja-centro { flex: 1 0 100%; order: 3; }
  .franja-acciones { margin-left: auto; }
}

/* El enlace al registro, al pie de la pantalla de entrada (MOR-158). */
.acceso-registro {
  margin-top: var(--space-md);
  font-size: 14px;
  color: var(--text-secondary);
  text-align: center;
}

.acceso-registro a { color: var(--brand-primary); font-weight: 500; }

/* Las dos pantallas del camino de entrada: registro y alta del colegio. */
.registro { max-width: 46rem; margin: 0 auto; }
.registro-form { display: flex; flex-direction: column; gap: var(--space-md); }

/* El campo que aparece cuando el municipio o la parroquia no están en el
   catálogo (MOR-159). Va pegado a su desplegable, sin etiqueta propia: la
   etiqueta ya la puso el select de arriba. */
.ubicacion-libre { margin-top: var(--space-xs); }

/* La ficha de una sección. Salieron de estilos en línea que estaban escritos
   cinco veces en la misma vista (revisión rails-style). */
.seccion-conteo {
  color: var(--text-secondary);
  font-size: 14px;
  margin-bottom: var(--space-lg);
}

.seccion-soy-yo {
  margin-left: var(--space-micro);
  font-size: 12px;
  color: var(--brand-primary);
}

.seccion-separador {
  margin: var(--space-xl) 0;
  border: 0;
  border-top: 1px solid var(--border-standard);
}

.seccion-subtitulo { font-size: 16px; margin-bottom: var(--space-lg); }

/* El nombre de quien tiene la sesión, en la barra: hereda el color de la barra
   en vez de declararlo. */
.user-info { color: inherit; }

/* ═══════════════════════════════════════════════════════════════════════════
   LA AYUDA BAJO DEMANDA (MOR-161)

   Un «?» junto a la etiqueta; el texto aparece al tocarlo y se va solo. Ver
   `PistasHelper` para el porqué.

   ⚠️ Usa `popover` nativo: el navegador se encarga de cerrar al tocar afuera,
   de `esc`, y de dibujarlo en la CAPA SUPERIOR — o sea que ningún `overflow`
   del formulario lo recorta, que es lo que rompe a un tooltip casero.
   ═══════════════════════════════════════════════════════════════════════════ */

.form-etiqueta {
  display: flex;
  align-items: center;
  gap: var(--space-micro);
  margin-bottom: var(--space-micro);
}

.form-etiqueta .form-label { margin-bottom: 0; }

/* ⚠️ El ancla es de CADA pista y viene del marcado (`--ancla`, MOR-196).
   Con `anchor-name: --pista` para todas, el navegador resolvía al ÚLTIMO
   «?» del documento y cada popover salía junto a ése. */
.pista { display: inline-flex; anchor-name: var(--ancla); }

.pista-boton {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* 24px de caja para un icono de 14: el objetivo táctil no puede ser el
     dibujo. Va con `padding` y no con `width` para no deformar la línea. */
  width: 24px;
  height: 24px;
  padding: 0;
  border: 0;
  background: none;
  color: var(--text-tertiary);
  cursor: pointer;
  border-radius: 50%;
}

.pista-boton:hover { color: var(--brand-primary); background: var(--surface-hover); }

.pista-boton:focus-visible {
  outline: 2px solid var(--brand-primary);
  outline-offset: 1px;
}

.pista-texto {
  max-width: 26rem;
  margin: 0;
  padding: var(--space-sm) var(--space-md);
  border: 1px solid var(--border-standard);
  border-radius: var(--radius-md);
  background: var(--surface-elevated);
  box-shadow: 0 8px 24px rgba(10, 16, 26, 0.16);
  font-size: 13px;
  line-height: 1.55;
  color: var(--text-secondary);
}

/* Anclado al «?» donde el navegador sabe hacerlo. Donde no —Firefox todavía—,
   el popover se centra solo en la pantalla: se lee igual y se cierra igual. */
@supports (position-anchor: --pista) {
  .pista-texto {
    position-anchor: var(--ancla);
    position-area: bottom span-right;
    margin-top: var(--space-micro);
    position-try-fallbacks: flip-block, flip-inline;
  }
}

/* Un dato secundario: el teléfono debajo de un nombre, «Sin asignar», el
   formato de una columna (MOR-161).
   ⚠️ NO es `.nota-ayuda`. Se veían igual y por eso se confundían, pero una dice
   «esto te explica algo» y la otra es un dato del colegio. Mezclarlas hacía que
   el buscador de ayuda del proyecto —y quien lee el código— no supiera cuál es
   cuál. */
.dato-secundario {
  font-size: 13px;
  color: var(--text-tertiary);
}

/* La descripción de una opción, dentro del propio control (MOR-161).
   ⚠️ NO es ayuda: es parte de la opción. Esconderla detrás de un «?» obligaría
   a abrir dos tarjetas para poder elegir entre dos radios. */
.escala-opcion-detalle {
  font-size: 13px;
  color: var(--text-tertiary);
}

/* ---- «Por qué funciona así», plegado (MOR-161) ---- */
/* El hermano de `.pista` para el texto que no cuelga de un campo. Mismo
   argumento que los grupos del menú: `<details>` nativo, sin JavaScript. */

.mas-detalle {
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  margin-top: var(--space-md);
}

.mas-detalle-resumen {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  /* 44px: es un control que se toca. */
  min-height: 44px;
  padding: 0 var(--space-md);
  cursor: pointer;
  list-style: none;
  font-size: 14px;
  font-weight: 500;
  color: var(--text-secondary);
  user-select: none;
}

.mas-detalle-resumen::-webkit-details-marker { display: none; }

/* El triángulo lo dibujamos para que gire: el nativo no se anima igual entre
   navegadores. Mismo patrón que `.sidebar-grupo`. */
.mas-detalle-resumen::before {
  content: "";
  width: 0;
  height: 0;
  border-left: 5px solid currentColor;
  border-top: 4px solid transparent;
  border-bottom: 4px solid transparent;
  transition: transform var(--transition-fast);
}

.mas-detalle[open] > .mas-detalle-resumen::before { transform: rotate(90deg); }
.mas-detalle-resumen:hover { color: var(--brand-primary); }

.mas-detalle-resumen:focus-visible {
  outline: 2px solid var(--brand-primary);
  outline-offset: -2px;
  border-radius: var(--radius-md);
}

.mas-detalle-cuerpo {
  padding: 0 var(--space-md) var(--space-md);
  font-size: 14px;
  line-height: 1.6;
  color: var(--text-secondary);
  max-width: 68ch;
}

.mas-detalle-cuerpo p { margin: 0 0 var(--space-sm); }
.mas-detalle-cuerpo p:last-child { margin-bottom: 0; }

/* El control de contexto de la barra (MOR-223).
   ────────────────────────────────────────────
   Reemplaza a `.contexto-anio*` y `.contexto-nivel*`, que eran dos `<select>`
   con sus etiquetas, un botón índigo y dos chips: cinco controles diciendo la
   misma frase. Daniel: «la verdad el filtro no me encanta, creo que la barra en
   general se ve desordenada».

   ⚠️ Es un `<details>` NATIVO. Todo lo de aquí abajo es apariencia: si el CSS
   o el JavaScript no cargan, el panel sigue abriendo y los formularios de
   adentro siguen enviando. */
.contexto {
  position: relative;
  flex-shrink: 0;
}

/* La pastilla dice el estado completo en una frase —«2025-2026 · Primaria»— y
   por eso no lleva etiquetas al lado. */
/* ⚠️ La pastilla se RESALTA, y es una decisión de producto.
   
   Daniel: *«definitivamente se queda el select en el navbar, es el pivot
   principal. Se queda. De hecho vamos a resaltarlo un poco… una sutileza pero
   que de entrada la persona perciba dónde está parado y que todo gira en torno
   a esos elementos, Año Escolar y Nivel»*.
   
   Lo que la resalta es el FONDO, no el color del texto: un tinte del azul de
   marca la separa de la barra sin competir con el nombre del colegio, que está
   a su izquierda en la serif del sistema. Con solo el borde se leía como un
   campo más de un formulario. */
.contexto-pastilla {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  min-height: 34px;
  padding: 0 var(--space-md);
  border: 1px solid var(--brand-primary);
  border-radius: 999px;
  background: color-mix(in srgb, var(--brand-primary) 8%, var(--surface-elevated));
  color: var(--brand-primary);
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
  transition: all var(--transition-fast);
  /* El triángulo que Safari y Firefox le ponen a un `<summary>`: acá el chevrón
     propio es la señal, y dos marcadores compiten. */
  list-style: none;
}

.contexto-pastilla::-webkit-details-marker { display: none; }
.contexto-pastilla::marker { content: ""; }

.contexto-pastilla:hover {
  border-color: var(--brand-primary);
  background: color-mix(in srgb, var(--brand-primary) 16%, var(--surface-elevated));
}

.contexto-pastilla:focus-visible {
  outline: 2px solid var(--brand-primary);
  outline-offset: 2px;
}

.contexto[open] .contexto-pastilla {
  border-color: var(--brand-primary);
  background: color-mix(in srgb, var(--brand-primary) 16%, var(--surface-elevated));
}

.contexto[open] .contexto-pastilla-flecha { transform: rotate(180deg); }

/* ⚠️ Con el panel ABIERTO, el tooltip de la pastilla se apaga.
   
   Los dos salen hacia abajo desde el mismo punto, así que el panel tapaba al
   tooltip y solo asomaba una franja negra cortada por su borde — que fue lo que
   Daniel vio. La salida no es darle más alto al panel: es que no compitan.
   
   Y no se pierde nada: el panel abierto ya dice «SOBRE QUÉ ESTÁS TRABAJANDO»
   en su primer renglón, que es exactamente lo que decía el tooltip. */
.contexto[open] .contexto-pastilla::after,
.contexto[open] .contexto-pastilla::before {
  display: none;
}

.contexto-pastilla-flecha {
  color: var(--brand-primary);
  opacity: 0.6;
  transition: transform var(--transition-fast);
}

.contexto-pastilla-separador { color: var(--text-tertiary); margin: 0 2px; }


/* La nota de «acá el nivel no aplica» viaja CON la pastilla y no como un chip
   suelto en la barra: es una nota sobre este control, no otro elemento de la
   fila. El texto completo vive en el panel y en el tooltip. */
.contexto-pastilla-nota {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  color: var(--warning-text);
  font-size: 12px;
  font-weight: 500;
}

/* En el teléfono queda el icono: el texto entero empujaría el buscador fuera
   de la pantalla. El tooltip aparece también con `:focus-visible` (MOR-173). */
@media (max-width: 900px) {
  .contexto-pastilla-nota-texto { display: none; }
}

/* ── El panel ─────────────────────────────────────────────────────────────── */

/* ⚠️ Cerrado, el panel se ESCONDE explícitamente. Tercera vez que esta trampa
   aparece en el proyecto.

   El navegador oculta el contenido de un `<details>` cerrado, pero ese
   ocultamiento lo pierde cualquier regla que le dé al hijo un `display` propio
   —o, como acá, `position: absolute`, que fuerza un `display` calculado—. El
   panel seguía midiendo 549 px en un teléfono de 390 y corría la página entera
   de lado, sin verse.
   
   Ya pasó con `.sidebar-section .sidebar-nav { display: flex }` en el menú, y
   antes con `.nav-institutional`. El síntoma es siempre el mismo: un desborde
   horizontal idéntico en TODAS las pantallas, que es la firma de algo que vive
   en la barra o en el menú. */
.contexto:not([open]) .contexto-panel { display: none; }

.contexto-panel {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  z-index: 60;
  min-width: 20rem;
  max-width: min(24rem, calc(100vw - 2rem));
  padding: var(--space-md);
  border: 1px solid var(--border-standard);
  border-radius: var(--radius-md);
  background: var(--surface-elevated);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
  /* Que el panel scrollee y no la barra, cuando un colegio tiene seis años
     escolares cargados y la ventana es baja. */
  max-height: min(30rem, calc(100vh - 5rem));
  overflow-y: auto;
}

.contexto-panel-titulo {
  margin: 0 0 var(--space-sm);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-tertiary);
}

.contexto-grupo + .contexto-grupo {
  margin-top: var(--space-md);
  padding-top: var(--space-md);
  border-top: 1px solid var(--border-subtle);
}

.contexto-grupo-titulo {
  margin: 0 0 var(--space-xs);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-tertiary);
}

/* Cada opción es un formulario propio, así que el `<form>` no puede meter
   espacio ni romper la columna. */
.contexto-opcion-form { display: contents; }

/* Un año por renglón, con su situación al lado: es justo lo que hace falta al
   elegir — quien pone «2024-2025» tiene que ver ahí mismo que está cerrado. */
.contexto-opcion {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-sm);
  width: 100%;
  min-height: 40px;
  padding: var(--space-xs) var(--space-sm);
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: transparent;
  font-family: var(--font-sans);
  font-size: 14px;
  text-align: left;
  color: var(--text-primary);
  cursor: pointer;
  transition: background var(--transition-fast), border-color var(--transition-fast);
}

.contexto-opcion:hover { background: var(--surface-hover); }

.contexto-opcion:focus-visible {
  outline: 2px solid var(--brand-primary);
  outline-offset: -2px;
}

/* La elegida se PINTA. Un filtro puesto que no se ve es la manera de que
   alguien cargue veinte alumnos en el año equivocado y culpe al sistema. */
.contexto-opcion.elegida {
  background: var(--brand-primary);
  border-color: var(--brand-primary);
  color: #fff;
}

.contexto-opcion.elegida .contexto-opcion-nota { color: rgba(255, 255, 255, 0.75); }

.contexto-opcion-nombre { font-weight: 600; }

.contexto-opcion-nota {
  font-size: 12px;
  color: var(--text-tertiary);
  white-space: nowrap;
}

/* El nivel es un MODO DE TRABAJO —«vamos a trabajar Primaria»— y son tres
   opciones: en fila, como pastillas, no una lista de renglones. */
.contexto-niveles {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs);
}

.contexto-nivel-boton {
  min-height: 36px;
  padding: 0 var(--space-md);
  border: 1px solid var(--border-standard);
  border-radius: var(--radius-sm);
  background: var(--surface-elevated);
  font-family: var(--font-sans);
  font-size: 13px;
  font-weight: 500;
  color: var(--text-secondary);
  cursor: pointer;
  white-space: nowrap;
  transition: all var(--transition-fast);
}

.contexto-nivel-boton:hover {
  border-color: var(--brand-primary);
  color: var(--brand-primary);
}

.contexto-nivel-boton:focus-visible {
  outline: 2px solid var(--brand-primary);
  outline-offset: 2px;
}

.contexto-nivel-boton.elegida,
.contexto-nivel-boton.elegida:hover {
  background: var(--brand-primary);
  border-color: var(--brand-primary);
  color: #fff;
}

/* El motivo completo de «acá no aplica». En la barra se recortaba a «los
   departamen», que es peor que no decir nada. */
.contexto-panel-nota {
  display: flex;
  align-items: flex-start;
  gap: var(--space-xs);
  margin: var(--space-sm) 0 0;
  font-size: 13px;
  line-height: 1.5;
  color: var(--text-secondary);
}

.contexto-panel-nota svg { flex: 0 0 auto; margin-top: 2px; color: var(--warning-text); }

/* El atajo a los años escolares: era un botón índigo suelto en la barra y acá
   es un renglón del panel que ya habla del año. */
.contexto-panel-enlace {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  margin-top: var(--space-md);
  padding-top: var(--space-md);
  border-top: 1px solid var(--border-subtle);
  font-size: 13px;
  color: var(--text-secondary);
  text-decoration: none;
}

.contexto-panel-enlace:hover { color: var(--brand-primary); }

/* Sin nada que elegir —un profesor, un alumno— el contexto es TEXTO. Darle
   forma de control a algo que no se puede tocar es una promesa que la pantalla
   no cumple. */
.contexto-fijo {
  display: flex;
  align-items: baseline;
  gap: var(--space-xs);
  flex-shrink: 0;
  font-size: 14px;
}

/* ⚠️ El MISMO vocabulario en los dos caminos —la pastilla y el texto fijo—
   porque es el mismo dato: cuál año, en qué situación, o que no hay ninguno.
   Con dos juegos de clases, retocar uno dejaba al otro atrás. */
/* La inicial del usuario solo aparece en el teléfono; en escritorio manda el
   nombre. Sin esta regla se veían las DOS —«LUISAL»— porque el ancla de este
   bloque se había renombrado y el reemplazo no encontró dónde escribir. */
.user-info-inicial { display: none; }

.contexto-valor-anio { font-weight: 600; color: var(--brand-primary); }
.contexto-valor-situacion { color: var(--text-tertiary); font-size: 13px; }

/* Dentro de la pastilla el año ya hereda su color: repetirlo lo pinta dos veces
   y el `currentColor` del chevrón deja de coincidir. */
.contexto-pastilla .contexto-valor-anio { color: inherit; }

.contexto-valor-vacio {
  color: var(--warning-text);
  text-decoration: none;
  border-bottom: 1px dotted currentColor;
}

/* El control de contexto en un teléfono.
   ─────────────────────────────────────
   ⚠️ Estas reglas viven ACÁ y no en el `@media` grande de la barra, que está
   cinco mil líneas más arriba. Ahí estuvieron un rato y **no aplicaban**: a
   igual especificidad gana la última regla del archivo, y `.contexto {
   flex-shrink: 0 }` de este bloque pisaba al `flex-shrink: 1` del media. El
   síntoma fue un desborde de 168 px idéntico en las diecinueve pantallas.

   Es primo del accidente de `.nota-ayuda`, que estaba definida dos veces con
   4.000 líneas de distancia: en una hoja de 8.000 líneas, la distancia entre
   dos reglas del mismo selector ES el bug. */
@media (max-width: 640px) {
  /* ⚠️ Acá el que CEDE es el nombre del COLEGIO, no la pastilla.
     
     Estuvo al revés y el resultado se pudo medir: a 390 px el nombre se
     quedaba con 165 px y la pastilla con **13**. O sea, la identidad —que no
     cambia y que quien entró ya sabe— aplastaba al control con el que se
     trabaja. En un teléfono, «2025-2026 · Primaria» es lo que hay que poder
     leer; «COLEGIO A 65994001» no.
     
     El nombre igual no desaparece: se recorta con puntos suspensivos, que es
     lo que dice en cuál de los dos colegios está parada la secretaria que
     tiene los dos. */
  .contexto-colegio { max-width: 8ch; }

  /* ⚠️ Cede, pero con PISO. `flex-shrink: 0` a secas devolvía el desborde de
     45 px; `flex-shrink: 1` sin piso lo dejaba en 13 px, o sea invisible. El
     piso es lo que el año necesita —«2025-2026» son nueve caracteres— y lo que
     sobra se reparte con el nombre del colegio. */
  .contexto { min-width: 6.5rem; flex-shrink: 1; }
  .contexto-pastilla { max-width: 100%; min-width: 0; overflow: hidden; }
  .contexto-pastilla-texto { overflow: hidden; text-overflow: ellipsis; min-width: 0; }

  /* La situación del año se cae antes que el año: «finalizado el 31 de julio»
     son 24 caracteres para decir algo que el panel repite en cada opción. */
  .contexto-valor-situacion { display: none; }

  /* El panel se ancla a la DERECHA: pegado a la izquierda de la pastilla se
     salía por el borde de la pantalla. */
  /* ⚠️ El panel se ancla al VIEWPORT, no a la pastilla.
     
     Con `right: 0` se alinea al borde derecho de la pastilla, y la pastilla
     está a la izquierda de la barra: el panel se salía por el borde IZQUIERDO
     de la pantalla y el primer tercio quedaba cortado. Se vio en la captura.
     
     `position: fixed` lo saca del sistema de coordenadas de la pastilla y lo
     pone en el de la ventana, que es donde tiene que caber. El `top` fijo es el
     alto de la barra, que es fija también. */
  .contexto-panel {
    position: fixed;
    top: var(--alto-barra);
    left: var(--space-sm);
    right: var(--space-sm);
    width: auto;
    min-width: 0;
    max-width: none;
  }

  /* ⚠️ Los niveles se APILAN, no se recortan.
     
     En fila, «Todos · Educación Inicial · Primaria» no entra en 320 px y el
     último quedaba fuera del panel — Daniel: *«en el phone se oculta la opción
     de seleccionar todo»*. Y era el peor de los tres que se podía perder: es la
     salida del filtro. */
  .contexto-niveles { flex-direction: column; align-items: stretch; }
  .contexto-nivel-boton { justify-content: flex-start; }
}

/* ---- La planilla de informes descriptivos (MOR-216) ---- */
/* La maestra escribe veinticinco seguidos: la pantalla tiene que sentirse como
   una lista y no como veinticinco formularios. */
.informes-lista {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--space-lg);
}

.informe-cabecera {
  display: flex;
  align-items: baseline;
  gap: var(--space-sm);
  margin-bottom: var(--space-xs);
}

.informe-alumno {
  font-weight: 600;
  flex: 1 1 auto;
  min-width: 0;
}

/* El promedio al lado del nombre: escribiendo veinticinco informes seguidos hay
   que poder recordar de quién se habla sin salir de la pantalla.
   ⚠️ SIN color semántico — es una referencia para el docente, no una alarma, y
   en SIGA el rojo quiere decir «este niño está aplazado» (MOR-135). */
.informe-promedio {
  flex: 0 0 auto;
  font-size: 13px;
  font-variant-numeric: tabular-nums;
  color: var(--text-secondary);
  white-space: nowrap;
}

/* El estado del guardado, por fila. Un guardado silencioso que falla es peor
   que no guardar: se cierra el teléfono creyendo que se terminó. */
.informe-campo {
  /* Crece con lo que se escribe: tres líneas fijas esconden lo ya escrito justo
     cuando se relee para corregir. El alto lo pone el controlador. */
  resize: vertical;
  min-height: 4.5rem;
  overflow-y: hidden;
  line-height: 1.5;
}

.informe-fila[data-estado="error"] .informe-campo { border-color: var(--eliminar-primary); }

/* El informe en la boleta y en el portal de la familia. */
.informe-descriptivo {
  margin-top: var(--space-md);
  padding: var(--space-md);
  border-left: 3px solid var(--border-standard);
  background: var(--surface-hover);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
}

.informe-descriptivo-titulo {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-tertiary);
  margin-bottom: var(--space-xs);
}

.informe-descriptivo-cuerpo {
  margin: 0;
  white-space: pre-line;
  line-height: 1.6;
}

/* ---- Las competencias en el portal de la familia (MOR-136) ---- */
/* Es para lo ÚNICO que existen las competencias: que la madre lea qué se
   trabajó, no solo con qué nota salió su hijo. */
.portal-materia-fila {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-sm);
}

.portal-competencias {
  list-style: none;
  margin: var(--space-xs) 0 0;
  padding: 0 0 0 var(--space-sm);
  border-left: 2px solid var(--border-standard);
  display: grid;
  gap: var(--space-xs);
}

.portal-competencia-fila {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-sm);
  font-size: 14px;
  color: var(--text-secondary);
}

/* El nivel de la competencia es DATO, así que puede llevar tono — pero nunca
   rojo, que en SIGA quiere decir «este niño está aplazado» y ese significado no
   se gasta (regla de color, MOR-135). Neutro y con la sigla siempre legible. */
.portal-competencia-nivel {
  flex: 0 0 auto;
  font-weight: 600;
  font-size: 13px;
  font-variant-numeric: tabular-nums;
  color: var(--text-primary);
  white-space: nowrap;
}

.portal-indicadores {
  margin: 2px 0 0;
  padding-left: var(--space-md);
  font-size: 13px;
  color: var(--text-tertiary);
}

/* Cada indicador con SU nivel (MOR-136, segunda mitad). Un indicador lo mide
   una sola evaluación, así que el nivel es el de esa nota — una lectura, no un
   promedio inventado. Salen solo los que se midieron: el renglón sin nivel
   alarga una boleta que ya tiene ocho materias. */
.portal-indicador {
  display: flex;
  align-items: baseline;
  gap: var(--space-xs);
}

/* Se apoya en la línea de puntos para que el ojo llegue del texto a su nivel
   sin que la columna tenga que estar alineada — los indicadores son de largos
   muy distintos. */
.portal-indicador::after {
  content: "";
  flex: 1 1 auto;
  min-width: var(--space-sm);
  border-bottom: 1px dotted var(--border-subtle);
  transform: translateY(-3px);
}

.portal-indicador-nivel {
  order: 3;
  flex: 0 0 auto;
  font-weight: 600;
  color: var(--text-secondary);
  white-space: nowrap;
}

.portal-leyenda {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 4px;
}

.portal-leyenda strong { margin-right: var(--space-xs); }
.portal-leyenda .dato-secundario { margin-left: var(--space-xs); }

/* ---- Qué evalúa una evaluación (MOR-136) ---- */
/* El enganche entre el plan y la nota. Se marcan COMPETENCIAS; los indicadores
   van debajo como contexto —ayudan a reconocer cuál es cuál— y no se guardan. */
.que-evalua-lista {
  list-style: none;
  margin: var(--space-xs) 0 0;
  padding: 0;
  display: grid;
  gap: var(--space-xs);
}

.que-evalua-item {
  border: 1px solid var(--border-standard);
  border-radius: var(--radius-sm);
  padding: var(--space-sm) var(--space-md);
  background: var(--surface-elevated);
  transition: border-color var(--transition-fast), background var(--transition-fast);
}

/* Marcada se NOTA, y no solo por la casilla: en una lista de seis competencias
   de dos renglones cada una, un tilde de 13px no se encuentra de un vistazo. */
.que-evalua-item:has(input:checked) {
  border-color: var(--brand-primary);
  background: var(--surface-hover);
}

.que-evalua-competencia {
  display: flex;
  align-items: flex-start;
  gap: var(--space-xs);
  cursor: pointer;
  /* 44px de objetivo táctil: esto se marca desde un teléfono. */
  min-height: 44px;
  align-content: center;
  flex-wrap: wrap;
}

.que-evalua-competencia input {
  flex: 0 0 auto;
  margin-top: 3px;
  width: 18px;
  height: 18px;
  accent-color: var(--brand-primary);
}

.que-evalua-competencia span { flex: 1 1 0; min-width: 0; }

/* ⚠️ Los indicadores SÍ se marcan desde MOR-136 (segunda mitad): se elige cuál
   mide esta evaluación. Lo que sigue siendo cierto es que no se CALIFICAN
   aparte — su nivel en la boleta es el de su competencia. Van sangrados bajo
   ella porque describen; la casilla dice cuál se mide, no cuánto vale. */
.que-evalua-indicadores {
  margin: var(--space-micro) 0 0 calc(18px + var(--space-xs));
  padding-left: var(--space-md);
  color: var(--text-secondary);
  font-size: 13px;
}

.que-evalua-indicadores li { margin-top: 2px; }

/* Lo que se despliega al marcar la competencia: sus indicadores y el campo para
   escribir otros. Escondido mientras no esté marcada — quien no la mide no
   tiene nada que elegir ahí. */
.que-evalua-mide {
  margin-left: calc(18px + var(--space-xs));
  padding-top: var(--space-xs);
}

.que-evalua-indicador {
  display: flex;
  align-items: flex-start;
  gap: var(--space-xs);
  min-height: 34px;
  align-content: center;
  flex-wrap: wrap;
  padding: 3px 0;
  cursor: pointer;
}

.que-evalua-indicador input {
  flex: 0 0 auto;
  margin-top: 3px;
  width: 16px;
  height: 16px;
  accent-color: var(--brand-primary);
}

.que-evalua-indicador span { flex: 1 1 0; min-width: 0; }

/* Los que ya mide otra evaluación: se ven —o el profesor los busca— y no se
   pueden marcar. El nombre de quién los mide es lo que contesta la pregunta. */
.que-evalua-indicadores--tomados li {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--space-xs);
  color: var(--text-tertiary);
  font-size: 12px;
}

.que-evalua-mide .form-label { display: block; margin: var(--space-xs) 0 2px; }


/* Lo que la evaluación ya evalúa, en su ficha. */
.evalua-chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs);
}

.evalua-chip {
  display: inline-flex;
  align-items: center;
  padding: 4px var(--space-sm);
  border: 1px solid var(--border-standard);
  border-radius: var(--radius-sm);
  background: var(--surface-hover);
  font-size: 13px;
  color: var(--text-secondary);
}

/* ---- La rúbrica del colegio (MOR-136) ---- */
/* Una tabla de cinco filas que se editan juntas: es una ESCALERA, y sus
   peldaños solo tienen sentido unos respecto de otros. */
.tabla-rubrica .columna-codigo { width: 7rem; }
.tabla-rubrica .columna-desde  { width: 6rem; }
.tabla-rubrica .columna-cubre  { width: 8rem; }

.tabla-rubrica td { vertical-align: middle; }

/* El código es corto y se lee como una sigla: centrado y en mayúscula. */
.campo-rubrica-codigo {
  text-transform: uppercase;
  text-align: center;
  letter-spacing: 0.04em;
  font-weight: 600;
}

.campo-rubrica-valor { text-align: center; }

/* «Cubre» es DATO derivado, no un campo: se pinta como tal — sin caja, en el
   tono secundario y con cifras tabulares para que las bandas se lean como una
   columna de números y no como cinco frases. */
.rubrica-cubre {
  color: var(--text-secondary);
  font-size: 13px;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* Quitar es una CASILLA disfrazada de botón, y a propósito: el guardado es uno
   solo, así que una fila que desapareciera en el acto no se podría recuperar
   con el botón de atrás. Marcada, la fila se atenúa y se tacha — se ve que va
   a irse, y se puede desmarcar. */
.rubrica-quitar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 36px;
  min-height: 36px;
  border-radius: var(--radius-sm);
  color: var(--text-tertiary);
  cursor: pointer;
  transition: all var(--transition-fast);
}

.rubrica-quitar:hover { color: var(--eliminar-primary); background: var(--surface-hover); }

.rubrica-quitar-check { position: absolute; opacity: 0; width: 0; height: 0; }

.rubrica-quitar:has(.rubrica-quitar-check:checked) {
  color: var(--eliminar-primary);
}

.rubrica-fila:has(.rubrica-quitar-check:checked) td:not(.columna-acciones) {
  opacity: 0.4;
  text-decoration: line-through;
}

.rubrica-quitar:focus-within {
  outline: 2px solid var(--brand-primary);
  outline-offset: 2px;
}

/* El aviso de que la rúbrica es una propuesta y no está guardada. */
.rubrica-propuesta {
  display: flex;
  align-items: flex-start;
  gap: var(--space-xs);
  padding: var(--space-sm) var(--space-md);
  border-left: 3px solid var(--brand-primary);
  background: var(--surface-hover);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  margin-bottom: var(--space-md);
}

.rubrica-propuesta .icono { flex: 0 0 auto; margin-top: 2px; color: var(--brand-primary); }

@media (max-width: 700px) {
  /* En el teléfono la etiqueta pasa a ser un renglón propio: reservarle 9.5rem
     al lado dejaba a las pastillas en una columna de dos dedos de ancho. */
  .pastillas { gap: var(--space-micro); }
  .pastillas-etiqueta { min-width: 100%; }
}
}

/* ═══════════════════════════════════════════════════════════════════════════
   QUÉ NIVELES DICTA EL COLEGIO (MOR-153)

   Tres casillas que reemplazan catorce formularios. Se dibujan grandes a
   propósito: es la decisión más importante de la primera hora, y de ella salen
   los nombres de los grados, su orden y la escala de sus boletines.
   ═══════════════════════════════════════════════════════════════════════════ */

.niveles-opciones {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: var(--space-md);
  margin-bottom: var(--space-xl);
}

.nivel-opcion {
  display: flex;
  align-items: flex-start;
  gap: var(--space-sm);
  padding: var(--space-md);
  border: 1px solid var(--border-standard);
  border-radius: var(--radius-md);
  cursor: pointer;
  transition: border-color var(--transition-fast), background var(--transition-fast);
}

.nivel-opcion:hover { border-color: var(--brand-primary); }

/* ⚠️ `:has()` y no una clase que ponga JavaScript: la casilla marcada ES el
   estado, y duplicarlo en una clase deja dos verdades que se desincronizan. */
.nivel-opcion:has(input:checked) {
  border-color: var(--brand-primary);
  background: var(--brand-subtle);
}

.nivel-opcion:has(input:focus-visible) {
  outline: 2px solid var(--brand-primary);
  outline-offset: 2px;
}

.nivel-opcion input { margin-top: 3px; flex: 0 0 auto; }

.nivel-cuerpo { display: flex; flex-direction: column; gap: var(--space-micro); min-width: 0; }

.nivel-nombre { font-weight: 600; font-size: 15px; color: var(--text-primary); }
.nivel-detalle { font-size: 13px; color: var(--text-secondary); }

/* Los nombres de los grados se muestran ANTES de crear nada: un director tiene
   que poder ver qué va a aparecer, no descubrirlo después. */
.nivel-grados {
  font-size: 12px;
  color: var(--text-tertiary);
  line-height: 1.5;
}

.nivel-puesto {
  display: inline-flex;
  align-items: center;
  gap: var(--space-micro);
  font-size: 12px;
  color: var(--success-text);
}

/* ---- La escala de primaria ---- */

.escala-de-primaria {
  border-top: 1px solid var(--border-subtle);
  padding-top: var(--space-lg);
  margin-bottom: var(--space-lg);
}

.escala-opciones { display: flex; flex-wrap: wrap; gap: var(--space-md); }

.escala-opcion {
  display: flex;
  align-items: flex-start;
  gap: var(--space-xs);
  cursor: pointer;
}

.escala-opcion > span { display: flex; flex-direction: column; }
.escala-opcion .nota-ayuda { margin: 0; }

/* ---- Los grados que ya tiene ---- */

.niveles-titulo-grados {
  font-size: 15px;
  margin: var(--space-xl) 0 var(--space-sm);
  color: var(--text-secondary);
}

.niveles-lista { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: var(--space-xs); }

.niveles-item {
  display: flex;
  align-items: baseline;
  gap: var(--space-xs);
  padding: var(--space-xs) var(--space-sm);
  background: var(--surface-input);
  border-radius: var(--radius-sm);
  font-size: 14px;
}

.niveles-item-nombre { font-weight: 500; }
.niveles-item-nivel { font-size: 12px; color: var(--text-tertiary); }

/* Un grado con nombre fuera del catálogo no se rechaza —hay colegios con datos
   cargados antes de esto— pero sí se señala: es lo que el importador no va a
   poder emparejar contra una planilla. */
.niveles-item-aviso {
  display: inline-flex;
  align-items: center;
  gap: var(--space-micro);
  font-size: 12px;
  color: var(--danger-text);
}

/* ═══════════════════════════════════════════════════════════════════════════
   EL MAPA DE LA PUESTA EN MARCHA (MOR-154)

   No es un wizard: es una lista de pasos en el panel, donde cada uno enlaza a
   la pantalla que ya existe. Ver `_puesta_en_marcha.html.haml`.

   ⚠️ El color acá lo lleva el ESTADO, que es un dato —«esto ya está»— y nunca
   el botón. Un paso listo se tiñe; su enlace «Cambiar» no.
   ═══════════════════════════════════════════════════════════════════════════ */

.puesta-en-marcha { margin-bottom: var(--space-xl); }

.pasos {
  list-style: none;
  margin: var(--space-lg) 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
}

.paso {
  display: grid;
  grid-template-columns: 2rem 1fr auto;
  gap: var(--space-sm) var(--space-md);
  padding: var(--space-md) 0;
  border-top: 1px solid var(--border-subtle);
  align-items: start;
}

.paso:first-child { border-top: none; }

/* La marca: número mientras falta, visto cuando está. */
.paso-marca {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 1.6rem;
  color: var(--text-tertiary);
}

.paso-numero {
  font-family: var(--font-serif);
  font-size: 15px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.paso--listo .paso-marca { color: var(--success-text); }

.paso-cuerpo { display: flex; flex-direction: column; gap: var(--space-xs); min-width: 0; }

.paso-titulo {
  font-size: 15px;
  font-weight: 600;
  margin: 0;
  color: var(--text-primary);
}

/* Un paso terminado se colapsa a una línea con su resultado. NO se atenúa hasta
   volverse ilegible: sigue siendo información que alguien va a querer leer para
   verificar qué quedó configurado. */
.paso--listo .paso-titulo { font-weight: 500; color: var(--text-secondary); }

.paso-resultado {
  margin: 0;
  font-size: 14px;
  color: var(--text-primary);
}

/* La explicación del paso, como un aviso (MOR-203).
   ────────────────────────────────────────────────
   Daniel, en dos vueltas: «esos textos deberían ser más llamativos» y «aún las
   descripciones están más cortas que el ancho total; deberían ser mensajes tipo
   warning o info».

   ⚠️ El tono es AZUL y no ámbar, y eso no contradice el pedido: la regla de
   color (MOR-135) reserva el ámbar, el verde y el rojo para el DATO —un ámbar
   acá compite con «196 alumnos sin representante», que sí es una anomalía—. La
   familia fría queda libre para el cromo, y un aviso informativo es cromo: no
   dice que algo esté mal, dice qué es este paso.

   ⚠️ Y sin `max-width`. Lo tuvo en 62ch y después en 92ch, y las dos veces
   Daniel vio el texto cortado antes del borde. La medida cómoda de lectura está
   pensada para un párrafo largo; acá son dos renglones dentro de una caja que
   YA los acota con su propio relleno. */
.paso-explicacion {
  margin: 0;
  padding: var(--space-sm) var(--space-md);
  border-left: 3px solid var(--brand-primary);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  background: color-mix(in srgb, var(--brand-primary) 6%, transparent);
  font-size: 15px;
  line-height: 1.6;
  color: var(--text-primary);
  text-wrap: pretty;
}

/* El bloqueo se DICE, con el nombre de lo que falta, y el botón de al lado
   lleva ahí. Nunca un control apagado. */
.paso-bloqueo {
  margin: 0;
  font-size: 13px;
  color: var(--text-tertiary);
}

.paso-acciones {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs);
  margin-top: var(--space-micro);
}

.paso-acciones form { margin: 0; }

.paso-rehacer { display: flex; align-items: center; }

.paso-enlace-tenue {
  font-size: 13px;
  color: var(--text-tertiary);
  text-decoration: none;
  padding: var(--space-micro) var(--space-xs);
  border-radius: var(--radius-sm);
}

.paso-enlace-tenue:hover { color: var(--brand-primary); text-decoration: underline; }

.paso--omitido .paso-titulo,
.paso--omitido .paso-resultado { color: var(--text-tertiary); }

@media (max-width: 640px) {
  /* En el teléfono el enlace de «Cambiar» pasa abajo: tres columnas en 375px
     dejan el título en dos letras por línea. */
  .paso { grid-template-columns: 2rem 1fr; }
  .paso-rehacer { grid-column: 2; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   QUIÉN NO HA CARGADO SUS NOTAS (MOR-155)

   El único pendiente del panel con fecha de vencimiento: el lapso cierra y los
   boletines salen incompletos. Por eso va primero de los tres.
   ═══════════════════════════════════════════════════════════════════════════ */

.atrasados { display: flex; flex-direction: column; gap: var(--space-md); }

.atrasado {
  border: 1px solid var(--border-standard);
  border-radius: var(--radius-md);
  overflow: hidden;
}

.atrasado-cabecera {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-sm);
  padding: var(--space-md);
  background: var(--surface-input);
}

.atrasado-nombre { font-weight: 600; text-decoration: none; color: var(--text-primary); }
.atrasado-nombre:hover { color: var(--brand-primary); }

.atrasado-resumen {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-xs);
  font-size: 13px;
  color: var(--text-secondary);
}

.atrasado-punto { color: var(--border-emphasis); }

/* ⚠️ El rojo acá describe un DATO —cuántos niños no tienen nota—, no un
   control. La regla del proyecto prohíbe teñir el botón, no señalar la falta. */
.atrasado-dato--alerta { color: var(--danger-text); font-weight: 500; }

.atrasado-correo {
  display: inline-flex;
  align-items: center;
  gap: var(--space-micro);
  color: var(--brand-primary);
  text-decoration: none;
}
.atrasado-correo:hover { text-decoration: underline; }

.atrasado-lista { list-style: none; margin: 0; padding: 0; }

/* Una fila por evaluación, no una tabla: son dos o tres por profesor y una
   tabla de tres filas con cinco encabezados pesa más de lo que informa. */
.atrasado-fila {
  display: grid;
  grid-template-columns: 5rem minmax(9rem, 1fr) 2fr auto;
  gap: var(--space-sm);
  align-items: baseline;
  padding: var(--space-sm) var(--space-md);
  border-top: 1px solid var(--border-subtle);
  font-size: 14px;
}

.atrasado-fecha { color: var(--text-tertiary); font-size: 13px; white-space: nowrap; }

.atrasado-que { display: flex; flex-direction: column; }
.atrasado-materia { font-size: 12px; color: var(--text-tertiary); }

.atrasado-descripcion { color: var(--text-secondary); min-width: 0; }

.atrasado-avance {
  font-variant-numeric: tabular-nums;
  font-size: 13px;
  color: var(--text-secondary);
  white-space: nowrap;
  text-align: right;
}

@media (max-width: 640px) {
  /* En el teléfono la descripción pasa a su propia línea: cuatro columnas en
     375px dejan «Examen del primer corte» en una palabra por renglón. */
  .atrasado-fila { grid-template-columns: 5rem 1fr auto; }
  .atrasado-descripcion { grid-column: 1 / -1; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   LA FRANJA DEL RECORRIDO (MOR-154)

   Una línea que dice dónde está y cómo vuelve al plan. Ver
   `shared/_franja_de_recorrido`.

   ⚠️ NO es `position: fixed`, a propósito. En una laptop de 13" ya hay barra de
   contexto y a veces avisos de suscripción; una cuarta banda fija son doscientos
   píxeles de cromo antes del primer dato del colegio.
   ═══════════════════════════════════════════════════════════════════════════ */

.franja {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-sm);
  /* 44px de alto mínimo: los dos enlaces son objetivos táctiles. */
  min-height: 44px;
  padding: var(--space-xs) var(--space-md);
  margin-bottom: var(--space-lg);
  background: var(--brand-subtle);
  border: 1px solid var(--brand-primary);
  border-radius: var(--radius-md);
  font-size: 13px;
}

.franja-volver,
.franja-seguir {
  display: inline-flex;
  align-items: center;
  gap: var(--space-micro);
  color: var(--brand-primary);
  text-decoration: none;
  font-weight: 500;
  padding: var(--space-micro) var(--space-xs);
  border-radius: var(--radius-sm);
  white-space: nowrap;
}

.franja-volver:hover,
.franja-seguir:hover { text-decoration: underline; }

.franja-volver:focus-visible,
.franja-seguir:focus-visible,
.franja-cerrar:focus-visible {
  outline: 2px solid var(--brand-primary);
  outline-offset: 2px;
}

.franja-centro {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-micro);
  flex: 1 1 auto;
  min-width: 0;
  color: var(--text-secondary);
}

.franja-titulo { font-weight: 600; color: var(--text-primary); }
.franja-avance { font-variant-numeric: tabular-nums; }
.franja-punto { color: var(--border-emphasis); }
.franja-paso { color: var(--text-primary); }

/* Cuando se desvió a otra pantalla, la franja lo dice en vez de fingir. */
.franja-paso--lejos { color: var(--text-secondary); font-style: italic; }

.franja-acciones {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  margin-left: auto;
}

.franja-cerrar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* 32px porque va pegado a «seguir»: más chico se falla y se sale del
     recorrido queriendo avanzar. */
  width: 32px;
  height: 32px;
  color: var(--text-tertiary);
  border-radius: var(--radius-sm);
}

.franja-cerrar:hover { color: var(--text-primary); background: var(--surface-hover); }

@media (max-width: 640px) {
  /* En el teléfono el centro pasa a su propia línea y las acciones quedan
     abajo: los tres bloques en 375px dejan «va por materias» ilegible. */
  .franja { gap: var(--space-xs); }
  .franja-centro { flex: 1 0 100%; order: 3; }
  .franja-acciones { margin-left: auto; }
}

/* El enlace al registro, al pie de la pantalla de entrada (MOR-158). */
.acceso-registro {
  margin-top: var(--space-md);
  font-size: 14px;
  color: var(--text-secondary);
  text-align: center;
}

.acceso-registro a { color: var(--brand-primary); font-weight: 500; }

/* Las dos pantallas del camino de entrada: registro y alta del colegio. */
.registro { max-width: 46rem; margin: 0 auto; }
.registro-form { display: flex; flex-direction: column; gap: var(--space-md); }

/* El campo que aparece cuando el municipio o la parroquia no están en el
   catálogo (MOR-159). Va pegado a su desplegable, sin etiqueta propia: la
   etiqueta ya la puso el select de arriba. */
.ubicacion-libre { margin-top: var(--space-xs); }

/* La ficha de una sección. Salieron de estilos en línea que estaban escritos
   cinco veces en la misma vista (revisión rails-style). */
.seccion-conteo {
  color: var(--text-secondary);
  font-size: 14px;
  margin-bottom: var(--space-lg);
}

.seccion-soy-yo {
  margin-left: var(--space-micro);
  font-size: 12px;
  color: var(--brand-primary);
}

.seccion-separador {
  margin: var(--space-xl) 0;
  border: 0;
  border-top: 1px solid var(--border-standard);
}

.seccion-subtitulo { font-size: 16px; margin-bottom: var(--space-lg); }

/* El nombre de quien tiene la sesión, en la barra: hereda el color de la barra
   en vez de declararlo. */
.user-info { color: inherit; }

/* ═══════════════════════════════════════════════════════════════════════════
   LA AYUDA BAJO DEMANDA (MOR-161)

   Un «?» junto a la etiqueta; el texto aparece al tocarlo y se va solo. Ver
   `PistasHelper` para el porqué.

   ⚠️ Usa `popover` nativo: el navegador se encarga de cerrar al tocar afuera,
   de `esc`, y de dibujarlo en la CAPA SUPERIOR — o sea que ningún `overflow`
   del formulario lo recorta, que es lo que rompe a un tooltip casero.
   ═══════════════════════════════════════════════════════════════════════════ */

.form-etiqueta {
  display: flex;
  align-items: center;
  gap: var(--space-micro);
  margin-bottom: var(--space-micro);
}

.form-etiqueta .form-label { margin-bottom: 0; }

/* ⚠️ El ancla es de CADA pista y viene del marcado (`--ancla`, MOR-196).
   Con `anchor-name: --pista` para todas, el navegador resolvía al ÚLTIMO
   «?» del documento y cada popover salía junto a ése. */
.pista { display: inline-flex; anchor-name: var(--ancla); }

.pista-boton {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* 24px de caja para un icono de 14: el objetivo táctil no puede ser el
     dibujo. Va con `padding` y no con `width` para no deformar la línea. */
  width: 24px;
  height: 24px;
  padding: 0;
  border: 0;
  background: none;
  color: var(--text-tertiary);
  cursor: pointer;
  border-radius: 50%;
}

.pista-boton:hover { color: var(--brand-primary); background: var(--surface-hover); }

.pista-boton:focus-visible {
  outline: 2px solid var(--brand-primary);
  outline-offset: 1px;
}

.pista-texto {
  max-width: 26rem;
  margin: 0;
  padding: var(--space-sm) var(--space-md);
  border: 1px solid var(--border-standard);
  border-radius: var(--radius-md);
  background: var(--surface-elevated);
  box-shadow: 0 8px 24px rgba(10, 16, 26, 0.16);
  font-size: 13px;
  line-height: 1.55;
  color: var(--text-secondary);
}

/* Anclado al «?» donde el navegador sabe hacerlo. Donde no —Firefox todavía—,
   el popover se centra solo en la pantalla: se lee igual y se cierra igual. */
@supports (position-anchor: --pista) {
  .pista-texto {
    position-anchor: var(--ancla);
    position-area: bottom span-right;
    margin-top: var(--space-micro);
    position-try-fallbacks: flip-block, flip-inline;
  }
}

/* Un dato secundario: el teléfono debajo de un nombre, «Sin asignar», el
   formato de una columna (MOR-161).
   ⚠️ NO es `.nota-ayuda`. Se veían igual y por eso se confundían, pero una dice
   «esto te explica algo» y la otra es un dato del colegio. Mezclarlas hacía que
   el buscador de ayuda del proyecto —y quien lee el código— no supiera cuál es
   cuál. */
.dato-secundario {
  font-size: 13px;
  color: var(--text-tertiary);
}

/* La descripción de una opción, dentro del propio control (MOR-161).
   ⚠️ NO es ayuda: es parte de la opción. Esconderla detrás de un «?» obligaría
   a abrir dos tarjetas para poder elegir entre dos radios. */
.escala-opcion-detalle {
  font-size: 13px;
  color: var(--text-tertiary);
}

/* ---- «Por qué funciona así», plegado (MOR-161) ---- */
/* El hermano de `.pista` para el texto que no cuelga de un campo. Mismo
   argumento que los grupos del menú: `<details>` nativo, sin JavaScript. */

.mas-detalle {
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  margin-top: var(--space-md);
}

.mas-detalle-resumen {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  /* 44px: es un control que se toca. */
  min-height: 44px;
  padding: 0 var(--space-md);
  cursor: pointer;
  list-style: none;
  font-size: 14px;
  font-weight: 500;
  color: var(--text-secondary);
  user-select: none;
}

.mas-detalle-resumen::-webkit-details-marker { display: none; }

/* El triángulo lo dibujamos para que gire: el nativo no se anima igual entre
   navegadores. Mismo patrón que `.sidebar-grupo`. */
.mas-detalle-resumen::before {
  content: "";
  width: 0;
  height: 0;
  border-left: 5px solid currentColor;
  border-top: 4px solid transparent;
  border-bottom: 4px solid transparent;
  transition: transform var(--transition-fast);
}

.mas-detalle[open] > .mas-detalle-resumen::before { transform: rotate(90deg); }
.mas-detalle-resumen:hover { color: var(--brand-primary); }

.mas-detalle-resumen:focus-visible {
  outline: 2px solid var(--brand-primary);
  outline-offset: -2px;
  border-radius: var(--radius-md);
}

.mas-detalle-cuerpo {
  padding: 0 var(--space-md) var(--space-md);
  font-size: 14px;
  line-height: 1.6;
  color: var(--text-secondary);
  max-width: 68ch;
}

.mas-detalle-cuerpo p { margin: 0 0 var(--space-sm); }
.mas-detalle-cuerpo p:last-child { margin-bottom: 0; }

/* ⚠️ Acá vivía una SEGUNDA copia de `.contexto-anio*` y `.contexto-nivel*`,
   4.000 líneas más abajo que la primera. Es el mismo accidente que ya tuvo
   `.nota-ayuda`, y con el mismo síntoma: se retoca una y la otra gana sin
   que nadie entienda por qué. Las dos se fueron con MOR-223 — el control de
   contexto está definido UNA vez, junto a `.contexto` y su panel. */

/* ══════════════════════════════════════════════════════════════════════════
   La cabecera de un listado: título, buscador y alta en UNA línea (MOR-192)
   ══════════════════════════════════════════════════════════════════════════

   Daniel: «el sección filtro ocupa demasiado espacio, vamos a fusionarlo con
   el título, cabe todo».

   Eran dos tarjetas con borde, dos títulos y una etiqueta —unos 190 px— para
   un campo de texto. En una laptop de 13" eso es media pantalla de cromo antes
   de la primera fila de datos. */

/* ⚠️ DOS renglones: arriba QUÉ estoy viendo, abajo qué le hago (MOR-194).

   La primera versión metía todo en una línea y andaba mientras hubiera un
   buscador y un [+]. No escala: con los filtros del registro de actividad —que
   ya son cuatro— y con «Exportar» todavía por venir, el renglón se rompe igual,
   pero de una manera distinta en cada pantalla. Partirlo a propósito hace que
   las ocho se vean iguales tengan dos controles o seis. */
/* ⚠️ La cabecera trae su propia separación del contenido, y ya no la hereda de
   `.card-academic-header` (etapa ②).
   Al volverla componible —el partial ya no emite la tarjeta— tiene que
   funcionar igual sola en su franja que dentro de una tarjeta que ya existe.
   Es el mismo borde y el mismo aire que daba `.card-academic-header`, con su
   `:last-child` para cuando no hay nada debajo que separar. */
.cabecera-de-lista {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  /* ⚠️ Sin borde inferior propio: la FRANJA ya separa por contraste, y una
     línea debajo de un bloque de color se lee como un subrayado suelto. El
     margen es el que despega el contenido de la franja. */
  margin-bottom: var(--space-lg);
}

.cabecera-de-lista:last-child { margin-bottom: 0; }

/* ⚠️ Cuando la franja es LO ÚNICO de la tarjeta, la tarjeta no lleva relleno
   abajo: quedaba una banda blanca de veinte píxeles debajo del color, que se
   lee como una tarjeta a medio dibujar. Se ve en el portal del representante,
   donde el título vive solo en su tarjeta.
   `:has()` y no una clase a mano: la vista no tiene por qué saber si es la
   única, y una clase `--sola` se queda vieja el día que alguien agregue algo
   debajo. */
.card-academic:has(> .cabecera-de-lista:only-child) { padding-bottom: 0; }

.card-academic:has(> .cabecera-de-lista:only-child) > .cabecera-de-lista > .cabecera-de-lista-franja {
  margin-bottom: calc(-1 * var(--space-lg));
  border-radius: 0 var(--radius-md) var(--radius-md) 0;
}

/* Una sola línea cuando no hay controles que poner abajo (QA de Daniel).
   ─────────────────────────────────────────────────────────────────────
   Grados, Lapsos, Años escolares y Administradores no buscan ni importan: su
   renglón de abajo tiene un botón solo y cuesta ~60 px de alto que empujan la
   primera fila de datos fuera de la vista. Daniel, sobre Grados: «alto mínimo,
   espacio desaprovechado en este caso particular».

   ⚠️ Solo de 701 px para arriba. Debajo de eso manda el @media de más abajo,
   que apila TODO a propósito: en un teléfono el título y el botón compartiendo
   renglón dejan al botón de 90 px, que es donde se falla al tocarlo. */
@media (min-width: 701px) {


  /* Con una sola línea, el aire de arriba y abajo también sobra. */
}

.cabecera-de-lista-titular {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--space-xs) var(--space-md);
}

/* ⚠️ El título de la pantalla se ve como el ítem ACTIVO del sidebar, y eso es
   una asociación deliberada.
   QA de Daniel: «los títulos deberían ser similares a los ítems activos del
   sidebar: pipe con fondo de color sutil, habría una asociación y referencia».
   El ojo va del renglón marcado en el menú al título y reconoce la misma
   marca: es el mismo «estás acá», dicho dos veces.
   Mismos tokens que `.sidebar-nav-link.active`, para que sigan iguales el día
   que cambie la marca. */
.cabecera-de-lista-titulo {
  margin: 0;
  color: var(--brand-primary);
}

/* ⚠️ LA FRANJA: el título y los controles, en una sola barra de borde a borde.
   ─────────────────────────────────────────────────────────────────────────
   Daniel, en dos pasos. Primero: «que el título no se detenga ni en el botón
   ni en filtros, completo siempre, elementos adicionales sobre él». Y al ver
   el botón en un renglón aparte: «el botón quedó arriba, es dentro».

   Juntos dan esto — una barra completa con el título a la izquierda y lo que
   se hace con la pantalla a la derecha. Lleva la MISMA marca que el ítem
   activo del sidebar («habría una asociación y referencia»), con sus mismos
   tokens para que sigan iguales el día que cambie. */
.cabecera-de-lista-franja {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-sm) var(--space-md);
  padding: var(--space-sm) var(--space-lg);
  /* ⚠️ SANGRA hasta el borde de la tarjeta, saliéndose de su relleno.
     Daniel, sobre la franja: «así el title, sin padding». Con el relleno de la
     `.card-academic` alrededor, la barra flotaba en el medio y se leía como un
     bloque más; tocando los bordes se lee como el encabezado DE la tarjeta.
     ⚠️ El margen negativo es exactamente `--space-lg`, el relleno de
     `.card-academic`: si uno cambia, el otro tiene que cambiar con él. */
  margin: calc(-1 * var(--space-lg)) calc(-1 * var(--space-lg)) 0;
  border-left: 3px solid var(--brand-primary);
  /* Las esquinas de arriba siguen a las de la tarjeta; las de abajo son rectas
     porque ahí sigue el contenido. */
  border-radius: 0 var(--radius-md) 0 0;
  background: var(--brand-subtle);
  /* ⚠️ `min-width: 0` o la franja NO se encoge por debajo de su contenido.
     Un flex item toma `min-width: auto` por defecto, así que el campo de alta
     en línea de Departamentos —etiqueta + input + botón— la empujaba 1 px más
     allá de los 390 px de un teléfono. Lo destapó el test de desborde: un
     píxel es scroll horizontal igual. */
  min-width: 0;
}

/* ⚠️ El título de una TARJETA (nivel 2) no lleva franja: lleva su línea.
   Con la franja en los dos, el portal del representante salía con cuatro
   barras azules apiladas y el H1 dejaba de distinguirse de los H2. La marca de
   «esto es la pantalla» solo sirve si aparece una vez. */
.cabecera-de-lista-titular-simple {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-sm) var(--space-md);
  min-width: 0;
  padding-bottom: var(--space-sm);
  border-bottom: 1px solid var(--border-subtle);
}

.cabecera-de-lista-titular-simple > .cabecera-de-lista-titular { flex: 1 1 auto; min-width: 0; }
.cabecera-de-lista-titular-simple .cabecera-de-lista-titulo { color: var(--text-primary); }

/* El titular se come el sobrante para que los controles queden pegados a la
   derecha aunque el título sea corto. */
.cabecera-de-lista-franja > .cabecera-de-lista-titular { flex: 1 1 auto; min-width: 0; }
.cabecera-de-lista-franja > .cabecera-de-lista-controles { flex: 0 1 auto; min-width: 0; }
.cabecera-de-lista-titular-simple > .cabecera-de-lista-controles { flex: 0 1 auto; min-width: 0; }

.cabecera-de-lista-detalle {
  margin: 0;
  font-size: 14px;
  color: var(--text-secondary);
}

/* ⚠️ Cuando el detalle importa MÁS que el título.
   
   En «Competencias e indicadores» el título dice lo mismo las veinte veces que
   se abre; «1er Grado A · Matemática» dice dónde estoy parado. Se le da el peso
   del dato —tamaño, color de marca, la serif del sistema— sin llegar a competir
   con el `<h1>`: sigue siendo su subtítulo, no otro título. */
.cabecera-de-lista-detalle--destacado {
  margin-top: 2px;
  font-family: var(--font-serif);
  font-size: 18px;
  font-weight: 600;
  color: var(--brand-primary);
}

/* El plan que ya está copiado: no es un error ni un aviso, es un hecho. Va en
   el tono normal de una nota, con el visto en verde de «esto ya está». */
.plan-ya-copiado {
  display: flex;
  align-items: flex-start;
  gap: var(--space-xs);
}

.plan-ya-copiado svg { flex: 0 0 auto; margin-top: 2px; color: var(--success-text); }

/* El renglón de abajo. Los controles arrancan a la IZQUIERDA, alineados con el
   título: es el orden en que se leen. El [+] se va al final con su propio
   `margin-left: auto`. */
.cabecera-de-lista-controles {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  flex-wrap: wrap;
  /* Los controles van ARRIBA del título desde el 8 de septiembre — Daniel:
     «que el título no se detenga ni en el botón ni en filtros, completo
     siempre, elementos adicionales sobre él». */
  justify-content: flex-end;
}

/* Sin controles no hay fila: el `:empty` evita el hueco sin tener que envolver
   ciento cuarenta líneas de Haml en un `if`. */
.cabecera-de-lista-controles:empty { display: none; }

.cabecera-de-lista-filtros {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  flex-wrap: wrap;
  /* Se queda con el sobrante para que los filtros que envuelvan lo hagan
     DENTRO del formulario y no empujen al [+] a una tercera línea. */
  flex: 1;
}

/* El buscador se COME el espacio que sobra (MOR-194).
   ──────────────────────────────────────────────────
   Daniel: «tenemos todo ese espacio muerto, podemos estirar más el input de
   buscar».

   ⚠️ Y por eso ya no lleva un ancho fijo. Tuvo `width: min(40vw, 720px, 100%)`
   y NO funcionaba: el `100%` se resuelve contra `.campo-con-boton`, cuyo ancho
   lo decide su contenido —o sea el propio campo—. La referencia era circular,
   ganaba el contenido, y el campo quedaba en 230 px por más `vw` que se le
   escribiera al lado.

   Lo que sí funciona es dejar que el grupo CREZCA: el formulario se lleva el
   sobrante de la fila y el grupo se lleva el sobrante del formulario. El tope
   existe para que en un monitor ultra-ancho no sea una franja de mil píxeles
   para escribir un apellido. */
.cabecera-de-lista .campo-con-boton {
  flex: 1;
  min-width: 0;
  max-width: 720px;
}

.cabecera-de-lista .search-input-wrap {
  flex: 1;
  min-width: 0;
}

.cabecera-de-lista-select {
  width: auto;
  min-width: 150px;
}

/* «Desde» y «Hasta»: la única etiqueta que sobrevive en la cabecera, porque es
   la única que no se deduce del propio control — dos campos de fecha vacíos son
   idénticos entre sí. */
.cabecera-de-lista-fecha {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
}

.cabecera-de-lista-etiqueta {
  font-size: 13px;
  color: var(--text-secondary);
  white-space: nowrap;
}

@media (max-width: 700px) {
  /* UN control por línea (MOR-194).
     ─────────────────────────────
     Daniel: «en el caso del móvil, un elemento del filtro por línea queda
     mejor. El [+] puede cambiar a [+ Agregar]».

     Y queda mejor porque a 390 px los controles apretados en una fila se
     reparten un ancho que no alcanza: el buscador queda de 120 px —donde no
     entra ni un apellido— y el desplegable de grados, cortado. Apilados, cada
     uno tiene la pantalla entera.

     ⚠️ `min-width: 0` en TODA la cadena, no solo en el input. Un ítem flex se
     niega por defecto a encogerse por debajo de su contenido, y esa negativa se
     propaga hacia arriba: basta que un eslabón no la levante para que la fila
     empuje la página de lado. */
  .cabecera-de-lista-controles,
  .cabecera-de-lista-filtros {
    flex-direction: column;
    align-items: stretch;
    min-width: 0;
    width: 100%;
  }

  .cabecera-de-lista .campo-con-boton,
  .cabecera-de-lista .search-input-wrap {
    min-width: 0;
  }

  .cabecera-de-lista .campo-con-boton { width: 100%; }
  .cabecera-de-lista .search-input-wrap { flex: 1; width: auto; }

  /* ⚠️ Y el INPUT de adentro también tiene que poder encogerse.
     `min-width: 0` en el `.search-input-wrap` no alcanza: un `<input>` trae su
     propio ancho intrínseco de la UA (~180 px), y con el padre a `width: 100%`
     los hijos se desbordan sin que el rect del padre crezca — el botón de al
     lado terminaba 1 px fuera del viewport. Un píxel es scroll horizontal
     igual, y lo destapó el test de desborde, no el ojo. */
  .cabecera-de-lista .search-input-wrap > .form-control { width: 100%; min-width: 0; }

  /* ⚠️ Y el rótulo pegado adelante se sube a su propio renglón.
     «Agregar nuevo departamento» mide 222 px con `flex: 0 0 auto` —no encoge—
     así que en 390 px se comía el campo entero y empujaba al botón fuera del
     viewport. En el teléfono el rótulo va ENCIMA: el grupo sigue siendo uno
     solo, con los bordes redondeados de arriba en el rótulo. */
  .cabecera-de-lista .campo-con-boton { flex-wrap: wrap; }

  .cabecera-de-lista .campo-con-boton > .campo-con-boton-inicio {
    flex: 1 1 100%;
    border-radius: var(--radius-sm) var(--radius-sm) 0 0;
    border-right-width: 1px;
  }

  .cabecera-de-lista .campo-con-boton > .search-input-wrap > .form-control {
    border-radius: 0 0 0 var(--radius-sm);
    border-top-width: 0;
  }

  .cabecera-de-lista .campo-con-boton > .campo-con-boton-final {
    border-radius: 0 0 var(--radius-sm) 0;
    border-top-width: 0;
    margin-left: 0;
  }
  .cabecera-de-lista-select { width: 100%; }

  /* Con los controles apilados, el botón ya no va «al final de la fila»: es
     una fila más.

     ⚠️ Acá vivía una copia entera de `.btn-solo-icono` y `.columna-acciones`
     pegada por accidente, y el comentario que la abría se comió este selector:
     quedaba `.cabecera-de-lista .btn-solo-icono` y las tres reglas encerradas
     en el @media. No se veía —las buenas están más abajo y ganan por orden—
     pero era la clase de duplicado que un día se edita del lado muerto. */
  .cabecera-de-lista .btn-agregar {
    margin-left: 0;
    width: 100%;
  }
}

/* Un campo y su botón, pegados: son UN control (MOR-192).
   ──────────────────────────────────────────────────────
   Misma idea que `campo-compuesto` en la cédula: sin borde entre los dos y con
   las esquinas redondeadas solo por fuera, se leen como una pieza. El borde
   compartido es de 1 px —no dos pegados—, o queda una línea gruesa en el medio
   que los vuelve a separar visualmente. */
.campo-con-boton {
  display: flex;
  align-items: stretch;
}

/* El rótulo pegado ADELANTE (MOR-193).
   ───────────────────────────────────
   Primo de `.campo-compuesto-sigla` —la V/E de la cédula— y a propósito NO es
   la misma clase: allá el prefijo es un `select` de ancho fijo que se abre, y
   acá es un rótulo que solo enfoca. Comparten la idea, no el comportamiento.

   ⚠️ `cursor: text` y no `pointer`: al tocarlo, lo que pasa es que el cursor
   se mete en el campo. Una manito prometería un menú que no existe. */
.campo-con-boton-inicio {
  display: inline-flex;
  align-items: center;
  flex: 0 0 auto;
  padding-inline: var(--space-md);
  font-family: var(--font-sans);
  font-size: 14px;
  color: var(--text-secondary);
  background: var(--surface-hover);
  border: 1px solid var(--control-border);
  border-right: 0;
  border-radius: var(--radius-sm) 0 0 var(--radius-sm);
  white-space: nowrap;
  cursor: text;
}

/* Con rótulo adelante, el campo pierde el redondeo de los DOS lados: queda en
   el medio del grupo. */
.campo-con-boton-inicio + .search-input-wrap > .form-control { border-radius: 0; }

/* ⚠️ El foco se marca en TODO el grupo, no en la mitad que se tocó — misma
   razón que en la cédula: iluminar solo el input lo vuelve a leer como dos
   campos sueltos. */
.campo-con-boton:focus-within {
  outline: 2px solid var(--control-focus-border);
  outline-offset: 1px;
  border-radius: var(--radius-sm);
}

.campo-con-boton:focus-within .form-control,
.campo-con-boton:focus-within .btn { outline: none; }

.campo-con-boton > .search-input-wrap > .form-control,
.campo-con-boton > .form-control {
  border-top-right-radius: 0;
  border-bottom-right-radius: 0;
}

.campo-con-boton-final {
  border-top-left-radius: 0;
  border-bottom-left-radius: 0;
  margin-left: -1px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding-inline: var(--space-md);
}

/* El botón que es SOLO un icono.
   ─────────────────────────────
   ⚠️ Cuadrado y de 36 px de lado, que es el alto de los demás botones: un [+]
   más chico que el campo de al lado rompe la línea. Y 36 px es además el
   mínimo cómodo para un dedo — más chico se falla al tocarlo en el teléfono,
   que es donde un profesor usa esto. */
/* El botón de alta, con su nombre puesto (MOR-194).
   ────────────────────────────────────────────────
   Daniel: «colocarle el nombre [+ Agregar] al botón de new». Nació como un [+]
   pelado porque la cabecera era un solo renglón y no había sitio; con dos
   renglones sobra, y un botón que se lee no necesita que nadie lo descubra.

   ⚠️ El tooltip NO se quita: dice «Registrar inscripción», o sea QUÉ se agrega.
   Es lo que sostiene la regla de MOR-175 —una acción, un nombre— ahora que lo
   visible es el verbo a secas. */
/* Un botón que es SOLO un icono, dentro de una fila (MOR-205).
   ───────────────────────────────────────────────────────────
   La papelera y el atajo a «agregar una materia» de cada departamento. En una
   tabla el nombre de la acción se repetiría en cada fila y competiría con el
   dato; acá el nombre lo lleva el tooltip y el `aria-label`.

   ⚠️ Cuadrado y del alto del botón chico. Un control de menos de 32 px se falla
   al tocarlo en un teléfono, y una papelera que se falla borra la fila de al
   lado. */
.btn-solo-icono {
  width: 32px;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
}

/* La columna de acciones de una tabla: al final, sin estirarse. */
.columna-acciones {
  width: 1%;
  white-space: nowrap;
  text-align: right;
}

.columna-acciones form { display: inline-block; margin: 0; }
.columna-acciones > * + * { margin-left: var(--space-xs); }

.btn-agregar {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-xs);
  flex: 0 0 auto;
  white-space: nowrap;
}


/* El campo que falló (MOR-196).
   ────────────────────────────
   ⚠️ El borde NO es el único aviso: el resumen rojo de arriba sigue diciendo
   QUÉ pasó con cada campo. Esto contesta la otra pregunta —CUÁL de los nueve—,
   que en un formulario largo es la que hace falta y la que faltaba.

   Es una de las excepciones legítimas a la regla de color (MOR-135): el rojo
   acá no adorna un control, señala un dato que está mal. */
.campo-con-error,
.campo-con-error:hover {
  border-color: var(--danger-border);
  background: var(--danger-bg);
}

.campo-con-error:focus,
.campo-con-error:focus-visible {
  outline-color: var(--danger-border);
}


/* La fila que se está editando (MOR-205). El campo se lleva el ancho y los dos
   botones se le pegan al lado, en una sola línea. */
.fila-en-edicion {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  margin: 0;
}

.fila-en-edicion .form-control { flex: 1; min-width: 0; }


/* GUARDAR es verde, y BORRAR es rojo (MOR-207).
   ═════════════════════════════════════════════
   Daniel: «el tono de ese botón debe tender a verde, al igual que todos todos
   los submit, actuales y de ahora en más», y «el botón de eliminar debe tener
   un tono rojo».

   ⚠️ Esto MODIFICA la regla de color (MOR-135), que reservaba verde, rojo y
   ámbar para el DATO y dejaba los controles en la familia fría. La regla no se
   cae: lo que cambia es su alcance, y conviene que quede escrito.

       SIGUE SIENDO DEL DATO          PASA A SER DEL CONTROL
       ─────────────────────          ──────────────────────
       la nota en rojo del aplazado   el verde de GUARDAR
       el ámbar de una anomalía       el rojo de ELIMINAR
       los chips de estado

   Lo que hace que convivan es que no comparten pantalla ni forma: una nota en
   rojo es un NÚMERO dentro de una tabla, y el rojo de borrar es el fondo de un
   botón con un icono de papelera. Y son las dos acciones que TODO el mundo
   espera de ese color — pelear contra esa expectativa cuesta más de lo que la
   regla protegía.

   ⚠️ El verde es oscuro y desaturado a propósito (#1f6b47, no #22c55e): tiene
   que convivir con el azul institucional sin gritar, y sostener texto blanco
   con contraste suficiente. */
:root {
  --guardar-primary: #1f6b47;
  --guardar-hover: #175136;
  --guardar-active: #103a26;
  --eliminar-primary: #a3282a;
  --eliminar-hover: #86201f;
}

.btn-guardar {
  background: var(--guardar-primary);
  border-color: var(--guardar-primary);
  color: #fff;
}

.btn-guardar:hover {
  background: var(--guardar-hover);
  border-color: var(--guardar-hover);
  color: #fff;
}

.btn-guardar:active { background: var(--guardar-active); border-color: var(--guardar-active); }

/* ⚠️ `btn-danger` era «sin tono, y en hover tinta sólida». Ahora lleva su rojo
   desde el principio: una papelera que solo se pinta al pasar el mouse no
   existe en un teléfono, que es donde tocarla por error cuesta más caro. */
.btn-danger {
  background: var(--eliminar-primary);
  border-color: var(--eliminar-primary);
  color: #fff;
}

.btn-danger:hover {
  background: var(--eliminar-hover);
  border-color: var(--eliminar-hover);
  color: #fff;
}

/* ⚠️ El tooltip de la ÚLTIMA fila de una tabla se recortaba (MOR-207).
   Daniel: «los tooltips de abajo no se leen, salen cortados».

   `.table-academic` lleva `overflow: hidden` para que el borde redondeado
   recorte las esquinas de la primera y la última fila. Eso también recorta
   cualquier cosa que se salga de la tabla — y un tooltip que sale POR DEBAJO de
   la última fila se sale siempre.

   La salida no es quitar el `overflow` —se perderían las esquinas— sino que en
   una tabla el tooltip vaya ARRIBA, dentro de la caja. */
.table-academic [data-tooltip]::after {
  top: auto;
  bottom: calc(100% + 8px);
}

.table-academic [data-tooltip]::before {
  top: auto;
  bottom: calc(100% + 2px);
  border-bottom-color: transparent;
  border-top-color: var(--text-primary);
}

/* Y el de la última columna se sale por la DERECHA: se ancla a su borde en vez
   de centrarse. */
.columna-acciones [data-tooltip]::after {
  left: auto;
  right: 0;
  transform: none;
}

.columna-acciones [data-tooltip]::before { left: auto; right: 12px; transform: none; }

/* El alta en línea, alineada a la derecha (MOR-207). Daniel: «alinéalo todo a
   la derecha».

   ⚠️ Hay que APAGAR el `flex: 1` que trae `.cabecera-de-lista-filtros`, y sin
   eso el `margin-left: auto` no hace nada: un ítem que crece ya ocupa todo el
   sobrante, así que no queda margen que repartir. Se veía como «el cambio no se
   aplicó». */
/* ⚠️ `1 1 auto` y no `0 0 auto`: dentro de la franja tiene que poder
   ENCOGERSE. El alta en línea de Departamentos —«Agregar nuevo departamento» +
   campo + botón— desbordaba un teléfono por 1 px con `0 0 auto`, y un píxel es
   scroll horizontal igual. */
.cabecera-de-lista-alta {
  flex: 1 1 auto;
  min-width: 0;
  margin-left: auto;
}

.cabecera-de-lista-alta .campo-con-boton { min-width: 0; }
.cabecera-de-lista-alta .search-input-wrap { min-width: 0; }

/* ⚠️ En el teléfono la franja APILA, y no es cosmética: es lo que impide el
   scroll horizontal.
   Con título y controles compartiendo renglón, el alta en línea de
   Departamentos —etiqueta larga + campo + botón— no cabe en 390 px, y un flex
   item no encoge por debajo de su contenido aunque le pongan `min-width: 0`
   si sus hijos tampoco lo hacen. Apilar resuelve la causa en vez de pelearle
   a la cadena entera. */
@media (max-width: 640px) {
  .cabecera-de-lista-franja {
    flex-direction: column;
    align-items: stretch;
  }

  .cabecera-de-lista-franja > .cabecera-de-lista-controles { justify-content: flex-start; }
  .cabecera-de-lista-alta { margin-left: 0; width: 100%; }
}

/* Y el campo se queda con un ancho cómodo en vez de estirarse: el nombre de un
   departamento son dos palabras. */
.cabecera-de-lista-alta .search-input-wrap { width: min(320px, 100%); }


/* «Su colegio quedó en orden» (MOR-208).
   ─────────────────────────────────────
   La pantalla que no pide nada. La cifra manda: es la prueba de que el sistema
   hizo el trabajo, así que se lee antes que el texto que la explica. */
.arranque-listo-titulo {
  font-family: var(--font-serif);
  margin: 0 0 var(--space-sm);
}

.arranque-listo-cifra {
  font-family: var(--font-serif);
  font-size: 30px;
  font-weight: 700;
  color: var(--brand-primary);
  font-variant-numeric: tabular-nums;
  width: 1%;
  white-space: nowrap;
  padding-right: var(--space-md);
  vertical-align: middle;
}


/* El aviso de un campo comprobado en vivo (MOR-208). Va debajo del campo, chico,
   y NO desplaza el formulario: aparece en el hueco que el `.form-group` ya deja. */
.campo-aviso {
  margin: var(--space-micro) 0 0;
  font-size: 13px;
  line-height: 1.4;
}

.campo-aviso--bien { color: var(--guardar-primary); }
.campo-aviso--mal  { color: var(--eliminar-primary); }


/* Lo que falta aunque el paso esté listo (MOR-208).
   ────────────────────────────────────────────────
   ⚠️ ÁMBAR y no rojo, y esta vez el ámbar es correcto por la regla de color, no
   a pesar de ella: describe un DATO que le falta al colegio —no tiene escudo—
   y no un control. Rojo diría que algo está roto, y no lo está: se puede
   trabajar así y solo se nota al emitir el primer documento. */
.paso-pendiente {
  margin: var(--space-micro) 0 0;
  padding-left: var(--space-sm);
  border-left: 3px solid var(--warning-border);
  font-size: 14px;
  line-height: 1.55;
  color: var(--warning-text);
  max-width: 92ch;
  text-wrap: pretty;
}

/* El enlace que arregla lo que el aviso acaba de nombrar.
   ⚠️ En AZUL de enlace y no en el ámbar del aviso: el ámbar es del dato que
   falta (regla de color), y este es un control. Pintarlo del color del aviso lo
   escondería justo dentro del párrafo que lo hace falta. */
.paso-pendiente-enlace {
  margin-left: var(--space-xs);
  color: var(--brand-primary);
  font-weight: 600;
  white-space: nowrap;
}

.paso-pendiente-enlace::after { content: " →"; }

/* ══════════════════════════════════════════════════════════════════════════
   El plan de evaluación: competencias e indicadores (MOR-136)
   ══════════════════════════════════════════════════════════════════════════ */

/* El plan del año pasado, OFRECIDO y no cargado.
   ⚠️ En el tono de `accion` (índigo) y no de aviso: no hay nada que corregir,
   hay algo que se puede aprovechar. El ámbar diría que algo está mal. */
.plan-sugerido { border-left: 3px solid var(--accion-primary); }

.plan-sugerido-titulo {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  margin: 0 0 var(--space-xs);
  font-size: 16px;
  color: var(--accion-primary);
}

.plan-sugerido-lista {
  margin: var(--space-sm) 0 var(--space-md);
  padding-left: var(--space-lg);
  color: var(--text-secondary);
  font-size: 14px;
  line-height: 1.6;
}

/* La lista del plan. Numerada de verdad —`<ol>`— porque el orden es del
   docente y un lector de pantalla tiene que poder decir «3 de 21». */
.plan-competencias {
  margin: 0;
  padding: 0;
  list-style: none;
  counter-reset: competencia;
}

/* ⚠️ Una TARJETA, no un renglón separado por una línea (21 de septiembre).
   Daniel: «ve el detalle de que cada componente está en un contenedor, que
   contiene la evaluación y los indicadores». Con las evaluaciones y sus
   indicadores anidados dentro, un separador gris no alcanzaba para decir dónde
   termina una competencia y empieza la siguiente. */
.plan-competencia {
  margin-bottom: var(--space-md);
  padding: var(--space-md);
  border: 1px solid var(--border-standard);
  border-radius: var(--radius-md);
  background: var(--surface-elevated);
  counter-increment: competencia;
}

.plan-competencia:last-child { margin-bottom: 0; }

@media (max-width: 700px) {
  /* A 390 px el padding de la tarjeta, la sangría del cuerpo y el de la
     evaluación se suman y no queda ancho para el texto. */
  .plan-competencia { padding: var(--space-sm); }
}

/* ── El número, y lo que va DENTRO ───────────────────────────────────────── */
/* Daniel: «la segunda competencia está suelta y confunde: deben ir enumeradas
   las competencias en el título y un poco más evidente el contenido que va
   dentro».

   ⚠️ El número va en su propio elemento y NO como `::before` del título: desde
   que la descripción es un `<textarea>` no hay dónde ponerlo — un campo de
   formulario es un elemento reemplazado y no admite pseudoelementos. */
.plan-competencia-numero {
  flex: 0 0 auto;
  min-width: 1.5rem;
  padding-top: 2px;
  font-size: 15px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--text-tertiary);
}

.plan-competencia-numero::before { content: counter(competencia) "."; }

/* Indicadores y evaluaciones, sangrados a la altura del título: al mismo nivel,
   dónde terminaba una competencia y empezaba la siguiente había que deducirlo
   del renglón gris. */
.plan-competencia-cuerpo { padding-left: 1.5rem; }

@media (max-width: 700px) {
  /* A 390 px, kilo y medio de sangría se come el ancho de los indicadores. */
  .plan-competencia-cuerpo { padding-left: var(--space-sm); }
}


/* ⚠️ El flex es la CABECERA, no la competencia entera. Desde que las
   evaluaciones van anidadas debajo (8 de septiembre), tener el flex arriba
   ponía la lista de evaluaciones como una tercera columna al lado de los
   botones, con veinte píxeles de ancho. */
.plan-competencia-cabecera {
  display: flex;
  align-items: flex-start;
  gap: var(--space-md);
}

.plan-competencia-texto { flex: 1; min-width: 0; }

/* ⚠️ `.columna-acciones` trae `width: 1%` —sirve en una TABLA, donde el 1 % lo
   resuelve el algoritmo de la tabla— y dentro de un flex eso la deja encogerse
   por debajo de su contenido: los dos botones se salían del borde en el
   teléfono, con el de borrar cortado a la mitad. */
.plan-competencia .columna-acciones { flex: 0 0 auto; width: auto; }

@media (max-width: 700px) {
  /* Con el texto de una competencia —cuarenta palabras— y dos botones al lado,
     a 390 px no hay ancho para las dos cosas. Los botones bajan a su renglón. */
  .plan-competencia-cabecera { flex-wrap: wrap; }
  .plan-competencia .columna-acciones { width: 100%; }
}

/* ⚠️ El número va por CSS y no escrito: renumerar veintiuna a mano al borrar la
   tercera es el trabajo que este issue viene a quitar. */
.plan-competencia-descripcion {
  margin: 0;
  text-wrap: pretty;
  max-width: 80ch;
}

/* ── El alta desplegada en su sitio ──────────────────────────────────────── */
/* Daniel: «no hace falta perderse a otra vista totalmente independiente». El
   formulario cae debajo del botón que lo pidió, con los campos que faltan y
   nada más: la sección, el área, el lapso y la competencia ya están elegidos. */
.alta-en-linea {
  margin-top: var(--space-sm);
  padding: var(--space-md);
  border: 1px solid var(--border-standard);
  border-radius: var(--radius-md);
  background: var(--surface-elevated);
}

/* El título manda y los otros dos son cortos: una fila que envuelve sola en el
   teléfono, sin un `@media` que mantener. */
.alta-en-linea-campos {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm);
}

.alta-en-linea-campo { flex: 0 0 auto; }
.alta-en-linea-campo--titulo { flex: 1 1 16rem; }

/* ⚠️ El peso necesita ancho PROPIO: las flechitas del `number` ocupan lo suyo
   y dejaban «20» pegado contra ellas, sin sitio para escribir tres cifras.
   Acá sí se quedan —a diferencia de la fila del plan, donde se ocultan— porque
   en un campo de formulario con su etiqueta encima son la manera natural de
   subir y bajar de uno en uno. */
.alta-en-linea-campo--peso .form-control { width: 6.5rem; }
.alta-en-linea-campo .form-label { display: block; margin-bottom: 2px; }
.alta-en-linea-campo .form-control { width: 100%; }

.alta-en-linea-acciones {
  display: flex;
  gap: var(--space-sm);
  margin-top: var(--space-sm);
}

/* ── Qué mide la evaluación que se está creando ──────────────────────────── */
/* La competencia ya viene puesta —el botón cuelga de ella— así que lo que queda
   por elegir son sus INDICADORES. Y un indicador lo mide una sola evaluación:
   los que ya tomó otra se ven, apagados y con el nombre de quién los mide. */
.alta-mide {
  margin-top: var(--space-md);
  padding: var(--space-sm) var(--space-md);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-sm);
  background: var(--surface-canvas);
}

.alta-mide-titulo {
  padding: 0 4px;
  font-size: 11.5px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-tertiary);
}

.alta-mide-titulo strong {
  text-transform: none;
  letter-spacing: 0;
  color: var(--text-secondary);
}

.alta-mide-lista,
.alta-mide-tomados {
  margin: var(--space-xs) 0;
  padding: 0;
  list-style: none;
}

/* ⚠️ La casilla y su texto van en UNA etiqueta: en el teléfono el blanco de
   45 px de alto es el área que se toca, y una casilla suelta son 13 px. */
.alta-mide-opcion {
  display: flex;
  align-items: flex-start;
  gap: var(--space-xs);
  min-height: 34px;
  padding: 5px 4px;
  border-radius: var(--radius-sm);
  font-size: 13px;
  line-height: 1.45;
  color: var(--text-primary);
  cursor: pointer;
}

.alta-mide-opcion:hover { background: var(--surface-hover); }
.alta-mide-opcion input { margin-top: 2px; flex: 0 0 auto; }

/* Los que ya mide otra evaluación: se ven, no se marcan, y dicen quién. */
.alta-mide-tomados li {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--space-xs);
  padding: 3px 4px;
  font-size: 12px;
  color: var(--text-tertiary);
}

.alta-mide-tomado { text-decoration: line-through; }
.alta-mide-quien { font-size: 11px; font-style: italic; }

.alta-mide .form-label { display: block; margin: var(--space-xs) 0 2px; }

/* ── La planilla de Educación Inicial ────────────────────────────────────── */
/* La calificación de ese nivel es un TEXTO por área: ni 1–20, ni A/B/C, ni la
   rúbrica. Daniel: «los niños en Inicial no sacan ni A, ni B, ni C, ni PC, ni
   AP, ni 5, ni 20, entonces la calificación es descriptiva».

   Todos los niños en UNA pantalla, como la planilla de notas: con una pantalla
   por niño son treinta y dos clics de navegación además de lo que se escribe. */
.valoraciones-resumen {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--space-sm);
  margin-bottom: var(--space-md);
}

.valoraciones-cuenta { font-size: 13px; font-weight: 600; color: var(--text-secondary); }

.valoraciones-error {
  display: flex;
  align-items: center;
  gap: 4px;
  margin: 0;
  font-size: 12px;
  color: var(--danger-text);
}

.valoraciones { list-style: none; margin: 0; padding: 0; }

.valoracion-alumno {
  padding: var(--space-md) 0;
  border-top: 1px solid var(--border-subtle);
}

.valoracion-alumno:first-child { border-top: 0; }

.valoracion-nombre {
  margin: 0 0 var(--space-xs);
  font-size: 15px;
  font-weight: 600;
}

/* Sangrado bajo el nombre, como los indicadores del plan: se ve que las tres
   áreas son de ese niño y no una lista suelta. */
.valoracion-area { padding-left: 1.5rem; }
.valoracion-area .plan-rotulo { margin: var(--space-sm) 0 2px; }

@media (max-width: 700px) {
  .valoracion-area { padding-left: var(--space-sm); }
}

/* ⚠️ El campo ocupa el ancho ENTERO y crece con lo escrito: una valoración es
   un párrafo sobre un niño, no un dato. `resize: none` porque el alto lo lleva
   `campo-que-crece`, y con la manija puesta la maestra puede encoger el campo
   hasta tapar su propio texto. */
.valoracion-texto {
  display: block;
  width: 100%;
  padding: var(--space-xs) var(--space-sm);
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: transparent;
  font: inherit;
  font-size: 14px;
  line-height: 1.55;
  color: var(--text-primary);
  resize: none;
  overflow: hidden;
  transition: border-color var(--transition-fast), background var(--transition-fast);
}

.valoracion-texto:hover {
  border-color: var(--border-subtle);
  background: var(--surface-elevated);
}

.valoracion-texto:focus {
  border-color: var(--brand-primary);
  background: var(--surface-elevated);
  outline: none;
}

.valoracion-texto::placeholder { color: var(--text-tertiary); font-style: italic; }

.valoracion-texto-fijo {
  margin: 0;
  padding: var(--space-xs) var(--space-sm);
  font-size: 14px;
  line-height: 1.55;
  color: var(--text-secondary);
}

/* El mismo destello que el plan: en el guardado al salir del campo no hay botón
   que se quede apretado, así que esta es toda la confirmación que hay. */
.valoracion-area--guardada { animation: plan-guardada 1.2s ease-out; border-radius: var(--radius-sm); }

@media (prefers-reduced-motion: reduce) {
  .valoracion-area--guardada { animation: none; background: var(--success-bg); }
}

/* ── Los rótulos: qué es cada cosa ───────────────────────────────────────── */
/* Daniel: «coloca subtítulos de secciones para identificar Competencias,
   Indicadores y Evaluaciones».

   ⚠️ Los tres son del MISMO peso visual aunque uno esté dentro de otro: no son
   una jerarquía de importancia, son tres cosas distintas que había que poder
   nombrar. Un rótulo más grande arriba haría leer «indicadores» y
   «evaluaciones» como subordinados de la competencia, y la evaluación no es
   parte de la competencia — la mide. */
/* ⚠️ Con PESO, no en gris claro. Daniel, 23 de septiembre: «resalta un poco
   más las palabras Competencias, Evaluaciones e Indicadores, creo que se pierde
   en la lectura». Eran tres rótulos de 11 px en `--text-tertiary` dentro de una
   pantalla con tres niveles de tarjeta: se leían como fondo.

   Lo que los ordena sigue siendo el tamaño —cada nivel uno menos— y ahora
   además el color y una línea que los ancla a lo que encabezan. */
.plan-rotulo {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  margin: var(--space-md) 0 var(--space-xs);
  font-size: 11.5px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-primary);
}

/* La línea que sigue al texto ata el rótulo a su bloque sin encerrarlo. */
.plan-rotulo::after {
  content: "";
  flex: 1 1 auto;
  height: 1px;
  background: var(--border-subtle);
}

/* Tres niveles de rótulo, uno por nivel de la jerarquía. Decrecen para que el
   ojo sepa dónde está parado sin leer:

     COMPETENCIAS            la sección — abre la lista, sin margen arriba
       EVALUACIONES          dentro de una competencia
         INDICADORES QUE MIDE   dentro de una evaluación   */
.plan-rotulo--seccion {
  margin-top: 0;
  font-size: 13px;
  letter-spacing: 0.1em;
  color: var(--text-primary);
}

.plan-rotulo--indicadores {
  margin: 0 0 4px;
  font-size: 10.5px;
  letter-spacing: 0.07em;
  color: var(--text-secondary);
}

/* ── La competencia, editable en su sitio ────────────────────────────────── */
/* Daniel: «elevar el modelo que aplicamos a evaluaciones a competencias e
   indicadores». Misma regla que la fila de la evaluación: el campo ya es el
   campo, y lo único que cambia al apuntarlo es que se le ve el borde. */
.plan-competencia-campo { flex: 1; min-width: 0; }

/* ⚠️ `resize: none` y `overflow: hidden`: el alto lo lleva `campo-que-crece`
   con el contenido. Dejando la manija de redimensionar, el profesor puede
   encoger el campo hasta tapar su propio texto. */
/* ⚠️ SIN `max-width`. Lo tenía en 80ch —heredado del párrafo de solo lectura,
   donde una medida corta ayuda a leer— y en el campo editable eso lo cortaba a
   media pantalla, con el resto en blanco: el texto se plegaba en una columna
   mientras sus indicadores, que no lo llevan, ocupaban el ancho entero. Dos
   campos del mismo bloque midiendo distinto se lee como un defecto, no como
   una decisión tipográfica. */
.plan-competencia-descripcion-campo {
  display: block;
  width: 100%;
  padding: 2px 5px;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: transparent;
  font: inherit;
  color: var(--text-primary);
  text-wrap: pretty;
  resize: none;
  overflow: hidden;
  transition: border-color var(--transition-fast), background var(--transition-fast);
}

.plan-competencia-descripcion-campo:hover {
  border-color: var(--border-subtle);
  background: var(--surface-elevated);
}

.plan-competencia-descripcion-campo:focus {
  border-color: var(--brand-primary);
  background: var(--surface-elevated);
  outline: none;
}

.plan-competencia-error {
  display: flex;
  align-items: center;
  gap: 4px;
  margin: 4px 0 0;
  font-size: 12px;
  color: var(--danger-text);
}

.plan-competencia--con-error { background: var(--danger-bg); }

/* El mismo destello que la evaluación: en la edición en línea no hay botón que
   se quede apretado ni flash que avise. */
.plan-competencia--guardada { animation: plan-guardada 1.2s ease-out; }

@media (prefers-reduced-motion: reduce) {
  .plan-competencia--guardada { animation: none; background: var(--success-bg); }
}

/* ── Los indicadores, uno por uno ────────────────────────────────────────── */
/* ⚠️ El último campo va VACÍO y escribir en él crea el siguiente, como en
   cualquier lista donde uno escriba listas. Ahorra el botón «agregar» que
   habría que ir a buscar cada vez. */
/* ⚠️ `flex-start` y no `center`: desde que el indicador es un `<textarea>` que
   crece, un párrafo de tres líneas dejaba su × flotando a media altura, lejos
   del renglón donde empieza lo que borra. */
.plan-indicador {
  display: flex;
  align-items: flex-start;
  gap: 2px;
}

.plan-indicador-campo { flex: 1; min-width: 0; display: flex; }

/* La dirección MIRA el plan y no lo escribe, así que sus indicadores no son
   campos. Mismo tamaño y mismo color que el campo, para que la pantalla no
   cambie de forma según quién entra. */
.plan-indicador-texto-fijo {
  flex: 1;
  min-width: 0;
  padding: 3px 5px;
  font-size: 14px;
  line-height: 1.5;
  color: var(--text-secondary);
}

.plan-indicador-texto {
  display: block;
  width: 100%;
  padding: 1px 5px;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: transparent;
  font: inherit;
  font-size: 14px;
  line-height: 1.55;
  color: var(--text-secondary);
  resize: none;
  overflow: hidden;
  transition: border-color var(--transition-fast), background var(--transition-fast);
}

.plan-indicador-texto:hover {
  border-color: var(--border-subtle);
  background: var(--surface-elevated);
}

.plan-indicador-texto:focus {
  border-color: var(--brand-primary);
  background: var(--surface-elevated);
  color: var(--text-primary);
  outline: none;
}

/* El campo del final se distingue en reposo: es una invitación, no un dato. */
.plan-indicador--nuevo { list-style: none; }
.plan-indicador--nuevo .plan-indicador-texto { color: var(--text-tertiary); font-style: italic; }
.plan-indicador--nuevo .plan-indicador-texto:focus { color: var(--text-primary); font-style: normal; }

/* La × aparece al apuntar la línea: siempre visible, tres indicadores son tres
   controles compitiendo con su propio texto. */
.plan-indicador-quitar {
  flex: 0 0 auto;
  padding: 0 4px;
  border: 0;
  background: transparent;
  font: inherit;
  font-size: 14px;
  line-height: 1;
  color: var(--text-tertiary);
  cursor: pointer;
  opacity: 0;
  transition: opacity var(--transition-fast);
}

.plan-indicador:hover .plan-indicador-quitar,
.plan-indicador:focus-within .plan-indicador-quitar { opacity: 1; }
.plan-indicador-quitar:hover { color: var(--danger-text); }

@media (hover: none) {
  .plan-indicador-quitar { opacity: 1; }
}


/* Los indicadores DESCRIBEN: van en segundo plano, sin nivel al lado, porque no
   se califican. Que se vean subordinados es la mitad de decir eso. */
.plan-indicadores {
  margin: var(--space-xs) 0 0;
  padding-left: var(--space-lg);
  color: var(--text-secondary);
  font-size: 14px;
  line-height: 1.55;
}

/* ── Quién mide cada indicador ───────────────────────────────────────────── */
/* Un indicador lo mide UNA evaluación y no se repite en otra: los indicadores
   REPARTEN la competencia entre sus evaluaciones. Por eso el renglón dice cuál
   lo mide — es la pregunta que hace un profesor mirando su plan: qué ya está
   cubierto y qué le falta.

   ⚠️ El nombre va en gris y sin caja: es un DATO al margen, no un control. Con
   forma de chip competiría con la × que sí hace algo. */
.plan-indicador-mide,
.plan-indicador-libre {
  flex: 0 0 auto;
  max-width: 14rem;
  overflow: hidden;
  padding-top: 3px;
  font-size: 11px;
  line-height: 1.4;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--text-tertiary);
}

/* ⚠️ «sin evaluación» NO va en ámbar. Es el estado normal de septiembre —el
   plan se escribe antes que las evaluaciones— y pintar de alarma lo normal
   enseña a ignorar el color. La regla de MOR-135 al revés: acá el dato no es
   un problema todavía. */
.plan-indicador-libre { font-style: italic; opacity: 0.75; }

/* ── Qué indicadores mide una evaluación, bajo su fila ───────────────────── */
/* La fila ya está debajo de su competencia; cuál de sus cinco indicadores mide
   ESTA evaluación y no la de al lado no se deduce de nada. */
.plan-evaluacion-indicadores {
  flex: 1 0 100%;
  margin: 2px 0 0;
  padding-left: 22px;
  list-style: none;
  font-size: 11.5px;
  line-height: 1.5;
  color: var(--text-tertiary);
}

.plan-evaluacion-indicadores li { position: relative; padding-left: 10px; }

.plan-evaluacion-indicadores li::before {
  content: "·";
  position: absolute;
  left: 2px;
}

/* ⚠️ AVISO y no error: la evaluación cuenta para el promedio igual. Lo que no
   puede hacer la boleta es decir qué se midió. Las nacidas después de la regla
   no pueden estar así, así que esto solo alcanza a lo que ya existía. */
.plan-evaluacion-sin-indicador {
  flex: 1 0 100%;
  display: flex;
  align-items: center;
  gap: 3px;
  margin: 2px 0 0;
  padding-left: 22px;
  font-size: 11px;
  color: var(--warning-text);
}

.plan-acciones {
  margin-top: var(--space-lg);
  padding-top: var(--space-md);
  border-top: 1px solid var(--border-subtle);
}

.plan-acciones .nota-ayuda { margin-top: var(--space-xs); }
/*
 * This is a manifest file that'll be compiled into application.css, which will include all the files
 * listed below.
 *
 * Any CSS (and SCSS, if configured) file within this directory, lib/assets/stylesheets, or any plugin's
 * vendor/assets/stylesheets directory can be referenced here using a relative path.
 *
 * You're free to add application-wide styles to this file and they'll appear at the bottom of the
 * compiled file so the styles you add here take precedence over styles defined in any other CSS
 * files in this directory. Styles in this file should be added after the last require_* statement.
 * It is generally better to create a new file per style scope.
 *


 */
