[{"data":1,"prerenderedAt":1135},["ShallowReactive",2],{"blog-es-supabase-invalid-login":3,"blog-index-es":606},{"id":4,"title":5,"body":6,"category":564,"cover":565,"coverAlt":566,"description":567,"draft":568,"extension":569,"faq":570,"image":586,"keywords":587,"meta":594,"navigation":595,"ogTitle":596,"path":597,"published":598,"seo":599,"stem":600,"tldr":601,"updated":598,"__hash__":605},"blog_es\u002Fblog\u002Fsupabase-invalid-login.md","\"Invalid login credentials\" en Supabase: las tres causas",{"type":7,"value":8,"toc":551},"minimark",[9,13,24,27,35,40,43,64,94,97,101,104,110,116,122,131,137,141,148,161,168,174,177,255,262,266,269,272,275,288,292,295,306,332,335,339,342,345,348,357,367,377,384,389,393,396,432,435,441,444,448,451,460,474,483,503,507,538],[10,11,12],"p",{},"Alguien te escribe para decirte que no puede entrar en tu app, y está seguro de\nque la contraseña es correcta. Pruebas su dirección tú mismo y ves la misma línea\nroja debajo del formulario:",[14,15,20],"pre",{"className":16,"code":18,"language":19},[17],"language-text","Invalid login credentials\n","text",[21,22,18],"code",{"__ignoreMap":23},"",[10,25,26],{},"Supabase responde \"Invalid login credentials\" ante una contraseña incorrecta. Y\nresponde exactamente con las mismas palabras a otras dos personas: alguien cuyo\nemail no tiene cuenta en tu app y alguien que se registró con Google y nunca ha\ntenido contraseña. Tu app de Lovable, Bolt, v0, Cursor, Claude Code o Replit\nfunciona tal como se construyó. Da una sola respuesta a tres situaciones\ndistintas.",[10,28,29,30,34],{},"Esta es la parte que el arreglo obvio hace mal: ",[31,32,33],"strong",{},"el mensaje vago es deliberado,\ny hacerlo más útil abre un agujero."," Lo que ayuda es averiguar cuál de los tres\ncasos tienes delante, algo que tu propio panel te puede decir, mientras el\nformulario sigue diciendo tan poco como ahora.",[36,37,39],"h2",{"id":38},"qué-significa-invalid-login-credentials-en-supabase","¿Qué significa \"Invalid login credentials\" en Supabase?",[10,41,42],{},"Significa que Supabase Auth, la parte de Supabase que inicia la sesión de la\ngente, rechazó un email y una contraseña y se guarda el motivo.",[10,44,45,46,53,54,57,58,63],{},"El mensaje está escrito en el propio código del servidor de Auth. En\n",[47,48,52],"a",{"href":49,"rel":50},"https:\u002F\u002Fgithub.com\u002Fsupabase\u002Fauth\u002Fblob\u002Fmaster\u002Finternal\u002Fapi\u002Ftoken.go",[51],"nofollow","la función que gestiona un inicio de sesión con contraseña",",\nel mismo error se devuelve en tres puntos: cuando ningún usuario tiene ese email,\ncuando el usuario no tiene contraseña guardada y cuando la contraseña no\ncoincide. Cada vez la respuesta es HTTP 400 con el código ",[21,55,56],{},"invalid_credentials",",\nque la\n",[47,59,62],{"href":60,"rel":61},"https:\u002F\u002Fsupabase.com\u002Fdocs\u002Fguides\u002Fauth\u002Fdebugging\u002Ferror-codes",[51],"referencia de códigos de error","\nde Supabase describe como \"Login credentials or grant type not recognized.\"",[10,65,66,67,70,71,74,75,78,79,81,82,84,85,88,89,93],{},"En tu app llega como un ",[21,68,69],{},"AuthApiError"," de ",[21,72,73],{},"signInWithPassword",", con ",[21,76,77],{},"error.code","\nigual a ",[21,80,56],{},". La misma referencia indica identificar los errores\nsiempre por ",[21,83,77],{}," y ",[21,86,87],{},"error.name",", y eso importa más adelante, cuando\ndecidas qué dice tu formulario. Si tus usuarios tenían la sesión iniciada y\nluego los echó, es otro error con\n",[47,90,92],{"href":91},"\u002Fblog\u002Fsupabase-users-logged-out","una página propia",".",[10,95,96],{},"Piensa en tu formulario como el teclado junto a la puerta de un edificio. Alguien\nmarca un código y una luz responde. La verde abre la puerta. La roja se enciende\ncon un código equivocado, con un código que nunca se le dio a nadie y con un\nvecino que siempre ha entrado por la puerta lateral con su tarjeta y nunca ha\ntenido código.",[36,98,100],{"id":99},"las-tres-personas-detrás-del-mensaje","Las tres personas detrás del mensaje",[10,102,103],{},"Cada una necesita un arreglo distinto, así que conviene saber cuál tienes\ndelante.",[10,105,106,109],{},[31,107,108],{},"La contraseña incorrecta."," La cuenta existe, tiene contraseña, y lo que\nescribió no coincide. Eso incluye una contraseña cambiada en otro dispositivo y\nolvidada después, y un gestor de contraseñas que rellena una antigua.",[10,111,112,115],{},[31,113,114],{},"La dirección sin cuenta."," Nadie en tu proyecto se registró con ese email.\nPuede ser una segunda dirección (la del trabajo, cuando se registró con la\npersonal) o un error al escribir durante el registro que ahora está guardado\ncomo su email.",[10,117,118,121],{},[31,119,120],{},"La cuenta sin contraseña."," Se registró con Google, GitHub u otro botón de\ninicio de sesión, y Supabase nunca guardó una contraseña para ella. Cuando vuelve\ny escribe una, no hay nada con qué compararla. El servidor de Auth comprueba que\nhaya una contraseña guardada antes de mirar la escrita, y una cuenta sin ella\nrecibe el mismo 400.",[10,123,124,125,130],{},"Quien se registró con un magic link, el enlace de inicio de sesión enviado por\nemail, es una variante del primer caso. El\n",[47,126,129],{"href":127,"rel":128},"https:\u002F\u002Fgithub.com\u002Fsupabase\u002Fauth\u002Fblob\u002Fmaster\u002Finternal\u002Fapi\u002Fmagic_link.go",[51],"código de magic link","\nde Supabase crea la cuenta con una contraseña aleatoria que nunca se le muestra a\nnadie, así que cualquier contraseña que escriba es incorrecta.",[132,133],"diagram",{"alt":134,"caption":135,"src":136},"Tres personas van hacia el mismo teclado: una con una llave que no encaja, otra dibujada con contorno discontinuo porque no tiene cuenta, y otra con una tarjeta en lugar de llave. Sus tres caminos se juntan en el teclado, su única luz se enciende en rojo y sale una única respuesta idéntica.","Una contraseña incorrecta, una dirección desconocida y una cuenta sin contraseña llegan a la misma comprobación y reciben la misma respuesta.","\u002Fblog\u002Fsupabase-invalid-login\u002Fthree-people-one-light-1600x760.png",[36,138,140],{"id":139},"cómo-sé-cuál-de-los-tres-fue","¿Cómo sé cuál de los tres fue?",[10,142,143,144,147],{},"Pregúntale a tu propia base de datos. Cada cuenta de tu proyecto es una fila en\nuna tabla llamada ",[21,145,146],{},"auth.users",", y esa fila dice cómo inicia sesión la persona.",[10,149,150,151,156,157,160],{},"La ",[47,152,155],{"href":153,"rel":154},"https:\u002F\u002Fsupabase.com\u002Fdocs\u002Fguides\u002Fauth\u002Fusers",[51],"guía de usuarios"," de Supabase\ndescribe el campo que importa: según Supabase, ",[21,158,159],{},"app_metadata.providers"," es la\nlista de proveedores con los que el usuario puede iniciar sesión. Un usuario\npuede tener varias identidades, así que una misma persona puede tener contraseña\ny también Google. Si hay una contraseña guardada lo dice la columna que el propio\nservidor comprueba.",[10,162,163,164,167],{},"Abre el ",[31,165,166],{},"SQL Editor"," en tu panel de Supabase y ejecuta esto con la dirección\nque te dio la persona:",[14,169,172],{"className":170,"code":171,"language":19},[17],"select email,\n       raw_app_meta_data->'providers' as signs_in_with,\n       coalesce(encrypted_password, '') \u003C> '' as has_password,\n       email_confirmed_at\nfrom auth.users\nwhere lower(email) = lower('person@example.com');\n",[21,173,171],{"__ignoreMap":23},[10,175,176],{},"Lee una fila y nunca muestra una contraseña, solo si existe.",[178,179,180,193],"table",{},[181,182,183],"thead",{},[184,185,186,190],"tr",{},[187,188,189],"th",{},"Qué devuelve",[187,191,192],{},"Qué significa",[194,195,196,205,232,244],"tbody",{},[184,197,198,202],{},[199,200,201],"td",{},"Ninguna fila",[199,203,204],{},"Ninguna cuenta tiene esa dirección en este proyecto. Pregunta qué otra dirección pudo usar",[184,206,207,229],{},[199,208,209,212,213,216,217,220,221,224,225,228],{},[21,210,211],{},"has_password"," es ",[21,214,215],{},"false",", y ",[21,218,219],{},"signs_in_with"," incluye ",[21,222,223],{},"google",", ",[21,226,227],{},"github"," o algo parecido",[199,230,231],{},"Se registró con ese botón y nunca ha tenido contraseña",[184,233,234,241],{},[199,235,236,212,238],{},[21,237,211],{},[21,239,240],{},"true",[199,242,243],{},"La contraseña que escribió es incorrecta, o se registró con un magic link y nunca supo la suya",[184,245,246,252],{},[199,247,248,251],{},[21,249,250],{},"email_confirmed_at"," está vacío",[199,253,254],{},"Aún no ha confirmado su email, lo que se convierte en otro mensaje cuando la contraseña es correcta",[10,256,257,258,261],{},"Si prefieres hacer clic a escribir, ",[31,259,260],{},"Authentication → Users"," en el panel\nmuestra las mismas cuentas.",[36,263,265],{"id":264},"la-cuenta-que-nunca-tuvo-contraseña","La cuenta que nunca tuvo contraseña",[10,267,268],{},"Mándala de vuelta al botón que usó, o deja que cree una contraseña con tu enlace\npara restablecerla.",[10,270,271],{},"En el teclado, este es el vecino que siempre usó la puerta lateral. No hay\nningún código guardado para él, así que cualquier código que pruebe se rechaza.",[10,273,274],{},"El primer arreglo está en tu página de inicio de sesión. Si el registro ofrece\n\"Continuar con Google\", la página de inicio de sesión necesita el mismo botón,\npara que quien se registró con él lo vea cuando vuelva.",[10,276,277,278,283,284,287],{},"Si además quiere una contraseña, tu enlace \"¿Olvidaste tu contraseña?\" ya lo\nhace. El\n",[47,279,282],{"href":280,"rel":281},"https:\u002F\u002Fgithub.com\u002Fsupabase\u002Fauth\u002Fblob\u002Fmaster\u002Finternal\u002Fapi\u002Frecover.go",[51],"código de restablecimiento de contraseña","\nde Supabase envía el email de recuperación a cualquier cuenta que encuentre, sin\ncomprobar cómo se creó. Un usuario de Google que sigue el enlace y define una\ncontraseña nueva puede entrar de las dos formas después. Eso depende de que tu\napp tenga la página que define la contraseña nueva, que llama a\n",[21,285,286],{},"supabase.auth.updateUser({ password })",". Si el enlace lleva a una página que\nnunca la pide, la persona entra una vez y sigue sin contraseña.",[36,289,291],{"id":290},"email-not-confirmed-significa-que-la-contraseña-era-correcta","\"Email not confirmed\" significa que la contraseña era correcta",[10,293,294],{},"Es otro mensaje con otro código, y solo le llega a alguien que ya pasó la\ncomprobación de la contraseña.",[10,296,297,298,301,302,305],{},"En la misma función del servidor de Auth, la comprobación del email confirmado\nva después de la de la contraseña. Así que ",[21,299,300],{},"Email not confirmed",", código\n",[21,303,304],{},"email_not_confirmed",", solo vuelve a alguien que escribió un email existente y\nsu contraseña correcta. La referencia de errores de Supabase lo expresa como\n\"Signing in is not allowed for this user as the email address is not\nconfirmed.\" En el teclado es otra luz: el código era correcto, y la puerta sigue\ncerrada hasta que se termine el papeleo de la mudanza.",[10,307,308,309,312,313,318,319,322,323,327,328,93],{},"Lo ves cuando ",[31,310,311],{},"Confirm email"," está activado en el proveedor Email de tu\nproyecto. Según la\n",[47,314,317],{"href":315,"rel":316},"https:\u002F\u002Fsupabase.com\u002Fdocs\u002Fguides\u002Fauth\u002Fgeneral-configuration",[51],"guía de configuración","\nde Supabase, los usuarios tienen que confirmar su dirección de email antes de\niniciar sesión por primera vez. El arreglo es el email en el que aún no hizo\nclic: dile que revise la bandeja de entrada y la carpeta de spam, y ofrece\nreenviarlo con ",[21,320,321],{},"supabase.auth.resend({ type: 'signup', email })",". Si esos emails\nno llegan en absoluto,\n",[47,324,326],{"href":325},"\u002Fblog\u002Fsupabase-email-rate-limit-exceeded","el límite de envío de emails"," es lo\nprimero que hay que descartar. Si llegan y el enlace responde \"Email link is\ninvalid or has expired\",\n",[47,329,331],{"href":330},"\u002Fblog\u002Femail-link-is-invalid-or-has-expired","probablemente algo lo abrió antes que la persona",[10,333,334],{},"Como este mensaje solo aparece tras una contraseña correcta, pedirle a la persona\nque revise su bandeja no le da a un desconocido nada que no tuviera ya.",[36,336,338],{"id":337},"por-qué-el-mensaje-sigue-siendo-vago","Por qué el mensaje sigue siendo vago",[10,340,341],{},"Porque la versión útil les diría a desconocidos quiénes son tus usuarios.",[10,343,344],{},"Imagina que el teclado respondiera \"en el 4B no vive nadie\". Cualquiera que\npasara podría recorrer la lista de pisos y saber cuáles están ocupados.",[10,346,347],{},"Tu formulario funciona igual. Si dijera \"No hay ninguna cuenta con ese email\",\ncualquiera con una lista de direcciones podría escribirlas una a una y saber\ncuáles son de tus usuarios. Una filtración de cualquier otra web deja atrás una\nlista así, a veces con las contraseñas que la gente usaba allí. Quien sabe qué\ndirecciones son tus usuarios sabe de quién vale la pena probar contraseñas\nantiguas en tu app, y quién probablemente abrirá un email que parezca tuyo.",[10,349,350,351,356],{},"Supabase aplica la misma regla en todos los sitios donde se escribe una\ndirección. Su\n",[47,352,355],{"href":353,"rel":354},"https:\u002F\u002Fgithub.com\u002Fsupabase\u002Fauth\u002Fblob\u002Fmaster\u002Finternal\u002Fapi\u002Fsignup.go",[51],"código de registro",",\ncuando se le pide crear una cuenta para un email ya registrado y confirmado,\npuede devolver un usuario inventado con un ID aleatorio, mediante una función\ncuyo comentario dice que existe para evitar que se filtre si un usuario está\nregistrado o no. El restablecimiento de contraseña responde a una dirección\ndesconocida con el mismo éxito vacío que a una conocida.",[10,358,359,360,301,363,366],{},"El lado del registro tiene un interruptor que controlas tú. Ese usuario\ninventado es lo que vuelve cuando se exige confirmación tanto para los registros\npor email como por teléfono. Si cualquiera de las dos está desactivada (la del\nteléfono cuenta aunque tu app nunca use el inicio de sesión por teléfono), una\ndirección registrada recibe ",[21,361,362],{},"User already registered",[21,364,365],{},"user_already_exists",", y tu formulario de registro se convierte en un\ncomprobador de listas por sí mismo.",[10,368,369,370,373,374,376],{},"El mensaje útil, además, tiene que salir de algún sitio. Supabase no le dice a tu\napp si un email tiene cuenta, así que para mostrar \"No hay ninguna cuenta con\nese email\" tu app tiene que averiguarlo por su cuenta. Eso significa una\nconsulta que funcione para un visitante sin sesión: una lectura de tu tabla\n",[21,371,372],{},"profiles"," que solo funciona si cualquiera puede leer los emails que contiene, o\nuna función de base de datos que busca en ",[21,375,146],{}," para quien pregunte. En\ncualquier caso, la lista de tus usuarios pasa a poder leerse desde fuera.",[10,378,379,380,93],{},"La primera de las dos se ve desde fuera, y así es como la busca nuestro escaneo\ngratuito. Pregunta a cada tabla que nombra tu app cuántas filas podría obtener\nun visitante sin sesión, lee solo el recuento y tarda unos 20 segundos, sin\ncuenta: ",[47,381,383],{"href":382},"\u002Fsecurity-scanner","escanea tu app",[132,385],{"alt":386,"caption":387,"src":388},"Una pila de direcciones de email pasa por dos formularios de inicio de sesión. El de arriba nombra las cuentas que no existen, y la pila sale dividida en dos montones, uno marcado con un visto y otro tachado, con el montón marcado resaltado en ámbar. El de abajo da a cada dirección la misma respuesta, y la pila sale como un único montón uniforme.","Un formulario que nombra una cuenta inexistente separa cualquier lista de direcciones en tus usuarios y todos los demás. Una sola respuesta para todos deja la lista como estaba.","\u002Fblog\u002Fsupabase-invalid-login\u002Fthe-list-checker-1600x720.png",[36,390,392],{"id":391},"qué-poner-en-tu-página-de-inicio-de-sesión","Qué poner en tu página de inicio de sesión",[10,394,395],{},"Mantén el mensaje vago y pon todas las formas de volver a entrar en la misma\npágina.",[397,398,399,406,412,418],"ul",{},[400,401,402,405],"li",{},[31,403,404],{},"Un mensaje para los tres casos",", con palabras sencillas y las otras vías al\nlado: \"El email y la contraseña no coinciden. Si te registraste con Google,\nusa el botón de abajo.\" Ayuda al usuario de Google y no le dice a un\ndesconocido nada que tu página de registro no diga ya.",[400,407,408,411],{},[31,409,410],{},"Todos los métodos de tu página de registro."," El botón de Google, el magic\nlink y el campo de contraseña, todos también en la de inicio de sesión.",[400,413,414,417],{},[31,415,416],{},"Un enlace \"¿Olvidaste tu contraseña?\" a una página que la defina."," Supabase\nresponde igual a cualquier dirección ahí, así que es seguro mostrarlo, y es\ncomo alguien sin contraseña consigue una.",[400,419,420,425,426,428,429,431],{},[31,421,422,423,93],{},"Decisiones según ",[21,424,77],{}," Muestra tu mensaje vago para\n",[21,427,56],{},", y \"revisa tu bandeja de entrada\" con un botón de\nreenvío para ",[21,430,304],{},". La referencia de errores de Supabase\nindica identificar los errores por su código en lugar de por el texto del\nmensaje, y advierte de que los códigos de estado HTTP pueden cambiar sin\naviso.",[10,433,434],{},"En código, son dos ramas:",[14,436,439],{"className":437,"code":438,"language":19},[17],"const { error } = await supabase.auth.signInWithPassword({ email, password })\nif (error?.code === 'email_not_confirmed') showCheckYourInbox(email)\nelse if (error) showSignInHelp()\n",[21,440,438],{"__ignoreMap":23},[10,442,443],{},"Si un builder escribe tu página de inicio de sesión, puedes pegarle estos cuatro\npuntos como instrucción, incluido el que dice lo que la página debe callarse.",[36,445,447],{"id":446},"que-tu-página-de-inicio-de-sesión-siga-como-la-dejaste","Que tu página de inicio de sesión siga como la dejaste",[10,449,450],{},"Dos cosas pueden salir mal después de hoy: un prompt posterior reescribe la\npágina para que sea \"más útil\", y un cambio en otra parte pierde las cuentas a\nlas que la página da acceso.",[10,452,453],{},[31,454,455,459],{},[47,456,458],{"href":457},"\u002Fwebsite-monitoring","Reeve Monitor"," sigue comprobando desde fuera:",[397,461,462,465,468,471],{},[400,463,464],{},"las nueve comprobaciones cada hora, en hasta tres apps, incluido cuántas filas puede contar un visitante sin sesión en las tablas que nombra tu app",[400,466,467],{},"un correo el día en que un nuevo escaneo califica tu app peor que el anterior",[400,469,470],{},"si la app está disponible, cada 60 segundos",[400,472,473],{},"un informe mensual, en lenguaje claro, de lo que vio",[10,475,476],{},[31,477,478,482],{},[47,479,481],{"href":480},"\u002Fsupabase-backups","Reeve Care"," guarda una copia de tu base de datos de\nSupabase, con las cuentas incluidas:",[397,484,485,488,491,494,497,500],{},[400,486,487],{},"una copia cifrada según una programación, en el almacenamiento propio de Reeve en la UE, fuera de tu cuenta de Supabase",[400,489,490],{},"el esquema auth en el que viven las cuentas de tus usuarios, para que vuelvan con todo lo demás",[400,492,493],{},"cada copia verificada antes de contar, contando las filas de cada tabla",[400,495,496],{},"también tus archivos subidos, en cuanto conectes una credencial de Storage",[400,498,499],{},"una restauración en un clic, con una copia de seguridad del estado actual tomada antes",[400,501,502],{},"todo lo que hace Monitor",[36,504,506],{"id":505},"qué-hacer-ahora","Qué hacer ahora",[508,509,510],"key-takeaways",{},[397,511,512,518,521,524,527,535],{},[400,513,514,515,517],{},"Ejecuta la consulta sobre ",[21,516,146],{}," con la dirección que te dio la persona y mira cuál de los tres casos es.",[400,519,520],{},"Ninguna fila: pregunta con qué otra dirección pudo registrarse.",[400,522,523],{},"Sin contraseña: indícale el botón de Google o GitHub que usó, o tu enlace para restablecer la contraseña.",[400,525,526],{},"Con contraseña y aun así rechazada: envíale el enlace para restablecerla.",[400,528,529,530,532,533,93],{},"Muestra un mensaje vago para ",[21,531,56],{},", y \"revisa tu bandeja de entrada\" solo para ",[21,534,304],{},[400,536,537],{},"Nunca añadas una comprobación que le diga a un visitante si un email tiene cuenta.",[10,539,540,541,545,546,550],{},"Si el problema empieza un paso antes y las cuentas nuevas no llegan a crearse,\n",[47,542,544],{"href":543},"\u002Fblog\u002Fdatabase-error-saving-new-user","Database error saving new user"," cubre ese\nextremo del formulario. La ",[47,547,549],{"href":548},"\u002Fchecklist","lista de seguridad de 10 minutos"," cubre\nlo que suele quedar abierto en una app recién lanzada.",{"title":23,"searchDepth":552,"depth":552,"links":553},3,[554,556,557,558,559,560,561,562,563],{"id":38,"depth":555,"text":39},2,{"id":99,"depth":555,"text":100},{"id":139,"depth":555,"text":140},{"id":264,"depth":555,"text":265},{"id":290,"depth":555,"text":291},{"id":337,"depth":555,"text":338},{"id":391,"depth":555,"text":392},{"id":446,"depth":555,"text":447},{"id":505,"depth":555,"text":506},"Fundamentos de seguridad","\u002Fblog\u002Fsupabase-invalid-login\u002Fcover-1200x630.png","Un teclado junto a una puerta con su única luz encendida en rojo y, delante, una tarjeta de acceso para la que el teclado no tiene ranura.","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.",false,"md",[571,574,577,580,583],{"q":572,"a":573},"¿Por qué Supabase dice \"Invalid login credentials\" si la contraseña es correcta?","Porque la contraseña puede ser correcta para una cuenta que no existe, o la cuenta puede no tener contraseña. Comprueba que el email sea el que usó para registrarse, y si se registró con Google u otro botón de inicio de sesión, porque entonces la cuenta no tiene contraseña. Una consulta sobre auth.users en el SQL Editor te dice cuál es.",{"q":575,"a":576},"¿\"Invalid login credentials\" significa que la cuenta no existe?","A veces. Supabase devuelve el mismo mensaje, con el mismo código, invalid_credentials, cuando ninguna cuenta tiene ese email, cuando la cuenta no tiene contraseña y cuando la contraseña es incorrecta. El mensaje por sí solo no te dice cuál, y está hecho así.",{"q":578,"a":579},"¿Por qué alguien que se registró con Google no puede entrar con contraseña?","Porque su cuenta se creó sin ella. Supabase comprueba que haya una contraseña guardada antes de comparar la que se escribe, y una cuenta sin contraseña recibe \"Invalid login credentials\". Puede seguir usando el botón de Google o usar el enlace para restablecer la contraseña de tu app y crear una: Supabase envía ese email a cualquier cuenta que encuentre, sin importar cómo se creó.",{"q":581,"a":582},"¿Debería mi formulario decir que el email no está registrado?","No. Ese mensaje le dice a cualquiera que escriba una dirección si pertenece a uno de tus usuarios, y alguien con una lista de direcciones filtradas puede comprobarlas todas. Tu app además necesitaría una consulta que respondiera esa pregunta a desconocidos, y eso es un segundo agujero. Deja el mensaje vago y muestra las otras formas de entrar.",{"q":584,"a":585},"¿Qué diferencia hay entre \"Invalid login credentials\" y \"Email not confirmed\"?","Supabase solo devuelve \"Email not confirmed\" después de comprobar la contraseña y darla por correcta. Así que la persona correcta escribió la contraseña correcta, y la cuenta espera a que haga clic en el enlace de su email de confirmación. Es seguro decirle que revise su bandeja de entrada y ofrecerle reenviar el email.","\u002Fblog\u002Fsupabase-invalid-login\u002Fcard-800x500.png",[588,589,590,591,56,592,593],"invalid login credentials supabase","invalid login credentials","supabase login no funciona","email not confirmed supabase","supabase user enumeration","supabase usuario google sin contraseña",{},true,"\"Invalid login credentials\" en Supabase","\u002Fblog\u002Fsupabase-invalid-login","2026-10-28",{"title":5,"description":567},"blog\u002Fsupabase-invalid-login",[602,603,604],"Supabase responde \"Invalid login credentials\" a tres personas distintas: alguien que escribió mal la contraseña, alguien cuyo email no tiene cuenta y alguien que se registró con Google y nunca tuvo contraseña.","Lo hace a propósito. Un formulario de inicio de sesión que dijera cuál de los tres casos fue le diría a cualquiera con una lista de emails cuáles son de tus usuarios.","Puedes distinguir los tres tú mismo con una consulta sobre auth.users. Tu página de inicio de sesión debería mantener el mensaje vago y ofrecer todas las formas en que pudo crearse la cuenta.","JGEWkn4pLzBZoiKMDXADEgzpFgK04ovyQ2QQv202XWo",[607,613,619,625,631,637,643,649,655,660,666,673,679,680,686,692,698,704,710,716,722,727,732,738,744,750,756,761,767,773,779,785,791,797,803,809,815,821,827,833,839,845,851,857,863,869,874,880,886,892,898,904,910,916,922,928,934,940,946,952,958,964,970,976,982,988,994,1000,1006,1012,1018,1024,1030,1036,1042,1048,1054,1060,1066,1072,1078,1084,1090,1096,1101,1107,1113,1119,1124,1130],{"path":608,"title":609,"description":610,"published":611,"category":564,"image":612,"draft":568},"\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":614,"title":615,"description":616,"published":617,"category":564,"image":618,"draft":568},"\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":620,"title":621,"description":622,"published":623,"category":564,"image":624,"draft":568},"\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":626,"title":627,"description":628,"published":629,"category":564,"image":630,"draft":568},"\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":632,"title":633,"description":634,"published":635,"category":564,"image":636,"draft":568},"\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":638,"title":639,"description":640,"published":641,"category":564,"image":642,"draft":568},"\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":644,"title":645,"description":646,"published":647,"category":564,"image":648,"draft":568},"\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":650,"title":651,"description":652,"published":653,"category":564,"image":654,"draft":568},"\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":330,"title":656,"description":657,"published":658,"category":564,"image":659,"draft":568},"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":661,"title":662,"description":663,"published":664,"category":564,"image":665,"draft":568},"\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":667,"title":668,"description":669,"published":670,"category":671,"image":672,"draft":568},"\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":674,"title":675,"description":676,"published":677,"category":564,"image":678,"draft":568},"\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":597,"title":5,"description":567,"published":598,"category":564,"image":586,"draft":568},{"path":681,"title":682,"description":683,"published":684,"category":564,"image":685,"draft":568},"\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":687,"title":688,"description":689,"published":690,"category":564,"image":691,"draft":568},"\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":693,"title":694,"description":695,"published":696,"category":564,"image":697,"draft":568},"\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":699,"title":700,"description":701,"published":702,"category":564,"image":703,"draft":568},"\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":705,"title":706,"description":707,"published":708,"category":564,"image":709,"draft":568},"\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":711,"title":712,"description":713,"published":714,"category":564,"image":715,"draft":568},"\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":717,"title":718,"description":719,"published":720,"category":564,"image":721,"draft":568},"\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":325,"title":723,"description":724,"published":725,"category":564,"image":726,"draft":568},"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":91,"title":728,"description":729,"published":730,"category":564,"image":731,"draft":568},"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":733,"title":734,"description":735,"published":736,"category":564,"image":737,"draft":568},"\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":739,"title":740,"description":741,"published":742,"category":564,"image":743,"draft":568},"\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":745,"title":746,"description":747,"published":748,"category":564,"image":749,"draft":568},"\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":751,"title":752,"description":753,"published":754,"category":564,"image":755,"draft":568},"\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":543,"title":757,"description":758,"published":759,"category":564,"image":760,"draft":568},"\"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":762,"title":763,"description":764,"published":765,"category":564,"image":766,"draft":568},"\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":768,"title":769,"description":770,"published":771,"category":564,"image":772,"draft":568},"\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":774,"title":775,"description":776,"published":777,"category":564,"image":778,"draft":568},"\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":780,"title":781,"description":782,"published":783,"category":564,"image":784,"draft":568},"\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":786,"title":787,"description":788,"published":789,"category":564,"image":790,"draft":568},"\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":792,"title":793,"description":794,"published":795,"category":564,"image":796,"draft":568},"\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":798,"title":799,"description":800,"published":801,"category":564,"image":802,"draft":568},"\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":804,"title":805,"description":806,"published":807,"category":564,"image":808,"draft":568},"\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":810,"title":811,"description":812,"published":813,"category":564,"image":814,"draft":568},"\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":816,"title":817,"description":818,"published":819,"category":564,"image":820,"draft":568},"\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":822,"title":823,"description":824,"published":825,"category":564,"image":826,"draft":568},"\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":828,"title":829,"description":830,"published":831,"category":564,"image":832,"draft":568},"\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":834,"title":835,"description":836,"published":837,"category":564,"image":838,"draft":568},"\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":840,"title":841,"description":842,"published":843,"category":564,"image":844,"draft":568},"\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":846,"title":847,"description":848,"published":849,"category":564,"image":850,"draft":568},"\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":852,"title":853,"description":854,"published":855,"category":671,"image":856,"draft":568},"\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":858,"title":859,"description":860,"published":861,"category":671,"image":862,"draft":568},"\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":864,"title":865,"description":866,"published":867,"category":671,"image":868,"draft":568},"\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":870,"title":871,"description":872,"published":867,"category":564,"image":873,"draft":568},"\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":875,"title":876,"description":877,"published":878,"category":564,"image":879,"draft":568},"\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":881,"title":882,"description":883,"published":884,"category":564,"image":885,"draft":568},"\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":887,"title":888,"description":889,"published":890,"category":671,"image":891,"draft":568},"\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":893,"title":894,"description":895,"published":896,"category":564,"image":897,"draft":568},"\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":899,"title":900,"description":901,"published":902,"category":564,"image":903,"draft":568},"\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":905,"title":906,"description":907,"published":908,"category":564,"image":909,"draft":568},"\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":911,"title":912,"description":913,"published":914,"category":564,"image":915,"draft":568},"\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":917,"title":918,"description":919,"published":920,"category":564,"image":921,"draft":568},"\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":923,"title":924,"description":925,"published":926,"category":564,"image":927,"draft":568},"\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":929,"title":930,"description":931,"published":932,"category":564,"image":933,"draft":568},"\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":935,"title":936,"description":937,"published":938,"category":564,"image":939,"draft":568},"\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":941,"title":942,"description":943,"published":944,"category":671,"image":945,"draft":568},"\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":947,"title":948,"description":949,"published":950,"category":671,"image":951,"draft":568},"\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":953,"title":954,"description":955,"published":956,"category":671,"image":957,"draft":568},"\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":959,"title":960,"description":961,"published":962,"category":564,"image":963,"draft":568},"\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":965,"title":966,"description":967,"published":968,"category":564,"image":969,"draft":568},"\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":971,"title":972,"description":973,"published":974,"category":564,"image":975,"draft":568},"\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":977,"title":978,"description":979,"published":980,"category":564,"image":981,"draft":568},"\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":983,"title":984,"description":985,"published":986,"category":564,"image":987,"draft":568},"\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":989,"title":990,"description":991,"published":992,"category":564,"image":993,"draft":568},"\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":995,"title":996,"description":997,"published":998,"category":564,"image":999,"draft":568},"\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":1001,"title":1002,"description":1003,"published":1004,"category":564,"image":1005,"draft":568},"\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":1007,"title":1008,"description":1009,"published":1010,"category":564,"image":1011,"draft":568},"\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":1013,"title":1014,"description":1015,"published":1016,"category":564,"image":1017,"draft":568},"\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":1019,"title":1020,"description":1021,"published":1022,"category":671,"image":1023,"draft":568},"\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":1025,"title":1026,"description":1027,"published":1028,"category":564,"image":1029,"draft":568},"\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":1031,"title":1032,"description":1033,"published":1034,"category":671,"image":1035,"draft":568},"\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":1037,"title":1038,"description":1039,"published":1040,"category":564,"image":1041,"draft":568},"\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":1043,"title":1044,"description":1045,"published":1046,"category":564,"image":1047,"draft":568},"\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":1049,"title":1050,"description":1051,"published":1052,"category":564,"image":1053,"draft":568},"\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":1055,"title":1056,"description":1057,"published":1058,"category":564,"image":1059,"draft":568},"\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":1061,"title":1062,"description":1063,"published":1064,"category":671,"image":1065,"draft":568},"\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":1067,"title":1068,"description":1069,"published":1070,"category":671,"image":1071,"draft":568},"\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":1073,"title":1074,"description":1075,"published":1076,"category":564,"image":1077,"draft":568},"\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":1079,"title":1080,"description":1081,"published":1082,"category":564,"image":1083,"draft":568},"\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":1085,"title":1086,"description":1087,"published":1088,"category":671,"image":1089,"draft":568},"\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":1091,"title":1092,"description":1093,"published":1094,"category":564,"image":1095,"draft":568},"\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":1097,"title":1098,"description":1099,"published":1094,"category":564,"image":1100,"draft":568},"\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":1102,"title":1103,"description":1104,"published":1105,"category":564,"image":1106,"draft":568},"\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":1108,"title":1109,"description":1110,"published":1111,"category":671,"image":1112,"draft":568},"\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":1114,"title":1115,"description":1116,"published":1117,"category":564,"image":1118,"draft":568},"\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":1120,"title":1121,"description":1122,"published":1117,"category":671,"image":1123,"draft":568},"\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":1125,"title":1126,"description":1127,"published":1128,"category":671,"image":1129,"draft":568},"\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":1131,"title":1132,"description":1133,"published":1128,"category":564,"image":1134,"draft":568},"\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",1794213645014]