""" Servicio de sincronización de pacientes RIPS → WhatsApp Lab. Mapea campos Firebird al formato esperado por /api/lab/ingest_paciente.php. """ import re import json import httpx from datetime import datetime from typing import Optional from app.database import get_connection def guardar_sync_log( resultado: dict, user_id: int, origen: str = "manual", modo: str = "insertar", ) -> None: errores_det = None errores = [d for d in resultado.get("detalle", []) if not d.get("ok")] if errores: errores_det = json.dumps( [{"doc": e["doc"], "nombre": e["nombre"], "msg": e["message"]} for e in errores], ensure_ascii=False, ) conn = get_connection() conn.execute( """INSERT INTO sync_wa_log (user_id, origen, modo, total, created, skipped, updated, errores, errores_det) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)""", ( user_id, origen, modo, resultado.get("total", 0), resultado.get("created", 0), resultado.get("skipped", 0), resultado.get("updated", 0), resultado.get("errores", 0), errores_det, ), ) conn.commit() conn.close() _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": "lab", } async def sync_paciente( row: dict, url: str, api_key: str, client: Optional[httpx.AsyncClient] = None, modo: str = "upsert", ) -> dict: """Envía un paciente al endpoint de WhatsApp. Retorna {ok, action, message}.""" payload = mapear_paciente(row) payload["modo"] = modo 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, modo: str = "upsert") -> dict: """Envía una lista de filas de pacientes. Retorna resumen {total, created, skipped, updated, errores}.""" resultado = {"total": len(rows), "created": 0, "skipped": 0, "updated": 0, "errores": 0, "detalle": []} async with httpx.AsyncClient(timeout=timeout) as client: for row in rows: r = await sync_paciente(row, url, api_key, client, modo) if r["ok"]: action = r["action"] if action == "created": resultado["created"] += 1 elif action == "skipped": resultado["skipped"] += 1 else: resultado["updated"] += 1 else: resultado["errores"] += 1 resultado["detalle"].append(r) return resultado