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