Se corrigen incidencias

This commit is contained in:
2026-01-21 12:07:07 -06:00
parent 25acb9aeaa
commit c396ce8104
5 changed files with 186 additions and 251 deletions

View File

@@ -1,37 +1,27 @@
import { Link, useRouterState } from '@tanstack/react-router'
import { ArrowLeft, GraduationCap, Pencil, Sparkles } from 'lucide-react'
import { useCallback, useState, useEffect } from 'react'
import { Link } from '@tanstack/react-router'
import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { BibliographyItem } from './BibliographyItem'
import { ContenidoTematico } from './ContenidoTematico'
import { DocumentoSEPTab } from './DocumentoSEPTab'
import { HistorialTab } from './HistorialTab'
import { IAMateriaTab } from './IAMateriaTab'
import type { CampoEstructura, IAMessage, IASugerencia } from '@/types/materia'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { Separator } from '@/components/ui/separator'
import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs'
import { Textarea } from '@/components/ui/textarea'
import {
ArrowLeft,
GraduationCap,
Edit2,
Save,
Pencil,
Sparkles,
} from 'lucide-react'
import { ContenidoTematico } from './ContenidoTematico'
import { BibliographyItem } from './BibliographyItem'
import { IAMateriaTab } from './IAMateriaTab'
import type {
CampoEstructura,
IAMessage,
IASugerencia,
UnidadTematica,
} from '@/types/materia'
import { useSubject } from '@/data/hooks/useSubjects'
import {
mockMateria,
mockEstructura,
mockDocumentoSep,
mockHistorial,
} from '@/data/mockMateriaData'
import { DocumentoSEPTab } from './DocumentoSEPTab'
import { HistorialTab } from './HistorialTab'
import { useSubject } from '@/data/hooks/useSubjects'
export interface BibliografiaEntry {
id: string
@@ -41,8 +31,8 @@ export interface BibliografiaEntry {
fuenteBiblioteca?: any
}
export interface BibliografiaTabProps {
bibliografia: BibliografiaEntry[]
onSave: (bibliografia: BibliografiaEntry[]) => void
bibliografia: Array<BibliografiaEntry>
onSave: (bibliografia: Array<BibliografiaEntry>) => void
isSaving: boolean
}
@@ -90,13 +80,15 @@ function EditableHeaderField({
)
}
export default function MateriaDetailPage() {
const routerState = useRouterState()
const state = routerState.location.state as any
const { data: asignaturasApi, isLoading: loadingAsig } = useSubject(
'9d4dda6a-488f-428a-8a07-38081592a641',
state?.realId,
)
// 1. Asegúrate de tener estos estados en tu componente principal
const [messages, setMessages] = useState<IAMessage[]>([])
const [messages, setMessages] = useState<Array<IAMessage>>([])
const [datosGenerales, setDatosGenerales] = useState({})
const [campos, setCampos] = useState<CampoEstructura[]>([])
const [campos, setCampos] = useState<Array<CampoEstructura>>([])
// Dentro de MateriaDetailPage
const [headerData, setHeaderData] = useState({
@@ -142,16 +134,16 @@ export default function MateriaDetailPage() {
setMessages([...messages, newMessage])
// Aquí llamarías a tu API de OpenAI/Claude
//toast.info("Enviando consulta a la IA...");
// toast.info("Enviando consulta a la IA...");
}
const handleAcceptSuggestion = (sugerencia: IASugerencia) => {
// Lógica para actualizar el valor del campo en tu estado de datosGenerales
//toast.success(`Sugerencia aplicada a ${sugerencia.campoNombre}`);
// toast.success(`Sugerencia aplicada a ${sugerencia.campoNombre}`);
}
// Dentro de tu componente principal (donde están los Tabs)
const [bibliografia, setBibliografia] = useState<BibliografiaEntry[]>([
const [bibliografia, setBibliografia] = useState<Array<BibliografiaEntry>>([
{
id: '1',
tipo: 'BASICA',
@@ -160,7 +152,7 @@ export default function MateriaDetailPage() {
])
const [isSaving, setIsSaving] = useState(false)
const handleSaveBibliografia = (data: BibliografiaEntry[]) => {
const handleSaveBibliografia = (data: Array<BibliografiaEntry>) => {
setIsSaving(true)
// Aquí iría tu llamada a la API
setBibliografia(data)
@@ -168,7 +160,7 @@ export default function MateriaDetailPage() {
// Simulamos un guardado
setTimeout(() => {
setIsSaving(false)
//toast.success("Cambios guardados");
// toast.success("Cambios guardados");
}, 1000)
}
@@ -304,7 +296,7 @@ export default function MateriaDetailPage() {
(id) =>
console.log(
'Rechazada',
) /*toast.error("Sugerencia rechazada")*/
) /* toast.error("Sugerencia rechazada")*/
}
/>
</TabsContent>
@@ -507,7 +499,7 @@ function InfoCard({
}
// Vista de Requisitos
function RequirementsView({ items }: { items: any[] }) {
function RequirementsView({ items }: { items: Array<any> }) {
return (
<div className="space-y-3">
{items.map((req, i) => (
@@ -528,7 +520,7 @@ function RequirementsView({ items }: { items: any[] }) {
}
// Vista de Evaluación
function EvaluationView({ items }: { items: any[] }) {
function EvaluationView({ items }: { items: Array<any> }) {
return (
<div className="space-y-2">
{items.map((item, i) => (

View File

@@ -1,24 +1,4 @@
import { createFileRoute } from '@tanstack/react-router'
import { useState, useMemo } from 'react'
import type { Materia, LineaCurricular } from '@/types/plan'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Badge } from '@/components/ui/badge'
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@/components/ui/table'
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select'
import { createFileRoute, useNavigate } from '@tanstack/react-router'
import {
Plus,
Copy,
@@ -28,6 +8,28 @@ import {
BookOpen,
Loader2,
} from 'lucide-react'
import { useState, useMemo } from 'react'
import type { Materia } from '@/types/plan'
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 { usePlanAsignaturas, usePlanLineas } from '@/data'
// --- Configuración de Estilos ---
@@ -44,7 +46,7 @@ const tipoConfig: Record<string, { label: string; className: string }> = {
}
// --- Mapeadores de API ---
const mapAsignaturas = (asigApi: any[] = []): Materia[] => {
const mapAsignaturas = (asigApi: Array<any> = []): Array<Materia> => {
return asigApi.map((asig) => ({
id: asig.id,
clave: asig.codigo,
@@ -66,6 +68,7 @@ export const Route = createFileRoute('/planes/$planId/_detalle/materias')({
function MateriasPage() {
const { planId } = Route.useParams()
const navigate = useNavigate()
// 1. Fetch de datos reales
const { data: asignaturasApi, isLoading: loadingAsig } = usePlanAsignaturas(
@@ -224,6 +227,18 @@ function MateriasPage() {
<TableRow
key={materia.id}
className="group cursor-pointer transition-colors hover:bg-slate-50/80"
onClick={() =>
navigate({
to: '/planes/$planId/asignaturas/$asignaturaId',
params: {
planId,
asignaturaId: 'asignatura', // 👈 puede ser índice, consecutivo o slug
},
state: {
realId: materia.id, // 👈 ID largo oculto
} as any,
})
}
>
<TableCell className="font-mono text-xs font-bold text-slate-400">
{materia.clave}

View File

@@ -1,6 +1,16 @@
import { createFileRoute, Outlet, Link } from '@tanstack/react-router'
import { ChevronLeft, GraduationCap, Clock, Hash, CalendarDays, Rocket, BookOpen, CheckCircle2 } from "lucide-react"
import { Badge } from "@/components/ui/badge"
import {
ChevronLeft,
GraduationCap,
Clock,
Hash,
CalendarDays,
Rocket,
BookOpen,
CheckCircle2,
} from 'lucide-react'
import { Badge } from '@/components/ui/badge'
export const Route = createFileRoute('/planes/$planId/_detalle')({
component: RouteComponent,
@@ -12,11 +22,11 @@ function RouteComponent() {
return (
<div className="min-h-screen bg-white">
{/* 1. Header Superior con Sombra (Volver a planes) */}
<div className="border-b bg-white/50 backdrop-blur-sm sticky top-0 z-20 shadow-sm">
<div className="sticky top-0 z-20 border-b bg-white/50 shadow-sm backdrop-blur-sm">
<div className="px-6 py-2">
<Link
to="/planes"
className="flex items-center gap-1 text-xs text-gray-500 hover:text-gray-800 transition-colors w-fit"
<Link
to="/planes"
className="flex w-fit items-center gap-1 text-xs text-gray-500 transition-colors hover:text-gray-800"
>
<ChevronLeft size={14} /> Volver a planes
</Link>
@@ -24,54 +34,91 @@ function RouteComponent() {
</div>
{/* 2. Contenido Principal con Padding */}
<div className="p-8 max-w-[1600px] mx-auto space-y-8">
<div className="mx-auto max-w-[1600px] space-y-8 p-8">
{/* Header del Plan y Badges */}
<div className="flex flex-col md:flex-row justify-between items-start gap-4">
<div className="flex flex-col items-start justify-between gap-4 md:flex-row">
<div>
<h1 className="text-3xl font-bold tracking-tight text-slate-900">Plan de Estudios 2024</h1>
<p className="text-lg text-slate-500 font-medium mt-1">
<h1 className="text-3xl font-bold tracking-tight text-slate-900">
Plan de Estudios 2024
</h1>
<p className="mt-1 text-lg font-medium text-slate-500">
Ingeniería en Sistemas Computacionales
</p>
</div>
{/* Badges de la derecha */}
<div className="flex gap-2">
<Badge variant="secondary" className="bg-blue-50 text-blue-700 border-blue-100 gap-1 px-3">
<Badge
variant="secondary"
className="gap-1 border-blue-100 bg-blue-50 px-3 text-blue-700"
>
<Rocket size={12} /> Ingeniería
</Badge>
<Badge variant="secondary" className="bg-orange-50 text-orange-700 border-orange-100 gap-1 px-3">
<Badge
variant="secondary"
className="gap-1 border-orange-100 bg-orange-50 px-3 text-orange-700"
>
<BookOpen size={12} /> Licenciatura
</Badge>
<Badge className="bg-teal-50 text-teal-700 border-teal-200 gap-1 px-3 hover:bg-teal-100">
<Badge className="gap-1 border-teal-200 bg-teal-50 px-3 text-teal-700 hover:bg-teal-100">
<CheckCircle2 size={12} /> En Revisión
</Badge>
</div>
</div>
{/* 3. Cards de Información (Nivel, Duración, etc.) */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
<InfoCard icon={<GraduationCap className="text-slate-400" />} label="Nivel" value="Superior" />
<InfoCard icon={<Clock className="text-slate-400" />} label="Duración" value="9 Semestres" />
<InfoCard icon={<Hash className="text-slate-400" />} label="Créditos" value="320" />
<InfoCard icon={<CalendarDays className="text-slate-400" />} label="Creación" value="14 ene 2024" />
<div className="grid grid-cols-2 gap-4 md:grid-cols-4">
<InfoCard
icon={<GraduationCap className="text-slate-400" />}
label="Nivel"
value="Superior"
/>
<InfoCard
icon={<Clock className="text-slate-400" />}
label="Duración"
value="9 Semestres"
/>
<InfoCard
icon={<Hash className="text-slate-400" />}
label="Créditos"
value="320"
/>
<InfoCard
icon={<CalendarDays className="text-slate-400" />}
label="Creación"
value="14 ene 2024"
/>
</div>
{/* 4. Navegación de Tabs */}
<div className="border-b overflow-x-auto scrollbar-hide">
<nav className="flex gap-8 min-w-max">
<Tab to="/planes/$planId/datos" params={{ planId }}>Datos Generales</Tab>
<Tab to="/planes/$planId/mapa" params={{ planId }}>Mapa Curricular</Tab>
<Tab to="/planes/$planId/materias" params={{ planId }}>Materias</Tab>
<Tab to="/planes/$planId/flujo" params={{ planId }}>Flujo y Estados</Tab>
<Tab to="/planes/$planId/iaplan" params={{ planId }}>IA del Plan</Tab>
<Tab to="/planes/$planId/documento" params={{ planId }}>Documento</Tab>
<Tab to="/planes/$planId/historial" params={{ planId }}>Historial</Tab>
<div className="scrollbar-hide overflow-x-auto border-b">
<nav className="flex min-w-max gap-8">
<Tab to="/planes/$planId/datos" params={{ planId }}>
Datos Generales
</Tab>
<Tab to="/planes/$planId/mapa" params={{ planId }}>
Mapa Curricular
</Tab>
<Tab to="/planes/$planId/materias" params={{ planId }}>
Materias
</Tab>
<Tab to="/planes/$planId/flujo" params={{ planId }}>
Flujo y Estados
</Tab>
<Tab to="/planes/$planId/iaplan" params={{ planId }}>
IA del Plan
</Tab>
<Tab to="/planes/$planId/documento" params={{ planId }}>
Documento
</Tab>
<Tab to="/planes/$planId/historial" params={{ planId }}>
Historial
</Tab>
</nav>
</div>
{/* 5. Contenido del Tab */}
<main className="pt-2 animate-in fade-in duration-500">
<main className="animate-in fade-in pt-2 duration-500">
<Outlet />
</main>
</div>
@@ -79,36 +126,43 @@ function RouteComponent() {
)
}
// Sub-componente para las tarjetas de información
function InfoCard({ icon, label, value }: { icon: React.ReactNode, label: string, value: string }) {
function InfoCard({
icon,
label,
value,
}: {
icon: React.ReactNode
label: string
value: string
}) {
return (
<div className="flex items-center gap-4 bg-slate-50/50 border border-slate-200/60 p-4 rounded-xl shadow-sm">
<div className="p-2 bg-white rounded-lg border shadow-sm">
{icon}
</div>
<div className="flex items-center gap-4 rounded-xl border border-slate-200/60 bg-slate-50/50 p-4 shadow-sm">
<div className="rounded-lg border bg-white p-2 shadow-sm">{icon}</div>
<div>
<p className="text-[10px] uppercase font-bold text-slate-400 tracking-wider leading-none mb-1">{label}</p>
<p className="mb-1 text-[10px] leading-none font-bold tracking-wider text-slate-400 uppercase">
{label}
</p>
<p className="text-sm font-semibold text-slate-700">{value}</p>
</div>
</div>
)
}
function Tab({
to,
params,
children
}: {
to: string;
params?: any;
children: React.ReactNode
}) {
function Tab({
to,
params,
children,
}: {
to: string
params?: any
children: React.ReactNode
}) {
return (
<Link
to={to}
params={params}
className="pb-3 text-sm font-medium text-slate-500 border-b-2 border-transparent hover:text-slate-800 transition-all"
className="border-b-2 border-transparent pb-3 text-sm font-medium text-slate-500 transition-all hover:text-slate-800"
activeProps={{
className: 'border-teal-600 text-teal-700 font-bold',
}}
@@ -116,4 +170,4 @@ function Tab({
{children}
</Link>
)
}
}

View File

@@ -1,146 +1,10 @@
import { createFileRoute } from '@tanstack/react-router'
import { Button } from '@/components/ui/button'
import { Card, CardContent } from '@/components/ui/card'
import {
BookOpen,
Sparkles,
FileText,
Library,
LayoutTemplate,
History,
ArrowRight,
GraduationCap,
} from 'lucide-react'
import { createFileRoute, redirect } from '@tanstack/react-router'
export const Route = createFileRoute(
'/planes/$planId/asignaturas/'
)({
component: MateriasLandingPage,
export const Route = createFileRoute('/planes/$planId/asignaturas/')({
beforeLoad: ({ params }) => {
throw redirect({
to: '/planes/$planId/materias',
params,
})
},
})
export default function MateriasLandingPage() {
return (
<div className="w-full">
{/* ================= HERO ================= */}
<section className="bg-gradient-to-b from-[#0b1d3a] to-[#0e2a5c] text-white">
<div className="max-w-7xl mx-auto px-6 py-28">
<div className="flex items-center gap-2 mb-6 text-sm text-blue-200">
<GraduationCap className="w-5 h-5 text-yellow-400" />
<span>SISTEMA DE GESTIÓN CURRICULAR</span>
</div>
<h1 className="text-5xl font-bold mb-6">
Universidad La Salle
</h1>
<p className="max-w-xl text-lg text-blue-100 mb-10">
Diseña, documenta y mejora programas de estudio con herramientas
de inteligencia artificial integradas y cumplimiento normativo SEP.
</p>
<Button
size="lg"
className="bg-yellow-400 text-black hover:bg-yellow-300 font-semibold"
>
Ver materia de ejemplo
<ArrowRight className="ml-2 h-4 w-4" />
</Button>
</div>
</section>
{/* ================= FEATURES ================= */}
<section className="bg-white py-24">
<div className="max-w-7xl mx-auto px-6">
<h2 className="text-center text-2xl font-semibold mb-14">
Características principales
</h2>
<div className="grid gap-8 md:grid-cols-3">
<FeatureCard
icon={<BookOpen />}
title="Gestión de Materias"
description="Edita datos generales, contenido temático y bibliografía con una interfaz intuitiva."
/>
<FeatureCard
icon={<Sparkles />}
title="IA Integrada"
description="Usa inteligencia artificial para mejorar objetivos, competencias y alinear con perfiles de egreso."
/>
<FeatureCard
icon={<FileText />}
title="Documentos SEP"
description="Genera automáticamente documentos oficiales para la Secretaría de Educación Pública."
/>
<FeatureCard
icon={<Library />}
title="Biblioteca Digital"
description="Busca y vincula recursos del repositorio de Biblioteca La Salle directamente."
/>
<FeatureCard
icon={<LayoutTemplate />}
title="Plantillas Flexibles"
description="Adapta la estructura de materias según plantillas SEP o institucionales."
/>
<FeatureCard
icon={<History />}
title="Historial Completo"
description="Rastrea todos los cambios con historial detallado por usuario y fecha."
/>
</div>
</div>
</section>
{/* ================= CTA ================= */}
<section className="bg-gray-50 py-20">
<div className="max-w-3xl mx-auto text-center px-6">
<h3 className="text-xl font-semibold mb-4">
Explora la vista de detalle de materia
</h3>
<p className="text-muted-foreground mb-8">
Navega por las diferentes pestañas para ver cómo funciona el sistema
de gestión curricular.
</p>
<Button size="lg" className="bg-[#0e2a5c] hover:bg-[#0b1d3a]">
Ir a Inteligencia Artificial Aplicada
<ArrowRight className="ml-2 h-4 w-4" />
</Button>
</div>
</section>
</div>
)
}
/* ================= FEATURE CARD ================= */
function FeatureCard({
icon,
title,
description,
}: {
icon: React.ReactNode
title: string
description: string
}) {
return (
<Card className="border border-gray-200 shadow-sm">
<CardContent className="p-6 space-y-4">
<div className="w-10 h-10 rounded-md bg-yellow-100 text-yellow-600 flex items-center justify-center">
{icon}
</div>
<h4 className="font-semibold">{title}</h4>
<p className="text-sm text-muted-foreground">
{description}
</p>
</CardContent>
</Card>
)
}

View File

@@ -0,0 +1,10 @@
import { createFileRoute, redirect } from '@tanstack/react-router'
export const Route = createFileRoute('/planes/$planId/')({
beforeLoad: ({ params }) => {
throw redirect({
to: '/planes/$planId/datos',
params,
})
},
})