diff --git a/src/components/asignaturas/detalle/IAAsignaturaTab.tsx b/src/components/asignaturas/detalle/IAAsignaturaTab.tsx
index 4e087b8..45e5bd5 100644
--- a/src/components/asignaturas/detalle/IAAsignaturaTab.tsx
+++ b/src/components/asignaturas/detalle/IAAsignaturaTab.tsx
@@ -131,15 +131,45 @@ export function IAAsignaturaTab({
}, [todasConversaciones])
const availableFields = useMemo(() => {
- if (!datosGenerales?.datos) return []
- const estructuraProps =
- datosGenerales?.estructuras_asignatura?.definicion?.properties || {}
- return Object.keys(datosGenerales.datos).map((key) => ({
- key,
- label:
- estructuraProps[key]?.title || key.replace(/_/g, ' ').toUpperCase(),
- value: String(datosGenerales.datos[key] || ''),
- }))
+ // 1. Obtenemos los campos dinámicos de la DB
+ const dynamicFields = datosGenerales?.datos
+ ? Object.keys(datosGenerales.datos).map((key) => {
+ const estructuraProps =
+ datosGenerales?.estructuras_asignatura?.definicion?.properties || {}
+ return {
+ key,
+ label:
+ estructuraProps[key]?.title ||
+ key.replace(/_/g, ' ').toUpperCase(),
+ value: String(datosGenerales.datos[key] || ''),
+ }
+ })
+ : []
+
+ // 2. Definimos tus campos manuales (hardcoded)
+ const hardcodedFields = [
+ {
+ key: 'contenido_tematico',
+ label: 'Contenido temático',
+ value: '', // Puedes dejarlo vacío o buscarlo en datosGenerales si existiera
+ },
+ {
+ key: 'criterios_de_evaluacion',
+ label: 'Criterios de evaluación',
+ value: '',
+ },
+ ]
+
+ // 3. Unimos ambos, filtrando duplicados por si acaso el backend ya los envía
+ const combined = [...dynamicFields]
+
+ hardcodedFields.forEach((hf) => {
+ if (!combined.some((f) => f.key === hf.key)) {
+ combined.push(hf)
+ }
+ })
+
+ return combined
}, [datosGenerales])
// --- PROCESAMIENTO DE MENSAJES ---
@@ -269,7 +299,7 @@ export function IAAsignaturaTab({
}
setInput('')
- setSelectedFields([])
+ // setSelectedFields([])
// Invalidamos la lista de conversaciones para que el nuevo chat aparezca en el historial (panel izquierdo)
queryClient.invalidateQueries({
@@ -511,6 +541,7 @@ export function IAAsignaturaTab({
>
{/* Texto del mensaje principal */}
{
+ // Filtramos el array para conservar todos MENOS el que se aplicó
+ console.log(campoFinalizado)
+ console.log('campos:', selectedFields)
+
+ setSelectedFields((prev) =>
+ prev.filter((fieldObj) => {
+ // Accedemos a .key porque fieldObj es { key: "...", label: "..." }
+ return fieldObj.key !== campoFinalizado
+ }),
+ )
+ }}
/>
))}
diff --git a/src/components/asignaturas/detalle/SaveAsignatura/ImprovementCardProps.tsx b/src/components/asignaturas/detalle/SaveAsignatura/ImprovementCardProps.tsx
index 83dac0d..6fabf3c 100644
--- a/src/components/asignaturas/detalle/SaveAsignatura/ImprovementCardProps.tsx
+++ b/src/components/asignaturas/detalle/SaveAsignatura/ImprovementCardProps.tsx
@@ -1,4 +1,4 @@
-import { Check, Loader2 } from 'lucide-react'
+import { Check, Loader2, BookOpen, Clock, ListChecks } from 'lucide-react'
import { useState } from 'react'
import type { IASugerencia } from '@/types/asignatura'
@@ -7,50 +7,65 @@ import { Button } from '@/components/ui/button'
import {
useUpdateAsignatura,
useSubject,
- useUpdateSubjectRecommendation, // Importamos tu nuevo hook
+ useUpdateSubjectRecommendation,
} from '@/data'
+import { cn } from '@/lib/utils'
interface ImprovementCardProps {
sug: IASugerencia
asignaturaId: string
+ onApplied: (campoKey: string) => void
}
-export function ImprovementCard({ sug, asignaturaId }: ImprovementCardProps) {
+export function ImprovementCard({
+ sug,
+ asignaturaId,
+ onApplied,
+}: ImprovementCardProps) {
const { data: asignatura } = useSubject(asignaturaId)
const updateAsignatura = useUpdateAsignatura()
-
- // Hook para marcar en la base de datos que la sugerencia fue aceptada
const updateRecommendation = useUpdateSubjectRecommendation()
const [isApplying, setIsApplying] = useState(false)
const handleApply = async () => {
- if (!asignatura?.datos) return
+ if (!asignatura) return
setIsApplying(true)
try {
- // 1. Actualizar el contenido real de la asignatura (JSON datos)
- const nuevosDatos = {
- ...asignatura.datos,
- [sug.campoKey]: sug.valorSugerido,
+ // 1. Identificar a qué columna debe ir el guardado
+ let patchData = {}
+
+ if (sug.campoKey === 'contenido_tematico') {
+ // Se guarda directamente en la columna contenido_tematico
+ patchData = { contenido_tematico: sug.valorSugerido }
+ } else if (sug.campoKey === 'criterios_de_evaluacion') {
+ // Se guarda directamente en la columna criterios_de_evaluacion
+ patchData = { criterios_de_evaluacion: sug.valorSugerido }
+ } else {
+ // Otros campos (ciclo, fines, etc.) se siguen guardando en el JSON de la columna 'datos'
+ patchData = {
+ datos: {
+ ...asignatura.datos,
+ [sug.campoKey]: sug.valorSugerido,
+ },
+ }
}
+ // 2. Ejecutar la actualización con la estructura correcta
await updateAsignatura.mutateAsync({
asignaturaId: asignaturaId as any,
- patch: {
- datos: nuevosDatos,
- } as any,
+ patch: patchData as any,
})
- // 2. Marcar la sugerencia como "aplicada: true" en la tabla de mensajes
- // Usamos los datos que vienen en el objeto 'sug'
+ // 3. Marcar la recomendación como aplicada
await updateRecommendation.mutateAsync({
mensajeId: sug.messageId,
campoAfectado: sug.campoKey,
})
+ console.log(sug.campoKey)
- // Al terminar, React Query invalidará 'subject-messages'
- // y la card pasará automáticamente al estado "Aplicado" (gris)
+ onApplied(sug.campoKey)
} catch (error) {
console.error('Error al aplicar mejora:', error)
} finally {
@@ -58,10 +73,89 @@ export function ImprovementCard({ sug, asignaturaId }: ImprovementCardProps) {
}
}
+ // --- FUNCIÓN PARA RENDERIZAR EL CONTENIDO DE FORMA SEGURA ---
+ const renderContenido = (valor: any) => {
+ // Si no es un array, es texto simple
+ if (!Array.isArray(valor)) {
+ return "{String(valor)}"
+ }
+
+ // --- CASO 1: CONTENIDO TEMÁTICO (Detectamos si el primer objeto tiene 'unidad') ---
+ if (valor[0]?.hasOwnProperty('unidad')) {
+ return (
+
+ {valor.map((u: any, idx: number) => (
+
+
+ Unidad {u.unidad}: {u.titulo}
+
+
+ {u.temas?.map((t: any, tidx: number) => (
+ -
+ • {t.nombre}
+
+ {t.horasEstimadas}h
+
+
+ ))}
+
+
+ ))}
+
+ )
+ }
+
+ // --- CASO 2: CRITERIOS DE EVALUACIÓN (Detectamos si tiene 'criterio') ---
+ if (valor[0]?.hasOwnProperty('criterio')) {
+ return (
+
+
+ Desglose de evaluación
+
+ {valor.map((c: any, idx: number) => (
+
+
+ {c.criterio}
+
+
+ {c.porcentaje}%
+
+
+ ))}
+ {/* Opcional: Suma total para verificar que de 100% */}
+
+ Total:{' '}
+ {valor.reduce(
+ (acc: number, curr: any) => acc + (curr.porcentaje || 0),
+ 0,
+ )}
+ %
+
+
+ )
+ }
+
+ // Caso por defecto (Array genérico)
+ return (
+
+ {/* JSON.stringify(valor, null, 2)*/ 'hola'}
+
+ )
+ }
+
// --- ESTADO APLICADO ---
if (sug.aceptada) {
return (
-
+
{sug.campoNombre}
@@ -72,7 +166,7 @@ export function ImprovementCard({ sug, asignaturaId }: ImprovementCardProps) {
- "{sug.valorSugerido}"
+ {renderContenido(sug.valorSugerido)}
)
@@ -101,8 +195,13 @@ export function ImprovementCard({ sug, asignaturaId }: ImprovementCardProps) {
-
- "{sug.valorSugerido}"
+
+ {renderContenido(sug.valorSugerido)}
)
diff --git a/src/routes/planes/$planId/_detalle/iaplan.tsx b/src/routes/planes/$planId/_detalle/iaplan.tsx
index ee3519d..a2e0ee8 100644
--- a/src/routes/planes/$planId/_detalle/iaplan.tsx
+++ b/src/routes/planes/$planId/_detalle/iaplan.tsx
@@ -227,7 +227,7 @@ function RouteComponent() {
scrollToBottom()
}, [chatMessages, isLoading])
- useEffect(() => {
+ /* useEffect(() => {
// Verificamos cuáles campos de la lista "selectedFields" ya no están presentes en el texto del input
const camposActualizados = selectedFields.filter((field) =>
input.includes(field.label),
@@ -237,7 +237,7 @@ function RouteComponent() {
if (camposActualizados.length !== selectedFields.length) {
setSelectedFields(camposActualizados)
}
- }, [input, selectedFields])
+ }, [input, selectedFields]) */
useEffect(() => {
if (isLoadingConv || isSending) return
@@ -297,7 +297,7 @@ function RouteComponent() {
},
])
setInput('')
- setSelectedFields([])
+ // setSelectedFields([])
}
const archiveChat = (e: React.MouseEvent, id: string) => {
@@ -405,7 +405,7 @@ function RouteComponent() {
setIsSending(true)
setOptimisticMessage(finalContent)
setInput('')
- setSelectedFields([])
+ // setSelectedFields([])
try {
const payload = {