Publicat per

(PR2) Camina i fes UNLOCK! Tarragona

Publicat per

(PR2) Camina i fes UNLOCK! Tarragona

  Procés d’ideació i evolució del projecte El projecte Camina i fes UNLOCK! neix amb la voluntat de transformar una activitat quotidiana…
  Procés d’ideació i evolució del projecte El projecte Camina i fes UNLOCK! neix amb la voluntat de transformar…

 

Procés d’ideació i evolució del projecte

El projecte Camina i fes UNLOCK! neix amb la voluntat de transformar una activitat quotidiana com caminar en una experiència lúdica i visualment significativa. A partir de la base desenvolupada a la PR1, l’objectiu de la PR2 ha estat ampliar aquesta proposta mantenint la mecànica principal, però afegint una nova capa de contingut mitjançant la integració d’una API externa. Enfocarem aquesta nova versió com una app específica amb contingut de Tarragona.

Des del punt de vista conceptual, s’ha considerat important no alterar la naturalesa “effort-free” de l’app. L’ usuari continua interactuant mínimament amb el dispositiu, mentre el progrés es produeix de manera natural a mesura que camina. La nova funcionalitat introduïda no substitueix el joc, sinó que l’enriqueix, aportant context cultural i informatiu a les imatges desbloquejades.

 

Integració d’una API externa

S’ha integrat una API externa pública: la MediaWiki API (Viquipèdia) en llengua catalana.

L’ elecció d’aquesta API respon a diversos criteris:

  • és gratuïta i sense necessitat d’autenticació,
  • permet consultes dinàmiques en format JSON,
  • ofereix contingut rellevant i contextualitzat,
  • i disposa de suport CORS, imprescindible per a la seva integració en una aplicació web híbrida amb Capacitor.

Cada imatge desbloquejada a l’ app està associada a un indret real de la ciutat de Tarragona. Quan l’usuari accedeix a la galeria i selecciona el botó Info, l’ aplicació realitza una crida dinàmica a l’API de la Viquipèdia per obtenir informació contextual sobre aquell indret: títol, resum descriptiu i enllaç per ampliar la informació.

Aquesta integració compleix el requisit de “petició personalitzada”, ja que la consulta depèn directament de la imatge seleccionada, i inclou control d’errors per gestionar possibles problemes de xarxa o respostes no disponibles.

Visualització de la informació

La informació retornada per l’API es mostra dins l’ aplicació mitjançant un modal dedicat, evitant així que l’usuari abandoni el flux principal del joc. Aquesta solució permet mantenir la coherència visual de l’app i reforça la idea que la informació forma part de l’ experiència de desbloqueig.

 

La presentació de les dades s’ha dissenyat de manera clara i jerarquitzada, prioritzant la llegibilitat i l’accessibilitat, i reforçant el valor afegit de l’API sense sobrecarregar la interfície.

Funcionalitats natives i feedback tècnic

L’aplicació continua fent ús de funcionalitats natives del dispositiu, principalment la geolocalització, que és l’eix central del funcionament del joc. El càlcul de la distància recorreguda permet determinar el progrés de desbloqueig de cada imatge.

Arran del feedback rebut a la PR1, es va analitzar la possibilitat d’implementar watchPosition per millorar la precisió del seguiment continu. Tot i reconèixer els avantatges d’aquesta aproximació, en aquesta iteració s’ha prioritzat l’estabilitat del MVP i la integració correcta de l’API externa. Com a solució tècnica per facilitar el desenvolupament i el testeig, es manté el Mode Demo, que permet simular l’avanç de metres sense necessitat de desplaçar-se físicament. Aquest plantejament ha permès continuar iterant sobre el projecte de manera eficient, deixant la millora del seguiment GPS com a possible evolució futura.

Galeria, navegació i estat del joc

Les imatges completades es poden consultar en qualsevol moment i s’obren en un visor amb opcions de zoom. La integració del botó Info dins la galeria reforça la connexió entre el contingut visual i la informació contextual obtinguda de l’API.

S’ha tingut especial cura a separar els estats de “desbloqueig en curs” i “visualització”, evitant animacions o efectes que poguessin generar confusió quan l’usuari navega per contingut ja completat.

 

Proves i entorn de test

Les proves s’han realitzat principalment amb Android Studio, utilitzant diferents perfils d’emulador per validar el comportament de l’aplicació en diverses resolucions i formats de dispositiu. Aquest procés ha permès detectar i solucionar problemes relacionats amb l’estat persistent, la configuració de l’entorn i la reconstrucció del projecte en entorns nets.

Com a millora respecte versions anteriors, l’aplicació arrenca des d’un estat inicial net en el primer llançament, evitant conflictes derivats de dades de proves prèvies guardades en emmagatzematge local.

Les proves finals s’han realitzat amb Android Studio amb els següents perfils:

  • Tablet Pixel (API 35)
  • Pixel 9 Pro XL (API 36)
  • Medium Phone (API 36.1)

Dependències i avisos de seguretat

Durant la instal·lació del projecte mitjançant npm install, el gestor de paquets indica l’existència d’algunes vulnerabilitats en dependències de tercers (2 de nivell moderat i 2 de nivell alt).

Aquests avisos corresponen a llibreries utilitzades internament pel framework i no a codi propi del projecte. En tractar-se d’un MVP amb finalitats educatives i no d’una aplicació destinada a producció, s’ha prioritzat l’estabilitat del projecte i no s’han aplicat actualitzacions forçades (npm audit fix --force), ja que podrien introduir canvis trencadors o incompatibilitats amb Capacitor i Vite. Aquesta decisió reflecteix un criteri habitual en entorns reals, on la gestió de vulnerabilitats es fa de manera controlada i contextualitzada segons l’estat i l’objectiu del producte.

 

Resultat final i aprenentatges

El resultat final és una aplicació Android funcional, estable i coherent amb el concepte inicial. A nivell d’aprenentatge, la PR2 ha posat de manifest la importància de:

  • integrar serveis externs de manera significativa,
  • gestionar correctament els estats i errors,
  • i prendre decisions tècniques equilibrades entre ambició i estabilitat.

El projecte consolida el potencial de Capacitor com a eina per portar aplicacions web a l’àmbit mòbil i estableix una base sòlida per futures ampliacions.

 

ENLLAÇ A GITHUB: https://josepmsole.github.io/CaminaifesUnlockTarragona/

 

Debat0el (PR2) Camina i fes UNLOCK! Tarragona

No hi ha comentaris.

Publicat per

Pràctica 2

Publicat per

Pràctica 2

Ideació inicial de la nova App L’objectiu d’aquesta pràctica 2 ha estat donar continuïtat a la feina feta en la pràctica 1…
Ideació inicial de la nova App L’objectiu d’aquesta pràctica 2 ha estat donar continuïtat a la feina feta en…

Ideació inicial de la nova App

L’objectiu d’aquesta pràctica 2 ha estat donar continuïtat a la feina feta en la pràctica 1 i desenvolupar una app interactiva basada en un puzle de peces mòbils, que permet a l’usuari carregar la imatge amb la qual es jugarà dins del puzle.
A la pràctica 1, l’app permetia escollir entre una imatge feta amb la càmera o una imatge existent a la galeria. A la pràctica 2 mantinc aquestes funcionalitats i n’afegeixo una de nova: obtenir imatges de Picsum per utilitzar-les en el puzle.
També es manté l’opció de mostrar un botó de configuració per escollir la mida del puzle amb què es vol jugar.
Aquests són els esbossos de la pràctica 1 i el resultat final de la pràctica 2.

Interfície proposada a la pràctica 1
Evolució del disseny de la pràctica 2

 

Consultes i passos seguits a la documentació de Capacitor

Per tal de desenvolupar l’app amb Capacitor i integrar funcionalitats natives, he seguit el procés de consulta i experimentació següent:

  • Revisió de la documentació oficial de Capacitor per entendre com funciona la integració amb projectes web.
  • Consulta dels plugins oficials i dels seus exemples d’ús.

Especial focus en:

  • Camera Plugin: per capturar imatges directament des del dispositiu o carregar-les des de la galeria.
  • Haptics Plugin: per afegir feedback tàctil durant la interacció amb el puzle.
  • També s’ha considerat com adaptar el codi perquè funcioni correctament dins d’una app híbrida (ús de webPath, càrrega d’imatges amb p5.loadImage, etc.).

Aquestes consultes han permès comprendre com utilitzar les APIs natives des de JavaScript, i com gestionar errors, permisos i diferents comportaments segons el dispositiu.

Proves amb diferents funcionalitats natives del dispositiu

Per a la pràctica, s’han provat funcionalitats natives per donar valor afegit a l’aplicació:

Càmera i galeria (Camera Plugin)

L’usuari pot:

  • Fer una foto amb la càmera.
  • Seleccionar una imatge de la galeria.

Aquestes imatges es processen i es transformen en un puzle.

Vibració / feedback tàctil (Haptics Plugin)
  • Quan el jugador fa un moviment vàlid, l’app fa una vibració lleugera.
  • Quan el puzle queda resolt, es mostra un efecte extra (vibració més forta o més llarga).

Aquestes funcionalitats fan que l’experiència sigui més “game-like” i milloren la resposta al tacte.

Proves realitzades:
  • Verificar la càrrega correcta de la imatge a p5.js.
  • Comprovar el comportament quan l’usuari cancel·la la selecció (sense perdre la partida actual).
  • Detectar problemes amb l’orientació/simetria de la foto (amb càmera frontal). Aquest punt queda pendent de resoldre per falta de temps per buscar una solució.

Decisions preses i aprenentatges adquirits

Durant el desenvolupament de l’MVP, he pres diverses decisions tècniques i de disseny:

Decisió 1: API externa → Picsum

S’ha triat Picsum com a API externa per:

  • Poder obtenir imatges aleatòries per jugar sense necessitat de tenir fotos personals.
  • Tenir una resposta en format JSON amb dades útils (com l’autor).
  • Evitar complicacions com l’autenticació o l’ús d’API keys.
  • Facilitar la idea d’un joc “rejugable”.
Decisió 2: Mostrar l’autor de la imatge

Quan s’utilitza una imatge de Picsum, es mostra un text sota el puzle amb l’autor. Això aporta:

  • Transparència.
  • Un toc de “crèdit” visual.
  • Millor integració amb el requisit de mostrar dades de l’API dins l’app.
Decisió 3: Afegir una previsualització de la imatge original

Com que el puzle pot ser difícil, s’ha afegit:

  • Una icona que permet veure la imatge sencera com a referència. No es tracta de la imatge original completa, sinó d’una secció quadrada de la imatge original. Aquesta imatge retallada és la que utilitzarà el puzle.
  • Aquesta funcionalitat millora el gameplay i permet resoldre el puzle amb més facilitat.
Aprenentatges principals
  • Les funcionalitats natives en apps híbrides poden tenir comportaments diferents segons el dispositiu (càmera frontal invertida, cancel·lacions, permisos…).
  • La UI afecta molt l’experiència: cal ajustar espais, alineacions, overlays i coherència visual.

Esbossos, captures i iteracions que mostrin l’evolució del projecte

Iteració 1: MVP funcional (PR1): puzzle amb imatge local i funcionament bàsic:

 

 

 

 

 

 

Iteració 2: Integració de l’API

  • Afegir el botó de Picsum per arregar una imatge aleatòria des del JSON.
  • Mostrar l’autor de la imatges
  • Afegir un HUD amb:
    • Comptador de moviments.
    • Comptador de temps.
    • Icona de previsualització i overlay per mostrar la imatge utilitzada en el puzzle itenir-la de referència.

Justificació breu: problemes trobats, solucions i conclusions

  • Problema 1: l’autor desapareix quan es cancel·la galeria/càmera
    Causa: el text de l’autor s’esborrava abans de confirmar que s’havia carregat una nova imatge.
    Solució: netejar l’autor només quan la càrrega de la nova imatge sigui exitosa.
  • Problema 2: puzles que començaven ja resolts
    Causa: el shuffle podia deixar el mateix estat final que el puzle resolt.
    Solució: aplicar una comprovació després del shuffle i repetir-lo fins que el puzle no estigui resolt.
  • Problema 3: el temps no s’aturava en resoldre el puzle
    Causa: el valor puzzle.solved no s’actualitzava just en el mateix moment del touchStarted.
    Solució: aturar el cronòmetre des del draw() quan el puzle està resolt.

Citació de totes les fonts utilitzades

Documentació oficial / fonts tècniques:

  • Capacitor (documentació oficial): Camera Plugin i Haptics Plugin.
  • Recursos sobre CORS i consum d’APIs en aplicacions web.

API externa:

  • Lorem Picsum API (endpoint /v2/list i càrrega d’imatges per ID).

He utilitzat ChatGPT com a suport per:

  • Depurar errors de l’app.
  • Proposar estructures de codi.
  • Millorar el CSS i la UX.
  • Resoldre conflictes d’overlays i layout.

Vídeo

Per realitzar aquest vídeo he utilitzat un ZTE Blade A51, Android versió 11.

Carregant...

Debat0el Pràctica 2

No hi ha comentaris.

Publicat per

DropApp – PR2

Publicat per

DropApp – PR2

1. Ideació i Evolució del Projecte Concepte: DropApp evoluciona d’un temporitzador passiu (PR1) a un assistent oftalmològic contextual. La sequedat ocular depèn…
1. Ideació i Evolució del Projecte Concepte: DropApp evoluciona d’un temporitzador passiu (PR1) a un assistent oftalmològic contextual. La…

1. Ideació i Evolució del Projecte

Concepte:

DropApp evoluciona d’un temporitzador passiu (PR1) a un assistent oftalmològic contextual. La sequedat ocular depèn directament de la humitat ambiental. Per tant, l’App no només avisa quan posar-se les gotes, sinó que informa l’usuari de l’estat del seu entorn immediat.

Selecció d’APIs (Estratègia de Dades):

Per oferir una experiència completa, he optat per combinar dues APIs complementàries:

  • BigDataCloud (Reverse Geocoding): Permet traduir les coordenades GPS fredes (ex: 41.38, 2.17) a un nom de ciutat comprensible per a l’usuari (ex: “Barcelona”).
  • Open-Meteo (Temps): Aporta la dada mèdica rellevant (% Humitat Relativa) sense necessitat de registre ni claus d’API.

Esbós preliminar de l’app

2. Desenvolupament Tècnic amb Capacitor

2.1. Arquitectura Híbrida

L’aplicació segueix el patró Bridge:

  • Capa Lògica (main.js): Actua com a orquestrador. Gestiona la geolocalització nativa i encadena les peticions a les dues APIs.

  • Capa Visual (sketch.js): Rep les dades processades (Nom de ciutat i Humitat) a través d’un objecte global compartit window.eyeWeather i les pinta al canvas

2.2. Integració de les APIs (Flux en Cascada)

S’ha implementat una lògica asíncrona robusta (async/await) al fitxer main.js per connectar el dispositiu amb les dues APIs externes:

  1. GPS Natiu (Geolocation): El plugin @capacitor/geolocation obté la latitud i longitud precises quan l’usuari té activat el mode “Ubicació GPS (Automàtic)”. S’utilitza enableHighAccuracy: true i un timeout de 10 segons per evitar bloquejos.

  2. Identificació (API 1 – BigDataCloud): Amb les coordenades rebudes, es consulta l’endpoint reverse-geocode-client de BigDataCloud amb el paràmetre localityLanguage=ca, prioritzant el nom de ciutat/localitat en català sempre que sigui possible.

  3. Dades Climàtiques (API 2 – Open-Meteo): A partir de la mateixa latitud/longitud es consulta Open-Meteo per obtenir la temperatura actual i la humitat relativa (temperature_2mrelative_humidity_2m), sense necessitat de registres ni claus d’API.

  4. Persistència i flux manual: Si l’usuari desactiva el GPS i selecciona una ciutat manualment, les coordenades escollides es desen a localStorage i es reutilitzen a les crides següents, mantenint el flux de dades encara que el GPS estigui aturat.

  5. Fallback per IP: Si no disposem de coordenades (primera obertura, error de GPS) l’app consulta de nou BigDataCloud, però en aquest cas mitjançant geolocalització per IP, per obtenir unes coordenades aproximades i un nom de ciutat genèric com a reserva.

  6. Sincronització i estats UX: Mentre esperem resposta de les APIs, l’objecte global window.eyeWeather mostra un estat de “????️ Cercant GPS” per indicar activitat. Si alguna de les crides falla, l’App entra en “Mode Offline (Sense dades)”, però evita trencar el flux visual del temporitzador i manté un estat de seguretat per a l’usuari.

2.3. Funcionalitats Natives

A part de la geolocalització, s’han integrat tres funcionalitats hardware per a l’experiència d’alerta:

    • Llanterna (@capawesome/capacitor-torch): Des de sketch.js es controla una màquina d’estats (0 = off, 1 = llum fixa, 2 = estroboscòpic) i, en funció del temps restant, es fan crides a window.setFlashMode() exposada per main.js. Aquest pont és el que activa o desactiva realment el maquinari de la llanterna al dispositiu.

    • Haptics (@capacitor/haptics): main.js encapsula el patró de vibració en bucle (setInterval) quan sona l’alarma final, i sketch.js hi accedeix mitjançant window.dropappVibrate() per donar feedback tàctil immediat en prémer els botons.

    • Local Notifications: Per garantir l’avís fins i tot si el dispositiu entra en repòs profund (Doze Mode), main.js programa l’alarma en el moment d’iniciar el comptador utilitzant la propietat allowWhileIdle i canals prioritaris. Això delega la responsabilitat al sistema operatiu, assegurant la puntualitat exacta independentment de l’estat de l’app.

Integració final mostrant ubicació i dades ambientals.

Temporitzador acabat, moment on està activat el flash, la vibració i on apareix la notificació

2.4. Mode de proves (interval 0)

Durant el desenvolupament ha estat necessari verificar el comportament del temporitzador i de les alertes sense haver d’esperar una hora real entre dosis. Per això, al fitxer sketch.js he implementat un mode prova:

  • L’usuari pot configurar les “Hores entre dosis” a 0 mitjançant la finestra de configuració.

  • Si el valor llegit des de localStorage és 0, l’aplicació interpreta que es tracta d’un mode debug i força l’interval intern a 1 minut (60 * 1000 ms) en lloc d’1 hora (60 * 60 * 1000 ms).

  • Això permet testejar totes les fases de l’alarma (pre-avís, estroboscopi, vibració i notificació) de forma ràpida, mantenint la configuració normal per a l’ús mèdic real.

D’aquesta manera puc comprovar el flux complet de l’app (UI p5.js + hardware + notificacions) sense haver d’esperar períodes llargs.

2.5. Resiliència i Gestió del Segon Pla

Per garantir que l’usuari rebi l’avís de la gota encara que el sistema operatiu “congeli” el JavaScript de l’App per estalviar bateria (comú en Android), he implementat una Estratègia Híbrida de Resurrecció:

  • Alarmes Exactes Nativament: He configurat els permisos SCHEDULE_EXACT_ALARM i USE_EXACT_ALARM a l’AndroidManifest. Això permet que el sistema natiu de notificacions desperti el dispositiu en el moment precís, independentment de l’estat de l’App.

  • Sincronització al Resume: Al fitxer main.js, he programat un listener del cicle de vida (resume). Quan l’usuari obre l’App des de la notificació, el codi compara immediatament el Timestamp actual amb l’hora objectiu emmagatzemada a localStorage.

  • Feedback Immediat: Si el temps s’ha esgotat durant el segon pla, l’App reactiva instantàniament les funcions de hardware (flash estroboscòpic i vibració) i l’estat d’alarma visual a p5.js, garantint una transició fluida i sense errors entre el segon pla i el primer pla.

3. Decisions Preses i Solució de Problemes

Un aspecte clau del disseny va ser la interpretació visual de les dades (Data Viz). No volia que l’usuari hagués d’interpretar xifres fredes. Per això, vaig implementar una lògica reactiva: si l’API d’Open-Meteo detecta una humitat inferior al 35% (llindar de risc per ull sec), l’app canvia automàticament el text d’estat a “Ambient SEC (Risc sequetat ocular)” i tenyeix la gota central de color ambre/groc. Això ofereix un diagnòstic visual instantani sense necessitat de lectura atenta.

Un altre repte va ser la gestió de noms de ciutat llargs al canvas. Ciutats com ” Colinas del Campo de Martín Moro” es tallaven visualment, així que al sketch.js vaig optar per utilitzar caixes de text delimitades que permeten el salt de línia automàtic (text wrapping), mantenint el text sempre centrat i llegible.

La latència de les APIs també va aparèixer com a problema: com que es fan dues crides externes (BigDataCloud + Open-Meteo), el temps de resposta podia allargar-se. Per evitar la sensació que l’app s’havia penjat, vaig afegir un estat intermedi “Cercant GPS…” a l’objecte global window.eyeWeather, de manera que l’usuari rep feedback immediat mentre arriba la informació.

Pel que fa als permisos de GPS, a Android 13 l’app fallava si no tenia ACCESS_FINE_LOCATION declarat al AndroidManifest.xml. A més d’afegir el permís, vaig introduir un timeout de 10 segons a la crida de geolocalització per evitar bloquejos en entorns amb mala cobertura de satèl·lits.

També es va detectar un crash relacionat amb les notificacions per falta del recurs gràfic de la icona. La solució va passar per generar el recurs ic_stat_dropapp dins de res/drawable i referenciar-lo a la configuració de LocalNotifications.schedule(), assegurant que Android disposa sempre d’una icona vàlida per mostrar l’avís.

Finalment, per garantir que l’alerta fos efectiva en entorns sorollosos o quan el dispositiu no és a la vista, es va decidir combinar tres canals: l’efecte visual de la llanterna i el fons “d’alarma”, la vibració repetida amb @capacitor/haptics i la notificació de sistema. Aquesta decisió reforça l’ús del maquinari natiu i millora l’accessibilitat general de l’app.

Objecte global:

window.eyeWeather: Sincronitza dades API entre main.js (productor) i sketch.js (consumidor), amb estats UX (“Cercant GPS…”, “Mode Offline”).

Finestra flotant de la configuració i cerca de GPS

4. Pròxims Passos: Publicació i Beta

Per convertir aquest MVP en una versió final preparada per a la Google Play Store, els passos següents són:

  • Optimització de Processos en Background: Tot i que l’estratègia actual de resurrecció és efectiva, el següent pas tècnic seria implementar @capacitor/background-runner. Això permetria que l’App seguís consultant l’API meteorològica de forma invisible per a l’usuari, actualitzant el diagnòstic climàtic fins i tot si l’App no s’obre en tot el dia.

  • Signatura de l’Aplicació (Keystore): Cal generar un Android App Bundle (.aab) signat criptogràficament des d’Android Studio. Aquest és un requisit indispensable de seguretat per identificar-me com a desenvolupador oficial davant de Google.

  • Privacitat i GDPR: Atès que l’App fa ús de geolocalització precisa i APIs de tercers (BigDataCloud/Open-Meteo), és necessari redactar i enllaçar una Política de Privacitat formal. En ella s’ha d’especificar que les coordenades GPS s’utilitzen de manera efímera i no s’emmagatzemen en servidors externs.

  • Proves en més dispositius: Tot i que el funcionament en Android 15 (Xiaomi) és correcte, caldria testejar l’App en versions anteriors d’Android (11 i 12) per assegurar que la gestió de permisos natius es comporta de manera consistent.

5. Referències i Recursos Utilitzats

Serveis de Dades (APIs)

  • Open-Meteo Weather API: Servei de dades meteorològiques (Humitat Relativa) sota llicència Creative Commons (CC BY 4.0). [https://open-meteo.com/]

  • BigDataCloud Reverse Geocoding: API client-side per a la transformació de coordenades a toponímia. Versió gratuïta per a desenvolupadors. [https://www.bigdatacloud.com/]

Tecnologies i Llibreries

  • Nucli: Capacitor (v6) per a la compilació nativa Android.

  • Visualització: p5.js (Processing Foundation) per al renderitzat del canvas i animacions.

  • Plugins de Sistema:

    • @capacitor/geolocation: Accés al GPS natiu.

    • @capacitor/local-notifications: Gestió d’avisos en segon pla.

    • @capacitor/haptics: Motor de vibració del dispositiu.

    • @capawesome/capacitor-torch: Control del maquinari de la llanterna (Flash).

Repositori del Projecte

  • Codi Font: Repositori públic disponible a GitHub: [https://github.com/Javime85/DropApp-PR2]

Debat0el DropApp – PR2

No hi ha comentaris.

Publicat per

PR2 – SKIP BALLS

Publicat per

PR2 – SKIP BALLS

Vídeo de presentación del proyectoEn este vídeo se muestra el funcionamiento de la App. Ideación de la App y selección de la…
Vídeo de presentación del proyectoEn este vídeo se muestra el funcionamiento de la App. Ideación de la App y…

Vídeo de presentación del proyecto
En este vídeo se muestra el funcionamiento de la App.

Ideación de la App y selección de la API externa

Para continuar con la segunda práctica, como se pedía en el enunciado, debía integrar una API externa que aportara valor real a la aplicación desarrollada en la PR1. Desde el inicio tuve claro que no quería añadir la API como un simple “extra técnico”, sino que debía integrarse de forma coherente dentro del concepto del juego.

Mi primera idea, antes incluso de investigar APIs existentes, fue que los skins de los robots pudieran obtenerse externamente. Sin embargo, rápidamente descarté esta opción, ya que el diseño del juego requiere que los personajes tengan ruedas y no animaciones de caminar, lo que hacía muy compleja la integración de assets externos sin romper la coherencia visual y funcional del juego.

La segunda idea fue utilizar la API Astronomy Picture of the Day de la NASA para mostrar un fondo distinto cada día. Llegué incluso a obtener el token y hacer pruebas, pero finalmente descarté esta opción al darme cuenta de que trabajar con imágenes tan pesadas no era adecuado para un juego arcade sencillo, además de suponer una carga innecesaria de procesamiento en cada iteración del draw.

Finalmente opté por integrar JokeAPI, una API que ofrece chistes de forma ligera y rápida. Me pareció una opción especialmente adecuada para un MVP, ya que aporta personalidad y trasfondo a los personajes sin interferir en la jugabilidad. En un juego arcade tan simple, dotar de carácter a los robots suma valor a la experiencia del usuario. Además, esta idea deja abierta una clara evolución futura del proyecto, como la creación de un lore o guion más elaborado, o personajes con estilos de humor propios que se puedan desbloquear progresivamente.

Para reforzar esta idea, el usuario puede seleccionar desde el panel de ajustes la categoría de humor que desea, y será el robot el que le “cuente” el chiste, reforzando la sensación de personaje con identidad propia.

Consultas y pasos seguidos en la documentación de Capacitor

El primer uso de Capacitor en el proyecto fue Capacitor Motion, utilizado para leer la inclinación del dispositivo móvil y permitir el movimiento lateral del jugador. Esta funcionalidad se encapsuló en un módulo externo motion.js, que posteriormente se importaba en sketch.js. En concreto, se utilizó Motion.addListener('orientation',
callback)
, obteniendo el valor gamma para controlar el movimiento izquierda/derecha.

En una iteración posterior del proyecto fue necesario mantener la pantalla encendida mientras el usuario jugaba, ya que en partidas largas la pantalla se apagaba automáticamente. Para solucionar esto se utilizó Capacitor Keep Awake, llamando a KeepAwake.keepAwake() desde setup(). Para asegurar la persistencia de los datos a largo plazo se decidió utilizar Capacitor Preferences. A diferencia del almacenamiento temporal, Preferences garantiza que los datos no se pierdan entre sesiones. Este plugin se utilizó para guardar información como la categoría de chistes seleccionada, opciones de audio, personajes desbloqueados, nombre de usuario y ranking de puntuaciones. La lógica se centralizó en el módulo storage.js, utilizando Preferences.get() y Preferences.set().

Durante el desarrollo surgió un problema con el audio: la música continuaba sonando aunque el usuario cambiara de aplicación o apagara la pantalla. Para gestionar correctamente estos estados se utilizó Capacitor App, empleando App.addListener('appStateChange',
callback)
para pausar música y lógica del juego cuando la app pasaba a segundo plano.

Para mejorar el feedback durante la partida se integró Capacitor Haptics. Se creó un módulo específico haptics.js, desde el que se utilizó Haptics.impact() para acciones suaves como recoger monedas y Haptics.vibrate() con duración para el Game Over. En este punto fue importante adaptar la implementación a las limitaciones del dispositivo utilizado.

Finalmente, la integración de la API externa se realizó mediante fetch, gestionando todas las llamadas de forma asíncrona con async/await.

El flujo de desarrollo fue el habitual con Capacitor:

  • En ordenador: npm run dev
  • En dispositivo real (Xiaomi Redmi Note 12): npm run
    build
    , npx cap sync android, npx cap open
    android

Pruebas con funcionalidades nativas del dispositivo

Para ajustar el control por inclinación fue necesario realizar múltiples pruebas, afinando la velocidad del movimiento del jugador hasta encontrar un equilibrio entre sensibilidad y control. Se limitaron los rangos de inclinación para evitar comportamientos erráticos.

En el caso de Haptics, el dispositivo Xiaomi utilizado no diferenciaba claramente entre los estilos light, medium y heavy. Por este motivo se optó por combinar Haptics.impact() para acciones leves y Haptics.vibrate() con duración para eventos importantes como la muerte del jugador, ofreciendo así un feedback más claro.

Integración de la API: dinamismo y control de errores

Cuando el jugador pierde la partida al colisionar con una bomba, el juego realiza una petición a la API de forma dinámica. Esta llamada depende directamente de la configuración del usuario, ya que la categoría de chistes seleccionada se inyecta como parámetro en la URL de la petición.

Dado que la conexión a internet puede fallar o no estar disponible, se implementó un sistema de fallback. Si la petición a la API falla o tarda demasiado, el juego utiliza una base de datos interna de chistes almacenados localmente, garantizando que la experiencia no se rompa.

Aunque la API permite seleccionar idioma, la cantidad de chistes disponibles en castellano era muy limitada, por lo que se decidió mantener los chistes en inglés, manteniendo el resto de la interfaz del juego en castellano.

Decisiones y aprendizajes

Inicialmente la idea era mostrar los chistes durante la partida cada vez que el jugador recogía una moneda. Sin embargo, tras probarlo, se comprobó que esta decisión rompía el ritmo del juego y perjudicaba la experiencia de usuario. Finalmente se optó por mostrar los chistes únicamente en la pantalla de Game Over, momento en el que el jugador puede leerlos con calma.

Fue necesario implementar un sistema claro de estados del juego: pantalla de inicio, juego activo, Game Over y ajustes. Esto permitió que el proyecto funcionara como un MVP estable y comprensible.

Durante el desarrollo también fue clave aprender a depurar directamente en Android mediante Logcat, ya que muchos errores no eran visibles desde las herramientas de desarrollo del navegador.

Problemas, soluciones y conclusiones

Uno de los problemas más complejos fue gestionar correctamente el audio y la persistencia de datos asíncronos. Al tener el código distribuido en varios módulos y trabajar de forma intermitente en el tiempo, fue necesario apoyarse en flags, pruebas controladas y herramientas de IA como Gemini y Copilot para identificar rápidamente los errores.

Como conclusión, este proyecto ha servido para consolidar el flujo completo de desarrollo de una app híbrida con Capacitor, desde la ideación hasta la distribución, y para entender la importancia de diseñar bien un MVP antes de escalar funcionalidades.

Bocetos, capturas e iteraciones

Primeros bocetos para la implementación de los chistes:

Resultados del estado de inicio de la app, estado de juego, Game Over y settings:

Fuentes y citaciones

Vínculo al .apk

https://github.com/elecinas/skip-balls/releases/download/v1.0/app-debug.apk

Debat0el PR2 – SKIP BALLS

No hi ha comentaris.