--- id: INT-011 type: Integration title: "Storage Cloud" provider: "AWS S3/Cloudflare R2/MinIO" status: Planificado integration_type: "storage" created_at: 2026-01-10 updated_at: 2026-01-10 simco_version: "4.0.1" tags: - storage - s3 - r2 - files - multi-cloud --- # INT-011: Storage Cloud ## Metadata | Campo | Valor | |-------|-------| | **Codigo** | INT-011 | | **Proveedor** | AWS S3, Cloudflare R2, MinIO | | **Tipo** | Almacenamiento | | **Estado** | Planificado | | **Multi-tenant** | Si | | **Epic Relacionada** | MCH-029 | | **Owner** | Backend Team | --- ## 1. Descripcion Sistema de almacenamiento abstracto que soporta multiples proveedores cloud (S3, R2, MinIO). Permite subir archivos como imagenes de productos, facturas y documentos con URLs firmadas y control de acceso por tenant. **Casos de uso principales:** - Imagenes de productos - Fotos de perfil de usuario - Facturas y recibos PDF - Respaldos de datos - Assets de la tienda (logo, banner) --- ## 2. Credenciales Requeridas ### Variables de Entorno | Variable | Descripcion | Tipo | Obligatorio | |----------|-------------|------|-------------| | `STORAGE_PROVIDER` | Proveedor (s3/r2/minio) | string | SI | | `S3_BUCKET` | Nombre del bucket | string | SI | | `S3_REGION` | Region AWS | string | SI (S3) | | `S3_ACCESS_KEY` | Access Key | string | SI | | `S3_SECRET_KEY` | Secret Key | string | SI | | `S3_ENDPOINT` | Endpoint custom (R2/MinIO) | string | NO | | `STORAGE_CDN_URL` | URL de CDN (opcional) | string | NO | ### Ejemplo de .env ```env # Storage Configuration STORAGE_PROVIDER=s3 # AWS S3 S3_BUCKET=michangarrito-uploads S3_REGION=us-east-1 S3_ACCESS_KEY=AKIAXXXXXXXXXXXXXXXX S3_SECRET_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx # Cloudflare R2 (alternativa) # STORAGE_PROVIDER=r2 # S3_ENDPOINT=https://xxx.r2.cloudflarestorage.com # S3_ACCESS_KEY=xxx # S3_SECRET_KEY=xxx # MinIO (desarrollo) # STORAGE_PROVIDER=minio # S3_ENDPOINT=http://localhost:9000 # S3_ACCESS_KEY=minioadmin # S3_SECRET_KEY=minioadmin ``` --- ## 3. SDK Utilizado ### AWS SDK v3 ```typescript import { S3Client, PutObjectCommand, GetObjectCommand } from '@aws-sdk/client-s3'; import { getSignedUrl } from '@aws-sdk/s3-request-presigner'; const s3Client = new S3Client({ region: process.env.S3_REGION, endpoint: process.env.S3_ENDPOINT, credentials: { accessKeyId: process.env.S3_ACCESS_KEY, secretAccessKey: process.env.S3_SECRET_KEY, }, }); ``` ### Operaciones | Operacion | Comando SDK | Descripcion | |-----------|-------------|-------------| | Upload | PutObjectCommand | Subir archivo | | Download | GetObjectCommand | Descargar archivo | | Delete | DeleteObjectCommand | Eliminar archivo | | List | ListObjectsV2Command | Listar archivos | | Signed URL | getSignedUrl | URL temporal | --- ## 4. Limites por Plan | Plan | Almacenamiento | Archivos Max | Tamano Max/Archivo | |------|----------------|--------------|-------------------| | Basic | 1 GB | 500 | 5 MB | | Pro | 10 GB | 5,000 | 25 MB | | Enterprise | 100 GB | 50,000 | 100 MB | ### MIME Types Permitidos ```typescript const ALLOWED_MIME_TYPES = [ 'image/jpeg', 'image/png', 'image/webp', 'image/gif', 'application/pdf', 'application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', ]; ``` --- ## 5. Manejo de Errores | Codigo | Descripcion | Accion | Retry | |--------|-------------|--------|-------| | 400 | Archivo invalido | Validar MIME/size | NO | | 403 | Sin permisos | Verificar policy | NO | | 404 | Archivo no existe | - | NO | | 413 | Archivo muy grande | Reducir tamano | NO | | 500 | Error de storage | Retry | SI | | 503 | Servicio no disponible | Retry con backoff | SI | --- ## 6. Estructura de Archivos ### Path Convention ``` {bucket}/ ├── tenants/ │ └── {tenant_id}/ │ ├── products/ │ │ └── {product_id}/ │ │ ├── main.jpg │ │ └── thumb.jpg │ ├── invoices/ │ │ └── {year}/{month}/ │ │ └── INV-{id}.pdf │ ├── users/ │ │ └── {user_id}/ │ │ └── avatar.jpg │ └── assets/ │ ├── logo.png │ └── banner.jpg └── public/ └── templates/ ``` ### Ejemplo de Path ``` tenants/550e8400-e29b-41d4-a716-446655440000/products/abc123/main.jpg ``` --- ## 7. URLs Firmadas ### Generacion ```typescript async function getSignedUploadUrl( tenantId: string, filename: string, contentType: string, expiresIn: number = 3600 ): Promise { const key = `tenants/${tenantId}/uploads/${Date.now()}-${filename}`; const command = new PutObjectCommand({ Bucket: process.env.S3_BUCKET, Key: key, ContentType: contentType, }); return getSignedUrl(s3Client, command, { expiresIn }); } ``` ### Expiracion por Tipo | Tipo | Expiracion | Uso | |------|------------|-----| | Upload | 1 hora | Subida de archivos | | Download publico | 24 horas | Imagenes de productos | | Download privado | 15 minutos | Facturas, documentos | --- ## 8. Multi-tenant ### Aislamiento - Cada tenant tiene su propio directorio - RLS en tabla `files` por `tenant_id` - Bucket policies restringen acceso ### Almacenamiento de Metadata ```sql CREATE SCHEMA IF NOT EXISTS storage; CREATE TABLE storage.files ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL, folder_id UUID REFERENCES storage.folders(id), name VARCHAR(255) NOT NULL, original_name VARCHAR(255) NOT NULL, path VARCHAR(1000) NOT NULL, size_bytes BIGINT NOT NULL, mime_type VARCHAR(100) NOT NULL, provider VARCHAR(20) NOT NULL, url TEXT, is_public BOOLEAN DEFAULT false, metadata JSONB, created_by UUID, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), deleted_at TIMESTAMP WITH TIME ZONE ); CREATE TABLE storage.folders ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL, parent_id UUID REFERENCES storage.folders(id), name VARCHAR(255) NOT NULL, path VARCHAR(1000) NOT NULL, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); CREATE TABLE storage.storage_usage ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID UNIQUE NOT NULL, bytes_used BIGINT DEFAULT 0, files_count INTEGER DEFAULT 0, last_calculated_at TIMESTAMP WITH TIME ZONE ); ``` --- ## 9. Testing ### MinIO para Desarrollo ```yaml # docker-compose.yml services: minio: image: minio/minio:latest ports: - "9000:9000" - "9001:9001" environment: MINIO_ROOT_USER: minioadmin MINIO_ROOT_PASSWORD: minioadmin command: server /data --console-address ":9001" volumes: - minio_data:/data ``` ### Test de Conexion ```bash # Listar buckets aws s3 ls --endpoint-url http://localhost:9000 # Subir archivo aws s3 cp test.jpg s3://michangarrito-uploads/test/ --endpoint-url http://localhost:9000 ``` --- ## 10. Monitoreo ### Metricas | Metrica | Descripcion | Alerta | |---------|-------------|--------| | storage_bytes_total | Bytes usados | > 80% plan | | storage_uploads_total | Uploads exitosos | - | | storage_failures_total | Uploads fallidos | > 5% | | storage_signed_urls_total | URLs generadas | - | --- ## 11. Referencias - [AWS S3 Developer Guide](https://docs.aws.amazon.com/s3/) - [Cloudflare R2 Docs](https://developers.cloudflare.com/r2/) - [MinIO Docs](https://min.io/docs/minio/linux/index.html) - [ADR-0006: Storage Abstraction](../97-adr/ADR-0006-storage-abstraction.md) --- **Ultima actualizacion:** 2026-01-10 **Autor:** Backend Team