Files
social-media-automation/app/api/routes/dashboard.py
Consultoría AS ecc2ca73ea feat: Add Analytics, Odoo Integration, A/B Testing, and Content features
Phase 1 - Analytics y Reportes:
- PostMetrics and AnalyticsReport models for tracking engagement
- Analytics service with dashboard stats, top posts, optimal times
- 8 API endpoints at /api/analytics/*
- Interactive dashboard with Chart.js charts
- Celery tasks for metrics fetch (15min) and weekly reports

Phase 2 - Integración Odoo:
- Lead and OdooSyncLog models for CRM integration
- Odoo fields added to Product and Service models
- XML-RPC service for bidirectional sync
- Lead management API at /api/leads/*
- Leads dashboard template
- Celery tasks for product/service sync and lead export

Phase 3 - A/B Testing y Recycling:
- ABTest, ABTestVariant, RecycledPost models
- Statistical winner analysis using chi-square test
- Content recycling with engagement-based scoring
- APIs at /api/ab-tests/* and /api/recycling/*
- Automated test evaluation and content recycling tasks

Phase 4 - Thread Series y Templates:
- ThreadSeries and ThreadPost models for multi-post threads
- AI-powered thread generation
- Enhanced ImageTemplate with HTML template support
- APIs at /api/threads/* and /api/templates/*
- Thread scheduling with reply chain support

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 03:10:42 +00:00

209 lines
6.7 KiB
Python

"""
API Routes para el Dashboard.
"""
from datetime import datetime, timedelta
from typing import Optional
from fastapi import APIRouter, Depends, Request
from fastapi.responses import HTMLResponse, RedirectResponse
from fastapi.templating import Jinja2Templates
from sqlalchemy.orm import Session
from sqlalchemy import func
from app.core.database import get_db
from app.models.post import Post
from app.models.interaction import Interaction
from app.models.user import User
from app.api.routes.auth import get_current_user_from_cookie
router = APIRouter()
templates = Jinja2Templates(directory="dashboard/templates")
def require_auth(request: Request, db: Session) -> Optional[User]:
"""Verificar autenticación. Retorna None si no está autenticado."""
return get_current_user_from_cookie(request, db)
@router.get("/", response_class=HTMLResponse)
async def dashboard_home(request: Request, db: Session = Depends(get_db)):
"""Página principal del dashboard."""
user = require_auth(request, db)
if not user:
return RedirectResponse(url="/login", status_code=302)
# Estadísticas
now = datetime.utcnow()
today_start = now.replace(hour=0, minute=0, second=0, microsecond=0)
week_start = today_start - timedelta(days=now.weekday())
stats = {
"posts_today": db.query(Post).filter(
Post.published_at >= today_start
).count(),
"posts_week": db.query(Post).filter(
Post.published_at >= week_start
).count(),
"pending_approval": db.query(Post).filter(
Post.status == "pending_approval"
).count(),
"scheduled": db.query(Post).filter(
Post.status == "scheduled"
).count(),
"interactions_pending": db.query(Interaction).filter(
Interaction.responded == False,
Interaction.is_archived == False
).count()
}
# Posts pendientes
pending_posts = db.query(Post).filter(
Post.status == "pending_approval"
).order_by(Post.scheduled_at.asc()).limit(5).all()
# Próximas publicaciones
scheduled_posts = db.query(Post).filter(
Post.status == "scheduled",
Post.scheduled_at >= now
).order_by(Post.scheduled_at.asc()).limit(5).all()
# Interacciones recientes
recent_interactions = db.query(Interaction).filter(
Interaction.responded == False
).order_by(Interaction.interaction_at.desc()).limit(5).all()
return templates.TemplateResponse("index.html", {
"request": request,
"user": user.to_dict(),
"stats": stats,
"pending_posts": [p.to_dict() for p in pending_posts],
"scheduled_posts": [p.to_dict() for p in scheduled_posts],
"recent_interactions": [i.to_dict() for i in recent_interactions]
})
@router.get("/compose", response_class=HTMLResponse)
async def dashboard_compose(request: Request, db: Session = Depends(get_db)):
"""Página para crear nuevas publicaciones."""
user = require_auth(request, db)
if not user:
return RedirectResponse(url="/login", status_code=302)
return templates.TemplateResponse("compose.html", {
"request": request,
"user": user.to_dict()
})
@router.get("/posts", response_class=HTMLResponse)
async def dashboard_posts(request: Request, db: Session = Depends(get_db)):
"""Página de gestión de posts."""
user = require_auth(request, db)
if not user:
return RedirectResponse(url="/login", status_code=302)
posts = db.query(Post).order_by(Post.created_at.desc()).limit(50).all()
return templates.TemplateResponse("posts.html", {
"request": request,
"user": user.to_dict(),
"posts": [p.to_dict() for p in posts]
})
@router.get("/calendar", response_class=HTMLResponse)
async def dashboard_calendar(request: Request, db: Session = Depends(get_db)):
"""Página de calendario."""
user = require_auth(request, db)
if not user:
return RedirectResponse(url="/login", status_code=302)
return templates.TemplateResponse("calendar.html", {
"request": request,
"user": user.to_dict()
})
@router.get("/interactions", response_class=HTMLResponse)
async def dashboard_interactions(request: Request, db: Session = Depends(get_db)):
"""Página de interacciones."""
user = require_auth(request, db)
if not user:
return RedirectResponse(url="/login", status_code=302)
interactions = db.query(Interaction).filter(
Interaction.is_archived == False
).order_by(Interaction.interaction_at.desc()).limit(50).all()
return templates.TemplateResponse("interactions.html", {
"request": request,
"user": user.to_dict(),
"interactions": [i.to_dict() for i in interactions]
})
@router.get("/products", response_class=HTMLResponse)
async def dashboard_products(request: Request, db: Session = Depends(get_db)):
"""Página de productos."""
user = require_auth(request, db)
if not user:
return RedirectResponse(url="/login", status_code=302)
return templates.TemplateResponse("products.html", {
"request": request,
"user": user.to_dict()
})
@router.get("/services", response_class=HTMLResponse)
async def dashboard_services(request: Request, db: Session = Depends(get_db)):
"""Página de servicios."""
user = require_auth(request, db)
if not user:
return RedirectResponse(url="/login", status_code=302)
return templates.TemplateResponse("services.html", {
"request": request,
"user": user.to_dict()
})
@router.get("/settings", response_class=HTMLResponse)
async def dashboard_settings(request: Request, db: Session = Depends(get_db)):
"""Página de configuración."""
user = require_auth(request, db)
if not user:
return RedirectResponse(url="/login", status_code=302)
return templates.TemplateResponse("settings.html", {
"request": request,
"user": user.to_dict()
})
@router.get("/analytics", response_class=HTMLResponse)
async def dashboard_analytics(request: Request, db: Session = Depends(get_db)):
"""Página de analytics."""
user = require_auth(request, db)
if not user:
return RedirectResponse(url="/login", status_code=302)
return templates.TemplateResponse("analytics.html", {
"request": request,
"user": user.to_dict()
})
@router.get("/leads", response_class=HTMLResponse)
async def dashboard_leads(request: Request, db: Session = Depends(get_db)):
"""Página de leads."""
user = require_auth(request, db)
if not user:
return RedirectResponse(url="/login", status_code=302)
return templates.TemplateResponse("leads.html", {
"request": request,
"user": user.to_dict()
})