Skip to main content
The profile setup system in FitAiid allows users to manage their personal information, view their fitness statistics, and customize their account settings.

Profile Overview

The profile page (perfil.html) provides a comprehensive dashboard for users to manage their account.

Personal Data

Age, height, weight, and contact information

Fitness Profile

Goals, experience level, and training preferences

Statistics

Workouts completed, calories burned, active time

Profile Components

Hero Section

The profile hero displays key user information with interactive elements:
frontend/pages/perfil.html
<section class="profile-hero">
  <div class="avatar-container">
    <div class="avatar" onclick="openAvatarModal()">
      <img id="userAvatar" src="default-avatar.svg" alt="Avatar">
    </div>
    <div class="avatar-edit" onclick="openAvatarModal()">
      <i class="fas fa-camera"></i>
    </div>
  </div>
  
  <div class="profile-info">
    <h1>HOLA, <span id="userName">USUARIO</span></h1>
    <p id="userEmail">[email protected]</p>
    
    <div class="profile-badges">
      <span class="badge badge-level">
        <i class="fas fa-star"></i> <span id="userLevel">Principiante</span>
      </span>
      <span class="badge badge-goal">
        <i class="fas fa-bullseye"></i> <span id="userGoal">Perder Peso</span>
      </span>
      <span class="badge badge-streak">
        <i class="fas fa-fire"></i> <span id="streakBadge">7 días</span>
      </span>
    </div>
  </div>
</section>
The profile hero includes real-time statistics like workout count, calories burned, and active hours.

Loading Profile Data

Profile data is loaded from localStorage after successful login or questionnaire completion:
function loadUserData() {
  const userString = localStorage.getItem('user');
  const fitnessProfile = localStorage.getItem('fitnessProfile');
  
  // Load basic user info
  if (userString) {
    const user = JSON.parse(userString);
    document.getElementById('userName').textContent = 
      (user.firstName || 'USUARIO').toUpperCase();
    document.getElementById('userEmail').textContent = 
      user.email || '[email protected]';
  }
  
  // Load fitness profile from questionnaire
  if (fitnessProfile) {
    const profile = JSON.parse(fitnessProfile);
    
    // Update age
    if (profile.age) {
      document.getElementById('userAge').textContent = profile.age + ' años';
    }
    
    // Update height
    if (profile.height) {
      document.getElementById('userHeight').textContent = profile.height + ' cm';
    }
    
    // Update weight
    if (profile.weight) {
      document.getElementById('userWeight').textContent = profile.weight + ' kg';
    }
    
    // Update goal
    if (profile.mainGoal) {
      const goalText = profile.mainGoal.charAt(0).toUpperCase() + 
                       profile.mainGoal.slice(1);
      document.getElementById('userGoal').textContent = goalText;
      document.getElementById('userObjective').textContent = goalText;
    }
    
    // Update fitness level
    if (profile.fitnessLevel) {
      const levelText = profile.fitnessLevel.charAt(0).toUpperCase() + 
                        profile.fitnessLevel.slice(1);
      document.getElementById('userLevel').textContent = levelText;
      document.getElementById('userFitnessLevel').textContent = levelText;
    }
  }
}

// Initialize on page load
loadUserData();

Avatar Customization

Users can customize their profile picture by uploading an image or selecting from predefined avatars.
1

Open Avatar Modal

User clicks on their current avatar or the edit button:
function openAvatarModal() {
  const avatars = [
    '😎', '🤩', '😁', '🥳', '🤓', '😇', '🥰', '😊',
    'https://api.dicebear.com/7.x/avataaars/svg?seed=Felix',
    'https://api.dicebear.com/7.x/avataaars/svg?seed=Aneka',
    'https://api.dicebear.com/7.x/avataaars/svg?seed=Coco',
    'https://api.dicebear.com/7.x/avataaars/svg?seed=Milo'
  ];
  
  const grid = document.getElementById('avatarGrid');
  grid.innerHTML = '';
  
  avatars.forEach(avatar => {
    const div = document.createElement('div');
    div.className = 'avatar-option';
    div.onclick = () => selectAvatar(div, avatar);
    
    if (avatar.startsWith('http')) {
      const img = document.createElement('img');
      img.src = avatar;
      img.style.width = '100%';
      img.style.borderRadius = '50%';
      div.appendChild(img);
    } else {
      div.textContent = avatar;
    }
    
    grid.appendChild(div);
  });
  
  document.getElementById('avatarModal').classList.add('active');
}
2

Upload or Select Avatar

Users can either upload their own image or select a predefined avatar:
function handleImageUpload(e) {
  const file = e.target.files[0];
  if (!file) return;
  
  const reader = new FileReader();
  reader.onload = (ev) => {
    uploadedImageData = ev.target.result;
    document.getElementById('previewImg').src = uploadedImageData;
    document.getElementById('uploadPreview').style.display = 'block';
    
    // Deselect predefined avatars
    document.querySelectorAll('.avatar-option').forEach(o => 
      o.classList.remove('selected')
    );
    selectedAvatar = null;
  };
  reader.readAsDataURL(file);
}
3

Save Avatar

The selected avatar is saved and displayed:
function saveAvatar() {
  let avatarToSave = null;
  
  // Priority: uploaded image > selected avatar
  if (uploadedImageData) {
    avatarToSave = uploadedImageData;
  } else if (selectedAvatar) {
    if (selectedAvatar.startsWith('http')) {
      avatarToSave = selectedAvatar;
    } else {
      // Convert emoji to image
      const canvas = document.createElement('canvas');
      canvas.width = 100;
      canvas.height = 100;
      const ctx = canvas.getContext('2d');
      ctx.fillStyle = '#1a1a1a';
      ctx.fillRect(0, 0, 100, 100);
      ctx.font = '60px Arial';
      ctx.textAlign = 'center';
      ctx.textBaseline = 'middle';
      ctx.fillText(selectedAvatar, 50, 55);
      avatarToSave = canvas.toDataURL();
    }
  }
  
  if (avatarToSave) {
    document.getElementById('userAvatar').src = avatarToSave;
    closeAvatarModal();
  } else {
    alert('Selecciona un avatar o sube una foto.');
  }
}

Updating Profile Information

Users can update their profile by re-taking the questionnaire or making direct updates.
backend/src/controllers/authController.js
const updateProfile = async (req, res) => {
  const userId = req.user?.id;
  
  if (!userId) {
    throw new AppError('ID de usuario requerido', 400);
  }
  
  // Allowed fields for update
  const allowedUpdates = [
    'firstName', 
    'lastName', 
    'phone', 
    'dateOfBirth',
    'gender',
    'avatar',
    'address'
  ];
  
  // Filter only allowed fields
  const updates = {};
  Object.keys(req.body).forEach(key => {
    if (allowedUpdates.includes(key)) {
      updates[key] = req.body[key];
    }
  });
  
  if (Object.keys(updates).length === 0) {
    throw new AppError('No hay campos para actualizar', 400);
  }
  
  // Update user
  const user = await User.findByIdAndUpdate(
    userId,
    updates,
    { 
      new: true,
      runValidators: true
    }
  );
  
  if (!user) {
    throw new AppError('Usuario no encontrado', 404);
  }
  
  const publicProfile = user.getPublicProfile();
  
  res.status(200).json({
    success: true,
    message: 'Perfil actualizado exitosamente',
    user: publicProfile
  });
};
Certain sensitive fields like email and password require separate endpoints with additional verification.

Profile Statistics Dashboard

The profile displays real-time statistics cards:

Workouts

Total completed workouts and weekly progress

Calories

Total calories burned across all sessions

Active Time

Cumulative hours spent training
Profile Stats Display
<div class="profile-stats">
  <div class="profile-stat">
    <div class="profile-stat-value" id="totalWorkouts">24</div>
    <div class="profile-stat-label">Entrenamientos</div>
  </div>
  <div class="profile-stat">
    <div class="profile-stat-value" id="totalCalories">8,420</div>
    <div class="profile-stat-label">Calorías Quemadas</div>
  </div>
  <div class="profile-stat">
    <div class="profile-stat-value" id="totalHours">18h</div>
    <div class="profile-stat-label">Tiempo Activo</div>
  </div>
</div>

Fitness Calculators

The profile page includes three built-in calculators:

BMI Calculator

Calculates Body Mass Index based on height and weight:
function calcularIMC() {
  const peso = parseFloat(document.getElementById('pesoIMC').value);
  const altura = parseFloat(document.getElementById('alturaIMC').value);
  
  if (!peso || !altura || peso <= 0 || altura <= 0) {
    alert('Por favor ingresa peso y altura válidos.');
    return;
  }
  
  const alturaM = altura / 100;
  const imc = peso / (alturaM * alturaM);
  
  let clasificacion = '';
  if (imc < 18.5) clasificacion = 'Bajo peso';
  else if (imc < 24.9) clasificacion = 'Peso normal';
  else if (imc < 29.9) clasificacion = 'Sobrepeso';
  else clasificacion = 'Obesidad';
  
  document.getElementById('valorIMC').textContent = imc.toFixed(1);
  document.getElementById('clasificacionIMC').textContent = clasificacion;
}

Water Consumption Calculator

Calculates daily water intake needs:
function calcularAgua() {
  const peso = parseFloat(document.getElementById('pesoAgua').value);
  const edad = parseInt(document.getElementById('edadAgua').value);
  
  if (!peso || !edad) {
    alert("Por favor ingresa todos los datos");
    return;
  }
  
  // Formula: adults need ~35ml per kg of weight
  let litros = peso * 0.035;
  
  // Age adjustment
  if (edad < 14) litros *= 0.8;      // children
  else if (edad > 65) litros *= 0.9; // elderly
  
  document.getElementById('valorAgua').textContent = litros.toFixed(2) + " litros";
}

Macronutrient Calculator

Calculates daily calorie and macronutrient needs:
function calcularMacronutrientes() {
  const edad = parseInt(document.getElementById('edad').value);
  const altura = parseFloat(document.getElementById('altura').value);
  const peso = parseFloat(document.getElementById('peso').value);
  const actividad = document.getElementById('actividad').value;
  const meta = document.getElementById('meta').value;
  const genero = document.querySelector('input[name="genero"]:checked');
  
  // Harris-Benedict Formula
  let bmr = 0;
  if (genero.value === "masculino") {
    bmr = 88.362 + (13.397 * peso) + (4.799 * altura) - (5.677 * edad);
  } else {
    bmr = 447.593 + (9.247 * peso) + (3.098 * altura) - (4.330 * edad);
  }
  
  // Activity factor
  let factorActividad = 1.2;
  if (actividad === 'ligero') factorActividad = 1.375;
  if (actividad === 'moderado') factorActividad = 1.55;
  if (actividad === 'intenso') factorActividad = 1.725;
  
  let calorias = bmr * factorActividad;
  
  // Goal adjustment
  if (meta === 'bajar') calorias -= 400;
  if (meta === 'subir') calorias += 400;
  
  document.getElementById('valorKcal').textContent = `${Math.round(calorias)} kcal`;
}

Activity Feed

The profile displays recent user activities:
<div class="activity-list">
  <div class="activity-item">
    <div class="activity-icon">🏋️</div>
    <div class="activity-info">
      <div class="activity-title">Circuito Funcional</div>
      <div class="activity-date">Hoy, 10:30 AM</div>
    </div>
    <span class="activity-badge">Completado</span>
  </div>
  <div class="activity-item">
    <div class="activity-icon">🥗</div>
    <div class="activity-info">
      <div class="activity-title">Plan Nutricional Revisado</div>
      <div class="activity-date">Ayer, 8:00 PM</div>
    </div>
    <span class="activity-badge">Completado</span>
  </div>
</div>

Achievements Display

Users can view their unlocked achievements:
<div class="achievements-grid">
  <div class="achievement">
    <div class="achievement-icon">🔥</div>
    <div class="achievement-name">7 Días Racha</div>
  </div>
  <div class="achievement">
    <div class="achievement-icon">💪</div>
    <div class="achievement-name">Primer Entreno</div>
  </div>
  <div class="achievement">
    <div class="achievement-icon">🎯</div>
    <div class="achievement-name">Meta Cumplida</div>
  </div>
  <div class="achievement locked">
    <div class="achievement-icon">🏆</div>
    <div class="achievement-name">30 Días</div>
  </div>
</div>

Streak Tracking

The profile prominently displays the user’s workout streak:
<div class="streak-card">
  <div class="streak-icon">🔥</div>
  <div class="streak-value" id="streakCount">7</div>
  <div class="streak-label">Días consecutivos</div>
  <div class="streak-days">
    <div class="streak-day completed">L</div>
    <div class="streak-day completed">M</div>
    <div class="streak-day completed">X</div>
    <div class="streak-day completed">J</div>
    <div class="streak-day completed">V</div>
    <div class="streak-day completed">S</div>
    <div class="streak-day today">D</div>
  </div>
</div>
The streak feature encourages user engagement by rewarding consistent workout habits.

API Endpoints

EndpointMethodDescriptionAuth Required
/api/auth/profileGETGet user profile dataYes
/api/auth/profilePUTUpdate profile informationYes
/api/profile/photoPOSTUpload profile photoYes

Data Persistence

Profile data is stored in multiple locations:
1

MongoDB Database

Permanent storage of user profile in the User model with fitness profile subdocument
2

LocalStorage

Client-side caching for quick access and offline viewing
3

JWT Token

User ID encoded in authentication token for secure API requests

Build docs developers (and LLMs) love