Utilidades helper de la app
Referencia de un `helper.tsx` típico (o `utils/helpers`) en Expo/React Native, basado en un esquema común con Expo Router, AsyncStorage, i18next, dayjs, toasts y expo-notifications. Nombres como `saveToSecureStorage` aquí usan AsyncStorage; cámbialo por almacenamiento seguro si manejas secretos. Rutas como `@/src/language-config/i18n` son ejemplos: adáptalas a tu proyecto.
Paquetes a instalar
Instala estos paquetes npm en tu app Expo (expo install suele ir mejor con módulos nativos). El helper.tsx descargado incluye la misma lista y anota cada función.
@react-native-async-storage/async-storagedayjsexpo-deviceexpo-notificationsexpo-routerreact-native-toast-message
Recomendado
npx expo install @react-native-async-storage/async-storage dayjs expo-device expo-notifications expo-router react-native-toast-messagenpm
npm install @react-native-async-storage/async-storage dayjs expo-device expo-notifications expo-router react-native-toast-messagereact y react-native ya vienen con Expo.
Conecta en tu proyecto
Ajusta rutas de i18n (`@/src/language-config/...`), `../constants/routes` y `../context/ThemeContext`. Opcional: EXPO_PUBLIC_GOOGLE_API_KEY para mapas estáticos.
Idioma y tema
- `changeLanguage`
src/utils/helper.tsxAsync: guarda la clave de idioma (p. ej. `@language`) y llama `i18n.changeLanguage` para refrescar strings.
Usa
- npm: @react-native-async-storage/async-storage
- Tus módulos: @/src/language-config/i18n
export const changeLanguage = async (lang: string) => { await saveToSecureStorage("@language", lang); i18n.changeLanguage(lang); };- `getTheme`
src/utils/helper.tsxResuelve el tema efectivo: si el usuario eligió `default`, sigue el `ColorSchemeName` del sistema; si no, devuelve el `Theme` explícito del contexto.
Usa
- React Native: ColorSchemeName
- Tus módulos: ../context/ThemeContext (`Theme`)
export const getTheme = (theme: Theme, systemTheme: ColorSchemeName) => { if (theme === "default" && systemTheme === "dark") { return "dark"; } else if (theme === "default" && systemTheme === "light") { return "light"; } else { return theme; } };
Logs y toasts
- `devLog`
src/utils/helper.tsxEnvuelve `console.log` para que solo registre cuando `__DEV__` es verdadero.
Usa
- Integrado: __DEV__ (Metro / React Native)
export const devLog = (...args: any[]) => { if (__DEV__) { console.log(...args); } };- `ShowToastOptions`
src/utils/helper.tsxCampos opcionales `text1`, `text2` y `type` (success | error) para `showError` / `showSuccess` cuando no pasas solo un string.
Usa
- Integrado: Type-only — pair with react-native-toast-message in showError / showSuccess
export interface ShowToastOptions { text1?: string; text2?: string; type?: "success" | "error"; }- `showError`
src/utils/helper.tsxMuestra un toast de react-native-toast-message. Acepta string u opciones; rellena con `getTextList().somethingWentWrong` si faltan textos.
Usa
- npm: react-native-toast-message
- Tus módulos: @/src/language-config/TextList (`getTextList`)
export const showError = (messageOrOptions: string | ShowToastOptions) => { let type = "error"; let text1 = ""; let text2 = getTextList().somethingWentWrong; if (typeof messageOrOptions === "string") { text2 = messageOrOptions || text2; } else { type = messageOrOptions.type || type; text1 = messageOrOptions.text1 || text1; text2 = messageOrOptions.text2 || text2; } Toast.show({ type, text1: `${text1}`, text2, }); };- `showSuccess`
src/utils/helper.tsxIgual que `showError` pero orientado a éxito (confirmaciones).
Usa
- npm: react-native-toast-message
- Tus módulos: @/src/language-config/TextList (`getTextList`)
export const showSuccess = (messageOrOptions: string | ShowToastOptions) => { let type = "success"; let text1 = ""; let text2 = getTextList().somethingWentWrong; if (typeof messageOrOptions === "string") { text2 = messageOrOptions || text2; } else { type = messageOrOptions.type || type; text1 = messageOrOptions.text1 || text1; text2 = messageOrOptions.text2 || text2; } Toast.show({ type, text1: `${text1}`, text2, }); };
Persistencia (AsyncStorage)
- `saveToSecureStorage`
src/utils/helper.tsx`AsyncStorage.setItem` con try/catch y `devLog`. El nombre sugiere secure, pero el ejemplo usa AsyncStorage.
Usa
- npm: @react-native-async-storage/async-storage
export async function saveToSecureStorage( key: string, value: string ): Promise<void> { try { await AsyncStorage.setItem(key, value); } catch (error) { devLog("[AsyncStorage] Error saving data:", error); } }- `getFromSecureStorage`
src/utils/helper.tsxLee un string por clave; devuelve `null` si no hay valor o hay error.
Usa
- npm: @react-native-async-storage/async-storage
export async function getFromSecureStorage( key: string ): Promise<string | null> { try { const value = await AsyncStorage.getItem(key); return value; } catch (error) { devLog("[AsyncStorage] Error retrieving data:", error); return null; } }- `removeKeysFromSecureStorage`
src/utils/helper.tsx`AsyncStorage.multiRemove` para borrar varias claves (logout, desactivar recordarme…).
Usa
- npm: @react-native-async-storage/async-storage
export async function removeKeysFromSecureStorage(key: string[]) { try { await AsyncStorage.multiRemove(key); } catch (error) { devLog("[AsyncStorage] Error removing data:", error); return null; } }- `removeAllKeysFromSecureStorage`
src/utils/helper.tsxVacía todo AsyncStorage—solo para reset fuerte o cierre de sesión global.
Usa
- npm: @react-native-async-storage/async-storage
export async function removeAllKeysFromSecureStorage() { try { await AsyncStorage.clear(); } catch (error) { devLog("[AsyncStorage] Error removing all data:", error); return null; } }
Recordarme
- `RememberMeCredentials`
src/utils/helper.tsxForma: `email`, `password`, `rememberMe`. Guardar contraseñas en AsyncStorage es arriesgado en producción; mejor tokens o almacén seguro.
Usa
- Integrado: Type-only — no runtime imports
export interface RememberMeCredentials { email: string; password: string; rememberMe: boolean; }- `saveRememberMeCredentials`
src/utils/helper.tsxSi `rememberMe` es true, guarda email/contraseña/`@remember_me`; si no, borra esas claves.
Usa
- npm: @react-native-async-storage/async-storage
- Nota: Uses saveToSecureStorage / removeKeysFromSecureStorage
export const saveRememberMeCredentials = async (credentials: RememberMeCredentials): Promise<void> => { try { if (credentials.rememberMe) { await saveToSecureStorage("@remembered_email", credentials.email); await saveToSecureStorage("@remembered_password", credentials.password); await saveToSecureStorage("@remember_me", "true"); } else { await removeKeysFromSecureStorage([ "@remembered_email", "@remembered_password", "@remember_me", ]); } } catch (error) { devLog("[Remember Me] Error saving credentials:", error); } };- `loadRememberMeCredentials`
src/utils/helper.tsxLee credenciales si `@remember_me` vale la cadena true; si no, devuelve vacío y rememberMe false.
Usa
- npm: @react-native-async-storage/async-storage
export const loadRememberMeCredentials = async (): Promise<RememberMeCredentials> => { try { const savedEmail = await getFromSecureStorage("@remembered_email"); const savedPassword = await getFromSecureStorage("@remembered_password"); const rememberMe = await getFromSecureStorage("@remember_me"); if (savedEmail && savedPassword && rememberMe === "true") { return { email: savedEmail, password: savedPassword, rememberMe: true, }; } return { email: "", password: "", rememberMe: false, }; } catch (error) { devLog("[Remember Me] Error loading credentials:", error); return { email: "", password: "", rememberMe: false, }; } };- `clearRememberMeCredentials`
src/utils/helper.tsxElimina solo las tres claves de recordarme.
Usa
- npm: @react-native-async-storage/async-storage
export const clearRememberMeCredentials = async (): Promise<void> => { try { await removeKeysFromSecureStorage([ "@remembered_email", "@remembered_password", "@remember_me", ]); } catch (error) { devLog("[Remember Me] Error clearing credentials:", error); } };
Texto, fechas y moneda
- `capitalizeFirstLetter`
src/utils/helper.tsxDevuelve cadena vacía si no hay texto; si no, mayúscula inicial y resto igual.
Usa
- Integrado: No packages
export const capitalizeFirstLetter = (text: string): string => { if (!text) return ""; return text.charAt(0).toUpperCase() + text.slice(1); };- `getTimeOfDay`
src/utils/helper.tsxSaludo por franja horaria (mañana/tarde/tarde-noche/noche) con strings localizados de `getTextList()`.
Usa
- Tus módulos: @/src/language-config/TextList (`getTextList`)
export const getTimeOfDay = () => { const currentHour = new Date().getHours(); if (currentHour >= 5 && currentHour < 12) { return getTextList().morning; } else if (currentHour >= 12 && currentHour < 17) { return getTextList().afternoon; } else if (currentHour >= 17 && currentHour < 21) { return getTextList().evening; } else { return getTextList().night; } };- `formatDate`
src/utils/helper.tsxFormatea `Date` o string con dayjs según el patrón (ej. YYYY-MM-DD).
Usa
- npm: dayjs
export const formatDate = (date: string | Date, format: string): string => { return dayjs(date).format(format); };- `formatCurrency`
src/utils/helper.tsx`Intl.NumberFormat` en modo moneda con 2 decimales; moneda por defecto GBP si falta.
Usa
- Integrado: Intl.NumberFormat
export const formatCurrency = (amount: number, currency: string) => { return new Intl.NumberFormat("en-US", { style: "currency", currency: currency || "GBP", minimumFractionDigits: 2, maximumFractionDigits: 2, }).format(amount); };- `getCurrencySymbol`
src/utils/helper.tsxMapea códigos ISO a símbolos; si falta o es desconocido, cae en £.
Usa
- Integrado: Intl (standard JS)
export const getCurrencySymbol = (currencyCode: string | null | undefined): string => { const normalizedCurrencyCode = currencyCode?.trim().toUpperCase(); if (!normalizedCurrencyCode) { return "£"; } const currencyMap: Record<string, string> = { USD: "$", GBP: "£", EUR: "€", INR: "₹", JPY: "¥", CNY: "¥", AUD: "A$", CAD: "C$", CHF: "CHF", SEK: "kr", NOK: "kr", DKK: "kr", PLN: "zł", RUB: "₽", BRL: "R$", MXN: "$", ZAR: "R", SGD: "S$", HKD: "HK$", NZD: "NZ$", KRW: "₩", TRY: "₺", AED: "د.إ", SAR: "﷼", PKR: "₨", BDT: "৳", THB: "฿", MYR: "RM", IDR: "Rp", PHP: "₱", VND: "₫", }; return currencyMap[normalizedCurrencyCode] || "£"; };
Linking y ajustes
- `openAppSettings`
src/utils/helper.tsxiOS: URL `app-settings:`; Android: `Linking.openSettings()` (pantalla de la app).
Usa
- React Native: Linking, Platform
export const openAppSettings = () => { if (Platform.OS === "ios") { Linking.openURL("app-settings:"); } else { Linking.openSettings(); } };- `openWebUrl`
src/utils/helper.tsxWrapper de `Linking.openURL` para URLs web o deep links.
Usa
- React Native: Linking
export const openWebUrl = (url: string) => { Linking.openURL(url); };
Navegación con Expo Router
- `navigate`
src/utils/helper.tsxEnvoltorio de `router.push`: `pathname`, `params` opcional y objeto `search` para Expo Router.
Usa
- npm: expo-router
type RelativePathString = string; type NavigateOptions = { params?: Record<string, string>; search?: Record<string, string | number | boolean | undefined>; }; export const navigate = ( route: RelativePathString | any, options?: NavigateOptions ) => { router.push({ pathname: route, ...(options?.params && { params: options.params }), ...(options?.search && { search: options.search }), }); };- `navigateBack`
src/utils/helper.tsxLlama `router.back()` para volver en la pila.
Usa
- npm: expo-router
export const navigateBack = () => { router.back(); };- `updateUser`
src/utils/helper.tsxGuarda tokens de acceso/refresh, actualiza usuario con `setUser` y navega a una ruta o al root de tabs.
Usa
- npm: @react-native-async-storage/async-storage, expo-router
- Tus módulos: ../constants/routes · `navigate` in same file
- Nota: Expects API shape with data.access_token, data.refresh_token, data.user
export const updateUser = async ( data: any, setUser: (user: any) => void, route?: any ) => { await saveToSecureStorage("@access_token", data?.data?.access_token); await saveToSecureStorage("@refresh_token", data?.data?.refresh_token); setUser(data?.data?.user); if (route) { navigate(route); } else { navigate(Routes.TABS.ROOT); } };
Datos para UI
- `getConsistentColor`
src/utils/helper.tsxColor pastel estable a partir de un id (hash simple sobre una paleta fija)—avatars o etiquetas.
Usa
- Integrado: No packages
export const getConsistentColor = (reviewId: string) => { const colors = [ "#FF6B6B", "#4ECDC4", "#45B7D1", "#96CEB4", "#FFEAA7", "#DDA0DD", "#98D8C8", "#F7DC6F", "#BB8FCE", "#85C1E9", "#F8C471", "#82E0AA", "#F1948A", "#85C1E9", "#D7BDE2", ]; let hash = 0; for (let i = 0; i < reviewId.length; i++) { hash = reviewId.charCodeAt(i) + ((hash << 5) - hash); } return colors[Math.abs(hash) % colors.length]; };
Google Static Maps
- `MapMarker`
src/utils/helper.tsxMarcador: `lat`/`lng` obligatorios; `name`, `color`, `label` opcionales para Static Maps.
Usa
- Integrado: Type-only
export interface MapMarker { name?: string; lat: number; lng: number; color?: string; label?: string; }- `StaticMapOptions`
src/utils/helper.tsxOpciones de URL estática: centro, zoom, tamaño, tipo de mapa, marcadores, escala, formato.
Usa
- Integrado: Type-only
export interface StaticMapOptions { center?: { lat: number; lng: number }; zoom?: number; size?: string; maptype?: "roadmap" | "satellite" | "terrain" | "hybrid"; markers?: MapMarker[]; scale?: 1 | 2; format?: "png" | "jpg" | "gif"; }- `generateStaticMapUrl`
src/utils/helper.tsxConstruye URL de Google Static Maps; usa `EXPO_PUBLIC_GOOGLE_API_KEY`; si falta, devuelve cadena vacía y registra aviso.
Usa
- Integrado: EXPO_PUBLIC_GOOGLE_API_KEY (env), global URL / encodeURIComponent
- Nota: Uses Google Maps Static API
export const generateStaticMapUrl = (options: StaticMapOptions): string => { const { center, zoom = 13, size = "600x200", maptype = "roadmap", markers = [], scale = 1, format = "png", } = options; const apiKey = process.env.EXPO_PUBLIC_GOOGLE_API_KEY; if (!apiKey) { devLog("Warning: Google Maps API key not found"); return ""; } const baseUrl = "https://maps.googleapis.com/maps/api/staticmap"; const params: string[] = []; if (center) { params.push(`center=${center.lat},${center.lng}`); } else if (markers.length > 0) { params.push(`center=${markers[0].lat},${markers[0].lng}`); } params.push(`zoom=${zoom}`); params.push(`size=${size}`); params.push(`maptype=${maptype}`); params.push(`scale=${scale}`); params.push(`format=${format}`); if (markers.length > 0) { markers.forEach((marker) => { const markerParams: string[] = []; if (marker.color) { markerParams.push(`color:${marker.color}`); } if (marker.label) { markerParams.push(`label:${marker.label}`); } markerParams.push(`${marker.lat},${marker.lng}`); const markerString = markerParams.join("|"); params.push(`markers=${encodeURIComponent(markerString)}`); }); } params.push(`key=${apiKey}`); const finalUrl = `${baseUrl}?${params.join("&")}`; devLog("Generated Static Map URL:", finalUrl); return finalUrl; };- `generateStaticMapUrlWithMarker`
src/utils/helper.tsxAtajo con un marcador rojo centrado y el resto de opciones.
Usa
- Integrado: Builds on generateStaticMapUrl
export const generateStaticMapUrlWithMarker = ( name: string, lat: number, lng: number, options?: Omit<StaticMapOptions, "markers" | "center"> ): string => { return generateStaticMapUrl({ center: { lat, lng }, markers: [{ name, lat, lng, color: "red" }], ...options, }); };
Notificaciones locales
- `initializeNotifications`
src/utils/helper.tsxEn dispositivo real pide permiso; en Android crea canal default con importancia máxima (expo-notifications).
Usa
- npm: expo-notifications, expo-device
- React Native: Platform
export const initializeNotifications = async () => { if (Device.isDevice) { const { status } = await Notifications.requestPermissionsAsync(); if (status !== "granted") { devLog("Permission for notifications not granted!"); return; } } if (Platform.OS === "android") { await Notifications.setNotificationChannelAsync("default", { name: "default", importance: Notifications.AndroidImportance.MAX, }); } };- `showNotification`
src/utils/helper.tsxPrograma notificación local inmediata (`trigger: null`) con título y cuerpo.
Usa
- npm: expo-notifications
export const showNotification = async (title: string, body: string) => { await Notifications.scheduleNotificationAsync({ content: { title, body }, trigger: null, }); };