Merge branch 'main' into issue/80-deshacerse-de-todos-estos-query-params-de-la-url
This commit is contained in:
@@ -27,14 +27,25 @@ export function PasoBasicosForm({
|
|||||||
|
|
||||||
const [creditosInput, setCreditosInput] = useState<string>(() => {
|
const [creditosInput, setCreditosInput] = useState<string>(() => {
|
||||||
const c = Number(wizard.datosBasicos.creditos ?? 0)
|
const c = Number(wizard.datosBasicos.creditos ?? 0)
|
||||||
return c > 0 ? c.toFixed(2) : ''
|
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)
|
const [creditosFocused, setCreditosFocused] = useState(false)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (creditosFocused) return
|
if (creditosFocused) return
|
||||||
const c = Number(wizard.datosBasicos.creditos ?? 0)
|
const c = Number(wizard.datosBasicos.creditos ?? 0)
|
||||||
setCreditosInput(c > 0 ? c.toFixed(2) : '')
|
let newC = c
|
||||||
|
if (Number.isFinite(c) && c > 999) {
|
||||||
|
newC = 999
|
||||||
|
}
|
||||||
|
setCreditosInput(newC > 0 ? newC.toFixed(2) : '')
|
||||||
}, [wizard.datosBasicos.creditos, creditosFocused])
|
}, [wizard.datosBasicos.creditos, creditosFocused])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -44,6 +55,7 @@ export function PasoBasicosForm({
|
|||||||
<Input
|
<Input
|
||||||
id="nombre"
|
id="nombre"
|
||||||
placeholder="Ej. Matemáticas Discretas"
|
placeholder="Ej. Matemáticas Discretas"
|
||||||
|
maxLength={200}
|
||||||
value={wizard.datosBasicos.nombre}
|
value={wizard.datosBasicos.nombre}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
onChange(
|
onChange(
|
||||||
@@ -67,6 +79,7 @@ export function PasoBasicosForm({
|
|||||||
<Input
|
<Input
|
||||||
id="codigo"
|
id="codigo"
|
||||||
placeholder="Ej. MAT-101"
|
placeholder="Ej. MAT-101"
|
||||||
|
maxLength={200}
|
||||||
value={wizard.datosBasicos.codigo || ''}
|
value={wizard.datosBasicos.codigo || ''}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
onChange(
|
onChange(
|
||||||
@@ -123,6 +136,7 @@ export function PasoBasicosForm({
|
|||||||
id="creditos"
|
id="creditos"
|
||||||
type="text"
|
type="text"
|
||||||
inputMode="decimal"
|
inputMode="decimal"
|
||||||
|
maxLength={6}
|
||||||
pattern="^\\d*(?:[.,]\\d{0,2})?$"
|
pattern="^\\d*(?:[.,]\\d{0,2})?$"
|
||||||
value={creditosInput}
|
value={creditosInput}
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
@@ -144,7 +158,7 @@ export function PasoBasicosForm({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const normalized = raw.replace(',', '.')
|
const normalized = raw.replace(',', '.')
|
||||||
const asNumber = Number.parseFloat(normalized)
|
let asNumber = Number.parseFloat(normalized)
|
||||||
if (!Number.isFinite(asNumber) || asNumber <= 0) {
|
if (!Number.isFinite(asNumber) || asNumber <= 0) {
|
||||||
setCreditosInput('')
|
setCreditosInput('')
|
||||||
onChange((w) => ({
|
onChange((w) => ({
|
||||||
@@ -154,6 +168,9 @@ export function PasoBasicosForm({
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cap to 999
|
||||||
|
if (asNumber > 999) asNumber = 999
|
||||||
|
|
||||||
const fixed = asNumber.toFixed(2)
|
const fixed = asNumber.toFixed(2)
|
||||||
setCreditosInput(fixed)
|
setCreditosInput(fixed)
|
||||||
onChange((w) => ({
|
onChange((w) => ({
|
||||||
@@ -174,6 +191,22 @@ export function PasoBasicosForm({
|
|||||||
|
|
||||||
if (!/^\d*(?:[.,]\d{0,2})?$/.test(nextRaw)) 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)
|
setCreditosInput(nextRaw)
|
||||||
|
|
||||||
const asNumber = Number.parseFloat(nextRaw.replace(',', '.'))
|
const asNumber = Number.parseFloat(nextRaw.replace(',', '.'))
|
||||||
@@ -191,94 +224,6 @@ export function PasoBasicosForm({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid gap-1">
|
|
||||||
<Label htmlFor="horasAcademicas">
|
|
||||||
Horas Académicas
|
|
||||||
<span className="text-xs font-normal text-gray-500 dark:text-gray-400">
|
|
||||||
(Opcional)
|
|
||||||
</span>
|
|
||||||
</Label>
|
|
||||||
<Input
|
|
||||||
id="horasAcademicas"
|
|
||||||
type="number"
|
|
||||||
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<HTMLInputElement>) =>
|
|
||||||
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"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="grid gap-1">
|
|
||||||
<Label htmlFor="horasIndependientes">
|
|
||||||
Horas Independientes
|
|
||||||
<span className="text-xs font-normal text-gray-500 dark:text-gray-400">
|
|
||||||
(Opcional)
|
|
||||||
</span>
|
|
||||||
</Label>
|
|
||||||
<Input
|
|
||||||
id="horasIndependientes"
|
|
||||||
type="number"
|
|
||||||
min={1}
|
|
||||||
step={1}
|
|
||||||
inputMode="numeric"
|
|
||||||
pattern="[0-9]*"
|
|
||||||
value={wizard.datosBasicos.horasIndependientes ?? ''}
|
|
||||||
onKeyDown={(e) => {
|
|
||||||
if (['.', ',', '-', 'e', 'E', '+'].includes(e.key)) {
|
|
||||||
e.preventDefault()
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
|
||||||
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"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="grid gap-1">
|
<div className="grid gap-1">
|
||||||
<Label htmlFor="estructura">Estructura de la asignatura</Label>
|
<Label htmlFor="estructura">Estructura de la asignatura</Label>
|
||||||
<Select
|
<Select
|
||||||
@@ -314,6 +259,98 @@ export function PasoBasicosForm({
|
|||||||
Define los campos requeridos (ej. Objetivos, Temario, Evaluación).
|
Define los campos requeridos (ej. Objetivos, Temario, Evaluación).
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="grid gap-1">
|
||||||
|
<Label htmlFor="horasAcademicas">
|
||||||
|
Horas Académicas
|
||||||
|
<span className="text-xs font-normal text-gray-500 dark:text-gray-400">
|
||||||
|
(Opcional)
|
||||||
|
</span>
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
id="horasAcademicas"
|
||||||
|
type="number"
|
||||||
|
min={1}
|
||||||
|
max={999}
|
||||||
|
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<HTMLInputElement>) =>
|
||||||
|
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"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid gap-1">
|
||||||
|
<Label htmlFor="horasIndependientes">
|
||||||
|
Horas Independientes
|
||||||
|
<span className="text-xs font-normal text-gray-500 dark:text-gray-400">
|
||||||
|
(Opcional)
|
||||||
|
</span>
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
id="horasIndependientes"
|
||||||
|
type="number"
|
||||||
|
min={1}
|
||||||
|
max={999}
|
||||||
|
step={1}
|
||||||
|
inputMode="numeric"
|
||||||
|
pattern="[0-9]*"
|
||||||
|
value={wizard.datosBasicos.horasIndependientes ?? ''}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (['.', ',', '-', 'e', 'E', '+'].includes(e.key)) {
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||||
|
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"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ export function PasoDetallesPanel({
|
|||||||
<Label>Descripción del enfoque académico</Label>
|
<Label>Descripción del enfoque académico</Label>
|
||||||
<Textarea
|
<Textarea
|
||||||
placeholder="Describe el enfoque, alcance y público objetivo. Ej.: Teórica-práctica enfocada en patrones de diseño, con proyectos semanales."
|
placeholder="Describe el enfoque, alcance y público objetivo. Ej.: Teórica-práctica enfocada en patrones de diseño, con proyectos semanales."
|
||||||
|
maxLength={7000}
|
||||||
value={wizard.iaConfig?.descripcionEnfoqueAcademico}
|
value={wizard.iaConfig?.descripcionEnfoqueAcademico}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
onChange(
|
onChange(
|
||||||
@@ -80,6 +81,7 @@ export function PasoDetallesPanel({
|
|||||||
</Label>
|
</Label>
|
||||||
<Textarea
|
<Textarea
|
||||||
placeholder="Opcional: restricciones y preferencias. Ej.: incluye bibliografía en español, evita contenido avanzado, prioriza evaluación por proyectos."
|
placeholder="Opcional: restricciones y preferencias. Ej.: incluye bibliografía en español, evita contenido avanzado, prioriza evaluación por proyectos."
|
||||||
|
maxLength={7000}
|
||||||
value={wizard.iaConfig?.instruccionesAdicionalesIA}
|
value={wizard.iaConfig?.instruccionesAdicionalesIA}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
onChange(
|
onChange(
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ export function PasoBasicosForm({
|
|||||||
id="nombrePlan"
|
id="nombrePlan"
|
||||||
placeholder="Ej. Ingeniería en Sistemas (2026)"
|
placeholder="Ej. Ingeniería en Sistemas (2026)"
|
||||||
value={wizard.datosBasicos.nombrePlan}
|
value={wizard.datosBasicos.nombrePlan}
|
||||||
|
maxLength={200}
|
||||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||||
onChange(
|
onChange(
|
||||||
(w): NewPlanWizardState => ({
|
(w): NewPlanWizardState => ({
|
||||||
@@ -228,6 +229,7 @@ export function PasoBasicosForm({
|
|||||||
id="numCiclos"
|
id="numCiclos"
|
||||||
type="number"
|
type="number"
|
||||||
min={1}
|
min={1}
|
||||||
|
max={99}
|
||||||
step={1}
|
step={1}
|
||||||
inputMode="numeric"
|
inputMode="numeric"
|
||||||
pattern="[0-9]*"
|
pattern="[0-9]*"
|
||||||
@@ -251,7 +253,8 @@ export function PasoBasicosForm({
|
|||||||
if (Number.isNaN(asNumber)) return null
|
if (Number.isNaN(asNumber)) return null
|
||||||
// Coerce to positive integer (natural numbers without zero)
|
// Coerce to positive integer (natural numbers without zero)
|
||||||
const n = Math.floor(Math.abs(asNumber))
|
const n = Math.floor(Math.abs(asNumber))
|
||||||
return n >= 1 ? n : 1
|
const capped = Math.min(n >= 1 ? n : 1, 99)
|
||||||
|
return capped
|
||||||
})(),
|
})(),
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ export function PasoDetallesPanel({
|
|||||||
id="desc"
|
id="desc"
|
||||||
className="bg-background text-foreground ring-offset-background focus-visible:ring-ring min-h-24 w-full rounded-md border px-3 py-2 text-sm shadow-sm focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none"
|
className="bg-background text-foreground ring-offset-background focus-visible:ring-ring min-h-24 w-full rounded-md border px-3 py-2 text-sm shadow-sm focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none"
|
||||||
placeholder="Describe el enfoque del programa…"
|
placeholder="Describe el enfoque del programa…"
|
||||||
|
maxLength={7000}
|
||||||
value={wizard.iaConfig?.descripcionEnfoqueAcademico || ''}
|
value={wizard.iaConfig?.descripcionEnfoqueAcademico || ''}
|
||||||
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) =>
|
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) =>
|
||||||
onChange((w) => ({
|
onChange((w) => ({
|
||||||
@@ -73,6 +74,7 @@ export function PasoDetallesPanel({
|
|||||||
id="notas"
|
id="notas"
|
||||||
className="bg-background text-foreground ring-offset-background focus-visible:ring-ring min-h-24 w-full rounded-md border px-3 py-2 text-sm shadow-sm focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none"
|
className="bg-background text-foreground ring-offset-background focus-visible:ring-ring min-h-24 w-full rounded-md border px-3 py-2 text-sm shadow-sm focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none"
|
||||||
placeholder="Lineamientos institucionales, restricciones, etc."
|
placeholder="Lineamientos institucionales, restricciones, etc."
|
||||||
|
maxLength={7000}
|
||||||
value={wizard.iaConfig?.instruccionesAdicionalesIA || ''}
|
value={wizard.iaConfig?.instruccionesAdicionalesIA || ''}
|
||||||
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) =>
|
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) =>
|
||||||
onChange((w) => ({
|
onChange((w) => ({
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ export async function plans_list(
|
|||||||
`,
|
`,
|
||||||
{ count: 'exact' },
|
{ count: 'exact' },
|
||||||
)
|
)
|
||||||
.order('actualizado_en', { ascending: false })
|
.order('creado_en', { ascending: false })
|
||||||
|
|
||||||
// 2. Aplicamos filtros dinámicos
|
// 2. Aplicamos filtros dinámicos
|
||||||
|
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ export type Database = {
|
|||||||
asignatura_hash: string | null
|
asignatura_hash: string | null
|
||||||
codigo: string | null
|
codigo: string | null
|
||||||
contenido_tematico: Json
|
contenido_tematico: Json
|
||||||
|
conversation_id: string | null
|
||||||
creado_en: string
|
creado_en: string
|
||||||
creado_por: string | null
|
creado_por: string | null
|
||||||
creditos: number
|
creditos: number
|
||||||
@@ -116,6 +117,7 @@ export type Database = {
|
|||||||
asignatura_hash?: string | null
|
asignatura_hash?: string | null
|
||||||
codigo?: string | null
|
codigo?: string | null
|
||||||
contenido_tematico?: Json
|
contenido_tematico?: Json
|
||||||
|
conversation_id?: string | null
|
||||||
creado_en?: string
|
creado_en?: string
|
||||||
creado_por?: string | null
|
creado_por?: string | null
|
||||||
creditos: number
|
creditos: number
|
||||||
@@ -139,6 +141,7 @@ export type Database = {
|
|||||||
asignatura_hash?: string | null
|
asignatura_hash?: string | null
|
||||||
codigo?: string | null
|
codigo?: string | null
|
||||||
contenido_tematico?: Json
|
contenido_tematico?: Json
|
||||||
|
conversation_id?: string | null
|
||||||
creado_en?: string
|
creado_en?: string
|
||||||
creado_por?: string | null
|
creado_por?: string | null
|
||||||
creditos?: number
|
creditos?: number
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
v2.67.1
|
v2.75.0
|
||||||
Reference in New Issue
Block a user