El Pokémon del día en el núcleo
Al terminar: Elegir el Pokémon del día de forma determinista y modelar el permiso de notificaciones.
Empieza una función nueva de la app: cada día, un Pokémon distinto, con un aviso en el móvil. Suena a servidor, a base de datos y a notificaciones «push» (las que un servidor manda al móvil cuando quiere). No es nada de eso.
La decisión que hace sencilla toda esta parte del curso se toma en esta lección y cabe en una frase: el Pokémon del día es una función pura de la fecha. Sin red, sin servidor y sin guardar nada. Dos móviles con la misma fecha muestran el mismo Pokémon, y eso permite programar el aviso de dentro de seis días sin consultar a nadie.
Aquí se instala una librería para manejar fechas, se escribe esa función, se prueba en las tres plataformas y se modela el permiso de notificaciones con los tres estados que de verdad tiene, que no son dos.
El concepto · Un Pokémon por día, sin servidor
Hay dos formas de elegir «el Pokémon de hoy». La primera es sortearlo: cada vez que alguien pregunta, se tira un dado. La segunda es calcularlo: se parte de algo que sabemos todos (la fecha de hoy) y se hace una cuenta. Solo la segunda sirve, y la razón se ve con un ejemplo ajeno al proyecto: el menú del día de un comedor.
Una función es pura cuando su resultado depende solo de lo que recibe: con la misma entrada da siempre la misma salida, y no mira la hora, ni la red, ni un dado. Es como una tabla de multiplicar: 7 × 8 es 56 hoy, mañana y en cualquier móvil.
import kotlin.random.Random
// El menú del día de un comedor, de dos formas.
val platos = listOf("lentejas", "paella", "sopa", "tortilla", "macarrones")
// Calculado: depende solo del número de día. Mismo día, mismo plato.
// `%` da el resto de dividir: 12 entre 5 sobra 2, así que el día 12 es platos[2].
fun platoCalculado(dia: Int): String = platos[dia % platos.size]
// Sorteado: cada llamada tira un dado nuevo.
fun platoSorteado(): String = platos[Random.nextInt(platos.size)]
fun main() {
// List(3) { … } crea una lista de 3 elementos; el bloque calcula cada uno.
println(List(3) { platoCalculado(dia = 12) }) // tres veces el día 12
println((12..16).map { platoCalculado(it) }) // cinco días seguidos
println(List(3) { platoSorteado() }) // tres sorteos
}Esto es lo que sale (la tercera línea cambia en cada ejecución; dos ejecuciones dieron [paella, paella, macarrones] y [macarrones, sopa, tortilla]):
[sopa, sopa, sopa]
[sopa, tortilla, macarrones, lentejas, paella]
[paella, paella, macarrones]Fíjate en las dos primeras líneas: tres preguntas por el día 12 dan el mismo plato. En la tercera, cada sorteo es independiente: no hay nada que obligue a repetir ni a cambiar, así que en cada ejecución sale otra cosa. Con el sorteo, el usuario vería un Pokémon en la notificación y otro al abrir la app. Y con el cálculo hay otra cosa que se nota: la segunda línea sigue el orden de la lista, plato a plato. Es justo lo que arregla la mezcla del paso 2.
Un día de calendario no es un instante
Falta decidir qué recibe la función. Hay dos ideas que parecen la misma y no lo son:
- Un instante es un punto en el tiempo, igual en todo el mundo, como una marca en una cinta de vídeo.
- Una fecha de calendario (año, mes y día, sin hora) es lo que pone el calendario de tu cocina, y depende de dónde esté esa cocina.
Lo que enlaza las dos es la zona horaria: la hora oficial de un lugar. Cuando en Madrid son las nueve de la noche, en Tokio ya son las cinco de la mañana del día siguiente. El mismo instante es «14 de marzo» en un sitio y «15 de marzo» en otro.
Para trabajar con fechas, Kotlin necesita una librería (kotlinx-datetime, la instalas en el paso 1). Este ejemplo, también ajeno al proyecto, enseña sus piezas:
import kotlinx.datetime.DateTimeUnit
import kotlinx.datetime.LocalDate
import kotlinx.datetime.TimeZone
import kotlinx.datetime.plus
import kotlinx.datetime.toLocalDateTime
import kotlin.time.Instant
fun main() {
// Un día del calendario: año, mes y día. Sin hora y sin zona.
val cumple = LocalDate(2026, 3, 14)
println(cumple) // se imprime como año-mes-día
println(cumple.plus(30, DateTimeUnit.DAY)) // treinta días después
println(cumple.toEpochDays()) // días desde el 1 de enero de 1970
// Un instante: un punto en el tiempo, igual en todo el mundo.
// La «Z» del final quiere decir «hora UTC»: la hora de referencia mundial.
val instante = Instant.parse("2026-03-14T20:00:00Z")
// El mismo instante, visto desde dos cocinas distintas:
println(instante.toLocalDateTime(TimeZone.of("Europe/Madrid")).date)
println(instante.toLocalDateTime(TimeZone.of("Asia/Tokyo")).date)
}Esto es lo que sale:
2026-03-14
2026-04-13
20526
2026-03-14
2026-03-15toEpochDays() cuenta los días desde el 1 de enero de 1970, una fecha que los ordenadores usan como punto cero: el 14 de marzo de 2026 es el día 20526. Y las dos últimas líneas son el mismo instante visto desde Madrid y desde Tokio: dos fechas distintas. La Z de 2026-03-14T20:00:00Z quiere decir «hora UTC»: la hora de referencia mundial, la de Greenwich (Londres), contra la que se miden todas las zonas. Madrid va una hora por delante de ella en esas fechas (son las 21:00) y Tokio nueve (son las 05:00 del día siguiente).
Esto no se ejecuta en el Playground de Kotlin (no trae kotlinx-datetime): es un ejemplo para leer.
De aquí sale una regla de diseño. La función del Pokémon recibe una fecha de calendario y nada más. Convertir «ahora» en «qué día es hoy aquí» se hace fuera de la regla, con la zona horaria del móvil, y la regla recibe la fecha ya resuelta. Esa es la frontera: la lección de Android la cruza con una función hoy(), que escribirás allí y que más adelante, en la de trabajo en segundo plano, quedará en la carpeta daily para que todo lo que necesite saber qué día es la use.
¿Y si la regla tomara un instante? Cambiaría en cada llamada, porque los segundos no se repiten. Y aunque se redondeara al día de UTC, en Tokio el Pokémon cambiaría a las nueve de la mañana, en mitad del día, porque su día y el de Greenwich no empiezan a la vez. Por eso la regla toma una fecha, no un momento.
Paso 1 · Instala kotlinx-datetime
1 · Qué instalas y por qué ahora. kotlinx-datetime es la librería de JetBrains para fechas y horas en Kotlin Multiplatform. La regla de este paso necesita un tipo de «día del calendario» (LocalDate) y la biblioteca estándar de Kotlin no lo trae: kotlin.time da el instante, pero no el calendario ni la zona horaria.
2 · La versión, en [versions]. Abre gradle/libs.versions.toml y añade una línea en la sección [versions], justo debajo de la de datastore:
datetime = "0.8.0" # nombre de la versión: la línea de [libraries] lo citará con version.refLo que va detrás de # es un comentario: Gradle lo ignora y está para que lo leas tú. (El orden de las líneas dentro de una sección da igual; se dice «debajo de datastore» para que tu fichero quede como el de la guía.)
3 · La librería, en [libraries]. En esa sección, justo debajo de la línea androidx-datastore-preferences-core (la cabecera [libraries] ya existe; no la repitas):
# module = "grupo:nombre"; version.ref apunta a la línea de [versions]
kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "datetime" }La primera es un comentario y la segunda, la librería.
4 · No es un plugin, así que [plugins] y el build.gradle.kts de la raíz no se tocan.
5 · La dependencia, en el módulo que la usa. Abre shared/build.gradle.kts y, dentro de commonMain.dependencies { … }, justo debajo de la línea api(libs.androidx.datastore.preferences.core):
// api y no implementation: LocalDate aparecerá en la firma pública de
// PokemonOfTheDay, así que quien use `shared` (la app de Android) tiene
// que poder verlo. La diferencia está en «La primera dependencia».
api(libs.kotlinx.datetime)La diferencia entre api e implementation se explicó en La primera dependencia. Aquí la regla práctica: si un tipo de la librería sale en una función pública de shared, va con api.
6 · Sync Now, y comprueba. Pulsa Sync Now. Si sale en rojo, repasa la caja «Si Sync sale en rojo» de Los ficheros de Gradle de tu proyecto; el error típico aquí es libs.kotlinx-datetime con guion en vez de libs.kotlinx.datetime con punto. Para comprobar que llegó, en la Terminal, en la carpeta de tu proyecto:
./gradlew :shared:dependencies --configuration jvmCompileClasspath | grep datetimeLa primera parte pide a Gradle la lista de librerías con las que se compila la parte JVM de shared (jvmCompileClasspath), y | grep datetime deja solo las líneas que nombran datetime. Debe aparecer org.jetbrains.kotlinx:kotlinx-datetime:0.8.0.
Paso 2 · La regla: calculada, nunca sorteada
El archivo. PokemonOfTheDay.kt contiene una sola función: recibe una fecha y devuelve el número de un Pokémon (de 1 a 1025). Es la regla de toda la función del día.
Créalo. Clic derecho sobre la carpeta shared → New → File y escribe la ruta desde la carpeta del módulo (el método de El módulo compartido y la prueba de humo):
src/commonMain/kotlin/com/sebastiannarvaez/pokedex/feature/daily/PokemonOfTheDay.ktPlan B, si ese menú no te lo ofrece: en la Terminal, en la carpeta de tu proyecto, mkdir -p crea las carpetas que falten y touch el fichero vacío:
mkdir -p shared/src/commonMain/kotlin/com/sebastiannarvaez/pokedex/feature/daily
touch shared/src/commonMain/kotlin/com/sebastiannarvaez/pokedex/feature/daily/PokemonOfTheDay.ktAparece en shared/src/commonMain/kotlin/com/sebastiannarvaez/pokedex/feature/daily/. Escríbelo entero, con sus comentarios:
package com.sebastiannarvaez.pokedex.feature.daily
import kotlinx.datetime.LocalDate // «un día del calendario», sin hora ni zona
/**
* El Pokémon del día, calculado y no sorteado.
* Función pura de la fecha: no hace falta red, ni servidor, ni guardar nada.
*/
object PokemonOfTheDay {
/** Cuántos Pokémon numerados de forma continua hay en PokeAPI. */
const val TOTAL = 1025
/** Un número entre 1 y TOTAL a partir de la fecha. */
fun idParaFecha(fecha: LocalDate): Int {
// Los días transcurridos desde el 1 de enero de 1970. Ya es un Long (64 bits).
val dias = fecha.toEpochDays()
// Mezcla de bits, como las de las funciones hash: multiplica, desplaza
// (shr) y combina (xor). No hace falta seguir la cuenta: solo sirve para
// que días seguidos no den números seguidos.
var x = dias * 2_654_435_761L
x = x xor (x shr 16)
x *= 2_246_822_519L // x *= n es lo mismo que x = x * n
x = x xor (x shr 13)
// `and` con esta máscara se queda con 31 bits: el resultado nunca es negativo
// (la explicación está debajo del código).
val positivo = (x and 0x7FFF_FFFFL)
// Resto de dividir entre 1025 (0..1024) y +1 para que sea 1..1025.
return (positivo % TOTAL).toInt() + 1
}
}La mezcla de bits, sin seguir la cuenta. Un número Long se guarda como 64 casillas de 0 y 1 (bits). shr 16 corre esas casillas 16 sitios a la derecha, xor compara dos números casilla a casilla y pone 1 donde son distintas, y and con una máscara deja pasar solo las casillas que la máscara tiene a 1. 0x7FFF_FFFFL es un número escrito en hexadecimal (base 16, por eso lleva 0x delante): es el mayor positivo de 31 bits; la L final dice que es Long y los _ solo separan cifras para leerlo mejor, como el punto de los miles.
Por qué «nunca negativo». La primera casilla de un Long es la del signo: si vale 1, el número es negativo. Al multiplicar números tan grandes el resultado se sale de lo que cabe en 64 casillas y da la vuelta sin avisar (se llama desbordamiento, y en Kotlin no es un error), así que x puede acabar siendo negativo. La máscara se queda solo con las 31 casillas de abajo y deja la del signo a 0: positivo nunca es negativo, y el resto de dividir un número no negativo entre 1025 tampoco.
Hay dos decisiones ahí que parecen manía y no lo son.
Random no vale. Es lo primero que se piensa y es exactamente lo que rompe la función: una llamada daría 25 y la siguiente 803, así que el usuario vería un Pokémon en la notificación y otro al abrir la app. Es el sorteo del comedor de arriba.
El día no se usa tal cual. toEpochDays() avanza de uno en uno, así que dias % TOTAL + 1 (con el 11 de septiembre de 2026) daría 208 hoy, 209 mañana y 210 pasado. Se nota a los tres días: es la segunda línea del menú del comedor, plato a plato. La mezcla rompe esa secuencia sin dejar de ser determinista.
Paso 3 · Los tests que fijan lo que importa
El archivo. PokemonOfTheDayTest.kt guarda cuatro comprobaciones de la regla. Va en commonTest: se escribe una vez y se ejecuta en cada plataforma, como en Testing del núcleo compartido.
Créalo como el anterior (clic derecho sobre shared → New → File):
src/commonTest/kotlin/com/sebastiannarvaez/pokedex/feature/daily/PokemonOfTheDayTest.ktPlan B, desde la carpeta del proyecto:
mkdir -p shared/src/commonTest/kotlin/com/sebastiannarvaez/pokedex/feature/daily
touch shared/src/commonTest/kotlin/com/sebastiannarvaez/pokedex/feature/daily/PokemonOfTheDayTest.ktLos tests usan unas cuantas herramientas de listas. Tres las conoces: map (de Lambdas con listas: filter, map y reglas), count { } con un par abierto en dos nombres, como (a, b) -> (de Colecciones) y los rangos como 0..6 (de Bucles y tu primer test). Las otras cuatro son nuevas (List(n) { }, que ya salió arriba, distinct(), zipWithNext() y repeat(n) { }) y se ven aquí, con un ejemplo ajeno al proyecto:
fun main() {
// List(n) { … }: una lista de n elementos; la lambda calcula cada uno (it = posición).
val cuadrados = List(4) { it * it }
println(cuadrados)
// distinct(): la misma lista sin repetidos.
val tiradas = listOf(3, 5, 3, 3, 6)
println(tiradas.distinct())
// zipWithNext(): cada elemento emparejado con el siguiente.
val temperaturas = listOf(18, 21, 20, 25)
println(temperaturas.zipWithNext())
// (a, b) -> separa cada par en dos nombres; count { } cuenta los que cumplen.
val subidas = temperaturas.zipWithNext().count { (a, b) -> b > a }
println("subió $subidas veces")
// repeat(n) { … }: repite el bloque n veces.
var ahorro = 0
repeat(3) { ahorro += 10 }
println(ahorro)
}Esto es lo que sale:
[0, 1, 4, 9]
[3, 5, 6]
[(18, 21), (21, 20), (20, 25)]
subió 2 veces
30Pégalo en el Playground de Kotlin (play.kotlinlang.org) si quieres verlo funcionar. Ahora sí, el esqueleto y el test que protege la decisión de arriba:
package com.sebastiannarvaez.pokedex.feature.daily
import kotlinx.datetime.DateTimeUnit // la unidad «día» para sumar días a una fecha
import kotlinx.datetime.LocalDate
import kotlinx.datetime.plus // fecha.plus(3, DateTimeUnit.DAY)
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
class PokemonOfTheDayTest {
@Test
fun elMismoDiaDaSiempreElMismoPokemon() {
val fecha = LocalDate(2026, 9, 11)
// Diez llamadas, un resultado. Con `Random` esto fallaría.
val resultados = List(10) { PokemonOfTheDay.idParaFecha(fecha) }.distinct()
assertEquals(1, resultados.size)
}
}Los tres siguientes van dentro de la clase, debajo del primero. El segundo comprueba lo que ningún compilador puede comprobar: que la secuencia no se note.
@Test
fun diasSeguidosNoDanNumerosSeguidos() {
val base = LocalDate(2026, 9, 11)
val ids = (0..6).map { PokemonOfTheDay.idParaFecha(base.plus(it, DateTimeUnit.DAY)) }
// Sin la mezcla, el identificador avanzaría de uno en uno.
val consecutivos = ids.zipWithNext().count { (a, b) -> b == a + 1 }
assertTrue(consecutivos <= 1, "la secuencia se nota: $ids")
}zipWithNext() da los pares (día 1, día 2), (día 2, día 3)… y (a, b) -> los separa en dos nombres: a es el de un día y b el del siguiente. Con el 11 de septiembre de 2026 como base salen [248, 61, 129, 336, 167, 748, 255]: ningún par seguido.
Fíjate en que el test no exige cero consecutivos, sino «a lo sumo uno». Las fechas son fijas y el resultado no cambia de una ejecución a otra, pero con 1025 números posibles, que dos días caigan seguidos alguna vez es normal (en cinco años de fechas pasa una vez). Si exigieras cero, el test dependería de la coincidencia de estas siete fechas: cambiarías una constante de la mezcla y fallaría sin que la regla estuviera peor.
El tercero barre cinco años de fechas para que ninguna se salga del rango:
@Test
fun siempreCaeDentroDelRangoValido() {
// Cinco años de fechas: ninguna puede dar 0 ni pasarse del total.
var fecha = LocalDate(2026, 1, 1)
repeat(5 * 365) {
val id = PokemonOfTheDay.idParaFecha(fecha)
assertTrue(id in 1..PokemonOfTheDay.TOTAL, "fuera de rango en $fecha: $id")
fecha = fecha.plus(1, DateTimeUnit.DAY)
}
}Ese + 1 del final de la regla es el que convierte un resto de 0..1024 en un identificador de 1..1025. Sin él, un día de cada mil la app pediría el Pokémon número 0, que no existe.
El cuarto fija dos números concretos. Es el que prueba que las tres plataformas calculan lo mismo:
@Test
fun daElMismoNumeroEnTodasLasPlataformas() {
// Este test corre en la JVM, en Android y en iOS. Si la mezcla usara una
// aritmética que se comporta distinto en alguna, el número cambiaría.
assertEquals(248, PokemonOfTheDay.idParaFecha(LocalDate(2026, 9, 11)))
assertEquals(417, PokemonOfTheDay.idParaFecha(LocalDate(2026, 10, 1)))
}Si a ti te sale otro número, hay una cifra mal copiada en la mezcla del paso 2: es una comprobación gratis de que escribiste la misma función.
Ejecútalos en las tres plataformas. En la Terminal, en la carpeta de tu proyecto:
./gradlew :shared:jvmTest --tests '*PokemonOfTheDayTest*' :shared:testAndroidHostTest --tests '*PokemonOfTheDayTest*' :shared:iosSimulatorArm64Test --tests '*PokemonOfTheDayTest*'--tests filtra solo la tarea que tiene justo delante, así que se repite detrás de cada una. Si lo escribes una sola vez al final, las otras dos tareas ejecutarían todos los tests del proyecto. La última tarea solo corre en un Mac (necesita el simulador de iOS), que es lo que ya exige este curso.
Termina con BUILD SUCCESSFUL: los cuatro tests han pasado en la JVM, en Android (en tu ordenador, sin emulador) y en el simulador de iOS. Para verlo con tus ojos, abre el informe de una plataforma, por ejemplo open shared/build/reports/tests/jvmTest/index.html: arriba hay unos recuadros y el que lleva el rótulo tests dice 4. Son solo los de esta clase, porque el filtro --tests deja fuera el resto.
Provócalo tú, solo en la JVM. Cambia el 248 por 249 y lanza:
./gradlew :shared:jvmTest --tests '*PokemonOfTheDayTest*'Gradle marca PokemonOfTheDayTest[jvm] > daElMismoNumeroEnTodasLasPlataformas[jvm] FAILED y termina en BUILD FAILED. Devuélvelo a 248. (Con las tres plataformas a la vez, Gradle se para en la primera que falle y la línea cambia según cuál sea: por eso se provoca solo en una.)
Cuenta todos los tests. Lanza las tres tareas sin filtro y abre los informes, como en Ajustes en el núcleo:
./gradlew :shared:jvmTest :shared:testAndroidHostTest :shared:iosSimulatorArm64Test
open shared/build/reports/tests/jvmTest/index.html
open shared/build/reports/tests/testAndroidHostTest/index.html
open shared/build/reports/tests/iosSimulatorArm64Test/index.htmlEn el recuadro tests de cada informe salen 80 en la JVM, 44 en Android host y 47 en iOS: los 76 / 40 / 43 que traías más los cuatro de PokemonOfTheDayTest en cada plataforma.
¿Por qué no coinciden? Los 44 de commonTest (los que se escriben una vez) corren en las tres plataformas. La JVM suma además los que viven en la carpeta jvmTest, escritos solo para ella (los de la base de datos y los de Koin, entre otros): 36 más, por eso 44 + 36 = 80. iOS suma 3 de la carpeta iosTest (44 + 3 = 47) y Android host no suma ninguno.
Paso 4 · El permiso tiene tres estados, no dos
El archivo. EstadoDelPermiso.kt es la lista cerrada de situaciones en las que puede estar el permiso. Pedir el permiso es cosa de cada sistema (cada lección de plataforma lo hará con sus llamadas), pero nombrar las situaciones es vocabulario común: las dos apps (y sus pantallas de Ajustes) hablan de los mismos tres estados. Por eso vive en el núcleo aunque pedir el permiso no. El texto que ve el usuario en cada uno vive en cada app, en su idioma y con su estilo.
Créalo como los anteriores (clic derecho sobre shared → New → File, con la ruta src/commonMain/kotlin/com/sebastiannarvaez/pokedex/feature/daily/EstadoDelPermiso.kt; el plan B es el mismo mkdir -p y touch, ahora con EstadoDelPermiso.kt). Queda en shared/src/commonMain/kotlin/com/sebastiannarvaez/pokedex/feature/daily/EstadoDelPermiso.kt:
package com.sebastiannarvaez.pokedex.feature.daily
/**
* El estado del permiso de notificaciones, en lenguaje común.
* Los tres existen en las dos plataformas aunque se llamen distinto.
*/
enum class EstadoDelPermiso {
/** Todavía no se ha preguntado. Se puede pedir. */
SIN_PREGUNTAR,
/** Concedido. */
CONCEDIDO,
/**
* Denegado. Tras decir que no (en iOS, una vez; en Android, dos), el sistema
* ya no enseña el diálogo: hay que mandar al usuario a los ajustes del sistema.
*/
DENEGADO,
}Tratarlo como un booleano —lo tengo o no lo tengo— produce la app que pide el permiso una y otra vez sin que salga ningún diálogo, porque el sistema ya no lo enseña. Con tres estados, la pantalla puede decir la verdad: «toca para activar» en uno y «están desactivadas, ve a ajustes» en el otro.
Punto de control
gradle/libs.versions.tomltienedatetime = "0.8.0"en[versions]ykotlinx-datetimeen[libraries], yshared/build.gradle.ktstieneapi(libs.kotlinx.datetime)encommonMain. Sync Now terminó sin errores rojos.- En
shared/src/commonMain/kotlin/com/sebastiannarvaez/pokedex/feature/daily/estánPokemonOfTheDay.ktyEstadoDelPermiso.kt, y enshared/src/commonTest/kotlin/com/sebastiannarvaez/pokedex/feature/daily/,PokemonOfTheDayTest.kt. - El comando de las tres plataformas, con
--testsdetrás de cada tarea, termina enBUILD SUCCESSFULy el recuadro tests de cada informe dice 4. - Las tres tareas sin filtro terminan en
BUILD SUCCESSFUL, y los recuadros tests de los informes dan 80 en la JVM, 44 en Android host y 47 en iOS. PokemonOfTheDay.idParaFecha(LocalDate(2026, 9, 11))da 248 en las tres.EstadoDelPermisotiene tres valores, yDENEGADOno es lo mismo queSIN_PREGUNTAR.
Comprueba lo que entendiste
8 preguntas · necesitas 6 para completar la lección
1.¿Por qué el Pokémon del día se calcula y no se sortea con
Random?2.¿Para qué sirve la mezcla de enteros que se aplica a los días desde 1970?
3.¿Por qué la regla toma un
LocalDatey no un instante?4.¿Qué distingue
DENEGADOdeSIN_PREGUNTARenEstadoDelPermiso?5.¿Por qué
EstadoDelPermisovive encommonMainsi pedir el permiso es cosa de cada sistema?6.¿Por qué
kotlinx-datetimese instala conapiy no conimplementation?7.¿Qué demuestra el test que fija
248para el 11 de septiembre de 2026 y se ejecuta en la JVM, en Android y en iOS?8.Lanzas los tests con
--tests '*PokemonOfTheDayTest*'y el informe de la JVM dice 4. ¿Por qué no 80?