issue/6-generacin-de-materias #16

Merged
Guillermo.Arrieta merged 5 commits from issue/6-generacin-de-materias into main 2026-02-05 22:07:23 +00:00
5 changed files with 26 additions and 37 deletions
Showing only changes of commit 00f0bddf1c - Show all commits
+6 -2
View File
@@ -1,8 +1,12 @@
{ {
"allowScripts": ["npm:deno@2.6.4", "npm:supabase-js@1.0.4", "npm:supabase@2.72.6"], "allowScripts": [
"npm:deno@2.6.4",
"npm:supabase-js@1.0.4",
"npm:supabase@2.72.6"
],
"imports": { "imports": {
"@openai/openai": "jsr:@openai/openai@^6.16.0", "@openai/openai": "jsr:@openai/openai@^6.16.0",
"@zod/zod": "jsr:@zod/zod@^4.3.5" "@zod/zod": "jsr:@zod/zod@^4.3.5",
"@david/dax": "jsr:@david/dax@^0.44.2" "@david/dax": "jsr:@david/dax@^0.44.2"
} }
} }
+1 -1
View File
@@ -1,5 +1,5 @@
export const corsHeaders = { export const corsHeaders = {
"Access-Control-Allow-Origin": "*", "Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers": "Access-Control-Allow-Headers":
"authorization, x-client-info, apikey, content-type", "authorization, x-client-info, apikey, content-type, x-supabase-client-platform",
}; };
+13 -4
View File
@@ -168,8 +168,15 @@ Deno.serve(async (req: Request): Promise<Response> => {
const userPrompt = const userPrompt =
`Genera un borrador completo del PLAN DE ESTUDIOS con base en lo siguiente: `Genera un borrador completo del PLAN DE ESTUDIOS con base en lo siguiente:
- Descripción del enfoque: ${payload.iaConfig.descripcionEnfoque} - Nombre de la institución: Universidad La Salle México
- Notas adicionales: ${payload.iaConfig.notasAdicionales ?? "Ninguna"}`; - Nombre del plan: ${payload.datosBasicos.nombrePlan}
- Nivel: ${payload.datosBasicos.nivel}
- Tipo de ciclo: ${payload.datosBasicos.tipoCiclo}
- Número de ciclos: ${payload.datosBasicos.numCiclos}
- Descripción del enfoque académico (sobre el contenido de la respuesta generada): ${payload.iaConfig.descripcionEnfoqueAcademico}
- Notas adicionales (sobre el formato de la respuesta generada): ${
payload.iaConfig.instruccionesAdicionalesIA ?? "Ninguna"
}`;
// Ensure the JSON schema is an object as required by OpenAI types // Ensure the JSON schema is an object as required by OpenAI types
const schemaDef: Record<string, unknown> = const schemaDef: Record<string, unknown> =
typeof estructuraPlan?.definicion === "object" && typeof estructuraPlan?.definicion === "object" &&
@@ -313,8 +320,10 @@ Deno.serve(async (req: Request): Promise<Response> => {
repositoriosIds: payload.iaConfig?.repositoriosIds ?? null, repositoriosIds: payload.iaConfig?.repositoriosIds ?? null,
}, },
iaConfig: { iaConfig: {
descripcionEnfoque: payload.iaConfig?.descripcionEnfoque ?? null, descripcionEnfoqueAcademico:
notasAdicionales: payload.iaConfig?.notasAdicionales ?? null, payload.iaConfig?.descripcionEnfoqueAcademico ?? null,
instruccionesAdicionalesIA:
payload.iaConfig?.instruccionesAdicionalesIA ?? null,
usarMCP: Boolean(payload.iaConfig?.usarMCP), usarMCP: Boolean(payload.iaConfig?.usarMCP),
}, },
} as unknown as Json, } as unknown as Json,
+2 -2
View File
@@ -9,8 +9,8 @@ export type AIGeneratePlanInput = {
estructuraPlanId: string; estructuraPlanId: string;
}; };
iaConfig: { iaConfig: {
descripcionEnfoque: string; descripcionEnfoqueAcademico: string;
notasAdicionales?: string; instruccionesAdicionalesIA?: string;
archivosReferencia?: Array<string>; archivosReferencia?: Array<string>;
repositoriosIds?: Array<string>; repositoriosIds?: Array<string>;
usarMCP?: boolean; usarMCP?: boolean;
@@ -1,7 +1,6 @@
// Setup type definitions for built-in Supabase Runtime APIs // Setup type definitions for built-in Supabase Runtime APIs
import "@supabase/functions-js/edge-runtime.d.ts"; import "@supabase/functions-js/edge-runtime.d.ts";
import { createClient } from "https://esm.sh/@supabase/supabase-js@2"; import { createClient } from "https://esm.sh/@supabase/supabase-js@2";
type WebhookPayload = { type WebhookPayload = {
type: "INSERT" | "UPDATE" | "DELETE"; type: "INSERT" | "UPDATE" | "DELETE";
table: string; table: string;
@@ -9,28 +8,23 @@ type WebhookPayload = {
record: Record<string, unknown> | null; record: Record<string, unknown> | null;
old_record: Record<string, unknown> | null; old_record: Record<string, unknown> | null;
}; };
const SUPABASE_URL = Deno.env.get("SUPABASE_URL") ?? ""; const SUPABASE_URL = Deno.env.get("SUPABASE_URL") ?? "";
const SUPABASE_SERVICE_ROLE_KEY = const SUPABASE_SERVICE_ROLE_KEY = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY") ??
Deno.env.get("SUPABASE_SERVICE_ROLE_KEY") ?? ""; "";
const OPENAI_API_KEY = Deno.env.get("OPENAI_API_KEY") ?? ""; const OPENAI_API_KEY = Deno.env.get("OPENAI_API_KEY") ?? "";
const OPENAI_BASE_URL = const OPENAI_BASE_URL = Deno.env.get("OPENAI_BASE_URL") ??
Deno.env.get("OPENAI_BASE_URL") ?? "https://api.openai.com/v1"; "https://api.openai.com/v1";
const ALLOWED_SCHEMA = "public"; const ALLOWED_SCHEMA = "public";
const ALLOWED_TABLES = new Set(["planes_estudio", "asignaturas"]); const ALLOWED_TABLES = new Set(["planes_estudio", "asignaturas"]);
const supabase = createClient(SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY, { const supabase = createClient(SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY, {
auth: { persistSession: false }, auth: { persistSession: false },
}); });
function jsonResponse(status: number, body: Record<string, unknown>) { function jsonResponse(status: number, body: Record<string, unknown>) {
return new Response(JSON.stringify(body), { return new Response(JSON.stringify(body), {
status, status,
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
}); });
} }
async function createConversationId(metadata: Record<string, string>) { async function createConversationId(metadata: Record<string, string>) {
const response = await fetch(`${OPENAI_BASE_URL}/conversations`, { const response = await fetch(`${OPENAI_BASE_URL}/conversations`, {
method: "POST", method: "POST",
@@ -40,61 +34,47 @@ async function createConversationId(metadata: Record<string, string>) {
}, },
body: JSON.stringify({ metadata }), body: JSON.stringify({ metadata }),
}); });
if (!response.ok) { if (!response.ok) {
const errorText = await response.text(); const errorText = await response.text();
throw new Error(`OpenAI error: ${response.status} ${errorText}`); throw new Error(`OpenAI error: ${response.status} ${errorText}`);
} }
const data = await response.json(); const data = await response.json();
const conversationId = data?.id as string | undefined; const conversationId = data?.id as string | undefined;
if (!conversationId) { if (!conversationId) {
throw new Error("OpenAI response missing conversation id"); throw new Error("OpenAI response missing conversation id");
} }
return conversationId; return conversationId;
} }
Deno.serve(async (req) => { Deno.serve(async (req) => {
if (req.method !== "POST") { if (req.method !== "POST") {
return jsonResponse(405, { error: "Method not allowed" }); return jsonResponse(405, { error: "Method not allowed" });
} }
if (!SUPABASE_URL || !SUPABASE_SERVICE_ROLE_KEY) { if (!SUPABASE_URL || !SUPABASE_SERVICE_ROLE_KEY) {
return jsonResponse(500, { error: "Supabase env vars missing" }); return jsonResponse(500, { error: "Supabase env vars missing" });
} }
if (!OPENAI_API_KEY) { if (!OPENAI_API_KEY) {
return jsonResponse(500, { error: "OPENAI_API_KEY missing" }); return jsonResponse(500, { error: "OPENAI_API_KEY missing" });
} }
let payload: WebhookPayload; let payload: WebhookPayload;
try { try {
payload = await req.json(); payload = await req.json();
} catch { } catch {
return jsonResponse(400, { error: "Invalid JSON body" }); return jsonResponse(400, { error: "Invalid JSON body" });
} }
const { type, table, schema, record } = payload; const { type, table, schema, record } = payload;
if (schema !== ALLOWED_SCHEMA || !ALLOWED_TABLES.has(table)) { if (schema !== ALLOWED_SCHEMA || !ALLOWED_TABLES.has(table)) {
return jsonResponse(400, { error: "Table not allowed" }); return jsonResponse(400, { error: "Table not allowed" });
} }
if (type !== "INSERT") { if (type !== "INSERT") {
return jsonResponse(200, { status: "ignored", reason: "event_type" }); return jsonResponse(200, { status: "ignored", reason: "event_type" });
} }
if (!record || typeof record !== "object") { if (!record || typeof record !== "object") {
return jsonResponse(400, { error: "Missing record" }); return jsonResponse(400, { error: "Missing record" });
} }
const recordId = record["id"] as string | number | undefined; const recordId = record["id"] as string | number | undefined;
if (!recordId) { if (!recordId) {
return jsonResponse(400, { error: "Record id missing" }); return jsonResponse(400, { error: "Record id missing" });
} }
const existingConversationId = record["conversation_id"] as const existingConversationId = record["conversation_id"] as
| string | string
| null | null
@@ -105,7 +85,6 @@ Deno.serve(async (req) => {
conversation_id: existingConversationId, conversation_id: existingConversationId,
}); });
} }
let conversationId: string; let conversationId: string;
try { try {
conversationId = await createConversationId({ conversationId = await createConversationId({
@@ -115,16 +94,13 @@ Deno.serve(async (req) => {
} catch (error) { } catch (error) {
return jsonResponse(502, { error: (error as Error).message }); return jsonResponse(502, { error: (error as Error).message });
} }
const { error } = await supabase const { error } = await supabase
.from(table) .from(table)
.update({ conversation_id: conversationId }) .update({ conversation_id: conversationId })
.eq("id", recordId); .eq("id", recordId);
if (error) { if (error) {
return jsonResponse(500, { error: error.message }); return jsonResponse(500, { error: error.message });
} }
return jsonResponse(200, { return jsonResponse(200, {
status: "updated", status: "updated",
table, table,