SimpleStats en Laravel: Analytics GDPR sin Dependencias
Introducción: El Dilema de las Analytics en Laravel
Cuando desarrollamos una aplicación web moderna, necesitamos entender el comportamiento de nuestros usuarios: qué canales traen más tráfico, cuál es la tasa de conversión real, cuánto ingresos genera cada campaña. Sin embargo, soluciones populares como Google Analytics tienen un problema fundamental: envían datos a servidores externos, violando la privacidad del usuario y generando complicaciones con GDPR.
SimpleStats emerge como una solución elegante: un stack de analytics construido específicamente para Laravel que resuelve este dilema. Es server-side, GDPR-compliant (cumple normativas de privacidad), y más importante aún, vincula cada visitante, registro y pago a la campaña que lo originó.
En este artículo, exploraremos cómo integrar SimpleStats en tu aplicación Laravel, capturar datos de atribución correctamente, y construir dashboards que realmente te digan dónde viene tu dinero.
¿Qué es SimpleStats y por qué debería importarte?
El Problema con Analytics Tradicionales
Las soluciones convencionales tienen limitaciones críticas:
- Privacidad comprometida: Los datos viajan a terceros
- Tracking incompleto: No conectan visitantes con pagos
- Compliance complejo: GDPR, CCPA y normativas locales requieren consentimiento explícito
- Dependencias externas: Si el servicio falla, pierdes datos
Por qué SimpleStats es Diferente
SimpleStats es una plataforma de analytics server-side construida para Laravel que:
- Mantiene todo local - Los datos nunca salen de tu servidor
- Rastrea atribución - Vincula cada conversión a su fuente original
- Es GDPR-compliant - Cumple normativas de privacidad por defecto
- Integración nativa - Diseñada pensando en Laravel desde el inicio
Instalación y Configuración Inicial
Paso 1: Instalación del Paquete
Primero, instala SimpleStats a través de Composer:
composer require simplestats/laravel
Luego, publica los assets y migraciones:
php artisan vendor:publish --provider="SimpleStats\Laravel\SimpleStatsServiceProvider"
php artisan migrate
Paso 2: Configurar el Archivo .env
Añade las variables de configuración en tu .env:
SIMPLESTATS_ENABLED=true
SIMPLESTATS_HASH_IPS=true
SIMPLESTATS_RETENTION_DAYS=365
SIMPLESTATS_TRACK_REVENUE=true
Paso 3: Publicar Configuración
php artisan vendor:publish --tag=simplestats-config
Esto crea config/simplestats.php donde puedes personalizar el comportamiento:
<?php
return [
'enabled' => env('SIMPLESTATS_ENABLED', true),
'hash_ips' => env('SIMPLESTATS_HASH_IPS', true),
'retention_days' => env('SIMPLESTATS_RETENTION_DAYS', 365),
'track_revenue' => env('SIMPLESTATS_TRACK_REVENUE', true),
'attribution_window_days' => 30,
'database' => 'mysql',
];
Capturando Eventos: Visitantes, Signups y Pagos
SimpleStats rastrea tres eventos fundamentales. Veamos cómo implementar cada uno.
Evento 1: Rastrear Visitantes
El rastreo automático de visitantes se activa mediante un middleware que se registra automáticamente:
// config/simplestats.php
'middleware' => [
\SimpleStats\Laravel\Middleware\TrackPageViews::class,
],
Este middleware captura automáticamente:
- URL visitada
- Referrer (origen)
- User-Agent del navegador
- IP hasheada (opcional)
- Timestamp
Para eventos personalizados, usa la facade:
<?php
namespace App\Http\Controllers;
use SimpleStats\Facades\Analytics;
class HomeController extends Controller
{
public function index()
{
// Registrar evento personalizado
Analytics::trackEvent('homepage_viewed', [
'section' => 'hero',
'duration' => 5,
]);
return view('home');
}
}
Evento 2: Rastrear Registros (Signups)
Cuando un usuario se registra, queremos vincular ese evento a la campaña que lo trajo:
<?php
namespace App\Http\Controllers\Auth;
use SimpleStats\Facades\Analytics;
use App\Models\User;
class RegisterController extends Controller
{
public function store(Request $request)
{
$validated = $request->validate([
'email' => 'required|email|unique:users',
'name' => 'required|string',
'password' => 'required|min:8|confirmed',
]);
$user = User::create([
'email' => $validated['email'],
'name' => $validated['name'],
'password' => bcrypt($validated['password']),
]);
// Registrar signup vinculado a la campaña
Analytics::trackSignup(
user_id: $user->id,
email: $user->email,
metadata: [
'plan' => 'free',
'source' => 'organic',
]
);
return redirect('/dashboard');
}
}
Evento 3: Rastrear Ingresos
Este es el evento más crítico. Cada pago debe vincularse a su origen:
<?php
namespace App\Http\Controllers;
use SimpleStats\Facades\Analytics;
use App\Models\Subscription;
class SubscriptionController extends Controller
{
public function store(Request $request)
{
$plan = $request->input('plan');
$price = $this->getPlanPrice($plan);
// Procesar pago (Stripe, PayPal, etc.)
$payment = $this->processPayment($price);
if ($payment->successful()) {
$subscription = Subscription::create([
'user_id' => auth()->id(),
'plan' => $plan,
'amount' => $price,
'stripe_id' => $payment->id,
]);
// Registrar conversión monetaria
Analytics::trackRevenue(
user_id: auth()->id(),
amount: $price,
currency: 'USD',
transaction_id: $payment->id,
plan: $plan,
metadata: [
'subscription_id' => $subscription->id,
]
);
return response()->json(['success' => true]);
}
return response()->json(['error' => 'Payment failed'], 400);
}
}
Rastreo de Atribución: Vincular Campañas con Conversiones
La magia de SimpleStats está en la atribución. Debes asegurar que los parámetros UTM (o tu propio sistema) se capturen en el primer contacto.
Capturar Parámetros UTM
<?php
namespace App\Http\Middleware;
use SimpleStats\Facades\Analytics;
use Closure;
class CaptureUTMParameters
{
public function handle($request, Closure $next)
{
$utm = [
'source' => $request->query('utm_source'),
'medium' => $request->query('utm_medium'),
'campaign' => $request->query('utm_campaign'),
'content' => $request->query('utm_content'),
'term' => $request->query('utm_term'),
];
// Guardar en sesión para vincular con eventos posteriores
if (array_filter($utm)) {
session(['utm_data' => $utm]);
Analytics::trackCampaignSource(
source: $utm['source'],
medium: $utm['medium'],
campaign: $utm['campaign'],
metadata: $utm
);
}
return $next($request);
}
}
Registra este middleware en app/Http/Kernel.php:
protected $middleware = [
// ...
\App\Http\Middleware\CaptureUTMParameters::class,
];
Configurar la Ventana de Atribución
Por defecto, SimpleStats usa una ventana de 30 días. Personalízala según tus necesidades:
// config/simplestats.php
'attribution_window_days' => 14, // Para ecommerce de corta conversión
Consultando los Datos: El Query Builder
SimpleStats proporciona un fluido query builder para extraer insights:
<?php
namespace App\Http\Controllers;
use SimpleStats\Facades\Analytics;
use Illuminate\Support\Facades\DB;
class DashboardController extends Controller
{
public function index()
{
// Visitantes únicos por fuente en los últimos 30 días
$visitorsBySource = Analytics::query()
->selectRaw('utm_source, COUNT(DISTINCT visitor_id) as count')
->whereDateBetween('created_at', now()->subDays(30), now())
->groupBy('utm_source')
->orderByDesc('count')
->get();
// ROI por campaña
$roiByCampaign = Analytics::query()
->selectRaw(
'utm_campaign,
COUNT(DISTINCT visitor_id) as visitors,
COUNT(DISTINCT signup_id) as signups,
COALESCE(SUM(revenue), 0) as total_revenue'
)
->whereDateBetween('created_at', now()->subDays(90), now())
->groupBy('utm_campaign')
->orderByDesc('total_revenue')
->get();
$roiByCampaign = $roiByCampaign->map(function ($item) {
return [
'campaign' => $item->utm_campaign,
'visitors' => $item->visitors,
'signups' => $item->signups,
'conversion_rate' => ($item->signups / $item->visitors * 100),
'revenue' => $item->total_revenue,
'roi' => ($item->total_revenue / ($item->visitors * 0.1)), // Costo estimado
];
});
return view('dashboard', [
'visitorsBySource' => $visitorsBySource,
'roiByCampaign' => $roiByCampaign,
]);
}
}
Construir un Dashboard Interactivo
Veamos cómo renderizar estos datos en una vista Blade:
@extends('layouts.app')
@section('content')
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<!-- Tarjeta ROI por Campaña -->
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg mb-6">
<div class="p-6">
<h2 class="text-xl font-bold mb-4">ROI por Campaña (últimos 90 días)</h2>
<table class="w-full">
<thead>
<tr class="border-b">
<th class="text-left py-2">Campaña</th>
<th class="text-right py-2">Visitantes</th>
<th class="text-right py-2">Registros</th>
<th class="text-right py-2">Conversión</th>
<th class="text-right py-2">Ingresos</th>
<th class="text-right py-2">ROI</th>
</tr>
</thead>
<tbody>
@foreach($roiByCampaign as $campaign)
<tr class="border-b hover:bg-gray-50">
<td class="py-3">{{ $campaign['campaign'] }}</td>
<td class="text-right">{{ number_format($campaign['visitors']) }}</td>
<td class="text-right">{{ number_format($campaign['signups']) }}</td>
<td class="text-right">
<span class="badge badge-primary">
{{ number_format($campaign['conversion_rate'], 2) }}%
</span>
</td>
<td class="text-right font-bold">${{ number_format($campaign['revenue'], 2) }}</td>
<td class="text-right">
@if($campaign['roi'] > 0)
<span class="text-green-600">+{{ number_format($campaign['roi'], 0) }}%</span>
@else
<span class="text-red-600">{{ number_format($campaign['roi'], 0) }}%</span>
@endif
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
<!-- Visitantes por Fuente -->
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6">
<h2 class="text-xl font-bold mb-4">Visitantes por Fuente (últimos 30 días)</h2>
<div class="grid grid-cols-3 gap-4">
@foreach($visitorsBySource as $source)
<div class="bg-gradient-to-br from-blue-50 to-blue-100 p-4 rounded-lg">
<p class="text-gray-600 text-sm uppercase">{{ $source->utm_source ?? 'Directo' }}</p>
<p class="text-3xl font-bold text-blue-600">{{ number_format($source->count) }}</p>
</div>
@endforeach
</div>
</div>
</div>
</div>
</div>
@endsection
Privacidad y Cumplimiento GDPR
SimpleStats está diseñado para privacidad desde el inicio. Algunos detalles importantes:
Hashing de IPs
Las IPs se hashean por defecto para anonimizar visitantes:
// config/simplestats.php
'hash_ips' => true, // Recomendado para GDPR
Retención de Datos
Configura cuánto tiempo almacenar datos:
// config/simplestats.php
'retention_days' => 365, // Eliminar datos después de 1 año
SimpleStats limpia automáticamente registros antiguos:
php artisan simplestats:prune
Añade esto a tu app/Console/Kernel.php para automatizar:
protected function schedule(Schedule $schedule)
{
$schedule->command('simplestats:prune')
->daily()
->at('02:00');
}
Aviso de Privacidad
Aunque SimpleStats es GDPR-compliant, comunica claramente a los usuarios que recopiles datos:
<!-- En footer o política -->
<p class="text-sm text-gray-600">
Utilizamos analytics propios para entender cómo usas nuestro sitio.
No compartimos tus datos