Files

1 line
15 KiB
JSON

{"repo": "andresfj158/vehicruz.com", "files": {"vehicruz-system/tests/Feature/AiAssistantTest.php": {"1c07ad48a412eb99e072dae2f5f66500ba4b8f10": {"t": 1785745768.4626453, "n": 2923, "h": "524d87c3521ef099", "c": "<?php\n\nnamespace Tests\\Feature;\n\nuse App\\Models\\Permission;\nuse App\\Models\\Role;\nuse App\\Models\\User;\nuse Illuminate\\Foundation\\Testing\\RefreshDatabase;\nuse Illuminate\\Support\\Facades\\Http;\nuse Tests\\TestCase;\n\nclass AiAssistantTest extends TestCase\n{\n use RefreshDatabase;\n\n public function test_generate_ai_content_with_successful_mock(): void\n {\n // 1. Setup user with vehicles.manage permission\n $permission = Permission::query()->create(['name' => 'Gestionar vehículos', 'slug' => 'vehicles.manage']);\n $role = Role::query()->create(['name' => 'Editor', 'slug' => 'editor']);\n $role->permissions()->attach($permission);\n $user = User::factory()->create();\n $user->roles()->attach($role);\n\n // 2. Setup env variables for test\n putenv('DEEPSEEK_API_KEY=test-api-key');\n\n // 3. Fake HTTP response\n $mockJson = json_encode([\n 'choices' => [\n [\n 'message' => [\n 'content' => json_encode([\n 'description' => '<p>Toyota Land Cruiser en excelente estado.</p><ul><li>Motor V8</li><li>4x4</li></ul>',\n 'seo_title' => 'Toyota Land Cruiser en Venta Prestige',\n 'seo_description' => 'Compra tu Toyota Land Cruiser en Prestige al mejor precio de Bolivia.',\n 'social_facebook' => '¡Nueva Toyota Land Cruiser disponible! #Prestige',\n 'social_instagram' => 'Lujo y aventura con la Toyota Land Cruiser. #Prestige',\n 'social_tiktok' => 'Hook: ¿Buscas el mejor 4x4? Mira esta Toyota Land Cruiser.'\n ])\n ]\n ]\n ]\n ]);\n\n Http::fake([\n 'api.deepseek.com/*' => Http::response($mockJson, 200)\n ]);\n\n // 4. Hit the route\n $response = $this->actingAs($user)->postJson(route('admin.ai.generate'), [\n 'brand' => 'Toyota',\n 'model' => 'Land Cruiser',\n 'year' => 2023,\n 'price' => 135000,\n 'currency' => 'USD',\n 'transmission' => 'Automática',\n 'fuel_type' => 'Gasolina',\n 'exterior_color' => 'Negro',\n 'condition' => 'Usado',\n 'features' => ['4x4', 'Cuero', 'Sunroof'],\n 'extra_info' => 'Único dueño en Bolivia.'\n ]);\n\n // 5. Assertions\n $response->assertStatus(200);\n $response->assertJsonPath('success', true);\n $response->assertJsonPath('data.seo_title', 'Toyota Land Cruiser en Venta Prestige');\n $response->assertJsonPath('data.description', '<p>Toyota Land Cruiser en excelente estado.</p><ul><li>Motor V8</li><li>4x4</li></ul>');\n }\n\n protected function tearDown(): void\n {\n putenv('DEEPSEEK_API_KEY=');\n parent::tearDown();\n }\n}\n"}}, "vehicruz-system/app/Http/Controllers/Frontend/VehicleCatalogController.php": {"1c07ad48a412eb99e072dae2f5f66500ba4b8f10": {"t": 1785745775.5939927, "n": 11073, "h": "37fbe428ef7dae47", "c": "<?php\n\nnamespace App\\Http\\Controllers\\Frontend;\n\nuse App\\Http\\Controllers\\Controller;\nuse App\\Http\\Requests\\Frontend\\VehicleMessageRequest;\nuse App\\Models\\Vehicle;\nuse App\\Models\\VehicleBrand;\nuse App\\Models\\VehicleMessage;\nuse App\\Models\\VehicleModel;\nuse App\\Models\\AnalyticsEvent;\nuse App\\Services\\SeoService;\nuse App\\Services\\SettingsService;\nuse App\\Services\\VehicleQueryService;\nuse Illuminate\\Http\\RedirectResponse;\nuse Illuminate\\Http\\JsonResponse;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Http\\Response;\nuse Illuminate\\Support\\Facades\\Http;\nuse Illuminate\\Support\\Facades\\Mail;\nuse Illuminate\\View\\View;\n\nclass VehicleCatalogController extends Controller\n{\n public function index(Request $request, VehicleQueryService $queryService, SeoService $seo): View\n {\n $filters = $request->only([\n 'brand',\n 'model',\n 'min_price',\n 'max_price',\n 'year',\n 'mileage',\n 'transmission',\n 'fuel',\n 'condition',\n 'body_type',\n 'featured',\n 'status',\n 'sort',\n ]);\n\n return view('frontend.vehicles.index', [\n 'vehicles' => $queryService->catalog($filters),\n 'filters' => $filters,\n 'brands' => VehicleBrand::query()->where('is_active', true)->orderBy('name')->get(),\n 'models' => VehicleModel::query()->where('is_active', true)->orderBy('name')->get(),\n 'years' => Vehicle::query()->published()->whereNotNull('year')->distinct()->orderByDesc('year')->pluck('year'),\n 'transmissions' => Vehicle::query()->published()->whereNotNull('transmission')->distinct()->orderBy('transmission')->pluck('transmission'),\n 'fuels' => Vehicle::query()->published()->whereNotNull('fuel_type')->distinct()->orderBy('fuel_type')->pluck('fuel_type'),\n 'conditions' => Vehicle::query()->published()->whereNotNull('condition')->distinct()->orderBy('condition')->pluck('condition'),\n 'meta' => $seo->forPage(null, 'Catálogo de vehículos', 'Explora vehículos disponibles, filtra por marca, modelo, precio, año y más.'),\n ]);\n }\n\n public function show(Vehicle $vehicle, SeoService $seo, SettingsService $settings): View\n {\n abort_unless(in_array($vehicle->publication_status, [\n Vehicle::STATUS_PUBLISHED,\n Vehicle::STATUS_RESERVED,\n Vehicle::STATUS_SOLD,\n ], true), 404);\n\n $vehicle->increment('views');\n $vehicle->load(['brand', 'model', 'images', 'features', 'seller']);\n\n // Log analytical event for visit\n AnalyticsEvent::create([\n 'event_type' => 'visit',\n 'traffic_source' => session('traffic_source', 'Directo'),\n 'utm_source' => session('utm_source'),\n 'utm_medium' => session('utm_medium'),\n 'utm_campaign' => session('utm_campaign'),\n 'vehicle_id' => $vehicle->id,\n 'ip_address' => request()->ip(),\n ]);\n\n // Machine Learning: k-NN based similarity algorithm\n $maxPrice = Vehicle::available()->max('price') ?: 1;\n $maxMileage = Vehicle::available()->max('mileage') ?: 1;\n $maxYear = Vehicle::available()->max('year') ?: 2050;\n\n $related = Vehicle::query()\n ->with(['brand', 'model', 'images'])\n ->available()\n ->whereKeyNot($vehicle->id)\n ->select('*')\n ->selectRaw(\"\n (\n 1.5 * ABS(price - ?) / ? +\n 1.0 * ABS(COALESCE(mileage, 0) - ?) / ? +\n 2.0 * ABS(COALESCE(year, 2010) - ?) / ?\n ) as distance\",\n [\n $vehicle->price, $maxPrice,\n $vehicle->mileage ?? 0, $maxMileage,\n $vehicle->year ?? 2010, $maxYear\n ]\n )\n ->orderBy('distance', 'asc')\n ->take(4)\n ->get();\n\n $sellerPhone = $vehicle->seller?->phone;\n $whatsappUrl = $settings->whatsappUrl($vehicle->whatsapp_message, $sellerPhone);\n\n return view('frontend.vehicles.show', [\n 'vehicle' => $vehicle,\n 'related' => $related,\n 'whatsappUrl' => $whatsappUrl,\n 'meta' => $seo->forVehicle($vehicle),\n ]);\n }\n\n public function message(VehicleMessageRequest $request, Vehicle $vehicle): RedirectResponse\n {\n $message = VehicleMessage::query()->create([\n ...$request->validated(),\n 'vehicle_id' => $vehicle->id,\n 'ip_address' => $request->ip(),\n 'user_agent' => $request->userAgent(),\n ]);\n\n Mail::raw(\n \"Consulta por vehículo: {$vehicle->title}\\nNombre: {$message->name}\\nEmail: {$message->email}\\nTeléfono: {$message->phone}\\n\\n{$message->message}\",\n fn ($mail) => $mail->to(config('mail.from.address'))->subject('Consulta por vehículo en Prestige'),\n );\n\n return back()->with('status', 'Tu consulta fue enviada correctamente.');\n }\n\n public function modelsByBrand(VehicleBrand $brand): JsonResponse\n {\n $models = $brand->models()\n ->where('is_active', true)\n ->orderBy('name')\n ->get(['id', 'name', 'slug']);\n\n return response()->json($models);\n }\n\n public function trackWhatsapp(Vehicle $vehicle): Response\n {\n $vehicle->increment('phone_clicks');\n\n // Log analytical event for WhatsApp click\n AnalyticsEvent::create([\n 'event_type' => 'whatsapp_click',\n 'traffic_source' => session('traffic_source', 'Directo'),\n 'utm_source' => session('utm_source'),\n 'utm_medium' => session('utm_medium'),\n 'utm_campaign' => session('utm_campaign'),\n 'vehicle_id' => $vehicle->id,\n 'ip_address' => request()->ip(),\n ]);\n\n return response()->noContent();\n }\n\n public function trackShare(Vehicle $vehicle): Response\n {\n $vehicle->increment('share_clicks');\n\n return response()->noContent();\n }\n\n public function aiChat(Request $request, Vehicle $vehicle): JsonResponse\n {\n $question = mb_strtolower(trim((string) $request->input('message', '')));\n $vehicle->loadMissing(['brand', 'model', 'features']);\n\n $answer = $this->askDeepseek($vehicle, $question);\n if (! $answer) {\n $answer = $this->buildVehicleAnswer($vehicle, $question);\n }\n\n return response()->json(['answer' => $answer]);\n }\n\n private function askDeepseek(Vehicle $vehicle, string $question): ?string\n {\n $apiKey = (string) env('DEEPSEEK_API_KEY', '');\n if ($apiKey === '' || $question === '') {\n return null;\n }\n\n $baseUrl = rtrim((string) env('DEEPSEEK_BASE_URL', 'https://api.deepseek.com'), '/');\n $model = (string) env('DEEPSEEK_MODEL', 'deepseek-chat');\n\n $features = $vehicle->features->pluck('name')->implode(', ');\n $context = [\n 'titulo' => $vehicle->title,\n 'marca' => $vehicle->brand?->name,\n 'modelo' => $vehicle->model?->name,\n 'version' => $vehicle->version,\n 'anio' => $vehicle->year,\n 'precio' => $vehicle->formatted_price,\n 'kilometraje' => $vehicle->mileage !== null ? number_format($vehicle->mileage).' km' : null,\n 'combustible' => $vehicle->fuel_type,\n 'transmision' => $vehicle->transmission,\n 'traccion' => $vehicle->drivetrain,\n 'estado_publicacion' => $vehicle->publication_status,\n 'ubicacion' => $vehicle->location,\n 'caracteristicas' => $features,\n 'descripcion' => strip_tags((string) $vehicle->description),\n ];\n\n $systemPrompt = \"Eres un asesor de ventas de autos de Prestige. Responde en español, breve y claro. Usa SOLO los datos entregados del vehículo. Si no hay dato, dilo explícitamente y sugiere contactar por WhatsApp.\";\n $userPrompt = \"Datos del vehículo (JSON): \".json_encode($context, JSON_UNESCAPED_UNICODE).\"\\n\\nPregunta del cliente: \".$question;\n\n try {\n $response = Http::withToken($apiKey)\n ->timeout(20)\n ->acceptJson()\n ->post($baseUrl.'/chat/completions', [\n 'model' => $model,\n 'messages' => [\n ['role' => 'system', 'content' => $systemPrompt],\n ['role' => 'user', 'content' => $userPrompt],\n ],\n 'temperature' => 0.3,\n 'max_tokens' => 250,\n ]);\n\n if (! $response->successful()) {\n return null;\n }\n\n $content = trim((string) data_get($response->json(), 'choices.0.message.content', ''));\n\n return $content !== '' ? $content : null;\n } catch (\\Throwable) {\n return null;\n }\n }\n\n private function buildVehicleAnswer(Vehicle $vehicle, string $question): string\n {\n $base = \"Sobre {$vehicle->title}: \";\n\n if ($question === '') {\n return $base.'puedo ayudarte con precio, kilometraje, año, transmisión, combustible, ubicación y equipamiento.';\n }\n\n if (str_contains($question, 'precio')) {\n return $base.'el precio publicado es '.$vehicle->formatted_price.'.';\n }\n if (str_contains($question, 'kilomet') || str_contains($question, 'km')) {\n return $base.'el kilometraje registrado es '.($vehicle->mileage !== null ? number_format($vehicle->mileage).' km' : 'no especificado').'.';\n }\n if (str_contains($question, 'año') || str_contains($question, 'anio')) {\n return $base.'el año del vehículo es '.($vehicle->year ?: 'no especificado').'.';\n }\n if (str_contains($question, 'transm')) {\n return $base.'la transmisión es '.($vehicle->transmission ?: 'no especificada').'.';\n }\n if (str_contains($question, 'combust')) {\n return $base.'el combustible es '.($vehicle->fuel_type ?: 'no especificado').'.';\n }\n if (str_contains($question, 'ubic') || str_contains($question, 'donde')) {\n return $base.'la ubicación registrada es '.($vehicle->location ?: 'no especificada').'.';\n }\n if (str_contains($question, 'estado') || str_contains($question, 'dispon')) {\n return $base.'el estado actual es '.ucfirst($vehicle->publication_status).'.';\n }\n if (str_contains($question, 'caracter') || str_contains($question, 'equip')) {\n $features = $vehicle->features->pluck('name')->take(8)->implode(', ');\n return $base.($features !== '' ? \"estas son algunas características: {$features}.\" : 'no hay características cargadas todavía.');\n }\n\n return $base.'te recomiendo consultar por WhatsApp para una asesoría completa y disponibilidad en tiempo real.';\n }\n}\n"}}}}