1

Construyendo apps React Native que escalan: 7 puntos dolorosos que resolver antes de que sea tarde

Tu app React Native funciona perfectamente con 3 desarrolladores y 20 pantallas. Pero ¿qué pasa cuando necesitas escalar? Aprende los puntos dolorosos que sorprenden a los equipos y cómo abordarlos proactivamente.

Del prototipo a producción a escala

Tu app React Native funciona perfectamente... hasta que no.

Todos los equipos con los que he trabajado empiezan con la misma suposición: "Refactorizaremos cuando sea necesario." Pero escalar no es un interruptor que activas—es un proceso gradual que te sorprende. Un día estás entregando features rápidamente. Al siguiente, estás depurando problemas de manejo de estado a las 2 AM, preguntándote cómo todo se volvió tan complicado.

La verdad? La mayoría de los problemas de escalado son predecibles. Siguen patrones que puedes detectar temprano y abordar antes de que se conviertan en emergencias.


🎯 Qué cubriremos

Este artículo revisa 7 puntos dolorosos críticos que emergen cuando las apps React Native crecen:

  • Cuándo aparece típicamente cada problema
  • Por qué sucede
  • Soluciones prácticas que puedes implementar hoy

Estos no son problemas teóricos—son desafíos reales que he visto descarrilar apps en producción. Abórdalos proactivamente y mantendrás a tu equipo productivo y tu app mantenible.


1️⃣ Caos en el manejo de estado

🔥 Cuándo golpea

Lo notarás alrededor de 20+ pantallas con múltiples desarrolladores trabajando en paralelo. De repente, pasar props a través de 5 componentes se siente mal, pero cada solución de manejo de estado parece excesiva.

📖 El problema

Lo que empieza simple se vuelve complicado rápido:

  • Múltiples soluciones de estado: Redux para auth, Context para theme, Zustand para cart, estado local por todas partes
  • Prop drilling: Pasando callbacks y datos a través de 7 capas de componentes
  • Flujo de datos poco claro: Nadie sabe dónde vive el estado o cómo actualizarlo
  • Pesadillas de re-render: Un pequeño cambio de estado dispara re-renders en toda la app

El impacto:

  • Depurar se vuelve un juego de adivinanzas
  • Onboarding de nuevos desarrolladores toma semanas en vez de días
  • El rendimiento se degrada por re-renders innecesarios
  • Las features tardan más porque nadie sabe dónde poner el estado

🔧 La solución

Consolida en un solo enfoque de manejo de estado. Aquí hay una estrategia que funciona:

Opción 1: Zustand para estado global (recomendado para la mayoría de equipos)

Zustand te da poder similar a Redux con mínimo boilerplate:

// stores/authStore.ts
import create from 'zustand';
import { persist } from 'zustand/middleware';

interface AuthState {
  user: User | null;
  token: string | null;
  login: (user: User, token: string) => void;
  logout: () => void;
}

export const useAuthStore = create<AuthState>()(
  persist(
    (set) => ({
      user: null,
      token: null,
      login: (user, token) => set({ user, token }),
      logout: () => set({ user: null, token: null }),
    }),
    { name: 'auth-storage' }
  )
);

// Uso en componentes
const ProfileScreen = () => {
  const { user, logout } = useAuthStore();
  // El componente re-renderiza automáticamente solo cuando user o logout cambian
  return <View>...</View>;
};

Por qué Zustand funciona:

  • ✅ Boilerplate mínimo—crea un store en 5 líneas
  • ✅ Soporte TypeScript out of the box
  • ✅ Selectores incorporados previenen re-renders innecesarios
  • ✅ Middleware persist para fácil almacenamiento local

💡 Consejo pro: Usa Zustand para estado global (auth, theme, settings), estado React para estado local del componente, y Context solo para preocupaciones verdaderamente app-wide (como proveedores de tema).

Opción 2: React Query + estado local para estado de servidor

Para apps con muchas llamadas API, React Query maneja el estado del servidor mientras el estado local maneja la UI:

import { useQuery, useMutation } from '@tanstack/react-query';

// React Query maneja el estado del servidor
const useProducts = () => {
  return useQuery({
    queryKey: ['products'],
    queryFn: () => api.getProducts(),
    staleTime: 5 * 60 * 1000, // Cache por 5 minutos
  });
};

// Estado local para preocupaciones solo UI
const ProductList = () => {
  const [filter, setFilter] = useState('');
  const { data: products, isLoading } = useProducts();
  
  const filtered = products?.filter(p => p.name.includes(filter));
  
  return <View>...</View>;
};

💬 Comentario: React Query elimina la necesidad de almacenar datos del servidor en estado global. Maneja caché, refetch y sincronización automáticamente.

✅ Plan de acción

  1. Semana 1: Audita tu manejo de estado actual. Lista cada lugar donde almacenas estado (Redux, Context, estado local, etc.)
  2. Semana 2: Elige un enfoque para estado global (Zustand recomendado). Migra una feature como prueba de concepto.
  3. Mes 1: Gradualmente migra el resto. Establece convenciones del equipo para cuándo usar estado global vs local.

2️⃣ Explosión del tamaño del bundle

🔥 Cuándo golpea

Típicamente alrededor de 50+ dependencias o cuando tu bundle de JavaScript excede 2-3MB. Los usuarios se quejan de lentitud en el arranque de la app, y las reviews de App Store mencionan tiempos de carga.

📖 El problema

Cada npm install parece inofensivo, pero se acumulan:

  • Librerías pesadas: Incluyendo sets completos de iconos cuando usas 3 iconos
  • Dependencias duplicadas: Múltiples versiones de la misma librería
  • Sin code splitting: Toda la app se carga al arranque
  • Código no usado: Código muerto de features removidas todavía en el bundle

El impacto:

  • El arranque de la app toma 5+ segundos en dispositivos promedio
  • Métricas pobres en App Store (bajas tasas de finalización de instalación)
  • Mayor uso de memoria
  • Builds más largos

🔧 La solución

1. Audita el tamaño de tu bundle

# Analiza tu bundle
npx react-native-bundle-visualizer

# O usa el analizador incorporado de Metro bundler
npx react-native start --reset-cache

Esto muestra qué dependencias están tomando más espacio.

2. Reemplaza librerías pesadas con alternativas más ligeras

// ❌ No hagas: Importar toda la librería
import { Icon1, Icon2, Icon3 } from '@fortawesome/react-native-fontawesome';

// ✅ Haz: Tree-shake o usa alternativas más ligeras
import Icon from 'react-native-vector-icons/Feather'; // Solo importa lo que usas

Dependencias pesadas comunes y alternativas:

  • Lodash → Importa funciones específicas: import debounce from 'lodash/debounce'
  • Moment.js → date-fns (más pequeño, tree-shakeable)
  • Librerías UI completas → Usa solo lo que necesitas o construye componentes custom

3. Implementa imports dinámicos

React Native soporta imports dinámicos para code splitting:

// Carga lazy de pantallas pesadas
const HeavyFeature = React.lazy(() => import('./HeavyFeature'));

const App = () => {
  return (
    <Suspense fallback={<LoadingScreen />}>
      <HeavyFeature />
    </Suspense>
  );
};

4. Remueve dependencias no usadas

# Encuentra dependencias no usadas
npx depcheck

# Remueve lo que no usas
npm uninstall unused-package

5. Usa Hermes (el motor optimizado de React Native)

Hermes mejora el tiempo de arranque y reduce el uso de memoria:

// android/app/build.gradle
project.ext.react = [
    enableHermes: true
]

💡 Consejo pro: Establece un presupuesto de tamaño de bundle. Si un PR aumenta el tamaño del bundle en más de 50KB, requiere justificación. Herramientas como bundlewatch pueden forzar esto en CI.

✅ Plan de acción

  1. Esta semana: Ejecuta análisis de bundle. Identifica las 5 dependencias más grandes.
  2. Próxima semana: Reemplaza o remueve los mayores ofensores.
  3. Continuo: Agrega checks de tamaño de bundle a CI/CD. Bloquea PRs que aumenten el tamaño significativamente.

3️⃣ Complejidad de navegación

🔥 Cuándo golpea

Usualmente alrededor de navegación multi-modal, deep linking, o flujos de auth complejos. Los bugs de navegación se vuelven difíciles de reproducir, y el comportamiento del botón atrás se vuelve impredecible.

📖 El problema

React Navigation es poderoso, pero la complejidad crece con tu app:

  • Deep linking se rompe: Los parámetros de URL no coinciden con el estado de navegación
  • Casos edge del botón atrás: El botón atrás de Android va a la pantalla equivocada
  • Problemas de estado de navegación: El stack se corrompe después de ciertos flujos
  • Manejo de modales: Múltiples modales crean confusión en el stack de navegación

El impacto:

  • Los usuarios se pierden en la app
  • Los deep links fallan al abrir las pantallas correctas
  • El botón atrás cierra la app cuando debería ir a la pantalla anterior
  • Los bugs de navegación son difíciles de depurar y reproducir

🔧 La solución

1. Centraliza la configuración de navegación

Crea una única fuente de verdad para la estructura de tu navegación:

// navigation/types.ts
export type RootStackParamList = {
  Home: undefined;
  ProductDetail: { productId: string };
  Checkout: { cartId: string };
  Auth: undefined;
};

// navigation/AppNavigation.tsx
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';

const Stack = createNativeStackNavigator<RootStackParamList>();

export const AppNavigation = () => {
  return (
    <NavigationContainer>
      <Stack.Navigator>
        <Stack.Screen name="Home" component={HomeScreen} />
        <Stack.Screen name="ProductDetail" component={ProductDetailScreen} />
      </Stack.Navigator>
    </NavigationContainer>
  );
};

2. Implementa deep linking correctamente

// navigation/linking.ts
import { LinkingOptions } from '@react-navigation/native';

const linking: LinkingOptions<RootStackParamList> = {
  prefixes: ['myapp://', 'https://myapp.com'],
  config: {
    screens: {
      Home: '',
      ProductDetail: 'product/:productId',
      Checkout: 'checkout/:cartId',
    },
  },
};

// En tu NavigationContainer
<NavigationContainer linking={linking}>
  {/* ... */}
</NavigationContainer>

3. Maneja el botón atrás de Android correctamente

import { BackHandler } from 'react-native';
import { useFocusEffect } from '@react-navigation/native';

const ProductDetailScreen = ({ navigation }) => {
  useFocusEffect(
    React.useCallback(() => {
      const onBackPress = () => {
        // Maneja el botón atrás
        navigation.goBack();
        return true; // Previene comportamiento por defecto
      };

      BackHandler.addEventListener('hardwareBackPress', onBackPress);
      return () => BackHandler.removeEventListener('hardwareBackPress', onBackPress);
    }, [navigation])
  );
};

4. Usa refs de navegación para navegación programática

// navigation/navigationRef.ts
import { createNavigationContainerRef } from '@react-navigation/native';

export const navigationRef = createNavigationContainerRef();

export function navigate(name: string, params?: any) {
  if (navigationRef.isReady()) {
    navigationRef.navigate(name, params);
  }
}

// Usa en cualquier parte de tu app
import { navigate } from './navigation/navigationRef';
navigate('ProductDetail', { productId: '123' });

💬 Comentario: Los refs de navegación te permiten navegar desde fuera de componentes React (como en interceptores de API o manejadores de error) sin prop drilling.

✅ Plan de acción

  1. Semana 1: Documenta tu estructura de navegación. Crea definiciones de tipos para todas las rutas.
  2. Semana 2: Implementa y prueba deep linking para flujos de usuario críticos.
  3. Continuo: Agrega tests de navegación. Usa herramientas como Maestro o Detox para testear flujos de navegación automáticamente.

4️⃣ Infierno de integración de módulos nativos

🔥 Cuándo golpea

Usualmente aparece con múltiples dependencias nativas o cuando necesitas actualizar versiones de React Native. Los builds empiezan a fallar, el linking se rompe, y el código específico de plataforma se vuelve difícil de mantener.

📖 El problema

Los módulos nativos son poderosos pero frágiles:

  • Problemas de linking: Autolinking falla, linking manual es propenso a errores
  • Conflictos de versión: Dependencia nativa A requiere RN 0.72, pero dependencia B requiere RN 0.73
  • Inconsistencias de plataforma: El código funciona en iOS pero se rompe en Android
  • Bloqueadores de actualización: No puedes actualizar React Native porque un módulo nativo no es compatible

El impacto:

  • Fallos de build que toman días en depurar
  • No puedes actualizar React Native (riesgos de seguridad, features faltantes)
  • Bugs específicos de plataforma que solo aparecen en producción
  • La velocidad de desarrollo se ralentiza mientras los problemas nativos consumen tiempo

🔧 La solución

1. Prefiere librerías mantenidas con comunidades activas

Antes de agregar una dependencia nativa, verifica:

  • ✅ Actualizada en los últimos 6 meses
  • ✅ Issues/discusiones activas en GitHub
  • ✅ Compatible con las últimas versiones de React Native
  • ✅ Buena documentación y ejemplos

Busca alternativas:

# Busca alternativas en npm
npm search react-native-[feature]

# Verifica actividad en GitHub
# Busca: estrellas, commits recientes, issues abiertos

2. Usa autolinking (React Native 0.60+)

La mayoría de librerías modernas soportan autolinking. Si una librería requiere linking manual, considera alternativas:

# Verifica qué se está autolinkeando
npx react-native config

# El linking manual debería ser raro
# Si es necesario, usa react-native.config.js

3. Crea un módulo nativo local para funcionalidad custom

En vez de encontrar una librería para todo, a veces un pequeño módulo nativo es mejor:

// ios/MyAppModule.m
#import <React/RCTBridgeModule.h>

@interface RCT_EXTERN_MODULE(MyAppModule, NSObject)
RCT_EXTERN_METHOD(doSomething:(NSString *)param
                  resolver:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject)
@end
// MyAppModule.ts
import { NativeModules } from 'react-native';

const { MyAppModule } = NativeModules;

export const doSomething = async (param: string) => {
  return MyAppModule.doSomething(param);
};

4. Documenta dependencias nativas

Mantén una lista actualizada de dependencias nativas y sus propósitos:

# Dependencias Nativas

- `@react-native-community/netinfo` - Estado de red
- `react-native-keychain` - Almacenamiento seguro
- `@react-native-async-storage/async-storage` - Almacenamiento local

## Notas de Actualización
- NetInfo: Compatible con RN 0.72+
- Keychain: Requiere pod install manual en iOS

5. Prueba en ambas plataformas temprano

# Prueba en ambas plataformas en desarrollo
npx react-native run-ios
npx react-native run-android

# Antes de commitear

💡 Consejo pro: Usa Expo si es posible. Maneja dependencias nativas por ti y reduce dolores de cabeza de integración. Para React Native bare, considera Expo Modules para mejor manejo de dependencias.

✅ Plan de acción

  1. Este mes: Audita dependencias nativas. Remueve las no usadas, documenta las restantes.
  2. Antes de actualizaciones: Verifica compatibilidad de todos los módulos nativos con la versión RN objetivo.
  3. Continuo: Prefiere soluciones solo JavaScript cuando sea posible. Los módulos nativos deberían ser un último recurso.

5️⃣ Degradación de rendimiento

🔥 Cuándo golpea

Usualmente alrededor de listas grandes, pantallas complejas, u operaciones pesadas. Los usuarios reportan animaciones entrecortadas, la app se siente lenta, y ves reviews negativas en App Store sobre rendimiento.

📖 El problema

Los problemas de rendimiento se infiltran gradualmente:

  • Bloqueo del hilo UI: Operaciones JavaScript pesadas bloquean el renderizado
  • Problemas de renderizado de listas: FlatList se vuelve lento con cientos de items
  • Memory leaks: Los componentes no se limpian, el uso de memoria crece con el tiempo
  • Re-renders innecesarios: Los componentes re-renderizan cuando no lo necesitan

El impacto:

  • Pobre experiencia de usuario (animaciones entrecortadas, interacciones lentas)
  • Mayor tasa de crashes por presión de memoria
  • Reviews negativas que lastiman el ranking en app store
  • Usuarios desinstalan la app

🔧 La solución

1. Optimiza el renderizado de FlatList

// ❌ No hagas: Renderizar todo a la vez
<FlatList
  data={items}
  renderItem={({ item }) => <ItemComponent item={item} />}
/>

// ✅ Haz: Usa props de optimización
<FlatList
  data={items}
  renderItem={({ item }) => <ItemComponent item={item} />}
  keyExtractor={(item) => item.id}
  removeClippedSubviews={true}
  maxToRenderPerBatch={10}
  windowSize={10}
  getItemLayout={(data, index) => ({
    length: ITEM_HEIGHT,
    offset: ITEM_HEIGHT * index,
    index,
  })}
  initialNumToRender={10}
/>

2. Mueve operaciones pesadas fuera del hilo UI

// ❌ No hagas: Bloquear hilo UI
const processData = (data) => {
  // Computación pesada bloquea renderizado
  return data.map(complexTransformation);
};

// ✅ Haz: Usa interaction manager o Web Workers
import { InteractionManager } from 'react-native';

const processData = (data) => {
  return new Promise((resolve) => {
    InteractionManager.runAfterInteractions(() => {
      // Se ejecuta después de que las animaciones completen
      const result = data.map(complexTransformation);
      resolve(result);
    });
  });
};

3. Memoiza computaciones costosas

import { useMemo } from 'react';

const ExpensiveComponent = ({ items, filter }) => {
  // ✅ Memoiza items filtrados
  const filteredItems = useMemo(() => {
    return items.filter(item => item.category === filter);
  }, [items, filter]);

  return <FlatList data={filteredItems} />;
};

4. Arregla memory leaks con limpieza apropiada

import { useEffect } from 'react';

const ComponentWithSubscriptions = () => {
  useEffect(() => {
    const subscription = eventEmitter.addListener('event', handleEvent);
    
    // ✅ Siempre limpia
    return () => {
      subscription.remove();
    };
  }, []);

  // También limpia timers, animaciones, etc.
  useEffect(() => {
    const timer = setInterval(() => {
      // Hacer algo
    }, 1000);

    return () => clearInterval(timer);
  }, []);
};

5. Usa React.memo para prevenir re-renders innecesarios

// ✅ Memoiza componentes que re-renderizan frecuentemente
const ProductCard = React.memo(({ product, onPress }) => {
  return (
    <TouchableOpacity onPress={() => onPress(product.id)}>
      <Text>{product.name}</Text>
    </TouchableOpacity>
  );
}, (prevProps, nextProps) => {
  // Solo re-renderiza si product.id o onPress cambian
  return prevProps.product.id === nextProps.product.id &&
         prevProps.onPress === nextProps.onPress;
});

6. Perfila y mide

// Usa React DevTools Profiler en desarrollo
// Usa Flipper Performance Monitor
// Usa Sentry Performance Monitoring en producción

import * as Sentry from '@sentry/react-native';

const transaction = Sentry.startTransaction({
  name: 'LoadProductList',
  op: 'navigation',
});

// ... tu código ...

transaction.finish();

💡 Consejo pro: Establece presupuestos de rendimiento. Si una pantalla toma más de 2 segundos en volverse interactiva, necesita optimización. Usa herramientas como React DevTools Profiler o Flipper para identificar cuellos de botella.

✅ Plan de acción

  1. Esta semana: Perfila tu app. Identifica las pantallas más lentas.
  2. Próximas 2 semanas: Optimiza FlatLists y componentes pesados usando las técnicas arriba.
  3. Continuo: Monitorea métricas de rendimiento en producción. Usa Sentry Performance o herramientas similares.

6️⃣ Cuellos de botella en testing y CI/CD

🔥 Cuándo golpea

Usualmente alrededor de una suite de tests creciente o múltiples plataformas/configuraciones. Los builds toman 15+ minutos, los tests E2E son flaky, y los deployments se vuelven estresantes.

📖 El problema

La infraestructura de testing no escala:

  • Tests E2E flaky: Los tests pasan a veces, fallan otras sin razón
  • Builds CI lentos: Los builds toman 15-30 minutos, bloqueando desarrolladores
  • Brechas en cobertura de tests: Los flujos críticos no están testeados, bugs se escapan
  • Complejidad de plataforma: Testear en iOS, Android, múltiples versiones consume tiempo

El impacto:

  • Ciclos de release lentos (esperas por CI)
  • Baja confianza en releases (tests flaky)
  • Bugs llegan a producción porque los tests no cubren caminos críticos
  • Frustración de desarrolladores con loops de feedback largos

🔧 La solución

1. Escribe tests E2E confiables con waits apropiados

// ❌ No hagas: Usar timeouts arbitrarios
await waitFor(() => {
  expect(element(by.id('button'))).toBeVisible();
}, { timeout: 5000 });

// ✅ Haz: Espera condiciones específicas
await waitFor(async () => {
  await expect(element(by.id('button'))).toBeVisible();
}, {
  timeout: 5000,
  interval: 100,
});

Usa Maestro para tests E2E más confiables:

# e2e/login.flow.yaml
appId: com.myapp
---
- launchApp
- tapOn: "Login"
- inputText: "test@example.com" into: "Email"
- inputText: "password123" into: "Password"
- tapOn: "Submit"
- assertVisible: "Welcome"

Maestro es más resistente a cambios de UI y menos flaky que Detox.

2. Optimiza tiempos de build CI

# .github/workflows/ci.yml
name: CI

on: [pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      # ✅ Cachea dependencias
      - uses: actions/cache@v3
        with:
          path: |
            node_modules
            ~/.gradle/caches
          key: ${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
      
      - name: Install dependencies
        run: npm ci  # Usa ci en vez de install para builds más rápidos y confiables
      
      # ✅ Ejecuta tests en paralelo
      - name: Run tests
        run: npm run test:ci
      
      # ✅ Solo ejecuta E2E en branch main o PRs específicos
      - name: Run E2E
        if: github.ref == 'refs/heads/main' || contains(github.event.pull_request.labels.*.name, 'e2e')
        run: npm run test:e2e

3. Enfoca testing en flujos críticos de usuario

No trates de testear todo. Enfócate en:

  • ✅ Autenticación de usuario
  • ✅ Flujos de negocio críticos (checkout, pagos)
  • ✅ Navegación de app
  • ✅ Manejo de errores

Usa la pirámide de testing:

  • Tests unitarios: Rápidos, muchos, testean funciones individuales
  • Tests de integración: Velocidad media, testean interacciones de componentes
  • Tests E2E: Lentos, pocos, testean journeys críticos de usuario

4. Usa mocks y fixtures de tests

// __mocks__/api.ts
export const api = {
  getUser: jest.fn(() => Promise.resolve({ id: '1', name: 'Test User' })),
  updateUser: jest.fn(() => Promise.resolve()),
};

// Usa en tests
import { api } from '../__mocks__/api';

5. Configura reportes de tests

# Usa un reporter de tests
npm install --save-dev jest-html-reporter

# En jest.config.js
reporters: [
  'default',
  ['jest-html-reporter', {
    outputPath: './test-results.html',
  }],
],

💬 Comentario: No apuntes a 100% de cobertura de tests. Apunta a confianza. Si un test no aumenta tu confianza de que la app funciona, no vale la pena mantenerlo.

✅ Plan de acción

  1. Semana 1: Audita tu suite de tests. Identifica tests flaky y remuévelos o arréglalos.
  2. Semana 2: Configura caché en CI. Optimiza tiempos de build.
  3. Mes 1: Enfoca tests E2E solo en flujos críticos (5-10 tests máximo). Usa Maestro para confiabilidad.
  4. Continuo: Revisa y remueve tests de bajo valor. Mantén la suite rápida y confiable.

7️⃣ Colapso de colaboración en equipo

🔥 Cuándo golpea

Usualmente alrededor de 5+ desarrolladores trabajando en múltiples features en paralelo. Los conflictos de merge aumentan, el code review toma para siempre, y el conocimiento se vuelve silo.

📖 El problema

La coordinación del equipo se descompone:

  • Conflictos de merge: Múltiples personas editando los mismos archivos
  • Patrones inconsistentes: Todos resuelven problemas de manera diferente
  • Silos de conocimiento: Solo una persona sabe cómo funciona X
  • Code reviews lentos: Los reviews toman días, bloqueando trabajo

El impacto:

  • Entrega de features más lenta (esperando reviews)
  • Deuda técnica se acumula (patrones inconsistentes)
  • Burnout (resolución constante de conflictos)
  • Onboarding toma semanas

🔧 La solución

1. Establece patrones de código claros y convenciones

Crea una guía de estilo del equipo:

# Guía de Estilo

## Manejo de Estado
- Usa Zustand para estado global
- Usa estado React para estado local del componente
- Nunca uses Context para datos que cambian frecuentemente

## Estructura de Componentes
```tsx
// 1. Imports (React, RN, third-party, local)
import React from 'react';
import { View, Text } from 'react-native';
import { useAuthStore } from '@/stores';

// 2. Types/Interfaces
interface Props {
  title: string;
}

// 3. Componente
export const MyComponent: React.FC<Props> = ({ title }) => {
  // Lógica del componente
  return <View>...</View>;
};

Convenciones de Nombres

  • Componentes: PascalCase (UserProfile)
  • Archivos: kebab-case (user-profile.tsx)
  • Hooks: camelCase con prefijo "use" (useAuth)

#### 2. Usa checklists de code review

```markdown
# Checklist de PR

- [ ] El código sigue la guía de estilo
- [ ] Sin console.logs o código de debug
- [ ] Los tests pasan localmente
- [ ] Sin errores de TypeScript
- [ ] La descripción del PR explica qué y por qué

3. Documenta decisiones arquitectónicas

# docs/architecture.md

## Por qué Usamos Zustand

Elegimos Zustand sobre Redux porque:
1. Menos boilerplate (desarrollo más rápido)
2. Mejor soporte TypeScript
3. Modelo mental más simple

## Estructura de Archivos

src/
  components/     # Componentes UI reutilizables
  screens/        # Componentes de pantalla
  stores/        # Stores de Zustand
  services/       # API y lógica de negocio
  hooks/          # Custom React hooks

4. Pair programming para features complejas

  • Haz pairing en la primera implementación de un patrón
  • Documenta el patrón para uso futuro
  • Esparce conocimiento a través del equipo

5. Usa herramientas para forzar consistencia

// .eslintrc.js - Fuerza patrones automáticamente
{
  "rules": {
    "no-console": "error",
    "@typescript-eslint/explicit-function-return-type": "warn",
    "import/order": ["error", {
      "groups": ["builtin", "external", "internal", "parent", "sibling"],
    }],
  }
}
// .prettierrc - Formateo consistente
{
  "singleQuote": true,
  "trailingComma": "es5",
  "tabWidth": 2
}

6. Revisiones regulares de arquitectura

Programa reuniones mensuales para:

  • Revisar patrones y decisiones recientes
  • Discutir puntos dolorosos
  • Actualizar guía de estilo basada en aprendizajes

💡 Consejo pro: Automatiza lo que puedas. Usa ESLint, Prettier y pre-commit hooks para forzar patrones automáticamente. Guarda discusiones para decisiones arquitectónicas, no estilo de código.

✅ Plan de acción

  1. Esta semana: Crea una guía de estilo. Documenta patrones actuales.
  2. Próxima semana: Configura ESLint y Prettier para forzar estilo automáticamente.
  3. Mes 1: Realiza primera revisión de arquitectura. Documenta decisiones.
  4. Continuo: Mantén la guía de estilo actualizada. Úsala en code reviews.

🎯 La conclusión

Escalar apps React Native no es sobre agregar más servidores o infraestructura—es sobre mantener calidad de código y velocidad del equipo mientras la complejidad crece.

Los puntos dolorosos que cubrimos son predecibles:

  • Aparecen en etapas específicas de crecimiento
  • Siguen patrones que puedes reconocer temprano
  • Son abordables con planificación proactiva

Los equipos que tienen éxito a escala:

  • ✅ Toman decisiones arquitectónicas temprano (manejo de estado, estrategia de testing)
  • ✅ Automatizan lo que se puede automatizar (linting, formateo, tests)
  • ✅ Documentan patrones y decisiones
  • ✅ Refactorizan continuamente, no solo cuando duele

Los equipos que luchan:

  • ❌ Retrasan decisiones ("refactorizaremos después")
  • ❌ Dejan que la deuda técnica se acumule
  • ❌ Trabajan en silos sin patrones compartidos
  • ❌ Solo optimizan cuando hay una crisis

🚀 Tus próximos pasos

No necesitas abordar los 7 puntos dolorosos hoy. Empieza con los que coinciden con tu etapa actual:

Si estás en 10-20 pantallas con 2-3 desarrolladores:

  1. Consolida manejo de estado (Punto Doloroso #1)
  2. Establece convenciones de equipo (Punto Doloroso #7)
  3. Empieza a monitorear tamaño de bundle (Punto Doloroso #2)

Si estás en 30+ pantallas con 5+ desarrolladores:

  1. Optimiza rendimiento (Punto Doloroso #5)
  2. Arregla complejidad de navegación (Punto Doloroso #3)
  3. Mejora pipeline CI/CD (Punto Doloroso #6)

Si estás escalando más allá de 50 pantallas:

  1. Audita y optimiza todo lo anterior
  2. Considera patrones arquitectónicos (organización basada en features, micro-frontends, etc.)
  3. Invierte en tooling de desarrollador y automatización

Toda app exitosa empezó pequeña. La diferencia entre apps que escalan y apps que se rompen no es el código—son las decisiones que tomas temprano y los patrones que estableces antes de que los problemas se conviertan en emergencias.

Empieza a abordar estos puntos dolorosos hoy, y tu yo futuro te lo agradecerá.


#react-native #desarrollo-móvil #escalado #ingeniería-de-software #mejores-prácticas