# 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 |