Skip to main content

Por qué REF en lugar de USD

Venezuela tiene una economía dolarizada informal — los negocios manejan dólares pero de forma mixta con bolívares. Mostrar precios como ”$” puede confundir porque:
  • No todos los clientes pagan en efectivo USD
  • La tasa BCV cambia diario
  • Algunos negocios prefieren mostrar solo bolívares
Por eso SYNTIweb usa REF (precio de referencia) como símbolo neutral.
El dueño puede personalizar el símbolo desde el dashboard (Diseño → Moneda). Algunos usan ”$”, “USD”, “Precio”, etc.

Los 4 modos de visualización

El dueño elige cómo mostrar precios desde Dashboard → Diseño → Moneda:

1. Solo REF (reference_only)

Muestra únicamente el precio en moneda de referencia.
Pizza Margarita
8.00 REF

2. Solo Bolívares (bolivares_only)

Convierte automáticamente con la tasa BCV del día.
Pizza Margarita
292.00 Bs.

3. Toggle REF/Bs (both_toggle)

Muestra un botón que permite al cliente cambiar entre REF y Bs.
Pizza Margarita
8.00 REF   [🔄 Ver en Bs]
Al tocar el botón → 292.00 Bs.

4. Ocultar precios (hidden)

No muestra ningún precio. Útil para negocios que cotizan por WhatsApp.

Configuración en el backend

// DashboardController.php:893-944
public function updateCurrencyConfig(Request $request, int $tenantId): JsonResponse
{
    $displayMode = $request->input('display_mode', 'reference_only');
    $symbol = $request->input('symbol', 'REF');

    // Mapear display_mode a flags booleanos
    $showReference = in_array($displayMode, ['reference_only', 'both_toggle', 'euro_toggle']);
    $showBolivares = in_array($displayMode, ['bolivares_only', 'both_toggle', 'euro_toggle']);
    $showEuro = $displayMode === 'euro_toggle';
    $hidePrice = $displayMode === 'hidden';
    $hasToggle = in_array($displayMode, ['both_toggle', 'euro_toggle']);

    $settings['engine_settings']['currency']['display']['show_reference'] = $showReference;
    $settings['engine_settings']['currency']['display']['show_bolivares'] = $showBolivares;
    $settings['engine_settings']['currency']['display']['show_euro'] = $showEuro;
    $settings['engine_settings']['currency']['display']['hide_price'] = $hidePrice;
    $settings['engine_settings']['currency']['display']['has_toggle'] = $hasToggle;
    $settings['engine_settings']['currency']['display']['symbols']['reference'] = $symbol;
    $settings['engine_settings']['currency']['display']['saved_display_mode'] = $displayMode;

    $tenant->settings = $settings;
    $tenant->save();
}

Tasa BCV automática

SYNTIweb actualiza la tasa del dólar y el euro cada hora desde la API de DolarToday/BCV.

Servicio DollarRateService

// DollarRateService.php:35-40
private const API_URL = 'https://ve.dolarapi.com/v1/dolares/oficial';
private const EURO_API_URL = 'https://ve.dolarapi.com/v1/euros/oficial';
private const CACHE_TTL = 3600; // 1 hora

Fetch y almacenamiento

// DollarRateService.php:130-236
public function fetchAndStore(): array
{
    $response = Http::timeout(10)->acceptJson()->get(self::API_URL);

    if (!$response->successful()) {
        return ['success' => false, 'message' => "API returned status {$response->status()}"];
    }

    $data = $response->json();
    $newRate = (float) $data['promedio'];

    // Validar que la tasa no haya cambiado más de 10% (protección contra datos erróneos)
    $previousRate = $this->getCurrentRate();
    if ($previousRate !== null) {
        $changePercent = abs(($newRate - $previousRate) / $previousRate) * 100;
        if ($changePercent > 10.0) {
            Log::warning('DollarRateService: Unusual rate change detected', [
                'previous_rate' => $previousRate,
                'new_rate' => $newRate,
                'change_percent' => round($changePercent, 2)
            ]);
        }
    }

    // Desactivar tasas anteriores
    DollarRate::where('currency_type', 'USD')
        ->where('is_active', true)
        ->update(['is_active' => false, 'effective_until' => Carbon::now()]);

    // Crear nuevo registro
    DollarRate::create([
        'rate' => $newRate,
        'source' => 'dolarapi',
        'currency_type' => 'USD',
        'effective_from' => Carbon::now(),
        'is_active' => true
    ]);

    Cache::forget('dollar_rate_current');
}

Propagación a todos los tenants

// DollarRateService.php:394-489
public function propagateRateToTenants(?float $rate = null): array
{
    $rate ??= $this->getCurrentRate();
    $tenants = Tenant::where('status', 'active')->get();
    $updatedCount = 0;

    foreach ($tenants as $tenant) {
        $settings = $tenant->settings ?? [];
        $autoUpdate = data_get($settings, 'engine_settings.currency.auto_update', true);

        if (!$autoUpdate) {
            continue; // El tenant desactivó actualizaciones automáticas
        }

        data_set($settings, 'engine_settings.currency.exchange_rate', $rate);
        data_set($settings, 'engine_settings.currency.source', 'dolarapi');
        data_set($settings, 'engine_settings.currency.last_update', now()->toDateString());

        $tenant->settings = $settings;
        $tenant->save();
        $updatedCount++;
    }

    return ['success' => true, 'updated_count' => $updatedCount];
}
Los tenants pueden desactivar actualizaciones automáticas si prefieren fijar una tasa manualmente (por ejemplo, casas de cambio).

Cache de tasas

// DollarRateService.php:58-87
public function getCurrentRate(): float
{
    return Cache::remember('dollar_rate_current', 3600, function (): float {
        $rate = DollarRate::where('currency_type', 'USD')
            ->where('is_active', true)
            ->orderByDesc('effective_from')
            ->first();

        if ($rate === null) {
            Log::warning('No active USD rate found, using fallback');
            return 36.50; // Fallback
        }

        return (float) $rate->rate;
    });
}
La tasa se cachea por 1 hora para reducir queries a la base de datos.

Historial de tasas

// DollarRateService.php:497-525
public function getHistoricalRates(int $days = 30): array
{
    $rates = DollarRate::where('effective_from', '>=', Carbon::now()->subDays($days))
        ->orderByDesc('effective_from')
        ->get();

    return $rates->map(function (DollarRate $rate): array {
        return [
            'rate' => (float) $rate->rate,
            'source' => $rate->source,
            'date' => $rate->effective_from->toDateTimeString(),
            'is_active' => $rate->is_active
        ];
    })->toArray();
}
El dashboard puede mostrar un gráfico de evolución de la tasa en los últimos 30 días usando este método.

Comando Artisan

La actualización automática se ejecuta vía cron cada hora:
php artisan dollar:fetch
Este comando llama a DollarRateService::fetchAndPropagateAll() que actualiza USD, EUR y propaga a todos los tenants.

Modo Euro (euro_toggle)

El Plan Anual de SYNTIfood y SYNTIcat incluye soporte para mostrar precios en Euros además de REF y Bs.
// DollarRateService.php:244-314
public function fetchAndStoreEuro(): array
{
    $response = Http::timeout(10)->get(self::EURO_API_URL);
    $newRate = (float) $response->json()['promedio'];

    DollarRate::where('currency_type', 'EUR')
        ->where('is_active', true)
        ->update(['is_active' => false, 'effective_until' => Carbon::now()]);

    DollarRate::create([
        'rate' => $newRate,
        'source' => 'dolarapi',
        'currency_type' => 'EUR',
        'effective_from' => Carbon::now(),
        'is_active' => true
    ]);
}

Build docs developers (and LLMs) love