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.DESCUENTO, 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("/enviados") async def get_enviados(request: Request, user: dict = Depends(get_current_user)): db = get_connection() rows = db.execute( "SELECT idrecepcion, ok FROM rda_test_log ORDER BY created_at DESC" ).fetchall() db.close() enviados = {r["idrecepcion"]: bool(r["ok"]) for r in rows} return JSONResponse({"enviados": enviados}) @router.get("/candidatos") async def get_candidatos(request: Request, user: dict = Depends(get_current_user), contrato: str = "", factura: str = "", articulos: str = "", tipo_usuario: str = "", offset: int = 0): db = get_connection() cfg = {r[0]: r[1] for r in db.execute("SELECT key, value FROM config").fetchall()} db.close() fb, fb_ok, fb_msg = get_firebird_from_config(cfg) if not fb_ok: return JSONResponse({"error": f"Error Firebird: {fb_msg}"}) arts_default = "CH4,CREA,TGP,VSG,PCR,AU,FER,TPO,TSH,TGO,GLI,BUN,COL,TRI,HDL,LDL,HBA1,VB12,VITD,PO,K,NA,MG,URO,PRL,T4L,T3,T4,T3L,HIV,VDRL,PSA,AFP,CEA,CA125,CA199,FV,TP,TPT" arts_str = articulos.strip() if articulos.strip() else arts_default arts_list = [a.strip().upper() for a in arts_str.split(",") if a.strip()] arts_in = ", ".join(f"'{a}'" for a in arts_list) es_particular = tipo_usuario.strip() == "12" filtros = ["r.NUM_FACTURA > 0"] if not es_particular: # Para convenios: solo registros con contrato registrado filtros += [ "e.CODCONTRATO IS NOT NULL", "TRIM(e.CODCONTRATO) <> ''", ] # Solo exámenes configurados en TNS filtros.append( f"NOT EXISTS (SELECT 1 FROM RELACION rx WHERE rx.IDRECEPCION = r.IDRECEPCION AND rx.COD_EXAMEN NOT IN ({arts_in}))" ) params = {} if contrato: c = contrato.strip() c_alt = c.lstrip("0") or c # "012" → "12", "12" → "12" c_pad = c.zfill(max(3, len(c))) # "12" → "012", "011" → "011" filtros.append("TRIM(e.CODCONTRATO) IN (:contrato, :contrato_alt, :contrato_pad)") params["contrato"] = c params["contrato_alt"] = c_alt params["contrato_pad"] = c_pad elif es_particular: # Particulares: NIT_EMPRESA es PART/PARTIC o sin contrato filtros.append( "(r.TIPOUSUSISPRO = '12' OR r.NIT_EMPRESA IN ('PART', 'PARTIC', 'PARTICULAR') " "OR COALESCE(TRIM(e.CODCONTRATO), '') = '')" ) if tipo_usuario and not es_particular: filtros.append("r.TIPOUSUSISPRO = :tipo_usuario") params["tipo_usuario"] = tipo_usuario.strip() if factura: filtros.append("r.NUM_FACTURA = :factura") params["factura"] = int(factura) sql_final = f""" SELECT FIRST 10 SKIP {int(offset)} 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.get("/preview") async def preview_rda(request: Request, user: dict = Depends(get_current_user), idrecepcion: int = 0): if not idrecepcion: return JSONResponse({"error": "Falta idrecepcion"}) 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") fb, fb_ok, fb_msg = get_firebird_from_config(cfg) if not fb_ok: return JSONResponse({"error": fb_msg}) ok2, err2, rows_rda = fb.execute_query( _SQL_RDA_BY_WHERE.format(where="r.IDRECEPCION = :val"), {"val": idrecepcion} ) fb.disconnect() if not ok2 or not rows_rda: return JSONResponse({"error": err2 or "Sin registros"}) grupos = agrupar_por_recepcion(rows_rda) rda_json = generar_rda_paciente(list(grupos.values())[0], prof_def, esp_def, remis_def, prefijo_def) return JSONResponse({"json": rda_json}) @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, fb_ok, fb_msg = get_firebird_from_config(cfg) if not fb_ok: return JSONResponse({"error": f"Error Firebird: {fb_msg}"}) if modo == "factura": import re as _re nums = _re.findall(r'\d+', valor) num_val = int(nums[-1]) if nums else 0 prefix = _re.sub(r'[\d\s]', '', valor).strip().upper() if prefix: where = "r.NUM_FACTURA = :val AND TRIM(r.PREFIJO) = :prefijo" params = {"val": num_val, "prefijo": prefix} else: where = "r.NUM_FACTURA = :val" params = {"val": num_val} 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(): num_factura = str(grupo_rows[0].get("NUM_FACTURA") or "").strip() numero_override = num_factura rda_json = generar_rda_paciente(grupo_rows, prof_def, esp_def, remis_def, prefijo_def, numero_override) examenes = [r.get("COD_EXAMEN", "") for r in grupo_rows] try: r = await client.post(endpoint_rda, json=rda_json, headers=headers) raw_resp = r.text try: data = r.json() ok_rda = bool(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 raw_resp[:300] ) except Exception: ok_rda = r.status_code < 300 msg_rda = raw_resp[:300] 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, "respuesta_tns": raw_resp, "json_enviado": rda_json, }) if ok_rda: facturas_usadas_sesion.add(num_factura) db_log = get_connection() db_log.execute( "INSERT OR REPLACE INTO rda_test_log (idrecepcion, factura, contrato, ok, mensaje) VALUES (?,?,?,?,?)", (id_rec, rda_json.get("numero", ""), rda_json.get("numeroContrato", ""), 1 if ok_rda else 0, msg_rda) ) db_log.commit() db_log.close() 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, })