@@ -9,8 +9,8 @@ if (!admin.apps.length) {
99 admin . initializeApp ( ) ;
1010}
1111
12- /** Scrape the BCV official USD rate directly from bcv.org.ve */
13- async function scrapeBCVRate ( ) : Promise < number | null > {
12+ /** Scrape the BCV official USD and EUR rates directly from bcv.org.ve */
13+ async function scrapeBCVRates ( ) : Promise < { usd : number ; eur : number } | null > {
1414 try {
1515 const httpsAgent = new https . Agent ( { rejectUnauthorized : false } ) ;
1616 const response = await axios . get ( 'https://www.bcv.org.ve/' , {
@@ -24,22 +24,34 @@ async function scrapeBCVRate(): Promise<number | null> {
2424 } ) ;
2525
2626 const $ = cheerio . load ( response . data ) ;
27- const rateText = $ ( '#dolar .centrado strong' ) . text ( ) . trim ( ) ;
2827
29- if ( ! rateText ) {
30- logger . error ( 'Could not find USD rate on BCV website' ) ;
31- return null ;
28+ function parseRate ( selector : string , label : string ) : number | null {
29+ const text = $ ( selector ) . text ( ) . trim ( ) ;
30+ if ( ! text ) {
31+ logger . error ( `Could not find ${ label } rate on BCV website` ) ;
32+ return null ;
33+ }
34+ const rate = parseFloat ( text . replace ( ',' , '.' ) ) ;
35+ if ( isNaN ( rate ) || rate <= 0 ) {
36+ logger . error ( `Failed to parse BCV ${ label } rate: "${ text } "` ) ;
37+ return null ;
38+ }
39+ return rate ;
3240 }
3341
34- // BCV uses comma as decimal separator, e.g. "459,4525"
35- const rate = parseFloat ( rateText . replace ( ',' , '.' ) ) ;
36- if ( isNaN ( rate ) || rate <= 0 ) {
37- logger . error ( `Failed to parse BCV rate: "${ rateText } "` ) ;
38- return null ;
42+ const usd = parseRate ( '#dolar .centrado strong' , 'USD' ) ;
43+ const eur = parseRate ( '#euro .centrado strong' , 'EUR' ) ;
44+
45+ if ( ! usd ) return null ;
46+
47+ // EUR is optional — USD is required
48+ if ( eur ) {
49+ logger . info ( `Scraped BCV rates: 1 USD = ${ usd } VES, 1 EUR = ${ eur } VES` ) ;
50+ return { usd, eur } ;
3951 }
4052
41- logger . info ( `Scraped BCV rate: 1 USD = ${ rate } VES` ) ;
42- return rate ;
53+ logger . warn ( 'EUR rate not found, using USD-derived estimate' ) ;
54+ return { usd , eur : usd * 1.08 } ;
4355 } catch ( error ) {
4456 logger . error ( 'Error scraping BCV website:' , error ) ;
4557 return null ;
@@ -55,21 +67,21 @@ export const fetchExchangeRates = onSchedule(
5567 async ( ) => {
5668 logger . info ( 'Fetching BCV exchange rates...' ) ;
5769
58- let usdToVes = await scrapeBCVRate ( ) ;
59- let source = 'bcv.org.ve (scrape)' ;
70+ const bcvRates = await scrapeBCVRates ( ) ;
71+ const source = 'bcv.org.ve (scrape)' ;
6072
61- if ( ! usdToVes ) {
62- logger . error ( 'BCV scrape failed — no rate obtained' ) ;
73+ if ( ! bcvRates ) {
74+ logger . error ( 'BCV scrape failed — no rates obtained' ) ;
6375 return ;
6476 }
6577
6678 const db = admin . firestore ( ) ;
6779 const now = admin . firestore . Timestamp . now ( ) ;
6880
6981 await db . collection ( 'exchangeRates' ) . add ( {
70- rates : { USD : usdToVes } ,
82+ rates : { USD : bcvRates . usd , EUR : bcvRates . eur } ,
7183 source,
7284 recordedAt : now ,
7385 } ) ;
74- logger . info ( `Stored BCV rate : 1 USD = ${ usdToVes } VES (source: ${ source } )` ) ;
86+ logger . info ( `Stored BCV rates : 1 USD = ${ bcvRates . usd } VES, 1 EUR = ${ bcvRates . eur } VES (source: ${ source } )` ) ;
7587 } ) ;
0 commit comments