generación de plan con invalidación de queries
This commit is contained in:
@@ -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 { Button } from '@/components/ui/button'
|
||||||
|
import { useGeneratePlanAI } from '@/data/hooks/usePlans'
|
||||||
|
|
||||||
export function WizardControls({
|
export function WizardControls({
|
||||||
errorMessage,
|
errorMessage,
|
||||||
onPrev,
|
onPrev,
|
||||||
onNext,
|
onNext,
|
||||||
onCreate,
|
|
||||||
disablePrev,
|
disablePrev,
|
||||||
disableNext,
|
disableNext,
|
||||||
disableCreate,
|
disableCreate,
|
||||||
isLastStep,
|
isLastStep,
|
||||||
|
wizard,
|
||||||
|
setWizard,
|
||||||
}: {
|
}: {
|
||||||
errorMessage?: string | null
|
errorMessage?: string | null
|
||||||
onPrev: () => void
|
onPrev: () => void
|
||||||
onNext: () => void
|
onNext: () => void
|
||||||
onCreate: () => void
|
|
||||||
disablePrev: boolean
|
disablePrev: boolean
|
||||||
disableNext: boolean
|
disableNext: boolean
|
||||||
disableCreate: boolean
|
disableCreate: boolean
|
||||||
isLastStep: boolean
|
isLastStep: boolean
|
||||||
|
wizard: NewPlanWizardState
|
||||||
|
setWizard: React.Dispatch<React.SetStateAction<NewPlanWizardState>>
|
||||||
}) {
|
}) {
|
||||||
|
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 (
|
return (
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
@@ -33,7 +111,7 @@ export function WizardControls({
|
|||||||
Anterior
|
Anterior
|
||||||
</Button>
|
</Button>
|
||||||
{isLastStep ? (
|
{isLastStep ? (
|
||||||
<Button onClick={onCreate} disabled={disableCreate}>
|
<Button onClick={handleCreate} disabled={disableCreate}>
|
||||||
Crear plan
|
Crear plan
|
||||||
</Button>
|
</Button>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -116,11 +116,25 @@ export function useCreatePlanManual() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function useGeneratePlanAI() {
|
export function useGeneratePlanAI() {
|
||||||
|
const qc = useQueryClient();
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: ai_generate_plan,
|
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() {
|
export function usePersistPlanFromAI() {
|
||||||
const qc = useQueryClient();
|
const qc = useQueryClient();
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import * as Icons from 'lucide-react'
|
|||||||
|
|
||||||
import { useNuevoPlanWizard } from './hooks/useNuevoPlanWizard'
|
import { useNuevoPlanWizard } from './hooks/useNuevoPlanWizard'
|
||||||
|
|
||||||
import type { NewPlanWizardState } from './types'
|
// import type { NewPlanWizardState } from './types'
|
||||||
|
|
||||||
import { PasoBasicosForm } from '@/components/planes/wizard/PasoBasicosForm/PasoBasicosForm'
|
import { PasoBasicosForm } from '@/components/planes/wizard/PasoBasicosForm/PasoBasicosForm'
|
||||||
import { PasoDetallesPanel } from '@/components/planes/wizard/PasoDetallesPanel/PasoDetallesPanel'
|
import { PasoDetallesPanel } from '@/components/planes/wizard/PasoDetallesPanel/PasoDetallesPanel'
|
||||||
@@ -25,7 +25,7 @@ import {
|
|||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from '@/components/ui/dialog'
|
} from '@/components/ui/dialog'
|
||||||
import { useGeneratePlanAI } from '@/data/hooks/usePlans'
|
// import { useGeneratePlanAI } from '@/data/hooks/usePlans'
|
||||||
|
|
||||||
// Mock de permisos/rol
|
// Mock de permisos/rol
|
||||||
const auth_get_current_user_role = () => 'JEFE_CARRERA' as const
|
const auth_get_current_user_role = () => 'JEFE_CARRERA' as const
|
||||||
@@ -48,8 +48,7 @@ const Wizard = defineStepper(
|
|||||||
export default function NuevoPlanModalContainer() {
|
export default function NuevoPlanModalContainer() {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const role = auth_get_current_user_role()
|
const role = auth_get_current_user_role()
|
||||||
const generatePlanAI = useGeneratePlanAI()
|
// const generatePlanAI = useGeneratePlanAI()
|
||||||
// const persistPlanFromAI = usePersistPlanFromAI()
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
wizard,
|
wizard,
|
||||||
@@ -63,74 +62,7 @@ export default function NuevoPlanModalContainer() {
|
|||||||
navigate({ to: '/planes', resetScroll: false })
|
navigate({ to: '/planes', resetScroll: false })
|
||||||
}
|
}
|
||||||
|
|
||||||
const crearPlan = async () => {
|
// Crear plan: ahora la lógica vive en WizardControls
|
||||||
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 }))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={true} onOpenChange={(open) => !open && handleClose()}>
|
<Dialog open={true} onOpenChange={(open) => !open && handleClose()}>
|
||||||
@@ -233,7 +165,6 @@ export default function NuevoPlanModalContainer() {
|
|||||||
errorMessage={wizard.errorMessage}
|
errorMessage={wizard.errorMessage}
|
||||||
onPrev={() => methods.prev()}
|
onPrev={() => methods.prev()}
|
||||||
onNext={() => methods.next()}
|
onNext={() => methods.next()}
|
||||||
onCreate={crearPlan}
|
|
||||||
disablePrev={
|
disablePrev={
|
||||||
Wizard.utils.getIndex(methods.current.id) === 0 ||
|
Wizard.utils.getIndex(methods.current.id) === 0 ||
|
||||||
wizard.isLoading
|
wizard.isLoading
|
||||||
@@ -252,6 +183,8 @@ export default function NuevoPlanModalContainer() {
|
|||||||
Wizard.utils.getIndex(methods.current.id) >=
|
Wizard.utils.getIndex(methods.current.id) >=
|
||||||
Wizard.steps.length - 1
|
Wizard.steps.length - 1
|
||||||
}
|
}
|
||||||
|
wizard={wizard}
|
||||||
|
setWizard={setWizard}
|
||||||
/>
|
/>
|
||||||
</Wizard.Stepper.Controls>
|
</Wizard.Stepper.Controls>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user