2104 lines
111 KiB
PHP
2104 lines
111 KiB
PHP
@extends('template.layout')
|
||
|
||
@push('css')
|
||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.css" />
|
||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css">
|
||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/sweetalert2@11/dist/sweetalert2.min.css">
|
||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.8/css/intlTelInput.css">
|
||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.3/css/all.min.css">
|
||
<style>
|
||
/* ========== VARIABLES GLOBALES (MODO DÍA POR DEFECTO) ========== */
|
||
:root {
|
||
color-scheme: light dark;
|
||
--primary-color: #2563eb;
|
||
--primary-dark: #1d4ed8;
|
||
--primary-light: #eff6ff;
|
||
--success-color: #10b981;
|
||
--warning-color: #f59e0b;
|
||
--danger-color: #ef4444;
|
||
|
||
--gray-50: #f9fafb;
|
||
--gray-100: #f3f4f6;
|
||
--gray-200: #e5e7eb;
|
||
--gray-300: #d1d5db;
|
||
--gray-400: #9ca3af;
|
||
--gray-500: #6b7280;
|
||
--gray-600: #4b5563;
|
||
--gray-700: #374151;
|
||
--gray-800: #1f2937;
|
||
--gray-900: #111827;
|
||
|
||
--bg-body: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
--bg-card: #ffffff;
|
||
--bg-input: #ffffff;
|
||
|
||
--border-radius: 1rem;
|
||
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
|
||
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1);
|
||
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1);
|
||
--shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1);
|
||
}
|
||
|
||
/* ========== MODO OSCURO (DARK MODE) ========== */
|
||
.dark {
|
||
--bg-body: linear-gradient(135deg, #0f172a 0%, #1e1b4b 100%);
|
||
--bg-card: #1f2937;
|
||
--bg-input: #374151;
|
||
|
||
--gray-50: #111827;
|
||
--gray-100: #1f2937;
|
||
--gray-200: #374151;
|
||
--gray-300: #4b5563;
|
||
--gray-400: #6b7280;
|
||
--gray-500: #9ca3af;
|
||
--gray-600: #d1d5db;
|
||
--gray-700: #e5e7eb;
|
||
--gray-800: #f3f4f6;
|
||
--gray-900: #f9fafb;
|
||
|
||
--primary-light: #1e3a8a;
|
||
|
||
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.3);
|
||
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.4);
|
||
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.5);
|
||
--shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.5);
|
||
}
|
||
|
||
/* ========== ESTILOS BASE CON TRANSICIONES ========== */
|
||
* {
|
||
margin: 0;
|
||
padding: 0;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
html {
|
||
scroll-behavior: smooth;
|
||
}
|
||
|
||
body {
|
||
font-family: 'Inter', system-ui, sans-serif;
|
||
background: var(--bg-body);
|
||
min-height: 100vh;
|
||
color: var(--gray-800);
|
||
transition: background 0.3s ease, color 0.3s ease;
|
||
}
|
||
|
||
.flight-details {
|
||
padding: 2rem 1rem;
|
||
}
|
||
|
||
.container {
|
||
max-width: 1400px;
|
||
margin: 0 auto;
|
||
}
|
||
|
||
.details-grid {
|
||
display: grid;
|
||
grid-template-columns: 1fr 380px;
|
||
gap: 2rem;
|
||
}
|
||
|
||
@media (max-width:1024px) {
|
||
.details-grid {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
}
|
||
|
||
.main-card, .sidebar-card, .boarding-pass {
|
||
background: var(--bg-card);
|
||
border-radius: var(--border-radius);
|
||
box-shadow: var(--shadow-xl);
|
||
overflow: hidden;
|
||
transition: background 0.3s ease, box-shadow 0.3s ease;
|
||
}
|
||
|
||
.card-body {
|
||
padding: 2rem;
|
||
}
|
||
|
||
@media (max-width:768px) {
|
||
.card-body {
|
||
padding: 1.5rem;
|
||
}
|
||
}
|
||
|
||
.flight-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: flex-start;
|
||
flex-wrap: wrap;
|
||
gap: 1rem;
|
||
margin-bottom: 1.5rem;
|
||
padding-bottom: 1.5rem;
|
||
border-bottom: 1px solid var(--gray-200);
|
||
}
|
||
|
||
.flight-title h1 {
|
||
font-size: 1.875rem;
|
||
font-weight: 700;
|
||
color: var(--gray-900);
|
||
margin-bottom: 0.5rem;
|
||
}
|
||
|
||
.flight-title .subtitle {
|
||
color: var(--gray-600);
|
||
font-size: 0.875rem;
|
||
}
|
||
|
||
.badge {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
padding: 0.375rem 1rem;
|
||
border-radius: 9999px;
|
||
font-size: 0.75rem;
|
||
font-weight: 600;
|
||
color: white;
|
||
margin-top: 0.75rem;
|
||
}
|
||
|
||
.price-box {
|
||
text-align: right;
|
||
background: linear-gradient(135deg, var(--primary-light) 0%, #e0e7ff 100%);
|
||
padding: 1rem 1.5rem;
|
||
border-radius: var(--border-radius);
|
||
position: relative;
|
||
transition: background 0.3s ease;
|
||
}
|
||
|
||
.dark .price-box {
|
||
background: linear-gradient(135deg, #1e3a8a 0%, #312e81 100%);
|
||
}
|
||
|
||
.price-badge {
|
||
position: absolute;
|
||
top: -0.75rem;
|
||
right: 1rem;
|
||
background: var(--danger-color);
|
||
color: white;
|
||
font-size: 0.75rem;
|
||
font-weight: 700;
|
||
padding: 0.25rem 0.75rem;
|
||
border-radius: 9999px;
|
||
}
|
||
|
||
.price-old {
|
||
font-size: 0.875rem;
|
||
color: var(--gray-500);
|
||
text-decoration: line-through;
|
||
}
|
||
|
||
.price-current {
|
||
font-size: 2rem;
|
||
font-weight: 800;
|
||
color: var(--primary-color);
|
||
line-height: 1;
|
||
}
|
||
|
||
.price-label {
|
||
font-size: 0.75rem;
|
||
color: var(--gray-500);
|
||
}
|
||
|
||
.meta-info {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 1.5rem;
|
||
margin-bottom: 1.5rem;
|
||
padding-bottom: 1rem;
|
||
border-bottom: 1px solid var(--gray-200);
|
||
}
|
||
|
||
.meta-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
color: var(--gray-600);
|
||
font-size: 0.875rem;
|
||
}
|
||
|
||
.meta-item i {
|
||
color: var(--primary-color);
|
||
}
|
||
|
||
.rating {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
}
|
||
|
||
.stars {
|
||
display: flex;
|
||
gap: 0.125rem;
|
||
color: #fbbf24;
|
||
}
|
||
|
||
.rating-value {
|
||
font-weight: 600;
|
||
color: var(--gray-700);
|
||
}
|
||
|
||
.gallery-section {
|
||
margin-bottom: 2rem;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.gallery-section.hide-gallery {
|
||
display: none !important;
|
||
}
|
||
|
||
.main-swiper {
|
||
border-radius: var(--border-radius);
|
||
overflow: hidden;
|
||
margin-bottom: 1rem;
|
||
}
|
||
|
||
.main-swiper .swiper-slide img {
|
||
width: 100%;
|
||
height: 400px;
|
||
object-fit: cover;
|
||
}
|
||
|
||
.thumbs-swiper {
|
||
margin-top: 1rem;
|
||
}
|
||
|
||
.thumbs-swiper .swiper-slide {
|
||
width: 100px;
|
||
cursor: pointer;
|
||
opacity: 0.5;
|
||
transition: opacity 0.3s;
|
||
}
|
||
|
||
.thumbs-swiper .swiper-slide-thumb-active {
|
||
opacity: 1;
|
||
}
|
||
|
||
.thumbs-swiper .swiper-slide img {
|
||
width: 100%;
|
||
height: 70px;
|
||
object-fit: cover;
|
||
border-radius: 0.5rem;
|
||
}
|
||
|
||
@media (max-width:768px) {
|
||
.main-swiper .swiper-slide img {
|
||
height: 250px;
|
||
}
|
||
}
|
||
|
||
.step-indicator {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
margin-bottom: 2rem;
|
||
background: var(--gray-100);
|
||
padding: 1rem;
|
||
border-radius: var(--border-radius);
|
||
transition: background 0.3s ease;
|
||
}
|
||
|
||
.step-item {
|
||
flex: 1;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.75rem;
|
||
padding: 0.5rem;
|
||
cursor: pointer;
|
||
transition: all 0.3s;
|
||
border-radius: 0.5rem;
|
||
color: var(--gray-700);
|
||
}
|
||
|
||
.step-item:hover {
|
||
background: var(--gray-200);
|
||
}
|
||
|
||
.step-item.active {
|
||
background: var(--primary-light);
|
||
color: var(--primary-color);
|
||
}
|
||
|
||
.step-item.completed .step-number {
|
||
background: var(--success-color);
|
||
color: white;
|
||
}
|
||
|
||
.step-number {
|
||
width: 32px;
|
||
height: 32px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: var(--gray-300);
|
||
border-radius: 50%;
|
||
font-weight: 700;
|
||
font-size: 0.875rem;
|
||
color: var(--gray-700);
|
||
transition: all 0.3s;
|
||
}
|
||
|
||
.step-item.active .step-number {
|
||
background: var(--primary-color);
|
||
color: white;
|
||
}
|
||
|
||
.step-label {
|
||
font-size: 0.875rem;
|
||
font-weight: 500;
|
||
color: var(--gray-700);
|
||
}
|
||
|
||
.step-item.active .step-label {
|
||
color: var(--primary-color);
|
||
font-weight: 600;
|
||
}
|
||
|
||
.requirements-checklist {
|
||
background: var(--gray-50);
|
||
border-radius: var(--border-radius);
|
||
padding: 1rem;
|
||
margin: 1rem 0;
|
||
border-left: 4px solid var(--warning-color);
|
||
transition: background 0.3s ease;
|
||
}
|
||
|
||
.requirement-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.75rem;
|
||
padding: 0.5rem 0;
|
||
color: var(--gray-700);
|
||
}
|
||
|
||
.requirement-icon {
|
||
width: 24px;
|
||
height: 24px;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: var(--bg-card);
|
||
border-radius: 50%;
|
||
color: var(--warning-color);
|
||
}
|
||
|
||
.form-group {
|
||
margin-bottom: 1.5rem;
|
||
}
|
||
|
||
.form-group label {
|
||
display: block;
|
||
margin-bottom: 0.5rem;
|
||
font-weight: 600;
|
||
color: var(--gray-700);
|
||
font-size: 0.875rem;
|
||
}
|
||
|
||
.form-control {
|
||
width: 100%;
|
||
padding: 0.75rem 1rem;
|
||
border: 2px solid var(--gray-200);
|
||
border-radius: 0.75rem;
|
||
font-size: 1rem;
|
||
transition: all 0.3s;
|
||
background: var(--bg-input);
|
||
color: var(--gray-900);
|
||
}
|
||
|
||
.form-control::placeholder {
|
||
color: var(--gray-400);
|
||
}
|
||
|
||
.form-control:focus {
|
||
outline: none;
|
||
border-color: var(--primary-color);
|
||
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.15);
|
||
}
|
||
|
||
.dark .form-control:focus {
|
||
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.3);
|
||
}
|
||
|
||
.inline-fields {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 1rem;
|
||
}
|
||
|
||
@media (max-width:640px) {
|
||
.inline-fields {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
}
|
||
|
||
.btn-primary, .btn-secondary, .btn-pay, .btn-imprimir {
|
||
padding: 0.75rem 1.5rem;
|
||
border: none;
|
||
border-radius: 0.75rem;
|
||
font-weight: 600;
|
||
font-size: 1rem;
|
||
cursor: pointer;
|
||
transition: all 0.3s;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
}
|
||
|
||
.btn-primary {
|
||
background: var(--primary-color);
|
||
color: white;
|
||
}
|
||
|
||
.btn-primary:hover {
|
||
background: var(--primary-dark);
|
||
transform: translateY(-2px);
|
||
box-shadow: var(--shadow-md);
|
||
}
|
||
|
||
.btn-secondary {
|
||
background: var(--gray-200);
|
||
color: var(--gray-700);
|
||
}
|
||
|
||
.btn-secondary:hover {
|
||
background: var(--gray-300);
|
||
}
|
||
|
||
.btn-pay {
|
||
background: linear-gradient(135deg, var(--success-color) 0%, #059669 100%);
|
||
color: white;
|
||
width: 100%;
|
||
justify-content: center;
|
||
font-size: 1.125rem;
|
||
padding: 1rem;
|
||
margin-top: 1rem;
|
||
}
|
||
|
||
.btn-pay:hover {
|
||
transform: translateY(-2px);
|
||
box-shadow: var(--shadow-lg);
|
||
}
|
||
|
||
.counter-group, .service-qty-control {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
background: var(--gray-100);
|
||
border-radius: 0.75rem;
|
||
padding: 0.25rem;
|
||
transition: background 0.3s ease;
|
||
}
|
||
|
||
.counter-btn, .qty-btn {
|
||
width: 36px;
|
||
height: 36px;
|
||
border: none;
|
||
border-radius: 0.5rem;
|
||
background: var(--bg-card);
|
||
font-size: 1.25rem;
|
||
font-weight: 700;
|
||
cursor: pointer;
|
||
transition: all 0.3s;
|
||
color: var(--gray-700);
|
||
}
|
||
|
||
.counter-btn:hover, .qty-btn:hover {
|
||
background: var(--primary-color);
|
||
color: white;
|
||
}
|
||
|
||
.counter-input {
|
||
width: 60px;
|
||
text-align: center;
|
||
border: none;
|
||
font-size: 1rem;
|
||
font-weight: 600;
|
||
background: transparent;
|
||
color: var(--gray-900);
|
||
}
|
||
|
||
.checkbox-group {
|
||
margin-bottom: 1rem;
|
||
}
|
||
|
||
.checkbox-label {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.75rem;
|
||
cursor: pointer;
|
||
user-select: none;
|
||
color: var(--gray-700);
|
||
}
|
||
|
||
.checkbox-label input[type="checkbox"] {
|
||
width: 20px;
|
||
height: 20px;
|
||
cursor: pointer;
|
||
accent-color: var(--primary-color);
|
||
}
|
||
|
||
.extras-category {
|
||
margin-bottom: 2rem;
|
||
}
|
||
|
||
.category-title {
|
||
font-weight: 700;
|
||
font-size: 1.125rem;
|
||
color: var(--gray-800);
|
||
margin-bottom: 1rem;
|
||
padding-bottom: 0.5rem;
|
||
border-bottom: 2px solid var(--primary-color);
|
||
display: inline-block;
|
||
}
|
||
|
||
.extras-table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
}
|
||
|
||
.extras-table th {
|
||
text-align: left;
|
||
padding: 0.75rem;
|
||
background: var(--gray-100);
|
||
font-weight: 600;
|
||
font-size: 0.875rem;
|
||
color: var(--gray-600);
|
||
transition: background 0.3s ease, color 0.3s ease;
|
||
}
|
||
|
||
.extras-table td {
|
||
padding: 0.75rem;
|
||
border-bottom: 1px solid var(--gray-200);
|
||
color: var(--gray-700);
|
||
}
|
||
|
||
.service-image {
|
||
width: 60px;
|
||
height: 60px;
|
||
object-fit: cover;
|
||
border-radius: 0.5rem;
|
||
}
|
||
|
||
.section {
|
||
margin-top: 2rem;
|
||
padding-top: 1.5rem;
|
||
border-top: 1px solid var(--gray-200);
|
||
}
|
||
|
||
.section-title {
|
||
font-size: 1.25rem;
|
||
font-weight: 700;
|
||
color: var(--gray-800);
|
||
margin-bottom: 1rem;
|
||
}
|
||
|
||
.section-subtitle {
|
||
font-size: 1rem;
|
||
font-weight: 600;
|
||
color: var(--gray-700);
|
||
margin-bottom: 1rem;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
}
|
||
|
||
.note {
|
||
background: var(--primary-light);
|
||
padding: 1rem;
|
||
border-radius: var(--border-radius);
|
||
margin-top: 1.5rem;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.75rem;
|
||
font-size: 0.875rem;
|
||
color: var(--primary-color);
|
||
transition: background 0.3s ease, color 0.3s ease;
|
||
}
|
||
|
||
.dark .note {
|
||
background: #1e3a8a;
|
||
color: #93c5fd;
|
||
}
|
||
|
||
.error-message {
|
||
color: var(--danger-color);
|
||
font-size: 0.75rem;
|
||
margin-top: 0.25rem;
|
||
display: none;
|
||
}
|
||
|
||
.error-message.show {
|
||
display: block;
|
||
}
|
||
|
||
hr {
|
||
margin: 1.5rem 0;
|
||
border: none;
|
||
border-top: 1px solid var(--gray-200);
|
||
}
|
||
|
||
.includes-list-modern {
|
||
list-style: none;
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
||
gap: 0.75rem;
|
||
}
|
||
|
||
.includes-list-modern li {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.75rem;
|
||
padding: 0.75rem;
|
||
border-radius: 0.75rem;
|
||
border: 1px solid var(--gray-200);
|
||
transition: all 0.3s ease;
|
||
color: var(--gray-700);
|
||
}
|
||
|
||
.includes-list-modern li:hover {
|
||
border-color: var(--primary-color);
|
||
box-shadow: var(--shadow-sm);
|
||
transform: translateY(-2px);
|
||
}
|
||
|
||
.includes-list-modern li i {
|
||
width: 28px;
|
||
height: 28px;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: var(--primary-light);
|
||
color: var(--primary-color);
|
||
border-radius: 50%;
|
||
font-size: 0.75rem;
|
||
flex-shrink: 0;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.dark .includes-list-modern li i {
|
||
background: #1e3a8a;
|
||
color: #93c5fd;
|
||
}
|
||
|
||
.sidebar {
|
||
position: sticky;
|
||
top: 2rem;
|
||
height: fit-content;
|
||
}
|
||
|
||
.card-title {
|
||
font-size: 1.125rem;
|
||
font-weight: 700;
|
||
color: var(--gray-800);
|
||
margin-bottom: 1.5rem;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding-bottom: 0.75rem;
|
||
border-bottom: 2px solid var(--gray-200);
|
||
}
|
||
|
||
.edit-link {
|
||
color: var(--primary-color);
|
||
cursor: pointer;
|
||
font-size: 0.75rem;
|
||
margin-left: 0.5rem;
|
||
}
|
||
|
||
.edit-link:hover {
|
||
text-decoration: underline;
|
||
}
|
||
|
||
.summary-item {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
padding: 0.75rem 0;
|
||
border-bottom: 1px dashed var(--gray-200);
|
||
font-size: 0.875rem;
|
||
color: var(--gray-700);
|
||
}
|
||
|
||
.summary-item span:first-child {
|
||
color: var(--gray-600);
|
||
}
|
||
|
||
.summary-item span:last-child {
|
||
font-weight: 600;
|
||
color: var(--gray-800);
|
||
}
|
||
|
||
.subtotal-vuelo {
|
||
font-weight: 700;
|
||
color: var(--gray-900);
|
||
}
|
||
|
||
.summary-extras {
|
||
margin: 0.5rem 0;
|
||
}
|
||
|
||
.extra-item {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
padding: 0.5rem 0;
|
||
font-size: 0.75rem;
|
||
color: var(--gray-600);
|
||
border-bottom: 1px dashed var(--gray-200);
|
||
}
|
||
|
||
.summary-total {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
padding: 1rem 0;
|
||
margin-top: 1rem;
|
||
border-top: 2px solid var(--gray-200);
|
||
font-weight: 800;
|
||
font-size: 1.125rem;
|
||
color: var(--primary-color);
|
||
}
|
||
|
||
.trust-badges {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
margin-top: 1.5rem;
|
||
padding-top: 1rem;
|
||
border-top: 1px solid var(--gray-200);
|
||
}
|
||
|
||
.trust-badge {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
font-size: 0.75rem;
|
||
color: var(--gray-500);
|
||
}
|
||
|
||
.trust-badge i {
|
||
color: var(--success-color);
|
||
}
|
||
|
||
.discount-toggle {
|
||
color: var(--primary-color);
|
||
cursor: pointer;
|
||
font-size: 0.875rem;
|
||
display: inline-block;
|
||
margin-bottom: 0.5rem;
|
||
}
|
||
|
||
.discount-toggle:hover {
|
||
text-decoration: underline;
|
||
}
|
||
|
||
.discount-input {
|
||
display: none;
|
||
gap: 0.5rem;
|
||
margin-bottom: 1rem;
|
||
}
|
||
|
||
.discount-input.active {
|
||
display: flex;
|
||
}
|
||
|
||
.boarding-pass {
|
||
font-family: monospace;
|
||
padding: 20px;
|
||
max-width: 800px;
|
||
margin: 0 auto;
|
||
border: 2px solid var(--gray-800);
|
||
}
|
||
|
||
/* ========== FLATPICKR DARK MODE OVERRIDES ========== */
|
||
.dark .flatpickr-calendar {
|
||
background: var(--bg-card);
|
||
border-color: var(--gray-200);
|
||
box-shadow: var(--shadow-xl);
|
||
}
|
||
.dark .flatpickr-day.selected, .dark .flatpickr-day.startRange, .dark .flatpickr-day.endRange {
|
||
background: var(--primary-color);
|
||
border-color: var(--primary-color);
|
||
}
|
||
.dark .flatpickr-day.inRange {
|
||
background: rgba(37, 99, 235, 0.2);
|
||
}
|
||
.dark .flatpickr-months .flatpickr-month {
|
||
background: var(--bg-card);
|
||
color: var(--gray-900);
|
||
}
|
||
.dark .flatpickr-current-month .flatpickr-monthDropdown-months,
|
||
.dark .flatpickr-current-month input.cur-year {
|
||
color: var(--gray-900);
|
||
}
|
||
.dark .flatpickr-weekdays {
|
||
background: var(--bg-card);
|
||
}
|
||
.dark .flatpickr-weekday {
|
||
color: var(--gray-600);
|
||
}
|
||
.dark .flatpickr-day {
|
||
color: var(--gray-900);
|
||
}
|
||
.dark .flatpickr-day.prevMonthDay, .dark .flatpickr-day.nextMonthDay {
|
||
color: var(--gray-500);
|
||
}
|
||
|
||
/* ========== BOTÓN FLOTANTE DE TEMA ========== */
|
||
.theme-toggle-btn {
|
||
position: fixed;
|
||
bottom: 2rem;
|
||
right: 2rem;
|
||
width: 50px;
|
||
height: 50px;
|
||
border-radius: 50%;
|
||
background: var(--bg-card);
|
||
color: var(--gray-700);
|
||
border: 2px solid var(--gray-200);
|
||
box-shadow: var(--shadow-lg);
|
||
cursor: pointer;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 1.25rem;
|
||
z-index: 1000;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.theme-toggle-btn:hover {
|
||
transform: scale(1.1);
|
||
border-color: var(--primary-color);
|
||
color: var(--primary-color);
|
||
}
|
||
|
||
.dark .theme-toggle-btn {
|
||
background: var(--gray-700);
|
||
color: #fbbf24;
|
||
border-color: var(--gray-600);
|
||
}
|
||
|
||
.dark .theme-toggle-btn:hover {
|
||
border-color: #fbbf24;
|
||
color: #fbbf24;
|
||
}
|
||
|
||
/* ========== MEDIA QUERIES ========== */
|
||
@media (max-width:768px) {
|
||
.flight-header {
|
||
flex-direction: column;
|
||
}
|
||
.price-box {
|
||
text-align: center;
|
||
width: 100%;
|
||
}
|
||
.step-label {
|
||
display: none;
|
||
}
|
||
.step-item {
|
||
justify-content: center;
|
||
}
|
||
.trust-badges {
|
||
flex-direction: column;
|
||
gap: 0.5rem;
|
||
align-items: center;
|
||
}
|
||
.flatpickr-calendar {
|
||
width: 90% !important;
|
||
left: 5% !important;
|
||
right: 5% !important;
|
||
}
|
||
}
|
||
|
||
@media print {
|
||
body * {
|
||
visibility: hidden;
|
||
}
|
||
.boarding-pass, .boarding-pass * {
|
||
visibility: visible;
|
||
}
|
||
.boarding-pass {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
}
|
||
.no-print, .theme-toggle-btn {
|
||
display: none !important;
|
||
}
|
||
}
|
||
</style>
|
||
@endpush
|
||
|
||
@section('content')
|
||
<br><br><br>
|
||
|
||
@php
|
||
// ========== VARIABLES SEGURAS ==========
|
||
$vuelo = $datosVuelo['vuelo'] ?? null;
|
||
$categoria = $datosVuelo['categoria'] ?? null;
|
||
$imagenesGaleria = collect($datosVuelo['imagenes'] ?? [])->pluck('ruta_imagen_vuelo')->toArray();
|
||
$incluye = $datosVuelo['incluye'] ?? [];
|
||
$adicionales = $adicionales ?? [];
|
||
$extrasArray = is_array($adicionales) ? $adicionales : [];
|
||
if (empty($imagenesGaleria)) {
|
||
$imagenesGaleria = ['https://placehold.co/1200x800/2563eb/white?text=Sky+Balloons'];
|
||
}
|
||
$moneda = 'MXN';
|
||
$simbolo = '$';
|
||
$precioActual = (float) ($vuelo['precio_especial'] ?? ($vuelo['precio_publico'] ?? 0));
|
||
$precioOriginal = (float) ($vuelo['precio_publico'] ?? 0);
|
||
$precioNino = (float) ($vuelo['precios_adicionales'][0]['precio'] ?? 1900);
|
||
if (($vuelo['mostrar_precios_dolares'] ?? 0) == 1) {
|
||
$moneda = 'USD';
|
||
$simbolo = 'US$';
|
||
}
|
||
$tieneDescuento = $precioActual < $precioOriginal;
|
||
$descuentoPorcentaje = $tieneDescuento ? round((1 - $precioActual / $precioOriginal) * 100) : 0;
|
||
$locale = app()->getLocale();
|
||
$isSpanish = $locale === 'es';
|
||
|
||
$serviciosIncluidosIds = [];
|
||
foreach ($incluye as $item) {
|
||
$posiblesIds = [$item['id_item'] ?? null, $item['id'] ?? null, $item['servicio_id'] ?? null, $item['id_servicio'] ?? null, $item['item_id'] ?? null, $item['servicio']['id'] ?? null];
|
||
foreach ($posiblesIds as $id) {
|
||
if ($id && is_numeric($id)) {
|
||
$serviciosIncluidosIds[] = (int) $id;
|
||
}
|
||
}
|
||
}
|
||
$serviciosIncluidosIds = array_unique($serviciosIncluidosIds);
|
||
|
||
function isServicioIncluido($servicioId, $incluidosIds) {
|
||
return in_array($servicioId, $incluidosIds);
|
||
}
|
||
|
||
$extrasFiltrados = [];
|
||
$totalOriginalExtras = 0;
|
||
foreach ($extrasArray as $categoriaData) {
|
||
$categoriaNombre = $categoriaData['nombre_categoria'] ?? ($isSpanish ? 'Otros servicios' : 'Other services');
|
||
$serviciosFiltrados = [];
|
||
foreach ($categoriaData['servicios'] ?? [] as $servicio) {
|
||
$servicioId = $servicio['id'] ?? null;
|
||
$totalOriginalExtras++;
|
||
if (!$servicioId || !isServicioIncluido($servicioId, $serviciosIncluidosIds)) {
|
||
$serviciosFiltrados[] = $servicio;
|
||
}
|
||
}
|
||
if (!empty($serviciosFiltrados)) {
|
||
$extrasFiltrados[] = ['nombre_categoria' => $categoriaNombre, 'servicios' => $serviciosFiltrados];
|
||
}
|
||
}
|
||
$totalExtrasDisponibles = collect($extrasFiltrados)->sum(fn($cat) => count($cat['servicios']));
|
||
$todosIncluidos = ($totalOriginalExtras > 0 && $totalExtrasDisponibles === 0);
|
||
$clipApiKey = config('clip.api_key', '');
|
||
@endphp
|
||
|
||
@if ($vuelo)
|
||
<article class="flight-details" data-flight-id="{{ $vuelo['id_vuelo'] }}">
|
||
<div class="container">
|
||
<div class="details-grid">
|
||
<div class="main-card">
|
||
<div class="card-body">
|
||
<header class="flight-header">
|
||
<div class="flight-title">
|
||
<h1>{{ $vuelo['nombre_vuelo'] }}</h1>
|
||
<p class="subtitle">{{ $vuelo['descripcion_corta'] }}</p>
|
||
@if (!empty($vuelo['highlighted_text_flight_es']) && $isSpanish)
|
||
<span class="badge" style="background: {{ $vuelo['highlighted_background_color_badge_flight_es'] ?? '#2563eb' }}">
|
||
<i class="fas fa-star"></i> {{ $vuelo['highlighted_text_flight_es'] }}
|
||
</span>
|
||
@endif
|
||
</div>
|
||
<div class="price-box">
|
||
@if ($tieneDescuento)
|
||
<div class="price-badge">-{{ $descuentoPorcentaje }}%</div>
|
||
<div class="price-old">{{ $simbolo }}{{ number_format($precioOriginal, 2, '.', ',') }}</div>
|
||
@endif
|
||
<div class="price-current">{{ $simbolo }}{{ number_format($precioActual, 2, '.', ',') }}</div>
|
||
<div class="price-label">{{ $isSpanish ? 'por adulto' : 'per adult' }}</div>
|
||
</div>
|
||
</header>
|
||
|
||
<div class="meta-info">
|
||
<div class="meta-item"><i class="fas fa-tag"></i><span>{{ $categoria['nombre'] ?? ($isSpanish ? 'Aventura' : 'Adventure') }}</span></div>
|
||
<div class="meta-item"><i class="far fa-clock"></i><span>{{ \Carbon\Carbon::parse($vuelo['hora_actividad'] ?? '06:30')->format('g:i A') }}</span></div>
|
||
<div class="rating">
|
||
<div class="stars">
|
||
@for ($i = 1; $i <= 5; $i++)
|
||
@if ($i <= round($vuelo['raiting_global_number'] ?? 0))
|
||
<i class="fas fa-star"></i>
|
||
@elseif($i - 0.5 <= round($vuelo['raiting_global_number'] ?? 0))
|
||
<i class="fas fa-star-half-alt"></i>
|
||
@else
|
||
<i class="far fa-star"></i>
|
||
@endif
|
||
@endfor
|
||
</div>
|
||
<span class="rating-value">{{ number_format($vuelo['raiting_global_number'] ?? 0, 1) }}</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- GALERÍA -->
|
||
<div class="gallery-section" id="gallerySection">
|
||
<div class="swiper main-swiper">
|
||
<div class="swiper-wrapper">
|
||
@foreach ($imagenesGaleria as $index => $img)
|
||
<div class="swiper-slide"><img src="{{ $img }}" alt="Imagen {{ $index + 1 }}" loading="{{ $index === 0 ? 'eager' : 'lazy' }}"></div>
|
||
@endforeach
|
||
</div>
|
||
<div class="swiper-button-next"></div>
|
||
<div class="swiper-button-prev"></div>
|
||
<div class="swiper-pagination"></div>
|
||
</div>
|
||
@if (count($imagenesGaleria) > 1)
|
||
<div class="swiper thumbs-swiper">
|
||
<div class="swiper-wrapper">
|
||
@foreach ($imagenesGaleria as $index => $img)
|
||
<div class="swiper-slide"><img src="{{ $img }}" alt="Miniatura {{ $index + 1 }}"></div>
|
||
@endforeach
|
||
</div>
|
||
</div>
|
||
@endif
|
||
</div>
|
||
|
||
<!-- STEP INDICATOR -->
|
||
<div class="step-indicator" role="tablist">
|
||
<div class="step-item active" data-step="1">
|
||
<div class="step-number">1</div><span class="step-label">{{ $isSpanish ? 'Vuelo' : 'Flight' }}</span>
|
||
</div>
|
||
<div class="step-item" data-step="2">
|
||
<div class="step-number">2</div><span class="step-label">{{ $isSpanish ? 'Responsable' : 'Responsible' }}</span>
|
||
</div>
|
||
<div class="step-item" data-step="3">
|
||
<div class="step-number">3</div><span class="step-label">{{ $isSpanish ? 'Extras' : 'Extras' }}</span>
|
||
</div>
|
||
<div class="step-item" data-step="4">
|
||
<div class="step-number">4</div><span class="step-label">{{ $isSpanish ? 'Pago' : 'Payment' }}</span>
|
||
</div>
|
||
<div class="step-item" data-step="5">
|
||
<div class="step-number">5</div><span class="step-label">{{ $isSpanish ? 'Pasajeros' : 'Passengers' }}</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- STEP 1 -->
|
||
<div id="step-1" class="step-content">
|
||
<form id="formStep1">
|
||
<div class="form-group">
|
||
<label>{{ $isSpanish ? 'Fecha del vuelo *' : 'Flight date *' }}</label>
|
||
<input type="text" id="fecha_vuelo" class="form-control flatpickr-input" placeholder="{{ $isSpanish ? 'Selecciona una fecha' : 'Select a date' }}">
|
||
<input type="date" id="fecha_vuelo_native" class="form-control native-datepicker" style="display: none;" hidden>
|
||
<div class="error-message" id="fecha-error"></div>
|
||
</div>
|
||
<div class="checkbox-group">
|
||
<label class="checkbox-label">
|
||
<input type="checkbox" id="fechaLibreCheckbox">
|
||
<span class="checkbox-text">{{ $isSpanish ? 'Fecha por confirmar (me pondré en contacto)' : 'Date to be confirmed (I will contact you)' }}</span>
|
||
</label>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>{{ $isSpanish ? 'Adultos (+12 años) *' : 'Adults (+12 years) *' }}</label>
|
||
<div class="counter-group">
|
||
<button type="button" id="minusAdult" class="counter-btn">-</button>
|
||
<input type="number" id="adultos" value="1" min="1" max="10" class="counter-input">
|
||
<button type="button" id="plusAdult" class="counter-btn">+</button>
|
||
</div>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>{{ $isSpanish ? 'Niños (4-12 años)' : 'Children (4-12 years)' }}</label>
|
||
<div class="counter-group">
|
||
<button type="button" id="minusChild" class="counter-btn">-</button>
|
||
<input type="number" id="ninios" value="0" min="0" max="10" class="counter-input">
|
||
<button type="button" id="plusChild" class="counter-btn">+</button>
|
||
</div>
|
||
</div>
|
||
<button type="button" class="btn-primary next-step" data-next="2">{{ $isSpanish ? 'Continuar' : 'Continue' }} <i class="fas fa-arrow-right"></i></button>
|
||
</form>
|
||
</div>
|
||
|
||
<!-- STEP 2 -->
|
||
<div id="step-2" class="step-content" style="display: none;">
|
||
<form id="formStep2">
|
||
<h3 style="font-size: 1.2rem; margin-bottom: 1rem; color: var(--gray-800);">
|
||
{{ $isSpanish ? 'Datos del responsable de la reserva' : 'Booking responsible data' }}
|
||
</h3>
|
||
<div class="inline-fields">
|
||
<div class="form-group">
|
||
<label>{{ $isSpanish ? 'Nombres *' : 'First names *' }}</label>
|
||
<input type="text" id="resp_nombres" class="form-control" placeholder="{{ $isSpanish ? 'Nombres' : 'First names' }}">
|
||
<div class="error-message" id="resp_nombres-error"></div>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>{{ $isSpanish ? 'Apellidos *' : 'Last names *' }}</label>
|
||
<input type="text" id="resp_apellidos" class="form-control" placeholder="{{ $isSpanish ? 'Apellidos' : 'Last names' }}">
|
||
<div class="error-message" id="resp_apellidos-error"></div>
|
||
</div>
|
||
</div>
|
||
<div class="inline-fields">
|
||
<div class="form-group">
|
||
<label>{{ $isSpanish ? 'Correo electrónico *' : 'Email *' }}</label>
|
||
<input type="email" id="resp_email" class="form-control" placeholder="ejemplo@correo.com">
|
||
<div class="error-message" id="resp_email-error"></div>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>{{ $isSpanish ? 'Teléfono (con lada) *' : 'Phone (with area code) *' }}</label>
|
||
<input type="tel" id="resp_telefono" class="form-control">
|
||
<div class="error-message" id="resp_telefono-error"></div>
|
||
</div>
|
||
</div>
|
||
<div class="note"><i class="fas fa-info-circle"></i>
|
||
{{ $isSpanish ? 'Los datos de cada pasajero se solicitarán después de realizar el pago.' : 'Passenger details will be requested after payment.' }}
|
||
</div>
|
||
<div class="requirements-checklist">
|
||
<div class="requirement-item">
|
||
<div class="requirement-icon">⚠️</div>
|
||
<span>{{ $isSpanish ? 'Presentar identificación oficial el día del vuelo' : 'Present official ID on flight day' }}</span>
|
||
</div>
|
||
<div class="requirement-item">
|
||
<div class="requirement-icon">⚠️</div>
|
||
<span>{{ $isSpanish ? 'Llegar 30 minutos antes de la hora programada' : 'Arrive 30 minutes before scheduled time' }}</span>
|
||
</div>
|
||
<div class="requirement-item">
|
||
<div class="requirement-icon">⚠️</div>
|
||
<span>{{ $isSpanish ? 'El peso registrado debe ser exacto' : 'Registered weight must be accurate' }}</span>
|
||
</div>
|
||
</div>
|
||
<div style="display: flex; gap: 1rem; margin-top: 1.5rem;">
|
||
<button type="button" class="btn-secondary prev-step" data-prev="1"><i class="fas fa-arrow-left"></i> {{ $isSpanish ? 'Volver' : 'Back' }}</button>
|
||
<button type="button" class="btn-primary next-step" data-next="3">{{ $isSpanish ? 'Continuar' : 'Continue' }} <i class="fas fa-arrow-right"></i></button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
|
||
<!-- STEP 3: EXTRAS -->
|
||
<div id="step-3" class="step-content" style="display: none;">
|
||
<h3 style="font-size: 1.3rem; margin-bottom: 1.5rem; color: var(--gray-800);">
|
||
{{ $isSpanish ? 'Servicios adicionales' : 'Additional services' }}</h3>
|
||
<p style="margin-bottom: 1.5rem; color: var(--gray-600);">
|
||
{{ $isSpanish ? 'Selecciona los servicios que deseas agregar a tu reserva.' : 'Select the services you want to add to your booking.' }}
|
||
</p>
|
||
@if($todosIncluidos)
|
||
<div class="alert alert-info" style="padding:2rem; text-align:center; background:var(--gray-100); border-radius:1rem; color: var(--gray-700);">
|
||
<i class="fas fa-check-circle" style="font-size:2rem; color:var(--success-color); margin-bottom:0.5rem; display:inline-block;"></i>
|
||
<p style="margin:0;">
|
||
{{ $isSpanish ? '¡Todos los servicios adicionales ya están incluidos en tu paquete! No hay extras disponibles para seleccionar.' : 'All additional services are already included in your package! No extras available to select.' }}
|
||
</p>
|
||
</div>
|
||
@elseif($totalExtrasDisponibles > 0)
|
||
<div id="extras-container">
|
||
@foreach ($extrasFiltrados as $categoriaData)
|
||
@php $servicios = $categoriaData['servicios']; @endphp
|
||
@if (!empty($servicios))
|
||
<div class="extras-category">
|
||
<div class="category-title">{{ $categoriaData['nombre_categoria'] }}</div>
|
||
<table class="extras-table">
|
||
<thead>
|
||
<tr>
|
||
<th style="width:80px"></th>
|
||
<th>{{ $isSpanish ? 'Servicio' : 'Service' }}</th>
|
||
<th style="width:100px;text-align:center">{{ $isSpanish ? 'Costo' : 'Cost' }}</th>
|
||
<th style="width:120px;text-align:center">{{ $isSpanish ? 'Cantidad' : 'Quantity' }}</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
@foreach ($servicios as $servicio)
|
||
@php
|
||
$servicioId = $servicio['id'] ?? null;
|
||
$servicioPrecio = $servicio['precio'] ?? 0;
|
||
$servicioDescripcion = $servicio['descripcion'] ?? 'Sin descripción';
|
||
$servicioImagen = !empty($servicio['imagenes'][0]) ? $servicio['imagenes'][0] : null;
|
||
@endphp
|
||
@if ($servicioId)
|
||
<tr data-service-id="{{ $servicioId }}" data-price="{{ $servicioPrecio }}" data-description="{{ addslashes($servicioDescripcion) }}">
|
||
<td>
|
||
@if($servicioImagen)
|
||
<img src="{{ $servicioImagen }}" class="service-image" alt="{{ $servicioDescripcion }}" onerror="this.style.display='none'">
|
||
@else
|
||
<div class="service-image" style="background: var(--gray-200); display: flex; align-items: center; justify-content: center;">
|
||
<i class="fas fa-gift" style="color: var(--gray-400); font-size: 1.5rem;"></i>
|
||
</div>
|
||
@endif
|
||
</td>
|
||
<td>
|
||
<strong>{{ $servicioDescripcion }}</strong>
|
||
@if(!empty($servicio['observaciones']))
|
||
<br><small style="color: var(--gray-500); font-size: 0.7rem;">{{ Str::limit($servicio['observaciones'], 100) }}</small>
|
||
@endif
|
||
</td>
|
||
<td style="text-align: center; font-weight: 600;">{{ $simbolo }}{{ number_format($servicioPrecio, 0, ',', '.') }}</td>
|
||
<td style="text-align: center;">
|
||
<div class="service-qty-control">
|
||
<button type="button" class="qty-btn btn-decrement" data-service-id="{{ $servicioId }}">−</button>
|
||
<span class="qty-display service-qty-display" data-service-id="{{ $servicioId }}">0</span>
|
||
<button type="button" class="qty-btn btn-increment" data-service-id="{{ $servicioId }}">+</button>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
@endif
|
||
@endforeach
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
@endif
|
||
@endforeach
|
||
</div>
|
||
@else
|
||
<div class="alert alert-info" style="padding:2rem; text-align:center; background:var(--gray-100); border-radius:1rem; color: var(--gray-700);">
|
||
<i class="fas fa-info-circle" style="font-size:2rem; color:var(--primary-color); margin-bottom:0.5rem; display:inline-block;"></i>
|
||
<p style="margin:0;">
|
||
{{ $isSpanish ? 'No hay servicios adicionales disponibles para este vuelo.' : 'No additional services available for this flight.' }}
|
||
</p>
|
||
</div>
|
||
@endif
|
||
<div style="display: flex; gap: 1rem; margin-top: 2rem;">
|
||
<button type="button" class="btn-secondary prev-step" data-prev="2"><i class="fas fa-arrow-left"></i> {{ $isSpanish ? 'Volver' : 'Back' }}</button>
|
||
<button type="button" class="btn-primary next-step" data-next="4">{{ $isSpanish ? 'Continuar' : 'Continue' }} <i class="fas fa-arrow-right"></i></button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- STEP 4: PAYMENT (Clip) -->
|
||
<div id="step-4" class="step-content" style="display: none;">
|
||
<div style="text-align: center; margin-bottom: 1.5rem;">
|
||
<p style="font-size:1rem; color:var(--gray-600);">{{ $isSpanish ? 'Total a pagar' : 'Total to pay' }}</p>
|
||
<h2 id="totalMostrado" style="font-size:2.5rem; color:var(--primary-color); font-weight:800;">{{ $simbolo }}{{ number_format($precioActual, 0, ',', '.') }}</h2>
|
||
</div>
|
||
<form id="clip-payment-form">
|
||
@csrf
|
||
<div class="form-group">
|
||
<label>{{ $isSpanish ? 'Correo electrónico para el recibo *' : 'Email for receipt *' }}</label>
|
||
<input type="email" id="payment_email" class="form-control" required>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>{{ $isSpanish ? 'Teléfono *' : 'Phone *' }}</label>
|
||
<input type="tel" id="payment_phone" class="form-control" required>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>{{ $isSpanish ? 'Descripción del pago' : 'Payment description' }}</label>
|
||
<input type="text" id="payment_description" class="form-control" value="{{ $vuelo['nombre_vuelo'] }}">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>💳 {{ $isSpanish ? 'Datos de la tarjeta' : 'Card details' }}</label>
|
||
<div id="clip-checkout"></div>
|
||
<div id="clip-error" class="error-message" style="margin-top:0.5rem;"></div>
|
||
</div>
|
||
<div>
|
||
<span class="discount-toggle" id="toggleCupon">{{ $isSpanish ? '¿Tienes un código de descuento?' : 'Have a discount code?' }}</span>
|
||
<div class="discount-input" id="cuponInput" style="display:none;">
|
||
<input type="text" id="cuponCode" class="form-control" placeholder="{{ $isSpanish ? 'Ingresa tu código' : 'Enter your code' }}">
|
||
<button type="button" id="aplicarCupon" class="btn-secondary" style="width:auto;">{{ $isSpanish ? 'Aplicar' : 'Apply' }}</button>
|
||
</div>
|
||
</div>
|
||
<button type="submit" id="btnPagar" class="btn-pay"><i class="fas fa-lock"></i> {{ $isSpanish ? 'Pagar ahora' : 'Pay now' }}</button>
|
||
</form>
|
||
<div class="trust-badges">
|
||
<div class="trust-badge"><i class="fas fa-shield-alt"></i> {{ $isSpanish ? 'Pago seguro SSL' : 'SSL secure payment' }}</div>
|
||
<div class="trust-badge"><i class="fas fa-undo-alt"></i> {{ $isSpanish ? 'Cancelación gratuita (24h)' : 'Free cancellation (24h)' }}</div>
|
||
<div class="trust-badge"><i class="fas fa-headset"></i> {{ $isSpanish ? 'Atención 24/7' : '24/7 support' }}</div>
|
||
</div>
|
||
<button type="button" class="btn-secondary prev-step" data-prev="3" style="margin-top:1rem; width:100%;"><i class="fas fa-arrow-left"></i> {{ $isSpanish ? 'Volver' : 'Back' }}</button>
|
||
</div>
|
||
|
||
<!-- STEP 5: PASAJEROS Y PASE DE ABORDAR -->
|
||
<div id="step-5" class="step-content" style="display: none;">
|
||
<h3 style="font-size: 1.3rem; margin-bottom: 1.5rem; color: var(--gray-800);">
|
||
{{ $isSpanish ? 'Datos de los pasajeros' : 'Passenger details' }}</h3>
|
||
<div id="passengers-step5-container"></div>
|
||
<div id="boarding-pass-container" style="display: none; margin-top: 2rem;"></div>
|
||
<div style="display: flex; gap: 1rem; margin-top: 2rem;">
|
||
<button type="button" class="btn-secondary" id="backFromStep5Btn"><i class="fas fa-arrow-left"></i> {{ $isSpanish ? 'Volver' : 'Back' }}</button>
|
||
<button type="button" class="btn-primary" id="savePassengersBtn">{{ $isSpanish ? 'Guardar datos' : 'Save passengers' }} <i class="fas fa-save"></i></button>
|
||
</div>
|
||
<div id="imprimirContainer" style="margin-top: 1rem; text-align: center;"></div>
|
||
</div>
|
||
|
||
<!-- Detalles del viaje e incluye -->
|
||
@if (!empty($vuelo['descripcion_larga']))
|
||
<section class="section">
|
||
<h2 class="section-title">{{ $isSpanish ? 'Detalles del viaje' : 'Trip details' }}</h2>
|
||
<div class="description" style="color: var(--gray-600); line-height: 1.6;">
|
||
{!! $vuelo['descripcion_larga'] !!}
|
||
</div>
|
||
</section>
|
||
@endif
|
||
@if (!empty($incluye))
|
||
<section class="section">
|
||
<h3 class="section-subtitle"><i class="fas fa-gift"></i> {{ $isSpanish ? '¿Qué incluye tu experiencia?' : 'What\'s included in your experience?' }}</h3>
|
||
<ul class="includes-list-modern">
|
||
@foreach ($incluye as $item)
|
||
<li>
|
||
<i class="fas fa-check"></i>
|
||
<span>{{ $item['titulo_item'] ?? ($item['nombre'] ?? 'Item') }}</span>
|
||
</li>
|
||
@endforeach
|
||
</ul>
|
||
</section>
|
||
@endif
|
||
</div>
|
||
</div>
|
||
|
||
<!-- SIDEBAR -->
|
||
<aside class="sidebar">
|
||
<div class="sidebar-card">
|
||
<h3 class="card-title">
|
||
<i class="fas fa-receipt"></i> {{ $isSpanish ? 'Resumen de reserva' : 'Booking summary' }}
|
||
<a class="edit-link" data-edit-step="1"><i class="fas fa-edit"></i></a>
|
||
</h3>
|
||
<div class="summary-item">
|
||
<span>{{ $vuelo['nombre_vuelo'] }}</span>
|
||
<span id="resumenPasajeros">{{ $isSpanish ? '1 adulto' : '1 adult' }}</span>
|
||
</div>
|
||
<div class="summary-item">
|
||
<span>{{ $isSpanish ? 'Fecha' : 'Date' }}</span>
|
||
<span id="resumenFecha">{{ $isSpanish ? 'No seleccionada' : 'Not selected' }} <a class="edit-link" data-edit-step="1"><i class="fas fa-pencil-alt"></i></a></span>
|
||
</div>
|
||
<div class="summary-item">
|
||
<span>{{ $isSpanish ? 'Adultos' : 'Adults' }}</span>
|
||
<span id="resumenAdultos">1 <a class="edit-link" data-edit-step="1"><i class="fas fa-pencil-alt"></i></a></span>
|
||
</div>
|
||
<div class="summary-item">
|
||
<span>{{ $isSpanish ? 'Niños' : 'Children' }}</span>
|
||
<span id="resumenNinos">0 <a class="edit-link" data-edit-step="1"><i class="fas fa-pencil-alt"></i></a></span>
|
||
</div>
|
||
<div class="summary-item subtotal-vuelo">
|
||
<span>{{ $isSpanish ? 'Subtotal vuelo' : 'Flight subtotal' }}</span>
|
||
<span id="subtotalVuelo">{{ $simbolo }}{{ number_format($precioActual, 0, ',', '.') }}</span>
|
||
</div>
|
||
<div id="extras-summary" class="summary-extras"></div>
|
||
<div class="summary-item" id="descuentoAplicado" style="display:none;">
|
||
<span>{{ $isSpanish ? 'Descuento' : 'Discount' }}</span>
|
||
<span id="descuentoMonto" style="color: var(--success-color);">-<span></span></span>
|
||
</div>
|
||
<div class="summary-total">
|
||
<span>{{ $isSpanish ? 'Total' : 'Total' }}</span>
|
||
<span id="resumenTotal">{{ $simbolo }}{{ number_format($precioActual, 0, ',', '.') }}</span>
|
||
</div>
|
||
</div>
|
||
</aside>
|
||
</div>
|
||
</div>
|
||
</article>
|
||
@else
|
||
<div class="container">
|
||
<div style="background: var(--bg-card); padding:2rem; border-radius:1rem; text-align:center; color: var(--gray-700);">
|
||
<i class="fas fa-exclamation-triangle" style="font-size:3rem; color:var(--warning-color); margin-bottom:1rem;"></i>
|
||
<h2>{{ $isSpanish ? 'Vuelo no encontrado' : 'Flight not found' }}</h2>
|
||
</div>
|
||
</div>
|
||
@endif
|
||
|
||
<!-- Botón flotante para cambiar tema (Modo Noche/Día) -->
|
||
<button id="theme-toggle" class="theme-toggle-btn" aria-label="Cambiar tema">
|
||
<i class="fas fa-moon"></i>
|
||
</button>
|
||
@endsection
|
||
|
||
@push('js')
|
||
<script src="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.js"></script>
|
||
<script src="https://cdn.jsdelivr.net/npm/flatpickr"></script>
|
||
<script src="https://cdn.jsdelivr.net/npm/flatpickr/dist/l10n/es.js"></script>
|
||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
|
||
<script src="https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.8/js/intlTelInput.min.js"></script>
|
||
<script src="https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.8/js/utils.min.js"></script>
|
||
<script src="https://sdk.clip.mx/js/clip-sdk.js"></script>
|
||
|
||
<script>
|
||
// ========== VARIABLES PHP EXPORTADAS ==========
|
||
const PHP_VARS = {
|
||
vuelo: @json($vuelo),
|
||
categoria: @json($categoria),
|
||
imagenesGaleria: @json($imagenesGaleria),
|
||
incluye: @json($incluye),
|
||
extrasArray: @json($extrasArray),
|
||
serviciosIncluidosIds: @json($serviciosIncluidosIds),
|
||
extrasFiltrados: @json($extrasFiltrados),
|
||
totalExtrasDisponibles: {{ $totalExtrasDisponibles }},
|
||
todosIncluidos: {{ $todosIncluidos ? 'true' : 'false' }},
|
||
moneda: '{{ $moneda }}',
|
||
simbolo: '{{ $simbolo }}',
|
||
precioActual: {{ $precioActual }},
|
||
precioOriginal: {{ $precioOriginal }},
|
||
precioNino: {{ $precioNino }},
|
||
tieneDescuento: {{ $tieneDescuento ? 'true' : 'false' }},
|
||
descuentoPorcentaje: {{ $descuentoPorcentaje }},
|
||
isSpanish: {{ $isSpanish ? 'true' : 'false' }},
|
||
flightId: {{ $vuelo['id_vuelo'] ?? 0 }},
|
||
flightName: '{{ addslashes($vuelo['nombre_vuelo'] ?? '') }}',
|
||
flightShortDesc: '{{ addslashes($vuelo['descripcion_corta'] ?? '') }}',
|
||
flightLongDesc: `{!! addslashes($vuelo['descripcion_larga'] ?? '') !!}`,
|
||
flightTime: '{{ $vuelo['hora_actividad'] ?? '06:30' }}',
|
||
ratingGlobal: {{ $vuelo['raiting_global_number'] ?? 0 }},
|
||
csrfToken: '{{ csrf_token() }}',
|
||
clipApiKey: '{{ $clipApiKey }}',
|
||
routeCheckoutProcesar: '{{ route("checkout.procesar") }}',
|
||
routePaseAbordar: '{{ route("checkout.pase.abordar") }}',
|
||
routeCotizaVuelo: '{{ route("checkout.cotiza.vuelo") }}',
|
||
highlightedText: '{{ $vuelo["highlighted_text_flight_es"] ?? "" }}',
|
||
highlightedBgColor: '{{ $vuelo["highlighted_background_color_badge_flight_es"] ?? "#2563eb" }}'
|
||
};
|
||
|
||
function isServicioIncluidoEnPaquete(serviceId) {
|
||
return PHP_VARS.serviciosIncluidosIds.includes(parseInt(serviceId));
|
||
}
|
||
|
||
document.addEventListener('DOMContentLoaded', function () {
|
||
// ========== MODO OSCURO / CLARO ==========
|
||
const themeToggleBtn = document.getElementById('theme-toggle');
|
||
if (themeToggleBtn) {
|
||
const themeIcon = themeToggleBtn.querySelector('i');
|
||
const savedTheme = localStorage.getItem('theme');
|
||
const systemPrefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||
|
||
if (savedTheme === 'dark' || (!savedTheme && systemPrefersDark)) {
|
||
document.body.classList.add('dark');
|
||
themeIcon.classList.replace('fa-moon', 'fa-sun');
|
||
}
|
||
|
||
themeToggleBtn.addEventListener('click', () => {
|
||
document.body.classList.toggle('dark');
|
||
const isDark = document.body.classList.contains('dark');
|
||
localStorage.setItem('theme', isDark ? 'dark' : 'light');
|
||
|
||
if (isDark) {
|
||
themeIcon.classList.replace('fa-moon', 'fa-sun');
|
||
} else {
|
||
themeIcon.classList.replace('fa-sun', 'fa-moon');
|
||
}
|
||
});
|
||
}
|
||
|
||
// ========== CONFIGURACIÓN INICIAL ==========
|
||
const precioAdulto = PHP_VARS.precioActual;
|
||
const precioNino = PHP_VARS.precioNino;
|
||
const simbolo = PHP_VARS.simbolo;
|
||
const isSpanish = PHP_VARS.isSpanish;
|
||
let adultos = 1, ninios = 0, fechaSeleccionada = '', fechaLibre = false;
|
||
let serviciosSeleccionados = [], descuentoCupon = 0, currentStep = 1, reservationId = null;
|
||
let passengerDataSaved = false;
|
||
let clipCard = null;
|
||
let itiResponsable = null;
|
||
|
||
window.adultos = adultos;
|
||
window.ninios = ninios;
|
||
window.fechaSeleccionada = fechaSeleccionada;
|
||
window.fechaLibre = fechaLibre;
|
||
window.serviciosSeleccionados = serviciosSeleccionados;
|
||
|
||
window.aplicarCuponAuto = function (codigo) {
|
||
if (codigo === 'DESCUENTO10') {
|
||
const subtotal = (adultos * precioAdulto) + (ninios * precioNino) + getTotalExtras();
|
||
descuentoCupon = subtotal * 0.10;
|
||
actualizarResumen();
|
||
return true;
|
||
}
|
||
return false;
|
||
};
|
||
|
||
function capitalizeWords(str) {
|
||
if (!str) return '';
|
||
return str.toLowerCase().replace(/\b\w/g, char => char.toUpperCase());
|
||
}
|
||
|
||
function setupNameCapitalization(inputElement) {
|
||
if (!inputElement) return;
|
||
inputElement.addEventListener('blur', function () {
|
||
const formatted = capitalizeWords(this.value);
|
||
if (this.value !== formatted) {
|
||
this.value = formatted;
|
||
this.dispatchEvent(new Event('change', { bubbles: true }));
|
||
}
|
||
});
|
||
}
|
||
|
||
function generateDayOptions() {
|
||
let html = '<option value="">' + (isSpanish ? 'Día' : 'Day') + '</option>';
|
||
for (let i = 1; i <= 31; i++) html += `<option value="${i}">${i}</option>`;
|
||
return html;
|
||
}
|
||
|
||
function generateMonthOptions() {
|
||
const months = isSpanish ? ['Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre'] : ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
||
let html = '<option value="">' + (isSpanish ? 'Mes' : 'Month') + '</option>';
|
||
months.forEach((m, idx) => html += `<option value="${idx + 1}">${m}</option>`);
|
||
return html;
|
||
}
|
||
|
||
function generateYearOptions() {
|
||
const currentYear = new Date().getFullYear();
|
||
let html = '<option value="">' + (isSpanish ? 'Año' : 'Year') + '</option>';
|
||
for (let y = currentYear; y >= 1900; y--) html += `<option value="${y}">${y}</option>`;
|
||
return html;
|
||
}
|
||
|
||
function renderPassengersStep5(container, totalAdultos, totalNinos, existingData = []) {
|
||
if (!container) return;
|
||
const total = totalAdultos + totalNinos;
|
||
container.innerHTML = '';
|
||
for (let i = 0; i < total; i++) {
|
||
const tipo = i < totalAdultos ? (isSpanish ? 'Adulto' : 'Adult') : (isSpanish ? 'Niño' : 'Child');
|
||
const idx = i + 1;
|
||
const card = document.createElement('div');
|
||
card.className = 'passenger-card';
|
||
card.style.marginBottom = '1rem';
|
||
card.style.padding = '1rem';
|
||
card.style.border = '1px solid var(--gray-200)';
|
||
card.style.borderRadius = '0.75rem';
|
||
card.style.background = 'var(--bg-card)';
|
||
card.innerHTML = `
|
||
<div class="passenger-title" style="font-weight:700; margin-bottom:0.75rem; color: var(--gray-800);">${tipo} ${idx}</div>
|
||
<div class="inline-fields">
|
||
<div class="form-group"><label>${isSpanish ? 'Nombres *' : 'First names *'}</label><input type="text" class="form-control passenger-nombres" data-pax="${i}" placeholder="${isSpanish ? 'Nombres' : 'First names'}"></div>
|
||
<div class="form-group"><label>${isSpanish ? 'Apellidos *' : 'Last names *'}</label><input type="text" class="form-control passenger-apellidos" data-pax="${i}" placeholder="${isSpanish ? 'Apellidos' : 'Last names'}"></div>
|
||
</div>
|
||
<div class="inline-fields">
|
||
<div class="form-group"><label>${isSpanish ? 'Día *' : 'Day *'}</label><select class="form-control passenger-day" data-pax="${i}">${generateDayOptions()}</select></div>
|
||
<div class="form-group"><label>${isSpanish ? 'Mes *' : 'Month *'}</label><select class="form-control passenger-month" data-pax="${i}">${generateMonthOptions()}</select></div>
|
||
<div class="form-group"><label>${isSpanish ? 'Año *' : 'Year *'}</label><select class="form-control passenger-year" data-pax="${i}">${generateYearOptions()}</select></div>
|
||
</div>
|
||
<div class="form-group"><label>${isSpanish ? 'Peso aproximado (kg) *' : 'Approximate weight (kg) *'}</label><input type="number" step="1" class="form-control passenger-peso" data-pax="${i}" placeholder="${isSpanish ? 'Ej: 75' : 'Ex: 75'}"></div>
|
||
`;
|
||
container.appendChild(card);
|
||
}
|
||
for (let i = 0; i < total; i++) {
|
||
const nombresInput = document.querySelector(`#passengers-step5-container .passenger-nombres[data-pax="${i}"]`);
|
||
const apellidosInput = document.querySelector(`#passengers-step5-container .passenger-apellidos[data-pax="${i}"]`);
|
||
setupNameCapitalization(nombresInput);
|
||
setupNameCapitalization(apellidosInput);
|
||
if (existingData[i]) {
|
||
const p = existingData[i];
|
||
if (nombresInput) nombresInput.value = capitalizeWords(p.nombres || '');
|
||
if (apellidosInput) apellidosInput.value = capitalizeWords(p.apellidos || '');
|
||
const daySelect = document.querySelector(`#passengers-step5-container .passenger-day[data-pax="${i}"]`);
|
||
const monthSelect = document.querySelector(`#passengers-step5-container .passenger-month[data-pax="${i}"]`);
|
||
const yearSelect = document.querySelector(`#passengers-step5-container .passenger-year[data-pax="${i}"]`);
|
||
const pesoInput = document.querySelector(`#passengers-step5-container .passenger-peso[data-pax="${i}"]`);
|
||
if (daySelect && p.fecha_nacimiento) {
|
||
const [year, month, day] = p.fecha_nacimiento.split('-');
|
||
daySelect.value = parseInt(day, 10);
|
||
monthSelect.value = parseInt(month, 10);
|
||
yearSelect.value = parseInt(year, 10);
|
||
}
|
||
if (pesoInput) pesoInput.value = p.peso_kg || '';
|
||
}
|
||
}
|
||
}
|
||
|
||
function collectPassengerDataFromStep5(totalAdultos, totalNinos) {
|
||
const total = totalAdultos + totalNinos;
|
||
const pasajeros = [];
|
||
for (let i = 0; i < total; i++) {
|
||
const nombresRaw = document.querySelector(`#passengers-step5-container .passenger-nombres[data-pax="${i}"]`)?.value.trim() || '';
|
||
const apellidosRaw = document.querySelector(`#passengers-step5-container .passenger-apellidos[data-pax="${i}"]`)?.value.trim() || '';
|
||
const nombres = capitalizeWords(nombresRaw);
|
||
const apellidos = capitalizeWords(apellidosRaw);
|
||
const dia = document.querySelector(`#passengers-step5-container .passenger-day[data-pax="${i}"]`)?.value;
|
||
const mes = document.querySelector(`#passengers-step5-container .passenger-month[data-pax="${i}"]`)?.value;
|
||
const año = document.querySelector(`#passengers-step5-container .passenger-year[data-pax="${i}"]`)?.value;
|
||
const peso = document.querySelector(`#passengers-step5-container .passenger-peso[data-pax="${i}"]`)?.value;
|
||
if (!nombres || !apellidos || !dia || !mes || !año || !peso) return null;
|
||
const fechaNac = `${año}-${mes.padStart(2, '0')}-${dia.padStart(2, '0')}`;
|
||
const fechaObj = new Date(fechaNac);
|
||
if (isNaN(fechaObj.getTime()) || fechaObj > new Date()) return null;
|
||
pasajeros.push({
|
||
nombres, apellidos,
|
||
fecha_nacimiento: fechaNac,
|
||
peso_kg: parseInt(peso),
|
||
tipo: i < totalAdultos ? 'adulto' : 'nino'
|
||
});
|
||
}
|
||
return pasajeros;
|
||
}
|
||
|
||
function getSubtotalVuelo() { return (adultos * precioAdulto) + (ninios * precioNino); }
|
||
function getTotalExtras() { return serviciosSeleccionados.reduce((sum, s) => sum + (s.cantidad * s.precio), 0); }
|
||
function getTotalGeneral() { return Math.max(0, getSubtotalVuelo() + getTotalExtras() - descuentoCupon); }
|
||
|
||
function actualizarFechaResumen() {
|
||
let texto = '';
|
||
if (fechaLibre) texto = isSpanish ? 'Por confirmar' : 'To be confirmed';
|
||
else if (fechaSeleccionada) {
|
||
const date = new Date(fechaSeleccionada);
|
||
texto = !isNaN(date.getTime()) ? date.toLocaleDateString(isSpanish ? 'es-MX' : 'en-US', { year: 'numeric', month: 'long', day: 'numeric' }) : fechaSeleccionada;
|
||
} else texto = isSpanish ? 'No seleccionada' : 'Not selected';
|
||
const rf = document.getElementById('resumenFecha');
|
||
if (rf) rf.innerHTML = texto + ' <a class="edit-link" data-edit-step="1"><i class="fas fa-pencil-alt"></i></a>';
|
||
}
|
||
|
||
function actualizarResumen() {
|
||
const subtotalVuelo = getSubtotalVuelo(), totalExtras = getTotalExtras(), totalGeneral = getTotalGeneral();
|
||
const ra = document.getElementById('resumenAdultos'), rn = document.getElementById('resumenNinos'), rp = document.getElementById('resumenPasajeros');
|
||
const sv = document.getElementById('subtotalVuelo'), es = document.getElementById('extras-summary'), rt = document.getElementById('resumenTotal'), tm = document.getElementById('totalMostrado');
|
||
if (ra) ra.innerHTML = adultos + ' <a class="edit-link" data-edit-step="1"><i class="fas fa-pencil-alt"></i></a>';
|
||
if (rn) rn.innerHTML = ninios + ' <a class="edit-link" data-edit-step="1"><i class="fas fa-pencil-alt"></i></a>';
|
||
if (rp) {
|
||
const adultText = isSpanish ? `adulto${adultos !== 1 ? 's' : ''}` : `adult${adultos !== 1 ? 's' : ''}`;
|
||
const childText = isSpanish ? `niño${ninios !== 1 ? 's' : ''}` : `child${ninios !== 1 ? 'ren' : ''}`;
|
||
rp.textContent = `${adultos} ${adultText}${ninios > 0 ? `, ${ninios} ${childText}` : ''}`;
|
||
}
|
||
if (sv) sv.textContent = simbolo + subtotalVuelo.toLocaleString('es-MX');
|
||
if (es) {
|
||
if (serviciosSeleccionados.length === 0 || serviciosSeleccionados.every(s => s.cantidad === 0))
|
||
es.innerHTML = `<div class="extra-item" style="color:var(--gray-400); justify-content:center;">${isSpanish ? 'No hay extras seleccionados' : 'No extras selected'}</div>`;
|
||
else {
|
||
let html = '';
|
||
serviciosSeleccionados.forEach(s => {
|
||
if (s.cantidad > 0) html += `<div class="extra-item"><span>${s.cantidad}x ${s.descripcion.substring(0, 30)}${s.descripcion.length > 30 ? '...' : ''}</span><span>${simbolo}${(s.cantidad * s.precio).toLocaleString('es-MX')}</span></div>`;
|
||
});
|
||
es.innerHTML = html;
|
||
}
|
||
}
|
||
const totalFormateado = simbolo + totalGeneral.toLocaleString('es-MX');
|
||
if (rt) rt.textContent = totalFormateado;
|
||
if (tm) tm.textContent = totalFormateado;
|
||
guardarDatosEnLocalStorage();
|
||
}
|
||
|
||
function syncServiciosDesdeVisores() {
|
||
const displays = document.querySelectorAll('#step-3 .service-qty-display');
|
||
const nuevos = [];
|
||
displays.forEach(span => {
|
||
const id = parseInt(span.dataset.serviceId);
|
||
const cantidad = parseInt(span.textContent) || 0;
|
||
if (!isServicioIncluidoEnPaquete(id) && cantidad > 0) {
|
||
const row = span.closest('tr');
|
||
if (row) {
|
||
const precio = parseFloat(row.dataset.price);
|
||
const descripcion = row.dataset.description;
|
||
nuevos.push({ id, cantidad, precio, descripcion });
|
||
}
|
||
}
|
||
});
|
||
serviciosSeleccionados = nuevos;
|
||
window.serviciosSeleccionados = nuevos;
|
||
actualizarResumen();
|
||
}
|
||
|
||
function showError(id, mensaje) {
|
||
const el = document.getElementById(id);
|
||
if (el) { el.textContent = mensaje; el.classList.add('show'); }
|
||
}
|
||
function clearErrors() { document.querySelectorAll('.error-message').forEach(el => el.classList.remove('show')); }
|
||
|
||
function validarStep1() {
|
||
clearErrors();
|
||
let valido = true;
|
||
if (!fechaSeleccionada && !fechaLibre) { showError('fecha-error', isSpanish ? 'Selecciona una fecha o marca "Fecha por confirmar"' : 'Select a date or check "Date to be confirmed"'); valido = false; }
|
||
if (adultos < 1) { showError('fecha-error', isSpanish ? 'Debe haber al menos 1 adulto' : 'There must be at least 1 adult'); valido = false; }
|
||
return valido;
|
||
}
|
||
|
||
function validarStep2() {
|
||
clearErrors();
|
||
let valido = true;
|
||
const nombres = document.getElementById('resp_nombres')?.value.trim();
|
||
const apellidos = document.getElementById('resp_apellidos')?.value.trim();
|
||
const email = document.getElementById('resp_email')?.value.trim();
|
||
const telefonoRaw = document.getElementById('resp_telefono')?.value.trim();
|
||
|
||
if (!nombres) { showError('resp_nombres-error', isSpanish ? 'Nombres requeridos' : 'First names required'); valido = false; }
|
||
if (!apellidos) { showError('resp_apellidos-error', isSpanish ? 'Apellidos requeridos' : 'Last names required'); valido = false; }
|
||
if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) { showError('resp_email-error', isSpanish ? 'Correo inválido' : 'Invalid email'); valido = false; }
|
||
let telefonoValido = itiResponsable ? itiResponsable.isValidNumber() : (telefonoRaw?.replace(/\D/g, '').length >= 8);
|
||
if (!telefonoRaw || !telefonoValido) { showError('resp_telefono-error', isSpanish ? 'Teléfono inválido' : 'Invalid phone number'); valido = false; }
|
||
|
||
return valido;
|
||
}
|
||
|
||
function canGoToStep(targetStep) {
|
||
if (targetStep < currentStep) return true;
|
||
if (targetStep === 2) return validarStep1();
|
||
if (targetStep === 3) return validarStep1() && validarStep2();
|
||
if (targetStep === 4) return validarStep1() && validarStep2();
|
||
if (targetStep === 5) return validarStep1() && validarStep2() && (reservationId !== null);
|
||
return true;
|
||
}
|
||
|
||
function inicializarClipEnPaso4() {
|
||
if (clipCard) return;
|
||
const clipCheckoutDiv = document.getElementById('clip-checkout');
|
||
if (clipCheckoutDiv && PHP_VARS.clipApiKey && typeof window.ClipSDK !== 'undefined') {
|
||
try {
|
||
const clip = new window.ClipSDK(PHP_VARS.clipApiKey);
|
||
clipCard = clip.element.create("Card", {
|
||
theme: document.body.classList.contains('dark') ? "dark" : "light",
|
||
locale: isSpanish ? "es" : "en"
|
||
});
|
||
clipCard.mount("clip-checkout");
|
||
} catch (e) {
|
||
console.error('Clip init error:', e);
|
||
const errDiv = document.getElementById('clip-error');
|
||
if (errDiv) errDiv.textContent = isSpanish ? 'Error al cargar el módulo de pago' : 'Error loading payment module';
|
||
}
|
||
}
|
||
}
|
||
|
||
function cambiarPaso(step) {
|
||
if (!canGoToStep(step)) {
|
||
if (step === 2 && !validarStep1()) cambiarPaso(1);
|
||
else if ((step === 3 || step === 4) && !validarStep2()) cambiarPaso(2);
|
||
return;
|
||
}
|
||
document.querySelectorAll('.step-content').forEach((content, idx) => { content.style.display = (idx + 1 === step) ? 'block' : 'none'; });
|
||
document.querySelectorAll('.step-item').forEach((item, idx) => {
|
||
const num = idx + 1;
|
||
if (num === step) item.classList.add('active'); else item.classList.remove('active');
|
||
if (num < step) item.classList.add('completed'); else item.classList.remove('completed');
|
||
});
|
||
const gallery = document.getElementById('gallerySection');
|
||
if (gallery) gallery.classList.toggle('hide-gallery', step > 1);
|
||
if (step === 3) { setupExtraButtons(); syncServiciosDesdeVisores(); }
|
||
if (step === 4) {
|
||
inicializarClipEnPaso4();
|
||
const pe = document.getElementById('payment_email'), pp = document.getElementById('payment_phone');
|
||
const re = document.getElementById('resp_email'), rp = document.getElementById('resp_telefono');
|
||
if (pe && re) pe.value = re.value;
|
||
if (pp && rp) pp.value = rp.value;
|
||
}
|
||
if (step === 5 && !passengerDataSaved) {
|
||
renderPassengersStep5(document.getElementById('passengers-step5-container'), adultos, ninios, []);
|
||
}
|
||
currentStep = step;
|
||
document.querySelector('.main-card')?.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||
}
|
||
|
||
function handleIncrementDecrement(e) {
|
||
const btn = e.currentTarget;
|
||
const serviceId = parseInt(btn.dataset.serviceId);
|
||
if (isServicioIncluidoEnPaquete(serviceId)) {
|
||
Swal.fire({ icon: 'warning', title: isSpanish ? 'Servicio incluido' : 'Service included', text: isSpanish ? 'Este servicio ya viene incluido en tu paquete.' : 'This service is already included in your package.', timer: 2000, showConfirmButton: false });
|
||
return;
|
||
}
|
||
const display = document.querySelector(`.service-qty-display[data-service-id="${serviceId}"]`);
|
||
if (!display) return;
|
||
let currentQty = parseInt(display.textContent) || 0;
|
||
if (btn.classList.contains('btn-increment')) currentQty++;
|
||
else if (btn.classList.contains('btn-decrement')) currentQty = Math.max(0, currentQty - 1);
|
||
display.textContent = currentQty;
|
||
syncServiciosDesdeVisores();
|
||
}
|
||
|
||
function setupExtraButtons() {
|
||
document.querySelectorAll('#step-3 .btn-increment, #step-3 .btn-decrement').forEach(btn => {
|
||
btn.removeEventListener('click', handleIncrementDecrement);
|
||
btn.addEventListener('click', handleIncrementDecrement);
|
||
});
|
||
}
|
||
|
||
function getReservationData(cardToken = null) {
|
||
return {
|
||
id_vuelo: PHP_VARS.flightId,
|
||
fecha: fechaLibre ? null : fechaSeleccionada,
|
||
adultos, ninios,
|
||
responsable: {
|
||
nombres: capitalizeWords(document.getElementById('resp_nombres')?.value.trim() || ''),
|
||
apellidos: capitalizeWords(document.getElementById('resp_apellidos')?.value.trim() || ''),
|
||
email: document.getElementById('resp_email')?.value.trim(),
|
||
telefono: itiResponsable ? itiResponsable.getNumber() : document.getElementById('resp_telefono')?.value.trim()
|
||
},
|
||
servicios: serviciosSeleccionados.filter(s => s.cantidad > 0).map(s => ({ id: s.id, cantidad: s.cantidad })),
|
||
total: getTotalGeneral(),
|
||
cupon: document.getElementById('cuponCode')?.value || '',
|
||
_token: PHP_VARS.csrfToken,
|
||
...(cardToken && { card_token: cardToken })
|
||
};
|
||
}
|
||
|
||
async function enviarACotizacion(reservationData, paymentResponse) {
|
||
try {
|
||
const cotizacionPayload = { ...reservationData, reservation_id: reservationId, payment_id: paymentResponse.payment_id || null, payment_status: paymentResponse.payment_status, payment_email: document.getElementById('payment_email')?.value.trim(), payment_phone: document.getElementById('payment_phone')?.value.trim(), payment_description: document.getElementById('payment_description')?.value.trim(), fecha_vuelo: reservationData.fecha ? new Date(reservationData.fecha).toISOString() : null, created_at: new Date().toISOString() };
|
||
await axios.post(PHP_VARS.routeCotizaVuelo, cotizacionPayload, { headers: { 'X-CSRF-TOKEN': PHP_VARS.csrfToken } });
|
||
} catch (error) { console.error('Error al registrar cotización:', error); }
|
||
}
|
||
|
||
function guardarDatosEnLocalStorage() {
|
||
let pasajerosGuardados = [];
|
||
if (currentStep === 5 && !passengerDataSaved) {
|
||
const total = adultos + ninios;
|
||
for (let i = 0; i < total; i++) {
|
||
const nombres = document.querySelector(`#passengers-step5-container .passenger-nombres[data-pax="${i}"]`)?.value;
|
||
const apellidos = document.querySelector(`#passengers-step5-container .passenger-apellidos[data-pax="${i}"]`)?.value;
|
||
const dia = document.querySelector(`#passengers-step5-container .passenger-day[data-pax="${i}"]`)?.value;
|
||
const mes = document.querySelector(`#passengers-step5-container .passenger-month[data-pax="${i}"]`)?.value;
|
||
const año = document.querySelector(`#passengers-step5-container .passenger-year[data-pax="${i}"]`)?.value;
|
||
const peso = document.querySelector(`#passengers-step5-container .passenger-peso[data-pax="${i}"]`)?.value;
|
||
if (nombres && apellidos && dia && mes && año && peso) pasajerosGuardados.push({ nombres, apellidos, dia, mes, año, peso });
|
||
}
|
||
}
|
||
const datos = {
|
||
step1: { fecha_vuelo: window.fechaSeleccionada || '', fechaLibre: window.fechaLibre || false, adultos: window.adultos || 1, ninios: window.ninios || 0 },
|
||
step2: { responsable: { nombres: document.getElementById('resp_nombres')?.value || '', apellidos: document.getElementById('resp_apellidos')?.value || '', email: document.getElementById('resp_email')?.value || '', telefono: document.getElementById('resp_telefono')?.value || '' } },
|
||
step3: { extras_seleccionados: window.serviciosSeleccionados || [] },
|
||
step4: { payment_email: document.getElementById('payment_email')?.value || '', payment_phone: document.getElementById('payment_phone')?.value || '', payment_description: document.getElementById('payment_description')?.value || '', cupon: document.getElementById('cuponCode')?.value || '' },
|
||
step5: { pasajeros: pasajerosGuardados, reservationId: reservationId }
|
||
};
|
||
localStorage.setItem('datosReservaVuelo', JSON.stringify(datos));
|
||
}
|
||
|
||
function cargarDatosDesdeLocalStorage() {
|
||
const datosGuardados = localStorage.getItem('datosReservaVuelo');
|
||
if (!datosGuardados) return false;
|
||
try {
|
||
const datos = JSON.parse(datosGuardados);
|
||
if (datos.step1) {
|
||
if (datos.step1.fecha_vuelo && document.getElementById('fecha_vuelo')) {
|
||
document.getElementById('fecha_vuelo').value = datos.step1.fecha_vuelo;
|
||
if (document.getElementById('fecha_vuelo_native')) document.getElementById('fecha_vuelo_native').value = datos.step1.fecha_vuelo;
|
||
window.fechaSeleccionada = datos.step1.fecha_vuelo;
|
||
fechaSeleccionada = datos.step1.fecha_vuelo;
|
||
}
|
||
if (datos.step1.fechaLibre !== undefined && document.getElementById('fechaLibreCheckbox')) {
|
||
document.getElementById('fechaLibreCheckbox').checked = datos.step1.fechaLibre;
|
||
window.fechaLibre = datos.step1.fechaLibre;
|
||
fechaLibre = datos.step1.fechaLibre;
|
||
const fi = document.getElementById('fecha_vuelo'), fn = document.getElementById('fecha_vuelo_native');
|
||
if (window.fechaLibre) { if (fi) fi.disabled = true; if (fn) fn.disabled = true; }
|
||
else { if (fi) fi.disabled = false; if (fn) fn.disabled = false; }
|
||
}
|
||
if (datos.step1.adultos !== undefined && document.getElementById('adultos')) {
|
||
document.getElementById('adultos').value = datos.step1.adultos;
|
||
window.adultos = datos.step1.adultos;
|
||
adultos = datos.step1.adultos;
|
||
}
|
||
if (datos.step1.ninios !== undefined && document.getElementById('ninios')) {
|
||
document.getElementById('ninios').value = datos.step1.ninios;
|
||
window.ninios = datos.step1.ninios;
|
||
ninios = datos.step1.ninios;
|
||
}
|
||
}
|
||
if (datos.step2 && datos.step2.responsable) {
|
||
const resp = datos.step2.responsable;
|
||
if (document.getElementById('resp_nombres')) document.getElementById('resp_nombres').value = capitalizeWords(resp.nombres || '');
|
||
if (document.getElementById('resp_apellidos')) document.getElementById('resp_apellidos').value = capitalizeWords(resp.apellidos || '');
|
||
if (document.getElementById('resp_email')) document.getElementById('resp_email').value = resp.email || '';
|
||
if (document.getElementById('resp_telefono')) document.getElementById('resp_telefono').value = resp.telefono || '';
|
||
}
|
||
if (datos.step3 && datos.step3.extras_seleccionados) window.extrasToLoad = datos.step3.extras_seleccionados;
|
||
if (datos.step4) {
|
||
if (document.getElementById('payment_email')) document.getElementById('payment_email').value = datos.step4.payment_email || '';
|
||
if (document.getElementById('payment_phone')) document.getElementById('payment_phone').value = datos.step4.payment_phone || '';
|
||
if (document.getElementById('payment_description') && datos.step4.payment_description) document.getElementById('payment_description').value = datos.step4.payment_description;
|
||
if (document.getElementById('cuponCode') && datos.step4.cupon) {
|
||
document.getElementById('cuponCode').value = datos.step4.cupon;
|
||
setTimeout(() => { if (window.aplicarCuponAuto) window.aplicarCuponAuto(datos.step4.cupon); }, 500);
|
||
}
|
||
}
|
||
if (datos.step5 && datos.step5.pasajeros && datos.step5.pasajeros.length) {
|
||
window.passengerDataToLoad = datos.step5.pasajeros;
|
||
reservationId = datos.step5.reservationId;
|
||
}
|
||
setTimeout(() => {
|
||
actualizarResumen();
|
||
actualizarFechaResumen();
|
||
if (currentStep === 3 && window.extrasToLoad) {
|
||
setTimeout(() => {
|
||
window.extrasToLoad.forEach(extra => {
|
||
const display = document.querySelector(`.service-qty-display[data-service-id="${extra.id}"]`);
|
||
if (display) display.textContent = extra.cantidad;
|
||
});
|
||
syncServiciosDesdeVisores();
|
||
window.extrasToLoad = null;
|
||
}, 200);
|
||
}
|
||
if (currentStep === 5 && window.passengerDataToLoad) renderPassengersStep5(document.getElementById('passengers-step5-container'), adultos, ninios, window.passengerDataToLoad);
|
||
}, 100);
|
||
return true;
|
||
} catch (e) { return false; }
|
||
}
|
||
|
||
function setupAutoSave() {
|
||
setInterval(() => { if (document.visibilityState === 'visible') guardarDatosEnLocalStorage(); }, 30000);
|
||
window.addEventListener('beforeunload', () => guardarDatosEnLocalStorage());
|
||
const inputs = ['#fecha_vuelo', '#fecha_vuelo_native', '#fechaLibreCheckbox', '#adultos', '#ninios', '#resp_nombres', '#resp_apellidos', '#resp_email', '#resp_telefono', '#payment_email', '#payment_phone', '#cuponCode'];
|
||
inputs.forEach(sel => {
|
||
const el = document.querySelector(sel);
|
||
if (el) {
|
||
el.addEventListener('change', () => guardarDatosEnLocalStorage());
|
||
if (el.tagName === 'INPUT' && el.type !== 'checkbox') el.addEventListener('input', () => guardarDatosEnLocalStorage());
|
||
}
|
||
});
|
||
}
|
||
|
||
const respTel = document.getElementById('resp_telefono');
|
||
if (respTel) {
|
||
itiResponsable = window.intlTelInput(respTel, {
|
||
initialCountry: "mx",
|
||
utilsScript: "https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.8/js/utils.js",
|
||
preferredCountries: ['mx', 'us', 'es'],
|
||
separateDialCode: true
|
||
});
|
||
}
|
||
|
||
const clipForm = document.getElementById('clip-payment-form');
|
||
if (clipForm) {
|
||
clipForm.addEventListener('submit', async function (e) {
|
||
e.preventDefault();
|
||
if (!validarStep1()) { cambiarPaso(1); return; }
|
||
if (!validarStep2()) { cambiarPaso(2); return; }
|
||
const paymentEmail = document.getElementById('payment_email');
|
||
const paymentPhone = document.getElementById('payment_phone');
|
||
if (!paymentEmail.value.trim() || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(paymentEmail.value.trim())) {
|
||
Swal.fire({ icon: 'error', title: isSpanish ? 'Error' : 'Error', text: isSpanish ? 'Ingresa un correo electrónico válido' : 'Enter a valid email', timer: 2000, showConfirmButton: false });
|
||
return;
|
||
}
|
||
if (!paymentPhone.value.trim()) {
|
||
Swal.fire({ icon: 'error', title: isSpanish ? 'Error' : 'Error', text: isSpanish ? 'Ingresa un número de teléfono' : 'Enter a phone number', timer: 2000, showConfirmButton: false });
|
||
return;
|
||
}
|
||
const btnPagar = document.getElementById('btnPagar');
|
||
btnPagar.disabled = true;
|
||
btnPagar.innerHTML = '<i class="fas fa-spinner fa-pulse"></i> ' + (isSpanish ? 'Procesando pago...' : 'Processing...');
|
||
try {
|
||
if (!clipCard) throw new Error(isSpanish ? 'Módulo de pago no inicializado' : 'Payment module not initialized');
|
||
const cardToken = await clipCard.cardToken();
|
||
const reservationData = getReservationData(cardToken.id);
|
||
reservationData.payment_email = paymentEmail.value.trim();
|
||
reservationData.payment_phone = paymentPhone.value.trim();
|
||
reservationData.payment_description = document.getElementById('payment_description')?.value.trim() || '';
|
||
const response = await axios.post(PHP_VARS.routeCheckoutProcesar, reservationData);
|
||
if (response.data.success && response.data.payment_status === 'approved') {
|
||
reservationId = response.data.reservation_id;
|
||
await enviarACotizacion(reservationData, response.data);
|
||
guardarDatosEnLocalStorage();
|
||
passengerDataSaved = false;
|
||
cambiarPaso(5);
|
||
Swal.fire({ icon: 'success', title: isSpanish ? 'Pago aprobado' : 'Payment approved', text: isSpanish ? 'Ahora completa los datos de los pasajeros' : 'Now complete passenger details', timer: 2000, showConfirmButton: false });
|
||
} else {
|
||
throw new Error(response.data.message || (isSpanish ? 'Error en el pago' : 'Payment error'));
|
||
}
|
||
} catch (error) {
|
||
Swal.fire({ icon: 'error', title: isSpanish ? 'Error' : 'Error', text: error.message });
|
||
} finally {
|
||
btnPagar.disabled = false;
|
||
btnPagar.innerHTML = '<i class="fas fa-lock"></i> ' + (isSpanish ? 'Pagar ahora' : 'Pay now');
|
||
}
|
||
});
|
||
}
|
||
|
||
document.getElementById('savePassengersBtn')?.addEventListener('click', async () => {
|
||
const pasajeros = collectPassengerDataFromStep5(adultos, ninios);
|
||
if (!pasajeros) {
|
||
Swal.fire({ icon: 'error', title: isSpanish ? 'Datos incompletos' : 'Incomplete data', text: isSpanish ? 'Por favor complete todos los campos de los pasajeros' : 'Please fill all passenger fields' });
|
||
return;
|
||
}
|
||
Swal.fire({ title: isSpanish ? 'Generando pase de abordar...' : 'Generating boarding pass...', text: isSpanish ? 'Por favor espere, estamos procesando su información.' : 'Please wait, we are processing your information.', allowOutsideClick: false, allowEscapeKey: false, showConfirmButton: false, didOpen: () => { Swal.showLoading(); } });
|
||
try {
|
||
const responsableData = {
|
||
nombres: capitalizeWords(document.getElementById('resp_nombres')?.value.trim() || ''),
|
||
apellidos: capitalizeWords(document.getElementById('resp_apellidos')?.value.trim() || ''),
|
||
email: document.getElementById('resp_email')?.value.trim() || '',
|
||
telefono: itiResponsable ? itiResponsable.getNumber() : document.getElementById('resp_telefono')?.value.trim() || ''
|
||
};
|
||
const payload = {
|
||
reservation_id: reservationId,
|
||
_token: PHP_VARS.csrfToken,
|
||
id_vuelo: PHP_VARS.flightId,
|
||
vuelo_nombre: PHP_VARS.flightName,
|
||
vuelo_descripcion: PHP_VARS.flightShortDesc,
|
||
vuelo_hora: PHP_VARS.flightTime,
|
||
fecha_vuelo: fechaLibre ? null : fechaSeleccionada,
|
||
fecha_libre: fechaLibre,
|
||
adultos, ninios,
|
||
responsable: responsableData,
|
||
pasajeros: pasajeros,
|
||
servicios: serviciosSeleccionados.filter(s => s.cantidad > 0 && !isServicioIncluidoEnPaquete(s.id)).map(s => ({ id: s.id, cantidad: s.cantidad, precio: s.precio, descripcion: s.descripcion })),
|
||
subtotal_vuelo: getSubtotalVuelo(),
|
||
total_extras: getTotalExtras(),
|
||
descuento: descuentoCupon,
|
||
total: getTotalGeneral(),
|
||
payment_email: document.getElementById('payment_email')?.value.trim() || '',
|
||
payment_phone: document.getElementById('payment_phone')?.value.trim() || '',
|
||
payment_description: document.getElementById('payment_description')?.value.trim() || '',
|
||
moneda: PHP_VARS.moneda,
|
||
simbolo: PHP_VARS.simbolo,
|
||
categoria: PHP_VARS.categoria?.nombre || '',
|
||
incluye: PHP_VARS.incluye || [],
|
||
rating: PHP_VARS.ratingGlobal,
|
||
highlighted_text: PHP_VARS.highlightedText || '',
|
||
highlighted_bg_color: PHP_VARS.highlightedBgColor || ''
|
||
};
|
||
const pdfResponse = await axios.post(PHP_VARS.routePaseAbordar, payload, { responseType: 'blob', headers: { 'Content-Type': 'application/json', 'Accept': 'application/pdf' } });
|
||
Swal.close();
|
||
if (pdfResponse.data.type === 'application/json') {
|
||
const errorText = await pdfResponse.data.text();
|
||
const errorJson = JSON.parse(errorText);
|
||
throw new Error(errorJson.message || (isSpanish ? 'Error al generar el pase de abordar' : 'Error generating boarding pass'));
|
||
}
|
||
const blobUrl = window.URL.createObjectURL(pdfResponse.data);
|
||
const link = document.createElement('a');
|
||
link.href = blobUrl;
|
||
link.setAttribute('download', `pase_abordar_${reservationId}_${Date.now()}.pdf`);
|
||
document.body.appendChild(link);
|
||
link.click();
|
||
link.remove();
|
||
window.URL.revokeObjectURL(blobUrl);
|
||
passengerDataSaved = true;
|
||
guardarDatosEnLocalStorage();
|
||
const imprimirContainer = document.getElementById('imprimirContainer');
|
||
if (imprimirContainer) {
|
||
imprimirContainer.innerHTML = `<button class="btn-imprimir" onclick="window.print()"><i class="fas fa-print"></i> ${isSpanish ? 'Imprimir pase de abordar' : 'Print boarding pass'}</button><button class="btn-primary" style="margin-top: 0.5rem; width: 100%;" onclick="window.location.reload()"><i class="fas fa-redo"></i> ${isSpanish ? 'Nueva reserva' : 'New booking'}</button>`;
|
||
}
|
||
Swal.fire({ icon: 'success', title: isSpanish ? '¡Reserva completada!' : 'Booking completed!', text: isSpanish ? 'El pase de abordar se ha descargado automáticamente. También lo recibirás por correo electrónico.' : 'The boarding pass has been downloaded automatically. You will also receive it by email.', timer: 4000, showConfirmButton: true, confirmButtonText: isSpanish ? 'Entendido' : 'Understood' });
|
||
} catch (error) {
|
||
Swal.close();
|
||
console.error('Error al generar pase de abordar:', error);
|
||
let errorMessage = error.message || (isSpanish ? 'No se pudo generar el pase de abordar' : 'Could not generate boarding pass');
|
||
if (error.response?.data instanceof Blob && error.response.data.type === 'application/json') {
|
||
try {
|
||
const text = await error.response.data.text();
|
||
const jsonError = JSON.parse(text);
|
||
errorMessage = jsonError.message || errorMessage;
|
||
} catch (e) { }
|
||
}
|
||
Swal.fire({ icon: 'error', title: isSpanish ? 'Error' : 'Error', text: errorMessage, confirmButtonText: isSpanish ? 'Intentar de nuevo' : 'Try again', showCancelButton: true, cancelButtonText: isSpanish ? 'Cancelar' : 'Cancel' }).then((result) => { if (result.isConfirmed) document.getElementById('savePassengersBtn')?.click(); });
|
||
}
|
||
});
|
||
|
||
document.getElementById('backFromStep5Btn')?.addEventListener('click', () => { cambiarPaso(4); });
|
||
|
||
let flatpickrInstance = null;
|
||
const fechaInput = document.getElementById('fecha_vuelo'), fechaNative = document.getElementById('fecha_vuelo_native');
|
||
if (fechaInput) {
|
||
flatpickrInstance = flatpickr(fechaInput, {
|
||
minDate: "today",
|
||
dateFormat: "Y-m-d",
|
||
altInput: true,
|
||
altFormat: "j F, Y",
|
||
locale: isSpanish ? "es" : "default",
|
||
disableMobile: true,
|
||
onChange: (dates, dateStr) => { fechaSeleccionada = dateStr; window.fechaSeleccionada = dateStr; if (fechaNative) fechaNative.value = dateStr; fechaLibre = false; window.fechaLibre = false; if (document.getElementById('fechaLibreCheckbox')) document.getElementById('fechaLibreCheckbox').checked = false; actualizarFechaResumen(); actualizarResumen(); const fe = document.getElementById('fecha-error'); if (fe) fe.classList.remove('show'); }
|
||
});
|
||
}
|
||
if (fechaNative) {
|
||
fechaNative.addEventListener('change', function (e) { fechaSeleccionada = e.target.value; window.fechaSeleccionada = e.target.value; if (flatpickrInstance) flatpickrInstance.setDate(fechaSeleccionada); fechaLibre = false; window.fechaLibre = false; if (document.getElementById('fechaLibreCheckbox')) document.getElementById('fechaLibreCheckbox').checked = false; actualizarFechaResumen(); actualizarResumen(); const fe = document.getElementById('fecha-error'); if (fe) fe.classList.remove('show'); });
|
||
}
|
||
function detectMobileAndShowInput() { if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) && fechaNative && fechaInput) { fechaInput.style.display = 'none'; fechaNative.style.display = 'block'; } }
|
||
detectMobileAndShowInput();
|
||
const fechaLibreCB = document.getElementById('fechaLibreCheckbox');
|
||
if (fechaLibreCB) {
|
||
fechaLibreCB.addEventListener('change', (e) => { fechaLibre = e.target.checked; window.fechaLibre = fechaLibre; if (fechaLibre) { if (fechaInput) fechaInput.disabled = true; if (fechaNative) fechaNative.disabled = true; if (flatpickrInstance) flatpickrInstance.clear(); fechaSeleccionada = ''; window.fechaSeleccionada = ''; } else { if (fechaInput) fechaInput.disabled = false; if (fechaNative) fechaNative.disabled = false; } actualizarFechaResumen(); actualizarResumen(); const fe = document.getElementById('fecha-error'); if (fe) fe.classList.remove('show'); });
|
||
}
|
||
const adultosInput = document.getElementById('adultos'), niniosInput = document.getElementById('ninios');
|
||
const minusAdult = document.getElementById('minusAdult'), plusAdult = document.getElementById('plusAdult'), minusChild = document.getElementById('minusChild'), plusChild = document.getElementById('plusChild');
|
||
function actualizarContadores() { adultos = parseInt(adultosInput.value) || 1; ninios = parseInt(niniosInput.value) || 0; if (adultos < 1) adultos = 1; if (ninios < 0) ninios = 0; window.adultos = adultos; window.ninios = ninios; actualizarResumen(); actualizarFechaResumen(); }
|
||
if (adultosInput) adultosInput.addEventListener('change', actualizarContadores);
|
||
if (niniosInput) niniosInput.addEventListener('change', actualizarContadores);
|
||
if (minusAdult) minusAdult.addEventListener('click', () => { if (adultos > 1) { adultos--; adultosInput.value = adultos; actualizarContadores(); } });
|
||
if (plusAdult) plusAdult.addEventListener('click', () => { if (adultos < 10) { adultos++; adultosInput.value = adultos; actualizarContadores(); } });
|
||
if (minusChild) minusChild.addEventListener('click', () => { if (ninios > 0) { ninios--; niniosInput.value = ninios; actualizarContadores(); } });
|
||
if (plusChild) plusChild.addEventListener('click', () => { if (ninios < 10) { ninios++; niniosInput.value = ninios; actualizarContadores(); } });
|
||
document.querySelectorAll('.next-step').forEach(btn => { btn.addEventListener('click', () => { const nextStep = parseInt(btn.dataset.next); cambiarPaso(nextStep); }); });
|
||
document.querySelectorAll('.prev-step').forEach(btn => { const prevStep = parseInt(btn.dataset.prev); if (prevStep) cambiarPaso(prevStep); });
|
||
document.querySelectorAll('.step-item').forEach(item => { item.addEventListener('click', () => { const step = parseInt(item.dataset.step); if (step && canGoToStep(step)) cambiarPaso(step); else if (step > 1) Swal.fire({ icon: 'info', title: isSpanish ? 'Completa los pasos anteriores' : 'Complete previous steps', text: isSpanish ? 'Debes llenar la información requerida antes de avanzar.' : 'You must complete the required information before proceeding.', timer: 2000, showConfirmButton: false }); else if (step === 1) cambiarPaso(1); }); });
|
||
document.querySelectorAll('.edit-link').forEach(link => { link.addEventListener('click', (e) => { e.stopPropagation(); const step = parseInt(link.dataset.editStep); if (step) cambiarPaso(step); }); });
|
||
const toggleCupon = document.getElementById('toggleCupon'), cuponDiv = document.getElementById('cuponInput');
|
||
if (toggleCupon && cuponDiv) {
|
||
toggleCupon.addEventListener('click', () => { cuponDiv.style.display = cuponDiv.style.display === 'block' ? 'none' : 'block'; });
|
||
document.getElementById('aplicarCupon')?.addEventListener('click', () => { const codigo = document.getElementById('cuponCode').value.trim(); if (window.aplicarCuponAuto(codigo)) { Swal.fire({ icon: 'success', title: isSpanish ? 'Cupón aplicado' : 'Coupon applied', text: isSpanish ? '10% de descuento' : '10% discount', timer: 2000, showConfirmButton: false }); } else { Swal.fire({ icon: 'error', title: isSpanish ? 'Cupón inválido' : 'Invalid coupon', text: isSpanish ? 'El código no es válido' : 'The code is invalid', timer: 2000, showConfirmButton: false }); } });
|
||
}
|
||
setupNameCapitalization(document.getElementById('resp_nombres'));
|
||
setupNameCapitalization(document.getElementById('resp_apellidos'));
|
||
if (document.querySelector('.main-swiper')) {
|
||
const mainSwiper = new Swiper('.main-swiper', { loop: true, pagination: { el: '.swiper-pagination', clickable: true }, navigation: { nextEl: '.swiper-button-next', prevEl: '.swiper-button-prev' }, autoplay: { delay: 5000, disableOnInteraction: false } });
|
||
if (document.querySelector('.thumbs-swiper')) { new Swiper('.thumbs-swiper', { loop: false, spaceBetween: 10, slidesPerView: 'auto', freeMode: true, watchSlidesProgress: true, thumbs: { swiper: mainSwiper } }); }
|
||
}
|
||
actualizarResumen();
|
||
actualizarFechaResumen();
|
||
cargarDatosDesdeLocalStorage();
|
||
setupAutoSave();
|
||
});
|
||
</script>
|
||
@endpush |