# 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