Productos más Populares - bootcamp-uchile-2026/grupo-2-backend GitHub Wiki

Productos más Populares

La funcionalidad Productos más Populares permite mostrar en la página de inicio de BuenOrigen una selección de los productos con mayor nivel de ventas.

Desde la perspectiva Backend, esta funcionalidad consiste en ordenar los productos según la cantidad de ventas registradas durante el último mes y devolver únicamente la cantidad solicitada por Frontend o Mobile.


Objetivo

Proporcionar a Frontend y Mobile una selección de los productos más vendidos para ser presentada en la página de inicio de BuenOrigen.


Estado

Implementado

La funcionalidad se encuentra implementada mediante:

ProductosModule
      |
      v
ProductosController
      |
      v
ProductosService
      |
      v
ProductoMasVendidoDto[]

Responsabilidad Backend

Actualmente el Backend debe:

  • Recibir la cantidad de productos solicitados.
  • Transformar el Query Parameter cantidad a un número entero.
  • Consultar los productos disponibles.
  • Ordenar los productos según ventasUltimoMes.
  • Presentar primero los productos con mayor cantidad de ventas.
  • Limitar los resultados según cantidad.
  • Transformar cada producto a ProductoMasVendidoDto.
  • Devolver los resultados a Frontend o Mobile.

Contrato de API

Endpoint

GET /productos/mas-vendidos

Query Parameter

cantidad

El parámetro es obligatorio.

Ejemplo:

GET /productos/mas-vendidos?cantidad=5

DTO de salida

ProductoMasVendidoDto[]

Página relacionada:

Productos más Populares

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

Endpoints

La estructura completa del DTO se encuentra documentada en:

DTOs


Query Parameter cantidad

El endpoint recibe:

cantidad

mediante:

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

Esto significa que el valor recibido originalmente desde HTTP debe poder convertirse correctamente a un número entero.

Por ejemplo:

GET /productos/mas-vendidos?cantidad=5

HTTP entrega inicialmente:

"5"

y ParseIntPipe lo transforma en:

5

antes de entregarlo al método del Controller.

Conceptualmente:

HTTP
"5"
 |
 v
ParseIntPipe
 |
 v
5
 |
 v
ProductosController

Validación de cantidad

Actualmente ParseIntPipe permite validar que el parámetro pueda interpretarse como un número entero.

Por ejemplo:

GET /productos/mas-vendidos?cantidad=abc

no corresponde a una cantidad válida y NestJS responde con:

400 Bad Request

También se espera que el parámetro sea enviado porque está definido como obligatorio.

Sin embargo, actualmente no existe una regla adicional como:

cantidad >= 1

Por lo tanto, la validación de valores positivos podrá mejorarse posteriormente.

Una posible evolución podría utilizar:

ParseIntPipe
+
validación de mínimo

para impedir cantidades como:

0
-1
-5

Criterio de Popularidad Actual

Actualmente el criterio utilizado para determinar qué productos son más populares es:

ventasUltimoMes

Cada producto posee esta propiedad dentro del modelo.

Por ejemplo:

{
  id: 1,
  nombre: 'Bolsa reutilizable',
  ventasUltimoMes: 150
}

La lógica ordena los productos desde el valor más alto al más bajo.

Conceptualmente:

Producto A → 35 ventas
Producto B → 120 ventas
Producto C → 75 ventas

después del ordenamiento:

Producto B → 120
Producto C → 75
Producto A → 35

Ordenamiento

El Service utiliza un orden descendente:

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

Conceptualmente:

Productos
    |
    | sort()
    v
Mayor ventasUltimoMes
    |
    v
Menor ventasUltimoMes

De esta forma, los productos con mayor nivel de ventas aparecen primero.


Período de Ventas

Actualmente no se calcula dinámicamente el número de ventas desde un historial de pedidos.

El modelo Producto posee directamente la propiedad:

ventasUltimoMes: number;

Por lo tanto, el criterio actual es:

ventasUltimoMes

y no:

ventas históricas
ventas totales
promedio de puntuación
cantidad de favoritos

Conceptualmente:

Producto
    |
    v
ventasUltimoMes
    |
    v
Criterio de popularidad

En una futura implementación conectada a una base de datos o a un dominio de ventas/pedidos, este valor podrá obtenerse dinámicamente.


Limitación de Resultados

Después de ordenar los productos, el Service utiliza:

slice(0, cantidad)

para seleccionar únicamente la cantidad solicitada.

Por ejemplo, si existen:

1. Producto A
2. Producto B
3. Producto C
4. Producto D
5. Producto E

y Frontend solicita:

GET /productos/mas-vendidos?cantidad=3

el Backend devolverá:

Producto A
Producto B
Producto C

Flujo de la Operación

Conceptualmente:

GET /productos/mas-vendidos?cantidad=5
                |
                v
        ParseIntPipe
                |
                v
      cantidad: number
                |
                v
      ProductosController
                |
                v
       ProductosService
                |
                | sort()
                v
 Ordenar por ventasUltimoMes
                |
                | slice()
                v
     Limitar por cantidad
                |
                | map()
                v
 ProductoMasVendidoDto[]
                |
                v
             200 OK

Procesamiento en ProductosService

La operación principal corresponde a:

obtenerProductosMasVendidos(
  cantidad: number,
): ProductoMasVendidoDto[]

El proceso puede dividirse conceptualmente en tres etapas:

1. Ordenar
     |
     v
productosOrdenados


2. Limitar
     |
     v
productosLimitados


3. Transformar
     |
     v
ProductoMasVendidoDto[]

DTO de Salida

La operación utiliza:

ProductoMasVendidoDto

con la siguiente estructura:

export class ProductoMasVendidoDto {
  id: number;
  marca: string;
  nombre: string;
  precio: number;
  puntuacion: number | null;
  imagenUrl: string;
}

El Backend no devuelve directamente el modelo completo Producto.

Conceptualmente:

Producto
   |
   | seleccionar campos necesarios
   v
ProductoMasVendidoDto
   |
   v
Frontend / Mobile

Puntuación

El DTO incluye:

puntuacion: number | null;

Esto permite representar productos que todavía no poseen una puntuación.

Por ejemplo:

{
  "puntuacion": null
}

continúa siendo una respuesta válida.

La puntuacion se entrega como información visual del producto, pero actualmente no se utiliza para determinar cuáles productos son más vendidos.

El criterio de ordenamiento continúa siendo:

ventasUltimoMes

Ejemplo de Solicitud

GET /productos/mas-vendidos?cantidad=3

Ejemplo de Respuesta

[
  {
    "id": 1,
    "marca": "EcoWear",
    "nombre": "Polera de Algodón Orgánico",
    "precio": 12990,
    "puntuacion": 4.8,
    "imagenUrl": "https://ejemplo.cl/imagenes/productos/polera.jpg"
  },
  {
    "id": 2,
    "marca": "EcoRaíz",
    "nombre": "Bolsa Reutilizable",
    "precio": 5990,
    "puntuacion": 4.5,
    "imagenUrl": "https://ejemplo.cl/imagenes/productos/bolsa.jpg"
  },
  {
    "id": 3,
    "marca": "Verde Hogar",
    "nombre": "Set de Utensilios Sustentables",
    "precio": 8990,
    "puntuacion": null,
    "imagenUrl": "https://ejemplo.cl/imagenes/productos/utensilios.jpg"
  }
]

Respuesta HTTP:

200 OK

Cantidad Mayor al Número de Productos

Si el cliente solicita una cantidad superior al número de productos disponibles:

GET /productos/mas-vendidos?cantidad=100

slice() devolverá todos los productos disponibles sin producir un error.

Por ejemplo:

Productos disponibles: 5
Cantidad solicitada:   100

resultado:

5 productos

Resultado sin Productos

Si el catálogo no contiene productos, la operación devolverá:

[]

con:

200 OK

Respuestas HTTP

Código Situación
200 OK Productos más vendidos obtenidos correctamente
200 OK con [] No existen productos disponibles
400 Bad Request cantidad no puede transformarse correctamente a un número entero

Documentación Swagger

El endpoint se encuentra documentado mediante:

@ApiOperation()
@ApiQuery()
@ApiOkResponse()

El Query Parameter se documenta como:

cantidad

de tipo:

Number

y:

required: true

La respuesta exitosa corresponde a:

ProductoMasVendidoDto[]

Para mayor información consultar:

OpenAPI y Swagger


Regla de Negocio Actual

La regla puede resumirse de la siguiente forma:

Productos
    |
    v
Ordenar por ventasUltimoMes DESC
    |
    v
Tomar los primeros "cantidad"
    |
    v
ProductoMasVendidoDto[]

Por lo tanto, actualmente:

Popularidad = mayor cantidad de ventasUltimoMes

Posibles Mejoras Futuras

Posteriormente podrán evaluarse mejoras como:

  • Validar que cantidad sea mayor que cero.
  • Definir un máximo permitido para cantidad.
  • Calcular ventasUltimoMes desde pedidos reales.
  • Permitir distintos períodos de ventas.
  • Incorporar ventas semanales.
  • Incorporar ventas históricas.
  • Incorporar otros criterios de popularidad.
  • Incorporar paginación.

Estas mejoras deberán agregarse únicamente cuando sean requeridas por las reglas de negocio de BuenOrigen.


Relación con la Página de Inicio

Página de Inicio
      |
      v
Productos más Populares
      |
      v
GET /productos/mas-vendidos
      |
      v
ProductosController
      |
      v
ProductosService
      |
      v
ProductoMasVendidoDto[]
      |
      v
Frontend / Mobile

Relación con la Arquitectura

La funcionalidad pertenece al dominio:

Productos

y utiliza:

ProductosModule
      |
      v
ProductosController
      |
      v
ProductosService
      |
      v
ProductoMasVendidoDto[]

Páginas relacionadas: