Ir al contenido principal

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.

Comentarios

Entradas populares de este blog

Crear proyecto de frontend con Vite, React, Tailwind, Typescript y JSX

 ¿Cómo se crea un proyecto exclusivo de frontend con Vite, React, Tailwind, Typescript y JSX? ¡Absolutamente! Entiendo que quieres la guía completa para pasar de cero a tener un proyecto de frontend moderno, vacío y listo para ser llenado con tu código. A continuación, te proporciono un archivo de guía detallada que incluye todos los comandos necesarios para inicializar el proyecto, configurar Tailwind CSS, y los pasos clave para empezar a "poblarlo". Voy a generar un documento Markdown con la guía paso a paso, incluyendo los comandos de terminal y las modificaciones esenciales a los archivos de configuración. Guía: Creación de un Proyecto Frontend Puro (Vite, React, TypeScript, Tailwind) Este documento detalla el proceso para inicializar un proyecto de frontend vacío y optimizado, utilizando Vite como empaquetador, React como librería, TypeScript para tipado y Tailwind CSS para estilizado. 1. Inicialización del Proyecto con Vite Vite es la herramienta más rápida para emp...

Tokens V2

Tokens: Programme to discover tokens, where there are not. Now available at  https://puszcza.gnu.org.ua/projects/tokens/ This is Version 2, for  version 1, go here . Synopsis: use TokensV2; sub printFile; my @FORMAT = ( ['<Message Date=".*?" Time=".*?" DateTime=".*?" SessionID=".*?"><From>(?:<User FriendlyName=".*?"/>)+</From><To>(?:<User FriendlyName=".*?"/>)+</To><Text(?: Style=".*?")?>.*?</Text></Message>',   sub {     my $fh = $_[1];     my ($d, $t, $f, $s, $T) = $_[0] =~ m|<Message Date="(.*?)" Time="(.*?)" DateTime=".*?" SessionID=".*?">(<From>(?:<User FriendlyName=".*?"/>)+</From>)<To>(?:<User FriendlyName=".*?"/>)+</To><Text(?: Style="(.*?)")?>(.*?)</Text></Message>|;     my $F = join '<br />', ...

Perl Net::LDAP::SimpleServer

Adaptaciones sobre el módulo LDAP Server para Windows (Strawberry Perl) Lista de adaptaciones (continúa más abajo): - Relajación de condiciones de bind:     - Cuenta principal (principal account)     - Validación de contraseñas Ubicación del archivo: %Strawberry_Perl%\site\lib\net\ldap\SimpleServer\ProtocolHandler.pm CPAN: http://search.cpan.org/~russoz/Net-LDAP-SimpleServer-0.0.17/lib/Net/LDAP/SimpleServer.pm Código: package Net::LDAP::SimpleServer::ProtocolHandler; use strict; use warnings; # ABSTRACT: LDAP protocol handler used with Net::LDAP::SimpleServer our $VERSION = '0.0.17';    # VERSION use Net::LDAP::Server; use base 'Net::LDAP::Server'; use fields qw(store root_dn root_pw allow_anon); use Carp; use Net::LDAP::LDIF; use Net::LDAP::Util qw{canonical_dn}; use Net::LDAP::FilterMatch; use Net::LDAP::Constant (     qw/LDAP_SUCCESS LDAP_AUTH_UNKNOWN LDAP_INVALID_CREDENTIALS/,   ...