6 Commits

Author SHA1 Message Date
9065899616 bugfix: el enlace a Datos generales aparecía como activo aunque no se estuviera en ese tab 2026-01-28 16:14:53 -06:00
9cad2a0f62 Merge branch 'fix/incidencias' into feat/not-found-pages 2026-01-28 14:25:54 -06:00
dc85e2c946 Redirección de plan de estudios
fix #22
2026-01-28 14:22:37 -06:00
4e00262ab0 Redirección de plan de estudios y arreglo de placeholders en datos
close #22:
Al darle clic a un plan te lleva al index de planes/$planId, el cual es ahora la tab de datos.
Al darle al enlace de volver al plan desde el detalle de la asignatura, ya te redirige a planes/$planId/materias.
Se cambió el estilo de los placeholders en la tab de datos del detalle de plan, y ahora solo se muestra el primer ejemplo.
2026-01-28 14:06:17 -06:00
35ea4caa39 Fallback elegante de vista no encontrada
close #44:
Se creó la NotFoundPage y se utiliza en __root con el notFoundComponent.
Se agregó la lógica del loader tanto de plan de estudios como de asignaturas.
Se agregó el NotFoundComponent para el detalle de plan de estudios y el de asignaturas
2026-01-28 12:58:50 -06:00
5224e632f8 Usar los titles de la definición de la estructura
fix #48
fix #49
2026-01-28 12:25:51 -06:00
14 changed files with 348 additions and 234 deletions

View File

@@ -87,7 +87,8 @@ export function IAMateriaTab({
const availableFields = useMemo(() => { const availableFields = useMemo(() => {
// Extraemos las claves directamente del objeto datosGenerales // Extraemos las claves directamente del objeto datosGenerales
// ["nombre", "descripcion", "perfil_de_egreso", "fines_de_aprendizaje_o_formacion"] // ["nombre", "descripcion", "perfil_de_egreso", "fines_de_aprendizaje_o_formacion"]
return Object.keys(datosGenerales).map((key) => { if (!datosGenerales.datos) return []
return Object.keys(datosGenerales.datos).map((key) => {
// Buscamos si existe un nombre amigable en la estructura de campos // Buscamos si existe un nombre amigable en la estructura de campos
const estructuraCampo = campos.find((c) => c.id === key) const estructuraCampo = campos.find((c) => c.id === key)
@@ -110,12 +111,15 @@ export function IAMateriaTab({
const state = routerState.location.state as any const state = routerState.location.state as any
if (state?.prefillCampo && availableFields.length > 0) { if (state?.prefillCampo && availableFields.length > 0) {
console.log(state?.prefillCampo)
console.log(availableFields)
const field = availableFields.find((f) => f.key === state.prefillCampo) const field = availableFields.find((f) => f.key === state.prefillCampo)
if (field && !selectedFields.find((sf) => sf.key === field.key)) { if (field && !selectedFields.find((sf) => sf.key === field.key)) {
setSelectedFields([field]) setSelectedFields([field])
// Sincronizamos el texto inicial con el campo pre-seleccionado // Sincronizamos el texto inicial con el campo pre-seleccionado
setInput(`Mejora el campo ${field.key}: `) setInput(`Mejora el campo ${field.label}: `)
} }
} }
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -139,37 +143,32 @@ export function IAMateriaTab({
setSelectedFields((prev) => { setSelectedFields((prev) => {
const isSelected = prev.find((f) => f.key === field.key) const isSelected = prev.find((f) => f.key === field.key)
// Si lo estamos seleccionando (no estaba antes) // 1. Si ya está seleccionado, lo quitamos (Toggle OFF)
if (!isSelected) { if (isSelected) {
// Actualizamos el texto del input: return prev.filter((f) => f.key !== field.key)
// Si termina en ":", lo reemplazamos por el key para que sea "Mejora perfil_de_egreso "
// Si no, simplemente lo añadimos al final.
setInput((prevText) => {
const [beforeColon, afterColon = ''] = prevText.split(':')
// Campos ya escritos después de :
const existingKeys = afterColon
.split(',')
.map((k) => k.trim())
.filter(Boolean)
// Si ya existe, no lo volvemos a agregar
if (existingKeys.includes(field.key)) {
return prevText
}
const updatedKeys = [...existingKeys, field.key].join(', ')
return `${beforeColon.trim()}: ${updatedKeys} `
})
return [field]
} }
// Si lo estamos deseleccionando, solo quitamos el tag // 2. Si no está, lo agregamos a la lista (Toggle ON)
return prev.filter((f) => f.key !== field.key) const newSelected = [...prev, field]
// 3. Actualizamos el texto del input para reflejar los títulos (labels)
setInput((prevText) => {
// Separamos lo que el usuario escribió antes del disparador ":"
// y lo que viene después (posibles keys/labels previos)
const parts = prevText.split(':')
const beforeColon = parts[0]
// Creamos un string con los labels de todos los campos seleccionados
const labelsPath = newSelected.map((f) => f.label).join(', ')
return `${beforeColon.trim()}: ${labelsPath} `
})
return newSelected
}) })
setShowSuggestions(false)
// Opcional: mantener abierto si quieres que el usuario elija varios seguidos
// setShowSuggestions(false)
} }
const buildPrompt = (userInput: string) => { const buildPrompt = (userInput: string) => {

View File

@@ -22,6 +22,12 @@ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { Separator } from '@/components/ui/separator' import { Separator } from '@/components/ui/separator'
import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs' import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs'
import { Textarea } from '@/components/ui/textarea' import { Textarea } from '@/components/ui/textarea'
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from '@/components/ui/tooltip'
import { useSubject } from '@/data/hooks/useSubjects' import { useSubject } from '@/data/hooks/useSubjects'
import { import {
mockMateria, mockMateria,
@@ -150,7 +156,7 @@ export default function MateriaDetailPage() {
/* ---------- sincronizar API ---------- */ /* ---------- sincronizar API ---------- */
useEffect(() => { useEffect(() => {
if (asignaturasApi?.datos) { if (asignaturasApi?.datos) {
setDatosGenerales(asignaturasApi.datos) setDatosGenerales(asignaturasApi)
} }
}, [asignaturasApi]) }, [asignaturasApi])
@@ -211,7 +217,7 @@ export default function MateriaDetailPage() {
<section className="bg-gradient-to-b from-[#0b1d3a] to-[#0e2a5c] text-white"> <section className="bg-gradient-to-b from-[#0b1d3a] to-[#0e2a5c] text-white">
<div className="mx-auto max-w-7xl px-6 py-10"> <div className="mx-auto max-w-7xl px-6 py-10">
<Link <Link
to="/planes/$planId" to="/planes/$planId/asignaturas"
params={{ planId }} params={{ planId }}
className="mb-4 flex items-center gap-2 text-sm text-blue-200 hover:text-white" className="mb-4 flex items-center gap-2 text-sm text-blue-200 hover:text-white"
> >
@@ -392,6 +398,13 @@ function DatosGenerales({
const formatTitle = (key: string): string => const formatTitle = (key: string): string =>
key.replace(/_/g, ' ').replace(/\b\w/g, (l: string) => l.toUpperCase()) key.replace(/_/g, ' ').replace(/\b\w/g, (l: string) => l.toUpperCase())
// 1. Extraemos la definición de la estructura (los metadatos)
const structureProps =
data?.estructuras_asignatura?.definicion?.properties || {}
// 2. Extraemos los valores reales (el contenido redactado)
const valoresActuales = data?.datos || {}
return ( return (
<div className="animate-in fade-in mx-auto max-w-7xl space-y-8 px-4 py-8 duration-500"> <div className="animate-in fade-in mx-auto max-w-7xl space-y-8 px-4 py-8 duration-500">
{/* Encabezado de la Sección */} {/* Encabezado de la Sección */}
@@ -413,19 +426,47 @@ function DatosGenerales({
{isLoading && <p>Cargando información...</p>} {isLoading && <p>Cargando información...</p>}
{!isLoading && {!isLoading &&
Object.entries(data).map(([key, value]) => ( Object.entries(structureProps).map(
<InfoCard ([key, config]: [string, any]) => {
asignaturaId={asignaturaId} // 1. METADATOS (Vienen de structureProps -> config)
key={key} const cardTitle = config.title || key
clave={key} const description = config.description || ''
title={formatTitle(key)}
initialContent={value} // Obtenemos el placeholder del arreglo 'examples' de la estructura
onEnhanceAI={(contenido) => { const placeholder =
console.log('Llevar a IA:', contenido) config.examples && config.examples.length > 0
// Aquí tu lógica: setPestañaActiva('mejorar-con-ia'); ? config.examples[0]
}} : ''
/>
))} // 2. CONTENIDO REAL (Viene de data.datos -> valoresActuales)
// El problema: Si 'description' en 'datos' es igual a la de la 'estructura',
// el usuario aún no ha redactado nada real.
const valActual = valoresActuales[key]
// Lógica para determinar si mostrar el contenido o dejarlo vacío (para que salga el placeholder)
// Si el contenido en 'datos' es idéntico a la instrucción de la 'estructura',
// asumimos que no hay contenido real todavía.
const isContentEmpty =
!valActual?.description ||
valActual.description === config.description
const currentContent = valActual.description ?? ''
return (
<InfoCard
asignaturaId={asignaturaId}
key={key}
clave={key}
title={cardTitle}
initialContent={currentContent} // Si es igual a la descripción de la SEP, pasamos vacío
placeholder={placeholder} // Aquí irá "Primer semestre", "MAT-101", etc.
description={description} // El texto largo de "Indicar el ciclo..."
onEnhanceAI={(contenido) => console.log(contenido)}
/>
)
},
)}
</div> </div>
{/* Columna Lateral (Información Secundaria) */} {/* Columna Lateral (Información Secundaria) */}
@@ -469,11 +510,14 @@ function DatosGenerales({
interface InfoCardProps { interface InfoCardProps {
asignaturaId?: string asignaturaId?: string
clave: string clave?: string
title: string title: string
initialContent: any initialContent: any
placeholder?: string
description?: string
required?: boolean // Nueva prop para el asterisco
type?: 'text' | 'requirements' | 'evaluation' type?: 'text' | 'requirements' | 'evaluation'
onEnhanceAI?: (content: any) => void // Nueva prop para la acción de IA onEnhanceAI?: (content: any) => void
} }
function InfoCard({ function InfoCard({
@@ -481,76 +525,111 @@ function InfoCard({
clave, clave,
title, title,
initialContent, initialContent,
placeholder,
description,
required,
type = 'text', type = 'text',
onEnhanceAI,
}: InfoCardProps) { }: InfoCardProps) {
const [isEditing, setIsEditing] = useState(false) const [isEditing, setIsEditing] = useState(false)
const [data, setData] = useState(initialContent) const [data, setData] = useState(initialContent)
const [tempText, setTempText] = useState( const [tempText, setTempText] = useState(initialContent)
type === 'text' ? initialContent : JSON.stringify(initialContent, null, 2),
)
const navigate = useNavigate() const navigate = useNavigate()
useEffect(() => {
setData(initialContent)
setTempText(initialContent)
}, [initialContent])
const handleSave = () => { const handleSave = () => {
setData(tempText) setData(tempText)
setIsEditing(false) setIsEditing(false)
// Aquí iría tu lógica de guardado a la DB
} }
const handleIARequest = (data) => {
console.log(data) const handleIARequest = (campoClave: string) => {
console.log(asignaturaId) console.log(placeholder)
navigate({ navigate({
to: '/planes/$planId/asignaturas/$asignaturaId', to: '/planes/$planId/asignaturas/$asignaturaId',
params: { params: { asignaturaId: asignaturaId! },
asignaturaId: asignaturaId,
},
state: { state: {
activeTab: 'ia', activeTab: 'ia',
prefillCampo: data, prefillCampo: campoClave,
prefillContenido: data, // el contenido actual del card prefillContenido: data,
} as any, } as any,
}) })
} }
return ( return (
<Card className="transition-all hover:border-slate-300"> <Card className="overflow-hidden transition-all hover:border-slate-300">
<CardHeader className="flex flex-row items-start justify-between space-y-0 pb-3"> <TooltipProvider>
<CardTitle className="text-sm font-bold text-slate-700"> <CardHeader className="border-b bg-slate-50/50 px-5 py-3">
{title} <div className="flex items-center justify-between">
</CardTitle> <div className="flex items-center gap-2">
<Tooltip>
<TooltipTrigger asChild>
<CardTitle className="cursor-help text-sm font-bold text-slate-700">
{title}
</CardTitle>
</TooltipTrigger>
<TooltipContent side="top" className="max-w-xs text-xs">
{description || 'Información del campo'}
</TooltipContent>
</Tooltip>
{!isEditing && ( {required && (
<div className="flex gap-1"> <span
{/* NUEVO: Botón de Mejorar con IA */} className="text-sm font-bold text-red-500"
<Button title="Requerido"
variant="ghost" >
size="icon" *
className="h-8 w-8 text-blue-500 hover:bg-blue-50 hover:text-blue-600" </span>
onClick={() => handleIARequest(clave)} // Enviamos la data actual a la IA )}
title="Mejorar con IA" </div>
>
<Sparkles className="h-4 w-4" />
</Button>
{/* Botón de Editar original */} {!isEditing && (
<Button <div className="flex gap-1">
variant="ghost" <Tooltip>
size="icon" <TooltipTrigger asChild>
className="h-8 w-8 text-slate-400" <Button
onClick={() => setIsEditing(true)} variant="ghost"
> size="icon"
<Pencil className="h-3 w-3" /> className="h-8 w-8 text-blue-500 hover:bg-blue-100"
</Button> onClick={() => handleIARequest(clave)}
>
<Sparkles className="h-4 w-4" />
</Button>
</TooltipTrigger>
<TooltipContent>Mejorar con IA</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="icon"
className="h-8 w-8 text-slate-400"
onClick={() => setIsEditing(true)}
>
<Pencil className="h-3 w-3" />
</Button>
</TooltipTrigger>
<TooltipContent>Editar campo</TooltipContent>
</Tooltip>
</div>
)}
</div> </div>
)} </CardHeader>
</CardHeader> </TooltipProvider>
<CardContent> <CardContent className="pt-4">
{isEditing ? ( {isEditing ? (
<div className="space-y-3"> <div className="space-y-3">
<Textarea <Textarea
value={tempText} value={tempText}
placeholder={placeholder}
onChange={(e) => setTempText(e.target.value)} onChange={(e) => setTempText(e.target.value)}
className="min-h-[100px] text-xs" className="min-h-[120px] text-sm leading-relaxed"
/> />
<div className="flex justify-end gap-2"> <div className="flex justify-end gap-2">
<Button <Button
@@ -570,10 +649,17 @@ function InfoCard({
</div> </div>
</div> </div>
) : ( ) : (
<div className="text-sm"> <div className="text-sm leading-relaxed text-slate-600">
{type === 'text' &&
(data ? (
<p className="whitespace-pre-wrap">{data}</p>
) : (
<p className="text-slate-400 italic">
Sin información. Ejemplo: {placeholder}
</p>
))}
{type === 'requirements' && <RequirementsView items={data} />} {type === 'requirements' && <RequirementsView items={data} />}
{type === 'evaluation' && <EvaluationView items={data} />} {type === 'evaluation' && <EvaluationView items={data} />}
{type === 'text' && <p className="text-slate-600">{data}</p>}
</div> </div>
)} )}
</CardContent> </CardContent>

View File

@@ -118,7 +118,10 @@ export function WizardControls({
} }
return ( return (
<div className="flex items-center justify-between"> <div className="flex grow items-center justify-between">
<Button variant="secondary" onClick={onPrev} disabled={disablePrev}>
Anterior
</Button>
<div className="flex-1"> <div className="flex-1">
{errorMessage && ( {errorMessage && (
<span className="text-destructive text-sm font-medium"> <span className="text-destructive text-sm font-medium">
@@ -126,20 +129,15 @@ export function WizardControls({
</span> </span>
)} )}
</div> </div>
<div className="flex gap-4"> {isLastStep ? (
<Button variant="secondary" onClick={onPrev} disabled={disablePrev}> <Button onClick={handleCreate} disabled={disableCreate}>
Anterior Crear plan
</Button> </Button>
{isLastStep ? ( ) : (
<Button onClick={handleCreate} disabled={disableCreate}> <Button onClick={onNext} disabled={disableNext}>
Crear plan Siguiente
</Button> </Button>
) : ( )}
<Button onClick={onNext} disabled={disableNext}>
Siguiente
</Button>
)}
</div>
</div> </div>
) )
} }

View File

@@ -0,0 +1,44 @@
import { Link, useRouter } from '@tanstack/react-router'
import { FileQuestion, Home, ArrowLeft } from 'lucide-react'
import { Button } from './button'
interface NotFoundPageProps {
title?: string
message?: string
children?: React.ReactNode
}
export function NotFoundPage({
title = 'Página no encontrada',
message = 'Lo sentimos, no pudimos encontrar lo que buscabas. Es posible que la página haya sido movida o eliminada.',
children,
}: NotFoundPageProps) {
const router = useRouter()
return (
<div className="flex min-h-[60vh] flex-col items-center justify-center p-4 text-center">
<div className="bg-muted mb-6 rounded-full p-6">
<FileQuestion className="text-muted-foreground h-12 w-12" />
</div>
<h1 className="mb-2 text-3xl font-bold tracking-tight">{title}</h1>
<p className="text-muted-foreground mb-8 max-w-125">{message}</p>
<div className="flex flex-col gap-2 sm:flex-row">
<Button variant="outline" onClick={() => router.history.back()}>
<ArrowLeft className="mr-2 h-4 w-4" />
Regresar
</Button>
<Button asChild>
<Link to="/">
<Home className="mr-2 h-4 w-4" />
Ir al inicio
</Link>
</Button>
{children}
</div>
</div>
)
}

View File

@@ -123,6 +123,8 @@ export async function plans_list(
} }
export async function plans_get(planId: UUID): Promise<PlanEstudio> { export async function plans_get(planId: UUID): Promise<PlanEstudio> {
console.log('plans_get')
const supabase = supabaseBrowser() const supabase = supabaseBrowser()
const { data, error } = await supabase const { data, error } = await supabase
@@ -350,7 +352,7 @@ export async function plans_update_fields(
patch: PlansUpdateFieldsPatch, patch: PlansUpdateFieldsPatch,
): Promise<PlanEstudio> { ): Promise<PlanEstudio> {
const supabase = supabaseBrowser() const supabase = supabaseBrowser()
const { data, error } = await supabase const { data, error } = await supabase
.from('planes_estudio') .from('planes_estudio')
.update(patch) .update(patch)

View File

@@ -53,7 +53,10 @@ export function usePlanes(filters: PlanListFilters) {
export function usePlan(planId: UUID | null | undefined) { export function usePlan(planId: UUID | null | undefined) {
return useQuery({ return useQuery({
queryKey: planId ? qk.plan(planId) : ['planes', 'detail', null], queryKey: planId ? qk.plan(planId) : ['planes', 'detail', null],
queryFn: () => plans_get(planId as UUID), queryFn: () => {
console.log('usePlan')
return plans_get(planId as UUID)
},
enabled: Boolean(planId), enabled: Boolean(planId),
}) })
} }

View File

@@ -14,18 +14,16 @@ import { Route as DashboardRouteImport } from './routes/dashboard'
import { Route as IndexRouteImport } from './routes/index' import { Route as IndexRouteImport } from './routes/index'
import { Route as DemoTanstackQueryRouteImport } from './routes/demo/tanstack-query' import { Route as DemoTanstackQueryRouteImport } from './routes/demo/tanstack-query'
import { Route as PlanesListaRouteRouteImport } from './routes/planes/_lista/route' import { Route as PlanesListaRouteRouteImport } from './routes/planes/_lista/route'
import { Route as PlanesPlanIdIndexRouteImport } from './routes/planes/$planId/index'
import { Route as PlanesListaNuevoRouteImport } from './routes/planes/_lista/nuevo' import { Route as PlanesListaNuevoRouteImport } from './routes/planes/_lista/nuevo'
import { Route as PlanesPlanIdAsignaturasRouteRouteImport } from './routes/planes/$planId/asignaturas/route' import { Route as PlanesPlanIdAsignaturasRouteRouteImport } from './routes/planes/$planId/asignaturas/route'
import { Route as PlanesPlanIdDetalleRouteRouteImport } from './routes/planes/$planId/_detalle/route' import { Route as PlanesPlanIdDetalleRouteRouteImport } from './routes/planes/$planId/_detalle/route'
import { Route as PlanesPlanIdAsignaturasIndexRouteImport } from './routes/planes/$planId/asignaturas/index' import { Route as PlanesPlanIdDetalleIndexRouteImport } from './routes/planes/$planId/_detalle/index'
import { Route as PlanesPlanIdDetalleMateriasRouteImport } from './routes/planes/$planId/_detalle/materias'
import { Route as PlanesPlanIdDetalleMapaRouteImport } from './routes/planes/$planId/_detalle/mapa' import { Route as PlanesPlanIdDetalleMapaRouteImport } from './routes/planes/$planId/_detalle/mapa'
import { Route as PlanesPlanIdDetalleIaplanRouteImport } from './routes/planes/$planId/_detalle/iaplan' import { Route as PlanesPlanIdDetalleIaplanRouteImport } from './routes/planes/$planId/_detalle/iaplan'
import { Route as PlanesPlanIdDetalleHistorialRouteImport } from './routes/planes/$planId/_detalle/historial' import { Route as PlanesPlanIdDetalleHistorialRouteImport } from './routes/planes/$planId/_detalle/historial'
import { Route as PlanesPlanIdDetalleFlujoRouteImport } from './routes/planes/$planId/_detalle/flujo' import { Route as PlanesPlanIdDetalleFlujoRouteImport } from './routes/planes/$planId/_detalle/flujo'
import { Route as PlanesPlanIdDetalleDocumentoRouteImport } from './routes/planes/$planId/_detalle/documento' import { Route as PlanesPlanIdDetalleDocumentoRouteImport } from './routes/planes/$planId/_detalle/documento'
import { Route as PlanesPlanIdDetalleDatosRouteImport } from './routes/planes/$planId/_detalle/datos' import { Route as PlanesPlanIdDetalleAsignaturasRouteImport } from './routes/planes/$planId/_detalle/asignaturas'
import { Route as PlanesPlanIdAsignaturasListaRouteRouteImport } from './routes/planes/$planId/asignaturas/_lista/route' import { Route as PlanesPlanIdAsignaturasListaRouteRouteImport } from './routes/planes/$planId/asignaturas/_lista/route'
import { Route as PlanesPlanIdAsignaturasAsignaturaIdRouteRouteImport } from './routes/planes/$planId/asignaturas/$asignaturaId/route' import { Route as PlanesPlanIdAsignaturasAsignaturaIdRouteRouteImport } from './routes/planes/$planId/asignaturas/$asignaturaId/route'
import { Route as PlanesPlanIdAsignaturasListaNuevaRouteImport } from './routes/planes/$planId/asignaturas/_lista/nueva' import { Route as PlanesPlanIdAsignaturasListaNuevaRouteImport } from './routes/planes/$planId/asignaturas/_lista/nueva'
@@ -55,11 +53,6 @@ const PlanesListaRouteRoute = PlanesListaRouteRouteImport.update({
path: '/planes', path: '/planes',
getParentRoute: () => rootRouteImport, getParentRoute: () => rootRouteImport,
} as any) } as any)
const PlanesPlanIdIndexRoute = PlanesPlanIdIndexRouteImport.update({
id: '/planes/$planId/',
path: '/planes/$planId/',
getParentRoute: () => rootRouteImport,
} as any)
const PlanesListaNuevoRoute = PlanesListaNuevoRouteImport.update({ const PlanesListaNuevoRoute = PlanesListaNuevoRouteImport.update({
id: '/nuevo', id: '/nuevo',
path: '/nuevo', path: '/nuevo',
@@ -77,16 +70,10 @@ const PlanesPlanIdDetalleRouteRoute =
path: '/planes/$planId', path: '/planes/$planId',
getParentRoute: () => rootRouteImport, getParentRoute: () => rootRouteImport,
} as any) } as any)
const PlanesPlanIdAsignaturasIndexRoute = const PlanesPlanIdDetalleIndexRoute =
PlanesPlanIdAsignaturasIndexRouteImport.update({ PlanesPlanIdDetalleIndexRouteImport.update({
id: '/', id: '/',
path: '/', path: '/',
getParentRoute: () => PlanesPlanIdAsignaturasRouteRoute,
} as any)
const PlanesPlanIdDetalleMateriasRoute =
PlanesPlanIdDetalleMateriasRouteImport.update({
id: '/materias',
path: '/materias',
getParentRoute: () => PlanesPlanIdDetalleRouteRoute, getParentRoute: () => PlanesPlanIdDetalleRouteRoute,
} as any) } as any)
const PlanesPlanIdDetalleMapaRoute = PlanesPlanIdDetalleMapaRouteImport.update({ const PlanesPlanIdDetalleMapaRoute = PlanesPlanIdDetalleMapaRouteImport.update({
@@ -118,10 +105,10 @@ const PlanesPlanIdDetalleDocumentoRoute =
path: '/documento', path: '/documento',
getParentRoute: () => PlanesPlanIdDetalleRouteRoute, getParentRoute: () => PlanesPlanIdDetalleRouteRoute,
} as any) } as any)
const PlanesPlanIdDetalleDatosRoute = const PlanesPlanIdDetalleAsignaturasRoute =
PlanesPlanIdDetalleDatosRouteImport.update({ PlanesPlanIdDetalleAsignaturasRouteImport.update({
id: '/datos', id: '/asignaturas',
path: '/datos', path: '/asignaturas',
getParentRoute: () => PlanesPlanIdDetalleRouteRoute, getParentRoute: () => PlanesPlanIdDetalleRouteRoute,
} as any) } as any)
const PlanesPlanIdAsignaturasListaRouteRoute = const PlanesPlanIdAsignaturasListaRouteRoute =
@@ -149,18 +136,15 @@ export interface FileRoutesByFullPath {
'/planes': typeof PlanesListaRouteRouteWithChildren '/planes': typeof PlanesListaRouteRouteWithChildren
'/demo/tanstack-query': typeof DemoTanstackQueryRoute '/demo/tanstack-query': typeof DemoTanstackQueryRoute
'/planes/$planId': typeof PlanesPlanIdDetalleRouteRouteWithChildren '/planes/$planId': typeof PlanesPlanIdDetalleRouteRouteWithChildren
'/planes/$planId/asignaturas': typeof PlanesPlanIdAsignaturasListaRouteRouteWithChildren '/planes/$planId/asignaturas': typeof PlanesPlanIdDetalleAsignaturasRoute
'/planes/nuevo': typeof PlanesListaNuevoRoute '/planes/nuevo': typeof PlanesListaNuevoRoute
'/planes/$planId/': typeof PlanesPlanIdIndexRoute
'/planes/$planId/asignaturas/$asignaturaId': typeof PlanesPlanIdAsignaturasAsignaturaIdRouteRoute '/planes/$planId/asignaturas/$asignaturaId': typeof PlanesPlanIdAsignaturasAsignaturaIdRouteRoute
'/planes/$planId/datos': typeof PlanesPlanIdDetalleDatosRoute
'/planes/$planId/documento': typeof PlanesPlanIdDetalleDocumentoRoute '/planes/$planId/documento': typeof PlanesPlanIdDetalleDocumentoRoute
'/planes/$planId/flujo': typeof PlanesPlanIdDetalleFlujoRoute '/planes/$planId/flujo': typeof PlanesPlanIdDetalleFlujoRoute
'/planes/$planId/historial': typeof PlanesPlanIdDetalleHistorialRoute '/planes/$planId/historial': typeof PlanesPlanIdDetalleHistorialRoute
'/planes/$planId/iaplan': typeof PlanesPlanIdDetalleIaplanRoute '/planes/$planId/iaplan': typeof PlanesPlanIdDetalleIaplanRoute
'/planes/$planId/mapa': typeof PlanesPlanIdDetalleMapaRoute '/planes/$planId/mapa': typeof PlanesPlanIdDetalleMapaRoute
'/planes/$planId/materias': typeof PlanesPlanIdDetalleMateriasRoute '/planes/$planId/': typeof PlanesPlanIdDetalleIndexRoute
'/planes/$planId/asignaturas/': typeof PlanesPlanIdAsignaturasIndexRoute
'/planes/$planId/asignaturas/nueva': typeof PlanesPlanIdAsignaturasListaNuevaRoute '/planes/$planId/asignaturas/nueva': typeof PlanesPlanIdAsignaturasListaNuevaRoute
} }
export interface FileRoutesByTo { export interface FileRoutesByTo {
@@ -169,17 +153,15 @@ export interface FileRoutesByTo {
'/login': typeof LoginRoute '/login': typeof LoginRoute
'/planes': typeof PlanesListaRouteRouteWithChildren '/planes': typeof PlanesListaRouteRouteWithChildren
'/demo/tanstack-query': typeof DemoTanstackQueryRoute '/demo/tanstack-query': typeof DemoTanstackQueryRoute
'/planes/$planId': typeof PlanesPlanIdIndexRoute '/planes/$planId/asignaturas': typeof PlanesPlanIdDetalleAsignaturasRoute
'/planes/nuevo': typeof PlanesListaNuevoRoute '/planes/nuevo': typeof PlanesListaNuevoRoute
'/planes/$planId/asignaturas/$asignaturaId': typeof PlanesPlanIdAsignaturasAsignaturaIdRouteRoute '/planes/$planId/asignaturas/$asignaturaId': typeof PlanesPlanIdAsignaturasAsignaturaIdRouteRoute
'/planes/$planId/asignaturas': typeof PlanesPlanIdAsignaturasIndexRoute
'/planes/$planId/datos': typeof PlanesPlanIdDetalleDatosRoute
'/planes/$planId/documento': typeof PlanesPlanIdDetalleDocumentoRoute '/planes/$planId/documento': typeof PlanesPlanIdDetalleDocumentoRoute
'/planes/$planId/flujo': typeof PlanesPlanIdDetalleFlujoRoute '/planes/$planId/flujo': typeof PlanesPlanIdDetalleFlujoRoute
'/planes/$planId/historial': typeof PlanesPlanIdDetalleHistorialRoute '/planes/$planId/historial': typeof PlanesPlanIdDetalleHistorialRoute
'/planes/$planId/iaplan': typeof PlanesPlanIdDetalleIaplanRoute '/planes/$planId/iaplan': typeof PlanesPlanIdDetalleIaplanRoute
'/planes/$planId/mapa': typeof PlanesPlanIdDetalleMapaRoute '/planes/$planId/mapa': typeof PlanesPlanIdDetalleMapaRoute
'/planes/$planId/materias': typeof PlanesPlanIdDetalleMateriasRoute '/planes/$planId': typeof PlanesPlanIdDetalleIndexRoute
'/planes/$planId/asignaturas/nueva': typeof PlanesPlanIdAsignaturasListaNuevaRoute '/planes/$planId/asignaturas/nueva': typeof PlanesPlanIdAsignaturasListaNuevaRoute
} }
export interface FileRoutesById { export interface FileRoutesById {
@@ -192,17 +174,15 @@ export interface FileRoutesById {
'/planes/$planId/_detalle': typeof PlanesPlanIdDetalleRouteRouteWithChildren '/planes/$planId/_detalle': typeof PlanesPlanIdDetalleRouteRouteWithChildren
'/planes/$planId/asignaturas': typeof PlanesPlanIdAsignaturasRouteRouteWithChildren '/planes/$planId/asignaturas': typeof PlanesPlanIdAsignaturasRouteRouteWithChildren
'/planes/_lista/nuevo': typeof PlanesListaNuevoRoute '/planes/_lista/nuevo': typeof PlanesListaNuevoRoute
'/planes/$planId/': typeof PlanesPlanIdIndexRoute
'/planes/$planId/asignaturas/$asignaturaId': typeof PlanesPlanIdAsignaturasAsignaturaIdRouteRoute '/planes/$planId/asignaturas/$asignaturaId': typeof PlanesPlanIdAsignaturasAsignaturaIdRouteRoute
'/planes/$planId/asignaturas/_lista': typeof PlanesPlanIdAsignaturasListaRouteRouteWithChildren '/planes/$planId/asignaturas/_lista': typeof PlanesPlanIdAsignaturasListaRouteRouteWithChildren
'/planes/$planId/_detalle/datos': typeof PlanesPlanIdDetalleDatosRoute '/planes/$planId/_detalle/asignaturas': typeof PlanesPlanIdDetalleAsignaturasRoute
'/planes/$planId/_detalle/documento': typeof PlanesPlanIdDetalleDocumentoRoute '/planes/$planId/_detalle/documento': typeof PlanesPlanIdDetalleDocumentoRoute
'/planes/$planId/_detalle/flujo': typeof PlanesPlanIdDetalleFlujoRoute '/planes/$planId/_detalle/flujo': typeof PlanesPlanIdDetalleFlujoRoute
'/planes/$planId/_detalle/historial': typeof PlanesPlanIdDetalleHistorialRoute '/planes/$planId/_detalle/historial': typeof PlanesPlanIdDetalleHistorialRoute
'/planes/$planId/_detalle/iaplan': typeof PlanesPlanIdDetalleIaplanRoute '/planes/$planId/_detalle/iaplan': typeof PlanesPlanIdDetalleIaplanRoute
'/planes/$planId/_detalle/mapa': typeof PlanesPlanIdDetalleMapaRoute '/planes/$planId/_detalle/mapa': typeof PlanesPlanIdDetalleMapaRoute
'/planes/$planId/_detalle/materias': typeof PlanesPlanIdDetalleMateriasRoute '/planes/$planId/_detalle/': typeof PlanesPlanIdDetalleIndexRoute
'/planes/$planId/asignaturas/': typeof PlanesPlanIdAsignaturasIndexRoute
'/planes/$planId/asignaturas/_lista/nueva': typeof PlanesPlanIdAsignaturasListaNuevaRoute '/planes/$planId/asignaturas/_lista/nueva': typeof PlanesPlanIdAsignaturasListaNuevaRoute
} }
export interface FileRouteTypes { export interface FileRouteTypes {
@@ -216,16 +196,13 @@ export interface FileRouteTypes {
| '/planes/$planId' | '/planes/$planId'
| '/planes/$planId/asignaturas' | '/planes/$planId/asignaturas'
| '/planes/nuevo' | '/planes/nuevo'
| '/planes/$planId/'
| '/planes/$planId/asignaturas/$asignaturaId' | '/planes/$planId/asignaturas/$asignaturaId'
| '/planes/$planId/datos'
| '/planes/$planId/documento' | '/planes/$planId/documento'
| '/planes/$planId/flujo' | '/planes/$planId/flujo'
| '/planes/$planId/historial' | '/planes/$planId/historial'
| '/planes/$planId/iaplan' | '/planes/$planId/iaplan'
| '/planes/$planId/mapa' | '/planes/$planId/mapa'
| '/planes/$planId/materias' | '/planes/$planId/'
| '/planes/$planId/asignaturas/'
| '/planes/$planId/asignaturas/nueva' | '/planes/$planId/asignaturas/nueva'
fileRoutesByTo: FileRoutesByTo fileRoutesByTo: FileRoutesByTo
to: to:
@@ -234,17 +211,15 @@ export interface FileRouteTypes {
| '/login' | '/login'
| '/planes' | '/planes'
| '/demo/tanstack-query' | '/demo/tanstack-query'
| '/planes/$planId' | '/planes/$planId/asignaturas'
| '/planes/nuevo' | '/planes/nuevo'
| '/planes/$planId/asignaturas/$asignaturaId' | '/planes/$planId/asignaturas/$asignaturaId'
| '/planes/$planId/asignaturas'
| '/planes/$planId/datos'
| '/planes/$planId/documento' | '/planes/$planId/documento'
| '/planes/$planId/flujo' | '/planes/$planId/flujo'
| '/planes/$planId/historial' | '/planes/$planId/historial'
| '/planes/$planId/iaplan' | '/planes/$planId/iaplan'
| '/planes/$planId/mapa' | '/planes/$planId/mapa'
| '/planes/$planId/materias' | '/planes/$planId'
| '/planes/$planId/asignaturas/nueva' | '/planes/$planId/asignaturas/nueva'
id: id:
| '__root__' | '__root__'
@@ -256,17 +231,15 @@ export interface FileRouteTypes {
| '/planes/$planId/_detalle' | '/planes/$planId/_detalle'
| '/planes/$planId/asignaturas' | '/planes/$planId/asignaturas'
| '/planes/_lista/nuevo' | '/planes/_lista/nuevo'
| '/planes/$planId/'
| '/planes/$planId/asignaturas/$asignaturaId' | '/planes/$planId/asignaturas/$asignaturaId'
| '/planes/$planId/asignaturas/_lista' | '/planes/$planId/asignaturas/_lista'
| '/planes/$planId/_detalle/datos' | '/planes/$planId/_detalle/asignaturas'
| '/planes/$planId/_detalle/documento' | '/planes/$planId/_detalle/documento'
| '/planes/$planId/_detalle/flujo' | '/planes/$planId/_detalle/flujo'
| '/planes/$planId/_detalle/historial' | '/planes/$planId/_detalle/historial'
| '/planes/$planId/_detalle/iaplan' | '/planes/$planId/_detalle/iaplan'
| '/planes/$planId/_detalle/mapa' | '/planes/$planId/_detalle/mapa'
| '/planes/$planId/_detalle/materias' | '/planes/$planId/_detalle/'
| '/planes/$planId/asignaturas/'
| '/planes/$planId/asignaturas/_lista/nueva' | '/planes/$planId/asignaturas/_lista/nueva'
fileRoutesById: FileRoutesById fileRoutesById: FileRoutesById
} }
@@ -278,7 +251,6 @@ export interface RootRouteChildren {
DemoTanstackQueryRoute: typeof DemoTanstackQueryRoute DemoTanstackQueryRoute: typeof DemoTanstackQueryRoute
PlanesPlanIdDetalleRouteRoute: typeof PlanesPlanIdDetalleRouteRouteWithChildren PlanesPlanIdDetalleRouteRoute: typeof PlanesPlanIdDetalleRouteRouteWithChildren
PlanesPlanIdAsignaturasRouteRoute: typeof PlanesPlanIdAsignaturasRouteRouteWithChildren PlanesPlanIdAsignaturasRouteRoute: typeof PlanesPlanIdAsignaturasRouteRouteWithChildren
PlanesPlanIdIndexRoute: typeof PlanesPlanIdIndexRoute
} }
declare module '@tanstack/react-router' { declare module '@tanstack/react-router' {
@@ -318,25 +290,6 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof PlanesListaRouteRouteImport preLoaderRoute: typeof PlanesListaRouteRouteImport
parentRoute: typeof rootRouteImport parentRoute: typeof rootRouteImport
} }
'/planes/$planId/': {
id: '/planes/$planId/'
path: '/planes/$planId'
<<<<<<< HEAD
fullPath: '/planes/$planId/'
=======
<<<<<<< HEAD
<<<<<<< HEAD
fullPath: '/planes/$planId/'
=======
fullPath: '/planes/$planId'
>>>>>>> 4950f7efbf664bbd31ac8a673fe594af5baf07f6
=======
fullPath: '/planes/$planId/'
>>>>>>> cbe4e54 (Se cierran incidencias #10, #21, #24, #25; se añade generación manual de planes)
>>>>>>> 9584cd0c048cf1f4477a4db80947de38e6c75632
preLoaderRoute: typeof PlanesPlanIdIndexRouteImport
parentRoute: typeof rootRouteImport
}
'/planes/_lista/nuevo': { '/planes/_lista/nuevo': {
id: '/planes/_lista/nuevo' id: '/planes/_lista/nuevo'
path: '/nuevo' path: '/nuevo'
@@ -358,18 +311,11 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof PlanesPlanIdDetalleRouteRouteImport preLoaderRoute: typeof PlanesPlanIdDetalleRouteRouteImport
parentRoute: typeof rootRouteImport parentRoute: typeof rootRouteImport
} }
'/planes/$planId/asignaturas/': { '/planes/$planId/_detalle/': {
id: '/planes/$planId/asignaturas/' id: '/planes/$planId/_detalle/'
path: '/' path: '/'
fullPath: '/planes/$planId/asignaturas/' fullPath: '/planes/$planId/'
preLoaderRoute: typeof PlanesPlanIdAsignaturasIndexRouteImport preLoaderRoute: typeof PlanesPlanIdDetalleIndexRouteImport
parentRoute: typeof PlanesPlanIdAsignaturasRouteRoute
}
'/planes/$planId/_detalle/materias': {
id: '/planes/$planId/_detalle/materias'
path: '/materias'
fullPath: '/planes/$planId/materias'
preLoaderRoute: typeof PlanesPlanIdDetalleMateriasRouteImport
parentRoute: typeof PlanesPlanIdDetalleRouteRoute parentRoute: typeof PlanesPlanIdDetalleRouteRoute
} }
'/planes/$planId/_detalle/mapa': { '/planes/$planId/_detalle/mapa': {
@@ -407,11 +353,11 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof PlanesPlanIdDetalleDocumentoRouteImport preLoaderRoute: typeof PlanesPlanIdDetalleDocumentoRouteImport
parentRoute: typeof PlanesPlanIdDetalleRouteRoute parentRoute: typeof PlanesPlanIdDetalleRouteRoute
} }
'/planes/$planId/_detalle/datos': { '/planes/$planId/_detalle/asignaturas': {
id: '/planes/$planId/_detalle/datos' id: '/planes/$planId/_detalle/asignaturas'
path: '/datos' path: '/asignaturas'
fullPath: '/planes/$planId/datos' fullPath: '/planes/$planId/asignaturas'
preLoaderRoute: typeof PlanesPlanIdDetalleDatosRouteImport preLoaderRoute: typeof PlanesPlanIdDetalleAsignaturasRouteImport
parentRoute: typeof PlanesPlanIdDetalleRouteRoute parentRoute: typeof PlanesPlanIdDetalleRouteRoute
} }
'/planes/$planId/asignaturas/_lista': { '/planes/$planId/asignaturas/_lista': {
@@ -450,24 +396,24 @@ const PlanesListaRouteRouteWithChildren =
PlanesListaRouteRoute._addFileChildren(PlanesListaRouteRouteChildren) PlanesListaRouteRoute._addFileChildren(PlanesListaRouteRouteChildren)
interface PlanesPlanIdDetalleRouteRouteChildren { interface PlanesPlanIdDetalleRouteRouteChildren {
PlanesPlanIdDetalleDatosRoute: typeof PlanesPlanIdDetalleDatosRoute PlanesPlanIdDetalleAsignaturasRoute: typeof PlanesPlanIdDetalleAsignaturasRoute
PlanesPlanIdDetalleDocumentoRoute: typeof PlanesPlanIdDetalleDocumentoRoute PlanesPlanIdDetalleDocumentoRoute: typeof PlanesPlanIdDetalleDocumentoRoute
PlanesPlanIdDetalleFlujoRoute: typeof PlanesPlanIdDetalleFlujoRoute PlanesPlanIdDetalleFlujoRoute: typeof PlanesPlanIdDetalleFlujoRoute
PlanesPlanIdDetalleHistorialRoute: typeof PlanesPlanIdDetalleHistorialRoute PlanesPlanIdDetalleHistorialRoute: typeof PlanesPlanIdDetalleHistorialRoute
PlanesPlanIdDetalleIaplanRoute: typeof PlanesPlanIdDetalleIaplanRoute PlanesPlanIdDetalleIaplanRoute: typeof PlanesPlanIdDetalleIaplanRoute
PlanesPlanIdDetalleMapaRoute: typeof PlanesPlanIdDetalleMapaRoute PlanesPlanIdDetalleMapaRoute: typeof PlanesPlanIdDetalleMapaRoute
PlanesPlanIdDetalleMateriasRoute: typeof PlanesPlanIdDetalleMateriasRoute PlanesPlanIdDetalleIndexRoute: typeof PlanesPlanIdDetalleIndexRoute
} }
const PlanesPlanIdDetalleRouteRouteChildren: PlanesPlanIdDetalleRouteRouteChildren = const PlanesPlanIdDetalleRouteRouteChildren: PlanesPlanIdDetalleRouteRouteChildren =
{ {
PlanesPlanIdDetalleDatosRoute: PlanesPlanIdDetalleDatosRoute, PlanesPlanIdDetalleAsignaturasRoute: PlanesPlanIdDetalleAsignaturasRoute,
PlanesPlanIdDetalleDocumentoRoute: PlanesPlanIdDetalleDocumentoRoute, PlanesPlanIdDetalleDocumentoRoute: PlanesPlanIdDetalleDocumentoRoute,
PlanesPlanIdDetalleFlujoRoute: PlanesPlanIdDetalleFlujoRoute, PlanesPlanIdDetalleFlujoRoute: PlanesPlanIdDetalleFlujoRoute,
PlanesPlanIdDetalleHistorialRoute: PlanesPlanIdDetalleHistorialRoute, PlanesPlanIdDetalleHistorialRoute: PlanesPlanIdDetalleHistorialRoute,
PlanesPlanIdDetalleIaplanRoute: PlanesPlanIdDetalleIaplanRoute, PlanesPlanIdDetalleIaplanRoute: PlanesPlanIdDetalleIaplanRoute,
PlanesPlanIdDetalleMapaRoute: PlanesPlanIdDetalleMapaRoute, PlanesPlanIdDetalleMapaRoute: PlanesPlanIdDetalleMapaRoute,
PlanesPlanIdDetalleMateriasRoute: PlanesPlanIdDetalleMateriasRoute, PlanesPlanIdDetalleIndexRoute: PlanesPlanIdDetalleIndexRoute,
} }
const PlanesPlanIdDetalleRouteRouteWithChildren = const PlanesPlanIdDetalleRouteRouteWithChildren =
@@ -493,7 +439,6 @@ const PlanesPlanIdAsignaturasListaRouteRouteWithChildren =
interface PlanesPlanIdAsignaturasRouteRouteChildren { interface PlanesPlanIdAsignaturasRouteRouteChildren {
PlanesPlanIdAsignaturasAsignaturaIdRouteRoute: typeof PlanesPlanIdAsignaturasAsignaturaIdRouteRoute PlanesPlanIdAsignaturasAsignaturaIdRouteRoute: typeof PlanesPlanIdAsignaturasAsignaturaIdRouteRoute
PlanesPlanIdAsignaturasListaRouteRoute: typeof PlanesPlanIdAsignaturasListaRouteRouteWithChildren PlanesPlanIdAsignaturasListaRouteRoute: typeof PlanesPlanIdAsignaturasListaRouteRouteWithChildren
PlanesPlanIdAsignaturasIndexRoute: typeof PlanesPlanIdAsignaturasIndexRoute
} }
const PlanesPlanIdAsignaturasRouteRouteChildren: PlanesPlanIdAsignaturasRouteRouteChildren = const PlanesPlanIdAsignaturasRouteRouteChildren: PlanesPlanIdAsignaturasRouteRouteChildren =
@@ -502,7 +447,6 @@ const PlanesPlanIdAsignaturasRouteRouteChildren: PlanesPlanIdAsignaturasRouteRou
PlanesPlanIdAsignaturasAsignaturaIdRouteRoute, PlanesPlanIdAsignaturasAsignaturaIdRouteRoute,
PlanesPlanIdAsignaturasListaRouteRoute: PlanesPlanIdAsignaturasListaRouteRoute:
PlanesPlanIdAsignaturasListaRouteRouteWithChildren, PlanesPlanIdAsignaturasListaRouteRouteWithChildren,
PlanesPlanIdAsignaturasIndexRoute: PlanesPlanIdAsignaturasIndexRoute,
} }
const PlanesPlanIdAsignaturasRouteRouteWithChildren = const PlanesPlanIdAsignaturasRouteRouteWithChildren =
@@ -519,7 +463,6 @@ const rootRouteChildren: RootRouteChildren = {
PlanesPlanIdDetalleRouteRoute: PlanesPlanIdDetalleRouteRouteWithChildren, PlanesPlanIdDetalleRouteRoute: PlanesPlanIdDetalleRouteRouteWithChildren,
PlanesPlanIdAsignaturasRouteRoute: PlanesPlanIdAsignaturasRouteRoute:
PlanesPlanIdAsignaturasRouteRouteWithChildren, PlanesPlanIdAsignaturasRouteRouteWithChildren,
PlanesPlanIdIndexRoute: PlanesPlanIdIndexRoute,
} }
export const routeTree = rootRouteImport export const routeTree = rootRouteImport
._addFileChildren(rootRouteChildren) ._addFileChildren(rootRouteChildren)

View File

@@ -7,6 +7,8 @@ import TanStackQueryDevtools from '../integrations/tanstack-query/devtools'
import type { QueryClient } from '@tanstack/react-query' import type { QueryClient } from '@tanstack/react-query'
import { NotFoundPage } from '@/components/ui/NotFoundPage'
interface MyRouterContext { interface MyRouterContext {
queryClient: QueryClient queryClient: QueryClient
} }
@@ -31,6 +33,8 @@ export const Route = createRootRouteWithContext<MyRouterContext>()({
</> </>
), ),
notFoundComponent: () => <NotFoundPage />,
errorComponent: ({ error, reset }) => { errorComponent: ({ error, reset }) => {
return ( return (
<div className="flex min-h-[50vh] flex-col items-center justify-center space-y-4 p-6 text-center"> <div className="flex min-h-[50vh] flex-col items-center justify-center space-y-4 p-6 text-center">

View File

@@ -62,7 +62,7 @@ const mapAsignaturas = (asigApi: Array<any> = []): Array<Materia> => {
})) }))
} }
export const Route = createFileRoute('/planes/$planId/_detalle/materias')({ export const Route = createFileRoute('/planes/$planId/_detalle/asignaturas')({
component: MateriasPage, component: MateriasPage,
}) })
@@ -139,7 +139,7 @@ function MateriasPage() {
{/* Barra de Filtros Avanzada */} {/* Barra de Filtros Avanzada */}
<div className="flex flex-wrap items-center gap-3 rounded-xl border bg-slate-50 p-4"> <div className="flex flex-wrap items-center gap-3 rounded-xl border bg-slate-50 p-4">
<div className="relative min-w-[240px] flex-1"> <div className="relative min-w-60 flex-1">
<Search className="text-muted-foreground absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2" /> <Search className="text-muted-foreground absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2" />
<Input <Input
placeholder="Buscar por nombre o clave..." placeholder="Buscar por nombre o clave..."
@@ -153,7 +153,7 @@ function MateriasPage() {
<Filter className="text-muted-foreground mr-1 h-4 w-4" /> <Filter className="text-muted-foreground mr-1 h-4 w-4" />
<Select value={filterTipo} onValueChange={setFilterTipo}> <Select value={filterTipo} onValueChange={setFilterTipo}>
<SelectTrigger className="w-[140px] bg-white"> <SelectTrigger className="w-35 bg-white">
<SelectValue placeholder="Tipo" /> <SelectValue placeholder="Tipo" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
@@ -164,7 +164,7 @@ function MateriasPage() {
</Select> </Select>
<Select value={filterEstado} onValueChange={setFilterEstado}> <Select value={filterEstado} onValueChange={setFilterEstado}>
<SelectTrigger className="w-[140px] bg-white"> <SelectTrigger className="w-35 bg-white">
<SelectValue placeholder="Estado" /> <SelectValue placeholder="Estado" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
@@ -176,7 +176,7 @@ function MateriasPage() {
</Select> </Select>
<Select value={filterLinea} onValueChange={setFilterLinea}> <Select value={filterLinea} onValueChange={setFilterLinea}>
<SelectTrigger className="w-[180px] bg-white"> <SelectTrigger className="w-45 bg-white">
<SelectValue placeholder="Línea" /> <SelectValue placeholder="Línea" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
@@ -196,14 +196,14 @@ function MateriasPage() {
<Table> <Table>
<TableHeader> <TableHeader>
<TableRow className="bg-slate-50/50"> <TableRow className="bg-slate-50/50">
<TableHead className="w-[120px]">Clave</TableHead> <TableHead className="w-30">Clave</TableHead>
<TableHead>Nombre</TableHead> <TableHead>Nombre</TableHead>
<TableHead className="text-center">Créditos</TableHead> <TableHead className="text-center">Créditos</TableHead>
<TableHead className="text-center">Ciclo</TableHead> <TableHead className="text-center">Ciclo</TableHead>
<TableHead>Línea Curricular</TableHead> <TableHead>Línea Curricular</TableHead>
<TableHead>Tipo</TableHead> <TableHead>Tipo</TableHead>
<TableHead>Estado</TableHead> <TableHead>Estado</TableHead>
<TableHead className="w-[50px]"></TableHead> <TableHead className="w-12.5"></TableHead>
</TableRow> </TableRow>
</TableHeader> </TableHeader>
<TableBody> <TableBody>

View File

@@ -21,7 +21,7 @@ import {
import { usePlan } from '@/data' import { usePlan } from '@/data'
// import { toast } from 'sonner' // Asegúrate de tener sonner instalado o quita la línea // import { toast } from 'sonner' // Asegúrate de tener sonner instalado o quita la línea
export const Route = createFileRoute('/planes/$planId/_detalle/datos')({ export const Route = createFileRoute('/planes/$planId/_detalle/')({
component: DatosGeneralesPage, component: DatosGeneralesPage,
}) })
@@ -204,8 +204,8 @@ function DatosGeneralesPage() {
<Textarea <Textarea
value={editValue} value={editValue}
onChange={(e) => setEditValue(e.target.value)} onChange={(e) => setEditValue(e.target.value)}
className="min-h-[120px]" className="placeholder:text-muted-foreground/70 min-h-30 not-italic placeholder:italic"
placeholder={campo.holder} placeholder={`Ej. ${campo.holder[0] as string}`}
/> />
<div className="flex justify-end gap-2"> <div className="flex justify-end gap-2">
<Button <Button
@@ -225,7 +225,7 @@ function DatosGeneralesPage() {
</div> </div>
</div> </div>
) : ( ) : (
<div className="min-h-[100px]"> <div className="min-h-25">
{campo.value ? ( {campo.value ? (
<div className="text-sm leading-relaxed text-slate-600"> <div className="text-sm leading-relaxed text-slate-600">
{campo.tipo === 'lista' ? ( {campo.tipo === 'lista' ? (

View File

@@ -1,4 +1,4 @@
import { createFileRoute, Outlet, Link } from '@tanstack/react-router' import { createFileRoute, Outlet, Link, notFound } from '@tanstack/react-router'
import { import {
ChevronLeft, ChevronLeft,
GraduationCap, GraduationCap,
@@ -17,10 +17,37 @@ import {
DropdownMenuItem, DropdownMenuItem,
DropdownMenuTrigger, DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu' } from '@/components/ui/dropdown-menu'
import { NotFoundPage } from '@/components/ui/NotFoundPage'
import { Skeleton } from '@/components/ui/skeleton' import { Skeleton } from '@/components/ui/skeleton'
import { plans_get } from '@/data/api/plans.api'
import { usePlan } from '@/data/hooks/usePlans' import { usePlan } from '@/data/hooks/usePlans'
import { qk } from '@/data/query/keys'
export const Route = createFileRoute('/planes/$planId/_detalle')({ export const Route = createFileRoute('/planes/$planId/_detalle')({
loader: async ({ context: { queryClient }, params: { planId } }) => {
try {
console.log('loader')
await queryClient.ensureQueryData({
queryKey: qk.plan(planId),
queryFn: () => plans_get(planId),
})
} catch (e: any) {
// PGRST116: The result contains 0 rows
if (e?.code === 'PGRST116') {
throw notFound()
}
throw e
}
},
notFoundComponent: () => {
return (
<NotFoundPage
title="Plan de Estudios no encontrado"
message="El plan de estudios que intentas consultar no existe o no tienes permisos para verlo."
/>
)
},
component: RouteComponent, component: RouteComponent,
}) })
@@ -86,11 +113,11 @@ function RouteComponent() {
</div> </div>
</div> </div>
<div className="mx-auto max-w-[1600px] space-y-8 p-8"> <div className="mx-auto max-w-400 space-y-8 p-8">
{/* Header del Plan */} {/* Header del Plan */}
{isLoading ? ( {isLoading ? (
/* ===== SKELETON ===== */ /* ===== SKELETON ===== */
<div className="mx-auto max-w-[1600px] p-8"> <div className="mx-auto max-w-400 p-8">
<div className="grid grid-cols-1 gap-6 lg:grid-cols-2"> <div className="grid grid-cols-1 gap-6 lg:grid-cols-2">
{Array.from({ length: 6 }).map((_, i) => ( {Array.from({ length: 6 }).map((_, i) => (
<DatosGeneralesSkeleton key={i} /> <DatosGeneralesSkeleton key={i} />
@@ -189,13 +216,13 @@ function RouteComponent() {
{/* 4. Navegación de Tabs */} {/* 4. Navegación de Tabs */}
<div className="scrollbar-hide overflow-x-auto border-b"> <div className="scrollbar-hide overflow-x-auto border-b">
<nav className="flex min-w-max gap-8"> <nav className="flex min-w-max gap-8">
<Tab to="/planes/$planId/datos" params={{ planId }}> <Tab to="/planes/$planId/" params={{ planId }}>
Datos Generales Datos Generales
</Tab> </Tab>
<Tab to="/planes/$planId/mapa" params={{ planId }}> <Tab to="/planes/$planId/mapa" params={{ planId }}>
Mapa Curricular Mapa Curricular
</Tab> </Tab>
<Tab to="/planes/$planId/materias" params={{ planId }}> <Tab to="/planes/$planId/asignaturas" params={{ planId }}>
Materias Materias
</Tab> </Tab>
<Tab to="/planes/$planId/flujo" params={{ planId }}> <Tab to="/planes/$planId/flujo" params={{ planId }}>
@@ -237,7 +264,7 @@ const InfoCard = forwardRef<
<div <div
ref={ref} ref={ref}
{...props} {...props}
className={`flex h-[72px] w-full items-center gap-4 rounded-xl border border-slate-200/60 bg-slate-50/50 p-4 shadow-sm transition-all ${ className={`flex h-18 w-full items-center gap-4 rounded-xl border border-slate-200/60 bg-slate-50/50 p-4 shadow-sm transition-all ${
isEditable isEditable
? 'cursor-pointer hover:border-teal-200 hover:bg-white focus:outline-none focus-visible:ring-2 focus-visible:ring-teal-500/40' ? 'cursor-pointer hover:border-teal-200 hover:bg-white focus:outline-none focus-visible:ring-2 focus-visible:ring-teal-500/40'
: '' : ''
@@ -273,6 +300,9 @@ function Tab({
params={params} params={params}
className="border-b-2 border-transparent pb-3 text-sm font-medium text-slate-500 transition-all hover:text-slate-800" 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' }} activeProps={{ className: 'border-teal-600 text-teal-700 font-bold' }}
activeOptions={{
exact: true,
}}
> >
{children} {children}
</Link> </Link>

View File

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

View File

@@ -1,6 +1,31 @@
import { createFileRoute, Outlet } from '@tanstack/react-router' import { createFileRoute, Outlet, notFound } from '@tanstack/react-router'
import { NotFoundPage } from '@/components/ui/NotFoundPage'
import { plans_get } from '@/data/api/plans.api'
import { qk } from '@/data/query/keys'
export const Route = createFileRoute('/planes/$planId/asignaturas')({ export const Route = createFileRoute('/planes/$planId/asignaturas')({
loader: async ({ context: { queryClient }, params: { planId } }) => {
try {
await queryClient.ensureQueryData({
queryKey: qk.plan(planId),
queryFn: () => plans_get(planId),
})
} catch (e: any) {
if (e?.code === 'PGRST116') {
throw notFound()
}
throw e
}
},
notFoundComponent: () => {
return (
<NotFoundPage
title="Plan de Estudios no encontrado"
message="El plan de estudios que intentas consultar no existe o no tienes permisos para verlo."
/>
)
},
component: AsignaturasLayout, component: AsignaturasLayout,
}) })

View File

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