# US-MGN-007-002-001: Crear Sales Order desde Cotización

**RF Asociado:** [RF-MGN-007-002](../../02-modelado/requerimientos-funcionales/mgn-007/RF-MGN-007-002-conversión-a-órdenes-de-venta.md)
**Módulo:** MGN-007 - Ventas Básico
**Epic:** Órdenes de Venta
**Prioridad:** P0
**Story Points:** 5
**Sprint:** Sprint 16
**Estado:** Ready for Development
**Fecha:** 2025-11-24

---

## User Story

**Como** vendedor,
**Quiero** convertir una cotización confirmada en una orden de venta (Sales Order),
**Para** formalizar la venta y poder continuar con entregas y facturación.

---

## Descripción Detallada

Una Sales Order (SO) es el documento formal que confirma la venta. Se crea desde una cotización aceptada copiando:
- Datos del cliente
- Líneas de productos con cantidades y precios
- Descuentos e impuestos
- Términos de pago
- Fecha de entrega esperada

Estados de SO: draft, confirmed, done (entregado y facturado), cancelled.

La conversión de cotización → SO:
1. Valida que cotización esté en estado sent o draft
2. Crea nueva SO copiando todos los datos
3. Genera número secuencial SO/YYYY/NNNN
4. Cambia estado de cotización a sale (convertida)
5. Vincula quotation_id en sales.orders

---

## Criterios de Aceptación

### Escenario 1: Convertir cotización a SO (Camino Feliz)
**Dado que** cotización id=1 está en estado sent con total=1000,
**Cuando** ejecuto action "Convert to Sales Order",
**Entonces** sistema crea SO con número SO/2024/0001, copia todas las líneas, marca cotización como sale, vincula quotation_id=1.

### Escenario 2: Validar que cotización no está convertida
**Dado que** cotización ya está en estado sale (convertida),
**Cuando** intento convertir nuevamente,
**Entonces** sistema retorna error 400 "Cotización ya fue convertida a orden de venta".

### Escenario 3: Validar que cotización no está cancelada
**Dado que** cotización está en estado cancelled,
**Cuando** intento convertir,
**Entonces** sistema retorna error 400 "No se puede convertir cotización cancelada".

### Escenario 4: Copiar líneas correctamente
**Dado que** cotización tiene 3 líneas con productos, cantidades, precios,
**Cuando** se convierte a SO,
**Entonces** sistema copia las 3 líneas manteniendo todos los valores exactos.

### Escenario 5: Generar número secuencial de SO
**Dado que** última SO fue SO/2024/0005,
**Cuando** creo nueva SO desde cotización,
**Entonces** sistema genera número SO/2024/0006.

### Escenario 6: Vincular cotización y SO
**Dado que** cotización id=1 se convierte a SO id=10,
**Cuando** se completa conversión,
**Entonces** SO.quotation_id=1 y Quotation.state=sale.

---

## Reglas de Negocio

- **RN-1:** Solo cotizaciones en draft o sent pueden convertirse.
- **RN-2:** Una cotización solo puede convertirse una vez.
- **RN-3:** SO se crea en estado draft inicialmente.
- **RN-4:** Número secuencial: SO/{year}/{seq:04d}.
- **RN-5:** Todos los datos (líneas, impuestos, descuentos, totales) se copian exactamente.
- **RN-6:** Estado de cotización cambia a sale (no editable después).
- **RN-7:** Se crea mensaje en chatter: "Cotización convertida a SO/2024/NNNN".

---

## Tareas Técnicas

### Backend
- [ ] Endpoint: `POST /api/v1/sales/quotations/:id/convert-to-order`
- [ ] Service: `QuotationService.convertToSalesOrder(id)`
- [ ] Service: `SalesOrderService.createFromQuotation(quotation)`
- [ ] Validar estado de cotización (draft o sent)
- [ ] Validar que no esté ya convertida
- [ ] Copiar header de cotización
- [ ] Copiar líneas con todos los campos
- [ ] Generar número secuencial SO
- [ ] Actualizar quotation.state = sale
- [ ] Crear mensaje en chatter
- [ ] Unit tests (>80%)
- [ ] Integration tests
- [ ] Swagger docs

### Frontend
- [ ] Botón: "Convert to Sales Order" en vista de cotización
- [ ] Confirmación modal antes de convertir
- [ ] Navegación automática a SO creada después de conversión
- [ ] API client: `quotationApi.convertToOrder(id)`
- [ ] Notificación toast de éxito
- [ ] Deshabilitar botón si ya está convertida
- [ ] Component tests
- [ ] E2E test: "should convert quotation to sales order"

### Database
- [ ] Tabla: `sales.orders` (id, company_id, quotation_id, number, customer_id, state, total)
- [ ] Tabla: `sales.order_lines` (id, order_id, product_id, quantity, price_unit, subtotal)
- [ ] FK: `sales.orders.quotation_id → sales.quotations.id`
- [ ] Índices: idx_orders_quotation_id, idx_orders_customer_id, idx_orders_state
- [ ] RLS policy: company_isolation_sales_orders

---

## Mockups / Wireframes

**Vista Cotización con botón Convertir:**
```
┌─────────────────────────────────────────────┐
│ Cotización QT/2024/0001     [sent]          │
├─────────────────────────────────────────────┤
│ Cliente: ABC Corp                           │
│ Total: $1,000.00                            │
│                                             │
│ [Líneas de productos...]                    │
├─────────────────────────────────────────────┤
│ [Edit] [Send Email] [Convert to SO] [Cancel]│
└─────────────────────────────────────────────┘
```

**Modal de Confirmación:**
```
┌─────────────────────────────────────────────┐
│ Confirmar Conversión                        │
├─────────────────────────────────────────────┤
│ ¿Desea convertir la cotización QT/2024/0001│
│ en una orden de venta?                      │
│                                             │
│ Esta acción:                                │
│ • Creará una nueva Sales Order              │
│ • Marcará la cotización como convertida     │
│ • No es reversible                          │
│                                             │
│ [Confirmar] [Cancelar]                      │
└─────────────────────────────────────────────┘
```

---

## Casos de Prueba

### Funcionales
1. **TC-001:** Convertir cotización sent exitosamente
2. **TC-002:** Convertir cotización draft exitosamente
3. **TC-003:** Error al convertir cotización ya convertida
4. **TC-004:** Error al convertir cotización cancelada
5. **TC-005:** Todas las líneas se copian correctamente
6. **TC-006:** Totales coinciden entre cotización y SO
7. **TC-007:** Número secuencial se genera correctamente
8. **TC-008:** quotation_id se vincula correctamente
9. **TC-009:** Estado de cotización cambia a sale
10. **TC-010:** Mensaje se crea en chatter

### No Funcionales
1. **Performance:** < 500ms para convertir cotización con 20 líneas
2. **Seguridad:** JWT + permiso sales_user

---

## Dependencias

- **US bloqueantes:**
  - US-MGN-007-001-001 (Crear Cotización)
- **Módulos requeridos:** MGN-003 (Partners), MGN-005 (Productos)

---

## Notas de Implementación

- Usar transacción para garantizar atomicidad (crear SO + actualizar quotation)
- Copiar campos: customer_id, payment_term_id, delivery_date, notes, etc.
- Validar que productos de las líneas sigan existiendo y activos
- Considerar copiar también adjuntos de la cotización
- Frontend: Mostrar toast con link a nueva SO creada

---

## Estimación Detallada

| Tarea | Horas |
|-------|-------|
| Backend | 2.5 |
| Frontend | 1.5 |
| Testing | 2 |
| Code Review | 1 |
| **TOTAL** | **7 horas = 5 SP** |

---

## Definition of Done

- [ ] Código implementado según ET
- [ ] Tests pasando (>80%)
- [ ] Code review aprobado
- [ ] Conversión funciona correctamente
- [ ] Validaciones aplicadas
- [ ] Número secuencial generado
- [ ] Estado de cotización actualizado
- [ ] Mensaje en chatter creado
- [ ] Swagger docs actualizado
- [ ] QA validado
- [ ] PO aprobado

---

## Referencias

- [RF-MGN-007-002](../../02-modelado/requerimientos-funcionales/mgn-007/RF-MGN-007-002-conversión-a-órdenes-de-venta.md)
- [ET Backend](../../02-modelado/especificaciones-tecnicas/backend/mgn-007/ET-BACKEND-MGN-007-002-conversion-ordenes.md)
- [ET Frontend](../../02-modelado/especificaciones-tecnicas/frontend/mgn-007/ET-FRONTEND-MGN-007-002-conversion-ordenes.md)
- [Traceability](../../02-modelado/trazabilidad/TRACEABILITY-MGN-007.yaml)
- [Schema](../../02-modelado/database-design/schemas/sales-schema-ddl.sql)
