Somos una plataforma de intercambio local que facilita el trueque de objetos entre vecinos. Con un mapa interactivo en tiempo real, los usuarios publican anuncios, descubren intercambios disponibles en su área y coordinan encuentros para realizar trueques de manera segura.
Impulsamos una economía circular basada en el intercambio responsable, donde cada objeto encuentra un nuevo propietario en lugar de convertirse en residuo.
[SM] Scrum Master malmorox
[CM] Cloud Master CodeByChriss
[DS] Designer aiitttor
El diseño inicial de la aplicación ha sido desarrollado en Figma, donde se ha definido la estructura visual, los flujos de navegación y las principales pantallas de la app.
🔗 Enlace al prototipo en Figma
El desarrollo se organizó en 4 sprints a lo largo de 3 meses, siguiendo la metodología ágil SCRUM para facilitar la planificación, el seguimiento y la entrega incremental de funcionalidades.
Durante el primer sprint se sentaron las bases del proyecto, centrando el trabajo en la definición visual y conceptual de la aplicación.
Objetivos alcanzados:
- Definición de la idea y alcance del proyecto (con ayuda del profesor).
- Creación del prototipo de la aplicación en Figma.
- Diseño inicial de pantallas.
En el segundo sprint se ha comenzado con el desarrollo técnico del proyecto y la implementación de la interfaz.
Objetivos en alcanzados:
- Creación del proyecto en GitHub.
- Implementación de las pantallas de:
- Splash
- Login
- Registro
- Diseño y uso de temas/estilos en Compose.
En el tercer sprint se amplía la funcionalidad de la aplicación desarrollando la navegación completa, optimizando la interfaz para diferentes dispositivos y añadiendo características de accesibilidad e internacionalización.
Objetivos alcanzados:
-
Desarrollo de la interfaz principal
- Menús de navegación y fragments/tabs (no hacen falta más activities).
- Sistema completo de navegabilidad entre pantallas.
-
Optimización del diseño
- Adaptación a distintas densidades de pantalla y orientaciones.
- Layouts responsivos.
-
Internacionalización y temas
- Soporte multiidioma.
- Modo claro/oscuro (Day/Night).
En el cuarto sprint se ha llevado a cabo la integración completa del sistema, la resolución de incidencias detectadas y la preparación de la versión final para su distribución pública.
Objetivos alcanzados:
-
Integración completa del proyecto
- Conexión definitiva entre frontend (Jetpack Compose), backend (Firebase) y almacenamiento (Supabase).
- Integración del mapa interactivo con publicaciones en tiempo real.
- Resolución de conflictos y errores derivados de la integración de módulos.
-
Pruebas, validación y documentación
- Pruebas funcionales de navegación, autenticación y publicación de truekes.
- Validación de flujos completos de trueke.
- Corrección de bugs detectados durante el testing.
- Elaboración de informes técnicos y documentación del proyecto.
-
Publicación y distribución
- Generación de versión release firmada.
- Configuración de ficha en Google Play.
- Subida y despliegue de la aplicación en producción.
🚀 Release alpha publicada en Google Play
Cada intercambio pasa por los siguientes estados:
OPEN ──────────► RESERVED ──────────► COMPLETED
│ │ │
Visible en Acuerdo entre Intercambio
el mapa dos usuarios exitoso
OPEN→ El anuncio está publicado y visible en el mapa para todos los usuarios.RESERVED→ Dos usuarios han acordado el intercambio. El anuncio desaparece del mapa general.COMPLETED→ El trueke se ha realizado. Queda registrado en el historial de ambos perfiles.
Este apartado documenta las principales librerías utilizadas en el proyecto y cómo se han implementado.
Propósito: Implementación de un mapa interactivo para visualizar ubicaciones de Trueke.
Funcionalidades implementadas:
- Mapa interactivo integrado con Jetpack Compose.
- Marcadores personalizados (
Marker) con colores del tema de la aplicación.import com.mapbox.maps.extension.compose.annotation.Marker
- Sistema de clicks en marcadores que despliega un Bottom Sheet con información detallada del Trueke.
- Animaciones suaves de cámara (
flyTo) al seleccionar ubicaciones.
Funcionalidades implementadas:
- Carga asíncrona de imágenes de productos y avatares de usuario con
AsyncImage - Recorte de imágenes con formas personalizadas (circular para avatares, con bordes redondeados para productos)
- Ajuste automático del contenido con
ContentScale.Crop
AsyncImage(
model = item.imageUrls.first(),
contentDescription = "Imagen del producto",
modifier = Modifier
.size(80.dp)
.clip(CircleShape), // circular para avatares
contentScale = ContentScale.Crop
)https://github.com/zetbaitsu/Compressor
Propósito: Optimización de recursos multimedia mediante la reducción del peso de las imágenes antes de subirlas a Supabase.
Funcionalidades implementadas:
- Compresión adaptativa: Reducción de dimensiones a un máximo de 320px y calidad 80%.
- Integración con corrutinas: Procesamiento asíncrono de imágenes para evitar bloqueos en el hilo principal de la UI.
// Compresión de imagen antes de la subida
val compressedFile = Compressor.compress(context, originalFile) {
resolution(320, 320)
quality(80)
}Propósito: Incorporación de un conjunto ampliado de iconos Material para mejorar la experiencia visual y la claridad de la interfaz de usuario.
dependencies {
implementation("androidx.compose.material:material-icons-extended")
}Propósito: Gestión centralizada de autenticación y persistencia de datos en Firestore.
Funcionalidades implementadas:
- Autenticación híbrida y social: Integración de
FirebaseAuthpara registro con Email/Password y soporte paraGoogleAuthProvider. - Identificación dual de usuario: Sistema de inicio de sesión flexible que permite el acceso mediante correo electrónico o nombre de usuario, realizando consultas dinámicas en Firestore.
- Garantía de unicidad (transacciones): Uso de
db.runTransactionpara asegurar que no existan duplicados en la colección deusernamesdurante el registro o actualización. - Gestión automática de perfiles: Generación de nombres de usuario aleatorios con lógica de reintento automático para nuevos registros mediante proveedores externos (Google).
- Flujos ssíncronos con corrutinas: Implementación de
suspend functionsy extensión.await()para un manejo eficiente y no bloqueante de las tareas de Firebase. - Seguridad en el registro: Implementación de envío automático de correo de verificación tras la creación de cuenta exitosa.
Propósito: Almacenamiento y gestión de activos multimedia de alta disponibilidad mediante Supabase Storage.
Funcionalidades implementadas:
- Gestión de buckets: Configuración de contenedores públicos para el almacenamiento centralizado de avatares de usuario.
- Optimización de almacenamiento (Upsert): Implementación de lógica de subida con sobrescritura automática (
upsert = true) para minimizar el uso de cuota en el tier gratuito. - Políticas de seguridad (RLS): Configuración de Row Level Security para controlar los permisos de lectura y escritura de archivos desde el cliente móvil.
- Generación de URLs públicas: Obtención dinámica de enlaces permanentes para la persistencia de rutas de imagen en los perfiles de Firestore.
Además de las librerías externas, la aplicación incluye lógica desarrollada desde cero para cubrir necesidades específicas del proyecto.
Al publicar un producto, el usuario puede indicar su marca. Para facilitar esta tarea y mantener los datos limpios y consistentes, hemos desarrollado un buscador de marcas propio.
¿Cómo funciona?
La app incluye un catálogo interno de más de 200 marcas organizadas por categorías (tecnología, moda, hogar, motor, etc.). A medida que el usuario escribe, el buscador filtra las coincidencias en tiempo real y muestra sugerencias desplegables, sin necesidad de conectarse a ninguna API externa.
Además, el sistema corrige automáticamente el formato del texto (por ejemplo, convierte "sAmSuNg" en "Samsung") y elimina caracteres no válidos como emojis o símbolos especiales, asegurando que los datos guardados tengan siempre un aspecto uniforme.
Resultado: una experiencia de escritura más rápida y fluida, y una base de datos de productos más ordenada y coherente.
// Lógica de filtrado en tiempo real en BrandData.kt
fun search(query: String, limit: Int = 8): List<String> {
if (query.isBlank()) return emptyList()
return knownBrands
.filter { it.contains(query, ignoreCase = true) } // Búsqueda case-insensitive
.take(limit) // Limitamos resultados para optimizar la UI
}| Crear producto | Crear trueke | Truekes pendientes | Trueke al detalle |
|---|---|---|---|
|
|
|
|
| Crear producto | Encontrar trueke | Hacer propuesta | Esperar respuesta |
|---|---|---|---|
|
|
|
|
| Aceptar propuesta | Trueke aceptado | Marcar como completado | Trueke completado |
|---|---|---|---|
|
|
|
|












