Eliminación de cuentas para apps con Supabase y Firebase, mapeada y construida con Codex
Las dos tiendas rechazan las apps que permiten registrarse pero no darse de baja. La función parece pequeña, pero una eliminación correcta toca la autenticación, cada colección o tabla, el almacenamiento de archivos, las suscripciones y el token de Sign in with Apple, que es justo el tipo de cambio transversal que Codex hace bien en cuanto tiene un inventario. Este es un flujo en dos pasadas: Codex mapea los datos en una sesión de solo lectura y después implementa la purga con tests.
Desde $109 de pago único por plataforma, o deja que nuestro equipo lo haga todo desde $698.
Garantía de aprobación – o te devolvemos el dinero en 14 días
Sin programar
Pago único, actualizaciones gratuitas
Soporte humano 24/7
Hecho en Alemania desde 2014 · ISO 27001
9:41
Tu app
Tu appYa en tu bolsillo
App Store · Google Play Aprobada
Qué exigen las tiendas y qué pulsa un revisor
Apple (Directriz 5.1.1(v)): si la app permite crear cuentas, la gente tiene que poder iniciar la eliminación de la cuenta dentro de la app, y eliminar significa borrar la cuenta y sus datos, no marcarla como desactivada. Un paso de confirmación está bien. Si se puede iniciar sesión con Apple, Apple espera que revoques los tokens a través de la API REST de Sign in with Apple cuando el usuario elimina su cuenta. Google Play: un camino dentro de la app y además una página web en la que se pueda solicitar la eliminación sin reinstalar la app, cuya URL introduces en la sección Data safety de Play Console.
Lo que hace de verdad el revisor: se registra o usa tu cuenta demo, abre los ajustes, busca Eliminar cuenta y a veces llega hasta el final. En Google Play también se abre el enlace web; si exige iniciar sesión o devuelve un 404, es un rechazo (el rechazo de Play explicado).
Por qué dos pasadas con Codex: la eliminación falla en silencio. Una colección o una carpeta de almacenamiento olvidada no da ningún error, simplemente se queda ahí. Mapear los datos antes de escribir código es lo que hace que el resultado sea completo. Los ejemplos usan React, Express, Firebase Auth, Firestore y Cloud Storage; los equivalentes de Supabase están en la tabla. La guía sobre el requisito de eliminación de cuentas cubre las mismas reglas para quienes crean sin agente de programación, y el helper isNativeApp() viene de la plantilla de AGENTS.md.
Pasada 1: mapea los datos en una sesión de solo lectura
El sandbox read-only deja que Codex lea archivos y ejecute comandos inofensivos como rg, pero no que cambie nada. Así la primera pasada es honesta: solo puede mirar.
bash
# Pasada 1: solo inventario. Codex puede leer, buscar y responder, pero no editar.
cd my-app
codex --sandbox read-only --ask-for-approval on-request
# Pega esto en la sesión:
# Lee AGENTS.md. Encuentra cada lugar en el que este código guarda o envía datos
# sobre un usuario: proveedores de autenticación, colecciones y subcolecciones de
# Firestore (y el campo que vincula cada documento con el usuario), rutas de
# Cloud Storage, logs del servidor, terceros que reciben datos de usuarios
# (RevenueCat, Stripe, OneSignal, email, analítica, seguimiento de errores) y lo
# que la app web guarda en el dispositivo (cookies, localStorage, IndexedDB,
# valores escritos con writevalue://).
# Devuelve una sola tabla Markdown: ubicación | vínculo con el usuario | borrar o conservar | motivo.
# Marca todo aquello de lo que no estés seguro. No propongas código todavía.
# Cuando la tabla esté bien, cambia a acceso de escritura dentro de la sesión con
# /permissions y pide a Codex que la guarde como docs/user-data-map.md. Haz commit.
Qué cubre una eliminación completa
Ubicación
Firebase (API en Express)
Supabase
Usuario de autenticación
getAuth().deleteUser(uid) con firebase-admin, como último paso
supabase.auth.admin.deleteUser(id) en una Edge Function con la service role key, como último paso
Base de datos
recursiveDelete() para el documento del usuario y sus subcolecciones, más consultas sobre los campos de propietario
Claves foráneas a auth.users con on delete cascade, o borrados explícitos antes de la llamada de autenticación
Lista la carpeta del usuario y después storage.from(bucket).remove(paths)
Sign in with Apple
revokeAccessToken(auth, token) después de que el usuario se vuelva a autenticar con Apple; Firebase no guarda tokens de Apple
No es automático. El endpoint de revocación de Apple necesita un token del inicio de sesión; pide a Codex que compruebe qué expone tu flujo y que señale lo que falte
Suscripciones de la tienda
No puedes cancelarlas por el usuario. Dilo en el diálogo y ofrece cancelinapppurchase://; opcionalmente, borra el cliente de RevenueCat en el servidor
Igual
Push, email, analítica
Borra las filas de dispositivos push, elimina el contacto de tu herramienta de email y usa la API de eliminación de cada proveedor
Igual
El dispositivo
reset:// hace que WebViewGold borre cookies y caché; sobrescribe todo lo guardado con writevalue://, que sobrevive al borrado de la caché
Igual
WebViewGold nos ha permitido el tiempo de mercado más corto y eficiente que jamás sea posible de todas las otras plataformas de aplicaciones que hemos probado durante los últimos 5 años. No sé por qué no lo había descubierto antes, ¡pero estoy muy, muy contento de haberlo hecho. Actualmente cerca de 6000 descargas de aplicaciones tanto en iOS App Store & Google Play Store sin ningún problema!
Empieza una sesión nueva con codex --sandbox workspace-write --ask-for-approval on-request para que la fuente de verdad sea el mapa y no la conversación anterior. Para Supabase, sustituye el paso 3 por una Edge Function que verifique el JWT, elimine los objetos de almacenamiento, confíe en las claves foráneas en cascada y llame a auth.admin.deleteUser.
Pégalo en Codex (workspace-write, aprobaciones bajo demanda)
Lee AGENTS.md y docs/user-data-map.md. Implementa una eliminación de cuenta que cumpla la Directriz 5.1.1(v) del App Store y la política de eliminación de cuentas de Google Play. Stack: front end en React, API en Express, Firebase Auth, Firestore, Cloud Storage. Esta app web también se ejecuta dentro de apps nativas de iOS y Android creadas con WebViewGold; usa isNativeApp() para el comportamiento exclusivo de la app.
1. Ajustes > Cuenta > Eliminar cuenta. Una hoja de confirmación explica qué se borra, qué conservamos y durante cuánto tiempo (según el mapa), y que Apple o Google siguen cobrando las suscripciones de la tienda hasta que se cancelan. Si el usuario tiene una suscripción activa en la tienda e isNativeApp() es true, muestra un botón Gestionar suscripción que navegue a cancelinapppurchase://.
2. Inicio de sesión reciente: si el servidor responde recent-login-required, vuelve a autenticar. Para los usuarios con el proveedor apple.com, vuelve a autenticar siempre con OAuthProvider("apple.com"), lee el access token con OAuthProvider.credentialFromResult y llama a revokeAccessToken(auth, token) antes de la llamada al servidor.
3. DELETE /api/account en Express: verifica el ID token con checkRevoked, exige que auth_time esté dentro de los últimos 5 minutos y después borra en este orden: los archivos de Storage bajo users/{uid}/, cada ubicación de Firestore del mapa (recursiveDelete para las subcolecciones, incluida pushDevices), el cliente de Stripe y, si el mapa lo indica, el cliente de RevenueCat (detrás de un feature flag, con el secreto tomado del entorno) y, por último, el usuario de Auth. Cada paso tiene que poder repetirse sin riesgo si la petición falla a mitad.
4. Si todo sale bien: cierra la sesión y, cuando isNativeApp() sea true, navega a reset:// para que WebViewGold borre cookies y caché; en los navegadores, borra localStorage. Después muestra una pantalla de despedida.
5. Página pública /delete-account que funcione sin iniciar sesión: explica los pasos dentro de la app y ofrece un formulario de solicitud (dirección de email) para quien ya no tenga la app. Las solicitudes van a una colección deletionRequests y disparan un email a [dirección de soporte].
6. Añade a /privacy una sección breve sobre eliminación y conservación de datos.
7. Tests: usa los emuladores de Firebase si el proyecto ya los usa; si no, mocks. La eliminación borra todas las ubicaciones del mapa, una segunda llamada no hace nada y un token caducado se rechaza.
Informa de los archivos modificados, de la URL que debo introducir en Play Console y de lo que tengo que configurar.
El código que debes buscar en el diff
Hay dos archivos con la lógica. El orden en el servidor importa: primero los datos y al final el usuario de autenticación, para que una petición fallida se pueda reintentar sin más. El cliente se encarga de la revocación de Apple, porque Firebase no guarda los tokens de Apple y el usuario tiene que volver a autenticarse de todos modos.
javascript
// server/routes/account.js (Express + firebase-admin)
import express from "express";
import { getAuth } from "firebase-admin/auth";
import { getFirestore } from "firebase-admin/firestore";
import { getStorage } from "firebase-admin/storage";
export const accountRouter = express.Router();
const OWNED = [["projects", "ownerId"], ["comments", "authorId"], ["pushDevices", "uid"]]; // from docs/user-data-map.md
accountRouter.delete("/api/account", async (req, res) => {
const idToken = (req.get("Authorization") || "").replace(/^Bearer /, "");
let user;
try {
user = await getAuth().verifyIdToken(idToken, true); // true = reject revoked tokens
} catch {
return res.status(401).json({ error: "unauthenticated" });
}
if (Date.now() / 1000 - user.auth_time > 300) {
return res.status(403).json({ error: "recent-login-required" });
}
const uid = user.uid;
const db = getFirestore();
await getStorage().bucket().deleteFiles({ prefix: "users/" + uid + "/" });
for (const [name, field] of OWNED) {
const owned = await db.collection(name).where(field, "==", uid).get();
for (const doc of owned.docs) await db.recursiveDelete(doc.ref);
}
await db.recursiveDelete(db.collection("users").doc(uid));
await getAuth().deleteUser(uid); // last: everything above is safe to re-run
res.status(204).end();
});
// src/lib/deleteAccount.js (client, Firebase JS SDK)
import { OAuthProvider, reauthenticateWithPopup, revokeAccessToken } from "firebase/auth";
import { isNativeApp } from "./platform";
export async function deleteAccount(auth) {
const user = auth.currentUser;
if (user.providerData.some((p) => p.providerId === "apple.com")) {
const result = await reauthenticateWithPopup(user, new OAuthProvider("apple.com"));
const credential = OAuthProvider.credentialFromResult(result);
await revokeAccessToken(auth, credential.accessToken); // Apple 5.1.1(v)
}
const res = await fetch("/api/account", {
method: "DELETE",
headers: { Authorization: "Bearer " + (await user.getIdToken(true)) }
});
if (res.status === 403) throw new Error("recent-login-required");
if (!res.ok) throw new Error("delete-failed");
await auth.signOut();
if (isNativeApp()) window.location.href = "reset://"; // WebViewGold clears cookies and cache
}
El flujo completo de principio a fin
Mapear
Ejecuta la pasada 1 en el sandbox de solo lectura, corrige tú mismo la tabla (Codex no puede saber qué datos tienes que conservar por motivos fiscales o legales) y haz commit de docs/user-data-map.md.
Construir
Ejecuta la pasada 2 en una sesión nueva. Pide un /review antes de aceptar los cambios y compara la ruta del servidor con el mapa, fila por fila.
Configurar
Introduce la URL pública de /delete-account en la sección Data safety de Play Console. Menciona la eliminación y la conservación de datos en tu política de privacidad (qué debe incluir la política).
Verificar
Recorre la checklist de abajo con una cuenta desechable en una build de TestFlight y en una build de pruebas internas.
Protege la cuenta demo
Los revisores pueden borrarla. Vuelve a crearla antes de cada envío y di en las notas para la revisión que pueden crear una cuenta nueva para probar la eliminación. Más sobre el camino hasta la publicación en la guía del flujo con Codex.
Compruébalo como lo haría un revisor
Una cuenta de prueba con datos en cada ubicación del mapa (un proyecto, un comentario, un archivo subido, un dispositivo push).
Borra desde la app: el usuario desaparece de Firebase Authentication y no queda ningún documento de Firestore ni archivo de Storage de ese usuario.
Un usuario de prueba de Sign in with Apple ya no ve tu app entre las apps de Sign in with Apple en los ajustes de su cuenta de Apple después de la eliminación.
La app vuelve a un estado limpio sin sesión; al abrirla de nuevo no se restaura la sesión anterior.
La página /delete-account se abre sin iniciar sesión en una ventana privada del navegador y el formulario de solicitud llega.
Llamar dos veces a la ruta de eliminación, o después de un fallo simulado a mitad, termina en el mismo estado limpio.
Trampas habituales
Desactivar en lugar de borrar. Un flag deleted: true no es una eliminación según la 5.1.1(v).
Borrados en el cliente a través de las reglas de seguridad. Son parciales por diseño (sin permisos de administrador, sin el usuario de autenticación, sin las referencias de otros usuarios); hazlo en el servidor.
Borrar primero el usuario de autenticación. Si después la petición falla, los datos quedan huérfanos y nadie puede autenticarse para terminar el trabajo.
Popups dentro de la app. Si reauthenticateWithPopup se comporta mal en el WebView, usa reauthenticateWithRedirect y lee el resultado con getRedirectResult.
Olvidarse de las suscripciones. Eliminar la cuenta no detiene la facturación del App Store ni de Google Play; díselo al usuario antes de que confirme.
Una página /delete-account detrás de un login, o una service role key en el código del cliente en Supabase. Las dos cosas se detectan rápido.
Preguntas frecuentes
¿Se permite un periodo de gracia?
Sí. Puedes programar la eliminación definitiva para un poco más adelante (por ejemplo, 14 días) si lo indicas en la confirmación y en la política de privacidad, y dejar que el usuario la cancele iniciando sesión. La eliminación en sí tiene que producirse igualmente.
¿Puedo usar en su lugar la extensión Delete User Data de Firebase?
Es una buena base: borra las rutas de Firestore y los archivos de Storage configurados cuando se elimina el usuario de autenticación. Aun así necesitas el botón en la app, la revocación del token de Apple, la página pública de solicitud y comprobar que su configuración coincide con tu mapa de datos.
¿La página web de solicitud tiene que borrar automáticamente?
No. Google Play pide una forma de solicitar la eliminación fuera de la app. Un formulario que crea una solicitud que luego tú procesas es aceptable, siempre que de verdad la proceses en el plazo que indicas.
¿Tengo que borrar también los datos en RevenueCat y Stripe?
Borra o anonimiza lo que no necesites conservar. Los registros de pagos pueden tener plazos legales de conservación; indica en la política de privacidad qué conservas y por qué. Cancelar las suscripciones de la tienda sigue siendo cosa del usuario.
¿Debería darle a Codex acceso a mi base de datos de producción para esto?
No. Codex escribe el código y los tests contra emuladores o mocks; tú ejecutas la eliminación en producción a través de la ruta desplegada. Mantén las credenciales de producción fuera de las sesiones de los agentes y de los entornos en la nube.
Tres formas de llevar tu app a las tiendas
El mismo resultado, con distinta cantidad de trabajo por tu parte. Las tres incluyen la garantía de aprobación y soporte humano 24/7.
Hazlo tú mismo
$109 por plataforma, pago único
La licencia de WebViewGold: un proyecto de Xcode o Android Studio listo para usar. Pega tu URL, define nombre e icono, activa funciones, compila y envía. Actualizaciones gratuitas para siempre.
Sin Mac, sin Xcode, sin Android Studio: configura en el navegador, compilamos ambas apps en la nube y las entregamos a App Store Connect y a Play Console.
Envíanos tu URL y tu icono. Nuestro equipo configura, compila y entrega tu app en 72 horas, en marca blanca y con los proyectos fuente. Ambas plataformas por $1,158.
Los reembolsos se conceden bajo las condiciones indicadas en nuestras FAQ: un rechazo por motivos ajenos al contenido por parte de Apple y/o Google, reclamado dentro de los 14 días posteriores a la compra.