from fastapi import APIRouter, Depends, HTTPException, Body from pydantic import BaseModel from sqlalchemy.orm import Session, joinedload from sqlalchemy import func from datetime import datetime, timezone from ..database import get_db from ..models import Alumno, Curso, Matricula, Actividad, Entrega, Calificacion, Progreso, Profesor, Horario, Proyecto, ProyectoEstudiante, Examen from .auth import get_current_student router = APIRouter(prefix="/api/student", tags=["Student"]) class TaskSubmit(BaseModel): contenido: str class ProjectProgress(BaseModel): progress: float @router.get("/dashboard") def get_dashboard(student: Alumno = Depends(get_current_student), db: Session = Depends(get_db)): student_id = student.id_alumno alumno = db.query(Alumno).options(joinedload(Alumno.usuario)).filter(Alumno.id_alumno == student_id).first() progresos = db.query(Progreso).filter(Progreso.id_alumno == student_id).all() avg_progress = sum(float(p.porcentaje) for p in progresos) / len(progresos) if progresos else 0 # Gamification mechanics xp = int(avg_progress * 150) level = max(1, xp // 1000) streak = 12 # Hardcoded for demo purposes matriculas = db.query(Matricula).filter(Matricula.id_alumno == student_id).all() curso_ids = [m.id_curso for m in matriculas] active_courses = len(curso_ids) pending_tasks = 0 if curso_ids: actividades = db.query(Actividad).filter(Actividad.id_curso.in_(curso_ids)).all() now = datetime.now(timezone.utc) for a in actividades: e = db.query(Entrega).filter(Entrega.id_actividad == a.id_actividad, Entrega.id_alumno == student_id).first() if not e and (not a.fecha_entrega or a.fecha_entrega.replace(tzinfo=timezone.utc) >= now): pending_tasks += 1 return { "id": alumno.id_alumno, "name": f"{alumno.usuario.nombre} {alumno.usuario.apellidos or ''}".strip(), "email": alumno.usuario.email, "level_name": alumno.nivel, "xp": xp, "level": level, "streak": streak, "avg_progress": round(avg_progress, 1), "gpa": round(avg_progress * 0.1, 1), # Simulated GPA 0-10 "active_courses": active_courses, "pending_tasks": pending_tasks } @router.get("/courses") def get_courses(student: Alumno = Depends(get_current_student), db: Session = Depends(get_db)): student_id = student.id_alumno matriculas = db.query(Matricula).options( joinedload(Matricula.curso).joinedload(Curso.profesor).joinedload(Profesor.usuario) ).filter(Matricula.id_alumno == student_id).all() courses = [] for m in matriculas: c = m.curso prof_name = f"{c.profesor.usuario.nombre} {c.profesor.usuario.apellidos or ''}".strip() progreso = db.query(Progreso).filter(Progreso.id_alumno == student_id, Progreso.id_curso == c.id_curso).first() prog_pct = float(progreso.porcentaje) if progreso else 0 courses.append({ "id": c.id_curso, "name": c.nombre, "description": c.descripcion, "teacher": prof_name, "progress": prog_pct }) return courses @router.get("/tasks") def get_tasks(student: Alumno = Depends(get_current_student), db: Session = Depends(get_db)): student_id = student.id_alumno matriculas = db.query(Matricula).filter(Matricula.id_alumno == student_id).all() curso_ids = [m.id_curso for m in matriculas] if not curso_ids: return [] actividades = db.query(Actividad).options(joinedload(Actividad.curso)).filter(Actividad.id_curso.in_(curso_ids)).all() tasks = [] now = datetime.now(timezone.utc) for a in actividades: entrega = db.query(Entrega).options(joinedload(Entrega.calificacion)).filter( Entrega.id_actividad == a.id_actividad, Entrega.id_alumno == student_id ).first() status = "pending" score = None if entrega: status = "submitted" if entrega.calificacion: status = "graded" score = float(entrega.calificacion.nota) elif a.fecha_entrega and a.fecha_entrega.replace(tzinfo=timezone.utc) < now: status = "overdue" tasks.append({ "id": a.id_actividad, "title": a.titulo, "course": a.curso.nombre, "due_date": a.fecha_entrega.isoformat() if a.fecha_entrega else None, "max_score": float(a.puntuacion_maxima), "status": status, "score": score }) return tasks @router.post("/tasks/{task_id}/submit") def submit_task(task_id: int, payload: TaskSubmit, student: Alumno = Depends(get_current_student), db: Session = Depends(get_db)): student_id = student.id_alumno actividad = db.query(Actividad).filter(Actividad.id_actividad == task_id).first() if not actividad: raise HTTPException(status_code=404, detail="Tarea no encontrada") entrega = db.query(Entrega).filter(Entrega.id_actividad == task_id, Entrega.id_alumno == student_id).first() if entrega: entrega.contenido = payload.contenido entrega.estado = "entregado" entrega.fecha_entrega = datetime.now(timezone.utc) else: entrega = Entrega( id_actividad=task_id, id_alumno=student_id, contenido=payload.contenido, estado="entregado" ) db.add(entrega) db.commit() return {"message": "Tarea entregada correctamente"} @router.get("/grades") def get_grades(student: Alumno = Depends(get_current_student), db: Session = Depends(get_db)): student_id = student.id_alumno entregas = db.query(Entrega).options( joinedload(Entrega.actividad).joinedload(Actividad.curso), joinedload(Entrega.calificacion) ).filter(Entrega.id_alumno == student_id).all() grades = [] for e in entregas: if not e.calificacion: continue a = e.actividad grades.append({ "task_id": a.id_actividad, "task_title": a.titulo, "course": a.curso.nombre, "score": float(e.calificacion.nota), "max_score": float(a.puntuacion_maxima), "feedback": e.calificacion.observaciones, "date": e.calificacion.fecha_calificacion.isoformat() }) return grades @router.get("/schedule") def get_schedule(student: Alumno = Depends(get_current_student), db: Session = Depends(get_db)): student_id = student.id_alumno matriculas = db.query(Matricula).filter(Matricula.id_alumno == student_id).all() curso_ids = [m.id_curso for m in matriculas] if not curso_ids: return [] horarios = db.query(Horario).options(joinedload(Horario.curso)).filter(Horario.id_curso.in_(curso_ids)).all() res = [] for h in horarios: res.append({ "day": h.dia_semana, "start": h.hora_inicio, "end": h.hora_fin, "course": h.curso.nombre, "room": h.aula }) return res @router.get("/projects") def get_projects(student: Alumno = Depends(get_current_student), db: Session = Depends(get_db)): student_id = student.id_alumno pes = db.query(ProyectoEstudiante).options(joinedload(ProyectoEstudiante.proyecto).joinedload(Proyecto.curso)).filter(ProyectoEstudiante.id_alumno == student_id).all() res = [] for pe in pes: p = pe.proyecto # Simulated teammates for UI purposes teammates = [{"name": "A", "initial": "A"}, {"name": "B", "initial": "B"}, {"name": "C", "initial": "C"}] res.append({ "id": p.id_proyecto, "title": p.titulo, "description": p.descripcion, "course": p.curso.nombre, "due_date": p.fecha_entrega.isoformat() if p.fecha_entrega else None, "status": p.estado, "progress": float(p.porcentaje_completado), "team": teammates }) return res @router.post("/projects/{project_id}/progress") def update_project_progress(project_id: int, payload: ProjectProgress, student: Alumno = Depends(get_current_student), db: Session = Depends(get_db)): student_id = student.id_alumno pe = db.query(ProyectoEstudiante).filter(ProyectoEstudiante.id_proyecto == project_id, ProyectoEstudiante.id_alumno == student_id).first() if not pe: raise HTTPException(status_code=403, detail="No eres parte de este proyecto") p = db.query(Proyecto).filter(Proyecto.id_proyecto == project_id).first() if p: p.porcentaje_completado = max(0, min(100, payload.progress)) if p.porcentaje_completado == 100: p.estado = "entregado" else: p.estado = "en curso" db.commit() return {"message": "Progreso actualizado"} @router.get("/exams") def get_exams(student: Alumno = Depends(get_current_student), db: Session = Depends(get_db)): student_id = student.id_alumno matriculas = db.query(Matricula).filter(Matricula.id_alumno == student_id).all() curso_ids = [m.id_curso for m in matriculas] if not curso_ids: return [] examenes = db.query(Examen).options(joinedload(Examen.curso)).filter(Examen.id_curso.in_(curso_ids)).all() res = [] for e in examenes: res.append({ "id": e.id_examen, "title": e.titulo, "syllabus": e.temario, "course": e.curso.nombre, "date": e.fecha.isoformat(), "duration": e.duracion_minutos, "mode": e.modalidad }) return res