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:
2026-01-28 01:11:44 +00:00
commit 049d2133f9
53 changed files with 5876 additions and 0 deletions

View File

@@ -0,0 +1,226 @@
"""
API Routes para gestión de Interacciones.
"""
from datetime import datetime
from typing import List, Optional
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from pydantic import BaseModel
from app.core.database import get_db
from app.models.interaction import Interaction
router = APIRouter()
# ===========================================
# SCHEMAS
# ===========================================
class InteractionResponse(BaseModel):
content: str
class Config:
from_attributes = True
# ===========================================
# ENDPOINTS
# ===========================================
@router.get("/")
async def list_interactions(
platform: Optional[str] = Query(None),
interaction_type: Optional[str] = Query(None),
responded: Optional[bool] = Query(None),
is_lead: Optional[bool] = Query(None),
is_read: Optional[bool] = Query(None),
limit: int = Query(50, le=100),
offset: int = Query(0),
db: Session = Depends(get_db)
):
"""Listar interacciones con filtros."""
query = db.query(Interaction)
if platform:
query = query.filter(Interaction.platform == platform)
if interaction_type:
query = query.filter(Interaction.interaction_type == interaction_type)
if responded is not None:
query = query.filter(Interaction.responded == responded)
if is_lead is not None:
query = query.filter(Interaction.is_lead == is_lead)
if is_read is not None:
query = query.filter(Interaction.is_read == is_read)
interactions = query.order_by(
Interaction.interaction_at.desc()
).offset(offset).limit(limit).all()
return [i.to_dict() for i in interactions]
@router.get("/pending")
async def list_pending_interactions(db: Session = Depends(get_db)):
"""Listar interacciones sin responder."""
interactions = db.query(Interaction).filter(
Interaction.responded == False,
Interaction.is_archived == False
).order_by(
Interaction.priority.desc(),
Interaction.interaction_at.desc()
).all()
return [i.to_dict() for i in interactions]
@router.get("/leads")
async def list_leads(db: Session = Depends(get_db)):
"""Listar interacciones marcadas como leads."""
interactions = db.query(Interaction).filter(
Interaction.is_lead == True
).order_by(Interaction.interaction_at.desc()).all()
return [i.to_dict() for i in interactions]
@router.get("/stats")
async def get_interaction_stats(db: Session = Depends(get_db)):
"""Obtener estadísticas de interacciones."""
total = db.query(Interaction).count()
pending = db.query(Interaction).filter(
Interaction.responded == False,
Interaction.is_archived == False
).count()
leads = db.query(Interaction).filter(Interaction.is_lead == True).count()
# Por plataforma
by_platform = {}
for platform in ["x", "threads", "instagram", "facebook"]:
by_platform[platform] = db.query(Interaction).filter(
Interaction.platform == platform
).count()
return {
"total": total,
"pending": pending,
"leads": leads,
"by_platform": by_platform
}
@router.get("/{interaction_id}")
async def get_interaction(interaction_id: int, db: Session = Depends(get_db)):
"""Obtener una interacción por ID."""
interaction = db.query(Interaction).filter(Interaction.id == interaction_id).first()
if not interaction:
raise HTTPException(status_code=404, detail="Interacción no encontrada")
return interaction.to_dict()
@router.post("/{interaction_id}/respond")
async def respond_to_interaction(
interaction_id: int,
response_data: InteractionResponse,
db: Session = Depends(get_db)
):
"""Responder a una interacción."""
interaction = db.query(Interaction).filter(Interaction.id == interaction_id).first()
if not interaction:
raise HTTPException(status_code=404, detail="Interacción no encontrada")
# TODO: Enviar respuesta a la plataforma correspondiente
# from app.publishers import get_publisher
# publisher = get_publisher(interaction.platform)
# result = await publisher.reply(interaction.external_id, response_data.content)
interaction.responded = True
interaction.response_content = response_data.content
interaction.responded_at = datetime.utcnow()
db.commit()
return {
"message": "Respuesta guardada",
"interaction_id": interaction_id,
"note": "La publicación a la plataforma está en desarrollo"
}
@router.post("/{interaction_id}/suggest-response")
async def suggest_response(interaction_id: int, db: Session = Depends(get_db)):
"""Generar sugerencia de respuesta con IA."""
interaction = db.query(Interaction).filter(Interaction.id == interaction_id).first()
if not interaction:
raise HTTPException(status_code=404, detail="Interacción no encontrada")
# TODO: Implementar generación con DeepSeek
# from app.services.content_generator import generate_response_suggestion
# suggestions = await generate_response_suggestion(interaction)
# Respuestas placeholder
suggestions = [
f"¡Gracias por tu comentario! Nos da gusto que te interese nuestro contenido.",
f"¡Hola! Gracias por escribirnos. ¿En qué podemos ayudarte?",
f"¡Excelente pregunta! Te respondemos por DM para darte más detalles."
]
return {
"suggestions": suggestions,
"note": "La generación con IA está en desarrollo"
}
@router.post("/{interaction_id}/mark-as-lead")
async def mark_as_lead(interaction_id: int, db: Session = Depends(get_db)):
"""Marcar interacción como lead potencial."""
interaction = db.query(Interaction).filter(Interaction.id == interaction_id).first()
if not interaction:
raise HTTPException(status_code=404, detail="Interacción no encontrada")
interaction.is_lead = not interaction.is_lead
db.commit()
return {
"message": f"{'Marcado' if interaction.is_lead else 'Desmarcado'} como lead",
"is_lead": interaction.is_lead
}
@router.post("/{interaction_id}/mark-as-read")
async def mark_as_read(interaction_id: int, db: Session = Depends(get_db)):
"""Marcar interacción como leída."""
interaction = db.query(Interaction).filter(Interaction.id == interaction_id).first()
if not interaction:
raise HTTPException(status_code=404, detail="Interacción no encontrada")
interaction.is_read = True
db.commit()
return {"message": "Marcado como leído", "interaction_id": interaction_id}
@router.post("/{interaction_id}/archive")
async def archive_interaction(interaction_id: int, db: Session = Depends(get_db)):
"""Archivar una interacción."""
interaction = db.query(Interaction).filter(Interaction.id == interaction_id).first()
if not interaction:
raise HTTPException(status_code=404, detail="Interacción no encontrada")
interaction.is_archived = True
db.commit()
return {"message": "Interacción archivada", "interaction_id": interaction_id}
@router.delete("/{interaction_id}")
async def delete_interaction(interaction_id: int, db: Session = Depends(get_db)):
"""Eliminar una interacción."""
interaction = db.query(Interaction).filter(Interaction.id == interaction_id).first()
if not interaction:
raise HTTPException(status_code=404, detail="Interacción no encontrada")
db.delete(interaction)
db.commit()
return {"message": "Interacción eliminada", "interaction_id": interaction_id}