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
+3
View File
@@ -2,6 +2,7 @@ from fastapi import APIRouter, Request, Form, Depends, HTTPException
from fastapi.responses import RedirectResponse, JSONResponse
from app.database import get_connection
from app.auth import hash_password, verify_password, create_token, get_current_user
from app.utils.activity import log_activity, get_ip
router = APIRouter(prefix="/auth", tags=["auth"])
@@ -62,10 +63,12 @@ async def login(
"SELECT * FROM users WHERE username = ?", (username,)
).fetchone()
if not user or not verify_password(password, user["password_hash"]):
log_activity(0, username, "login_fallido", f"Intento fallido desde {get_ip(request)}", get_ip(request))
return request.app.state.templates.TemplateResponse("login.html", {
"request": request, "error": "Usuario o contraseña incorrectos"
})
log_activity(user["id"], user["username"], "login", f"Inicio de sesión", get_ip(request))
token = create_token(user["id"], user["username"])
resp = RedirectResponse("/dashboard", status_code=302)
resp.set_cookie(key="token", value=token, httponly=True)