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

7.5 KiB

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

  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

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

  • Upload presigned funciona
  • Download presigned funciona
  • Limites se respetan
  • Thumbnails se generan (futuro)
  • Uso se trackea
  • 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