Que se puedan aplicar todos los campos y todas las mejoras

fix #165
This commit is contained in:
2026-03-25 15:14:29 -06:00
parent 1f27ea6252
commit f2900f3d22
3 changed files with 354 additions and 33 deletions
@@ -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<HTMLDivElement>(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<string>
>([])
const [uploadedFiles, setUploadedFiles] = useState<Array<File>>([])
const [selectedImprovements, setSelectedImprovements] = useState<
Array<string>
>([])
const handleApplyMultiple = async (sugerencias: Array<any>) => {
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<any>) => {
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 && (
<div className="space-y-3 border-t bg-slate-50/50 p-3">
<p className="mb-1 text-[10px] font-bold text-slate-400 uppercase">
Mejoras disponibles:
</p>
{msg.sugerencias.map((sug: any) => (
<ImprovementCard
key={sug.id}
sug={sug}
asignaturaId={asignaturaId}
onApplied={(campoFinalizado) => {
// Filtramos el array para conservar todos MENOS el que se aplicó
console.log(campoFinalizado)
console.log('campos:', selectedFields)
<div className="flex items-center justify-between">
<p className="text-[10px] font-bold text-slate-400 uppercase">
Mejoras disponibles
</p>
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) && (
<Button
variant="ghost"
className="h-6 text-[10px] text-teal-600 hover:bg-teal-100"
onClick={() =>
toggleAllFromMessage(msg.sugerencias)
}
>
{msg.sugerencias
.filter((s) => !s.aceptada)
.every((s) =>
selectedImprovements.includes(s.id),
)
? 'Desmarcar todas'
: 'Seleccionar todas'}
</Button>
)}
</div>
{msg.sugerencias.map((sug: any) => (
<div key={sug.id} className="flex gap-2">
{!sug.aceptada && (
<input
type="checkbox"
className="mt-4 h-4 w-4 rounded border-slate-300 accent-teal-600"
checked={selectedImprovements.includes(
sug.id,
)}
onChange={() =>
toggleImprovementSelection(sug.id)
}
/>
)}
<div className="flex-1">
<ImprovementCard
sug={sug}
asignaturaId={asignaturaId}
onApplied={(key) =>
setSelectedFields((prev) =>
prev.filter((f) => f.key !== key),
)
}
/>
</div>
</div>
))}
{/* 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),
) && (
<Button
size="sm"
disabled={isSending}
className="w-full bg-teal-600 text-xs font-bold shadow-md hover:bg-teal-700"
onClick={() => {
const seleccionadasDeEsteMensaje =
msg.sugerencias.filter((s) =>
selectedImprovements.includes(s.id),
)
handleApplyMultiple(
seleccionadasDeEsteMensaje,
)
}}
/>
))}
>
{isSending ? (
<Loader2
className="mr-2 animate-spin"
size={14}
/>
) : (
<Check className="mr-2" size={14} />
)}
Aplicar mejoras seleccionadas
</Button>
)}
</div>
)}
</div>