diff --git a/src/routes/planes/$planId/_detalle/mapa.tsx b/src/routes/planes/$planId/_detalle/mapa.tsx index 626e7a1..4a4e545 100644 --- a/src/routes/planes/$planId/_detalle/mapa.tsx +++ b/src/routes/planes/$planId/_detalle/mapa.tsx @@ -9,9 +9,11 @@ import { Trash2, Pencil, } from 'lucide-react' -import { useMemo, useState, useEffect } from 'react' +import { useMemo, useState, useEffect, Fragment } from 'react' +import type { TipoAsignatura } from '@/data' import type { Asignatura, LineaCurricular } from '@/types/plan' +import type { TablesUpdate } from '@/types/supabase' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' @@ -60,21 +62,23 @@ const mapLineasToLineaCurricular = ( const mapAsignaturasToAsignaturas = ( asigApi: Array = [], ): Array => { - return asigApi.map((asig) => ({ - id: asig.id, - clave: asig.codigo, - nombre: asig.nombre, - creditos: asig.creditos ?? 0, - ciclo: asig.numero_ciclo ?? null, - lineaCurricularId: asig.linea_plan_id ?? null, - tipo: asig.tipo === 'OBLIGATORIA' ? 'obligatoria' : 'optativa', - estado: 'borrador', - orden: asig.orden_celda ?? 0, - // Mapeo directo de los nuevos campos de la API - hd: asig.horas_academicas ?? 0, - hi: asig.horas_independientes ?? 0, - prerrequisitos: [], - })) + return asigApi.map((asig) => { + return { + id: asig.id, + clave: asig.codigo, + nombre: asig.nombre, + creditos: asig.creditos ?? 0, + ciclo: asig.numero_ciclo ?? null, + lineaCurricularId: asig.linea_plan_id ?? null, + tipo: asig.tipo, + estado: 'borrador', + orden: asig.orden_celda ?? 0, + // Mapeo directo de los nuevos campos de la API + hd: asig.horas_academicas ?? 0, + hi: asig.horas_independientes ?? 0, + prerrequisitos: [], + } + }) } const lineColors = [ @@ -190,7 +194,7 @@ function MapaCurricularPage() { const [isEditModalOpen, setIsEditModalOpen] = useState(false) const [nombreNuevaLinea, setNombreNuevaLinea] = useState('') // Para el input de nombre personalizado const { mutate: updateAsignatura } = useUpdateAsignatura() - const [seriacionValue, setSeriacionValue] = useState('unassigned') + const [seriacionValue, setSeriacionValue] = useState('') useEffect(() => { if (data?.numero_ciclos) { @@ -323,7 +327,17 @@ function MapaCurricularPage() { setAsignaturas((prev) => prev.map((m) => (m.id === editingData.id ? { ...editingData } : m)), ) - const patch = { + type AsignaturaPatch = { + codigo?: TablesUpdate<'asignaturas'>['codigo'] + nombre?: TablesUpdate<'asignaturas'>['nombre'] + tipo?: TablesUpdate<'asignaturas'>['tipo'] + creditos?: TablesUpdate<'asignaturas'>['creditos'] + horas_academicas?: TablesUpdate<'asignaturas'>['horas_academicas'] + horas_independientes?: TablesUpdate<'asignaturas'>['horas_independientes'] + numero_ciclo?: TablesUpdate<'asignaturas'>['numero_ciclo'] + linea_plan_id?: TablesUpdate<'asignaturas'>['linea_plan_id'] + } + const patch: Partial = { nombre: editingData.nombre, codigo: editingData.clave, creditos: editingData.creditos, @@ -331,12 +345,11 @@ function MapaCurricularPage() { horas_independientes: editingData.hi, numero_ciclo: editingData.ciclo, linea_plan_id: editingData.lineaCurricularId, - tipo: editingData.tipo.toUpperCase(), // Asegurar que coincida con el ENUM (OBLIGATORIA/OPTATIVA) - // datos: editingData.datos, // Si editaste algo del JSONB + tipo: editingData.tipo.toUpperCase() as TipoAsignatura, // Asegurar que coincida con el ENUM (OBLIGATORIA/OPTATIVA) } updateAsignatura( - { asignaturaId: editingData.id, patch }, + { asignaturaId: editingData.id, patch: patch as any }, { onSuccess: () => { setIsEditModalOpen(false) @@ -575,138 +588,124 @@ function MapaCurricularPage() {
LÍNEA CURRICULAR
+ {ciclosArray.map((n) => (
Ciclo {n}
))} +
SUBTOTAL
-
- {lineas.map((linea, idx) => { - const sub = getSubtotalLinea(linea.id) - return ( -
-
-
- handleKeyDownLinea(e, linea.id)} - onBlur={(e) => handleBlurLinea(e, linea.id)} - onClick={() => { - if (editingLineaId !== linea.id) { - setEditingLineaId(linea.id) - setTempNombreLinea(linea.nombre) - } - }} - className={`block w-full text-xs font-bold break-words outline-none ${ - editingLineaId === linea.id - ? 'cursor-text border-b border-teal-500/50 pb-1' - : 'cursor-pointer' - }`} - > - {linea.nombre} - -
+ {lineas.map((linea, idx) => { + const sub = getSubtotalLinea(linea.id) -
- {/* Botón de edición que aparece en hover o si está editando */} - - - borrarLinea(linea.id)} - /> -
-
- - {ciclosArray.map((ciclo) => ( + return ( +
handleDrop(e, null, null)} - // AHORA: Usamos las variables 'ciclo' y 'linea.id' del map - onDrop={(e) => handleDrop(e, ciclo, linea.id)} - className="min-h-[140px] space-y-2 rounded-xl border-2 border-dashed border-slate-100 bg-slate-50/20 p-2" + className={`group relative flex items-center justify-between rounded-xl border-l-4 p-4 transition-all ${ + lineColors[idx % lineColors.length] + } ${editingLineaId === linea.id ? 'bg-white ring-2 ring-teal-500/20' : ''}`} > - {asignaturas - .filter( - (m) => - m.ciclo === ciclo && m.lineaCurricularId === linea.id, - ) - .map((m) => ( - { - setEditingData(m) - setIsEditModalOpen(true) - }} - /> - ))} +
+ handleKeyDownLinea(e, linea.id)} + onBlur={(e) => handleBlurLinea(e, linea.id)} + onClick={() => { + if (editingLineaId !== linea.id) { + setEditingLineaId(linea.id) + setTempNombreLinea(linea.nombre) + } + }} + className={`block w-full text-xs font-bold break-words outline-none ${ + editingLineaId === linea.id + ? 'cursor-text border-b border-teal-500/50 pb-1' + : 'cursor-pointer' + }`} + > + {linea.nombre} + +
+
+ + borrarLinea(linea.id)} + className="..." + size={14} + /> +
- ))} -
-
Cr: {sub.cr}
-
HD: {sub.hd}
-
HI: {sub.hi}
-
-
- ) - })} + {ciclosArray.map((ciclo) => ( +
handleDrop(e, ciclo, linea.id)} + className="min-h-[140px] space-y-2 rounded-xl border-2 border-dashed border-slate-100 bg-slate-50/20 p-2" + > + {asignaturas + .filter( + (m) => + m.ciclo === ciclo && + m.lineaCurricularId === linea.id, + ) + .map((m) => ( + { + setEditingData(m) + setIsEditModalOpen(true) + }} + /> + ))} +
+ ))} -
-
+
+
Cr: {sub.cr}
+
HD: {sub.hd}
+
HI: {sub.hi}
+
+ + ) + })} + +
+ +
Totales por Ciclo
+ {ciclosArray.map((ciclo) => { const t = getTotalesCiclo(ciclo) return (
Cr: {t.cr}
@@ -716,6 +715,7 @@ function MapaCurricularPage() {
) })} +
{stats.cr} Cr
{stats.hd + stats.hi} Hrs
@@ -725,7 +725,6 @@ function MapaCurricularPage() {
{/* Asignaturas Sin Asignar */} - {/* SECCIÓN DE MATERIAS SIN ASIGNAR (Mejorada para estar siempre disponible) */}
@@ -941,8 +940,8 @@ function MapaCurricularPage() { + onValueChange={(val: 'OBLIGATORIA' | 'OPTATIVA') => setEditingData({ ...editingData, tipo: val }) } > @@ -1014,8 +1011,8 @@ function MapaCurricularPage() { - Obligatoria - Optativa + Obligatoria + Optativa