| id |
title |
type |
status |
priority |
module |
version |
created_date |
updated_date |
| SAAS-011 |
Storage |
Module |
Published |
P1 |
storage |
1.0.0 |
2026-01-07 |
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
- Upload seguro con presigned URLs
- Organizacion por tenant
- Limites de almacenamiento por plan
- Soporte multi-provider
- 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
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
// 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
# 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
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
- AWS SDK v3: Se usa
@aws-sdk/client-s3 y @aws-sdk/s3-request-presigner
- Soft Delete: Los archivos se marcan como deleted, no se eliminan fisicamente de inmediato
- RLS Policies: Aplicadas para aislamiento por tenant
- Triggers: El uso se actualiza automaticamente via trigger
Ultima actualizacion: 2026-01-10