Skip to main content

Qué se mide

SYNTIweb registra 6 tipos de eventos para cada tenant:
  1. pageview → Visita a la landing
  2. click_whatsapp → Clic en botón de WhatsApp
  3. click_call → Clic en botón de llamada telefónica
  4. click_toggle_currency → Clic en toggle REF/Bs
  5. qr_scan → Escaneo del QR de tracking
  6. time_on_page → Tiempo de permanencia (cada 30 segundos)
El sistema NO usa cookies ni Google Analytics — todo se registra en la tabla analytics_events de la base de datos.

Tracking desde el frontend

La landing incluye un script JavaScript que envía eventos al backend vía POST:
// landing/templates/studio.blade.php:294
if (e.target.closest('a[href*="wa.me"]')) track('click_whatsapp');
if (e.target.closest('a[href^="tel:"]')) track('click_call');

Función track()

function track(eventType, metadata = {}) {
    fetch('/api/analytics/track', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content
        },
        body: JSON.stringify({
            tenant_id: {{ $tenant->id }},
            event_type: eventType,
            metadata: metadata
        })
    });
}

Endpoint de tracking

// AnalyticsController.php:23-88
public function track(Request $request): JsonResponse
{
    $validated = $request->validate([
        'tenant_id' => 'required|integer|exists:tenants,id',
        'event_type' => 'required|string|in:pageview,click_whatsapp,click_call,click_toggle_currency,time_on_page,qr_scan',
        'metadata' => 'nullable|array'
    ]);

    $tenantId = $validated['tenant_id'];
    $eventType = $validated['event_type'];

    // Rate limiting: máx 100 eventos/minuto por tenant
    $rateLimitKey = "analytics_rate_limit:{$tenantId}";
    $currentCount = Cache::get($rateLimitKey, 0);

    if ($currentCount >= 100) {
        return response()->json(['success' => false, 'message' => 'Rate limit exceeded'], 429);
    }

    Cache::put($rateLimitKey, $currentCount + 1, now()->addMinute());

    // Hash de IP (no guardar IP completa)
    $ipHash = hash('sha256', $request->ip() . config('app.key'));
    $now = now();

    AnalyticsEvent::create([
        'tenant_id' => $tenantId,
        'event_type' => $eventType,
        'user_ip' => substr($ipHash, 0, 45),
        'user_agent' => $request->userAgent(),
        'referer' => $request->header('referer'),
        'event_date' => $now->toDateString(),
        'event_hour' => (int) $now->format('H')
    ]);

    return response()->json(['success' => true]);
}

Rate limiting

El sistema limita a 100 eventos por minuto por tenant para prevenir:
  • Bots que disparan eventos falsos
  • Scripts maliciosos
  • Errores de loop infinito en el frontend

Estructura de la tabla analytics_events

CREATE TABLE analytics_events (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    tenant_id INT NOT NULL,
    event_type VARCHAR(50) NOT NULL,
    reference_type VARCHAR(50) NULL,
    reference_id INT NULL,
    user_ip VARCHAR(45) NOT NULL,  -- Hash de la IP
    user_agent TEXT,
    referer TEXT,
    event_date DATE NOT NULL,
    event_hour TINYINT NOT NULL,   -- 0-23
    created_at TIMESTAMP,
    INDEX idx_tenant_date (tenant_id, event_date),
    INDEX idx_tenant_type (tenant_id, event_type)
);
Los índices compuestos idx_tenant_date y idx_tenant_type optimizan las queries de analytics que filtran por tenant + fecha o tenant + tipo de evento.

Dashboard: métricas principales

El dashboard muestra estas métricas en tiempo real:

Visitantes únicos

// AnalyticsController.php:105-116
$visitorsToday = AnalyticsEvent::where('tenant_id', $tenantId)
    ->where('event_date', $today)
    ->where('event_type', 'pageview')
    ->distinct('user_ip')
    ->count('user_ip');

$visitorsWeek = AnalyticsEvent::where('tenant_id', $tenantId)
    ->whereBetween('event_date', [$weekAgo, $today])
    ->where('event_type', 'pageview')
    ->distinct('user_ip')
    ->count('user_ip');
Por qué distinct('user_ip'): Evita contar múltiples pageviews de la misma persona como visitantes distintos.

Clics en WhatsApp

// AnalyticsController.php:119-123
$whatsappClicks = AnalyticsEvent::where('tenant_id', $tenantId)
    ->where('event_type', 'click_whatsapp')
    ->whereBetween('event_date', [$weekAgo, $today])
    ->count();

Escaneos QR

// AnalyticsController.php:136-140
$qrScans = AnalyticsEvent::where('tenant_id', $tenantId)
    ->where('event_type', 'qr_scan')
    ->whereBetween('event_date', [$weekAgo, $today])
    ->count();

Tiempo promedio en página

// AnalyticsController.php:143-146
$avgTimeOnPage = AnalyticsEvent::where('tenant_id', $tenantId)
    ->where('event_type', 'time_on_page')
    ->whereBetween('event_date', [$weekAgo, $today])
    ->avg(DB::raw('1')) * 30; // Cada evento = 30 segundos
El frontend envía un evento time_on_page cada 30 segundos que el usuario permanece en la página.

Gráfico de últimos 7 días

// AnalyticsController.php:148-162
$last7Days = [];
for ($i = 6; $i >= 0; $i--) {
    $date = now()->subDays($i)->toDateString();
    $visitors = AnalyticsEvent::where('tenant_id', $tenantId)
        ->where('event_date', $date)
        ->where('event_type', 'pageview')
        ->distinct('user_ip')
        ->count('user_ip');

    $last7Days[] = [
        'date' => $date,
        'visitors' => $visitors
    ];
}
El dashboard renderiza este array como gráfico de línea con Chart.js.

Endpoint para datos del día

// AnalyticsController.php:190-223
public function getToday(int $tenantId): JsonResponse
{
    $today = now()->toDateString();

    $visitors = AnalyticsEvent::where('tenant_id', $tenantId)
        ->where('event_date', $today)
        ->where('event_type', 'pageview')
        ->distinct('user_ip')->count('user_ip');

    $whatsapp = AnalyticsEvent::where('tenant_id', $tenantId)
        ->where('event_date', $today)
        ->where('event_type', 'click_whatsapp')->count();

    $qr = AnalyticsEvent::where('tenant_id', $tenantId)
        ->where('event_date', $today)
        ->where('event_type', 'qr_scan')->count();

    $products = AnalyticsEvent::where('tenant_id', $tenantId)
        ->where('event_date', $today)
        ->where('event_type', 'product_click')->count();

    return response()->json([
        'success' => true,
        'visitors_today' => $visitors,
        'whatsapp_clicks' => $whatsapp,
        'qr_scans' => $qr,
        'products_viewed' => $products
    ]);
}
Este endpoint se llama cada 30 segundos desde el dashboard para actualizar métricas en vivo sin recargar.

Privacidad y GDPR

Hash de IP

$ipHash = hash('sha256', $request->ip() . config('app.key'));
$event->user_ip = substr($ipHash, 0, 45);
La IP real nunca se guarda. Se hashea con SHA-256 + salt del app key. Por qué: Permite contar visitantes únicos sin almacenar datos personales identificables.

Sin cookies

El sistema no usa cookies ni localStorage — cada pageview se registra como nuevo evento. Limitación: Si un usuario visita 3 veces en el día desde la misma red (misma IP hasheada), cuenta como 1 visitante único.

Tracking por referencia

La tabla incluye campos reference_type y reference_id para eventos específicos:
AnalyticsEvent::create([
    'tenant_id' => $tenantId,
    'event_type' => 'product_click',
    'reference_type' => 'product',
    'reference_id' => $productId,
    // ...
]);
Uso: Medir qué productos específicos generan más clics.

Eventos por hora del día

El campo event_hour (0-23) permite analizar:
  • ¿A qué hora hay más visitas?
  • ¿Cuándo es mejor publicar ofertas?
// Ejemplo: heatmap de visitas por hora
$hourlyData = AnalyticsEvent::where('tenant_id', $tenantId)
    ->where('event_type', 'pageview')
    ->whereBetween('event_date', [$weekAgo, $today])
    ->select('event_hour', DB::raw('count(*) as total'))
    ->groupBy('event_hour')
    ->orderBy('event_hour')
    ->get();
La Fase F (futura) incluirá gráfico de heatmap por hora para identificar horarios pico.

Comparación con Google Analytics

FeatureSYNTIweb AnalyticsGoogle Analytics
Visitantes únicos✅ (por IP hash)✅ (por cookie)
Eventos personalizados
Tiempo en página✅ (30s intervals)✅ (exacto)
Embudo de conversión❌ (Fase F)
Datos en tiempo real
Privacidad GDPR✅ (sin cookies)⚠️ (requiere banner)
SYNTIweb analytics está optimizado para negocios venezolanos que necesitan métricas simples (visitantes, WhatsApp, QR) sin complicaciones legales.

Build docs developers (and LLMs) love