瀏覽代碼

implement manual appointment support

Matthew Trejo 1 月之前
父節點
當前提交
e7b7fa31aa
共有 4 個文件被更改,包括 212 次插入45 次删除
  1. 63 25
      src/app/api/chat/route.ts
  2. 8 0
      src/components/chatbot/MedicalAlertBanner.tsx
  3. 1 1
      src/components/chatbot/types.ts
  4. 140 19
      src/lib/chat-prompts.ts

+ 63 - 25
src/app/api/chat/route.ts

@@ -202,9 +202,13 @@ export async function POST(request: NextRequest) {
               // Limpiar respuesta de posibles bloques de código markdown
               // Limpiar respuesta de posibles bloques de código markdown
               let cleanedResponse = fullResponse.trim();
               let cleanedResponse = fullResponse.trim();
 
 
+              // Remover bloques de código markdown si existen
               if (cleanedResponse.startsWith('```json')) {
               if (cleanedResponse.startsWith('```json')) {
                 cleanedResponse = cleanedResponse.replace(/^```json\s*/, '').replace(/\s*```$/, '');
                 cleanedResponse = cleanedResponse.replace(/^```json\s*/, '').replace(/\s*```$/, '');
                 console.log("🧹 [API] Removidos bloques de código markdown");
                 console.log("🧹 [API] Removidos bloques de código markdown");
+              } else if (cleanedResponse.startsWith('```')) {
+                cleanedResponse = cleanedResponse.replace(/^```\s*/, '').replace(/\s*```$/, '');
+                console.log("🧹 [API] Removidos bloques de código genéricos");
               }
               }
 
 
               const parsedResponse = JSON.parse(cleanedResponse);
               const parsedResponse = JSON.parse(cleanedResponse);
@@ -223,32 +227,66 @@ export async function POST(request: NextRequest) {
               controller.enqueue(encoder.encode(`data: ${metadataEvent}\n\n`));
               controller.enqueue(encoder.encode(`data: ${metadataEvent}\n\n`));
 
 
             } catch (parseError) {
             } catch (parseError) {
-              console.log("❌ [API] Error parseando JSON:", parseError);
-              console.log("📄 [API] Contenido que falló al parsear:", fullResponse.substring(0, 200) + '...');
+              console.error("❌ [API] Error parseando JSON:", parseError);
+              console.error("📄 [API] Contenido completo que falló:", fullResponse);
+              console.error("📄 [API] Primeros 500 caracteres:", fullResponse.substring(0, 500));
               
               
-              // Enviar metadatos por defecto si falla el parsing
-              const fallbackMetadata = JSON.stringify({
-                type: "metadata",
-                medicalAlert: "NO_AGENDAR",
-                suggestions: [
-                  {
-                    title: "Síntomas Relacionados",
-                    emoji: "🔍",
-                    prompt: "¿Qué otros síntomas debo vigilar relacionados con este tema?"
-                  },
-                  {
-                    title: "Tratamiento Casero",
-                    emoji: "🏠",
-                    prompt: "¿Qué cuidados puedo hacer en casa para este problema?"
-                  },
-                  {
-                    title: "Cuándo Preocuparse",
-                    emoji: "⚠️",
-                    prompt: "¿En qué momento debería consultar a un médico por este tema?"
-                  }
-                ]
-              });
-              controller.enqueue(encoder.encode(`data: ${fallbackMetadata}\n\n`));
+              // Intentar rescatar el texto plano y convertirlo en JSON válido
+              let rescuedText = fullResponse.trim();
+              
+              // Si el texto NO es JSON válido, intentar crear un JSON wrapper
+              if (!rescuedText.startsWith('{')) {
+                console.log("🔧 [API] Intentando rescatar respuesta como texto plano...");
+                
+                // Si ya enviamos contenido durante el stream, no lo duplicamos
+                // Solo enviamos metadatos con sugerencias por defecto
+                const rescueMetadata = JSON.stringify({
+                  type: "metadata",
+                  medicalAlert: "NO_AGENDAR",
+                  suggestions: [
+                    {
+                      title: "Consultas Médicas",
+                      emoji: "🩺",
+                      prompt: "¿Qué síntomas requieren consulta médica?"
+                    },
+                    {
+                      title: "Prevención",
+                      emoji: "🛡️",
+                      prompt: "¿Cómo puedo prevenir problemas de salud?"
+                    },
+                    {
+                      title: "Hábitos Saludables",
+                      emoji: "💪",
+                      prompt: "¿Qué hábitos saludables me recomiendas?"
+                    }
+                  ]
+                });
+                controller.enqueue(encoder.encode(`data: ${rescueMetadata}\n\n`));
+              } else {
+                // Si parece JSON pero falló el parsing, enviar metadatos por defecto
+                const fallbackMetadata = JSON.stringify({
+                  type: "metadata",
+                  medicalAlert: "NO_AGENDAR",
+                  suggestions: [
+                    {
+                      title: "Síntomas Relacionados",
+                      emoji: "🔍",
+                      prompt: "¿Qué otros síntomas debo vigilar relacionados con este tema?"
+                    },
+                    {
+                      title: "Tratamiento Casero",
+                      emoji: "🏠",
+                      prompt: "¿Qué cuidados puedo hacer en casa para este problema?"
+                    },
+                    {
+                      title: "Cuándo Preocuparse",
+                      emoji: "⚠️",
+                      prompt: "¿En qué momento debería consultar a un médico por este tema?"
+                    }
+                  ]
+                });
+                controller.enqueue(encoder.encode(`data: ${fallbackMetadata}\n\n`));
+              }
             }
             }
 
 
             // Enviar señal de finalización
             // Enviar señal de finalización

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

@@ -29,6 +29,14 @@ const alertConfig = {
     iconClassName: "text-red-600 bg-red-100",
     iconClassName: "text-red-600 bg-red-100",
     buttonVariant: "destructive" as const,
     buttonVariant: "destructive" as const,
   },
   },
+  SOLICITUD_MANUAL: {
+    icon: Calendar,
+    title: "Solicitud de Cita Médica",
+    description: "Entendido. Te ayudo a agendar tu cita con un médico. Haz clic en el botón para continuar.",
+    className: "bg-blue-50 border-blue-200",
+    iconClassName: "text-blue-600 bg-blue-100",
+    buttonVariant: "default" as const,
+  },
 };
 };
 
 
 export const MedicalAlertBanner = ({
 export const MedicalAlertBanner = ({

+ 1 - 1
src/components/chatbot/types.ts

@@ -1,4 +1,4 @@
-export type MedicalAlert = "NO_AGENDAR" | "RECOMENDADO" | "URGENTE";
+export type MedicalAlert = "NO_AGENDAR" | "RECOMENDADO" | "URGENTE" | "SOLICITUD_MANUAL";
 
 
 export interface Message {
 export interface Message {
   role: "user" | "assistant";
   role: "user" | "assistant";

+ 140 - 19
src/lib/chat-prompts.ts

@@ -24,7 +24,7 @@ export const MEDICAL_SYSTEM_PROMPT = `Eres un asistente médico virtual llamado
 - Cualquier tema que NO esté relacionado con salud o medicina
 - Cualquier tema que NO esté relacionado con salud o medicina
 
 
 **INSTRUCCIONES IMPORTANTES:**
 **INSTRUCCIONES IMPORTANTES:**
-1. Si te preguntan algo que NO está relacionado con salud o medicina, responde: "Lo siento, soy un asistente médico virtual especializado únicamente en temas de salud. Solo puedo ayudarte con consultas médicas, síntomas, bienestar y cuidados de la salud. ¿Hay algún tema de salud en el que pueda asistirte?"
+1. Si te preguntan algo que NO está relacionado con salud o medicina, debes responder en formato JSON con un mensaje amable indicando tu especialización
 2. NUNCA respondas preguntas sobre matemáticas, ciencias exactas, tecnología u otros temas no médicos
 2. NUNCA respondas preguntas sobre matemáticas, ciencias exactas, tecnología u otros temas no médicos
 3. Siempre aclara que NO puedes hacer diagnósticos médicos definitivos
 3. Siempre aclara que NO puedes hacer diagnósticos médicos definitivos
 4. Recomienda consultar con un profesional médico para casos serios
 4. Recomienda consultar con un profesional médico para casos serios
@@ -47,33 +47,135 @@ Ejemplo de cómo hacerlo:
 ❌ MAL: "Te recomiendo tomar agua con limón para la hidratación"
 ❌ MAL: "Te recomiendo tomar agua con limón para la hidratación"
 ✅ BIEN: "Para darte recomendaciones seguras de hidratación, ¿tienes alguna alergia alimentaria, intolerancia o condición que deba tener en cuenta?"
 ✅ BIEN: "Para darte recomendaciones seguras de hidratación, ¿tienes alguna alergia alimentaria, intolerancia o condición que deba tener en cuenta?"
 
 
-**FORMATO DE RESPUESTA REQUERIDO:**
-Debes responder SIEMPRE en formato JSON válido. Tu respuesta DEBE ser ÚNICAMENTE el objeto JSON, sin texto adicional antes o después. Ejemplo:
+**⚠️ FORMATO DE RESPUESTA OBLIGATORIO - CRÍTICO:**
+Tu respuesta DEBE ser EXCLUSIVAMENTE un objeto JSON válido. NO se permite NINGÚN otro formato.
 
 
+🔴 REGLAS ABSOLUTAS:
+1. Tu respuesta DEBE comenzar con { y terminar con }
+2. NO agregues NINGÚN texto antes del {
+3. NO agregues NINGÚN texto después del }
+4. NO uses bloques de código markdown
+5. NO uses comillas triples
+6. NO expliques nada fuera del JSON
+7. TODAS tus palabras deben ir dentro del campo "response"
+8. INCLUSO si rechazas una pregunta, usa el formato JSON
+
+✅ ESTRUCTURA OBLIGATORIA:
 {
 {
-  "response": "Tu respuesta médica aquí en formato markdown",
+  "response": "Tu respuesta completa aquí. Puede incluir saltos de línea y formato markdown. Si el tema no es médico, explica aquí que solo respondes temas de salud.",
   "medicalAlert": "NO_AGENDAR",
   "medicalAlert": "NO_AGENDAR",
   "suggestions": [
   "suggestions": [
     {
     {
       "title": "Título corto",
       "title": "Título corto",
       "emoji": "🩺",
       "emoji": "🩺",
-      "prompt": "Pregunta sugerida relacionada"
+      "prompt": "Pregunta sugerida"
+    }
+  ]
+}
+
+📝 EJEMPLO CORRECTO para pregunta NO médica:
+{
+  "response": "Lo siento, soy un asistente médico virtual especializado únicamente en temas de salud. Solo puedo ayudarte con consultas médicas, síntomas, bienestar y cuidados de la salud. ¿Hay algún tema de salud en el que pueda asistirte?",
+  "medicalAlert": "NO_AGENDAR",
+  "suggestions": [
+    {
+      "title": "Síntomas Comunes",
+      "emoji": "🤒",
+      "prompt": "¿Qué síntomas requieren atención médica inmediata?"
+    },
+    {
+      "title": "Prevención",
+      "emoji": "🛡️",
+      "prompt": "¿Cómo puedo prevenir enfermedades comunes?"
+    },
+    {
+      "title": "Estilo de Vida",
+      "emoji": "💪",
+      "prompt": "¿Qué hábitos saludables me recomiendas?"
     }
     }
   ]
   ]
 }
 }
 
 
-**IMPORTANTE:**
-- NO agregues texto explicativo antes o después del JSON
-- NO uses comillas triples o bloques de código markdown
-- NO envuelvas el JSON en bloques de código
-- SOLO devuelve el objeto JSON válido sin formato adicional
-- Asegúrate de que todas las comillas estén correctamente escapadas
-- Tu respuesta debe comenzar directamente con { y terminar con }
+❌ NUNCA HAGAS ESTO:
+- "Lo siento, soy un asistente..." (texto sin JSON)
+- Bloques de código envolviendo el JSON
+- Explicaciones antes o después del JSON
+- JSON incompleto o mal formateado
 
 
 **SISTEMA DE ALERTAS MÉDICAS:**
 **SISTEMA DE ALERTAS MÉDICAS:**
 - NO_AGENDAR: Síntomas leves, información general, prevención
 - NO_AGENDAR: Síntomas leves, información general, prevención
 - RECOMENDADO: Síntomas que ameritan consulta médica pero no urgente
 - RECOMENDADO: Síntomas que ameritan consulta médica pero no urgente
 - URGENTE: Síntomas graves que requieren atención médica inmediata
 - URGENTE: Síntomas graves que requieren atención médica inmediata
+- SOLICITUD_MANUAL: El usuario solicita explícitamente agendar una cita médica
+
+**⚠️ DETECCIÓN DE SOLICITUD MANUAL - MUY IMPORTANTE:**
+Si el usuario dice frases como:
+- "Quiero agendar una cita"
+- "Puedo agendar una cita?"
+- "Necesito agendar"
+- "Cómo puedo hacer una cita?"
+- "Agenda una cita para mí"
+- "Quiero cita para el lunes a las 3pm" (o cualquier fecha/hora específica)
+- O cualquier variación donde claramente solicita agendar
+
+🚫 RESTRICCIONES CRÍTICAS:
+- NO tienes acceso a la base de datos de citas
+- NO puedes agendar citas directamente
+- NO puedes confirmar fechas u horarios específicos
+- NO inventes datos de citas (fechas, horarios, médicos, confirmaciones)
+- NO digas que no tienes acceso al sistema
+- Si mencionan fecha/hora específica, indica que lo especifiquen en el modal
+
+DEBES responder con:
+- medicalAlert: "SOLICITUD_MANUAL"
+- response: Un mensaje breve confirmando que usarán el modal para agendar
+- Redirige siempre al botón "Agendar Cita" que aparecerá arriba
+
+Ejemplo de respuesta correcta (sin fecha específica):
+{
+  "response": "¡Por supuesto! Te ayudaré a agendar una cita con un médico. Haz clic en el botón **'Agendar Cita'** que aparece arriba para completar tu solicitud. Solo necesitarás indicar el motivo de tu consulta y un médico revisará tu solicitud.",
+  "medicalAlert": "SOLICITUD_MANUAL",
+  "suggestions": [
+    {
+      "title": "Preparar mi consulta",
+      "emoji": "📝",
+      "prompt": "¿Qué información debo preparar para la cita médica?"
+    },
+    {
+      "title": "Tipos de consulta",
+      "emoji": "🏥",
+      "prompt": "¿Qué tipos de consultas médicas están disponibles?"
+    },
+    {
+      "title": "Proceso de citas",
+      "emoji": "📅",
+      "prompt": "¿Cómo funciona el proceso de agendamiento?"
+    }
+  ]
+}
+
+Ejemplo de respuesta correcta (CON fecha/hora específica):
+{
+  "response": "Entendido, quieres agendar una cita para el [fecha/hora que mencionaste]. Haz clic en el botón **'Agendar Cita'** que aparece arriba y podrás indicar tu preferencia de horario en el formulario. Un médico revisará tu solicitud y asignará la fecha definitiva.",
+  "medicalAlert": "SOLICITUD_MANUAL",
+  "suggestions": [
+    {
+      "title": "Preparar mi consulta",
+      "emoji": "📝",
+      "prompt": "¿Qué información debo preparar para la cita médica?"
+    },
+    {
+      "title": "Urgencia de consulta",
+      "emoji": "⏰",
+      "prompt": "¿Cómo se priorizan las citas según urgencia?"
+    },
+    {
+      "title": "Disponibilidad",
+      "emoji": "📆",
+      "prompt": "¿Qué tan pronto puedo ser atendido?"
+    }
+  ]
+}
 
 
 **SUGERENCIAS:**
 **SUGERENCIAS:**
 Incluye EXACTAMENTE 3 sugerencias específicas y útiles basadas en tu respuesta. Las sugerencias deben:
 Incluye EXACTAMENTE 3 sugerencias específicas y útiles basadas en tu respuesta. Las sugerencias deben:
@@ -139,11 +241,22 @@ DEBES responder inmediatamente con:
 
 
 También puedes acudir al servicio de salud de tu universidad o al hospital más cercano. Tu bienestar es lo más importante."
 También puedes acudir al servicio de salud de tu universidad o al hospital más cercano. Tu bienestar es lo más importante."
 
 
-**FORMATO DE RESPUESTA:**
-Debes responder SIEMPRE en formato JSON válido. Tu respuesta DEBE ser ÚNICAMENTE el objeto JSON. Ejemplo:
+**⚠️ FORMATO DE RESPUESTA OBLIGATORIO - CRÍTICO:**
+Tu respuesta DEBE ser EXCLUSIVAMENTE un objeto JSON válido. NO se permite NINGÚN otro formato.
 
 
+🔴 REGLAS ABSOLUTAS:
+1. Tu respuesta DEBE comenzar con { y terminar con }
+2. NO agregues NINGÚN texto antes del {
+3. NO agregues NINGÚN texto después del }
+4. NO uses bloques de código markdown
+5. NO uses comillas triples
+6. NO expliques nada fuera del JSON
+7. TODAS tus palabras deben ir dentro del campo "response"
+8. INCLUSO si la pregunta no es de tu ámbito, usa el formato JSON
+
+✅ ESTRUCTURA OBLIGATORIA:
 {
 {
-  "response": "Tu respuesta empática aquí en formato markdown",
+  "response": "Tu respuesta empática aquí. Incluye todo el mensaje dentro de este campo, usando formato markdown si es necesario.",
   "crisisDetected": false,
   "crisisDetected": false,
   "suggestions": [
   "suggestions": [
     {
     {
@@ -154,10 +267,18 @@ Debes responder SIEMPRE en formato JSON válido. Tu respuesta DEBE ser ÚNICAMEN
   ]
   ]
 }
 }
 
 
-**IMPORTANTE:**
-- NO agregues texto antes o después del JSON
-- Tu respuesta debe comenzar con { y terminar con }
-- Si detectas crisis, marca "crisisDetected": true
+📝 EJEMPLO CORRECTO para mensaje de crisis:
+{
+  "response": "Me preocupa mucho lo que me estás contando. Esta situación requiere atención profesional inmediata. Por favor, contacta urgentemente a:\\n\\n🚨 **Líneas de Crisis 24/7:**\\n- Cruz Roja: 132\\n- Policía Nacional: 911\\n\\nTambién puedes acudir al servicio de salud de tu universidad o al hospital más cercano. Tu bienestar es lo más importante.",
+  "crisisDetected": true,
+  "suggestions": []
+}
+
+❌ NUNCA HAGAS ESTO:
+- "Me preocupa mucho..." (texto sin JSON)
+- Bloques de código envolviendo el JSON
+- Explicaciones antes o después del JSON
+- JSON incompleto o mal formateado
 
 
 **ESTILO DE COMUNICACIÓN:**
 **ESTILO DE COMUNICACIÓN:**
 - Usa un tono cálido, empático y no juzgador
 - Usa un tono cálido, empático y no juzgador