Mostrando entradas con la etiqueta firebase. Mostrar todas las entradas
Mostrando entradas con la etiqueta firebase. Mostrar todas las entradas

lunes, 8 de septiembre de 2025

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

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

¡Excelente decisión! La historia clínica es el pilar de tu aplicación. Al tenerla lista, tendrás un lugar centralizado para almacenar todos los registros de los pacientes, incluyendo los módulos que ya has desarrollado y los que están por venir.

He preparado un archivo HTML que incluye todo lo necesario: la estructura, el diseño y la lógica para conectarse a una base de datos de Firebase Firestore. Este código te permitirá crear, ver y actualizar el historial de cada paciente en tiempo real.

Historia Clínica Dental
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Historia Clínica Dental</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
body {
font-family: 'Inter', sans-serif;
background-color: #f3f4f6;
}
.container {
max-width: 900px;
}
.card {
background-color: #ffffff;
border-radius: 1rem;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
padding: 2rem;
}
.scrollable-content {
max-height: 500px;
overflow-y: auto;
}
</style>
</head>
<body class="bg-gray-100 flex items-center justify-center min-h-screen p-4">

<div id="app-container" class="container mx-auto">
<div id="loading" class="flex items-center justify-center h-full">
<div class="text-xl font-semibold text-gray-600">Cargando...</div>
</div>

<div id="content" class="hidden">
<h1 class="text-3xl font-bold text-gray-800 mb-6 text-center">Historia Clínica del Paciente</h1>
<p id="user-info" class="text-sm text-gray-600 text-center mb-6"></p>

<!-- Sección de Datos del Paciente (Ejemplo de integración) -->
<div class="card mb-6">
<h2 class="text-xl font-semibold text-gray-700 mb-4">Datos del Paciente</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<p class="text-sm text-gray-500">Nombre del Paciente</p>
<p class="font-medium text-gray-900">Juan Pérez</p>
</div>
<div>
<p class="text-sm text-gray-500">ID del Paciente</p>
<p class="font-medium text-gray-900" id="patient-id">patient-001</p>
</div>
<!-- Se pueden agregar más datos como antecedentes médicos y odontograma aquí -->
</div>
</div>

<!-- Formulario para agregar una nueva consulta -->
<div class="card mb-6">
<h2 class="text-xl font-semibold text-gray-700 mb-4">Nueva Consulta</h2>
<form id="record-form">
<div class="mb-4">
<label for="reason" class="block text-sm font-medium text-gray-700 mb-1">Motivo de la Consulta</label>
<input type="text" id="reason" name="reason" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="Ej: Dolor de muela" required>
</div>
<div class="mb-4">
<label for="diagnosis" class="block text-sm font-medium text-gray-700 mb-1">Diagnóstico</label>
<textarea id="diagnosis" name="diagnosis" rows="3" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="Ej: Caries en pieza 46" required></textarea>
</div>
<div class="mb-4">
<label for="treatment" class="block text-sm font-medium text-gray-700 mb-1">Plan de Tratamiento</label>
<textarea id="treatment" name="treatment" rows="3" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="Ej: Restauración con resina"></textarea>
</div>
<div id="message-box" class="hidden p-3 mt-4 text-sm text-center font-medium rounded-lg" role="alert"></div>
<button type="submit" class="w-full px-4 py-2 mt-2 font-semibold text-white bg-blue-600 rounded-lg hover:bg-blue-700 transition duration-300">
Guardar Registro
</button>
</form>
</div>

<!-- Historial de consultas -->
<div class="card">
<h2 class="text-xl font-semibold text-gray-700 mb-4">Historial de Consultas</h2>
<div id="records-container" class="scrollable-content space-y-4">
<!-- Los registros se insertarán aquí dinámicamente -->
</div>
</div>
</div>
</div>

<script type="module">
import { initializeApp } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-app.js";
import { getAuth, signInAnonymously, signInWithCustomToken, onAuthStateChanged } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-auth.js";
import { getFirestore, doc, getDoc, addDoc, setDoc, updateDoc, deleteDoc, onSnapshot, collection, query, where, orderBy, getDocs } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-firestore.js";
// --- Configuración de Firebase y Autenticación ---
// Se utilizan las variables globales proporcionadas por el entorno de Canvas
const firebaseConfig = JSON.parse(typeof __firebase_config !== 'undefined' ? __firebase_config : '{}');
const appId = typeof __app_id !== 'undefined' ? __app_id : 'default-app-id';
const initialAuthToken = typeof __initial_auth_token !== 'undefined' ? __initial_auth_token : null;

const app = initializeApp(firebaseConfig);
const db = getFirestore(app);
const auth = getAuth(app);
// Variables para los elementos del DOM
const loadingScreen = document.getElementById('loading');
const mainContent = document.getElementById('content');
const userInfo = document.getElementById('user-info');
const recordForm = document.getElementById('record-form');
const recordsContainer = document.getElementById('records-container');
const messageBox = document.getElementById('message-box');

// Se usa un ID de paciente de ejemplo. En una app real, este ID vendría de la lista de pacientes.
const patientId = document.getElementById('patient-id').textContent;
// Lógica de Autenticación
onAuthStateChanged(auth, async (user) => {
if (user) {
const userId = user.uid;
userInfo.textContent = `Usuario autenticado: ${userId}`;
loadingScreen.classList.add('hidden');
mainContent.classList.remove('hidden');

// Iniciar la escucha de los registros una vez que el usuario esté autenticado
listenForClinicalHistory(patientId);

} else {
// Si no hay token, se inicia sesión de forma anónima
if (initialAuthToken) {
try {
await signInWithCustomToken(auth, initialAuthToken);
} catch (error) {
console.error("Error al iniciar sesión con token personalizado:", error);
console.log("Intentando iniciar sesión de forma anónima...");
await signInAnonymously(auth);
}
} else {
await signInAnonymously(auth);
}
}
});

// --- Funciones de la Aplicación ---
/**
* Muestra un mensaje en la caja de mensajes.
* @param {string} message - El mensaje a mostrar.
* @param {string} type - El tipo de mensaje ('success' o 'error').
*/
function showMessage(message, type) {
messageBox.textContent = message;
messageBox.classList.remove('hidden', 'bg-green-100', 'text-green-700', 'bg-red-100', 'text-red-700');
if (type === 'success') {
messageBox.classList.add('bg-green-100', 'text-green-700');
} else if (type === 'error') {
messageBox.classList.add('bg-red-100', 'text-red-700');
}
}

/**
* Escucha los cambios en la colección de historia clínica para un paciente.
* Utiliza onSnapshot para actualizaciones en tiempo real.
* @param {string} patientId - El ID del paciente.
*/
function listenForClinicalHistory(patientId) {
const clinicalHistoryRef = collection(db, `artifacts/${appId}/public/data/clinical_histories`);
const q = query(clinicalHistoryRef, where("patientId", "==", patientId));

onSnapshot(q, (snapshot) => {
recordsContainer.innerHTML = ''; // Limpiar el contenedor
if (snapshot.empty) {
recordsContainer.innerHTML = '<p class="text-gray-500 text-center">No hay registros de consultas para este paciente.</p>';
}
const records = [];
snapshot.forEach(doc => {
records.push({ id: doc.id, ...doc.data() });
});

// Ordenar los registros por fecha (del más reciente al más antiguo)
records.sort((a, b) => new Date(b.date) - new Date(a.date));

records.forEach(record => {
const recordElement = document.createElement('div');
recordElement.className = 'border-l-4 border-blue-500 pl-4 py-2 bg-gray-50 rounded-lg';
recordElement.innerHTML = `
<div class="flex justify-between items-start">
<p class="text-sm text-gray-400">${new Date(record.date).toLocaleDateString()}</p>
</div>
<p class="font-semibold text-gray-800">${record.reason}</p>
<p class="text-gray-600 mt-1">**Diagnóstico:** ${record.diagnosis}</p>
<p class="text-gray-600 mt-1">**Tratamiento:** ${record.treatment || 'N/A'}</p>
`;
recordsContainer.appendChild(recordElement);
});
}, (error) => {
console.error("Error al escuchar los datos:", error);
showMessage("Error al cargar la historia clínica. Inténtelo de nuevo.", 'error');
});
}

/**
* Guarda un nuevo registro de consulta en Firestore.
* @param {Event} e - El evento de envío del formulario.
*/
async function addClinicalRecord(e) {
e.preventDefault();

const reason = document.getElementById('reason').value;
const diagnosis = document.getElementById('diagnosis').value;
const treatment = document.getElementById('treatment').value;
const date = new Date().toISOString();

if (!reason || !diagnosis) {
showMessage("Motivo y diagnóstico son campos obligatorios.", 'error');
return;
}

try {
const clinicalHistoryRef = collection(db, `artifacts/${appId}/public/data/clinical_histories`);
await addDoc(clinicalHistoryRef, {
patientId: patientId,
date: date,
reason: reason,
diagnosis: diagnosis,
treatment: treatment
});

showMessage("¡Registro guardado exitosamente!", 'success');
recordForm.reset();
} catch (error) {
console.error("Error al guardar el registro:", error);
showMessage("Error al guardar el registro. Por favor, inténtelo de nuevo.", 'error');
}
}

// --- Event Listeners ---
recordForm.addEventListener('submit', addClinicalRecord);

</script>
</body>
</html>

He usado Firebase Firestore para la base de datos, ya que es la mejor opción para aplicaciones colaborativas como esta. El código incluye un onSnapshot que actualizará la lista de consultas en tiempo real a medida que agregues nuevos registros.

Este código es la base para el módulo. A partir de aquí, podrías integrar la información de los antecedentes médicos y el odontograma que ya desarrollaste, así como añadir un campo para "diagnóstico" y "plan de tratamiento".

¿Qué te parece? Si quieres, podemos seguir desarrollando el módulo de Turnos ahora que tenemos el esqueleto de la historia clínica.