Skip to main content

Meal Planning

JCV Fitness provides structured meal plans with detailed food exchanges, allowing users to customize their nutrition while maintaining macro balance. The system uses phase-based meal plans that adapt to user goals.

Overview

The meal planning feature includes:
  • Pre-built meal templates for different phases (adaptation, growth, etc.)
  • 7-day meal cycles with 5 meals per day
  • Food exchange system for flexible substitutions
  • Portion sizes in grams with alternative units
  • Meal timing guidance for optimal results
  • Macro tracking per meal and per day

Architecture

Core Types

// src/features/meal-plan/types/index.ts

export interface FoodItem {
  name: string;           // "Pechuga de pollo"
  grams: number;          // 100
  unit?: string;          // "1 porción" (optional display)
}

export interface Meal {
  id: string;             // "d1m1" (day 1, meal 1)
  name: string;           // "Desayuno"
  time: string;           // "7:00 AM"
  foods: FoodItem[];      // List of foods
  notes?: string;         // Optional preparation notes
}

export interface DayPlan {
  day: number;            // 1-7
  dayName: string;        // "Lunes"
  meals: Meal[];          // 5 meals per day
}

export interface MealPlanConfig {
  phase: number;          // Phase number (1, 2, 3, etc.)
  phaseName: string;      // "Fase de Adaptación"
  duration: string;       // "4 semanas"
  dailyMeals: number;     // 5
  days: DayPlan[];        // 7 days
  exchanges: FoodExchange[];  // Food substitution options
}

Food Exchange System

Allows users to swap foods while maintaining nutritional balance:
export interface FoodExchange {
  category: "protein" | "carbs" | "fats" | "vegetables";
  name: string;           // "Pechuga de pollo"
  equivalentGrams: number;  // 100g
  baseFood: string;       // What it can replace
}

Phase 1: Adaptation Meal Plan

The default meal plan focuses on adaptation and establishing healthy eating patterns.

Configuration

// src/features/meal-plan/data/meal-plan-phase1.ts
export const mealPlanPhase1: MealPlanConfig = {
  phase: 1,
  phaseName: "Fase de Adaptación",
  duration: "4 semanas",
  dailyMeals: 5,
  exchanges: allExchanges,
  days: [
    // 7 days of detailed meal plans
  ]
};

Sample Day (Monday)

{
  id: "d1m1",
  name: "Desayuno",
  time: "7:00 AM",
  foods: [
    { name: "Claras de huevo", grams: 150 },
    { name: "Huevo entero", grams: 50, unit: "1 unidad" },
    { name: "Avena en hojuelas", grams: 40 },
    { name: "Banano", grams: 100 },
  ],
  notes: "Preparar las claras revueltas o en tortilla con el huevo entero"
}
Nutrition Focus: High protein breakfast with complex carbs for sustained energy.

Food Exchange Tables

Users can substitute foods while maintaining nutritional equivalence:

Protein Exchanges

const proteinExchanges: FoodExchange[] = [
  { category: "protein", name: "Pechuga de pollo", equivalentGrams: 100, baseFood: "Proteína animal" },
  { category: "protein", name: "Pescado blanco", equivalentGrams: 100, baseFood: "Proteína animal" },
  { category: "protein", name: "Carne de res magra", equivalentGrams: 100, baseFood: "Proteína animal" },
  { category: "protein", name: "Claras de huevo", equivalentGrams: 150, baseFood: "Proteína animal" },
  { category: "protein", name: "Atún en agua", equivalentGrams: 100, baseFood: "Proteína animal" },
  // ... more proteins
];

Carbohydrate Exchanges

const carbExchanges: FoodExchange[] = [
  { category: "carbs", name: "Arroz blanco", equivalentGrams: 80, baseFood: "Carbohidrato complejo" },
  { category: "carbs", name: "Arroz integral", equivalentGrams: 80, baseFood: "Carbohidrato complejo" },
  { category: "carbs", name: "Papa cocida", equivalentGrams: 150, baseFood: "Carbohidrato complejo" },
  { category: "carbs", name: "Batata", equivalentGrams: 130, baseFood: "Carbohidrato complejo" },
  { category: "carbs", name: "Avena", equivalentGrams: 40, baseFood: "Carbohidrato complejo" },
  // ... more carbs
];

Fat Exchanges

const fatExchanges: FoodExchange[] = [
  { category: "fats", name: "Aguacate", equivalentGrams: 50, baseFood: "Grasa saludable" },
  { category: "fats", name: "Aceite de oliva", equivalentGrams: 10, baseFood: "Grasa saludable" },
  { category: "fats", name: "Almendras", equivalentGrams: 15, baseFood: "Grasa saludable" },
  { category: "fats", name: "Nueces", equivalentGrams: 15, baseFood: "Grasa saludable" },
  // ... more fats
];

Components

Meal Card Component

Displays individual meals with food items and portions:
// src/features/meal-plan/components/MealCard.tsx
interface MealCardProps {
  meal: Meal;
  onEdit?: () => void;
}

export function MealCard({ meal, onEdit }: MealCardProps) {
  return (
    <div className="bg-gray-900 border border-gray-800 rounded-xl p-6">
      <div className="flex justify-between items-start mb-4">
        <div>
          <h3 className="text-xl font-bold text-white">{meal.name}</h3>
          <p className="text-sm text-accent-cyan">{meal.time}</p>
        </div>
        {onEdit && (
          <button onClick={onEdit} className="text-gray-400 hover:text-white">
            Editar
          </button>
        )}
      </div>

      <div className="space-y-2">
        {meal.foods.map((food, idx) => (
          <div key={idx} className="flex justify-between text-sm">
            <span className="text-gray-300">{food.name}</span>
            <span className="text-gray-500">
              {food.unit || `${food.grams}g`}
            </span>
          </div>
        ))}
      </div>

      {meal.notes && (
        <div className="mt-4 p-3 bg-accent-cyan/10 rounded-lg">
          <p className="text-xs text-gray-400">{meal.notes}</p>
        </div>
      )}
    </div>
  );
}

Day Plan View

Displays all meals for a single day:
// src/features/meal-plan/components/DayPlanView.tsx
interface DayPlanViewProps {
  dayPlan: DayPlan;
}

export function DayPlanView({ dayPlan }: DayPlanViewProps) {
  return (
    <div className="space-y-6">
      <div className="text-center">
        <h2 className="text-2xl font-bold text-white">
          Día {dayPlan.day} - {dayPlan.dayName}
        </h2>
      </div>

      <div className="grid gap-6">
        {dayPlan.meals.map((meal) => (
          <MealCard key={meal.id} meal={meal} />
        ))}
      </div>
    </div>
  );
}

Week View Component

Navigate through all 7 days of the meal plan:
// src/features/meal-plan/components/MealPlanWeek.tsx
export function MealPlanWeek({ config }: { config: MealPlanConfig }) {
  const [selectedDay, setSelectedDay] = useState(0);
  const currentDayPlan = config.days[selectedDay];

  return (
    <div className="space-y-8">
      {/* Phase header */}
      <div className="text-center">
        <h1 className="text-3xl font-bold text-white mb-2">
          {config.phaseName}
        </h1>
        <p className="text-gray-400">
          Duración: {config.duration} | {config.dailyMeals} comidas diarias
        </p>
      </div>

      {/* Day selector */}
      <div className="flex gap-2 overflow-x-auto pb-2">
        {config.days.map((day, idx) => (
          <button
            key={day.day}
            onClick={() => setSelectedDay(idx)}
            className={`px-4 py-2 rounded-lg font-medium whitespace-nowrap ${
              selectedDay === idx
                ? 'bg-accent-cyan text-black'
                : 'bg-gray-800 text-gray-400 hover:text-white'
            }`}
          >
            {day.dayName}
          </button>
        ))}
      </div>

      {/* Current day meals */}
      <DayPlanView dayPlan={currentDayPlan} />

      {/* Food exchange guide */}
      <FoodExchangeTable exchanges={config.exchanges} />
    </div>
  );
}

Food Exchange Table

Displays substitution options organized by category:
// src/features/meal-plan/components/FoodExchangeTable.tsx
export function FoodExchangeTable({ exchanges }: { exchanges: FoodExchange[] }) {
  const categories = {
    protein: exchanges.filter(e => e.category === 'protein'),
    carbs: exchanges.filter(e => e.category === 'carbs'),
    fats: exchanges.filter(e => e.category === 'fats'),
    vegetables: exchanges.filter(e => e.category === 'vegetables'),
  };

  return (
    <div className="bg-gray-950 border border-gray-800 rounded-xl p-6">
      <h3 className="text-xl font-bold text-white mb-4">
        Tabla de Intercambios
      </h3>
      <p className="text-sm text-gray-400 mb-6">
        Puedes sustituir alimentos dentro de la misma categoría manteniendo las porciones indicadas
      </p>

      <Tabs>
        <Tab title="Proteínas">
          <ExchangeList items={categories.protein} />
        </Tab>
        <Tab title="Carbohidratos">
          <ExchangeList items={categories.carbs} />
        </Tab>
        <Tab title="Grasas">
          <ExchangeList items={categories.fats} />
        </Tab>
        <Tab title="Vegetales">
          <ExchangeList items={categories.vegetables} />
        </Tab>
      </Tabs>
    </div>
  );
}

Integration with Wizard

Meal plans are generated based on wizard selections:
// Generate meal plan based on user preferences
function generateMealPlan(wizardData: WizardState): MealPlanConfig {
  const { goal, userBodyData, selectedFoods } = wizardData;
  
  // Start with base phase 1 plan
  let plan = { ...mealPlanPhase1 };
  
  // Filter foods based on user preferences
  if (selectedFoods.length > 0) {
    plan = filterMealsByPreferences(plan, selectedFoods);
  }
  
  // Adjust portions based on calorie target
  if (userBodyData) {
    const calories = calculateCalories(userBodyData);
    plan = adjustPortions(plan, calories.target);
  }
  
  return plan;
}

Meal Timing Guidelines

Optimal meal timing for different goals:

Muscle Gain

  • Breakfast: 7:00 AM
  • Pre-workout: 10:00 AM
  • Post-workout: 1:00 PM (high carbs)
  • Afternoon: 4:00 PM
  • Dinner: 7:00 PM

Fat Loss

  • Breakfast: 8:00 AM
  • Mid-morning: 11:00 AM
  • Lunch: 2:00 PM
  • Afternoon: 5:00 PM
  • Dinner: 7:00 PM (low carbs)

Maintenance

  • Breakfast: 7:00 AM
  • Snack: 10:00 AM
  • Lunch: 1:00 PM
  • Snack: 4:00 PM
  • Dinner: 7:30 PM

Usage Example

import { mealPlanPhase1 } from '@/features/meal-plan/data/meal-plan-phase1';
import { MealPlanWeek } from '@/features/meal-plan/components';

function MealPlanPage() {
  return (
    <div className="container mx-auto py-8">
      <MealPlanWeek config={mealPlanPhase1} />
    </div>
  );
}

Best Practices

Medical Disclaimer: The meal plans are informational guides, not medical or nutritional advice. Users should consult with licensed nutritionists for personalized meal planning.
Flexibility: Encourage users to use the food exchange system to maintain adherence. Perfect adherence to exact foods is less important than hitting macro targets.
Hydration: Remind users to drink 2-3 liters of water daily, distributed throughout the day between meals.

See Also

Build docs developers (and LLMs) love