# File: validar_estudiante.php (compatible con PHP 8.2)

```php
<?php
// ================================================
// EDUSAC – Portal del Estudiante (PHP 8.2)
// Reestilizado + migración de mysql_* -> mysqli_*
// Mantiene estructura y consultas originales.
// ================================================

// Sesión y cabeceras
session_start();
header('Cache-Control: no-cache, no-store, must-revalidate');
session_cache_limiter('private_no_expire');
header('Content-Type: text/html; charset=UTF-8');

// Includes originales (mantener tal cual)
require_once __DIR__ . '/session.php';
require_once __DIR__ . '/mensajes.php';
require_once __DIR__ . '/conexion.php'; // Debe exponer $conexion (mysqli)

// Asegurar charset
if (isset($conexion) && $conexion instanceof mysqli) {
    // Si tu conexion.php ya lo hace, esto no estorba
    @$conexion->set_charset('utf8mb4');
}

// Utilidades mínimas
function dbq(string $sql) {
    global $conexion;
    $res = mysqli_query($conexion, $sql);
    if (!$res) {
        error_log('SQL Error: ' . mysqli_error($conexion) . ' | ' . $sql);
    }
    return $res;
}
function dbrow($res) { return $res ? mysqli_fetch_assoc($res) : null; }
function h($s) { return htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8'); }

// Normalizador de apellidos (preferencia EDUSAC)
function corregir_apellido(string $texto): string {
    $mapa = [
        'NUNEZ'=>'NUÑEZ','NINO'=>'NIÑO','BELENO'=>'BELEÑO','MUNOZ'=>'MUÑOZ','PENA'=>'PEÑA','CARRENO'=>'CARREÑO',
        'FANDINO'=>'FANDIÑO','ZUNIGA'=>'ZUÑIGA','PENARANDA'=>'PEÑARANDA','PINA'=>'PIÑA','PATINO'=>'PATIÑO',
        'ESCANO'=>'ESCAÑO','LONDONO'=>'LONDOÑO','BOLANOS'=>'BOLAÑOS','BOLANO'=>'BOLAÑO','ONATE'=>'OÑATE',
        'CALENO'=>'CALEÑO','CASTANO'=>'CASTAÑO','CASTANEDA'=>'CASTAÑEDA','CANA'=>'CAÑA','ESPANA'=>'ESPAÑA',
        'PINON'=>'PIÑON','ORDONEZ'=>'ORDOÑEZ','ACUNA'=>'ACUÑA','OCANA'=>'OCAÑA','MALACANAN'=>'MALACAÑAN',
        'PENAQUE'=>'PEÑAQUE','LINAN'=>'LIÑAN'
    ];
    // Reemplazo tolerante a mayúsculas
    foreach ($mapa as $sin => $con) {
        $texto = preg_replace('/\b' . preg_quote($sin, '/') . '\b/i', $con, $texto);
    }
    return $texto;
}

// Seguridad básica de POST para PHP 8.2 (evitar notices)
$_POST = array_map(function($v){ return is_string($v)? trim($v): $v; }, $_POST);

// Validación de sesión
if (!isset($_SESSION['nom_user'])) {
    $mensaje_funcion = 'VUELVA A INGRESAR POR FAVOR, SU SESIÓN SE HA CERRADO';
    mensaje_funcion($mensaje_funcion);
    echo '<center><img src="nubeconrayos.gif" width="191" height="151" /></center>';   
    echo '<script> setTimeout(function(){ window.location="entrar.php"; }, 4000); </script>';
    exit;
}

// Contexto de usuario
$nom_user_x_edusac   = $_SESSION['nom_user'] ?? '';
$clave_user_x_edusac = $_SESSION['clave_user'] ?? '';
$cargo_fun_x_edusac  = $_SESSION['cargo_fun'] ?? '';

// Solo ESTUDIANTE en este panel (manteniendo lógica original)
if ($cargo_fun_x_edusac !== 'ESTUDIANTE') {
    // Opcional: redirigir a inicio si no es estudiante
}

// Cargar datos del estudiante
$id_est = $nombre_comp = $num_id_est = '';
$resEst = dbq("SELECT * FROM estudiantes WHERE clave_user = '".h($clave_user_x_edusac)."' AND nom_user = '".h($nom_user_x_edusac)."'");
if ($resEst && mysqli_num_rows($resEst)) {
    while ($fila = dbrow($resEst)) {
        $id_est      = $fila['id_est'] ?? '';
        $nombre_comp = $fila['nombre_comp'] ?? '';
        $num_id_est  = $fila['num_id_est'] ?? '';
    }
}

// Bloqueo de estudiantes
$bloqueadoOK = false;
$resBlo = dbq("SELECT * FROM bloqueo_estudiantes WHERE bloqueado = 'NO'");
if ($resBlo && mysqli_num_rows($resBlo)) { $bloqueadoOK = true; }

// Año vigente
$ano_actual = date('Y');
$resAno = dbq("SELECT * FROM ano_vigente");
if ($resAno && mysqli_num_rows($resAno)) {
    $regAno = dbrow($resAno);
    if (!empty($regAno['ano'])) $ano_actual = $regAno['ano'];
}

// Parámetros de formulario
$cod_per_vencido = $_POST['cod_per_vencido'] ?? '';

// Escalas (BAJO/BASICO/ALTO/SUPERIOR)
$escala = [];
foreach (['BAJO','BASICO','ALTO','SUPERIOR'] as $nivel) {
    $r = dbq("SELECT * FROM escala WHERE nivel = '".$nivel."'");
    if ($r && ($x = dbrow($r))) {
        $escala[$nivel] = ['de' => $x['de'] ?? 0, 'hasta' => $x['hasta'] ?? 0];
    }
}
$hasta1 = $escala['BAJO']['hasta'] ?? 0;

// Colegio (branding)
$colegioInfo = [
  'colegio'=>'','rector'=>'','direccion'=>'','nombresecretario'=>'','municipio'=>'','reso1'=>'','perfil'=>'','genero'=>'',
  'contenido_cert'=>'','mensaje_cert'=>'','dane'=>'','icfes'=>'','nit'=>'','cedularector'=>'','cedulasecretario'=>'',
  'email'=>'','telefono'=>'','telefono2'=>'','caracter'=>'','abre'=>'','media_secundaria'=>'','adulta'=>'','periodos_colegio'=>4,
  'convencionales'=>'','x'=>'','p'=>'','c'=>'','ciclo26'=>'','pfc'=>'','secreta'=>'','taller6_9'=>'','taller10_11'=>'','humano'=>'',
  'facebook_cont'=>'','web'=>'','webmunicipio'=>'','clavecontenido'=>'','telefono_cont'=>''
];
$resCol = dbq("SELECT upper(colegio) as colegio, upper(rector) as rector, upper(direccion) as direccion, upper(nombresecretario) as nombresecretario, upper(municipio) as municipio, upper(reso1) as reso1, upper(perfil) as perfil, upper(genero) as genero, upper(contenido_cert) as contenido_cert, upper(mensaje_cert) as mensaje_cert, dane, icfes, nit, cedularector, cedulasecretario, email, telefono, telefono2, caracter, abre, upper(media_secundaria) as media_secundaria, upper(adulta) as adulta, periodos_colegio, convencionales, x, p, c, ciclo26, pfc, secreta, taller6_9, taller10_11, humano, facebook_cont, web, webmunicipio, clavecontenido, telefono_cont FROM colegio LIMIT 1");
if ($resCol && ($rc = dbrow($resCol))) {
    $colegioInfo = array_merge($colegioInfo, $rc);
}
$periodos_colegio = (int)($colegioInfo['periodos_colegio'] ?? 4);

// Periodos activos por nivel (Primaria, Secundaria, Adultos) – reproducimos tu lógica
$activo0 = $activo1 = $activo2 = '';
if ($r1 = dbq("SELECT * FROM cerrarperiodo WHERE cerrado = 'ABIERTO' AND dif = '0'")) { $x=dbrow($r1); $nom_per10 = $x['nom_per'] ?? ''; }
if ($r1b= dbq("SELECT * FROM periodo WHERE est_per = 'ACTIVO' AND dif = '0'")) { $x=dbrow($r1b); $nom_per20 = $x['nom_per'] ?? ''; $cod_per20 = $x['cod_per'] ?? ''; }
if (!empty($nom_per10) && $nom_per10 === ($nom_per20 ?? '')) { $activo0 = $nom_per10; }

if ($r2 = dbq("SELECT * FROM cerrarperiodo WHERE cerrado = 'ABIERTO' AND dif = '1'")) { $x=dbrow($r2); $nom_per11 = $x['nom_per'] ?? ''; }
if ($r2b= dbq("SELECT * FROM periodo WHERE est_per = 'ACTIVO' AND dif = '1'")) { $x=dbrow($r2b); $nom_per21 = $x['nom_per'] ?? ''; $cod_per21 = $x['cod_per'] ?? ''; }
if (!empty($nom_per11) && $nom_per11 === ($nom_per21 ?? '')) { $activo1 = $nom_per11; }

if ($r3 = dbq("SELECT * FROM cerrarperiodo WHERE cerrado = 'ABIERTO' AND dif = '2'")) { $x=dbrow($r3); $nom_per12 = $x['nom_per'] ?? ''; }
if ($r3b= dbq("SELECT * FROM periodo WHERE est_per = 'ACTIVO' AND dif = '2'")) { $x=dbrow($r3b); $nom_per22 = $x['nom_per'] ?? ''; $cod_per22 = $x['cod_per'] ?? ''; }
if (!empty($nom_per12) && $nom_per12 === ($nom_per22 ?? '')) { $activo2 = $nom_per12; }

// Matricula más reciente del estudiante
$cod_mat = $grado_mat = $sede_mat = $ano_mat_estudio = $est_mat = $retirado = '';
$resUlt = dbq("SELECT MAX(matriculas.cod_mat) AS cod_mat FROM matriculas JOIN estudiantes ON estudiantes.id_est = matriculas.id_est WHERE estudiantes.id_est = '".h($id_est)."'");
if ($resUlt && ($r = dbrow($resUlt)) && !empty($r['cod_mat'])) {
    $cod_mat = $r['cod_mat'];
    $resM = dbq("SELECT * FROM matriculas WHERE cod_mat = '".h($cod_mat)."' LIMIT 1");
    if ($resM && ($m = dbrow($resM))) {
        $grado_mat = $m['grado_mat'] ?? '';
        $sede_mat  = $m['sede_mat'] ?? '';
        $ano_mat_estudio = $m['ano_mat'] ?? '';
        $est_mat = $m['est_mat'] ?? '';
        $retirado = $m['retirado'] ?? '';
    }
}

// Determinar $cod_per por nivel según grado (reproduce tu regla)
$cod_per = '';
if ($grado_mat >= '0001' && $grado_mat <= '0510') { $cod_per = $activo0; }
if ($grado_mat >= '0601' && $grado_mat <= '1110') { $cod_per = $activo1; }
if ($grado_mat >= '1201' && $grado_mat <= '1303') { $cod_per = $activo2; }
if ($grado_mat >= 'CICLO 2101' && $grado_mat <= 'CICLO 2505') { $cod_per = $activo2; }
// Para la vista de detalle si no se eligió periodo manual, usamos el activo
if ($cod_per_vencido === '') { $cod_per_vencido = $cod_per ?: '1'; }

// Estado de bloqueo de boletines
$bloqueo_b = 'NO';
if ($rB = dbq("SELECT * FROM bloqueo_boletines_estudiantes WHERE codigo = '1'")) {
    while ($rb = dbrow($rB)) { $bloqueo_b = $rb['bloqueo'] ?? 'NO'; }
}

?>
<!DOCTYPE html>
<html lang="es">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <title>EDUSAC | Portal del Estudiante</title>
  <link rel="stylesheet" href="assets/edusac.css?v=1">
  <link rel="shortcut icon" href="icono.ico">
  <script defer src="assets/edusac.js?v=1"></script>
</head>
<body class="theme-edusac">
  <header class="topbar">
    <div class="brand">
      <img src="escudo.png" alt="Institución" class="brand__logo">
      <div class="brand__text">
        <strong><?php echo h($colegioInfo['colegio']); ?></strong>
        <small>Portal del Estudiante</small>
      </div>
    </div>
    <div class="topbar__user">
      <span class="chip"><?php echo h($nom_user_x_edusac); ?></span>
      <a class="btn btn--ghost" href="salir.php" title="Cerrar sesión">Salir</a>
    </div>
  </header>
  <div class="layout">
    <aside class="sidebar">
      <nav class="menu">
        <a class="menu__item" href="#resumen">Resumen</a>
        <a class="menu__item" href="#boletines">Boletines</a>
        <a class="menu__item" href="#calificaciones">Calificaciones</a>
        <a class="menu__item" href="#comportamiento">Comportamiento</a>
        <a class="menu__item" href="#observador">Observador</a>
        <a class="menu__item" href="#recuperaciones">Recuperaciones</a>
        <a class="menu__item" href="#planes">Planes de mejoramiento</a>
        <a class="menu__item" href="#reuniones">Reuniones</a>
      </nav>
    </aside>
    <main class="content">

      <!-- =================== RESUMEN =================== -->
      <a id="resumen"></a>
      <section class="card card--soft">
        <div class="stat">
          <div class="stat__item">
            <span class="muted">Usuario</span>
            <b><?php echo h($nom_user_x_edusac); ?></b>
            <small><?php echo h(corregir_apellido($nombre_comp)); ?></small>
          </div>
          <div class="stat__item">
            <span class="muted">Documento</span>
            <b><?php echo h($num_id_est); ?></b>
          </div>
          <div class="stat__item">
            <span class="muted">Perfil</span>
            <b><?php echo h($cargo_fun_x_edusac . ' / ' . $sede_mat); ?></b>
          </div>
          <div class="stat__item">
            <span class="muted">Periodo activo</span>
            <b><?php echo h($cod_per ?: '—'); ?></b>
          </div>
        </div>
        <hr>
        <div class="stat">
          <div class="stat__item">
            <span class="muted">Institución</span>
            <b><?php echo h($colegioInfo['colegio']); ?></b>
            <small class="muted">DANE: <?php echo h($colegioInfo['dane']); ?> · ICFES: <?php echo h($colegioInfo['icfes']); ?> · NIT: <?php echo h($colegioInfo['nit']); ?></small>
          </div>
          <div class="stat__item">
            <span class="muted">Resolución</span>
            <b><?php echo h($colegioInfo['reso1']); ?></b>
          </div>
        </div>
      </section>

      <!-- =================== BOLETINES =================== -->
      <a id="boletines"></a>
      <section class="card">
        <h2>Boletines</h2>
        <p class="muted">Genere su informe/boletín por periodo.</p>
        <div class="table-wrap">
          <table>
            <tr>
              <td>
                <form action="mediador_est2.php" method="post" target="_blank">
                  <label><strong>Periodo:</strong></label>
                  <select name="cod_per">
                    <option>1</option>
                    <option>2</option>
                    <option>3</option>
                    <?php if ($periodos_colegio === 4): ?><option>4</option><?php endif; ?>
                  </select>
                  <label><strong>Formato:</strong></label>
                  <select name="bloque"><option>NORMAL</option><option>REDUCIDO</option></select>
                  <?php if ($bloqueo_b === 'SI'): ?>
                    <span class="badge" style="background:#fee2e2;color:#991b1b">BLOQUEADO DESDE RECTORIA</span>
                  <?php else: ?>
                    <input type="image" name="--" src="ir_arriba.png" title="Generar boletín" />
                  <?php endif; ?>
                  <input type="hidden" name="cod_mat" value="<?php echo h($cod_mat); ?>" />
                  <input type="hidden" name="btnboletin" value="1" />
                  <input type="hidden" name="ano_actual" value="<?php echo h($ano_actual); ?>" />
                  <input type="hidden" name="id_est" value="<?php echo h($id_est); ?>" />
                  <input type="hidden" name="grado_mat" value="<?php echo h($grado_mat); ?>" />
                  <input type="hidden" name="materia" value="<?php echo h($_POST['materia'] ?? ''); ?>" />
                  <input type="hidden" name="sede_mat" value="<?php echo h($sede_mat); ?>" />
                </form>
                <small class="muted">La visualización del informe final está sujeta a la autorización de la Institución Educativa.</small>
              </td>
            </tr>
          </table>
        </div>
      </section>

      <!-- =================== CALIFICACIONES =================== -->
      <a id="calificaciones"></a>
      <section class="card">
        <h2>Calificaciones del periodo</h2>
        <form action="" method="post" style="margin-bottom:10px">
          <label>Seleccione periodo:&nbsp;</label>
          <select name="cod_per_vencido">
            <option value="">—</option>
            <option<?php if($cod_per_vencido==='1') echo ' selected'; ?>>1</option>
            <option<?php if($cod_per_vencido==='2') echo ' selected'; ?>>2</option>
            <option<?php if($cod_per_vencido==='3') echo ' selected'; ?>>3</option>
            <?php if ($periodos_colegio === 4): ?><option<?php if($cod_per_vencido==='4') echo ' selected'; ?>>4</option><?php endif; ?>
          </select>
          <button class="btn" type="submit">Cambiar</button>
        </form>

        <h3>Detalle de calificaciones del periodo: <span class="badge"><?php echo h($cod_per_vencido); ?></span></h3>
        <div class="table-wrap">
          <table>
            <thead>
              <tr>
                <th>No</th>
                <th>Asignatura</th>
                <th colspan="4">Cognitivo 40%</th>
                <th colspan="5">Procedimental 40%</th>
                <th colspan="3">Actitudinal 15%</th>
                <th colspan="3">Comportamental 5%</th>
                <th>DEF</th>
              </tr>
              <tr>
                <th></th><th></th>
                <th>COG-1</th><th>COG-2</th><th>COG-3</th><th>COG-4</th>
                <th>PRO-1</th><th>PRO-2</th><th>PRO-3</th><th>PRO-4</th><th>PRO-5</th>
                <th>ACT-1</th><th>ACT-2</th><th>ACT-3</th>
                <th>ASI-1</th><th>ASI-2</th><th>ASI-3</th>
                <th>Prom</th>
              </tr>
            </thead>
            <tbody>
            <?php
            $n = 1;
            $materiasRes = dbq("SELECT DISTINCT cod_materia FROM notas WHERE cod_mat = '".h($cod_mat)."'");
            if ($materiasRes) {
              while ($mx = dbrow($materiasRes)) {
                $cod_materia = $mx['cod_materia'];
                $resNotas = dbq("SELECT * FROM notas WHERE cod_mat='".h($cod_mat)."' AND cod_per='".h($cod_per_vencido)."' AND cod_materia='".h($cod_materia)."' LIMIT 1");
                $nota = dbrow($resNotas) ?: [];

                // Recuperación para ese periodo/materia (si existe) – afecta pmg mostrado
                $pmg = $nota['pmg'] ?? '';
                $rec = dbq("SELECT * FROM recuperaciones WHERE cod_per='".h($cod_per_vencido)."' AND cod_materia='".h($cod_materia)."' AND cod_mat='".h($cod_mat)."' LIMIT 1");
                if ($rec && ($rx = dbrow($rec))) { $pmg = $rx['nota']; }

                // Nombre de materia
                $nom_materia = '';
                $mInfo = dbq("SELECT * FROM materias WHERE cod_materia='".h($cod_materia)."' AND ano='".h($ano_actual)."' ORDER BY nom_materia LIMIT 1");
                if ($mInfo && ($mm = dbrow($mInfo))) { $nom_materia = $mm['nom_materia']; }
                ?>
                <tr>
                  <td style="text-align:center;"><?php echo $n++; ?></td>
                  <td><b><?php echo h($nom_materia); ?></b></td>
                  <td style="text-align:center;"><?php echo h($nota['nota1'] ?? ''); ?></td>
                  <td style="text-align:center;"><?php echo h($nota['nota2'] ?? ''); ?></td>
                  <td style="text-align:center;"><?php echo h($nota['nota3'] ?? ''); ?></td>
                  <td style="text-align:center;"><?php echo h($nota['nota4'] ?? ''); ?></td>
                  <td style="text-align:center;"><?php echo h($nota['nota5'] ?? ''); ?></td>
                  <td style="text-align:center;"><?php echo h($nota['nota6'] ?? ''); ?></td>
                  <td style="text-align:center;"><?php echo h($nota['nota7'] ?? ''); ?></td>
                  <td style="text-align:center;"><?php echo h($nota['nota8'] ?? ''); ?></td>
                  <td style="text-align:center;"><?php echo h($nota['nota9'] ?? ''); ?></td>
                  <td style="text-align:center;"><?php echo h($nota['nota10'] ?? ''); ?></td>
                  <td style="text-align:center;"><?php echo h($nota['nota11'] ?? ''); ?></td>
                  <td style="text-align:center;"><?php echo h($nota['nota12'] ?? ''); ?></td>
                  <td style="text-align:center;"><?php echo h($nota['nota13'] ?? ''); ?></td>
                  <td style="text-align:center;"><?php echo h($nota['nota14'] ?? ''); ?></td>
                  <td style="text-align:center;"><?php echo h($nota['nota15'] ?? ''); ?></td>
                  <td style="text-align:center;">
                    <?php if (is_numeric($pmg) && $pmg < $hasta1): ?>
                      <font color="#FF0000"><b><?php echo h($pmg); ?></b></font>
                    <?php else: ?>
                      <font color="#009900"><b><?php echo h($pmg); ?></b></font>
                    <?php endif; ?>
                  </td>
                </tr>
                <?php
              }
            }
            ?>
            </tbody>
          </table>
        </div>
      </section>

      <!-- =================== COMPORTAMIENTO =================== -->
      <a id="comportamiento"></a>
      <section class="card">
        <h2>Calificaciones de comportamiento</h2>
        <div class="table-wrap">
          <table>
            <thead><tr><th>Periodo</th><th>Nota</th></tr></thead>
            <tbody>
            <?php
            $queryComp = ($periodos_colegio === 4)
              ? "SELECT * FROM comportamiento WHERE cod_mat='".h($cod_mat)."' ORDER BY cod_per"
              : "SELECT * FROM comportamiento WHERE cod_mat='".h($cod_mat)."' AND cod_per < '4' ORDER BY cod_per";
            $resComp = dbq($queryComp);
            if ($resComp && mysqli_num_rows($resComp)) {
              while ($c = dbrow($resComp)) {
                $notaC = $c['nota'] ?? '';
                ?>
                <tr>
                  <td style="text-align:center;"><?php echo h($c['cod_per'] ?? ''); ?></td>
                  <td style="text-align:center;">
                    <?php if (is_numeric($notaC) && $notaC < $hasta1): ?>
                      <span style="color:#ef4444;font-weight:700;"><?php echo h($notaC); ?></span>
                    <?php else: echo h($notaC); endif; ?>
                  </td>
                </tr>
                <?php
              }
            }
            ?>
            </tbody>
          </table>
        </div>
      </section>

      <!-- =================== DATOS PERSONALES =================== -->
      <section class="card">
        <h2>Datos personales</h2>
        <p class="muted">Actualice su información personal.</p>
        <form action="observador_almacenamiento_estudiante.php" method="post" target="_blank">
          <input type="image" name="btnobservador" src="datos-personales.png" width="80" height="80" title="Editar datos personales" />
          <input type="hidden" name="id_est" value="<?php echo h($id_est); ?>" />
          <input type="hidden" name="cod_mat" value="<?php echo h($cod_mat); ?>" />
          <input type="hidden" name="cod_per" value="<?php echo h($cod_per); ?>" />
          <input type="hidden" name="materia" value="<?php echo h($_POST['materia'] ?? ''); ?>" />
          <input type="hidden" name="sede" value="<?php echo h($sede_mat); ?>" />
          <input type="hidden" name="sw" value="2" />
        </form>
      </section>

      <!-- =================== OBSERVADOR (Estructurado) =================== -->
      <a id="observador"></a>
      <section class="card">
        <h2>1 — Observaciones del Director de Grupo</h2>
        <div class="table-wrap">
          <table>
            <thead><tr><th>Periodo</th><th>Detalle</th></tr></thead>
            <tbody>
              <?php
              $rObs = dbq("SELECT * FROM reporte_disciplinario WHERE cod_mat='".h($cod_mat)."' AND verbal='NO' AND periodo_estructura='SI' ORDER BY periodo");
              if ($rObs && mysqli_num_rows($rObs)) {
                while ($o = dbrow($rObs)) {
                  echo '<tr><td style="text-align:center;">'.h($o['periodo']).'</td><td>'.(strlen($o['reporte'])? h($o['reporte']) : 'No hay información').'</td></tr>';
                }
              } else {
                echo '<tr><td colspan="2" style="text-align:center;color:#ef4444;">NINGUNA INFORMACIÓN EN EL OBSERVADOR AÚN</td></tr>';
              }
              ?>
            </tbody>
          </table>
        </div>
      </section>

      <!-- =================== REPORTES DISCIPLINARIOS ESCRITOS =================== -->
      <section class="card">
        <h2>2 — Reportes disciplinarios escritos</h2>
        <div class="table-wrap">
          <table>
            <thead><tr><th>Periodo</th><th>Detalle</th><th>¿Llevado al OBS?</th></tr></thead>
            <tbody>
              <?php
              $rRep = dbq("SELECT * FROM reporte_disciplinario WHERE id_est='".h($id_est)."' AND verbal='NO' AND (periodo_estructura='' OR periodo_estructura IS NULL) ORDER BY periodo");
              if ($rRep && mysqli_num_rows($rRep)) {
                while ($r = dbrow($rRep)) {
                  echo '<tr><td style="text-align:center;">'.h($r['periodo']).'</td><td>'.h($r['reporte']).'</td><td style="text-align:center;"><b>'.h($r['llevado']).'</b></td></tr>';
                }
              } else {
                echo '<tr><td colspan="3" style="text-align:center;color:#ef4444;">NINGÚN REPORTE ESCRITO</td></tr>';
              }
              ?>
            </tbody>
          </table>
        </div>
      </section>

      <!-- =================== REPORTES DISCIPLINARIOS VERBALES =================== -->
      <section class="card">
        <h2>3 — Reportes disciplinarios verbales</h2>
        <div class="table-wrap">
          <table>
            <thead><tr><th>Periodo</th><th>Detalle</th><th>¿Llevado al OBS?</th></tr></thead>
            <tbody>
              <?php
              $rVer = dbq("SELECT * FROM reporte_disciplinario WHERE id_est='".h($id_est)."' AND verbal='SI' ORDER BY periodo");
              if ($rVer && mysqli_num_rows($rVer)) {
                while ($v = dbrow($rVer)) {
                  echo '<tr><td style="text-align:center;">'.h($v['periodo']).'</td><td>'.h($v['reporte']).'</td><td style="text-align:center;"><b>'.h($v['llevado']).'</b></td></tr>';
                }
              } else {
                echo '<tr><td colspan="3" style="text-align:center;color:#ef4444;">NINGÚN REPORTE VERBAL</td></tr>';
              }
              ?>
            </tbody>
          </table>
        </div>
      </section>

      <!-- =================== RECUPERACIONES =================== -->
      <a id="recuperaciones"></a>
      <section class="card">
        <h2>4 — Recuperaciones por periodo (Último año: <?php echo h($ano_mat_estudio); ?>)</h2>
        <div class="table-wrap">
          <table>
            <thead><tr><th>Asignatura</th><th>Periodo</th><th>Nota nueva</th><th>Nota anterior</th><th>Fecha</th></tr></thead>
            <tbody>
              <?php
              $rRec = dbq("SELECT * FROM recuperaciones WHERE cod_mat='".h($cod_mat)."' ORDER BY cod_rec");
              if ($rRec && mysqli_num_rows($rRec)) {
                while ($rr = dbrow($rRec)) {
                  $nom_materia = '';
                  $mm = dbq("SELECT * FROM materias WHERE cod_materia='".h($rr['cod_materia'])."' AND ano='".h($ano_mat_estudio)."' ORDER BY nom_materia LIMIT 1");
                  if ($mm && ($m2=dbrow($mm))) { $nom_materia = $m2['nom_materia']; }
                  echo '<tr>';
                  echo '<td style="text-align:center;">'.h($nom_materia). ' / ' . h($rr['cod_materia']).'</td>';
                  echo '<td style="text-align:center;">'.h($rr['cod_per']).'</td>';
                  echo '<td style="text-align:center;"><b>*'.h($rr['nota']).'</b></td>';
                  echo '<td style="text-align:center;">'.h($rr['notav']).'</td>';
                  echo '<td style="text-align:center;">'.h($rr['fecha']).'</td>';
                  echo '</tr>';
                }
              } else {
                echo '<tr><td colspan="5" style="text-align:center;color:#ef4444;">NO POSEE RECUPERACIONES POR PERIODO</td></tr>';
              }
              ?>
            </tbody>
          </table>
        </div>
      </section>

      <!-- =================== PLANES DE MEJORAMIENTO =================== -->
      <a id="planes"></a>
      <section class="card">
        <h2>Relación de Planes de Mejoramiento</h2>
        <div class="table-wrap">
          <table>
            <thead>
              <tr>
                <th>#</th><th>Periodo</th><th>Asignatura</th><th>Dificultad detectada</th><th>Plan de mejoramiento</th><th>Observaciones</th><th>Anexo</th>
              </tr>
            </thead>
            <tbody>
              <?php
              $rSeg = dbq("SELECT * FROM seguimiento WHERE cod_mat='".h($cod_mat)."' ORDER BY cod_per, cod_materia");
              if ($rSeg && mysqli_num_rows($rSeg)) {
                $f=0;
                while ($sg = dbrow($rSeg)) {
                  $f++;
                  $mm = dbq("SELECT * FROM materias WHERE cod_materia='".h($sg['cod_materia'])."' LIMIT 1");
                  $nom_materia = ($mm && ($m=dbrow($mm))) ? $m['nom_materia'] : '';
                  echo '<tr>';
                  echo '<td style="text-align:center;"><b>'.$f.'</b></td>';
                  echo '<td style="text-align:center;"><b><font size="+2">'.h($sg['cod_per']).'</font></b></td>';
                  echo '<td style="text-align:center;"><b>'.h($nom_materia).'</b></td>';
                  echo '<td><textarea cols="30" rows="3" readonly>'.h($sg['dificultad']).'</textarea></td>';
                  echo '<td><textarea cols="30" rows="3" readonly>'.h($sg['plan']).'</textarea></td>';
                  echo '<td><textarea cols="30" rows="3" readonly>'.h($sg['observacion']).'</textarea></td>';
                  echo '<td style="text-align:center;">';
                  if (!empty($sg['archivo'])) {
                    echo '<div style="background:#FFF">Fecha última actualización<br>'.h($sg['fecha_archivo']).'<br>';
                    echo '<img src="aprobado.png" width="44" height="40">';
                    echo '<a href="'.h($sg['archivo']).'" target="_blank"><span style="color:#16a34a;font-weight:700">DESCARGAR FORMATO ALIMENTADO</span></a></div>';
                  }
                  echo '</td>';
                  echo '</tr>';
                }
              }
              ?>
            </tbody>
          </table>
        </div>
      </section>

      <!-- =================== REUNIONES =================== -->
      <a id="reuniones"></a>
      <section class="card">
        <h2>Reuniones virtuales</h2>
        <div class="table-wrap">
          <table>
            <thead>
              <tr><th>#</th><th>Funcionario creador</th><th>Tema</th><th>Fecha y hora</th><th>Acción</th></tr>
            </thead>
            <tbody>
            <?php
            $icc=0;
            $resL = dbq("SELECT * FROM link_reunion WHERE id_receptor='".h($id_est)."' ORDER BY fecha_reunion, hora_creacion");
            if ($resL && mysqli_num_rows($resL)) {
              while ($lr = dbrow($resL)) {
                $icc++;
                $nombre_docente_creador = '';
                $rf = dbq("SELECT * FROM funcionario WHERE id_fun='".h($lr['id_creador'])."'");
                if ($rf && ($ff=dbrow($rf))) { $nombre_docente_creador = $ff['nombre_comp']; }

                $fecha_reunion = $lr['fecha_reunion'];
                $hora_reunion  = $lr['hora_reunion'];
                $link          = $lr['link'];

                // Ventana de activación: 5 minutos antes hasta 60 minutos después
                $horaActual = date('H:i');
                $hIni = date('H:i', strtotime($hora_reunion) - 5*60);
                $hFin = date('H:i', strtotime($hora_reunion) + 60*60);
                $activado = (date('Y-m-d') === $fecha_reunion) && ($horaActual >= $hIni) && ($horaActual <= $hFin);
                echo '<tr>';
                echo '<td style="text-align:center">'.$icc.'</td>';
                echo '<td style="text-align:center">'.h($nombre_docente_creador).'</td>';
                echo '<td><textarea cols="50" readonly>'.h($lr['tema']).'</textarea></td>';
                echo '<td style="text-align:center">'.h($fecha_reunion).' '.h($hora_reunion).'</td>';
                echo '<td style="text-align:center">';
                if ($fecha_reunion > date('Y-m-d') || !$activado) {
                  echo '<span class="muted">Link se activará 5 minutos antes ('.h($hIni).')</span>';
                } else {
                  echo '<form action="antesala.php" method="post" target="_blank">';
                  echo '<input type="image" src="ir.png" width="60" height="60" title="IR A CLASE VIRTUAL" />';
                  echo '<input type="hidden" name="codigo" value="'.h($lr['codigo']).'" />';
                  echo '<input type="hidden" name="link" value="'.h($link).'" />';
                  echo '<input type="hidden" name="id_receptor" value="'.h($id_est).'" />';
                  echo '<input type="hidden" name="id_creador" value="'.h($lr['id_creador']).'" />';
                  echo '</form>';
                }
                echo '</td>';
                echo '</tr>';
              }
            }
            ?>
            </tbody>
          </table>
        </div>
      </section>

      <section class="card" style="text-align:center;color:#37AFDB">
        <?php $ano = date('Y'); ?>
        <b>PLATAFORMA EDUSAC</b><br><?php echo h($ano); ?>
        <div style="margin-top:10px"><img src="6db6b73658d06c5c0fd6ec2ff3fb0a238bd2f0b8_00.gif" width="120" height="156" alt=""></div>
      </section>

    </main>
  </div>
</body>
</html>
```

---

# File: assets/edusac.css

```css
/* EDUSAC – UI corporativa para validar_estudiante.php (PHP 8.2 friendly) */
:root{
  --bg:#f8fafc; --panel:#ffffff; --text:#0f172a; --muted:#64748b;
  --primary:#0ea5e9; --primary-600:#0284c7; --ring:rgba(14,165,233,.25);
  --success:#16a34a; --danger:#ef4444; --warning:#f59e0b; --card:#ffffff;
  --border:#e2e8f0; --shadow:0 8px 20px rgba(2,8,23,.06);
  --radius:16px;
  /* Anchos fijos para sticky cols en calificaciones */
  --w-no:56px; --w-asig:320px;
}
/* Variables para modo oscuro */
.theme-dark{
  --bg:#0b1220; --panel:#0f172a; --card:#0f172a;
  --text:#e5e7eb; --muted:#9ca3af; --border:#172033; --shadow:0 8px 20px rgba(0,0,0,.35);
}
.theme-dark .topbar{box-shadow:none}
*{box-sizing:border-box}
html,body{height:100%}
body.theme-edusac{
  margin:0; font:14px/1.5 system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,"Helvetica Neue",Arial;
  color:var(--text); background:var(--bg);
}
/* Modo oscuro corporativo */
.theme-dark{
  --bg:#0b1220; --panel:#0f172a; --card:#0f172a; --text:#e5e7eb; --muted:#9ca3af;
  --border:#172033; --shadow:0 8px 20px rgba(0,0,0,.35);
}
.theme-dark .topbar{box-shadow:none}
.theme-dark table{background:var(--card)}
.theme-dark table thead th{background:#0b2434}
.theme-dark .sidebar{background:var(--panel)}
.theme-dark .notice{border-color:#1d2a3c;background:#0f1a29}
.theme-dark .stat__item{background:#0b1626;border-color:#152039}
/* Topbar */
.topbar{position:sticky;top:0;z-index:50;display:flex;align-items:center;justify-content:space-between;
  padding:12px 20px;background:linear-gradient(90deg,var(--primary),#22d3ee);color:#fff;box-shadow:var(--shadow)}
.brand{display:flex;gap:12px;align-items:center}
.brand__logo{width:36px;height:36px;border-radius:8px;object-fit:contain;background:#fff;padding:4px}
.brand__text small{display:block;opacity:.9;font-weight:500}
.topbar__user{display:flex;align-items:center;gap:10px}
.chip{display:inline-block;padding:6px 10px;border-radius:999px;background:rgba(255,255,255,.2);backdrop-filter:saturate(1.2);font-weight:600}
.btn{display:inline-flex;align-items:center;justify-content:center;padding:8px 12px;border-radius:10px;border:1px solid transparent;text-decoration:none;cursor:pointer;transition:.2s}
.btn--ghost{color:#fff;border-color:rgba(255,255,255,.35)}
.btn--ghost:hover{background:rgba(255,255,255,.15)}

/* Botón de tema */
.theme-toggle{
  width:36px;height:36px;border-radius:10px;border:1px solid rgba(255,255,255,.35);
  background:transparent;color:#fff;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;
}
.theme-toggle:hover{background:rgba(255,255,255,.15)}
.theme-toggle .moon{display:none}
.theme-dark .theme-toggle{border-color:#334155}
.theme-dark .theme-toggle .sun{display:none}
.theme-dark .theme-toggle .moon{display:inline}

/* Layout */
.layout{display:grid;grid-template-columns:260px 1fr;gap:22px;max-width:1400px;margin:22px auto;padding:0 22px}
.sidebar{background:var(--panel);border:1px solid var(--border);border-radius:var(--radius);box-shadow:var(--shadow);height:max-content;position:sticky;top:76px}
.menu{display:flex;flex-direction:column;padding:10px}
.menu__item{padding:10px 12px;border-radius:10px;color:var(--text);text-decoration:none;font-weight:600}
.menu__item:hover{background:#e6f9ff}
.content{min-width:0}

/* Cards */
.card{background:var(--card);border:1px solid var(--border);border-radius:var(--radius);box-shadow:var(--shadow);padding:18px;margin-bottom:22px}
.card--soft{background:linear-gradient(180deg,#ffffff,#fafdff)}

/* Tipografía utilitaria */
h1,h2,h3{margin:0 0 10px 0}
.muted{color:var(--muted)}
.badge{display:inline-block;padding:3px 8px;border-radius:999px;background:#e0f2fe;color:#075985;font-weight:700}
.stat{display:flex;flex-wrap:wrap;gap:16px}
.stat__item{min-width:160px;padding:10px 12px;border:1px dashed var(--border);border-radius:12px;background:#fcfeff}
.stat__item b{display:block;font-size:18px}

/* Tablas – aplica estilo sin tocar HTML existente */
.table-wrap{overflow:auto}
/* Notices */
.notice{border:1px solid var(--border);border-radius:12px;padding:10px 12px;margin:10px 0;font-weight:600}
.notice--ok{background:#ecfdf5;color:#065f46;border-color:#a7f3d0}
.notice--warn{background:#fff7ed;color:#9a3412;border-color:#fed7aa}

/* Leyenda de escalas */
.scale-legend{display:flex;gap:14px;align-items:center;margin:10px 0 16px 0;flex-wrap:wrap}
.legend-item{display:flex;gap:8px;align-items:center;padding:6px 10px;border:1px solid var(--border);border-radius:999px;background:#fff}
.swatch{width:10px;height:10px;border-radius:2px;display:inline-block}
.swatch--danger{background:var(--danger)}
.swatch--warning{background:var(--warning)}
.swatch--ok{background:var(--success)}
.swatch--info{background:#0284c7}
.theme-dark .legend-item{background:#0b1626}

/* Tablas – aplica estilo sin tocar HTML existente */
.table-wrap{overflow:auto;max-height:70vh;position:relative}
table{width:100%;border-collapse:separate;border-spacing:0;background:#fff;border:1px solid var(--border);border-radius:12px;overflow:hidden}
table th, table td{padding:10px 12px;border-bottom:1px solid var(--border);vertical-align:middle}
table tr:last-child td{border-bottom:0}
table thead th{background:#f1f5f9;text-transform:uppercase;font-size:12px;letter-spacing:.03em;color:#334155;position:sticky;top:0;z-index:2;box-shadow:0 1px 0 var(--border)}
table tbody tr:hover{background:#f8feff}

/* Tabla de calificaciones: sticky cols + anchos */
.table--grades th.sticky-col,
.table--grades td:first-child{position:sticky;left:0;z-index:3;background:inherit;min-width:var(--w-no);width:var(--w-no);text-align:center}
.table--grades th.sticky-col-2,
.table--grades td:nth-child(2){position:sticky;left:var(--w-no);z-index:3;background:inherit;min-width:var(--w-asig);width:var(--w-asig)}
.table--grades th.sticky-col,
.table--grades th.sticky-col-2{box-shadow:1px 0 0 var(--border)}

/* Fila de grupos (encabezado superior) */
.group-row th.group{font-size:12px;color:#0f172a}
.g-crit{background:linear-gradient(90deg,#e0f2fe,#ecfeff)}
.g-def{background:#e5f9f2}
.theme-dark .g-crit{background:linear-gradient(90deg,#0f2a3b,#0b2233);color:#dbeafe}
.theme-dark .g-def{background:#0e2a20;color:#c7f0e5}


/* Tabla de calificaciones – mejoras corporativas */
.table--grades thead .group-row th{
  background:linear-gradient(90deg,#e0f2fe,#f7fee7);
  font-weight:800; color:#0f172a; border-bottom:2px solid #cffafe
}
.group{border-left:1px solid #bae6fd}
.g-crit{background:linear-gradient(90deg,#e0f2fe,#e9d5ff)}
.g-def{background:#dcfce7}
.sticky-col{position:sticky;left:0;background:#ffffff;z-index:1}
.sticky-col-2{position:sticky;left:120px;background:#ffffff;z-index:1}
.table--grades th[title]{cursor:help}

/* Avisos */
.notice{padding:10px 12px;border-radius:10px;margin:10px 0;border:1px solid var(--border)}
.notice--ok{background:#ecfeff;border-color:#a5f3fc;color:#075985}
.notice--warn{background:#fff7ed;border-color:#fed7aa;color:#9a3412}


/* Tabla de calificaciones (más corporativa) */
.table--grades thead .group-row th.group{
  background: linear-gradient(180deg, rgba(14,165,233,.12), rgba(14,165,233,.06));
  color:#075985; font-weight:800; letter-spacing:.02em; border-bottom:1px solid var(--border);
}
.table--grades thead th{ white-space:nowrap }
.table--grades .sticky-col   { position:sticky; left:0;  background:#fff; z-index:1 }
.table--grades .sticky-col-2 { position:sticky; left:52px; background:#fff; z-index:1 }
.table--grades td:first-child,
.table--grades th.sticky-col { text-align:center; width:52px }
.table--grades td:nth-child(2),
.table--grades th.sticky-col-2 { min-width:220px }
.table--grades .g-cog{ border-left:2px solid #bae6fd }
.table--grades .g-pro{ border-left:2px solid #a7f3d0 }
.table--grades .g-act{ border-left:2px solid #fde68a }
.table--grades .g-asi{ border-left:2px solid #fecaca }
.table--grades .g-def{ border-left:2px solid #e5e7eb; background:#f8fafc }

/* Avisos informativos */
.notice{
  margin:8px 0 14px; padding:10px 12px; border-radius:10px; border:1px solid var(--border);
  background:#fff; display:flex; gap:8px; align-items:center; box-shadow:0 1px 2px rgba(2,8,23,.03)
}
.notice--warn{ border-color:#fde68a; background:#fffbeb; color:#92400e }
.notice--ok{ border-color:#bbf7d0; background:#f0fdf4; color:#065f46 }


/* Estados de notas con colores (aplica sin cambiar PHP) */
td font[color="#FF0000"]{color:var(--danger)!important;font-weight:700}
td font[color="#009900"]{color:var(--success)!important;font-weight:700}

/* Inputs e imágenes usadas como botones */
input[type="image"]{filter:drop-shadow(0 4px 12px rgba(2,8,23,.15));transition:.2s}
input[type="image"]:hover{transform:translateY(-2px)}

/* Responsivo */
@media (max-width: 1024px){ .layout{grid-template-columns:1fr} .sidebar{position:static} }
@media (max-width: 640px){
  .topbar{flex-direction:column;align-items:flex-start;gap:8px}
  .chip{font-size:12px}
  table th, table td{padding:8px}
  :root{ --w-asig:230px; }
}
```

---

# File: assets/edusac.js

```js
// EDUSAC – mejoras progresivas para validar_estudiante.php
(function () {
  // Scroll suave para el menú lateral
  document.addEventListener('click', (e) => {
    const a = e.target.closest('a.menu__item[href^="#"]');
    if (!a) return;
    const id = a.getAttribute('href');
    const el = document.querySelector(id);
    if (el) {
      e.preventDefault();
      window.scrollTo({ top: el.getBoundingClientRect().top + window.scrollY - 70, behavior: 'smooth' });
    }
  });

  // Envuelve tablas existentes en contenedores con overflow
  Array.from(document.querySelectorAll('table')).forEach((t) => {
    if (t.closest('.table-wrap')) return;
    const wrap = document.createElement('div');
    wrap.className = 'table-wrap';
    t.parentNode.insertBefore(wrap, t);
    wrap.appendChild(t);
   });
  // =====================
  //  Modo oscuro / claro
  // =====================
  const KEY = 'edusac-theme';
  const prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
  const saved = localStorage.getItem(KEY);
  if ((saved === 'dark') || (!saved && prefersDark)) {
    document.body.classList.add('theme-dark');
  }
  const btn = document.getElementById('themeToggle');
  if (btn) {
    btn.addEventListener('click', () => {
      document.body.classList.toggle('theme-dark');
      localStorage.setItem(KEY, document.body.classList.contains('theme-dark') ? 'dark' : 'light');
    });
  }
})();
