10 KiB
US-MGN-004-003-002: Validar y Postear Asiento Contable
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 validar y publicar (postear) asientos contables en estado draft, Para confirmar que son correctos e inmutables, y que impacten los balances de las cuentas contables.
Descripción Detallada
Esta US implementa la acción de "postear" un asiento contable, cambiando su estado de draft a posted. Una vez publicado:
- El asiento NO puede modificarse ni eliminarse
- Los balances de las cuentas contables se actualizan
- Se registra fecha de publicación y usuario que publicó
- El número de asiento queda definitivo
El sistema valida antes de postear:
- Asiento está balanceado (débitos = créditos)
- Todas las cuentas están activas
- Journal está activo
- Fecha contable está dentro del período fiscal abierto
Criterios de Aceptación
Escenario 1: Postear asiento draft exitosamente
Dado que tengo un asiento con id=10, state='draft', balanceado correctamente, Cuando ejecuto POST /api/v1/financial/journal-entries/10/post, Entonces el sistema:
- Cambia state='posted'
- Asigna posted_date=now()
- Asigna posted_by=current_user_id
- Actualiza balances de cuentas involucradas
- Retorna asiento actualizado
Escenario 2: Error al postear asiento desbalanceado
Dado que tengo un asiento draft con total_debit=1000 y total_credit=900, Cuando intento postearlo, Entonces el sistema retorna error 400 "No se puede postear asiento desbalanceado".
Escenario 3: No se puede postear asiento ya posted
Dado que tengo un asiento con state='posted', Cuando intento postearlo nuevamente, Entonces el sistema retorna error 409 "El asiento ya está publicado".
Escenario 4: Validar cuentas activas antes de postear
Dado que un asiento draft tiene una línea con cuenta inactiva (active=false), Cuando intento postearlo, Entonces el sistema retorna error 400 "Cuenta [código] está inactiva. No se puede postear".
Escenario 5: Validar journal activo antes de postear
Dado que un asiento draft tiene journal inactivo, Cuando intento postearlo, Entonces el sistema retorna error 400 "Journal inactivo. No se puede postear".
Escenario 6: Actualizar balances de cuentas
Dado que una cuenta tiene balance_debit=5000 y balance_credit=3000, Cuando posteo un asiento que agrega debit=1000 a esa cuenta, Entonces el balance_debit se actualiza a 6000.
Reglas de Negocio
- RN-1: Solo asientos con state='draft' pueden postearse.
- RN-2: Al postear, se valida nuevamente que débitos = créditos (por si hubo cambios).
- RN-3: Todas las cuentas en las líneas deben estar activas.
- RN-4: Journal debe estar activo.
- RN-5: Una vez posted, el asiento es inmutable (no se puede editar ni eliminar).
- RN-6: Se actualizan los balances acumulados de las cuentas (balance_debit, balance_credit).
- RN-7: Se registra auditoría: posted_date, posted_by.
- RN-8: Fecha contable debe estar dentro de período fiscal abierto (validación futura).
Tareas Técnicas
Backend
- Endpoint:
POST /api/v1/financial/journal-entries/:id/post - Service:
JournalEntryService.post(id, userId) - Service:
JournalEntryService.validateForPosting(entry)- Validaciones previas - Service:
AccountService.updateBalances(entryLines)- Actualizar balances - Validar state='draft' antes de postear
- Validar balance débitos = créditos
- Validar que cuentas están activas
- Validar que journal está activo
- Actualizar state='posted', posted_date=now(), posted_by=user_id
- Actualizar balances de cuentas: balance_debit += line.debit, balance_credit += line.credit
- Transacción atómica: postear + actualizar balances en misma transacción DB
- Unit tests (10 test cases)
- Integration tests (8 test cases)
- Swagger docs
Frontend
- Componente:
PostJournalEntryButton.tsx(botón "Publicar") - Modal:
ConfirmPostModal.tsx(confirmación antes de postear) - Estado: Deshabilitar edición si state='posted'
- Badge: Mostrar "Borrador" / "Publicado" según state
- API client:
journalEntryApi.post(id) - Toast: "Asiento publicado exitosamente"
- Component test: PostJournalEntryButton.test.tsx
- E2E test: "should post draft entry successfully"
- E2E test: "should prevent editing posted entry"
Database
- Campo:
journal_entries.state(enum: 'draft', 'posted') - Campo:
journal_entries.posted_date(timestamp nullable) - Campo:
journal_entries.posted_by(uuid nullable, FK a auth.users) - Campo:
accounts.balance_debit(numeric, default 0) - Campo:
accounts.balance_credit(numeric, default 0) - Índice: idx_journal_entries_state (para filtrar drafts vs posted)
- Trigger o función: Actualizar balances de cuentas después de postear
Mockups / Wireframes
Vista Detalle de Asiento (Draft):
┌──────────────────────────────────────────┐
│ Asiento VEN/2024/0001 [Borrador] │
│ [Editar] [Publicar] [Eliminar] │
├──────────────────────────────────────────┤
│ Journal: VEN - Ventas │
│ Fecha: 2024-01-15 │
│ Referencia: Factura #123 │
│ │
│ Líneas: │
│ 1.1.01.001 | Debe: 1000 | Haber: 0 │
│ 4.1.01.001 | Debe: 0 | Haber: 1000 │
└──────────────────────────────────────────┘
Modal Confirmación Publicar:
┌──────────────────────────────────────────┐
│ ⚠️ Confirmar Publicación │
├──────────────────────────────────────────┤
│ ¿Está seguro de publicar este asiento? │
│ │
│ Una vez publicado, NO podrá modificarlo │
│ ni eliminarlo. │
│ │
│ [Cancelar] [Sí, Publicar] │
└──────────────────────────────────────────┘
Vista Detalle de Asiento (Posted):
┌──────────────────────────────────────────┐
│ Asiento VEN/2024/0001 [Publicado ✓] │
│ [Ver Asiento de Reversión] │
├──────────────────────────────────────────┤
│ Journal: VEN - Ventas │
│ Fecha: 2024-01-15 │
│ Publicado: 2024-01-15 10:30 (Juan P.) │
│ Referencia: Factura #123 │
└──────────────────────────────────────────┘
Casos de Prueba
Funcionales
- TC-001: Postear draft exitosamente
- TC-002: Error por asiento desbalanceado
- TC-003: Error por asiento ya posted
- TC-004: Error por cuenta inactiva
- TC-005: Error por journal inactivo
- TC-006: Actualizar balance_debit correctamente
- TC-007: Actualizar balance_credit correctamente
- TC-008: No se puede editar asiento posted
- TC-009: No se puede eliminar asiento posted
No Funcionales
- Performance: < 300ms para postear (incluye actualización balances)
- Atomicidad: Rollback completo si falla actualización de balances
- Seguridad: Solo usuarios con permiso accounting_manager pueden postear
Dependencias
- US bloqueantes:
- US-MGN-004-003-001 (Crear Asiento Draft)
- Módulos: MGN-004
Notas de Implementación
- Transacción atómica: Usar
@TransactionaloBEGIN/COMMITpara garantizar que postear + actualizar balances ocurre todo o nada - Actualización de balances: Considerar usar triggers de PostgreSQL para actualizar automáticamente
accounts.balance_*al insertar/actualizarjournal_entry_lines - Performance: Si hay muchas líneas, batch update los balances en una sola query
- Auditoría: Registrar evento de "post" en tabla
audit.journal_entry_eventspara trazabilidad - Validación de período fiscal: En Fase 2, agregar validación de que fecha contable está dentro de período fiscal abierto
- Reversión: Esta US NO cubre reversión (cancelación). Eso es US siguiente
Estimación Detallada
| Tarea | Estimación |
|---|---|
| Backend | 3 horas |
| Frontend | 2 horas |
| Testing | 2 horas |
| Code Review | 1 hora |
| TOTAL | 8 horas = 5 SP |
Definition of Done
- Código backend implementado
- Código frontend implementado
- Unit tests pasando (>80%)
- Integration tests pasando
- E2E tests pasando
- Validaciones de negocio funcionan
- Balances se actualizan correctamente
- Transacción atómica funciona (rollback en caso de error)
- Code review aprobado
- Swagger docs actualizado
- Merge a develop
- QA validado (happy path + errores)
- PO aprobado