Files
rips_manager/app/routes/automation.py
T
Lizandro GuarnizoandClaude Sonnet 4.6 b4a7271d8d Fix param validation errors and add TNS config section
- terceros/transaccion: return clear error when required params (doc_num, num_factura) are missing instead of passing empty dict to Firebird causing Column unknown error
- transaccion: remove prefijo from 'por factura' query; parse num_factura as int when digit
- automation: add /preview endpoint to show patient/reception counts before sending; update UI with preview step and disabled run button until preview loads
- config: add TNS credentials section (tns_empresa, tns_usuario, tns_password, api_sucursal) with test-connection button
- config: add POST /config/test-tns endpoint

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-25 19:29:32 -05:00

265 lines
9.3 KiB
Python

import json as json_lib
from datetime import datetime, date
from fastapi import APIRouter, Request, Form, 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="/automation", tags=["automation"])
# Query para obtener pacientes únicos por rango de fecha
_SQL_PACIENTES = """
SELECT DISTINCT
p.CODIGO,
p.TIPOIDENT,
p.DOCIDENT,
p.NOMBRES,
p.APELLIDOS,
p.DIRECCION,
p.CIUDAD AS COD_CIUDAD,
c.NOMBRE AS NOM_CIUDAD,
p.TELEFONOS,
p.EMAIL,
p.F_NACIMIENTO,
p.SEXO,
p.TIPORES,
p.CODETNIA
FROM PACIENTE p
JOIN RECEPCION r ON r.COD_PACIENTE = p.CODIGO
LEFT JOIN CIUDAD c ON c.CODIGO = p.CIUDAD
WHERE r.FECHA_RECEPCION BETWEEN :fecha_ini AND :fecha_fin
"""
# Query para obtener recepciones con exámenes por rango de fecha
_SQL_RDA = """
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,
r.USUARIO AS profesional
FROM RECEPCION r
JOIN RELACION rel ON rel.IDRECEPCION = r.IDRECEPCION
LEFT JOIN MEDICO m ON m.CODIGO = r.COD_MEDICO
WHERE r.FECHA_RECEPCION BETWEEN :fecha_ini AND :fecha_fin
ORDER BY r.IDRECEPCION
"""
@router.get("")
async def automation_page(request: Request, user: dict = Depends(get_current_user)):
today = date.today().isoformat()
return request.app.state.templates.TemplateResponse("automation.html", {
"request": request, "user": user, "today": today,
})
@router.post("/preview")
async def preview_automation(
request: Request,
user: dict = Depends(get_current_user),
fecha: str = Form(...),
):
conn = get_connection()
configs = {row["key"]: row["value"] for row in conn.execute("SELECT * FROM config").fetchall()}
conn.close()
fb, fb_ok, fb_msg = get_firebird_from_config(configs)
if not fb_ok:
return JSONResponse({"success": False, "message": f"Error Firebird: {fb_msg}"})
fecha_ini = f"{fecha} 00:00:00"
fecha_fin = f"{fecha} 23:59:59"
params = {"fecha_ini": fecha_ini, "fecha_fin": fecha_fin}
ok1, err1, rows_pac = fb.execute_query(_SQL_PACIENTES, params)
if not ok1:
fb.disconnect()
return JSONResponse({"success": False, "message": f"Error BD pacientes: {err1}"})
ok2, err2, rows_rda = fb.execute_query(_SQL_RDA, params)
fb.disconnect()
if not ok2:
return JSONResponse({"success": False, "message": f"Error BD RDA: {err2}"})
grupos = agrupar_por_recepcion(rows_rda)
total_examenes = sum(len(v) for v in grupos.values())
pacientes_preview = [
{
"doc": str(p.get("DOCIDENT", "")),
"nombre": f"{p.get('NOMBRES', '')} {p.get('APELLIDOS', '')}".strip(),
"tipo": str(p.get("TIPOIDENT", "")),
}
for p in rows_pac[:10]
]
return JSONResponse({
"success": True,
"fecha": fecha,
"pacientes": len(rows_pac),
"recepciones": len(grupos),
"examenes": total_examenes,
"pacientes_preview": pacientes_preview,
"hay_mas": len(rows_pac) > 10,
})
@router.post("/run")
async def run_automation(
request: Request,
user: dict = Depends(get_current_user),
fecha: str = Form(...),
):
conn = get_connection()
configs = {row["key"]: row["value"] for row in conn.execute("SELECT * FROM config").fetchall()}
conn.close()
fb, fb_ok, fb_msg = get_firebird_from_config(configs)
if not fb_ok:
return JSONResponse({"success": False, "message": f"Error Firebird: {fb_msg}"})
# Rango: día completo de la fecha seleccionada
fecha_ini = f"{fecha} 00:00:00"
fecha_fin = f"{fecha} 23:59:59"
params = {"fecha_ini": fecha_ini, "fecha_fin": fecha_fin}
# ── Paso 1: obtener pacientes únicos ─────────────────────────────────────
ok1, err1, rows_pac = fb.execute_query(_SQL_PACIENTES, params)
if not ok1:
fb.disconnect()
return JSONResponse({"success": False, "message": f"Error Firebird pacientes: {err1}"})
# ── Paso 2: obtener recepciones ───────────────────────────────────────────
ok2, err2, rows_rda = fb.execute_query(_SQL_RDA, params)
fb.disconnect()
if not ok2:
return JSONResponse({"success": False, "message": f"Error Firebird RDA: {err2}"})
if not rows_pac and not rows_rda:
return JSONResponse({"success": False, "message": f"No hay datos para la fecha {fecha}"})
# ── Login TNS ─────────────────────────────────────────────────────────────
token, token_err = await get_tns_token(
configs.get("tns_empresa", ""),
configs.get("tns_usuario", ""),
configs.get("tns_password", ""),
)
if not token:
return JSONResponse({"success": False, "message": f"Error login TNS: {token_err}"})
headers = {"Content-Type": "application/json", "Authorization": f"Bearer {token}"}
api_sucursal = configs.get("api_sucursal", "")
timeout = int(configs.get("api_timeout", 30))
resultado = {
"fecha": fecha,
"paso1_terceros": {"enviados": 0, "errores": 0, "detalle": []},
"paso2_rda": {"enviados": 0, "errores": 0, "detalle": []},
}
# ── PASO 1: Enviar Terceros ───────────────────────────────────────────────
async with httpx.AsyncClient(timeout=timeout) as client:
for row in rows_pac:
tercero_json = generar_tercero_api(row)
doc = tercero_json["nit"]
nombre = tercero_json["nombre"]
try:
resp = await client.post(
f"{TNS_BASE}/v2/tablas/Tercero/Crear",
json=tercero_json, headers=headers,
)
ok = resp.is_success
msg = resp.json().get("message", "") if ok else resp.text[:200]
except Exception as e:
ok = False
msg = str(e)
if ok:
resultado["paso1_terceros"]["enviados"] += 1
else:
resultado["paso1_terceros"]["errores"] += 1
resultado["paso1_terceros"]["detalle"].append({
"doc": doc, "nombre": nombre, "ok": ok, "msg": msg,
})
_guardar_envio(user["user_id"], "terceros", fecha, tercero_json, msg, ok)
# ── PASO 2: Enviar RDA Paciente ───────────────────────────────────────────
grupos = agrupar_por_recepcion(rows_rda)
endpoint = f"{TNS_BASE}/v2/rda/RdaPaciente/Insertar"
if api_sucursal:
endpoint += f"?codigosucursal={api_sucursal}"
async with httpx.AsyncClient(timeout=timeout) as client:
for id_recepcion, grupo_rows in grupos.items():
rda_json = generar_rda_paciente(grupo_rows)
factura = str(grupo_rows[0].get("NUM_FACTURA", id_recepcion))
try:
resp = await client.post(endpoint, json=rda_json, headers=headers)
ok = resp.is_success
msg = resp.json().get("message", "") if ok else resp.text[:200]
except Exception as e:
ok = False
msg = str(e)
if ok:
resultado["paso2_rda"]["enviados"] += 1
else:
resultado["paso2_rda"]["errores"] += 1
resultado["paso2_rda"]["detalle"].append({
"factura": factura,
"paciente": str(grupo_rows[0].get("COD_PACIENTE", "")),
"examenes": len(grupo_rows),
"ok": ok, "msg": msg,
})
_guardar_envio(user["user_id"], "transaccion", factura, rda_json, msg, ok,
fecha_inicio=fecha, fecha_fin=fecha,
servicios=len(grupo_rows))
return JSONResponse({"success": True, "resultado": resultado})
def _guardar_envio(user_id, tipo, factura, json_data, respuesta, ok,
fecha_inicio=None, fecha_fin=None, servicios=0):
conn = get_connection()
conn.execute("""
INSERT INTO envios (user_id, tipo, factura, fecha_inicio, fecha_fin,
pacientes_count, servicios_count, status, json_enviado, respuesta_api, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""", (
user_id, tipo, factura, fecha_inicio, fecha_fin,
1, servicios,
"success" if ok else "error",
json_lib.dumps(json_data, indent=2, ensure_ascii=False)[:5000],
respuesta[:1000] if respuesta else "",
datetime.now().isoformat(),
))
conn.commit()
conn.close()