[{"data":1,"prerenderedAt":1131},["ShallowReactive",2],{"blog-es-failed-to-send-a-request-to-the-edge-function":3,"blog-index-es":602},{"id":4,"title":5,"body":6,"category":558,"cover":559,"coverAlt":560,"description":561,"draft":562,"extension":563,"faq":564,"image":582,"keywords":583,"meta":590,"navigation":591,"ogTitle":592,"path":593,"published":594,"seo":595,"stem":596,"tldr":597,"updated":594,"__hash__":601},"blog_es\u002Fblog\u002Ffailed-to-send-a-request-to-the-edge-function.md","\"Failed to send a request to the Edge Function\": la causa real",{"type":7,"value":8,"toc":547},"minimark",[9,13,24,27,35,40,43,65,68,83,95,99,102,115,122,128,137,154,160,164,167,255,273,282,288,307,326,330,333,352,360,368,375,378,390,396,401,405,408,418,426,429,444,448,454,463,478,488,505,509,532],[10,11,12],"p",{},"Tu app tiene un botón que llama a una Supabase Edge Function, ese pequeño trozo\nde código de servidor que una app hecha con Lovable, Bolt, Cursor o Claude Code\nusa para el trabajo que no puede hacerse en el navegador, como llamar a OpenAI\ncon una clave. Lo pulsas y, en lugar de un resultado, tu app muestra esto:",[14,15,20],"pre",{"className":16,"code":18,"language":19},[17],"language-text","Failed to send a request to the Edge Function\n","text",[21,22,18],"code",{"__ignoreMap":23},"",[10,25,26],{},"La función está desplegada. Llamada desde una terminal, puede que incluso\nresponda perfectamente. Desde tu app falla cada vez, y \"failed to send a request\nto the Edge Function\" da a entender que tu petición nunca salió.",[10,28,29,30,34],{},"Bien puede que sí saliera. ",[31,32,33],"strong",{},"Tu función quizá se ejecutó, hizo su trabajo y\nrespondió, y el navegador se negó a entregarle la respuesta a tu app."," Cuál de\nlas dos cosas pasó está escrito en un solo sitio, y no es el error que recibió tu\napp.",[36,37,39],"h2",{"id":38},"qué-significa-failed-to-send-a-request-to-the-edge-function","¿Qué significa \"Failed to send a request to the Edge Function\"?",[10,41,42],{},"Significa que supabase-js pidió una respuesta y no recibió ninguna que pudiera\nleer.",[10,44,45,46,53,54,57,58,64],{},"La frase es un texto fijo dentro de\n",[47,48,52],"a",{"href":49,"rel":50},"https:\u002F\u002Fgithub.com\u002Fsupabase\u002Fsupabase-js\u002Fblob\u002Fmaster\u002Fpackages\u002Fcore\u002Ffunctions-js\u002Fsrc\u002Ftypes.ts",[51],"nofollow","supabase-js",",\nasociado a un error llamado ",[21,55,56],{},"FunctionsFetchError",". supabase-js lo lanza cuando\nfalla la propia petición, antes de que exista ningún código de estado que mirar.\nEso lo separa de su hermano más conocido:\n",[47,59,61],{"href":60},"\u002Fblog\u002Fedge-function-returned-a-non-2xx-status-code",[21,62,63],{},"Edge Function returned a non-2xx status code","\nsignifica que volvió una respuesta y era un error. Este significa que no volvió\nninguna respuesta legible.",[10,66,67],{},"Una petición falla así por un puñado de motivos. El dispositivo está sin\nconexión, la dirección del proyecto en tu app es incorrecta, o las reglas del\nnavegador para otras direcciones rechazaron la respuesta. Esas reglas se llaman\nCORS, de Cross-Origin Resource Sharing, e impiden que una página web lea una\nrespuesta de otra dirección salvo que la respuesta lo permita. Cuando la función\nresponde desde una terminal y solo falla en tu app, el sospechoso es CORS, y el\nnavegador te dirá si lo fue.",[10,69,70,71,74,75,78,79,82],{},"Tu app vive en una dirección, por ejemplo ",[21,72,73],{},"yourapp.lovable.app",", y tu función en\notra, ",[21,76,77],{},"\u003Cproject-ref>.supabase.co",". Piensa en el navegador como el portero de tu\napp. El correo que llega de otra dirección solo se le entrega a tu app si lleva\nuna etiqueta del remitente que diga que tu app puede tenerlo: una cabecera de\nrespuesta llamada ",[21,80,81],{},"Access-Control-Allow-Origin",". Con una carta como la tuya, el\nportero primero llama a la puerta del remitente para preguntar si la acepta. Y\ncuando rechaza algo, a tu app solo le dice que no funcionó. El motivo lo apunta\nen su propia libreta.",[10,84,85,86,89,90,94],{},"Un tercer mensaje es de la misma familia. ",[21,87,88],{},"Relay Error invoking the Edge Function"," es supabase-js avisando de que el relay de Supabase no pudo llegar a tu\nfunción, así que\n",[47,91,93],{"href":92},"\u002Fblog\u002Fis-supabase-down-or-your-app","comprueba si Supabase tiene un problema","\nantes de cambiar código.",[36,96,98],{"id":97},"por-qué-tu-app-no-se-entera-de-más","Por qué tu app no se entera de más",[10,100,101],{},"Porque el navegador le oculta el motivo a tu código a propósito.",[10,103,104,105,110,111,114],{},"La ",[47,106,109],{"href":107,"rel":108},"https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FHTTP\u002FGuides\u002FCORS\u002FErrors",[51],"página de MDN sobre errores de CORS","\nlo dice sin rodeos: por motivos de seguridad, el código JavaScript no tiene\nacceso a los detalles de qué salió mal en una petición CORS. Tu app sabe que algo\nfalló y nada más. En Chrome, el error que envuelve supabase-js dice\n",[21,112,113],{},"TypeError: Failed to fetch",", un texto que Chromium usa igual para cualquier\nfallo de red, así que imprimirlo no aporta nada. Según la misma página de MDN, la\núnica forma de saber qué pasó es la consola del navegador.",[10,116,117,118,121],{},"Esa es la libreta del portero. Abre tu app publicada en Chrome o Edge, pulsa\nF12, elige ",[31,119,120],{},"Console"," y vuelve a pulsar el botón de tu app. La línea que buscas\nes roja y empieza así:",[14,123,126],{"className":124,"code":125,"language":19},[17],"Access to fetch at 'https:\u002F\u002F\u003Cproject-ref>.supabase.co\u002Ffunctions\u002Fv1\u002F\u003Cname>' from origin 'https:\u002F\u002Fyourapp.lovable.app' has been blocked by CORS policy:\n",[21,127,125],{"__ignoreMap":23},[10,129,130,131,136],{},"Todo lo que va después de los dos puntos es el motivo. Firefox y Safari lo\nredactan de otra forma; las citas de este artículo son las de Chrome, sacadas del\n",[47,132,135],{"href":133,"rel":134},"https:\u002F\u002Fgithub.com\u002Fchromium\u002Fchromium\u002Fblob\u002Fmain\u002Fthird_party\u002Fblink\u002Frenderer\u002Fplatform\u002Floader\u002Fcors\u002Fcors_error_string.cc",[51],"código fuente de Chromium",".",[10,138,139,140,143,144,147,148,153],{},"La llamada a la puerta tiene un nombre técnico, preflight request. Es una\npetición con el método ",[21,141,142],{},"OPTIONS"," que el navegador envía antes que la tuya, y\nsupabase-js siempre provoca una: envía tu clave en una cabecera ",[21,145,146],{},"Authorization","\ny tus datos como JSON, y la\n",[47,149,152],{"href":150,"rel":151},"https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FHTTP\u002FGuides\u002FCORS",[51],"guía de CORS de MDN","\nno incluye ninguna de las dos cosas entre lo que una página puede enviar sin\npreguntar antes.",[155,156],"diagram",{"alt":157,"caption":158,"src":159},"Dos intentos de la misma llamada. Arriba, la respuesta a la llamada OPTIONS del navegador vuelve con un contorno vacío donde debería estar su etiqueta, el navegador la rechaza y la petición POST nunca sale. Abajo, la respuesta a la llamada lleva su etiqueta y pasa, la petición POST llega a la función, que hace su trabajo, y la respuesta vuelve sin etiqueta y se detiene en el control del navegador.","Arriba, la llamada se rechaza y tu petición nunca sale. Abajo, la llamada pasa, la función hace el trabajo y solo su respuesta queda retenida.","\u002Fblog\u002Ffailed-to-send-a-request-to-the-edge-function\u002Fthe-knock-first-1600x760.png",[36,161,163],{"id":162},"qué-mitad-falló-y-llegó-a-ejecutarse-tu-función","¿Qué mitad falló y llegó a ejecutarse tu función?",[10,165,166],{},"Las palabras \"preflight request\" en la línea de la consola lo deciden. Si están,\nel navegador se detuvo en la llamada a la puerta y tu petición nunca se envió. Si\nno están, tu petición pasó, tu función se ejecutó y su respuesta quedó retenida.",[168,169,170,186],"table",{},[171,172,173],"thead",{},[174,175,176,180,183],"tr",{},[177,178,179],"th",{},"La línea de la consola termina con",[177,181,182],{},"Qué pasó",[177,184,185],{},"¿Hizo la función el trabajo?",[187,188,189,203,216,228,244],"tbody",{},[174,190,191,197,200],{},[192,193,194],"td",{},[21,195,196],{},"Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.",[192,198,199],{},"La llamada recibió respuesta sin etiqueta",[192,201,202],{},"No. Solo le llegó la llamada",[174,204,205,210,213],{},[192,206,207],{},[21,208,209],{},"Response to preflight request doesn't pass access control check: It does not have HTTP ok status.",[192,211,212],{},"La llamada recibió un error como respuesta",[192,214,215],{},"No",[174,217,218,223,226],{},[192,219,220],{},[21,221,222],{},"Request header field \u003Cname> is not allowed by Access-Control-Allow-Headers in preflight response.",[192,224,225],{},"Tu app envía una cabecera que la lista de la función no nombra",[192,227,215],{},[174,229,230,236,239],{},[192,231,232,235],{},[21,233,234],{},"No 'Access-Control-Allow-Origin' header is present on the requested resource.",", sin mencionar preflight",[192,237,238],{},"La llamada pasó, tu petición llegó y la respuesta no tenía etiqueta",[192,240,241],{},[31,242,243],{},"Sí",[174,245,246,249,252],{},[192,247,248],{},"Ninguna línea de CORS",[192,250,251],{},"No es CORS: la conexión, la dirección o un tiempo límite de tu código",[192,253,254],{},"Mira la pestaña Invocations",[10,256,257,262,263,268,269,272],{},[31,258,259,260,136],{},"La primera fila es lo que produce una función sin manejador para\n",[21,261,142],{}," La\n",[47,264,267],{"href":265,"rel":266},"https:\u002F\u002Fsupabase.com\u002Fdocs\u002Fguides\u002Ftroubleshooting\u002Fedge-function-500-error-response",[51],"página de Supabase sobre el error 500","\nlo describe desde el lado de la función: la llamada llega a tu código como\ncualquier otra petición, y la función registra ",[21,270,271],{},"SyntaxError: Unexpected end of JSON input",", porque una llamada a la puerta no trae contenido y el código intentó\nleer uno. Su respuesta es un error sin cabeceras, y Chrome informa de la cabecera\nque falta antes de mirar el estado. Si esa línea está en la pestaña Logs de la\nfunción, ya lo tienes.",[10,274,275,278,279,281],{},[31,276,277],{},"La segunda fila significa que la llamada recibió las cabeceras, y un error con\nellas."," Una forma de llegar ahí es una comprobación colocada encima de la rama\nde ",[21,280,142],{}," que la llamada no puede superar, como una comprobación de quién\nllama. La llamada no lleva ninguna de tus cabeceras, tampoco tu clave, así que\nfalla cada vez.",[10,283,284,287],{},[31,285,286],{},"La cuarta fila es la que pide ir despacio, porque el trabajo se hizo."," Si la\nfunción guarda una fila, envía un correo o cobra una tarjeta, eso ya está hecho,\ny volver a pulsar el botón lo hace otra vez. Lo que falló fue la respuesta, que\nsalió sin etiqueta. Busca primero un error: un fallo cuya respuesta tu código ni\nllegó a escribir, o una rama de error que devuelve sin las cabeceras. La misma\npágina de Supabase avisa de que entonces el navegador culpa a CORS de un fallo\ncuya causa real es otra.",[10,289,290,291,294,295,298,299,302,303,306],{},"Así que lee el propio registro de la función antes de tocar ninguna cabecera. En\nel panel de Supabase, abre ",[31,292,293],{},"Edge Functions",", elige la función y lee sus\npestañas ",[31,296,297],{},"Invocations"," y ",[31,300,301],{},"Logs"," en el minuto en que pulsaste el botón.\n",[47,304,305],{"href":60},"Leer el error real"," recorre\nlas dos, y lo que significa cada código de estado.",[10,308,309,312,313,316,317,320,321,325],{},[31,310,311],{},"La última fila significa que el portero no rechazó nada."," La petición falló\npor el camino: el dispositivo perdió la conexión, o la dirección del proyecto a\nla que llama tu app no existe. Un caso viene de tu propio código. Si pasa un\n",[21,314,315],{},"timeout"," a ",[21,318,319],{},"supabase.functions.invoke",",\n",[47,322,52],{"href":323,"rel":324},"https:\u002F\u002Fgithub.com\u002Fsupabase\u002Fsupabase-js\u002Fblob\u002Fmaster\u002Fpackages\u002Fcore\u002Ffunctions-js\u002Fsrc\u002FFunctionsClient.ts",[51],"\ncancela la petición cuando se acaba el tiempo e informa de esta misma frase,\naunque la función quizá siga trabajando.",[36,327,329],{"id":328},"cómo-arreglarlo-responder-a-la-llamada-y-etiquetar-cada-respuesta","Cómo arreglarlo: responder a la llamada y etiquetar cada respuesta",[10,331,332],{},"Dos cambios en la función, y van en el mismo despliegue.",[334,335,336,346],"ol",{},[337,338,339,345],"li",{},[31,340,341,342,344],{},"Una rama para ",[21,343,142],{}," lo primero de todo",", antes de cualquier línea que\nlea el contenido o compruebe quién llama. Responde a la llamada con las\ncabeceras CORS y nada más.",[337,347,348,351],{},[31,349,350],{},"Las cabeceras CORS en cada respuesta que devuelve la función",", las que\nsalen bien y los errores. Una respuesta sin ellas queda retenida, diga lo que\ndiga.",[10,353,104,354,359],{},[47,355,358],{"href":356,"rel":357},"https:\u002F\u002Fsupabase.com\u002Fdocs\u002Fguides\u002Ffunctions\u002Fcors",[51],"guía de CORS de Supabase","\narma una función más o menos así:",[14,361,366],{"className":362,"code":364,"language":365,"meta":23},[363],"language-ts","import { corsHeaders } from 'npm:@supabase\u002Fsupabase-js@^2\u002Fcors'\n\nDeno.serve(async (req) => {\n  if (req.method === 'OPTIONS') {\n    return Response.json({ ok: true }, { headers: corsHeaders })\n  }\n  try {\n    const body = await req.json()\n    \u002F\u002F the function's own work goes here\n    return Response.json({ done: true }, { headers: corsHeaders })\n  } catch (error) {\n    const message = error instanceof Error ? error.message : String(error)\n    return Response.json({ error: message }, { status: 400, headers: corsHeaders })\n  }\n})\n","ts",[21,367,364],{"__ignoreMap":23},[10,369,370,371,374],{},"Las cabeceras vienen del propio supabase-js en vez de una lista pegada en la\nfunción. La guía las importa así desde la versión 2.95.0 de supabase-js, para que\nla lista coincida con lo que envían las bibliotecas cliente, y dice que hay que\nvolver a desplegar tras actualizar. Las funciones más antiguas llevan una copia\nescrita a mano de la misma lista, en un archivo llamado ",[21,372,373],{},"_shared\u002Fcors.ts",". De esa\ncopia sale la tercera fila de la tabla: una cabecera que tu app envía y que la\nlista no nombra.",[10,376,377],{},"Si le pides el arreglo a Lovable, Bolt, Cursor o Claude Code, esos dos puntos\nnumerados son el prompt. Nombra los dos. El segundo se olvida con facilidad, y\nuna función que solo tiene el primero pasa de la primera fila de la tabla a la\ncuarta la próxima vez que falle.",[10,379,380,381,384,385,389],{},"Los ejemplos más recientes de Supabase envuelven toda la función en\n",[21,382,383],{},"withSupabase",", que responde a la llamada por ti.\n",[47,386,388],{"href":387},"\u002Fblog\u002Fmove-a-secret-to-an-edge-function","Mover un secreto a una Edge Function","\nmuestra ese envoltorio con una comprobación de quién llama, y esa comprobación es\nel tema de la siguiente sección.",[10,391,392,393,395],{},"Con las cabeceras en cada respuesta, una función que falla deja de mostrar este\nmensaje y empieza a mostrar ",[21,394,63],{},". La\nrespuesta ya llega a tu app, con el motivo dentro.",[155,397],{"alt":398,"caption":399,"src":400},"Una función devuelve tres respuestas hacia el control del navegador. Una respuesta 200 con etiqueta pasa y llega a la app. Una respuesta 500 sin etiqueta se detiene en el control. Una respuesta 400 con etiqueta pasa, así que la app recibe el error y puede leerlo.","El navegador busca la etiqueta y nunca mira lo que dice la respuesta. Un error que lleva las cabeceras llega a tu app; uno sin ellas queda retenido y se informa como petición fallida.","\u002Fblog\u002Ffailed-to-send-a-request-to-the-edge-function\u002Ftag-every-reply-1600x720.png",[36,402,404],{"id":403},"decide-la-cabecera-cors-quién-puede-llamar-a-tu-función","¿Decide la cabecera CORS quién puede llamar a tu función?",[10,406,407],{},"No. Decide qué páginas web pueden leer la respuesta, y solo los navegadores la\nmiran.",[10,409,410,411,413,414,417],{},"La propia lista de Supabase pone ",[21,412,81],{}," en ",[21,415,416],{},"*",", que\npermite cualquier origen, tanto en la copia que exporta supabase-js como en la\nantigua escrita a mano. En una función que comprueba el token de quien llama, eso\nestá bien. En una función que no comprueba nada, permite que cualquier página de\ncualquier web llame a tu función desde los navegadores de sus propios visitantes\ny lea lo que vuelve.",[10,419,420,421,425],{},"Limitar la cabecera a tu propio dominio tampoco cierra la función. Un script, una\nterminal u otro servidor envía la misma petición sin navegador, nunca lee la\ncabecera y recibe la respuesta completa.\n",[47,422,424],{"href":423},"\u002Fblog\u002Fcors-wildcard-security-risk","Qué abre y qué no un comodín de CORS"," lo\nexplica en detalle.",[10,427,428],{},"El cerrojo de una Edge Function es una comprobación de quién llama, escrita en la\nfunción. Importa sobre todo en las funciones que acaban detrás de este error,\nporque son las que guardan una clave de OpenAI o la clave secreta de tu proyecto,\ny la clave secreta se salta todas las reglas de row level security que\nescribiste.",[10,430,431,432,435,436,439,440,136],{},"La otra salida de un error de CORS es dejar de usar la función: poner la clave en\nuna variable ",[21,433,434],{},"VITE_"," y llamar a OpenAI directamente desde la página. Funciona al\nmomento, y pone la clave en el archivo que descarga cada visitante. Es el\nprimero de los\n",[47,437,438],{"href":60},"dos arreglos que te dejan peor",".\nEse error se ve desde fuera. Nuestro escaneo gratuito lee el JavaScript de tu app\npublicada como lo haría un desconocido y nombra cada clave que encuentra,\nincluido si es de las que deben estar ahí. Tarda unos 20 segundos y no necesita\ncuenta: ",[47,441,443],{"href":442},"\u002Fsecurity-scanner","escanea tu app",[36,445,447],{"id":446},"que-la-clave-no-acabe-en-la-página-tras-el-próximo-cambio","Que la clave no acabe en la página tras el próximo cambio",[10,449,450,453],{},[31,451,452],{},"Una función que arreglaste hoy puede reescribirse la próxima vez que le pidas\nuna funcionalidad a tu builder",", y la forma más rápida de saltarse un error de\nCORS es la que pone tu clave en la página.",[10,455,456],{},[31,457,458,462],{},[47,459,461],{"href":460},"\u002Fwebsite-monitoring","Reeve Monitor"," vuelve a escanear tu app publicada cada hora:",[464,465,466,469,472,475],"ul",{},[337,467,468],{},"las nueve comprobaciones cada hora, en hasta tres apps",[337,470,471],{},"un correo el día en que un nuevo escaneo califica tu app peor que el anterior; una clave secreta en tu JavaScript limita por sí sola la nota a D",[337,473,474],{},"si la app está disponible, cada 60 segundos",[337,476,477],{},"un informe mensual de lo que vio",[10,479,480],{},[31,481,482,483,487],{},"Una función que guarda la clave secreta de tu proyecto puede cambiar cualquier\nfila de tu base de datos para quien la llame. ",[47,484,486],{"href":485},"\u002Fsupabase-backups","Reeve Care","\nguarda una copia de tu base de datos de Supabase donde nada de tu proyecto puede\nalcanzarla.",[464,489,490,493,496,499,502],{},[337,491,492],{},"una copia cifrada según un calendario, guardada en el almacenamiento propio de Reeve en la UE, fuera de tu cuenta de Supabase",[337,494,495],{},"cada copia verificada antes de contar, contando las filas de cada tabla",[337,497,498],{},"una restauración en un clic que guarda lo que hay ahora antes de reemplazarlo",[337,500,501],{},"también tus archivos subidos, en cuanto conectes una credencial de Storage",[337,503,504],{},"todo lo que hace Monitor",[36,506,508],{"id":507},"qué-hacer-ahora","Qué hacer ahora",[510,511,512],"key-takeaways",{},[464,513,514,517,523,526,529],{},[337,515,516],{},"Abre la consola del navegador en tu app publicada, vuelve a pulsar el botón y lee el final de la línea roja de CORS.",[337,518,519,520,522],{},"Si la línea menciona la preflight request, añade al principio de la función una rama para ",[21,521,142],{}," que devuelva las cabeceras CORS.",[337,524,525],{},"Si no la menciona, tu función se ejecutó. Lee sus pestañas Invocations y Logs y comprueba lo que ya hizo antes de que nadie vuelva a pulsar el botón.",[337,527,528],{},"Devuelve las cabeceras CORS en cada respuesta, errores incluidos, e impórtalas desde supabase-js 2.95.0 o posterior para que la lista siga al día.",[337,530,531],{},"Mantén dentro de la función una comprobación de quién llama. La cabecera CORS solo dice a los navegadores quién puede leer la respuesta.",[10,533,534,535,537,538,541,542,546],{},"Cuando el error de tu app cambie a ",[21,536,63],{},", las cabeceras funcionan y la respuesta llega;\n",[47,539,540],{"href":60},"ese artículo"," la lee desde\nahí. La ",[47,543,545],{"href":544},"\u002Fchecklist","lista de seguridad de 10 minutos"," cubre lo demás que una app\nrecién lanzada suele dejar abierto.",{"title":23,"searchDepth":548,"depth":548,"links":549},3,[550,552,553,554,555,556,557],{"id":38,"depth":551,"text":39},2,{"id":97,"depth":551,"text":98},{"id":162,"depth":551,"text":163},{"id":328,"depth":551,"text":329},{"id":403,"depth":551,"text":404},{"id":446,"depth":551,"text":447},{"id":507,"depth":551,"text":508},"Fundamentos de seguridad","\u002Fblog\u002Ffailed-to-send-a-request-to-the-edge-function\u002Fcover-1200x630.png","Una barrera bajada atravesando el paso, con un sobre retenido al otro lado y un contorno amarillo vacío donde debería colgar su etiqueta.","\"Failed to send a request to the Edge Function\": el navegador no recibió una respuesta legible. Su consola dice por qué y si tu función se ejecutó.",false,"md",[565,567,570,573,576,579],{"q":39,"a":566},"Significa que supabase-js pidió una respuesta a tu Supabase Edge Function y no recibió ninguna que pudiera leer. O la petición falló por el camino, por ejemplo porque el dispositivo estaba sin conexión, o el navegador se negó a entregar la respuesta a tu app según sus reglas para peticiones a otras direcciones, llamadas CORS. El navegador oculta el motivo a tu código a propósito y lo escribe en su consola.",{"q":568,"a":569},"¿Por qué mi Edge Function funciona desde una terminal pero no desde mi app?","Porque solo los navegadores aplican CORS. Una terminal, un script u otro servidor envía la petición y lee la respuesta entera. Un navegador comprueba antes que la respuesta diga que tu app puede leerla, y antes de una petición como la que envía supabase-js además pide permiso a la función con una petición OPTIONS aparte. Si falla cualquiera de los dos pasos, tu app recibe este error mientras la misma llamada funciona en cualquier otro sitio.",{"q":571,"a":572},"¿Se ejecutó mi Edge Function si me sale este error?","Lee la línea roja de la consola del navegador. Si menciona la preflight request, el navegador se detuvo en la petición de permiso y tu petición nunca salió. Si no la menciona, la petición pasó, la función hizo su trabajo y solo la respuesta quedó retenida. En ese caso revisa las pestañas Invocations y Logs de la función antes de volver a pulsar el botón, porque una segunda pulsación repite el trabajo.",{"q":574,"a":575},"¿Cómo arreglo un error de CORS en una Supabase Edge Function?","Haz que la función responda primero a la petición OPTIONS, antes de cualquier código que lea el contenido o compruebe a quien llama, y devuelve las cabeceras CORS en cada respuesta, también en las de error. La guía de CORS de Supabase importa esas cabeceras desde supabase-js, versión 2.95.0 o posterior, para que la lista coincida con lo que envía el cliente. Vuelve a desplegar la función después del cambio.",{"q":577,"a":578},"¿Qué es \"Relay Error invoking the Edge Function\"?","Es un tercer mensaje de supabase-js, que aparece cuando el relay de Supabase no pudo llegar a tu función. No es un rechazo de CORS ni una respuesta de tu propio código, así que comprueba primero si el propio Supabase tiene un problema antes de cambiar nada.",{"q":580,"a":581},"¿Es seguro permitir cualquier origen en las cabeceras CORS de mi Edge Function?","Solo si la función comprueba quién la llama. La cabecera CORS dice a los navegadores qué páginas web pueden leer la respuesta; no impide a nadie llamar a la función. Las cabeceras del propio Supabase permiten cualquier origen, lo cual está bien para una función que comprueba el token de quien llama y deja que cualquier web use una que no lo hace. Limitarla a tu dominio tampoco cierra la función a los scripts.","\u002Fblog\u002Ffailed-to-send-a-request-to-the-edge-function\u002Fcard-800x500.png",[584,56,585,586,587,588,589],"failed to send a request to the edge function","error cors supabase edge function","supabase edge function blocked by cors policy","response to preflight request doesn't pass access control check","relay error invoking the edge function","lovable edge function cors",{},true,"\"Failed to send a request to the Edge Function\" en Supabase","\u002Fblog\u002Ffailed-to-send-a-request-to-the-edge-function","2026-11-03",{"title":5,"description":561},"blog\u002Ffailed-to-send-a-request-to-the-edge-function",[598,599,600],"\"Failed to send a request to the Edge Function\" significa que supabase-js no recibió ninguna respuesta que pudiera leer. O la petición falló por el camino, o el navegador retuvo la respuesta, y tu app no se entera de más a propósito.","La consola del navegador tiene el motivo. Una línea que menciona la preflight request significa que tu petición nunca salió. Una línea que no la menciona significa que tu función se ejecutó y solo su respuesta quedó retenida.","La solución es una rama para OPTIONS al principio de la función y las cabeceras CORS en cada respuesta, errores incluidos. Esas cabeceras solo dicen a los navegadores quién puede leer la respuesta; quién puede llamar lo decide una comprobación en tu código.","AQwHthQ3i9FoIwfXG3fT9tySSxo7iXkMpaXVAi2eXiY",[603,609,615,621,627,633,639,640,646,652,658,665,671,677,682,688,694,700,706,712,718,724,730,736,742,748,754,760,766,772,778,784,790,796,802,808,813,819,825,831,836,842,848,854,860,866,871,877,883,889,895,901,907,913,919,925,931,937,943,949,955,961,967,973,979,985,991,997,1003,1009,1015,1021,1027,1033,1039,1045,1051,1056,1062,1068,1074,1080,1086,1092,1097,1103,1109,1115,1120,1126],{"path":604,"title":605,"description":606,"published":607,"category":558,"image":608,"draft":562},"\u002Fblog\u002Fmax-client-connections-reached","Supabase \"max client connections reached\", explicado","Supabase \"max client connections reached\": el pooler no tiene sitio para un cliente más. El puerto de tu cadena de conexión decide el límite.","2026-11-09","\u002Fblog\u002Fmax-client-connections-reached\u002Fcard-800x500.png",{"path":610,"title":611,"description":612,"published":613,"category":558,"image":614,"draft":562},"\u002Fblog\u002Funsupported-provider-provider-is-not-enabled","Error de Supabase: \"Unsupported provider: provider is not enabled\"","\"Unsupported provider: provider is not enabled\": Google está desactivado en tu proyecto Supabase. Cómo activarlo y arreglar redirect_uri_mismatch.","2026-11-08","\u002Fblog\u002Funsupported-provider-provider-is-not-enabled\u002Fcard-800x500.png",{"path":616,"title":617,"description":618,"published":619,"category":558,"image":620,"draft":562},"\u002Fblog\u002Fviolates-foreign-key-constraint","Error de Supabase \"violates foreign key constraint\" al insertar","Supabase \"insert or update on table violates foreign key constraint\": tu fila apunta a una fila que no existe. Cuál, por qué, y cómo arreglarlo.","2026-11-07","\u002Fblog\u002Fviolates-foreign-key-constraint\u002Fcard-800x500.png",{"path":622,"title":623,"description":624,"published":625,"category":558,"image":626,"draft":562},"\u002Fblog\u002Fnull-value-violates-not-null-constraint","Error de Supabase \"null value in column violates not-null constraint\"","Supabase \"null value in column violates not-null constraint\": una columna obligatoria llegó vacía. De dónde vino el null, y cómo arreglarlo.","2026-11-06","\u002Fblog\u002Fnull-value-violates-not-null-constraint\u002Fcard-800x500.png",{"path":628,"title":629,"description":630,"published":631,"category":558,"image":632,"draft":562},"\u002Fblog\u002Fauth-session-missing","Supabase \"Auth session missing!\": adónde fue la sesión","\"Auth session missing!\" es supabase-js sin encontrar ninguna sesión iniciada y sin llegar a preguntar a Supabase. Adónde fue la sesión, y el arreglo.","2026-11-05","\u002Fblog\u002Fauth-session-missing\u002Fcard-800x500.png",{"path":634,"title":635,"description":636,"published":637,"category":558,"image":638,"draft":562},"\u002Fblog\u002Finvalid-claim-missing-sub-claim","Supabase \"invalid claim: missing sub claim\": no hay nadie conectado","\"invalid claim: missing sub claim\": Supabase Auth recibió un token auténtico sin ningún usuario, la clave del proyecto en lugar de una sesión.","2026-11-04","\u002Fblog\u002Finvalid-claim-missing-sub-claim\u002Fcard-800x500.png",{"path":593,"title":5,"description":561,"published":594,"category":558,"image":582,"draft":562},{"path":641,"title":642,"description":643,"published":644,"category":558,"image":645,"draft":562},"\u002Fblog\u002Fsupabase-auth-redirects-to-localhost","Por qué los enlaces de email de Supabase redirigen a localhost:3000","Si Supabase redirige a localhost:3000, la Site URL nunca se cambió o tu dirección falta en Redirect URLs. La solución, y el comodín que debes evitar.","2026-11-02","\u002Fblog\u002Fsupabase-auth-redirects-to-localhost\u002Fcard-800x500.png",{"path":647,"title":648,"description":649,"published":650,"category":558,"image":651,"draft":562},"\u002Fblog\u002Femail-link-is-invalid-or-has-expired","Supabase \"Email link is invalid or has expired\" con un enlace nuevo","Con un enlace enviado hace un minuto, \"Email link is invalid or has expired\" en Supabase suele indicar que un filtro de correo lo usó antes. Cómo arreglarlo.","2026-11-01","\u002Fblog\u002Femail-link-is-invalid-or-has-expired\u002Fcard-800x500.png",{"path":653,"title":654,"description":655,"published":656,"category":558,"image":657,"draft":562},"\u002Fblog\u002Fcanceling-statement-due-to-statement-timeout","Supabase \"canceling statement due to statement timeout\", explicado","Supabase \"canceling statement due to statement timeout\": una consulta superó el límite de su rol, 3 segundos para un visitante. Revisa antes la política RLS.","2026-10-31","\u002Fblog\u002Fcanceling-statement-due-to-statement-timeout\u002Fcard-800x500.png",{"path":659,"title":660,"description":661,"published":662,"category":663,"image":664,"draft":562},"\u002Fblog\u002Fdatabase-error-deleting-user","\"Database error deleting user\" en Supabase, y qué se va con él","\"Database error deleting user\" en Supabase: tu base de datos rechazó el borrado. La causa está en tus logs y el arreglo decide qué más se borra.","2026-10-30","Backups","\u002Fblog\u002Fdatabase-error-deleting-user\u002Fcard-800x500.png",{"path":666,"title":667,"description":668,"published":669,"category":558,"image":670,"draft":562},"\u002Fblog\u002Fstripe-webhook-no-signatures-found","\"No signatures found matching the expected signature\" en Supabase","¿Tu webhook de Stripe en una Edge Function de Supabase dice \"No signatures found matching the expected signature\"? Tiene dos causas. Sigue verificando.","2026-10-29","\u002Fblog\u002Fstripe-webhook-no-signatures-found\u002Fcard-800x500.png",{"path":672,"title":673,"description":674,"published":675,"category":558,"image":676,"draft":562},"\u002Fblog\u002Fsupabase-invalid-login","\"Invalid login credentials\" en Supabase: las tres causas","Supabase dice \"Invalid login credentials\" ante una contraseña errónea, un email desconocido y una cuenta de Google. Cómo saber cuál es.","2026-10-28","\u002Fblog\u002Fsupabase-invalid-login\u002Fcard-800x500.png",{"path":60,"title":678,"description":679,"published":680,"category":558,"image":681,"draft":562},"\"Edge Function returned a non-2xx status code\": el error real","\"Edge Function returned a non-2xx status code\" significa que tu función de Supabase respondió con un error. Aquí ves dónde leer el real.","2026-10-27","\u002Fblog\u002Fedge-function-returned-a-non-2xx-status-code\u002Fcard-800x500.png",{"path":683,"title":684,"description":685,"published":686,"category":558,"image":687,"draft":562},"\u002Fblog\u002Fcertificate-not-trusted","Certificado no confiable: tres causas aparte de la caducidad","¿Certificado no confiable aunque la fecha está bien? Nombra otra dirección, se firmó a sí mismo o a su cadena le falta un eslabón. Cómo saber cuál.","2026-10-26","\u002Fblog\u002Fcertificate-not-trusted\u002Fcard-800x500.png",{"path":689,"title":690,"description":691,"published":692,"category":558,"image":693,"draft":562},"\u002Fblog\u002Finvalid-input-syntax-for-type-uuid","Supabase \"invalid input syntax for type uuid\": lee las comillas","Supabase \"invalid input syntax for type uuid\" significa que tu app envió algo que no es un id. Lee las comillas, frena la consulta, conserva la columna.","2026-10-25","\u002Fblog\u002Finvalid-input-syntax-for-type-uuid\u002Fcard-800x500.png",{"path":695,"title":696,"description":697,"published":698,"category":558,"image":699,"draft":562},"\u002Fblog\u002Fduplicate-key-value-violates-unique-constraint","Supabase \"duplicate key value violates unique constraint\", explicado","Supabase \"duplicate key value violates unique constraint\": el valor ya está ocupado. Encuentra la columna, elige un arreglo y aparta el valor de la pantalla.","2026-10-24","\u002Fblog\u002Fduplicate-key-value-violates-unique-constraint\u002Fcard-800x500.png",{"path":701,"title":702,"description":703,"published":704,"category":558,"image":705,"draft":562},"\u002Fblog\u002Fsupabase-relation-does-not-exist","Supabase \"relation does not exist\" aunque la tabla está ahí","Supabase \"relation does not exist\" o PGRST205: ninguna tabla tiene ese nombre en ese esquema. Cuatro comprobaciones, y lo que abre exponer un esquema.","2026-10-23","\u002Fblog\u002Fsupabase-relation-does-not-exist\u002Fcard-800x500.png",{"path":707,"title":708,"description":709,"published":710,"category":558,"image":711,"draft":562},"\u002Fblog\u002Fsupabase-function-not-found","Supabase PGRST202: \"Could not find the function in the schema cache\"","Supabase \"Could not find the function in the schema cache\" muestra los nombres de argumentos que envió tu app. Compáralos con la función.","2026-10-22","\u002Fblog\u002Fsupabase-function-not-found\u002Fcard-800x500.png",{"path":713,"title":714,"description":715,"published":716,"category":558,"image":717,"draft":562},"\u002Fblog\u002Fsupabase-schema-cache-column-error","Supabase PGRST204: \"Could not find the column in the schema cache\"","Supabase \"Could not find the column in the schema cache\" suele ser un nombre que no coincide. Cuatro comprobaciones y la línea que recarga.","2026-10-21","\u002Fblog\u002Fsupabase-schema-cache-column-error\u002Fcard-800x500.png",{"path":719,"title":720,"description":721,"published":722,"category":558,"image":723,"draft":562},"\u002Fblog\u002Fsupabase-email-rate-limit-exceeded","Supabase \"Email Rate Limit Exceeded\": dos límites, un solo código","Supabase \"Email Rate Limit Exceeded\" limita cada email que envía tu proyecto: 2 por hora con el remitente integrado. Qué cuenta y cómo subirlo.","2026-10-20","\u002Fblog\u002Fsupabase-email-rate-limit-exceeded\u002Fcard-800x500.png",{"path":725,"title":726,"description":727,"published":728,"category":558,"image":729,"draft":562},"\u002Fblog\u002Fsupabase-users-logged-out","Supabase \"Invalid Refresh Token: Refresh Token Not Found\": 3 causas","Supabase \"Invalid Refresh Token: Refresh Token Not Found\" cierra la sesión de tus usuarios. Tres causas, y cómo las separa el log de Auth.","2026-10-19","\u002Fblog\u002Fsupabase-users-logged-out\u002Fcard-800x500.png",{"path":731,"title":732,"description":733,"published":734,"category":558,"image":735,"draft":562},"\u002Fblog\u002Fpermission-denied-for-schema-public","Supabase \"permission denied for schema public\": tres causas","Supabase \"permission denied for schema public\" es un grant que falta en el esquema. Qué lo quitó y la línea que lo devuelve.","2026-10-18","\u002Fblog\u002Fpermission-denied-for-schema-public\u002Fcard-800x500.png",{"path":737,"title":738,"description":739,"published":740,"category":558,"image":741,"draft":562},"\u002Fblog\u002Fsupabase-bucket-not-found","Supabase \"Bucket not found\": las tres cosas que significa","Supabase \"Bucket not found\" es una de tres cosas: un enlace público a un bucket privado, un nombre que no coincide exactamente o el proyecto equivocado.","2026-10-17","\u002Fblog\u002Fsupabase-bucket-not-found\u002Fcard-800x500.png",{"path":743,"title":744,"description":745,"published":746,"category":558,"image":747,"draft":562},"\u002Fblog\u002Fsupabase-invalid-jwt","Supabase invalid JWT: ¿qué clave dejó de funcionar?","Un error Supabase invalid JWT es PGRST301: un token firmado no pasó la verificación. En un proyecto nuevo se trata de la sesión, no de la clave de API.","2026-10-16","\u002Fblog\u002Fsupabase-invalid-jwt\u002Fcard-800x500.png",{"path":749,"title":750,"description":751,"published":752,"category":558,"image":753,"draft":562},"\u002Fblog\u002Fsupabase-single-row-error","Supabase \"multiple (or no) rows returned\" casi siempre es ninguna","Supabase devuelve PGRST116, \"JSON object requested, multiple (or no) rows returned\", cuando tu consulta no encontró nada. Una fila oculta parece ausente.","2026-10-15","\u002Fblog\u002Fsupabase-single-row-error\u002Fcard-800x500.png",{"path":755,"title":756,"description":757,"published":758,"category":558,"image":759,"draft":562},"\u002Fblog\u002Fdatabase-error-saving-new-user","\"Database error saving new user\" en Supabase: dónde mirar","\"Database error saving new user\" significa que algo unido a tu registro de Supabase falló y se deshizo entero. El error real está en tus logs.","2026-10-14","\u002Fblog\u002Fdatabase-error-saving-new-user\u002Fcard-800x500.png",{"path":761,"title":762,"description":763,"published":764,"category":558,"image":765,"draft":562},"\u002Fblog\u002Fwhat-is-supabase","¿Qué es Supabase? La base de datos que eligió tu builder","Qué es Supabase: la base de datos, el login y el almacenamiento de archivos de tu app. Supabase lleva los servidores; las reglas sobre tus filas son tuyas.","2026-10-13","\u002Fblog\u002Fwhat-is-supabase\u002Fcard-800x500.png",{"path":767,"title":768,"description":769,"published":770,"category":558,"image":771,"draft":562},"\u002Fblog\u002Faws-access-key-exposed-in-frontend","Una clave AWS expuesta en tu frontend no es como una clave OpenAI","Una clave de acceso de AWS expuesta en tu frontend lee y escribe tu almacenamiento. La solución suele ser una presigned URL, no una clave nueva.","2026-10-12","\u002Fblog\u002Faws-access-key-exposed-in-frontend\u002Fcard-800x500.png",{"path":773,"title":774,"description":775,"published":776,"category":558,"image":777,"draft":562},"\u002Fblog\u002Fstorage-upload-violates-row-level-security","\"Row-level security policy for table objects\" al subir","\"New row violates row-level security policy for table objects\" significa que tu subida no tiene regla insert. Hacer público el bucket no añade ninguna.","2026-10-11","\u002Fblog\u002Fstorage-upload-violates-row-level-security\u002Fcard-800x500.png",{"path":779,"title":780,"description":781,"published":782,"category":558,"image":783,"draft":562},"\u002Fblog\u002Fbase44-security-scan","Escaneo de seguridad de Base44: lo que solo él ve","El escaneo de seguridad de Base44 revisa siete tipos de problema desde dentro. Esta es la mitad que lee y nadie más puede leer, y la mitad que nunca mira.","2026-10-10","\u002Fblog\u002Fbase44-security-scan\u002Fcard-800x500.png",{"path":785,"title":786,"description":787,"published":788,"category":558,"image":789,"draft":562},"\u002Fblog\u002Fapi-key-leaked-what-to-do","Tu clave API se filtró. El orden en el que hay que actuar","Una clave API se filtró y no sabes por dónde empezar. No todas las claves de tu frontend lo son, y el orden importa más que la velocidad.","2026-10-09","\u002Fblog\u002Fapi-key-leaked-what-to-do\u002Fcard-800x500.png",{"path":791,"title":792,"description":793,"published":794,"category":558,"image":795,"draft":562},"\u002Fblog\u002Finfinite-recursion-in-policy-for-relation","\"Infinite recursion detected in policy\" sin desactivar RLS","\"Infinite recursion detected in policy for relation\" significa que tu policy preguntó a la tabla que protege. Así se rompe el círculo.","2026-10-08","\u002Fblog\u002Finfinite-recursion-in-policy-for-relation\u002Fcard-800x500.png",{"path":797,"title":798,"description":799,"published":800,"category":558,"image":801,"draft":562},"\u002Fblog\u002Fno-api-key-found-in-request","\"No API key found in request\" en Supabase, y el arreglo equivocado","\"No API key found in request\" significa que tu petición llegó a Supabase sin clave. Casi todas las respuestas apuntan a las reglas de tu base de datos.","2026-10-07","\u002Fblog\u002Fno-api-key-found-in-request\u002Fcard-800x500.png",{"path":803,"title":804,"description":805,"published":806,"category":558,"image":807,"draft":562},"\u002Fblog\u002Fis-base44-safe","¿Es seguro Base44? Lo que vimos en 5.442 apps Base44","¿Es seguro Base44? Pasamos nueve comprobaciones a 5.442 apps Base44 en producción. Casi todas sacaron B por motivos que su dueño nunca eligió.","2026-10-06","\u002Fblog\u002Fis-base44-safe\u002Fcard-800x500.png",{"path":387,"title":809,"description":810,"published":811,"category":558,"image":812,"draft":562},"Ocultar una clave API: muévela a una Edge Function de Supabase","Ocultar una clave API significa sacarla del navegador, y una Edge Function de Supabase es el sitio más pequeño donde ponerla. Dos pasos importan más.","2026-10-05","\u002Fblog\u002Fmove-a-secret-to-an-edge-function\u002Fcard-800x500.png",{"path":814,"title":815,"description":816,"published":817,"category":558,"image":818,"draft":562},"\u002Fblog\u002Fenv-file-exposed-on-your-server","¿Tu archivo .env está expuesto? Las doce rutas a revisar","¿Tu archivo .env está expuesto en tu propio servidor web? Doce direcciones te lo dicen, y un acierto significa que todo lo que contiene ya es público.","2026-10-04","\u002Fblog\u002Fenv-file-exposed-on-your-server\u002Fcard-800x500.png",{"path":820,"title":821,"description":822,"published":823,"category":558,"image":824,"draft":562},"\u002Fblog\u002Fis-v0-safe","Seguridad de v0: las 1.790 aplicaciones escaneadas sacaron una A","Seguridad de v0, medida en 1.790 aplicaciones v0 en vivo: todas sacaron A. Solo 17 nombraban una base de datos, y eso es casi todo lo que mide la A.","2026-10-03","\u002Fblog\u002Fis-v0-safe\u002Fcard-800x500.png",{"path":826,"title":827,"description":828,"published":829,"category":558,"image":830,"draft":562},"\u002Fblog\u002Fis-bolt-safe","¿Es seguro Bolt? Lo que vimos en 1.123 aplicaciones Bolt","¿Es seguro Bolt? Pasamos 1.123 aplicaciones Bolt en vivo por nueve comprobaciones. El hosting salió limpio. Los hallazgos eran claves y tablas abiertas dentro.","2026-10-02","\u002Fblog\u002Fis-bolt-safe\u002Fcard-800x500.png",{"path":92,"title":832,"description":833,"published":834,"category":558,"image":835,"draft":562},"¿Supabase está caído o es tu app? Cómo distinguirlo","¿Supabase está caído o es tu app? Un proyecto pausado, una base de datos llena y un fallo tuyo parecen lo mismo. Cuatro comprobaciones los separan.","2026-10-01","\u002Fblog\u002Fis-supabase-down-or-your-app\u002Fcard-800x500.png",{"path":837,"title":838,"description":839,"published":840,"category":558,"image":841,"draft":562},"\u002Fblog\u002Fdoes-supabase-encrypt-my-data","¿Supabase cifra mis datos? Sí. Esto es lo que detiene","¿Supabase cifra los datos? Sí: AES-256 en reposo, TLS en tránsito, SOC 2 e ISO 27001. Qué cubre cada uno, y la fuga que ninguno de ellos detiene.","2026-09-30","\u002Fblog\u002Fdoes-supabase-encrypt-my-data\u002Fcard-800x500.png",{"path":843,"title":844,"description":845,"published":846,"category":558,"image":847,"draft":562},"\u002Fblog\u002Fsupabase-free-plan-limits","¿Cuánto cuesta Supabase? Los límites del plan gratuito","Los límites del plan gratuito de Supabase: 500 MB de base de datos, 5 GB de egress y ningún backup. Qué pasa en cada uno y qué cambia Pro por $25.","2026-09-29","\u002Fblog\u002Fsupabase-free-plan-limits\u002Fcard-800x500.png",{"path":849,"title":850,"description":851,"published":852,"category":663,"image":853,"draft":562},"\u002Fblog\u002Ftest-your-supabase-backup","Prueba tu backup de Supabase antes del día que lo necesites","Cómo probar tu backup de Supabase: restaurarlo en un proyecto de prueba, contar filas, iniciar sesión y buscar la línea que le falta a un archivo cortado.","2026-09-28","\u002Fblog\u002Ftest-your-supabase-backup\u002Fcard-800x500.png",{"path":855,"title":856,"description":857,"published":858,"category":663,"image":859,"draft":562},"\u002Fblog\u002Fsupabase-backup-github-action","Backup gratis de Supabase con una GitHub Action, y la trampa","Un backup de Supabase con GitHub Action es gratis y sirve a muchas apps. El workflow, el connection string correcto y el egress de cada ejecución.","2026-09-27","\u002Fblog\u002Fsupabase-backup-github-action\u002Fcard-800x500.png",{"path":861,"title":862,"description":863,"published":864,"category":663,"image":865,"draft":562},"\u002Fblog\u002Fdownload-your-supabase-backup","Por qué no puedes descargar tu backup de Supabase","No puedes descargar tu backup de Supabase en un proyecto actual: la copia diaria es una instantánea física. Cómo comprobarlo y tener tu propia copia.","2026-09-26","\u002Fblog\u002Fdownload-your-supabase-backup\u002Fcard-800x500.png",{"path":867,"title":868,"description":869,"published":864,"category":558,"image":870,"draft":562},"\u002Fblog\u002Fsupabase-permission-denied-for-table","Supabase \"permission denied for table\": el grant que falta","Desde el 30 de octubre, una tabla nueva de Supabase responde \"permission denied for table\" hasta que le das acceso. El grant del email es la mitad del arreglo.","\u002Fblog\u002Fsupabase-permission-denied-for-table\u002Fcard-800x500.png",{"path":872,"title":873,"description":874,"published":875,"category":558,"image":876,"draft":562},"\u002Fblog\u002Fopen-api-endpoint-exposed","¿Un endpoint de API abierto es un problema? Mira el JSON","Tu escaneo señaló un endpoint de API abierto. Que importe depende de lo que devolvió, y la mayoría de los que encontramos eran de la plataforma.","2026-09-25","\u002Fblog\u002Fopen-api-endpoint-exposed\u002Fcard-800x500.png",{"path":878,"title":879,"description":880,"published":881,"category":558,"image":882,"draft":562},"\u002Fblog\u002Flovable-security-scan","Escaneo de seguridad de Lovable: lo que no puede probar","Escaneo de seguridad de Lovable: qué revisan el Quick scan y el Deep scan, cuándo corre cada uno, y lo único que ningún escaneo desde dentro puede probar.","2026-09-24","\u002Fblog\u002Flovable-security-scan\u002Fcard-800x500.png",{"path":884,"title":885,"description":886,"published":887,"category":663,"image":888,"draft":562},"\u002Fblog\u002Fsupabase-backup-auth-users","Por qué tu dump de Supabase no tiene usuarios dentro","Ejecuta supabase db dump por sí solo y obtienes la forma de tu base de datos y ninguna de sus filas, sin el esquema auth donde viven tus usuarios.","2026-09-23","\u002Fblog\u002Fsupabase-backup-auth-users\u002Fcard-800x500.png",{"path":890,"title":891,"description":892,"published":893,"category":558,"image":894,"draft":562},"\u002Fblog\u002Fdomain-and-certificate-expiry","Dominio caducado, web caída: qué pasa a partir de ahora","Tu dominio caducó y tu web está caída. Aquí tienes el reloj que acaba de arrancar, por qué el certificado es el menor problema, y cómo comprobar ambos.","2026-09-22","\u002Fblog\u002Fdomain-and-certificate-expiry\u002Fcard-800x500.png",{"path":896,"title":897,"description":898,"published":899,"category":558,"image":900,"draft":562},"\u002Fblog\u002Fis-lovable-safe","¿Es seguro Lovable? Lo que vimos en 18.554 aplicaciones Lovable","¿Es seguro Lovable? Pasamos 18.554 aplicaciones Lovable por nueve comprobaciones. La plataforma fue la más limpia de cinco. Los hallazgos estaban dentro.","2026-09-21","\u002Fblog\u002Fis-lovable-safe\u002Fcard-800x500.png",{"path":902,"title":903,"description":904,"published":905,"category":558,"image":906,"draft":562},"\u002Fblog\u002Fvibe-coded-app-security-checklist","La lista de seguridad del vibe coding, en nueve comprobaciones","Una lista de seguridad del vibe coding con nueve puntos, cada uno verificable en tu app desde fuera por cualquiera, y cada uno con una prueba de una línea.","2026-09-20","\u002Fblog\u002Fvibe-coded-app-security-checklist\u002Fcard-800x500.png",{"path":908,"title":909,"description":910,"published":911,"category":558,"image":912,"draft":562},"\u002Fblog\u002Fstripe-secret-key-in-frontend","Una clave secreta de Stripe en tu frontend puede mover dinero","Una clave secreta de Stripe expuesta en tu frontend puede reembolsar, cobrar y leer cada ficha de cliente que tienes. Tu clave pk_live_ sí debe estar ahí.","2026-09-19","\u002Fblog\u002Fstripe-secret-key-in-frontend\u002Fcard-800x500.png",{"path":914,"title":915,"description":916,"published":917,"category":558,"image":918,"draft":562},"\u002Fblog\u002Fvite-and-next-public-env-vars","Variables de entorno de Vite expuestas: el prefijo dice publica esto","Variables de entorno de Vite expuestas en tu app: hicieron lo que pedía el prefijo. VITE_ y NEXT_PUBLIC_ significan publica esto, y la IA nunca supo el coste.","2026-09-18","\u002Fblog\u002Fvite-and-next-public-env-vars\u002Fcard-800x500.png",{"path":920,"title":921,"description":922,"published":923,"category":558,"image":924,"draft":562},"\u002Fblog\u002Fbase44-source-maps","Seguridad en Base44: qué marca un escaneo y qué te toca arreglar a ti","Seguridad en Base44 en 5.438 apps escaneadas: tres hallazgos en casi todas son de la plataforma, el source map es la insignia de Base44 y lo tuyo es poco.","2026-09-17","\u002Fblog\u002Fbase44-source-maps\u002Fcard-800x500.png",{"path":926,"title":927,"description":928,"published":929,"category":558,"image":930,"draft":562},"\u002Fblog\u002Fis-cursor-ai-safe","¿Es seguro Cursor AI? El editor, el código y la app que publicaste","¿Es seguro Cursor AI? Tres preguntas en una búsqueda: qué guarda Cursor, en qué falla el código que escribe y si la app que publicaste está abierta.","2026-09-16","\u002Fblog\u002Fis-cursor-ai-safe\u002Fcard-800x500.png",{"path":932,"title":933,"description":934,"published":935,"category":558,"image":936,"draft":562},"\u002Fblog\u002Fis-replit-safe","¿Es seguro Replit? Lo que vimos en 3.042 aplicaciones Replit en vivo","¿Es seguro Replit? Pasamos 3.042 aplicaciones Replit en vivo por nueve comprobaciones externas. Los hallazgos estaban en la aplicación publicada, no en el host.","2026-09-15","\u002Fblog\u002Fis-replit-safe\u002Fcard-800x500.png",{"path":938,"title":939,"description":940,"published":941,"category":663,"image":942,"draft":562},"\u002Fblog\u002Fsupabase-storage-backup","Supabase storage backup: tu copia de la base no tiene archivos","Un Supabase storage backup es una tarea aparte. Las copias de la base guardan la lista de tus archivos y ninguno de ellos, así que cada upload queda roto.","2026-09-14","\u002Fblog\u002Fsupabase-storage-backup\u002Fcard-800x500.png",{"path":944,"title":945,"description":946,"published":947,"category":663,"image":948,"draft":562},"\u002Fblog\u002Fsupabase-point-in-time-recovery","Point-in-time recovery de Supabase: precio y límites","El point-in-time recovery de Supabase rebobina tu base de datos a cualquier segundo de la última semana. Cuesta $100 al mes sobre Pro y solo cubre la base.","2026-09-13","\u002Fblog\u002Fsupabase-point-in-time-recovery\u002Fcard-800x500.png",{"path":950,"title":951,"description":952,"published":953,"category":663,"image":954,"draft":562},"\u002Fblog\u002Fsupabase-project-paused-recover","¿Proyecto de Supabase pausado? Tus datos siguen ahí","Supabase pausó tu proyecto tras una semana sin actividad. No se ha borrado nada, Restaurar está junto al nombre y tienes un año antes de que eso cambie.","2026-09-12","\u002Fblog\u002Fsupabase-project-paused-recover\u002Fcard-800x500.png",{"path":956,"title":957,"description":958,"published":959,"category":558,"image":960,"draft":562},"\u002Fblog\u002Fsafest-ai-app-builder","¿Qué builder de apps con IA es más seguro? 30.998 apps escaneadas","¿Qué builder de apps con IA es más seguro? Escaneamos 30.998 apps activas de Lovable, Base44, Replit, v0 y Bolt. El builder no es lo que decide tu nota.","2026-09-11","\u002Fblog\u002Fsafest-ai-app-builder\u002Fcard-800x500.png",{"path":962,"title":963,"description":964,"published":965,"category":558,"image":966,"draft":562},"\u002Fblog\u002Fenable-rls-on-every-supabase-table","Activa Row Level Security en cada tabla de Supabase y compruébalo","Activar Row Level Security en Supabase sin política cierra la tabla del todo. Una política sin el interruptor no hace nada. Aquí tienes el SQL y la prueba.","2026-09-10","\u002Fblog\u002Fenable-rls-on-every-supabase-table\u002Fcard-800x500.png",{"path":968,"title":969,"description":970,"published":971,"category":558,"image":972,"draft":562},"\u002Fblog\u002Fsupabase-rls-disabled-in-public","Supabase \"RLS disabled in public\": lo que el aviso no ve","Supabase marca \"RLS disabled in public\" como error. No dice nada de la política de lectura que deja tu tabla igual de abierta a cualquiera.","2026-09-09","\u002Fblog\u002Fsupabase-rls-disabled-in-public\u002Fcard-800x500.png",{"path":974,"title":975,"description":976,"published":977,"category":558,"image":978,"draft":562},"\u002Fblog\u002Frotate-supabase-service-role-key","Cómo rotar una clave service_role de Supabase filtrada","Supabase dice que primero cierres la fuga. Otras guías dicen que rotes ya. Cuál es lo correcto depende de dónde se filtró tu clave service_role.","2026-09-08","\u002Fblog\u002Frotate-supabase-service-role-key\u002Fcard-800x500.png",{"path":980,"title":981,"description":982,"published":983,"category":558,"image":984,"draft":562},"\u002Fblog\u002Fvibe-coding-security-scanners-compared","Escáneres de seguridad vibe coding comparados, el nuestro incluido","El mejor escáner de seguridad para vibe coding depende de tres preguntas que ninguna lista de funciones responde. Ocho herramientas, con precios.","2026-09-07","\u002Fblog\u002Fvibe-coding-security-scanners-compared\u002Fcard-800x500.png",{"path":986,"title":987,"description":988,"published":989,"category":558,"image":990,"draft":562},"\u002Fblog\u002Fsupabase-security-checker","Supabase security checker: haz tú mismo las cinco comprobaciones","Un Supabase security checker lee tu app publicada en lugar de la configuración de tu proyecto. Estas son las cinco comprobaciones y cómo hacer cada una.","2026-09-06","\u002Fblog\u002Fsupabase-security-checker\u002Fcard-800x500.png",{"path":992,"title":993,"description":994,"published":995,"category":558,"image":996,"draft":562},"\u002Fblog\u002Fvibe-coding-security-scanner","Escáner de seguridad para vibe coding: qué se le escapa","Un escáner de seguridad para vibe coding lee tu app en vivo desde fuera. Qué cubre eso, las cuatro cosas que no puede ver y cómo leer el resultado.","2026-09-05","\u002Fblog\u002Fvibe-coding-security-scanner\u002Fcard-800x500.png",{"path":998,"title":999,"description":1000,"published":1001,"category":558,"image":1002,"draft":562},"\u002Fblog\u002Fmissing-security-headers","Faltan cabeceras de seguridad: cuándo importa de verdad","Que falten cabeceras de seguridad es el hallazgo que más imprime nuestro escáner. Contra qué protegen y cuándo son la línea menos urgente de tu informe.","2026-09-04","\u002Fblog\u002Fmissing-security-headers\u002Fcard-800x500.png",{"path":1004,"title":1005,"description":1006,"published":1007,"category":558,"image":1008,"draft":562},"\u002Fblog\u002Fopenai-api-key-exposed-in-frontend","Tu clave de API de OpenAI está expuesta en tu frontend. Rótala.","Una clave de API de OpenAI expuesta en tu frontend no se puede atar a un dominio. Rótala hoy, mueve la llamada a tu servidor y limita el gasto.","2026-09-03","\u002Fblog\u002Fopenai-api-key-exposed-in-frontend\u002Fcard-800x500.png",{"path":1010,"title":1011,"description":1012,"published":1013,"category":558,"image":1014,"draft":562},"\u002Fblog\u002Fwhat-secrets-leak-from-vibe-coded-apps","Clave de API expuesta en el frontend: qué llevan 30.998 apps","Una clave de API expuesta en tu frontend suele ser una clave de Google Maps. Escaneamos 30.998 apps vibe-coded y contamos qué secretos se filtran de verdad.","2026-09-02","\u002Fblog\u002Fwhat-secrets-leak-from-vibe-coded-apps\u002Fcard-800x500.png",{"path":1016,"title":1017,"description":1018,"published":1019,"category":663,"image":1020,"draft":562},"\u002Fblog\u002Fsupabase-backup-tools-compared","Herramientas de backup de Supabase comparadas, la nuestra incluida","Cuatro tipos de herramienta de backup de Supabase, qué copia cada una y cuándo una GitHub Action gratuita le gana a pagar a cualquiera, nosotros incluidos.","2026-09-01","\u002Fblog\u002Fsupabase-backup-tools-compared\u002Fcard-800x500.png",{"path":1022,"title":1023,"description":1024,"published":1025,"category":558,"image":1026,"draft":562},"\u002Fblog\u002Freplit-secrets-explained","Cómo usar secrets en Replit, y qué se publica de todos modos","Cómo usar secrets en Replit: añadir uno, leerlo y arreglar las dos causas del undefined. Y las claves que la herramienta Secrets no puede mantener privadas.","2026-08-31","\u002Fblog\u002Freplit-secrets-explained\u002Fcard-800x500.png",{"path":1028,"title":1029,"description":1030,"published":1031,"category":663,"image":1032,"draft":562},"\u002Fblog\u002Fback-up-supabase-free-tier","Backups del plan gratuito de Supabase: cómo hacer uno sin terminal","No hay backups en el plan gratuito de Supabase: la copia tienes que hacerla tú. Cómo sacarla desde el panel y qué deja fuera la vía CSV.","2026-08-30","\u002Fblog\u002Fback-up-supabase-free-tier\u002Fcard-800x500.png",{"path":1034,"title":1035,"description":1036,"published":1037,"category":558,"image":1038,"draft":562},"\u002Fblog\u002Fis-supabase-secure","¿Supabase es seguro? Sí. Tu proyecto es otra pregunta","¿Supabase es seguro? La plataforma está auditada, cifrada y sometida a pentests. Su propia documentación dice dónde termina eso y empiezan tus ajustes.","2026-08-29","\u002Fblog\u002Fis-supabase-secure\u002Fcard-800x500.png",{"path":1040,"title":1041,"description":1042,"published":1043,"category":558,"image":1044,"draft":562},"\u002Fblog\u002Fwhere-to-find-supabase-api-keys","Dónde están tus claves API de Supabase: anon, service_role y URL","La URL del proyecto, la clave anon y la clave service_role están en una sola página del panel. Aquí tienes dónde está, y cuál de los cuatro va en tu app.","2026-08-28","\u002Fblog\u002Fwhere-to-find-supabase-api-keys\u002Fcard-800x500.png",{"path":1046,"title":1047,"description":1048,"published":1049,"category":558,"image":1050,"draft":562},"\u002Fblog\u002Fnew-row-violates-row-level-security-policy","New row violates row-level security policy en Supabase. ¿Y ahora?","\"New row violates row-level security policy\" significa que Supabase rechazó una escritura. El arreglo rápido vuelve a abrir la tabla para cualquiera.","2026-08-27","\u002Fblog\u002Fnew-row-violates-row-level-security-policy\u002Fcard-800x500.png",{"path":423,"title":1052,"description":1053,"published":1054,"category":558,"image":1055,"draft":562},"¿Es un comodín CORS un riesgo de seguridad? Casi nunca.","¿Es un comodín CORS un riesgo de seguridad? Casi siempre es el ajuste por defecto de tu builder y no entrega nada que tu servidor no diera ya a cualquiera.","2026-08-26","\u002Fblog\u002Fcors-wildcard-security-risk\u002Fcard-800x500.png",{"path":1057,"title":1058,"description":1059,"published":1060,"category":663,"image":1061,"draft":562},"\u002Fblog\u002Fsupabase-branching-is-not-a-backup","Supabase branching no es un backup. Solo va hacia adelante.","Por qué el branching de Supabase no es un backup: una rama arranca sin tus datos y un merge solo mueve el esquema. Para qué sirve y qué usar en su lugar.","2026-08-25","\u002Fblog\u002Fsupabase-branching-is-not-a-backup\u002Fcard-800x500.png",{"path":1063,"title":1064,"description":1065,"published":1066,"category":663,"image":1067,"draft":562},"\u002Fblog\u002Fhow-to-restore-a-supabase-backup","Cómo restaurar un backup de Supabase, y qué se rompe después","Cómo restaurar un backup de Supabase desde el panel o desde un archivo de volcado, qué reemplaza la restauración y por qué tu app puede seguir rota al terminar.","2026-08-24","\u002Fblog\u002Fhow-to-restore-a-supabase-backup\u002Fcard-800x500.png",{"path":1069,"title":1070,"description":1071,"published":1072,"category":558,"image":1073,"draft":562},"\u002Fblog\u002Fsupabase-storage-bucket-public","Tu bucket de Supabase Storage es público. ¿Es un problema?","Un bucket público de Supabase Storage significa que quien tenga la URL de un archivo puede abrirlo. No significa que alguien pueda listar lo que hay dentro.","2026-08-23","\u002Fblog\u002Fsupabase-storage-bucket-public\u002Fcard-800x500.png",{"path":1075,"title":1076,"description":1077,"published":1078,"category":558,"image":1079,"draft":562},"\u002Fblog\u002Fgoogle-api-key-exposed-in-frontend","¿Es un problema una clave de API de Google en tu frontend?","Una clave de API de Google en tu frontend es la clave que más encuentra nuestro escáner, y casi siempre no pasa nada. Un ajuste gratuito decide cuál es cuál.","2026-08-22","\u002Fblog\u002Fgoogle-api-key-exposed-in-frontend\u002Fcard-800x500.png",{"path":1081,"title":1082,"description":1083,"published":1084,"category":663,"image":1085,"draft":562},"\u002Fblog\u002Fai-agent-deleted-my-database","Un agente de IA borró mis datos de Supabase. ¿Qué puedo recuperar?","Un agente de IA borró datos de tu base. Lo que puedes recuperar ya estaba decidido antes de que se ejecutara, y los próximos minutos deciden el resto.","2026-08-21","\u002Fblog\u002Fai-agent-deleted-my-database\u002Fcard-800x500.png",{"path":1087,"title":1088,"description":1089,"published":1090,"category":558,"image":1091,"draft":562},"\u002Fblog\u002Fcan-anyone-read-your-supabase-database","¿Puede cualquiera leer tu base de datos Supabase? 3.680 apps","¿Puede cualquiera leer tu base de datos Supabase sin iniciar sesión? Escaneamos 30.998 apps activas hechas con builders de IA y medimos cuántas dicen que sí.","2026-08-18","\u002Fblog\u002Fcan-anyone-read-your-supabase-database\u002Fcard-800x500.png",{"path":1093,"title":1094,"description":1095,"published":1090,"category":558,"image":1096,"draft":562},"\u002Fblog\u002Fsource-maps-exposed-in-production","Source maps expuestos: tu app está publicando su código original","Un source map expuesto deja que cualquiera lea el código original de tu app, comentarios incluidos. La comprobación de 30 segundos y lo que de verdad importa.","\u002Fblog\u002Fsource-maps-exposed-in-production\u002Fcard-800x500.png",{"path":1098,"title":1099,"description":1100,"published":1101,"category":558,"image":1102,"draft":562},"\u002Fblog\u002Fsupabase-new-api-keys","Supabase publishable key: ¿es la misma que la anon key?","Supabase sustituyó anon y service_role por claves publicables y secretas. ¿Cuál pertenece a tu app y cuál no debe estar nunca?","2026-08-12","\u002Fblog\u002Fsupabase-new-api-keys\u002Fcard-800x500.png",{"path":1104,"title":1105,"description":1106,"published":1107,"category":663,"image":1108,"draft":562},"\u002Fblog\u002Fdoes-supabase-back-up-my-database","¿Supabase respalda mi base de datos? Depende de tu plan.","¿Supabase respalda tu base de datos? A diario en los planes de pago, y nada en el gratuito. Cómo saber cuál tienes y qué es lo que esa copia no cubre.","2026-08-11","\u002Fblog\u002Fdoes-supabase-back-up-my-database\u002Fcard-800x500.png",{"path":1110,"title":1111,"description":1112,"published":1113,"category":558,"image":1114,"draft":562},"\u002Fblog\u002Fsupabase-rls-on-but-table-still-public","Supabase Row Level Security está activado. Tu tabla sigue pública.","Activar Supabase Row Level Security no protege una tabla. Lo hacen tus políticas, y la que arregló tu app rota puede dejar entrar a cualquiera.","2026-08-10","\u002Fblog\u002Fsupabase-rls-on-but-table-still-public\u002Fcard-800x500.png",{"path":1116,"title":1117,"description":1118,"published":1113,"category":663,"image":1119,"draft":562},"\u002Fblog\u002Fversion-history-is-not-a-backup","El historial de versiones no es un backup. No recupera una tabla.","Lovable y Bolt guardan un historial de versiones de tu código. Tu base de datos es un servicio aparte, y volver atrás no te devuelve tus datos.","\u002Fblog\u002Fversion-history-is-not-a-backup\u002Fcard-800x500.png",{"path":1121,"title":1122,"description":1123,"published":1124,"category":663,"image":1125,"draft":562},"\u002Fblog\u002Fthree-ways-to-back-up-a-supabase-database","Tres formas de respaldar una base Supabase, y qué falta en cada una","El panel, pg_dump y un servicio gestionado. Qué guarda de verdad cada uno, qué deja fuera sin avisar y cuál sobrevive a perder la cuenta.","2026-08-09","\u002Fblog\u002Fthree-ways-to-back-up-a-supabase-database\u002Fcard-800x500.png",{"path":1127,"title":1128,"description":1129,"published":1124,"category":558,"image":1130,"draft":562},"\u002Fblog\u002Fwhich-api-keys-are-safe-in-your-frontend","Qué claves de API son seguras en el navegador y cuáles no","Tu clave anon de Supabase está pensada para ser pública. Tu clave service_role no, y se salta todas las reglas que pongas. Cómo distinguirlas.","\u002Fblog\u002Fwhich-api-keys-are-safe-in-your-frontend\u002Fcard-800x500.png",1794213644912]