Skip to main content

Para qué sirven los QR

Los códigos QR permiten a los negocios imprimir stickers y pegarlos en lugares físicos (puerta, mesa, caja) para que los clientes escaneen y accedan a la página web. Lo especial: cada escaneo se registra en analytics, permitiendo al dueño medir cuántos clientes llegaron desde el QR vs desde redes sociales.
Los negocios imprimen el QR en stickers tamaño tarjeta (5×5cm) y los pegan en la entrada, en mesas, o en bolsas de delivery.

Cómo funciona el tracking

1. Generar código único

Cada tenant tiene un código único de 8 caracteres basado en su ID + hash del app key:
// QRService.php:26-31
private function generateUniqueCode(int $tenantId): string
{
    $raw = $tenantId . config('app.key') . 'qr_tracking';
    return substr(hash('sha256', $raw), 0, 8);
}
Ejemplo: Para tenant ID 7 → código a3f9b2c1
// QRService.php:39-43
public function getTrackingShortlink(int $tenantId): string
{
    $uniqueCode = $this->generateUniqueCode($tenantId);
    return "https://syntiweb.me/t/{$tenantId}/{$uniqueCode}";
}
Ejemplo: https://syntiweb.me/t/7/a3f9b2c1

3. Generar QR SVG

// QRService.php:52-61
public function generateQR(int $tenantId, int $size = 300): string
{
    $trackingUrl = $this->getTrackingShortlink($tenantId);
    
    return (string) QrCode::size($size)
        ->margin(1)
        ->errorCorrection('M')
        ->generate($trackingUrl);
}
El QR se genera como SVG por defecto (escala sin perder calidad).

4. Descargar QR como PNG

// QRService.php:70-80
public function generateQRPNG(int $tenantId, int $size = 300): string
{
    $trackingUrl = $this->getTrackingShortlink($tenantId);
    
    return (string) QrCode::format('png')
        ->size($size)
        ->margin(1)
        ->errorCorrection('M')
        ->generate($trackingUrl);
}
Desde el dashboard, el dueño puede descargar el QR en formato PNG (300×300px) listo para imprimir.

Redirección y registro de eventos

Cuando un cliente escanea el QR, el flujo es:
  1. URL: https://syntiweb.me/t/{tenantId}/{code}
  2. Servidor verifica código único
  3. Registra evento qr_scan en analytics
  4. Redirige a la landing del tenant
// QRTrackingController.php:26-67
public function handleShortlink(int $tenantId, string $code): RedirectResponse
{
    // Verificar código válido
    if (!$this->qrService->verifyUniqueCode($tenantId, $code)) {
        abort(404, 'Invalid QR code');
    }

    $tenant = Tenant::find($tenantId);
    if (!$tenant) {
        abort(404, 'Tenant not found');
    }

    // Registrar evento qr_scan
    $ipHash = hash('sha256', request()->ip() . config('app.key'));
    $now = now();
    
    AnalyticsEvent::create([
        'tenant_id' => $tenantId,
        'event_type' => 'qr_scan',
        'user_ip' => substr($ipHash, 0, 45), // Hash por privacidad
        'user_agent' => request()->userAgent(),
        'referer' => request()->header('referer'),
        'event_date' => $now->toDateString(),
        'event_hour' => (int) $now->format('H')
    ]);

    // Redirigir a landing
    return redirect(url('/' . $tenant->subdomain));
}
El sistema NO guarda la IP real del cliente — guarda un hash SHA-256 de la IP + app key para proteger privacidad.

Verificación de código único

// QRService.php:89-92
public function verifyUniqueCode(int $tenantId, string $code): bool
{
    return $this->generateUniqueCode($tenantId) === $code;
}
Esto previene que alguien intente acceder a /t/7/codigo_falso — el sistema valida que el código corresponda al tenant.

Dashboard: visualizar QR

Al cargar el dashboard, se genera el QR automáticamente:
// DashboardController.php:94-95
$trackingQR = $this->qrService->generateQR($tenant->id, 300);
$trackingShortlink = $this->qrService->getTrackingShortlink($tenant->id);
El QR se muestra en el tab Analytics → QR Code con opciones:
  • Ver QR en pantalla (SVG)
  • Descargar PNG
  • Copiar shortlink

Ruta de descarga

// QRTrackingController.php:74-87
public function downloadQR(int $id): Response
{
    $tenant = Tenant::findOrFail($id);
    $qrPng = $this->qrService->generateQRPNG($tenant->id, 300);
    $filename = $tenant->subdomain . '_qr.png';
    
    return response($qrPng, 200)
        ->header('Content-Type', 'image/png')
        ->header('Content-Disposition', 'attachment; filename="' . $filename . '"');
}
Ejemplo: Descargar QR para tenant pizzaexpress → archivo pizzaexpress_qr.png

Analytics: escaneos QR

Los escaneos se registran con event_type = 'qr_scan' y pueden consultarse desde el dashboard:
// AnalyticsController.php:136-140
$qrScans = AnalyticsEvent::where('tenant_id', $tenantId)
    ->where('event_type', 'qr_scan')
    ->whereBetween('event_date', [$weekAgo, $today])
    ->count();
El dashboard muestra:
  • Escaneos hoy: Cuántos QR se escanearon hoy
  • Escaneos esta semana: Total últimos 7 días
  • Gráfico de tendencia: Escaneos por día
Los negocios pueden A/B test dónde colocar el QR (entrada vs mesa) comparando días con distinta ubicación.

Formato del código

El código único tiene 8 caracteres alfanuméricos (256^8 combinaciones posibles = ~18 quintillones). Por qué 8 caracteres: Balance entre URLs cortas y probabilidad cero de colisión.

Biblioteca QR usada

SYNTIweb usa simplesoftwareio/simple-qrcode (wrapper de BaconQrCode):
use SimpleSoftwareIO\QrCode\Facades\QrCode;
Configuración:
  • size(300): 300×300 píxeles
  • margin(1): Margen mínimo (para maximizar espacio)
  • errorCorrection(‘M’): Nivel medio (tolera hasta 15% de daño)
El nivel M de corrección de errores permite que el QR siga funcionando aunque parte del sticker esté rayado o sucio.

Dominio syntiweb.me

Los shortlinks usan el dominio syntiweb.me (más corto que syntiweb.com) para:
  1. URLs más cortas → QR más simple
  2. Separar tracking de landing
  3. Posibilidad de rotar dominios sin afectar landing
Ruta en web.php:
Route::get('/t/{tenantId}/{code}', [QRTrackingController::class, 'handleShortlink']);

Build docs developers (and LLMs) love