9.9 KiB
US-MGN-004-003-001: Crear Asiento Contable (Draft)
RF Asociado: RF-MGN-004-003 Módulo: MGN-004 - Financiero Básico Epic: Asientos Contables Prioridad: P0 (MVP) Story Points: 5 Sprint: Sprint 8 Estado: Ready for Development Fecha: 2025-11-24
User Story
Como contador, Quiero crear asientos contables en estado borrador (draft) con múltiples líneas, Para registrar transacciones contables antes de confirmarlas y validarlas.
Descripción Detallada
Un asiento contable (journal entry) es el registro de una transacción en el sistema contable. Cada asiento tiene:
- Número secuencial generado automáticamente por el journal
- Fecha contable (accounting_date)
- Journal asociado
- Estado: draft (borrador) o posted (publicado)
- Referencia (opcional, ej: "Factura #123")
- Líneas contables (journal_entry_lines) con débitos y créditos
Esta US cubre la creación de asientos en estado draft, permitiendo editar y corregir antes de publicar.
Criterios de Aceptación
Escenario 1: Crear asiento con líneas válidas
Dado que soy contador con permisos accounting_user, Cuando creo un asiento con journal_id=1, date="2024-01-15", y líneas:
- Cuenta 1.1.01.001 (Caja), debit=1000, credit=0
- Cuenta 4.1.01.001 (Ingresos), debit=0, credit=1000, Entonces el sistema crea el asiento en estado draft y retorna id, number (ej: "VEN/2024/0001").
Escenario 2: Validar suma débitos = créditos
Dado que intento crear un asiento con total_debit=1000 y total_credit=900, Cuando envío el request, Entonces el sistema retorna error 400 "Asiento desbalanceado. Suma de débitos (1000) debe igualar suma de créditos (900)".
Escenario 3: Validar que journal existe y está activo
Dado que intento crear un asiento con journal_id=999 (no existe), Cuando envío el request, Entonces el sistema retorna error 404 "Journal no encontrado o inactivo".
Escenario 4: Validar que cuentas existen
Dado que una línea tiene account_id=888 (no existe), Cuando intento crear el asiento, Entonces el sistema retorna error 400 "Cuenta contable 888 no encontrada".
Escenario 5: Generar número secuencial automático
Dado que el journal "VEN" tiene secuencia "VEN/2024/{seq}", Cuando creo un asiento en el journal VEN, Entonces el sistema genera automáticamente number="VEN/2024/0001" (o el siguiente disponible).
Escenario 6: Permitir múltiples líneas
Dado que creo un asiento con 5 líneas contables, Cuando las sumas de débitos y créditos coinciden, Entonces el sistema crea el asiento exitosamente con las 5 líneas asociadas.
Reglas de Negocio
- RN-1: Suma de débitos debe igualar suma de créditos (asiento balanceado).
- RN-2: Estado inicial es siempre "draft". No se puede crear directamente en "posted".
- RN-3: Número de asiento se genera automáticamente usando secuencia del journal.
- RN-4: Cada línea debe tener account_id válido (cuenta existente y activa).
- RN-5: Cada línea debe tener debit O credit (no ambos, no ninguno). Al menos uno > 0.
- RN-6: Fecha contable (accounting_date) no puede ser futura. Máximo fecha actual.
- RN-7: Journal debe estar activo (active=true).
- RN-8: Asientos draft pueden editarse y eliminarse libremente.
Tareas Técnicas
Backend
- Endpoint:
POST /api/v1/financial/journal-entries - Service:
JournalEntryService.create(createJournalEntryDto) - Service:
JournalEntryService.validateBalance(lines)- Suma débitos = créditos - Service:
JournalEntryService.generateEntryNumber(journalId, date)- Secuencia - DTO:
CreateJournalEntryDto(journal_id, date, reference, lines) - DTO:
JournalEntryLineDto(account_id, debit, credit, label, partner_id?) - Validar journal existe y está activo
- Validar cuentas existen y están activas
- Validar que debit >= 0 y credit >= 0
- Validar que al menos una línea tiene debit > 0 o credit > 0
- Unit tests (10 test cases)
- Integration tests (8 test cases)
- Swagger docs con ejemplo de request/response
Frontend
- Componente:
CreateJournalEntryForm.tsx - Componente:
JournalEntryLineItems.tsx(tabla editable de líneas) - Componente:
AddLineButton.tsx(agregar línea) - Página:
CreateJournalEntryPage.tsx(/financial/journal-entries/create) - Lógica: Calcular totales de débito/crédito en tiempo real
- Validación: Mostrar warning si débito ≠ crédito
- API client:
journalEntryApi.create(data) - Store:
useJournalEntryStore.ts - Component tests (6 test cases)
- E2E test: "should create journal entry successfully"
Database
- Tabla:
financial.journal_entries(id, company_id, journal_id, number, date, state, reference) - Tabla:
financial.journal_entry_lines(id, entry_id, account_id, debit, credit, label, partner_id) - Índices: idx_journal_entries_journal_id, idx_journal_entries_date, idx_journal_entries_state
- Índices: idx_journal_entry_lines_entry_id, idx_journal_entry_lines_account_id
- RLS policies: company_isolation
Mockups / Wireframes
Formulario Crear Asiento:
┌─────────────────────────────────────────────────┐
│ Crear Asiento Contable │
├─────────────────────────────────────────────────┤
│ Journal: [Dropdown: VEN - Ventas] ▼ │
│ Fecha: [Date Picker: 2024-01-15] │
│ Referencia: [Input: Factura #123] (opcional) │
├─────────────────────────────────────────────────┤
│ Líneas Contables: │
│ ┌───────────────────────────────────────────┐ │
│ │Cuenta │ Partner │Debe │Haber │Etiqueta│ │
│ ├─────────┼─────────┼─────┼──────┼────────┤ │
│ │1.1.01..│ - │1000 │ 0 │Cobro │[×]│
│ │4.1.01..│Cliente A│ 0 │1000 │Venta │[×]│
│ └───────────────────────────────────────────┘ │
│ [+ Agregar Línea] │
├─────────────────────────────────────────────────┤
│ Total Débito: 1000.00 Total Crédito: 1000.00 │
│ Diferencia: 0.00 ✓ │
├─────────────────────────────────────────────────┤
│ [Guardar Borrador] [Cancelar] │
└─────────────────────────────────────────────────┘
Interacciones:
- Al agregar línea: Nueva fila en blanco
- Al cambiar débito/crédito: Recalcular totales
- Diferencia ≠ 0: Mostrar warning "Asiento desbalanceado"
- Al guardar: Validar balance antes de enviar
Casos de Prueba
Funcionales
- TC-001: Crear asiento balanceado exitosamente
- TC-002: Error por asiento desbalanceado
- TC-003: Error por journal inexistente
- TC-004: Error por cuenta inexistente en línea
- TC-005: Generar número secuencial correcto
- TC-006: Crear asiento con 5 líneas
- TC-007: Error por fecha futura
- TC-008: RLS filtra por empresa
No Funcionales
- Performance: < 500ms para crear asiento con 10 líneas
- Seguridad: JWT + permiso accounting_user
Dependencias
- US bloqueantes:
- US-MGN-004-001-001 (CRUD Cuentas)
- US-MGN-004-002-001 (CRUD Journals)
- US-MGN-003-001-001 (CRUD Partners) - Opcional para partner_id
- Módulos: MGN-003, MGN-004
Notas de Implementación
- Secuencia de número: Usar formato "{journal_code}/{year}/{seq:05d}" ej: "VEN/2024/00001"
- Validación de balance: Redondear a 2 decimales para evitar errores por precisión flotante
- Líneas: Mínimo 2 líneas requeridas (al menos 1 débito y 1 crédito)
- Frontend: Usar React Table o Tanstack Table para edición inline de líneas
- Considerar agregar campo
descriptional asiento para notas adicionales
Estimación Detallada
| Tarea | Estimación |
|---|---|
| Backend | 3 horas |
| Frontend | 3 horas |
| Testing | 2 horas |
| Code Review | 1 hora |
| TOTAL | 9 horas = 5 SP |
Definition of Done
- Código backend implementado
- Código frontend implementado
- Unit tests pasando (>80%)
- Integration tests pasando
- E2E tests pasando
- Code review aprobado
- Swagger docs actualizado
- RLS aplicado
- Validación de balance funciona
- Secuencia automática funciona
- Merge a develop
- QA validado
- PO aprobado