Audit Trail y PII¶
GexCom implementa un registro de auditoria inmutable y proteccion de datos personales (PII) segun la Ley 1581/2012 de Colombia.
Audit Trail¶
Arquitectura¶
El IAuditLogger es un Port (Protocol) implementado por SqlAuditLogger en infrastructure:
# src/gexcom/application/ports/audit.py
class IAuditLogger(Protocol):
def log(
self,
tabla: str,
accion: str,
usuario_id: UUID | None,
detalles: dict[str, object],
) -> None: ...
Use Cases que Registran Auditoria¶
| Use Case | Accion registrada |
|---|---|
CrearNotificacionUseCase |
CREAR_NOTIFICACION |
CambiarEstadoNotificacionUseCase |
CAMBIAR_ESTADO |
RegistrarProcesoUseCase |
CREAR_PROCESO |
GestionarSujetoUseCase |
CREAR_SUJETO, ACTUALIZAR_SUJETO |
GestionarAudienciaUseCase |
CREAR_AUDIENCIA |
DispatchNotificacionUseCase |
DESPACHO_NOTIFICACION |
Schema del AuditLog¶
CREATE TABLE audit_log (
id SERIAL PRIMARY KEY, -- autoincrement, append-only
tabla VARCHAR NOT NULL,
accion VARCHAR NOT NULL,
usuario_id UUID, -- NULL si accion automatica
detalles JSONB,
timestamp TIMESTAMP DEFAULT NOW()
);
Inmutabilidad en PostgreSQL¶
Un trigger impide modificaciones al audit_log:
-- src/gexcom/infrastructure/persistence/audit_trigger.py
CREATE RULE audit_log_no_delete
AS ON DELETE TO audit_log DO INSTEAD NOTHING;
CREATE RULE audit_log_no_update
AS ON UPDATE TO audit_log DO INSTEAD NOTHING;
Solo PostgreSQL
El trigger de inmutabilidad aplica unicamente en PostgreSQL. En SQLite (desarrollo/tests) no hay proteccion a nivel de DB — confiar en que el codigo de aplicacion nunca elimina registros de audit_log.
Backlog P03
AUDIT-TRUNCATE: El comando TRUNCATE no esta bloqueado por las reglas (solo DELETE/UPDATE). En produccion se recomienda revocar el privilegio TRUNCATE al usuario de la aplicacion con REVOKE TRUNCATE ON audit_log FROM gexcom_user;.
PII — Proteccion de Datos Personales¶
GexCom maneja datos de identificacion personal (PII) de las partes procesales. Cumple con la Ley 1581/2012 (Habeas Data) mediante:
1. Enmascaramiento en UI¶
# src/gexcom/interfaces/gui/pii_masking.py
def mask_dni(dni: str) -> str:
"""123456789 → ***6789"""
if len(dni) <= 4:
return "****"
return "*" * (len(dni) - 4) + dni[-4:]
def mask_email(email: str) -> str:
"""juan.perez@mail.com → j***@mail.com"""
local, domain = email.split("@", 1)
return local[0] + "***@" + domain
def mask_telefono(telefono: str) -> str:
"""3001234567 → ******4567"""
if len(telefono) <= 4:
return "****"
return "*" * (len(telefono) - 4) + telefono[-4:]
2. PII Excluido de Logs¶
Los campos dni, email y telefono de sujetos nunca se incluyen en:
- Logs de structlog
- Mensajes de error
- Detalles del AuditLog (se usan UUIDs en su lugar)
# CORRECTO — usar ID, no PII
audit_logger.log("sujeto", "CREAR", usuario_id, {"sujeto_id": str(sujeto.id)})
# INCORRECTO — no incluir PII en logs
# audit_logger.log("sujeto", "CREAR", usuario_id, {"dni": sujeto.dni})
3. Control de Acceso por Rol¶
En la pagina Directorio:
| Rol | DNI | Telefono | |
|---|---|---|---|
| ADMINISTRADOR | Completo | Completo | Completo |
| NOTIFICADOR | Enmascarado (***6789) |
Enmascarado (j***@mail.com) |
Enmascarado (***4567) |
4. Directorio Siempre Accesible¶
Segun la politica del CSJ Bello, el directorio de contactos debe ser accesible para el notificador en todo momento (aunque con datos enmascarados). Esta restriccion esta documentada en CLAUDE.md:
Directorio de contactos accesible en cualquier momento
Resumen de Cumplimiento¶
| Requerimiento | Implementacion | Estado |
|---|---|---|
| PII enmascarado en UI | pii_masking.py aplicado en 4 paginas |
✅ |
| PII excluido de logs | Convencion de codigo verificada en auditoria | ✅ |
| Audit trail inmutable | Rules PG + append-only en aplicacion | ✅ |
| RBAC en datos sensibles | check_permission() en todas las paginas |
✅ |
| Lockout cuenta | TTL 15 min persistente en DB | ✅ |
| TRUNCATE bloqueado | — | ⏳ P03 |