From f2900f3d22531e89244db135a36d83fcfe5a1e3b Mon Sep 17 00:00:00 2001 From: "Roberto.silva" Date: Wed, 25 Mar 2026 15:14:29 -0600 Subject: [PATCH] Que se puedan aplicar todos los campos y todas las mejoras fix #165 --- .../asignaturas/detalle/IAAsignaturaTab.tsx | 184 ++++++++++++++-- .../SaveAsignatura/ImprovementCardProps.tsx | 1 + src/routes/planes/$planId/_detalle/iaplan.tsx | 202 +++++++++++++++++- 3 files changed, 354 insertions(+), 33 deletions(-) diff --git a/src/components/asignaturas/detalle/IAAsignaturaTab.tsx b/src/components/asignaturas/detalle/IAAsignaturaTab.tsx index 8bc30a6..1e274d5 100644 --- a/src/components/asignaturas/detalle/IAAsignaturaTab.tsx +++ b/src/components/asignaturas/detalle/IAAsignaturaTab.tsx @@ -14,7 +14,8 @@ import { MessageSquarePlus, Archive, History, - Edit2, // Agregado + Edit2, + Loader2, // Agregado } from 'lucide-react' import { useState, useEffect, useRef, useMemo } from 'react' @@ -42,8 +43,10 @@ import { useConversationBySubject, useMessagesBySubjectChat, useSubject, + useUpdateAsignatura, useUpdateSubjectConversationName, useUpdateSubjectConversationStatus, + useUpdateSubjectRecommendation, } from '@/data' import { cn } from '@/lib/utils' @@ -71,6 +74,9 @@ export function IAAsignaturaTab() { const scrollRef = useRef(null) const [isSidebarOpen, setIsSidebarOpen] = useState(false) + const updateAsignatura = useUpdateAsignatura() + const updateRecommendation = useUpdateSubjectRecommendation() + // --- DATA QUERIES --- const { data: datosGenerales } = useSubject(asignaturaId) const { data: todasConversaciones, isLoading: loadingConv } = @@ -94,6 +100,81 @@ export function IAAsignaturaTab() { Array >([]) const [uploadedFiles, setUploadedFiles] = useState>([]) + const [selectedImprovements, setSelectedImprovements] = useState< + Array + >([]) + + const handleApplyMultiple = async (sugerencias: Array) => { + if (!asignaturaId || !datosGenerales || sugerencias.length === 0) return + + setIsSending(true) + try { + // 1. Consolidar el Patch + const patchData: any = { + datos: { ...datosGenerales.datos }, + } + + for (const sug of sugerencias) { + if (sug.campoKey === 'contenido_tematico') { + patchData.contenido_tematico = sug.valorSugerido + } else if (sug.campoKey === 'criterios_de_evaluacion') { + patchData.criterios_de_evaluacion = sug.valorSugerido + } else { + patchData.datos[sug.campoKey] = sug.valorSugerido + } + } + + // 2. Actualización única de la asignatura + await updateAsignatura.mutateAsync({ + asignaturaId: asignaturaId as any, + patch: patchData, + }) + + // 3. Marcar recomendaciones una por una (Secuencialmente para evitar colisiones) + for (const sug of sugerencias) { + await updateRecommendation.mutateAsync({ + mensajeId: sug.messageId, + campoAfectado: sug.campoKey, + }) + } + + // 4. Limpieza de estados + const appliedKeys = sugerencias.map((s) => s.campoKey) + setSelectedFields((prev) => + prev.filter((f) => !appliedKeys.includes(f.key)), + ) + setSelectedImprovements([]) + + // Forzar actualización visual + queryClient.invalidateQueries({ queryKey: ['subject', asignaturaId] }) + } catch (error) { + console.error('Error en aplicación masiva:', error) + } finally { + setIsSending(false) + } + } + const toggleImprovementSelection = (sugId: string) => { + setSelectedImprovements((prev) => + prev.includes(sugId) + ? prev.filter((id) => id !== sugId) + : [...prev, sugId], + ) + } + + const toggleAllFromMessage = (sugerencias: Array) => { + const allIds = sugerencias.map((s) => s.id) + const allSelected = allIds.every((id) => selectedImprovements.includes(id)) + + if (allSelected) { + setSelectedImprovements((prev) => + prev.filter((id) => !allIds.includes(id)), + ) + } else { + setSelectedImprovements((prev) => + Array.from(new Set([...prev, ...allIds])), + ) + } + } // Cálculo del total para el Badge del botón const totalReferencias = @@ -678,31 +759,90 @@ export function IAAsignaturaTab() { {/* CONTENEDOR DE SUGERENCIAS INTEGRADO */} {msg.role === 'assistant' && - msg.sugerencias && - msg.sugerencias.length > 0 && ( + msg.sugerencias?.length > 0 && (
-

- Mejoras disponibles: -

- {msg.sugerencias.map((sug: any) => ( - { - // Filtramos el array para conservar todos MENOS el que se aplicó - console.log(campoFinalizado) - console.log('campos:', selectedFields) +
+

+ Mejoras disponibles +

- setSelectedFields((prev) => - prev.filter((fieldObj) => { - // Accedemos a .key porque fieldObj es { key: "...", label: "..." } - return fieldObj.key !== campoFinalizado - }), + {/* Solo mostramos "Seleccionar todo" si hay sugerencias pendientes en ESTE mensaje */} + {msg.sugerencias.some((s) => !s.aceptada) && ( + + )} +
+ + {msg.sugerencias.map((sug: any) => ( +
+ {!sug.aceptada && ( + + toggleImprovementSelection(sug.id) + } + /> + )} +
+ + setSelectedFields((prev) => + prev.filter((f) => f.key !== key), + ) + } + /> +
+
+ ))} + + {/* EL BOTÓN DE APLICAR: Lo movemos para que solo aparezca si hay algo seleccionado de ESTE mensaje */} + {msg.sugerencias.some((s) => + selectedImprovements.includes(s.id), + ) && ( + + )}
)} diff --git a/src/components/asignaturas/detalle/SaveAsignatura/ImprovementCardProps.tsx b/src/components/asignaturas/detalle/SaveAsignatura/ImprovementCardProps.tsx index 6fabf3c..6dab5eb 100644 --- a/src/components/asignaturas/detalle/SaveAsignatura/ImprovementCardProps.tsx +++ b/src/components/asignaturas/detalle/SaveAsignatura/ImprovementCardProps.tsx @@ -15,6 +15,7 @@ interface ImprovementCardProps { sug: IASugerencia asignaturaId: string onApplied: (campoKey: string) => void + isSelected?: boolean } export function ImprovementCard({ diff --git a/src/routes/planes/$planId/_detalle/iaplan.tsx b/src/routes/planes/$planId/_detalle/iaplan.tsx index db91b2f..2329252 100644 --- a/src/routes/planes/$planId/_detalle/iaplan.tsx +++ b/src/routes/planes/$planId/_detalle/iaplan.tsx @@ -40,6 +40,8 @@ import { useMessagesByChat, useUpdateConversationStatus, useUpdateConversationTitle, + useUpdatePlanFields, + useUpdateRecommendationApplied, } from '@/data' import { usePlan } from '@/data/hooks/usePlans' @@ -143,6 +145,12 @@ function RouteComponent() { const [isHistoryOpen, setIsHistoryOpen] = useState(false) const [isActionsOpen, setIsActionsOpen] = useState(false) + const [selectedImprovements, setSelectedImprovements] = useState< + Array + >([]) + const updatePlan = useUpdatePlanFields() + const updateAppliedStatus = useUpdateRecommendationApplied() + const availableFields = useMemo(() => { const definicion = data?.estructuras_plan ?.definicion as EstructuraDefinicion @@ -210,6 +218,96 @@ function RouteComponent() { return messages }) }, [mensajesDelChat, activeChatId, availableFields]) + + // En tu componente principal (el padre) + + const handleApplyMultiple = async ( + sugerencias: Array, + dbMessageId: string, + ) => { + if (!planId || !data?.datos || sugerencias.length === 0) return + + setIsSending(true) + try { + const datosActualizados = { ...data.datos } + + for (const sug of sugerencias) { + const key = sug.key + const newValue = sug.newValue + const currentValue = datosActualizados[key] + + if ( + typeof currentValue === 'object' && + currentValue !== null && + 'description' in currentValue + ) { + datosActualizados[key] = { ...currentValue, description: newValue } + } else { + datosActualizados[key] = newValue + } + } + + await updatePlan.mutateAsync({ + planId: planId as any, + patch: { datos: datosActualizados }, + }) + + for (const sug of sugerencias) { + try { + await updateAppliedStatus.mutateAsync({ + conversacionId: dbMessageId, + campoAfectado: sug.key, + }) + console.log(sug.key) + + removeSelectedField(sug.key) + } catch (err) { + console.error( + `Error al marcar aplicada la sugerencia: ${sug.key}`, + err, + ) + // Si una falla, el bucle sigue con las demás + } + } + + setSelectedImprovements([]) + await Promise.all([ + queryClient.invalidateQueries({ queryKey: ['plan', planId] }), + queryClient.invalidateQueries({ queryKey: ['conversation-messages'] }), + ]) + } catch (error) { + console.error('Error crítico en aplicación masiva:', error) + } finally { + setIsSending(false) + } + } + + const toggleImprovementSelection = (sugKey: string) => { + setSelectedImprovements((prev) => + prev.includes(sugKey) + ? prev.filter((key) => key !== sugKey) + : [...prev, sugKey], + ) + } + + const toggleAllFromMessage = (suggestions: Array) => { + const pending = suggestions.filter((s) => !s.applied) + const allKeys = pending.map((s) => s.key) + const allSelected = allKeys.every((key) => + selectedImprovements.includes(key), + ) + + if (allSelected) { + setSelectedImprovements((prev) => + prev.filter((key) => !allKeys.includes(key)), + ) + } else { + setSelectedImprovements((prev) => + Array.from(new Set([...prev, ...allKeys])), + ) + } + } + const scrollToBottom = (behavior: ScrollBehavior = 'smooth') => { if (scrollRef.current) { const scrollContainer = scrollRef.current.querySelector( @@ -478,6 +576,7 @@ function RouteComponent() { }, [selectedArchivoIds, selectedRepositorioIds, uploadedFiles]) const removeSelectedField = (fieldKey: string) => { + console.log(fieldKey) setSelectedFields((prev) => prev.filter((f) => f.key !== fieldKey)) } @@ -752,17 +851,98 @@ function RouteComponent() { {/* Recomendaciones */} {isAI && msg.suggestions?.length > 0 && ( -
- - removeSelectedField(key) - } - /> +
+
+ + Sugerencias de mejora + + {/* Botón Seleccionar Todo */} + {msg.suggestions.some( + (s: any) => !s.applied, + ) && ( + + )} +
+ + {msg.suggestions.map((sug: any) => ( +
+ {!sug.applied && ( + + toggleImprovementSelection(sug.key) + } + /> + )} +
+ + removeSelectedField(key) + } + /> +
+
+ ))} + + {/* Botón de Acción Masiva específico para este mensaje */} + {msg.suggestions.some((s: any) => + selectedImprovements.includes(s.key), + ) && ( + + )}
)}
-- 2.52.0