feat(pacientes): modo insertar en sync-all — solo crea nuevos, omite existentes

sync_todos() ahora acepta modo='insertar'|'upsert'. La ruta /pacientes/sync-all
usa insertar: skippea cédulas que ya están en lab_pacientes. Los hooks
automáticos (terceros/automation) siguen en upsert.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Lizandro Guarnizo
2026-07-09 11:06:14 -05:00
co-authored by Claude Sonnet 4.6
parent e7c6bc794a
commit 1aab14f705
3 changed files with 27 additions and 14 deletions
+4 -4
View File
@@ -117,12 +117,12 @@ async def sync_all(
return JSONResponse({"success": False, "message": "No se encontraron pacientes con esos criterios."})
timeout = int(configs.get("api_timeout", 30))
resultado = await sync_todos(rows, ingest_url, wa_key, timeout)
resultado = await sync_todos(rows, ingest_url, wa_key, timeout, modo="insertar")
resultado["success"] = True
resultado["message"] = (
f"{resultado['total']} pacientes procesados — "
f"{resultado['created']} creados, "
f"{resultado['updated']} actualizados, "
f"{resultado['total']} procesados — "
f"{resultado['created']} nuevos, "
f"{resultado['skipped']} ya existían, "
f"{resultado['errores']} errores."
)
return JSONResponse(resultado)
+16 -7
View File
@@ -63,9 +63,16 @@ def mapear_paciente(row: dict) -> dict:
}
async def sync_paciente(row: dict, url: str, api_key: str, client: Optional[httpx.AsyncClient] = None) -> dict:
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,
@@ -95,17 +102,19 @@ async def sync_paciente(row: dict, url: str, api_key: str, client: Optional[http
}
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 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)
r = await sync_paciente(row, url, api_key, client, modo)
if r["ok"]:
if r["action"] == "created":
action = r["action"]
if action == "created":
resultado["created"] += 1
elif action == "skipped":
resultado["skipped"] += 1
else:
resultado["updated"] += 1
else:
+7 -3
View File
@@ -135,7 +135,7 @@ async function runSync() {
result.innerHTML = `
<div class="bg-green-50 border border-green-200 rounded-lg p-4 space-y-3">
<p class="font-medium text-green-800"><i class="fas fa-check-circle mr-2"></i>${json.message}</p>
<div class="grid grid-cols-3 gap-3 text-center text-sm">
<div class="grid grid-cols-4 gap-3 text-center text-sm">
<div class="bg-white rounded-lg p-3 border border-green-100">
<p class="text-2xl font-bold text-gray-800">${json.total}</p>
<p class="text-gray-500">Total</p>
@@ -145,8 +145,12 @@ async function runSync() {
<p class="text-gray-500">Nuevos</p>
</div>
<div class="bg-white rounded-lg p-3 border border-green-100">
<p class="text-2xl font-bold text-green-600">${json.updated}</p>
<p class="text-gray-500">Actualizados</p>
<p class="text-2xl font-bold text-gray-400">${json.skipped ?? 0}</p>
<p class="text-gray-500">Ya existían</p>
</div>
<div class="bg-white rounded-lg p-3 border border-green-100">
<p class="text-2xl font-bold text-red-500">${json.errores}</p>
<p class="text-gray-500">Errores</p>
</div>
</div>
${json.errores > 0 ? `<p class="text-sm text-red-600"><i class="fas fa-exclamation-triangle mr-1"></i>${json.errores} errores — revisa los detalles abajo.</p>` : ''}