diff --git a/src/components/asignaturas/detalle/IAMateriaTab.tsx b/src/components/asignaturas/detalle/IAMateriaTab.tsx index faffe89..40ce816 100644 --- a/src/components/asignaturas/detalle/IAMateriaTab.tsx +++ b/src/components/asignaturas/detalle/IAMateriaTab.tsx @@ -87,7 +87,8 @@ export function IAMateriaTab({ const availableFields = useMemo(() => { // Extraemos las claves directamente del objeto datosGenerales // ["nombre", "descripcion", "perfil_de_egreso", "fines_de_aprendizaje_o_formacion"] - return Object.keys(datosGenerales).map((key) => { + if (!datosGenerales.datos) return [] + return Object.keys(datosGenerales.datos).map((key) => { // Buscamos si existe un nombre amigable en la estructura de campos const estructuraCampo = campos.find((c) => c.id === key) @@ -110,12 +111,15 @@ export function IAMateriaTab({ const state = routerState.location.state as any if (state?.prefillCampo && availableFields.length > 0) { + console.log(state?.prefillCampo) + console.log(availableFields) + const field = availableFields.find((f) => f.key === state.prefillCampo) if (field && !selectedFields.find((sf) => sf.key === field.key)) { setSelectedFields([field]) // Sincronizamos el texto inicial con el campo pre-seleccionado - setInput(`Mejora el campo ${field.key}: `) + setInput(`Mejora el campo ${field.label}: `) } } // eslint-disable-next-line react-hooks/exhaustive-deps @@ -139,37 +143,32 @@ export function IAMateriaTab({ setSelectedFields((prev) => { const isSelected = prev.find((f) => f.key === field.key) - // Si lo estamos seleccionando (no estaba antes) - if (!isSelected) { - // Actualizamos el texto del input: - // Si termina en ":", lo reemplazamos por el key para que sea "Mejora perfil_de_egreso " - // Si no, simplemente lo añadimos al final. - setInput((prevText) => { - const [beforeColon, afterColon = ''] = prevText.split(':') - - // Campos ya escritos después de : - const existingKeys = afterColon - .split(',') - .map((k) => k.trim()) - .filter(Boolean) - - // Si ya existe, no lo volvemos a agregar - if (existingKeys.includes(field.key)) { - return prevText - } - - const updatedKeys = [...existingKeys, field.key].join(', ') - - return `${beforeColon.trim()}: ${updatedKeys} ` - }) - - return [field] + // 1. Si ya está seleccionado, lo quitamos (Toggle OFF) + if (isSelected) { + return prev.filter((f) => f.key !== field.key) } - // Si lo estamos deseleccionando, solo quitamos el tag - return prev.filter((f) => f.key !== field.key) + // 2. Si no está, lo agregamos a la lista (Toggle ON) + const newSelected = [...prev, field] + + // 3. Actualizamos el texto del input para reflejar los títulos (labels) + setInput((prevText) => { + // Separamos lo que el usuario escribió antes del disparador ":" + // y lo que viene después (posibles keys/labels previos) + const parts = prevText.split(':') + const beforeColon = parts[0] + + // Creamos un string con los labels de todos los campos seleccionados + const labelsPath = newSelected.map((f) => f.label).join(', ') + + return `${beforeColon.trim()}: ${labelsPath} ` + }) + + return newSelected }) - setShowSuggestions(false) + + // Opcional: mantener abierto si quieres que el usuario elija varios seguidos + // setShowSuggestions(false) } const buildPrompt = (userInput: string) => { diff --git a/src/components/asignaturas/detalle/MateriaDetailPage.tsx b/src/components/asignaturas/detalle/MateriaDetailPage.tsx index 66e996c..834dd59 100644 --- a/src/components/asignaturas/detalle/MateriaDetailPage.tsx +++ b/src/components/asignaturas/detalle/MateriaDetailPage.tsx @@ -22,6 +22,12 @@ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' import { Separator } from '@/components/ui/separator' import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs' import { Textarea } from '@/components/ui/textarea' +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from '@/components/ui/tooltip' import { useSubject } from '@/data/hooks/useSubjects' import { mockMateria, @@ -150,7 +156,7 @@ export default function MateriaDetailPage() { /* ---------- sincronizar API ---------- */ useEffect(() => { if (asignaturasApi?.datos) { - setDatosGenerales(asignaturasApi.datos) + setDatosGenerales(asignaturasApi) } }, [asignaturasApi]) @@ -211,7 +217,7 @@ export default function MateriaDetailPage() {
@@ -392,6 +398,13 @@ function DatosGenerales({ const formatTitle = (key: string): string => key.replace(/_/g, ' ').replace(/\b\w/g, (l: string) => l.toUpperCase()) + // 1. Extraemos la definición de la estructura (los metadatos) + const structureProps = + data?.estructuras_asignatura?.definicion?.properties || {} + + // 2. Extraemos los valores reales (el contenido redactado) + const valoresActuales = data?.datos || {} + return (
{/* Encabezado de la Sección */} @@ -413,19 +426,47 @@ function DatosGenerales({ {isLoading &&

Cargando información...

} {!isLoading && - Object.entries(data).map(([key, value]) => ( - { - console.log('Llevar a IA:', contenido) - // Aquí tu lógica: setPestañaActiva('mejorar-con-ia'); - }} - /> - ))} + Object.entries(structureProps).map( + ([key, config]: [string, any]) => { + // 1. METADATOS (Vienen de structureProps -> config) + const cardTitle = config.title || key + const description = config.description || '' + + // Obtenemos el placeholder del arreglo 'examples' de la estructura + const placeholder = + config.examples && config.examples.length > 0 + ? config.examples[0] + : '' + + // 2. CONTENIDO REAL (Viene de data.datos -> valoresActuales) + // El problema: Si 'description' en 'datos' es igual a la de la 'estructura', + // el usuario aún no ha redactado nada real. + + const valActual = valoresActuales[key] + + // Lógica para determinar si mostrar el contenido o dejarlo vacío (para que salga el placeholder) + // Si el contenido en 'datos' es idéntico a la instrucción de la 'estructura', + // asumimos que no hay contenido real todavía. + const isContentEmpty = + !valActual?.description || + valActual.description === config.description + + const currentContent = valActual.description ?? '' + + return ( + console.log(contenido)} + /> + ) + }, + )}
{/* Columna Lateral (Información Secundaria) */} @@ -469,11 +510,14 @@ function DatosGenerales({ interface InfoCardProps { asignaturaId?: string - clave: string + clave?: string title: string initialContent: any + placeholder?: string + description?: string + required?: boolean // Nueva prop para el asterisco type?: 'text' | 'requirements' | 'evaluation' - onEnhanceAI?: (content: any) => void // Nueva prop para la acción de IA + onEnhanceAI?: (content: any) => void } function InfoCard({ @@ -481,76 +525,111 @@ function InfoCard({ clave, title, initialContent, + placeholder, + description, + required, type = 'text', - onEnhanceAI, }: InfoCardProps) { const [isEditing, setIsEditing] = useState(false) const [data, setData] = useState(initialContent) - const [tempText, setTempText] = useState( - type === 'text' ? initialContent : JSON.stringify(initialContent, null, 2), - ) + const [tempText, setTempText] = useState(initialContent) const navigate = useNavigate() + + useEffect(() => { + setData(initialContent) + setTempText(initialContent) + }, [initialContent]) + const handleSave = () => { setData(tempText) setIsEditing(false) + // Aquí iría tu lógica de guardado a la DB } - const handleIARequest = (data) => { - console.log(data) - console.log(asignaturaId) + + const handleIARequest = (campoClave: string) => { + console.log(placeholder) navigate({ to: '/planes/$planId/asignaturas/$asignaturaId', - params: { - asignaturaId: asignaturaId, - }, + params: { asignaturaId: asignaturaId! }, state: { activeTab: 'ia', - prefillCampo: data, - prefillContenido: data, // el contenido actual del card + prefillCampo: campoClave, + prefillContenido: data, } as any, }) } return ( - - - - {title} - + + + +
+
+ + + + {title} + + + + {description || 'Información del campo'} + + - {!isEditing && ( -
- {/* NUEVO: Botón de Mejorar con IA */} - + {required && ( + + * + + )} +
- {/* Botón de Editar original */} - + {!isEditing && ( +
+ + + + + Mejorar con IA + + + + + + + Editar campo + +
+ )}
- )} - + + - + {isEditing ? (