Skip to main content

Overview

NutriFit’s database is designed using Laravel migrations for version control and portability. The schema supports:
  • Multi-role user management (Admin, Nutritionist, Patient)
  • Appointment scheduling with state tracking
  • Detailed medical attention records with anthropometric data
  • Queue system for asynchronous tasks
  • Session management and authentication
Database Support:
  • Development: SQLite (embedded, no configuration)
  • Production: MySQL 8.0+ (recommended)

Entity Relationship Diagram

Core Tables

users

Purpose: Core user accounts for all system roles Location: database/migrations/2025_10_28_193453_create_users_table.php
ColumnTypeConstraintsDescription
idbigintPRIMARY KEYAuto-incrementing user ID
namestringNOT NULLFull name of the user
emailstringUNIQUE, NOT NULLEmail address (login credential)
email_verified_attimestampNULLVerification timestamp
passwordstringNOT NULLHashed password (bcrypt)
role_idbigintFK → roles, DEFAULT 3User role (1=admin, 2=nutritionist, 3=patient)
user_state_idbigintFK → user_states, DEFAULT 1Account status (1=active, 2=inactive)
data_consentbooleanDEFAULT falseGDPR/LOPD consent flag
data_consent_attimestampNULLWhen consent was given
two_factor_secrettextNULL2FA secret key
two_factor_recovery_codestextNULL2FA backup codes
remember_tokenstring(100)NULL”Remember me” token
created_attimestampAUTOAccount creation
updated_attimestampAUTOLast update
Key Relationships:
// app/Models/User.php
public function role(): BelongsTo
public function personalData(): HasOne
public function appointmentsAsPaciente(): HasMany
public function appointmentsAsNutricionista(): HasMany
public function attentionsAsPaciente(): HasMany
public function attentionsAsNutricionista(): HasMany
public function schedules(): HasMany
Business Logic:
public function estaHabilitadoClinicamente(): bool
{
    return $this->isActive() && $this->hasVerifiedEmail();
}

roles

Purpose: Define user access levels Location: database/migrations/2025_10_28_193452_create_roles_table.php
ColumnTypeConstraintsDescription
idbigintPRIMARY KEYRole ID
namestringUNIQUERole identifier (administrador, nutricionista, paciente)
descriptionstringNULLHuman-readable description
created_attimestampAUTO
updated_attimestampAUTO
Seeded Roles:
[
    ['id' => 1, 'name' => 'administrador', 'description' => 'Administrator with full access'],
    ['id' => 2, 'name' => 'nutricionista', 'description' => 'Nutritionist managing appointments'],
    ['id' => 3, 'name' => 'paciente', 'description' => 'Patient with limited access'],
]

user_states

Purpose: Track account activation status Location: database/migrations/2025_10_28_193451_create_user_states_table.php
ColumnTypeDescription
idbigintState ID
namestringState name (activo, inactivo)
descriptionstringExplanation
States:
  • 1 = activo - Can log in and use system
  • 2 = inactivo - Account disabled by admin

personal_data

Purpose: Extended profile information (1:1 with users) Location: database/migrations/2025_10_28_193605_create_personal_data_table.php
ColumnTypeConstraintsDescription
idbigintPRIMARY KEY
user_idbigintFK → users, CASCADEOwner user
cedulastring(20)UNIQUE, NULLNational ID (Ecuador)
phonestring(10)NULLContact number
addressstringNULLPhysical address
birth_datedateNULLDate of birth
genderenumNULLmale, female, other
profile_photostringNULLStorage path to photo
created_attimestampAUTO
updated_attimestampAUTO
Cascade Behavior: Deleting a user automatically deletes their personal data.

Appointment System

appointments

Purpose: Schedule patient consultations with nutritionists Location: database/migrations/2025_10_28_193618_create_appointments_table.php
ColumnTypeConstraintsDescription
idbigintPRIMARY KEY
appointment_state_idbigintFK → appointment_statesCurrent state
paciente_idbigintFK → users, CASCADEPatient
nutricionista_idbigintFK → users, CASCADENutritionist
start_timetimestampNOT NULLAppointment start
end_timetimestampNULLAppointment end
reasontextNULLConsultation reason
appointment_typeenumDEFAULT ‘primera_vez’primera_vez, seguimiento, control
pricedecimal(8,2)NULLConsultation cost
notestextNULLAdditional notes
created_attimestampAUTO
updated_attimestampAUTO
Appointment Types:
  • primera_vez - First-time consultation
  • seguimiento - Follow-up appointment
  • control - Routine check-up
State Transitions:
pendiente → confirmada → completada

  cancelada

  vencida (expired)

appointment_states

Purpose: Track appointment lifecycle Location: database/migrations/2025_10_28_193612_create_appointment_states_table.php
ColumnTypeDescription
idbigintState ID
namestringState name
descriptiontextExplanation
States:
  1. pendiente - Awaiting confirmation
  2. confirmada - Confirmed by patient/nutritionist
  3. completada - Consultation finished
  4. cancelada - Cancelled before appointment
  5. vencida - Expired (time passed without completion)

nutricionista_schedules

Purpose: Define availability windows for nutritionists Location: database/migrations/2025_11_11_144639_create_nutricionista_schedules_table.php
ColumnTypeConstraintsDescription
idbigintPRIMARY KEY
nutricionista_idbigintFK → users, CASCADENutritionist
day_of_weektinyintNOT NULL0=Sunday, 1=Monday, …, 6=Saturday
start_timetimeNOT NULLOpening time (e.g., 09:00:00)
end_timetimeNOT NULLClosing time (e.g., 17:00:00)
consultation_durationintDEFAULT 45Slot duration in minutes
is_activebooleanDEFAULT trueEnable/disable schedule
created_attimestampAUTO
updated_attimestampAUTO
Index: (nutricionista_id, day_of_week) for fast lookups Usage:
// Get Monday availability for nutritionist ID 5
$schedule = NutricionistaSchedule::where('nutricionista_id', 5)
    ->where('day_of_week', 1)
    ->where('is_active', true)
    ->first();

Medical Records

attentions

Purpose: Document completed medical consultations Location: database/migrations/2025_10_28_193623_create_attentions_table.php
ColumnTypeConstraintsDescription
idbigintPRIMARY KEY
appointment_idbigintFK → appointments, CASCADEAssociated appointment
paciente_idbigintFK → users, CASCADEPatient
nutricionista_idbigintFK → users, CASCADEAttending nutritionist
diagnosistextNULLClinical diagnosis
recommendationstextNULLNutritional recommendations
created_attimestampAUTOConsultation date
updated_attimestampAUTO
Relationship: Each appointment can have at most one attention record (1:1).

attention_data

Purpose: Store anthropometric measurements and nutrition calculations Location: database/migrations/2025_10_28_193629_create_attention_data_table.php
ColumnTypeDescription
weightdecimal(5,2)Body weight (kg)
heightdecimal(5,2)Height (cm)
waistdecimal(5,2)Waist circumference (cm)
hipdecimal(5,2)Hip circumference (cm)
neckdecimal(5,2)Neck circumference (cm)
wristdecimal(5,2)Wrist circumference (cm)
arm_contracteddecimal(5,2)Arm circumference (contracted)
arm_relaxeddecimal(5,2)Arm circumference (relaxed)
thighdecimal(5,2)Thigh circumference (cm)
calfdecimal(5,2)Calf circumference (cm)
Activity Levels (enum):
  • sedentary - Little to no exercise
  • light - Light exercise 1-3 days/week
  • moderate - Moderate exercise 3-5 days/week
  • active - Heavy exercise 6-7 days/week
  • very_active - Very intense exercise + physical job
Nutrition Goals (enum):
  • deficit - Weight loss (calorie deficit)
  • maintenance - Maintain current weight
  • surplus - Weight gain (calorie surplus)

Queue & Job System

jobs

Purpose: Store queued background tasks (email sending, notifications) Location: database/migrations/2025_10_28_193455_create_jobs_table.php
ColumnTypeDescription
idbigintJob ID
queuestringQueue name (default)
payloadlongtextSerialized job data
attemptstinyintRetry attempts
reserved_atintWhen worker picked up job
available_atintWhen job becomes available
created_atintJob creation time
Worker Command:
php artisan queue:work

failed_jobs

Purpose: Log jobs that failed after max retries
ColumnTypeDescription
idbigintFailed job ID
uuidstringUnique identifier
connectiontextQueue connection
queuetextQueue name
payloadlongtextJob data
exceptionlongtextError stack trace
failed_attimestampFailure time
Retry Failed Jobs:
php artisan queue:retry all

Session Management

sessions

Purpose: Database-backed user sessions (stateless application)
ColumnTypeDescription
idstringSession ID (primary key)
user_idbigintAuthenticated user (nullable)
ip_addressstring(45)Client IP
user_agenttextBrowser info
payloadlongtextSession data (serialized)
last_activityintLast activity timestamp
Security: Sessions automatically cleaned up after expiration (default 120 minutes).

password_reset_tokens

Purpose: Temporary tokens for password reset flow
ColumnTypeDescription
emailstringUser email (primary key)
tokenstringHashed reset token
created_attimestampToken creation time
Expiration: Tokens expire after 60 minutes (configurable).

System Configuration

system_settings

Purpose: Store global system configuration (contact info, branding) Location: database/migrations/2026_01_16_192714_create_system_settings_table.php Used for:
  • Contact form email routing
  • System-wide settings
  • Branding information

nutricionista_settings

Purpose: Per-nutritionist preferences and configuration Location: database/migrations/2026_01_14_000000_create_nutricionista_settings_table.php Custom settings for individual nutritionists.

Migration Commands

Run Migrations

# Fresh install
php artisan migrate

# Reset and re-run all
php artisan migrate:fresh

# With seeders
php artisan migrate:fresh --seed

Rollback

# Rollback last batch
php artisan migrate:rollback

# Rollback all
php artisan migrate:reset

Status

# Check migration status
php artisan migrate:status

Database Seeding

Default Admin User:
// DatabaseSeeder.php
User::create([
    'name' => 'Admin',
    'email' => '[email protected]',
    'password' => Hash::make(env('ADMIN_PASSWORD', 'NutriAdmin123')),
    'role_id' => 1, // administrador
    'email_verified_at' => now(),
]);
Run Seeders:
php artisan db:seed

Query Examples

Get All Appointments for a Patient

$appointments = Appointment::where('paciente_id', $patientId)
    ->with(['nutricionista', 'appointmentState'])
    ->orderBy('start_time', 'desc')
    ->get();

Find Available Time Slots

$schedule = NutricionistaSchedule::where('nutricionista_id', $nutricionistaId)
    ->where('day_of_week', $dayOfWeek)
    ->where('is_active', true)
    ->first();

$bookedAppointments = Appointment::where('nutricionista_id', $nutricionistaId)
    ->whereDate('start_time', $date)
    ->get();

// Calculate available slots (business logic)

Get Patient Medical History

$history = Attention::where('paciente_id', $patientId)
    ->with(['attentionData', 'nutricionista', 'appointment'])
    ->orderBy('created_at', 'desc')
    ->get();

Database Backup

SQLite Backup

cp database/database.sqlite database/backups/backup-$(date +%Y%m%d).sqlite

MySQL Backup

mysqldump -u username -p nutrifit > backup-$(date +%Y%m%d).sql

Automated Backups

Consider using:
  • Laravel Backup package (spatie/laravel-backup)
  • Cron job scheduled backups
  • Cloud storage (S3, Dropbox)

Next Steps

Architecture Overview

Understand the full system architecture

TALL Stack

How the technology stack integrates

Models Reference

Eloquent model documentation

Migrations

Creating and modifying database schema

Build docs developers (and LLMs) love