get('reserva_pendiente'); if (!$reserva) { return response()->json(['error' => 'No hay reserva pendiente'], 400); } $id_cotizacion = $reserva['id_cotizacion']; $folio = $reserva['folio']; $datos = $reserva['datos']; // Si no se pasó el monto pagado, usar el total de la compra if ($montoPagado === null) { $montoPagado = $datos['total_compra']; } DB::beginTransaction(); try { // 1. Insertar en la tabla cotizaciones $fecha_vuelo = isset($datos['fecha_vuelo']) ? Carbon::createFromFormat('m/d/Y', $datos['fecha_vuelo'])->format('Y-m-d') : null; // Construir el JSON de vuelos (exactamente como lo hacías con modelos) $vuelos_field = [ [ 'moneda' => 'MXN', 'precio_vendido_p_pax_vuelo' => $datos['precio_publico'], 'id_vuelo' => $datos['id_vuelo'], 'id_unidad' => $datos['id_unidad'], 'cantidades' => $datos['pasajeros'], 'precio_especial' => '0' ] ]; if (!empty($datos['num_pax_adicionales']) && $datos['num_pax_adicionales'] > 0) { $vuelos_field[0]['pasajeros_adicionales'] = [ [ 'moneda' => 'MXN', 'cantidades' => $datos['num_pax_adicionales'], 'id_unidad' => $datos['id_unidad_adicionales'], 'precio' => $datos['precio_adicional'], 'precio_especial' => $datos['precio_adicional'] ] ]; } DB::table('cotizaciones')->insert([ 'id_sitio_web' => 6, 'cotizacion_abierta' => isset($datos['fecha_libre']) ? 1 : 0, 'folio' => $folio, 'nombre' => $datos['nombre_reserva'], 'apellidos' => $datos['apellido_reserva'], 'id_medio_contacto' => 12, 'id_genero' => 1, 'telefono' => $datos['tel_reserva'], 'iso_country_id_primary_phone' => $datos['Lada'], 'celular' => $datos['tel_reserva'], 'iso_country_id_secondary_phone' => $datos['Lada'], 'cp' => 0, 'mail' => $datos['email_reserva'], 'fecha_vuelo' => $fecha_vuelo, 'vuelos' => json_encode($vuelos_field), 'servicios_adicionales' => $datos['servicios_adicionales'], 'tipo_descuento' => null, 'descuento' => null, 'descuento_usd' => null, 'subtotal' => $datos['total_compra'], 'total' => $datos['total_compra'], 'total_con_iva' => $datos['total_compra'], 'subtotal_usd' => 0, 'total_usd' => 0, 'tipo_cargo' => 0, 'total_cargos' => 0, 'requiere_factura' => 0, 'observaciones' => 'checkout Globopuerto Teotihuacan', 'id_motivo' => 1, 'tipo_moneda' => 'MXN', 'pais_persona' => 484, 'estado_persona' => 1, 'id_user_alta' => 98, 'peso_kg' => 0, 'number_of_pax' => $this->getNumberOfPaxes( $vuelos_field, $vuelos_field[0]['pasajeros_adicionales'] ?? [], $datos['id_vuelo'] ), 'fecha_alta' => Carbon::now(), ]); // 2. Insertar vuelos_cotizaciones (vuelo principal) DB::table('vuelos_cotizaciones')->insert([ 'id_vuelo' => $datos['id_vuelo'], 'id_unidad' => $datos['id_unidad'], 'cantidades' => $datos['pasajeros'], 'precio_vendido_p_pax_vuelo' => $datos['precio_publico'], 'precio_vendido_p_pax_usd' => 0, 'precio_especial' => $datos['precio_publico'], 'id_cotizacion' => $id_cotizacion, 'pasajero_adicional' => 0, 'id_estatus' => 1, 'id_user_alta' => 98, 'fecha_alta' => Carbon::now(), ]); // 3. Insertar vuelos_cotizaciones adicionales (si existen) if (!empty($datos['num_pax_adicionales']) && $datos['num_pax_adicionales'] > 0) { DB::table('vuelos_cotizaciones')->insert([ 'id_vuelo' => $datos['id_vuelo'], 'id_unidad' => $datos['id_unidad_adicionales'], 'cantidades' => $datos['num_pax_adicionales'], 'precio_vendido_p_pax_vuelo' => $datos['precio_adicional'], 'precio_vendido_p_pax_usd' => 0, 'precio_especial' => $datos['precio_adicional'], 'id_cotizacion' => $id_cotizacion, 'pasajero_adicional' => 1, 'id_estatus' => 1, 'id_user_alta' => 98, 'id_user_mod' => 98, 'fecha_alta' => Carbon::now(), 'fecha_mod' => Carbon::now(), ]); } // 4. Insertar servicios_adicionales_cotizaciones if (!empty($datos['servicios_adicionales'])) { $servicios = json_decode($datos['servicios_adicionales'], true); foreach ($servicios as $servicio) { DB::table('servicios_adicionales_cotizaciones')->insert([ 'id_servicio_adicional' => $servicio['id_servicio'], 'cantidades' => $servicio['cantidad_solicitada'], 'id_unidad' => $servicio['id_unidad'], 'precio_vendido' => $servicio['precio'], 'precio_vendido_usd' => 0, 'id_cotizacion' => $id_cotizacion, 'id_estatus' => 1, 'id_user_alta' => 88, 'fecha_alta' => Carbon::now(), ]); } } // 5. Insertar el pago llamando al procedimiento almacenado (usando DB::statement) $params_pago = [ 'hora_cita' => $datos['hora_cita'], 'id_cotizacion' => $id_cotizacion, 'foto_comprobante' => null, 'cantidad_pagada' => $montoPagado, 'cantidad_pagada_usd' => 0, 'pago_confirmado_default' => 1, 'fecha_pago' => Carbon::now()->toDateString(), 'observaciones' => 'Pago exitoso desde checkout', 'observaciones_logistica' => 'Pago exitoso', 'ultimos_digitos' => null, 'id_cuenta_bancaria' => 0, 'id_metodo_pago_cuenta_bancaria' => ($metodoPago == 'card' ? 6 : ($metodoPago == 'oxxo' ? 8 : 9)), 'id_banco' => ($metodoPago == 'card' ? 11 : ($metodoPago == 'oxxo' ? 12 : 2)), 'id_user_alta' => 98, 'fecha_alta' => Carbon::now(), 'crear_pase' => 1, 'id_lugar_cita' => $datos['id_lugar_cita'], 'generar_pase_ingles' => 0, ]; // Llamar al procedimiento almacenado sp_crear_pagos_reservacion DB::statement('CALL sp_crear_pagos_reservacion(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)', [ $params_pago['hora_cita'], $params_pago['id_cotizacion'], $params_pago['foto_comprobante'], $params_pago['cantidad_pagada'], $params_pago['cantidad_pagada_usd'], $params_pago['pago_confirmado_default'], $params_pago['fecha_pago'], $params_pago['observaciones'], $params_pago['observaciones_logistica'], $params_pago['ultimos_digitos'], $params_pago['id_cuenta_bancaria'], $params_pago['id_metodo_pago_cuenta_bancaria'], $params_pago['id_banco'], $params_pago['id_user_alta'], $params_pago['fecha_alta'], $params_pago['crear_pase'], $params_pago['id_lugar_cita'], $params_pago['generar_pase_ingles'], ]); // 6. Enviar el pase (opcional, pero mantén la llamada a la API) //$url_mail = "https://erp.skyballoons.mx/enviar-pase/6/" . $id_cotizacion; //file_get_contents($url_mail); // o usar Http::get si prefieres DB::commit(); // Limpiar la sesión para que no se pueda reutilizar session()->forget('reserva_pendiente'); return response()->json([ 'exito' => 1, 'id_cotizacion' => $id_cotizacion, 'folio' => $folio ]); } catch (\Exception $e) { DB::rollBack(); return response()->json([ 'exito' => 0, 'error' => $e->getMessage() ], 500); } } public function guardarPosibleCompra(Request $request) { $datos = $request->all(); // Generar un ID de cotización (solo numérico, aún no se inserta) $id_cotizacion = DB::table('cotizaciones')->max('id_cotizacion') + 1; $folio = 'COT' . str_pad($id_cotizacion, 7, '0', STR_PAD_LEFT); // Guardar en posibles_compras (solo lo que tiene la tabla) DB::table('posibles_compras')->insert([ 'id_cotizacion' => $id_cotizacion, 'id_sitio_web' => 6, 'total_reservado' => $datos['total_compra'], 'total_reserva' => $datos['total_compra'], 'fecha_alta' => Carbon::now(), ]); // Guardar TODOS los datos de la reserva en sesión (no en BD) session()->put('reserva_pendiente', [ 'id_cotizacion' => $id_cotizacion, 'folio' => $folio, 'datos' => $datos, ]); return response()->json([ 'exito' => 1, 'id_cotizacion' => $id_cotizacion ]); } // /* public function imprimirPaseAbordar(Request $request) { // dd($request->all()); $generator = new BarcodeGeneratorPNG(); $barcodeBase64 = base64_encode( $generator->getBarcode('VG1025TEO2026', $generator::TYPE_CODE_128) ); $barcodeImg = 'data:image/png;base64,' . $barcodeBase64; $logo = public_path('assets/img/vueloenglobo/logo-vueloenglobo.png'); $html = '
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); } } }