Actualizar backend/main.py

This commit is contained in:
2026-09-19 11:01:54 -05:00
parent 8c566220ed
commit 238f0c4d4e

View File

@@ -5,28 +5,14 @@ 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=["*"],
CORSMiddleware, allow_origins=["*"], allow_credentials=True,
allow_methods=["*"], allow_headers=["*"],
)
@@ -49,7 +35,7 @@ def _startup():
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=1800)
start_background_refresh(services.refrescar_todo, interval=1800) # 30 min (con horario)
@app.get("/api/health")
@@ -145,6 +131,164 @@ def get_alertas_dash():
raise HTTPException(status_code=500, detail=str(e))
@app.get("/api/plantillas")
def get_plantillas():
"""Lista las plantillas de WhatsApp del inbox de Chatwoot (nombre + categoría)."""
import os, requests
CHATWOOT_URL = os.getenv("CHATWOOT_URL", "https://gestor.escueladerefrigeracion.edu.pe")
TOKEN = os.getenv("CHATWOOT_TOKEN", "4anazHvZnvKLtup8biu5Zuoh")
ACCOUNT_ID = os.getenv("CHATWOOT_ACCOUNT_ID", "1")
INBOX_ID = os.getenv("CHATWOOT_INBOX_ID", "8")
headers = {"api_access_token": TOKEN, "Content-Type": "application/json"}
try:
r = requests.get(f"{CHATWOOT_URL}/api/v1/accounts/{ACCOUNT_ID}/inboxes/{INBOX_ID}",
headers=headers, timeout=15)
r.raise_for_status()
inbox = r.json()
tpls = inbox.get("message_templates") or []
out = []
for t in tpls:
# El texto está en el componente tipo BODY (components[].text).
texto = ""
for comp in (t.get("components") or []):
if str(comp.get("type", "")).upper() == "BODY":
texto = comp.get("text", "") or ""
break
out.append({
"nombre": t.get("name", ""),
"categoria": t.get("category", ""),
"texto": texto,
})
out.sort(key=lambda x: x["nombre"])
return {"plantillas": out}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.post("/api/plantillas/enviar")
def post_plantillas_enviar(payload: dict = Body(...)):
"""Envía una plantilla de WhatsApp (Chatwoot) a una lista de números.
payload: {nombre, categoria, idioma, texto, telefonos:[...]}.
Solo se envía a números peruanos con código 51 (formato 51XXXXXXXXX)."""
import os, requests, time
CHATWOOT_URL = os.getenv("CHATWOOT_URL", "https://gestor.escueladerefrigeracion.edu.pe")
TOKEN = os.getenv("CHATWOOT_TOKEN", "4anazHvZnvKLtup8biu5Zuoh")
ACCOUNT_ID = os.getenv("CHATWOOT_ACCOUNT_ID", "1")
INBOX_ID = int(os.getenv("CHATWOOT_INBOX_ID", "8"))
headers = {"api_access_token": TOKEN, "Content-Type": "application/json"}
nombre = payload.get("nombre", "")
categoria = payload.get("categoria", "MARKETING")
idioma = payload.get("idioma", "es_PE")
texto = payload.get("texto", "")
telefonos_raw = payload.get("telefonos", []) or []
def ok(s): return s in (200, 201)
def normaliza(v):
s = str(v or "").strip()
if s.endswith(".0"): s = s[:-2]
for x in ("+", " ", "-", "(", ")"): s = s.replace(x, "")
return s
# Validar: debe empezar con 51 y tener 11 dígitos (51 + 9)
validos, invalidos = [], []
for t in telefonos_raw:
s = normaliza(t)
if s.isdigit() and s.startswith("51") and len(s) == 11:
validos.append(s)
else:
invalidos.append(str(t))
def chat_id(tel):
tel_p = "+" + tel
try:
r = requests.get(f"{CHATWOOT_URL}/api/v1/accounts/{ACCOUNT_ID}/contacts/search",
headers=headers, params={"q": tel_p}, timeout=20)
data = r.json()
except Exception:
return None
cid = None
if ok(r.status_code) and data.get("payload"):
cid = data["payload"][0]["id"]
else:
rc = requests.post(f"{CHATWOOT_URL}/api/v1/accounts/{ACCOUNT_ID}/contacts",
headers=headers, json={"phone_number": tel_p, "inbox_id": INBOX_ID}, timeout=20)
if ok(rc.status_code):
cid = rc.json()["payload"]["contact"]["id"]
else:
return None
rconv = requests.get(f"{CHATWOOT_URL}/api/v1/accounts/{ACCOUNT_ID}/contacts/{cid}/conversations",
headers=headers, timeout=20)
for conv in rconv.json().get("payload", []):
if conv.get("inbox_id") == INBOX_ID:
return conv["id"]
rn = requests.post(f"{CHATWOOT_URL}/api/v1/accounts/{ACCOUNT_ID}/conversations",
headers=headers, json={"source_id": str(cid), "inbox_id": INBOX_ID, "contact_id": cid}, timeout=20)
return rn.json().get("id") if ok(rn.status_code) else None
enviados, fallidos = 0, []
for tel in validos:
cid = chat_id(tel)
if not cid:
fallidos.append(tel); continue
body = {"content": texto, "private": False,
"template_params": {"name": nombre, "category": categoria,
"language": idioma, "processed_params": {}}}
try:
r = requests.post(f"{CHATWOOT_URL}/api/v1/accounts/{ACCOUNT_ID}/conversations/{cid}/messages",
headers=headers, json=body, timeout=30)
if ok(r.status_code):
enviados += 1
else:
fallidos.append(tel)
except Exception:
fallidos.append(tel)
time.sleep(1)
return {"status": "ok", "enviados": enviados,
"fallidos": fallidos, "invalidos": invalidos,
"total": len(telefonos_raw)}
@app.get("/api/plantillas/campanias")
def get_plantillas_campanias():
"""Lista las campañas (código + frase) para el checklist."""
try:
return services.plantillas_campanias()
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.get("/api/plantillas/asesores")
def get_plantillas_asesores():
"""Lista los asesores (para el checklist de filtro)."""
try:
return services.plantillas_asesores()
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.get("/api/plantillas/estados")
def get_plantillas_estados():
"""Lista los estados/objeción (para el checklist de filtro)."""
try:
return services.plantillas_estados()
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.post("/api/plantillas/base")
def post_plantillas_base(payload: dict = Body(...)):
"""Devuelve la base de números (teléfono + 2 fechas + asesor + estado) de los
códigos elegidos, filtrando por asesores ('__VACIO__' = sin asesor) y estados."""
try:
return services.plantillas_base_por_codigos(
payload.get("codigos", []), payload.get("asesores"), payload.get("estados"))
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.get("/api/roas")
def get_roas(
ano: str = Query("TODOS"),