| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619 |
- #!/usr/bin/env python3
- """
- Seed de la base de datos de la plataforma de usuarios.
- Crea: roles, usuarios, credenciales, profesores, alumnos,
- cursos, matrículas, actividades, entregas, calificaciones y progreso.
- Uso (desde user_platform/):
- python seed.py
- La contraseña por defecto de todos los usuarios es: Password123!
- """
- import os
- import random
- from datetime import datetime, timedelta, timezone
- from decimal import Decimal
- # ── DB URL (lee el .env raíz del proyecto) ──────────────────────────────────────
- def _read_env(path: str) -> dict[str, str]:
- result: dict[str, str] = {}
- if os.path.exists(path):
- with open(path) as f:
- for line in f:
- line = line.strip()
- if line and not line.startswith("#") and "=" in line:
- key, _, val = line.partition("=")
- result[key.strip()] = val.strip()
- return result
- # Busca el .env raíz subiendo desde user_platform/
- _here = os.path.dirname(os.path.abspath(__file__))
- env = _read_env(os.path.join(_here, "..", ".env"))
- pg_user = env.get("PLATFORM_DB_USER", "user-database-user")
- pg_pass = env.get("PLATFORM_DB_PASSWORD", "")
- pg_db = env.get("PLATFORM_DB_NAME", "user-database")
- pg_port = env.get("PLATFORM_DB_PORT", "55557")
- DB_URL = os.environ.get("PLATFORM_DB_URL", f"postgresql://{pg_user}:{pg_pass}@localhost:{pg_port}/{pg_db}")
- # ── ORM ─────────────────────────────────────────────────────────────────────────
- from sqlalchemy import create_engine, Column, Integer, String, Boolean, Text, DateTime, Numeric, ForeignKey, UniqueConstraint
- from sqlalchemy.orm import sessionmaker, DeclarativeBase, relationship
- from passlib.context import CryptContext
- pwd_ctx = CryptContext(schemes=["bcrypt"], deprecated="auto")
- engine = create_engine(DB_URL, pool_pre_ping=True)
- Session = sessionmaker(bind=engine)
- class Base(DeclarativeBase):
- pass
- class Rol(Base):
- __tablename__ = "roles"
- id_rol = Column(Integer, primary_key=True)
- nombre = Column(String(50), unique=True, nullable=False)
- usuarios = relationship("Usuario", back_populates="rol")
- class Usuario(Base):
- __tablename__ = "usuarios"
- id_usuario = Column(Integer, primary_key=True)
- nombre = Column(String(100), nullable=False)
- apellidos = Column(String(150))
- email = Column(String(150), unique=True, nullable=False)
- fecha_creacion = Column(DateTime, default=lambda: datetime.now(timezone.utc))
- activo = Column(Boolean, default=True)
- id_rol = Column(Integer, ForeignKey("roles.id_rol"), nullable=False)
- rol = relationship("Rol", back_populates="usuarios")
- credencial = relationship("Credencial", back_populates="usuario", uselist=False)
- profesor = relationship("Profesor", back_populates="usuario", uselist=False)
- alumno = relationship("Alumno", back_populates="usuario", uselist=False)
- class Credencial(Base):
- __tablename__ = "credenciales"
- id_credencial = Column(Integer, primary_key=True)
- id_usuario = Column(Integer, ForeignKey("usuarios.id_usuario", ondelete="CASCADE"), unique=True)
- password_hash = Column(Text, nullable=False)
- ultimo_login = Column(DateTime)
- usuario = relationship("Usuario", back_populates="credencial")
- class Profesor(Base):
- __tablename__ = "profesores"
- id_profesor = Column(Integer, primary_key=True)
- id_usuario = Column(Integer, ForeignKey("usuarios.id_usuario", ondelete="CASCADE"), unique=True)
- especialidad = Column(String(100))
- usuario = relationship("Usuario", back_populates="profesor")
- cursos = relationship("Curso", back_populates="profesor")
- class Alumno(Base):
- __tablename__ = "alumnos"
- id_alumno = Column(Integer, primary_key=True)
- id_usuario = Column(Integer, ForeignKey("usuarios.id_usuario", ondelete="CASCADE"), unique=True)
- nivel = Column(String(50))
- usuario = relationship("Usuario", back_populates="alumno")
- matriculas = relationship("Matricula", back_populates="alumno")
- entregas = relationship("Entrega", back_populates="alumno")
- progreso = relationship("Progreso", back_populates="alumno")
- class Curso(Base):
- __tablename__ = "cursos"
- id_curso = Column(Integer, primary_key=True)
- nombre = Column(String(150), nullable=False)
- descripcion = Column(Text)
- fecha_creacion = Column(DateTime, default=lambda: datetime.now(timezone.utc))
- id_profesor = Column(Integer, ForeignKey("profesores.id_profesor"), nullable=False)
- profesor = relationship("Profesor", back_populates="cursos")
- matriculas = relationship("Matricula", back_populates="curso")
- actividades = relationship("Actividad", back_populates="curso")
- progreso = relationship("Progreso", back_populates="curso")
- class Matricula(Base):
- __tablename__ = "matriculas"
- __table_args__ = (UniqueConstraint("id_alumno", "id_curso"),)
- id_matricula = Column(Integer, primary_key=True)
- id_alumno = Column(Integer, ForeignKey("alumnos.id_alumno", ondelete="CASCADE"))
- id_curso = Column(Integer, ForeignKey("cursos.id_curso", ondelete="CASCADE"))
- fecha_matricula = Column(DateTime, default=lambda: datetime.now(timezone.utc))
- alumno = relationship("Alumno", back_populates="matriculas")
- curso = relationship("Curso", back_populates="matriculas")
- class Actividad(Base):
- __tablename__ = "actividades"
- id_actividad = Column(Integer, primary_key=True)
- titulo = Column(String(200), nullable=False)
- descripcion = Column(Text)
- fecha_publicacion = Column(DateTime, default=lambda: datetime.now(timezone.utc))
- fecha_entrega = Column(DateTime)
- puntuacion_maxima = Column(Numeric(5, 2))
- id_curso = Column(Integer, ForeignKey("cursos.id_curso", ondelete="CASCADE"))
- curso = relationship("Curso", back_populates="actividades")
- entregas = relationship("Entrega", back_populates="actividad")
- class Entrega(Base):
- __tablename__ = "entregas"
- id_entrega = Column(Integer, primary_key=True)
- id_actividad = Column(Integer, ForeignKey("actividades.id_actividad", ondelete="CASCADE"))
- id_alumno = Column(Integer, ForeignKey("alumnos.id_alumno", ondelete="CASCADE"))
- fecha_entrega = Column(DateTime, default=lambda: datetime.now(timezone.utc))
- contenido = Column(Text)
- estado = Column(String(50), default="calificado")
- actividad = relationship("Actividad", back_populates="entregas")
- alumno = relationship("Alumno", back_populates="entregas")
- calificacion = relationship("Calificacion", back_populates="entrega", uselist=False)
- class Calificacion(Base):
- __tablename__ = "calificaciones"
- id_calificacion = Column(Integer, primary_key=True)
- id_entrega = Column(Integer, ForeignKey("entregas.id_entrega", ondelete="CASCADE"), unique=True)
- nota = Column(Numeric(5, 2))
- observaciones = Column(Text)
- fecha_calificacion = Column(DateTime, default=lambda: datetime.now(timezone.utc))
- entrega = relationship("Entrega", back_populates="calificacion")
- class Progreso(Base):
- __tablename__ = "progreso"
- __table_args__ = (UniqueConstraint("id_alumno", "id_curso"),)
- id_progreso = Column(Integer, primary_key=True)
- id_alumno = Column(Integer, ForeignKey("alumnos.id_alumno", ondelete="CASCADE"))
- id_curso = Column(Integer, ForeignKey("cursos.id_curso", ondelete="CASCADE"))
- porcentaje = Column(Numeric(5, 2), default=0)
- alumno = relationship("Alumno", back_populates="progreso")
- curso = relationship("Curso", back_populates="progreso")
- class Horario(Base):
- __tablename__ = "horarios"
- id_horario = Column(Integer, primary_key=True)
- id_curso = Column(Integer, ForeignKey("cursos.id_curso", ondelete="CASCADE"))
- dia_semana = Column(Integer, nullable=False)
- hora_inicio = Column(String(5), nullable=False)
- hora_fin = Column(String(5), nullable=False)
- aula = Column(String(50))
- class Proyecto(Base):
- __tablename__ = "proyectos"
- id_proyecto = Column(Integer, primary_key=True)
- id_curso = Column(Integer, ForeignKey("cursos.id_curso", ondelete="CASCADE"))
- titulo = Column(String(200), nullable=False)
- descripcion = Column(Text)
- fecha_entrega = Column(DateTime)
- estado = Column(String(50), default="en curso")
- porcentaje_completado = Column(Numeric(5, 2), default=0)
- class ProyectoEstudiante(Base):
- __tablename__ = "proyectos_estudiantes"
- id_proyecto = Column(Integer, ForeignKey("proyectos.id_proyecto", ondelete="CASCADE"), primary_key=True)
- id_alumno = Column(Integer, ForeignKey("alumnos.id_alumno", ondelete="CASCADE"), primary_key=True)
- class Examen(Base):
- __tablename__ = "examenes"
- id_examen = Column(Integer, primary_key=True)
- id_curso = Column(Integer, ForeignKey("cursos.id_curso", ondelete="CASCADE"))
- titulo = Column(String(200), nullable=False)
- temario = Column(Text)
- fecha = Column(DateTime, nullable=False)
- duracion_minutos = Column(Integer, nullable=False)
- modalidad = Column(String(50))
- # ── Data ─────────────────────────────────────────────────────────────────────────
- DEFAULT_PASSWORD = pwd_ctx.hash("Password123!")
- ROLES = ["admin", "profesor", "estudiante"]
- ADMINS = [
- {"nombre": "Emmanuel", "apellidos": "Bizimana", "email": "admin@opendata.edu"},
- ]
- TEACHERS = [
- {"nombre": "Sarah", "apellidos": "Kamau", "email": "s.kamau@opendata.edu", "especialidad": "Mathematics"},
- {"nombre": "Patrick", "apellidos": "Habimana", "email": "p.habimana@opendata.edu", "especialidad": "Science"},
- {"nombre": "Aline", "apellidos": "Nkurunziza", "email": "a.nkurunziza@opendata.edu","especialidad": "English"},
- {"nombre": "Moses", "apellidos": "Kato", "email": "m.kato@opendata.edu", "especialidad": "ICT"},
- {"nombre": "Faith", "apellidos": "Okello", "email": "f.okello@opendata.edu", "especialidad": "History & Civics"},
- {"nombre": "Neema", "apellidos": "Mushi", "email": "n.mushi@opendata.edu", "especialidad": "Geography"},
- {"nombre": "Samuel", "apellidos": "Otieno", "email": "s.otieno@opendata.edu", "especialidad": "Biology & Chemistry"},
- ]
- STUDENTS = [
- {"nombre": "Grace", "apellidos": "Uwimana", "email": "g.uwimana@students.edu", "nivel": "Secondary 5"},
- {"nombre": "John", "apellidos": "Mwangi", "email": "j.mwangi@students.edu", "nivel": "Secondary 3"},
- {"nombre": "Claudine", "apellidos": "Bizimana", "email": "c.bizimana@students.edu", "nivel": "Secondary 2"},
- {"nombre": "Peter", "apellidos": "Habimana", "email": "p.habimana2@students.edu", "nivel": "Secondary 4"},
- {"nombre": "Amina", "apellidos": "Kamau", "email": "a.kamau@students.edu", "nivel": "Secondary 1"},
- {"nombre": "David", "apellidos": "Okello", "email": "d.okello@students.edu", "nivel": "Secondary 6"},
- {"nombre": "Fatuma", "apellidos": "Otieno", "email": "f.otieno@students.edu", "nivel": "Secondary 3"},
- {"nombre": "Joseph", "apellidos": "Kato", "email": "j.kato@students.edu", "nivel": "Secondary 2"},
- {"nombre": "Esperance","apellidos": "Nkurunziza", "email": "e.nkurunziza@students.edu","nivel": "Secondary 5"},
- {"nombre": "Ali", "apellidos": "Mushi", "email": "a.mushi@students.edu", "nivel": "Secondary 4"},
- {"nombre": "Zawadi", "apellidos": "Habimana", "email": "z.habimana@students.edu", "nivel": "Secondary 1"},
- {"nombre": "Rehema", "apellidos": "Uwimana", "email": "r.uwimana@students.edu", "nivel": "Secondary 3"},
- {"nombre": "Baraka", "apellidos": "Mwangi", "email": "b.mwangi@students.edu", "nivel": "Secondary 6"},
- {"nombre": "Solange", "apellidos": "Kato", "email": "s.kato@students.edu", "nivel": "Secondary 2"},
- {"nombre": "Omar", "apellidos": "Otieno", "email": "o.otieno@students.edu", "nivel": "Secondary 4"},
- {"nombre": "Immaculée","apellidos": "Bizimana", "email": "i.bizimana@students.edu", "nivel": "Secondary 5"},
- {"nombre": "Mussa", "apellidos": "Kamau", "email": "m.kamau@students.edu", "nivel": "Secondary 1"},
- {"nombre": "Vestine", "apellidos": "Okello", "email": "v.okello@students.edu", "nivel": "Secondary 3"},
- {"nombre": "Patrick", "apellidos": "Mushi", "email": "p.mushi@students.edu", "nivel": "Secondary 6"},
- {"nombre": "Dativa", "apellidos": "Nkurunziza", "email": "d.nkurunziza@students.edu","nivel": "Secondary 2"},
- ]
- COURSES = [
- {
- "nombre": "Advanced Mathematics — Secondary 5 & 6",
- "descripcion": "Álgebra lineal, cálculo diferencial, estadística aplicada y resolución de problemas complejos.",
- "teacher_email": "s.kamau@opendata.edu",
- },
- {
- "nombre": "English Language & Literature",
- "descripcion": "Comprensión lectora, escritura académica, análisis literario y comunicación oral.",
- "teacher_email": "a.nkurunziza@opendata.edu",
- },
- {
- "nombre": "Integrated Science — Secondary 1 & 2",
- "descripcion": "Fundamentos de biología, química y física con enfoque experimental.",
- "teacher_email": "p.habimana@opendata.edu",
- },
- {
- "nombre": "ICT & Digital Literacy",
- "descripcion": "Ofimática, programación básica, seguridad digital y gestión de datos.",
- "teacher_email": "m.kato@opendata.edu",
- },
- {
- "nombre": "History, Civics & Social Studies",
- "descripcion": "Historia de África del Este, gobierno, derechos humanos y ciudadanía activa.",
- "teacher_email": "f.okello@opendata.edu",
- },
- {
- "nombre": "Geography & Environment",
- "descripcion": "Geografía física y humana, cambio climático y gestión de recursos naturales.",
- "teacher_email": "n.mushi@opendata.edu",
- },
- {
- "nombre": "Biology & Chemistry — Secondary 4 & 5",
- "descripcion": "Biología celular, genética, reacciones químicas orgánicas e inorgánicas.",
- "teacher_email": "s.otieno@opendata.edu",
- },
- ]
- ACTIVITY_TEMPLATES = [
- {
- "titulo": "Tarea 1 — Evaluación inicial",
- "descripcion": "Evaluación de conocimientos previos y diagnóstico del nivel del estudiante.",
- "puntuacion_maxima": Decimal("20.00"),
- "dias_publicacion": -60,
- "dias_entrega": -55,
- },
- {
- "titulo": "Cuestionario — Trimestre 1",
- "descripcion": "Cuestionario de seguimiento sobre los contenidos del primer trimestre.",
- "puntuacion_maxima": Decimal("30.00"),
- "dias_publicacion": -40,
- "dias_entrega": -35,
- },
- {
- "titulo": "Proyecto colaborativo",
- "descripcion": "Trabajo en grupo sobre un caso de estudio real relacionado con la asignatura.",
- "puntuacion_maxima": Decimal("50.00"),
- "dias_publicacion": -25,
- "dias_entrega": -15,
- },
- {
- "titulo": "Examen Parcial — Trimestre 2",
- "descripcion": "Evaluación escrita de los contenidos del segundo trimestre.",
- "puntuacion_maxima": Decimal("40.00"),
- "dias_publicacion": -10,
- "dias_entrega": -5,
- },
- {
- "titulo": "Entrega final — Portfolio",
- "descripcion": "Portfolio de trabajos realizados durante el curso con reflexión personal.",
- "puntuacion_maxima": Decimal("60.00"),
- "dias_publicacion": -3,
- "dias_entrega": 10,
- },
- ]
- SUBMISSION_TEXTS = [
- "He revisado el material y completado todos los ejercicios propuestos.",
- "Adjunto mi trabajo con las correcciones indicadas en clase.",
- "He realizado la tarea individualmente siguiendo las instrucciones del profesor.",
- "Incluyo referencias adicionales que encontré durante la investigación.",
- "Trabajo completado. Tuve dificultades con la parte práctica pero lo resolví.",
- ]
- # ── Seed logic ───────────────────────────────────────────────────────────────────
- def seed():
- print("\n=== Seed: Plataforma de Usuarios ===\n")
- Base.metadata.create_all(bind=engine)
- print("✓ Esquema verificado")
- db = Session()
- random.seed(42)
- try:
- # ── Roles
- rol_map: dict[str, Rol] = {}
- for nombre in ROLES:
- r = db.query(Rol).filter(Rol.nombre == nombre).first()
- if not r:
- r = Rol(nombre=nombre)
- db.add(r)
- db.flush()
- rol_map[nombre] = r
- db.commit()
- print(f"✓ Roles: {list(rol_map.keys())}")
- # ── Admins
- for data in ADMINS:
- if db.query(Usuario).filter(Usuario.email == data["email"]).first():
- continue
- u = Usuario(**data, id_rol=rol_map["admin"].id_rol)
- db.add(u)
- db.flush()
- db.add(Credencial(id_usuario=u.id_usuario, password_hash=DEFAULT_PASSWORD))
- db.commit()
- print(f"✓ Admins: {len(ADMINS)}")
- # ── Teachers
- profesor_map: dict[str, Profesor] = {}
- for data in TEACHERS:
- u = db.query(Usuario).filter(Usuario.email == data["email"]).first()
- if not u:
- u = Usuario(
- nombre=data["nombre"], apellidos=data["apellidos"],
- email=data["email"], id_rol=rol_map["profesor"].id_rol,
- )
- db.add(u)
- db.flush()
- db.add(Credencial(id_usuario=u.id_usuario, password_hash=DEFAULT_PASSWORD))
- p = db.query(Profesor).filter(Profesor.id_usuario == u.id_usuario).first()
- if not p:
- p = Profesor(id_usuario=u.id_usuario, especialidad=data["especialidad"])
- db.add(p)
- db.flush()
- profesor_map[data["email"]] = p
- db.commit()
- print(f"✓ Profesores: {len(TEACHERS)}")
- # ── Students
- alumno_list: list[Alumno] = []
- for data in STUDENTS:
- u = db.query(Usuario).filter(Usuario.email == data["email"]).first()
- if not u:
- u = Usuario(
- nombre=data["nombre"], apellidos=data["apellidos"],
- email=data["email"], id_rol=rol_map["estudiante"].id_rol,
- )
- db.add(u)
- db.flush()
- db.add(Credencial(id_usuario=u.id_usuario, password_hash=DEFAULT_PASSWORD))
- a = db.query(Alumno).filter(Alumno.id_usuario == u.id_usuario).first()
- if not a:
- a = Alumno(id_usuario=u.id_usuario, nivel=data["nivel"])
- db.add(a)
- db.flush()
- alumno_list.append(a)
- db.commit()
- print(f"✓ Alumnos: {len(STUDENTS)}")
- # ── Courses
- curso_list: list[Curso] = []
- for data in COURSES:
- c = db.query(Curso).filter(Curso.nombre == data["nombre"]).first()
- if not c:
- profesor = profesor_map[data["teacher_email"]]
- c = Curso(
- nombre=data["nombre"],
- descripcion=data["descripcion"],
- id_profesor=profesor.id_profesor,
- fecha_creacion=datetime.now(timezone.utc) - timedelta(days=90),
- )
- db.add(c)
- db.flush()
- curso_list.append(c)
- db.commit()
- print(f"✓ Cursos: {len(COURSES)}")
- # ── Horarios, Proyectos, Examenes
- horario_count = 0
- proyecto_count = 0
- examen_count = 0
- proyecto_list: list[Proyecto] = []
- for curso in curso_list:
- # Horarios
- dias = random.sample([1, 2, 3, 4, 5], 2)
- for dia in dias:
- h_inicio = random.choice(["08:00", "10:00", "12:00", "14:00"])
- h_fin = f"{int(h_inicio[:2])+2:02d}:00"
- aula = random.choice(["Sala Digital A", "Sala Digital B", "Lab 1", "Lab 2", "Aula 101"])
- h = Horario(id_curso=curso.id_curso, dia_semana=dia, hora_inicio=h_inicio, hora_fin=h_fin, aula=aula)
- db.add(h)
- horario_count += 1
-
- # Proyectos
- now = datetime.now(timezone.utc)
- p1 = Proyecto(id_curso=curso.id_curso, titulo=f"Proyecto Final: {curso.nombre}", descripcion="Aplicar los conceptos del curso en un caso práctico.", fecha_entrega=now + timedelta(days=30), estado="en curso", porcentaje_completado=Decimal(str(random.randint(10, 80))))
- p2 = Proyecto(id_curso=curso.id_curso, titulo=f"Investigación: {curso.nombre}", descripcion="Investigación bibliográfica y presentación.", fecha_entrega=now - timedelta(days=10), estado="entregado", porcentaje_completado=Decimal("100.00"))
- db.add_all([p1, p2])
- db.flush()
- proyecto_list.extend([p1, p2])
- proyecto_count += 2
- # Examenes
- e1 = Examen(id_curso=curso.id_curso, titulo=f"Examen Parcial", temario="Unidades 1 a 3", fecha=now - timedelta(days=5), duracion_minutos=90, modalidad="Presencial")
- e2 = Examen(id_curso=curso.id_curso, titulo=f"Examen Final", temario="Todo el temario", fecha=now + timedelta(days=25), duracion_minutos=120, modalidad="Online")
- db.add_all([e1, e2])
- examen_count += 2
-
- db.commit()
- print(f"✓ Horarios: {horario_count}, Proyectos: {proyecto_count}, Exámenes: {examen_count}")
- # ── Activities (5 per course)
- actividad_map: dict[int, list[Actividad]] = {}
- for curso in curso_list:
- actividad_map[curso.id_curso] = []
- for tmpl in ACTIVITY_TEMPLATES:
- titulo_full = f"{curso.nombre.split('—')[0].strip()} — {tmpl['titulo']}"
- a = db.query(Actividad).filter(Actividad.titulo == titulo_full).first()
- if not a:
- now = datetime.now(timezone.utc)
- a = Actividad(
- titulo=titulo_full,
- descripcion=tmpl["descripcion"],
- puntuacion_maxima=tmpl["puntuacion_maxima"],
- id_curso=curso.id_curso,
- fecha_publicacion=now + timedelta(days=tmpl["dias_publicacion"]),
- fecha_entrega=now + timedelta(days=tmpl["dias_entrega"]),
- )
- db.add(a)
- db.flush()
- actividad_map[curso.id_curso].append(a)
- db.commit()
- total_acts = sum(len(v) for v in actividad_map.values())
- print(f"✓ Actividades: {total_acts}")
- # ── Enrollments: cada alumno en 3-4 cursos aleatorios
- matricula_count = 0
- entrega_count = 0
- for alumno in alumno_list:
- n_cursos = random.randint(3, 5)
- cursos_alumno = random.sample(curso_list, min(n_cursos, len(curso_list)))
- for curso in cursos_alumno:
- # Matrícula
- m = db.query(Matricula).filter(
- Matricula.id_alumno == alumno.id_alumno,
- Matricula.id_curso == curso.id_curso,
- ).first()
- if not m:
- m = Matricula(
- id_alumno=alumno.id_alumno,
- id_curso=curso.id_curso,
- fecha_matricula=datetime.now(timezone.utc) - timedelta(days=85),
- )
- db.add(m)
- db.flush()
- matricula_count += 1
- # Entregas y calificaciones (solo actividades ya cerradas)
- actividades_cerradas = [
- a for a in actividad_map[curso.id_curso]
- if a.fecha_entrega and a.fecha_entrega.replace(tzinfo=timezone.utc) < datetime.now(timezone.utc)
- ]
- notas: list[float] = []
- for actividad in actividades_cerradas:
- e = db.query(Entrega).filter(
- Entrega.id_alumno == alumno.id_alumno,
- Entrega.id_actividad == actividad.id_actividad,
- ).first()
- if not e:
- entrega_fecha = actividad.fecha_entrega - timedelta(hours=random.randint(1, 48))
- e = Entrega(
- id_actividad=actividad.id_actividad,
- id_alumno=alumno.id_alumno,
- fecha_entrega=entrega_fecha,
- contenido=random.choice(SUBMISSION_TEXTS),
- estado="calificado",
- )
- db.add(e)
- db.flush()
- entrega_count += 1
- # Nota: distribución realista centrada en 60-85
- max_nota = float(actividad.puntuacion_maxima)
- base_pct = random.gauss(0.72, 0.15)
- base_pct = max(0.3, min(1.0, base_pct))
- nota = round(base_pct * max_nota, 2)
- notas.append(nota / max_nota * 100)
- obs_list = [
- "Buen trabajo, sigue así.",
- "Necesita mejorar la argumentación.",
- "Excelente presentación y contenido.",
- "Trabajo correcto pero le falta profundidad.",
- "Muy buen desempeño.",
- ]
- db.add(Calificacion(
- id_entrega=e.id_entrega,
- nota=Decimal(str(nota)),
- observaciones=random.choice(obs_list),
- fecha_calificacion=entrega_fecha + timedelta(days=random.randint(1, 5)),
- ))
- # Progreso del alumno en este curso
- if actividades_cerradas:
- entregadas = len(actividades_cerradas)
- total_act = len(actividad_map[curso.id_curso])
- pct_entrega = (entregadas / total_act) * 100
- pct_nota = (sum(notas) / len(notas)) if notas else 0
- porcentaje = round((pct_entrega * 0.4 + pct_nota * 0.6), 2)
- prog = db.query(Progreso).filter(
- Progreso.id_alumno == alumno.id_alumno,
- Progreso.id_curso == curso.id_curso,
- ).first()
- if not prog:
- db.add(Progreso(
- id_alumno=alumno.id_alumno,
- id_curso=curso.id_curso,
- porcentaje=Decimal(str(porcentaje)),
- ))
- # Asignar proyectos del curso al alumno
- proyectos_curso = [p for p in proyecto_list if p.id_curso == curso.id_curso]
- for p in proyectos_curso:
- pe = db.query(ProyectoEstudiante).filter(ProyectoEstudiante.id_proyecto == p.id_proyecto, ProyectoEstudiante.id_alumno == alumno.id_alumno).first()
- if not pe:
- db.add(ProyectoEstudiante(id_proyecto=p.id_proyecto, id_alumno=alumno.id_alumno))
- db.commit()
- print(f"✓ Matrículas: {matricula_count}")
- print(f"✓ Entregas: {entrega_count}")
- print(f"✓ Progreso calculado")
- # ── Resumen final
- print("\n─── Resumen ────────────────────────────────")
- print(f" Usuarios totales: {db.query(Usuario).count()}")
- print(f" Profesores: {db.query(Profesor).count()}")
- print(f" Alumnos: {db.query(Alumno).count()}")
- print(f" Cursos: {db.query(Curso).count()}")
- print(f" Actividades: {db.query(Actividad).count()}")
- print(f" Entregas: {db.query(Entrega).count()}")
- print(f" Calificaciones: {db.query(Calificacion).count()}")
- print(f" Progreso registros:{db.query(Progreso).count()}")
- print(f" Horarios: {db.query(Horario).count()}")
- print(f" Proyectos: {db.query(Proyecto).count()}")
- print(f" Exámenes: {db.query(Examen).count()}")
- print("────────────────────────────────────────────")
- print("\n Contraseña de todos los usuarios: Password123!")
- print(" Ya puedes hacer login desde la API.\n")
- except Exception as e:
- db.rollback()
- print(f"\n✗ Error durante el seed: {e}")
- raise
- finally:
- db.close()
- if __name__ == "__main__":
- seed()
|