# RF-CATALOG-004: Unidades de Medida (UoM)

## Identificacion

| Campo | Valor |
|-------|-------|
| **ID** | RF-CATALOG-004 |
| **Modulo** | MGN-005 Catalogs |
| **Titulo** | Unidades de Medida (UoM) |
| **Prioridad** | Media |
| **Estado** | Draft |
| **Fecha** | 2025-12-05 |

---

## Descripcion

El sistema debe permitir gestionar unidades de medida agrupadas por categorias, con factores de conversion entre unidades de la misma categoria.

**Referencia Odoo:**
- `uom.category` (addons/uom/models/uom_uom.py)
- `uom.uom` (mismo archivo)

---

## Arquitectura

### Por Tenant

A diferencia de paises y monedas, las unidades de medida son **por tenant**:

```sql
core_catalogs.uom_categories (tenant_id UUID NOT NULL)
core_catalogs.uom (tenant_id UUID NOT NULL)
```

**Rationale:** Cada tenant puede necesitar unidades personalizadas segun su industria.

---

## Requisitos Funcionales

### RF-CATALOG-004.1: Categorias de UoM

Las unidades se agrupan en categorias. Solo se pueden convertir unidades de la misma categoria.

| Campo | Tipo | Descripcion |
|-------|------|-------------|
| id | UUID | Identificador unico |
| tenant_id | FK | Tenant propietario |
| name | string | Nombre de categoria |
| is_active | boolean | Disponible para uso |

**Categorias por defecto (seed):**

| Categoria | Descripcion |
|-----------|-------------|
| Unit | Unidades discretas (piezas, docenas) |
| Weight | Peso (kg, g, lb, oz) |
| Volume | Volumen (L, mL, gal) |
| Length | Longitud (m, cm, ft, in) |
| Time | Tiempo (hora, dia, semana) |
| Area | Area (m2, ha, acre) |

### RF-CATALOG-004.2: Unidades de Medida

| Campo | Tipo | Descripcion |
|-------|------|-------------|
| id | UUID | Identificador unico |
| tenant_id | FK | Tenant propietario |
| category_id | FK | Categoria de UoM |
| name | string | Nombre de unidad |
| uom_type | enum | reference/bigger/smaller |
| factor | decimal | Factor de conversion |
| rounding | decimal | Precision de redondeo |
| is_active | boolean | Disponible para uso |

### RF-CATALOG-004.3: Tipos de Unidad

Cada categoria tiene exactamente UNA unidad de referencia:

| Tipo | Descripcion | Factor |
|------|-------------|--------|
| `reference` | Unidad base de la categoria | 1.0 |
| `bigger` | Mayor que la referencia | < 1.0 |
| `smaller` | Menor que la referencia | > 1.0 |

**Ejemplo categoria "Weight":**

| Unidad | Tipo | Factor | Equivalencia |
|--------|------|--------|--------------|
| kg | reference | 1.0 | 1 kg = 1 kg |
| g | smaller | 1000 | 1 kg = 1000 g |
| lb | bigger | 0.453592 | 1 lb = 0.453592 kg |
| oz | smaller | 35.274 | 1 kg = 35.274 oz |

### RF-CATALOG-004.4: Conversion de Unidades

El sistema debe proporcionar funciones de conversion:

```typescript
// Convertir cantidad de una unidad a otra
convertUom(
  quantity: number,
  fromUom: UUID,
  toUom: UUID
): number

// Redondear segun unidad
roundUom(quantity: number, uom: UUID): number
```

**Formula de conversion:**
```
quantity_to = quantity_from * (factor_from / factor_to)
```

**Validacion:** Solo se permite conversion entre unidades de la misma categoria.

### RF-CATALOG-004.5: Constraints de Categoria

| Constraint | Descripcion |
|------------|-------------|
| Una referencia | Exactamente 1 unidad `reference` por categoria |
| Factor referencia = 1 | La unidad reference debe tener factor = 1.0 |
| Factor > 0 | El factor de conversion debe ser positivo |

### RF-CATALOG-004.6: Unidades Protegidas

Algunas unidades del seed no deben modificarse:

```sql
-- Unidades protegidas
is_protected BOOLEAN DEFAULT false
```

| Unidad | Categoria | Protegida | Razon |
|--------|-----------|-----------|-------|
| pcs | Unit | Si | Referencia universal |
| kg | Weight | Si | SI standard |
| L | Volume | Si | SI standard |
| m | Length | Si | SI standard |
| hr | Time | Si | Referencia de tiempo |

---

## Operaciones CRUD

### Listar Categorias

```typescript
GET /api/v1/catalogs/uom-categories

Response:
{
  "data": [
    { "id": "uuid", "name": "Unit", "uomCount": 3 },
    { "id": "uuid", "name": "Weight", "uomCount": 5 },
    ...
  ]
}
```

### Listar Unidades por Categoria

```typescript
GET /api/v1/catalogs/uom?categoryId=uuid-weight

Response:
{
  "data": [
    {
      "id": "uuid",
      "name": "kg",
      "uomType": "reference",
      "factor": 1.0,
      "rounding": 0.001
    },
    {
      "id": "uuid",
      "name": "g",
      "uomType": "smaller",
      "factor": 1000,
      "rounding": 1
    }
  ]
}
```

### Crear Unidad

```typescript
POST /api/v1/catalogs/uom
{
  "categoryId": "uuid-weight",
  "name": "Tonelada",
  "uomType": "bigger",
  "factor": 0.001,  // 1 ton = 0.001 kg^-1 = 1000 kg
  "rounding": 0.001
}
```

### Convertir Cantidad

```typescript
GET /api/v1/catalogs/uom/convert?from=uuid-kg&to=uuid-lb&quantity=10

Response:
{
  "from": { "uom": "kg", "quantity": 10 },
  "to": { "uom": "lb", "quantity": 22.0462 },
  "factor": 2.20462
}
```

---

## Data Seed

### Categoria: Unit

| Nombre | Tipo | Factor | Rounding |
|--------|------|--------|----------|
| pcs (Pieces) | reference | 1 | 1 |
| dozen | bigger | 0.0833 | 1 |
| pair | bigger | 0.5 | 1 |

### Categoria: Weight

| Nombre | Tipo | Factor | Rounding |
|--------|------|--------|----------|
| kg | reference | 1 | 0.001 |
| g | smaller | 1000 | 1 |
| lb | bigger | 0.453592 | 0.01 |
| oz | smaller | 35.274 | 0.01 |
| ton | bigger | 0.001 | 0.001 |

### Categoria: Volume

| Nombre | Tipo | Factor | Rounding |
|--------|------|--------|----------|
| L | reference | 1 | 0.001 |
| mL | smaller | 1000 | 1 |
| gal (US) | bigger | 0.264172 | 0.001 |
| m3 | bigger | 0.001 | 0.001 |

### Categoria: Length

| Nombre | Tipo | Factor | Rounding |
|--------|------|--------|----------|
| m | reference | 1 | 0.001 |
| cm | smaller | 100 | 1 |
| ft | bigger | 0.3048 | 0.01 |
| in | smaller | 39.3701 | 0.1 |

### Categoria: Time

| Nombre | Tipo | Factor | Rounding |
|--------|------|--------|----------|
| hour | reference | 1 | 0.01 |
| day | bigger | 0.0417 | 0.01 |
| week | bigger | 0.00595 | 0.01 |

---

## Reglas de Negocio

| ID | Regla | Severidad |
|----|-------|-----------|
| BR-001 | Una unidad reference por categoria | Error |
| BR-002 | Reference factor = 1.0 | Error |
| BR-003 | Factor > 0 | Error |
| BR-004 | Solo convertir misma categoria | Error |
| BR-005 | No eliminar unidad con productos | Error |
| BR-006 | No modificar unidades protegidas | Error |

---

## Casos de Prueba

| ID | Escenario | Resultado Esperado |
|----|-----------|-------------------|
| TC-001 | Crear categoria nueva | Categoria creada |
| TC-002 | Crear unidad reference | Factor = 1.0 validado |
| TC-003 | Crear segunda reference | Error: ya existe |
| TC-004 | Convertir kg a lb | 10 kg = 22.0462 lb |
| TC-005 | Convertir kg a L (diferente categoria) | Error |
| TC-006 | Redondear 10.12345 kg (rounding=0.001) | 10.123 |
| TC-007 | Eliminar unidad con productos | Error |

---

## Criterios de Aceptacion

- [ ] Categorias CRUD funcional
- [ ] Unidades CRUD funcional
- [ ] Constraint de una reference
- [ ] Conversion dentro de categoria
- [ ] Redondeo segun precision
- [ ] Seed de unidades comunes
- [ ] Proteccion de unidades base

---

## Notas Tecnicas

### Funcion de Conversion SQL

```sql
CREATE OR REPLACE FUNCTION core_catalogs.convert_uom(
    p_quantity DECIMAL,
    p_from_uom UUID,
    p_to_uom UUID
) RETURNS DECIMAL AS $$
DECLARE
    v_from_factor DECIMAL;
    v_to_factor DECIMAL;
    v_from_category UUID;
    v_to_category UUID;
BEGIN
    -- Obtener datos de origen
    SELECT factor, category_id INTO v_from_factor, v_from_category
    FROM core_catalogs.uom WHERE id = p_from_uom;

    -- Obtener datos de destino
    SELECT factor, category_id INTO v_to_factor, v_to_category
    FROM core_catalogs.uom WHERE id = p_to_uom;

    -- Validar misma categoria
    IF v_from_category != v_to_category THEN
        RAISE EXCEPTION 'Cannot convert between different UoM categories';
    END IF;

    -- Convertir
    RETURN p_quantity * (v_from_factor / v_to_factor);
END;
$$ LANGUAGE plpgsql STABLE;
```

---

## Historial

| Version | Fecha | Autor | Cambios |
|---------|-------|-------|---------|
| 1.0 | 2025-12-05 | System | Creacion inicial |
