feat: sistema de 3 estados (success/warning/error) para envíos TNS

- _parse_tns_resp retorna 'success'|'warning'|'error' en lugar de bool
- 'ya existe/registrado/autorización' → warning (sin reenvío, no es error real)
- errores reales → error (con reenvío)
- Guarda JSON crudo completo en respuesta_api
- logs.html: badge amarillo para warning, verde para success, rojo para error
- Filtro de historial incluye opción 'warning'
- reenvío en logs.py solo disponible para status='error'
- reenvío en logs.py usa misma lógica _parse_tns_resp y soporta ventas

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Lizandro Guarnizo
2026-07-24 10:45:15 -05:00
co-authored by Claude Sonnet 4.6
parent a37c1bb7d6
commit 6a452cd950
3 changed files with 73 additions and 86 deletions
+46 -53
View File
@@ -587,27 +587,22 @@ async def run_automation(
f"{TNS_BASE}/v2/tablas/Tercero/Crear",
json=tercero_json, headers=headers,
)
body = resp.json()
ok = bool(body.get("status")) or bool((body.get("data") or {}).get("success"))
msg = body.get("message") or (body.get("data") or {}).get("response") or resp.text[:200]
# "ya registrado" no es error real — el tercero existe en TNS
if not ok and msg and any(s in msg.lower() for s in ("ya esta registrado", "ya existe", "already exist")):
ok = True
status, msg = _parse_tns_resp(resp)
except Exception as e:
ok = False
msg = str(e)
status, msg = "error", str(e)
if ok:
resultado["paso1_terceros"]["enviados"] += 1
else:
if status == "error":
resultado["paso1_terceros"]["errores"] += 1
terceros_fallidos.add(codigo_pac)
else:
resultado["paso1_terceros"]["enviados"] += 1
resultado["paso1_terceros"]["detalle"].append({
"codigo": codigo_pac, "doc": doc, "nombre": nombre, "ok": ok, "msg": msg,
"codigo": codigo_pac, "doc": doc, "nombre": nombre,
"ok": status != "error", "status": status, "msg": msg,
})
_guardar_envio(user["user_id"], "terceros", fecha, tercero_json, msg, ok,
_guardar_envio(user["user_id"], "terceros", fecha, tercero_json, msg, status,
cedula=str(row.get("DOCIDENT") or "").strip())
# ── Sync paralelo a WhatsApp Lab (silencioso) ─────────────────────────────
@@ -656,24 +651,24 @@ async def run_automation(
factura = num_override or str(id_recepcion)
try:
resp = await client.post(endpoint, json=rda_json, headers=headers)
ok, msg = _parse_tns_resp(resp)
status, msg = _parse_tns_resp(resp)
except Exception as e:
ok, msg = False, str(e)
status, msg = "error", str(e)
if ok:
resultado["paso2_rda"]["enviados"] += 1
else:
if status == "error":
resultado["paso2_rda"]["errores"] += 1
else:
resultado["paso2_rda"]["enviados"] += 1
resultado["paso2_rda"]["detalle"].append({
"idrecepcion": id_recepcion,
"factura": factura,
"paciente": str(grupo_rows[0].get("COD_PACIENTE", "")),
"examenes": len(grupo_rows),
"ok": ok, "msg": msg,
"ok": status != "error", "status": status, "msg": msg,
})
_guardar_envio(user["user_id"], "transaccion", factura, rda_json, msg, ok,
_guardar_envio(user["user_id"], "transaccion", factura, rda_json, msg, status,
fecha_inicio=fecha, fecha_fin=fecha, servicios=len(grupo_rows),
idrecepcion=id_recepcion, contrato=str(grupo_rows[0].get("CODCONTRATO") or "").strip(),
cedula=pac_map.get(str(grupo_rows[0].get("COD_PACIENTE", "")), ""))
@@ -710,24 +705,24 @@ async def run_automation(
factura_ps = f"{ps_prefijo}-{ps_numero.zfill(5)}"
try:
resp = await client.post(endpoint, json=rda_json, headers=headers)
ok, msg = _parse_tns_resp(resp)
status, msg = _parse_tns_resp(resp)
except Exception as e:
ok, msg = False, str(e)
status, msg = "error", str(e)
if ok:
resultado["paso3_preserv"]["enviados"] += 1
else:
if status == "error":
resultado["paso3_preserv"]["errores"] += 1
else:
resultado["paso3_preserv"]["enviados"] += 1
resultado["paso3_preserv"]["detalle"].append({
"idrecepcion": id_ps,
"factura": factura_ps,
"paciente": str(grupo_rows[0].get("COD_PACIENTE", "")),
"examenes": len(grupo_rows),
"ok": ok, "msg": msg,
"ok": status != "error", "status": status, "msg": msg,
})
_guardar_envio(user["user_id"], "transaccion", factura_ps, rda_json, msg, ok,
_guardar_envio(user["user_id"], "transaccion", factura_ps, rda_json, msg, status,
fecha_inicio=fecha, fecha_fin=fecha, servicios=len(grupo_rows),
idrecepcion=id_ps, contrato=str(grupo_rows[0].get("CODCONTRATO") or "").strip(),
cedula=pac_map.get(str(grupo_rows[0].get("COD_PACIENTE", "")), ""))
@@ -752,24 +747,24 @@ async def run_automation(
contrato_vta = str(grupo_rows[0].get("CODCONTRATO") or "").strip()
try:
resp = await client.post(endpoint_venta, json=venta_json, headers=headers)
ok, msg = _parse_tns_resp(resp)
status, msg = _parse_tns_resp(resp)
except Exception as e:
ok, msg = False, str(e)
status, msg = "error", str(e)
if ok:
resultado["paso4_ventas"]["enviados"] += 1
else:
if status == "error":
resultado["paso4_ventas"]["errores"] += 1
else:
resultado["paso4_ventas"]["enviados"] += 1
resultado["paso4_ventas"]["detalle"].append({
"idrecepcion": id_rec,
"factura": factura_display,
"paciente": str(grupo_rows[0].get("COD_PACIENTE", "")),
"examenes": len(grupo_rows),
"ok": ok, "msg": msg,
"ok": status != "error", "status": status, "msg": msg,
})
_guardar_envio(user["user_id"], "ventas", factura_display, venta_json, msg, ok,
_guardar_envio(user["user_id"], "ventas", factura_display, venta_json, msg, status,
fecha_inicio=fecha, fecha_fin=fecha, servicios=len(grupo_rows),
idrecepcion=id_rec, contrato=contrato_vta,
cedula=pac_map.get(str(grupo_rows[0].get("COD_PACIENTE", "")), ""))
@@ -787,36 +782,34 @@ async def run_automation(
return JSONResponse({"success": True, "resultado": resultado})
_YA_EXISTE = ("ya esta registrado", "ya existe", "already exist", "ya existe una autorización")
_YA_WARNING = (
"ya esta registrado", "ya existe", "already exist",
"ya existe una autorización", "ya existe un rda",
)
def _parse_tns_resp(resp) -> tuple[bool, str]:
"""Parsea respuesta TNS: retorna (ok, msg). Trata 'ya existe' como OK."""
def _parse_tns_resp(resp) -> tuple[str, str]:
"""Parsea respuesta TNS. Retorna (status, msg) donde status es 'success'|'warning'|'error'."""
raw_body = resp.text
try:
body = resp.json()
raw_status = body.get("status")
# TNS usa status=true/false (booleano); ASP.NET usa status=400 (entero HTTP) → error
if isinstance(raw_status, bool):
ok = raw_status
elif isinstance(raw_status, int):
ok = raw_status < 400
else:
ok = bool(raw_status) or bool((body.get("data") or {}).get("success"))
# Mensaje: TNS normal vs error de validación ASP.NET
errors = body.get("errors")
if errors and isinstance(errors, dict):
msgs = [f"{k}: {v[0] if isinstance(v, list) else v}" for k, v in errors.items()]
msg = " | ".join(msgs)
else:
msg = body.get("message") or (body.get("data") or {}).get("response") or body.get("title") or resp.text[:200]
msg = raw_body # siempre guardamos el JSON crudo completo
except Exception:
ok = resp.is_success
msg = resp.text[:200]
if not ok and msg and any(s in str(msg).lower() for s in _YA_EXISTE):
ok = True
return ok, str(msg or "")
msg = raw_body[:500]
if not ok and any(s in msg.lower() for s in _YA_WARNING):
return "warning", msg
return ("success" if ok else "error"), msg
def _guardar_envio(user_id, tipo, factura, json_data, respuesta, ok,
def _guardar_envio(user_id, tipo, factura, json_data, respuesta, status,
fecha_inicio=None, fecha_fin=None, servicios=0,
idrecepcion=None, contrato=None, cedula=None):
try:
@@ -830,10 +823,10 @@ def _guardar_envio(user_id, tipo, factura, json_data, respuesta, ok,
user_id, tipo, factura, idrecepcion, contrato, cedula,
fecha_inicio, fecha_fin,
1, servicios,
"success" if ok else "error",
status,
json_lib.dumps(json_data, indent=2, ensure_ascii=False)[:10000],
respuesta[:1000] if respuesta else "",
respuesta[:300] if respuesta else "",
respuesta[:5000] if respuesta else "",
respuesta[:500] if respuesta else "",
datetime.now().isoformat(),
))
conn.commit()