@acvl/mobile-components
v0.0.9
Published
Librería de componentes de las apps móviles de ACV (Expo / React Native).
Readme
@acvl/mobile-components
La librería de componentes de las apps móviles de ACV (Expo / React Native): tms, wms y suma. Es el
equivalente de @acvl/frontend-components
para las apps: lo que se ve igual en todas vive aquí, y cada app pone sólo lo suyo (su API, su
dominio).
El punto de partida es la app de tms, la más reciente. Su diseño es para quien usa la app de pie y
con prisa: letra grande (nada por debajo de tipografia.etiqueta), nada importante recortado a
un renglón, y todo listo para la letra del teléfono al 180%.
Qué trae
| Área | Qué |
| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Tema | ProveedorDeTema, usePreferenciaDeTema, PALETA_CLARA/PALETA_OSCURA, usePaleta, crearEstilos, useEsOscuro, tipografia, renglon, Text, TextInput |
| Componentes | BotonFlotante (con HUECO_DEL_BOTON_FLOTANTE), BotonGrande, Chip (con su tono morado), EstadoVacio, IconoDeRenglon, Nota, OpcionesExcluyentes, Pestanas, Rotulo, RotuloDeSeccion (con su accion), Tarjeta, TarjetaDeDatos, Logo, LogoCompleto, ContenidoDePantalla |
| Navegación | useOpcionesDelEncabezado (el header de las pestañas), BarraDePestanas, CabeceraEmpujada, AccionesDelEncabezado (con insignia), BotonDeAvatar |
| Diálogos | mostrarDialogo, confirmar, avisar y su HostDeDialogos |
| Formularios | CampoDeTexto, CampoDeFecha, CampoDeFechaHora, CampoInterruptor, CampoDeOpciones, CampoDeTelefono, CampoDeSeleccion (del servidor o de una lista fija), CampoDeVarias, SelectorDeOpcion, SelectorDeVarias, Campo (el marco de un campo propio; con accesorio junto al rótulo), CajaDeCampo (la caja con el rótulo adentro que usan todos), resaltarVacio en el texto y los selectores, sus …Controlado de react-hook-form, TextoDeAccesoControlado (el login), useGuardado, soloLoQueCambio, mensajeDeError, setErrors, useBusqueda |
| Listas y fichas | ListaPaginada (con Pagina y renglonesDe), CabeceraDeLista, Buscador, TarjetaDeLista, TarjetaDeslizable, HojaDeFiltros y sus opciones (OrdenDeLista nombra los sentidos de cada campo), SeccionDeFicha, RenglonDeLiga, FilaConContador, RenglonDeContacto, RenglonDeDireccion (abre el mapa del teléfono), RenglonDeHoja, EstadoDeHoja |
| Idioma | ProveedorDeIdioma (es o en; sin él, español), useTextos, textosActuales, useIdiomaDeLaLibreria |
| Fotos | Foto, ProveedorDeFotos, TiraDeFotos, VisorDeFoto |
| Pantallas | PantallaDeAcceso (el login), PantallaDeFicha, PantallaDeHistorial (la bitácora de un registro, con TarjetaDeEvento), EsperaDeEdicion/FormularioDeEdicion, PantallaDePerfil, PantallaDeError |
Y tres entradas aparte, para no obligar a cada app a instalar lo que no usa:
| Entrada | Qué | Pide |
| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------- |
| @acvl/mobile-components/camara | tomarFoto (con alTomar para guardar la original), elegirDeGaleria, pedirPermisoDeCamara, reducirFoto, y CampoDeFotos / FotosControladas, las fotos de un formulario (las nuevas y las ya guardadas) | expo-image-picker, expo-image-manipulator |
| @acvl/mobile-components/fotos | Todo lo de camara, más elegirDocumentos y elegirAdjunto (cámara, galería o un documento) | expo-image-picker, expo-image-manipulator, expo-document-picker, expo-file-system |
| @acvl/mobile-components/biometria | useBiometriaDeAcceso (entrar con Face ID o huella en PantallaDeAcceso), useBiometriaGuardada y DejarDeUsarBiometria (para el perfil), el servicio biometria | expo-local-authentication, expo-secure-store |
| @acvl/mobile-components/hoja | HojaInferior, useMasDeLaFicha (la sección «Más» de una ficha: renglones con contador que abren su hoja), HojaDeLista (una lista con buscador, scroll infinito y sus acciones, que sube sobre la ficha) | @gorhom/bottom-sheet |
Cada componente lleva su documentación en el código: por qué es como es, y qué se probó antes.
Instalar
npm install @acvl/mobile-componentsLo que la librería usa lo pone la app (peerDependencies): react-native-paper, expo-router,
react-native-reanimated, react-native-safe-area-context, react-native-keyboard-controller,
react-native-gesture-handler, @expo/vector-icons, @react-native-community/datetimepicker,
react-hook-form, dayjs, expo-haptics, expo-status-bar y expo-system-ui. Sirve desde el
SDK 54 de Expo.
Las fotos del servidor
Si las fotos del servidor piden la sesión, la app dice cómo bajarlas en el layout raíz: en Android
el Image no manda las headers, así que se bajan con fetch y se pinta el archivo.
<ProveedorDeFotos bajar={(ruta) => bajarConMiToken(ruta) /* → la uri local */}>Sin proveedor, la ruta de una foto se pinta tal cual, como URL pública.
La tipografía de Android
Android va con Inter (iOS se queda con San Francisco; ver src/tema/fuentes.ts). Los archivos
vienen en el paquete y cada app los registra en su app.json:
["expo-font", {
"android": {
"fonts": [{
"fontFamily": "Inter",
"fontDefinitions": [
{ "path": "./node_modules/@acvl/mobile-components/assets/fonts/Inter_400Regular.ttf", "weight": 400 },
{ "path": "./node_modules/@acvl/mobile-components/assets/fonts/Inter_500Medium.ttf", "weight": 500 },
{ "path": "./node_modules/@acvl/mobile-components/assets/fonts/Inter_600SemiBold.ttf", "weight": 600 },
{ "path": "./node_modules/@acvl/mobile-components/assets/fonts/Inter_700Bold.ttf", "weight": 700 },
{ "path": "./node_modules/@acvl/mobile-components/assets/fonts/Inter_800ExtraBold.ttf", "weight": 800 },
{ "path": "./node_modules/@acvl/mobile-components/assets/fonts/Inter_900Black.ttf", "weight": 900 }
]
}]
}
}]Cambiar esa lista obliga a reconstruir el binario.
Face ID o huella
@acvl/mobile-components/biometria guarda el usuario y la contraseña en el llavero, atados a la
biometría del teléfono, y los manda al mismo login de la app (no hay nada en el backend). Pide
expo-local-authentication y expo-secure-store, módulos nativos: instalarlos o cambiar sus
plugins obliga a un binario nuevo; un update OTA no basta. Los dos plugins llevan el texto del
permiso de Face ID en el app.json:
["expo-secure-store", { "faceIDPermission": "Usamos Face ID para que entres a la app sin escribir tu contraseña." }],
["expo-local-authentication", { "faceIDPermission": "Usamos Face ID para que entres a la app sin escribir tu contraseña." }]En el login, el hook da la prop de la pantalla y la pregunta de guardar. La app la hace tras entrar
de verdad (no en una contraseña temporal, ni en una cuenta sin acceso a esta app), y marca su 401
con credencialesRechazadas: con lo guardado, quiere decir que la contraseña cambió, y el hook lo
olvida y lo dice.
import { useBiometriaDeAcceso, type ResultadoDeEntrar } from "@acvl/mobile-components/biometria";
const { biometria, ofrecerGuardar } = useBiometriaDeAcceso({ entrar: (datos) => entrar(datos) });
const entrar = async (datos: DatosDeAcceso): Promise<ResultadoDeEntrar> => {
try {
await login(datos).unwrap();
void ofrecerGuardar(datos);
return null;
} catch (err: any) {
if (err?.status === 401) return { mensaje: "Usuario o contraseña incorrectos", credencialesRechazadas: true };
return { mensaje: "No se pudo iniciar sesión" };
}
};
<PantallaDeAcceso nombre="TMS" onEntrar={entrar} biometria={biometria} />En el perfil, «Dejar de usar…» (sólo sale si lo guardado es de quien está en sesión):
<PantallaDePerfil rotulo="Operador" nombre={user.name} onCerrarSesion={salir}>
<DejarDeUsarBiometria usuario={user.username} />
</PantallaDePerfil>Donde la contraseña cambie sin pasar por el login (restablecerla), la app llama a
biometria.olvidar(): lo guardado tenía la vieja.
Armar una app
El layout raíz monta el tema y los diálogos, y exporta la pantalla de error:
import { HostDeDialogos, PantallaDeError, ProveedorDeTema } from "@acvl/mobile-components";
import * as SecureStore from "expo-secure-store";
const almacenDelTema = {
leer: () => SecureStore.getItemAsync("TEMA"),
guardar: (tema) => SecureStore.setItemAsync("TEMA", tema),
};
export const ErrorBoundary = (props) => (
<PantallaDeError {...props} onError={(error) => Sentry.captureException(error)} />
);
export default function RootLayout() {
return (
<ProveedorDeTema almacen={almacenDelTema}>
<Stack screenOptions={{ headerShown: false }} />
<HostDeDialogos />
</ProveedorDeTema>
);
}Los textos de la librería (el login, el perfil, las fichas, las listas, los diálogos) salen en
español. Para inglés, la app envuelve todo en ProveedorDeIdioma con el idioma ya resuelto (de
dónde sale —el teléfono, el token, lo que eligió el usuario— es asunto de la app); avisar y
confirmar lo siguen aunque se llamen fuera de un componente:
<ProveedorDeIdioma idioma={idioma}>
<Stack screenOptions={{ headerShown: false }} />
</ProveedorDeIdioma>Las fechas de CampoDeFecha usan el locale global de dayjs, que también pone la app.
Las pestañas toman el header y la barra de la casa:
const encabezado = useOpcionesDelEncabezado({
eyebrow: "Operador",
derecha: <BotonDeAvatar iniciales="JP" onPress={() => router.push("/perfil")} />,
});
<Tabs screenOptions={encabezado} tabBar={(props) => <BarraDePestanas {...props} iconos={{ index: "assignment" }} />}>Y una pantalla toma sus colores de la paleta, nunca hexadecimales:
const useStyles = crearEstilos((c) => ({
tarjeta: { backgroundColor: c.superficie, borderRadius: 14, padding: 14 },
titulo: { ...renglon(tipografia.subtitulo), fontWeight: "800", color: c.texto },
}));Desarrollar
npm install
npm run typecheck
npm test # la lógica pura: diálogos, errores del servidor, biometría
npm run build # bob: lib/module y lib/typescript
npm run formatPara ver un cambio en una app sin publicar, la app de tms tiene npm run start:lib: Metro sirve
src/ de este checkout (../../acv-mobile-components) en lugar del paquete de node_modules.
Tiene que estar junto a la app, en la misma carpeta que acv-tms.
Publicar
Igual que la del portal: cada push a main corre el typecheck y las pruebas, sube el patch si la
versión ya está en npm, publica y hace commit y tag del bump. Un bump a mano (un minor) se respeta.
Necesita el secret NPM_TOKEN en el repo.
