Migraciones de base de datos con Claude Code en producción
Guía práctica para usar Claude Code en migraciones DB: expand/contract, Prisma, CI, backfills y límites de rollback.
Una migración de base de datos en producción no debería empezar con un prompt vago como “cambia el schema”. El riesgo real no está solo en la sintaxis SQL. Está en el orden: despliegue de la aplicación, locks de la base de datos, backups, backfill de datos, feature flags, checks de CI y decisiones de rollback.
Claude Code es útil si lo usas como revisor y asistente de implementación. Es peligroso si le pides que genere una migración grande y la aplicas sin separar las fases. Esta guía está pensada para equipos que usan PostgreSQL y Prisma Migrate, aunque el mismo enfoque sirve para migraciones SQL manuales.
Ten las fuentes oficiales abiertas durante la revisión: documentación de Claude Code, PostgreSQL sobre explicit locking y ALTER TABLE, Prisma sobre development and production y CLI reference, y GitHub Actions workflow syntax.
Modelo expand/contract
El patrón principal es expand/contract. Expand significa ampliar la base de datos para que la versión antigua y la versión nueva de la aplicación puedan convivir. Contract significa quitar columnas antiguas, lecturas antiguas o restricciones temporales solo cuando la nueva ruta ya está estable en producción.
flowchart LR
A["Backup and review"]
B["Expand: add nullable column or new table"]
C["Deploy code with dual write or feature flag"]
D["Backfill data in small batches"]
E["Validate staging and production metrics"]
F["Contract: add NOT NULL, remove old path"]
A --> B --> C --> D --> E --> F
El error típico es pedirle a Claude Code que añada una columna, copie datos, marque NOT NULL y elimine la columna antigua en una sola migración. En una base local pequeña puede pasar. En una tabla users u orders con millones de filas puede causar locks largos, escrituras bloqueadas, timeouts o pérdida de datos.
Algunos términos conviene explicarlos. Un lock es el mecanismo por el que la base de datos evita cambios concurrentes incompatibles. Un backfill es un proceso que rellena valores nuevos para filas existentes. Una shadow database es una base temporal que Prisma usa en desarrollo para reproducir el historial de migraciones y detectar drift. No es una garantía de seguridad para producción.
Primero pide una revisión
El primer prompt debe pedir revisión, no cambios de archivos. Dale a Claude Code el tamaño de las tablas, la forma de despliegue, el ORM y las expectativas de rollback.
Review this database migration plan before editing files.
Context:
- Production database: PostgreSQL
- ORM: Prisma Migrate
- Hot tables: users has about 8 million rows, orders has about 25 million rows
- Deploy style: blue/green app deploy, database migration runs in CI/CD
- Requirement: split users.name into users.full_name and users.display_name
Check:
1. Can old and new app versions run at the same time?
2. Which SQL statements may take strong locks or scan the whole table?
3. Which steps must be expand, backfill, validate, and contract?
4. What backup or point-in-time recovery check is needed before deploy?
5. What can be rolled back by app deploy, and what can only be rolled forward?
Return a migration plan first. Do not edit files yet.
La frase “Do not edit files yet” es importante. Claude Code puede avanzar muy rápido, pero una migración de base de datos necesita una pausa de diseño. Si la primera propuesta mezcla pasos peligrosos, pide una versión más conservadora.
Rewrite the plan so that no step drops a column, rewrites a large table, or sets NOT NULL before the backfill is verified. Include a staging rehearsal and a production abort condition.
Así conviertes a Claude Code en un revisor de migraciones. Tú conservas el criterio de riesgo, y el agente te ayuda a enumerar archivos, comandos, SQL, checks de CI y señales de monitoreo.
SQL de expand
Supongamos que users.name se va a dividir en full_name y display_name. La migración de expand solo añade columnas nullable y un índice. No hace backfill, no añade NOT NULL y no elimina users.name.
-- 20260602090000_expand_users_names.sql
-- Keep this migration small. Do not backfill and do not drop users.name here.
ALTER TABLE users
ADD COLUMN full_name text,
ADD COLUMN display_name text;
-- Run outside a transaction in PostgreSQL migration tools that support it.
-- CREATE INDEX CONCURRENTLY cannot run inside a transaction block.
CREATE INDEX CONCURRENTLY IF NOT EXISTS users_display_name_idx
ON users (display_name);
La documentación de PostgreSQL indica que ALTER TABLE puede tomar distintos niveles de lock según el subcomando. Si no hay una nota que indique un lock más ligero, revisa el cambio de forma conservadora. No le pidas a Claude Code que adivine el riesgo; pídele que lo contraste con la documentación oficial.
Con Prisma, genera la migración sin aplicarla y revisa el SQL.
npx prisma migrate dev --name expand-users-names --create-only
npx prisma validate
La guía de Prisma indica que en test y producción se usa npx prisma migrate deploy. También explica que migrate deploy aplica migraciones pendientes, pero no detecta drift y no usa shadow database. Por eso un deploy correcto no equivale a una prueba completa de producción.
npx prisma migrate deploy
Cambiar la aplicación sin romper versiones
Después de expand, la aplicación debe tolerar las dos formas del schema. Las lecturas necesitan fallback y las escrituras deben rellenar los campos antiguos y nuevos durante el periodo de transición.
// src/domain/userName.ts
type UserNameRow = {
name: string | null;
fullName: string | null;
displayName: string | null;
};
export function readDisplayName(user: UserNameRow): string {
return user.displayName ?? user.fullName ?? user.name ?? "Unknown user";
}
export function buildNameUpdate(input: { name: string }) {
const normalized = input.name.trim().replace(/\s+/g, " ");
return {
name: normalized,
fullName: normalized,
displayName: normalized.length > 40 ? `${normalized.slice(0, 39)}...` : normalized,
};
}
Una feature flag separa el cambio de base de datos del cambio visible para usuarios. Mantén la ruta nueva apagada mientras haces backfill, enciéndela después de validar datos, y apágala si aparecen errores. Para la implementación, revisa la guía interna de feature flags con Claude Code.
Este patrón cubre varios casos reales: renombrar o dividir campos de perfil, añadir columnas calculadas como saldo de factura o etiqueta de búsqueda, y añadir índices o foreign keys a tablas con mucho tráfico. En todos los casos conviene separar expand, despliegue de aplicación, backfill, validación y contract.
Backfill en lotes pequeños
Un UPDATE gigante puede aumentar locks, WAL, retraso de réplicas y riesgo operativo. Pídele a Claude Code un script reiniciable por lotes.
// scripts/backfill-user-names.mjs
import pg from "pg";
const { Client } = pg;
const batchSize = Number(process.env.BATCH_SIZE ?? 1000);
const sleepMs = Number(process.env.SLEEP_MS ?? 200);
const client = new Client({ connectionString: process.env.DATABASE_URL });
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
await client.connect();
try {
let total = 0;
while (true) {
const result = await client.query(
`
WITH target AS (
SELECT id, name
FROM users
WHERE full_name IS NULL
AND name IS NOT NULL
ORDER BY id
LIMIT $1
FOR UPDATE SKIP LOCKED
)
UPDATE users AS u
SET
full_name = target.name,
display_name = CASE
WHEN length(target.name) > 40 THEN substring(target.name from 1 for 39) || '...'
ELSE target.name
END
FROM target
WHERE u.id = target.id
RETURNING u.id
`,
[batchSize],
);
total += result.rowCount;
console.log(`updated=${result.rowCount} total=${total}`);
if (result.rowCount === 0) break;
await sleep(sleepMs);
}
} finally {
await client.end();
}
Antes de usarlo, revisa idempotencia, fallo parcial, ejecución paralela y controles operativos. Un buen backfill tiene tamaño de lote, pausa, logs, condición de parada y una consulta que se pueda volver a ejecutar sin corromper datos.
CI y staging
CI debe aplicar el historial completo de migraciones sobre una base temporal. En GitHub Actions, los workflows viven en .github/workflows y se escriben en YAML.
name: migration-check
on:
pull_request:
paths:
- "prisma/**"
- "scripts/backfill-*.mjs"
- ".github/workflows/migration-check.yml"
jobs:
prisma-migrations:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: app
ports:
- "5432:5432"
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
env:
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/app?schema=public
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "22"
cache: npm
- run: npm ci
- run: npx prisma validate
- run: npx prisma migrate deploy
- run: npx prisma migrate status
- name: Detect schema drift after migrations
run: |
npx prisma migrate diff \
--exit-code \
--from-config-datasource \
--to-schema=prisma/schema.prisma
El ejemplo usa argumentos de Prisma ORM v7 basados en config datasource. No copies flags antiguos como --from-url o --shadow-database-url sin verificar la referencia actual.
Staging debe parecerse más a producción que CI: volumen de filas similar, índices similares, timeouts parecidos y el mismo runner de migraciones. Pide a Claude Code una checklist de ensayo con lock waits, replication lag, latencia, logs de aplicación y condiciones de aborto.
Contract y límites de rollback
Solo haz contract cuando la nueva aplicación esté estable y el backfill esté validado. Para NOT NULL, valida primero con una constraint.
-- 20260602120000_contract_users_names.sql
-- Run only after the new application version has been stable in production.
ALTER TABLE users
ADD CONSTRAINT users_full_name_present
CHECK (full_name IS NOT NULL) NOT VALID;
ALTER TABLE users
VALIDATE CONSTRAINT users_full_name_present;
ALTER TABLE users
ALTER COLUMN full_name SET NOT NULL;
ALTER TABLE users
DROP CONSTRAINT users_full_name_present;
-- Drop old columns in a later deploy, not in the same deploy that changes reads.
-- ALTER TABLE users DROP COLUMN name;
El peor malentendido es creer que un down migration restaura todo. Columnas eliminadas, valores sobrescritos y conversiones con pérdida no vuelven por ejecutar SQL inverso. Lo que normalmente puedes revertir es la versión de la aplicación o una feature flag. La base de datos requiere backup, point-in-time recovery o una corrección hacia adelante.
prisma migrate resolve --rolled-back tampoco deshace una migración exitosa. Sirve para resolver el estado del historial cuando una migración falla. Pide a Claude Code que separe “rollback de app”, “fix hacia adelante en DB” y “restauración de datos requerida”.
Fallos comunes y flujo de equipo
El primer fallo es tratar un rename como drop and add. El ORM no siempre sabe tu intención, así que revisa el SQL generado. El segundo es mezclar cambio de schema y reescritura masiva de datos. El tercero es confiar demasiado en shadow database: ayuda en desarrollo, pero no simula distribución real de datos, bloat, colas de locks ni retraso de réplicas. El cuarto es saltarse la verificación de backups.
Escribe reglas de base de datos en CLAUDE.md: no borrar columnas de tablas calientes en el mismo PR, no hacer backfills grandes dentro de migrations de schema, crear migraciones Prisma con --create-only para revisión SQL y añadir enlaces oficiales en la revisión. Para la estructura, usa la guía de CLAUDE.md best practices.
En SaaS con ingresos reales, una mala migración puede parar facturación, altas de usuarios y soporte. Los productos de ClaudeCodeLab incluyen prompts y checklists reutilizables, y la formación de Claude Code ayuda a instalar este flujo en un repositorio real.
En pruebas prácticas, la mejora más grande vino de reducir lo que cada migración intenta hacer. Un PR solo de expand, un job separado de backfill y un PR posterior de contract hacen que la salida de Claude Code sea más revisable y que la decisión humana sea más clara. Antes de pedir SQL, pide condiciones de aborto y pasos de recuperación.
Resumen
Claude Code no convierte automáticamente una migración de producción en algo seguro. Es útil cuando el equipo le da un modelo seguro: expand/contract, despliegue por etapas, backfills pequeños, CI, ensayo en staging, feature flags y límites explícitos de rollback.
Para tu próxima migración, pide primero una revisión de riesgos. Si el plan sobrevive, entonces deja que Claude Code escriba SQL, Prisma migration, workflow de GitHub Actions y script de backfill.
PDF gratis: cheatsheet de Claude Code
Introduce tu email y descarga una hoja con comandos, hábitos de revisión y flujos seguros.
Cuidamos tus datos y no enviamos spam.
Sobre el autor
Masa
Ingeniero enfocado en workflows prácticos con Claude Code.
Artículos relacionados
Workflow de Obsidian a CLAUDE.md con Claude Code
Convierte notas de trabajo de Obsidian en notas operativas de CLAUDE.md para no repetir contexto.
Claude Code Revenue CTA Routing: de artículos a PDF, Gumroad y consulta
Un flujo con Claude Code para dirigir lectores a PDF gratis, Gumroad o consulta según intención.
Reglas de handoff para equipos con Claude Code: evidencia, permisos, rollback e ingresos
Formato práctico para entregar trabajo de Claude Code con pruebas, permisos, rollback, PDF gratis, Gumroad y consulta.