diff --git a/src/routeTree.gen.ts b/src/routeTree.gen.ts
index 0858c7d..8b40112 100644
--- a/src/routeTree.gen.ts
+++ b/src/routeTree.gen.ts
@@ -17,7 +17,10 @@ import { Route as IndexRouteImport } from './routes/index'
import { Route as DemoTanstackQueryRouteImport } from './routes/demo/tanstack-query'
import { Route as PlanesListaRouteRouteImport } from './routes/planes/_lista/route'
import { Route as PlanesPlanIdRouteRouteImport } from './routes/planes/$planId/route'
+import { Route as AsignaturasListaRouteRouteImport } from './routes/asignaturas/_lista/route'
+import { Route as AsignaturasAsignaturaIdRouteRouteImport } from './routes/asignaturas/$asignaturaId/route'
import { Route as PlanesListaNuevoRouteImport } from './routes/planes/_lista/nuevo'
+import { Route as AsignaturasListaNuevaRouteImport } from './routes/asignaturas/_lista/nueva'
const Stepper2Route = Stepper2RouteImport.update({
id: '/stepper2',
@@ -59,11 +62,27 @@ const PlanesPlanIdRouteRoute = PlanesPlanIdRouteRouteImport.update({
path: '/planes/$planId',
getParentRoute: () => rootRouteImport,
} as any)
+const AsignaturasListaRouteRoute = AsignaturasListaRouteRouteImport.update({
+ id: '/asignaturas/_lista',
+ path: '/asignaturas',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const AsignaturasAsignaturaIdRouteRoute =
+ AsignaturasAsignaturaIdRouteRouteImport.update({
+ id: '/asignaturas/$asignaturaId',
+ path: '/asignaturas/$asignaturaId',
+ getParentRoute: () => rootRouteImport,
+ } as any)
const PlanesListaNuevoRoute = PlanesListaNuevoRouteImport.update({
id: '/nuevo',
path: '/nuevo',
getParentRoute: () => PlanesListaRouteRoute,
} as any)
+const AsignaturasListaNuevaRoute = AsignaturasListaNuevaRouteImport.update({
+ id: '/nueva',
+ path: '/nueva',
+ getParentRoute: () => AsignaturasListaRouteRoute,
+} as any)
export interface FileRoutesByFullPath {
'/': typeof IndexRoute
@@ -71,9 +90,12 @@ export interface FileRoutesByFullPath {
'/login': typeof LoginRoute
'/stepper': typeof StepperRoute
'/stepper2': typeof Stepper2Route
+ '/asignaturas/$asignaturaId': typeof AsignaturasAsignaturaIdRouteRoute
+ '/asignaturas': typeof AsignaturasListaRouteRouteWithChildren
'/planes/$planId': typeof PlanesPlanIdRouteRoute
'/planes': typeof PlanesListaRouteRouteWithChildren
'/demo/tanstack-query': typeof DemoTanstackQueryRoute
+ '/asignaturas/nueva': typeof AsignaturasListaNuevaRoute
'/planes/nuevo': typeof PlanesListaNuevoRoute
}
export interface FileRoutesByTo {
@@ -82,9 +104,12 @@ export interface FileRoutesByTo {
'/login': typeof LoginRoute
'/stepper': typeof StepperRoute
'/stepper2': typeof Stepper2Route
+ '/asignaturas/$asignaturaId': typeof AsignaturasAsignaturaIdRouteRoute
+ '/asignaturas': typeof AsignaturasListaRouteRouteWithChildren
'/planes/$planId': typeof PlanesPlanIdRouteRoute
'/planes': typeof PlanesListaRouteRouteWithChildren
'/demo/tanstack-query': typeof DemoTanstackQueryRoute
+ '/asignaturas/nueva': typeof AsignaturasListaNuevaRoute
'/planes/nuevo': typeof PlanesListaNuevoRoute
}
export interface FileRoutesById {
@@ -94,9 +119,12 @@ export interface FileRoutesById {
'/login': typeof LoginRoute
'/stepper': typeof StepperRoute
'/stepper2': typeof Stepper2Route
+ '/asignaturas/$asignaturaId': typeof AsignaturasAsignaturaIdRouteRoute
+ '/asignaturas/_lista': typeof AsignaturasListaRouteRouteWithChildren
'/planes/$planId': typeof PlanesPlanIdRouteRoute
'/planes/_lista': typeof PlanesListaRouteRouteWithChildren
'/demo/tanstack-query': typeof DemoTanstackQueryRoute
+ '/asignaturas/_lista/nueva': typeof AsignaturasListaNuevaRoute
'/planes/_lista/nuevo': typeof PlanesListaNuevoRoute
}
export interface FileRouteTypes {
@@ -107,9 +135,12 @@ export interface FileRouteTypes {
| '/login'
| '/stepper'
| '/stepper2'
+ | '/asignaturas/$asignaturaId'
+ | '/asignaturas'
| '/planes/$planId'
| '/planes'
| '/demo/tanstack-query'
+ | '/asignaturas/nueva'
| '/planes/nuevo'
fileRoutesByTo: FileRoutesByTo
to:
@@ -118,9 +149,12 @@ export interface FileRouteTypes {
| '/login'
| '/stepper'
| '/stepper2'
+ | '/asignaturas/$asignaturaId'
+ | '/asignaturas'
| '/planes/$planId'
| '/planes'
| '/demo/tanstack-query'
+ | '/asignaturas/nueva'
| '/planes/nuevo'
id:
| '__root__'
@@ -129,9 +163,12 @@ export interface FileRouteTypes {
| '/login'
| '/stepper'
| '/stepper2'
+ | '/asignaturas/$asignaturaId'
+ | '/asignaturas/_lista'
| '/planes/$planId'
| '/planes/_lista'
| '/demo/tanstack-query'
+ | '/asignaturas/_lista/nueva'
| '/planes/_lista/nuevo'
fileRoutesById: FileRoutesById
}
@@ -141,6 +178,8 @@ export interface RootRouteChildren {
LoginRoute: typeof LoginRoute
StepperRoute: typeof StepperRoute
Stepper2Route: typeof Stepper2Route
+ AsignaturasAsignaturaIdRouteRoute: typeof AsignaturasAsignaturaIdRouteRoute
+ AsignaturasListaRouteRoute: typeof AsignaturasListaRouteRouteWithChildren
PlanesPlanIdRouteRoute: typeof PlanesPlanIdRouteRoute
PlanesListaRouteRoute: typeof PlanesListaRouteRouteWithChildren
DemoTanstackQueryRoute: typeof DemoTanstackQueryRoute
@@ -204,6 +243,20 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof PlanesPlanIdRouteRouteImport
parentRoute: typeof rootRouteImport
}
+ '/asignaturas/_lista': {
+ id: '/asignaturas/_lista'
+ path: '/asignaturas'
+ fullPath: '/asignaturas'
+ preLoaderRoute: typeof AsignaturasListaRouteRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/asignaturas/$asignaturaId': {
+ id: '/asignaturas/$asignaturaId'
+ path: '/asignaturas/$asignaturaId'
+ fullPath: '/asignaturas/$asignaturaId'
+ preLoaderRoute: typeof AsignaturasAsignaturaIdRouteRouteImport
+ parentRoute: typeof rootRouteImport
+ }
'/planes/_lista/nuevo': {
id: '/planes/_lista/nuevo'
path: '/nuevo'
@@ -211,9 +264,29 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof PlanesListaNuevoRouteImport
parentRoute: typeof PlanesListaRouteRoute
}
+ '/asignaturas/_lista/nueva': {
+ id: '/asignaturas/_lista/nueva'
+ path: '/nueva'
+ fullPath: '/asignaturas/nueva'
+ preLoaderRoute: typeof AsignaturasListaNuevaRouteImport
+ parentRoute: typeof AsignaturasListaRouteRoute
+ }
}
}
+interface AsignaturasListaRouteRouteChildren {
+ AsignaturasListaNuevaRoute: typeof AsignaturasListaNuevaRoute
+}
+
+const AsignaturasListaRouteRouteChildren: AsignaturasListaRouteRouteChildren = {
+ AsignaturasListaNuevaRoute: AsignaturasListaNuevaRoute,
+}
+
+const AsignaturasListaRouteRouteWithChildren =
+ AsignaturasListaRouteRoute._addFileChildren(
+ AsignaturasListaRouteRouteChildren,
+ )
+
interface PlanesListaRouteRouteChildren {
PlanesListaNuevoRoute: typeof PlanesListaNuevoRoute
}
@@ -231,6 +304,8 @@ const rootRouteChildren: RootRouteChildren = {
LoginRoute: LoginRoute,
StepperRoute: StepperRoute,
Stepper2Route: Stepper2Route,
+ AsignaturasAsignaturaIdRouteRoute: AsignaturasAsignaturaIdRouteRoute,
+ AsignaturasListaRouteRoute: AsignaturasListaRouteRouteWithChildren,
PlanesPlanIdRouteRoute: PlanesPlanIdRouteRoute,
PlanesListaRouteRoute: PlanesListaRouteRouteWithChildren,
DemoTanstackQueryRoute: DemoTanstackQueryRoute,
diff --git a/src/routes/asignaturas/$asignaturaId/route.tsx b/src/routes/asignaturas/$asignaturaId/route.tsx
new file mode 100644
index 0000000..e314491
--- /dev/null
+++ b/src/routes/asignaturas/$asignaturaId/route.tsx
@@ -0,0 +1,9 @@
+import { createFileRoute } from '@tanstack/react-router'
+
+export const Route = createFileRoute('/asignaturas/$asignaturaId')({
+ component: RouteComponent,
+})
+
+function RouteComponent() {
+ return
Hello "/asignaturas/$asignaturaId"!
+}
diff --git a/src/routes/asignaturas/_lista/nueva.tsx b/src/routes/asignaturas/_lista/nueva.tsx
new file mode 100644
index 0000000..f0dc9bd
--- /dev/null
+++ b/src/routes/asignaturas/_lista/nueva.tsx
@@ -0,0 +1,1083 @@
+import { createFileRoute, useNavigate } from '@tanstack/react-router'
+import * as Icons from 'lucide-react'
+import { useState } from 'react'
+
+import { CircularProgress } from '@/components/CircularProgress'
+import { defineStepper } from '@/components/stepper'
+import { Button } from '@/components/ui/button'
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardHeader,
+ CardTitle,
+} from '@/components/ui/card'
+import {
+ Dialog,
+ DialogContent,
+ DialogHeader,
+ DialogTitle,
+} from '@/components/ui/dialog'
+import { Input } from '@/components/ui/input'
+import { Label } from '@/components/ui/label'
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from '@/components/ui/select'
+import { Textarea } from '@/components/ui/textarea'
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipProvider,
+ TooltipTrigger,
+} from '@/components/ui/tooltip'
+
+export const Route = createFileRoute('/asignaturas/_lista/nueva')({
+ component: NuevaMateriaModal,
+})
+
+// --- TIPOS Y ESTADO ---
+
+type ModoCreacion = 'MANUAL' | 'IA' | 'CLONADO'
+type SubModoClonado = 'INTERNO' | 'TRADICIONAL'
+type TipoMateria = 'OBLIGATORIA' | 'OPTATIVA' | 'TRONCAL' | 'OTRO'
+
+type MateriaPreview = {
+ nombre: string
+ objetivo: string
+ unidades: number
+ bibliografiaCount: number
+}
+
+type NewSubjectWizardState = {
+ step: 1 | 2 | 3 | 4
+ // planId: string
+ modoCreacion: ModoCreacion | null
+ subModoClonado?: SubModoClonado
+ datosBasicos: {
+ nombre: string
+ clave?: string
+ tipo: TipoMateria
+ creditos: number
+ horasSemana?: number
+ estructuraId: string
+ }
+ clonInterno?: {
+ facultadId?: string
+ carreraId?: string
+ planOrigenId?: string
+ materiaOrigenId?: string | null
+ }
+ clonTradicional?: {
+ archivoWordMateriaId: string | null
+ archivosAdicionalesIds: Array
+ }
+ iaConfig?: {
+ descripcionEnfoque: string
+ notasAdicionales: string
+ archivosExistentesIds: Array
+ }
+ resumen: {
+ previewMateria?: MateriaPreview
+ }
+ isLoading: boolean
+ errorMessage: string | null
+}
+
+// --- MOCKS (Hardcoded Data) ---
+
+const auth_get_current_user_role = () => 'JEFE_CARRERA' as const
+
+const ESTRUCTURAS_SEP = [
+ { id: 'sep-lic-2025', label: 'Licenciatura SEP v2025' },
+ { id: 'sep-pos-2023', label: 'Posgrado SEP v2023' },
+ { id: 'ulsa-int-2024', label: 'Estándar Interno ULSA 2024' },
+]
+
+const TIPOS_MATERIA: Array<{ value: TipoMateria; label: string }> = [
+ { value: 'OBLIGATORIA', label: 'Obligatoria' },
+ { value: 'OPTATIVA', label: 'Optativa' },
+ { value: 'TRONCAL', label: 'Troncal / Eje común' },
+ { value: 'OTRO', label: 'Otro' },
+]
+
+const FACULTADES = [
+ { id: 'ing', nombre: 'Facultad de Ingeniería' },
+ { id: 'med', nombre: 'Facultad de Medicina' },
+ { id: 'neg', nombre: 'Facultad de Negocios' },
+]
+
+const CARRERAS = [
+ { id: 'sis', nombre: 'Ing. en Sistemas', facultadId: 'ing' },
+ { id: 'ind', nombre: 'Ing. Industrial', facultadId: 'ing' },
+ { id: 'medico', nombre: 'Médico Cirujano', facultadId: 'med' },
+ { id: 'act', nombre: 'Actuaría', facultadId: 'neg' },
+]
+
+const PLANES_MOCK = [
+ { id: 'p1', nombre: 'Plan 2010 Sistemas', carreraId: 'sis' },
+ { id: 'p2', nombre: 'Plan 2016 Sistemas', carreraId: 'sis' },
+ { id: 'p3', nombre: 'Plan 2015 Industrial', carreraId: 'ind' },
+]
+
+const MATERIAS_MOCK = [
+ {
+ id: 'm1',
+ nombre: 'Programación Orientada a Objetos',
+ creditos: 8,
+ clave: 'POO-101',
+ },
+ { id: 'm2', nombre: 'Cálculo Diferencial', creditos: 6, clave: 'MAT-101' },
+ { id: 'm3', nombre: 'Ética Profesional', creditos: 4, clave: 'HUM-302' },
+ {
+ id: 'm4',
+ nombre: 'Bases de Datos Avanzadas',
+ creditos: 8,
+ clave: 'BD-201',
+ },
+]
+
+const ARCHIVOS_SISTEMA_MOCK = [
+ { id: 'doc1', name: 'Sílabo_Base_Ingenieria.pdf' },
+ { id: 'doc2', name: 'Competencias_Egreso_2025.docx' },
+ { id: 'doc3', name: 'Reglamento_Academico.pdf' },
+]
+
+// --- STEPPER CONFIG ---
+
+const Wizard = defineStepper(
+ {
+ id: 'metodo',
+ title: 'Método',
+ description: 'Manual, IA o Clonado',
+ },
+ {
+ id: 'basicos',
+ title: 'Datos básicos',
+ description: 'Nombre y estructura',
+ },
+ {
+ id: 'configuracion',
+ title: 'Configuración',
+ description: 'Detalles según modo',
+ },
+ {
+ id: 'resumen',
+ title: 'Resumen',
+ description: 'Confirmar creación',
+ },
+)
+
+// --- COMPONENTE PRINCIPAL ---
+
+function NuevaMateriaModal() {
+ const navigate = useNavigate()
+ // const { planId } = Route.useParams()
+ const role = auth_get_current_user_role()
+
+ const [wizard, setWizard] = useState({
+ step: 1,
+ // planId: planId,
+ modoCreacion: null,
+ datosBasicos: {
+ nombre: '',
+ clave: '',
+ tipo: 'OBLIGATORIA',
+ creditos: 0,
+ horasSemana: 0,
+ estructuraId: '',
+ },
+ clonInterno: {},
+ clonTradicional: {
+ archivoWordMateriaId: null,
+ archivosAdicionalesIds: [],
+ },
+ iaConfig: {
+ descripcionEnfoque: '',
+ notasAdicionales: '',
+ archivosExistentesIds: [],
+ },
+ resumen: {},
+ isLoading: false,
+ errorMessage: null,
+ })
+
+ const handleClose = () => {
+ // Redirige a la pestaña de materias del plan
+ navigate({ to: `/planes`, resetScroll: false })
+ }
+
+ // --- Validaciones ---
+ const canContinueDesdeMetodo =
+ wizard.modoCreacion === 'MANUAL' ||
+ wizard.modoCreacion === 'IA' ||
+ (wizard.modoCreacion === 'CLONADO' && !!wizard.subModoClonado)
+
+ const canContinueDesdeBasicos =
+ !!wizard.datosBasicos.nombre &&
+ wizard.datosBasicos.creditos > 0 &&
+ !!wizard.datosBasicos.estructuraId
+
+ const canContinueDesdeConfig = (() => {
+ if (wizard.modoCreacion === 'MANUAL') return true
+ if (wizard.modoCreacion === 'IA') {
+ return !!wizard.iaConfig?.descripcionEnfoque
+ }
+ if (wizard.modoCreacion === 'CLONADO') {
+ if (wizard.subModoClonado === 'INTERNO') {
+ return !!wizard.clonInterno?.materiaOrigenId
+ }
+ if (wizard.subModoClonado === 'TRADICIONAL') {
+ return !!wizard.clonTradicional?.archivoWordMateriaId
+ }
+ }
+ return false
+ })()
+
+ // --- Simulaciones de API ---
+ const simularGeneracionIA = async () => {
+ setWizard((w) => ({ ...w, isLoading: true }))
+ await new Promise((r) => setTimeout(r, 1500))
+ setWizard((w) => ({
+ ...w,
+ isLoading: false,
+ resumen: {
+ previewMateria: {
+ nombre: w.datosBasicos.nombre,
+ objetivo:
+ 'Aplicar los fundamentos teóricos para la resolución de problemas...',
+ unidades: 5,
+ bibliografiaCount: 3,
+ },
+ },
+ }))
+ }
+
+ const crearMateria = async () => {
+ setWizard((w) => ({ ...w, isLoading: true }))
+ await new Promise((r) => setTimeout(r, 1000))
+ // Aquí iría la llamada real al backend
+ // Toast de éxito...
+ handleClose()
+ }
+
+ return (
+
+ )
+}
+
+// --- SUB-COMPONENTES DE PASOS ---
+
+function PasoMetodo({
+ wizard,
+ onChange,
+}: {
+ wizard: NewSubjectWizardState
+ onChange: React.Dispatch>
+}) {
+ const isSelected = (m: ModoCreacion) => wizard.modoCreacion === m
+ const isSubSelected = (s: SubModoClonado) => wizard.subModoClonado === s
+
+ return (
+
+ {/* 1. MANUAL */}
+
+ onChange((w) => ({
+ ...w,
+ modoCreacion: 'MANUAL',
+ subModoClonado: undefined,
+ }))
+ }
+ role="button"
+ tabIndex={0}
+ >
+
+
+ Manual
+
+ Materia vacía con estructura base.
+
+
+
+ {/* 2. CON IA */}
+
+ onChange((w) => ({
+ ...w,
+ modoCreacion: 'IA',
+ subModoClonado: undefined,
+ }))
+ }
+ role="button"
+ tabIndex={0}
+ >
+
+
+ Con IA
+
+ Generar contenido automático.
+
+
+
+ {/* 3. CLONADO */}
+
onChange((w) => ({ ...w, modoCreacion: 'CLONADO' }))}
+ role="button"
+ tabIndex={0}
+ >
+
+
+ Clonado
+
+ De otra materia o archivo Word.
+
+ {wizard.modoCreacion === 'CLONADO' && (
+
+
+ {/* Opción Interna */}
+
{
+ e.stopPropagation()
+ onChange((w) => ({ ...w, subModoClonado: 'INTERNO' }))
+ }}
+ className={`hover:border-primary/50 hover:bg-accent flex cursor-pointer items-center gap-4 rounded-lg border p-4 text-left transition-all ${
+ isSubSelected('INTERNO')
+ ? 'bg-primary/5 text-primary ring-primary border-primary ring-1'
+ : 'border-border text-muted-foreground'
+ }`}
+ >
+
+
+ Del sistema
+
+ Buscar en otros planes
+
+
+
+
+ {/* Opción Tradicional */}
+
{
+ e.stopPropagation()
+ onChange((w) => ({ ...w, subModoClonado: 'TRADICIONAL' }))
+ }}
+ className={`hover:border-primary/50 hover:bg-accent flex cursor-pointer items-center gap-4 rounded-lg border p-4 text-left transition-all ${
+ isSubSelected('TRADICIONAL')
+ ? 'bg-primary/5 text-primary ring-primary border-primary ring-1'
+ : 'border-border text-muted-foreground'
+ }`}
+ >
+
+
+ Desde archivos
+
+ Subir Word existente
+
+
+
+
+
+ )}
+
+
+ )
+}
+
+function PasoBasicos({
+ wizard,
+ onChange,
+}: {
+ wizard: NewSubjectWizardState
+ onChange: React.Dispatch>
+}) {
+ return (
+
+
+
+
+ onChange((w) => ({
+ ...w,
+ datosBasicos: { ...w.datosBasicos, nombre: e.target.value },
+ }))
+ }
+ />
+
+
+
+
+
+ onChange((w) => ({
+ ...w,
+ datosBasicos: { ...w.datosBasicos, clave: e.target.value },
+ }))
+ }
+ />
+
+
+
+
+
+
+
+
+
+
+ onChange((w) => ({
+ ...w,
+ datosBasicos: {
+ ...w.datosBasicos,
+ creditos: Number(e.target.value || 0),
+ },
+ }))
+ }
+ />
+
+
+
+
+
+ onChange((w) => ({
+ ...w,
+ datosBasicos: {
+ ...w.datosBasicos,
+ horasSemana: Number(e.target.value || 0),
+ },
+ }))
+ }
+ />
+
+
+
+
+
+
+ Define los campos requeridos (ej. Objetivos, Temario, Evaluación).
+
+
+
+ )
+}
+
+function PasoConfiguracion({
+ wizard,
+ onChange,
+ onGenerarIA,
+}: {
+ wizard: NewSubjectWizardState
+ onChange: React.Dispatch>
+ onGenerarIA: () => void
+}) {
+ if (wizard.modoCreacion === 'MANUAL') {
+ return (
+
+
+ Configuración Manual
+
+ La materia se creará vacía. Podrás editar el contenido detallado en
+ la siguiente pantalla.
+
+
+
+ )
+ }
+
+ if (wizard.modoCreacion === 'IA') {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ {ARCHIVOS_SISTEMA_MOCK.map((file) => (
+
+ {
+ const checked = e.target.checked
+ onChange((w) => ({
+ ...w,
+ iaConfig: {
+ ...w.iaConfig!,
+ archivosExistentesIds: checked
+ ? [
+ ...(w.iaConfig?.archivosExistentesIds || []),
+ file.id,
+ ]
+ : w.iaConfig?.archivosExistentesIds.filter(
+ (id) => id !== file.id,
+ ) || [],
+ },
+ }))
+ }}
+ />
+
+
+ ))}
+
+
+
+
+
+
+
+ {wizard.resumen.previewMateria && (
+
+
+ Vista previa generada
+
+
+
+ Objetivo:{' '}
+ {wizard.resumen.previewMateria.objetivo}
+
+
+ Se detectaron {wizard.resumen.previewMateria.unidades} unidades
+ temáticas y {wizard.resumen.previewMateria.bibliografiaCount}{' '}
+ fuentes bibliográficas.
+
+
+
+ )}
+
+ )
+ }
+
+ if (wizard.subModoClonado === 'INTERNO') {
+ return (
+
+ {/* Filtros */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* Lista de materias */}
+
+ {MATERIAS_MOCK.map((m) => (
+
+ onChange((w) => ({
+ ...w,
+ clonInterno: { ...w.clonInterno, materiaOrigenId: m.id },
+ }))
+ }
+ className={`hover:bg-accent flex cursor-pointer items-center justify-between rounded-md border p-3 ${
+ wizard.clonInterno?.materiaOrigenId === m.id
+ ? 'border-primary bg-primary/5 ring-primary ring-1'
+ : ''
+ }`}
+ >
+
+
{m.nombre}
+
+ {m.clave} • {m.creditos} créditos
+
+
+ {wizard.clonInterno?.materiaOrigenId === m.id && (
+
+ )}
+
+ ))}
+
+
+ )
+ }
+
+ if (wizard.subModoClonado === 'TRADICIONAL') {
+ return (
+
+
+
+
+ Sube el Word de la materia
+
+
+ Arrastra el archivo o haz clic para buscar (.doc, .docx)
+
+
+ onChange((w) => ({
+ ...w,
+ clonTradicional: {
+ ...w.clonTradicional!,
+ archivoWordMateriaId:
+ e.target.files?.[0]?.name || 'mock_file',
+ },
+ }))
+ }
+ />
+
+ {wizard.clonTradicional?.archivoWordMateriaId && (
+
+
+ Archivo cargado listo para procesar.
+
+ )}
+
+ )
+ }
+
+ return null
+}
+
+function PasoResumen({ wizard }: { wizard: NewSubjectWizardState }) {
+ return (
+
+
+ Resumen de creación
+
+ Verifica los datos antes de crear la materia.
+
+
+
+
+
+
Nombre:
+
{wizard.datosBasicos.nombre}
+
+
+
Tipo:
+
{wizard.datosBasicos.tipo}
+
+
+
Créditos:
+
{wizard.datosBasicos.creditos}
+
+
+
Estructura:
+
+ {
+ ESTRUCTURAS_SEP.find(
+ (e) => e.id === wizard.datosBasicos.estructuraId,
+ )?.label
+ }
+
+
+
+
+
+
Modo de creación:
+
+ {wizard.modoCreacion === 'MANUAL' && (
+ <>
+ Manual (Vacía)
+ >
+ )}
+ {wizard.modoCreacion === 'IA' && (
+ <>
+ Generada con IA
+ >
+ )}
+ {wizard.modoCreacion === 'CLONADO' && (
+ <>
+ Clonada
+ {wizard.subModoClonado === 'INTERNO'
+ ? ' (Sistema)'
+ : ' (Archivo)'}
+ >
+ )}
+
+
+
+
+ )
+}
+
+// --- UTILS COMPONENTES ---
+
+function StepWithTooltip({ title, desc }: { title: string; desc: string }) {
+ const [isOpen, setIsOpen] = useState(false)
+ return (
+
+
+
+ {
+ e.stopPropagation()
+ setIsOpen((prev) => !prev)
+ }}
+ onMouseEnter={() => setIsOpen(true)}
+ onMouseLeave={() => setIsOpen(false)}
+ >
+ {title}
+
+
+
+ {desc}
+
+
+
+ )
+}
+
+function VistaSinPermisos({ onClose }: { onClose: () => void }) {
+ return (
+ <>
+
+ Nueva Materia
+
+
+
+
+
+
+ Sin permisos
+
+
+ Solo el Jefe de Carrera puede crear materias.
+
+
+
+
+
+
+
+ >
+ )
+}
diff --git a/src/routes/asignaturas/_lista/route.tsx b/src/routes/asignaturas/_lista/route.tsx
new file mode 100644
index 0000000..dda38c1
--- /dev/null
+++ b/src/routes/asignaturas/_lista/route.tsx
@@ -0,0 +1,15 @@
+import { createFileRoute, Outlet } from '@tanstack/react-router'
+
+export const Route = createFileRoute('/asignaturas/_lista')({
+ component: RouteComponent,
+})
+
+function RouteComponent() {
+ return (
+
+
+
+
+
+ )
+}
diff --git a/src/routes/planes/_lista/nuevo.tsx b/src/routes/planes/_lista/nuevo.tsx
index 9e5abc3..571e3a7 100644
--- a/src/routes/planes/_lista/nuevo.tsx
+++ b/src/routes/planes/_lista/nuevo.tsx
@@ -692,7 +692,7 @@ function PasoBasicos({
))}
*/}
-
+
- ) =>
+ onValueChange={(value) =>
onChange((w) => ({
...w,
- datosBasicos: { ...w.datosBasicos, nivel: e.target.value },
+ datosBasicos: { ...w.datosBasicos, nivel: value },
}))
}
>
-
- {NIVELES.map((n) => (
-
- ))}
-
+
+
+
+
+ {NIVELES.map((n) => (
+
+ {n}
+
+ ))}
+
+
- ) =>
+ onValueChange={(value) =>
onChange((w) => ({
...w,
datosBasicos: {
...w.datosBasicos,
- tipoCiclo: e.target.value as TipoCiclo,
+ tipoCiclo: value as TipoCiclo,
},
}))
}
>
- {TIPOS_CICLO.map((t) => (
-
- ))}
-
+
+
+
+
+ {TIPOS_CICLO.map((t) => (
+
+ {t.label}
+
+ ))}
+
+
@@ -803,7 +820,7 @@ function PasoBasicos({
...w,
datosBasicos: {
...w.datosBasicos,
- numCiclos: Number(e.target.value || 0),
+ numCiclos: Number(e.target.value || 1),
},
}))
}