Solución de problemas
Lo que tienes es un síntoma: un mensaje de error o algo que no pasa. Así está ordenada esta página. Busca el tuyo, y debajo tienes la causa y el arreglo.
Casi todos salen de la configuración, no del código, y casi todos se arreglan con una línea.
«No migrations to apply!» y las migraciones están ahí
Sección titulada ««No migrations to apply!» y las migraciones están ahí»Síntoma. wrangler d1 migrations apply —o cms db:apply, que lo llama por dentro— dice que
no hay nada que aplicar, con el directorio migrations/ lleno de carpetas delante.
Causa. Falta migrations_pattern en tu wrangler.jsonc. drizzle-kit 1.x escribe
migrations/<fecha>_<nombre>/migration.sql, un nivel más hondo que el migrations/*.sql que wrangler
busca por defecto. Wrangler mira donde le dijeron y no encuentra nada, que es la verdad desde su punto
de vista.
Arreglo. Una línea en el bloque de D1:
{ "d1_databases": [ { "binding": "DB", "database_name": "mi-sitio", "database_id": "<id>", "migrations_dir": "migrations", "migrations_pattern": "migrations/*/migration.sql" } ]}Ver Instalación.
«No hay ninguna migración que aplicar»
Sección titulada ««No hay ninguna migración que aplicar»»Síntoma. Este mensaje, que es de la CLI y no de wrangler:
No hay ninguna migración que aplicar. Ejecuta `cms db:migrate` para generarla.Causa. Distinta de la anterior: aquí el directorio migrations/ está vacío de verdad. Has
lanzado db:apply antes de generar nada.
Arreglo. Son dos comandos, siempre en este orden: db:migrate escribe el SQL, tú lo lees,
db:apply lo aplica.
pnpm cms db:migratepnpm cms db:applyEncadenarlos en un solo comando es justo lo que la CLI evita a propósito. Ver CLI.
db:migrate falla diciendo que drizzle-kit no está
Sección titulada «db:migrate falla diciendo que drizzle-kit no está»Síntoma.
drizzle-kit no está instalado y `cms db:migrate` lo necesita para generar la migración.Instálalo con `pnpm add -D drizzle-kit` (o el equivalente de tu gestor).Causa. drizzle-kit es quien escribe el SQL, y no viaja dentro del paquete: lo lanzamos como
subproceso para no arrastrarlo al node_modules de producción de todo el que instale el CMS.
Arreglo. Instálalo como dependencia de desarrollo, y añade su config si aún no la tienes:
pnpm add -D drizzle-kitimport { defineConfig } from 'drizzle-kit'
export default defineConfig({ dialect: 'sqlite', schema: './.cms/schema.ts', out: './migrations',})Ver Schema y migraciones.
env.KV es undefined y el binding estaba declarado
Sección titulada «env.KV es undefined y el binding estaba declarado»Síntoma. El sitio arranca, pero todo lo que vive en KV —los ajustes del
sitio— falla. Tu wrangler.jsonc declara KV y aun así no está.
Causa. Falta SESSION en kv_namespaces. Ese namespace no es del CMS: es de
@astrojs/cloudflare, que guarda ahí las sesiones de Astro. Cuando no lo encuentra, el adaptador
sustituye el array entero por el suyo, y KV desaparece sin un solo aviso.
Arreglo. Declara los dos, aunque tú solo uses uno:
{ "kv_namespaces": [ { "binding": "SESSION", "id": "<id>" }, { "binding": "KV", "id": "<id>" } ]}Ver Instalación.
Un error de node:crypto al iniciar sesión
Sección titulada «Un error de node:crypto al iniciar sesión»Síntoma. El sitio arranca y el contenido se lee, pero cualquier ruta de autenticación revienta con un error de módulo de Node.
Causa. Falta la marca nodejs_compat. La autenticación es better-auth, y better-auth usa
node:crypto, que en el runtime de Workers no existe sin esa marca.
Arreglo.
{ "compatibility_flags": ["nodejs_compat"] }Ver Autenticación.
MISSING_EMAIL en la primera lectura de sesión
Sección titulada «MISSING_EMAIL en la primera lectura de sesión»Síntoma. El contenido y la API REST funcionan, pero en cuanto algo lee la sesión salta
MISSING_EMAIL. Nadie puede autenticarse.
Causa. El correo no está configurado, y la verificación de la dirección es obligatoria. El error
es perezoso —no tumba el arranque— y aparece cuando de verdad hace falta mandar un correo. Alguno de
estos falta: el binding send_email, un email.from no vacío, o
los tres requisitos de plataforma: plan Workers
de pago, dominio con DNS de Cloudflare dado de alta en Email Sending, y que from pertenezca a ese
dominio.
Arreglo. El propio mensaje enumera los tres requisitos, porque saber solo que «falta el binding»
manda a mirar al sitio equivocado dos de cada tres veces. En local basta con el binding y un from
cualquiera que no sea vacío:
{ "send_email": [{ "name": "EMAIL" }] }import { defineConfig } from '@kevolution-co/cms'
export default defineConfig({ email: { from: 'noreply@tudominio.com', siteName: 'Mi sitio' }, collections: [],})Ver Email.
El correo nunca llega a la bandeja
Sección titulada «El correo nunca llega a la bandeja»Síntoma. El flujo de verificación o de restablecimiento se completa sin errores, pero al buzón no llega nada.
Causa. En local es lo esperado, no un fallo: sin "remote": true, wrangler dev simula el
envío. Escribe el mensaje en un fichero temporal e imprime su ruta en la consola.
[wrangler:info] send_email binding called with MessageBuilder:From: noreply@example.comTo: kevin@example.comSubject: Verifica tu cuenta en Kevin CMSText: /tmp/miniflare-…/email-text/<id>.txtArreglo. Abre ese fichero y sigue el enlace: los flujos completos se desarrollan así, sin dominio
ni plan de pago. Si lo que quieres es mandar correo de verdad desde tu máquina, añade "remote": true
al binding —y cumple antes los tres requisitos de plataforma—:
{ "send_email": [{ "name": "EMAIL", "remote": true }] }Desplegado no hay simulación que valga: ahí el envío es real siempre. Ver Email y Despliegue.
401 en un GET /api/cms/<slug>
Sección titulada «401 en un GET /api/cms/<slug>»Síntoma.
{ "error": "UNAUTHORIZED", "message": "Esta operación sobre \"posts\" requiere una sesión iniciada"}Causa. Leer por la API REST exige sesión salvo que la colección diga lo contrario. Sin
access: { read: 'public' }, un GET sin cookie es 401.
Arreglo. Si esa colección se lee sin sesión —un blog, un catálogo—, decláralo:
defineCollection({ slug: 'posts', access: { read: 'public' }, fields: [{ name: 'title', type: 'text', required: true }],})'public' es el único valor admitido; cualquier otro es un error de configuración al arrancar.
Y esto solo abre las lecturas: escribir sigue exigiendo sesión siempre.
Ver API REST.
Astro.locals.cms.collections.posts no está tipado
Sección titulada «Astro.locals.cms.collections.posts no está tipado»Síntoma. El editor no autocompleta tus colecciones, o posts sale como error de tipo, aunque
.cms/types.d.ts exista con todo dentro.
Causa. El fichero no está incluido en tu tsconfig.json. Un comodín no entra en un directorio
cuyo nombre empieza por punto, así que **/* no lo alcanza: hay que nombrarlo.
Arreglo.
{ "include": [ ".astro/types.d.ts", "./.cms/types.d.ts", "**/*" ]}La integración lo comprueba al arrancar y lo dice, pero como aviso y no como error: sin esa línea todo funciona, solo pierdes el tipado. Ver Integración de Astro.