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>
264 lines
5.4 KiB
Markdown
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
|