177 lines
6.2 KiB
Python
177 lines
6.2 KiB
Python
from datetime import datetime
|
|
from app import db
|
|
|
|
class MilestoneType(db.Model):
|
|
"""
|
|
Catálogo dinámico y administrable de tipificaciones de hitos y evaluaciones académicas.
|
|
(Parciales, Trabajos Prácticos, Recuperatorios, Finales, etc.)
|
|
"""
|
|
__tablename__ = 'milestone_types'
|
|
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
name = db.Column(db.String(100), nullable=False) # Ej: "Parcial 1", "Trabajo Práctico 1"
|
|
code = db.Column(db.String(50), unique=True, nullable=False, index=True) # Ej: "parcial_1", "tp_1"
|
|
color = db.Column(db.String(30), default='#0d6efd') # Hex color para badges y calendario
|
|
description = db.Column(db.String(255), nullable=True)
|
|
sort_order = db.Column(db.Integer, default=0) # Orden de aparición en selects
|
|
is_active = db.Column(db.Boolean, default=True, nullable=False) # Activo / Inactivo
|
|
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
|
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
|
|
|
# Relación uno-a-muchos con AcademicMilestone
|
|
milestones = db.relationship('AcademicMilestone', backref='type_obj', lazy='dynamic', cascade='all, delete-orphan')
|
|
|
|
def to_dict(self):
|
|
return {
|
|
'id': self.id,
|
|
'name': self.name,
|
|
'code': self.code,
|
|
'color': self.color or '#0d6efd',
|
|
'description': self.description or '',
|
|
'sort_order': self.sort_order or 0,
|
|
'is_active': self.is_active,
|
|
'created_at': self.created_at.isoformat() if self.created_at else None
|
|
}
|
|
|
|
def __repr__(self):
|
|
return f'<MilestoneType {self.code}: {self.name}>'
|
|
|
|
|
|
class AcademicMilestone(db.Model):
|
|
"""
|
|
Hito o evento académico programado para una materia/comisión (Examen, TP, Entrega).
|
|
"""
|
|
__tablename__ = 'academic_milestones'
|
|
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
title = db.Column(db.String(150), nullable=True)
|
|
materia_codigo = db.Column(db.String(50), nullable=False, index=True) # Código de materia (ej: INGA0003)
|
|
subject_id = db.Column(db.Integer, db.ForeignKey('subjects.id'), nullable=True)
|
|
milestone_type_id = db.Column(db.Integer, db.ForeignKey('milestone_types.id'), nullable=False)
|
|
date = db.Column(db.DateTime, nullable=False) # Fecha y hora del hito
|
|
classroom = db.Column(db.String(100), nullable=True) # Aula o enlace virtual asignado
|
|
link = db.Column(db.String(255), nullable=True) # Link Teams / Meet / Zoom si aplica
|
|
notes = db.Column(db.Text, nullable=True) # Temario, consignas o notas
|
|
is_active = db.Column(db.Boolean, default=True, nullable=False)
|
|
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
|
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
|
|
|
# Relación con Asignatura
|
|
subject = db.relationship('Subject', backref=db.backref('milestones', lazy='dynamic'))
|
|
|
|
@property
|
|
def tipo(self):
|
|
return self.type_obj.name if self.type_obj else 'Otro'
|
|
|
|
@property
|
|
def color(self):
|
|
return self.type_obj.color if self.type_obj and self.type_obj.color else '#0d6efd'
|
|
|
|
def to_dict(self):
|
|
return {
|
|
'id': self.id,
|
|
'title': self.title or self.tipo,
|
|
'materiaCodigo': self.materia_codigo,
|
|
'tipo': self.tipo,
|
|
'milestone_type_id': self.milestone_type_id,
|
|
'tipo_info': self.type_obj.to_dict() if self.type_obj else None,
|
|
'color': self.color,
|
|
'fecha': self.date.isoformat() if self.date else None,
|
|
'aula': self.classroom,
|
|
'link': self.link,
|
|
'notas': self.notes,
|
|
'is_active': self.is_active
|
|
}
|
|
|
|
def __repr__(self):
|
|
return f'<AcademicMilestone {self.materia_codigo} - {self.tipo} ({self.date})>'
|
|
|
|
|
|
# Datos semilla estándar para inicialización
|
|
BASE_MILESTONE_TYPES = [
|
|
{
|
|
'name': 'Parcial 1',
|
|
'code': 'parcial_1',
|
|
'color': '#0d6efd',
|
|
'description': 'Primer examen parcial de la cursada',
|
|
'sort_order': 1
|
|
},
|
|
{
|
|
'name': 'Parcial 2',
|
|
'code': 'parcial_2',
|
|
'color': '#0b5ed7',
|
|
'description': 'Segundo examen parcial de la cursada',
|
|
'sort_order': 2
|
|
},
|
|
{
|
|
'name': 'Trabajo Práctico 1',
|
|
'code': 'tp_1',
|
|
'color': '#198754',
|
|
'description': 'Primera entrega o evaluación práctica',
|
|
'sort_order': 3
|
|
},
|
|
{
|
|
'name': 'Trabajo Práctico 2',
|
|
'code': 'tp_2',
|
|
'color': '#157347',
|
|
'description': 'Segunda entrega o evaluación práctica',
|
|
'sort_order': 4
|
|
},
|
|
{
|
|
'name': 'Trabajo Práctico 3',
|
|
'code': 'tp_3',
|
|
'color': '#0f5132',
|
|
'description': 'Tercera entrega o proyecto integrador',
|
|
'sort_order': 5
|
|
},
|
|
{
|
|
'name': 'Recuperatorio Parcial 1',
|
|
'code': 'recu_p1',
|
|
'color': '#fd7e14',
|
|
'description': 'Instancia de recuperación para el Parcial 1',
|
|
'sort_order': 6
|
|
},
|
|
{
|
|
'name': 'Recuperatorio Parcial 2',
|
|
'code': 'recu_p2',
|
|
'color': '#e76b08',
|
|
'description': 'Instancia de recuperación para el Parcial 2',
|
|
'sort_order': 7
|
|
},
|
|
{
|
|
'name': 'Recuperatorio TP 1',
|
|
'code': 'recu_tp1',
|
|
'color': '#ffc107',
|
|
'description': 'Reentrega o recuperación de Trabajo Práctico 1',
|
|
'sort_order': 8
|
|
},
|
|
{
|
|
'name': 'Recuperatorio TP 2',
|
|
'code': 'recu_tp2',
|
|
'color': '#e0a800',
|
|
'description': 'Reentrega o recuperación de Trabajo Práctico 2',
|
|
'sort_order': 9
|
|
},
|
|
{
|
|
'name': 'Recuperatorio TP 3',
|
|
'code': 'recu_tp3',
|
|
'color': '#c69500',
|
|
'description': 'Reentrega o recuperación de Trabajo Práctico 3',
|
|
'sort_order': 10
|
|
},
|
|
{
|
|
'name': 'Final',
|
|
'code': 'final',
|
|
'color': '#dc3545',
|
|
'description': 'Examen final regular o libre',
|
|
'sort_order': 11
|
|
},
|
|
{
|
|
'name': 'Otro',
|
|
'code': 'otro',
|
|
'color': '#6c757d',
|
|
'description': 'Taller, seminario, clase especial o hito no tipificado',
|
|
'sort_order': 12
|
|
}
|
|
]
|