Kotlin Lab

Aprender / Proyectos capstone

Proyectos capstone

Los niveles del laboratorio te dan los ladrillos. Los proyectos te obligan a construir una casa. No hagas los seis: elige uno por fase y llévalo hasta el final.

Cómo elegir

Si quieres…Haz…
afianzar Compose y UIProyecto 1
dominar dominio y testsProyecto 2
practicar Android de verdadProyecto 3
aprender KMPProyecto 4
aprender backend KotlinProyecto 5
demostrar que sabes trabajar en un repo ajenoProyecto 6

Regla: un proyecto terminado vale más que cuatro empezados. “Terminado” significa: compila, tiene tests, tiene README y puedes explicarlo en 3 minutos.


Proyecto 1 — Portar una pantalla Flutter a Compose

Objetivo: traducir a Compose una pantalla real de una app Flutter tuya (de trabajo o personal) sin copiar la estructura de Flutter.

Alcance mínimo

  • Una pantalla con lista y detalle, o formulario con validación.

  • Tres estados: Loading, Content, Empty/Error.

  • Navegación entre lista y detalle.

  • @Preview para cada estado.

  • Un test de UI o de ViewModel.

  • Accesibilidad básica: contentDescription, tamaños táctiles, contraste.

Restricciones

  • Nada de lógica de negocio dentro de @Composable.

  • El estado se modela con un data class inmutable.

  • Los eventos suben; el estado baja.

  • No uses remember para estado que deba sobrevivir a navegación.

Archivos de referencia en Kontor

  • feature/accounts/src/main/kotlin/com/rubengmlp/kontor/feature/accounts/AccountsScreen.kt

  • feature/accounts/src/main/kotlin/com/rubengmlp/kontor/feature/accounts/AccountsViewModel.kt

  • core/designsystem/src/main/kotlin/com/rubengmlp/kontor/core/designsystem/components/KontorComponents.kt

Entregable

  • Código en un módulo nuevo o en una carpeta experiments/ de Kontor.

  • Capturas o GIF de los tres estados.

  • README con: qué pantalla, qué decisiones tomaste, qué cambió respecto a Flutter.

  • Tests verdes.

Criterios de aceptación

  • [ ] La UI no conoce el repositorio.

  • [ ] El ViewModel no conoce Compose.

  • [ ] Hay un estado por caso, no cinco booleanos.

  • [ ] Los previews no necesitan backend.

  • [ ] El test falla si rompes el estado vacío.

  • [ ] Puedes explicar remember vs ViewModel sin mirar.

Tiempo estimado: 8–12 horas.

Stretch: animaciones, tema oscuro, layout para tablet, screenshot tests.


Proyecto 2 — Librería de dominio bancario (JVM)

Objetivo: construir un núcleo de dominio testeable, sin Android, que podría compartirse con KMP.

Alcance mínimo

  • Money con céntimos, suma, resta y formato local.

  • Iban con normalización y validación de checksum.

  • TransferRequest, TransferValidation y TransferValidator.

  • Account con saldo y saldo disponible.

  • Un caso de uso SubmitTransfer que devuelva Result<TransferReceipt>.

  • 20 tests, incluidos bordes: cero, negativo, IBAN inválido, saldo insuficiente, referencia larga.

Restricciones

  • Nada de Double para dinero.

  • Nada de !!.

  • Los errores esperados se modelan como datos, no como excepciones.

  • El dominio no depende de Room, Retrofit ni Android.

Dónde construirlo

  • Nuevo módulo projects/banking-domain dentro de kotlin-lab, o un repo aparte.

  • Reutiliza los niveles 3, 4 y 7 (el Iban con smart constructor) como punto de partida.

Entregable

  • README con el modelo y un diagrama pequeño.

  • Tests verdes.

  • Un ADR de una página: “por qué céntimos y no BigDecimal en la API”.

Criterios de aceptación

  • [ ] Money no se puede construir con un Double accidentalmente.

  • [ ] El validador devuelve todos los motivos, no solo el primero.

  • [ ] El caso de uso es independiente de la UI.

  • [ ] Hay tests de límites, no solo del camino feliz.

  • [ ] Puedes explicar por qué Result no es lo mismo que una excepción.

Tiempo estimado: 6–10 horas.

Stretch: property-based testing con Kotest, serialización JSON, módulo KMP.


Proyecto 3 — Kontor: transferencias programadas

Objetivo: añadir una feature completa a Kontor: programar una transferencia para una fecha futura, listarla y cancelarla.

Alcance mínimo

  • Modelo ScheduledTransfer en core/domain.

  • Entidad y DAO en core/data/local.

  • Migración de Room.

  • Repositorio y caso de uso.

  • ScheduledTransfersViewModel con UiState.

  • Pantalla Compose con lista, estado vacío y cancelación.

  • Validación: fecha futura, importe positivo, saldo suficiente, IBAN válido.

  • Tests de dominio, ViewModel y DAO.

  • Navegación desde la pantalla de transferencias.

Archivos de referencia

  • core/domain/src/main/kotlin/com/rubengmlp/kontor/core/domain/model/Transfer.kt

  • core/data/src/main/kotlin/com/rubengmlp/kontor/core/data/local/TransferDao.kt

  • core/data/src/main/kotlin/com/rubengmlp/kontor/core/data/local/KontorDatabase.kt

  • feature/transfer/src/main/kotlin/com/rubengmlp/kontor/feature/transfer/TransferViewModel.kt

  • app/src/main/kotlin/com/rubengmlp/kontor/navigation/Routes.kt

Restricciones

  • La transferencia programada no mueve dinero al crearse.

  • Cancelar no borra el historial: marca estado CANCELLED.

  • La UI no calcula validaciones; usa el validador de dominio.

  • La fecha se guarda como Instant o epoch millis, no como String.

Entregable

  • Feature funcionando en el emulador.

  • Migración probada.

  • Tests verdes.

  • Nota de 10 líneas: qué harías distinto en producción (idempotencia, reintentos, WorkManager, backend autoritativo).

Criterios de aceptación

  • [ ] No se puede programar en el pasado.

  • [ ] Cancelar una transferencia ya ejecutada falla con un error claro.

  • [ ] La lista reacciona a cambios de Room.

  • [ ] El ViewModel no usa GlobalScope.

  • [ ] Hay un test que cubre la migración o el DAO.

  • [ ] La pantalla tiene estados loading/empty/content/error.

Tiempo estimado: 12–18 horas.

Stretch: recurrencia mensual, notificaciones, confirmación biométrica, idempotency key.


Proyecto 4 — Kotlin Multiplatform: núcleo compartido

Objetivo: compartir Money e IbanValidator entre Android y JVM sin duplicar lógica.

Alcance mínimo

  • Módulo shared-core con commonMain.

  • Money, IbanValidator y un TransferValidator en común.

  • Tests en commonTest.

  • Consumo desde un pequeño programa JVM.

  • Consumo desde Kontor o desde un módulo Android de prueba.

  • README con el grafo de dependencias.

Restricciones

  • commonMain no puede usar APIs de Android ni de JVM.

  • Si necesitas fecha/hora, usa kotlinx-datetime o pasa el dato desde fuera.

  • No compartas UI en esta fase; comparte dominio.

Entregable

  • Módulo compilando para JVM y Android.

  • Tests comunes verdes.

  • Explicación de expect/actual y de cuándo no usarlo.

Criterios de aceptación

  • [ ] El dominio compartido no importa android.*.

  • [ ] Los tests se ejecutan en JVM.

  • [ ] Kontor o el programa JVM usan el módulo real, no una copia.

  • [ ] Puedes explicar qué código no merece la pena compartir.

Tiempo estimado: 10–16 horas.

Stretch: Ktor client compartido, SQLDelight, publicación local del módulo.


Proyecto 5 — API Ktor de cuentas y transferencias

Objetivo: construir el backend que Kontor merece, con tests y errores bien modelados.

Alcance mínimo

  • GET /accounts

  • GET /accounts/{id}

  • POST /transfers

  • GET /transfers/{id}

  • DTOs con kotlinx.serialization.

  • Validación de IBAN, importe y saldo.

  • Errores con código y mensaje: 400, 404, 409, 422.

  • Repositorio en memoria.

  • Tests con testApplication.

  • README con ejemplos curl.

Restricciones

  • No devuelvas 200 con un error dentro.

  • No uses Double para dinero.

  • El servidor valida otra vez; no confía en el cliente.

  • Los tests no dependen de una base de datos real.

Entregable

  • API ejecutable con ./gradlew run.

  • Colección de ejemplos o curl.

  • Tests verdes.

  • Nota sobre idempotencia y concurrencia.

Criterios de aceptación

  • [ ] Un importe negativo devuelve 422.

  • [ ] Un IBAN inválido devuelve 422.

  • [ ] Una transferencia sin saldo devuelve 409.

  • [ ] Un id inexistente devuelve 404.

  • [ ] Hay un test por código de error.

  • [ ] El README se puede seguir sin preguntarte nada.

Tiempo estimado: 8–14 horas.

Stretch: autenticación, idempotency key, paginación, OpenAPI, PostgreSQL.


Proyecto 6 — Contribución open source

Objetivo: demostrar que puedes entrar en un código que no escribiste, entenderlo y mejorarlo.

Alcance mínimo

  • Encuentra un issue pequeño en un repo Kotlin/Android (documentación, bug acotado, test que falta).

  • Reproduce el problema.

  • Escribe un test que falle.

  • Haz el cambio mínimo.

  • Abre un PR con descripción, evidencia y limitaciones.

Dónde buscar

  • Issues con etiqueta good first issue en repos de Kotlin, Ktor, Compose, Room o librerías que uses.

  • No empieces por un repo enorme si es tu primera contribución.

  • Evita issues sin respuesta durante meses.

Entregable

  • Enlace al issue y al PR.

  • Nota: qué entendiste mal al principio, cómo lo depuraste, qué aprendiste del maintainer.

  • Aunque el PR no se mergee, el análisis cuenta.

Criterios de aceptación

  • [ ] El test falla antes del cambio.

  • [ ] El cambio es pequeño y explicable.

  • [ ] No mezclas refactors no relacionados.

  • [ ] La descripción del PR explica el “antes” y el “después”.

  • [ ] Respetas el estilo y las convenciones del repo.

Tiempo estimado: 10–20 horas.

Stretch: revisar el PR de otra persona, escribir documentación, participar en una discusión de diseño.


README de portfolio

Cada proyecto debe tener un README con estas secciones:

# Nombre

## Qué hace
Dos frases. Sin marketing.

## Demo
GIF, captura o enlace.

## Arquitectura
Módulos, capas y flujo de datos. Un diagrama vale más que 20 líneas.

## Decisiones
- Por qué céntimos y no Double.
- Por qué Room es la fuente de verdad.
- Por qué el ViewModel no conoce Compose.

## Tests
Qué cubren y qué no.

## Cómo ejecutarlo
Comandos exactos.

## Límites
Qué no hace, qué harías con más tiempo, qué es demo y qué es producción.

## Qué aprendí
Tres cosas concretas, no “aprendí mucho”.

Cómo contarlo en una entrevista

Usa este guion de 3 minutos:

  1. Problema: “Kontor necesitaba transferencias programadas y no había forma de cancelarlas.”

  2. Diseño: “Modelé el estado como sealed, guardé la fecha como Instant y usé Room como fuente de verdad.”

  3. Decisión difícil: “Elegí no ejecutar la transferencia en el cliente; el backend sería autoritativo.”

  4. Prueba: “Tengo tests de dominio, ViewModel y DAO; el de cancelación cubre el caso de estado inválido.”

  5. Límite: “No implementé reintentos ni idempotencia real; lo siguiente sería una idempotency key y WorkManager.”

Si no puedes contar el límite, no has entendido el proyecto.

Fuente: docs/PROJECTS.md