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 <noreply@anthropic.com>
This commit is contained in:
Lizandro Guarnizo
2026-06-26 18:57:54 -05:00
co-authored by Claude Sonnet 4.6
parent 3017c7956e
commit c64a68ec6f
10 changed files with 521 additions and 10 deletions
+6 -2
View File
@@ -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)
+2
View File
@@ -25,6 +25,8 @@ DEFAULT_KEYS = [
("cod_prestador", ""),
("profesional_default", ""),
("especialidad_default", ""),
("remisionante_default", "00"),
("prefijo_tns_default", "00"),
]
+4 -2
View File
@@ -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
+263
View File
@@ -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,
})
+3 -1
View File
@@ -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 = ""
+6 -3
View File
@@ -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",
+3
View File
@@ -56,6 +56,9 @@
</a>
<a href="/automation" class="flex items-center px-3 py-2.5 rounded-lg text-sm font-medium {% if request.url.path == '/automation' %}bg-blue-600 text-white{% else %}text-gray-300 hover:bg-gray-700{% endif %}">
<i class="fas fa-robot w-5 mr-2"></i> Automatización␍
</a>
<a href="/test-rda" class="flex items-center px-3 py-2.5 rounded-lg text-sm font-medium {% if request.url.path == '/test-rda' %}bg-blue-600 text-white{% else %}text-gray-300 hover:bg-gray-700{% endif %}">
<i class="fas fa-flask w-5 mr-2"></i> Prueba RDA
</a>
<hr class="my-3 border-gray-700">
<a href="/logs" class="flex items-center px-3 py-2.5 rounded-lg text-sm font-medium {% if request.url.path == '/logs' %}bg-blue-600 text-white{% else %}text-gray-300 hover:bg-gray-700{% endif %}">
+15 -1
View File
@@ -127,7 +127,7 @@
<h4 class="font-medium text-gray-800"><i class="fas fa-user-md mr-2 text-green-500"></i>Valores por Defecto RDA</h4>
<div class="grid grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Profesional Default <span class="text-gray-400 font-normal">(código TNS, ej: 02)</span></label>
<label class="block text-sm font-medium text-gray-700 mb-1">Profesional Default <span class="text-gray-400 font-normal">(código TNS, ej: XIMENA)</span></label>
<input type="text" name="config_profesional_default"
value="{{ configs|selectattr('key', 'equalto', 'profesional_default')|map(attribute='value')|first|default('') }}"
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 text-sm">
@@ -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">
</div>
</div>
<div class="grid grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Remisionante Default <span class="text-gray-400 font-normal">(cédula médico si no hay en Firebird)</span></label>
<input type="text" name="config_remisionante_default"
value="{{ configs|selectattr('key', 'equalto', 'remisionante_default')|map(attribute='value')|first|default('00') }}"
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 text-sm">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Prefijo TNS Default <span class="text-gray-400 font-normal">(prefijo de facturación TNS, ej: 00)</span></label>
<input type="text" name="config_prefijo_tns_default"
value="{{ configs|selectattr('key', 'equalto', 'prefijo_tns_default')|map(attribute='value')|first|default('00') }}"
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 text-sm">
</div>
</div>
<button type="submit"
class="px-6 py-2.5 bg-blue-600 hover:bg-blue-700 text-white font-medium rounded-lg transition-colors">
+217
View File
@@ -0,0 +1,217 @@
{% extends "base.html" %}
{% block title %}Prueba RDA{% endblock %}
{% block header %}Prueba RDA Individual{% endblock %}
{% block content %}
<div class="max-w-6xl space-y-6">
<!-- Filtros y candidatos -->
<div class="bg-white rounded-xl shadow-sm border border-gray-200">
<div class="px-6 py-4 border-b border-gray-200 flex items-center justify-between">
<h3 class="font-semibold text-gray-800"><i class="fas fa-list mr-2 text-blue-500"></i>Candidatos disponibles</h3>
<div class="flex gap-2 items-center">
<input id="filtro-contrato" type="text" placeholder="Contrato (ej: 011)"
class="px-3 py-1.5 border border-gray-300 rounded-lg text-sm w-32">
<input id="filtro-factura" type="text" placeholder="Factura (ej: 3990)"
class="px-3 py-1.5 border border-gray-300 rounded-lg text-sm w-36">
<button onclick="cargarCandidatos()"
class="px-4 py-1.5 bg-blue-600 text-white rounded-lg text-sm hover:bg-blue-700">
<i class="fas fa-search mr-1"></i> Buscar
</button>
</div>
</div>
<div class="p-4">
<div id="cargando-candidatos" class="hidden text-center py-6 text-gray-400">
<i class="fas fa-spinner fa-spin text-2xl"></i>
<p class="mt-2 text-sm">Consultando Firebird...</p>
</div>
<div id="tabla-candidatos" class="hidden">
<table class="w-full text-sm">
<thead>
<tr class="text-left text-xs text-gray-500 uppercase border-b">
<th class="pb-2 pr-3">IDRECEPCION</th>
<th class="pb-2 pr-3">Factura</th>
<th class="pb-2 pr-3">Fecha</th>
<th class="pb-2 pr-3">Paciente</th>
<th class="pb-2 pr-3">Contrato</th>
<th class="pb-2 pr-3">Exámenes</th>
<th class="pb-2"></th>
</tr>
</thead>
<tbody id="tbody-candidatos" class="divide-y divide-gray-100"></tbody>
</table>
</div>
<p id="sin-candidatos" class="hidden text-center py-6 text-gray-400 text-sm">No se encontraron registros con los filtros aplicados.</p>
</div>
</div>
<!-- Envío manual -->
<div class="bg-white rounded-xl shadow-sm border border-gray-200">
<div class="px-6 py-4 border-b border-gray-200">
<h3 class="font-semibold text-gray-800"><i class="fas fa-paper-plane mr-2 text-green-500"></i>Enviar RDA</h3>
</div>
<div class="p-6">
<div class="flex gap-3 items-end">
<div>
<label class="block text-xs font-medium text-gray-600 mb-1">Tipo</label>
<select id="modo" class="px-3 py-2 border border-gray-300 rounded-lg text-sm">
<option value="idrecepcion">IDRECEPCION</option>
<option value="factura">Factura</option>
</select>
</div>
<div class="flex-1 max-w-xs">
<label class="block text-xs font-medium text-gray-600 mb-1">Número</label>
<input id="valor" type="text" placeholder="ej: 314863"
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-blue-500">
</div>
<button onclick="enviarRDA()"
class="px-5 py-2 bg-green-600 text-white rounded-lg text-sm hover:bg-green-700 font-medium">
<i class="fas fa-paper-plane mr-1"></i> Enviar
</button>
</div>
</div>
</div>
<!-- Resultado -->
<div id="resultado-box" class="hidden bg-white rounded-xl shadow-sm border border-gray-200">
<div class="px-6 py-4 border-b border-gray-200 flex items-center justify-between">
<h3 class="font-semibold text-gray-800"><i class="fas fa-chart-bar mr-2 text-purple-500"></i>Resultado</h3>
<div id="resumen-badges" class="flex gap-2"></div>
</div>
<div id="resultado-contenido" class="p-6 space-y-3"></div>
</div>
</div>
<script>
async function cargarCandidatos() {
const contrato = document.getElementById('filtro-contrato').value.trim();
const factura = document.getElementById('filtro-factura').value.trim();
document.getElementById('cargando-candidatos').classList.remove('hidden');
document.getElementById('tabla-candidatos').classList.add('hidden');
document.getElementById('sin-candidatos').classList.add('hidden');
const params = new URLSearchParams();
if (contrato) params.append('contrato', contrato);
if (factura) params.append('factura', factura);
const resp = await fetch('/test-rda/candidatos?' + params.toString());
const data = await resp.json();
document.getElementById('cargando-candidatos').classList.add('hidden');
if (data.error) {
document.getElementById('sin-candidatos').textContent = 'Error: ' + data.error;
document.getElementById('sin-candidatos').classList.remove('hidden');
return;
}
const candidatos = data.candidatos || [];
if (!candidatos.length) {
document.getElementById('sin-candidatos').classList.remove('hidden');
return;
}
const tbody = document.getElementById('tbody-candidatos');
tbody.innerHTML = '';
candidatos.forEach(c => {
const tr = document.createElement('tr');
tr.className = 'hover:bg-gray-50';
tr.innerHTML = `
<td class="py-2 pr-3 font-mono text-blue-700 font-medium">${c.idrecepcion}</td>
<td class="py-2 pr-3 font-mono">${c.factura}</td>
<td class="py-2 pr-3 text-gray-500">${c.fecha}</td>
<td class="py-2 pr-3 text-gray-600">${c.paciente}</td>
<td class="py-2 pr-3">
<span class="px-2 py-0.5 bg-blue-50 text-blue-700 rounded text-xs font-mono">${c.contrato}</span>
</td>
<td class="py-2 pr-3 text-gray-500 text-xs max-w-xs truncate" title="${c.examenes}">${c.examenes} <span class="text-gray-400">(${c.total})</span></td>
<td class="py-2">
<button onclick="enviarDesde(${c.idrecepcion})"
class="px-3 py-1 bg-green-100 text-green-700 rounded text-xs hover:bg-green-200 font-medium">
<i class="fas fa-paper-plane mr-1"></i>Enviar
</button>
</td>
`;
tbody.appendChild(tr);
});
document.getElementById('tabla-candidatos').classList.remove('hidden');
}
function enviarDesde(idrecepcion) {
document.getElementById('modo').value = 'idrecepcion';
document.getElementById('valor').value = idrecepcion;
enviarRDA();
}
async function enviarRDA() {
const modo = document.getElementById('modo').value;
const valor = document.getElementById('valor').value.trim();
if (!valor) { alert('Ingresa un número'); return; }
const box = document.getElementById('resultado-box');
const contenido = document.getElementById('resultado-contenido');
const badges = document.getElementById('resumen-badges');
box.classList.remove('hidden');
contenido.innerHTML = '<div class="text-center py-8 text-gray-400"><i class="fas fa-spinner fa-spin text-3xl"></i><p class="mt-2">Enviando...</p></div>';
badges.innerHTML = '';
const fd = new FormData();
fd.append('modo', modo);
fd.append('valor', valor);
const resp = await fetch('/test-rda/enviar', { method: 'POST', body: fd });
const data = await resp.json();
if (data.error) {
contenido.innerHTML = `<div class="p-4 bg-red-50 border border-red-200 rounded-lg text-red-700 text-sm"><i class="fas fa-times-circle mr-2"></i>${data.error}</div>`;
return;
}
const res = data.resumen || {};
badges.innerHTML = `
<span class="px-2 py-1 bg-green-100 text-green-700 rounded text-xs font-medium">${res.rda_exitosos} exitosos</span>
<span class="px-2 py-1 bg-red-100 text-red-700 rounded text-xs font-medium">${res.rda_errores} errores</span>
<span class="px-2 py-1 bg-gray-100 text-gray-600 rounded text-xs">${res.pacientes_procesados} pacientes</span>
`;
let html = '';
(data.pasos || []).forEach(p => {
const icon = p.ok ? '✅' : '❌';
if (p.tipo === 'tercero') {
html += `
<div class="flex items-start gap-3 p-3 rounded-lg border ${p.ok ? 'border-green-100 bg-green-50' : 'border-red-100 bg-red-50'}">
<span class="text-lg">${icon}</span>
<div class="flex-1 min-w-0">
<p class="text-sm font-medium text-gray-700">Tercero: <span class="font-mono">${p.codigo}</span> — ${p.nombre}</p>
<p class="text-xs text-gray-500 mt-0.5">${p.mensaje}</p>
</div>
</div>`;
} else {
const jsonStr = JSON.stringify(p.json_enviado || {}, null, 2);
const uid = 'json_' + p.idrecepcion;
html += `
<div class="rounded-lg border ${p.ok ? 'border-green-200 bg-green-50' : 'border-red-200 bg-red-50'}">
<div class="flex items-start gap-3 p-3">
<span class="text-lg">${icon}</span>
<div class="flex-1">
<p class="text-sm font-medium text-gray-800">
IDRECEPCION <span class="font-mono text-blue-700">${p.idrecepcion}</span>
· Factura <span class="font-mono">${p.factura}</span>
· Contrato <span class="font-mono text-purple-700">${p.contrato}</span>
</p>
<p class="text-xs text-gray-500 mt-0.5">Exámenes: ${(p.examenes || []).join(', ')}</p>
<p class="text-sm mt-1 ${p.ok ? 'text-green-700' : 'text-red-700'} font-medium">${p.mensaje}</p>
</div>
<button onclick="document.getElementById('${uid}').classList.toggle('hidden')"
class="text-xs text-gray-400 hover:text-gray-600 shrink-0">JSON</button>
</div>
<pre id="${uid}" class="hidden mx-3 mb-3 p-3 bg-gray-900 text-green-300 rounded text-xs overflow-x-auto">${jsonStr.replace(/</g,'&lt;')}</pre>
</div>`;
}
});
contenido.innerHTML = html;
}
// Cargar candidatos al abrir la página
cargarCandidatos();
</script>
{% endblock %}
+2 -1
View File
@@ -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__":