tipado desde supabase, primer listado de planes, ajustes en src/data

This commit is contained in:
2026-01-09 15:51:36 -06:00
parent 8d6b7c4ba9
commit 5a7672677d
26 changed files with 7812 additions and 1577 deletions

View File

@@ -67,7 +67,7 @@ export function PasoConfiguracionPanel({
},
}))
}
className="min-h-[100px]"
className="min-h-25"
/>
</div>
<div className="grid gap-1">
@@ -213,7 +213,7 @@ export function PasoConfiguracionPanel({
</div>
</div>
<div className="grid max-h-[300px] gap-2 overflow-y-auto">
<div className="grid max-h-75 gap-2 overflow-y-auto">
{MATERIAS_MOCK.map((m) => (
<div
key={m.id}

View File

@@ -31,7 +31,7 @@ export function StepWithTooltip({
{title}
</span>
</TooltipTrigger>
<TooltipContent className="max-w-[200px] text-xs">
<TooltipContent className="max-w-50 text-xs">
<p>{desc}</p>
</TooltipContent>
</Tooltip>

View File

@@ -32,7 +32,7 @@ export function StepWithTooltip({
{title}
</span>
</TooltipTrigger>
<TooltipContent className="max-w-[200px] text-xs">
<TooltipContent className="max-w-50 text-xs">
<p>{desc}</p>
</TooltipContent>
</Tooltip>

View File

@@ -1,52 +1,54 @@
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import { Slot } from '@radix-ui/react-slot'
import { cva } from 'class-variance-authority'
import * as React from 'react'
import { cn } from "@/lib/utils"
import type { VariantProps } from 'class-variance-authority'
import { cn } from '@/lib/utils'
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
"focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive inline-flex shrink-0 items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-all outline-none focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
destructive:
"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
'bg-destructive hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60 text-white',
outline:
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
'bg-background hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 border shadow-xs',
secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
'bg-secondary text-secondary-foreground hover:bg-secondary/80',
ghost:
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
link: "text-primary underline-offset-4 hover:underline",
'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',
link: 'text-primary underline-offset-4 hover:underline',
},
size: {
default: "h-9 px-4 py-2 has-[>svg]:px-3",
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
icon: "size-9",
"icon-sm": "size-8",
"icon-lg": "size-10",
default: 'h-9 px-4 py-2 has-[>svg]:px-3',
sm: 'h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5',
lg: 'h-10 rounded-md px-6 has-[>svg]:px-4',
icon: 'size-9',
'icon-sm': 'size-8',
'icon-lg': 'size-10',
},
},
defaultVariants: {
variant: "default",
size: "default",
variant: 'default',
size: 'default',
},
}
},
)
function Button({
className,
variant = "default",
size = "default",
variant = 'default',
size = 'default',
asChild = false,
...props
}: React.ComponentProps<"button"> &
}: React.ComponentProps<'button'> &
VariantProps<typeof buttonVariants> & {
asChild?: boolean
}) {
const Comp = asChild ? Slot : "button"
const Comp = asChild ? Slot : 'button'
return (
<Comp

View File

@@ -1,6 +1,8 @@
import { supabaseBrowser } from "../supabase/client";
import { invokeEdge } from "../supabase/invokeEdge";
import { buildRange, throwIfError, requireData } from "./_helpers";
import { buildRange, requireData, throwIfError } from "./_helpers";
import type {
Asignatura,
CambioPlan,
@@ -18,6 +20,7 @@ const EDGE = {
ai_generate_plan: "ai_generate_plan",
plans_persist_from_ai: "plans_persist_from_ai",
plans_clone_from_existing: "plans_clone_from_existing",
plans_import_from_files: "plans_import_from_files",
plans_update_fields: "plans_update_fields",
@@ -39,23 +42,33 @@ export type PlanListFilters = {
offset?: number;
};
export async function plans_list(filters: PlanListFilters = {}): Promise<Paged<PlanEstudio>> {
export async function plans_list(
filters: PlanListFilters = {},
): Promise<Paged<PlanEstudio>> {
const supabase = supabaseBrowser();
// 1. Construimos la query.
// TypeScript validará que "planes_estudio" existe en Database
let q = supabase
.from("planes_estudio")
.select(
`
id,carrera_id,estructura_id,nombre,nivel,tipo_ciclo,numero_ciclos,datos,estado_actual_id,activo,tipo_origen,meta_origen,creado_por,actualizado_por,creado_en,actualizado_en,
carreras(id,facultad_id,nombre,nombre_corto,clave_sep,activa, facultades(id,nombre,nombre_corto,color,icono)),
estructuras_plan(id,nombre,tipo,version,definicion),
estados_plan(id,clave,etiqueta,orden,es_final)
`,
{ count: "exact" }
*,
carreras (
*,
facultades (*)
),
estructuras_plan (*),
estados_plan (*)
`,
{ count: "exact" },
)
.order("actualizado_en", { ascending: false });
if (filters.search?.trim()) q = q.ilike("nombre", `%${filters.search.trim()}%`);
// 2. Aplicamos filtros dinámicos
if (filters.search?.trim()) {
q = q.ilike("nombre", `%${filters.search.trim()}%`);
}
if (filters.carreraId) q = q.eq("carrera_id", filters.carreraId);
if (filters.estadoId) q = q.eq("estado_actual_id", filters.estadoId);
if (typeof filters.activo === "boolean") q = q.eq("activo", filters.activo);
@@ -63,13 +76,19 @@ export async function plans_list(filters: PlanListFilters = {}): Promise<Paged<P
// filtro por FK “hacia arriba” (PostgREST soporta filtros en recursos embebidos)
if (filters.facultadId) q = q.eq("carreras.facultad_id", filters.facultadId);
// 3. Paginación
const { from, to } = buildRange(filters.limit, filters.offset);
if (typeof from === "number" && typeof to === "number") q = q.range(from, to);
if (from !== undefined && to !== undefined) q = q.range(from, to);
const { data, error, count } = await q;
throwIfError(error);
return { data: data ?? [], count: count ?? null };
return {
// 1. Si data es null, usa [].
// 2. Luego dile a TS que el resultado es tu Array tipado.
data: (data ?? []) as unknown as Array<PlanEstudio>,
count: count ?? 0,
};
}
export async function plans_get(planId: UUID): Promise<PlanEstudio> {
@@ -79,11 +98,11 @@ export async function plans_get(planId: UUID): Promise<PlanEstudio> {
.from("planes_estudio")
.select(
`
id,carrera_id,estructura_id,nombre,nivel,tipo_ciclo,numero_ciclos,datos,estado_actual_id,activo,tipo_origen,meta_origen,creado_por,actualizado_por,creado_en,actualizado_en,
carreras(id,facultad_id,nombre,nombre_corto,clave_sep,activa, facultades(id,nombre,nombre_corto,color,icono)),
estructuras_plan(id,nombre,tipo,version,definicion),
estados_plan(id,clave,etiqueta,orden,es_final)
`
*,
carreras (*, facultades(*)),
estructuras_plan (*),
estados_plan (*)
`,
)
.eq("id", planId)
.single();
@@ -92,7 +111,9 @@ export async function plans_get(planId: UUID): Promise<PlanEstudio> {
return requireData(data, "Plan no encontrado.");
}
export async function plan_lineas_list(planId: UUID): Promise<LineaPlan[]> {
export async function plan_lineas_list(
planId: UUID,
): Promise<Array<LineaPlan>> {
const supabase = supabaseBrowser();
const { data, error } = await supabase
.from("lineas_plan")
@@ -104,12 +125,14 @@ export async function plan_lineas_list(planId: UUID): Promise<LineaPlan[]> {
return data ?? [];
}
export async function plan_asignaturas_list(planId: UUID): Promise<Asignatura[]> {
export async function plan_asignaturas_list(
planId: UUID,
): Promise<Array<Asignatura>> {
const supabase = supabaseBrowser();
const { data, error } = await supabase
.from("asignaturas")
.select(
"id,plan_estudio_id,estructura_id,facultad_propietaria_id,codigo,nombre,tipo,creditos,horas_semana,numero_ciclo,linea_plan_id,orden_celda,datos,contenido_tematico,tipo_origen,meta_origen,creado_por,actualizado_por,creado_en,actualizado_en"
"id,plan_estudio_id,estructura_id,facultad_propietaria_id,codigo,nombre,tipo,creditos,horas_semana,numero_ciclo,linea_plan_id,orden_celda,datos,contenido_tematico,tipo_origen,meta_origen,creado_por,actualizado_por,creado_en,actualizado_en",
)
.eq("plan_estudio_id", planId)
.order("numero_ciclo", { ascending: true, nullsFirst: false })
@@ -120,11 +143,13 @@ export async function plan_asignaturas_list(planId: UUID): Promise<Asignatura[]>
return data ?? [];
}
export async function plans_history(planId: UUID): Promise<CambioPlan[]> {
export async function plans_history(planId: UUID): Promise<Array<CambioPlan>> {
const supabase = supabaseBrowser();
const { data, error } = await supabase
.from("cambios_plan")
.select("id,plan_estudio_id,cambiado_por,cambiado_en,tipo,campo,valor_anterior,valor_nuevo,interaccion_ia_id")
.select(
"id,plan_estudio_id,cambiado_por,cambiado_en,tipo,campo,valor_anterior,valor_nuevo,interaccion_ia_id",
)
.eq("plan_estudio_id", planId)
.order("cambiado_en", { ascending: false });
@@ -143,7 +168,9 @@ export type PlansCreateManualInput = {
datos?: Partial<PlanDatosSep> & Record<string, any>;
};
export async function plans_create_manual(input: PlansCreateManualInput): Promise<PlanEstudio> {
export async function plans_create_manual(
input: PlansCreateManualInput,
): Promise<PlanEstudio> {
return invokeEdge<PlanEstudio>(EDGE.plans_create_manual, input);
}
@@ -161,27 +188,35 @@ export type AIGeneratePlanInput = {
descripcionEnfoque: string;
poblacionObjetivo?: string;
notasAdicionales?: string;
archivosReferencia?: UUID[];
repositoriosIds?: UUID[];
archivosReferencia?: Array<UUID>;
repositoriosIds?: Array<UUID>;
usarMCP?: boolean;
};
};
export async function ai_generate_plan(input: AIGeneratePlanInput): Promise<any> {
export async function ai_generate_plan(
input: AIGeneratePlanInput,
): Promise<any> {
return invokeEdge<any>(EDGE.ai_generate_plan, input);
}
export async function plans_persist_from_ai(payload: { jsonPlan: any }): Promise<PlanEstudio> {
export async function plans_persist_from_ai(
payload: { jsonPlan: any },
): Promise<PlanEstudio> {
return invokeEdge<PlanEstudio>(EDGE.plans_persist_from_ai, payload);
}
export async function plans_clone_from_existing(payload: {
planOrigenId: UUID;
overrides: Partial<Pick<PlanEstudio, "nombre" | "nivel" | "tipo_ciclo" | "numero_ciclos">> & {
carrera_id?: UUID;
estructura_id?: UUID;
datos?: Partial<PlanDatosSep> & Record<string, any>;
};
overrides:
& Partial<
Pick<PlanEstudio, "nombre" | "nivel" | "tipo_ciclo" | "numero_ciclos">
>
& {
carrera_id?: UUID;
estructura_id?: UUID;
datos?: Partial<PlanDatosSep> & Record<string, any>;
};
}): Promise<PlanEstudio> {
return invokeEdge<PlanEstudio>(EDGE.plans_clone_from_existing, payload);
}
@@ -211,27 +246,33 @@ export type PlansUpdateFieldsPatch = {
datos?: Partial<PlanDatosSep> & Record<string, any>;
};
export async function plans_update_fields(planId: UUID, patch: PlansUpdateFieldsPatch): Promise<PlanEstudio> {
export async function plans_update_fields(
planId: UUID,
patch: PlansUpdateFieldsPatch,
): Promise<PlanEstudio> {
return invokeEdge<PlanEstudio>(EDGE.plans_update_fields, { planId, patch });
}
/** Operaciones del mapa curricular (mover/reordenar) */
export type PlanMapOperation =
| {
op: "MOVE_ASIGNATURA";
asignaturaId: UUID;
numero_ciclo: number | null;
linea_plan_id: UUID | null;
orden_celda?: number | null;
}
op: "MOVE_ASIGNATURA";
asignaturaId: UUID;
numero_ciclo: number | null;
linea_plan_id: UUID | null;
orden_celda?: number | null;
}
| {
op: "REORDER_CELDA";
linea_plan_id: UUID;
numero_ciclo: number;
asignaturaIdsOrdenados: UUID[];
};
op: "REORDER_CELDA";
linea_plan_id: UUID;
numero_ciclo: number;
asignaturaIdsOrdenados: Array<UUID>;
};
export async function plans_update_map(planId: UUID, ops: PlanMapOperation[]): Promise<{ ok: true }> {
export async function plans_update_map(
planId: UUID,
ops: Array<PlanMapOperation>,
): Promise<{ ok: true }> {
return invokeEdge<{ ok: true }>(EDGE.plans_update_map, { planId, ops });
}
@@ -251,10 +292,16 @@ export type DocumentoResult = {
nombre?: string;
};
export async function plans_generate_document(planId: UUID): Promise<DocumentoResult> {
export async function plans_generate_document(
planId: UUID,
): Promise<DocumentoResult> {
return invokeEdge<DocumentoResult>(EDGE.plans_generate_document, { planId });
}
export async function plans_get_document(planId: UUID): Promise<DocumentoResult | null> {
return invokeEdge<DocumentoResult | null>(EDGE.plans_get_document, { planId });
export async function plans_get_document(
planId: UUID,
): Promise<DocumentoResult | null> {
return invokeEdge<DocumentoResult | null>(EDGE.plans_get_document, {
planId,
});
}

View File

@@ -1,4 +1,5 @@
import { useMutation } from "@tanstack/react-query";
import {
ai_plan_chat,
ai_plan_improve,

View File

@@ -1,7 +1,10 @@
import { keepPreviousData, useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { qk } from "../query/keys";
import type { PlanEstudio, UUID } from "../types/domain";
import type { PlanListFilters, PlanMapOperation, PlansCreateManualInput, PlansUpdateFieldsPatch } from "../api/plans.api";
import {
keepPreviousData,
useMutation,
useQuery,
useQueryClient,
} from "@tanstack/react-query";
import {
ai_generate_plan,
plan_asignaturas_list,
@@ -19,13 +22,30 @@ import {
plans_update_fields,
plans_update_map,
} from "../api/plans.api";
import { qk } from "../query/keys";
import type {
PlanListFilters,
PlanMapOperation,
PlansCreateManualInput,
PlansUpdateFieldsPatch,
} from "../api/plans.api";
import type { UUID } from "../types/domain";
export function usePlanes(filters: PlanListFilters) {
// 🧠 Tip: memoiza "filters" (useMemo) para que queryKey sea estable.
return useQuery({
// Usamos la factory de keys para consistencia
queryKey: qk.planesList(filters),
// La función fetch
queryFn: () => plans_list(filters),
// UX: Mantiene los datos viejos mientras carga la paginación nueva
placeholderData: keepPreviousData,
// Opcional: Tiempo que la data se considera fresca
staleTime: 1000 * 60 * 5, // 5 minutos
});
}
@@ -47,7 +67,9 @@ export function usePlanLineas(planId: UUID | null | undefined) {
export function usePlanAsignaturas(planId: UUID | null | undefined) {
return useQuery({
queryKey: planId ? qk.planAsignaturas(planId) : ["planes", "asignaturas", null],
queryKey: planId
? qk.planAsignaturas(planId)
: ["planes", "asignaturas", null],
queryFn: () => plan_asignaturas_list(planId as UUID),
enabled: Boolean(planId),
});
@@ -144,7 +166,8 @@ export function useUpdatePlanMapa() {
const qc = useQueryClient();
return useMutation({
mutationFn: (vars: { planId: UUID; ops: PlanMapOperation[] }) => plans_update_map(vars.planId, vars.ops),
mutationFn: (vars: { planId: UUID; ops: Array<PlanMapOperation> }) =>
plans_update_map(vars.planId, vars.ops),
// ✅ Optimista (rápida) para el caso MOVE_ASIGNATURA
onMutate: async (vars) => {
@@ -152,9 +175,7 @@ export function useUpdatePlanMapa() {
const prev = qc.getQueryData<any>(qk.planAsignaturas(vars.planId));
// solo optimizamos MOVEs simples
const moves = vars.ops.filter((x) => x.op === "MOVE_ASIGNATURA") as Array<
Extract<PlanMapOperation, { op: "MOVE_ASIGNATURA" }>
>;
const moves = vars.ops.filter((x) => x.op === "MOVE_ASIGNATURA");
if (prev && Array.isArray(prev) && moves.length) {
const next = prev.map((a: any) => {

View File

@@ -1,14 +0,0 @@
import { QueryClient } from "@tanstack/react-query";
export const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 30_000,
refetchOnWindowFocus: false,
retry: (failureCount) => failureCount < 2,
},
mutations: {
retry: 0,
},
},
});

View File

@@ -1,7 +1,20 @@
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
export function getContext() {
const queryClient = new QueryClient()
const queryClient = new QueryClient(
{
defaultOptions: {
queries: {
staleTime: 30_000,
refetchOnWindowFocus: false,
retry: (failureCount) => failureCount < 2,
},
mutations: {
retry: 0,
},
},
}
)
return {
queryClient,
}

View File

@@ -1,7 +1,10 @@
import { createClient, type SupabaseClient } from "@supabase/supabase-js";
import type { Database } from "../types/database";
import { createClient } from "@supabase/supabase-js";
import { getEnv } from "./env";
import type { SupabaseClient } from "@supabase/supabase-js";
import type { Database } from "src/types/supabase.js";
let _client: SupabaseClient<Database> | null = null;
export function supabaseBrowser(): SupabaseClient<Database> {
@@ -10,13 +13,13 @@ export function supabaseBrowser(): SupabaseClient<Database> {
const url = getEnv(
"VITE_SUPABASE_URL",
"NEXT_PUBLIC_SUPABASE_URL",
"SUPABASE_URL"
"SUPABASE_URL",
);
const anonKey = getEnv(
"VITE_SUPABASE_ANON_KEY",
"NEXT_PUBLIC_SUPABASE_ANON_KEY",
"SUPABASE_ANON_KEY"
"SUPABASE_ANON_KEY",
);
_client = createClient<Database>(url, anonKey, {

View File

@@ -1,27 +1,26 @@
import type { Json } from "./database";
import type { Enums, Tables } from "../../types/supabase";
import type { Json } from "src/types/supabase";
export type UUID = string;
export type TipoEstructuraPlan = "CURRICULAR" | "NO_CURRICULAR";
export type NivelPlanEstudio =
| "LICENCIATURA"
| "MAESTRIA"
| "DOCTORADO"
| "ESPECIALIDAD"
| "DIPLOMADO"
| "OTRO";
export type TipoEstructuraPlan = Enums<"tipo_estructura_plan">;
export type NivelPlanEstudio = Enums<"nivel_plan_estudio">;
export type TipoCiclo = Enums<"tipo_ciclo">;
export type TipoCiclo = "SEMESTRE" | "CUATRIMESTRE" | "TRIMESTRE" | "OTRO";
export type TipoOrigen = Enums<"tipo_origen">;
export type TipoOrigen = "MANUAL" | "IA" | "CLONADO_INTERNO" | "TRADICIONAL" | "OTRO";
export type TipoAsignatura = "OBLIGATORIA" | "OPTATIVA" | "TRONCAL" | "OTRA";
export type TipoAsignatura = Enums<"tipo_asignatura">;
export type TipoBibliografia = "BASICA" | "COMPLEMENTARIA";
export type TipoFuenteBibliografia = "MANUAL" | "BIBLIOTECA";
export type EstadoTareaRevision = "PENDIENTE" | "COMPLETADA" | "OMITIDA";
export type TipoNotificacion = "PLAN_ASIGNADO" | "ESTADO_CAMBIADO" | "TAREA_ASIGNADA" | "COMENTARIO" | "OTRA";
export type TipoNotificacion =
| "PLAN_ASIGNADO"
| "ESTADO_CAMBIADO"
| "TAREA_ASIGNADA"
| "COMENTARIO"
| "OTRA";
export type TipoInteraccionIA = "GENERAR" | "MEJORAR_SECCION" | "CHAT" | "OTRA";
@@ -58,38 +57,12 @@ export type PlanDatosSep = {
propuesta_de_evaluacion_periodica_del_plan_de_estudios?: string | null;
};
export type Paged<T> = { data: T[]; count: number | null };
export type Paged<T> = { data: Array<T>; count: number | null };
export type Facultad = {
id: UUID;
nombre: string;
nombre_corto: string | null;
color: string | null;
icono: string | null;
creado_en: string;
actualizado_en: string;
};
export type FacultadRow = Tables<"facultades">;
export type CarreraRow = Tables<"carreras">;
export type Carrera = {
id: UUID;
facultad_id: UUID;
nombre: string;
nombre_corto: string | null;
clave_sep: string | null;
activa: boolean;
creado_en: string;
actualizado_en: string;
facultades?: Facultad | null;
};
export type EstructuraPlan = {
id: UUID;
nombre: string;
tipo: TipoEstructuraPlan;
version: string | null;
definicion: Json;
};
export type EstructuraPlanRow = Tables<"estructuras_plan">;
export type EstructuraAsignatura = {
id: UUID;
@@ -98,41 +71,13 @@ export type EstructuraAsignatura = {
definicion: Json;
};
export type EstadoPlan = {
id: UUID;
clave: string;
etiqueta: string;
orden: number;
es_final: boolean;
};
export type EstadoPlanRow = Tables<"estados_plan">;
export type PlanEstudioRow = Tables<"planes_estudio">;
export type PlanEstudio = {
id: UUID;
carrera_id: UUID;
estructura_id: UUID;
nombre: string;
nivel: NivelPlanEstudio;
tipo_ciclo: TipoCiclo;
numero_ciclos: number;
datos: Json;
estado_actual_id: UUID | null;
activo: boolean;
tipo_origen: TipoOrigen | null;
meta_origen: Json;
creado_por: UUID | null;
actualizado_por: UUID | null;
creado_en: string;
actualizado_en: string;
carreras?: Carrera | null;
estructuras_plan?: EstructuraPlan | null;
estados_plan?: EstadoPlan | null;
export type PlanEstudio = PlanEstudioRow & {
carreras: (CarreraRow & { facultades: FacultadRow | null }) | null;
estructuras_plan: EstructuraPlanRow | null;
estados_plan: EstadoPlanRow | null;
};
export type LineaPlan = {

View File

@@ -2,10 +2,11 @@ import { RouterProvider, createRouter } from '@tanstack/react-router'
import { StrictMode } from 'react'
import ReactDOM from 'react-dom/client'
import * as TanStackQueryProvider from './integrations/tanstack-query/root-provider.tsx'
import reportWebVitals from './reportWebVitals.ts'
import { routeTree } from './routeTree.gen'
import * as TanStackQueryProvider from '@/data/query/queryClient.tsx'
import './styles.css'
// Create a new router instance

View File

@@ -12,6 +12,7 @@ import { Route as rootRouteImport } from './routes/__root'
import { Route as LoginRouteImport } from './routes/login'
import { Route as DashboardRouteImport } from './routes/dashboard'
import { Route as IndexRouteImport } from './routes/index'
import { Route as PlanesPlanesListRouteRouteImport } from './routes/planes/PlanesListRoute'
import { Route as DemoTanstackQueryRouteImport } from './routes/demo/tanstack-query'
import { Route as PlanesListaRouteRouteImport } from './routes/planes/_lista/route'
import { Route as PlanesListaNuevoRouteImport } from './routes/planes/_lista/nuevo'
@@ -44,6 +45,11 @@ const IndexRoute = IndexRouteImport.update({
path: '/',
getParentRoute: () => rootRouteImport,
} as any)
const PlanesPlanesListRouteRoute = PlanesPlanesListRouteRouteImport.update({
id: '/planes/PlanesListRoute',
path: '/planes/PlanesListRoute',
getParentRoute: () => rootRouteImport,
} as any)
const DemoTanstackQueryRoute = DemoTanstackQueryRouteImport.update({
id: '/demo/tanstack-query',
path: '/demo/tanstack-query',
@@ -142,6 +148,7 @@ export interface FileRoutesByFullPath {
'/login': typeof LoginRoute
'/planes': typeof PlanesListaRouteRouteWithChildren
'/demo/tanstack-query': typeof DemoTanstackQueryRoute
'/planes/PlanesListRoute': typeof PlanesPlanesListRouteRoute
'/planes/$planId': typeof PlanesPlanIdDetalleRouteRouteWithChildren
'/planes/$planId/asignaturas': typeof PlanesPlanIdAsignaturasListaRouteRouteWithChildren
'/planes/nuevo': typeof PlanesListaNuevoRoute
@@ -162,6 +169,7 @@ export interface FileRoutesByTo {
'/login': typeof LoginRoute
'/planes': typeof PlanesListaRouteRouteWithChildren
'/demo/tanstack-query': typeof DemoTanstackQueryRoute
'/planes/PlanesListRoute': typeof PlanesPlanesListRouteRoute
'/planes/$planId': typeof PlanesPlanIdDetalleRouteRouteWithChildren
'/planes/nuevo': typeof PlanesListaNuevoRoute
'/planes/$planId/asignaturas/$asignaturaId': typeof PlanesPlanIdAsignaturasAsignaturaIdRouteRoute
@@ -182,6 +190,7 @@ export interface FileRoutesById {
'/login': typeof LoginRoute
'/planes/_lista': typeof PlanesListaRouteRouteWithChildren
'/demo/tanstack-query': typeof DemoTanstackQueryRoute
'/planes/PlanesListRoute': typeof PlanesPlanesListRouteRoute
'/planes/$planId/_detalle': typeof PlanesPlanIdDetalleRouteRouteWithChildren
'/planes/$planId/asignaturas': typeof PlanesPlanIdAsignaturasRouteRouteWithChildren
'/planes/_lista/nuevo': typeof PlanesListaNuevoRoute
@@ -205,6 +214,7 @@ export interface FileRouteTypes {
| '/login'
| '/planes'
| '/demo/tanstack-query'
| '/planes/PlanesListRoute'
| '/planes/$planId'
| '/planes/$planId/asignaturas'
| '/planes/nuevo'
@@ -225,6 +235,7 @@ export interface FileRouteTypes {
| '/login'
| '/planes'
| '/demo/tanstack-query'
| '/planes/PlanesListRoute'
| '/planes/$planId'
| '/planes/nuevo'
| '/planes/$planId/asignaturas/$asignaturaId'
@@ -244,6 +255,7 @@ export interface FileRouteTypes {
| '/login'
| '/planes/_lista'
| '/demo/tanstack-query'
| '/planes/PlanesListRoute'
| '/planes/$planId/_detalle'
| '/planes/$planId/asignaturas'
| '/planes/_lista/nuevo'
@@ -266,6 +278,7 @@ export interface RootRouteChildren {
LoginRoute: typeof LoginRoute
PlanesListaRouteRoute: typeof PlanesListaRouteRouteWithChildren
DemoTanstackQueryRoute: typeof DemoTanstackQueryRoute
PlanesPlanesListRouteRoute: typeof PlanesPlanesListRouteRoute
PlanesPlanIdDetalleRouteRoute: typeof PlanesPlanIdDetalleRouteRouteWithChildren
PlanesPlanIdAsignaturasRouteRoute: typeof PlanesPlanIdAsignaturasRouteRouteWithChildren
}
@@ -293,6 +306,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof IndexRouteImport
parentRoute: typeof rootRouteImport
}
'/planes/PlanesListRoute': {
id: '/planes/PlanesListRoute'
path: '/planes/PlanesListRoute'
fullPath: '/planes/PlanesListRoute'
preLoaderRoute: typeof PlanesPlanesListRouteRouteImport
parentRoute: typeof rootRouteImport
}
'/demo/tanstack-query': {
id: '/demo/tanstack-query'
path: '/demo/tanstack-query'
@@ -486,6 +506,7 @@ const rootRouteChildren: RootRouteChildren = {
LoginRoute: LoginRoute,
PlanesListaRouteRoute: PlanesListaRouteRouteWithChildren,
DemoTanstackQueryRoute: DemoTanstackQueryRoute,
PlanesPlanesListRouteRoute: PlanesPlanesListRouteRoute,
PlanesPlanIdDetalleRouteRoute: PlanesPlanIdDetalleRouteRouteWithChildren,
PlanesPlanIdAsignaturasRouteRoute:
PlanesPlanIdAsignaturasRouteRouteWithChildren,

View File

@@ -0,0 +1,42 @@
import { createFileRoute } from '@tanstack/react-router'
import { useMemo, useState } from 'react'
import { usePlanes } from '@/data'
export const Route = createFileRoute('/planes/PlanesListRoute')({
component: RouteComponent,
})
function RouteComponent() {
const [search, setSearch] = useState('')
const filters = useMemo(
() => ({ search, limit: 20, offset: 0, activo: true }),
[search],
)
const { data, isLoading, isError, error } = usePlanes(filters)
return (
<div style={{ padding: 16 }}>
<h1>Planes</h1>
<input
value={search}
onChange={(e) => setSearch(e.target.value)}
placeholder="Buscar…"
/>
{isLoading && <div>Cargando</div>}
{isError && <div>Error: {(error as any).message}</div>}
<ul>
{(data?.data ?? []).map((p) => (
<li key={p.id}>
<pre>{JSON.stringify(p, null, 2)}</pre>
</li>
))}
</ul>
</div>
)
}

BIN
src/types/supabase.ts Normal file

Binary file not shown.