import { createFileRoute, useNavigate, useLocation, } from '@tanstack/react-router' // import confetti from 'canvas-confetti' import { Pencil, Check, X, Sparkles, AlertCircle } from 'lucide-react' import { useState, useEffect } from 'react' import type { DatosGeneralesField } from '@/types/plan' import { Button } from '@/components/ui/button' import { lateralConfetti } from '@/components/ui/lateral-confetti' import { Textarea } from '@/components/ui/textarea' import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from '@/components/ui/tooltip' import { usePlan, useUpdatePlanFields } from '@/data' // import { toast } from 'sonner' // Asegúrate de tener sonner instalado o quita la línea export const Route = createFileRoute('/planes/$planId/_detalle/')({ component: DatosGeneralesPage, }) const formatLabel = (key: string) => { const result = key.replace(/_/g, ' ') return result.charAt(0).toUpperCase() + result.slice(1) } function DatosGeneralesPage() { const { planId } = Route.useParams() const { data, isLoading } = usePlan(planId) const navigate = useNavigate() // Inicializamos campos como un arreglo vacío const [campos, setCampos] = useState>([]) const [editingId, setEditingId] = useState(null) const [editValue, setEditValue] = useState('') const location = useLocation() const updatePlan = useUpdatePlanFields() // Confetti al llegar desde creación useEffect(() => { if (location.state.showConfetti) { lateralConfetti() window.history.replaceState({}, document.title) // Limpiar el estado para que no se repita } }, [location.state]) // Efecto para transformar data?.datos en el arreglo de campos useEffect(() => { const definicion = data?.estructuras_plan?.definicion as any const properties = definicion?.properties const requiredOrder = definicion?.required as Array | undefined const valores = (data?.datos as Record) || {} if (properties && typeof properties === 'object') { let keys = Object.keys(properties) // Ordenar llaves basado en la lista "required" si existe if (Array.isArray(requiredOrder)) { keys = keys.sort((a, b) => { const indexA = requiredOrder.indexOf(a) const indexB = requiredOrder.indexOf(b) // Si 'a' está en la lista y 'b' no -> 'a' primero (-1) if (indexA !== -1 && indexB === -1) return -1 // Si 'b' está en la lista y 'a' no -> 'b' primero (1) if (indexA === -1 && indexB !== -1) return 1 // Si ambos están, comparar índices if (indexA !== -1 && indexB !== -1) return indexA - indexB // Ninguno en la lista, mantener orden relativo return 0 }) } const datosTransformados: Array = keys.map( (key, index) => { const schema = properties[key] const rawValue = valores[key] return { clave: key, id: (index + 1).toString(), label: schema?.title || formatLabel(key), helperText: schema?.description || '', holder: schema?.examples || '', value: rawValue !== undefined && rawValue !== null ? String(rawValue) : '', requerido: true, tipo: Array.isArray(schema?.enum) ? 'select' : schema?.type === 'number' ? 'number' : 'texto', opciones: schema?.enum || [], } }, ) setCampos(datosTransformados) } }, [data]) // 3. Manejadores de acciones (Ahora como funciones locales) const handleEdit = (nuevoCampo: DatosGeneralesField) => { // 1. SI YA ESTÁBAMOS EDITANDO OTRO CAMPO, GUARDAMOS EL ANTERIOR PRIMERO if (editingId && editingId !== nuevoCampo.id) { const campoAnterior = campos.find((c) => c.id === editingId) if (campoAnterior && editValue !== campoAnterior.value) { // Solo guardamos si el valor realmente cambió ejecutarGuardadoSilencioso(campoAnterior, editValue) } } // 2. ABRIMOS EL NUEVO CAMPO setEditingId(nuevoCampo.id) setEditValue(nuevoCampo.value) } const handleCancel = () => { setEditingId(null) setEditValue('') } // Función auxiliar para procesar los datos (fuera o dentro del componente) const prepararDatosActualizados = ( data: any, campo: DatosGeneralesField, valor: string, ) => { const currentValue = data.datos[campo.clave] let newValue: any if ( typeof currentValue === 'object' && currentValue !== null && 'description' in currentValue ) { newValue = { ...currentValue, description: valor } } else { newValue = valor } return { ...data.datos, [campo.clave]: newValue, } } const ejecutarGuardadoSilencioso = ( campo: DatosGeneralesField, valor: string, ) => { if (!data?.datos) return const datosActualizados = prepararDatosActualizados(data, campo, valor) console.log(datosActualizados) updatePlan.mutate({ planId, patch: { datos: datosActualizados }, }) // Actualizar UI localmente setCampos((prev) => prev.map((c) => (c.id === campo.id ? { ...c, value: valor } : c)), ) } const handleSave = (campo: DatosGeneralesField) => { if (!data?.datos) return const currentValue = (data.datos as any)[campo.clave] let newValue: any if ( typeof currentValue === 'object' && currentValue !== null && 'description' in currentValue ) { // Caso 1: objeto con description newValue = { ...currentValue, description: editValue, } } else { // Caso 2: valor plano (string, number, etc) newValue = editValue } const datosActualizados = { ...data.datos, [campo.clave]: newValue, } updatePlan.mutate({ planId, patch: { datos: datosActualizados, }, }) // UI optimista setCampos((prev) => prev.map((c) => (c.id === campo.id ? { ...c, value: editValue } : c)), ) ejecutarGuardadoSilencioso(campo, editValue) setEditingId(null) setEditValue('') } const handleIARequest = (clave: string) => { navigate({ to: '/planes/$planId/iaplan', params: { planId: planId, // o dinámico }, state: { campo_edit: clave, } as any, }) } return (

Datos Generales del Plan

Información estructural y descriptiva del plan de estudios

{campos.map((campo) => { const isEditing = editingId === campo.id return (
{/* Header de la Card */}

{campo.label}

{campo.helperText || 'Información del campo'}
{campo.requerido && ( * )}
{!isEditing && (
Generar con IA Editar campo
)}
{/* Contenido de la Card */}
{isEditing ? (