feat: log de actividad por usuario

- Tabla activity_log en SQLite (migración automática)
- Registra: login, login_fallido, rda_enviado, rda_masivo, rda_directo,
  automation_run, contrato_creado, contrato_editado, contrato_toggle
- Endpoint GET /logs/actividad con filtros por usuario, acción y fecha
- UI /logs/actividad con badges de color por tipo de acción
- Enlace "Actividad" en navegación lateral

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Lizandro Guarnizo
2026-07-10 17:11:33 -05:00
co-authored by Claude Sonnet 4.6
parent 8e29e2d04f
commit c20e1a7476
10 changed files with 257 additions and 0 deletions
+10
View File
@@ -2,6 +2,7 @@ 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.utils.activity import log_activity, get_ip
router = APIRouter(prefix="/contratos", tags=["contratos"])
@@ -134,6 +135,8 @@ async def contrato_create(
(numero_contrato.strip(), nit_empresa.strip(), tipo_usuario.strip(), descripcion.strip(), int(excluir)),
)
conn.commit()
log_activity(user["user_id"], user["username"], "contrato_creado",
f"Contrato {numero_contrato.strip()}{descripcion.strip()}", get_ip(request))
except Exception:
pass
conn.close()
@@ -156,6 +159,8 @@ async def contrato_update(
(numero_contrato.strip(), nit_empresa.strip(), tipo_usuario.strip(), descripcion.strip(), int(excluir), contrato_id),
)
conn.commit()
log_activity(user["user_id"], user["username"], "contrato_editado",
f"Contrato {numero_contrato.strip()}{descripcion.strip()}", get_ip(request))
conn.close()
return RedirectResponse("/contratos", status_code=302)
@@ -168,6 +173,11 @@ async def toggle_excluir(contrato_id: int, user: dict = Depends(get_current_user
(contrato_id,)
)
conn.commit()
row = conn.execute("SELECT numero_contrato, excluir FROM contratos WHERE id=?", (contrato_id,)).fetchone()
if row:
estado = "excluido" if row["excluir"] else "activado"
log_activity(user["user_id"], user["username"], "contrato_toggle",
f"Contrato {row['numero_contrato']}{estado}", get_ip(request))
conn.close()
return RedirectResponse("/contratos", status_code=302)