template-saas/docs/01-modulos/SAAS-011-storage.md
rckrdmrd 50a821a415
Some checks failed
CI / Backend CI (push) Has been cancelled
CI / Frontend CI (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / CI Summary (push) Has been cancelled
[SIMCO-V38] feat: Actualizar a SIMCO v3.8.0
- HERENCIA-SIMCO.md actualizado con directivas v3.7 y v3.8
- Actualizaciones de configuracion

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-10 08:53:08 -06:00

275 lines
7.5 KiB
Markdown

---
id: "SAAS-011"
title: "Storage"
type: "Module"
status: "Published"
priority: "P1"
module: "storage"
version: "1.0.0"
created_date: "2026-01-07"
updated_date: "2026-01-10"
---
# SAAS-011: Storage
## Metadata
- **Codigo:** SAAS-011
- **Modulo:** Storage
- **Prioridad:** P1
- **Estado:** Implementado
- **Fase:** 3 - Features Core
## Descripcion
Sistema de almacenamiento de archivos: upload/download con presigned URLs, organizacion por tenant, limites por plan, y soporte multi-provider (S3, R2, MinIO).
## Objetivos
1. Upload seguro con presigned URLs
2. Organizacion por tenant
3. Limites de almacenamiento por plan
4. Soporte multi-provider
5. Procesamiento de imagenes
## Implementacion
### Estado de Entregables
| Entregable | Estado | Archivo |
|------------|--------|---------|
| storage.module.ts | Implementado | `apps/backend/src/modules/storage/storage.module.ts` |
| storage.controller.ts | Implementado | `apps/backend/src/modules/storage/storage.controller.ts` |
| storage.service.ts | Implementado | `apps/backend/src/modules/storage/services/storage.service.ts` |
| s3.provider.ts | Implementado | `apps/backend/src/modules/storage/providers/s3.provider.ts` |
| DDL storage schema | Implementado | `apps/database/ddl/schemas/storage/` |
| Frontend components | Implementado | `apps/frontend/src/components/storage/` |
| StoragePage | Implementado | `apps/frontend/src/pages/dashboard/StoragePage.tsx` |
### Archivos Creados
**Backend:**
```
apps/backend/src/modules/storage/
├── storage.module.ts # NestJS module
├── storage.controller.ts # REST endpoints (8 endpoints)
├── index.ts # Module barrel export
├── entities/
│ ├── file.entity.ts # FileEntity with enums
│ ├── pending-upload.entity.ts
│ ├── storage-usage.entity.ts
│ └── index.ts
├── dto/
│ ├── storage.dto.ts # Request/Response DTOs
│ └── index.ts
├── providers/
│ ├── s3.provider.ts # S3/R2/MinIO provider
│ └── index.ts
└── services/
├── storage.service.ts # Business logic
└── index.ts
```
**Database:**
```
apps/database/ddl/schemas/storage/
└── tables/
├── 01-files.sql # files, pending_uploads tables
└── 02-storage-usage.sql # usage table, triggers, functions
```
**Frontend:**
```
apps/frontend/src/
├── components/storage/
│ ├── FileUpload.tsx # Drag & drop upload component
│ ├── FileItem.tsx # File display (grid/list view)
│ ├── FileList.tsx # Paginated file list
│ ├── StorageUsageCard.tsx # Usage statistics card
│ └── index.ts
├── hooks/
│ └── useStorage.ts # React Query hooks
├── pages/dashboard/
│ └── StoragePage.tsx # Main storage page
└── services/
└── api.ts # storageApi client (added)
```
### Tablas de Base de Datos
| Tabla | Descripcion |
|-------|-------------|
| storage.files | Archivos subidos con metadata |
| storage.pending_uploads | Uploads pendientes de confirmacion |
| storage.usage | Uso de almacenamiento por tenant |
### Funciones de Base de Datos
| Funcion | Descripcion |
|---------|-------------|
| storage.update_usage_on_file_change() | Trigger para actualizar usage |
| storage.can_upload() | Valida si tenant puede subir |
| storage.get_tenant_stats() | Estadisticas de uso |
| storage.cleanup_expired_uploads() | Limpieza de uploads expirados |
## Endpoints API
| Metodo | Endpoint | Descripcion | Implementado |
|--------|----------|-------------|--------------|
| POST | /storage/upload-url | Obtener presigned upload URL | ✓ |
| POST | /storage/confirm | Confirmar upload exitoso | ✓ |
| GET | /storage/files | Listar archivos | ✓ |
| GET | /storage/files/:id | Metadata de archivo | ✓ |
| GET | /storage/files/:id/download | Presigned download URL | ✓ |
| PATCH | /storage/files/:id | Actualizar metadata | ✓ |
| DELETE | /storage/files/:id | Eliminar archivo (soft) | ✓ |
| GET | /storage/usage | Uso actual | ✓ |
## Flujo de Upload
```
1. Cliente solicita upload URL
POST /storage/upload-url
Body: { filename, mimeType, sizeBytes, folder?, visibility? }
2. Backend valida:
- Tipo MIME permitido
- Extension no bloqueada
- Tamaño dentro de limite
- Espacio disponible
3. Backend genera presigned URL (AWS SDK v3)
- Expira en 15 minutos
- Limite de tamaño
4. Cliente sube directo a S3/R2
PUT [presigned-url]
Headers: Content-Type
Body: [file binary]
5. Cliente confirma
POST /storage/confirm
Body: { uploadId, metadata? }
6. Backend:
- Verifica archivo existe (HeadObject)
- Crea registro en storage.files
- Actualiza storage.usage via trigger
```
## Tipos MIME Permitidos
```typescript
const ALLOWED_MIME_TYPES = {
images: ['image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/svg+xml'],
documents: [
'application/pdf',
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
],
spreadsheets: [
'application/vnd.ms-excel',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
],
data: ['text/csv', 'application/json', 'text/plain']
};
const BLOCKED_EXTENSIONS = ['.exe', '.bat', '.sh', '.php', '.js', '.cmd', '.com', '.scr'];
```
## Proveedores Soportados
| Proveedor | Uso | Config |
|-----------|-----|--------|
| AWS S3 | Produccion | Bucket por region |
| Cloudflare R2 | Produccion | Sin egress fees |
| MinIO | Desarrollo | Docker local |
## Limites por Plan
| Plan | Storage | Max Archivo |
|------|---------|-------------|
| Free | 100 MB | 5 MB |
| Starter | 1 GB | 25 MB |
| Pro | 10 GB | 100 MB |
| Enterprise | Ilimitado | 500 MB |
## Frontend Hooks
```typescript
// Listar archivos
const { data, isLoading } = useFiles({ page, limit, folder, search });
// Obtener archivo individual
const { data: file } = useFile(fileId);
// Uso de almacenamiento
const { data: usage } = useStorageUsage();
// Subir archivo
const uploadMutation = useUploadFile();
await uploadMutation.mutateAsync({
file,
folder: 'documents',
visibility: 'private',
onProgress: (percent) => setProgress(percent)
});
// Actualizar archivo
const updateMutation = useUpdateFile();
await updateMutation.mutateAsync({ id, data: { folder: 'archive' } });
// Eliminar archivo
const deleteMutation = useDeleteFile();
await deleteMutation.mutateAsync(fileId);
```
## Configuracion
### Variables de Entorno
```bash
# Provider: s3 | r2 | minio
STORAGE_PROVIDER=s3
STORAGE_BUCKET=my-bucket
# AWS/S3
AWS_REGION=us-east-1
AWS_ACCESS_KEY_ID=...
AWS_SECRET_ACCESS_KEY=...
# Para R2/MinIO (opcional)
STORAGE_ENDPOINT=https://...
```
## Criterios de Aceptacion
- [x] Upload presigned funciona
- [x] Download presigned funciona
- [x] Limites se respetan
- [ ] Thumbnails se generan (futuro)
- [x] Uso se trackea
- [x] Archivos se aislan por tenant
## Dependencias
### Depende de
- SAAS-002 (Tenants)
- SAAS-005 (Plans - limites)
- AWS S3 / Cloudflare R2 / MinIO
### Bloquea a
- Upload de avatares
- Adjuntos en modulos
- Importacion de datos
## Notas de Implementacion
1. **AWS SDK v3**: Se usa `@aws-sdk/client-s3` y `@aws-sdk/s3-request-presigner`
2. **Soft Delete**: Los archivos se marcan como deleted, no se eliminan fisicamente de inmediato
3. **RLS Policies**: Aplicadas para aislamiento por tenant
4. **Triggers**: El uso se actualiza automaticamente via trigger
---
**Ultima actualizacion:** 2026-01-10