| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244 |
- 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
|