refactor: Update CriterioEvaluacionRow structure and related logic for consistency

This commit was merged in pull request #157.
This commit is contained in:
2026-03-04 15:49:43 -06:00
parent fa200acbfd
commit 37fab3ead6

View File

@@ -39,14 +39,14 @@ export interface AsignaturaResponse {
} }
type CriterioEvaluacionRow = { type CriterioEvaluacionRow = {
label: string criterio: string
value: number porcentaje: number
} }
type CriterioEvaluacionRowDraft = { type CriterioEvaluacionRowDraft = {
id: string id: string
label: string criterio: string
value: string // allow empty while editing porcentaje: string // allow empty while editing
} }
export const Route = createFileRoute( export const Route = createFileRoute(
@@ -134,20 +134,20 @@ function DatosGenerales({
const rows: Array<CriterioEvaluacionRow> = [] const rows: Array<CriterioEvaluacionRow> = []
for (const item of raw) { for (const item of raw) {
if (!isRecord(item)) continue if (!isRecord(item)) continue
const label = typeof item.label === 'string' ? item.label : '' const criterio = typeof item.criterio === 'string' ? item.criterio : ''
const valueNum = const porcentajeNum =
typeof item.value === 'number' typeof item.porcentaje === 'number'
? item.value ? item.porcentaje
: typeof item.value === 'string' : typeof item.porcentaje === 'string'
? Number(item.value) ? Number(item.porcentaje)
: NaN : NaN
if (!label.trim()) continue if (!criterio.trim()) continue
if (!Number.isFinite(valueNum)) continue if (!Number.isFinite(porcentajeNum)) continue
const value = Math.trunc(valueNum) const porcentaje = Math.trunc(porcentajeNum)
if (value < 1 || value > 100) continue if (porcentaje < 1 || porcentaje > 100) continue
rows.push({ label: label.trim(), value }) rows.push({ criterio: criterio.trim(), porcentaje: porcentaje })
} }
return rows return rows
@@ -354,22 +354,22 @@ function InfoCard({
const raw = Array.isArray(initialContent) ? initialContent : [] const raw = Array.isArray(initialContent) ? initialContent : []
const rows: Array<CriterioEvaluacionRowDraft> = raw const rows: Array<CriterioEvaluacionRowDraft> = raw
.map((r: any): CriterioEvaluacionRowDraft | null => { .map((r: any): CriterioEvaluacionRowDraft | null => {
const label = typeof r?.label === 'string' ? r.label : '' const criterio = typeof r?.criterio === 'string' ? r.criterio : ''
const valueNum = const porcentajeNum =
typeof r?.value === 'number' typeof r?.porcentaje === 'number'
? r.value ? r.porcentaje
: typeof r?.value === 'string' : typeof r?.porcentaje === 'string'
? Number(r.value) ? Number(r.porcentaje)
: NaN : NaN
const value = Number.isFinite(valueNum) const porcentaje = Number.isFinite(porcentajeNum)
? String(Math.trunc(valueNum)) ? String(Math.trunc(porcentajeNum))
: '' : ''
return { return {
id: crypto.randomUUID(), id: crypto.randomUUID(),
label, criterio,
value, porcentaje,
} }
}) })
.filter(Boolean) as Array<CriterioEvaluacionRowDraft> .filter(Boolean) as Array<CriterioEvaluacionRowDraft>
@@ -396,25 +396,25 @@ function InfoCard({
if (type === 'evaluation') { if (type === 'evaluation') {
const cleaned: Array<CriterioEvaluacionRow> = [] const cleaned: Array<CriterioEvaluacionRow> = []
for (const r of evalRows) { for (const r of evalRows) {
const label = String(r.label).trim() const criterio = String(r.criterio).trim()
const valueStr = String(r.value).trim() const porcentajeStr = String(r.porcentaje).trim()
if (!label) continue if (!criterio) continue
if (!valueStr) continue if (!porcentajeStr) continue
const n = Number(valueStr) const n = Number(porcentajeStr)
if (!Number.isFinite(n)) continue if (!Number.isFinite(n)) continue
const value = Math.trunc(n) const porcentaje = Math.trunc(n)
if (value < 1 || value > 100) continue if (porcentaje < 1 || porcentaje > 100) continue
cleaned.push({ label, value }) cleaned.push({ criterio, porcentaje })
} }
setData(cleaned) setData(cleaned)
setEvalRows( setEvalRows(
cleaned.map((x) => ({ cleaned.map((x) => ({
id: crypto.randomUUID(), id: crypto.randomUUID(),
label: x.label, criterio: x.criterio,
value: String(x.value), porcentaje: String(x.porcentaje),
})), })),
) )
setIsEditing(false) setIsEditing(false)
@@ -451,13 +451,13 @@ function InfoCard({
const evaluationTotal = useMemo(() => { const evaluationTotal = useMemo(() => {
if (type !== 'evaluation') return 0 if (type !== 'evaluation') return 0
return evalRows.reduce((acc, r) => { return evalRows.reduce((acc, r) => {
const v = String(r.value).trim() const v = String(r.porcentaje).trim()
if (!v) return acc if (!v) return acc
const n = Number(v) const n = Number(v)
if (!Number.isFinite(n)) return acc if (!Number.isFinite(n)) return acc
const value = Math.trunc(n) const porcentaje = Math.trunc(n)
if (value < 1 || value > 100) return acc if (porcentaje < 1 || porcentaje > 100) return acc
return acc + value return acc + porcentaje
}, 0) }, 0)
}, [type, evalRows]) }, [type, evalRows])
@@ -550,14 +550,14 @@ function InfoCard({
className="grid grid-cols-[2fr_1fr_1ch_32px] items-center gap-2" className="grid grid-cols-[2fr_1fr_1ch_32px] items-center gap-2"
> >
<Input <Input
value={row.label} value={row.criterio}
placeholder="Criterio (label)" placeholder="Criterio"
onChange={(e) => { onChange={(e) => {
const nextLabel = e.target.value const nextCriterio = e.target.value
setEvalRows((prev) => setEvalRows((prev) =>
prev.map((r) => prev.map((r) =>
r.id === row.id r.id === row.id
? { ...r, label: nextLabel } ? { ...r, criterio: nextCriterio }
: r, : r,
), ),
) )
@@ -565,7 +565,7 @@ function InfoCard({
/> />
<Input <Input
value={row.value} value={row.porcentaje}
placeholder="%" placeholder="%"
type="number" type="number"
min={1} min={1}
@@ -580,7 +580,13 @@ function InfoCard({
if (raw === '') { if (raw === '') {
setEvalRows((prev) => setEvalRows((prev) =>
prev.map((r) => prev.map((r) =>
r.id === row.id ? { ...r, value: '' } : r, r.id === row.id
? {
id: r.id,
criterio: r.criterio,
porcentaje: '',
}
: r,
), ),
) )
return return
@@ -588,17 +594,23 @@ function InfoCard({
const n = Number(raw) const n = Number(raw)
if (!Number.isFinite(n)) return if (!Number.isFinite(n)) return
const value = Math.trunc(n) const porcentaje = Math.trunc(n)
if (value < 1 || value > 100) return if (porcentaje < 1 || porcentaje > 100) return
// No permitir suma > 100 // No permitir suma > 100
setEvalRows((prev) => { setEvalRows((prev) => {
const next = prev.map((r) => const next = prev.map((r) =>
r.id === row.id ? { ...r, value: raw } : r, r.id === row.id
? {
id: r.id,
criterio: r.criterio,
porcentaje: raw,
}
: r,
) )
const total = next.reduce((acc, r) => { const total = next.reduce((acc, r) => {
const v = String(r.value).trim() const v = String(r.porcentaje).trim()
if (!v) return acc if (!v) return acc
const nn = Number(v) const nn = Number(v)
if (!Number.isFinite(nn)) return acc if (!Number.isFinite(nn)) return acc
@@ -638,7 +650,14 @@ function InfoCard({
</div> </div>
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<span className="text-muted-foreground text-xs"> <span
className={
'text-sm ' +
(evaluationTotal === 100
? 'text-muted-foreground'
: 'text-destructive font-semibold')
}
>
Total: {evaluationTotal}/100 Total: {evaluationTotal}/100
</span> </span>
@@ -652,8 +671,8 @@ function InfoCard({
...prev, ...prev,
{ {
id: crypto.randomUUID(), id: crypto.randomUUID(),
label: '', criterio: '',
value: '', porcentaje: '',
}, },
]) ])
}} }}
@@ -679,14 +698,15 @@ function InfoCard({
if (type === 'evaluation') { if (type === 'evaluation') {
const raw = Array.isArray(data) ? data : [] const raw = Array.isArray(data) ? data : []
setEvalRows( setEvalRows(
raw.map((r: any) => ({ raw.map((r: CriterioEvaluacionRow) => ({
id: crypto.randomUUID(), id: crypto.randomUUID(),
label: typeof r?.label === 'string' ? r.label : '', criterio:
value: typeof r.criterio === 'string' ? r.criterio : '',
typeof r?.value === 'number' porcentaje:
? String(Math.trunc(r.value)) typeof r.porcentaje === 'number'
: typeof r?.value === 'string' ? String(Math.trunc(r.porcentaje))
? String(Math.trunc(Number(r.value))) : typeof r.porcentaje === 'string'
? String(Math.trunc(Number(r.porcentaje)))
: '', : '',
})), })),
) )
@@ -714,7 +734,9 @@ function InfoCard({
<p className="text-slate-400 italic">Sin información.</p> <p className="text-slate-400 italic">Sin información.</p>
))} ))}
{type === 'requirements' && <RequirementsView items={data} />} {type === 'requirements' && <RequirementsView items={data} />}
{type === 'evaluation' && <EvaluationView items={data} />} {type === 'evaluation' && (
<EvaluationView items={data as Array<CriterioEvaluacionRow>} />
)}
</div> </div>
)} )}
</CardContent> </CardContent>
@@ -745,7 +767,11 @@ function RequirementsView({ items }: { items: Array<any> }) {
} }
// Vista de Evaluación // Vista de Evaluación
function EvaluationView({ items }: { items: Array<any> }) { function EvaluationView({ items }: { items: Array<CriterioEvaluacionRow> }) {
const porcentajeTotal = items.reduce(
(total, item) => total + Number(item.porcentaje),
0,
)
return ( return (
<div className="space-y-2"> <div className="space-y-2">
{items.map((item, i) => ( {items.map((item, i) => (
@@ -753,10 +779,15 @@ function EvaluationView({ items }: { items: Array<any> }) {
key={i} key={i}
className="flex justify-between border-b border-slate-50 pb-1.5 text-sm italic" className="flex justify-between border-b border-slate-50 pb-1.5 text-sm italic"
> >
<span className="text-slate-500">{item.label}</span> <span className="text-slate-500">{item.criterio}</span>
<span className="font-bold text-blue-600">{item.value}%</span> <span className="font-bold text-blue-600">{item.porcentaje}%</span>
</div> </div>
))} ))}
{porcentajeTotal < 100 && (
<p className="text-destructive text-sm font-medium">
El porcentaje total es menor a 100%.
</p>
)}
</div> </div>
) )
} }
@@ -813,12 +844,12 @@ function parseCriteriosEvaluacionToPlainText(value: unknown): string {
const lines: Array<string> = [] const lines: Array<string> = []
for (const item of value) { for (const item of value) {
if (!isRecord(item)) continue if (!isRecord(item)) continue
const label = typeof item.label === 'string' ? item.label.trim() : '' const label = typeof item.criterio === 'string' ? item.criterio.trim() : ''
const valueNum = const valueNum =
typeof item.value === 'number' typeof item.porcentaje === 'number'
? item.value ? item.porcentaje
: typeof item.value === 'string' : typeof item.porcentaje === 'string'
? Number(item.value) ? Number(item.porcentaje)
: NaN : NaN
if (!label) continue if (!label) continue