[{"data":1,"prerenderedAt":1333},["ShallowReactive",2],{"blog-es-supabase-function-not-found":3,"blog-index-es":806},{"id":4,"title":5,"body":6,"category":763,"cover":764,"coverAlt":765,"description":766,"draft":767,"extension":768,"faq":769,"image":785,"keywords":786,"meta":795,"navigation":796,"ogTitle":5,"path":797,"published":798,"seo":799,"stem":800,"tldr":801,"updated":798,"__hash__":805},"blog_es\u002Fblog\u002Fsupabase-function-not-found.md","Supabase PGRST202: \"Could not find the function in the schema cache\"",{"type":7,"value":8,"toc":749},"minimark",[9,13,24,31,47,52,55,62,73,100,103,109,113,116,124,135,144,179,208,211,219,225,253,257,264,279,285,296,299,306,320,325,329,344,347,377,397,423,427,430,466,469,483,487,514,517,523,530,534,541,547,558,561,567,570,575,579,595,601,616,622,630,634,637,646,666,675,693,697,742],[10,11,12],"p",{},"Tu builder escribió una función de base de datos para cargar los pedidos de un\ncliente, y la página que la llama se queda vacía. La consola del navegador muestra\nesto:",[14,15,20],"pre",{"className":16,"code":18,"language":19},[17],"language-text","{\n  \"code\": \"PGRST202\",\n  \"details\": \"Searched for the function public.get_orders with parameter userId or with a single unnamed json\u002Fjsonb parameter, but no matches were found in the schema cache.\",\n  \"hint\": \"Perhaps you meant to call the function public.get_orders(user_id)\",\n  \"message\": \"Could not find the function public.get_orders(userId) in the schema cache\"\n}\n","text",[21,22,18],"code",{"__ignoreMap":23},"",[10,25,26,27,30],{},"Supabase \"Could not find the function in the schema cache\" significa que PostgREST\nno tiene ninguna función con ese nombre que acepte exactamente los argumentos que\nenvió tu app. PostgREST es la parte de Supabase que responde a las llamadas\n",[21,28,29],{},"supabase.rpc()"," de tu app, y lleva su propia lista de todas las funciones que\npuede llamar, llamada schema cache.",[10,32,33,34,38,39,42,43,46],{},"Esta es la parte en la que casi todas las respuestas se equivocan: ",[35,36,37],"strong",{},"te dicen que\nrecargues el schema cache."," En Supabase esa lista se actualiza sola cada vez que\nse crea o se cambia una función. El mensaje ya te ha dicho qué falla. ",[21,40,41],{},"userId",",\nentre paréntesis, es lo que envió tu app. ",[21,44,45],{},"user_id",", en la pista, es lo que espera\nla función.",[48,49,51],"h2",{"id":50},"qué-significa-could-not-find-the-function-in-the-schema-cache","¿Qué significa \"Could not find the function in the schema cache\"?",[10,53,54],{},"Que ninguna función del esquema donde buscó PostgREST tiene ese nombre junto con\nexactamente esos nombres de argumentos.",[10,56,57,58,61],{},"Piensa en cada función de la base de datos como un formulario impreso clavado\ndetrás de un mostrador: su nombre arriba y una casilla rotulada por cada argumento.\nCuando tu app llama a ",[21,59,60],{},"supabase.rpc('get_orders', { userId: id })",", entrega una\ncopia rellenada. PostgREST busca en la pared un formulario con el mismo nombre y\nlos mismos rótulos. Si ninguno los tiene, la copia se rechaza en el mostrador, y el\nmensaje te lee los rótulos que rellenaste.",[10,63,64,65,72],{},"Cuatro detalles están fijados en\n",[66,67,71],"a",{"href":68,"rel":69},"https:\u002F\u002Fgithub.com\u002FPostgREST\u002Fpostgrest\u002Fblob\u002Fmain\u002Fsrc\u002Flibrary\u002FPostgREST\u002FError.hs",[70],"nofollow","el propio código de PostgREST",":",[74,75,76,84,91,97],"ul",{},[77,78,79,80,83],"li",{},"el código es ",[21,81,82],{},"PGRST202"," y el estado HTTP 404, el de una página que no existe",[77,85,86,87,90],{},"los paréntesis listan los nombres de argumentos que envió tu app. Cuando no\nenvió ninguno, el mensaje dice ",[21,88,89],{},"without parameters"," en su lugar",[77,92,93,96],{},[21,94,95],{},"public."," delante del nombre es el esquema donde buscó PostgREST",[77,98,99],{},"la pista, cuando la hay, nombra la función más parecida que PostgREST sí conoce,\ncon los nombres de argumentos de esa función",[10,101,102],{},"Compara los paréntesis con la pista antes que nada.",[104,105],"diagram",{"alt":106,"caption":107,"src":108},"Una copia rellenada del formulario get_orders junto al formulario get_orders clavado en la pared. La casilla limit de la copia se une a la casilla limit de la pared y lleva una marca. La casilla userId de la copia corre en ámbar hasta un signo de distinto junto a user_id y baja hasta una placa con PGRST202 y 404.","PostgREST empareja una llamada por el nombre de la función y cada nombre de argumento. userId y user_id son dos rótulos distintos, así que ningún formulario de la pared coincide.","\u002Fblog\u002Fsupabase-function-not-found\u002Fargument-names-must-match-1600x680.png",[48,110,112],{"id":111},"causa-1-los-nombres-de-los-argumentos-no-coinciden","Causa 1: los nombres de los argumentos no coinciden",[10,114,115],{},"PostgREST elige una función por su nombre y los nombres de sus argumentos juntos,\nasí que una función llamada con los nombres equivocados es, para PostgREST, una\nfunción que no existe.",[10,117,118,119,72],{},"Las reglas, sacadas de la búsqueda en\n",[66,120,123],{"href":121,"rel":122},"https:\u002F\u002Fgithub.com\u002FPostgREST\u002Fpostgrest\u002Fblob\u002Fmain\u002Fsrc\u002Flibrary\u002FPostgREST\u002FPlan.hs",[70],"el planificador de PostgREST",[74,125,126,129,132],{},[77,127,128],{},"cada argumento sin valor por defecto tiene que enviarse, con su nombre exacto",[77,130,131],{},"un argumento con valor por defecto puede faltar",[77,133,134],{},"un nombre que la función no declara la descarta, aunque todos los demás nombres\ncoincidan",[10,136,137,138,140,141,143],{},"Un nombre puede estar mal de tres maneras fáciles de pasar por alto. La primera es\nJavaScript encontrándose con SQL: tu app escribe ",[21,139,41],{}," y la función declara\n",[21,142,45],{},".",[10,145,146,147,150,151,153,154,157,158,163,164,167,168,171,172,167,175,178],{},"La segunda es un prefijo. Algunas funciones llaman a sus argumentos ",[21,148,149],{},"p_user_id","\npara que no se confundan con una columna llamada ",[21,152,45],{},", y entonces la llamada\ntambién tiene que llevar el ",[21,155,156],{},"p_",". En una\n",[66,159,162],{"href":160,"rel":161},"https:\u002F\u002Fgithub.com\u002Forgs\u002Fsupabase\u002Fdiscussions\u002F28379",[70],"discusión de Supabase"," la\nllamada enviaba ",[21,165,166],{},"id",", ",[21,169,170],{},"nombre"," y tres más, la función declaraba ",[21,173,174],{},"p_id",[21,176,177],{},"p_nombre","\ny el resto, y la pista imprimía cada uno de los nombres correctos.",[10,180,181,182,185,186,189,190,195,196,198,199,202,203,207],{},"La tercera es la regla que también renombra columnas. Un nombre escrito en SQL sin\ncomillas dobles se pasa a minúsculas, así que\n",[21,183,184],{},"create function get_orders(userId uuid)"," guarda el argumento como ",[21,187,188],{},"userid",". Un\n",[66,191,194],{"href":192,"rel":193},"https:\u002F\u002Fcommunity.flutterflow.io\u002Fask-the-community\u002Fpost\u002Fcustom-action-calling-a-supabase-function---fails-with-a-postgres-IsfG19NQeLRmfRP",[70],"usuario de FlutterFlow","\nse topó exactamente con esto: la llamada enviaba ",[21,197,41],{}," y la pista respondía\n",[21,200,201],{},"(userid)",". ",[66,204,206],{"href":205},"\u002Fblog\u002Fsupabase-schema-cache-column-error","La versión de columnas de este error","\ntiene una tabla de lo que crea cada forma de escribirlo.",[10,209,210],{},"Para ver los nombres que guardó la base de datos, ejecuta esto en el SQL Editor:",[14,212,217],{"className":213,"code":215,"language":216,"meta":23},[214],"language-sql","select p.proname, pg_get_function_arguments(p.oid)\nfrom pg_proc p\njoin pg_namespace n on n.oid = p.pronamespace\nwhere n.nspname = 'public' and p.proname = 'get_orders';\n","sql",[21,218,215],{"__ignoreMap":23},[10,220,221,224],{},[21,222,223],{},"pg_get_function_arguments"," muestra los argumentos tal como se declararon, con sus\nvalores por defecto, así que también enseña cuáles puedes omitir.",[10,226,227,228,231,232,234,235,238,239,242,243,248,249,252],{},"El arreglo más seguro está en la app, porque no cambia nada en la base de datos.\nPide a tu builder que llame a la función con sus nombres guardados: \"",[21,229,230],{},"get_orders","\nrecibe ",[21,233,45],{},"; pasa ",[21,236,237],{},"{ user_id: … }"," en cada llamada ",[21,240,241],{},".rpc()"," a ella\".\nRenombrar el argumento es más difícil de lo que parece. La\n",[66,244,247],{"href":245,"rel":246},"https:\u002F\u002Fwww.postgresql.org\u002Fdocs\u002Fcurrent\u002Fsql-createfunction.html",[70],"documentación de Postgres","\ndice de ",[21,250,251],{},"CREATE OR REPLACE FUNCTION",": \"You cannot change the name already assigned\nto any input parameter\". Hay que borrar la función y crearla de nuevo, y cualquier\notra pantalla que la llame con el nombre antiguo falla después de la misma manera.",[48,254,256],{"id":255},"causa-2-el-valor-era-undefined-así-que-el-nombre-nunca-se-envió","Causa 2: el valor era undefined, así que el nombre nunca se envió",[10,258,259,260,263],{},"Un argumento cuyo valor es ",[21,261,262],{},"undefined"," nunca llega a PostgREST, y entonces la\nfunción se busca sin él.",[10,265,266,267,269,270,278],{},"supabase-js envía tus argumentos como JSON, y JSON no tiene ",[21,268,262],{},". La\n",[66,271,274,275],{"href":272,"rel":273},"https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FJavaScript\u002FReference\u002FGlobal_Objects\u002FJSON\u002Fstringify",[70],"referencia de MDN sobre ",[21,276,277],{},"JSON.stringify","\ndice que esos valores \"are either omitted (when found in an object) or changed to\nnull (when found in an array)\". Así que esta llamada, hecha antes de que cargue el\nusuario que ha iniciado sesión:",[14,280,283],{"className":281,"code":282,"language":19},[17],"await supabase.rpc('get_orders', { user_id: user?.id })\n",[21,284,282],{"__ignoreMap":23},[10,286,287,288,291,292,295],{},"envía ",[21,289,290],{},"{}",", y la respuesta es\n",[21,293,294],{},"Could not find the function public.get_orders without parameters in the schema cache",".\nCon dos argumentos y uno de ellos en undefined, los paréntesis listan solo el que\nllegó.",[10,297,298],{},"El nombre en tu código está bien, y por eso leer el código no lo encuentra. El\nmensaje sí: los paréntesis están vacíos, o les falta un nombre respecto a lo que\npasa el código.",[10,300,301,302,305],{},"El arreglo es hacer la llamada cuando el valor ya exista. Si la función está\nescrita para manejar un valor que falta, pasa ",[21,303,304],{},"null"," a propósito, que JSON sí\nconserva.",[10,307,308,309,312,313,315,316,143],{},"Un filtro hace lo contrario con el mismo valor. ",[21,310,311],{},".eq('user_id', user?.id)"," lo mete\nen la petición como la palabra ",[21,314,262],{},", y una columna uuid responde\n",[66,317,319],{"href":318},"\u002Fblog\u002Finvalid-input-syntax-for-type-uuid","invalid input syntax for type uuid",[104,321],{"alt":322,"caption":323,"src":324},"Una copia rellenada de get_orders con user_id conteniendo undefined en ámbar y limit conteniendo 10. Una flecha marcada JSON.stringify lleva a la copia que se envía de verdad, donde la casilla user_id es un hueco vacío de trazos y solo queda limit 10. Una segunda flecha lleva a una placa con PGRST202 y 404.","JSON deja fuera un valor undefined, así que el argumento desaparece por el camino y PostgREST busca una función sin él.","\u002Fblog\u002Fsupabase-function-not-found\u002Fundefined-is-never-sent-1600x560.png",[48,326,328],{"id":327},"causa-3-la-función-está-en-otro-esquema","Causa 3: la función está en otro esquema",[10,330,331,333,334,337,338,343],{},[21,332,29],{}," busca en el esquema ",[21,335,336],{},"public",", el que nombra el mensaje, salvo que\nle digas otra cosa. Una función creada en otro sitio no está en la lista de\nPostgREST:\n",[66,339,342],{"href":340,"rel":341},"https:\u002F\u002Fgithub.com\u002FPostgREST\u002Fpostgrest\u002Fblob\u002Fmain\u002Fsrc\u002Flibrary\u002FPostgREST\u002FSchemaCache.hs",[70],"solo carga funciones","\nde los esquemas que tu proyecto expone por su API.",[10,345,346],{},"Aparece de dos formas:",[74,348,349,364],{},[77,350,351,352,355,356,359,360,363],{},"la función se creó en un esquema propio, como ",[21,353,354],{},"private"," o ",[21,357,358],{},"billing",", y se llama\ncon un ",[21,361,362],{},".rpc('get_orders')"," sin más",[77,365,366,367,370,371,373,374],{},"el esquema va escrito en el nombre, como ",[21,368,369],{},".rpc('billing.get_orders')",". PostgREST\nlee toda la cadena como un solo nombre y lo busca en ",[21,372,336],{},", así que el\nmensaje dice ",[21,375,376],{},"public.billing.get_orders",[10,378,379,380,383,384,389,390,167,393,396],{},"Una función de otro esquema se llama con ",[21,381,382],{},".schema('billing').rpc('get_orders', …)",",\ny ese esquema tiene que aparecer en Exposed schemas, en los ajustes de la API del\nproyecto, como muestra\n",[66,385,388],{"href":386,"rel":387},"https:\u002F\u002Fsupabase.com\u002Fdocs\u002Fguides\u002Fapi\u002Fusing-custom-schemas",[70],"la guía de Supabase sobre esquemas propios",".\nHasta entonces la respuesta cambia a ",[21,391,392],{},"PGRST106",[21,394,395],{},"Invalid schema: billing",", con una\npista que enumera los esquemas expuestos.",[10,398,399,400,403,404,407,408,412,413,417,418,422],{},"Exponer un esquema como lo hace esa guía abre cada tabla que contiene. La guía da a\n",[21,401,402],{},"anon"," y ",[21,405,406],{},"authenticated"," acceso a cada tabla del nuevo esquema, y desde entonces\nsolo tus reglas de ",[66,409,411],{"href":410},"\u002Fblog\u002Fenable-rls-on-every-supabase-table","Row Level Security","\ndeciden lo que un visitante puede leer ahí.\n",[66,414,416],{"href":415},"\u002Fblog\u002Fsupabase-relation-does-not-exist","La versión de este error para tablas","\ntiene la consulta que lista qué tablas de un esquema tienen esas reglas\ndesactivadas, para ejecutarla antes de exponerlo. Algunas funciones se guardan en un\nesquema privado para que nadie pueda llamarlas desde fuera: nuestra\n",[66,419,421],{"href":420},"\u002Fblog\u002Finfinite-recursion-in-policy-for-relation","guía sobre la recursión infinita en una policy","\npone una ahí precisamente por eso. Una llamada a una función así desde el navegador\nrecibe PGRST202 a propósito, y su esquema debe seguir sin exponerse.",[48,424,426],{"id":425},"causa-4-el-nombre-de-la-propia-función-o-ninguna-función","Causa 4: el nombre de la propia función, o ninguna función",[10,428,429],{},"PostgREST busca el nombre de forma exacta, letra por letra y con mayúsculas y\nminúsculas.",[74,431,432,442,452],{},[77,433,434,435,438,439,441],{},"una errata, o ",[21,436,437],{},"getOrders"," en la llamada frente a ",[21,440,230],{}," en la base de datos",[77,443,444,445,447,448,451],{},"una función creada como ",[21,446,437],{}," sin comillas dobles, que se guarda como\n",[21,449,450],{},"getorders"," por la misma regla que sus argumentos",[77,453,454,455,458,459,461,462,465],{},"un procedimiento. PostgREST solo lista funciones, así que ",[21,456,457],{},"create procedure","\ncrea algo a lo que ",[21,460,241],{}," no llega, y lo mismo pasa con una función que\n",[21,463,464],{},"returns trigger",", que se ejecuta desde un trigger y nunca desde tu app",[10,467,468],{},"La pista también ayuda aquí. Cuando no existe ninguna función con ese nombre,\nPostgREST sugiere el nombre más cercano que tiene, si alguno se parece lo\nsuficiente.",[10,470,471,472,477,478,482],{},"O la función nunca se creó en la base de datos a la que llama tu app. Si construyes\ncon Lovable, repasa el chat buscando SQL que te pidieran aprobar. La\n",[66,473,476],{"href":474,"rel":475},"https:\u002F\u002Fdocs.lovable.dev\u002Fintegrations\u002Fsupabase",[70],"documentación de Supabase de Lovable","\ndice que \"writes the SQL, shows it to you, and asks for your approval in the\nproject chat before running it\". Hasta que lo apruebas, la función solo existe en\nel código que la llama. Si alguien la creó a mano, comprueba que fue al proyecto\ncon el que habla tu app:\n",[66,479,481],{"href":480},"\u002Fblog\u002Fsupabase-branching-is-not-a-backup","una rama es un proyecto aparte"," con su\npropia base de datos.",[48,484,486],{"id":485},"causa-5-el-caché-de-verdad-está-desactualizado","Causa 5: el caché de verdad está desactualizado",[10,488,489,490,493,494,497,498,403,501,504,505,508,509,143],{},"En Supabase este es el caso raro. Cada base de datos de Supabase tiene un event\ntrigger, ",[21,491,492],{},"pgrst_ddl_watch",", que envía ",[21,495,496],{},"NOTIFY pgrst, 'reload schema'"," después de\ncada ",[21,499,500],{},"CREATE FUNCTION",[21,502,503],{},"ALTER FUNCTION",", y un segundo, ",[21,506,507],{},"pgrst_drop_watch",", lo\nenvía cuando se borra una función. Los dos están en\n",[66,510,513],{"href":511,"rel":512},"https:\u002F\u002Fgithub.com\u002Fsupabase\u002Fpostgres\u002Fblob\u002Fdevelop\u002Fmigrations\u002Fschema-17.sql",[70],"la configuración de base de datos de Supabase",[10,515,516],{},"Cuando ese mensaje no llega, recarga la lista a mano en el SQL Editor:",[14,518,521],{"className":519,"code":520,"language":216,"meta":23},[214],"NOTIFY pgrst, 'reload schema';\n",[21,522,520],{"__ignoreMap":23},[10,524,525,526,529],{},"Si nada cambia,\n",[66,527,528],{"href":205},"la versión de columnas de este error","\ncubre los dos pasos siguientes, la cola de notificaciones atascada y el reinicio del\nproyecto. Los dos funcionan igual para las funciones.",[48,531,533],{"id":532},"cuando-dos-funciones-comparten-un-nombre","Cuando dos funciones comparten un nombre",[10,535,536,537,540],{},"PostgREST no adivina entre ellas. Cuando dos funciones tienen el mismo nombre y las\ndos aceptan los argumentos que envió tu app, la llamada falla con ",[21,538,539],{},"PGRST203"," y\nHTTP 300:",[14,542,545],{"className":543,"code":544,"language":19},[17],"Could not choose the best candidate function between: public.get_orders(user_id => uuid), public.get_orders(user_id => text)\n",[21,546,544],{"__ignoreMap":23},[10,548,549,550,552,553,557],{},"Arreglar un PGRST202 es una forma de llegar aquí. Tu builder cambia el tipo de un\nargumento con ",[21,551,251],{},", y Postgres no reemplaza nada. Su\n",[66,554,556],{"href":245,"rel":555},[70],"documentación","\ndice que los tipos de los argumentos no se pueden cambiar así: \"if you tried, you\nwould actually be creating a new, distinct function\". Ahora existen las dos\nversiones una al lado de la otra.",[10,559,560],{},"La pista de PostgREST para este caso dice \"Try renaming the parameters or the\nfunction itself in the database so function overloading can be resolved\". El\narreglo más sencillo es borrar la versión que no quieres, indicando el tipo de su\nargumento para que Postgres sepa a cuál te refieres:",[14,562,565],{"className":563,"code":564,"language":216,"meta":23},[214],"drop function public.get_orders(text);\n",[21,566,564],{"__ignoreMap":23},[10,568,569],{},"Comprueba qué versión llama tu app antes de borrar cualquiera de las dos.",[104,571],{"alt":572,"caption":573,"src":574},"Una copia rellenada de get_orders con user_id, y dos formularios get_orders clavados en la pared, uno cuya casilla user_id recibe uuid y otro que recibe text. Líneas de trazos en ámbar van de la copia a los dos formularios, y la copia baja hasta una placa con PGRST203 y 300.","Cambiar el tipo de un argumento con CREATE OR REPLACE añade una segunda función con el mismo nombre. Una llamada que encaja con las dos se rechaza.","\u002Fblog\u002Fsupabase-function-not-found\u002Ftwo-functions-one-name-1600x700.png",[48,576,578],{"id":577},"cómo-llamar-bien-a-una-función-de-supabase","Cómo llamar bien a una función de Supabase",[10,580,581,582,587,588,591,592,143],{},"Pasa un objeto cuyas claves sean los nombres de los argumentos de la función,\nescritos como los guardó la base de datos. En\n",[66,583,586],{"href":584,"rel":585},"https:\u002F\u002Fsupabase.com\u002Fdocs\u002Fguides\u002Fdatabase\u002Ffunctions",[70],"la guía de Supabase sobre funciones de base de datos",",\nuna función declarada como ",[21,589,590],{},"add_planet(name text)"," se llama con\n",[21,593,594],{},"supabase.rpc('add_planet', { name: 'Jakku' })",[10,596,597,598,600],{},"En cuanto tu app puede llamar a la función, cualquier otra persona también puede.\nLa misma guía lo dice claro: \"By default, any role can run a database function.\"\nEso incluye ",[21,599,402],{},", el rol detrás de cada petición de un visitante que no ha\niniciado sesión. Una función que tu app llama desde el navegador la puede llamar\ncualquiera que tenga tu clave pública, con los argumentos que quiera.",[10,602,603,604,607,608,610,611,613,614,143],{},"Para una función que solo deben ejecutar usuarios con sesión iniciada, sigue la\nguía: quita ",[21,605,606],{},"execute"," a ",[21,609,336],{}," y a ",[21,612,402],{},", y devuélveselo a ",[21,615,406],{},[14,617,620],{"className":618,"code":619,"language":216,"meta":23},[214],"revoke execute on function public.get_orders from public, anon;\ngrant execute on function public.get_orders to authenticated;\n",[21,621,619],{"__ignoreMap":23},[10,623,624,625,629],{},"Tus tablas son la otra mitad de lo que alcanza esa clave pública. Nuestro\n",[66,626,628],{"href":627},"\u002Fsecurity-scanner","escaneo gratuito"," comprueba desde fuera si entregan filas a\ncualquiera que la tenga, y no necesita cuenta.",[48,631,633],{"id":632},"cambiar-una-función-de-la-que-depende-tu-app","Cambiar una función de la que depende tu app",[10,635,636],{},"Guarda una copia de la base de datos de antes del cambio, y entérate cuando cambie\nalgo que tu app expone.",[10,638,639],{},[35,640,641,645],{},[66,642,644],{"href":643},"\u002Fsupabase-backups","Reeve Care"," guarda una copia de tu base de datos de Supabase:",[74,647,648,651,654,657,660,663],{},[77,649,650],{},"una copia cifrada según un calendario, en el almacenamiento propio de Reeve en la UE, fuera de tu cuenta de Supabase",[77,652,653],{},"la copia de antes de tu cambio tiene tus funciones tal como estaban definidas entonces, junto a tus tablas y filas",[77,655,656],{},"cada copia verificada antes de contar, contando las filas de cada tabla",[77,658,659],{},"también tus archivos subidos, en cuanto conectas una credencial de Storage",[77,661,662],{},"restauración con un clic, con una copia de seguridad del estado actual tomada primero",[77,664,665],{},"todo lo que hace Monitor",[10,667,668],{},[35,669,670,674],{},[66,671,673],{"href":672},"\u002Fwebsite-monitoring","Reeve Monitor"," sigue comprobando desde fuera:",[74,676,677,680,683,690],{},[77,678,679],{},"si tu app está en línea, comprobado cada minuto",[77,681,682],{},"la comprobación de seguridad completa cada hora, en hasta tres apps",[77,684,685,686,689],{},"un mensaje cuando un resultado ",[35,687,688],{},"cambia",", para que un deploy que cambia lo que tu app expone te llegue sin que tengas que ir a mirar",[77,691,692],{},"un informe mensual, en lenguaje claro, de lo que vio",[48,694,696],{"id":695},"qué-hacer-ahora","Qué hacer ahora",[698,699,700],"key-takeaways",{},[74,701,702,705,711,717,724,727,734],{},[77,703,704],{},"Lee el mensaje: los nombres entre paréntesis son lo que envió tu app, y la pista nombra lo que tiene la base de datos.",[77,706,707,708,710],{},"Compara los nombres de los argumentos letra por letra, prefijo ",[21,709,156],{}," incluido. Un nombre escrito en SQL sin comillas se guarda en minúsculas.",[77,712,713,714,716],{},"Si los paréntesis están vacíos o les falta un nombre, busca un valor que estaba en ",[21,715,262],{}," cuando se hizo la llamada.",[77,718,719,720,723],{},"Revisa el esquema del mensaje. Una función de otro esquema necesita ",[21,721,722],{},".schema('nombre').rpc(…)"," y un esquema expuesto, y exponer uno hace accesibles también sus tablas.",[77,725,726],{},"Si dos funciones comparten el nombre, borra la versión que tu app no llama.",[77,728,729,730,733],{},"Ejecuta ",[21,731,732],{},"NOTIFY pgrst, 'reload schema';"," solo cuando los nombres ya coincidan.",[77,735,736,737,607,739,741],{},"Quita ",[21,738,606],{},[21,740,402],{}," en cualquier función que un visitante sin sesión no deba ejecutar nunca.",[10,743,744,745,748],{},"Empieza por los paréntesis, porque contienen exactamente lo que envió tu app. Antes\nde borrar o reemplazar una función de la que depende tu app,\n",[66,746,747],{"href":643},"guarda una copia de la base de datos"," de antes del cambio.",{"title":23,"searchDepth":750,"depth":750,"links":751},3,[752,754,755,756,757,758,759,760,761,762],{"id":50,"depth":753,"text":51},2,{"id":111,"depth":753,"text":112},{"id":255,"depth":753,"text":256},{"id":327,"depth":753,"text":328},{"id":425,"depth":753,"text":426},{"id":485,"depth":753,"text":486},{"id":532,"depth":753,"text":533},{"id":577,"depth":753,"text":578},{"id":632,"depth":753,"text":633},{"id":695,"depth":753,"text":696},"Fundamentos de seguridad","\u002Fblog\u002Fsupabase-function-not-found\u002Fcover-1200x630.png","Un formulario impreso clavado en la pared con una fila de casillas rotuladas, y delante una copia rellenada con una casilla iluminada en ámbar.","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.",false,"md",[770,773,776,779,782],{"q":771,"a":772},"¿Qué significa PGRST202?","PGRST202 es el código que devuelve PostgREST, la parte de Supabase que responde a las llamadas supabase.rpc(), cuando ninguna función coincide con una llamada. Llega con HTTP 404 y el mensaje \"Could not find the function public.name(arguments) in the schema cache\". Los nombres entre paréntesis son los argumentos que envió tu app, y public es el esquema donde buscó.",{"q":774,"a":775},"La función existe. ¿Por qué mi llamada RPC dice que falta?","PostgREST reconoce una función por su nombre y los nombres de sus argumentos juntos. Envía cada argumento que no tenga valor por defecto, con exactamente el nombre que guardó la base de datos, y nada que la función no declare. Un nombre escrito en SQL sin comillas dobles se guarda en minúsculas, así que userId en un create function se convierte en userid.",{"q":777,"a":778},"¿Por qué el error dice \"without parameters\"?","Tu app no envió ningún argumento. Con supabase-js la razón habitual es un valor que estaba en undefined cuando se hizo la llamada: los argumentos viajan como JSON, que deja fuera los valores undefined, así que { user_id: undefined } llega como {}. Haz la llamada cuando el valor ya exista, o pasa null si la función está escrita para manejar un valor que falta.",{"q":780,"a":781},"¿La función tiene que estar en el esquema public?","supabase.rpc() busca en public salvo que elijas otro esquema con .schema('nombre'), y ese esquema tiene que aparecer en Exposed schemas, en los ajustes de la API del proyecto. Exponer un esquema hace accesible por la API lo que hay en él, así que conviene decidirlo a propósito.",{"q":783,"a":784},"¿Cómo recargo el schema cache de Supabase?","Ejecuta NOTIFY pgrst, 'reload schema'; en el SQL Editor. Supabase ya envía ese mensaje por sí solo cada vez que se crea, cambia o borra una función, así que recargar solo ayuda cuando el mensaje no llegó. Revisa primero los nombres.","\u002Fblog\u002Fsupabase-function-not-found\u002Fcard-800x500.png",[787,788,789,790,791,792,793,794],"could not find the function in the schema cache","pgrst202","supabase rpc función no encontrada","supabase rpc 404","supabase función parámetros no coinciden","could not find the function without parameters in the schema cache","pgrst203","could not choose the best candidate function",{},true,"\u002Fblog\u002Fsupabase-function-not-found","2026-10-22",{"title":5,"description":766},"blog\u002Fsupabase-function-not-found",[802,803,804],"Supabase \"Could not find the function in the schema cache\" (PGRST202) significa que ninguna función tiene ese nombre y acepta exactamente los nombres de argumentos que envió tu app. Los nombres entre paréntesis en el mensaje son los que envió tu app.","Revisa primero los nombres de los argumentos: userId enviado a una función que declara user_id, un prefijo p_ que falta, o un valor que estaba en undefined y por eso nunca se envió.","Supabase actualiza su lista de funciones por sí solo cada vez que se crea, cambia o borra una, así que revisa los nombres y el esquema antes de recargar nada.","I8TAUW5J86iDtb1yZyZuAu1PJAXhJEFq0FU-bp1HdlU",[807,813,819,825,831,837,843,849,855,861,867,874,880,886,892,898,903,909,914,915,920,926,932,938,944,950,956,962,968,974,980,986,992,997,1003,1009,1015,1021,1027,1033,1039,1045,1051,1057,1063,1069,1074,1080,1086,1092,1098,1104,1110,1116,1122,1128,1134,1140,1146,1152,1158,1164,1169,1175,1181,1187,1193,1199,1205,1211,1217,1223,1229,1235,1241,1247,1253,1259,1264,1270,1276,1282,1288,1294,1299,1305,1311,1317,1322,1328],{"path":808,"title":809,"description":810,"published":811,"category":763,"image":812,"draft":767},"\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":814,"title":815,"description":816,"published":817,"category":763,"image":818,"draft":767},"\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":820,"title":821,"description":822,"published":823,"category":763,"image":824,"draft":767},"\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":826,"title":827,"description":828,"published":829,"category":763,"image":830,"draft":767},"\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":832,"title":833,"description":834,"published":835,"category":763,"image":836,"draft":767},"\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":838,"title":839,"description":840,"published":841,"category":763,"image":842,"draft":767},"\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":844,"title":845,"description":846,"published":847,"category":763,"image":848,"draft":767},"\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":850,"title":851,"description":852,"published":853,"category":763,"image":854,"draft":767},"\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":856,"title":857,"description":858,"published":859,"category":763,"image":860,"draft":767},"\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":862,"title":863,"description":864,"published":865,"category":763,"image":866,"draft":767},"\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":868,"title":869,"description":870,"published":871,"category":872,"image":873,"draft":767},"\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":875,"title":876,"description":877,"published":878,"category":763,"image":879,"draft":767},"\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":881,"title":882,"description":883,"published":884,"category":763,"image":885,"draft":767},"\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":887,"title":888,"description":889,"published":890,"category":763,"image":891,"draft":767},"\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":893,"title":894,"description":895,"published":896,"category":763,"image":897,"draft":767},"\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":318,"title":899,"description":900,"published":901,"category":763,"image":902,"draft":767},"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":904,"title":905,"description":906,"published":907,"category":763,"image":908,"draft":767},"\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":415,"title":910,"description":911,"published":912,"category":763,"image":913,"draft":767},"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":797,"title":5,"description":766,"published":798,"category":763,"image":785,"draft":767},{"path":205,"title":916,"description":917,"published":918,"category":763,"image":919,"draft":767},"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":921,"title":922,"description":923,"published":924,"category":763,"image":925,"draft":767},"\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":927,"title":928,"description":929,"published":930,"category":763,"image":931,"draft":767},"\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":933,"title":934,"description":935,"published":936,"category":763,"image":937,"draft":767},"\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":939,"title":940,"description":941,"published":942,"category":763,"image":943,"draft":767},"\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":945,"title":946,"description":947,"published":948,"category":763,"image":949,"draft":767},"\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":951,"title":952,"description":953,"published":954,"category":763,"image":955,"draft":767},"\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":957,"title":958,"description":959,"published":960,"category":763,"image":961,"draft":767},"\u002Fblog\u002Fdatabase-error-saving-new-user","\"Database error saving new user\" en Supabase: dónde mirar","\"Database error saving new user\" significa que algo unido a tu registro de Supabase falló y se deshizo entero. El error real está en tus logs.","2026-10-14","\u002Fblog\u002Fdatabase-error-saving-new-user\u002Fcard-800x500.png",{"path":963,"title":964,"description":965,"published":966,"category":763,"image":967,"draft":767},"\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":969,"title":970,"description":971,"published":972,"category":763,"image":973,"draft":767},"\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":975,"title":976,"description":977,"published":978,"category":763,"image":979,"draft":767},"\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":981,"title":982,"description":983,"published":984,"category":763,"image":985,"draft":767},"\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":987,"title":988,"description":989,"published":990,"category":763,"image":991,"draft":767},"\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":420,"title":993,"description":994,"published":995,"category":763,"image":996,"draft":767},"\"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":998,"title":999,"description":1000,"published":1001,"category":763,"image":1002,"draft":767},"\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":1004,"title":1005,"description":1006,"published":1007,"category":763,"image":1008,"draft":767},"\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":1010,"title":1011,"description":1012,"published":1013,"category":763,"image":1014,"draft":767},"\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":1016,"title":1017,"description":1018,"published":1019,"category":763,"image":1020,"draft":767},"\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":1022,"title":1023,"description":1024,"published":1025,"category":763,"image":1026,"draft":767},"\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":1028,"title":1029,"description":1030,"published":1031,"category":763,"image":1032,"draft":767},"\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":1034,"title":1035,"description":1036,"published":1037,"category":763,"image":1038,"draft":767},"\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":1040,"title":1041,"description":1042,"published":1043,"category":763,"image":1044,"draft":767},"\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":1046,"title":1047,"description":1048,"published":1049,"category":763,"image":1050,"draft":767},"\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":1052,"title":1053,"description":1054,"published":1055,"category":872,"image":1056,"draft":767},"\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":1058,"title":1059,"description":1060,"published":1061,"category":872,"image":1062,"draft":767},"\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":1064,"title":1065,"description":1066,"published":1067,"category":872,"image":1068,"draft":767},"\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":1070,"title":1071,"description":1072,"published":1067,"category":763,"image":1073,"draft":767},"\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":1075,"title":1076,"description":1077,"published":1078,"category":763,"image":1079,"draft":767},"\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":1081,"title":1082,"description":1083,"published":1084,"category":763,"image":1085,"draft":767},"\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":1087,"title":1088,"description":1089,"published":1090,"category":872,"image":1091,"draft":767},"\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":1093,"title":1094,"description":1095,"published":1096,"category":763,"image":1097,"draft":767},"\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":1099,"title":1100,"description":1101,"published":1102,"category":763,"image":1103,"draft":767},"\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":1105,"title":1106,"description":1107,"published":1108,"category":763,"image":1109,"draft":767},"\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":1111,"title":1112,"description":1113,"published":1114,"category":763,"image":1115,"draft":767},"\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":1117,"title":1118,"description":1119,"published":1120,"category":763,"image":1121,"draft":767},"\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":1123,"title":1124,"description":1125,"published":1126,"category":763,"image":1127,"draft":767},"\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":1129,"title":1130,"description":1131,"published":1132,"category":763,"image":1133,"draft":767},"\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":1135,"title":1136,"description":1137,"published":1138,"category":763,"image":1139,"draft":767},"\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":1141,"title":1142,"description":1143,"published":1144,"category":872,"image":1145,"draft":767},"\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":1147,"title":1148,"description":1149,"published":1150,"category":872,"image":1151,"draft":767},"\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":1153,"title":1154,"description":1155,"published":1156,"category":872,"image":1157,"draft":767},"\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":1159,"title":1160,"description":1161,"published":1162,"category":763,"image":1163,"draft":767},"\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":410,"title":1165,"description":1166,"published":1167,"category":763,"image":1168,"draft":767},"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":1170,"title":1171,"description":1172,"published":1173,"category":763,"image":1174,"draft":767},"\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":1176,"title":1177,"description":1178,"published":1179,"category":763,"image":1180,"draft":767},"\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":1182,"title":1183,"description":1184,"published":1185,"category":763,"image":1186,"draft":767},"\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":1188,"title":1189,"description":1190,"published":1191,"category":763,"image":1192,"draft":767},"\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":1194,"title":1195,"description":1196,"published":1197,"category":763,"image":1198,"draft":767},"\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":1200,"title":1201,"description":1202,"published":1203,"category":763,"image":1204,"draft":767},"\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":1206,"title":1207,"description":1208,"published":1209,"category":763,"image":1210,"draft":767},"\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":1212,"title":1213,"description":1214,"published":1215,"category":763,"image":1216,"draft":767},"\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":1218,"title":1219,"description":1220,"published":1221,"category":872,"image":1222,"draft":767},"\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":1224,"title":1225,"description":1226,"published":1227,"category":763,"image":1228,"draft":767},"\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":1230,"title":1231,"description":1232,"published":1233,"category":872,"image":1234,"draft":767},"\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":1236,"title":1237,"description":1238,"published":1239,"category":763,"image":1240,"draft":767},"\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":1242,"title":1243,"description":1244,"published":1245,"category":763,"image":1246,"draft":767},"\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":1248,"title":1249,"description":1250,"published":1251,"category":763,"image":1252,"draft":767},"\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":1254,"title":1255,"description":1256,"published":1257,"category":763,"image":1258,"draft":767},"\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":480,"title":1260,"description":1261,"published":1262,"category":872,"image":1263,"draft":767},"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":1265,"title":1266,"description":1267,"published":1268,"category":872,"image":1269,"draft":767},"\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":1271,"title":1272,"description":1273,"published":1274,"category":763,"image":1275,"draft":767},"\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":1277,"title":1278,"description":1279,"published":1280,"category":763,"image":1281,"draft":767},"\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":1283,"title":1284,"description":1285,"published":1286,"category":872,"image":1287,"draft":767},"\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":1289,"title":1290,"description":1291,"published":1292,"category":763,"image":1293,"draft":767},"\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":1295,"title":1296,"description":1297,"published":1292,"category":763,"image":1298,"draft":767},"\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":1300,"title":1301,"description":1302,"published":1303,"category":763,"image":1304,"draft":767},"\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":1306,"title":1307,"description":1308,"published":1309,"category":872,"image":1310,"draft":767},"\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":1312,"title":1313,"description":1314,"published":1315,"category":763,"image":1316,"draft":767},"\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":1318,"title":1319,"description":1320,"published":1315,"category":872,"image":1321,"draft":767},"\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":1323,"title":1324,"description":1325,"published":1326,"category":872,"image":1327,"draft":767},"\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":1329,"title":1330,"description":1331,"published":1326,"category":763,"image":1332,"draft":767},"\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",1794213645195]