<?php
// concurso_nobel/asignar_salon.php
// Asignación de salón por DNI para el día del examen.
// Si el participante ya existe, se jala de BD y se asigna al salón.
// Si no existe, se consulta DNI por API, se completa/crea manualmente y se asigna al salón.

declare(strict_types=1);

session_start();

require_once __DIR__ . '/includes/db.php';
require_once __DIR__ . '/includes/concurso_contexto.php';

date_default_timezone_set('America/Lima');

function as_h($valor): string
{
    return htmlspecialchars((string)$valor, ENT_QUOTES, 'UTF-8');
}

function as_upper(string $valor): string
{
    $valor = trim($valor);
    return function_exists('mb_strtoupper') ? mb_strtoupper($valor, 'UTF-8') : strtoupper($valor);
}

function as_strlen(string $valor): int
{
    return function_exists('mb_strlen') ? mb_strlen($valor, 'UTF-8') : strlen($valor);
}

function as_nivel_base(string $nivel): string
{
    $nivel = as_upper($nivel);

    if (strpos($nivel, 'INICIAL') !== false) {
        return 'INICIAL';
    }

    if (strpos($nivel, 'PRIMARIA') !== false) {
        return 'PRIMARIA';
    }

    if (strpos($nivel, 'SECUNDARIA') !== false) {
        return 'SECUNDARIA';
    }

    return $nivel;
}

function as_tipo_valido(string $tipo): bool
{
    return in_array($tipo, ['PUBLICA', 'PRIVADA'], true);
}

function as_post(string $key, string $default = ''): string
{
    return trim((string)($_POST[$key] ?? $default));
}

function as_get(string $key, string $default = ''): string
{
    return trim((string)($_GET[$key] ?? $default));
}

function as_json(array $data): void
{
    header('Content-Type: application/json; charset=utf-8');
    echo json_encode($data, JSON_UNESCAPED_UNICODE);
    exit;
}

function as_stmt_bind(mysqli_stmt $stmt, string $types, array $params): bool
{
    $refs = [];
    $refs[] = $stmt;
    $refs[] = $types;

    foreach ($params as $key => $value) {
        $refs[] = &$params[$key];
    }

    return (bool)call_user_func_array('mysqli_stmt_bind_param', $refs);
}

function as_columna_existe(mysqli $conn, string $tabla, string $columna): bool
{
    $tablaSql = mysqli_real_escape_string($conn, $tabla);
    $columnaSql = mysqli_real_escape_string($conn, $columna);
    $res = mysqli_query($conn, "SHOW COLUMNS FROM `{$tablaSql}` LIKE '{$columnaSql}'");
    return $res instanceof mysqli_result && mysqli_num_rows($res) > 0;
}

function as_generar_codigo_participante(mysqli $conn): string
{
    for ($i = 0; $i < 20; $i++) {
        $codigo = 'NOBEX-' . date('Y') . '-' . str_pad((string)random_int(1, 99999), 5, '0', STR_PAD_LEFT);

        $stmt = mysqli_prepare($conn, "
            SELECT id_participante
            FROM concurso_participantes
            WHERE codigo_participante = ?
            LIMIT 1
        ");

        if (!$stmt) {
            return $codigo;
        }

        mysqli_stmt_bind_param($stmt, 's', $codigo);
        mysqli_stmt_execute($stmt);
        $res = mysqli_stmt_get_result($stmt);
        $existe = $res instanceof mysqli_result ? mysqli_fetch_assoc($res) : null;
        mysqli_stmt_close($stmt);

        if (!$existe) {
            return $codigo;
        }
    }

    return 'NOBEX-' . date('YmdHis') . '-' . random_int(100, 999);
}

function as_generar_documento_temporal(mysqli $conn, int $idConcurso): string
{
    // Código alfanumérico de 8 caracteres para estudiantes sin DNI.
    // Se evitan caracteres confusos: I, O, 0 y 1.
    $caracteres = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';
    $longitud = strlen($caracteres);

    for ($intento = 0; $intento < 50; $intento++) {
        $codigo = '';

        for ($i = 0; $i < 8; $i++) {
            $codigo .= $caracteres[random_int(0, $longitud - 1)];
        }

        $stmt = mysqli_prepare($conn, "
            SELECT id_participante
            FROM concurso_participantes
            WHERE id_concurso = ?
              AND documento = ?
              AND estado = 'ACTIVO'
            LIMIT 1
        ");

        if (!$stmt) {
            return $codigo;
        }

        mysqli_stmt_bind_param($stmt, 'is', $idConcurso, $codigo);
        mysqli_stmt_execute($stmt);
        $res = mysqli_stmt_get_result($stmt);
        $existe = $res instanceof mysqli_result ? mysqli_fetch_assoc($res) : null;
        mysqli_stmt_close($stmt);

        if (!$existe) {
            return $codigo;
        }
    }

    return strtoupper(substr(bin2hex(random_bytes(4)), 0, 8));
}

function as_grupos_concurso(mysqli $conn, int $idConcurso, int $idGrupoFijo): array
{
    return cn_grupos_concurso($conn, $idConcurso, $idGrupoFijo);
}

function as_salones_grupo(mysqli $conn, int $idConcurso, int $idGrupo): array
{
    $salones = [];

    if ($idConcurso <= 0 || $idGrupo <= 0) {
        return $salones;
    }

    $stmt = mysqli_prepare($conn, "
        SELECT
            s.id_salon,
            s.id_grupo,
            s.codigo_salon,
            s.nombre_salon,
            s.capacidad,
            s.orden,
            COUNT(p.id_participante) AS ocupados
        FROM concurso_salones s
        LEFT JOIN concurso_participantes p
            ON p.id_salon = s.id_salon
           AND p.estado = 'ACTIVO'
        WHERE s.id_concurso = ?
          AND s.id_grupo = ?
          AND s.estado = 1
        GROUP BY
            s.id_salon,
            s.id_grupo,
            s.codigo_salon,
            s.nombre_salon,
            s.capacidad,
            s.orden
        ORDER BY s.orden ASC, s.nombre_salon ASC
    ");

    if (!$stmt) {
        return $salones;
    }

    mysqli_stmt_bind_param($stmt, 'ii', $idConcurso, $idGrupo);
    mysqli_stmt_execute($stmt);
    $res = mysqli_stmt_get_result($stmt);

    if ($res instanceof mysqli_result) {
        while ($row = mysqli_fetch_assoc($res)) {
            $salones[] = $row;
        }
    }

    mysqli_stmt_close($stmt);
    return $salones;
}

function as_obtener_salon(mysqli $conn, int $idConcurso, int $idSalon): ?array
{
    if ($idConcurso <= 0 || $idSalon <= 0) {
        return null;
    }

    $stmt = mysqli_prepare($conn, "
        SELECT
            s.id_salon,
            s.id_concurso,
            s.id_grupo,
            s.codigo_salon,
            s.nombre_salon,
            s.capacidad,
            g.nombre AS grupo_nombre,
            g.nivel AS grupo_nivel,
            COUNT(p.id_participante) AS ocupados
        FROM concurso_salones s
        INNER JOIN concurso_grupos g ON g.id_grupo = s.id_grupo
        LEFT JOIN concurso_participantes p
            ON p.id_salon = s.id_salon
           AND p.estado = 'ACTIVO'
        WHERE s.id_concurso = ?
          AND s.id_salon = ?
          AND s.estado = 1
        GROUP BY
            s.id_salon,
            s.id_concurso,
            s.id_grupo,
            s.codigo_salon,
            s.nombre_salon,
            s.capacidad,
            g.nombre,
            g.nivel
        LIMIT 1
    ");

    if (!$stmt) {
        return null;
    }

    mysqli_stmt_bind_param($stmt, 'ii', $idConcurso, $idSalon);
    mysqli_stmt_execute($stmt);
    $res = mysqli_stmt_get_result($stmt);
    $row = $res instanceof mysqli_result ? mysqli_fetch_assoc($res) : null;
    mysqli_stmt_close($stmt);

    return $row ?: null;
}


function as_obtener_grupo(mysqli $conn, int $idConcurso, int $idGrupo): ?array
{
    if ($idConcurso <= 0 || $idGrupo <= 0) {
        return null;
    }

    $stmt = mysqli_prepare($conn, "
        SELECT
            id_grupo,
            nombre,
            nivel
        FROM concurso_grupos
        WHERE id_concurso = ?
          AND id_grupo = ?
        LIMIT 1
    ");

    if (!$stmt) {
        return null;
    }

    mysqli_stmt_bind_param($stmt, 'ii', $idConcurso, $idGrupo);
    mysqli_stmt_execute($stmt);
    $res = mysqli_stmt_get_result($stmt);
    $row = $res instanceof mysqli_result ? mysqli_fetch_assoc($res) : null;
    mysqli_stmt_close($stmt);

    return $row ?: null;
}

function as_nivel_contexto_acceso(
    mysqli $conn,
    int $idConcurso,
    int $idGrupoFijo,
    int $idSalonFijo,
    int $idGrupoSeleccion = 0,
    int $idSalonSeleccion = 0
): string {
    if ($idSalonFijo > 0) {
        $salon = as_obtener_salon($conn, $idConcurso, $idSalonFijo);
        return $salon ? as_nivel_base((string)($salon['grupo_nivel'] ?: $salon['grupo_nombre'])) : '';
    }

    if ($idGrupoFijo > 0) {
        $grupo = as_obtener_grupo($conn, $idConcurso, $idGrupoFijo);
        return $grupo ? as_nivel_base((string)($grupo['nivel'] ?: $grupo['nombre'])) : '';
    }

    if ($idSalonSeleccion > 0) {
        $salon = as_obtener_salon($conn, $idConcurso, $idSalonSeleccion);
        return $salon ? as_nivel_base((string)($salon['grupo_nivel'] ?: $salon['grupo_nombre'])) : '';
    }

    if ($idGrupoSeleccion > 0) {
        $grupo = as_obtener_grupo($conn, $idConcurso, $idGrupoSeleccion);
        return $grupo ? as_nivel_base((string)($grupo['nivel'] ?: $grupo['nombre'])) : '';
    }

    return '';
}

function as_salon_tiene_cupo(mysqli $conn, int $idSalon): bool
{
    $stmt = mysqli_prepare($conn, "
        SELECT
            s.capacidad,
            COUNT(p.id_participante) AS ocupados
        FROM concurso_salones s
        LEFT JOIN concurso_participantes p
            ON p.id_salon = s.id_salon
           AND p.estado = 'ACTIVO'
        WHERE s.id_salon = ?
          AND s.estado = 1
        GROUP BY s.id_salon, s.capacidad
        LIMIT 1
    ");

    if (!$stmt) {
        return false;
    }

    mysqli_stmt_bind_param($stmt, 'i', $idSalon);
    mysqli_stmt_execute($stmt);
    $res = mysqli_stmt_get_result($stmt);
    $row = $res instanceof mysqli_result ? mysqli_fetch_assoc($res) : null;
    mysqli_stmt_close($stmt);

    if (!$row) {
        return false;
    }

    return (int)$row['ocupados'] < (int)$row['capacidad'];
}

function as_bloquear_salon_y_verificar_cupo(mysqli $conn, int $idSalon): array
{
    if ($idSalon <= 0) {
        throw new RuntimeException('Salón inválido para validar cupo.');
    }

    /*
     * IMPORTANTE:
     * Esta función debe ejecutarse dentro de una transacción abierta.
     * El SELECT ... FOR UPDATE bloquea la fila del salón para evitar que dos usuarios
     * asignen el último cupo al mismo tiempo.
     */
    $stmtSalon = mysqli_prepare($conn, "
        SELECT id_salon, capacidad
        FROM concurso_salones
        WHERE id_salon = ?
          AND estado = 1
        LIMIT 1
        FOR UPDATE
    ");

    if (!$stmtSalon) {
        throw new RuntimeException('No se pudo bloquear el salón para validar cupos.');
    }

    mysqli_stmt_bind_param($stmtSalon, 'i', $idSalon);
    mysqli_stmt_execute($stmtSalon);
    $resSalon = mysqli_stmt_get_result($stmtSalon);
    $salon = $resSalon instanceof mysqli_result ? mysqli_fetch_assoc($resSalon) : null;
    mysqli_stmt_close($stmtSalon);

    if (!$salon) {
        throw new RuntimeException('El salón seleccionado ya no está disponible.');
    }

    $stmtOcupados = mysqli_prepare($conn, "
        SELECT COUNT(*) AS ocupados
        FROM concurso_participantes
        WHERE id_salon = ?
          AND estado = 'ACTIVO'
    ");

    if (!$stmtOcupados) {
        throw new RuntimeException('No se pudo calcular el cupo ocupado del salón.');
    }

    mysqli_stmt_bind_param($stmtOcupados, 'i', $idSalon);
    mysqli_stmt_execute($stmtOcupados);
    $resOcupados = mysqli_stmt_get_result($stmtOcupados);
    $rowOcupados = $resOcupados instanceof mysqli_result ? mysqli_fetch_assoc($resOcupados) : null;
    mysqli_stmt_close($stmtOcupados);

    $capacidad = (int)$salon['capacidad'];
    $ocupados = (int)($rowOcupados['ocupados'] ?? 0);
    $libres = max(0, $capacidad - $ocupados);

    return [
        'capacidad' => $capacidad,
        'ocupados' => $ocupados,
        'libres' => $libres,
        'tiene_cupo' => $ocupados < $capacidad
    ];
}

function as_buscar_participante_documento(mysqli $conn, int $idConcurso, string $documento): ?array
{
    $stmt = mysqli_prepare($conn, "
        SELECT
            p.*,
            g.nombre AS grupo_nombre,
            g.nivel AS grupo_nivel,
            s.nombre_salon,
            s.codigo_salon
        FROM concurso_participantes p
        LEFT JOIN concurso_grupos g ON g.id_grupo = p.id_grupo
        LEFT JOIN concurso_salones s ON s.id_salon = p.id_salon
        WHERE p.id_concurso = ?
          AND p.documento = ?
          AND p.estado = 'ACTIVO'
        ORDER BY p.id_participante DESC
        LIMIT 1
    ");

    if (!$stmt) {
        return null;
    }

    mysqli_stmt_bind_param($stmt, 'is', $idConcurso, $documento);
    mysqli_stmt_execute($stmt);
    $res = mysqli_stmt_get_result($stmt);
    $row = $res instanceof mysqli_result ? mysqli_fetch_assoc($res) : null;
    mysqli_stmt_close($stmt);

    return $row ?: null;
}

function as_institucion_por_id(mysqli $conn, int $idInstitucion, string $gestion): ?array
{
    if ($idInstitucion <= 0) {
        return null;
    }

    $stmt = mysqli_prepare($conn, "
        SELECT
            id_institucion,
            nombre_institucion,
            gestion,
            departamento,
            provincia,
            distrito
        FROM concurso_instituciones
        WHERE id_institucion = ?
          AND gestion = ?
          AND estado = 1
        LIMIT 1
    ");

    if (!$stmt) {
        return null;
    }

    mysqli_stmt_bind_param($stmt, 'is', $idInstitucion, $gestion);
    mysqli_stmt_execute($stmt);
    $res = mysqli_stmt_get_result($stmt);
    $row = $res instanceof mysqli_result ? mysqli_fetch_assoc($res) : null;
    mysqli_stmt_close($stmt);

    return $row ?: null;
}

function as_institucion_modular_por_id(mysqli $conn, int $idModular, string $gestion): ?array
{
    if ($idModular <= 0) {
        return null;
    }

    $stmt = mysqli_prepare($conn, "
        SELECT
            m.id_modular,
            m.id_institucion,
            m.codigo_modular,
            m.nivel_modalidad,
            i.nombre_institucion,
            i.gestion,
            i.departamento,
            i.provincia,
            i.distrito
        FROM concurso_institucion_modulares m
        INNER JOIN concurso_instituciones i
            ON i.id_institucion = m.id_institucion
        WHERE m.id_modular = ?
          AND i.gestion = ?
          AND i.estado = 1
        LIMIT 1
    ");

    if (!$stmt) {
        return null;
    }

    mysqli_stmt_bind_param($stmt, 'is', $idModular, $gestion);
    mysqli_stmt_execute($stmt);
    $res = mysqli_stmt_get_result($stmt);
    $row = $res instanceof mysqli_result ? mysqli_fetch_assoc($res) : null;
    mysqli_stmt_close($stmt);

    return $row ?: null;
}

function as_insertar_participante(
    mysqli $conn,
    int $idConcurso,
    int $idGrupo,
    int $idSalon,
    int $idAcceso,
    string $codigoParticipante,
    string $apellidosNombres,
    string $apellidoPaterno,
    string $apellidoMaterno,
    string $nombresParticipante,
    string $institucionEducativa,
    string $tipoInstitucion,
    ?int $idInstitucion,
    ?string $codigoModularIe,
    ?string $nivelModalidadIe,
    string $documento,
    string $observacion
): int {
    $columnas = [
        'id_concurso',
        'id_grupo',
        'id_salon',
        'id_acceso_registro',
        'codigo_participante',
        'apellidos_nombres',
        'institucion_educativa',
        'tipo_institucion'
    ];

    $placeholders = ['?', '?', '?', '?', '?', '?', '?', '?'];
    $tipos = 'iiiissss';
    $params = [
        $idConcurso,
        $idGrupo,
        $idSalon,
        $idAcceso,
        $codigoParticipante,
        $apellidosNombres,
        $institucionEducativa,
        $tipoInstitucion
    ];

    if (as_columna_existe($conn, 'concurso_participantes', 'id_institucion')) {
        $columnas[] = 'id_institucion';
        $placeholders[] = '?';
        $tipos .= 'i';
        $params[] = $idInstitucion;
    }

    if (as_columna_existe($conn, 'concurso_participantes', 'codigo_modular_ie')) {
        $columnas[] = 'codigo_modular_ie';
        $placeholders[] = '?';
        $tipos .= 's';
        $params[] = $codigoModularIe;
    }

    if (as_columna_existe($conn, 'concurso_participantes', 'nivel_modalidad_ie')) {
        $columnas[] = 'nivel_modalidad_ie';
        $placeholders[] = '?';
        $tipos .= 's';
        $params[] = $nivelModalidadIe;
    }

    if (as_columna_existe($conn, 'concurso_participantes', 'apellido_paterno')) {
        $columnas[] = 'apellido_paterno';
        $placeholders[] = '?';
        $tipos .= 's';
        $params[] = $apellidoPaterno;
    }

    if (as_columna_existe($conn, 'concurso_participantes', 'apellido_materno')) {
        $columnas[] = 'apellido_materno';
        $placeholders[] = '?';
        $tipos .= 's';
        $params[] = $apellidoMaterno;
    }

    if (as_columna_existe($conn, 'concurso_participantes', 'nombres')) {
        $columnas[] = 'nombres';
        $placeholders[] = '?';
        $tipos .= 's';
        $params[] = $nombresParticipante;
    }

    $columnas[] = 'documento';
    $placeholders[] = '?';
    $tipos .= 's';
    $params[] = $documento;

    $columnas[] = 'observacion';
    $placeholders[] = '?';
    $tipos .= 's';
    $params[] = $observacion;

    $sql = "
        INSERT INTO concurso_participantes
            (" . implode(', ', $columnas) . ", estado, created_at)
        VALUES
            (" . implode(', ', $placeholders) . ", 'ACTIVO', NOW())
    ";

    $stmt = mysqli_prepare($conn, $sql);

    if (!$stmt) {
        throw new RuntimeException('No se pudo preparar el registro del participante.');
    }

    as_stmt_bind($stmt, $tipos, $params);

    if (!mysqli_stmt_execute($stmt)) {
        $error = mysqli_stmt_error($stmt);
        mysqli_stmt_close($stmt);
        throw new RuntimeException('No se pudo crear el participante. ' . $error);
    }

    mysqli_stmt_close($stmt);
    return (int)mysqli_insert_id($conn);
}

function as_listar_asignados_salon(mysqli $conn, int $idConcurso, int $idSalon): array
{
    $lista = [];

    if ($idConcurso <= 0 || $idSalon <= 0) {
        return $lista;
    }

    $stmt = mysqli_prepare($conn, "
        SELECT
            p.codigo_participante,
            p.documento,
            p.apellidos_nombres,
            p.institucion_educativa,
            p.tipo_institucion,
            p.codigo_modular_ie,
            p.nivel_modalidad_ie,
            p.created_at,
            p.updated_at,
            r.correctas,
            r.incorrectas,
            r.blanco,
            r.puntaje,
            r.tiempo_texto,
            g.nombre AS grupo,
            s.nombre_salon
        FROM concurso_participantes p
        LEFT JOIN concurso_grupos g ON g.id_grupo = p.id_grupo
        INNER JOIN concurso_salones s ON s.id_salon = p.id_salon
        LEFT JOIN concurso_resultados r
            ON r.id_participante = p.id_participante
           AND r.estado = 'ACTIVO'
        WHERE p.id_concurso = ?
          AND p.id_salon = ?
          AND p.estado = 'ACTIVO'
        ORDER BY p.updated_at DESC, p.created_at DESC, p.id_participante DESC
        LIMIT 150
    ");

    if (!$stmt) {
        return $lista;
    }

    mysqli_stmt_bind_param($stmt, 'ii', $idConcurso, $idSalon);
    mysqli_stmt_execute($stmt);
    $res = mysqli_stmt_get_result($stmt);

    if ($res instanceof mysqli_result) {
        while ($row = mysqli_fetch_assoc($res)) {
            $lista[] = $row;
        }
    }

    mysqli_stmt_close($stmt);

    return $lista;
}

function as_responsable_activo_salon(mysqli $conn, int $idSalon): ?array
{
    if ($idSalon <= 0) {
        return null;
    }

    $stmt = mysqli_prepare($conn, "
        SELECT
            r.id_responsable,
            r.id_concurso,
            r.id_grupo,
            r.id_salon,
            r.id_acceso,
            r.dni_responsable,
            r.nombre_responsable,
            r.estado,
            r.created_at,
            r.updated_at,
            a.codigo AS codigo_acceso
        FROM concurso_salon_responsables r
        LEFT JOIN concurso_accesos a
            ON a.id_acceso = r.id_acceso
        WHERE r.id_salon = ?
          AND r.estado = 'ACTIVO'
        ORDER BY r.id_responsable DESC
        LIMIT 1
    ");

    if (!$stmt) {
        return null;
    }

    mysqli_stmt_bind_param($stmt, 'i', $idSalon);
    mysqli_stmt_execute($stmt);
    $res = mysqli_stmt_get_result($stmt);
    $row = $res instanceof mysqli_result ? mysqli_fetch_assoc($res) : null;
    mysqli_stmt_close($stmt);

    return $row ?: null;
}

function as_registrar_responsable_salon(
    mysqli $conn,
    int $idConcurso,
    int $idGrupo,
    int $idSalon,
    int $idAcceso,
    string $dniResponsable,
    string $nombreResponsable
): int {
    $stmtExiste = mysqli_prepare($conn, "
        SELECT id_responsable
        FROM concurso_salon_responsables
        WHERE id_salon = ?
          AND estado = 'ACTIVO'
        LIMIT 1
    ");

    if (!$stmtExiste) {
        throw new RuntimeException('No se pudo validar si el salón ya tiene responsable.');
    }

    mysqli_stmt_bind_param($stmtExiste, 'i', $idSalon);
    mysqli_stmt_execute($stmtExiste);
    $resExiste = mysqli_stmt_get_result($stmtExiste);
    $existe = $resExiste instanceof mysqli_result ? mysqli_fetch_assoc($resExiste) : null;
    mysqli_stmt_close($stmtExiste);

    if ($existe) {
        throw new RuntimeException('Este salón ya tiene un docente responsable activo.');
    }

    $stmt = mysqli_prepare($conn, "
        INSERT INTO concurso_salon_responsables
            (
                id_concurso,
                id_grupo,
                id_salon,
                id_acceso,
                dni_responsable,
                nombre_responsable,
                estado,
                created_at
            )
        VALUES
            (?, ?, ?, ?, ?, ?, 'ACTIVO', NOW())
    ");

    if (!$stmt) {
        throw new RuntimeException('No se pudo preparar el registro del docente responsable.');
    }

    mysqli_stmt_bind_param(
        $stmt,
        'iiiiss',
        $idConcurso,
        $idGrupo,
        $idSalon,
        $idAcceso,
        $dniResponsable,
        $nombreResponsable
    );

    if (!mysqli_stmt_execute($stmt)) {
        $error = mysqli_stmt_error($stmt);
        mysqli_stmt_close($stmt);
        throw new RuntimeException('No se pudo registrar al docente responsable. ' . $error);
    }

    mysqli_stmt_close($stmt);
    return (int)mysqli_insert_id($conn);
}


function as_cal_formato_tiempo(?int $segundos): ?string
{
    if ($segundos === null) {
        return null;
    }

    $h = intdiv($segundos, 3600);
    $m = intdiv($segundos % 3600, 60);
    $s = $segundos % 60;

    if ($h > 0) {
        return sprintf('%02d:%02d:%02d', $h, $m, $s);
    }

    return sprintf('%02d:%02d', $m, $s);
}

function as_cal_parse_tiempo(string $txt): ?int
{
    $txt = trim($txt);

    if ($txt === '') {
        return null;
    }

    if (preg_match('/^\d+$/', $txt)) {
        return (int)$txt;
    }

    if (preg_match('/^(\d{1,2}):(\d{2})$/', $txt, $m)) {
        return ((int)$m[1] * 60) + (int)$m[2];
    }

    if (preg_match('/^(\d{1,2}):(\d{2}):(\d{2})$/', $txt, $m)) {
        return ((int)$m[1] * 3600) + ((int)$m[2] * 60) + (int)$m[3];
    }

    return -1;
}

function as_cal_puntaje(int $correctas, int $incorrectas, int $blanco, float $pCorrecta, float $pIncorrecta, float $pBlanco): float
{
    return ($correctas * $pCorrecta) - ($incorrectas * $pIncorrecta) + ($blanco * $pBlanco);
}

function as_cal_recalcular_puestos_categoria(mysqli $conn, int $idConcurso, int $idGrupo, string $tipoInstitucion): void
{
    $stmt = mysqli_prepare($conn, "
        SELECT r.id_resultado
        FROM concurso_resultados r
        INNER JOIN concurso_participantes p
            ON p.id_participante = r.id_participante
        WHERE r.id_concurso = ?
          AND r.id_grupo = ?
          AND r.estado = 'ACTIVO'
          AND p.estado = 'ACTIVO'
          AND p.tipo_institucion = ?
        ORDER BY
          r.puntaje DESC,
          (r.tiempo_segundos IS NULL) ASC,
          r.tiempo_segundos ASC,
          r.correctas DESC,
          r.incorrectas ASC,
          r.id_resultado ASC
    ");

    if (!$stmt) {
        return;
    }

    mysqli_stmt_bind_param($stmt, 'iis', $idConcurso, $idGrupo, $tipoInstitucion);
    mysqli_stmt_execute($stmt);
    $res = mysqli_stmt_get_result($stmt);

    $puesto = 1;

    while ($row = mysqli_fetch_assoc($res)) {
        $idResultado = (int)$row['id_resultado'];

        $up = mysqli_prepare($conn, "
            UPDATE concurso_resultados
            SET puesto = ?
            WHERE id_resultado = ?
            LIMIT 1
        ");

        if ($up) {
            mysqli_stmt_bind_param($up, 'ii', $puesto, $idResultado);
            mysqli_stmt_execute($up);
            mysqli_stmt_close($up);
        }

        $puesto++;
    }

    mysqli_stmt_close($stmt);
}

function as_cal_recalcular_puestos_grupo(mysqli $conn, int $idConcurso, int $idGrupo): void
{
    as_cal_recalcular_puestos_categoria($conn, $idConcurso, $idGrupo, 'PRIVADA');
    as_cal_recalcular_puestos_categoria($conn, $idConcurso, $idGrupo, 'PUBLICA');
}

function as_obtener_grupo_calificacion(mysqli $conn, int $idConcurso, int $idGrupo): ?array
{
    if ($idConcurso <= 0 || $idGrupo <= 0) {
        return null;
    }

    $stmt = mysqli_prepare($conn, "
        SELECT
            id_grupo,
            nombre,
            nivel,
            total_preguntas,
            puntos_correcta,
            penalizacion_incorrecta,
            puntos_blanco
        FROM concurso_grupos
        WHERE id_concurso = ?
          AND id_grupo = ?
        LIMIT 1
    ");

    if (!$stmt) {
        return null;
    }

    mysqli_stmt_bind_param($stmt, 'ii', $idConcurso, $idGrupo);
    mysqli_stmt_execute($stmt);
    $res = mysqli_stmt_get_result($stmt);
    $row = $res instanceof mysqli_result ? mysqli_fetch_assoc($res) : null;
    mysqli_stmt_close($stmt);

    return $row ?: null;
}

function as_resultado_activo_participante(mysqli $conn, int $idParticipante): ?array
{
    if ($idParticipante <= 0) {
        return null;
    }

    $stmt = mysqli_prepare($conn, "
        SELECT
            id_resultado,
            correctas,
            incorrectas,
            blanco,
            puntaje,
            tiempo_segundos,
            tiempo_texto,
            observacion
        FROM concurso_resultados
        WHERE id_participante = ?
          AND estado = 'ACTIVO'
        LIMIT 1
    ");

    if (!$stmt) {
        return null;
    }

    mysqli_stmt_bind_param($stmt, 'i', $idParticipante);
    mysqli_stmt_execute($stmt);
    $res = mysqli_stmt_get_result($stmt);
    $row = $res instanceof mysqli_result ? mysqli_fetch_assoc($res) : null;
    mysqli_stmt_close($stmt);

    return $row ?: null;
}

function as_debe_guardar_calificacion_directa(): bool
{
    /*
     * Seguridad:
     * La calificación directa solo se guarda cuando se ingresan correctas o incorrectas.
     * Tiempo y observación NO activan el guardado por sí solos, para evitar notas accidentales.
     */
    return as_post('correctas') !== ''
        || as_post('incorrectas') !== '';
}

function as_guardar_calificacion_directa(
    mysqli $conn,
    int $idConcurso,
    int $idGrupo,
    int $idSalon,
    int $idParticipante,
    int $idAcceso,
    int $correctas,
    int $incorrectas,
    ?int $tiempoSegundos,
    string $observacion
): array {
    $stmtPart = mysqli_prepare($conn, "
        SELECT
            p.id_participante,
            p.id_concurso,
            p.id_grupo,
            p.id_salon,
            p.apellidos_nombres,
            p.tipo_institucion,
            g.total_preguntas,
            g.puntos_correcta,
            g.penalizacion_incorrecta,
            g.puntos_blanco
        FROM concurso_participantes p
        INNER JOIN concurso_grupos g ON g.id_grupo = p.id_grupo
        WHERE p.id_participante = ?
          AND p.id_concurso = ?
          AND p.estado = 'ACTIVO'
        LIMIT 1
    ");

    if (!$stmtPart) {
        throw new RuntimeException('No se pudo preparar la validación de calificación.');
    }

    mysqli_stmt_bind_param($stmtPart, 'ii', $idParticipante, $idConcurso);
    mysqli_stmt_execute($stmtPart);
    $resPart = mysqli_stmt_get_result($stmtPart);
    $part = $resPart instanceof mysqli_result ? mysqli_fetch_assoc($resPart) : null;
    mysqli_stmt_close($stmtPart);

    if (!$part) {
        throw new RuntimeException('No se pudo guardar calificación: participante inválido.');
    }

    if ((int)$part['id_grupo'] !== $idGrupo) {
        throw new RuntimeException('No se pudo guardar calificación: el participante no pertenece al grado seleccionado.');
    }

    if ((int)$part['id_salon'] !== $idSalon) {
        throw new RuntimeException('No se pudo guardar calificación: el participante no pertenece al salón seleccionado.');
    }

    $totalPreguntas = (int)$part['total_preguntas'];

    if ($correctas < 0 || $incorrectas < 0) {
        throw new RuntimeException('Correctas e incorrectas no pueden ser negativas.');
    }

    if (($correctas + $incorrectas) > $totalPreguntas) {
        throw new RuntimeException('Correctas + incorrectas no puede superar ' . $totalPreguntas . ' preguntas.');
    }

    $blanco = $totalPreguntas - $correctas - $incorrectas;
    $pCorrecta = (float)$part['puntos_correcta'];
    $pIncorrecta = (float)$part['penalizacion_incorrecta'];
    $pBlanco = (float)$part['puntos_blanco'];
    $puntaje = as_cal_puntaje($correctas, $incorrectas, $blanco, $pCorrecta, $pIncorrecta, $pBlanco);
    $tiempoTexto = as_cal_formato_tiempo($tiempoSegundos);

    $stmtExiste = mysqli_prepare($conn, "
        SELECT id_resultado
        FROM concurso_resultados
        WHERE id_participante = ?
        LIMIT 1
    ");

    $idResultadoExiste = 0;

    if ($stmtExiste) {
        mysqli_stmt_bind_param($stmtExiste, 'i', $idParticipante);
        mysqli_stmt_execute($stmtExiste);
        $resExiste = mysqli_stmt_get_result($stmtExiste);
        $rowExiste = $resExiste instanceof mysqli_result ? mysqli_fetch_assoc($resExiste) : null;
        $idResultadoExiste = $rowExiste ? (int)$rowExiste['id_resultado'] : 0;
        mysqli_stmt_close($stmtExiste);
    }

    if ($idResultadoExiste > 0) {
        $stmtSave = mysqli_prepare($conn, "
            UPDATE concurso_resultados
            SET
                id_acceso_registro = ?,
                total_preguntas_usada = ?,
                puntos_correcta_usada = ?,
                penalizacion_incorrecta_usada = ?,
                puntos_blanco_usada = ?,
                correctas = ?,
                incorrectas = ?,
                blanco = ?,
                puntaje = ?,
                tiempo_segundos = ?,
                tiempo_texto = ?,
                observacion = ?,
                estado = 'ACTIVO',
                updated_at = NOW()
            WHERE id_resultado = ?
            LIMIT 1
        ");

        if (!$stmtSave) {
            throw new RuntimeException('No se pudo preparar la actualización de calificación.');
        }

        mysqli_stmt_bind_param(
            $stmtSave,
            'iidddiiidissi',
            $idAcceso,
            $totalPreguntas,
            $pCorrecta,
            $pIncorrecta,
            $pBlanco,
            $correctas,
            $incorrectas,
            $blanco,
            $puntaje,
            $tiempoSegundos,
            $tiempoTexto,
            $observacion,
            $idResultadoExiste
        );
    } else {
        $stmtSave = mysqli_prepare($conn, "
            INSERT INTO concurso_resultados
                (
                    id_concurso,
                    id_grupo,
                    id_salon,
                    id_participante,
                    id_acceso_registro,
                    total_preguntas_usada,
                    puntos_correcta_usada,
                    penalizacion_incorrecta_usada,
                    puntos_blanco_usada,
                    correctas,
                    incorrectas,
                    blanco,
                    puntaje,
                    tiempo_segundos,
                    tiempo_texto,
                    observacion,
                    estado,
                    created_at
                )
            VALUES
                (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'ACTIVO', NOW())
        ");

        if (!$stmtSave) {
            throw new RuntimeException('No se pudo preparar el registro de calificación.');
        }

        mysqli_stmt_bind_param(
            $stmtSave,
            'iiiiiidddiiidiss',
            $idConcurso,
            $idGrupo,
            $idSalon,
            $idParticipante,
            $idAcceso,
            $totalPreguntas,
            $pCorrecta,
            $pIncorrecta,
            $pBlanco,
            $correctas,
            $incorrectas,
            $blanco,
            $puntaje,
            $tiempoSegundos,
            $tiempoTexto,
            $observacion
        );
    }

    if (!mysqli_stmt_execute($stmtSave)) {
        $error = mysqli_stmt_error($stmtSave);
        mysqli_stmt_close($stmtSave);
        throw new RuntimeException('No se pudo guardar la calificación. ' . $error);
    }

    mysqli_stmt_close($stmtSave);

    as_cal_recalcular_puestos_grupo($conn, $idConcurso, $idGrupo);

    cn_auditar(
        $conn,
        $idConcurso,
        $idGrupo,
        $idAcceso,
        $idParticipante,
        'CALIFICACION_DIRECTA_GUARDADA',
        'Calificación directa guardada desde asignar_salon.php para ' . $part['apellidos_nombres'] . ' | ' . $part['tipo_institucion'] . ' | puntaje ' . $puntaje
    );

    return [
        'correctas' => $correctas,
        'incorrectas' => $incorrectas,
        'blanco' => $blanco,
        'puntaje' => $puntaje,
        'tiempo_texto' => $tiempoTexto
    ];
}


function as_buscar_participantes_por_nombre(
    mysqli $conn,
    int $idConcurso,
    string $q,
    string $nivelContexto = ''
): array {
    $q = as_upper($q);
    $q = trim((string)preg_replace('/\s+/u', ' ', $q));

    if ($idConcurso <= 0 || as_strlen($q) < 2) {
        return [];
    }

    $predicados = [];
    $params = [$idConcurso];
    $types = 'i';

    $likeNormal = '%' . $q . '%';
    $likeFlexible = '%' . str_replace(' ', '%', $q) . '%';

    $camposBusqueda = [
        'documento',
        'codigo_participante',
        'apellidos_nombres',
        'apellido_paterno',
        'apellido_materno',
        'nombres'
    ];

    foreach ($camposBusqueda as $campo) {
        if ($campo === 'documento' || $campo === 'codigo_participante' || as_columna_existe($conn, 'concurso_participantes', $campo)) {
            $predicados[] = "p.`{$campo}` LIKE ?";
            $params[] = ($campo === 'apellidos_nombres') ? $likeFlexible : $likeNormal;
            $types .= 's';
        }
    }

    if (empty($predicados)) {
        return [];
    }

    $sql = "
        SELECT
            p.*,
            g.nombre AS grupo_nombre,
            g.nivel AS grupo_nivel,
            s.nombre_salon,
            s.codigo_salon,
            r.id_resultado,
            r.correctas AS resultado_correctas,
            r.incorrectas AS resultado_incorrectas,
            r.blanco AS resultado_blanco,
            r.puntaje AS resultado_puntaje,
            r.tiempo_segundos AS resultado_tiempo_segundos,
            r.tiempo_texto AS resultado_tiempo_texto,
            r.observacion AS resultado_observacion
        FROM concurso_participantes p
        LEFT JOIN concurso_grupos g ON g.id_grupo = p.id_grupo
        LEFT JOIN concurso_salones s ON s.id_salon = p.id_salon
        LEFT JOIN concurso_resultados r
            ON r.id_participante = p.id_participante
           AND r.estado = 'ACTIVO'
        WHERE p.id_concurso = ?
          AND p.estado = 'ACTIVO'
          AND (" . implode(' OR ', $predicados) . ")
        ORDER BY
            CASE
                WHEN p.documento = ? THEN 0
                WHEN p.apellidos_nombres = ? THEN 1
                WHEN p.apellidos_nombres LIKE CONCAT(?, '%') THEN 2
                ELSE 3
            END,
            p.apellidos_nombres ASC,
            p.id_participante DESC
        LIMIT 25
    ";

    $params[] = $q;
    $params[] = $q;
    $params[] = $q;
    $types .= 'sss';

    $stmt = mysqli_prepare($conn, $sql);

    if (!$stmt) {
        return [];
    }

    as_stmt_bind($stmt, $types, $params);
    mysqli_stmt_execute($stmt);
    $res = mysqli_stmt_get_result($stmt);

    $items = [];
    $vistos = [];

    if ($res instanceof mysqli_result) {
        while ($row = mysqli_fetch_assoc($res)) {
            $idParticipante = (int)($row['id_participante'] ?? 0);

            if ($idParticipante <= 0 || isset($vistos[$idParticipante])) {
                continue;
            }

            $vistos[$idParticipante] = true;

            $nivelParticipante = isset($row['nivel_modalidad_ie'])
                ? as_nivel_base((string)$row['nivel_modalidad_ie'])
                : '';

            $blocked = false;
            $blockedMessage = '';
            $canFixLevel = false;

            if ($nivelContexto !== '') {
                if ($nivelParticipante === '') {
                    $blocked = true;
                    $blockedMessage = 'Este participante no tiene nivel detectado. Este salón corresponde a ' . $nivelContexto . '.';
                } elseif ($nivelParticipante !== $nivelContexto) {
                    $blocked = true;
                    $blockedMessage = 'Este estudiante está registrado como ' . $nivelParticipante . ', pero este salón corresponde a ' . $nivelContexto . '.';
                }
            }

            $resultado = null;

            if (!empty($row['id_resultado'])) {
                $resultado = [
                    'id_resultado' => (int)$row['id_resultado'],
                    'correctas' => $row['resultado_correctas'],
                    'incorrectas' => $row['resultado_incorrectas'],
                    'blanco' => $row['resultado_blanco'],
                    'puntaje' => $row['resultado_puntaje'],
                    'tiempo_segundos' => $row['resultado_tiempo_segundos'],
                    'tiempo_texto' => $row['resultado_tiempo_texto'],
                    'observacion' => $row['resultado_observacion']
                ];

                if ($blocked) {
                    $blockedMessage .= ' Ya tiene calificación; no se puede cambiar de nivel desde esta pantalla.';
                }
            } elseif ($blocked) {
                $canFixLevel = true;
            }

            $items[] = [
                'id_participante' => $idParticipante,
                'codigo_participante' => (string)($row['codigo_participante'] ?? ''),
                'documento' => (string)($row['documento'] ?? ''),
                'apellidos_nombres' => (string)($row['apellidos_nombres'] ?? ''),
                'apellido_paterno' => (string)($row['apellido_paterno'] ?? ''),
                'apellido_materno' => (string)($row['apellido_materno'] ?? ''),
                'nombres' => (string)($row['nombres'] ?? ''),
                'institucion_educativa' => (string)($row['institucion_educativa'] ?? ''),
                'tipo_institucion' => (string)($row['tipo_institucion'] ?? ''),
                'id_institucion' => isset($row['id_institucion']) ? (int)$row['id_institucion'] : 0,
                'codigo_modular_ie' => (string)($row['codigo_modular_ie'] ?? ''),
                'nivel_modalidad_ie' => $nivelParticipante,
                'id_grupo' => (int)($row['id_grupo'] ?? 0),
                'grupo_nombre' => (string)($row['grupo_nombre'] ?? ''),
                'grupo_nivel' => isset($row['grupo_nivel']) ? as_nivel_base((string)$row['grupo_nivel']) : '',
                'id_salon' => (int)($row['id_salon'] ?? 0),
                'nombre_salon' => (string)($row['nombre_salon'] ?? ''),
                'codigo_salon' => (string)($row['codigo_salon'] ?? ''),
                'blocked' => $blocked,
                'blocked_message' => $blockedMessage,
                'can_fix_level' => $canFixLevel,
                'nivel_contexto' => $nivelContexto,
                'resultado' => $resultado
            ];
        }
    }

    mysqli_stmt_close($stmt);

    return $items;
}



function as_obtener_participante_id(mysqli $conn, int $idConcurso, int $idParticipante): ?array
{
    if ($idConcurso <= 0 || $idParticipante <= 0) {
        return null;
    }

    $stmt = mysqli_prepare($conn, "
        SELECT
            p.*,
            g.nombre AS grupo_nombre,
            g.nivel AS grupo_nivel,
            s.nombre_salon,
            s.codigo_salon
        FROM concurso_participantes p
        LEFT JOIN concurso_grupos g ON g.id_grupo = p.id_grupo
        LEFT JOIN concurso_salones s ON s.id_salon = p.id_salon
        WHERE p.id_concurso = ?
          AND p.id_participante = ?
          AND p.estado = 'ACTIVO'
        LIMIT 1
    ");

    if (!$stmt) {
        return null;
    }

    mysqli_stmt_bind_param($stmt, 'ii', $idConcurso, $idParticipante);
    mysqli_stmt_execute($stmt);
    $res = mysqli_stmt_get_result($stmt);
    $row = $res instanceof mysqli_result ? mysqli_fetch_assoc($res) : null;
    mysqli_stmt_close($stmt);

    return $row ?: null;
}

function as_payload_participante(array $row, ?array $resultadoParticipante = null, string $nivelContexto = '', bool $blocked = false, string $blockedMessage = '', bool $canFixLevel = false): array
{
    $nivelParticipante = isset($row['nivel_modalidad_ie'])
        ? as_nivel_base((string)$row['nivel_modalidad_ie'])
        : '';

    return [
        'id_participante' => (int)($row['id_participante'] ?? 0),
        'codigo_participante' => (string)($row['codigo_participante'] ?? ''),
        'documento' => (string)($row['documento'] ?? ''),
        'apellidos_nombres' => (string)($row['apellidos_nombres'] ?? ''),
        'apellido_paterno' => (string)($row['apellido_paterno'] ?? ''),
        'apellido_materno' => (string)($row['apellido_materno'] ?? ''),
        'nombres' => (string)($row['nombres'] ?? ''),
        'institucion_educativa' => (string)($row['institucion_educativa'] ?? ''),
        'tipo_institucion' => (string)($row['tipo_institucion'] ?? ''),
        'id_institucion' => isset($row['id_institucion']) ? (int)$row['id_institucion'] : 0,
        'codigo_modular_ie' => (string)($row['codigo_modular_ie'] ?? ''),
        'nivel_modalidad_ie' => $nivelParticipante,
        'id_grupo' => (int)($row['id_grupo'] ?? 0),
        'grupo_nombre' => (string)($row['grupo_nombre'] ?? ''),
        'grupo_nivel' => isset($row['grupo_nivel']) ? as_nivel_base((string)$row['grupo_nivel']) : '',
        'id_salon' => (int)($row['id_salon'] ?? 0),
        'nombre_salon' => (string)($row['nombre_salon'] ?? ''),
        'codigo_salon' => (string)($row['codigo_salon'] ?? ''),
        'blocked' => $blocked,
        'blocked_message' => $blockedMessage,
        'can_fix_level' => $canFixLevel,
        'nivel_contexto' => $nivelContexto,
        'resultado' => $resultadoParticipante
    ];
}


function as_corregir_nombre_participante(
    mysqli $conn,
    int $idConcurso,
    int $idParticipante,
    int $idAcceso,
    string $apellidoPaterno,
    string $apellidoMaterno,
    string $nombresParticipante,
    string $motivo
): array {
    if ($idParticipante <= 0) {
        throw new RuntimeException('Participante inválido para corregir nombre.');
    }

    $apellidoPaterno = as_upper($apellidoPaterno);
    $apellidoMaterno = as_upper($apellidoMaterno);
    $nombresParticipante = as_upper($nombresParticipante);

    $apellidoPaterno = trim((string)preg_replace('/\s+/u', ' ', $apellidoPaterno));
    $apellidoMaterno = trim((string)preg_replace('/\s+/u', ' ', $apellidoMaterno));
    $nombresParticipante = trim((string)preg_replace('/\s+/u', ' ', $nombresParticipante));

    if ($apellidoPaterno === '') {
        throw new RuntimeException('Ingresa el apellido paterno correcto.');
    }

    if ($apellidoMaterno === '') {
        throw new RuntimeException('Ingresa el apellido materno correcto.');
    }

    if ($nombresParticipante === '') {
        throw new RuntimeException('Ingresa los nombres correctos.');
    }

    $participante = as_obtener_participante_id($conn, $idConcurso, $idParticipante);

    if (!$participante) {
        throw new RuntimeException('No se encontró el participante activo.');
    }

    $nombreAnterior = trim((string)($participante['apellidos_nombres'] ?? ''));
    $documento = trim((string)($participante['documento'] ?? ''));

    $apellidosNombres = trim((string)preg_replace(
        '/\s+/u',
        ' ',
        $apellidoPaterno . ' ' . $apellidoMaterno . ' ' . $nombresParticipante
    ));

    if ($apellidosNombres === $nombreAnterior
        && $apellidoPaterno === trim((string)($participante['apellido_paterno'] ?? ''))
        && $apellidoMaterno === trim((string)($participante['apellido_materno'] ?? ''))
        && $nombresParticipante === trim((string)($participante['nombres'] ?? ''))
    ) {
        throw new RuntimeException('No hay cambios para guardar. El nombre ingresado es igual al registrado.');
    }

    $detalleObs = trim($motivo);
    if ($detalleObs === '') {
        $detalleObs = 'Corrección de digitación del nombre del participante.';
    }

    $detalleAuditoria = 'Antes: ' . ($nombreAnterior ?: 'SIN NOMBRE')
        . ' | Ahora: ' . $apellidosNombres
        . ' | Motivo: ' . $detalleObs;

    $sets = [
        'apellidos_nombres = ?',
        'id_acceso_registro = ?',
        "observacion = TRIM(CONCAT(COALESCE(observacion, ''), '\nCORRECCIÓN DE NOMBRE: ', ?))",
        'updated_at = NOW()'
    ];

    $types = 'sis';
    $params = [
        $apellidosNombres,
        $idAcceso,
        $detalleAuditoria
    ];

    if (as_columna_existe($conn, 'concurso_participantes', 'apellido_paterno')) {
        $sets[] = 'apellido_paterno = ?';
        $types .= 's';
        $params[] = $apellidoPaterno;
    }

    if (as_columna_existe($conn, 'concurso_participantes', 'apellido_materno')) {
        $sets[] = 'apellido_materno = ?';
        $types .= 's';
        $params[] = $apellidoMaterno;
    }

    if (as_columna_existe($conn, 'concurso_participantes', 'nombres')) {
        $sets[] = 'nombres = ?';
        $types .= 's';
        $params[] = $nombresParticipante;
    }

    $types .= 'ii';
    $params[] = $idParticipante;
    $params[] = $idConcurso;

    $stmt = mysqli_prepare($conn, "
        UPDATE concurso_participantes
        SET " . implode(",\n            ", $sets) . "
        WHERE id_participante = ?
          AND id_concurso = ?
          AND estado = 'ACTIVO'
        LIMIT 1
    ");

    if (!$stmt) {
        throw new RuntimeException('No se pudo preparar la corrección del nombre.');
    }

    as_stmt_bind($stmt, $types, $params);

    if (!mysqli_stmt_execute($stmt)) {
        $error = mysqli_stmt_error($stmt);
        mysqli_stmt_close($stmt);
        throw new RuntimeException('No se pudo corregir el nombre del participante. ' . $error);
    }

    $afectados = mysqli_stmt_affected_rows($stmt);
    mysqli_stmt_close($stmt);

    if ($afectados <= 0) {
        throw new RuntimeException('No se aplicó la corrección. Verifica que el participante siga activo.');
    }

    cn_auditar(
        $conn,
        $idConcurso,
        (int)($participante['id_grupo'] ?? 0),
        $idAcceso,
        $idParticipante,
        'NOMBRE_PARTICIPANTE_CORREGIDO',
        'Documento: ' . $documento . ' | ' . $detalleAuditoria
    );

    $actualizado = as_obtener_participante_id($conn, $idConcurso, $idParticipante);

    if (!$actualizado) {
        throw new RuntimeException('Se corrigió el nombre, pero no se pudo recargar el participante.');
    }

    return $actualizado;
}


function as_corregir_nivel_y_asignar_participante(
    mysqli $conn,
    int $idConcurso,
    int $idParticipante,
    int $idSalonDestino,
    int $idAcceso,
    string $motivo
): array {
    if ($idParticipante <= 0) {
        throw new RuntimeException('Participante inválido para corregir nivel.');
    }

    if ($idSalonDestino <= 0) {
        throw new RuntimeException('Selecciona primero el salón correcto.');
    }

    $salonDestino = as_obtener_salon($conn, $idConcurso, $idSalonDestino);

    if (!$salonDestino) {
        throw new RuntimeException('El salón destino no está disponible.');
    }

    $idGrupoDestino = (int)$salonDestino['id_grupo'];
    $nivelDestino = as_nivel_base((string)($salonDestino['grupo_nivel'] ?: $salonDestino['grupo_nombre']));

    if ($nivelDestino === '') {
        throw new RuntimeException('No se pudo detectar el nivel del salón destino.');
    }

    $participante = as_obtener_participante_id($conn, $idConcurso, $idParticipante);

    if (!$participante) {
        throw new RuntimeException('No se encontró el participante activo.');
    }

    $idGrupoActual = (int)($participante['id_grupo'] ?? 0);
    $idSalonActual = (int)($participante['id_salon'] ?? 0);
    $nivelActual = isset($participante['nivel_modalidad_ie'])
        ? as_nivel_base((string)$participante['nivel_modalidad_ie'])
        : '';

    $resultadoActual = as_resultado_activo_participante($conn, $idParticipante);

    if ($resultadoActual && $idGrupoActual !== $idGrupoDestino) {
        throw new RuntimeException('Este participante ya tiene calificación. Por seguridad no se puede cambiar de nivel. Primero revisa o elimina su calificación desde administración.');
    }

    $detalleObs = trim($motivo);
    if ($detalleObs === '') {
        $detalleObs = 'Corrección rápida desde asignar_salon.php. Nivel anterior: ' . ($nivelActual ?: 'SIN NIVEL') . '. Nivel nuevo: ' . $nivelDestino . '.';
    }

    if ($idSalonActual !== $idSalonDestino) {
        $cupoSalon = as_bloquear_salon_y_verificar_cupo($conn, $idSalonDestino);

        if (!$cupoSalon['tiene_cupo']) {
            throw new RuntimeException('Este salón ya no tiene cupos disponibles. Cupos usados: ' . $cupoSalon['ocupados'] . '/' . $cupoSalon['capacidad'] . '.');
        }
    }

    $sets = [
        'id_grupo = ?',
        'id_salon = ?',
        'id_acceso_registro = ?',
        "observacion = TRIM(CONCAT(COALESCE(observacion, ''), '\nCORRECCIÓN DE NIVEL/SALÓN: ', ?))",
        'updated_at = NOW()'
    ];

    $types = 'iiis';
    $params = [
        $idGrupoDestino,
        $idSalonDestino,
        $idAcceso,
        $detalleObs
    ];

    if (as_columna_existe($conn, 'concurso_participantes', 'nivel_modalidad_ie')) {
        $sets[] = 'nivel_modalidad_ie = ?';
        $types .= 's';
        $params[] = $nivelDestino;
    }

    if (as_columna_existe($conn, 'concurso_participantes', 'codigo_modular_ie')) {
        $sets[] = 'codigo_modular_ie = NULL';
    }

    $types .= 'ii';
    $params[] = $idParticipante;
    $params[] = $idConcurso;

    $stmt = mysqli_prepare($conn, "
        UPDATE concurso_participantes
        SET " . implode(",\n            ", $sets) . "
        WHERE id_participante = ?
          AND id_concurso = ?
          AND estado = 'ACTIVO'
        LIMIT 1
    ");

    if (!$stmt) {
        throw new RuntimeException('No se pudo preparar la corrección de nivel.');
    }

    as_stmt_bind($stmt, $types, $params);

    if (!mysqli_stmt_execute($stmt)) {
        $error = mysqli_stmt_error($stmt);
        mysqli_stmt_close($stmt);
        throw new RuntimeException('No se pudo corregir el nivel del participante. ' . $error);
    }

    mysqli_stmt_close($stmt);

    if ($resultadoActual && $idGrupoActual === $idGrupoDestino) {
        $stmtRes = mysqli_prepare($conn, "
            UPDATE concurso_resultados
            SET
                id_grupo = ?,
                id_salon = ?,
                id_acceso_registro = ?,
                observacion = TRIM(CONCAT(COALESCE(observacion, ''), '\nCORRECCIÓN DE SALÓN: ', ?)),
                updated_at = NOW()
            WHERE id_participante = ?
              AND id_concurso = ?
              AND estado = 'ACTIVO'
        ");

        if (!$stmtRes) {
            throw new RuntimeException('No se pudo preparar la corrección de resultado.');
        }

        mysqli_stmt_bind_param($stmtRes, 'iiisii', $idGrupoDestino, $idSalonDestino, $idAcceso, $detalleObs, $idParticipante, $idConcurso);

        if (!mysqli_stmt_execute($stmtRes)) {
            $error = mysqli_stmt_error($stmtRes);
            mysqli_stmt_close($stmtRes);
            throw new RuntimeException('No se pudo actualizar el resultado del participante. ' . $error);
        }

        mysqli_stmt_close($stmtRes);
    }

    cn_auditar(
        $conn,
        $idConcurso,
        $idGrupoDestino,
        $idAcceso,
        $idParticipante,
        'NIVEL_SALON_CORREGIDO_DESDE_ASIGNAR',
        'Participante: ' . ($participante['apellidos_nombres'] ?? '')
        . ' | Documento: ' . ($participante['documento'] ?? '')
        . ' | Nivel anterior: ' . ($nivelActual ?: 'SIN NIVEL')
        . ' | Nivel nuevo: ' . $nivelDestino
        . ' | Salón destino: ' . ($salonDestino['nombre_salon'] ?? '')
        . ' | Motivo: ' . $detalleObs
    );

    $actualizado = as_obtener_participante_id($conn, $idConcurso, $idParticipante);
    $resultado = as_resultado_activo_participante($conn, $idParticipante);

    if (!$actualizado) {
        throw new RuntimeException('Se corrigió el nivel, pero no se pudo recargar el participante.');
    }

    return [
        'participante' => $actualizado,
        'resultado' => $resultado,
        'nivel_destino' => $nivelDestino,
        'salon_destino' => $salonDestino
    ];
}


$acceso = cn_validar_acceso($conn, 'index.php');

if ((int)$acceso['puede_registrar_participantes'] !== 1 && !cn_es_admin($acceso)) {
    http_response_code(403);
    die('Tu código no tiene permiso para asignar participantes.');
}

$esAdmin = cn_es_admin($acceso);
$idConcurso = cn_concurso_trabajo($conn, $acceso);

if ($esAdmin && isset($_GET['id_concurso']) && is_numeric($_GET['id_concurso'])) {
    $idConcurso = (int)$_GET['id_concurso'];
}

if ($esAdmin && $_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['id_concurso']) && is_numeric($_POST['id_concurso'])) {
    $idConcurso = (int)$_POST['id_concurso'];
}

if ($idConcurso <= 0) {
    http_response_code(500);
    die('No hay concurso activo disponible.');
}

$concursoTrabajo = cn_datos_concurso($conn, $idConcurso);
$concursosActivos = $esAdmin ? cn_concursos_activos($conn) : [];
$idGrupoFijo = $esAdmin ? 0 : cn_id_grupo_fijo($acceso);
$idSalonFijo = $esAdmin ? 0 : cn_id_salon_fijo($acceso);
$esAccesoSalon = $idSalonFijo > 0 && !$esAdmin;

if (empty($_SESSION['csrf_asignar_salon_nobex'])) {
    $_SESSION['csrf_asignar_salon_nobex'] = bin2hex(random_bytes(32));
}

$csrf = (string)$_SESSION['csrf_asignar_salon_nobex'];

if (isset($_GET['ajax'])) {
    $ajax = as_get('ajax');

    if ($ajax === 'departamentos') {
        $gestion = strtoupper(as_get('gestion', 'PRIVADA'));

        if (!as_tipo_valido($gestion)) {
            as_json(['success' => true, 'items' => []]);
        }

        $items = [];
        $stmt = mysqli_prepare($conn, "
            SELECT DISTINCT departamento
            FROM concurso_instituciones
            WHERE estado = 1
              AND gestion = ?
              AND departamento <> ''
            ORDER BY departamento ASC
        ");

        if ($stmt) {
            mysqli_stmt_bind_param($stmt, 's', $gestion);
            mysqli_stmt_execute($stmt);
            $res = mysqli_stmt_get_result($stmt);

            if ($res instanceof mysqli_result) {
                while ($row = mysqli_fetch_assoc($res)) {
                    $items[] = $row['departamento'];
                }
            }

            mysqli_stmt_close($stmt);
        }

        as_json(['success' => true, 'items' => $items]);
    }

    if ($ajax === 'provincias') {
        $gestion = strtoupper(as_get('gestion', 'PRIVADA'));
        $departamento = as_upper(as_get('departamento'));

        if (!as_tipo_valido($gestion) || $departamento === '') {
            as_json(['success' => true, 'items' => []]);
        }

        $items = [];
        $stmt = mysqli_prepare($conn, "
            SELECT DISTINCT provincia
            FROM concurso_instituciones
            WHERE estado = 1
              AND gestion = ?
              AND departamento = ?
              AND provincia <> ''
            ORDER BY provincia ASC
        ");

        if ($stmt) {
            mysqli_stmt_bind_param($stmt, 'ss', $gestion, $departamento);
            mysqli_stmt_execute($stmt);
            $res = mysqli_stmt_get_result($stmt);

            if ($res instanceof mysqli_result) {
                while ($row = mysqli_fetch_assoc($res)) {
                    $items[] = $row['provincia'];
                }
            }

            mysqli_stmt_close($stmt);
        }

        as_json(['success' => true, 'items' => $items]);
    }

    if ($ajax === 'distritos') {
        $gestion = strtoupper(as_get('gestion', 'PRIVADA'));
        $departamento = as_upper(as_get('departamento'));
        $provincia = as_upper(as_get('provincia'));

        if (!as_tipo_valido($gestion) || $departamento === '' || $provincia === '') {
            as_json(['success' => true, 'items' => []]);
        }

        $items = [];
        $stmt = mysqli_prepare($conn, "
            SELECT DISTINCT distrito
            FROM concurso_instituciones
            WHERE estado = 1
              AND gestion = ?
              AND departamento = ?
              AND provincia = ?
              AND distrito <> ''
            ORDER BY distrito ASC
        ");

        if ($stmt) {
            mysqli_stmt_bind_param($stmt, 'sss', $gestion, $departamento, $provincia);
            mysqli_stmt_execute($stmt);
            $res = mysqli_stmt_get_result($stmt);

            if ($res instanceof mysqli_result) {
                while ($row = mysqli_fetch_assoc($res)) {
                    $items[] = $row['distrito'];
                }
            }

            mysqli_stmt_close($stmt);
        }

        as_json(['success' => true, 'items' => $items]);
    }

    if ($ajax === 'buscar_ie') {
        $q = as_upper(as_get('q'));
        $gestion = strtoupper(as_get('gestion', 'PRIVADA'));
        $departamento = as_upper(as_get('departamento'));
        $provincia = as_upper(as_get('provincia'));
        $distrito = as_upper(as_get('distrito'));
        $idGrupoAjax = (int)as_get('id_grupo');
        $idSalonAjax = (int)as_get('id_salon');

        $nivelContexto = as_nivel_contexto_acceso(
            $conn,
            $idConcurso,
            $idGrupoFijo,
            $idSalonFijo,
            $idGrupoAjax,
            $idSalonAjax
        );

        if (!as_tipo_valido($gestion) || as_strlen($q) < 2) {
            as_json(['success' => true, 'nivel_contexto' => $nivelContexto, 'items' => []]);
        }

        $sql = "
            SELECT
                m.id_modular,
                m.id_institucion,
                m.codigo_modular,
                m.nivel_modalidad,
                i.nombre_institucion,
                i.gestion,
                i.departamento,
                i.provincia,
                i.distrito
            FROM concurso_institucion_modulares m
            INNER JOIN concurso_instituciones i
                ON i.id_institucion = m.id_institucion
            WHERE i.estado = 1
              AND i.gestion = ?
              AND (
                    i.nombre_institucion LIKE CONCAT('%', ?, '%')
                 OR i.nombre_busqueda LIKE CONCAT('%', ?, '%')
              )
        ";

        $types = 'sss';
        $params = [$gestion, $q, $q];

        if ($nivelContexto !== '') {
            $sql .= " AND m.nivel_modalidad LIKE ? ";
            $types .= 's';
            $params[] = '%' . $nivelContexto . '%';
        }

        if ($departamento !== '') {
            $sql .= " AND i.departamento = ? ";
            $types .= 's';
            $params[] = $departamento;
        }

        if ($provincia !== '') {
            $sql .= " AND i.provincia = ? ";
            $types .= 's';
            $params[] = $provincia;
        }

        if ($distrito !== '') {
            $sql .= " AND i.distrito = ? ";
            $types .= 's';
            $params[] = $distrito;
        }

        $sql .= "
            ORDER BY
                CASE WHEN i.nombre_institucion = ? THEN 0 ELSE 1 END,
                i.nombre_institucion ASC,
                CASE
                    WHEN m.nivel_modalidad LIKE 'INICIAL%' THEN 1
                    WHEN m.nivel_modalidad LIKE 'PRIMARIA%' THEN 2
                    WHEN m.nivel_modalidad LIKE 'SECUNDARIA%' THEN 3
                    ELSE 4
                END ASC,
                m.codigo_modular ASC
            LIMIT 40
        ";

        $types .= 's';
        $params[] = $q;

        $stmt = mysqli_prepare($conn, $sql);
        $items = [];

        if ($stmt) {
            as_stmt_bind($stmt, $types, $params);
            mysqli_stmt_execute($stmt);
            $res = mysqli_stmt_get_result($stmt);

            if ($res instanceof mysqli_result) {
                while ($row = mysqli_fetch_assoc($res)) {
                    $nivelBase = as_nivel_base((string)$row['nivel_modalidad']);

                    $label = $row['nombre_institucion']
                        . ' | ' . $nivelBase
                        . ' | Cód. modular: ' . $row['codigo_modular']
                        . ' | ' . $row['gestion']
                        . ' | ' . $row['departamento'] . ' / ' . $row['provincia'] . ' / ' . $row['distrito'];

                    $items[] = [
                        'id_modular' => (int)$row['id_modular'],
                        'id_institucion' => (int)$row['id_institucion'],
                        'codigo_modular' => $row['codigo_modular'],
                        'nivel_modalidad' => $nivelBase,
                        'nivel_modalidad_original' => $row['nivel_modalidad'],
                        'nombre_institucion' => $row['nombre_institucion'],
                        'gestion' => $row['gestion'],
                        'departamento' => $row['departamento'],
                        'provincia' => $row['provincia'],
                        'distrito' => $row['distrito'],
                        'label' => $label
                    ];
                }
            }

            mysqli_stmt_close($stmt);
        }

        as_json([
            'success' => true,
            'nivel_contexto' => $nivelContexto,
            'items' => $items
        ]);
    }


    if ($ajax === 'buscar_participante_nombre') {
        $q = as_get('q');
        $idGrupoAjax = (int)as_get('id_grupo');
        $idSalonAjax = (int)as_get('id_salon');

        $nivelContexto = as_nivel_contexto_acceso(
            $conn,
            $idConcurso,
            $idGrupoFijo,
            $idSalonFijo,
            $idGrupoAjax,
            $idSalonAjax
        );

        if (as_strlen(as_upper($q)) < 2) {
            as_json([
                'success' => true,
                'nivel_contexto' => $nivelContexto,
                'items' => [],
                'message' => 'Escribe mínimo 2 caracteres para buscar.'
            ]);
        }

        $items = as_buscar_participantes_por_nombre($conn, $idConcurso, $q, $nivelContexto);

        as_json([
            'success' => true,
            'nivel_contexto' => $nivelContexto,
            'items' => $items,
            'message' => empty($items) ? 'No se encontraron participantes registrados con esos apellidos, nombres o DNI.' : ''
        ]);
    }


    if ($ajax === 'corregir_nivel_participante') {
        $csrfPost = as_post('csrf_token');

        if (!hash_equals($csrf, $csrfPost)) {
            as_json([
                'success' => false,
                'message' => 'Solicitud no válida. Recarga la página.'
            ]);
        }

        $idParticipante = (int)as_post('id_participante');
        $idSalonAjax = $idSalonFijo > 0 ? $idSalonFijo : (int)as_post('id_salon');
        $motivo = trim(as_post('motivo'));

        $salonAjax = as_obtener_salon($conn, $idConcurso, $idSalonAjax);

        if (!$salonAjax) {
            as_json([
                'success' => false,
                'message' => 'Selecciona un salón válido para corregir el nivel.'
            ]);
        }

        $idGrupoAjax = (int)$salonAjax['id_grupo'];

        if ($idGrupoFijo > 0 && $idGrupoAjax !== $idGrupoFijo) {
            as_json([
                'success' => false,
                'message' => 'El salón seleccionado no pertenece a tu grado asignado.'
            ]);
        }

        $responsableActualAjax = as_responsable_activo_salon($conn, $idSalonAjax);

        if (!$responsableActualAjax) {
            as_json([
                'success' => false,
                'message' => 'Primero debe registrarse el docente responsable de este salón.'
            ]);
        }

        try {
            mysqli_begin_transaction($conn);

            $corregido = as_corregir_nivel_y_asignar_participante(
                $conn,
                $idConcurso,
                $idParticipante,
                $idSalonAjax,
                (int)$acceso['id_acceso'],
                $motivo
            );

            mysqli_commit($conn);

            as_json([
                'success' => true,
                'message' => 'Nivel corregido y participante asignado al salón actual.',
                'data' => as_payload_participante(
                    $corregido['participante'],
                    $corregido['resultado'],
                    (string)$corregido['nivel_destino'],
                    false,
                    '',
                    false
                ),
                'resultado' => $corregido['resultado'],
                'nivel_destino' => $corregido['nivel_destino']
            ]);
        } catch (Throwable $e) {
            mysqli_rollback($conn);

            as_json([
                'success' => false,
                'message' => $e->getMessage()
            ]);
        }
    }


    if ($ajax === 'corregir_nombre_participante') {
        $csrfPost = as_post('csrf_token');

        if (!hash_equals($csrf, $csrfPost)) {
            as_json([
                'success' => false,
                'message' => 'Solicitud no válida. Recarga la página.'
            ]);
        }

        $idParticipante = (int)as_post('id_participante');
        $apellidoPaterno = as_post('apellido_paterno');
        $apellidoMaterno = as_post('apellido_materno');
        $nombresParticipante = as_post('nombres');
        $motivo = trim(as_post('motivo'));

        try {
            mysqli_begin_transaction($conn);

            $actualizado = as_corregir_nombre_participante(
                $conn,
                $idConcurso,
                $idParticipante,
                (int)$acceso['id_acceso'],
                $apellidoPaterno,
                $apellidoMaterno,
                $nombresParticipante,
                $motivo
            );

            mysqli_commit($conn);

            $resultadoParticipante = as_resultado_activo_participante($conn, (int)$actualizado['id_participante']);

            as_json([
                'success' => true,
                'message' => 'Nombre del participante corregido correctamente.',
                'data' => as_payload_participante($actualizado, $resultadoParticipante),
                'resultado' => $resultadoParticipante
            ]);
        } catch (Throwable $e) {
            mysqli_rollback($conn);

            as_json([
                'success' => false,
                'message' => $e->getMessage()
            ]);
        }
    }


    if ($ajax === 'buscar_documento') {
        $documento = trim(as_get('documento'));
        $idGrupoAjax = (int)as_get('id_grupo');
        $idSalonAjax = (int)as_get('id_salon');

        $nivelContexto = as_nivel_contexto_acceso(
            $conn,
            $idConcurso,
            $idGrupoFijo,
            $idSalonFijo,
            $idGrupoAjax,
            $idSalonAjax
        );

        if ($documento === '') {
            as_json(['success' => false, 'exists' => false, 'message' => 'Documento vacío.']);
        }

        $row = as_buscar_participante_documento($conn, $idConcurso, $documento);

        if ($row) {
            $nivelParticipante = isset($row['nivel_modalidad_ie'])
                ? as_nivel_base((string)$row['nivel_modalidad_ie'])
                : '';

            $resultadoParticipante = as_resultado_activo_participante($conn, (int)$row['id_participante']);

            if ($nivelContexto !== '') {
                if ($nivelParticipante === '') {
                    $msgNivel = 'Este estudiante no tiene nivel detectado. Este salón corresponde a ' . $nivelContexto . '. ¿Deseas modificarlo y asignarlo a este salón?';

                    as_json([
                        'success' => true,
                        'exists' => true,
                        'blocked' => true,
                        'can_fix_level' => empty($resultadoParticipante),
                        'nivel_contexto' => $nivelContexto,
                        'nivel_participante' => '',
                        'message' => empty($resultadoParticipante)
                            ? $msgNivel
                            : 'Este estudiante no tiene nivel detectado, pero ya tiene calificación. No se puede cambiar de nivel desde esta pantalla.',
                        'resultado' => $resultadoParticipante,
                        'data' => as_payload_participante($row, $resultadoParticipante, $nivelContexto, true, $msgNivel, empty($resultadoParticipante))
                    ]);
                }

                if ($nivelParticipante !== $nivelContexto) {
                    $msgNivel = 'Este estudiante está registrado como ' . $nivelParticipante . ', pero este salón corresponde a ' . $nivelContexto . '. ¿Deseas modificarlo y asignarlo a este salón?';

                    as_json([
                        'success' => true,
                        'exists' => true,
                        'blocked' => true,
                        'can_fix_level' => empty($resultadoParticipante),
                        'nivel_contexto' => $nivelContexto,
                        'nivel_participante' => $nivelParticipante,
                        'message' => empty($resultadoParticipante)
                            ? $msgNivel
                            : 'Este estudiante está registrado como ' . $nivelParticipante . ', pero ya tiene calificación. No se puede cambiar de nivel desde esta pantalla.',
                        'resultado' => $resultadoParticipante,
                        'data' => as_payload_participante($row, $resultadoParticipante, $nivelContexto, true, $msgNivel, empty($resultadoParticipante))
                    ]);
                }
            }

            as_json([
                'success' => true,
                'exists' => true,
                'blocked' => false,
                'nivel_contexto' => $nivelContexto,
                'message' => 'Participante encontrado en base de datos.',
                'resultado' => $resultadoParticipante,
                'data' => as_payload_participante($row, $resultadoParticipante, $nivelContexto, false, '', false)
            ]);
        }

        as_json([
            'success' => true,
            'exists' => false,
            'blocked' => false,
            'nivel_contexto' => $nivelContexto,
            'message' => 'No está pre-registrado. Puedes crearlo y asignarlo a este salón.'
        ]);
    }

    as_json(['success' => false, 'message' => 'Solicitud AJAX no reconocida.']);
}

if (empty($_SESSION['csrf_asignar_salon_nobex'])) {
    $_SESSION['csrf_asignar_salon_nobex'] = bin2hex(random_bytes(32));
}

$csrf = (string)$_SESSION['csrf_asignar_salon_nobex'];
$mensajeOk = '';
$mensajeError = '';

$grupos = as_grupos_concurso($conn, $idConcurso, $idGrupoFijo);

$idGrupoSeleccion = $idGrupoFijo > 0 ? $idGrupoFijo : (int)(as_get('id_grupo') ?: ($_POST['id_grupo'] ?? 0));
$salones = $idGrupoSeleccion > 0 ? as_salones_grupo($conn, $idConcurso, $idGrupoSeleccion) : [];
$idSalonSeleccion = $idSalonFijo > 0 ? $idSalonFijo : (int)(as_get('id_salon') ?: ($_POST['id_salon'] ?? 0));
$salonTrabajo = $idSalonSeleccion > 0 ? as_obtener_salon($conn, $idConcurso, $idSalonSeleccion) : null;

if ($idSalonFijo > 0 && (!$salonTrabajo || (int)$salonTrabajo['id_salon'] !== $idSalonFijo)) {
    http_response_code(403);
    die('El salón asignado a este código no está disponible.');
}

if ($idSalonFijo > 0 && $salonTrabajo) {
    $idGrupoSeleccion = (int)$salonTrabajo['id_grupo'];
}

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $csrfPost = as_post('csrf_token');
    $accionPost = as_post('accion', 'asignar_participante');

    if (!hash_equals($csrf, $csrfPost)) {
        $mensajeError = 'Solicitud no válida. Recarga la página.';
    } else {
        $idSalonPost = $idSalonFijo > 0 ? $idSalonFijo : (int)as_post('id_salon');
        $salonPost = as_obtener_salon($conn, $idConcurso, $idSalonPost);
        $idGrupoPost = $salonPost ? (int)$salonPost['id_grupo'] : 0;
        $nivelSalon = $salonPost ? as_nivel_base((string)($salonPost['grupo_nivel'] ?: $salonPost['grupo_nombre'])) : '';

        if (!$salonPost) {
            $mensajeError = 'Selecciona un salón válido.';
        } elseif ($idGrupoFijo > 0 && $idGrupoPost !== $idGrupoFijo) {
            $mensajeError = 'El salón seleccionado no pertenece a tu grado asignado.';
        } elseif ($accionPost === 'registrar_responsable') {
            $dniResponsable = trim(as_post('dni_responsable'));
            $nombreResponsable = as_upper(as_post('nombre_responsable'));

            if (!preg_match('/^\d{8}$/', $dniResponsable)) {
                $mensajeError = 'El DNI del docente responsable debe tener 8 dígitos.';
            } elseif ($nombreResponsable === '') {
                $mensajeError = 'Consulta el DNI del docente para autocompletar el nombre antes de confirmar.';
            } else {
                try {
                    mysqli_begin_transaction($conn);

                    $idResponsable = as_registrar_responsable_salon(
                        $conn,
                        $idConcurso,
                        $idGrupoPost,
                        $idSalonPost,
                        (int)$acceso['id_acceso'],
                        $dniResponsable,
                        $nombreResponsable
                    );

                    $mensajeOk = 'Docente responsable registrado correctamente: ' . $nombreResponsable . ' | DNI: ' . $dniResponsable . '.';
                    cn_auditar($conn, $idConcurso, $idGrupoPost, (int)$acceso['id_acceso'], null, 'DOCENTE_RESPONSABLE_SALON_REGISTRADO', $mensajeOk . ' ID responsable: ' . $idResponsable);

                    mysqli_commit($conn);

                    $_SESSION['csrf_asignar_salon_nobex'] = bin2hex(random_bytes(32));
                    $csrf = (string)$_SESSION['csrf_asignar_salon_nobex'];
                } catch (Throwable $e) {
                    mysqli_rollback($conn);
                    $mensajeError = $e->getMessage();
                }
            }
        } else {
            $responsableActual = as_responsable_activo_salon($conn, $idSalonPost);

            $documento = trim(as_post('documento'));
            $apellidoPaterno = as_upper(as_post('apellido_paterno'));
            $apellidoMaterno = as_upper(as_post('apellido_materno'));
            $nombresParticipante = as_upper(as_post('nombres'));
            $apellidosNombres = trim((string)preg_replace('/\s+/u', ' ', $apellidoPaterno . ' ' . $apellidoMaterno . ' ' . $nombresParticipante));
            $tipoInstitucion = strtoupper(as_post('tipo_institucion', 'PRIVADA'));
            $institucionEducativa = as_upper(as_post('institucion_educativa'));
            $idInstitucion = (int)as_post('id_institucion_catalogo');
            $idModular = (int)as_post('id_modular_catalogo');
            $observacion = trim(as_post('observacion'));
            $correctasCalificacionRaw = as_post('correctas');
            $incorrectasCalificacionRaw = as_post('incorrectas');
            $tiempoCalificacionRaw = as_post('tiempo_calificacion');
            $observacionCalificacion = trim(as_post('observacion_calificacion'));
            $documentoGeneradoAutomaticamente = false;

            if ($documento === '') {
                $documento = as_generar_documento_temporal($conn, $idConcurso);
                $documentoGeneradoAutomaticamente = true;
                $observacion = trim($observacion . "\nCÓDIGO TEMPORAL GENERADO AUTOMÁTICAMENTE POR NO PRESENTAR DNI.");
            }

            if (!$responsableActual) {
                $mensajeError = 'Primero debe registrarse el docente responsable de este salón con su DNI.';
            } elseif (!preg_match('/^[0-9A-Za-z\-]{4,30}$/', $documento)) {
                $mensajeError = 'El DNI o documento solo debe contener letras, números o guiones.';
            } elseif (!as_tipo_valido($tipoInstitucion)) {
                $mensajeError = 'Selecciona tipo de institución válido.';
            } else {
                try {
                    mysqli_begin_transaction($conn);

                    $participante = as_buscar_participante_documento($conn, $idConcurso, $documento);
                    $idAcceso = (int)$acceso['id_acceso'];

                    if ($participante) {
                        $idParticipante = (int)$participante['id_participante'];
                        $idGrupoParticipante = (int)($participante['id_grupo'] ?? 0);
                        $idSalonActual = (int)($participante['id_salon'] ?? 0);
                        $nivelParticipante = isset($participante['nivel_modalidad_ie']) ? as_nivel_base((string)$participante['nivel_modalidad_ie']) : '';

                        if ($nivelSalon !== '' && $nivelParticipante === '') {
                            throw new RuntimeException('Este participante existe, pero no tiene nivel detectado. Busca su DNI o nombre y usa la opción "Modificar nivel y asignar a este salón".');
                        }

                        if ($nivelParticipante !== '' && $nivelSalon !== '' && $nivelParticipante !== $nivelSalon) {
                            throw new RuntimeException('Este estudiante está registrado como ' . $nivelParticipante . ', pero este salón corresponde a ' . $nivelSalon . '. Busca su DNI o nombre y confirma la corrección automática de nivel.');
                        }

                        if ($idGrupoParticipante > 0 && $idGrupoParticipante !== $idGrupoPost) {
                            throw new RuntimeException('El participante ya pertenece al grado ' . ($participante['grupo_nombre'] ?: 'registrado') . ' y este salón pertenece a otro grado.');
                        }

                        if ($idSalonActual > 0 && $idSalonActual !== $idSalonPost) {
                            throw new RuntimeException('El participante ya está asignado a otro salón: ' . ($participante['nombre_salon'] ?: 'Salón registrado') . '.');
                        }

                        if ($idSalonActual === $idSalonPost) {
                            $mensajeOk = 'El participante ya estaba asignado a este salón: ' . $participante['apellidos_nombres'] . '.';
                        } else {
                            $cupoSalon = as_bloquear_salon_y_verificar_cupo($conn, $idSalonPost);

                            if (!$cupoSalon['tiene_cupo']) {
                                throw new RuntimeException('Este salón ya no tiene cupos disponibles. Cupos usados: ' . $cupoSalon['ocupados'] . '/' . $cupoSalon['capacidad'] . '.');
                            }

                            $stmtUpd = mysqli_prepare($conn, "
                                UPDATE concurso_participantes
                                SET id_grupo = ?,
                                    id_salon = ?,
                                    id_acceso_registro = ?,
                                    updated_at = NOW()
                                WHERE id_participante = ?
                                  AND id_salon IS NULL
                                  AND estado = 'ACTIVO'
                            ");

                            if (!$stmtUpd) {
                                throw new RuntimeException('No se pudo preparar la asignación de salón.');
                            }

                            mysqli_stmt_bind_param($stmtUpd, 'iiii', $idGrupoPost, $idSalonPost, $idAcceso, $idParticipante);

                            if (!mysqli_stmt_execute($stmtUpd)) {
                                $error = mysqli_stmt_error($stmtUpd);
                                mysqli_stmt_close($stmtUpd);
                                throw new RuntimeException('No se pudo asignar el salón. ' . $error);
                            }

                            $afectados = mysqli_stmt_affected_rows($stmtUpd);
                            mysqli_stmt_close($stmtUpd);

                            if ($afectados <= 0) {
                                throw new RuntimeException('No se pudo asignar porque el participante pudo haber sido tomado por otro salón.');
                            }

                            $mensajeOk = 'Participante asignado correctamente al salón ' . $salonPost['nombre_salon'] . ': ' . $participante['apellidos_nombres'] . '.';
                            cn_auditar($conn, $idConcurso, $idGrupoPost, $idAcceso, $idParticipante, 'SALON_ASIGNADO', $mensajeOk);
                        }
                    } else {
                        if ($apellidoPaterno === '') {
                            throw new RuntimeException('Ingresa el apellido paterno del participante.');
                        }

                        if ($apellidoMaterno === '') {
                            throw new RuntimeException('Ingresa el apellido materno del participante.');
                        }

                        if ($nombresParticipante === '') {
                            throw new RuntimeException('Ingresa los nombres del participante.');
                        }

                        if ($institucionEducativa === '') {
                            throw new RuntimeException('Ingresa el nombre de la I.E.');
                        }

                        $institucion = null;
                        $institucionManual = false;
                        $nombreIeGuardar = $institucionEducativa;
                        $tipoIeGuardar = $tipoInstitucion;
                        $idInstitucionGuardar = null;
                        $codigoModularGuardar = null;
                        $nivelIe = $nivelSalon;

                        if ($idModular > 0) {
                            $institucion = as_institucion_modular_por_id($conn, $idModular, $tipoInstitucion);

                            if (!$institucion) {
                                throw new RuntimeException('La I.E. seleccionada desde el catálogo ya no es válida. Vuelve a buscarla o escribe el nombre manualmente.');
                            }

                            $nivelIe = as_nivel_base((string)$institucion['nivel_modalidad']);

                            if (!in_array($nivelIe, ['INICIAL', 'PRIMARIA', 'SECUNDARIA'], true)) {
                                throw new RuntimeException('El código modular seleccionado no permite identificar si la I.E. es Inicial, Primaria o Secundaria.');
                            }

                            if ($nivelSalon !== '' && $nivelIe !== $nivelSalon) {
                                throw new RuntimeException('El código modular seleccionado corresponde a ' . $nivelIe . ', pero este salón pertenece a ' . $nivelSalon . '.');
                            }

                            $nombreIeGuardar = (string)$institucion['nombre_institucion'];
                            $tipoIeGuardar = (string)$institucion['gestion'];
                            $idInstitucionGuardar = (int)$institucion['id_institucion'];
                            $codigoModularGuardar = (string)$institucion['codigo_modular'];
                        } else {
                            $institucionManual = true;

                            if (as_strlen($institucionEducativa) < 3) {
                                throw new RuntimeException('Escribe un nombre válido de institución educativa.');
                            }

                            if ($nivelSalon === '') {
                                throw new RuntimeException('No se pudo detectar el nivel del salón. Selecciona primero un salón válido.');
                            }

                            $observacion = trim($observacion . "\nI.E. REGISTRADA MANUALMENTE POR NO ENCONTRARSE EN EL CATÁLOGO.");
                        }

                        $cupoSalon = as_bloquear_salon_y_verificar_cupo($conn, $idSalonPost);

                        if (!$cupoSalon['tiene_cupo']) {
                            throw new RuntimeException('Este salón ya no tiene cupos disponibles. Cupos usados: ' . $cupoSalon['ocupados'] . '/' . $cupoSalon['capacidad'] . '.');
                        }

                        $codigoParticipante = as_generar_codigo_participante($conn);

                        $idParticipante = as_insertar_participante(
                            $conn,
                            $idConcurso,
                            $idGrupoPost,
                            $idSalonPost,
                            $idAcceso,
                            $codigoParticipante,
                            $apellidosNombres,
                            $apellidoPaterno,
                            $apellidoMaterno,
                            $nombresParticipante,
                            $nombreIeGuardar,
                            $tipoIeGuardar,
                            $idInstitucionGuardar,
                            $codigoModularGuardar,
                            $nivelIe,
                            $documento,
                            $observacion
                        );

                        $mensajeOk = 'Participante creado y asignado correctamente. Código: ' . $codigoParticipante . ' | Documento: ' . $documento . ' | Nivel: ' . $nivelIe . ' | Salón: ' . $salonPost['nombre_salon'] . '.';

                        if ($institucionManual) {
                            $mensajeOk .= ' La I.E. fue registrada manualmente porque no estaba en el catálogo.';
                        }

                        if ($documentoGeneradoAutomaticamente) {
                            $mensajeOk .= ' Se generó un código temporal porque el estudiante no presentó DNI.';
                        }

                        cn_auditar($conn, $idConcurso, $idGrupoPost, $idAcceso, $idParticipante, 'PARTICIPANTE_CREADO_Y_SALON_ASIGNADO', $mensajeOk);
                    }

                    if (as_debe_guardar_calificacion_directa()) {
                        $tiempoSegundosCalificacion = as_cal_parse_tiempo($tiempoCalificacionRaw);

                        if ($tiempoSegundosCalificacion === -1) {
                            throw new RuntimeException('Tiempo inválido. Usa MM:SS, HH:MM:SS o segundos.');
                        }

                        $resumenCalificacion = as_guardar_calificacion_directa(
                            $conn,
                            $idConcurso,
                            $idGrupoPost,
                            $idSalonPost,
                            $idParticipante,
                            $idAcceso,
                            $correctasCalificacionRaw === '' ? 0 : (int)$correctasCalificacionRaw,
                            $incorrectasCalificacionRaw === '' ? 0 : (int)$incorrectasCalificacionRaw,
                            $tiempoSegundosCalificacion,
                            $observacionCalificacion
                        );

                        $mensajeOk .= ' Calificación guardada: Correctas ' . $resumenCalificacion['correctas']
                            . ', Incorrectas ' . $resumenCalificacion['incorrectas']
                            . ', Blanco ' . $resumenCalificacion['blanco']
                            . ', Puntaje ' . number_format((float)$resumenCalificacion['puntaje'], 2, '.', '')
                            . '.';
                    }

                    mysqli_commit($conn);

                    $_SESSION['csrf_asignar_salon_nobex'] = bin2hex(random_bytes(32));
                    $csrf = (string)$_SESSION['csrf_asignar_salon_nobex'];
                } catch (Throwable $e) {
                    mysqli_rollback($conn);
                    $mensajeError = $e->getMessage();
                }
            }
        }
    }
}

$responsableSalon = $salonTrabajo ? as_responsable_activo_salon($conn, (int)$salonTrabajo['id_salon']) : null;
$requiereResponsable = $salonTrabajo && !$responsableSalon;

$asignados = $idSalonSeleccion > 0 ? as_listar_asignados_salon($conn, $idConcurso, $idSalonSeleccion) : [];
$ambito = cn_ambito_texto($acceso, $concursoTrabajo);
$grupoCalificacion = $idGrupoSeleccion > 0 ? as_obtener_grupo_calificacion($conn, $idConcurso, $idGrupoSeleccion) : null;
$totalPreguntasCalificacion = $grupoCalificacion ? (int)$grupoCalificacion['total_preguntas'] : 0;
$puntosCorrectaCalificacion = $grupoCalificacion ? (float)$grupoCalificacion['puntos_correcta'] : 0.0;
$penalizacionIncorrectaCalificacion = $grupoCalificacion ? (float)$grupoCalificacion['penalizacion_incorrecta'] : 0.0;
$puntosBlancoCalificacion = $grupoCalificacion ? (float)$grupoCalificacion['puntos_blanco'] : 0.0;

?>
<?php
$ocupadosSalonVista = $salonTrabajo ? (int)($salonTrabajo['ocupados'] ?? 0) : 0;
$capacidadSalonVista = $salonTrabajo ? (int)($salonTrabajo['capacidad'] ?? 0) : 0;
$cuposLibresVista = max(0, $capacidadSalonVista - $ocupadosSalonVista);
$porcentajeSalonVista = $capacidadSalonVista > 0 ? min(100, round(($ocupadosSalonVista / $capacidadSalonVista) * 100)) : 0;
$totalAsignadosVista = count($asignados);
$responsableNombreVista = $responsableSalon ? (string)$responsableSalon['nombre_responsable'] : 'Pendiente';
$responsableDniVista = $responsableSalon ? (string)$responsableSalon['dni_responsable'] : '';
?>
<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <title>Asignar salón | NOBEX</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- Bootstrap Icons + Font Awesome + SweetAlert2 -->
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css">
    <link rel="stylesheet" href="assets/css/menu_concurso.css?v=5">
    <script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>

    <style>
        *{box-sizing:border-box}

        :root{
            --red:#dc2626;
            --red-dark:#991b1b;
            --gold:#facc15;
            --blue:#1d4ed8;
            --green:#16a34a;
            --orange:#ea580c;
            --ink:#0f172a;
            --muted:#64748b;
            --line:#e2e8f0;
            --soft:#f8fafc;
            --card:#ffffff;
            --shadow:0 22px 50px rgba(15,23,42,.10);
        }

        body{
            margin:0;
            min-height:100vh;
            font-family:Inter,Arial,Helvetica,sans-serif;
            background:
                radial-gradient(circle at top left,rgba(250,204,21,.15),transparent 32%),
                linear-gradient(135deg,#f8fafc 0%,#eef2ff 45%,#fff7ed 100%);
            color:var(--ink);
        }

        a{text-decoration:none;color:inherit}

        .topbar{
            color:#fff;
            background:
                radial-gradient(circle at 18% 0%,rgba(250,204,21,.28),transparent 28%),
                linear-gradient(135deg,#0f172a,#1f2937 50%,#991b1b);
            border-bottom:7px solid var(--gold);
            box-shadow:0 20px 50px rgba(15,23,42,.22);
        }

        .topbar-inner{
            max-width:1380px;
            margin:0 auto;
            padding:22px 18px;
            display:flex;
            justify-content:space-between;
            gap:16px;
            align-items:center;
        }

        .brand{
            display:flex;
            align-items:center;
            gap:14px;
        }

        .brand-mark{
            width:56px;
            height:56px;
            border-radius:19px;
            display:flex;
            align-items:center;
            justify-content:center;
            background:rgba(255,255,255,.13);
            border:1px solid rgba(255,255,255,.22);
            box-shadow:inset 0 -12px 24px rgba(0,0,0,.18);
            font-size:25px;
        }

        .eyebrow{
            margin:0 0 5px;
            color:#fde68a;
            font-size:12px;
            font-weight:950;
            letter-spacing:.08em;
            text-transform:uppercase;
        }

        .topbar h1{
            margin:0;
            font-size:clamp(25px,3.2vw,36px);
            line-height:1.05;
            letter-spacing:-.04em;
            font-weight:950;
        }

        .topbar p{
            margin:7px 0 0;
            color:#e5e7eb;
            font-weight:850;
            font-size:14px;
        }

        .top-actions{
            display:flex;
            gap:10px;
            flex-wrap:wrap;
            justify-content:flex-end;
        }

        .btn-top{
            display:inline-flex;
            align-items:center;
            justify-content:center;
            gap:8px;
            border-radius:15px;
            padding:12px 15px;
            color:#fff;
            border:1px solid rgba(255,255,255,.28);
            background:rgba(255,255,255,.10);
            font-weight:950;
            white-space:nowrap;
        }

        .btn-top:hover{background:rgba(255,255,255,.18)}

        .container{
            max-width:1380px;
            margin:22px auto;
            padding:0 18px 42px;
        }

        .context-grid{
            display:grid;
            grid-template-columns:repeat(4,1fr);
            gap:14px;
            margin-bottom:18px;
        }

        .metric{
            background:rgba(255,255,255,.95);
            border:1px solid rgba(226,232,240,.95);
            border-radius:23px;
            padding:17px;
            box-shadow:0 15px 36px rgba(15,23,42,.08);
            display:flex;
            gap:13px;
            align-items:center;
            min-height:102px;
        }

        .metric-icon{
            width:48px;
            height:48px;
            border-radius:17px;
            display:flex;
            align-items:center;
            justify-content:center;
            font-size:20px;
            flex:0 0 auto;
        }

        .icon-blue{background:#dbeafe;color:#1d4ed8}
        .icon-green{background:#dcfce7;color:#166534}
        .icon-yellow{background:#fef3c7;color:#92400e}
        .icon-red{background:#fee2e2;color:#991b1b}
        .icon-gray{background:#f1f5f9;color:#475569}

        .metric strong{
            display:block;
            font-size:23px;
            line-height:1;
            color:#0f172a;
            font-weight:950;
            letter-spacing:-.03em;
        }

        .metric span{
            display:block;
            color:#64748b;
            font-size:12px;
            font-weight:950;
            text-transform:uppercase;
            margin-top:7px;
        }

        .grid{
            display:grid;
            grid-template-columns:1fr;
            gap:18px;
            align-items:start;
        }

        .asignar-layout{
            display:grid;
            grid-template-columns:minmax(0,1.55fr) minmax(300px,.9fr);
            gap:18px;
            align-items:start;
        }

        .asignar-main{
            display:grid;
            gap:16px;
        }

        .aside-summary{
            border:1px solid #e2e8f0;
            border-radius:20px;
            background:linear-gradient(180deg,#fff,#f8fafc);
            padding:16px;
            display:grid;
            gap:12px;
            position:sticky;
            top:18px;
        }

        .aside-block{
            border:1px solid #e2e8f0;
            border-radius:16px;
            background:#fff;
            padding:13px 14px;
        }

        .aside-label{
            display:block;
            color:#64748b;
            font-size:11px;
            font-weight:950;
            text-transform:uppercase;
            margin-bottom:6px;
            letter-spacing:.04em;
        }

        .aside-value{
            color:#0f172a;
            font-weight:950;
            line-height:1.35;
            font-size:15px;
        }

        .mini-metrics{
            display:grid;
            grid-template-columns:1fr 1fr;
            gap:10px;
        }

        .mini-metric{
            border-radius:16px;
            padding:12px;
            color:#fff;
        }

        .mini-metric strong{
            display:block;
            font-size:22px;
            line-height:1;
            font-weight:950;
            margin-bottom:6px;
        }

        .mini-metric span{
            font-size:12px;
            font-weight:850;
            opacity:.95;
        }

        .mini-metric.blue{background:linear-gradient(135deg,#2563eb,#1d4ed8)}
        .mini-metric.green{background:linear-gradient(135deg,#16a34a,#15803d)}
        .mini-metric.orange{background:linear-gradient(135deg,#f59e0b,#ea580c)}
        .mini-metric.red{background:linear-gradient(135deg,#ef4444,#dc2626)}

        .section-card{
            border:1px solid #e2e8f0;
            border-radius:20px;
            background:#fff;
            overflow:hidden;
        }

        .section-card-head{
            display:flex;
            justify-content:space-between;
            gap:12px;
            align-items:center;
            padding:14px 16px;
            background:linear-gradient(90deg,#fff1f2,#fff);
            border-bottom:1px solid #e2e8f0;
        }

        .section-card-head h3{
            margin:0;
            font-size:17px;
            font-weight:950;
            color:#991b1b;
        }

        .section-card-head p{
            margin:4px 0 0;
            color:#64748b;
            font-size:12px;
            font-weight:800;
        }

        .section-card-body{padding:16px}

        .card{
            background:rgba(255,255,255,.96);
            border:1px solid rgba(226,232,240,.98);
            border-radius:26px;
            box-shadow:var(--shadow);
            backdrop-filter:blur(8px);
            overflow:hidden;
        }

        .card-pad{padding:22px}

        .card-head{
            display:flex;
            justify-content:space-between;
            align-items:center;
            gap:12px;
            padding:22px 22px 0;
        }

        .card-title{
            display:flex;
            gap:12px;
            align-items:center;
        }

        .card-icon{
            width:44px;
            height:44px;
            border-radius:16px;
            display:flex;
            align-items:center;
            justify-content:center;
            color:#dc2626;
            background:#fee2e2;
            font-size:19px;
            flex:0 0 auto;
        }

        h2{
            margin:0;
            font-size:22px;
            font-weight:950;
            letter-spacing:-.03em;
        }

        .subtle{
            margin:5px 0 0;
            color:#64748b;
            font-size:13px;
            font-weight:800;
            line-height:1.45;
        }

        .pill{
            display:inline-flex;
            align-items:center;
            gap:6px;
            border-radius:999px;
            padding:6px 10px;
            font-size:12px;
            font-weight:950;
            white-space:nowrap;
        }

        .pill-blue{background:#dbeafe;color:#1e40af}
        .pill-green{background:#dcfce7;color:#166534}
        .pill-yellow{background:#fef3c7;color:#92400e}
        .pill-red{background:#fee2e2;color:#991b1b}
        .pill-gray{background:#f1f5f9;color:#475569}
        .pill-orange{background:#ffedd5;color:#9a3412}

        .selector-panel{
            background:linear-gradient(180deg,#ffffff,#fbfdff);
            border:1px solid #e2e8f0;
            border-radius:22px;
            padding:16px;
            margin:16px 0 0;
            box-shadow:0 12px 28px rgba(15,23,42,.05);
        }

        .context-panel{
            overflow:hidden;
        }

        .context-head{
            display:flex;
            align-items:flex-start;
            justify-content:space-between;
            gap:14px;
            margin-bottom:14px;
            padding:0 2px;
            flex-wrap:wrap;
        }

        .context-head-main{
            display:flex;
            align-items:center;
            gap:12px;
            min-width:0;
        }

        .context-head-icon{
            width:42px;
            height:42px;
            border-radius:14px;
            display:flex;
            align-items:center;
            justify-content:center;
            background:linear-gradient(135deg,#fee2e2,#fff1f2);
            color:#dc2626;
            box-shadow:inset 0 0 0 1px rgba(220,38,38,.10);
            flex:0 0 auto;
        }

        .context-head h3{
            margin:0;
            font-size:21px;
            line-height:1.1;
            color:#0f172a;
        }

        .context-head p{
            margin:4px 0 0;
            color:#64748b;
            font-size:13px;
            font-weight:800;
        }

        .context-status{
            display:flex;
            align-items:center;
            gap:8px;
            background:#eff6ff;
            color:#1d4ed8;
            border:1px solid #bfdbfe;
            border-radius:999px;
            padding:9px 12px;
            font-size:12px;
            font-weight:950;
            white-space:nowrap;
        }

        .context-grid{
            display:grid;
            grid-template-columns:1.25fr .8fr 1fr;
            gap:12px;
        }

        .context-field{
            background:#ffffff;
            border:1px solid #e5e7eb;
            border-radius:18px;
            padding:12px;
            box-shadow:inset 0 1px 0 rgba(255,255,255,.6);
        }

        .context-field label{
            margin:0 0 8px;
            font-size:12px;
            letter-spacing:.02em;
            text-transform:uppercase;
            color:#334155;
        }

        .context-field select{
            border-radius:14px;
            min-height:44px;
            padding:11px 13px;
            font-weight:800;
            background:#fff;
        }

        .context-caption{
            margin-top:8px;
            font-size:11px;
            color:#64748b;
            font-weight:800;
            line-height:1.35;
        }

        .section-line{
            margin:18px 0 11px;
            padding:12px 14px;
            border-radius:16px;
            background:linear-gradient(135deg,#fff,#f8fafc);
            border:1px solid #e5e7eb;
            color:#111827;
            font-weight:950;
            display:flex;
            gap:9px;
            align-items:center;
        }

        .section-line i{color:#dc2626}

        label{
            display:block;
            font-weight:950;
            margin:13px 0 7px;
            color:#1f2937;
            font-size:13px;
        }

        label i{
            color:#dc2626;
            margin-right:7px;
        }

        input,select,textarea{
            width:100%;
            border:1px solid #dbe3ef;
            border-radius:15px;
            padding:12px 13px;
            font-size:15px;
            outline:none;
            background:#fff;
            min-height:46px;
            color:#0f172a;
        }

        textarea{min-height:78px;resize:vertical}

        input:focus,select:focus,textarea:focus{
            border-color:#dc2626;
            box-shadow:0 0 0 4px rgba(220,38,38,.12);
        }

        input:disabled,select:disabled,input[readonly]{
            background:#f8fafc;
            color:#64748b;
        }

        .input-icon{position:relative}

        .input-icon > i{
            position:absolute;
            left:14px;
            top:50%;
            transform:translateY(-50%);
            color:#64748b;
            pointer-events:none;
            font-size:15px;
        }

        .input-icon input,
        .input-icon select{
            padding-left:42px;
        }

        .input-row{
            display:flex;
            gap:9px;
            align-items:center;
        }

        .input-with-status{position:relative}
        .input-with-status input{padding-right:104px}

        .spin{
            display:none;
            font-size:12px;
            color:#1d4ed8;
            font-weight:950;
            min-width:78px;
        }

        .input-with-status .spin{
            position:absolute;
            right:12px;
            top:50%;
            transform:translateY(-50%);
            min-width:auto;
            background:#eff6ff;
            color:#1d4ed8;
            padding:5px 8px;
            border-radius:999px;
        }

        .field-ok{border-color:#22c55e!important}
        .field-bad{border-color:#ef4444!important}

        .row2{
            display:grid;
            grid-template-columns:1fr 1fr;
            gap:12px;
        }

        .ie-box{
            background:linear-gradient(135deg,#eff6ff,#ffffff);
            border:1px solid #bfdbfe;
            border-radius:22px;
            padding:16px;
            margin-top:15px;
        }

        .ie-results{
            display:none;
            margin-top:10px;
            border:1px solid #cbd5e1;
            border-radius:18px;
            overflow:hidden;
            background:#fff;
            max-height:290px;
            overflow-y:auto;
            box-shadow:0 12px 28px rgba(15,23,42,.10);
        }

        .ie-item{
            padding:13px 14px;
            border-bottom:1px solid #e5e7eb;
            cursor:pointer;
            font-size:13px;
            line-height:1.35;
            color:#0f172a;
            font-weight:850;
        }

        .ie-item:hover{background:#f8fafc}
        .ie-item:last-child{border-bottom:0}

        .ie-selected{
            display:none;
            margin-top:11px;
            background:#ecfdf5;
            border:1px solid #bbf7d0;
            color:#166534;
            border-radius:17px;
            padding:12px;
            font-weight:900;
            line-height:1.45;
            font-size:13px;
        }


        .score-panel{
            background:linear-gradient(135deg,#fff7ed,#ffffff);
            border:1px solid #fed7aa;
            border-radius:22px;
            padding:16px;
            margin-top:14px;
        }

        .score-grid{
            display:grid;
            grid-template-columns:repeat(5,1fr);
            gap:10px;
            align-items:end;
        }

        .score-grid label{
            font-size:12px;
            margin-top:0;
        }

        .score-grid input{
            text-align:center;
            font-weight:950;
        }

        .score-meta{
            display:flex;
            gap:8px;
            flex-wrap:wrap;
            margin:0 0 12px;
        }

        .score-note{
            margin-top:10px;
            color:#9a3412;
            font-size:12px;
            font-weight:850;
            line-height:1.35;
        }

        @media(max-width:900px){
            .score-grid{grid-template-columns:1fr 1fr}
        }

        .notice-mini{
            display:none;
            margin-top:10px;
            border-radius:15px;
            padding:11px 12px;
            font-weight:900;
            line-height:1.35;
            font-size:13px;
        }

        .notice-mini.ok{display:block;background:#dcfce7;color:#166534;border:1px solid #bbf7d0}
        .notice-mini.warn{display:block;background:#fef3c7;color:#92400e;border:1px solid #fde68a}
        .notice-mini.error{display:block;background:#fee2e2;color:#991b1b;border:1px solid #fecaca}

        .responsable-banner{
            border-radius:22px;
            padding:16px;
            margin:18px 0 0;
            display:flex;
            gap:13px;
            align-items:flex-start;
        }

        .responsable-banner.ok{
            background:linear-gradient(135deg,#ecfdf5,#fff);
            border:1px solid #bbf7d0;
            color:#166534;
        }

        .responsable-banner.pending{
            background:linear-gradient(135deg,#fff7ed,#fff);
            border:1px solid #fed7aa;
            color:#9a3412;
        }

        .responsable-banner i{
            width:42px;
            height:42px;
            border-radius:15px;
            display:flex;
            align-items:center;
            justify-content:center;
            flex:0 0 auto;
            background:rgba(255,255,255,.75);
            font-size:18px;
        }

        .responsable-banner h3{
            margin:0 0 5px;
            font-size:17px;
            font-weight:950;
        }

        .responsable-banner p{
            margin:0;
            font-weight:850;
            line-height:1.45;
        }

        .btn{
            width:100%;
            border:0;
            border-radius:17px;
            color:#fff;
            font-weight:950;
            padding:14px 16px;
            cursor:pointer;
            font-size:15px;
            display:flex;
            justify-content:center;
            align-items:center;
            gap:9px;
            text-decoration:none;
        }

        .btn-primary{
            background:linear-gradient(135deg,#1d4ed8,#2563eb);
            box-shadow:0 14px 28px rgba(37,99,235,.24);
            margin-top:17px;
        }

        .btn-primary:hover{background:linear-gradient(135deg,#1e40af,#1d4ed8)}

        .btn-secondary{
            background:linear-gradient(135deg,#475569,#1f2937);
            margin-top:9px;
        }

        .btn:disabled{
            background:#9ca3af!important;
            box-shadow:none;
            cursor:not-allowed;
        }

        .table-toolbar{
            display:flex;
            justify-content:space-between;
            gap:12px;
            align-items:center;
            flex-wrap:wrap;
            padding:18px 22px 0;
        }

        .search-box{
            position:relative;
            max-width:350px;
            min-width:230px;
            flex:1;
        }

        .search-box i{
            position:absolute;
            left:13px;
            top:50%;
            transform:translateY(-50%);
            color:#64748b;
        }

        .search-box input{
            min-height:42px;
            padding-left:40px;
        }

        .table-wrap{
            overflow:auto;
            padding:14px 22px 22px;
        }

        table{
            width:100%;
            border-collapse:separate;
            border-spacing:0 8px;
            font-size:14px;
            min-width:940px;
        }

        th,td{
            padding:11px 10px;
            text-align:left;
            vertical-align:top;
        }

        th{
            border:0;
            background:transparent;
            color:#64748b;
            font-size:12px;
            text-transform:uppercase;
            white-space:nowrap;
        }

        td{
            background:#fff;
            border-top:1px solid #e5e7eb;
            border-bottom:1px solid #e5e7eb;
        }

        tbody tr td:first-child{
            border-left:1px solid #e5e7eb;
            border-radius:14px 0 0 14px;
        }

        tbody tr td:last-child{
            border-right:1px solid #e5e7eb;
            border-radius:0 14px 14px 0;
        }

        tbody tr:hover td{background:#f8fafc}

        .codigo{
            color:#1d4ed8;
            font-weight:950;
            white-space:nowrap;
        }

        .student{
            color:#0f172a;
            font-weight:950;
        }

        .school{
            max-width:250px;
            color:#475569;
            font-weight:800;
        }

        .empty{
            margin:22px;
            padding:28px;
            border:1px dashed #cbd5e1;
            border-radius:20px;
            background:#f8fafc;
            color:#475569;
            font-weight:900;
            text-align:center;
            line-height:1.45;
        }

        .empty i{
            display:block;
            font-size:34px;
            color:#94a3b8;
            margin-bottom:9px;
        }

        .progress-box{
            margin-top:11px;
        }

        .progress-line{
            height:11px;
            background:#e5e7eb;
            border-radius:999px;
            overflow:hidden;
        }

        .progress-bar{
            height:100%;
            background:linear-gradient(90deg,#1d4ed8,#2563eb);
            width:<?php echo as_h($porcentajeSalonVista); ?>%;
        }

        .swal2-popup{border-radius:24px!important}
        .swal2-title{letter-spacing:-.02em!important}

        @media(max-width:1120px){
            .grid{grid-template-columns:1fr}
            .context-grid{grid-template-columns:repeat(2,1fr)}
        }

        @media(max-width:720px){
            .topbar-inner{flex-direction:column;align-items:flex-start}
            .top-actions{justify-content:flex-start}
            .context-grid{grid-template-columns:1fr}
            .row2{grid-template-columns:1fr}
            .card-head{align-items:flex-start;flex-direction:column}
            .input-row{display:block}
            .input-row .spin{margin-top:7px}
        }
    
        /* ============================================================
           MEJORA FINAL ASIGNAR SALÓN
           Diseño compacto tipo ficha, sin cambiar lógica principal.
        ============================================================ */

        .container{
            max-width:1720px!important;
            margin:18px auto!important;
            padding:0 22px 36px!important;
        }

        .topbar-inner{
            max-width:1720px!important;
            padding:16px 22px!important;
        }

        .brand-mark{
            width:48px!important;
            height:48px!important;
            border-radius:16px!important;
        }

        .topbar h1{
            font-size:30px!important;
        }

        .topbar p{
            margin-top:4px!important;
        }

        .context-grid{
            grid-template-columns:repeat(4,minmax(0,1fr))!important;
            gap:10px!important;
            margin-bottom:14px!important;
        }

        .metric{
            min-height:78px!important;
            padding:12px 14px!important;
            border-radius:18px!important;
        }

        .metric-icon{
            width:40px!important;
            height:40px!important;
            border-radius:13px!important;
            font-size:17px!important;
        }

        .metric strong{
            font-size:19px!important;
        }

        .metric span{
            font-size:11px!important;
        }

        .grid{
            grid-template-columns:1fr!important;
            gap:16px!important;
        }

        .card{
            border-radius:20px!important;
        }

        .card-head{
            padding:15px 18px 0!important;
        }

        .card-pad{
            padding:16px 18px 18px!important;
        }

        .card-icon{
            width:38px!important;
            height:38px!important;
            border-radius:13px!important;
            font-size:16px!important;
        }

        h2{
            font-size:21px!important;
        }

        .subtle{
            font-size:12px!important;
            margin-top:2px!important;
        }

        .selector-panel{
            margin-top:12px!important;
            padding:12px!important;
            border-radius:16px!important;
        }

        label{
            margin:0 0 5px!important;
            font-size:12px!important;
            line-height:1.1!important;
        }

        input,
        select,
        textarea{
            min-height:38px!important;
            height:38px!important;
            border-radius:12px!important;
            padding:8px 11px!important;
            font-size:14px!important;
        }

        textarea{
            height:auto!important;
            min-height:52px!important;
        }

        .input-icon > i{
            left:12px!important;
            font-size:13px!important;
        }

        .input-icon input,
        .input-icon select{
            padding-left:36px!important;
        }

        .input-with-status input{
            padding-right:94px!important;
        }

        .input-with-status .spin{
            right:8px!important;
            padding:4px 8px!important;
            font-size:11px!important;
        }

        .section-line{
            margin:12px 0 8px!important;
            padding:9px 11px!important;
            border-radius:13px!important;
            font-size:13px!important;
        }

        .compact-section-title{
            margin:14px 0 0;
            padding:10px 14px;
            border-radius:16px 16px 0 0;
            background:linear-gradient(135deg,#1d4ed8,#0ea5e9);
            color:#ffffff;
            display:flex;
            align-items:center;
            justify-content:space-between;
            gap:12px;
            font-weight:950;
            letter-spacing:.01em;
        }

        .compact-section-title span{
            display:flex;
            align-items:center;
            gap:8px;
        }

        .compact-section-title small{
            font-size:12px;
            color:#e0f2fe;
            font-weight:900;
            white-space:nowrap;
        }

        .compact-section-title-red{
            background:linear-gradient(135deg,#b91c1c,#ef4444);
        }

        .compact-section-title-score{
            background:linear-gradient(135deg,#ea580c,#f59e0b);
        }

        .form-row{
            display:grid;
            gap:12px;
            padding:14px;
            border:1px solid #dbe3ef;
            border-top:0;
            border-radius:0 0 16px 16px;
            background:#ffffff;
            align-items:end;
        }

        .form-row-participante{
            grid-template-columns:210px minmax(190px,1fr) minmax(190px,1fr) minmax(240px,1.25fr);
        }

        .field-help{
            margin-top:5px;
            color:#64748b;
            font-size:11px;
            font-weight:800;
            line-height:1.15;
        }

        .ie-box{
            background:#ffffff!important;
            border:1px solid #dbe3ef!important;
            border-top:0!important;
            border-radius:0 0 16px 16px!important;
            padding:0!important;
            margin-top:0!important;
        }

        .form-row-ie{
            grid-template-columns:180px minmax(320px,2.2fr) minmax(150px,.8fr) minmax(150px,.8fr) minmax(150px,.8fr);
            border:0;
            border-radius:0;
        }

        .form-row-ie-manual{
            grid-template-columns:190px minmax(420px,1fr)!important;
            border:0!important;
            border-radius:0!important;
            align-items:start!important;
        }

        .ie-box-manual .field-ie .field-help{
            color:#64748b;
            font-size:11px;
            font-weight:800;
            margin-top:5px;
            line-height:1.2;
        }

        .ie-box-manual .ie-results{
            display:none!important;
        }

        .ie-results{
            margin:0 14px 12px!important;
            border-radius:14px!important;
            max-height:250px!important;
            box-shadow:0 10px 22px rgba(15,23,42,.08)!important;
        }

        .ie-item{
            padding:10px 13px!important;
            font-size:13px!important;
            border-left:4px solid #3b82f6;
        }

        .ie-item:hover{
            background:#f8fafc!important;
        }

        .ie-selected{
            margin:0 14px 12px!important;
            border-radius:13px!important;
            padding:10px 12px!important;
            font-size:13px!important;
        }

        .score-panel{
            margin-top:0!important;
            padding:13px 14px!important;
            border-radius:0 0 16px 16px!important;
            border-top:0!important;
        }

        .score-grid{
            grid-template-columns:repeat(5,minmax(100px,1fr))!important;
            gap:9px!important;
        }

        .score-note{
            font-size:12px!important;
            margin-top:8px!important;
        }

        #frmAsignar > label{
            margin-top:12px!important;
        }

        .form-actions{
            display:grid;
            grid-template-columns:minmax(240px,320px) minmax(190px,240px);
            gap:10px;
            align-items:center;
            justify-content:end;
            margin-top:12px;
        }

        .btn{
            min-height:42px!important;
            padding:10px 13px!important;
            border-radius:13px!important;
            font-size:14px!important;
        }

        .btn-primary{
            margin-top:0!important;
            background:linear-gradient(135deg,#dc2626,#991b1b)!important;
            box-shadow:0 14px 28px rgba(220,38,38,.22)!important;
        }

        .btn-primary:hover{
            background:linear-gradient(135deg,#b91c1c,#7f1d1d)!important;
        }

        .btn-secondary{
            margin-top:0!important;
        }

        .responsable-banner{
            padding:13px 14px!important;
            border-radius:17px!important;
            margin-bottom:12px!important;
        }

        .table-card{
            margin-top:0!important;
        }

        .table-wrap{
            max-height:520px;
            overflow:auto;
        }

        table{
            font-size:13px!important;
        }

        th,
        td{
            padding:9px 10px!important;
        }

        @media(max-width:1180px){
            .context-grid{
                grid-template-columns:repeat(2,minmax(0,1fr))!important;
            }

            .form-row-participante,
            .form-row-ie,
            .form-row-ie-manual,
            .score-grid{
                grid-template-columns:repeat(2,minmax(0,1fr))!important;
            }

            .form-actions{
                grid-template-columns:1fr!important;
            }
        }

        @media(max-width:720px){
            .context-grid,
            .form-row-participante,
            .form-row-ie,
            .form-row-ie-manual,
            .score-grid{
                grid-template-columns:1fr!important;
            }

            .compact-section-title{
                flex-direction:column;
                align-items:flex-start;
            }
        }

    
        /* ============================================================
           BUSCADOR DE PARTICIPANTES POR NOMBRE
        ============================================================ */

        .participante-search-panel{
            margin:0 0 12px;
            padding:12px 14px;
            border:1px solid #dbeafe;
            border-radius:16px;
            background:linear-gradient(180deg,#eff6ff 0%,#ffffff 100%);
            box-shadow:inset 0 0 0 1px rgba(255,255,255,.85);
        }

        .participante-search-panel label{
            margin:0 0 7px!important;
            color:#1e3a8a!important;
            font-size:13px!important;
        }

        .participante-search-panel .field-help{
            margin-top:6px;
            color:#475569;
            font-size:12px;
            font-weight:800;
        }

        .participante-results{
            display:none;
            margin-top:10px;
            border:1px solid #bfdbfe;
            border-radius:15px;
            background:#fff;
            max-height:260px;
            overflow-y:auto;
            box-shadow:0 14px 30px rgba(15,23,42,.10);
        }

        .participante-item{
            width:100%;
            border:0;
            border-bottom:1px solid #e5e7eb;
            background:#fff;
            padding:12px 14px;
            text-align:left;
            cursor:pointer;
            display:block;
            transition:background .15s ease, box-shadow .15s ease;
        }

        .participante-item:last-child{
            border-bottom:0;
        }

        .participante-item:hover{
            background:#f8fafc;
            box-shadow:inset 4px 0 0 #2563eb;
        }

        .participante-item.blocked{
            opacity:.72;
            cursor:not-allowed;
            background:#f8fafc;
        }


        .participante-actions{
            display:flex;
            flex-wrap:wrap;
            gap:8px;
            margin-top:10px;
        }

        .participante-action{
            border:0;
            border-radius:999px;
            padding:8px 11px;
            font-size:11px;
            font-weight:950;
            cursor:pointer;
            display:inline-flex;
            align-items:center;
            gap:6px;
            transition:transform .12s ease, box-shadow .12s ease;
        }

        .participante-action:hover{
            transform:translateY(-1px);
            box-shadow:0 8px 18px rgba(15,23,42,.14);
        }

        .participante-action-use{
            background:#dbeafe;
            color:#1d4ed8;
        }

        .participante-action-correct{
            background:#fef3c7;
            color:#92400e;
        }

        .btn-inline-correct-name{
            margin-top:10px;
            border:1px solid #f59e0b;
            background:#fffbeb;
            color:#92400e;
            border-radius:14px;
            padding:10px 12px;
            font-size:12px;
            font-weight:950;
            cursor:pointer;
            display:inline-flex;
            align-items:center;
            gap:8px;
            box-shadow:0 8px 18px rgba(245,158,11,.16);
        }

        .btn-inline-correct-name:hover{
            background:#fef3c7;
        }


        .participante-main{
            display:flex;
            align-items:center;
            gap:8px;
            color:#0f172a;
            font-weight:950;
            font-size:14px;
        }

        .participante-main i{
            color:#2563eb;
        }

        .participante-meta{
            display:flex;
            flex-wrap:wrap;
            gap:6px;
            margin-top:8px;
        }

        .participante-empty{
            padding:12px 14px;
            color:#64748b;
            font-weight:850;
        }

    
        /* ============================================================
           CALIFICACIÓN OPCIONAL EN PANEL DERECHO
           Se mantiene visible, pero NO obligatoria.
        ============================================================ */
        .score-side-card{
            box-shadow:none;
            border-color:#fed7aa;
        }

        .score-panel-side{
            margin-top:0!important;
            border:0!important;
            border-radius:0!important;
            background:linear-gradient(180deg,#fff7ed,#ffffff)!important;
            padding:15px!important;
        }

        .score-grid-side{
            grid-template-columns:repeat(2,minmax(0,1fr))!important;
            gap:10px!important;
        }

        .score-grid-side > div:nth-child(5){
            grid-column:1 / -1;
        }

        .score-panel-side textarea{
            min-height:66px!important;
            resize:vertical;
        }

        .score-panel-side .score-meta{
            gap:6px!important;
        }

        .score-panel-side .pill{
            font-size:11px!important;
            padding:6px 8px!important;
        }

        .score-panel-side .score-note{
            background:#fffbeb;
            border:1px solid #fde68a;
            border-radius:13px;
            padding:9px 10px;
            color:#92400e;
        }

        @media(max-width:1200px){
            .score-grid-side{
                grid-template-columns:repeat(2,minmax(0,1fr))!important;
            }
        }

        @media(max-width:720px){
            .score-grid-side{
                grid-template-columns:1fr!important;
            }
            .score-grid-side > div:nth-child(5){
                grid-column:auto;
            }
        }

    
        /* ============================================================
           PANEL DERECHO: SOLO CALIFICACIÓN OPCIONAL
           Se eliminó el resumen repetido de concurso/grado/salón/responsable.
        ============================================================ */
        .aside-score-only{
            padding:0!important;
            border:none!important;
            background:transparent!important;
            box-shadow:none!important;
            display:block!important;
        }

        .aside-score-only .score-side-card{
            margin:0!important;
            box-shadow:0 12px 28px rgba(15,23,42,.07)!important;
        }

        .aside-score-only .section-card-head{
            background:linear-gradient(90deg,#fff7ed,#ffffff)!important;
        }

        .aside-score-only .score-panel-side{
            border-radius:0 0 20px 20px!important;
        }

    
        /* ============================================================
           DATOS DEL ESTUDIANTE MÁS COMPACTO
           Búsqueda y DNI en la misma fila; apellidos/nombres abajo.
        ============================================================ */
        .student-entry-grid{
            display:grid;
            grid-template-columns:minmax(360px,1.55fr) minmax(210px,.55fr);
            gap:12px;
            align-items:start;
            margin-bottom:12px;
        }

        .estudiante-search-compact{
            margin:0!important;
            padding:12px 14px!important;
            border-radius:18px!important;
            background:#f8fbff!important;
            border:1px solid #dbeafe!important;
            min-height:126px;
        }

        .estudiante-search-compact label,
        .dni-side-box label{
            margin-top:0!important;
        }

        .estudiante-search-compact .field-help,
        .dni-side-box .field-help{
            font-size:12px!important;
            line-height:1.25!important;
            margin-top:7px!important;
        }

        .dni-side-box{
            min-height:126px;
            border:1px solid #e2e8f0;
            border-radius:18px;
            background:#fff;
            padding:12px 14px;
            margin:0!important;
        }

        .form-row-nombres-compacto{
            grid-template-columns:repeat(3,minmax(0,1fr))!important;
            gap:12px!important;
        }

        .form-row-nombres-compacto .field{
            margin-bottom:0!important;
        }

        @media(max-width:980px){
            .student-entry-grid{
                grid-template-columns:1fr;
            }

            .form-row-nombres-compacto{
                grid-template-columns:1fr!important;
            }

            .estudiante-search-compact,
            .dni-side-box{
                min-height:auto;
            }
        }

    
        /* ============================================================
           BUSCADOR REGISTRADO ULTRA COMPACTO
           Reduce alto, padding y texto del bloque de búsqueda.
        ============================================================ */
        .student-entry-grid{
            grid-template-columns:minmax(280px,1fr) minmax(190px,240px)!important;
            gap:10px!important;
            margin-bottom:10px!important;
        }

        .estudiante-search-compact{
            min-height:0!important;
            height:auto!important;
            padding:9px 11px!important;
            border-radius:14px!important;
            display:grid!important;
            grid-template-columns:86px 1fr auto!important;
            gap:8px!important;
            align-items:center!important;
            background:#f8fbff!important;
        }

        .estudiante-search-compact label{
            margin:0!important;
            font-size:13px!important;
            white-space:nowrap!important;
            display:flex!important;
            align-items:center!important;
            gap:6px!important;
        }

        .estudiante-search-compact .input-icon{
            margin:0!important;
        }

        .estudiante-search-compact input{
            min-height:38px!important;
            height:38px!important;
            padding-top:8px!important;
            padding-bottom:8px!important;
            border-radius:12px!important;
            font-size:14px!important;
        }

        .estudiante-search-compact .field-help{
            margin:0!important;
            font-size:11px!important;
            line-height:1.1!important;
            color:#64748b!important;
            white-space:nowrap!important;
        }

        .estudiante-search-compact .participante-results{
            grid-column:1 / -1!important;
            margin-top:6px!important;
        }

        .dni-side-box{
            min-height:0!important;
            padding:9px 11px!important;
            border-radius:14px!important;
        }

        .dni-side-box input{
            min-height:38px!important;
            height:38px!important;
            border-radius:12px!important;
            font-size:14px!important;
        }

        .dni-side-box .field-help{
            display:none!important;
        }

        @media(max-width:980px){
            .student-entry-grid{
                grid-template-columns:1fr!important;
            }

            .estudiante-search-compact{
                grid-template-columns:1fr!important;
            }

            .estudiante-search-compact .field-help{
                white-space:normal!important;
            }
        }

    
        /* ============================================================
           CONTEXTO: RESPONSABLE EN CABECERA Y SIN REPETICIÓN DE SALÓN
        ============================================================ */
        .topbar{
            align-items:flex-start!important;
        }

        .context-head{
            align-items:stretch!important;
        }

        .context-side-box{
            min-width:320px;
            max-width:430px;
            flex:0 1 430px;
        }

        .context-responsable-card{
            margin:0!important;
            min-height:100%;
            border-radius:18px!important;
            padding:14px 16px!important;
            box-shadow:none!important;
        }

        .context-responsable-card i{
            width:40px!important;
            height:40px!important;
            border-radius:14px!important;
            font-size:17px!important;
        }

        .context-responsable-card h3{
            font-size:15px!important;
            margin-bottom:4px!important;
        }

        .context-responsable-card p{
            font-size:12px!important;
            line-height:1.35!important;
        }

        .context-responsable-card strong{
            font-size:14px;
        }

        @media(max-width:1100px){
            .context-side-box{
                min-width:100%;
                max-width:none;
                flex:1 1 100%;
            }
        }

    
        /* ============================================================
           LIMPIEZA VISUAL: SIN TEXTOS PEQUEÑOS AUXILIARES
           Oculta subtítulos, ayudas y descripciones pequeñas del formulario.
        ============================================================ */
        .subtle,
        .context-head p,
        .context-caption,
        .section-card-head p,
        .field-help,
        .score-note{
            display:none !important;
        }

        .context-head{
            margin-bottom:10px !important;
        }

        .context-field{
            padding:12px !important;
        }

        .section-card-head{
            padding:13px 16px !important;
        }

        .section-card-body{
            padding:14px 16px !important;
        }

        .estudiante-search-compact{
            min-height:auto !important;
            padding:9px 11px !important;
        }

        .dni-side-box{
            min-height:auto !important;
        }

        .score-panel-side{
            padding-top:13px !important;
        }

        .responsable-banner{
            margin:14px 0 0 !important;
        }

    
        /* ============================================================
           RESPONSABLE: MOSTRAR NOMBRE Y DNI
        ============================================================ */
        .context-responsable-card p,
        .responsable-banner p{
            display:block !important;
            visibility:visible !important;
            opacity:1 !important;
            color:#166534 !important;
            margin:2px 0 0 !important;
            font-size:15px !important;
            line-height:1.35 !important;
            font-weight:900 !important;
        }

        .context-responsable-card strong,
        .responsable-banner strong{
            display:inline !important;
            color:#166534 !important;
            font-size:18px !important;
            font-weight:950 !important;
        }

    
        /* ============================================================
           RESPONSABLE MÁS COMPACTO
           Reduce tamaño del bloque verde y de sus textos.
        ============================================================ */
        .context-side-box{
            min-width:280px !important;
            max-width:360px !important;
            flex:0 1 360px !important;
        }

        .context-responsable-card,
        .responsable-banner.context-responsable-card{
            padding:10px 12px !important;
            border-radius:16px !important;
        }

        .context-responsable-card i,
        .responsable-banner.context-responsable-card i{
            width:34px !important;
            height:34px !important;
            font-size:15px !important;
            border-radius:12px !important;
        }

        .context-responsable-card h3,
        .responsable-banner.context-responsable-card h3{
            font-size:13px !important;
            margin:0 0 2px !important;
            line-height:1.2 !important;
        }

        .context-responsable-card p,
        .responsable-banner.context-responsable-card p{
            font-size:12px !important;
            line-height:1.25 !important;
            margin:0 !important;
        }

        .context-responsable-card strong,
        .responsable-banner.context-responsable-card strong{
            font-size:14px !important;
            line-height:1.2 !important;
        }

        @media(max-width:1100px){
            .context-side-box{
                min-width:100% !important;
                max-width:none !important;
                flex:1 1 100% !important;
            }
        }

    
        /* ============================================================
           RESPONSABLE SIN SOBREPOSICIÓN
           Corrige el bloque verde para que no se encime con el contexto.
        ============================================================ */
        .context-head{
            display:grid !important;
            grid-template-columns:minmax(0,1fr) minmax(280px,360px) !important;
            gap:14px !important;
            align-items:start !important;
            margin-bottom:18px !important;
        }

        .context-head-main{
            min-width:0 !important;
            align-self:center;
        }

        .context-side-box{
            min-width:0 !important;
            max-width:none !important;
            width:100% !important;
            flex:none !important;
            align-self:start !important;
        }

        .context-responsable-card,
        .responsable-banner.context-responsable-card{
            margin:0 !important;
            width:100% !important;
        }

        @media(max-width:1100px){
            .context-head{
                grid-template-columns:1fr !important;
            }

            .context-side-box{
                width:100% !important;
            }
        }

    
        /* ============================================================
           ALINEAR BUSCAR Y DNI
           Ambos bloques en una misma línea visual y misma altura.
        ============================================================ */
        .student-entry-grid{
            align-items:stretch !important;
            grid-template-columns:minmax(0,1fr) 330px !important;
            gap:14px !important;
        }

        .estudiante-search-compact,
        .dni-side-box{
            min-height:auto !important;
            height:auto !important;
            padding:12px 14px !important;
            border-radius:18px !important;
            display:grid !important;
            grid-template-columns:110px minmax(0,1fr) !important;
            gap:10px !important;
            align-items:center !important;
        }

        .estudiante-search-compact{
            background:#f8fbff !important;
        }

        .dni-side-box{
            background:#fff !important;
            border:1px solid #dbe4f0 !important;
        }

        .estudiante-search-compact label,
        .dni-side-box label{
            margin:0 !important;
            min-height:40px !important;
            display:flex !important;
            align-items:center !important;
            font-size:13px !important;
            white-space:nowrap !important;
        }

        .estudiante-search-compact .input-icon,
        .dni-side-box .input-icon{
            margin:0 !important;
        }

        .estudiante-search-compact input,
        .dni-side-box input{
            min-height:40px !important;
            height:40px !important;
        }

        .estudiante-search-compact .field-help,
        .dni-side-box .field-help{
            display:none !important;
        }

        .estudiante-search-compact .participante-results{
            grid-column:1 / -1 !important;
            margin-top:2px !important;
        }

        @media(max-width:980px){
            .student-entry-grid{
                grid-template-columns:1fr !important;
            }

            .estudiante-search-compact,
            .dni-side-box{
                grid-template-columns:100px minmax(0,1fr) !important;
            }
        }

    
        /* ============================================================
           RESULTADOS PARTICIPANTE: NO BUSCAR NI DESTACAR I.E.
           La búsqueda es por DNI, código, apellidos y nombres.
           La institución queda como dato secundario, no como criterio de búsqueda.
        ============================================================ */
        .participante-item .pill-school,
        .participante-item .participante-school,
        .participante-item [data-school],
        .participante-meta .school{
            font-size:11px !important;
            color:#64748b !important;
            background:#f8fafc !important;
            border-color:#e2e8f0 !important;
            font-weight:800 !important;
        }

        .participante-results::before{
            content:"Resultados por participante";
            display:block;
            padding:8px 14px;
            font-size:11px;
            font-weight:950;
            letter-spacing:.04em;
            text-transform:uppercase;
            color:#475569;
            background:#f8fafc;
            border-bottom:1px solid #e2e8f0;
        }

    
        /* ============================================================
           BUSCAR Y DNI SIN TEXTO RECORTADO
           Más espacio útil para el input y mejor ajuste visual.
        ============================================================ */
        .student-entry-grid{
            grid-template-columns:minmax(0,1fr) minmax(290px,360px) !important;
            gap:14px !important;
            align-items:start !important;
        }

        .estudiante-search-compact,
        .dni-side-box{
            grid-template-columns:84px minmax(0,1fr) !important;
            padding:10px 12px !important;
            column-gap:10px !important;
        }

        .estudiante-search-compact label,
        .dni-side-box label{
            font-size:12px !important;
            font-weight:900 !important;
            min-height:38px !important;
            line-height:1.1 !important;
        }

        .estudiante-search-compact .input-icon,
        .dni-side-box .input-icon{
            min-width:0 !important;
            width:100% !important;
        }

        .estudiante-search-compact input,
        .dni-side-box input{
            width:100% !important;
            min-width:0 !important;
            min-height:38px !important;
            height:38px !important;
            font-size:14px !important;
            padding-right:12px !important;
            box-sizing:border-box !important;
        }

        .dni-side-box input{
            letter-spacing:0 !important;
            font-weight:700 !important;
        }

        .estudiante-search-compact .participante-results{
            margin-top:8px !important;
        }

        @media(max-width:980px){
            .student-entry-grid{
                grid-template-columns:1fr !important;
            }

            .estudiante-search-compact,
            .dni-side-box{
                grid-template-columns:80px minmax(0,1fr) !important;
            }
        }

    
        /* ============================================================
           RESPONSIVE TOTAL: PC, LAPTOP, TABLET Y CELULAR
           Evita recortes, sobreposiciones y scroll horizontal innecesario.
        ============================================================ */

        html,
        body{
            width:100% !important;
            max-width:100% !important;
            overflow-x:hidden !important;
        }

        body{
            -webkit-text-size-adjust:100%;
            text-size-adjust:100%;
        }

        img,
        svg,
        video,
        canvas{
            max-width:100%;
            height:auto;
        }

        .container{
            width:min(100% - 24px, 1480px) !important;
            max-width:1480px !important;
            margin-left:auto !important;
            margin-right:auto !important;
        }

        .topbar,
        .card,
        .metric,
        .selector-panel,
        .section-card,
        .context-field,
        .responsable-banner,
        .participante-search-panel,
        .dni-side-box,
        .ie-box,
        .score-panel{
            max-width:100% !important;
        }

        .topbar-inner,
        .card-head,
        .context-head,
        .section-card-head,
        .form-actions{
            min-width:0 !important;
        }

        input,
        select,
        textarea,
        button{
            max-width:100% !important;
        }

        select{
            text-overflow:ellipsis;
        }

        .input-icon,
        .input-with-status{
            min-width:0 !important;
        }

        .input-icon input,
        .input-icon select,
        .input-with-status input{
            min-width:0 !important;
        }

        .table-wrap{
            width:100% !important;
            overflow-x:auto !important;
            -webkit-overflow-scrolling:touch;
        }

        .table-wrap table{
            width:100% !important;
        }

        .participante-results,
        .ie-results{
            max-width:100% !important;
            overflow-x:hidden !important;
        }

        .participante-item,
        .ie-item{
            word-break:break-word;
            overflow-wrap:anywhere;
        }

        .participant-name,
        .student,
        .school,
        .codigo{
            overflow-wrap:anywhere;
        }

        @media(max-width:1320px){
            .container{
                width:min(100% - 20px, 1280px) !important;
            }

            .asignar-layout{
                grid-template-columns:minmax(0,1fr) minmax(300px,360px) !important;
                gap:14px !important;
            }

            .context-grid{
                grid-template-columns:1.1fr .8fr 1fr !important;
            }
        }

        @media(max-width:1180px){
            .topbar-inner{
                display:flex !important;
                flex-wrap:wrap !important;
                gap:14px !important;
            }

            .top-actions{
                width:100% !important;
                justify-content:flex-start !important;
                flex-wrap:wrap !important;
            }

            .context-head{
                grid-template-columns:1fr !important;
            }

            .context-side-box{
                width:100% !important;
                max-width:none !important;
            }

            .context-grid{
                grid-template-columns:1fr 1fr !important;
            }

            .context-field:first-child{
                grid-column:1 / -1 !important;
            }

            .asignar-layout{
                grid-template-columns:1fr !important;
            }

            .aside-score-only,
            .aside-summary{
                position:static !important;
                width:100% !important;
            }

            .score-grid-side{
                grid-template-columns:repeat(5,minmax(90px,1fr)) !important;
            }
        }

        @media(max-width:980px){
            .container{
                width:min(100% - 16px, 960px) !important;
            }

            .context-grid{
                grid-template-columns:1fr !important;
            }

            .context-field:first-child{
                grid-column:auto !important;
            }

            .student-entry-grid{
                grid-template-columns:1fr !important;
            }

            .estudiante-search-compact,
            .dni-side-box{
                grid-template-columns:92px minmax(0,1fr) !important;
            }

            .form-row-participante,
            .form-row-nombres-compacto,
            .form-row-ie-manual{
                grid-template-columns:1fr 1fr !important;
            }

            .form-row-nombres-compacto .field-nombres,
            .form-row-ie-manual .field-ie{
                grid-column:1 / -1 !important;
            }

            .score-grid-side,
            .score-grid{
                grid-template-columns:repeat(2,minmax(0,1fr)) !important;
            }

            .score-grid-side > div:nth-child(5),
            .score-grid > div:nth-child(5){
                grid-column:1 / -1 !important;
            }
        }

        @media(max-width:760px){
            .container{
                width:100% !important;
                padding-left:10px !important;
                padding-right:10px !important;
            }

            .topbar{
                border-radius:0 0 22px 22px !important;
                padding:14px 12px !important;
            }

            .brand{
                align-items:flex-start !important;
                gap:10px !important;
            }

            .brand-mark{
                width:44px !important;
                height:44px !important;
                border-radius:15px !important;
                font-size:20px !important;
                flex:0 0 auto !important;
            }

            .brand h1{
                font-size:25px !important;
                line-height:1.1 !important;
            }

            .brand p{
                font-size:12px !important;
                line-height:1.35 !important;
                overflow-wrap:anywhere !important;
            }

            .top-actions{
                display:grid !important;
                grid-template-columns:1fr 1fr !important;
                gap:8px !important;
            }

            .btn-top{
                width:100% !important;
                justify-content:center !important;
                padding:10px 9px !important;
                font-size:13px !important;
                white-space:normal !important;
            }

            .context-head-main{
                align-items:flex-start !important;
            }

            .context-head-icon{
                width:38px !important;
                height:38px !important;
                border-radius:13px !important;
            }

            .context-head h3{
                font-size:20px !important;
            }

            .context-responsable-card{
                padding:10px 11px !important;
            }

            .card{
                border-radius:20px !important;
            }

            .card-head{
                display:flex !important;
                flex-direction:column !important;
                align-items:flex-start !important;
                gap:10px !important;
                padding:15px !important;
            }

            .card-title{
                width:100% !important;
                min-width:0 !important;
            }

            .card-title h2{
                font-size:21px !important;
                line-height:1.15 !important;
            }

            .card-icon{
                width:40px !important;
                height:40px !important;
                border-radius:14px !important;
                flex:0 0 auto !important;
            }

            .card-pad,
            .section-card-body{
                padding:12px !important;
            }

            .section-card-head{
                padding:12px !important;
                align-items:flex-start !important;
                flex-direction:column !important;
            }

            .section-card-head h3{
                font-size:16px !important;
            }

            .context-field,
            .estudiante-search-compact,
            .dni-side-box{
                padding:10px !important;
                border-radius:15px !important;
            }

            .estudiante-search-compact,
            .dni-side-box{
                grid-template-columns:80px minmax(0,1fr) !important;
                gap:8px !important;
            }

            .estudiante-search-compact label,
            .dni-side-box label{
                font-size:12px !important;
            }

            input,
            select,
            textarea{
                font-size:16px !important; /* evita zoom en iPhone/Android */
                min-height:42px !important;
                height:auto !important;
                border-radius:13px !important;
            }

            .input-icon > i{
                left:12px !important;
            }

            .input-icon input,
            .input-icon select{
                padding-left:38px !important;
            }

            .input-with-status input{
                padding-right:88px !important;
            }

            .spin{
                right:7px !important;
                font-size:10px !important;
                padding:4px 6px !important;
            }

            .form-row,
            .form-row-participante,
            .form-row-nombres-compacto,
            .form-row-ie-manual,
            .score-grid-side,
            .score-grid{
                grid-template-columns:1fr !important;
                gap:10px !important;
            }

            .form-row-nombres-compacto .field-nombres,
            .form-row-ie-manual .field-ie,
            .score-grid-side > div:nth-child(5),
            .score-grid > div:nth-child(5){
                grid-column:auto !important;
            }

            .score-meta{
                display:flex !important;
                gap:6px !important;
            }

            .score-meta .pill{
                font-size:10px !important;
                padding:6px 7px !important;
            }

            .form-actions{
                display:grid !important;
                grid-template-columns:1fr !important;
                gap:9px !important;
            }

            .btn{
                width:100% !important;
                min-height:42px !important;
                font-size:14px !important;
            }

            .table-toolbar{
                display:grid !important;
                grid-template-columns:1fr !important;
                gap:9px !important;
                padding:12px !important;
            }

            .search-box{
                width:100% !important;
            }

            #tablaAsignados{
                min-width:780px !important;
            }
        }

        @media(max-width:520px){
            .container{
                padding-left:8px !important;
                padding-right:8px !important;
            }

            .top-actions{
                grid-template-columns:1fr !important;
            }

            .metric-strip,
            .context-grid{
                grid-template-columns:1fr !important;
            }

            .estudiante-search-compact,
            .dni-side-box{
                grid-template-columns:1fr !important;
            }

            .estudiante-search-compact label,
            .dni-side-box label{
                min-height:auto !important;
                margin-bottom:4px !important;
            }

            .estudiante-search-compact .input-icon,
            .dni-side-box .input-icon{
                width:100% !important;
            }

            .participante-results{
                max-height:300px !important;
            }

            .participante-main{
                font-size:13px !important;
                line-height:1.25 !important;
            }

            .participante-meta{
                gap:5px !important;
            }

            .participante-meta .pill,
            .participante-item .pill{
                font-size:10px !important;
                padding:5px 7px !important;
            }

            .context-responsable-card p,
            .responsable-banner.context-responsable-card p{
                font-size:11px !important;
            }

            .context-responsable-card strong,
            .responsable-banner.context-responsable-card strong{
                font-size:13px !important;
            }

            .school,
            .student{
                max-width:none !important;
            }
        }

    </style>
</head>
<body class="cn-con-sidebar">

<?php include __DIR__ . '/includes/sidebar_concurso.php'; ?>

<header class="topbar">
    <div class="topbar-inner">
        <div class="brand">
            <div class="brand-mark">
                <i class="fa-solid fa-door-open"></i>
            </div>
            <div>
                <p class="eyebrow">NOBEX · Control de ingreso</p>
                <h1>Asignar salón</h1>
                <p><?php echo as_h($ambito); ?> · Código: <?php echo as_h($acceso['codigo']); ?></p>
            </div>
        </div>

        <div class="top-actions">
            <button type="button" class="btn-top" id="btnInfoAcceso">
                <i class="bi bi-info-circle"></i>
                Mi acceso
            </button>
            <a class="btn-top" href="panel.php">
                <i class="bi bi-arrow-left-circle"></i>
                Panel
            </a>
        </div>
    </div>
</header>

<main class="container">
    <section class="context-grid">
        <div class="metric">
            <div class="metric-icon icon-blue"><i class="fa-solid fa-layer-group"></i></div>
            <div>
                <strong><?php echo $salonTrabajo ? as_h($salonTrabajo['grupo_nombre']) : '—'; ?></strong>
                <span>Grado</span>
            </div>
        </div>

        <div class="metric">
            <div class="metric-icon icon-green"><i class="fa-solid fa-door-open"></i></div>
            <div>
                <strong><?php echo $salonTrabajo ? as_h($salonTrabajo['nombre_salon']) : 'Sin salón'; ?></strong>
                <span>Salón</span>
            </div>
        </div>

        <div class="metric">
            <div class="metric-icon icon-yellow"><i class="fa-solid fa-users"></i></div>
            <div>
                <strong><?php echo as_h($ocupadosSalonVista . '/' . $capacidadSalonVista); ?></strong>
                <span>Cupos usados</span>
                <div class="progress-box">
                    <div class="progress-line"><div class="progress-bar"></div></div>
                </div>
            </div>
        </div>

        <div class="metric">
            <div class="metric-icon <?php echo $responsableSalon ? 'icon-green' : 'icon-red'; ?>">
                <i class="fa-solid <?php echo $responsableSalon ? 'fa-user-check' : 'fa-user-clock'; ?>"></i>
            </div>
            <div>
                <strong><?php echo as_h($responsableSalon ? 'Registrado' : 'Pendiente'); ?></strong>
                <span>Responsable</span>
            </div>
        </div>
    </section>

    <div class="grid">
        <section class="card">
            <div class="card-head">
                <div class="card-title">
                    <div class="card-icon">
                        <i class="fa-solid fa-user-check"></i>
                    </div>
                    <div>
                        <h2>Registro de ingreso</h2>
                        <p class="subtle">DNI, I.E. y salón.</p>
                    </div>
                </div>

            </div>

            <div class="card-pad">
                <?php if ($esAdmin): ?>
                    <form method="get" action="" id="frmContexto" class="selector-panel context-panel">
                        <div class="context-head">
                            <div class="context-head-main">
                                <div class="context-head-icon">
                                    <i class="fa-solid fa-sliders"></i>
                                </div>
                                <div>
                                    <h3>Contexto de trabajo</h3>
                                    <p>Todo en un bloque compacto: concurso, grado y salón.</p>
                                </div>
                            </div>

                            <div class="context-side-box">
                                <?php if (!$salonTrabajo): ?>
                                    <div class="responsable-banner pending context-responsable-card">
                                        <i class="fa-solid fa-circle-exclamation"></i>
                                        <div>
                                            <h3>Selecciona un salón</h3>
                                            <p>Elige concurso, grado y salón para continuar.</p>
                                        </div>
                                    </div>
                                <?php elseif ($responsableSalon): ?>
                                    <div class="responsable-banner ok context-responsable-card">
                                        <i class="fa-solid fa-user-check"></i>
                                        <div>
                                            <h3>Responsable registrado</h3>
                                            <p>
                                                <strong><?php echo as_h($responsableSalon['nombre_responsable']); ?></strong><br>
                                                DNI: <?php echo as_h($responsableSalon['dni_responsable']); ?>
                                            </p>
                                        </div>
                                    </div>
                                <?php else: ?>
                                    <div class="responsable-banner pending context-responsable-card">
                                        <i class="fa-solid fa-user-shield"></i>
                                        <div>
                                            <h3>Responsable pendiente</h3>
                                            <p>Registra al docente responsable de este salón.</p>
                                        </div>
                                    </div>
                                <?php endif; ?>
                            </div>
                        </div>

                        <div class="context-grid">
                            <div class="context-field">
                                <label><i class="bi bi-trophy"></i> Concurso</label>
                                <select name="id_concurso" onchange="this.form.submit()">
                                    <?php foreach ($concursosActivos as $c): ?>
                                        <option value="<?php echo as_h($c['id_concurso']); ?>" <?php echo (int)$c['id_concurso'] === $idConcurso ? 'selected' : ''; ?>>
                                            <?php echo as_h(trim($c['nombre'] . ' ' . $c['subtitulo'])); ?>
                                        </option>
                                    <?php endforeach; ?>
                                </select>
                                <div class="context-caption">Selecciona el concurso activo donde harás la asignación.</div>
                            </div>

                            <div class="context-field">
                                <label><i class="bi bi-layers"></i> Grado</label>
                                <select name="id_grupo" onchange="this.form.submit()" required>
                                    <option value="">Seleccionar grado</option>
                                    <?php foreach ($grupos as $grupo): ?>
                                        <option value="<?php echo as_h($grupo['id_grupo']); ?>" <?php echo (int)$grupo['id_grupo'] === $idGrupoSeleccion ? 'selected' : ''; ?>>
                                            <?php echo as_h($grupo['nombre']); ?>
                                        </option>
                                    <?php endforeach; ?>
                                </select>
                                <div class="context-caption">Primero elige el grado para cargar sus salones disponibles.</div>
                            </div>

                            <div class="context-field">
                                <label><i class="bi bi-door-open"></i> Salón</label>
                                <select name="id_salon" onchange="this.form.submit()" required>
                                    <option value="">Seleccionar salón</option>
                                    <?php foreach ($salones as $salon): ?>
                                        <option value="<?php echo as_h($salon['id_salon']); ?>" <?php echo (int)$salon['id_salon'] === $idSalonSeleccion ? 'selected' : ''; ?>>
                                            <?php echo as_h($salon['nombre_salon'] . ' · ' . $salon['codigo_salon'] . ' · ' . (int)$salon['ocupados'] . '/' . (int)$salon['capacidad']); ?>
                                        </option>
                                    <?php endforeach; ?>
                                </select>
                                <div class="context-caption">Código corto y cupos visibles para que se vea más limpio.</div>
                            </div>
                        </div>
                    </form>
                <?php endif; ?>

                <?php if (!$salonTrabajo): ?>
                <?php elseif ($requiereResponsable): ?>
                    <form method="post" action="" id="frmResponsable">
                        <input type="hidden" name="csrf_token" value="<?php echo as_h($csrf); ?>">
                        <input type="hidden" name="accion" value="registrar_responsable">
                        <input type="hidden" name="id_salon" value="<?php echo as_h($idSalonSeleccion); ?>">

                        <div class="section-line">
                            <i class="fa-solid fa-id-card"></i>
                            Docente responsable
                        </div>

                        <label><i class="bi bi-credit-card-2-front"></i> DNI del docente</label>
                        <div class="input-icon input-with-status">
                            <i class="fa-solid fa-id-card"></i>
                            <input type="text" id="dni_responsable" name="dni_responsable" maxlength="8" placeholder="Ingrese DNI" autocomplete="off" required>
                            <span id="docenteSpin" class="spin">Buscando...</span>
                        </div>

                        <label><i class="bi bi-person-vcard"></i> Nombre del docente</label>
                        <div class="input-icon">
                            <i class="fa-solid fa-user-tie"></i>
                            <input type="text" id="nombre_responsable" name="nombre_responsable" maxlength="180" readonly required placeholder="Se completará automáticamente">
                        </div>

                        <div id="docenteAviso" class="notice-mini"></div>

                        <button type="submit" id="btnConfirmarResponsable" class="btn btn-primary" disabled>
                            <i class="fa-solid fa-user-check"></i>
                            Confirmar responsable
                        </button>
                    </form>
                <?php else: ?>
                    <form method="post" action="" id="frmAsignar">
                        <input type="hidden" name="csrf_token" value="<?php echo as_h($csrf); ?>">
                        <input type="hidden" name="accion" value="asignar_participante">
                        <input type="hidden" name="id_concurso" id="id_concurso_actual" value="<?php echo as_h($idConcurso); ?>">
                        <input type="hidden" name="id_grupo" id="id_grupo_actual" value="<?php echo as_h($idGrupoSeleccion); ?>">
                        <input type="hidden" name="id_salon" id="id_salon_actual" value="<?php echo as_h($idSalonSeleccion); ?>">
                        <input type="hidden" name="id_participante_detectado" id="id_participante_detectado" value="">
                        <input type="hidden" name="id_institucion_catalogo" id="id_institucion_catalogo" value="">
                        <input type="hidden" name="id_modular_catalogo" id="id_modular_catalogo" value="">
                        <input type="hidden" name="codigo_modular_ie" id="codigo_modular_ie" value="">
                        <input type="hidden" name="nivel_modalidad_ie" id="nivel_modalidad_ie" value="">

                        <div class="asignar-layout">
                            <div class="asignar-main">
                                <div class="section-card">
                                    <div class="section-card-head">
                                        <div>
                                            <h3><i class="fa-solid fa-address-card"></i> Datos del estudiante</h3>
                                            <p>Búsqueda por nombre o DNI y registro compacto del participante.</p>
                                        </div>
                                        <span class="pill pill-blue"><i class="fa-solid fa-user-check"></i> Asignación</span>
                                    </div>
                                    <div class="section-card-body">
                                        <div class="student-entry-grid">
                                            <div class="participante-search-panel estudiante-search-compact">
                                                <label><i class="fa-solid fa-magnifying-glass"></i> Buscar</label>
                                                <div class="input-icon input-with-status">
                                                    <i class="fa-solid fa-users-viewfinder"></i>
                                                    <input type="text" id="buscar_participante_nombre" placeholder="Apellidos, nombres o DNI" autocomplete="off">
                                                    <span id="participanteSpin" class="spin">Buscando...</span>
                                                </div>
                                                <div class="field-help">Base de datos.</div>
                                                <div id="participanteResultados" class="participante-results" style="display:none"></div>
                                            </div>

                                            <div class="field field-dni dni-side-box">
                                                <label><i class="bi bi-credit-card-2-front"></i> DNI</label>
                                                <div class="input-icon input-with-status">
                                                    <i class="fa-solid fa-id-card"></i>
                                                    <input type="text" id="documento" name="documento" maxlength="8" placeholder="8 dígitos" autocomplete="off">
                                                    <span id="dniSpin" class="spin">Buscando...</span>
                                                </div>
                                                <div class="field-help">Se autocompleta con padrón/API.</div>
                                            </div>
                                        </div>

                                        <div class="form-row form-row-participante form-row-nombres-compacto">
                                            <div class="field">
                                                <label><i class="bi bi-person-vcard"></i> Apellido paterno</label>
                                                <div class="input-icon">
                                                    <i class="fa-solid fa-user"></i>
                                                    <input type="text" id="apellido_paterno" name="apellido_paterno" maxlength="80" placeholder="Apellido paterno" autocomplete="off">
                                                </div>
                                            </div>

                                            <div class="field">
                                                <label><i class="bi bi-person-vcard"></i> Apellido materno</label>
                                                <div class="input-icon">
                                                    <i class="fa-regular fa-user"></i>
                                                    <input type="text" id="apellido_materno" name="apellido_materno" maxlength="80" placeholder="Apellido materno" autocomplete="off">
                                                </div>
                                            </div>

                                            <div class="field field-nombres">
                                                <label><i class="bi bi-person-lines-fill"></i> Nombres</label>
                                                <div class="input-icon">
                                                    <i class="fa-solid fa-signature"></i>
                                                    <input type="text" id="nombres" name="nombres" maxlength="120" placeholder="Nombres" autocomplete="off">
                                                </div>
                                            </div>
                                        </div>

                                        <div id="dniAviso" class="notice-mini"></div>
                                        <button type="button" id="btnCorregirNombreSeleccionado" class="btn-inline-correct-name" style="display:none">
                                            <i class="fa-solid fa-user-pen"></i>
                                            Corregir nombre del estudiante seleccionado
                                        </button>
                                    </div>
                                </div>

                                <div class="section-card">
                                    <div class="section-card-head">
                                        <div>
                                            <h3><i class="fa-solid fa-school"></i> Institución educativa</h3>
                                            <p>Ingreso manual simple del colegio cuando el participante no está pre-registrado.</p>
                                        </div>
                                        <span class="pill pill-red"><i class="fa-solid fa-pen-to-square"></i> Manual</span>
                                    </div>
                                    <div class="section-card-body">
                                        <div class="ie-box ie-box-compact ie-box-manual" style="border:none;padding:0;background:transparent;box-shadow:none">
                                            <div class="form-row form-row-ie-manual">
                                                <div class="field field-tipo">
                                                    <label><i class="bi bi-building"></i> Tipo</label>
                                                    <div class="input-icon">
                                                        <i class="fa-solid fa-layer-group"></i>
                                                        <select name="tipo_institucion" id="tipo_institucion" required>
                                                            <option value="PRIVADA">PRIVADA</option>
                                                            <option value="PUBLICA">PÚBLICA</option>
                                                        </select>
                                                    </div>
                                                </div>

                                                <div class="field field-ie">
                                                    <label><i class="fa-solid fa-school-flag"></i> Nombre del colegio</label>
                                                    <div class="input-icon">
                                                        <i class="fa-solid fa-pen-to-square"></i>
                                                        <input type="text" id="institucion_educativa" name="institucion_educativa" maxlength="180" placeholder="Escribe el nombre completo del colegio" autocomplete="off">
                                                    </div>
                                                    <div class="field-help">Si el participante no está pre-registrado, escribe aquí la I.E. manualmente. No se buscará por departamento, provincia ni código modular.</div>
                                                </div>
                                            </div>

                                            <div id="ieResults" class="ie-results" style="display:none"></div>
                                            <div id="ieSelected" class="ie-selected"></div>
                                        </div>
                                    </div>
                                </div>
                            </div>

                            <aside class="aside-summary aside-score-only">
<div class="section-card score-side-card">
                                    <div class="section-card-head">
                                        <div>
                                            <h3><i class="fa-solid fa-clipboard-check"></i> Calificación directa</h3>
                                            <p>Opcional. Solo se guarda si ingresas correctas o incorrectas.</p>
                                        </div>
                                        <span class="pill pill-orange"><i class="fa-regular fa-circle-check"></i> Opcional</span>
                                    </div>

                                    <div
                                        class="score-panel score-panel-side"
                                        id="scorePanel"
                                        data-total="<?php echo as_h($totalPreguntasCalificacion); ?>"
                                        data-correcta="<?php echo as_h($puntosCorrectaCalificacion); ?>"
                                        data-incorrecta="<?php echo as_h($penalizacionIncorrectaCalificacion); ?>"
                                        data-blanco="<?php echo as_h($puntosBlancoCalificacion); ?>"
                                    >
                                        <div class="score-meta">
                                            <span class="pill pill-orange">
                                                <i class="fa-solid fa-list-ol"></i>
                                                Total: <?php echo as_h($totalPreguntasCalificacion); ?>
                                            </span>
                                            <span class="pill pill-green">
                                                <i class="fa-solid fa-check"></i>
                                                Buena: <?php echo as_h(number_format($puntosCorrectaCalificacion, 2, '.', '')); ?>
                                            </span>
                                            <span class="pill pill-red">
                                                <i class="fa-solid fa-xmark"></i>
                                                Mala: -<?php echo as_h(number_format($penalizacionIncorrectaCalificacion, 2, '.', '')); ?>
                                            </span>
                                            <span class="pill pill-blue">
                                                <i class="fa-regular fa-circle"></i>
                                                Blanco: <?php echo as_h(number_format($puntosBlancoCalificacion, 2, '.', '')); ?>
                                            </span>
                                        </div>

                                        <div class="score-grid score-grid-side">
                                            <div>
                                                <label><i class="bi bi-check-circle"></i> Correctas</label>
                                                <input type="number" id="correctas" name="correctas" min="0" max="<?php echo as_h($totalPreguntasCalificacion); ?>" placeholder="0">
                                            </div>

                                            <div>
                                                <label><i class="bi bi-x-circle"></i> Incorrectas</label>
                                                <input type="number" id="incorrectas" name="incorrectas" min="0" max="<?php echo as_h($totalPreguntasCalificacion); ?>" placeholder="0">
                                            </div>

                                            <div>
                                                <label><i class="bi bi-dash-circle"></i> Blanco</label>
                                                <input type="number" id="blanco" name="blanco_mostrado" readonly value="">
                                            </div>

                                            <div>
                                                <label><i class="bi bi-star-fill"></i> Puntaje</label>
                                                <input type="text" id="puntaje_directo" name="puntaje_mostrado" readonly value="">
                                            </div>

                                            <div>
                                                <label><i class="bi bi-stopwatch"></i> Tiempo</label>
                                                <input type="text" id="tiempo_calificacion" name="tiempo_calificacion" placeholder="MM:SS">
                                            </div>
                                        </div>

                                        <label style="margin-top:12px"><i class="bi bi-chat-left-text"></i> Observación de nota</label>
                                        <textarea name="observacion_calificacion" id="observacion_calificacion" maxlength="1000" placeholder="Opcional"></textarea>

                                        <div class="score-note">
                                            Puedes dejar todo vacío. El sistema asignará salón sin guardar calificación.
                                        </div>
                                    </div>
                                </div>
                            </aside>
                        </div>

                        <label><i class="bi bi-chat-left-text"></i> Observación de asignación</label>
                        <textarea name="observacion" maxlength="1000" placeholder="Opcional"></textarea>


                        <div class="form-actions">
                            <button type="submit" id="btnGuardar" class="btn btn-primary">
                                <i class="fa-solid fa-floppy-disk"></i>
                                Asignar salón
                            </button>
                            <button type="button" class="btn btn-secondary" id="btnLimpiar">
                                <i class="fa-solid fa-eraser"></i>
                                Limpiar formulario
                            </button>
                        </div>
                    </form>
                <?php endif; ?>
            </div>
        </section>

        <?php if (!$esAccesoSalon): ?>
        <section class="card">
            <div class="card-head">
                <div class="card-title">
                    <div class="card-icon">
                        <i class="fa-solid fa-users"></i>
                    </div>
                    <div>
                        <h2>Asignados al salón</h2>
                        <p class="subtle">
                            <?php echo $salonTrabajo ? as_h($salonTrabajo['grupo_nombre'] . ' · ' . $salonTrabajo['nombre_salon']) : 'Sin salón seleccionado'; ?>
                        </p>
                    </div>
                </div>

                <span class="pill <?php echo $cuposLibresVista <= 0 ? 'pill-red' : 'pill-green'; ?>">
                    <i class="fa-solid fa-users"></i>
                    <?php echo as_h($ocupadosSalonVista . '/' . $capacidadSalonVista); ?>
                </span>
            </div>

            <?php if ($salonTrabajo): ?>
                <div class="table-toolbar">
                    <div class="search-box">
                        <i class="fa-solid fa-magnifying-glass"></i>
                        <input type="text" id="buscarAsignado" placeholder="Buscar DNI, nombre o I.E.">
                    </div>

                    <span class="pill pill-gray">
                        <i class="fa-solid fa-database"></i>
                        <?php echo as_h($totalAsignadosVista); ?> registros
                    </span>
                </div>
            <?php endif; ?>

            <?php if (!empty($asignados)): ?>
                <div class="table-wrap">
                    <table id="tablaAsignados">
                        <thead>
                            <tr>
                                <th>Código</th>
                                <th>DNI</th>
                                <th>Participante</th>
                                <th>I.E.</th>
                                <th>Tipo</th>
                                <th>Grado</th>
                                <th>Salón</th>
                                <th>Registro</th>
                            </tr>
                        </thead>
                        <tbody>
                            <?php foreach ($asignados as $p): ?>
                                <?php $tipoClase = ((string)$p['tipo_institucion'] === 'PUBLICA') ? 'pill-blue' : 'pill-red'; ?>
                                <tr>
                                    <td class="codigo"><?php echo as_h($p['codigo_participante']); ?></td>
                                    <td><?php echo as_h($p['documento']); ?></td>
                                    <td class="student"><?php echo as_h($p['apellidos_nombres']); ?></td>
                                    <td class="school"><?php echo as_h($p['institucion_educativa']); ?></td>
                                    <td><span class="pill <?php echo as_h($tipoClase); ?>"><?php echo as_h($p['tipo_institucion']); ?></span></td>
                                    <td><?php echo as_h($p['grupo']); ?></td>
                                    <td><?php echo as_h($p['nombre_salon']); ?></td>
                                    <td><?php echo as_h($p['updated_at'] ?: $p['created_at']); ?></td>
                                </tr>
                            <?php endforeach; ?>
                        </tbody>
                    </table>
                </div>
            <?php else: ?>
                <div class="empty">
                    <i class="fa-regular fa-folder-open"></i>
                    Todavía no hay participantes asignados a este salón.
                </div>
            <?php endif; ?>
        </section>
        <?php endif; ?>
    </div>
</main>

<script>
document.addEventListener('DOMContentLoaded', function () {
    const mensajeServidorOk = <?php echo json_encode($mensajeOk, JSON_UNESCAPED_UNICODE); ?>;
    const mensajeServidorError = <?php echo json_encode($mensajeError, JSON_UNESCAPED_UNICODE); ?>;

    const documento = document.getElementById('documento');
    const buscarParticipanteNombre = document.getElementById('buscar_participante_nombre');
    const participanteResultados = document.getElementById('participanteResultados');
    const participanteSpin = document.getElementById('participanteSpin');
    const apellidoPaterno = document.getElementById('apellido_paterno');
    const apellidoMaterno = document.getElementById('apellido_materno');
    const nombres = document.getElementById('nombres');
    const tipo = document.getElementById('tipo_institucion');
    const ie = document.getElementById('institucion_educativa');
    const idInstitucion = document.getElementById('id_institucion_catalogo');
    const idModular = document.getElementById('id_modular_catalogo');
    const codigoModular = document.getElementById('codigo_modular_ie');
    const nivelModalidad = document.getElementById('nivel_modalidad_ie');
    const idParticipante = document.getElementById('id_participante_detectado');
    const aviso = document.getElementById('dniAviso');
    const spin = document.getElementById('dniSpin');
    const btnGuardar = document.getElementById('btnGuardar');
    const btnLimpiar = document.getElementById('btnLimpiar');
    const dep = document.getElementById('ie_departamento');
    const prov = document.getElementById('ie_provincia');
    const dist = document.getElementById('ie_distrito');
    const results = document.getElementById('ieResults');
    const selected = document.getElementById('ieSelected');
    const dniResponsable = document.getElementById('dni_responsable');
    const nombreResponsable = document.getElementById('nombre_responsable');
    const csrfAsignarSalon = document.querySelector('input[name="csrf_token"]');
    const docenteAviso = document.getElementById('docenteAviso');
    const docenteSpin = document.getElementById('docenteSpin');
    const btnConfirmarResponsable = document.getElementById('btnConfirmarResponsable');
    const frmResponsable = document.getElementById('frmResponsable');
    const frmAsignar = document.getElementById('frmAsignar');
    const btnInfoAcceso = document.getElementById('btnInfoAcceso');
    const buscarAsignado = document.getElementById('buscarAsignado');
    const tablaAsignados = document.getElementById('tablaAsignados');
    const scorePanel = document.getElementById('scorePanel');
    const correctasInput = document.getElementById('correctas');
    const incorrectasInput = document.getElementById('incorrectas');
    const blancoInput = document.getElementById('blanco');
    const puntajeInput = document.getElementById('puntaje_directo');
    const tiempoCalificacion = document.getElementById('tiempo_calificacion');
    const observacionCalificacion = document.getElementById('observacion_calificacion');
    const btnCorregirNombreSeleccionado = document.getElementById('btnCorregirNombreSeleccionado');

    let ieTimer = null;
    let participanteTimer = null;
    let ultimoSweetTexto = '';
    let ultimoSweetTiempo = 0;
    let participanteActual = null;

    // En asignación de salón, cuando el participante no existe en la BD,
    // la institución se ingresa manualmente. No se usa búsqueda por catálogo.
    const BUSQUEDA_IE_CATALOGO_ACTIVA = false;

    function escapeHtml(texto) {
        return String(texto || '')
            .replace(/&/g, '&amp;')
            .replace(/</g, '&lt;')
            .replace(/>/g, '&gt;')
            .replace(/"/g, '&quot;')
            .replace(/'/g, '&#039;');
    }

    async function fetchJsonRapido(url, timeoutMs) {
        const controller = new AbortController();
        const timer = setTimeout(function () {
            controller.abort();
        }, timeoutMs || 3000);

        try {
            const resp = await fetch(url, {
                headers: { 'Accept': 'application/json' },
                cache: 'no-store',
                signal: controller.signal
            });

            return await resp.json();
        } finally {
            clearTimeout(timer);
        }
    }

    function sweet(texto, tipo, tituloPersonalizado) {
        if (!texto || typeof Swal === 'undefined') return;

        const ahora = Date.now();
        const key = String(tipo || '') + '|' + String(texto || '');

        if (key === ultimoSweetTexto && (ahora - ultimoSweetTiempo) < 900) return;

        ultimoSweetTexto = key;
        ultimoSweetTiempo = ahora;

        let icono = 'info';
        let titulo = tituloPersonalizado || 'Aviso';

        if (tipo === 'error') {
            icono = 'error';
            titulo = tituloPersonalizado || 'No permitido';
        } else if (tipo === 'ok') {
            icono = 'success';
            titulo = tituloPersonalizado || 'Correcto';
            Swal.fire({
                toast:true,
                position:'top-end',
                icon:icono,
                title:titulo,
                text:texto,
                showConfirmButton:false,
                timer:2300,
                timerProgressBar:true,
                background:'#ffffff',
                color:'#0f172a'
            });
            return;
        } else if (tipo === 'warn') {
            icono = 'warning';
            titulo = tituloPersonalizado || 'Atención';
        }

        Swal.fire({
            icon:icono,
            title:titulo,
            text:texto,
            confirmButtonText:'Entendido',
            confirmButtonColor:tipo === 'error' ? '#dc2626' : '#1d4ed8'
        });
    }

    function setMiniAviso(el, texto, modo) {
        if (!el) return;

        el.textContent = texto || '';
        el.className = 'notice-mini';

        if (!texto) {
            el.style.display = 'none';
            return;
        }

        el.classList.add(modo === 'error' ? 'error' : (modo === 'ok' ? 'ok' : 'warn'));
        el.style.display = 'block';
    }

    function mostrarAviso(texto, modo) {
        setMiniAviso(aviso, '', '');
        if (texto) sweet(texto, modo);
    }

    function mostrarDocenteAviso(texto, modo) {
        setMiniAviso(docenteAviso, texto, modo);
        if (texto) sweet(texto, modo);
    }

    function extraerNombreDniApi(data) {
        if (!data) return '';
        const apP = data.apellido_paterno || data.apellidoPaterno || data.ape_paterno || '';
        const apM = data.apellido_materno || data.apellidoMaterno || data.ape_materno || '';
        const nom = data.nombres || '';
        const completo = data.nombre_completo || data.nombreCompleto || [apP, apM, nom].filter(Boolean).join(' ');
        return String(completo || '').toUpperCase().trim();
    }

    function extraerDatosDniApi(data) {
        if (!data) {
            return { apellido_paterno: '', apellido_materno: '', nombres: '', completo: '' };
        }

        const apP = data.apellido_paterno || data.apellidoPaterno || data.ape_paterno || data.apellidoPaternoPersona || '';
        const apM = data.apellido_materno || data.apellidoMaterno || data.ape_materno || data.apellidoMaternoPersona || '';
        const nom = data.nombres || data.nombresPersona || data.nombre || '';
        const completo = data.nombre_completo || data.nombreCompleto || data.full_name || [apP, apM, nom].filter(Boolean).join(' ');

        return {
            apellido_paterno: String(apP || '').toUpperCase().trim(),
            apellido_materno: String(apM || '').toUpperCase().trim(),
            nombres: String(nom || '').toUpperCase().trim(),
            completo: String(completo || '').toUpperCase().trim()
        };
    }

    function llenarNombreParticipante(apP, apM, nom, completo) {
        let paterno = String(apP || '').toUpperCase().trim();
        let materno = String(apM || '').toUpperCase().trim();
        let nombresPersona = String(nom || '').toUpperCase().trim();
        const textoCompleto = String(completo || '').toUpperCase().trim();

        if ((!paterno || !materno || !nombresPersona) && textoCompleto) {
            const partes = textoCompleto.split(/\s+/).filter(Boolean);

            if (!paterno) paterno = partes[0] || '';
            if (!materno) materno = partes[1] || '';
            if (!nombresPersona) nombresPersona = partes.slice(2).join(' ');
        }

        if (apellidoPaterno) apellidoPaterno.value = paterno;
        if (apellidoMaterno) apellidoMaterno.value = materno;
        if (nombres) nombres.value = nombresPersona;
    }

    function limpiarNombreParticipante() {
        if (apellidoPaterno) apellidoPaterno.value = '';
        if (apellidoMaterno) apellidoMaterno.value = '';
        if (nombres) nombres.value = '';
    }


    function mostrarBotonCorregirNombre() {
        if (!btnCorregirNombreSeleccionado) return;
        btnCorregirNombreSeleccionado.style.display = participanteActual && participanteActual.id_participante ? 'inline-flex' : 'none';
    }

    function limpiarParticipanteActual() {
        participanteActual = null;
        mostrarBotonCorregirNombre();
    }

    function mostrarIeExistente(d) {
        if (!selected) return;

        const institucion = String(d.institucion_educativa || '').toUpperCase();
        const tipoTexto = String(d.tipo_institucion || '').toUpperCase();
        const nivelTexto = String(d.nivel_modalidad_ie || '').toUpperCase();
        const modularTexto = String(d.codigo_modular_ie || '').toUpperCase();

        if (!institucion) {
            selected.style.display = 'none';
            selected.textContent = '';
            return;
        }

        selected.style.display = 'block';
        selected.innerHTML =
            '<i class="bi bi-check-circle-fill"></i> <strong>I.E. registrada:</strong> ' + escapeHtml(institucion) +
            '<br><span class="pill ' + (tipoTexto === 'PUBLICA' ? 'pill-blue' : 'pill-red') + '">' + escapeHtml(tipoTexto || 'I.E.') + '</span>' +
            (nivelTexto ? ' <span class="pill pill-green">' + escapeHtml(nivelTexto) + '</span>' : '') +
            (modularTexto ? ' <span class="pill pill-orange">' + escapeHtml(modularTexto) + '</span>' : '');
    }

    function aplicarParticipanteExistente(d, resultado, origen) {
        d = d || {};
        participanteActual = Object.assign({}, d);
        participanteActual.resultado = resultado || d.resultado || null;
        mostrarBotonCorregirNombre();

        if (documento) {
            documento.value = String(d.documento || '').toUpperCase();
            documento.classList.remove('field-bad');
            documento.classList.add('field-ok');
        }

        if (idParticipante) idParticipante.value = d.id_participante || '';

        llenarNombreParticipante(
            d.apellido_paterno || '',
            d.apellido_materno || '',
            d.nombres || '',
            d.apellidos_nombres || ''
        );

        if (ie) ie.value = String(d.institucion_educativa || '').toUpperCase();
        if (tipo && d.tipo_institucion) tipo.value = String(d.tipo_institucion || '').toUpperCase();
        if (idInstitucion) idInstitucion.value = d.id_institucion || '';
        if (idModular) idModular.value = d.id_modular || '';
        if (codigoModular) codigoModular.value = d.codigo_modular_ie || '';
        if (nivelModalidad) nivelModalidad.value = d.nivel_modalidad_ie || '';

        llenarCalificacionExistente(resultado || d.resultado || null);
        mostrarIeExistente(d);

        if (participanteResultados) {
            participanteResultados.style.display = 'none';
            participanteResultados.innerHTML = '';
        }

        if (buscarParticipanteNombre && origen === 'nombre') {
            buscarParticipanteNombre.value = String(d.apellidos_nombres || d.documento || '').toUpperCase();
        }

        if (origen === 'corregido') {
            mostrarAviso('Nivel corregido y participante asignado al salón actual.', 'ok');
        } else if (origen === 'nombre_corregido') {
            mostrarAviso('Nombre corregido correctamente.', 'ok');
        } else if (d.id_salon && Number(d.id_salon) > 0) {
            mostrarAviso('Este participante ya tiene salón: ' + (d.nombre_salon || 'registrado') + '.', 'warn');
        } else {
            mostrarAviso('Participante encontrado en base de datos.', 'ok');
        }
    }

    function ocultarResultadosParticipante() {
        if (!participanteResultados) return;
        participanteResultados.style.display = 'none';
        participanteResultados.innerHTML = '';
    }



    async function corregirNombreParticipante(item, origen) {
        item = item || participanteActual || {};

        const idParticipanteCorregir = item.id_participante || (idParticipante ? idParticipante.value : '');
        const token = csrfAsignarSalon ? csrfAsignarSalon.value : '';

        if (!idParticipanteCorregir) {
            sweet('Primero selecciona o busca al participante que deseas corregir.', 'error');
            return;
        }

        const nombreActual = String(item.apellidos_nombres || '').toUpperCase();
        const apPActual = String(item.apellido_paterno || (apellidoPaterno ? apellidoPaterno.value : '') || '').toUpperCase();
        const apMActual = String(item.apellido_materno || (apellidoMaterno ? apellidoMaterno.value : '') || '').toUpperCase();
        const nombresActual = String(item.nombres || (nombres ? nombres.value : '') || '').toUpperCase();

        const modal = await Swal.fire({
            icon: 'warning',
            title: 'Corregir nombre del estudiante',
            html:
                '<div style="text-align:left;line-height:1.45">' +
                    '<div style="font-size:12px;color:#475569;margin-bottom:10px">' +
                        '<b>Actual:</b> ' + escapeHtml(nombreActual || 'SIN NOMBRE') +
                    '</div>' +
                    '<label style="font-size:12px;font-weight:900;color:#334155">Apellido paterno</label>' +
                    '<input id="swal_apellido_paterno" class="swal2-input" maxlength="80" value="' + escapeHtml(apPActual) + '" style="margin:5px 0 10px;width:100%;text-transform:uppercase">' +
                    '<label style="font-size:12px;font-weight:900;color:#334155">Apellido materno</label>' +
                    '<input id="swal_apellido_materno" class="swal2-input" maxlength="80" value="' + escapeHtml(apMActual) + '" style="margin:5px 0 10px;width:100%;text-transform:uppercase">' +
                    '<label style="font-size:12px;font-weight:900;color:#334155">Nombres</label>' +
                    '<input id="swal_nombres" class="swal2-input" maxlength="120" value="' + escapeHtml(nombresActual) + '" style="margin:5px 0 10px;width:100%;text-transform:uppercase">' +
                    '<label style="font-size:12px;font-weight:900;color:#334155">Motivo / sustento</label>' +
                    '<textarea id="swal_motivo_nombre" class="swal2-textarea" maxlength="250" style="margin:5px 0 0;width:100%;min-height:70px">Corrección por error de digitación.</textarea>' +
                '</div>',
            showCancelButton: true,
            confirmButtonText: 'Guardar corrección',
            cancelButtonText: 'Cancelar',
            confirmButtonColor: '#d97706',
            cancelButtonColor: '#64748b',
            focusConfirm: false,
            didOpen: function () {
                ['swal_apellido_paterno', 'swal_apellido_materno', 'swal_nombres'].forEach(function (id) {
                    const el = document.getElementById(id);
                    if (el) {
                        el.addEventListener('input', function () {
                            el.value = el.value.toUpperCase();
                        });
                    }
                });
            },
            preConfirm: function () {
                const apP = String(document.getElementById('swal_apellido_paterno')?.value || '').trim().toUpperCase().replace(/\s+/g, ' ');
                const apM = String(document.getElementById('swal_apellido_materno')?.value || '').trim().toUpperCase().replace(/\s+/g, ' ');
                const nom = String(document.getElementById('swal_nombres')?.value || '').trim().toUpperCase().replace(/\s+/g, ' ');
                const mot = String(document.getElementById('swal_motivo_nombre')?.value || '').trim();

                if (!apP) {
                    Swal.showValidationMessage('Ingresa el apellido paterno correcto.');
                    return false;
                }

                if (!apM) {
                    Swal.showValidationMessage('Ingresa el apellido materno correcto.');
                    return false;
                }

                if (!nom) {
                    Swal.showValidationMessage('Ingresa los nombres correctos.');
                    return false;
                }

                return {
                    apellido_paterno: apP,
                    apellido_materno: apM,
                    nombres: nom,
                    motivo: mot || 'Corrección por error de digitación.'
                };
            }
        });

        if (!modal.isConfirmed || !modal.value) {
            return;
        }

        const body = new URLSearchParams();
        body.set('csrf_token', token);
        body.set('id_participante', String(idParticipanteCorregir));
        body.set('apellido_paterno', modal.value.apellido_paterno);
        body.set('apellido_materno', modal.value.apellido_materno);
        body.set('nombres', modal.value.nombres);
        body.set('motivo', modal.value.motivo);

        const idConcursoActual = document.getElementById('id_concurso_actual');
        if (idConcursoActual && idConcursoActual.value) {
            body.set('id_concurso', idConcursoActual.value);
        }

        try {
            Swal.fire({
                title: 'Guardando corrección...',
                text: 'Se actualizará solo el nombre del participante.',
                allowOutsideClick: false,
                allowEscapeKey: false,
                didOpen: function () {
                    Swal.showLoading();
                }
            });

            const resp = await fetch('asignar_salon.php?ajax=corregir_nombre_participante', {
                method: 'POST',
                headers: {
                    'Accept': 'application/json',
                    'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'
                },
                body: body.toString()
            });

            const json = await resp.json();

            if (!json || !json.success) {
                Swal.close();
                sweet(json && json.message ? json.message : 'No se pudo corregir el nombre.', 'error');
                return;
            }

            Swal.close();

            aplicarParticipanteExistente(json.data || item, json.resultado || item.resultado || null, 'nombre_corregido');

            if (buscarParticipanteNombre) {
                buscarParticipanteNombre.value = String((json.data && json.data.apellidos_nombres) || '').toUpperCase();
            }

            ocultarResultadosParticipante();
            sweet(json.message || 'Nombre corregido correctamente.', 'ok', 'Corregido');
        } catch (e) {
            Swal.close();
            sweet('No se pudo completar la corrección. Revisa tu conexión e intenta de nuevo.', 'error');
        }
    }

    async function corregirNivelYAsignar(item, origen) {
        item = item || {};

        const idParticipanteCorregir = item.id_participante || '';
        const idSalonActual = document.getElementById('id_salon_actual');
        const idGrupoActual = document.getElementById('id_grupo_actual');
        const salonDestino = idSalonActual ? idSalonActual.value : '';
        const grupoDestino = idGrupoActual ? idGrupoActual.value : '';
        const token = csrfAsignarSalon ? csrfAsignarSalon.value : '';

        if (!idParticipanteCorregir || !salonDestino) {
            sweet('No se pudo detectar el participante o el salón actual.', 'error');
            return;
        }

        if (!item.can_fix_level) {
            sweet(item.blocked_message || 'Este participante no puede cambiar de nivel desde esta pantalla.', 'error');
            return;
        }

        const nivelActual = String(item.nivel_modalidad_ie || item.grupo_nivel || 'SIN NIVEL').toUpperCase();
        const nivelDestino = String(item.nivel_contexto || 'NIVEL DEL SALÓN').toUpperCase();
        const nombre = String(item.apellidos_nombres || item.documento || '').toUpperCase();

        const confirmacion = await Swal.fire({
            icon: 'warning',
            title: 'Estudiante en otro nivel',
            html:
                '<div style="text-align:left;line-height:1.55">' +
                '<b>Participante:</b> ' + escapeHtml(nombre) + '<br>' +
                '<b>Nivel registrado:</b> ' + escapeHtml(nivelActual) + '<br>' +
                '<b>Nivel del salón actual:</b> ' + escapeHtml(nivelDestino) + '<br><br>' +
                '¿Deseas modificarlo y asignarlo a este salón?' +
                '</div>',
            showCancelButton: true,
            confirmButtonText: 'Sí, modificar y asignar',
            cancelButtonText: 'Cancelar',
            confirmButtonColor: '#1d4ed8',
            cancelButtonColor: '#64748b'
        });

        if (!confirmacion.isConfirmed) {
            return;
        }

        const body = new URLSearchParams();
        body.set('csrf_token', token);
        body.set('id_participante', String(idParticipanteCorregir));
        body.set('id_salon', String(salonDestino));
        if (grupoDestino) body.set('id_grupo', String(grupoDestino));
        body.set('motivo', 'Corrección rápida: participante registrado en ' + nivelActual + ' y asignado al nivel ' + nivelDestino + ' desde pantalla de asignación.');

        try {
            Swal.fire({
                title: 'Corrigiendo nivel...',
                text: 'Espera un momento.',
                allowOutsideClick: false,
                allowEscapeKey: false,
                didOpen: function () {
                    Swal.showLoading();
                }
            });

            const resp = await fetch('asignar_salon.php?ajax=corregir_nivel_participante', {
                method: 'POST',
                headers: {
                    'Accept': 'application/json',
                    'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'
                },
                body: body.toString()
            });

            const json = await resp.json();

            if (!json || !json.success) {
                Swal.close();
                sweet(json && json.message ? json.message : 'No se pudo corregir el nivel.', 'error');
                return;
            }

            Swal.close();

            aplicarParticipanteExistente(json.data || item, json.resultado || null, 'corregido');

            sweet(json.message || 'Nivel corregido y participante asignado.', 'ok', 'Corregido');
        } catch (e) {
            Swal.close();
            sweet('No se pudo completar la corrección. Revisa tu conexión e intenta de nuevo.', 'error');
        }
    }

    function renderParticipantesEncontrados(items, mensaje) {
        if (!participanteResultados) return;

        participanteResultados.innerHTML = '';

        if (!items || !items.length) {
            participanteResultados.style.display = 'block';
            const empty = document.createElement('div');
            empty.className = 'participante-empty';
            empty.innerHTML = '<i class="fa-solid fa-circle-info"></i> ' + escapeHtml(mensaje || 'No se encontraron participantes registrados.');
            participanteResultados.appendChild(empty);
            return;
        }

        items.forEach(function (item) {
            const card = document.createElement('div');
            card.className = 'participante-item' + (item.blocked ? ' blocked' : '');
            card.setAttribute('role', 'button');
            card.setAttribute('tabindex', '0');

            const salonTxt = item.nombre_salon ? ('Salón: ' + item.nombre_salon) : 'Sin salón';
            const nivelTxt = item.nivel_modalidad_ie || item.grupo_nivel || '';
            const tipoTxt = String(item.tipo_institucion || '').toUpperCase();
            const ieTxt = String(item.institucion_educativa || '').toUpperCase();

            card.innerHTML =
                '<div class="participante-main"><i class="fa-solid fa-user-graduate"></i> ' + escapeHtml(item.apellidos_nombres || '') + '</div>' +
                '<div class="participante-meta">' +
                    '<span class="pill pill-blue"><i class="fa-solid fa-id-card"></i> ' + escapeHtml(item.documento || '') + '</span>' +
                    (tipoTxt ? '<span class="pill ' + (tipoTxt === 'PUBLICA' ? 'pill-blue' : 'pill-red') + '">' + escapeHtml(tipoTxt) + '</span>' : '') +
                    (nivelTxt ? '<span class="pill pill-green">' + escapeHtml(nivelTxt) + '</span>' : '') +
                    '<span class="pill ' + (item.id_salon ? 'pill-yellow' : 'pill-gray') + '">' + escapeHtml(salonTxt) + '</span>' +
                '</div>' +
                (ieTxt ? '<div style="margin-top:7px;color:#475569;font-size:12px;font-weight:850"><i class="fa-solid fa-school"></i> ' + escapeHtml(ieTxt) + '</div>' : '') +
                (item.blocked ? '<div style="margin-top:7px;color:#991b1b;font-size:12px;font-weight:900"><i class="fa-solid fa-triangle-exclamation"></i> ' + escapeHtml(item.blocked_message || 'No compatible con este salón.') + '</div>' : '') +
                '<div class="participante-actions">' +
                    '<button type="button" class="participante-action participante-action-use"><i class="fa-solid fa-check"></i> Usar</button>' +
                    '<button type="button" class="participante-action participante-action-correct"><i class="fa-solid fa-user-pen"></i> Corregir nombre</button>' +
                '</div>';

            function usarItem() {
                if (item.blocked) {
                    if (item.can_fix_level) {
                        corregirNivelYAsignar(item, 'nombre');
                    } else {
                        sweet(item.blocked_message || 'Este participante no corresponde al nivel del salón.', 'error');
                    }
                    return;
                }

                aplicarParticipanteExistente(item, item.resultado || null, 'nombre');
            }

            card.addEventListener('click', function (event) {
                if (event.target && event.target.closest('.participante-action')) {
                    return;
                }

                usarItem();
            });

            card.addEventListener('keydown', function (event) {
                if (event.key === 'Enter' || event.key === ' ') {
                    event.preventDefault();
                    usarItem();
                }
            });

            const btnUsar = card.querySelector('.participante-action-use');
            const btnCorregir = card.querySelector('.participante-action-correct');

            if (btnUsar) {
                btnUsar.addEventListener('click', function (event) {
                    event.stopPropagation();
                    usarItem();
                });
            }

            if (btnCorregir) {
                btnCorregir.addEventListener('click', function (event) {
                    event.stopPropagation();
                    corregirNombreParticipante(item, 'resultado_busqueda');
                });
            }

            participanteResultados.appendChild(card);
        });

        participanteResultados.style.display = 'block';
    }

    async function buscarParticipantePorNombre() {
        if (!buscarParticipanteNombre || !participanteResultados) return;

        const q = buscarParticipanteNombre.value.trim();

        if (q.length < 2) {
            ocultarResultadosParticipante();
            return;
        }

        const params = new URLSearchParams();
        const idGrupoActual = document.getElementById('id_grupo_actual');
        const idSalonActual = document.getElementById('id_salon_actual');

        params.set('ajax', 'buscar_participante_nombre');
        params.set('q', q);

        if (idGrupoActual && idGrupoActual.value) params.set('id_grupo', idGrupoActual.value);
        if (idSalonActual && idSalonActual.value) params.set('id_salon', idSalonActual.value);

        try {
            if (participanteSpin) participanteSpin.style.display = 'inline-block';

            const json = await fetchJsonRapido(
                'asignar_salon.php?' + params.toString(),
                2800
            );

            if (!json || !json.success) {
                renderParticipantesEncontrados([], json && json.message ? json.message : 'No se pudo buscar participantes.');
                return;
            }

            renderParticipantesEncontrados(Array.isArray(json.items) ? json.items : [], json.message || '');
        } catch (e) {
            renderParticipantesEncontrados([], 'La búsqueda demoró demasiado. Intenta con menos palabras.');
        } finally {
            if (participanteSpin) participanteSpin.style.display = 'none';
        }
    }

    function programarBuscarParticipanteNombre() {
        if (participanteTimer) clearTimeout(participanteTimer);
        participanteTimer = setTimeout(buscarParticipantePorNombre, 260);
    }

    function numeroScore(valor) {
        const n = Number(String(valor || '').replace(',', '.'));
        return Number.isFinite(n) ? n : 0;
    }

    function scoreConfig() {
        return {
            total: scorePanel ? Number(scorePanel.dataset.total || 0) : 0,
            correcta: scorePanel ? Number(scorePanel.dataset.correcta || 0) : 0,
            incorrecta: scorePanel ? Number(scorePanel.dataset.incorrecta || 0) : 0,
            blanco: scorePanel ? Number(scorePanel.dataset.blanco || 0) : 0
        };
    }

    function recalcularCalificacion() {
        if (!scorePanel || !correctasInput || !incorrectasInput || !blancoInput || !puntajeInput) return true;

        const cfg = scoreConfig();

        if (correctasInput.value.trim() === '' && incorrectasInput.value.trim() === '') {
            blancoInput.value = '';
            puntajeInput.value = '';
            return true;
        }

        const c = correctasInput.value.trim() === '' ? 0 : Number(correctasInput.value);
        const i = incorrectasInput.value.trim() === '' ? 0 : Number(incorrectasInput.value);

        if (c < 0 || i < 0 || !Number.isFinite(c) || !Number.isFinite(i)) {
            blancoInput.value = '';
            puntajeInput.value = '';
            return false;
        }

        if ((c + i) > cfg.total) {
            blancoInput.value = '';
            puntajeInput.value = '';
            return false;
        }

        const blanco = cfg.total - c - i;
        const puntaje = (c * cfg.correcta) - (i * cfg.incorrecta) + (blanco * cfg.blanco);

        blancoInput.value = String(blanco);
        puntajeInput.value = puntaje.toFixed(2);

        return true;
    }

    function limpiarCalificacion() {
        if (correctasInput) correctasInput.value = '';
        if (incorrectasInput) incorrectasInput.value = '';
        if (blancoInput) blancoInput.value = '';
        if (puntajeInput) puntajeInput.value = '';
        if (tiempoCalificacion) tiempoCalificacion.value = '';
        if (observacionCalificacion) observacionCalificacion.value = '';
    }

    function llenarCalificacionExistente(resultado) {
        if (!resultado) {
            limpiarCalificacion();
            return;
        }

        if (correctasInput) correctasInput.value = resultado.correctas ?? '';
        if (incorrectasInput) incorrectasInput.value = resultado.incorrectas ?? '';
        if (tiempoCalificacion) tiempoCalificacion.value = resultado.tiempo_texto || '';
        if (observacionCalificacion) observacionCalificacion.value = resultado.observacion || '';

        recalcularCalificacion();
    }

    function upperInput(el) {
        if (!el) return;
        el.addEventListener('input', function () {
            el.value = el.value.toUpperCase();
        });
    }

    function limpiarSelect(select, texto) {
        if (!select) return;
        select.innerHTML = '';
        const opt = document.createElement('option');
        opt.value = '';
        opt.textContent = texto;
        select.appendChild(opt);
    }

    async function cargarSelect(url, select, texto) {
        limpiarSelect(select, texto);

        try {
            const resp = await fetch(url, { headers: { 'Accept': 'application/json' } });
            const json = await resp.json();
            const items = json && Array.isArray(json.items) ? json.items : [];

            items.forEach(function (valor) {
                const opt = document.createElement('option');
                opt.value = valor;
                opt.textContent = valor;
                select.appendChild(opt);
            });
        } catch (e) {
            limpiarSelect(select, texto);
        }
    }

    function limpiarIeSeleccionada() {
        if (idInstitucion) idInstitucion.value = '';
        if (idModular) idModular.value = '';
        if (codigoModular) codigoModular.value = '';
        if (nivelModalidad) nivelModalidad.value = '';

        if (selected) {
            selected.style.display = 'none';
            selected.textContent = '';
        }
    }

    function mostrarIeSeleccionada(item) {
        if (!selected) return;

        selected.style.display = 'block';
        selected.innerHTML =
            '<i class="bi bi-check-circle-fill"></i> <strong>I.E. seleccionada:</strong> ' + escapeHtml(item.nombre_institucion) +
            '<br><span class="pill pill-green"><i class="fa-solid fa-layer-group"></i> ' + escapeHtml(item.nivel_modalidad || '') + '</span> ' +
            '<span class="pill pill-orange"><i class="fa-solid fa-barcode"></i> ' + escapeHtml(item.codigo_modular || '') + '</span> ' +
            '<span class="pill pill-gray"><i class="fa-solid fa-location-dot"></i> ' + escapeHtml((item.departamento || '') + ' / ' + (item.provincia || '') + ' / ' + (item.distrito || '')) + '</span>';
    }

    function mostrarIeManual(nombre, nivelContexto) {
        if (!selected) return;

        selected.style.display = 'block';
        selected.innerHTML =
            '<i class="bi bi-pencil-square"></i> <strong>I.E. registrada manualmente:</strong> ' + escapeHtml(nombre) +
            '<br><span class="pill pill-yellow"><i class="fa-solid fa-keyboard"></i> Manual</span> ' +
            '<span class="pill pill-green"><i class="fa-solid fa-layer-group"></i> ' + escapeHtml(nivelContexto || 'Nivel del salón') + '</span>' +
            '<div style="margin-top:7px;color:#64748b;font-size:12px;font-weight:800">No se guardará código modular porque no fue encontrada en el catálogo.</div>';
    }

    async function buscarIe() {
        if (!BUSQUEDA_IE_CATALOGO_ACTIVA) {
            if (results) {
                results.style.display = 'none';
                results.innerHTML = '';
            }
            return;
        }

        if (!ie || !results || !tipo) return;

        const q = ie.value.trim();

        if (q.length < 2) {
            results.style.display = 'none';
            results.innerHTML = '';
            return;
        }

        const params = new URLSearchParams();
        params.set('ajax', 'buscar_ie');
        params.set('q', q);
        params.set('gestion', tipo.value);

        const idGrupoActual = document.getElementById('id_grupo_actual');
        const idSalonActual = document.getElementById('id_salon_actual');

        if (idGrupoActual && idGrupoActual.value) params.set('id_grupo', idGrupoActual.value);
        if (idSalonActual && idSalonActual.value) params.set('id_salon', idSalonActual.value);
        if (dep && dep.value) params.set('departamento', dep.value);
        if (prov && prov.value) params.set('provincia', prov.value);
        if (dist && dist.value) params.set('distrito', dist.value);

        try {
            const resp = await fetch('asignar_salon.php?' + params.toString(), {
                headers: { 'Accept': 'application/json' }
            });

            const json = await resp.json();
            const items = json && Array.isArray(json.items) ? json.items : [];

            results.innerHTML = '';

            if (!items.length) {
                results.style.display = 'block';

                const empty = document.createElement('div');
                empty.className = 'ie-item';
                empty.innerHTML = '<strong>Sin coincidencias en catálogo</strong><br><span style="color:#64748b">Puedes escribir el nombre del colegio y usarlo como registro manual.</span>';
                results.appendChild(empty);

                const manual = document.createElement('div');
                manual.className = 'ie-item';
                manual.innerHTML =
                    '<strong><i class="fa-solid fa-keyboard"></i> Usar nombre escrito</strong><br>' +
                    '<span class="pill pill-yellow">MANUAL</span> ' +
                    '<span style="color:#64748b">' + escapeHtml(q.toUpperCase()) + '</span>';

                manual.addEventListener('click', function () {
                    if (idInstitucion) idInstitucion.value = '';
                    if (idModular) idModular.value = '';
                    if (codigoModular) codigoModular.value = '';
                    if (nivelModalidad) nivelModalidad.value = json.nivel_contexto || '';
                    if (ie) ie.value = q.toUpperCase();

                    results.style.display = 'none';
                    results.innerHTML = '';
                    mostrarIeManual(q.toUpperCase(), json.nivel_contexto || '');
                    sweet('Se usará el nombre escrito como I.E. manual.', 'ok', 'I.E. manual');
                });

                results.appendChild(manual);
                return;
            }

            items.forEach(function (item) {
                const div = document.createElement('div');
                div.className = 'ie-item';
                div.innerHTML =
                    '<strong><i class="fa-solid fa-school"></i> ' + escapeHtml(item.nombre_institucion || '') + '</strong><br>' +
                    '<span class="pill pill-green">' + escapeHtml(item.nivel_modalidad || '') + '</span> ' +
                    '<span class="pill pill-orange">' + escapeHtml(item.codigo_modular || '') + '</span> ' +
                    '<span style="color:#64748b">' + escapeHtml((item.departamento || '') + ' / ' + (item.provincia || '') + ' / ' + (item.distrito || '')) + '</span>';

                div.addEventListener('click', function () {
                    if (idInstitucion) idInstitucion.value = item.id_institucion;
                    if (idModular) idModular.value = item.id_modular || '';
                    if (codigoModular) codigoModular.value = item.codigo_modular || '';
                    if (nivelModalidad) nivelModalidad.value = item.nivel_modalidad || '';
                    if (ie) ie.value = String(item.nombre_institucion || '').toUpperCase();
                    if (tipo) tipo.value = item.gestion;

                    results.style.display = 'none';
                    results.innerHTML = '';
                    mostrarIeSeleccionada(item);
                    sweet('Institución seleccionada correctamente.', 'ok', 'I.E. lista');
                });

                results.appendChild(div);
            });

            results.style.display = 'block';
        } catch (e) {
            results.style.display = 'none';
            results.innerHTML = '';
        }
    }

    function programarBuscarIe() {
        if (ieTimer) clearTimeout(ieTimer);
        ieTimer = setTimeout(buscarIe, 280);
    }

    async function cargarDepartamentos() {
        if (!BUSQUEDA_IE_CATALOGO_ACTIVA) return;
        if (!tipo || !dep) return;

        await cargarSelect('asignar_salon.php?ajax=departamentos&gestion=' + encodeURIComponent(tipo.value), dep, 'Todos');
        limpiarSelect(prov, 'Todas');
        limpiarSelect(dist, 'Todos');
    }

    upperInput(apellidoPaterno);
    upperInput(apellidoMaterno);
    upperInput(nombres);
    upperInput(ie);

    if (buscarParticipanteNombre) {
        buscarParticipanteNombre.addEventListener('input', function () {
            buscarParticipanteNombre.value = buscarParticipanteNombre.value.toUpperCase();
            programarBuscarParticipanteNombre();
        });

        buscarParticipanteNombre.addEventListener('keydown', function (event) {
            if (event.key === 'Enter') {
                event.preventDefault();
                buscarParticipantePorNombre();
            }

            if (event.key === 'Escape') {
                ocultarResultadosParticipante();
            }
        });
    }

    if (correctasInput) correctasInput.addEventListener('input', recalcularCalificacion);
    if (incorrectasInput) incorrectasInput.addEventListener('input', recalcularCalificacion);
    recalcularCalificacion();

    if (tipo) {
        tipo.addEventListener('change', async function () {
            limpiarIeSeleccionada();
            if (idInstitucion) idInstitucion.value = '';
            if (idModular) idModular.value = '';
            if (codigoModular) codigoModular.value = '';
            if (nivelModalidad) nivelModalidad.value = '';
            await cargarDepartamentos();
        });
    }

    if (dep) {
        dep.addEventListener('change', async function () {
            limpiarSelect(prov, 'Todas');
            limpiarSelect(dist, 'Todos');

            if (tipo && tipo.value && dep.value) {
                await cargarSelect(
                    'asignar_salon.php?ajax=provincias&gestion=' + encodeURIComponent(tipo.value) + '&departamento=' + encodeURIComponent(dep.value),
                    prov,
                    'Todas'
                );
            }

            programarBuscarIe();
        });
    }

    if (prov) {
        prov.addEventListener('change', async function () {
            limpiarSelect(dist, 'Todos');

            if (tipo && tipo.value && dep && dep.value && prov.value) {
                await cargarSelect(
                    'asignar_salon.php?ajax=distritos&gestion=' + encodeURIComponent(tipo.value) + '&departamento=' + encodeURIComponent(dep.value) + '&provincia=' + encodeURIComponent(prov.value),
                    dist,
                    'Todos'
                );
            }

            programarBuscarIe();
        });
    }

    if (dist) dist.addEventListener('change', programarBuscarIe);

    if (ie) {
        ie.addEventListener('input', function () {
            ie.value = ie.value.toUpperCase();
            if (idInstitucion) idInstitucion.value = '';
            if (idModular) idModular.value = '';
            if (codigoModular) codigoModular.value = '';
            if (nivelModalidad) nivelModalidad.value = '';

            if (results) {
                results.style.display = 'none';
                results.innerHTML = '';
            }

            if (!idParticipante || !idParticipante.value) {
                if (ie.value.trim().length >= 3) {
                    mostrarIeManual(ie.value.trim().toUpperCase(), 'Nivel del salón');
                } else {
                    limpiarIeSeleccionada();
                }
            }
        });
    }

    if (dniResponsable) {
        dniResponsable.addEventListener('input', function () {
            dniResponsable.value = dniResponsable.value.replace(/\D/g, '').slice(0, 8);
            dniResponsable.classList.remove('field-ok', 'field-bad');

            if (nombreResponsable) {
                nombreResponsable.value = '';
                nombreResponsable.classList.remove('field-ok', 'field-bad');
            }

            if (btnConfirmarResponsable) btnConfirmarResponsable.disabled = true;
            setMiniAviso(docenteAviso, '', '');
        });

        dniResponsable.addEventListener('blur', async function () {
            const dni = dniResponsable.value.trim();

            if (dni === '') return;

            if (!/^\d{8}$/.test(dni)) {
                dniResponsable.classList.add('field-bad');
                mostrarDocenteAviso('El DNI del docente debe tener 8 dígitos.', 'error');
                return;
            }

            try {
                if (docenteSpin) docenteSpin.style.display = 'inline-block';
                if (btnConfirmarResponsable) btnConfirmarResponsable.disabled = true;

                const json = await fetchJsonRapido(
                    'dni/buscar_dni.php?dni=' + encodeURIComponent(dni),
                    3200
                );

                if (json && json.success && json.data) {
                    const nombre = extraerNombreDniApi(json.data);

                    if (nombre !== '') {
                        if (nombreResponsable) {
                            nombreResponsable.value = nombre;
                            nombreResponsable.classList.remove('field-bad');
                            nombreResponsable.classList.add('field-ok');
                        }

                        dniResponsable.classList.remove('field-bad');
                        dniResponsable.classList.add('field-ok');

                        if (btnConfirmarResponsable) btnConfirmarResponsable.disabled = false;

                        mostrarDocenteAviso('Docente encontrado. Confirma para tomar responsabilidad del salón.', 'ok');
                    } else {
                        dniResponsable.classList.add('field-bad');
                        mostrarDocenteAviso('El API respondió, pero no devolvió nombre del docente.', 'error');
                    }
                } else {
                    dniResponsable.classList.add('field-bad');
                    mostrarDocenteAviso('No se encontró el DNI del docente en el API.', 'error');
                }
            } catch (error) {
                dniResponsable.classList.add('field-bad');
                mostrarDocenteAviso('No se pudo consultar el API de DNI.', 'error');
            } finally {
                if (docenteSpin) docenteSpin.style.display = 'none';
            }
        });
    }

    if (documento) {
        documento.addEventListener('input', function () {
            documento.value = documento.value.replace(/\D/g, '').slice(0, 8);
            documento.classList.remove('field-ok', 'field-bad');

            if (idParticipante) idParticipante.value = '';
            limpiarParticipanteActual();
            setMiniAviso(aviso, '', '');
        });

        documento.addEventListener('blur', async function () {
            const dni = documento.value.trim();

            if (dni === '') return;

            if (!/^\d{8}$/.test(dni)) {
                documento.classList.add('field-bad');
                mostrarAviso('El DNI debe tener 8 dígitos.', 'error');
                return;
            }

            try {
                if (spin) spin.style.display = 'inline-block';

                const paramsDocumento = new URLSearchParams();
                const idGrupoActual = document.getElementById('id_grupo_actual');
                const idSalonActual = document.getElementById('id_salon_actual');

                paramsDocumento.set('ajax', 'buscar_documento');
                paramsDocumento.set('documento', dni);

                if (idGrupoActual && idGrupoActual.value) paramsDocumento.set('id_grupo', idGrupoActual.value);
                if (idSalonActual && idSalonActual.value) paramsDocumento.set('id_salon', idSalonActual.value);

                const json = await fetchJsonRapido(
                    'asignar_salon.php?' + paramsDocumento.toString(),
                    2800
                );

                if (json && json.blocked) {
                    const dataBloqueada = json.data || {};
                    dataBloqueada.can_fix_level = !!json.can_fix_level;
                    dataBloqueada.blocked_message = json.message || dataBloqueada.blocked_message || 'Este DNI no corresponde al nivel del salón seleccionado.';
                    dataBloqueada.nivel_contexto = json.nivel_contexto || dataBloqueada.nivel_contexto || '';

                    documento.classList.remove('field-ok');
                    documento.classList.add('field-bad');

                    if (json.can_fix_level && dataBloqueada.id_participante) {
                        await corregirNivelYAsignar(dataBloqueada, 'dni');
                        return;
                    }

                    if (idParticipante) idParticipante.value = '';
                    limpiarParticipanteActual();
                    limpiarNombreParticipante();
                    if (ie) ie.value = '';
                    if (idInstitucion) idInstitucion.value = '';
                    if (idModular) idModular.value = '';
                    if (codigoModular) codigoModular.value = '';
                    if (nivelModalidad) nivelModalidad.value = '';

                    if (selected) {
                        selected.style.display = 'none';
                        selected.textContent = '';
                    }

                    mostrarAviso(json.message || 'Este DNI no corresponde al nivel del salón seleccionado.', 'error');
                    return;
                }

                if (json && json.success && json.exists) {
                    const d = json.data || {};

                    documento.classList.remove('field-bad');
                    documento.classList.add('field-ok');

                    aplicarParticipanteExistente(d, json.resultado || null, 'dni');

                    return;
                }

                if (idParticipante) idParticipante.value = '';
                limpiarParticipanteActual();

                const apiJson = await fetchJsonRapido(
                    'dni/buscar_dni.php?dni=' + encodeURIComponent(dni),
                    3200
                );

                if (apiJson && apiJson.success && apiJson.data) {
                    const datosApi = extraerDatosDniApi(apiJson.data);
                    const nombreApi = extraerNombreDniApi(apiJson.data);

                    llenarNombreParticipante(
                        datosApi.apellido_paterno,
                        datosApi.apellido_materno,
                        datosApi.nombres,
                        datosApi.completo || nombreApi
                    );

                    documento.classList.remove('field-bad');
                    documento.classList.add('field-ok');

                    if (idInstitucion) idInstitucion.value = '';
                    if (idModular) idModular.value = '';
                    if (codigoModular) codigoModular.value = '';
                    if (nivelModalidad) nivelModalidad.value = '';
                    if (selected && ie && ie.value.trim().length >= 3) mostrarIeManual(ie.value.trim().toUpperCase(), 'Nivel del salón');

                    mostrarAviso('DNI encontrado. Escribe el nombre del colegio y asigna salón.', 'ok');
                } else {
                    documento.classList.add('field-bad');
                    if (idInstitucion) idInstitucion.value = '';
                    if (idModular) idModular.value = '';
                    if (codigoModular) codigoModular.value = '';
                    if (nivelModalidad) nivelModalidad.value = '';
                    mostrarAviso('No estaba pre-registrado y no se encontró en API. Completa nombres y escribe el colegio manualmente.', 'warn');
                }
            } catch (error) {
                documento.classList.add('field-bad');
                mostrarAviso('No se pudo consultar. Completa nombres y colegio manualmente.', 'warn');
            } finally {
                if (spin) spin.style.display = 'none';
            }
        });
    }


    if (btnCorregirNombreSeleccionado) {
        btnCorregirNombreSeleccionado.addEventListener('click', function () {
            corregirNombreParticipante(participanteActual, 'seleccionado');
        });
    }

    if (frmResponsable) {
        frmResponsable.addEventListener('submit', function (event) {
            if (frmResponsable.dataset.confirmado === '1') return;

            event.preventDefault();

            Swal.fire({
                icon:'question',
                title:'¿Confirmar responsable?',
                text:'El docente quedará registrado como responsable activo de este salón.',
                showCancelButton:true,
                confirmButtonText:'Sí, confirmar',
                cancelButtonText:'Cancelar',
                confirmButtonColor:'#1d4ed8',
                cancelButtonColor:'#64748b'
            }).then(function (result) {
                if (result.isConfirmed) {
                    frmResponsable.dataset.confirmado = '1';
                    if (btnConfirmarResponsable) {
                        btnConfirmarResponsable.disabled = true;
                        btnConfirmarResponsable.innerHTML = '<i class="fa-solid fa-spinner fa-spin"></i> Guardando...';
                    }
                    frmResponsable.submit();
                }
            });
        });
    }

    if (frmAsignar) {
        frmAsignar.addEventListener('submit', function (event) {
            if (frmAsignar.dataset.confirmado === '1') return;

            if (documento && documento.value.trim() !== '' && !/^[0-9A-Za-z\-]{4,30}$/.test(documento.value.trim())) {
                event.preventDefault();
                mostrarAviso('Ingresa un DNI válido o código temporal válido. También puedes dejar el campo vacío para generar un código automático.', 'error');
                documento.focus();
                return;
            }

            if (!idParticipante || !idParticipante.value) {
                if (!apellidoPaterno || apellidoPaterno.value.trim() === '') {
                    event.preventDefault();
                    mostrarAviso('Completa el apellido paterno del participante.', 'error');
                    if (apellidoPaterno) apellidoPaterno.focus();
                    return;
                }

                if (!apellidoMaterno || apellidoMaterno.value.trim() === '') {
                    event.preventDefault();
                    mostrarAviso('Completa el apellido materno del participante.', 'error');
                    if (apellidoMaterno) apellidoMaterno.focus();
                    return;
                }

                if (!nombres || nombres.value.trim() === '') {
                    event.preventDefault();
                    mostrarAviso('Completa los nombres del participante.', 'error');
                    if (nombres) nombres.focus();
                    return;
                }

                if (!ie || ie.value.trim() === '') {
                    event.preventDefault();
                    mostrarAviso('Ingresa el nombre del colegio.', 'error');
                    if (ie) ie.focus();
                    return;
                }

                if (ie.value.trim().length < 3) {
                    event.preventDefault();
                    mostrarAviso('El nombre de la I.E. debe tener al menos 3 caracteres.', 'error');
                    if (ie) ie.focus();
                    return;
                }
            }

            if ((correctasInput && correctasInput.value.trim() !== '') || (incorrectasInput && incorrectasInput.value.trim() !== '')) {
                if (!recalcularCalificacion()) {
                    event.preventDefault();
                    mostrarAviso('Correctas + incorrectas no puede superar el total de preguntas.', 'error');
                    return;
                }
            }

            event.preventDefault();

            const guardaNota = (correctasInput && correctasInput.value.trim() !== '') || (incorrectasInput && incorrectasInput.value.trim() !== '');

            Swal.fire({
                icon:'question',
                title: guardaNota ? '¿Asignar y calificar?' : '¿Asignar al salón?',
                text: guardaNota ? 'Se asignará el salón y se guardará la calificación directa.' : 'Se registrará el ingreso del participante al salón seleccionado.',
                showCancelButton:true,
                confirmButtonText: guardaNota ? 'Sí, asignar y calificar' : 'Sí, asignar',
                cancelButtonText:'Cancelar',
                confirmButtonColor:'#1d4ed8',
                cancelButtonColor:'#64748b'
            }).then(function (result) {
                if (result.isConfirmed) {
                    frmAsignar.dataset.confirmado = '1';
                    if (btnGuardar) {
                        btnGuardar.disabled = true;
                        btnGuardar.innerHTML = '<i class="fa-solid fa-spinner fa-spin"></i> Guardando...';
                    }
                    frmAsignar.submit();
                }
            });
        });
    }

    if (btnLimpiar) {
        btnLimpiar.addEventListener('click', function () {
            if (documento) documento.value = '';
            limpiarNombreParticipante();
            if (ie) ie.value = '';
            if (idInstitucion) idInstitucion.value = '';
            if (idModular) idModular.value = '';
            if (codigoModular) codigoModular.value = '';
            if (nivelModalidad) nivelModalidad.value = '';
            if (idParticipante) idParticipante.value = '';
            limpiarParticipanteActual();
            limpiarCalificacion();

            if (results) {
                results.style.display = 'none';
                results.innerHTML = '';
            }

            if (buscarParticipanteNombre) buscarParticipanteNombre.value = '';
            ocultarResultadosParticipante();

            if (selected) {
                selected.style.display = 'none';
                selected.textContent = '';
            }

            setMiniAviso(aviso, '', '');
            if (documento) documento.focus();
        });
    }

    if (buscarAsignado && tablaAsignados) {
        buscarAsignado.addEventListener('input', function () {
            const q = buscarAsignado.value.toLowerCase().trim();
            const filas = tablaAsignados.querySelectorAll('tbody tr');

            filas.forEach(function (fila) {
                fila.style.display = fila.textContent.toLowerCase().includes(q) ? '' : 'none';
            });
        });
    }

    if (btnInfoAcceso) {
        btnInfoAcceso.addEventListener('click', function () {
            Swal.fire({
                icon:'info',
                title:'Datos del acceso',
                html:'<div style="text-align:left;line-height:1.7;font-weight:700">' +
                    '<b>Código:</b> <?php echo as_h($acceso['codigo']); ?><br>' +
                    '<b>Ámbito:</b> <?php echo as_h($ambito); ?><br>' +
                    '<b>Salón:</b> <?php echo $salonTrabajo ? as_h($salonTrabajo['nombre_salon'] . ' · ' . $salonTrabajo['codigo_salon']) : 'Sin salón'; ?><br>' +
                    '<b>Responsable:</b> <?php echo as_h($responsableNombreVista); ?>' +
                    '</div>',
                confirmButtonText:'Entendido',
                confirmButtonColor:'#1d4ed8'
            });
        });
    }

    if (mensajeServidorError) {
        sweet(mensajeServidorError, 'error', 'Error');
    } else if (mensajeServidorOk) {
        sweet(mensajeServidorOk, 'ok', 'Operación realizada');
    }

    if (BUSQUEDA_IE_CATALOGO_ACTIVA) {
        cargarDepartamentos();
    }
});
</script>

</body>
</html>
