[{"data":1,"prerenderedAt":1158},["ShallowReactive",2],{"blog-es-auth-session-missing":3,"blog-index-es":632},{"id":4,"title":5,"body":6,"category":590,"cover":591,"coverAlt":592,"description":593,"draft":594,"extension":595,"faq":596,"image":611,"keywords":612,"meta":620,"navigation":621,"ogTitle":622,"path":623,"published":624,"seo":625,"stem":626,"tldr":627,"updated":624,"__hash__":631},"blog_es\u002Fblog\u002Fauth-session-missing.md","Supabase \"Auth session missing!\": adónde fue la sesión",{"type":7,"value":8,"toc":578},"minimark",[9,13,24,27,43,48,51,54,72,75,81,85,90,108,116,123,150,153,159,170,174,177,186,212,237,246,258,263,267,270,276,301,307,324,340,344,347,356,366,372,375,381,387,405,415,419,422,439,457,464,468,474,483,497,507,524,528,565],[10,11,12],"p",{},"Tu aplicación de Lovable o Bolt inicia la sesión de la gente con Supabase. Una\nmañana abres la consola del navegador y encuentras esto en rojo, en una página\ndonde nada parece ir mal:",[14,15,20],"pre",{"className":16,"code":18,"language":19},[17],"language-text","AuthSessionMissingError: Auth session missing!\n","text",[21,22,18],"code",{"__ignoreMap":23},"",[10,25,26],{},"O una clienta te escribe para decirte que la página \"Elige una contraseña nueva\"\nla rechaza con esas mismas palabras, cada vez.",[10,28,29,30,33,34,37,38,42],{},"Busca \"auth session missing supabase\" y encontrarás hilos llenos de arreglos:\ncambiar ",[21,31,32],{},"getUser()"," por ",[21,35,36],{},"getSession()",", bajar de versión la biblioteca, añadir\nuna espera, reiniciar el proyecto. Dan por hecho que algo se rompió entre tu\naplicación y Supabase. ",[39,40,41],"strong",{},"No se hizo ninguna petición."," El error se escribe\ndentro de tu aplicación, lo escribe la biblioteca de Supabase, antes de que nada\nsalga del navegador.",[44,45,47],"h2",{"id":46},"qué-significa-auth-session-missing-en-supabase","¿Qué significa \"Auth session missing!\" en Supabase?",[10,49,50],{},"A supabase-js le preguntaron quién había iniciado sesión, buscó una sesión, no\nencontró ninguna y respondió por su cuenta.",[10,52,53],{},"Una sesión es lo que Supabase Auth le da a una persona cuando inicia sesión: un\npar de tokens que dicen quién es, guardados por el navegador para que la\nsiguiente carga de página la siga reconociendo. supabase-js es la biblioteca con\nla que tu aplicación habla con Supabase, y cada llamada que necesita a una\npersona empieza por sacar esa sesión.",[10,55,56,57,60,61,64,65,68,69,71],{},"El mensaje está escrito en el propio supabase-js, en el archivo ",[21,58,59],{},"errors.ts"," de la\nbiblioteca de autenticación: un ",[21,62,63],{},"AuthSessionMissingError"," con el texto\n",[21,66,67],{},"Auth session missing!"," y el estado 400. Ese 400 es una etiqueta que se pone la\npropia biblioteca. Ningún servidor respondió con él, y los registros de Supabase\nno mostrarán nada, porque ",[21,70,32],{}," busca una sesión antes de hacer cualquier\notra cosa. Un comentario en su código lo dice: devuelve un error si no hay ni\ntoken de acceso ni una cabecera Authorization propia.",[10,73,74],{},"Imagina una recepción que sabe decirte qué visitante es cada uno. A cada persona\nque inicia sesión le dan una credencial en la puerta, y supabase-js guarda esa\ncredencial en el bolsillo del navegador. Cuando tu código pregunta \"¿quién es\neste?\", supabase-js mira primero en el bolsillo. Si hay una credencial, la lleva\na recepción y vuelve con un nombre. Si el bolsillo está vacío, ni siquiera va a\nrecepción. Vuelve en el acto y te dice que no hay credencial.",[76,77],"diagram",{"alt":78,"caption":79,"src":80},"Dos carriles salen de una ventana de navegador. En el de arriba una credencial llega hasta una recepción, que responde con una persona y una marca verde. En el de abajo la funda de la credencial está vacía y marcada en ámbar, y el carril se detiene en una cruz con 400 al lado, mucho antes de la recepción, que se queda tenue y discontinua.","Con una sesión, supabase-js pregunta a Supabase Auth. Sin ella, se detiene en tu aplicación, y el error sale de ahí.","\u002Fblog\u002Fauth-session-missing\u002Fthe-empty-pocket-1600x720.png",[44,82,84],{"id":83},"se-devuelve-no-se-lanza","Se devuelve, no se lanza",[10,86,87,89],{},[21,88,32],{}," devuelve el error junto a un usuario vacío, y tu código decide qué\npasa después.",[10,91,92,93,96,97,100,101,104,105,107],{},"La llamada es así, y sin sesión ",[21,94,95],{},"user"," queda en ",[21,98,99],{},"null"," y ",[21,102,103],{},"error"," contiene el\n",[21,106,63],{},":",[14,109,114],{"className":110,"code":112,"language":113,"meta":23},[111],"language-ts","const { data: { user }, error } = await supabase.auth.getUser()\n","ts",[21,115,112],{"__ignoreMap":23},[10,117,118,119,122],{},"No se lanza nada, así que un ",[21,120,121],{},"try … catch"," alrededor de la llamada no atrapa\nnada. De ahí viene el daño visible, con una de dos formas:",[124,125,126,136],"ul",{},[127,128,129,135],"li",{},[39,130,131,132,134],{},"El código muestra cualquier ",[21,133,103],{}," que recibe."," Un visitante que aún no ha\niniciado sesión ve \"Auth session missing!\" en un aviso o en un recuadro rojo,\nen una página que tenía derecho a ver.",[127,137,138,141,142,145,146,149],{},[39,139,140],{},"El código usa el usuario sin comprobarlo."," Lee ",[21,143,144],{},"user.id"," y la página se\nrompe con ",[21,147,148],{},"Cannot read properties of null",", que parece otro fallo distinto.",[10,151,152],{},"Las dos son el mismo error: tratar \"nadie ha iniciado sesión\" como una avería.\nComprueba primero el usuario, y dale a un visitante sin sesión la pantalla pensada\npara él:",[14,154,157],{"className":155,"code":156,"language":113,"meta":23},[111],"const { data: { user } } = await supabase.auth.getUser()\nif (!user) {\n  \u002F\u002F nadie ha iniciado sesión: mostrar la pantalla de acceso o la página pública\n  return\n}\n",[21,158,156],{"__ignoreMap":23},[10,160,161,162,165,166,169],{},"Si quieres distinguir esto de una avería de verdad, la biblioteca le pone nombre:\n",[21,163,164],{},"error.name"," es ",[21,167,168],{},"'AuthSessionMissingError'",".",[44,171,173],{"id":172},"dónde-se-pierde-la-sesión","Dónde se pierde la sesión",[10,175,176],{},"En tres sitios, y solo el primero es inofensivo.",[10,178,179,182,183,185],{},[39,180,181],{},"1. Nadie ha iniciado sesión en este navegador."," Tu aplicación llama a\n",[21,184,32],{}," en una página que cualquiera puede abrir, quizá para decidir si\nmuestra \"Iniciar sesión\" o \"Mi cuenta\". Para un visitante, el bolsillo vacío es\nla respuesta verdadera. Aquí es donde la mayoría se encuentra el mensaje, y el\narreglo de código de arriba es todo lo que necesita.",[10,187,188,191,192,195,196,203,204,207,208,211],{},[39,189,190],{},"2. Una página para restablecer la contraseña o de enlace mágico."," Poner una\ncontraseña nueva llama a ",[21,193,194],{},"updateUser({ password })",", y la\n",[197,198,202],"a",{"href":199,"rel":200},"https:\u002F\u002Fsupabase.com\u002Fdocs\u002Fguides\u002Fauth\u002Fpasswords",[201],"nofollow","guía de contraseñas de Supabase","\nes clara con el orden: es una vez que hay sesión cuando se recoge la contraseña\nnueva y se llama a ",[21,205,206],{},"updateUser",". En esa página, la sesión tiene que venir del\nenlace del correo, que inicia la sesión de la persona al abrirse. Cuando no lo\nhace, el bolsillo está vacío y ",[21,209,210],{},"updateUser()"," responde \"Auth session missing!\".\nLas razones habituales:",[124,213,214,222,225],{},[127,215,216,217,221],{},"El enlace ya se había usado, o había caducado, antes de abrirlo.\n",[197,218,220],{"href":219},"\u002Fblog\u002Femail-link-is-invalid-or-has-expired","Por qué Supabase dice que el enlace del correo no es válido o ha caducado","\nrepasa cada una.",[127,223,224],{},"Se abrió en otro navegador o aplicación distinto del que lo pidió. Cuando una\naplicación usa el inicio de sesión PKCE de Supabase, parte del intercambio se\nqueda en el navegador que lo empezó, y según la documentación de Supabase el\nintercambio del código tiene que iniciarse en el mismo navegador y dispositivo\nen que empezó el proceso.",[127,226,227,228,231,232,236],{},"Mandó a la persona a otro sitio, como ",[21,229,230],{},"localhost",", así que la sesión llegó a\nuna dirección que no es tu aplicación.\n",[197,233,235],{"href":234},"\u002Fblog\u002Fsupabase-auth-redirects-to-localhost","Por qué Supabase redirige a localhost","\ncubre ese caso.",[10,238,239,242,243,245],{},[39,240,241],{},"3. Tu servidor o una Edge Function nunca recibió la sesión."," La credencial\nestá en el navegador de tu visitante. El código que corre en otra parte, una Edge\nFunction de Supabase o la mitad de servidor de una aplicación Next.js hecha con\nv0 o Cursor, crea su propio cliente de Supabase, y un cliente nuevo empieza con\nel bolsillo vacío. Mientras no le pases la sesión del visitante, ",[21,244,32],{}," no\nencuentra nada allí y responde \"Auth session missing!\" sin preguntar a Supabase.",[10,247,248,249,252,253,257],{},"En una Edge Function la sesión viaja en la cabecera ",[21,250,251],{},"Authorization"," de la\npetición, y supabase-js solo la usa si se la entregas al crear el cliente. En una\naplicación Next.js viaja en cookies, que la herramienta de servidor de Supabase\nsolo lee si le das las de la petición. Si una Edge Function devuelve ese fallo\ncomo error, tu aplicación muestra\n",[197,254,256],{"href":255},"\u002Fblog\u002Fedge-function-returned-a-non-2xx-status-code","\"Edge Function returned a non-2xx status code\"",",\ncon este mensaje esperando en el registro de la función.",[76,259],{"alt":260,"caption":261,"src":262},"Una persona ante una ventana de navegador, con su credencial puesta, envía dos peticiones a una Edge Function. En el carril de arriba la petición llega sin la credencial, la funda propia de la función está vacía y marcada en ámbar, y el carril termina en una cruz. En el de abajo la credencial viaja con la petición bajo la etiqueta Authorization, la funda de la función está llena, y la recepción responde con una marca verde.","La sesión es del navegador. Una función o un servidor solo la tiene si la petición la lleva y el código se la pasa a su cliente.","\u002Fblog\u002Fauth-session-missing\u002Fthe-badge-stays-behind-1600x760.png",[44,264,266],{"id":265},"cómo-saber-cuál-es-tu-caso","Cómo saber cuál es tu caso",[10,268,269],{},"Mira dónde aparece el mensaje, y luego qué envió de verdad el navegador.",[10,271,272,275],{},[39,273,274],{},"En la consola del navegador, en una página que cualquiera puede abrir."," Caso",[277,278,279],"ol",{},[127,280,281,282,285,286,289,290,293,294,297,298,169],{},"Abre las herramientas de desarrollo de tu navegador (F12), elige\n",[39,283,284],{},"Application",", luego ",[39,287,288],{},"Local Storage"," y la dirección de tu aplicación.\nsupabase-js guarda la sesión bajo una clave formada por ",[21,291,292],{},"sb-",", el identificador\nde tu proyecto y ",[21,295,296],{},"-auth-token",". Si no hay ninguna entrada así, este navegador no\ntiene sesión, y el mensaje dice la verdad. Una aplicación Next.js que usa las\nherramientas de servidor de Supabase guarda la sesión en cookies, así que busca\nel mismo nombre en ",[39,299,300],{},"Cookies",[10,302,303,306],{},[39,304,305],{},"En una página para restablecer la contraseña o de enlace mágico."," Caso 2. Mira\nla dirección en la que se abrió el enlace. Si no es tu aplicación, o es un enlace\nque tú o un escáner de correo ya habíais abierto, la sesión no tuvo ninguna\noportunidad de llegar.",[10,308,309,312,313,316,317,319,320,323],{},[39,310,311],{},"En el registro de una Edge Function, o de tu servidor."," Caso 3. Abre la\npestaña ",[39,314,315],{},"Network",", repite la acción y haz clic en la petición a tu función. Si\nsu cabecera ",[21,318,251],{}," lleva una cadena larga que empieza por ",[21,321,322],{},"eyJ",", el\nnavegador envió el token de una persona, y tu función no se lo pasó a su cliente.",[10,325,326,327,330,331,334,335,339],{},"Lo que no encontrarás por ninguna vía es una petición a ",[21,328,329],{},"\u002Fauth\u002Fv1\u002Fuser"," que\nvolviera con este mensaje. Si ves una que falló, es otro error. Una respuesta que\ndice ",[21,332,333],{},"invalid claim: missing sub claim"," significa que la clave de tu proyecto se\nenvió donde debía ir una persona, y ",[197,336,338],{"href":337},"\u002Fblog\u002Finvalid-claim-missing-sub-claim","ese artículo","\nes su gemelo.",[44,341,343],{"id":342},"el-arreglo-para-cada-caso","El arreglo para cada caso",[10,345,346],{},"Dale a cada uno la sesión que le falta, o una respuesta que no la necesite.",[10,348,349,352,353,355],{},[39,350,351],{},"Caso 1, el visitante sin sesión:"," comprueba ",[21,354,95],{}," antes de usarlo, como en el\ncódigo de arriba, y muestra la pantalla pensada para alguien que no ha iniciado\nsesión.",[10,357,358,361,362,365],{},[39,359,360],{},"Caso 2, la página para restablecer la contraseña:"," espera a que el enlace\nhaya iniciado la sesión de la persona antes de mostrar el formulario. Supabase\nemite un evento ",[21,363,364],{},"PASSWORD_RECOVERY"," cuando alguien llega desde un enlace de\nrecuperación, y su referencia muestra la página esperándolo:",[14,367,370],{"className":368,"code":369,"language":113,"meta":23},[111],"supabase.auth.onAuthStateChange((event) => {\n  if (event === 'PASSWORD_RECOVERY') {\n    \u002F\u002F el enlace inició su sesión: ahora mostrar el formulario de contraseña nueva\n  }\n})\n",[21,371,369],{"__ignoreMap":23},[10,373,374],{},"Si el evento no llega nunca, el enlace no funcionó. Dilo y ofrece enviar uno\nnuevo, en lugar de mostrar un formulario que va a fallar con seguridad.",[10,376,377,380],{},[39,378,379],{},"Caso 3, la función o el servidor:"," pásale la sesión del visitante al cliente\nque creas. En una Edge Function, créalo con la cabecera que llega:",[14,382,385],{"className":383,"code":384,"language":113,"meta":23},[111],"const supabase = createClient(\n  Deno.env.get('SUPABASE_URL')!,\n  Deno.env.get('SUPABASE_ANON_KEY')!,\n  { global: { headers: { Authorization: req.headers.get('Authorization')! } } },\n)\nconst { data: { user } } = await supabase.auth.getUser()\nif (!user) {\n  return new Response(JSON.stringify({ error: 'Sign in first' }), {\n    status: 401,\n    headers: { ...corsHeaders, 'Content-Type': 'application\u002Fjson' },\n  })\n}\n",[21,386,384],{"__ignoreMap":23},[10,388,389,390,393,394,398,399,401,402,404],{},"El envoltorio más reciente de Supabase, ",[21,391,392],{},"withSupabase({ auth: 'user' })",", hace lo\nmismo antes de que se ejecute tu manejador, y el\n",[197,395,397],{"href":396},"\u002Fblog\u002Fmove-a-secret-to-an-edge-function","artículo sobre Edge Functions"," muestra\ncómo montarlo. Mantén el 401 para quien llame sin sesión, para que una petición\ndesde una página sin sesión reciba un rechazo claro. Con la cabecera pasada,\n",[21,400,32],{}," puede devolver ahí ",[21,403,333],{}," para quien\nllame sin sesión: el mismo visitante visto desde el otro lado, y el 401 cubre los\ndos.",[10,406,407,408,411,412,414],{},"En una aplicación Next.js, crea el cliente de servidor con la herramienta\n",[21,409,410],{},"@supabase\u002Fssr"," de Supabase y las cookies de la petición, como muestra su\nconfiguración de servidor, y llama a ",[21,413,32],{}," en ese cliente.",[44,416,418],{"id":417},"dos-arreglos-que-lo-empeoran","Dos arreglos que lo empeoran",[10,420,421],{},"Los dos se sugieren para este mensaje, y cada uno para el error quitando la\ncomprobación que hay detrás.",[10,423,424,431,432,435,436,438],{},[39,425,426,427,33,429,169],{},"Cambiar ",[21,428,32],{},[21,430,36],{}," En el navegador no cambia nada que\nimporte: el almacenamiento sigue vacío, así que no obtienes ni sesión ni usuario,\nsolo que sin el mensaje. En un servidor es peligroso. La guía de servidor de\nSupabase dice que nunca hay que fiarse de ",[21,433,434],{},"supabase.auth.getSession()"," en código\nde servidor, porque lee la sesión de la cookie sin volver a validarla. Una cookie\nes algo que te manda el visitante, y cualquiera puede mandarte la cookie que\nquiera. ",[21,437,32],{}," es el método del que supabase-js documenta que hace una\npetición al servidor de Supabase Auth, así que su resultado es auténtico.\nMantenlo en todos los sitios donde la respuesta decide qué puede hacer alguien.",[10,440,441,444,445,448,449,452,453,169],{},[39,442,443],{},"Poner la clave secreta en el cliente para que la llamada deje de fallar."," Un\ncliente creado con la clave ",[21,446,447],{},"service_role"," o ",[21,450,451],{},"sb_secret_"," no necesita sesión para\nleer y escribir, porque se salta por completo las reglas de tu base de datos. La\nfunción vuelve a funcionar, para cualquiera que la llame, con sesión o sin ella.\nSi un builder hace ese cambio en código que va al navegador, la clave viaja con\nél a cada visitante. Si ya ha pasado, ",[197,454,456],{"href":455},"\u002Fblog\u002Frotate-supabase-service-role-key","rótala primero",[10,458,459,460,169],{},"Eso se puede comprobar desde fuera. Nuestro escaneo gratuito lee el JavaScript de\ntu aplicación publicada como lo haría un desconocido y nombra cada clave que\nencuentra, incluido si es una de las que deben estar ahí. Tarda unos 20 segundos\ny no pide cuenta: ",[197,461,463],{"href":462},"\u002Fsecurity-scanner","escanea tu app",[44,465,467],{"id":466},"que-la-comprobación-siga-ahí-tras-el-próximo-cambio","Que la comprobación siga ahí tras el próximo cambio",[10,469,470,473],{},[39,471,472],{},"La comprobación de quién ha iniciado sesión es la parte de una página que más\nfácil desaparece cuando le pides a tu builder que haga desaparecer un error",", y\nlos dos arreglos rápidos de arriba la quitan.",[10,475,476],{},[39,477,478,482],{},[197,479,481],{"href":480},"\u002Fwebsite-monitoring","Reeve Monitor"," vuelve a escanear tu app publicada cada hora:",[124,484,485,488,491,494],{},[127,486,487],{},"las nueve comprobaciones cada hora, en hasta tres apps",[127,489,490],{},"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",[127,492,493],{},"si la app está disponible, cada 60 segundos",[127,495,496],{},"un informe mensual de lo que vio",[10,498,499],{},[39,500,501,502,506],{},"Un servidor que se cree cualquier cookie, o una función que funciona con la\nclave secreta, deja que un desconocido cambie las filas de tus usuarios.\n",[197,503,505],{"href":504},"\u002Fsupabase-backups","Reeve Care"," guarda una copia de tu base de datos de Supabase\nde antes de que eso pase.",[124,508,509,512,515,518,521],{},[127,510,511],{},"una copia cifrada según un calendario, guardada en el almacenamiento propio de Reeve en la UE, fuera de tu cuenta de Supabase",[127,513,514],{},"cada copia verificada antes de contar, contando las filas de cada tabla",[127,516,517],{},"una restauración en un clic que guarda lo que hay ahora antes de reemplazarlo",[127,519,520],{},"también tus archivos subidos, en cuanto conectes una credencial de Storage",[127,522,523],{},"todo lo que hace Monitor",[44,525,527],{"id":526},"qué-hacer-ahora","Qué hacer ahora",[529,530,531],"key-takeaways",{},[124,532,533,536,544,550,556],{},[127,534,535],{},"Lee \"Auth session missing!\" como \"aquí nadie ha iniciado sesión\". La petición nunca salió de tu aplicación.",[127,537,538,539,165,541,543],{},"Comprueba si ",[21,540,95],{},[21,542,99],{}," antes de usarlo, y dale a un visitante sin sesión la pantalla pensada para él en lugar del error.",[127,545,546,547,549],{},"En una página para restablecer la contraseña, espera al evento ",[21,548,364],{}," antes de mostrar el formulario, y ofrece un enlace nuevo si nunca llega.",[127,551,552,553,555],{},"En una Edge Function o en un servidor, pásale la cabecera ",[21,554,251],{}," o las cookies del visitante al cliente que creas, y responde 401 si sigue sin haber usuario.",[127,557,558,559,561,562,564],{},"Mantén ",[21,560,32],{}," en el servidor. Allí ",[21,563,36],{}," se cree cualquier cookie que le manden.",[10,566,567,568,572,573,577],{},"Si tus sesiones existen y luego desaparecen, de modo que gente que había iniciado\nsesión se ve fuera de repente, es otro problema con sus propias causas, y\n",[197,569,571],{"href":570},"\u002Fblog\u002Fsupabase-users-logged-out","por qué se cierra la sesión de los usuarios de Supabase","\nlo cubre. La ",[197,574,576],{"href":575},"\u002Fchecklist","lista de seguridad de 10 minutos"," cubre lo que una\naplicación recién lanzada suele dejar abierto además.",{"title":23,"searchDepth":579,"depth":579,"links":580},3,[581,583,584,585,586,587,588,589],{"id":46,"depth":582,"text":47},2,{"id":83,"depth":582,"text":84},{"id":172,"depth":582,"text":173},{"id":265,"depth":582,"text":266},{"id":342,"depth":582,"text":343},{"id":417,"depth":582,"text":418},{"id":466,"depth":582,"text":467},{"id":526,"depth":582,"text":527},"Fundamentos de seguridad","\u002Fblog\u002Fauth-session-missing\u002Fcover-1200x630.png","Una funda de credencial vacía colgada de una cinta, con el hueco de la credencial marcado en ámbar, delante de dos credenciales con cara.","\"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.",false,"md",[597,599,602,605,608],{"q":47,"a":598},"Significa que a supabase-js, la biblioteca de Supabase dentro de tu aplicación, le pidieron el usuario con sesión iniciada y no tenía ninguna sesión con la que preguntar. Una sesión es lo que Supabase Auth le da a una persona cuando inicia sesión, y la biblioteca la guarda en el navegador. Si no hay ninguna, no contacta con Supabase en absoluto. Devuelve AuthSessionMissingError con el estado 400 y un usuario null.",{"q":600,"a":601},"¿Por qué veo \"Auth session missing!\" cuando nadie ha iniciado sesión?","Porque esa es la respuesta para un visitante que no ha iniciado sesión. Tu aplicación llamó a getUser() en una página que cualquiera puede abrir, y supabase-js informó de que nadie había iniciado sesión. No hay nada roto. Tu código solo tiene que leer el usuario como null y mostrar la pantalla de acceso, en lugar de mostrar el error.",{"q":603,"a":604},"¿Por qué mi página para restablecer la contraseña dice \"Auth session missing!\"?","Cambiar una contraseña con updateUser() necesita una sesión, y en esa página la sesión tiene que venir del enlace del correo. Si el enlace ya se había usado o había caducado, se abrió en otro navegador distinto del que lo pidió, o mandó a la persona a otra dirección, no llegó ninguna sesión y updateUser() no tiene a quién cambiar. Espera al evento PASSWORD_RECOVERY antes de mostrar el formulario, y ofrece un enlace nuevo si nunca llega.",{"q":606,"a":607},"¿Es lo mismo que \"invalid claim: missing sub claim\"?","No. \"Auth session missing!\" se decide dentro de tu aplicación, antes de que salga ninguna petición. \"invalid claim: missing sub claim\" vuelve de Supabase Auth, que recibió una petición y encontró la clave de tu proyecto donde debía estar el token de una persona. Lo primero significa que no había nada que enviar. Lo segundo, que se envió lo que no era.",{"q":609,"a":610},"¿Debo usar getSession() en lugar de getUser()?","No para quitar este error. En el navegador, getSession() encuentra el mismo almacenamiento vacío y no devuelve ninguna sesión, así que nada cambia salvo que el mensaje desaparece. En un servidor es peor: supabase-js documenta que getSession() lee la sesión directamente del almacenamiento, por ejemplo de las cookies de la petición, que pueden no ser auténticas, y recomienda getUser() en su lugar porque lo comprueba con Supabase Auth.","\u002Fblog\u002Fauth-session-missing\u002Fcard-800x500.png",[613,614,615,616,617,618,619],"auth session missing supabase","AuthSessionMissingError: Auth session missing!","supabase getuser auth session missing","supabase updateuser auth session missing","supabase restablecer contraseña auth session missing","supabase edge function auth session missing","supabase ssr auth session missing",{},true,"\"Auth session missing!\" en Supabase","\u002Fblog\u002Fauth-session-missing","2026-11-05",{"title":5,"description":593},"blog\u002Fauth-session-missing",[628,629,630],"\"Auth session missing!\" (AuthSessionMissingError, estado 400) sale de supabase-js, dentro de tu aplicación. La biblioteca buscó una sesión iniciada, no encontró ninguna y nunca envió la petición a Supabase.","En una página donde nadie ha iniciado sesión es la respuesta correcta, y tu código solo tiene que tratarla. En una página para restablecer la contraseña significa que el enlace no inició la sesión de nadie aquí. En una Edge Function o en un servidor, la sesión del visitante nunca llegó.","Deja getUser() donde está. Cambiarlo por getSession() en un servidor hace desaparecer el error creyéndose lo que diga la cookie.","TvG524C80M8rf-4bcOtC5lh8M-FHmA9w-6dCGssap3o",[633,639,645,651,657,658,663,669,674,679,685,692,698,704,709,715,721,727,733,739,745,751,756,762,768,774,780,786,792,798,804,810,816,822,828,834,839,845,851,857,863,869,875,881,887,893,898,904,910,916,922,928,934,940,946,952,958,964,970,976,982,988,994,1000,1005,1011,1017,1023,1029,1035,1041,1047,1053,1059,1065,1071,1077,1083,1089,1095,1101,1107,1113,1119,1124,1130,1136,1142,1147,1153],{"path":634,"title":635,"description":636,"published":637,"category":590,"image":638,"draft":594},"\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":640,"title":641,"description":642,"published":643,"category":590,"image":644,"draft":594},"\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":646,"title":647,"description":648,"published":649,"category":590,"image":650,"draft":594},"\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":652,"title":653,"description":654,"published":655,"category":590,"image":656,"draft":594},"\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":623,"title":5,"description":593,"published":624,"category":590,"image":611,"draft":594},{"path":337,"title":659,"description":660,"published":661,"category":590,"image":662,"draft":594},"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":664,"title":665,"description":666,"published":667,"category":590,"image":668,"draft":594},"\u002Fblog\u002Ffailed-to-send-a-request-to-the-edge-function","\"Failed to send a request to the Edge Function\": la causa real","\"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ó.","2026-11-03","\u002Fblog\u002Ffailed-to-send-a-request-to-the-edge-function\u002Fcard-800x500.png",{"path":234,"title":670,"description":671,"published":672,"category":590,"image":673,"draft":594},"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":219,"title":675,"description":676,"published":677,"category":590,"image":678,"draft":594},"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":680,"title":681,"description":682,"published":683,"category":590,"image":684,"draft":594},"\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":686,"title":687,"description":688,"published":689,"category":690,"image":691,"draft":594},"\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":693,"title":694,"description":695,"published":696,"category":590,"image":697,"draft":594},"\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":699,"title":700,"description":701,"published":702,"category":590,"image":703,"draft":594},"\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":255,"title":705,"description":706,"published":707,"category":590,"image":708,"draft":594},"\"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":710,"title":711,"description":712,"published":713,"category":590,"image":714,"draft":594},"\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":716,"title":717,"description":718,"published":719,"category":590,"image":720,"draft":594},"\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":722,"title":723,"description":724,"published":725,"category":590,"image":726,"draft":594},"\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":728,"title":729,"description":730,"published":731,"category":590,"image":732,"draft":594},"\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":734,"title":735,"description":736,"published":737,"category":590,"image":738,"draft":594},"\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":740,"title":741,"description":742,"published":743,"category":590,"image":744,"draft":594},"\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":746,"title":747,"description":748,"published":749,"category":590,"image":750,"draft":594},"\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":570,"title":752,"description":753,"published":754,"category":590,"image":755,"draft":594},"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":757,"title":758,"description":759,"published":760,"category":590,"image":761,"draft":594},"\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":763,"title":764,"description":765,"published":766,"category":590,"image":767,"draft":594},"\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":769,"title":770,"description":771,"published":772,"category":590,"image":773,"draft":594},"\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":775,"title":776,"description":777,"published":778,"category":590,"image":779,"draft":594},"\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":781,"title":782,"description":783,"published":784,"category":590,"image":785,"draft":594},"\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":787,"title":788,"description":789,"published":790,"category":590,"image":791,"draft":594},"\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":793,"title":794,"description":795,"published":796,"category":590,"image":797,"draft":594},"\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":799,"title":800,"description":801,"published":802,"category":590,"image":803,"draft":594},"\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":805,"title":806,"description":807,"published":808,"category":590,"image":809,"draft":594},"\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":811,"title":812,"description":813,"published":814,"category":590,"image":815,"draft":594},"\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":817,"title":818,"description":819,"published":820,"category":590,"image":821,"draft":594},"\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":823,"title":824,"description":825,"published":826,"category":590,"image":827,"draft":594},"\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":829,"title":830,"description":831,"published":832,"category":590,"image":833,"draft":594},"\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":396,"title":835,"description":836,"published":837,"category":590,"image":838,"draft":594},"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":840,"title":841,"description":842,"published":843,"category":590,"image":844,"draft":594},"\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":846,"title":847,"description":848,"published":849,"category":590,"image":850,"draft":594},"\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":852,"title":853,"description":854,"published":855,"category":590,"image":856,"draft":594},"\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":858,"title":859,"description":860,"published":861,"category":590,"image":862,"draft":594},"\u002Fblog\u002Fis-supabase-down-or-your-app","¿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":864,"title":865,"description":866,"published":867,"category":590,"image":868,"draft":594},"\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":870,"title":871,"description":872,"published":873,"category":590,"image":874,"draft":594},"\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":876,"title":877,"description":878,"published":879,"category":690,"image":880,"draft":594},"\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":882,"title":883,"description":884,"published":885,"category":690,"image":886,"draft":594},"\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":888,"title":889,"description":890,"published":891,"category":690,"image":892,"draft":594},"\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":894,"title":895,"description":896,"published":891,"category":590,"image":897,"draft":594},"\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":899,"title":900,"description":901,"published":902,"category":590,"image":903,"draft":594},"\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":905,"title":906,"description":907,"published":908,"category":590,"image":909,"draft":594},"\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":911,"title":912,"description":913,"published":914,"category":690,"image":915,"draft":594},"\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":917,"title":918,"description":919,"published":920,"category":590,"image":921,"draft":594},"\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":923,"title":924,"description":925,"published":926,"category":590,"image":927,"draft":594},"\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":929,"title":930,"description":931,"published":932,"category":590,"image":933,"draft":594},"\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":935,"title":936,"description":937,"published":938,"category":590,"image":939,"draft":594},"\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":941,"title":942,"description":943,"published":944,"category":590,"image":945,"draft":594},"\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":947,"title":948,"description":949,"published":950,"category":590,"image":951,"draft":594},"\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":953,"title":954,"description":955,"published":956,"category":590,"image":957,"draft":594},"\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":959,"title":960,"description":961,"published":962,"category":590,"image":963,"draft":594},"\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":965,"title":966,"description":967,"published":968,"category":690,"image":969,"draft":594},"\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":971,"title":972,"description":973,"published":974,"category":690,"image":975,"draft":594},"\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":977,"title":978,"description":979,"published":980,"category":690,"image":981,"draft":594},"\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":983,"title":984,"description":985,"published":986,"category":590,"image":987,"draft":594},"\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":989,"title":990,"description":991,"published":992,"category":590,"image":993,"draft":594},"\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":995,"title":996,"description":997,"published":998,"category":590,"image":999,"draft":594},"\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":455,"title":1001,"description":1002,"published":1003,"category":590,"image":1004,"draft":594},"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":1006,"title":1007,"description":1008,"published":1009,"category":590,"image":1010,"draft":594},"\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":1012,"title":1013,"description":1014,"published":1015,"category":590,"image":1016,"draft":594},"\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":1018,"title":1019,"description":1020,"published":1021,"category":590,"image":1022,"draft":594},"\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":1024,"title":1025,"description":1026,"published":1027,"category":590,"image":1028,"draft":594},"\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":1030,"title":1031,"description":1032,"published":1033,"category":590,"image":1034,"draft":594},"\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":1036,"title":1037,"description":1038,"published":1039,"category":590,"image":1040,"draft":594},"\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":1042,"title":1043,"description":1044,"published":1045,"category":690,"image":1046,"draft":594},"\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":1048,"title":1049,"description":1050,"published":1051,"category":590,"image":1052,"draft":594},"\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":1054,"title":1055,"description":1056,"published":1057,"category":690,"image":1058,"draft":594},"\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":1060,"title":1061,"description":1062,"published":1063,"category":590,"image":1064,"draft":594},"\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":1066,"title":1067,"description":1068,"published":1069,"category":590,"image":1070,"draft":594},"\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":1072,"title":1073,"description":1074,"published":1075,"category":590,"image":1076,"draft":594},"\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":1078,"title":1079,"description":1080,"published":1081,"category":590,"image":1082,"draft":594},"\u002Fblog\u002Fcors-wildcard-security-risk","¿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":1084,"title":1085,"description":1086,"published":1087,"category":690,"image":1088,"draft":594},"\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":1090,"title":1091,"description":1092,"published":1093,"category":690,"image":1094,"draft":594},"\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":1096,"title":1097,"description":1098,"published":1099,"category":590,"image":1100,"draft":594},"\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":1102,"title":1103,"description":1104,"published":1105,"category":590,"image":1106,"draft":594},"\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":1108,"title":1109,"description":1110,"published":1111,"category":690,"image":1112,"draft":594},"\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":1114,"title":1115,"description":1116,"published":1117,"category":590,"image":1118,"draft":594},"\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":1120,"title":1121,"description":1122,"published":1117,"category":590,"image":1123,"draft":594},"\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":1125,"title":1126,"description":1127,"published":1128,"category":590,"image":1129,"draft":594},"\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":1131,"title":1132,"description":1133,"published":1134,"category":690,"image":1135,"draft":594},"\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":1137,"title":1138,"description":1139,"published":1140,"category":590,"image":1141,"draft":594},"\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":1143,"title":1144,"description":1145,"published":1140,"category":690,"image":1146,"draft":594},"\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":1148,"title":1149,"description":1150,"published":1151,"category":690,"image":1152,"draft":594},"\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":1154,"title":1155,"description":1156,"published":1151,"category":590,"image":1157,"draft":594},"\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",1794213644726]