# React Native — Ejemplos Completos

## React Navigation (proyectos existentes)

```tsx
// navigation/AppNavigator.tsx
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';

const Stack = createNativeStackNavigator<RootStackParamList>();
const Tab = createBottomTabNavigator<TabParamList>();

// Tipos tipados para navegación segura
export type RootStackParamList = {
  Home: undefined;
  DetalleUsuario: { id: string; nombre: string };
  Modal: { contenido: string };
};

// Hook tipado
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
type NavProp = NativeStackNavigationProp<RootStackParamList, 'Home'>;

function HomeScreen() {
  const navigation = useNavigation<NavProp>();
  return (
    <Button onPress={() => navigation.navigate('DetalleUsuario', { id: '1', nombre: 'Ana' })} />
  );
}
```

---

## Redux Toolkit (proyectos con equipo grande)

```typescript
// store/slices/carrito.slice.ts
import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit';

export const obtenerProductos = createAsyncThunk(
  'carrito/obtenerProductos',
  async (categoriaId: string) => {
    const respuesta = await api.get(`/productos?categoria=${categoriaId}`);
    return respuesta.data;
  }
);

const carritoSlice = createSlice({
  name: 'carrito',
  initialState: { items: [], cargando: false } as CarritoState,
  reducers: {
    agregarItem: (state, action: PayloadAction<Producto>) => {
      state.items.push(action.payload);
    },
    eliminarItem: (state, action: PayloadAction<string>) => {
      state.items = state.items.filter(i => i.id !== action.payload);
    },
  },
  extraReducers: (builder) => {
    builder
      .addCase(obtenerProductos.pending, state => { state.cargando = true; })
      .addCase(obtenerProductos.fulfilled, (state, action) => {
        state.cargando = false;
        state.items = action.payload;
      });
  },
});
```

---

## Native Modules — Turbo Module y Expo Modules API

### Turbo Module (Nueva Arquitectura)

```typescript
// NativeModules/BiometricModule.ts
import type { TurboModule } from 'react-native';
import { TurboModuleRegistry } from 'react-native';

export interface Spec extends TurboModule {
  readonly autenticar: (motivo: string) => Promise<boolean>;
  readonly estaDisponible: () => boolean;
}

export default TurboModuleRegistry.getEnforcing<Spec>('BiometricModule');
```

### Expo Modules API (forma más fácil)

```swift
// ios/ExpoFingerprint.swift
import ExpoModulesCore
import LocalAuthentication

public class ExpoFingerprintModule: Module {
  public func definition() -> ModuleDefinition {
    Name("ExpoFingerprint")
    AsyncFunction("autenticar") { (motivo: String) -> Bool in
      let context = LAContext()
      var error: NSError?
      guard context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) else {
        throw Exception(name: "NO_BIOMETRICS", description: error?.localizedDescription ?? "")
      }
      return try await context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: motivo)
    }
  }
}
```

---

## Gesture Handler + Reanimated

```tsx
import { Gesture, GestureDetector } from 'react-native-gesture-handler';

function TarjetaDeslizable() {
  const translateX = useSharedValue(0);

  const gesture = Gesture.Pan()
    .onUpdate(event => {
      translateX.value = event.translationX;
    })
    .onEnd(event => {
      const umbral = 150;
      if (Math.abs(event.translationX) > umbral) {
        translateX.value = withTiming(event.translationX > 0 ? 400 : -400);
      } else {
        translateX.value = withSpring(0);
      }
    });

  const estiloAnimado = useAnimatedStyle(() => ({
    transform: [{ translateX: translateX.value }],
  }));

  return (
    <GestureDetector gesture={gesture}>
      <Animated.View style={[styles.tarjeta, estiloAnimado]} />
    </GestureDetector>
  );
}
```

---

## Styling: NativeWind v4 (Tailwind en React Native)

```tsx
// Con NativeWind, usar className como en web
import { View, Text, Pressable } from 'react-native';

function TarjetaUsuario({ usuario }: Props) {
  return (
    <View className="bg-white rounded-xl p-4 mb-3 shadow-sm border border-slate-200">
      <Text className="text-lg font-semibold text-slate-900 mb-1">
        {usuario.nombre}
      </Text>
      <Text className="text-sm text-slate-500">
        {usuario.email}
      </Text>
      <Pressable className="mt-3 bg-blue-600 rounded-lg py-2 active:bg-blue-700">
        <Text className="text-white text-center font-medium">Ver perfil</Text>
      </Pressable>
    </View>
  );
}
```

---

## Testing — Jest + RNTL y Detox

### Jest + React Native Testing Library

```tsx
// __tests__/TarjetaUsuario.test.tsx
import { render, screen, fireEvent } from '@testing-library/react-native';
import { TarjetaUsuario } from '../components/TarjetaUsuario';

const usuarioMock = { id: '1', nombre: 'Ana López', email: 'ana@ejemplo.com' };

describe('TarjetaUsuario', () => {
  it('muestra el nombre y email del usuario', () => {
    render(<TarjetaUsuario usuario={usuarioMock} onPress={jest.fn()} />);
    expect(screen.getByText('Ana López')).toBeTruthy();
    expect(screen.getByText('ana@ejemplo.com')).toBeTruthy();
  });

  it('llama onPress al presionar', () => {
    const onPress = jest.fn();
    render(<TarjetaUsuario usuario={usuarioMock} onPress={onPress} />);
    fireEvent.press(screen.getByText('Ver perfil'));
    expect(onPress).toHaveBeenCalledWith(usuarioMock.id);
  });
});
```

### Detox para E2E

```javascript
// e2e/login.test.js
describe('Flujo de Login', () => {
  beforeAll(async () => {
    await device.launchApp({ newInstance: true });
  });

  it('permite iniciar sesión con credenciales válidas', async () => {
    await element(by.id('input-email')).typeText('usuario@ejemplo.com');
    await element(by.id('input-password')).typeText('password123');
    await element(by.id('btn-login')).tap();
    await expect(element(by.id('pantalla-home'))).toBeVisible();
  });
});
```
