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

@@ -1,5 +1,6 @@
import { TemplateSelectorCard } from './TemplateSelectorCard'
import type { NivelPlanEstudio, TipoCiclo } from '@/data/types/domain'
import type { CARRERAS } from '@/features/planes/nuevo/catalogs'
import type { NewPlanWizardState } from '@/features/planes/nuevo/types'
@@ -13,6 +14,7 @@ import {
SelectValue,
} from '@/components/ui/select'
import { Separator } from '@/components/ui/separator'
import { useCatalogosPlanes } from '@/data/hooks/usePlans'
import {
FACULTADES,
NIVELES,
@@ -31,6 +33,18 @@ export function PasoBasicosForm({
onChange: React.Dispatch<React.SetStateAction<NewPlanWizardState>>
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 (
<div className="flex flex-col gap-2">
<div className="grid gap-4 sm:grid-cols-2">
@@ -40,7 +54,7 @@ export function PasoBasicosForm({
</Label>
<Input
id="nombrePlan"
placeholder="Ej. Ingeniería en Sistemas 2026"
placeholder="Ej. Ingeniería en Sistemas (2026)"
value={wizard.datosBasicos.nombrePlan}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
onChange((w) => ({
@@ -79,7 +93,7 @@ export function PasoBasicosForm({
<SelectValue placeholder="Ej. Facultad de Ingeniería" />
</SelectTrigger>
<SelectContent>
{FACULTADES.map((f) => (
{facultadesList.map((f: any) => (
<SelectItem key={f.id} value={f.id}>
{f.nombre}
</SelectItem>
@@ -112,7 +126,7 @@ export function PasoBasicosForm({
<SelectValue placeholder="Ej. Ingeniería en Cibernética y Sistemas Computacionales" />
</SelectTrigger>
<SelectContent>
{carrerasFiltradas.map((c) => (
{filteredCarreras.map((c: any) => (
<SelectItem key={c.id} value={c.id}>
{c.nombre}
</SelectItem>
@@ -125,11 +139,13 @@ export function PasoBasicosForm({
<Label htmlFor="nivel">Nivel</Label>
<Select
value={wizard.datosBasicos.nivel}
onValueChange={(value) =>
onChange((w) => ({
...w,
datosBasicos: { ...w.datosBasicos, nivel: value },
}))
onValueChange={(value: NivelPlanEstudio) =>
onChange(
(w): NewPlanWizardState => ({
...w,
datosBasicos: { ...w.datosBasicos, nivel: value },
}),
)
}
>
<SelectTrigger
@@ -157,7 +173,7 @@ export function PasoBasicosForm({
<Label htmlFor="tipoCiclo">Tipo de ciclo</Label>
<Select
value={wizard.datosBasicos.tipoCiclo}
onValueChange={(value) =>
onValueChange={(value: TipoCiclo) =>
onChange((w) => ({
...w,
datosBasicos: {
@@ -180,8 +196,8 @@ export function PasoBasicosForm({
</SelectTrigger>
<SelectContent>
{TIPOS_CICLO.map((t) => (
<SelectItem key={t.value} value={t.value}>
{t.label}
<SelectItem key={t} value={t}>
{t}
</SelectItem>
))}
</SelectContent>

View File

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

View File

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