orquestra-mcp 1.43.0 → 1.44.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "orquestra-mcp",
3
- "version": "1.43.0",
3
+ "version": "1.44.0",
4
4
  "description": "Servidor MCP de Orquestra -- 135 tools para leer y escribir tareas, ideas, changelog, infraestructura, contenido, QA y más desde un agente de IA",
5
5
  "type": "module",
6
6
  "bin": {
@@ -19,74 +19,177 @@ export const PLATFORM_PLAYBOOKS = {
19
19
  moduleDescription: 'Checklist verificable para enviar la app a revisión en App Store Connect, sin sorpresas de último momento.',
20
20
  moduleTags: ['playbook', 'publicacion'],
21
21
 
22
+ // Revisado contra App Store Connect real el 19 oct 2026 (ORQ-383). `section`
23
+ // agrupa la guía en el mismo orden que el menú de la consola; las keys son
24
+ // estables (viven en distributions.publishForm), no se renombran.
22
25
  referenceGuide: [
26
+ // ── App Information ──
23
27
  {
24
- key: 'name',
25
- title: 'Name',
26
- body: 'El nombre de marca tal cual. Apple no permite keyword-stuffing en el nombre desde 2017.',
28
+ key: 'name', section: 'App Information',
29
+ title: 'Name (máx. 30 caracteres)',
30
+ body: 'El nombre de marca tal cual. Apple no permite keyword-stuffing en el nombre desde 2017. Es común a todas las versiones (se edita en App Information, no en la versión).',
27
31
  },
28
32
  {
29
- key: 'subtitle',
33
+ key: 'subtitle', section: 'App Information',
30
34
  title: 'Subtitle (máx. 30 caracteres)',
31
- body: 'Una frase corta que diga a quién sirve y qué resuelve, no un slogan genérico. Cuenta caracteres literal.',
35
+ body: 'Una frase corta que diga a quién sirve y qué resuelve, no un slogan genérico. Cuenta caracteres literal. Las palabras del subtítulo ya cuentan para búsqueda: no las repitas en Keywords.',
32
36
  },
33
37
  {
34
- key: 'category',
35
- title: 'Category',
36
- body: 'Primary + Secondary. Piensa en la categoría real de uso. "Developer Tools" está separada de "Business" -- si la app es una herramienta del ciclo de vida de software, Developer Tools calza mejor que Business.',
38
+ key: 'category', section: 'App Information',
39
+ title: 'Category (Primary + Secondary)',
40
+ body: 'La primaria es la que más pesa en búsqueda y rankings: elige la del uso principal del usuario final (ej. Productivity para gestión de proyectos), y la secundaria para el caso de nicho (ej. Developer Tools si también integra repos/QA). Si la app ES una herramienta del ciclo de vida de software, Developer Tools va de primaria.',
37
41
  },
38
42
  {
39
- key: 'content_rights',
43
+ key: 'content_rights', section: 'App Information',
40
44
  title: 'Content Rights',
41
45
  body: 'Pregunta si la app contiene/muestra/accede a contenido de terceros. Apunta a apps que REDISTRIBUYEN contenido con derechos de terceros (streaming, stock media) -- no a "cualquier dato que pase por la app". Si solo se muestran datos propios del usuario/equipo (o conectados desde su propia cuenta, ej. su propio repo de GitHub), la respuesta es "No".',
42
46
  },
43
47
  {
44
- key: 'age_features',
48
+ key: 'age_features', section: 'App Information',
45
49
  title: 'Age Ratings — Paso 1 (Features)',
46
- body: 'Formulario sobre controles/capacidades técnicas (Parental Controls, Age Assurance, Unrestricted Web Access, User-Generated Content, Social Media, Messaging and Chat, Advertising). Ojo con "User-Generated Content": la definición de Apple es sobre "broad distribution" (distribución amplia/pública) -- si el contenido queda dentro de un workspace/equipo privado entre miembros invitados, NO cuenta como UGC para esta pregunta. Para una app B2B típica todo va en "No".',
50
+ body: 'Formulario sobre controles/capacidades técnicas (Parental Controls, Age Assurance, Unrestricted Web Access, User-Generated Content, Social Media, Messaging and Chat, Advertising). Ojo con "User-Generated Content": la definición de Apple es sobre "broad distribution" (distribución amplia/pública) -- si el contenido queda dentro de un workspace/equipo privado entre miembros invitados, NO cuenta como UGC para esta pregunta. Pero un link público (ej. "compartir doc fuera del equipo") sí puede contar. Para una app B2B típica todo va en "No".',
47
51
  },
48
52
  {
49
- key: 'age_content',
53
+ key: 'age_content', section: 'App Information',
50
54
  title: 'Age Ratings — Pasos 2-6 (descriptores de contenido)',
51
- body: 'Mature Themes, Medical/Wellness, Sexuality/Nudity, Violence, Chance-Based Activities -- para una app sin ese tipo de contenido, todo en "None"/"No". El resultado esperado es clasificación 4+.',
55
+ body: 'Mature Themes, Medical/Wellness, Sexuality/Nudity, Violence, Chance-Based Activities -- para una app sin ese tipo de contenido, todo en "None"/"No". El resultado esperado es clasificación 4+. Si marcas "frequent" en "Medical or Treatment Information", Apple te va a pedir además la declaración de dispositivo médico regulado (Regulations & Permits).',
52
56
  },
53
57
  {
54
- key: 'age_override',
55
- title: 'Age Ratings — Paso 7 (Age Categories and Override)',
56
- body: 'Deja "Not Applicable" salvo que la app esté realmente dirigida a niños (Kids Category) o tenga una razón real para pedir una clasificación más alta.',
58
+ key: 'age_override', section: 'App Information',
59
+ title: 'Age Ratings — Paso 7 (Override) + Age Suitability URL',
60
+ body: 'Deja "Not Applicable" salvo que la app esté realmente dirigida a niños (Kids Category) o tenga una razón real para pedir una clasificación más alta. App Store Connect ya muestra clasificaciones por región (ej. Brasil, Corea, Vietnam) calculadas de tus respuestas -- revísalas, no se llenan aparte. "Age Suitability URL" es opcional: solo si tienes una página propia que explique para qué edades es la app.',
57
61
  },
62
+
63
+ // ── Ficha de la versión (Product Page Information) ──
58
64
  {
59
- key: 'pricing',
60
- title: 'App Pricing',
61
- body: 'Depende del modelo de negocio real del producto -- no asumas que es gratis, esto lo decide cada proyecto.',
65
+ key: 'promotional_text', section: 'Ficha de la versión',
66
+ title: 'Promotional Text (máx. 170 caracteres)',
67
+ body: 'Aparece arriba de la descripción y se puede cambiar en cualquier momento SIN mandar una versión nueva a revisión -- úsalo para lo que cambia (novedad, temporada, campaña). No cuenta para búsqueda.',
68
+ },
69
+ {
70
+ key: 'description', section: 'Ficha de la versión',
71
+ title: 'Description (máx. 4000 caracteres)',
72
+ body: 'Beneficios primero (1-2 párrafos para quién es y qué resuelve), después una lista real de funciones -- sin inventar lo que la app no hace ni prometer lo que está en otra plataforma. Sin precios ni nombres de planes si lo pagado no se vende como In-App Purchase dentro de la app (Guideline 3.1.1/3.1.3: eso cuenta como "call to action" a una compra externa). No cuenta para búsqueda; editarla exige versión nueva.',
62
73
  },
63
74
  {
64
- key: 'availability',
65
- title: 'App Availability',
66
- body: '"All Countries or Regions" es el default razonable salvo que haya una restricción regulatoria real específica de la app.',
75
+ key: 'keywords', section: 'Ficha de la versión',
76
+ title: 'Keywords (máx. 100 caracteres)',
77
+ body: 'Separadas por coma SIN espacios ("tareas,equipo,kanban"), en singular, sin repetir palabras del nombre/subtítulo ni de la categoría, sin marcas ajenas. Es el único campo de texto que es 100% para búsqueda: aprovecha los 100 caracteres. Las Custom Product Pages pueden reusar estas keywords para aparecer en búsqueda con su propia página.',
67
78
  },
68
79
  {
69
- key: 'privacy_choices_url',
70
- title: 'User Privacy Choices URL (opcional)',
71
- body: 'Junto a la Privacy Policy URL, App Store Connect deja un campo opcional para una URL de "elecciones de privacidad" (ej. opt-out de venta/uso de datos con fines publicitarios, al estilo CCPA). Solo aplica si la app realmente vende/comparte datos con terceros para publicidad -- para una app sin SDKs de publicidad ni venta de datos (como Orquestra), se deja en blanco.',
80
+ key: 'urls', section: 'Ficha de la versión',
81
+ title: 'Support URL / Marketing URL',
82
+ body: 'Support URL es obligatoria y tiene que responder 200 con una forma real de contacto (página, formulario o correo visible). Marketing URL es opcional (la landing). Pruébalas en el navegador antes de pegarlas -- una SPA sin rewrite puede dar 404 en una ruta profunda.',
83
+ },
84
+ {
85
+ key: 'copyright', section: 'Ficha de la versión',
86
+ title: 'Copyright',
87
+ body: 'Año + titular de los derechos: la persona o empresa que publica (ej. "2026 Mi Empresa S.A. de C.V."). No lleva el símbolo ©, App Store lo agrega.',
88
+ },
89
+ {
90
+ key: 'whats_new', section: 'Ficha de la versión',
91
+ title: "What's New (solo en actualizaciones)",
92
+ body: 'No aparece en la 1.0. En cada actualización: 2-4 líneas de lo que el usuario nota (no la lista de commits). El changelog de la distribución en Orquestra es la fuente natural.',
93
+ },
94
+ {
95
+ key: 'screenshots', section: 'Ficha de la versión',
96
+ title: 'Capturas y previews (por dispositivo)',
97
+ body: 'Hasta 10 capturas y 3 previews por tamaño. Hoy pide: iPhone con Dynamic Island (6.1"/6.3": 1179×2556 o 1206×2622; se escalan a los tamaños menores), iPad 13" (2048×2732 o 2064×2752) si la app corre en iPad, y el nuevo iPhone Duo (1398×2034 o 2007×2853) -- obligatorio desde abril 2027 para apps compiladas con el SDK de iOS 27.1 o superior. Las capturas deben mostrar la app en uso (Guideline 2.3.3), sin marcos engañosos. Se suben a la Asset Library y de ahí se asignan a la versión. Si App Store Connect rechaza una medida, guíate por su mensaje, no por esta lista.',
98
+ },
99
+
100
+ // ── App Review ──
101
+ {
102
+ key: 'review_info', section: 'App Review',
103
+ title: 'App Review Information (sign-in, contacto, notas)',
104
+ body: 'Si la app pide login: usuario y contraseña de una cuenta demo dedicada, ya poblada con datos de ejemplo para que el revisor vea todo sin crear nada. Contacto: nombre, teléfono con código de país y correo de quien responde a Apple. Notes: cómo navegar lo que no es obvio y, si hay funciones que dependen de algo externo (hardware, cuenta de empresa, compras en la web), explicarlo sin precios. Anota aquí DÓNDE vive la contraseña (gestor de contraseñas), no la contraseña en sí.',
105
+ },
106
+ {
107
+ key: 'release_option', section: 'App Review',
108
+ title: 'App Store Version Release',
109
+ body: 'Manual (tú presionas publicar después de la aprobación), automático apenas se aprueba, o automático no antes de una fecha. Para una 1.0 con campaña de lanzamiento, manual o con fecha evita que salga antes de que estén listos la landing y el contenido.',
110
+ },
111
+
112
+ // ── Trust & Safety ──
113
+ {
114
+ key: 'privacy_choices_url', section: 'Trust & Safety',
115
+ title: 'Privacy Policy URL + User Privacy Choices URL (opcional)',
116
+ body: 'Privacy Policy URL es obligatoria (pública, responde 200). Junto a ella, App Store Connect deja un campo opcional de "elecciones de privacidad" (ej. opt-out de venta/uso de datos con fines publicitarios, al estilo CCPA). Solo aplica si la app realmente vende/comparte datos con terceros para publicidad -- para una app sin SDKs de publicidad ni venta de datos, se deja en blanco.',
72
117
  },
73
118
  {
74
- key: 'data_collection',
119
+ key: 'data_collection', section: 'Trust & Safety',
75
120
  title: 'Data Collection (App Privacy)',
76
- body: 'Apple pide declarar, por cada tipo de dato que la app recolecta, para qué se usa (App Functionality / Analytics / Advertising / etc.), si está vinculado a la identidad del usuario, y si se usa para "tracking" (definición estricta de Apple: compartir con OTRAS empresas para publicidad/medición cross-app, no cualquier analítica interna). Para una app típica de Orquestra (Firebase Auth + Firestore + Analytics + Crashlytics, sin ads): declara Name/Email/User ID/User Content como vinculados a identidad y con propósito "App Functionality"; Device ID y Product Interaction como Analytics (vinculados a identidad SOLO si el código llama a Analytics.setUserID/Crashlytics.setUserID con el uid real -- revisar caso por caso); nada se usa para tracking si no hay SDKs de publicidad/ATT.',
121
+ body: 'Por cada tipo de dato que la app recolecta: propósito (App Functionality / Analytics / Advertising / etc.), si está vinculado a la identidad y si se usa para "tracking" (definición estricta de Apple: compartir con OTRAS empresas para publicidad/medición cross-app, no cualquier analítica interna). Para una app con Firebase Auth + Firestore + Analytics + Crashlytics, sin ads: Name / Email / User ID / User Content (Contact Info, Identifiers, User Content) con App Functionality; Crash Data (Diagnostics) si hay Crashlytics -- se olvida seguido; Product Interaction (Usage Data) con Analytics; Device ID según su uso real (push/sesión = App Functionality, solo medición = Analytics). Vinculados a identidad SOLO si el código llama a Analytics.setUserID/Crashlytics.setUserID con el uid real. Nada es tracking si no hay SDKs de publicidad/ATT -- ojo con dependencias transitivas (ej. conversiones de Google Ads dentro de GoogleAppMeasurement) aunque el código propio no las llame.',
77
122
  },
78
123
  {
79
- key: 'delete_account',
124
+ key: 'accessibility', section: 'Trust & Safety',
125
+ title: 'App Accessibility (Accessibility Nutrition Labels)',
126
+ body: 'Sección nueva: declaras qué funciones de accesibilidad soporta la app en sus tareas comunes, y se muestran en la página de producto. Son 9: VoiceOver, Voice Control, Larger Text (Dynamic Type hasta 200%), Dark Interface, Differentiate Without Color Alone, Sufficient Contrast, Reduced Motion, Captions y Audio Descriptions (estas dos solo si hay video/audio). Declara solo lo que funciona de verdad en TODAS las tareas comunes (Apple publica criterios por función) -- revísalo en el código (ej. fuentes con Dynamic Type, colores con variante oscura, animaciones que respetan Reduce Motion) y en un iPhone físico con cada ajuste prendido. Es opcional, pero vacío se ve como "el desarrollador no ha indicado".',
127
+ },
128
+ {
129
+ key: 'delete_account', section: 'Trust & Safety',
80
130
  title: 'Eliminar cuenta desde la app (Guideline 5.1.1(v))',
81
131
  body: 'Obligatorio desde 2022: si la app permite crear cuenta, tiene que permitir ELIMINARLA desde DENTRO de la app -- "escríbenos por correo" no cumple el requisito, aunque sea rápido y real. Verifica que exista un flujo de borrado self-service (botón "Eliminar cuenta" en Perfil/Ajustes, con confirmación) ANTES de enviar a revisión -- si no existe, Apple puede rechazar la app durante el review, incluso si el resto de la metadata está perfecta. Orquestra ya tiene este flujo (Zona de riesgo en Perfil, backend server-side que borra cuenta+datos), úsalo de referencia de implementación si tu app lo necesita desde cero.',
82
132
  },
83
133
  {
84
- key: 'sign_in_apple',
134
+ key: 'sign_in_apple', section: 'Trust & Safety',
85
135
  title: 'Sign in with Apple si hay login de terceros (Guideline 4.8)',
86
136
  body: 'Si la app ofrece login con Google, Facebook u otro proveedor de terceros, Apple exige que TAMBIÉN ofrezcas "Sign in with Apple" como alternativa equivalente -- Guideline 4.8 ("Login Services"). No es una recomendación: si falta, Apple RECHAZA el build completo en revisión (motivo real, no hipotético) sin importar que el resto de la metadata esté perfecta. Implementación de referencia: botón nativo (`AuthenticationServices`, nonce) + capability "Sign in with Apple" habilitada en el App ID (Apple Developer Portal) + provider "Apple" activado en Firebase Auth si se usa Firebase. Si el login es solo email/password propio, sin ningún proveedor de terceros, esta regla no aplica.',
87
137
  },
138
+
139
+ // ── Cumplimiento ──
140
+ {
141
+ key: 'encryption', section: 'Cumplimiento',
142
+ title: 'App Encryption (export compliance)',
143
+ body: 'Agrega `ITSAppUsesNonExemptEncryption` al Info.plist: `NO` si la app solo usa el cifrado del sistema (HTTPS/TLS, Keychain, CryptoKit para lo estándar) -- es el caso de casi todas las apps. Sin la clave, App Store Connect pregunta en CADA build subido y el build queda trabado en "Missing Compliance" hasta que contestes. `YES` solo con cifrado propio o no estándar, y ahí Apple pide documentación.',
144
+ },
145
+ {
146
+ key: 'regulations', section: 'Cumplimiento',
147
+ title: 'App Store Regulations & Permits (DSA y otros)',
148
+ body: 'Digital Services Act (UE): declarar si eres "trader" (comerciante). Si la app genera ingresos o la publica una empresa, casi seguro eres trader: Apple pide dirección, teléfono y correo verificados y los MUESTRA en la página de producto en la UE. Como "non-trader" Apple puede limitar la distribución en la UE si su revisión no coincide. Los demás (ICP de China continental, licencia de juegos de Vietnam, dispositivo médico regulado) solo aplican si distribuyes en esos mercados o eres de esas categorías.',
149
+ },
150
+ {
151
+ key: 'payments_iap', section: 'Cumplimiento',
152
+ title: 'Pagos dentro de la app (Guideline 3.1.1)',
153
+ body: 'Si algo pagado DESBLOQUEA funcionalidad dentro de la app (planes, créditos, funciones premium), Apple exige venderlo como In-App Purchase en iOS -- aunque se compre en la web y la app no tenga botones de compra (rechazo real de Orquestra por 3.1.1). La excepción 3.1.3(f) solo protege apps gratis cuyo contenido pago se usa FUERA de la app. Para ofrecer IAP: Paid Applications Agreement aceptado por el Account Holder, datos fiscales (en México, RFC/constancia de situación fiscal) y cuenta bancaria en Business → Agreements, y los productos en In-App Purchases/Subscriptions. Sin pagos, anótalo aquí como "No aplica".',
154
+ },
155
+
156
+ // ── Pricing and Availability ──
157
+ {
158
+ key: 'pricing', section: 'Pricing and Availability',
159
+ title: 'App Pricing',
160
+ body: 'Depende del modelo de negocio real del producto -- no asumas que es gratis, esto lo decide cada proyecto. El país base define cómo Apple ajusta el precio en las demás regiones. Tax Category: "App Store software" salvo un caso especial.',
161
+ },
162
+ {
163
+ key: 'availability', section: 'Pricing and Availability',
164
+ title: 'App Availability (países, Mac y Vision Pro)',
165
+ body: '"All Countries or Regions" es el default razonable salvo que haya una restricción regulatoria real específica de la app. Las apps de iPhone/iPad quedan disponibles por default en Macs con Apple Silicon y en Apple Vision Pro -- apágalo si la app no se probó ahí o depende de hardware del iPhone.',
166
+ },
167
+ {
168
+ key: 'distribution_method', section: 'Pricing and Availability',
169
+ title: 'App Distribution Methods (público o privado)',
170
+ body: 'Público (cualquiera la encuentra en App Store, el default) o Privado (custom app solo para organizaciones en Apple Business/School Manager). NO se puede cambiar después de la aprobación -- decídelo antes de la primera revisión. "Reduced price on Apple School Manager" solo importa si la app es de pago.',
171
+ },
172
+
173
+ // ── Crecimiento (después de la aprobación) ──
174
+ {
175
+ key: 'creative_assets', section: 'Crecimiento (después de la aprobación)',
176
+ title: 'Header and Search Results + Asset Library',
177
+ body: 'Nuevo desde oct 2026: piezas creativas (imagen o video) que se suman a las capturas -- Header = banner arriba de la página de producto (21:9, 3840×1646) y Search Results = lo que se ve en los resultados de búsqueda (3:2, de 1920×1280 a 3840×2560); una pieza universal 16:9 de 5244×2950 sirve para las dos. Solo se ven en iOS/iPadOS 27 o superior (en versiones anteriores siguen las capturas). Son piezas de diseño, no capturas de la app. Se suben a la Asset Library (repositorio de capturas, previews y piezas creativas independiente de las versiones), se mandan a revisión ("Add for Review") sin versión nueva y luego se reusan en la versión, en Custom Product Pages, en pruebas de PPO o en In-App Events.',
178
+ },
179
+ {
180
+ key: 'custom_product_pages', section: 'Crecimiento (después de la aprobación)',
181
+ title: 'Custom Product Pages (hasta 70)',
182
+ body: 'Variantes de tu página de producto, cada una con sus capturas, header y promotional text, un link propio (`?ppid=`) para campañas/anuncios, keywords asignadas (de las de la última versión aprobada) para aparecer en búsqueda orgánica en vez de la página default, y un deep link para que "Abrir" lleve a una pantalla específica. Una por público o caso de uso real (ej. "freelancers" vs "equipos chicos"), no una por anuncio. Necesitan una versión ya aprobada.',
183
+ },
184
+ {
185
+ key: 'product_page_optimization', section: 'Crecimiento (después de la aprobación)',
186
+ title: 'Product Page Optimization (pruebas A/B)',
187
+ body: 'Prueba hasta 3 variantes de la página default (ícono, capturas, previews y piezas creativas) contra la original, mostradas al azar a usuarios elegibles y medidas en App Analytics. Solo tiene sentido con tráfico real (cientos de visitas por semana) -- anota aquí la hipótesis antes de crear la prueba (qué cambias y qué esperas que mejore).',
188
+ },
88
189
  ],
89
190
 
191
+ // `optional: true` = no viene seleccionada por default al aplicar (pasos
192
+ // de crecimiento que solo tienen sentido con la app ya aprobada).
90
193
  tasks: [
91
194
  {
92
195
  title: 'Verificar/crear política de privacidad pública (URL)',
@@ -95,43 +198,88 @@ export const PLATFORM_PLAYBOOKS = {
95
198
  },
96
199
  {
97
200
  title: 'Verificar/crear página o contacto de soporte',
98
- description: 'Apple pide una URL de soporte visible para el usuario (puede ser un mailto, un formulario o una página de FAQ).',
201
+ description: 'Apple pide una URL de soporte visible para el usuario (puede ser un mailto, un formulario o una página de FAQ). Probarla en el navegador: tiene que responder 200.',
99
202
  type: 'business', priority: 'high', tags: ['publicacion', 'ios'], offsetDays: 2, checkKey: 'support',
100
203
  },
101
204
  {
102
- title: 'Confirmar pricing y disponibilidad por país',
103
- description: 'Definir el modelo de precio real (gratis, de pago, suscripción) y el alcance de países -- "All Countries or Regions" es el default razonable salvo restricción regulatoria específica.',
104
- type: 'business', priority: 'low', tags: ['publicacion', 'ios'], offsetDays: 3,
205
+ title: 'Confirmar que la app permite eliminar la cuenta desde adentro',
206
+ description: 'Guideline 5.1.1(v), obligatorio -- "escríbenos por correo" NO cumple el requisito. Tiene que haber un botón real de "Eliminar cuenta" (con confirmación) en Perfil/Ajustes que borre la cuenta y los datos sin salir de la app. Si no existe, constrúyelo ANTES de mandar a revisión, no después de un rechazo.',
207
+ type: 'security', priority: 'high', tags: ['publicacion', 'ios', 'compliance'], offsetDays: 1,
208
+ },
209
+ {
210
+ title: 'Verificar que si hay login de terceros (Google/Facebook/etc.), también esté "Sign in with Apple"',
211
+ description: 'Guideline 4.8, obligatorio -- si la app ofrece login con Google, Facebook u otro proveedor externo, Apple rechaza la app en revisión si no ofrece también "Sign in with Apple" como alternativa. No aplica si el login es solo email/password propio, sin proveedores de terceros. Si falta, agrégalo ANTES de mandar a revisión, no después de un rechazo.',
212
+ type: 'security', priority: 'high', tags: ['publicacion', 'ios', 'compliance'], offsetDays: 1,
213
+ },
214
+ {
215
+ title: 'Revisar si algo pagado desbloquea funciones en la app (In-App Purchase, Guideline 3.1.1)',
216
+ description: 'Si planes, créditos o funciones premium cambian lo que se puede hacer DENTRO de la app iOS, Apple exige In-App Purchase aunque se compre en la web. Decidir antes de mandar a revisión: (a) ofrecer IAP -- requiere Paid Applications Agreement, datos fiscales (RFC/constancia de situación fiscal en México) y cuenta bancaria en Business → Agreements --, o (b) que la versión iOS no dependa de nada pagado. Quitar de la app y de la ficha precios, nombres de planes y cualquier invitación a comprar afuera.',
217
+ type: 'business', priority: 'high', tags: ['publicacion', 'ios', 'compliance'], offsetDays: 3,
218
+ },
219
+ {
220
+ title: 'Agregar ITSAppUsesNonExemptEncryption al Info.plist',
221
+ description: 'Export compliance: `NO` si la app solo usa el cifrado del sistema (HTTPS/TLS, Keychain). Sin la clave, cada build subido queda en "Missing Compliance" hasta contestar a mano en App Store Connect.',
222
+ type: 'tech', priority: 'medium', tags: ['publicacion', 'ios', 'compliance'], offsetDays: 3,
223
+ },
224
+ {
225
+ title: 'Declarar el estatus de trader de la Digital Services Act (UE)',
226
+ description: 'App Information → App Store Regulations & Permits. Si la app genera ingresos o la publica una empresa, declararse trader con dirección, teléfono y correo verificados (Apple los muestra en la UE). Decidirlo con la persona dueña de la cuenta, no por default.',
227
+ type: 'business', priority: 'medium', tags: ['publicacion', 'ios', 'compliance'], offsetDays: 3,
228
+ },
229
+ {
230
+ title: 'Confirmar pricing, disponibilidad por país y método de distribución',
231
+ description: 'Definir el modelo de precio real (gratis, de pago, suscripción) y el alcance de países -- "All Countries or Regions" es el default razonable salvo restricción regulatoria específica. Revisar disponibilidad en Mac con Apple Silicon y Vision Pro. Elegir público o privado: el método de distribución NO se puede cambiar después de la aprobación.',
232
+ type: 'business', priority: 'medium', tags: ['publicacion', 'ios'], offsetDays: 3,
105
233
  },
106
234
  {
107
235
  title: 'Preparar cuenta demo para el reviewer, si el login lo requiere',
108
- description: 'Si la app pide login, "App Review Information" necesita un usuario y contraseña de prueba (o notas de acceso) para que el reviewer entre sin fricción.',
236
+ description: 'Si la app pide login, "App Review Information" necesita un usuario y contraseña de una cuenta dedicada, ya poblada con datos de ejemplo, más contacto y notas para el revisor. La contraseña va en App Store Connect y en un gestor de contraseñas, no en la descripción de esta tarea.',
109
237
  type: 'business', priority: 'medium', tags: ['publicacion', 'ios'], offsetDays: 5,
110
238
  },
239
+ {
240
+ title: 'Escribir la ficha de la versión: promotional text, description, keywords, URLs y copyright',
241
+ description: 'Promotional text (170, editable sin revisión), description (4000, beneficios primero y funciones reales, sin precios si no hay IAP), keywords (100, separadas por coma sin espacios, sin repetir nombre/subtítulo), Support URL, Marketing URL y copyright. Respaldo en el formulario de la Guía de publicación.',
242
+ type: 'business', priority: 'medium', tags: ['publicacion', 'ios'], offsetDays: 6,
243
+ },
111
244
  {
112
245
  title: 'Preparar capturas de pantalla en los tamaños exactos que pide App Store Connect',
113
- description: 'Las dimensiones válidas cambian con cada generación de iPhone/iPad -- si App Store Connect rechaza una captura al subirla, el mensaje de error indica el tamaño exacto que espera para ese dispositivo; ajusta según ese mensaje real, no de memoria.',
246
+ description: 'iPhone con Dynamic Island (1179×2556 o 1206×2622), iPad 13" si la app corre en iPad (2048×2732 o 2064×2752) e iPhone Duo (1398×2034 o 2007×2853, obligatorio desde abril 2027 con el SDK de iOS 27.1+). Las dimensiones válidas cambian con cada generación -- si App Store Connect rechaza una captura al subirla, el mensaje de error indica el tamaño exacto que espera para ese dispositivo; ajusta según ese mensaje real, no de memoria.',
114
247
  type: 'business', priority: 'medium', tags: ['publicacion', 'ios'], offsetDays: 7, checklistTool: 'screenshots',
115
248
  },
116
249
  {
117
250
  title: 'Completar el cuestionario de privacidad de la tienda (Data Collection)',
118
- description: 'Por cada tipo de dato que la app recolecta (nombre, email, user ID, contenido del usuario, device ID, product interaction), declarar el propósito (App Functionality / Analytics / etc.), si está vinculado a la identidad y si se usa para tracking (definición estricta de Apple: compartir con otras empresas para publicidad/medición cross-app).',
251
+ description: 'Por cada tipo de dato que la app recolecta (nombre, email, user ID, contenido del usuario, crash data, device ID, product interaction), declarar el propósito (App Functionality / Analytics / etc.), si está vinculado a la identidad y si se usa para tracking (definición estricta de Apple: compartir con otras empresas para publicidad/medición cross-app). Revisar en el código si se llama a setUserID de Analytics/Crashlytics.',
119
252
  type: 'business', priority: 'high', tags: ['publicacion', 'ios'], offsetDays: 9,
120
253
  },
254
+ {
255
+ title: 'Declarar App Accessibility según lo que la app soporta de verdad',
256
+ description: 'App Store → App Accessibility: marcar solo las funciones (VoiceOver, Voice Control, Larger Text, Dark Interface, Differentiate Without Color, Sufficient Contrast, Reduced Motion, Captions, Audio Descriptions) que funcionan en todas las tareas comunes. Probar cada una en un iPhone físico con el ajuste prendido antes de declararla.',
257
+ type: 'business', priority: 'medium', tags: ['publicacion', 'ios', 'accesibilidad'], offsetDays: 9,
258
+ },
121
259
  {
122
260
  title: 'Completar clasificación de edad / Age Rating',
123
- description: 'Recorrer los 7 pasos del cuestionario de Age Ratings en App Store Connect: Features, los 5 descriptores de contenido y Age Categories/Override. Para una app B2B sin ese contenido, el resultado esperado es 4+.',
261
+ description: 'Recorrer los 7 pasos del cuestionario de Age Ratings en App Store Connect: Features, los 5 descriptores de contenido y Age Categories/Override. Para una app B2B sin ese contenido, el resultado esperado es 4+. Revisar las clasificaciones regionales que calcula App Store Connect.',
124
262
  type: 'business', priority: 'medium', tags: ['publicacion', 'ios'], offsetDays: 10,
125
263
  },
126
264
  {
127
- title: 'Confirmar que la app permite eliminar la cuenta desde adentro',
128
- description: 'Guideline 5.1.1(v), obligatorio -- "escríbenos por correo" NO cumple el requisito. Tiene que haber un botón real de "Eliminar cuenta" (con confirmación) en Perfil/Ajustes que borre la cuenta y los datos sin salir de la app. Si no existe, constrúyelo ANTES de mandar a revisión, no después de un rechazo.',
129
- type: 'security', priority: 'high', tags: ['publicacion', 'ios', 'compliance'], offsetDays: 1,
265
+ title: 'Elegir cómo se publica la versión al aprobarse (manual, automático o con fecha)',
266
+ description: 'App Store Version Release. Para una 1.0 con campaña de lanzamiento, manual o con fecha evita que salga antes de que estén listos la landing y el contenido.',
267
+ type: 'business', priority: 'low', tags: ['publicacion', 'ios'], offsetDays: 10,
130
268
  },
131
269
  {
132
- title: 'Verificar que si hay login de terceros (Google/Facebook/etc.), también esté "Sign in with Apple"',
133
- description: 'Guideline 4.8, obligatorio -- si la app ofrece login con Google, Facebook u otro proveedor externo, Apple rechaza la app en revisión si no ofrece también "Sign in with Apple" como alternativa. No aplica si el login es solo email/password propio, sin proveedores de terceros. Si falta, agrégalo ANTES de mandar a revisión, no después de un rechazo.',
134
- type: 'security', priority: 'high', tags: ['publicacion', 'ios', 'compliance'], offsetDays: 1,
270
+ title: 'Preparar las piezas creativas: header y resultados de búsqueda (iOS 27+)',
271
+ description: 'Después de la aprobación. Header 21:9 (3840×1646), Search Results 3:2 (1920×1280 a 3840×2560) o una universal 16:9 (5244×2950). Son piezas de diseño, no capturas. Subirlas a la Asset Library y mandarlas a revisión ("Add for Review"), sin versión nueva.',
272
+ type: 'design', priority: 'low', tags: ['publicacion', 'ios', 'crecimiento'], offsetDays: 21, optional: true,
273
+ },
274
+ {
275
+ title: 'Crear Custom Product Pages por público con sus keywords y deep link',
276
+ description: 'Después de la aprobación. Una página por público o caso de uso real (no una por anuncio), con capturas/header propios, keywords tomadas de la versión aprobada y un deep link a la pantalla relevante. Usar su link `?ppid=` en las campañas de Contenido.',
277
+ type: 'business', priority: 'low', tags: ['publicacion', 'ios', 'crecimiento'], offsetDays: 28, optional: true,
278
+ },
279
+ {
280
+ title: 'Planear una prueba de Product Page Optimization',
281
+ description: 'Cuando haya tráfico real. Hasta 3 variantes de la página default (ícono, capturas o piezas creativas), con la hipótesis escrita antes de crearla y revisión de resultados en App Analytics.',
282
+ type: 'business', priority: 'low', tags: ['publicacion', 'ios', 'crecimiento'], offsetDays: 45, optional: true,
135
283
  },
136
284
  ],
137
285
  },
package/src/server.mjs CHANGED
@@ -110,7 +110,7 @@ Si get_project_context devuelve un proyecto vacío (sin módulos ni tareas), es
110
110
 
111
111
  Al aplicar un playbook con apply_stage_playbook -- sobre todo el de etapa "post" (marketing/contenido) -- nunca asumas herramientas o canales que este proyecto no tiene registrados en Orquestra. Antes de crear tareas de contenido/publicación, llama a list_channels: si viene vacío, la plantilla ya incluye una tarea para conectar canales reales primero -- no la saltees ni generes tareas del tipo "publica en Instagram/LinkedIn/TikTok" para un canal que el proyecto no tiene. Si list_channels SÍ trae canales reales, no te quedes solo con la tarea recordatorio de "programa tu contenido" -- además creá piezas REALES con create_content (un plan de lanzamiento concreto: teaser, anuncio, demo del caso de uso, historia de por qué se construyó, 1-2 testimonios, repartidos entre esos canales) usando siempre un channelId de list_channels, nunca un canal inventado. Mismo criterio para cualquier otra tarea del playbook que dependa de una integración -- si el hueco es real (ej. no hay servicios/distribuciones configurados), la tarea correcta es configurar eso primero, no asumir que ya existe.
112
112
 
113
- Al aplicar apply_platform_playbook (Platform Playbook de publicación iOS/Android) con publishForm: solo completa las claves para las que tengas base real del proyecto (código, get_project_context, servicios/entornos ya configurados) -- mismo criterio que el botón "Completar con IA" de la web: category/content_rights/data_collection/content_rating suelen inferirse bien para una app B2B sin ads/tracking, pero pricing, availability, package/bundle ID, cuentas demo para el reviewer o el nombre exacto de la app NUNCA se inventan -- dejalas fuera de publishForm para que el usuario las complete a mano. Si la tool devuelve publishFormConflict, la distribución ya tenía datos guardados en esas claves y no se escribió nada -- mostrale al usuario el valor actual vs. el propuesto de cada una, y repetí el llamado con confirmReplace:true solo si confirma explícito el reemplazo (no lo asumas ni lo repitas sin esa confirmación).
113
+ Al aplicar apply_platform_playbook (Platform Playbook de publicación iOS/Android) con publishForm: solo completa las claves para las que tengas base real del proyecto (código, get_project_context, servicios/entornos ya configurados) -- mismo criterio que el botón "Completar con IA" de la web: category/content_rights/data_collection/content_rating suelen inferirse bien para una app B2B sin ads/tracking (y en iOS también promotional_text/description/keywords/custom_product_pages a partir de la descripción real del proyecto, y accessibility/encryption SOLO si revisaste el código de la app), pero pricing, availability, distribution_method, release_option, regulations (estatus de trader de la DSA), payments_iap, package/bundle ID, cuentas demo para el reviewer (review_info: nunca escribas una contraseña ahí) o el nombre exacto de la app NUNCA se inventan -- dejalas fuera de publishForm para que el usuario las complete a mano. Si la tool devuelve publishFormConflict, la distribución ya tenía datos guardados en esas claves y no se escribió nada -- mostrale al usuario el valor actual vs. el propuesto de cada una, y repetí el llamado con confirmReplace:true solo si confirma explícito el reemplazo (no lo asumas ni lo repitas sin esa confirmación).
114
114
 
115
115
  Analítica y anuncios en un sitio web: antes de instalar o tocar Google Analytics 4, Meta Pixel o Tag Manager en el código, llama a get_tracking_ids -- si la distribución web ya tiene el ID guardado (ga4_measurement_id, meta_pixel_id, gtm_container_id), úsalo tal cual; nunca inventes un ID ni se lo vuelvas a pedir al usuario. Si falta y el usuario te lo da (o lo lees literal en el código/consola real), guárdalo con set_tracking_ids para que el resto de agentes y la guía web lo vean. Si ya hay uno distinto guardado, no lo reemplaces sin confirmación explícita (confirmReplace).
116
116
 
@@ -512,7 +512,7 @@ export function createServer() {
512
512
  server.registerTool(
513
513
  'get_platform_playbook',
514
514
  {
515
- description: 'Guía de referencia + checklist accionable para publicar en App Store Connect (iOS) o Google Play Console (Android) -- "Platform Playbook", paralelo a get_stage_playbook pero sobre una PLATAFORMA/distribución concreta, no la etapa del proyecto. No llama a la API -- datos estáticos. "referenceGuide" es texto de referencia (no genera tareas); "tasks" es el checklist accionable (misma forma que get_stage_playbook, se puede convertir en módulo + tareas reales).',
515
+ description: 'Guía de referencia + checklist accionable para publicar en App Store Connect (iOS) o Google Play Console (Android) -- "Platform Playbook", paralelo a get_stage_playbook pero sobre una PLATAFORMA/distribución concreta, no la etapa del proyecto. No llama a la API -- datos estáticos. "referenceGuide" es texto de referencia (no genera tareas); "tasks" es el checklist accionable (misma forma que get_stage_playbook, se puede convertir en módulo + tareas reales). En iOS cada ítem de referenceGuide trae "section" (mismo orden que el menú de App Store Connect) y las tareas con optional:true son de crecimiento después de la aprobación (apply_platform_playbook no las crea salvo includeOptional).',
516
516
  inputSchema: { platform: z.enum(['ios', 'android', 'web']).describe('ios (App Store Connect) | android (Google Play Console) | web (SEO y analítica: Search Console, GA4/Firebase Analytics, sitemap, meta tags, Core Web Vitals)') },
517
517
  },
518
518
  async ({ platform }) => textResult(PLATFORM_PLAYBOOKS[platform])
@@ -740,7 +740,9 @@ export function createServer() {
740
740
  const ACTIVITY_FIELDS_SCHEMA = {
741
741
  location: z.string().optional().describe('Plataforma o lugar ("Zoom", "Oficina del cliente") o contexto libre para un seguimiento.'),
742
742
  participants: z.array(z.string()).optional().describe('Nombres libres de quienes participan (cliente, equipo) -- no uids.'),
743
- description: z.string().optional().describe('Agenda o notas previas.'),
743
+ description: z.string().optional().describe('Notas previas.'),
744
+ agendaItems: z.array(z.union([z.string(), z.object({ id: z.string().optional(), text: z.string() })])).optional()
745
+ .describe('Puntos a revisar en la reunión (ej. "Revisar [[ORQ-123]]"). Lista simple, NO checklist. Reemplaza la lista COMPLETA: para agregar uno, lee la actual con list_activities y manda todos (con sus id para conservarlos).'),
744
746
  outcome: z.string().optional().describe('Resultado / minuta corta -- qué se acordó. Llénalo al marcarla hecha.'),
745
747
  nextStep: z.string().optional().describe('Siguiente paso acordado. Si es trabajo real, además crea la tarea con create_task y liga su id en nextStepTaskId.'),
746
748
  nextStepTaskId: z.string().optional().describe('id de la tarea creada desde nextStep.'),
@@ -2636,7 +2638,7 @@ export function createServer() {
2636
2638
  registerRead('list_agents', '/v1/agents',
2637
2639
  'Lista los agentes (kind+máquina+stack+ruta local) configurados en un proyecto. Úsala antes de create_agent o de create_agent_run con stack, para saber si ya existe uno.',
2638
2640
  { kind: z.enum(AGENT_KINDS).optional().describe('Filtra por kind: "qa" (lint/test/build/E2E), "security" (npm audit + gitleaks), "performance" (build size + Lighthouse) o "screenshots" (captura de pantallas para el checklist de publicación, solo ios/android)') },
2639
- (args, projectId) => ({ projectId }))
2641
+ (args, projectId) => ({ projectId, ...(args.kind ? { kind: args.kind } : {}) }))
2640
2642
 
2641
2643
  registerCreate('create_agent', '/v1/agents', 'Crea un agente: asocia un kind ("qa" | "security" | "performance" | "screenshots") a una máquina (runner, ver Workspace → Máquinas), un stack de este proyecto y una ruta de checkout en ESA máquina. kind="performance" además necesita perfUrl.', {
2642
2644
  name: z.string().describe('Nombre a mostrar, ej. "Agente web"'),
@@ -2820,9 +2822,10 @@ export function createServer() {
2820
2822
  publishForm: z.record(z.string()).optional().describe('Respuestas a completar, clave = key de referenceGuide (ver get_platform_playbook). Omitido o vacío = solo crea módulo + tareas, sin tocar el formulario.'),
2821
2823
  confirmReplace: z.boolean().optional().describe('Default false. Mandá true solo cuando el usuario ya confirmó explícitamente reemplazar valores existentes que te devolvió publishFormConflict en un llamado anterior.'),
2822
2824
  force: z.boolean().optional().describe('Default false. Si este playbook ya se aplicó antes sobre esta distribución, por default la tool NO crea un segundo módulo/checklist -- mandá true solo cuando el usuario pidió explícitamente crear uno nuevo encima del que ya existe.'),
2825
+ includeOptional: z.boolean().optional().describe('Default false. Las tareas marcadas optional:true en get_platform_playbook (crecimiento después de la aprobación: piezas creativas, Custom Product Pages, Product Page Optimization) no se crean salvo que mandes true -- igual que en la web, donde vienen desmarcadas.'),
2823
2826
  },
2824
2827
  },
2825
- async ({ projectId, platform, distributionId, moduleName, publishForm, confirmReplace, force }) => {
2828
+ async ({ projectId, platform, distributionId, moduleName, publishForm, confirmReplace, force, includeOptional }) => {
2826
2829
  try {
2827
2830
  const pid = resolveProjectId(projectId)
2828
2831
  const playbook = PLATFORM_PLAYBOOKS[platform]
@@ -2846,6 +2849,7 @@ export function createServer() {
2846
2849
 
2847
2850
  const taskIds = []
2848
2851
  for (const t of playbook.tasks) {
2852
+ if (t.optional && !includeOptional) continue
2849
2853
  const dueDate = new Date(Date.now() + t.offsetDays * 86400000).toISOString().slice(0, 10)
2850
2854
  const created = await apiPost(config, '/v1/tasks', {
2851
2855
  projectId: pid,