Files
rips_manager/app/services/scheduler.py
T
Lizandro GuarnizoandClaude Sonnet 4.6 dc18d128fb feat(envios/erp): add manual sync trigger endpoint and test UI
- POST /envios/erp/sync-now?ventana_min=N calls sync_recientes with configurable window
- sync_recientes() now returns a result dict (ok, total, created, updated, skipped, errores, detalle)
- ERP tab shows a "Prueba manual" card with ventana selector (2m / 30m / 2h / hoy) and result breakdown

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-16 21:40:54 -05:00

155 lines
5.9 KiB
Python

"""
Scheduler: sync automático de pacientes con recepción reciente → WhatsApp Lab.
Corre cada minuto y envía solo los pacientes con HORAINICIORECEPCION en los últimos 2 min.
"""
from datetime import datetime, timedelta
from app.database import get_connection
from app.services.firebird_service import get_firebird_from_config
from app.services.whatsapp_sync import sync_todos, guardar_sync_log
_SQL_HOY = """
SELECT DISTINCT
p.CODIGO,
p.TIPOIDENT,
p.DOCIDENT,
p.NOMBRES,
p.APELLIDOS,
p.DIRECCION,
p.CIUDAD AS COD_CIUDAD,
c.NOMBRE AS NOM_CIUDAD,
p.TELEFONOS,
p.EMAIL,
p.F_NACIMIENTO,
p.SEXO,
p.TIPORES,
p.CODETNIA,
r.HORAINICIORECEPCION
FROM PACIENTE p
LEFT JOIN CIUDAD c ON c.CODIGO = p.CIUDAD
JOIN RECEPCION r ON r.COD_PACIENTE = p.CODIGO
WHERE r.FECHA_RECEPCION = CURRENT_DATE
AND r.NUM_FACTURA > 0
"""
_SQL_EXAMENES_HOY = """
SELECT
TRIM(p.DOCIDENT) AS DOCIDENT,
r.IDRECEPCION,
r.HORAINICIORECEPCION,
TRIM(rel.COD_EXAMEN) AS COD_EXAMEN,
TRIM(ex.NOMBRE) AS NOM_EXAMEN,
COALESCE(NULLIF(TRIM(ex.NUM_ISS), ''), TRIM(rel.COD_EXAMEN)) AS CUPS,
rel.PRECIO,
TRIM(r.DIAG_PPAL) AS DIAG_PPAL,
TRIM(d.CONCEPTO) AS DIAG_CONCEPTO,
TRIM(m.DOCIDMEDICO) AS DOCIDMEDICO,
TRIM(r.NIT_EMPRESA) AS NIT_EMPRESA,
r.VALORTOTAL
FROM RECEPCION r
JOIN RELACION rel ON rel.IDRECEPCION = r.IDRECEPCION
LEFT JOIN EXAMEN ex ON TRIM(ex.CODIGO) = TRIM(rel.COD_EXAMEN)
LEFT JOIN DIAGNOSTICO d ON TRIM(d.COD_DIAG) = TRIM(r.DIAG_PPAL)
LEFT JOIN MEDICO m ON m.CODIGO = r.COD_MEDICO
JOIN PACIENTE p ON p.CODIGO = r.COD_PACIENTE
WHERE r.FECHA_RECEPCION = CURRENT_DATE
AND r.NUM_FACTURA > 0
ORDER BY r.IDRECEPCION
"""
def _parse_hora(val) -> datetime | None:
if not val:
return None
s = str(val)
try:
if "T" in s:
dt = datetime.fromisoformat(s)
ahora = datetime.now()
return ahora.replace(hour=dt.hour, minute=dt.minute, second=dt.second, microsecond=0)
partes = s.split(":")
ahora = datetime.now()
return ahora.replace(hour=int(partes[0]), minute=int(partes[1]),
second=int(partes[2].split(".")[0]), microsecond=0)
except Exception:
return None
async def sync_recientes(ventana_min: int = 2) -> dict:
"""Job que corre cada minuto: sincroniza pacientes con recepción en los últimos `ventana_min` min."""
conn = get_connection()
configs = {row["key"]: row["value"] for row in conn.execute("SELECT * FROM config").fetchall()}
conn.close()
wa_url = configs.get("whatsapp_url", "").rstrip("/")
wa_key = configs.get("whatsapp_api_key", "")
if not wa_url or not wa_key:
return {"ok": False, "error": "whatsapp_url o whatsapp_api_key no configurados"}
fb, ok, msg = get_firebird_from_config(configs)
if not ok:
return {"ok": False, "error": f"Firebird: {msg}"}
ok_pac, err_pac, rows_pac = fb.execute_query(_SQL_HOY, None)
ok_ex, _, rows_ex = fb.execute_query(_SQL_EXAMENES_HOY, None)
fb.disconnect()
if not ok_pac:
return {"ok": False, "error": f"Query pacientes: {err_pac}"}
total_hoy = len(rows_pac) if rows_pac else 0
limite = datetime.now() - timedelta(minutes=ventana_min)
recientes = [
row for row in (rows_pac or [])
if (h := _parse_hora(row.get("HORAINICIORECEPCION"))) is None or h >= limite
]
if not recientes:
return {"ok": True, "total": 0, "created": 0, "updated": 0,
"skipped": 0, "errores": 0, "total_hoy": total_hoy,
"ventana_min": ventana_min, "mensaje": "Sin recepciones en la ventana de tiempo"}
examenes_map: dict[str, list] = {}
if ok_ex and rows_ex:
for ex in rows_ex:
h = _parse_hora(ex.get("HORAINICIORECEPCION"))
if h is not None and h < limite:
continue
doc = str(ex.get("DOCIDENT") or "").strip()
if not doc:
continue
examenes_map.setdefault(doc, []).append({
"cod_examen": (ex.get("COD_EXAMEN") or "").strip(),
"nombre": (ex.get("NOM_EXAMEN") or "").strip(),
"cups": (ex.get("CUPS") or "").strip(),
"precio": ex.get("PRECIO"),
"recepcion_id": ex.get("IDRECEPCION"),
"hora": str(ex.get("HORAINICIORECEPCION") or "")[:8],
"diagnostico_cod": (ex.get("DIAG_PPAL") or "").strip(),
"diagnostico_nombre": (ex.get("DIAG_CONCEPTO") or "").strip(),
"medico_docidmedico": (ex.get("DOCIDMEDICO") or "").strip(),
"nit_empresa": (ex.get("NIT_EMPRESA") or "").strip(),
"valor_total": ex.get("VALORTOTAL"),
})
ingest_url = f"{wa_url}/api/lab/ingest_paciente.php"
timeout = int(configs.get("api_timeout", 30))
resultado = await sync_todos(recientes, ingest_url, wa_key, timeout,
modo="upsert", examenes_map=examenes_map)
if resultado["total"] > 0:
guardar_sync_log(resultado, 0, origen="scheduler", modo="upsert")
return {
"ok": True,
"total": resultado["total"],
"created": resultado["created"],
"updated": resultado["updated"],
"skipped": resultado["skipped"],
"errores": resultado["errores"],
"total_hoy": total_hoy,
"ventana_min": ventana_min,
"detalle": resultado.get("detalle", []),
}