Profile Overview
Your tourist profile (dashboard-profile.html) is your digital identity on Kin Conecta. A complete profile helps guides understand your travel style and provides you with more personalized recommendations.
Profile Data Structure
The platform stores comprehensive profile information:
const DEFAULT_TOURIST_PROFILE = {
// Basic Information
name: "Sofía Ramírez" ,
location: "Ciudad de México" ,
memberSince: "Ene 2023" ,
badge: "Viajera frecuente" ,
bio: "Amante de la naturaleza y la comida callejera." ,
// Travel Preferences
interests: [ "Aventura" , "Gastronomía" , "Playa" , "Fotografía" ],
travelStyle: "Exploración cultural con itinerarios flexibles." ,
tripType: "Escapadas de fin de semana y viajes urbanos." ,
languages: [ "Español" , "Inglés" ],
// Activity Preferences
paceAndCompany: "Ritmo moderado en compañía de grupos pequeños." ,
activityLevel: "Moderado" ,
groupPreference: "Pareja o grupos de 3 a 6 personas." ,
// Special Preferences
dietaryPreferences: "Sin restricciones, con prioridad en comida local." ,
planningLevel: "Intermedio" ,
amenities: "Hospedajes céntricos, conectividad y descanso." ,
transport: "Caminata y transporte público." ,
photoPreference: "Fotos espontáneas y retratos naturales." ,
accessibility: "Sin necesidades especiales actualmente." ,
additionalNotes: "Disponible para ajustar actividades según clima e intereses." ,
// Media
avatar: "profile-image-url" ,
cover: "cover-image-url"
};
Profile data is stored both locally (localStorage) and synced with the backend API for cross-device access.
Profile Sections
The profile header displays your identity and membership status:
Cover Photo
Full-width background image that personalizes your profile if ( dom . cover ) {
dom . cover . style . backgroundImage = `url(' ${ state . profile . cover } ')` ;
}
Profile Avatar
Circular profile photo overlaid on the cover image if ( dom . avatar ) {
dom . avatar . style . backgroundImage = `url(' ${ state . profile . avatar } ')` ;
}
Name and Location
Your display name and city/region
Member Badge
Status badge (e.g., “Viajera frecuente”) showing your activity level
Member Since
Join date displayed as “Miembro desde [Date]“
Bio Section
Share your travel personality:
< div class = "profile-section" >
< h2 > Acerca de mí </ h2 >
< p id = "profileBio" > Amante de la naturaleza y la comida callejera. </ p >
</ div >
Interests
Hashtag-style interest chips:
if ( dom . interests ) {
dom . interests . innerHTML = '' ;
state . profile . interests . forEach (( tag ) => {
const item = document . createElement ( 'span' );
item . textContent = `# ${ tag } ` ;
dom . interests . appendChild ( item );
});
}
Add 3-8 interests for optimal guide matching. Popular interests include: Aventura, Gastronomía, Historia, Fotografía, Playa, Montaña, Arte, Cultura.
Travel Style
Describe how you like to travel:
// Examples of travel styles
"Exploración cultural con itinerarios flexibles."
"Viajes estructurados con actividades planificadas."
"Aventuras espontáneas y descubrimiento libre."
"Experiencias inmersivas con comunidades locales."
Activity Preferences
Choose your preferred intensity:
Ligero : Paseos relajados, visitas culturales sin prisa
Moderado : Caminatas de duración media, actividades variadas
Intenso : Senderismo desafiante, deportes de aventura
activityLevel : "Moderado"
Specify your ideal group composition: groupPreference : "Pareja o grupos de 3 a 6 personas."
Common preferences:
Solo traveler
Pareja
Familia (2-4 personas)
Grupos pequeños (3-6)
Grupos grandes (7+)
Describe your travel rhythm: paceAndCompany : "Ritmo moderado en compañía de grupos pequeños."
Special Preferences
Dietary Food restrictions and preferences dietaryPreferences : "Sin restricciones, con prioridad en comida local."
Accessibility Mobility or accessibility requirements accessibility : "Sin necesidades especiales actualmente."
Photography Photo preferences during tours photoPreference : "Fotos espontáneas y retratos naturales."
Transport Preferred modes of transportation transport : "Caminata y transporte público."
Trip History
Your past experiences are displayed in a visual gallery:
function renderHistory () {
if ( ! dom . history ) return ;
dom . history . innerHTML = '' ;
state . history . forEach (( item ) => {
const row = document . createElement ( 'article' );
row . className = 'trip-history__item' ;
row . innerHTML = `
<div class="trip-history__image" style="background-image:url(' ${ item . image } ');"></div>
<div class="trip-history__body">
<h4 class="trip-history__title"> ${ item . title } </h4>
<p class="trip-history__meta">
<span class="material-symbols-outlined">calendar_month</span>
${ item . dateLabel }
</p>
</div>
` ;
dom . history . appendChild ( row );
});
}
Editing Profile
Access Edit Mode
Click “Editar perfil” in the navigation to access the edit page:
dom . btnEditProfileNav ?. addEventListener ( 'click' , () => {
window . location . href = './profileTouristEdit.html' ;
});
Edit Avatar
Click the camera icon on your profile photo:
dom . btnAvatarEdit ?. addEventListener ( 'click' , () => openMediaModal ( 'avatar' ));
Open Media Modal
A modal dialog appears with file upload interface
Select Image
Choose an image file (JPG, PNG, WebP) if ( ! file . type . startsWith ( 'image/' )) {
dom . mediaStatus . textContent = 'Selecciona un archivo de imagen válido.' ;
return ;
}
Preview
The image preview updates in real-time: state . media . objectUrl = window . URL . createObjectURL ( file );
dom . mediaPreview . src = state . media . objectUrl ;
Save
Click “Guardar” to upload and apply the new avatar
Edit Cover Photo
Click the camera icon on your cover image:
dom . btnCardEdit ?. addEventListener ( 'click' , () => {
openMediaModal ( 'cover' );
});
The process is identical to avatar editing but applies to the cover photo.
async function handleMediaSave () {
if ( ! state . media . file ) {
dom . mediaStatus . textContent = 'Selecciona una imagen antes de guardar.' ;
return ;
}
dom . mediaSave . setAttribute ( 'disabled' , 'disabled' );
dom . mediaStatus . textContent = 'Guardando imagen...' ;
try {
const encodedImage = await fileToDataUrl ( state . media . file );
if ( state . media . mode === 'cover' ) {
state . profile . cover = encodedImage ;
} else {
state . profile . avatar = encodedImage ;
}
persistProfile ( state . profile );
renderProfile ();
// Sync with API
await syncMediaWithApi ( state . media . mode , state . media . file );
closeMediaModal ();
} catch ( error ) {
dom . mediaStatus . textContent = error . message ;
} finally {
dom . mediaSave . removeAttribute ( 'disabled' );
}
}
Local Storage
Profile data is cached locally for offline access and faster loading:
const TOURIST_PROFILE_STORAGE_KEY = 'kc_tourist_profile_v1' ;
function readProfileFromStorage () {
try {
const raw = window . localStorage . getItem ( TOURIST_PROFILE_STORAGE_KEY );
if ( ! raw ) return cloneDefaultProfile ();
return normalizeProfile ( JSON . parse ( raw ));
} catch ( error ) {
console . warn ( 'No se pudo leer el perfil local de turista.' , error );
return cloneDefaultProfile ();
}
}
function persistProfile ( profile ) {
try {
window . localStorage . setItem (
TOURIST_PROFILE_STORAGE_KEY ,
JSON . stringify ( profile )
);
} catch ( error ) {
console . warn ( 'No se pudo guardar el perfil local de turista.' , error );
}
}
Local storage is limited to ~5-10MB per domain. Large base64-encoded images may reach this limit.
API Endpoints
Get Profile
Fetch your profile data:
const profile = {
getMe : () => api . get ( '/tourist/profile/me' )
};
// Usage
const response = await window . KCTouristApi . profile . getMe ();
const data = response ?. data || {};
Update Profile
Save profile changes:
const profile = {
updateMe : ( payload ) => api . put ( '/tourist/profile/me' , payload )
};
// Example payload
const updates = {
name: "Sofía Ramírez" ,
bio: "Amante de la naturaleza y la comida callejera." ,
interests: [ "Aventura" , "Gastronomía" , "Playa" ],
activityLevel: "Moderado"
};
await window . KCTouristApi . profile . updateMe ( updates );
Update Security
Change password or security settings:
const profile = {
updateSecurity : ( payload ) => api . put ( '/tourist/profile/me/security' , payload )
};
// Example
await profile . updateSecurity ({
currentPassword: 'old-password' ,
newPassword: 'new-password'
});
Upload Avatar
Upload a new profile photo:
const profile = {
uploadAvatar : ( formData ) => api . post ( '/tourist/profile/me/avatar' , formData , {
headers: {} // Let browser set Content-Type for FormData
})
};
// Usage
const formData = new FormData ();
formData . append ( 'avatar' , file );
await window . KCTouristApi . profile . uploadAvatar ( formData );
Upload Cover
Upload a new cover image:
const profile = {
uploadCover : ( formData ) => api . post ( '/tourist/profile/me/cover' , formData , {
headers: {}
})
};
// Usage
const formData = new FormData ();
formData . append ( 'cover' , file );
await window . KCTouristApi . profile . uploadCover ( formData );
Data Normalization
The platform ensures data consistency:
function normalizeProfile ( rawProfile ) {
const base = cloneDefaultProfile ();
if ( ! rawProfile || typeof rawProfile !== 'object' ) return base ;
base . name = normalizeText ( rawProfile . name , base . name );
base . location = normalizeText ( rawProfile . location , base . location );
base . bio = normalizeText ( rawProfile . bio , base . bio );
base . interests = normalizeList ( rawProfile . interests , base . interests );
base . languages = normalizeList ( rawProfile . languages , base . languages );
// ... normalize all fields
return base ;
}
function normalizeText ( rawValue , fallbackValue ) {
const value = String ( rawValue || '' ). trim ();
return value || fallbackValue ;
}
function normalizeList ( rawValue , fallbackList ) {
if ( Array . isArray ( rawValue )) {
const list = rawValue
. map ( item => String ( item || '' ). trim ())
. filter ( Boolean );
return list . length ? list : [ ... fallbackList ];
}
if ( typeof rawValue === 'string' ) {
const list = rawValue
. split ( / [ \n, ] + / g )
. map ( item => item . trim ())
. filter ( Boolean );
return list . length ? list : [ ... fallbackList ];
}
return [ ... fallbackList ];
}
API Mapping
Backend data is mapped to the frontend profile structure:
function mapApiProfile ( raw ) {
return {
name: raw . name || raw . fullName || '' ,
location: raw . location || '' ,
memberSince: raw . memberSince || '' ,
badge: raw . badge || '' ,
bio: raw . bio || raw . about || '' ,
interests: raw . interests || [],
travelStyle: raw . travelStyle || '' ,
tripType: raw . tripType || '' ,
languages: raw . languages || [],
paceAndCompany: raw . paceAndCompany || '' ,
activityLevel: raw . activityLevel || '' ,
groupPreference: raw . groupPreference || '' ,
dietaryPreferences: raw . dietaryPreferences || '' ,
planningLevel: raw . planningLevel || '' ,
amenities: raw . amenities || '' ,
transport: raw . transport || '' ,
photoPreference: raw . photoPreference || '' ,
accessibility: raw . accessibility || '' ,
additionalNotes: raw . additionalNotes || '' ,
avatar: raw . avatarUrl || raw . avatarImage || '' ,
cover: raw . coverUrl || raw . coverImage || ''
};
}
Profile Initialization
The profile page loads data from multiple sources:
async function init () {
bind ();
setupActions ();
renderLoadingState ();
// 1. Load from localStorage first (fast)
state . profile = readProfileFromStorage ();
// 2. Fetch from API (authoritative)
await hydrateFromApi ();
// 3. Render final state
renderProfile ();
renderHistory ();
}
Best Practices
Complete Profile Fill in all sections for better guide recommendations
High-Quality Photos Use clear, recent photos for avatar and cover
Specific Interests Be specific about your travel preferences
Keep Updated Update your profile as your interests evolve
Accessibility
The profile interface includes accessibility features:
// Loading state announcements
< div class = "guide-loading" role = "status" aria-live = "polite" aria-busy = "true" >
< span class = "guide-loading__spinner" aria-hidden = "true" ></ span >
< span > Cargando perfil... </ span >
</ div >
// Media modal labels
< button aria-label = "Editar foto de perfil" >
< span class = "material-symbols-outlined" > photo_camera </ span >
</ button >
Screen readers announce loading states and modal titles automatically.
Next Steps
Finding Guides Discover guides that match your profile
Booking Tours Start booking your first experience
API Reference Explore profile management endpoints