- Move 5 non-standard folders to _archive/ - Archive 2 extra root files - Update _MAP.md with standardized structure Standard: SIMCO-ESTANDAR-ORCHESTRATION v1.0.0 Level: CONSUMER (L2) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
16 KiB
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:
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:
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
isInternetReachablenull (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:
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:
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.tsxsrc/components/ui/AnimatedList.tsxsrc/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:
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.tsxsrc/components/skeletons/StoreCardSkeleton.tsxsrc/components/skeletons/CreditCardSkeleton.tsxsrc/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<T> |
Generic Component | FlatList con animaciones |
useListItemEntering |
Hook | Crear entering animation |
AnimatedListItem |
Component | Item individual animado |
Props AnimatedList:
interface AnimatedListProps<T> {
renderItem: (info: { item: T; index: number }) => ReactElement;
staggerDelay?: number; // Default: 50ms
animationType?: 'fade' | 'slide' | 'spring'; // Default: 'fade'
animateOnRefresh?: boolean; // Default: true
onRefresh?: () => Promise<void> | 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:
// ANTES
export const useStore = create<State>()((set, get) => ({
// ...state
}));
// DESPUÉS
import { persist, createJSONStorage } from 'zustand/middleware';
import AsyncStorage from '@react-native-async-storage/async-storage';
export const useStore = create<State>()(
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 <OfflineBanner /> |
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:
function ActionCard({ icon, title, description, onPress, index }) {
const { animatedStyle, onPressIn, onPressOut } = usePressScale(0.98);
return (
<Animated.View entering={FadeInRight.delay(200 + index * 100)}>
<AnimatedTouchable style={[styles.actionCard, animatedStyle]} ...>
...
</AnimatedTouchable>
</Animated.View>
);
}
function StatCard({ value, label, index }) {
return (
<Animated.View entering={FadeInDown.delay(400 + index * 100)}>
...
</Animated.View>
);
}
function HomeSkeleton() {
return (
<View>
<SkeletonText /><Skeleton /><SkeletonStat />...
</View>
);
}
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:
function InventoryItemCard({ item, index }) {
const { animatedStyle, onPressIn, onPressOut } = usePressScale(0.98);
return (
<Animated.View
entering={FadeInRight.delay(Math.min(index * 50, 300)).duration(300)}
exiting={FadeOut.duration(200)}
layout={Layout.springify()}
>
<AnimatedTouchable style={[styles.itemCard, animatedStyle]} ...>
{/* Item content */}
</AnimatedTouchable>
</Animated.View>
);
}
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:
- Todos los archivos nuevos siguen patrones consistentes
- No hay dependencias circulares
- Todos los imports son válidos
- Los stores mantienen compatibilidad hacia atrás
- 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