<?php
declare(strict_types=1);

require_once __DIR__ . '/includes/bootstrap.php';

set_time_limit(0);
ini_set('memory_limit', '1024M');

$ctx = omr_context('procesar');
$conn = $ctx['conn'];
$idConcurso = (int)$ctx['id_concurso'];
$idGrupoFijo = (int)$ctx['id_grupo_fijo'];
$idSalonFijo = (int)$ctx['id_salon_fijo'];

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

$csrf = (string)$_SESSION['csrf_omr_procesar'];
$detalle = [];
$msg = '';
$err = '';

function omr_uploaded_files(): array
{
    if (!isset($_FILES['archivos'])) {
        return [];
    }

    $f = $_FILES['archivos'];
    $items = [];

    if (is_array($f['name'])) {
        $total = count($f['name']);

        for ($i = 0; $i < $total; $i++) {
            $items[] = [
                'name' => (string)($f['name'][$i] ?? ''),
                'tmp_name' => (string)($f['tmp_name'][$i] ?? ''),
                'error' => (int)($f['error'][$i] ?? UPLOAD_ERR_NO_FILE),
                'size' => (int)($f['size'][$i] ?? 0),
            ];
        }
    } else {
        $items[] = [
            'name' => (string)($f['name'] ?? ''),
            'tmp_name' => (string)($f['tmp_name'] ?? ''),
            'error' => (int)($f['error'] ?? UPLOAD_ERR_NO_FILE),
            'size' => (int)($f['size'] ?? 0),
        ];
    }

    return $items;
}

function omr_extract_zip(string $zipPath, string $outDir): array
{
    if (!class_exists('ZipArchive')) {
        throw new RuntimeException('PHP no tiene habilitada la extension ZipArchive.');
    }

    $zip = new ZipArchive();
    if ($zip->open($zipPath) !== true) {
        throw new RuntimeException('No se pudo abrir el ZIP.');
    }

    omr_ensure_dir($outDir);
    $items = [];

    try {
        $limit = min((int)$zip->numFiles, CN_OMR_MAX_ZIP_ENTRIES);

        for ($i = 0; $i < $limit; $i++) {
            $inner = (string)$zip->getNameIndex($i);
            $normal = str_replace('\\', '/', $inner);

            if ($inner === '' || substr($normal, -1) === '/') {
                continue;
            }

            if (strpos($normal, '../') !== false || substr($normal, 0, 1) === '/' || preg_match('/^[A-Za-z]:\//', $normal)) {
                continue;
            }

            $base = omr_safe_filename($normal);
            $ext = omr_ext($base);

            if (!in_array($ext, CN_OMR_ALLOWED_EXTENSIONS, true) || $ext === 'zip') {
                continue;
            }

            $stream = $zip->getStream($inner);
            if (!$stream) {
                continue;
            }

            $dest = rtrim($outDir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . 'zip_' . ($i + 1) . '_' . bin2hex(random_bytes(3)) . '_' . $base;
            $out = fopen($dest, 'wb');

            if (!$out) {
                fclose($stream);
                continue;
            }

            stream_copy_to_stream($stream, $out);
            fclose($stream);
            fclose($out);

            if (is_file($dest) && filesize($dest) > 0) {
                $items[] = [
                    'path' => $dest,
                    'original' => $inner,
                    'ext' => $ext,
                    'source' => 'ZIP',
                ];
            }
        }
    } finally {
        $zip->close();
    }

    return $items;
}

function omr_pdf_to_images(string $pdfPath, string $outDir): array
{
    omr_ensure_dir($outDir);

    $cmd = omr_python_command([
        CN_OMR_PDF_TO_PAGES,
        '--pdf',
        $pdfPath,
        '--outdir',
        $outDir,
        '--dpi',
        (string)CN_OMR_RENDER_DPI,
    ]);

    $json = omr_exec_json($cmd);

    if (!($json['ok'] ?? false)) {
        throw new RuntimeException((string)($json['error'] ?? 'No se pudo convertir el PDF.'));
    }

    return is_array($json['pages'] ?? null) ? $json['pages'] : [];
}

function omr_read_image(string $imagePath): array
{
    $cmd = omr_python_command([
        CN_OMR_PROCESSOR,
        '--image',
        $imagePath,
        '--template',
        CN_OMR_TEMPLATE_PATH,
    ]);

    $json = omr_exec_json($cmd);

    if (!($json['ok'] ?? false)) {
        throw new RuntimeException((string)($json['error'] ?? 'No se pudo leer la ficha.'));
    }

    return $json;
}

function omr_answer_value(array $answer): string
{
    $value = strtoupper(trim((string)($answer['value'] ?? '')));
    $status = strtolower(trim((string)($answer['status'] ?? '')));

    if ($value === 'DOBLE' || strpos($status, 'double') !== false || strpos($status, 'multiple') !== false) {
        return 'DOBLE';
    }

    if (in_array($value, ['A', 'B', 'C', 'D'], true)) {
        return $value;
    }

    return 'BLANCO';
}

function omr_calcular(array $answers, array $clave, int $totalPreguntas): array
{
    $correctas = 0;
    $incorrectas = 0;
    $blanco = 0;
    $dobles = 0;

    for ($i = 1; $i <= $totalPreguntas; $i++) {
        $answer = $answers[(string)$i] ?? [];
        if (!is_array($answer)) {
            $answer = [];
        }

        $leida = omr_answer_value($answer);

        if ($leida === 'DOBLE') {
            $dobles++;
            continue;
        }

        if ($leida === 'BLANCO') {
            $blanco++;
            continue;
        }

        $correcta = $clave[$i]['respuesta'] ?? null;

        if ($correcta !== null && $leida === $correcta) {
            $correctas++;
        } else {
            $incorrectas++;
        }
    }

    return [
        'correctas' => $correctas,
        'incorrectas' => $incorrectas,
        'blanco' => $blanco,
        'dobles' => $dobles,
    ];
}

function omr_guardar_log(mysqli $conn, array $data): int
{
    $stmt = mysqli_prepare($conn, "
        INSERT INTO concurso_omr_procesamientos
            (
                id_concurso, id_grupo, id_salon, id_participante, id_acceso,
                lote, archivo_original, archivo_guardado, pagina,
                dni_leido, nivel_leido, grado_leido, tipo_leido,
                total_preguntas, correctas, incorrectas, blanco, dobles, puntaje,
                estado, aplicado_resultado, observacion, raw_json, created_at
            )
        VALUES
            (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW())
    ");

    if (!$stmt) {
        throw new RuntimeException('No se pudo preparar log OMR.');
    }

    mysqli_stmt_bind_param(
        $stmt,
        'iiiiisssisiisiiiiidsiss',
        $data['id_concurso'],
        $data['id_grupo'],
        $data['id_salon'],
        $data['id_participante'],
        $data['id_acceso'],
        $data['lote'],
        $data['archivo_original'],
        $data['archivo_guardado'],
        $data['pagina'],
        $data['dni_leido'],
        $data['nivel_leido'],
        $data['grado_leido'],
        $data['tipo_leido'],
        $data['total_preguntas'],
        $data['correctas'],
        $data['incorrectas'],
        $data['blanco'],
        $data['dobles'],
        $data['puntaje'],
        $data['estado'],
        $data['aplicado_resultado'],
        $data['observacion'],
        $data['raw_json']
    );

    if (!mysqli_stmt_execute($stmt)) {
        $error = mysqli_stmt_error($stmt);
        mysqli_stmt_close($stmt);
        throw new RuntimeException('No se pudo guardar log OMR: ' . $error);
    }

    $id = (int)mysqli_insert_id($conn);
    mysqli_stmt_close($stmt);

    return $id;
}

function omr_guardar_respuestas(mysqli $conn, int $idProcesamiento, array $answers, array $clave, int $totalPreguntas): void
{
    $stmt = mysqli_prepare($conn, "
        INSERT INTO concurso_omr_respuestas
            (id_procesamiento, pregunta, respuesta_leida, respuesta_correcta, es_correcta, score_a, score_b, score_c, score_d)
        VALUES
            (?, ?, ?, ?, ?, ?, ?, ?, ?)
    ");

    if (!$stmt) {
        return;
    }

    for ($i = 1; $i <= $totalPreguntas; $i++) {
        $answer = $answers[(string)$i] ?? [];
        if (!is_array($answer)) {
            $answer = [];
        }

        $leida = omr_answer_value($answer);
        $correcta = $clave[$i]['respuesta'] ?? null;
        $esCorrecta = ($correcta !== null && $leida === $correcta) ? 1 : 0;
        $scores = is_array($answer['scores'] ?? null) ? $answer['scores'] : [];
        $a = isset($scores['A']) ? (float)$scores['A'] : null;
        $b = isset($scores['B']) ? (float)$scores['B'] : null;
        $c = isset($scores['C']) ? (float)$scores['C'] : null;
        $d = isset($scores['D']) ? (float)$scores['D'] : null;

        mysqli_stmt_bind_param($stmt, 'iissidddd', $idProcesamiento, $i, $leida, $correcta, $esCorrecta, $a, $b, $c, $d);
        mysqli_stmt_execute($stmt);
    }

    mysqli_stmt_close($stmt);
}

function omr_process_image_entry(mysqli $conn, array $ctx, array $entry, int $pagina, string $lote): array
{
    $idConcurso = (int)$ctx['id_concurso'];
    $idGrupoFijo = (int)$ctx['id_grupo_fijo'];
    $idSalonFijo = (int)$ctx['id_salon_fijo'];
    $acceso = $ctx['acceso'];
    $json = omr_read_image($entry['path']);
    $data = is_array($json['data'] ?? null) ? $json['data'] : [];

    $dni = preg_replace('/\D+/', '', (string)($data['dni']['value'] ?? ''));
    $nivel = (int)($data['nivel']['value'] ?? 0);
    $grado = (int)($data['grado']['value'] ?? 0);
    $tipo = strtoupper(trim((string)($data['tipo']['value'] ?? '')));
    $answers = is_array($data['answers'] ?? null) ? $data['answers'] : [];

    $observaciones = [];
    $participante = null;
    $clave = [];
    $calc = ['correctas' => 0, 'incorrectas' => 0, 'blanco' => 0, 'dobles' => 0];
    $puntaje = 0.0;
    $aplicado = 0;

    if (strlen($dni) !== 8) {
        $observaciones[] = 'DNI no leido correctamente.';
    }

    if ($nivel < 1 || $nivel > 3) {
        $observaciones[] = 'Nivel no leido correctamente.';
    }

    if ($grado < 1 || $grado > 6) {
        $observaciones[] = 'Grado no leido correctamente.';
    }

    if (!omr_tipo_valido($tipo)) {
        $observaciones[] = 'Tipo de prueba no leido correctamente.';
    }

    if (strlen($dni) === 8) {
        $participante = omr_participante_por_dni($conn, $idConcurso, $dni);

        if (!$participante) {
            $observaciones[] = 'DNI no encontrado en participantes del concurso.';
        }
    }

    if ($participante) {
        $idGrupo = (int)$participante['id_grupo'];
        $idSalon = $participante['id_salon'] !== null ? (int)$participante['id_salon'] : 0;

        if ($idGrupoFijo > 0 && $idGrupo !== $idGrupoFijo) {
            $observaciones[] = 'El participante no pertenece al grado asignado a este acceso.';
        }

        if ($idSalonFijo > 0 && $idSalon !== $idSalonFijo) {
            $observaciones[] = 'El participante no pertenece al salon asignado a este acceso.';
        }

        $nivelEsperado = omr_nivel_numero((string)$participante['grupo_nivel']);
        if ($nivel > 0 && $nivelEsperado > 0 && $nivel !== $nivelEsperado) {
            $observaciones[] = 'Nivel marcado no coincide con el grado del participante.';
        }

        if ($grado > 0 && $grado !== (int)$participante['grado_orden']) {
            $observaciones[] = 'Grado marcado no coincide con el participante.';
        }

        $totalPreguntas = max(1, min(80, (int)$participante['total_preguntas']));

        if (omr_tipo_valido($tipo)) {
            $clave = omr_clave($conn, $idConcurso, $idGrupo, $tipo);

            if (count($clave) < $totalPreguntas) {
                $observaciones[] = 'Clave OMR incompleta para el grado y tipo marcados.';
            }

            $calc = omr_calcular($answers, $clave, $totalPreguntas);
            $puntaje = omr_calculate_score((int)$calc['correctas'], (int)$calc['incorrectas'], (int)$calc['blanco'], $participante);

            if ((int)$calc['dobles'] > 0) {
                $observaciones[] = 'La ficha tiene marcas dobles.';
            }
        }
    } else {
        $totalPreguntas = 0;
    }

    $estado = empty($observaciones) ? 'PROCESADO' : 'OBSERVADO';
    $observacion = empty($observaciones) ? 'Lectura OMR aplicada al resultado final.' : implode(' ', $observaciones);

    mysqli_begin_transaction($conn);

    if ($estado === 'PROCESADO' && $participante) {
        omr_guardar_resultado_final(
            $conn,
            $acceso,
            $participante,
            (int)$calc['correctas'],
            (int)$calc['incorrectas'],
            (int)$calc['blanco'],
            $puntaje,
            'OMR | Tipo ' . $tipo . ' | Archivo: ' . $entry['original']
        );

        $aplicado = 1;
    }

    $rawJson = json_encode($json, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
    if ($rawJson === false) {
        $rawJson = '{}';
    }

    $idProcesamiento = omr_guardar_log($conn, [
        'id_concurso' => $idConcurso,
        'id_grupo' => $participante ? (int)$participante['id_grupo'] : null,
        'id_salon' => $participante && $participante['id_salon'] !== null ? (int)$participante['id_salon'] : null,
        'id_participante' => $participante ? (int)$participante['id_participante'] : null,
        'id_acceso' => (int)$acceso['id_acceso'],
        'lote' => $lote,
        'archivo_original' => (string)$entry['original'],
        'archivo_guardado' => (string)$entry['path'],
        'pagina' => $pagina > 0 ? $pagina : null,
        'dni_leido' => $dni !== '' ? $dni : null,
        'nivel_leido' => $nivel > 0 ? $nivel : null,
        'grado_leido' => $grado > 0 ? $grado : null,
        'tipo_leido' => omr_tipo_valido($tipo) ? $tipo : null,
        'total_preguntas' => (int)$totalPreguntas,
        'correctas' => (int)$calc['correctas'],
        'incorrectas' => (int)$calc['incorrectas'],
        'blanco' => (int)$calc['blanco'],
        'dobles' => (int)$calc['dobles'],
        'puntaje' => $puntaje,
        'estado' => $estado,
        'aplicado_resultado' => $aplicado,
        'observacion' => $observacion,
        'raw_json' => $rawJson,
    ]);

    if ($totalPreguntas > 0) {
        omr_guardar_respuestas($conn, $idProcesamiento, $answers, $clave, $totalPreguntas);
    }

    cn_auditar(
        $conn,
        $idConcurso,
        $participante ? (int)$participante['id_grupo'] : null,
        (int)$acceso['id_acceso'],
        $participante ? (int)$participante['id_participante'] : null,
        'OMR_PROCESADO',
        $estado . ' | DNI ' . ($dni !== '' ? $dni : '-') . ' | ' . $observacion
    );

    mysqli_commit($conn);

    return [
        'id' => $idProcesamiento,
        'archivo' => (string)$entry['original'],
        'pagina' => $pagina > 0 ? (string)$pagina : '-',
        'dni' => $dni !== '' ? $dni : '-',
        'participante' => $participante ? (string)$participante['apellidos_nombres'] : '-',
        'estado' => $estado,
        'aplicado' => $aplicado,
        'correctas' => (int)$calc['correctas'],
        'incorrectas' => (int)$calc['incorrectas'],
        'blanco' => (int)$calc['blanco'],
        'dobles' => (int)$calc['dobles'],
        'puntaje' => number_format($puntaje, 2, '.', ''),
        'observacion' => $observacion,
    ];
}

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    try {
        $csrfPost = (string)($_POST['csrf_token'] ?? '');

        if (!hash_equals($csrf, $csrfPost)) {
            throw new RuntimeException('Solicitud no valida. Recarga la pagina.');
        }

        $files = omr_uploaded_files();
        if (empty($files)) {
            throw new RuntimeException('Selecciona al menos un archivo.');
        }

        $lote = 'omr_' . date('Ymd_His') . '_' . bin2hex(random_bytes(3));
        $baseDir = CN_OMR_UPLOADS_DIR . DIRECTORY_SEPARATOR . 'lotes' . DIRECTORY_SEPARATOR . $lote;
        $originalesDir = $baseDir . DIRECTORY_SEPARATOR . 'originales';
        $paginasDir = $baseDir . DIRECTORY_SEPARATOR . 'paginas';
        $zipDir = $baseDir . DIRECTORY_SEPARATOR . 'zip_extraidos';

        omr_ensure_dir($originalesDir);
        omr_ensure_dir($paginasDir);

        $entries = [];

        foreach ($files as $idx => $file) {
            $original = (string)$file['name'];
            $errorUpload = (int)$file['error'];
            $size = (int)$file['size'];

            if ($errorUpload !== UPLOAD_ERR_OK) {
                $detalle[] = ['archivo' => $original, 'pagina' => '-', 'dni' => '-', 'participante' => '-', 'estado' => 'ERROR', 'aplicado' => 0, 'correctas' => 0, 'incorrectas' => 0, 'blanco' => 0, 'dobles' => 0, 'puntaje' => '0.00', 'observacion' => 'Error de subida: ' . $errorUpload];
                continue;
            }

            if ($size <= 0 || $size > CN_OMR_MAX_FILE_BYTES) {
                $detalle[] = ['archivo' => $original, 'pagina' => '-', 'dni' => '-', 'participante' => '-', 'estado' => 'ERROR', 'aplicado' => 0, 'correctas' => 0, 'incorrectas' => 0, 'blanco' => 0, 'dobles' => 0, 'puntaje' => '0.00', 'observacion' => 'Archivo vacio o mayor a 25 MB.'];
                continue;
            }

            $ext = omr_ext($original);
            if (!in_array($ext, CN_OMR_ALLOWED_EXTENSIONS, true)) {
                $detalle[] = ['archivo' => $original, 'pagina' => '-', 'dni' => '-', 'participante' => '-', 'estado' => 'ERROR', 'aplicado' => 0, 'correctas' => 0, 'incorrectas' => 0, 'blanco' => 0, 'dobles' => 0, 'puntaje' => '0.00', 'observacion' => 'Formato no permitido.'];
                continue;
            }

            $tmp = (string)$file['tmp_name'];
            if ($tmp === '' || !is_uploaded_file($tmp)) {
                $detalle[] = ['archivo' => $original, 'pagina' => '-', 'dni' => '-', 'participante' => '-', 'estado' => 'ERROR', 'aplicado' => 0, 'correctas' => 0, 'incorrectas' => 0, 'blanco' => 0, 'dobles' => 0, 'puntaje' => '0.00', 'observacion' => 'Archivo temporal invalido.'];
                continue;
            }

            $safe = 'subido_' . ($idx + 1) . '_' . bin2hex(random_bytes(3)) . '_' . omr_safe_filename($original);
            $dest = $originalesDir . DIRECTORY_SEPARATOR . $safe;

            if (!move_uploaded_file($tmp, $dest)) {
                $detalle[] = ['archivo' => $original, 'pagina' => '-', 'dni' => '-', 'participante' => '-', 'estado' => 'ERROR', 'aplicado' => 0, 'correctas' => 0, 'incorrectas' => 0, 'blanco' => 0, 'dobles' => 0, 'puntaje' => '0.00', 'observacion' => 'No se pudo guardar el archivo.'];
                continue;
            }

            if ($ext === 'zip') {
                $zipEntries = omr_extract_zip($dest, $zipDir . DIRECTORY_SEPARATOR . pathinfo($safe, PATHINFO_FILENAME));
                foreach ($zipEntries as $ze) {
                    $entries[] = $ze;
                }
            } else {
                $entries[] = [
                    'path' => $dest,
                    'original' => $original,
                    'ext' => $ext,
                    'source' => 'SUBIDA',
                ];
            }
        }

        foreach ($entries as $entry) {
            try {
                if ($entry['ext'] === 'pdf') {
                    $outDir = $paginasDir . DIRECTORY_SEPARATOR . pathinfo((string)$entry['path'], PATHINFO_FILENAME);
                    $pages = omr_pdf_to_images((string)$entry['path'], $outDir);

                    foreach ($pages as $page) {
                        $image = (string)($page['image'] ?? '');
                        $pageNum = (int)($page['page'] ?? 0);

                        if ($image === '' || !is_file($image)) {
                            throw new RuntimeException('No se encontro una pagina convertida del PDF.');
                        }

                        $detalle[] = omr_process_image_entry($conn, $ctx, [
                            'path' => $image,
                            'original' => (string)$entry['original'],
                            'ext' => omr_ext($image),
                            'source' => 'PDF',
                        ], $pageNum, $lote);
                    }
                } else {
                    $detalle[] = omr_process_image_entry($conn, $ctx, $entry, 0, $lote);
                }
            } catch (Throwable $e) {
                @mysqli_rollback($conn);
                $detalle[] = ['archivo' => (string)$entry['original'], 'pagina' => '-', 'dni' => '-', 'participante' => '-', 'estado' => 'ERROR', 'aplicado' => 0, 'correctas' => 0, 'incorrectas' => 0, 'blanco' => 0, 'dobles' => 0, 'puntaje' => '0.00', 'observacion' => $e->getMessage()];
            }
        }

        $_SESSION['csrf_omr_procesar'] = bin2hex(random_bytes(32));
        $csrf = (string)$_SESSION['csrf_omr_procesar'];
        $msg = 'Procesamiento terminado. Revisa el detalle.';
    } catch (Throwable $e) {
        $err = $e->getMessage();
    }
}

omr_page_header('Procesar escaneos OMR', $ctx);
?>

<?php if ($msg !== ''): ?><div class="alert ok"><?php echo omr_h($msg); ?></div><?php endif; ?>
<?php if ($err !== ''): ?><div class="alert err"><?php echo omr_h($err); ?></div><?php endif; ?>

<div class="card">
    <form method="post" enctype="multipart/form-data">
        <input type="hidden" name="csrf_token" value="<?php echo omr_h($csrf); ?>">
        <label>Archivos OMR</label>
        <input type="file" name="archivos[]" multiple accept=".pdf,.png,.jpg,.jpeg,.tif,.tiff,.zip" required>
        <p class="muted">
            Acepta imagenes, PDF o ZIP. Las fichas correctas se guardan automaticamente en resultados.
            Las fichas con DNI no encontrado, clave incompleta, marcas dobles o datos inconsistentes quedan observadas para correccion manual.
        </p>
        <button class="btn" type="submit">Procesar ahora</button>
        <a class="btn light" href="resultados.php?id_concurso=<?php echo $idConcurso; ?>">Ver historial</a>
    </form>
</div>

<?php if (!empty($detalle)): ?>
    <div class="card">
        <h2 style="margin-top:0;">Detalle del lote</h2>
        <div style="overflow:auto;">
            <table>
                <thead>
                    <tr>
                        <th>Estado</th>
                        <th>Archivo</th>
                        <th>Pag.</th>
                        <th>DNI</th>
                        <th>Participante</th>
                        <th>Resultado</th>
                        <th>Obs.</th>
                    </tr>
                </thead>
                <tbody>
                    <?php foreach ($detalle as $row): ?>
                        <?php
                        $estado = (string)$row['estado'];
                        $badge = $estado === 'PROCESADO' ? 'b-ok' : ($estado === 'OBSERVADO' ? 'b-warn' : 'b-err');
                        ?>
                        <tr>
                            <td><span class="badge <?php echo $badge; ?>"><?php echo omr_h($estado); ?></span><?php if ((int)$row['aplicado'] === 1): ?><br><span class="badge b-blue">Ranking</span><?php endif; ?></td>
                            <td><?php echo omr_h($row['archivo']); ?></td>
                            <td><?php echo omr_h($row['pagina']); ?></td>
                            <td><?php echo omr_h($row['dni']); ?></td>
                            <td><?php echo omr_h($row['participante']); ?></td>
                            <td>
                                C: <?php echo (int)$row['correctas']; ?> |
                                I: <?php echo (int)$row['incorrectas']; ?> |
                                B: <?php echo (int)$row['blanco']; ?> |
                                D: <?php echo (int)$row['dobles']; ?><br>
                                <strong><?php echo omr_h($row['puntaje']); ?></strong>
                            </td>
                            <td><?php echo omr_h($row['observacion']); ?></td>
                        </tr>
                    <?php endforeach; ?>
                </tbody>
            </table>
        </div>
    </div>
<?php endif; ?>

<?php omr_page_footer(); ?>
