# 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)
