From 408ea50d21d1269a71a65276c1a475dc08605e35 Mon Sep 17 00:00:00 2001 From: Lizandro Guarnizo <77708265+lizandrogd@users.noreply.github.com> Date: Thu, 18 Jun 2026 12:25:44 -0500 Subject: [PATCH] hola --- app/auth.py | 25 +- app/database.py | 13 - app/models.py | 18 - app/routes/automation.py | 164 +++--- app/routes/config.py | 1 - app/routes/queries.py | 649 ++++++++++++------------ app/routes/terceros.py | 76 ++- app/routes/transaccion.py | 142 ++---- app/services/firebird_service.py | 14 +- app/services/json_generator.py | 11 + app/templates/queries.html | 837 ++++++++++++++++--------------- main.py | 192 ++++--- 12 files changed, 1042 insertions(+), 1100 deletions(-) diff --git a/app/auth.py b/app/auth.py index abf2ca6..b9fddfd 100644 --- a/app/auth.py +++ b/app/auth.py @@ -1,10 +1,13 @@ +import os import bcrypt -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone +from typing import Optional from jose import JWTError, jwt -from fastapi import Depends, HTTPException, status +from fastapi import Depends, HTTPException, Request, status from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials -SECRET_KEY = "rips-manager-secret-key-change-in-production" +# ponytail: fallback inseguro — set RIPS_SECRET_KEY en producción +SECRET_KEY = os.environ.get("RIPS_SECRET_KEY", "rips-manager-secret-key-change-in-production") ALGORITHM = "HS256" ACCESS_TOKEN_EXPIRE_HOURS = 12 @@ -23,12 +26,11 @@ def create_token(user_id: int, username: str) -> str: payload = { "user_id": user_id, "username": username, + "exp": datetime.now(timezone.utc) + timedelta(hours=ACCESS_TOKEN_EXPIRE_HOURS), } return jwt.encode(payload, SECRET_KEY, algorithm=ALGORITHM) -from typing import Optional - def decode_token(token: str) -> Optional[dict]: try: payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) @@ -37,23 +39,14 @@ def decode_token(token: str) -> Optional[dict]: return None -from fastapi import Request - - def get_current_user(request: Request, credentials: HTTPAuthorizationCredentials = Depends(security)): if hasattr(request.state, "user") and request.state.user: return request.state.user if credentials is None: - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Not authenticated", - ) + raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Not authenticated") payload = decode_token(credentials.credentials) if payload is None: - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Invalid token", - ) + raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token") return payload diff --git a/app/database.py b/app/database.py index a0a1f0f..5cc8b73 100644 --- a/app/database.py +++ b/app/database.py @@ -39,19 +39,6 @@ def init_db(): created_at TEXT NOT NULL DEFAULT (datetime('now')) ); - CREATE TABLE IF NOT EXISTS logs ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - user_id INTEGER, - action TEXT NOT NULL, - step TEXT, - status TEXT NOT NULL CHECK(status IN ('success','error')), - payload TEXT, - response TEXT, - error_message TEXT, - created_at TEXT NOT NULL DEFAULT (datetime('now')), - FOREIGN KEY (user_id) REFERENCES users(id) - ); - CREATE TABLE IF NOT EXISTS envios ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER, diff --git a/app/models.py b/app/models.py index 2672773..561469d 100644 --- a/app/models.py +++ b/app/models.py @@ -13,26 +13,8 @@ class UserLogin(BaseModel): password: str -class ConfigUpdate(BaseModel): - key: str - value: str - - class QueryCreate(BaseModel): name: str query_type: str query_text: str description: Optional[str] = None - - -class QueryUpdate(BaseModel): - name: Optional[str] = None - query_text: Optional[str] = None - description: Optional[str] = None - - -class SendRequest(BaseModel): - tipo: str - fecha_inicio: str - fecha_fin: str - factura: Optional[str] = None diff --git a/app/routes/automation.py b/app/routes/automation.py index 218465c..9b3b88f 100644 --- a/app/routes/automation.py +++ b/app/routes/automation.py @@ -1,9 +1,12 @@ +import json as json_lib +import httpx +from datetime import datetime from fastapi import APIRouter, Request, Form, Depends from fastapi.responses import JSONResponse from app.database import get_connection from app.auth import get_current_user -from app.services.firebird_service import FirebirdService -from app.services.json_generator import generar_terceros, generar_transaccion +from app.services.firebird_service import get_firebird_from_config +from app.services.json_generator import generar_terceros, generar_transaccion, agrupar_por_factura router = APIRouter(prefix="/automation", tags=["automation"]) @@ -31,11 +34,6 @@ async def run_automation( fecha_fin: str = Form(...), factura: str = Form(""), ): - import json as json_lib - import httpx - from datetime import datetime - from collections import defaultdict - conn = get_connection() q_terceros = conn.execute("SELECT * FROM queries WHERE id = ?", (query_terceros_id,)).fetchone() q_trans = conn.execute("SELECT * FROM queries WHERE id = ?", (query_transaccion_id,)).fetchone() @@ -45,15 +43,8 @@ async def run_automation( if not q_terceros or not q_trans: return JSONResponse({"success": False, "message": "Consultas no encontradas"}) - fb = FirebirdService() - fb_success, fb_msg = fb.connect( - configs.get("firebird_host", "localhost"), - int(configs.get("firebird_port", 3050)), - configs.get("firebird_database", ""), - configs.get("firebird_user", "SYSDBA"), - configs.get("firebird_password", "masterkey"), - ) - if not fb_success: + fb, fb_ok, fb_msg = get_firebird_from_config(configs) + if not fb_ok: return JSONResponse({"success": False, "message": f"Error Firebird: {fb_msg}"}) api_url = configs.get("api_url", "") @@ -74,7 +65,10 @@ async def run_automation( if ":doc_num" in q_terceros["query_text"]: params["doc_num"] = "" - success, error, rows = fb.execute_query(q_terceros["query_text"], params if ":fecha_ini" in q_terceros["query_text"] else None) + success, error, rows = fb.execute_query( + q_terceros["query_text"], + params if ":fecha_ini" in q_terceros["query_text"] else None + ) if not success: resultado["paso1_terceros"] = {"status": "error", "message": error} @@ -85,40 +79,44 @@ async def run_automation( terceros_errores = 0 pacientes_enviados = [] - for row in rows: - tercero_json = generar_terceros(row) - doc_id = tercero_json["numDocumentoIdentificacion"] - if doc_id in pacientes_enviados: - continue - pacientes_enviados.append(doc_id) + async with httpx.AsyncClient(timeout=int(configs.get("api_timeout", 30))) as client: + for row in rows: + tercero_json = generar_terceros(row) + doc_id = tercero_json["numDocumentoIdentificacion"] + if doc_id in pacientes_enviados: + continue + pacientes_enviados.append(doc_id) - try: - async with httpx.AsyncClient(timeout=int(configs.get("api_timeout", 30))) as client: + resp_ok = False + resp_text = "" + try: if api_method == "POST": resp = await client.post(api_url + "/terceros", json=tercero_json, headers=headers) else: resp = await client.put(api_url + "/terceros", json=tercero_json, headers=headers) - - if resp.is_success: - terceros_enviados += 1 - else: + resp_ok = resp.is_success + resp_text = resp.text[:1000] + if resp_ok: + terceros_enviados += 1 + else: + terceros_errores += 1 + except Exception as e: terceros_errores += 1 - except Exception as e: - terceros_errores += 1 + resp_text = str(e) - conn = get_connection() - conn.execute(""" - INSERT INTO envios (user_id, tipo, factura, status, json_enviado, respuesta_api, created_at) - VALUES (?, ?, ?, ?, ?, ?, ?) - """, ( - user["user_id"], "terceros", factura or "AUTO", - "success" if resp.is_success else "error", - json_lib.dumps(tercero_json, indent=2, ensure_ascii=False), - resp.text[:1000] if resp.is_success else str(e), - datetime.now().isoformat(), - )) - conn.commit() - conn.close() + conn = get_connection() + conn.execute(""" + INSERT INTO envios (user_id, tipo, factura, status, json_enviado, respuesta_api, created_at) + VALUES (?, ?, ?, ?, ?, ?, ?) + """, ( + user["user_id"], "terceros", factura or "AUTO", + "success" if resp_ok else "error", + json_lib.dumps(tercero_json, indent=2, ensure_ascii=False), + resp_text, + datetime.now().isoformat(), + )) + conn.commit() + conn.close() resultado["paso1_terceros"] = { "status": "success" if terceros_errores == 0 else "partial", @@ -140,59 +138,51 @@ async def run_automation( elif not rows: resultado["paso2_transaccion"] = {"status": "error", "message": "No hay servicios para enviar"} else: - grupos = defaultdict(lambda: {"factura": "", "procedimientos": [], "paciente": {}}) - for row in rows: - doc_key = (row.get("tipo_doc_paciente", "CC"), row.get("num_doc_paciente", "")) - fact = row.get("num_factura", factura) - grupos[(fact, doc_key)]["factura"] = fact - grupos[(fact, doc_key)]["procedimientos"].append(dict(row)) - + grupos = agrupar_por_factura(rows, factura) trans_enviados = 0 trans_errores = 0 - for (fact, doc_key), grupo in grupos.items(): - paciente_data = {"tipoDocumentoIdentificacion": doc_key[0], "numDocumentoIdentificacion": doc_key[1]} - trans_json = generar_transaccion( - fact, - configs.get("num_documento_obligado", ""), - paciente_data, - grupo["procedimientos"], - ) + async with httpx.AsyncClient(timeout=int(configs.get("api_timeout", 30))) as client: + for (fact, doc_key), grupo in grupos.items(): + paciente_data = {"tipoDocumentoIdentificacion": doc_key[0], "numDocumentoIdentificacion": doc_key[1]} + trans_json = generar_transaccion( + fact, configs.get("num_documento_obligado", ""), + paciente_data, grupo["procedimientos"], + ) - try: - async with httpx.AsyncClient(timeout=int(configs.get("api_timeout", 30))) as client: + status_ok = False + resp_text = "" + try: if api_method == "POST": resp = await client.post(api_url + "/transaccion", json=trans_json, headers=headers) else: resp = await client.put(api_url + "/transaccion", json=trans_json, headers=headers) + status_ok = resp.is_success + resp_text = resp.text[:1000] + except Exception as e: + resp_text = str(e) - status_ok = resp.is_success - resp_text = resp.text[:1000] - except Exception as e: - status_ok = False - resp_text = str(e) + if status_ok: + trans_enviados += 1 + else: + trans_errores += 1 - if status_ok: - trans_enviados += 1 - else: - trans_errores += 1 - - 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["user_id"], "transaccion", fact, - fecha_inicio, fecha_fin, - 1, len(grupo["procedimientos"]), - "success" if status_ok else "error", - json_lib.dumps(trans_json, indent=2, ensure_ascii=False)[:5000], - resp_text, - datetime.now().isoformat(), - )) - conn.commit() - conn.close() + 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["user_id"], "transaccion", fact, + fecha_inicio, fecha_fin, + 1, len(grupo["procedimientos"]), + "success" if status_ok else "error", + json_lib.dumps(trans_json, indent=2, ensure_ascii=False)[:5000], + resp_text, + datetime.now().isoformat(), + )) + conn.commit() + conn.close() resultado["paso2_transaccion"] = { "status": "success" if trans_errores == 0 else "partial", diff --git a/app/routes/config.py b/app/routes/config.py index f817ff5..5a5361b 100644 --- a/app/routes/config.py +++ b/app/routes/config.py @@ -32,7 +32,6 @@ def ensure_defaults(): @router.get("") async def config_page(request: Request, user: dict = Depends(get_current_user)): - ensure_defaults() conn = get_connection() configs = conn.execute("SELECT * FROM config ORDER BY key").fetchall() conn.close() diff --git a/app/routes/queries.py b/app/routes/queries.py index 89193a3..415567b 100644 --- a/app/routes/queries.py +++ b/app/routes/queries.py @@ -1,325 +1,324 @@ -from fastapi import APIRouter, Request, Form, Depends -from fastapi.responses import RedirectResponse, JSONResponse -from app.database import get_connection -from app.auth import get_current_user -from app.models import QueryCreate - -router = APIRouter(prefix="/queries", tags=["queries"]) - -QUERY_DEFAULTS = [ - { - "name": "Terceros - Datos del paciente", - "query_type": "terceros", - "query_text": """SELECT - p.TIPO_DOCUMENTO as tipo_documento, - p.NUMERO_DOCUMENTO as numero_documento, - p.PRIMER_NOMBRE as primer_nombre, - p.SEGUNDO_NOMBRE as segundo_nombre, - p.PRIMER_APELLIDO as primer_apellido, - p.SEGUNDO_APELLIDO as segundo_apellido, - p.FECHA_NACIMIENTO as fecha_nacimiento, - p.SEXO as cod_sexo, - p.COD_ENTIDAD as cod_entidad, - p.TIPO_USUARIO as tipo_usuario, - p.COD_MUNICIPIO as cod_municipio, - p.ZONA as cod_zona, - p.DIRECCION as direccion -FROM USUAHOS p -WHERE p.NUMERO_DOCUMENTO = :doc_num""", - "description": "Consulta datos maestros del paciente por documento" - }, - { - "name": "Procedimientos por factura", - "query_type": "transaccion", - "query_text": """SELECT - s.CODIGO_CUP as cod_procedimiento, - s.FECHA_ATENCION as fecha_atencion, - s.COD_DIAGNOSTICO as cod_diagnostico, - s.FINALIDAD as finalidad, - s.VIA_INGRESO as via_ingreso, - s.MODALIDAD as modalidad, - s.GRUPO_SERVICIO as grupo_servicio, - s.COD_SERVICIO as cod_servicio, - s.COD_PRESTADOR as cod_prestador, - s.TIPO_DOC_PROFESIONAL as tipo_doc_profesional, - s.NUM_DOC_PROFESIONAL as num_doc_profesional, - s.VR_SERVICIO as vr_servicio, - s.VALOR_PAGO_MODERADOR as valor_pago_moderador, - s.CONCEPTO_RECAUDO as concepto_recaudo, - s.NUM_AUTORIZACION as num_autorizacion -FROM SERVICIOS s -WHERE s.NUM_FACTURA = :factura - AND s.FECHA_ATENCION BETWEEN :fecha_ini AND :fecha_fin""", - "description": "Consulta procedimientos por factura y rango de fechas" - }, - { - "name": "Procedimientos por fecha", - "query_type": "transaccion", - "query_text": """SELECT - s.FACTURA as num_factura, - s.CODIGO_CUP as cod_procedimiento, - s.FECHA_ATENCION as fecha_atencion, - s.COD_DIAGNOSTICO as cod_diagnostico, - s.FINALIDAD as finalidad, - s.VIA_INGRESO as via_ingreso, - s.MODALIDAD as modalidad, - s.GRUPO_SERVICIO as grupo_servicio, - s.COD_SERVICIO as cod_servicio, - s.COD_PRESTADOR as cod_prestador, - s.TIPO_DOC_PROFESIONAL as tipo_doc_profesional, - s.NUM_DOC_PROFESIONAL as num_doc_profesional, - s.VR_SERVICIO as vr_servicio, - p.TIPO_DOCUMENTO as tipo_doc_paciente, - p.NUMERO_DOCUMENTO as num_doc_paciente -FROM SERVICIOS s -JOIN USUAHOS p ON s.COD_PACIENTE = p.COD_PACIENTE -WHERE s.FECHA_ATENCION BETWEEN :fecha_ini AND :fecha_fin""", - "description": "Consulta todos los procedimientos en rango de fechas" - }, -] - - -def ensure_defaults(): - conn = get_connection() - for q in QUERY_DEFAULTS: - exists = conn.execute( - "SELECT id FROM queries WHERE name = ?", (q["name"],) - ).fetchone() - if not exists: - conn.execute( - "INSERT INTO queries (name, query_type, query_text, description) VALUES (?, ?, ?, ?)", - (q["name"], q["query_type"], q["query_text"], q["description"]), - ) - conn.commit() - conn.close() - - -@router.get("") -async def queries_page(request: Request, user: dict = Depends(get_current_user)): - ensure_defaults() - conn = get_connection() - queries = conn.execute("SELECT * FROM queries ORDER BY query_type, name").fetchall() - conn.close() - return request.app.state.templates.TemplateResponse("queries.html", { - "request": request, "user": user, "queries": queries - }) - - -@router.post("/create") -async def query_create( - request: Request, - user: dict = Depends(get_current_user), - name: str = Form(...), - query_type: str = Form(...), - query_text: str = Form(...), - description: str = Form(""), -): - conn = get_connection() - conn.execute( - "INSERT INTO queries (name, query_type, query_text, description) VALUES (?, ?, ?, ?)", - (name, query_type, query_text, description), - ) - conn.commit() - conn.close() - return RedirectResponse("/queries", status_code=302) - - -@router.post("/update/{query_id}") -async def query_update( - query_id: int, - request: Request, - user: dict = Depends(get_current_user), - name: str = Form(...), - query_text: str = Form(...), - description: str = Form(""), -): - conn = get_connection() - conn.execute( - "UPDATE queries SET name = ?, query_text = ?, description = ? WHERE id = ?", - (name, query_text, description, query_id), - ) - conn.commit() - conn.close() - return RedirectResponse("/queries", status_code=302) - - -@router.post("/run-sql") -async def run_sql( - request: Request, - sql: str = Form(...), - user: dict = Depends(get_current_user), -): - from app.services.firebird_service import FirebirdService - conn = get_connection() - configs = {row["key"]: row["value"] for row in conn.execute("SELECT * FROM config").fetchall()} - conn.close() - - fb = FirebirdService() - fb_success, fb_msg = fb.connect( - configs.get("firebird_host", "localhost"), - int(configs.get("firebird_port", 3050)), - configs.get("firebird_database", ""), - configs.get("firebird_user", "SYSDBA"), - configs.get("firebird_password", "masterkey"), - ) - if not fb_success: - return JSONResponse({"error": f"Error Firebird: {fb_msg}"}) - - success, fb_err, rows = fb.execute_query(sql) - fb.disconnect() - - if not success: - return JSONResponse({"error": fb_err}) - - limit = rows[:200] if rows else [] - return JSONResponse({ - "rows": limit, - "total": len(rows), - "columns": list(limit[0].keys()) if limit else [] - }) - - -@router.post("/delete/{query_id}") -async def query_delete(query_id: int, user: dict = Depends(get_current_user)): - conn = get_connection() - conn.execute("DELETE FROM queries WHERE id = ?", (query_id,)) - conn.commit() - conn.close() - return RedirectResponse("/queries", status_code=302) - - -@router.post("/esquema") -async def obtener_esquema(user: dict = Depends(get_current_user)): - from app.services.firebird_service import FirebirdService - conn = get_connection() - configs = {row["key"]: row["value"] for row in conn.execute("SELECT * FROM config").fetchall()} - conn.close() - - fb = FirebirdService() - fb_success, fb_msg = fb.connect( - configs.get("firebird_host", "localhost"), - int(configs.get("firebird_port", 3050)), - configs.get("firebird_database", ""), - configs.get("firebird_user", "SYSDBA"), - configs.get("firebird_password", "masterkey"), - ) - if not fb_success: - return JSONResponse({"error": f"Error Firebird: {fb_msg}"}, status_code=400) - - success, fb_err, tablas = fb.execute_query(""" - SELECT RDB$RELATION_NAME as nombre - FROM RDB$RELATIONS - WHERE RDB$SYSTEM_FLAG = 0 - AND RDB$RELATION_NAME NOT LIKE 'RDB$%' - ORDER BY 1 - """, {}) - - if not success: - fb.disconnect() - return JSONResponse({"error": fb_err}, status_code=400) - - resultado = [] - for t in tablas: - nombre = t["NOMBRE"].strip() - ok, err, cols = fb.execute_query(f""" - SELECT - rf.RDB$FIELD_NAME as COLUMN_NAME, - f.RDB$FIELD_TYPE as FIELD_TYPE, - f.RDB$FIELD_LENGTH as FIELD_LENGTH - FROM RDB$RELATION_FIELDS rf - JOIN RDB$FIELDS f ON rf.RDB$FIELD_SOURCE = f.RDB$FIELD_NAME - WHERE rf.RDB$RELATION_NAME = '{nombre}' - ORDER BY rf.RDB$FIELD_POSITION - """) - columnas = [] - if ok: - for c in cols: - tiponum = c["FIELD_TYPE"] - tipos = {7: "SMALLINT", 8: "INTEGER", 10: "FLOAT", 12: "DATE", 13: "TIME", - 14: "CHAR", 16: "BIGINT", 27: "DOUBLE", 35: "TIMESTAMP", 37: "VARCHAR", - 40: "BLOB", 45: "BLOB_ID", 261: "BLOB"} - columnas.append({ - "nombre": c["COLUMN_NAME"].strip(), - "tipo": tipos.get(tiponum, f"UNKNOWN({tiponum})"), - "longitud": c["FIELD_LENGTH"] - }) - resultado.append({"tabla": nombre, "columnas": columnas}) - - fb.disconnect() - return JSONResponse({"tablas": resultado}) - - -@router.post("/tablas") -async def listar_tablas(user: dict = Depends(get_current_user)): - from app.services.firebird_service import FirebirdService - conn = get_connection() - configs = {row["key"]: row["value"] for row in conn.execute("SELECT * FROM config").fetchall()} - conn.close() - - fb = FirebirdService() - fb_success, fb_msg = fb.connect( - configs.get("firebird_host", "localhost"), - int(configs.get("firebird_port", 3050)), - configs.get("firebird_database", ""), - configs.get("firebird_user", "SYSDBA"), - configs.get("firebird_password", "masterkey"), - ) - if not fb_success: - return JSONResponse({"error": f"Error Firebird: {fb_msg}"}, status_code=400) - - success, fb_err, rows = fb.execute_query(""" - SELECT RDB$RELATION_NAME as nombre - FROM RDB$RELATIONS - WHERE RDB$SYSTEM_FLAG = 0 - AND RDB$RELATION_NAME NOT LIKE 'RDB$%' - ORDER BY 1 - """, {}) - fb.disconnect() - - if not success: - return JSONResponse({"error": fb_err}, status_code=400) - - return JSONResponse({ - "tablas": [r["NOMBRE"].strip() for r in rows] - }) - - -@router.post("/test") -async def query_test( - request: Request, - query_id: int = Form(...), - user: dict = Depends(get_current_user), -): - from app.services.firebird_service import FirebirdService - - conn = get_connection() - q = conn.execute("SELECT * FROM queries WHERE id = ?", (query_id,)).fetchone() - configs = {row["key"]: row["value"] for row in conn.execute("SELECT * FROM config").fetchall()} - conn.close() - - if not q: - return JSONResponse({"error": "Consulta no encontrada"}, status_code=404) - - fb = FirebirdService() - fb_success, fb_msg = fb.connect( - configs.get("firebird_host", "localhost"), - int(configs.get("firebird_port", 3050)), - configs.get("firebird_database", ""), - configs.get("firebird_user", "SYSDBA"), - configs.get("firebird_password", "masterkey"), - ) - if not fb_success: - return JSONResponse({"error": f"Error Firebird: {fb_msg}"}, status_code=400) - - success, fb_err, rows = fb.execute_query(q["query_text"], {}) - fb.disconnect() - - if not success: - return JSONResponse({"error": fb_err}, status_code=400) - - limit = rows[:20] if rows else [] - return JSONResponse({ - "rows": limit, - "total": len(rows), - "columns": list(limit[0].keys()) if limit else [] - }) +from fastapi import APIRouter, Request, Form, Depends +from fastapi.responses import RedirectResponse, JSONResponse +from app.database import get_connection +from app.auth import get_current_user +from app.models import QueryCreate + +router = APIRouter(prefix="/queries", tags=["queries"]) + +QUERY_DEFAULTS = [ + { + "name": "Terceros - Datos del paciente", + "query_type": "terceros", + "query_text": """SELECT + p.TIPO_DOCUMENTO as tipo_documento, + p.NUMERO_DOCUMENTO as numero_documento, + p.PRIMER_NOMBRE as primer_nombre, + p.SEGUNDO_NOMBRE as segundo_nombre, + p.PRIMER_APELLIDO as primer_apellido, + p.SEGUNDO_APELLIDO as segundo_apellido, + p.FECHA_NACIMIENTO as fecha_nacimiento, + p.SEXO as cod_sexo, + p.COD_ENTIDAD as cod_entidad, + p.TIPO_USUARIO as tipo_usuario, + p.COD_MUNICIPIO as cod_municipio, + p.ZONA as cod_zona, + p.DIRECCION as direccion +FROM USUAHOS p +WHERE p.NUMERO_DOCUMENTO = :doc_num""", + "description": "Consulta datos maestros del paciente por documento" + }, + { + "name": "Procedimientos por factura", + "query_type": "transaccion", + "query_text": """SELECT + s.CODIGO_CUP as cod_procedimiento, + s.FECHA_ATENCION as fecha_atencion, + s.COD_DIAGNOSTICO as cod_diagnostico, + s.FINALIDAD as finalidad, + s.VIA_INGRESO as via_ingreso, + s.MODALIDAD as modalidad, + s.GRUPO_SERVICIO as grupo_servicio, + s.COD_SERVICIO as cod_servicio, + s.COD_PRESTADOR as cod_prestador, + s.TIPO_DOC_PROFESIONAL as tipo_doc_profesional, + s.NUM_DOC_PROFESIONAL as num_doc_profesional, + s.VR_SERVICIO as vr_servicio, + s.VALOR_PAGO_MODERADOR as valor_pago_moderador, + s.CONCEPTO_RECAUDO as concepto_recaudo, + s.NUM_AUTORIZACION as num_autorizacion +FROM SERVICIOS s +WHERE s.NUM_FACTURA = :factura + AND s.FECHA_ATENCION BETWEEN :fecha_ini AND :fecha_fin""", + "description": "Consulta procedimientos por factura y rango de fechas" + }, + { + "name": "Procedimientos por fecha", + "query_type": "transaccion", + "query_text": """SELECT + s.FACTURA as num_factura, + s.CODIGO_CUP as cod_procedimiento, + s.FECHA_ATENCION as fecha_atencion, + s.COD_DIAGNOSTICO as cod_diagnostico, + s.FINALIDAD as finalidad, + s.VIA_INGRESO as via_ingreso, + s.MODALIDAD as modalidad, + s.GRUPO_SERVICIO as grupo_servicio, + s.COD_SERVICIO as cod_servicio, + s.COD_PRESTADOR as cod_prestador, + s.TIPO_DOC_PROFESIONAL as tipo_doc_profesional, + s.NUM_DOC_PROFESIONAL as num_doc_profesional, + s.VR_SERVICIO as vr_servicio, + p.TIPO_DOCUMENTO as tipo_doc_paciente, + p.NUMERO_DOCUMENTO as num_doc_paciente +FROM SERVICIOS s +JOIN USUAHOS p ON s.COD_PACIENTE = p.COD_PACIENTE +WHERE s.FECHA_ATENCION BETWEEN :fecha_ini AND :fecha_fin""", + "description": "Consulta todos los procedimientos en rango de fechas" + }, +] + + +def ensure_defaults(): + conn = get_connection() + for q in QUERY_DEFAULTS: + exists = conn.execute( + "SELECT id FROM queries WHERE name = ?", (q["name"],) + ).fetchone() + if not exists: + conn.execute( + "INSERT INTO queries (name, query_type, query_text, description) VALUES (?, ?, ?, ?)", + (q["name"], q["query_type"], q["query_text"], q["description"]), + ) + conn.commit() + conn.close() + + +@router.get("") +async def queries_page(request: Request, user: dict = Depends(get_current_user)): + conn = get_connection() + queries = conn.execute("SELECT * FROM queries ORDER BY query_type, name").fetchall() + conn.close() + return request.app.state.templates.TemplateResponse("queries.html", { + "request": request, "user": user, "queries": queries + }) + + +@router.post("/create") +async def query_create( + request: Request, + user: dict = Depends(get_current_user), + name: str = Form(...), + query_type: str = Form(...), + query_text: str = Form(...), + description: str = Form(""), +): + conn = get_connection() + conn.execute( + "INSERT INTO queries (name, query_type, query_text, description) VALUES (?, ?, ?, ?)", + (name, query_type, query_text, description), + ) + conn.commit() + conn.close() + return RedirectResponse("/queries", status_code=302) + + +@router.post("/update/{query_id}") +async def query_update( + query_id: int, + request: Request, + user: dict = Depends(get_current_user), + name: str = Form(...), + query_text: str = Form(...), + description: str = Form(""), +): + conn = get_connection() + conn.execute( + "UPDATE queries SET name = ?, query_text = ?, description = ? WHERE id = ?", + (name, query_text, description, query_id), + ) + conn.commit() + conn.close() + return RedirectResponse("/queries", status_code=302) + + +@router.post("/run-sql") +async def run_sql( + request: Request, + sql: str = Form(...), + user: dict = Depends(get_current_user), +): + from app.services.firebird_service import FirebirdService, get_firebird_from_config + conn = get_connection() + configs = {row["key"]: row["value"] for row in conn.execute("SELECT * FROM config").fetchall()} + conn.close() + + fb = FirebirdService() + fb_success, fb_msg = fb.connect( + configs.get("firebird_host", "localhost"), + int(configs.get("firebird_port", 3050)), + configs.get("firebird_database", ""), + configs.get("firebird_user", "SYSDBA"), + configs.get("firebird_password", "masterkey"), + ) + if not fb_success: + return JSONResponse({"error": f"Error Firebird: {fb_msg}"}) + + success, fb_err, rows = fb.execute_query(sql) + fb.disconnect() + + if not success: + return JSONResponse({"error": fb_err}) + + limit = rows[:200] if rows else [] + return JSONResponse({ + "rows": limit, + "total": len(rows), + "columns": list(limit[0].keys()) if limit else [] + }) + + +@router.post("/delete/{query_id}") +async def query_delete(query_id: int, user: dict = Depends(get_current_user)): + conn = get_connection() + conn.execute("DELETE FROM queries WHERE id = ?", (query_id,)) + conn.commit() + conn.close() + return RedirectResponse("/queries", status_code=302) + + +@router.post("/esquema") +async def obtener_esquema(user: dict = Depends(get_current_user)): + from app.services.firebird_service import FirebirdService, get_firebird_from_config + conn = get_connection() + configs = {row["key"]: row["value"] for row in conn.execute("SELECT * FROM config").fetchall()} + conn.close() + + fb = FirebirdService() + fb_success, fb_msg = fb.connect( + configs.get("firebird_host", "localhost"), + int(configs.get("firebird_port", 3050)), + configs.get("firebird_database", ""), + configs.get("firebird_user", "SYSDBA"), + configs.get("firebird_password", "masterkey"), + ) + if not fb_success: + return JSONResponse({"error": f"Error Firebird: {fb_msg}"}, status_code=400) + + success, fb_err, tablas = fb.execute_query(""" + SELECT RDB$RELATION_NAME as nombre + FROM RDB$RELATIONS + WHERE RDB$SYSTEM_FLAG = 0 + AND RDB$RELATION_NAME NOT LIKE 'RDB$%' + ORDER BY 1 + """, {}) + + if not success: + fb.disconnect() + return JSONResponse({"error": fb_err}, status_code=400) + + resultado = [] + for t in tablas: + nombre = t["NOMBRE"].strip() + ok, err, cols = fb.execute_query(""" + SELECT + rf.RDB$FIELD_NAME as COLUMN_NAME, + f.RDB$FIELD_TYPE as FIELD_TYPE, + f.RDB$FIELD_LENGTH as FIELD_LENGTH + FROM RDB$RELATION_FIELDS rf + JOIN RDB$FIELDS f ON rf.RDB$FIELD_SOURCE = f.RDB$FIELD_NAME + WHERE rf.RDB$RELATION_NAME = :nombre + ORDER BY rf.RDB$FIELD_POSITION + """, {"nombre": nombre}) + columnas = [] + if ok: + for c in cols: + tiponum = c["FIELD_TYPE"] + tipos = {7: "SMALLINT", 8: "INTEGER", 10: "FLOAT", 12: "DATE", 13: "TIME", + 14: "CHAR", 16: "BIGINT", 27: "DOUBLE", 35: "TIMESTAMP", 37: "VARCHAR", + 40: "BLOB", 45: "BLOB_ID", 261: "BLOB"} + columnas.append({ + "nombre": c["COLUMN_NAME"].strip(), + "tipo": tipos.get(tiponum, f"UNKNOWN({tiponum})"), + "longitud": c["FIELD_LENGTH"] + }) + resultado.append({"tabla": nombre, "columnas": columnas}) + + fb.disconnect() + return JSONResponse({"tablas": resultado}) + + +@router.post("/tablas") +async def listar_tablas(user: dict = Depends(get_current_user)): + from app.services.firebird_service import FirebirdService, get_firebird_from_config + conn = get_connection() + configs = {row["key"]: row["value"] for row in conn.execute("SELECT * FROM config").fetchall()} + conn.close() + + fb = FirebirdService() + fb_success, fb_msg = fb.connect( + configs.get("firebird_host", "localhost"), + int(configs.get("firebird_port", 3050)), + configs.get("firebird_database", ""), + configs.get("firebird_user", "SYSDBA"), + configs.get("firebird_password", "masterkey"), + ) + if not fb_success: + return JSONResponse({"error": f"Error Firebird: {fb_msg}"}, status_code=400) + + success, fb_err, rows = fb.execute_query(""" + SELECT RDB$RELATION_NAME as nombre + FROM RDB$RELATIONS + WHERE RDB$SYSTEM_FLAG = 0 + AND RDB$RELATION_NAME NOT LIKE 'RDB$%' + ORDER BY 1 + """, {}) + fb.disconnect() + + if not success: + return JSONResponse({"error": fb_err}, status_code=400) + + return JSONResponse({ + "tablas": [r["NOMBRE"].strip() for r in rows] + }) + + +@router.post("/test") +async def query_test( + request: Request, + query_id: int = Form(...), + user: dict = Depends(get_current_user), +): + from app.services.firebird_service import FirebirdService, get_firebird_from_config + + conn = get_connection() + q = conn.execute("SELECT * FROM queries WHERE id = ?", (query_id,)).fetchone() + configs = {row["key"]: row["value"] for row in conn.execute("SELECT * FROM config").fetchall()} + conn.close() + + if not q: + return JSONResponse({"error": "Consulta no encontrada"}, status_code=404) + + fb = FirebirdService() + fb_success, fb_msg = fb.connect( + configs.get("firebird_host", "localhost"), + int(configs.get("firebird_port", 3050)), + configs.get("firebird_database", ""), + configs.get("firebird_user", "SYSDBA"), + configs.get("firebird_password", "masterkey"), + ) + if not fb_success: + return JSONResponse({"error": f"Error Firebird: {fb_msg}"}, status_code=400) + + success, fb_err, rows = fb.execute_query(q["query_text"], {}) + fb.disconnect() + + if not success: + return JSONResponse({"error": fb_err}, status_code=400) + + limit = rows[:20] if rows else [] + return JSONResponse({ + "rows": limit, + "total": len(rows), + "columns": list(limit[0].keys()) if limit else [] + }) diff --git a/app/routes/terceros.py b/app/routes/terceros.py index ad60154..dc4366d 100644 --- a/app/routes/terceros.py +++ b/app/routes/terceros.py @@ -1,13 +1,23 @@ +import json +import httpx +from datetime import datetime from fastapi import APIRouter, Request, Form, Depends -from fastapi.responses import RedirectResponse, JSONResponse +from fastapi.responses import JSONResponse from app.database import get_connection from app.auth import get_current_user -from app.services.firebird_service import FirebirdService +from app.services.firebird_service import get_firebird_from_config from app.services.json_generator import generar_terceros router = APIRouter(prefix="/terceros", tags=["terceros"]) +def _load_configs() -> dict: + conn = get_connection() + configs = {row["key"]: row["value"] for row in conn.execute("SELECT * FROM config").fetchall()} + conn.close() + return configs + + @router.get("") async def terceros_page(request: Request, user: dict = Depends(get_current_user)): conn = get_connection() @@ -41,6 +51,7 @@ async def test_connection( fb_user: str = Form(...), fb_password: str = Form(...), ): + from app.services.firebird_service import FirebirdService fb = FirebirdService() success, msg = fb.connect(host, port, database, fb_user, fb_password) if success: @@ -63,16 +74,9 @@ async def preview_query( if not q: return JSONResponse({"success": False, "message": "Consulta no encontrada"}) - fb = FirebirdService() - fb_success, fb_msg = fb.connect( - configs.get("firebird_host", "localhost"), - int(configs.get("firebird_port", 3050)), - configs.get("firebird_database", ""), - configs.get("firebird_user", "SYSDBA"), - configs.get("firebird_password", "masterkey"), - ) - if not fb_success: - return JSONResponse({"success": False, "message": f"Error Firebird: {fb_msg}"}) + fb, ok, msg = get_firebird_from_config(configs) + if not ok: + return JSONResponse({"success": False, "message": f"Error Firebird: {msg}"}) params = {} if ":doc_num" in q["query_text"] and doc_num: @@ -89,9 +93,7 @@ async def preview_query( if not success: return JSONResponse({"success": False, "message": error}) - json_result = None - if rows: - json_result = generar_terceros(rows[0]) + json_result = generar_terceros(rows[0]) if rows else None return JSONResponse({ "success": True, @@ -109,10 +111,6 @@ async def send_terceros( query_id: int = Form(...), doc_num: str = Form(""), ): - import json - import httpx - from datetime import datetime - conn = get_connection() q = conn.execute("SELECT * FROM queries WHERE id = ?", (query_id,)).fetchone() configs = {row["key"]: row["value"] for row in conn.execute("SELECT * FROM config").fetchall()} @@ -121,16 +119,9 @@ async def send_terceros( if not q: return JSONResponse({"success": False, "message": "Consulta no encontrada"}) - fb = FirebirdService() - fb_success, fb_msg = fb.connect( - configs.get("firebird_host", "localhost"), - int(configs.get("firebird_port", 3050)), - configs.get("firebird_database", ""), - configs.get("firebird_user", "SYSDBA"), - configs.get("firebird_password", "masterkey"), - ) - if not fb_success: - return JSONResponse({"success": False, "message": f"Error Firebird: {fb_msg}"}) + fb, ok, msg = get_firebird_from_config(configs) + if not ok: + return JSONResponse({"success": False, "message": f"Error Firebird: {msg}"}) params = {} if ":doc_num" in q["query_text"]: @@ -141,51 +132,50 @@ async def send_terceros( if not success: return JSONResponse({"success": False, "message": error}) - if not rows: return JSONResponse({"success": False, "message": "No se encontraron datos"}) - # Generar JSON terceros tercero_json = generar_terceros(rows[0]) - # Enviar a API api_url = configs.get("api_url", "") api_key = configs.get("api_key", "") api_method = configs.get("api_method", "POST") - headers = {"Content-Type": "application/json"} if api_key: headers["Authorization"] = f"Bearer {api_key}" + resp_ok = False + resp_text = "" + resp_code = 0 try: async with httpx.AsyncClient(timeout=int(configs.get("api_timeout", 30))) as client: if api_method == "POST": resp = await client.post(api_url + "/terceros", json=tercero_json, headers=headers) else: resp = await client.put(api_url + "/terceros", json=tercero_json, headers=headers) - - result = resp.status_code, resp.is_success, resp.text + resp_ok = resp.is_success + resp_text = resp.text + resp_code = resp.status_code except Exception as e: - result = (0, False, str(e)) + resp_text = str(e) - # Guardar log conn = get_connection() conn.execute(""" INSERT INTO envios (user_id, tipo, status, json_enviado, respuesta_api, created_at) VALUES (?, ?, ?, ?, ?, ?) """, ( user["user_id"], "terceros", - "success" if result[1] else "error", + "success" if resp_ok else "error", json.dumps(tercero_json, indent=2, ensure_ascii=False), - str(result[2])[:1000], + resp_text[:1000], datetime.now().isoformat(), )) conn.commit() conn.close() return JSONResponse({ - "success": result[1], - "status_code": result[0], - "message": "Envío exitoso" if result[1] else f"Error: {result[2]}", - "cuv": result[2][:200] if result[1] else None, + "success": resp_ok, + "status_code": resp_code, + "message": "Envío exitoso" if resp_ok else f"Error: {resp_text}", + "cuv": resp_text[:200] if resp_ok else None, }) diff --git a/app/routes/transaccion.py b/app/routes/transaccion.py index 600b8ce..c0f8cc4 100644 --- a/app/routes/transaccion.py +++ b/app/routes/transaccion.py @@ -1,9 +1,12 @@ +import json as json_lib +import httpx +from datetime import datetime from fastapi import APIRouter, Request, Form, Depends from fastapi.responses import JSONResponse from app.database import get_connection from app.auth import get_current_user -from app.services.firebird_service import FirebirdService -from app.services.json_generator import generar_terceros, generar_transaccion +from app.services.firebird_service import get_firebird_from_config +from app.services.json_generator import generar_transaccion, agrupar_por_factura router = APIRouter(prefix="/transaccion", tags=["transaccion"]) @@ -45,16 +48,9 @@ async def preview_transaccion( if not q: return JSONResponse({"success": False, "message": "Consulta no encontrada"}) - fb = FirebirdService() - fb_success, fb_msg = fb.connect( - configs.get("firebird_host", "localhost"), - int(configs.get("firebird_port", 3050)), - configs.get("firebird_database", ""), - configs.get("firebird_user", "SYSDBA"), - configs.get("firebird_password", "masterkey"), - ) - if not fb_success: - return JSONResponse({"success": False, "message": f"Error Firebird: {fb_msg}"}) + fb, ok, msg = get_firebird_from_config(configs) + if not ok: + return JSONResponse({"success": False, "message": f"Error Firebird: {msg}"}) params = {"fecha_ini": fecha_inicio, "fecha_fin": fecha_fin} if ":factura" in q["query_text"] and factura: @@ -66,26 +62,14 @@ async def preview_transaccion( if not success: return JSONResponse({"success": False, "message": error}) - # Agrupar por paciente y factura - from collections import defaultdict - grupos = defaultdict(lambda: {"factura": "", "procedimientos": [], "paciente": {}}) - - for row in rows: - doc_key = (row.get("tipo_doc_paciente", "CC"), row.get("num_doc_paciente", "")) - fact = row.get("num_factura", factura) - grupos[(fact, doc_key)]["factura"] = fact - grupos[(fact, doc_key)]["procedimientos"].append(dict(row)) - + grupos = agrupar_por_factura(rows, factura) json_result = [] for (fact, doc_key), grupo in grupos.items(): paciente_data = {"tipoDocumentoIdentificacion": doc_key[0], "numDocumentoIdentificacion": doc_key[1]} - trans = generar_transaccion( - fact, - configs.get("num_documento_obligado", ""), - paciente_data, - grupo["procedimientos"], - ) - json_result.append(trans) + json_result.append(generar_transaccion( + fact, configs.get("num_documento_obligado", ""), + paciente_data, grupo["procedimientos"], + )) return JSONResponse({ "success": True, @@ -107,11 +91,6 @@ async def send_transaccion( fecha_inicio: str = Form(...), fecha_fin: str = Form(...), ): - import json as json_lib - import httpx - from datetime import datetime - from collections import defaultdict - conn = get_connection() q = conn.execute("SELECT * FROM queries WHERE id = ?", (query_id,)).fetchone() configs = {row["key"]: row["value"] for row in conn.execute("SELECT * FROM config").fetchall()} @@ -120,16 +99,9 @@ async def send_transaccion( if not q: return JSONResponse({"success": False, "message": "Consulta no encontrada"}) - fb = FirebirdService() - fb_success, fb_msg = fb.connect( - configs.get("firebird_host", "localhost"), - int(configs.get("firebird_port", 3050)), - configs.get("firebird_database", ""), - configs.get("firebird_user", "SYSDBA"), - configs.get("firebird_password", "masterkey"), - ) - if not fb_success: - return JSONResponse({"success": False, "message": f"Error Firebird: {fb_msg}"}) + fb, ok, msg = get_firebird_from_config(configs) + if not ok: + return JSONResponse({"success": False, "message": f"Error Firebird: {msg}"}) params = {"fecha_ini": fecha_inicio, "fecha_fin": fecha_fin} if ":factura" in q["query_text"] and factura: @@ -143,14 +115,7 @@ async def send_transaccion( if not rows: return JSONResponse({"success": False, "message": "No se encontraron datos"}) - # Agrupar - grupos = defaultdict(lambda: {"factura": "", "procedimientos": [], "paciente": {}}) - for row in rows: - doc_key = (row.get("tipo_doc_paciente", "CC"), row.get("num_doc_paciente", "")) - fact = row.get("num_factura", factura) - grupos[(fact, doc_key)]["factura"] = fact - grupos[(fact, doc_key)]["procedimientos"].append(dict(row)) - + grupos = agrupar_por_factura(rows, factura) api_url = configs.get("api_url", "") api_key = configs.get("api_key", "") api_method = configs.get("api_method", "POST") @@ -162,52 +127,49 @@ async def send_transaccion( total_errores = 0 resultados = [] - for (fact, doc_key), grupo in grupos.items(): - paciente_data = {"tipoDocumentoIdentificacion": doc_key[0], "numDocumentoIdentificacion": doc_key[1]} - trans_json = generar_transaccion( - fact, - configs.get("num_documento_obligado", ""), - paciente_data, - grupo["procedimientos"], - ) + async with httpx.AsyncClient(timeout=int(configs.get("api_timeout", 30))) as client: + for (fact, doc_key), grupo in grupos.items(): + paciente_data = {"tipoDocumentoIdentificacion": doc_key[0], "numDocumentoIdentificacion": doc_key[1]} + trans_json = generar_transaccion( + fact, configs.get("num_documento_obligado", ""), + paciente_data, grupo["procedimientos"], + ) - try: - async with httpx.AsyncClient(timeout=int(configs.get("api_timeout", 30))) as client: + status_ok = False + response_text = "" + try: if api_method == "POST": resp = await client.post(api_url + "/transaccion", json=trans_json, headers=headers) else: resp = await client.put(api_url + "/transaccion", json=trans_json, headers=headers) + status_ok = resp.is_success + response_text = resp.text[:1000] + except Exception as e: + response_text = str(e) - status_ok = resp.is_success - response_text = resp.text[:1000] - except Exception as e: - status_ok = False - response_text = str(e) + if status_ok: + total_enviados += 1 + else: + total_errores += 1 - if status_ok: - total_enviados += 1 - else: - total_errores += 1 + resultados.append({"factura": fact, "success": status_ok}) - resultados.append({"factura": fact, "success": status_ok}) - - # Guardar log - 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["user_id"], "transaccion", fact, - fecha_inicio, fecha_fin, - 1, len(grupo["procedimientos"]), - "success" if status_ok else "error", - json_lib.dumps(trans_json, indent=2, ensure_ascii=False)[:5000], - response_text, - datetime.now().isoformat(), - )) - conn.commit() - conn.close() + 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["user_id"], "transaccion", fact, + fecha_inicio, fecha_fin, + 1, len(grupo["procedimientos"]), + "success" if status_ok else "error", + json_lib.dumps(trans_json, indent=2, ensure_ascii=False)[:5000], + response_text, + datetime.now().isoformat(), + )) + conn.commit() + conn.close() return JSONResponse({ "success": total_errores == 0, diff --git a/app/services/firebird_service.py b/app/services/firebird_service.py index ac00bf6..cd9a451 100644 --- a/app/services/firebird_service.py +++ b/app/services/firebird_service.py @@ -44,7 +44,7 @@ class FirebirdService: return False, "No hay conexión activa", [] try: cur = self.conn.cursor() - if params: + if params is not None: cur.execute(query, params) else: cur.execute(query) @@ -53,3 +53,15 @@ class FirebirdService: return True, "", [dict(zip(columns, row)) for row in rows] except Exception as e: return False, str(e), [] + + +def get_firebird_from_config(configs: dict) -> tuple: + fb = FirebirdService() + ok, msg = fb.connect( + configs.get("firebird_host", "localhost"), + int(configs.get("firebird_port", 3050)), + configs.get("firebird_database", ""), + configs.get("firebird_user", "SYSDBA"), + configs.get("firebird_password", "masterkey"), + ) + return fb, ok, msg diff --git a/app/services/json_generator.py b/app/services/json_generator.py index b62a8fc..b19e04d 100644 --- a/app/services/json_generator.py +++ b/app/services/json_generator.py @@ -1,3 +1,4 @@ +from collections import defaultdict from datetime import datetime from typing import Optional @@ -49,6 +50,16 @@ def generar_procedimiento(row: dict, consecutivo: int) -> dict: } +def agrupar_por_factura(rows: list, factura_default: str = "") -> dict: + grupos = defaultdict(lambda: {"factura": "", "procedimientos": []}) + for row in rows: + doc_key = (row.get("tipo_doc_paciente", "CC"), row.get("num_doc_paciente", "")) + fact = row.get("num_factura", factura_default) + grupos[(fact, doc_key)]["factura"] = fact + grupos[(fact, doc_key)]["procedimientos"].append(dict(row)) + return grupos + + def generar_transaccion( factura: str, num_doc_obligado: str, diff --git a/app/templates/queries.html b/app/templates/queries.html index 357feb1..856e7d6 100644 --- a/app/templates/queries.html +++ b/app/templates/queries.html @@ -1,403 +1,434 @@ -{% extends "base.html" %} -{% block title %}Consultas SQL{% endblock %} -{% block header %}Consultas SQL{% endblock %} -{% block content %} - -
Presiona "Cargar esquema"
-Seleccioná una tabla
-{{ q.description or 'Sin descripción' }}
-Presioná Conectar para
cargar el esquema Firebird
Sin consultas guardadas
+ {% endfor %} +Ejecutá una consulta para ver resultados
+