Documentation Index Fetch the complete documentation index at: https://mintlify.com/Jesus-Puertos/h-ayuntamiento/llms.txt
Use this file to discover all available pages before exploring further.
Overview
The tourism onboarding system is a 5-step interactive questionnaire that collects user preferences to generate personalized tourism recommendations. It’s implemented as a modal component that appears after authentication.
Onboarding Flow
Trigger Onboarding
User clicks “Descubre tu ruta” button or visits /turismo?onboarding=1
Check Authentication
System checks if user is authenticated. If not, shows OAuth login options
Question 1: Experiences
User selects types of experiences they enjoy (naturaleza, cultura, gastronomia, etc.)
Question 2: Duration
User chooses trip duration (unas-horas, medio-dia, dia-completo, varios-dias)
Question 3: Difficulty
User selects difficulty level (facil, moderado, dificil, extremo)
Question 4: Travel Group
User indicates who they’re traveling with (solo, pareja, familia, amigos)
Question 5: Specific Interests
User selects specific interests (cascadas, miradores, cuevas, cafe, artesanias, etc.)
Generate Results
System calculates recommendations, unlocks badges, and generates unique ticket
Step Details
Step 1: Experience Types
const EXPERIENCIAS = [
{ value: 'naturaleza' , label: 'Naturaleza y ecoturismo' , icon: '🌲' },
{ value: 'cultura' , label: 'Cultura y tradiciones' , icon: '🎭' },
{ value: 'gastronomia' , label: 'Gastronomía local' , icon: '🍽️' },
{ value: 'aventura' , label: 'Aventura y deporte' , icon: '⛰️' },
{ value: 'relajacion' , label: 'Relajación' , icon: '🧘' },
];
Multiple selection allowed - Users can select multiple experience types to match their interests.
Step 2: Trip Duration
const DURACIONES = [
{ value: 'unas-horas' , label: 'Unas horas (2-4h)' , icon: '⏰' },
{ value: 'medio-dia' , label: 'Medio día (4-6h)' , icon: '🌅' },
{ value: 'dia-completo' , label: 'Día completo (6-8h)' , icon: '☀️' },
{ value: 'varios-dias' , label: 'Varios días' , icon: '🌄' },
];
Single selection - Users choose one duration that fits their schedule.
Step 3: Difficulty Level
const DIFICULTADES = [
{ value: 'facil' , label: 'Fácil (para todos)' , icon: '😊' , color: 'green' },
{ value: 'moderado' , label: 'Moderado' , icon: '🙂' , color: 'blue' },
{ value: 'dificil' , label: 'Difícil' , icon: '😅' , color: 'orange' },
{ value: 'extremo' , label: 'Extremo' , icon: '😤' , color: 'red' },
];
Single selection - Determines the physical challenge level users are comfortable with.
Difficulty levels help filter attractions:
Fácil : Short walks, accessible viewpoints, town tours
Moderado : Hiking trails, cave exploration with guides
Difícil : Long hikes, steep climbs, waterfall descents
Extremo : Rappelling, rock climbing, multi-day expeditions
Step 4: Travel Group
const GRUPOS = [
{ value: 'solo' , label: 'Solo/a' , icon: '🚶' },
{ value: 'pareja' , label: 'En pareja' , icon: '💑' },
{ value: 'familia' , label: 'Familia' , icon: '👨👩👧👦' },
{ value: 'amigos' , label: 'Con amigos' , icon: '🤝' },
];
Single selection - Influences recommendations based on group dynamics:
Familia : Prioritizes safe, accessible locations
Pareja : Suggests romantic viewpoints and experiences
Amigos : Recommends adventure and social activities
Solo : Focuses on safe, well-marked trails
Step 5: Specific Interests
const INTERESES = [
{ value: 'cascadas' , label: 'Cascadas' , icon: '💧' },
{ value: 'miradores' , label: 'Miradores' , icon: '🌄' },
{ value: 'cuevas' , label: 'Cuevas y grutas' , icon: '🕳️' },
{ value: 'pueblos' , label: 'Pueblos nahuas' , icon: '🏘️' },
{ value: 'cafe' , label: 'Café de altura' , icon: '☕' },
{ value: 'artesanias' , label: 'Artesanías' , icon: '🎨' },
{ value: 'senderismo' , label: 'Senderismo' , icon: '🥾' },
{ value: 'fotografia' , label: 'Fotografía' , icon: '📷' },
];
Multiple selection allowed - Fine-tunes recommendations to specific attraction types.
Component Implementation
The onboarding is implemented in src/components/TurismoOnboarding.tsx:
src/components/TurismoOnboarding.tsx
import { useState , useEffect } from 'react' ;
import { supabase , saveUserPreferences } from '@/lib/supabase' ;
import { AuthButtons } from './AuthButtons' ;
export function TurismoOnboarding () {
const [ step , setStep ] = useState ( 0 );
const [ user , setUser ] = useState ( null );
const [ preferences , setPreferences ] = useState ({
experiencia: [],
duracion: '' ,
dificultad: '' ,
grupo: '' ,
intereses: []
});
useEffect (() => {
supabase . auth . getUser (). then (({ data }) => {
setUser ( data . user );
});
}, []);
const handleNext = () => {
if ( step < 4 ) {
setStep ( step + 1 );
} else {
handleSubmit ();
}
};
const handleSubmit = async () => {
if ( ! user ) return ;
// Save preferences to database
await saveUserPreferences ({
user_id: user . id ,
... preferences
});
// Generate recommendations
// Unlock badges
// Create ticket
// Show results
};
if ( ! user ) {
return (
< div className = "text-center" >
< h2 > Inicia sesión para continuar </ h2 >
< AuthButtons />
</ div >
);
}
return (
< div className = "modal" >
{ step === 0 && < ExperiencesStep /> }
{ step === 1 && < DurationStep /> }
{ step === 2 && < DifficultyStep /> }
{ step === 3 && < GroupStep /> }
{ step === 4 && < InterestsStep /> }
</ div >
);
}
Data Storage
Preferences are saved to the user_preferences table:
CREATE TABLE user_preferences (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES auth . users (id) ON DELETE CASCADE ,
experiencia TEXT [] NOT NULL ,
duracion TEXT NOT NULL ,
dificultad TEXT NOT NULL ,
grupo TEXT NOT NULL ,
intereses TEXT [] NOT NULL ,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW ()
);
Triggering Onboarding
Method 1: URL Parameter
Add ?onboarding=1 to the tourism page URL:
https://yourdomain.com/turismo?onboarding=1
< button
onClick = { () => {
window . dispatchEvent ( new CustomEvent ( 'open-turismo-onboarding' ));
} }
>
Descubre tu ruta
</ button >
Method 3: Automatic for New Users
Check if user has completed onboarding:
const { data : preferences } = await getUserPreferences ( user . id );
if ( ! preferences ) {
// Show onboarding modal
}
Best Practices
Progressive Disclosure : Show one question at a time to avoid overwhelming users
Visual Feedback : Highlight selected options with color and icons
Skip Option : Allow users to skip optional questions (e.g., specific interests)
Always check authentication before showing the questionnaire. Unauthenticated users cannot save preferences.
Analytics
Track onboarding completion:
// Track step completion
analytics . track ( 'onboarding_step_completed' , {
step: stepNumber ,
user_id: user . id
});
// Track full completion
analytics . track ( 'onboarding_completed' , {
user_id: user . id ,
preferences: preferences
});
Next Steps
Recommendations Learn how recommendations are generated from preferences
Badges See which badges are unlocked during onboarding
Authentication Understand the OAuth authentication flow
Component Code View the full TurismoOnboarding component