From 73062af8bc23478fe404dc034c54551c3d6569e0 Mon Sep 17 00:00:00 2001 From: Lizandro Guarnizo <77708265+lizandrogd@users.noreply.github.com> Date: Sat, 11 Jul 2026 14:29:39 -0500 Subject: [PATCH] feat: paso 4 ventas en automation + fixes varios MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Automation: agrega Paso 4 (Ventas/Crear) con fetch _SQL_VENTAS, filtro por excluded_ventas_set, loop de envío y log de actividad - Automation: _guardar_envio ahora almacena idrecepcion y contrato para que /ventas reconozca registros enviados por automation - Contratos: corrige NameError en toggle_excluir (faltaba request: Request) - Ventas: agrega check if not q en send_one y send para evitar TypeError - Ventas HTML: agrega columna Contrato en la tabla de facturas - json_generator: normaliza etnia >2 chars a '99' (ej: 999 → 99) - json_generator: agrega campo ciudadExp usando el mismo cod_ciudad Co-Authored-By: Claude Sonnet 4.6 --- app/routes/automation.py | 123 ++++++++++++++++++++++++++++++--- app/routes/contratos.py | 91 ++++++++++++------------ app/routes/queries.py | 16 ++--- app/routes/ventas.py | 4 ++ app/services/json_generator.py | 3 +- app/templates/automation.html | 76 +++++++++++++++++++- app/templates/ventas.html | 4 +- 7 files changed, 249 insertions(+), 68 deletions(-) diff --git a/app/routes/automation.py b/app/routes/automation.py index ce616b8..84d7a85 100644 --- a/app/routes/automation.py +++ b/app/routes/automation.py @@ -11,10 +11,11 @@ from app.services.json_generator import ( generar_tercero_api, generar_rda_paciente, agrupar_por_recepcion, + generar_factura_venta, ) from app.services.api_client import get_tns_token, TNS_BASE from app.services.whatsapp_sync import sync_paciente, sync_todos, guardar_sync_log -from app.routes.contratos import load_contrato_map, load_excluded_set, load_sin_contrato_set +from app.routes.contratos import load_contrato_map, load_excluded_set, load_sin_contrato_set, load_excluded_ventas_set from app.utils.activity import log_activity, get_ip router = APIRouter(prefix="/automation", tags=["automation"]) @@ -152,6 +153,31 @@ ORDER BY ps.ID_PS, rel.COD_EXAMEN """ +_SQL_VENTAS = """ +SELECT + r.IDRECEPCION, + r.PREFIJO, + r.NUM_FACTURA, + r.FECHA_RECEPCION, + r.COD_PACIENTE, + r.NIT_EMPRESA, + r.VALORTOTAL, + r.VALORDESC, + 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, + TRIM(e.CODCONTRATO) AS CODCONTRATO +FROM RECEPCION r +JOIN RELACION rel ON rel.IDRECEPCION = r.IDRECEPCION +LEFT JOIN EXAMEN ex ON TRIM(ex.CODIGO) = TRIM(rel.COD_EXAMEN) +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 +ORDER BY r.IDRECEPCION +""" + + def _agrupar_por_presserv(rows: list) -> dict: """Agrupa filas de pre-servicios por ID_PS.""" from collections import defaultdict @@ -267,11 +293,17 @@ async def preview_automation( return JSONResponse({"success": False, "message": f"Error BD RDA: {err2}"}) ok3, err3, rows_ps = fb.execute_query(_SQL_PRESERV, {"fecha_ini": fecha_ini, "fecha_fin": fecha_fin}) - fb.disconnect() if not ok3: + fb.disconnect() return JSONResponse({"success": False, "message": f"Error BD Pre-servicios: {err3}"}) + ok4, err4, rows_vta = fb.execute_query(_SQL_VENTAS, {"fecha_ini": fecha_ini, "fecha_fin": fecha_fin}) + fb.disconnect() + if not ok4: + return JSONResponse({"success": False, "message": f"Error BD Ventas: {err4}"}) + excluded = load_excluded_set() + excluded_ventas = load_excluded_ventas_set() grupos_all = agrupar_por_recepcion(rows_rda) grupos = {k: v for k, v in grupos_all.items() @@ -345,6 +377,25 @@ async def preview_automation( if rda_por_pac.get(str(p.get("CODIGO", ""))) ] + grupos_vta_all = agrupar_por_recepcion(rows_vta) + grupos_vta = {k: v for k, v in grupos_vta_all.items() + if str(v[0].get("CODCONTRATO") or "").strip() not in excluded_ventas} + + ventas_preview = [] + for id_rec, grupo_rows in grupos_vta.items(): + num_fac = str(grupo_rows[0].get("NUM_FACTURA") or "").strip() + prefijo = str(grupo_rows[0].get("PREFIJO") or "").strip() + venta_json = generar_factura_venta(grupo_rows, default_vendedor="00", + default_prefijo=prefijo_def, numero_override=num_fac) + ventas_preview.append({ + "factura": f"{prefijo}-{num_fac}", + "paciente": str(grupo_rows[0].get("COD_PACIENTE", "")), + "fecha": str(grupo_rows[0].get("FECHA_RECEPCION", ""))[:10], + "contrato": str(grupo_rows[0].get("CODCONTRATO") or "").strip(), + "examenes": len(grupo_rows), + "json": venta_json, + }) + return JSONResponse({ "success": True, "fecha": fecha, @@ -353,8 +404,10 @@ async def preview_automation( "examenes": total_examenes, "excluidos": excluidos_count, "preservicios": len(grupos_ps), + "ventas": len(grupos_vta), "pacientes_preview": pacientes_preview, "preservicios_preview": preservicios_preview, + "ventas_preview": ventas_preview, }) @@ -401,15 +454,21 @@ async def run_automation( # ── Pacientes EPS (para registrar terceros en paso 1) ───────────────────── ok4, err4, rows_pac_eps = fb.execute_query(_SQL_PACIENTES_EPS, {"fecha_ini": fecha_ini, "fecha_fin": fecha_fin}) - fb.disconnect() if not ok4: + fb.disconnect() return JSONResponse({"success": False, "message": f"Error Firebird pacientes EPS: {err4}"}) + # ── Ventas (Factura Venta) ──────────────────────────────────────────────── + ok5, err5, rows_vta = fb.execute_query(_SQL_VENTAS, {"fecha_ini": fecha_ini, "fecha_fin": fecha_fin}) + fb.disconnect() + if not ok5: + return JSONResponse({"success": False, "message": f"Error Firebird Ventas: {err5}"}) + # Fusionar pacientes: particulares + EPS, deduplicando por CODIGO codigos_vistos = {str(r.get("CODIGO")) for r in rows_pac} rows_pac_todos = list(rows_pac) + [r for r in rows_pac_eps if str(r.get("CODIGO")) not in codigos_vistos] - if not rows_pac_todos and not rows_rda and not rows_ps: + if not rows_pac_todos and not rows_rda and not rows_ps and not rows_vta: return JSONResponse({"success": False, "message": f"No hay datos para la fecha {fecha}"}) # ── Login TNS ───────────────────────────────────────────────────────────── @@ -430,6 +489,7 @@ async def run_automation( "paso1_terceros": {"enviados": 0, "errores": 0, "detalle": []}, "paso2_rda": {"enviados": 0, "errores": 0, "detalle": []}, "paso3_preserv": {"enviados": 0, "errores": 0, "detalle": []}, + "paso4_ventas": {"enviados": 0, "errores": 0, "detalle": []}, } # ── PASO 1: Enviar Terceros (particulares + EPS) ───────────────────────── @@ -558,27 +618,72 @@ async def run_automation( fecha_inicio=fecha, fecha_fin=fecha, servicios=len(grupo_rows)) + # ── PASO 4: Enviar Facturas Venta ───────────────────────────────────────── + excluded_ventas = load_excluded_ventas_set() + grupos_vta_all = agrupar_por_recepcion(rows_vta) + grupos_vta = {k: v for k, v in grupos_vta_all.items() + if str(v[0].get("CODCONTRATO") or "").strip() not in excluded_ventas} + resultado["paso4_ventas"]["excluidos"] = len(grupos_vta_all) - len(grupos_vta) + + endpoint_venta = f"{TNS_BASE}/v2/facturacion/Ventas/Crear" + async with httpx.AsyncClient(timeout=timeout) as client: + for id_rec, grupo_rows in grupos_vta.items(): + num_fac = str(grupo_rows[0].get("NUM_FACTURA") or "").strip() + prefijo = str(grupo_rows[0].get("PREFIJO") or "").strip() + venta_json = generar_factura_venta(grupo_rows, default_vendedor="00", + default_prefijo=prefijo_def, numero_override=num_fac) + factura_display = f"{prefijo}-{num_fac}" + contrato_vta = str(grupo_rows[0].get("CODCONTRATO") or "").strip() + try: + resp = await client.post(endpoint_venta, json=venta_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["paso4_ventas"]["enviados"] += 1 + else: + resultado["paso4_ventas"]["errores"] += 1 + + resultado["paso4_ventas"]["detalle"].append({ + "factura": factura_display, + "paciente": str(grupo_rows[0].get("COD_PACIENTE", "")), + "examenes": len(grupo_rows), + "ok": ok, "msg": msg, + }) + + _guardar_envio(user["user_id"], "ventas", factura_display, venta_json, msg, ok, + fecha_inicio=fecha, fecha_fin=fecha, servicios=len(grupo_rows), + idrecepcion=id_rec, contrato=contrato_vta) + r1 = resultado["paso1_terceros"] r2 = resultado["paso2_rda"] r3 = resultado["paso3_preserv"] + r4 = resultado["paso4_ventas"] log_activity(user["user_id"], user["username"], "automation_run", 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", + f"PreServ: {r3['enviados']} OK/{r3['errores']} err | " + f"Ventas: {r4['enviados']} OK/{r4['errores']} err", get_ip(request)) 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): + fecha_inicio=None, fecha_fin=None, servicios=0, + idrecepcion=None, contrato=None): try: conn = get_connection() conn.execute(""" - INSERT INTO envios (user_id, tipo, factura, fecha_inicio, fecha_fin, + INSERT INTO envios (user_id, tipo, factura, idrecepcion, contrato, + fecha_inicio, fecha_fin, pacientes_count, servicios_count, status, json_enviado, respuesta_api, created_at) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, ( - user_id, tipo, factura, fecha_inicio, fecha_fin, + user_id, tipo, factura, idrecepcion, contrato, + fecha_inicio, fecha_fin, 1, servicios, "success" if ok else "error", json_lib.dumps(json_data, indent=2, ensure_ascii=False)[:10000], diff --git a/app/routes/contratos.py b/app/routes/contratos.py index ae3d3ed..417c612 100644 --- a/app/routes/contratos.py +++ b/app/routes/contratos.py @@ -7,52 +7,53 @@ from app.utils.activity import log_activity, get_ip router = APIRouter(prefix="/contratos", tags=["contratos"]) # (numero_contrato, nit_empresa, tipo_usuario, descripcion, excluir_rda, sin_contrato, excluir_ventas) +# excluir_ventas=0 → activo para Ventas/Crear | excluir_ventas=1 → bloqueado para ventas _SEED = [ - ("001", "860078828", "11", "EPS SANITAS", 0, 0, 0), - ("002", "830054904", "11", "EPS COMPENSAR", 0, 0, 0), - ("003", "900278729", "12", "PARTICULAR", 0, 0, 0), - ("004", "800106339", "11", "EPS NUEVA EPS", 0, 0, 0), - ("005", "800153424", "11", "EPS SURA", 0, 0, 0), - ("006", "805009741", "11", "EPS COOMEVA", 0, 0, 0), - ("007", "860002183", "11", "EPS FAMISANAR", 0, 0, 0), - ("008", "860002503", "11", "EPS CRUZ BLANCA", 0, 0, 0), - ("009", "860027404", "11", "EPS COLSANITAS", 0, 0, 0), - ("010", "860039988", "11", "EPS SALUD TOTAL", 0, 0, 0), - ("011", "890903790", "11", "EPS SAVIA SALUD", 0, 0, 0), - ("012", "900178724", "11", "EPS MUTUAL SER", 0, 0, 0), - ("034", "860078828", "11", "EPS SANITAS (alt)", 0, 0, 0), - ("035", "860078828", "11", "EPS SANITAS (alt)", 0, 0, 0), - ("036", "860078828", "11", "EPS SANITAS (alt)", 0, 0, 0), - ("20062026", "800182856", "01", "SUBSIDIADO", 0, 0, 0), - ("CW225489", "899999068", "07", "POLIZA / SEGURO", 0, 0, 0), - # Contratos excluidos del envío TNS - ("013", "", "11", "MEDILAVORO S.A.S", 0, 0, 0), - ("014", "", "11", "LABORATORIO UROCLINICO",0, 0, 0), - ("015", "", "11", "ANDRES AFANADOR VILLAMIZAR", 1, 0, 0), - ("016", "", "11", "OMAR FERNANDO RIBERO GOMEZ", 1, 0, 0), - ("017", "", "11", "CLAUDIA BELEN JULIO SEPULVEDA", 1, 0, 0), - ("018", "", "11", "LABORATORIO MICROBIOLOGICO - MARGIE OJEDA", 1, 0, 0), - ("019", "", "11", "LABORATORIO TOXICOLOGICO - MARTHA MORALES", 1, 0, 0), - ("020", "", "11", "MARTHA LUCIA GALLARDO", 1, 0, 0), - ("021", "", "11", "LABORATORIO VILMA OROZCO AYALA", 1, 0, 0), - ("022", "", "11", "CLINICA SAN JOSE DE CUCUTA", 1, 0, 0), - ("023", "", "11", "URONORTE S.A", 1, 0, 0), - ("024", "", "11", "LABORATORIO CLINICO BIOLAB S.A.S", 1, 0, 0), - ("025", "", "11", "JOEL LEONARDO CARRILLO CORREDOR", 1, 0, 0), - ("026", "", "11", "ROLANDO IVAN PENARANDA DEVIA", 1, 0, 0), - ("027", "", "11", "JOSE WILMER GARCIA CALDERON", 1, 0, 0), - ("028", "", "11", "ONCOMEDICAL IPS S.A.S", 1, 0, 0), - ("029", "", "11", "GENETIX S.A.S", 1, 0, 0), - ("030", "", "11", "NORFETUS S.A.S", 1, 0, 0), - ("031", "", "11", "TBTB GLOBAL LAB S.A.S", 1, 0, 0), - ("032", "", "11", "COLGENES S.A.S", 1, 0, 0), - ("033", "", "11", "IPS FIGURAS SPA CUCUTA S.A.S", 1, 0, 0), - ("037", "", "11", "CLINICA URGENCIAS LA MERCED", 1, 0, 0), - ("038", "", "11", "CLINICA COLSANITAS S.A.", 1, 0, 0), - ("039", "", "11", "GOMEZ GIL JOSE JESUS", 1, 0, 0), + ("001", "860078828", "11", "EPS SANITAS", 0, 0, 1), + ("002", "830054904", "11", "EPS COMPENSAR", 0, 0, 1), + ("003", "900278729", "12", "PARTICULAR", 0, 0, 1), + ("004", "800106339", "11", "EPS NUEVA EPS", 0, 0, 1), + ("005", "800153424", "11", "EPS SURA", 0, 0, 1), + ("006", "805009741", "11", "EPS COOMEVA", 0, 0, 1), + ("007", "860002183", "11", "EPS FAMISANAR", 0, 0, 1), + ("008", "860002503", "11", "EPS CRUZ BLANCA", 0, 0, 1), + ("009", "860027404", "11", "EPS COLSANITAS", 0, 0, 1), + ("010", "860039988", "11", "EPS SALUD TOTAL", 0, 0, 1), + ("011", "890903790", "11", "EPS SAVIA SALUD", 0, 0, 1), + ("012", "900178724", "11", "EPS MUTUAL SER", 0, 0, 1), + ("034", "860078828", "11", "EPS SANITAS (alt)", 0, 0, 1), + ("035", "860078828", "11", "EPS SANITAS (alt)", 0, 0, 1), + ("036", "860078828", "11", "EPS SANITAS (alt)", 0, 0, 1), + ("20062026", "800182856", "01", "SUBSIDIADO", 0, 0, 1), + ("CW225489", "899999068", "07", "POLIZA / SEGURO", 0, 0, 1), + # Contratos excluidos del envío RDA + ("013", "", "11", "MEDILAVORO S.A.S", 0, 0, 1), + ("014", "", "11", "LABORATORIO UROCLINICO",0, 0, 1), + ("015", "", "11", "ANDRES AFANADOR VILLAMIZAR", 1, 0, 1), + ("016", "", "11", "OMAR FERNANDO RIBERO GOMEZ", 1, 0, 1), + ("017", "", "11", "CLAUDIA BELEN JULIO SEPULVEDA", 1, 0, 1), + ("018", "", "11", "LABORATORIO MICROBIOLOGICO - MARGIE OJEDA", 1, 0, 1), + ("019", "", "11", "LABORATORIO TOXICOLOGICO - MARTHA MORALES", 1, 0, 1), + ("020", "", "11", "MARTHA LUCIA GALLARDO", 1, 0, 1), + ("021", "", "11", "LABORATORIO VILMA OROZCO AYALA", 1, 0, 1), + ("022", "", "11", "CLINICA SAN JOSE DE CUCUTA", 1, 0, 1), + ("023", "", "11", "URONORTE S.A", 1, 0, 1), + ("024", "", "11", "LABORATORIO CLINICO BIOLAB S.A.S", 1, 0, 1), + ("025", "", "11", "JOEL LEONARDO CARRILLO CORREDOR", 1, 0, 1), + ("026", "", "11", "ROLANDO IVAN PENARANDA DEVIA", 1, 0, 1), + ("027", "", "11", "JOSE WILMER GARCIA CALDERON", 1, 0, 1), + ("028", "", "11", "ONCOMEDICAL IPS S.A.S", 1, 0, 1), + ("029", "", "11", "GENETIX S.A.S", 1, 0, 1), + ("030", "", "11", "NORFETUS S.A.S", 1, 0, 1), + ("031", "", "11", "TBTB GLOBAL LAB S.A.S", 1, 0, 1), + ("032", "", "11", "COLGENES S.A.S", 1, 0, 1), + ("033", "", "11", "IPS FIGURAS SPA CUCUTA S.A.S", 1, 0, 1), + ("037", "", "11", "CLINICA URGENCIAS LA MERCED", 1, 0, 1), + ("038", "", "11", "CLINICA COLSANITAS S.A.", 1, 0, 1), + ("039", "", "11", "GOMEZ GIL JOSE JESUS", 1, 0, 1), ("040", "", "11", "MARTHA LILIANA SALGAR GALLEGO", 1, 1, 0), - ("041", "", "11", "GENCELL PHARMA S.A.S", 1, 0, 0), - ("042", "", "07", "AXA COLPATRIA HYC", 1, 0, 0), + ("041", "", "11", "GENCELL PHARMA S.A.S", 1, 0, 1), + ("042", "", "07", "AXA COLPATRIA HYC", 1, 0, 1), ] @@ -200,7 +201,7 @@ async def contrato_update( @router.post("/toggle-excluir/{contrato_id}") -async def toggle_excluir(contrato_id: int, user: dict = Depends(get_current_user)): +async def toggle_excluir(contrato_id: int, request: Request, user: dict = Depends(get_current_user)): conn = get_connection() conn.execute( "UPDATE contratos SET excluir = CASE WHEN excluir=1 THEN 0 ELSE 1 END WHERE id=?", diff --git a/app/routes/queries.py b/app/routes/queries.py index af8d538..63d03a0 100644 --- a/app/routes/queries.py +++ b/app/routes/queries.py @@ -188,7 +188,7 @@ ORDER BY ps.ID_PS, rel.COD_EXAMEN""", "description": "Pre-servicio por número PS (buscar RCXC05291 → escribe solo el número)" }, { - "name": "Factura Venta 040 por fecha", + "name": "Factura Venta por fecha", "query_type": "ventas", "query_text": """SELECT r.IDRECEPCION, @@ -203,19 +203,18 @@ ORDER BY ps.ID_PS, 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, - e.CODCONTRATO + TRIM(e.CODCONTRATO) AS CODCONTRATO FROM RECEPCION r JOIN RELACION rel ON rel.IDRECEPCION = r.IDRECEPCION LEFT JOIN EXAMEN ex ON TRIM(ex.CODIGO) = TRIM(rel.COD_EXAMEN) 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 TRIM(e.CODCONTRATO) = '040' ORDER BY r.IDRECEPCION""", - "description": "Facturas de venta contrato 040 en un rango de fechas" + "description": "Facturas de venta en un rango de fechas (filtra por excluir_ventas en /contratos)" }, { - "name": "Factura Venta 040 por número", + "name": "Factura Venta por número", "query_type": "ventas", "query_text": """SELECT r.IDRECEPCION, @@ -230,15 +229,14 @@ ORDER BY r.IDRECEPCION""", COALESCE(NULLIF(TRIM(ex.NUM_ISS), ''), TRIM(rel.COD_EXAMEN)) AS CUPS, rel.PRECIO, COALESCE(t.VALOR, rel.PRECIO) AS PRECIO_TARIFA, - e.CODCONTRATO + TRIM(e.CODCONTRATO) AS CODCONTRATO FROM RECEPCION r JOIN RELACION rel ON rel.IDRECEPCION = r.IDRECEPCION LEFT JOIN EXAMEN ex ON TRIM(ex.CODIGO) = TRIM(rel.COD_EXAMEN) 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.NUM_FACTURA = :num_factura - AND TRIM(e.CODCONTRATO) = '040'""", - "description": "Factura de venta contrato 040 por número de factura" +WHERE r.NUM_FACTURA = :num_factura""", + "description": "Factura de venta por número (filtra por excluir_ventas en /contratos)" }, ] diff --git a/app/routes/ventas.py b/app/routes/ventas.py index a5aece8..9905444 100644 --- a/app/routes/ventas.py +++ b/app/routes/ventas.py @@ -188,6 +188,8 @@ async def send_one( q = conn.execute("SELECT * FROM queries WHERE id = ?", (query_id,)).fetchone() cfg = {r["key"]: r["value"] for r in conn.execute("SELECT * FROM config").fetchall()} conn.close() + if not q: + return JSONResponse({"success": False, "message": "Consulta no encontrada"}) rows, err = _query_rows(cfg, q["query_text"], factura, fecha_inicio, fecha_fin) if rows is None: @@ -243,6 +245,8 @@ async def send_ventas( q = conn.execute("SELECT * FROM queries WHERE id = ?", (query_id,)).fetchone() cfg = {r["key"]: r["value"] for r in conn.execute("SELECT * FROM config").fetchall()} conn.close() + if not q: + return JSONResponse({"success": False, "message": "Consulta no encontrada"}) rows, err = _query_rows(cfg, q["query_text"], factura, fecha_inicio, fecha_fin) if rows is None: diff --git a/app/services/json_generator.py b/app/services/json_generator.py index 0f3aa28..8bd0fac 100644 --- a/app/services/json_generator.py +++ b/app/services/json_generator.py @@ -106,7 +106,8 @@ def generar_tercero_api(row: dict) -> dict: "sexo": sexo, "identidadGenero": identidad_genero, "zona": zona, - "etnia": str(row.get("CODETNIA") or "99").strip(), + "etnia": (lambda v: v if v and len(v) <= 2 else "99")(str(row.get("CODETNIA") or "").strip()), + "ciudadExp": cod_ciudad, "antecedentes": "", "cliente": "S", "enfermedadCronica": False, diff --git a/app/templates/automation.html b/app/templates/automation.html index 6eef50e..cb44b18 100644 --- a/app/templates/automation.html +++ b/app/templates/automation.html @@ -48,7 +48,7 @@
-
+
0
Pacientes
@@ -66,6 +66,11 @@
Pre-servicios
RCXC / SC
+
+
0
+
Ventas
+
Ventas/Crear
+

Pacientes — clic para ver sus recepciones:

@@ -75,6 +80,10 @@

Pre-servicios RCXC / SC — clic para ver JSON:

+
@@ -110,6 +119,16 @@ +
+
+
+
4
+ Facturas Venta +
+ Esperando... +
+ +
@@ -133,7 +152,7 @@ :

-
+
Terceros
@@ -154,6 +173,11 @@
Pre-servicios
RCXC / SC
+
+ +
Ventas
+
Ventas/Crear
+
@@ -214,6 +238,7 @@ async function previewAutomation() { document.getElementById('cnt-recepciones').textContent = data.recepciones; document.getElementById('cnt-examenes').textContent = data.examenes; document.getElementById('cnt-preservicios').textContent = data.preservicios || 0; + document.getElementById('cnt-ventas').textContent = data.ventas || 0; document.getElementById('preview-fecha').textContent = data.fecha; const psTable = document.getElementById('preview-ps-table'); @@ -240,6 +265,31 @@ async function previewAutomation() { psSection.classList.add('hidden'); } + const vtaTable = document.getElementById('preview-vta-table'); + const vtaSection = document.getElementById('ventas-section'); + if (data.ventas_preview && data.ventas_preview.length > 0) { + vtaTable.innerHTML = data.ventas_preview.map((vta, i) => { + const key = `vta-json-${i}`; + _rdaJsonStore[key] = vta.json; + return ` +
+
+ + ${vta.factura} + ${vta.fecha} + ${vta.contrato || '—'} + Pac. ${vta.paciente} + ${vta.examenes} examen(es) +
+ +
`; + }).join(''); + vtaSection.classList.remove('hidden'); + } else { + vtaSection.classList.add('hidden'); + } + const table = document.getElementById('preview-table'); if (data.pacientes === 0) { table.innerHTML = '
No hay pacientes para esta fecha
'; @@ -297,6 +347,7 @@ function abrirModalConfirmar() { 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-cnt-vta').textContent = d.ventas || 0; document.getElementById('modal-lista-pac').innerHTML = d.pacientes_preview.map(p => `
@@ -327,6 +378,8 @@ async function confirmarEnvio() { 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'; + document.getElementById('p4-badge').textContent = 'Esperando...'; + document.getElementById('p4-badge').className = 'text-sm text-gray-400'; const contrato = document.getElementById('contrato-input').value.trim(); const form = new FormData(); @@ -364,8 +417,14 @@ async function confirmarEnvio() { col2: `Pac. ${d.paciente} — ${d.examenes} examen(es)`, col3: `${d.ok ? '✅' : '❌'} ${d.msg || ''}`, ok: d.ok, }))); + renderBadge('p4-badge', r.paso4_ventas.enviados, r.paso4_ventas.errores); + renderDetalle('p4-detalle', r.paso4_ventas.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 + r.paso3_preserv.errores; + const totalErr = r.paso1_terceros.errores + r.paso2_rda.errores + r.paso3_preserv.errores + r.paso4_ventas.errores; showToast(totalErr === 0 ? 'Envío completado sin errores' : `Completado con ${totalErr} error(es)`, totalErr === 0 ? 'success' : 'warning'); } catch (e) { @@ -405,6 +464,17 @@ function togglePs(id, row) { if (chev) chev.style.transform = hidden ? 'rotate(90deg)' : ''; } +function toggleVta(id, row) { + const div = document.getElementById(id); + const chev = row.querySelector('[id^="vtachev-"]'); + const hidden = div.classList.contains('hidden'); + if (hidden && _rdaJsonStore[id]) { + div.textContent = JSON.stringify(_rdaJsonStore[id], null, 2); + } + div.classList.toggle('hidden', !hidden); + if (chev) chev.style.transform = hidden ? 'rotate(90deg)' : ''; +} + function renderBadge(id, enviados, errores) { const el = document.getElementById(id); if (errores === 0) { diff --git a/app/templates/ventas.html b/app/templates/ventas.html index 19b070b..645e70c 100644 --- a/app/templates/ventas.html +++ b/app/templates/ventas.html @@ -178,6 +178,7 @@ function renderTabla() { IDRECEP. Factura Paciente + Contrato Exámenes Estado @@ -217,7 +218,8 @@ function filaHtml(item) { ${id} ${escHtml(String(item.factura || '-'))} ${escHtml(String(item.paciente || '-'))} - ${escHtml(examsStr)} + ${escHtml(String(item.contrato || '-'))} + ${escHtml(examsStr)} ${badgeHtml}