from app import db from werkzeug.security import generate_password_hash, check_password_hash from datetime import datetime class User(db.Model): __tablename__ = 'users' id = db.Column(db.Integer, primary_key=True) email = db.Column(db.String(255), unique=True, nullable=False, index=True) password_hash = db.Column(db.String(255), nullable=False) name = db.Column(db.String(100), nullable=False) role = db.Column(db.String(50), nullable=False) is_active = db.Column(db.Boolean, default=True, nullable=False) created_at = db.Column(db.DateTime, default=datetime.utcnow) last_login = db.Column(db.DateTime) preferred_language = db.Column(db.String(10), default=None, nullable=True) theme_preference = db.Column(db.String(10), default='auto', nullable=False) role_id = db.Column(db.Integer, db.ForeignKey('roles.id', ondelete='SET NULL'), nullable=True) # Extended personal, contact and identity document fields first_name = db.Column(db.String(100), nullable=True) last_name = db.Column(db.String(100), nullable=True) phone = db.Column(db.String(50), nullable=True) personal_email = db.Column(db.String(255), nullable=True) address = db.Column(db.String(255), nullable=True) document_type = db.Column(db.String(20), default='DNI', nullable=True) document_number = db.Column(db.String(30), nullable=True, index=True) def get_first_name(self): if self.first_name: return self.first_name return self.name.split()[0] if self.name else '' def get_last_name(self): if self.last_name: return self.last_name parts = self.name.split() if self.name else [] return ' '.join(parts[1:]) if len(parts) > 1 else '' def get_formatted_document(self): from app.constants.document_types import format_document return format_document(self.document_type, self.document_number) # Relationships reservations = db.relationship('Reservation', backref='user', lazy=True, cascade='all, delete-orphan') def set_password(self, password): """Set password hash""" self.password_hash = generate_password_hash(password, method='pbkdf2:sha256') def check_password(self, password): """Check password""" return check_password_hash(self.password_hash, password) def is_admin(self): """Check if user is admin""" if self.role_obj and self.role_obj.name.lower() in ['admin', 'administrador']: return True return str(self.role).upper() in ['ADMIN', 'ADMINISTRADOR'] def get_institutional_role(self): """Returns normalized institutional role: 'admin', 'bedelia', 'docente', 'alumno'""" raw = '' if self.role_obj and self.role_obj.name: raw = self.role_obj.name.lower().strip() elif self.role: raw = str(self.role).lower().strip() if 'admin' in raw: return 'admin' if 'bedel' in raw: return 'bedelia' if 'docent' in raw or 'prof' in raw or 'teacher' in raw: return 'docente' if 'alumn' in raw or 'estud' in raw or 'student' in raw: return 'alumno' return 'docente' def is_bedelia(self): return self.get_institutional_role() == 'bedelia' def is_docente(self): return self.get_institutional_role() == 'docente' def is_alumno(self): return self.get_institutional_role() == 'alumno' @property def role_badge_display(self): role_map = { 'admin': {'label': 'Administrador', 'class': 'badge-admin', 'icon': 'bi-shield-check', 'color': 'primary'}, 'bedelia': {'label': 'Bedelía', 'class': 'badge-bedelia', 'icon': 'bi-building-gear', 'color': 'warning'}, 'docente': {'label': 'Profesor / Docente', 'class': 'badge-docente', 'icon': 'bi-person-video3', 'color': 'info'}, 'alumno': {'label': 'Alumno / Estudiante', 'class': 'badge-alumno', 'icon': 'bi-mortarboard', 'color': 'success'} } return role_map.get(self.get_institutional_role(), {'label': self.role or 'Docente', 'class': 'bg-secondary', 'icon': 'bi-person', 'color': 'secondary'}) def has_permission(self, module, min_level='read'): """Check if user has granular permission on a given module""" if self.is_admin(): return True if self.role_obj: return self.role_obj.has_permission(module, min_level) return False def can_manage(self): """Check if user can access the Management / Admin section""" return ( self.is_admin() or self.is_bedelia() or self.has_permission('users', 'read') or self.has_permission('milestone_types', 'read') or self.has_permission('import', 'read') or self.has_permission('reservations', 'read_write') or self.has_permission('classrooms', 'read_write') or self.has_permission('buildings', 'read_write') or self.has_permission('metrics', 'read') ) def can_edit_reservation(self, reservation): """Check if user has permission to edit a given reservation""" if not reservation: return False if self.is_admin(): return True role_name = (self.role_obj.name if self.role_obj else self.role or '').lower() if role_name in ['admin', 'administrador', 'bedelia', 'bedelía', 'operador']: return True if self.has_permission('reservations', 'read_write'): # El docente creador puede modificar su propia reserva if reservation.user_id == self.id: return True # Bedelía o roles de gestión con permiso de escritura total if role_name in ['bedelia', 'bedelía', 'operador']: return True return False def update_last_login(self): """Update last login timestamp""" self.last_login = datetime.utcnow() db.session.commit() def to_dict(self): """Convert user to dictionary""" return { 'id': self.id, 'email': self.email, 'institutional_email': self.email, 'personal_email': self.personal_email or '', 'name': self.name, 'first_name': self.get_first_name(), 'last_name': self.get_last_name(), 'phone': self.phone or '', 'address': self.address or '', 'document_type': self.document_type or 'DNI', 'document_number': self.document_number or '', 'document_formatted': self.get_formatted_document(), 'role': self.role, 'role_id': self.role_id, 'is_active': self.is_active, 'created_at': self.created_at.isoformat() if self.created_at else None, 'last_login': self.last_login.isoformat() if self.last_login else None, 'preferred_language': self.preferred_language, 'theme_preference': self.theme_preference } def __repr__(self): return f''