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:
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
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
TMB (Basal Metabolic Rate)
Base caloric expenditure at rest (500-5000 kcal)
TDEE (Total Daily Energy Expenditure)
Total calories burned including activity (500-10000 kcal)
Waist circumference divided by hip circumference
WHtR (Waist-to-Height Ratio)
Waist circumference divided by height
Body frame size indicator
Nutritional Planning
Activity Levels
Sedentary
Light
Moderate
Active
Very Active
Little to no exercise
Light exercise 1-3 days/week
Moderate exercise 3-5 days/week
Hard exercise 6-7 days/week
Very hard exercise & physical job
Nutrition Goals
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
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
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' );
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
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
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