From 254f6383d7063fc91f34cd2f5d737eae5b95a62d Mon Sep 17 00:00:00 2001 From: Guillermo Arrieta Medina Date: Wed, 21 Jan 2026 12:11:12 -0600 Subject: [PATCH] =?UTF-8?q?generaci=C3=B3n=20de=20plan=20con=20invalidaci?= =?UTF-8?q?=C3=B3n=20de=20queries?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../planes/wizard/WizardControls.tsx | 84 ++++++++++++++++++- src/data/hooks/usePlans.ts | 14 ++++ .../planes/nuevo/NuevoPlanModalContainer.tsx | 79 ++--------------- 3 files changed, 101 insertions(+), 76 deletions(-) diff --git a/src/components/planes/wizard/WizardControls.tsx b/src/components/planes/wizard/WizardControls.tsx index 70ea27f..6d2bc1f 100644 --- a/src/components/planes/wizard/WizardControls.tsx +++ b/src/components/planes/wizard/WizardControls.tsx @@ -1,24 +1,102 @@ +import { useNavigate } from '@tanstack/react-router' + +import type { NewPlanWizardState } from '@/features/planes/nuevo/types' + import { Button } from '@/components/ui/button' +import { useGeneratePlanAI } from '@/data/hooks/usePlans' export function WizardControls({ errorMessage, onPrev, onNext, - onCreate, disablePrev, disableNext, disableCreate, isLastStep, + wizard, + setWizard, }: { errorMessage?: string | null onPrev: () => void onNext: () => void - onCreate: () => void disablePrev: boolean disableNext: boolean disableCreate: boolean isLastStep: boolean + wizard: NewPlanWizardState + setWizard: React.Dispatch> }) { + const navigate = useNavigate() + const generatePlanAI = useGeneratePlanAI() + // const persistPlanFromAI = usePersistPlanFromAI() + + const handleCreate = async () => { + // Start loading + setWizard( + (w: NewPlanWizardState): NewPlanWizardState => ({ + ...w, + isLoading: true, + errorMessage: null, + }), + ) + + try { + if (wizard.tipoOrigen === 'IA') { + const tipoCicloSafe = (wizard.datosBasicos.tipoCiclo || + 'Semestre') as any + const numCiclosSafe = + typeof wizard.datosBasicos.numCiclos === 'number' + ? wizard.datosBasicos.numCiclos + : 1 + + const aiInput = { + datosBasicos: { + nombrePlan: wizard.datosBasicos.nombrePlan, + carreraId: wizard.datosBasicos.carreraId, + facultadId: wizard.datosBasicos.facultadId || undefined, + nivel: wizard.datosBasicos.nivel as string, + tipoCiclo: tipoCicloSafe, + numCiclos: numCiclosSafe, + estructuraPlanId: wizard.datosBasicos.estructuraPlanId as string, + }, + iaConfig: { + descripcionEnfoque: wizard.iaConfig?.descripcionEnfoque || '', + notasAdicionales: wizard.iaConfig?.notasAdicionales || '', + archivosReferencia: wizard.iaConfig?.archivosReferencia || [], + repositoriosIds: wizard.iaConfig?.repositoriosReferencia || [], + archivosAdjuntos: wizard.iaConfig?.archivosAdjuntos || [], + }, + } + + const data = await generatePlanAI.mutateAsync(aiInput as any) + + // navigate({ to: `/planes/${data.plan.id}` }) + return + } + + // Fallback mocks for non-IA origins + await new Promise((r) => setTimeout(r, 900)) + const nuevoId = (() => { + if (wizard.tipoOrigen === 'MANUAL') return 'plan_new_manual_001' + if ( + wizard.tipoOrigen === 'CLONADO_INTERNO' || + wizard.tipoOrigen === 'CLONADO_TRADICIONAL' + ) + return 'plan_new_clone_001' + return 'plan_new_import_001' + })() + navigate({ to: `/planes/${nuevoId}` }) + } catch (err: any) { + setWizard((w) => ({ + ...w, + isLoading: false, + errorMessage: err?.message ?? 'Error generando el plan', + })) + } finally { + setWizard((w) => ({ ...w, isLoading: false })) + } + } + return (
@@ -33,7 +111,7 @@ export function WizardControls({ Anterior {isLastStep ? ( - ) : ( diff --git a/src/data/hooks/usePlans.ts b/src/data/hooks/usePlans.ts index 1a1802c..4a4bd58 100644 --- a/src/data/hooks/usePlans.ts +++ b/src/data/hooks/usePlans.ts @@ -116,11 +116,25 @@ export function useCreatePlanManual() { } export function useGeneratePlanAI() { + const qc = useQueryClient(); return useMutation({ mutationFn: ai_generate_plan, + onSuccess: (data) => { + // Asumiendo que la Edge Function devuelve { ok: true, plan: { id: ... } } + const newPlan = data.plan; + + if (newPlan) { + // 1. Invalidar la lista para que aparezca el nuevo plan + qc.invalidateQueries({ queryKey: ["planes", "list"] }); + + // 2. (Opcional) Pre-cargar el dato individual para que la navegación sea instantánea + // qc.setQueryData(["planes", "detail", newPlan.id], newPlan); + } + }, }); } +// Funcion obsoleta porque ahora el plan se persiste directamente en useGeneratePlanAI export function usePersistPlanFromAI() { const qc = useQueryClient(); diff --git a/src/features/planes/nuevo/NuevoPlanModalContainer.tsx b/src/features/planes/nuevo/NuevoPlanModalContainer.tsx index 5e1e35f..41062e2 100644 --- a/src/features/planes/nuevo/NuevoPlanModalContainer.tsx +++ b/src/features/planes/nuevo/NuevoPlanModalContainer.tsx @@ -3,7 +3,7 @@ import * as Icons from 'lucide-react' import { useNuevoPlanWizard } from './hooks/useNuevoPlanWizard' -import type { NewPlanWizardState } from './types' +// import type { NewPlanWizardState } from './types' import { PasoBasicosForm } from '@/components/planes/wizard/PasoBasicosForm/PasoBasicosForm' import { PasoDetallesPanel } from '@/components/planes/wizard/PasoDetallesPanel/PasoDetallesPanel' @@ -25,7 +25,7 @@ import { DialogHeader, DialogTitle, } from '@/components/ui/dialog' -import { useGeneratePlanAI } from '@/data/hooks/usePlans' +// import { useGeneratePlanAI } from '@/data/hooks/usePlans' // Mock de permisos/rol const auth_get_current_user_role = () => 'JEFE_CARRERA' as const @@ -48,8 +48,7 @@ const Wizard = defineStepper( export default function NuevoPlanModalContainer() { const navigate = useNavigate() const role = auth_get_current_user_role() - const generatePlanAI = useGeneratePlanAI() - // const persistPlanFromAI = usePersistPlanFromAI() + // const generatePlanAI = useGeneratePlanAI() const { wizard, @@ -63,74 +62,7 @@ export default function NuevoPlanModalContainer() { navigate({ to: '/planes', resetScroll: false }) } - const crearPlan = async () => { - setWizard( - (w: NewPlanWizardState): NewPlanWizardState => ({ - ...w, - isLoading: true, - errorMessage: null, - }), - ) - - try { - if (wizard.tipoOrigen === 'IA') { - const tipoCicloSafe = (wizard.datosBasicos.tipoCiclo || - 'Semestre') as any - const numCiclosSafe = - typeof wizard.datosBasicos.numCiclos === 'number' - ? wizard.datosBasicos.numCiclos - : 1 - - const aiInput = { - datosBasicos: { - nombrePlan: wizard.datosBasicos.nombrePlan, - carreraId: wizard.datosBasicos.carreraId, - facultadId: wizard.datosBasicos.facultadId || undefined, - nivel: wizard.datosBasicos.nivel as string, - tipoCiclo: tipoCicloSafe, - numCiclos: numCiclosSafe, - estructuraPlanId: wizard.datosBasicos.estructuraPlanId as string, - }, - iaConfig: { - descripcionEnfoque: wizard.iaConfig?.descripcionEnfoque || '', - notasAdicionales: wizard.iaConfig?.notasAdicionales || '', - archivosReferencia: wizard.iaConfig?.archivosReferencia || [], - repositoriosIds: wizard.iaConfig?.repositoriosReferencia || [], - archivosAdjuntos: wizard.iaConfig?.archivosAdjuntos || [], - }, - } - - const response = await generatePlanAI.mutateAsync(aiInput as any) - // const createdPlan = await persistPlanFromAI.mutateAsync({ - // jsonPlan: generatedJson, - // }) - // navigate({ to: `/planes/${createdPlan.id}` }) - console.log('Plan generado por IA:', response) - return - } - - // Fallback: comportamiento previo para otros modos (mock IDs) - await new Promise((r) => setTimeout(r, 900)) - const nuevoId = (() => { - if (wizard.tipoOrigen === 'MANUAL') return 'plan_new_manual_001' - if ( - wizard.tipoOrigen === 'CLONADO_INTERNO' || - wizard.tipoOrigen === 'CLONADO_TRADICIONAL' - ) - return 'plan_new_clone_001' - return 'plan_new_import_001' - })() - navigate({ to: `/planes/${nuevoId}` }) - } catch (err: any) { - setWizard((w) => ({ - ...w, - isLoading: false, - errorMessage: err?.message ?? 'Error generando el plan con IA', - })) - } finally { - setWizard((w) => ({ ...w, isLoading: false })) - } - } + // Crear plan: ahora la lógica vive en WizardControls return ( !open && handleClose()}> @@ -233,7 +165,6 @@ export default function NuevoPlanModalContainer() { errorMessage={wizard.errorMessage} onPrev={() => methods.prev()} onNext={() => methods.next()} - onCreate={crearPlan} disablePrev={ Wizard.utils.getIndex(methods.current.id) === 0 || wizard.isLoading @@ -252,6 +183,8 @@ export default function NuevoPlanModalContainer() { Wizard.utils.getIndex(methods.current.id) >= Wizard.steps.length - 1 } + wizard={wizard} + setWizard={setWizard} />