import { useQueryClient } from '@tanstack/react-query' import { createFileRoute, Outlet, useNavigate } from '@tanstack/react-router' import { Plus, Copy, Search, Filter, ChevronRight, BookOpen, Loader2, } from 'lucide-react' import { useEffect, useMemo, useState } from 'react' import type { Asignatura, AsignaturaStatus, TipoAsignatura } from '@/types/plan' import type { Tables } from '@/types/supabase' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select' import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table' import { qk, supabaseBrowser, usePlanAsignaturas, usePlanLineas } from '@/data' // --- Configuración de Estilos --- const statusConfig: Record< AsignaturaStatus, { label: string; className: string } > = { generando: { label: 'Generando', className: 'bg-slate-100 text-slate-600' }, borrador: { label: 'Borrador', className: 'bg-slate-100 text-slate-600' }, revisada: { label: 'Revisada', className: 'bg-amber-100 text-amber-700' }, aprobada: { label: 'Aprobada', className: 'bg-emerald-100 text-emerald-700' }, } const tipoConfig: Record = { OBLIGATORIA: { label: 'Obligatoria', className: 'bg-blue-100 text-blue-700', }, OPTATIVA: { label: 'Optativa', className: 'bg-purple-100 text-purple-700' }, TRONCAL: { label: 'Troncal', className: 'bg-slate-100 text-slate-700' }, OTRA: { label: 'Otra', className: 'bg-slate-100 text-slate-700' }, } // --- Mapeadores de API --- const mapAsignaturas = ( asigApi: Array> = [], ): Array => { return asigApi.map((asig) => ({ id: asig.id, clave: asig.codigo ?? '', nombre: asig.nombre, creditos: asig.creditos, ciclo: asig.numero_ciclo ?? null, lineaCurricularId: asig.linea_plan_id ?? null, tipo: asig.tipo, estado: asig.estado, hd: asig.horas_academicas ?? 0, hi: asig.horas_independientes ?? 0, prerrequisitos: [], })) } export const Route = createFileRoute('/planes/$planId/_detalle/asignaturas')({ component: AsignaturasPage, }) function AsignaturasPage() { const { planId } = Route.useParams() const navigate = useNavigate() const queryClient = useQueryClient() // 1. Fetch de datos reales const { data: asignaturasApi, isLoading: loadingAsig } = usePlanAsignaturas(planId) const { data: lineasApi, isLoading: loadingLineas } = usePlanLineas(planId) useEffect(() => { const supabase = supabaseBrowser() const channel = supabase .channel(`plan:${planId}:asignaturas:updates`) .on( 'postgres_changes', { event: 'UPDATE', schema: 'public', table: 'asignaturas', filter: `plan_estudio_id=eq.${planId}`, }, () => { queryClient.invalidateQueries({ queryKey: qk.planAsignaturas(planId), }) }, ) .subscribe() return () => { supabase.removeChannel(channel) } }, [planId, queryClient]) // 2. Estados de filtrado const [searchTerm, setSearchTerm] = useState('') const [filterTipo, setFilterTipo] = useState('all') const [filterEstado, setFilterEstado] = useState('all') const [filterLinea, setFilterLinea] = useState('all') // 3. Procesamiento de datos const asignaturas = useMemo( () => mapAsignaturas(asignaturasApi), [asignaturasApi], ) const lineas = useMemo(() => lineasApi || [], [lineasApi]) const filteredAsignaturas = asignaturas.filter((m) => { const matchesSearch = m.nombre.toLowerCase().includes(searchTerm.toLowerCase()) || m.clave.toLowerCase().includes(searchTerm.toLowerCase()) const matchesTipo = filterTipo === 'all' || m.tipo === filterTipo const matchesEstado = filterEstado === 'all' || m.estado === filterEstado const matchesLinea = filterLinea === 'all' || m.lineaCurricularId === filterLinea return matchesSearch && matchesTipo && matchesEstado && matchesLinea }) const getLineaNombre = (lineaId: string | null) => { if (!lineaId) return 'Sin asignar' return lineas.find((l: any) => l.id === lineaId)?.nombre || 'Desconocida' } if (loadingAsig || loadingLineas) { return (
) } return (
{/* Header */}

Asignaturas del Plan

{asignaturas.length} asignaturas en total •{' '} {filteredAsignaturas.length} filtradas

{/* Barra de Filtros Avanzada */}
setSearchTerm(e.target.value)} className="bg-white pl-9" />
{/* Tabla Pro */}
Clave Nombre Créditos Ciclo Línea Curricular Tipo Estado {filteredAsignaturas.length === 0 ? (

No se encontraron asignaturas

Intenta cambiar los filtros de búsqueda

) : ( filteredAsignaturas.map((asignatura) => ( navigate({ to: '/planes/$planId/asignaturas/$asignaturaId', params: { planId, asignaturaId: asignatura.id, // 👈 puede ser índice, consecutivo o slug }, state: { realId: asignatura.id, // 👈 ID largo oculto asignaturaId: asignatura.id, } as any, }) } > {asignatura.clave} {asignatura.nombre} {asignatura.creditos} {asignatura.ciclo ? ( Ciclo {asignatura.ciclo} ) : ( )} {getLineaNombre(asignatura.lineaCurricularId)} {tipoConfig[asignatura.tipo].label} {statusConfig[asignatura.estado].label}
)) )}
) }