|
@@ -1,298 +0,0 @@
|
|
|
-# Refactor: Agendamiento de Citas desde Chatbot
|
|
|
|
|
-
|
|
|
|
|
-## 🎯 Objetivo
|
|
|
|
|
-
|
|
|
|
|
-Cambiar el flujo de agendamiento para que las citas se creen desde el chatbot (sin fecha/hora específica) y el doctor asigne la fecha al aprobar.
|
|
|
|
|
-
|
|
|
|
|
-## 📋 Tareas
|
|
|
|
|
-
|
|
|
|
|
-###### 🎯 Flujo Actualizado
|
|
|
|
|
-1. **Paciente**: Chatbot recomienda → Modal → Crea cita SIN fecha
|
|
|
|
|
-2. **Doctor**: Ve cita pendiente → Aprobar → Selecciona fecha/hora → Confirma (SIN roomName)
|
|
|
|
|
-3. **Ambos**: Esperan hasta el horario asignado
|
|
|
|
|
-4. **15 min antes o durante**: Aparece botón "Unirse a Videollamada"
|
|
|
|
|
-5. **Al hacer click**: Se genera `roomName` y se redirige a sala Jitsi
|
|
|
|
|
-6. **Durante videollamada**: Doctor puede tomar notas médicas
|
|
|
|
|
-7. **Finalizar consulta**: Doctor guarda y comparte notas con el paciente
|
|
|
|
|
-8. **Después**: Paciente puede ver las notas en el detalle de la cita
|
|
|
|
|
-
|
|
|
|
|
-### 📝 Sistema de Notas de Consulta
|
|
|
|
|
-
|
|
|
|
|
-#### ✅ Implementado
|
|
|
|
|
-1. **Schema actualizado** (`prisma/schema.prisma`):
|
|
|
|
|
- - `notasConsulta`: String? - Texto de las notas médicas
|
|
|
|
|
- - `notasGuardadas`: Boolean - Si están compartidas con el paciente
|
|
|
|
|
- - `notasGuardadasAt`: DateTime? - Fecha de cuando se compartieron
|
|
|
|
|
-
|
|
|
|
|
-2. **API de notas** (`/api/appointments/[id]/consultation-notes`):
|
|
|
|
|
- - `GET`: Obtener notas (doctor ve siempre, paciente solo si están guardadas)
|
|
|
|
|
- - `POST`: Guardar notas (solo doctor)
|
|
|
|
|
- - `guardar: false` → Borrador privado
|
|
|
|
|
- - `guardar: true` → Compartidas con paciente
|
|
|
|
|
-
|
|
|
|
|
-3. **Componente `ConsultationNotes`**:
|
|
|
|
|
- - Vista doctor: Editor con 12 líneas, botones "Guardar Borrador" y "Guardar y Compartir"
|
|
|
|
|
- - Vista paciente: Muestra notas si están compartidas, mensaje de espera si no
|
|
|
|
|
- - Auto-carga notas existentes al montar
|
|
|
|
|
- - Feedback visual cuando están compartidas
|
|
|
|
|
-
|
|
|
|
|
-4. **Integración en `/meet`**:
|
|
|
|
|
- - Layout de 2 columnas en pantallas grandes (videollamada + notas)
|
|
|
|
|
- - Responsive: apila en móviles
|
|
|
|
|
- - Detección automática de rol (isDoctor)
|
|
|
|
|
- - **Modal de confirmación al salir**: Previene salidas accidentales
|
|
|
|
|
- - **Intercepta beforeunload**: Alerta al cerrar pestaña o navegar fuera
|
|
|
|
|
- - Recordatorio para guardar notas (solo doctores)
|
|
|
|
|
-
|
|
|
|
|
-5. **Visualización en detalle**:
|
|
|
|
|
- - Sección verde con las notas guardadas
|
|
|
|
|
- - Muestra fecha de guardado
|
|
|
|
|
- - Solo visible si `notasGuardadas === true`
|
|
|
|
|
- - **Citas completadas**: No muestra botón de videollamada, solo estado final
|
|
|
|
|
- - **Acciones para completadas**: Mensaje que indica que las notas están arriba
|
|
|
|
|
-
|
|
|
|
|
-#### 🎯 Flujo de Notas
|
|
|
|
|
-1. **Durante videollamada**: Doctor escribe notas en tiempo real
|
|
|
|
|
-2. **Guardar borrador**: Notas privadas, solo el doctor las ve
|
|
|
|
|
-3. **Guardar y compartir**: Notas visibles para el paciente
|
|
|
|
|
-4. **Después de la consulta**: Paciente puede ver las notas en detalle de la cita
|
|
|
|
|
-
|
|
|
|
|
-### 🔧 Próximos Pasos Opcionales
|
|
|
|
|
-- [x] Modelo Prisma: `fechaSolicitada` ahora es `DateTime?`
|
|
|
|
|
-- [x] Migración aplicada: `20251008165451_make_fecha_solicitada_optional`
|
|
|
|
|
-- [x] API actualizada: `fechaSolicitada` es opcional en POST
|
|
|
|
|
-- [x] Tipos actualizados: `CreateAppointmentInput` sin fecha requerida
|
|
|
|
|
-
|
|
|
|
|
-### Frontend - Nuevo Componente
|
|
|
|
|
-- [x] `AppointmentModalFromChat.tsx`
|
|
|
|
|
- - Modal simplificado dentro del chat
|
|
|
|
|
- - Solo pide: motivo de consulta
|
|
|
|
|
- - Llama a POST `/api/appointments` sin fecha
|
|
|
|
|
- - Toast de confirmación
|
|
|
|
|
-
|
|
|
|
|
-### Frontend - Modificar Componentes
|
|
|
|
|
-- [x] `MedicalAlert.tsx`
|
|
|
|
|
- - Cambiar botón Link por onClick handler
|
|
|
|
|
- - Agregar prop `onAppointmentClick?: () => void`
|
|
|
|
|
- - Mantener tipado estricto con `MedicalAlertType`
|
|
|
|
|
-
|
|
|
|
|
-- [x] `ChatMessage.tsx`
|
|
|
|
|
- - Agregar prop `onAppointmentClick?: () => void`
|
|
|
|
|
- - Pasar callback a `MedicalAlert`
|
|
|
|
|
-
|
|
|
|
|
-- [x] `ChatInterface.tsx`
|
|
|
|
|
- - Estado: `showAppointmentModal: boolean`
|
|
|
|
|
- - Handler: `handleOpenAppointmentModal`
|
|
|
|
|
- - Renderizar `AppointmentModalFromChat`
|
|
|
|
|
- - Pasar callback a `ChatMessages`
|
|
|
|
|
-
|
|
|
|
|
-- [x] `ChatMessages.tsx`
|
|
|
|
|
- - Pasar callback a `ChatMessage`
|
|
|
|
|
-
|
|
|
|
|
-- [x] `AppointmentCard.tsx`
|
|
|
|
|
- - Manejar `fechaSolicitada: null`
|
|
|
|
|
- - Mostrar "Fecha por asignar" cuando no hay fecha
|
|
|
|
|
-
|
|
|
|
|
-- [x] `/appointments/[id]/page.tsx`
|
|
|
|
|
- - Manejar `fechaSolicitada: null` en detalle
|
|
|
|
|
- - Mostrar mensaje contextual cuando no hay fecha
|
|
|
|
|
-
|
|
|
|
|
-### Tipos
|
|
|
|
|
-- [x] `appointments.ts`
|
|
|
|
|
- - `fechaSolicitada` ahora es `Date | string | null`
|
|
|
|
|
- - `CreateAppointmentInput.fechaSolicitada` ahora es opcional
|
|
|
|
|
-
|
|
|
|
|
-### Opcional
|
|
|
|
|
-- [ ] Ocultar botón "Nueva Cita" en `/appointments/page.tsx`
|
|
|
|
|
-- [ ] Actualizar `APPOINTMENTS_SYSTEM.md`
|
|
|
|
|
-
|
|
|
|
|
-## 🔧 Implementación
|
|
|
|
|
-
|
|
|
|
|
-### 1. Crear Modal (`AppointmentModalFromChat.tsx`)
|
|
|
|
|
-```tsx
|
|
|
|
|
-interface Props {
|
|
|
|
|
- open: boolean;
|
|
|
|
|
- onClose: () => void;
|
|
|
|
|
- onSuccess?: () => void;
|
|
|
|
|
-}
|
|
|
|
|
-```
|
|
|
|
|
-
|
|
|
|
|
-### 2. Props en Componentes
|
|
|
|
|
-```tsx
|
|
|
|
|
-// MedicalAlert
|
|
|
|
|
-interface MedicalAlertProps {
|
|
|
|
|
- alert: MedicalAlertType;
|
|
|
|
|
- className?: string;
|
|
|
|
|
- onAppointmentClick?: () => void; // Nuevo
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// ChatMessage
|
|
|
|
|
-interface ChatMessageProps {
|
|
|
|
|
- message: Message;
|
|
|
|
|
- onAppointmentClick?: () => void; // Nuevo
|
|
|
|
|
-}
|
|
|
|
|
-```
|
|
|
|
|
-
|
|
|
|
|
-### 3. Estado en ChatInterface
|
|
|
|
|
-```tsx
|
|
|
|
|
-const [showAppointmentModal, setShowAppointmentModal] = useState(false);
|
|
|
|
|
-
|
|
|
|
|
-const handleOpenAppointmentModal = () => {
|
|
|
|
|
- setShowAppointmentModal(true);
|
|
|
|
|
-};
|
|
|
|
|
-```
|
|
|
|
|
-
|
|
|
|
|
-## 📦 Componentes Reutilizados
|
|
|
|
|
-- `Dialog` de shadcn/ui
|
|
|
|
|
-- `Textarea` para motivo
|
|
|
|
|
-- `Button` para acciones
|
|
|
|
|
-- Lógica de API call de `useAppointments`
|
|
|
|
|
-
|
|
|
|
|
-## ✅ Verificación
|
|
|
|
|
-- [x] Tipado TypeScript sin `any`
|
|
|
|
|
-- [x] Modal se abre desde alerta en chat
|
|
|
|
|
-- [x] Cita se crea sin fecha
|
|
|
|
|
-- [x] Toast de éxito aparece
|
|
|
|
|
-- [x] Modal se cierra correctamente
|
|
|
|
|
-- [x] No hay errores en consola
|
|
|
|
|
-- [x] Componente exportado en index.ts
|
|
|
|
|
-- [x] OrderBy cambiado de `fechaSolicitada` a `createdAt`
|
|
|
|
|
-- [x] AppointmentCard maneja fechas null correctamente
|
|
|
|
|
-- [x] Página de detalle maneja fechas null correctamente
|
|
|
|
|
-
|
|
|
|
|
-## 🐛 Bugs Corregidos
|
|
|
|
|
-- ✅ **GET /api/appointments retornaba 401**: `orderBy` con campo nullable causaba error
|
|
|
|
|
- - **Solución**: Cambiar de `fechaSolicitada: "asc"` a `createdAt: "desc"`
|
|
|
|
|
- - **Afectados**: Médicos y pacientes al listar citas
|
|
|
|
|
-
|
|
|
|
|
-- ✅ **Usuarios UTB (sin email) recibían 401 en todas las APIs**
|
|
|
|
|
- - **Causa**: APIs buscaban usuario por `session.user.email` (vacío en usuarios UTB)
|
|
|
|
|
- - **Solución**: Cambiar a `session.user.id` en todas las APIs de appointments
|
|
|
|
|
- - **Archivos modificados**:
|
|
|
|
|
- - `/api/appointments/route.ts` (GET, POST)
|
|
|
|
|
- - `/api/appointments/[id]/route.ts` (GET, PATCH)
|
|
|
|
|
- - `/api/appointments/[id]/approve/route.ts` (POST)
|
|
|
|
|
- - `/api/appointments/[id]/reject/route.ts` (POST)
|
|
|
|
|
- - `/api/appointments/[id]/complete/route.ts` (POST)
|
|
|
|
|
-
|
|
|
|
|
-- ✅ **Médicos recibían 403 al ver detalles de citas pendientes**
|
|
|
|
|
- - **Causa**: Validación solo permitía acceso si `medicoId === user.id`, pero citas pendientes tienen `medicoId: null`
|
|
|
|
|
- - **Solución**: Agregar condición para que cualquier médico pueda ver citas PENDIENTES sin médico asignado
|
|
|
|
|
- - **Archivo**: `/api/appointments/[id]/route.ts` (GET)
|
|
|
|
|
-
|
|
|
|
|
-## 🚀 Resultado Final
|
|
|
|
|
-
|
|
|
|
|
-**Antes:** Chat → Link → `/appointments` → Form con fecha → Submit
|
|
|
|
|
-
|
|
|
|
|
-**Después:** Chat → Click botón → Modal inline → Solo motivo → Submit → Doctor asigna fecha
|
|
|
|
|
-
|
|
|
|
|
-## 📝 Resumen de Implementación
|
|
|
|
|
-
|
|
|
|
|
-### ✅ Completado
|
|
|
|
|
-1. **Nuevo componente**: `AppointmentModalFromChat.tsx`
|
|
|
|
|
- - Modal simplificado con solo motivo de consulta
|
|
|
|
|
- - Llama al API sin `fechaSolicitada`
|
|
|
|
|
- - Toast de confirmación
|
|
|
|
|
- - Tipado estricto sin `any`
|
|
|
|
|
-
|
|
|
|
|
-2. **Componentes modificados**:
|
|
|
|
|
- - `MedicalAlert.tsx`: Botón con onClick en lugar de Link
|
|
|
|
|
- - `ChatMessage.tsx`: Prop drilling del callback
|
|
|
|
|
- - `ChatMessages.tsx`: Prop drilling del callback
|
|
|
|
|
- - `ChatInterface.tsx`: Estado del modal y handlers
|
|
|
|
|
- - `index.ts`: Export del nuevo componente
|
|
|
|
|
-
|
|
|
|
|
-3. **Backend**:
|
|
|
|
|
- - **Schema**: `fechaSolicitada DateTime?` (nullable)
|
|
|
|
|
- - **Migración**: `20251008165451_make_fecha_solicitada_optional`
|
|
|
|
|
- - **API**: Validación solo requiere `motivoConsulta`
|
|
|
|
|
- - **Sin `any`**: Uso de spread operator condicional
|
|
|
|
|
-
|
|
|
|
|
-4. **Tipos actualizados**:
|
|
|
|
|
- - `Appointment.fechaSolicitada`: `Date | string | null`
|
|
|
|
|
- - `CreateAppointmentInput.fechaSolicitada`: `Date` (opcional)
|
|
|
|
|
-
|
|
|
|
|
-### 🎯 Funcionamiento
|
|
|
|
|
-- Cuando el chatbot detecta `RECOMENDADO` o `URGENTE`
|
|
|
|
|
-- El usuario hace clic en "Agendar Cita" dentro del chat
|
|
|
|
|
-- Se abre modal que solo pide motivo de consulta
|
|
|
|
|
-- La cita se crea como `PENDIENTE` sin fecha específica
|
|
|
|
|
-- El doctor revisa y asigna fecha al aprobar
|
|
|
|
|
-
|
|
|
|
|
-## 🆕 Flujo de Aprobación con Fecha
|
|
|
|
|
-
|
|
|
|
|
-### ✅ Implementado
|
|
|
|
|
-1. **Nuevo componente**: `ApproveAppointmentModal.tsx`
|
|
|
|
|
- - Modal con DateTimePicker para seleccionar fecha/hora
|
|
|
|
|
- - Campo opcional de notas para el doctor
|
|
|
|
|
- - Validación de fecha futura
|
|
|
|
|
- - Integración completa con el API
|
|
|
|
|
-
|
|
|
|
|
-2. **DateTimePicker mejorado**:
|
|
|
|
|
- - Convertido a componente controlado con props `date` y `setDate`
|
|
|
|
|
- - Lógica corregida para formato 12 horas con AM/PM
|
|
|
|
|
- - Mejor highlighting de hora seleccionada
|
|
|
|
|
- - Preserva hora al cambiar fecha
|
|
|
|
|
-
|
|
|
|
|
-3. **API actualizado** (`/api/appointments/[id]/approve`):
|
|
|
|
|
- - Ahora **requiere** `fechaSolicitada` en el body
|
|
|
|
|
- - Valida que la fecha sea futura
|
|
|
|
|
- - Actualiza cita con fecha, estado APROBADA y medicoId
|
|
|
|
|
-
|
|
|
|
|
-4. **Página de detalle modificada** (`/appointments/[id]/page.tsx`):
|
|
|
|
|
- - Botón "Aprobar Cita" abre modal con selector de fecha
|
|
|
|
|
- - Handler `handleApprove` recibe fecha y notas
|
|
|
|
|
- - Envía datos completos al API
|
|
|
|
|
- - Actualiza UI tras aprobación exitosa
|
|
|
|
|
-
|
|
|
|
|
-### 🎯 Flujo Completo
|
|
|
|
|
-1. **Paciente**: Chatbot recomienda cita → Modal inline → Crea cita sin fecha
|
|
|
|
|
-2. **Doctor**: Ve cita pendiente → Click "Aprobar" → Selecciona fecha/hora en modal → Confirma
|
|
|
|
|
-3. **Sistema**: Actualiza cita con fecha y estado APROBADA → Notifica paciente
|
|
|
|
|
-
|
|
|
|
|
-### ✅ Correcciones Finales
|
|
|
|
|
-- **Hook `useAppointments`**: Actualizado para requerir `fechaSolicitada` en `approveAppointment`
|
|
|
|
|
-- **Página de listado doctor**: Ahora usa `ApproveAppointmentModal` en lugar de aprobar directamente
|
|
|
|
|
-- **DateTimePicker**: Aumentado z-index a `9999` para que aparezca sobre el Dialog
|
|
|
|
|
-- **Modal de aprobación**: Ancho aumentado a `700px`, `modal={false}` para permitir interacción con Popover, overlay manual para mantener opacidad
|
|
|
|
|
-
|
|
|
|
|
-### 🎥 Sistema de Videollamadas con Horario
|
|
|
|
|
-
|
|
|
|
|
-#### ✅ Implementado
|
|
|
|
|
-1. **API de aprobación modificada** (`/api/appointments/[id]/approve`):
|
|
|
|
|
- - Ya NO genera `roomName` al aprobar
|
|
|
|
|
- - Solo asigna fecha, estado APROBADA y medicoId
|
|
|
|
|
-
|
|
|
|
|
-2. **Nuevo endpoint** (`/api/appointments/[id]/start-meeting`):
|
|
|
|
|
- - Valida que sea el momento correcto (15 min antes hasta 1 hora después)
|
|
|
|
|
- - Genera `roomName` solo cuando se inicia la videollamada
|
|
|
|
|
- - Retorna error con mensaje si es muy temprano o muy tarde
|
|
|
|
|
- - Si ya existe `roomName`, lo reutiliza
|
|
|
|
|
-
|
|
|
|
|
-3. **Utilidades de tiempo** (`/utils/appointments.ts`):
|
|
|
|
|
- - `canJoinMeeting()`: Valida si es tiempo de unirse
|
|
|
|
|
- - `getAppointmentTimeStatus()`: Retorna mensaje de estado ("En 30 minutos", "Puedes unirte ahora", etc.)
|
|
|
|
|
-
|
|
|
|
|
-4. **UI en página de detalle**:
|
|
|
|
|
- - Botón "Unirse a Videollamada" solo aparece cuando es tiempo
|
|
|
|
|
- - Botón deshabilitado muestra tiempo restante si es muy temprano
|
|
|
|
|
- - Mensaje de error si el tiempo límite expiró (1 hora después)
|
|
|
|
|
- - Funciona para médicos y pacientes
|
|
|
|
|
-
|
|
|
|
|
-#### 🕐 Reglas de Tiempo
|
|
|
|
|
-- **15 minutos antes**: Usuarios pueden unirse
|
|
|
|
|
-- **Durante la cita**: Usuarios pueden unirse
|
|
|
|
|
-- **Hasta 1 hora después**: Usuarios pueden unirse
|
|
|
|
|
-- **Después de 1 hora**: La videollamada ya no está disponible
|
|
|
|
|
-
|
|
|
|
|
-#### 🎯 Flujo Actualizado
|
|
|
|
|
-1. **Paciente**: Chatbot recomienda → Modal → Crea cita SIN fecha
|
|
|
|
|
-2. **Doctor**: Ve cita pendiente → Aprobar → Selecciona fecha/hora → Confirma (SIN roomName)
|
|
|
|
|
-3. **Ambos**: Esperan hasta el horario asignado
|
|
|
|
|
-4. **15 min antes o durante**: Aparece botón "Unirse a Videollamada"
|
|
|
|
|
-5. **Al hacer click**: Se genera `roomName` y se redirige a sala Jitsi
|
|
|
|
|
-6. **Después de 1 hora**: Ya no se puede unir
|
|
|
|
|
-
|
|
|
|
|
-### 🔧 Próximos Pasos Opcionales
|
|
|
|
|
-- [ ] Ocultar botón "Nueva Cita" en `/appointments/page.tsx`
|
|
|
|
|
-- [ ] Actualizar `APPOINTMENTS_SYSTEM.md` con nuevo flujo
|
|
|