# MODELO DE DOMINIO: Inventario y Compras

**Módulos:** MGN-005 (Inventario), MGN-006 (Compras)
**Fecha:** 2025-11-24
**Referencia Odoo:** stock, purchase
**Referencia Gamilit:** inventory_management, purchasing_management

---

## Diagrama de Entidades (Texto UML)

```
[Product]
  - id: UUID (PK)
  - tenant_id: UUID (FK)
  - name: String
  - code: String
  - barcode: String
  - product_type: ENUM (storable, consumable, service)
  - category_id: UUID (FK)
  - uom_id: UUID (FK)
  - cost_price: Decimal
  - sale_price: Decimal

  1 <----> * [StockMove]
  1 <----> * [PurchaseOrderLine]

[Warehouse]
  - id: UUID (PK)
  - tenant_id: UUID (FK)
  - company_id: UUID (FK)
  - name: String
  - code: String

  1 <----> * [Location]

[Location]
  - id: UUID (PK)
  - warehouse_id: UUID (FK)
  - name: String
  - parent_id: UUID (FK self)
  - location_type: ENUM (physical, virtual, supplier, customer)

  1 <----> * [StockQuant]
  1 <----> * [StockMove] (origen/destino)

[StockQuant]
  - id: UUID (PK)
  - product_id: UUID (FK)
  - location_id: UUID (FK)
  - quantity: Decimal
  - lot_id: UUID (FK)
  - cost: Decimal

[StockMove]
  - id: UUID (PK)
  - tenant_id: UUID (FK)
  - product_id: UUID (FK)
  - location_src_id: UUID (FK)
  - location_dest_id: UUID (FK)
  - quantity: Decimal
  - uom_id: UUID (FK)
  - status: ENUM (draft, confirmed, done, cancelled)
  - picking_id: UUID (FK)

[Picking]
  - id: UUID (PK)
  - tenant_id: UUID (FK)
  - company_id: UUID (FK)
  - name: String
  - picking_type: ENUM (incoming, outgoing, internal)
  - partner_id: UUID (FK)
  - date: Date
  - status: ENUM (draft, ready, done, cancelled)

  1 <----> * [StockMove]

[Lot]
  - id: UUID (PK)
  - product_id: UUID (FK)
  - name: String
  - lot_date: Date
  - expiration_date: Date

[PurchaseOrder]
  - id: UUID (PK)
  - tenant_id: UUID (FK)
  - company_id: UUID (FK)
  - partner_id: UUID (FK)
  - name: String
  - order_date: Date
  - expected_date: Date
  - status: ENUM (draft, confirmed, received, billed, cancelled)
  - amount_total: Decimal

  1 <----> * [PurchaseOrderLine]
  1 <----> * [Picking]

[PurchaseOrderLine]
  - id: UUID (PK)
  - order_id: UUID (FK)
  - product_id: UUID (FK)
  - description: String
  - quantity: Decimal
  - price_unit: Decimal
  - tax_ids: UUID[]
  - subtotal: Decimal
  - total: Decimal
  - analytic_account_id: UUID (FK)
```

## Entidades Principales

### 1. Product (Producto)
**Descripción:** Producto almacenable, consumible o servicio.

**Atributos:**
- `id`: UUID
- `name`: Nombre del producto
- `code`: Código interno
- `barcode`: Código de barras
- `product_type`: storable, consumable, service
- `category_id`: Categoría
- `uom_id`: Unidad de medida
- `cost_price`: Precio de costo
- `sale_price`: Precio de venta

**Relaciones:**
- 1 Product → N StockMoves
- 1 Product → N PurchaseOrderLines
- 1 Product → N StockQuants

**Patrón Odoo:** product.product + product.template
**Tipos:**
- storable: Productos físicos con inventario
- consumable: Productos que no se trackean (ej: tornillos)
- service: Servicios (no tienen stock)

### 2. Warehouse (Almacén)
**Descripción:** Almacén físico de la empresa.

**Atributos:**
- `id`: UUID
- `company_id`: Empresa propietaria
- `name`: Nombre del almacén
- `code`: Código (ej: "WH-MEX")

**Relaciones:**
- 1 Warehouse → N Locations

**Patrón Odoo:** stock.warehouse
**Almacenes típicos:**
- Almacén Principal
- Almacén de Tránsito
- Almacén de Obra (construcción)

### 3. Location (Ubicación)
**Descripción:** Ubicación física o virtual dentro de almacén.

**Atributos:**
- `id`: UUID
- `warehouse_id`: Almacén propietario
- `name`: Nombre de ubicación
- `parent_id`: Ubicación padre (jerarquía)
- `location_type`: physical, virtual, supplier, customer

**Relaciones:**
- 1 Location → N StockQuants
- 1 Location → N StockMoves (origen/destino)

**Patrón Odoo:** stock.location
**Tipos:**
- physical: Ubicaciones físicas
- virtual: Ubicaciones virtuales (proveedores, clientes, producción)
- supplier: Stock de proveedores
- customer: Stock de clientes

### 4. StockQuant (Cantidad en Stock)
**Descripción:** Cantidad de producto en ubicación específica.

**Atributos:**
- `id`: UUID
- `product_id`: Producto
- `location_id`: Ubicación
- `quantity`: Cantidad disponible
- `lot_id`: Lote (opcional)
- `cost`: Costo unitario

**Relaciones:**
- N StockQuants → 1 Product
- N StockQuants → 1 Location

**Patrón Odoo:** stock.quant
**Nota:** Se actualiza automáticamente con cada movimiento

### 5. StockMove (Movimiento de Inventario)
**Descripción:** Movimiento de stock de una ubicación a otra.

**Atributos:**
- `id`: UUID
- `product_id`: Producto movido
- `location_src_id`: Ubicación origen
- `location_dest_id`: Ubicación destino
- `quantity`: Cantidad movida
- `status`: draft, confirmed, done, cancelled
- `picking_id`: Picking asociado

**Relaciones:**
- N StockMoves → 1 Product
- N StockMoves → 1 Picking

**Patrón Odoo:** stock.move
**Flujo:** draft → confirmed → done

### 6. Picking (Albarán)
**Descripción:** Agrupación de movimientos de stock.

**Atributos:**
- `id`: UUID
- `name`: Número de albarán
- `picking_type`: incoming (recepción), outgoing (entrega), internal (transferencia)
- `partner_id`: Partner asociado
- `date`: Fecha
- `status`: draft, ready, done, cancelled

**Relaciones:**
- 1 Picking → N StockMoves

**Patrón Odoo:** stock.picking
**Tipos:**
- incoming: Recepción de proveedores
- outgoing: Entrega a clientes
- internal: Transferencias internas

### 7. Lot (Lote)
**Descripción:** Lote de producción o número de serie.

**Atributos:**
- `id`: UUID
- `product_id`: Producto
- `name`: Número de lote/serie
- `lot_date`: Fecha de producción
- `expiration_date`: Fecha de caducidad

**Relaciones:**
- N Lots → 1 Product

**Patrón Odoo:** stock.production.lot
**Uso:** Trazabilidad completa

### 8. PurchaseOrder (Orden de Compra)
**Descripción:** Orden de compra a proveedor.

**Atributos:**
- `id`: UUID
- `partner_id`: Proveedor
- `name`: Número de orden
- `order_date`: Fecha de orden
- `expected_date`: Fecha esperada de entrega
- `status`: draft, confirmed, received, billed, cancelled
- `amount_total`: Monto total

**Relaciones:**
- 1 PurchaseOrder → N PurchaseOrderLines
- 1 PurchaseOrder → N Pickings (recepciones)

**Patrón Odoo:** purchase.order
**Flujo:** draft → confirmed → received → billed

### 9. PurchaseOrderLine (Línea de Orden de Compra)
**Descripción:** Línea de producto en orden de compra.

**Atributos:**
- `id`: UUID
- `order_id`: Orden propietaria
- `product_id`: Producto
- `description`: Descripción
- `quantity`: Cantidad ordenada
- `price_unit`: Precio unitario
- `tax_ids`: Impuestos
- `analytic_account_id`: Cuenta analítica (opcional)

**Relaciones:**
- N PurchaseOrderLines → 1 PurchaseOrder

**Patrón Odoo:** purchase.order.line

## Reglas de Negocio

### RN-INV-001: Movimientos Atómicos
- TODO movimiento de stock debe tener origen y destino
- Cantidad debe ser > 0
- Producto debe ser tipo 'storable' para afectar stock

### RN-INV-002: Actualización Automática de Quants
- Al confirmar StockMove (status=done), actualizar StockQuants
- Decrementar en location_src
- Incrementar en location_dest

### RN-INV-003: Valoración de Inventario
- Método FIFO por defecto
- Calcular costo promedio automáticamente
- Generar asientos contables si valoración automática activa

### RN-INV-004: Trazabilidad
- Productos con tracking='lot' requieren lot_id en movimientos
- Productos con tracking='serial' requieren quantity=1 por movimiento

### RN-INV-005: Integración Compras-Inventario
- Al confirmar PO, generar Picking automáticamente
- Al validar Picking, marcar PO como 'received'

### RN-INV-006: 3-Way Match
- Validar: PO quantity = Picking quantity = Invoice quantity
- Permitir tolerancias configurables (ej: +/- 5%)

## Casos de Uso Principales

1. **UC-INV-001:** Usuario crea producto almacenable
2. **UC-INV-002:** Usuario crea almacén con ubicaciones
3. **UC-INV-003:** Usuario realiza transferencia interna
4. **UC-INV-004:** Usuario crea orden de compra
5. **UC-INV-005:** Sistema genera picking al confirmar PO
6. **UC-INV-006:** Usuario valida recepción de productos
7. **UC-INV-007:** Sistema actualiza stock automáticamente
8. **UC-INV-008:** Usuario consulta inventario por ubicación

## Validaciones y Constraints

```sql
-- Product code único por tenant
UNIQUE (tenant_id, code)

-- Cantidad > 0 en movimientos
CHECK (quantity > 0)

-- Ubicaciones no pueden ser su propio padre
CHECK (parent_id != id)

-- Status transitions válidos
-- (state machine)
```

## Índices Requeridos

```sql
CREATE INDEX idx_products_code ON inventory.products(code);
CREATE INDEX idx_products_barcode ON inventory.products(barcode);
CREATE INDEX idx_stock_quants_product_id ON inventory.stock_quants(product_id);
CREATE INDEX idx_stock_quants_location_id ON inventory.stock_quants(location_id);
CREATE INDEX idx_stock_moves_product_id ON inventory.stock_moves(product_id);
CREATE INDEX idx_stock_moves_picking_id ON inventory.stock_moves(picking_id);
CREATE INDEX idx_purchase_orders_partner_id ON purchase.purchase_orders(partner_id);
CREATE INDEX idx_purchase_orders_status ON purchase.purchase_orders(status);
```

## Referencias
- [ALCANCE-POR-MODULO.md - MGN-005](../../01-definicion-modulos/ALCANCE-POR-MODULO.md#mgn-005)
- [ALCANCE-POR-MODULO.md - MGN-006](../../01-definicion-modulos/ALCANCE-POR-MODULO.md#mgn-006)
- [ADR-007: Database Design](../../adr/ADR-007-database-design.md)
