12 KiB
US-MGN-004-001-001: CRUD de Cuentas Contables
RF Asociado: RF-MGN-004-001 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:
CreateAccountDtocon 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
-
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
-
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"
-
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
-
TC-004: Actualizar nombre de cuenta
- Input: PUT /accounts/123 {"name": "Caja Principal"}
- Expected: Status 200, cuenta actualizada con nuevo nombre
-
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"
-
TC-006: Soft delete exitoso
- Input: DELETE /accounts/456 (sin movimientos)
- Expected: Status 200, active=false, deleted_at!=null
-
TC-007: RLS filtra por empresa
- Input: Usuario de empresa A lista cuentas
- Expected: Solo ve cuentas de empresa A, no de empresa B
-
TC-008: Validar parent_id existe
- Input: parent_id=999 (no existe)
- Expected: Status 400, error "Cuenta padre no encontrada"
Pruebas No Funcionales
- Performance: Response time < 200ms para GET /accounts (100 registros)
- Seguridad:
- JWT token requerido en header Authorization
- Permiso accounting_user o accounting_manager requerido
- RLS aplica filtro automático por company_id
- 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
ILIKEoto_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_idrecursivo 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
developcompletado - QA manual completado (happy path + edge cases)
- Product Owner ha validado funcionalidad