Files
rips_manager/app/routes/logs.py
T
Lizandro GuarnizoandClaude Sonnet 4.6 599391739f feat(logs): filtro por cédula de paciente en historial de envíos
- Agrega columna cedula a tabla envios (migración automática)
- _guardar_envio almacena la cédula en los 4 pasos de automation
- pac_map (COD_PACIENTE→DOCIDENT) para RDA/preserv/ventas
- logs.py busca por cedula LIKE en vez de idrecepcion/contrato
- logs.html etiqueta el input como "Cédula paciente"

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-22 22:37:40 -05:00

236 lines
8.0 KiB
Python

import json as json_lib
from datetime import datetime
import httpx
from fastapi import APIRouter, Request, Depends
from fastapi.responses import JSONResponse
from app.database import get_connection
from app.auth import get_current_user
from app.services.json_generator import _clean_times
from app.services.api_client import get_tns_token, TNS_BASE
from app.utils.activity import log_activity, get_ip
router = APIRouter(prefix="/logs", tags=["logs"])
def _limpiar_json_enviado(raw: str) -> str:
if not raw:
return raw
try:
return json_lib.dumps(_clean_times(json_lib.loads(raw)), ensure_ascii=False, indent=2)
except Exception:
return raw
PAGE_SIZE = 50
@router.get("")
async def logs_page(
request: Request,
user: dict = Depends(get_current_user),
tipo: str = "",
status: str = "",
factura: str = "",
paciente: str = "",
fecha_desde: str = "",
fecha_hasta: str = "",
offset: int = 0,
):
conn = get_connection()
where = ["1=1"]
params = []
if tipo:
where.append("e.tipo = ?")
params.append(tipo)
if status:
where.append("e.status = ?")
params.append(status)
if factura:
where.append("e.factura LIKE ?")
params.append(f"%{factura}%")
if paciente:
where.append("(e.cedula LIKE ? OR e.factura LIKE ?)")
params += [f"%{paciente}%", f"%{paciente}%"]
if fecha_desde:
where.append("DATE(e.created_at) >= ?")
params.append(fecha_desde)
if fecha_hasta:
where.append("DATE(e.created_at) <= ?")
params.append(fecha_hasta)
w = " AND ".join(where)
total = conn.execute(f"SELECT COUNT(*) FROM envios e WHERE {w}", params).fetchone()[0]
envios = conn.execute(f"""
SELECT e.*, u.username FROM envios e
LEFT JOIN users u ON e.user_id = u.id
WHERE {w}
ORDER BY e.created_at DESC
LIMIT ? OFFSET ?
""", params + [PAGE_SIZE, offset]).fetchall()
stats = conn.execute("""
SELECT tipo, status, COUNT(*) as cnt
FROM envios
GROUP BY tipo, status
ORDER BY tipo, status
""").fetchall()
conn.close()
has_more = (offset + PAGE_SIZE) < total
return request.app.state.templates.TemplateResponse("logs.html", {
"request": request, "user": user,
"envios": envios, "stats": stats, "total": total,
"filtro_tipo": tipo, "filtro_status": status,
"filtro_factura": factura, "filtro_paciente": paciente,
"filtro_fecha_desde": fecha_desde, "filtro_fecha_hasta": fecha_hasta,
"offset": offset, "page_size": PAGE_SIZE, "has_more": has_more,
})
@router.get("/actividad")
async def actividad_page(
request: Request,
user: dict = Depends(get_current_user),
username: str = "",
accion: str = "",
fecha_desde: str = "",
fecha_hasta: str = "",
offset: int = 0,
):
conn = get_connection()
where = ["1=1"]
params = []
if username:
where.append("a.username = ?")
params.append(username)
if accion:
where.append("a.accion = ?")
params.append(accion)
if fecha_desde:
where.append("DATE(a.created_at) >= ?")
params.append(fecha_desde)
if fecha_hasta:
where.append("DATE(a.created_at) <= ?")
params.append(fecha_hasta)
w = " AND ".join(where)
total = conn.execute(f"SELECT COUNT(*) FROM activity_log a WHERE {w}", params).fetchone()[0]
rows = conn.execute(f"""
SELECT a.* FROM activity_log a
WHERE {w} ORDER BY a.created_at DESC LIMIT ? OFFSET ?
""", params + [PAGE_SIZE, offset]).fetchall()
usuarios = conn.execute("SELECT DISTINCT username FROM activity_log ORDER BY username").fetchall()
acciones = conn.execute("SELECT DISTINCT accion FROM activity_log ORDER BY accion").fetchall()
conn.close()
return request.app.state.templates.TemplateResponse("actividad.html", {
"request": request, "user": user,
"rows": rows, "total": total,
"usuarios": [u["username"] for u in usuarios],
"acciones": [a["accion"] for a in acciones],
"filtro_username": username, "filtro_accion": accion,
"filtro_fecha_desde": fecha_desde, "filtro_fecha_hasta": fecha_hasta,
"offset": offset, "page_size": PAGE_SIZE,
"has_more": (offset + PAGE_SIZE) < total,
})
@router.post("/reenviar/{envio_id}")
async def reenviar_envio(envio_id: int, request: Request, user: dict = Depends(get_current_user)):
conn = get_connection()
row = conn.execute("SELECT * FROM envios WHERE id = ?", (envio_id,)).fetchone()
conn.close()
if not row:
return JSONResponse({"success": False, "message": "Registro no encontrado"})
if row["status"] == "success":
return JSONResponse({"success": False, "message": "Ya fue enviado exitosamente"})
if not row["json_enviado"]:
return JSONResponse({"success": False, "message": "Sin JSON guardado para reenviar"})
try:
rda_json = json_lib.loads(row["json_enviado"])
except Exception:
return JSONResponse({"success": False, "message": "JSON inválido en registro"})
conn_cfg = get_connection()
cfg = {r["key"]: r["value"] for r in conn_cfg.execute("SELECT * FROM config").fetchall()}
conn_cfg.close()
token, err = await get_tns_token(
cfg.get("tns_empresa", ""), cfg.get("tns_usuario", ""), cfg.get("tns_password", "")
)
if not token:
return JSONResponse({"success": False, "message": f"Error login TNS: {err}"})
headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
api_sucursal = cfg.get("api_sucursal", "00") or "00"
tipo = row["tipo"]
endpoint = (
f"{TNS_BASE}/v2/rda/RdaPaciente/Insertar?codigosucursal={api_sucursal}"
if tipo == "transaccion"
else f"{TNS_BASE}/v2/tablas/Tercero/Crear"
)
ok = False
msg = ""
raw_resp = ""
try:
async with httpx.AsyncClient(timeout=30) as client:
r = await client.post(endpoint, json=rda_json, headers=headers)
raw_resp = r.text
try:
data = r.json()
if tipo == "transaccion":
ok = bool(data.get("status") or (data.get("data") or {}).get("success", False))
msg = ((data.get("data") or {}).get("response") or data.get("message") or raw_resp[:300])
else:
ok = r.is_success
msg = data.get("message", "") or raw_resp[:200]
except Exception:
ok = r.status_code < 300
msg = raw_resp[:300]
except Exception as ex:
msg = str(ex)
if ok:
conn = get_connection()
conn.execute(
"UPDATE envios SET status='success', mensaje_tns=?, respuesta_api=?, created_at=? WHERE id=?",
(msg, raw_resp[:2000], datetime.now().isoformat(), envio_id)
)
conn.commit()
conn.close()
log_activity(user["user_id"], user["username"], "reenvio_ok",
f"Envío #{envio_id} factura {row['factura']} reenvío exitoso", get_ip(request))
return JSONResponse({"success": ok, "message": msg})
@router.get("/detalle/{envio_id}")
async def detalle_envio(envio_id: int, request: Request, user: dict = Depends(get_current_user)):
conn = get_connection()
row = conn.execute(
"SELECT e.*, u.username FROM envios e LEFT JOIN users u ON e.user_id = u.id WHERE e.id = ?",
(envio_id,)
).fetchone()
conn.close()
if not row:
return JSONResponse({"error": "No encontrado"}, status_code=404)
return JSONResponse({
"id": row["id"],
"tipo": row["tipo"],
"factura": row["factura"],
"idrecepcion": row["idrecepcion"],
"contrato": row["contrato"],
"status": row["status"],
"mensaje_tns": row["mensaje_tns"],
"respuesta_api": row["respuesta_api"],
"json_enviado": _limpiar_json_enviado(row["json_enviado"]),
"fecha_inicio": row["fecha_inicio"],
"fecha_fin": row["fecha_fin"],
"created_at": row["created_at"],
"username": row["username"],
})