From c64a68ec6f4ff2d47820d284cb6119a554ec6d67 Mon Sep 17 00:00:00 2001 From: Lizandro Guarnizo <77708265+lizandrogd@users.noreply.github.com> Date: Fri, 26 Jun 2026 18:57:54 -0500 Subject: [PATCH] Add test-rda page, fix prefijo/remisionante/especialidad for TNS RDA sends - New /test-rda route: list Firebird candidates with valid contract/factura, send individual RDA with auto tercero creation and full result display - json_generator: use prefijo_tns_default config instead of Firebird PREFIJO, add profesionalRemisionante from DOCIDMEDICO or remisionante_default config - All queries: add DOCIDMEDICO column from MEDICO JOIN - automation/transaccion: add remisionante_default and prefijo_tns_default params - config: add remisionante_default (00) and prefijo_tns_default (00) keys - automation SQL: filter NUM_FACTURA > 0 to skip unbilled records - config.html: add UI fields for new config keys Co-Authored-By: Claude Sonnet 4.6 --- app/routes/automation.py | 8 +- app/routes/config.py | 2 + app/routes/queries.py | 6 +- app/routes/test_rda.py | 263 +++++++++++++++++++++++++++++++++ app/routes/transaccion.py | 4 +- app/services/json_generator.py | 9 +- app/templates/base.html | 3 + app/templates/config.html | 16 +- app/templates/test_rda.html | 217 +++++++++++++++++++++++++++ main.py | 3 +- 10 files changed, 521 insertions(+), 10 deletions(-) create mode 100644 app/routes/test_rda.py create mode 100644 app/templates/test_rda.html diff --git a/app/routes/automation.py b/app/routes/automation.py index 06fe222..8b71287 100644 --- a/app/routes/automation.py +++ b/app/routes/automation.py @@ -60,12 +60,14 @@ SELECT rel.FECHA_REPORTADO, m.COD_ESPECIALIDAD, COALESCE(NULLIF(TRIM(m.CODIGO), ''), NULLIF(TRIM(r.USUARIO), ''), '') AS profesional, - e.CODCONTRATO + e.CODCONTRATO, + TRIM(m.DOCIDMEDICO) AS DOCIDMEDICO FROM RECEPCION r JOIN RELACION rel ON rel.IDRECEPCION = r.IDRECEPCION LEFT JOIN MEDICO m ON m.CODIGO = r.COD_MEDICO LEFT JOIN EMPRESA e ON e.NIT = r.NIT_EMPRESA WHERE r.FECHA_RECEPCION BETWEEN :fecha_ini AND :fecha_fin + AND r.NUM_FACTURA > 0 ORDER BY r.IDRECEPCION """ @@ -231,10 +233,12 @@ async def run_automation( endpoint = f"{TNS_BASE}/v2/rda/RdaPaciente/Insertar?codigosucursal={api_sucursal or '00'}" prof_def = configs.get("profesional_default", "") esp_def = configs.get("especialidad_default", "") + remis_def = configs.get("remisionante_default", "00") + prefijo_def = configs.get("prefijo_tns_default", "00") async with httpx.AsyncClient(timeout=timeout) as client: for id_recepcion, grupo_rows in grupos.items(): - rda_json = generar_rda_paciente(grupo_rows, prof_def, esp_def) + rda_json = generar_rda_paciente(grupo_rows, prof_def, esp_def, remis_def, prefijo_def) factura = str(grupo_rows[0].get("NUM_FACTURA", id_recepcion)) try: resp = await client.post(endpoint, json=rda_json, headers=headers) diff --git a/app/routes/config.py b/app/routes/config.py index d5883a5..0e10823 100644 --- a/app/routes/config.py +++ b/app/routes/config.py @@ -25,6 +25,8 @@ DEFAULT_KEYS = [ ("cod_prestador", ""), ("profesional_default", ""), ("especialidad_default", ""), + ("remisionante_default", "00"), + ("prefijo_tns_default", "00"), ] diff --git a/app/routes/queries.py b/app/routes/queries.py index 9aa2fc4..b06716c 100644 --- a/app/routes/queries.py +++ b/app/routes/queries.py @@ -52,7 +52,8 @@ WHERE p.DOCIDENT = :doc_num""", rel.FECHA_REPORTADO, m.COD_ESPECIALIDAD, COALESCE(NULLIF(TRIM(m.CODIGO), ''), NULLIF(TRIM(r.USUARIO), ''), '') AS profesional, - e.CODCONTRATO + e.CODCONTRATO, + TRIM(m.DOCIDMEDICO) AS DOCIDMEDICO FROM RECEPCION r JOIN RELACION rel ON rel.IDRECEPCION = r.IDRECEPCION LEFT JOIN MEDICO m ON m.CODIGO = r.COD_MEDICO @@ -82,7 +83,8 @@ WHERE r.NUM_FACTURA = :num_factura""", rel.FECHA_REPORTADO, m.COD_ESPECIALIDAD, COALESCE(NULLIF(TRIM(m.CODIGO), ''), NULLIF(TRIM(r.USUARIO), ''), '') AS profesional, - e.CODCONTRATO + e.CODCONTRATO, + TRIM(m.DOCIDMEDICO) AS DOCIDMEDICO FROM RECEPCION r JOIN RELACION rel ON rel.IDRECEPCION = r.IDRECEPCION LEFT JOIN MEDICO m ON m.CODIGO = r.COD_MEDICO diff --git a/app/routes/test_rda.py b/app/routes/test_rda.py new file mode 100644 index 0000000..8e186ef --- /dev/null +++ b/app/routes/test_rda.py @@ -0,0 +1,263 @@ +import json as json_lib +from fastapi import APIRouter, Request, Depends +from fastapi.responses import JSONResponse + +import httpx + +from app.database import get_connection +from app.auth import get_current_user +from app.services.firebird_service import get_firebird_from_config +from app.services.json_generator import ( + generar_tercero_api, + generar_rda_paciente, + agrupar_por_recepcion, +) +from app.services.api_client import get_tns_token, TNS_BASE + +router = APIRouter(prefix="/test-rda", tags=["test-rda"]) + +_SQL_RDA_BY_WHERE = """ +SELECT + r.IDRECEPCION, r.PREFIJO, r.NUM_FACTURA, r.FECHA_RECEPCION, r.COD_PACIENTE, + r.NIT_EMPRESA, r.DIAG_PPAL, r.TIPOUSU, r.TIPOUSUSISPRO, r.AUTORIZACION, r.CLASEPROC, + r.HORAINICIORECEPCION, r.VALORTOTAL, rel.COD_EXAMEN, rel.PRECIO, rel.FECHA_REPORTADO, + m.COD_ESPECIALIDAD, + COALESCE(NULLIF(TRIM(m.CODIGO), ''), NULLIF(TRIM(r.USUARIO), ''), '') AS profesional, + e.CODCONTRATO, + TRIM(m.DOCIDMEDICO) AS DOCIDMEDICO +FROM RECEPCION r +JOIN RELACION rel ON rel.IDRECEPCION = r.IDRECEPCION +LEFT JOIN MEDICO m ON m.CODIGO = r.COD_MEDICO +LEFT JOIN EMPRESA e ON e.NIT = r.NIT_EMPRESA +WHERE {where} + AND r.NUM_FACTURA > 0 + AND e.CODCONTRATO IS NOT NULL + AND TRIM(e.CODCONTRATO) <> '' +ORDER BY r.IDRECEPCION +""" + +_SQL_CANDIDATOS = """ +SELECT FIRST 100 + r.IDRECEPCION, + r.NUM_FACTURA, + r.FECHA_RECEPCION, + r.COD_PACIENTE, + r.NIT_EMPRESA, + e.CODCONTRATO, + LIST(DISTINCT rel.COD_EXAMEN, ', ') AS examenes, + COUNT(DISTINCT rel.COD_EXAMEN) AS total_examenes +FROM RECEPCION r +JOIN RELACION rel ON rel.IDRECEPCION = r.IDRECEPCION +LEFT JOIN EMPRESA e ON e.NIT = r.NIT_EMPRESA +WHERE r.NUM_FACTURA > 0 + AND e.CODCONTRATO IS NOT NULL + AND TRIM(e.CODCONTRATO) <> '' +GROUP BY r.IDRECEPCION, r.NUM_FACTURA, r.FECHA_RECEPCION, r.COD_PACIENTE, r.NIT_EMPRESA, e.CODCONTRATO +ORDER BY r.IDRECEPCION DESC +""" + +_SQL_PACIENTE = """ +SELECT p.CODIGO, p.NOMBRES, p.APELLIDOS, p.DOCIDENT, p.TIPOIDENT, + p.F_NACIMIENTO, p.SEXO, p.DIRECCION, p.TELEFONOS, p.EMAIL +FROM PACIENTE p WHERE p.CODIGO = :cod +""" + + +@router.get("") +async def test_page(request: Request, user: dict = Depends(get_current_user)): + return request.app.state.templates.TemplateResponse( + "test_rda.html", {"request": request, "user": user} + ) + + +@router.get("/candidatos") +async def get_candidatos(request: Request, user: dict = Depends(get_current_user), + contrato: str = "", factura: str = ""): + db = get_connection() + cfg = {r[0]: r[1] for r in db.execute("SELECT key, value FROM config").fetchall()} + db.close() + + fb = get_firebird_from_config(cfg) + ok, err = fb.connect() + if not ok: + return JSONResponse({"error": f"Error Firebird: {err}"}) + + sql = _SQL_CANDIDATOS + params = {} + filtros = ["r.NUM_FACTURA > 0", "e.CODCONTRATO IS NOT NULL", "TRIM(e.CODCONTRATO) <> ''"] + if contrato: + filtros.append("e.CODCONTRATO = :contrato") + params["contrato"] = contrato + if factura: + filtros.append("r.NUM_FACTURA = :factura") + params["factura"] = int(factura) + + sql_final = """ + SELECT FIRST 100 + r.IDRECEPCION, r.NUM_FACTURA, r.FECHA_RECEPCION, r.COD_PACIENTE, r.NIT_EMPRESA, + e.CODCONTRATO, LIST(DISTINCT rel.COD_EXAMEN, ', ') AS examenes, + COUNT(DISTINCT rel.COD_EXAMEN) AS total_examenes + FROM RECEPCION r + JOIN RELACION rel ON rel.IDRECEPCION = r.IDRECEPCION + LEFT JOIN EMPRESA e ON e.NIT = r.NIT_EMPRESA + WHERE """ + " AND ".join(filtros) + """ + GROUP BY r.IDRECEPCION, r.NUM_FACTURA, r.FECHA_RECEPCION, r.COD_PACIENTE, r.NIT_EMPRESA, e.CODCONTRATO + ORDER BY r.IDRECEPCION DESC + """ + + ok2, err2, rows = fb.execute_query(sql_final, params) + fb.disconnect() + + if not ok2: + return JSONResponse({"error": err2}) + + candidatos = [] + for row in rows: + candidatos.append({ + "idrecepcion": row.get("IDRECEPCION"), + "factura": row.get("NUM_FACTURA"), + "fecha": str(row.get("FECHA_RECEPCION", ""))[:10], + "paciente": row.get("COD_PACIENTE", ""), + "empresa": row.get("NIT_EMPRESA", ""), + "contrato": row.get("CODCONTRATO", ""), + "examenes": row.get("examenes", ""), + "total": row.get("total_examenes", 0), + }) + + return JSONResponse({"candidatos": candidatos}) + + +@router.post("/enviar") +async def test_enviar(request: Request, user: dict = Depends(get_current_user)): + form = await request.form() + modo = form.get("modo", "idrecepcion") + valor = str(form.get("valor", "")).strip() + + if not valor: + return JSONResponse({"error": "Ingresa un IDRECEPCION o número de factura"}) + + db = get_connection() + cfg = {r[0]: r[1] for r in db.execute("SELECT key, value FROM config").fetchall()} + db.close() + + prof_def = cfg.get("profesional_default", "XIMENA") + esp_def = cfg.get("especialidad_default", "02") + remis_def = cfg.get("remisionante_default", "00") + prefijo_def = cfg.get("prefijo_tns_default", "00") + api_sucursal = cfg.get("api_sucursal", "00") or "00" + + fb = get_firebird_from_config(cfg) + ok, err = fb.connect() + if not ok: + return JSONResponse({"error": f"Error Firebird: {err}"}) + + if modo == "factura": + where = "r.NUM_FACTURA = :val" + params = {"val": int(valor)} + else: + where = "r.IDRECEPCION = :val" + params = {"val": int(valor)} + + ok2, err2, rows_rda = fb.execute_query( + _SQL_RDA_BY_WHERE.format(where=where), params + ) + if not ok2: + fb.disconnect() + return JSONResponse({"error": f"Error consulta: {err2}"}) + + if not rows_rda: + fb.disconnect() + return JSONResponse({"error": "Sin registros (¿NUM_FACTURA = 0 o contrato vacío?)"}) + + # Pacientes únicos + pacientes_unicos = {} + for row in rows_rda: + cod = str(row.get("COD_PACIENTE", "")).strip() + if cod and cod not in pacientes_unicos: + ok3, err3, prows = fb.execute_query(_SQL_PACIENTE, {"cod": cod}) + if ok3 and prows: + pacientes_unicos[cod] = prows[0] + + fb.disconnect() + + token, err_tns = await get_tns_token( + cfg.get("tns_empresa", ""), cfg.get("tns_usuario", ""), cfg.get("tns_password", "") + ) + if not token: + return JSONResponse({"error": f"Error login TNS: {err_tns}"}) + + headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} + endpoint_rda = f"{TNS_BASE}/v2/rda/RdaPaciente/Insertar?codigosucursal={api_sucursal}" + + pasos = [] + + # Paso 1: crear terceros + async with httpx.AsyncClient(timeout=20) as client: + for cod, pac_row in pacientes_unicos.items(): + tercero_json = generar_tercero_api(pac_row) + try: + r = await client.post( + f"{TNS_BASE}/v2/tablas/Tercero/Crear", + json=tercero_json, headers=headers, + ) + try: + data = r.json() + msg = data.get("message") or r.text[:200] + except Exception: + msg = r.text[:200] + pasos.append({ + "tipo": "tercero", + "codigo": cod, + "nombre": tercero_json.get("nombre", ""), + "status": r.status_code, + "ok": r.status_code in (200, 201), + "mensaje": msg, + }) + except Exception as ex: + pasos.append({"tipo": "tercero", "codigo": cod, "ok": False, "mensaje": str(ex)}) + + # Paso 2: enviar RDA + grupos = agrupar_por_recepcion(rows_rda) + async with httpx.AsyncClient(timeout=30) as client: + for id_rec, grupo_rows in grupos.items(): + rda_json = generar_rda_paciente(grupo_rows, prof_def, esp_def, remis_def, prefijo_def) + examenes = [r.get("COD_EXAMEN", "") for r in grupo_rows] + try: + r = await client.post(endpoint_rda, json=rda_json, headers=headers) + try: + data = r.json() + ok_rda = (data.get("status") or (data.get("data") or {}).get("success", False)) if isinstance(data, dict) else False + msg_rda = ((data.get("data") or {}).get("response") or data.get("message") or r.text[:200]) + except Exception: + ok_rda = r.status_code < 300 + msg_rda = r.text[:200] + pasos.append({ + "tipo": "rda", + "idrecepcion": id_rec, + "factura": rda_json.get("numero", ""), + "paciente": rda_json.get("codTercero", ""), + "contrato": rda_json.get("numeroContrato", ""), + "examenes": examenes, + "status": r.status_code, + "ok": ok_rda, + "mensaje": msg_rda, + "json_enviado": rda_json, + }) + except Exception as ex: + pasos.append({ + "tipo": "rda", "idrecepcion": id_rec, "ok": False, + "examenes": examenes, "mensaje": str(ex), "json_enviado": {}, + }) + + rda_ok = sum(1 for p in pasos if p["tipo"] == "rda" and p["ok"]) + rda_err = sum(1 for p in pasos if p["tipo"] == "rda" and not p["ok"]) + + return JSONResponse({ + "resumen": { + "total_recepciones": len(grupos), + "rda_exitosos": rda_ok, + "rda_errores": rda_err, + "pacientes_procesados": len(pacientes_unicos), + }, + "pasos": pasos, + }) diff --git a/app/routes/transaccion.py b/app/routes/transaccion.py index 3f81c26..8e420ca 100644 --- a/app/routes/transaccion.py +++ b/app/routes/transaccion.py @@ -155,10 +155,12 @@ async def send_transaccion( resultados = [] prof_def = configs.get("profesional_default", "") esp_def = configs.get("especialidad_default", "") + remis_def = configs.get("remisionante_default", "00") + prefijo_def = configs.get("prefijo_tns_default", "00") async with httpx.AsyncClient(timeout=int(configs.get("api_timeout", 30))) as client: for id_recepcion, grupo_rows in grupos.items(): - trans_json = generar_rda_paciente(grupo_rows, prof_def, esp_def) + trans_json = generar_rda_paciente(grupo_rows, prof_def, esp_def, remis_def, prefijo_def) status_ok = False response_text = "" diff --git a/app/services/json_generator.py b/app/services/json_generator.py index 30e2f9b..aabd831 100644 --- a/app/services/json_generator.py +++ b/app/services/json_generator.py @@ -96,7 +96,8 @@ def agrupar_por_recepcion(rows: list) -> dict: return grupos -def generar_rda_paciente(rows: list, default_profesional: str = "", default_especialidad: str = "") -> dict: +def generar_rda_paciente(rows: list, default_profesional: str = "", default_especialidad: str = "", + default_remisionante: str = "00", default_prefijo: str = "00") -> dict: if not rows: return {} h = rows[0] @@ -116,6 +117,7 @@ def generar_rda_paciente(rows: list, default_profesional: str = "", default_espe row.get("FECHA_REPORTADO") if str(row.get("FECHA_REPORTADO") or "")[:4] != "1900" else h.get("FECHA_RECEPCION") ) + cedula_medico = str(row.get("DOCIDMEDICO") or "").strip() detalle_pedido.append({ "codigoMaterial": str(row.get("COD_EXAMEN") or "").strip(), "codigoBodega": "00", @@ -128,6 +130,7 @@ def generar_rda_paciente(rows: list, default_profesional: str = "", default_espe "observacion": "", "profesional": default_profesional or None, "especialidad": default_especialidad or None, + "profesionalRemisionante": cedula_medico or default_remisionante or None, "diagnosticoprincipal": str(h.get("DIAG_PPAL") or "").strip(), "fechaHoraRealizacion": fecha_real or egreso, }) @@ -152,8 +155,8 @@ def generar_rda_paciente(rows: list, default_profesional: str = "", default_espe modalidad = "01" # Intramural (laboratorio en sede fija) return { - "codigoPrefijo": str(h.get("PREFIJO") or "00").strip(), - "numero": str(h.get("NUM_FACTURA") or "").strip(), + "codigoPrefijo": default_prefijo or str(h.get("PREFIJO") or "00").strip(), + "numero": str(h.get("NUM_FACTURA") or "").strip() if (h.get("NUM_FACTURA") or 0) != 0 else "", "fecha": fecha, "codTercero": str(h.get("COD_PACIENTE") or "").strip(), "codVendedor": "00", diff --git a/app/templates/base.html b/app/templates/base.html index fab8e57..f97bbd4 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -56,6 +56,9 @@ Automatización + + + Prueba RDA
diff --git a/app/templates/config.html b/app/templates/config.html index b01327b..0a36d49 100644 --- a/app/templates/config.html +++ b/app/templates/config.html @@ -127,7 +127,7 @@

Valores por Defecto RDA

- + @@ -139,6 +139,20 @@ class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 text-sm">
+
+
+ + +
+
+ + +
+
+ + +
+ + + +
+ + + +
+
+

Enviar RDA

+
+
+
+
+ + +
+
+ + +
+ +
+
+
+ + + + + + + +{% endblock %} diff --git a/main.py b/main.py index d60903a..eaa3ce8 100644 --- a/main.py +++ b/main.py @@ -65,7 +65,7 @@ async def root(): return RedirectResponse(url="/dashboard") -from app.routes import auth, dashboard, config, queries, terceros, transaccion, logs, automation +from app.routes import auth, dashboard, config, queries, terceros, transaccion, logs, automation, test_rda app.include_router(auth.router) app.include_router(dashboard.router) @@ -75,6 +75,7 @@ app.include_router(terceros.router) app.include_router(transaccion.router) app.include_router(logs.router) app.include_router(automation.router) +app.include_router(test_rda.router) if __name__ == "__main__":