# Guía: Módulo de Auditoría y Guard de Suscripción

> Documentación sobre cómo aplicar los módulos de auditoría y control de suscripción en el backend.

---

## 1. Módulo de Auditoría (`AuditModule`)

El módulo de auditoría permite registrar automáticamente o manualmente las operaciones realizadas en el sistema. Está registrado como `@Global()`, por lo que `AuditService` está disponible en todo el backend sin necesidad de importar el módulo en cada feature module.

### Archivos involucrados

| Archivo | Descripción |
|---|---|
| `src/audit/audit.module.ts` | Módulo global que exporta `AuditService` |
| `src/audit/audit.service.ts` | Servicio con métodos `log()`, `getByEntity()`, `getByEmpresa()` |
| `src/audit/audit.interceptor.ts` | Interceptor que audita automáticamente endpoints decorados |
| `src/audit/auditable.decorator.ts` | Decorador `@Auditable('entidad')` para marcar endpoints |

### Estructura de un registro de auditoría

```typescript
interface AuditLogEntry {
  empresa_id?: string;      // ID de la empresa
  user_id?: string;          // ID del usuario que realizó la acción
  action: string;            // CREATE, UPDATE, CHANGE_STATUS, DELETE
  entity_type: string;       // Tipo de entidad (ej: 'producto', 'suscripcion')
  entity_id?: string;        // ID de la entidad afectada
  old_value?: Record<string, unknown>;  // Valor anterior (para updates)
  new_value?: Record<string, unknown>;  // Valor nuevo
  ip_address?: string;       // IP del cliente
  user_agent?: string;       // User-Agent del cliente
  descripcion?: string;      // Descripción opcional
}
```

---

### Opción A: Auditoría automática con decorador + interceptor

Agrega `@Auditable('nombre_entidad')` y `@UseInterceptors(AuditInterceptor)` en los endpoints del controller:

```typescript
import { UseInterceptors } from '@nestjs/common';
import { Auditable } from '../audit/auditable.decorator';
import { AuditInterceptor } from '../audit/audit.interceptor';

@Controller('productos')
export class ProductosController {

  @Post()
  @UseInterceptors(AuditInterceptor)
  @Auditable('producto')
  async crear(@Body() dto: CreateProductoDto, @GetUser() user) {
    // ... lógica de creación
  }

  @Patch(':id')
  @UseInterceptors(AuditInterceptor)
  @Auditable('producto')
  async actualizar(@Param('id') id: string, @Body() dto) {
    // ... lógica de actualización
  }

  @Delete(':id')
  @UseInterceptors(AuditInterceptor)
  @Auditable('producto')
  async eliminar(@Param('id') id: string) {
    // ... lógica de eliminación
  }
}
```

**Comportamiento automático del interceptor:**

- Solo audita mutaciones (`POST`, `PUT`, `PATCH`, `DELETE`); ignora `GET`.
- Captura automáticamente: `empresa_id`, `user_id`, IP, user-agent.
- Extrae `entity_id` de `req.params.id` o `req.params.suscripcionId`.
- Guarda en `audit_logs` de forma **fire-and-forget** (no bloquea la respuesta).
- Mapeo de método HTTP → acción:
  - `POST` → `CREATE`
  - `PUT` → `UPDATE`
  - `PATCH` → `CHANGE_STATUS`
  - `DELETE` → `DELETE`

---

### Opción B: Auditoría manual desde un Service

Para mayor control (por ejemplo, guardar `old_value` y `new_value`), inyecta `AuditService` directamente:

```typescript
import { AuditService } from '../audit/audit.service';

@Injectable()
export class MiService {
  constructor(private readonly auditService: AuditService) {}

  async actualizarAlgo(id: string, datos: any, userId: string, empresaId: string) {
    const anterior = await this.prisma.tabla.findUnique({ where: { id } });
    const nuevo = await this.prisma.tabla.update({ where: { id }, data: datos });

    // Registro manual con old_value y new_value
    await this.auditService.log({
      empresa_id: empresaId,
      user_id: userId,
      action: 'UPDATE',
      entity_type: 'mi_entidad',
      entity_id: id,
      old_value: anterior,
      new_value: nuevo,
      descripcion: 'Se actualizó X campo',
    });
  }
}
```

---

### Consultar registros de auditoría

```typescript
// Por entidad específica (ej: ver historial de un producto)
const logs = await this.auditService.getByEntity('producto', productoId);

// Por empresa (con filtros opcionales)
const logs = await this.auditService.getByEmpresa(empresaId, {
  action: 'CREATE',        // opcional: filtrar por acción
  entity_type: 'producto', // opcional: filtrar por tipo de entidad
  limit: 100,              // opcional: máximo 200
});
```

---

## 2. Guard de Suscripción (`SuscripcionGuard`)

El guard verifica que la empresa del usuario tenga una suscripción activa antes de permitir operaciones de escritura. Bloquea mutaciones si la suscripción está vencida.

### Archivos involucrados

| Archivo | Descripción |
|---|---|
| `src/common/guards/suscripcion.guard.ts` | Guard + decorador `@SkipSuscripcionCheck()` |

### Registrar como guard global

Para activarlo en todo el backend, registrarlo en `AppModule`:

```typescript
// app.module.ts
import { APP_GUARD } from '@nestjs/core';
import { SuscripcionGuard } from './common/guards/suscripcion.guard';

@Module({
  providers: [
    {
      provide: APP_GUARD,
      useClass: SuscripcionGuard,
    },
  ],
})
export class AppModule {}
```

### Comportamiento

Una vez activo globalmente, el guard aplica las siguientes reglas:

| Condición | Resultado |
|---|---|
| Método `GET`, `OPTIONS`, `HEAD` | ✅ Permitido siempre |
| Ruta en `allowedPaths` | ✅ Permitido siempre |
| Decorador `@SkipSuscripcionCheck()` | ✅ Permitido siempre |
| No hay usuario autenticado | ✅ Pasa al siguiente guard |
| Suscripción activa | ✅ Permitido |
| Suscripción vencida/inexistente | ❌ `403 Forbidden` |

### Respuesta de bloqueo

Cuando la suscripción está vencida, el guard lanza:

```json
{
  "blocked": true,
  "reason": "subscription_expired",
  "message": "Su suscripción ha vencido. No puede realizar esta operación hasta renovar su plan.",
  "detalle": "..."
}
```

### Rutas permitidas por defecto

Estas rutas siempre están permitidas independientemente del estado de la suscripción:

```typescript
private readonly allowedPaths = [
  '/auth/',
  '/suscripciones/mi-suscripcion',
  '/suscripciones/mis-pagos',
  '/suscripciones/comprobantes',
  '/suscripciones/cron/',
  '/suscripciones/holding/',
];
```

Para agregar más rutas, editar el array `allowedPaths` en `suscripcion.guard.ts`.

### Excluir un endpoint específico

Usar el decorador `@SkipSuscripcionCheck()` para que un endpoint no verifique la suscripción:

```typescript
import { SkipSuscripcionCheck } from '../common/guards/suscripcion.guard';

@Post('mi-endpoint-libre')
@SkipSuscripcionCheck()
async miEndpoint() {
  // Este endpoint funciona aunque la suscripción esté vencida
}
```

---

## 3. Resumen rápido

| Módulo | Cómo aplicar | Alcance |
|---|---|---|
| **Auditoría automática** | `@Auditable('entidad')` + `@UseInterceptors(AuditInterceptor)` en el controller | Por endpoint |
| **Auditoría manual** | `this.auditService.log({...})` en el service | Donde se necesite |
| **Guard suscripción** | `APP_GUARD` en `AppModule` | Global (todas las mutaciones) |
| **Excluir del guard** | `@SkipSuscripcionCheck()` en el endpoint | Por endpoint |

---

## 4. Ejemplo completo: Controller con ambos módulos

```typescript
import { Controller, Post, Patch, Delete, Param, Body, UseInterceptors } from '@nestjs/common';
import { Auditable } from '../audit/auditable.decorator';
import { AuditInterceptor } from '../audit/audit.interceptor';
import { SkipSuscripcionCheck } from '../common/guards/suscripcion.guard';

@Controller('clientes')
export class ClientesController {

  // Auditable + requiere suscripción activa (por defecto)
  @Post()
  @UseInterceptors(AuditInterceptor)
  @Auditable('cliente')
  async crear(@Body() dto: CreateClienteDto) {
    // ...
  }

  // Auditable + requiere suscripción activa
  @Patch(':id')
  @UseInterceptors(AuditInterceptor)
  @Auditable('cliente')
  async actualizar(@Param('id') id: string, @Body() dto) {
    // ...
  }

  // Auditable + NO requiere suscripción (excepción)
  @Delete(':id')
  @UseInterceptors(AuditInterceptor)
  @Auditable('cliente')
  @SkipSuscripcionCheck()
  async eliminar(@Param('id') id: string) {
    // ...
  }
}
```
