diff --git a/.DS_Store b/.DS_Store
new file mode 100644
index 0000000..5d6e68b
Binary files /dev/null and b/.DS_Store differ
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..8f0de65
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,18 @@
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+indent_size = 4
+indent_style = space
+insert_final_newline = true
+trim_trailing_whitespace = true
+
+[*.md]
+trim_trailing_whitespace = false
+
+[*.{yml,yaml}]
+indent_size = 2
+
+[docker-compose.yml]
+indent_size = 4
diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000..ea0665b
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,59 @@
+APP_NAME=Laravel
+APP_ENV=local
+APP_KEY=
+APP_DEBUG=true
+APP_URL=http://localhost
+
+LOG_CHANNEL=stack
+LOG_DEPRECATIONS_CHANNEL=null
+LOG_LEVEL=debug
+
+DB_CONNECTION=mysql
+DB_HOST=127.0.0.1
+DB_PORT=3306
+DB_DATABASE=laravel
+DB_USERNAME=root
+DB_PASSWORD=
+
+BROADCAST_DRIVER=log
+CACHE_DRIVER=file
+FILESYSTEM_DISK=local
+QUEUE_CONNECTION=sync
+SESSION_DRIVER=file
+SESSION_LIFETIME=120
+
+MEMCACHED_HOST=127.0.0.1
+
+REDIS_HOST=127.0.0.1
+REDIS_PASSWORD=null
+REDIS_PORT=6379
+
+MAIL_MAILER=smtp
+MAIL_HOST=mailpit
+MAIL_PORT=1025
+MAIL_USERNAME=null
+MAIL_PASSWORD=null
+MAIL_ENCRYPTION=null
+MAIL_FROM_ADDRESS="hello@example.com"
+MAIL_FROM_NAME="${APP_NAME}"
+
+AWS_ACCESS_KEY_ID=
+AWS_SECRET_ACCESS_KEY=
+AWS_DEFAULT_REGION=us-east-1
+AWS_BUCKET=
+AWS_USE_PATH_STYLE_ENDPOINT=false
+
+PUSHER_APP_ID=
+PUSHER_APP_KEY=
+PUSHER_APP_SECRET=
+PUSHER_HOST=
+PUSHER_PORT=443
+PUSHER_SCHEME=https
+PUSHER_APP_CLUSTER=mt1
+
+VITE_APP_NAME="${APP_NAME}"
+VITE_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
+VITE_PUSHER_HOST="${PUSHER_HOST}"
+VITE_PUSHER_PORT="${PUSHER_PORT}"
+VITE_PUSHER_SCHEME="${PUSHER_SCHEME}"
+VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..fcb21d3
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,11 @@
+* text=auto eol=lf
+
+*.blade.php diff=html
+*.css diff=css
+*.html diff=html
+*.md diff=markdown
+*.php diff=php
+
+/.github export-ignore
+CHANGELOG.md export-ignore
+.styleci.yml export-ignore
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..71e4b6e
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,20 @@
+/.phpunit.cache
+/node_modules
+/public/build
+/public/hot
+/public/storage
+/storage/*.key
+/vendor
+.env
+.env.backup
+.env.production
+.phpunit.result.cache
+Homestead.json
+Homestead.yaml
+auth.json
+npm-debug.log
+yarn-error.log
+/.fleet
+/.idea
+/.vscode
+Caddyfile
\ No newline at end of file
diff --git a/.htaccess b/.htaccess
new file mode 100644
index 0000000..69a01f1
--- /dev/null
+++ b/.htaccess
@@ -0,0 +1,8 @@
+
PASE DE ABORDAR
+ '; + + + $data = [ + 'pasajero' => 'JAVIER ALONSO', + 'fecha' => '25 MAY 2026', + 'hora' => '05:30', + 'zona' => 'SKY 01', + 'folio' => 'VG1025', + 'barcode' => $barcodeImg, + 'logo' => $logo, + ]; + + $pdf = Pdf::loadView('pdf.pases.pase-abordar', $data)->setPaper('letter', 'portrait'); + + return response()->streamDownload(fn() => print $pdf->output(), 'pase-de-abordar.pdf'); + } */ + + public function imprimirPaseAbordar(Request $request) + { + // Log para ver qué está llegando (para debug) + \Log::info('Datos recibidos en imprimirPaseAbordar:', $request->all()); + + // Validación de los datos principales + $validated = $request->validate([ + 'reservation_id' => 'required', + 'id_vuelo' => 'required|integer', + 'vuelo_nombre' => 'nullable|string', + 'vuelo_descripcion' => 'nullable|string', + 'vuelo_hora' => 'nullable|string', + 'fecha_vuelo' => 'nullable|string', + 'fecha_libre' => 'boolean', + 'adultos' => 'required|integer|min:1', + 'ninios' => 'required|integer|min:0', + 'responsable' => 'required|array', + 'responsable.nombres' => 'required|string', + 'responsable.apellidos' => 'required|string', + 'responsable.email' => 'required|email', + 'responsable.telefono' => 'required|string', + 'pasajeros' => 'required|array|min:1', + 'pasajeros.*.nombres' => 'required|string', + 'pasajeros.*.apellidos' => 'required|string', + 'pasajeros.*.fecha_nacimiento' => 'required|date', + 'pasajeros.*.peso_kg' => 'required|numeric', + 'pasajeros.*.tipo' => 'required|string|in:adulto,nino', + 'servicios' => 'nullable|array', + 'servicios.*.id' => 'required|integer', + 'servicios.*.cantidad' => 'required|integer|min:1', + 'servicios.*.precio' => 'required|numeric', + 'servicios.*.descripcion' => 'nullable|string', + 'subtotal_vuelo' => 'required|numeric', + 'total_extras' => 'required|numeric', + 'descuento' => 'nullable|numeric', + 'total' => 'required|numeric', + 'payment_email' => 'nullable|email', + 'payment_phone' => 'nullable|string', + 'payment_description' => 'nullable|string', + 'moneda' => 'nullable|string', + 'simbolo' => 'nullable|string', + 'categoria' => 'nullable|string', + 'incluye' => 'nullable|array', + 'rating' => 'nullable|numeric', + 'highlighted_text' => 'nullable|string', + 'highlighted_bg_color' => 'nullable|string', + ]); + + try { + // Generar código de barras único + $generator = new BarcodeGeneratorPNG(); + $barcodeNumber = $request->reservation_id . rand(1000, 9999); + $barcodeBase64 = base64_encode($generator->getBarcode($barcodeNumber, $generator::TYPE_CODE_128)); + $barcodeImg = 'data:image/png;base64,' . $barcodeBase64; + + // Logo en Base64 + $logoPath = public_path('assets/img/vueloenglobo/logo-vueloenglobo.png'); + $logoBase64 = ''; + if (file_exists($logoPath)) { + $logoData = base64_encode(file_get_contents($logoPath)); + $logoBase64 = 'data:image/png;base64,' . $logoData; + } else { + // Logo por defecto si no existe + $logoBase64 = 'https://placehold.co/60x60/4A63D2/white?text=SB'; + } + + // Obtener los pasajeros + $pasajeros = $request->pasajeros; + $pasajeroPrincipal = $pasajeros[0] ?? null; + $nombrePasajero = $pasajeroPrincipal + ? strtoupper(($pasajeroPrincipal['nombres'] ?? '') . ' ' . ($pasajeroPrincipal['apellidos'] ?? '')) + : 'PASAJERO NO ESPECIFICADO'; + + // Formatear la fecha del vuelo - CORREGIDO + $fechaVueloFormateada = $request->fecha_vuelo; + if ($request->fecha_libre) { + $fechaVueloFormateada = 'POR CONFIRMAR'; + } elseif ($fechaVueloFormateada) { + try { + // Intentar parsear fecha en formato dd/mm/YYYY + if (preg_match('/^\d{2}\/\d{2}\/\d{4}$/', $fechaVueloFormateada)) { + $fechaCarbon = \Carbon\Carbon::createFromFormat('d/m/Y', $fechaVueloFormateada); + $fechaVueloFormateada = $fechaCarbon->format('d/m/Y'); + } + // Intentar parsear fecha en formato YYYY-mm-dd + elseif (preg_match('/^\d{4}-\d{2}-\d{2}$/', $fechaVueloFormateada)) { + $fechaCarbon = \Carbon\Carbon::parse($fechaVueloFormateada); + $fechaVueloFormateada = $fechaCarbon->format('d/m/Y'); + } + // Intentar parsear cualquier otro formato + else { + $fechaCarbon = \Carbon\Carbon::parse($fechaVueloFormateada); + $fechaVueloFormateada = $fechaCarbon->format('d/m/Y'); + } + } catch (\Exception $e) { + \Log::warning('No se pudo parsear la fecha del vuelo: ' . $fechaVueloFormateada . ' - Error: ' . $e->getMessage()); + $fechaVueloFormateada = date('d/m/Y'); // Usar fecha actual como fallback + } + } else { + $fechaVueloFormateada = date('d/m/Y'); // Fecha actual si no hay fecha + } + + // Formatear hora del vuelo - CORREGIDO + $horaCita = '05:30 AM'; + if ($request->vuelo_hora && !empty($request->vuelo_hora)) { + try { + // Intentar parsear hora en diferentes formatos + $horaCarbon = \Carbon\Carbon::parse($request->vuelo_hora); + $horaCita = $horaCarbon->format('h:i A'); + } catch (\Exception $e) { + \Log::warning('No se pudo parsear la hora del vuelo: ' . $request->vuelo_hora); + $horaCita = $request->vuelo_hora; // Usar la hora tal como viene + } + } + + // Datos del vuelo + $datosVuelo = [ + 'nombre_vuelo' => $request->vuelo_nombre ?? 'Vuelo en Globo', + 'tipo_vuelo' => $request->categoria ?? 'Compartido', + 'fecha_vuelo' => $fechaVueloFormateada, + 'hora_cita' => $horaCita, + 'lugar_despegue' => 'SKY BALLOONS ', + 'duracion' => '40 min', + 'piloto' => '', + 'precio_total' => $request->total ?? 0, + 'anticipo' => $request->total ?? 0, + 'saldo_pendiente' => 0, + 'limite_pago' => date('d/m/Y', strtotime('+7 days')), + ]; + + // Datos del responsable + $responsable = $request->responsable; + $nombreResponsable = strtoupper( + ($responsable['nombres'] ?? '') . ' ' . ($responsable['apellidos'] ?? '') + ); + $emailResponsable = $responsable['email'] ?? ''; + $telefonoResponsable = $responsable['telefono'] ?? ''; + + // Procesar servicios adicionales + $serviciosAdicionales = []; + if (!empty($request->servicios)) { + foreach ($request->servicios as $servicio) { + $serviciosAdicionales[] = [ + 'nombre' => $servicio['descripcion'] ?? 'Servicio adicional', + 'cantidad' => $servicio['cantidad'] ?? 1, + 'precio_unitario' => $servicio['precio'] ?? 0, + 'precio_total' => ($servicio['cantidad'] ?? 1) * ($servicio['precio'] ?? 0), + 'estado' => 'Contratado', + 'clase' => 'contratado', + ]; + } + } + + // Si no hay servicios adicionales contratados, mostrar algunos por defecto + if (empty($serviciosAdicionales)) { + $serviciosAdicionales = [ + [ + 'nombre' => 'Fotografía profesional', + 'cantidad' => 1, + 'precio_unitario' => 500, + 'precio_total' => 500, + 'estado' => 'No contratado', + 'clase' => 'no-contratado', + ], + [ + 'nombre' => 'Video recuerdo', + 'cantidad' => 1, + 'precio_unitario' => 0, + 'precio_total' => 0, + 'estado' => 'No contratado', + 'clase' => 'no-contratado', + ], + ]; + } + + // Procesar lo que incluye el vuelo + $incluye = []; + if (!empty($request->incluye)) { + foreach ($request->incluye as $item) { + $incluye[] = $item['titulo_item'] ?? $item['nombre'] ?? 'Item incluido'; + } + } + + // Resumen de pasajeros - CORREGIDO (manejo de fechas de nacimiento) + $resumenPasajeros = []; + foreach ($pasajeros as $pasajero) { + $fechaNacimientoFormateada = 'No especificada'; + if (isset($pasajero['fecha_nacimiento']) && !empty($pasajero['fecha_nacimiento'])) { + try { + // Intentar parsear fecha de nacimiento + if (preg_match('/^\d{2}\/\d{2}\/\d{4}$/', $pasajero['fecha_nacimiento'])) { + $fechaNac = \Carbon\Carbon::createFromFormat('d/m/Y', $pasajero['fecha_nacimiento']); + $fechaNacimientoFormateada = $fechaNac->format('d/m/Y'); + } else { + $fechaNac = \Carbon\Carbon::parse($pasajero['fecha_nacimiento']); + $fechaNacimientoFormateada = $fechaNac->format('d/m/Y'); + } + } catch (\Exception $e) { + \Log::warning('No se pudo parsear fecha de nacimiento: ' . $pasajero['fecha_nacimiento']); + $fechaNacimientoFormateada = $pasajero['fecha_nacimiento']; + } + } + + $resumenPasajeros[] = [ + 'nombre_completo' => strtoupper( + ($pasajero['nombres'] ?? '') . ' ' . ($pasajero['apellidos'] ?? '') + ), + 'tipo' => $pasajero['tipo'] ?? 'adulto', + 'peso_kg' => $pasajero['peso_kg'] ?? 'No especificado', + 'fecha_nacimiento' => $fechaNacimientoFormateada, + ]; + } + + // Información de pago + $infoPago = [ + 'metodo' => 'Tarjeta de crédito/débito', + 'email' => $request->payment_email ?? $emailResponsable, + 'telefono' => $request->payment_phone ?? $telefonoResponsable, + 'descripcion' => $request->payment_description ?? $request->vuelo_nombre ?? 'Reserva de vuelo', + 'moneda' => $request->moneda ?? 'MXN', + 'simbolo' => $request->simbolo ?? '$', + ]; + + // Preparar todos los datos para la vista + $data = [ + // Datos del vuelo + 'nombre_vuelo' => $datosVuelo['nombre_vuelo'], + 'tipo_vuelo' => $datosVuelo['tipo_vuelo'], + 'fecha_vuelo' => $datosVuelo['fecha_vuelo'], + 'hora_cita' => $datosVuelo['hora_cita'], + 'lugar_despegue' => $datosVuelo['lugar_despegue'], + 'duracion' => $datosVuelo['duracion'], + 'piloto' => $datosVuelo['piloto'], + 'highlighted_text' => $request->highlighted_text ?? '', + 'highlighted_bg_color' => $request->highlighted_bg_color ?? '#2563eb', + 'rating' => $request->rating ?? 0, + + // Datos del responsable + 'nombre_responsable' => $nombreResponsable, + 'email_responsable' => $emailResponsable, + 'telefono_responsable' => $telefonoResponsable, + + // Datos de pasajeros + 'nombre_pasajero' => $nombrePasajero, + 'lista_pasajeros' => $pasajeros, + 'resumen_pasajeros' => $resumenPasajeros, + + // Totales y precios + 'precio_total' => $datosVuelo['precio_total'], + 'subtotal_vuelo' => $request->subtotal_vuelo ?? 0, + 'total_extras' => $request->total_extras ?? 0, + 'descuento' => $request->descuento ?? 0, + 'anticipo' => $datosVuelo['anticipo'], + 'saldo_pendiente' => $datosVuelo['saldo_pendiente'], + 'limite_pago' => $datosVuelo['limite_pago'], + + // Información de pago + 'info_pago' => $infoPago, + + // Servicios e inclusiones + 'servicios_adicionales' => $serviciosAdicionales, + 'incluye' => $incluye, + + // Datos de la reserva + 'folio_reserva' => $request->reservation_id, + 'fecha_reserva' => now()->format('d/m/Y H:i:s'), + + // Códigos e imágenes + 'barcode' => $barcodeImg, + 'barcode_number' => $barcodeNumber, + 'logo' => $logoBase64, + + // Datos adicionales + 'adultos' => $request->adultos ?? 1, + 'ninios' => $request->ninios ?? 0, + 'total_pasajeros' => ($request->adultos ?? 0) + ($request->ninios ?? 0), + 'vuelo_descripcion' => $request->vuelo_descripcion ?? '', + ]; + + // Generar PDF + $pdf = Pdf::loadView('pdf.pases.pase-abordar', $data) + ->setPaper('letter', 'portrait') + ->setOptions([ + 'defaultFont' => 'sans-serif', + 'isHtml5ParserEnabled' => true, + 'isRemoteEnabled' => true, + 'chroot' => public_path(), + ]); + + // Guardar una copia del PDF (opcional) + $pdfPath = storage_path('app/public/pases-abordar/'); + if (!file_exists($pdfPath)) { + mkdir($pdfPath, 0755, true); + } + $pdfPathCompleto = $pdfPath . "pase_abordar_" . $request->reservation_id . "_" . date('Ymd_His') . ".pdf"; + $pdf->save($pdfPathCompleto); + + // Enviar correo con el pase de abordar (opcional - comentado) + try { + // Mail::to($emailResponsable)->send(new PaseAbordarMail($data, $pdf)); + // \Log::info("Correo enviado a: {$emailResponsable}"); + } catch (\Exception $e) { + \Log::error("Error enviando correo: " . $e->getMessage()); + } + + // Descargar el PDF + return $pdf->download("pase_abordar_" . $request->reservation_id . ".pdf"); + + } catch (\Exception $e) { + \Log::error('Error generando pase de abordar: ' . $e->getMessage()); + \Log::error('Trace: ' . $e->getTraceAsString()); + + return response()->json([ + 'success' => false, + 'message' => 'Error al generar el pase de abordar: ' . $e->getMessage() + ], 500); + } + } +} diff --git a/app/Http/Controllers/PaymentController.php b/app/Http/Controllers/PaymentController.php new file mode 100644 index 0000000..edfe140 --- /dev/null +++ b/app/Http/Controllers/PaymentController.php @@ -0,0 +1,49 @@ +validate([ + 'token' => 'required|string', + 'amount' => 'required|numeric|min:0.01', + 'description' => 'nullable|string|max:255', + 'email' => 'required|email', + 'phone' => 'required|string', + ]); + + $payload = [ + 'amount' => (float) $validated['amount'], + 'currency' => 'MXN', + 'description' => $validated['description'] ?? 'Pago con SDK Clip', + 'payment_method' => [ + 'token' => $validated['token'] + ], + 'customer' => [ + 'email' => $validated['email'], + 'phone' => $validated['phone'] + ] + ]; + + $response = Http::withHeaders([ + 'Authorization' => 'Bearer ' . config('clip.api_key'), + 'Content-Type' => 'application/json', + ])->post(config('clip.payments_url'), $payload); + + if ($response->successful()) { + return response()->json($response->json(), 201); + } + + return response()->json([ + 'error' => $response->json('detail') ?? 'Error al procesar el pago', + 'code' => $response->json('error_code') + ], $response->status()); + } +} diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php new file mode 100644 index 0000000..326d3c3 --- /dev/null +++ b/app/Http/Kernel.php @@ -0,0 +1,98 @@ + + */ + protected $middleware = [ + // \App\Http\Middleware\TrustHosts::class, + TrustProxies::class, + HandleCors::class, + PreventRequestsDuringMaintenance::class, + ValidatePostSize::class, + TrimStrings::class, + ConvertEmptyStringsToNull::class, + //MinifyHtml::class, + ]; + + /** + * The application's route middleware groups. + * + * @var arrayNo se encontraron vuelos para "${utils.escapeHtml(query)}"
+Prueba con: amanecer, familiar, premium, teotihuacán
+$1')
+ .replace(/\n/g, '