diff --git a/src/components/asignaturas/detalle/AsignaturaDetailPage.tsx b/src/components/asignaturas/detalle/AsignaturaDetailPage.tsx index f95cbf6..9828078 100644 --- a/src/components/asignaturas/detalle/AsignaturaDetailPage.tsx +++ b/src/components/asignaturas/detalle/AsignaturaDetailPage.tsx @@ -1,18 +1,8 @@ -import { - createFileRoute, - useNavigate, - useParams, - useRouterState, -} from '@tanstack/react-router' +import { createFileRoute, useNavigate, useParams } from '@tanstack/react-router' import { Pencil, Sparkles } from 'lucide-react' -import { useCallback, useState, useEffect } from 'react' +import { useState, useEffect } from 'react' import type { AsignaturaDetail } from '@/data' -import type { - CampoEstructura, - IAMessage, - IASugerencia, -} from '@/types/asignatura' import { Button } from '@/components/ui/button' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' @@ -97,47 +87,6 @@ const columnParsers: Partial string>> = { contenido_tematico: parseContenidoTematicoToPlainText, } -function EditableHeaderField({ - value, - onSave, - className, -}: { - value: string | number - onSave: (val: string) => void - className?: string -}) { - const textValue = String(value) - - // Manejador para cuando el usuario termina de editar (pierde el foco) - const handleBlur = (e: React.FocusEvent) => { - const newValue = e.currentTarget.innerText - if (newValue !== textValue) { - onSave(newValue) - } - } - - const handleKeyDown = (e: React.KeyboardEvent) => { - if (e.key === 'Enter') { - e.preventDefault() - e.currentTarget.blur() // Forzamos el guardado al presionar Enter - } - } - - return ( - // eslint-disable-next-line jsx-a11y/no-static-element-interactions - - {textValue} - - ) -} - export const Route = createFileRoute( '/planes/$planId/asignaturas/$asignaturaId', )({ @@ -145,67 +94,14 @@ export const Route = createFileRoute( }) export default function AsignaturaDetailPage() { - const routerState = useRouterState() - const state = routerState.location.state as any const { asignaturaId } = useParams({ from: '/planes/$planId/asignaturas/$asignaturaId', }) - const { planId } = useParams({ - from: '/planes/$planId/asignaturas/$asignaturaId', - }) - const { data: asignaturaApi, isLoading: loadingAsig } = - useSubject(asignaturaId) - // 1. Asegúrate de tener estos estados en tu componente principal - const [messages, setMessages] = useState>([]) + const { data: asignaturaApi } = useSubject(asignaturaId) + const [asignatura, setAsignatura] = useState(null) - const [campos] = useState>([]) - const [activeTab, setActiveTab] = useState('datos') const updateAsignatura = useUpdateAsignatura() - // Dentro de AsignaturaDetailPage - const [headerData, setHeaderData] = useState({ - codigo: '', - nombre: '', - creditos: 0, - ciclo: 0, - }) - - useEffect(() => { - // Si en el state de la ruta viene una pestaña específica, cámbiate a ella - if (state?.activeTab) { - setActiveTab(state.activeTab) - } - }, [state]) - - // Sincronizar cuando llegue la API - useEffect(() => { - if (asignaturaApi) { - setHeaderData({ - codigo: asignaturaApi.codigo ?? '', - nombre: asignaturaApi.nombre, - creditos: asignaturaApi.creditos, - ciclo: asignaturaApi.numero_ciclo ?? 0, - }) - } - }, [asignaturaApi]) - - const handleUpdateHeader = (key: string, value: string | number) => { - const newData = { ...headerData, [key]: value } - setHeaderData(newData) - - const patch: Record = - key === 'ciclo' - ? { numero_ciclo: value } - : { - [key]: value, - } - - updateAsignatura.mutate({ - asignaturaId, - patch, - }) - } - const handlePersistDatoGeneral = (clave: string, value: string) => { const baseDatos = asignatura?.datos ?? (asignaturaApi as any)?.datos ?? {} const mergedDatos = { ...baseDatos, [clave]: value } @@ -228,76 +124,9 @@ export default function AsignaturaDetailPage() { if (asignaturaApi) setAsignatura(asignaturaApi) }, [asignaturaApi]) - // 2. Funciones de manejo para la IA - const handleSendMessage = (text: string, campoId?: string) => { - const newMessage: IAMessage = { - id: Date.now().toString(), - role: 'user', - content: text, - timestamp: new Date(), - campoAfectado: campoId, - } - setMessages([...messages, newMessage]) - - // Aquí llamarías a tu API de OpenAI/Claude - // toast.info("Enviando consulta a la IA..."); - } - - const handleAcceptSuggestion = (_sugerencia: IASugerencia) => { - // Lógica para actualizar el valor del campo en tu estado de asignatura - // toast.success(`Sugerencia aplicada a ${sugerencia.campoNombre}`); - } - - // Dentro de tu componente principal (donde están los Tabs) - const [bibliografia, setBibliografia] = useState>([ - { - id: '1', - tipo: 'BASICA', - cita: 'Russell, S., & Norvig, P. (2020). Artificial Intelligence: A Modern Approach. Pearson.', - }, - ]) - const [isSaving, setIsSaving] = useState(false) - - const handleSaveBibliografia = (data: Array) => { - setIsSaving(true) - // Aquí iría tu llamada a la API - setBibliografia(data) - - // Simulamos un guardado - setTimeout(() => { - setIsSaving(false) - // toast.success("Cambios guardados"); - }, 1000) - } - - const [isRegenerating, setIsRegenerating] = useState(false) - - const handleRegenerateDocument = useCallback(() => { - setIsRegenerating(true) - setTimeout(() => { - setIsRegenerating(false) - }, 2000) - }, []) - return } -interface EstructuraDefinicion { - properties?: Record< - string, - { - title?: string - description?: string - examples?: Array - } - > -} -interface DatosGeneralesProps { - asignaturaId: string - data: AsignaturaDetail | null - isLoading: boolean - onPersistDato: (clave: string, value: string) => void -} function DatosGenerales({ onPersistDato, }: { @@ -309,10 +138,22 @@ function DatosGenerales({ const { data: data, isLoading: isLoading } = useSubject(asignaturaId) - const structureProps = - (data?.estructuras_asignatura?.definicion as EstructuraDefinicion) - .properties || {} - const valoresActuales = data?.datos || {} + // 1. Extraemos la definición de la estructura (los metadatos) + const definicionRaw = data?.estructuras_asignatura?.definicion + const definicion = isRecord(definicionRaw) + ? (definicionRaw as Record) + : null + + const propertiesRaw = definicion ? (definicion as any).properties : undefined + const structureProps = isRecord(propertiesRaw) + ? (propertiesRaw as Record) + : {} + + // 2. Extraemos los valores reales (el contenido redactado) + const datosRaw = data?.datos + const valoresActuales = isRecord(datosRaw) + ? (datosRaw as Record) + : {} if (isLoading) return

Cargando información...

return ( @@ -338,13 +179,28 @@ function DatosGenerales({ const cardTitle = config.title || key const description = config.description || '' + const xColumn = + typeof config?.['x-column'] === 'string' + ? config['x-column'] + : undefined + + // Obtenemos el placeholder del arreglo 'examples' de la estructura const placeholder = config.examples && config.examples.length > 0 ? config.examples[0] : '' - const valActual = (valoresActuales as Record)[key] - const currentContent = valActual ?? '' + const valActual = valoresActuales[key] + + let currentContent = valActual ?? '' + + if (xColumn) { + const rawValue = (data as any)?.[xColumn] + const parser = columnParsers[xColumn] + currentContent = parser + ? parser(rawValue) + : String(rawValue ?? '') + } return ( onPersistDato(clave, value)} @@ -524,12 +381,8 @@ function InfoCard({ // Agregamos un timestamp para forzar la actualización // de la location.state aunque la ruta sea la misma. navigate({ - to: '/planes/$planId/asignaturas/$asignaturaId', + to: '/planes/$planId/asignaturas/$asignaturaId/contenido', params: { planId, asignaturaId: asignaturaId! }, - state: { - activeTab: 'contenido', - _ts: Date.now(), - } as any, }) return }