workspace-v1/orchestration/directivas/simco/SIMCO-MOBILE.md
rckrdmrd 66161b1566 feat: Workspace-v1 complete migration with NEXUS v3.4
Sistema NEXUS v3.4 migrado con:

Estructura principal:
- core/orchestration: Sistema SIMCO + CAPVED (27 directivas, 28 perfiles)
- core/catalog: Catalogo de funcionalidades reutilizables
- shared/knowledge-base: Base de conocimiento compartida
- devtools/scripts: Herramientas de desarrollo
- control-plane/registries: Control de servicios y CI/CD
- orchestration/: Configuracion de orquestacion de agentes

Proyectos incluidos (11):
- gamilit (submodule -> GitHub)
- trading-platform (OrbiquanTIA)
- erp-suite con 5 verticales:
  - erp-core, construccion, vidrio-templado
  - mecanicas-diesel, retail, clinicas
- betting-analytics
- inmobiliaria-analytics
- platform_marketing_content
- pos-micro, erp-basico

Configuracion:
- .gitignore completo para Node.js/Python/Docker
- gamilit como submodule (git@github.com:rckrdmrd/gamilit-workspace.git)
- Sistema de puertos estandarizado (3005-3199)

Generated with NEXUS v3.4 Migration System
EPIC-010: Configuracion Git y Repositorios
2026-01-04 03:37:42 -06:00

21 KiB

SIMCO: OPERACIONES MOBILE (React Native/TypeScript)

Versión: 1.0.0 Fecha: 2025-12-08 Aplica a: Todo agente que trabaje con código móvil React Native Prioridad: OBLIGATORIA para operaciones mobile


RESUMEN EJECUTIVO

Types alineados + Store configurado + Componentes optimizados + Navegación funcional = Mobile completo.


PRINCIPIO FUNDAMENTAL

╔══════════════════════════════════════════════════════════════════════╗
║  MOBILE-FIRST PERFORMANCE                                            ║
║                                                                       ║
║  • Componentes optimizados (memoización)                             ║
║  • Listas virtualizadas (FlatList)                                   ║
║  • Imágenes optimizadas                                              ║
║  • Offline-first cuando aplique                                      ║
║  • Navegación fluida (<16ms por frame)                               ║
╚══════════════════════════════════════════════════════════════════════╝

ESTRUCTURA DE PROYECTO

mobile/
├── src/
│   ├── app/                          # Entry point y configuración
│   │   ├── App.tsx                   # Root component
│   │   ├── providers/
│   │   │   ├── index.tsx             # Provider composer
│   │   │   ├── QueryProvider.tsx     # TanStack Query
│   │   │   └── ThemeProvider.tsx
│   │   └── linking.ts                # Deep linking config
│   ├── screens/                      # Pantallas
│   │   ├── auth/
│   │   │   ├── LoginScreen.tsx
│   │   │   └── RegisterScreen.tsx
│   │   ├── home/
│   │   │   └── HomeScreen.tsx
│   │   └── {module}/
│   │       ├── {Module}Screen.tsx
│   │       └── {Module}DetailScreen.tsx
│   ├── components/                   # Componentes
│   │   ├── common/                   # Compartidos
│   │   │   ├── Button.tsx
│   │   │   ├── Input.tsx
│   │   │   ├── Card.tsx
│   │   │   └── Loading.tsx
│   │   ├── forms/
│   │   │   └── FormField.tsx
│   │   └── {module}/
│   │       └── {Component}.tsx
│   ├── navigation/                   # Navegación
│   │   ├── RootNavigator.tsx         # Navigator principal
│   │   ├── AuthNavigator.tsx
│   │   ├── MainNavigator.tsx
│   │   └── types.ts                  # Navigation types
│   ├── hooks/                        # Hooks personalizados
│   │   ├── useAuth.ts
│   │   ├── useApi.ts
│   │   └── use{Module}.ts
│   ├── services/                     # API services
│   │   ├── api.ts                    # Axios instance
│   │   ├── auth.service.ts
│   │   └── {module}.service.ts
│   ├── stores/                       # State management
│   │   ├── auth.store.ts
│   │   └── {module}.store.ts
│   ├── types/                        # TypeScript types
│   │   ├── index.ts
│   │   ├── api.types.ts
│   │   └── {module}.types.ts
│   ├── utils/                        # Utilidades
│   │   ├── storage.ts                # AsyncStorage helpers
│   │   ├── validation.ts
│   │   └── formatting.ts
│   ├── constants/                    # Constantes
│   │   ├── colors.ts
│   │   ├── spacing.ts
│   │   └── config.ts
│   └── theme/                        # Tema global
│       ├── index.ts
│       ├── colors.ts
│       └── typography.ts
├── android/                          # Proyecto Android nativo
├── ios/                              # Proyecto iOS nativo
├── __tests__/
├── app.json
├── metro.config.js
├── babel.config.js
├── tsconfig.json
└── package.json

CONVENCIONES DE NOMENCLATURA

Archivos

// Screens: PascalCase + Screen suffix
HomeScreen.tsx
ProductDetailScreen.tsx
UserProfileScreen.tsx

// Components: PascalCase
Button.tsx
ProductCard.tsx
UserAvatar.tsx

// Hooks: camelCase con prefijo use
useAuth.ts
useProducts.ts
useNavigation.ts

// Services: camelCase + .service suffix
auth.service.ts
products.service.ts

// Stores: camelCase + .store suffix
auth.store.ts
cart.store.ts

// Types: camelCase + .types suffix
user.types.ts
product.types.ts

Clases y Tipos

// Interfaces: PascalCase con prefijo I (opcional)
interface User {}
interface IAuthState {}

// Types: PascalCase
type NavigationProps = {}
type ThemeColors = {}

// Enums: PascalCase
enum OrderStatus {
  PENDING = 'pending',
  COMPLETED = 'completed',
}

Funciones y Variables

// Componentes: PascalCase
const ProductCard: React.FC<Props> = () => {}

// Funciones: camelCase con verbo
const fetchProducts = async () => {}
const handleSubmit = () => {}
const formatPrice = (price: number) => {}

// Variables: camelCase
const isLoading = true
const productList = []
const currentUser = null

// Constantes: UPPER_SNAKE_CASE
const API_URL = ''
const MAX_ITEMS = 50

TEMPLATES

Screen Component

// screens/{module}/{Module}Screen.tsx
import React, { useCallback, useMemo } from 'react';
import { View, StyleSheet, FlatList, RefreshControl } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { NativeStackScreenProps } from '@react-navigation/native-stack';

import { RootStackParamList } from '@/navigation/types';
import { useProducts } from '@/hooks/useProducts';
import { ProductCard, Loading, EmptyState } from '@/components';
import { Product } from '@/types';

type Props = NativeStackScreenProps<RootStackParamList, 'Products'>;

/**
 * ProductsScreen - Lista de productos
 *
 * Muestra productos con pull-to-refresh y navegación a detalle.
 */
export const ProductsScreen: React.FC<Props> = ({ navigation }) => {
  const { products, isLoading, isRefreshing, refresh } = useProducts();

  const handleProductPress = useCallback((product: Product) => {
    navigation.navigate('ProductDetail', { productId: product.id });
  }, [navigation]);

  const renderItem = useCallback(({ item }: { item: Product }) => (
    <ProductCard
      product={item}
      onPress={() => handleProductPress(item)}
    />
  ), [handleProductPress]);

  const keyExtractor = useCallback((item: Product) => item.id, []);

  const ListEmptyComponent = useMemo(() => (
    <EmptyState
      title="No hay productos"
      message="Intenta de nuevo más tarde"
    />
  ), []);

  if (isLoading && !products.length) {
    return <Loading />;
  }

  return (
    <SafeAreaView style={styles.container} edges={['bottom']}>
      <FlatList
        data={products}
        renderItem={renderItem}
        keyExtractor={keyExtractor}
        contentContainerStyle={styles.list}
        ListEmptyComponent={ListEmptyComponent}
        refreshControl={
          <RefreshControl
            refreshing={isRefreshing}
            onRefresh={refresh}
          />
        }
        // Optimizaciones de rendimiento
        removeClippedSubviews
        maxToRenderPerBatch={10}
        windowSize={5}
        initialNumToRender={10}
        getItemLayout={(_, index) => ({
          length: ITEM_HEIGHT,
          offset: ITEM_HEIGHT * index,
          index,
        })}
      />
    </SafeAreaView>
  );
};

const ITEM_HEIGHT = 120;

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
  },
  list: {
    padding: 16,
    gap: 12,
  },
});

Component (Reusable)

// components/common/Button.tsx
import React, { memo } from 'react';
import {
  TouchableOpacity,
  Text,
  StyleSheet,
  ActivityIndicator,
  ViewStyle,
  TextStyle,
} from 'react-native';
import { colors, spacing } from '@/theme';

interface ButtonProps {
  title: string;
  onPress: () => void;
  variant?: 'primary' | 'secondary' | 'outline';
  size?: 'small' | 'medium' | 'large';
  disabled?: boolean;
  loading?: boolean;
  style?: ViewStyle;
  textStyle?: TextStyle;
}

/**
 * Button - Botón reutilizable
 *
 * @example
 * <Button
 *   title="Submit"
 *   onPress={handleSubmit}
 *   variant="primary"
 *   loading={isSubmitting}
 * />
 */
export const Button = memo<ButtonProps>(({
  title,
  onPress,
  variant = 'primary',
  size = 'medium',
  disabled = false,
  loading = false,
  style,
  textStyle,
}) => {
  const isDisabled = disabled || loading;

  return (
    <TouchableOpacity
      onPress={onPress}
      disabled={isDisabled}
      style={[
        styles.base,
        styles[variant],
        styles[size],
        isDisabled && styles.disabled,
        style,
      ]}
      activeOpacity={0.7}
    >
      {loading ? (
        <ActivityIndicator
          color={variant === 'outline' ? colors.primary : colors.white}
        />
      ) : (
        <Text
          style={[
            styles.text,
            styles[`${variant}Text`],
            styles[`${size}Text`],
            textStyle,
          ]}
        >
          {title}
        </Text>
      )}
    </TouchableOpacity>
  );
});

Button.displayName = 'Button';

const styles = StyleSheet.create({
  base: {
    borderRadius: 8,
    alignItems: 'center',
    justifyContent: 'center',
  },
  primary: {
    backgroundColor: colors.primary,
  },
  secondary: {
    backgroundColor: colors.secondary,
  },
  outline: {
    backgroundColor: 'transparent',
    borderWidth: 1,
    borderColor: colors.primary,
  },
  small: {
    paddingVertical: spacing.xs,
    paddingHorizontal: spacing.sm,
  },
  medium: {
    paddingVertical: spacing.sm,
    paddingHorizontal: spacing.md,
  },
  large: {
    paddingVertical: spacing.md,
    paddingHorizontal: spacing.lg,
  },
  disabled: {
    opacity: 0.5,
  },
  text: {
    fontWeight: '600',
  },
  primaryText: {
    color: colors.white,
  },
  secondaryText: {
    color: colors.white,
  },
  outlineText: {
    color: colors.primary,
  },
  smallText: {
    fontSize: 14,
  },
  mediumText: {
    fontSize: 16,
  },
  largeText: {
    fontSize: 18,
  },
});

Navigation Setup

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

import { useAuthStore } from '@/stores/auth.store';
import { AuthNavigator } from './AuthNavigator';
import { MainNavigator } from './MainNavigator';
import { RootStackParamList } from './types';
import { linking } from '@/app/linking';

const Stack = createNativeStackNavigator<RootStackParamList>();

export const RootNavigator: React.FC = () => {
  const isAuthenticated = useAuthStore((state) => state.isAuthenticated);

  return (
    <NavigationContainer linking={linking}>
      <Stack.Navigator screenOptions={{ headerShown: false }}>
        {isAuthenticated ? (
          <Stack.Screen name="Main" component={MainNavigator} />
        ) : (
          <Stack.Screen name="Auth" component={AuthNavigator} />
        )}
      </Stack.Navigator>
    </NavigationContainer>
  );
};

// navigation/types.ts
import { NativeStackScreenProps } from '@react-navigation/native-stack';

export type RootStackParamList = {
  Auth: undefined;
  Main: undefined;
  ProductDetail: { productId: string };
};

export type RootStackScreenProps<T extends keyof RootStackParamList> =
  NativeStackScreenProps<RootStackParamList, T>;

declare global {
  namespace ReactNavigation {
    interface RootParamList extends RootStackParamList {}
  }
}

Store (Zustand)

// stores/auth.store.ts
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { authService } from '@/services/auth.service';
import { User, LoginDto, RegisterDto } from '@/types';

interface AuthState {
  // State
  token: string | null;
  user: User | null;
  isAuthenticated: boolean;
  isLoading: boolean;
  error: string | null;

  // Actions
  login: (credentials: LoginDto) => Promise<void>;
  register: (data: RegisterDto) => Promise<void>;
  logout: () => void;
  refreshUser: () => Promise<void>;
  clearError: () => void;
}

export const useAuthStore = create<AuthState>()(
  persist(
    (set, get) => ({
      // Initial state
      token: null,
      user: null,
      isAuthenticated: false,
      isLoading: false,
      error: null,

      // Actions
      login: async (credentials) => {
        set({ isLoading: true, error: null });
        try {
          const response = await authService.login(credentials);
          set({
            token: response.token,
            user: response.user,
            isAuthenticated: true,
            isLoading: false,
          });
        } catch (error) {
          set({
            error: error instanceof Error ? error.message : 'Login failed',
            isLoading: false,
          });
          throw error;
        }
      },

      register: async (data) => {
        set({ isLoading: true, error: null });
        try {
          const response = await authService.register(data);
          set({
            token: response.token,
            user: response.user,
            isAuthenticated: true,
            isLoading: false,
          });
        } catch (error) {
          set({
            error: error instanceof Error ? error.message : 'Registration failed',
            isLoading: false,
          });
          throw error;
        }
      },

      logout: () => {
        set({
          token: null,
          user: null,
          isAuthenticated: false,
          error: null,
        });
      },

      refreshUser: async () => {
        const { token } = get();
        if (!token) return;

        try {
          const user = await authService.getProfile();
          set({ user });
        } catch {
          // Token might be expired
          get().logout();
        }
      },

      clearError: () => set({ error: null }),
    }),
    {
      name: 'auth-storage',
      storage: createJSONStorage(() => AsyncStorage),
      partialize: (state) => ({
        token: state.token,
        user: state.user,
        isAuthenticated: state.isAuthenticated,
      }),
    }
  )
);

API Service

// services/api.ts
import axios, { AxiosInstance, AxiosError } from 'axios';
import { useAuthStore } from '@/stores/auth.store';
import { API_URL } from '@/constants/config';

const api: AxiosInstance = axios.create({
  baseURL: API_URL,
  timeout: 10000,
  headers: {
    'Content-Type': 'application/json',
  },
});

// Request interceptor - add auth token
api.interceptors.request.use(
  (config) => {
    const token = useAuthStore.getState().token;
    if (token) {
      config.headers.Authorization = `Bearer ${token}`;
    }
    return config;
  },
  (error) => Promise.reject(error)
);

// Response interceptor - handle errors
api.interceptors.response.use(
  (response) => response,
  (error: AxiosError) => {
    if (error.response?.status === 401) {
      useAuthStore.getState().logout();
    }
    return Promise.reject(error);
  }
);

export default api;

// services/products.service.ts
import api from './api';
import { Product, CreateProductDto } from '@/types';

export const productsService = {
  getAll: async (): Promise<Product[]> => {
    const { data } = await api.get('/products');
    return data;
  },

  getById: async (id: string): Promise<Product> => {
    const { data } = await api.get(`/products/${id}`);
    return data;
  },

  create: async (dto: CreateProductDto): Promise<Product> => {
    const { data } = await api.post('/products', dto);
    return data;
  },

  update: async (id: string, dto: Partial<CreateProductDto>): Promise<Product> => {
    const { data } = await api.put(`/products/${id}`, dto);
    return data;
  },

  delete: async (id: string): Promise<void> => {
    await api.delete(`/products/${id}`);
  },
};

Custom Hook

// hooks/useProducts.ts
import { useCallback, useEffect } from 'react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { productsService } from '@/services/products.service';
import { Product, CreateProductDto } from '@/types';

export const useProducts = () => {
  const queryClient = useQueryClient();

  const {
    data: products = [],
    isLoading,
    isRefetching,
    refetch,
  } = useQuery({
    queryKey: ['products'],
    queryFn: productsService.getAll,
  });

  const createMutation = useMutation({
    mutationFn: productsService.create,
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['products'] });
    },
  });

  const updateMutation = useMutation({
    mutationFn: ({ id, data }: { id: string; data: Partial<CreateProductDto> }) =>
      productsService.update(id, data),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['products'] });
    },
  });

  const deleteMutation = useMutation({
    mutationFn: productsService.delete,
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['products'] });
    },
  });

  const refresh = useCallback(() => {
    refetch();
  }, [refetch]);

  return {
    products,
    isLoading,
    isRefreshing: isRefetching,
    refresh,
    createProduct: createMutation.mutateAsync,
    updateProduct: updateMutation.mutateAsync,
    deleteProduct: deleteMutation.mutateAsync,
    isCreating: createMutation.isPending,
    isUpdating: updateMutation.isPending,
    isDeleting: deleteMutation.isPending,
  };
};

export const useProduct = (id: string) => {
  return useQuery({
    queryKey: ['products', id],
    queryFn: () => productsService.getById(id),
    enabled: !!id,
  });
};

VALIDACIONES OBLIGATORIAS

# 1. TypeScript (OBLIGATORIO)
npx tsc --noEmit
# ✅ Sin errores de tipos

# 2. Lint (OBLIGATORIO)
npm run lint
# ✅ Sin errores

# 3. Tests
npm run test
# ✅ Deben pasar

# 4. iOS (Mac required)
cd ios && pod install && cd ..
npx react-native run-ios
# ✅ Debe compilar e iniciar

# 5. Android
npx react-native run-android
# ✅ Debe compilar e iniciar

# 6. Verificar en dispositivo/emulador
# ✅ Sin errores en consola
# ✅ Navegación funcional
# ✅ API responde
# ✅ Sin memory leaks

OPTIMIZACIONES DE RENDIMIENTO

FlatList Optimizada

<FlatList
  data={items}
  renderItem={renderItem}
  keyExtractor={keyExtractor}
  // Optimizaciones críticas
  removeClippedSubviews={true}
  maxToRenderPerBatch={10}
  windowSize={5}
  initialNumToRender={10}
  getItemLayout={(_, index) => ({
    length: ITEM_HEIGHT,
    offset: ITEM_HEIGHT * index,
    index,
  })}
/>

Memoización

// Componentes con memo
export const ProductCard = memo<ProductCardProps>(({ product, onPress }) => {
  // ...
});

// useCallback para handlers
const handlePress = useCallback(() => {
  onPress(item);
}, [onPress, item]);

// useMemo para cálculos costosos
const sortedProducts = useMemo(() => {
  return [...products].sort((a, b) => a.price - b.price);
}, [products]);

Imágenes Optimizadas

import FastImage from 'react-native-fast-image';

<FastImage
  source={{ uri: imageUrl, priority: FastImage.priority.normal }}
  style={styles.image}
  resizeMode={FastImage.resizeMode.cover}
/>

CHECKLIST MOBILE

SCREEN
├── [ ] SafeAreaView para notches
├── [ ] Manejo de loading state
├── [ ] Manejo de error state
├── [ ] Manejo de empty state
├── [ ] Pull-to-refresh si aplica
├── [ ] Keyboard avoiding si tiene inputs
└── [ ] TypeScript types correctos

COMPONENTE
├── [ ] memo() si recibe props estables
├── [ ] useCallback para handlers
├── [ ] StyleSheet fuera del componente
├── [ ] Props tipadas con interface
└── [ ] displayName para debugging

NAVEGACIÓN
├── [ ] Types para params definidos
├── [ ] Deep linking configurado si aplica
├── [ ] Back button handling
└── [ ] Transiciones fluidas

STORE
├── [ ] Persist configurado si necesario
├── [ ] Actions tipadas
├── [ ] Selectors optimizados
└── [ ] Reset on logout

API
├── [ ] Interceptors de auth
├── [ ] Manejo de 401
├── [ ] Timeout configurado
└── [ ] Error handling

VALIDACIÓN
├── [ ] TypeScript sin errores
├── [ ] Lint sin errores
├── [ ] iOS compila
├── [ ] Android compila
└── [ ] Tests pasan

ERRORES COMUNES

Error Causa Solución
Re-renders excesivos Callbacks inline useCallback, memo
Lista lenta FlatList sin optimizar getItemLayout, windowSize
Memory leak Subscriptions no limpias Cleanup en useEffect
Keyboard cubre input No KeyboardAvoidingView Agregar wrapper
Flash en navigation Estado inicial incorrecto Hydration correcta

REFERENCIAS

  • Crear archivos: @CREAR (SIMCO-CREAR.md)
  • Validar: @VALIDAR (SIMCO-VALIDAR.md)
  • Backend: @BACKEND (SIMCO-BACKEND.md)
  • Perfil Mobile: @PERFILES/PERFIL-MOBILE-AGENT.md

Versión: 1.0.0 | Sistema: SIMCO | Mantenido por: Tech Lead