""" User Model - ServiceManagerWeb Modelo para usuarios del sistema (internos y clientes) """ from sqlalchemy import String, Boolean, DateTime, ForeignKey, Text, ARRAY from sqlalchemy.orm import Mapped, mapped_column, relationship from sqlalchemy.dialects.postgresql import UUID, ENUM from typing import Optional, List import enum import uuid from datetime import datetime from app.core.database import Base class UserRole(str, enum.Enum): """Roles de usuario en el sistema.""" # Staff interno ADMIN = "ADMIN" # Control total SUPPORT_MANAGER = "SUPPORT_MANAGER" # Gestión de equipos y SLAs AGENT = "AGENT" # Atención de tickets AUDITOR = "AUDITOR" # Solo lectura para auditoría # Clientes CLIENT_ADMIN = "CLIENT_ADMIN" # Admin de organización cliente CLIENT_USER = "CLIENT_USER" # Usuario final cliente class User(Base): """Modelo de Usuario.""" __tablename__ = "users" # Relación con tenant tenant_id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), ForeignKey("tenants.id", ondelete="CASCADE"), nullable=False ) # Información básica email: Mapped[str] = mapped_column(String(320), nullable=False) first_name: Mapped[str] = mapped_column(String(100), nullable=False) last_name: Mapped[str] = mapped_column(String(100), nullable=False) avatar_url: Mapped[Optional[str]] = mapped_column(String(500)) # Autenticación password_hash: Mapped[str] = mapped_column(String(255), nullable=False) role: Mapped[UserRole] = mapped_column(ENUM(UserRole, name="user_role_enum"), nullable=False) # 2FA (opcional para staff interno) totp_secret: Mapped[Optional[str]] = mapped_column(String(32)) totp_enabled: Mapped[bool] = mapped_column(Boolean, default=False) backup_codes: Mapped[Optional[List[str]]] = mapped_column(ARRAY(String)) # Estado is_active: Mapped[bool] = mapped_column(Boolean, default=True) email_verified: Mapped[bool] = mapped_column(Boolean, default=False) last_login: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True)) last_activity: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True)) # Preferencias language: Mapped[str] = mapped_column(String(10), default="es") timezone: Mapped[str] = mapped_column(String(50), default="UTC") notifications_email: Mapped[bool] = mapped_column(Boolean, default=True) # Relaciones tenant: Mapped["Tenant"] = relationship("Tenant", back_populates="users") created_tickets: Mapped[List["Ticket"]] = relationship( "Ticket", back_populates="created_by_user", foreign_keys="Ticket.created_by" ) assigned_tickets: Mapped[List["Ticket"]] = relationship( "Ticket", back_populates="assigned_to_user", foreign_keys="Ticket.assigned_to" ) refresh_tokens: Mapped[List["RefreshToken"]] = relationship( "RefreshToken", back_populates="user", foreign_keys="RefreshToken.user_id", cascade="all, delete-orphan" ) # Unique constraint por tenant __table_args__ = ( {"postgresql_tablespace": "users"}, ) def __repr__(self) -> str: return f"" @property def full_name(self) -> str: """Get user's full name.""" return f"{self.first_name} {self.last_name}" @property def is_staff(self) -> bool: """Check if user is internal staff.""" return self.role in [ UserRole.ADMIN, UserRole.SUPPORT_MANAGER, UserRole.AGENT, UserRole.AUDITOR ] @property def is_client(self) -> bool: """Check if user is a client.""" return self.role in [ UserRole.CLIENT_ADMIN, UserRole.CLIENT_USER ] @property def can_manage_users(self) -> bool: """Check if user can manage other users.""" return self.role in [ UserRole.ADMIN, UserRole.SUPPORT_MANAGER, UserRole.CLIENT_ADMIN ] @property def can_manage_tickets(self) -> bool: """Check if user can manage tickets.""" return self.role in [ UserRole.ADMIN, UserRole.SUPPORT_MANAGER, UserRole.AGENT ] @property def requires_2fa(self) -> bool: """Check if 2FA is required for this user.""" # 2FA opcional para staff interno, no requerido para clientes return self.is_staff