feat: implementacion de dashboards por rol, soporte de clases virtuales concurrentes en importador de sheets y cartelera del dia

This commit is contained in:
2026-09-18 23:04:48 -03:00
parent 47b50ede7e
commit 1080acd43a
240 changed files with 8354 additions and 8886 deletions
+76
View File
@@ -0,0 +1,76 @@
from typing import Optional, List, Dict, Any
from flask import request, g, has_request_context
from flask_login import current_user
import json
from app import db
from app.models.audit_log import AuditLog
from app.models.user import User
class AuditService:
"""Servicio para la captura y consulta de la bitácora inmutable de auditoría."""
@staticmethod
def log(action: str, module: str, entity_id: Optional[int] = None,
details: Optional[Any] = None, user: Optional[User] = None,
ip_address: Optional[str] = None) -> AuditLog:
"""
Registra un evento de auditoría de forma segura, infiriendo el usuario e IP
desde el contexto actual de la petición si no se especifican.
"""
user_id = None
user_email = None
# 1. Determinar usuario actuante
if user:
user_id = user.id
user_email = user.email
elif has_request_context():
if hasattr(g, 'jwt_user') and g.jwt_user:
user_id = g.jwt_user.id
user_email = g.jwt_user.email
elif current_user and current_user.is_authenticated:
user_id = current_user.id
user_email = current_user.email
# 2. Determinar IP de origen
if not ip_address and has_request_context():
if request.headers.get('X-Forwarded-For'):
ip_address = request.headers.get('X-Forwarded-For').split(',')[0].strip()
else:
ip_address = request.remote_addr
# 3. Serializar detalles si es estructura de datos
details_str = None
if details is not None:
if isinstance(details, (dict, list)):
try:
details_str = json.dumps(details, ensure_ascii=False)
except Exception:
details_str = str(details)
else:
details_str = str(details)
log_entry = AuditLog(
user_id=user_id,
user_email=user_email,
action=action.upper(),
module=module.lower(),
entity_id=entity_id,
details=details_str,
ip_address=ip_address
)
try:
db.session.add(log_entry)
db.session.commit()
except Exception:
db.session.rollback()
return log_entry
@staticmethod
def get_recent_logs(limit: int = 50, module: Optional[str] = None) -> List[AuditLog]:
"""Obtiene las entradas más recientes de auditoría."""
query = AuditLog.query
if module:
query = query.filter_by(module=module.lower())
return query.order_by(AuditLog.created_at.desc()).limit(limit).all()