Files
acad-ia-2/src/features/asignaturas/nueva/NuevaAsignaturaModalContainer.tsx
Guillermo Arrieta Medina f3414f23f6 Refactor: unifica wizards con WizardLayout/WizardResponsiveHeader y convierte asignaturas en layout con Outlet
- Se introdujo un layout genérico de wizard (WizardLayout) con headerSlot/footerSlot y se migraron los modales de Nuevo Plan y Nueva Asignatura a esta estructura usando defineStepper.
- Se creó y reutilizó WizardResponsiveHeader para un encabezado responsivo consistente (progreso en móvil y navegación en escritorio) en ambos wizards.
- Se homologó WizardControls del wizard de asignaturas para alinearlo al patrón del wizard de planes (props onPrev/onNext, flags de disable, manejo de error/loading y creación).
- Se mejoró la captura de datos en el wizard de asignatura: créditos como flotante con 2 decimales, placeholders/estilos en inputs/selects y uso de catálogo real de estructuras vía useSubjectEstructuras con qk.estructurasAsignatura.
- Se reorganizó la sección de asignaturas del detalle del plan: el contenido del antiguo index se movió a asignaturas.tsx como layout y se agregó <Outlet />; navegación a “nueva asignatura” ajustada al path correcto.
2026-02-04 13:36:46 -06:00

164 lines
5.0 KiB
TypeScript

import { useNavigate } from '@tanstack/react-router'
import * as Icons from 'lucide-react'
import { useNuevaAsignaturaWizard } from './hooks/useNuevaAsignaturaWizard'
import { PasoBasicosForm } from '@/components/asignaturas/wizard/PasoBasicosForm'
import { PasoDetallesPanel } from '@/components/asignaturas/wizard/PasoDetallesPanel'
import { PasoMetodoCardGroup } from '@/components/asignaturas/wizard/PasoMetodoCardGroup'
import { PasoResumenCard } from '@/components/asignaturas/wizard/PasoResumenCard'
import { WizardControls } from '@/components/asignaturas/wizard/WizardControls'
import { defineStepper } from '@/components/stepper'
import { Button } from '@/components/ui/button'
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from '@/components/ui/card'
import { WizardLayout } from '@/components/wizard/WizardLayout'
import { WizardResponsiveHeader } from '@/components/wizard/WizardResponsiveHeader'
const auth_get_current_user_role = (): string => 'JEFE_CARRERA'
const Wizard = defineStepper(
{
id: 'metodo',
title: 'Método',
description: 'Manual, IA o Clonado',
},
{
id: 'basicos',
title: 'Datos básicos',
description: 'Nombre y estructura',
},
{
id: 'detalles',
title: 'Detalles',
description: 'Detalles según modo',
},
{
id: 'resumen',
title: 'Resumen',
description: 'Confirmar creación',
},
)
export function NuevaAsignaturaModalContainer({ planId }: { planId: string }) {
const navigate = useNavigate()
const role = auth_get_current_user_role()
const {
wizard,
setWizard,
canContinueDesdeMetodo,
canContinueDesdeBasicos,
canContinueDesdeDetalles,
simularGeneracionIA,
crearAsignatura,
} = useNuevaAsignaturaWizard(planId)
const handleClose = () => {
navigate({ to: `/planes/${planId}/asignaturas`, resetScroll: false })
}
if (role !== 'JEFE_CARRERA') {
return (
<WizardLayout title="Nueva Asignatura" onClose={handleClose}>
<Card className="border-destructive/40">
<CardHeader>
<CardTitle className="text-destructive flex items-center gap-2">
<Icons.ShieldAlert className="h-5 w-5" />
Sin permisos
</CardTitle>
<CardDescription>
Solo el Jefe de Carrera puede crear asignaturas.
</CardDescription>
</CardHeader>
<CardContent className="flex justify-end">
<Button variant="secondary" onClick={handleClose}>
Volver
</Button>
</CardContent>
</Card>
</WizardLayout>
)
}
return (
<Wizard.Stepper.Provider
initialStep={Wizard.utils.getFirst().id}
className="flex h-full flex-col"
>
{({ methods }) => {
const idx = Wizard.utils.getIndex(methods.current.id)
return (
<WizardLayout
title="Nueva Asignatura"
onClose={handleClose}
headerSlot={
<WizardResponsiveHeader wizard={Wizard} methods={methods} />
}
footerSlot={
<Wizard.Stepper.Controls>
<WizardControls
errorMessage={wizard.errorMessage}
onPrev={() => methods.prev()}
onNext={() => methods.next()}
disablePrev={idx === 0 || wizard.isLoading}
disableNext={
wizard.isLoading ||
(idx === 0 && !canContinueDesdeMetodo) ||
(idx === 1 && !canContinueDesdeBasicos) ||
(idx === 2 && !canContinueDesdeDetalles)
}
disableCreate={wizard.isLoading}
isLastStep={idx >= Wizard.steps.length - 1}
wizard={wizard}
setWizard={setWizard}
onCreate={async () => {
await crearAsignatura()
handleClose()
}}
/>
</Wizard.Stepper.Controls>
}
>
<div className="mx-auto max-w-3xl">
{idx === 0 && (
<Wizard.Stepper.Panel>
<PasoMetodoCardGroup wizard={wizard} onChange={setWizard} />
</Wizard.Stepper.Panel>
)}
{idx === 1 && (
<Wizard.Stepper.Panel>
<PasoBasicosForm wizard={wizard} onChange={setWizard} />
</Wizard.Stepper.Panel>
)}
{idx === 2 && (
<Wizard.Stepper.Panel>
<PasoDetallesPanel
wizard={wizard}
onChange={setWizard}
onGenerarIA={simularGeneracionIA}
/>
</Wizard.Stepper.Panel>
)}
{idx === 3 && (
<Wizard.Stepper.Panel>
<PasoResumenCard wizard={wizard} />
</Wizard.Stepper.Panel>
)}
</div>
</WizardLayout>
)
}}
</Wizard.Stepper.Provider>
)
}