Provider en Flutter: Simplifica el Manejo de Estado como un Experto

1. Introducción: El Poder de Provider en el Manejo de Estados

En el mundo del desarrollo Flutter, construir interfaces de usuario atractivas es solo una parte de la ecuación. La otra parte, igualmente crucial, es gestionar el estado de tu aplicación. ¿Pero qué significa esto exactamente? Imagina el estado como la información que define cómo se ve y se comporta tu aplicación en un momento dado. Puede ser cualquier cosa, desde el texto que se muestra en un campo de entrada hasta la lista de productos en un carrito de compras.

A medida que las aplicaciones crecen en complejidad, la gestión del estado se vuelve un desafío. Es fácil perderse en un mar de setState() y callbacks, dificultando el mantenimiento y la escalabilidad del código. Aquí es donde Provider entra en escena, ofreciendo una solución elegante y eficiente para domar la complejidad del estado en Flutter.

Provider es un paquete popular que simplifica la forma en que compartimos y actualizamos datos en una aplicación Flutter. Actúa como un intermediario, proporcionando un mecanismo sencillo para que los widgets accedan y reaccionen a los cambios de estado sin enredos innecesarios.

¿Por qué usar Provider?

  • Simplicidad: Provider ofrece una sintaxis intuitiva y fácil de aprender, lo que facilita su integración en proyectos Flutter.
  • Rendimiento: Con mecanismos de actualización optimizados, Provider minimiza el número de widgets que se reconstruyen cuando el estado cambia, mejorando el rendimiento de la aplicación.
  • Mantenibilidad: Al separar la lógica de la interfaz de usuario, Provider promueve un código más limpio, modular y fácil de mantener.
  • Escalabilidad: Provider se adapta a aplicaciones de cualquier tamaño, desde proyectos pequeños hasta aplicaciones complejas con múltiples pantallas y flujos de datos.

En este artículo, exploraremos en profundidad el funcionamiento de Provider, sus diferentes tipos, y cómo utilizarlo eficazmente en aplicaciones Flutter de nivel intermedio. ¡Prepárate para dominar la gestión de estado y llevar tus habilidades de desarrollo Flutter al siguiente nivel!

2. Conceptos Fundamentales de Provider

Ahora que ya entendemos la importancia de la gestión de estado y el rol de Provider, es hora de sumergirnos en los conceptos fundamentales que hacen que este paquete sea tan poderoso.

ChangeNotifier: El Motor de las Notificaciones

En el corazón de Provider se encuentra ChangeNotifier, una clase que permite a los objetos “notificar” a los widgets cuando ocurre un cambio en su estado. Piensa en él como un sistema de alertas: cuando un objeto ChangeNotifier modifica sus datos, envía una señal a todos los widgets que están “escuchando” para que se actualicen.

Para usar ChangeNotifier, simplemente extiende tu clase de estado de él e implementa el método notifyListeners() cada vez que haya un cambio en los datos.

Dart

class ContadorModel extends ChangeNotifier {
  int _contador = 0;

  int get contador => _contador;

  void incrementar() {
    _contador++;
    notifyListeners(); // Notifica a los widgets sobre el cambio
  }
}

Tipos de Providers: Un Provider para cada Necesidad

Provider ofrece una variedad de clases para adaptarse a diferentes escenarios de gestión de estado:

  • Provider: El más básico, se usa para proporcionar un único valor a los widgets descendientes.
  • ListenableProvider: Similar a Provider, pero se usa para exponer objetos que implementan la interfaz Listenable, como AnimationController.
  • ChangeNotifierProvider: Ideal para exponer objetos ChangeNotifier, como en el ejemplo del contador.
  • FutureProvider: Perfecto para manejar datos asíncronos, como resultados de una llamada a una API.
  • StreamProvider: Útil para trabajar con flujos de datos, como actualizaciones en tiempo real desde una base de datos.
  • MultiProvider: Permite combinar múltiples Providers en un solo widget, simplificando la estructura del árbol de widgets.

Consumer Widgets: Accediendo al Estado

Para que los widgets puedan acceder al estado proporcionado por un Provider, necesitamos usar los Consumer Widgets. Estos widgets “escuchan” las notificaciones de los Providers y se reconstruyen cuando el estado cambia.

Consumer recibe tres argumentos:

  • builder: Una función que construye el widget en función del estado proporcionado.
  • child: Un widget hijo que no necesita reconstruirse cuando el estado cambia (optimización).
  • listen: Un booleano que indica si el widget debe “escuchar” las notificaciones del Provider (por defecto es true).

Dart

Consumer<ContadorModel>(
  builder: (context, contador, child) {
    return Text('Valor del contador: ${contador.contador}');
  },
)

Provider.of: Obteniendo la Instancia del Provider

En ocasiones, necesitamos acceder a la instancia del Provider directamente, sin usar un Consumer. Para ello, podemos usar Provider.of<T>(context).

Dart

final contador = Provider.of<ContadorModel>(context);

Selector: Optimizando el Consumo de Datos

Selector es una versión especializada de Consumer que permite filtrar los datos del Provider antes de reconstruir el widget. Esto es útil cuando solo nos interesa una parte específica del estado.

Dart

Selector<ContadorModel, bool>(
  selector: (context, contador) => contador.contador > 10,
  builder: (context, esMayorQueDiez, child) {
    return Text(esMayorQueDiez ? '¡El contador es grande!' : '');
  },
)

En este ejemplo, el widget solo se reconstruirá cuando el valor de contador.contador cambie de ser mayor o menor que 10.

Con estos conceptos básicos, ya estás listo para empezar a usar Provider en tus aplicaciones Flutter. En las siguientes secciones, veremos ejemplos prácticos y buenas prácticas para aprovechar al máximo este poderoso paquete.

3. Provider en Acción: Ejemplos Prácticos

¡Es hora de poner manos a la obra! Veamos cómo aplicar los conceptos que hemos aprendido con ejemplos concretos que ilustran el poder de Provider en diferentes escenarios.

Ejemplo 1: Contador Simple con ChangeNotifierProvider

Empecemos con un ejemplo clásico: un contador que podemos incrementar con un botón.

Primero, definimos nuestro modelo ContadorModel extendiendo ChangeNotifier:

Dart

class ContadorModel extends ChangeNotifier {
  int _contador = 0;

  int get contador => _contador;

  void incrementar() {
    _contador++;
    notifyListeners();
  }
}

Luego, en nuestro widget principal, envolvemos la aplicación con un ChangeNotifierProvider para proporcionar una instancia de ContadorModel:

Dart

void main() {
  runApp(
    ChangeNotifierProvider(
      create: (context) => ContadorModel(),
      child: MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Ejemplo Contador'),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Consumer<ContadorModel>(
                builder: (context, contador, child) {
                  return Text(
                    '${contador.contador}',
                    style: TextStyle(fontSize: 40),
                  );
                },
              ),
              ElevatedButton(
                onPressed: () {
                  Provider.of<ContadorModel>(context, listen: false)
                      .incrementar();
                },
                child: Text('Incrementar'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

En este ejemplo, Consumer se encarga de mostrar el valor actual del contador y reconstruir el widget Text cada vez que se llama a incrementar().

Ejemplo 2: Lista de Tareas con ChangeNotifierProvider

Vamos a crear una lista de tareas que podemos agregar y marcar como completadas.

Primero, definimos nuestro modelo Tarea:

Dart

class Tarea {
  String nombre;
  bool completada;

  Tarea({required this.nombre, this.completada = false});

  void toggleCompletada() {
    completada = !completada;
  }
}

Luego, creamos un modelo ListaTareasModel que extiende ChangeNotifier y maneja la lista de tareas:

Dart

class ListaTareasModel extends ChangeNotifier {
  List<Tarea> _tareas = [];

  List<Tarea> get tareas => _tareas;

  void agregarTarea(String nombre) {
    _tareas.add(Tarea(nombre: nombre));
    notifyListeners();
  }

  void toggleTarea(int indice) {
    _tareas[indice].toggleCompletada();
    notifyListeners();
  }
}

Finalmente, integramos el modelo en nuestro widget:

Dart

// ... (código similar al ejemplo anterior)

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Lista de Tareas'),
        ),
        body: Consumer<ListaTareasModel>(
          builder: (context, listaTareas, child) {
            return ListView.builder(
              itemCount: listaTareas.tareas.length,
              itemBuilder: (context, indice) {
                final tarea = listaTareas.tareas[indice];
                return ListTile(
                  title: Text(tarea.nombre),
                  trailing: Checkbox(
                    value: tarea.completada,
                    onChanged: (valor) {
                      listaTareas.toggleTarea(indice);
                    },
                  ),
                );
              },
            );
          },
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: () {
            // Mostrar un diálogo para agregar una nueva tarea
          },
          child: Icon(Icons.add),
        ),
      ),
    );
  }
}

Ejemplo 3: Formulario de Inicio de Sesión con FutureProvider

Para simular una llamada a una API, usaremos FutureProvider para manejar el estado de un formulario de inicio de sesión.

Dart

Future<bool> iniciarSesion(String usuario, String contraseña) async {
  // Simulamos una llamada a una API con un delay
  await Future.delayed(Duration(seconds: 2));
  if (usuario == 'flutter' && contraseña == 'provider') {
    return true;
  } else {
    return false;
  }
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Inicio de Sesión'),
        ),
        body: Center(
          child: FutureProvider<bool>(
            create: (context) => iniciarSesion('flutter', 'provider'),
            initialData: null,
            child: Consumer<bool>(
              builder: (context, resultado, child) {
                if (resultado == null) {
                  return CircularProgressIndicator();
                } else if (resultado) {
                  return Text('Inicio de sesión exitoso!');
                } else {
                  return Text('Credenciales incorrectas');
                }
              },
            ),
          ),
        ),
      ),
    );
  }
}

En este ejemplo, FutureProvider ejecuta la función iniciarSesion y proporciona el resultado (un booleano) a los widgets descendientes.

Estos son solo algunos ejemplos de cómo Provider puede simplificar la gestión de estado en tus aplicaciones Flutter. En la próxima sección, veremos algunas buenas prácticas para usar Provider de forma eficiente y mantener tu código limpio y organizado.

4. Buenas Prácticas con Provider

Para aprovechar al máximo Provider y escribir código limpio, mantenible y escalable, es fundamental seguir algunas buenas prácticas. Estas recomendaciones te ayudarán a organizar tu proyecto, optimizar el rendimiento y evitar errores comunes.

Estructura de Carpetas: Organización para la Claridad

A medida que tu aplicación crece, es crucial mantener tus Providers organizados. Una buena práctica es crear una carpeta dedicada (por ejemplo, “providers”) y subcarpetas para diferentes funcionalidades o módulos de tu aplicación.

providers/
  auth_provider.dart
  user_provider.dart
  product_provider.dart
  ...

Esta estructura facilita la localización y el mantenimiento de tus Providers.

Inyección de Dependencias: Modularidad y Pruebas

Provider facilita la inyección de dependencias, un patrón de diseño que promueve la modularidad y la testabilidad del código. En lugar de crear instancias de tus Providers directamente en los widgets, puedes inyectarlos a través de los constructores.

Dart

class MiWidget extends StatelessWidget {
  final MiProvider provider;

  MiWidget({required this.provider});

  // ...
}

Esto permite reemplazar fácilmente las dependencias en las pruebas y facilita el desarrollo de código modular.

Manejo de la Disposición: Previniendo Fugas de Memoria

Cuando trabajamos con recursos como Streams o animaciones, es importante liberar esos recursos cuando ya no se necesitan. Los Providers que extienden ChangeNotifier tienen un método dispose() que se llama automáticamente cuando el Provider es eliminado del árbol de widgets.

Dart

class MiProvider extends ChangeNotifier {
  // ...

  @override
  void dispose() {
    // Liberar recursos aquí (ej. cerrar streams)
    super.dispose();
  }
}

Combinando Providers: MultiProvider al Rescate

Si necesitas proporcionar múltiples valores a tus widgets, MultiProvider es tu aliado. En lugar de anidar múltiples Providers, puedes usar MultiProvider para agruparlos.

Dart

MultiProvider(
  providers: [
    ChangeNotifierProvider(create: (context) => Provider1()),
    ChangeNotifierProvider(create: (context) => Provider2()),
    // ...
  ],
  child: MiAplicacion(),
)

Esto simplifica la estructura de tu árbol de widgets y mejora la legibilidad del código.

Pruebas Unitarias: Asegurando la Calidad del Código

Escribir pruebas unitarias para tus Providers es fundamental para asegurar la calidad de tu código. Puedes usar el paquete provider_test para facilitar las pruebas.

Dart

import 'package:flutter_test/flutter_test.dart';
import 'package:provider/provider.dart';
import 'package:mi_app/providers/mi_provider.dart';

void main() {
  group('MiProvider', () {
    test('valor inicial es correcto', () {
      final provider = MiProvider();
      expect(provider.valor, 0);
    });

    test('incrementar valor funciona correctamente', () {
      final provider = MiProvider();
      provider.incrementar();
      expect(provider.valor, 1);
    });
  });
}

Siguiendo estas buenas prácticas, podrás escribir código Flutter más limpio, eficiente y fácil de mantener con Provider.

5. Provider y la Navegación

En aplicaciones Flutter que constan de múltiples pantallas, es esencial poder compartir y actualizar el estado a través de las diferentes rutas. Provider, con su enfoque flexible, se integra perfectamente con la navegación en Flutter, facilitando la gestión de datos entre pantallas.

Pasando Datos entre Pantallas con Provider

Una forma común de pasar datos entre pantallas es utilizando el constructor de la siguiente pantalla. Sin embargo, con Provider, podemos acceder al estado desde cualquier widget en el árbol, lo que simplifica el proceso.

Imaginemos una aplicación con dos pantallas: una lista de productos y una pantalla de detalles del producto.

Dart

// Pantalla de la lista de productos
class ListaProductos extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Consumer<ListaProductosModel>(
      builder: (context, listaProductos, child) {
        return ListView.builder(
          itemCount: listaProductos.productos.length,
          itemBuilder: (context, indice) {
            final producto = listaProductos.productos[indice];
            return ListTile(
              title: Text(producto.nombre),
              onTap: () {
                Navigator.push(
                  context,
                  MaterialPageRoute(
                    builder: (context) => DetalleProducto(producto: producto),
                  ),
                );
              },
            );
          },
        );
      },
    );
  }
}

// Pantalla de detalles del producto
class DetalleProducto extends StatelessWidget {
  final Producto producto;

  DetalleProducto({required this.producto});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(producto.nombre),
      ),
      body: Center(
        child: Text(
          'Detalles de ${producto.nombre}',
          style: TextStyle(fontSize: 20),
        ),
      ),
    );
  }
}

En este ejemplo, al navegar a la pantalla DetalleProducto, pasamos el objeto producto como argumento. Sin embargo, si la información del producto está almacenada en un Provider, podríamos acceder a ella directamente desde la pantalla DetalleProducto sin necesidad de pasarla como argumento.

Actualizando el Estado en Diferentes Rutas

Provider permite actualizar el estado desde cualquier pantalla y reflejar los cambios en todas las pantallas que estén escuchando ese Provider.

Por ejemplo, si en la pantalla DetalleProducto tenemos un botón para marcar el producto como “favorito”, podemos actualizar el estado en el Provider y la lista de productos se actualizará automáticamente.

Dart

// En la pantalla DetalleProducto
ElevatedButton(
  onPressed: () {
    Provider.of<ListaProductosModel>(context, listen: false)
        .marcarComoFavorito(producto);
  },
  child: Text('Marcar como favorito'),
)

Manejo de Estados Compartidos en la Navegación

En aplicaciones con navegación compleja, es común tener estados que necesitan ser compartidos entre varias pantallas. Provider facilita el manejo de estos estados, ya que podemos acceder al mismo Provider desde cualquier parte del árbol de widgets.

Por ejemplo, un carrito de compras puede ser representado por un Provider que se accede desde diferentes pantallas de la aplicación (lista de productos, detalles del producto, checkout, etc.).

Al utilizar Provider en conjunto con la navegación en Flutter, podemos crear aplicaciones más robustas, mantenibles y con una gestión de estado eficiente a través de las diferentes pantallas.

6. Preguntas y Respuestas sobre Provider

Provider suele generar algunas dudas, especialmente al inicio. Aquí te presentamos algunas de las preguntas más frecuentes que nos hacen sobre este poderoso paquete:

1. ¿Cuál es la diferencia entre Provider y setState()?

Aunque ambos se utilizan para actualizar la interfaz de usuario, setState() es un método propio de los StatefulWidget que reconstruye todo el widget cuando se llama. Provider, en cambio, permite una reconstrucción más precisa, actualizando solo los widgets que están escuchando un cambio específico en el estado. Esto mejora el rendimiento, especialmente en aplicaciones complejas.

2. ¿Cómo puedo acceder a un Provider desde un widget que no es un descendiente directo?

Existen varias opciones:

  • Provider.of<T>(context, listen: false): Permite acceder al Provider sin que el widget se redibuje cuando el estado cambie. Útil para acciones como llamar a un método del Provider.
  • Consumer<T>: Permite acceder al Provider y redibujar el widget cuando el estado cambie.
  • Selector<T, R>: Similar a Consumer, pero permite filtrar los datos del Provider y optimizar la reconstrucción del widget.
  • Pasar el Provider como argumento: En algunos casos, puedes pasar la instancia del Provider como argumento al constructor del widget.

3. ¿Cuándo debo usar Selector en lugar de Consumer?

Selector es ideal cuando solo necesitas una parte específica del estado proporcionado por el Provider. Al usar selector, puedes filtrar los datos y el widget solo se reconstruirá cuando esa parte específica del estado cambie, optimizando el rendimiento.

4. ¿Provider es adecuado para aplicaciones grandes y complejas?

¡Sí! Provider es escalable y se adapta a aplicaciones de cualquier tamaño. Su flexibilidad y la capacidad de combinar Providers con MultiProvider lo hacen ideal para proyectos complejos con múltiples flujos de datos y estados interconectados.

5. ¿Qué alternativas a Provider existen en Flutter?

Si bien Provider es una excelente opción, existen otras alternativas para la gestión de estado en Flutter:

  • BLoC (Business Logic Component): Una arquitectura que separa la lógica de negocio de la interfaz de usuario, utilizando Streams para la comunicación.
  • Riverpod: Una evolución de Provider que ofrece mejoras en la inyección de dependencias y la seguridad de tipos, previniendo errores comunes.
  • GetX: Un framework completo que ofrece gestión de estado, navegación, inyección de dependencias y otras funcionalidades.

La elección de la herramienta depende de las necesidades y preferencias de cada proyecto.

7. Puntos Relevantes

A lo largo de este artículo, hemos explorado en profundidad el poder de Provider para la gestión de estado en Flutter. Recapitulemos los puntos clave que debes recordar:

  • Simplificación: Provider simplifica la gestión de estado, evitando el uso excesivo de setState() y facilitando la comunicación entre widgets.
  • Flexibilidad: Ofrece diferentes tipos de Providers para adaptarse a diversas necesidades, desde valores simples hasta flujos de datos asíncronos.
  • Eficiencia: Optimiza el rendimiento al actualizar solo los widgets necesarios cuando el estado cambia.
  • Organización: Promueve la estructuración del código y la inyección de dependencias para una mejor mantenibilidad.
  • Navegación: Se integra fluidamente con la navegación en Flutter, permitiendo compartir y actualizar el estado entre diferentes pantallas.

Dominar Provider te permitirá construir aplicaciones Flutter más robustas, escalables y fáciles de mantener.

8. Conclusión: Dominando la Gestión de Estado con Provider

A lo largo de este viaje por el mundo de la gestión de estado en Flutter, hemos descubierto cómo Provider se alza como una herramienta poderosa y versátil para simplificar el desarrollo de aplicaciones. Desde los conceptos básicos hasta las buenas prácticas y la integración con la navegación, hemos explorado las diferentes facetas de este paquete que ha conquistado el corazón de la comunidad Flutter.

Recordemos que una gestión de estado eficiente es crucial para construir aplicaciones robustas, escalables y fáciles de mantener. Provider, con su enfoque intuitivo y sus mecanismos optimizados, nos permite controlar el flujo de datos en nuestras aplicaciones, evitando la complejidad y promoviendo la claridad del código.

Si bien hemos cubierto los aspectos esenciales de Provider, el camino del aprendizaje continúa. Te invitamos a seguir explorando este paquete, experimentando con sus diferentes funcionalidades y aplicándolo en tus propios proyectos. Recuerda que la práctica es clave para dominar cualquier herramienta.

¡No te detengas aquí! El ecosistema Flutter ofrece un abanico de opciones para la gestión de estado. Explora alternativas como BLoC y Riverpod, compara sus enfoques y elige la solución que mejor se adapte a tus necesidades y preferencias.

Con Provider en tu arsenal de herramientas, estarás mejor equipado para enfrentar los desafíos del desarrollo Flutter y crear aplicaciones que no solo sean visualmente atractivas, sino también eficientes y fáciles de mantener. ¡Sigue aprendiendo, sigue creciendo y sigue construyendo aplicaciones increíbles con Flutter!

9. Recursos Adicionales

Para aquellos que deseen profundizar en el mundo de Provider y la gestión de estado en Flutter, aquí les presentamos una selección de recursos que les serán de gran utilidad:

Documentación Oficial:

  • Provider: La fuente principal de información, con explicaciones detalladas, ejemplos y API completa. pub.dev/packages/provider

Tutoriales y Artículos:

  • Flutter Provider – Tutorial Completo: Una guía paso a paso para aprender Provider con ejemplos prácticos. [se quitó una URL no válida]
  • Gestión de estados en Flutter, Provider: Un artículo que explica los fundamentos de Provider con ejemplos sencillos. dev.to/marcelo/manejo-de-estados-en-flutter-provider-5dkd
  • Domina la gestión de estados en Flutter con Provider! 🛠️ (Vídeo + Código fuente): Un recurso con video y código fuente para un aprendizaje interactivo. [se quitó una URL no válida]

Alternativas a Provider:

  • BLoC: Documentación oficial del paquete flutter_bloc. pub.dev/packages/flutter_bloc
  • Riverpod: Documentación oficial de Riverpod, con ejemplos y tutoriales. riverpod.dev
  • GetX: Sitio web oficial de GetX, con documentación, ejemplos y comunidad. getx.dev [se quitó una URL no válida]

Comunidades:

  • r/FlutterDev (Reddit): Una comunidad activa de desarrolladores Flutter donde puedes hacer preguntas y compartir tus experiencias. [se quitó una URL no válida]

¡Esperamos que estos recursos te inspiren a seguir aprendiendo y mejorando tus habilidades en el desarrollo Flutter!

10. Siguientes Pasos

¡Felicidades por llegar hasta aquí! Ahora que ya tienes un buen entendimiento de Provider, es momento de seguir expandiendo tus habilidades en la gestión de estado en Flutter. Aquí te presentamos algunas sugerencias para continuar tu aprendizaje:

1. Profundiza en StreamProvider: Explora el uso de StreamProvider para manejar flujos de datos en tiempo real. Puedes crear aplicaciones que reaccionen a eventos como actualizaciones de ubicación, notificaciones o cambios en una base de datos.

2. Explora la arquitectura BLoC: Adéntrate en el mundo de BLoC (Business Logic Component), una arquitectura popular que separa la lógica de negocio de la interfaz de usuario. Aprende cómo usar Streams y Sinks para comunicar eventos y estados entre diferentes partes de tu aplicación.

3. Aprende sobre Riverpod: Descubre Riverpod, una evolución de Provider que ofrece mejoras en la inyección de dependencias y la seguridad de tipos. Compara sus ventajas con Provider y evalúa si se ajusta mejor a tus necesidades.

Con estas sugerencias, podrás continuar tu camino hacia el dominio de la gestión de estado en Flutter y construir aplicaciones cada vez más complejas y sofisticadas. ¡Sigue aprendiendo y explorando las infinitas posibilidades que ofrece este framework!

Deja un comentario

Scroll al inicio

Discover more from

Subscribe now to keep reading and get access to the full archive.

Continue reading