From c15e2f941dcda3c29e3bbf3ded66086b38f2e467 Mon Sep 17 00:00:00 2001 From: "Roberto.silva" Date: Mon, 26 Jan 2026 13:52:12 -0600 Subject: [PATCH] Se corrigen incidencias 35, 36, 33, 32 --- .../asignaturas/detalle/MateriaDetailPage.tsx | 82 ++++++++++-- src/data/api/plans.api.ts | 2 +- .../planes/$planId/_detalle/documento.tsx | 121 +++++++++--------- .../planes/$planId/_detalle/historial.tsx | 13 +- src/routes/planes/$planId/_detalle/iaplan.tsx | 86 ++++++++----- 5 files changed, 192 insertions(+), 112 deletions(-) diff --git a/src/components/asignaturas/detalle/MateriaDetailPage.tsx b/src/components/asignaturas/detalle/MateriaDetailPage.tsx index e0594dd..a3b3acc 100644 --- a/src/components/asignaturas/detalle/MateriaDetailPage.tsx +++ b/src/components/asignaturas/detalle/MateriaDetailPage.tsx @@ -1,6 +1,7 @@ import { createFileRoute, Link, + useNavigate, useParams, useRouterState, } from '@tanstack/react-router' @@ -59,13 +60,17 @@ function EditableHeaderField({ onSave: (val: string) => void className?: string }) { + const textValue = String(value) + return ( onSave(e.target.value)} onBlur={(e) => onSave(e.target.value)} - className={` w-[${String(value).length || 1}ch] max-w-[6ch] border-none bg-transparent text-center outline-none focus:ring-2 focus:ring-blue-400 ${className ?? ''} `} + className={`border-none bg-transparent outline-none focus:ring-2 focus:ring-blue-400 ${className ?? ''}`} /> ) } @@ -91,6 +96,7 @@ export default function MateriaDetailPage() { const [messages, setMessages] = useState>([]) const [datosGenerales, setDatosGenerales] = useState({}) const [campos, setCampos] = useState>([]) + const [activeTab, setActiveTab] = useState('datos') // Dentro de MateriaDetailPage const [headerData, setHeaderData] = useState({ @@ -100,6 +106,13 @@ export default function MateriaDetailPage() { 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 (asignaturasApi) { @@ -208,11 +221,23 @@ export default function MateriaDetailPage() {
- - {asignaturasApi?.planes_estudio?.datos?.nombre} + + {/* Eliminamos el max-w y dejamos que el flex-wrap haga su trabajo */} + handleUpdateHeader('plan_nombre', val)} + className="min-w-[10ch] text-blue-100" // min-w para que sea clickeable si está vacío + /> - - {asignaturasApi?.planes_estudio?.carreras?.facultades?.nombre} + + handleUpdateHeader('facultad_nombre', val)} + className="min-w-[10ch] text-blue-100" + />
@@ -258,7 +283,11 @@ export default function MateriaDetailPage() { {/* ================= TABS ================= */}
- + Datos generales Contenido temático @@ -272,7 +301,11 @@ export default function MateriaDetailPage() { {/* ================= TAB: DATOS GENERALES ================= */} - + @@ -330,10 +363,15 @@ export default function MateriaDetailPage() { /* ================= TAB CONTENT ================= */ interface DatosGeneralesProps { + asignaturaId: string data: AsignaturaDatos isLoading: boolean } -function DatosGenerales({ data, isLoading }: DatosGeneralesProps) { +function DatosGenerales({ + data, + isLoading, + asignaturaId, +}: DatosGeneralesProps) { const formatTitle = (key: string): string => key.replace(/_/g, ' ').replace(/\b\w/g, (l: string) => l.toUpperCase()) @@ -360,7 +398,9 @@ function DatosGenerales({ data, isLoading }: DatosGeneralesProps) { {!isLoading && Object.entries(data).map(([key, value]) => ( { @@ -411,6 +451,8 @@ function DatosGenerales({ data, isLoading }: DatosGeneralesProps) { } interface InfoCardProps { + asignaturaId?: string + clave: string title: string initialContent: any type?: 'text' | 'requirements' | 'evaluation' @@ -418,6 +460,8 @@ interface InfoCardProps { } function InfoCard({ + asignaturaId, + clave, title, initialContent, type = 'text', @@ -428,11 +472,27 @@ function InfoCard({ const [tempText, setTempText] = useState( type === 'text' ? initialContent : JSON.stringify(initialContent, null, 2), ) - + const navigate = useNavigate() const handleSave = () => { setData(tempText) setIsEditing(false) } + const handleIARequest = (data) => { + console.log(data) + console.log(asignaturaId) + + navigate({ + to: '/planes/$planId/asignaturas/$asignaturaId', + params: { + asignaturaId: asignaturaId, + }, + state: { + activeTab: 'ia', + prefillCampo: data, + prefillContenido: data, // el contenido actual del card + } as any, + }) + } return ( @@ -448,7 +508,7 @@ function InfoCard({ variant="ghost" size="icon" className="h-8 w-8 text-blue-500 hover:bg-blue-50 hover:text-blue-600" - onClick={() => onEnhanceAI?.(data)} // Enviamos la data actual a la IA + onClick={() => handleIARequest(clave)} // Enviamos la data actual a la IA title="Mejorar con IA" > diff --git a/src/data/api/plans.api.ts b/src/data/api/plans.api.ts index a05c309..a6821b0 100644 --- a/src/data/api/plans.api.ts +++ b/src/data/api/plans.api.ts @@ -178,7 +178,7 @@ export async function plans_history(planId: UUID): Promise> { const { data, error } = await supabase .from('cambios_plan') .select( - 'id,plan_estudio_id,cambiado_por,cambiado_en,tipo,campo,valor_anterior,valor_nuevo,interaccion_ia_id', + 'id,plan_estudio_id,cambiado_por,cambiado_en,tipo,campo,valor_anterior,valor_nuevo', ) .eq('plan_estudio_id', planId) .order('cambiado_en', { ascending: false }) diff --git a/src/routes/planes/$planId/_detalle/documento.tsx b/src/routes/planes/$planId/_detalle/documento.tsx index dba0d3d..782beff 100644 --- a/src/routes/planes/$planId/_detalle/documento.tsx +++ b/src/routes/planes/$planId/_detalle/documento.tsx @@ -8,6 +8,7 @@ import { Clock, FileJson, } from 'lucide-react' +import { useCallback, useEffect, useState } from 'react' import { Button } from '@/components/ui/button' import { Card, CardContent } from '@/components/ui/card' @@ -19,9 +20,34 @@ export const Route = createFileRoute('/planes/$planId/_detalle/documento')({ function RouteComponent() { const { planId } = useParams({ from: '/planes/$planId/_detalle/documento' }) - const handleDownloadPdf = async () => { - console.log('entre aqui ') + const [pdfUrl, setPdfUrl] = useState(null) + const [isLoading, setIsLoading] = useState(true) + const loadPdfPreview = useCallback(async () => { + try { + setIsLoading(true) + const pdfBlob = await fetchPlanPdf({ plan_estudio_id: planId }) + const url = window.URL.createObjectURL(pdfBlob) + + // Limpiar URL anterior si existe para evitar fugas de memoria + if (pdfUrl) window.URL.revokeObjectURL(pdfUrl) + + setPdfUrl(url) + } catch (error) { + console.error('Error cargando preview:', error) + } finally { + setIsLoading(false) + } + }, [planId]) + + useEffect(() => { + loadPdfPreview() + return () => { + if (pdfUrl) window.URL.revokeObjectURL(pdfUrl) + } + }, [loadPdfPreview]) + + const handleDownloadPdf = async () => { try { const pdfBlob = await fetchPlanPdf({ plan_estudio_id: planId, @@ -54,7 +80,12 @@ function RouteComponent() {

-
{/* CONTENEDOR DEL DOCUMENTO (Visor) */} + {/* CONTENEDOR DEL VISOR REAL */}
- - Plan_Estudios_ISC_2024.pdf + Preview_Documento.pdf
- + {pdfUrl && ( + + )}
- - {/* SIMULACIÓN DE HOJA DE PAPEL */} -
- {/* Contenido del Plan */} -
-

- Universidad Tecnológica -

-

- Plan de Estudios 2024 -

-

- Ingeniería en Sistemas Computacionales -

-

- Facultad de Ingeniería -

+ + {isLoading ? ( +
+ +

Generando vista previa del PDF...

- -
-
-

1. Objetivo General

-

- Formar profesionales altamente capacitados en el desarrollo de - soluciones tecnológicas innovadoras, con sólidos conocimientos - en programación, bases de datos, redes y seguridad - informática. -

-
- -
-

2. Perfil de Ingreso

-

- Egresados de educación media superior con conocimientos - básicos de matemáticas, razonamiento lógico y habilidades de - comunicación. Interés por la tecnología y la resolución de - problemas. -

-
- -
-

3. Perfil de Egreso

-

- Profesional capaz de diseñar, desarrollar e implementar - sistemas de software de calidad, administrar infraestructuras - de red y liderar proyectos tecnológicos multidisciplinarios. -

-
+ ) : pdfUrl ? ( + /* 3. VISOR DE PDF REAL */ +