- 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>
227 lines
7.5 KiB
Python
227 lines
7.5 KiB
Python
"""
|
|
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}
|