migraciones para soportar el full text search
Deploy Function / deploy (pull_request) Successful in 31s
Deploy Migrations to Production / deploy (pull_request) Successful in 12s

This commit is contained in:
2026-03-25 16:44:11 -06:00
parent 220b830aa8
commit 542f2bc61a
3 changed files with 111 additions and 39 deletions
+1 -1
View File
@@ -1 +1 @@
v2.78.1 v2.84.2
+61 -5
View File
@@ -1,4 +1,4 @@
export type Json = export type Json =
| string | string
| number | number
| boolean | boolean
@@ -7,10 +7,30 @@ export type Json =
| Json[] | Json[]
export type Database = { export type Database = {
// Allows to automatically instantiate createClient with right options graphql_public: {
// instead of createClient<Database, { PostgrestVersion: 'XX' }>(URL, KEY) Tables: {
__InternalSupabase: { [_ in never]: never
PostgrestVersion: "14.1" }
Views: {
[_ in never]: never
}
Functions: {
graphql: {
Args: {
extensions?: Json
operationName?: string
query?: string
variables?: Json
}
Returns: Json
}
}
Enums: {
[_ in never]: never
}
CompositeTypes: {
[_ in never]: never
}
} }
public: { public: {
Tables: { Tables: {
@@ -135,6 +155,7 @@ export type Database = {
orden_celda: number | null orden_celda: number | null
plan_estudio_id: string plan_estudio_id: string
prerrequisito_asignatura_id: string | null prerrequisito_asignatura_id: string | null
search_vector: unknown
tipo: Database["public"]["Enums"]["tipo_asignatura"] tipo: Database["public"]["Enums"]["tipo_asignatura"]
tipo_origen: Database["public"]["Enums"]["tipo_origen"] | null tipo_origen: Database["public"]["Enums"]["tipo_origen"] | null
} }
@@ -161,6 +182,7 @@ export type Database = {
orden_celda?: number | null orden_celda?: number | null
plan_estudio_id: string plan_estudio_id: string
prerrequisito_asignatura_id?: string | null prerrequisito_asignatura_id?: string | null
search_vector?: unknown
tipo?: Database["public"]["Enums"]["tipo_asignatura"] tipo?: Database["public"]["Enums"]["tipo_asignatura"]
tipo_origen?: Database["public"]["Enums"]["tipo_origen"] | null tipo_origen?: Database["public"]["Enums"]["tipo_origen"] | null
} }
@@ -187,6 +209,7 @@ export type Database = {
orden_celda?: number | null orden_celda?: number | null
plan_estudio_id?: string plan_estudio_id?: string
prerrequisito_asignatura_id?: string | null prerrequisito_asignatura_id?: string | null
search_vector?: unknown
tipo?: Database["public"]["Enums"]["tipo_asignatura"] tipo?: Database["public"]["Enums"]["tipo_asignatura"]
tipo_origen?: Database["public"]["Enums"]["tipo_origen"] | null tipo_origen?: Database["public"]["Enums"]["tipo_origen"] | null
} }
@@ -1373,6 +1396,35 @@ export type Database = {
Args: { p_append: Json; p_id: string } Args: { p_append: Json; p_id: string }
Returns: undefined Returns: undefined
} }
build_asignaturas_prefix_tsquery: {
Args: { p_search: string }
Returns: unknown
}
recalcular_vectores_asignaturas: { Args: never; Returns: undefined }
search_asignaturas: {
Args: {
p_carrera_id?: string
p_facultad_id?: string
p_limit?: number
p_offset?: number
p_plan_estudio_id?: string
p_search?: string
}
Returns: {
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"]
total_count: number
}[]
}
suma_porcentajes: { Args: { "": Json }; Returns: number } suma_porcentajes: { Args: { "": Json }; Returns: number }
unaccent: { Args: { "": string }; Returns: string } unaccent: { Args: { "": string }; Returns: string }
unaccent_immutable: { Args: { "": string }; Returns: string } unaccent_immutable: { Args: { "": string }; Returns: string }
@@ -1548,6 +1600,9 @@ export type CompositeTypes<
: never : never
export const Constants = { export const Constants = {
graphql_public: {
Enums: {},
},
public: { public: {
Enums: { Enums: {
estado_asignatura: ["borrador", "revisada", "aprobada", "generando"], estado_asignatura: ["borrador", "revisada", "aprobada", "generando"],
@@ -1607,3 +1662,4 @@ export const Constants = {
}, },
}, },
} as const } as const
@@ -52,27 +52,18 @@ for each row
execute function public.fn_asignaturas_update_search_vector(); execute function public.fn_asignaturas_update_search_vector();
-- Paso 5 -- Paso 5
update public.asignaturas create or replace function public.recalcular_vectores_asignaturas()
set search_vector = returns void
setweight( language sql
to_tsvector('public.es_simple_unaccent', coalesce(nombre, '')), as $$
'A' UPDATE public.asignaturas
) SET search_vector =
|| setweight(to_tsvector('public.es_simple_unaccent', coalesce(nombre, '')), 'A') ||
setweight( setweight(to_tsvector('public.es_simple_unaccent', coalesce(codigo, '')), 'A') ||
to_tsvector('public.es_simple_unaccent', coalesce(codigo, '')), setweight(to_tsvector('public.es_simple_unaccent', coalesce(datos, '{}'::jsonb)::text), 'B') ||
'A' setweight(to_tsvector('public.es_simple_unaccent', coalesce(contenido_tematico, '[]'::jsonb)::text), 'B')
) WHERE id IS NOT NULL;
|| $$;
setweight(
to_tsvector('public.es_simple_unaccent', coalesce(datos, '{}'::jsonb)::text),
'B'
)
||
setweight(
to_tsvector('public.es_simple_unaccent', coalesce(contenido_tematico, '[]'::jsonb)::text),
'B'
);
-- Paso 6 -- Paso 6
create index if not exists asignaturas_search_vector_gin_idx create index if not exists asignaturas_search_vector_gin_idx
@@ -117,7 +108,9 @@ $$;
-- Paso 8 -- Paso 8
create or replace function public.search_asignaturas( create or replace function public.search_asignaturas(
p_search text, p_search text default '',
p_facultad_id uuid default null,
p_carrera_id uuid default null,
p_plan_estudio_id uuid default null, p_plan_estudio_id uuid default null,
p_limit integer default 20, p_limit integer default 20,
p_offset integer default 0 p_offset integer default 0
@@ -133,14 +126,19 @@ returns table (
datos jsonb, datos jsonb,
contenido_tematico jsonb, contenido_tematico jsonb,
estado public.estado_asignatura, estado public.estado_asignatura,
rank real rank real,
total_count bigint -- 👈 Total para la paginación del frontend
) )
language sql language plpgsql
stable stable
as $$ as $$
with q as ( declare
select public.build_asignaturas_prefix_tsquery(p_search) as tsq v_tsq tsquery;
) begin
-- 1. Construimos el query solo si hay texto
v_tsq := public.build_asignaturas_prefix_tsquery(p_search);
return query
select select
a.id, a.id,
a.plan_estudio_id, a.plan_estudio_id,
@@ -152,16 +150,34 @@ as $$
a.datos, a.datos,
a.contenido_tematico, a.contenido_tematico,
a.estado, a.estado,
ts_rank(a.search_vector, q.tsq) as rank coalesce(ts_rank(a.search_vector, v_tsq), 0)::real as rank,
count(*) OVER() as total_count -- 👈 Cuenta total ignorando el LIMIT
from public.asignaturas a from public.asignaturas a
cross join q -- 2. JOINS para poder filtrar por la jerarquía superior
left join public.planes_estudio p on a.plan_estudio_id = p.id
left join public.carreras c on p.carrera_id = c.id
where where
q.tsq is not null -- 3. Si no hay búsqueda, trae todo. Si hay búsqueda, usa el FTS
and a.search_vector @@ q.tsq (v_tsq is null or a.search_vector @@ v_tsq)
-- 4. Filtros jerárquicos dinámicos
and (p_plan_estudio_id is null or a.plan_estudio_id = p_plan_estudio_id) and (p_plan_estudio_id is null or a.plan_estudio_id = p_plan_estudio_id)
and (p_carrera_id is null or p.carrera_id = p_carrera_id)
and (p_facultad_id is null or c.facultad_id = p_facultad_id)
order by order by
rank desc, (case when v_tsq is not null then ts_rank(a.search_vector, v_tsq) else 0 end) desc,
a.nombre asc a.nombre asc
limit p_limit limit p_limit
offset p_offset; offset p_offset;
$$; end;
$$;
-- Corrección de bug con triggers
-- 1. Borramos la llave foránea estricta actual
ALTER TABLE public.cambios_asignatura
DROP CONSTRAINT cambios_asignatura_asignatura_id_fkey;
-- 2. La volvemos a crear, pero le decimos que espere al final de la transacción para validar
ALTER TABLE public.cambios_asignatura
ADD CONSTRAINT cambios_asignatura_asignatura_id_fkey
FOREIGN KEY (asignatura_id) REFERENCES public.asignaturas(id)
DEFERRABLE INITIALLY DEFERRED;