student.py 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. from fastapi import APIRouter, Depends, HTTPException, Body
  2. from pydantic import BaseModel
  3. from sqlalchemy.orm import Session, joinedload
  4. from sqlalchemy import func
  5. from datetime import datetime, timezone
  6. from ..database import get_db
  7. from ..models import Alumno, Curso, Matricula, Actividad, Entrega, Calificacion, Progreso, Profesor, Horario, Proyecto, ProyectoEstudiante, Examen
  8. from .auth import get_current_student
  9. router = APIRouter(prefix="/api/student", tags=["Student"])
  10. class TaskSubmit(BaseModel):
  11. contenido: str
  12. class ProjectProgress(BaseModel):
  13. progress: float
  14. @router.get("/dashboard")
  15. def get_dashboard(student: Alumno = Depends(get_current_student), db: Session = Depends(get_db)):
  16. student_id = student.id_alumno
  17. alumno = db.query(Alumno).options(joinedload(Alumno.usuario)).filter(Alumno.id_alumno == student_id).first()
  18. progresos = db.query(Progreso).filter(Progreso.id_alumno == student_id).all()
  19. avg_progress = sum(float(p.porcentaje) for p in progresos) / len(progresos) if progresos else 0
  20. # Gamification mechanics
  21. xp = int(avg_progress * 150)
  22. level = max(1, xp // 1000)
  23. streak = 12 # Hardcoded for demo purposes
  24. matriculas = db.query(Matricula).filter(Matricula.id_alumno == student_id).all()
  25. curso_ids = [m.id_curso for m in matriculas]
  26. active_courses = len(curso_ids)
  27. pending_tasks = 0
  28. if curso_ids:
  29. actividades = db.query(Actividad).filter(Actividad.id_curso.in_(curso_ids)).all()
  30. now = datetime.now(timezone.utc)
  31. for a in actividades:
  32. e = db.query(Entrega).filter(Entrega.id_actividad == a.id_actividad, Entrega.id_alumno == student_id).first()
  33. if not e and (not a.fecha_entrega or a.fecha_entrega.replace(tzinfo=timezone.utc) >= now):
  34. pending_tasks += 1
  35. return {
  36. "id": alumno.id_alumno,
  37. "name": f"{alumno.usuario.nombre} {alumno.usuario.apellidos or ''}".strip(),
  38. "email": alumno.usuario.email,
  39. "level_name": alumno.nivel,
  40. "xp": xp,
  41. "level": level,
  42. "streak": streak,
  43. "avg_progress": round(avg_progress, 1),
  44. "gpa": round(avg_progress * 0.1, 1), # Simulated GPA 0-10
  45. "active_courses": active_courses,
  46. "pending_tasks": pending_tasks
  47. }
  48. @router.get("/courses")
  49. def get_courses(student: Alumno = Depends(get_current_student), db: Session = Depends(get_db)):
  50. student_id = student.id_alumno
  51. matriculas = db.query(Matricula).options(
  52. joinedload(Matricula.curso).joinedload(Curso.profesor).joinedload(Profesor.usuario)
  53. ).filter(Matricula.id_alumno == student_id).all()
  54. courses = []
  55. for m in matriculas:
  56. c = m.curso
  57. prof_name = f"{c.profesor.usuario.nombre} {c.profesor.usuario.apellidos or ''}".strip()
  58. progreso = db.query(Progreso).filter(Progreso.id_alumno == student_id, Progreso.id_curso == c.id_curso).first()
  59. prog_pct = float(progreso.porcentaje) if progreso else 0
  60. courses.append({
  61. "id": c.id_curso,
  62. "name": c.nombre,
  63. "description": c.descripcion,
  64. "teacher": prof_name,
  65. "progress": prog_pct
  66. })
  67. return courses
  68. @router.get("/tasks")
  69. def get_tasks(student: Alumno = Depends(get_current_student), db: Session = Depends(get_db)):
  70. student_id = student.id_alumno
  71. matriculas = db.query(Matricula).filter(Matricula.id_alumno == student_id).all()
  72. curso_ids = [m.id_curso for m in matriculas]
  73. if not curso_ids:
  74. return []
  75. actividades = db.query(Actividad).options(joinedload(Actividad.curso)).filter(Actividad.id_curso.in_(curso_ids)).all()
  76. tasks = []
  77. now = datetime.now(timezone.utc)
  78. for a in actividades:
  79. entrega = db.query(Entrega).options(joinedload(Entrega.calificacion)).filter(
  80. Entrega.id_actividad == a.id_actividad,
  81. Entrega.id_alumno == student_id
  82. ).first()
  83. status = "pending"
  84. score = None
  85. if entrega:
  86. status = "submitted"
  87. if entrega.calificacion:
  88. status = "graded"
  89. score = float(entrega.calificacion.nota)
  90. elif a.fecha_entrega and a.fecha_entrega.replace(tzinfo=timezone.utc) < now:
  91. status = "overdue"
  92. tasks.append({
  93. "id": a.id_actividad,
  94. "title": a.titulo,
  95. "course": a.curso.nombre,
  96. "due_date": a.fecha_entrega.isoformat() if a.fecha_entrega else None,
  97. "max_score": float(a.puntuacion_maxima),
  98. "status": status,
  99. "score": score
  100. })
  101. return tasks
  102. @router.post("/tasks/{task_id}/submit")
  103. def submit_task(task_id: int, payload: TaskSubmit, student: Alumno = Depends(get_current_student), db: Session = Depends(get_db)):
  104. student_id = student.id_alumno
  105. actividad = db.query(Actividad).filter(Actividad.id_actividad == task_id).first()
  106. if not actividad:
  107. raise HTTPException(status_code=404, detail="Tarea no encontrada")
  108. entrega = db.query(Entrega).filter(Entrega.id_actividad == task_id, Entrega.id_alumno == student_id).first()
  109. if entrega:
  110. entrega.contenido = payload.contenido
  111. entrega.estado = "entregado"
  112. entrega.fecha_entrega = datetime.now(timezone.utc)
  113. else:
  114. entrega = Entrega(
  115. id_actividad=task_id,
  116. id_alumno=student_id,
  117. contenido=payload.contenido,
  118. estado="entregado"
  119. )
  120. db.add(entrega)
  121. db.commit()
  122. return {"message": "Tarea entregada correctamente"}
  123. @router.get("/grades")
  124. def get_grades(student: Alumno = Depends(get_current_student), db: Session = Depends(get_db)):
  125. student_id = student.id_alumno
  126. entregas = db.query(Entrega).options(
  127. joinedload(Entrega.actividad).joinedload(Actividad.curso),
  128. joinedload(Entrega.calificacion)
  129. ).filter(Entrega.id_alumno == student_id).all()
  130. grades = []
  131. for e in entregas:
  132. if not e.calificacion:
  133. continue
  134. a = e.actividad
  135. grades.append({
  136. "task_id": a.id_actividad,
  137. "task_title": a.titulo,
  138. "course": a.curso.nombre,
  139. "score": float(e.calificacion.nota),
  140. "max_score": float(a.puntuacion_maxima),
  141. "feedback": e.calificacion.observaciones,
  142. "date": e.calificacion.fecha_calificacion.isoformat()
  143. })
  144. return grades
  145. @router.get("/schedule")
  146. def get_schedule(student: Alumno = Depends(get_current_student), db: Session = Depends(get_db)):
  147. student_id = student.id_alumno
  148. matriculas = db.query(Matricula).filter(Matricula.id_alumno == student_id).all()
  149. curso_ids = [m.id_curso for m in matriculas]
  150. if not curso_ids: return []
  151. horarios = db.query(Horario).options(joinedload(Horario.curso)).filter(Horario.id_curso.in_(curso_ids)).all()
  152. res = []
  153. for h in horarios:
  154. res.append({
  155. "day": h.dia_semana,
  156. "start": h.hora_inicio,
  157. "end": h.hora_fin,
  158. "course": h.curso.nombre,
  159. "room": h.aula
  160. })
  161. return res
  162. @router.get("/projects")
  163. def get_projects(student: Alumno = Depends(get_current_student), db: Session = Depends(get_db)):
  164. student_id = student.id_alumno
  165. pes = db.query(ProyectoEstudiante).options(joinedload(ProyectoEstudiante.proyecto).joinedload(Proyecto.curso)).filter(ProyectoEstudiante.id_alumno == student_id).all()
  166. res = []
  167. for pe in pes:
  168. p = pe.proyecto
  169. # Simulated teammates for UI purposes
  170. teammates = [{"name": "A", "initial": "A"}, {"name": "B", "initial": "B"}, {"name": "C", "initial": "C"}]
  171. res.append({
  172. "id": p.id_proyecto,
  173. "title": p.titulo,
  174. "description": p.descripcion,
  175. "course": p.curso.nombre,
  176. "due_date": p.fecha_entrega.isoformat() if p.fecha_entrega else None,
  177. "status": p.estado,
  178. "progress": float(p.porcentaje_completado),
  179. "team": teammates
  180. })
  181. return res
  182. @router.post("/projects/{project_id}/progress")
  183. def update_project_progress(project_id: int, payload: ProjectProgress, student: Alumno = Depends(get_current_student), db: Session = Depends(get_db)):
  184. student_id = student.id_alumno
  185. pe = db.query(ProyectoEstudiante).filter(ProyectoEstudiante.id_proyecto == project_id, ProyectoEstudiante.id_alumno == student_id).first()
  186. if not pe:
  187. raise HTTPException(status_code=403, detail="No eres parte de este proyecto")
  188. p = db.query(Proyecto).filter(Proyecto.id_proyecto == project_id).first()
  189. if p:
  190. p.porcentaje_completado = max(0, min(100, payload.progress))
  191. if p.porcentaje_completado == 100:
  192. p.estado = "entregado"
  193. else:
  194. p.estado = "en curso"
  195. db.commit()
  196. return {"message": "Progreso actualizado"}
  197. @router.get("/exams")
  198. def get_exams(student: Alumno = Depends(get_current_student), db: Session = Depends(get_db)):
  199. student_id = student.id_alumno
  200. matriculas = db.query(Matricula).filter(Matricula.id_alumno == student_id).all()
  201. curso_ids = [m.id_curso for m in matriculas]
  202. if not curso_ids: return []
  203. examenes = db.query(Examen).options(joinedload(Examen.curso)).filter(Examen.id_curso.in_(curso_ids)).all()
  204. res = []
  205. for e in examenes:
  206. res.append({
  207. "id": e.id_examen,
  208. "title": e.titulo,
  209. "syllabus": e.temario,
  210. "course": e.curso.nombre,
  211. "date": e.fecha.isoformat(),
  212. "duration": e.duracion_minutos,
  213. "mode": e.modalidad
  214. })
  215. return res