[{"data":1,"prerenderedAt":1227},["ShallowReactive",2],{"blog-es-violates-foreign-key-constraint":3,"blog-index-es":699},{"id":4,"title":5,"body":6,"category":658,"cover":659,"coverAlt":660,"description":661,"draft":662,"extension":663,"faq":664,"image":680,"keywords":681,"meta":688,"navigation":689,"ogTitle":5,"path":690,"published":691,"seo":692,"stem":693,"tldr":694,"updated":691,"__hash__":698},"blog_es\u002Fblog\u002Fviolates-foreign-key-constraint.md","Error de Supabase \"violates foreign key constraint\" al insertar",{"type":7,"value":8,"toc":645},"minimark",[9,13,24,31,34,39,50,53,170,179,191,195,198,221,234,247,250,258,265,271,274,278,281,287,295,312,318,341,351,387,391,394,401,407,421,425,439,445,450,454,457,464,470,480,483,489,497,503,507,512,529,532,538,541,545,548,557,579,588,602,606,638],[10,11,12],"p",{},"Pulsas Guardar en un perfil nuevo de tu app y no cambia nada. La página no muestra\nningún mensaje. La consola del navegador tiene uno:",[14,15,20],"pre",{"className":16,"code":18,"language":19},[17],"language-text","{\n  \"code\": \"23503\",\n  \"details\": \"Key is not present in table \\\"users\\\".\",\n  \"hint\": null,\n  \"message\": \"insert or update on table \\\"profiles\\\" violates foreign key constraint \\\"profiles_id_fkey\\\"\"\n}\n","text",[21,22,18],"code",{"__ignoreMap":23},"",[10,25,26,27,30],{},"Supabase \"violates foreign key constraint\" significa que la fila que intentaste\nguardar nombra una fila de otra tabla, y esa fila no existe. Aquí el perfil llevaba\nun ",[21,28,29],{},"id"," que debía pertenecer a alguien que se registró, y Postgres no encontró a\nnadie con ese id. No se guardó nada.",[10,32,33],{},"La forma más rápida de que el mensaje desaparezca es borrar la regla. Después los\nguardados pasan, y las filas que crean apuntan a la nada: una línea de pedido sin\npedido, un perfil sin ninguna persona detrás. Averigua primero qué fila falta y\npor qué. Se perdió de una de unas pocas maneras.",[35,36,38],"h2",{"id":37},"qué-significa-violates-foreign-key-constraint-en-supabase","¿Qué significa \"violates foreign key constraint\" en Supabase?",[10,40,41,42,49],{},"Significa que una columna de tu fila tiene que coincidir con una fila de otra\ntabla, el valor que enviaste no coincide con ninguna, y Postgres rechazó la fila\nentera. La\n",[43,44,48],"a",{"href":45,"rel":46},"https:\u002F\u002Fwww.postgresql.org\u002Fdocs\u002Fcurrent\u002Fddl-constraints.html",[47],"nofollow","documentación de Postgres sobre restricciones","\ndefine la regla en una frase: una clave foránea \"specifies that the values in a\ncolumn (or a group of columns) must match the values appearing in some row of\nanother table.\"",[10,51,52],{},"Piensa en la otra tabla como una calle, y en cada una de sus filas como una casa\ncon un número. Tu fila nueva es una carta, y la columna con la clave foránea es el\nnúmero de casa escrito en ella. Postgres es la oficina de clasificación. Antes de\naceptar la carta busca el número en la calle, y una carta para una casa que no\nexiste vuelve en el acto. El mensaje dice qué carta volvió. La línea de detalle\ndice en qué calle se buscó.",[54,55,56,72],"table",{},[57,58,59],"thead",{},[60,61,62,66,69],"tr",{},[63,64,65],"th",{},"Campo",[63,67,68],{},"Ejemplo",[63,70,71],{},"Qué te dice",[73,74,75,99,114,130,145],"tbody",{},[60,76,77,82,87],{},[78,79,80],"td",{},[21,81,21],{},[78,83,84],{},[21,85,86],{},"23503",[78,88,89,90,93,94],{},"El código de Postgres para este error, llamado ",[21,91,92],{},"foreign_key_violation"," en ",[43,95,98],{"href":96,"rel":97},"https:\u002F\u002Fgithub.com\u002Fpostgres\u002Fpostgres\u002Fblob\u002Fmaster\u002Fsrc\u002Fbackend\u002Futils\u002Ferrcodes.txt",[47],"su lista de códigos",[60,100,101,106,111],{},[78,102,103],{},[21,104,105],{},"message",[78,107,108],{},[21,109,110],{},"insert or update on table \"profiles\" violates foreign key constraint \"profiles_id_fkey\"",[78,112,113],{},"La tabla en la que escribiste, y luego el nombre de la regla",[60,115,116,121,127],{},[78,117,118],{},[21,119,120],{},"details",[78,122,123,126],{},[21,124,125],{},"Key (id)=(…) is not present in table \"users\".",", o lo mismo sin el valor",[78,128,129],{},"La columna, el valor que enviaste y la tabla donde buscó Postgres. La sección siguiente explica cuándo se omite el valor",[60,131,132,137,142],{},[78,133,134],{},[21,135,136],{},"hint",[78,138,139],{},[21,140,141],{},"null",[78,143,144],{},"Nada que añadir para este error",[60,146,147,150,155],{},[78,148,149],{},"Estado HTTP",[78,151,152],{},[21,153,154],{},"409 Conflict",[78,156,157,158,161,162,164,165],{},"PostgREST, la parte de Supabase que convierte las llamadas ",[21,159,160],{},"supabase.from(...)"," en SQL, asigna ",[21,163,86],{}," al estado 409 en ",[43,166,169],{"href":167,"rel":168},"https:\u002F\u002Fgithub.com\u002FPostgREST\u002Fpostgrest\u002Fblob\u002Fmain\u002Fsrc\u002Flibrary\u002FPostgREST\u002FError.hs",[47],"su código",[10,171,172,173,178],{},"El texto viene del\n",[43,174,177],{"href":175,"rel":176},"https:\u002F\u002Fgithub.com\u002Fpostgres\u002Fpostgres\u002Fblob\u002Fmaster\u002Fsrc\u002Fbackend\u002Futils\u002Fadt\u002Fri_triggers.c",[47],"archivo de Postgres que ejecuta estas comprobaciones",".\nUn update también lo provoca, cuando cambia la columna a un número sin casa, y por\neso el mensaje empieza con \"insert or update\".",[10,180,181,182,185,186,190],{},"Un mensaje que empieza con ",[21,183,184],{},"update or delete on table"," es la misma regla vista\ndesde la calle: intentaste derribar una casa a la que todavía apuntan cartas. Ese\nviene de borrar, y\n",[43,187,189],{"href":188},"\u002Fblog\u002Fdatabase-error-deleting-user","el artículo sobre borrar usuarios"," lo\nexplica.",[35,192,194],{"id":193},"qué-fila-está-buscando-postgres","¿Qué fila está buscando Postgres?",[10,196,197],{},"La de la línea de detalle, en la tabla que nombra esa línea. Dos cosas la hacen\nmás difícil de leer de lo que parece.",[10,199,200,204,205,208,209,212,213,216,217,220],{},[201,202,203],"strong",{},"A veces falta el valor."," Postgres escribe ",[21,206,207],{},"Key (id)=(…)"," solo cuando quien\nllama puede leer esa columna de tu tabla. Cuando Row Level Security está activada\nen la tabla en la que escribiste,\n",[43,210,169],{"href":175,"rel":211},[47],"\nomite el valor, con el comentario \"we don't return any specifics to avoid leaking\ndata\", y recibes ",[21,214,215],{},"Key is not present in table \"users\"."," Eso es lo que ve una\npetición del navegador a una tabla protegida. El valor es el que envió tu app, así\nque abre las herramientas de desarrollo del navegador, busca la petición a\n",[21,218,219],{},"\u002Frest\u002Fv1\u002Fprofiles"," en la pestaña Red y léelo en el payload.",[10,222,223,224,228,229,233],{},"Si tu consola muestra el valor en una petición que tu app hizo con la clave\npública, Row Level Security está desactivada en la tabla en la que escribiste. Una\ntabla con sus reglas desactivadas le da sus filas a un desconocido con la misma\nfacilidad que a ti, y\n",[43,225,227],{"href":226},"\u002Fblog\u002Fenable-rls-on-every-supabase-table","activar las reglas en cada tabla","\nexplica el interruptor y la primera regla. Nuestro\n",[43,230,232],{"href":231},"\u002Fsecurity-scanner","escaneo gratuito"," lee tu app en producción con la clave\npública que recibe cualquier visitante y lista las tablas que le responden. No\nnecesita cuenta.",[10,235,236,204,239,242,243,246],{},[201,237,238],{},"El nombre de la tabla no lleva esquema.",[21,240,241],{},"users",", y en un\nproyecto de Supabase una columna que apunta a personas casi siempre apunta a\n",[21,244,245],{},"auth.users",", la tabla donde Supabase Auth guarda a todas las personas\nregistradas. No aparece entre tus tablas públicas en el Table Editor, y por eso el\nmensaje parece nombrar una tabla que nunca creaste.",[10,248,249],{},"Para ver exactamente adónde apunta la regla, ejecuta esto en el SQL Editor con el\nnombre de la regla que trae el mensaje:",[14,251,256],{"className":252,"code":254,"language":255,"meta":23},[253],"language-sql","select conrelid::regclass as on_table,\n       pg_get_constraintdef(oid) as rule\nfrom pg_constraint\nwhere conname = 'profiles_id_fkey';\n","sql",[21,257,254],{"__ignoreMap":23},[10,259,260,261,264],{},"La respuesta da la tabla que tiene la regla y lo que dice la regla, por ejemplo\n",[21,262,263],{},"FOREIGN KEY (id) REFERENCES auth.users(id) ON DELETE CASCADE",": tu columna, y\nluego la tabla y la columna con las que tiene que coincidir. Después busca el\nvalor:",[14,266,269],{"className":267,"code":268,"language":255,"meta":23},[253],"select id, email, created_at from auth.users where id = '…';\n",[21,270,268],{"__ignoreMap":23},[10,272,273],{},"Si no vuelve ninguna fila, la persona que nombra tu perfil no existe en este\nproyecto.",[35,275,277],{"id":276},"de-dónde-sale-una-fila-que-apunta-a-la-nada","¿De dónde sale una fila que apunta a la nada?",[10,279,280],{},"De unos pocos sitios, y la tabla de la línea de detalle te dice dónde mirar\nprimero.",[10,282,283,286],{},[201,284,285],{},"La fila a la que apunta nunca se guardó, o se guardó en segundo lugar."," Un\npedido y sus líneas son dos guardados. Si tu app guarda primero las líneas, o\nguarda el pedido, recibe un error que nunca lee y sigue con las líneas, cada línea\nnombra un pedido que no existe. Guarda primero la fila a la que se apunta, toma su\nid de la respuesta y lee el error de cada paso antes del siguiente:",[14,288,293],{"className":289,"code":291,"language":292,"meta":23},[290],"language-js","const { data: order, error } = await supabase\n  .from('orders')\n  .insert({ user_id: user.id })\n  .select('id')\n  .single()\n\nif (error) throw error\n\nconst { error: linesError } = await supabase\n  .from('order_items')\n  .insert(cart.map((item) => ({ ...item, order_id: order.id })))\n","js",[21,294,291],{"__ignoreMap":23},[10,296,297,300,301,306,307,311],{},[21,298,299],{},".select()"," después de un insert vuelve a leer la fila nueva, así que la persona\nnecesita una regla que le permita leer sus propios pedidos. La\n",[43,302,305],{"href":303,"rel":304},"https:\u002F\u002Fwww.postgresql.org\u002Fdocs\u002Fcurrent\u002Fsql-createpolicy.html",[47],"documentación de Postgres sobre policies","\ndice que una fila nueva que no pasa las reglas de lectura hace que el guardado\nlance un error, y en Supabase ese error es\n",[43,308,310],{"href":309},"\u002Fblog\u002Fnew-row-violates-row-level-security-policy","new row violates row-level security policy",".\nLos dos guardados son peticiones separadas, así que si fallan las líneas, el\npedido ya está guardado: vuelve a enviar las líneas o borra el pedido.",[313,314],"diagram",{"alt":315,"caption":316,"src":317},"Dos carriles. En el primero, una línea de pedido dirigida al pedido 14 se envía antes de que exista ese pedido: la calle de los pedidos tiene un solar vacío bordeado en ámbar donde debería estar el 14, y el carril termina en 23503 y 409. En el segundo, el pedido 14 se guarda primero y se ilumina en turquesa, la línea lo sigue con su número, la calle tiene la casa y el carril termina con una marca de verificación.","Las líneas guardadas antes que su pedido apuntan a una casa que todavía no existe. Guarda el pedido, toma su id de la respuesta y luego guarda las líneas.","\u002Fblog\u002Fviolates-foreign-key-constraint\u002Fsave-the-order-first-1600x760.png",[10,319,320,323,324,326,327,329,330,332,333,336,337,340],{},[201,321,322],{},"El id vino de otro sitio."," Cada proyecto de Supabase tiene su propio\n",[21,325,245],{},". Un id copiado de tu proyecto de pruebas, uno que un creador con IA\ndejó en el código desde una vista previa, o filas copiadas de un proyecto a otro\nnombran todos a personas que el proyecto en producción nunca ha visto. Cuando\ncopies datos entre proyectos, copia las filas a las que se apunta antes que las\nfilas que apuntan a ellas. Las personas detrás de una columna que apunta a\n",[21,328,245],{}," no se copian así: se registran en el proyecto nuevo y reciben ids\nnuevos al hacerlo. Un perfil cuyo ",[21,331,29],{}," tiene que ser el id de la persona también\nse rompe cuando la app lo rellena con un valor aleatorio nuevo, como\n",[21,334,335],{},"crypto.randomUUID()",". Ese número no es de ninguna casa. Usa ",[21,338,339],{},"user.id"," de la\nsesión iniciada.",[10,342,343,346,347,350],{},[201,344,345],{},"La fila se borró entretanto."," Un producto retirado de tu tienda mientras estaba\nen el carrito de alguien, o una cuenta borrada en otra pestaña. El guardado que\nviene después apunta a una casa que existía cuando se cargó la página. El arreglo\nestá en tu app: comprueba ",[21,348,349],{},"error.code === '23503'"," y dile a la persona qué cambió,\npor ejemplo \"Ese producto ya no está disponible.\"",[10,352,353,356,357,359,360,363,364,369,370,373,374,377,378,382,383,386],{},[201,354,355],{},"Un trigger que se ejecuta demasiado pronto."," Un trigger en ",[21,358,245],{}," que\ncopia a cada persona nueva en ",[21,361,362],{},"profiles"," tiene que ejecutarse después de que la\npersona se guarde. La\n",[43,365,368],{"href":366,"rel":367},"https:\u002F\u002Fwww.postgresql.org\u002Fdocs\u002Fcurrent\u002Ftrigger-datachanges.html",[47],"documentación de Postgres sobre triggers","\ndice que el cambio que dispara un trigger ",[21,371,372],{},"BEFORE"," por fila \"is naturally not\nvisible to SQL commands executed in a row-level BEFORE trigger, because it hasn't\nhappened yet\". Un perfil escrito desde un trigger ",[21,375,376],{},"before insert"," apunta, por\ntanto, a una persona que todavía no existe. Cada registro falla, y Supabase lo\ninforma como ",[43,379,381],{"href":380},"\u002Fblog\u002Fdatabase-error-saving-new-user","\"Database error saving new user\"",".\nEl patrón del propio Supabase, que ese artículo reproduce completo, usa\n",[21,384,385],{},"after insert",".",[35,388,390],{"id":389},"por-qué-pasa-un-valor-vacío","¿Por qué pasa un valor vacío?",[10,392,393],{},"Porque una clave foránea solo comprueba los valores que están. La documentación de\nPostgres sobre restricciones lo dice así: \"Normally, a referencing row need not\nsatisfy the foreign key constraint if any of its referencing columns are null.\"\nNull es la palabra de Postgres para ningún valor en absoluto. Una carta sin número\nde casa nunca se busca. Va al saco y no pertenece a ninguna casa.",[10,395,396,397,400],{},"Así que una línea de pedido guardada con un ",[21,398,399],{},"order_id"," vacío se acepta, y no\npertenece a ningún pedido. Tu app se la encuentra más tarde, como una línea que no\naparece en el carrito de nadie. La misma página da el arreglo: \"declare the\nreferencing column(s) as NOT NULL\".",[14,402,405],{"className":403,"code":404,"language":255,"meta":23},[253],"alter table public.order_items alter column order_id set not null;\n",[21,406,404],{"__ignoreMap":23},[10,408,409,410,414,415,417,418,386],{},"Después de eso también se rechaza un valor vacío, con un mensaje que nombra la\ncolumna directamente,\n",[43,411,413],{"href":412},"\u002Fblog\u002Fnull-value-violates-not-null-constraint","null value in column violates not-null constraint",".\nLa sentencia falla mientras ya existan filas con un ",[21,416,399],{}," vacío, así que\nlístalas primero con ",[21,419,420],{},"select * from public.order_items where order_id is null",[35,422,424],{"id":423},"puede-una-regla-estar-ocultando-la-fila-que-busca","¿Puede una regla estar ocultando la fila que busca?",[10,426,427,428,433,434,438],{},"No. Postgres comprueba una clave foránea contra cada fila de la otra tabla, digan\nlo que digan tus reglas de Row Level Security.\n",[43,429,432],{"href":430,"rel":431},"https:\u002F\u002Fwww.postgresql.org\u002Fdocs\u002Fcurrent\u002Fddl-rowsecurity.html",[47],"Su documentación sobre la seguridad por filas","\nlo dice sin rodeos: \"Referential integrity checks, such as unique or primary key\nconstraints and foreign key references, always bypass row security to ensure that\ndata integrity is maintained.\" En\n",[43,435,437],{"href":175,"rel":436},[47],"el código que ejecuta la comprobación",",\nla búsqueda cambia al propietario de la otra tabla, con un indicador que impide\nque se aplique incluso una regla forzada.",[10,440,441,442,444],{},"Así que la oficina de clasificación trabaja con el plano completo de la calle. Tu\napp ve la calle a través de sus reglas, que quizá solo le muestran las casas que\npuede visitar, y la comprobación nunca usa esa vista. Un ",[21,443,86],{}," significa que la\ncasa no está para nadie. Si la fila existe y tu app no puede verla, el guardado\npasa.",[313,446],{"alt":447,"caption":448,"src":449},"La misma calle de casas dibujada dos veces. A la izquierda, vista desde una ventana del navegador a través de una valla con candado, solo la casa 12 está iluminada y las demás son contornos pálidos y discontinuos. A la derecha, una carta dirigida al 14, una de las casas pálidas, baja a la misma calle, donde cada casa está dibujada entera, y recibe una marca de verificación.","Tu app lee las filas que sus reglas permiten. La comprobación de la clave foránea lee todas las filas, así que se acepta una fila que apunta a una casa que tu app no puede ver.","\u002Fblog\u002Fviolates-foreign-key-constraint\u002Fthe-check-sees-every-house-1600x760.png",[35,451,453],{"id":452},"puede-alguien-colgar-una-fila-de-un-registro-que-no-es-suyo","¿Puede alguien colgar una fila de un registro que no es suyo?",[10,455,456],{},"Sí, salvo que tu regla de inserción compruebe de quién es ese registro. La\ncomprobación de que la casa existe ignora tus reglas, así que dice que sí a una\ncasa que es de otra persona.",[10,458,459,460,463],{},"Toma ",[21,461,462],{},"order_items"," con esta regla de inserción, que parece segura:",[14,465,468],{"className":466,"code":467,"language":255,"meta":23},[253],"create policy \"Add your own order lines\"\non public.order_items for insert\nto authenticated\nwith check (auth.uid() = user_id);\n",[21,469,467],{"__ignoreMap":23},[10,471,472,473,476,477,479],{},"Comprueba que la línea es tuya, y nunca mira a qué pedido se suma. Una persona con\nsesión iniciada que envía su propio ",[21,474,475],{},"user_id"," con el ",[21,478,399],{}," de otra persona\npasa la regla y pasa la clave foránea, y una línea que ella escribió está ahora en\nel pedido de un desconocido. La misma forma pone un comentario bajo la publicación\nprivada de otra persona, o a un miembro en un equipo al que nadie lo invitó.",[10,481,482],{},"Sustitúyela por una regla que mire el pedido:",[14,484,487],{"className":485,"code":486,"language":255,"meta":23},[253],"create policy \"Add lines to your own orders\"\non public.order_items for insert\nto authenticated\nwith check (\n  exists (\n    select 1 from public.orders\n    where orders.id = order_items.order_id\n      and orders.user_id = auth.uid()\n  )\n);\n",[21,488,486],{"__ignoreMap":23},[10,490,491,492,496],{},"Borra la primera regla cuando añadas esta. Postgres combina las reglas del mismo\ntipo con \"OR\", en palabras de\n",[43,493,495],{"href":303,"rel":494},[47],"su documentación sobre policies",",\nasí que la antigua seguiría dejando pasar la línea por sí sola.",[10,498,499,500,502],{},"La comprobación también responde una pregunta a cualquiera que pueda insertar. La\nfrase que sigue a la citada arriba, en la documentación sobre la seguridad por\nfilas, lo advierte: \"Care must be taken when developing schemas and row level\npolicies to avoid 'covert channel' leaks of information through such referential\nintegrity checks.\" Un guardado que falla con ",[21,501,86],{}," dice que una casa no está.\nUno que funciona dice que sí está, en una calle que la persona no puede leer. Los\nids de usuario de Supabase son valores aleatorios largos que nadie adivina. Cuando\nuna columna apunta a algo adivinable, como una dirección de correo o un id que\ncuenta desde 1, una persona puede probar valores de uno en uno y saber cuáles\nexisten.",[35,504,506],{"id":505},"deberías-quitar-la-clave-foránea","¿Deberías quitar la clave foránea?",[10,508,509,510,386],{},"No. Sin ella, el guardado pasa, la fila apunta a la nada, y desde entonces nada te\navisa cuando ocurre. Un carrito muestra una línea de un producto que retiraste el\nmes pasado, y un perfil se queda ahí por una persona que ya no está en\n",[21,511,245],{},[10,513,514,515,518,519,521,522,524,525,528],{},"La regla también decide qué pasa con esas filas cuando se borra la fila a la que\napuntan. Con ",[21,516,517],{},"on delete cascade"," en la regla que va de ",[21,520,362],{}," a ",[21,523,245],{},",\nborrar a una persona borra su perfil. Sin la regla, el perfil se queda, con los\ndatos personales que contenga.\n",[43,526,527],{"href":188},"El artículo sobre borrar usuarios"," explica\ncómo elegir qué conserva cada tabla.",[10,530,531],{},"Si filas antiguas ya apuntan a la nada, lístalas antes de volver a añadir una\nregla o de endurecer una, porque Postgres se niega a añadir una clave foránea\nmientras alguna fila la incumpla:",[14,533,536],{"className":534,"code":535,"language":255,"meta":23},[253],"select i.*\nfrom public.order_items i\nleft join public.orders o on o.id = i.order_id\nwhere i.order_id is not null\n  and o.id is null;\n",[21,537,535],{"__ignoreMap":23},[10,539,540],{},"Decide para cada una si recupera su pedido o se va. Borrarlas no tiene vuelta\natrás sin una copia de la base de datos de antes.",[35,542,544],{"id":543},"antes-de-reparar-las-filas","Antes de reparar las filas",[10,546,547],{},"Guarda una copia de la base de datos de antes de borrar filas que apuntan a la\nnada, copiar datos entre proyectos o cambiar una regla, y entérate cuando una\ntabla empiece a responder a desconocidos.",[10,549,550],{},[201,551,552,556],{},[43,553,555],{"href":554},"\u002Fsupabase-backups","Reeve Care"," guarda una copia de tu base de datos de Supabase:",[558,559,560,564,567,570,573,576],"ul",{},[561,562,563],"li",{},"una copia cifrada según un calendario, en el almacenamiento propio de Reeve en la UE, fuera de tu cuenta de Supabase",[561,565,566],{},"cada fila tal como estaba antes de que una limpieza borrara las que no debía",[561,568,569],{},"cada copia verificada antes de contar, contando las filas de cada tabla",[561,571,572],{},"también tus archivos subidos, en cuanto conectas una credencial de Storage",[561,574,575],{},"una restauración con un clic, con una copia de seguridad del estado actual tomada antes",[561,577,578],{},"todo lo que hace Monitor",[10,580,581],{},[201,582,583,587],{},[43,584,586],{"href":585},"\u002Fwebsite-monitoring","Reeve Monitor"," sigue comprobando desde fuera:",[558,589,590,593,596,599],{},[561,591,592],{},"si tu app está en línea, comprobado cada minuto",[561,594,595],{},"la comprobación de seguridad completa cada hora, en hasta tres apps",[561,597,598],{},"un correo el día en que un nuevo escaneo califica tu app peor que el anterior, para que una tabla cuyas reglas se desactivaron te llegue sin que tengas que ir a mirar",[561,600,601],{},"un informe mensual, en lenguaje claro, de lo que vio",[35,603,605],{"id":604},"qué-hacer-ahora","Qué hacer ahora",[607,608,609],"key-takeaways",{},[558,610,611,619,622,625,632,635],{},[561,612,613,614,616,617,386],{},"Lee la tabla de la línea de detalle. En Supabase, ",[21,615,241],{}," ahí casi siempre significa ",[21,618,245],{},[561,620,621],{},"Encuentra el valor: en la línea de detalle o en la petición que envió tu app.",[561,623,624],{},"Guarda primero la fila a la que se apunta, toma su id de la respuesta y lee cada error antes del siguiente guardado.",[561,626,627,628,631],{},"Dale ",[21,629,630],{},"not null"," a una columna que tiene que apuntar a algo, para que también se rechace un valor vacío.",[561,633,634],{},"Haz que cada regla de inserción compruebe de quién es la fila a la que apunta la nueva.",[561,636,637],{},"Mantén la clave foránea, y lista las filas que apuntan a la nada antes de borrar ninguna.",[10,639,640,641,644],{},"Empieza por la tabla de la línea de detalle y el id que envió tu app. Si el\narreglo borra filas o las copia entre proyectos,\n",[43,642,643],{"href":554},"guarda una copia de la base de datos"," de antes de ejecutarlo.",{"title":23,"searchDepth":646,"depth":646,"links":647},3,[648,650,651,652,653,654,655,656,657],{"id":37,"depth":649,"text":38},2,{"id":193,"depth":649,"text":194},{"id":276,"depth":649,"text":277},{"id":389,"depth":649,"text":390},{"id":423,"depth":649,"text":424},{"id":452,"depth":649,"text":453},{"id":505,"depth":649,"text":506},{"id":543,"depth":649,"text":544},{"id":604,"depth":649,"text":605},"Fundamentos de seguridad","\u002Fblog\u002Fviolates-foreign-key-constraint\u002Fcover-1200x630.png","Una calle de casas numeradas, con un solar vacío bordeado en ámbar, y delante una carta dirigida al número que falta.","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.",false,"md",[665,668,671,674,677],{"q":666,"a":667},"¿Qué significa insert or update on table violates foreign key constraint?","La fila que intentaste guardar apunta a una fila de otra tabla que no existe, y Postgres la rechazó. No se escribió nada. El mensaje nombra la tabla en la que escribiste y la regla, y la línea de detalle nombra la tabla donde buscó Postgres. El código es 23503, y desde una app de Supabase llega como HTTP 409.",{"q":669,"a":670},"¿Por qué el error habla de la tabla \"users\" si yo no tengo ninguna?","Porque la tabla a la que se refiere es casi con seguridad auth.users, donde Supabase Auth guarda a todas las personas registradas. Postgres escribe el nombre de una tabla sin su esquema. Tu columna apunta a una persona, y el id que contiene no es de nadie en este proyecto.",{"q":672,"a":673},"¿Puede Row Level Security causar un error de clave foránea?","No. Postgres comprueba una clave foránea contra cada fila de la otra tabla, incluidas las filas que tus reglas ocultan, así que el error significa que la fila de verdad no está. El mismo hecho funciona al revés: una fila que apunta a un registro que la persona no puede ver se acepta, salvo que tu regla de inserción compruebe de quién es ese registro.",{"q":675,"a":676},"¿Por qué la línea de detalle dice \"Key is not present\" sin el valor?","Postgres omite el valor cuando Row Level Security está activada en la tabla en la que escribiste, o cuando quien llama no puede leer esa columna. El valor es el que envió tu app, así que léelo en la petición, en la pestaña Red de las herramientas de desarrollo de tu navegador.",{"q":678,"a":679},"¿Debería quitar la clave foránea para que el error desaparezca?","No. Los guardados pasan y las filas apuntan a la nada: una línea de pedido sin pedido, un perfil sin nadie detrás. La regla también decide qué pasa con esas filas cuando se borra un usuario. Averigua qué fila falta y guárdala primero, o corrige el id.","\u002Fblog\u002Fviolates-foreign-key-constraint\u002Fcard-800x500.png",[682,683,684,685,686,687],"violates foreign key constraint supabase","insert or update on table violates foreign key constraint","key is not present in table","postgres 23503","supabase clave foránea auth.users","supabase clave foránea row level security",{},true,"\u002Fblog\u002Fviolates-foreign-key-constraint","2026-11-07",{"title":5,"description":661},"blog\u002Fviolates-foreign-key-constraint",[695,696,697],"Supabase \"violates foreign key constraint\" al insertar (código de Postgres 23503, HTTP 409 en tu app) significa que la fila que guardaste nombra una fila de otra tabla que no existe. No se guardó nada. El mensaje nombra tu tabla y la regla, y la línea de detalle nombra la tabla donde buscó Postgres.","La fila que falta nunca se guardó, se guardó en segundo lugar, se borró entretanto o vive en otro proyecto. Cuando la línea de detalle dice \"users\", casi siempre se refiere a auth.users, donde Supabase Auth guarda a todas las personas registradas.","La comprobación lee cada fila de la otra tabla, digan lo que digan tus reglas de Row Level Security. Así que también acepta una fila que apunta al registro de otra persona, y tu regla de inserción tiene que comprobar de quién es ese registro.","06Va7v3p2iY1HTGqoXV-MlmbumJJqXMDX3PLT8dy6PM",[700,706,712,713,718,724,730,736,742,748,754,760,766,772,778,784,790,796,802,808,814,820,826,832,838,844,850,855,861,867,873,879,885,891,897,903,909,915,921,927,933,939,945,951,957,963,968,974,980,986,992,998,1004,1010,1016,1022,1028,1034,1040,1046,1052,1058,1063,1069,1075,1081,1087,1093,1099,1105,1111,1117,1123,1129,1135,1141,1146,1152,1158,1164,1170,1176,1182,1188,1193,1199,1205,1211,1216,1222],{"path":701,"title":702,"description":703,"published":704,"category":658,"image":705,"draft":662},"\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":707,"title":708,"description":709,"published":710,"category":658,"image":711,"draft":662},"\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":690,"title":5,"description":661,"published":691,"category":658,"image":680,"draft":662},{"path":412,"title":714,"description":715,"published":716,"category":658,"image":717,"draft":662},"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":719,"title":720,"description":721,"published":722,"category":658,"image":723,"draft":662},"\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":725,"title":726,"description":727,"published":728,"category":658,"image":729,"draft":662},"\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":731,"title":732,"description":733,"published":734,"category":658,"image":735,"draft":662},"\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":737,"title":738,"description":739,"published":740,"category":658,"image":741,"draft":662},"\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":743,"title":744,"description":745,"published":746,"category":658,"image":747,"draft":662},"\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":749,"title":750,"description":751,"published":752,"category":658,"image":753,"draft":662},"\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":188,"title":755,"description":756,"published":757,"category":758,"image":759,"draft":662},"\"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":761,"title":762,"description":763,"published":764,"category":658,"image":765,"draft":662},"\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":767,"title":768,"description":769,"published":770,"category":658,"image":771,"draft":662},"\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":773,"title":774,"description":775,"published":776,"category":658,"image":777,"draft":662},"\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":779,"title":780,"description":781,"published":782,"category":658,"image":783,"draft":662},"\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":785,"title":786,"description":787,"published":788,"category":658,"image":789,"draft":662},"\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":791,"title":792,"description":793,"published":794,"category":658,"image":795,"draft":662},"\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":797,"title":798,"description":799,"published":800,"category":658,"image":801,"draft":662},"\u002Fblog\u002Fsupabase-relation-does-not-exist","Supabase \"relation does not exist\" aunque la tabla está ahí","Supabase \"relation does not exist\" o PGRST205: ninguna tabla tiene ese nombre en ese esquema. Cuatro comprobaciones, y lo que abre exponer un esquema.","2026-10-23","\u002Fblog\u002Fsupabase-relation-does-not-exist\u002Fcard-800x500.png",{"path":803,"title":804,"description":805,"published":806,"category":658,"image":807,"draft":662},"\u002Fblog\u002Fsupabase-function-not-found","Supabase PGRST202: \"Could not find the function in the schema cache\"","Supabase \"Could not find the function in the schema cache\" muestra los nombres de argumentos que envió tu app. Compáralos con la función.","2026-10-22","\u002Fblog\u002Fsupabase-function-not-found\u002Fcard-800x500.png",{"path":809,"title":810,"description":811,"published":812,"category":658,"image":813,"draft":662},"\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":815,"title":816,"description":817,"published":818,"category":658,"image":819,"draft":662},"\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":821,"title":822,"description":823,"published":824,"category":658,"image":825,"draft":662},"\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":827,"title":828,"description":829,"published":830,"category":658,"image":831,"draft":662},"\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":833,"title":834,"description":835,"published":836,"category":658,"image":837,"draft":662},"\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":839,"title":840,"description":841,"published":842,"category":658,"image":843,"draft":662},"\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":845,"title":846,"description":847,"published":848,"category":658,"image":849,"draft":662},"\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":380,"title":851,"description":852,"published":853,"category":658,"image":854,"draft":662},"\"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":856,"title":857,"description":858,"published":859,"category":658,"image":860,"draft":662},"\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":862,"title":863,"description":864,"published":865,"category":658,"image":866,"draft":662},"\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":868,"title":869,"description":870,"published":871,"category":658,"image":872,"draft":662},"\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":874,"title":875,"description":876,"published":877,"category":658,"image":878,"draft":662},"\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":880,"title":881,"description":882,"published":883,"category":658,"image":884,"draft":662},"\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":886,"title":887,"description":888,"published":889,"category":658,"image":890,"draft":662},"\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":892,"title":893,"description":894,"published":895,"category":658,"image":896,"draft":662},"\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":898,"title":899,"description":900,"published":901,"category":658,"image":902,"draft":662},"\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":904,"title":905,"description":906,"published":907,"category":658,"image":908,"draft":662},"\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":910,"title":911,"description":912,"published":913,"category":658,"image":914,"draft":662},"\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":916,"title":917,"description":918,"published":919,"category":658,"image":920,"draft":662},"\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":922,"title":923,"description":924,"published":925,"category":658,"image":926,"draft":662},"\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":928,"title":929,"description":930,"published":931,"category":658,"image":932,"draft":662},"\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":934,"title":935,"description":936,"published":937,"category":658,"image":938,"draft":662},"\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":940,"title":941,"description":942,"published":943,"category":658,"image":944,"draft":662},"\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":946,"title":947,"description":948,"published":949,"category":758,"image":950,"draft":662},"\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":952,"title":953,"description":954,"published":955,"category":758,"image":956,"draft":662},"\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":958,"title":959,"description":960,"published":961,"category":758,"image":962,"draft":662},"\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":964,"title":965,"description":966,"published":961,"category":658,"image":967,"draft":662},"\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":969,"title":970,"description":971,"published":972,"category":658,"image":973,"draft":662},"\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":975,"title":976,"description":977,"published":978,"category":658,"image":979,"draft":662},"\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":981,"title":982,"description":983,"published":984,"category":758,"image":985,"draft":662},"\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":987,"title":988,"description":989,"published":990,"category":658,"image":991,"draft":662},"\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":993,"title":994,"description":995,"published":996,"category":658,"image":997,"draft":662},"\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":999,"title":1000,"description":1001,"published":1002,"category":658,"image":1003,"draft":662},"\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":1005,"title":1006,"description":1007,"published":1008,"category":658,"image":1009,"draft":662},"\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":1011,"title":1012,"description":1013,"published":1014,"category":658,"image":1015,"draft":662},"\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":1017,"title":1018,"description":1019,"published":1020,"category":658,"image":1021,"draft":662},"\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":1023,"title":1024,"description":1025,"published":1026,"category":658,"image":1027,"draft":662},"\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":1029,"title":1030,"description":1031,"published":1032,"category":658,"image":1033,"draft":662},"\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":1035,"title":1036,"description":1037,"published":1038,"category":758,"image":1039,"draft":662},"\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":1041,"title":1042,"description":1043,"published":1044,"category":758,"image":1045,"draft":662},"\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":1047,"title":1048,"description":1049,"published":1050,"category":758,"image":1051,"draft":662},"\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":1053,"title":1054,"description":1055,"published":1056,"category":658,"image":1057,"draft":662},"\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":226,"title":1059,"description":1060,"published":1061,"category":658,"image":1062,"draft":662},"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":1064,"title":1065,"description":1066,"published":1067,"category":658,"image":1068,"draft":662},"\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":1070,"title":1071,"description":1072,"published":1073,"category":658,"image":1074,"draft":662},"\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":1076,"title":1077,"description":1078,"published":1079,"category":658,"image":1080,"draft":662},"\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":1082,"title":1083,"description":1084,"published":1085,"category":658,"image":1086,"draft":662},"\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":1088,"title":1089,"description":1090,"published":1091,"category":658,"image":1092,"draft":662},"\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":1094,"title":1095,"description":1096,"published":1097,"category":658,"image":1098,"draft":662},"\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":1100,"title":1101,"description":1102,"published":1103,"category":658,"image":1104,"draft":662},"\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":1106,"title":1107,"description":1108,"published":1109,"category":658,"image":1110,"draft":662},"\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":1112,"title":1113,"description":1114,"published":1115,"category":758,"image":1116,"draft":662},"\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":1118,"title":1119,"description":1120,"published":1121,"category":658,"image":1122,"draft":662},"\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":1124,"title":1125,"description":1126,"published":1127,"category":758,"image":1128,"draft":662},"\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":1130,"title":1131,"description":1132,"published":1133,"category":658,"image":1134,"draft":662},"\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":1136,"title":1137,"description":1138,"published":1139,"category":658,"image":1140,"draft":662},"\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":309,"title":1142,"description":1143,"published":1144,"category":658,"image":1145,"draft":662},"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":1147,"title":1148,"description":1149,"published":1150,"category":658,"image":1151,"draft":662},"\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":1153,"title":1154,"description":1155,"published":1156,"category":758,"image":1157,"draft":662},"\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":1159,"title":1160,"description":1161,"published":1162,"category":758,"image":1163,"draft":662},"\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":1165,"title":1166,"description":1167,"published":1168,"category":658,"image":1169,"draft":662},"\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":1171,"title":1172,"description":1173,"published":1174,"category":658,"image":1175,"draft":662},"\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":1177,"title":1178,"description":1179,"published":1180,"category":758,"image":1181,"draft":662},"\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":1183,"title":1184,"description":1185,"published":1186,"category":658,"image":1187,"draft":662},"\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":1189,"title":1190,"description":1191,"published":1186,"category":658,"image":1192,"draft":662},"\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":1194,"title":1195,"description":1196,"published":1197,"category":658,"image":1198,"draft":662},"\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":1200,"title":1201,"description":1202,"published":1203,"category":758,"image":1204,"draft":662},"\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":1206,"title":1207,"description":1208,"published":1209,"category":658,"image":1210,"draft":662},"\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":1212,"title":1213,"description":1214,"published":1209,"category":758,"image":1215,"draft":662},"\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":1217,"title":1218,"description":1219,"published":1220,"category":758,"image":1221,"draft":662},"\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":1223,"title":1224,"description":1225,"published":1220,"category":658,"image":1226,"draft":662},"\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",1794213644710]