Skip to content

[codex] issue 221: admin de catálogo de insumos - #246

Merged
vgpastor merged 11 commits into
GlobalEmergency:mainfrom
sergioballesteros-vd:codex/issue-221-supplies-categories-admin
Jun 30, 2026
Merged

[codex] issue 221: admin de catálogo de insumos#246
vgpastor merged 11 commits into
GlobalEmergency:mainfrom
sergioballesteros-vd:codex/issue-221-supplies-categories-admin

Conversation

@sergioballesteros-vd

Copy link
Copy Markdown
Contributor

Cambios

  • Expone listado público de categorías con label localizado.
  • Añade endpoints admin para crear, actualizar y archivar categorías.
  • Introduce migración para archived_at y soporte de traducciones.

Motivo

  • La fase 2 necesita gestión de catálogo sin duplicar taxonomía por contexto.

Validación

  • pnpm --filter api exec prettier --check ...
  • pnpm --filter api exec eslint ... --max-warnings=0
  • pnpm --filter api exec jest --runInBand --runTestsByPath ... --globalSetup=""

Closes #221

@vercel

vercel Bot commented Jun 29, 2026

Copy link
Copy Markdown

@abiatarprado is attempting to deploy a commit to the GlobalEmergency Team on Vercel.

A member of the Team first needs to authorize it.

@sergioballesteros-vd
sergioballesteros-vd marked this pull request as ready for review June 29, 2026 20:20

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

Revisión centrada en API pública/privada (lo que veníamos hablando) + SOLID/DDD/Clean Code. La dirección es buena y la cobertura de tests está bien; dejo puntos por fichero y un par de cosas del gate antes de mergear.

1) Pública vs. privada (el tema central)

  • Lo bien hecho: el GET /categories público sigue exponiendo solo label/labelEs/labelEn y filtra archivadas; translations[] y archivedAt quedan solo en CategoryAdminDto. Es la separación que buscábamos (en la línea de #245).
  • A corregir: el controller admin comparte @ApiTags('categories') y cuelga de /categories/admin, así que en el /docs (único y público) el CRUD privilegiado queda mezclado con la lectura pública. Propongo tag propio (categories-admin/admin), registrarlo en main.ts, y valorar si esa superficie debe figurar en los docs públicos. Detalle en el comentario del controller.

2) DDD / hexagonal

  • Regla de dominio ("slug núcleo inmutable/no borrable") dispersa entre categories-admin.controller.ts y update-category.ts vía Object.values(Category).includes(...). Centralizar en el dominio de supplies.
  • El adaptador Drizzle re-valida invariantes que ya hace el use-case (slug vacío, existe, padre, propio-padre) → misma regla en dos capas, round-trips duplicados y acoplamiento infra→application. Dejar el adaptador como persistencia "tonta".

3) Clean Code

  • update-category.ts: nextSlug es lógica muerta — todo rename se rechaza siempre, así que slug en el update DTO es engañoso y, de paso, bloquea renombrar subcategorías no-núcleo (contradice #221). Decidir: quitar slug del update o soportar rename de no-núcleo.
  • create-category.ts: relecturas redundantes; que el puerto createCategory devuelva la entidad creada.
  • selectCategoryRows/hydrateCategories: el cartesiano con centinela [null] cuesta leerlo; agrupar traducciones en un Map.

4) Gate antes de mergear (AGENTS.md)

  • ⚠️ pnpm gen:api no ejecutado: el diff no incluye packages/api-client/src/schema.ts ni openapi.json, pese a nuevos endpoints (/categories/admin) y el nuevo label en CategoryDto. Regenerar y commitear.
  • La validación corrió jest con --globalSetup="" (salta migraciones), así que no se probó contra el esquema real (category_translations ya existe en 0037, archived_at lo añade este 0039). Pasar el gate completo: pnpm --filter api build + test con global-setup + eslint --max-warnings=0 + prettier --check.
  • catalogue:manage solo lo recibe platform_admin (vía ALL_PERMISSIONS); org_admin no. Para un catálogo global parece correcto, pero confirmadlo (criterio "sin permiso → 403" cubierto).

Nada de esto es estructural; con el ajuste de tag/ruta, mover la validación a un único sitio y regenerar el cliente, queda redondo.


Generated by Claude Code

Comment thread apps/api/src/contexts/supplies/application/update-category.ts
Comment thread apps/api/src/contexts/supplies/application/create-category.ts

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

Seguimiento: contrastado contra el main actual del repo destino

Importante: esta PR está basada en un main anterior a #245 (refactor(supplies): catálogo público sin fuga de datos + cache y limpieza DDD), y al contrastarla contra el main real de destino aparecen conflictos y solapamientos que cambian el sentido de parte de mi review anterior. #245 ya implementó buena parte de esto:

Lo que la PR "añade" Estado real en main
locale.ts (resolveLocale/localizedText) Ya existe (#245), con otra firma → conflicto
label en CategoryDto Ya existe (#245)
Localización en categories.controller.ts Ya existe (#245)
runCategoryCommand/toHttpError en el controller main ya tiene SuppliesDomainExceptionFilter
archivedAt/translations en CategoryDefinition main lo prohíbe explícitamente (proyección pública)

Lo más relevante para el tema público/privado: en main, category-definition.ts documenta que CategoryDefinition es la proyección PÚBLICA y no debe crecer con datos de gestión interna (lo interno va a la API interna con su propio modelo). Esta PR mete ahí archivedAt + translations, deshaciendo justo la separación que #245 dejó montada.

Recomendación principal: rebase sobre main actual y reconciliar:

  1. Modelo separado para la API interna/admin (read-model/DTO propio), dejando CategoryDefinition como proyección pública mínima. Ideal: separar también el puerto de escritura/archivado del de lectura (ISP) en vez de engordar el único CategoryRepository.
  2. Reutilizar locale.ts y SuppliesDomainExceptionFilter existentes en vez de re-añadirlos / mapear a mano (y alinear códigos HTTP con la convención del contexto: validación → 422).
  3. Decidir un enfoque de i18n sobre el main actual (es/en de #245 vs. category_translations multi-idioma de esta PR), no dos en paralelo.

Corrección a mi review anterior: el comentario donde sugería "factorizar un mapper público compartido" entre el público y el admin queda invalidado — la dirección correcta es la contraria: mantener separados el modelo público (ya en main) y el interno, no unificarlos. El resto de puntos (regla de slug-núcleo dispersa, validación duplicada en el adaptador, lógica muerta del rename, pnpm gen:api) siguen vigentes, pero deben reevaluarse después del rebase porque varios ficheros cambian de base.


Generated by Claude Code

Comment thread apps/api/src/contexts/supplies/domain/category-definition.ts
Comment thread apps/api/src/contexts/supplies/infrastructure/http/locale.ts
@vercel

vercel Bot commented Jun 29, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
response-grid Ready Ready Preview, Comment Jun 30, 2026 4:01am

Request Review

@vgpastor
vgpastor merged commit bfc70b6 into GlobalEmergency:main Jun 30, 2026
4 of 5 checks passed

vgpastor commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

ℹ️ Reimplementado sobre el main actual (post-#245) en #321, aplicando los puntos de esta review (separación pública/interna, modelo interno propio, filtro de dominio global, gen:api regenerado). Esta PR queda superada por #321 — se puede cerrar.


Generated by Claude Code

vgpastor pushed a commit that referenced this pull request Jul 4, 2026
… 404 en admin de categorías (#326)

Salda parte de la deuda técnica de #326 (del merge de #246), sin cambios
funcionales salvo dos correcciones de código HTTP:

- La regla de dominio "no archivar/borrar categoría núcleo" sale del controller
  (que lanzaba BadRequestException) y vive en el caso de uso UpdateCategory como
  CategoryProtectedError, mapeado a 409 por el SuppliesDomainExceptionFilter
  global. El controller admin ya no conoce la regla ni lanza HttpException.
- Fix (regresión latente): PATCH/DELETE de una categoría inexistente devolvía
  500 porque el filtro capturaba el CategoryNotFoundError de domain/supply-errors
  y el caso de uso lanza el de application. Ahora el filtro captura ambos → 404.

Pendiente en #326 (más transversal, toca create-supply/edit-supply): separar
puerto de lectura pública y escritura admin (ISP) y el modelo interno.

Refs #326
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.

[FEATURE] Admin CRUD de categorias y subcategorias (jerarquia, orden, i18n, slugs nucleo protegidos)

2 participants