Skip to content

[TASK] Deuda técnica del admin de categorías (#246): separar proyección pública/interna, ISP en el puerto y regla de dominio fuera del controller #326

Description

@vgpastor

Parte de la épica #228. Deuda detectada al revisar #246 (que cerró #221 y quedó mergeada en main). No bloquea; refactor de calidad (DDD/SOLID) sobre código ya en producción.

Contexto

El CRUD admin de categorías entró vía #246. La review dejó varios puntos de DDD/Clean Code que se mergearon sin resolver. Este issue los agrupa para saldarlos en un refactor acotado, sin cambiar comportamiento observable (salvo el punto 1, que es una decisión de exposición pública).

Puntos

1. La proyección pública filtra campos internos

CategoryDefinition (que sirve GET /categories público) carga kind, archivedAt y codePrefix, y el DTO público CategoryDto expone kind. Contradice el contrato documentado en category-definition.ts (introducido en #245): "proyección PÚBLICA… no debe crecer con datos de gestión interna".

  • Propuesta: modelo interno propio (p. ej. CategoryRecord con archivedAt/kind/codePrefix) para la API admin, dejando CategoryDefinition como proyección pública mínima. Decidir explícitamente si kind debe ser público (si sí, documentarlo en el contrato; si no, quitarlo del GET /categories).
  • Archivos: supplies/domain/category-definition.ts, infrastructure/http/category-response.dto.ts, categories.controller.ts.

2. El puerto CategoryRepository no respeta ISP

Un único CategoryRepository mezcla lectura pública (listCategories) con escritura/gestión admin (findBySlug con includeArchived, createCategory, updateCategory).

  • Propuesta: separar en puerto de lectura pública + puerto admin (escritura + lectura con archivadas), como sugería la review. Un mismo adaptador Drizzle puede implementar ambos.
  • Archivos: supplies/domain/ports/category.repository.ts, infrastructure/drizzle/drizzle-category.repository.ts.

3. Regla de dominio y mapeo de error en el controller

CategoriesAdminController.delete() hace isCoreCategory(slug) y lanza BadRequestException directamente, mientras el resto de invariantes se lanzan como errores de dominio desde los casos de uso. Manejo de errores inconsistente (parte en filtro, parte inline en el controller).

  • Propuesta: mover la protección de slug núcleo al caso de uso (error de dominio CategoryProtectedError o equivalente) y mapear todos los errores de categoría en el SuppliesDomainExceptionFilter global (o un filtro de categorías), quitando el throw HTTP del controller.
  • Archivos: infrastructure/http/categories-admin.controller.ts, application/*category*.ts, infrastructure/http/supplies-domain-exception.filter.ts.

Criterios de aceptación

Referencias

Review con el detalle: PR #246 (comentarios de review). Contrato público/interno: #245. Feature original: #221.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2area:dataIngesta, taxonomia y datos de recursosin-progressClaim activo: una sesion trabaja esta issue (protocolo en AGENTS.md)task

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions