Skip to main content

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.

System Overview

Mi API BCV is built on Laravel 12 and follows a layered architecture that separates concerns between data collection, storage, and API delivery. The system uses the Strategy pattern for extensibility and Laravel’s built-in features for caching, scheduling, and database management.

High-Level Architecture

┌─────────────────┐
│   HTTP Client   │
└────────┬────────┘


┌─────────────────┐
│  API Routes     │  (routes/api.php)
└────────┬────────┘


┌─────────────────┐
│  Controller     │  (ScraperController)
└────────┬────────┘


┌─────────────────┐
│  Cache Layer    │  (1-hour TTL)
└────────┬────────┘


┌─────────────────┐
│  Eloquent ORM   │  (ReferenceRecord)
└────────┬────────┘


┌─────────────────┐
│    Database     │  (SQLite/MySQL/PostgreSQL)
└─────────────────┘

Data Update Flow

┌─────────────────┐
│  Cron Scheduler │
└────────┬────────┘


┌─────────────────┐
│ rates:update    │  (FetchExchangeRates command)
└────────┬────────┘


┌─────────────────┐
│ UrlProvider     │  (Strategy context)
└────────┬────────┘


┌─────────────────┐
│ UrlStrategy     │  (BCV, Banplus, BNC, BDV)
└────────┬────────┘


┌─────────────────┐
│ ScraperService  │  (HTTP + DOM parsing)
└────────┬────────┘


┌─────────────────┐
│ ReferenceRecord │  (Eloquent model)
└────────┬────────┘


┌─────────────────┐
│ ReferenceObserver│ (Cache invalidation)
└────────┬────────┘


┌─────────────────┐
│    Database     │
└─────────────────┘

Core Components

API Layer

Location: app/Http/Controllers/Api/ScraperController.php The API controller handles HTTP requests and provides two main endpoints:
  1. GET / - Returns today’s rates from all banks
  2. GET /info/{date}/{source?} - Returns historical rates with optional filtering
Both endpoints implement caching with 1-hour TTL to reduce database load.
public function show(Request $request)
{
    $this->logApi($request);
    
    $cacheKey = 'tasas_bancos_' . now()->toDateString();
    
    $records = Cache::remember($cacheKey, 3600, function () {
        return ReferenceRecord::where('date', now()->toDateString())
            ->get()
            ->keyBy('source');
    });
    
    // ... response formatting
}

Data Collection Layer

ScraperService (app/Services/ScraperService.php) Handles HTTP requests and HTML parsing for each bank’s website:
  • Configures HTTP client with browser headers
  • Uses Symfony DomCrawler for HTML parsing
  • Applies bank-specific parsing logic
  • Normalizes decimal values
UrlProviderService (app/Services/UrlProviderService.php) Acts as the Strategy pattern context, managing concrete URL strategies for each bank.

Strategy Pattern Implementation

UrlStrategy Interface (app/Interfaces/UrlStrategy.php)
interface UrlStrategy
{
    public function getValue(): float;
}
Concrete Strategies:
  • UrlBcv - Scrapes Banco Central de Venezuela
  • UrlBanplus - Scrapes Banplus news ticker
  • UrlBnc - Scrapes Banco Nacional de Crédito
  • UrlBdv - Fetches from Banco de Venezuela (JSON API)
Each strategy:
  1. Defines the target URL
  2. Calls ScraperService with bank identifier
  3. Returns the parsed exchange rate value

Data Model

ReferenceRecord (app/Models/ReferenceRecord.php) Eloquent model representing exchange rate records:
Schema::create('reference_records', function (Blueprint $table) {
    $table->id();
    $table->string('source');        // Bank identifier
    $table->decimal('value', 64, 4); // Exchange rate
    $table->date('date');            // Rate date
    $table->timestamps();
});
ReferenceObserver (app/Observers/ReferenceObserver.php) Automatically invalidates cache when records are created or updated, ensuring API responses stay fresh.

Scheduled Tasks

FetchExchangeRates Command (app/Console/Commands/FetchExchangeRates.php)
protected $signature = 'rates:update';
protected $description = 'Consulta el BCV y otros bancos para actualizar tasas';
Iterates through all configured banks and:
  1. Checks if today’s rate already exists
  2. Fetches new rate via appropriate strategy
  3. Saves to database
  4. Logs success/failure
  5. Continues with next bank if one fails

Design Patterns

Strategy Pattern

Purpose: Allow easy addition of new bank sources without modifying existing code Benefits:
  • Open/Closed Principle: Open for extension, closed for modification
  • Each bank’s scraping logic is isolated
  • Easy to test individual strategies
  • Simple to add new banks

Observer Pattern

Purpose: Automatically invalidate cache when data changes Implementation: ReferenceObserver listens to Eloquent model events Benefits:
  • Decouples cache management from business logic
  • Ensures data consistency
  • Centralized cache invalidation logic

Repository Pattern (via Eloquent)

Purpose: Abstract database operations Implementation: Laravel’s Eloquent ORM Benefits:
  • Database-agnostic code
  • Query builder and relationships
  • Migration support

Laravel Components Used

HTTP Client

Http::withoutVerifying()->withHeaders([
    'User-Agent' => 'Mozilla/5.0 ...',
    'Accept' => 'text/html,application/xhtml+xml',
])->get($url);
Configured to bypass SSL verification and mimic browser requests.

Cache Facade

Cache::remember($key, $ttl, $callback);
Supports multiple drivers: database, file, Redis, Memcached.

Console Commands

Artisan commands for manual and scheduled rate updates.

Task Scheduling

Laravel’s scheduler (configured via cron) runs rates:update daily.

Directory Structure

app/
├── Console/
│   └── Commands/
│       └── FetchExchangeRates.php  # Scheduler command
├── Http/
│   └── Controllers/
│       └── Api/
│           └── ScraperController.php  # API endpoints
├── Interfaces/
│   └── UrlStrategy.php              # Strategy interface
├── Models/
│   └── ReferenceRecord.php          # Eloquent model
├── Observers/
│   └── ReferenceObserver.php        # Cache invalidation
├── Services/
│   ├── ScraperService.php           # Web scraping
│   └── UrlProviderService.php       # Strategy provider
└── Strategies/
    └── Url/
        ├── UrlBcv.php               # BCV strategy
        ├── UrlBanplus.php           # Banplus strategy
        ├── UrlBnc.php               # BNC strategy
        └── UrlBdv.php               # BDV strategy

routes/
└── api.php                          # API route definitions

database/
└── migrations/
    └── 2026_01_28_203351_create_reference_records_table.php

Data Flow Examples

API Request Flow

  1. Client sends GET / request
  2. Route dispatches to ScraperController::show()
  3. Controller checks cache for today’s rates
  4. If cache miss, query database
  5. Format response as JSON
  6. Cache result for 1 hour
  7. Return to client

Scheduled Update Flow

  1. Cron triggers php artisan rates:update
  2. Command iterates: ['bdv', 'banplus', 'bnc', 'bcv']
  3. For each bank:
    • Check if today’s record exists (skip if yes)
    • Call UrlProviderService::getStrategy($bank)
    • Strategy calls ScraperService::scrapeData($url, $bank)
    • ScraperService fetches HTML and parses
    • Strategy returns float value
    • Command saves to ReferenceRecord
    • Observer invalidates cache
  4. Log completion

Performance Optimizations

Caching Strategy

  • TTL: 1 hour (rates don’t change frequently)
  • Key Format: tasas_bancos_YYYY-MM-DD
  • Invalidation: Automatic via Observer
  • Driver: Configurable (file, database, Redis)

Database Indexing

Recommended indexes:
CREATE INDEX idx_source_date ON reference_records(source, date);
CREATE INDEX idx_date ON reference_records(date);

HTTP Optimization

  • Reuse HTTP client connections
  • Set reasonable timeouts
  • Handle failures gracefully

Error Handling

API Layer

  • 404 when no rates available
  • 400 for invalid date formats
  • 404 for unknown routes (fallback handler)

Scraper Layer

  • Try/catch around each bank fetch
  • Log errors but continue with next bank
  • Throw exceptions for missing DOM elements

Command Layer

  • Per-bank error isolation
  • Comprehensive logging
  • Graceful degradation

Security Considerations

Input Validation

private function validateDate(&$date)
{
    $dt = Carbon::parse($date);
    return $dt->lessThanOrEqualTo(now());
}

Rate Limiting

No built-in rate limiting (consider adding Laravel’s throttle middleware for production).

CORS

Configure in config/cors.php for production deployments.

Next Steps

ScraperService Deep Dive

Learn how web scraping works in detail

Strategy Pattern

Understand the Strategy pattern implementation

Adding Banks

Extend the system with new data sources

Scheduler Setup

Configure automatic rate updates

Build docs developers (and LLMs) love