CHATBOT_APPOINTMENTS_REFACTOR.md 12 KB

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

  • Modelo Prisma: fechaSolicitada ahora es DateTime?
  • Migración aplicada: 20251008165451_make_fecha_solicitada_optional
  • API actualizada: fechaSolicitada es opcional en POST
  • Tipos actualizados: CreateAppointmentInput sin fecha requerida

Frontend - Nuevo Componente

  • 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

  • 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)

interface Props {
  open: boolean;
  onClose: () => void;
  onSuccess?: () => void;
}

2. Props en Componentes

// MedicalAlert
interface MedicalAlertProps {
  alert: MedicalAlertType;
  className?: string;
  onAppointmentClick?: () => void; // Nuevo
}

// ChatMessage
interface ChatMessageProps {
  message: Message;
  onAppointmentClick?: () => void; // Nuevo
}

3. Estado en ChatInterface

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

  • Tipado TypeScript sin any
  • Modal se abre desde alerta en chat
  • Cita se crea sin fecha
  • Toast de éxito aparece
  • Modal se cierra correctamente
  • No hay errores en consola
  • Componente exportado en index.ts
  • OrderBy cambiado de fechaSolicitada a createdAt
  • AppointmentCard maneja fechas null correctamente
  • 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