Overview
The Appointment model manages scheduled consultations between nutritionists and patients in the NutriFit system.
Namespace: App\Models\Appointment
Extends: Illuminate\Database\Eloquent\Model
Table: appointments
Fillable Attributes
Foreign key to the AppointmentState model (pendiente, completada, cancelada, vencida)
Foreign key to the User model representing the patient
Foreign key to the User model representing the nutritionist
When the appointment starts (cast to datetime)
When the appointment ends (cast to datetime)
The reason for the appointment (e.g., “Primera consulta”, “Seguimiento”)
Type of appointment (e.g., “presencial”, “virtual”)
Cost of the appointment (cast to decimal with 2 decimal places)
Additional notes or special instructions for the appointment
Casts
The model automatically casts these attributes:
start_time → datetime
end_time → datetime
price → decimal:2
Relationships
BelongsTo
Returns the appointment’s current state (AppointmentState model)$appointment->appointmentState; // pendiente, completada, etc.
Returns the patient user (User model)$appointment->paciente->name; // Patient's full name
Returns the nutritionist user (User model)$appointment->nutricionista->name; // Nutritionist's full name
HasOne
Returns the clinical attention record associated with this appointment (Attention model)// Check if appointment has been attended
if ($appointment->attention) {
$diagnosis = $appointment->attention->diagnosis;
}
Static Methods
markExpiredAppointments()
public static function markExpiredAppointments(): int
Automatically marks appointments as expired if their end_time has passed and they are still in ‘pendiente’ state.
Returns: The number of appointments that were marked as expired.
Usage:
// Typically run via scheduled task
$expiredCount = Appointment::markExpiredAppointments();
Log::info("Marked {$expiredCount} appointments as expired");
Implementation:
- Finds the ‘vencida’ state from AppointmentState
- Updates all ‘pendiente’ appointments where
end_time < now()
- Returns count of updated records
Instance Methods
isExpired()
public function isExpired(): bool
Checks if this specific appointment is expired (end time has passed and still marked as ‘pendiente’).
Returns: true if the appointment is expired, false otherwise.
Usage:
if ($appointment->isExpired()) {
// Show "This appointment has expired" message
// Prevent editing or attending
}
Usage Examples
Creating an Appointment
use App\Models\Appointment;
use Carbon\Carbon;
$appointment = Appointment::create([
'appointment_state_id' => 1, // pendiente
'paciente_id' => $patient->id,
'nutricionista_id' => $nutritionist->id,
'start_time' => Carbon::parse('2026-03-10 10:00:00'),
'end_time' => Carbon::parse('2026-03-10 11:00:00'),
'reason' => 'Primera consulta nutricional',
'appointment_type' => 'presencial',
'price' => 50.00,
'notes' => 'Paciente tiene alergia a frutos secos',
]);
Querying Appointments
// Get upcoming appointments for a nutritionist
$upcomingAppointments = Appointment::where('nutricionista_id', $user->id)
->whereHas('appointmentState', fn($q) => $q->where('name', 'pendiente'))
->where('start_time', '>', now())
->orderBy('start_time')
->get();
// Get completed appointments for a patient
$completedAppointments = Appointment::where('paciente_id', $user->id)
->whereHas('appointmentState', fn($q) => $q->where('name', 'completada'))
->with(['nutricionista.personalData', 'attention'])
->orderBy('start_time', 'desc')
->get();
Checking Appointment Status
$appointment = Appointment::with('appointmentState')->find(1);
if ($appointment->isExpired()) {
// Mark as expired if not already
$vencidaState = AppointmentState::where('name', 'vencida')->first();
$appointment->update(['appointment_state_id' => $vencidaState->id]);
}
// Check if appointment has been attended
if ($appointment->attention) {
echo "This appointment was completed with diagnosis: ";
echo $appointment->attention->diagnosis;
}
Scheduled Task Example
// In app/Console/Kernel.php
protected function schedule(Schedule $schedule)
{
// Run every hour to mark expired appointments
$schedule->call(function () {
Appointment::markExpiredAppointments();
})->hourly();
}
Calculating Duration
$appointment = Appointment::find(1);
$durationInMinutes = $appointment->start_time->diffInMinutes($appointment->end_time);
echo "Duration: {$durationInMinutes} minutes";
// Format for display
echo $appointment->start_time->format('d/m/Y H:i'); // 10/03/2026 10:00
Validation Before Booking
// Check for conflicts
$hasConflict = Appointment::where('nutricionista_id', $nutricionista->id)
->whereHas('appointmentState', fn($q) =>
$q->whereIn('name', ['pendiente', 'completada'])
)
->where(function ($query) use ($startTime, $endTime) {
$query->whereBetween('start_time', [$startTime, $endTime])
->orWhereBetween('end_time', [$startTime, $endTime])
->orWhere(function ($q) use ($startTime, $endTime) {
$q->where('start_time', '<=', $startTime)
->where('end_time', '>=', $endTime);
});
})
->exists();
if ($hasConflict) {
return back()->withErrors('El nutricionista ya tiene una cita en ese horario');
}