[{"data":1,"prerenderedAt":1370},["ShallowReactive",2],{"blog-es-duplicate-key-value-violates-unique-constraint":3,"blog-index-es":842},{"id":4,"title":5,"body":6,"category":800,"cover":801,"coverAlt":802,"description":803,"draft":804,"extension":805,"faq":806,"image":821,"keywords":822,"meta":831,"navigation":832,"ogTitle":5,"path":833,"published":834,"seo":835,"stem":836,"tldr":837,"updated":834,"__hash__":841},"blog_es\u002Fblog\u002Fduplicate-key-value-violates-unique-constraint.md","Supabase \"duplicate key value violates unique constraint\", explicado",{"type":7,"value":8,"toc":788},"minimark",[9,13,24,31,34,39,42,45,48,167,180,184,193,267,270,278,292,296,299,302,324,337,343,366,379,385,389,392,399,407,413,419,432,443,447,450,477,514,520,540,556,559,564,568,571,592,607,615,631,634,640,662,667,671,674,683,706,715,733,737,778],[10,11,12],"p",{},"Alguien rellena el formulario de tu lista de espera, espera y vuelve a pulsar el\nbotón. La primera pulsación ya había entrado. La segunda vuelve como error, y la\nconsola del navegador lo muestra entero:",[14,15,20],"pre",{"className":16,"code":18,"language":19},[17],"language-text","{\n  \"code\": \"23505\",\n  \"details\": \"Key (email)=(maria@example.com) already exists.\",\n  \"hint\": null,\n  \"message\": \"duplicate key value violates unique constraint \\\"waitlist_email_key\\\"\"\n}\n","text",[21,22,18],"code",{"__ignoreMap":23},"",[10,25,26,27,30],{},"Supabase \"duplicate key value violates unique constraint\" significa que Postgres\nse negó a añadir la fila porque otra fila ya tiene el mismo valor en una columna\nque admite cada valor una sola vez. Aquí la columna es ",[21,28,29],{},"email",", y Maria ya está en\ntu lista.",[10,32,33],{},"El consejo que circula para este error es cambiar el insert por un upsert. A veces\nes lo correcto. Pero en una tabla donde tus visitantes solo pueden añadir, un\nupsert desde el navegador necesita reglas que les dejen leer la tabla, y la línea\ncon la dirección de Maria es algo que tu app no debería mostrar nunca a un\nvisitante.",[35,36,38],"h2",{"id":37},"qué-significa-duplicate-key-value-violates-unique-constraint","¿Qué significa \"duplicate key value violates unique constraint\"?",[10,40,41],{},"Significa que el valor que querías guardar ya está en una columna que admite cada\nvalor una vez, así que Postgres rechazó la fila entera. No se escribió nada, y la\nfila que estaba primero sigue igual.",[10,43,44],{},"Piensa en la tabla como la lista de invitados de una puerta, una línea por nombre.\nUna restricción única es la regla del portero: un nombre entra en la lista una\nsola vez. Tu app llegó con un nombre que ya estaba escrito, y él la rechazó.",[10,46,47],{},"Cada parte del error dice una cosa:",[49,50,51,67],"table",{},[52,53,54],"thead",{},[55,56,57,61,64],"tr",{},[58,59,60],"th",{},"Campo",[58,62,63],{},"Ejemplo",[58,65,66],{},"Qué te dice",[68,69,70,96,111,126,141],"tbody",{},[55,71,72,77,82],{},[73,74,75],"td",{},[21,76,21],{},[73,78,79],{},[21,80,81],{},"23505",[73,83,84,85,88,89],{},"El código de Postgres para este error, llamado ",[21,86,87],{},"unique_violation"," en ",[90,91,95],"a",{"href":92,"rel":93},"https:\u002F\u002Fgithub.com\u002Fpostgres\u002Fpostgres\u002Fblob\u002Fmaster\u002Fsrc\u002Fbackend\u002Futils\u002Ferrcodes.txt",[94],"nofollow","su lista de códigos",[55,97,98,103,108],{},[73,99,100],{},[21,101,102],{},"message",[73,104,105],{},[21,106,107],{},"duplicate key value violates unique constraint \"waitlist_email_key\"",[73,109,110],{},"Qué regla rechazó la fila, por su nombre",[55,112,113,118,123],{},[73,114,115],{},[21,116,117],{},"details",[73,119,120],{},[21,121,122],{},"Key (email)=(maria@example.com) already exists.",[73,124,125],{},"La columna y el valor que chocaron, cuando Postgres decide que quien llama puede verlos",[55,127,128,133,138],{},[73,129,130],{},[21,131,132],{},"hint",[73,134,135],{},[21,136,137],{},"null",[73,139,140],{},"Nada que añadir para este error",[55,142,143,146,151],{},[73,144,145],{},"Estado HTTP",[73,147,148],{},[21,149,150],{},"409 Conflict",[73,152,153,154,156,157,160,161,166],{},"Cómo informa de ",[21,155,81],{}," PostgREST, la parte de Supabase que convierte las llamadas ",[21,158,159],{},"supabase.from(...)"," en SQL (",[90,162,165],{"href":163,"rel":164},"https:\u002F\u002Fgithub.com\u002FPostgREST\u002Fpostgrest\u002Fblob\u002Fmain\u002Fsrc\u002Flibrary\u002FPostgREST\u002FError.hs",[94],"su código",")",[10,168,169,170,175,176,179],{},"El texto es del propio Postgres.\n",[90,171,174],{"href":172,"rel":173},"https:\u002F\u002Fgithub.com\u002Fpostgres\u002Fpostgres\u002Fblob\u002Fmaster\u002Fsrc\u002Fbackend\u002Faccess\u002Fnbtree\u002Fnbtinsert.c",[94],"El código que lanza el error","\nsolo añade la línea ",[21,177,178],{},"Key ... already exists"," cuando tiene una descripción de la\nclave que dar, y una sección más abajo explica cuándo no la tiene.",[35,181,183],{"id":182},"qué-columna-causó-el-error","¿Qué columna causó el error?",[10,185,186,187,192],{},"Te lo dice el nombre entre comillas, porque Postgres lo construye a partir de la\ntabla y las columnas, salvo que alguien haya elegido otro.\n",[90,188,191],{"href":189,"rel":190},"https:\u002F\u002Fgithub.com\u002Fpostgres\u002Fpostgres\u002Fblob\u002Fmaster\u002Fsrc\u002Fbackend\u002Fcommands\u002Findexcmds.c",[94],"Su código de nombres","\nda tres formas:",[49,194,195,207],{},[52,196,197],{},[55,198,199,202,205],{},[58,200,201],{},"El nombre acaba en",[58,203,204],{},"Es",[58,206,63],{},[68,208,209,233,251],{},[55,210,211,216,221],{},[73,212,213],{},[21,214,215],{},"_key",[73,217,218,219],{},"una restricción única sobre las columnas entre el nombre de la tabla y ",[21,220,215],{},[73,222,223,226,227,230,231],{},[21,224,225],{},"waitlist_email_key",": tabla ",[21,228,229],{},"waitlist",", columna ",[21,232,29],{},[55,234,235,240,246],{},[73,236,237],{},[21,238,239],{},"_pkey",[73,241,242,243],{},"la clave primaria de la tabla, normalmente ",[21,244,245],{},"id",[73,247,248],{},[21,249,250],{},"orders_pkey",[55,252,253,259,262],{},[73,254,255,258],{},[21,256,257],{},"_idx",", u otra cosa",[73,260,261],{},"un índice único creado a mano, o una restricción a la que alguien puso nombre",[73,263,264],{},[21,265,266],{},"profiles_username_idx",[10,268,269],{},"El mensaje dice \"unique constraint\" en los tres casos. Para ver exactamente qué\ncubre un nombre, ejecuta esto en el SQL Editor con el nombre de tu mensaje:",[14,271,276],{"className":272,"code":274,"language":275,"meta":23},[273],"language-sql","select tablename, indexdef\nfrom pg_indexes\nwhere indexname = 'waitlist_email_key';\n","sql",[21,277,274],{"__ignoreMap":23},[10,279,280,283,284,287,288,291],{},[21,281,282],{},"indexdef"," vuelve con algo como\n",[21,285,286],{},"CREATE UNIQUE INDEX waitlist_email_key ON public.waitlist USING btree (email)",",\ny las columnas entre paréntesis son las que no pueden repetirse. Una regla sobre\ndos columnas, como ",[21,289,290],{},"(team_id, email)",", deja que la misma dirección entre en dos\nequipos y la rechaza la segunda vez en el mismo.",[35,293,295],{"id":294},"key-id1-already-exists-cuando-nunca-enviaste-un-id","\"Key (id)=(1) already exists\" cuando nunca enviaste un id",[10,297,298],{},"Este significa que el contador de id de la tabla va por detrás de las filas que ya\ntiene, así que repartió un número ocupado. Tu app no envió ningún id. Postgres\ntomó el siguiente número del contador, y ya había una fila con ese número.",[10,300,301],{},"En la lista de invitados, el portero también reparte pulseras numeradas de un\nrollo. Alguien copió a mano a los doscientos primeros invitados, números de\npulsera incluidos, y nadie hizo avanzar el rollo. Sigue empezando en 1.",[10,303,304,305,310,311,316,317,319,320,323],{},"Una columna que se numera sola toma cada número de una secuencia, que es como\nPostgres llama a ese contador. La\n",[90,306,309],{"href":307,"rel":308},"https:\u002F\u002Fwww.postgresql.org\u002Fdocs\u002Fcurrent\u002Fddl-identity-columns.html",[94],"página de Postgres sobre columnas de identidad","\ndice que una columna así \"does not guarantee uniqueness\", y da este caso como una\nde las razones: hay valores que \"could be inserted manually into the identity\ncolumn\". Llegan filas con el número ya puesto desde una importación de CSV que\nincluye la columna id, una copia desde otro proyecto o un script de seed. El\ncontador no se entera de ninguna.\n",[90,312,315],{"href":313,"rel":314},"https:\u002F\u002Fsupabase.com\u002Fdocs\u002Fguides\u002Ftroubleshooting\u002Fwhy-are-there-gaps-in-my-postgres-id-sequence-Frifus",[94],"La página de solución de problemas de Supabase sobre secuencias de id","\ntrata exactamente este mensaje en un ",[21,318,239],{},", y su arreglo también es un ",[21,321,322],{},"setval",".",[10,325,326,327,330,331,333,334,336],{},"El arreglo es una sola instrucción. Pon tu tabla y tu columna en lugar de ",[21,328,329],{},"orders","\ne ",[21,332,245],{},"; la tabla es la parte del nombre antes de ",[21,335,239],{},":",[14,338,341],{"className":339,"code":340,"language":275,"meta":23},[273],"select setval(\n  pg_get_serial_sequence('public.orders', 'id'),\n  (select max(id) from public.orders)\n);\n",[21,342,340],{"__ignoreMap":23},[10,344,345,348,349,354,355,361,362,323],{},[21,346,347],{},"pg_get_serial_sequence"," encuentra el contador detrás de la columna, y\n",[90,350,353],{"href":351,"rel":352},"https:\u002F\u002Fwww.postgresql.org\u002Fdocs\u002Fcurrent\u002Ffunctions-info.html",[94],"la documentación de Postgres","\ndice que sirve para los dos tipos: \"If the column is an identity column, the\nassociated sequence is the sequence internally created for that column.\" Después,\n",[90,356,359],{"href":357,"rel":358},"https:\u002F\u002Fwww.postgresql.org\u002Fdocs\u002Fcurrent\u002Ffunctions-sequence.html",[94],[21,360,322],{}," con\ndos argumentos ajusta el contador para que \"the next nextval will advance the\nsequence before returning a value\". La siguiente fila nueva recibe el id más alto\nmás uno. Si Postgres responde que la relación no existe, la tabla se escribe de\notra forma, lleva una mayúscula o está en otro esquema, y\n",[90,363,365],{"href":364},"\u002Fblog\u002Fsupabase-relation-does-not-exist","ese error tiene su propio artículo",[10,367,368,369,372,373,378],{},"Una restauración desde la salida de ",[21,370,371],{},"pg_dump"," no te trae hasta aquí.\n",[90,374,377],{"href":375,"rel":376},"https:\u002F\u002Fwww.postgresql.org\u002Fdocs\u002Fcurrent\u002Fapp-pgdump.html",[94],"Su documentación","\nenumera lo que lleva un volcado de datos: \"Table data, large objects, and sequence\nvalues are dumped.\" Cada contador vuelve con sus filas.",[380,381],"diagram",{"alt":382,"caption":383,"src":384},"Una lista de cinco filas numeradas, del 1 al 5, junto a un contador. A la izquierda el contador marca 1 y apunta a la fila 1, iluminada en ámbar, que termina en una placa con Key (id)=(1). A la derecha el contador marca 6 y apunta a un nombre nuevo en la sexta línea, con una marca de verificación debajo.","Las filas copiadas con sus propios id dejan el contador donde estaba. setval lo lleva más allá del id más alto, y la siguiente fila recibe un número libre.","\u002Fblog\u002Fduplicate-key-value-violates-unique-constraint\u002Fthe-counter-is-behind-1600x640.png",[35,386,388],{"id":387},"las-tres-formas-de-tratar-un-duplicado","Las tres formas de tratar un duplicado",[10,390,391],{},"Decide qué debe pasar cuando el valor ya está: rechazar la fila nueva y decirlo,\ndejar que sustituya a la antigua o dejar de enviarla.",[10,393,394,398],{},[395,396,397],"strong",{},"Recházala, con tus palabras."," Encaja cuando una segunda fila sería un error: una\ndirección ya registrada, un nombre de usuario ya ocupado. El insert falla, tu app\nlee el código y la persona ve una frase que escribiste tú:",[14,400,405],{"className":401,"code":403,"language":404,"meta":23},[402],"language-js","const { error } = await supabase.from('profiles').insert({ id: user.id, username })\n\nif (error?.code === '23505') {\n  setMessage('Ese nombre de usuario ya está ocupado. Prueba con otro.')\n} else if (error) {\n  setMessage('Algo ha fallado. Vuelve a intentarlo.')\n}\n","js",[21,406,403],{"__ignoreMap":23},[10,408,409,412],{},[395,410,411],{},"Deja que sustituya a la antigua."," Encaja cuando debe ganar la versión más nueva:\nuna fila de ajustes por usuario, un carrito por sesión. Eso es un upsert, y la\nsección siguiente trata de cuándo sale mal.",[10,414,415,418],{},[395,416,417],{},"Deja de enviarla."," A veces la segunda fila nunca debió existir, y llega desde\nuno de estos sitios:",[420,421,422,426,429],"ul",{},[423,424,425],"li",{},"un botón que vuelve a enviar el formulario cuando se pulsa otra vez.\nDesactívalo hasta que responda la primera petición.",[423,427,428],{},"un reintento tras una respuesta lenta, cuando el primer intento sí había entrado",[423,430,431],{},"dos escritores para una misma fila, como un trigger que crea el perfil de cada\nusuario nuevo y código de la app que inserta ese mismo perfil después del\nregistro",[10,433,434,435,439,440,442],{},"El caso del trigger conviene reconocerlo a la primera. Un trigger que copia los\nusuarios nuevos en una tabla tuya falla con este error cuando el valor que copia\nya está ahí, y Supabase informa de todo el registro como\n",[90,436,438],{"href":437},"\u002Fblog\u002Fdatabase-error-saving-new-user","\"Database error saving new user\"",". El\n",[21,441,81],{}," en sí está en los logs de Postgres, y ese artículo muestra dónde.",[35,444,446],{"id":445},"deberías-usar-upsert","¿Deberías usar upsert?",[10,448,449],{},"Úsalo cuando la fila nueva deba sustituir a la antigua, y comprueba tres cosas\nantes de confiar en él.",[10,451,452,455,456,461,462,465,466,469,470,473,474,323],{},[21,453,454],{},".upsert()"," en supabase-js es un insert que hace otra cosa cuando la fila existe.\n",[90,457,460],{"href":458,"rel":459},"https:\u002F\u002Fgithub.com\u002Fsupabase\u002Fsupabase-js\u002Fblob\u002Fmaster\u002Fpackages\u002Fcore\u002Fpostgrest-js\u002Fsrc\u002FPostgrestQueryBuilder.ts",[94],"Su código fuente","\nlo dice en dos líneas: \"If ",[21,463,464],{},"true",", duplicate rows are ignored. If ",[21,467,468],{},"false",",\nduplicate rows are merged with existing rows.\" Es la opción ",[21,471,472],{},"ignoreDuplicates",", y\nPostgres ejecuta las dos versiones como un ",[21,475,476],{},"insert ... on conflict",[10,478,479,486,491,492,495,496,498,499,501,502,504,505,507,508,513],{},[395,480,481,482,485],{},"1. ",[21,483,484],{},"onConflict"," tiene que nombrar la columna de tu error.",[90,487,490],{"href":488,"rel":489},"https:\u002F\u002Fdocs.postgrest.org\u002Fen\u002Fstable\u002Freferences\u002Fapi\u002Ftables_views.html",[94],"La documentación de PostgREST","\ndice: \"By default, upsert operates based on the primary key columns\". Un upsert de\n",[21,493,494],{},"{ email }"," en una tabla cuya clave primaria es ",[21,497,245],{}," busca un choque en ",[21,500,245],{},", no lo\nencuentra porque el id es nuevo, inserta y se topa con la regla de ",[21,503,29],{},", con el\nmismo ",[21,506,81],{},". La\n",[90,509,512],{"href":510,"rel":511},"https:\u002F\u002Fwww.postgresql.org\u002Fdocs\u002Fcurrent\u002Fsql-insert.html",[94],"referencia de INSERT de Postgres","\ndice que la acción alternativa se aplica \"if an arbiter constraint or index\nspecified by conflict_target is violated\". Un choque en cualquier otra columna\nrechaza la fila como antes.",[14,515,518],{"className":516,"code":517,"language":404,"meta":23},[402],"await supabase\n  .from('settings')\n  .upsert({ user_id: user.id, theme: 'dark' }, { onConflict: 'user_id' })\n",[21,519,517],{"__ignoreMap":23},[10,521,522,525,526,529,530,533,534,539],{},[395,523,524],{},"2. Esa columna necesita una regla única propia."," Sin ella, Postgres no tiene con\nqué comparar y responde\n",[21,527,528],{},"there is no unique or exclusion constraint matching the ON CONFLICT specification",",\ncódigo ",[21,531,532],{},"42P10","\n(",[90,535,538],{"href":536,"rel":537},"https:\u002F\u002Fgithub.com\u002Fpostgres\u002Fpostgres\u002Fblob\u002Fmaster\u002Fsrc\u002Fbackend\u002Foptimizer\u002Futil\u002Fplancat.c",[94],"la línea que lo lanza",").",[10,541,542,545,546,551,552,323],{},[395,543,544],{},"3. Desde el navegador, la Row Level Security lee más reglas tuyas."," La Row\nLevel Security es el conjunto de reglas por fila que Supabase comprueba en cada\npetición hecha con tu clave pública. Para un upsert, la\n",[90,547,550],{"href":548,"rel":549},"https:\u002F\u002Fwww.postgresql.org\u002Fdocs\u002Fcurrent\u002Fsql-createpolicy.html",[94],"referencia de Postgres sobre policies","\ndice: \"SELECT permissions are required on the relation, and the rows proposed for\ninsertion are checked using the relation's SELECT policies.\" Cuando la fila existe\ny el upsert fusiona, la fila existente también se comprueba contra las reglas de\nactualización de la tabla. Una fila que no pasa alguna de esas comprobaciones\ntermina la petición con\n",[90,553,555],{"href":554},"\u002Fblog\u002Fnew-row-violates-row-level-security-policy","new row violates row-level security policy",[10,557,558],{},"Así que en la lista de espera, un upsert desde el navegador falla mientras la\ntabla no tenga una regla de lectura que un visitante cumpla. Una regla de lectura\nque un desconocido cumple para cualquier dirección es una regla que deja a los\ndesconocidos leer tu lista. En la puerta, reescribir una línea significa que antes\nte enseñaron la lista. Para una tabla así, mejor rechaza el duplicado, o usa la\nfunción de la sección siguiente.",[380,560],{"alt":561,"caption":562,"src":563},"Dos peticiones a la misma tabla. Un insert cruza una barrera, insert, y llega a ella. Un upsert tiene que cruzar tres barreras seguidas, insert, select y update; la barrera select está cerrada y dibujada en ámbar, así que el upsert se detiene ahí.","Desde el navegador, un upsert se comprueba contra las reglas de lectura y de actualización de la tabla además de su regla de inserción. En una tabla donde los visitantes solo pueden añadir, la regla de lectura es la que lo detiene.","\u002Fblog\u002Fduplicate-key-value-violates-unique-constraint\u002Fupsert-reads-three-rules-1600x660.png",[35,565,567],{"id":566},"es-seguro-mostrar-este-error-a-tus-usuarios","¿Es seguro mostrar este error a tus usuarios?",[10,569,570],{},"No. Muestra una frase tuya y guarda el error en bruto en tus logs, por dos\nrazones: la línea de detalle puede devolver el valor, y el error confirma que el\nvalor existe muestres lo que muestres.",[10,572,573,576,577,580,581,588,589,323],{},[395,574,575],{},"La línea de detalle repite el valor."," ",[21,578,579],{},"Key (email)=(maria@example.com)"," no hace\ndaño en la pantalla de Maria, y es otra cosa en una captura publicada en un foro\nde soporte, en una herramienta de seguimiento de errores o en la pantalla de\nalguien que escribió la dirección de otra persona en tu formulario. Postgres ya la\nprotege. La función que construye la línea\n(",[90,582,585],{"href":583,"rel":584},"https:\u002F\u002Fgithub.com\u002Fpostgres\u002Fpostgres\u002Fblob\u002Fmaster\u002Fsrc\u002Fbackend\u002Faccess\u002Findex\u002Fgenam.c",[94],[21,586,587],{},"BuildIndexValueDescription",")\ncomprueba primero: \"if RLS is enabled for the relation. If so, return NULL to\navoid leaking data.\" Una petición hecha con tu clave pública a una tabla con Row\nLevel Security activada recibe ",[21,590,591],{},"\"details\": null",[10,593,594,595,598,599,601,602,606],{},"Leída al revés, la línea te dice algo de la tabla. Si la consola de tu navegador\nmuestra ",[21,596,597],{},"Key (...)=(...)"," para una petición que tu app hizo con la clave pública,\nla Row Level Security está desactivada en esa tabla. La única excepción es una\npetición que tu app envía a través de código de servidor con la clave secreta, que\nse salta todas las reglas. ",[21,600,137],{}," demuestra menos: Postgres también quita la línea\ncuando quien llama no puede leer todas las columnas de la clave.\n",[90,603,605],{"href":604},"\u002Fblog\u002Fenable-rls-on-every-supabase-table","Activar Row Level Security en cada tabla","\ncubre el interruptor y la primera regla.",[10,608,609,610,614],{},"Una tabla con las reglas desactivadas le responde a un desconocido igual que a ti.\nNuestro ",[90,611,613],{"href":612},"\u002Fsecurity-scanner","escaneo gratuito"," lee tu app en vivo con la misma\nclave pública que recibe un visitante, informa de qué tablas devuelven filas y no\nnecesita cuenta.",[10,616,617,620,621,624,625,630],{},[395,618,619],{},"El error responde a una pregunta que quizá no quieras responder."," Aun sin la\nlínea de detalle, un ",[21,622,623],{},"409"," le dice a quien envió la fila que el valor ya está.\nLa ",[90,626,629],{"href":627,"rel":628},"https:\u002F\u002Fwww.postgresql.org\u002Fdocs\u002Fcurrent\u002Fddl-rowsecurity.html",[94],"página de Postgres sobre seguridad de filas","\ndice que las restricciones únicas \"always bypass row security to ensure that data\nintegrity is maintained\", y te pide diseñar tablas y reglas para que esas\ncomprobaciones no se conviertan en un canal encubierto. En la lista de espera,\ncualquiera puede saber si una dirección está en tu lista intentando añadirla. El\nportero nunca lee el nombre en voz alta, y cuando rechaza a alguien, la cola sabe\nigualmente que el nombre estaba ahí.",[10,632,633],{},"Si eso importa para tu lista, dale al navegador la misma respuesta en los dos\ncasos. Una función en la base de datos puede añadir la dirección, saltársela si ya\nestá y no devolver nada en ninguno de los dos casos:",[14,635,638],{"className":636,"code":637,"language":275,"meta":23},[273],"create function public.join_waitlist(new_email text)\nreturns void\nlanguage sql\nsecurity definer\nset search_path = ''\nas $$\n  insert into public.waitlist (email)\n  values (new_email)\n  on conflict (email) do nothing;\n$$;\n",[21,639,637],{"__ignoreMap":23},[10,641,642,643,646,647,650,651,656,657,661],{},"Tu app llama a ",[21,644,645],{},"supabase.rpc('join_waitlist', { new_email: email })",".\n",[21,648,649],{},"security definer"," ejecuta el insert como el dueño de la función, así que los\nvisitantes no necesitan ninguna regla sobre la tabla, y la regla que les dejaba\ninsertar puede irse, o el camino viejo sigue abierto junto al nuevo.\n",[90,652,653,654],{"href":437},"Lo que \"Database error saving new user\" cuenta de ",[21,655,649],{},"\nexplica lo que cede, y\n",[90,658,660],{"href":659},"\u002Fblog\u002Fsupabase-function-not-found","llamar a una función de Supabase"," muestra cómo\nhacer coincidir los nombres de los argumentos.",[380,663],{"alt":664,"caption":665,"src":666},"El mismo duplicado enviado a dos tablas. La tabla de la izquierda lleva un candado, y su respuesta dice 23505 y 409 con una línea details vacía. La tabla de la derecha no lo lleva, y su respuesta trae una línea details en ámbar que dice Key (email)=(…).","Postgres escribe el valor en la línea de detalle solo para quien llama sin que le aplique la Row Level Security. Verlo desde el navegador significa que las reglas de la tabla están desactivadas.","\u002Fblog\u002Fduplicate-key-value-violates-unique-constraint\u002Fthe-detail-line-and-rls-1600x700.png",[35,668,670],{"id":669},"antes-de-cambiar-cómo-escribe-tu-app","Antes de cambiar cómo escribe tu app",[10,672,673],{},"Guarda una copia de la base de datos de antes de pasar un insert a upsert o de\nreajustar un contador, y entérate cuando una tabla empiece a responder a\ndesconocidos.",[10,675,676],{},[395,677,678,682],{},[90,679,681],{"href":680},"\u002Fsupabase-backups","Reeve Care"," guarda una copia de tu base de datos de Supabase:",[420,684,685,688,691,694,697,700,703],{},[423,686,687],{},"una copia cifrada según un calendario, en el almacenamiento propio de Reeve en la UE, fuera de tu cuenta de Supabase",[423,689,690],{},"cada tabla con sus filas tal como estaban antes de que una fusión las sobrescribiera",[423,692,693],{},"el contador de id de cada tabla vuelve con sus filas al restaurar, para que la siguiente fila nueva reciba un número libre",[423,695,696],{},"cada copia verificada antes de contar, contando las filas de cada tabla",[423,698,699],{},"también tus archivos subidos, en cuanto conectas una credencial de Storage",[423,701,702],{},"una restauración con un clic, con una copia de seguridad del estado actual tomada antes",[423,704,705],{},"todo lo que hace Monitor",[10,707,708],{},[395,709,710,714],{},[90,711,713],{"href":712},"\u002Fwebsite-monitoring","Reeve Monitor"," sigue comprobando desde fuera:",[420,716,717,720,723,730],{},[423,718,719],{},"si tu app está en línea, comprobado cada minuto",[423,721,722],{},"la comprobación de seguridad completa cada hora, en hasta tres apps",[423,724,725,726,729],{},"un mensaje cuando un resultado ",[395,727,728],{},"cambia",", para que una tabla que empieza a responder a desconocidos tras un despliegue te llegue sin que tengas que ir a mirar",[423,731,732],{},"un informe mensual, en lenguaje claro, de lo que vio",[35,734,736],{"id":735},"qué-hacer-ahora","Qué hacer ahora",[738,739,740],"key-takeaways",{},[420,741,742,751,760,766,772],{},[423,743,744,745,747,748,750],{},"Lee el nombre entre comillas. La tabla y la columna están antes de ",[21,746,215],{},", y ",[21,749,239],{}," significa la clave primaria.",[423,752,753,754,756,757,759],{},"Si el nombre acaba en ",[21,755,239],{}," y tu app no envía id, ajusta el contador con la instrucción ",[21,758,322],{}," de arriba.",[423,761,762,763,765],{},"Captura ",[21,764,81],{}," en tu app y muestra una frase que hayas escrito tú, nunca el mensaje en bruto.",[423,767,768,769,771],{},"Usa upsert solo donde la fila nueva deba sustituir a la antigua, con ",[21,770,484],{}," en la columna del error.",[423,773,774,775,777],{},"Si la consola del navegador muestra una línea ",[21,776,597],{}," para una petición hecha con tu clave pública, activa la Row Level Security en esa tabla.",[10,779,780,781,783,784,787],{},"Empieza por el nombre entre comillas, porque dice qué regla rechazó la fila y qué\ncolumna vigila esa regla. Antes de ejecutar un ",[21,782,322],{},", un upsert o cualquier otra\ncosa que reescriba filas de las que dependen personas,\n",[90,785,786],{"href":680},"guarda una copia de la base de datos"," de antes del cambio.",{"title":23,"searchDepth":789,"depth":789,"links":790},3,[791,793,794,795,796,797,798,799],{"id":37,"depth":792,"text":38},2,{"id":182,"depth":792,"text":183},{"id":294,"depth":792,"text":295},{"id":387,"depth":792,"text":388},{"id":445,"depth":792,"text":446},{"id":566,"depth":792,"text":567},{"id":669,"depth":792,"text":670},{"id":735,"depth":792,"text":736},"Fundamentos de seguridad","\u002Fblog\u002Fduplicate-key-value-violates-unique-constraint\u002Fcover-1200x630.png","Una lista de nombres en una tablilla, una línea en turquesa, y delante un papel con el mismo nombre en ámbar que se queda fuera.","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.",false,"md",[807,810,812,815,818],{"q":808,"a":809},"¿Qué significa duplicate key value violates unique constraint?","Significa que Postgres se negó a añadir o cambiar una fila porque otra fila ya tiene el mismo valor en una columna, o grupo de columnas, que admite cada valor una sola vez. No se escribió nada. El código es 23505, y desde una app de Supabase llega como HTTP 409.",{"q":183,"a":811},"Te lo dice el nombre de la regla entre comillas. Postgres nombra una restricción única según su tabla y sus columnas, con _key al final, así que waitlist_email_key es la columna email de la tabla waitlist, y un nombre que acaba en _pkey es la clave primaria, normalmente id. La línea de detalle, cuando aparece, nombra la columna y el valor.",{"q":813,"a":814},"¿Debería usar upsert?","Solo cuando la fila nueva deba sustituir a la antigua, como una fila de ajustes por usuario. Pon onConflict en la columna del error, porque los upserts de Supabase comprueban la clave primaria por defecto. Desde el navegador, un upsert necesita además reglas de lectura que la fila cumpla, así que en una tabla donde los visitantes solo pueden añadir falla o te obliga a abrir la tabla para lectura.",{"q":816,"a":817},"¿Por qué pasa esto al registrarse?","Hay dos caminos: un trigger copia cada usuario nuevo en una tabla tuya y el valor que copia ya está ahí, o tu app inserta la misma fila de perfil que el trigger ya creó. Supabase informa de ambos como \"Database error saving new user\", y el 23505 en sí está en los logs de Postgres.",{"q":819,"a":820},"¿Es seguro mostrar este error a los usuarios?","No. La línea de detalle puede repetir el valor, el mensaje nombra tu tabla y tu columna, y el propio error le dice a quien envió la fila que el valor existe. Captura el código 23505, muestra una frase que hayas escrito tú y guarda el error en bruto en tus propios logs.","\u002Fblog\u002Fduplicate-key-value-violates-unique-constraint\u002Fcard-800x500.png",[823,824,825,826,827,828,829,830],"duplicate key value violates unique constraint","supabase error clave duplicada","postgres 23505","supabase upsert duplicado","key already exists","duplicate key value violates unique constraint pkey","there is no unique or exclusion constraint matching the on conflict specification","supabase onconflict",{},true,"\u002Fblog\u002Fduplicate-key-value-violates-unique-constraint","2026-10-24",{"title":5,"description":803},"blog\u002Fduplicate-key-value-violates-unique-constraint",[838,839,840],"Supabase \"duplicate key value violates unique constraint\" (código de Postgres 23505, HTTP 409 en tu app) significa que una fila ya tiene ese valor en una columna que admite cada valor una sola vez. El nombre entre comillas dice qué columna.","\"Key (id)=(1) already exists\" cuando tu app no envía ningún id significa que el contador de id de la tabla va por detrás de sus filas. Una línea con setval lo pone en su sitio.","Upsert solo ayuda si onConflict nombra esa columna, y desde el navegador también necesita las reglas de lectura de la tabla. Enséñales a tus usuarios una frase tuya: el mensaje en bruto puede devolver el valor.","fF5ydVihRW3eH0UHuhY0xSWzCjzTRmr27BRU8hI7X7U",[843,849,855,861,867,873,879,885,891,897,903,910,916,922,928,934,940,941,946,951,957,963,969,975,981,987,993,998,1004,1010,1016,1022,1028,1034,1040,1046,1052,1058,1064,1070,1076,1082,1088,1094,1100,1106,1111,1117,1123,1129,1135,1141,1147,1153,1159,1165,1171,1177,1183,1189,1195,1201,1206,1212,1218,1224,1230,1236,1242,1248,1254,1260,1266,1272,1278,1284,1289,1295,1301,1307,1313,1319,1325,1331,1336,1342,1348,1354,1359,1365],{"path":844,"title":845,"description":846,"published":847,"category":800,"image":848,"draft":804},"\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":850,"title":851,"description":852,"published":853,"category":800,"image":854,"draft":804},"\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":856,"title":857,"description":858,"published":859,"category":800,"image":860,"draft":804},"\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":862,"title":863,"description":864,"published":865,"category":800,"image":866,"draft":804},"\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":868,"title":869,"description":870,"published":871,"category":800,"image":872,"draft":804},"\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":874,"title":875,"description":876,"published":877,"category":800,"image":878,"draft":804},"\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":880,"title":881,"description":882,"published":883,"category":800,"image":884,"draft":804},"\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":886,"title":887,"description":888,"published":889,"category":800,"image":890,"draft":804},"\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":892,"title":893,"description":894,"published":895,"category":800,"image":896,"draft":804},"\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":898,"title":899,"description":900,"published":901,"category":800,"image":902,"draft":804},"\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":904,"title":905,"description":906,"published":907,"category":908,"image":909,"draft":804},"\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":911,"title":912,"description":913,"published":914,"category":800,"image":915,"draft":804},"\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":917,"title":918,"description":919,"published":920,"category":800,"image":921,"draft":804},"\u002Fblog\u002Fsupabase-invalid-login","\"Invalid login credentials\" en Supabase: las tres causas","Supabase dice \"Invalid login credentials\" ante una contraseña errónea, un email desconocido y una cuenta de Google. Cómo saber cuál es.","2026-10-28","\u002Fblog\u002Fsupabase-invalid-login\u002Fcard-800x500.png",{"path":923,"title":924,"description":925,"published":926,"category":800,"image":927,"draft":804},"\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":929,"title":930,"description":931,"published":932,"category":800,"image":933,"draft":804},"\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":935,"title":936,"description":937,"published":938,"category":800,"image":939,"draft":804},"\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":833,"title":5,"description":803,"published":834,"category":800,"image":821,"draft":804},{"path":364,"title":942,"description":943,"published":944,"category":800,"image":945,"draft":804},"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":659,"title":947,"description":948,"published":949,"category":800,"image":950,"draft":804},"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":952,"title":953,"description":954,"published":955,"category":800,"image":956,"draft":804},"\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":958,"title":959,"description":960,"published":961,"category":800,"image":962,"draft":804},"\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":964,"title":965,"description":966,"published":967,"category":800,"image":968,"draft":804},"\u002Fblog\u002Fsupabase-users-logged-out","Supabase \"Invalid Refresh Token: Refresh Token Not Found\": 3 causas","Supabase \"Invalid Refresh Token: Refresh Token Not Found\" cierra la sesión de tus usuarios. Tres causas, y cómo las separa el log de Auth.","2026-10-19","\u002Fblog\u002Fsupabase-users-logged-out\u002Fcard-800x500.png",{"path":970,"title":971,"description":972,"published":973,"category":800,"image":974,"draft":804},"\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":976,"title":977,"description":978,"published":979,"category":800,"image":980,"draft":804},"\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":982,"title":983,"description":984,"published":985,"category":800,"image":986,"draft":804},"\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":988,"title":989,"description":990,"published":991,"category":800,"image":992,"draft":804},"\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":437,"title":994,"description":995,"published":996,"category":800,"image":997,"draft":804},"\"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":999,"title":1000,"description":1001,"published":1002,"category":800,"image":1003,"draft":804},"\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":1005,"title":1006,"description":1007,"published":1008,"category":800,"image":1009,"draft":804},"\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":1011,"title":1012,"description":1013,"published":1014,"category":800,"image":1015,"draft":804},"\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":1017,"title":1018,"description":1019,"published":1020,"category":800,"image":1021,"draft":804},"\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":1023,"title":1024,"description":1025,"published":1026,"category":800,"image":1027,"draft":804},"\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":1029,"title":1030,"description":1031,"published":1032,"category":800,"image":1033,"draft":804},"\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":1035,"title":1036,"description":1037,"published":1038,"category":800,"image":1039,"draft":804},"\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":1041,"title":1042,"description":1043,"published":1044,"category":800,"image":1045,"draft":804},"\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":1047,"title":1048,"description":1049,"published":1050,"category":800,"image":1051,"draft":804},"\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":1053,"title":1054,"description":1055,"published":1056,"category":800,"image":1057,"draft":804},"\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":1059,"title":1060,"description":1061,"published":1062,"category":800,"image":1063,"draft":804},"\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":1065,"title":1066,"description":1067,"published":1068,"category":800,"image":1069,"draft":804},"\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":1071,"title":1072,"description":1073,"published":1074,"category":800,"image":1075,"draft":804},"\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":1077,"title":1078,"description":1079,"published":1080,"category":800,"image":1081,"draft":804},"\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":1083,"title":1084,"description":1085,"published":1086,"category":800,"image":1087,"draft":804},"\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":1089,"title":1090,"description":1091,"published":1092,"category":908,"image":1093,"draft":804},"\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":1095,"title":1096,"description":1097,"published":1098,"category":908,"image":1099,"draft":804},"\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":1101,"title":1102,"description":1103,"published":1104,"category":908,"image":1105,"draft":804},"\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":1107,"title":1108,"description":1109,"published":1104,"category":800,"image":1110,"draft":804},"\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":1112,"title":1113,"description":1114,"published":1115,"category":800,"image":1116,"draft":804},"\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":1118,"title":1119,"description":1120,"published":1121,"category":800,"image":1122,"draft":804},"\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":1124,"title":1125,"description":1126,"published":1127,"category":908,"image":1128,"draft":804},"\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":1130,"title":1131,"description":1132,"published":1133,"category":800,"image":1134,"draft":804},"\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":1136,"title":1137,"description":1138,"published":1139,"category":800,"image":1140,"draft":804},"\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":1142,"title":1143,"description":1144,"published":1145,"category":800,"image":1146,"draft":804},"\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":1148,"title":1149,"description":1150,"published":1151,"category":800,"image":1152,"draft":804},"\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":1154,"title":1155,"description":1156,"published":1157,"category":800,"image":1158,"draft":804},"\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":1160,"title":1161,"description":1162,"published":1163,"category":800,"image":1164,"draft":804},"\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":1166,"title":1167,"description":1168,"published":1169,"category":800,"image":1170,"draft":804},"\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":1172,"title":1173,"description":1174,"published":1175,"category":800,"image":1176,"draft":804},"\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":1178,"title":1179,"description":1180,"published":1181,"category":908,"image":1182,"draft":804},"\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":1184,"title":1185,"description":1186,"published":1187,"category":908,"image":1188,"draft":804},"\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":1190,"title":1191,"description":1192,"published":1193,"category":908,"image":1194,"draft":804},"\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":1196,"title":1197,"description":1198,"published":1199,"category":800,"image":1200,"draft":804},"\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":604,"title":1202,"description":1203,"published":1204,"category":800,"image":1205,"draft":804},"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":1207,"title":1208,"description":1209,"published":1210,"category":800,"image":1211,"draft":804},"\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":1213,"title":1214,"description":1215,"published":1216,"category":800,"image":1217,"draft":804},"\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":1219,"title":1220,"description":1221,"published":1222,"category":800,"image":1223,"draft":804},"\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":1225,"title":1226,"description":1227,"published":1228,"category":800,"image":1229,"draft":804},"\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":1231,"title":1232,"description":1233,"published":1234,"category":800,"image":1235,"draft":804},"\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":1237,"title":1238,"description":1239,"published":1240,"category":800,"image":1241,"draft":804},"\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":1243,"title":1244,"description":1245,"published":1246,"category":800,"image":1247,"draft":804},"\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":1249,"title":1250,"description":1251,"published":1252,"category":800,"image":1253,"draft":804},"\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":1255,"title":1256,"description":1257,"published":1258,"category":908,"image":1259,"draft":804},"\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":1261,"title":1262,"description":1263,"published":1264,"category":800,"image":1265,"draft":804},"\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":1267,"title":1268,"description":1269,"published":1270,"category":908,"image":1271,"draft":804},"\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":1273,"title":1274,"description":1275,"published":1276,"category":800,"image":1277,"draft":804},"\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":1279,"title":1280,"description":1281,"published":1282,"category":800,"image":1283,"draft":804},"\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":554,"title":1285,"description":1286,"published":1287,"category":800,"image":1288,"draft":804},"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":1290,"title":1291,"description":1292,"published":1293,"category":800,"image":1294,"draft":804},"\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":1296,"title":1297,"description":1298,"published":1299,"category":908,"image":1300,"draft":804},"\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":1302,"title":1303,"description":1304,"published":1305,"category":908,"image":1306,"draft":804},"\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":1308,"title":1309,"description":1310,"published":1311,"category":800,"image":1312,"draft":804},"\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":1314,"title":1315,"description":1316,"published":1317,"category":800,"image":1318,"draft":804},"\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":1320,"title":1321,"description":1322,"published":1323,"category":908,"image":1324,"draft":804},"\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":1326,"title":1327,"description":1328,"published":1329,"category":800,"image":1330,"draft":804},"\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":1332,"title":1333,"description":1334,"published":1329,"category":800,"image":1335,"draft":804},"\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":1337,"title":1338,"description":1339,"published":1340,"category":800,"image":1341,"draft":804},"\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":1343,"title":1344,"description":1345,"published":1346,"category":908,"image":1347,"draft":804},"\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":1349,"title":1350,"description":1351,"published":1352,"category":800,"image":1353,"draft":804},"\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":1355,"title":1356,"description":1357,"published":1352,"category":908,"image":1358,"draft":804},"\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":1360,"title":1361,"description":1362,"published":1363,"category":908,"image":1364,"draft":804},"\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":1366,"title":1367,"description":1368,"published":1363,"category":800,"image":1369,"draft":804},"\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",1794213645104]