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
2. Generar shortlink
// 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:
- URL:
https://syntiweb.me/t/{tenantId}/{code}
- Servidor verifica código único
- Registra evento
qr_scan en analytics
- 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.
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:
- URLs más cortas → QR más simple
- Separar tracking de landing
- Posibilidad de rotar dominios sin afectar landing
Ruta en web.php:
Route::get('/t/{tenantId}/{code}', [QRTrackingController::class, 'handleShortlink']);