Matthew Trejo 2 miesięcy temu
rodzic
commit
73556c5baf

+ 98 - 0
docs/CHAT_MEDICAL_ALERT_BANNER.md

@@ -0,0 +1,98 @@
+# Chat Medical Alert Banner - Implementation
+
+## Objetivo
+Mostrar banner persistente cuando chatbot detecta `RECOMENDADO` o `URGENTE`, permitiendo agendar cita con reporte generado automáticamente.
+
+## Flujo Nuevo
+```
+Mensaje con RECOMENDADO/URGENTE
+→ Banner sticky top: "⚠️ Consulta [recomendada/urgente]"
+→ [Agendar Cita Ahora] [Continuar Conversación]
+→ Si Agendar: genera reporte + abre modal + asocia reportId
+→ Si Continuar: banner permanece visible
+→ Al mensaje 3 o agendar: fin + reporte
+```
+
+## Tasks
+
+### ✅ Completadas
+
+#### 1. Nuevo Componente: `MedicalAlertBanner.tsx`
+- ✅ Crear `/src/components/chatbot/MedicalAlertBanner.tsx`
+- ✅ Props: `alert`, `onSchedule`, `onDismiss`, `isScheduling`
+- ✅ Sticky top, colores según alert type
+- ✅ Botones: "Agendar Cita Ahora" + "X" (dismiss)
+
+#### 2. Modificar `useChat.ts`
+- ✅ State: `medicalAlertDetected: MedicalAlert | null`
+- ✅ State: `showMedicalAlertBanner: boolean`
+- ✅ State: `isSchedulingFromAlert: boolean`
+- ✅ Logic: detectar cuando `response.medicalAlert !== "NO_AGENDAR"`
+- ✅ Función: `handleScheduleFromAlert(callback)` → genera reporte + callback con reportId
+- ✅ Función: `dismissMedicalAlertBanner()`
+- ✅ Reset states al limpiar chat
+
+#### 3. Modificar `ChatInterface.tsx`
+- ✅ Import `MedicalAlertBanner`
+- ✅ State: `appointmentReportId`
+- ✅ Renderizar banner después de `ChatHeader`
+- ✅ Handler: `handleScheduleFromAlertClick()` → genera reporte y abre modal
+- ✅ Pasar `reportId` al `AppointmentModalFromChat`
+
+#### 4. Modificar `ChatMessage.tsx`
+- ✅ Remover import de `MedicalAlert`
+- ✅ Quitar render de `<MedicalAlert />` individual
+- ✅ Solo mostrar texto de respuesta
+
+#### 5. Modificar `AppointmentModalFromChat.tsx`
+- ✅ Prop: `reportId?: string`
+- ✅ Incluir `reportId` en payload al crear cita
+
+#### 6. API: `/api/appointments/route.ts`
+- ✅ Agregar campo `reportId` opcional en body
+- ✅ Asociar en Prisma create con spread operator
+
+#### 7. Schema Prisma
+- ✅ Agregar campo `reportId String?` a `Appointment`
+- ✅ Migration ejecutada: `20251013144104_add_report_id_to_appointments`
+
+#### 8. Testing
+- ✅ Dev server corriendo sin errores
+- 🔄 Pendiente testing manual del flujo completo
+
+### 🔄 En Progreso
+Ninguna
+
+### 📋 Pendientes Testing Manual
+- [ ] Enviar mensaje que genere alerta RECOMENDADO
+- [ ] Verificar que banner aparezca sticky top
+- [ ] Click en "Agendar Cita Ahora"
+- [ ] Verificar que se genera reporte
+- [ ] Verificar que modal se abre con reportId
+- [ ] Crear cita y verificar que reportId se guarda en DB
+- [ ] Probar dismiss del banner
+- [ ] Probar con alerta URGENTE
+- [ ] Verificar que banner persiste entre mensajes
+
+## Archivos a Modificar
+```
+NEW: src/components/chatbot/MedicalAlertBanner.tsx
+MOD: src/hooks/useChat.ts
+MOD: src/components/chatbot/ChatInterface.tsx
+MOD: src/components/chatbot/ChatMessage.tsx
+MOD: src/components/chatbot/AppointmentModalFromChat.tsx
+MOD: src/app/api/appointments/route.ts
+CHK: prisma/schema.prisma
+```
+
+## Notas de Implementación
+- Banner NO usa toast, es componente persistente
+- Dismiss temporal: solo oculta UI, state se mantiene
+- Al agendar desde banner: misma lógica que reset con reporte
+- ReportId se pasa al modal, no se crea nueva cita sin contexto
+
+## Decisiones de Diseño
+- Banner sticky top (no dentro de mensajes)
+- Colores: amarillo (RECOMENDADO), rojo (URGENTE)
+- Solo aparece una vez por sesión de chat
+- Al generar reporte, se asocia automáticamente a cita

+ 2 - 0
prisma/migrations/20251013144104_add_report_id_to_appointments/migration.sql

@@ -0,0 +1,2 @@
+-- AlterTable
+ALTER TABLE "Appointment" ADD COLUMN     "reportId" TEXT;

+ 1 - 0
prisma/schema.prisma

@@ -73,6 +73,7 @@ model Appointment {
   medico          User?     @relation("DoctorAppointments", fields: [medicoId], references: [id], onDelete: SetNull)
   recordId        String?   @unique
   record          Record?   @relation(fields: [recordId], references: [id], onDelete: SetNull)
+  reportId        String?
   
   // Info de la cita
   fechaSolicitada DateTime?

+ 2 - 1
src/app/api/appointments/route.ts

@@ -148,7 +148,7 @@ export async function POST(request: NextRequest) {
     }
 
     const body = await request.json();
-    const { fechaSolicitada, motivoConsulta, recordId } = body;
+    const { fechaSolicitada, motivoConsulta, recordId, reportId } = body;
 
     // Solo motivoConsulta es requerido ahora
     if (!motivoConsulta) {
@@ -175,6 +175,7 @@ export async function POST(request: NextRequest) {
         motivoConsulta,
         estado: "PENDIENTE",
         ...(recordId && { recordId }),
+        ...(reportId && { reportId }),
         ...(fechaSolicitada && { fechaSolicitada: new Date(fechaSolicitada) }),
       },
       include: {

+ 23 - 12
src/app/auth/login/page.tsx

@@ -107,18 +107,29 @@ export default function LoginPage() {
                 required
               />
             </div>
-            <Button 
-              type="submit" 
-              className="w-full" 
-              disabled={loading}
-            >
-              {loading ? (
-                <div className="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin mr-2"></div>
-              ) : (
-                <Lock className="w-4 h-4 mr-2" />
-              )}
-              Iniciar Sesión
-            </Button>
+            <div className="space-y-2">
+              <Button 
+                type="submit" 
+                className="w-full" 
+                disabled={loading}
+              >
+                {loading ? (
+                  <div className="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin mr-2"></div>
+                ) : (
+                  <Lock className="w-4 h-4 mr-2" />
+                )}
+                Iniciar Sesión
+              </Button>
+              <Button 
+                type="button" 
+                variant="outline"
+                className="w-full" 
+                onClick={() => router.push("/")}
+                disabled={loading}
+              >
+                Cancelar
+              </Button>
+            </div>
           </form>
           
           <div className="mt-6 text-center">

+ 0 - 2
src/app/page.tsx

@@ -4,7 +4,6 @@ import Footer from "@/components/Footer";
 import Header from "@/components/landing/Header";
 import HeroSection from "@/components/landing/HeroSection";
 import HowItWorksSection from "@/components/landing/HowItWorksSection";
-import ServicesSection from "@/components/landing/ServicesSection";
 import CTASection from "@/components/landing/CTASection";
 
 export default function HomePage() {
@@ -13,7 +12,6 @@ export default function HomePage() {
       <Header />
       <HeroSection />
       <HowItWorksSection />
-      <ServicesSection />
       <CTASection />
       <Footer variant="landing" />
     </div>

+ 3 - 0
src/components/chatbot/AppointmentModalFromChat.tsx

@@ -19,12 +19,14 @@ interface AppointmentModalFromChatProps {
   open: boolean;
   onClose: () => void;
   onSuccess?: () => void;
+  reportId?: string;
 }
 
 export const AppointmentModalFromChat = ({
   open,
   onClose,
   onSuccess,
+  reportId,
 }: AppointmentModalFromChatProps) => {
   const [motivoConsulta, setMotivoConsulta] = useState("");
   const [isSubmitting, setIsSubmitting] = useState(false);
@@ -44,6 +46,7 @@ export const AppointmentModalFromChat = ({
         headers: { "Content-Type": "application/json" },
         body: JSON.stringify({
           motivoConsulta: motivoConsulta.trim(),
+          reportId: reportId || undefined,
           // No enviamos fechaSolicitada - el doctor la asignará
         }),
       });

+ 26 - 0
src/components/chatbot/ChatInterface.tsx

@@ -15,6 +15,7 @@ import { ResetButton } from "./ResetButton";
 import { ReportModal } from "./ReportModal";
 import { ResetConfirmationModal } from "./ResetConfirmationModal";
 import { AppointmentModalFromChat } from "./AppointmentModalFromChat";
+import { MedicalAlertBanner } from "./MedicalAlertBanner";
 
 const MAX_MESSAGES = 3;
 
@@ -25,6 +26,7 @@ export const ChatInterface = () => {
   const [showLastMessageToast, setShowLastMessageToast] = useState(false);
   const [isResetting, setIsResetting] = useState(false);
   const [showAppointmentModal, setShowAppointmentModal] = useState(false);
+  const [appointmentReportId, setAppointmentReportId] = useState<string | undefined>();
 
   const {
     messages,
@@ -50,6 +52,11 @@ export const ChatInterface = () => {
     setShowDynamicSuggestions,
     inputDisabledForSuggestions,
     setInputDisabledForSuggestions,
+    medicalAlertDetected,
+    showMedicalAlertBanner,
+    isSchedulingFromAlert,
+    handleScheduleFromAlert,
+    dismissMedicalAlertBanner,
   } = useChat();
 
   // Usar efectos del chat
@@ -108,6 +115,14 @@ export const ChatInterface = () => {
 
   const handleCloseAppointmentModal = () => {
     setShowAppointmentModal(false);
+    setAppointmentReportId(undefined);
+  };
+
+  const handleScheduleFromAlertClick = () => {
+    handleScheduleFromAlert((reportId) => {
+      setAppointmentReportId(reportId);
+      setShowAppointmentModal(true);
+    });
   };
 
   if (!session) {
@@ -133,6 +148,16 @@ export const ChatInterface = () => {
             onResetClick={handleResetClick}
           />
 
+          {/* Medical Alert Banner */}
+          {showMedicalAlertBanner && medicalAlertDetected && (
+            <MedicalAlertBanner
+              alert={medicalAlertDetected}
+              onSchedule={handleScheduleFromAlertClick}
+              onDismiss={dismissMedicalAlertBanner}
+              isScheduling={isSchedulingFromAlert}
+            />
+          )}
+
           <div className="flex flex-col min-h-[70vh]">
             {/* Chat Content */}
             <div className="flex-1 overflow-hidden">
@@ -215,6 +240,7 @@ export const ChatInterface = () => {
       <AppointmentModalFromChat
         open={showAppointmentModal}
         onClose={handleCloseAppointmentModal}
+        reportId={appointmentReportId}
       />
     </div>
   );

+ 0 - 11
src/components/chatbot/ChatMessage.tsx

@@ -1,7 +1,6 @@
 import { User, Bot } from "lucide-react";
 import { Message } from "./types";
 import { ReactMarkdownRenderer } from "@/utils/markdown";
-import { MedicalAlert } from "./MedicalAlert";
 
 interface ChatMessageProps {
   message: Message;
@@ -71,16 +70,6 @@ export const ChatMessage = ({ message, onAppointmentClick }: ChatMessageProps) =
             <p>{message.content}</p>
           )}
         </div>
-        
-        {/* Mostrar alerta médica solo para mensajes del asistente que no están en streaming */}
-        {message.role === "assistant" && message.medicalAlert && !message.isStreaming && (
-          <div className="mt-3">
-            <MedicalAlert 
-              alert={message.medicalAlert}
-              onAppointmentClick={onAppointmentClick}
-            />
-          </div>
-        )}
       </div>
     </div>
   );

+ 105 - 0
src/components/chatbot/MedicalAlertBanner.tsx

@@ -0,0 +1,105 @@
+"use client";
+
+import { AlertTriangle, Clock, Calendar, X } from "lucide-react";
+import { Button } from "@/components/ui/button";
+import { MedicalAlert as MedicalAlertType } from "./types";
+import { cn } from "@/lib/utils";
+
+interface MedicalAlertBannerProps {
+  alert: MedicalAlertType;
+  onSchedule: () => void;
+  onDismiss?: () => void;
+  isScheduling?: boolean;
+}
+
+const alertConfig = {
+  RECOMENDADO: {
+    icon: Clock,
+    title: "Consulta Médica Recomendada",
+    description: "Basado en tu consulta, recomendamos agendar una cita con un profesional médico.",
+    className: "bg-yellow-50 border-yellow-200",
+    iconClassName: "text-yellow-600 bg-yellow-100",
+    buttonVariant: "default" as const,
+  },
+  URGENTE: {
+    icon: AlertTriangle,
+    title: "Atención Médica Urgente",
+    description: "Tu consulta requiere atención médica inmediata. Por favor, agenda una cita lo antes posible.",
+    className: "bg-red-50 border-red-200",
+    iconClassName: "text-red-600 bg-red-100",
+    buttonVariant: "destructive" as const,
+  },
+};
+
+export const MedicalAlertBanner = ({
+  alert,
+  onSchedule,
+  onDismiss,
+  isScheduling,
+}: MedicalAlertBannerProps) => {
+  // Solo mostrar para alertas médicas
+  if (alert === "NO_AGENDAR") return null;
+
+  const config = alertConfig[alert];
+  const Icon = config.icon;
+
+  return (
+    <div
+      className={cn(
+        "sticky top-0 z-10 border-b mb-4 p-4 relative",
+        config.className
+      )}
+    >
+      {onDismiss && (
+        <Button
+          variant="ghost"
+          size="sm"
+          className="absolute top-2 right-2 h-6 w-6 p-0 hover:bg-background/50"
+          onClick={onDismiss}
+        >
+          <X className="w-4 h-4" />
+        </Button>
+      )}
+
+      <div className="flex items-center gap-4 pr-8">
+        <div
+          className={cn(
+            "w-10 h-10 rounded-full flex items-center justify-center flex-shrink-0",
+            config.iconClassName
+          )}
+        >
+          <Icon className="w-5 h-5" />
+        </div>
+
+        <div className="flex-1">
+          <h3 className="font-semibold text-foreground text-sm">
+            {config.title}
+          </h3>
+          <p className="text-muted-foreground text-xs mt-1">
+            {config.description}
+          </p>
+        </div>
+
+        <Button
+          onClick={onSchedule}
+          variant={config.buttonVariant}
+          size="sm"
+          disabled={isScheduling}
+          className="flex-shrink-0"
+        >
+          {isScheduling ? (
+            <>
+              <div className="w-4 h-4 border-2 border-current border-t-transparent rounded-full animate-spin mr-2"></div>
+              Procesando...
+            </>
+          ) : (
+            <>
+              <Calendar className="h-4 w-4 mr-2" />
+              Agendar Cita Ahora
+            </>
+          )}
+        </Button>
+      </div>
+    </div>
+  );
+};

+ 1 - 1
src/components/landing/CTASection.tsx

@@ -13,7 +13,7 @@ export default function CTASection() {
           Tu salud es nuestra prioridad
         </h2>
         <p className="text-xl text-primary-foreground/70 max-w-2xl mx-auto mb-8">
-          Únete a miles de pacientes que ya confían en Ani Assistant para el
+          Únete a la lista de estudiantes que ya confían en Ani Assistant para el
           cuidado de su salud
         </p>
         <Link href="/auth/login">

+ 1 - 1
src/components/landing/ChatPreview.tsx

@@ -179,7 +179,7 @@ export default function ChatPreview() {
         </CardContent>
       </Card>
 
-      <Badge className="absolute -bottom-3 left-1/2 transform -translate-x-1/2 bg-primary/10 text-primary border-primary/20">
+      <Badge className="absolute -bottom-3 left-1/2 transform -translate-x-1/2 bg-primary/10 text-primary border-primary/20 hover:bg-primary/10">
         Vista previa del chat
       </Badge>
     </div>

+ 0 - 21
src/components/landing/Header.tsx

@@ -18,27 +18,6 @@ export default function Header() {
             </span>
           </div>
 
-          <nav className="hidden md:flex items-center space-x-8">
-            <a
-              href="#inicio"
-              className="text-muted-foreground hover:text-primary transition-colors"
-            >
-              Inicio
-            </a>
-            <a
-              href="#servicios"
-              className="text-muted-foreground hover:text-primary transition-colors"
-            >
-              Servicios
-            </a>
-            <a
-              href="#contacto"
-              className="text-muted-foreground hover:text-primary transition-colors"
-            >
-              Contacto
-            </a>
-          </nav>
-
           <Link href="/auth/login">
             <Button className="bg-primary hover:bg-primary/90 text-primary-foreground">
               Ingresar al Sistema

+ 1 - 30
src/components/landing/HeroSection.tsx

@@ -63,40 +63,11 @@ export default function HeroSection() {
                 </span>
               </div>
             </div>
-
-            <div className="flex flex-col sm:flex-row gap-4">
-              <Link href="/auth/login">
-                <Button
-                  size="lg"
-                  className="bg-primary hover:bg-primary/90 text-primary-foreground"
-                >
-                  <MessageCircle className="mr-2 h-5 w-5" />
-                  Iniciar Pre-consulta
-                </Button>
-              </Link>
-              <Link href="/auth/login">
-                <Button
-                  size="lg"
-                  variant="outline"
-                  className="border-primary text-primary hover:bg-primary/5 bg-transparent"
-                >
-                  <Calendar className="mr-2 h-5 w-5" />
-                  Agendar Cita
-                </Button>
-              </Link>
-            </div>
           </div>
         </div>
 
         {/* Stats */}
-        <div className="grid grid-cols-1 md:grid-cols-3 gap-8 mt-20">
-          <div className="text-center">
-            <div className="bg-card p-6 rounded-xl shadow-sm border border-border">
-              <Users className="h-12 w-12 text-primary mx-auto mb-4" />
-              <h3 className="text-3xl font-bold text-foreground">10,000+</h3>
-              <p className="text-muted-foreground">Pacientes Atendidos</p>
-            </div>
-          </div>
+        <div className="grid grid-cols-1 md:grid-cols-2 gap-8 mt-20">
           <div className="text-center">
             <div className="bg-card p-6 rounded-xl shadow-sm border border-border">
               <Clock className="h-12 w-12 text-primary mx-auto mb-4" />

+ 3 - 45
src/components/landing/HowItWorksSection.tsx

@@ -27,7 +27,7 @@ export default function HowItWorksSection() {
           </p>
         </div>
 
-        <div className="grid grid-cols-1 md:grid-cols-3 gap-8 mb-16">
+        <div className="grid grid-cols-1 md:grid-cols-2 gap-8 mb-16">
           {/* Paso 1 */}
           <div className="text-center">
             <div className="relative mb-6">
@@ -62,26 +62,7 @@ export default function HowItWorksSection() {
             </h3>
             <p className="text-muted-foreground">
               Basándose en tu evaluación inicial, te conectamos con el
-              especialista adecuado y agendas tu cita
-            </p>
-          </div>
-
-          {/* Paso 3 */}
-          <div className="text-center">
-            <div className="relative mb-6">
-              <div className="bg-primary w-16 h-16 rounded-full flex items-center justify-center mx-auto mb-4">
-                <Activity className="h-8 w-8 text-primary-foreground" />
-              </div>
-              <Badge className="absolute -top-2 -right-2 bg-primary/10 text-primary text-xs">
-                3
-              </Badge>
-            </div>
-            <h3 className="text-xl font-semibold text-foreground mb-3">
-              Seguimiento
-            </h3>
-            <p className="text-muted-foreground">
-              Recibe seguimiento personalizado, recordatorios de medicamentos
-              y monitoreo continuo de tu salud
+              especialista y agendas tu cita
             </p>
           </div>
         </div>
@@ -94,7 +75,7 @@ export default function HowItWorksSection() {
             </h3>
           </div>
 
-          <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
+          <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
             <div className="text-center">
               <div className="bg-card w-12 h-12 rounded-lg flex items-center justify-center mx-auto mb-3 shadow-sm">
                 <Shield className="h-6 w-6 text-primary" />
@@ -119,18 +100,6 @@ export default function HowItWorksSection() {
               </p>
             </div>
 
-            <div className="text-center">
-              <div className="bg-card w-12 h-12 rounded-lg flex items-center justify-center mx-auto mb-3 shadow-sm">
-                <Users className="h-6 w-6 text-primary" />
-              </div>
-              <h4 className="font-semibold text-foreground mb-2">
-                Especialistas Certificados
-              </h4>
-              <p className="text-sm text-muted-foreground">
-                Red de médicos profesionales y especializados
-              </p>
-            </div>
-
             <div className="text-center">
               <div className="bg-card w-12 h-12 rounded-lg flex items-center justify-center mx-auto mb-3 shadow-sm">
                 <Heart className="h-6 w-6 text-primary" />
@@ -143,17 +112,6 @@ export default function HowItWorksSection() {
               </p>
             </div>
           </div>
-
-          <div className="text-center mt-8">
-            <Link href="/auth/login">
-              <Button
-                size="lg"
-                className="bg-primary hover:bg-primary/90 text-primary-foreground"
-              >
-                Comenzar mi Evaluación
-              </Button>
-            </Link>
-          </div>
         </div>
       </div>
     </section>

+ 0 - 111
src/components/landing/ServicesSection.tsx

@@ -1,111 +0,0 @@
-"use client";
-
-import Link from "next/link";
-import { Button } from "@/components/ui/button";
-import {
-  Card,
-  CardContent,
-  CardDescription,
-  CardHeader,
-  CardTitle,
-} from "@/components/ui/card";
-import { MessageCircle, Calendar, Activity } from "lucide-react";
-
-export default function ServicesSection() {
-  return (
-    <section id="servicios" className="py-20 bg-muted">
-      <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
-        <div className="text-center mb-16">
-          <h2 className="text-3xl md:text-4xl font-bold text-foreground mb-4">
-            Nuestros Servicios
-          </h2>
-          <p className="text-xl text-muted-foreground max-w-2xl mx-auto">
-            Ofrecemos una gama completa de servicios de telemedicina para
-            cuidar tu salud
-          </p>
-        </div>
-
-        <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
-          {/* Pre-consulta */}
-          <Card className="hover:shadow-lg transition-shadow border border-border">
-            <CardHeader>
-              <div className="bg-primary/10 w-12 h-12 rounded-lg flex items-center justify-center mb-4">
-                <MessageCircle className="h-6 w-6 text-primary" />
-              </div>
-              <CardTitle className="text-xl">Pre-consulta con IA</CardTitle>
-              <CardDescription>
-                Evaluación inicial de síntomas con nuestro asistente
-                inteligente
-              </CardDescription>
-            </CardHeader>
-            <CardContent>
-              <ul className="space-y-2 text-sm text-muted-foreground">
-                <li>• Análisis de síntomas 24/7</li>
-                <li>• Recomendaciones personalizadas</li>
-                <li>• Triaje médico inteligente</li>
-                <li>• Historial de consultas</li>
-              </ul>
-              <Link href="/auth/login">
-                <Button className="w-full mt-4 bg-primary hover:bg-primary/90">
-                  Iniciar Pre-consulta
-                </Button>
-              </Link>
-            </CardContent>
-          </Card>
-
-          {/* Agendar Citas */}
-          <Card className="hover:shadow-lg transition-shadow border border-border">
-            <CardHeader>
-              <div className="bg-primary/10 w-12 h-12 rounded-lg flex items-center justify-center mb-4">
-                <Calendar className="h-6 w-6 text-primary" />
-              </div>
-              <CardTitle className="text-xl">Agendar Citas</CardTitle>
-              <CardDescription>
-                Programa consultas médicas con especialistas certificados
-              </CardDescription>
-            </CardHeader>
-            <CardContent>
-              <ul className="space-y-2 text-sm text-muted-foreground">
-                <li>• Agenda en tiempo real</li>
-                <li>• Múltiples especialidades</li>
-                <li>• Recordatorios automáticos</li>
-                <li>• Reagendamiento fácil</li>
-              </ul>
-              <Link href="/auth/login">
-                <Button className="w-full mt-4 bg-primary hover:bg-primary/90">
-                  Agendar Cita
-                </Button>
-              </Link>
-            </CardContent>
-          </Card>
-
-          {/* Seguimiento Médico */}
-          <Card className="hover:shadow-lg transition-shadow border border-border">
-            <CardHeader>
-              <div className="bg-primary/10 w-12 h-12 rounded-lg flex items-center justify-center mb-4">
-                <Activity className="h-6 w-6 text-primary" />
-              </div>
-              <CardTitle className="text-xl">Seguimiento Médico</CardTitle>
-              <CardDescription>
-                Monitoreo continuo de tu salud y tratamientos
-              </CardDescription>
-            </CardHeader>
-            <CardContent>
-              <ul className="space-y-2 text-sm text-muted-foreground">
-                <li>• Seguimiento de tratamientos</li>
-                <li>• Recordatorios de medicamentos</li>
-                <li>• Monitoreo de signos vitales</li>
-                <li>• Reportes de progreso</li>
-              </ul>
-              <Link href="/auth/login">
-                <Button className="w-full mt-4 bg-primary hover:bg-primary/90">
-                  Ver Seguimiento
-                </Button>
-              </Link>
-            </CardContent>
-          </Card>
-        </div>
-      </div>
-    </section>
-  );
-}

+ 61 - 1
src/hooks/useChat.ts

@@ -1,7 +1,7 @@
 import { useState, useEffect, useCallback } from "react";
 import { notifications } from "@/lib/notifications";
 import { generateReportFromMessages } from "@/utils/reports";
-import { Message, ChatState, ChatResponse, SuggestedPrompt } from "@/components/chatbot/types";
+import { Message, ChatState, ChatResponse, SuggestedPrompt, MedicalAlert } from "@/components/chatbot/types";
 
 const MAX_MESSAGES = 3;
 
@@ -19,6 +19,9 @@ export const useChat = () => {
   const [currentSuggestions, setCurrentSuggestions] = useState<SuggestedPrompt[]>([]);
   const [showDynamicSuggestions, setShowDynamicSuggestions] = useState(false);
   const [inputDisabledForSuggestions, setInputDisabledForSuggestions] = useState(false);
+  const [medicalAlertDetected, setMedicalAlertDetected] = useState<MedicalAlert | null>(null);
+  const [isSchedulingFromAlert, setIsSchedulingFromAlert] = useState(false);
+  const [showMedicalAlertBanner, setShowMedicalAlertBanner] = useState(false);
 
   const remainingMessages = Math.max(0, MAX_MESSAGES - messageCount);
   const isLastMessage = remainingMessages === 1;
@@ -192,6 +195,13 @@ export const useChat = () => {
           suggestionsCount: data.suggestions?.length || 0
         });
 
+        // Detectar alerta médica
+        if (data.medicalAlert && data.medicalAlert !== "NO_AGENDAR" && !medicalAlertDetected) {
+          console.log("🚨 [CHAT] Alerta médica detectada:", data.medicalAlert);
+          setMedicalAlertDetected(data.medicalAlert);
+          setShowMedicalAlertBanner(true);
+        }
+
         // Crear mensaje del asistente con streaming
         const assistantMessage: Message = {
           role: "assistant",
@@ -351,6 +361,9 @@ export const useChat = () => {
     setCurrentSuggestions([]);
     setShowDynamicSuggestions(false);
     setInputDisabledForSuggestions(false);
+    setMedicalAlertDetected(null);
+    setShowMedicalAlertBanner(false);
+    setIsSchedulingFromAlert(false);
     // Limpiar localStorage
     localStorage.removeItem("chatState");
     notifications.chat.newConsultation();
@@ -394,6 +407,48 @@ export const useChat = () => {
     }
   };
 
+  const handleScheduleFromAlert = async (onSuccess: (reportId: string) => void) => {
+    setIsSchedulingFromAlert(true);
+    
+    try {
+      // Generar reporte con la conversación actual
+      const currentReport = generateReportFromMessages(messages);
+
+      // Guardar el reporte en la base de datos
+      const response = await fetch("/api/chat/report", {
+        method: "POST",
+        headers: {
+          "Content-Type": "application/json",
+        },
+        body: JSON.stringify({
+          content: currentReport,
+          messages: messages,
+        }),
+      });
+
+      if (response.ok) {
+        const data = await response.json();
+        const reportId = data.id;
+        
+        // Callback con el reportId para abrir el modal
+        onSuccess(reportId);
+        
+        notifications.records.saved();
+      } else {
+        notifications.records.saveError();
+      }
+    } catch (error) {
+      console.error("Error al generar reporte para cita:", error);
+      notifications.records.generateError();
+    } finally {
+      setIsSchedulingFromAlert(false);
+    }
+  };
+
+  const dismissMedicalAlertBanner = () => {
+    setShowMedicalAlertBanner(false);
+  };
+
   return {
     messages,
     messageCount,
@@ -418,6 +473,11 @@ export const useChat = () => {
     setShowDynamicSuggestions,
     inputDisabledForSuggestions,
     setInputDisabledForSuggestions,
+    medicalAlertDetected,
+    showMedicalAlertBanner,
+    isSchedulingFromAlert,
+    handleScheduleFromAlert,
+    dismissMedicalAlertBanner,
   };
 };