Implementación inicial del sistema de automatización de redes sociales
- Estructura completa del proyecto con FastAPI - Modelos de base de datos (productos, servicios, posts, calendario, interacciones) - Publishers para X, Threads, Instagram, Facebook - Generador de contenido con DeepSeek API - Worker de Celery con tareas programadas - Dashboard básico con templates HTML - Docker Compose para despliegue - Documentación completa Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
24
app/models/__init__.py
Normal file
24
app/models/__init__.py
Normal file
@@ -0,0 +1,24 @@
|
||||
"""
|
||||
Modelos de base de datos SQLAlchemy.
|
||||
"""
|
||||
|
||||
from app.core.database import Base
|
||||
|
||||
from app.models.product import Product
|
||||
from app.models.service import Service
|
||||
from app.models.tip_template import TipTemplate
|
||||
from app.models.post import Post
|
||||
from app.models.content_calendar import ContentCalendar
|
||||
from app.models.image_template import ImageTemplate
|
||||
from app.models.interaction import Interaction
|
||||
|
||||
__all__ = [
|
||||
"Base",
|
||||
"Product",
|
||||
"Service",
|
||||
"TipTemplate",
|
||||
"Post",
|
||||
"ContentCalendar",
|
||||
"ImageTemplate",
|
||||
"Interaction"
|
||||
]
|
||||
66
app/models/content_calendar.py
Normal file
66
app/models/content_calendar.py
Normal file
@@ -0,0 +1,66 @@
|
||||
"""
|
||||
Modelo de ContentCalendar - Calendario de publicación.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from sqlalchemy import Column, Integer, String, Time, Boolean, DateTime
|
||||
from sqlalchemy.dialects.postgresql import ARRAY
|
||||
|
||||
from app.core.database import Base
|
||||
|
||||
|
||||
class ContentCalendar(Base):
|
||||
"""Modelo para el calendario de contenido."""
|
||||
|
||||
__tablename__ = "content_calendar"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
|
||||
# Programación
|
||||
day_of_week = Column(Integer, nullable=False) # 0=Lunes, 6=Domingo
|
||||
time = Column(Time, nullable=False) # Hora de publicación
|
||||
|
||||
# Tipo de contenido
|
||||
content_type = Column(String(50), nullable=False)
|
||||
# Tipos: tip_tech, dato_curioso, producto, servicio, etc.
|
||||
|
||||
# Plataformas
|
||||
platforms = Column(ARRAY(String), nullable=False)
|
||||
# Ejemplo: ["x", "threads", "instagram"]
|
||||
|
||||
# Configuración
|
||||
is_active = Column(Boolean, default=True)
|
||||
requires_approval = Column(Boolean, default=False)
|
||||
|
||||
# Restricciones opcionales
|
||||
category_filter = Column(String(100), nullable=True) # Solo tips de esta categoría
|
||||
priority = Column(Integer, default=0) # Mayor = más prioritario
|
||||
|
||||
# Descripción
|
||||
description = Column(String(255), nullable=True)
|
||||
# Ejemplo: "Tip tech diario matutino"
|
||||
|
||||
# Timestamps
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<ContentCalendar {self.day_of_week} {self.time} - {self.content_type}>"
|
||||
|
||||
def to_dict(self):
|
||||
"""Convertir a diccionario."""
|
||||
days = ["Lunes", "Martes", "Miércoles", "Jueves", "Viernes", "Sábado", "Domingo"]
|
||||
return {
|
||||
"id": self.id,
|
||||
"day_of_week": self.day_of_week,
|
||||
"day_name": days[self.day_of_week] if 0 <= self.day_of_week <= 6 else "Desconocido",
|
||||
"time": self.time.strftime("%H:%M") if self.time else None,
|
||||
"content_type": self.content_type,
|
||||
"platforms": self.platforms,
|
||||
"is_active": self.is_active,
|
||||
"requires_approval": self.requires_approval,
|
||||
"category_filter": self.category_filter,
|
||||
"priority": self.priority,
|
||||
"description": self.description,
|
||||
"created_at": self.created_at.isoformat() if self.created_at else None
|
||||
}
|
||||
75
app/models/image_template.py
Normal file
75
app/models/image_template.py
Normal file
@@ -0,0 +1,75 @@
|
||||
"""
|
||||
Modelo de ImageTemplate - Plantillas para generar imágenes.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from sqlalchemy import Column, Integer, String, Text, Boolean, DateTime, JSON
|
||||
from sqlalchemy.dialects.postgresql import ARRAY
|
||||
|
||||
from app.core.database import Base
|
||||
|
||||
|
||||
class ImageTemplate(Base):
|
||||
"""Modelo para plantillas de imágenes."""
|
||||
|
||||
__tablename__ = "image_templates"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
|
||||
# Información básica
|
||||
name = Column(String(100), nullable=False, index=True)
|
||||
description = Column(String(255), nullable=True)
|
||||
|
||||
# Categoría
|
||||
category = Column(String(50), nullable=False)
|
||||
# Categorías: tip, producto, servicio, promocion, etc.
|
||||
|
||||
# Archivo de plantilla
|
||||
template_file = Column(String(255), nullable=False) # Ruta al archivo HTML/template
|
||||
|
||||
# Variables que acepta la plantilla
|
||||
variables = Column(ARRAY(String), nullable=False)
|
||||
# Ejemplo: ["titulo", "contenido", "hashtags", "logo"]
|
||||
|
||||
# Configuración de diseño
|
||||
design_config = Column(JSON, nullable=True)
|
||||
# Ejemplo: {
|
||||
# "width": 1080,
|
||||
# "height": 1080,
|
||||
# "background_color": "#1a1a2e",
|
||||
# "accent_color": "#d4a574",
|
||||
# "font_family": "Inter"
|
||||
# }
|
||||
|
||||
# Tamaños de salida
|
||||
output_sizes = Column(JSON, nullable=True)
|
||||
# Ejemplo: {
|
||||
# "instagram": {"width": 1080, "height": 1080},
|
||||
# "x": {"width": 1200, "height": 675},
|
||||
# "facebook": {"width": 1200, "height": 630}
|
||||
# }
|
||||
|
||||
# Estado
|
||||
is_active = Column(Boolean, default=True)
|
||||
|
||||
# Timestamps
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<ImageTemplate {self.name}>"
|
||||
|
||||
def to_dict(self):
|
||||
"""Convertir a diccionario."""
|
||||
return {
|
||||
"id": self.id,
|
||||
"name": self.name,
|
||||
"description": self.description,
|
||||
"category": self.category,
|
||||
"template_file": self.template_file,
|
||||
"variables": self.variables,
|
||||
"design_config": self.design_config,
|
||||
"output_sizes": self.output_sizes,
|
||||
"is_active": self.is_active,
|
||||
"created_at": self.created_at.isoformat() if self.created_at else None
|
||||
}
|
||||
87
app/models/interaction.py
Normal file
87
app/models/interaction.py
Normal file
@@ -0,0 +1,87 @@
|
||||
"""
|
||||
Modelo de Interaction - Interacciones en redes sociales.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from sqlalchemy import Column, Integer, String, Text, Boolean, DateTime, ForeignKey
|
||||
|
||||
from app.core.database import Base
|
||||
|
||||
|
||||
class Interaction(Base):
|
||||
"""Modelo para interacciones (comentarios, DMs, menciones)."""
|
||||
|
||||
__tablename__ = "interactions"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
|
||||
# Plataforma
|
||||
platform = Column(String(50), nullable=False, index=True)
|
||||
# Valores: x, threads, instagram, facebook
|
||||
|
||||
# Tipo de interacción
|
||||
interaction_type = Column(String(50), nullable=False, index=True)
|
||||
# Tipos: comment, reply, dm, mention, like, repost, quote
|
||||
|
||||
# Post relacionado (si aplica)
|
||||
post_id = Column(Integer, ForeignKey("posts.id"), nullable=True)
|
||||
|
||||
# ID externo en la plataforma
|
||||
external_id = Column(String(100), nullable=False, unique=True)
|
||||
external_post_id = Column(String(100), nullable=True) # ID del post en la plataforma
|
||||
|
||||
# Autor de la interacción
|
||||
author_username = Column(String(100), nullable=False)
|
||||
author_name = Column(String(255), nullable=True)
|
||||
author_profile_url = Column(String(500), nullable=True)
|
||||
author_avatar_url = Column(String(500), nullable=True)
|
||||
|
||||
# Contenido
|
||||
content = Column(Text, nullable=True)
|
||||
|
||||
# Respuesta
|
||||
responded = Column(Boolean, default=False, index=True)
|
||||
response_content = Column(Text, nullable=True)
|
||||
responded_at = Column(DateTime, nullable=True)
|
||||
response_external_id = Column(String(100), nullable=True)
|
||||
|
||||
# Clasificación
|
||||
is_lead = Column(Boolean, default=False) # Potencial cliente
|
||||
sentiment = Column(String(50), nullable=True) # positive, negative, neutral
|
||||
priority = Column(Integer, default=0) # 0=normal, 1=importante, 2=urgente
|
||||
|
||||
# Estado
|
||||
is_read = Column(Boolean, default=False)
|
||||
is_archived = Column(Boolean, default=False)
|
||||
|
||||
# Timestamps
|
||||
interaction_at = Column(DateTime, nullable=False) # Cuándo ocurrió en la plataforma
|
||||
created_at = Column(DateTime, default=datetime.utcnow) # Cuándo se guardó aquí
|
||||
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Interaction {self.platform} - {self.interaction_type} - {self.author_username}>"
|
||||
|
||||
def to_dict(self):
|
||||
"""Convertir a diccionario."""
|
||||
return {
|
||||
"id": self.id,
|
||||
"platform": self.platform,
|
||||
"interaction_type": self.interaction_type,
|
||||
"post_id": self.post_id,
|
||||
"external_id": self.external_id,
|
||||
"author_username": self.author_username,
|
||||
"author_name": self.author_name,
|
||||
"author_profile_url": self.author_profile_url,
|
||||
"author_avatar_url": self.author_avatar_url,
|
||||
"content": self.content,
|
||||
"responded": self.responded,
|
||||
"response_content": self.response_content,
|
||||
"responded_at": self.responded_at.isoformat() if self.responded_at else None,
|
||||
"is_lead": self.is_lead,
|
||||
"sentiment": self.sentiment,
|
||||
"priority": self.priority,
|
||||
"is_read": self.is_read,
|
||||
"interaction_at": self.interaction_at.isoformat() if self.interaction_at else None,
|
||||
"created_at": self.created_at.isoformat() if self.created_at else None
|
||||
}
|
||||
134
app/models/post.py
Normal file
134
app/models/post.py
Normal file
@@ -0,0 +1,134 @@
|
||||
"""
|
||||
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
|
||||
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}
|
||||
|
||||
# Timestamps
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Post {self.id} - {self.status}>"
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
80
app/models/product.py
Normal file
80
app/models/product.py
Normal file
@@ -0,0 +1,80 @@
|
||||
"""
|
||||
Modelo de Producto - Catálogo de equipos de cómputo e impresoras 3D.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from sqlalchemy import Column, Integer, String, Text, Float, Boolean, DateTime, JSON
|
||||
from sqlalchemy.dialects.postgresql import ARRAY
|
||||
|
||||
from app.core.database import Base
|
||||
|
||||
|
||||
class Product(Base):
|
||||
"""Modelo para productos del catálogo."""
|
||||
|
||||
__tablename__ = "products"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
|
||||
# Información básica
|
||||
name = Column(String(255), nullable=False, index=True)
|
||||
description = Column(Text, nullable=True)
|
||||
short_description = Column(String(500), nullable=True) # Para posts
|
||||
|
||||
# Categorización
|
||||
category = Column(String(100), nullable=False, index=True) # laptop, desktop, impresora_3d, etc.
|
||||
subcategory = Column(String(100), nullable=True)
|
||||
brand = Column(String(100), nullable=True)
|
||||
model = Column(String(100), nullable=True)
|
||||
|
||||
# Precio y stock
|
||||
price = Column(Float, nullable=False)
|
||||
price_usd = Column(Float, nullable=True) # Precio en dólares (opcional)
|
||||
stock = Column(Integer, default=0)
|
||||
is_available = Column(Boolean, default=True)
|
||||
|
||||
# Especificaciones técnicas (JSON flexible)
|
||||
specs = Column(JSON, nullable=True)
|
||||
# Ejemplo: {"cpu": "Intel i7", "ram": "16GB", "storage": "512GB SSD"}
|
||||
|
||||
# Imágenes
|
||||
images = Column(ARRAY(String), nullable=True) # URLs de imágenes
|
||||
main_image = Column(String(500), nullable=True)
|
||||
|
||||
# SEO y marketing
|
||||
tags = Column(ARRAY(String), nullable=True) # Tags para búsqueda
|
||||
highlights = Column(ARRAY(String), nullable=True) # Puntos destacados
|
||||
|
||||
# Control de publicación
|
||||
is_featured = Column(Boolean, default=False) # Producto destacado
|
||||
last_posted_at = Column(DateTime, nullable=True) # Última vez que se publicó
|
||||
|
||||
# Timestamps
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Product {self.name}>"
|
||||
|
||||
def to_dict(self):
|
||||
"""Convertir a diccionario."""
|
||||
return {
|
||||
"id": self.id,
|
||||
"name": self.name,
|
||||
"description": self.description,
|
||||
"short_description": self.short_description,
|
||||
"category": self.category,
|
||||
"subcategory": self.subcategory,
|
||||
"brand": self.brand,
|
||||
"model": self.model,
|
||||
"price": self.price,
|
||||
"stock": self.stock,
|
||||
"is_available": self.is_available,
|
||||
"specs": self.specs,
|
||||
"images": self.images,
|
||||
"main_image": self.main_image,
|
||||
"tags": self.tags,
|
||||
"highlights": self.highlights,
|
||||
"is_featured": self.is_featured,
|
||||
"created_at": self.created_at.isoformat() if self.created_at else None
|
||||
}
|
||||
90
app/models/service.py
Normal file
90
app/models/service.py
Normal file
@@ -0,0 +1,90 @@
|
||||
"""
|
||||
Modelo de Servicio - Servicios de consultoría de Consultoría AS.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from sqlalchemy import Column, Integer, String, Text, Boolean, DateTime, JSON
|
||||
from sqlalchemy.dialects.postgresql import ARRAY
|
||||
|
||||
from app.core.database import Base
|
||||
|
||||
|
||||
class Service(Base):
|
||||
"""Modelo para servicios de consultoría."""
|
||||
|
||||
__tablename__ = "services"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
|
||||
# Información básica
|
||||
name = Column(String(255), nullable=False, index=True)
|
||||
description = Column(Text, nullable=False)
|
||||
short_description = Column(String(500), nullable=True) # Para posts
|
||||
|
||||
# Categorización
|
||||
category = Column(String(100), nullable=False, index=True)
|
||||
# Categorías: ia, automatizacion, desarrollo, iot, voip, crm, etc.
|
||||
|
||||
# Sectores objetivo
|
||||
target_sectors = Column(ARRAY(String), nullable=True)
|
||||
# Ejemplo: ["hoteles", "construccion", "logistica", "retail"]
|
||||
|
||||
# Beneficios y características
|
||||
benefits = Column(ARRAY(String), nullable=True)
|
||||
# Ejemplo: ["Ahorro de tiempo", "Reducción de errores", "24/7"]
|
||||
|
||||
features = Column(ARRAY(String), nullable=True)
|
||||
# Características técnicas del servicio
|
||||
|
||||
# Casos de éxito
|
||||
case_studies = Column(JSON, nullable=True)
|
||||
# Ejemplo: [{"client": "Hotel X", "result": "50% menos tiempo en reservas"}]
|
||||
|
||||
# Imágenes e íconos
|
||||
icon = Column(String(100), nullable=True) # Nombre del ícono
|
||||
images = Column(ARRAY(String), nullable=True)
|
||||
main_image = Column(String(500), nullable=True)
|
||||
|
||||
# Precios (opcional, puede ser "cotizar")
|
||||
price_range = Column(String(100), nullable=True) # "Desde $5,000 MXN"
|
||||
has_free_demo = Column(Boolean, default=False)
|
||||
|
||||
# SEO y marketing
|
||||
tags = Column(ARRAY(String), nullable=True)
|
||||
call_to_action = Column(String(255), nullable=True) # "Agenda una demo"
|
||||
|
||||
# Control de publicación
|
||||
is_active = Column(Boolean, default=True)
|
||||
is_featured = Column(Boolean, default=False)
|
||||
last_posted_at = Column(DateTime, nullable=True)
|
||||
|
||||
# Timestamps
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Service {self.name}>"
|
||||
|
||||
def to_dict(self):
|
||||
"""Convertir a diccionario."""
|
||||
return {
|
||||
"id": self.id,
|
||||
"name": self.name,
|
||||
"description": self.description,
|
||||
"short_description": self.short_description,
|
||||
"category": self.category,
|
||||
"target_sectors": self.target_sectors,
|
||||
"benefits": self.benefits,
|
||||
"features": self.features,
|
||||
"case_studies": self.case_studies,
|
||||
"icon": self.icon,
|
||||
"images": self.images,
|
||||
"main_image": self.main_image,
|
||||
"price_range": self.price_range,
|
||||
"has_free_demo": self.has_free_demo,
|
||||
"tags": self.tags,
|
||||
"call_to_action": self.call_to_action,
|
||||
"is_active": self.is_active,
|
||||
"is_featured": self.is_featured,
|
||||
"created_at": self.created_at.isoformat() if self.created_at else None
|
||||
}
|
||||
77
app/models/tip_template.py
Normal file
77
app/models/tip_template.py
Normal file
@@ -0,0 +1,77 @@
|
||||
"""
|
||||
Modelo de TipTemplate - Banco de tips para generar contenido automático.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from sqlalchemy import Column, Integer, String, Text, Boolean, DateTime
|
||||
from sqlalchemy.dialects.postgresql import ARRAY
|
||||
|
||||
from app.core.database import Base
|
||||
|
||||
|
||||
class TipTemplate(Base):
|
||||
"""Modelo para templates de tips tech."""
|
||||
|
||||
__tablename__ = "tip_templates"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
|
||||
# Categoría del tip
|
||||
category = Column(String(100), nullable=False, index=True)
|
||||
# Categorías: hardware, software, seguridad, productividad, ia, redes, etc.
|
||||
|
||||
subcategory = Column(String(100), nullable=True)
|
||||
|
||||
# Contenido del tip
|
||||
title = Column(String(255), nullable=False) # Título corto
|
||||
template = Column(Text, nullable=False) # Template con variables
|
||||
|
||||
# Variables que se pueden reemplazar
|
||||
# Ejemplo: ["sistema_operativo", "herramienta", "beneficio"]
|
||||
variables = Column(ARRAY(String), nullable=True)
|
||||
|
||||
# Variaciones del tip (diferentes formas de decirlo)
|
||||
variations = Column(ARRAY(String), nullable=True)
|
||||
|
||||
# Metadatos
|
||||
difficulty = Column(String(50), nullable=True) # basico, intermedio, avanzado
|
||||
target_audience = Column(String(100), nullable=True) # empresas, desarrolladores, general
|
||||
|
||||
# Control de uso
|
||||
used_count = Column(Integer, default=0)
|
||||
last_used = Column(DateTime, nullable=True)
|
||||
|
||||
# Plataformas recomendadas
|
||||
recommended_platforms = Column(ARRAY(String), nullable=True)
|
||||
# Ejemplo: ["x", "threads", "instagram"]
|
||||
|
||||
# Estado
|
||||
is_active = Column(Boolean, default=True)
|
||||
is_evergreen = Column(Boolean, default=True) # ¿Siempre relevante?
|
||||
|
||||
# Timestamps
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<TipTemplate {self.title}>"
|
||||
|
||||
def to_dict(self):
|
||||
"""Convertir a diccionario."""
|
||||
return {
|
||||
"id": self.id,
|
||||
"category": self.category,
|
||||
"subcategory": self.subcategory,
|
||||
"title": self.title,
|
||||
"template": self.template,
|
||||
"variables": self.variables,
|
||||
"variations": self.variations,
|
||||
"difficulty": self.difficulty,
|
||||
"target_audience": self.target_audience,
|
||||
"used_count": self.used_count,
|
||||
"last_used": self.last_used.isoformat() if self.last_used else None,
|
||||
"recommended_platforms": self.recommended_platforms,
|
||||
"is_active": self.is_active,
|
||||
"is_evergreen": self.is_evergreen,
|
||||
"created_at": self.created_at.isoformat() if self.created_at else None
|
||||
}
|
||||
Reference in New Issue
Block a user