diff --git a/app/routes/automation.py b/app/routes/automation.py index 4ec114a..7c83b3d 100644 --- a/app/routes/automation.py +++ b/app/routes/automation.py @@ -42,6 +42,7 @@ LEFT JOIN EMPRESA e ON e.NIT = r.NIT_EMPRESA LEFT JOIN CIUDAD c ON c.CODIGO = p.CIUDAD WHERE r.FECHA_RECEPCION BETWEEN :fecha_ini AND :fecha_fin AND r.NUM_FACTURA > 0 + AND r.PS_NUM IS NULL {filtro_contrato_pac} """ @@ -79,11 +80,63 @@ 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 r.NUM_FACTURA > 0 + AND r.PS_NUM IS NULL {filtro_contrato_rda} ORDER BY r.IDRECEPCION """ +_SQL_PRESERV = """ +SELECT + ps.ID_PS, + ps.PS_PREFIJO, + ps.PS_NUMERO, + r.IDRECEPCION, + 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, + COALESCE((SELECT FIRST 1 pg.VALOR FROM PAGOS pg + WHERE pg.NUMRECEP = r.IDRECEPCION + AND pg.ESINICIAL = 'T'), 0) AS COPAGO, + 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 +""" + + +def _agrupar_por_presserv(rows: list) -> dict: + """Agrupa filas de pre-servicios por ID_PS.""" + from collections import defaultdict + grupos = defaultdict(list) + for row in rows: + grupos[row.get("ID_PS")].append(dict(row)) + return grupos + + @router.get("") async def automation_page(request: Request, user: dict = Depends(get_current_user)): today = date.today().isoformat() @@ -134,17 +187,27 @@ async def preview_automation( 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: + fb.disconnect() return JSONResponse({"success": False, "message": f"Error BD RDA: {err2}"}) - grupos_all = agrupar_por_recepcion(rows_rda) + ok3, err3, rows_ps = fb.execute_query(_SQL_PRESERV, {"fecha_ini": fecha_ini, "fecha_fin": fecha_fin}) + fb.disconnect() + if not ok3: + return JSONResponse({"success": False, "message": f"Error BD Pre-servicios: {err3}"}) + excluded = load_excluded_set() + + grupos_all = agrupar_por_recepcion(rows_rda) grupos = {k: v for k, v in grupos_all.items() if str(v[0].get("CODCONTRATO") or "").strip() not in excluded} excluidos_count = len(grupos_all) - len(grupos) total_examenes = sum(len(v) for v in grupos.values()) + grupos_ps_all = _agrupar_por_presserv(rows_ps) + grupos_ps = {k: v for k, v in grupos_ps_all.items() + if str(v[0].get("CODCONTRATO") or "").strip() not in excluded} + # Agrupar RDA por COD_PACIENTE rda_por_pac = {} for id_rec, filas in grupos.items(): @@ -180,6 +243,7 @@ async def preview_automation( "recepciones": len(grupos), "examenes": total_examenes, "excluidos": excluidos_count, + "preservicios": len(grupos_ps), "pacientes_preview": pacientes_preview, }) @@ -215,10 +279,16 @@ async def run_automation( # ── Paso 2: obtener recepciones ─────────────────────────────────────────── ok2, err2, rows_rda = fb.execute_query(sql_rda, params) - fb.disconnect() if not ok2: + fb.disconnect() return JSONResponse({"success": False, "message": f"Error Firebird RDA: {err2}"}) + # ── Paso 3: obtener pre-servicios ───────────────────────────────────────── + ok3, err3, rows_ps = fb.execute_query(_SQL_PRESERV, {"fecha_ini": fecha_ini, "fecha_fin": fecha_fin}) + fb.disconnect() + if not ok3: + return JSONResponse({"success": False, "message": f"Error Firebird Pre-servicios: {err3}"}) + if not rows_pac and not rows_rda: return JSONResponse({"success": False, "message": f"No hay datos para la fecha {fecha}"}) @@ -239,6 +309,7 @@ async def run_automation( "fecha": fecha, "paso1_terceros": {"enviados": 0, "errores": 0, "detalle": []}, "paso2_rda": {"enviados": 0, "errores": 0, "detalle": []}, + "paso3_preserv": {"enviados": 0, "errores": 0, "detalle": []}, } # ── PASO 1: Enviar Terceros ─────────────────────────────────────────────── @@ -324,10 +395,54 @@ async def run_automation( fecha_inicio=fecha, fecha_fin=fecha, servicios=len(grupo_rows)) + # ── PASO 3: Enviar Pre-servicios (RCXC / SC) ────────────────────────────── + grupos_ps_all = _agrupar_por_presserv(rows_ps) + grupos_ps = {k: v for k, v in grupos_ps_all.items() + if str(v[0].get("CODCONTRATO") or "").strip() not in excluded} + resultado["paso3_preserv"]["excluidos"] = len(grupos_ps_all) - len(grupos_ps) + + async with httpx.AsyncClient(timeout=timeout) as client: + for id_ps, grupo_rows in grupos_ps.items(): + ps_prefijo = str(grupo_rows[0].get("PS_PREFIJO") or "SC").strip() + ps_numero = str(grupo_rows[0].get("PS_NUMERO") or "").strip() + rda_json = generar_rda_paciente( + grupo_rows, prof_def, esp_def, remis_def, prefijo_def, + numero_override=ps_numero, + contrato_map=contrato_map, + prefijo_override=ps_prefijo, + ) + factura_ps = f"{ps_prefijo}-{ps_numero.zfill(5)}" + 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["paso3_preserv"]["enviados"] += 1 + else: + resultado["paso3_preserv"]["errores"] += 1 + + resultado["paso3_preserv"]["detalle"].append({ + "factura": factura_ps, + "paciente": str(grupo_rows[0].get("COD_PACIENTE", "")), + "examenes": len(grupo_rows), + "ok": ok, "msg": msg, + }) + + _guardar_envio(user["user_id"], "preserv", factura_ps, rda_json, msg, ok, + fecha_inicio=fecha, fecha_fin=fecha, + servicios=len(grupo_rows)) + r1 = resultado["paso1_terceros"] r2 = resultado["paso2_rda"] + r3 = resultado["paso3_preserv"] log_activity(user["user_id"], user["username"], "automation_run", - f"Fecha {fecha} | Terceros: {r1['enviados']} OK/{r1['errores']} err | RDA: {r2['enviados']} OK/{r2['errores']} err/{r2.get('excluidos',0)} excluidos", + f"Fecha {fecha} | Terceros: {r1['enviados']} OK/{r1['errores']} err | " + f"RDA: {r2['enviados']} OK/{r2['errores']} err | " + f"PreServ: {r3['enviados']} OK/{r3['errores']} err", get_ip(request)) return JSONResponse({"success": True, "resultado": resultado}) diff --git a/app/services/json_generator.py b/app/services/json_generator.py index 4b4eaf7..9ec998f 100644 --- a/app/services/json_generator.py +++ b/app/services/json_generator.py @@ -124,17 +124,10 @@ def agrupar_por_recepcion(rows: list) -> dict: return grupos -def _calcular_descuento_pct(rows: list) -> int: - total_cobrado = sum(float(r.get("PRECIO") or 0) for r in rows) - total_tarifa = sum(float(r.get("PRECIO_TARIFA") or r.get("PRECIO") or 0) for r in rows) - if total_tarifa > 0 and total_tarifa > total_cobrado: - return round((total_tarifa - total_cobrado) / total_tarifa * 100) - return 0 - - def generar_rda_paciente(rows: list, default_profesional: str = "", default_especialidad: str = "", default_remisionante: str = "00", default_prefijo: str = "00", - numero_override: str = "", contrato_map: dict = None) -> dict: + numero_override: str = "", contrato_map: dict = None, + prefijo_override: str = "") -> dict: if not rows: return {} h = rows[0] @@ -220,7 +213,7 @@ def generar_rda_paciente(rows: list, default_profesional: str = "", default_espe descuento_pct = round(_desc * 100 / _total, 2) if _total > 0 else 0 result = { - "codigoPrefijo": str(h.get("PREFIJO") or "").strip() or default_prefijo or "00", + "codigoPrefijo": prefijo_override or str(h.get("PREFIJO") or "").strip() or default_prefijo or "00", "numero": (numero_override if numero_override else ( str(h.get("NUM_FACTURA") or "").strip() if (h.get("NUM_FACTURA") or 0) != 0 else "" )).zfill(5) or "", diff --git a/app/templates/automation.html b/app/templates/automation.html index b53ec87..0c1b0af 100644 --- a/app/templates/automation.html +++ b/app/templates/automation.html @@ -48,7 +48,7 @@
-
+
0
Pacientes
@@ -61,6 +61,11 @@
0
Exámenes
+
+
0
+
Pre-servicios
+
RCXC / SC
+

Pacientes — clic para ver sus recepciones:

@@ -91,6 +96,16 @@
+
+
+
+
3
+ Pre-servicios RDA (RCXC / SC) +
+ Esperando... +
+ +
@@ -114,7 +129,7 @@ :

-
+
Terceros
@@ -130,6 +145,11 @@
Exámenes
detallePedido
+
+ +
Pre-servicios
+
RCXC / SC
+
@@ -183,6 +203,7 @@ async function previewAutomation() { document.getElementById('cnt-pacientes').textContent = data.pacientes; document.getElementById('cnt-recepciones').textContent = data.recepciones; document.getElementById('cnt-examenes').textContent = data.examenes; + document.getElementById('cnt-preservicios').textContent = data.preservicios || 0; document.getElementById('preview-fecha').textContent = data.fecha; const table = document.getElementById('preview-table'); @@ -232,6 +253,7 @@ function abrirModalConfirmar() { document.getElementById('modal-cnt-pac').textContent = d.pacientes; document.getElementById('modal-cnt-rec').textContent = d.recepciones; document.getElementById('modal-cnt-exa').textContent = d.examenes; + document.getElementById('modal-cnt-ps').textContent = d.preservicios || 0; document.getElementById('modal-lista-pac').innerHTML = d.pacientes_preview.map(p => `
@@ -260,6 +282,8 @@ async function confirmarEnvio() { document.getElementById('p1-badge').className = 'text-sm text-blue-500'; document.getElementById('p2-badge').textContent = 'Esperando...'; document.getElementById('p2-badge').className = 'text-sm text-gray-400'; + document.getElementById('p3-badge').textContent = 'Esperando...'; + document.getElementById('p3-badge').className = 'text-sm text-gray-400'; const contrato = document.getElementById('contrato-input').value.trim(); const form = new FormData(); @@ -291,8 +315,14 @@ async function confirmarEnvio() { col2: `Pac. ${d.paciente} — ${d.examenes} examen(es)`, col3: `${d.ok ? '✅' : '❌'} ${d.msg || ''}`, ok: d.ok, }))); + renderBadge('p3-badge', r.paso3_preserv.enviados, r.paso3_preserv.errores); + renderDetalle('p3-detalle', r.paso3_preserv.detalle.map(d => ({ + col1: `${d.factura}`, + col2: `Pac. ${d.paciente} — ${d.examenes} examen(es)`, + col3: `${d.ok ? '✅' : '❌'} ${d.msg || ''}`, ok: d.ok, + }))); - const totalErr = r.paso1_terceros.errores + r.paso2_rda.errores; + const totalErr = r.paso1_terceros.errores + r.paso2_rda.errores + r.paso3_preserv.errores; showToast(totalErr === 0 ? 'Envío completado sin errores' : `Completado con ${totalErr} error(es)`, totalErr === 0 ? 'success' : 'warning'); } catch (e) {