From 93c7f11621d5274337da7a22cafd655032619702 Mon Sep 17 00:00:00 2001 From: Guillermo Arrieta Medina Date: Tue, 10 Feb 2026 12:22:39 -0600 Subject: [PATCH 1/7] =?UTF-8?q?nueva=20edge=20function=20para=20la=20gener?= =?UTF-8?q?aci=C3=B3n=20de=20sugerencais=20de=20asignaturas=20con=20IA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- supabase/config.toml | 11 +++++++ supabase/functions/_shared/database.types.ts | 3 ++ .../generate-subject-suggestions/.npmrc | 3 ++ .../generate-subject-suggestions/deno.json | 3 ++ .../generate-subject-suggestions/index.ts | 32 +++++++++++++++++++ ...6172724_conversation_id_en_asignaturas.sql | 16 ++++++++++ 6 files changed, 68 insertions(+) create mode 100644 supabase/functions/generate-subject-suggestions/.npmrc create mode 100644 supabase/functions/generate-subject-suggestions/deno.json create mode 100644 supabase/functions/generate-subject-suggestions/index.ts create mode 100644 supabase/migrations/20260206172724_conversation_id_en_asignaturas.sql diff --git a/supabase/config.toml b/supabase/config.toml index 5f119d7..98b01a5 100644 --- a/supabase/config.toml +++ b/supabase/config.toml @@ -37,3 +37,14 @@ entrypoint = "./functions/ai-generate-subject/index.ts" # Specifies static files to be bundled with the function. Supports glob patterns. # For example, if you want to serve static HTML pages in your function: # static_files = [ "./functions/ai-generate-subject/*.html" ] + +[functions.generate-subject-suggestions] +enabled = true +verify_jwt = true +import_map = "./functions/generate-subject-suggestions/deno.json" +# Uncomment to specify a custom file path to the entrypoint. +# Supported file extensions are: .ts, .js, .mjs, .jsx, .tsx +entrypoint = "./functions/generate-subject-suggestions/index.ts" +# Specifies static files to be bundled with the function. Supports glob patterns. +# For example, if you want to serve static HTML pages in your function: +# static_files = [ "./functions/generate-subject-suggestions/*.html" ] diff --git a/supabase/functions/_shared/database.types.ts b/supabase/functions/_shared/database.types.ts index bf4b960..80007b3 100644 --- a/supabase/functions/_shared/database.types.ts +++ b/supabase/functions/_shared/database.types.ts @@ -93,6 +93,7 @@ export type Database = { asignatura_hash: string | null; codigo: string | null; contenido_tematico: Json; + conversation_id: string | null; creado_en: string; creado_por: string | null; creditos: number; @@ -118,6 +119,7 @@ export type Database = { asignatura_hash?: string | null; codigo?: string | null; contenido_tematico?: Json; + conversation_id?: string | null; creado_en?: string; creado_por?: string | null; creditos: number; @@ -143,6 +145,7 @@ export type Database = { asignatura_hash?: string | null; codigo?: string | null; contenido_tematico?: Json; + conversation_id?: string | null; creado_en?: string; creado_por?: string | null; creditos?: number; diff --git a/supabase/functions/generate-subject-suggestions/.npmrc b/supabase/functions/generate-subject-suggestions/.npmrc new file mode 100644 index 0000000..48c6388 --- /dev/null +++ b/supabase/functions/generate-subject-suggestions/.npmrc @@ -0,0 +1,3 @@ +# Configuration for private npm package dependencies +# For more information on using private registries with Edge Functions, see: +# https://supabase.com/docs/guides/functions/import-maps#importing-from-private-registries diff --git a/supabase/functions/generate-subject-suggestions/deno.json b/supabase/functions/generate-subject-suggestions/deno.json new file mode 100644 index 0000000..f6ca845 --- /dev/null +++ b/supabase/functions/generate-subject-suggestions/deno.json @@ -0,0 +1,3 @@ +{ + "imports": {} +} diff --git a/supabase/functions/generate-subject-suggestions/index.ts b/supabase/functions/generate-subject-suggestions/index.ts new file mode 100644 index 0000000..439e4d0 --- /dev/null +++ b/supabase/functions/generate-subject-suggestions/index.ts @@ -0,0 +1,32 @@ +// Follow this setup guide to integrate the Deno language server with your editor: +// https://deno.land/manual/getting_started/setup_your_environment +// This enables autocomplete, go to definition, etc. + +// Setup type definitions for built-in Supabase Runtime APIs +import "jsr:@supabase/functions-js/edge-runtime.d.ts" + +console.log("Hello from Functions!") + +Deno.serve(async (req) => { + const { name } = await req.json() + const data = { + message: `Hello ${name}!`, + } + + return new Response( + JSON.stringify(data), + { headers: { "Content-Type": "application/json" } }, + ) +}) + +/* To invoke locally: + + 1. Run `supabase start` (see: https://supabase.com/docs/reference/cli/supabase-start) + 2. Make an HTTP request: + + curl -i --location --request POST 'http://127.0.0.1:54321/functions/v1/generate-subject-suggestions' \ + --header 'Authorization: Bearer eyJhbGciOiJFUzI1NiIsImtpZCI6ImI4MTI2OWYxLTIxZDgtNGYyZS1iNzE5LWMyMjQwYTg0MGQ5MCIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjIwODYxMDc1NTl9.68Q-uYZ-5EHQfzzqL38ivfGTUYyQ_ncJ34eAivWwl2So5NcvWGL9DSjg-fJ8UA9NKqnH5r-i8Z1bONE9LPMwBg' \ + --header 'Content-Type: application/json' \ + --data '{"name":"Functions"}' + +*/ diff --git a/supabase/migrations/20260206172724_conversation_id_en_asignaturas.sql b/supabase/migrations/20260206172724_conversation_id_en_asignaturas.sql new file mode 100644 index 0000000..4ac4f50 --- /dev/null +++ b/supabase/migrations/20260206172724_conversation_id_en_asignaturas.sql @@ -0,0 +1,16 @@ +drop view if exists "public"."plantilla_plan"; + +alter table "public"."asignaturas" add column "conversation_id" text; + +CREATE UNIQUE INDEX asignaturas_conversation_id_key ON public.asignaturas USING btree (conversation_id); + +alter table "public"."asignaturas" add constraint "asignaturas_conversation_id_key" UNIQUE using index "asignaturas_conversation_id_key"; + +create or replace view "public"."plantilla_plan" as SELECT plan.id AS plan_estudio_id, + struct.id AS estructura_id, + struct.template_id + FROM (public.planes_estudio plan + JOIN public.estructuras_plan struct ON ((plan.estructura_id = struct.id))); + + + -- 2.52.0 From ce5360a5c2e5a896e79a14bdfaef82ba735af314 Mon Sep 17 00:00:00 2001 From: Guillermo Arrieta Medina Date: Tue, 10 Feb 2026 16:38:33 -0600 Subject: [PATCH 2/7] wip --- deno.json | 1 + .../generate-subject-suggestions/deno.json | 8 +- .../generate-subject-suggestions/index.ts | 198 ++++++++++++++++-- .../sugerencias.ts | 118 +++++++++++ 4 files changed, 301 insertions(+), 24 deletions(-) create mode 100644 supabase/functions/generate-subject-suggestions/sugerencias.ts diff --git a/deno.json b/deno.json index 2128fe4..7270a0b 100644 --- a/deno.json +++ b/deno.json @@ -10,6 +10,7 @@ "@david/dax": "jsr:@david/dax@^0.44.2", "zod": "https://deno.land/x/zod@v3.22.4/mod.ts", "openai": "npm:openai@6.16.0", + "openai/helpers/zod": "npm:openai@6.16.0/helpers/zod", "@supabase/supabase-js": "npm:@supabase/supabase-js@2", "@supabase/functions-js/edge-runtime.d.ts": "jsr:@supabase/functions-js@^2/edge-runtime.d.ts" } diff --git a/supabase/functions/generate-subject-suggestions/deno.json b/supabase/functions/generate-subject-suggestions/deno.json index f6ca845..a42ac38 100644 --- a/supabase/functions/generate-subject-suggestions/deno.json +++ b/supabase/functions/generate-subject-suggestions/deno.json @@ -1,3 +1,9 @@ { - "imports": {} + "imports": { + "zod": "https://deno.land/x/zod@v3.22.4/mod.ts", + "openai": "npm:openai@6.16.0", + "openai/helpers/zod": "npm:openai@6.16.0/helpers/zod", + "@supabase/supabase-js": "npm:@supabase/supabase-js@2", + "@supabase/functions-js/edge-runtime.d.ts": "jsr:@supabase/functions-js@^2/edge-runtime.d.ts" + } } diff --git a/supabase/functions/generate-subject-suggestions/index.ts b/supabase/functions/generate-subject-suggestions/index.ts index 439e4d0..4ec4ce3 100644 --- a/supabase/functions/generate-subject-suggestions/index.ts +++ b/supabase/functions/generate-subject-suggestions/index.ts @@ -1,32 +1,184 @@ -// Follow this setup guide to integrate the Deno language server with your editor: -// https://deno.land/manual/getting_started/setup_your_environment -// This enables autocomplete, go to definition, etc. +import "@supabase/functions-js/edge-runtime.d.ts"; +import { corsHeaders } from "../_shared/cors.ts"; +import { HttpError, sendError, sendSuccess } from "../_shared/utils.ts"; +import type { Tables } from "../_shared/database.types.ts"; +import { z } from "zod"; +import { zodTextFormat } from "openai/helpers/zod"; +import { + OpenAIService, + type StructuredResponseOptions, +} from "../_shared/openai-service.ts"; -// Setup type definitions for built-in Supabase Runtime APIs -import "jsr:@supabase/functions-js/edge-runtime.d.ts" +export type DataAsignaturaSugerida = { + nombre: Tables<"asignaturas">["nombre"]; + codigo?: Tables<"asignaturas">["codigo"]; + tipo: Tables<"asignaturas">["tipo"] | null; + creditos: Tables<"asignaturas">["creditos"] | null; + horasAcademicas?: number | null; + horasIndependientes?: number | null; + descripcion?: string; +}; -console.log("Hello from Functions!") +const AsignaturaSugeridaItemSchema: z.ZodType = z + .object({ + nombre: z.string().describe("Nombre de la asignatura a crear"), + codigo: z.string().optional().describe( + "Código o clave de la asignatura. Un string único que la identifique, como 'MAT101' o 'FIS202'. Opcional, pero recomendado para evitar confusiones.", + ), + tipo: z.enum(["TRONCAL", "OBLIGATORIA", "OPTATIVA", "OTRA"]).nullable(), + creditos: z.number().nullable(), + horasAcademicas: z.number().optional().nullable(), + horasIndependientes: z.number().optional().nullable(), + descripcion: z.string().optional(), + }); -Deno.serve(async (req) => { - const { name } = await req.json() - const data = { - message: `Hello ${name}!`, +const AsignaturaSugeridaSchema = z + .array(AsignaturaSugeridaItemSchema) + .length(6) + .describe("Arreglo de 6 sugerencias de asignatura"); + +Deno.serve(async (req: Request): Promise => { + const url = new URL(req.url); + const functionName = url.pathname.split("/").pop(); + console.log( + `[${new Date().toISOString()}][${functionName}]: Request received`, + ); + + if (req.method === "OPTIONS") { + return new Response(null, { status: 204, headers: corsHeaders }); } - return new Response( - JSON.stringify(data), - { headers: { "Content-Type": "application/json" } }, - ) -}) + try { + const method = req.method; + if (method !== "POST") { + throw new HttpError( + 405, + "Método no permitido.", + "METHOD_NOT_ALLOWED", + { method }, + ); + } -/* To invoke locally: + const authHeaderRaw = req.headers.get("Authorization") ?? + req.headers.get("authorization"); + if (!authHeaderRaw) { + throw new HttpError( + 401, + "No autorizado.", + "UNAUTHORIZED", + { reason: "missing_authorization_header" }, + ); + } - 1. Run `supabase start` (see: https://supabase.com/docs/reference/cli/supabase-start) - 2. Make an HTTP request: + const svc = OpenAIService.fromEnv(); + if (!(svc instanceof OpenAIService)) { + throw new HttpError( + 500, + "Configuración del servidor incompleta.", + "OPENAI_MISCONFIGURED", + svc, + ); + } - curl -i --location --request POST 'http://127.0.0.1:54321/functions/v1/generate-subject-suggestions' \ - --header 'Authorization: Bearer eyJhbGciOiJFUzI1NiIsImtpZCI6ImI4MTI2OWYxLTIxZDgtNGYyZS1iNzE5LWMyMjQwYTg0MGQ5MCIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjIwODYxMDc1NTl9.68Q-uYZ-5EHQfzzqL38ivfGTUYyQ_ncJ34eAivWwl2So5NcvWGL9DSjg-fJ8UA9NKqnH5r-i8Z1bONE9LPMwBg' \ - --header 'Content-Type: application/json' \ - --data '{"name":"Functions"}' + const options: StructuredResponseOptions = { + model: "gpt-4o-mini", + input: [ + { + role: "user", + content: [ + { + type: "input_text", + text: + "Genera 6 sugerencias de asignatura para el ciclo 6 de 9 del plan de estudios de una carrera de ingeniería mecánica.", + }, + ], + }, + ], + text: { + format: zodTextFormat( + AsignaturaSugeridaSchema, + "estructura_asignaturas", + ), + }, + }; -*/ + const aiResult = await svc.createStructuredResponse< + typeof AsignaturaSugeridaSchema._output + >( + options, + ); + if (!aiResult.ok) { + const status = aiResult.code === "MissingEnv" ? 500 : 502; + throw new HttpError( + status, + "No se pudieron generar sugerencias con IA.", + "OPENAI_REQUEST_FAILED", + aiResult, + ); + } + + let output = aiResult.output ?? null; + if (output == null && aiResult.outputText) { + try { + output = JSON.parse(aiResult.outputText); + } catch { + throw new HttpError( + 502, + "La respuesta de la IA no es JSON válido.", + "OPENAI_INVALID_JSON", + { outputText: aiResult.outputText }, + ); + } + } + if (output == null) { + throw new HttpError( + 502, + "La respuesta de la IA no contiene salida estructurada.", + "OPENAI_MISSING_STRUCTURED_OUTPUT", + { outputText: aiResult.outputText ?? null }, + ); + } + + const parsed = AsignaturaSugeridaSchema.safeParse(output); + if (!parsed.success) { + throw new HttpError( + 502, + "La salida estructurada no coincide con el esquema esperado.", + "OPENAI_SCHEMA_MISMATCH", + parsed.error, + ); + } + + return sendSuccess(parsed.data); + } catch (error) { + if (error instanceof HttpError) { + console.error( + `[${new Date().toISOString()}][${functionName}] ⚠️ Handled Error:`, + { + message: error.message, + code: error.code, + internalDetails: error.internalDetails || "N/A", + }, + ); + + return sendError(error.status, error.message, error.code); + } + + const unexpectedError = error instanceof Error + ? error + : new Error(String(error)); + + console.error( + `[${ + new Date().toISOString() + }][${functionName}] 💥 CRITICAL UNHANDLED ERROR:`, + unexpectedError.stack || unexpectedError.message, + ); + + return sendError( + 500, + "Ocurrió un error inesperado en el servidor.", + "INTERNAL_SERVER_ERROR", + ); + } +}); diff --git a/supabase/functions/generate-subject-suggestions/sugerencias.ts b/supabase/functions/generate-subject-suggestions/sugerencias.ts new file mode 100644 index 0000000..19b07ec --- /dev/null +++ b/supabase/functions/generate-subject-suggestions/sugerencias.ts @@ -0,0 +1,118 @@ +// ...existing code... +import type { DataAsignaturaSugerida } from "./index.ts"; + +export const sugerencias: DataAsignaturaSugerida[][] = [ + [ + { + nombre: "Transferencia de Calor", + tipo: "TRONCAL", + creditos: 5, + descripcion: + "Conducción, convección y radiación; diseño de aletas e intercambiadores; análisis transitorio y numérico en equipos térmicos.", + horasAcademicas: 43, + horasIndependientes: 35, + }, + { + nombre: "Mecánica de Fluidos II", + tipo: "TRONCAL", + creditos: 4.5, + descripcion: + "Flujo turbulento, capas límite, turbomáquinas y redes de tuberías; medición y control de caudal; CFD básico para aplicaciones mecánicas.", + horasAcademicas: 43, + horasIndependientes: 34, + }, + { + nombre: "Diseño de Elementos de Máquinas I", + tipo: "TRONCAL", + creditos: 5, + descripcion: + "Selección y dimensionamiento de ejes, tornillos, resortes, engranajes y uniones; criterios de falla estática y a fatiga; normas y materiales.", + horasAcademicas: 43, + horasIndependientes: 35, + }, + { + nombre: "Dinámica de Máquinas", + tipo: "TRONCAL", + creditos: 4.5, + descripcion: + "Modelado y análisis de mecanismos, cinemática y dinámica multicuerpo; fuerzas inerciales, equilibrado, resortes y amortiguadores, trenes de leva.", + horasAcademicas: 43, + horasIndependientes: 34, + }, + { + nombre: "Vibraciones Mecánicas", + tipo: "TRONCAL", + creditos: 4, + descripcion: + "Vibraciones libres y forzadas en 1 y múltiples grados; amortiguamiento, resonancia, aislamiento y balanceo; análisis modal y mediciones.", + horasAcademicas: 33, + horasIndependientes: 33, + }, + { + nombre: "Control Automático para Sistemas Mecánicos", + tipo: "TRONCAL", + creditos: 4, + descripcion: + "Modelado LTI, funciones de transferencia y espacio de estados; diseño PID y de compensadores; sensores y actuadores en sistemas mecánicos.", + horasAcademicas: 33, + horasIndependientes: 33, + }, + ], + [ + { + nombre: "Estructuras de Datos y Algoritmos", + tipo: "TRONCAL", + creditos: 6, + descripcion: + "Diseño, análisis e implementación de estructuras de datos (listas, pilas, colas, árboles, grafos, tablas hash) y algoritmos fundamentales; complejidad temporal y espacial; aplicación a resolución eficiente de problemas.", + horasAcademicas: 60, + horasIndependientes: 30, + }, + { + nombre: "Programación Orientada a Objetos", + tipo: "OBLIGATORIA", + creditos: 5, + descripcion: + "Principios y prácticas de la programación orientada a objetos: clases, objetos, herencia, polimorfismo, diseño basado en patrones, pruebas unitarias y gestión de proyectos simples en lenguajes modernos.", + horasAcademicas: 50, + horasIndependientes: 25, + }, + { + nombre: "Matemáticas Discretas", + tipo: "TRONCAL", + creditos: 4, + descripcion: + "Conceptos fundamentales: lógica proposicional y de predicados, teoría de conjuntos, relaciones y funciones, combinatoria, teoría de grafos y demostraciones formales aplicadas a la computación.", + horasAcademicas: 40, + horasIndependientes: 20, + }, + { + nombre: "Circuitos Digitales y Lógica", + tipo: "OBLIGATORIA", + creditos: 4, + descripcion: + "Análisis y diseño de circuitos digitales: álgebra booleana, puertas lógicas, minimización, diseño secuencial y combinacional, máquinas de estados y uso de herramientas de simulación.", + horasAcademicas: 40, + horasIndependientes: 20, + }, + { + nombre: "Señales y Sistemas Básicos", + tipo: "TRONCAL", + creditos: 4, + descripcion: + "Introducción a señales y sistemas: representación de señales, sistemas lineales invariantes en el tiempo, transformadas (Fourier, Laplace) y aplicaciones básicas en procesamiento y comunicaciones.", + horasAcademicas: 40, + horasIndependientes: 20, + }, + { + nombre: "Laboratorio de Programación y Prácticas de Ingeniería", + tipo: "OPTATIVA", + creditos: 3, + descripcion: + "Asignatura práctica orientada a aplicar técnicas de programación, integración de software y herramientas de desarrollo; proyectos cortos que consolidan conceptos de algoritmos y diseño de software.", + horasAcademicas: 30, + horasIndependientes: 15, + }, + ], +]; +// ...existing code... -- 2.52.0 From b1d431a683b192844c500165d8ff81dccf0cb197 Mon Sep 17 00:00:00 2001 From: Guillermo Arrieta Medina Date: Tue, 10 Feb 2026 17:23:45 -0600 Subject: [PATCH 3/7] =?UTF-8?q?Generaci=C3=B3n=20con=20IA=20funcional?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- supabase/functions/ai-generate-plan/deno.json | 5 +--- .../generate-subject-suggestions/deno.json | 2 +- .../generate-subject-suggestions/index.ts | 25 +++++++++++++------ 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/supabase/functions/ai-generate-plan/deno.json b/supabase/functions/ai-generate-plan/deno.json index 8b204ca..f5db280 100644 --- a/supabase/functions/ai-generate-plan/deno.json +++ b/supabase/functions/ai-generate-plan/deno.json @@ -1,8 +1,5 @@ { "imports": { - "zod": "https://deno.land/x/zod@v3.22.4/mod.ts", - "openai": "npm:openai@6.16.0", - "@supabase/supabase-js": "npm:@supabase/supabase-js@2", - "@supabase/functions-js/edge-runtime.d.ts": "jsr:@supabase/functions-js@^2/edge-runtime.d.ts" + "zod": "https://deno.land/x/zod@v3.22.4/mod.ts" } } diff --git a/supabase/functions/generate-subject-suggestions/deno.json b/supabase/functions/generate-subject-suggestions/deno.json index a42ac38..43cab55 100644 --- a/supabase/functions/generate-subject-suggestions/deno.json +++ b/supabase/functions/generate-subject-suggestions/deno.json @@ -1,6 +1,6 @@ { "imports": { - "zod": "https://deno.land/x/zod@v3.22.4/mod.ts", + "zod": "npm:zod@4.3.5", "openai": "npm:openai@6.16.0", "openai/helpers/zod": "npm:openai@6.16.0/helpers/zod", "@supabase/supabase-js": "npm:@supabase/supabase-js@2", diff --git a/supabase/functions/generate-subject-suggestions/index.ts b/supabase/functions/generate-subject-suggestions/index.ts index 4ec4ce3..2bb5ea1 100644 --- a/supabase/functions/generate-subject-suggestions/index.ts +++ b/supabase/functions/generate-subject-suggestions/index.ts @@ -16,26 +16,30 @@ export type DataAsignaturaSugerida = { creditos: Tables<"asignaturas">["creditos"] | null; horasAcademicas?: number | null; horasIndependientes?: number | null; - descripcion?: string; + descripcion: string; }; const AsignaturaSugeridaItemSchema: z.ZodType = z .object({ nombre: z.string().describe("Nombre de la asignatura a crear"), - codigo: z.string().optional().describe( + codigo: z.string().optional().nullable().describe( "Código o clave de la asignatura. Un string único que la identifique, como 'MAT101' o 'FIS202'. Opcional, pero recomendado para evitar confusiones.", ), tipo: z.enum(["TRONCAL", "OBLIGATORIA", "OPTATIVA", "OTRA"]).nullable(), creditos: z.number().nullable(), horasAcademicas: z.number().optional().nullable(), horasIndependientes: z.number().optional().nullable(), - descripcion: z.string().optional(), + descripcion: z.string(), }); const AsignaturaSugeridaSchema = z - .array(AsignaturaSugeridaItemSchema) - .length(6) - .describe("Arreglo de 6 sugerencias de asignatura"); + .object({ + sugerencias: z + .array(AsignaturaSugeridaItemSchema) + .length(6) + .describe("Arreglo de 6 sugerencias de asignatura"), + }) + .describe("Respuesta estructurada con 6 sugerencias de asignatura"); Deno.serve(async (req: Request): Promise => { const url = new URL(req.url); @@ -81,7 +85,7 @@ Deno.serve(async (req: Request): Promise => { } const options: StructuredResponseOptions = { - model: "gpt-4o-mini", + model: "gpt-5-mini", input: [ { role: "user", @@ -149,7 +153,12 @@ Deno.serve(async (req: Request): Promise => { ); } - return sendSuccess(parsed.data); + console.log( + `[${ + new Date().toISOString() + }][${functionName}]: Request processed successfully`, + ); + return sendSuccess(parsed.data.sugerencias); } catch (error) { if (error instanceof HttpError) { console.error( -- 2.52.0 From 24b6338074875d34089efd8610943ad8f8fbbfa2 Mon Sep 17 00:00:00 2001 From: Guillermo Arrieta Medina Date: Wed, 11 Feb 2026 13:12:47 -0600 Subject: [PATCH 4/7] =?UTF-8?q?Implementar=20validaci=C3=B3n=20y=20generac?= =?UTF-8?q?i=C3=B3n=20de=20sugerencias=20de=20asignaturas=20con=20integrac?= =?UTF-8?q?i=C3=B3n=20a=20Supabase?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../generate-subject-suggestions/index.ts | 214 +++++++++++++++++- 1 file changed, 203 insertions(+), 11 deletions(-) diff --git a/supabase/functions/generate-subject-suggestions/index.ts b/supabase/functions/generate-subject-suggestions/index.ts index 2bb5ea1..312b26f 100644 --- a/supabase/functions/generate-subject-suggestions/index.ts +++ b/supabase/functions/generate-subject-suggestions/index.ts @@ -1,7 +1,8 @@ import "@supabase/functions-js/edge-runtime.d.ts"; import { corsHeaders } from "../_shared/cors.ts"; import { HttpError, sendError, sendSuccess } from "../_shared/utils.ts"; -import type { Tables } from "../_shared/database.types.ts"; +import { createClient } from "@supabase/supabase-js"; +import type { Database, Tables } from "../_shared/database.types.ts"; import { z } from "zod"; import { zodTextFormat } from "openai/helpers/zod"; import { @@ -32,14 +33,38 @@ const AsignaturaSugeridaItemSchema: z.ZodType = z descripcion: z.string(), }); -const AsignaturaSugeridaSchema = z - .object({ - sugerencias: z - .array(AsignaturaSugeridaItemSchema) - .length(6) - .describe("Arreglo de 6 sugerencias de asignatura"), - }) - .describe("Respuesta estructurada con 6 sugerencias de asignatura"); +const RequestSchema = z.object({ + plan_estudio_id: z.string().uuid(), + numero_de_ciclo: z.number().int().positive(), + enfoque: z.string().trim().min(1).optional(), + cantidad_de_sugerencias: z.number().int().positive().max(50), + sugerencias_conservadas: z + .array( + z.object({ + nombre: z.string().trim().min(1), + descripcion: z.string().trim().min(1), + }), + ) + .default([]), +}); + +function normalizeName(value: string): string { + return value + .normalize("NFD") + .replace(/\p{Diacritic}/gu, "") + .toLowerCase() + .replace(/\s+/g, " ") + .trim(); +} + +function formatZodIssues(issues: z.ZodIssue[]): string { + return issues + .map((issue, i) => { + const path = issue.path.length ? issue.path.join(".") : "(root)"; + return `${i + 1}. ${path}: ${issue.message}`; + }) + .join("\n"); +} Deno.serve(async (req: Request): Promise => { const url = new URL(req.url); @@ -74,6 +99,173 @@ Deno.serve(async (req: Request): Promise => { ); } + const contentType = (req.headers.get("content-type") || "").toLowerCase(); + if (!contentType.includes("application/json")) { + throw new HttpError( + 415, + "Content-Type no soportado.", + "UNSUPPORTED_MEDIA_TYPE", + { contentType }, + ); + } + + let rawBody: unknown; + try { + rawBody = await req.json(); + } catch (e) { + throw new HttpError( + 400, + "Body JSON inválido.", + "INVALID_JSON", + { cause: e }, + ); + } + + const validated = RequestSchema.safeParse(rawBody); + if (!validated.success) { + throw new HttpError( + 422, + formatZodIssues(validated.error.issues), + "VALIDATION_ERROR", + validated.error, + ); + } + + const { + plan_estudio_id, + numero_de_ciclo, + enfoque, + cantidad_de_sugerencias, + sugerencias_conservadas, + } = validated.data; + + const SUPABASE_URL = Deno.env.get("SUPABASE_URL"); + const SERVICE_ROLE_KEY = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY"); + if (!SUPABASE_URL || !SERVICE_ROLE_KEY) { + throw new HttpError( + 500, + "Configuración del servidor incompleta.", + "MISSING_ENV", + { + missing: [ + !SUPABASE_URL ? "SUPABASE_URL" : null, + !SERVICE_ROLE_KEY ? "SUPABASE_SERVICE_ROLE_KEY" : null, + ].filter(Boolean), + }, + ); + } + + const supabaseService = createClient( + SUPABASE_URL, + SERVICE_ROLE_KEY, + ); + + const { data: plan, error: planError } = await supabaseService + .from("planes_estudio") + .select("id,nombre,nivel,tipo_ciclo,numero_ciclos,datos") + .eq("id", plan_estudio_id) + .single(); + if (planError) { + const maybeCode = (planError as { code?: string }).code; + if (maybeCode === "PGRST116") { + throw new HttpError( + 404, + "No se encontró el plan de estudio.", + "NOT_FOUND", + { table: "planes_estudio", id: plan_estudio_id }, + ); + } + throw new HttpError( + 500, + "No se pudo obtener el plan de estudio.", + "SUPABASE_QUERY_FAILED", + planError, + ); + } + + const { data: asignaturas, error: asignaturasError } = await supabaseService + .from("asignaturas") + .select("id,nombre,numero_ciclo,codigo,tipo,creditos") + .eq("plan_estudio_id", plan_estudio_id); + if (asignaturasError) { + throw new HttpError( + 500, + "No se pudieron obtener las asignaturas del plan de estudio.", + "SUPABASE_QUERY_FAILED", + asignaturasError, + ); + } + + const existingNames = new Set( + (asignaturas ?? []) + .map((a) => normalizeName(a.nombre)) + .filter(Boolean), + ); + + const conservedNames = new Set( + (sugerencias_conservadas ?? []) + .map((s) => normalizeName(s.nombre)) + .filter(Boolean), + ); + + const forbiddenNames = Array.from( + new Set([...existingNames, ...conservedNames]), + ); + + const asignaturasResumen = (asignaturas ?? []) + .map((a) => { + const ciclo = a.numero_ciclo == null ? "(sin ciclo)" : a.numero_ciclo; + const codigo = a.codigo ? ` - ${a.codigo}` : ""; + return `- [ciclo ${ciclo}] ${a.nombre}${codigo}`; + }) + .join("\n"); + + const sugerenciasConservadasResumen = (sugerencias_conservadas ?? []) + .map((s) => `- ${s.nombre}: ${s.descripcion}`) + .join("\n"); + + const systemPrompt = + "Eres un asistente experto en diseño curricular. Responde únicamente con JSON válido que cumpla estrictamente el esquema proporcionado."; + + const userPrompt = + `Necesito sugerencias NUEVAS de asignaturas para un plan de estudios.\n\n` + + `Plan de estudio:\n` + + `- id: ${plan.id}\n` + + `- nombre: ${plan.nombre}\n` + + `- nivel: ${plan.nivel}\n` + + `- tipo_ciclo: ${plan.tipo_ciclo}\n` + + `- numero_ciclos: ${plan.numero_ciclos}\n\n` + + `Datos del plan (JSON):\n${JSON.stringify(plan.datos)}\n\n` + + `Asignaturas existentes en el plan (NO repetir):\n${ + asignaturasResumen || "(ninguna)" + }\n\n` + + `Sugerencias conservadas por el usuario (NO repetir):\n${ + sugerenciasConservadasResumen || "(ninguna)" + }\n\n` + + `Ciclo objetivo: ${numero_de_ciclo}\n` + + `Enfoque (opcional): ${enfoque ?? "(ninguno)"}\n\n` + + `Requisitos estrictos:\n` + + `1) Genera EXACTAMENTE ${cantidad_de_sugerencias} sugerencias.\n` + + `2) No repitas nombres que ya existan en el plan ni los nombres de las sugerencias conservadas.\n` + + `3) Tampoco repitas nombres entre tus nuevas sugerencias.\n` + + `4) Evita nombres demasiado similares (diferencias solo por mayúsculas, tildes, signos o palabras triviales).\n` + + `5) Cada sugerencia debe incluir un nombre y una descripción clara y útil.\n\n` + + `Lista de nombres prohibidos (normalizados):\n` + + forbiddenNames.map((n) => `- ${n}`).join("\n"); + + const AsignaturaSugeridaSchema = z + .object({ + sugerencias: z + .array(AsignaturaSugeridaItemSchema) + .length(cantidad_de_sugerencias) + .describe( + `Arreglo de ${cantidad_de_sugerencias} sugerencias de asignatura`, + ), + }) + .describe( + `Respuesta estructurada con ${cantidad_de_sugerencias} sugerencias de asignatura`, + ); + const svc = OpenAIService.fromEnv(); if (!(svc instanceof OpenAIService)) { throw new HttpError( @@ -87,13 +279,13 @@ Deno.serve(async (req: Request): Promise => { const options: StructuredResponseOptions = { model: "gpt-5-mini", input: [ + { role: "system", content: systemPrompt }, { role: "user", content: [ { type: "input_text", - text: - "Genera 6 sugerencias de asignatura para el ciclo 6 de 9 del plan de estudios de una carrera de ingeniería mecánica.", + text: userPrompt, }, ], }, -- 2.52.0 From 48ab986cb8678ee08c24093bb7f06f2e0ff68156 Mon Sep 17 00:00:00 2001 From: Guillermo Arrieta Medina Date: Wed, 11 Feb 2026 16:04:24 -0600 Subject: [PATCH 5/7] =?UTF-8?q?Actualizar=20esquema=20de=20validaci=C3=B3n?= =?UTF-8?q?:=20limitar=20la=20descripci=C3=B3n=20a=20200=20caracteres=20y?= =?UTF-8?q?=20reducir=20la=20cantidad=20m=C3=A1xima=20de=20sugerencias=20a?= =?UTF-8?q?=2015?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- supabase/functions/generate-subject-suggestions/index.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/supabase/functions/generate-subject-suggestions/index.ts b/supabase/functions/generate-subject-suggestions/index.ts index 312b26f..7cfa1e9 100644 --- a/supabase/functions/generate-subject-suggestions/index.ts +++ b/supabase/functions/generate-subject-suggestions/index.ts @@ -30,14 +30,14 @@ const AsignaturaSugeridaItemSchema: z.ZodType = z creditos: z.number().nullable(), horasAcademicas: z.number().optional().nullable(), horasIndependientes: z.number().optional().nullable(), - descripcion: z.string(), + descripcion: z.string().max(200), }); const RequestSchema = z.object({ plan_estudio_id: z.string().uuid(), - numero_de_ciclo: z.number().int().positive(), + // numero_de_ciclo: z.number().int().positive(), // ya no se usa enfoque: z.string().trim().min(1).optional(), - cantidad_de_sugerencias: z.number().int().positive().max(50), + cantidad_de_sugerencias: z.number().int().positive().max(15), sugerencias_conservadas: z .array( z.object({ @@ -133,7 +133,7 @@ Deno.serve(async (req: Request): Promise => { const { plan_estudio_id, - numero_de_ciclo, + // numero_de_ciclo, enfoque, cantidad_de_sugerencias, sugerencias_conservadas, @@ -242,7 +242,6 @@ Deno.serve(async (req: Request): Promise => { `Sugerencias conservadas por el usuario (NO repetir):\n${ sugerenciasConservadasResumen || "(ninguna)" }\n\n` + - `Ciclo objetivo: ${numero_de_ciclo}\n` + `Enfoque (opcional): ${enfoque ?? "(ninguno)"}\n\n` + `Requisitos estrictos:\n` + `1) Genera EXACTAMENTE ${cantidad_de_sugerencias} sugerencias.\n` + -- 2.52.0 From 2ff587d63969b2c9d82770f1271392588be6c6f4 Mon Sep 17 00:00:00 2001 From: Guillermo Arrieta Medina Date: Thu, 12 Feb 2026 15:48:04 -0600 Subject: [PATCH 6/7] =?UTF-8?q?feat:=20opcion=20de=20update=20a=20asignatu?= =?UTF-8?q?ras=20ya=20creadas=20para=20ponerles=20la=20informaci=C3=B3n=20?= =?UTF-8?q?generada=20con=20IA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Created a new enum type "estado_asignatura" with values 'borrador', 'revisada', 'aprobada', and 'generando'. - Added a new column "estado" to the "asignaturas" table, defaulting to 'generando'. - Implemented a trigger function to log changes in the "planes_estudio" table, capturing inserts, updates, and deletes. - Updated the logging mechanism to track changes in various fields, including JSONB data. - Removed the "asignaturas" table from the supabase_realtime publication. - Added a script for generating TypeScript types from the local Supabase database. --- database-types.ts | 1318 +++++++++++++++++ scripts/generacion_de_tipos_de_supabase.txt | 2 + supabase/functions/_shared/database.types.ts | 59 +- .../functions/ai-generate-subject/index.ts | 357 ++++- ...60212214439_columna_estado_asignaturas.sql | 171 +++ supabase/snippets/Untitled query 470.sql | 1 + 6 files changed, 1877 insertions(+), 31 deletions(-) create mode 100644 database-types.ts create mode 100644 scripts/generacion_de_tipos_de_supabase.txt create mode 100644 supabase/migrations/20260212214439_columna_estado_asignaturas.sql create mode 100644 supabase/snippets/Untitled query 470.sql diff --git a/database-types.ts b/database-types.ts new file mode 100644 index 0000000..f7f6bb9 --- /dev/null +++ b/database-types.ts @@ -0,0 +1,1318 @@ +export type Json = + | string + | number + | boolean + | null + | { [key: string]: Json | undefined } + | Json[] + +export type Database = { + graphql_public: { + Tables: { + [_ in never]: never + } + 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: { + Tables: { + archivos: { + Row: { + bytes: number | null + id: string + mime_type: string | null + nombre: string + notas: string | null + openai_file_id: string | null + ruta_storage: string + subido_en: string + subido_por: string | null + temporal: boolean + } + Insert: { + bytes?: number | null + id?: string + mime_type?: string | null + nombre: string + notas?: string | null + openai_file_id?: string | null + ruta_storage: string + subido_en?: string + subido_por?: string | null + temporal?: boolean + } + Update: { + bytes?: number | null + id?: string + mime_type?: string | null + nombre?: string + notas?: string | null + openai_file_id?: string | null + ruta_storage?: string + subido_en?: string + subido_por?: string | null + temporal?: boolean + } + Relationships: [ + { + foreignKeyName: "archivos_subido_por_fkey" + columns: ["subido_por"] + isOneToOne: false + referencedRelation: "usuarios_app" + referencedColumns: ["id"] + }, + ] + } + asignaturas: { + Row: { + actualizado_en: string + actualizado_por: string | null + asignatura_hash: string | null + codigo: string | null + contenido_tematico: Json + conversation_id: string | null + creado_en: string + creado_por: string | null + creditos: number + datos: Json + estado: Database["public"]["Enums"]["estado_asignatura"] + estructura_id: string | null + horas_academicas: number | null + horas_independientes: number | null + id: string + linea_plan_id: string | null + meta_origen: Json + nombre: string + numero_ciclo: number | null + orden_celda: number | null + plan_estudio_id: string + tipo: Database["public"]["Enums"]["tipo_asignatura"] + tipo_origen: Database["public"]["Enums"]["tipo_origen"] | null + } + Insert: { + actualizado_en?: string + actualizado_por?: string | null + asignatura_hash?: string | null + codigo?: string | null + contenido_tematico?: Json + conversation_id?: string | null + creado_en?: string + creado_por?: string | null + creditos: number + datos?: Json + estado?: Database["public"]["Enums"]["estado_asignatura"] + estructura_id?: string | null + horas_academicas?: number | null + horas_independientes?: number | null + id?: string + linea_plan_id?: string | null + meta_origen?: Json + nombre: string + numero_ciclo?: number | null + orden_celda?: number | null + plan_estudio_id: string + tipo?: Database["public"]["Enums"]["tipo_asignatura"] + tipo_origen?: Database["public"]["Enums"]["tipo_origen"] | null + } + Update: { + actualizado_en?: string + actualizado_por?: string | null + asignatura_hash?: string | null + codigo?: string | null + contenido_tematico?: Json + conversation_id?: string | null + creado_en?: string + creado_por?: string | null + creditos?: number + datos?: Json + estado?: Database["public"]["Enums"]["estado_asignatura"] + estructura_id?: string | null + horas_academicas?: number | null + horas_independientes?: number | null + id?: string + linea_plan_id?: string | null + meta_origen?: Json + nombre?: string + numero_ciclo?: number | null + orden_celda?: number | null + plan_estudio_id?: string + tipo?: Database["public"]["Enums"]["tipo_asignatura"] + tipo_origen?: Database["public"]["Enums"]["tipo_origen"] | null + } + Relationships: [ + { + foreignKeyName: "asignaturas_actualizado_por_fkey" + columns: ["actualizado_por"] + isOneToOne: false + referencedRelation: "usuarios_app" + referencedColumns: ["id"] + }, + { + foreignKeyName: "asignaturas_creado_por_fkey" + columns: ["creado_por"] + isOneToOne: false + referencedRelation: "usuarios_app" + referencedColumns: ["id"] + }, + { + foreignKeyName: "asignaturas_estructura_id_fkey" + columns: ["estructura_id"] + isOneToOne: false + referencedRelation: "estructuras_asignatura" + referencedColumns: ["id"] + }, + { + foreignKeyName: "asignaturas_linea_plan_fk_compuesta" + columns: ["linea_plan_id", "plan_estudio_id"] + isOneToOne: false + referencedRelation: "lineas_plan" + referencedColumns: ["id", "plan_estudio_id"] + }, + { + foreignKeyName: "asignaturas_plan_estudio_id_fkey" + columns: ["plan_estudio_id"] + isOneToOne: false + referencedRelation: "planes_estudio" + referencedColumns: ["id"] + }, + { + foreignKeyName: "asignaturas_plan_estudio_id_fkey" + columns: ["plan_estudio_id"] + isOneToOne: false + referencedRelation: "plantilla_plan" + referencedColumns: ["plan_estudio_id"] + }, + ] + } + bibliografia_asignatura: { + Row: { + actualizado_en: string + asignatura_id: string + biblioteca_item_id: string | null + cita: string + creado_en: string + creado_por: string | null + id: string + tipo: Database["public"]["Enums"]["tipo_bibliografia"] + tipo_fuente: Database["public"]["Enums"]["tipo_fuente_bibliografia"] + } + Insert: { + actualizado_en?: string + asignatura_id: string + biblioteca_item_id?: string | null + cita: string + creado_en?: string + creado_por?: string | null + id?: string + tipo: Database["public"]["Enums"]["tipo_bibliografia"] + tipo_fuente?: Database["public"]["Enums"]["tipo_fuente_bibliografia"] + } + Update: { + actualizado_en?: string + asignatura_id?: string + biblioteca_item_id?: string | null + cita?: string + creado_en?: string + creado_por?: string | null + id?: string + tipo?: Database["public"]["Enums"]["tipo_bibliografia"] + tipo_fuente?: Database["public"]["Enums"]["tipo_fuente_bibliografia"] + } + Relationships: [ + { + foreignKeyName: "bibliografia_asignatura_asignatura_id_fkey" + columns: ["asignatura_id"] + isOneToOne: false + referencedRelation: "asignaturas" + referencedColumns: ["id"] + }, + { + foreignKeyName: "bibliografia_asignatura_creado_por_fkey" + columns: ["creado_por"] + isOneToOne: false + referencedRelation: "usuarios_app" + referencedColumns: ["id"] + }, + ] + } + cambios_asignatura: { + Row: { + asignatura_id: string + cambiado_en: string + cambiado_por: string | null + campo: string | null + fuente: Database["public"]["Enums"]["fuente_cambio"] | null + id: string + interaccion_ia_id: string | null + tipo: Database["public"]["Enums"]["tipo_cambio"] + valor_anterior: Json | null + valor_nuevo: Json | null + } + Insert: { + asignatura_id: string + cambiado_en?: string + cambiado_por?: string | null + campo?: string | null + fuente?: Database["public"]["Enums"]["fuente_cambio"] | null + id?: string + interaccion_ia_id?: string | null + tipo: Database["public"]["Enums"]["tipo_cambio"] + valor_anterior?: Json | null + valor_nuevo?: Json | null + } + Update: { + asignatura_id?: string + cambiado_en?: string + cambiado_por?: string | null + campo?: string | null + fuente?: Database["public"]["Enums"]["fuente_cambio"] | null + id?: string + interaccion_ia_id?: string | null + tipo?: Database["public"]["Enums"]["tipo_cambio"] + valor_anterior?: Json | null + valor_nuevo?: Json | null + } + Relationships: [ + { + foreignKeyName: "cambios_asignatura_asignatura_id_fkey" + columns: ["asignatura_id"] + isOneToOne: false + referencedRelation: "asignaturas" + referencedColumns: ["id"] + }, + { + foreignKeyName: "cambios_asignatura_cambiado_por_fkey" + columns: ["cambiado_por"] + isOneToOne: false + referencedRelation: "usuarios_app" + referencedColumns: ["id"] + }, + ] + } + cambios_plan: { + Row: { + cambiado_en: string + cambiado_por: string | null + campo: string | null + id: string + plan_estudio_id: string + response_id: string | null + tipo: Database["public"]["Enums"]["tipo_cambio"] + valor_anterior: Json | null + valor_nuevo: Json | null + } + Insert: { + cambiado_en?: string + cambiado_por?: string | null + campo?: string | null + id?: string + plan_estudio_id: string + response_id?: string | null + tipo: Database["public"]["Enums"]["tipo_cambio"] + valor_anterior?: Json | null + valor_nuevo?: Json | null + } + Update: { + cambiado_en?: string + cambiado_por?: string | null + campo?: string | null + id?: string + plan_estudio_id?: string + response_id?: string | null + tipo?: Database["public"]["Enums"]["tipo_cambio"] + valor_anterior?: Json | null + valor_nuevo?: Json | null + } + Relationships: [ + { + foreignKeyName: "cambios_plan_cambiado_por_fkey" + columns: ["cambiado_por"] + isOneToOne: false + referencedRelation: "usuarios_app" + referencedColumns: ["id"] + }, + ] + } + carreras: { + Row: { + activa: boolean + actualizado_en: string + clave_sep: string | null + creado_en: string + facultad_id: string + id: string + nombre: string + nombre_corto: string | null + } + Insert: { + activa?: boolean + actualizado_en?: string + clave_sep?: string | null + creado_en?: string + facultad_id: string + id?: string + nombre: string + nombre_corto?: string | null + } + Update: { + activa?: boolean + actualizado_en?: string + clave_sep?: string | null + creado_en?: string + facultad_id?: string + id?: string + nombre?: string + nombre_corto?: string | null + } + Relationships: [ + { + foreignKeyName: "carreras_facultad_id_fkey" + columns: ["facultad_id"] + isOneToOne: false + referencedRelation: "facultades" + referencedColumns: ["id"] + }, + ] + } + estados_plan: { + Row: { + clave: string + es_final: boolean + etiqueta: string + id: string + orden: number + } + Insert: { + clave: string + es_final?: boolean + etiqueta: string + id?: string + orden?: number + } + Update: { + clave?: string + es_final?: boolean + etiqueta?: string + id?: string + orden?: number + } + Relationships: [] + } + estructuras_asignatura: { + Row: { + actualizado_en: string + creado_en: string + definicion: Json + id: string + nombre: string + version: string | null + } + Insert: { + actualizado_en?: string + creado_en?: string + definicion?: Json + id?: string + nombre: string + version?: string | null + } + Update: { + actualizado_en?: string + creado_en?: string + definicion?: Json + id?: string + nombre?: string + version?: string | null + } + Relationships: [] + } + estructuras_plan: { + Row: { + actualizado_en: string + creado_en: string + definicion: Json + id: string + nombre: string + template_id: string | null + tipo: Database["public"]["Enums"]["tipo_estructura_plan"] + } + Insert: { + actualizado_en?: string + creado_en?: string + definicion?: Json + id?: string + nombre: string + template_id?: string | null + tipo: Database["public"]["Enums"]["tipo_estructura_plan"] + } + Update: { + actualizado_en?: string + creado_en?: string + definicion?: Json + id?: string + nombre?: string + template_id?: string | null + tipo?: Database["public"]["Enums"]["tipo_estructura_plan"] + } + Relationships: [] + } + facultades: { + Row: { + actualizado_en: string + color: string | null + creado_en: string + icono: string | null + id: string + nombre: string + nombre_corto: string | null + } + Insert: { + actualizado_en?: string + color?: string | null + creado_en?: string + icono?: string | null + id?: string + nombre: string + nombre_corto?: string | null + } + Update: { + actualizado_en?: string + color?: string | null + creado_en?: string + icono?: string | null + id?: string + nombre?: string + nombre_corto?: string | null + } + Relationships: [] + } + interacciones_ia: { + Row: { + aceptada: boolean + asignatura_id: string | null + conversacion_id: string | null + creado_en: string + id: string + ids_archivos: Json + ids_vector_store: Json + modelo: string | null + plan_estudio_id: string | null + prompt: Json + respuesta: Json + rutas_storage: Json + temperatura: number | null + tipo: Database["public"]["Enums"]["tipo_interaccion_ia"] + usuario_id: string | null + } + Insert: { + aceptada?: boolean + asignatura_id?: string | null + conversacion_id?: string | null + creado_en?: string + id?: string + ids_archivos?: Json + ids_vector_store?: Json + modelo?: string | null + plan_estudio_id?: string | null + prompt?: Json + respuesta?: Json + rutas_storage?: Json + temperatura?: number | null + tipo: Database["public"]["Enums"]["tipo_interaccion_ia"] + usuario_id?: string | null + } + Update: { + aceptada?: boolean + asignatura_id?: string | null + conversacion_id?: string | null + creado_en?: string + id?: string + ids_archivos?: Json + ids_vector_store?: Json + modelo?: string | null + plan_estudio_id?: string | null + prompt?: Json + respuesta?: Json + rutas_storage?: Json + temperatura?: number | null + tipo?: Database["public"]["Enums"]["tipo_interaccion_ia"] + usuario_id?: string | null + } + Relationships: [ + { + foreignKeyName: "interacciones_ia_asignatura_id_fkey" + columns: ["asignatura_id"] + isOneToOne: false + referencedRelation: "asignaturas" + referencedColumns: ["id"] + }, + { + foreignKeyName: "interacciones_ia_plan_estudio_id_fkey" + columns: ["plan_estudio_id"] + isOneToOne: false + referencedRelation: "planes_estudio" + referencedColumns: ["id"] + }, + { + foreignKeyName: "interacciones_ia_plan_estudio_id_fkey" + columns: ["plan_estudio_id"] + isOneToOne: false + referencedRelation: "plantilla_plan" + referencedColumns: ["plan_estudio_id"] + }, + { + foreignKeyName: "interacciones_ia_usuario_id_fkey" + columns: ["usuario_id"] + isOneToOne: false + referencedRelation: "usuarios_app" + referencedColumns: ["id"] + }, + ] + } + lineas_plan: { + Row: { + actualizado_en: string + area: string | null + creado_en: string + id: string + nombre: string + orden: number + plan_estudio_id: string + } + Insert: { + actualizado_en?: string + area?: string | null + creado_en?: string + id?: string + nombre: string + orden?: number + plan_estudio_id: string + } + Update: { + actualizado_en?: string + area?: string | null + creado_en?: string + id?: string + nombre?: string + orden?: number + plan_estudio_id?: string + } + Relationships: [ + { + foreignKeyName: "lineas_plan_plan_estudio_id_fkey" + columns: ["plan_estudio_id"] + isOneToOne: false + referencedRelation: "planes_estudio" + referencedColumns: ["id"] + }, + { + foreignKeyName: "lineas_plan_plan_estudio_id_fkey" + columns: ["plan_estudio_id"] + isOneToOne: false + referencedRelation: "plantilla_plan" + referencedColumns: ["plan_estudio_id"] + }, + ] + } + notificaciones: { + Row: { + creado_en: string + id: string + leida: boolean + leida_en: string | null + payload: Json + tipo: Database["public"]["Enums"]["tipo_notificacion"] + usuario_id: string + } + Insert: { + creado_en?: string + id?: string + leida?: boolean + leida_en?: string | null + payload?: Json + tipo: Database["public"]["Enums"]["tipo_notificacion"] + usuario_id: string + } + Update: { + creado_en?: string + id?: string + leida?: boolean + leida_en?: string | null + payload?: Json + tipo?: Database["public"]["Enums"]["tipo_notificacion"] + usuario_id?: string + } + Relationships: [ + { + foreignKeyName: "notificaciones_usuario_id_fkey" + columns: ["usuario_id"] + isOneToOne: false + referencedRelation: "usuarios_app" + referencedColumns: ["id"] + }, + ] + } + planes_estudio: { + Row: { + activo: boolean + actualizado_en: string + actualizado_por: string | null + carrera_id: string + conversation_id: string | null + creado_en: string + creado_por: string | null + datos: Json + estado_actual_id: string | null + estructura_id: string + id: string + meta_origen: Json + nivel: Database["public"]["Enums"]["nivel_plan_estudio"] + nombre: string + nombre_search: string | null + numero_ciclos: number + plan_hash: string | null + tipo_ciclo: Database["public"]["Enums"]["tipo_ciclo"] + tipo_origen: Database["public"]["Enums"]["tipo_origen"] | null + } + Insert: { + activo?: boolean + actualizado_en?: string + actualizado_por?: string | null + carrera_id: string + conversation_id?: string | null + creado_en?: string + creado_por?: string | null + datos?: Json + estado_actual_id?: string | null + estructura_id: string + id?: string + meta_origen?: Json + nivel: Database["public"]["Enums"]["nivel_plan_estudio"] + nombre: string + nombre_search?: string | null + numero_ciclos: number + plan_hash?: string | null + tipo_ciclo: Database["public"]["Enums"]["tipo_ciclo"] + tipo_origen?: Database["public"]["Enums"]["tipo_origen"] | null + } + Update: { + activo?: boolean + actualizado_en?: string + actualizado_por?: string | null + carrera_id?: string + conversation_id?: string | null + creado_en?: string + creado_por?: string | null + datos?: Json + estado_actual_id?: string | null + estructura_id?: string + id?: string + meta_origen?: Json + nivel?: Database["public"]["Enums"]["nivel_plan_estudio"] + nombre?: string + nombre_search?: string | null + numero_ciclos?: number + plan_hash?: string | null + tipo_ciclo?: Database["public"]["Enums"]["tipo_ciclo"] + tipo_origen?: Database["public"]["Enums"]["tipo_origen"] | null + } + Relationships: [ + { + foreignKeyName: "planes_estudio_actualizado_por_fkey" + columns: ["actualizado_por"] + isOneToOne: false + referencedRelation: "usuarios_app" + referencedColumns: ["id"] + }, + { + foreignKeyName: "planes_estudio_carrera_id_fkey" + columns: ["carrera_id"] + isOneToOne: false + referencedRelation: "carreras" + referencedColumns: ["id"] + }, + { + foreignKeyName: "planes_estudio_creado_por_fkey" + columns: ["creado_por"] + isOneToOne: false + referencedRelation: "usuarios_app" + referencedColumns: ["id"] + }, + { + foreignKeyName: "planes_estudio_estado_actual_id_fkey" + columns: ["estado_actual_id"] + isOneToOne: false + referencedRelation: "estados_plan" + referencedColumns: ["id"] + }, + { + foreignKeyName: "planes_estudio_estructura_id_fkey" + columns: ["estructura_id"] + isOneToOne: false + referencedRelation: "estructuras_plan" + referencedColumns: ["id"] + }, + { + foreignKeyName: "planes_estudio_estructura_id_fkey" + columns: ["estructura_id"] + isOneToOne: false + referencedRelation: "plantilla_plan" + referencedColumns: ["estructura_id"] + }, + ] + } + responsables_asignatura: { + Row: { + asignatura_id: string + creado_en: string + id: string + rol: Database["public"]["Enums"]["rol_responsable_asignatura"] + usuario_id: string + } + Insert: { + asignatura_id: string + creado_en?: string + id?: string + rol?: Database["public"]["Enums"]["rol_responsable_asignatura"] + usuario_id: string + } + Update: { + asignatura_id?: string + creado_en?: string + id?: string + rol?: Database["public"]["Enums"]["rol_responsable_asignatura"] + usuario_id?: string + } + Relationships: [ + { + foreignKeyName: "responsables_asignatura_asignatura_id_fkey" + columns: ["asignatura_id"] + isOneToOne: false + referencedRelation: "asignaturas" + referencedColumns: ["id"] + }, + { + foreignKeyName: "responsables_asignatura_usuario_id_fkey" + columns: ["usuario_id"] + isOneToOne: false + referencedRelation: "usuarios_app" + referencedColumns: ["id"] + }, + ] + } + roles: { + Row: { + clave: string + descripcion: string | null + id: string + nombre: string + } + Insert: { + clave: string + descripcion?: string | null + id?: string + nombre: string + } + Update: { + clave?: string + descripcion?: string | null + id?: string + nombre?: string + } + Relationships: [] + } + tareas_revision: { + Row: { + asignado_a: string + completado_en: string | null + creado_en: string + estado_id: string | null + estatus: Database["public"]["Enums"]["estado_tarea_revision"] + fecha_limite: string | null + id: string + plan_estudio_id: string + rol_id: string | null + } + Insert: { + asignado_a: string + completado_en?: string | null + creado_en?: string + estado_id?: string | null + estatus?: Database["public"]["Enums"]["estado_tarea_revision"] + fecha_limite?: string | null + id?: string + plan_estudio_id: string + rol_id?: string | null + } + Update: { + asignado_a?: string + completado_en?: string | null + creado_en?: string + estado_id?: string | null + estatus?: Database["public"]["Enums"]["estado_tarea_revision"] + fecha_limite?: string | null + id?: string + plan_estudio_id?: string + rol_id?: string | null + } + Relationships: [ + { + foreignKeyName: "tareas_revision_asignado_a_fkey" + columns: ["asignado_a"] + isOneToOne: false + referencedRelation: "usuarios_app" + referencedColumns: ["id"] + }, + { + foreignKeyName: "tareas_revision_estado_id_fkey" + columns: ["estado_id"] + isOneToOne: false + referencedRelation: "estados_plan" + referencedColumns: ["id"] + }, + { + foreignKeyName: "tareas_revision_plan_estudio_id_fkey" + columns: ["plan_estudio_id"] + isOneToOne: false + referencedRelation: "planes_estudio" + referencedColumns: ["id"] + }, + { + foreignKeyName: "tareas_revision_plan_estudio_id_fkey" + columns: ["plan_estudio_id"] + isOneToOne: false + referencedRelation: "plantilla_plan" + referencedColumns: ["plan_estudio_id"] + }, + { + foreignKeyName: "tareas_revision_rol_id_fkey" + columns: ["rol_id"] + isOneToOne: false + referencedRelation: "roles" + referencedColumns: ["id"] + }, + ] + } + transiciones_estado_plan: { + Row: { + creado_en: string + desde_estado_id: string + hacia_estado_id: string + id: string + rol_permitido_id: string + } + Insert: { + creado_en?: string + desde_estado_id: string + hacia_estado_id: string + id?: string + rol_permitido_id: string + } + Update: { + creado_en?: string + desde_estado_id?: string + hacia_estado_id?: string + id?: string + rol_permitido_id?: string + } + Relationships: [ + { + foreignKeyName: "transiciones_estado_plan_desde_estado_id_fkey" + columns: ["desde_estado_id"] + isOneToOne: false + referencedRelation: "estados_plan" + referencedColumns: ["id"] + }, + { + foreignKeyName: "transiciones_estado_plan_hacia_estado_id_fkey" + columns: ["hacia_estado_id"] + isOneToOne: false + referencedRelation: "estados_plan" + referencedColumns: ["id"] + }, + { + foreignKeyName: "transiciones_estado_plan_rol_permitido_id_fkey" + columns: ["rol_permitido_id"] + isOneToOne: false + referencedRelation: "roles" + referencedColumns: ["id"] + }, + ] + } + usuarios_app: { + Row: { + actualizado_en: string + creado_en: string + email: string | null + externo: boolean + id: string + nombre_completo: string | null + } + Insert: { + actualizado_en?: string + creado_en?: string + email?: string | null + externo?: boolean + id: string + nombre_completo?: string | null + } + Update: { + actualizado_en?: string + creado_en?: string + email?: string | null + externo?: boolean + id?: string + nombre_completo?: string | null + } + Relationships: [] + } + usuarios_roles: { + Row: { + carrera_id: string | null + creado_en: string + facultad_id: string | null + id: string + rol_id: string + usuario_id: string + } + Insert: { + carrera_id?: string | null + creado_en?: string + facultad_id?: string | null + id?: string + rol_id: string + usuario_id: string + } + Update: { + carrera_id?: string | null + creado_en?: string + facultad_id?: string | null + id?: string + rol_id?: string + usuario_id?: string + } + Relationships: [ + { + foreignKeyName: "usuarios_roles_carrera_id_fkey" + columns: ["carrera_id"] + isOneToOne: false + referencedRelation: "carreras" + referencedColumns: ["id"] + }, + { + foreignKeyName: "usuarios_roles_facultad_id_fkey" + columns: ["facultad_id"] + isOneToOne: false + referencedRelation: "facultades" + referencedColumns: ["id"] + }, + { + foreignKeyName: "usuarios_roles_rol_id_fkey" + columns: ["rol_id"] + isOneToOne: false + referencedRelation: "roles" + referencedColumns: ["id"] + }, + { + foreignKeyName: "usuarios_roles_usuario_id_fkey" + columns: ["usuario_id"] + isOneToOne: false + referencedRelation: "usuarios_app" + referencedColumns: ["id"] + }, + ] + } + vector_stores: { + Row: { + creado_en: string + creado_por: string | null + id: string + nombre: string + openai_vector_id: string | null + } + Insert: { + creado_en?: string + creado_por?: string | null + id?: string + nombre: string + openai_vector_id?: string | null + } + Update: { + creado_en?: string + creado_por?: string | null + id?: string + nombre?: string + openai_vector_id?: string | null + } + Relationships: [ + { + foreignKeyName: "vector_stores_creado_por_fkey" + columns: ["creado_por"] + isOneToOne: false + referencedRelation: "usuarios_app" + referencedColumns: ["id"] + }, + ] + } + } + Views: { + plantilla_plan: { + Row: { + estructura_id: string | null + plan_estudio_id: string | null + template_id: string | null + } + Relationships: [] + } + } + Functions: { + unaccent: { Args: { "": string }; Returns: string } + unaccent_immutable: { Args: { "": string }; Returns: string } + } + Enums: { + estado_asignatura: "borrador" | "revisada" | "aprobada" | "generando" + estado_tarea_revision: "PENDIENTE" | "COMPLETADA" | "OMITIDA" + fuente_cambio: "HUMANO" | "IA" + nivel_plan_estudio: + | "Licenciatura" + | "Maestría" + | "Doctorado" + | "Especialidad" + | "Diplomado" + | "Otro" + puesto_tipo: + | "vicerrector" + | "director_facultad" + | "secretario_academico" + | "jefe_carrera" + | "profesor" + | "lci" + rol_responsable_asignatura: "PROFESOR_RESPONSABLE" | "COAUTOR" | "REVISOR" + tipo_asignatura: "OBLIGATORIA" | "OPTATIVA" | "TRONCAL" | "OTRA" + tipo_bibliografia: "BASICA" | "COMPLEMENTARIA" + tipo_cambio: + | "ACTUALIZACION_CAMPO" + | "ACTUALIZACION_MAPA" + | "TRANSICION_ESTADO" + | "OTRO" + | "CREACION" + | "ACTUALIZACION" + tipo_ciclo: "Semestre" | "Cuatrimestre" | "Trimestre" | "Otro" + tipo_estructura_plan: "CURRICULAR" | "NO_CURRICULAR" + tipo_fuente_bibliografia: "MANUAL" | "BIBLIOTECA" + tipo_interaccion_ia: "GENERAR" | "MEJORAR_SECCION" | "CHAT" | "OTRA" + tipo_notificacion: + | "PLAN_ASIGNADO" + | "ESTADO_CAMBIADO" + | "TAREA_ASIGNADA" + | "COMENTARIO" + | "OTRA" + tipo_origen: + | "MANUAL" + | "IA" + | "CLONADO_INTERNO" + | "CLONADO_TRADICIONAL" + | "OTRO" + } + CompositeTypes: { + [_ in never]: never + } + } +} + +type DatabaseWithoutInternals = Omit + +type DefaultSchema = DatabaseWithoutInternals[Extract] + +export type Tables< + DefaultSchemaTableNameOrOptions extends + | keyof (DefaultSchema["Tables"] & DefaultSchema["Views"]) + | { schema: keyof DatabaseWithoutInternals }, + TableName extends DefaultSchemaTableNameOrOptions extends { + schema: keyof DatabaseWithoutInternals + } + ? keyof (DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] & + DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Views"]) + : never = never, +> = DefaultSchemaTableNameOrOptions extends { + schema: keyof DatabaseWithoutInternals +} + ? (DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] & + DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Views"])[TableName] extends { + Row: infer R + } + ? R + : never + : DefaultSchemaTableNameOrOptions extends keyof (DefaultSchema["Tables"] & + DefaultSchema["Views"]) + ? (DefaultSchema["Tables"] & + DefaultSchema["Views"])[DefaultSchemaTableNameOrOptions] extends { + Row: infer R + } + ? R + : never + : never + +export type TablesInsert< + DefaultSchemaTableNameOrOptions extends + | keyof DefaultSchema["Tables"] + | { schema: keyof DatabaseWithoutInternals }, + TableName extends DefaultSchemaTableNameOrOptions extends { + schema: keyof DatabaseWithoutInternals + } + ? keyof DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] + : never = never, +> = DefaultSchemaTableNameOrOptions extends { + schema: keyof DatabaseWithoutInternals +} + ? DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"][TableName] extends { + Insert: infer I + } + ? I + : never + : DefaultSchemaTableNameOrOptions extends keyof DefaultSchema["Tables"] + ? DefaultSchema["Tables"][DefaultSchemaTableNameOrOptions] extends { + Insert: infer I + } + ? I + : never + : never + +export type TablesUpdate< + DefaultSchemaTableNameOrOptions extends + | keyof DefaultSchema["Tables"] + | { schema: keyof DatabaseWithoutInternals }, + TableName extends DefaultSchemaTableNameOrOptions extends { + schema: keyof DatabaseWithoutInternals + } + ? keyof DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] + : never = never, +> = DefaultSchemaTableNameOrOptions extends { + schema: keyof DatabaseWithoutInternals +} + ? DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"][TableName] extends { + Update: infer U + } + ? U + : never + : DefaultSchemaTableNameOrOptions extends keyof DefaultSchema["Tables"] + ? DefaultSchema["Tables"][DefaultSchemaTableNameOrOptions] extends { + Update: infer U + } + ? U + : never + : never + +export type Enums< + DefaultSchemaEnumNameOrOptions extends + | keyof DefaultSchema["Enums"] + | { schema: keyof DatabaseWithoutInternals }, + EnumName extends DefaultSchemaEnumNameOrOptions extends { + schema: keyof DatabaseWithoutInternals + } + ? keyof DatabaseWithoutInternals[DefaultSchemaEnumNameOrOptions["schema"]]["Enums"] + : never = never, +> = DefaultSchemaEnumNameOrOptions extends { + schema: keyof DatabaseWithoutInternals +} + ? DatabaseWithoutInternals[DefaultSchemaEnumNameOrOptions["schema"]]["Enums"][EnumName] + : DefaultSchemaEnumNameOrOptions extends keyof DefaultSchema["Enums"] + ? DefaultSchema["Enums"][DefaultSchemaEnumNameOrOptions] + : never + +export type CompositeTypes< + PublicCompositeTypeNameOrOptions extends + | keyof DefaultSchema["CompositeTypes"] + | { schema: keyof DatabaseWithoutInternals }, + CompositeTypeName extends PublicCompositeTypeNameOrOptions extends { + schema: keyof DatabaseWithoutInternals + } + ? keyof DatabaseWithoutInternals[PublicCompositeTypeNameOrOptions["schema"]]["CompositeTypes"] + : never = never, +> = PublicCompositeTypeNameOrOptions extends { + schema: keyof DatabaseWithoutInternals +} + ? DatabaseWithoutInternals[PublicCompositeTypeNameOrOptions["schema"]]["CompositeTypes"][CompositeTypeName] + : PublicCompositeTypeNameOrOptions extends keyof DefaultSchema["CompositeTypes"] + ? DefaultSchema["CompositeTypes"][PublicCompositeTypeNameOrOptions] + : never + +export const Constants = { + graphql_public: { + Enums: {}, + }, + public: { + Enums: { + estado_asignatura: ["borrador", "revisada", "aprobada", "generando"], + estado_tarea_revision: ["PENDIENTE", "COMPLETADA", "OMITIDA"], + fuente_cambio: ["HUMANO", "IA"], + nivel_plan_estudio: [ + "Licenciatura", + "Maestría", + "Doctorado", + "Especialidad", + "Diplomado", + "Otro", + ], + puesto_tipo: [ + "vicerrector", + "director_facultad", + "secretario_academico", + "jefe_carrera", + "profesor", + "lci", + ], + rol_responsable_asignatura: [ + "PROFESOR_RESPONSABLE", + "COAUTOR", + "REVISOR", + ], + tipo_asignatura: ["OBLIGATORIA", "OPTATIVA", "TRONCAL", "OTRA"], + tipo_bibliografia: ["BASICA", "COMPLEMENTARIA"], + tipo_cambio: [ + "ACTUALIZACION_CAMPO", + "ACTUALIZACION_MAPA", + "TRANSICION_ESTADO", + "OTRO", + "CREACION", + "ACTUALIZACION", + ], + tipo_ciclo: ["Semestre", "Cuatrimestre", "Trimestre", "Otro"], + tipo_estructura_plan: ["CURRICULAR", "NO_CURRICULAR"], + tipo_fuente_bibliografia: ["MANUAL", "BIBLIOTECA"], + tipo_interaccion_ia: ["GENERAR", "MEJORAR_SECCION", "CHAT", "OTRA"], + tipo_notificacion: [ + "PLAN_ASIGNADO", + "ESTADO_CAMBIADO", + "TAREA_ASIGNADA", + "COMENTARIO", + "OTRA", + ], + tipo_origen: [ + "MANUAL", + "IA", + "CLONADO_INTERNO", + "CLONADO_TRADICIONAL", + "OTRO", + ], + }, + }, +} as const + diff --git a/scripts/generacion_de_tipos_de_supabase.txt b/scripts/generacion_de_tipos_de_supabase.txt new file mode 100644 index 0000000..e98678d --- /dev/null +++ b/scripts/generacion_de_tipos_de_supabase.txt @@ -0,0 +1,2 @@ +Usar --local para generar tipos de la BDD local, y --linked para generar tipos de la BDD remota. +cmd /c "dx supabase gen types typescript --local > database-types.ts" \ No newline at end of file diff --git a/supabase/functions/_shared/database.types.ts b/supabase/functions/_shared/database.types.ts index 80007b3..4e5b0a0 100644 --- a/supabase/functions/_shared/database.types.ts +++ b/supabase/functions/_shared/database.types.ts @@ -4,14 +4,9 @@ | boolean | null | { [key: string]: Json | undefined } - | Array; + | Json[]; export type Database = { - // Allows to automatically instantiate createClient with right options - // instead of createClient(URL, KEY) - __InternalSupabase: { - PostgrestVersion: "12.2.3 (519615d)"; - }; graphql_public: { Tables: { [_ in never]: never; @@ -98,6 +93,7 @@ export type Database = { creado_por: string | null; creditos: number; datos: Json; + estado: Database["public"]["Enums"]["estado_asignatura"]; estructura_id: string | null; horas_academicas: number | null; horas_independientes: number | null; @@ -124,6 +120,7 @@ export type Database = { creado_por?: string | null; creditos: number; datos?: Json; + estado?: Database["public"]["Enums"]["estado_asignatura"]; estructura_id?: string | null; horas_academicas?: number | null; horas_independientes?: number | null; @@ -150,6 +147,7 @@ export type Database = { creado_por?: string | null; creditos?: number; datos?: Json; + estado?: Database["public"]["Enums"]["estado_asignatura"]; estructura_id?: string | null; horas_academicas?: number | null; horas_independientes?: number | null; @@ -815,20 +813,18 @@ export type Database = { asignatura_id: string; creado_en?: string; id?: string; - rol?: - Database["public"]["Enums"][ - "rol_responsable_asignatura" - ]; + rol?: Database["public"]["Enums"][ + "rol_responsable_asignatura" + ]; usuario_id: string; }; Update: { asignatura_id?: string; creado_en?: string; id?: string; - rol?: - Database["public"]["Enums"][ - "rol_responsable_asignatura" - ]; + rol?: Database["public"]["Enums"][ + "rol_responsable_asignatura" + ]; usuario_id?: string; }; Relationships: [ @@ -1126,6 +1122,11 @@ export type Database = { unaccent_immutable: { Args: { "": string }; Returns: string }; }; Enums: { + estado_asignatura: + | "borrador" + | "revisada" + | "aprobada" + | "generando"; estado_tarea_revision: "PENDIENTE" | "COMPLETADA" | "OMITIDA"; fuente_cambio: "HUMANO" | "IA"; nivel_plan_estudio: @@ -1233,15 +1234,13 @@ export type TablesInsert< | { schema: keyof DatabaseWithoutInternals }, TableName extends DefaultSchemaTableNameOrOptions extends { schema: keyof DatabaseWithoutInternals; - } - ? keyof DatabaseWithoutInternals[ + } ? keyof DatabaseWithoutInternals[ DefaultSchemaTableNameOrOptions["schema"] ]["Tables"] : never = never, > = DefaultSchemaTableNameOrOptions extends { schema: keyof DatabaseWithoutInternals; -} - ? DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]][ +} ? DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]][ "Tables" ][TableName] extends { Insert: infer I; @@ -1260,15 +1259,13 @@ export type TablesUpdate< | { schema: keyof DatabaseWithoutInternals }, TableName extends DefaultSchemaTableNameOrOptions extends { schema: keyof DatabaseWithoutInternals; - } - ? keyof DatabaseWithoutInternals[ + } ? keyof DatabaseWithoutInternals[ DefaultSchemaTableNameOrOptions["schema"] ]["Tables"] : never = never, > = DefaultSchemaTableNameOrOptions extends { schema: keyof DatabaseWithoutInternals; -} - ? DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]][ +} ? DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]][ "Tables" ][TableName] extends { Update: infer U; @@ -1287,15 +1284,13 @@ export type Enums< | { schema: keyof DatabaseWithoutInternals }, EnumName extends DefaultSchemaEnumNameOrOptions extends { schema: keyof DatabaseWithoutInternals; - } - ? keyof DatabaseWithoutInternals[ + } ? keyof DatabaseWithoutInternals[ DefaultSchemaEnumNameOrOptions["schema"] ]["Enums"] : never = never, > = DefaultSchemaEnumNameOrOptions extends { schema: keyof DatabaseWithoutInternals; -} - ? DatabaseWithoutInternals[DefaultSchemaEnumNameOrOptions["schema"]][ +} ? DatabaseWithoutInternals[DefaultSchemaEnumNameOrOptions["schema"]][ "Enums" ][EnumName] : DefaultSchemaEnumNameOrOptions extends keyof DefaultSchema["Enums"] @@ -1308,15 +1303,13 @@ export type CompositeTypes< | { schema: keyof DatabaseWithoutInternals }, CompositeTypeName extends PublicCompositeTypeNameOrOptions extends { schema: keyof DatabaseWithoutInternals; - } - ? keyof DatabaseWithoutInternals[ + } ? keyof DatabaseWithoutInternals[ PublicCompositeTypeNameOrOptions["schema"] ]["CompositeTypes"] : never = never, > = PublicCompositeTypeNameOrOptions extends { schema: keyof DatabaseWithoutInternals; -} - ? DatabaseWithoutInternals[PublicCompositeTypeNameOrOptions["schema"]][ +} ? DatabaseWithoutInternals[PublicCompositeTypeNameOrOptions["schema"]][ "CompositeTypes" ][CompositeTypeName] : PublicCompositeTypeNameOrOptions extends @@ -1330,6 +1323,12 @@ export const Constants = { }, public: { Enums: { + estado_asignatura: [ + "borrador", + "revisada", + "aprobada", + "generando", + ], estado_tarea_revision: ["PENDIENTE", "COMPLETADA", "OMITIDA"], fuente_cambio: ["HUMANO", "IA"], nivel_plan_estudio: [ diff --git a/supabase/functions/ai-generate-subject/index.ts b/supabase/functions/ai-generate-subject/index.ts index 9045854..7da3162 100644 --- a/supabase/functions/ai-generate-subject/index.ts +++ b/supabase/functions/ai-generate-subject/index.ts @@ -9,6 +9,66 @@ import { type StructuredResponseOptions, } from "../_shared/openai-service.ts"; +/** + * JSON input signature (when `Content-Type: application/json`) + * + * Required: + * - `id` (uuid) - corresponde a la columna `id` de `asignaturas` + * + * Optional patch fields (any subset is allowed): + * - `nombre` (string) + * - `codigo` (string | null) + * - `tipo` (string | null) // must match DB enum values + * - `creditos` (number) + * - `horas_academicas` (number | null) + * - `horas_independientes` (number | null) + * - `numero_ciclo` (number | null) + * - `estructura_id` (uuid | null) + * - `plan_estudio_id` (uuid) + * - `linea_plan_id` (uuid | null) + * - `orden_celda` (number | null) + * + * IA config (optional): + * - `descripcionEnfoqueAcademico` (string) + * + * Notes: + * - This JSON flow does NOT accept `instruccionesAdicionalesIA`. + * - Missing optional fields are ignored (the existing DB values remain). + */ + +const JsonUpdateSchema = z + .object({ + id: z.string().uuid("id debe ser un UUID"), + + // patch fields (all optional) — nombres coinciden con columnas DB + nombre: z.string().min(1).optional(), + codigo: z.union([z.string().min(1), z.null()]).optional(), + tipo: z.union([z.string().min(1), z.null()]).optional(), + creditos: z.number().positive().optional(), + horas_academicas: z.number().int().nonnegative().nullable().optional(), + horas_independientes: z.number().int().nonnegative().nullable().optional(), + numero_ciclo: z.number().int().positive().nullable().optional(), + estructura_id: z.string().uuid().nullable().optional(), + plan_estudio_id: z.string().uuid().optional(), + linea_plan_id: z.string().uuid().nullable().optional(), + orden_celda: z.number().int().nonnegative().nullable().optional(), + + // IA config (no instruccionesAdicionalesIA) + descripcionEnfoqueAcademico: z.string().optional(), + }) + .strict(); + +type EdgeAIGenerateSubjectJsonUpdateInput = z.infer; + +function formatZodIssues(issues: z.ZodIssue[]): string { + return issues + .map((issue, i) => { + const path = issue.path.length ? issue.path.join(".") : "(root)"; + return `${i + 1}. ${path}: ${issue.message}`; + }) + .join("\n"); +} + Deno.serve(async (req: Request): Promise => { const url = new URL(req.url); const functionName = url.pathname.split("/").pop(); @@ -53,7 +113,9 @@ Deno.serve(async (req: Request): Promise => { } const contentType = (req.headers.get("content-type") || "").toLowerCase(); - if (!contentType.startsWith("multipart/form-data")) { + const isMultipart = contentType.startsWith("multipart/form-data"); + const isJson = contentType.includes("application/json"); + if (!isMultipart && !isJson) { console.error( `[${ new Date().toISOString() @@ -108,6 +170,299 @@ Deno.serve(async (req: Request): Promise => { SERVICE_ROLE_KEY, ); + // ----------------------------- + // JSON update flow + // ----------------------------- + if (isJson) { + let rawBody: unknown; + try { + rawBody = await req.json(); + } catch (e) { + throw new HttpError( + 400, + "Body JSON inválido.", + "INVALID_JSON", + { cause: e }, + ); + } + + const parsedBody = JsonUpdateSchema.safeParse(rawBody); + if (!parsedBody.success) { + throw new HttpError( + 422, + formatZodIssues(parsedBody.error.issues), + "VALIDATION_ERROR", + parsedBody.error, + ); + } + + const payload: EdgeAIGenerateSubjectJsonUpdateInput = parsedBody.data; + + const { data: existingAsignatura, error: existingAsignaturaError } = + await supabaseService + .from("asignaturas") + .select( + "id,plan_estudio_id,estructura_id,nombre,codigo,tipo,creditos,horas_academicas,horas_independientes,numero_ciclo,linea_plan_id,orden_celda", + ) + .eq("id", payload.id) + .single(); + + if (existingAsignaturaError) { + const maybeCode = (existingAsignaturaError as { code?: string }).code; + if (maybeCode === "PGRST116") { + throw new HttpError( + 404, + "No se encontró la asignatura.", + "NOT_FOUND", + { table: "asignaturas", id: payload.id }, + ); + } + throw new HttpError( + 500, + "No se pudo obtener la asignatura.", + "SUPABASE_QUERY_FAILED", + existingAsignaturaError, + ); + } + + const resolved = { + plan_estudio_id: payload.plan_estudio_id ?? + existingAsignatura.plan_estudio_id, + estructura_id: payload.estructura_id ?? + existingAsignatura.estructura_id, + nombre: payload.nombre ?? existingAsignatura.nombre, + codigo: payload.codigo ?? existingAsignatura.codigo, + tipo: payload.tipo ?? existingAsignatura.tipo, + creditos: payload.creditos ?? existingAsignatura.creditos, + horas_academicas: payload.horas_academicas ?? + existingAsignatura.horas_academicas, + horas_independientes: payload.horas_independientes ?? + existingAsignatura.horas_independientes, + numero_ciclo: payload.numero_ciclo ?? existingAsignatura.numero_ciclo, + linea_plan_id: payload.linea_plan_id ?? + existingAsignatura.linea_plan_id, + orden_celda: payload.orden_celda ?? existingAsignatura.orden_celda, + }; + + if (!resolved.estructura_id) { + throw new HttpError( + 422, + "estructura_id es requerido (no está presente ni en el JSON ni en la asignatura existente).", + "VALIDATION_ERROR", + { + id: payload.id, + existing_estructura_id: existingAsignatura.estructura_id, + }, + ); + } + + const { data: estructura, error: estructuraError } = await supabaseService + .from("estructuras_asignatura") + .select("id,nombre,definicion,version") + .eq("id", resolved.estructura_id) + .single(); + if (estructuraError) { + const maybeCode = (estructuraError as { code?: string }).code; + if (maybeCode === "PGRST116") { + throw new HttpError( + 404, + "No se encontró la estructura de la asignatura.", + "NOT_FOUND", + { table: "estructuras_asignatura", id: resolved.estructura_id }, + ); + } + throw new HttpError( + 500, + "No se pudo obtener la estructura de la asignatura.", + "SUPABASE_QUERY_FAILED", + estructuraError, + ); + } + + const systemPrompt = + "Eres un asistente experto en diseño curricular. Responde únicamente con JSON válido que cumpla estrictamente el JSON Schema proporcionado."; + + const userPrompt = + `Genera un borrador completo completo de una ASIGNATURA con base en lo siguiente:\n` + + `- Nombre de la asignatura: ${resolved.nombre}\n` + + `- Código (clave de la asignatura): ${ + resolved.codigo ?? "(no especificado)" + }\n` + + `- Tipo: ${resolved.tipo ?? "(no especificado)"}\n` + + `- Número de créditos: ${resolved.creditos}\n` + + `- Horas académicas: ${ + resolved.horas_academicas ?? "(no especificado)" + }\n` + + `- Horas independientes: ${ + resolved.horas_independientes ?? "(no especificado)" + }\n` + + `- Descripción del enfoque académico (sobre el contenido de la respuesta generada): ${ + payload.descripcionEnfoqueAcademico ?? "(ninguna)" + }`; + + const schemaDef: Record = + typeof estructura?.definicion === "object" && + estructura?.definicion !== null + ? (estructura.definicion as Record) + : {}; + + const aiStructuredPayload: StructuredResponseOptions = { + model: "gpt-5-nano", + input: [ + { role: "system", content: systemPrompt }, + { role: "user", content: userPrompt }, + ], + text: { + format: { + type: "json_schema", + name: "asignatura_contenido_tematico", + schema: schemaDef, + strict: true, + }, + }, + }; + + const svc = OpenAIService.fromEnv(); + if (!(svc instanceof OpenAIService)) { + throw new HttpError( + 500, + "Configuración del servidor incompleta.", + "OPENAI_MISCONFIGURED", + svc, + ); + } + + const aiResult = await svc.createStructuredResponse(aiStructuredPayload); + if (!aiResult.ok) { + const status = aiResult.code === "MissingEnv" ? 500 : 502; + throw new HttpError( + status, + "No se pudo generar la asignatura con IA.", + "OPENAI_REQUEST_FAILED", + aiResult, + ); + } + + let aiOutput = aiResult.output ?? null; + if (aiOutput == null && aiResult.outputText) { + try { + aiOutput = JSON.parse(aiResult.outputText); + } catch { + throw new HttpError( + 502, + "La respuesta de la IA no es JSON válido.", + "OPENAI_INVALID_JSON", + { outputText: aiResult.outputText }, + ); + } + } + if (!aiOutput) { + throw new HttpError( + 502, + "La respuesta de la IA no contiene salida estructurada.", + "OPENAI_MISSING_STRUCTURED_OUTPUT", + { outputText: aiResult.outputText ?? null }, + ); + } + + const aiOutputJson: Json = aiOutput as unknown as Json; + + const updatePatch: Database["public"]["Tables"]["asignaturas"]["Update"] = + { + datos: aiOutputJson, + tipo_origen: "IA", + estado: "borrador", + }; + + // Apply only provided JSON fields (do not overwrite if missing) + if (payload.plan_estudio_id !== undefined) { + updatePatch.plan_estudio_id = payload.plan_estudio_id; + } + if (payload.estructura_id !== undefined) { + updatePatch.estructura_id = payload.estructura_id; + } + if (payload.nombre !== undefined) { + updatePatch.nombre = payload.nombre; + } + if (payload.codigo !== undefined) { + updatePatch.codigo = payload.codigo; + } + if (payload.tipo !== undefined) { + updatePatch.tipo = payload.tipo as Database["public"]["Tables"][ + "asignaturas" + ]["Update"]["tipo"]; + } + if (payload.creditos !== undefined) { + updatePatch.creditos = payload.creditos; + } + if (payload.horas_academicas !== undefined) { + updatePatch.horas_academicas = payload.horas_academicas; + } + if (payload.horas_independientes !== undefined) { + updatePatch.horas_independientes = payload.horas_independientes; + } + if (payload.numero_ciclo !== undefined) { + updatePatch.numero_ciclo = payload.numero_ciclo; + } + if (payload.linea_plan_id !== undefined) { + updatePatch.linea_plan_id = payload.linea_plan_id; + } + if (payload.orden_celda !== undefined) { + updatePatch.orden_celda = payload.orden_celda; + } + + updatePatch.meta_origen = { + generado_por: "ai_generate_subject_update_json", + ai: { + responseId: aiResult.responseId ?? null, + conversationId: aiResult.conversationId ?? null, + model: aiResult.model, + usage: aiResult.usage ?? null, + }, + iaConfig: { + descripcionEnfoqueAcademico: payload.descripcionEnfoqueAcademico ?? + null, + }, + } as unknown as Json; + + const { data: asignatura, error: asignaturaError } = await supabaseService + .from("asignaturas") + .update(updatePatch) + .eq("id", payload.id) + .select( + "id,nombre,codigo,tipo,creditos,horas_academicas,horas_independientes,estructura_id,plan_estudio_id,contenido_tematico,meta_origen,datos,creado_en,actualizado_en,numero_ciclo,linea_plan_id,orden_celda", + ) + .single(); + + if (asignaturaError) { + const maybeCode = (asignaturaError as { code?: string }).code; + if (maybeCode === "PGRST116") { + throw new HttpError( + 404, + "No se encontró la asignatura.", + "NOT_FOUND", + { table: "asignaturas", id: payload.id }, + ); + } + throw new HttpError( + 500, + "No se pudo actualizar la asignatura.", + "SUPABASE_UPDATE_FAILED", + asignaturaError, + ); + } + + console.log( + `[${ + new Date().toISOString() + }][${functionName}]: JSON update processed successfully`, + ); + return sendSuccess(asignatura); + } + + // ----------------------------- + // Multipart create flow (existing) + // ----------------------------- const formData = await req.formData(); const validation = parseAndValidate(formData); if (!validation.success) { diff --git a/supabase/migrations/20260212214439_columna_estado_asignaturas.sql b/supabase/migrations/20260212214439_columna_estado_asignaturas.sql new file mode 100644 index 0000000..e17a355 --- /dev/null +++ b/supabase/migrations/20260212214439_columna_estado_asignaturas.sql @@ -0,0 +1,171 @@ +create type "public"."estado_asignatura" as enum ('borrador', 'revisada', 'aprobada', 'generando'); + +alter table "public"."asignaturas" add column "estado" public.estado_asignatura not null default 'generando'::public.estado_asignatura; + +set check_function_bodies = off; + +CREATE OR REPLACE FUNCTION public.fn_log_cambios_planes_estudio() + RETURNS trigger + LANGUAGE plpgsql +AS $function$declare + k text; + old_val jsonb; + new_val jsonb; + + v_response_id text; +begin + v_response_id := nullif(new.meta_origen->>'response_id',''); + + -- INSERT -> CREACION + if tg_op = 'INSERT' then + insert into public.cambios_plan ( + plan_estudio_id, + cambiado_por, + tipo, + campo, + valor_anterior, + valor_nuevo, + response_id + ) + values ( + new.id, + new.creado_por, + 'CREACION'::public.tipo_cambio, + null, + null, + to_jsonb(new), + null + ); + + return new; + end if; + + -- DELETE (opcional): si no lo quieres, bórralo + if tg_op = 'DELETE' then + insert into public.cambios_plan ( + plan_estudio_id, + cambiado_por, + tipo, + campo, + valor_anterior, + valor_nuevo, + response_id + ) + values ( + old.id, + old.actualizado_por, + 'OTRO'::public.tipo_cambio, + 'DELETE', + to_jsonb(old), + null, + null + ); + + return old; + end if; + + -- UPDATE ---------------------------------------------------------- + -- 1) Transición de estado + if (new.estado_actual_id is distinct from old.estado_actual_id) then + insert into public.cambios_plan ( + plan_estudio_id, cambiado_por, tipo, campo, valor_anterior, valor_nuevo, response_id + ) + values ( + new.id, + new.actualizado_por, + 'TRANSICION_ESTADO'::public.tipo_cambio, + 'estado_actual_id', + to_jsonb(old.estado_actual_id), + to_jsonb(new.estado_actual_id), + null + ); + end if; + + -- 2) Cambios en JSONB "datos" (diff top-level por llave) + if (new.datos is distinct from old.datos) then + for k in + select distinct key + from ( + select jsonb_object_keys(coalesce(old.datos, '{}'::jsonb)) as key + union all + select jsonb_object_keys(coalesce(new.datos, '{}'::jsonb)) as key + ) t + loop + old_val := coalesce(old.datos, '{}'::jsonb) -> k; + new_val := coalesce(new.datos, '{}'::jsonb) -> k; + + if (old_val is distinct from new_val) then + insert into public.cambios_plan ( + plan_estudio_id, cambiado_por, tipo, campo, valor_anterior, valor_nuevo, response_id + ) + values ( + new.id, + new.actualizado_por, + 'ACTUALIZACION_CAMPO'::public.tipo_cambio, + k, + old_val, + new_val, + v_response_id + ); + end if; + end loop; + + end if; + + -- 3) Cambios en columnas "normales" (uno por columna) + if (new.nombre is distinct from old.nombre) then + insert into public.cambios_plan values (gen_random_uuid(), new.id, new.actualizado_por, now(), + 'ACTUALIZACION'::public.tipo_cambio, 'nombre', to_jsonb(old.nombre), to_jsonb(new.nombre), null); + end if; + + if (new.nivel is distinct from old.nivel) then + insert into public.cambios_plan values (gen_random_uuid(), new.id, new.actualizado_por, now(), + 'ACTUALIZACION'::public.tipo_cambio, 'nivel', to_jsonb(old.nivel), to_jsonb(new.nivel), null); + end if; + + if (new.tipo_ciclo is distinct from old.tipo_ciclo) then + insert into public.cambios_plan values (gen_random_uuid(), new.id, new.actualizado_por, now(), + 'ACTUALIZACION'::public.tipo_cambio, 'tipo_ciclo', to_jsonb(old.tipo_ciclo), to_jsonb(new.tipo_ciclo), null); + end if; + + if (new.numero_ciclos is distinct from old.numero_ciclos) then + insert into public.cambios_plan values (gen_random_uuid(), new.id, new.actualizado_por, now(), + 'ACTUALIZACION'::public.tipo_cambio, 'numero_ciclos', to_jsonb(old.numero_ciclos), to_jsonb(new.numero_ciclos), null); + end if; + + if (new.activo is distinct from old.activo) then + insert into public.cambios_plan values (gen_random_uuid(), new.id, new.actualizado_por, now(), + 'ACTUALIZACION'::public.tipo_cambio, 'activo', to_jsonb(old.activo), to_jsonb(new.activo), null); + end if; + + if (new.carrera_id is distinct from old.carrera_id) then + insert into public.cambios_plan values (gen_random_uuid(), new.id, new.actualizado_por, now(), + 'ACTUALIZACION'::public.tipo_cambio, 'carrera_id', to_jsonb(old.carrera_id), to_jsonb(new.carrera_id), null); + end if; + + if (new.estructura_id is distinct from old.estructura_id) then + insert into public.cambios_plan values (gen_random_uuid(), new.id, new.actualizado_por, now(), + 'ACTUALIZACION'::public.tipo_cambio, 'estructura_id', to_jsonb(old.estructura_id), to_jsonb(new.estructura_id), null); + end if; + + if (new.tipo_origen is distinct from old.tipo_origen) then + insert into public.cambios_plan values (gen_random_uuid(), new.id, new.actualizado_por, now(), + 'ACTUALIZACION'::public.tipo_cambio, 'tipo_origen', to_jsonb(old.tipo_origen), to_jsonb(new.tipo_origen), null); + end if; + + + if (new.conversation_id is distinct from old.conversation_id) then + insert into public.cambios_plan values (gen_random_uuid(), new.id, new.actualizado_por, now(), + 'ACTUALIZACION'::public.tipo_cambio, 'conversation_id', to_jsonb(old.conversation_id), to_jsonb(new.conversation_id), null); + end if; + + -- 🔥 consumirlo para que NO se guarde en planes_estudio + if v_response_id is not null then + new.meta_origen := new.meta_origen - 'response_id'; + end if; + + return new; +end;$function$ +; + + diff --git a/supabase/snippets/Untitled query 470.sql b/supabase/snippets/Untitled query 470.sql new file mode 100644 index 0000000..33c8b94 --- /dev/null +++ b/supabase/snippets/Untitled query 470.sql @@ -0,0 +1 @@ +alter publication supabase_realtime drop table asignaturas; \ No newline at end of file -- 2.52.0 From 9981b302608d966f81f7a878e8a3efe42c5e14c9 Mon Sep 17 00:00:00 2001 From: Guillermo Arrieta Medina Date: Thu, 12 Feb 2026 16:45:38 -0600 Subject: [PATCH 7/7] feat: update subject suggestion guidelines and remove obsolete migration - Enhanced the subject suggestion guidelines to specify that descriptions must not exceed 200 characters. - Deleted the migration file for adding conversation_id to asignaturas and the associated view, as it is no longer needed. --- deno.lock | 240 +- scripts/generacion_de_tipos_de_supabase.txt | 7 +- supabase/functions/_shared/database.types.ts | 2776 +++++++++-------- .../generate-subject-suggestions/index.ts | 2 +- ...6172724_conversation_id_en_asignaturas.sql | 16 - 5 files changed, 1654 insertions(+), 1387 deletions(-) delete mode 100644 supabase/migrations/20260206172724_conversation_id_en_asignaturas.sql diff --git a/deno.lock b/deno.lock index c3819bf..5aae0d0 100644 --- a/deno.lock +++ b/deno.lock @@ -16,11 +16,15 @@ "jsr:@std/io@0.225": "0.225.3", "jsr:@std/path@1": "1.1.4", "jsr:@std/path@^1.1.4": "1.1.4", + "jsr:@supabase/functions-js@2": "2.95.3", "jsr:@zod/zod@^4.3.5": "4.3.5", + "npm:@supabase/supabase-js@2": "2.90.1", "npm:@supabase/supabase-js@^2.90.1": "2.90.1", "npm:@types/bun@^1.3.5": "1.3.5", "npm:deno@^2.6.4": "2.6.4", - "npm:openai@^6.16.0": "6.16.0", + "npm:openai@6.16.0": "6.16.0_zod@3.25.76", + "npm:openai@^4.52.5": "4.104.0_zod@3.25.76", + "npm:openai@^6.16.0": "6.16.0_zod@3.25.76", "npm:supabase@^2.72.6": "2.72.6", "npm:zod@3": "3.25.76" }, @@ -87,6 +91,12 @@ "jsr:@std/internal" ] }, + "@supabase/functions-js@2.95.3": { + "integrity": "cedb60df283f2d9dabf39655a8829f2c99d173e5e11a867c46e6987c59a1d582", + "dependencies": [ + "npm:openai@^4.52.5" + ] + }, "@zod/zod@4.3.5": { "integrity": "3d14553f025d6e3d1a836b9c56e366bbce27d942301e8c7c1ac4c365df5f8085" } @@ -178,8 +188,15 @@ "bun-types" ] }, - "@types/node@25.0.8": { - "integrity": "sha512-powIePYMmC3ibL0UJ2i2s0WIbq6cg6UyVFQxSCpaPxxzAaziRfimGivjdF943sSGV6RADVbk0Nvlm5P/FB44Zg==", + "@types/node-fetch@2.6.13": { + "integrity": "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==", + "dependencies": [ + "@types/node", + "form-data" + ] + }, + "@types/node@18.19.130": { + "integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==", "dependencies": [ "undici-types" ] @@ -193,9 +210,24 @@ "@types/node" ] }, + "abort-controller@3.0.0": { + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dependencies": [ + "event-target-shim" + ] + }, "agent-base@7.1.4": { "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==" }, + "agentkeepalive@4.6.0": { + "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", + "dependencies": [ + "humanize-ms" + ] + }, + "asynckit@0.4.0": { + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, "bin-links@6.0.0": { "integrity": "sha512-X4CiKlcV2GjnCMwnKAfbVWpHa++65th9TuzAEYtZoATiOE2DQKhSp4CJlyLoTqdhBKlXjpXjCTYPNNFS33Fi6w==", "dependencies": [ @@ -212,12 +244,25 @@ "@types/node" ] }, + "call-bind-apply-helpers@1.0.2": { + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dependencies": [ + "es-errors", + "function-bind" + ] + }, "chownr@3.0.0": { "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==" }, "cmd-shim@8.0.0": { "integrity": "sha512-Jk/BK6NCapZ58BKUxlSI+ouKRbjH1NLZCgJkYoab+vEHUY3f6OzpNBN9u7HFSv9J6TRDGs4PLOHezoKGaFRSCA==" }, + "combined-stream@1.0.8": { + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": [ + "delayed-stream" + ] + }, "data-uri-to-buffer@4.0.1": { "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==" }, @@ -227,6 +272,9 @@ "ms" ] }, + "delayed-stream@1.0.0": { + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" + }, "deno@2.6.4": { "integrity": "sha512-/nHUtrwoqUsjVTBWGqz3SjAbtBSfqCLb38YdkwstrO5qpQd3RC20dxsGYPPANy9RqMFJMfDyARL7RtOcUfatpw==", "optionalDependencies": [ @@ -240,11 +288,63 @@ "scripts": true, "bin": true }, + "dunder-proto@1.0.1": { + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dependencies": [ + "call-bind-apply-helpers", + "es-errors", + "gopd" + ] + }, + "es-define-property@1.0.1": { + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==" + }, + "es-errors@1.3.0": { + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==" + }, + "es-object-atoms@1.1.1": { + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dependencies": [ + "es-errors" + ] + }, + "es-set-tostringtag@2.1.0": { + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dependencies": [ + "es-errors", + "get-intrinsic", + "has-tostringtag", + "hasown" + ] + }, + "event-target-shim@5.0.1": { + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==" + }, "fetch-blob@3.2.0": { "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", "dependencies": [ "node-domexception", - "web-streams-polyfill" + "web-streams-polyfill@3.3.3" + ] + }, + "form-data-encoder@1.7.2": { + "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==" + }, + "form-data@4.0.5": { + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "dependencies": [ + "asynckit", + "combined-stream", + "es-set-tostringtag", + "hasown", + "mime-types" + ] + }, + "formdata-node@4.4.1": { + "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==", + "dependencies": [ + "node-domexception", + "web-streams-polyfill@4.0.0-beta.3" ] }, "formdata-polyfill@4.0.10": { @@ -253,6 +353,49 @@ "fetch-blob" ] }, + "function-bind@1.1.2": { + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" + }, + "get-intrinsic@1.3.0": { + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dependencies": [ + "call-bind-apply-helpers", + "es-define-property", + "es-errors", + "es-object-atoms", + "function-bind", + "get-proto", + "gopd", + "has-symbols", + "hasown", + "math-intrinsics" + ] + }, + "get-proto@1.0.1": { + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dependencies": [ + "dunder-proto", + "es-object-atoms" + ] + }, + "gopd@1.2.0": { + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==" + }, + "has-symbols@1.1.0": { + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==" + }, + "has-tostringtag@1.0.2": { + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dependencies": [ + "has-symbols" + ] + }, + "hasown@2.0.2": { + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": [ + "function-bind" + ] + }, "https-proxy-agent@7.0.6": { "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", "dependencies": [ @@ -260,12 +403,30 @@ "debug" ] }, + "humanize-ms@1.2.1": { + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "dependencies": [ + "ms" + ] + }, "iceberg-js@0.8.1": { "integrity": "sha512-1dhVQZXhcHje7798IVM+xoo/1ZdVfzOMIc8/rgVSijRK38EDqOJoGula9N/8ZI5RD8QTxNQtK/Gozpr+qUqRRA==" }, "imurmurhash@0.1.4": { "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==" }, + "math-intrinsics@1.1.0": { + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==" + }, + "mime-db@1.52.0": { + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + }, + "mime-types@2.1.35": { + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": [ + "mime-db" + ] + }, "minipass@7.1.2": { "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==" }, @@ -282,6 +443,12 @@ "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", "deprecated": true }, + "node-fetch@2.7.0": { + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": [ + "whatwg-url" + ] + }, "node-fetch@3.3.2": { "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", "dependencies": [ @@ -293,8 +460,31 @@ "npm-normalize-package-bin@5.0.0": { "integrity": "sha512-CJi3OS4JLsNMmr2u07OJlhcrPxCeOeP/4xq67aWNai6TNWWbTrlNDgl8NcFKVlcBKp18GPj+EzbNIgrBfZhsag==" }, - "openai@6.16.0": { + "openai@4.104.0_zod@3.25.76": { + "integrity": "sha512-p99EFNsA/yX6UhVO93f5kJsDRLAg+CTA2RBqdHK4RtK8u5IJw32Hyb2dTGKbnnFmnuoBv5r7Z2CURI9sGZpSuA==", + "dependencies": [ + "@types/node", + "@types/node-fetch", + "abort-controller", + "agentkeepalive", + "form-data-encoder", + "formdata-node", + "node-fetch@2.7.0", + "zod" + ], + "optionalPeers": [ + "zod" + ], + "bin": true + }, + "openai@6.16.0_zod@3.25.76": { "integrity": "sha512-fZ1uBqjFUjXzbGc35fFtYKEOxd20kd9fDpFeqWtsOZWiubY8CZ1NAlXHW3iathaFvqmNtCWMIsosCuyeI7Joxg==", + "dependencies": [ + "zod" + ], + "optionalPeers": [ + "zod" + ], "bin": true }, "proc-log@6.1.0": { @@ -311,7 +501,7 @@ "dependencies": [ "bin-links", "https-proxy-agent", - "node-fetch", + "node-fetch@3.3.2", "tar" ], "scripts": true, @@ -325,17 +515,34 @@ "minipass", "minizlib", "yallist" - ] + ], + "deprecated": true + }, + "tr46@0.0.3": { + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, "tslib@2.8.1": { "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" }, - "undici-types@7.16.0": { - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==" + "undici-types@5.26.5": { + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" }, "web-streams-polyfill@3.3.3": { "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==" }, + "web-streams-polyfill@4.0.0-beta.3": { + "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==" + }, + "webidl-conversions@3.0.1": { + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "whatwg-url@5.0.0": { + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": [ + "tr46", + "webidl-conversions" + ] + }, "write-file-atomic@7.0.0": { "integrity": "sha512-YnlPC6JqnZl6aO4uRc+dx5PHguiR9S6WeoLtpxNT9wIG+BDya7ZNE1q7KOjVgaA73hKhKLpVPgJ5QA9THQ5BRg==", "dependencies": [ @@ -353,6 +560,21 @@ "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==" } }, + "remote": { + "https://deno.land/x/zod@v3.22.4/ZodError.ts": "4de18ff525e75a0315f2c12066b77b5c2ae18c7c15ef7df7e165d63536fdf2ea", + "https://deno.land/x/zod@v3.22.4/errors.ts": "5285922d2be9700cc0c70c95e4858952b07ae193aa0224be3cbd5cd5567eabef", + "https://deno.land/x/zod@v3.22.4/external.ts": "a6cfbd61e9e097d5f42f8a7ed6f92f93f51ff927d29c9fbaec04f03cbce130fe", + "https://deno.land/x/zod@v3.22.4/helpers/enumUtil.ts": "54efc393cc9860e687d8b81ff52e980def00fa67377ad0bf8b3104f8a5bf698c", + "https://deno.land/x/zod@v3.22.4/helpers/errorUtil.ts": "7a77328240be7b847af6de9189963bd9f79cab32bbc61502a9db4fe6683e2ea7", + "https://deno.land/x/zod@v3.22.4/helpers/parseUtil.ts": "f791e6e65a0340d85ad37d26cd7a3ba67126cd9957eac2b7163162155283abb1", + "https://deno.land/x/zod@v3.22.4/helpers/partialUtil.ts": "998c2fe79795257d4d1cf10361e74492f3b7d852f61057c7c08ac0a46488b7e7", + "https://deno.land/x/zod@v3.22.4/helpers/typeAliases.ts": "0fda31a063c6736fc3cf9090dd94865c811dfff4f3cb8707b932bf937c6f2c3e", + "https://deno.land/x/zod@v3.22.4/helpers/util.ts": "8baf19b19b2fca8424380367b90364b32503b6b71780269a6e3e67700bb02774", + "https://deno.land/x/zod@v3.22.4/index.ts": "d27aabd973613985574bc31f39e45cb5d856aa122ef094a9f38a463b8ef1a268", + "https://deno.land/x/zod@v3.22.4/locales/en.ts": "a7a25cd23563ccb5e0eed214d9b31846305ddbcdb9c5c8f508b108943366ab4c", + "https://deno.land/x/zod@v3.22.4/mod.ts": "64e55237cb4410e17d968cd08975566059f27638ebb0b86048031b987ba251c4", + "https://deno.land/x/zod@v3.22.4/types.ts": "724185522fafe43ee56a52333958764c8c8cd6ad4effa27b42651df873fc151e" + }, "workspace": { "dependencies": [ "jsr:@david/dax@~0.44.2", diff --git a/scripts/generacion_de_tipos_de_supabase.txt b/scripts/generacion_de_tipos_de_supabase.txt index e98678d..35d8d2b 100644 --- a/scripts/generacion_de_tipos_de_supabase.txt +++ b/scripts/generacion_de_tipos_de_supabase.txt @@ -1,2 +1,5 @@ -Usar --local para generar tipos de la BDD local, y --linked para generar tipos de la BDD remota. -cmd /c "dx supabase gen types typescript --local > database-types.ts" \ No newline at end of file +Ejecutar estos dos comandos para la generación de tipos de supabase: + +[Console]::OutputEncoding = [System.Text.Encoding]::UTF8 + +dx supabase gen types typescript --local | Out-File -Encoding utf8 -FilePath "supabase\functions\_shared\database.types.ts" \ No newline at end of file diff --git a/supabase/functions/_shared/database.types.ts b/supabase/functions/_shared/database.types.ts index 4e5b0a0..c0dc240 100644 --- a/supabase/functions/_shared/database.types.ts +++ b/supabase/functions/_shared/database.types.ts @@ -1,1385 +1,1443 @@ export type Json = - | string - | number - | boolean - | null - | { [key: string]: Json | undefined } - | Json[]; + | string + | number + | boolean + | null + | { [key: string]: Json | undefined } + | Json[] export type Database = { - graphql_public: { - Tables: { - [_ in never]: never; - }; - 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: { - Tables: { - archivos: { - Row: { - bytes: number | null; - id: string; - mime_type: string | null; - nombre: string; - notas: string | null; - openai_file_id: string | null; - ruta_storage: string; - subido_en: string; - subido_por: string | null; - temporal: boolean; - }; - Insert: { - bytes?: number | null; - id?: string; - mime_type?: string | null; - nombre: string; - notas?: string | null; - openai_file_id?: string | null; - ruta_storage: string; - subido_en?: string; - subido_por?: string | null; - temporal?: boolean; - }; - Update: { - bytes?: number | null; - id?: string; - mime_type?: string | null; - nombre?: string; - notas?: string | null; - openai_file_id?: string | null; - ruta_storage?: string; - subido_en?: string; - subido_por?: string | null; - temporal?: boolean; - }; - Relationships: [ - { - foreignKeyName: "archivos_subido_por_fkey"; - columns: ["subido_por"]; - isOneToOne: false; - referencedRelation: "usuarios_app"; - referencedColumns: ["id"]; - }, - ]; - }; - asignaturas: { - Row: { - actualizado_en: string; - actualizado_por: string | null; - asignatura_hash: string | null; - codigo: string | null; - contenido_tematico: Json; - conversation_id: string | null; - creado_en: string; - creado_por: string | null; - creditos: number; - datos: Json; - estado: Database["public"]["Enums"]["estado_asignatura"]; - estructura_id: string | null; - horas_academicas: number | null; - horas_independientes: number | null; - id: string; - linea_plan_id: string | null; - meta_origen: Json; - nombre: string; - numero_ciclo: number | null; - orden_celda: number | null; - plan_estudio_id: string; - tipo: Database["public"]["Enums"]["tipo_asignatura"]; - tipo_origen: - | Database["public"]["Enums"]["tipo_origen"] - | null; - }; - Insert: { - actualizado_en?: string; - actualizado_por?: string | null; - asignatura_hash?: string | null; - codigo?: string | null; - contenido_tematico?: Json; - conversation_id?: string | null; - creado_en?: string; - creado_por?: string | null; - creditos: number; - datos?: Json; - estado?: Database["public"]["Enums"]["estado_asignatura"]; - estructura_id?: string | null; - horas_academicas?: number | null; - horas_independientes?: number | null; - id?: string; - linea_plan_id?: string | null; - meta_origen?: Json; - nombre: string; - numero_ciclo?: number | null; - orden_celda?: number | null; - plan_estudio_id: string; - tipo?: Database["public"]["Enums"]["tipo_asignatura"]; - tipo_origen?: - | Database["public"]["Enums"]["tipo_origen"] - | null; - }; - Update: { - actualizado_en?: string; - actualizado_por?: string | null; - asignatura_hash?: string | null; - codigo?: string | null; - contenido_tematico?: Json; - conversation_id?: string | null; - creado_en?: string; - creado_por?: string | null; - creditos?: number; - datos?: Json; - estado?: Database["public"]["Enums"]["estado_asignatura"]; - estructura_id?: string | null; - horas_academicas?: number | null; - horas_independientes?: number | null; - id?: string; - linea_plan_id?: string | null; - meta_origen?: Json; - nombre?: string; - numero_ciclo?: number | null; - orden_celda?: number | null; - plan_estudio_id?: string; - tipo?: Database["public"]["Enums"]["tipo_asignatura"]; - tipo_origen?: - | Database["public"]["Enums"]["tipo_origen"] - | null; - }; - Relationships: [ - { - foreignKeyName: "asignaturas_actualizado_por_fkey"; - columns: ["actualizado_por"]; - isOneToOne: false; - referencedRelation: "usuarios_app"; - referencedColumns: ["id"]; - }, - { - foreignKeyName: "asignaturas_creado_por_fkey"; - columns: ["creado_por"]; - isOneToOne: false; - referencedRelation: "usuarios_app"; - referencedColumns: ["id"]; - }, - { - foreignKeyName: "asignaturas_estructura_id_fkey"; - columns: ["estructura_id"]; - isOneToOne: false; - referencedRelation: "estructuras_asignatura"; - referencedColumns: ["id"]; - }, - { - foreignKeyName: "asignaturas_linea_plan_fk_compuesta"; - columns: ["linea_plan_id", "plan_estudio_id"]; - isOneToOne: false; - referencedRelation: "lineas_plan"; - referencedColumns: ["id", "plan_estudio_id"]; - }, - { - foreignKeyName: "asignaturas_plan_estudio_id_fkey"; - columns: ["plan_estudio_id"]; - isOneToOne: false; - referencedRelation: "planes_estudio"; - referencedColumns: ["id"]; - }, - { - foreignKeyName: "asignaturas_plan_estudio_id_fkey"; - columns: ["plan_estudio_id"]; - isOneToOne: false; - referencedRelation: "plantilla_plan"; - referencedColumns: ["plan_estudio_id"]; - }, - ]; - }; - bibliografia_asignatura: { - Row: { - actualizado_en: string; - asignatura_id: string; - biblioteca_item_id: string | null; - cita: string; - creado_en: string; - creado_por: string | null; - id: string; - tipo: Database["public"]["Enums"]["tipo_bibliografia"]; - tipo_fuente: - Database["public"]["Enums"]["tipo_fuente_bibliografia"]; - }; - Insert: { - actualizado_en?: string; - asignatura_id: string; - biblioteca_item_id?: string | null; - cita: string; - creado_en?: string; - creado_por?: string | null; - id?: string; - tipo: Database["public"]["Enums"]["tipo_bibliografia"]; - tipo_fuente?: - Database["public"]["Enums"]["tipo_fuente_bibliografia"]; - }; - Update: { - actualizado_en?: string; - asignatura_id?: string; - biblioteca_item_id?: string | null; - cita?: string; - creado_en?: string; - creado_por?: string | null; - id?: string; - tipo?: Database["public"]["Enums"]["tipo_bibliografia"]; - tipo_fuente?: - Database["public"]["Enums"]["tipo_fuente_bibliografia"]; - }; - Relationships: [ - { - foreignKeyName: - "bibliografia_asignatura_asignatura_id_fkey"; - columns: ["asignatura_id"]; - isOneToOne: false; - referencedRelation: "asignaturas"; - referencedColumns: ["id"]; - }, - { - foreignKeyName: - "bibliografia_asignatura_creado_por_fkey"; - columns: ["creado_por"]; - isOneToOne: false; - referencedRelation: "usuarios_app"; - referencedColumns: ["id"]; - }, - ]; - }; - cambios_asignatura: { - Row: { - asignatura_id: string; - cambiado_en: string; - cambiado_por: string | null; - campo: string | null; - fuente: Database["public"]["Enums"]["fuente_cambio"] | null; - id: string; - interaccion_ia_id: string | null; - tipo: Database["public"]["Enums"]["tipo_cambio"]; - valor_anterior: Json | null; - valor_nuevo: Json | null; - }; - Insert: { - asignatura_id: string; - cambiado_en?: string; - cambiado_por?: string | null; - campo?: string | null; - fuente?: - | Database["public"]["Enums"]["fuente_cambio"] - | null; - id?: string; - interaccion_ia_id?: string | null; - tipo: Database["public"]["Enums"]["tipo_cambio"]; - valor_anterior?: Json | null; - valor_nuevo?: Json | null; - }; - Update: { - asignatura_id?: string; - cambiado_en?: string; - cambiado_por?: string | null; - campo?: string | null; - fuente?: - | Database["public"]["Enums"]["fuente_cambio"] - | null; - id?: string; - interaccion_ia_id?: string | null; - tipo?: Database["public"]["Enums"]["tipo_cambio"]; - valor_anterior?: Json | null; - valor_nuevo?: Json | null; - }; - Relationships: [ - { - foreignKeyName: "cambios_asignatura_asignatura_id_fkey"; - columns: ["asignatura_id"]; - isOneToOne: false; - referencedRelation: "asignaturas"; - referencedColumns: ["id"]; - }, - { - foreignKeyName: "cambios_asignatura_cambiado_por_fkey"; - columns: ["cambiado_por"]; - isOneToOne: false; - referencedRelation: "usuarios_app"; - referencedColumns: ["id"]; - }, - ]; - }; - cambios_plan: { - Row: { - cambiado_en: string; - cambiado_por: string | null; - campo: string | null; - id: string; - plan_estudio_id: string; - response_id: string | null; - tipo: Database["public"]["Enums"]["tipo_cambio"]; - valor_anterior: Json | null; - valor_nuevo: Json | null; - }; - Insert: { - cambiado_en?: string; - cambiado_por?: string | null; - campo?: string | null; - id?: string; - plan_estudio_id: string; - response_id?: string | null; - tipo: Database["public"]["Enums"]["tipo_cambio"]; - valor_anterior?: Json | null; - valor_nuevo?: Json | null; - }; - Update: { - cambiado_en?: string; - cambiado_por?: string | null; - campo?: string | null; - id?: string; - plan_estudio_id?: string; - response_id?: string | null; - tipo?: Database["public"]["Enums"]["tipo_cambio"]; - valor_anterior?: Json | null; - valor_nuevo?: Json | null; - }; - Relationships: [ - { - foreignKeyName: "cambios_plan_cambiado_por_fkey"; - columns: ["cambiado_por"]; - isOneToOne: false; - referencedRelation: "usuarios_app"; - referencedColumns: ["id"]; - }, - ]; - }; - carreras: { - Row: { - activa: boolean; - actualizado_en: string; - clave_sep: string | null; - creado_en: string; - facultad_id: string; - id: string; - nombre: string; - nombre_corto: string | null; - }; - Insert: { - activa?: boolean; - actualizado_en?: string; - clave_sep?: string | null; - creado_en?: string; - facultad_id: string; - id?: string; - nombre: string; - nombre_corto?: string | null; - }; - Update: { - activa?: boolean; - actualizado_en?: string; - clave_sep?: string | null; - creado_en?: string; - facultad_id?: string; - id?: string; - nombre?: string; - nombre_corto?: string | null; - }; - Relationships: [ - { - foreignKeyName: "carreras_facultad_id_fkey"; - columns: ["facultad_id"]; - isOneToOne: false; - referencedRelation: "facultades"; - referencedColumns: ["id"]; - }, - ]; - }; - estados_plan: { - Row: { - clave: string; - es_final: boolean; - etiqueta: string; - id: string; - orden: number; - }; - Insert: { - clave: string; - es_final?: boolean; - etiqueta: string; - id?: string; - orden?: number; - }; - Update: { - clave?: string; - es_final?: boolean; - etiqueta?: string; - id?: string; - orden?: number; - }; - Relationships: []; - }; - estructuras_asignatura: { - Row: { - actualizado_en: string; - creado_en: string; - definicion: Json; - id: string; - nombre: string; - version: string | null; - }; - Insert: { - actualizado_en?: string; - creado_en?: string; - definicion?: Json; - id?: string; - nombre: string; - version?: string | null; - }; - Update: { - actualizado_en?: string; - creado_en?: string; - definicion?: Json; - id?: string; - nombre?: string; - version?: string | null; - }; - Relationships: []; - }; - estructuras_plan: { - Row: { - actualizado_en: string; - creado_en: string; - definicion: Json; - id: string; - nombre: string; - template_id: string | null; - tipo: Database["public"]["Enums"]["tipo_estructura_plan"]; - }; - Insert: { - actualizado_en?: string; - creado_en?: string; - definicion?: Json; - id?: string; - nombre: string; - template_id?: string | null; - tipo: Database["public"]["Enums"]["tipo_estructura_plan"]; - }; - Update: { - actualizado_en?: string; - creado_en?: string; - definicion?: Json; - id?: string; - nombre?: string; - template_id?: string | null; - tipo?: Database["public"]["Enums"]["tipo_estructura_plan"]; - }; - Relationships: []; - }; - facultades: { - Row: { - actualizado_en: string; - color: string | null; - creado_en: string; - icono: string | null; - id: string; - nombre: string; - nombre_corto: string | null; - }; - Insert: { - actualizado_en?: string; - color?: string | null; - creado_en?: string; - icono?: string | null; - id?: string; - nombre: string; - nombre_corto?: string | null; - }; - Update: { - actualizado_en?: string; - color?: string | null; - creado_en?: string; - icono?: string | null; - id?: string; - nombre?: string; - nombre_corto?: string | null; - }; - Relationships: []; - }; - interacciones_ia: { - Row: { - aceptada: boolean; - asignatura_id: string | null; - conversacion_id: string | null; - creado_en: string; - id: string; - ids_archivos: Json; - ids_vector_store: Json; - modelo: string | null; - plan_estudio_id: string | null; - prompt: Json; - respuesta: Json; - rutas_storage: Json; - temperatura: number | null; - tipo: Database["public"]["Enums"]["tipo_interaccion_ia"]; - usuario_id: string | null; - }; - Insert: { - aceptada?: boolean; - asignatura_id?: string | null; - conversacion_id?: string | null; - creado_en?: string; - id?: string; - ids_archivos?: Json; - ids_vector_store?: Json; - modelo?: string | null; - plan_estudio_id?: string | null; - prompt?: Json; - respuesta?: Json; - rutas_storage?: Json; - temperatura?: number | null; - tipo: Database["public"]["Enums"]["tipo_interaccion_ia"]; - usuario_id?: string | null; - }; - Update: { - aceptada?: boolean; - asignatura_id?: string | null; - conversacion_id?: string | null; - creado_en?: string; - id?: string; - ids_archivos?: Json; - ids_vector_store?: Json; - modelo?: string | null; - plan_estudio_id?: string | null; - prompt?: Json; - respuesta?: Json; - rutas_storage?: Json; - temperatura?: number | null; - tipo?: Database["public"]["Enums"]["tipo_interaccion_ia"]; - usuario_id?: string | null; - }; - Relationships: [ - { - foreignKeyName: "interacciones_ia_asignatura_id_fkey"; - columns: ["asignatura_id"]; - isOneToOne: false; - referencedRelation: "asignaturas"; - referencedColumns: ["id"]; - }, - { - foreignKeyName: "interacciones_ia_plan_estudio_id_fkey"; - columns: ["plan_estudio_id"]; - isOneToOne: false; - referencedRelation: "planes_estudio"; - referencedColumns: ["id"]; - }, - { - foreignKeyName: "interacciones_ia_plan_estudio_id_fkey"; - columns: ["plan_estudio_id"]; - isOneToOne: false; - referencedRelation: "plantilla_plan"; - referencedColumns: ["plan_estudio_id"]; - }, - { - foreignKeyName: "interacciones_ia_usuario_id_fkey"; - columns: ["usuario_id"]; - isOneToOne: false; - referencedRelation: "usuarios_app"; - referencedColumns: ["id"]; - }, - ]; - }; - lineas_plan: { - Row: { - actualizado_en: string; - area: string | null; - creado_en: string; - id: string; - nombre: string; - orden: number; - plan_estudio_id: string; - }; - Insert: { - actualizado_en?: string; - area?: string | null; - creado_en?: string; - id?: string; - nombre: string; - orden?: number; - plan_estudio_id: string; - }; - Update: { - actualizado_en?: string; - area?: string | null; - creado_en?: string; - id?: string; - nombre?: string; - orden?: number; - plan_estudio_id?: string; - }; - Relationships: [ - { - foreignKeyName: "lineas_plan_plan_estudio_id_fkey"; - columns: ["plan_estudio_id"]; - isOneToOne: false; - referencedRelation: "planes_estudio"; - referencedColumns: ["id"]; - }, - { - foreignKeyName: "lineas_plan_plan_estudio_id_fkey"; - columns: ["plan_estudio_id"]; - isOneToOne: false; - referencedRelation: "plantilla_plan"; - referencedColumns: ["plan_estudio_id"]; - }, - ]; - }; - notificaciones: { - Row: { - creado_en: string; - id: string; - leida: boolean; - leida_en: string | null; - payload: Json; - tipo: Database["public"]["Enums"]["tipo_notificacion"]; - usuario_id: string; - }; - Insert: { - creado_en?: string; - id?: string; - leida?: boolean; - leida_en?: string | null; - payload?: Json; - tipo: Database["public"]["Enums"]["tipo_notificacion"]; - usuario_id: string; - }; - Update: { - creado_en?: string; - id?: string; - leida?: boolean; - leida_en?: string | null; - payload?: Json; - tipo?: Database["public"]["Enums"]["tipo_notificacion"]; - usuario_id?: string; - }; - Relationships: [ - { - foreignKeyName: "notificaciones_usuario_id_fkey"; - columns: ["usuario_id"]; - isOneToOne: false; - referencedRelation: "usuarios_app"; - referencedColumns: ["id"]; - }, - ]; - }; - planes_estudio: { - Row: { - activo: boolean; - actualizado_en: string; - actualizado_por: string | null; - carrera_id: string; - conversation_id: string | null; - creado_en: string; - creado_por: string | null; - datos: Json; - estado_actual_id: string | null; - estructura_id: string; - id: string; - meta_origen: Json; - nivel: Database["public"]["Enums"]["nivel_plan_estudio"]; - nombre: string; - nombre_search: string | null; - numero_ciclos: number; - plan_hash: string | null; - tipo_ciclo: Database["public"]["Enums"]["tipo_ciclo"]; - tipo_origen: - | Database["public"]["Enums"]["tipo_origen"] - | null; - }; - Insert: { - activo?: boolean; - actualizado_en?: string; - actualizado_por?: string | null; - carrera_id: string; - conversation_id?: string | null; - creado_en?: string; - creado_por?: string | null; - datos?: Json; - estado_actual_id?: string | null; - estructura_id: string; - id?: string; - meta_origen?: Json; - nivel: Database["public"]["Enums"]["nivel_plan_estudio"]; - nombre: string; - nombre_search?: string | null; - numero_ciclos: number; - plan_hash?: string | null; - tipo_ciclo: Database["public"]["Enums"]["tipo_ciclo"]; - tipo_origen?: - | Database["public"]["Enums"]["tipo_origen"] - | null; - }; - Update: { - activo?: boolean; - actualizado_en?: string; - actualizado_por?: string | null; - carrera_id?: string; - conversation_id?: string | null; - creado_en?: string; - creado_por?: string | null; - datos?: Json; - estado_actual_id?: string | null; - estructura_id?: string; - id?: string; - meta_origen?: Json; - nivel?: Database["public"]["Enums"]["nivel_plan_estudio"]; - nombre?: string; - nombre_search?: string | null; - numero_ciclos?: number; - plan_hash?: string | null; - tipo_ciclo?: Database["public"]["Enums"]["tipo_ciclo"]; - tipo_origen?: - | Database["public"]["Enums"]["tipo_origen"] - | null; - }; - Relationships: [ - { - foreignKeyName: "planes_estudio_actualizado_por_fkey"; - columns: ["actualizado_por"]; - isOneToOne: false; - referencedRelation: "usuarios_app"; - referencedColumns: ["id"]; - }, - { - foreignKeyName: "planes_estudio_carrera_id_fkey"; - columns: ["carrera_id"]; - isOneToOne: false; - referencedRelation: "carreras"; - referencedColumns: ["id"]; - }, - { - foreignKeyName: "planes_estudio_creado_por_fkey"; - columns: ["creado_por"]; - isOneToOne: false; - referencedRelation: "usuarios_app"; - referencedColumns: ["id"]; - }, - { - foreignKeyName: "planes_estudio_estado_actual_id_fkey"; - columns: ["estado_actual_id"]; - isOneToOne: false; - referencedRelation: "estados_plan"; - referencedColumns: ["id"]; - }, - { - foreignKeyName: "planes_estudio_estructura_id_fkey"; - columns: ["estructura_id"]; - isOneToOne: false; - referencedRelation: "estructuras_plan"; - referencedColumns: ["id"]; - }, - { - foreignKeyName: "planes_estudio_estructura_id_fkey"; - columns: ["estructura_id"]; - isOneToOne: false; - referencedRelation: "plantilla_plan"; - referencedColumns: ["estructura_id"]; - }, - ]; - }; - responsables_asignatura: { - Row: { - asignatura_id: string; - creado_en: string; - id: string; - rol: Database["public"]["Enums"][ - "rol_responsable_asignatura" - ]; - usuario_id: string; - }; - Insert: { - asignatura_id: string; - creado_en?: string; - id?: string; - rol?: Database["public"]["Enums"][ - "rol_responsable_asignatura" - ]; - usuario_id: string; - }; - Update: { - asignatura_id?: string; - creado_en?: string; - id?: string; - rol?: Database["public"]["Enums"][ - "rol_responsable_asignatura" - ]; - usuario_id?: string; - }; - Relationships: [ - { - foreignKeyName: - "responsables_asignatura_asignatura_id_fkey"; - columns: ["asignatura_id"]; - isOneToOne: false; - referencedRelation: "asignaturas"; - referencedColumns: ["id"]; - }, - { - foreignKeyName: - "responsables_asignatura_usuario_id_fkey"; - columns: ["usuario_id"]; - isOneToOne: false; - referencedRelation: "usuarios_app"; - referencedColumns: ["id"]; - }, - ]; - }; - roles: { - Row: { - clave: string; - descripcion: string | null; - id: string; - nombre: string; - }; - Insert: { - clave: string; - descripcion?: string | null; - id?: string; - nombre: string; - }; - Update: { - clave?: string; - descripcion?: string | null; - id?: string; - nombre?: string; - }; - Relationships: []; - }; - tareas_revision: { - Row: { - asignado_a: string; - completado_en: string | null; - creado_en: string; - estado_id: string | null; - estatus: - Database["public"]["Enums"]["estado_tarea_revision"]; - fecha_limite: string | null; - id: string; - plan_estudio_id: string; - rol_id: string | null; - }; - Insert: { - asignado_a: string; - completado_en?: string | null; - creado_en?: string; - estado_id?: string | null; - estatus?: - Database["public"]["Enums"]["estado_tarea_revision"]; - fecha_limite?: string | null; - id?: string; - plan_estudio_id: string; - rol_id?: string | null; - }; - Update: { - asignado_a?: string; - completado_en?: string | null; - creado_en?: string; - estado_id?: string | null; - estatus?: - Database["public"]["Enums"]["estado_tarea_revision"]; - fecha_limite?: string | null; - id?: string; - plan_estudio_id?: string; - rol_id?: string | null; - }; - Relationships: [ - { - foreignKeyName: "tareas_revision_asignado_a_fkey"; - columns: ["asignado_a"]; - isOneToOne: false; - referencedRelation: "usuarios_app"; - referencedColumns: ["id"]; - }, - { - foreignKeyName: "tareas_revision_estado_id_fkey"; - columns: ["estado_id"]; - isOneToOne: false; - referencedRelation: "estados_plan"; - referencedColumns: ["id"]; - }, - { - foreignKeyName: "tareas_revision_plan_estudio_id_fkey"; - columns: ["plan_estudio_id"]; - isOneToOne: false; - referencedRelation: "planes_estudio"; - referencedColumns: ["id"]; - }, - { - foreignKeyName: "tareas_revision_plan_estudio_id_fkey"; - columns: ["plan_estudio_id"]; - isOneToOne: false; - referencedRelation: "plantilla_plan"; - referencedColumns: ["plan_estudio_id"]; - }, - { - foreignKeyName: "tareas_revision_rol_id_fkey"; - columns: ["rol_id"]; - isOneToOne: false; - referencedRelation: "roles"; - referencedColumns: ["id"]; - }, - ]; - }; - transiciones_estado_plan: { - Row: { - creado_en: string; - desde_estado_id: string; - hacia_estado_id: string; - id: string; - rol_permitido_id: string; - }; - Insert: { - creado_en?: string; - desde_estado_id: string; - hacia_estado_id: string; - id?: string; - rol_permitido_id: string; - }; - Update: { - creado_en?: string; - desde_estado_id?: string; - hacia_estado_id?: string; - id?: string; - rol_permitido_id?: string; - }; - Relationships: [ - { - foreignKeyName: - "transiciones_estado_plan_desde_estado_id_fkey"; - columns: ["desde_estado_id"]; - isOneToOne: false; - referencedRelation: "estados_plan"; - referencedColumns: ["id"]; - }, - { - foreignKeyName: - "transiciones_estado_plan_hacia_estado_id_fkey"; - columns: ["hacia_estado_id"]; - isOneToOne: false; - referencedRelation: "estados_plan"; - referencedColumns: ["id"]; - }, - { - foreignKeyName: - "transiciones_estado_plan_rol_permitido_id_fkey"; - columns: ["rol_permitido_id"]; - isOneToOne: false; - referencedRelation: "roles"; - referencedColumns: ["id"]; - }, - ]; - }; - usuarios_app: { - Row: { - actualizado_en: string; - creado_en: string; - email: string | null; - externo: boolean; - id: string; - nombre_completo: string | null; - }; - Insert: { - actualizado_en?: string; - creado_en?: string; - email?: string | null; - externo?: boolean; - id: string; - nombre_completo?: string | null; - }; - Update: { - actualizado_en?: string; - creado_en?: string; - email?: string | null; - externo?: boolean; - id?: string; - nombre_completo?: string | null; - }; - Relationships: []; - }; - usuarios_roles: { - Row: { - carrera_id: string | null; - creado_en: string; - facultad_id: string | null; - id: string; - rol_id: string; - usuario_id: string; - }; - Insert: { - carrera_id?: string | null; - creado_en?: string; - facultad_id?: string | null; - id?: string; - rol_id: string; - usuario_id: string; - }; - Update: { - carrera_id?: string | null; - creado_en?: string; - facultad_id?: string | null; - id?: string; - rol_id?: string; - usuario_id?: string; - }; - Relationships: [ - { - foreignKeyName: "usuarios_roles_carrera_id_fkey"; - columns: ["carrera_id"]; - isOneToOne: false; - referencedRelation: "carreras"; - referencedColumns: ["id"]; - }, - { - foreignKeyName: "usuarios_roles_facultad_id_fkey"; - columns: ["facultad_id"]; - isOneToOne: false; - referencedRelation: "facultades"; - referencedColumns: ["id"]; - }, - { - foreignKeyName: "usuarios_roles_rol_id_fkey"; - columns: ["rol_id"]; - isOneToOne: false; - referencedRelation: "roles"; - referencedColumns: ["id"]; - }, - { - foreignKeyName: "usuarios_roles_usuario_id_fkey"; - columns: ["usuario_id"]; - isOneToOne: false; - referencedRelation: "usuarios_app"; - referencedColumns: ["id"]; - }, - ]; - }; - vector_stores: { - Row: { - creado_en: string; - creado_por: string | null; - id: string; - nombre: string; - openai_vector_id: string | null; - }; - Insert: { - creado_en?: string; - creado_por?: string | null; - id?: string; - nombre: string; - openai_vector_id?: string | null; - }; - Update: { - creado_en?: string; - creado_por?: string | null; - id?: string; - nombre?: string; - openai_vector_id?: string | null; - }; - Relationships: [ - { - foreignKeyName: "vector_stores_creado_por_fkey"; - columns: ["creado_por"]; - isOneToOne: false; - referencedRelation: "usuarios_app"; - referencedColumns: ["id"]; - }, - ]; - }; - }; - Views: { - plantilla_plan: { - Row: { - estructura_id: string | null; - plan_estudio_id: string | null; - template_id: string | null; - }; - Relationships: []; - }; - }; - Functions: { - unaccent: { Args: { "": string }; Returns: string }; - unaccent_immutable: { Args: { "": string }; Returns: string }; - }; - Enums: { - estado_asignatura: - | "borrador" - | "revisada" - | "aprobada" - | "generando"; - estado_tarea_revision: "PENDIENTE" | "COMPLETADA" | "OMITIDA"; - fuente_cambio: "HUMANO" | "IA"; - nivel_plan_estudio: - | "Licenciatura" - | "Maestría" - | "Doctorado" - | "Especialidad" - | "Diplomado" - | "Otro"; - puesto_tipo: - | "vicerrector" - | "director_facultad" - | "secretario_academico" - | "jefe_carrera" - | "profesor" - | "lci"; - rol_responsable_asignatura: - | "PROFESOR_RESPONSABLE" - | "COAUTOR" - | "REVISOR"; - tipo_asignatura: "OBLIGATORIA" | "OPTATIVA" | "TRONCAL" | "OTRA"; - tipo_bibliografia: "BASICA" | "COMPLEMENTARIA"; - tipo_cambio: - | "ACTUALIZACION_CAMPO" - | "ACTUALIZACION_MAPA" - | "TRANSICION_ESTADO" - | "OTRO" - | "CREACION" - | "ACTUALIZACION"; - tipo_ciclo: "Semestre" | "Cuatrimestre" | "Trimestre" | "Otro"; - tipo_estructura_plan: "CURRICULAR" | "NO_CURRICULAR"; - tipo_fuente_bibliografia: "MANUAL" | "BIBLIOTECA"; - tipo_interaccion_ia: - | "GENERAR" - | "MEJORAR_SECCION" - | "CHAT" - | "OTRA"; - tipo_notificacion: - | "PLAN_ASIGNADO" - | "ESTADO_CAMBIADO" - | "TAREA_ASIGNADA" - | "COMENTARIO" - | "OTRA"; - tipo_origen: - | "MANUAL" - | "IA" - | "CLONADO_INTERNO" - | "CLONADO_TRADICIONAL" - | "OTRO"; - }; - CompositeTypes: { - [_ in never]: never; - }; - }; -}; + graphql_public: { + Tables: { + [_ in never]: never + } + 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: { + Tables: { + archivos: { + Row: { + bytes: number | null + id: string + mime_type: string | null + nombre: string + notas: string | null + openai_file_id: string | null + ruta_storage: string + subido_en: string + subido_por: string | null + temporal: boolean + } + Insert: { + bytes?: number | null + id?: string + mime_type?: string | null + nombre: string + notas?: string | null + openai_file_id?: string | null + ruta_storage: string + subido_en?: string + subido_por?: string | null + temporal?: boolean + } + Update: { + bytes?: number | null + id?: string + mime_type?: string | null + nombre?: string + notas?: string | null + openai_file_id?: string | null + ruta_storage?: string + subido_en?: string + subido_por?: string | null + temporal?: boolean + } + Relationships: [ + { + foreignKeyName: "archivos_subido_por_fkey" + columns: ["subido_por"] + isOneToOne: false + referencedRelation: "usuarios_app" + referencedColumns: ["id"] + }, + ] + } + asignaturas: { + Row: { + actualizado_en: string + actualizado_por: string | null + asignatura_hash: string | null + codigo: string | null + contenido_tematico: Json + creado_en: string + creado_por: string | null + creditos: number + datos: Json + estado: Database["public"]["Enums"]["estado_asignatura"] + estructura_id: string | null + horas_academicas: number | null + horas_independientes: number | null + id: string + linea_plan_id: string | null + meta_origen: Json + nombre: string + numero_ciclo: number | null + orden_celda: number | null + plan_estudio_id: string + tipo: Database["public"]["Enums"]["tipo_asignatura"] + tipo_origen: Database["public"]["Enums"]["tipo_origen"] | null + } + Insert: { + actualizado_en?: string + actualizado_por?: string | null + asignatura_hash?: string | null + codigo?: string | null + contenido_tematico?: Json + creado_en?: string + creado_por?: string | null + creditos: number + datos?: Json + estado?: Database["public"]["Enums"]["estado_asignatura"] + estructura_id?: string | null + horas_academicas?: number | null + horas_independientes?: number | null + id?: string + linea_plan_id?: string | null + meta_origen?: Json + nombre: string + numero_ciclo?: number | null + orden_celda?: number | null + plan_estudio_id: string + tipo?: Database["public"]["Enums"]["tipo_asignatura"] + tipo_origen?: Database["public"]["Enums"]["tipo_origen"] | null + } + Update: { + actualizado_en?: string + actualizado_por?: string | null + asignatura_hash?: string | null + codigo?: string | null + contenido_tematico?: Json + creado_en?: string + creado_por?: string | null + creditos?: number + datos?: Json + estado?: Database["public"]["Enums"]["estado_asignatura"] + estructura_id?: string | null + horas_academicas?: number | null + horas_independientes?: number | null + id?: string + linea_plan_id?: string | null + meta_origen?: Json + nombre?: string + numero_ciclo?: number | null + orden_celda?: number | null + plan_estudio_id?: string + tipo?: Database["public"]["Enums"]["tipo_asignatura"] + tipo_origen?: Database["public"]["Enums"]["tipo_origen"] | null + } + Relationships: [ + { + foreignKeyName: "asignaturas_actualizado_por_fkey" + columns: ["actualizado_por"] + isOneToOne: false + referencedRelation: "usuarios_app" + referencedColumns: ["id"] + }, + { + foreignKeyName: "asignaturas_creado_por_fkey" + columns: ["creado_por"] + isOneToOne: false + referencedRelation: "usuarios_app" + referencedColumns: ["id"] + }, + { + foreignKeyName: "asignaturas_estructura_id_fkey" + columns: ["estructura_id"] + isOneToOne: false + referencedRelation: "estructuras_asignatura" + referencedColumns: ["id"] + }, + { + foreignKeyName: "asignaturas_linea_plan_fk_compuesta" + columns: ["linea_plan_id", "plan_estudio_id"] + isOneToOne: false + referencedRelation: "lineas_plan" + referencedColumns: ["id", "plan_estudio_id"] + }, + { + foreignKeyName: "asignaturas_plan_estudio_id_fkey" + columns: ["plan_estudio_id"] + isOneToOne: false + referencedRelation: "planes_estudio" + referencedColumns: ["id"] + }, + { + foreignKeyName: "asignaturas_plan_estudio_id_fkey" + columns: ["plan_estudio_id"] + isOneToOne: false + referencedRelation: "plantilla_plan" + referencedColumns: ["plan_estudio_id"] + }, + ] + } + bibliografia_asignatura: { + Row: { + actualizado_en: string + asignatura_id: string + biblioteca_item_id: string | null + cita: string + creado_en: string + creado_por: string | null + id: string + tipo: Database["public"]["Enums"]["tipo_bibliografia"] + tipo_fuente: Database["public"]["Enums"]["tipo_fuente_bibliografia"] + } + Insert: { + actualizado_en?: string + asignatura_id: string + biblioteca_item_id?: string | null + cita: string + creado_en?: string + creado_por?: string | null + id?: string + tipo: Database["public"]["Enums"]["tipo_bibliografia"] + tipo_fuente?: Database["public"]["Enums"]["tipo_fuente_bibliografia"] + } + Update: { + actualizado_en?: string + asignatura_id?: string + biblioteca_item_id?: string | null + cita?: string + creado_en?: string + creado_por?: string | null + id?: string + tipo?: Database["public"]["Enums"]["tipo_bibliografia"] + tipo_fuente?: Database["public"]["Enums"]["tipo_fuente_bibliografia"] + } + Relationships: [ + { + foreignKeyName: "bibliografia_asignatura_asignatura_id_fkey" + columns: ["asignatura_id"] + isOneToOne: false + referencedRelation: "asignaturas" + referencedColumns: ["id"] + }, + { + foreignKeyName: "bibliografia_asignatura_creado_por_fkey" + columns: ["creado_por"] + isOneToOne: false + referencedRelation: "usuarios_app" + referencedColumns: ["id"] + }, + ] + } + cambios_asignatura: { + Row: { + asignatura_id: string + cambiado_en: string + cambiado_por: string | null + campo: string | null + fuente: Database["public"]["Enums"]["fuente_cambio"] | null + id: string + interaccion_ia_id: string | null + tipo: Database["public"]["Enums"]["tipo_cambio"] + valor_anterior: Json | null + valor_nuevo: Json | null + } + Insert: { + asignatura_id: string + cambiado_en?: string + cambiado_por?: string | null + campo?: string | null + fuente?: Database["public"]["Enums"]["fuente_cambio"] | null + id?: string + interaccion_ia_id?: string | null + tipo: Database["public"]["Enums"]["tipo_cambio"] + valor_anterior?: Json | null + valor_nuevo?: Json | null + } + Update: { + asignatura_id?: string + cambiado_en?: string + cambiado_por?: string | null + campo?: string | null + fuente?: Database["public"]["Enums"]["fuente_cambio"] | null + id?: string + interaccion_ia_id?: string | null + tipo?: Database["public"]["Enums"]["tipo_cambio"] + valor_anterior?: Json | null + valor_nuevo?: Json | null + } + Relationships: [ + { + foreignKeyName: "cambios_asignatura_asignatura_id_fkey" + columns: ["asignatura_id"] + isOneToOne: false + referencedRelation: "asignaturas" + referencedColumns: ["id"] + }, + { + foreignKeyName: "cambios_asignatura_cambiado_por_fkey" + columns: ["cambiado_por"] + isOneToOne: false + referencedRelation: "usuarios_app" + referencedColumns: ["id"] + }, + ] + } + cambios_plan: { + Row: { + cambiado_en: string + cambiado_por: string | null + campo: string | null + id: string + plan_estudio_id: string + response_id: string | null + tipo: Database["public"]["Enums"]["tipo_cambio"] + valor_anterior: Json | null + valor_nuevo: Json | null + } + Insert: { + cambiado_en?: string + cambiado_por?: string | null + campo?: string | null + id?: string + plan_estudio_id: string + response_id?: string | null + tipo: Database["public"]["Enums"]["tipo_cambio"] + valor_anterior?: Json | null + valor_nuevo?: Json | null + } + Update: { + cambiado_en?: string + cambiado_por?: string | null + campo?: string | null + id?: string + plan_estudio_id?: string + response_id?: string | null + tipo?: Database["public"]["Enums"]["tipo_cambio"] + valor_anterior?: Json | null + valor_nuevo?: Json | null + } + Relationships: [ + { + foreignKeyName: "cambios_plan_cambiado_por_fkey" + columns: ["cambiado_por"] + isOneToOne: false + referencedRelation: "usuarios_app" + referencedColumns: ["id"] + }, + ] + } + carreras: { + Row: { + activa: boolean + actualizado_en: string + clave_sep: string | null + creado_en: string + facultad_id: string + id: string + nombre: string + nombre_corto: string | null + } + Insert: { + activa?: boolean + actualizado_en?: string + clave_sep?: string | null + creado_en?: string + facultad_id: string + id?: string + nombre: string + nombre_corto?: string | null + } + Update: { + activa?: boolean + actualizado_en?: string + clave_sep?: string | null + creado_en?: string + facultad_id?: string + id?: string + nombre?: string + nombre_corto?: string | null + } + Relationships: [ + { + foreignKeyName: "carreras_facultad_id_fkey" + columns: ["facultad_id"] + isOneToOne: false + referencedRelation: "facultades" + referencedColumns: ["id"] + }, + ] + } + conversaciones_asignatura: { + Row: { + archivado_en: string | null + archivado_por: string | null + asignatura_id: string + conversacion_json: Json + creado_en: string + creado_por: string | null + estado: Database["public"]["Enums"]["estado_conversacion"] + id: string + intento_archivado: number + openai_conversation_id: string + } + Insert: { + archivado_en?: string | null + archivado_por?: string | null + asignatura_id: string + conversacion_json?: Json + creado_en?: string + creado_por?: string | null + estado?: Database["public"]["Enums"]["estado_conversacion"] + id?: string + intento_archivado?: number + openai_conversation_id: string + } + Update: { + archivado_en?: string | null + archivado_por?: string | null + asignatura_id?: string + conversacion_json?: Json + creado_en?: string + creado_por?: string | null + estado?: Database["public"]["Enums"]["estado_conversacion"] + id?: string + intento_archivado?: number + openai_conversation_id?: string + } + Relationships: [ + { + foreignKeyName: "conversaciones_asignatura_archivado_por_fkey" + columns: ["archivado_por"] + isOneToOne: false + referencedRelation: "usuarios_app" + referencedColumns: ["id"] + }, + { + foreignKeyName: "conversaciones_asignatura_asignatura_id_fkey" + columns: ["asignatura_id"] + isOneToOne: false + referencedRelation: "asignaturas" + referencedColumns: ["id"] + }, + { + foreignKeyName: "conversaciones_asignatura_creado_por_fkey" + columns: ["creado_por"] + isOneToOne: false + referencedRelation: "usuarios_app" + referencedColumns: ["id"] + }, + ] + } + conversaciones_plan: { + Row: { + archivado_en: string | null + archivado_por: string | null + conversacion_json: Json + creado_en: string + creado_por: string | null + estado: Database["public"]["Enums"]["estado_conversacion"] + id: string + intento_archivado: number + openai_conversation_id: string + plan_estudio_id: string + } + Insert: { + archivado_en?: string | null + archivado_por?: string | null + conversacion_json?: Json + creado_en?: string + creado_por?: string | null + estado?: Database["public"]["Enums"]["estado_conversacion"] + id?: string + intento_archivado?: number + openai_conversation_id: string + plan_estudio_id: string + } + Update: { + archivado_en?: string | null + archivado_por?: string | null + conversacion_json?: Json + creado_en?: string + creado_por?: string | null + estado?: Database["public"]["Enums"]["estado_conversacion"] + id?: string + intento_archivado?: number + openai_conversation_id?: string + plan_estudio_id?: string + } + Relationships: [ + { + foreignKeyName: "conversaciones_plan_archivado_por_fkey" + columns: ["archivado_por"] + isOneToOne: false + referencedRelation: "usuarios_app" + referencedColumns: ["id"] + }, + { + foreignKeyName: "conversaciones_plan_creado_por_fkey" + columns: ["creado_por"] + isOneToOne: false + referencedRelation: "usuarios_app" + referencedColumns: ["id"] + }, + { + foreignKeyName: "conversaciones_plan_plan_estudio_id_fkey" + columns: ["plan_estudio_id"] + isOneToOne: false + referencedRelation: "planes_estudio" + referencedColumns: ["id"] + }, + { + foreignKeyName: "conversaciones_plan_plan_estudio_id_fkey" + columns: ["plan_estudio_id"] + isOneToOne: false + referencedRelation: "plantilla_plan" + referencedColumns: ["plan_estudio_id"] + }, + ] + } + estados_plan: { + Row: { + clave: string + es_final: boolean + etiqueta: string + id: string + orden: number + } + Insert: { + clave: string + es_final?: boolean + etiqueta: string + id?: string + orden?: number + } + Update: { + clave?: string + es_final?: boolean + etiqueta?: string + id?: string + orden?: number + } + Relationships: [] + } + estructuras_asignatura: { + Row: { + actualizado_en: string + creado_en: string + definicion: Json + id: string + nombre: string + version: string | null + } + Insert: { + actualizado_en?: string + creado_en?: string + definicion?: Json + id?: string + nombre: string + version?: string | null + } + Update: { + actualizado_en?: string + creado_en?: string + definicion?: Json + id?: string + nombre?: string + version?: string | null + } + Relationships: [] + } + estructuras_plan: { + Row: { + actualizado_en: string + creado_en: string + definicion: Json + id: string + nombre: string + template_id: string | null + tipo: Database["public"]["Enums"]["tipo_estructura_plan"] + } + Insert: { + actualizado_en?: string + creado_en?: string + definicion?: Json + id?: string + nombre: string + template_id?: string | null + tipo: Database["public"]["Enums"]["tipo_estructura_plan"] + } + Update: { + actualizado_en?: string + creado_en?: string + definicion?: Json + id?: string + nombre?: string + template_id?: string | null + tipo?: Database["public"]["Enums"]["tipo_estructura_plan"] + } + Relationships: [] + } + facultades: { + Row: { + actualizado_en: string + color: string | null + creado_en: string + icono: string | null + id: string + nombre: string + nombre_corto: string | null + } + Insert: { + actualizado_en?: string + color?: string | null + creado_en?: string + icono?: string | null + id?: string + nombre: string + nombre_corto?: string | null + } + Update: { + actualizado_en?: string + color?: string | null + creado_en?: string + icono?: string | null + id?: string + nombre?: string + nombre_corto?: string | null + } + Relationships: [] + } + interacciones_ia: { + Row: { + aceptada: boolean + asignatura_id: string | null + conversacion_id: string | null + creado_en: string + id: string + ids_archivos: Json + ids_vector_store: Json + modelo: string | null + plan_estudio_id: string | null + prompt: Json + respuesta: Json + rutas_storage: Json + temperatura: number | null + tipo: Database["public"]["Enums"]["tipo_interaccion_ia"] + usuario_id: string | null + } + Insert: { + aceptada?: boolean + asignatura_id?: string | null + conversacion_id?: string | null + creado_en?: string + id?: string + ids_archivos?: Json + ids_vector_store?: Json + modelo?: string | null + plan_estudio_id?: string | null + prompt?: Json + respuesta?: Json + rutas_storage?: Json + temperatura?: number | null + tipo: Database["public"]["Enums"]["tipo_interaccion_ia"] + usuario_id?: string | null + } + Update: { + aceptada?: boolean + asignatura_id?: string | null + conversacion_id?: string | null + creado_en?: string + id?: string + ids_archivos?: Json + ids_vector_store?: Json + modelo?: string | null + plan_estudio_id?: string | null + prompt?: Json + respuesta?: Json + rutas_storage?: Json + temperatura?: number | null + tipo?: Database["public"]["Enums"]["tipo_interaccion_ia"] + usuario_id?: string | null + } + Relationships: [ + { + foreignKeyName: "interacciones_ia_asignatura_id_fkey" + columns: ["asignatura_id"] + isOneToOne: false + referencedRelation: "asignaturas" + referencedColumns: ["id"] + }, + { + foreignKeyName: "interacciones_ia_plan_estudio_id_fkey" + columns: ["plan_estudio_id"] + isOneToOne: false + referencedRelation: "planes_estudio" + referencedColumns: ["id"] + }, + { + foreignKeyName: "interacciones_ia_plan_estudio_id_fkey" + columns: ["plan_estudio_id"] + isOneToOne: false + referencedRelation: "plantilla_plan" + referencedColumns: ["plan_estudio_id"] + }, + { + foreignKeyName: "interacciones_ia_usuario_id_fkey" + columns: ["usuario_id"] + isOneToOne: false + referencedRelation: "usuarios_app" + referencedColumns: ["id"] + }, + ] + } + lineas_plan: { + Row: { + actualizado_en: string + area: string | null + creado_en: string + id: string + nombre: string + orden: number + plan_estudio_id: string + } + Insert: { + actualizado_en?: string + area?: string | null + creado_en?: string + id?: string + nombre: string + orden?: number + plan_estudio_id: string + } + Update: { + actualizado_en?: string + area?: string | null + creado_en?: string + id?: string + nombre?: string + orden?: number + plan_estudio_id?: string + } + Relationships: [ + { + foreignKeyName: "lineas_plan_plan_estudio_id_fkey" + columns: ["plan_estudio_id"] + isOneToOne: false + referencedRelation: "planes_estudio" + referencedColumns: ["id"] + }, + { + foreignKeyName: "lineas_plan_plan_estudio_id_fkey" + columns: ["plan_estudio_id"] + isOneToOne: false + referencedRelation: "plantilla_plan" + referencedColumns: ["plan_estudio_id"] + }, + ] + } + notificaciones: { + Row: { + creado_en: string + id: string + leida: boolean + leida_en: string | null + payload: Json + tipo: Database["public"]["Enums"]["tipo_notificacion"] + usuario_id: string + } + Insert: { + creado_en?: string + id?: string + leida?: boolean + leida_en?: string | null + payload?: Json + tipo: Database["public"]["Enums"]["tipo_notificacion"] + usuario_id: string + } + Update: { + creado_en?: string + id?: string + leida?: boolean + leida_en?: string | null + payload?: Json + tipo?: Database["public"]["Enums"]["tipo_notificacion"] + usuario_id?: string + } + Relationships: [ + { + foreignKeyName: "notificaciones_usuario_id_fkey" + columns: ["usuario_id"] + isOneToOne: false + referencedRelation: "usuarios_app" + referencedColumns: ["id"] + }, + ] + } + planes_estudio: { + Row: { + activo: boolean + actualizado_en: string + actualizado_por: string | null + carrera_id: string + creado_en: string + creado_por: string | null + datos: Json + estado_actual_id: string | null + estructura_id: string + id: string + meta_origen: Json + nivel: Database["public"]["Enums"]["nivel_plan_estudio"] + nombre: string + nombre_search: string | null + numero_ciclos: number + plan_hash: string | null + tipo_ciclo: Database["public"]["Enums"]["tipo_ciclo"] + tipo_origen: Database["public"]["Enums"]["tipo_origen"] | null + } + Insert: { + activo?: boolean + actualizado_en?: string + actualizado_por?: string | null + carrera_id: string + creado_en?: string + creado_por?: string | null + datos?: Json + estado_actual_id?: string | null + estructura_id: string + id?: string + meta_origen?: Json + nivel: Database["public"]["Enums"]["nivel_plan_estudio"] + nombre: string + nombre_search?: string | null + numero_ciclos: number + plan_hash?: string | null + tipo_ciclo: Database["public"]["Enums"]["tipo_ciclo"] + tipo_origen?: Database["public"]["Enums"]["tipo_origen"] | null + } + Update: { + activo?: boolean + actualizado_en?: string + actualizado_por?: string | null + carrera_id?: string + creado_en?: string + creado_por?: string | null + datos?: Json + estado_actual_id?: string | null + estructura_id?: string + id?: string + meta_origen?: Json + nivel?: Database["public"]["Enums"]["nivel_plan_estudio"] + nombre?: string + nombre_search?: string | null + numero_ciclos?: number + plan_hash?: string | null + tipo_ciclo?: Database["public"]["Enums"]["tipo_ciclo"] + tipo_origen?: Database["public"]["Enums"]["tipo_origen"] | null + } + Relationships: [ + { + foreignKeyName: "planes_estudio_actualizado_por_fkey" + columns: ["actualizado_por"] + isOneToOne: false + referencedRelation: "usuarios_app" + referencedColumns: ["id"] + }, + { + foreignKeyName: "planes_estudio_carrera_id_fkey" + columns: ["carrera_id"] + isOneToOne: false + referencedRelation: "carreras" + referencedColumns: ["id"] + }, + { + foreignKeyName: "planes_estudio_creado_por_fkey" + columns: ["creado_por"] + isOneToOne: false + referencedRelation: "usuarios_app" + referencedColumns: ["id"] + }, + { + foreignKeyName: "planes_estudio_estado_actual_id_fkey" + columns: ["estado_actual_id"] + isOneToOne: false + referencedRelation: "estados_plan" + referencedColumns: ["id"] + }, + { + foreignKeyName: "planes_estudio_estructura_id_fkey" + columns: ["estructura_id"] + isOneToOne: false + referencedRelation: "estructuras_plan" + referencedColumns: ["id"] + }, + { + foreignKeyName: "planes_estudio_estructura_id_fkey" + columns: ["estructura_id"] + isOneToOne: false + referencedRelation: "plantilla_plan" + referencedColumns: ["estructura_id"] + }, + ] + } + responsables_asignatura: { + Row: { + asignatura_id: string + creado_en: string + id: string + rol: Database["public"]["Enums"]["rol_responsable_asignatura"] + usuario_id: string + } + Insert: { + asignatura_id: string + creado_en?: string + id?: string + rol?: Database["public"]["Enums"]["rol_responsable_asignatura"] + usuario_id: string + } + Update: { + asignatura_id?: string + creado_en?: string + id?: string + rol?: Database["public"]["Enums"]["rol_responsable_asignatura"] + usuario_id?: string + } + Relationships: [ + { + foreignKeyName: "responsables_asignatura_asignatura_id_fkey" + columns: ["asignatura_id"] + isOneToOne: false + referencedRelation: "asignaturas" + referencedColumns: ["id"] + }, + { + foreignKeyName: "responsables_asignatura_usuario_id_fkey" + columns: ["usuario_id"] + isOneToOne: false + referencedRelation: "usuarios_app" + referencedColumns: ["id"] + }, + ] + } + roles: { + Row: { + clave: string + descripcion: string | null + id: string + nombre: string + } + Insert: { + clave: string + descripcion?: string | null + id?: string + nombre: string + } + Update: { + clave?: string + descripcion?: string | null + id?: string + nombre?: string + } + Relationships: [] + } + tareas_revision: { + Row: { + asignado_a: string + completado_en: string | null + creado_en: string + estado_id: string | null + estatus: Database["public"]["Enums"]["estado_tarea_revision"] + fecha_limite: string | null + id: string + plan_estudio_id: string + rol_id: string | null + } + Insert: { + asignado_a: string + completado_en?: string | null + creado_en?: string + estado_id?: string | null + estatus?: Database["public"]["Enums"]["estado_tarea_revision"] + fecha_limite?: string | null + id?: string + plan_estudio_id: string + rol_id?: string | null + } + Update: { + asignado_a?: string + completado_en?: string | null + creado_en?: string + estado_id?: string | null + estatus?: Database["public"]["Enums"]["estado_tarea_revision"] + fecha_limite?: string | null + id?: string + plan_estudio_id?: string + rol_id?: string | null + } + Relationships: [ + { + foreignKeyName: "tareas_revision_asignado_a_fkey" + columns: ["asignado_a"] + isOneToOne: false + referencedRelation: "usuarios_app" + referencedColumns: ["id"] + }, + { + foreignKeyName: "tareas_revision_estado_id_fkey" + columns: ["estado_id"] + isOneToOne: false + referencedRelation: "estados_plan" + referencedColumns: ["id"] + }, + { + foreignKeyName: "tareas_revision_plan_estudio_id_fkey" + columns: ["plan_estudio_id"] + isOneToOne: false + referencedRelation: "planes_estudio" + referencedColumns: ["id"] + }, + { + foreignKeyName: "tareas_revision_plan_estudio_id_fkey" + columns: ["plan_estudio_id"] + isOneToOne: false + referencedRelation: "plantilla_plan" + referencedColumns: ["plan_estudio_id"] + }, + { + foreignKeyName: "tareas_revision_rol_id_fkey" + columns: ["rol_id"] + isOneToOne: false + referencedRelation: "roles" + referencedColumns: ["id"] + }, + ] + } + transiciones_estado_plan: { + Row: { + creado_en: string + desde_estado_id: string + hacia_estado_id: string + id: string + rol_permitido_id: string + } + Insert: { + creado_en?: string + desde_estado_id: string + hacia_estado_id: string + id?: string + rol_permitido_id: string + } + Update: { + creado_en?: string + desde_estado_id?: string + hacia_estado_id?: string + id?: string + rol_permitido_id?: string + } + Relationships: [ + { + foreignKeyName: "transiciones_estado_plan_desde_estado_id_fkey" + columns: ["desde_estado_id"] + isOneToOne: false + referencedRelation: "estados_plan" + referencedColumns: ["id"] + }, + { + foreignKeyName: "transiciones_estado_plan_hacia_estado_id_fkey" + columns: ["hacia_estado_id"] + isOneToOne: false + referencedRelation: "estados_plan" + referencedColumns: ["id"] + }, + { + foreignKeyName: "transiciones_estado_plan_rol_permitido_id_fkey" + columns: ["rol_permitido_id"] + isOneToOne: false + referencedRelation: "roles" + referencedColumns: ["id"] + }, + ] + } + usuarios_app: { + Row: { + actualizado_en: string + creado_en: string + email: string | null + externo: boolean + id: string + nombre_completo: string | null + } + Insert: { + actualizado_en?: string + creado_en?: string + email?: string | null + externo?: boolean + id: string + nombre_completo?: string | null + } + Update: { + actualizado_en?: string + creado_en?: string + email?: string | null + externo?: boolean + id?: string + nombre_completo?: string | null + } + Relationships: [] + } + usuarios_roles: { + Row: { + carrera_id: string | null + creado_en: string + facultad_id: string | null + id: string + rol_id: string + usuario_id: string + } + Insert: { + carrera_id?: string | null + creado_en?: string + facultad_id?: string | null + id?: string + rol_id: string + usuario_id: string + } + Update: { + carrera_id?: string | null + creado_en?: string + facultad_id?: string | null + id?: string + rol_id?: string + usuario_id?: string + } + Relationships: [ + { + foreignKeyName: "usuarios_roles_carrera_id_fkey" + columns: ["carrera_id"] + isOneToOne: false + referencedRelation: "carreras" + referencedColumns: ["id"] + }, + { + foreignKeyName: "usuarios_roles_facultad_id_fkey" + columns: ["facultad_id"] + isOneToOne: false + referencedRelation: "facultades" + referencedColumns: ["id"] + }, + { + foreignKeyName: "usuarios_roles_rol_id_fkey" + columns: ["rol_id"] + isOneToOne: false + referencedRelation: "roles" + referencedColumns: ["id"] + }, + { + foreignKeyName: "usuarios_roles_usuario_id_fkey" + columns: ["usuario_id"] + isOneToOne: false + referencedRelation: "usuarios_app" + referencedColumns: ["id"] + }, + ] + } + vector_stores: { + Row: { + creado_en: string + creado_por: string | null + id: string + nombre: string + openai_vector_id: string | null + } + Insert: { + creado_en?: string + creado_por?: string | null + id?: string + nombre: string + openai_vector_id?: string | null + } + Update: { + creado_en?: string + creado_por?: string | null + id?: string + nombre?: string + openai_vector_id?: string | null + } + Relationships: [ + { + foreignKeyName: "vector_stores_creado_por_fkey" + columns: ["creado_por"] + isOneToOne: false + referencedRelation: "usuarios_app" + referencedColumns: ["id"] + }, + ] + } + } + Views: { + plantilla_plan: { + Row: { + estructura_id: string | null + plan_estudio_id: string | null + template_id: string | null + } + Relationships: [] + } + } + Functions: { + unaccent: { Args: { "": string }; Returns: string } + unaccent_immutable: { Args: { "": string }; Returns: string } + } + Enums: { + estado_asignatura: "borrador" | "revisada" | "aprobada" | "generando" + estado_conversacion: "ACTIVA" | "ARCHIVANDO" | "ARCHIVADA" | "ERROR" + estado_tarea_revision: "PENDIENTE" | "COMPLETADA" | "OMITIDA" + fuente_cambio: "HUMANO" | "IA" + nivel_plan_estudio: + | "Licenciatura" + | "Maestría" + | "Doctorado" + | "Especialidad" + | "Diplomado" + | "Otro" + puesto_tipo: + | "vicerrector" + | "director_facultad" + | "secretario_academico" + | "jefe_carrera" + | "profesor" + | "lci" + rol_responsable_asignatura: "PROFESOR_RESPONSABLE" | "COAUTOR" | "REVISOR" + tipo_asignatura: "OBLIGATORIA" | "OPTATIVA" | "TRONCAL" | "OTRA" + tipo_bibliografia: "BASICA" | "COMPLEMENTARIA" + tipo_cambio: + | "ACTUALIZACION_CAMPO" + | "ACTUALIZACION_MAPA" + | "TRANSICION_ESTADO" + | "OTRO" + | "CREACION" + | "ACTUALIZACION" + tipo_ciclo: "Semestre" | "Cuatrimestre" | "Trimestre" | "Otro" + tipo_estructura_plan: "CURRICULAR" | "NO_CURRICULAR" + tipo_fuente_bibliografia: "MANUAL" | "BIBLIOTECA" + tipo_interaccion_ia: "GENERAR" | "MEJORAR_SECCION" | "CHAT" | "OTRA" + tipo_notificacion: + | "PLAN_ASIGNADO" + | "ESTADO_CAMBIADO" + | "TAREA_ASIGNADA" + | "COMENTARIO" + | "OTRA" + tipo_origen: + | "MANUAL" + | "IA" + | "CLONADO_INTERNO" + | "CLONADO_TRADICIONAL" + | "OTRO" + } + CompositeTypes: { + [_ in never]: never + } + } +} -type DatabaseWithoutInternals = Omit; +type DatabaseWithoutInternals = Omit -type DefaultSchema = - DatabaseWithoutInternals[Extract]; +type DefaultSchema = DatabaseWithoutInternals[Extract] export type Tables< - DefaultSchemaTableNameOrOptions extends - | keyof (DefaultSchema["Tables"] & DefaultSchema["Views"]) - | { schema: keyof DatabaseWithoutInternals }, - TableName extends DefaultSchemaTableNameOrOptions extends { - schema: keyof DatabaseWithoutInternals; - } ? keyof ( - & DatabaseWithoutInternals[ - DefaultSchemaTableNameOrOptions["schema"] - ]["Tables"] - & DatabaseWithoutInternals[ - DefaultSchemaTableNameOrOptions["schema"] - ]["Views"] - ) - : never = never, + DefaultSchemaTableNameOrOptions extends + | keyof (DefaultSchema["Tables"] & DefaultSchema["Views"]) + | { schema: keyof DatabaseWithoutInternals }, + TableName extends DefaultSchemaTableNameOrOptions extends { + schema: keyof DatabaseWithoutInternals + } + ? keyof (DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] & + DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Views"]) + : never = never, > = DefaultSchemaTableNameOrOptions extends { - schema: keyof DatabaseWithoutInternals; -} ? ( - & DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]][ - "Tables" - ] - & DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]][ - "Views" - ] - )[TableName] extends { - Row: infer R; - } ? R + schema: keyof DatabaseWithoutInternals +} + ? (DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] & + DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Views"])[TableName] extends { + Row: infer R + } + ? R + : never + : DefaultSchemaTableNameOrOptions extends keyof (DefaultSchema["Tables"] & + DefaultSchema["Views"]) + ? (DefaultSchema["Tables"] & + DefaultSchema["Views"])[DefaultSchemaTableNameOrOptions] extends { + Row: infer R + } + ? R + : never : never - : DefaultSchemaTableNameOrOptions extends keyof ( - & DefaultSchema["Tables"] - & DefaultSchema["Views"] - ) ? ( - & DefaultSchema["Tables"] - & DefaultSchema["Views"] - )[DefaultSchemaTableNameOrOptions] extends { - Row: infer R; - } ? R - : never - : never; export type TablesInsert< - DefaultSchemaTableNameOrOptions extends - | keyof DefaultSchema["Tables"] - | { schema: keyof DatabaseWithoutInternals }, - TableName extends DefaultSchemaTableNameOrOptions extends { - schema: keyof DatabaseWithoutInternals; - } ? keyof DatabaseWithoutInternals[ - DefaultSchemaTableNameOrOptions["schema"] - ]["Tables"] - : never = never, + DefaultSchemaTableNameOrOptions extends + | keyof DefaultSchema["Tables"] + | { schema: keyof DatabaseWithoutInternals }, + TableName extends DefaultSchemaTableNameOrOptions extends { + schema: keyof DatabaseWithoutInternals + } + ? keyof DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] + : never = never, > = DefaultSchemaTableNameOrOptions extends { - schema: keyof DatabaseWithoutInternals; -} ? DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]][ - "Tables" - ][TableName] extends { - Insert: infer I; - } ? I + schema: keyof DatabaseWithoutInternals +} + ? DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"][TableName] extends { + Insert: infer I + } + ? I + : never + : DefaultSchemaTableNameOrOptions extends keyof DefaultSchema["Tables"] + ? DefaultSchema["Tables"][DefaultSchemaTableNameOrOptions] extends { + Insert: infer I + } + ? I + : never : never - : DefaultSchemaTableNameOrOptions extends keyof DefaultSchema["Tables"] - ? DefaultSchema["Tables"][DefaultSchemaTableNameOrOptions] extends { - Insert: infer I; - } ? I - : never - : never; export type TablesUpdate< - DefaultSchemaTableNameOrOptions extends - | keyof DefaultSchema["Tables"] - | { schema: keyof DatabaseWithoutInternals }, - TableName extends DefaultSchemaTableNameOrOptions extends { - schema: keyof DatabaseWithoutInternals; - } ? keyof DatabaseWithoutInternals[ - DefaultSchemaTableNameOrOptions["schema"] - ]["Tables"] - : never = never, + DefaultSchemaTableNameOrOptions extends + | keyof DefaultSchema["Tables"] + | { schema: keyof DatabaseWithoutInternals }, + TableName extends DefaultSchemaTableNameOrOptions extends { + schema: keyof DatabaseWithoutInternals + } + ? keyof DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] + : never = never, > = DefaultSchemaTableNameOrOptions extends { - schema: keyof DatabaseWithoutInternals; -} ? DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]][ - "Tables" - ][TableName] extends { - Update: infer U; - } ? U + schema: keyof DatabaseWithoutInternals +} + ? DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"][TableName] extends { + Update: infer U + } + ? U + : never + : DefaultSchemaTableNameOrOptions extends keyof DefaultSchema["Tables"] + ? DefaultSchema["Tables"][DefaultSchemaTableNameOrOptions] extends { + Update: infer U + } + ? U + : never : never - : DefaultSchemaTableNameOrOptions extends keyof DefaultSchema["Tables"] - ? DefaultSchema["Tables"][DefaultSchemaTableNameOrOptions] extends { - Update: infer U; - } ? U - : never - : never; export type Enums< - DefaultSchemaEnumNameOrOptions extends - | keyof DefaultSchema["Enums"] - | { schema: keyof DatabaseWithoutInternals }, - EnumName extends DefaultSchemaEnumNameOrOptions extends { - schema: keyof DatabaseWithoutInternals; - } ? keyof DatabaseWithoutInternals[ - DefaultSchemaEnumNameOrOptions["schema"] - ]["Enums"] - : never = never, + DefaultSchemaEnumNameOrOptions extends + | keyof DefaultSchema["Enums"] + | { schema: keyof DatabaseWithoutInternals }, + EnumName extends DefaultSchemaEnumNameOrOptions extends { + schema: keyof DatabaseWithoutInternals + } + ? keyof DatabaseWithoutInternals[DefaultSchemaEnumNameOrOptions["schema"]]["Enums"] + : never = never, > = DefaultSchemaEnumNameOrOptions extends { - schema: keyof DatabaseWithoutInternals; -} ? DatabaseWithoutInternals[DefaultSchemaEnumNameOrOptions["schema"]][ - "Enums" - ][EnumName] - : DefaultSchemaEnumNameOrOptions extends keyof DefaultSchema["Enums"] - ? DefaultSchema["Enums"][DefaultSchemaEnumNameOrOptions] - : never; + schema: keyof DatabaseWithoutInternals +} + ? DatabaseWithoutInternals[DefaultSchemaEnumNameOrOptions["schema"]]["Enums"][EnumName] + : DefaultSchemaEnumNameOrOptions extends keyof DefaultSchema["Enums"] + ? DefaultSchema["Enums"][DefaultSchemaEnumNameOrOptions] + : never export type CompositeTypes< - PublicCompositeTypeNameOrOptions extends - | keyof DefaultSchema["CompositeTypes"] - | { schema: keyof DatabaseWithoutInternals }, - CompositeTypeName extends PublicCompositeTypeNameOrOptions extends { - schema: keyof DatabaseWithoutInternals; - } ? keyof DatabaseWithoutInternals[ - PublicCompositeTypeNameOrOptions["schema"] - ]["CompositeTypes"] - : never = never, + PublicCompositeTypeNameOrOptions extends + | keyof DefaultSchema["CompositeTypes"] + | { schema: keyof DatabaseWithoutInternals }, + CompositeTypeName extends PublicCompositeTypeNameOrOptions extends { + schema: keyof DatabaseWithoutInternals + } + ? keyof DatabaseWithoutInternals[PublicCompositeTypeNameOrOptions["schema"]]["CompositeTypes"] + : never = never, > = PublicCompositeTypeNameOrOptions extends { - schema: keyof DatabaseWithoutInternals; -} ? DatabaseWithoutInternals[PublicCompositeTypeNameOrOptions["schema"]][ - "CompositeTypes" - ][CompositeTypeName] - : PublicCompositeTypeNameOrOptions extends - keyof DefaultSchema["CompositeTypes"] - ? DefaultSchema["CompositeTypes"][PublicCompositeTypeNameOrOptions] - : never; + schema: keyof DatabaseWithoutInternals +} + ? DatabaseWithoutInternals[PublicCompositeTypeNameOrOptions["schema"]]["CompositeTypes"][CompositeTypeName] + : PublicCompositeTypeNameOrOptions extends keyof DefaultSchema["CompositeTypes"] + ? DefaultSchema["CompositeTypes"][PublicCompositeTypeNameOrOptions] + : never export const Constants = { - graphql_public: { - Enums: {}, + graphql_public: { + Enums: {}, + }, + public: { + Enums: { + estado_asignatura: ["borrador", "revisada", "aprobada", "generando"], + estado_conversacion: ["ACTIVA", "ARCHIVANDO", "ARCHIVADA", "ERROR"], + estado_tarea_revision: ["PENDIENTE", "COMPLETADA", "OMITIDA"], + fuente_cambio: ["HUMANO", "IA"], + nivel_plan_estudio: [ + "Licenciatura", + "Maestría", + "Doctorado", + "Especialidad", + "Diplomado", + "Otro", + ], + puesto_tipo: [ + "vicerrector", + "director_facultad", + "secretario_academico", + "jefe_carrera", + "profesor", + "lci", + ], + rol_responsable_asignatura: [ + "PROFESOR_RESPONSABLE", + "COAUTOR", + "REVISOR", + ], + tipo_asignatura: ["OBLIGATORIA", "OPTATIVA", "TRONCAL", "OTRA"], + tipo_bibliografia: ["BASICA", "COMPLEMENTARIA"], + tipo_cambio: [ + "ACTUALIZACION_CAMPO", + "ACTUALIZACION_MAPA", + "TRANSICION_ESTADO", + "OTRO", + "CREACION", + "ACTUALIZACION", + ], + tipo_ciclo: ["Semestre", "Cuatrimestre", "Trimestre", "Otro"], + tipo_estructura_plan: ["CURRICULAR", "NO_CURRICULAR"], + tipo_fuente_bibliografia: ["MANUAL", "BIBLIOTECA"], + tipo_interaccion_ia: ["GENERAR", "MEJORAR_SECCION", "CHAT", "OTRA"], + tipo_notificacion: [ + "PLAN_ASIGNADO", + "ESTADO_CAMBIADO", + "TAREA_ASIGNADA", + "COMENTARIO", + "OTRA", + ], + tipo_origen: [ + "MANUAL", + "IA", + "CLONADO_INTERNO", + "CLONADO_TRADICIONAL", + "OTRO", + ], }, - public: { - Enums: { - estado_asignatura: [ - "borrador", - "revisada", - "aprobada", - "generando", - ], - estado_tarea_revision: ["PENDIENTE", "COMPLETADA", "OMITIDA"], - fuente_cambio: ["HUMANO", "IA"], - nivel_plan_estudio: [ - "Licenciatura", - "Maestría", - "Doctorado", - "Especialidad", - "Diplomado", - "Otro", - ], - puesto_tipo: [ - "vicerrector", - "director_facultad", - "secretario_academico", - "jefe_carrera", - "profesor", - "lci", - ], - rol_responsable_asignatura: [ - "PROFESOR_RESPONSABLE", - "COAUTOR", - "REVISOR", - ], - tipo_asignatura: ["OBLIGATORIA", "OPTATIVA", "TRONCAL", "OTRA"], - tipo_bibliografia: ["BASICA", "COMPLEMENTARIA"], - tipo_cambio: [ - "ACTUALIZACION_CAMPO", - "ACTUALIZACION_MAPA", - "TRANSICION_ESTADO", - "OTRO", - "CREACION", - "ACTUALIZACION", - ], - tipo_ciclo: ["Semestre", "Cuatrimestre", "Trimestre", "Otro"], - tipo_estructura_plan: ["CURRICULAR", "NO_CURRICULAR"], - tipo_fuente_bibliografia: ["MANUAL", "BIBLIOTECA"], - tipo_interaccion_ia: ["GENERAR", "MEJORAR_SECCION", "CHAT", "OTRA"], - tipo_notificacion: [ - "PLAN_ASIGNADO", - "ESTADO_CAMBIADO", - "TAREA_ASIGNADA", - "COMENTARIO", - "OTRA", - ], - tipo_origen: [ - "MANUAL", - "IA", - "CLONADO_INTERNO", - "CLONADO_TRADICIONAL", - "OTRO", - ], - }, - }, -} as const; + }, +} as const + diff --git a/supabase/functions/generate-subject-suggestions/index.ts b/supabase/functions/generate-subject-suggestions/index.ts index 7cfa1e9..027ab06 100644 --- a/supabase/functions/generate-subject-suggestions/index.ts +++ b/supabase/functions/generate-subject-suggestions/index.ts @@ -248,7 +248,7 @@ Deno.serve(async (req: Request): Promise => { `2) No repitas nombres que ya existan en el plan ni los nombres de las sugerencias conservadas.\n` + `3) Tampoco repitas nombres entre tus nuevas sugerencias.\n` + `4) Evita nombres demasiado similares (diferencias solo por mayúsculas, tildes, signos o palabras triviales).\n` + - `5) Cada sugerencia debe incluir un nombre y una descripción clara y útil.\n\n` + + `5) Cada sugerencia debe incluir un nombre y una descripción clara y útil (sin pasarse del límite de 200 caracteres).\n\n` + `Lista de nombres prohibidos (normalizados):\n` + forbiddenNames.map((n) => `- ${n}`).join("\n"); diff --git a/supabase/migrations/20260206172724_conversation_id_en_asignaturas.sql b/supabase/migrations/20260206172724_conversation_id_en_asignaturas.sql deleted file mode 100644 index 4ac4f50..0000000 --- a/supabase/migrations/20260206172724_conversation_id_en_asignaturas.sql +++ /dev/null @@ -1,16 +0,0 @@ -drop view if exists "public"."plantilla_plan"; - -alter table "public"."asignaturas" add column "conversation_id" text; - -CREATE UNIQUE INDEX asignaturas_conversation_id_key ON public.asignaturas USING btree (conversation_id); - -alter table "public"."asignaturas" add constraint "asignaturas_conversation_id_key" UNIQUE using index "asignaturas_conversation_id_key"; - -create or replace view "public"."plantilla_plan" as SELECT plan.id AS plan_estudio_id, - struct.id AS estructura_id, - struct.template_id - FROM (public.planes_estudio plan - JOIN public.estructuras_plan struct ON ((plan.estructura_id = struct.id))); - - - -- 2.52.0