feat(pacientes): sync de pacientes Firebird → WhatsApp Lab
- Nuevo servicio whatsapp_sync.py: mapeo de campos RIPS→lab_pacientes y cliente HTTP para /api/lab/ingest_paciente.php - Nueva ruta /pacientes con sync masiva por rango de fechas o todos - Hook en /terceros/send y /automation/run: sync silencioso a WhatsApp después de cada envío a TNS - Config: campos whatsapp_url y whatsapp_api_key + botón probar conexión Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
b4288ac899
commit
e7c6bc794a
@@ -0,0 +1,115 @@
|
||||
"""
|
||||
Servicio de sincronización de pacientes RIPS → WhatsApp Lab.
|
||||
Mapea campos Firebird al formato esperado por /api/lab/ingest_paciente.php.
|
||||
"""
|
||||
|
||||
import re
|
||||
import httpx
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
_TIPO_DOC_MAP = {
|
||||
"CC": "CC", "TI": "TI", "RC": "RC", "CE": "CE",
|
||||
"PA": "PA", "NIT": "NIT", "MS": "MS", "AS": "CC",
|
||||
"SI": "CC", "CN": "CC", "DE": "CE", "CD": "PA",
|
||||
"PE": "CE", "PT": "PA",
|
||||
}
|
||||
|
||||
_SEXO_MAP = {"M": "M", "F": "F", "H": "M"}
|
||||
|
||||
|
||||
def _fmt_fecha_iso(val) -> Optional[str]:
|
||||
if not val:
|
||||
return None
|
||||
if hasattr(val, "strftime"):
|
||||
return val.strftime("%Y-%m-%d")
|
||||
s = str(val)[:10]
|
||||
if re.match(r"\d{4}-\d{2}-\d{2}", s):
|
||||
return s
|
||||
# dd/mm/yyyy
|
||||
parts = s.split("/")
|
||||
if len(parts) == 3:
|
||||
return f"{parts[2]}-{parts[1]}-{parts[0]}"
|
||||
return None
|
||||
|
||||
|
||||
def mapear_paciente(row: dict) -> dict:
|
||||
"""Convierte una fila de Firebird PACIENTE al body de ingest_paciente.php."""
|
||||
nombres = (row.get("NOMBRES") or "").strip()
|
||||
apellidos = (row.get("APELLIDOS") or "").strip()
|
||||
nombre_completo = f"{nombres} {apellidos}".strip().upper()
|
||||
|
||||
tipo_raw = str(row.get("TIPOIDENT") or "CC").strip()
|
||||
tipo_doc = _TIPO_DOC_MAP.get(tipo_raw, "CC")
|
||||
|
||||
sexo_raw = str(row.get("SEXO") or "M").strip().upper()
|
||||
genero = _SEXO_MAP.get(sexo_raw, "M")
|
||||
|
||||
email = (row.get("EMAIL") or "").strip().lower()
|
||||
if not email or "@sinregistro" in email:
|
||||
email = ""
|
||||
|
||||
return {
|
||||
"nombre_completo": nombre_completo,
|
||||
"numero_documento": str(row.get("DOCIDENT") or "").strip(),
|
||||
"tipo_documento": tipo_doc,
|
||||
"telefono": str(row.get("TELEFONOS") or "").strip(),
|
||||
"email": email,
|
||||
"fecha_nacimiento": _fmt_fecha_iso(row.get("F_NACIMIENTO")),
|
||||
"genero": genero,
|
||||
"direccion": (row.get("DIRECCION") or "").strip(),
|
||||
"ciudad": (row.get("NOM_CIUDAD") or "").strip(),
|
||||
"origen": "rips",
|
||||
}
|
||||
|
||||
|
||||
async def sync_paciente(row: dict, url: str, api_key: str, client: Optional[httpx.AsyncClient] = None) -> dict:
|
||||
"""Envía un paciente al endpoint de WhatsApp. Retorna {ok, action, message}."""
|
||||
payload = mapear_paciente(row)
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"X-Lab-Key": api_key,
|
||||
}
|
||||
try:
|
||||
if client:
|
||||
resp = await client.post(url, json=payload, headers=headers)
|
||||
else:
|
||||
async with httpx.AsyncClient(timeout=15) as c:
|
||||
resp = await c.post(url, json=payload, headers=headers)
|
||||
|
||||
data = resp.json()
|
||||
return {
|
||||
"ok": data.get("ok", False),
|
||||
"action": data.get("action", ""),
|
||||
"message": data.get("message") or data.get("error", ""),
|
||||
"doc": payload["numero_documento"],
|
||||
"nombre": payload["nombre_completo"],
|
||||
}
|
||||
except Exception as e:
|
||||
return {
|
||||
"ok": False,
|
||||
"action": "error",
|
||||
"message": str(e),
|
||||
"doc": payload.get("numero_documento", ""),
|
||||
"nombre": payload.get("nombre_completo", ""),
|
||||
}
|
||||
|
||||
|
||||
async def sync_todos(rows: list, url: str, api_key: str, timeout: int = 30) -> dict:
|
||||
"""Envía una lista de filas de pacientes. Retorna resumen {total, created, updated, errores}."""
|
||||
resultado = {"total": len(rows), "created": 0, "updated": 0, "errores": 0, "detalle": []}
|
||||
headers = {"Content-Type": "application/json", "X-Lab-Key": api_key}
|
||||
|
||||
async with httpx.AsyncClient(timeout=timeout) as client:
|
||||
for row in rows:
|
||||
r = await sync_paciente(row, url, api_key, client)
|
||||
if r["ok"]:
|
||||
if r["action"] == "created":
|
||||
resultado["created"] += 1
|
||||
else:
|
||||
resultado["updated"] += 1
|
||||
else:
|
||||
resultado["errores"] += 1
|
||||
resultado["detalle"].append(r)
|
||||
|
||||
return resultado
|
||||
Reference in New Issue
Block a user