Documentation Index Fetch the complete documentation index at: https://mintlify.com/MiguelNavas19/miapibcv/llms.txt
Use this file to discover all available pages before exploring further.
Overview
Mi API BCV uses Laravel’s task scheduler to automatically fetch exchange rates daily. The rates:update command runs on a schedule and updates the database with the latest rates from all configured banks.
The FetchExchangeRates Command
Location : app/Console/Commands/FetchExchangeRates.php
Command Signature
protected $signature = 'rates:update' ;
protected $description = 'Consulta el BCV y otros bancos para actualizar tasas' ;
Run manually:
Command Structure
namespace App\Console\Commands ;
use App\Models\ ReferenceRecord ;
use App\Services\ UrlProviderService ;
use Illuminate\Console\ Command ;
use Illuminate\Support\Facades\ Log ;
class FetchExchangeRates extends Command
{
protected $signature = 'rates:update' ;
protected $description = 'Consulta el BCV y otros bancos para actualizar tasas' ;
protected $urlProvider ;
protected array $banco = [];
public function __construct ( UrlProviderService $urlProvider )
{
parent :: __construct ();
$this -> urlProvider = $urlProvider ;
$this -> banco = [ 'bdv' , 'banplus' , 'bnc' , 'bcv' ];
}
public function handle ()
{
$this -> info ( 'Iniciando actualización de tasas...' );
$this -> logApi ();
foreach ( $this -> banco as $banco ) {
try {
$this -> store ( $banco );
$this -> info ( "Banco { $banco } procesado." );
} catch ( \ Exception $e ) {
$this -> error ( "Error en { $banco }: " . $e -> getMessage ());
Log :: error ( "Fallo en cron para { $banco }: " . $e -> getMessage ());
}
}
$this -> info ( 'Tasas actualizadas exitosamente.' );
}
protected function store ( string $banco )
{
$today = now () -> toDateString ();
// Check if record already exists for today
$exists = ReferenceRecord :: where ( 'source' , $banco )
-> where ( 'date' , $today )
-> exists ();
if ( $exists ) {
$this -> info ( "El registro para { $banco } ya existe hoy. Saltando..." );
return ;
}
// Fetch value via strategy
$value = $this -> urlProvider -> getStrategy ( $banco ) -> getValue ();
if ( $value ) {
$record = new ReferenceRecord ();
$record -> source = $banco ;
$record -> value = $value ;
$record -> date = $today ;
$record -> save ();
$this -> info ( "Guardado exitoso: { $banco } -> { $value }" );
} else {
$this -> warn ( "No se obtuvo valor para { $banco }" );
}
}
protected function logApi ()
{
Log :: info ( "SE EJECUTO EL CRON" , [
'time' => now () -> toDateTimeString ()
]);
}
}
How It Works
Execution Flow
Command starts
Laravel invokes the handle() method
Log execution
Records the cron run in the log file
Iterate through banks
Loops through ['bdv', 'banplus', 'bnc', 'bcv']
Check for existing record
Queries database to see if today’s rate already exists ReferenceRecord :: where ( 'source' , $banco )
-> where ( 'date' , today ())
-> exists ();
Fetch rate if needed
If no record exists:
Get appropriate strategy from UrlProviderService
Call getValue() to scrape/fetch the rate
Save to database
Handle errors gracefully
If one bank fails, log the error and continue with the next bank
Complete
Output success message
Error Isolation
Each bank is processed independently:
foreach ( $this -> banco as $banco ) {
try {
$this -> store ( $banco );
} catch ( \ Exception $e ) {
// Log error but continue
$this -> error ( "Error en { $banco }: " . $e -> getMessage ());
Log :: error ( "Fallo en cron para { $banco }: " . $e -> getMessage ());
}
}
Result : If BCV fails, Banplus, BNC, and BDV still update.
Duplicate Prevention
The command checks if today’s rate already exists:
$exists = ReferenceRecord :: where ( 'source' , $banco )
-> where ( 'date' , $today )
-> exists ();
if ( $exists ) {
$this -> info ( "El registro para { $banco } ya existe hoy. Saltando..." );
return ;
}
Benefit : Running the command multiple times per day won’t create duplicate records.
Setting Up Laravel Scheduler
Edit app/Console/Kernel.php (or routes/console.php in Laravel 11+):
namespace App\Console ;
use Illuminate\Console\Scheduling\ Schedule ;
use Illuminate\Foundation\Console\ Kernel as ConsoleKernel ;
class Kernel extends ConsoleKernel
{
protected function schedule ( Schedule $schedule )
{
// Run rates:update every day at 9:00 AM
$schedule -> command ( 'rates:update' )
-> dailyAt ( '09:00' )
-> timezone ( 'America/Caracas' );
}
}
In Laravel 11+, scheduled tasks may be defined in routes/console.php instead of Kernel.php.
Scheduling Options
Daily at Specific Time
$schedule -> command ( 'rates:update' ) -> dailyAt ( '09:00' );
Every Weekday
$schedule -> command ( 'rates:update' )
-> weekdays ()
-> at ( '09:00' );
Multiple Times Per Day
$schedule -> command ( 'rates:update' ) -> twiceDaily ( 9 , 15 );
Runs at 9:00 AM and 3:00 PM.
Custom Times
$schedule -> command ( 'rates:update' ) -> dailyAt ( '09:00' );
$schedule -> command ( 'rates:update' ) -> dailyAt ( '15:00' );
Every Hour (Not Recommended)
$schedule -> command ( 'rates:update' ) -> hourly ();
Exchange rates don’t change every hour. Daily updates are sufficient and reduce server load and scraping requests.
Advanced Scheduling
Prevent Overlaps
Ensure command doesn’t run if previous execution is still running:
$schedule -> command ( 'rates:update' )
-> dailyAt ( '09:00' )
-> withoutOverlapping ();
Run in Background
$schedule -> command ( 'rates:update' )
-> dailyAt ( '09:00' )
-> runInBackground ();
Send Output to Log
$schedule -> command ( 'rates:update' )
-> dailyAt ( '09:00' )
-> appendOutputTo ( storage_path ( 'logs/scheduler.log' ));
Email on Failure
$schedule -> command ( 'rates:update' )
-> dailyAt ( '09:00' )
-> emailOutputOnFailure ( 'admin@example.com' );
Requires mail configuration in .env.
Ping URL on Completion
For monitoring services like Laravel Envoyer or Oh Dear :
$schedule -> command ( 'rates:update' )
-> dailyAt ( '09:00' )
-> pingBefore ( 'https://monitor.example.com/start/12345' )
-> thenPing ( 'https://monitor.example.com/end/12345' );
Setting Up System Cron
Laravel’s scheduler requires a single cron entry that runs every minute:
Step 2: Add Cron Entry
Edit the crontab:
Add this line:
* * * * * cd /path/to/miapibcv && php artisan schedule:run >> /dev/null 2>&1
Replace /path/to/miapibcv with your actual project path.
Example (production):
* * * * * cd /var/www/miapibcv && php artisan schedule:run >> /dev/null 2>&1
What This Does
* * * * * - Run every minute
cd /path/to/miapibcv - Navigate to project directory
php artisan schedule:run - Laravel checks if any scheduled tasks should run
>> /dev/null 2>&1 - Suppress output
The cron runs every minute, but schedule:run only executes scheduled tasks when their time arrives.
Verify Cron is Active
List current cron jobs:
Should output:
* * * * * cd /var/www/miapibcv && php artisan schedule:run >> /dev/null 2>&1
Check Cron Logs
View system cron logs:
grep CRON /var/log/syslog
For Laravel’s output, check:
tail -f storage/logs/laravel.log
Production Configuration
Timezone Configuration
Ensure correct timezone in .env:
APP_TIMEZONE=America/Caracas
Or in config/app.php:
'timezone' => 'America/Caracas' ,
Scheduler in Schedule Definition
Explicitly set timezone:
$schedule -> command ( 'rates:update' )
-> dailyAt ( '09:00' )
-> timezone ( 'America/Caracas' );
User Permissions
Ensure cron user has permissions:
sudo chown -R www-data:www-data /var/www/miapibcv
sudo chmod -R 755 /var/www/miapibcv/storage
sudo chmod -R 755 /var/www/miapibcv/bootstrap/cache
PHP Path
If php isn’t in PATH, use absolute path:
* * * * * cd /var/www/miapibcv && /usr/bin/php artisan schedule:run >> /dev/null 2>&1
Find PHP path:
Monitoring Scheduled Tasks
Log Output
Modify cron to log output:
* * * * * cd /var/www/miapibcv && php artisan schedule:run >> /var/www/miapibcv/storage/logs/cron.log 2>&1
View logs:
tail -f storage/logs/cron.log
Laravel Logs
Check application logs:
tail -f storage/logs/laravel.log
Look for:
[2026-03-04 09:00:00] local.INFO: SE EJECUTO EL CRON {"time":"2026-03-04 09:00:00"}
Database Verification
Check if rates are being updated:
use App\Models\ ReferenceRecord ;
// Get today's records
ReferenceRecord :: whereDate ( 'date' , today ()) -> get ();
// Count records per day
ReferenceRecord :: selectRaw ( 'date, count(*) as count' )
-> groupBy ( 'date' )
-> orderBy ( 'date' , 'desc' )
-> limit ( 7 )
-> get ();
Health Check Endpoint
Create a health check endpoint to monitor updates:
// routes/api.php
Route :: get ( '/health' , function () {
$today = now () -> toDateString ();
$count = ReferenceRecord :: where ( 'date' , $today ) -> count ();
return response () -> json ([
'status' => $count > 0 ? 'healthy' : 'unhealthy' ,
'date' => $today ,
'banks_updated' => $count ,
'expected' => 4 , // bcv, banplus, bnc, bdv
]);
});
Test:
curl http://localhost:8000/api/health
Troubleshooting
Cron Not Running
Check cron service :
sudo systemctl status cron
Restart if needed:
sudo systemctl restart cron
Verify crontab :
Command Not Executing
Test manually :
cd /var/www/miapibcv
php artisan schedule:run
Check output for errors.
Test the rates:update command directly :
Permission Denied
Check file permissions :
Fix:
sudo chown -R www-data:www-data storage
sudo chmod -R 775 storage
Database Connection Issues
Check .env configuration :
DB_CONNECTION=sqlite
DB_DATABASE=/var/www/miapibcv/database/database.sqlite
Verify database file exists and is writable:
ls -la database/database.sqlite
Scraping Failures
If all banks fail:
Check network connectivity
Verify DNS resolution
Test bank websites manually
Check server firewall rules
For individual bank failures, see ScraperService troubleshooting.
Alternative: Supervisor Queue
For more robust scheduling, use Laravel queues with Supervisor:
In .env:
QUEUE_CONNECTION=database
2. Create Migration
php artisan queue:table
php artisan migrate
3. Dispatch Job
Create a job:
php artisan make:job FetchExchangeRatesJob
Implement:
namespace App\Jobs ;
use Illuminate\Bus\ Queueable ;
use Illuminate\Contracts\Queue\ ShouldQueue ;
use App\Services\ UrlProviderService ;
use App\Models\ ReferenceRecord ;
class FetchExchangeRatesJob implements ShouldQueue
{
use Queueable ;
public function handle ( UrlProviderService $urlProvider )
{
$banks = [ 'bdv' , 'banplus' , 'bnc' , 'bcv' ];
foreach ( $banks as $bank ) {
// ... fetch logic
}
}
}
Schedule:
$schedule -> job ( new FetchExchangeRatesJob ) -> dailyAt ( '09:00' );
4. Run Queue Worker
Use Supervisor to keep it running (see Deployment Guide ).
Best Practices
Scheduling Time
Choose when banks typically update their rates:
9:00 AM : After banks open
3:00 PM : Midday update
Weekdays only : Banks don’t update rates on weekends
Error Notifications
Set up alerts for failures:
$schedule -> command ( 'rates:update' )
-> dailyAt ( '09:00' )
-> onFailure ( function () {
// Send email, Slack notification, etc.
\ Log :: critical ( 'Rates update failed!' );
});
Retries
Add retry logic for transient failures:
use Illuminate\Support\Facades\ Http ;
$value = retry ( 3 , function () use ( $url , $banco ) {
return $this -> scraper -> scrapeData ( $url , $banco );
}, 100 ); // 100ms delay between retries
Monitoring
Use services like:
Next Steps
Deployment Guide Deploy to production with full scheduler setup
Architecture Understand how the scheduler fits into the system