6 Commits

Author SHA1 Message Date
8786aaae25 Arreglado orden de renderizado de campos 2026-01-28 18:08:00 -06:00
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
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
ddb3a5023c Merge pull request 'fix/Incidencias' (#46) from fix/Incidencias into main
Reviewed-on: #46
2026-01-27 21:59:13 +00:00
11 changed files with 216 additions and 154 deletions

View File

@@ -118,7 +118,10 @@ export function WizardControls({
}
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">
{errorMessage && (
<span className="text-destructive text-sm font-medium">
@@ -126,20 +129,15 @@ export function WizardControls({
</span>
)}
</div>
<div className="flex gap-4">
<Button variant="secondary" onClick={onPrev} disabled={disablePrev}>
Anterior
{isLastStep ? (
<Button onClick={handleCreate} disabled={disableCreate}>
Crear plan
</Button>
{isLastStep ? (
<Button onClick={handleCreate} disabled={disableCreate}>
Crear plan
</Button>
) : (
<Button onClick={onNext} disabled={disableNext}>
Siguiente
</Button>
)}
</div>
) : (
<Button onClick={onNext} disabled={disableNext}>
Siguiente
</Button>
)}
</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> {
console.log('plans_get')
const supabase = supabaseBrowser()
const { data, error } = await supabase
@@ -350,7 +352,7 @@ export async function plans_update_fields(
patch: PlansUpdateFieldsPatch,
): Promise<PlanEstudio> {
const supabase = supabaseBrowser()
const { data, error } = await supabase
.from('planes_estudio')
.update(patch)

View File

@@ -53,7 +53,10 @@ export function usePlanes(filters: PlanListFilters) {
export function usePlan(planId: UUID | null | undefined) {
return useQuery({
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),
})
}

View File

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

View File

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

View File

@@ -139,7 +139,7 @@ function MateriasPage() {
{/* Barra de Filtros Avanzada */}
<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" />
<Input
placeholder="Buscar por nombre o clave..."
@@ -153,7 +153,7 @@ function MateriasPage() {
<Filter className="text-muted-foreground mr-1 h-4 w-4" />
<Select value={filterTipo} onValueChange={setFilterTipo}>
<SelectTrigger className="w-[140px] bg-white">
<SelectTrigger className="w-35 bg-white">
<SelectValue placeholder="Tipo" />
</SelectTrigger>
<SelectContent>
@@ -164,7 +164,7 @@ function MateriasPage() {
</Select>
<Select value={filterEstado} onValueChange={setFilterEstado}>
<SelectTrigger className="w-[140px] bg-white">
<SelectTrigger className="w-35 bg-white">
<SelectValue placeholder="Estado" />
</SelectTrigger>
<SelectContent>
@@ -176,7 +176,7 @@ function MateriasPage() {
</Select>
<Select value={filterLinea} onValueChange={setFilterLinea}>
<SelectTrigger className="w-[180px] bg-white">
<SelectTrigger className="w-45 bg-white">
<SelectValue placeholder="Línea" />
</SelectTrigger>
<SelectContent>
@@ -196,14 +196,14 @@ function MateriasPage() {
<Table>
<TableHeader>
<TableRow className="bg-slate-50/50">
<TableHead className="w-[120px]">Clave</TableHead>
<TableHead className="w-30">Clave</TableHead>
<TableHead>Nombre</TableHead>
<TableHead className="text-center">Créditos</TableHead>
<TableHead className="text-center">Ciclo</TableHead>
<TableHead>Línea Curricular</TableHead>
<TableHead>Tipo</TableHead>
<TableHead>Estado</TableHead>
<TableHead className="w-[50px]"></TableHead>
<TableHead className="w-12.5"></TableHead>
</TableRow>
</TableHeader>
<TableBody>

View File

@@ -21,7 +21,7 @@ import {
import { usePlan } from '@/data'
// 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,
})
@@ -50,36 +50,59 @@ function DatosGeneralesPage() {
// Efecto para transformar data?.datos en el arreglo de campos
useEffect(() => {
const properties = data?.estructuras_plan?.definicion?.properties
const definicion = data?.estructuras_plan?.definicion as any
const properties = definicion?.properties
const requiredOrder = definicion?.required as Array<string> | undefined
const valores = data?.datos as Record<string, unknown>
const valores = (data?.datos as Record<string, unknown>) || {}
if (properties && typeof properties === 'object') {
const datosTransformados: Array<DatosGeneralesField> = Object.entries(
properties,
).map(([key, schema], index) => {
const rawValue = valores[key]
let keys = Object.keys(properties)
return {
id: (index + 1).toString(),
label: schema?.title || formatLabel(key),
helperText: schema?.description || '',
holder: schema?.examples || '',
value:
rawValue !== undefined && rawValue !== null ? String(rawValue) : '',
// Ordenar llaves basado en la lista "required" si existe
if (Array.isArray(requiredOrder)) {
keys = keys.sort((a, b) => {
const indexA = requiredOrder.indexOf(a)
const indexB = requiredOrder.indexOf(b)
// Si 'a' está en la lista y 'b' no -> 'a' primero (-1)
if (indexA !== -1 && indexB === -1) return -1
// Si 'b' está en la lista y 'a' no -> 'b' primero (1)
if (indexA === -1 && indexB !== -1) return 1
// Si ambos están, comparar índices
if (indexA !== -1 && indexB !== -1) return indexA - indexB
// Ninguno en la lista, mantener orden relativo
return 0
})
}
requerido: true,
const datosTransformados: Array<DatosGeneralesField> = keys.map(
(key, index) => {
const schema = properties[key]
const rawValue = valores[key]
// 👇 TIPO DE CAMPO
tipo: Array.isArray(schema?.enum)
? 'select'
: schema?.type === 'number'
? 'number'
: 'texto',
return {
id: (index + 1).toString(),
label: schema?.title || formatLabel(key),
helperText: schema?.description || '',
holder: schema?.examples || '',
value:
rawValue !== undefined && rawValue !== null
? String(rawValue)
: '',
opciones: schema?.enum || [],
}
})
requerido: true,
// 👇 TIPO DE CAMPO
tipo: Array.isArray(schema?.enum)
? 'select'
: schema?.type === 'number'
? 'number'
: 'texto',
opciones: schema?.enum || [],
}
},
)
setCampos(datosTransformados)
}
@@ -204,8 +227,8 @@ function DatosGeneralesPage() {
<Textarea
value={editValue}
onChange={(e) => setEditValue(e.target.value)}
className="min-h-[120px]"
placeholder={campo.holder}
className="placeholder:text-muted-foreground/70 min-h-30 not-italic placeholder:italic"
placeholder={`Ej. ${campo.holder[0]}`}
/>
<div className="flex justify-end gap-2">
<Button
@@ -225,7 +248,7 @@ function DatosGeneralesPage() {
</div>
</div>
) : (
<div className="min-h-[100px]">
<div className="min-h-25">
{campo.value ? (
<div className="text-sm leading-relaxed text-slate-600">
{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 {
ChevronLeft,
GraduationCap,
@@ -17,10 +17,37 @@ import {
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu'
import { NotFoundPage } from '@/components/ui/NotFoundPage'
import { Skeleton } from '@/components/ui/skeleton'
import { plans_get } from '@/data/api/plans.api'
import { usePlan } from '@/data/hooks/usePlans'
import { qk } from '@/data/query/keys'
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,
})
@@ -86,11 +113,11 @@ function RouteComponent() {
</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 */}
{isLoading ? (
/* ===== 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">
{Array.from({ length: 6 }).map((_, i) => (
<DatosGeneralesSkeleton key={i} />
@@ -189,7 +216,7 @@ function RouteComponent() {
{/* 4. Navegación de Tabs */}
<div className="scrollbar-hide overflow-x-auto border-b">
<nav className="flex min-w-max gap-8">
<Tab to="/planes/$planId/datos" params={{ planId }}>
<Tab to="/planes/$planId/" params={{ planId }}>
Datos Generales
</Tab>
<Tab to="/planes/$planId/mapa" params={{ planId }}>
@@ -237,7 +264,7 @@ const InfoCard = forwardRef<
<div
ref={ref}
{...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
? '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}
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' }}
activeOptions={{
exact: true,
}}
>
{children}
</Link>

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')({
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,
})

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/datos',
params,
})
},
})