# US-MGN-004-001-001: CRUD de Cuentas Contables **RF Asociado:** [RF-MGN-004-001](../../02-modelado/requerimientos-funcionales/mgn-004/RF-MGN-004-001-gestión-de-plan-de-cuentas.md) **Módulo:** MGN-004 - Financiero Básico **Epic:** Plan de Cuentas **Prioridad:** P0 (MVP) **Story Points:** 5 **Sprint:** Sprint 7 **Estado:** Ready for Development **Fecha:** 2025-11-24 --- ## User Story **Como** contador de la empresa, **Quiero** crear, consultar, actualizar y eliminar cuentas contables, **Para** mantener el plan de cuentas actualizado según las necesidades fiscales y contables de mi empresa. --- ## Descripción Detallada Esta user story cubre las operaciones básicas CRUD (Create, Read, Update, Delete) sobre cuentas contables. Las cuentas contables son la base del sistema financiero y se clasifican en tipos: Asset (Activo), Liability (Pasivo), Equity (Patrimonio), Income (Ingresos), Expense (Gastos). Cada cuenta tiene un código único por empresa, nombre, tipo, moneda, y puede estar activa o inactiva. Las cuentas pueden organizarse jerárquicamente mediante el campo parent_id. --- ## Criterios de Aceptación ### Escenario 1: Crear cuenta contable exitosamente **Dado que** soy un contador con permisos de accounting_manager, **Cuando** creo una cuenta con código "1.1.01.001", nombre "Caja General", tipo "asset", moneda "USD" y activa=true, **Entonces** el sistema crea la cuenta y retorna el registro completo con id, created_at y company_id asignados. ### Escenario 2: Validar código único por empresa **Dado que** ya existe una cuenta con código "1.1.01.001" en mi empresa, **Cuando** intento crear otra cuenta con el mismo código, **Entonces** el sistema retorna error 400 "El código de cuenta ya existe en esta empresa". ### Escenario 3: Listar cuentas con filtros **Dado que** tengo 50 cuentas contables en mi empresa, **Cuando** listo cuentas con filtro type="asset" y active=true, **Entonces** el sistema retorna solo las cuentas activas de tipo Asset con paginación (limit=20 por defecto). ### Escenario 4: Actualizar cuenta contable **Dado que** tengo una cuenta existente con id=123, **Cuando** actualizo el nombre de "Caja General" a "Caja Principal", **Entonces** el sistema actualiza el nombre y retorna la cuenta modificada con updated_at actualizado. ### Escenario 5: No se puede eliminar cuenta con movimientos **Dado que** tengo una cuenta con id=123 que tiene asientos contables asociados, **Cuando** intento eliminar la cuenta (soft delete), **Entonces** el sistema retorna error 409 "No se puede eliminar cuenta con movimientos contables. Desactívela en su lugar". ### Escenario 6: Soft delete de cuenta sin movimientos **Dado que** tengo una cuenta con id=456 sin movimientos asociados, **Cuando** ejecuto DELETE /api/v1/financial/accounts/456, **Entonces** el sistema marca active=false y deleted_at=now(), sin eliminar el registro físicamente. --- ## Reglas de Negocio - **RN-1:** Código de cuenta debe ser único por empresa (no globalmente). Empresa A y B pueden tener código "1.1.01.001" sin conflicto. - **RN-2:** Código de cuenta debe tener formato alfanumérico, longitud máxima 20 caracteres. - **RN-3:** Tipos de cuenta válidos: asset, liability, equity, income, expense. - **RN-4:** Una cuenta puede tener parent_id (cuenta padre) para jerarquía, pero no puede ser su propio padre (prevenir ciclos). - **RN-5:** No se puede eliminar físicamente una cuenta con movimientos contables. Solo soft delete (active=false). - **RN-6:** Moneda de cuenta debe existir en catalogo master.currencies. - **RN-7:** RLS (Row Level Security) filtra cuentas por company_id del usuario autenticado. --- ## Tareas Técnicas (Checklist de Implementación) ### Backend - [ ] Endpoint: `POST /api/v1/financial/accounts` - [ ] Endpoint: `GET /api/v1/financial/accounts` (con filtros: type, active, search, parent_id) - [ ] Endpoint: `GET /api/v1/financial/accounts/:id` - [ ] Endpoint: `PUT /api/v1/financial/accounts/:id` - [ ] Endpoint: `DELETE /api/v1/financial/accounts/:id` (soft delete) - [ ] Service método: `AccountService.create(createAccountDto)` - [ ] Service método: `AccountService.findAll(filters, pagination)` - [ ] Service método: `AccountService.findOne(id)` - [ ] Service método: `AccountService.update(id, updateAccountDto)` - [ ] Service método: `AccountService.remove(id)` (con validación de movimientos) - [ ] DTO: `CreateAccountDto` con validaciones @IsString, @IsEnum, @IsOptional - [ ] DTO: `UpdateAccountDto` (partial de CreateAccountDto) - [ ] DTO: `AccountFilterDto` (type, active, parent_id, search) - [ ] Validar código único por empresa (constraint o validator) - [ ] Validar que parent_id existe y no crea ciclos - [ ] Unit tests: AccountService (8 test cases) - [ ] Integration tests: AccountController (8 test cases) - [ ] Swagger docs con ejemplos de request/response ### Frontend - [ ] Componente: `AccountsTable.tsx` (lista de cuentas con filtros) - [ ] Componente: `CreateAccountForm.tsx` (formulario creación) - [ ] Componente: `EditAccountForm.tsx` (formulario edición) - [ ] Componente: `AccountCard.tsx` (vista detalle de cuenta) - [ ] Página: `AccountsPage.tsx` (/financial/accounts) - [ ] Página: `CreateAccountPage.tsx` (/financial/accounts/create) - [ ] Página: `EditAccountPage.tsx` (/financial/accounts/:id/edit) - [ ] API client: `accountApi.ts` (getAll, getById, create, update, delete) - [ ] Zustand store: `useAccountStore.ts` (accounts, filters, setAccounts, addAccount, updateAccount, deleteAccount) - [ ] Formularios con React Hook Form + Zod validation - [ ] Component tests: AccountsTable.test.tsx (5 test cases) - [ ] Component tests: CreateAccountForm.test.tsx (5 test cases) - [ ] E2E test: "should create account successfully" (Playwright) - [ ] E2E test: "should update account successfully" - [ ] E2E test: "should soft delete account without movements" ### Database - [ ] Tabla: `financial.accounts` (ya existe en schema DDL) - [ ] Validar índices: idx_accounts_company_id, idx_accounts_code, idx_accounts_parent_id, idx_accounts_type - [ ] Validar constraint: uq_accounts_code_company (código único por empresa) - [ ] Validar RLS policy: company_isolation_accounts - [ ] Seed data: Plan de cuentas básico para testing (10 cuentas por tipo) --- ## Mockups / Wireframes **Pantalla Lista de Cuentas (/financial/accounts):** - Header con título "Plan de Cuentas" y botón "Nueva Cuenta" (primary) - Filtros: Tipo (dropdown), Estado (Activo/Inactivo), Búsqueda (input text) - Tabla con columnas: Código, Nombre, Tipo, Moneda, Estado (badge), Acciones (Ver, Editar, Eliminar) - Paginación: 20 registros por página - Vista árbol (tree view) opcional para mostrar jerarquía **Formulario Crear/Editar Cuenta:** - Campo: Código (input, required, max 20 chars, alfanumérico) - Campo: Nombre (input, required, max 100 chars) - Campo: Tipo (select, required, opciones: Asset, Liability, Equity, Income, Expense) - Campo: Cuenta Padre (select, opcional, lista de cuentas existentes) - Campo: Moneda (select, required, lista de monedas activas) - Campo: Activa (checkbox, default true) - Campo: Descripción (textarea, opcional) - Botones: Guardar (primary), Cancelar (secondary) **Estados:** - Loading: Skeleton loaders en tabla - Success: Toast "Cuenta creada exitosamente" con icono de éxito - Error: Alert con mensaje de error arriba del formulario --- ## Casos de Prueba (Test Scenarios) ### Pruebas Funcionales 1. **TC-001: Crear cuenta exitosamente** - Input: código="1.1.01.001", nombre="Caja General", type="asset", currency="USD" - Expected: Status 201, cuenta creada con id, company_id asignado 2. **TC-002: Error por código duplicado** - Input: código="1.1.01.001" (ya existe en empresa) - Expected: Status 400, error "Código de cuenta ya existe" 3. **TC-003: Listar cuentas con filtro type=asset** - Input: GET /api/v1/financial/accounts?type=asset&limit=10 - Expected: Array de cuentas tipo asset, máximo 10 registros 4. **TC-004: Actualizar nombre de cuenta** - Input: PUT /accounts/123 {"name": "Caja Principal"} - Expected: Status 200, cuenta actualizada con nuevo nombre 5. **TC-005: Error al eliminar cuenta con movimientos** - Input: DELETE /accounts/123 (tiene movimientos) - Expected: Status 409, error "No se puede eliminar cuenta con movimientos" 6. **TC-006: Soft delete exitoso** - Input: DELETE /accounts/456 (sin movimientos) - Expected: Status 200, active=false, deleted_at!=null 7. **TC-007: RLS filtra por empresa** - Input: Usuario de empresa A lista cuentas - Expected: Solo ve cuentas de empresa A, no de empresa B 8. **TC-008: Validar parent_id existe** - Input: parent_id=999 (no existe) - Expected: Status 400, error "Cuenta padre no encontrada" ### Pruebas No Funcionales 1. **Performance:** Response time < 200ms para GET /accounts (100 registros) 2. **Seguridad:** - JWT token requerido en header Authorization - Permiso accounting_user o accounting_manager requerido - RLS aplica filtro automático por company_id 3. **Usabilidad:** - Formulario valida en tiempo real (on blur) - Mensajes de error claros y en español - Foco automático en primer campo del formulario --- ## Dependencias - **US bloqueantes:** - US-MGN-002-001-001 (CRUD de Empresas) - Requerido para company_id - US-MGN-003-003-001 (Gestión de Monedas) - Requerido para currency_id - **Módulos requeridos:** MGN-002 (Empresas), MGN-003 (Monedas) - **Datos maestros necesarios:** - Al menos 1 empresa activa con company_id - Al menos 1 moneda activa (USD, EUR, etc.) --- ## Notas de Implementación - Implementar búsqueda full-text en código y nombre usando PostgreSQL `ILIKE` o `to_tsvector` - Considerar indexación GIN para búsqueda full-text si el catálogo crece (>10,000 cuentas) - Tree view para jerarquía: usar `parent_id` recursivo o Common Table Expressions (CTE) para calcular niveles - Soft delete: Usar campo `deleted_at` (nullable timestamp) en lugar de eliminar registro - Auditoría: Registrar cambios en tabla `audit.account_changes` (created_by, updated_by, changed_fields) - Cache: Considerar cachear lista de cuentas activas en Redis (TTL 5 minutos) para mejorar performance --- ## Estimación Detallada | Tarea | Estimación | |-------|------------| | Backend Development (Endpoints + Services + DTOs) | 3 horas | | Frontend Development (Componentes + Forms + API Client) | 3 horas | | Testing (Unit + Integration + E2E) | 2 horas | | Code Review | 1 hora | | **TOTAL** | **9 horas** | **Equivalente:** 5 Story Points --- ## Definition of Done (DoD) - [ ] Código backend implementado según ET-BACKEND-MGN-004-001 - [ ] Código frontend implementado según ET-FRONTEND-MGN-004-001 - [ ] Unit tests escritos y pasando (cobertura > 80%) - [ ] Integration tests escritos y pasando - [ ] E2E tests escritos y pasando (create, update, delete) - [ ] Code review completado y aprobado por Tech Lead - [ ] Documentación Swagger actualizada con ejemplos - [ ] Sin vulnerabilidades de seguridad (SonarQube green) - [ ] RLS policies aplicadas y testeadas - [ ] Merge a branch `develop` completado - [ ] QA manual completado (happy path + edge cases) - [ ] Product Owner ha validado funcionalidad --- ## Referencias - [RF-MGN-004-001: Gestión de Plan de Cuentas](../../02-modelado/requerimientos-funcionales/mgn-004/RF-MGN-004-001-gestión-de-plan-de-cuentas.md) - [ET-BACKEND-MGN-004-001](../../02-modelado/especificaciones-tecnicas/backend/mgn-004/ET-BACKEND-MGN-004-001-gestión-de-plan-de-cuentas.md) - [ET-FRONTEND-MGN-004-001](../../02-modelado/especificaciones-tecnicas/frontend/mgn-004/ET-FRONTEND-MGN-004-001-gestión-de-plan-de-cuentas.md) - [TRACEABILITY-MGN-004.yaml](../../02-modelado/trazabilidad/TRACEABILITY-MGN-004.yaml) - [Financial Schema DDL](../../02-modelado/database-design/schemas/financial-schema-ddl.sql)