fix: handle Firebird Decimal and datetime types in JSON serialization

- PRECIO and VALORTOTAL are Decimal from fdb driver; convert via _safe_num()
- HORAINICIORECEPCION is a datetime object; use _fmt_hora_str() for HH:MM:SS
- _parse_hora() now handles datetime objects directly (no str() roundtrip)
- Same fixes applied to pacientes.py exam builder
- sync-now endpoint catches exceptions and returns traceback as JSON

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Lizandro Guarnizo
2026-07-17 09:10:41 -05:00
co-authored by Claude Sonnet 4.6
parent fc18acc761
commit 863e012c12
3 changed files with 65 additions and 12 deletions
+6 -2
View File
@@ -33,5 +33,9 @@ async def erp_sync_now(
ventana_min: int = Query(default=30, ge=1, le=1440),
user: dict = Depends(get_current_user),
):
resultado = await sync_recientes(ventana_min=ventana_min)
return JSONResponse(resultado)
import traceback as tb
try:
resultado = await sync_recientes(ventana_min=ventana_min)
return JSONResponse(resultado)
except Exception as e:
return JSONResponse({"ok": False, "error": str(e), "traceback": tb.format_exc()}, status_code=200)
+25 -5
View File
@@ -113,10 +113,12 @@ async def examenes_paciente(request: Request, cedula: str = ""):
def parse_hora(raw) -> datetime | None:
if not raw:
return None
if isinstance(raw, datetime):
return ahora.replace(hour=raw.hour, minute=raw.minute, second=raw.second, microsecond=0)
s = str(raw)
try:
if "T" in s:
dt = datetime.fromisoformat(s)
if len(s) > 10 and (s[10] == "T" or s[10] == " "):
dt = datetime.fromisoformat(s.replace(" ", "T"))
return ahora.replace(hour=dt.hour, minute=dt.minute, second=dt.second, microsecond=0)
partes = s.split(":")
return ahora.replace(hour=int(partes[0]), minute=int(partes[1]),
@@ -124,6 +126,24 @@ async def examenes_paciente(request: Request, cedula: str = ""):
except Exception:
return None
def safe_num(val):
if val is None:
return None
try:
return float(val)
except Exception:
return None
def fmt_hora_str(val) -> str:
if not val:
return ""
if isinstance(val, datetime):
return val.strftime("%H:%M:%S")
s = str(val)
if len(s) > 10 and (s[10] == "T" or s[10] == " "):
return s[11:19]
return s[:8]
examenes = []
for row in rows:
hora_dt = parse_hora(row.get("HORAINICIORECEPCION"))
@@ -131,16 +151,16 @@ async def examenes_paciente(request: Request, cedula: str = ""):
continue
examenes.append({
"recepcion_id": row.get("IDRECEPCION"),
"hora": row.get("HORAINICIORECEPCION"),
"hora": fmt_hora_str(row.get("HORAINICIORECEPCION")),
"cod_examen": (row.get("COD_EXAMEN") or "").strip(),
"nombre": (row.get("NOM_EXAMEN") or "").strip(),
"cups": (row.get("CUPS") or "").strip(),
"precio": row.get("PRECIO"),
"precio": safe_num(row.get("PRECIO")),
"diagnostico_cod": (row.get("DIAG_PPAL") or "").strip(),
"diagnostico_nombre": (row.get("DIAG_CONCEPTO") or "").strip(),
"medico_docidmedico": (row.get("DOCIDMEDICO") or "").strip(),
"nit_empresa": (row.get("NIT_EMPRESA") or "").strip(),
"valor_total": row.get("VALORTOTAL"),
"valor_total": safe_num(row.get("VALORTOTAL")),
})
return JSONResponse({"ok": True, "examenes": examenes})
+34 -5
View File
@@ -62,12 +62,18 @@ ORDER BY r.IDRECEPCION
def _parse_hora(val) -> datetime | None:
if not val:
return None
# Si ya es datetime (fdb devuelve objetos datetime)
if isinstance(val, datetime):
ahora = datetime.now()
return ahora.replace(hour=val.hour, minute=val.minute, second=val.second, microsecond=0)
s = str(val)
try:
if "T" in s:
dt = datetime.fromisoformat(s)
# "2026-07-16T15:20:12" o "2026-07-16 15:20:12"
if len(s) > 10 and (s[10] == "T" or s[10] == " "):
dt = datetime.fromisoformat(s.replace(" ", "T"))
ahora = datetime.now()
return ahora.replace(hour=dt.hour, minute=dt.minute, second=dt.second, microsecond=0)
# "15:20:12"
partes = s.split(":")
ahora = datetime.now()
return ahora.replace(hour=int(partes[0]), minute=int(partes[1]),
@@ -76,6 +82,29 @@ def _parse_hora(val) -> datetime | None:
return None
def _fmt_hora_str(val) -> str:
"""Extrae HH:MM:SS de un datetime o string timestamp de Firebird."""
if not val:
return ""
if isinstance(val, datetime):
return val.strftime("%H:%M:%S")
s = str(val)
# "2026-07-16T15:20:12" o "2026-07-16 15:20:12"
if len(s) > 10 and (s[10] == "T" or s[10] == " "):
return s[11:19]
return s[:8]
def _safe_num(val):
"""Convierte Decimal/int/float a float, o None."""
if val is None:
return None
try:
return float(val)
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()
@@ -123,14 +152,14 @@ async def sync_recientes(ventana_min: int = 2) -> dict:
"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"),
"precio": _safe_num(ex.get("PRECIO")),
"recepcion_id": ex.get("IDRECEPCION"),
"hora": str(ex.get("HORAINICIORECEPCION") or "")[:8],
"hora": _fmt_hora_str(ex.get("HORAINICIORECEPCION")),
"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"),
"valor_total": _safe_num(ex.get("VALORTOTAL")),
})
ingest_url = f"{wa_url}/api/lab/ingest_paciente.php"