This commit is contained in:
2026-01-13 14:30:57 -06:00
parent 55c37b83b4
commit b08d58e262
12 changed files with 178 additions and 107 deletions

View File

@@ -45,6 +45,7 @@
"@tanstack/eslint-config": "^0.3.0", "@tanstack/eslint-config": "^0.3.0",
"@testing-library/dom": "^10.4.0", "@testing-library/dom": "^10.4.0",
"@testing-library/react": "^16.2.0", "@testing-library/react": "^16.2.0",
"@types/bun": "^1.3.6",
"@types/node": "^22.10.2", "@types/node": "^22.10.2",
"@types/react": "^19.2.0", "@types/react": "^19.2.0",
"@types/react-dom": "^19.2.0", "@types/react-dom": "^19.2.0",
@@ -503,6 +504,8 @@
"@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="], "@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="],
"@types/bun": ["@types/bun@1.3.6", "", { "dependencies": { "bun-types": "1.3.6" } }, "sha512-uWCv6FO/8LcpREhenN1d1b6fcspAB+cefwD7uti8C8VffIv0Um08TKMn98FynpTiU38+y2dUO55T11NgDt8VAA=="],
"@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="], "@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="],
"@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="], "@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="],
@@ -663,6 +666,8 @@
"browserslist": ["browserslist@4.28.1", "", { "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA=="], "browserslist": ["browserslist@4.28.1", "", { "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA=="],
"bun-types": ["bun-types@1.3.6", "", { "dependencies": { "@types/node": "*" } }, "sha512-OlFwHcnNV99r//9v5IIOgQ9Uk37gZqrNMCcqEaExdkVq3Avwqok1bJFmvGMCkCE0FqzdY8VMOZpfpR3lwI+CsQ=="],
"cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="], "cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="],
"call-bind": ["call-bind@1.0.8", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", "get-intrinsic": "^1.2.4", "set-function-length": "^1.2.2" } }, "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww=="], "call-bind": ["call-bind@1.0.8", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", "get-intrinsic": "^1.2.4", "set-function-length": "^1.2.2" } }, "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww=="],

View File

@@ -58,6 +58,7 @@
"@tanstack/eslint-config": "^0.3.0", "@tanstack/eslint-config": "^0.3.0",
"@testing-library/dom": "^10.4.0", "@testing-library/dom": "^10.4.0",
"@testing-library/react": "^16.2.0", "@testing-library/react": "^16.2.0",
"@types/bun": "^1.3.6",
"@types/node": "^22.10.2", "@types/node": "^22.10.2",
"@types/react": "^19.2.0", "@types/react": "^19.2.0",
"@types/react-dom": "^19.2.0", "@types/react-dom": "^19.2.0",

18
scripts/update-types.ts Normal file
View File

@@ -0,0 +1,18 @@
// scripts/update-types.ts
// Uso:
// bun run scripts/update-types.ts
import { $ } from "bun";
console.log("🔄 Generando tipos de Supabase...");
try {
// Ejecutamos el comando y capturamos la salida como texto
const output = await $`supabase gen types typescript --linked`.text();
// Escribimos el archivo directamente con Bun (garantiza UTF-8)
await Bun.write("src/types/supabase.ts", output);
console.log("✅ Tipos actualizados correctamente con acentos.");
} catch (error) {
console.error("❌ Error generando tipos:", error);
}

View File

@@ -1,5 +1,6 @@
import { TemplateSelectorCard } from './TemplateSelectorCard' import { TemplateSelectorCard } from './TemplateSelectorCard'
import type { NivelPlanEstudio, TipoCiclo } from '@/data/types/domain'
import type { CARRERAS } from '@/features/planes/nuevo/catalogs' import type { CARRERAS } from '@/features/planes/nuevo/catalogs'
import type { NewPlanWizardState } from '@/features/planes/nuevo/types' import type { NewPlanWizardState } from '@/features/planes/nuevo/types'
@@ -13,6 +14,7 @@ import {
SelectValue, SelectValue,
} from '@/components/ui/select' } from '@/components/ui/select'
import { Separator } from '@/components/ui/separator' import { Separator } from '@/components/ui/separator'
import { useCatalogosPlanes } from '@/data/hooks/usePlans'
import { import {
FACULTADES, FACULTADES,
NIVELES, NIVELES,
@@ -31,6 +33,18 @@ export function PasoBasicosForm({
onChange: React.Dispatch<React.SetStateAction<NewPlanWizardState>> onChange: React.Dispatch<React.SetStateAction<NewPlanWizardState>>
carrerasFiltradas: typeof CARRERAS carrerasFiltradas: typeof CARRERAS
}) { }) {
const { data: catalogos } = useCatalogosPlanes()
// Preferir los catálogos remotos si están disponibles; si no, usar los locales
const facultadesList = catalogos?.facultades ?? FACULTADES
const rawCarreras = catalogos?.carreras ?? carrerasFiltradas
const filteredCarreras = rawCarreras.filter((c: any) => {
const facId = wizard.datosBasicos.facultadId
if (!facId) return true
// soportar ambos shapes: `facultad_id` (BD) o `facultadId` (local)
return c.facultad_id ? c.facultad_id === facId : c.facultadId === facId
})
return ( return (
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<div className="grid gap-4 sm:grid-cols-2"> <div className="grid gap-4 sm:grid-cols-2">
@@ -40,7 +54,7 @@ export function PasoBasicosForm({
</Label> </Label>
<Input <Input
id="nombrePlan" id="nombrePlan"
placeholder="Ej. Ingeniería en Sistemas 2026" placeholder="Ej. Ingeniería en Sistemas (2026)"
value={wizard.datosBasicos.nombrePlan} value={wizard.datosBasicos.nombrePlan}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
onChange((w) => ({ onChange((w) => ({
@@ -79,7 +93,7 @@ export function PasoBasicosForm({
<SelectValue placeholder="Ej. Facultad de Ingeniería" /> <SelectValue placeholder="Ej. Facultad de Ingeniería" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
{FACULTADES.map((f) => ( {facultadesList.map((f: any) => (
<SelectItem key={f.id} value={f.id}> <SelectItem key={f.id} value={f.id}>
{f.nombre} {f.nombre}
</SelectItem> </SelectItem>
@@ -112,7 +126,7 @@ export function PasoBasicosForm({
<SelectValue placeholder="Ej. Ingeniería en Cibernética y Sistemas Computacionales" /> <SelectValue placeholder="Ej. Ingeniería en Cibernética y Sistemas Computacionales" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
{carrerasFiltradas.map((c) => ( {filteredCarreras.map((c: any) => (
<SelectItem key={c.id} value={c.id}> <SelectItem key={c.id} value={c.id}>
{c.nombre} {c.nombre}
</SelectItem> </SelectItem>
@@ -125,11 +139,13 @@ export function PasoBasicosForm({
<Label htmlFor="nivel">Nivel</Label> <Label htmlFor="nivel">Nivel</Label>
<Select <Select
value={wizard.datosBasicos.nivel} value={wizard.datosBasicos.nivel}
onValueChange={(value) => onValueChange={(value: NivelPlanEstudio) =>
onChange((w) => ({ onChange(
(w): NewPlanWizardState => ({
...w, ...w,
datosBasicos: { ...w.datosBasicos, nivel: value }, datosBasicos: { ...w.datosBasicos, nivel: value },
})) }),
)
} }
> >
<SelectTrigger <SelectTrigger
@@ -157,7 +173,7 @@ export function PasoBasicosForm({
<Label htmlFor="tipoCiclo">Tipo de ciclo</Label> <Label htmlFor="tipoCiclo">Tipo de ciclo</Label>
<Select <Select
value={wizard.datosBasicos.tipoCiclo} value={wizard.datosBasicos.tipoCiclo}
onValueChange={(value) => onValueChange={(value: TipoCiclo) =>
onChange((w) => ({ onChange((w) => ({
...w, ...w,
datosBasicos: { datosBasicos: {
@@ -180,8 +196,8 @@ export function PasoBasicosForm({
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
{TIPOS_CICLO.map((t) => ( {TIPOS_CICLO.map((t) => (
<SelectItem key={t.value} value={t.value}> <SelectItem key={t} value={t}>
{t.label} {t}
</SelectItem> </SelectItem>
))} ))}
</SelectContent> </SelectContent>

View File

@@ -30,7 +30,7 @@ export function PasoDetallesPanel({
onGenerarIA: () => void onGenerarIA: () => void
isLoading: boolean isLoading: boolean
}) { }) {
if (wizard.modoCreacion === 'MANUAL') { if (wizard.tipoOrigen === 'MANUAL') {
return ( return (
<Card> <Card>
<CardHeader> <CardHeader>
@@ -43,7 +43,7 @@ export function PasoDetallesPanel({
) )
} }
if (wizard.modoCreacion === 'IA') { if (wizard.tipoOrigen === 'IA') {
return ( return (
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-1">
@@ -162,10 +162,7 @@ export function PasoDetallesPanel({
) )
} }
if ( if (wizard.tipoOrigen === 'CLONADO_INTERNO') {
wizard.modoCreacion === 'CLONADO' &&
wizard.subModoClonado === 'INTERNO'
) {
return ( return (
<div className="grid gap-4"> <div className="grid gap-4">
<div className="grid gap-3 sm:grid-cols-3"> <div className="grid gap-3 sm:grid-cols-3">
@@ -269,10 +266,7 @@ export function PasoDetallesPanel({
) )
} }
if ( if (wizard.tipoOrigen === 'CLONADO_TRADICIONAL') {
wizard.modoCreacion === 'CLONADO' &&
wizard.subModoClonado === 'TRADICIONAL'
) {
return ( return (
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-1">

View File

@@ -1,10 +1,7 @@
import * as Icons from 'lucide-react' import * as Icons from 'lucide-react'
import type { import type { TipoOrigen } from '@/data/types/domain'
NewPlanWizardState, import type { NewPlanWizardState } from '@/features/planes/nuevo/types'
ModoCreacion,
SubModoClonado,
} from '@/features/planes/nuevo/types'
import { import {
Card, Card,
@@ -21,8 +18,7 @@ export function PasoModoCardGroup({
wizard: NewPlanWizardState wizard: NewPlanWizardState
onChange: React.Dispatch<React.SetStateAction<NewPlanWizardState>> onChange: React.Dispatch<React.SetStateAction<NewPlanWizardState>>
}) { }) {
const isSelected = (m: ModoCreacion) => wizard.modoCreacion === m const isSelected = (m: TipoOrigen) => wizard.tipoOrigen === m
const isSubSelected = (s: SubModoClonado) => wizard.subModoClonado === s
const handleKeyActivate = (e: React.KeyboardEvent, cb: () => void) => { const handleKeyActivate = (e: React.KeyboardEvent, cb: () => void) => {
const key = e.key const key = e.key
if ( if (
@@ -41,19 +37,21 @@ export function PasoModoCardGroup({
<Card <Card
className={isSelected('MANUAL') ? 'ring-ring ring-2' : ''} className={isSelected('MANUAL') ? 'ring-ring ring-2' : ''}
onClick={() => onClick={() =>
onChange((w) => ({ onChange(
(w): NewPlanWizardState => ({
...w, ...w,
modoCreacion: 'MANUAL', tipoOrigen: 'MANUAL',
subModoClonado: undefined, }),
})) )
} }
onKeyDown={(e: React.KeyboardEvent) => onKeyDown={(e: React.KeyboardEvent) =>
handleKeyActivate(e, () => handleKeyActivate(e, () =>
onChange((w) => ({ onChange(
(w): NewPlanWizardState => ({
...w, ...w,
modoCreacion: 'MANUAL', tipoOrigen: 'MANUAL',
subModoClonado: undefined, }),
})), ),
) )
} }
role="button" role="button"
@@ -70,19 +68,21 @@ export function PasoModoCardGroup({
<Card <Card
className={isSelected('IA') ? 'ring-ring ring-2' : ''} className={isSelected('IA') ? 'ring-ring ring-2' : ''}
onClick={() => onClick={() =>
onChange((w) => ({ onChange(
(w): NewPlanWizardState => ({
...w, ...w,
modoCreacion: 'IA', tipoOrigen: 'IA',
subModoClonado: undefined, }),
})) )
} }
onKeyDown={(e: React.KeyboardEvent) => onKeyDown={(e: React.KeyboardEvent) =>
handleKeyActivate(e, () => handleKeyActivate(e, () =>
onChange((w) => ({ onChange(
(w): NewPlanWizardState => ({
...w, ...w,
modoCreacion: 'IA', tipoOrigen: 'IA',
subModoClonado: undefined, }),
})), ),
) )
} }
role="button" role="button"
@@ -99,11 +99,13 @@ export function PasoModoCardGroup({
</Card> </Card>
<Card <Card
className={isSelected('CLONADO') ? 'ring-ring ring-2' : ''} className={isSelected('OTRO') ? 'ring-ring ring-2' : ''}
onClick={() => onChange((w) => ({ ...w, modoCreacion: 'CLONADO' }))} onClick={() =>
onChange((w): NewPlanWizardState => ({ ...w, tipoOrigen: 'OTRO' }))
}
onKeyDown={(e: React.KeyboardEvent) => onKeyDown={(e: React.KeyboardEvent) =>
handleKeyActivate(e, () => handleKeyActivate(e, () =>
onChange((w) => ({ ...w, modoCreacion: 'CLONADO' })), onChange((w): NewPlanWizardState => ({ ...w, tipoOrigen: 'OTRO' })),
) )
} }
role="button" role="button"
@@ -115,22 +117,34 @@ export function PasoModoCardGroup({
</CardTitle> </CardTitle>
<CardDescription>Desde un plan existente o archivos.</CardDescription> <CardDescription>Desde un plan existente o archivos.</CardDescription>
</CardHeader> </CardHeader>
{wizard.modoCreacion === 'CLONADO' && ( {(wizard.tipoOrigen === 'OTRO' ||
wizard.tipoOrigen === 'CLONADO_INTERNO' ||
wizard.tipoOrigen === 'CLONADO_TRADICIONAL') && (
<CardContent className="flex flex-col gap-3"> <CardContent className="flex flex-col gap-3">
<div <div
role="button" role="button"
tabIndex={0} tabIndex={0}
onClick={(e) => { onClick={(e) => {
e.stopPropagation() e.stopPropagation()
onChange((w) => ({ ...w, subModoClonado: 'INTERNO' })) onChange(
(w): NewPlanWizardState => ({
...w,
tipoOrigen: 'CLONADO_INTERNO',
}),
)
}} }}
onKeyDown={(e: React.KeyboardEvent) => onKeyDown={(e: React.KeyboardEvent) =>
handleKeyActivate(e, () => handleKeyActivate(e, () =>
onChange((w) => ({ ...w, subModoClonado: 'INTERNO' })), onChange(
(w): NewPlanWizardState => ({
...w,
tipoOrigen: 'CLONADO_INTERNO',
}),
),
) )
} }
className={`hover:border-primary/50 hover:bg-accent flex cursor-pointer flex-row items-center justify-center gap-2 rounded-lg border p-4 text-center transition-all sm:flex-col ${ className={`hover:border-primary/50 hover:bg-accent flex cursor-pointer flex-row items-center justify-center gap-2 rounded-lg border p-4 text-center transition-all sm:flex-col ${
isSubSelected('INTERNO') isSelected('CLONADO_INTERNO')
? 'border-primary bg-primary/5 ring-primary text-primary ring-1' ? 'border-primary bg-primary/5 ring-primary text-primary ring-1'
: 'border-border text-muted-foreground' : 'border-border text-muted-foreground'
} `} } `}
@@ -144,15 +158,25 @@ export function PasoModoCardGroup({
tabIndex={0} tabIndex={0}
onClick={(e) => { onClick={(e) => {
e.stopPropagation() e.stopPropagation()
onChange((w) => ({ ...w, subModoClonado: 'TRADICIONAL' })) onChange(
(w): NewPlanWizardState => ({
...w,
tipoOrigen: 'CLONADO_TRADICIONAL',
}),
)
}} }}
onKeyDown={(e: React.KeyboardEvent) => onKeyDown={(e: React.KeyboardEvent) =>
handleKeyActivate(e, () => handleKeyActivate(e, () =>
onChange((w) => ({ ...w, subModoClonado: 'TRADICIONAL' })), onChange(
(w): NewPlanWizardState => ({
...w,
tipoOrigen: 'CLONADO_TRADICIONAL',
}),
),
) )
} }
className={`hover:border-primary/50 hover:bg-accent flex cursor-pointer flex-row items-center justify-center gap-2 rounded-lg border p-4 text-center transition-all sm:flex-col ${ className={`hover:border-primary/50 hover:bg-accent flex cursor-pointer flex-row items-center justify-center gap-2 rounded-lg border p-4 text-center transition-all sm:flex-col ${
isSubSelected('TRADICIONAL') isSelected('CLONADO_TRADICIONAL')
? 'border-primary bg-primary/5 ring-primary text-primary ring-1' ? 'border-primary bg-primary/5 ring-primary text-primary ring-1'
: 'border-border text-muted-foreground' : 'border-border text-muted-foreground'
} `} } `}

View File

@@ -1,4 +1,4 @@
import type { Database, Enums, Tables } from "../../types/supabase"; import type { Enums, Tables } from "../../types/supabase";
export type UUID = string; export type UUID = string;
@@ -52,14 +52,14 @@ export type PlanDatosSep = {
}; };
export type PlanEstudioWithRel = export type PlanEstudioWithRel =
& Database["public"]["Tables"]["planes_estudio"]["Row"] & Tables<"planes_estudio">
& { & {
carreras: carreras:
| Database["public"]["Tables"]["carreras"]["Row"] & { | Tables<"carreras"> & {
facultades: Database["public"]["Tables"]["facultades"]["Row"] | null; facultades: Tables<"facultades"> | null;
} }
| null; | null;
estados_plan: Database["public"]["Tables"]["estados_plan"]["Row"] | null; estados_plan: Tables<"estados_plan"> | null;
}; };
export type Paged<T> = { data: Array<T>; count: number | null }; export type Paged<T> = { data: Array<T>; count: number | null };

View File

@@ -3,6 +3,8 @@ import * as Icons from 'lucide-react'
import { useNuevoPlanWizard } from './hooks/useNuevoPlanWizard' import { useNuevoPlanWizard } from './hooks/useNuevoPlanWizard'
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'
import { PasoModoCardGroup } from '@/components/planes/wizard/PasoModoCardGroup' import { PasoModoCardGroup } from '@/components/planes/wizard/PasoModoCardGroup'
@@ -61,12 +63,20 @@ export default function NuevoPlanModalContainer() {
} }
const crearPlan = async () => { const crearPlan = async () => {
setWizard((w) => ({ ...w, isLoading: true, errorMessage: null })) setWizard((w: NewPlanWizardState) => ({
...w,
isLoading: true,
errorMessage: null,
}))
await new Promise((r) => setTimeout(r, 900)) await new Promise((r) => setTimeout(r, 900))
const nuevoId = (() => { const nuevoId = (() => {
if (wizard.modoCreacion === 'MANUAL') return 'plan_new_manual_001' if (wizard.tipoOrigen === 'MANUAL') return 'plan_new_manual_001'
if (wizard.modoCreacion === 'IA') return 'plan_new_ai_001' if (wizard.tipoOrigen === 'IA') return 'plan_new_ai_001'
if (wizard.subModoClonado === 'INTERNO') return 'plan_new_clone_001' if (
wizard.tipoOrigen === 'CLONADO_INTERNO' ||
wizard.tipoOrigen === 'CLONADO_TRADICIONAL'
)
return 'plan_new_clone_001'
return 'plan_new_import_001' return 'plan_new_import_001'
})() })()
navigate({ to: `/planes/${nuevoId}` }) navigate({ to: `/planes/${nuevoId}` })
@@ -124,7 +134,7 @@ export default function NuevoPlanModalContainer() {
totalSteps={totalSteps} totalSteps={totalSteps}
currentTitle={methods.current.title} currentTitle={methods.current.title}
currentDescription={methods.current.description} currentDescription={methods.current.description}
nextTitle={nextStep?.title} nextTitle={nextStep.title}
onClose={handleClose} onClose={handleClose}
Wizard={Wizard} Wizard={Wizard}
/> />

View File

@@ -1,4 +1,4 @@
import type { TipoCiclo } from "./types"; import type { NivelPlanEstudio, TipoCiclo } from "@/data/types/domain";
export const FACULTADES = [ export const FACULTADES = [
{ id: "ing", nombre: "Facultad de Ingeniería" }, { id: "ing", nombre: "Facultad de Ingeniería" },
@@ -16,16 +16,20 @@ export const CARRERAS = [
{ id: "act", nombre: "Actuaría", facultadId: "neg" }, { id: "act", nombre: "Actuaría", facultadId: "neg" },
]; ];
export const NIVELES = [ export const NIVELES: Array<NivelPlanEstudio> = [
"Licenciatura", "Licenciatura",
"Especialidad",
"Maestría", "Maestría",
"Doctorado", "Doctorado",
"Especialidad",
"Diplomado",
"Otro",
]; ];
export const TIPOS_CICLO: Array<{ value: TipoCiclo; label: string }> = [
{ value: "SEMESTRE", label: "Semestre" }, export const TIPOS_CICLO: Array<TipoCiclo> = [
{ value: "CUATRIMESTRE", label: "Cuatrimestre" }, "Semestre",
{ value: "TRIMESTRE", label: "Trimestre" }, "Cuatrimestre",
"Trimestre",
"Otro",
]; ];
export const PLANES_EXISTENTES = [ export const PLANES_EXISTENTES = [

View File

@@ -2,12 +2,13 @@ import { useMemo, useState } from "react";
import { CARRERAS } from "../catalogs"; import { CARRERAS } from "../catalogs";
import type { NewPlanWizardState, PlanPreview, TipoCiclo } from "../types"; import type { NewPlanWizardState, PlanPreview } from "../types";
import type { NivelPlanEstudio, TipoCiclo } from "@/data/types/domain";
export function useNuevoPlanWizard() { export function useNuevoPlanWizard() {
const [wizard, setWizard] = useState<NewPlanWizardState>({ const [wizard, setWizard] = useState<NewPlanWizardState>({
step: 1, step: 1,
modoCreacion: null, tipoOrigen: null,
datosBasicos: { datosBasicos: {
nombrePlan: "", nombrePlan: "",
carreraId: "", carreraId: "",
@@ -40,7 +41,6 @@ export function useNuevoPlanWizard() {
}, },
iaConfig: { iaConfig: {
descripcionEnfoque: "", descripcionEnfoque: "",
poblacionObjetivo: "",
notasAdicionales: "", notasAdicionales: "",
archivosReferencia: [], archivosReferencia: [],
repositoriosReferencia: [], repositoriosReferencia: [],
@@ -56,9 +56,10 @@ export function useNuevoPlanWizard() {
return fac ? CARRERAS.filter((c) => c.facultadId === fac) : CARRERAS; return fac ? CARRERAS.filter((c) => c.facultadId === fac) : CARRERAS;
}, [wizard.datosBasicos.facultadId]); }, [wizard.datosBasicos.facultadId]);
const canContinueDesdeModo = wizard.modoCreacion === "MANUAL" || const canContinueDesdeModo = wizard.tipoOrigen === "MANUAL" ||
wizard.modoCreacion === "IA" || wizard.tipoOrigen === "IA" ||
(wizard.modoCreacion === "CLONADO" && !!wizard.subModoClonado); (wizard.tipoOrigen === "CLONADO_INTERNO" ||
wizard.tipoOrigen === "CLONADO_TRADICIONAL");
const canContinueDesdeBasicos = !!wizard.datosBasicos.nombrePlan && const canContinueDesdeBasicos = !!wizard.datosBasicos.nombrePlan &&
!!wizard.datosBasicos.carreraId && !!wizard.datosBasicos.carreraId &&
@@ -73,17 +74,16 @@ export function useNuevoPlanWizard() {
!!wizard.datosBasicos.plantillaMapaVersion; !!wizard.datosBasicos.plantillaMapaVersion;
const canContinueDesdeDetalles = (() => { const canContinueDesdeDetalles = (() => {
if (wizard.modoCreacion === "MANUAL") return true; if (wizard.tipoOrigen === "MANUAL") return true;
if (wizard.modoCreacion === "IA") { if (wizard.tipoOrigen === "IA") {
// Requerimos descripción del enfoque y notas adicionales // Requerimos descripción del enfoque y notas adicionales
return !!wizard.iaConfig?.descripcionEnfoque && return !!wizard.iaConfig?.descripcionEnfoque &&
!!wizard.iaConfig?.notasAdicionales; !!wizard.iaConfig.notasAdicionales;
} }
if (wizard.modoCreacion === "CLONADO") { if (wizard.tipoOrigen === "CLONADO_INTERNO") {
if (wizard.subModoClonado === "INTERNO") {
return !!wizard.clonInterno?.planOrigenId; return !!wizard.clonInterno?.planOrigenId;
} }
if (wizard.subModoClonado === "TRADICIONAL") { if (wizard.tipoOrigen === "CLONADO_TRADICIONAL") {
const t = wizard.clonTradicional; const t = wizard.clonTradicional;
if (!t) return false; if (!t) return false;
const tieneWord = !!t.archivoWordPlanId; const tieneWord = !!t.archivoWordPlanId;
@@ -91,7 +91,6 @@ export function useNuevoPlanWizard() {
!!t.archivoAsignaturasExcelId; !!t.archivoAsignaturasExcelId;
return tieneWord && tieneAlMenosUnExcel; return tieneWord && tieneAlMenosUnExcel;
} }
}
return false; return false;
})(); })();
@@ -101,7 +100,7 @@ export function useNuevoPlanWizard() {
// Ensure preview has the stricter types required by `PlanPreview`. // Ensure preview has the stricter types required by `PlanPreview`.
let tipoCicloSafe: TipoCiclo; let tipoCicloSafe: TipoCiclo;
if (wizard.datosBasicos.tipoCiclo === "") { if (wizard.datosBasicos.tipoCiclo === "") {
tipoCicloSafe = "SEMESTRE"; tipoCicloSafe = "Semestre";
} else { } else {
tipoCicloSafe = wizard.datosBasicos.tipoCiclo; tipoCicloSafe = wizard.datosBasicos.tipoCiclo;
} }
@@ -112,7 +111,7 @@ export function useNuevoPlanWizard() {
const preview: PlanPreview = { const preview: PlanPreview = {
nombrePlan: wizard.datosBasicos.nombrePlan || "Plan sin nombre", nombrePlan: wizard.datosBasicos.nombrePlan || "Plan sin nombre",
nivel: wizard.datosBasicos.nivel || "Licenciatura", nivel: wizard.datosBasicos.nivel as NivelPlanEstudio,
tipoCiclo: tipoCicloSafe, tipoCiclo: tipoCicloSafe,
numCiclos: numCiclosSafe, numCiclos: numCiclosSafe,
numAsignaturasAprox: numCiclosSafe * 6, numAsignaturasAprox: numCiclosSafe * 6,
@@ -121,7 +120,7 @@ export function useNuevoPlanWizard() {
{ id: "perfil", titulo: "Perfil de egreso", resumen: "Borrador…" }, { id: "perfil", titulo: "Perfil de egreso", resumen: "Borrador…" },
], ],
}; };
setWizard((w) => ({ setWizard((w: NewPlanWizardState) => ({
...w, ...w,
isLoading: false, isLoading: false,
resumen: { previewPlan: preview }, resumen: { previewPlan: preview },

View File

@@ -1,10 +1,12 @@
export type TipoCiclo = "SEMESTRE" | "CUATRIMESTRE" | "TRIMESTRE"; import type {
export type ModoCreacion = "MANUAL" | "IA" | "CLONADO"; NivelPlanEstudio,
export type SubModoClonado = "INTERNO" | "TRADICIONAL"; TipoCiclo,
TipoOrigen,
} from "@/data/types/domain";
export type PlanPreview = { export type PlanPreview = {
nombrePlan: string; nombrePlan: string;
nivel: string; nivel: NivelPlanEstudio;
tipoCiclo: TipoCiclo; tipoCiclo: TipoCiclo;
numCiclos: number; numCiclos: number;
numAsignaturasAprox?: number; numAsignaturasAprox?: number;
@@ -13,13 +15,12 @@ export type PlanPreview = {
export type NewPlanWizardState = { export type NewPlanWizardState = {
step: 1 | 2 | 3 | 4; step: 1 | 2 | 3 | 4;
modoCreacion: ModoCreacion | null; tipoOrigen: TipoOrigen | null;
subModoClonado?: SubModoClonado;
datosBasicos: { datosBasicos: {
nombrePlan: string; nombrePlan: string;
carreraId: string; carreraId: string;
facultadId: string; facultadId: string;
nivel: string; nivel: NivelPlanEstudio | "";
tipoCiclo: TipoCiclo | ""; tipoCiclo: TipoCiclo | "";
numCiclos: number | undefined; numCiclos: number | undefined;
// Selección de plantillas (obligatorias) // Selección de plantillas (obligatorias)
@@ -53,7 +54,6 @@ export type NewPlanWizardState = {
}; };
iaConfig?: { iaConfig?: {
descripcionEnfoque: string; descripcionEnfoque: string;
poblacionObjetivo: string;
notasAdicionales: string; notasAdicionales: string;
archivosReferencia: Array<string>; archivosReferencia: Array<string>;
repositoriosReferencia?: Array<string>; repositoriosReferencia?: Array<string>;

Binary file not shown.