import { createFileRoute, useNavigate, useParams, useRouterState, } from '@tanstack/react-router' import { Pencil, Sparkles } from 'lucide-react' import { useCallback, 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' import { Textarea } from '@/components/ui/textarea' import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from '@/components/ui/tooltip' import { useSubject, useUpdateAsignatura } from '@/data/hooks/useSubjects' export interface BibliografiaEntry { id: string tipo: 'BASICA' | 'COMPLEMENTARIA' cita: string fuenteBibliotecaId?: string fuenteBiblioteca?: any } export interface BibliografiaTabProps { id: string bibliografia: Array onSave: (bibliografia: Array) => void isSaving: boolean } export interface AsignaturaDatos { [key: string]: string } export interface AsignaturaResponse { datos: AsignaturaDatos } 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', )({ component: AsignaturaDetailPage, }) 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 [asignatura, setAsignatura] = useState({}) const [campos, setCampos] = 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 as any)?.datos ?? (asignaturaApi as any)?.datos ?? {} const mergedDatos = { ...baseDatos, [clave]: value } // Mantener estado local coherente para merges posteriores. setAsignatura((prev: any) => ({ ...(prev && Object.keys(prev).length ? prev : ((asignaturaApi as any) ?? {})), datos: mergedDatos, })) updateAsignatura.mutate({ asignaturaId, patch: { datos: mergedDatos, }, }) } /* ---------- sincronizar API ---------- */ useEffect(() => { if (asignaturaApi?.datos) { 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 isLoading: boolean onPersistDato: (clave: string, value: string) => void } function DatosGenerales({ onPersistDato, }: { onPersistDato: (clave: string, value: string) => void }) { const { asignaturaId } = useParams({ from: '/planes/$planId/asignaturas/$asignaturaId', }) const { data: data, isLoading: isLoading } = useSubject(asignaturaId) const structureProps = (data?.estructuras_asignatura?.definicion as EstructuraDefinicion) .properties || {} const valoresActuales = data?.datos || {} if (isLoading) return

Cargando información...

return (
{/* Encabezado de la Sección */}

Datos Generales

Información oficial estructurada bajo los lineamientos de la SEP.

{/* Grid de Información */}
{/* Columna Principal (Más ancha) */}
{Object.entries(structureProps).map( ([key, config]: [string, any]) => { const cardTitle = config.title || key const description = config.description || '' const placeholder = config.examples && config.examples.length > 0 ? config.examples[0] : '' const valActual = (valoresActuales as Record)[key] const currentContent = valActual ?? '' return ( onPersistDato(clave, value)} /> ) }, )}
{/* Columna Lateral (Información Secundaria) */}
{/* Tarjeta de Requisitos */} {/* Tarjeta de Evaluación */}
) } interface InfoCardProps { asignaturaId?: 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 onPersist?: (clave: string, value: string) => void } function InfoCard({ asignaturaId, clave, title, initialContent, placeholder, description, required, type = 'text', onPersist, }: InfoCardProps) { const [isEditing, setIsEditing] = useState(false) const [data, setData] = useState(initialContent) const [tempText, setTempText] = useState(initialContent) const navigate = useNavigate() const { planId } = useParams({ from: '/planes/$planId/asignaturas/$asignaturaId', }) useEffect(() => { setData(initialContent) setTempText(initialContent) }, [initialContent]) const handleSave = () => { console.log('clave, valor:', clave, String(tempText ?? '')) setData(tempText) setIsEditing(false) if (type === 'text' && clave && onPersist) { onPersist(clave, String(tempText ?? '')) } } const handleIARequest = (campoClave: string) => { console.log(placeholder) navigate({ to: '/planes/$planId/asignaturas/$asignaturaId/iaasignatura', params: { planId, asignaturaId: asignaturaId! }, state: { activeTab: 'ia', prefillCampo: campoClave, prefillContenido: data, } as any, }) } return (
{title} {description || 'Información del campo'} {required && ( * )}
{!isEditing && (
Mejorar con IA Editar campo
)}
{isEditing ? (