Skip to main content

Tarea 2 - Build Systems

Imagina que trabajas en una empresa de desarrollo de software que desea construir una plataforma para monitorear el clima en distintas ciudades. El equipo ha decidido comenzar con una prueba de concepto enfocada en Santiago, Chile, utilizando la API pública de Open-Meteo.

Como parte del equipo, se te ha asignado la responsabilidad de:

  • Diseñar la estructura inicial del proyecto con una buena separación de responsabilidades.
  • Implementar dos aplicaciones simples que consulten y muestren el clima actual, una en grados Celsius y otra en Fahrenheit.
  • Encapsular la lógica compartida en una biblioteca reutilizable.
  • Automatizar parte del flujo de desarrollo mediante tareas y plugins de Gradle que analicen el código en busca de problemas comunes o malas prácticas.

Este ejercicio simula un entorno de trabajo real donde se espera que combines habilidades técnicas (Kotlin, Gradle, modularización) con buenas prácticas de diseño de proyectos mantenibles y extensibles.

Dependencias entre partes

Esta tarea está dividida en tres partes:

  • P1 es independiente y puede resolverse por separado. Su objetivo es practicar la estructura de proyectos multi-módulo y la reutilización de código entre aplicaciones.
  • P2 introduce una tarea personalizada de Gradle para análisis de código.
  • P3 extiende la lógica de P2 encapsulándola en un plugin configurable. Por tanto, P3 depende directamente de lo implementado en P2.
tip

Puedes comenzar con P1 o con P2, según te resulte más cómodo. Sin embargo, para completar P3 necesitas tener una solución funcional de P2.

P1 [2 pts] – Proyecto Multi-Módulo con Lógica Compartida

Construirás un proyecto Gradle multi-módulo utilizando Kotlin DSL. El objetivo es reutilizar código entre dos pequeñas aplicaciones que consultan el clima actual en Santiago, Chile, usando la API pública de Open-Meteo.

Requisitos

  1. Inicializa un proyecto Gradle multi-módulo con Kotlin DSL.
  2. Crea tres módulos:
    • lib: biblioteca compartida que encapsula la lógica común.
    • app-celsius: aplicación que muestra el clima de Santiago en grados Celsius.
    • app-fahrenheit: aplicación que muestra el clima de Santiago en grados Fahrenheit.
  3. Usa el plugin application en cada app para facilitar su ejecución.
  4. En settings.gradle.kts, registra todos los módulos y define el nombre del proyecto.
  5. Centraliza las versiones y dependencias utilizando un archivo libs.versions.toml o un bloque común.
  6. Incluye las dependencias necesarias para cada módulo en su respectivo build.gradle.kts.
  7. Separa correctamente la lógica en sus módulos correspondientes, de acuerdo a la lógica de negocios y aplicación.

API sugerida

Consulta el clima actual en Santiago mediante la siguiente URL:

https://api.open-meteo.com/v1/forecast?latitude=-33.45&longitude=-70.66&current=temperature_2m&temperature_unit=celsius

Recomendación: OkHttp + org.json

OkHttp es una biblioteca moderna para realizar peticiones HTTP. Puedes combinarla con org.json, una biblioteca liviana que permite analizar respuestas JSON sin anotaciones ni configuración especial.

Eres libre de usar cualquier otra biblioteca si lo prefieres, mientras hagas la configuración apropiada con Gradle.

Ejemplo: consultar un usuario con JSONPlaceholder

import okhttp3.OkHttpClient
import okhttp3.Request
import org.json.JSONObject

val client = OkHttpClient()

fun getUsername(userId: Int): String {
val url = "https://jsonplaceholder.typicode.com/users/$userId"

val request = Request.Builder().url(url).build()
val response = client.newCall(request).execute()

if (!response.isSuccessful) error("Request failed with code: ${response.code}")

val body = response.body?.string() ?: error("Empty response body")
val json = JSONObject(body)

// Extraemos el nombre de usuario del campo "username"
return json.getString("username")
}
¿Qué debes aprender de esto?
  • Puedes construir URLs manualmente como https://.../recurso?id=valor.
  • Las respuestas se acceden como objetos JSONObject, donde usas:
    • getString("clave"), getInt("clave"), getDouble("clave") según el tipo.
    • getJSONObject("clave") para acceder a objetos anidados.
  • Puedes usar este patrón para adaptarlo fácilmente a otras APIs, como la de Open-Meteo.

Ejemplo de ejecución esperada (app-celsius):

The current temperature in Santiago is 22.1°C

Ejemplo de ejecución esperada (app-fahrenheit):

The current temperature in Santiago is 71.8°F

Cómo ejecutar tus aplicaciones

Para probar cada módulo de aplicación (app-celsius y app-fahrenheit), debes configurar el plugin application en sus respectivos archivos build.gradle.kts.

Asegúrate de definir la clase principal con:

application {
mainClass.set("tu.paquete.PrincipalKt")
}

Reemplaza tu.paquete.PrincipalKt por el nombre completo de la clase que contiene tu función fun main.

Luego, puedes ejecutar la app desde la raíz del proyecto con:

./gradlew :app-celsius:run
./gradlew :app-fahrenheit:run

Esto compilará y ejecutará la aplicación correspondiente.

P2 [2 pts] – Tarea de Gradle

Crea una tarea personalizada que realice un análisis básico del código fuente. Esta tarea debe inspeccionar recursivamente todos los archivos .kt dentro de un directorio y generar un informe con:

  • Comentarios TODO o líneas sospechosas (FIXME, HACK, etc.), detectadas mediante expresiones regulares configurables.
  • Cantidad de funciones declaradas por archivo.
Hint

Para encontrar coincidencias puedes usar Regex.findAll:

val regex = todoRegex.get().toRegex()
val lines = file.readLines()
val matches = mutableListOf<Pair<Int, String>>()

for (i in lines.indices) {
val line = lines[i]
if (regex.findAll(line).any()) {
matches.add(i to line)
}
}

Esto te da una lista con número de línea y contenido donde se encontraron coincidencias.

note

La secuencia que retorna findAll() se puede convertir a lista con .toList() o filtrar con .any().

Parámetros esperados

  • todoRegex: String: expresión regular para detectar TODOs.
  • suspiciousRegex: String: expresión regular para FIXME, HACK, etc.
  • sourceDir: File: directorio raíz a analizar.
  • reportFile: File: archivo donde se escribirá el informe.

Ejemplo de uso

tasks.register<AnalyzeSourcesTask>("analyzeSources") {
todoRegex.set("TODO")
suspiciousRegex.set("FIXME|HACK")
sourceDir.set(file("src/main/kotlin"))
reportFile.set(file("build/reports/analysis-report.txt"))
}

Ejemplo de salida esperada

File: UserService.kt
- Function count: 5
- TODO found at line 42: // TODO: refactor this logic

File: AuthValidator.kt
- Function count: 2
- Suspicious line at line 17: // FIXME: this is a hack

P3 [2 pts] – Plugin Avanzado

Encapsula la lógica de análisis en un plugin Gradle reutilizable.

Objetivo

Implementar un plugin que:

  • Registre automáticamente una tarea de análisis en los proyectos que lo apliquen.
  • Exponga una extensión con propiedades configurables:
    • maxTodoAllowed: máximo número de TODOs permitidos.
    • enableRegexCheck: activa/desactiva los chequeos con expresiones regulares.
  • Adapte su comportamiento según la configuración. Si el número de TODOs supera el umbral, la tarea debe fallar.

Requisitos

  • Implementa la clase CodeAnalysisPlugin.
  • Define una extensión (CodeAnalysisExtension) con propiedades configurables.
  • La tarea debe:
    • Emitir un informe con los datos recolectados.
    • Lanzar un GradleException si el número de TODOs excede el umbral.

Ejemplo de configuración

codeAnalysis {
maxTodoAllowed.set(5)
enableRegexCheck.set(true)
}

Ejemplo de salida

File: UserService.kt
- Function count: 5
- TODO found at line 42: // TODO: refactor this logic
- TODO found at line 57: // TODO: add logging

File: AuthValidator.kt
- Function count: 2
- Suspicious line at line 17: // FIXME: this is a hack

Summary:
- Total TODOs found: 3
- Max allowed: 2 ❌