Skip to content

Implement logical deletion of category by ID - #102

Merged
Benjakr04 merged 2 commits into
devfrom
OM-346-deleteCategoryById
Apr 15, 2026
Merged

Benjakr04 merged 2 commits into
devfrom
OM-346-deleteCategoryById

Conversation

@CrisNAC

@CrisNAC CrisNAC commented Apr 15, 2026 •

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

Nuevas Características

  • Se agregó la capacidad de eliminar categorías de productos en el panel administrativo. Los usuarios con permisos de administrador pueden eliminar categorías a través de un nuevo endpoint protegido. Durante la eliminación, los productos asociados se reasignan automáticamente a una categoría predeterminada, y se incluyen validaciones de seguridad apropiadas.

@coderabbitai

coderabbitai Bot commented Apr 15, 2026 •

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Descripción General

Se añade un nuevo endpoint DELETE para eliminar categorías de productos en el módulo de administración. La funcionalidad realiza un soft-delete estableciendo el estado a falso y reasigna los productos relacionados a la categoría predeterminada, con validaciones para evitar la eliminación de categorías protegidas.

Cambios

Cohort / Archivo(s) Resumen
Controlador y Rutas
src/modules/admin/categories/admin-category.controller.js, src/modules/admin/categories/admin-category.routes.js
Se añade el controlador deleteAdminProductCategory que valida el parámetro id y maneja la lógica de eliminación. Se define la ruta DELETE protegida por autenticación y rol ADMIN, con documentación OpenAPI incluida.
Servicio
src/modules/admin/categories/admin-category.service.js
Se implementa deleteAdminProductCategoryService(id) que valida que id !== 1, verifica existencia de la categoría, y ejecuta transaccionalmente: reasigna productos a categoría 1 y marca la categoría como inactiva (soft-delete).
Tests
tests/unit/admin/admin-category.test.js
Se añade suite de pruebas para el endpoint DELETE cubriendo: validaciones de autenticación y roles, validación de id, protección de categoría por defecto, categoría inexistente, eliminación exitosa con verificación de transacción.

Esfuerzo Estimado de Revisión

🎯 3 (Moderado) | ⏱️ ~25 minutos

PRs Posiblemente Relacionados

Revisores Sugeridos

  • Benjakr04
  • Andoumeda

Poema

🐰 Hoppy hop, las categorías se van,
Con soft-delete elegante, sin sufrir más dolor,
Los productos a casa número uno volverán,
Transacciones seguras guardan nuestro valor,
¡Validaciones firmes protegen lo especial! ✨

🚥 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 acertadamente la funcionalidad principal agregada: la eliminación lógica de categorías por ID, que es el cambio central en todos los archivos modificados.
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 OM-346-deleteCategoryById

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@CrisNAC

CrisNAC commented Apr 15, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Apr 15, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tests/unit/admin/admin-category.test.js (1)

7-11: ⚠️ Potential issue | 🟡 Minor

Consolidar los dos vi.mock del módulo Prisma en una única definición.

El módulo ../../../src/lib/prisma.js está siendo mockeado en dos ocasiones (líneas 7-11 y 38-44). Solo la última definición será efectiva, lo que genera ambigüedad sobre qué métodos están realmente mockeados y complica el mantenimiento de los tests.

Ubicaciones afectadas
  • Líneas 7-11: Primer mock (solo findUnique)
  • Líneas 38-44: Segundo mock (findUnique y update)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/unit/admin/admin-category.test.js` around lines 7 - 11, Hay dos mocks
separados de the Prisma module causing only the latter to take effect;
consolidate into a single vi.mock for "../../../src/lib/prisma.js" that exports
prisma.productCategories with both mocked methods used in tests
(productCategories.findUnique and productCategories.update) so both are defined
as vi.fn() and can be configured per-test; update any test setups that call
vi.mock individually to use this single mock and adjust expect/restore usage
accordingly.
🧹 Nitpick comments (3)
src/modules/admin/categories/admin-category.service.js (2)

40-49: Evitá el “magic number” 1 y centralizá la categoría fallback.

El ID de fallback está hardcodeado en varios puntos; conviene centralizarlo para evitar desalineaciones futuras.

♻️ Refactor sugerido
+const DEFAULT_CATEGORY_ID = 1;

 export const deleteAdminProductCategoryService = async (id) => {
-  if (id === 1) {
+  if (id === DEFAULT_CATEGORY_ID) {
     throw new ValidationError("No se puede eliminar la categoría Sin categoría");
   }

   await prisma.$transaction([
     prisma.products.updateMany({
       where: { fk_product_category: id },
-      data: { fk_product_category: 1 }
+      data: { fk_product_category: DEFAULT_CATEGORY_ID }
     }),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/modules/admin/categories/admin-category.service.js` around lines 40 - 49,
Se está usando el valor literal 1 como categoría fallback en
prisma.products.updateMany (campo fk_product_category) lo cual es un "magic
number"; reemplazá ese hardcode por una referencia centralizada (por ejemplo una
constante DEFAULT_FALLBACK_CATEGORY_ID en un archivo de configuración o helper)
o, mejor, resolvé dinámicamente la categoría fallback (p. ej. buscar la
categoría por slug/nombre único) y usá ese id en prisma.products.updateMany y en
cualquier otro lugar que dependa del fallback; actualizá las referencias a
prisma.products.updateMany and prisma.productCategories.update para consumir esa
única fuente de verdad en lugar del 1 literal.

2-2: Usá ConflictError para la categoría protegida (409), no ValidationError (400).

Acá no falla el formato del input; falla una regla de negocio de estado/conflicto. Mantener 400 confunde a clientes que diferencian validación vs conflicto operativo.

💡 Propuesta de ajuste
-import { NotFoundError, ValidationError  } from "../../../lib/errors.js";
+import { NotFoundError, ConflictError } from "../../../lib/errors.js";

 export const deleteAdminProductCategoryService = async (id) => {
   if (id === 1) {
-    throw new ValidationError("No se puede eliminar la categoría Sin categoría");
+    throw new ConflictError("No se puede eliminar la categoría Sin categoría");
   }

Also applies to: 30-32

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

In `@src/modules/admin/categories/admin-category.service.js` at line 2, La
importación y los lanzamientos usan ValidationError para el caso de categoría
protegida pero deben usar ConflictError (HTTP 409) porque es una regla de
negocio, no un fallo de formato; en admin-category.service.js reemplazá
ValidationError por ConflictError en la lista de imports (junto a NotFoundError)
y cambiá las instancias donde se arroja ValidationError para la categoría
protegida por ConflictError (revisá también las otras ocurrencias en el archivo
que actualmente usan ValidationError, p. ej. las ubicadas cerca de las
comprobaciones de categoría protegida alrededor de las referencias a estas
funciones/métodos).
tests/unit/admin/admin-category.test.js (1)

214-224: El test de reasignación debería validar también los argumentos de update, no sólo $transaction.

Con la aserción actual puede pasar aunque cambie incorrectamente el payload de reasignación/soft-delete.

✅ Aserciones adicionales sugeridas
   it("reasigna los productos a la categoría 1 al eliminar", async () => {
     prisma.productCategories.findUnique.mockResolvedValue(mockCategory);
     prisma.$transaction.mockImplementation(async (ops) => ops);

     await asRole(
       request(app).delete("/api/admin/categories/2"),
       "admin"
     );

     expect(prisma.$transaction).toHaveBeenCalled();
+    expect(prisma.products.updateMany).toHaveBeenCalledWith({
+      where: { fk_product_category: 2 },
+      data: { fk_product_category: 1 }
+    });
+    expect(prisma.productCategories.update).toHaveBeenCalledWith({
+      where: { id_product_category: 2 },
+      data: { status: false }
+    });
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/unit/admin/admin-category.test.js` around lines 214 - 224, The test
"reasigna los productos a la categoría 1 al eliminar" currently only asserts
prisma.$transaction was invoked; change the
prisma.$transaction.mockImplementation to capture the ops array (e.g., const
capturedOps = ops) and assert the transaction contains the expected update
operations: one operation that updates products (prisma.product.updateMany or
equivalent) with where matching categoryId: 2 and data that sets categoryId: 1,
and the other operation that soft-deletes the category
(prisma.productCategories.update or equivalent) with where matching id: 2 and
data setting the deleted/active flag; verify the exact argument objects passed
to those operations rather than just that $transaction was called.
🤖 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/admin/categories/admin-category.routes.js`:
- Around line 44-65: Update the Swagger block for the DELETE
/api/admin/categories/{id} operation to include 401 and 403 responses and
clarify that this is a logical deletion (soft-delete/deactivate) rather than a
hard delete; specifically, in the JSDoc swagger for the DELETE route in
admin-category.routes.js, add responses for 401 (Unauthorized - missing/invalid
credentials) and 403 (Forbidden - requires admin role) and change the
204/description and possibly other descriptions to indicate the category is
"desactivada" or "eliminación lógica" to avoid implying a permanent hard delete.

---

Outside diff comments:
In `@tests/unit/admin/admin-category.test.js`:
- Around line 7-11: Hay dos mocks separados de the Prisma module causing only
the latter to take effect; consolidate into a single vi.mock for
"../../../src/lib/prisma.js" that exports prisma.productCategories with both
mocked methods used in tests (productCategories.findUnique and
productCategories.update) so both are defined as vi.fn() and can be configured
per-test; update any test setups that call vi.mock individually to use this
single mock and adjust expect/restore usage accordingly.

---

Nitpick comments:
In `@src/modules/admin/categories/admin-category.service.js`:
- Around line 40-49: Se está usando el valor literal 1 como categoría fallback
en prisma.products.updateMany (campo fk_product_category) lo cual es un "magic
number"; reemplazá ese hardcode por una referencia centralizada (por ejemplo una
constante DEFAULT_FALLBACK_CATEGORY_ID en un archivo de configuración o helper)
o, mejor, resolvé dinámicamente la categoría fallback (p. ej. buscar la
categoría por slug/nombre único) y usá ese id en prisma.products.updateMany y en
cualquier otro lugar que dependa del fallback; actualizá las referencias a
prisma.products.updateMany and prisma.productCategories.update para consumir esa
única fuente de verdad en lugar del 1 literal.
- Line 2: La importación y los lanzamientos usan ValidationError para el caso de
categoría protegida pero deben usar ConflictError (HTTP 409) porque es una regla
de negocio, no un fallo de formato; en admin-category.service.js reemplazá
ValidationError por ConflictError en la lista de imports (junto a NotFoundError)
y cambiá las instancias donde se arroja ValidationError para la categoría
protegida por ConflictError (revisá también las otras ocurrencias en el archivo
que actualmente usan ValidationError, p. ej. las ubicadas cerca de las
comprobaciones de categoría protegida alrededor de las referencias a estas
funciones/métodos).

In `@tests/unit/admin/admin-category.test.js`:
- Around line 214-224: The test "reasigna los productos a la categoría 1 al
eliminar" currently only asserts prisma.$transaction was invoked; change the
prisma.$transaction.mockImplementation to capture the ops array (e.g., const
capturedOps = ops) and assert the transaction contains the expected update
operations: one operation that updates products (prisma.product.updateMany or
equivalent) with where matching categoryId: 2 and data that sets categoryId: 1,
and the other operation that soft-deletes the category
(prisma.productCategories.update or equivalent) with where matching id: 2 and
data setting the deleted/active flag; verify the exact argument objects passed
to those operations rather than just that $transaction was called.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ee5cfc19-50ca-40e1-ab55-fa64f4715a59

📥 Commits

Reviewing files that changed from the base of the PR and between 2186eaf and 99f7127.

📒 Files selected for processing (4)
  • src/modules/admin/categories/admin-category.controller.js
  • src/modules/admin/categories/admin-category.routes.js
  • src/modules/admin/categories/admin-category.service.js
  • tests/unit/admin/admin-category.test.js

Comment on lines +44 to +65
/**
* @swagger
* /api/admin/categories/{id}:
* delete:
* summary: Borrar categoría de productos (Admin)
* tags: [Admin]
* security:
* - cookieAuth: []
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: integer
* responses:
* 204:
* description: Categoría eliminada correctamente
* 400:
* description: ID inválido o categoría protegida
* 404:
* description: Categoría no encontrada
*/

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

La documentación Swagger del DELETE está incompleta para una ruta protegida.

Faltan 401 y 403 en responses, aunque la ruta exige autenticación/rol admin (Line 66). Además, como la operación es lógica, conviene describirla como “desactivar”/“eliminación lógica” para no sugerir hard delete.

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

In `@src/modules/admin/categories/admin-category.routes.js` around lines 44 - 65,
Update the Swagger block for the DELETE /api/admin/categories/{id} operation to
include 401 and 403 responses and clarify that this is a logical deletion
(soft-delete/deactivate) rather than a hard delete; specifically, in the JSDoc
swagger for the DELETE route in admin-category.routes.js, add responses for 401
(Unauthorized - missing/invalid credentials) and 403 (Forbidden - requires admin
role) and change the 204/description and possibly other descriptions to indicate
the category is "desactivada" or "eliminación lógica" to avoid implying a
permanent hard delete.

@Benjakr04
Benjakr04 merged commit b49c0eb into dev Apr 15, 2026
2 checks passed
@Andoumeda
Andoumeda deleted the OM-346-deleteCategoryById branch April 23, 2026 17:03
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