v1.0.11 - Plugin BCV Exchange Rate para iDempiere v10

This commit is contained in:
2026-07-03 15:00:31 -04:00
commit e9c19b4b3b
16 changed files with 1700 additions and 0 deletions
@@ -0,0 +1,276 @@
package com.venezuela.bcvrate.service;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
public class BCVApiService {
private static final Logger log = Logger.getLogger(BCVApiService.class.getName());
private static final String BASE_URL = "https://bcv.today/api/v1";
private static final String BCV_WEBSITE = "https://www.bcv.org.ve/";
private static final int CONNECTION_TIMEOUT = 15000;
private static final int READ_TIMEOUT = 30000;
private static final int MAX_DAYS_BACK = 10;
public BCVRateResponse getRateForDateWithFallback(String requestedDate) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Calendar cal = Calendar.getInstance();
try {
cal.setTime(sdf.parse(requestedDate));
} catch (Exception e) {
log.log(Level.WARNING, "Error parseando fecha: " + requestedDate, e);
return null;
}
for (int i = 0; i <= MAX_DAYS_BACK; i++) {
String dateToQuery = sdf.format(cal.getTime());
BCVRateResponse rate = getRateFromApi(dateToQuery);
if (rate != null && rate.isValid()) {
log.info("Tasa encontrada (API) consultando " + dateToQuery
+ " para fecha solicitada " + requestedDate
+ ": USD=" + rate.getDollar()
+ " effective=" + rate.getDate());
return new BCVRateResponse(rate.getDollar(), requestedDate, rate.getDate());
}
cal.add(Calendar.DAY_OF_MONTH, -1);
}
log.info("API no tiene tasa para " + requestedDate + ", intentando scraping BCV...");
BCVRateResponse websiteRate = getRateFromBCVWebsite();
if (websiteRate != null && websiteRate.isValid()) {
log.info("Tasa obtenida del sitio web BCV: USD=" + websiteRate.getDollar()
+ " effective=" + websiteRate.getDate());
return new BCVRateResponse(websiteRate.getDollar(), requestedDate, websiteRate.getDate());
}
log.info("No se encontro tasa en los ultimos " + MAX_DAYS_BACK + " dias para: " + requestedDate);
return null;
}
public BCVRateResponse getRateForDate(String date) {
return getRateFromApi(date);
}
public BCVRateResponse getLatestRate() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
for (int i = 1; i <= 10; i++) {
Calendar cal = Calendar.getInstance();
cal.add(Calendar.DAY_OF_MONTH, i);
String futureDate = sdf.format(cal.getTime());
BCVRateResponse rate = getRateFromApi(futureDate);
if (rate != null && rate.isValid()) {
log.info("Tasa encontrada (API futura) para " + futureDate
+ ": USD=" + rate.getDollar() + " effective=" + rate.getDate());
return rate;
}
}
log.info("API no tiene tasa futura, intentando scraping BCV...");
try {
BCVRateResponse websiteRate = getRateFromBCVWebsite();
if (websiteRate != null && websiteRate.isValid()) {
log.info("Tasa obtenida del sitio web BCV: USD=" + websiteRate.getDollar()
+ " effective=" + websiteRate.getDate());
return websiteRate;
}
log.info("Scraping BCV no devolvio tasa valida");
} catch (Exception e) {
log.log(Level.WARNING, "Scraping BCV fallo", e);
}
log.info("Fallback a API actual...");
BCVRateResponse apiRate = getCurrentRate();
return apiRate;
}
private BCVRateResponse getRateFromApi(String date) {
String urlStr = BASE_URL + "/history/" + date + ".json";
try {
String response = httpGet(urlStr);
if (response == null) return null;
BigDecimal dollar = extractUSD(response);
String effectiveDate = extractField(response, "effective_date");
if (effectiveDate == null) effectiveDate = extractField(response, "date");
if (dollar != null && effectiveDate != null) {
return new BCVRateResponse(dollar, effectiveDate);
}
return null;
} catch (Exception e) {
log.log(Level.WARNING, "Error consultando fecha: " + date, e);
return null;
}
}
public BCVRateResponse getCurrentRate() {
String urlStr = BASE_URL + "/rate.json";
try {
String response = httpGet(urlStr);
if (response == null) return null;
BigDecimal dollar = extractUSD(response);
String effectiveDate = extractField(response, "effective_date");
if (effectiveDate == null) effectiveDate = extractField(response, "date");
if (dollar != null && effectiveDate != null) {
log.info("Tasa actual (API): USD=" + dollar + " effective=" + effectiveDate);
return new BCVRateResponse(dollar, effectiveDate);
}
return null;
} catch (Exception e) {
log.log(Level.WARNING, "Error consultando tasa actual", e);
return null;
}
}
private BCVRateResponse getRateFromBCVWebsite() {
try {
log.info("Iniciando scraping de BCV website...");
String html = httpGetHtml(BCV_WEBSITE);
if (html == null) {
log.warning("No se pudo obtener pagina BCV (httpGetHtml returned null)");
return null;
}
log.info("Pagina BCV obtenida, longitud: " + html.length());
Matcher rateMatcher = Pattern.compile(
"USD</span>.*?strong-tb\">\\s*([\\d.,]+)\\s*</strong>", Pattern.DOTALL).matcher(html);
if (!rateMatcher.find()) {
log.warning("No se encontro tasa USD en pagina BCV (regex no match)");
return null;
}
String rateStr = rateMatcher.group(1).replace(",", ".");
log.info("Tasa encontrada en HTML: " + rateStr);
BigDecimal dollar = new BigDecimal(rateStr).setScale(4, RoundingMode.HALF_UP);
Matcher dateMatcher = Pattern.compile(
"content=\"(\\d{4}-\\d{2}-\\d{2})T").matcher(html);
String effectiveDate = null;
if (dateMatcher.find()) {
effectiveDate = dateMatcher.group(1);
}
if (dollar.compareTo(BigDecimal.ZERO) > 0) {
if (effectiveDate == null) {
effectiveDate = new SimpleDateFormat("yyyy-MM-dd").format(new java.util.Date());
}
log.info("Scraping BCV exitoso: USD=" + dollar + " effective=" + effectiveDate);
return new BCVRateResponse(dollar, effectiveDate);
}
return null;
} catch (Exception e) {
log.log(Level.WARNING, "Error scraping BCV website: " + e.getMessage(), e);
return null;
}
}
private BigDecimal extractUSD(String json) {
Matcher m = Pattern.compile("\"USD\"\\s*:\\s*([0-9.]+)").matcher(json);
if (m.find()) {
return new BigDecimal(m.group(1)).setScale(4, RoundingMode.HALF_UP);
}
return null;
}
private String extractField(String json, String field) {
Matcher m = Pattern.compile("\"" + field + "\"\\s*:\\s*\"([^\"]+)\"").matcher(json);
if (m.find()) {
return m.group(1);
}
return null;
}
private String httpGet(String urlStr) throws Exception {
URL url = new URL(urlStr);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("Accept", "application/json");
conn.setConnectTimeout(CONNECTION_TIMEOUT);
conn.setReadTimeout(READ_TIMEOUT);
int responseCode = conn.getResponseCode();
if (responseCode != 200) {
conn.disconnect();
return null;
}
StringBuilder sb = new StringBuilder();
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(conn.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
} finally {
conn.disconnect();
}
return sb.toString();
}
private String httpGetHtml(String urlStr) throws Exception {
URL url = new URL(urlStr);
TrustManager[] trustAllCerts = new TrustManager[]{
new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() { return null; }
public void checkClientTrusted(X509Certificate[] certs, String authType) {}
public void checkServerTrusted(X509Certificate[] certs, String authType) {}
}
};
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);
conn.setRequestMethod("GET");
conn.setRequestProperty("Accept", "text/html");
conn.setConnectTimeout(CONNECTION_TIMEOUT);
conn.setReadTimeout(READ_TIMEOUT);
int responseCode = conn.getResponseCode();
if (responseCode != 200) {
conn.disconnect();
return null;
}
StringBuilder sb = new StringBuilder();
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(conn.getInputStream(), "UTF-8"))) {
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
} finally {
conn.disconnect();
}
return sb.toString();
}
}