erp-core/mobile/app/(tabs)/settings.tsx
rckrdmrd 0086695b4c
Some checks failed
ERP Core CI / Backend Lint (push) Has been cancelled
ERP Core CI / Backend Unit Tests (push) Has been cancelled
ERP Core CI / Backend Integration Tests (push) Has been cancelled
ERP Core CI / Frontend Lint (push) Has been cancelled
ERP Core CI / Frontend Unit Tests (push) Has been cancelled
ERP Core CI / Frontend E2E Tests (push) Has been cancelled
ERP Core CI / Database DDL Validation (push) Has been cancelled
ERP Core CI / Backend Build (push) Has been cancelled
ERP Core CI / Frontend Build (push) Has been cancelled
ERP Core CI / CI Success (push) Has been cancelled
Performance Tests / Lighthouse CI (push) Has been cancelled
Performance Tests / Bundle Size Analysis (push) Has been cancelled
Performance Tests / k6 Load Tests (push) Has been cancelled
Performance Tests / Performance Summary (push) Has been cancelled
[SIMCO-V38] feat: Actualizar a SIMCO v3.8.0 + cambios backend
- HERENCIA-SIMCO.md actualizado con directivas v3.7 y v3.8
- Actualizaciones en modulos CRM y OpenAPI

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-10 08:53:05 -06:00

417 lines
11 KiB
TypeScript

/**
* Settings Screen
*
* User settings and app configuration
*/
import { View, Text, StyleSheet, ScrollView, TouchableOpacity, Alert, Switch } from 'react-native';
import { useState, useEffect } from 'react';
import { Ionicons } from '@expo/vector-icons';
import { useAuthStore } from '@/stores';
import { useNotifications, useBiometrics } from '@/hooks';
import { useOfflineStore, syncManager } from '@/services/offline';
interface SettingItemProps {
icon: keyof typeof Ionicons.glyphMap;
title: string;
subtitle?: string;
onPress?: () => void;
trailing?: React.ReactNode;
danger?: boolean;
}
function SettingItem({ icon, title, subtitle, onPress, trailing, danger }: SettingItemProps) {
return (
<TouchableOpacity
style={styles.settingItem}
onPress={onPress}
disabled={!onPress}
activeOpacity={onPress ? 0.6 : 1}
>
<View style={[styles.settingIcon, danger && styles.settingIconDanger]}>
<Ionicons name={icon} size={20} color={danger ? '#dc2626' : '#1e40af'} />
</View>
<View style={styles.settingContent}>
<Text style={[styles.settingTitle, danger && styles.settingTitleDanger]}>{title}</Text>
{subtitle && <Text style={styles.settingSubtitle}>{subtitle}</Text>}
</View>
{trailing || (onPress && <Ionicons name="chevron-forward" size={20} color="#9ca3af" />)}
</TouchableOpacity>
);
}
export default function SettingsScreen() {
const { user, token, logout } = useAuthStore();
const [darkMode, setDarkMode] = useState(false);
// Notifications
const {
isPermissionGranted: notificationsGranted,
requestPermission: requestNotificationPermission,
unreadCount,
} = useNotifications();
// Biometrics
const {
isAvailable: biometricsAvailable,
isEnrolled: biometricsEnrolled,
isEnabled: biometricsEnabled,
biometricTypeName,
enableBiometricLogin,
disableBiometricLogin,
isLoading: biometricsLoading,
} = useBiometrics();
// Offline
const { isOnline, syncQueue } = useOfflineStore();
const pendingSyncCount = syncQueue.filter((s) => s.status === 'pending').length;
const handleNotificationsToggle = async (value: boolean) => {
if (value && !notificationsGranted) {
const granted = await requestNotificationPermission();
if (!granted) {
Alert.alert(
'Permisos Requeridos',
'Necesitamos permiso para enviar notificaciones. Por favor, habilítalas en la configuración del dispositivo.'
);
}
}
};
const handleBiometricsToggle = async (value: boolean) => {
if (value) {
if (!biometricsAvailable) {
Alert.alert('No Disponible', 'Tu dispositivo no soporta autenticación biométrica.');
return;
}
if (!biometricsEnrolled) {
Alert.alert('No Configurado', 'No hay datos biométricos registrados en tu dispositivo.');
return;
}
if (token) {
const success = await enableBiometricLogin(token);
if (!success) {
Alert.alert('Error', 'No se pudo habilitar el inicio de sesión biométrico.');
}
}
} else {
await disableBiometricLogin();
}
};
const handleLogout = () => {
Alert.alert(
'Cerrar Sesión',
'¿Estás seguro que deseas cerrar sesión?',
[
{ text: 'Cancelar', style: 'cancel' },
{
text: 'Cerrar Sesión',
style: 'destructive',
onPress: () => logout(),
},
]
);
};
const handleChangePassword = () => {
Alert.alert('Próximamente', 'Esta función estará disponible pronto');
};
const handleHelp = () => {
Alert.alert('Ayuda', 'Contacta soporte en: soporte@erp-generic.com');
};
const handleSyncNow = () => {
if (!isOnline) {
Alert.alert('Sin Conexión', 'No hay conexión a internet para sincronizar.');
return;
}
if (pendingSyncCount === 0) {
Alert.alert('Todo Sincronizado', 'No hay cambios pendientes por sincronizar.');
return;
}
Alert.alert('Sincronizando', `Sincronizando ${pendingSyncCount} cambios...`);
};
return (
<ScrollView style={styles.container} contentContainerStyle={styles.content}>
{/* Profile Section */}
<View style={styles.section}>
<View style={styles.profileCard}>
<View style={styles.profileAvatar}>
<Text style={styles.profileInitial}>
{user?.fullName?.charAt(0).toUpperCase() || 'U'}
</Text>
</View>
<View style={styles.profileInfo}>
<Text style={styles.profileName}>{user?.fullName || 'Usuario'}</Text>
<Text style={styles.profileEmail}>{user?.email || 'email@ejemplo.com'}</Text>
</View>
</View>
</View>
{/* Account Section */}
<View style={styles.section}>
<Text style={styles.sectionTitle}>Cuenta</Text>
<View style={styles.sectionContent}>
<SettingItem
icon="person-outline"
title="Editar Perfil"
subtitle="Nombre, email, foto"
onPress={() => {}}
/>
<SettingItem
icon="lock-closed-outline"
title="Cambiar Contraseña"
onPress={handleChangePassword}
/>
<SettingItem
icon="business-outline"
title="Empresa"
subtitle={user?.companyId ? 'Demo Company' : 'Sin asignar'}
/>
</View>
</View>
{/* Preferences Section */}
<View style={styles.section}>
<Text style={styles.sectionTitle}>Preferencias</Text>
<View style={styles.sectionContent}>
<SettingItem
icon="notifications-outline"
title="Notificaciones"
subtitle={notificationsGranted ? `${unreadCount} sin leer` : 'No habilitadas'}
trailing={
<Switch
value={notificationsGranted}
onValueChange={handleNotificationsToggle}
trackColor={{ false: '#e5e7eb', true: '#93c5fd' }}
thumbColor={notificationsGranted ? '#1e40af' : '#f4f4f5'}
/>
}
/>
<SettingItem
icon="finger-print-outline"
title={biometricTypeName}
subtitle={
!biometricsAvailable
? 'No disponible'
: !biometricsEnrolled
? 'No configurado'
: biometricsEnabled
? 'Habilitado'
: 'Deshabilitado'
}
trailing={
<Switch
value={biometricsEnabled}
onValueChange={handleBiometricsToggle}
trackColor={{ false: '#e5e7eb', true: '#93c5fd' }}
thumbColor={biometricsEnabled ? '#1e40af' : '#f4f4f5'}
disabled={!biometricsAvailable || !biometricsEnrolled || biometricsLoading}
/>
}
/>
<SettingItem
icon="moon-outline"
title="Modo Oscuro"
trailing={
<Switch
value={darkMode}
onValueChange={setDarkMode}
trackColor={{ false: '#e5e7eb', true: '#93c5fd' }}
thumbColor={darkMode ? '#1e40af' : '#f4f4f5'}
/>
}
/>
<SettingItem
icon="language-outline"
title="Idioma"
subtitle="Español (México)"
onPress={() => {}}
/>
<SettingItem
icon="card-outline"
title="Moneda"
subtitle="MXN - Peso Mexicano"
onPress={() => {}}
/>
</View>
</View>
{/* Sync Section */}
<View style={styles.section}>
<Text style={styles.sectionTitle}>Sincronización</Text>
<View style={styles.sectionContent}>
<SettingItem
icon={isOnline ? 'cloud-done-outline' : 'cloud-offline-outline'}
title="Estado"
subtitle={isOnline ? 'Conectado' : 'Sin conexión'}
/>
<SettingItem
icon="sync-outline"
title="Cambios Pendientes"
subtitle={pendingSyncCount > 0 ? `${pendingSyncCount} por sincronizar` : 'Todo sincronizado'}
onPress={handleSyncNow}
/>
</View>
</View>
{/* Support Section */}
<View style={styles.section}>
<Text style={styles.sectionTitle}>Soporte</Text>
<View style={styles.sectionContent}>
<SettingItem
icon="help-circle-outline"
title="Centro de Ayuda"
onPress={handleHelp}
/>
<SettingItem
icon="chatbubble-outline"
title="Contactar Soporte"
onPress={handleHelp}
/>
<SettingItem
icon="document-text-outline"
title="Términos y Condiciones"
onPress={() => {}}
/>
<SettingItem
icon="shield-checkmark-outline"
title="Política de Privacidad"
onPress={() => {}}
/>
</View>
</View>
{/* Logout Section */}
<View style={styles.section}>
<View style={styles.sectionContent}>
<SettingItem
icon="log-out-outline"
title="Cerrar Sesión"
onPress={handleLogout}
danger
/>
</View>
</View>
{/* Version */}
<View style={styles.footer}>
<Text style={styles.version}>ERP Generic Mobile v0.1.0</Text>
<Text style={styles.copyright}>© 2026 ERP Generic</Text>
</View>
</ScrollView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#f8fafc',
},
content: {
padding: 16,
paddingBottom: 32,
},
section: {
marginBottom: 24,
},
sectionTitle: {
fontSize: 13,
fontWeight: '600',
color: '#64748b',
textTransform: 'uppercase',
letterSpacing: 0.5,
marginBottom: 8,
marginLeft: 4,
},
sectionContent: {
backgroundColor: '#ffffff',
borderRadius: 12,
overflow: 'hidden',
},
profileCard: {
backgroundColor: '#ffffff',
borderRadius: 12,
padding: 20,
flexDirection: 'row',
alignItems: 'center',
},
profileAvatar: {
width: 64,
height: 64,
borderRadius: 32,
backgroundColor: '#1e40af',
justifyContent: 'center',
alignItems: 'center',
},
profileInitial: {
fontSize: 24,
fontWeight: '700',
color: '#ffffff',
},
profileInfo: {
marginLeft: 16,
flex: 1,
},
profileName: {
fontSize: 20,
fontWeight: '600',
color: '#1f2937',
},
profileEmail: {
fontSize: 14,
color: '#64748b',
marginTop: 2,
},
settingItem: {
flexDirection: 'row',
alignItems: 'center',
padding: 16,
borderBottomWidth: 1,
borderBottomColor: '#f1f5f9',
},
settingIcon: {
width: 36,
height: 36,
borderRadius: 8,
backgroundColor: '#eff6ff',
justifyContent: 'center',
alignItems: 'center',
marginRight: 12,
},
settingIconDanger: {
backgroundColor: '#fef2f2',
},
settingContent: {
flex: 1,
},
settingTitle: {
fontSize: 16,
color: '#1f2937',
},
settingTitleDanger: {
color: '#dc2626',
},
settingSubtitle: {
fontSize: 13,
color: '#9ca3af',
marginTop: 2,
},
footer: {
alignItems: 'center',
paddingTop: 16,
},
version: {
fontSize: 13,
color: '#9ca3af',
},
copyright: {
fontSize: 12,
color: '#d1d5db',
marginTop: 4,
},
});