Dashboard Leads - version limpia para servidor (.env y config de despliegue restaurados)
This commit is contained in:
289
backend/main.py
Normal file
289
backend/main.py
Normal file
@@ -0,0 +1,289 @@
|
||||
# backend/main.py
|
||||
"""API REST del Dashboard de LEADS (FastAPI)."""
|
||||
from fastapi import FastAPI, Query, HTTPException, Body
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from datetime import datetime
|
||||
import uvicorn
|
||||
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Cargar variables de entorno desde .env
|
||||
load_dotenv()
|
||||
|
||||
import services
|
||||
from cache_manager import start_background_refresh, cache_stats
|
||||
|
||||
app = FastAPI(title="Dashboard Leads API", version="1.0")
|
||||
|
||||
|
||||
# Orígenes CORS configurables (por defecto permite todo o el dominio configurado)
|
||||
cors_env = os.getenv("CORS_ORIGINS", "*")
|
||||
origins = [o.strip() for o in cors_env.split(",") if o.strip()] if cors_env != "*" else ["*"]
|
||||
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=origins if origins else ["*"],
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
|
||||
@app.on_event("startup")
|
||||
def _startup():
|
||||
# Precarga en SEGUNDO PLANO (hilo aparte) para no bloquear el arranque del server.
|
||||
import threading
|
||||
def _precarga_bg():
|
||||
print("[startup] Precargando datos de Leads (2do plano)...")
|
||||
try:
|
||||
services.precargar_todo()
|
||||
print("[startup] Precarga completa.")
|
||||
except Exception as e:
|
||||
print(f"[startup] Precarga falló: {e}")
|
||||
threading.Thread(target=_precarga_bg, daemon=True).start()
|
||||
# Otros General en un hilo aparte (sus consultas son mas pesadas)
|
||||
def _precarga_otros_bg():
|
||||
try:
|
||||
services.precargar_otros_general()
|
||||
except Exception as e:
|
||||
print(f"[startup] Precarga Otros falló: {e}")
|
||||
threading.Thread(target=_precarga_otros_bg, daemon=True).start()
|
||||
start_background_refresh(services.refrescar_todo, interval=900)
|
||||
|
||||
|
||||
@app.get("/api/health")
|
||||
def health():
|
||||
return {"status": "ok", "hora": datetime.now().isoformat()}
|
||||
|
||||
|
||||
@app.get("/api/cache/stats")
|
||||
def stats():
|
||||
return cache_stats()
|
||||
|
||||
|
||||
@app.post("/api/cache/refresh")
|
||||
def refresh():
|
||||
services.refrescar_todo()
|
||||
return {"status": "refrescado"}
|
||||
|
||||
|
||||
@app.get("/api/leads/filtros")
|
||||
def get_filtros():
|
||||
try:
|
||||
return services.opciones_filtros()
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@app.get("/api/leads")
|
||||
def get_leads(
|
||||
ano: str = Query("TODOS"),
|
||||
mes: str = Query("TODOS"),
|
||||
dia: str = Query("TODOS"),
|
||||
programa: str = Query("TODOS"),
|
||||
sede: str = Query("TODOS"),
|
||||
):
|
||||
try:
|
||||
return services.leads_dashboard(ano, mes, dia, programa, sede)
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@app.get("/api/vendedores")
|
||||
def get_vendedores(
|
||||
ano: str = Query("TODOS"),
|
||||
mes: str = Query("TODOS"),
|
||||
dia: str = Query("TODOS"),
|
||||
):
|
||||
try:
|
||||
return services.vendedores_dashboard(ano, mes, dia)
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@app.get("/api/roas")
|
||||
def get_roas(
|
||||
ano: str = Query("TODOS"),
|
||||
mes: str = Query("TODOS"),
|
||||
dia: str = Query("TODOS"),
|
||||
):
|
||||
try:
|
||||
return services.roas_dashboard(ano, mes, dia)
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@app.get("/api/conjuntos-sin-sede")
|
||||
def get_conjuntos_sin_sede():
|
||||
try:
|
||||
return services.conjuntos_sin_sede()
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@app.post("/api/conjuntos-sin-sede/guardar")
|
||||
def post_conjunto_sede(body: dict = Body(...)):
|
||||
try:
|
||||
cambios = body.get("cambios") or []
|
||||
return services.guardar_conjunto_sede(cambios)
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@app.post("/api/conjuntos-sin-sede/borrar")
|
||||
def post_borrar_conjunto_sede(body: dict = Body(...)):
|
||||
try:
|
||||
return services.borrar_conjunto_sede(body.get("conjunto") or "")
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@app.get("/api/otros-general")
|
||||
def get_otros_general(
|
||||
ano: str = Query("TODOS"),
|
||||
mes: str = Query("TODOS"),
|
||||
dia: str = Query("TODOS"),
|
||||
):
|
||||
try:
|
||||
return services.otros_general(ano, mes, dia)
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@app.get("/api/conjuntos-sin-pauta")
|
||||
def get_conjuntos_sin_pauta():
|
||||
try:
|
||||
return {"conjuntos": services.conjuntos_sin_pauta()}
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@app.get("/api/programas-ocultos")
|
||||
def get_programas_ocultos():
|
||||
try:
|
||||
return services.programas_ocultos()
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@app.post("/api/programas-ocultos/encender")
|
||||
def post_encender_programas(body: dict = Body(...)):
|
||||
try:
|
||||
nis = body.get("num_indices") or []
|
||||
return services.encender_programas(nis)
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@app.get("/api/programas-disponibles")
|
||||
def get_programas_disponibles():
|
||||
try:
|
||||
return services.programas_disponibles()
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@app.get("/api/leyenda-anuncios")
|
||||
def get_leyenda():
|
||||
try:
|
||||
return services.leyenda_anuncios()
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@app.post("/api/leyenda-anuncios/guardar")
|
||||
def post_leyenda(body: dict = Body(...)):
|
||||
try:
|
||||
cambios = body.get("cambios") or []
|
||||
return services.guardar_leyenda(cambios)
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
# ── LEYENDA (campanias de Supabase): listar / agregar / borrar ──
|
||||
@app.get("/api/campanias")
|
||||
def get_campanias():
|
||||
try:
|
||||
return {"filas": services.campanias_listar()}
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@app.post("/api/campanias/agregar")
|
||||
def post_campania(body: dict = Body(...)):
|
||||
try:
|
||||
return services.campania_agregar(body or {})
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@app.post("/api/campanias/editar")
|
||||
def post_editar_campania(body: dict = Body(...)):
|
||||
try:
|
||||
cid = body.get("id")
|
||||
return services.campania_editar(cid, body or {})
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@app.post("/api/campanias/borrar")
|
||||
def post_borrar_campania(body: dict = Body(...)):
|
||||
try:
|
||||
return services.campania_borrar(body.get("id"))
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@app.get("/api/pauta/uso")
|
||||
def get_uso_pauta(pauta: str = Query(...), excluir: str = Query("")):
|
||||
try:
|
||||
return services.uso_de_pauta(pauta, excluir)
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@app.post("/api/curso/guardar-pauta")
|
||||
def post_guardar_pauta(body: dict = Body(...)):
|
||||
try:
|
||||
ni = str(body.get("num_indice", "")).strip()
|
||||
pa = str(body.get("pauta", "")).strip()
|
||||
cj = body.get("conjunto") or None
|
||||
contar = body.get("contar") # "SI" / "NO" / None
|
||||
if not ni:
|
||||
raise HTTPException(status_code=400, detail="num_indice es obligatorio")
|
||||
return services.guardar_edicion_curso(ni, pa, cj, contar)
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@app.get("/api/ultima-actualizacion")
|
||||
def get_ultima_actualizacion():
|
||||
try:
|
||||
return services.ultima_actualizacion()
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@app.get("/api/leads/alertas")
|
||||
def get_alertas():
|
||||
"""Valores de programa/sede en cartera_junta que NO están en el diccionario."""
|
||||
try:
|
||||
return services.alertas()
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@app.get("/api/debug/cursos")
|
||||
def debug_cursos(ano: str = "2026", mes: str = "1"):
|
||||
"""Diagnóstico: lista cursos del periodo con inscritos, tipo y meta."""
|
||||
try:
|
||||
return services.debug_cursos(ano, mes)
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
uvicorn.run("main:app", host="0.0.0.0", port=8001, reload=False)
|
||||
Reference in New Issue
Block a user