lunes, 8 de septiembre de 2025

Módulo de historia clínica para clínica odontológica en React

 Me gustaría comenzar con el código para el módulo de historia clínica.

Con un stack como Laravel, Inertia, React y TypeScript, no usaremos Firebase. En cambio, el frontend (React) se comunicará con tu backend de Laravel a través de una API. Tu base de datos SQL se encargará de toda la persistencia de datos.

He creado un componente de React en TypeScript, diseñado para funcionar perfectamente dentro de tu flujo de Inertia. Utiliza los hooks de React para gestionar el estado, Tailwind CSS para los estilos y está listo para integrarse con los componentes de shadcn/ui que ya tienes. Asumo que tu backend de Laravel proporcionará los endpoints necesarios para gestionar los datos de la historia clínica.

Historia Clínica
import React, { useState, useEffect } from 'react';

// Se asume que estos tipos serían definidos globalmente o en un archivo de tipos.
// interface PatientData {
// id: string;
// name: string;
// //... otros datos del paciente
// }

interface ClinicalRecord {
id: string;
patientId: string;
date: string;
reason: string;
diagnosis: string;
treatment: string | null;
}

// Este es el componente principal que se usará como una página de Inertia.
const ClinicalHistoryPage: React.FC = () => {
// Estado para los registros clínicos del paciente
const [records, setRecords] = useState<ClinicalRecord[]>([]);
// Estado para el formulario de nueva consulta
const [formData, setFormData] = useState({
reason: '',
diagnosis: '',
treatment: '',
});
// Estado para la carga de datos
const [isLoading, setIsLoading] = useState(true);
// Estado para mensajes de la interfaz de usuario
const [message, setMessage] = useState<{ text: string; type: 'success' | 'error' } | null>(null);

// Asumimos un ID de paciente. En una aplicación real, esto vendría como una prop de Inertia.
const patientId = 'patient-001';
const patientName = 'Juan Pérez';

// Hook para simular la obtención de datos de tu backend de Laravel
useEffect(() => {
// En una aplicación real, harías una llamada API:
// const response = await fetch(`/api/patients/${patientId}/clinical-history`);
// const data = await response.json();
// setRecords(data);
// Simulación de una llamada API
setTimeout(() => {
const mockRecords: ClinicalRecord[] = [
{
id: 'rec-001',
patientId: 'patient-001',
date: '2025-09-01T10:00:00Z',
reason: 'Dolor de muela',
diagnosis: 'Caries en pieza 46',
treatment: 'Restauración con resina',
},
{
id: 'rec-002',
patientId: 'patient-001',
date: '2025-08-15T14:30:00Z',
reason: 'Control rutinario',
diagnosis: 'Sin hallazgos patológicos',
treatment: 'Limpieza y aplicación de flúor',
},
];
setRecords(mockRecords);
setIsLoading(false);
}, 1500);
}, []);

// Maneja el cambio en los campos del formulario
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
setFormData({ ...formData, [e.target.name]: e.target.value });
};

// Maneja el envío del formulario
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();

// En una aplicación real, harías una llamada a tu API de Laravel
// const response = await fetch(`/api/patients/${patientId}/clinical-history`, {
// method: 'POST',
// headers: { 'Content-Type': 'application/json' },
// body: JSON.stringify({ ...formData, patientId: patientId }),
// });

// Simulación de una llamada API exitosa
setTimeout(() => {
const newRecord: ClinicalRecord = {
id: `rec-${Date.now()}`,
patientId: patientId,
date: new Date().toISOString(),
...formData,
};
// Actualiza el estado con el nuevo registro
setRecords(prevRecords => [newRecord, ...prevRecords]);
setFormData({ reason: '', diagnosis: '', treatment: '' }); // Reset del formulario
setMessage({ text: '¡Registro guardado exitosamente!', type: 'success' });
}, 500);
};

if (isLoading) {
return (
<div className="flex items-center justify-center min-h-screen">
<p className="text-xl font-semibold text-gray-600">Cargando...</p>
</div>
);
}

return (
<div className="p-4 md:p-8 max-w-4xl mx-auto font-sans">
<h1 className="text-3xl md:text-4xl font-bold text-gray-800 mb-2">Historia Clínica</h1>
<p className="text-lg text-gray-600 mb-6">Paciente: <span className="font-semibold">{patientName}</span></p>

{/* Formulario de nueva consulta */}
<div className="bg-white rounded-xl shadow-lg p-6 mb-8">
<h2 className="text-xl font-semibold text-gray-700 mb-4">Nueva Consulta</h2>
<form onSubmit={handleSubmit}>
<div className="space-y-4">
<div>
{/* Componente shadcn/ui: Label */}
<label htmlFor="reason" className="block text-sm font-medium text-gray-700 mb-1">Motivo de la Consulta</label>
{/* Componente shadcn/ui: Input */}
<input
id="reason"
name="reason"
type="text"
value={formData.reason}
onChange={handleChange}
className="w-full rounded-lg border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500"
required
/>
</div>
<div>
{/* Componente shadcn/ui: Label */}
<label htmlFor="diagnosis" className="block text-sm font-medium text-gray-700 mb-1">Diagnóstico</label>
{/* Componente shadcn/ui: Textarea */}
<textarea
id="diagnosis"
name="diagnosis"
rows={3}
value={formData.diagnosis}
onChange={handleChange}
className="w-full rounded-lg border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500"
required
></textarea>
</div>
<div>
{/* Componente shadcn/ui: Label */}
<label htmlFor="treatment" className="block text-sm font-medium text-gray-700 mb-1">Plan de Tratamiento</label>
{/* Componente shadcn/ui: Textarea */}
<textarea
id="treatment"
name="treatment"
rows={3}
value={formData.treatment}
onChange={handleChange}
className="w-full rounded-lg border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500"
></textarea>
</div>
</div>
{message && (
<div className={`p-4 mt-4 text-sm font-medium rounded-lg text-center ${message.type === 'success' ? 'bg-green-100 text-green-700' : 'bg-red-100 text-red-700'}`}>
{message.text}
</div>
)}
{/* Componente shadcn/ui: Button */}
<button type="submit" className="mt-6 w-full py-2 px-4 rounded-lg bg-blue-600 text-white font-semibold hover:bg-blue-700 transition-colors duration-300">
Guardar Registro
</button>
</form>
</div>

{/* Historial de consultas */}
<div className="bg-white rounded-xl shadow-lg p-6">
<h2 className="text-xl font-semibold text-gray-700 mb-4">Historial de Consultas</h2>
<div className="space-y-4">
{records.map(record => (
// Componente shadcn/ui: Card (simulado con div)
<div key={record.id} className="bg-gray-50 border border-gray-200 rounded-lg p-4 transition-transform hover:shadow-md">
<div className="flex justify-between items-center mb-2">
<p className="text-sm font-medium text-gray-500">{new Date(record.date).toLocaleDateString()}</p>
</div>
<p className="font-semibold text-gray-800">{record.reason}</p>
<p className="text-gray-600 mt-1"><span className="font-medium">Diagnóstico:</span> {record.diagnosis}</p>
<p className="text-gray-600 mt-1"><span className="font-medium">Tratamiento:</span> {record.treatment || 'N/A'}</p>
</div>
))}
</div>
</div>
</div>
);
};

export default ClinicalHistoryPage;

Este archivo .tsx es un componente de React que puedes colocar en la carpeta resources/js/Pages de tu proyecto Laravel. Cuando lo integres, Inertia se encargará de renderizarlo.

El código incluye:

  • Un componente funcional de React.

  • Tipado de TypeScript para una mejor gestión de los datos.

  • Hooks de React para el estado y los efectos (como la carga inicial de datos).

  • Diseño responsivo y claro con clases de Tailwind.

  • Comentarios que indican cómo se conectarían los componentes de shadcn/ui y cómo se realizarían las llamadas a tu backend de Laravel.

Este componente está diseñado para que lo puedas conectar fácilmente a los endpoints de tu API en Laravel.

Ahora que tenemos el componente de la historia clínica listo, ¿qué te parece si pasamos a los turnos? Podemos crear otro componente de React para gestionarlos y te puedo dar ideas sobre cómo estructurar el backend.

No hay comentarios: