# US-MGN-004-003-002: Validar y Postear Asiento Contable **RF Asociado:** [RF-MGN-004-003](../../02-modelado/requerimientos-funcionales/mgn-004/RF-MGN-004-003-registro-de-asientos-contables.md) **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 1. **TC-001:** Postear draft exitosamente 2. **TC-002:** Error por asiento desbalanceado 3. **TC-003:** Error por asiento ya posted 4. **TC-004:** Error por cuenta inactiva 5. **TC-005:** Error por journal inactivo 6. **TC-006:** Actualizar balance_debit correctamente 7. **TC-007:** Actualizar balance_credit correctamente 8. **TC-008:** No se puede editar asiento posted 9. **TC-009:** No se puede eliminar asiento posted ### No Funcionales 1. **Performance:** < 300ms para postear (incluye actualización balances) 2. **Atomicidad:** Rollback completo si falla actualización de balances 3. **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 `@Transactional` o `BEGIN/COMMIT` para 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/actualizar `journal_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_events` para 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 --- ## Referencias - [RF-MGN-004-003](../../02-modelado/requerimientos-funcionales/mgn-004/RF-MGN-004-003-registro-de-asientos-contables.md) - [ET-BACKEND-MGN-004-003](../../02-modelado/especificaciones-tecnicas/backend/mgn-004/ET-BACKEND-MGN-004-003-registro-de-asientos-contables.md) - [ET-FRONTEND-MGN-004-003](../../02-modelado/especificaciones-tecnicas/frontend/mgn-004/ET-FRONTEND-MGN-004-003-registro-de-asientos-contables.md) - [TRACEABILITY-MGN-004.yaml](../../02-modelado/trazabilidad/TRACEABILITY-MGN-004.yaml) - [Financial Schema DDL](../../02-modelado/database-design/schemas/financial-schema-ddl.sql)