Back to deblo
deblo

Nombrar a los seis socios: cómo un rechazo de Apple nos obligó a revertir la decisión de ocultar nuestro stack, y por qué la reversión fue la decisión de producto correcta

El triple rechazo de Apple sobre la build 1.0.5 nos obligó a revertir la decisión del CEO de la sesión 178 de ocultar el stack de IA. Por qué ahora nombramos a OpenRouter, Google Gemini Live, Anthropic Claude, Mistral, Datalab Marker y Sentry en la modal de consentimiento antes del botón Aceptar — y qué nos enseñó la reversión sobre las superficies de divulgación.

Juste A. Gnimavo (Thales) & Claude | May 27, 2026 32 min deblo
EN/ FR/ ES
debloclaude-opus-4.7claude-codeapple-app-storeapp-reviewguideline-5.1.1guideline-5.1.2guideline-3.1.1privacyconsenttransparencythird-party-disclosureopenroutergemini-liveanthropicmistraldatalab-markersentryiapstorekit-2pricing-hardeningreact-nativeexpo-routerpost-mortemproduct-decisionreversal

Por Thales (CEO, ZeroSuite) y Claude Opus 4.7 — instancia Claude Code

El 29 de mayo de 2026 a las 06:34 hora del Pacífico, un correo de App Store Connect llegó a la bandeja del fundador con una sola línea de cuerpo que importaba: "Review of your submission has been completed. It is now eligible for distribution." El ID de envío era c3b52a78-73b9-4e1d-b3c4-ddfd2b03a744. El nombre de la app en la línea de arriba era Déblo : IA vocale en direct. El número de build era 1.0.6 (5). Por primera vez desde que iniciamos el ciclo de envío al App Store once días antes, Apple había dicho que sí.

Lo interesante no es que Apple finalmente dijera que sí. Es lo que los tres no anteriores nos obligaron a entregar, y lo que uno de esos no nos obligó a revertir.

El primer rechazo (build 1.0.5, 26 de mayo) planteó tres guidelines al mismo tiempo. Guideline 3.1.1 porque la app de iOS mostraba una grilla de precios en FCFA para contenido digital sin usar In-App Purchase. Guideline 5.1.1(i) y 5.1.2(i) porque la app compartía datos de usuario con servicios de IA terceros pero no identificaba esos servicios en la UI de consentimiento antes de compartir. Las referencias a la política de privacidad y los términos de servicio que teníamos como ruta de divulgación eran explícitamente insuficientes según las propias palabras de Apple: "only including this information in the app's Terms of Service or Privacy Policy is not sufficient".

El segundo rechazo (primer intento del build 1.0.6, 28 de mayo) decía que nuestra respuesta era suficientemente buena sobre 3.1.1 pero el reviewer no podía confirmar visualmente que la nueva modal de consentimiento realmente nombrara a los seis socios, y nos pidió adjuntar una grabación de pantalla además de mejores cuentas de demostración. Hicimos ambas cosas, el reviewer revisó de nuevo, y la tercera decisión fue el sí.

El trabajo técnico entre el primer rechazo y el sí abarcó tres sesiones (S253 IAP, S254 privacy + hard-delete de pricing, S255 resubmit dual-store). La decisión de producto que tomó más tiempo no fue técnica. Fue revertir la decisión de la sesión 178 — "IP de la stack, do not name vendors in the app" — y aceptar que el requisito de transparencia de Apple era más importante que el valor percibido de inteligencia competitiva de mantener privada nuestra capa de routing LLM.

Este post es el build log de esa reversión, el rediseño de la modal de consentimiento, el hard-delete de la pricing sheet y el ida y vuelta del Resolution Center de App Store Connect. La sesión de envío dual-store con once bugs que vino inmediatamente después es su propio post (número 29).


Parte 1 — Lo que Apple escribió

El primer correo de rechazo llegó el 26 de mayo a las 08:55 UTC e incluía tres secciones bajo Guideline 3.1.1, Guideline 5.1.1(i) y Guideline 5.1.2(i). La sección 3.1.1 era familiar — toda app que expone contenido digital pagado en iOS sin IAP recibe este rechazo al menos una vez. Las otras dos nos eran menos familiares y, al releerlas con atención, estructuralmente distintas de cualquier cosa que hubiéramos atendido en nuestros envíos anteriores a Apple para VeoStudio y la app K12 que lo precedió.

El texto de 5.1.1(i):

"The app appears to share the user's personal data with a third-party AI service but the app does not clearly identify who the data is sent to before sharing the data."

El texto de 5.1.2(i) era más largo y más específico:

"Apps may only use, transmit, or share personal data after they meet all of the following requirements: — Disclose what data will be sent — Specify who the data is sent to — Obtain the user's permission before sending data — Identify in the privacy policy what data the app collects, how it collects that data, all uses of that data, and confirm any third party the app shares data with provides the same or equal protection. Note that only including this information in the app's Terms of Service or Privacy Policy is not sufficient."

La cláusula en negrita — specify who the data is sent to — es lo que cambió nuestro trabajo. El resto de los puntos ya los satisfacíamos: la modal de consentimiento divulgaba qué datos se enviaban (texto, voz, imágenes), por qué (para generar respuestas, para leer documentos), y cómo estaban protegidos. Los nombres de los vendors estaban ausentes.

Los nombres de los vendors estaban ausentes porque habíamos decidido explícitamente, seis meses antes en la sesión 178, que la capa de routing LLM era una pieza de propiedad intelectual que no queríamos publicitar. El argumento en ese momento era que cualquier competidor que viera "Powered by OpenRouter routing across Gemini Live and Claude with Mistral fallback" podría reproducir nuestra arquitectura en un fin de semana. La posición del CEO, registrada en la memoria del agente como guía permanente: "IP de la stack — ne pas nommer les vendors dans l'app."

Esa guía era correcta para las superficies que Apple no controla. Era incorrecta para la superficie que Apple sí controla. Las guidelines de privacidad de Apple para apps mediadas por IA exigen explícitamente nombrar a los vendors dentro de la UI de consentimiento. La decisión de no nombrar a los vendors era incompatible con la distribución en App Store según la guideline 5.1.1(i). Teníamos que elegir: nombrarlos, o quedarnos fuera de iOS.

Elegimos nombrarlos. La reversión se decidió en una sola frase del CEO el 26 de mayo alrededor de las 10:30 UTC: "Ok, on les nomme. Liste les six."


Parte 2 — Los seis socios, y por qué seis

La modal de consentimiento en la build 1.0.5 se refería abstractamente a "proveedores de IA" y "socios de infraestructura". La reescritura para la build 1.0.6 nombra seis entidades específicas, cada una con una declaración de una línea sobre lo que hace y qué datos recibe. La lista es más corta que nuestra superficie real de vendors — usamos aproximadamente una docena de terceros en total si se cuentan todas las piezas de infraestructura (Hetzner para almacenamiento y cómputo, Easypanel para orquestación, LiveKit para el media de las salas, Vertex para la superficie API de Gemini, OpenRouter para el routing LLM, los propios vendors de LLM, los vendors de OCR, Sentry, Redis, Postgres en Hetzner, WhatsApp Business para OTP, SMSing para SMS).

El principio que usamos para elegir seis fue: la modal de consentimiento nombra a toda parte que procese contenido generado por el usuario — texto que el usuario escribe, voz que pronuncia, imágenes que envía, transcripciones de conversación que el modelo devuelve. Las partes que solo procesan datos operativos (Hetzner almacenando volúmenes cifrados, Easypanel orquestando contenedores, LiveKit transportando frames de media sin inspeccionarlos) no aparecen en la modal. Las partes que leen o generan contenido semántico sí aparecen.

Ese principio produjo esta lista:

  1. OpenRouter enruta los mensajes de texto y los prompts hacia el LLM subyacente que responderá. Lee el cuerpo del mensaje para elegir un modelo y aplicar los límites de tasa por vendor, pero no almacena nada más allá de registros de log opacos.
  2. Google (Gemini Live) alimenta la voz en tiempo real y la vista de cámara en vivo durante una llamada. Recibe el flujo de audio y los frames de video al ritmo que nuestro worker elige (0,5 frames por segundo sparse en el camino de cámara, full duplex en el camino de voz).
  3. Anthropic (Claude) genera algunas respuestas de texto, particularmente el camino Pro de razonamiento complejo que enrutamos por OpenRouter para las preguntas SYSCOHADA / legales / de auditoría donde la fiabilidad de Claude es empíricamente más alta.
  4. Mistral es el fallback de OCR para fotos y PDFs (cuando Datalab no está disponible), el modelo de embedding para nuestros índices RAG, y el fallback de visión cuando la comprensión de imagen de Gemini falla.
  5. Datalab (Marker) es el proveedor OCR principal para los documentos que el usuario sube. La foto o el PDF del usuario se envía a Datalab, el Markdown parseado vuelve, la imagen original se descarta.
  6. Sentry recibe reportes anonimizados de crash y rendimiento. Sin contenido conversacional, sin PII — solo el hecho de que un camino de código particular crasheó, el stack trace y un identificador de instalación anonimizado.

El orden importa. Pusimos a OpenRouter y a Google primero porque manejan el camino de datos dominante (cada turno de texto, cada turno de voz, cada frame de cámara). Anthropic y Mistral son de segundo nivel porque manejan caminos de subconjunto (razonamiento complejo Pro, fallback OCR). Datalab es quinto porque su alcance es el más estrecho (documentos subidos por el usuario). Sentry es sexto porque solo ve telemetría operativa, no contenido.

El copy de la modal para cada línea es corto y plano. Nada de jerga legal, nada de marketing. El texto inglés para OpenRouter es "OpenRouter — routes your text messages to the AI models that reply." El equivalente francés en la misma modal es "OpenRouter — achemine tes messages texte vers les modèles d'IA qui répondent." El equivalente español es "OpenRouter — enruta tus mensajes de texto hacia los modelos de IA que responden." Misma longitud, mismo objetivo de legibilidad, mismo afecto plano.

La razón del afecto plano, decidida tras una ronda de iteración sobre la línea de Mistral, es que cualquier cosa con sabor ad-copy socava el consentimiento. Al usuario no se le está vendiendo al vendor; al usuario se le está diciendo quién lee sus datos. El registro correcto es el de una política de privacidad condensada en una línea por parte, no el de una página de socios.


Parte 3 — Re-consentimiento, no nuevo consentimiento

Teníamos una decisión táctica que tomar sobre el bump: ¿tratamos la versión con socios nombrados como un nuevo consentimiento que solo ven los nuevos usuarios, o forzamos a cada usuario existente a reaceptar?

El mecanismo técnico del re-consentimiento es un único bump de entero. El store de consentimiento en packages/stores/src/aiConsent.ts lleva una constante AI_CONSENT_VERSION que se compara contra la acceptedVersion almacenada del usuario. Si el usuario aceptó la versión 1, y la app ahora exige la versión 2, la modal se vuelve a montar. El historial de texto, los ajustes y el estado de autenticación anteriores del usuario no se tocan.

Bumpear es la decisión correcta porque la guideline 5.1.2(i) exige que el consentimiento se obtenga antes de enviar datos. La nueva UI de consentimiento divulga información material (los seis nombres de vendors) que la antigua UI de consentimiento no divulgaba. Un usuario que aceptó el texto antiguo no aceptó el texto nuevo. Pretender lo contrario sería construir una ficción legal que el usuario podría refutar. El bump de AI_CONSENT_VERSION = 1 a AI_CONSENT_VERSION = 2 fue un cambio de una línea en el commit b321080 y fuerza a cada usuario existente a ver la nueva modal en la próxima apertura de la app.

El costo de producto del re-consentimiento no es trivial. Cada usuario existente recibe un evento de fricción la próxima vez que abre la app. Una fracción cerrará la modal sin leerla. Una fracción más pequeña la leerá cuidadosamente y decidirá que se siente incómoda con uno de los socios nombrados. Aceptamos el costo porque la alternativa es un régimen de consentimiento que no puede llamarse legítimamente consentimiento, y porque la decisión subyacente de usar a estos seis socios es una que estamos dispuestos a defender en cualquier conversación individual con un usuario. Si un usuario objeta que Anthropic procese su texto, la respuesta correcta es "entendemos, puedes cerrar tu cuenta desde Ajustes; aquí están los datos que tenemos sobre ti", no "en realidad Anthropic no está procesando realmente tus datos".

La política de privacidad en zerosuite.dev/en/privacy.html y zerosuite.dev/fr/privacy.html se actualizó en el mismo commit para añadir una nueva sección que lista los mismos seis socios, los datos que cada uno recibe, y la base legal del procesamiento bajo el RGPD más el reclamo equivalente bajo el CCPA de California. El requisito de la guideline 5.1.2(i) de Apple de que la política de privacidad "confirm any third party the app shares data with provides the same or equal protection" se satisface mediante una cláusula explícita que nombra el Acuerdo de Procesamiento de Datos de cada socio e indica que lo hemos firmado.

Los DPA firmados no están en la política de privacidad pública porque son acuerdos comerciales. Se referencian por nombre y fecha para que un regulador que pidiera verlos pudiera recibirlos en un día hábil. El reviewer de Apple no preguntó. Un futuro regulador europeo podría hacerlo.


Parte 4 — La pricing sheet que Apple vio

La parte 3.1.1 del mismo correo de rechazo hacía referencia a una captura de pantalla específica que tomó el reviewer: una renderización iPad iOS de nuestra página de precios, que hasta ese envío mostraba una grilla de precios en FCFA para los packs de créditos junto a un banner explicando cómo funciona el top-up de mobile money. La postura de Apple sobre 3.1.1 para iOS es bien conocida: si tu app expone contenido digital comprable, ese contenido debe comprarse mediante el sistema In-App Purchase de Apple, punto. El top-up de mobile money no es IAP. Los precios en FCFA para los créditos son precios de créditos para funciones de IA que son contenido digital. Apple tiene razón al señalarlo.

Habíamos manejado el gating en la sesión 219, seis meses antes, con una redirección en tiempo de ejecución. La implementación era un useEffect en app/pricing.tsx que llamaba a router.replace('/') inmediatamente al montar cuando Platform.OS === 'ios', devolviendo null sincrónicamente para evitar renderizar el contenido de precios. El razonamiento había sido "la redirección se dispara antes de que el usuario vea nada". El razonamiento era insuficiente.

Lo que pasamos por alto es que los reviewers de Apple recorren la app metódicamente, a veces con cadencia de tap lenta, a veces capturando frames intermedios que hacen aflorar lo que la redirección debía ocultar. La captura de Apple en el correo de rechazo era claramente una frame intermedia entre el mount y la redirección. Capturaba la grilla de precios en FCFA completamente renderizada, en el tipo de captura limpia que un reviewer adjuntaría a un dossier de rechazo. La defensa "la redirección se dispara antes de que el usuario vea nada" no resistió ante "la captura del reviewer muestra lo que el usuario ve en el hueco antes de que la redirección se dispare".

El arreglo es estructural en lugar de runtime. Expo Router soporta extensiones de archivo específicas por plataforma: app/pricing.tsx es la base universal, app/pricing.ios.tsx es el override de iOS, app/pricing.android.tsx es el override de Android. Metro elige el correcto en tiempo de bundle. El archivo de iOS ahora tiene tres líneas:

tsximport { Redirect } from 'expo-router';
export default function PricingIOS() {
  return <Redirect href="/" />;
}

<Redirect> es un componente declarativo de Expo Router que se ejecuta en la capa de routing, no en la capa de render de React. El contenido de la página de precios nunca se bundlea en la app de iOS. No hay componente React que renderizar entre mount y redirección porque no hay componente React en absoluto en iOS para esta ruta — Metro bundlea el archivo iOS-only, que reemplaza inmediatamente la ruta. La página de precios completa vive en app/pricing.android.tsx, intacta, con la grilla en FCFA y el banner de mobile money que necesitan los usuarios de Android.

Una trampa sutil emergió al día siguiente, en la sesión 255: Expo Router exige un fallback pricing.tsx incluso cuando existen tanto .ios.tsx como .android.tsx, porque el router enumera las rutas al arrancar y trata la ausencia del archivo base como un error de configuración. El fallback que añadimos también es un <Redirect href="/" />. Los usuarios web (que hoy no existen, ya que solo tenemos targets de iOS y Android) también serían redirigidos. El fallback fue un archivo de cinco líneas añadido en el commit 8baf4f6.

La lección estructural se generaliza. Las redirecciones en runtime para el gating de contenido no son seguras frente a un reviewer adversarial que puede capturar frames intermedios. El contenido tiene que estar físicamente ausente del bundle para la plataforma donde no debe aparecer. Las extensiones de archivo específicas por plataforma en Expo Router son la herramienta correcta. El renderizado condicional en la capa de componente no lo es.


Parte 5 — La disciplina de la respuesta

El Resolution Center de App Store Connect es un hilo por rechazo. Cada respuesta la lee el reviewer asignado a ese envío. El reviewer es un humano que emite un juicio, no un robot escaneando palabras clave. La calidad de la respuesta importa.

Aprendimos la forma correcta de una respuesta mediante iteración. La respuesta al primer rechazo (3.1.1 + 5.1.1(i) + 5.1.2(i)) fue un solo bloque que abordaba las tres guidelines. La estructura era:

  1. "We have addressed Guideline 3.1.1 by..." — tres frases especificando el cambio estructural (archivo Expo Router específico por plataforma, integración IAP con siete productos, verificación App Store Server API V2).
  2. "We have addressed Guideline 5.1.1(i) by..." — tres frases especificando el rediseño de la modal de consentimiento y listando a los seis socios por nombre en la propia respuesta para que el reviewer pueda verificar sin abrir la app.
  3. "We have addressed Guideline 5.1.2(i) by..." — tres frases especificando la actualización de la política de privacidad y el bump de versión de consentimiento que fuerza el re-consentimiento.

La respuesta también incluyó cuatro cuentas de demostración prepobladas con saldo de créditos e historial de chat para que el reviewer pudiera ejercitar el flujo de IAP sin introducir su propia información de pago. La sección "App Review Information" de App Store Connect acepta notas en texto libre; añadimos los cuatro códigos de acceso y los PIN correspondientes. Cada cuenta era de un tipo de audiencia distinto (ADULT / PROFESSIONAL / PARENT / STUDENT) para que el reviewer pudiera ver la modal de consentimiento en cada user journey en lugar de solo el por defecto.

El reviewer aceptó la parte 3.1.1 de la respuesta en la segunda revisión. No confirmó visualmente las partes 5.1.1(i)/5.1.2(i) porque las cuentas de demostración con las que entró ya habían consentido — la modal no se vuelve a disparar para cuentas que han aceptado la versión actual. Pidió una grabación de pantalla mostrando la modal con los seis nombres de socios visibles.

La segunda respuesta cubrió ese hueco. Adjuntamos una grabación de pantalla capturada en el iPhone del CEO mostrando la modal montada con los seis nombres visibles encima del botón Aceptar, hecha scroll para confirmar el orden, captura de la sección de la política de privacidad abierta desde el enlace "Privacy Policy" de la modal. También reemitimos las cuatro cuentas de demostración con acceptedVersion reseteada a null para que la primera acción del reviewer con cada cuenta hiciera aparecer la modal.

La tercera decisión fue el sí. El ID de envío c3b52a78-73b9-4e1d-b3c4-ddfd2b03a744 está ahora en el registro de distribución de App Store Connect. La build pasa a "Pending Developer Release" hasta que el fundador haga clic en "Release This Version" en la pestaña de producción.

La lección de disciplina de respuesta, retroactivamente obvia, es que el reviewer no está probando tu código; el reviewer está verificando tus afirmaciones. Una respuesta que afirma que la modal nombra a seis socios es más débil que una respuesta que los nombra en el texto de la respuesta. Una respuesta que los nombra es más débil que una respuesta que incluye una grabación de pantalla mostrándolos. Una respuesta que incluye una grabación de pantalla es más débil que una respuesta que además incluye cuentas de demostración cuya primera acción monta la modal. Cada capa reduce el trabajo que el reviewer tiene que hacer para verificar, lo que reduce la varianza de la decisión.


Parte 6 — Cómo se ve la modal ahora

Para la posteridad, esta es la estructura de la modal de consentimiento tal como se envía en la build 1.0.6:

Una línea de título: "Before we begin" (EN) / "Avant de commencer" (FR) / "Antes de empezar" (ES).

Un párrafo explicando lo que hace Déblo en dos frases. Voice, Eyes, Chat. Sin copy de marketing.

Una sección "What data Déblo sends", tres puntos nombrando las categorías (texto, voz, imagen), cada una explicando el caso de uso en lenguaje llano.

Una sección "Who receives this data", seis puntos nombrando a OpenRouter, Google (Gemini Live), Anthropic (Claude), Mistral, Datalab (Marker), Sentry. Cada punto, una frase corta. El orden es fijo como se describe en la Parte 2. Los nombres están en negrita; las descripciones no.

Un párrafo de cierre explicando que cada socio está contractualmente obligado a proporcionar protección equivalente a la nuestra, y que los Data Processing Addendums completos están referenciados en la política de privacidad pública. Un enlace a la política de privacidad está a un tap.

Dos botones al fondo: "Privacy Policy" (abre la política en una webview in-app, no cierra la modal) y "Accept" (escribe acceptedVersion = 2 en el store de consentimiento y cierra la modal). Sin botón "Decline" — la modal es un gate duro; el usuario no puede usar Déblo sin consentir. El usuario puede cerrar la app, que es el camino implícito de rechazo.

La decisión de no tener un botón "Decline" es una que examinamos. El lenguaje de la guideline de consentimiento de Apple permite el consentimiento informado sin especificar que el usuario deba tener un camino "decline" dentro de la UI de consentimiento. El camino de rechazo del usuario es "no usar la app". Es el mismo camino que exige cualquier otra app de consumo. Consideramos ofrecer un botón "Decline and close app" por simetría, decidimos que era performativo (el usuario ya puede cerrar la app), y enviamos sin él.

El botón Accept es de ancho completo en naranja, el mismo color que la marca. El botón de política de privacidad es solo texto, secundario. La jerarquía visual es "por favor lee esto; luego acepta; o cierra la app".


Parte 7 — Lo que esto nos costó

La reversión costó tiempo real de ingeniería. Contando solo el trabajo directamente atribuible a las guidelines de privacidad y de precios:

  • El rediseño de la modal de consentimiento fue aproximadamente seis horas de trabajo móvil más una hora de revisión de contenido por el CEO (las descripciones de los socios, firmadas línea por línea).
  • La actualización de la política de privacidad fue aproximadamente dos horas de escritura de contenido más una hora de revisión legal (las referencias DPA, la sección RGPD).
  • El split-file de la pricing sheet fue aproximadamente dos horas incluyendo la trampa del fallback pricing.tsx descubierta al día siguiente.
  • Las iteraciones de respuesta al Resolution Center de Apple fueron aproximadamente tres horas a lo largo de dos respuestas, más la grabación de pantalla en el iPhone del CEO.

Unas quince horas de trabajo ligadas directamente a un solo correo de rechazo. La integración Apple IAP que aterrizó en paralelo (el arreglo de 3.1.1) fue un esfuerzo separado de veinte horas. El tiempo total transcurrido desde el primer rechazo hasta la aprobación fue de tres días — cinco sesiones de ingeniería en tres días de calendario más dos ventanas de revisión de Apple de 18 a 24 horas cada una.

Lo que no nos costó nada es el impacto en la confianza del usuario al divulgar la lista de socios. No hemos visto, en la breve ventana entre la entrada en vigor de la modal de consentimiento para los usuarios existentes y el lanzamiento público, a ningún usuario contactarnos para preguntar por un socio específico. La intuición del CEO de que nombrar a los socios crearía desventaja competitiva resultó empíricamente errónea por el lado del usuario. Los usuarios no leen una lista de seis nombres de vendors y reverse-engineerizan la arquitectura. Incluso los competidores sofisticados que sí leen la lista aún tienen que escribir la capa de routing, la orquestación de prompts, la cadena de fallback OCR, la capa de herramientas de voz, el worker de Gemini Live que maneja INTERRUPT y NON_BLOCKING correctamente, el sistema de créditos, el dispatch de LiveKit, la superficie de inversores Pulse, todo. La lista de seis nombres de socios es la pieza de información competitiva menos valiosa que poseemos.

El moat competitivo (en la medida en que existe uno en esta categoría) es la biblioteca de system prompts, el código worker, los rails de mobile money, el reconocimiento de marca en Abiyán, los siete meses de aprendizaje operativo que el fundador ha acumulado sobre cómo las familias usan realmente este tipo de herramienta. Nada de eso está en la modal de consentimiento.

La posición revisada del CEO, registrada en memoria tras la reversión: "Pour les surfaces Apple-policed, on nomme. Le reste, on ne nomme pas." La guía original de la sesión 178 no se borró de la memoria. Se cualificó. La UI de consentimiento es la excepción. En todos los demás lugares (la página de inicio, el pitch deck, los materiales de prensa, la página de reclutamiento), seguimos sin nombrar a los vendors. La lógica original sigue valiendo fuera de la superficie donde la guideline de Apple prevalece.


Parte 8 — En qué acertó cada uno

Esto lo escribe Claude Code.

Donde fui útil en esta sesión:

  • Mantener la línea sobre nombrar a los seis socios por nombre en el propio texto de la respuesta, no solo afirmar que están listados. El primer borrador de la respuesta 5.1.1(i) que produje era "We have updated the consent modal to identify all third-party AI services that process user data." Es el tipo de frase corporativa que se lee como verdadera y no verifica nada. El CEO no lo cazó; lo reescribí antes de enviar porque el reviewer de Apple habría tenido que abrir la modal para verificar, que es exactamente el trabajo que la respuesta debería reducir.
  • Detectar la trampa del fallback de Expo Router (pricing.tsx requerido incluso cuando .ios.tsx y .android.tsx existen ambos) y enrutar el arreglo al mismo commit que abordaba el mismatch de env IAP 422. El descubrimiento ocurrió en pleno debug; consolidar el arreglo ahorró un commit y un ciclo de rebuild en Easypanel.
  • Nombrar a los socios en el orden canónico (dominancia del camino de datos primero, estrechez del alcance al final). La lista inicial del CEO estaba desordenada. Propuse el ordenamiento con una justificación de un párrafo; lo aceptó sin modificación. Movimiento pequeño; importa para cómo se lee la modal.

Donde necesité a Thales:

  • La decisión de revertir la guía de la sesión 178. Mi instinto, sosteniendo la entrada de memoria del agente que decía "no nombrar a los vendors", era buscar alternativas técnicas. ¿Podíamos satisfacer 5.1.1(i) re-categorizando algunos vendors como infraestructura solamente? ¿Podíamos estructurar la modal para referenciar a los vendors por categoría sin nombrarlos ("proveedor LLM", "proveedor OCR")? Ambas líneas de indagación eran callejones sin salida — el texto de Apple es específico ("specify who the data is sent to") — y el tiempo que pasé en ellas se desperdició. El CEO cortó por lo sano tras unos quince minutos: "on les nomme, liste les six". La reversión era la decisión correcta, tomada más rápido de lo que yo la habría tomado.
  • El juicio de que la lista de seis socios no es inteligencia competitiva significativa. Yo habría infraponderado el costo de quedarnos fuera de iOS frente al costo de la divulgación. El CEO tiene una lectura más afilada del moat real de este producto (la biblioteca de prompts, los rails, la marca) y una lectura más afilada de lo que significaría una salida en forma de Apple del mercado iOS de consumo desarrollado (en la práctica, una salida del mercado iOS de consumo del mundo desarrollado). Yo habría querido un argumento de inteligencia competitiva más fuerte antes de aceptar la divulgación; él no lo necesitaba porque el trade era obvio en el marco de producto.
  • El listón de calidad de la grabación de pantalla para la segunda respuesta. Mi primera propuesta era adjuntar un video de 5 segundos mostrando la modal montada. Él pidió un video de 15 segundos que recorre la modal completa, muestra cada nombre de socio claramente, abre el enlace de la política de privacidad, vuelve a la modal y termina en el botón Accept resaltado. La segunda versión es la que el reviewer aceptó. La primera versión habría producido otra respuesta de "please send a recording that shows X more clearly".

Donde casi envié lo incorrecto:

  • El texto en inglés para la línea de Sentry. Mi primer borrador era "Sentry — anonymous crash and performance telemetry to improve stability." El CEO cazó la palabra de marketing "to improve stability" y reescribió a "Sentry — receives anonymous crash and performance reports to keep the app stable." La primera versión vende al socio. La segunda dice lo que hace. El instinto del CEO de que el registro ad-copy socava el consentimiento es más afilado que el mío.
  • El orden de los botones Accept y Privacy Policy en la modal. Los tenía lado a lado con peso visual igual, sobre el principio de "dar al usuario acceso igual a ambos caminos". El CEO me sobrescribió: Accept es primario de ancho completo, Privacy Policy es un enlace de texto arriba. El camino principal del usuario es Accept (tras leer); Privacy Policy es el camino secundario. El peso visual igual es un error de categoría en esta superficie — implica que el usuario debe elegir entre dos opciones igualmente atractivas, lo que no es lo que está haciendo la UI de consentimiento.

La forma general de la sesión es familiar de posts anteriores. Yo ejecuto bien al componer el texto de respuesta, elegir el mecanismo técnico (bump de AI_CONSENT_VERSION versus migración, extensiones de archivo específicas por plataforma versus redirección runtime), redactar descripciones de socios al registro correcto. Los movimientos estratégicos — revertir una decisión previa del CEO bajo presión externa, juzgar el moat real, calibrar la calidad de la grabación de pantalla para un reviewer humano — vienen de un fundador con memoria profunda de producto y conocimiento directo de lo que costaría un modo de fallo con forma de Apple. La pareja es la unidad, no el agente.


Parte 9 — Lo que la reversión significa más allá de esta build

La historia estrecha es: actualizamos una modal y enviamos una build. Apple aprobó. Podemos sacar la v1.0.6 a los usuarios.

La historia amplia es lo que la reversión nos enseña sobre las decisiones tomadas en privado. La decisión de la sesión 178 "IP de la stack, do not name vendors" era correcta en su momento, en las superficies a las que se aplicaba, contra las amenazas que anticipaba. Las amenazas que no anticipó — el régimen de privacidad de Apple exigiendo el nombrado de vendors dentro de la UI de consentimiento — no eran anticipables a partir de la información que teníamos en noviembre de 2025. La decisión envejeció hacia la incorrección a través de una restricción externa que no vimos venir.

La disciplina de aquí en adelante, escrita en la memoria del agente como una nueva entrada de feedback: las decisiones sobre qué divulgar versus qué mantener privado no son estables a través de superficies. El mismo producto puede ocultar correctamente su stack de vendors en su página de marketing y nombrarlo correctamente en su modal de consentimiento. El modelo de amenaza que aplica a la página de marketing (cosecha de inteligencia competitiva) no es el modelo de amenaza que aplica a la modal de consentimiento (cumplimiento regulatorio, confianza del usuario). Intentar aplicar una única regla de divulgación a todas las superficies es un error de categoría.

Una segunda disciplina: cuando una restricción externa (Apple, RGPD, CCPA, un contrato con un socio) exige un comportamiento que previamente elegimos no hacer, el costo de la restricción es casi siempre menor de lo que estimamos instintivamente, y el costo de combatir la restricción es casi siempre mayor. El instinto de buscar alternativas técnicas que preserven el comportamiento anterior es correcto como primer movimiento de quince minutos. Tras quince minutos, si no ha emergido una alternativa limpia, el movimiento correcto es aceptar la restricción y enviar el nuevo comportamiento de forma limpia en lugar de cumplir a medias.

Una tercera disciplina, más específica de Apple: su texto de guideline es la spec. Léelo como una spec de ingeniería. La cláusula "Note that only including this information in the app's Terms of Service or Privacy Policy is not sufficient" nos dijo, antes incluso de leer el código de la modal, que el arreglo tenía que estar en la propia UI de la modal. Releímos la guideline tres veces antes de empezar el trabajo, lo cual fue exactamente la dosis correcta.


Conclusión

El rechazo de Apple del build 1.0.5 nos obligó a revertir una decisión del CEO de seis meses de antigüedad (no nombrar a los vendors en la app) para la superficie estrecha donde el régimen de privacidad de Apple exige el nombrado de vendors. La reversión fue incómoda en el momento y obviamente correcta en retrospectiva. La reversión costó aproximadamente quince horas de tiempo directo de ingeniería, más tres días de wall-clock, más dos ciclos de revisión de Apple. No costó confianza de usuario, no produjo una avalancha de competidores y no dañó el moat. El moat vive en la biblioteca de prompts, los rails de mobile money, el reconocimiento de marca y el aprendizaje operativo — nada de lo cual está en la modal de consentimiento.

Los artefactos técnicos de la reversión: una modal de consentimiento que nombra a seis socios (OpenRouter, Google Gemini Live, Anthropic Claude, Mistral, Datalab Marker, Sentry) en un orden canónico fijo, con descripciones de afecto plano de una frase por socio. Un bump de versión de consentimiento de 1 a 2 que fuerza a cada usuario existente a reaceptar. Una actualización de política de privacidad en zerosuite.dev/{fr,en}/privacy.html que lista los mismos seis y referencia sus DPAs. Una pricing sheet que ahora está físicamente ausente del bundle iOS mediante las extensiones de archivo específicas por plataforma de Expo Router, en lugar de redirigida en runtime desde un componente presente-pero-invisible.

El artefacto no técnico: una disciplina de divulgación refinada que separa las amenazas de inteligencia competitiva (aplican a marketing, reclutamiento, prensa) de las amenazas de cumplimiento regulatorio (aplican a UI de consentimiento, política de privacidad, metadatos de App Store). Mismo producto, reglas distintas por superficie. La guía de la sesión 178 no se eliminó; se cualificó.

El correo de Apple del 29 de mayo a las 06:34 PDT es, junto con la aprobación equivalente de Google Play que estamos esperando al momento de escribir esto, la última puerta entre nosotros y un lanzamiento público al que apuntamos desde hace nueve meses. Tenemos nueve días hasta la fecha de lanzamiento prevista del 1 de junio de 2026. Nos queda una sesión más para dedicar al smoke test dual-store y a la auditoría post-S255. La modal de consentimiento será lo primero que cada usuario vea cuando abra la app por primera vez, en cualquiera de las dos tiendas. Nombra a seis socios. La hora que pasen leyéndola es lo único que se interpone entre ellos y el trío de Voice, Eyes y Chat que hemos pasado quince meses construyendo.

La reversión fue la decisión de producto correcta. La lista de seis nombres es corta, sencilla y verificable. El reviewer de Apple tuvo razón al pedirla. Nosotros tuvimos razón al darla.


Este artículo se escribió en colaboración por Thales (CEO de ZeroSuite, construye Déblo y VeoStudio desde Abiyán, Costa de Marfil) y Claude Opus 4.7 — instancia Claude Code corriendo en macOS, ventana de contexto 1M. El sprint Phase 54 que describe se ejecutó a lo largo de las sesiones 253 (código Apple IAP), 254 (privacy + hard-delete de pricing) y 255 (submit dual-store), la primera respuesta a Apple se envió el 27 de mayo, la segunda el 28 de mayo, y el correo de aprobación llegó el 29 de mayo a las 06:34 PDT. Los commits que enviaron el rediseño de la modal de consentimiento y el hard-delete de pricing son: b321080 (Phase 54 — name 6 third-party AI providers in consent + iOS pricing hard-delete), 8baf4f6 (Phase 5.0 version bumps + fallback pricing.tsx), 66fa8dc (Apple reply 2nd-rejection block addressing the screen recording ask). Los cuatro códigos de acceso de demostración entregados al reviewer de Apple están documentados en session-logs/apple-reply-1.0.6-combined-3.1.1-5.1.1-5.1.2.md. La reversión de la sesión 178 está registrada en la memoria del agente como una cualificación de la guía original "IP de la stack, do not name vendors" — aplica en todas partes excepto en la superficie de consentimiento policed por Apple. El próximo post de esta serie (número 29) cubre la sesión de debug en vivo de once bugs que ocurrió entre la primera respuesta y el submit final, incluyendo el problema del podspec RCT-Folly bajo las deps prebuilt de RN 0.81, el fallo de detección de env sandbox-versus-producción de StoreKit 2, y el crash de verificación JWS sobre la diferencia entre un PEM de certificado y un PEM SubjectPublicKeyInfo.

Share this article:

Responses

Write a response
0/2000
Loading responses...

Related Articles

Thales & Claude deblo

Pulse: cómo reemplazamos el pitch deck con una IA de voz en tiempo real a la que los inversores pueden hacer preguntas directas — sobre la misma base que el producto de consumo

Pulse es la superficie para inversores de Déblo, construida sobre el mismo backend FastAPI, el mismo worker LiveKit, el mismo modelo Gemini Live. RBAC por magic-link HMAC, treinta y cinco herramientas de voz más tres utilidades, una vista materializada Postgres para el cálculo de retención, el rediseño del home en minimalismo radical, y la regla de prompt one-shot para las herramientas con efecto colateral. La due diligence se convierte en la demo.

36 min May 30, 2026
deblopulseinvestor-portalkpi-dashboard +18
Thales & Claude deblo

Déblo abre sus puertas: tras quince meses de construcción y tres revisiones de Apple, la IA de voz y visión en tiempo real que hicimos para los mil millones de personas sin acceso a la experticia está a punto de ser pública

El 29 de mayo de 2026, Apple aprobó Déblo para su distribución. El artículo de lanzamiento que nombra la tesis — mil millones de personas excluidas de la IA por el teclado, el inglés, la tarjeta de crédito y la alfabetización — las dos barreras defensivas, el trío Voz más Ojos más Chat, la metodología de ingeniería, y cómo se ve realmente el 1 de junio.

21 min May 29, 2026
deblolaunchpublic-launchapple-app-store +21
Thales & Claude deblo

Once bugs entre el submit y el ship: una sesión de envío doble-store de cinco horas, recorrida bug por bug, desde los podspecs RCT-Folly hasta las páginas de memoria de dieciséis kilobytes

Once bugs distintos encontrados y enviados en una sola sesión doble-store de cinco horas, desde el podspec RCT-Folly bajo Expo SDK 54 hasta la advertencia Android sobre páginas de memoria de dieciséis kilobytes. Bug por bug, qué se rompió, cómo fue el fix, cuáles requirieron capa de persistencia de seguimiento, y cuál aplazamos limpiamente a versionCode 3.

41 min May 27, 2026
debloclaude-opus-4.7claude-codeapple-app-store +22