Productos Relacionados - bootcamp-uchile-2026/grupo-2-backend GitHub Wiki

Productos Relacionados

La funcionalidad Productos Relacionados permite mostrar una selección de productos vinculados al producto que el usuario está consultando en BuenOrigen.

Esta información puede presentarse dentro del detalle del producto para facilitar el descubrimiento de otros productos pertenecientes a la misma subcategoría.

Desde la perspectiva Backend, esta funcionalidad consiste en identificar el producto consultado, buscar otros productos de su misma subcategoría, ordenarlos según su nivel de ventas y devolver una cantidad determinada de resultados.


Objetivo

Proporcionar a Frontend y Mobile una selección de productos relacionados con el producto que se está visualizando.


Estado

Implementado

La funcionalidad se encuentra implementada mediante:

ProductosModule
      |
      v
ProductosController
      |
      v
ProductosService
      |
      v
ProductoRelacionadoDto[]

Responsabilidad Backend

Actualmente el Backend debe:

  • Recibir el identificador del producto consultado.
  • Transformar productoId a un número entero.
  • Recibir la cantidad máxima de productos relacionados.
  • Transformar cantidad a un número entero.
  • Verificar que el producto consultado exista.
  • Buscar otros productos pertenecientes a la misma subcategoría.
  • Excluir el producto que se está consultando.
  • Ordenar los productos relacionados según ventasUltimoMes.
  • Limitar los resultados según cantidad.
  • Transformar los resultados a ProductoRelacionadoDto.
  • Retornar [] cuando no existan productos relacionados.
  • Responder 404 Not Found cuando el producto consultado no exista.

Contrato de API

Endpoint

GET /productos/:productoId/relacionados

Ejemplo:

GET /productos/1/relacionados?cantidad=4

Parámetro de Ruta

productoId

Tipo esperado:

number

Es obligatorio.

Query Parameter

cantidad

Tipo esperado:

number

Es obligatorio.

DTO de salida

ProductoRelacionadoDto[]

La definición general del endpoint se encuentra centralizada en:

Endpoints

La estructura completa del DTO se encuentra documentada en:

DTOs


Parámetro productoId

El Controller recibe el identificador utilizando:

@Param('productoId', ParseIntPipe) productoId: number

Por ejemplo:

GET /productos/1/relacionados?cantidad=4

el valor recibido desde HTTP corresponde inicialmente a:

"1"

ParseIntPipe lo transforma en:

1

Conceptualmente:

HTTP
"1"
 |
 v
ParseIntPipe
 |
 v
1
 |
 v
ProductosController

Query Parameter cantidad

El parámetro cantidad también utiliza:

@Query('cantidad', ParseIntPipe) cantidad: number

Por ejemplo:

GET /productos/1/relacionados?cantidad=4

el valor:

"4"

se transforma en:

4

Conceptualmente:

HTTP
"4"
 |
 v
ParseIntPipe
 |
 v
4
 |
 v
ProductosController

Validación de Parámetros

Ambos parámetros utilizan:

ParseIntPipe

Por lo tanto, solicitudes como:

GET /productos/abc/relacionados?cantidad=4

o:

GET /productos/1/relacionados?cantidad=abc

no contienen valores enteros válidos.

NestJS responde:

400 Bad Request

antes de ejecutar la lógica de ProductosService.


Validación Actual de cantidad

Actualmente ParseIntPipe comprueba que:

cantidad

pueda convertirse a un número entero.

Sin embargo, todavía no existe una validación adicional que establezca explícitamente:

cantidad >= 1

ni un máximo permitido.

Por lo tanto, valores como:

0
-1

no se encuentran controlados mediante una regla de negocio específica.

Esta validación podrá mejorarse posteriormente.


Verificación del Producto

Antes de buscar productos relacionados, el Service verifica que el producto solicitado exista.

Conceptualmente:

Producto[]
    |
    | find()
    v
producto.id === productoId
    |
 +--+--+
 |     |
Sí     No
 |     |
 v     v
Producto  undefined

Si el producto no existe, el Service genera:

NotFoundException

y responde:

404 Not Found

Criterio de Relación

Actualmente dos productos se consideran relacionados cuando pertenecen a la misma:

subcategoriaId

Conceptualmente:

Producto consultado
        |
        | subcategoriaId
        v
Buscar productos
con la misma subcategoriaId
        |
        v
Productos relacionados

Por ejemplo:

Producto 1
subcategoriaId = 3

Producto 2
subcategoriaId = 3
→ relacionado

Producto 3
subcategoriaId = 5
→ no relacionado

Exclusión del Producto Consultado

El producto consultado no debe aparecer dentro de sus propios productos relacionados.

La condición utilizada corresponde a:

producto.id !== productoId

Por lo tanto, el filtrado completo utiliza conceptualmente:

producto.subcategoriaId === productoEncontrado.subcategoriaId &&
producto.id !== productoId

Filtrado de Productos Relacionados

La operación puede representarse como:

Producto[]
    |
    | filter()
    v
Misma subcategoriaId
    +
id diferente al producto consultado
    |
    v
Productos relacionados

Ejemplo:

Producto consultado
id = 1
subcategoriaId = 2

Producto A
id = 2
subcategoriaId = 2
→ incluir

Producto B
id = 3
subcategoriaId = 2
→ incluir

Producto C
id = 4
subcategoriaId = 5
→ excluir

Producto consultado
id = 1
subcategoriaId = 2
→ excluir

Ordenamiento

Después del filtrado, los productos relacionados se ordenan mediante:

ventasUltimoMes

de mayor a menor.

La comparación utilizada es:

(a, b) => b.ventasUltimoMes - a.ventasUltimoMes

Por ejemplo:

Producto A → 20 ventas
Producto B → 80 ventas
Producto C → 45 ventas

después del ordenamiento:

Producto B → 80
Producto C → 45
Producto A → 20

Significado de Popularidad

La propiedad:

ventasUltimoMes

se utiliza únicamente como criterio para ordenar los productos que ya fueron considerados relacionados.

Por lo tanto:

Primero:
misma subcategoría
        |
        v
Después:
ordenar por ventasUltimoMes

No se utilizan actualmente como criterio:

puntuación
favoritos
compras conjuntas
historial del usuario
mismo emprendedor

Limitación de Resultados

Después de ordenar los productos se utiliza:

slice(0, cantidad)

para limitar la respuesta.

Por ejemplo, si existen:

Producto A
Producto B
Producto C
Producto D
Producto E

y se solicita:

GET /productos/1/relacionados?cantidad=3

se devolverán únicamente:

Producto A
Producto B
Producto C

una vez aplicado previamente el ordenamiento.


Flujo Completo

GET /productos/1/relacionados?cantidad=4
                 |
                 v
           ParseIntPipe
       productoId y cantidad
                 |
                 v
       ProductosController
                 |
                 v
        ProductosService
                 |
                 | find()
                 v
         ¿Existe producto?
           |
      +----+----+
      |         |
     Sí         No
      |         |
      v         v
   filter()    404
      |      Not Found
      v
Misma subcategoriaId
+
id distinto
      |
      v
    sort()
      |
      v
ventasUltimoMes DESC
      |
      v
   slice()
      |
      v
Limitar por cantidad
      |
      v
     map()
      |
      v
ProductoRelacionadoDto[]
      |
      v
    200 OK

ProductoRelacionadoDto

Cada producto seleccionado se transforma a:

ProductoRelacionadoDto

con la estructura:

export class ProductoRelacionadoDto {
  id: number;
  marca: string;
  nombre: string;
  precio: number;
  imagenUrl: string;
}

La respuesta corresponde a:

ProductoRelacionadoDto[]

Modelo vs DTO

El modelo Producto contiene información adicional utilizada para determinar la relación y el orden.

Por ejemplo:

subcategoriaId
ventasUltimoMes

Estas propiedades son utilizadas internamente por el Backend, pero no forman parte de:

ProductoRelacionadoDto

Conceptualmente:

Producto
   |
   | subcategoriaId
   | ventasUltimoMes
   | se utilizan internamente
   v
Producto relacionado
   |
   | seleccionar campos públicos
   v
ProductoRelacionadoDto

Ejemplo de Solicitud

GET /productos/1/relacionados?cantidad=4

Ejemplo de Respuesta

[
  {
    "id": 2,
    "marca": "EcoWear",
    "nombre": "Polera orgánica",
    "precio": 15990,
    "imagenUrl": "https://ejemplo.cl/productos/polera-organica.jpg"
  },
  {
    "id": 5,
    "marca": "EcoWear",
    "nombre": "Polerón de algodón orgánico",
    "precio": 24990,
    "imagenUrl": "https://ejemplo.cl/productos/poleron-organico.jpg"
  }
]

Respuesta:

200 OK

Resultado sin Productos Relacionados

Si el producto existe pero no existen otros productos que cumplan:

misma subcategoriaId
+
id diferente

el resultado de filter() será:

[]

La API responde:

200 OK

Esto no se considera un error.


Diferencia entre Producto Inexistente y Sin Relacionados

Es importante distinguir ambos casos.

Producto no existe
        |
        v
404 Not Found

Mientras que:

Producto existe
pero no tiene
productos relacionados
        |
        v
200 OK
[]

Cantidad Superior a los Resultados Disponibles

Si existen solamente dos productos relacionados y el cliente solicita:

GET /productos/1/relacionados?cantidad=10

slice() devolverá únicamente los productos disponibles.

Por ejemplo:

Relacionados disponibles: 2
Cantidad solicitada:     10
Resultado:                2

Esto no genera un error.


Manejo de Errores

productoId inválido

Ejemplo:

GET /productos/abc/relacionados?cantidad=4

Respuesta:

400 Bad Request

cantidad inválida

Ejemplo:

GET /productos/1/relacionados?cantidad=abc

Respuesta:

400 Bad Request

Producto inexistente

Ejemplo:

GET /productos/999/relacionados?cantidad=4

Respuesta:

404 Not Found

Respuestas HTTP

Código Situación
200 OK Productos relacionados obtenidos correctamente
200 OK con [] El producto existe pero no posee productos relacionados
400 Bad Request productoId o cantidad no corresponde a un entero válido
404 Not Found El producto consultado no existe

Reglas de Negocio Actuales

Actualmente la operación puede resumirse como:

1. productoId debe ser un entero válido.

2. cantidad debe ser un entero válido.

3. El producto consultado debe existir.

4. Si no existe:
      404 Not Found.

5. Buscar productos cuya:
      subcategoriaId sea igual
      a la del producto consultado.

6. Excluir:
      producto.id === productoId.

7. Ordenar por:
      ventasUltimoMes DESC.

8. Limitar mediante:
      slice(0, cantidad).

9. Transformar a:
      ProductoRelacionadoDto[].

10. Si no existen coincidencias:
      retornar [].

Reglas que Actualmente no se Aplican

Actualmente la funcionalidad no considera todavía:

stock disponible
estado activo
estado de publicación
mismo emprendedor
puntuación
favoritos
historial del usuario
compras conjuntas

El criterio actual es intencionalmente simple:

misma subcategoría
+
mayor ventasUltimoMes

Posibles Mejoras Futuras

En una etapa posterior podrán incorporarse criterios más avanzados como:

  • Misma categoría.
  • Mismo emprendedor.
  • Productos complementarios.
  • Productos con características similares.
  • Stock disponible.
  • Productos mejor valorados.
  • Productos comprados conjuntamente.
  • Historial de navegación.
  • Historial de compras.
  • Favoritos del usuario.
  • Recomendaciones personalizadas.
  • Paginación.
  • Validar que cantidad sea mayor que cero.
  • Establecer una cantidad máxima permitida.

Estas mejoras no forman parte de la implementación actual.


Relación con Detalle de Producto

Los productos relacionados corresponden a información ampliada asociada a:

Detalle de Producto

pero se consultan mediante un endpoint independiente.

Detalle de Producto
        |
        +------------------------------+
        |                              |
        v                              v
ProductoDetalleDto          Productos Relacionados
                                     |
                                     v
                         ProductoRelacionadoDto[]

Documentación Swagger

El endpoint se encuentra documentado mediante:

@ApiOperation()
@ApiParam()
@ApiQuery()
@ApiOkResponse()
@ApiNotFoundResponse()

productoId se documenta mediante:

@ApiParam()

mientras que:

cantidad

se documenta mediante:

@ApiQuery()

La respuesta exitosa se documenta como un arreglo de:

ProductoRelacionadoDto

Para mayor información consultar:

OpenAPI y Swagger


Relación con la Arquitectura

La funcionalidad pertenece al dominio:

Productos

y utiliza:

ProductosModule
      |
      v
ProductosController
      |
      v
ProductosService
      |
      v
ProductoRelacionadoDto[]

Páginas relacionadas: