""" Modelo de Post - Posts generados y programados. """ from datetime import datetime from sqlalchemy import Column, Integer, String, Text, Boolean, DateTime, ForeignKey, JSON, Enum from sqlalchemy.dialects.postgresql import ARRAY from sqlalchemy.orm import relationship import enum from app.core.database import Base class PostStatus(enum.Enum): """Estados posibles de un post.""" DRAFT = "draft" PENDING_APPROVAL = "pending_approval" APPROVED = "approved" SCHEDULED = "scheduled" PUBLISHING = "publishing" PUBLISHED = "published" FAILED = "failed" CANCELLED = "cancelled" class ContentType(enum.Enum): """Tipos de contenido.""" TIP_TECH = "tip_tech" DATO_CURIOSO = "dato_curioso" FRASE_MOTIVACIONAL = "frase_motivacional" EFEMERIDE = "efemeride" PRODUCTO = "producto" SERVICIO = "servicio" HILO_EDUCATIVO = "hilo_educativo" CASO_EXITO = "caso_exito" PROMOCION = "promocion" ANUNCIO = "anuncio" MANUAL = "manual" class Post(Base): """Modelo para posts de redes sociales.""" __tablename__ = "posts" id = Column(Integer, primary_key=True, index=True) # Contenido content = Column(Text, nullable=False) content_type = Column(String(50), nullable=False, index=True) # Contenido adaptado por plataforma (opcional) content_x = Column(Text, nullable=True) # Versión para X (280 chars) content_threads = Column(Text, nullable=True) content_instagram = Column(Text, nullable=True) content_facebook = Column(Text, nullable=True) # Plataformas destino platforms = Column(ARRAY(String), nullable=False) # Ejemplo: ["x", "threads", "instagram", "facebook"] # Estado y programación status = Column(String(50), default="draft", index=True) scheduled_at = Column(DateTime, nullable=True, index=True) published_at = Column(DateTime, nullable=True) # Imagen image_url = Column(String(500), nullable=True) image_template_id = Column(Integer, ForeignKey("image_templates.id"), nullable=True) # IDs de publicación en cada plataforma platform_post_ids = Column(JSON, nullable=True) # Ejemplo: {"x": "123456", "instagram": "789012", ...} # Errores de publicación error_message = Column(Text, nullable=True) retry_count = Column(Integer, default=0) # Aprobación approval_required = Column(Boolean, default=False) approved_by = Column(String(100), nullable=True) approved_at = Column(DateTime, nullable=True) # Relaciones con contenido fuente product_id = Column(Integer, ForeignKey("products.id"), nullable=True) service_id = Column(Integer, ForeignKey("services.id"), nullable=True) tip_template_id = Column(Integer, ForeignKey("tip_templates.id"), nullable=True) # Metadatos hashtags = Column(ARRAY(String), nullable=True) mentions = Column(ARRAY(String), nullable=True) # Métricas (actualizadas después de publicar) metrics = Column(JSON, nullable=True) # Ejemplo: {"likes": 10, "retweets": 5, "comments": 3} # A/B Testing ab_test_id = Column(Integer, ForeignKey("ab_tests.id"), nullable=True, index=True) # Recycling is_recyclable = Column(Boolean, default=True) recycled_from_id = Column(Integer, ForeignKey("posts.id"), nullable=True) recycle_count = Column(Integer, default=0) # Times this post has been recycled # AI Generation Quality quality_score = Column(Integer, nullable=True, index=True) # 0-100 score from validator score_breakdown = Column(JSON, nullable=True) # Detailed scoring breakdown generation_attempts = Column(Integer, default=1) # Times regenerated before acceptance # Timestamps created_at = Column(DateTime, default=datetime.utcnow) updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) # Relationships metrics_history = relationship("PostMetrics", back_populates="post", cascade="all, delete-orphan") def __repr__(self): return f"" def to_dict(self): """Convertir a diccionario.""" return { "id": self.id, "content": self.content, "content_type": self.content_type, "content_x": self.content_x, "content_threads": self.content_threads, "content_instagram": self.content_instagram, "content_facebook": self.content_facebook, "platforms": self.platforms, "status": self.status, "scheduled_at": self.scheduled_at.isoformat() if self.scheduled_at else None, "published_at": self.published_at.isoformat() if self.published_at else None, "image_url": self.image_url, "platform_post_ids": self.platform_post_ids, "error_message": self.error_message, "approval_required": self.approval_required, "hashtags": self.hashtags, "metrics": self.metrics, "created_at": self.created_at.isoformat() if self.created_at else None, "ab_test_id": self.ab_test_id, "is_recyclable": self.is_recyclable, "recycled_from_id": self.recycled_from_id, "recycle_count": self.recycle_count, "quality_score": self.quality_score, "score_breakdown": self.score_breakdown, "generation_attempts": self.generation_attempts } def get_content_for_platform(self, platform: str) -> str: """Obtener contenido adaptado para una plataforma específica.""" platform_content = { "x": self.content_x, "threads": self.content_threads, "instagram": self.content_instagram, "facebook": self.content_facebook } return platform_content.get(platform) or self.content