[{"data":1,"prerenderedAt":1270},["ShallowReactive",2],{"blog-es-supabase-users-logged-out":3,"blog-index-es":741},{"id":4,"title":5,"body":6,"category":700,"cover":701,"coverAlt":702,"description":703,"draft":704,"extension":705,"faq":706,"image":722,"keywords":723,"meta":730,"navigation":731,"ogTitle":5,"path":732,"published":733,"seo":734,"stem":735,"tldr":736,"updated":733,"__hash__":740},"blog_es\u002Fblog\u002Fsupabase-users-logged-out.md","Supabase \"Invalid Refresh Token: Refresh Token Not Found\": 3 causas",{"type":7,"value":8,"toc":685},"minimark",[9,13,24,27,35,40,43,62,84,110,114,117,126,217,225,229,232,238,260,266,270,291,294,297,303,306,311,315,327,340,358,363,369,373,376,398,414,417,421,427,430,434,452,455,513,520,526,529,533,536,562,571,577,581,584,593,611,620,640,644,679],[10,11,12],"p",{},"Tus usuarios te escriben para decirte que se les cerró la sesión. Uno estaba a\nmitad de un formulario, y otra está segura de que no tocó nada. Cuando pruebas la\napp tú mismo, funciona. Luego abres tus logs y encuentras la misma línea, una y\notra vez:",[14,15,20],"pre",{"className":16,"code":18,"language":19},[17],"language-text","AuthApiError: Invalid Refresh Token: Refresh Token Not Found\n","text",[21,22,18],"code",{"__ignoreMap":23},"",[10,25,26],{},"Supabase \"Invalid Refresh Token: Refresh Token Not Found\" es Supabase Auth, la\nparte de Supabase que inicia la sesión de la gente, diciendo que no tiene\nregistro del token que tu app envió para mantener una sesión abierta. Sin nada\nque renovar, tu app cierra la sesión del usuario.",[10,28,29,30,34],{},"Esta es la parte que es fácil entender al revés: ",[31,32,33],"strong",{},"este error es el último paso,\ny la causa llegó antes."," Cuando Supabase ya no encuentra el token, la sesión a\nla que pertenecía ya terminó. La terminó un cierre de sesión, un token enviado\ndos veces o un límite que alguien puso. Hacer que el access token dure más, o\napagar la comprobación que termina una sesión, vuelve la línea más rara en tus\nlogs y deja la causa exactamente donde estaba.",[36,37,39],"h2",{"id":38},"qué-significa-invalid-refresh-token-refresh-token-not-found","¿Qué significa \"Invalid Refresh Token: Refresh Token Not Found\"?",[10,41,42],{},"Significa que Supabase Auth recibió un refresh token del que no tiene registro,\nasí que se negó a renovar la sesión.",[10,44,45,46,49,50,53,54,61],{},"Cada usuario con sesión iniciada tiene dos tokens. El ",[31,47,48],{},"access token"," funciona\ncomo una pulsera: demuestra quién es durante un rato, una hora si no has cambiado\nel ajuste, y tu app la muestra en cada petición. El ",[31,51,52],{},"refresh token"," es un\nticket. Antes de que caduque la pulsera, tu app lleva el ticket al mostrador, que\nes Supabase Auth, y lo cambia por una pulsera nueva y un ticket nuevo. La\n",[55,56,60],"a",{"href":57,"rel":58},"https:\u002F\u002Fsupabase.com\u002Fdocs\u002Fguides\u002Fauth\u002Fsessions",[59],"nofollow","guía de sesiones"," de Supabase\ndice que los refresh tokens \"never expire but can only be used once\".",[10,63,64,67,68,71,72,77,78,83],{},[21,65,66],{},"Refresh Token Not Found"," es el mostrador buscando un ticket y no encontrando\nnada. El servidor responde con HTTP 400 y el código ",[21,69,70],{},"refresh_token_not_found",",\nque la\n",[55,73,76],{"href":74,"rel":75},"https:\u002F\u002Fsupabase.com\u002Fdocs\u002Fguides\u002Fauth\u002Fdebugging\u002Ferror-codes",[59],"lista de códigos de error de Auth","\nde Supabase describe como \"Session containing the refresh token not found.\" Si\nla pulsera también ha caducado,\n",[55,79,82],{"href":80,"rel":81},"https:\u002F\u002Fgithub.com\u002Fsupabase\u002Fsupabase-js\u002Fblob\u002Fmaster\u002Fpackages\u002Fcore\u002Fauth-js\u002Fsrc\u002FGoTrueClient.ts",[59],"el cliente de JavaScript de Supabase","\nquita la sesión del navegador y le avisa a tu app de que el usuario cerró\nsesión. En ese momento tu usuario se encuentra con la página de inicio de\nsesión.",[10,85,86,87,91,92,95,96,100,101,104,105,109],{},"Ninguno de los dos tokens es una de las API keys de tu proyecto, que son otra\ncuestión:\n",[55,88,90],{"href":89},"\u002Fblog\u002Fwhich-api-keys-are-safe-in-your-frontend","cuáles de ellas van en tu app",".\nSi tu mensaje dice ",[21,93,94],{},"Invalid JWT",", se rechazó la pulsera y no el ticket, y\n",[55,97,99],{"href":98},"\u002Fblog\u002Fsupabase-invalid-jwt","ese error tiene su propia página",".\nSi después no puede volver a entrar y el formulario dice\n",[21,102,103],{},"Invalid login credentials",",\n",[55,106,108],{"href":107},"\u002Fblog\u002Fsupabase-invalid-login","ese mensaje cubre a tres personas distintas",".",[36,111,113],{"id":112},"por-qué-se-desconectan-mis-usuarios-de-supabase","¿Por qué se desconectan mis usuarios de Supabase?",[10,115,116],{},"Porque la sesión detrás de su ticket terminó, y la pulsera que todavía llevan\nlos mantuvo conectados hasta que caducó. Ese retraso, de hasta una hora con el\najuste por defecto, es lo que hace que las desconexiones parezcan aleatorias: el\nusuario sale un rato después de que terminara la sesión.",[10,118,119,120,125],{},"Supabase Auth tiene cuatro maneras de decir que un ticket no vale, y cada una\napunta a una causa distinta. Estos son los mensajes exactos del\n",[55,121,124],{"href":122,"rel":123},"https:\u002F\u002Fgithub.com\u002Fsupabase\u002Fauth\u002Fblob\u002Fmaster\u002Finternal\u002Ftokens\u002Fservice.go",[59],"código del propio servidor de Supabase Auth",":",[127,128,129,145],"table",{},[130,131,132],"thead",{},[133,134,135,139,142],"tr",{},[136,137,138],"th",{},"El mensaje en tu log",[136,140,141],{},"El código",[136,143,144],{},"Qué pasó",[146,147,148,163,178,202],"tbody",{},[133,149,150,156,160],{},[151,152,153],"td",{},[21,154,155],{},"Invalid Refresh Token: Refresh Token Not Found",[151,157,158],{},[21,159,70],{},[151,161,162],{},"El mostrador no tiene registro de este ticket. Un cierre de sesión terminó su sesión, o se cambió hace tanto que su registro ya no está.",[133,164,165,170,175],{},[151,166,167],{},[21,168,169],{},"Invalid Refresh Token: Already Used",[151,171,172],{},[21,173,174],{},"refresh_token_already_used",[151,176,177],{},"El ticket se cambió hace más de diez segundos y ha vuelto. Supabase termina toda la sesión cuando pasa esto.",[133,179,180,194,199],{},[151,181,182,185,186,189,190,193],{},[21,183,184],{},"Invalid Refresh Token: Session Expired",", a veces con ",[21,187,188],{},"(Inactivity)"," o ",[21,191,192],{},"(Revoked by Newer Login)"," detrás",[151,195,196],{},[21,197,198],{},"session_expired",[151,200,201],{},"Un límite de sesión en tus ajustes de Auth la terminó.",[133,203,204,209,214],{},[151,205,206],{},[21,207,208],{},"Invalid Refresh Token: No Valid Session Found",[151,210,211],{},[21,212,213],{},"session_not_found",[151,215,216],{},"La sesión a la que pertenecía el ticket ya no existe.",[10,218,219,220,224],{},"Detrás de estos mensajes hay tres causas, según la propia documentación de\nSupabase y los reportes en sus issue trackers, y cada una tiene su arreglo. Si\nnadie puede iniciar sesión, o toda la app está en blanco, es otro problema, y\n",[55,221,223],{"href":222},"\u002Fblog\u002Fis-supabase-down-or-your-app","¿Supabase está caído, o es tu app?"," es por\ndonde empezar.",[36,226,228],{"id":227},"un-refresh-token-funciona-una-vez","Un refresh token funciona una vez",[10,230,231],{},"Cada vez que tu app renueva una sesión, entrega el ticket y el mostrador rompe\nel viejo. Supabase llama a esto rotación de refresh tokens. El ticket viejo sigue\nfuncionando diez segundos más, lo que la guía de sesiones llama el reuse\ninterval, para que un servidor y un navegador que renuevan casi al mismo tiempo\nno se echen el uno al otro. Un cliente que cambió su ticket y perdió la respuesta\npor una mala conexión también está cubierto: recibe de vuelta el ticket actual.",[10,233,234,235,109],{},"Fuera de esos dos casos, un ticket viejo que vuelve cuenta como señal de alarma,\nporque un token que aparece dos veces es lo que parece uno robado. La guía dice\nque entonces toda la sesión \"is regarded as terminated and all refresh tokens\nbelonging to it are marked as revoked\". La respuesta esa vez es ",[21,236,237],{},"Already Used",[10,239,240,241,243,244,247,248,253,254,256,257,259],{},"Un paso más convierte ",[21,242,237],{}," en ",[21,245,246],{},"Not Found",". El servidor de Supabase Auth\n",[55,249,252],{"href":250,"rel":251},"https:\u002F\u002Fgithub.com\u002Fsupabase\u002Fauth\u002Fblob\u002Fmaster\u002Finternal\u002Fmodels\u002Fcleanup.go",[59],"borra los registros de los tickets rotos","\n24 horas después de su revocación. Así que el ticket caducado que hoy recibe\n",[21,255,237],{}," recibe mañana ",[21,258,66],{},", porque el mostrador ya no\ntiene el registro con el que compararlo.",[261,262],"diagram",{"alt":263,"caption":264,"src":265},"Una línea de tiempo para un ticket. Primero se cambia en el mostrador por una pulsera nueva y un ticket nuevo, y el viejo se rompe. Mostrado otra vez antes de 10 segundos, todavía se acepta. Más tarde ese mismo día se rechaza como ya usado. Pasadas 24 horas, el libro del mostrador tiene una línea vacía donde estaba su registro.","Un ticket en cuatro momentos. Antes de diez segundos todavía funciona. Después se rechaza como ya usado, y cuando su registro se borra un día más tarde, el mostrador ya no lo encuentra.","\u002Fblog\u002Fsupabase-users-logged-out\u002Fone-ticket-four-moments-1600x700.png",[36,267,269],{"id":268},"causa-1-alguien-cerró-sesión-en-este-dispositivo-o-en-otro","Causa 1: alguien cerró sesión, en este dispositivo o en otro",[10,271,272,273,276,277,280,281,286,287,290],{},"Un cierre de sesión borra la sesión del lado del mostrador, y cada ticket de esa\nsesión pasa a ser uno que el mostrador no encuentra. En el cliente de JavaScript\nde Supabase, ",[21,274,275],{},"signOut()"," sin nada entre los paréntesis termina ",[31,278,279],{},"todas"," las\nsesiones de ese usuario, en todos sus dispositivos. La\n",[55,282,285],{"href":283,"rel":284},"https:\u002F\u002Fsupabase.com\u002Fdocs\u002Fguides\u002Fauth\u002Fsignout",[59],"guía para cerrar sesión"," de\nSupabase lo dice directamente: con ese valor por defecto, las demás sesiones\nfallan con ",[21,288,289],{},"AuthApiError: Invalid Refresh Token: Refresh Token Not Found"," la\npróxima vez que intentan renovarse, \"even though no one signed out on that\ndevice.\"",[10,292,293],{},"Así que un usuario que cierra sesión en el móvil también queda fuera en el\nportátil, hasta una hora después, cuando la pulsera del portátil caduca y su\nticket vuelve como desconocido. Desde su lado, parece que tu app lo echó sin\nmotivo.",[10,295,296],{},"Si cada dispositivo debe seguir conectado por su cuenta, la llamada de cierre de\nsesión tiene que terminar solo la sesión desde la que se hizo:",[14,298,301],{"className":299,"code":300,"language":19},[17],"await supabase.auth.signOut({ scope: 'local' })\n",[21,302,300],{"__ignoreMap":23},[10,304,305],{},"Pídele a tu builder que use esa forma en todos los sitios donde tu app cierra la\nsesión de la gente. La otra forma habitual de terminar una sesión es un cambio de\ncontraseña, que la guía de sesiones de Supabase incluye entre las cosas que\nterminan una.",[261,307],{"alt":308,"caption":309,"src":310},"Dos paneles. En el izquierdo, un móvil cierra sesión con signOut() y los tickets del móvil, un portátil y una tableta quedan todos rotos. En el derecho, el móvil cierra sesión con scope local, y solo se rompe el ticket del móvil mientras el portátil y la tableta conservan los suyos.","Sin scope, un cierre de sesión termina la sesión en todos los dispositivos. Con scope local, termina solo la del que tienes en la mano.","\u002Fblog\u002Fsupabase-users-logged-out\u002Fone-sign-out-every-device-1600x720.png",[36,312,314],{"id":313},"causa-2-el-ticket-nuevo-nunca-llegó-al-navegador","Causa 2: el ticket nuevo nunca llegó al navegador",[10,316,317,318,321,322,324,325,109],{},"Si tu app genera las páginas en un servidor, como hace una app de Next.js, ese\nservidor también renueva sesiones. Lee el ticket de una cookie, lo cambia en el\nmostrador y tiene que devolver el ticket nuevo al navegador en una cabecera\n",[21,319,320],{},"Set-Cookie"," de su respuesta. Cuando esa cookie no llega, el navegador se queda\ncon el ticket viejo, y la siguiente petición presenta un ticket que el servidor\nya cambió. Antes de diez segundos, eso pasa. Después es ",[21,323,237],{}," y la\nsesión termina, y un día más tarde el mismo ticket es ",[21,326,246],{},[10,328,329,330,335,336,339],{},"La\n",[55,331,334],{"href":332,"rel":333},"https:\u002F\u002Fsupabase.com\u002Fdocs\u002Fguides\u002Fauth\u002Fserver-side\u002Fcreating-a-client",[59],"guía de Supabase para crear un cliente de servidor","\nnombra el error que provoca esto en una app de Next.js. El código que renueva la\nsesión, que Next.js 16 llama Proxy y las versiones anteriores middleware, tiene\nque devolver la respuesta en la que se escribieron las cookies nuevas: \"An\nearlier response doesn't carry the refreshed cookies, so the user is signed out\non the next request.\" La misma guía advierte que en Next.js 15 y anteriores un\narchivo llamado ",[21,337,338],{},"proxy.ts"," nunca se ejecuta, \"so sessions never refresh and\nusers get signed out\". Un asistente que trabaja con la documentación más nueva\npuede escribir justo ese archivo en una app más antigua.",[10,341,342,343,351,352,357],{},"La guía de Supabase también explica por qué este cuesta encontrarlo: \"it looks\nlike users being signed out at random rather than an error in your code.\" Los\nreportes suenan igual. Un desarrollador escribió en el\n",[55,344,347,348],{"href":345,"rel":346},"https:\u002F\u002Fgithub.com\u002Fsupabase\u002Fssr\u002Fissues\u002F68",[59],"issue tracker de ",[21,349,350],{},"@supabase\u002Fssr","\n\"I'm inadvertently logged out of my web application every day\", y el ingeniero\nde Supabase que respondió señaló cómo el middleware pasaba su respuesta. El\n",[55,353,356],{"href":354,"rel":355},"https:\u002F\u002Fgithub.com\u002Fsupabase\u002Fsupabase-swift\u002Fissues\u002F486",[59],"reporte de una app de iOS","\ndescribe la desconexión llegando \"2-3 days after logging in\", y se cerró después\nde que unas actualizaciones de la librería de Swift de Supabase redujeran los\nreportes.",[261,359],{"alt":360,"caption":361,"src":362},"Un navegador envía su ticket a un servidor. El servidor lo cambia en el mostrador y recibe uno nuevo. La respuesta que lleva el ticket nuevo al navegador se corta antes de llegar, así que el navegador envía otra vez el ticket viejo, y el mostrador lo rechaza como ya usado.","El servidor renovó la sesión y el ticket nuevo nunca volvió. El navegador sigue teniendo el que ya se cambió.","\u002Fblog\u002Fsupabase-users-logged-out\u002Fnew-ticket-lost-1600x640.png",[10,364,365,366,368],{},"Para comprobarlo, abre las herramientas de desarrollo de tu navegador en una\npágina que haya renovado la sesión y mira la respuesta en la pestaña Network.\nDebería llevar un ",[21,367,320],{}," con el token nuevo, y la siguiente petición\ndebería enviar ese token de vuelta.",[36,370,372],{"id":371},"causa-3-un-límite-de-sesión-en-tus-ajustes-de-auth","Causa 3: un límite de sesión en tus ajustes de Auth",[10,374,375],{},"Desde el plan Pro, Supabase puede terminar sesiones a propósito, desde la página\nSessions de los ajustes de Auth de tu proyecto. Hay tres límites, y la guía de\nsesiones describe cada uno:",[377,378,379,386,392],"ul",{},[380,381,382,385],"li",{},[31,383,384],{},"Las sesiones con límite de tiempo"," terminan pasado un tiempo fijo, por\nactivo que esté el usuario.",[380,387,388,391],{},[31,389,390],{},"Un tiempo de inactividad"," termina una sesión que lleva un rato sin\nrenovarse.",[380,393,394,397],{},[31,395,396],{},"Una sola sesión por usuario"," conserva solo el inicio de sesión más\nreciente, así que entrar desde el móvil termina la sesión en el portátil.",[10,399,400,401,404,405,407,408,410,411,413],{},"Estos lo dicen en su propio mensaje, ",[21,402,403],{},"Session Expired",", con ",[21,406,188],{}," o\n",[21,409,192],{}," detrás en el segundo y el tercero. La guía añade que\nlas sesiones terminadas se borran 24 horas después, así que un ticket de una de\nellas que vuelve pasado ese plazo recibe ",[21,412,246],{}," en su lugar.",[10,415,416],{},"Si pusiste uno de estos, las desconexiones son el ajuste haciendo su trabajo. Si\nno era tu intención, o lo activó una plantilla o un asistente por ti, la página\nmuestra qué límite está activo.",[36,418,420],{"id":419},"el-caso-inofensivo","El caso inofensivo",[10,422,423,424,426],{},"Un ticket de una sesión que ya terminó, que llega desde una pestaña vieja o desde\nun navegador que nadie abrió en semanas, también recibe\n",[21,425,66],{},". La persona detrás ya estaba desconectada. Su navegador\npreguntó una vez, Supabase dijo que no, y ve tu página de inicio de sesión, que\nes lo que habría visto de todos modos.",[10,428,429],{},"En un log se ve así: una línea para un usuario, a menudo poco después de que ese\nusuario cerrara sesión en algún sitio, y luego nada más de él. Líneas así son el\nerror haciendo su trabajo. Las que merece la pena seguir son las mismas personas\nuna y otra vez, o un montón de personas distintas el mismo día.",[36,431,433],{"id":432},"cómo-leer-tus-logs-de-auth-para-esto","Cómo leer tus logs de Auth para esto",[10,435,436,437,440,441,444,445,447,448,451],{},"Abre ",[31,438,439],{},"Logs"," en tu panel de Supabase, elige el tipo de log ",[31,442,443],{},"Auth"," y busca\n",[21,446,52],{}," en los mensajes de evento. Selecciona una fila para ver sus\ndetalles, incluido el ID del usuario, y el filtro ",[31,449,450],{},"User"," reduce entonces la\nlista a esa persona. Hasta dónde llegan los logs depende de tu plan, así que\nléelos mientras las quejas son recientes.",[10,453,454],{},"Luego lee lo que se repite:",[127,456,457,467],{},[130,458,459],{},[133,460,461,464],{},[136,462,463],{},"Lo que ves en el log de Auth",[136,465,466],{},"A qué apunta",[146,468,469,480,490,503],{},[133,470,471,477],{},[151,472,473,474,476],{},"Un ",[21,475,66],{}," para un usuario, y nada después",[151,478,479],{},"Una pestaña vieja o un navegador que vuelve. Inofensivo.",[133,481,482,487],{},[151,483,484,486],{},[21,485,66],{}," para un usuario poco después de cerrar sesión en otro sitio",[151,488,489],{},"Causa 1, un cierre de sesión que terminó todos los dispositivos",[133,491,492,500],{},[151,493,494,496,497,499],{},[21,495,237],{},", y un día después ",[21,498,66],{},", para los mismos usuarios, una y otra vez",[151,501,502],{},"Causa 2, tickets nuevos que no llegan al navegador",[133,504,505,510],{},[151,506,507,509],{},[21,508,403],{},", con un motivo entre paréntesis",[151,511,512],{},"Causa 3, un límite de sesión",[10,514,515,516,519],{},"También puedes preguntarle a la base de datos con qué frecuencia inicia sesión\nla gente. Cada inicio de sesión añade una fila a ",[21,517,518],{},"auth.sessions",", y un cierre de\nsesión la borra. Pega esto en el SQL Editor:",[14,521,524],{"className":522,"code":523,"language":19},[17],"select user_id, count(*) as sessions, max(created_at) as latest\nfrom auth.sessions\nwhere created_at > now() - interval '14 days'\ngroup by user_id\norder by sessions desc\nlimit 20;\n",[21,525,523],{"__ignoreMap":23},[10,527,528],{},"Un usuario con muchas más sesiones que dispositivos ha iniciado sesión una y otra\nvez sin cerrarla nunca, que es lo que hace alguien a quien tu app echa\nconstantemente.",[36,530,532],{"id":531},"qué-no-cambiar-mientras-lo-arreglas","Qué no cambiar mientras lo arreglas",[10,534,535],{},"Tus reglas de Row Level Security, la detección de reutilización y la duración\ndel access token. Las tres parecen un arreglo y dejan la causa donde está, y\naflojar la primera puede abrir tus datos a desconocidos.",[10,537,538,541,542,545,546,551,553,554,557,558,109],{},[31,539,540],{},"Deja en paz tus reglas de Row Level Security."," Row Level Security es el\nconjunto de reglas que decide qué filas recibe cada visitante. Mientras a los\nusuarios se les cierra la sesión, cada pantalla cuya regla lee ",[21,543,544],{},"auth.uid()"," les\nsale vacía, porque según la\n",[55,547,550],{"href":548,"rel":549},"https:\u002F\u002Fsupabase.com\u002Fdocs\u002Fguides\u002Fdatabase\u002Fpostgres\u002Frow-level-security",[59],"documentación de Supabase",[21,552,544],{}," devuelve ",[21,555,556],{},"null"," cuando la sesión ha caducado. El arreglo tentador es\naflojar esa regla hasta que la pantalla vuelva a llenarse, y entonces también se\nllena para desconocidos. Si alguien ya lo hizo, nuestro escaneo gratuito\npregunta a cada tabla que nombra tu app cuántas filas recibiría un visitante sin\nsesión, lee el número sin descargar una sola fila y tarda unos 20 segundos, sin\ncuenta: ",[55,559,561],{"href":560},"\u002Fsecurity-scanner","escanea tu app",[10,563,564,567,568,570],{},[31,565,566],{},"Deja activada la detección de reutilización."," Supabase te deja apagarla, en\nlos ajustes avanzados de la página Sessions, y lo considera \"generally not\nrecommended\". Con ella apagada, la causa 2 deja de producir ",[21,569,237],{}," y el\nticket nuevo sigue sin llegar al navegador.",[10,572,573,576],{},[31,574,575],{},"Deja el access token en una hora."," Una pulsera más larga retrasa la\ndesconexión y deja su causa en su sitio. La guía de Supabase dice que ponerlo por\nencima de una hora es \"generally discouraged for security reasons\".",[36,578,580],{"id":579},"mantener-las-sesiones-funcionando-después-del-arreglo","Mantener las sesiones funcionando después del arreglo",[10,582,583],{},"Dos cosas pueden seguir saliendo mal cuando paran las desconexiones: un cambio\nposterior las trae de vuelta, y una regla aflojada por el camino deja una tabla\nabierta a desconocidos.",[10,585,586],{},[31,587,588,592],{},[55,589,591],{"href":590},"\u002Fwebsite-monitoring","Reeve Monitor"," sigue comprobando desde fuera:",[377,594,595,598,601,608],{},[380,596,597],{},"si tu app está en línea, comprobado cada minuto, para que una ola de mensajes de \"me sacó de la sesión\" no se confunda con una caída",[380,599,600],{},"la comprobación de seguridad completa cada hora, en hasta tres apps",[380,602,603,604,607],{},"un mensaje cuando un resultado ",[31,605,606],{},"cambia",", para que una regla aflojada durante un arreglo no espere a que mires",[380,609,610],{},"un informe mensual, en lenguaje claro, de lo que vio",[10,612,613],{},[31,614,615,619],{},[55,616,618],{"href":617},"\u002Fsupabase-backups","Reeve Care"," guarda una copia de tu base de datos de Supabase:",[377,621,622,625,628,631,634,637],{},[380,623,624],{},"una copia cifrada según una programación, en el almacenamiento propio de Reeve en la UE, fuera de tu cuenta de Supabase",[380,626,627],{},"el esquema auth en el que viven las cuentas de tus usuarios, para que vuelvan con todo lo demás",[380,629,630],{},"cada copia verificada antes de contar, contando las filas de cada tabla",[380,632,633],{},"también tus archivos subidos, en cuanto conectes una credencial de Storage",[380,635,636],{},"una restauración en un clic, con una copia de seguridad del estado actual tomada antes",[380,638,639],{},"todo lo que hace Monitor",[36,641,643],{"id":642},"qué-hacer-ahora","Qué hacer ahora",[645,646,647],"key-takeaways",{},[377,648,649,655,661,667,670,673],{},[380,650,651,652,654],{},"Busca ",[21,653,52],{}," en tus logs de Auth y lee qué mensaje se repite, y con quién.",[380,656,657,658,109],{},"Si un cierre de sesión en un dispositivo termina los demás, cámbialo a ",[21,659,660],{},"signOut({ scope: 'local' })",[380,662,663,664,666],{},"Si tu app genera páginas en un servidor, asegúrate de que la respuesta que renovó una sesión es la que llega al navegador, y busca su ",[21,665,320],{}," en la pestaña Network.",[380,668,669],{},"Mira en la página Sessions de tus ajustes de Auth si hay un límite de tiempo, un tiempo de inactividad o una sola sesión por usuario que nadie quiso activar.",[380,671,672],{},"Deja Row Level Security, la detección de reutilización y la duración del access token como están mientras lo arreglas.",[380,674,675,676,678],{},"Trata un ",[21,677,246],{}," por usuario como ruido, y a los mismos usuarios cada día como un fallo.",[10,680,681,682,684],{},"Empieza por el log de Auth, porque el mensaje que se repite te dice cuál de las\ntres causas tienes. Después, cuando la gente siga conectada,\n",[55,683,561],{"href":560}," para comprobar que no se aflojó nada por el\ncamino.",{"title":23,"searchDepth":686,"depth":686,"links":687},3,[688,690,691,692,693,694,695,696,697,698,699],{"id":38,"depth":689,"text":39},2,{"id":112,"depth":689,"text":113},{"id":227,"depth":689,"text":228},{"id":268,"depth":689,"text":269},{"id":313,"depth":689,"text":314},{"id":371,"depth":689,"text":372},{"id":419,"depth":689,"text":420},{"id":432,"depth":689,"text":433},{"id":531,"depth":689,"text":532},{"id":579,"depth":689,"text":580},{"id":642,"depth":689,"text":643},"Fundamentos de seguridad","\u002Fblog\u002Fsupabase-users-logged-out\u002Fcover-1200x630.png","Un ticket roto en dos, con la mitad izquierda iluminada y la derecha apagada y cayendo, delante de una fila de tickets enteros.","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.",false,"md",[707,710,713,716,719],{"q":708,"a":709},"¿Por qué mis usuarios de Supabase se desconectan sin motivo aparente?","Porque la sesión detrás de su refresh token terminó, y el access token que todavía tienen los mantiene conectados hasta una hora, así que la desconexión llega después de su causa. Las razones habituales son un cierre de sesión en otro dispositivo, un token renovado que nunca llegó al navegador y un límite de sesión en tus ajustes de Auth. El log de Auth muestra cuál es.",{"q":711,"a":712},"¿Qué es un refresh token?","Es la mitad duradera de una sesión de Supabase. El access token demuestra quién es el usuario durante una hora por defecto, y antes de que caduque tu app cambia el refresh token por un par nuevo. Cada refresh token funciona una vez, y el viejo deja de funcionar diez segundos después del cambio.",{"q":714,"a":715},"¿\"Refresh Token Not Found\" es señal de un ataque?","Por sí solo, no. Significa que Supabase Auth rechazó un token del que no tiene registro, que es la respuesta correcta. La protección contra un token robado es la detección de reutilización, que termina una sesión cuando vuelve un token viejo, y Supabase recomienda dejarla activada.",{"q":717,"a":718},"¿Por qué solo pasa en producción?","Porque la mayoría de las causas necesitan tiempo o un segundo dispositivo. Un token caducado se rechaza como ya usado el primer día y como no encontrado cuando su registro se borra un día después, y una sesión que abres mientras construyes rara vez dura tanto. Además, los usuarios reales entran desde varios dispositivos, y ahí es donde aparece un cierre de sesión que termina todas las sesiones.",{"q":720,"a":721},"¿Cuánto dura una sesión de Supabase?","Por defecto, hasta que el usuario cierra sesión. El access token dura una hora si no lo cambias, y el refresh token nunca caduca, pero solo funciona una vez. Desde el plan Pro puedes limitar las sesiones por tiempo total, por inactividad o a una por usuario, en la página Sessions de tus ajustes de Auth.","\u002Fblog\u002Fsupabase-users-logged-out\u002Fcard-800x500.png",[724,725,70,726,727,728,729],"invalid refresh token refresh token not found","supabase invalid refresh token","supabase cierra la sesión de los usuarios","supabase refresh token already used","supabase session expired","supabase ssr error refresh token",{},true,"\u002Fblog\u002Fsupabase-users-logged-out","2026-10-19",{"title":5,"description":703},"blog\u002Fsupabase-users-logged-out",[737,738,739],"Supabase \"Invalid Refresh Token: Refresh Token Not Found\" significa que Supabase Auth no tiene registro del refresh token que envió tu app, así que no puede renovar la sesión y el usuario queda desconectado.","Tres cosas borran ese registro: un cierre de sesión (que en JavaScript termina todos los dispositivos por defecto), un token ya usado que llega otra vez y un límite de sesión en tus ajustes de Auth.","Parte de esto es una línea inofensiva de una pestaña vieja. Otra parte desconecta a todos tus usuarios cada día. El log de Auth te dice cuál es, y cada causa tiene su propio arreglo.","cGOLS-LL0WbIYkf8zH38lY_-K10oY10IZwSomhwP1OI",[742,748,754,760,766,772,778,784,790,796,802,809,815,820,826,832,838,844,850,856,862,868,869,875,881,886,892,898,904,910,916,922,928,934,940,946,952,958,964,970,975,981,987,993,999,1005,1010,1016,1022,1028,1034,1040,1046,1052,1058,1064,1070,1076,1082,1088,1094,1100,1106,1112,1118,1124,1130,1136,1142,1148,1154,1160,1166,1172,1178,1184,1190,1196,1202,1208,1214,1220,1226,1232,1237,1243,1249,1255,1260,1266],{"path":743,"title":744,"description":745,"published":746,"category":700,"image":747,"draft":704},"\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":749,"title":750,"description":751,"published":752,"category":700,"image":753,"draft":704},"\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":755,"title":756,"description":757,"published":758,"category":700,"image":759,"draft":704},"\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":761,"title":762,"description":763,"published":764,"category":700,"image":765,"draft":704},"\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":767,"title":768,"description":769,"published":770,"category":700,"image":771,"draft":704},"\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":773,"title":774,"description":775,"published":776,"category":700,"image":777,"draft":704},"\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":779,"title":780,"description":781,"published":782,"category":700,"image":783,"draft":704},"\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":785,"title":786,"description":787,"published":788,"category":700,"image":789,"draft":704},"\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":791,"title":792,"description":793,"published":794,"category":700,"image":795,"draft":704},"\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":797,"title":798,"description":799,"published":800,"category":700,"image":801,"draft":704},"\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":803,"title":804,"description":805,"published":806,"category":807,"image":808,"draft":704},"\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":810,"title":811,"description":812,"published":813,"category":700,"image":814,"draft":704},"\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":107,"title":816,"description":817,"published":818,"category":700,"image":819,"draft":704},"\"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":821,"title":822,"description":823,"published":824,"category":700,"image":825,"draft":704},"\u002Fblog\u002Fedge-function-returned-a-non-2xx-status-code","\"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":827,"title":828,"description":829,"published":830,"category":700,"image":831,"draft":704},"\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":833,"title":834,"description":835,"published":836,"category":700,"image":837,"draft":704},"\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":839,"title":840,"description":841,"published":842,"category":700,"image":843,"draft":704},"\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":845,"title":846,"description":847,"published":848,"category":700,"image":849,"draft":704},"\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":851,"title":852,"description":853,"published":854,"category":700,"image":855,"draft":704},"\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":857,"title":858,"description":859,"published":860,"category":700,"image":861,"draft":704},"\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":863,"title":864,"description":865,"published":866,"category":700,"image":867,"draft":704},"\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":732,"title":5,"description":703,"published":733,"category":700,"image":722,"draft":704},{"path":870,"title":871,"description":872,"published":873,"category":700,"image":874,"draft":704},"\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":876,"title":877,"description":878,"published":879,"category":700,"image":880,"draft":704},"\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":98,"title":882,"description":883,"published":884,"category":700,"image":885,"draft":704},"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":887,"title":888,"description":889,"published":890,"category":700,"image":891,"draft":704},"\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":893,"title":894,"description":895,"published":896,"category":700,"image":897,"draft":704},"\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":899,"title":900,"description":901,"published":902,"category":700,"image":903,"draft":704},"\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":905,"title":906,"description":907,"published":908,"category":700,"image":909,"draft":704},"\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":911,"title":912,"description":913,"published":914,"category":700,"image":915,"draft":704},"\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":917,"title":918,"description":919,"published":920,"category":700,"image":921,"draft":704},"\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":923,"title":924,"description":925,"published":926,"category":700,"image":927,"draft":704},"\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":929,"title":930,"description":931,"published":932,"category":700,"image":933,"draft":704},"\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":935,"title":936,"description":937,"published":938,"category":700,"image":939,"draft":704},"\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":941,"title":942,"description":943,"published":944,"category":700,"image":945,"draft":704},"\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":947,"title":948,"description":949,"published":950,"category":700,"image":951,"draft":704},"\u002Fblog\u002Fmove-a-secret-to-an-edge-function","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":953,"title":954,"description":955,"published":956,"category":700,"image":957,"draft":704},"\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":959,"title":960,"description":961,"published":962,"category":700,"image":963,"draft":704},"\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":965,"title":966,"description":967,"published":968,"category":700,"image":969,"draft":704},"\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":222,"title":971,"description":972,"published":973,"category":700,"image":974,"draft":704},"¿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":976,"title":977,"description":978,"published":979,"category":700,"image":980,"draft":704},"\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":982,"title":983,"description":984,"published":985,"category":700,"image":986,"draft":704},"\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":988,"title":989,"description":990,"published":991,"category":807,"image":992,"draft":704},"\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":994,"title":995,"description":996,"published":997,"category":807,"image":998,"draft":704},"\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":1000,"title":1001,"description":1002,"published":1003,"category":807,"image":1004,"draft":704},"\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":1006,"title":1007,"description":1008,"published":1003,"category":700,"image":1009,"draft":704},"\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":1011,"title":1012,"description":1013,"published":1014,"category":700,"image":1015,"draft":704},"\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":1017,"title":1018,"description":1019,"published":1020,"category":700,"image":1021,"draft":704},"\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":1023,"title":1024,"description":1025,"published":1026,"category":807,"image":1027,"draft":704},"\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":1029,"title":1030,"description":1031,"published":1032,"category":700,"image":1033,"draft":704},"\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":1035,"title":1036,"description":1037,"published":1038,"category":700,"image":1039,"draft":704},"\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":1041,"title":1042,"description":1043,"published":1044,"category":700,"image":1045,"draft":704},"\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":1047,"title":1048,"description":1049,"published":1050,"category":700,"image":1051,"draft":704},"\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":1053,"title":1054,"description":1055,"published":1056,"category":700,"image":1057,"draft":704},"\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":1059,"title":1060,"description":1061,"published":1062,"category":700,"image":1063,"draft":704},"\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":1065,"title":1066,"description":1067,"published":1068,"category":700,"image":1069,"draft":704},"\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":1071,"title":1072,"description":1073,"published":1074,"category":700,"image":1075,"draft":704},"\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":1077,"title":1078,"description":1079,"published":1080,"category":807,"image":1081,"draft":704},"\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":1083,"title":1084,"description":1085,"published":1086,"category":807,"image":1087,"draft":704},"\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":1089,"title":1090,"description":1091,"published":1092,"category":807,"image":1093,"draft":704},"\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":1095,"title":1096,"description":1097,"published":1098,"category":700,"image":1099,"draft":704},"\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":1101,"title":1102,"description":1103,"published":1104,"category":700,"image":1105,"draft":704},"\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":1107,"title":1108,"description":1109,"published":1110,"category":700,"image":1111,"draft":704},"\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":1113,"title":1114,"description":1115,"published":1116,"category":700,"image":1117,"draft":704},"\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":1119,"title":1120,"description":1121,"published":1122,"category":700,"image":1123,"draft":704},"\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":1125,"title":1126,"description":1127,"published":1128,"category":700,"image":1129,"draft":704},"\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":1131,"title":1132,"description":1133,"published":1134,"category":700,"image":1135,"draft":704},"\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":1137,"title":1138,"description":1139,"published":1140,"category":700,"image":1141,"draft":704},"\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":1143,"title":1144,"description":1145,"published":1146,"category":700,"image":1147,"draft":704},"\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":1149,"title":1150,"description":1151,"published":1152,"category":700,"image":1153,"draft":704},"\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":1155,"title":1156,"description":1157,"published":1158,"category":807,"image":1159,"draft":704},"\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":1161,"title":1162,"description":1163,"published":1164,"category":700,"image":1165,"draft":704},"\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":1167,"title":1168,"description":1169,"published":1170,"category":807,"image":1171,"draft":704},"\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":1173,"title":1174,"description":1175,"published":1176,"category":700,"image":1177,"draft":704},"\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":1179,"title":1180,"description":1181,"published":1182,"category":700,"image":1183,"draft":704},"\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":1185,"title":1186,"description":1187,"published":1188,"category":700,"image":1189,"draft":704},"\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":1191,"title":1192,"description":1193,"published":1194,"category":700,"image":1195,"draft":704},"\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":1197,"title":1198,"description":1199,"published":1200,"category":807,"image":1201,"draft":704},"\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":1203,"title":1204,"description":1205,"published":1206,"category":807,"image":1207,"draft":704},"\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":1209,"title":1210,"description":1211,"published":1212,"category":700,"image":1213,"draft":704},"\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":1215,"title":1216,"description":1217,"published":1218,"category":700,"image":1219,"draft":704},"\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":1221,"title":1222,"description":1223,"published":1224,"category":807,"image":1225,"draft":704},"\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":1227,"title":1228,"description":1229,"published":1230,"category":700,"image":1231,"draft":704},"\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":1233,"title":1234,"description":1235,"published":1230,"category":700,"image":1236,"draft":704},"\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":1238,"title":1239,"description":1240,"published":1241,"category":700,"image":1242,"draft":704},"\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":1244,"title":1245,"description":1246,"published":1247,"category":807,"image":1248,"draft":704},"\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":1250,"title":1251,"description":1252,"published":1253,"category":700,"image":1254,"draft":704},"\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":1256,"title":1257,"description":1258,"published":1253,"category":807,"image":1259,"draft":704},"\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":1261,"title":1262,"description":1263,"published":1264,"category":807,"image":1265,"draft":704},"\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":89,"title":1267,"description":1268,"published":1264,"category":700,"image":1269,"draft":704},"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",1794213645208]