# ANÁLISIS DETALLADO - UX MOBILE: Animaciones + Modo Offline --- id: ANALISIS-DET-UX-MOBILE-001 type: DetailedAnalysis status: Approved created_date: 2026-01-12 updated_date: 2026-01-12 phase: 2-detallado simco_version: "4.0.0" --- ## FASE 2: ANÁLISIS DETALLADO DE CAMBIOS ### 2.1 Archivos Nuevos - Análisis Completo --- #### 2.1.1 `src/hooks/useAnimations.ts` **Propósito:** Colección de hooks reutilizables para animaciones con react-native-reanimated **Líneas de Código:** 187 **Dependencias Externas:** react-native-reanimated **Exports:** | Export | Tipo | Parámetros | Retorno | Uso | |--------|------|------------|---------|-----| | `useFadeIn` | Hook | `delay?: number` | `{ animatedStyle, opacity }` | Fade in de 0 a 1 | | `useSlideIn` | Hook | `delay?: number, distance?: number` | `{ animatedStyle, opacity, translateY }` | Slide desde abajo | | `useSlideFromRight` | Hook | `delay?: number, distance?: number` | `{ animatedStyle, opacity, translateX }` | Slide desde derecha | | `usePressScale` | Hook | `pressedScale?: number` | `{ animatedStyle, onPressIn, onPressOut, scale }` | Efecto press en botones | | `useListItemAnimation` | Hook | `index: number, baseDelay?: number` | Return de useSlideIn | Animación stagger en listas | | `useShimmer` | Hook | - | `{ animatedStyle, shimmerValue }` | Efecto shimmer para skeletons | | `usePulse` | Hook | `minScale?: number, maxScale?: number` | `{ animatedStyle, scale }` | Animación de pulso | | `useToggleAnimation` | Hook | `isVisible: boolean` | `{ animatedStyle }` | Entrada/salida de elementos | | `Animated` | Re-export | - | - | Re-export de reanimated | **Configuración por Defecto:** ```typescript const DEFAULT_TIMING: WithTimingConfig = { duration: 300, easing: Easing.bezier(0.25, 0.1, 0.25, 1), }; const DEFAULT_SPRING: WithSpringConfig = { damping: 15, stiffness: 150, }; ``` **Consumidores:** - `src/app/(tabs)/index.tsx` (HomeScreen) - `src/app/(tabs)/inventory.tsx` (InventoryScreen) - `src/components/ui/Skeleton.tsx` --- #### 2.1.2 `src/hooks/useNetworkStatus.ts` **Propósito:** Detectar y monitorear estado de conexión de red **Líneas de Código:** 73 **Dependencias Externas:** @react-native-community/netinfo **Exports:** | Export | Tipo | Parámetros | Retorno | Uso | |--------|------|------------|---------|-----| | `useNetworkStatus` | Hook | - | `NetworkStatus` | Estado completo de red | | `useIsOnline` | Hook | - | `boolean` | ¿Está online? | | `useIsOffline` | Hook | - | `boolean` | ¿Está offline? | **Interface NetworkStatus:** ```typescript interface NetworkStatus { isConnected: boolean; isInternetReachable: boolean | null; type: NetInfoStateType; isWifi: boolean; isCellular: boolean; } ``` **Comportamiento:** - Suscripción a eventos de NetInfo en useEffect - Fetch inicial del estado de red - Cleanup automático en unmount - Manejo de `isInternetReachable` null (estado indeterminado) **Consumidores:** - `src/components/ui/OfflineBanner.tsx` --- #### 2.1.3 `src/theme/ThemeContext.tsx` **Propósito:** Sistema de temas con soporte light/dark mode **Líneas de Código:** 77 **Dependencias Externas:** react (useColorScheme) **Exports:** | Export | Tipo | Descripción | |--------|------|-------------| | `ThemeColors` | Interface | Definición de colores del tema | | `Theme` | Interface | `{ colors: ThemeColors, isDark: boolean }` | | `ThemeProvider` | Component | Provider del contexto | | `useTheme` | Hook | Acceso al tema completo | | `useColors` | Hook | Acceso solo a colores | **Paleta Light:** ```typescript const lightColors: ThemeColors = { primary: '#2563eb', // Azul principal primaryLight: '#f0f9ff', // Azul claro background: '#f5f5f5', // Gris fondo card: '#ffffff', // Blanco tarjetas text: '#1a1a1a', // Negro texto textSecondary: '#666666',// Gris texto border: '#e5e5e5', // Gris bordes error: '#ef4444', // Rojo error success: '#22c55e', // Verde éxito warning: '#f59e0b', // Naranja warning }; ``` **Paleta Dark:** ```typescript const darkColors: ThemeColors = { primary: '#3b82f6', primaryLight: '#1e3a5f', background: '#0f0f0f', card: '#1a1a1a', text: '#ffffff', textSecondary: '#a3a3a3', border: '#2d2d2d', error: '#f87171', success: '#4ade80', warning: '#fbbf24', }; ``` **Consumidores:** - `src/app/_layout.tsx` (Provider) - `src/components/ui/Skeleton.tsx` - `src/components/ui/AnimatedList.tsx` - `src/components/skeletons/*.tsx` (4 archivos) --- #### 2.1.4 `src/components/ui/Skeleton.tsx` **Propósito:** Componentes skeleton base con animación shimmer **Líneas de Código:** 216 **Dependencias:** - react-native-reanimated - `../../theme/ThemeContext` **Exports:** | Export | Tipo | Props | Descripción | |--------|------|-------|-------------| | `Skeleton` | Component | `width?, height?, borderRadius?, style?` | Skeleton base | | `SkeletonText` | Component | `width?, height?, style?` | Línea de texto | | `SkeletonCircle` | Component | `size?, style?` | Avatar circular | | `SkeletonImage` | Component | `width?, height?, borderRadius?, style?` | Imagen cuadrada | | `SkeletonCard` | Component | `style?` | Tarjeta completa | | `SkeletonListItem` | Component | `style?` | Item de lista | | `SkeletonStat` | Component | `style?` | Estadística | | `SkeletonList` | Component | `count?, style?` | Lista de skeletons | **Animación Shimmer:** ```typescript shimmerValue.value = withRepeat( withTiming(1, { duration: 1200 }), -1, // Infinito false // Sin reverse ); // Interpolación de opacidad opacity: interpolate(shimmerValue.value, [0, 0.5, 1], [0.3, 0.6, 0.3]) ``` **Consumidores:** - `src/app/(tabs)/index.tsx` (HomeSkeleton) - `src/components/skeletons/InventoryItemSkeleton.tsx` - `src/components/skeletons/StoreCardSkeleton.tsx` - `src/components/skeletons/CreditCardSkeleton.tsx` - `src/components/skeletons/NotificationSkeleton.tsx` --- #### 2.1.5 `src/components/ui/OfflineBanner.tsx` **Propósito:** Banner visual que indica pérdida de conexión **Líneas de Código:** 115 **Dependencias:** - react-native-reanimated - react-native-safe-area-context - `../../hooks/useNetworkStatus` - @expo/vector-icons **Exports:** | Export | Tipo | Props | Descripción | |--------|------|-------|-------------| | `OfflineBanner` | Component | `message?, showIcon?` | Banner offline | | `WithOfflineBanner` | Component | `children` | Wrapper con banner | **Comportamiento:** - Posición absoluta top con z-index 9999 - Animación slide desde arriba con spring - Respeta safe area insets - Solo renderiza cuando `isOffline === true` - Color rojo (#EF4444) para indicar problema **Consumidores:** - `src/app/_layout.tsx` --- #### 2.1.6 `src/components/ui/AnimatedList.tsx` **Propósito:** FlatList con animaciones de entrada staggered **Líneas de Código:** 155 **Dependencias:** - react-native-reanimated - `../../theme/ThemeContext` **Exports:** | Export | Tipo | Descripción | |--------|------|-------------| | `AnimatedList` | Generic Component | FlatList con animaciones | | `useListItemEntering` | Hook | Crear entering animation | | `AnimatedListItem` | Component | Item individual animado | **Props AnimatedList:** ```typescript interface AnimatedListProps { renderItem: (info: { item: T; index: number }) => ReactElement; staggerDelay?: number; // Default: 50ms animationType?: 'fade' | 'slide' | 'spring'; // Default: 'fade' animateOnRefresh?: boolean; // Default: true onRefresh?: () => Promise | void; isRefreshing?: boolean; // ...FlatListProps } ``` **Tipos de Animación:** | Tipo | Animación | |------|-----------| | `fade` | FadeIn con delay | | `slide` | SlideInRight con delay | | `spring` | FadeIn springify | **Consumidores:** - Disponible para uso en cualquier pantalla con listas --- #### 2.1.7-10 Skeletons Específicos **Archivos:** - `src/components/skeletons/InventoryItemSkeleton.tsx` (72 líneas) - `src/components/skeletons/StoreCardSkeleton.tsx` (68 líneas) - `src/components/skeletons/CreditCardSkeleton.tsx` (115 líneas) - `src/components/skeletons/NotificationSkeleton.tsx` (62 líneas) **Exports por Archivo:** | Archivo | Exports | |---------|---------| | InventoryItemSkeleton | `InventoryItemSkeleton`, `InventoryListSkeleton`, `InventoryStatsSkeleton` | | StoreCardSkeleton | `StoreCardSkeleton`, `StoreListSkeleton` | | CreditCardSkeleton | `CreditBalanceSkeleton`, `TransactionSkeleton`, `TransactionListSkeleton`, `CreditPackageSkeleton`, `CreditPackageListSkeleton` | | NotificationSkeleton | `NotificationItemSkeleton`, `NotificationListSkeleton`, `NotificationHeaderSkeleton` | --- ### 2.2 Archivos Modificados - Análisis Completo --- #### 2.2.1 Stores (4 archivos) **Patrón de Modificación Común:** ```typescript // ANTES export const useStore = create()((set, get) => ({ // ...state })); // DESPUÉS import { persist, createJSONStorage } from 'zustand/middleware'; import AsyncStorage from '@react-native-async-storage/async-storage'; export const useStore = create()( persist( (set, get) => ({ // ...state lastFetched: null, // NUEVO campo }), { name: 'miinventario-{store-name}', storage: createJSONStorage(() => AsyncStorage), partialize: (state) => ({ // Solo datos, no funciones }), } ) ); ``` **Detalle por Store:** | Store | Nombre Persistencia | Datos Persistidos | Límite | |-------|---------------------|-------------------|--------| | `stores.store.ts` | `miinventario-stores` | stores, currentStore, total, lastFetched | Sin límite | | `inventory.store.ts` | `miinventario-inventory` | items, total, selectedStoreId, lastFetched | 100 items | | `credits.store.ts` | `miinventario-credits` | balance, transactions, transactionsTotal, lastFetched | 50 tx | | `notifications.store.ts` | `miinventario-notifications` | notifications, unreadCount, total, lastFetched | 50 notif | **Campos Nuevos:** - `lastFetched: number | null` - Timestamp de última carga (para futuro stale-while-revalidate) --- #### 2.2.2 `src/app/_layout.tsx` **Cambios Realizados:** | # | Cambio | Tipo | Impacto | |---|--------|------|---------| | 1 | Import `OfflineBanner` | Nuevo import | - | | 2 | Import `ThemeProvider` | Nuevo import | - | | 3 | Wrap con `ThemeProvider` | Nuevo wrapper | Tema disponible globalmente | | 4 | Agregar `` | Nuevo componente | Banner visible globalmente | | 5 | `animation: 'slide_from_right'` | Nueva opción | Transiciones mejoradas | | 6 | `animationDuration: 250` | Nueva opción | Duración consistente | **Jerarquía de Providers:** ``` GestureHandlerRootView └── ThemeProvider ← NUEVO └── SafeAreaProvider └── QueryClientProvider └── View ├── OfflineBanner ← NUEVO ├── StatusBar └── Stack (con animación) ← MODIFICADO ``` --- #### 2.2.3 `src/app/(tabs)/index.tsx` **Cambios Realizados:** | # | Cambio | Descripción | |---|--------|-------------| | 1 | Imports reanimated | `FadeIn, FadeInDown, FadeInRight, Layout` | | 2 | Import hooks | `useFadeIn, usePressScale` | | 3 | Import skeletons | `Skeleton, SkeletonText, SkeletonStat` | | 4 | `AnimatedTouchable` | `Animated.createAnimatedComponent(TouchableOpacity)` | | 5 | `ActionCard` component | Nuevo componente con animación | | 6 | `StatCard` component | Nuevo componente con animación | | 7 | `HomeSkeleton` component | Skeleton para carga inicial | | 8 | Estado `initialLoad` | Control de skeleton vs contenido | | 9 | Animaciones en header | `FadeIn.duration(400)` | | 10 | Animaciones en cards | `FadeInDown`, `FadeInRight` con delays | **Componentes Nuevos Internos:** ```typescript function ActionCard({ icon, title, description, onPress, index }) { const { animatedStyle, onPressIn, onPressOut } = usePressScale(0.98); return ( ... ); } function StatCard({ value, label, index }) { return ( ... ); } function HomeSkeleton() { return ( ... ); } ``` --- #### 2.2.4 `src/app/(tabs)/inventory.tsx` **Cambios Realizados:** | # | Cambio | Descripción | |---|--------|-------------| | 1 | Imports reanimated | `FadeIn, FadeInDown, FadeInRight, FadeOut, Layout` | | 2 | Import hooks | `usePressScale` | | 3 | Import skeletons | `InventoryListSkeleton` | | 4 | `AnimatedTouchable` | Para items de lista | | 5 | `InventoryItemCard` component | Nuevo componente con animación | | 6 | Estado `initialLoad` | Control de skeleton vs lista | | 7 | Animaciones en header | `FadeIn`, `FadeInDown` | | 8 | Animaciones en items | `FadeInRight` con delay | | 9 | Animación de salida | `FadeOut` al eliminar | | 10 | `Layout.springify()` | Animación de reordenamiento | **Componente InventoryItemCard:** ```typescript function InventoryItemCard({ item, index }) { const { animatedStyle, onPressIn, onPressOut } = usePressScale(0.98); return ( {/* Item content */} ); } ``` --- ### 2.3 Resumen de Líneas de Código | Categoría | Archivos | Líneas Nuevas | Líneas Modificadas | |-----------|----------|---------------|-------------------| | Hooks | 2 | 260 | 0 | | Theme | 1 | 77 | 0 | | Components/UI | 3 | 486 | 0 | | Components/Skeletons | 4 | 317 | 0 | | Stores | 4 | 0 | ~80 (20 por archivo) | | Screens | 3 | 0 | ~200 | **Total:** ~1,140 líneas nuevas + ~280 líneas modificadas --- ### 2.4 Validación de Exports/Imports #### Grafo de Dependencias Internas: ``` ThemeContext.tsx ↓ ┌───┴────────────────────────────────────────┐ │ │ Skeleton.tsx ← AnimatedList.tsx │ ↓ │ ┌───┴───────────────────────┐ │ │ │ │ │ │ │ │ Inventory Store Credit Notification │ │ Skeleton Skeleton Skeleton Skeleton │ └───────────────────────────────────────────┘ useNetworkStatus.ts ↓ OfflineBanner.tsx ↓ _layout.tsx useAnimations.ts ↓ ┌───┴───────────────┐ │ │ (tabs)/index.tsx (tabs)/inventory.tsx ``` #### Validación de Imports: | Archivo | Imports | ¿Válido? | |---------|---------|----------| | `useAnimations.ts` | react, react-native-reanimated | ✅ | | `useNetworkStatus.ts` | react, @react-native-community/netinfo | ✅ | | `ThemeContext.tsx` | react, react-native | ✅ | | `Skeleton.tsx` | react, react-native, react-native-reanimated, ThemeContext | ✅ | | `OfflineBanner.tsx` | react, react-native, reanimated, safe-area, useNetworkStatus, icons | ✅ | | `AnimatedList.tsx` | react, react-native, reanimated, ThemeContext | ✅ | | `_layout.tsx` | expo-router, react-query, etc, OfflineBanner, ThemeContext | ✅ | --- ### 2.5 Conclusión Fase 2 **Estado:** ✅ Análisis Detallado Completado **Hallazgos:** 1. Todos los archivos nuevos siguen patrones consistentes 2. No hay dependencias circulares 3. Todos los imports son válidos 4. Los stores mantienen compatibilidad hacia atrás 5. Las pantallas mantienen funcionalidad existente + mejoras **Siguiente Fase:** Planeación de documentación basada en este análisis --- **Última Actualización:** 2026-01-12 **Responsable:** Claude Opus 4.5