# MODELO DE DOMINIO: Ventas

**Módulos:** MGN-007 (Ventas Básico)
**Fecha:** 2025-11-24
**Referencia Odoo:** sale
**Referencia Gamilit:** sales_management schema

---

## Diagrama de Entidades (Texto UML)

```
[SaleOrder]
  - id: UUID (PK)
  - tenant_id: UUID (FK)
  - company_id: UUID (FK)
  - partner_id: UUID (FK)
  - name: String
  - order_date: Date
  - validity_date: Date
  - expected_date: Date
  - status: ENUM (draft, sent, sale, done, cancelled)
  - amount_total: Decimal
  - amount_invoiced: Decimal
  - invoice_status: ENUM (to_invoice, invoiced)
  - delivery_status: ENUM (to_deliver, delivered)

  1 <----> * [SaleOrderLine]
  1 <----> * [Invoice]
  1 <----> * [Picking]

[SaleOrderLine]
  - id: UUID (PK)
  - order_id: UUID (FK)
  - product_id: UUID (FK)
  - description: String
  - quantity: Decimal
  - price_unit: Decimal
  - discount: Decimal
  - tax_ids: UUID[]
  - subtotal: Decimal
  - total: Decimal
  - analytic_account_id: UUID (FK)
  - qty_delivered: Decimal
  - qty_invoiced: Decimal

[Quotation]
  - id: UUID (PK)
  - tenant_id: UUID (FK)
  - company_id: UUID (FK)
  - partner_id: UUID (FK)
  - name: String
  - date: Date
  - validity_date: Date
  - status: ENUM (draft, sent, approved, rejected, converted)
  - amount_total: Decimal
  - terms_conditions: Text
  - signature: String (base64)
  - signature_date: Timestamp

  1 <----> * [SaleOrder] (conversión)

[PaymentTerm]
  - id: UUID (PK)
  - tenant_id: UUID (FK)
  - name: String
  - days: Integer
  - description: Text

[PriceList]
  - id: UUID (PK)
  - tenant_id: UUID (FK)
  - name: String
  - currency_id: UUID (FK)
  - active: Boolean

  1 <----> * [PriceListItem]

[PriceListItem]
  - id: UUID (PK)
  - pricelist_id: UUID (FK)
  - product_id: UUID (FK)
  - price: Decimal
  - min_quantity: Decimal
```

## Entidades Principales

### 1. SaleOrder (Orden de Venta)
**Descripción:** Orden de venta confirmada a cliente.

**Atributos:**
- `id`: UUID
- `partner_id`: Cliente
- `name`: Número de orden (ej: "SO001")
- `order_date`: Fecha de orden
- `validity_date`: Fecha de validez (opcional)
- `expected_date`: Fecha esperada de entrega
- `status`: draft, sent, sale, done, cancelled
- `amount_total`: Monto total
- `invoice_status`: to_invoice, invoiced
- `delivery_status`: to_deliver, delivered

**Relaciones:**
- 1 SaleOrder → N SaleOrderLines
- 1 SaleOrder → N Invoices
- 1 SaleOrder → N Pickings

**Patrón Odoo:** sale.order
**Estados:**
- draft: Borrador (editable)
- sent: Enviada a cliente
- sale: Confirmada (genera picking)
- done: Completada (entregada y facturada)
- cancelled: Cancelada

### 2. SaleOrderLine (Línea de Orden de Venta)
**Descripción:** Línea de producto/servicio en orden de venta.

**Atributos:**
- `id`: UUID
- `order_id`: Orden propietaria
- `product_id`: Producto
- `description`: Descripción
- `quantity`: Cantidad ordenada
- `price_unit`: Precio unitario
- `discount`: Descuento (%)
- `tax_ids`: Impuestos aplicados
- `subtotal`: Subtotal sin impuestos
- `total`: Total con impuestos
- `qty_delivered`: Cantidad entregada
- `qty_invoiced`: Cantidad facturada

**Relaciones:**
- N SaleOrderLines → 1 SaleOrder

**Patrón Odoo:** sale.order.line
**Cálculo:** total = ((quantity * price_unit) * (1 - discount/100)) + taxes

### 3. Quotation (Cotización)
**Descripción:** Cotización enviada a cliente (pre-orden).

**Atributos:**
- `id`: UUID
- `partner_id`: Cliente potencial
- `name`: Número de cotización
- `date`: Fecha de emisión
- `validity_date`: Fecha de expiración
- `status`: draft, sent, approved, rejected, converted
- `amount_total`: Monto total
- `terms_conditions`: Términos y condiciones
- `signature`: Firma electrónica (base64)
- `signature_date`: Fecha/hora de firma

**Relaciones:**
- 1 Quotation → 1 SaleOrder (conversión)

**Patrón Odoo:** sale.order (state=draft/sent)
**Flujo:**
1. Crear quotation
2. Enviar a cliente (sent)
3. Cliente aprueba/firma (approved)
4. Convertir a sale order (converted)

### 4. PaymentTerm (Término de Pago)
**Descripción:** Condiciones de pago para cliente.

**Atributos:**
- `id`: UUID
- `name`: Nombre (ej: "30 días")
- `days`: Días de plazo
- `description`: Descripción

**Relaciones:**
- 1 PaymentTerm → N SaleOrders

**Patrón Odoo:** account.payment.term
**Términos comunes:**
- Inmediato (0 días)
- 15 días
- 30 días
- 60 días

### 5. PriceList (Lista de Precios)
**Descripción:** Lista de precios por cliente o segmento.

**Atributos:**
- `id`: UUID
- `name`: Nombre (ej: "Precio Distribuidor")
- `currency_id`: Moneda
- `active`: Activa/Inactiva

**Relaciones:**
- 1 PriceList → N PriceListItems
- 1 PriceList → N Partners

**Patrón Odoo:** product.pricelist
**Uso:** Precios diferenciados por tipo de cliente

### 6. PriceListItem (Item de Lista de Precios)
**Descripción:** Precio específico de producto en lista.

**Atributos:**
- `id`: UUID
- `pricelist_id`: Lista propietaria
- `product_id`: Producto
- `price`: Precio
- `min_quantity`: Cantidad mínima

**Relaciones:**
- N PriceListItems → 1 PriceList

**Patrón Odoo:** product.pricelist.item

## Reglas de Negocio

### RN-SAL-001: Flujo de Estados
- Orden solo puede confirmarse (sale) desde draft o sent
- Una vez en 'sale', no puede volver a draft
- Cancelación posible desde cualquier estado excepto 'done'

### RN-SAL-002: Generación Automática de Picking
- Al confirmar orden (status=sale), generar Picking de entrega
- Picking tipo 'outgoing'
- Solo para productos tipo 'storable'

### RN-SAL-003: Facturación
- Facturar después de entrega (delivery → invoice)
- Facturar antes de entrega (invoice → delivery)
- Facturación parcial permitida

### RN-SAL-004: Control de Cantidades
- qty_delivered: actualizada desde Picking
- qty_invoiced: actualizada desde Invoice
- delivery_status: 'delivered' cuando qty_delivered = quantity
- invoice_status: 'invoiced' cuando qty_invoiced = quantity

### RN-SAL-005: Cotización con Firma
- Cotización aprobada requiere firma electrónica
- Firma incluye: canvas signature, timestamp, IP, user_agent
- Firma es inmutable

### RN-SAL-006: Validez de Cotización
- Cotización con validity_date vencida no puede confirmarse
- Sistema alerta si cotización está por vencer

### RN-SAL-007: Descuentos
- Descuento por línea (0-100%)
- Descuento aplicado antes de impuestos
- Subtotal = (quantity * price_unit) * (1 - discount/100)

## Casos de Uso Principales

1. **UC-SAL-001:** Usuario crea cotización y envía a cliente
2. **UC-SAL-002:** Cliente aprueba cotización online (portal)
3. **UC-SAL-003:** Cliente firma cotización electrónicamente
4. **UC-SAL-004:** Usuario convierte cotización a orden de venta
5. **UC-SAL-005:** Sistema genera picking al confirmar orden
6. **UC-SAL-006:** Usuario valida entrega de productos
7. **UC-SAL-007:** Usuario crea factura desde orden
8. **UC-SAL-008:** Usuario consulta reporte de ventas por cliente

## Validaciones y Constraints

```sql
-- Sale order name único por company
UNIQUE (tenant_id, company_id, name)

-- Cantidades > 0
CHECK (quantity > 0)
CHECK (price_unit >= 0)

-- Descuento entre 0 y 100
CHECK (discount >= 0 AND discount <= 100)

-- qty_delivered <= quantity
CHECK (qty_delivered <= quantity)

-- qty_invoiced <= quantity
CHECK (qty_invoiced <= quantity)

-- validity_date >= date (cotización)
CHECK (validity_date >= date)
```

## Índices Requeridos

```sql
CREATE INDEX idx_sale_orders_partner_id ON sales.sale_orders(partner_id);
CREATE INDEX idx_sale_orders_status ON sales.sale_orders(status);
CREATE INDEX idx_sale_orders_order_date ON sales.sale_orders(order_date);
CREATE INDEX idx_sale_order_lines_order_id ON sales.sale_order_lines(order_id);
CREATE INDEX idx_sale_order_lines_product_id ON sales.sale_order_lines(product_id);
CREATE INDEX idx_quotations_partner_id ON sales.quotations(partner_id);
CREATE INDEX idx_quotations_status ON sales.quotations(status);
CREATE INDEX idx_quotations_validity_date ON sales.quotations(validity_date);
```

## Integración con Otros Módulos

### Con MGN-005 (Inventario)
- Confirmar SO → genera Picking (outgoing)
- Validar Picking → actualiza qty_delivered

### Con MGN-004 (Financiero)
- Crear factura desde SO → copia líneas de SO
- Validar factura → actualiza qty_invoiced

### Con MGN-008 (Analítica)
- Líneas de SO pueden tener analytic_account_id
- Al facturar, genera líneas analíticas

### Con MGN-009 (CRM)
- Lead convertido → crea Quotation
- Quotation vinculada a Opportunity

### Con MGN-013 (Portal)
- Cliente ve quotations en portal
- Cliente aprueba/firma desde portal

## Referencias
- [ALCANCE-POR-MODULO.md - MGN-007](../../01-definicion-modulos/ALCANCE-POR-MODULO.md#mgn-007)
- [ADR-007: Database Design](../../adr/ADR-007-database-design.md)
- [odoo-sale-analysis.md](../../00-analisis-referencias/odoo/odoo-sale-analysis.md)
