Skip to content

agrega funcionalidad para comparar precios de productos similares - #41

Closed
elianasmz wants to merge 1 commit into
devfrom
mejora-en-busqueda
Closed

elianasmz wants to merge 1 commit into
devfrom
mejora-en-busqueda

Conversation

@elianasmz

@elianasmz elianasmz commented Mar 18, 2026 •

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

Notas de la Versión

  • Nuevas Funcionalidades

    • Se agregó la capacidad de comparar productos mediante búsqueda, mostrando precios y opciones de tiendas disponibles.
  • Mejoras

    • Se optimizó el procesamiento de búsquedas con sanitización mejorada de términos para resultados más precisos y relevantes.
  • Cambios Removidos

    • Se eliminó el endpoint de eliminación de productos.

@coderabbitai

coderabbitai Bot commented Mar 18, 2026 •

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Se eliminó el endpoint de eliminación de productos y su servicio asociado, reemplazándolos por un nuevo endpoint de comparación que reutiliza el servicio de búsqueda con límites expandidos. El servicio de búsqueda se mejoró con sanitización de términos y ordenamiento condicional de relevancia.

Changes

Cohort / Archivo(s) Resumen
Controlador de productos
src/modules/commerce/products/product.controller.js
Se eliminó el método deleteProduct, se agregó compareProducts que realiza búsquedas con límites expandidos y formatea datos de precio y tienda. Se actualizaron los imports removiendo deleteProductService.
Rutas de productos
src/modules/commerce/products/product.routes.js
Se removió la ruta DELETE /:id que invocaba deleteProduct. Se agregó la ruta GET /compare/search que utiliza el nuevo método compareProducts. Se actualizó el import del controlador.
Servicio de productos
src/modules/commerce/products/product.service.js
Se eliminó la función deleteProductService. Se mejoró getProductsSearchService con sanitización de términos de búsqueda mediante tsquery y ordenamiento condicional por relevancia según si existe búsqueda sanitizada. Se removió el import de ForbiddenError.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutos

Possibly related PRs

  • OM-208(productos): agregar eliminación de producto con soft delete #39: Directamente opuesta a este PR, ya que agrega deleteProductService, el controlador deleteProduct y la ruta DELETE — cambios que aquí se eliminan.
  • OM-89 #34: Modifica getProductsSearchService en el mismo archivo, extendiendo el mapeo de respuesta con campos de tienda, comercio y calificación, mientras que este PR agrega sanitización y ordenamiento condicional.
  • OM-77 #19: Relacionado con funcionalidad de búsqueda de productos; ese PR agregó getProductsSearchService original, y este PR lo modifica y crea un endpoint que lo reutiliza.

Suggested reviewers

  • leoAchu16
  • CrisNAC

Poem

🐰 Una gasa de búsqueda, limpia y ordenada,
Comparar productos, sin borrar la posada,
Relevancia que danza si hay término a buscar,
Nuevas rutas florecen, las viejas a dejar. ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed El título describe correctamente la funcionalidad principal añadida: un nuevo endpoint que compara productos similares, aunque no menciona explícitamente que se removió el endpoint de eliminación.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch mejora-en-busqueda
📝 Coding Plan
  • Generate coding plan for human review comments

Comment @coderabbitai help to get the list of available commands and usage tips.

Tip

You can customize the high-level summary generated by CodeRabbit.

Configure the reviews.high_level_summary_instructions setting to provide custom instructions for generating the high-level summary.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/modules/commerce/products/product.controller.js (1)

93-101: El endpoint de comparación debería validar que se proporcione un término de búsqueda o categoría.

Actualmente, si se llama a /compare/search sin search ni categoryId, el endpoint retorna los primeros 50 productos ordenados por ID, lo cual no tiene sentido semántico para una funcionalidad de "comparar productos similares".

✨ Sugerencia para validar parámetros requeridos
 export const compareProducts = async (request, response) => {
   try {
     const { search, categoryId } = request.query;

+    if (!search?.trim() && !categoryId) {
+      return response.status(400).json({
+        message: "Se requiere al menos un término de búsqueda o categoría para comparar productos"
+      });
+    }
+
     // Reutiliza la búsqueda actual, pero con un límite mayor
     const filters = {
       search,
       categoryId,
       page: 1,
       limit: 50
     };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/modules/commerce/products/product.controller.js` around lines 93 - 101,
El endpoint de comparación construye los filtros (variable filters) a partir de
request.query pero no valida que exista al menos search o categoryId; agrega una
comprobación al inicio del handler del endpoint /compare/search (antes de crear
filters) que si ni request.query.search ni request.query.categoryId están
presentes responde con un error 400 y un mensaje claro (por ejemplo "search or
categoryId required") y no procede a consultar productos; deja intacta la
creación de filters y la lógica posterior cuando la validación pasa.
src/modules/commerce/products/product.service.js (1)

570-588: La sanitización de búsqueda puede generar safeSearch vacío que aún activa el ordenamiento por relevancia.

Cuando el término de búsqueda contiene solo caracteres reservados (por ejemplo ":::" o "&&&"), la sanitización los reemplaza por espacios, y después del trim() y split(/\s+/), safeSearch resulta en "" (string vacío). Sin embargo, el string vacío es falsy, por lo que el ternario en la línea 580 caería correctamente al orderBy por defecto.

No obstante, existe un edge case: si search es " & " (espacios con un &), después de .replace(/[':()&|!]/g, " ") queda " ", luego .trim() da "", y .split(/\s+/) sobre string vacío da [""], y .join(" & ") da "". Esto es correcto.

Sin embargo, recomendaría validar explícitamente que safeSearch no esté vacío antes de usarlo para mayor claridad y evitar posibles errores en futuras versiones de Prisma con _relevance.

♻️ Sugerencia para mayor robustez
     const safeSearch = search
       // reemplazar caracteres reservados de tsquery por espacios
       .replace(/[':()&|!]/g, " ")
       .trim()
       // convertir espacios múltiples en operador AND
       .split(/\s+/)
+      .filter(Boolean)
       .join(" & ");

     // cambia el orden, solo si se busca o filtra por algun parametro
-    orderBy = safeSearch
+    orderBy = safeSearch.length > 0
       ? {
           _relevance: {
             fields: ["name"],
             search: safeSearch,
             sort: "desc"
           }
         }
       : orderBy;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/modules/commerce/products/product.service.js` around lines 570 - 588, The
current sanitization chain (the safeSearch variable created via
.replace(/[':()&|!]/g, " ").trim().split(/\s+/).join(" & ")) can produce an
empty string that still risks being used for Prisma's _relevance ordering;
update the logic so you explicitly validate safeSearch is non-empty before
assigning orderBy: compute safeSearch as you do, then check e.g. that
safeSearch.trim().length > 0 (or Boolean(safeSearch)) and only then set orderBy
= { _relevance: { fields: ["name"], search: safeSearch, sort: "desc" } },
otherwise leave orderBy unchanged; reference safeSearch and orderBy/_relevance
to locate and change the code.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/modules/commerce/products/product.controller.js`:
- Around line 91-141: The compareProducts handler is returning the baseProduct
twice because offers is built from all products including the first
(baseProduct); update the offers construction in compareProducts so it excludes
the base product (e.g., filter products where p.id_product !==
baseProduct.id_product or use products.slice(1) before mapping) and then map the
remaining items to the existing offer shape; keep baseProduct as the separate
product field and ensure pagination/response unchanged.

---

Nitpick comments:
In `@src/modules/commerce/products/product.controller.js`:
- Around line 93-101: El endpoint de comparación construye los filtros (variable
filters) a partir de request.query pero no valida que exista al menos search o
categoryId; agrega una comprobación al inicio del handler del endpoint
/compare/search (antes de crear filters) que si ni request.query.search ni
request.query.categoryId están presentes responde con un error 400 y un mensaje
claro (por ejemplo "search or categoryId required") y no procede a consultar
productos; deja intacta la creación de filters y la lógica posterior cuando la
validación pasa.

In `@src/modules/commerce/products/product.service.js`:
- Around line 570-588: The current sanitization chain (the safeSearch variable
created via .replace(/[':()&|!]/g, " ").trim().split(/\s+/).join(" & ")) can
produce an empty string that still risks being used for Prisma's _relevance
ordering; update the logic so you explicitly validate safeSearch is non-empty
before assigning orderBy: compute safeSearch as you do, then check e.g. that
safeSearch.trim().length > 0 (or Boolean(safeSearch)) and only then set orderBy
= { _relevance: { fields: ["name"], search: safeSearch, sort: "desc" } },
otherwise leave orderBy unchanged; reference safeSearch and orderBy/_relevance
to locate and change the code.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fb5d0780-9096-45b6-9bcc-00df4dae9f42

📥 Commits

Reviewing files that changed from the base of the PR and between 58bd0b4 and 6f2f453.

📒 Files selected for processing (3)
  • src/modules/commerce/products/product.controller.js
  • src/modules/commerce/products/product.routes.js
  • src/modules/commerce/products/product.service.js

Comment on lines +91 to +141
export const compareProducts = async (request, response) => {
try {
const { search, categoryId } = request.query;

// Reutiliza la búsqueda actual, pero con un límite mayor
const filters = {
search,
categoryId,
page: 1,
limit: 50
};

const result = await getProductsSearchService(filters);
const products = Array.isArray(result?.products) ? result.products : [];

if (!products.length) {
return response.status(200).json({
product: null,
offers: [],
pagination: result?.pagination || null
});
}

// Producto base: el primero de la lista (más relevante según tu servicio)
const baseProduct = products[0];

const offers = products.map((p) => ({
productId: p.id_product,
name: p.name,
description: p.description,
price: Number(p.price),
store: p.store
? {
id: p.store.id_store,
name: p.store.name
}
: null
}));

return response.status(200).json({
product: baseProduct,
offers,
pagination: result.pagination
});
} catch (error) {
console.error("Error al comparar productos:", error);
return response
.status(error.status || 500)
.json({ message: error.message || "Error interno del servidor." });
}
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

El endpoint compareProducts incluye el producto base duplicado en el array de ofertas.

El producto base (baseProduct) es el primer elemento de la lista, y luego el array offers incluye todos los productos, incluyendo ese mismo producto base. Esto resulta en datos duplicados en la respuesta.

Si la intención es mostrar el producto de referencia separado de las demás ofertas, deberías excluirlo del array offers:

🐛 Propuesta para evitar duplicación
     // Producto base: el primero de la lista (más relevante según tu servicio)
     const baseProduct = products[0];

-    const offers = products.map((p) => ({
+    const offers = products.slice(1).map((p) => ({
       productId: p.id_product,
       name: p.name,
       description: p.description,
       price: Number(p.price),
       store: p.store
         ? {
             id: p.store.id_store,
             name: p.store.name
           }
         : null
     }));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/modules/commerce/products/product.controller.js` around lines 91 - 141,
The compareProducts handler is returning the baseProduct twice because offers is
built from all products including the first (baseProduct); update the offers
construction in compareProducts so it excludes the base product (e.g., filter
products where p.id_product !== baseProduct.id_product or use products.slice(1)
before mapping) and then map the remaining items to the existing offer shape;
keep baseProduct as the separate product field and ensure pagination/response
unchanged.

@CrisNAC CrisNAC closed this Mar 18, 2026
@Andoumeda
Andoumeda deleted the mejora-en-busqueda branch March 26, 2026 18:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants