Skip to Content
📚 Bienvenido a la documentación técnica de Rial AI 👋
🔄 FlujosDescarga de imágenes: diseño técnico completo

Descarga de imágenes: diseño técnico completo

Documento técnico de la implementación operativa vigente de descarga de imágenes de Rial AI. Cubre frontend, backend, base de datos, Cloud Tasks, worker, GCS, seguridad, eficiencia, resiliencia, observabilidad y pruebas.

Cuando una guía y el código difieren, esta página describe el código y registra la brecha.

1. Resumen

Rial separa la descarga en dos fases:

  1. Preparación asíncrona: el frontend solicita un IMAGE_DOWNLOAD; el backend autoriza, crea un Job y encola una task. images-download consulta las imágenes y genera una signed URL por archivo.
  2. Transferencia local: el frontend hace polling hasta COMPLETED, descarga directamente desde GCS, genera el ZIP en un Web Worker y lo guarda en disco o como Blob.

El API no transporta todos los bytes ni construye el ZIP principal. La unidad de coordinación es Job; el trabajo se divide en JobBatch.

La optimización central es separar preparar el acceso de mover los bytes: reduce memoria, CPU, conexiones largas y timeouts del API, a cambio de usar recursos del navegador.

2. Alcance, permisos y contenido

ActorCrearCompletar estado clienteScope
COMPANY_ADMINDOWNLOADERSícompañía
BRAND_ADMINDOWNLOADERSímarcas autorizadas
ADMINSíNo usa el cierre clientecompañía seleccionada

Los endpoints aplican JWT, roles, autorización de recurso y DOWNLOADER. El worker vuelve a cargar usuario y scope. Un ID fuera de scope debe rechazar todo: no se crean tasks ni se filtran URLs.

La policy valida combinación válida de IDs/filtros, filtros incompatibles (brandId + projectId, projectName + projectId), estados permitidos, existencia de contenido y uso de companyId sólo por ADMIN.

ModoSelección
InputsADMIN + garmentImagesOnly=true; excluye GarmentType.BYPASS
GeneradasGeneratedImage.isFinal=true, opcionalmente imageStatuses
RechazadasREJECTED no final inmediatamente anterior a PENDING_REGENERATION final
MixtaSólo elementos válidos del scope; BYPASS no cuenta como generada

Los rechazos pueden incluir downloads/{jobId}/reporte_rechazos.xlsx. Una GarmentImage compartida se resuelve con el proyecto solicitado.

3. Contrato HTTP

Prefijo: /api/v1.

Disponibilidad

GET /stats/project-images/download-availability Authorization: Bearer <jwt>

Devuelve inputsCount, pendingInputsCount, approvedImagesCount, generatedImagesCount y rejectedCount. El frontend usa exactamente el mismo alcance. El backend calcula los contadores en paralelo; generadas incluye estados finales PENDING_APPROVAL, APPROVED, UNPUBLISHED e INCOMPLETE.

Crear job

POST /downloads/project-images?status=PENDING_DOWNLOAD&projectId=<public-id> Authorization: Bearer <jwt> Content-Type: application/json { "ids": ["project-image-public-id"] }

Filtros: status, garmentImagesOnly, imageStatuses[], projectName, projectId, companyId, brandId, projectType, startDate, endDate, isPrioritized.

Respuesta: { "jobId": "uuid" }.

El backend crea Job(status=PENDING, progress=0, type=IMAGE_DOWNLOAD), guarda userId, query, IDs y batchSize=200, y encola el orchestrator. Si falla el enqueue, marca FAILED.

Polling

GET /jobs/<jobId> Authorization: Bearer <jwt>

Resultado conceptual: { status, progress, totalBatches, currentBatch, result: { totalImages, signedUrls[] } }. Las signed URLs son credenciales temporales: nunca deben aparecer en logs, errores o documentación.

Completar estado cliente

POST /downloads/project-images/complete?status=PENDING_DOWNLOAD&projectId=<public-id> Authorization: Bearer <jwt> Content-Type: application/json { "ids": ["project-image-public-id"] }

Actualiza sólo ProjectImage.clientStatus=PENDING_DOWNLOAD a COMPLETED, con el mismo scope, y recalcula estados mediante StatusService. Es repetible e ignora filas ya completadas. Sólo se llama para clientes y sin fallos de transferencia.

4. Datos y estados

Job: publicId, type, status, payload, result, progress, totalBatches, currentBatch, startedAt, finishedAt, errorMessage JobBatch: jobId, batchNumber, status, result, errorMessage, startedAt, finishedAt

Estados de Job: PENDING, RUNNING, COMPLETED, FAILED, RETRYING, CANCELLED. Estados de JobBatch: PENDING, RUNNING, DONE, FAILED. unique(jobId, batchNumber) es parte de la idempotencia.

currentBatch se expone como el batch actual, pero la fase vigente lo actualiza con la cantidad de batches DONE.

El código tiene CANCELLED, pero no existe endpoint de cancelación en DownloadsController. El frontend cancela polling y ZIP localmente; una task remota ya entregada puede continuar.

5. Secuencia

6. Backend

DownloadsController sólo compone HTTP. DownloadsService delega en JobsService, que crea el job, persiste filtros/IDs y encola Cloud Tasks. La creación de DB y enqueue no son transacción distribuida; un fallo entre ambos puede dejar un PENDING huérfano. Para una plataforma nueva: outbox o reconciliación periódica.

buildDownloadProjectImagesQuery traduce projectId a project.publicId, projectName al folder, brandId a marca, usuarios no admin a user.companyId, BRAND_ADMIN a marcas asignadas, status a adminStatus/clientStatus, IDs a ProjectImage.publicId, y aplica fechas/prioridad.

El scope debe volver a aplicarse al consultar GarmentImage, GeneratedImage y relaciones compartidas. Validar sólo el padre no protege automáticamente los hijos.

Cloud Tasks usa POST, body base64 { jobId }, OIDC, audience del worker y nombre determinístico download-{jobId}. El cierre masivo condicionado a PENDING_DOWNLOAD evita duplicar side effects.

7. Worker images-download

handler -> DownloadsProcessor -> JobPhasesService orchestrator -> batch -> finalize -> DownloadsService

El handler mapea 405 método, 400 validación, 404 inexistente y 500 transitorio/inesperado. El processor omite jobs COMPLETED/FAILED y orchestrators con totalBatches ya definido.

Orchestrator

  1. Carga job, usuario y payload.
  2. Cuenta garments y generated images según el modo.
  3. Rechaza cero imágenes.
  4. Calcula ceil(imagesCount / batchSize).
  5. Crea batches con skipDuplicates.
  6. Persiste totalBatches, progress=0, totalImages.
  7. Encola una task determinística por batch.

Batch

El batch calcula skip = (batchNumber - 1) * batchSize y take = batchSize. Verifica que no esté DONE, marca RUNNING, consulta con el mismo scope, genera URLs con concurrencia 3, persiste { signedUrls }, marca DONE y calcula floor(doneBatches / totalBatches * 100). Si falla la persistencia, debe responder error para que Cloud Tasks reintente.

Finalize

Lee todos los batches, espera que todos estén DONE, concatena resultados por número y actualiza el job sólo si sigue RUNNING. Dos batches que finalizan simultáneamente no producen doble finalización.

Generated usa isFinal=true y relaciones autorizadas. Inputs excluye BYPASS y construye destino con folder, marca y género. Rechazadas combina la versión rechazada y su regeneración pendiente; el reporte Excel sólo se genera en el último batch.

El código reconsulta cada batch con skip/take; si el dataset cambia durante la descarga, los límites pueden desplazarse.

8. Signed URLs y GCS

El bucket es privado; el worker usa file.getSignedUrl V4 con acción read y una expiración actual de 24 horas. Las URLs no deben guardarse en localStorage ni aparecer en logs.

El worker persiste las URLs en JobBatch.result y Job.result.

9. Frontend

useDownloadImages solicita showSaveFilePicker si está disponible, llama al API, guarda { jobId, enqueuedAt, zipName } en localStorage, inicia el toast y comienza useJobPolling.

Polling: consulta inmediata; 3 s al inicio, 5 s en polls intermedios, 8 s después; timeout global 5 minutos. Se pausa con pestaña oculta y se reanuda con consulta inmediata. Un token de sesión invalida operaciones anteriores.

Reanudación: TTL local 10 minutos. COMPLETED vuelve a ofrecer URLs; FAILED/CANCELLED muestra error y limpia; jobs activos retoman polling; jobs inexistentes/expirados se eliminan. Esto reanuda preparación, no bytes parciales.

useZipDownloadWorker usa client-zip, FileSystemWritableFileStream y fallback Blob/file-saver. createSignedUrlEntryGenerator hace fetch anticipado, transmite response.body mediante TransformStream, cuenta bytes, usa AbortController y normaliza rutas eliminando /, . y ...

La escritura directa evita retener el ZIP completo en RAM; el fallback Blob sí lo acumula. El progreso reserva 90% a fetch y 10% a ZIP, con eventos agrupados cada 100 ms. HTTP no OK, body ausente o URL inválida incrementan failedCount; si todos fallan hay error, si algunos funcionan se entrega ZIP parcial.

La cancelación actual aborta fetch/streams, writable, worker, polling y estado local. No cancela la task remota.

10. Parámetros actuales

El payload usa batchSize=200 y el worker firma con concurrencia 3. El backend legacy de base images usa concurrencia 20, independiente de la del worker.

La implementación consulta y cuenta datos con el scope del usuario, proyecta los campos necesarios y pagina cada batch mediante skip/take.

JPEG/PNG se comprimen en el ZIP del navegador. La ruta legacy de base images usa zlib nivel 1.

11. Resiliencia e idempotencia

FaseProtección
Orchestratorno repetir si existe totalBatches
Batchesunique (jobId, batchNumber) + skipDuplicates
Queuetask IDs determinísticos + ALREADY_EXISTS
Batchomitir si ya está DONE
Finalizeupdate condicional status=RUNNING
Pollingsettle idempotente + token
Completeactualizar sólo PENDING_DOWNLOAD

Si se crean URLs y falla persistencia, el batch no está completo y debe reintentarse. Si una transferencia browser falla, no se llama complete ni se marcan imágenes como descargadas.

12. Seguridad y observabilidad

JWT para usuarios, OIDC para Cloud Tasks, bucket privado, object keys con scope exacto, revalidación de tenant en cada fase, TTL corta, limpieza de resultados y normalización de rutas ZIP. No loggear URLs, JWT, headers ni payloads sensibles.

Logs: jobId, batchNumber, intento, fase, transición, duración y counts. El flujo registra jobs por estado, duración de orchestrator/batch, URLs por segundo, bytes, fallos por URL y retries.

13. Estrategia de pruebas

Backend: roles/scope, filtros incompatibles, IDs inexistentes/externos, disponibilidad por tipo, payload del job, fallo de enqueue, cierre idempotente y carreras.

Worker: handler 405/400/404/500 y retry; terminales/duplicados; orchestrator sin imágenes; batches y enqueue duplicado; batch DONE; fallos de URL/persistencia; finalize concurrente; queries de scope; concurrencia; storage y redacción; integración completa con Prisma.

Frontend: query strings, persistencia, backoff, timeout, visibility pause, reanudación de todos los estados, cancelación, no complete con fallos ni para ADMIN, progreso no 100% en error, rutas ZIP, URL expirada, fallback Worker/hilo y File System Access/Blob.

Carga: 10, 100, 500, 2.000 y 10.000 imágenes; tamaños variados; fallos GCS; tasks duplicadas; múltiples instancias; reload durante polling/ZIP; memoria, latencia, egress, retries y throughput.

14. Estado actual y límites conocidos

  1. Cancelación: DownloadsController no expone un endpoint remoto; la UI sólo cancela polling y transferencia local.
  2. TTL: el worker configura actualmente 24 horas para las signed URLs de descarga.
  3. ZIP: documentación antigua describe ZIP generado por worker; el flujo vigente genera URLs y comprime en browser. downloadModelsBaseImages es otra ruta.
  4. Progreso: currentBatch documentado como activo, pero guardado como batches terminados.
  5. Reanudación: guarda job/nombre, no checkpoint de bytes.
  6. DB + queue: no son atómicos; pueden quedar jobs huérfanos.
  7. Paginación: skip/take puede cambiar si cambia el dataset.

15. Fuentes revisadas

Backend: src/data/downloads, src/data/jobs, src/lib/policies/download.policies.ts, src/lib/query/build-download-project-images-query.ts, src/services/cloud-tasks/cloud-tasks.service.ts, prisma/schema/job.prisma Worker: images-download/src/handler.ts, processor/downloads.processor.ts, services/job/phases.service.ts, services/downloads.service.ts, services/buckets.service.ts, services/cloud-tasks.service.ts Frontend: src/hooks/download, src/hooks/useJobPolling.ts, src/hooks/download/useZipDownloadWorker.ts, src/workers/zip-download.worker.ts, src/lib/download, src/services/download.service.ts

Si cambian estados, contratos, TTL, cancelación o estrategia ZIP, deben actualizarse esta página y las skills de flujo correspondientes.

Last updated on