--- 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