diff --git a/src/components/asignaturas/wizard/PasoBasicosForm.tsx b/src/components/asignaturas/wizard/PasoBasicosForm.tsx index 166907f..dff2726 100644 --- a/src/components/asignaturas/wizard/PasoBasicosForm.tsx +++ b/src/components/asignaturas/wizard/PasoBasicosForm.tsx @@ -1,7 +1,7 @@ -import type { - NewSubjectWizardState, - TipoAsignatura, -} from '@/features/asignaturas/nueva/types' +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' @@ -12,10 +12,9 @@ import { SelectTrigger, SelectValue, } from '@/components/ui/select' -import { - ESTRUCTURAS_SEP, - TIPOS_MATERIA, -} from '@/features/asignaturas/nueva/catalogs' +import { useSubjectEstructuras } from '@/data' +import { TIPOS_MATERIA } from '@/features/asignaturas/nueva/catalogs' +import { cn } from '@/lib/utils' export function PasoBasicosForm({ wizard, @@ -24,6 +23,20 @@ export function PasoBasicosForm({ wizard: NewSubjectWizardState onChange: React.Dispatch> }) { + const { data: estructuras } = useSubjectEstructuras() + + const [creditosInput, setCreditosInput] = useState(() => { + const c = Number(wizard.datosBasicos.creditos ?? 0) + return c > 0 ? c.toFixed(2) : '' + }) + const [creditosFocused, setCreditosFocused] = useState(false) + + useEffect(() => { + if (creditosFocused) return + const c = Number(wizard.datosBasicos.creditos ?? 0) + setCreditosInput(c > 0 ? c.toFixed(2) : '') + }, [wizard.datosBasicos.creditos, creditosFocused]) + return (
@@ -33,45 +46,66 @@ export function PasoBasicosForm({ placeholder="Ej. Matemáticas Discretas" value={wizard.datosBasicos.nombre} onChange={(e) => - onChange((w) => ({ - ...w, - datosBasicos: { ...w.datosBasicos, nombre: e.target.value }, - })) + 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) => ({ - ...w, - datosBasicos: { ...w.datosBasicos, clave: e.target.value }, - })) + 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" />
+ type="text" + inputMode="decimal" + pattern="^\\d*(?:[.,]\\d{0,2})?$" + value={creditosInput} + onKeyDown={(e) => { + 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(',', '.') + const asNumber = Number.parseFloat(normalized) + if (!Number.isFinite(asNumber) || asNumber <= 0) { + setCreditosInput('') + onChange((w) => ({ + ...w, + datosBasicos: { ...w.datosBasicos, creditos: 0 }, + })) + return + } + + 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 + + setCreditosInput(nextRaw) + + const asNumber = Number.parseFloat(nextRaw.replace(',', '.')) onChange((w) => ({ ...w, datosBasicos: { ...w.datosBasicos, - creditos: Number(e.target.value || 0), + 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" />
- + - onChange((w) => ({ - ...w, - datosBasicos: { - ...w.datosBasicos, - horasSemana: Number(e.target.value || 0), - }, - })) + min={1} + step={1} + inputMode="numeric" + pattern="[0-9]*" + value={wizard.datosBasicos.horasAcademicas ?? ''} + onKeyDown={(e) => { + 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)) + return n >= 1 ? n : 1 + })(), + }, + }), + ) } + 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)) + return n >= 1 ? n : 1 + })(), + }, + }), + ) + } + 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/PasoConfiguracionPanel.tsx b/src/components/asignaturas/wizard/PasoDetallesPanel.tsx similarity index 57% rename from src/components/asignaturas/wizard/PasoConfiguracionPanel.tsx rename to src/components/asignaturas/wizard/PasoDetallesPanel.tsx index c81fb77..612d4fc 100644 --- a/src/components/asignaturas/wizard/PasoConfiguracionPanel.tsx +++ b/src/components/asignaturas/wizard/PasoDetallesPanel.tsx @@ -1,11 +1,11 @@ import * as Icons from 'lucide-react' +import type { UploadedFile } from '@/components/planes/wizard/PasoDetallesPanel/FileDropZone' import type { NewSubjectWizardState } from '@/features/asignaturas/nueva/types' -import { Button } from '@/components/ui/button' +import ReferenciasParaIA from '@/components/planes/wizard/PasoDetallesPanel/ReferenciasParaIA' import { Card, - CardContent, CardDescription, CardHeader, CardTitle, @@ -21,22 +21,21 @@ import { } from '@/components/ui/select' import { Textarea } from '@/components/ui/textarea' import { - ARCHIVOS_SISTEMA_MOCK, FACULTADES, MATERIAS_MOCK, PLANES_MOCK, } from '@/features/asignaturas/nueva/catalogs' -export function PasoConfiguracionPanel({ +export function PasoDetallesPanel({ wizard, onChange, - onGenerarIA, + onGenerarIA: _onGenerarIA, }: { wizard: NewSubjectWizardState onChange: React.Dispatch> onGenerarIA: () => void }) { - if (wizard.modoCreacion === 'MANUAL') { + if (wizard.tipoOrigen === 'MANUAL') { return ( @@ -50,116 +49,104 @@ export function PasoConfiguracionPanel({ ) } - if (wizard.modoCreacion === 'IA') { + if (wizard.tipoOrigen === 'IA') { return ( -

-
- +
+
+