diff --git a/app/Filament/Resources/BookingResource.php b/app/Filament/Resources/BookingResource.php index 6ef9041..4fe2097 100644 --- a/app/Filament/Resources/BookingResource.php +++ b/app/Filament/Resources/BookingResource.php @@ -17,7 +17,10 @@ class BookingResource extends Resource { protected static ?string $model = Booking::class; - protected static ?string $navigationIcon = 'heroicon-o-rectangle-stack'; + protected static ?string $navigationIcon = 'heroicon-o-ticket'; + protected static ?string $navigationLabel = 'Bookings'; + protected static ?string $navigationGroup = 'Demo TAXILANZ'; + protected static ?int $navigationSort = 3; public static function form(Form $form): Form { diff --git a/app/Filament/Resources/EventResource.php b/app/Filament/Resources/EventResource.php index d134346..16892ff 100644 --- a/app/Filament/Resources/EventResource.php +++ b/app/Filament/Resources/EventResource.php @@ -17,7 +17,10 @@ class EventResource extends Resource { protected static ?string $model = Event::class; - protected static ?string $navigationIcon = 'heroicon-o-rectangle-stack'; + protected static ?string $navigationIcon = 'heroicon-o-bolt'; + protected static ?string $navigationLabel = 'Events'; + protected static ?string $navigationGroup = 'Demo TAXILANZ'; + protected static ?int $navigationSort = 4; public static function form(Form $form): Form { diff --git a/app/Filament/Resources/OfferResource.php b/app/Filament/Resources/OfferResource.php index 659d5ef..6a418ed 100644 --- a/app/Filament/Resources/OfferResource.php +++ b/app/Filament/Resources/OfferResource.php @@ -17,7 +17,10 @@ class OfferResource extends Resource { protected static ?string $model = Offer::class; - protected static ?string $navigationIcon = 'heroicon-o-rectangle-stack'; + protected static ?string $navigationIcon = 'heroicon-o-sparkles'; + protected static ?string $navigationLabel = 'Offers'; + protected static ?string $navigationGroup = 'Demo TAXILANZ'; + protected static ?int $navigationSort = 1; public static function form(Form $form): Form { diff --git a/app/Filament/Resources/RideResource.php b/app/Filament/Resources/RideResource.php index c138473..6360d67 100644 --- a/app/Filament/Resources/RideResource.php +++ b/app/Filament/Resources/RideResource.php @@ -17,7 +17,10 @@ class RideResource extends Resource { protected static ?string $model = Ride::class; - protected static ?string $navigationIcon = 'heroicon-o-rectangle-stack'; + protected static ?string $navigationIcon = 'heroicon-o-map'; + protected static ?string $navigationLabel = 'Rides'; + protected static ?string $navigationGroup = 'Demo TAXILANZ'; + protected static ?int $navigationSort = 2; public static function form(Form $form): Form { diff --git a/app/Filament/Widgets/FunnelOverview.php b/app/Filament/Widgets/FunnelOverview.php new file mode 100644 index 0000000..fde3022 --- /dev/null +++ b/app/Filament/Widgets/FunnelOverview.php @@ -0,0 +1,43 @@ +count(); + $clicks = Event::where('event_type', 'recommendation_clicked')->count(); + $bookings = Booking::count(); + $gmv = (float) Booking::sum('amount'); + $commission = (float) Booking::sum('commission_amount'); + + $viewToClick = $views > 0 ? round(($clicks / $views) * 100, 1) : 0; + $rideToBooking = $rides > 0 ? round(($bookings / $rides) * 100, 1) : 0; + + return [ + Stat::make('Taxi requests', number_format($rides)) + ->description('Entradas del funnel') + ->color('primary'), + Stat::make('Recommendation views', number_format($views)) + ->description('Usuarios expuestos a propuestas') + ->color('info'), + Stat::make('Clicks', number_format($clicks)) + ->description('CTR vista → clic: '.$viewToClick.'%') + ->color('warning'), + Stat::make('Bookings', number_format($bookings)) + ->description('Conversión ride → booking: '.$rideToBooking.'% · GMV €'.number_format($gmv, 0)) + ->color('success'), + Stat::make('Comisión estimada', '€'.number_format($commission, 0)) + ->description('Valor económico demo ya atribuible') + ->color('primary'), + ]; + } +} diff --git a/app/Filament/Widgets/FunnelStageChart.php b/app/Filament/Widgets/FunnelStageChart.php new file mode 100644 index 0000000..a35830f --- /dev/null +++ b/app/Filament/Widgets/FunnelStageChart.php @@ -0,0 +1,37 @@ +count(); + $clicks = Event::where('event_type', 'recommendation_clicked')->count(); + $bookings = Booking::count(); + + return [ + 'datasets' => [[ + 'label' => 'Eventos del funnel', + 'data' => [$rides, $views, $clicks, $bookings], + 'backgroundColor' => ['#0f766e', '#14b8a6', '#f59e0b', '#16a34a'], + 'borderRadius' => 10, + 'borderSkipped' => false, + ]], + 'labels' => ['Taxi requests', 'Recommendation views', 'Clicks', 'Bookings'], + ]; + } + + protected function getType(): string + { + return 'bar'; + } +} diff --git a/app/Filament/Widgets/SourceChannelChart.php b/app/Filament/Widgets/SourceChannelChart.php new file mode 100644 index 0000000..376979e --- /dev/null +++ b/app/Filament/Widgets/SourceChannelChart.php @@ -0,0 +1,35 @@ +selectRaw('source_channel, COUNT(*) as aggregate') + ->groupBy('source_channel') + ->orderByDesc('aggregate') + ->pluck('aggregate', 'source_channel'); + + return [ + 'datasets' => [[ + 'label' => 'Solicitudes', + 'data' => $channels->values()->all(), + 'backgroundColor' => ['#0f766e', '#14b8a6', '#f59e0b', '#0f172a'], + 'borderWidth' => 0, + ]], + 'labels' => $channels->keys()->map(fn (string $channel) => ucfirst($channel))->all(), + ]; + } + + protected function getType(): string + { + return 'bar'; + } +} diff --git a/app/Http/Controllers/HomeController.php b/app/Http/Controllers/HomeController.php index 2e562e8..4b63eed 100644 --- a/app/Http/Controllers/HomeController.php +++ b/app/Http/Controllers/HomeController.php @@ -2,6 +2,7 @@ namespace App\Http\Controllers; +use App\Models\Booking; use App\Models\Event; use App\Models\Offer; use App\Models\Ride; @@ -10,12 +11,23 @@ class HomeController extends Controller { public function __invoke() { + $rides = Ride::count(); + $views = Event::where('event_type', 'recommendation_viewed')->count(); + $clicks = Event::where('event_type', 'recommendation_clicked')->count(); + $bookings = Booking::count(); + $gmv = (float) Booking::sum('amount'); + $commission = (float) Booking::sum('commission_amount'); + return view('home', [ 'featuredOffers' => Offer::query()->where('status', 'published')->orderByDesc('is_featured')->orderByDesc('priority_score')->take(3)->get(), 'metrics' => [ - 'rides' => Ride::count(), - 'views' => Event::where('event_type', 'recommendation_viewed')->count(), - 'bookings' => Event::where('event_type', 'booking_completed')->count(), + 'rides' => $rides, + 'views' => $views, + 'clicks' => $clicks, + 'bookings' => $bookings, + 'gmv' => $gmv, + 'commission' => $commission, + 'ride_to_booking_rate' => $rides > 0 ? round(($bookings / $rides) * 100, 1) : 0, ], ]); } diff --git a/app/Http/Controllers/RideController.php b/app/Http/Controllers/RideController.php index 732a4dc..aec6f69 100644 --- a/app/Http/Controllers/RideController.php +++ b/app/Http/Controllers/RideController.php @@ -104,10 +104,14 @@ class RideController extends Controller $normalized = mb_strtolower($destinationLabel); return match (true) { - str_contains($normalized, 'beach') => 'beach', - str_contains($normalized, 'old town') => 'old town', + str_contains($normalized, 'puerto del carmen') => 'puerto del carmen', + str_contains($normalized, 'playa blanca') => 'playa blanca', + str_contains($normalized, 'costa teguise') => 'costa teguise', + str_contains($normalized, 'arrecife') => 'arrecife', str_contains($normalized, 'marina') => 'marina', - str_contains($normalized, 'center'), str_contains($normalized, 'centre') => 'city center', + str_contains($normalized, 'playa'), str_contains($normalized, 'beach') => 'playa', + str_contains($normalized, 'old town'), str_contains($normalized, 'casco antiguo') => 'casco antiguo', + str_contains($normalized, 'centro'), str_contains($normalized, 'center'), str_contains($normalized, 'centre') => 'centro', default => trim($destinationLabel), }; } @@ -115,6 +119,7 @@ class RideController extends Controller protected function estimateEta(string $pickup, string $destination): int { $seed = strlen($pickup) + strlen($destination); + return max(4, min(16, ($seed % 13) + 4)); } } diff --git a/app/Providers/Filament/AdminPanelProvider.php b/app/Providers/Filament/AdminPanelProvider.php index 6694332..08a0742 100644 --- a/app/Providers/Filament/AdminPanelProvider.php +++ b/app/Providers/Filament/AdminPanelProvider.php @@ -2,6 +2,10 @@ namespace App\Providers\Filament; +use App\Filament\Widgets\FunnelOverview; +use App\Filament\Widgets\FunnelStageChart; +use App\Filament\Widgets\SourceChannelChart; + use Filament\Http\Middleware\Authenticate; use Filament\Http\Middleware\AuthenticateSession; use Filament\Http\Middleware\DisableBladeIconComponents; @@ -28,7 +32,7 @@ class AdminPanelProvider extends PanelProvider ->path('admin') ->login() ->colors([ - 'primary' => Color::Amber, + 'primary' => Color::Teal, ]) ->discoverResources(in: app_path('Filament/Resources'), for: 'App\\Filament\\Resources') ->discoverPages(in: app_path('Filament/Pages'), for: 'App\\Filament\\Pages') @@ -38,7 +42,9 @@ class AdminPanelProvider extends PanelProvider ->discoverWidgets(in: app_path('Filament/Widgets'), for: 'App\\Filament\\Widgets') ->widgets([ Widgets\AccountWidget::class, - Widgets\FilamentInfoWidget::class, + FunnelOverview::class, + FunnelStageChart::class, + SourceChannelChart::class, ]) ->middleware([ EncryptCookies::class, diff --git a/app/Services/RecommendationEngine.php b/app/Services/RecommendationEngine.php index 5bf045a..7efe354 100644 --- a/app/Services/RecommendationEngine.php +++ b/app/Services/RecommendationEngine.php @@ -25,44 +25,45 @@ class RecommendationEngine if ($offer->available_now) { $score += 18; - $reasons[] = 'Available right now'; + $reasons[] = 'Disponible ahora'; } if ($offer->is_featured) { $score += 14; - $reasons[] = 'Featured partner'; + $reasons[] = 'Partner destacado'; } foreach (array_filter([$zone, $destination]) as $needle) { if ($needle !== '' && Str::contains($haystack, $needle)) { $score += 22; - $reasons[] = 'Matches destination context'; + $reasons[] = 'Encaja con el destino'; break; } } if ($hour >= 18 && $offer->category === 'restaurant') { $score += 12; - $reasons[] = 'Good fit for the evening'; + $reasons[] = 'Muy buena opción para esta tarde/noche'; } if ($hour >= 10 && $hour <= 18 && in_array($offer->category, ['experience', 'activity'], true)) { $score += 12; - $reasons[] = 'Works well while waiting or after arrival'; + $reasons[] = 'Funciona bien durante la espera o al llegar'; } if (($offer->duration_minutes ?? 0) > 0 && $offer->duration_minutes <= 120) { $score += 8; - $reasons[] = 'Easy to fit into today'; + $reasons[] = 'Fácil de encajar hoy'; } if (($offer->price_from ?? 0) > 0 && $offer->price_from <= 50) { $score += 6; - $reasons[] = 'Easy price point'; + $reasons[] = 'Precio fácil de aceptar'; } $offer->demo_score = $score; - $offer->demo_reason = collect($reasons)->unique()->take(2)->implode(' · ') ?: 'Strong local fit'; + $offer->demo_reason = collect($reasons)->unique()->take(2)->implode(' · ') ?: 'Buen encaje local'; + return $offer; }) ->sortByDesc(fn (Offer $offer) => [$offer->demo_score, $offer->is_featured, $offer->priority_score]) diff --git a/assets/pasted-20260406-061403-6c6e50ff.png b/assets/pasted-20260406-061403-6c6e50ff.png new file mode 100644 index 0000000..4ea6efd Binary files /dev/null and b/assets/pasted-20260406-061403-6c6e50ff.png differ diff --git a/assets/pasted-20260406-063915-8fc40ab6.png b/assets/pasted-20260406-063915-8fc40ab6.png new file mode 100644 index 0000000..4ea6efd Binary files /dev/null and b/assets/pasted-20260406-063915-8fc40ab6.png differ diff --git a/database/seeders/DemoDataSeeder.php b/database/seeders/DemoDataSeeder.php index 155e4c2..fb07cf0 100644 --- a/database/seeders/DemoDataSeeder.php +++ b/database/seeders/DemoDataSeeder.php @@ -11,11 +11,11 @@ class DemoDataSeeder extends Seeder { $offers = [ [ - 'title' => 'Sunset Marina Table', + 'title' => 'Mesa atardecer en Marina Lanzarote', 'slug' => 'sunset-marina-table', 'category' => 'restaurant', - 'excerpt' => 'A polished waterfront dinner spot for couples or small groups.', - 'description' => 'Priority seating, fast confirmation, and a memorable marina view that works well right after a taxi drop-off.', + 'excerpt' => 'Cena frente al puerto con confirmación rápida para parejas o grupos pequeños.', + 'description' => 'Una propuesta premium pero fácil de cerrar justo después del traslado, con vista a la marina y entrada sin fricción.', 'location_label' => 'Marina', 'price_from' => 42, 'duration_minutes' => 90, @@ -25,12 +25,12 @@ class DemoDataSeeder extends Seeder 'available_now' => true, ], [ - 'title' => 'Old Town Tapas Walk', + 'title' => 'Ruta de tapas por el casco antiguo', 'slug' => 'old-town-tapas-walk', 'category' => 'experience', - 'excerpt' => 'A short guided route through the most photogenic local food spots.', - 'description' => 'Ideal for first-time visitors who want a curated plan without committing half a day.', - 'location_label' => 'Old Town', + 'excerpt' => 'Recorrido corto por paradas fotogénicas y muy fáciles de disfrutar el mismo día.', + 'description' => 'Pensado para primeros visitantes que quieren una experiencia local curada sin bloquear media jornada.', + 'location_label' => 'Casco antiguo', 'price_from' => 34, 'duration_minutes' => 75, 'status' => 'published', @@ -39,12 +39,12 @@ class DemoDataSeeder extends Seeder 'available_now' => true, ], [ - 'title' => 'Beach Club Day Pass', + 'title' => 'Beach club con acceso rápido', 'slug' => 'beach-club-day-pass', 'category' => 'activity', - 'excerpt' => 'Fast-entry pass with towel, lounger, and drink credit included.', - 'description' => 'An easy same-day upgrade for guests heading toward the waterfront zone.', - 'location_label' => 'Beach', + 'excerpt' => 'Acceso directo con tumbona, toalla y crédito de bebida incluido.', + 'description' => 'Ideal para turistas que se dirigen a zona de playa y quieren una mejora simple, inmediata y medible.', + 'location_label' => 'Playa', 'price_from' => 29, 'duration_minutes' => 120, 'status' => 'published', @@ -53,12 +53,12 @@ class DemoDataSeeder extends Seeder 'available_now' => true, ], [ - 'title' => 'City Center Brunch Slot', + 'title' => 'Brunch asegurado en Arrecife centro', 'slug' => 'city-center-brunch-slot', 'category' => 'restaurant', - 'excerpt' => 'Reliable reservation at a high-demand brunch concept.', - 'description' => 'Convenient for arrivals into the city core with a predictable handoff to the host team.', - 'location_label' => 'City Center', + 'excerpt' => 'Reserva fiable en un local demandado del centro.', + 'description' => 'Muy útil para llegadas al núcleo urbano con necesidad de una decisión simple y rápida.', + 'location_label' => 'Arrecife', 'price_from' => 26, 'duration_minutes' => 60, 'status' => 'published', @@ -67,12 +67,12 @@ class DemoDataSeeder extends Seeder 'available_now' => true, ], [ - 'title' => 'Private Sailing Intro', + 'title' => 'Salida premium desde Puerto del Carmen', 'slug' => 'private-sailing-intro', 'category' => 'experience', - 'excerpt' => 'Short premium sailing experience from the marina.', - 'description' => 'A premium upsell for guests already moving toward the port area.', - 'location_label' => 'Marina', + 'excerpt' => 'Experiencia breve en barco para elevar una tarde sin complicaciones.', + 'description' => 'Upsell premium para huéspedes que ya van hacia marina o zona costera y están listos para decidir.', + 'location_label' => 'Puerto del Carmen', 'price_from' => 79, 'duration_minutes' => 105, 'status' => 'published', @@ -81,12 +81,12 @@ class DemoDataSeeder extends Seeder 'available_now' => true, ], [ - 'title' => 'Hotel Spa Recovery', + 'title' => 'Ritual spa recovery de llegada', 'slug' => 'hotel-spa-recovery', 'category' => 'service', - 'excerpt' => 'Fast-access recovery ritual for same-day arrivals.', - 'description' => 'Especially useful after airport or station pickups with short waiting windows.', - 'location_label' => 'Hotel District', + 'excerpt' => 'Acceso rápido a un ritual de recuperación para el mismo día.', + 'description' => 'Útil tras recogidas en aeropuerto u hotel cuando hay una ventana corta antes del siguiente plan.', + 'location_label' => 'Zona hotelera', 'price_from' => 55, 'duration_minutes' => 60, 'status' => 'published', diff --git a/resources/views/bookings/success.blade.php b/resources/views/bookings/success.blade.php index 6ce0d6c..ae6427d 100644 --- a/resources/views/bookings/success.blade.php +++ b/resources/views/bookings/success.blade.php @@ -1,32 +1,36 @@ @extends('layouts.app') -@section('title', 'Booking confirmed | ArrivalFlow') -@section('meta_description', 'Booking success screen for the taxi-to-offer demo flow.') +@section('title', 'Reserva confirmada | TAXILANZ Demo') +@section('meta_description', 'Pantalla de éxito de la reserva para el funnel taxi → recomendación → booking.') @section('content')
- 6 · Booking confirmed -

Reservation confirmed.

-

{{ $booking->customer_name }} is booked for {{ $booking->offer->title }}. This step closes the demo funnel with a real booking_completed event.

-
+ 6 · Reserva confirmada +

Reserva cerrada.

+

+ {{ $booking->customer_name }} ya quedó registrado para {{ $booking->offer->title }}. + Este paso cierra el funnel demo con un evento real de booking_completed. +

+
Booking UUID: {{ $booking->uuid }}
-
Status: {{ ucfirst($booking->status) }}
-
Amount: {{ $booking->amount ? '€'.number_format((float) $booking->amount, 2) : 'TBD' }}
-
Commission: {{ $booking->commission_amount ? '€'.number_format((float) $booking->commission_amount, 2) : 'TBD' }}
+
Estado: {{ ucfirst($booking->status) }}
+
Importe: {{ $booking->amount ? '€'.number_format((float) $booking->amount, 2) : 'Pendiente' }}
+
Comisión estimada: {{ $booking->commission_amount ? '€'.number_format((float) $booking->commission_amount, 2) : 'Pendiente' }}
+ diff --git a/resources/views/home.blade.php b/resources/views/home.blade.php index 1da7037..243d65b 100644 --- a/resources/views/home.blade.php +++ b/resources/views/home.blade.php @@ -1,52 +1,84 @@ @extends('layouts.app') -@section('title', 'Taxi arrival demo | ArrivalFlow') -@section('meta_description', 'Request a taxi, confirm the ride, and convert the arrival into a contextual local booking.') +@section('title', 'TAXILANZ Demo | Taxi, recomendaciones contextuales y reserva simple') +@section('meta_description', 'Demo TAXILANZ en Laravel: el taxi funciona como punto de entrada para activar 2–3 propuestas relevantes y convertirlas en reservas medibles.') @section('content')
- Thin slice · Laravel-first demo -

Turn a taxi request into a booking in under two minutes.

-

This demo keeps the story brutally simple: request a taxi, confirm the arrival, surface 2–3 relevant offers, and close a booking with measurable tracking.

+ Laravel-first demo · TAXILANZ +

El taxi se convierte en el inicio de una reserva útil.

+

+ Esta demo cuenta una historia muy concreta: un turista pide taxi, el trayecto se confirma, + aparecen 2–3 propuestas relevantes y una de ellas se convierte en reserva con tracking real. + Sin marketplace. Sin catálogo infinito. Solo activación en el momento exacto. +

+
-
{{ $metrics['rides'] }}Taxi requests
-
{{ $metrics['views'] }}Recommendation views
-
{{ $metrics['bookings'] }}Bookings completed
+
{{ $metrics['rides'] }}Solicitudes de taxi
+
{{ $metrics['views'] }}Vistas de recomendación
+
{{ $metrics['bookings'] }}Reservas cerradas
+
+
+
+
+ Impacto partner + {{ $metrics['ride_to_booking_rate'] }}% +

Conversión ride → booking

+

La demo enseña que un trayecto confirmado puede convertirse en ingreso atribuible, no solo en transporte resuelto.

+
+ +
+ Señal comercial + €{{ number_format($metrics['commission'], 0) }} +

Comisión estimada trazable

+

Desde el panel puedes enseñar GMV demo, comisión y etapas del funnel sin depender de discurso abstracto.

+
+ +
+ Ángulo ventas + {{ $metrics['clicks'] }} +

Clics con intención real

+

Menos catálogo, más contexto. Ese es el argumento que entienden hotel, recepción y operador local.

+ Abrir panel demo +
+
+
+
- 1 · Request taxi -

Request a ride

-

Use realistic destination context so the recommendation engine has something to work with.

+ 1 · Solicitar taxi +

Activa un trayecto demo

+

Usa un origen y destino realistas de Lanzarote para que el motor pueda puntuar mejor las propuestas.

@if ($errors->any()) -
- Please fix the form: +
+ Revisa el formulario:
    @foreach ($errors->all() as $error)
  • {{ $error }}
  • @@ -59,53 +91,54 @@ @csrf
    - - + +
-
-
+
- Demo inventory -

Seeded offers

+ Inventario demo +

Ofertas listas para sugerir

-

These are the kinds of offers the ride confirmation step can surface.

+

Contenido corto, accionable y relevante para un turista que ya ha pedido transporte.

+
@foreach ($featuredOffers as $offer)
@@ -113,11 +146,11 @@
{{ ucfirst($offer->category) }} - @if($offer->price_from) - From €{{ number_format((float) $offer->price_from, 0) }} + @if($offer->location_label) + {{ $offer->location_label }} @endif - @if($offer->duration_minutes) - {{ $offer->duration_minutes }} min + @if($offer->price_from) + Desde €{{ number_format((float) $offer->price_from, 0) }} @endif

{{ $offer->title }}

@@ -127,4 +160,15 @@ @endforeach
+ +
+
+
+ Cierre demo +

Front simple para turista. Historia clara para negocio.

+

Cuando termines el recorrido, entra en el panel para enseñar funnel, canales y reservas sin salir de la narrativa TAXILANZ.

+
+ Ir al dashboard Filament +
+
@endsection diff --git a/resources/views/layouts/app.blade.php b/resources/views/layouts/app.blade.php index 06275df..43597d8 100644 --- a/resources/views/layouts/app.blade.php +++ b/resources/views/layouts/app.blade.php @@ -1,150 +1,510 @@ - + - @yield('title', 'Taxi to booking demo') - - + @yield('title', 'TAXILANZ Demo | Taxi + recomendaciones + reserva') + + - @yield('head') + @stack('head')
+
-
@yield('content')
+
+ @yield('content') + +
+ No es un marketplace infinito: activa la mejor propuesta en el momento exacto. + {{ now()->format('Y') }} · Laravel + Filament demo slice +
+
- diff --git a/resources/views/offers/show.blade.php b/resources/views/offers/show.blade.php index 0080be5..bdcaa20 100644 --- a/resources/views/offers/show.blade.php +++ b/resources/views/offers/show.blade.php @@ -1,37 +1,40 @@ @extends('layouts.app') -@section('title', $offer->title . ' | ArrivalFlow') -@section('meta_description', $offer->excerpt ?: 'Contextual offer detail with a simple booking form.') +@section('title', $offer->title . ' | TAXILANZ Demo') +@section('meta_description', $offer->excerpt ?: 'Detalle de oferta contextual con reserva simple en TAXILANZ Demo.') @section('content')
- 4 · Offer detail -

{{ $offer->title }}

+ 4 · Detalle de oferta +

{{ $offer->title }}

{{ $offer->description ?: $offer->excerpt }}

{{ ucfirst($offer->category) }} @if($offer->location_label){{ $offer->location_label }}@endif - @if($offer->price_from)From €{{ number_format((float) $offer->price_from, 0) }}@endif + @if($offer->price_from)Desde €{{ number_format((float) $offer->price_from, 0) }}@endif @if($offer->duration_minutes){{ $offer->duration_minutes }} min@endif
+ @if($recommendation) -
- Recommendation context: This click is tied to ride #{{ $ride?->id }} and recommendation #{{ $recommendation->position }}. +
+ Contexto de recomendación: + esta visita viene del trayecto #{{ $ride?->id }} y de la sugerencia en posición {{ $recommendation->position }}. + El clic ya cuenta como recommendation_clicked.
@endif
diff --git a/resources/views/rides/confirmed.blade.php b/resources/views/rides/confirmed.blade.php index 6575382..bc72d0f 100644 --- a/resources/views/rides/confirmed.blade.php +++ b/resources/views/rides/confirmed.blade.php @@ -1,43 +1,76 @@ @extends('layouts.app') -@section('title', 'Taxi confirmed | ArrivalFlow') -@section('meta_description', 'Ride confirmed. Surface the best contextual offers while the guest waits or arrives.') +@section('title', 'Taxi confirmado | TAXILANZ Demo') +@section('meta_description', 'Pantalla de taxi confirmado con recomendaciones contextuales y tracking de visualización.') @section('content') -
-
- 2 · Taxi confirmed -

Your taxi is on the way.

-

Pickup: {{ $ride->pickup_label }}
Destination: {{ $ride->destination_label }}
ETA: {{ $ride->eta_minutes }} min

-
-
Ride UUID: {{ $ride->uuid }}
-
Source channel: {{ ucfirst($ride->source_channel) }}
-
Context zone: {{ $ride->context_zone ?: 'General' }}
+
+
+
+ 2 · Taxi confirmado +

Tu taxi llega en {{ $ride->eta_minutes ?? 6 }} min.

+

+ Mientras esperas, mostramos propuestas que encajan con tu destino y tu momento. + Aquí es donde TAXILANZ deja claro que no vende “más catálogo”, sino mejores decisiones justo a tiempo. +

+
+ +
+
Recogida: {{ $ride->pickup_label }}
+
Destino: {{ $ride->destination_label }}
+
Canal: {{ ucfirst($ride->source_channel) }} · Zona: {{ $ride->context_zone ?: 'General' }}
-
+
+
+ 3 · Recomendaciones +

Encajan con tu ruta ahora

+
+

Estas vistas ya están generando eventos recommendation_viewed.

+
+
@foreach ($recommendations as $recommendation)
- #{{ $recommendation->position }} - Score {{ number_format((float) $recommendation->score, 0) }} - {{ ucfirst($recommendation->offer->category) }} + Top {{ $recommendation->position }} + Score {{ (int) $recommendation->score }} + @if($recommendation->offer->location_label) + {{ $recommendation->offer->location_label }} + @endif

{{ $recommendation->offer->title }}

{{ $recommendation->offer->excerpt }}

-

Why it fits: {{ $recommendation->reason }}

+
+ Por qué encaja: {{ $recommendation->reason }} +
- View offer + Ver detalle y reservar
@endforeach