Un cliente me contrato para construir su producto desde cero. Sin equipo. Sin codigo heredado. Solo un brief, confianza, y la responsabilidad de tomar todas las decisiones tecnicas yo solo.
Waliki es un planificador de viajes movil. Exploras destinos, descubres sitios a traves de un catalogo filtrado, y montas itinerarios dia a dia que puedes compartir con quien te acompane. O con quien estes intentando convencer de que te acompane.
Disene la UI, elegi el stack, defini la arquitectura, y lo lleve a produccion: app movil en React Native (Expo), panel de admin en Next.js, Supabase como backend, paquete compartido para tipos y validaciones Zod, todo orquestado con Turborepo.

La app funciona offline. De verdad.
Puedes editar todo tu itinerario en modo avion. Anadir sitios, reordenar el dia, borrar ese restaurante del que cambiaste de opinion a las 2am. La UI no se inmuta porque cada cambio aterriza primero en la cache local, optimisticamente. Cuando recuperas wifi, el onlineManager de TanStack Query reenvía las mutaciones pausadas contra Supabase. Un banner abajo te dice “3 cambios pendientes” para que nunca estes adivinando. Testeado extensamente en vuelos reales donde deberia haber estado durmiendo.
La parte complicada fueron los IDs temporales. Creas una actividad offline, recibe un UUID temp_. El servidor asigna el real en el sync. La cache reconcilia en el settle. Me llevo unas cuantas iteraciones hacerlo bien, y unas cuantas mas dejar de sonar con race conditions.

Caching de catalogo que no molesta
Los datos de catalogo (destinos, lugares, categorias) cachean 24 horas. Pero no queria que los usuarios vieran contenido de ayer si algo habia cambiado. Asi que en cada launch y cada vez que la app vuelve a primer plano, una sola llamada RPC (get_catalog_versions) devuelve max(updated_at) por tabla. Comparo con las versiones locales e invalido solo las queries de tablas que realmente cambiaron. La mayoria de sesiones: cero invalidaciones. La app se siente instantanea y los datos estan frescos.
Localizacion sin boilerplate
El contenido en Supabase es { es: "...", en: "..." }. Lo obvio es meter un getLocalizedText() en cada campo de cada componente. Lo intente un dia antes de cansarme de teclear lo mismo. Asi que escribi un proxy que envuelve cada servicio. Los componentes reciben strings directamente, ya en el idioma correcto. Cambias de idioma, se invalida la cache, listo. La pereza es una virtud cuando produce mejor arquitectura.
Pipeline de imagenes con variantes
Cuando un admin sube una foto, el dashboard genera variantes por tamano (avatar 96px, card 800px, detail 1200px) y las guarda en Cloudflare R2. En la app, AutoImage elige el fichero correcto segun un prop preset. Nadie descarga un original de 4000px para un thumbnail. Tu plan de datos me lo puede agradecer despues. Blurhash rellena el hueco mientras carga.
Que mas hay dentro
- Sistema de suscripciones con RevenueCat (planes mensual/anual, periodos de prueba, gestion de problemas de pago)
- Viajes colaborativos con links de invitacion e itinerarios compartidos
- Busqueda de vuelos para anadir trayectos
- Abstraccion de mapas cross-platform (Apple Maps en iOS, Google Maps en Android, discusiones sobre cual es mejor: en todas partes)
- Dashboard de admin con CRUD completo para el catalogo, pipeline de procesamiento de imagenes incluido
- Onboarding con seleccion de destinos y recomendaciones personalizadas