Skip to main content

Overview

The clinical records system manages patient attention data, including anthropometric measurements, diagnoses, nutritional recommendations, and automated PDF report generation.
Each appointment can have one associated attention record that captures comprehensive clinical data.

Attention Model

The Attention model stores clinical consultation data:
app/Models/Attention.php
class Attention extends Model
{
    protected $fillable = [
        'appointment_id',
        'paciente_id',
        'nutricionista_id',
        'diagnosis',
        'recommendations',
    ];

    public function appointment(): BelongsTo
    {
        return $this->belongsTo(Appointment::class, 'appointment_id');
    }

    public function paciente(): BelongsTo
    {
        return $this->belongsTo(User::class, 'paciente_id');
    }

    public function nutricionista(): BelongsTo
    {
        return $this->belongsTo(User::class, 'nutricionista_id');
    }

    public function attentionData(): HasOne
    {
        return $this->hasOne(AttentionData::class, 'attention_id');
    }
}

Data Structure

Attention

Core clinical notes: diagnosis and recommendations

AttentionData

Detailed anthropometric measurements and calculations

Appointment

Links to the appointment being attended

Patient/Nutritionist

Tracks who performed and received the attention

Creating Attention Records

Access Control

Only the assigned nutritionist can create attention records:
app/Http/Controllers/AttentionController.php
public function create(Appointment $appointment)
{
    // Verificar que la cita pertenece al nutricionista autenticado
    if ($appointment->nutricionista_id !== auth()->id()) {
        abort(403, 'No tienes permiso para atender esta cita.');
    }

    // Verificar que la cita esté en estado pendiente
    if ($appointment->appointmentState->name !== 'pendiente') {
        return redirect()
            ->route('nutricionista.appointments.show', $appointment)
            ->with('error', 'Esta cita no puede ser atendida.');
    }
}

Routes

routes/web.php
Route::middleware(['auth', 'verified', 'role:nutricionista'])
    ->prefix('nutricionista')
    ->name('nutricionista.')
    ->group(function () {
        // Gestión de atenciones
        Route::get('/citas/{appointment}/atender', [AttentionController::class, 'create'])
            ->name('attentions.create');
        Route::post('/citas/{appointment}/atender', [AttentionController::class, 'store'])
            ->name('attentions.store');
        Route::get('/citas/{appointment}/editar-atencion', [AttentionController::class, 'edit'])
            ->name('attentions.edit');
        Route::put('/citas/{appointment}/editar-atencion', [AttentionController::class, 'update'])
            ->name('attentions.update');
    });

Anthropometric Data

The system captures comprehensive anthropometric measurements:

Basic Measurements

Weight

Body weight in kilograms (20-300 kg)

Height

Height in centimeters (100-250 cm)

Body Measurements

  • Waist (cintura): 30-200 cm
  • Hip (cadera): 30-200 cm
  • Neck (cuello): 20-100 cm
  • Wrist (muñeca): 10-30 cm
  • Arm Contracted (brazo contraído): 15-100 cm
  • Arm Relaxed (brazo relajado): 15-100 cm
  • Thigh (pierna): 30-150 cm
  • Calf (pantorrilla): 20-100 cm

Calculated Metrics

Automatically calculated from weight and height
Estimated body fat based on measurements
Base caloric expenditure at rest (500-5000 kcal)
Total calories burned including activity (500-10000 kcal)
Waist circumference divided by hip circumference
Waist circumference divided by height
Body frame size indicator

Nutritional Planning

Activity Levels

Little to no exercise

Nutrition Goals

Deficit

Weight loss

Maintenance

Weight maintenance

Surplus

Weight gain / muscle building

Macronutrient Distribution

The system calculates and tracks:
  • Target Calories: Total daily caloric goal
  • Protein: Grams and percentage
  • Fat: Grams and percentage
  • Carbohydrates: Grams and percentage

Food Equivalents System

Nutritionists can prescribe food portions using the equivalents system:

Cereales

Grains and cereals

Verduras

Vegetables

Frutas

Fruits

Lácteo

Dairy products

Animal

Animal proteins

Aceites

Oils and fats

Grasas con Proteína

Protein-rich fats

Leguminosas

Legumes and beans

Data Validation

The system enforces strict validation on all measurements:
app/Http/Controllers/AttentionController.php
$validated = $request->validate([
    // Medidas básicas (obligatorias)
    'weight' => 'required|numeric|min:20|max:300',
    'height' => 'required|numeric|min:100|max:250',
    // Medidas corporales (obligatorias)
    'waist' => 'required|numeric|min:30|max:200',
    'hip' => 'required|numeric|min:30|max:200',
    'neck' => 'required|numeric|min:20|max:100',
    // ... más validaciones
    'activity_level' => 'required|in:sedentary,light,moderate,active,very_active',
    'nutrition_goal' => 'required|in:deficit,maintenance,surplus',
    'bmi' => 'required|numeric|min:10|max:60',
    'diagnosis' => 'required|string|max:5000',
    'recommendations' => 'required|string|max:5000',
]);
All measurements have realistic min/max constraints to prevent data entry errors.

Storing Clinical Data

The storage process uses database transactions:
app/Http/Controllers/AttentionController.php
try {
    DB::beginTransaction();

    // Crear el registro de atención
    $attention = Attention::create([
        'appointment_id' => $appointment->id,
        'paciente_id' => $appointment->paciente_id,
        'nutricionista_id' => $appointment->nutricionista_id,
        'diagnosis' => $validated['diagnosis'],
        'recommendations' => $validated['recommendations'],
    ]);

    // Crear los datos antropométricos
    AttentionData::create([
        'attention_id' => $attention->id,
        'weight' => $validated['weight'],
        'height' => $validated['height'],
        // ... todos los datos
    ]);

    // Cambiar el estado de la cita a completada
    $completedState = AppointmentState::where('name', 'completada')->first();
    $appointment->update([
        'appointment_state_id' => $completedState->id,
    ]);

    DB::commit();

    // Enviar notificación al paciente
    $appointment->paciente->notify(new AttentionCompletedNotification($attention));

} catch (\Exception $e) {
    DB::rollBack();
    return redirect()->back()->with('error', 'Error al guardar la atención');
}
When attention is saved, the appointment automatically changes to “completada” state.

PDF Report Generation

The system generates professional PDF reports using DomPDF.

PDF Controller

app/Http/Controllers/AttentionPdfController.php
class AttentionPdfController extends Controller
{
    public function download(Appointment $appointment)
    {
        // Verificar que el usuario tenga permiso
        $user = auth()->user();
        
        if ($user->role->name === 'nutricionista' 
            && $appointment->nutricionista_id !== $user->id) {
            abort(403, 'No tienes permiso para ver esta atención.');
        }
        
        if ($user->role->name === 'paciente' 
            && $appointment->paciente_id !== $user->id) {
            abort(403, 'No tienes permiso para ver esta atención.');
        }

        // Cargar las relaciones necesarias
        $appointment->load([
            'paciente.personalData',
            'nutricionista.personalData',
            'attention.attentionData',
            'appointmentState'
        ]);

        // Calcular el número de atención
        $attentionNumber = Attention::where('paciente_id', $appointment->paciente_id)
            ->where('created_at', '<=', $appointment->attention->created_at)
            ->count();

        // Preparar datos
        $data = [
            'appointment' => $appointment,
            'attention' => $appointment->attention,
            'attentionData' => $appointment->attention->attentionData,
            'paciente' => $appointment->paciente,
            'nutricionista' => $appointment->nutricionista,
            'attentionNumber' => $attentionNumber,
            'attentionDate' => Carbon::parse($appointment->attention->created_at)
                ->format('d-m-Y'),
        ];

        // Generar el PDF
        $pdf = Pdf::loadView('pdf.attention', $data);
        $pdf->setPaper('a4', 'portrait');

        // Nombre del archivo: NombrePaciente_Atencion1_14-01-2026.pdf
        $pacienteName = str_replace(' ', '_', $appointment->paciente->name);
        $fileName = "{$pacienteName}_Atencion{$attentionNumber}_{$data['attentionDate']}.pdf";

        return $pdf->download($fileName);
    }
}

PDF Routes

routes/web.php
Route::get('/citas/{appointment}/pdf', 
    [AttentionPdfController::class, 'download'])
    ->name('attentions.pdf.download');
Route::get('/citas/{appointment}/pdf/ver', 
    [AttentionPdfController::class, 'view'])
    ->name('attentions.pdf.view');

PDF Features

Download

Download PDF file to device

View in Browser

Preview PDF without downloading

Professional Format

A4 portrait layout with branding

Complete Data

All measurements, calculations, and recommendations

Editing Records

Nutritionists can edit attention records after creation:
app/Http/Controllers/AttentionController.php
public function update(Request $request, Appointment $appointment)
{
    // Verificar permisos y validar datos
    try {
        DB::beginTransaction();

        // Actualizar el registro de atención
        $appointment->attention->update([
            'diagnosis' => $validated['diagnosis'],
            'recommendations' => $validated['recommendations'],
        ]);

        // Actualizar los datos antropométricos
        $appointment->attention->attentionData->update([
            // Todos los datos actualizados
        ]);

        DB::commit();
    } catch (\Exception $e) {
        DB::rollBack();
    }
}
Editing maintains the original creation timestamp but updates all clinical data.

Patient History

Patients can view their complete clinical history:

Patient Routes

routes/web.php
Route::middleware(['auth', 'verified', 'role:paciente', 'password.changed'])
    ->prefix('paciente')
    ->name('paciente.')
    ->group(function () {
        Route::get('/historial', [PacienteController::class, 'history'])
            ->name('history');
    });

Nutritionist Patient View

routes/web.php
Route::middleware(['auth', 'verified', 'role:nutricionista'])
    ->prefix('nutricionista')
    ->name('nutricionista.')
    ->group(function () {
        Route::get('/pacientes/{patient}/historial', 
            [NutricionistaController::class, 'patientHistory'])
            ->name('patients.history');
    });

Notifications

When attention is completed, the patient receives a detailed notification:
app/Notifications/AttentionCompletedNotification.php
public function toMail(object $notifiable): MailMessage
{
    $appointmentDate = $this->attention->appointment->start_time->format('d/m/Y');
    $nutricionistaName = $this->attention->nutricionista->name;
    $weight = $this->attention->attentionData->weight ?? 'N/A';
    $bmi = $this->attention->attentionData->bmi ?? 'N/A';

    return (new MailMessage)
        ->subject('✅ Atención Nutricional Completada - NutriFit')
        ->greeting('¡Hola ' . $notifiable->name . '!')
        ->line('Tu atención nutricional ha sido completada exitosamente.')
        ->line('**Nutricionista:** Dr(a). ' . $nutricionistaName)
        ->line('**Fecha de atención:** ' . $appointmentDate)
        ->line('**Peso registrado:** ' . $weight . ' kg')
        ->line('**IMC:** ' . number_format($bmi, 2))
        ->action('Ver Reporte Completo', 
            url('/paciente/citas/' . $this->attention->appointment_id));
}

Data Privacy

Clinical records contain sensitive health information and must be protected according to data privacy regulations.

Access Control

  • Nutritionists: Can only view/edit records for their own patients
  • Patients: Can only view their own records
  • Administrators: Have read-only access for oversight

Appointments

Learn about appointment scheduling

User Roles

Understand role-based access control

Notifications

Explore the notification system

Build docs developers (and LLMs) love