Skip to main content

Overview

The appointment system manages scheduling between nutritionists and patients, including availability management, state tracking, and automated reminders.
Appointments are the core of NutriFit’s workflow, connecting patients with nutritionists for clinical consultations.

Appointment Model

The Appointment model tracks all scheduling data and relationships.
app/Models/Appointment.php
class Appointment extends Model
{
    protected $fillable = [
        'appointment_state_id',
        'paciente_id',
        'nutricionista_id',
        'start_time',
        'end_time',
        'reason',
        'appointment_type',
        'price',
        'notes',
    ];

    protected $casts = [
        'start_time' => 'datetime',
        'end_time' => 'datetime',
        'price' => 'decimal:2',
    ];
}

Relationships

Patient

belongsTo User (role: paciente)

Nutritionist

belongsTo User (role: nutricionista)

State

belongsTo AppointmentState

Attention

hasOne Attention (clinical record)

Appointment States

Appointments progress through different states during their lifecycle:

Pendiente (Pending)

Initial state when appointment is created

Confirmada (Confirmed)

Appointment confirmed by nutritionist

Completada (Completed)

Clinical attention has been recorded

Cancelada (Cancelled)

Cancelled by patient or nutritionist

Vencida (Expired)

Automatically set when appointment time passes without completion

State Management

app/Models/AppointmentState.php
class AppointmentState extends Model
{
    protected $fillable = [
        'name',
        'description',
    ];
}

Scheduling Features

Patient Booking

Patients can schedule appointments through the booking interface:
routes/web.php
Route::middleware(['auth', 'verified', 'role:paciente', 'password.changed'])
    ->prefix('paciente')
    ->name('paciente.')
    ->group(function () {
        Route::get('/agendar', [PacienteController::class, 'showBooking'])
            ->name('booking.index');
        Route::get('/agendar/{nutricionista}', [PacienteController::class, 'selectSchedule'])
            ->name('booking.schedule');
        Route::post('/agendar/{nutricionista}', [PacienteController::class, 'storeAppointment'])
            ->name('booking.store');
    });
1

Select Nutritionist

Patient browses available nutritionists
2

View Availability

System shows nutritionist’s available time slots
3

Choose Time

Patient selects date and time
4

Confirm Booking

Appointment is created in “pendiente” state
5

Notifications Sent

Both patient and nutritionist receive email confirmations

Nutritionist Scheduling

Nutritionists can assign appointments to existing patients:
routes/web.php
Route::middleware(['auth', 'verified', 'role:nutricionista'])
    ->prefix('nutricionista')
    ->name('nutricionista.')
    ->group(function () {
        // Asignar citas a pacientes
        Route::get('/citas/asignar', [NutricionistaController::class, 'createAppointment'])
            ->name('appointments.create');
        Route::get('/citas/asignar/{paciente}/horarios', 
            [NutricionistaController::class, 'getAvailableSchedules'])
            ->name('appointments.schedules');
        Route::post('/citas/asignar', [NutricionistaController::class, 'storeAppointment'])
            ->name('appointments.store');
    });

Availability Management

Nutritionists manage their availability through the schedule interface.

Schedule Configuration

routes/web.php
// Gestión de horarios
Route::get('/horarios', [NutricionistaController::class, 'schedules'])
    ->name('schedules.index');
Route::post('/horarios', [NutricionistaController::class, 'saveSchedules'])
    ->name('schedules.save');
Nutritionists can:
  • Set working hours for each day of the week
  • Define appointment duration
  • Block specific time slots
  • Set recurring availability patterns

Appointment Operations

Viewing Appointments

routes/web.php
Route::get('/citas', [PacienteController::class, 'appointments'])
    ->name('appointments.index');
Route::get('/citas/{appointment}', [PacienteController::class, 'showAppointment'])
    ->name('appointments.show');

Cancellation

Both patients and nutritionists can cancel appointments:
routes/web.php
// Patient cancellation
Route::post('/citas/{appointment}/cancelar', [PacienteController::class, 'cancelAppointment'])
    ->name('appointments.cancel');

// Nutritionist cancellation
Route::post('/appointments/{appointment}/cancel', [NutricionistaController::class, 'cancelAppointment'])
    ->name('appointments.cancel');
When an appointment is cancelled, both parties receive notification emails (AppointmentCancelledByPatient or AppointmentCancelledByNutricionista).

Rescheduling

Nutritionists can reschedule appointments:
routes/web.php
Route::get('/appointments/{appointment}/reschedule', 
    [NutricionistaController::class, 'rescheduleForm'])
    ->name('appointments.reschedule');
Route::post('/appointments/{appointment}/reschedule', 
    [NutricionistaController::class, 'rescheduleAppointment'])
    ->name('appointments.reschedule.store');

Automated Reminders

Reminder Command

The system includes an automated reminder command that runs daily:
app/Console/Commands/SendAppointmentReminders.php
class SendAppointmentReminders extends Command
{
    protected $signature = 'appointments:send-reminders';
    protected $description = 'Envía recordatorios de citas que serán mañana';

    public function handle()
    {
        $tomorrow = Carbon::tomorrow()->toDateString();
        
        $appointments = Appointment::whereDate('start_time', $tomorrow)
            ->whereHas('appointmentState', function($query) {
                $query->whereIn('name', ['confirmada', 'pendiente']);
            })
            ->with(['paciente', 'nutricionista'])
            ->get();

        foreach ($appointments as $appointment) {
            // Notificar al paciente
            $appointment->paciente->notify(
                new AppointmentReminderNotification($appointment)
            );

            // Notificar al nutricionista
            $appointment->nutricionista->notify(
                new AppointmentReminderNotification($appointment)
            );
        }
    }
}

Scheduling the Command

Add to app/Console/Kernel.php:
protected function schedule(Schedule $schedule)
{
    $schedule->command('appointments:send-reminders')
        ->dailyAt('09:00');
}

Expired Appointments

The system automatically marks expired appointments:
app/Models/Appointment.php
public static function markExpiredAppointments(): int
{
    $vencidaState = AppointmentState::where('name', 'vencida')->first();
    
    if (!$vencidaState) {
        return 0;
    }

    // Actualizar citas que ya pasaron y aún están pendientes
    return self::whereHas('appointmentState', fn($q) => $q->where('name', 'pendiente'))
        ->where('end_time', '<', now())
        ->update(['appointment_state_id' => $vencidaState->id]);
}

public function isExpired(): bool
{
    return $this->end_time < now() && $this->appointmentState->name === 'pendiente';
}
Run this periodically using a scheduled command to keep appointment states accurate.

Notifications

The appointment system triggers various notifications:
Sent to nutritionist when patient books an appointment
Sent to patient confirming their booking
Sent when nutritionist confirms the appointment
Sent 24 hours before appointment to both parties
Sent when appointment time is changed
Sent to nutritionist when patient cancels
Sent to patient when nutritionist cancels

Integration with Clinical Records

Appointments link to clinical attention records:
// After appointment, nutritionist creates attention record
Route::get('/citas/{appointment}/atender', [AttentionController::class, 'create'])
    ->name('attentions.create');
Route::post('/citas/{appointment}/atender', [AttentionController::class, 'store'])
    ->name('attentions.store');
When attention is recorded, appointment state changes to “completada”.
See Clinical Records for more details on the attention workflow.

Admin Management

Administrators have full oversight:
routes/web.php
Route::middleware(['auth', 'verified', 'role:administrador'])
    ->prefix('administrador')
    ->name('admin.')
    ->group(function () {
        Route::get('/appointments', [AdminController::class, 'appointments'])
            ->name('appointments.index');
        Route::get('/appointments/{appointment}', [AdminController::class, 'showAppointment'])
            ->name('appointments.show');
        Route::post('/appointments/{appointment}/cancel', 
            [AdminController::class, 'cancelAppointment'])
            ->name('appointments.cancel');
    });

Clinical Records

Learn about attention records and patient data

Notifications

Explore appointment notification system

User Roles

Understand role-based permissions

Build docs developers (and LLMs) love