From daa50a70cf37a731bf23816df4734054a6eaa328 Mon Sep 17 00:00:00 2001 From: Lizandro Guarnizo <77708265+lizandrogd@users.noreply.github.com> Date: Sat, 11 Jul 2026 10:14:13 -0500 Subject: [PATCH] Add pre-servicios support in /transaccion + letter search fix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add 'RDA Pre-servicios por fecha' and 'RDA Pre-servicios por número' default queries joining PRESSERV_DIAN (ID_PS, PS_PREFIJO, PS_NUMERO) - Refactor _query_rows: only pass params the query actually uses; prefix filter works for both PREFIJO and PS_PREFIJO columns - Add _is_preserv(), _agrupar(), _build_rda() helpers that detect pre-servicio rows and produce correct prefijo/numero overrides - All 4 send endpoints (preview, send-one, send, send-direct) now group by ID_PS for pre-servicio queries and pass correct RDA fields - RCXC prefix still maps to '00' in the RDA codigoPrefijo Co-Authored-By: Claude Sonnet 4.6 --- app/routes/queries.py | 84 +++++++++++++++++ app/routes/transaccion.py | 184 ++++++++++++++++++++++---------------- 2 files changed, 190 insertions(+), 78 deletions(-) diff --git a/app/routes/queries.py b/app/routes/queries.py index f259021..271a775 100644 --- a/app/routes/queries.py +++ b/app/routes/queries.py @@ -103,6 +103,90 @@ WHERE r.FECHA_RECEPCION BETWEEN :fecha_ini AND :fecha_fin ORDER BY r.IDRECEPCION""", "description": "Servicios en un rango de fechas (para RdaPaciente/Insertar)" }, + { + "name": "RDA Pre-servicios por fecha", + "query_type": "transaccion", + "query_text": """SELECT + ps.ID_PS, + ps.PS_PREFIJO, + ps.PS_NUMERO, + 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, + r.VALORDESC, + rel.COD_EXAMEN, + COALESCE(NULLIF(TRIM(ex.NUM_ISS), ''), TRIM(rel.COD_EXAMEN)) AS CUPS, + rel.PRECIO, + COALESCE(t.VALOR, rel.PRECIO) AS PRECIO_TARIFA, + 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 PRESSERV_DIAN ps +JOIN RECEPCION r ON r.IDRECEPCION = ps.ID_RECEP +JOIN RELACION rel ON rel.IDRECEPCION = r.IDRECEPCION +LEFT JOIN EXAMEN ex ON TRIM(ex.CODIGO) = TRIM(rel.COD_EXAMEN) +LEFT JOIN MEDICO m ON m.CODIGO = r.COD_MEDICO +LEFT JOIN EMPRESA e ON e.NIT = r.NIT_EMPRESA +LEFT JOIN TARIFA t ON TRIM(t.COD_EXAMEN) = TRIM(rel.COD_EXAMEN) AND t.TARIFA = e.TARIFA +WHERE r.FECHA_RECEPCION BETWEEN :fecha_ini AND :fecha_fin + AND (ps.PS_ANULADA IS NULL OR ps.PS_ANULADA = 'F') +ORDER BY ps.ID_PS, rel.COD_EXAMEN""", + "description": "Pre-servicios RCXC/SC en un rango de fechas (agrupa por ID_PS)" + }, + { + "name": "RDA Pre-servicios por número", + "query_type": "transaccion", + "query_text": """SELECT + ps.ID_PS, + ps.PS_PREFIJO, + ps.PS_NUMERO, + 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, + r.VALORDESC, + rel.COD_EXAMEN, + COALESCE(NULLIF(TRIM(ex.NUM_ISS), ''), TRIM(rel.COD_EXAMEN)) AS CUPS, + rel.PRECIO, + COALESCE(t.VALOR, rel.PRECIO) AS PRECIO_TARIFA, + 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 PRESSERV_DIAN ps +JOIN RECEPCION r ON r.IDRECEPCION = ps.ID_RECEP +JOIN RELACION rel ON rel.IDRECEPCION = r.IDRECEPCION +LEFT JOIN EXAMEN ex ON TRIM(ex.CODIGO) = TRIM(rel.COD_EXAMEN) +LEFT JOIN MEDICO m ON m.CODIGO = r.COD_MEDICO +LEFT JOIN EMPRESA e ON e.NIT = r.NIT_EMPRESA +LEFT JOIN TARIFA t ON TRIM(t.COD_EXAMEN) = TRIM(rel.COD_EXAMEN) AND t.TARIFA = e.TARIFA +WHERE ps.PS_NUMERO = :ps_numero + AND (ps.PS_ANULADA IS NULL OR ps.PS_ANULADA = 'F') +ORDER BY ps.ID_PS, rel.COD_EXAMEN""", + "description": "Pre-servicio por número PS (buscar RCXC05291 → escribe solo el número)" + }, ] diff --git a/app/routes/transaccion.py b/app/routes/transaccion.py index 61c776d..9b01935 100644 --- a/app/routes/transaccion.py +++ b/app/routes/transaccion.py @@ -6,7 +6,7 @@ from fastapi.responses import JSONResponse 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_rda_paciente, agrupar_por_recepcion +from app.services.json_generator import generar_rda_paciente from app.services.api_client import get_tns_token, TNS_BASE from app.routes.contratos import load_contrato_map, load_excluded_set, load_sin_contrato_set from app.utils.activity import log_activity, get_ip @@ -27,23 +27,81 @@ def _query_rows(cfg, query_text, factura, fecha_inicio, fecha_fin): if not ok: return None, msg, None nums = _re.findall(r'\d+', factura or "") - num_val = int(nums[-1]) if nums else (factura or "") + num_val = int(nums[-1]) if nums else 0 prefix = _re.sub(r'[\d\s]', '', factura or "").strip().upper() - params = {"fecha_ini": f"{fecha_inicio} 00:00:00", "fecha_fin": f"{fecha_fin} 23:59:59"} + + # Solo agregar los parámetros que la query realmente usa + params = {} + if ":fecha_ini" in query_text: + params["fecha_ini"] = f"{fecha_inicio} 00:00:00" + if ":fecha_fin" in query_text: + params["fecha_fin"] = f"{fecha_fin} 23:59:59" if ":num_factura" in query_text: params["num_factura"] = num_val if ":prefijo" in query_text: params["prefijo"] = prefix + if ":ps_numero" in query_text: + params["ps_numero"] = num_val + ok2, err, rows = fb.execute_query(query_text, params) fb.disconnect() if not ok2: return None, err, None - # Si el usuario escribió prefijo (ej. "LHXC03726") y el query devuelve PREFIJO, filtramos - if prefix and rows and "PREFIJO" in rows[0]: - rows = [r for r in rows if str(r.get("PREFIJO") or "").strip().upper() == prefix] + + # Filtro Python por prefijo (PREFIJO o PS_PREFIJO) + if prefix and rows: + if "PS_PREFIJO" in rows[0]: + rows = [r for r in rows if str(r.get("PS_PREFIJO") or "").strip().upper() == prefix] + elif "PREFIJO" in rows[0]: + rows = [r for r in rows if str(r.get("PREFIJO") or "").strip().upper() == prefix] return rows, None, fb +def _is_preserv(rows: list) -> bool: + return bool(rows) and "ID_PS" in rows[0] + + +def _agrupar(rows: list) -> dict: + """Agrupa por ID_PS (pre-servicios) o IDRECEPCION (regulares).""" + from collections import defaultdict + grupos = defaultdict(list) + key_field = "ID_PS" if _is_preserv(rows) else "IDRECEPCION" + for row in rows: + grupos[row.get(key_field)].append(dict(row)) + return dict(grupos) + + +def _build_rda(grupo_rows: list, cfg: dict, contrato_map: dict, sin_contrato_set_val: set) -> tuple: + """Devuelve (rda_json, numero_override, prefijo_override, factura_display).""" + prof_def = cfg.get("profesional_default", "") + esp_def = cfg.get("especialidad_default", "") + remis_def = cfg.get("remisionante_default", "00") + prefijo_def = cfg.get("prefijo_tns_default", "00") + + if _is_preserv(grupo_rows): + ps_prefijo = str(grupo_rows[0].get("PS_PREFIJO") or "SC").strip() + ps_numero = str(grupo_rows[0].get("PS_NUMERO") or "").strip() + prefijo_override = "00" if ps_prefijo == "RCXC" else ps_prefijo + rda = generar_rda_paciente( + grupo_rows, prof_def, esp_def, remis_def, prefijo_def, + numero_override=ps_numero, + contrato_map=contrato_map, + prefijo_override=prefijo_override, + sin_contrato_set=sin_contrato_set_val, + ) + factura_display = f"{ps_prefijo}-{ps_numero.zfill(5)}" + return rda, ps_numero, prefijo_override, factura_display + else: + num_fac = str(grupo_rows[0].get("NUM_FACTURA") or "").strip() + rda = generar_rda_paciente( + grupo_rows, prof_def, esp_def, remis_def, prefijo_def, + numero_override=num_fac, + contrato_map=contrato_map, + sin_contrato_set=sin_contrato_set_val, + ) + return rda, num_fac, "", str(grupo_rows[0].get("NUM_FACTURA", "")) + + def _is_sent(idrecepcion: int) -> dict: """Returns the latest envio record for this idrecepcion, or None.""" conn = get_connection() @@ -90,26 +148,27 @@ async def preview_transaccion( if not rows: return JSONResponse({"success": False, "message": "Sin datos"}) - grupos = agrupar_por_recepcion(rows) - if contrato: - grupos = {k: v for k, v in grupos.items() - if str(v[0].get("CODCONTRATO") or "").strip() == contrato.strip()} - prof_def = cfg.get("profesional_default", "") - esp_def = cfg.get("especialidad_default", "") - remis_def = cfg.get("remisionante_default", "00") - prefijo_def = cfg.get("prefijo_tns_default", "00") - contrato_map = load_contrato_map() excluded = load_excluded_set() + contrato_map = load_contrato_map() + sin_contrato_set_val = load_sin_contrato_set() + grupos_all = _agrupar(rows) + + if contrato: + grupos = {k: v for k, v in grupos_all.items() + if str(v[0].get("CODCONTRATO") or "").strip() == contrato.strip()} + else: + grupos = {k: v for k, v in grupos_all.items() + if str(v[0].get("CODCONTRATO") or "").strip() not in excluded} items = [] - for id_rec, grupo_rows in grupos.items(): - enviado = _is_sent(id_rec) + for key, grupo_rows in grupos.items(): + enviado = _is_sent(key) cod_c = str(grupo_rows[0].get("CODCONTRATO") or "").strip() is_excluded = cod_c in excluded - rda = generar_rda_paciente(grupo_rows, prof_def, esp_def, remis_def, prefijo_def, contrato_map=contrato_map, sin_contrato_set=load_sin_contrato_set()) + rda, _, _, factura_display = _build_rda(grupo_rows, cfg, contrato_map, sin_contrato_set_val) items.append({ - "idrecepcion": id_rec, - "factura": grupo_rows[0].get("NUM_FACTURA", ""), + "idrecepcion": key, + "factura": factura_display, "paciente": grupo_rows[0].get("COD_PACIENTE", ""), "contrato": grupo_rows[0].get("CODCONTRATO", ""), "examenes": [r.get("COD_EXAMEN", "") for r in grupo_rows], @@ -151,10 +210,10 @@ async def send_one( if rows is None: return JSONResponse({"success": False, "message": err}) - grupos = agrupar_por_recepcion(rows) + grupos = _agrupar(rows) grupo_rows = grupos.get(idrecepcion) if not grupo_rows: - return JSONResponse({"success": False, "message": f"IDRECEPCION {idrecepcion} no encontrado"}) + return JSONResponse({"success": False, "message": f"Registro {idrecepcion} no encontrado"}) cod_c = str(grupo_rows[0].get("CODCONTRATO") or "").strip() if cod_c in load_excluded_set(): @@ -170,17 +229,8 @@ async def send_one( api_sucursal = cfg.get("api_sucursal", "") or "00" endpoint = f"{TNS_BASE}/v2/rda/RdaPaciente/Insertar?codigosucursal={api_sucursal}" - num_factura_one = str(grupo_rows[0].get("NUM_FACTURA") or "").strip() - numero_override_one = num_factura_one - rda_json = generar_rda_paciente( - grupo_rows, - cfg.get("profesional_default", ""), - cfg.get("especialidad_default", ""), - cfg.get("remisionante_default", "00"), - cfg.get("prefijo_tns_default", "00"), - numero_override_one, - contrato_map=load_contrato_map(), - sin_contrato_set=load_sin_contrato_set(), + rda_json, numero_override_one, _, factura_display = _build_rda( + grupo_rows, cfg, load_contrato_map(), load_sin_contrato_set() ) raw_resp = "" @@ -208,7 +258,7 @@ async def send_one( VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?) """, ( user["user_id"], "transaccion", - str(grupo_rows[0].get("NUM_FACTURA", idrecepcion)), + factura_display, idrecepcion, str(grupo_rows[0].get("CODCONTRATO", "")), fecha_inicio, fecha_fin, 1, len(grupo_rows), @@ -221,9 +271,8 @@ async def send_one( conn.commit() conn.close() - factura_log = str(grupo_rows[0].get("NUM_FACTURA", idrecepcion)) log_activity(user["user_id"], user["username"], "rda_enviado", - f"Factura {factura_log} | Contrato {grupo_rows[0].get('CODCONTRATO','')} | {'OK' if ok_rda else 'ERROR: '+msg_tns[:80]}", + f"Factura {factura_display} | Contrato {grupo_rows[0].get('CODCONTRATO','')} | {'OK' if ok_rda else 'ERROR: '+msg_tns[:80]}", get_ip(request)) return JSONResponse({"success": ok_rda, "message": msg_tns, "raw_tns": raw_resp, "idrecepcion": idrecepcion}) @@ -246,20 +295,21 @@ async def send_transaccion( if not rows: return JSONResponse({"success": False, "message": "Sin datos"}) - grupos = agrupar_por_recepcion(rows) + excluded = load_excluded_set() + contrato_map = load_contrato_map() + sin_contrato_set_val = load_sin_contrato_set() + grupos_all = _agrupar(rows) + if contrato: - grupos = {k: v for k, v in grupos.items() + grupos = {k: v for k, v in grupos_all.items() if str(v[0].get("CODCONTRATO") or "").strip() == contrato.strip()} + else: + grupos = {k: v for k, v in grupos_all.items() + if str(v[0].get("CODCONTRATO") or "").strip() not in excluded} if solo_pendientes == "1": grupos = {k: v for k, v in grupos.items() if not _is_sent(k)} - excluded = load_excluded_set() - grupos_enviar = {k: v for k, v in grupos.items() - if str(v[0].get("CODCONTRATO") or "").strip() not in excluded} - excluidos_count = len(grupos) - len(grupos_enviar) - grupos = grupos_enviar - - contrato_map = load_contrato_map() + excluidos_count = len(grupos_all) - len(grupos) token, token_err = await get_tns_token( cfg.get("tns_empresa", ""), cfg.get("tns_usuario", ""), cfg.get("tns_password", "") @@ -273,19 +323,8 @@ async def send_transaccion( resultados = [] async with httpx.AsyncClient(timeout=int(cfg.get("api_timeout", 30))) as client: - for id_rec, grupo_rows in grupos.items(): - num_factura = str(grupo_rows[0].get("NUM_FACTURA") or "").strip() - numero_override = num_factura - rda_json = generar_rda_paciente( - grupo_rows, - cfg.get("profesional_default", ""), - cfg.get("especialidad_default", ""), - cfg.get("remisionante_default", "00"), - cfg.get("prefijo_tns_default", "00"), - numero_override, - contrato_map=contrato_map, - sin_contrato_set=load_sin_contrato_set(), - ) + for key, grupo_rows in grupos.items(): + rda_json, _, _, factura_display = _build_rda(grupo_rows, cfg, contrato_map, sin_contrato_set_val) raw_resp = "" ok_rda = False msg_tns = "" @@ -310,8 +349,7 @@ async def send_transaccion( VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?) """, ( user["user_id"], "transaccion", - str(grupo_rows[0].get("NUM_FACTURA", id_rec)), - id_rec, + factura_display, key, str(grupo_rows[0].get("CODCONTRATO", "")), fecha_inicio, fecha_fin, 1, len(grupo_rows), "success" if ok_rda else "error", @@ -321,7 +359,7 @@ async def send_transaccion( )) conn.commit() conn.close() - resultados.append({"idrecepcion": id_rec, "success": ok_rda, "msg": msg_tns}) + resultados.append({"idrecepcion": key, "success": ok_rda, "msg": msg_tns}) ok_count = sum(1 for r in resultados if r["success"]) err_count = len(resultados) - ok_count @@ -363,11 +401,10 @@ async def send_direct( if not rows: return JSONResponse({"success": False, "message": f"No se encontró factura {factura_str} en Firebird"}) - grupos = agrupar_por_recepcion(rows) + grupos = _agrupar(rows) if not grupos: return JSONResponse({"success": False, "message": "Sin datos agrupables"}) - # Rechazar si todos los grupos pertenecen a contratos excluidos excluded = load_excluded_set() primer_contrato = str(list(grupos.values())[0][0].get("CODCONTRATO") or "").strip() if primer_contrato in excluded: @@ -383,21 +420,12 @@ async def send_direct( api_sucursal = cfg.get("api_sucursal", "") or "00" endpoint = f"{TNS_BASE}/v2/rda/RdaPaciente/Insertar?codigosucursal={api_sucursal}" contrato_map = load_contrato_map() + sin_contrato_set_val = load_sin_contrato_set() resultados = [] async with httpx.AsyncClient(timeout=int(cfg.get("api_timeout", 30))) as client: - for id_rec, grupo_rows in grupos.items(): - num_fac = str(grupo_rows[0].get("NUM_FACTURA") or "").strip() - rda_json = generar_rda_paciente( - grupo_rows, - cfg.get("profesional_default", ""), - cfg.get("especialidad_default", ""), - cfg.get("remisionante_default", "00"), - cfg.get("prefijo_tns_default", "00"), - num_fac, - contrato_map=contrato_map, - sin_contrato_set=load_sin_contrato_set(), - ) + for key, grupo_rows in grupos.items(): + rda_json, _, _, factura_display = _build_rda(grupo_rows, cfg, contrato_map, sin_contrato_set_val) raw_resp = "" ok_rda = False msg_tns = "" @@ -421,7 +449,7 @@ async def send_direct( status, json_enviado, respuesta_api, mensaje_tns, created_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?) """, ( - user["user_id"], "transaccion", factura_str, id_rec, + user["user_id"], "transaccion", factura_display, key, str(grupo_rows[0].get("CODCONTRATO", "")), "2000-01-01", "2099-12-31", 1, len(grupo_rows), "success" if ok_rda else "error", @@ -431,8 +459,8 @@ async def send_direct( conn.commit() conn.close() resultados.append({ - "idrecepcion": id_rec, - "factura": factura_str, + "idrecepcion": key, + "factura": factura_display, "success": ok_rda, "message": msg_tns, "raw_tns": raw_resp[:1000],