Se generan los planes con IA de manera correcta

This commit is contained in:
2026-01-22 12:39:52 -06:00
parent aa867e4612
commit 4950f7efbf
8 changed files with 115 additions and 154 deletions

View File

@@ -44,7 +44,7 @@ export function PasoBasicosForm({
<div className="grid gap-4 sm:grid-cols-2">
<div className="grid gap-1 sm:col-span-2">
<Label htmlFor="nombrePlan">
Nombre del plan <span className="text-destructive">*</span>
Nombre del plan {/* <span className="text-destructive">*</span> */}
</Label>
<Input
id="nombrePlan"

View File

@@ -65,7 +65,12 @@ export function PasoDetallesPanel({
</div>
<div className="flex flex-col gap-1">
<Label htmlFor="notas">Notas adicionales</Label>
<Label htmlFor="notas">
Notas adicionales
<span className="text-xs font-normal text-gray-500 dark:text-gray-400">
(Opcional)
</span>
</Label>
<textarea
id="notas"
className="bg-background text-foreground ring-offset-background focus-visible:ring-ring min-h-24 w-full rounded-md border px-3 py-2 text-sm shadow-sm focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none"

View File

@@ -68,9 +68,12 @@ export function WizardControls({
},
}
const data = await generatePlanAI.mutateAsync(aiInput as any)
console.log(`${new Date().toISOString()} - Enviando a generar plan IA`)
// navigate({ to: `/planes/${data.plan.id}` })
const data = await generatePlanAI.mutateAsync(aiInput as any)
console.log(`${new Date().toISOString()} - Plan IA generado`, data)
navigate({ to: `/planes/${data.plan.id}` })
return
}

View File

@@ -121,6 +121,8 @@ export function useGeneratePlanAI() {
mutationFn: ai_generate_plan,
onSuccess: (data) => {
// Asumiendo que la Edge Function devuelve { ok: true, plan: { id: ... } }
console.log('success de ai_generate_plan')
const newPlan = data.plan
if (newPlan) {

View File

@@ -1,17 +1,17 @@
import { useState } from "react";
import { useState } from 'react'
import type { NewPlanWizardState } from "../types";
import type { NewPlanWizardState } from '../types'
export function useNuevoPlanWizard() {
const [wizard, setWizard] = useState<NewPlanWizardState>({
step: 1,
tipoOrigen: null,
datosBasicos: {
nombrePlan: "",
carreraId: "",
facultadId: "",
nivel: "",
tipoCiclo: "",
nombrePlan: '',
carreraId: '',
facultadId: '',
nivel: '',
tipoCiclo: '',
numCiclos: undefined,
estructuraPlanId: null,
},
@@ -34,8 +34,8 @@ export function useNuevoPlanWizard() {
archivoAsignaturasExcelId: null,
},
iaConfig: {
descripcionEnfoque: "",
notasAdicionales: "",
descripcionEnfoque: '',
notasAdicionales: '',
archivosReferencia: [],
repositoriosReferencia: [],
archivosAdjuntos: [],
@@ -43,42 +43,43 @@ export function useNuevoPlanWizard() {
resumen: {},
isLoading: false,
errorMessage: null,
});
})
const canContinueDesdeModo = wizard.tipoOrigen === "MANUAL" ||
wizard.tipoOrigen === "IA" ||
(wizard.tipoOrigen === "CLONADO_INTERNO" ||
wizard.tipoOrigen === "CLONADO_TRADICIONAL");
const canContinueDesdeModo =
wizard.tipoOrigen === 'MANUAL' ||
wizard.tipoOrigen === 'IA' ||
wizard.tipoOrigen === 'CLONADO_INTERNO' ||
wizard.tipoOrigen === 'CLONADO_TRADICIONAL'
const canContinueDesdeBasicos = !!wizard.datosBasicos.nombrePlan &&
const canContinueDesdeBasicos =
!!wizard.datosBasicos.nombrePlan &&
!!wizard.datosBasicos.carreraId &&
!!wizard.datosBasicos.facultadId &&
!!wizard.datosBasicos.nivel &&
(wizard.datosBasicos.numCiclos !== undefined &&
wizard.datosBasicos.numCiclos > 0) &&
wizard.datosBasicos.numCiclos !== undefined &&
wizard.datosBasicos.numCiclos > 0 &&
// Requerir ambas plantillas (plan y mapa) con versión
!!wizard.datosBasicos.estructuraPlanId;
!!wizard.datosBasicos.estructuraPlanId
const canContinueDesdeDetalles = (() => {
if (wizard.tipoOrigen === "MANUAL") return true;
if (wizard.tipoOrigen === "IA") {
if (wizard.tipoOrigen === 'MANUAL') return true
if (wizard.tipoOrigen === 'IA') {
// Requerimos descripción del enfoque y notas adicionales
return !!wizard.iaConfig?.descripcionEnfoque &&
!!wizard.iaConfig.notasAdicionales;
return !!wizard.iaConfig?.descripcionEnfoque
}
if (wizard.tipoOrigen === "CLONADO_INTERNO") {
return !!wizard.clonInterno?.planOrigenId;
if (wizard.tipoOrigen === 'CLONADO_INTERNO') {
return !!wizard.clonInterno?.planOrigenId
}
if (wizard.tipoOrigen === "CLONADO_TRADICIONAL") {
const t = wizard.clonTradicional;
if (!t) return false;
const tieneWord = !!t.archivoWordPlanId;
const tieneAlMenosUnExcel = !!t.archivoMapaExcelId ||
!!t.archivoAsignaturasExcelId;
return tieneWord && tieneAlMenosUnExcel;
if (wizard.tipoOrigen === 'CLONADO_TRADICIONAL') {
const t = wizard.clonTradicional
if (!t) return false
const tieneWord = !!t.archivoWordPlanId
const tieneAlMenosUnExcel =
!!t.archivoMapaExcelId || !!t.archivoAsignaturasExcelId
return tieneWord && tieneAlMenosUnExcel
}
return false;
})();
return false
})()
return {
wizard,
@@ -86,5 +87,5 @@ export function useNuevoPlanWizard() {
canContinueDesdeModo,
canContinueDesdeBasicos,
canContinueDesdeDetalles,
};
}
}

View File

@@ -1,65 +1,61 @@
import type { UploadedFile } from "@/components/planes/wizard/PasoDetallesPanel/FileDropZone";
import type { UploadedFile } from '@/components/planes/wizard/PasoDetallesPanel/FileDropZone'
import type {
NivelPlanEstudio,
TipoCiclo,
TipoOrigen,
} from "@/data/types/domain";
} from '@/data/types/domain'
export type PlanPreview = {
nombrePlan: string;
nivel: NivelPlanEstudio;
tipoCiclo: TipoCiclo;
numCiclos: number;
numAsignaturasAprox?: number;
secciones?: Array<{ id: string; titulo: string; resumen: string }>;
};
nombrePlan: string
nivel: NivelPlanEstudio
tipoCiclo: TipoCiclo
numCiclos: number
numAsignaturasAprox?: number
secciones?: Array<{ id: string; titulo: string; resumen: string }>
}
export type NewPlanWizardState = {
step: 1 | 2 | 3 | 4;
tipoOrigen: TipoOrigen | null;
step: 1 | 2 | 3 | 4
tipoOrigen: TipoOrigen | null
datosBasicos: {
nombrePlan: string;
carreraId: string;
facultadId: string;
nivel: NivelPlanEstudio | "";
tipoCiclo: TipoCiclo | "";
numCiclos: number | undefined;
nombrePlan: string
carreraId: string
facultadId: string
nivel: NivelPlanEstudio | ''
tipoCiclo: TipoCiclo | ''
numCiclos: number | undefined
// Selección de plantillas (obligatorias)
estructuraPlanId: string | null;
};
clonInterno?: { planOrigenId: string | null };
estructuraPlanId: string | null
}
clonInterno?: { planOrigenId: string | null }
clonTradicional?: {
archivoWordPlanId:
| {
id: string;
name: string;
size: string;
type: string;
}
| null;
archivoWordPlanId: {
id: string
name: string
size: string
type: string
} | null
archivoMapaExcelId: {
id: string;
name: string;
size: string;
type: string;
} | null;
id: string
name: string
size: string
type: string
} | null
archivoAsignaturasExcelId: {
id: string;
name: string;
size: string;
type: string;
} | null;
};
id: string
name: string
size: string
type: string
} | null
}
iaConfig?: {
descripcionEnfoque: string;
notasAdicionales: string;
archivosReferencia: Array<string>;
repositoriosReferencia?: Array<string>;
archivosAdjuntos?: Array<
UploadedFile
>;
};
resumen: { previewPlan?: PlanPreview };
isLoading: boolean;
errorMessage: string | null;
};
descripcionEnfoque: string
notasAdicionales?: string
archivosReferencia: Array<string>
repositoriosReferencia?: Array<string>
archivosAdjuntos?: Array<UploadedFile>
}
resumen: { previewPlan?: PlanPreview }
isLoading: boolean
errorMessage: string | null
}

View File

@@ -12,9 +12,9 @@ import { Route as rootRouteImport } from './routes/__root'
import { Route as LoginRouteImport } from './routes/login'
import { Route as DashboardRouteImport } from './routes/dashboard'
import { Route as IndexRouteImport } from './routes/index'
import { Route as PlanesPlanesListRouteRouteImport } from './routes/planes/PlanesListRoute'
import { Route as DemoTanstackQueryRouteImport } from './routes/demo/tanstack-query'
import { Route as PlanesListaRouteRouteImport } from './routes/planes/_lista/route'
import { Route as PlanesPlanIdIndexRouteImport } from './routes/planes/$planId/index'
import { Route as PlanesListaNuevoRouteImport } from './routes/planes/_lista/nuevo'
import { Route as PlanesPlanIdAsignaturasRouteRouteImport } from './routes/planes/$planId/asignaturas/route'
import { Route as PlanesPlanIdDetalleRouteRouteImport } from './routes/planes/$planId/_detalle/route'
@@ -45,11 +45,6 @@ const IndexRoute = IndexRouteImport.update({
path: '/',
getParentRoute: () => rootRouteImport,
} as any)
const PlanesPlanesListRouteRoute = PlanesPlanesListRouteRouteImport.update({
id: '/planes/PlanesListRoute',
path: '/planes/PlanesListRoute',
getParentRoute: () => rootRouteImport,
} as any)
const DemoTanstackQueryRoute = DemoTanstackQueryRouteImport.update({
id: '/demo/tanstack-query',
path: '/demo/tanstack-query',
@@ -60,6 +55,11 @@ const PlanesListaRouteRoute = PlanesListaRouteRouteImport.update({
path: '/planes',
getParentRoute: () => rootRouteImport,
} as any)
const PlanesPlanIdIndexRoute = PlanesPlanIdIndexRouteImport.update({
id: '/planes/$planId/',
path: '/planes/$planId/',
getParentRoute: () => rootRouteImport,
} as any)
const PlanesListaNuevoRoute = PlanesListaNuevoRouteImport.update({
id: '/nuevo',
path: '/nuevo',
@@ -148,8 +148,7 @@ export interface FileRoutesByFullPath {
'/login': typeof LoginRoute
'/planes': typeof PlanesListaRouteRouteWithChildren
'/demo/tanstack-query': typeof DemoTanstackQueryRoute
'/planes/PlanesListRoute': typeof PlanesPlanesListRouteRoute
'/planes/$planId': typeof PlanesPlanIdDetalleRouteRouteWithChildren
'/planes/$planId': typeof PlanesPlanIdIndexRoute
'/planes/$planId/asignaturas': typeof PlanesPlanIdAsignaturasListaRouteRouteWithChildren
'/planes/nuevo': typeof PlanesListaNuevoRoute
'/planes/$planId/asignaturas/$asignaturaId': typeof PlanesPlanIdAsignaturasAsignaturaIdRouteRoute
@@ -169,8 +168,7 @@ export interface FileRoutesByTo {
'/login': typeof LoginRoute
'/planes': typeof PlanesListaRouteRouteWithChildren
'/demo/tanstack-query': typeof DemoTanstackQueryRoute
'/planes/PlanesListRoute': typeof PlanesPlanesListRouteRoute
'/planes/$planId': typeof PlanesPlanIdDetalleRouteRouteWithChildren
'/planes/$planId': typeof PlanesPlanIdIndexRoute
'/planes/nuevo': typeof PlanesListaNuevoRoute
'/planes/$planId/asignaturas/$asignaturaId': typeof PlanesPlanIdAsignaturasAsignaturaIdRouteRoute
'/planes/$planId/asignaturas': typeof PlanesPlanIdAsignaturasIndexRoute
@@ -190,10 +188,10 @@ export interface FileRoutesById {
'/login': typeof LoginRoute
'/planes/_lista': typeof PlanesListaRouteRouteWithChildren
'/demo/tanstack-query': typeof DemoTanstackQueryRoute
'/planes/PlanesListRoute': typeof PlanesPlanesListRouteRoute
'/planes/$planId/_detalle': typeof PlanesPlanIdDetalleRouteRouteWithChildren
'/planes/$planId/asignaturas': typeof PlanesPlanIdAsignaturasRouteRouteWithChildren
'/planes/_lista/nuevo': typeof PlanesListaNuevoRoute
'/planes/$planId/': typeof PlanesPlanIdIndexRoute
'/planes/$planId/asignaturas/$asignaturaId': typeof PlanesPlanIdAsignaturasAsignaturaIdRouteRoute
'/planes/$planId/asignaturas/_lista': typeof PlanesPlanIdAsignaturasListaRouteRouteWithChildren
'/planes/$planId/_detalle/datos': typeof PlanesPlanIdDetalleDatosRoute
@@ -214,7 +212,6 @@ export interface FileRouteTypes {
| '/login'
| '/planes'
| '/demo/tanstack-query'
| '/planes/PlanesListRoute'
| '/planes/$planId'
| '/planes/$planId/asignaturas'
| '/planes/nuevo'
@@ -235,7 +232,6 @@ export interface FileRouteTypes {
| '/login'
| '/planes'
| '/demo/tanstack-query'
| '/planes/PlanesListRoute'
| '/planes/$planId'
| '/planes/nuevo'
| '/planes/$planId/asignaturas/$asignaturaId'
@@ -255,10 +251,10 @@ export interface FileRouteTypes {
| '/login'
| '/planes/_lista'
| '/demo/tanstack-query'
| '/planes/PlanesListRoute'
| '/planes/$planId/_detalle'
| '/planes/$planId/asignaturas'
| '/planes/_lista/nuevo'
| '/planes/$planId/'
| '/planes/$planId/asignaturas/$asignaturaId'
| '/planes/$planId/asignaturas/_lista'
| '/planes/$planId/_detalle/datos'
@@ -278,9 +274,9 @@ export interface RootRouteChildren {
LoginRoute: typeof LoginRoute
PlanesListaRouteRoute: typeof PlanesListaRouteRouteWithChildren
DemoTanstackQueryRoute: typeof DemoTanstackQueryRoute
PlanesPlanesListRouteRoute: typeof PlanesPlanesListRouteRoute
PlanesPlanIdDetalleRouteRoute: typeof PlanesPlanIdDetalleRouteRouteWithChildren
PlanesPlanIdAsignaturasRouteRoute: typeof PlanesPlanIdAsignaturasRouteRouteWithChildren
PlanesPlanIdIndexRoute: typeof PlanesPlanIdIndexRoute
}
declare module '@tanstack/react-router' {
@@ -306,13 +302,6 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof IndexRouteImport
parentRoute: typeof rootRouteImport
}
'/planes/PlanesListRoute': {
id: '/planes/PlanesListRoute'
path: '/planes/PlanesListRoute'
fullPath: '/planes/PlanesListRoute'
preLoaderRoute: typeof PlanesPlanesListRouteRouteImport
parentRoute: typeof rootRouteImport
}
'/demo/tanstack-query': {
id: '/demo/tanstack-query'
path: '/demo/tanstack-query'
@@ -327,6 +316,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof PlanesListaRouteRouteImport
parentRoute: typeof rootRouteImport
}
'/planes/$planId/': {
id: '/planes/$planId/'
path: '/planes/$planId'
fullPath: '/planes/$planId'
preLoaderRoute: typeof PlanesPlanIdIndexRouteImport
parentRoute: typeof rootRouteImport
}
'/planes/_lista/nuevo': {
id: '/planes/_lista/nuevo'
path: '/nuevo'
@@ -506,10 +502,10 @@ const rootRouteChildren: RootRouteChildren = {
LoginRoute: LoginRoute,
PlanesListaRouteRoute: PlanesListaRouteRouteWithChildren,
DemoTanstackQueryRoute: DemoTanstackQueryRoute,
PlanesPlanesListRouteRoute: PlanesPlanesListRouteRoute,
PlanesPlanIdDetalleRouteRoute: PlanesPlanIdDetalleRouteRouteWithChildren,
PlanesPlanIdAsignaturasRouteRoute:
PlanesPlanIdAsignaturasRouteRouteWithChildren,
PlanesPlanIdIndexRoute: PlanesPlanIdIndexRoute,
}
export const routeTree = rootRouteImport
._addFileChildren(rootRouteChildren)

View File

@@ -1,42 +0,0 @@
import { createFileRoute } from '@tanstack/react-router'
import { useMemo, useState } from 'react'
import { usePlanes } from '@/data'
export const Route = createFileRoute('/planes/PlanesListRoute')({
component: RouteComponent,
})
function RouteComponent() {
const [search, setSearch] = useState('')
const filters = useMemo(
() => ({ search, limit: 20, offset: 0, activo: true }),
[search],
)
const { data, isLoading, isError, error } = usePlanes(filters)
return (
<div style={{ padding: 16 }}>
<h1>Planes</h1>
<input
value={search}
onChange={(e) => setSearch(e.target.value)}
placeholder="Buscar…"
/>
{isLoading && <div>Cargando</div>}
{isError && <div>Error: {(error as any).message}</div>}
<ul>
{(data?.data ?? []).map((p) => (
<li key={p.id}>
<pre>{JSON.stringify(p, null, 2)}</pre>
</li>
))}
</ul>
</div>
)
}