feat: implementacion de dashboards por rol, soporte de clases virtuales concurrentes en importador de sheets y cartelera del dia

This commit is contained in:
2026-09-18 23:04:48 -03:00
parent 47b50ede7e
commit 1080acd43a
240 changed files with 8354 additions and 8886 deletions
View File
+17
View File
@@ -0,0 +1,17 @@
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, BooleanField, SubmitField
from wtforms.validators import DataRequired, Email, Length
from flask_babel import lazy_gettext as _l
class LoginForm(FlaskForm):
email = StringField(_l('Email'),
validators=[
DataRequired(),
Email(),
Length(max=120)
],
filters=[lambda x: x.strip().lower() if x else '']
)
password = PasswordField(_l('Password'), validators=[DataRequired()])
remember_me = BooleanField(_l('Remember me'))
submit = SubmitField(_l('Sign In'))
+28
View File
@@ -0,0 +1,28 @@
from flask_wtf import FlaskForm
from wtforms import StringField, IntegerField, TextAreaField, BooleanField, SubmitField
from wtforms.validators import DataRequired, Length, NumberRange, Optional
from flask_babel import lazy_gettext as _l
class BuildingForm(FlaskForm):
name = StringField(_l('Building Name'), validators=[
DataRequired(),
Length(min=2, max=100)
])
code = StringField(_l('Code'), validators=[
Optional(),
Length(max=50)
])
address = StringField(_l('Address'), validators=[
Optional(),
Length(max=255)
])
floors = IntegerField(_l('Number of Floors'), default=1, validators=[
DataRequired(),
NumberRange(min=1, max=100)
])
description = TextAreaField(_l('Description'), validators=[
Optional(),
Length(max=500)
])
is_active = BooleanField(_l('Active'), default=True)
submit = SubmitField(_l('Save Building'))
+56
View File
@@ -0,0 +1,56 @@
from flask_wtf import FlaskForm
from wtforms import StringField, IntegerField, TextAreaField, BooleanField, SubmitField, SelectField
from wtforms.validators import DataRequired, InputRequired, Length, NumberRange, Optional, ValidationError
from flask_babel import lazy_gettext as _l
def validate_capacity(form, field):
val = str(field.data or '').strip()
if not val:
raise ValidationError(_l('La capacidad es obligatoria (ingrese un número o el símbolo ∞).'))
if val in ['∞', 'inf', 'INF', 'Infinity', 'infinity', '0', 'ilimitada', 'Ilimitada', 'sin limite', 'Sin límite']:
return
try:
num = int(val)
if num < 0:
raise ValidationError(_l('La capacidad no puede ser negativa.'))
if num > 10000:
raise ValidationError(_l('La capacidad máxima numérica es 10.000.'))
except ValueError:
raise ValidationError(_l('Ingrese un número entero o el símbolo ∞ para capacidad ilimitada.'))
class ClassroomForm(FlaskForm):
building_id = SelectField(_l('Edificio'), validators=[DataRequired()])
new_building_name = StringField(_l('Nombre del Nuevo Edificio'), validators=[Optional(), Length(max=100)])
room_number = StringField(_l('Número / Nombre del Aula'), validators=[
DataRequired(),
Length(min=1, max=50)
])
floor = StringField(_l('Piso'), validators=[
DataRequired(message=_l('El piso es obligatorio (ej: PB, 1, 2, 4°P)')),
Length(min=1, max=50)
])
capacity = StringField(_l('Capacidad'), validators=[
validate_capacity
])
description = TextAreaField(_l('Descripción'), validators=[
Optional(),
Length(max=500)
])
is_active = BooleanField(_l('Activa'), default=True)
submit = SubmitField(_l('Guardar Aula'))
class ClassroomResourceForm(FlaskForm):
resource_type = SelectField(_l('Resource Type'), choices=[
('PROJECTOR', _l('Projector')),
('WHITEBOARD', _l('Whiteboard')),
('COMPUTER', _l('Computer')),
('SOUND_SYSTEM', _l('Sound System')),
('MICROPHONE', _l('Microphone')),
('AIR_CONDITIONER', _l('Air Conditioner')),
('OTHER', _l('Other'))
], validators=[DataRequired()])
brand = StringField(_l('Brand'), validators=[Optional(), Length(max=100)])
model = StringField(_l('Model'), validators=[Optional(), Length(max=100)])
specifications = TextAreaField(_l('Specifications'), validators=[Optional()])
functional = BooleanField(_l('Functional'), default=True)
submit = SubmitField(_l('Save Resource'))
+49
View File
@@ -0,0 +1,49 @@
from flask_wtf import FlaskForm
from wtforms import SelectField, StringField, TextAreaField, IntegerField, SubmitField, DateTimeLocalField
from wtforms.validators import DataRequired, Length, NumberRange, Optional, ValidationError
from flask_babel import lazy_gettext as _l
from datetime import datetime, timedelta
class ReservationForm(FlaskForm):
classroom_id = SelectField(_l('Classroom'), coerce=int, validators=[DataRequired()])
commission_id = SelectField(_l('Commission/Subject'), coerce=int, validators=[DataRequired()])
start_time = DateTimeLocalField(_l('Start Time'), format='%Y-%m-%dT%H:%M', validators=[DataRequired()])
end_time = DateTimeLocalField(_l('End Time'), format='%Y-%m-%dT%H:%M', validators=[DataRequired()])
purpose = StringField(_l('Purpose'), validators=[
DataRequired(),
Length(min=3, max=500)
])
expected_attendees = IntegerField(_l('Expected Attendees'), validators=[
DataRequired(),
NumberRange(min=1, max=500)
])
notes = TextAreaField(_l('Notes'), validators=[
Optional(),
Length(max=1000)
])
virtual_link = StringField(_l('Virtual Classroom Link'), validators=[
Optional(),
Length(max=500)
])
submit = SubmitField(_l('Make Reservation'))
def __init__(self, *args, is_edit=False, **kwargs):
super().__init__(*args, **kwargs)
self.is_edit = is_edit
def validate_end_time(self, field):
if field.data <= self.start_time.data:
raise ValidationError('End time must be after start time.')
# Check if duration is reasonable (max 8 hours)
if field.data > self.start_time.data + timedelta(hours=8):
raise ValidationError('Reservation cannot exceed 8 hours.')
def validate_start_time(self, field):
# Don't allow new reservations in the past
if not self.is_edit and field.data < datetime.now() - timedelta(minutes=10):
raise ValidationError('Start time cannot be in the past.')
# Don't allow reservations too far in the future (max 3 months)
if field.data > datetime.now() + timedelta(days=90):
raise ValidationError('Reservations cannot be made more than 3 months in advance.')