Saltar a contenido

Autor: Adalberto González

Fecha creacion: 2026-05-20
Estado: desarrollo
Tipo: componente

Componente: CreacionesMasivaProductos

Selector: app-creaciones-masiva-productos
Ubicación: src/app/components/creaciones-masiva-productos
Acceso: Autenticado | Rol: Todos


¿Qué hace?

Modal de 3 pasos (stepper) que permite cargar un archivo Excel con múltiples productos y crearlos en lote en la colección Productos de MongoDB. Valida cada fila con reglas estructurales y de negocio antes de permitir la importación, gestiona la asignación de imágenes por producto, y muestra un resumen de creados vs. fallidos al finalizar.


Decoradores

Decorador Nombre Tipo Descripción
@Input isOpen boolean Controla la visibilidad del modal. Lo gestiona PortafolioComponent.
@Output closeEvent EventEmitter<void> Emite cuando el usuario cierra el modal para que el padre actualice su flag de apertura.
@Output refreshDataEvent EventEmitter<void> Emite cuando al menos un producto fue creado, para que el padre recargue la tabla.
@ViewChild inputArchivo ElementRef<HTMLInputElement> Referencia al input de archivo para limpiarlo programáticamente en limpiarArchivo().

Propiedades clave

Propiedad Tipo Descripción
paso 1 \| 2 \| 3 Paso activo del stepper.
archivoSeleccionado File \| null Archivo Excel cargado por el usuario en el paso 1.
filasPreview FilaPreview[] Resultado de evaluar todas las filas del Excel (válidas e inválidas).
filasValidas FilaPreview[] Copia de las filas que pasaron todas las validaciones. Se usa en pasos 2 y 3.
productoActualIndex number Índice del producto mostrado en el paso 2 de asignación de imágenes.
resumen ResumenItem[] Resultado de la importación: uno por cada fila válida procesada.
importando boolean true mientras se ejecuta la importación en el paso 3. Bloquea el botón de cerrar.
validator ProductoImportacionValidator Instancia del validador concreto (Template Method).

Servicios y endpoints

Servicio Método Endpoint Cuándo
ConsumoGenericoService consultarGenerico() GET metodoGenerico?coleccion=Tienda&Estado=ACTIVO Al abrir el modal (tiendas y proveedores)
ConsumoGenericoService consultarGenerico() GET metodoGenerico?coleccion=ProductosCategorias Al abrir el modal (categorías)
ConsumoGenericoService consultarGenerico() GET metodoGenerico?coleccion=Productos Al abrir el modal y antes de crear cada producto (race condition)
ConsumoGenericoService insertarGenerico() POST productos Por cada producto válido en el paso 3
ConsumoGenericoService insertarGenerico() POST actualizaInventarioLotes Por cada producto creado (registro de ingreso de inventario)
GetObjectService obtenerObjeto() S3 logigho-plantillas/Productos.xlsx Al abrir el modal (descarga la plantilla)
PutObjectService cargarInformacion() S3 imagenes-productos-logigho Por cada producto con imagen asignada en el paso 2

Flujo principal

ngOnChanges() [isOpen → true]
  → resetear()               — limpia estado de sesión anterior
  → cargarDatosBD()          — en paralelo:
      → cargarTiendas()      — GET Tienda (Zstd)
      → cargarCategorias()   — GET ProductosCategorias (Gzip)
      → cargarProveedores()  — GET Tienda filtrado por TipoTienda=Proveedor (Zstd)
      → cargarProductosExistentes() — GET Productos (Gzip) — base para IDs y SKUs
  → cargarPlantilla()        — GET S3 logigho-plantillas/Productos.xlsx

── PASO 1: Validar Excel ──
onArchivoSeleccionado()      — guarda File, limpia filasPreview
procesarExcel()
  → validar extensión (.xlsx / .csv)
  → leerExcel()              — FileReader → XLSX.read()
  → XLSX.utils.sheet_to_json
  → validar columnas con COLUMNAS_ESPERADAS
  → buildContexto()          — arma ContextoValidacion con tiendas/categorías/proveedores
  → validator.evaluarFila()  — por cada fila (Template Method)
  → filasPreview = [...]
avanzarAPaso2()
  → filasValidas = filasValidas_ (solo las que pasaron)
  → paso = 2

── PASO 2: Asignación de imágenes ──
onImagenSeleccionada()       — guarda File + genera URL.createObjectURL para preview
omitirImagen()               — limpia imagenFile y avanza al siguiente
avanzarAPaso3()
  → paso = 3
  → ejecutarImportacion()

── PASO 3: Importación ──
ejecutarImportacion() [por cada fila válida]
  → cargarProductosExistentes()    — consulta fresca para evitar race condition
  → obtenerIdConsecutivo()         — max(id) + 1
  → obtenerIdProductoConsecutivo() — max(idproducto) + 1
  → SKU dedup (Set local + cross-check BD)
  → subir imagen a S3 (si aplica) → URL CloudFront
  → insertarGenerico (colección Productos)
  → insertarGenerico (actualizaInventarioLotes)
  → resumen.push(...)
→ refreshDataEvent.emit() si hay al menos 1 creado

Historial de cambios

Fecha Autor Cambio
2026-05-20 Adalberto González Creación del componente con stepper de 3 pasos (validar Excel → imágenes → resumen).
2026-05-20 Adalberto González Integración del patrón Template Method (ProductoImportacionValidator) para validación de filas.
2026-05-20 Adalberto González Validación de columnas del Excel con COLUMNAS_ESPERADAS antes de procesar filas.
2026-05-20 Adalberto González Columna NombreCategoria en Excel: el usuario elige por nombre, el sistema resuelve al ID internamente.
2026-05-20 Adalberto González Regla 1 activa: precioventa > precioproveedor. Regla 2 activa: IdTienda debe pertenecer a las tiendas asignadas al usuario.
2026-05-20 Adalberto González Race condition corregida: id e idproducto se recalculan con datos frescos de la BD antes de insertar cada producto.
2026-05-20 Adalberto González SKU deduplicado con Set local + cross-check contra BD para evitar colisiones en el mismo lote.
2026-05-20 Adalberto González Cierre del modal al hacer clic fuera del contenido (modal-overlay + stopPropagation).
2026-05-20 Adalberto González Botón "Limpiar archivo": resetea archivoSeleccionado, filasPreview y el valor del input nativo para permitir re-subir un Excel corregido sin cerrar el modal.

Observaciones

  • Race condition mitigada, no eliminada: La consulta fresca antes de cada inserción reduce la ventana de colisión a milisegundos, pero la solución definitiva es que el backend genere y garantice los IDs atómicamente. Ver ADR para contexto.
  • Plantilla desde S3: Si cargarPlantilla() falla (sin conexión o bucket inaccesible), el botón "Descargar plantilla" queda silencioso (no muestra Swal). Hay un Swal.fire comentado que puede reactivarse si se prefiere feedback explícito.
  • Compresión diferenciada: Tiendas y proveedores usan Zstd (mcomp: '2'), categorías y productos usan Gzip estándar. Seguir este patrón al agregar nuevas fuentes de datos.
  • cantidad se guarda como String: Consistencia con el tipo del campo en MongoDB. El resto de campos numéricos se guardan como Number.
  • Template Method ubicado en portafolio/importacion-template-method/: Para agregar una nueva regla de negocio, editar solo validarNegocio() en ProductoImportacionValidator. Ver ADR-001-template-method-importacion-masiva.md.