Skip to content

editar producto / direcciones - #30

Merged
Andoumeda merged 7 commits into
devfrom
OM-175
Mar 16, 2026
Merged

Andoumeda merged 7 commits into
devfrom
OM-175

Conversation

@Lianyang1234

@Lianyang1234 Lianyang1234 commented Mar 13, 2026 •

Copy link
Copy Markdown
Collaborator

-Address para commerce y customer
-Put de Productos
-actualizado logica de crear productos

Summary by CodeRabbit

  • Nuevas funcionalidades

    • APIs CRUD para direcciones de comercios y de usuarios.
    • Endpoint para obtener el perfil de un usuario.
    • Posibilidad de actualizar productos vía nuevas rutas autenticadas.
  • Seguridad

    • Autenticación requerida para operaciones sobre direcciones y para crear/actualizar productos.
  • Chores

    • Reorganización de rutas: direcciones de comercios y product-tags movidos a nuevas bases de ruta.

@coderabbitai

coderabbitai Bot commented Mar 13, 2026 •

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • ✅ Review completed - (🔄 Check again to review again)
📝 Walkthrough

Walkthrough

Se agregan módulos CRUD de direcciones para comercios y usuarios, se exige autenticación en endpoints de productos (crear/actualizar), se refactoriza y extiende el servicio de productos (create/update/search/getById), se exporta la función de autorización de tienda y se ajustan montajes de rutas en src/index.js.

Changes

Cohort / File(s) Summary
Entrada/Enrutamiento principal
src/index.js
Se actualizan imports y montajes de rutas: se importa y monta commerceAddressRoutes en /api/commerces y se mueve product-tags a /api/product-tags.
Direcciones de comercios
src/modules/commerce/addresses/controllers/addresses.controllers.js, src/modules/commerce/addresses/routes/addresses.routes.js, src/modules/commerce/addresses/services/addresses.services.js
Nuevo módulo CRUD para direcciones de tiendas: rutas protegidas con authenticate, controladores con mapeo de errores y servicios con validación, normalización, autorización (getAuthorizedStoreOwnerService) y soft-delete.
Servicio de tiendas (export)
src/modules/commerce/commerces/store.service.js
getAuthorizedStoreOwnerService ahora se exporta públicamente (sin cambios lógicos).
Productos — controller & rutas
src/modules/commerce/products/product.controller.js, src/modules/commerce/products/product.routes.js
createProduct ahora requiere autenticación; se agrega updateProduct; rutas POST y PUT usan authenticate; controladores actualizados para usar req.user.id_user.
Productos — servicio
src/modules/commerce/products/product.service.js
Refactor amplio: validadores/normalizadores, builders para create/update, sincronización transaccional de tags, nuevo updateProductService, getProductByIdService, mejoras en getProductsSearchService y mapeo de respuestas.
Direcciones de usuarios
src/modules/users/addresses/controllers/addresses.controllers.js, src/modules/users/addresses/routes/addresses.routes.js, src/modules/users/addresses/services/addresses.services.js
Se extiende a CRUD completo para direcciones de usuario: autorización (getAuthorizedUserService), validación centralizada (buildAddressData), límite de direcciones activas, soft-delete y rutas protegidas por JWT.
Usuarios — profile
src/modules/users/users/controllers/users.controllers.js, src/modules/users/users/routes/users.routes.js, src/modules/users/users/services/users.services.js
Nuevo endpoint GET /:id_user y servicio getUserProfileService con autorización que valida visualización del perfil; ajustes en mensajes y selección de campos.
Otros cambios menores
package.json
Dependencias/metadatos referenciados por cambios en productos (se registra en diff).

Sequence Diagram(s)

sequenceDiagram
participant Client
participant Router
participant Auth as AuthenticateMiddleware
participant Controller
participant Service
participant DB as Prisma

Client->>Router: POST /:id_store/addresses (body)
Router->>Auth: validar JWT
Auth-->>Router: req.user con id_user
Router->>Controller: createStoreAddress(req)
Controller->>Service: createStoreAddressService(req.user.id_user, id_store, payload)
Service->>DB: verificar tienda y propietario, validar payload, crear dirección (tx)
DB-->>Service: dirección creada
Service-->>Controller: address data
Controller-->>Client: 201 { success: true, data }
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutos

Possibly related PRs

Suggested reviewers

  • CrisNAC
  • SebaKisser

Poem

🐇 Salté entre rutas y servicios esta vez,

Creé puertas y direcciones con gran sencillez,
Autenticación me guía en la madriguera,
Tags y perfiles cosidos en la carrera,
¡Brinco contento: el repo crece con rapidez!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive El título 'editar producto / direcciones' es parcialmente relacionado con los cambios principales del PR. Cubre dos aspectos importantes (productos y direcciones) pero de manera muy genérica y vaga, sin especificar el alcance completo de las modificaciones (rutas de comercio, servicios, controladores, autenticación, etc.). Considerar un título más específico y descriptivo como 'Agregar gestión de direcciones para comercios y usuarios, y endpoint PUT para productos' para mejor claridad sobre los cambios realizados.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
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 unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch OM-175
📝 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: 5

🧹 Nitpick comments (2)
src/modules/commerce/addresses/services/addresses.services.js (1)

19-118: Extraer validaciones compartidas para evitar drift entre módulos de direcciones.

parsePositiveInteger, validateRequiredStringField, validateOptionalStringField y buildAddressData están duplicados respecto a src/modules/users/addresses/services/addresses.services.js (snippets: líneas 21-162). Conviene moverlos a un módulo común de validación para mantener reglas consistentes.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/modules/commerce/addresses/services/addresses.services.js` around lines
19 - 118, The three validators (parsePositiveInteger,
validateRequiredStringField, validateOptionalStringField) and buildAddressData
are duplicated; extract them into a shared validation module (e.g., export these
functions from a new/common file) and replace the copies in this file and
src/modules/users/addresses/services/addresses.services.js with imports from
that module; ensure you export parsePositiveInteger,
validateRequiredStringField, validateOptionalStringField and buildAddressData
from the new module and update any call sites in create/update flows to import
the same symbols so both address service implementations use the single shared
logic.
src/modules/commerce/products/product.controller.js (1)

44-46: Unificar el formato de error en respuestas del controlador.

En el catch de updateProduct falta success: false, mientras en 401 sí se devuelve. Mantener el mismo envelope simplifica consumo del frontend.

♻️ Propuesta de ajuste
     return res.status(error.status || error.statusCode || 500).json({
+      success: false,
       message: error.message || "Error interno del servidor"
     });
🤖 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 44 - 46, En
el catch de la función updateProduct en product.controller.js la respuesta de
error no incluye el campo success: false (mientras que la ruta 401 lo hace), por
lo que debes unificar el envelope: modificar el return que usa
res.status(error.status || error.statusCode || 500).json(...) para que devuelva
{ success: false, message: error.message || "Error interno del servidor" }
(manteniendo las mismas claves que las otras respuestas de error) asegurándote
de usar el mismo formato que en la rama 401.
🤖 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/index.js`:
- Line 38: La ruta registrada cambió de contrato a "/api/product-tags" y puede
romper clientes que usan la ruta previa (p.ej. "/products/tags"); en el archivo
donde se llama a app.use con productTagRoutes, restaura compatibilidad temporal
añadiendo también el antiguo prefijo (por ejemplo registrando productTagRoutes
en "/products/tags") o añade un redirect/alias que reenvíe requests de
"/products/tags" hacia "/api/product-tags" para mantener ambas rutas funcionando
mientras se comunica la migración.

In `@src/modules/commerce/products/product.service.js`:
- Around line 264-285: buildCreateProductData currently accepts
visibility/status from the payload (via parseVisibilityOverride) allowing
clients to override PRODUCT_INITIAL_STATUS; remove or ignore payload-provided
visibility/status in buildCreateProductData and ensure createProductService
always initializes product status/visibility to the configured
PRODUCT_INITIAL_STATUS (or maps visibilityOverride only when caller explicitly
permits admin-level overrides). Concretely, stop calling
parseVisibilityOverride(payload) inside buildCreateProductData (or discard its
result) and have createProductService set status/visible from
PRODUCT_INITIAL_STATUS (and only apply any parsed visibilityOverride behind an
explicit admin check).
- Around line 504-506: Validate and sanitize page and limit before computing
skip: ensure filters.page and filters.limit are finite integers (>0) (use
Number, Number.isFinite and Math.floor or similar), default to 1 and 20
respectively if invalid, and cap limit to a reasonable max (e.g., MAX_LIMIT =
100) to avoid huge queries; then compute skip = (page - 1) * limit using the
sanitized/capped values (update the variables page, limit and skip in
product.service.js where they are defined).
- Around line 562-565: The public getProductByIdService currently returns any
product regardless of visibility; update it so it only exposes visible products
like getProductsSearchService does by either (A) passing a visibility filter
into getProductResponseByIdService (add an options param if needed) or (B) after
awaiting getProductResponseByIdService(productId) check product.visible === true
and if not return null or throw a 404/NotFound so callers (e.g.,
product.controller.js invoking getProductByIdService) cannot read pending/hidden
products; ensure you reference getProductByIdService and
getProductResponseByIdService when making the change.

In `@src/modules/users/addresses/services/addresses.services.js`:
- Around line 201-224: The current count-then-create in prisma.$transaction
(tx.addresses.count then tx.addresses.create) is racey; serialize concurrent
requests by taking a DB lock on the user's row inside the same transaction
before counting. Inside prisma.$transaction, issue a SELECT ... FOR UPDATE on
the users row (via tx.$executeRaw or tx.$executeRawUnsafe) for user.id_user to
acquire a row-level lock, then run tx.addresses.count and conditionally
tx.addresses.create using MAX_USER_ADDRESSES; this ensures the check+insert is
atomic. Alternatively, if you prefer a DB-enforced guarantee, add a
database-level constraint/trigger that prevents more than MAX_USER_ADDRESSES
active addresses per user and handle the constraint error in the create flow.

---

Nitpick comments:
In `@src/modules/commerce/addresses/services/addresses.services.js`:
- Around line 19-118: The three validators (parsePositiveInteger,
validateRequiredStringField, validateOptionalStringField) and buildAddressData
are duplicated; extract them into a shared validation module (e.g., export these
functions from a new/common file) and replace the copies in this file and
src/modules/users/addresses/services/addresses.services.js with imports from
that module; ensure you export parsePositiveInteger,
validateRequiredStringField, validateOptionalStringField and buildAddressData
from the new module and update any call sites in create/update flows to import
the same symbols so both address service implementations use the single shared
logic.

In `@src/modules/commerce/products/product.controller.js`:
- Around line 44-46: En el catch de la función updateProduct en
product.controller.js la respuesta de error no incluye el campo success: false
(mientras que la ruta 401 lo hace), por lo que debes unificar el envelope:
modificar el return que usa res.status(error.status || error.statusCode ||
500).json(...) para que devuelva { success: false, message: error.message ||
"Error interno del servidor" } (manteniendo las mismas claves que las otras
respuestas de error) asegurándote de usar el mismo formato que en la rama 401.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c7e4f290-dc91-4f84-829a-fd1aaf19baf6

📥 Commits

Reviewing files that changed from the base of the PR and between c63694b and 76be9a9.

📒 Files selected for processing (11)
  • src/index.js
  • src/modules/commerce/addresses/controllers/addresses.controllers.js
  • src/modules/commerce/addresses/routes/addresses.routes.js
  • src/modules/commerce/addresses/services/addresses.services.js
  • src/modules/commerce/commerces/store.service.js
  • src/modules/commerce/products/product.controller.js
  • src/modules/commerce/products/product.routes.js
  • src/modules/commerce/products/product.service.js
  • src/modules/users/addresses/controllers/addresses.controllers.js
  • src/modules/users/addresses/routes/addresses.routes.js
  • src/modules/users/addresses/services/addresses.services.js

Comment thread src/index.js Outdated
//Se encuentra indexado
app.use("/api/categories", categoriesRoutes);
app.use("/products/tags", productTagRoutes);
app.use("/api/product-tags", productTagRoutes);

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 | 🟠 Major

Posible cambio breaking de contrato en la ruta de product-tags.

En Line 38 el prefijo quedó en /api/product-tags. Si clientes ya consumen la ruta anterior (p. ej. /products/tags), esto rompe integración sin compatibilidad temporal ni aviso de migración.

💡 Sugerencia para compatibilidad backward temporal
+app.use("/products/tags", productTagRoutes); // compatibilidad transitoria
 app.use("/api/product-tags", productTagRoutes);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/index.js` at line 38, La ruta registrada cambió de contrato a
"/api/product-tags" y puede romper clientes que usan la ruta previa (p.ej.
"/products/tags"); en el archivo donde se llama a app.use con productTagRoutes,
restaura compatibilidad temporal añadiendo también el antiguo prefijo (por
ejemplo registrando productTagRoutes en "/products/tags") o añade un
redirect/alias que reenvíe requests de "/products/tags" hacia
"/api/product-tags" para mantener ambas rutas funcionando mientras se comunica
la migración.

Comment thread src/modules/commerce/products/product.service.js
Comment thread src/modules/commerce/products/product.service.js Outdated
Comment thread src/modules/commerce/products/product.service.js
Comment thread src/modules/users/addresses/services/addresses.services.js

@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: 2

🧹 Nitpick comments (3)
src/modules/commerce/products/product.service.js (2)

365-369: Optimización menor: usar Set para búsqueda de tags.

nextTagIds.includes() es O(n) por cada relación existente. Con muchos tags, esto se vuelve O(n×m). Considerando que existe MAX_TAGS_PER_PRODUCT, el impacto actual es mínimo, pero usar un Set sería más eficiente.

♻️ Propuesta de mejora
 const syncProductTagsService = async (tx, productId, nextTagIds) => {
+  const nextTagIdSet = new Set(nextTagIds);
+
   const existingRelations = await tx.productTagRelations.findMany({
     // ...
   });

   const activeRelationIdsToDisable = existingRelations
     .filter(
-      (relation) => relation.status && !nextTagIds.includes(relation.fk_product_tag)
+      (relation) => relation.status && !nextTagIdSet.has(relation.fk_product_tag)
     )
     .map((relation) => relation.id_product_tag_relation);
🤖 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 365 - 369, The
filter currently calls nextTagIds.includes(...) inside the existingRelations
loop, making lookups O(n) per relation; create a Set from nextTagIds first
(e.g., const nextTagIdSet = new Set(nextTagIds)) and then change the check in
the activeRelationIdsToDisable computation to use
nextTagIdSet.has(relation.fk_product_tag) so lookups become O(1); update
references in the activeRelationIdsToDisable expression (and any other places
that iterate using includes) to use the Set.

55-63: Considerar extraer parsePositiveInteger a un módulo utilitario compartido.

Esta función está duplicada en src/modules/commerce/commerces/store.service.js. Si bien mantener módulos independientes tiene mérito, extraerla a un helper compartido (ej: src/utils/validation.js) reduciría duplicación y facilitaría el mantenimiento.

🤖 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 55 - 63,
Extrae la función parsePositiveInteger a un helper compartido (por ejemplo crear
src/utils/validation.js) y reemplaza las implementaciones duplicadas en
product.service.js y store.service.js por importaciones de ese helper;
específicamente, mover la lógica de parsePositiveInteger (validación de
Number.isInteger y >0 y el throw con status/message) a una función exportada (p.
ej. export const parsePositiveInteger) y actualizar ambos archivos para importar
y usar esa función en lugar de las copias locales.
src/modules/users/users/controllers/users.controllers.js (1)

113-118: Unificá este catch con el resto del controller.

Acá sólo mirás error.status y devolvés message sin fallback, así que este endpoint queda con un contrato de error distinto a registerUser, updateUser y updateUserPassword.

♻️ Ajuste sugerido
-        return res.status(error.status || 500).json({
-            success:false,
-            message:error.message
-        });
+        const statusCode = error.statusCode || error.status || 500;
+        return res.status(statusCode).json({
+            success: false,
+            message: error.message || "Error interno del servidor",
+        });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/modules/users/users/controllers/users.controllers.js` around lines 113 -
118, El catch actual sólo devuelve error.status y error.message sin fallback y
rompe el contrato con los otros endpoints; cambia ese bloque de manejo de
errores para que use el mismo patrón que registerUser, updateUser y
updateUserPassword: calcular status = error.status || 500, message =
error.message || 'Internal server error' (o el fallback textual que usan los
otros controllers) y devolver el mismo JSON shape (por ejemplo { success: false,
message, ... } con los mismos campos opcionales/nulos que usan los otros
handlers) para mantener consistencia con las funciones registerUser, updateUser
y updateUserPassword.
🤖 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/users/addresses/services/addresses.services.js`:
- Around line 295-307: Replace the separate ownership/status check + update with
an atomic conditional write: use prisma.addresses.updateMany({ where: {
id_address: existingAddress.id_address, id_user_owner: user.id_user, status:
true }, data: dataToUpdate }) and then verify the returned count is 1; if count
is 0 throw the same not-found/forbidden error you would from
getOwnedPersonalAddressOrThrow. After a successful updateMany, re-read the
record with prisma.addresses.findUnique({ where: { id_address:
existingAddress.id_address }, select: ADDRESS_SELECT }) to return the
updatedAddress. This ensures ownership and status are validated inside the write
and prevents races.

In `@src/modules/users/users/services/users.services.js`:
- Around line 367-379: Hay un TOCTOU: tras la verificación en
getAuthorizedUserService() vuelves a leer el usuario con
prisma.users.findUnique(...) sin revalidar el status, por lo que si el usuario
cambió a inactivo/ se eliminó entre ambas consultas puedes devolver un perfil
inválido; arreglalo incluyendo la condición de estado en la segunda lectura (por
ejemplo en la cláusula where de prisma.users.findUnique usar id_user:
userProfile.id_user y status: true) y, si la consulta devuelve null, lanzar o
devolver un 404 como lo hace getAuthorizedUserService(); referencia:
getAuthorizedUserService, prisma.users.findUnique y USER_PROFILE_SELECT.

---

Nitpick comments:
In `@src/modules/commerce/products/product.service.js`:
- Around line 365-369: The filter currently calls nextTagIds.includes(...)
inside the existingRelations loop, making lookups O(n) per relation; create a
Set from nextTagIds first (e.g., const nextTagIdSet = new Set(nextTagIds)) and
then change the check in the activeRelationIdsToDisable computation to use
nextTagIdSet.has(relation.fk_product_tag) so lookups become O(1); update
references in the activeRelationIdsToDisable expression (and any other places
that iterate using includes) to use the Set.
- Around line 55-63: Extrae la función parsePositiveInteger a un helper
compartido (por ejemplo crear src/utils/validation.js) y reemplaza las
implementaciones duplicadas en product.service.js y store.service.js por
importaciones de ese helper; específicamente, mover la lógica de
parsePositiveInteger (validación de Number.isInteger y >0 y el throw con
status/message) a una función exportada (p. ej. export const
parsePositiveInteger) y actualizar ambos archivos para importar y usar esa
función en lugar de las copias locales.

In `@src/modules/users/users/controllers/users.controllers.js`:
- Around line 113-118: El catch actual sólo devuelve error.status y
error.message sin fallback y rompe el contrato con los otros endpoints; cambia
ese bloque de manejo de errores para que use el mismo patrón que registerUser,
updateUser y updateUserPassword: calcular status = error.status || 500, message
= error.message || 'Internal server error' (o el fallback textual que usan los
otros controllers) y devolver el mismo JSON shape (por ejemplo { success: false,
message, ... } con los mismos campos opcionales/nulos que usan los otros
handlers) para mantener consistencia con las funciones registerUser, updateUser
y updateUserPassword.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 79ad54cd-aef7-4750-bf58-8f724a55c2a7

📥 Commits

Reviewing files that changed from the base of the PR and between 76be9a9 and 89dda5c.

📒 Files selected for processing (5)
  • src/modules/commerce/products/product.service.js
  • src/modules/users/addresses/services/addresses.services.js
  • src/modules/users/users/controllers/users.controllers.js
  • src/modules/users/users/routes/users.routes.js
  • src/modules/users/users/services/users.services.js

Comment thread src/modules/users/addresses/services/addresses.services.js Outdated
Comment thread src/modules/users/users/services/users.services.js

@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.

♻️ Duplicate comments (1)
src/modules/users/addresses/services/addresses.services.js (1)

342-359: ⚠️ Potential issue | 🟠 Major

Hacé el borrado lógico como write condicional.

Acá quedó el mismo TOCTOU que ya habían corregido en update: primero validás ownership/estado y después hacés update() sólo por id_address. Si otra request borra la dirección entre ambas operaciones, este endpoint igual puede responder 200 sobre un recurso que ya no estaba activo.

💡 Ajuste propuesto
 export const deleteAddressService = async (
     authenticatedUserId,
     requestedUserId,
     requestedAddressId
 ) => {
     const user = await getAuthorizedUserService(
         authenticatedUserId,
         requestedUserId
     );
-    const existingAddress = await getOwnedPersonalAddressOrThrow(
-        user.id_user,
-        requestedAddressId
-    );
-
-    const deletedAddress = await prisma.addresses.update({
-        where: {
-            id_address: existingAddress.id_address,
-        },
-        data: {
-            status: false,
-        },
-        select: {
-            id_address: true,
-            status: true,
-            updated_at: true,
-        },
-    });
+    const addressId = parsePositiveInteger(requestedAddressId, "ID de direccion");
+
+    const deletedAddresses = await prisma.addresses.updateMany({
+        where: {
+            id_address: addressId,
+            fk_user: user.id_user,
+            fk_store: null,
+            status: true,
+        },
+        data: {
+            status: false,
+        },
+    });
+
+    if (deletedAddresses.count !== 1) {
+        throw {
+            status: 404,
+            message: "Direccion no encontrada",
+        };
+    }
+
+    const deletedAddress = await prisma.addresses.findUnique({
+        where: {
+            id_address: addressId,
+        },
+        select: {
+            id_address: true,
+            status: true,
+            updated_at: true,
+        },
+    });
+
+    if (!deletedAddress) {
+        throw {
+            status: 500,
+            message: "No se pudo recuperar la direccion eliminada",
+        };
+    }
 
     return deletedAddress;
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/modules/users/addresses/services/addresses.services.js` around lines 342
- 359, Eliminá el TOCTOU: en vez de validar ownership con
getOwnedPersonalAddressOrThrow y luego llamar prisma.addresses.update sólo por
id_address, hacé la escritura condicional en un único paso (por ejemplo usando
prisma.addresses.updateMany o un update con where que incluya tanto id_address
como status: true y/o id_user) y luego verificá el número de filas afectadas; si
no se actualizó ninguna fila, retorná/lanza el error de recurso no encontrado/ya
inactivo. Esto altera el flujo en la sección que actualmente usa
getOwnedPersonalAddressOrThrow seguido de prisma.addresses.update para que la
actualización sea atómica y detecte si otro request ya inactivó la dirección.
🧹 Nitpick comments (1)
src/modules/users/users/services/users.services.js (1)

101-141: Centralizá este helper de autorización.

La misma validación ya existe en src/modules/users/addresses/services/addresses.services.js. Como es lógica de acceso, mantener dos copias facilita drift en mensajes y checks; conviene extraerla a un módulo compartido y reutilizarla desde ambos servicios.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/modules/users/users/services/users.services.js` around lines 101 - 141,
Extract the duplicated authorization logic from getAuthorizedUserService into a
shared helper (e.g., ensureAuthorizedUser or authorizeUser) that accepts
(authenticatedUserId, requestedUserId), performs parsePositiveInteger checks,
compares IDs, queries prisma.users.findUnique for id_user and status, and throws
the same typed errors; export that helper and replace the body of
getAuthorizedUserService and the equivalent function in the addresses service to
call the shared helper instead of duplicating logic, ensuring error messages and
thrown shapes remain unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/modules/users/addresses/services/addresses.services.js`:
- Around line 342-359: Eliminá el TOCTOU: en vez de validar ownership con
getOwnedPersonalAddressOrThrow y luego llamar prisma.addresses.update sólo por
id_address, hacé la escritura condicional en un único paso (por ejemplo usando
prisma.addresses.updateMany o un update con where que incluya tanto id_address
como status: true y/o id_user) y luego verificá el número de filas afectadas; si
no se actualizó ninguna fila, retorná/lanza el error de recurso no encontrado/ya
inactivo. Esto altera el flujo en la sección que actualmente usa
getOwnedPersonalAddressOrThrow seguido de prisma.addresses.update para que la
actualización sea atómica y detecte si otro request ya inactivó la dirección.

---

Nitpick comments:
In `@src/modules/users/users/services/users.services.js`:
- Around line 101-141: Extract the duplicated authorization logic from
getAuthorizedUserService into a shared helper (e.g., ensureAuthorizedUser or
authorizeUser) that accepts (authenticatedUserId, requestedUserId), performs
parsePositiveInteger checks, compares IDs, queries prisma.users.findUnique for
id_user and status, and throws the same typed errors; export that helper and
replace the body of getAuthorizedUserService and the equivalent function in the
addresses service to call the shared helper instead of duplicating logic,
ensuring error messages and thrown shapes remain unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 361bbe48-242c-4ff1-9983-1b5d9312577f

📥 Commits

Reviewing files that changed from the base of the PR and between 89dda5c and 3eb5ef1.

📒 Files selected for processing (2)
  • src/modules/users/addresses/services/addresses.services.js
  • src/modules/users/users/services/users.services.js

Comment thread src/modules/commerce/products/product.service.js

@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.

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

55-63: Considerar extraer parsePositiveInteger a un módulo compartido.

Esta función está duplicada en múltiples archivos (store.service.js, users.services.js). Extraerla a un módulo de utilidades compartidas (src/utils/validators.js o similar) reduciría la duplicación y facilitaría el mantenimiento.

🤖 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 55 - 63,
Extract the duplicated function parsePositiveInteger into a shared validators
module (e.g., create a validators.js exporting parsePositiveInteger) and replace
the local implementations in product.service.js, store.service.js and
users.services.js with imports from that module; ensure the new exported
function preserves the same signature and error shape ({ status: 400, message:
`${fieldName} invalido` }) and update all call sites to import {
parsePositiveInteger } from the new validators module.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/modules/commerce/products/product.service.js`:
- Around line 55-63: Extract the duplicated function parsePositiveInteger into a
shared validators module (e.g., create a validators.js exporting
parsePositiveInteger) and replace the local implementations in
product.service.js, store.service.js and users.services.js with imports from
that module; ensure the new exported function preserves the same signature and
error shape ({ status: 400, message: `${fieldName} invalido` }) and update all
call sites to import { parsePositiveInteger } from the new validators module.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c825acb9-0496-4090-a2aa-c5863d589c97

📥 Commits

Reviewing files that changed from the base of the PR and between 3eb5ef1 and d6374eb.

📒 Files selected for processing (2)
  • src/modules/commerce/commerces/store.service.js
  • src/modules/commerce/products/product.service.js

@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 (1)
src/modules/commerce/products/product.service.js (1)

75-90: Considerar validación de tipo antes de toString().

Si value es un objeto (ej. {foo: "bar"}), toString() retorna "[object Object]" que pasaría la validación. Aunque es un edge case poco probable, podría agregar una verificación de tipo.

♻️ Propuesta de ajuste
 const validateRequiredStringField = (value, fieldName, maxLength = null) => {
+  if (value !== null && value !== undefined && typeof value !== "string" && typeof value !== "number") {
+    throw { status: 400, message: `${fieldName} debe ser texto` };
+  }
+
   const normalizedValue = value?.toString().trim();
🤖 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 75 - 90,
validateRequiredStringField currently calls value?.toString() which allows
objects (e.g. {foo:"bar"}) to become "[object Object]" and pass validation;
update the function to first check the value's type and reject
non-primitive/stringable types: ensure value is a string or number (or a String
object) before calling toString(), otherwise throw the same 400 error for
invalid type; then proceed to normalize (toString().trim()), apply the maxLength
check and return the normalized value. Reference: validateRequiredStringField.
🤖 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.service.js`:
- Around line 551-558: El campo name del modelo Products debe marcarse con
`@db.FullText` porque la ordenación por _relevance (usada donde se construye
orderBy con fields: ["name"]) requiere campos FullText; actualiza la definición
del campo name en el esquema Prisma (modelo Products) para incluir `@db.FullText`
junto a el tipo y longitud actuales, luego genera y aplica una migración de
Prisma (p. ej. prisma migrate dev / prisma migrate deploy según el entorno) para
que el cambio se refleje en la base de datos.

---

Nitpick comments:
In `@src/modules/commerce/products/product.service.js`:
- Around line 75-90: validateRequiredStringField currently calls
value?.toString() which allows objects (e.g. {foo:"bar"}) to become "[object
Object]" and pass validation; update the function to first check the value's
type and reject non-primitive/stringable types: ensure value is a string or
number (or a String object) before calling toString(), otherwise throw the same
400 error for invalid type; then proceed to normalize (toString().trim()), apply
the maxLength check and return the normalized value. Reference:
validateRequiredStringField.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 49311e6e-90ff-4dd9-99d1-f087d352f016

📥 Commits

Reviewing files that changed from the base of the PR and between d6374eb and b07484b.

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

Comment on lines 551 to 558
orderBy = {
_relevance: {
fields: ['name'],
fields: ["name"],
search: search,
sort: 'desc',
},
}
}

const [totalProducts, products] = await Promise.all(
[prisma.products.count({where}), // se calcula el total de productos que cumplen el filtro
prisma.products.findMany({ // se trae los productos segun el filtro
where,
skip,
take: limit,
orderBy,
select: {
id_product: true,
name: true,
description: true,
price: true,
store: {
select: {
id_store: true,
name: true,
}
sort: "desc"
}
}
})])
return {
products,
pagination: {
totalProducts,
page,
limit,
totalPages: Math.ceil(totalProducts/limit)
}};
};
export const getProductByIdService = async (id)=>{

const productId = Number(id);

if(!Number.isInteger(productId) || productId <= 0){

throw {
status:400,
message:"ID de producto inválido"
};

}

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 | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verificar configuración de Prisma para full-text search
cat prisma/schema.prisma | grep -A5 "generator\|datasource\|fullTextSearch\|fullTextIndex"

Repository: CrisNAC/BackendMarketplace

Length of output: 435


🏁 Script executed:

sed -n '540,570p' src/modules/commerce/products/product.service.js

Repository: CrisNAC/BackendMarketplace

Length of output: 891


🏁 Script executed:

rg -A 10 -B 10 "model Product" prisma/schema.prisma

Repository: CrisNAC/BackendMarketplace

Length of output: 4287


🏁 Script executed:

rg "@db\.FullText" prisma/

Repository: CrisNAC/BackendMarketplace

Length of output: 52


🏁 Script executed:

find prisma/migrations -type f -name "*.sql" | head -20 | xargs grep -l "tsvector\|fulltext\|GIN" 2>/dev/null || echo "No migration files with full-text found"

Repository: CrisNAC/BackendMarketplace

Length of output: 109


El campo name debe estar marcado con @db.FullText en el esquema Prisma.

El ordenamiento por _relevance requiere que los campos buscados tengan el atributo @db.FullText. Aunque fullTextSearchPostgres está habilitado en la configuración de Prisma y PostgreSQL está configurado como proveedor, el campo name en el modelo Products no tiene este atributo. Sin él, las consultas con _relevance fallarán en runtime. Es necesario:

  1. Agregar @db.FullText al campo name: name String @db.VarChar(100) @db.FullText``
  2. Crear una migración para aplicar el cambio a la base de datos
🤖 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 551 - 558, El
campo name del modelo Products debe marcarse con `@db.FullText` porque la
ordenación por _relevance (usada donde se construye orderBy con fields:
["name"]) requiere campos FullText; actualiza la definición del campo name en el
esquema Prisma (modelo Products) para incluir `@db.FullText` junto a el tipo y
longitud actuales, luego genera y aplica una migración de Prisma (p. ej. prisma
migrate dev / prisma migrate deploy según el entorno) para que el cambio se
refleje en la base de datos.

@Andoumeda
Andoumeda dismissed their stale review March 16, 2026 14:37

Ya se solucionaron y mergearon los conflictos anteriores

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