- TypeScript 98.8%
- Python 0.6%
- JavaScript 0.4%
- Shell 0.2%
| .claude | ||
| .vscode | ||
| __tests__ | ||
| assets/images | ||
| scripts | ||
| src | ||
| .env.example | ||
| .gitignore | ||
| AGENTS.md | ||
| app.json | ||
| CLAUDE.md | ||
| eas.json | ||
| eslint.config.js | ||
| google-services.json | ||
| HOJA_DE_RUTA.md | ||
| jest.resolver.js | ||
| jest.setup.js | ||
| LICENSE | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
| tsconfig.json | ||
Techos de España · app móvil
App para iOS y Android del reto de las 152 cimas provinciales. Es el hito H10
de la hoja de ruta del proyecto, y vive en su propio repositorio a
propósito: aquí hay node_modules, paso de compilación y medio internet
descargado; el servidor sigue arrancando con docker compose up y sin nada
que construir.
La frontera entre los dos repositorios es la API. De este lado no se copia
código del servidor ni datos del catálogo: las cimas las pide la app a
/api/v1/picos en tiempo de ejecución. Ver el LICENSE, donde eso es además
una condición y no una recomendación.
- Servidor y hoja de ruta: https://forgejo.dacowars.duckdns.org/dacowars/app_montanismo
- La razón de ser de la app está en el §5.3 de
HOJA_DE_RUTA.md: apuntar la cumbre desde arriba, sin cobertura, y que se suba sola al bajar.
Qué hace falta
- Node 22 LTS y npm.
- La app Expo Go en el teléfono, desde su tienda. Es el entorno de pruebas: no hacen falta ni Xcode ni Android Studio.
- Un servidor de pruebas con la API viva. No se desarrolla contra producción: la app escribe cumbres de verdad.
- Para los avisos al teléfono, un development build con EAS. Expo Go ya no recibe push remoto (salió de allí en el SDK 53), así que esa parte —y solo esa— no se puede probar con el QR. Ver «Avisos al teléfono», abajo.
Arrancar
npm install
cp .env.example .env.local # y apuntar EXPO_PUBLIC_API_URL al servidor de pruebas
npx expo start # y escanear el QR con Expo Go
Se entra desde la propia app: un botón, el navegador del teléfono, Google, y de vuelta. La llave que sale de ahí se guarda en el almacén seguro —Keychain en iOS, preferencias cifradas en Android— y no se enseña en ninguna pantalla.
En Expo Go hay que decírselo al servidor. La dirección de vuelta no es
picosmovil://vinculado sino exp://192.168.0.100:8082/--/vinculado, con la
IP de la máquina que corre Metro y el puerto que le tocara ese día —Expo coge
el 8082 si el 8081 está ocupado—. Esa dirección entera va en VUELTAS_DE_PRUEBA
en el .env del servidor, separada por comas si hay más de una. Es una lista
de direcciones literales y no un prefijo exp:// a propósito: lo que se abre
es una dirección, no una categoría.
Cómo está repartido
src/api/ el cliente de la API: la llave, los tipos, las llamadas y cómo
piden datos las pantallas (`carga.ts`)
src/datos/ lo que vive en el teléfono: SQLite, el catálogo, la cola y el GPS
src/app/ las pantallas (expo-router; el fichero es la ruta)
src/ui/ lo que se repite en varias pantallas: la cara, las miniaturas,
el visor de fotos y el mapa
src/tema.ts cuatro colores, dos modos y cómo se dicen las fechas
Las pantallas van en dos alturas. La pila de raíz (src/app/_layout.tsx)
tiene las dos mitades de la app —fuera: entrar; dentro: todo lo demás— y el
modal de apuntar, que es lo único que interrumpe. Dentro cuelga (app), que
son las tres pestañas, y cada pestaña lleva su propia pila, para que lo que
se abra desde una se abra dentro de ella y con la barra puesta. Solo el
cuaderno va entre paréntesis: un grupo no sale en la dirección, así que su
index es /.
src/api/tipos.ts es un espejo a mano de los esquemas de app/api.py del
servidor. Si allí cambia un campo, aquí no se enterará nadie hasta que algo
se pinte vacío: el día que duela, se generan desde /api/v1/openapi.json.
Cómo se entra, en cinco pasos
Ni el código ni la llave pasan nunca por una URL, y es lo único importante de todo el mecanismo. Es el mismo truco que OAuth llama PKCE:
- La app sortea 32 bytes —el verificador—, los guarda en el almacén seguro y calcula su SHA-256, el reto.
- Abre el navegador del sistema en
/movil/vincular?reto=…&nombre=…&vuelta=…. El navegador del sistema y no una vista dentro de la app: es lo que exige Google y lo que impide que esta app pueda ver la contraseña de nadie. - Allí se entra —o se crea la cuenta— y se confirma qué teléfono se está
conectando. La vinculación se crea en el
POSTde ese botón, nunca al abrir la página. - Se vuelve a la app con un identificador opaco, que no abre nada.
- La app llama a
POST /api/v1/vinculacion/canjecon{estado, verificador}y recibe la llave en el cuerpo de la respuesta.
Cualquier otra aplicación del teléfono puede haber registrado el mismo esquema y quedarse con la vuelta: no se lleva nada, porque el verificador no ha salido de aquí. Y «Salir de este teléfono», en los ajustes, corta esa llave en el servidor además de borrarla de aquí: los demás dispositivos siguen dentro.
Estado
Las tandas son las del H10.
| Tanda | Qué es | Estado |
|---|---|---|
| A | Entrar sin copiar una llave a mano | hecha y probada en el móvil (9-9-2026) |
| B | Apuntar desde la cima: cámara, GPS y cola | escrita entera (B1, B2 y B3, 9-9-2026). Falta apuntar una cumbre de verdad desde el teléfono para cerrar el «hecho cuando» |
| C | Leer: marcador, perfiles, catálogo y mapa | hecha (11-9-2026) |
| D | Escribir: lo social, clubes y ajustes | hecha (11-9-2026) |
| — | Arreglos: visor de fotos, ficha de cima entera y el mapa, que en Android salía vacío | hechos (11-9-2026) |
| — | Avisos al teléfono (H13 del servidor) | escritos (13-9-2026). Falta el development build para probarlos en un móvil de verdad |
| E | Publicar en las dos tiendas | pendiente |
Si algo se cae, que sea la D y no la B.
El mapa es Leaflet con teselas de OpenStreetMap dentro de un WebView, el
mismo que la web, y no react-native-maps: el de Android era el de Google y no
pinta nada sin una clave de Google Cloud. El porqué entero está en
src/ui/Mapa.tsx.
Avisos al teléfono
Tres y ninguno más: te piden seguirte, alguien a quien sigues apunta una cumbre, te comentan una salida. Se apagan uno a uno en Ajustes → Avisos, y el ajuste es de la cuenta: apagarlo lo apaga en todos tus teléfonos.
El permiso se pide en esa pantalla y nunca al arrancar. En iOS el diálogo del sistema sale una vez en la vida de la instalación, así que gastarlo en un arranque —antes de explicar qué se va a avisar— es perderlo para siempre.
Para probarlos hace falta compilar, una vez:
npx eas-cli@latest login # una vez: sin esto, lo demás dice «Not logged in»
npx eas-cli@latest init # escribe extra.eas.projectId en app.json
npx eas-cli@latest build --profile development --platform android
El paquete es eas-cli y el binario eas: npx eas init falla con «could not
determine executable to run» mientras no esté instalado. Con
npm install -g eas-cli basta eas init.
Y las credenciales, que hay que hacer a mano en las dos consolas: en Android,
una service account key de FCM v1 desde Firebase; en iOS, una clave de APNs,
que exige la cuenta de desarrollador de Apple (99 $/año). Sin
projectId la app no se cae: la pantalla de ajustes dice que este build no
puede recibir avisos.
El detalle entero —qué lleva dentro un aviso y qué no, y quién lo recibe— está
en el §9 de HOJA_DE_RUTA.md y en el README del servidor.
Avisos que cuestan tiempo si se descubren tarde
- No corras
npm audit fix --force. Expo fija versiones probadas entre sí; forzarlo las desancla y rompe la compilación. El chequeo bueno esnpx expo-doctor. - Todo lo que empieza por
EXPO_PUBLIC_viaja dentro del binario, en claro. Aquí no se guardan secretos: la llave de la API la obtiene cada teléfono para sí mismo. eas inittocaapp.jsony no solo para escribir elprojectId. El 13-9-2026 añadió además un bloqueandroid.permissionsque no hacía falta: los permisos los declaran los plugins, y el bloque solo repetía lo que ya estaba. Si vuelve a salir, se quita. Lo que sí hay que revisar de vez en cuando es la lista de verdad, connpx expo config --type introspect: la app pide cámara, ubicación, almacenamiento e internet, y nada más.RECORD_AUDIOlo metíaexpo-image-pickerpor defecto y está apagado a propósito conmicrophonePermission: false—esta app nunca graba audio, y un permiso de micrófono en la ficha de la tienda hay que explicarlo—.- El nombre y el identificador de aplicación (
com.…) se eligen una sola vez. Los de hoy son provisionales y se cambian libremente hasta el día que se manda la primera versión a revisar, y ni un minuto después. Ver el §8 de la hoja de ruta. - SDK 57 cambió cosas. Los docs de esta versión están en
https://docs.expo.dev/versions/v57.0.0/;
expo-file-systemtiene API nueva (File/Directory) y la de siempre vive enexpo-file-system/legacy.
Licencia
Código público, no software libre: todos los derechos reservados. Se
publica para que cualquiera pueda auditar qué hace la app con la cámara y con
la ubicación. Ver LICENSE.