feat: frontend alumnos sam-pruebas (RUT+clave, rendir y mis resultados)

This commit is contained in:
Nurfog committed 2026-09-28 10:58:45 -03:00
commit e6d283e184
26 files changed
+2555

No files matched your search

+133
View File
@@ -0,0 +1,133 @@
// Cliente del backend SAM: GET /api/sam-pruebas/* (repo ICHN/sam, prod desplegado).
const API_BASE = process.env.NEXT_PUBLIC_API_URL ?? "http://localhost:5000";
const B = `${API_BASE}/api/sam-pruebas`;
async function req(path: string, init?: RequestInit) {
const r = await fetch(B + path, { cache: "no-store", ...init });
if (!r.ok) {
const t = await r.text().catch(() => "");
throw new Error(t.slice(0, 300) || `HTTP ${r.status}`);
}
if (r.status === 204) return null;
return r.json();
}
export const apiGet = (path: string) => req(path);
export const apiPost = (path: string, body: unknown) =>
req(path, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) });
export const apiPut = (path: string, body: unknown) =>
req(path, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) });
/** JSON.parse tolerante (columnas ::text que pueden venir null o malformadas). */
export function parseJSON(s: string | null | undefined): any {
if (!s) return null;
try {
return JSON.parse(s);
} catch {
return null;
}
}
export interface Creador {
idUsuario: string;
nombres: string;
apPaterno: string;
apMaterno?: string;
email: string;
tipo?: string;
}
export interface Curso {
idCursos: number;
nombreCurso: string;
nivelCurso?: number;
duracion?: number;
}
export interface Alumno {
idAlumno: string;
nombres: string;
apPaterno: string;
apMaterno?: string;
email: string;
idCursoAbierto: number;
idCursos: number;
nombreCurso: string;
}
export interface Template {
id: string;
nombre: string;
descripcion?: string;
idCursos?: number;
nivel?: string;
tipoCurso: string;
tipoPrueba: string;
duracionMin: number;
puntajeAprobacion: number;
puntajeTotal: number;
instrucciones?: string;
usoCount: number;
creadoPor?: string;
}
export interface Pregunta {
id: string;
templateId: string;
sectionId?: string;
orden: number;
tipoPregunta: string;
pregunta: string;
opciones?: string;
respuestaCorrecta?: string;
explicacion?: string;
puntaje: number;
metadata?: string;
}
export interface BancoItem {
id: string;
pregunta: string;
tipoPregunta: string;
puntaje: number;
dificultad: string;
skill?: string;
activo: boolean;
usoCount: number;
}
export interface Asignacion {
id: string;
templateId: string;
origen: string;
idCursos?: number;
idCursoAbierto?: number;
fechaInicio?: string;
fechaFin?: string;
activo: boolean;
asignadoPor?: string;
}
export interface Rendicion {
id: string;
asignacionId: string;
idAlumno: string;
intento: number;
puntajeObtenido?: number;
puntajeMaximo?: number;
aprobado?: boolean;
inicio: string;
termino?: string;
respuestas: string;
}
export const TIPOS_PRUEBA = ["CA", "MWT", "MOT", "FOT", "FWT"];
export const TIPOS_PREGUNTA = [
"multiple-choice",
"true-false",
"short-answer",
"essay",
"matching",
"ordering",
"fill-in-the-blanks",
"audio-response"
];
/** Tipos con corrección automática en el frontend. */
export const AUTO_TYPES = ["multiple-choice", "true-false", "short-answer"];
export function nombreCompleto(a: { nombres: string; apPaterno: string; apMaterno?: string }) {
return `${a.nombres} ${a.apPaterno}${a.apMaterno ? " " + a.apMaterno : ""}`;
}