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.
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 sirveGET /categoriespúblico) cargakind,archivedAtycodePrefix, y el DTO públicoCategoryDtoexponekind. Contradice el contrato documentado encategory-definition.ts(introducido en #245): "proyección PÚBLICA… no debe crecer con datos de gestión interna".CategoryRecordconarchivedAt/kind/codePrefix) para la API admin, dejandoCategoryDefinitioncomo proyección pública mínima. Decidir explícitamente sikinddebe ser público (si sí, documentarlo en el contrato; si no, quitarlo delGET /categories).supplies/domain/category-definition.ts,infrastructure/http/category-response.dto.ts,categories.controller.ts.2. El puerto
CategoryRepositoryno respeta ISPUn único
CategoryRepositorymezcla lectura pública (listCategories) con escritura/gestión admin (findBySlugconincludeArchived,createCategory,updateCategory).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()haceisCoreCategory(slug)y lanzaBadRequestExceptiondirectamente, 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).CategoryProtectedErroro equivalente) y mapear todos los errores de categoría en elSuppliesDomainExceptionFilterglobal (o un filtro de categorías), quitando elthrowHTTP del controller.infrastructure/http/categories-admin.controller.ts,application/*category*.ts,infrastructure/http/supplies-domain-exception.filter.ts.Criterios de aceptación
GET /categoriespúblico no expone campos de gestión interna (okindqueda documentado como público a propósito).HttpExceptionse lanza desde el controller admin; los errores de categoría se mapean en el filtro.pnpm gen:apisi cambian DTOs.Referencias
Review con el detalle: PR #246 (comentarios de review). Contrato público/interno: #245. Feature original: #221.