Blog

Canción de la letra B, para aprender el abecedario con animales

En esta lección interactiva nos sumergiremos en el maravilloso mundo de la fauna salvaje infantil al ritmo de la letra B. Aquí encontrarás un recurso didáctico excepcional diseñado estratégicamente para la etapa de preescolar, educación infantil y primeros lectores.

Aprender la letra B nunca fue tan divertido

A través de rimas dulces, canciones pegadizas y juegos interactivos, los niños aprenderán a identificar la letra B y su sonido de manera natural, potenciando su conciencia fonológica, expandiendo su vocabulario y reforzando la estimulación lingüística tanto en el aula como en el hogar. ¡Acompaña a nuestros pequeños exploradores, sigue el ritmo y descubre qué animales se esconden en el bosque y en alta mar!

Objetivos pedagógicos de esta lección

  • Identificar el grafema y el fonema de la consonante B.
  • Fomentar la discriminación auditiva mediante rimas constantes y un ritmo alegre.
  • Expandir el vocabulario básico y el conocimiento del entorno natural (bosque, mar, pradera y desierto).
  • Desarrollar la atención y la retención mental con dinámicas de juego al final de la canción.

Vídeo de la letra B en YouTube

Marcas de tiempo interactivas (índice del vídeo)

Haz clic en cualquier marca de tiempo para ir directamente al momento exacto del vídeo en YouTube con el enlace ya activado:

  • 00:00 – Introducción: ¡La letra B!
  • 00:18 – El Búho, la Ballena y el Burro
  • 00:47 – ¡Baila con la B! (Estribillo)
  • 01:15 – El Bisonte, la Boa y el Buitre
  • 01:44 – ¡Sigue el ritmo! (Estribillo)
  • 02:13 – Juego de repaso: ¿Qué animal es?
  • 02:32 – Baile libre con la B
  • 02:58 – ¡Todos son geniales! (Despedida)

Letra de la canción

B, B, B, es una gran letra lista para usar.
Con los animales vamos a jugar.

El búho vigila desde su gran rama,
de noche no duerme, de día en la cama.
La inmensa ballena nada en alta mar,
lanza un chorro de agua y le gusta cantar.
El simpático burro es trabajador,
rebuzna muy fuerte, ¡es encantador!

Grandes y pequeños, de bosque o de mar,
su mundo salvaje vamos a explorar.
Tienen secretos y mucha emoción,
¡la vida animal es una gran lección!

B, B, B, baila con la B.
Busca por el bosque, dime tú qué ves.
Sigue bien el ritmo, suena fenomenal,
con la letra B, nombra un animal.
Sigue bien el ritmo, suena fenomenal,
con la letra B, nombra un animal.

El fuerte bisonte en la pradera está,
con sus grandes cuernos, caminando va.
La sigilosa boa trepa con destreza,
es una serpiente de mucha belleza.
El buitre planea buscando alimento,
usando sus alas a favor del viento.

Grandes y pequeños, de bosque o de mar,
su mundo salvaje vamos a explorar.
Tienen secretos y mucha emoción,
la vida animal es una gran lección.

B, B, B, baila con la B.
Busca por el bosque, dime tú qué ves.
Sigue bien el ritmo, suena fenomenal,
con la letra B, nombra un animal.
Sigue bien el ritmo, suena fenomenal,
con la letra B, nombra un animal.
¡Cuántos animales! ¡Son tan especiales!
Con la letra B, todos son geniales.

Los animales de la letra B

  • 🦉 Búho: Ave nocturna que vigila atentamente desde su gran rama.
  • 🐋 Ballena: Una inmensa criatura marina que nada en alta mar y se comunica cantando.
  • 🫏 Burro: Un personaje simpático, noble y muy trabajador que emite un fuerte rebuzno.
  • 🦬Bisonte: Un mamífero fuerte de la pradera que camina con grandes cuernos.
  • 🐍 Boa: Una serpiente sigilosa y de gran belleza que trepa árboles con destreza.
  • 🦅 Buitre: Ave de gran tamaño que planea por las alturas usando las alas a favor del viento.

Juego de adivinanzas interactivo: ¿quién es quién?

¡Es hora de poner a prueba la retención y la memoria de tus pequeños alumnos o hijos! Utiliza este divertido cuestionario basado en la lección:

  • Pregunta: ¿Quién nada en el mar?➡️ Respuesta: ¡La ballena!
  • Pregunta: ¿Quién vigila de noche?➡️ Respuesta: ¡El búho!
  • Pregunta: ¿Quién es trabajador?➡️ Respuesta: ¡El burro!
  • Pregunta: ¿Quién tiene grandes cuernos?➡️ Respuesta: ¡El bisonte!
  • Pregunta: ¿Quién es una serpiente?➡️ Respuesta: ¡La boa!
  • Pregunta: ¿Quién vuela en el viento?➡️ Respuesta: ¡El buitre!

    💡 Conclusión didáctica: Todos los nombres de estos increíbles y fabulosos animales empiezan con la letra B

    Sigue aprendiendo: el abecedario en español

    Para fomentar una sesión de estudio continua y un entorno digital controlado para niños, puedes acceder directamente a cualquiera de las lecciones de nuestra colección letra por letra:

    Maratón Completo: Todo el abecedario completo para niños

    Material educativo disponible en amazon

    Pasa de la pantalla al papel y refuerza el trazo de las letras, la grafomotricidad y la psicomotricidad fina con nuestros cuadernos impresos oficiales:

    • 📝 Cuaderno de caligrafía infantil: El recurso ideal para aprender a escribir las letras paso a paso de forma guiada y divertida. Consíguelo en Amazon aquí.
    • 🎨 Libro de ilustraciones a color y para colorear: Para que den vida a sus animales favoritos del alfabeto mientras fijan la correspondencia entre grafemas y fonemas. Consíguelo en Amazon aquí.

    🔔 ¡Apoya nuestro safari educativo! No olvides suscribirte al canal de YouTube y darle «Me gusta» 👍 a nuestros vídeos para no perderte ninguna aventura con las letras y los animales.

    Canción de la letra A, para aprender el abecedario con animales

    Bienvenidos a bordo de nuestro safari educativo. En este divertido vídeo musical para niños, acompañaremos a nuestro joven explorador en una emocionante aventura para descubrir el fantástico mundo de la letra A. Vamos a explorar la selva y la sabana para encontrar animales increíbles cuyos nombres empiezan con la primera vocal.

    Aprender el abecedario nunca fue tan divertido

    A través de dibujos coloridos tipo cómic y una canción pegadiza, los niños aprenderán a identificar la letra A y su sonido, mejorando su conciencia fonológica y conociendo las características más divertidas de estos animales. Este recurso es ideal para preescolar, primeros lectores y educación infantil, garantizando que aprendan el abecedario español de una forma interactiva, entretenida y con mucha estimulación lingüística.

    • 🐝 Una abeja recolectando polen.
    • 🕷️ Una araña tejiendo su red.
    • 🦅 Un majestuoso águila volando alto.
    • 🐿️ Una divertida ardilla buscando nueces.
    • 🦫 Un curioso armadillo que se hace una bola.
    • 🦩 ¡Y un veloz avestruz corriendo por la llanura!

    Vídeo de la letra A en YouTube

    Marcas de tiempo (índice del vídeo)

    Si deseas avanzar directamente a tu sección favorita, utiliza nuestra guía interactiva de tiempo:

    • 00:00 – Introducción: ¡A, A, A! La primera letra.
    • 00:19 – El águila, la abeja y la araña.
    • 00:34 – Con alas, con patas, o un caparazón.
    • 00:52 – Estribillo: ¡A, A, A! Letra genial.
    • 01:20 – El avestruz, la ardilla y el armadillo.
    • 01:34 – Cada animal es un gran campeón.
    • 01:52 – Estribillo: Dilo conmigo otra vez.
    • 02:12 – Juego de adivinanzas: ¿quién es quién?
    • 02:30 – Fin de la canción.

    Letra de la Canción

    Acompaña el ritmo de rap claro y constante cantando la letra completa con tus niños o alumnos:

    ¡A, A, A!
    ¡Es la primera letra, para empezar!
    ¡A, A, A!
    ¡Con los animales vamos a jugar!

    El águila observa muy alto en el cielo,
    y mira atenta lo que hay en el suelo.
    La abeja trabaja buscando el dulzor,
    hace la miel de la más bella flor.
    La araña teje su red con cuidado,
    moviendo sus patas de lado a lado.

    Con alas, con patas, o un caparazón,
    cada animal es un gran campeón.
    Sus rasgos y trucos debemos observar,
    ¡pues mucho nos van a enseñar!

    ¡A, A! Letra genial.
    ¡A, A! Nombra un animal.
    Dime qué hace, dime cómo es,
    dilo conmigo una y otra vez.
    (¡Otra vez, otra vez!)
    Dime qué hace, dime cómo es,
    ¡dilo conmigo una y otra vez!

    El gran avestruz es un ave que corre,
    no puede volar, ¡pero es como una torre!
    La inquieta ardilla se guarda una nuez,
    trepa las ramas con gran rapidez.
    El armadillo parece blindado,
    se hace una bola si está asustado.

    Con alas, con patas, o un caparazón,
    cada animal es un gran campeón.
    Sus rasgos y trucos debemos observar,
    ¡pues mucho nos van a enseñar!

    ¡A, A! Letra genial.
    ¡A, A! Nombra un animal.
    Dime qué hace, dime cómo es,
    dilo conmigo una y otra vez.
    (¡Otra vez, otra vez!)

    ¿Quién hace la miel? (¡La abeja!)
    ¿Quién teje la red? (¡La araña!)
    ¿Quién vuela alto? (¡El águila!)
    ¿Quién corre rápido? (¡El avestruz!)
    ¿Quién come nueces? (¡La ardilla!)
    ¡Todos empiezan con la letra A!

    Juego de adivinanzas interactivo: ¿quién es quién?

    Pon a prueba la retención escolar y la atención de los más pequeños con este divertido cuestionario final:

    • Pregunta: ¿Quién hace la miel? → Respuesta: ¡La abeja!
    • Pregunta: ¿Quién teje la red? → Respuesta: ¡La araña!
    • Pregunta: ¿Quién vuela alto? → Respuesta: ¡El águila!
    • Pregunta: ¿Quién corre rápido? → Respuesta: ¡El avestruz!
    • Pregunta: ¿Quién come nueces? → Respuesta: ¡La ardilla!

    💡 Conclusión didáctica: ¡Todos empiezan con la letra A!

    Sigue aprendiendo: el abecedario en español

    Fomenta una sesión de estudio continua y sin interrupciones accediendo directamente a todas las letras de nuestra colección:

    Material educativo disponible en amazon

    Pasa de la pantalla al papel y refuerza la grafomotricidad fina y el trazo con nuestros cuadernos oficiales impresos:

    • 📝 Cuaderno de caligrafía: Diseñado para guiar de forma interactiva la escritura paso a paso de las letras del abecedario. Consíguelo en Amazon aquí.
    • 🎨 Ilustraciones en color y para colorear: El complemento idóneo para dar vida a los animales favoritos mientras se asientan las bases de la lectura. Consíguelo en Amazon aquí.

    🔔 ¡Apoya nuestro safari educativo! No olvides suscribirte al canal y darle «Me gusta» 👍 al vídeo para no perderte ninguna de nuestras próximas aventuras con las letras y los animales.

    ¿Eres profesor o estudiante y quieres comprarte un ordenador?

    Comparativa rápida: Mac vs Windows

    A continuación tienes una comparación sencilla para ayudarte a decidir:

    Mac (Apple)

    • Sistema operativo muy optimizado
    • Excelente calidad en la fabricación y los materiales utilizados
    • Muy buena duración de batería
    • Ideal para estudiantes y tareas creativas
    • Precio generalmente más elevado (hasta ahora)

    Windows

    • Mayor variedad de marcas y precios
    • Más opciones de configuración
    • Compatible con una mayor cantidad de software
    • Ideal si buscas algo más económico o específico
    • Calidad variable según el fabricante

    Mac (Apple)

    Tienda online de Apple con los precios de educación:

    También los puedes comprar en k-tuin. Ofrecen los mismos productos, pero los precios pueden variar respecto a la tienda oficial de Apple:

    En la tienda oficial de Apple te pedirá confirmación de tu situación de profesor/a o estudiante, mediante correo electrónico (de Universidad, Conselleria de Educación, etc.). Es posible que te pidan algún documento adicional (nómina, etc.)

    En k-tuin el procedimiento es similar, pero previamente debes aplicar el cupón «EDUCPROMO» (antes de proceder al pago), y finalmente deberás aportar alguno de los siguientes justificantes:

    • Última nómina completa, en la que se especifique el Centro Educativo.
    • Certificado del centro con nombre, DNI, y fecha, indicando que es profesor del mismo vinculado con el proceso de enseñanza.
    • Carné de profesor en el que figure el nombre, el centro, y el año en curso.

    MacBook Neo

    Es el portátil de Apple con mejor relación calidad/precio que han fabricado hasta la fecha. Está resultando un éxito rotundo, con millones de unidades vendidas.

    Tiene una calidad premium y está disponible en cuatro colores desde sólo 599 € para profesores y estudiantes:

    El modelo básico resulta suficiente para la mayoría de los casos de uso, pero yo te recomiendo el de 699 €, ya que tiene el doble de almacenamiento:

    MacBook Air

    Tiene una calidad muy similar al MacBook Neo, pero ofrece mejores prestaciones (tiene más memoria RAM y mejor procesador). Lo puedes comprar desde 1089 €:

    Con Microsoft Windows

    Si prefieres un ordenador con el sistema operativo Windows, te recomiendo que tenga al menos 16GB de RAM y 512GB de disco duro. Y algunas de las marcas que suelo recomendar son: MSI, ASUS, GIGABYTE, HP, LENOVO, LG, SAMSUNG.

    A continuación te sugiero algunas tiendas online bastante populares.

    HP

    Ofrecen descuentos específicos para profesores y estudiantes:

    Y a continuación puedes encontrar las ofertas que tienen ahora mismo con las características recomendadas (deberás seleccionar manualmente el método de ordenación):

    MSI

    También ofrecen descuentos específicos para profesores y estudiantes:

    Y estos serían los ordenadores recomendados, ordenados de menor a mayor precio:

    PcComponentes

    Venden online desde Murcia. Tienen buen servicio y buenas opiniones de los clientes. No ofrecen descuentos específicos para profesores o estudiantes, pero sus precios habituales son muy competitivos. Aquí puedes encontrar un listado filtrado por los requisitos mínimos que te recomiendo, de menor a mayor precio:

    Amazon

    Suelen ofrecer muy buenos descuentos en algunos artículos, incluidos los ordenadores portátiles. Generalmente dichos descuentos suelen verse reflejados en las listas de los ordenadores más vendidos:

    Y este sería el listado de los ordenadores recomendados ordenados también según ventas:

    MediaMarkt

    Suelen ofrecer promociones con frecuencia. A continuación puedes encontrar un listado de los portátiles con los requisitos mínimos que te recomiendo:

    NeoByte

    No tienen descuentos específicos para profesores o estudiantes, pero sus precios son muy competitivos. Aquí tienes el listado de los ordenadores recomendados, ordenados por precio ascendente:

    CoolMod

    Muy popular entre estudiantes. Aunque no ofrecen descuentos específicos en este sector, ofrecen muy buenos precios. Aquí tienes el listado recomendado ordenado por precio ascendente:

    Consejos finales antes de comprar

    • Aprovecha descuentos para profesores o estudiantes siempre que sea posible.
    • Revisa opiniones y valoraciones antes de decidirte.
    • No te fijes solo en el precio: busca el mejor equilibrio calidad/precio. Ahora mismo el Macbook Neo de Apple ofrece la mejor relación calidad/precio en los portátiles para un uso estándar.
    • Piensa a medio plazo: un poco más de inversión ahora puede alargar la vida útil del equipo. En este aspecto los portátiles de Apple son más duraderos.
    • Memoria RAM: En un ordenador Mac, 8GB pueden resultar suficientes, pero en un ordenador con Windows recomiendo al menos 16GB.
    • Disco duro: Aunque 256GB pueden resultar suficientes para la mayoría de los casos, yo recomiendo al menos 512GB, sin importar la marca del ordenador.
    • Ten en cuenta que Apple ofrece un sistema operativo más cerrado. Si estás pensando en comprar un Mac y deseas instalar algún tipo de software específico, comprueba antes que el programa en cuestión esté disponible para Mac. En este sentido Windows suele ser mucho más estándar y permisivo, ya que la mayoría de las aplicaciones funcionan en el sistema operativo Windows sin problema.

    Generación y edición de imágenes con inteligencia artificial

    ChatGPT

    Se puede acceder online (https://chatgpt.com) y con la app:

    Gemini

    Se puede acceder online (https://gemini.google.com/app) y con la app:

    Qwen

    Se puede acceder online (https://chat.qwen.ai) y con la app:

    NotebookLM

    Se puede acceder online desde https://notebooklm.google.com/ y con la app:

    image.z.ai

    Se puede acceder online (https://image.z.ai/)

    Mistral

    Se puede acceder online (https://chat.mistral.ai/chat) y con la app:

    Grok

    Se puede acceder online (https://grok.com/) y con la app:

    ¿Qué IA es la mejor?

    Ranking mundial

    En la página web Arena podemos utilizar los modelos de inteligencia artificial más populares. Es una plataforma abierta y colaborativa que permite realizar comparaciones directas mediante “batallas” anónimas: el usuario introduce un prompt, recibe dos respuestas generadas por modelos distintos y elige cuál le parece mejor; luego se revelan las identidades de los modelos y el resultado se suma a un ranking público. Creada por investigadores de la Universidad de California en Berkeley, se ha convertido en un referente porque combina transparencia (sus datos se publican para investigación), participación comunitaria y alcance global, evaluando desde modelos de código abierto hasta prototipos pre-lanzamiento de gigantes como OpenAI, Google o Anthropic.

    Por ejemplo, en los siguientes enlaces se puede consultar el ranking mundial de los modelos de IA más populares para generación de texto en español, para resolución de problemas matemáticos, para escritura creativa, y de generación y edición de imágenes, según las valoraciones de los usuarios:

    Además, se pueden probar todos los modelos de forma gratuita a través de este enlace: https://arena.ai/?mode=direct

    Generación y edición de imágenes e infografías completas

    Ejemplos generados con ChatGPT utilizando imágenes reales (en diciembre del 2024, y agosto del 2025 respectivamente):

    Ejemplo generados con «Nano Banana» desde Google AI Studio con los siguientes prompts:

    • Crea un dibujo de un niño corriendo porque llega tarde al colegio y sus padres están detrás, con estilo manga.
    • Cambia la torre del reloj por la Torre Eiffel.

    Generación de vídeos con inteligencia artificial

    Vídeos a partir de imágenes

    Utilizando QWEN (https://chat.qwen.ai/)

    Su uso es gratuito para todos los servicios que ofrecen (generación de presentaciones, imágenes, vídeos, conversación compartiendo audio y vídeo en tiempo real, etc.). La aplicación de móvil no está disponible en España.

    Con voz en español

    El siguiente vídeo se ha generado utilizando estas instrucciones: Crea un vídeo animado muy gracioso de 6 segundos usando la imagen proporcionada como referencia visual principal. Mantén el estilo infantil dibujado a mano con rotuladores y colores vivos.

    • Segundo 0–2. La niña del dibujo cobra vida, mueve el brazo y dice con voz infantil y alegre en español: “¡Hola, soy Amapola!”
    • Segundo 2–4. El muñeco de nieve parpadea, sonríe exageradamente y responde con voz divertida: “¡Brrr, qué frío!” y empiezan a caer copos de nieve animados.
    • Segundo 4–6. El corazón del centro late rápido y brilla, el gorro del muñeco sale volando, el muñeco intenta atraparlo torpemente, y dice: “¡Uy!”, con risa infantil corta y música alegre de fondo.
    • Estilo: animación caricaturesca, infantil, movimientos exagerados, aspecto de dibujo hecho a mano.
    • Audio: voces claras en español, efectos cómicos (pop, boing suave), música alegre infantil.
    • Ambiente: tierno, familiar y muy divertido.

    Con voz en inglés

    No se proporcionaron instrucciones específicas para generar el siguiente vídeo. El modelo de inteligencia artificial eligió la animación, el idioma y las voces más adecuados.

    Utilizando FLOW (https://labs.google/flow/about)

    Se puede usar sin suscripción, con un límite de generación de vídeos cada día.

    El siguiente vídeo se ha generado utilizando simplemente la siguiente instrucción a partir de la imagen proporcionada: Genera un vídeo con los personajes cantando en español.

    El siguiente vídeo se ha generado utilizando simplemente la siguiente instrucción a partir de la imagen proporcionada: Haz que los libros bailen y canten en español.

    Utilizando Veo3 (https://gemini.google.com/)

    La generación de vídeo con Gemini sólo está disponible con la suscripción de pago. Se puede utilizar también desde la aplicación:

    No se proporcionaron instrucciones específicas para generar el siguiente vídeo. El modelo de inteligencia artificial eligió la animación más adecuada.

    Utilizando Grok (https://grok.com/)

    Actualmente la generación de vídeos solo está disponible con suscripción de pago. Se recomienda el uso desde la aplicación de móvil, que cuenta con muy buenas valoraciones:

    Con voz

    El siguiente vídeo se ha generado utilizando estas instrucciones: Crea un vídeo animado muy gracioso de 6 segundos usando la imagen proporcionada como referencia visual principal. Mantén el estilo infantil dibujado a mano con rotuladores y colores vivos.

    • Segundo 0–2. La niña del dibujo cobra vida, mueve el brazo y dice con voz infantil y alegre en español: “¡Hola, soy Amapola!”
    • Segundo 2–4. El muñeco de nieve parpadea, sonríe exageradamente y responde con voz divertida: “¡Brrr, qué frío!” y empiezan a caer copos de nieve animados.
    • Segundo 4–6. El corazón del centro late rápido y brilla, el gorro del muñeco sale volando, el muñeco intenta atraparlo torpemente, y dice: “¡Uy!”, con risa infantil corta y música alegre de fondo.
    • Estilo: animación caricaturesca, infantil, movimientos exagerados, aspecto de dibujo hecho a mano.
    • Audio: voces claras en español, efectos cómicos (pop, boing suave), música alegre infantil.
    • Ambiente: tierno, familiar y muy divertido.

    Sólo con música

    No se proporcionaron instrucciones específicas para generar el siguiente vídeo. El modelo de inteligencia artificial eligió la animación más adecuada.

    Con voz y ajustando el estilo de los personajes

    El siguiente vídeo se ha generado utilizando estas instrucciones: Usa la imagen como referencia principal y anima los personajes con estilo dibujo infantil colorido. Video corto de 6 segundos, tono muy gracioso y alegre.

    • La niña dibujada cobra vida y saluda moviendo la mano. El corazón del centro late y cambia de colores rápidamente. El muñeco de nieve mueve sus brazos y habla. Nieve cayendo suavemente.
    • Diálogo en español, voces infantiles divertidas:
      • Niña: “¡Hola, soy Amapola y este es mi mundo mágico!”
      • Muñeco de nieve: “¡Y yo traje nieve para jugar todo el año!”
    • Ambos ríen al final.
    • Animación fluida, estilo caricatura, colores brillantes, ambiente invernal festivo, final alegre y divertido.

    Vídeos a partir de cualquier fuente (texto, pdfs, otros vídeos, etc.)

    Utilizando NotebookLM (https://notebooklm.google.com/)

    Disponible también desde la aplicación de móvil:

    Utilizando NotebookLM + Google AI Studio (https://notebooklm.google.com/ + https://aistudio.google.com/generate-speech)

    Instrucciones utilizadas: Crea un cómic en español narrando la historia. Todo el texto debe aparecer en las viñetas del cómic, incluyendo diálogos relevantes. Cada diapositiva debe ser una página del cómic.

    Utilizando NotebookLM + Google Vids (https://notebooklm.google.com/ + https://workspace.google.com/intl/es/products/vids/)

    Utilizando Z.ai + Google Vids (https://chat.z.ai/ + https://workspace.google.com/intl/es/products/vids/)

    Seguridad contra nulos en Kotlin: Despídete del error del billón de dólares

    Introducción

    Si has llegado hasta aquí, ya sabes cómo crear tus propias clases, agrupar objetos y organizar tu código. ¡Enhorabuena! Pero hay un enemigo invisible que acecha a casi todos los programadores cuando empiezan a desarrollar aplicaciones reales: la ausencia de datos o, como se le conoce técnicamente, el valor null.

    En lenguajes más antiguos como Java, si intentas acceder a una propiedad de un objeto (por ejemplo, el correo electrónico de un usuario) y resulta que ese usuario no existe en la base de datos (es null), tu programa se cuelga abruptamente lanzando un fatídico NullPointerException. Es un fallo tan común, molesto y destructivo que a su propio inventor le gusta llamarlo «el error del billón de dólares».

    Aquí es donde Kotlin brilla con luz propia. Para evitar que tu aplicación explote en las manos de tus usuarios, Kotlin implementa en su núcleo la seguridad contra nulos (Null Safety). Este sistema es como un escudo protector que detecta los posibles problemas mientras escribes tu código (en tiempo de compilación), obligándote a manejarlos de forma elegante mucho antes de que el código llegue a ejecutarse.

    ¿Qué son los tipos anulables (Nullable types)?

    Por defecto, en Kotlin está estrictamente prohibido que una variable normal guarde un valor nulo. El compilador, simplemente, no te dejará hacerlo. Y esto es fantástico para dormir tranquilos.

    Sin embargo, a veces es necesario representar que algo «falta», está vacío o aún no se ha configurado (como un segundo apellido opcional en un formulario). Para decirle a Kotlin que una variable tiene permiso para ser nula, debes declarar un tipo anulable añadiendo explícitamente el símbolo de interrogación ? justo después de su tipo de dato.

    fun main() {
        // neverNull es un String normal, jamás aceptará nulos
        var neverNull: String = "Esto no puede ser nulo"
        
        // Si intentas hacer esto, el compilador te lanzará un error
        // neverNull = null
    
        // nullable es un String? (String anulable)
        var nullable: String? = "Aquí puedes guardar texto"
        
        // Esto es perfectamente válido
        nullable = null
    }

    Si intentas acceder directamente a una propiedad de una variable que podría ser nula, por ejemplo nullable.length, Kotlin te detendrá y te dará un error. ¿Por qué? Porque podría ser peligroso. Necesitas utilizar herramientas seguras.

    Comprobación tradicional de nulos

    La forma más básica de manejar un tipo anulable es usar una clásica estructura condicional if para asegurarte de que hay algo ahí dentro antes de usarlo.

    fun describirTexto(texto: String?): String {
        // Kotlin comprueba primero que la variable no esté vacía
        if (texto != null && texto.length > 0) {
            return "El texto tiene ${texto.length} caracteres"
        } else {
            return "El texto está vacío o es nulo"
        }
    }

    A tener en cuenta: Cuando realizas una comprobación manual como if (texto != null), el compilador de Kotlin es lo bastante inteligente como para aplicar un «smart cast» (conversión inteligente). Dentro de ese bloque if, Kotlin tratará automáticamente a tu variable anulable como si fuera totalmente segura, permitiéndote acceder a .length sin que te salte ningún error.

    Llamadas seguras (?.)

    Si tuviéramos que escribir un if cada vez que usamos algo que puede ser nulo, nuestro código sería larguísimo y muy difícil de leer. Afortunadamente, a Kotlin le encanta el código conciso.

    Para acceder de forma rápida a las propiedades de un objeto que podría contener un valor nulo, utilizamos el operador de llamada segura ?.

    Este operador lo hace todo por detrás: intenta leer la propiedad que le pides, y si resulta que el objeto original es nulo, en lugar de crashear tu aplicación, simplemente devuelve null de forma pacífica y sigue ejecutando el resto de tu código.

    fun longitudDelTexto(texto: String?): Int? = texto?.length
    
    fun main() {
        val miTextoNulo: String? = null
        println(longitudDelTexto(miTextoNulo)) 
        // Salida por consola: null
    }

    Una de las características más potentes de este operador es que las llamadas seguras se pueden encadenar. Si quieres acceder a un dato que se encuentra escondido muy profundamente en tu estructura de clases, puedes hacerlo así:

    val paisDelJefe = empleado.empresa?.departamento?.jefe?.pais

    Si el empleado no tiene empresa, o la empresa no tiene departamento, el viaje se detiene ahí mismo y la variable paisDelJefe guardará simplemente un null, sin pánicos ni errores de ejecución.

    El operador Elvis (?:)

    Muchas veces no queremos que el resultado final de nuestra llamada sea null. Es muy común querer proporcionar un valor por defecto si las cosas fallan. Para esto tenemos al espectacular operador Elvis ?: (se llama así porque si giras la cabeza a la izquierda, el símbolo recuerda al mítico tupé de Elvis Presley).

    La estructura es muy simple: escribes a la izquierda del Elvis lo que quieres intentar leer, y a la derecha escribes el valor de respaldo que quieres devolver en caso de que lo de la izquierda haya resultado ser nulo.

    fun main() {
        val texto: String? = null
        
        // Si texto?.length es nulo, usamos el 0 como comodín
        val longitud = texto?.length ?: 0
        
        println("La longitud es: $longitud")
        // Salida por consola: La longitud es: 0
    }

    La ejecución condicionada con let (?.let)

    ¿Qué ocurre si quieres ejecutar un bloque entero de código únicamente si tu variable no es nula? Combinando la llamada segura ?. con la función let, construimos un entorno hermético y totalmente blindado.

    fun main() {
        val correo: String? = "[email protected]"
        
        // Todo lo que hay dentro de las llaves solo se ejecuta si hay correo
        correo?.let { correoSeguro ->
            println("Enviando mensaje de bienvenida a $correoSeguro")
        }
    }

    La regla de oro: Acostúmbrate a usar siempre llamadas seguras ?. o el operador Elvis ?: para lidiar con tipos anulables. Únicamente hay un escenario en el que podrías forzar la ejecución del código, usando el operador de aserción !! (la doble exclamación). Este operador fuerza la lectura de la variable ignorando la seguridad; si el valor resulta ser nulo, tu aplicación se cerrará inmediatamente. Úsalo solo si pondrías la mano en el fuego de que ese dato existe, o mejor aún, ¡evítalo a toda costa en tu día a día!

    Ejercicios

    Aquí tienes varios ejercicios de menor a mayor dificultad (el primero es una traducción adaptada de la documentación oficial de Kotlin y el resto son extras para asentar los conceptos que hemos ampliado). Abre tu Kotlin Playground y realiza los siguientes ejercicios.

    El empleado sin sueldo

    Tienes la función employeeById que te da acceso a la base de datos de los empleados de una compañía según su ID. Por desgracia, la función devuelve un tipo Employee?, por lo que el resultado puede ser null si el empleado no existe. Tu objetivo es escribir el código de la función salaryById(id: Int) para que devuelva el salario del empleado si lo encuentra, o el valor 0 si el empleado ha desaparecido de los registros.

    data class Empleado(val nombre: String, var salario: Int)
    
    fun empleadoPorId(id: Int) = when(id) {
        1 -> Empleado("Pedro", 20)
        2 -> null
        3 -> Empleado("Juan", 21)
        4 -> Empleado("Ana", 23)
        else -> null
    }
    
    fun salarioPorId(id: Int): Int {
        // Escribe tu código aquí (Pista: usa llamada segura + operador Elvis)
    }
    
    fun main() {
        // Esto debería imprimir la suma total de los salarios existentes (64)
        println((1..5).sumOf { id -> salarioPorId(id) })
    }

    El formulario de contacto

    Imagina que estás programando un formulario de registro. Crea una función llamada obtenerLongitudTelefono que reciba un número de teléfono (String?) que puede ser nulo. Utiliza una llamada segura y el operador Elvis en una sola línea para devolver el número de caracteres del teléfono, o 0 si el usuario decidió no teclear ninguno.

    // Escribe aquí tu función obtenerLongitudTelefono
    
    fun main() {
        val telefono1: String? = "654321987"
        val telefono2: String? = null
    
        println(obtenerLongitudTelefono(telefono1)) // Debería imprimir 9
        println(obtenerLongitudTelefono(telefono2)) // Debería imprimir 0
    }

    La cadena de mando

    A veces los objetos contienen a otros objetos que, a su vez, también pueden ser nulos. Declara tres data classes:

    1. Jefe (con una propiedad nombre de tipo String).
    2. Departamento (con una propiedad jefe de tipo Jefe?).
    3. Empresa (con una propiedad departamento de tipo Departamento?).

    Luego, en tu función main(), crea una instancia de Empresa donde el departamento sea null. Usando llamadas seguras encadenadas, intenta recuperar el nombre del jefe. Si en algún momento la cadena se rompe por un nulo, haz que se asigne automáticamente el texto «Sin jefe asignado» usando el operador Elvis. Imprime el resultado final.

    // Escribe aquí tus clases
    
    fun main() {
        // 1. Crea una Empresa con departamento = null
        // 2. Encadena las llamadas e imprime el resultado
    }

    El sistema de alertas

    Tienes un mensaje de alerta opcional. Usando la llamada segura ?. junto a la función let, haz que el sistema imprima en mayúsculas (usando el método .uppercase()) el texto de la alerta solo y exclusivamente si este no es nulo.

    fun main() {
        val alertaActiva: String? = "Peligro de sobrecalentamiento en el núcleo"
        val alertaApagada: String? = null
    
        // Escribe aquí tu código usando let para procesar alertaActiva
        
        // Y luego repite exactamente lo mismo para alertaApagada 
        // (este segundo bloque no debería imprimir nada por consola)
    }

    Soluciones a los ejercicios

    No hagas trampas. Échale un vistazo a las soluciones únicamente cuando te hayas peleado un buen rato con tu editor de código y el compilador te haya gritado un par de veces.

    El empleado sin sueldo

    data class Empleado(val nombre: String, var salario: Int)
    
    fun empleadoPorId(id: Int) = when(id) {
        1 -> Empleado("Pedro", 20)
        2 -> null
        3 -> Empleado("Juan", 21)
        4 -> Empleado("Ana", 23)
        else -> null
    }
    
    // Usamos la llamada segura '?.salario' junto al valor por defecto '?: 0'
    fun salarioPorId(id: Int): Int = empleadoPorId(id)?.salario ?: 0
    
    fun main() {
        println((1..5).sumOf { id -> salarioPorId(id) })
    }

    El formulario de contacto

    fun obtenerLongitudTelefono(telefono: String?): Int {
        return telefono?.length ?: 0
    }
    
    fun main() {
        val telefono1: String? = "654321987"
        val telefono2: String? = null
    
        println(obtenerLongitudTelefono(telefono1)) 
        println(obtenerLongitudTelefono(telefono2)) 
    }

    La cadena de mando

    data class Jefe(val nombre: String)
    data class Departamento(val jefe: Jefe?)
    data class Empresa(val departamento: Departamento?)
    
    fun main() {
        // Creamos nuestra empresa pasándole un departamento nulo
        val miEmpresa = Empresa(departamento = null)
    
        // Encadenamos con '?.'. Como el departamento es null, salta directamente al Elvis
        val nombreDelJefe = miEmpresa.departamento?.jefe?.nombre ?: "Sin jefe asignado"
        
        println(nombreDelJefe)
    }

    El sistema de alertas

    fun main() {
        val alertaActiva: String? = "Peligro de sobrecalentamiento en el núcleo"
        val alertaApagada: String? = null
    
        alertaActiva?.let { mensaje ->
            println(mensaje.uppercase())
        }
    
        // Como esta variable es null, la ejecución ignorará todo el bloque let
        alertaApagada?.let { mensaje ->
            println(mensaje.uppercase())
        }
    }

    Clases y objetos en Kotlin: Modela tus datos con elegancia

    Introducción

    Si has llegado hasta aquí, ya sabes cómo manejar variables, colecciones, controlar el flujo de tu programa y crear funciones o lambdas para organizar tus acciones. ¡Enhorabuena! Tienes unas bases de programación fabulosas. Pero ahora toca dar el siguiente paso lógico y entrar de lleno en la Programación Orientada a Objetos (POO).

    Imagina que en tu aplicación tienes que gestionar la información de decenas de usuarios: su nombre, su identificador, su correo y su edad. ¿Vas a usar variables sueltas por todo el código para cada cosita de cada usuario? Sería un absoluto caos y muy propenso a errores. Para eso están las clases y los objetos. En esta unidad, vamos a ver cómo Kotlin hace que crear y agrupar estructuras de datos sea un proceso muy limpio, y descubriremos la magia de las data classes (clases de datos), una de las características estrella del lenguaje que te ahorrará horas de escribir código.

    ¿Qué son las clases y cómo se declaran?

    En Kotlin, la programación orientada a objetos es muy directa. Una clase te permite declarar un conjunto de características compartidas para un concepto. Piensa en la clase como si fuera el «molde» (por ejemplo, el concepto de Cliente) y en los objetos como las «galletas» reales que salen de ese molde (el cliente Juan, la clienta María).

    Para declarar una clase, simplemente utilizamos la palabra reservada class:

    class Cliente

    Propiedades de una clase

    Las características concretas de ese molde se llaman propiedades. Puedes declarar las propiedades de una clase directamente entre paréntesis () justo después del nombre de la misma. A esta parte se le conoce como el encabezado de la clase (class header).

    class Contacto(val id: Int, var email: String)

    También puedes definir propiedades adicionales dentro del cuerpo de la clase, abriendo unas llaves {}:

    class Contacto(val id: Int, var email: String) {
        val categoria: String = "trabajo"
    }

    La regla de oro: Te recomendamos encarecidamente que declares tus propiedades como de solo lectura (val) siempre que sea posible. Usa variables mutables (var) únicamente si tienes la absoluta certeza de que ese dato necesitará cambiar después de haber creado el objeto.

    Al igual que ocurría con las funciones, las propiedades de una clase pueden tener valores por defecto:

    class Contacto(val id: Int, var email: String = "[email protected]") {
        val categoria: String = "trabajo"
    }

    El dato: Si pones parámetros en el encabezado sin escribir val o var delante, Kotlin los tratará como simples parámetros de configuración y no como propiedades reales de la clase; por lo tanto, no podrás acceder a ellos después. ¡Acuérdate de poner siempre val o var!

    Nota extra: Kotlin te permite poner una coma al final de la última propiedad (conocido como trailing comma) para que te sea más fácil reordenar el código copiando y pegando líneas.

    Crear instancias (objetos)

    Para poder usar esa clase en la vida real, necesitas instanciarla (crear un objeto) a través de un constructor. En lenguajes más antiguos como Java tendrías que usar la palabra new a la fuerza, pero a Kotlin le gusta el código conciso, así que no hace falta.

    Por defecto, el lenguaje te crea un constructor automáticamente exigiendo los parámetros que hayas puesto en el encabezado.

    class Contacto(val id: Int, var email: String)
    
    fun main() {
        // Creamos la instancia y la guardamos en una variable
        val contacto1 = Contacto(1, "[email protected]")
    }

    En este ejemplo:

    • Contacto es la clase (el molde).
    • contacto1 es la instancia (el objeto real en la memoria).

    Acceder a las propiedades

    Para leer o modificar una propiedad de un objeto que ya has creado, la sintaxis es facilísima: solo tienes que escribir el nombre de la instancia, seguido de un punto ., y el nombre de la propiedad.

    fun main() {
        val contacto = Contacto(1, "[email protected]")
        
        // Imprimimos el valor de la propiedad
        println(contacto.email) 
        // Salida: [email protected]
    
        // Actualizamos el valor (es posible porque lo definimos como 'var')
        contacto.email = "[email protected]"
        
        // Imprimimos el nuevo valor
        println(contacto.email) 
        // Salida: [email protected]
    }

    Si necesitas concatenar el valor de esa propiedad dentro de un texto, recuerda usar las plantillas de cadenas con el símbolo del dólar. Como estás accediendo a una propiedad y no a una variable simple, es obligatorio envolverlo todo entre llaves ${}:

    println("Su correo electrónico de empresa es: ${contacto.email}")

    Funciones miembro

    Las clases no son simples «bolsas» para guardar datos estáticos. También pueden tener comportamiento propio. A las funciones que declaramos dentro del cuerpo de una clase se las denomina funciones miembro (o métodos).

    Para llamar a estas funciones, volvemos a usar la sintaxis del punto .:

    class Contacto(val id: Int, var email: String) {
        // Esta es una función miembro
        fun imprimirId() {
            println("El identificador secreto es: $id")
        }
    }
    
    fun main() {
        val contacto = Contacto(1, "[email protected]")
        // Llamamos a la función
        contacto.imprimirId() 
        // Salida: El identificador secreto es: 1
    }

    El superpoder de Kotlin: data classes

    Aquí es donde Kotlin brilla y demuestra por qué es un lenguaje tan popular. En el día a día, muchas veces vas a crear clases cuyo único propósito sea almacenar información de forma pasiva. Para estos casos concretos, Kotlin inventó las data classes (clases de datos).

    Tienen exactamente la misma funcionalidad que las clases normales que acabamos de ver, pero el compilador de Kotlin les inyecta automáticamente por detrás un montón de funciones extra para que no tengas que escribir código repetitivo o boilerplate.

    Para declararlas, solo tienes que poner la palabra data antes del class:

    data class Usuario(val nombre: String, val id: Int)

    Al hacer esto, ganas acceso automático a tres funciones vitales que analizamos en los siguientes puntos.

    Imprimir como texto (toString)

    Si imprimes una clase normal de Kotlin por la consola, te escupirá un nombre incomprensible con su referencia en memoria (algo tipo Usuario@2f92e0f4). Pero las data classes sobrescriben la función toString() de forma nativa para que la salida sea humana y legible.

    fun main() {
        val usuario = Usuario("Alex", 1)
        
        println(usuario) 
        // Salida automática y bonita: Usuario(nombre=Alex, id=1)
    }

    Esto es indispensable cuando estás haciendo pruebas o guardando logs en tu servidor.

    Comparar instancias (==)

    Si tú comparas dos instancias distintas de una clase normal usando ==, Kotlin te dirá que son diferentes false, porque mirará si ocupan el mismo espacio físico en la memoria del ordenador.

    Sin embargo, en las data classes, el operador == (que llama a la función equals()) es inteligente: compara los datos reales.

    fun main() {
        val user1 = Usuario("Alex", 1)
        val user2 = Usuario("Alex", 1)
        val user3 = Usuario("Max", 2)
    
        println("user1 y user2: ${user1 == user2}") // true (¡Tienen los mismos datos!)
        println("user1 y user3: ${user1 == user3}") // false
    }

    Copiar instancias (copy)

    Imagina que quieres crear un objeto nuevo partiendo de uno existente, pero alterando únicamente uno de sus datos. Modificar el objeto original usando un var puede causar fallos encadenados si otra parte de tu código estaba usando a ese mismo usuario.

    La función copy() te clona el objeto a la perfección, y como parámetros opcionales le puedes pasar el nuevo valor para las propiedades que quieras alterar:

    fun main() {
        val usuario = Usuario("Alex", 1)
    
        // Clonación exacta
        println(usuario.copy()) 
        // Usuario(nombre=Alex, id=1)
    
        // Clonación con el nombre alterado
        println(usuario.copy(nombre = "Max")) 
        // Usuario(nombre=Max, id=1)
    }

    Ejercicios

    Aquí tienes varios ejercicios de menos a más dificultad (los tres primeros son traducciones de la documentación oficial de Kotlin y los dos últimos son extras para asentar los conceptos). Abre tu Kotlin Playground y vamos a mancharnos las manos.

    El empleado

    Define una data class llamada Employee con dos propiedades: name (para el nombre en formato texto) y salary (para el salario en enteros). Asegúrate de que la propiedad del salario sea mutable, ¡o de lo contrario el empleado jamás podrá recibir un aumento a final de año!

    El main a continuación te muestra cómo lo vamos a usar.

    // Escribe tu código aquí
    
    fun main() {
        val emp = Employee("Mary", 20)
        println(emp)
        emp.salary += 10
        println(emp)
    }

    Estructuras anidadas

    A veces los objetos contienen a otros objetos. Declara las data classes adicionales que se necesitan para que el siguiente código compile sin errores. Fíjate bien en la llamada dentro de main() para adivinar qué propiedades y de qué tipo necesita cada clase.

    data class Person(val name: Name, val address: Address, val ownsAPet: Boolean = true)
    // Escribe tu código aquí:
    // data class Name(...)
    
    fun main() {
        val person = Person(
            Name("John", "Smith"),
            Address("123 Fake Street", City("Springfield", "US")),
            ownsAPet = false
        )
        println(person)
    }

    Generador aleatorio de empleados

    Para probar tu código en el futuro, vas a necesitar un generador que cree empleados al azar. Define una clase normal RandomEmployeeGenerator.

    El constructor de la clase debe recibir el minSalary y el maxSalary (ambos enteros y mutables).

    Dentro del cuerpo de la clase, guarda una lista fija de nombres potenciales. Y, por último, crea una función miembro llamada generateEmployee() que devuelva una nueva instancia de tu data class Employee usando un nombre aleatorio de tu lista y un salario aleatorio dentro de los márgenes dados.

    Pista: Las listas de Kotlin tienen una extensión muy útil llamada .random() que te devuelve un ítem al azar. Usa Random.nextInt(from = minSalary, until = maxSalary) para generar el número.

    import kotlin.random.Random
    
    data class Employee(val name: String, var salary: Int)
    
    // Escribe tu clase RandomEmployeeGenerator aquí
    
    fun main() {
        val empGen = RandomEmployeeGenerator(10, 30)
        println(empGen.generateEmployee())
        println(empGen.generateEmployee())
        
        empGen.minSalary = 50
        empGen.maxSalary = 100
        println(empGen.generateEmployee())
    }

    El inventario de la tienda

    Crea una clase (¡normal, no data class!) llamada Producto que reciba en su constructor un nombre de tipo String y un precio de tipo Double (ambos inmutables).

    Añade una propiedad dentro del cuerpo de la clase llamada stock de tipo entero, inicializada en 0, que por supuesto sea mutable.

    Crea una función miembro llamada añadirStock(cantidad: Int) que le sume esa cantidad al stock actual.

    En tu función main, crea un producto, añádele 50 unidades de stock, e imprime un texto final diciendo: «Tenemos [stock] unidades de [nombre]».

    Clonando clientes

    Tienes una data class Cliente(val id: Int, val nombre: String, val email: String). Fíjate en que todo está blindado y es inmutable (val).

    Crea una instancia para «Ana» con ID 1 y correo «[email protected]». De repente, Ana te avisa de que ha cambiado su correo corporativo a «[email protected]».

    Como no puedes hacer ana.email = ... porque daría error, usa la función de las data classes que hemos aprendido para crear un nuevo objeto llamado anaActualizada basado en Ana, pero con su correo modificado. Imprime por consola a anaActualizada.

    Soluciones a los ejercicios

    No hagas trampas. Échale un vistazo a las soluciones únicamente cuando te hayas peleado un buen rato con tu editor de código y el compilador te haya gritado un par de veces.

    El empleado

    data class Employee(val name: String, var salary: Int)
    
    fun main() {
        val emp = Employee("Mary", 20)
        println(emp)
        emp.salary += 10
        println(emp)
    }

    Estructuras anidadas

    data class Person(val name: Name, val address: Address, val ownsAPet: Boolean = true)
    data class Name(val first: String, val last: String)
    data class Address(val street: String, val city: City)
    data class City(val name: String, val countryCode: String)
    
    fun main() {
        val person = Person(
            Name("John", "Smith"),
            Address("123 Fake Street", City("Springfield", "US")),
            ownsAPet = false
        )
        println(person)
    }

    Generador aleatorio de empleados

    import kotlin.random.Random
    
    data class Employee(val name: String, var salary: Int)
    
    class RandomEmployeeGenerator(var minSalary: Int, var maxSalary: Int) {
        // Definimos la lista en el cuerpo de la clase
        val names = listOf("John", "Mary", "Ann", "Paul", "Jack", "Elizabeth")
        
        // Función miembro que devuelve un Employee
        fun generateEmployee() = Employee(
            names.random(), 
            Random.nextInt(from = minSalary, until = maxSalary)
        )
    }
    
    fun main() {
        val empGen = RandomEmployeeGenerator(10, 30)
        println(empGen.generateEmployee())
        println(empGen.generateEmployee())
        
        empGen.minSalary = 50
        empGen.maxSalary = 100
        println(empGen.generateEmployee())
    }

    El inventario de la tienda

    class Producto(val nombre: String, val precio: Double) {
        // Propiedad en el cuerpo inicializada a 0
        var stock: Int = 0
        
        fun añadirStock(cantidad: Int) {
            stock += cantidad
        }
    }
    
    fun main() {
        val miProducto = Producto("Teclado Mecánico", 45.99)
        miProducto.añadirStock(50)
        
        println("Tenemos ${miProducto.stock} unidades de ${miProducto.nombre}")
    }

    Clonando clientes

    data class Cliente(val id: Int, val nombre: String, val email: String)
    
    fun main() {
        // Creamos nuestra instancia original
        val ana = Cliente(1, "Ana", "[email protected]")
        
        // Clonamos el objeto modificando únicamente la propiedad email
        val anaActualizada = ana.copy(email = "[email protected]")
        
        // Comprobamos la magia
        println(anaActualizada)
        // Salida: Cliente(id=1, nombre=Ana, [email protected])
    }

    Funciones y lambdas en Kotlin: Crea código reutilizable y elegante

    Introducción

    Si has llegado hasta aquí, ya sabes cómo crear variables, manejar distintos tipos de datos, organizar información en colecciones y controlar el flujo de tu programa con condicionales y bucles. ¡Enhorabuena! Tienes las bases. Pero ahora toca dar el siguiente paso lógico en la programación: las funciones.

    Imagina que en tu código tienes que hacer un cálculo complejo o mostrar un mensaje específico docenas de veces a lo largo del programa. ¿Vas a copiar y pegar el mismo bloque de código una y otra vez? Para eso están las funciones. En esta unidad, vamos a ver cómo Kotlin hace que declarar y usar funciones sea un proceso muy limpio, adentrándonos por último en el fascinante mundo de las expresiones lambda, una de las características más apreciadas por los desarrolladores modernos.

    ¿Qué son las funciones y cómo se declaran?

    En Kotlin, puedes declarar tus propias funciones utilizando la palabra reservada fun (muy apropiado, ¿verdad? ¡Programar en Kotlin es divertido!). Una función no es más que un bloque de código al que le damos un nombre y que realiza una tarea concreta.

    fun sumar(x: Int, y: Int): Int {
        return x + y
    }
    
    fun main() {
        println(sumar(1, 2))
        // Salida: 3
    }

    Analizando el código paso a paso:

    • fun: La palabra clave mágica para decirle a Kotlin que vamos a crear una función.
    • Los parámetros: Se escriben siempre entre paréntesis (). Cada parámetro debe tener un tipo de dato explícito (en este caso x e y son de tipo Int), y si hay varios, se separan por comas. Son los datos que la función necesita para poder trabajar.
    • El tipo de retorno: Después de los paréntesis de los parámetros y separado por dos puntos :, indicamos qué tipo de dato va a devolver la función como resultado final. En este caso, devolverá un número entero (Int).
    • El cuerpo de la función: Es todo lo que va dentro de las llaves {}.
    • return: Usamos esta palabra para escupir o devolver el resultado de nuestro cálculo y dar por terminada la ejecución de la función.

    El dato: Las convenciones de código oficiales de Kotlin recomiendan nombrar las funciones empezando con minúscula y usando camelCase (por ejemplo, calcularPrecioTotal, imprimirUsuario), sin usar guiones bajos.

    Parámetros nombrados

    A veces, cuando llamas a una función que tiene muchísimos parámetros, el código puede volverse un poco lioso a simple vista. ¿Qué era ese true o ese 5 que le pasábamos a la función en la tercera posición? Kotlin nos permite usar parámetros nombrados.

    Si incluyes el nombre del parámetro explícitamente al llamar a la función, no solo haces que tu código sea muchísimo más fácil de leer, sino que además puedes poner los parámetros en el orden que quieras.

    fun imprimirMensaje(mensaje: String, prefijo: String) {
        println("[$prefijo] $mensaje")
    }
    
    fun main() {
        // Usamos argumentos nombrados y además invertimos el orden
        imprimirMensaje(prefijo = "LOG", mensaje = "Sistema iniciado correctamente")
        // Resultado: [LOG] Sistema iniciado correctamente
    }

    Valores por defecto

    En lenguajes más antiguos, si a veces querías llamar a una función pasándole tres parámetros y otras veces solo dos, tenías que crear varias versiones de la misma función (esto se conoce como sobrecarga de métodos).

    En Kotlin, te ahorras todo ese trabajo. Puedes definir valores por defecto para los parámetros utilizando el operador de asignación =. Si al llamar a la función omites ese parámetro, Kotlin utilizará automáticamente el valor por defecto que configuraste.

    fun imprimirMensaje(mensaje: String, prefijo: String = "INFO") {
        println("[$prefijo] $mensaje")
    }
    
    fun main() {
        // Llamamos a la función pasándole ambos parámetros
        imprimirMensaje("Usuario conectado", "LOG") 
        // Resultado: [LOG] Usuario conectado
        
        // Llamamos a la función pasándole SOLO el mensaje. ¡Usa el prefijo por defecto!
        imprimirMensaje("El proceso ha finalizado") 
        // Resultado: [INFO] El proceso ha finalizado
    }

    La regla de oro: Puedes saltarte parámetros concretos que tengan valores por defecto, pero si omites uno y quieres pasar el siguiente, tendrás que usar obligatoriamente parámetros nombrados para los que pongas a continuación.

    Funciones que no devuelven nada

    Si tu función simplemente realiza una acción (como imprimir un texto por la consola, reproducir un sonido o guardar un dato en la base de datos) pero no necesita devolverte ningún resultado matemático o lógico útil, su tipo de retorno en Kotlin es Unit (el equivalente al void en Java o C).

    La maravilla de Kotlin es que no hace falta que escribas Unit ni que pongas un return. El compilador ya lo sabe.

    fun saludar(nombre: String) {
        println("¡Hola, $nombre!")
        // Escribir `: Unit` arriba junto a los paréntesis o `return Unit` aquí abajo es totalmente opcional (y casi nadie lo hace).
    }

    Funciones de una sola expresión

    Para hacer tu código aún más conciso y espectacular, si tu función consta de una única instrucción que devuelve un valor, puedes ahorrarte de un plumazo las llaves {} y la palabra return. Solo tienes que usar el signo igual =.

    Tomemos la función sumar del principio:

    fun sumar(x: Int, y: Int): Int {
        return x + y
    }

    Se puede transformar en esta maravilla de una sola línea:

    fun sumar(x: Int, y: Int) = x + y

    Fíjate bien, ¡ni siquiera hemos puesto : Int! Como Kotlin cuenta con una característica llamada inferencia de tipos, deduce inmediatamente que si sumas dos Int, el resultado será forzosamente un Int.

    (No obstante, si estás trabajando en equipo y quieres que otros programadores lean tu código rápidamente, nunca está de más indicar explícitamente el tipo de retorno: fun sumar(x: Int, y: Int): Int = x + y).

    Retornos tempranos en funciones

    En ocasiones, quieres salir de una función inmediatamente si se cumple (o no) una condición, sin necesidad de seguir ejecutando y procesando el resto del código. Para eso usamos la palabra return dentro de un condicional if.

    val usuariosRegistrados = mutableListOf("juan_perez", "maria_lopez")
    
    fun registrarUsuario(usuario: String): String {
        // Retorno temprano si el usuario ya existe en nuestra base de datos (lista)
        if (usuario in usuariosRegistrados) {
            return "Error: El nombre de usuario ya está pillado. Elige otro."
        }
    
        // Si no hemos entrado en el if anterior, la función continúa de forma normal
        usuariosRegistrados.add(usuario)
        return "¡Usuario $usuario registrado con éxito!"
    }
    
    fun main() {
        println(registrarUsuario("juan_perez"))
        // Salida: Error: El nombre de usuario ya está pillado. Elige otro.
        
        println(registrarUsuario("nuevo_user"))
        // Salida: ¡Usuario nuevo_user registrado con éxito!
    }

    Expresiones lambda

    Kotlin te permite escribir funciones de forma todavía más compacta usando lo que conocemos como expresiones lambda. Básicamente, una lambda es una función anónima (sin nombre) que puedes guardar directamente en una variable o pasarla como si fuera un parámetro más a otras funciones.

    Mira esta función normal:

    fun aMayusculas(texto: String): String {
        return texto.uppercase()
    }

    Podemos escribir exactamente lo mismo como una expresión lambda y guardarla en una variable:

    val aMayusculasLambda = { texto: String -> texto.uppercase() }
    
    fun main() {
        println(aMayusculasLambda("hola"))
        // Resultado: HOLA
    }

    Estructura de una lambda:

    • Se encapsula siempre entre llaves { }.
    • Primero van los parámetros (texto: String).
    • Luego una flecha ->.
    • Y finalmente el cuerpo de la función (lo que hace y lo que se devuelve de forma automática: texto.uppercase()).

    Pasando lambdas a otras funciones

    Esto resulta muy útil cuando trabajamos con colecciones (listas, sets, mapas…). Multitud de funciones nativas de colecciones reciben una lambda para saber exactamente qué deben hacer con cada uno de los elementos.

    El mejor ejemplo es usar .filter() (para filtrar elementos en base a una condición) o .map() (para transformar los elementos de una lista en otros):

    fun main() {
        val numeros = listOf(1, -2, 3, -4, 5, -6)
        
        // Quedarnos solo con los positivos. La lambda comprueba si el número es mayor que 0.
        val positivos = numeros.filter({ x -> x > 0 })
        println(positivos) // [1, 3, 5]
        
        // Multiplicar todos los elementos de la lista por 2 usando map
        val dobles = numeros.map({ x -> x * 2 })
        println(dobles) // [2, -4, 6, -8, 10, -12]
    }

    Si la lambda es el último o el único parámetro que le pasas a una función (como pasa arriba con filter y map), Kotlin te permite sacarla fuera de los paréntesis (). E incluso puedes omitir los paréntesis por completo si es el único argumento.

    Reescribiendo lo anterior de forma idiomática:

    val positivos = numeros.filter { x -> x > 0 }

    Tipos de funciones

    Al igual que una variable puede ser de tipo String o Int, ¡las funciones también tienen su propio tipo! La sintaxis para definir el tipo de una función consiste en poner los parámetros de entrada entre paréntesis y, tras una flecha ->, el tipo que va a devolver.

    • (String) -> String (recibe un texto, devuelve un texto)
    • (Int, Int) -> Int (recibe dos enteros, devuelve un entero)
    • () -> Unit (no recibe absolutamente nada, no devuelve nada útil)

    Ejemplo declarando el tipo de forma explícita en una variable:

    val sumarLambda: (Int, Int) -> Int = { a, b -> a + b }

    Ejercicios

    Aquí tienes varios ejercicios de menos a más dificultad (algunos adaptados de la documentación oficial y otros extra para asentar todo lo aprendido). Abre tu Kotlin Playground y vamos a practicar lo aprendido.

    El área del círculo

    Escribe una función normal llamada areaCirculo que reciba el radio de un círculo en formato entero (Int) y devuelva el área de ese círculo (con decimales).

    Pista: Vas a necesitar importar la constante Pi desde kotlin.math.PI. La fórmula es PI * radio * radio.

    import kotlin.math.PI
    // Escribe tu función aquí
    
    fun main() {
        println(areaCirculo(2)) // Debería dar aprox 12.566...
    }

    El área del círculo en una sola línea

    Reescribe la función areaCirculo del ejercicio anterior pero simplifícala para convertirla en una función de una sola expresión (Single-expression function), eliminando las llaves y el return.

    Pasando el tiempo a segundos

    Tienes una función que traduce un intervalo de tiempo (horas, minutos y segundos) a segundos totales. La inmensa mayoría de las veces, solo vas a querer pasarle un parámetro y que el resto sean cero por defecto.

    Mejora la función y las llamadas en el main utilizando valores por defecto y argumentos nombrados para que el código quede impecable y súper legible.

    // Mejora esta función
    fun intervaloEnSegundos(horas: Int, minutos: Int, segundos: Int) =
        ((horas * 60) + minutos) * 60 + segundos
    
    fun main() {
        // Mejora estas llamadas
        println(intervaloEnSegundos(1, 20, 15))
        println(intervaloEnSegundos(0, 1, 25))
        println(intervaloEnSegundos(2, 0, 0))
        println(intervaloEnSegundos(0, 10, 0))
        println(intervaloEnSegundos(1, 0, 1))
    }

    Generador de URLs

    Tienes una lista de acciones de una API, un prefijo de una web y el ID de un recurso. Utilizando la función de colección .map() y una expresión lambda, crea una nueva lista de URLs que combinen los tres elementos para formar rutas completas (ejemplo esperado: https://ejemplo.com/libro/5/titulo).

    fun main() {
        val acciones = listOf("titulo", "anyo", "autor")
        val prefijo = "https://ejemplo.com/libro"
        val id = 5
        
        val urls = // ¡Escribe aquí tu map con lambda!
        
        println(urls)
    }

    Repetidor de acciones

    Escribe una función llamada repetirN que reciba un número entero n y una acción (una función de tipo () -> Unit). La función debe repetir esa acción el número de veces indicado utilizando un bucle clásico. Luego, en el main, úsala (usando una trailing lambda) para imprimir "¡Me encanta Kotlin!" 5 veces.

    El saludo personalizado

    Crea una función llamada saludoPersonalizado que reciba un nombre (String), una edad (Int) y una ciudad (String). El parámetro ciudad debe tener el valor por defecto "Madrid".

    La función debe usar un println() para mostrar: "Hola, me llamo [nombre], tengo [edad] años y vivo en [ciudad].".

    Luego, en el main, llama a la función de dos formas distintas:

    1. Pasando solo nombre y edad.
    2. Pasando nombre, edad y ciudad, pero utilizando argumentos nombrados en un orden distinto al original.

    Filtrando palabras largas

    Dada la siguiente lista de palabras: val palabras = listOf("sol", "murciélago", "pan", "ornitorrinco", "luz", "desarrollador")

    Utiliza la función .filter() y una trailing lambda para crear una nueva lista que solo contenga las palabras que tengan estrictamente más de 5 letras. (Pista: Los textos o Strings en Kotlin tienen una propiedad llamada .length que te dice lo largos que son). Imprime el resultado final.

    Soluciones a los ejercicios

    No hagas trampas. Échale un vistazo a las soluciones únicamente cuando te hayas peleado un buen rato con el código.

    El área del círculo

    import kotlin.math.PI
    
    fun areaCirculo(radio: Int): Double {
        return PI * radio * radio
    }
    
    fun main() {
        println(areaCirculo(2)) 
        // 12.566370614359172
    }

    El área del círculo en una sola línea

    import kotlin.math.PI
    
    // Quitamos llaves y return, y usamos el signo '='. Mantener el ': Double' es buena práctica.
    fun areaCirculo(radio: Int): Double = PI * radio * radio
    
    fun main() {
        println(areaCirculo(2))
    }

    Pasando el tiempo a segundos

    // Asignamos '= 0' a todos los parámetros para darles un valor por defecto
    fun intervaloEnSegundos(horas: Int = 0, minutos: Int = 0, segundos: Int = 0) =
        ((horas * 60) + minutos) * 60 + segundos
    
    fun main() {
        println(intervaloEnSegundos(1, 20, 15)) // Este lo pasamos normal
        println(intervaloEnSegundos(minutos = 1, segundos = 25)) // Omitimos las horas
        println(intervaloEnSegundos(horas = 2)) // Omitimos minutos y segundos
        println(intervaloEnSegundos(minutos = 10))
        println(intervaloEnSegundos(horas = 1, segundos = 1))
    }

    Generador de URLs

    fun main() {
        val acciones = listOf("titulo", "anyo", "autor")
        val prefijo = "https://ejemplo.com/libro"
        val id = 5
        
        // Usamos map para transformar cada elemento. Recuerda usar plantillas de texto ($) para fusionarlo todo.
        val urls = acciones.map { accion -> "$prefijo/$id/$accion" }
        
        println(urls)
        // [https://ejemplo.com/libro/5/titulo, https://ejemplo.com/libro/5/anyo, https://ejemplo.com/libro/5/autor]
    }

    Repetidor de acciones

    // El tipo de la acción es () -> Unit porque es un bloque de código que no recibe nada y no devuelve nada útil.
    fun repetirN(n: Int, accion: () -> Unit) {
        for (i in 1..n) {
            accion()
        }
    }
    
    fun main() {
        // Como la acción es el último parámetro, podemos sacar las llaves (trailing lambda)
        repetirN(5) {
            println("¡Me encanta Kotlin!")
        }
    }

    El saludo personalizado

    fun saludoPersonalizado(nombre: String, edad: Int, ciudad: String = "Madrid") {
        println("Hola, me llamo $nombre, tengo $edad años y vivo en $ciudad.")
    }
    
    fun main() {
        // Llamada 1: usamos el valor por defecto para ciudad
        saludoPersonalizado("Carlos", 28)
        
        // Llamada 2: usamos argumentos nombrados alterando el orden natural
        saludoPersonalizado(ciudad = "Valencia", edad = 32, nombre = "Laura")
    }

    Filtrando palabras largas

    fun main() {
        val palabras = listOf("sol", "murciélago", "pan", "ornitorrinco", "luz", "desarrollador")
        
        // filter con trailing lambda. Solo conservamos la palabra si su longitud es mayor a 5.
        val palabrasLargas = palabras.filter { palabra -> palabra.length > 5 }
        
        println(palabrasLargas)
        // [murciélago, ornitorrinco, desarrollador]
    }

    Control de flujo en Kotlin: Condicionales (if, when) y bucles (for, while)

    Introducción

    Hasta ahora, todos los programas que hemos escrito se ejecutaban en línea recta: el ordenador leía la línea 1, luego la 2, luego la 3… y terminaba. Pero en el mundo real, las aplicaciones necesitan tomar decisiones (si el usuario tiene saldo, haz la compra; si no, muestra un error) y repetir tareas (mostrar los 50 mensajes de un chat uno por uno).

    Para esto sirve el Control de Flujo. En Kotlin, contamos con herramientas modernizadas y súper potentes para dirigir el tráfico de nuestro código. ¡Vamos a descubrirlas!

    Tomando decisiones: el clásico if / else

    La forma más básica de tomar una decisión en programación es usar la estructura if (si ocurre esto…) y else (si no, haz esto otro…).

    La condición a evaluar siempre va entre paréntesis (), y el bloque de código que se ejecutará va entre llaves {}.

    fun main() {
        val edad = 18
    
        if (edad >= 18) {
            println("Puedes entrar a la discoteca.")
        } else {
            println("Lo siento, vuelve a casa.")
        }
    }

    El súper-poder de Kotlin: el if como expresión

    Si vienes de lenguajes como Java o JavaScript, conocerás el famoso operador ternario (condicion ? valor1 : valor2) para asignar variables en una sola línea. En Kotlin, el operador ternario no existe porque no hace falta.

    En Kotlin, un if puede devolver un valor directamente. Si tu if y tu else solo tienen una línea, puedes quitar las llaves y hacerlo así de elegante:

    fun main() { 
        val a = 10
        val b = 20
    
        // El resultado del if se guarda directamente en la variable 'mayor'
        val mayor = if (a > b) a else b 
        
        println("El número mayor es $mayor") // Imprime: 20
    }

    El condicional when: el switch con esteroides

    Cuando tienes que evaluar muchísimas opciones distintas, usar decenas de if / else if / else encadenados hace que el código sea ilegible.

    Otros lenguajes usan la palabra switch. Kotlin usa when (cuando), y es una de las herramientas más queridas por los desarrolladores.

    when como instrucción

    Colocamos la variable que queremos evaluar entre paréntesis. Luego usamos una «flechita» -> para indicar qué hacer en cada caso. El else actúa como la opción por defecto si no se cumple ninguna de las anteriores.

    fun main() {
        val boton = "X"
    
        when (boton) {
            "A" -> println("Saltar")
            "B" -> println("Atacar")
            "X" -> println("Abrir Inventario")
            "Y" -> println("Magia")
            else -> println("Botón no reconocido") 
        }
    }

    Nota: Kotlin evalúa de arriba a abajo. En cuanto encuentra una coincidencia, ejecuta esa línea y sale del when automáticamente. (¡Adiós a la pesadilla de olvidar poner los break de otros lenguajes!)

    when como expresión (devolviendo un valor)

    Al igual que el if, podemos usar when para asignar un valor directamente a una variable:

    val estadoSemaforo = "Rojo" 
     
    val accion = when (estadoSemaforo) {
        "Verde" -> "Acelerar"
        "Ambar" -> "Frenar poco a poco"
        "Rojo" -> "Detenerse"
        else -> "Llamar al mecánico" // Al devolver valor, el 'else' es OBLIGATORIO
    }
    println("Debes: $accion")

    Rangos: preparando el terreno para los bucles

    Antes de aprender a repetir tareas, necesitamos saber cómo crear Rangos (intervalos de valores) en Kotlin. Es facilísimo:

    • .. (Punto punto): Crea un rango que incluye el último número. 1..4 equivale a 1, 2, 3, 4.
    • ..< (Punto punto menor): Crea un rango que excluye el último número. 1..<4 equivale a 1, 2, 3.
    • downTo: Cuenta hacia atrás. 4 downTo 1 equivale a 4, 3, 2, 1.
    • step: Cambia el tamaño del salto. 1..5 step 2 equivale a 1, 3, 5.

    (¡También funciona con letras del abecedario! Ej: 'a'..'d')

    Bucles: repitiendo tareas sin cansarse

    El bucle for (para cada…)

    Se usa cuando sabes exactamente cuántas veces quieres repetir algo, o cuando quieres recorrer una Colección (como las Listas que vimos en el artículo anterior).

    fun main() {
        // Repetir un código un número exacto de veces usando un rango
        for (numero in 1..5) { 
            print(numero) // Imprime: 12345
        }
    
        println() // Salto de línea
    
        // Recorrer una lista
        val pasteles = listOf("Zanahoria", "Queso", "Chocolate")
        for (pastel in pasteles) {
            println("¡Qué rico, un pastel de $pastel!")
        }
    }

    Los bucles while y do-while (mientras que…)

    Se usan cuando no sabes cuántas veces se va a repetir algo, pero sabes que debe repetirse «mientras» se cumpla una condición.

    • while: Primero comprueba la condición. Si es falsa desde el principio, nunca se ejecuta.
    • do-while: Primero ejecuta el código una vez, y luego comprueba la condición. Te asegura que el bloque de código se va a ejecutar como mínimo una vez.
    fun main() {
        var porcionesComidas = 0
        
        // Bucle while normal
        while (porcionesComidas < 3) {
            println("Me como una porción")
            porcionesComidas++ // Esto suma 1 a la variable (es lo mismo que porcionesComidas = porcionesComidas + 1)
        }
    }

    Ejercicios

    Abre tu Kotlin Playground y vamos a machacar lo aprendido.

    Los dados

    Crea un minijuego donde ganas si al lanzar dos dados sacas el mismo número. Si son iguales, imprime "¡Has ganado :)". Si no, "Has perdido :(".

    (Nota: Para generar números aleatorios usaremos una librería nativa de Kotlin llamada Random).

    import kotlin.random.Random
    
    fun main() {
        // Genera un número aleatorio entre 0 y 5.
        val dado1 = Random.nextInt(6) 
        val dado2 = Random.nextInt(6)
        
        println("Dado 1: $dado1 | Dado 2: $dado2")
        // Escribe tu código (if/else) a partir de aquí:
        
    }

    Botones de consola

    Usando un when que actúe como expresión (asignando su resultado o metiéndolo directo en un println), haz que el programa imprima la acción correspondiente al botón pulsado:

    • A -> «Sí»
    • B -> «No»
    • X -> «Menú»
    • Y -> «Nada»
    • Cualquier otro -> «No existe ese botón»
    fun main() {
        val boton = "A"
        
        // Escribe tu when aquí dentro del println
        println(
            // ...
        )
    }

    Comiendo Pizza

    Tienes un código muy feo que cuenta porciones de pizza repitiendo líneas a mano. Conviértelo en un bucle while que cuente automáticamente hasta llegar a las 8 porciones.

    fun main() {
        var porciones = 0
        // ¡Borra este desastre y usa un bucle while!
        porciones++
        println("Solo hay $porciones porción/es de pizza :(")
        porciones++
        println("Solo hay $porciones porción/es de pizza :(")
        // ... así hasta 7 ...
        
        // Al salir del bucle debe imprimir esto (y la variable debe valer 8):
        println("¡Tenemos $porciones porciones! ¡Una pizza entera! :D")
    }

    El clásico reto FizzBuzz

    Este es uno de los ejercicios más famosos en las entrevistas de programación junior.

    Escribe un programa que imprima los números del 1 al 100, pero:

    • Si el número es divisible por 3, imprime la palabra "fizz" en lugar del número.
    • Si el número es divisible por 5, imprime "buzz".
    • Si el número es divisible por 3 Y por 5 (es decir, divisible por 15), imprime "fizzbuzz".

    Pista: Usa un bucle for del 1 al 100. Dentro, usa un when sin argumento para evaluar condiciones usando el operador Módulo % (que te da el resto de una división. Si numero % 3 == 0, es que es divisible por 3).

    fun main() {
        // Escribe tu código aquí (for + when)
    }

    Soluciones a los ejercicios

    No hagas trampas. Échale un vistazo a las soluciones únicamente cuando te hayas peleado un buen rato con el código.

    Los dados

    import kotlin.random.Random
    
    fun main() {
        val dado1 = Random.nextInt(6)
        val dado2 = Random.nextInt(6)
        println("Dado 1: $dado1 | Dado 2: $dado2")
        
        // Comprobamos la igualdad con ==
        if (dado1 == dado2) {
            println("¡Has ganado :)")
        } else {
            println("Has perdido :(")
        }
    }

    Botones de consola

    fun main() {
        val boton = "A"
        
        println(
            when (boton) {
                "A" -> "Sí"
                "B" -> "No"
                "X" -> "Menú"
                "Y" -> "Nada"
                else -> "No existe ese botón"
            }
        )
    }

    La pizza

    fun main() {
        var porciones = 0
        
        while (porciones < 7) {
            porciones++
            println("Solo hay $porciones porción/es de pizza :(")
        }
        
        porciones++ // Sumamos la octava porción al salir
        println("¡Tenemos $porciones porciones! ¡Una pizza entera! :D")
    }

    El clásico reto FizzBuzz

    fun main() {
        for (numero in 1..100) {
            println(
                // Al usar when SIN variable, podemos evaluar condiciones booleanas libres
                // ¡El orden importa! Hay que comprobar el 15 primero.
                when {
                    numero % 15 == 0 -> "fizzbuzz"
                    numero % 3 == 0 -> "fizz"
                    numero % 5 == 0 -> "buzz"
                    else -> numero // Si no cumple ninguna, imprime el número normal
                }
            )
        }
    }

    Guía Definitiva de colecciones en Kotlin: listas, conjuntos y mapas explicados desde cero

    Introducción

    En las unidades anteriores aprendimos a guardar un dato individual en una variable (como un número o un texto). Pero, ¿qué pasa si estás creando una app y necesitas guardar los nombres de 100 usuarios? ¿Vas a crear 100 variables distintas? ¡Por supuesto que no!

    Para eso existen las colecciones. Son estructuras que nos permiten agrupar múltiples datos bajo un mismo nombre para procesarlos más tarde.

    En Kotlin, la regla de seguridad que vimos con val y var se mantiene: las colecciones pueden ser de solo lectura (inmutables) o mutables (modificables). Kotlin nos ofrece tres tipos principales. ¡Vamos a destriparlas!

    Listas (Lists): El cajón ordenado

    Una Lista (List) es exactamente lo que imaginas: una colección de elementos ordenados uno detrás de otro.

    • Tienen un orden estricto (el primero, el segundo, el tercero…).
    • Permiten duplicados (puedes tener el mismo elemento varias veces).

    Crear listas

    Para crear una lista de solo lectura usamos listOf(). Si queremos una lista a la que podamos añadir o quitar cosas en el futuro, usamos mutableListOf().

    fun main() { 
        // Lista de solo lectura (Kotlin deduce que es de tipo String)
        val formas = listOf("triángulo", "cuadrado", "círculo")
        println(formas) // [triángulo, cuadrado, círculo]
        
        // Lista mutable (Aquí le decimos explícitamente el tipo <String>)
        val formasMutables: MutableList<String> = mutableListOf("triángulo", "cuadrado", "círculo")
    }

    Profundizando: El índice cero y los errores comunes

    Como las listas están ordenadas, cada elemento tiene una posición o «índice». ¡Ojo! En programación, siempre empezamos a contar desde el cero.

    Piensa en los ascensores en España: cuando entras al edificio desde la calle, estás en la Planta Baja (índice 0). Si subes un piso, llegas a la Planta 1 (el índice 1, que en realidad es el segundo nivel). El índice te dice cuántos saltos das desde el principio.

    fun main() { 
        val formas = listOf("triángulo", "cuadrado", "círculo")
        
        // Acceder por su posición (índice)
        println("El primer elemento es: ${formas[0]}") // triángulo
        
        // ¡CUIDADO! El terror de los novatos: IndexOutOfBoundsException
        // println(formas[3]) -> ¡El programa explotará porque no hay un 4º elemento!
    }

    Kotlin también nos regala funciones súper útiles para no tener que lidiar siempre con los números:

    fun main() {
        val formas = listOf("triángulo", "cuadrado", "círculo")
        
        println("El primer elemento: ${formas.first()}") // triángulo
        println("El último elemento: ${formas.last()}") // círculo
        println("Total de elementos: ${formas.count()}") // 3
        
        // Comprobar si algo existe con la palabra 'in'
        println("círculo" in formas) // true
    }

    Modificar listas mutables y el «truco del candado»

    Si tu lista es MutableList, usas .add() para añadir y .remove() para borrar.

    val carrito: MutableList<String> = mutableListOf("Manzanas", "Pan")
    carrito.add("Leche") // [Manzanas, Pan, Leche]
    carrito.remove("Pan") // [Manzanas, Leche]

    A veces tienes una lista mutable, pero se la vas a pasar a otra parte de tu código y no quieres que se modifique por accidente. Puedes «disfrazarla» de solo lectura:

    val inventarioMutable: MutableList<String> = mutableListOf("Espada", "Poción")
    val inventarioBloqueado: List<String> = inventarioMutable // ¡Candado puesto! Ahora no se puede modificar.

    Conjuntos (Sets): El VIP de la exclusividad

    Un conjunto (Set) es parecido a una lista, pero con dos diferencias vitales:

    • No tienen orden (no puedes pedir el elemento [0]).
    • NO permiten elementos duplicados. Son únicos y exclusivos.

    Se crean con setOf() y mutableSetOf().

    fun main() {
        // Fíjate que ponemos "cereza" dos veces
        val frutas = setOf("manzana", "plátano", "cereza", "cereza")
        
        // Al imprimir, ¡la segunda cereza ha desaparecido mágicamente!
        println(frutas) // [manzana, plátano, cereza]
        println("Hay ${frutas.count()} frutas únicas") // 3
    }

    Profundizando: ¿Por qué usar Sets? ¡Por la velocidad!

    Si quieres saber si el email «[email protected]» está en una Lista de 1 millón de usuarios, el ordenador mirará uno a uno. Es lentísimo.

    Si usas un Set, Kotlin usa matemáticas internas (una función Hash) para saber exactamente dónde está guardado. Lo encuentra al instante. ¡Usa Sets cuando trabajes con muchos datos únicos!

    val millonesDeUsuarios = setOf("[email protected]", "[email protected]")
    if ("[email protected]" in millonesDeUsuarios) {
        println("¡El usuario ya existe!") // Comprobación súper rápida
    }

    Mapas (Maps): El diccionario de datos

    Un Mapa (Map) almacena datos en pares Clave-Valor (Key-Value).

    Piénsalo como la carta de un restaurante: el nombre del plato es la clave y su precio es el valor.

    • Las claves (keys) deben ser únicas.
    • Los valores (values) sí pueden repetirse.

    Se crean con mapOf() y mutableMapOf(). Usamos la palabrita to para unir la pareja.

    fun main() {
        // Especificamos explícitamente: Claves String, Valores Int
        val menuZumos: MutableMap<String, Int> = mutableMapOf("manzana" to 3, "kiwi" to 4)
        
        // Leer un valor usando su clave entre corchetes
        println("El zumo de manzana cuesta: ${menuZumos["manzana"]}€") // 3€
    }

    Profundizando: Nulos y sobreescritura

    ¿Qué pasa si buscas algo que no existe en el menú? Kotlin te devuelve null (nulo, vacío). Así evita que tu aplicación se cuelgue por un error.

    println(menuZumos["piña"]) // Imprime: null

    ¿Y si añado una clave que ya existe? Se sobreescribe el valor antiguo. Es la forma oficial de actualizar datos:

    menuZumos["manzana"] = 5 // ¡Inflación! Actualizamos el precio a 5€
    menuZumos["coco"] = 6    // Como "coco" no existe, lo añade nuevo al mapa
    menuZumos.remove("kiwi") // Eliminamos el kiwi
    
    println(menuZumos.keys)   // [manzana, coco]
    println(menuZumos.values) // [5, 6]

    Ejercicios

    Abre el Kotlin Playground e intenta resolver estos ejercicios para asentar lo aprendido.

    Sumando listas

    Tienes una lista de números «verdes» y otra de «rojos». Imprime cuántos números hay en total sumando el tamaño de ambas listas.

    fun main() {
        val numerosVerdes = listOf(1, 4, 23)
        val numerosRojos = listOf(17, 2)
        // Escribe tu código aquí:
    }

    El protocolo de red

    Tienes un Set con protocolos en mayúsculas. Un usuario pide «smtp» en minúsculas. Comprueba si está soportado (debe devolver un Boolean).

    Pista: Usa .uppercase() en la petición para pasarla a mayúsculas antes de buscar en el Set con in.

    fun main() {
        val SOPORTADOS = setOf("HTTP", "HTTPS", "FTP")
        val peticion = "smtp"
        val estaSoportado = // Escribe tu código aquí 
        println("Soporte para $peticion: $estaSoportado")
    }

    Diccionario de números

    Crea un Map que relacione los números del 1 al 3 con su nombre escrito («uno», «dos», «tres»). Imprime cómo se escribe el número 2 buscándolo en el mapa.

    fun main() {
        val numeroAPalabra = // Escribe tu código aquí
        val n = 2
        // Imprime el resultado
    }

    El inventario del héroe

    Crea una lista mutable llamada inventario con: «Espada», «Escudo», «Poción».

    Añade un «Arco». Elimina la «Poción». Finalmente, imprime el inventario y cuántos objetos tiene usando .count().

    Soluciones a los ejercicios

    ¡No mires hasta haberlo intentado!

    Sumando listas

    fun main() {
        val numerosVerdes = listOf(1, 4, 23)
        val numerosRojos = listOf(17, 2)
        val total = numerosVerdes.count() + numerosRojos.count()
        println("Total: $total números")
    }

    El protocolo de red

    fun main() {
        val SOPORTADOS = setOf("HTTP", "HTTPS", "FTP")
        val peticion = "smtp"
        val estaSoportado = peticion.uppercase() in SOPORTADOS 
        println("Soporte para $peticion: $estaSoportado") // false
    }

    Diccionario de números

    fun main() {
        val numeroAPalabra = mapOf(1 to "uno", 2 to "dos", 3 to "tres")
        val n = 2
        println("El número $n se escribe como '${numeroAPalabra[n]}'")
    }

    El inventario del héroe

    fun main() {
        val inventario = mutableListOf("Espada", "Escudo", "Poción")
        inventario.add("Arco")
        inventario.remove("Poción")
        println("Inventario: $inventario")
        println("Tienes ${inventario.count()} objetos.")
    }