Cambiar tema

Cloudflare D1 en la práctica: SQLite en el edge con replicación global

Easton editorial illustration: large SQLite database core on an abstract edge-map desk

0,01 milisegundos.

Ese es el tiempo que tarda SQLite en leer una fila en local. La misma consulta en Cloudflare D1 ronda los 0,5 ms, y PostgreSQL entre regiones puede ir de 1 a 3 ms — ¿parece poca diferencia? Si tus usuarios están en Tokio y la base de datos en Virginia, solo el viaje de red se come más de 100 ms.

El año pasado me quedé atascado con esto en un proyecto de despliegue global. Las bases tradicionales o aguantan alta latencia o exigen réplicas de lectura complejas. Hasta que Cloudflare presentó en Developer Week 2025 la replicación global de D1, y las cosas empezaron a encajar.

En este artículo veremos cómo D1 lleva SQLite al edge, qué significan en la práctica conceptos como Durable Objects, marcas de tiempo de Lamport y Sessions API, y cuándo conviene elegirlo o buscar otra vía.

1. Qué es D1: SQLite en el edge

En pocas palabras, D1 es SQLite desplegado en la red edge de Cloudflare, con lectura y escritura en más de 300 ciudades del mundo.

Si crees que es solo «SQLite + CDN», subestimas la ambición del producto. SQLite tradicional tiene límites serios en producción: un solo archivo difícil de distribuir, sin recuperación ante fallos integrada y escrituras que bloquean toda la base. D1 rediseña todo eso.

En qué se diferencia del SQLite clásico

Primero, la integración. D1 corre dentro de Cloudflare Workers; puedes consultar la base como si fuera una función más:

// wrangler.toml
[[d1_databases]]
binding = "DB"
database_name = "my-database"
database_id = "xxxx-xxxx-xxxx"

// Consulta en el Worker
export default {
  async fetch(request, env) {
    const { results } = await env.DB.prepare(
      "SELECT * FROM users WHERE id = ?"
    ).bind(1).all();
    return Response.json(results);
  }
}

Segundo, Time Travel. D1 guarda versiones históricas y puedes volver a cualquier punto en el tiempo — un lujo para SQLite. Cuentas gratuitas: 30 días; planes de pago, más.

Tercero, replicación global (la gran novedad de 2025). El nodo principal está en una región, pero las réplicas de lectura se sincronizan en todo el mundo. Un usuario en Singapur lee desde la réplica local: la latencia pasa de tres cifras a un dígito.

Pero también tiene límites duros

D1 no lo resuelve todo. Antes de elegir, conviene conocer estas restricciones:

Límite de 10 GB por base de datos. Si lo superas, hay que particionar o cambiar de solución. Hasta 50.000 bases por cuenta — suficiente para la mayoría de proyectos, pero si tu modelo es «una base por usuario», haz los números.

Arquitectura de un solo escritor. Solo un nodo escribe a la vez, así que el throughput de escritura tiene techo. En pruebas reales: unos 500-2000 writes/sec, lejos de los 10K-50K de PostgreSQL. Para escritura intensiva (pujas en tiempo real, pipelines de logs), D1 puede quedarse corto.

Consistencia secuencial, no fuerte. Lo detallamos más adelante: lo que acabas de escribir puede no verse al leer al instante — salvo que uses bien Sessions API.

D1 encaja mejor en aplicaciones web con muchas lecturas y pocas escrituras. La mayoría de sitios supera el 90 % de operaciones de lectura; la replicación global hace que esas solicitudes respondan desde el nodo cercano, con mejora real de experiencia.

2. Arquitectura de D1: Durable Objects y replicación global

Este capítulo es más técnico, pero si quieres sacarle partido a D1, estos conceptos no se evitan.

Durable Objects: un «mayordomo» por base de datos

El núcleo de D1 son los Durable Objects. Imagina un proceso dedicado por base de datos que se encarga de:

  1. Garantizar unicidad global: todas las escrituras pasan por él; no hay conflictos por dos modificaciones simultáneas
  2. Mantener el log de transacciones: cada escritura queda registrada para recuperación y sincronización de réplicas
  3. Coordinar réplicas de lectura: avisa a las réplicas del mundo cuándo actualizarse

El diseño es ingenioso. Las bases distribuidas clásicas coordinan muchos nodos; la latencia y los fallos complican todo. D1 apuesta por un nodo principal: las escrituras hacen cola, se procesan en orden y luego se replican de forma asíncrona.

Snapshot Isolation: las lecturas no bloquean

Cuando ejecutas un SELECT en D1, no haces cola en el nodo principal: lees una «instantánea» de la réplica más cercana.

¿Qué implica? Si el principal está en Pekín y hay réplicas en Tokio, Singapur y Sídney, un usuario en Tokio lee desde la réplica local el estado en ese instante. La instantánea se fija al iniciar la consulta; aunque el principal esté escribiendo, tu lectura no se bloquea.

Pero hay un problema: si acabas de escribir y lees al momento, puede que no veas el dato — la réplica aún no se sincronizó.

Por eso existe Sessions API.

Marcas de tiempo de Lamport: dar sentido al orden

Leslie Lamport propuso en 1978 un método para ordenar eventos en sistemas distribuidos: las marcas de tiempo de Lamport. La idea es simple: cada evento tiene un reloj lógico; los posteriores tienen marca mayor.

D1 usa este mecanismo para «consistencia secuencial»: en una sesión, si escribes y luego lees, verás datos al menos tan recientes como tu escritura, no una réplica antigua.

¿Cómo? Tras cada escritura, D1 devuelve un «marcador» (commit token). Es un punto de referencia: «todo lo anterior a este punto ya está aplicado». En la siguiente consulta con ese marcador, los datos serán al menos tan nuevos como ese punto.

Usuario → escribe pedido → obtiene commit token "abc123"
Usuario → consulta pedido (con token "abc123") → ve el dato recién escrito

Cómo funciona la replicación global

Al crear una base D1 eliges una «región principal» (primary location). Por defecto, el centro de datos Cloudflare más cercano; también puedes indicarla manualmente.

Flujo de escritura:

  1. La solicitud llega al nodo edge cercano
  2. Se enruta al Durable Object de la región principal
  3. Se escribe en el archivo principal
  4. Replicación asíncrona a réplicas de cada región

Flujo de lectura:

  1. La solicitud llega al nodo edge cercano
  2. Se lee desde la réplica de esa región
  3. Si la lectura va con sesión, se respeta el marcador de consistencia

Cloudflare dice que la replicación global no tiene costo extra — razonable, porque el tráfico de datos no es barato. Pero las escrituras siguen yendo al principal: si está en EE. UU. y tus usuarios en Asia, notarán latencia al escribir.

3. Sessions API en la práctica: consistencia secuencial en código

Teoría aparte, veamos código.

Sessions API es la novedad de 2025 de D1 para el problema «escribir y leer después». Si conoces la consistencia causal de MongoDB o follower reads de CockroachDB, la idea es similar: un marcador que sigue la causalidad.

Uso básico

// Crear una Session
const session = env.DB.withSession();

// Lectura normal, enrutada a la réplica cercana
const { results } = await session.prepare(
  "SELECT * FROM products WHERE category = ?"
).bind("electronics").all();

// Escritura, enrutada al nodo principal
await session.prepare(
  "INSERT INTO orders (user_id, product_id, quantity) VALUES (?, ?, ?)"
).bind(userId, productId, 2).run();

// Obtener el marcador de consistencia de la sesión
const bookmark = session.latestCommitToken;

La clave es withSession(): crea un contexto de sesión donde todas las operaciones comparten la misma vista de consistencia.

Tres modos de consistencia

Sessions API ofrece tres modos según el escenario:

1. first-unconstrained (predeterminado)

const session = env.DB.withSession("first-unconstrained");

El más relajado. La lectura usa la réplica cercana sin exigir que esté al día. Ideal para listados de productos, blogs y contenido donde la frescura no es crítica.

2. first-primary

const session = env.DB.withSession("first-primary");

La primera lectura va al nodo principal; las siguientes usan réplicas. Garantiza al menos el estado al crear la sesión. Útil cuando necesitas ver datos recién escritos sin consultar siempre el principal.

3. Continuar la sesión con un marcador

// Obtener el marcador anterior del encabezado
const previousToken = request.headers.get("x-d1-token") ?? "first-unconstrained";

// Crear sesión continuando la anterior
const session = env.DB.withSession(previousToken);

// Ejecutar operaciones...

// Devolver el nuevo marcador
response.headers.set("x-d1-token", session.latestCommitToken);

El modo más potente. Guarda el marcador en el cliente (cookie o encabezado) y envíalo en cada solicitud para mantener consistencia entre peticiones.

Caso práctico: e-commerce global

Imagina una tienda global. Al navegar productos quieres la réplica cercana y mínima latencia. Tras un pedido, al ver el pedido debe aparecer lo que acabas de comprar.

export default {
  async fetch(request, env) {
    const url = new URL(request.url);

    // Obtener session token del encabezado (null en la primera solicitud)
    const token = request.headers.get("x-d1-token") ?? "first-unconstrained";
    const session = env.DB.withSession(token);

    // Caso 1: listar productos (sin consistencia fuerte)
    if (url.pathname === "/api/products") {
      const { results } = await session.prepare(
        "SELECT * FROM products WHERE status = ?"
      ).bind("active").all();

      return new Response(JSON.stringify(results), {
        headers: {
          "Content-Type": "application/json",
          "x-d1-token": session.latestCommitToken
        }
      });
    }

    // Caso 2: crear pedido (escritura, enrutada al principal)
    if (url.pathname === "/api/orders" && request.method === "POST") {
      const body = await request.json();

      await session.prepare(`
        INSERT INTO orders (user_id, total_amount, status)
        VALUES (?, ?, ?)
      `).bind(body.userId, body.total, "pending").run();

      // Consultar justo después de escribir
      const order = await session.prepare(`
        SELECT * FROM orders WHERE user_id = ?
        ORDER BY created_at DESC LIMIT 1
      `).bind(body.userId).first();

      return new Response(JSON.stringify(order), {
        headers: {
          "Content-Type": "application/json",
          "x-d1-token": session.latestCommitToken  // Devolver nuevo token
        }
      });
    }

    // Caso 3: detalle del pedido (token asegura consistencia)
    if (url.pathname.startsWith("/api/orders/")) {
      const orderId = url.pathname.split("/")[3];

      // Si el usuario acaba de pedir, el token garantiza datos recientes
      const order = await session.prepare(
        "SELECT * FROM orders WHERE id = ?"
      ).bind(orderId).first();

      return new Response(JSON.stringify(order), {
        headers: {
          "Content-Type": "application/json",
          "x-d1-token": session.latestCommitToken
        }
      });
    }
  }
}

Diseño práctico: first-unconstrained al navegar; tras el pedido, el cliente guarda el token para consultas posteriores.

Cómo colabora el cliente

En el frontend basta con guardar x-d1-token y enviarlo en cada solicitud.

// Ejemplo frontend
let d1Token = localStorage.getItem('d1-token') ?? 'first-unconstrained';

async function fetchProducts() {
  const response = await fetch('/api/products', {
    headers: { 'x-d1-token': d1Token }
  });
  d1Token = response.headers.get('x-d1-token');
  localStorage.setItem('d1-token', d1Token);
  return response.json();
}

async function createOrder(data) {
  const response = await fetch('/api/orders', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'x-d1-token': d1Token
    },
    body: JSON.stringify(data)
  });
  d1Token = response.headers.get('x-d1-token');
  localStorage.setItem('d1-token', d1Token);
  return response.json();
}

Poco código, gran impacto. Sin este mecanismo, tras pedir el usuario podría ver la lista de pedidos vacía — mala experiencia.

4. Benchmarks y comparación con competidores

Los números no mienten. Aquí va una comparación de opciones habituales, con datos de documentación oficial y pruebas de la comunidad.

Comparación de latencia

OpciónLatencia lectura (p50)Latencia lectura (p99)Latencia escritura (p50)Notas
D1~0,5 ms~2-5 ms~5-30 msLectura en réplica edge, escritura en principal
Turso~0,02 ms~0,1 ms~15-50 msLectura embebida, muy rápida
PlanetScale~3-8 ms~10-20 ms~3-8 msCompatible MySQL, lectura y escritura vía proxy
PostgreSQL (Neon)~3-10 ms~20-50 ms~1-5 msArquitectura clásica, cold start lento
0.5ms
Latencia lectura D1
p50, réplica edge
0.02ms
Latencia lectura Turso
Lectura embebida
500-2K
Throughput escritura D1
writes/sec
10GB
Límite por base
Particionar si se supera
Source: Documentación oficial y pruebas de la comunidad

Algunas observaciones:

La lectura de Turso es rapidísima. 0,02 ms, casi acceso a memoria local: SQLite embebido con el archivo en el nodo edge. Pero la sincronización cuesta más y la escritura puede ser más lenta.

D1 también lee muy bien. 0,5 ms es excelente para edge. La escritura sufre más: va al principal y la distancia física marca el mínimo. Principal en la costa oeste de EE. UU. y usuario en Singapur: al menos 30 ms de ida y vuelta.

PlanetScale y Neon encajan mejor en apps clásicas. Latencias menos llamativas que D1 o Turso, pero ecosistema maduro y SQL rico. Si necesitas procedimientos almacenados, triggers o muchos tipos de índice, son opciones sólidas.

Comparación de throughput

OpciónThroughput lectura (QPS)Throughput escritura (QPS)Notas
D110K-100K500-2KLímite por base de datos
TursoIlimitado (lectura local)Limitado por sincronizaciónCada nodo edge lee en local
PlanetScale10K-50K5K-20KEscala con sharding
PostgreSQL10K-100K10K-50KDepende del tamaño de instancia

El cuello de botella de D1 es la escritura. Un solo escritor fija el techo. Si necesitas más de 5000 escrituras por segundo, D1 puede frenarte: particionar complica el sistema, o cambias de solución.

Comparación de cuotas gratuitas

OpciónAlmacenamientoCuota lecturaCuota escrituraNotas
D15 GB25 mil millones filas/mes50 millones filas/mesLímite 10 GB/base
Turso9 GB1000 millones filas/mes25 millones filas/mesIncluye tráfico de replicación
PlanetScale1 GB10 mil millones filas/mes10 mil millones filas/mesSin límite de escritura
Neon0,5 GB100 millones unidades/mes100 millones unidades/mesUnidad = lectura o escritura

En cuota gratuita, D1 es generoso: 25 mil millones de lecturas bastan para proyectos personales y apps pequeñas. Ojo con escritura: 50 millones/mes ≈ 1,66 millones al día. Logs y telemetría intensiva pueden pasarse.

Modelo de facturación

D1 factura por uso, sin mínimo. Fuera de la cuota gratuita: $0,001 por millón de filas leídas, $0,10 por millón escritas. Almacenamiento: $0,75/GB/mes.

Turso mezcla «filas leídas» y «tráfico de replicación»; con datos que cambian mucho, la replicación puede encarecer.

PlanetScale cobra por lecturas y escrituras; escritura más barata que D1, lectura algo más cara.

Mi consejo: si ya usas Cloudflare (Workers, KV, R2), la facturación integrada de D1 simplifica el panorama. En un proyecto independiente, prueba las tres con datos reales.

5. Árbol de decisión: cuándo elegir D1

¿Conviene D1? Un árbol simple ayuda.

Escenarios donde D1 encaja

Aplicación con muchas lecturas. Sitios de contenido, e-commerce orientado a navegación, blogs, documentación. Más del 90 % de operaciones son lecturas; la replicación global baja la latencia a pocos milisegundos.

Usuarios repartidos por el mundo. Una base en una sola región obliga a usuarios lejanos a cruzar océanos. D1 acerca los datos al usuario.

Ya usas Cloudflare Workers. Integración nativa con D1; unas líneas de configuración. Sin pool de conexiones ni cold start de base de datos.

Datos por debajo de 10 GB. El límite por base es 10 GB; más allá, partición. Si tu modelo es «una base por inquilino», el límite importa menos.

Escenarios donde D1 no encaja

Escritura de alta frecuencia. Pujas en tiempo real, pipelines de logs, IoT — miles de escrituras por segundo. Mejor PostgreSQL, ClickHouse o TimescaleDB.

Transacciones complejas. D1 ofrece el nivel de transacción de SQLite; si necesitas aislamiento SERIALIZABLE, transacciones entre bases o procedimientos almacenados complejos, no basta.

Más de 10 GB de datos. Se puede particionar, pero sube la operación. Para series temporales o archivos de logs grandes, mejor otra opción desde el inicio.

Consistencia fuerte. D1 es eventualmente consistente: leer justo después de escribir puede no ver el dato nuevo (salvo Sessions API). Si en cualquier momento y lugar debe verse lo último, busca otra vía.

Migrar desde PostgreSQL

Si quieres pasar una app PostgreSQL a D1, anticipa esto:

1. Diferencias de dialecto SQL

SQLite no tiene algunas funciones de PostgreSQL:

  • Sin cláusula RETURNING (insertar y luego consultar)
  • Sin tipo SERIAL (usa INTEGER PRIMARY KEY AUTOINCREMENT)
  • Sin JSONB (guarda JSON en TEXT y usa json_extract())
  • Sin tipo ARRAY (tablas relacionadas)

2. Herramientas de migración

Cloudflare ofrece herramientas para exportar desde PostgreSQL e importar en D1:

# Exportar datos de PostgreSQL
pg_dump --format=insert mydb > dump.sql

# Importar en D1
npx wrangler d1 execute my-d1-database --file=dump.sql

Esquemas complejos pueden requerir ajustes manuales.

3. Cambio en el modelo de conexión

Las bases clásicas usan conexión larga; D1 son invocaciones sin estado. Tu ORM puede necesitar cambios, o SQL directo. Prisma tiene adaptador D1, aún en evolución.

Decisión rápida

Si aún dudas:

¿Tu app escribe más de 1000 veces/segundo?
├─ Sí → No elijas D1
└─ No
    └─ ¿Necesitas consistencia fuerte?
        ├─ Sí → No elijas D1 (o usa Sessions API)
        └─ No
            └─ ¿Datos > 10 GB?
                ├─ Sí → Evalúa con cuidado
                └─ No → D1 encaja bien

Resumen

El valor de D1 en tres ideas: despliegue en el edge con latencia de un dígito en milisegundos, arquitectura serverless sin operar infraestructura, y Sessions API que aborda la consistencia distribuida sin complejidad excesiva.

No es la respuesta para todo. Escritura intensiva, transacciones complejas o datos masivos siguen siendo terreno de PostgreSQL y bases especializadas. No hay bala de plata, solo compensaciones.

Si tienes usuarios globales, muchas lecturas y pocas escrituras, y ya usas Cloudflare Workers, D1 merece una prueba. Crear una base de prueba lleva minutos:

# Crear base de datos
npx wrangler d1 create my-first-db

# Crear tabla
npx wrangler d1 execute my-first-db --command="CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)"

# Insertar datos
npx wrangler d1 execute my-first-db --command="INSERT INTO users (name) VALUES ('test')"

Pruébalo y compara la latencia entre Tokio y una base en la costa oeste de EE. UU.; sabrás si encaja en tu proyecto.


"D1 es la base de datos SQLite en el edge de Cloudflare, con replicación global de lectura y experiencia serverless. Sessions API usa marcas de tiempo de Lamport para consistencia secuencial y resuelve el problema habitual de leer justo después de escribir en sistemas distribuidos."

Referencias

Inicio rápido con Cloudflare D1

Flujo completo desde crear la base de datos hasta lecturas consistentes con Sessions API

⏱️ Estimated time: 15 min

  1. 1

    Step 1: Crear una base de datos D1

    Usa wrangler CLI para crear la base de datos:

    ```bash
    npx wrangler d1 create my-first-db
    ```

    Tras la creación obtendrás un database_id; configúralo en wrangler.toml:

    ```toml
    [[d1_databases]]
    binding = "DB"
    database_name = "my-first-db"
    database_id = "your-database-id"
    ```
  2. 2

    Step 2: Crear tablas

    Ejecuta SQL para crear el esquema:

    ```bash
    npx wrangler d1 execute my-first-db --command="CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP)"
    ```

    También puedes ejecutar un archivo SQL por lotes:

    ```bash
    npx wrangler d1 execute my-first-db --file=./schema.sql
    ```
  3. 3

    Step 3: Usar Sessions API en un Worker

    Crea una conexión con sesión para consistencia lectura-tras-escritura:

    ```typescript
    export default {
    async fetch(request, env) {
    // Obtener el session token del encabezado de la solicitud
    const token = request.headers.get("x-d1-token") ?? "first-unconstrained";
    const session = env.DB.withSession(token);

    // Escribir datos
    await session.prepare("INSERT INTO users (name) VALUES (?)")
    .bind("test").run();

    // Leer asegurando consistencia
    const { results } = await session.prepare("SELECT * FROM users")
    .all();

    return new Response(JSON.stringify(results), {
    headers: { "x-d1-token": session.latestCommitToken }
    });
    }
    }
    ```
  4. 4

    Step 4: Configurar replicación global de lectura

    Especifica la región principal en wrangler.toml:

    ```toml
    [[d1_databases]]
    binding = "DB"
    database_name = "my-first-db"
    database_id = "your-database-id"
    primary_location_hint = "apne1" # Región de Tokio
    ```

    Códigos de región disponibles:
    - apne1: Tokio
    - sfo1: San Francisco
    - eur3: Fráncfort

FAQ

¿En qué se diferencia Cloudflare D1 de Turso?
Ambos son bases de datos SQLite en el edge, pero con arquitecturas distintas:

• D1: arquitectura de un solo escritor; las escrituras van al nodo principal, latencia de lectura ~0,5 ms, ideal para muchas lecturas y pocas escrituras
• Turso: lectura embebida, latencia aún menor (~0,02 ms), pero sincronización de datos más compleja

D1 destaca por integración nativa con Cloudflare Workers y mayor cuota gratuita (25 mil millones de filas leídas/mes); Turso destaca en rendimiento de lectura para escenarios sensibles a la latencia.
¿Cómo superar el límite de 10 GB por base de datos en D1?
Tres enfoques:

• Estrategia de partición: dividir por módulo de negocio, una base de datos por módulo
• Aislamiento por inquilino: una base de datos por inquilino; D1 admite hasta 50.000 bases de datos
• Almacenamiento híbrido: datos calientes en D1, datos fríos en R2 u otro almacenamiento de objetos

Si el volumen sigue creciendo por encima de 10 GB, conviene evaluar PlanetScale u otro PostgreSQL tradicional.
¿Cómo elegir entre los tres modos de Sessions API?
Según el escenario:

• first-unconstrained (predeterminado): listados de productos, blogs y contenido donde la frescura no es crítica
• first-primary: cuando necesitas ver datos recién escritos sin consultar siempre el nodo principal
• Modo commit token: pedidos de e-commerce, consulta de órdenes y consistencia entre solicitudes

En e-commerce: navegación con first-unconstrained; tras el pedido guarda el token y envíalo en solicitudes posteriores.
¿D1 sirve para escritura de alta frecuencia?
No demasiado. La arquitectura de un solo escritor limita el throughput a ~500-2000 writes/sec, muy por debajo de los 10K-50K de PostgreSQL.

Si tu aplicación tiene estas características, mejor otra opción:
• Sistemas de pujas en tiempo real
• Pipelines de logs y telemetría
• Recolección de datos IoT
• Más de 1000 escrituras por segundo

En esos casos encajan mejor PostgreSQL, ClickHouse o TimescaleDB.
¿Qué tener en cuenta al migrar de PostgreSQL a D1?
Principales diferencias:

• Dialecto SQL: SQLite no admite RETURNING, SERIAL, JSONB ni ARRAY
• Conexión: de conexión persistente a invocaciones sin estado
• ORM: Prisma tiene adaptador para D1, pero aún en evolución

Pasos de migración:
1. Exportar con pg_dump
2. Ajustar manualmente SQL incompatible
3. Importar con wrangler d1 execute

Prueba primero con un volumen pequeño antes de migrar todo.
¿La replicación global de D1 tiene costo adicional?
No. Cloudflare indica que la replicación global de lectura no tiene cargo extra; el costo de transferencia ya está incluido.

Pero ten en cuenta:
• Las escrituras siguen yendo al nodo principal; la latencia depende de la distancia física
• Si el principal está en EE. UU. y el usuario en Asia, la escritura puede superar 30 ms
• La cuota gratuita de lectura es alta (25 mil millones de filas/mes); escritura: 50 millones de filas/mes

Despliega el nodo principal donde esté concentrada tu audiencia para optimizar las escrituras.

14 min de lectura · Publicado el: 5 may 2026 · Actualizado el: 21 ago 2026

Comentarios

Inicia sesión con GitHub para dejar un comentario

Easton BlogEaston Blog