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
- 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>
417 lines
11 KiB
TypeScript
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,
|
|
},
|
|
});
|