# US-MGN-004-005-003: Cancelar Factura de Cliente

**RF Asociado:** [RF-MGN-004-005](../../02-modelado/requerimientos-funcionales/mgn-004/RF-MGN-004-005-gestión-de-facturas-de-cliente.md)
**Módulo:** MGN-004 - Financiero Básico
**Epic:** Facturas de Cliente
**Prioridad:** P0 (MVP)
**Story Points:** 3
**Sprint:** Sprint 10
**Estado:** Ready for Development
**Fecha:** 2025-11-24

---

## User Story

**Como** contador,
**Quiero** cancelar facturas de cliente validadas,
**Para** anular facturas erróneas creando asiento de reversión automático.

---

## Descripción Detallada

Cancelar factura genera asiento de reversión que anula el asiento contable original. Estado cambia a 'cancelled'. Factura queda cancelada permanentemente (no se puede reactivar).

---

## Criterios de Aceptación

### Escenario 1: Cancelar factura open
**Dado que** factura tiene state='open',
**Cuando** POST /customer-invoices/10/cancel con reason="Error en cliente",
**Entonces** state='cancelled', asiento reversión creado y posteado, cancelled_date y cancelled_by asignados.

### Escenario 2: Generar asiento de reversión
**Dado que** factura tiene asiento original con débito 1210 y créditos 1000+210,
**Cuando** cancelo factura,
**Entonces** asiento reversión invierte débitos ↔ créditos.

### Escenario 3: No cancelar factura ya cancelled
**Dado que** state='cancelled',
**Cuando** intento cancelar nuevamente,
**Entonces** error 409 "Factura ya está cancelada".

### Escenario 4: No cancelar factura paid
**Dado que** state='paid' (tiene pagos),
**Cuando** intento cancelar,
**Entonces** error 400 "No se puede cancelar factura pagada. Primero reversa los pagos".

---

## Reglas de Negocio

- **RN-1:** Solo facturas open pueden cancelarse.
- **RN-2:** Facturas paid requieren reversar pagos primero.
- **RN-3:** Se genera asiento de reversión automático.
- **RN-4:** Motivo de cancelación es obligatorio.
- **RN-5:** Estado final 'cancelled' es irreversible.

---

## Tareas Técnicas

### Backend
- [ ] POST /api/v1/financial/customer-invoices/:id/cancel
- [ ] DTO: CancelInvoiceDto (reason required)
- [ ] Service: CustomerInvoiceService.cancel()
- [ ] Validar state='open', no paid
- [ ] Crear asiento de reversión
- [ ] Actualizar state='cancelled', cancelled_date, cancelled_by
- [ ] Unit tests (6 test cases)
- [ ] Integration tests (5 test cases)

### Frontend
- [ ] Botón "Cancelar Factura" (si open y no paid)
- [ ] Modal: CancelInvoiceModal.tsx (reason required)
- [ ] Badge: Cancelada
- [ ] E2E test: cancel invoice

### Database
- [ ] Campo: invoices.cancelled_date, cancelled_by, cancel_reason

---

## Estimación: 6 horas = 3 SP

---

## References

- [RF-MGN-004-005](../../02-modelado/requerimientos-funcionales/mgn-004/RF-MGN-004-005-gestión-de-facturas-de-cliente.md)
