feat: integración completa con API TNS v2

- api_client: agrega get_tns_token() — login en /v2/Acceso/Login y retorna JWT
- json_generator: campos en camelCase según swagger v2; agrega nomRegTri y
  email placeholder cuando el paciente no tiene correo
- terceros/transaccion routes: obtienen token TNS antes de cada envío,
  usan endpoints /v2/tablas/Tercero/Crear y /v2/rda/RdaPaciente/Insertar
- config: agrega tns_empresa, tns_usuario, tns_password

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Lizandro Guarnizo
2026-06-25 18:56:31 -05:00
co-authored by Claude Sonnet 4.6
parent 774ce7d728
commit 6d5a85c55f
6 changed files with 96 additions and 67 deletions
+3
View File
@@ -17,6 +17,9 @@ DEFAULT_KEYS = [
("api_timeout", "30"), ("api_timeout", "30"),
("api_version", "1"), ("api_version", "1"),
("api_sucursal", ""), ("api_sucursal", ""),
("tns_empresa", "9002787299"),
("tns_usuario", "DOCUXER"),
("tns_password", "Nicolas2796*+"),
("num_documento_obligado", ""), ("num_documento_obligado", ""),
("cod_prestador", ""), ("cod_prestador", ""),
] ]
+10 -7
View File
@@ -7,6 +7,7 @@ from app.database import get_connection
from app.auth import get_current_user from app.auth import get_current_user
from app.services.firebird_service import get_firebird_from_config from app.services.firebird_service import get_firebird_from_config
from app.services.json_generator import generar_tercero_api from app.services.json_generator import generar_tercero_api
from app.services.api_client import get_tns_token, TNS_BASE
router = APIRouter(prefix="/terceros", tags=["terceros"]) router = APIRouter(prefix="/terceros", tags=["terceros"])
@@ -137,14 +138,16 @@ async def send_terceros(
tercero_json = generar_tercero_api(rows[0]) tercero_json = generar_tercero_api(rows[0])
api_url = configs.get("api_url", "") token, token_err = await get_tns_token(
api_version = configs.get("api_version", "1") configs.get("tns_empresa", ""),
api_key = configs.get("api_key", "") configs.get("tns_usuario", ""),
headers = {"Content-Type": "application/json"} configs.get("tns_password", ""),
if api_key: )
headers["Authorization"] = f"Bearer {api_key}" if not token:
return JSONResponse({"success": False, "message": f"Error login TNS: {token_err}"})
endpoint = f"{api_url}/v{api_version}/tablas/Tercero/Crear" headers = {"Content-Type": "application/json", "Authorization": f"Bearer {token}"}
endpoint = f"{TNS_BASE}/v2/tablas/Tercero/Crear"
resp_ok = False resp_ok = False
resp_text = "" resp_text = ""
+12 -8
View File
@@ -7,6 +7,7 @@ from app.database import get_connection
from app.auth import get_current_user from app.auth import get_current_user
from app.services.firebird_service import get_firebird_from_config 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, agrupar_por_recepcion
from app.services.api_client import get_tns_token, TNS_BASE
router = APIRouter(prefix="/transaccion", tags=["transaccion"]) router = APIRouter(prefix="/transaccion", tags=["transaccion"])
@@ -116,15 +117,18 @@ async def send_transaccion(
return JSONResponse({"success": False, "message": "No se encontraron datos"}) return JSONResponse({"success": False, "message": "No se encontraron datos"})
grupos = agrupar_por_recepcion(rows) grupos = agrupar_por_recepcion(rows)
api_url = configs.get("api_url", "")
api_version = configs.get("api_version", "1")
api_sucursal = configs.get("api_sucursal", "")
api_key = configs.get("api_key", "")
headers = {"Content-Type": "application/json"}
if api_key:
headers["Authorization"] = f"Bearer {api_key}"
endpoint = f"{api_url}/v{api_version}/rda/RdaPaciente/Insertar" 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", "")
endpoint = f"{TNS_BASE}/v2/rda/RdaPaciente/Insertar"
if api_sucursal: if api_sucursal:
endpoint += f"?codigosucursal={api_sucursal}" endpoint += f"?codigosucursal={api_sucursal}"
+19 -1
View File
@@ -1,8 +1,26 @@
import httpx import httpx
import json
from typing import Optional from typing import Optional
TNS_BASE = "https://api.tns.co"
async def get_tns_token(empresa: str, usuario: str, password: str) -> tuple:
"""Hace login en TNS v2 y devuelve (token, error_msg)."""
try:
async with httpx.AsyncClient(timeout=30) as client:
resp = await client.post(
f"{TNS_BASE}/v2/Acceso/Login",
json={"codigoEmpresa": empresa, "nombreUsuario": usuario, "contrasenia": password},
)
body = resp.json()
if resp.is_success and body.get("status"):
return body["data"], None
return None, body.get("message") or f"HTTP {resp.status_code}"
except Exception as e:
return None, str(e)
async def send_json( async def send_json(
url: str, url: str,
json_data: dict, json_data: dict,
+52 -51
View File
@@ -52,34 +52,35 @@ def generar_tercero_api(row: dict) -> dict:
zona = _ZONA_MAP.get(str(row.get("TIPORES") or "U").strip(), "01") zona = _ZONA_MAP.get(str(row.get("TIPORES") or "U").strip(), "01")
return { return {
"Codigo": str(row.get("CODIGO") or "").strip(), "codigo": str(row.get("CODIGO") or "").strip(),
"NatJuridica": "N", "natJuridica": "N",
"TipoDocumento": tipo_doc, "tipoDocumento": tipo_doc,
"Nit": str(row.get("DOCIDENT") or "").strip(), "nit": str(row.get("DOCIDENT") or "").strip(),
"Nombre": nombre_completo, "nombre": nombre_completo,
"Nombre1": nombres[0] if nombres else "", "nombre1": nombres[0] if nombres else "",
"Nombre2": nombres[1] if len(nombres) > 1 else "", "nombre2": nombres[1] if len(nombres) > 1 else "",
"Apellido1": apellidos[0] if apellidos else "", "apellido1": apellidos[0] if apellidos else "",
"Apellido2": apellidos[1] if len(apellidos) > 1 else "", "apellido2": apellidos[1] if len(apellidos) > 1 else "",
"Direccion": (row.get("DIRECCION") or "").strip(), "direccion": (row.get("DIRECCION") or "").strip(),
"CodigoCiudad": str(row.get("COD_CIUDAD") or "00").strip(), "codigoCiudad": str(row.get("COD_CIUDAD") or "00").strip(),
"NombreCiudad": (row.get("NOM_CIUDAD") or "SIN CIUDAD").strip().upper(), "nombreCiudad": (row.get("NOM_CIUDAD") or "SIN CIUDAD").strip().upper(),
"Zona1": "00", "zona1": "00",
"Clasificacion": "00", "clasificacion": "00",
"Telefono": str(row.get("TELEFONOS") or "").strip(), "nomRegTri": nombre_completo,
"Email": (row.get("EMAIL") or "").strip(), "telefono": str(row.get("TELEFONOS") or "").strip(),
"Inactivo": False, "email": (row.get("EMAIL") or "").strip() or f"{str(row.get('DOCIDENT') or 'paciente').strip()}@sinregistro.co",
"Privada": "N", "inactivo": False,
"Mixta": "N", "privada": "N",
"CodigoBarrio": "00", "mixta": "N",
"Comision": 0, "codigoBarrio": "00",
"FechaNacimiento": _fmt_fecha(row.get("F_NACIMIENTO")), "comision": 0,
"Sexo": str(row.get("SEXO") or "M").strip(), "fechaNacimiento": _fmt_fecha(row.get("F_NACIMIENTO")),
"Zona": zona, "sexo": str(row.get("SEXO") or "M").strip(),
"Etnia": str(row.get("CODETNIA") or "99").strip(), "zona": zona,
"Cliente": "S", "etnia": str(row.get("CODETNIA") or "99").strip(),
"EnfermedadCronica": False, "cliente": "S",
"NoEnviarMinSalud": False, "enfermedadCronica": False,
"noEnviarMinSalud": False,
} }
@@ -109,15 +110,15 @@ def generar_rda_paciente(rows: list) -> dict:
else h.get("FECHA_RECEPCION") else h.get("FECHA_RECEPCION")
) )
detalle_pedido.append({ detalle_pedido.append({
"CodigoMaterial": str(row.get("COD_EXAMEN") or "").strip(), "codigoMaterial": str(row.get("COD_EXAMEN") or "").strip(),
"CodigoBodega": "00", "codigoBodega": "00",
"Cantidad": 1, "cantidad": 1,
"TipoUnidad": "D", "tipoUnidad": "D",
"Valor": float(row.get("PRECIO") or 0), "valor": float(row.get("PRECIO") or 0),
"Descuento": 0, "descuento": 0,
"PorcentajeIva": 0, "porcentajeIva": 0,
"ImpConsumo": 0, "impConsumo": 0,
"Observacion": "", "observacion": "",
"profesional": str(h.get("profesional") or "").strip(), "profesional": str(h.get("profesional") or "").strip(),
"especialidad": str(row.get("COD_ESPECIALIDAD") or "").strip(), "especialidad": str(row.get("COD_ESPECIALIDAD") or "").strip(),
"diagnosticoprincipal": str(h.get("DIAG_PPAL") or "").strip(), "diagnosticoprincipal": str(h.get("DIAG_PPAL") or "").strip(),
@@ -127,14 +128,14 @@ def generar_rda_paciente(rows: list) -> dict:
autorizacion = str(h.get("AUTORIZACION") or "").strip() or None autorizacion = str(h.get("AUTORIZACION") or "").strip() or None
return { return {
"CodigoPrefijo": str(h.get("PREFIJO") or "00").strip(), "codigoPrefijo": str(h.get("PREFIJO") or "00").strip(),
"Numero": "", "numero": "",
"Fecha": fecha, "fecha": fecha,
"CodTercero": str(h.get("COD_PACIENTE") or "").strip(), "codTercero": str(h.get("COD_PACIENTE") or "").strip(),
"CodVendedor": "00", "codVendedor": "00",
"CodFormaPago": "CO", "codFormaPago": "CO",
"CodBanco": "00", "codBanco": "00",
"CodigoCentroCosto": "00", "codigoCentroCosto": "00",
"tipoIngreso": str(h.get("CLASEPROC") or "1").strip() or "1", "tipoIngreso": str(h.get("CLASEPROC") or "1").strip() or "1",
"fechaHoraIngreso": ingreso, "fechaHoraIngreso": ingreso,
"fechaHoraEgreso": egreso, "fechaHoraEgreso": egreso,
@@ -146,12 +147,12 @@ def generar_rda_paciente(rows: list) -> dict:
"esTerapia": False, "esTerapia": False,
"esProcedimiento": False, "esProcedimiento": False,
"numeroAutorizacion": autorizacion, "numeroAutorizacion": autorizacion,
"DetallePedido": detalle_pedido, "detallePedido": detalle_pedido,
"DetalleFormaPago": [{ "detalleFormaPago": [{
"CodigoFormaPago": "CO", "codigoFormaPago": "CO",
"PlazoDias": "0", "plazoDias": "0",
"FechaVencimiento": fecha, "fechaVencimiento": fecha,
"Valor": str(int(float(h.get("VALORTOTAL") or 0))), "valor": str(int(float(h.get("VALORTOTAL") or 0))),
}], }],
} }
BIN
View File
Binary file not shown.