diff --git a/src/components/asignaturas/wizard/PasoBasicosForm.tsx b/src/components/asignaturas/wizard/PasoBasicosForm.tsx deleted file mode 100644 index 6c6c0d3..0000000 --- a/src/components/asignaturas/wizard/PasoBasicosForm.tsx +++ /dev/null @@ -1,356 +0,0 @@ -import { useEffect, useState } from 'react' - -import type { NewSubjectWizardState } from '@/features/asignaturas/nueva/types' -import type { Database } from '@/types/supabase' - -import { Input } from '@/components/ui/input' -import { Label } from '@/components/ui/label' -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from '@/components/ui/select' -import { useSubjectEstructuras } from '@/data' -import { TIPOS_MATERIA } from '@/features/asignaturas/nueva/catalogs' -import { cn } from '@/lib/utils' - -export function PasoBasicosForm({ - wizard, - onChange, -}: { - wizard: NewSubjectWizardState - onChange: React.Dispatch> -}) { - const { data: estructuras } = useSubjectEstructuras() - - const [creditosInput, setCreditosInput] = useState(() => { - const c = Number(wizard.datosBasicos.creditos ?? 0) - let newC = c - console.log('antes', newC) - - if (Number.isFinite(c) && c > 999) { - newC = 999 - } - console.log('desp', newC) - return newC > 0 ? newC.toFixed(2) : '' - }) - const [creditosFocused, setCreditosFocused] = useState(false) - - useEffect(() => { - if (creditosFocused) return - const c = Number(wizard.datosBasicos.creditos ?? 0) - let newC = c - if (Number.isFinite(c) && c > 999) { - newC = 999 - } - setCreditosInput(newC > 0 ? newC.toFixed(2) : '') - }, [wizard.datosBasicos.creditos, creditosFocused]) - - return ( -
-
- - - onChange( - (w): NewSubjectWizardState => ({ - ...w, - datosBasicos: { ...w.datosBasicos, nombre: e.target.value }, - }), - ) - } - className="placeholder:text-muted-foreground/70 font-medium not-italic placeholder:font-normal placeholder:italic" - /> -
- -
- - - onChange( - (w): NewSubjectWizardState => ({ - ...w, - datosBasicos: { ...w.datosBasicos, codigo: e.target.value }, - }), - ) - } - className="placeholder:text-muted-foreground/70 placeholder:italicplaceholder:text-muted-foreground/70 font-medium not-italic placeholder:font-normal placeholder:italic" - /> -
- -
- - -
- -
- - { - if (['-', 'e', 'E', '+'].includes(e.key)) { - e.preventDefault() - } - }} - onFocus={() => setCreditosFocused(true)} - onBlur={() => { - setCreditosFocused(false) - - const raw = creditosInput.trim() - if (!raw) { - onChange((w) => ({ - ...w, - datosBasicos: { ...w.datosBasicos, creditos: 0 }, - })) - return - } - - const normalized = raw.replace(',', '.') - let asNumber = Number.parseFloat(normalized) - if (!Number.isFinite(asNumber) || asNumber <= 0) { - setCreditosInput('') - onChange((w) => ({ - ...w, - datosBasicos: { ...w.datosBasicos, creditos: 0 }, - })) - return - } - - // Cap to 999 - if (asNumber > 999) asNumber = 999 - - const fixed = asNumber.toFixed(2) - setCreditosInput(fixed) - onChange((w) => ({ - ...w, - datosBasicos: { ...w.datosBasicos, creditos: Number(fixed) }, - })) - }} - onChange={(e: React.ChangeEvent) => { - const nextRaw = e.target.value - if (nextRaw === '') { - setCreditosInput('') - onChange((w) => ({ - ...w, - datosBasicos: { ...w.datosBasicos, creditos: 0 }, - })) - return - } - - if (!/^\d*(?:[.,]\d{0,2})?$/.test(nextRaw)) return - - // If typed number exceeds 999, cap it immediately (prevents entering >999) - const asNumberRaw = Number.parseFloat(nextRaw.replace(',', '.')) - if (Number.isFinite(asNumberRaw) && asNumberRaw > 999) { - // show capped value to the user - const cappedStr = '999.00' - setCreditosInput(cappedStr) - onChange((w) => ({ - ...w, - datosBasicos: { - ...w.datosBasicos, - creditos: 999, - }, - })) - return - } - - setCreditosInput(nextRaw) - - const asNumber = Number.parseFloat(nextRaw.replace(',', '.')) - onChange((w) => ({ - ...w, - datosBasicos: { - ...w.datosBasicos, - creditos: - Number.isFinite(asNumber) && asNumber > 0 ? asNumber : 0, - }, - })) - }} - className="placeholder:text-muted-foreground/70 font-medium not-italic placeholder:font-normal placeholder:italic" - placeholder="Ej. 4.50" - /> -
- -
- - -

- Define los campos requeridos (ej. Objetivos, Temario, Evaluación). -

-
- -
- - { - if (['.', ',', '-', 'e', 'E', '+'].includes(e.key)) { - e.preventDefault() - } - }} - onChange={(e: React.ChangeEvent) => - onChange( - (w): NewSubjectWizardState => ({ - ...w, - datosBasicos: { - ...w.datosBasicos, - horasAcademicas: (() => { - const raw = e.target.value - if (raw === '') return null - const asNumber = Number(raw) - if (Number.isNaN(asNumber)) return null - // Coerce to positive integer (natural numbers without zero) - const n = Math.floor(Math.abs(asNumber)) - const capped = Math.min(n >= 1 ? n : 1, 999) - return capped - })(), - }, - }), - ) - } - className="placeholder:text-muted-foreground/70 font-medium not-italic placeholder:font-normal placeholder:italic" - placeholder="Ej. 48" - /> -
- -
- - { - if (['.', ',', '-', 'e', 'E', '+'].includes(e.key)) { - e.preventDefault() - } - }} - onChange={(e: React.ChangeEvent) => - onChange( - (w): NewSubjectWizardState => ({ - ...w, - datosBasicos: { - ...w.datosBasicos, - horasIndependientes: (() => { - const raw = e.target.value - if (raw === '') return null - const asNumber = Number(raw) - if (Number.isNaN(asNumber)) return null - // Coerce to positive integer (natural numbers without zero) - const n = Math.floor(Math.abs(asNumber)) - const capped = Math.min(n >= 1 ? n : 1, 999) - return capped - })(), - }, - }), - ) - } - className="placeholder:text-muted-foreground/70 font-medium not-italic placeholder:font-normal placeholder:italic" - placeholder="Ej. 24" - /> -
-
- ) -} diff --git a/src/components/asignaturas/wizard/PasoBasicosForm/PasoBasicosForm.tsx b/src/components/asignaturas/wizard/PasoBasicosForm/PasoBasicosForm.tsx new file mode 100644 index 0000000..cba5dfd --- /dev/null +++ b/src/components/asignaturas/wizard/PasoBasicosForm/PasoBasicosForm.tsx @@ -0,0 +1,362 @@ +import { useEffect, useState } from 'react' + +import PasoSugerenciasForm from './PasoSugerenciasForm' + +import type { NewSubjectWizardState } from '@/features/asignaturas/nueva/types' +import type { Database } from '@/types/supabase' + +import { Input } from '@/components/ui/input' +import { Label } from '@/components/ui/label' +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select' +import { useSubjectEstructuras } from '@/data' +import { TIPOS_MATERIA } from '@/features/asignaturas/nueva/catalogs' +import { cn } from '@/lib/utils' + +export function PasoBasicosForm({ + wizard, + onChange, +}: { + wizard: NewSubjectWizardState + onChange: React.Dispatch> +}) { + const { data: estructuras } = useSubjectEstructuras() + + const [creditosInput, setCreditosInput] = useState(() => { + const c = Number(wizard.datosBasicos.creditos ?? 0) + let newC = c + console.log('antes', newC) + + if (Number.isFinite(c) && c > 999) { + newC = 999 + } + console.log('desp', newC) + return newC > 0 ? newC.toFixed(2) : '' + }) + const [creditosFocused, setCreditosFocused] = useState(false) + + useEffect(() => { + if (creditosFocused) return + const c = Number(wizard.datosBasicos.creditos ?? 0) + let newC = c + if (Number.isFinite(c) && c > 999) { + newC = 999 + } + setCreditosInput(newC > 0 ? newC.toFixed(2) : '') + }, [wizard.datosBasicos.creditos, creditosFocused]) + + if (wizard.tipoOrigen !== 'IA_MULTIPLE') { + return ( +
+
+ + + onChange( + (w): NewSubjectWizardState => ({ + ...w, + datosBasicos: { ...w.datosBasicos, nombre: e.target.value }, + }), + ) + } + className="placeholder:text-muted-foreground/70 font-medium not-italic placeholder:font-normal placeholder:italic" + /> +
+ +
+ + + onChange( + (w): NewSubjectWizardState => ({ + ...w, + datosBasicos: { ...w.datosBasicos, codigo: e.target.value }, + }), + ) + } + className="placeholder:text-muted-foreground/70 placeholder:italicplaceholder:text-muted-foreground/70 font-medium not-italic placeholder:font-normal placeholder:italic" + /> +
+ +
+ + +
+ +
+ + { + if (['-', 'e', 'E', '+'].includes(e.key)) { + e.preventDefault() + } + }} + onFocus={() => setCreditosFocused(true)} + onBlur={() => { + setCreditosFocused(false) + + const raw = creditosInput.trim() + if (!raw) { + onChange((w) => ({ + ...w, + datosBasicos: { ...w.datosBasicos, creditos: 0 }, + })) + return + } + + const normalized = raw.replace(',', '.') + let asNumber = Number.parseFloat(normalized) + if (!Number.isFinite(asNumber) || asNumber <= 0) { + setCreditosInput('') + onChange((w) => ({ + ...w, + datosBasicos: { ...w.datosBasicos, creditos: 0 }, + })) + return + } + + // Cap to 999 + if (asNumber > 999) asNumber = 999 + + const fixed = asNumber.toFixed(2) + setCreditosInput(fixed) + onChange((w) => ({ + ...w, + datosBasicos: { ...w.datosBasicos, creditos: Number(fixed) }, + })) + }} + onChange={(e: React.ChangeEvent) => { + const nextRaw = e.target.value + if (nextRaw === '') { + setCreditosInput('') + onChange((w) => ({ + ...w, + datosBasicos: { ...w.datosBasicos, creditos: 0 }, + })) + return + } + + if (!/^\d*(?:[.,]\d{0,2})?$/.test(nextRaw)) return + + // If typed number exceeds 999, cap it immediately (prevents entering >999) + const asNumberRaw = Number.parseFloat(nextRaw.replace(',', '.')) + if (Number.isFinite(asNumberRaw) && asNumberRaw > 999) { + // show capped value to the user + const cappedStr = '999.00' + setCreditosInput(cappedStr) + onChange((w) => ({ + ...w, + datosBasicos: { + ...w.datosBasicos, + creditos: 999, + }, + })) + return + } + + setCreditosInput(nextRaw) + + const asNumber = Number.parseFloat(nextRaw.replace(',', '.')) + onChange((w) => ({ + ...w, + datosBasicos: { + ...w.datosBasicos, + creditos: + Number.isFinite(asNumber) && asNumber > 0 ? asNumber : 0, + }, + })) + }} + className="placeholder:text-muted-foreground/70 font-medium not-italic placeholder:font-normal placeholder:italic" + placeholder="Ej. 4.50" + /> +
+ +
+ + +

+ Define los campos requeridos (ej. Objetivos, Temario, Evaluación). +

+
+ +
+ + { + if (['.', ',', '-', 'e', 'E', '+'].includes(e.key)) { + e.preventDefault() + } + }} + onChange={(e: React.ChangeEvent) => + onChange( + (w): NewSubjectWizardState => ({ + ...w, + datosBasicos: { + ...w.datosBasicos, + horasAcademicas: (() => { + const raw = e.target.value + if (raw === '') return null + const asNumber = Number(raw) + if (Number.isNaN(asNumber)) return null + // Coerce to positive integer (natural numbers without zero) + const n = Math.floor(Math.abs(asNumber)) + const capped = Math.min(n >= 1 ? n : 1, 999) + return capped + })(), + }, + }), + ) + } + className="placeholder:text-muted-foreground/70 font-medium not-italic placeholder:font-normal placeholder:italic" + placeholder="Ej. 48" + /> +
+ +
+ + { + if (['.', ',', '-', 'e', 'E', '+'].includes(e.key)) { + e.preventDefault() + } + }} + onChange={(e: React.ChangeEvent) => + onChange( + (w): NewSubjectWizardState => ({ + ...w, + datosBasicos: { + ...w.datosBasicos, + horasIndependientes: (() => { + const raw = e.target.value + if (raw === '') return null + const asNumber = Number(raw) + if (Number.isNaN(asNumber)) return null + // Coerce to positive integer (natural numbers without zero) + const n = Math.floor(Math.abs(asNumber)) + const capped = Math.min(n >= 1 ? n : 1, 999) + return capped + })(), + }, + }), + ) + } + className="placeholder:text-muted-foreground/70 font-medium not-italic placeholder:font-normal placeholder:italic" + placeholder="Ej. 24" + /> +
+
+ ) + } + + return +} diff --git a/src/components/asignaturas/wizard/PasoBasicosForm/PasoSugerenciasForm.tsx b/src/components/asignaturas/wizard/PasoBasicosForm/PasoSugerenciasForm.tsx new file mode 100644 index 0000000..fcbc79f --- /dev/null +++ b/src/components/asignaturas/wizard/PasoBasicosForm/PasoSugerenciasForm.tsx @@ -0,0 +1,237 @@ +import { RefreshCw, Sparkles } from 'lucide-react' + +import type { NewSubjectWizardState } from '@/features/asignaturas/nueva/types' +import type { Dispatch, SetStateAction } from 'react' + +import { Button } from '@/components/ui/button' +import { Checkbox } from '@/components/ui/checkbox' +import { Input } from '@/components/ui/input' +import { Label } from '@/components/ui/label' +import { cn } from '@/lib/utils' + +// Interfaces +interface Suggestion { + id: string + nombre: string + tipo: 'Obligatoria' | 'Optativa' + creditos: number + horasAcademicas: number + horasIndependientes: number + descripcion: string +} + +// Datos Mock basados en tu imagen +const MOCK_SUGGESTIONS: Array = [ + { + id: '1', + nombre: 'Propiedad Intelectual en Entornos Digitales', + tipo: 'Optativa', + creditos: 4, + horasAcademicas: 32, + horasIndependientes: 16, + descripcion: + 'Derechos de autor, patentes de software y marcas en el ecosistema digital.', + }, + { + id: '2', + nombre: 'Derecho Constitucional Digital', + tipo: 'Obligatoria', + creditos: 8, + horasAcademicas: 64, + horasIndependientes: 32, + descripcion: + 'Marco constitucional aplicado al entorno digital y derechos fundamentales en línea.', + }, + { + id: '3', + nombre: 'Gobernanza de Internet', + tipo: 'Optativa', + creditos: 4, + horasAcademicas: 32, + horasIndependientes: 16, + descripcion: + 'Políticas públicas, regulación internacional y gobernanza del ecosistema digital.', + }, + { + id: '4', + nombre: 'Protección de Datos Personales', + tipo: 'Obligatoria', + creditos: 6, + horasAcademicas: 48, + horasIndependientes: 24, + descripcion: + 'Regulación y cumplimiento de leyes de protección de datos (GDPR, LFPDPPP).', + }, + { + id: '5', + nombre: 'Inteligencia Artificial y Ética Jurídica', + tipo: 'Optativa', + creditos: 4, + horasAcademicas: 32, + horasIndependientes: 16, + descripcion: + 'Implicaciones legales y éticas del uso de IA en la práctica jurídica.', + }, + { + id: '6', + nombre: 'Ciberseguridad y Derecho Penal', + tipo: 'Obligatoria', + creditos: 6, + horasAcademicas: 48, + horasIndependientes: 24, + descripcion: + 'Delitos informáticos, evidencia digital y marco penal en el ciberespacio.', + }, +] +export default function PasoSugerenciasForm({ + wizard, + onChange, +}: { + wizard: NewSubjectWizardState + onChange: Dispatch> +}) { + const selectedIds = wizard.iaMultiple?.selectedIds ?? [] + const ciclo = wizard.iaMultiple?.ciclo ?? '' + const enfoque = wizard.iaMultiple?.enfoque ?? '' + + const setIaMultiple = ( + patch: Partial>, + ) => + onChange((w) => ({ + ...w, + iaMultiple: { + ciclo: w.iaMultiple?.ciclo ?? '', + enfoque: w.iaMultiple?.enfoque ?? '', + selectedIds: w.iaMultiple?.selectedIds ?? [], + ...patch, + }, + })) + + const toggleAsignatura = (id: string, checked: boolean) => { + const prev = selectedIds + const next = checked ? [...prev, id] : prev.filter((x) => x !== id) + setIaMultiple({ selectedIds: next }) + } + + return ( +
+ {/* --- BLOQUE SUPERIOR: PARÁMETROS --- */} +
+
+ + + Parámetros de sugerencia + +
+ +
+ {/* Input Ciclo */} +
+ + setIaMultiple({ ciclo: e.target.value })} + /> +
+ + {/* Input Enfoque */} +
+ + setIaMultiple({ enfoque: e.target.value })} + /> +
+ + {/* Botón Refrescar */} + +
+
+ + {/* --- HEADER LISTA --- */} +
+
+

+ Asignaturas sugeridas +

+

+ Basadas en el plan "Licenciatura en Derecho Digital" +

+
+
+ {selectedIds.length} seleccionadas +
+
+ + {/* --- LISTA DE ASIGNATURAS (CON EL ESTILO PEDIDO) --- */} +
+ {MOCK_SUGGESTIONS.map((asignatura) => { + const isSelected = selectedIds.includes(asignatura.id) + + return ( + + ) + })} +
+
+ ) +} diff --git a/src/components/asignaturas/wizard/PasoDetallesPanel.tsx b/src/components/asignaturas/wizard/PasoDetallesPanel.tsx index 5ce9dd1..b8d9ecd 100644 --- a/src/components/asignaturas/wizard/PasoDetallesPanel.tsx +++ b/src/components/asignaturas/wizard/PasoDetallesPanel.tsx @@ -29,11 +29,9 @@ import { export function PasoDetallesPanel({ wizard, onChange, - onGenerarIA: _onGenerarIA, }: { wizard: NewSubjectWizardState onChange: React.Dispatch> - onGenerarIA: () => void }) { if (wizard.tipoOrigen === 'MANUAL') { return ( @@ -49,7 +47,7 @@ export function PasoDetallesPanel({ ) } - if (wizard.tipoOrigen === 'IA') { + if (wizard.tipoOrigen === 'IA_SIMPLE') { return (
@@ -148,6 +146,10 @@ export function PasoDetallesPanel({ ) } + if (wizard.tipoOrigen === 'IA_MULTIPLE') { + return
Hola
+ } + if (wizard.tipoOrigen === 'CLONADO_INTERNO') { return (
diff --git a/src/components/asignaturas/wizard/PasoMetodoCardGroup.tsx b/src/components/asignaturas/wizard/PasoMetodoCardGroup.tsx index 73db993..46b3d82 100644 --- a/src/components/asignaturas/wizard/PasoMetodoCardGroup.tsx +++ b/src/components/asignaturas/wizard/PasoMetodoCardGroup.tsx @@ -77,12 +77,93 @@ export function PasoMetodoCardGroup({ Generar contenido automático. + {(wizard.tipoOrigen === 'IA' || + wizard.tipoOrigen === 'IA_SIMPLE' || + wizard.tipoOrigen === 'IA_MULTIPLE') && ( + +
{ + e.stopPropagation() + onChange( + (w): NewSubjectWizardState => ({ + ...w, + tipoOrigen: 'IA_SIMPLE', + }), + ) + }} + onKeyDown={(e: React.KeyboardEvent) => + handleKeyActivate(e, () => + onChange( + (w): NewSubjectWizardState => ({ + ...w, + tipoOrigen: 'IA_SIMPLE', + }), + ), + ) + } + className={`hover:border-primary/50 hover:bg-accent flex cursor-pointer items-center gap-4 rounded-lg border p-4 text-left transition-all ${ + isSelected('IA_SIMPLE') + ? 'bg-primary/5 text-primary ring-primary border-primary ring-1' + : 'border-border text-muted-foreground' + }`} + > + +
+ Una asignatura + + Crear una asignatura con control detallado de metadatos. + +
+
+ +
{ + e.stopPropagation() + onChange( + (w): NewSubjectWizardState => ({ + ...w, + tipoOrigen: 'IA_MULTIPLE', + }), + ) + }} + onKeyDown={(e: React.KeyboardEvent) => + handleKeyActivate(e, () => + onChange( + (w): NewSubjectWizardState => ({ + ...w, + tipoOrigen: 'IA_MULTIPLE', + }), + ), + ) + } + className={`hover:border-primary/50 hover:bg-accent flex cursor-pointer items-center gap-4 rounded-lg border p-4 text-left transition-all ${ + isSelected('IA_MULTIPLE') + ? 'bg-primary/5 text-primary ring-primary border-primary ring-1' + : 'border-border text-muted-foreground' + }`} + > + +
+ Varias asignaturas + + Generar varias asignaturas a partir de sugerencias de la IA. + +
+
+
+ )} - onChange((w): NewSubjectWizardState => ({ ...w, tipoOrigen: 'OTRO' })) + onChange( + (w): NewSubjectWizardState => ({ ...w, tipoOrigen: 'CLONADO' }), + ) } role="button" tabIndex={0} @@ -93,7 +174,7 @@ export function PasoMetodoCardGroup({ De otra asignatura o archivo Word. - {(wizard.tipoOrigen === 'OTRO' || + {(wizard.tipoOrigen === 'CLONADO' || wizard.tipoOrigen === 'CLONADO_INTERNO' || wizard.tipoOrigen === 'CLONADO_TRADICIONAL') && ( diff --git a/src/components/wizard/WizardResponsiveHeader.tsx b/src/components/wizard/WizardResponsiveHeader.tsx index 7766e13..39f8427 100644 --- a/src/components/wizard/WizardResponsiveHeader.tsx +++ b/src/components/wizard/WizardResponsiveHeader.tsx @@ -4,9 +4,11 @@ import { StepWithTooltip } from '@/components/wizard/StepWithTooltip' export function WizardResponsiveHeader({ wizard, methods, + titleOverrides, }: { wizard: any methods: any + titleOverrides?: Record }) { const idx = wizard.utils.getIndex(methods.current.id) const totalSteps = wizard.steps.length @@ -14,6 +16,8 @@ export function WizardResponsiveHeader({ const hasNextStep = idx < totalSteps - 1 const nextStep = wizard.steps[currentIndex] + const resolveTitle = (step: any) => titleOverrides?.[step?.id] ?? step?.title + return ( <>
@@ -22,13 +26,13 @@ export function WizardResponsiveHeader({

{hasNextStep && nextStep ? (

- Siguiente: {nextStep.title} + Siguiente: {resolveTitle(nextStep)}

) : (

@@ -48,7 +52,10 @@ export function WizardResponsiveHeader({ className="whitespace-nowrap" > - + ))} diff --git a/src/features/asignaturas/nueva/NuevaAsignaturaModalContainer.tsx b/src/features/asignaturas/nueva/NuevaAsignaturaModalContainer.tsx index 9308fe2..8d48bd9 100644 --- a/src/features/asignaturas/nueva/NuevaAsignaturaModalContainer.tsx +++ b/src/features/asignaturas/nueva/NuevaAsignaturaModalContainer.tsx @@ -3,7 +3,7 @@ import * as Icons from 'lucide-react' import { useNuevaAsignaturaWizard } from './hooks/useNuevaAsignaturaWizard' -import { PasoBasicosForm } from '@/components/asignaturas/wizard/PasoBasicosForm' +import { PasoBasicosForm } from '@/components/asignaturas/wizard/PasoBasicosForm/PasoBasicosForm' import { PasoDetallesPanel } from '@/components/asignaturas/wizard/PasoDetallesPanel' import { PasoMetodoCardGroup } from '@/components/asignaturas/wizard/PasoMetodoCardGroup' import { PasoResumenCard } from '@/components/asignaturas/wizard/PasoResumenCard' @@ -55,10 +55,16 @@ export function NuevaAsignaturaModalContainer({ planId }: { planId: string }) { canContinueDesdeMetodo, canContinueDesdeBasicos, canContinueDesdeDetalles, - simularGeneracionIA, - crearAsignatura, } = useNuevaAsignaturaWizard(planId) + const titleOverrides = + wizard.tipoOrigen === 'IA_MULTIPLE' + ? { + basicos: 'Sugerencias', + detalles: 'Estructura', + } + : undefined + const handleClose = () => { navigate({ to: `/planes/${planId}/asignaturas`, resetScroll: false }) } @@ -99,7 +105,11 @@ export function NuevaAsignaturaModalContainer({ planId }: { planId: string }) { title="Nueva Asignatura" onClose={handleClose} headerSlot={ - + } footerSlot={ @@ -137,11 +147,7 @@ export function NuevaAsignaturaModalContainer({ planId }: { planId: string }) { {idx === 2 && ( - + )} diff --git a/src/features/asignaturas/nueva/hooks/useNuevaAsignaturaWizard.ts b/src/features/asignaturas/nueva/hooks/useNuevaAsignaturaWizard.ts index 4230248..4e281a4 100644 --- a/src/features/asignaturas/nueva/hooks/useNuevaAsignaturaWizard.ts +++ b/src/features/asignaturas/nueva/hooks/useNuevaAsignaturaWizard.ts @@ -1,6 +1,6 @@ import { useState } from 'react' -import type { AsignaturaPreview, NewSubjectWizardState } from '../types' +import type { NewSubjectWizardState } from '../types' export function useNuevaAsignaturaWizard(planId: string) { const [wizard, setWizard] = useState({ @@ -28,6 +28,11 @@ export function useNuevaAsignaturaWizard(planId: string) { repositoriosReferencia: [], archivosAdjuntos: [], }, + iaMultiple: { + ciclo: '', + enfoque: '', + selectedIds: ['1', '3', '6'], + }, resumen: {}, isLoading: false, errorMessage: null, @@ -35,16 +40,18 @@ export function useNuevaAsignaturaWizard(planId: string) { const canContinueDesdeMetodo = wizard.tipoOrigen === 'MANUAL' || - wizard.tipoOrigen === 'IA' || + wizard.tipoOrigen === 'IA_SIMPLE' || + wizard.tipoOrigen === 'IA_MULTIPLE' || wizard.tipoOrigen === 'CLONADO_INTERNO' || wizard.tipoOrigen === 'CLONADO_TRADICIONAL' const canContinueDesdeBasicos = - !!wizard.datosBasicos.nombre && - wizard.datosBasicos.tipo !== null && - wizard.datosBasicos.creditos !== null && - wizard.datosBasicos.creditos > 0 && - !!wizard.datosBasicos.estructuraId + (!!wizard.datosBasicos.nombre && + wizard.datosBasicos.tipo !== null && + wizard.datosBasicos.creditos !== null && + wizard.datosBasicos.creditos > 0 && + !!wizard.datosBasicos.estructuraId) || + true const canContinueDesdeDetalles = (() => { if (wizard.tipoOrigen === 'MANUAL') return true @@ -60,35 +67,11 @@ export function useNuevaAsignaturaWizard(planId: string) { return false })() - const simularGeneracionIA = async () => { - setWizard((w) => ({ ...w, isLoading: true })) - await new Promise((r) => setTimeout(r, 1500)) - setWizard((w) => ({ - ...w, - isLoading: false, - resumen: { - previewAsignatura: { - nombre: w.datosBasicos.nombre, - objetivo: - 'Aplicar los fundamentos teóricos para la resolución de problemas...', - unidades: 5, - bibliografiaCount: 3, - } as AsignaturaPreview, - }, - })) - } - - const crearAsignatura = async () => { - await new Promise((r) => setTimeout(r, 1000)) - } - return { wizard, setWizard, canContinueDesdeMetodo, canContinueDesdeBasicos, canContinueDesdeDetalles, - simularGeneracionIA, - crearAsignatura, } } diff --git a/src/features/asignaturas/nueva/types.ts b/src/features/asignaturas/nueva/types.ts index d57a9ac..064b308 100644 --- a/src/features/asignaturas/nueva/types.ts +++ b/src/features/asignaturas/nueva/types.ts @@ -15,7 +15,12 @@ export type AsignaturaPreview = { export type NewSubjectWizardState = { step: 1 | 2 | 3 | 4 plan_estudio_id: Asignatura['plan_estudio_id'] - tipoOrigen: Asignatura['tipo_origen'] | null + tipoOrigen: + | Asignatura['tipo_origen'] + | 'CLONADO' + | 'IA_SIMPLE' + | 'IA_MULTIPLE' + | null datosBasicos: { nombre: Asignatura['nombre'] codigo?: Asignatura['codigo'] @@ -42,6 +47,11 @@ export type NewSubjectWizardState = { repositoriosReferencia?: Array archivosAdjuntos?: Array } + iaMultiple?: { + ciclo: string + enfoque: string + selectedIds: Array + } resumen: { previewAsignatura?: AsignaturaPreview }