# RF-CATALOG-003: Monedas y Tasas de Cambio

## Identificacion

| Campo | Valor |
|-------|-------|
| **ID** | RF-CATALOG-003 |
| **Modulo** | MGN-005 Catalogs |
| **Titulo** | Monedas y Tasas de Cambio |
| **Prioridad** | Alta |
| **Estado** | Draft |
| **Fecha** | 2025-12-05 |

---

## Descripcion

El sistema debe proporcionar un catalogo de monedas (global) y tasas de cambio (por tenant) para soportar operaciones multi-moneda.

**Referencia Odoo:**
- `res.currency` (odoo/addons/base/models/res_currency.py)
- `res.currency.rate` (mismo archivo)

---

## Arquitectura de Monedas

### Catalogo Global vs Por Tenant

| Tabla | Scope | Descripcion |
|-------|-------|-------------|
| `core_catalogs.currencies` | Global | Lista ISO 4217 (sin tenant_id) |
| `core_catalogs.currency_rates` | Por Tenant | Tasas historicas (con tenant_id) |

**Rationale:**
- Las monedas son estandares ISO, compartidas globalmente
- Las tasas de cambio varian por tenant (cada uno puede usar diferentes fuentes)

---

## Requisitos Funcionales

### RF-CATALOG-003.1: Catalogo de Monedas (Global)

| Campo | Tipo | Descripcion | Ejemplo |
|-------|------|-------------|---------|
| id | UUID | Identificador unico | auto |
| name | char(3) | Codigo ISO 4217 | "MXN" |
| full_name | string | Nombre completo | "Mexican Peso" |
| symbol | string | Simbolo | "$" |
| iso_numeric | int | Codigo numerico ISO | 484 |
| decimal_places | int | Decimales | 2 |
| rounding | decimal | Factor de redondeo | 0.01 |
| position | enum | Posicion del simbolo | before/after |
| is_active | boolean | Disponible para uso | true |

**Datos:**
- ~180 monedas segun ISO 4217
- Precargadas en migracion inicial

### RF-CATALOG-003.2: Tasas de Cambio (Por Tenant)

Cada tenant mantiene su historial de tasas:

| Campo | Tipo | Descripcion |
|-------|------|-------------|
| id | UUID | Identificador unico |
| tenant_id | FK | Tenant propietario |
| currency_id | FK | Moneda (global) |
| rate | decimal(20,10) | Tasa vs moneda base |
| inverse_rate | computed | 1/rate |
| name | date | Fecha de la tasa |
| created_at | timestamp | Fecha creacion |
| created_by | FK | Usuario que registro |

**Reglas:**
- La moneda base del tenant tiene tasa = 1.0
- Tasas se expresan como: 1 BASE = X MONEDA
- Se mantiene historial completo

### RF-CATALOG-003.3: Moneda Base del Tenant

Cada tenant tiene una moneda base definida en `tenant_settings`:

```json
{
  "regional": {
    "defaultCurrency": "MXN",
    "currencyRateDate": "current"  // current | invoice_date
  }
}
```

**Reglas:**
- La moneda base NO puede cambiarse una vez hay transacciones
- Todas las tasas se calculan contra la moneda base

### RF-CATALOG-003.4: Conversion de Monedas

El sistema debe proporcionar funciones de conversion:

```typescript
// Convertir monto de una moneda a otra
convertAmount(
  amount: number,
  fromCurrency: UUID,
  toCurrency: UUID,
  date?: Date  // Default: hoy
): number

// Obtener tasa de cambio
getRate(
  fromCurrency: UUID,
  toCurrency: UUID,
  date?: Date
): number
```

**Logica de conversion:**
```
amount_to = amount_from * (rate_to / rate_from)
```

### RF-CATALOG-003.5: Redondeo de Monedas

Cada moneda define su precision:

```typescript
// Redondear segun precision de moneda
roundCurrency(amount: number, currency: Currency): number

// Ejemplo MXN (rounding = 0.01):
roundCurrency(123.456, MXN) // => 123.46
```

### RF-CATALOG-003.6: Actualizacion Automatica de Tasas

El sistema puede integrarse con APIs de tasas:

| Proveedor | Endpoint | Frecuencia |
|-----------|----------|------------|
| Open Exchange Rates | api.openexchangerates.org | Diaria |
| Fixer.io | data.fixer.io | Diaria |
| Banco de Mexico | banxico.org.mx | Diaria |
| ECB | ecb.europa.eu | Diaria |

**Configuracion por tenant:**
```json
{
  "regional": {
    "currencyRateProvider": "banxico",
    "currencyRateAutoUpdate": true
  }
}
```

---

## Operaciones

### Listar Monedas (Global)

```typescript
GET /api/v1/catalogs/currencies?active=true

Response:
{
  "data": [
    {
      "id": "uuid",
      "name": "MXN",
      "fullName": "Mexican Peso",
      "symbol": "$",
      "decimalPlaces": 2,
      "position": "before"
    },
    ...
  ]
}
```

### Obtener Tasas de un Tenant

```typescript
GET /api/v1/currencies/rates?currency=MXN&from=2025-01-01&to=2025-01-31

Response:
{
  "data": [
    { "date": "2025-01-01", "rate": 17.2345 },
    { "date": "2025-01-02", "rate": 17.3021 },
    ...
  ]
}
```

### Registrar Tasa Manual

```typescript
POST /api/v1/currencies/rates
{
  "currencyId": "uuid-usd",
  "rate": 17.5432,
  "date": "2025-12-05"
}
```

### Convertir Monto

```typescript
GET /api/v1/currencies/convert?from=USD&to=MXN&amount=100&date=2025-12-05

Response:
{
  "from": { "currency": "USD", "amount": 100 },
  "to": { "currency": "MXN", "amount": 1754.32 },
  "rate": 17.5432,
  "date": "2025-12-05"
}
```

---

## Reglas de Negocio

| ID | Regla | Severidad |
|----|-------|-----------|
| BR-001 | Moneda base tasa = 1.0 siempre | Error |
| BR-002 | Tasa debe ser > 0 | Error |
| BR-003 | Una tasa por moneda por fecha | Error |
| BR-004 | No eliminar moneda con transacciones | Error |
| BR-005 | Moneda inactiva no seleccionable | Warning |

---

## Casos de Prueba

| ID | Escenario | Resultado Esperado |
|----|-----------|-------------------|
| TC-001 | Listar monedas activas | ~30 monedas comunes |
| TC-002 | Registrar tasa USD | Tasa guardada |
| TC-003 | Registrar tasa duplicada (misma fecha) | Error |
| TC-004 | Convertir 100 USD a MXN | Monto convertido |
| TC-005 | Convertir sin tasa del dia | Usar ultima tasa |
| TC-006 | Redondear MXN (2 decimales) | Precision correcta |
| TC-007 | Actualizar tasas automaticas | Tasas actualizadas |

---

## Criterios de Aceptacion

- [ ] ~180 monedas ISO 4217 cargadas
- [ ] Registro de tasas por tenant
- [ ] Conversion bidireccional
- [ ] Redondeo segun moneda
- [ ] Historial de tasas
- [ ] Integracion con proveedor externo (opcional)

---

## Notas Tecnicas

### Precision de Tasas

```sql
-- Alta precision para tasas
rate DECIMAL(20, 10) NOT NULL
```

**Rationale:** Algunas monedas tienen tasas muy pequenas (ej: 1 BTC = 0.000012 USD inverso)

### Funcion de Conversion SQL

```sql
CREATE OR REPLACE FUNCTION core_catalogs.convert_currency(
    p_amount DECIMAL,
    p_from_currency UUID,
    p_to_currency UUID,
    p_tenant_id UUID,
    p_date DATE DEFAULT CURRENT_DATE
) RETURNS DECIMAL AS $$
DECLARE
    v_from_rate DECIMAL;
    v_to_rate DECIMAL;
BEGIN
    -- Obtener tasa de origen
    SELECT rate INTO v_from_rate
    FROM core_catalogs.currency_rates
    WHERE tenant_id = p_tenant_id
      AND currency_id = p_from_currency
      AND name <= p_date
    ORDER BY name DESC LIMIT 1;

    -- Obtener tasa de destino
    SELECT rate INTO v_to_rate
    FROM core_catalogs.currency_rates
    WHERE tenant_id = p_tenant_id
      AND currency_id = p_to_currency
      AND name <= p_date
    ORDER BY name DESC LIMIT 1;

    -- Convertir
    RETURN p_amount * (COALESCE(v_to_rate, 1) / COALESCE(v_from_rate, 1));
END;
$$ LANGUAGE plpgsql STABLE;
```

### Cache de Tasas

- Tasas del dia: Cache 1 hora
- Tasas historicas: Cache 24 horas
- Invalidacion: Al registrar nueva tasa

---

## Historial

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