Cómo está organizado el cliente
El cliente móvil tiene una estructura plana y corta. No hay capas como en la api: aquí lo que manda es para qué sirve un fichero, no en qué nivel de abstracción vive.
src/
app/ rutas — cada fichero es una pantalla (expo-router)
components/ piezas reutilizables de UI
hooks/ carga de datos y lógica reutilizable de pantalla
services/ todo lo que habla con fuera: api, almacenamiento, push
context/ estado global (hoy solo la sesión)
constants/ tema, etiquetas, listas fijas
config/ lectura de variables de entorno
lib/ utilidades puras sin dependencias
La regla de dónde va cada cosa
| Si es… | Va en |
|---|---|
| Una pantalla a la que se navega | app/ |
| UI que usan dos pantallas o más | components/ |
| Una llamada a la api | services/<recurso>-api.ts |
| “Cargar X y saber si está cargando” | hooks/use-<x>.ts |
| Algo que necesita toda la app a la vez | context/ |
| Un color, un espaciado, una lista de opciones | constants/ |
| Una función pura (formatear una fecha) | lib/ |
Dos criterios que evitan la mayoría de las dudas:
- Un componente nace en la pantalla que lo usa y se muda a
components/cuando aparece un segundo consumidor. No se crea “por si acaso”. services/es la única carpeta que toca el mundo exterior. Si un componente llama afetchdirectamente, eso quería ser un servicio.
No hay gestor de estado, y es a propósito
Ni Redux, ni Zustand, ni React Query. El estado se reparte en tres sitios y nada más:
useStatelocal en la pantalla, para lo que solo le importa a ella.- Un hook de datos (
useFetch/usePaginatedFetch) para lo que viene de la api. - Un contexto para lo único que de verdad es global: la sesión.
Funciona porque la api es la fuente de la verdad y no hay estado compartido complejo entre pantallas. Cada pantalla pide lo que necesita al entrar. La contrapartida asumida es que dos pantallas que muestran el mismo dato lo piden dos veces; a este tamaño, eso es más barato que mantener una caché.
Antes de añadir una librería de estado, comprueba que el problema no se resuelve con un hook compartido — que es cómo se han resuelto todos hasta ahora.
Dos detalles de convención
- Ficheros en
kebab-case, incluidos los componentes (pet-tag-card.tsx), noPascalCase. - Alias
@/apuntando asrc/, así que los imports son@/services/api-clienty no cadenas de../../.