From 1b178dd2a84adb441ee88d0578b22a205175bd89 Mon Sep 17 00:00:00 2001 From: "Roberto.silva" Date: Mon, 9 Mar 2026 16:25:58 -0600 Subject: [PATCH] Chats de ia en segundo plano para asignaturas #160 --- .../asignaturas/detalle/IAAsignaturaTab.tsx | 33 +++++++++++--- src/data/api/ai.api.ts | 2 +- src/data/hooks/useAI.ts | 43 ++++++++++++++++++- 3 files changed, 70 insertions(+), 8 deletions(-) diff --git a/src/components/asignaturas/detalle/IAAsignaturaTab.tsx b/src/components/asignaturas/detalle/IAAsignaturaTab.tsx index a56cad0..b5046af 100644 --- a/src/components/asignaturas/detalle/IAAsignaturaTab.tsx +++ b/src/components/asignaturas/detalle/IAAsignaturaTab.tsx @@ -76,6 +76,17 @@ export function IAAsignaturaTab({ const [isCreatingNewChat, setIsCreatingNewChat] = useState(false) const hasInitialSelected = useRef(false) + const isAiThinking = useMemo(() => { + if (isSending) return true + if (!rawMessages || rawMessages.length === 0) return false + + // Verificamos si el último mensaje está en estado de procesamiento + const lastMessage = rawMessages[rawMessages.length - 1] + return ( + lastMessage.estado === 'PROCESANDO' || lastMessage.estado === 'PENDIENTE' + ) + }, [isSending, rawMessages]) + // --- AUTO-SCROLL --- useEffect(() => { const viewport = scrollRef.current?.querySelector( @@ -392,11 +403,23 @@ export function IAAsignaturaTab({ ))} - {isSending && ( -
-
-
-
+ {isAiThinking && ( +
+ + + + + +
+
+
+
+
+
+
)}
diff --git a/src/data/api/ai.api.ts b/src/data/api/ai.api.ts index caa9d10..08f66cc 100644 --- a/src/data/api/ai.api.ts +++ b/src/data/api/ai.api.ts @@ -301,7 +301,7 @@ export async function getConversationBySubject(subjectId: string) { export async function getMessagesBySubjectConversation(conversationId: string) { const supabase = supabaseBrowser() const { data, error } = await supabase - .from('asignatura_mensajes_ia') // Tabla corregida + .from('asignatura_mensajes_ia' as any) .select('*') .eq('conversacion_asignatura_id', conversationId) .order('fecha_creacion', { ascending: true }) diff --git a/src/data/hooks/useAI.ts b/src/data/hooks/useAI.ts index bc8b8dd..464f423 100644 --- a/src/data/hooks/useAI.ts +++ b/src/data/hooks/useAI.ts @@ -243,15 +243,54 @@ export function useConversationBySubject(subjectId: string | null) { } export function useMessagesBySubjectChat(conversationId: string | null) { - return useQuery({ + const queryClient = useQueryClient() + + const query = useQuery({ queryKey: ['subject-messages', conversationId], - queryFn: () => { + queryFn: async () => { if (!conversationId) throw new Error('Conversation ID is required') return getMessagesBySubjectConversation(conversationId) }, enabled: !!conversationId, placeholderData: (previousData) => previousData, }) + + useEffect(() => { + if (!conversationId) return + + const supabase = supabaseBrowser() + + // Suscripción a cambios en la tabla específica para esta conversación + const channel = supabase + .channel(`subject_messages_${conversationId}`) + .on( + 'postgres_changes', + { + event: 'UPDATE', // Solo nos interesan las actualizaciones (cuando pasa de PROCESANDO a COMPLETADO) + schema: 'public', + table: 'asignatura_mensajes_ia', + filter: `conversacion_asignatura_id=eq.${conversationId}`, + }, + (payload) => { + // Si el mensaje se completó o dio error, invalidamos la caché para traer los datos nuevos + if ( + payload.new.estado === 'COMPLETADO' || + payload.new.estado === 'ERROR' + ) { + queryClient.invalidateQueries({ + queryKey: ['subject-messages', conversationId], + }) + } + }, + ) + .subscribe() + + return () => { + supabase.removeChannel(channel) + } + }, [conversationId, queryClient]) + + return query } export function useUpdateSubjectRecommendation() {