michangarrito/docs/97-adr/ADR-0008-audit-log-retention.md
rckrdmrd 2c916e75e5 [SIMCO-V4] feat: Agregar documentación SaaS, ADRs e integraciones
Nuevas Épicas (MCH-029 a MCH-033):
- Infraestructura SaaS multi-tenant
- Auth Social (OAuth2)
- Auditoría Empresarial
- Feature Flags
- Onboarding Wizard

Nuevas Integraciones (INT-010 a INT-014):
- Email Providers (SendGrid, Mailgun, SES)
- Storage Cloud (S3, GCS, Azure)
- OAuth Social
- Redis Cache
- Webhooks Outbound

Nuevos ADRs (0004 a 0011):
- Notifications Realtime
- Feature Flags Strategy
- Storage Abstraction
- Webhook Retry Strategy
- Audit Log Retention
- Rate Limiting
- OAuth Social Implementation
- Email Multi-provider

Actualizados:
- MASTER_INVENTORY.yml
- CONTEXT-MAP.yml
- HERENCIA-SIMCO.md
- Mapas de documentación

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-13 01:43:15 -06:00

264 lines
5.4 KiB
Markdown

---
id: ADR-0008
type: ADR
title: "Audit Log Retention"
status: Accepted
decision_date: 2026-01-10
updated_at: 2026-01-10
simco_version: "4.0.1"
stakeholders:
- "Equipo MiChangarrito"
tags:
- audit
- retention
- compliance
- storage
---
# ADR-0008: Audit Log Retention
## Metadata
| Campo | Valor |
|-------|-------|
| **ID** | ADR-0008 |
| **Estado** | Accepted |
| **Fecha** | 2026-01-10 |
| **Autor** | Architecture Team |
| **Supersede** | - |
---
## Contexto
MiChangarrito registra audit logs de todas las acciones del sistema para compliance, debugging y seguridad. Necesitamos definir:
1. Cuanto tiempo mantener los logs
2. Como archivar logs antiguos
3. Como purgar datos sin perder trazabilidad
---
## Decision
**Implementamos politica de retencion configurable por tenant con archivado opcional a storage antes de purga.**
- **Default:** 90 dias en base de datos
- **Archivado:** Opcional a S3/R2 antes de purga
- **Notificacion:** 7 dias antes de purga
- **Configuracion:** Por tenant segun plan
---
## Alternativas Consideradas
### Opcion 1: Retencion infinita
- **Pros:**
- Nunca se pierde informacion
- Simple
- **Cons:**
- Crecimiento ilimitado de BD
- Costos de almacenamiento
- Performance degradada
### Opcion 2: Retencion fija (30 dias)
- **Pros:**
- Simple de implementar
- Predecible
- **Cons:**
- No flexible para diferentes necesidades
- Puede no cumplir regulaciones
### Opcion 3: Retencion configurable + archivado (Elegida)
- **Pros:**
- Flexible por tenant
- Archivado economico
- Cumple regulaciones
- **Cons:**
- Mas complejo
- Necesita job de limpieza
---
## Consecuencias
### Positivas
1. **Compliance:** Tenants pueden configurar segun sus necesidades
2. **Performance:** BD no crece indefinidamente
3. **Economia:** Archivado en storage es barato
4. **Flexibilidad:** Diferentes politicas por plan
### Negativas
1. **Complejidad:** Job de limpieza y archivado
2. **Acceso:** Logs archivados requieren restauracion
---
## Implementacion
### Configuracion por Plan
| Plan | Retencion BD | Archivado | Restauracion |
|------|--------------|-----------|--------------|
| Basic | 30 dias | No | N/A |
| Pro | 90 dias | Si (1 ano) | Bajo demanda |
| Enterprise | 365 dias | Si (7 anos) | Autoservicio |
### Job de Limpieza
```typescript
@Cron('0 3 * * *') // 3 AM diario
async cleanupAuditLogs() {
const tenants = await this.tenantService.findAll();
for (const tenant of tenants) {
const config = await this.getRetentionConfig(tenant.id);
const cutoffDate = subDays(new Date(), config.retentionDays);
// 1. Archivar si esta habilitado
if (config.archiveBeforeDelete) {
await this.archiveLogs(tenant.id, cutoffDate);
}
// 2. Purgar logs antiguos
await this.purgeLogs(tenant.id, cutoffDate);
this.logger.log(`Cleaned audit logs for tenant ${tenant.id}`, {
cutoffDate,
archived: config.archiveBeforeDelete,
});
}
}
```
### Archivado
```typescript
async archiveLogs(tenantId: string, olderThan: Date) {
const logs = await this.auditRepository.find({
where: {
tenantId,
createdAt: LessThan(olderThan),
},
});
if (logs.length === 0) return;
// Comprimir y subir a storage
const archive = await this.compressLogs(logs);
const key = `audit-archives/${tenantId}/${format(new Date(), 'yyyy-MM')}.json.gz`;
await this.storageService.upload(key, archive, {
contentType: 'application/gzip',
metadata: {
tenantId,
recordCount: logs.length.toString(),
dateRange: `${logs[0].createdAt}-${logs[logs.length - 1].createdAt}`,
},
});
}
```
### Notificacion Pre-Purga
```typescript
@Cron('0 9 * * *') // 9 AM diario
async notifyUpcomingPurge() {
const tenants = await this.tenantService.findAll();
for (const tenant of tenants) {
const config = await this.getRetentionConfig(tenant.id);
if (!config.notifyBeforePurge) continue;
const warningDate = subDays(new Date(), config.retentionDays - 7);
const count = await this.countLogsOlderThan(tenant.id, warningDate);
if (count > 0) {
await this.notificationService.send(tenant.adminEmail, {
template: 'audit-purge-warning',
variables: {
count,
purgeDate: addDays(warningDate, 7),
archiveEnabled: config.archiveBeforeDelete,
},
});
}
}
}
```
---
## Formato de Archivo
### Estructura
```
audit-archives/
└── {tenant_id}/
├── 2026-01.json.gz
├── 2026-02.json.gz
└── ...
```
### Contenido (descomprimido)
```json
{
"tenant_id": "550e8400-...",
"exported_at": "2026-01-10T03:00:00Z",
"record_count": 15000,
"date_range": {
"from": "2025-10-01T00:00:00Z",
"to": "2025-10-31T23:59:59Z"
},
"records": [
{
"id": "...",
"user_id": "...",
"action": "UPDATE",
"entity_type": "Product",
"entity_id": "...",
"old_value": {...},
"new_value": {...},
"created_at": "2025-10-15T10:30:00Z"
}
]
}
```
---
## Restauracion
### API de Restauracion (Enterprise)
```typescript
// POST /api/audit/restore
{
"month": "2025-10",
"reason": "Auditoria externa"
}
// Response
{
"status": "processing",
"estimatedTime": "5 minutes",
"jobId": "restore-123"
}
```
---
## Referencias
- [GDPR Data Retention](https://gdpr.eu/data-retention/)
- [MCH-031: Auditoria Empresarial](../01-epicas/MCH-031-auditoria-empresarial.md)
---
**Fecha decision:** 2026-01-10
**Autores:** Architecture Team