# BCV Exchange Rate Plugin for iDempiere v10 ## Descripción Plugin OSGi que obtiene automáticamente la tasa de cambio oficial del BCV (Banco Central de Venezuela) USD/VES y la registra en la tabla C_Conversion_Rate de iDempiere. ## Características - **Doble fuente de datos**: API bcv.today + scraping directo del sitio web del BCV - **Fallback automático**: Si la API no tiene datos recientes, scrapea el sitio del BCV - **Reintentos automáticos**: Hasta 3 intentos con espera de 5 segundos entre ellos - **Timeout extendido**: 30s conexión / 60s lectura para redes lentas - **Busca hacia atrás** cuando el BCV no publica tasa para un día hábil - **Rellena automáticamente** días sin publicación (fines de semana, feriados) - **Ejecución manual** con rango de fechas específico - **Ejecución automática** que detecta días pendientes y los rellena - **Valida duplicados** antes de insertar - **Modo simulación** para pruebas sin guardar - **Scheduler automático** diario ## Fuentes de Datos ### Flujo de Selección ``` 1. Intentar API bcv.today (búsqueda hacia adelante 1-10 días) ↓ (si no encuentra) 2. Scraping directo del sitio web del BCV (hasta 3 reintentos) ↓ (si falla) 3. API bcv.today getCurrentRate() (último recurso) ``` ### Fuente Principal: API bcv.today - **Endpoint**: `https://bcv.today/api/v1/rate.json` - **Endpoint por fecha**: `https://bcv.today/api/v1/history/YYYY-MM-DD.json` - **Ventaja**: Rápida, estructurada - **Desventaja**: Puede tener retraso de actualización ### Fuente de Respaldo: Scraping BCV - **URL**: `https://www.bcv.org.ve/` - **Método**: Extracción directa del HTML con regex - **Ventaja**: Siempre actualizada (datos oficiales) - **Desventaja**: Requiere SSL bypass y parsing de HTML - **Reintentos**: 3 máximo con espera de 5 segundos ## Configuración de Red ### Timeouts | Operación | Valor | Descripción | |-----------|-------|-------------| | Conexión | 30 segundos | Tiempo máximo para establecer conexión | | Lectura | 60 segundos | Tiempo máximo para recibir datos | | Reintentos | 3 | Intentos máximos antes de fallar | | Entre reintentos | 5 segundos | Espera entre intentos | ### SSL Bypass El sitio web del BCV usa certificados SSL que pueden no estar en el cacerts de Java. El plugin implementa SSL bypass por-conexión (no global) para evitar conflictos con iDempiere. ## Comportamiento del Proceso ### Ejecución Manual (con parámetros de fecha) - Procesa cada día en el rango DateFrom → DateTo - Para cada día, busca la última tasa BCV hacia atrás (hasta 10 días) - Usa la tasa encontrada pero asigna ValidFrom = fecha solicitada ### Ejecución Automática (sin parámetros de fecha) 1. Obtiene la última tasa BCV publicada (API + scraping) 2. Compara con la última tasa en BD 3. Si hay tasa nueva no registrada, procesa desde la última en BD hasta esa fecha 4. Si ya están todas → "No hay días pendientes" ### Lógica de Fallback por Día | Situación | Acción | |-----------|--------| | BCV publica tasa para la fecha | Usa esa tasa, ValidFrom = fecha solicitada | | BCV no publica (feriado, fin de semana) | Busca hacia atrás, usa última tasa disponible | | No se encuentra tasa en 10 días | Registra error | ### Ejemplo | Fecha solicitada | BCV publica | Tasa usada | ValidFrom | |------------------|-------------|------------|-----------| | 24/06 (miércoles) | No | 617.6388 (del 23/06) | 24/06 | | 25/06 (jueves) | Sí (621.5299) | 621.5299 | 25/06 | | 27/06 (sábado) | No | tasa del 26/06 | 27/06 | | 28/06 (domingo) | No | tasa del 26/06 | 28/06 | ## Archivos del Plugin ``` com.venezuela.bcvrate/ ├ META-INF/ │ └ MANIFEST.MF # Manifest OSGi (Bundle-Activator) ├ src/com/venezuela/bcvrate/ │ ├ Activator.java # Registra IProcessFactory via BundleContext │ ├ factory/ │ │ └ BCVProcessFactory.java # Factory del proceso │ ├ process/ │ │ └ BCVExchangeRateProcess.java # Lógica principal del proceso │ └ service/ │ ├ BCVApiService.java # Cliente HTTP + scraping BCV + reintentos │ └ BCVRateResponse.java # Modelo de respuesta ├ dist/ │ └ com.venezuela.bcvrate.jar # JAR compilado listo para instalar ├ lib/ │ └ org.osgi.framework.jar # Dependencia OSGi ├ migration/ │ ├ install_v10.sql # SQL para registrar proceso │ └ BCV_ExchangeRateProcess_2Pack.xml # 2pack para despliegue ├ .gitignore # Archivos ignorados por git └ build.sh # Script de build para Linux ``` ## Instalación ### Prerrequisitos - iDempiere v10 ejecutándose - Acceso a consola Felix (http://servidor:8080/system/console) - Acceso a la base de datos PostgreSQL - Conectividad a internet (bcv.today y bcv.org.ve) ### Paso 1: Instalar JAR en Felix 1. Abrir consola Felix → **Bundles** 2. Click **Install/Update Bundle** 3. Seleccionar `com.venezuela.bcvrate.jar` 4. Seleccionar **Start** 5. Verificar estado **Active** **Importante**: Si actualizas el plugin, debes **uninstall** el bundle anterior primero, luego **install** el nuevo. Equinox no recarga si la versión es la misma. ### Paso 2: Registrar Proceso Opción A: Ejecutar `migration/install_v10.sql` en la BD Opción B: Crear manualmente via UI: - **Proceso**: BCV Exchange Rate Update - **ClassName**: com.venezuela.bcvrate.process.BCVExchangeRateProcess - **AccessLevel**: 3 (Client + Organization) - **Parámetros**: Client, Conversion Type, Date From, Date To, Simulation Mode Opción C: Importar 2pack `migration/BCV_ExchangeRateProcess_2Pack.xml` ### Paso 3: Agregar al Menú Crear registro en AD_TreeBar o importar 2pack con el menú configurado. ### Paso 4: Configurar Scheduler (Agenda) 1. Ir a **Agenda** (Process Scheduler) 2. Crear nuevo registro 3. Seleccionar proceso: **BCV Exchange Rate Update** 4. Frecuencia: **1 dia** 5. Hora: **17:00-18:00** (cuando BCV publica la tasa) 6. Dejar parámetros de fecha **vacíos** **Recomendación**: Configurar dos ejecuciones (17:00 y 21:00) por si la primera no captura la tasa. ## Parámetros del Proceso | Parámetro | Tipo | Requerido | Default | Descripción | |-----------|------|-----------|---------|-------------| | Grupo Empresarial | List | Sí | @#AD_Client_ID@ | Cliente de iDempiere | | Tipo de Conversión | List | Sí | BCV | Tipo de conversión | | Fecha desde | Date | No | (vacío) | Fecha inicio (vacío = auto) | | Fecha hasta | Date | No | (vacío) | Fecha fin (vacío = auto) | | Simulación | Yes/No | No | N | Ejecutar sin guardar cambios | ## Datos Clave | Campo | Valor | Descripción | |-------|-------|-------------| | C_Currency_ID | 100 | USD (hardcoded) | | C_Currency_ID_To | (dinámico) | Moneda funcional del cliente | | AD_Org_ID | 0 | Todas las organizaciones | | MultiplyRate | (API/scraping) | Tasa USD → VES (4 decimales) | | DivideRate | 1/MultiplyRate | Tasa VES → USD (10 decimales) | | ValidFrom | (fecha solicitada) | Fecha que necesita la empresa | | ValidTo | (fecha solicitada) | Misma que ValidFrom | ## Solución de Problemas ### Error "Failed to create new process instance" - Verificar que el bundle está **Active** en Felix - Verificar logs: `grep -i bcvrate /opt/idempiere-server/logs/idempiere.log` ### Error "Cross tenant PO writing" - Se resolvió usando `set_ValueNoCheck()` en vez de `setMultiplyRate()` - El proceso crea el registro con el client ID correcto del contexto ### Error "No se pudo obtener la moneda funcional" - Verificar que el cliente tiene un esquema contable configurado - Verificar que la moneda funcional no sea USD ### API bcv.today no responde o tiene retraso - Verificar conectividad: `curl https://bcv.today/api/v1/rate.json` - El plugin automáticamente intenta scraping del sitio web del BCV como respaldo ### Scraping BCV falla por timeout - El plugin reintenta hasta 3 veces con espera de 5 segundos - Verificar conectividad: `curl https://www.bcv.org.ve/` - Si hay proxy, configurarlo en el servidor - El log muestra "Scraping BCV fallo despues de 3 intentos" ### Error SSL en scraping del BCV - El plugin incluye SSL bypass para el sitio web del BCV - Si persiste, verificar que el bundle tiene la última versión ### Tasa con decimales incorrectos - Se resolvió usando `set_ValueNoCheck()` en vez de setters de MConversionRate - MultiplyRate: 4 decimales - DivideRate: 10 decimales ### Scraping captura tasa incorrecta (EUR en vez de USD) - El regex busca específicamente después de `USD` en el HTML - Verificar que el HTML del BCV no ha cambiado su estructura ## Compilación ### En Windows (desarrollo) ```powershell # Requiere: JDK 11, JARs de iDempiere v10 $javac = "C:\Program Files\Eclipse Adoptium\jdk-11.0.31.11-hotspot\bin\javac.exe" $v10Plugins = "path/to/idempiere-v10/plugins" $sources = Get-ChildItem -Path src -Recurse -Filter "*.java" $classpath = "$v10Plugins\org.adempiere.base_*.jar;$v10Plugins\json_*.jar;lib\org.osgi.framework.jar" & $javac -cp $classpath -d dist -source 11 -target 11 @($sources) & jar cfm dist/com.venezuela.bcvrate.jar META-INF/MANIFEST.MF -C dist . ``` ### En Linux (servidor) ```bash chmod +x build.sh ./build.sh ``` ## Dependencias - **org.adempiere.base** >= 10.0.0 - Framework iDempiere - **json** >= 20190722.0.0 - Parsing JSON (solo para API bcv.today) - **org.osgi.framework** - Framework OSGi (sistema) ## Notas Técnicas ### Dual Source: API + Scraping El plugin implementa dos fuentes de datos para máxima confiabilidad: 1. **API bcv.today**: Consulta REST rápida, pero puede tener retraso de actualización 2. **Scraping BCV**: Extracción directa del HTML del sitio web oficial El flujo automático intenta la API primero y fallback al scraping si no hay datos recientes. ### Lógica de Reintentos Cuando el scraping falla (timeout, error de conexión), el plugin reintenta automáticamente: ```java int maxRetries = 3; for (int attempt = 1; attempt <= maxRetries; attempt++) { // Intentar scraping // Si falla, esperar 5 segundos y reintentar // Si falla 3 veces, registrar error } ``` ### SSL Bypass para Scraping El sitio web del BCV usa certificados SSL que pueden no estar en el cacerts de Java. El plugin implementa SSL bypass por-conexión (no global) para evitar conflictos con iDempiere: ```java TrustManager[] trustAllCerts = new TrustManager[]{...}; SSLContext sc = SSLContext.getInstance("TLS"); sc.init(null, trustAllCerts, new SecureRandom()); HttpsURLConnection conn = (HttpsURLConnection) url.openConnection(); conn.setSSLSocketFactory(sc.getSocketFactory()); conn.setHostnameVerifier((hostname, session) -> true); ``` ### Extracción de Tasa USD del HTML El regex busca específicamente la tasa del dólar después del elemento `USD`: ``` USD.*?strong-tb">\s*([\d.,]+)\s* ``` Esto evita capturar otras tasas (EUR, CNY, etc.) que aparecen en la misma página. ### Por qué se usa BundleActivator en vez de DS - `component.xml` (Declarative Services) no funcionó en iDempiere v10 - `@Component` annotation tampoco funcionó - `BundleActivator` con `BundleContext.registerService()` es el más confiable - Requiere `Import-Package: org.osgi.framework` en MANIFEST.MF ### Por qué se usa regex en vez del JSON library - El `json_20190722.0.0.jar` bundled con iDempiere v10 tiene bugs de precisión - `getDouble("USD")` y `get("USD").toString()` corrompen decimales - Se extrae el valor directamente del string raw con regex: `"USD"\s*:\s*([0-9.]+)` ### Por qué se usa set_ValueNoCheck en vez de setters - `MConversionRate.setMultiplyRate()` modifica el valor internamente - `MConversionRate.setDivideRate()` redondea a 4 decimales - `set_ValueNoCheck()` bypassa la lógica interna y almacena el valor exacto ### Cross Tenant - `MConversionRate.setRate()` intenta guardar con AD_Client_ID=0 - Se crea el registro directamente con `MConversionRate` y se usa `set_ValueNoCheck("AD_Client_ID", ...)` ### Por qué el JAR debe cambiar de versión - Equinox (runtime OSGi) cachea bundles por versión - Si la versión no cambia, el bundle anterior permanece activo - Siempre incrementar `Bundle-Version` en MANIFEST.MF al actualizar ## Repositorio El código fuente está disponible en: - **Gitea**: https://gitea.inforcloud.net/ezerpa/eru - **Branch principal**: master ### Clonar repositorio ```bash git clone https://gitea.inforcloud.net/ezerpa/eru.git ``` ### Actualizar código ```bash git pull ``` ## Historial de Cambios ### v1.0.13 (2026-07-03) - Lógica de reintentos: 3 intentos máximo con espera de 5 segundos - Timeout incrementado: 30s conexión / 60s lectura - Logging detallado de reintentos en servidor ### v1.0.12 (2026-07-03) - Incremento de timeout HTTP a 30s/60s ### v1.0.11 (2026-07-03) - Fix regex scraping: captura tasa USD específicamente (no EUR) - Regex busca después de `USD` en el HTML ### v1.0.10 (2026-07-03) - Logging mejorado en proceso para ver fuente de datos - Scraping status visible en output del proceso ### v1.0.9 (2026-07-03) - SSL bypass por-conexión (no global) para scraping BCV - Logging detallado en getLatestRate() ### v1.0.8 (2026-07-03) - Scraping directo del sitio web del BCV como fuente de respaldo - Dual source: API bcv.today + scraping bcv.org.ve - Método getRateFromBCVWebsite() con extracción regex del HTML ### v1.0.7 (2026-06-23) - Lógica automática: detecta días pendientes entre BD y última tasa BCV - Ejecución sin parámetros: busca última tasa BCV y rellena huecos ### v1.0.6 (2026-06-23) - Fix: ejecución automática ahora detecta si hay tasa para hoy - Evita procesar si ya existen todas las tasas ### v1.0.5 (2026-06-23) - Lógica de fallback: busca hacia atrás cuando BCV no publica tasa - Rellena días sin publicación con última tasa disponible ### v1.0.4 (2026-06-23) - getRateForDateWithFallback(): busca hacia atrás hasta 10 días - BCVRateResponse: campo requestedDate vs effectiveDate ### v1.0.3 (2026-06-23) - DivideRate: 10 decimales en vez de 4 ### v1.0.2 (2026-06-23) - Todos los setters usan set_ValueNoCheck() para bypass MConversionRate - Log de verificación post-save ### v1.0.1 (2026-06-23) - Regex para extraer USD directo del string raw (bypass JSON library) ### v1.0.0 (2026-06-22) - Versión inicial - API: bcv.today - BundleActivator para registro de IProcessFactory ## Licencia Plugin personalizado para uso interno de Ezerpa.