From 42a437afa31a6f676a2df0c74daa867c9778e8c7 Mon Sep 17 00:00:00 2001 From: Guillermo Arrieta Medina Date: Thu, 5 Feb 2026 12:00:31 -0600 Subject: [PATCH] fix(functions): clasificar y estandarizar errores en ai-generate-plan MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Corregido el problema donde la función devolvía siempre 400; ahora se lanzan HttpError con status adecuados (405, 401, 415, 422, 404, 409, 502, 500) según el caso. - Refactor: usar sendError/sendSuccess y HttpError desde _shared/utils.ts para respuestas consistentes y logging seguro. - Validación: agrupar errores de parse/validación en un único mensaje numerado (cada error en nueva línea). - OpenAI: manejo explícito de fallos (MissingEnv, JSON inválido, salida estructurada ausente) y mapeo a status apropiados. - Supabase: distinguir errores 404/409/500 en consultas e inserts; incluir detalles en internalDetails para diagnóstico server-side. - Eliminada dependencia de invocación HTTP a ai-structured; usar servicio compartido directo. - Ajustes menores en mensajes y configuración (deno imports/deno.json) y tests marcados como ignorados donde corresponde. --- diff.txt | Bin 278366 -> 26264 bytes supabase/functions/_shared/utils.ts | 49 ++++ supabase/functions/ai-generate-plan/deno.json | 5 +- supabase/functions/ai-generate-plan/index.ts | 225 +++++++++++++----- .../functions/tests/ai-structured-test.ts | 3 - 5 files changed, 217 insertions(+), 65 deletions(-) create mode 100644 supabase/functions/_shared/utils.ts diff --git a/diff.txt b/diff.txt index 864c3967c9e919e37830ebc4d1de6b34861fbacc..b2704fc12fbf93bc76dd1ff384e32a1099a0a7ff 100644 GIT binary patch literal 26264 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 literal 278366 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 h*&gGrUAhtFe>7O1-!s^c@o#Gu$8h`Y%(data: T, status = 200): Response { + return new Response( + JSON.stringify(data), + { + status, + headers: { ...corsHeaders, "Content-Type": "application/json" }, + }, + ); +} + +// --- HELPER DE ERROR --- +export function sendError( + status: number, + message: string, + code: string, +): Response { + return new Response( + JSON.stringify({ + error: { message, code }, + }), + { + status, + headers: { ...corsHeaders, "Content-Type": "application/json" }, + }, + ); +} diff --git a/supabase/functions/ai-generate-plan/deno.json b/supabase/functions/ai-generate-plan/deno.json index f5db280..8b204ca 100644 --- a/supabase/functions/ai-generate-plan/deno.json +++ b/supabase/functions/ai-generate-plan/deno.json @@ -1,5 +1,8 @@ { "imports": { - "zod": "https://deno.land/x/zod@v3.22.4/mod.ts" + "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" } } diff --git a/supabase/functions/ai-generate-plan/index.ts b/supabase/functions/ai-generate-plan/index.ts index e3ff851..374a5f3 100644 --- a/supabase/functions/ai-generate-plan/index.ts +++ b/supabase/functions/ai-generate-plan/index.ts @@ -5,6 +5,7 @@ // Setup type definitions for built-in Supabase Runtime APIs import "jsr:@supabase/functions-js/edge-runtime.d.ts"; import { corsHeaders } from "../_shared/cors.ts"; +import { HttpError, sendError, sendSuccess } from "../_shared/utils.ts"; import { createClient } from "npm:@supabase/supabase-js@2"; import type { Database, Json } from "../_shared/database.types.ts"; import type { AIGeneratePlanInput } from "./types.ts"; @@ -18,7 +19,7 @@ type NivelType = type TipoCicloType = Database["public"]["Tables"]["planes_estudio"]["Insert"]["tipo_ciclo"]; -Deno.serve(async (req) => { +Deno.serve(async (req: Request): Promise => { const url = new URL(req.url); const functionName = url.pathname.split("/").pop(); console.log( @@ -37,12 +38,11 @@ Deno.serve(async (req) => { new Date().toISOString() }][${functionName}]: Invalid method: ${method}`, ); - return new Response( - JSON.stringify({ error: "Method not allowed" }), - { - headers: { ...corsHeaders, "Content-Type": "application/json" }, - status: 405, - }, + throw new HttpError( + 405, + "Método no permitido.", + "METHOD_NOT_ALLOWED", + { method }, ); } @@ -54,12 +54,11 @@ Deno.serve(async (req) => { new Date().toISOString() }][${functionName}]: Missing Authorization header`, ); - return new Response( - JSON.stringify({ error: "Authorization header missing" }), - { - headers: { ...corsHeaders, "Content-Type": "application/json" }, - status: 401, - }, + throw new HttpError( + 401, + "No autorizado.", + "UNAUTHORIZED", + { reason: "missing_authorization_header" }, ); } @@ -70,19 +69,28 @@ Deno.serve(async (req) => { new Date().toISOString() }][${functionName}]: Unsupported content type: ${contentType}`, ); - return new Response( - JSON.stringify({ error: "Unsupported content type" }), - { - headers: { ...corsHeaders, "Content-Type": "application/json" }, - status: 400, - }, + throw new HttpError( + 415, + "Content-Type no soportado.", + "UNSUPPORTED_MEDIA_TYPE", + { contentType }, ); } const SUPABASE_URL = Deno.env.get("SUPABASE_URL"); const SUPABASE_ANON_KEY = Deno.env.get("SUPABASE_ANON_KEY"); if (!SUPABASE_URL || !SUPABASE_ANON_KEY) { - throw new Error("Supabase environment variables are not set"); + throw new HttpError( + 500, + "Configuración del servidor incompleta.", + "MISSING_ENV", + { + missing: [ + !SUPABASE_URL ? "SUPABASE_URL" : null, + !SUPABASE_ANON_KEY ? "SUPABASE_ANON_KEY" : null, + ].filter(Boolean), + }, + ); } // If needed for RLS-protected reads, create an anon client with user's JWT @@ -97,7 +105,12 @@ Deno.serve(async (req) => { const SERVICE_ROLE_KEY = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY"); if (!SERVICE_ROLE_KEY) { - throw new Error("SUPABASE_SERVICE_ROLE_KEY is not set"); + throw new HttpError( + 500, + "Configuración del servidor incompleta.", + "MISSING_ENV", + { missing: ["SUPABASE_SERVICE_ROLE_KEY"] }, + ); } const supabaseService = createClient( @@ -112,12 +125,15 @@ Deno.serve(async (req) => { `[${new Date().toISOString()}][${functionName}]: Validation errors:`, validation.errors, ); - return new Response( - JSON.stringify({ errors: validation.errors }), - { - headers: { ...corsHeaders, "Content-Type": "application/json" }, - status: 400, - }, + const message = validation.errors + .map((e, i) => `(${i + 1}) ${e}`) + .join("\n"); + + throw new HttpError( + 422, + message, + "VALIDATION_ERROR", + { errors: validation.errors }, ); } @@ -130,8 +146,23 @@ Deno.serve(async (req) => { .eq("id", payload.datosBasicos.estructuraPlanId) .single(); if (estructuraPlanError) { - throw new Error( - "Error fetching estructuraPlan: " + estructuraPlanError.message, + const maybeCode = (estructuraPlanError as { code?: string }).code; + if (maybeCode === "PGRST116") { + throw new HttpError( + 404, + "No se encontró la estructura del plan.", + "NOT_FOUND", + { + table: "estructuras_plan", + id: payload.datosBasicos.estructuraPlanId, + }, + ); + } + throw new HttpError( + 500, + "No se pudo obtener la estructura del plan.", + "SUPABASE_QUERY_FAILED", + estructuraPlanError, ); } @@ -165,25 +196,64 @@ Deno.serve(async (req) => { // Use shared OpenAI service directly (no HTTP invoke) const svc = OpenAIService.fromEnv(); if (!(svc instanceof OpenAIService)) { - throw new Error(`OpenAI service misconfiguration: ${svc.message}`); + throw new HttpError( + 500, + "Configuración del servidor incompleta.", + "OPENAI_MISCONFIGURED", + svc, + ); } + // INICIO DE CÓDIGO PARA DEBBUGGING + // console.log( + // `[${ + // new Date().toISOString() + // }][${functionName}]: Request processed successfully`, + // ); + // const { data: plan_debug } = await supabaseService + // .from("planes_estudio") + // .select("*") + // .eq("id", "7ce657b1-1abf-4972-858d-5fffe1d51499") + // .maybeSingle(); + // return sendSuccess(plan_debug); + // FIN DE CÓDIGO PARA DEBBUGGING + const aiResult = await svc.createStructuredResponse( aiStructuredPayload, payload.archivosAdjuntos, ); if (!aiResult.ok) { - throw new Error( - `OpenAI call failed [${aiResult.code}]: ${aiResult.message}`, + const status = aiResult.code === "MissingEnv" ? 500 : 502; + throw new HttpError( + status, + "No se pudo generar el plan con IA.", + "OPENAI_REQUEST_FAILED", + aiResult, ); } // Prefer parsed output; fallback to parse outputText - const aiOutput = aiResult.output ?? - (aiResult.outputText ? JSON.parse(aiResult.outputText) : null); + 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 }, + ); + } + } const aiOutputJson: Json = aiOutput as unknown as Json; if (!aiOutput) { - throw new Error("OpenAI response did not contain structured output"); + throw new HttpError( + 502, + "La respuesta de la IA no contiene salida estructurada.", + "OPENAI_MISSING_STRUCTURED_OUTPUT", + { outputText: aiResult.outputText ?? null }, + ); } // TODO: Insertar interacciones con IA y quizas forzar a que la informacion de datosBasicos sea la misma que la recibida @@ -202,7 +272,20 @@ Deno.serve(async (req) => { .eq("id", payload.datosBasicos.carreraId) .maybeSingle(); if (carreraError) { - throw new Error("Error fetching carrera: " + carreraError.message); + throw new HttpError( + 500, + "No se pudo obtener la carrera.", + "SUPABASE_QUERY_FAILED", + carreraError, + ); + } + if (!carrera) { + throw new HttpError( + 404, + "No se encontró la carrera.", + "NOT_FOUND", + { table: "carreras", id: payload.datosBasicos.carreraId }, + ); } const planInsert: Database["public"]["Tables"]["planes_estudio"]["Insert"] = @@ -248,42 +331,62 @@ Deno.serve(async (req) => { // TODO: interaccion con IA y cambio de estado if (planError) { - throw new Error("Error inserting plan: " + planError.message); + const maybeCode = (planError as { code?: string }).code; + // Common cases: + // - foreign key / constraint violations -> 409 + // - others -> 500 + const status = maybeCode ? 409 : 500; + throw new HttpError( + status, + "No se pudo guardar el plan de estudios.", + "SUPABASE_INSERT_FAILED", + { ...planError, code: maybeCode }, + ); } // TODO: update a interaccion_ia y e insert a cambios_plan con id de plan generado - const jsonResponse = { - ok: true, - plan, - }; console.log( `[${ new Date().toISOString() }][${functionName}]: Request processed successfully`, ); - return new Response( - JSON.stringify(jsonResponse), - { - headers: { ...corsHeaders, "Content-Type": "application/json" }, - status: 200, - }, - ); + return sendSuccess(plan); } catch (error) { - // Log full error server-side for diagnostics - if (error instanceof Error) { + if (error instanceof HttpError) { console.error( - `[${ - new Date().toISOString() - }][${functionName}]: Request handler error:`, - error.message, + `[${new Date().toISOString()}][${functionName}] ⚠️ Handled Error:`, + { + message: error.message, + code: error.code, + internalDetails: error.internalDetails || "N/A", + }, ); + + // RESPONSE: Solo enviamos el mensaje limpio y el código + return sendError(error.status, error.message, error.code); } - const message = error instanceof Error ? error.message : String(error); - return new Response(JSON.stringify({ error: message }), { - headers: { ...corsHeaders, "Content-Type": "application/json" }, - status: 400, - }); + + // CASO B: Error Inesperado (Crash, Bug, Syntax Error, etc.) + // El usuario NO debe ver esto. + const unexpectedError = error instanceof Error + ? error + : new Error(String(error)); + + // LOG: Full stack trace y mensaje real + console.error( + `[${ + new Date().toISOString() + }][${functionName}] 💥 CRITICAL UNHANDLED ERROR:`, + unexpectedError.stack || unexpectedError.message, // Esto es lo que necesitas para debuguear + ); + + // RESPONSE: Mensaje genérico y seguro + return sendError( + 500, + "Ocurrió un error inesperado en el servidor.", + "INTERNAL_SERVER_ERROR", + ); } }); @@ -315,8 +418,8 @@ const DatosBasicosSchema: z.ZodType = z }); const IAConfigSchema: z.ZodType = z.object({ - descripcionEnfoque: z.string().min(10, "La descripción es muy corta"), - notasAdicionales: z.string().optional(), + descripcionEnfoqueAcademico: z.string(), + instruccionesAdicionalesIA: z.string().optional(), archivosReferencia: z.array(z.string().uuid()).optional(), repositoriosIds: z.array(z.string().uuid()).optional(), usarMCP: z.boolean().optional(), diff --git a/supabase/functions/tests/ai-structured-test.ts b/supabase/functions/tests/ai-structured-test.ts index 5a52e1e..0145f7d 100644 --- a/supabase/functions/tests/ai-structured-test.ts +++ b/supabase/functions/tests/ai-structured-test.ts @@ -25,9 +25,6 @@ function mustEnv() { if (!SUPABASE_ANON_KEY) throw new Error("SUPABASE_ANON_KEY is required"); } -async function getAuthedClient(): Promise< - { client: SupabaseClient; accessToken: string } -> { async function getAuthedClient(): Promise< { client: SupabaseClient; accessToken: string } > {