from sqlalchemy import Column, Integer, String, Boolean, DateTime, ForeignKey, Text, JSON, Float from sqlalchemy.orm import relationship from sqlalchemy.sql import func from app.core.database import Base class User(Base): __tablename__ = "users" id = Column(Integer, primary_key=True, index=True) username = Column(String(50), unique=True, index=True, nullable=False) email = Column(String(100), unique=True, index=True) password_hash = Column(String(255), nullable=False) role = Column(String(20), nullable=False) # admin, mechanic, asesor full_name = Column(String(100)) employee_code = Column(String(50)) # Nro Operario - código de otro sistema is_active = Column(Boolean, default=True) created_at = Column(DateTime(timezone=True), server_default=func.now()) # Relationships checklists_created = relationship("Checklist", back_populates="creator") inspections = relationship("Inspection", back_populates="mechanic") api_tokens = relationship("APIToken", back_populates="user", cascade="all, delete-orphan") class APIToken(Base): __tablename__ = "api_tokens" id = Column(Integer, primary_key=True, index=True) user_id = Column(Integer, ForeignKey("users.id"), nullable=False) token = Column(String(100), unique=True, index=True, nullable=False) description = Column(String(200)) is_active = Column(Boolean, default=True) last_used_at = Column(DateTime(timezone=True)) created_at = Column(DateTime(timezone=True), server_default=func.now()) # Relationship user = relationship("User", back_populates="api_tokens") class Checklist(Base): __tablename__ = "checklists" id = Column(Integer, primary_key=True, index=True) name = Column(String(200), nullable=False) description = Column(Text) ai_mode = Column(String(20), default="off") # off, assisted, copilot scoring_enabled = Column(Boolean, default=True) max_score = Column(Integer, default=0) logo_url = Column(String(500)) is_active = Column(Boolean, default=True) created_by = Column(Integer, ForeignKey("users.id")) created_at = Column(DateTime(timezone=True), server_default=func.now()) updated_at = Column(DateTime(timezone=True), onupdate=func.now()) # Relationships creator = relationship("User", back_populates="checklists_created") questions = relationship("Question", back_populates="checklist", cascade="all, delete-orphan") inspections = relationship("Inspection", back_populates="checklist") permissions = relationship("ChecklistPermission", back_populates="checklist", cascade="all, delete-orphan") class Question(Base): __tablename__ = "questions" id = Column(Integer, primary_key=True, index=True) checklist_id = Column(Integer, ForeignKey("checklists.id"), nullable=False) section = Column(String(100)) # Sistema eléctrico, Frenos, etc text = Column(Text, nullable=False) type = Column(String(30), nullable=False) # boolean, single_choice, multiple_choice, scale, text, number, date, time points = Column(Integer, default=1) options = Column(JSON) # Configuración flexible según tipo de pregunta order = Column(Integer, default=0) allow_photos = Column(Boolean, default=True) max_photos = Column(Integer, default=3) requires_comment_on_fail = Column(Boolean, default=False) send_notification = Column(Boolean, default=False) # Conditional logic - Subpreguntas anidadas hasta 5 niveles parent_question_id = Column(Integer, ForeignKey("questions.id"), nullable=True) show_if_answer = Column(String(50), nullable=True) # Valor que dispara esta pregunta depth_level = Column(Integer, default=0) # 0=principal, 1-5=subpreguntas anidadas # AI Analysis ai_prompt = Column(Text, nullable=True) # Prompt personalizado para análisis de IA de esta pregunta # Soft Delete is_deleted = Column(Boolean, default=False) # Soft delete: mantiene integridad de respuestas históricas created_at = Column(DateTime(timezone=True), server_default=func.now()) updated_at = Column(DateTime(timezone=True), onupdate=func.now()) # Relationships checklist = relationship("Checklist", back_populates="questions") answers = relationship("Answer", back_populates="question") subquestions = relationship("Question", backref="parent", remote_side=[id]) class Inspection(Base): __tablename__ = "inspections" id = Column(Integer, primary_key=True, index=True) checklist_id = Column(Integer, ForeignKey("checklists.id"), nullable=False) mechanic_id = Column(Integer, ForeignKey("users.id"), nullable=False) # Datos de la OR or_number = Column(String(50)) work_order_number = Column(String(50)) # Datos del vehículo vehicle_plate = Column(String(20), nullable=False, index=True) vehicle_brand = Column(String(50)) vehicle_model = Column(String(100)) vehicle_km = Column(Integer) order_number = Column(String(200)) # Nº de Pedido # Datos del mecánico mechanic_employee_code = Column(String(50)) # Código de operario del mecánico # Scoring score = Column(Integer, default=0) max_score = Column(Integer, default=0) percentage = Column(Float, default=0.0) flagged_items_count = Column(Integer, default=0) # Estado status = Column(String(20), default="draft") # draft, completed, inactive is_active = Column(Boolean, default=True) # Firma signature_data = Column(Text) # Base64 de la firma signed_at = Column(DateTime(timezone=True)) # Timestamps started_at = Column(DateTime(timezone=True), server_default=func.now()) completed_at = Column(DateTime(timezone=True)) created_at = Column(DateTime(timezone=True), server_default=func.now()) updated_at = Column(DateTime(timezone=True), onupdate=func.now()) pdf_url = Column(String(500)) # URL del PDF en S3 # Relationships checklist = relationship("Checklist", back_populates="inspections") mechanic = relationship("User", back_populates="inspections") answers = relationship("Answer", back_populates="inspection", cascade="all, delete-orphan") class Answer(Base): __tablename__ = "answers" id = Column(Integer, primary_key=True, index=True) inspection_id = Column(Integer, ForeignKey("inspections.id"), nullable=False) question_id = Column(Integer, ForeignKey("questions.id"), nullable=False) answer_value = Column(Text) # La respuesta del mecánico status = Column(String(20), default="ok") # ok, warning, critical, info points_earned = Column(Integer, default=0) comment = Column(Text) # Comentarios adicionales ai_analysis = Column(JSON) # Análisis de IA si aplica is_flagged = Column(Boolean, default=False) # Si requiere atención created_at = Column(DateTime(timezone=True), server_default=func.now()) updated_at = Column(DateTime(timezone=True), onupdate=func.now()) # Relationships inspection = relationship("Inspection", back_populates="answers") question = relationship("Question", back_populates="answers") media_files = relationship("MediaFile", back_populates="answer", cascade="all, delete-orphan") class MediaFile(Base): __tablename__ = "media_files" id = Column(Integer, primary_key=True, index=True) answer_id = Column(Integer, ForeignKey("answers.id"), nullable=False) file_path = Column(String(500), nullable=False) file_type = Column(String(20), default="image") # image, video caption = Column(Text) order = Column(Integer, default=0) uploaded_at = Column(DateTime(timezone=True), server_default=func.now()) # Relationships answer = relationship("Answer", back_populates="media_files") class AIConfiguration(Base): __tablename__ = "ai_configurations" id = Column(Integer, primary_key=True, index=True) provider = Column(String(50), nullable=False) # openai, gemini api_key = Column(Text, nullable=False) model_name = Column(String(100), nullable=True) logo_url = Column(Text, nullable=True) # URL del logo configurable is_active = Column(Boolean, default=True) created_at = Column(DateTime(timezone=True), server_default=func.now()) updated_at = Column(DateTime(timezone=True), onupdate=func.now()) class ChecklistPermission(Base): """Tabla intermedia para permisos de checklist por mecánico""" __tablename__ = "checklist_permissions" id = Column(Integer, primary_key=True, index=True) checklist_id = Column(Integer, ForeignKey("checklists.id", ondelete="CASCADE"), nullable=False) mechanic_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False) created_at = Column(DateTime(timezone=True), server_default=func.now()) # Relationships checklist = relationship("Checklist", back_populates="permissions") mechanic = relationship("User") class QuestionAuditLog(Base): """Registro de auditoría para cambios en preguntas de checklists""" __tablename__ = "question_audit_log" id = Column(Integer, primary_key=True, index=True) question_id = Column(Integer, ForeignKey("questions.id", ondelete="CASCADE"), nullable=False) checklist_id = Column(Integer, ForeignKey("checklists.id", ondelete="CASCADE"), nullable=False) user_id = Column(Integer, ForeignKey("users.id"), nullable=False) action = Column(String(50), nullable=False) # created, updated, deleted field_name = Column(String(100), nullable=True) # Campo modificado old_value = Column(Text, nullable=True) # Valor anterior new_value = Column(Text, nullable=True) # Valor nuevo comment = Column(Text, nullable=True) # Comentario del cambio created_at = Column(DateTime(timezone=True), server_default=func.now()) # Relationships question = relationship("Question") checklist = relationship("Checklist") user = relationship("User") class InspectionAuditLog(Base): """Registro de auditoría para cambios en inspecciones y respuestas""" __tablename__ = "inspection_audit_log" id = Column(Integer, primary_key=True, index=True) inspection_id = Column(Integer, ForeignKey("inspections.id", ondelete="CASCADE"), nullable=False) answer_id = Column(Integer, ForeignKey("answers.id", ondelete="CASCADE"), nullable=True) user_id = Column(Integer, ForeignKey("users.id"), nullable=False) action = Column(String(50), nullable=False) # created, updated, deleted, status_changed entity_type = Column(String(50), nullable=False) # inspection, answer field_name = Column(String(100), nullable=True) # Campo modificado old_value = Column(Text, nullable=True) # Valor anterior new_value = Column(Text, nullable=True) # Valor nuevo comment = Column(Text, nullable=True) # Comentario del cambio created_at = Column(DateTime(timezone=True), server_default=func.now()) # Relationships inspection = relationship("Inspection") answer = relationship("Answer") user = relationship("User")