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
productoIda un número entero. - Recibir la cantidad máxima de productos relacionados.
- Transformar
cantidada 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 Foundcuando 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:
La estructura completa del DTO se encuentra documentada en:
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
cantidadsea 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:
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:
Relación con la Arquitectura
La funcionalidad pertenece al dominio:
Productos
y utiliza:
ProductosModule
|
v
ProductosController
|
v
ProductosService
|
v
ProductoRelacionadoDto[]
Páginas relacionadas: