feat: CAT-001 Árbol N-ario de Rubros #30
Reference in New Issue
Block a user
Delete Branch "feature/CAT-001"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
feat: CAT-001 Árbol N-ario de Rubros
Resumen
Implementa el árbol N-ario de rubros (taxonomía jerárquica) que sustenta el catálogo comercial de SIG-CM 2.0. Cubre la pila completa: migración de BD, dominio, application, infraestructura, API REST y frontend React.
Cambios por capa
Base de datos (
d3ed830,4a88cb4)dbo.Rubrocon Adjacency List (ParentId INT NULL FK auto-referencial),SYSTEM_VERSIONING ON(historydbo.Rubro_History, retention 10 años), índice único filtradoUQ_Rubro_ParentId_Nombre_Activo WHERE ParentId IS NOT NULL AND Activo=1, índice cubrienteIX_Rubro_ParentId_Activo, permisocatalogo:rubros:gestionar+ RolPermiso admin.COLLATE SQL_Latin1_General_CP1_CI_AIdebe ir ANTES deNOT NULL(SQL Server 2019 parser).V016_ROLLBACK.sqlincluido. Aplicada aSIGCM2ySIGCM2_Test.Domain + Application (
4c9b7ea,d4c05cc)Rubroentity:sealed class, factory methodsForCreation,WithRenamed,WithMoved,WithActivo; validaciónValidateNombre.RubroNotFoundException(404),RubroNombreDuplicadoEnPadreException(409),RubroTieneHijosActivosException(409),RubroPadreInactivoException(400),RubroMaxDepthExceededException(422),RubroCycleDetectedException(400) — todas wired enExceptionFilter.IRubroRepository: 9 métodos incluyendoGetDepthAsync,GetMaxOrdenAsync,ExistsByNombreUnderParentAsync,GetDescendantIdsAsync.RubroTreeBuilder: O(n) conToLookup(noToDictionary— maneja clavesint?sinDuplicateKeyException).CreateRubro,UpdateRubro,DeactivateRubro,MoveRubro+ handlers.GetRubroTree,GetRubroById+ handlers.Infrastructure (
cc3108d)RubroRepository(Dapper): recursive CTEs para árbol,ExistsByNombreUnderParentAsyncconUPPER()CI,GetMaxOrdenAsynccon MAX+1 semántico.IRubroRepository → RubroRepositoryregistrado.API (
ff7c287)RubrosController(6 endpoints):GET /rubros/tree,GET /rubros/{id},POST /admin/rubros,PUT /admin/rubros/{id},DELETE /admin/rubros/{id},PATCH /admin/rubros/{id}/mover.rubro.*víaIAuditLogger.Frontend (
f6733ac,f8d861a)src/web/src/features/rubros/— types, api (6 fns), hooks (5), components (CategoryTree, CategoryTreeNode, RubroFormDialog, DeleteRubroDialog), pages (RubrosPage).CanPerformgate, Switch incluir-inactivos, dialogs wired./admin/rubros(ProtectedPage,catalogo:rubros:gestionar) + sidebar entry "Rubros".f8d861a):zodResolvertype inference con schemaz.string().transform+pipeen lugar dez.union([z.coerce.number(), z.literal('')]).Tests (
b1be4a5)Rubro_HistoryaTablesToIgnore+ counts de permisos 24 → 25 en 5 archivos.Commits
d3ed8304a88cb44c9b7ead4c05ccb1be4a5cc3108dff7c287f6733acf8d861aDecisiones clave
Seccion:Rubroindependiente del módulo de medios; asociación diferida a CAT-002+.WHERE ParentId IS NOT NULL AND Activo=1; raíces enforced porExistsByNombreUnderParentAsync.ToLookupen RubroTreeBuilder:GroupBy(...).ToDictionary()lanzaDuplicateKeyExceptionen runtime conint?keys.Resumen de tests
Seguimientos abiertos (out of scope)
SIGCM2_Testcompartida — resolver antes de CAT-002.onMove={() => {}}placeholder enRubrosPage— hook implementado y testeado, UI del dialog diferida.TestWebAppFactory.cs+AssignPermisosToRolCommandHandlerTests.cs— pre-existentes, out of scope.Artefactos SDD
Engram project
sig-cm2:sdd/cat-001-arbol-nario-rubros/{explore,proposal,spec,design,tasks,apply-progress,verify-report,pr-body}- Reemplaza z.union([z.coerce.number(), z.literal('')]) por z.string().transform+pipe para evitar inferencia unknown en zodResolver - Simplifica RubroFormValues a {nombre: string, tarifarioBaseId?: number | null} - Actualiza RubrosPage: tarifarioId ya llega como number|null del schema transformf5ed9c4b3cto389dda6e5e