From 3bb3a11779f8b8414253970b20244e7b64230049 Mon Sep 17 00:00:00 2001 From: Guillermo Arrieta Medina Date: Tue, 24 Mar 2026 20:43:29 -0600 Subject: [PATCH] wip ya funciona la busqueda de asignaturas, pero ocurre un bug en la insercion de la asignatura clonada --- .../wizard/PasoFuenteClonadoInterno.tsx | 178 ++++++++++++++---- .../asignaturas/wizard/WizardControls.tsx | 2 + src/types/supabase.ts | 28 +++ 3 files changed, 174 insertions(+), 34 deletions(-) diff --git a/src/components/asignaturas/wizard/PasoFuenteClonadoInterno.tsx b/src/components/asignaturas/wizard/PasoFuenteClonadoInterno.tsx index 7178f34..c43123a 100644 --- a/src/components/asignaturas/wizard/PasoFuenteClonadoInterno.tsx +++ b/src/components/asignaturas/wizard/PasoFuenteClonadoInterno.tsx @@ -4,6 +4,7 @@ import { useEffect, useMemo } from 'react' import { useDebounce } from 'use-debounce' import type { NewSubjectWizardState } from '@/features/asignaturas/nueva/types' +import type { Database } from '@/types/supabase' import Pagination03 from '@/components/shadcn-studio/pagination/pagination-03' import { Button } from '@/components/ui/button' @@ -28,12 +29,13 @@ type SourceSubjectRow = { tipo: any plan_estudio_id: string estructura_id: string | null + rank?: number } const ALL = '__all__' -const normalizeLikeTerm = (term: string) => - term.trim().replace(/[(),]/g, ' ').replace(/\s+/g, ' ') +type SearchAsignaturasRow = + Database['public']['Functions']['search_asignaturas']['Returns'][number] export function PasoFuenteClonadoInterno({ wizard, @@ -95,41 +97,146 @@ export function PasoFuenteClonadoInterno({ const from = (page - 1) * pageSize const to = from + pageSize - 1 - let q = supabase - .from('asignaturas') - .select( - 'id,nombre,codigo,creditos,tipo,plan_estudio_id,estructura_id', - { - count: 'exact', - }, - ) - .order('nombre', { ascending: true }) + const term = debouncedSearch.trim() - if (planOrigenId) { - q = q.eq('plan_estudio_id', planOrigenId) - } else if (needPlansForFilter) { - const planIds = plansForFilter.map((p) => p.id) - if (!planIds.length) { - return { data: [] as Array, count: 0 } - } - q = q.in('plan_estudio_id', planIds) - } - - const term = normalizeLikeTerm(debouncedSearch) + // Full text search (tsvector) para el campo Buscar. + // Si no hay término, conservamos el listado base por nombre. if (term) { - // PostgREST OR syntax - q = q.or(`nombre.ilike.%${term}%,codigo.ilike.%${term}%`) + const mapRow = (r: SearchAsignaturasRow): SourceSubjectRow => ({ + id: r.id, + nombre: r.nombre, + codigo: r.codigo, + creditos: Number(r.creditos), + tipo: r.tipo, + plan_estudio_id: r.plan_estudio_id, + estructura_id: null, + rank: r.rank, + }) + + if (planOrigenId) { + const args: Database['public']['Functions']['search_asignaturas']['Args'] = + { + p_search: term, + p_plan_estudio_id: planOrigenId, + p_limit: pageSize, + p_offset: from, + } + + const { data, error, count } = await supabase.rpc( + 'search_asignaturas', + args, + { count: 'exact' }, + ) + if (error) throw new Error(error.message) + + return { + data: data.map(mapRow), + count: count ?? 0, + } + } + + if (needPlansForFilter) { + const planIds = plansForFilter.map((p) => p.id) + if (!planIds.length) { + return { data: [] as Array, count: 0 } + } + + const perPlanLimit = pageSize * page + + const perPlan = await Promise.all( + planIds.map(async (planId) => { + const args: Database['public']['Functions']['search_asignaturas']['Args'] = + { + p_search: term, + p_plan_estudio_id: planId, + p_limit: perPlanLimit, + p_offset: 0, + } + + const { data, error, count } = await supabase.rpc( + 'search_asignaturas', + args, + { count: 'exact' }, + ) + if (error) throw new Error(error.message) + + return { + data, + count: count ?? 0, + } + }), + ) + + const merged = perPlan + .flatMap((p) => p.data) + .map(mapRow) + .sort((a, b) => { + const ar = a.rank ?? 0 + const br = b.rank ?? 0 + if (br !== ar) return br - ar + const byName = a.nombre.localeCompare(b.nombre, 'es') + if (byName !== 0) return byName + return String(a.id).localeCompare(String(b.id)) + }) + + const pageData = merged.slice(from, to + 1) + const totalCount = perPlan.reduce((acc, p) => acc + p.count, 0) + + return { + data: pageData, + count: totalCount, + } + } + + const args: Database['public']['Functions']['search_asignaturas']['Args'] = + { + p_search: term, + p_limit: pageSize, + p_offset: from, + } + + const { data, error, count } = await supabase.rpc( + 'search_asignaturas', + args, + { count: 'exact' }, + ) + if (error) throw new Error(error.message) + + return { + data: data.map(mapRow), + count: count ?? 0, + } } - q = q.range(from, to) + // let q = supabase + // .from('asignaturas') + // .select( + // 'id,nombre,codigo,creditos,tipo,plan_estudio_id,estructura_id', + // { + // count: 'exact', + // }, + // ) + // .order('nombre', { ascending: true }) - const { data, error, count } = await q - if (error) throw new Error(error.message) + // if (planOrigenId) { + // q = q.eq('plan_estudio_id', planOrigenId) + // } else if (needPlansForFilter) { + // const planIds = plansForFilter.map((p) => p.id) + // if (!planIds.length) { + // return { data: [] as Array, count: 0 } + // } + // q = q.in('plan_estudio_id', planIds) + // } - return { - data: data as unknown as Array, - count: count ?? 0, - } + // q = q.range(from, to) + + // const { data, error, count } = await q + // if (error) throw new Error(error.message) + + // return { + // data: data as unknown as Array, + // count: count ?? 0, + // } }, }) @@ -188,7 +295,7 @@ export function PasoFuenteClonadoInterno({ resetSelection() }} > - + @@ -217,7 +324,7 @@ export function PasoFuenteClonadoInterno({ }} disabled={!facultadId} > - + @@ -243,7 +350,10 @@ export function PasoFuenteClonadoInterno({ resetSelection() }} > - + diff --git a/src/components/asignaturas/wizard/WizardControls.tsx b/src/components/asignaturas/wizard/WizardControls.tsx index 2cc56c5..8f29fdb 100644 --- a/src/components/asignaturas/wizard/WizardControls.tsx +++ b/src/components/asignaturas/wizard/WizardControls.tsx @@ -257,6 +257,8 @@ export function WizardControls({ null, } + console.log('payload:', payload) + const { data: inserted, error: insertError } = await supabase .from('asignaturas') .insert(payload) diff --git a/src/types/supabase.ts b/src/types/supabase.ts index bafbe23..9fee47d 100644 --- a/src/types/supabase.ts +++ b/src/types/supabase.ts @@ -155,6 +155,7 @@ export type Database = { orden_celda: number | null plan_estudio_id: string prerrequisito_asignatura_id: string | null + search_vector: unknown tipo: Database['public']['Enums']['tipo_asignatura'] tipo_origen: Database['public']['Enums']['tipo_origen'] | null } @@ -181,6 +182,7 @@ export type Database = { orden_celda?: number | null plan_estudio_id: string prerrequisito_asignatura_id?: string | null + search_vector?: unknown tipo?: Database['public']['Enums']['tipo_asignatura'] tipo_origen?: Database['public']['Enums']['tipo_origen'] | null } @@ -207,6 +209,7 @@ export type Database = { orden_celda?: number | null plan_estudio_id?: string prerrequisito_asignatura_id?: string | null + search_vector?: unknown tipo?: Database['public']['Enums']['tipo_asignatura'] tipo_origen?: Database['public']['Enums']['tipo_origen'] | null } @@ -1393,6 +1396,31 @@ export type Database = { Args: { p_append: Json; p_id: string } Returns: undefined } + build_asignaturas_prefix_tsquery: { + Args: { p_search: string } + Returns: unknown + } + search_asignaturas: { + Args: { + p_limit?: number + p_offset?: number + p_plan_estudio_id?: string + p_search: string + } + Returns: Array<{ + codigo: string + contenido_tematico: Json + creditos: number + datos: Json + estado: Database['public']['Enums']['estado_asignatura'] + id: string + nombre: string + numero_ciclo: number + plan_estudio_id: string + rank: number + tipo: Database['public']['Enums']['tipo_asignatura'] + }> + } suma_porcentajes: { Args: { '': Json }; Returns: number } unaccent: { Args: { '': string }; Returns: string } unaccent_immutable: { Args: { '': string }; Returns: string }