# INT-005: Integracion S3/MinIO --- id: INT-005 type: Integration status: Pendiente version: "1.0.0" created_date: 2026-01-10 updated_date: 2026-01-10 simco_version: "4.0.0" --- ## Metadata | Campo | Valor | |-------|-------| | **ID** | INT-005 | | **Servicio** | AWS S3 / MinIO | | **Proposito** | Almacenamiento de videos y artefactos | | **Criticidad** | P0 | | **Estado** | Pendiente | --- ## 1. Descripcion Integracion con almacenamiento compatible con S3 para guardar videos de inventario, frames extraidos, y otros artefactos del procesamiento. --- ## 2. Informacion del Servicio | Campo | Valor | |-------|-------| | Desarrollo | MinIO (local) | | Produccion | AWS S3 o DigitalOcean Spaces | | SDK | @aws-sdk/client-s3, @aws-sdk/s3-request-presigner | --- ## 3. Configuracion ### Variables de Entorno ```env # Desarrollo (MinIO) S3_ENDPOINT=http://localhost:9002 S3_ACCESS_KEY=minioadmin S3_SECRET_KEY=minioadmin S3_BUCKET=miinventario S3_REGION=us-east-1 # Produccion (AWS S3) S3_ENDPOINT=https://s3.amazonaws.com S3_ACCESS_KEY=AKIA... S3_SECRET_KEY=... S3_BUCKET=miinventario-prod S3_REGION=us-east-1 ``` ### Instalacion ```bash npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presigner ``` --- ## 4. Estructura de Buckets ``` miinventario/ ├── videos/ │ └── {userId}/{sessionId}/ │ └── video.mp4 ├── frames/ │ └── {sessionId}/ │ ├── frame_001.jpg │ ├── frame_002.jpg │ └── ... ├── thumbnails/ │ └── {sessionId}/ │ └── thumb.jpg ├── evidence/ │ └── {sessionId}/ │ └── closeup_{itemId}.jpg └── products/ └── {productId}/ └── image.jpg ``` --- ## 5. Implementacion Backend ### Servicio S3 ```typescript import { S3Client, PutObjectCommand, GetObjectCommand, DeleteObjectCommand } from '@aws-sdk/client-s3'; import { getSignedUrl } from '@aws-sdk/s3-request-presigner'; @Injectable() export class S3Service { private client: S3Client; private bucket: string; constructor() { this.client = new S3Client({ endpoint: process.env.S3_ENDPOINT, region: process.env.S3_REGION, credentials: { accessKeyId: process.env.S3_ACCESS_KEY, secretAccessKey: process.env.S3_SECRET_KEY, }, forcePathStyle: true, // Necesario para MinIO }); this.bucket = process.env.S3_BUCKET; } async upload(key: string, body: Buffer, contentType: string) { await this.client.send(new PutObjectCommand({ Bucket: this.bucket, Key: key, Body: body, ContentType: contentType, })); return `${process.env.S3_ENDPOINT}/${this.bucket}/${key}`; } async getSignedUploadUrl(key: string, contentType: string, expiresIn = 3600) { const command = new PutObjectCommand({ Bucket: this.bucket, Key: key, ContentType: contentType, }); return getSignedUrl(this.client, command, { expiresIn }); } async getSignedDownloadUrl(key: string, expiresIn = 3600) { const command = new GetObjectCommand({ Bucket: this.bucket, Key: key, }); return getSignedUrl(this.client, command, { expiresIn }); } async delete(key: string) { await this.client.send(new DeleteObjectCommand({ Bucket: this.bucket, Key: key, })); } } ``` ### Upload Multipart ```typescript import { CreateMultipartUploadCommand, UploadPartCommand, CompleteMultipartUploadCommand } from '@aws-sdk/client-s3'; async initMultipartUpload(key: string) { const { UploadId } = await this.client.send( new CreateMultipartUploadCommand({ Bucket: this.bucket, Key: key, }) ); return UploadId; } async uploadPart(key: string, uploadId: string, partNumber: number, body: Buffer) { const { ETag } = await this.client.send( new UploadPartCommand({ Bucket: this.bucket, Key: key, UploadId: uploadId, PartNumber: partNumber, Body: body, }) ); return { ETag, PartNumber: partNumber }; } async completeMultipartUpload(key: string, uploadId: string, parts: any[]) { await this.client.send( new CompleteMultipartUploadCommand({ Bucket: this.bucket, Key: key, UploadId: uploadId, MultipartUpload: { Parts: parts }, }) ); } ``` --- ## 6. Flujo de Upload de Video ``` ┌──────────┐ ┌──────────┐ ┌──────────┐ │ Mobile │───▶│ Backend │───▶│ S3 │ └──────────┘ └──────────┘ └──────────┘ │ │ │ │ 1. Init │ │ │ upload │ │ │──────────────▶│ │ │ │ 2. Create │ │ │ multipart │ │ │──────────────▶│ │◀──────────────│ uploadId │ │ │ │ │ 3. Upload │ │ │ parts │ │ │───────────────────────────────▶ │ (presigned) │ │ │ │ │ │ 4. Complete │ │ │──────────────▶│ │ │ │ 5. Complete │ │ │ multipart │ │ │──────────────▶│ │◀──────────────│ │ ``` --- ## 7. Lifecycle Rules ### Configuracion de Expiracion ```json { "Rules": [ { "ID": "DeleteOldVideos", "Status": "Enabled", "Filter": { "Prefix": "videos/" }, "Expiration": { "Days": 30 } }, { "ID": "DeleteOldFrames", "Status": "Enabled", "Filter": { "Prefix": "frames/" }, "Expiration": { "Days": 7 } } ] } ``` --- ## 8. Docker Compose (MinIO) ```yaml minio: image: minio/minio container_name: mii_minio ports: - "9002:9000" - "9003:9001" environment: MINIO_ROOT_USER: minioadmin MINIO_ROOT_PASSWORD: minioadmin command: server /data --console-address ":9001" volumes: - minio_data:/data healthcheck: test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"] interval: 30s timeout: 20s retries: 3 ``` --- ## 9. Consideraciones de Seguridad | Aspecto | Implementacion | |---------|----------------| | URLs firmadas | Expiracion corta (1h) | | Bucket privado | No acceso publico | | CORS | Solo dominios permitidos | | Encriptacion | SSE-S3 en reposo | --- ## 10. Referencias - [AWS S3 SDK](https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/s3-example-creating-buckets.html) - [MinIO Docs](https://min.io/docs/minio/linux/index.html) - [MII-004](../01-epicas/MII-004-captura-video.md) - Captura de video --- **Ultima Actualizacion:** 2026-01-10