From 507e02db5488241242753e4c78a980c9514c8f80 Mon Sep 17 00:00:00 2001 From: Guillermo Arrieta Medina Date: Thu, 5 Feb 2026 12:04:20 -0600 Subject: [PATCH] Fix #63: mostrar mensaje real de error de Edge Function en UI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Mejorar invokeEdge para parsear el body JSON de errores HTTP de las Edge Functions y extraer un message humano (soporta { error: { message } }, { error: "..." } y { message: "..." }). - EdgeFunctionError ahora incluye status y details; se manejan también FunctionsRelayError y FunctionsFetchError con mensajes más descriptivos. - Ajustes en el front: WizardControls muestra el mensaje real del error (no el genérico "Edge Function returned a non-2xx status code"), y se corrige navegación/logging tras crear plan IA (uso de `plan` en vez de `data` y `navigate` a `/planes/{plan.id}`). - Actualización de types/API: renombrados campos en AIGeneratePlanInput para alinear nombres (descripcionEnfoqueAcademico, instruccionesAdicionalesIA). --- diff.txt | Bin 0 -> 12048 bytes .../planes/wizard/WizardControls.tsx | 8 +- src/data/api/plans.api.ts | 4 +- src/data/supabase/invokeEdge.ts | 90 ++++++++++++++---- 4 files changed, 77 insertions(+), 25 deletions(-) create mode 100644 diff.txt diff --git a/diff.txt b/diff.txt new file mode 100644 index 0000000000000000000000000000000000000000..82286c85a8567f461331acdaacba266433d07bcc GIT binary patch literal 12048 zcmdU#?NVIF5r(JyGgY|*{WzvV77DQlWF#fQvJk?tQnsYnvh!EKE&^MC%r5${tc%GF z&g@KgPk+4K(`WztpI$f)hq|h;s_UtqhPvMiYhe)1 z!-4)EgtKrGp6l;v=<74oH*0!+rdf>e^iu17((`%f>FFQY(@tDB)Hi2ZF$mqb{*`#_ zYv!-ZFS&2%c~dji!!=zuH2O(kH9A@S zAo_sYO1wUlX6}SL+SzKjsq3nCzOJX0;>l*9JFI^mJ>xK3NTUb3KaQ5ICR%$byoq|O zMt>{u$S-{BR9=8@^kt7{x_cUy!jtd^t;D-tYW_W0XsG+8upIs8EZh&@gopazS6G-e z%i*o&Vy#_`ywEuQ*%L+lxzZfGx33lWX?5wrm8flt<8me`RLaXGB|3Uh9%z25&z@x5 z6F0uec_8tlQIu?pM^{mFp;@+1w<@zlE-R|G9US+T!U{HpgS1j=aO;rfGL(4`qoz z>;IDm4ih!*o!v_h?}~Cy(Sh|0XPOdBqw4#p(`uts$$#C?k@Jl{gK#9NUk1wuhl-7% zuH(25qWD51m1Hytn^fOk#%KMz^GNGLG*0909M{%`?i-?aQ*>8^Zt3ZL;#w}vR=67@ z82g-x>kC;Ed;*7t!ioiHp&#=pxWsp_HCDyhJ3@+MefNXry@?}_^nG76KsB=TKr8km zPWIxQaxBfhJqByHqZHsD$bH^2YfJu=@4zUPKDZTSc_En&BDM`A*8@p*B3aqtVU+Nd zaClu$Mxl(zX%tSBZ&qcks+Cr*Z@YgZ&gZ(=(^tAjqP>X3RrsT>N(hVBH&-&UrPkr{P&aS24ERDLM2^oGNDs)3{aJxlu+xR^TpR6jFgkuGZ{rT8*J>X7 zYueFs@lACam6wfK1y)uWv#&#wc}=wu>!m~4Yg788rn;pODy@B8SaFt`YCisLb<%tk z93wVV>)jGY+>)Ftk~TfHVZ>1J`aD{keEwJ6c|DQm`aF|UA9w}ib}xMh+}gXW6Yk4f zK+JD--B;a{{MJ0cXn-h=qvQ<3Smvf!HD5gt^<&9=puhMel@Z?gW5iq5nLk?%RZ=+B zJWza~>Ilzs8hbIq2jR2Gx4u5b`YaM`i|SsCtCPsV^4fTs$eAM0B|@X^GE&*K%jwpj z+TxsZB>pe7<3adZb|pW7zSL)E+h}ZS3~Rj>YPf~!Xv-?9<$>0b2MlcovSv?Xv(u?d z#s0pR)xZ^~W4lrwGW6Lp~f75UWMF2Xpw=qV8B3 zBAv3VVk+%$SH46Ag-tlWP!}A=F>=m=G{sq+DgwV9YUD4G_KHSI+HkZu{XBY9I?WB_ zH&kZC!>(vsR?Vx%=87*ge<;~!olktxJ9yo)qTEwQ+G*;$KC-H~7=+FUE8Dc0a2vhe z6K`r<&;PdC(Bb-bXiXn&(g(J`*CFXMm3vm!5R0`xC9{Yj*u~K8+eR zAXrN+;+{zj^MgL*veSa%viO@;QypuF2JfZlk?C}|eCfAd%Mo{+le?m8CsK>gg&Ay} zP}QkT;+!%lv&Zr6%)r<47IfTm{XbmXd#bWgN?L*BRs$$H~%V!bqtlsp9nU05LA1AL$44o$7`c@-D-@f#c)@RP zw(bkvlH{fTq^N9WM)QnZvP#JLne=IPPxV1rchP6gQS#T~Vd!Ol@r|4hRg9l^L35(R zDmAMs$Vquuy1t`s!zYND+$)wEGrY$Gza06HZC}>t$=?heQtaj#g!78~5}ojrC0*|e zm)T@tL@Avm^ZC;$<$0-&SY?hWX_epQyP^A-TRn9nlZAMXMz1MZqYck;JJNOXD%kun ztu<~*mtc(5|8$G(y@bR3U z2%VL4OPQ!nJO|}gK>GR)>fe`kV)mM@Dtsr3oR7(PWO6%e5l42~x15dr{2Na2peO4% zuMlU?;!4iuAP;Yr_!vdHC0K}jK+lCcsORxne`CT4o%crgcWwnDIQc@;O17unD385! zy0Z8$aVM+8cOmYREKXOvFFCL~oD`IY3bP8FVgAPQ#@9qN%YkWBT{ zb3%=zM=XI%RA`32<#)-?9}11FV(njQ#r(Fko>hMMGl=zl)GAd4RYn!-i)*7dE=^7) zrMj;g!g)`;E1RTfZnLX4h z^+ru*c1PPD4n1keWW+>?Cx_>p}4L{FdtG1OOw@5c-J0tG` zSllKXr+J_1?2qqvq_zF1NpeU>mL4CYDNc8!Z+`bcHxnK0#yF|(6`SzxmeW1>y)@E{ z<5a?4bYd8Iq?sU&4Vx3GOF&HVOnLj>vsDtmhuWmW1VfAy-(f8)h(@;Lh}DI-6KdDfzs zj-~x3kawJRj^jV50EQ>uu0pCW4L7VR8DCv?V(r{-t@Y}AWPeR;$+^#Zg7`YpDc`4! z(^P3G%LGqUdw6#C6KkSb>BLjMQvzA}omrB#j{W@7tn`BSx7)V&lfQJg<~!8N$Etfj zigoW&_9oQ2j`Q!?bWm!-YTWC}mz-&+@A%!c*KS^ICwuUF6TFmD1f2-`owa$e$xXFv zm7nzl?cUFKb{01$A@cn>)5(#^I4`*`pDe^iDf0ssI2 literal 0 HcmV?d00001 diff --git a/src/components/planes/wizard/WizardControls.tsx b/src/components/planes/wizard/WizardControls.tsx index 35f5419..24ea695 100644 --- a/src/components/planes/wizard/WizardControls.tsx +++ b/src/components/planes/wizard/WizardControls.tsx @@ -77,11 +77,11 @@ export function WizardControls({ console.log(`${new Date().toISOString()} - Enviando a generar plan IA`) - const data = await generatePlanAI.mutateAsync(aiInput as any) - console.log(`${new Date().toISOString()} - Plan IA generado`, data) + const plan = await generatePlanAI.mutateAsync(aiInput as any) + console.log(`${new Date().toISOString()} - Plan IA generado`, plan) navigate({ - to: `/planes/${data.plan.id}`, + to: `/planes/${plan.id}`, state: { showConfetti: true }, }) return @@ -122,7 +122,7 @@ export function WizardControls({ -
+
{errorMessage && ( {errorMessage} diff --git a/src/data/api/plans.api.ts b/src/data/api/plans.api.ts index 206f25c..8035bb2 100644 --- a/src/data/api/plans.api.ts +++ b/src/data/api/plans.api.ts @@ -268,8 +268,8 @@ export type AIGeneratePlanInput = { estructuraPlanId: UUID } iaConfig: { - descripcionEnfoque: string - notasAdicionales?: string + descripcionEnfoqueAcademico: string + instruccionesAdicionalesIA?: string archivosReferencia?: Array repositoriosIds?: Array archivosAdjuntos: Array diff --git a/src/data/supabase/invokeEdge.ts b/src/data/supabase/invokeEdge.ts index 627b6eb..2aeff2e 100644 --- a/src/data/supabase/invokeEdge.ts +++ b/src/data/supabase/invokeEdge.ts @@ -1,12 +1,18 @@ -import { supabaseBrowser } from "./client"; +import { + FunctionsFetchError, + FunctionsHttpError, + FunctionsRelayError, +} from '@supabase/supabase-js' -import type { Database } from "@/types/supabase"; -import type { SupabaseClient } from "@supabase/supabase-js"; +import { supabaseBrowser } from './client' + +import type { Database } from '@/types/supabase' +import type { SupabaseClient } from '@supabase/supabase-js' export type EdgeInvokeOptions = { - method?: "GET" | "POST" | "PUT" | "PATCH" | "DELETE"; - headers?: Record; -}; + method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' + headers?: Record +} export class EdgeFunctionError extends Error { constructor( @@ -15,8 +21,8 @@ export class EdgeFunctionError extends Error { public readonly status?: number, public readonly details?: unknown, ) { - super(message); - this.name = "EdgeFunctionError"; + super(message) + this.name = 'EdgeFunctionError' } } @@ -34,23 +40,69 @@ export async function invokeEdge( opts: EdgeInvokeOptions = {}, client?: SupabaseClient, ): Promise { - const supabase = client ?? supabaseBrowser(); + const supabase = client ?? supabaseBrowser() const { data, error } = await supabase.functions.invoke(functionName, { body, - method: opts.method ?? "POST", + method: opts.method ?? 'POST', headers: opts.headers, - }); + }) if (error) { - const anyErr = error; - throw new EdgeFunctionError( - anyErr.message ?? "Error en Edge Function", - functionName, - anyErr.status, - anyErr, - ); + // Valores por defecto (por si falla el parseo o es otro tipo de error) + let message = error.message // El genérico "returned a non-2xx status code" + let status = undefined + let details: unknown = error + + // 2. Verificamos si es un error HTTP (4xx o 5xx) que trae cuerpo JSON + if (error instanceof FunctionsHttpError) { + try { + // Obtenemos el status real (ej. 404, 400) + status = error.context.status + + // ¡LA CLAVE! Leemos el JSON que tu Edge Function envió + const errorBody = await error.context.json() + details = errorBody + + // Intentamos extraer el mensaje humano según tu estructura { error: { message: "..." } } + // o la estructura simple { error: "..." } + if (errorBody && typeof errorBody === 'object') { + // Caso 1: Estructura anidada (la que definimos hace poco: { error: { message: "..." } }) + if ( + 'error' in errorBody && + typeof errorBody.error === 'object' && + errorBody.error !== null && + 'message' in errorBody.error + ) { + message = (errorBody.error as { message: string }).message + } + // Caso 2: Estructura simple ({ error: "Mensaje de error" }) + else if ( + 'error' in errorBody && + typeof errorBody.error === 'string' + ) { + message = errorBody.error + } + // Caso 3: Propiedad message directa ({ message: "..." }) + else if ( + 'message' in errorBody && + typeof errorBody.message === 'string' + ) { + message = errorBody.message + } + } + } catch (e) { + console.warn('No se pudo parsear el error JSON de la Edge Function', e) + } + } else if (error instanceof FunctionsRelayError) { + message = `Error de Relay Supabase: ${error.message}` + } else if (error instanceof FunctionsFetchError) { + message = `Error de conexión (Fetch): ${error.message}` + } + + // 3. Lanzamos tu error personalizado con los datos reales extraídos + throw new EdgeFunctionError(message, functionName, status, details) } - return data as TOut; + return data as TOut } -- 2.49.1