-
Parada cercana
-
{{ paradaCercana?.name }}
+
+ directions_bus
-
- {{ (distanciaMetros && distanciaMetros < 1000) ? Math.round(distanciaMetros) + 'm' : (distanciaMetros ? (distanciaMetros / 1000).toFixed(1) + 'km' : '') }}
+
+
+ Tiempo de llegada
+ {{ paradaCercana?.name }}
-
@@ -1531,6 +1552,76 @@ html.light-theme .uber-search-trigger-compact {
max-width: none;
}
+.best-stop-banner-compact {
+ flex: 1;
+ background: var(--header-bg);
+ backdrop-filter: blur(20px);
+ -webkit-backdrop-filter: blur(20px);
+ height: 40px; /* Más compacto (de 44px a 40px) */
+ border-radius: 10px;
+ display: flex;
+ align-items: center;
+ padding: 0 10px;
+ box-shadow: 0 4px 12px rgba(0,0,0,0.15);
+ border: 1px solid var(--border-color);
+ max-width: none;
+ pointer-events: auto;
+}
+
+.banner-icon-bg {
+ background: #EAB308; /* yellow-500 */
+ width: 24px;
+ height: 24px;
+ border-radius: 6px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+}
+
+.trigger-text-compact {
+ color: var(--text-primary);
+ font-size: 0.95rem;
+ font-weight: 700;
+ letter-spacing: -0.01em;
+}
+
+.eta-badge {
+ background: rgba(234, 179, 8, 0.1); /* yellow-500 with opacity */
+ color: #EAB308;
+ padding: 2px 8px;
+ border-radius: 6px;
+ display: flex;
+ align-items: baseline;
+ gap: 2px;
+ font-weight: 800;
+ margin-left: 8px;
+ border: 1px solid rgba(234, 179, 8, 0.2);
+}
+
+.eta-value {
+ font-size: 1.1rem;
+ line-height: 1;
+}
+
+.eta-unit {
+ font-size: 0.7rem;
+ text-transform: uppercase;
+}
+
+.eta-loader {
+ width: 14px;
+ height: 14px;
+ border: 2px solid #EAB308;
+ border-top-color: transparent;
+ border-radius: 50%;
+ animation: spin 0.8s linear infinite;
+}
+
+@keyframes spin {
+ to { transform: rotate(360deg); }
+}
+
.uber-search-panel {
position: fixed;
top: 70px; /* Debajo del header superior */
diff --git a/frontend/src/views/SchedulesView.vue b/frontend/src/views/SchedulesView.vue
index e85d14c..ddb74bb 100644
--- a/frontend/src/views/SchedulesView.vue
+++ b/frontend/src/views/SchedulesView.vue
@@ -56,7 +56,7 @@ function getScheduleDay(schedule: any): 'today' | 'tomorrow' | 'other' {
// Comparar con el tipo del horario
// Nota: Si el horario es 'todos', cuenta para hoy y mañana (pero priorizamos hoy si pides hoy)
- const type = schedule.schedule_type || 'todos'
+ const type = (schedule.schedule_type as string) || 'todos'
const isToday = type === todayType || type === 'todos'
const isTomorrow = type === tomorrowType || type === 'todos'
@@ -81,27 +81,35 @@ function getDayLabel(schedule: any): string {
// ── Filtrado de horarios
const filteredSchedules = computed(() => {
const now = new Date()
+ const tomorrow = new Date(now)
+ tomorrow.setDate(now.getDate() + 1)
+
const hhmmAhora = now.getHours() * 100 + now.getMinutes()
return scheduleStore.schedules.filter(s => {
- const d = getScheduleDay(s)
- const [hStr, mStr] = (s.departure_time || '00:00').split(':')
- const h = parseInt(hStr || '0')
- const m = parseInt(mStr || '0')
- const hhmmSched = h * 100 + m
- const isPassed = hhmmSched < hhmmAhora - 2 // margen de 2 min
+ const type = (s.schedule_type as string) || 'todos'
+ const todayType = (now.getDay() === 0 || now.getDay() === 6) ? 'weekend' : 'weekday'
+ const tomorrowType = (tomorrow.getDay() === 0 || tomorrow.getDay() === 6) ? 'weekend' : 'weekday'
+
+ const isActuallyToday = type === todayType || type === 'todos'
+ const isActuallyTomorrow = type === tomorrowType || type === 'todos'
// Filtro Hoy: Es hoy Y no ha pasado (o es de los que dice salir en este rango)
if (dayFilter.value === 'today') {
- return d === 'today' && !isPassed
+ const [hStr, mStr] = (s.departure_time || '00:00').split(':')
+ const h = parseInt(hStr || '0')
+ const m = parseInt(mStr || '0')
+ const hhmmSched = h * 100 + m
+ const isPassed = hhmmSched < hhmmAhora - 2 // margen de 2 min
+ return isActuallyToday && !isPassed
}
- // Filtro Mañana: Es mañana
+ // Filtro Mañana: Es mañana (sin importar si pasó la hora hoy)
if (dayFilter.value === 'tomorrow') {
- return d === 'tomorrow'
+ return isActuallyTomorrow
}
- // Filtro Todos: Mostrar todo sin importar si pasó o es otro día
+ // Filtro Todos: Mostrar todo
return true
})
})
@@ -281,7 +289,7 @@ onUnmounted(() => {
v-for="(schedule, i) in filteredSchedules"
:key="schedule.id"
class="schedule-card"
- :class="`schedule-card--${getBusStatus(schedule.departure_time)}`"
+ :class="dayFilter === 'today' ? `schedule-card--${getBusStatus(schedule.departure_time)}` : 'schedule-card--upcoming'"
>
@@ -295,8 +303,8 @@ onUnmounted(() => {
-
- {{ getDayLabel(schedule) }}
+
+ {{ dayFilter === 'tomorrow' ? 'Mañana' : (dayFilter === 'all' ? getDayLabel(schedule) : getDayLabel(schedule)) }}
SALIENDO
@@ -308,11 +316,11 @@ onUnmounted(() => {
-
+
- {{ getBusStatus(schedule.departure_time) === 'departing' ? 'directions_run' :
- getBusStatus(schedule.departure_time) === 'ontime' ? 'check_circle' :
- getBusStatus(schedule.departure_time) === 'passed' ? 'history' : 'access_time' }}
+ {{ (dayFilter === 'today' && getBusStatus(schedule.departure_time) === 'departing') ? 'directions_run' :
+ (dayFilter === 'today' && getBusStatus(schedule.departure_time) === 'ontime') ? 'check_circle' :
+ (dayFilter === 'today' && getBusStatus(schedule.departure_time) === 'passed') ? 'history' : 'access_time' }}
diff --git a/frontend/src/views/TransporteLayout.vue b/frontend/src/views/TransporteLayout.vue
index f15faa7..58ea4b2 100644
--- a/frontend/src/views/TransporteLayout.vue
+++ b/frontend/src/views/TransporteLayout.vue
@@ -1,10 +1,15 @@
-
-
-
-
- arrow_back
+
+
+
+
+ arrow_back
-
+
{{ shuttle?.company_name || 'Detalle del viaje' }}
@@ -90,32 +90,32 @@ const volver = () => {
class="w-full h-full object-cover"
@error="(e) => (e.target as HTMLImageElement).src = getImageUrl(null, 'shuttle')"
/>
-
+
directions_bus
{{ shuttle.vehicle_type }}
-
+
- Origen
-
+ Origen
+
{{ shuttle.origin }}
-
{{ shuttle.estimated_duration }}
+
{{ shuttle.estimated_duration }}
- east
+ east
- Destino
-
+ Destino
+
{{ shuttle.destination }}
@@ -123,28 +123,28 @@ const volver = () => {
-
+
-
{{ shuttle.company_name }}
-
{{ shuttle.description }}
+
{{ shuttle.company_name }}
+
{{ shuttle.description }}
-
+
- schedule Hora de salida
-
+ schedule Hora de salida
+
{{ shuttle.departure_times }}
- swap_horiz Tipo de viaje
-
+ swap_horiz Tipo de viaje
+
{{ shuttle.trip_type.replace('_', ' ') }}
- g_translate Idiomas
-
+ g_translate Idiomas
+
Español · English
@@ -167,10 +167,10 @@ const volver = () => {
-
+
-
Reserva e Información
-
Contacta directamente al operador para confirmar disponibilidad.
+
Reserva e Información
+
Contacta directamente al operador para confirmar disponibilidad.