Actualizar backend/main.py
This commit is contained in:
178
backend/main.py
178
backend/main.py
@@ -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"),
|
||||
|
||||
Reference in New Issue
Block a user