Skip to main content

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

Header Card

The profile header displays your identity and membership status:
1

Cover Photo

Full-width background image that personalizes your profile
if (dom.cover) {
  dom.cover.style.backgroundImage = `url('${state.profile.cover}')`;
}
2

Profile Avatar

Circular profile photo overlaid on the cover image
if (dom.avatar) {
  dom.avatar.style.backgroundImage = `url('${state.profile.avatar}')`;
}
3

Name and Location

Your display name and city/region
4

Member Badge

Status badge (e.g., “Viajera frecuente”) showing your activity level
5

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'));
1

Open Media Modal

A modal dialog appears with file upload interface
2

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;
}
3

Preview

The image preview updates in real-time:
state.media.objectUrl = window.URL.createObjectURL(file);
dom.mediaPreview.src = state.media.objectUrl;
4

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.

Media Upload Implementation

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

Build docs developers (and LLMs) love