Matthew Trejo 1 месяц назад
Родитель
Сommit
91b6d5cc9c

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

@@ -18,6 +18,7 @@ import { AppointmentModalFromChat } from "./AppointmentModalFromChat";
 import { MedicalAlertBanner } from "./MedicalAlertBanner";
 import { PsychologicalDisclaimer } from "./PsychologicalDisclaimer";
 import { CrisisAlertBanner } from "./CrisisAlertBanner";
+import { ContinueChatModal } from "./ContinueChatModal";
 
 interface ChatInterfaceProps {
   chatType: "medical" | "psychological";
@@ -40,6 +41,8 @@ export const ChatInterface = ({ chatType }: ChatInterfaceProps) => {
     return false;
   });
   const [appointmentRecordId, setAppointmentRecordId] = useState<string | undefined>();
+  const [showContinueChatModal, setShowContinueChatModal] = useState(false);
+  const [hasCheckedExistingChat, setHasCheckedExistingChat] = useState(false);
 
   const {
     messages,
@@ -85,6 +88,28 @@ export const ChatInterface = ({ chatType }: ChatInterfaceProps) => {
     setShowLastMessageToast,
   });
 
+  // Detectar si hay mensajes existentes al cargar la página (antes de cualquier interacción)
+  useEffect(() => {
+    // Solo verificar al montar el componente, antes de que el usuario interactúe
+    if (!hasCheckedExistingChat) {
+      const storageKey = `chatMessages_${chatType}`;
+      const storedMessages = localStorage.getItem(storageKey);
+      
+      if (storedMessages) {
+        try {
+          const parsedMessages = JSON.parse(storedMessages);
+          // Solo mostrar si hay mensajes guardados previamente
+          if (Array.isArray(parsedMessages) && parsedMessages.length > 0) {
+            setShowContinueChatModal(true);
+          }
+        } catch (error) {
+          console.error('Error parsing stored messages:', error);
+        }
+      }
+      setHasCheckedExistingChat(true);
+    }
+  }, [hasCheckedExistingChat, chatType]);
+
   // Advertencia antes de salir de la página cuando hay una petición en proceso
   useEffect(() => {
     const handleBeforeUnload = (event: BeforeUnloadEvent) => {
@@ -122,6 +147,20 @@ export const ChatInterface = ({ chatType }: ChatInterfaceProps) => {
     localStorage.setItem(`psychDisclaimer_seen`, "true");
   };
 
+  const handleContinueChat = () => {
+    setShowContinueChatModal(false);
+  };
+
+  const handleNewChat = async () => {
+    setShowContinueChatModal(false);
+    setIsResetting(true);
+    try {
+      await handleResetWithReport();
+    } finally {
+      setIsResetting(false);
+    }
+  };
+
   const handleResetWithReportAndModal = async () => {
     setIsResetting(true);
     try {
@@ -285,6 +324,15 @@ export const ChatInterface = ({ chatType }: ChatInterfaceProps) => {
         recordId={appointmentRecordId}
         onChatComplete={resetChat}
       />
+
+      {/* Continue Chat Modal */}
+      <ContinueChatModal
+        isOpen={showContinueChatModal}
+        onContinue={handleContinueChat}
+        onNewChat={handleNewChat}
+        chatType={chatType}
+        messageCount={messages.length}
+      />
     </div>
   );
 };

+ 93 - 0
src/components/chatbot/ContinueChatModal.tsx

@@ -0,0 +1,93 @@
+"use client";
+
+import { AlertCircle, MessageSquare, RefreshCw } from "lucide-react";
+import {
+  Dialog,
+  DialogContent,
+  DialogDescription,
+  DialogHeader,
+  DialogTitle,
+} from "@/components/ui/dialog";
+import { Button } from "@/components/ui/button";
+
+interface ContinueChatModalProps {
+  isOpen: boolean;
+  onContinue: () => void;
+  onNewChat: () => void;
+  chatType: "medical" | "psychological";
+  messageCount: number;
+}
+
+export const ContinueChatModal = ({
+  isOpen,
+  onContinue,
+  onNewChat,
+  chatType,
+  messageCount,
+}: ContinueChatModalProps) => {
+  const isMedical = chatType === "medical";
+
+  return (
+    <Dialog open={isOpen} onOpenChange={() => {}}>
+      <DialogContent className="sm:max-w-md">
+        <DialogHeader>
+          <div className="flex items-center justify-center mb-4">
+            <div className={`w-16 h-16 rounded-full flex items-center justify-center ${
+              isMedical ? "bg-blue-100" : "bg-purple-100"
+            }`}>
+              <AlertCircle className={`w-8 h-8 ${
+                isMedical ? "text-blue-600" : "text-purple-600"
+              }`} />
+            </div>
+          </div>
+          <DialogTitle className="text-center text-xl">
+            Conversación Existente
+          </DialogTitle>
+          <DialogDescription className="text-center text-base pt-2">
+            Tienes una conversación previa con {messageCount} {messageCount === 1 ? "mensaje" : "mensajes"}.
+            <br />
+            ¿Deseas continuar donde lo dejaste o empezar una nueva conversación?
+          </DialogDescription>
+        </DialogHeader>
+
+        <div className="flex flex-col gap-3 mt-4">
+          {/* Botón Continuar */}
+          <Button
+            onClick={onContinue}
+            className={`w-full h-12 text-base font-medium ${
+              isMedical
+                ? "bg-blue-600 hover:bg-blue-700"
+                : "bg-purple-600 hover:bg-purple-700"
+            }`}
+          >
+            <MessageSquare className="w-5 h-5 mr-2" />
+            Continuar Conversación
+          </Button>
+
+          {/* Botón Nueva Conversación */}
+          <Button
+            onClick={onNewChat}
+            variant="outline"
+            className="w-full h-12 text-base font-medium"
+          >
+            <RefreshCw className="w-5 h-5 mr-2" />
+            Empezar Nueva Conversación
+          </Button>
+        </div>
+
+        {/* Información adicional */}
+        <div className={`mt-4 p-3 rounded-lg text-sm ${
+          isMedical
+            ? "bg-blue-50 text-blue-800 border border-blue-200"
+            : "bg-purple-50 text-purple-800 border border-purple-200"
+        }`}>
+          <p className="text-center">
+            {isMedical
+              ? "Si empiezas una nueva conversación, se generará un reporte de la conversación anterior."
+              : "Si empiezas una nueva conversación, podrás guardar un registro de la conversación anterior si lo deseas."}
+          </p>
+        </div>
+      </DialogContent>
+    </Dialog>
+  );
+};

+ 1 - 0
src/components/chatbot/index.ts

@@ -10,4 +10,5 @@ export { ResetButton } from "./ResetButton";
 export { ReportModal } from "./ReportModal";
 export { ResetConfirmationModal } from "./ResetConfirmationModal";
 export { AppointmentModalFromChat } from "./AppointmentModalFromChat";
+export { ContinueChatModal } from "./ContinueChatModal";
 export * from "./types"; 

+ 2 - 2
src/components/records/RecordsCard.tsx

@@ -55,8 +55,8 @@ export default function RecordsCard({
 
   const getReportTypeColor = () => {
     return record.chatType === "PSYCHOLOGICAL" 
-      ? "bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-300" 
-      : "bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-300"
+      ? "bg-purple-100 text-purple-700 dark:bg-purple-200/30 dark:text-purple-600" 
+      : "bg-blue-100 text-blue-700 dark:bg-blue-200/30 dark:text-blue-600"
   }
 
   return (

+ 3 - 3
src/hooks/useChat.ts

@@ -105,7 +105,7 @@ export const useChat = ({ chatType }: UseChatProps) => {
       notifications.records.generating();
 
       // Generar reporte
-      const report = generateReportFromMessages(messages);
+      const report = generateReportFromMessages(messages, chatType);
       setGeneratedReport(report);
       setReportGenerated(true);
 
@@ -506,7 +506,7 @@ export const useChat = ({ chatType }: UseChatProps) => {
 
     try {
       // Generar reporte con la conversación actual
-      const currentReport = generateReportFromMessages(messages);
+      const currentReport = generateReportFromMessages(messages, chatType);
 
       // Guardar el reporte en la base de datos
       const response = await fetch("/api/chat/report", {
@@ -554,7 +554,7 @@ export const useChat = ({ chatType }: UseChatProps) => {
     
     try {
       // Generar reporte con la conversación actual
-      const currentReport = generateReportFromMessages(messages);
+      const currentReport = generateReportFromMessages(messages, chatType);
       console.log("📝 [useChat] Reporte generado, longitud:", currentReport.length);
 
       // Guardar el reporte en la base de datos

+ 2 - 1
src/utils/pdf/components/PDFHeader.tsx

@@ -12,6 +12,7 @@ interface PDFHeaderProps {
 
 export const PDFHeader: React.FC<PDFHeaderProps> = ({ record }) => {
   const formattedDate = formatDate(record.createdAt);
+  const reportTitle = record.chatType === 'PSYCHOLOGICAL' ? 'REPORTE PSICOLÓGICO' : 'REPORTE MÉDICO';
 
   return (
     <View style={styles.header}>
@@ -26,7 +27,7 @@ export const PDFHeader: React.FC<PDFHeaderProps> = ({ record }) => {
       </View>
       
       <View style={styles.reportInfo}>
-        <Text style={styles.reportTitle}>REPORTE MÉDICO</Text>
+        <Text style={styles.reportTitle}>{reportTitle}</Text>
         <View style={styles.reportMeta}>
           <View style={styles.metaItem}>
             <Text style={styles.metaLabel}>ID:</Text>

+ 9 - 3
src/utils/pdf/generator.ts

@@ -79,7 +79,8 @@ export async function generatePDF(
     }
 
     // Descargar PDF
-    const fileName = `reporte-medico-${record.id.slice(-8)}-${
+    const reportType = record.chatType === 'PSYCHOLOGICAL' ? 'psicologico' : 'medico';
+    const fileName = `reporte-${reportType}-${record.id.slice(-8)}-${
       new Date().toISOString().split("T")[0]
     }.pdf`;
     pdf.save(fileName);
@@ -105,6 +106,11 @@ function generateModalStyleHTML(record: Record): string {
         day: "numeric",
       })
     : "No disponible";
+  
+  // Determinar el tipo de reporte
+  const reportTitle = record.chatType === 'PSYCHOLOGICAL' ? 'Reporte Psicológico' : 'Reporte Médico';
+  const userLabel = record.chatType === 'PSYCHOLOGICAL' ? 'Usuario' : 'Paciente';
+  const reportFileName = record.chatType === 'PSYCHOLOGICAL' ? 'psicologico' : 'medico';
 
   return `
     <div style="font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; background: white; padding-bottom: 60px;">
@@ -123,7 +129,7 @@ function generateModalStyleHTML(record: Record): string {
         
         <!-- Información del reporte -->
         <div style="background: #f8fafc; padding: 16px; border-radius: 8px; border-left: 4px solid #2563eb;">
-          <h2 style="margin: 0; font-size: 18px; font-weight: 600; color: #1f2937;">📋 Reporte Médico</h2>
+          <h2 style="margin: 0; font-size: 18px; font-weight: 600; color: #1f2937;">📋 ${reportTitle}</h2>
           <div style="display: flex; gap: 24px; margin-top: 8px; font-size: 12px; color: #6b7280;">
             <span><strong>ID:</strong> ${record.id.slice(-8)}</span>
             <span><strong>Fecha:</strong> ${formattedDate}</span>
@@ -132,7 +138,7 @@ function generateModalStyleHTML(record: Record): string {
         </div>
       </div>
 
-      <!-- Información del Paciente -->
+      <!-- Información del ${userLabel} -->
       <div style="padding: 24px; background: linear-gradient(to right, #eff6ff, #dbeafe); border-radius: 12px; margin: 24px; border: 1px solid #bfdbfe; box-shadow: 0 4px 6px rgba(0,0,0,0.05);">
         <div style="display: flex; align-items: start; margin-bottom: 16px;">
           <!-- Foto de perfil -->

+ 79 - 26
src/utils/reports/generator.ts

@@ -1,25 +1,29 @@
 import { Message } from '../types'
 
-export function generateReportFromMessages(messages: Message[]): string {
+export function generateReportFromMessages(messages: Message[], chatType: 'medical' | 'psychological' = 'medical'): string {
   if (!messages || messages.length === 0) {
     return "No hay mensajes para generar un reporte."
   }
 
-  // Generar un resumen médico basado en los mensajes
-  let report = "## REPORTE MÉDICO\n\n"
+  const reportTitle = chatType === 'psychological' ? 'REPORTE PSICOLÓGICO' : 'REPORTE MÉDICO';
+  const userLabel = chatType === 'psychological' ? 'USUARIO' : 'PACIENTE';
+  const professionalType = chatType === 'psychological' ? 'psicólogo' : 'médico';
+  
+  // Generar un resumen basado en los mensajes
+  let report = `## ${reportTitle}\n\n`
   report += `**Fecha:** ${new Date().toLocaleDateString('es-ES')}\n`
   report += `**Hora:** ${new Date().toLocaleTimeString('es-ES')}\n\n`
 
   // Resumen de la consulta
   report += "### RESUMEN DE LA CONSULTA\n"
-  report += `El paciente realizó ${messages.filter(msg => msg.role === "user").length} consulta(s) con el asistente virtual.\n\n`
+  report += `El ${userLabel.toLowerCase()} realizó ${messages.filter(msg => msg.role === "user").length} consulta(s) con el asistente virtual.\n\n`
 
   // Incluir toda la conversación completa
   report += "### CONVERSACIÓN COMPLETA\n"
   report += "────────────────────────────────────────────────────────\n\n"
   
   messages.forEach((message, index) => {
-    const role = message.role === "user" ? "**PACIENTE**" : "**ASISTENTE**"
+    const role = message.role === "user" ? `**${userLabel}**` : "**ASISTENTE**"
     const timestamp = new Date().toLocaleTimeString('es-ES', { 
       hour: '2-digit', 
       minute: '2-digit' 
@@ -31,25 +35,39 @@ export function generateReportFromMessages(messages: Message[]): string {
   
   report += "────────────────────────────────────────────────────────\n\n"
 
-  // Analizar síntomas mencionados
+  // Analizar síntomas/temas mencionados
   const userMessages = messages.filter(msg => msg.role === "user").map(msg => msg.content)
-  const symptoms = extractSymptoms(userMessages.join(" "))
-  if (symptoms.length > 0) {
-    report += "### SÍNTOMAS IDENTIFICADOS\n"
-    symptoms.forEach(symptom => {
-      report += `- ${symptom}\n`
-    })
-    report += "\n"
-  }
+  
+  if (chatType === 'psychological') {
+    // Para reportes psicológicos, extraer temas emocionales
+    const topics = extractPsychologicalTopics(userMessages.join(" "))
+    if (topics.length > 0) {
+      report += "### TEMAS DISCUTIDOS\n"
+      topics.forEach(topic => {
+        report += `- ${topic}\n`
+      })
+      report += "\n"
+    }
+  } else {
+    // Para reportes médicos, extraer síntomas
+    const symptoms = extractSymptoms(userMessages.join(" "))
+    if (symptoms.length > 0) {
+      report += "### SÍNTOMAS IDENTIFICADOS\n"
+      symptoms.forEach(symptom => {
+        report += `- ${symptom}\n`
+      })
+      report += "\n"
+    }
 
-  // Principales temas discutidos
-  const topics = extractTopics(userMessages.join(" "))
-  if (topics.length > 0) {
-    report += "### TEMAS PRINCIPALES\n"
-    topics.forEach(topic => {
-      report += `- ${topic}\n`
-    })
-    report += "\n"
+    // Principales temas discutidos
+    const topics = extractTopics(userMessages.join(" "))
+    if (topics.length > 0) {
+      report += "### TEMAS PRINCIPALES\n"
+      topics.forEach(topic => {
+        report += `- ${topic}\n`
+      })
+      report += "\n"
+    }
   }
 
   // Resumen de recomendaciones del asistente
@@ -75,14 +93,17 @@ export function generateReportFromMessages(messages: Message[]): string {
   }
 
   // Información adicional basada en el contenido
-  const additionalInfo = extractAdditionalInfo(userMessages.join(" "))
+  const additionalInfo = extractAdditionalInfo(userMessages.join(" "), chatType)
   if (additionalInfo) {
     report += "### INFORMACIÓN ADICIONAL\n"
     report += `${additionalInfo}\n\n`
   }
 
-  report += "**NOTA:** Este es un reporte generado automáticamente por el asistente virtual. "
-  report += "Se recomienda consultar con un profesional médico para un diagnóstico completo.\n\n"
+  const noteText = chatType === 'psychological' 
+    ? "**NOTA:** Este es un reporte generado automáticamente por el asistente virtual de apoyo psicológico. Se recomienda consultar con un profesional de la salud mental para un diagnóstico y tratamiento completo."
+    : "**NOTA:** Este es un reporte generado automáticamente por el asistente virtual. Se recomienda consultar con un profesional médico para un diagnóstico completo."
+    
+  report += noteText + "\n\n"
   report += "### FIN DEL REPORTE"
 
   return report
@@ -120,6 +141,23 @@ function extractTopics(text: string): string[] {
   return foundTopics
 }
 
+function extractPsychologicalTopics(text: string): string[] {
+  const psychologicalTopics = [
+    "ansiedad", "estrés", "depresión", "autoestima", "relaciones",
+    "familia", "trabajo", "estudios", "emociones", "sentimientos",
+    "tristeza", "miedo", "enojo", "frustración", "soledad",
+    "confianza", "inseguridad", "motivación", "sueño", "descanso",
+    "preocupación", "pánico", "fobia", "trauma", "duelo",
+    "comunicación", "conflicto", "límites", "autocuidado", "bienestar"
+  ]
+
+  const foundTopics = psychologicalTopics.filter(topic => 
+    text.toLowerCase().includes(topic)
+  )
+
+  return foundTopics
+}
+
 function extractRecommendations(text: string): string[] {
   const recommendations: string[] = []
   const lines = text.split('\n')
@@ -171,9 +209,24 @@ function extractRecommendations(text: string): string[] {
   return recommendations.slice(0, 5) // Máximo 5 recomendaciones
 }
 
-function extractAdditionalInfo(text: string): string | null {
+function extractAdditionalInfo(text: string, chatType: 'medical' | 'psychological' = 'medical'): string | null {
   const lowerText = text.toLowerCase()
   
+  if (chatType === 'psychological') {
+    // Detectar si es una situación de crisis
+    if (lowerText.includes("suicid") || lowerText.includes("hacerme daño") || lowerText.includes("matarme")) {
+      return "Se identificó contenido relacionado con crisis emocional. Se recomienda atención profesional inmediata."
+    }
+    
+    // Detectar si es consulta de urgencia emocional
+    if (lowerText.includes("crisis") || lowerText.includes("urgente") || lowerText.includes("ataque de pánico")) {
+      return "Consulta de urgencia emocional identificada. Se recomienda apoyo profesional."
+    }
+    
+    return null
+  }
+  
+  // Para reportes médicos
   // Detectar si es una consulta de emergencia
   if (lowerText.includes("emergencia") || lowerText.includes("urgente")) {
     return "Se identificó como consulta de emergencia. Se recomienda atención médica inmediata."

+ 10 - 4
src/utils/reports/txtGenerator.ts

@@ -15,6 +15,12 @@ export function generateTXTReport(record: Record): string {
 
   // Renderizar el contenido markdown a texto plano
   const renderedContent = renderMarkdownToText(record.content)
+  
+  // Determinar el tipo de reporte (por defecto médico si no se especifica)
+  const reportType = record.chatType === 'PSYCHOLOGICAL' ? 'Reporte Psicológico Virtual' : 'Reporte Médico Virtual'
+  const userLabel = record.chatType === 'PSYCHOLOGICAL' ? 'USUARIO' : 'PACIENTE'
+  const professionalType = record.chatType === 'PSYCHOLOGICAL' ? 'profesional de la salud mental' : 'profesional médico'
+  const aiType = record.chatType === 'PSYCHOLOGICAL' ? 'IA de Apoyo Psicológico' : 'IA Médica'
 
   const reportContent = `
 ${'='.repeat(80)}
@@ -26,11 +32,11 @@ ${'='.repeat(80)}
 ${'-'.repeat(40)}
 ID del Reporte: ${record.id}
 Fecha de Creación: ${formattedDate}
-Generado por: ${COMPANY_CONFIG.name} - IA Médica
-Tipo de Documento: Reporte Médico Virtual
+Generado por: ${COMPANY_CONFIG.name} - ${aiType}
+Tipo de Documento: ${reportType}
 Estado: Consulta Completada
 
-👤 DATOS DEL PACIENTE
+👤 DATOS DEL ${userLabel}
 ${'-'.repeat(40)}
 Nombre Completo: ${fullName}
 Email: ${record.user.email}
@@ -53,7 +59,7 @@ ${'='.repeat(80)}
 ${'='.repeat(80)}
 
 Este reporte fue generado automáticamente por ${COMPANY_CONFIG.name}
-Se recomienda consultar con un profesional médico para un diagnóstico completo.
+Se recomienda consultar con un ${professionalType} para un diagnóstico y tratamiento completo.
 
 Fecha de impresión: ${new Date().toLocaleDateString('es-ES')} - ${new Date().toLocaleTimeString('es-ES')}
 ${'='.repeat(80)}

+ 1 - 0
src/utils/types.ts

@@ -6,6 +6,7 @@ export interface Message {
 export interface Record {
   id: string
   content: string
+  chatType?: 'MEDICAL' | 'PSYCHOLOGICAL'
   createdAt: string
   user: {
     name: string