La respuesta corta
Cuando un grupo tiene dos marcas y quiere una app para cada una, la respuesta casi nunca es duplicar el proyecto. Es un único código con flavors: en Android se llaman product flavors, en iOS son schemes y build configurations, y Flutter los unifica bajo el mismo concepto.
Cada flavor puede tener su nombre, su icono, su identificador de aplicación, sus assets y sus endpoints. Se compila con --flavor y en tiempo de ejecución se sabe cuál está corriendo. Dos apps en las tiendas, una base de código, un equipo.
Es exactamente lo que hicimos para el grupo Teka: una sola base de código Flutter sirve las apps de catálogo de Teka y Küppersbusch, en iOS y en Android.
Por qué no duplicar el proyecto
La tentación es obvia: copiar el repositorio, cambiar los colores y el logo, y seguir. Funciona durante tres meses. Después:
- Cada corrección hay que aplicarla dos veces, y alguien se olvida de una.
- Las dos copias divergen en dependencias y una se queda atrás en una versión de SDK.
- Un cambio de diseño transversal cuesta el doble, y el doble de QA.
- Cuando entra la tercera marca, el problema se multiplica, no se suma.
Con flavors, el coste marginal de la segunda marca es el de sus assets y su configuración. El de la tercera, también.
Android: product flavors
Se declaran en android/app/build.gradle.kts:
android {
flavorDimensions += "default"
productFlavors {
create("marcaA") {
dimension = "default"
applicationIdSuffix = ".marcaa"
}
create("marcaB") {
dimension = "default"
applicationIdSuffix = ".marcab"
}
}
}
El applicationIdSuffix es lo que permite que las dos convivan instaladas en el mismo dispositivo, que es imprescindible para QA: el equipo tiene que poder comparar las dos sin desinstalar.
Con dos flavors y dos tipos de build salen cuatro variantes: marcaADebug, marcaARelease, marcaBDebug, marcaBRelease.
Nombre e icono por marca
El nombre visible se define por flavor con resValue:
create("marcaA") {
dimension = "default"
resValue(type = "string", name = "app_name", value = "Marca A")
applicationIdSuffix = ".marcaa"
}
Y el manifiesto apunta a esa cadena en lugar de a un literal:
<application android:label="@string/app_name" ... />
Los iconos van en directorios de recursos por flavor:
android/app/src/marcaA/res/mipmap-*/ic_launcher.png
android/app/src/marcaB/res/mipmap-*/ic_launcher.png
Assets por flavor, sin inflar el binario
Desde pubspec.yaml se pueden filtrar assets por flavor, de forma que la app de una marca no arrastra el material gráfico de la otra:
flutter:
assets:
- assets/comunes/
- path: assets/marca-a/
flavors:
- marcaA
- path: assets/marca-b/
flavors:
- marcaB
Es el detalle que separa "una app con dos temas" de "dos apps de verdad": si cada binario pesa lo que pesan los dos catálogos, el argumento de la marca se cae en la primera revisión de tamaño de descarga.
Compilar y ejecutar
flutter run --flavor marcaA
flutter build apk --flavor marcaB
flutter build appbundle --flavor marcaA
Y se puede fijar uno por defecto para el día a día:
flutter:
default-flavor: marcaA
Saber qué marca está corriendo
En tiempo de ejecución, appFlavor da el flavor activo:
import 'package:flutter/services.dart';
void main() {
if (appFlavor == 'marcaA') {
Config.apiUrl = 'https://api.marca-a.com';
} else if (appFlavor == 'marcaB') {
Config.apiUrl = 'https://api.marca-b.com';
}
runApp(const MyApp());
}
Cuidado con el caso nulo: si la compilación no especifica flavor, appFlavor es null. Un if/else if sin rama por defecto deja la app sin configuración y el fallo aparece en el arranque, no al compilar. Conviene fallar de forma ruidosa y explícita en ese caso.
Dos avisos de la documentación
abiFiltersdentro de un product flavor no está recomendado. Va endefaultConfig. Si por lo que sea hay que ponerlo en el flavor, hay que pasar-Pdisable-abi-filtering=trueaflutter buildoflutter run.- iOS no se configura igual. No hay product flavors: hay schemes y build configurations en Xcode, y hay que crearlos y mantenerlos ahí. Es la parte que más se subestima al planificar, porque no se resuelve en un fichero de Gradle.
Lo que hay que decidir antes de escribir el primer flavor
- ¿Identificadores separados o suficijo? El
applicationIdSuffixvale para entornos; para dos marcas de verdad, cada una quiere su identificador propio y su ficha en la tienda. - ¿Una cuenta de desarrollador o dos? Si las marcas son de la misma entidad legal, una basta. Si son sociedades distintas, cada una necesita la suya — con su D-U-N-S y su verificación, como está detallado en la guía de la cuenta de empresa de Apple.
- ¿Qué se comparte y qué no? Backend, analítica, notificaciones y cuentas de usuario: decidir marca a marca. Compartir backend y separar analítica es lo habitual, pero hay que decidirlo, no heredarlo.
- ¿Cómo se distribuye cada una? Si alguna es interna o para un cliente concreto, cambia el canal — están las cinco vías en cómo distribuir una app interna.
Cuándo NO usar flavors
- Si las dos apps van a divergir de verdad en producto, no en marca. Flavors sirve para el mismo producto con distinta piel; en cuanto una marca pide pantallas que la otra no tiene, y luego otras cinco, el código se llena de condicionales y acabas manteniendo dos apps dentro de una. Peor que dos repositorios.
- Si lo que quieres es un modo de prueba. Para entornos —dev, staging, producción— los flavors están bien, pero eso es otra conversación y no exige nada de la parte de marca.
- Si el cliente espera dos equipos. Flavors ahorra código, no organización. Si cada marca tiene su responsable de producto con sus prioridades, hay que decidir quién arbitra la cola.
Cuando encaja, encaja del todo: un grupo con dos marcas de electrodomésticos publicando dos apps de catálogo en iOS y Android desde un único proyecto es exactamente el caso para el que existe esta herramienta. Si tu grupo está en esa situación, es el tipo de decisión de arquitectura que cerramos en la primera fase de un proyecto de Flutter para empresas.
Fuente primaria, consultada el 14 de agosto de 2026: Flavors en la documentación oficial de Flutter.




